From f2dae5ac86191d97db4ec35db5fd59a283335414 Mon Sep 17 00:00:00 2001 From: Joseph Rushton Wakeling Date: Sat, 3 Feb 2018 00:05:15 +0100 Subject: [PATCH 1/2] Add standalone functions to generate code for --eval and --loop flags The `--eval` and `--loop` flags allow the user to specify small snippets of code that are placed inside the body of an auto-generated `main` that is written to a file and evaluated. The existing implementation uses two separate string concatenations for the two different cases. Besides the code duplication, this means that the resulting code generation cannot be tested. A further more subtle issue arises from the existing implementation's insistence on adding a closing `;` to the generated code. While this takes care of the careless user who forgets to write one themselves, it means that if the user does close their statements correctly, we end up with an empty statement on the end of the code to be evaluated. While some D compilers don't care about this, some object: https://github.com/dlang/tools/pull/297#issuecomment-362447334 ... meaning that `rdmd` will fail to successfully evaluate the code. This patch therefore implements two new code functions to generate the required code. The first, `innerEvalCode`, takes care of the innermost code (the body of either the `main` function for `--eval`, or the loop for `--loop`), adding a terminating `;` if one is not present, but not doing so if one is already in place. The second, `makeEvalCode`, takes two parameters: the `string[]` of code snippets to be evaluated, and a boolean `Flag` indicating whether or not this is the body of a loop (in other words, whether this code should be generated for the `--eval` flag or the `--loop` flag). Unittests have been provided with both to ensure that they produce the expected outcomes. --- rdmd.d | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/rdmd.d b/rdmd.d index 153ad2d..05b9bf7 100755 --- a/rdmd.d +++ b/rdmd.d @@ -17,7 +17,7 @@ import std.algorithm, std.array, core.stdc.stdlib, std.datetime, std.digest.md, std.exception, std.file, std.getopt, std.parallelism, std.path, std.process, std.range, std.regex, - std.stdio, std.string, std.typetuple; + std.stdio, std.string, std.typecons, std.typetuple; version (Posix) { @@ -839,6 +839,101 @@ import std.stdio, std.algorithm, std.array, std.ascii, std.base64, std.zlib; "; +/** +Joins together the code provided via an `--eval` or `--loop` +flag, ensuring a trailing `;` is added if not already provided +by the user + +Params: + eval = array of strings generated by the `--eval` + or `--loop` rdmd flags + +Returns: + string of code to be evaluated, corresponding to the + inner code of either the program or the loop +*/ +string innerEvalCode(string[] eval) +{ + import std.string : join, stripRight; + // assumeSafeAppend just to avoid unnecessary reallocation + string code = eval.join("\n").stripRight.assumeSafeAppend; + if (code.length > 0 && code[$ - 1] != ';') + code ~= ';'; + return code; +} + +unittest +{ + assert(innerEvalCode([`writeln("Hello!")`]) == `writeln("Hello!");`); + assert(innerEvalCode([`writeln("Hello!");`]) == `writeln("Hello!");`); + + // test with trailing whitespace + assert(innerEvalCode([`writeln("Hello!") `]) == `writeln("Hello!");`); + assert(innerEvalCode([`writeln("Hello!"); `]) == `writeln("Hello!");`); + + // test with multiple entries + assert(innerEvalCode([`writeln("Hello!"); `, `writeln("You!") `]) + == "writeln(\"Hello!\"); \nwriteln(\"You!\");"); + assert(innerEvalCode([`writeln("Hello!"); `, `writeln("You!"); `]) + == "writeln(\"Hello!\"); \nwriteln(\"You!\");"); +} + +/** +Formats the code provided via `--eval` or `--loop` flags into a +string of complete program code that can be written to a file +and then compiled + +Params: + eval = array of strings generated by the `--eval` or + `--loop` rdmd flags + loop = set to `Yes.loop` if this code comes from a + `--loop` flag, `No.loop` if it comes from an + `--eval` flag + +Returns: + string of code to be evaluated, corresponding to the + inner code of either the program or the loop +*/ +string makeEvalCode(string[] eval, Flag!"loop" loop) +{ + import std.format : format; + immutable codeFormat = importWorld + ~ "void main(char[][] args) {%s%s\n%s}"; + + immutable innerCodeOpening = + loop ? " foreach (line; std.stdio.stdin.byLine()) {\n" + : "\n"; + + immutable innerCodeClosing = loop ? "} " : ""; + + return format(codeFormat, + innerCodeOpening, + innerEvalCode(eval), + innerCodeClosing); +} + +unittest +{ + // innerEvalCode already tests the cases for different + // contents in `eval` array, so let's focus on testing + // the difference based on the `loop` flag + assert(makeEvalCode([`writeln("Hello!") `], No.loop) == + importWorld + ~ "void main(char[][] args) {\n" + ~ "writeln(\"Hello!\");\n}"); + + assert(makeEvalCode([`writeln("What!"); `], No.loop) == + importWorld + ~ "void main(char[][] args) {\n" + ~ "writeln(\"What!\");\n}"); + + assert(makeEvalCode([`writeln("Loop!") ; `], Yes.loop) == + importWorld + ~ "void main(char[][] args) { " + ~ "foreach (line; std.stdio.stdin.byLine()) {\n" + ~ "writeln(\"Loop!\") ;\n} }"); +} + string makeEvalFile(string todo) { auto pathname = myOwnTmpDir; From 1a29cdbe66f77a424a5b8a27839beb6894ddafe9 Mon Sep 17 00:00:00 2001 From: Joseph Rushton Wakeling Date: Sat, 3 Feb 2018 00:37:00 +0100 Subject: [PATCH 2/2] Prevent empty statements at the end of --eval or --loop code This patch replaces the old ad-hoc generation of `--eval` or `--loop` program code with calls to the new `makeEvalCode` function. Besides improving separation of concerns, this also ensures that the generated code will never have a trailing blank statement (i.e. two `;` separated at most by whitespace). This should prevent issues when using `--eval` or `--loop` with a `--compiler` choice that is fussy about such things; see https://github.com/dlang/tools/pull/297#issuecomment-362447334 for an example. --- rdmd.d | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rdmd.d b/rdmd.d index 05b9bf7..250f3f2 100755 --- a/rdmd.d +++ b/rdmd.d @@ -180,18 +180,14 @@ int main(string[] args) { enforce(programPos == args.length, "Cannot have both --loop and a " ~ "program file ('" ~ args[programPos] ~ "')."); - root = makeEvalFile(importWorld ~ "void main(char[][] args) { " - ~ "foreach (line; std.stdio.stdin.byLine()) {\n" - ~ std.string.join(loop, "\n") - ~ ";\n} }"); + root = makeEvalFile(makeEvalCode(loop, Yes.loop)); argsBeforeProgram ~= "-d"; } else if (eval.ptr) { enforce(programPos == args.length, "Cannot have both --eval and a " ~ "program file ('" ~ args[programPos] ~ "')."); - root = makeEvalFile(importWorld ~ "void main(char[][] args) {\n" - ~ std.string.join(eval, "\n") ~ ";\n}"); + root = makeEvalFile(makeEvalCode(eval, No.loop)); argsBeforeProgram ~= "-d"; } else if (programPos < args.length)