From 3d6815dbd85e95db499ceb2055f1dbb1cef108ec Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Tue, 13 Feb 2018 04:19:10 +0100 Subject: [PATCH 1/2] rdmd: `--loop` and `--eval` with a last statement without effect should print it --- changelog/rdmd-writeln.dd | 47 +++++++++++++++++++++++++++++++++++++++ rdmd.d | 21 ++++++++++++++++- rdmd_test.d | 43 +++++++++++++++++++++++------------ 3 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 changelog/rdmd-writeln.dd diff --git a/changelog/rdmd-writeln.dd b/changelog/rdmd-writeln.dd new file mode 100644 index 0000000000..134aa1633e --- /dev/null +++ b/changelog/rdmd-writeln.dd @@ -0,0 +1,47 @@ +In rdmd `--eval` and `--loop` the last statement without effect will be printed + +`--eval` and `--loop` are two convenience features of `rdmd` that allow quick experimentation with D code. +However, until + +$(CONSOLE +rdmd --eval='"hello"' +/tmp/.rdmd-1000/eval.CFD785092747553A81673B51A318D6AE.d(18): $(RED Error): "hello" has no effect +) + +The new behavior will default to writeln if the last statement would have no effect: + +$(CONSOLE +rdmd --eval="hello" +hello +) + +A few more examples of `rdmd` in action: + +$(H4 Use `rdmd` as your calculator) + +$(CONSOLE +rdmd --eval="2 + 2" +4 +) + +$(CONSOLE +rdmd --eval="2.pow(4)" +16 +) + +$(H4 Select the first column in a CSV file) + +$(CONSOLE +rdmd --loop='line.splitter(",").take(1)' +16 +) + +$(H4 Use `rdmd` as drop-in for Unix tools) + +$(CONSOLE +seq 5 | rdmd --loop='stdin.byLine.drop(2).joiner(newline)' +2 +3 +4 +5 +) diff --git a/rdmd.d b/rdmd.d index 4ca39b380f..082374eef2 100755 --- a/rdmd.d +++ b/rdmd.d @@ -828,11 +828,20 @@ Returns: */ string innerEvalCode(string[] eval) { + import std.conv : text; 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 ~= ';'; + { + auto lastSemicolon = max(code.lastIndexOf(";"), code.lastIndexOf("}")); + if (code[lastSemicolon + 1 .. $].canFind("write")) + code ~= ';'; + else if (lastSemicolon == -1) + code = text("writeln(", code, ");"); + else + code = text(code[0 .. lastSemicolon + 1], "writeln(", code[lastSemicolon + 1 .. $], ");"); + } return code; } @@ -852,6 +861,16 @@ unittest == "writeln(\"Hello!\"); \nwriteln(\"You!\");"); } +unittest +{ + assert(innerEvalCode(["2"]) == "writeln(2);"); + assert(innerEvalCode(["2 + 2"]) == "writeln(2 + 2);"); + assert(innerEvalCode(["2 + 2;"]) == "2 + 2;"); + assert(innerEvalCode(["2 + 2;2 + 2"]) == "2 + 2;writeln(2 + 2);"); + assert(innerEvalCode(["2.pow(4)"]) == "writeln(2.pow(4));"); + assert(innerEvalCode(["if(0) {} 2 + 2"]) == "if(0) {}writeln( 2 + 2);"); +} + /** Formats the code provided via `--eval` or `--loop` flags into a string of complete program code that can be written to a file diff --git a/rdmd_test.d b/rdmd_test.d index 924b9eea52..37dfe21d84 100755 --- a/rdmd_test.d +++ b/rdmd_test.d @@ -183,6 +183,19 @@ void runTests(string rdmdApp, string compiler, string model) assert(res.status == 0, res.output); assert(res.output.canFind("eval_works")); // there could be a "DMD v2.xxx header in the output" + // Test automatic .writeln for --eval + import std.conv : text; + import std.typecons : tuple; + foreach (t; [tuple(`"eval_works"`, "eval_works"), + tuple("2 + 2", "4"), + tuple("2.write; 2 + 2", "24")]) + { + res = execute(rdmdArgs ~ ["--force", "-de", text("--eval=", t[0])]); + assert(res.status == 0, res.output); + // there could be a "DMD v2.xxx header in the output" (NB: only seems to be the case for GDC) + assert(res.output.canFind(t[1]), text("got:", res.output, " expected:", t[1])); + } + // compiler flags res = execute(rdmdArgs ~ ["--force", "-debug", "--eval=debug {} else assert(false);"]); @@ -256,22 +269,24 @@ void runTests(string rdmdApp, string compiler, string model) { auto testLines = "foo\nbar\ndoo".split("\n"); - auto pipes = pipeProcess(rdmdArgs ~ ["--force", "--loop=writeln(line);"], Redirect.stdin | Redirect.stdout); - foreach (input; testLines) - pipes.stdin.writeln(input); - pipes.stdin.close(); - - while (!testLines.empty) + // Test --loop with automatic writeln + foreach (loopArg; ["--loop=writeln(line);", "--loop=line"]) { - auto line = pipes.stdout.readln.strip; - if (line.empty || line.startsWith("DMD v")) continue; // git-head header - assert(line == testLines.front, "Expected %s, got %s".format(testLines.front, line)); - testLines.popFront; - } - auto status = pipes.pid.wait(); - assert(status == 0); - } + auto pipes = pipeProcess(rdmdArgs ~ ["--force", loopArg], Redirect.stdin | Redirect.stdout); + foreach (input; testLines) + pipes.stdin.writeln(input); + pipes.stdin.close(); + while (!testLines.empty) + { + auto line = pipes.stdout.readln.strip; + if (line.empty || line.startsWith("DMD v")) continue; // git-head header + assert(line == testLines.front, "Expected %s, got %s".format(testLines.front, line)); + testLines.popFront; + } + auto status = pipes.pid.wait(); + assert(status == 0); + }} // vs program file res = execute(rdmdArgs ~ ["--force", "--loop=assert(true);", voidMain]); From 6f754c2b66c3fb078bce6dcefe62032ab7779d3e Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 15 Feb 2018 02:52:44 -0800 Subject: [PATCH 2/2] rdmd --eval and --loop prints last @statement --- changelog/rdmd-writeln.dd | 29 ++++++++++++++--------------- rdmd.d | 39 +++++++++++++++++++-------------------- rdmd_test.d | 8 ++++---- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/changelog/rdmd-writeln.dd b/changelog/rdmd-writeln.dd index 134aa1633e..6a0badc1c7 100644 --- a/changelog/rdmd-writeln.dd +++ b/changelog/rdmd-writeln.dd @@ -1,17 +1,13 @@ -In rdmd `--eval` and `--loop` the last statement without effect will be printed +In rdmd `--eval` and `--loop` the last statement without effect will be printed if prefixed with `@` `--eval` and `--loop` are two convenience features of `rdmd` that allow quick experimentation with D code. -However, until $(CONSOLE rdmd --eval='"hello"' -/tmp/.rdmd-1000/eval.CFD785092747553A81673B51A318D6AE.d(18): $(RED Error): "hello" has no effect -) - -The new behavior will default to writeln if the last statement would have no effect: - -$(CONSOLE -rdmd --eval="hello" +$(RED Error): Error: found } when expecting ; following statement +rdmd --eval='"hello";' +$(RED Error): "hello" has no effect +rdmd --eval='@"hello"' hello ) @@ -20,28 +16,31 @@ A few more examples of `rdmd` in action: $(H4 Use `rdmd` as your calculator) $(CONSOLE -rdmd --eval="2 + 2" +rdmd --eval="@2 + 2" 4 ) $(CONSOLE -rdmd --eval="2.pow(4)" +rdmd --eval="@2.pow(4)" 16 ) +$(CONSOLE +rdmd --eval="auto a = 10; auto b = [a, a + 1]; @b" +[10, 11] +) + $(H4 Select the first column in a CSV file) $(CONSOLE -rdmd --loop='line.splitter(",").take(1)' +rdmd --loop='@line.splitter(",").take(1)' 16 ) $(H4 Use `rdmd` as drop-in for Unix tools) $(CONSOLE -seq 5 | rdmd --loop='stdin.byLine.drop(2).joiner(newline)' -2 -3 +seq 5 | rdmd --loop='@stdin.byLine.drop(2).joiner(newline)' 4 5 ) diff --git a/rdmd.d b/rdmd.d index 082374eef2..430a0b93a4 100755 --- a/rdmd.d +++ b/rdmd.d @@ -832,43 +832,42 @@ 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] != ';') - { - auto lastSemicolon = max(code.lastIndexOf(";"), code.lastIndexOf("}")); - if (code[lastSemicolon + 1 .. $].canFind("write")) - code ~= ';'; - else if (lastSemicolon == -1) - code = text("writeln(", code, ");"); - else - code = text(code[0 .. lastSemicolon + 1], "writeln(", code[lastSemicolon + 1 .. $], ");"); - } + size_t lastSemicolon; + if (code.empty || code[$ - 1] == ';') + goto Lret; + // TODO: phobos should have findSplit!(reverse.Flag = reverse.No); + lastSemicolon = code.lastIndexOf("@"); + if(lastSemicolon==-1) + goto Lret; // probably a user error + code = text(code[0..lastSemicolon], "writeln(", code[lastSemicolon+1..$], ");"); + Lret: return code; } unittest { - assert(innerEvalCode([`writeln("Hello!")`]) == `writeln("Hello!");`); + 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!")`); assert(innerEvalCode([`writeln("Hello!"); `]) == `writeln("Hello!");`); // test with multiple entries assert(innerEvalCode([`writeln("Hello!"); `, `writeln("You!") `]) - == "writeln(\"Hello!\"); \nwriteln(\"You!\");"); + == "writeln(\"Hello!\"); \nwriteln(\"You!\")"); assert(innerEvalCode([`writeln("Hello!"); `, `writeln("You!"); `]) == "writeln(\"Hello!\"); \nwriteln(\"You!\");"); } unittest { - assert(innerEvalCode(["2"]) == "writeln(2);"); - assert(innerEvalCode(["2 + 2"]) == "writeln(2 + 2);"); - assert(innerEvalCode(["2 + 2;"]) == "2 + 2;"); - assert(innerEvalCode(["2 + 2;2 + 2"]) == "2 + 2;writeln(2 + 2);"); - assert(innerEvalCode(["2.pow(4)"]) == "writeln(2.pow(4));"); - assert(innerEvalCode(["if(0) {} 2 + 2"]) == "if(0) {}writeln( 2 + 2);"); + assert(innerEvalCode(["@2"]) == "writeln(2);"); + assert(innerEvalCode(["@2 + 2"]) == "writeln(2 + 2);"); + assert(innerEvalCode(["@2 + 2; "]) == "@2 + 2;"); + assert(innerEvalCode(["2 + 2; @2 + 2"]) == "2 + 2; writeln(2 + 2);"); + assert(innerEvalCode(["@2.pow(4)"]) == "writeln(2.pow(4));"); + assert(innerEvalCode(["if(0) {} @2 + 2"]) == "if(0) {} writeln(2 + 2);"); } /** @@ -910,7 +909,7 @@ 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) == + assert(makeEvalCode([`writeln("Hello!"); `], No.loop) == importWorld ~ "void main(char[][] args) {\n" ~ "writeln(\"Hello!\");\n}"); diff --git a/rdmd_test.d b/rdmd_test.d index 37dfe21d84..03666b8611 100755 --- a/rdmd_test.d +++ b/rdmd_test.d @@ -186,9 +186,9 @@ void runTests(string rdmdApp, string compiler, string model) // Test automatic .writeln for --eval import std.conv : text; import std.typecons : tuple; - foreach (t; [tuple(`"eval_works"`, "eval_works"), - tuple("2 + 2", "4"), - tuple("2.write; 2 + 2", "24")]) + foreach (t; [tuple(`@"eval_works"`, "eval_works"), + tuple("@2 + 2", "4"), + tuple("2.write; @2 + 2", "24")]) { res = execute(rdmdArgs ~ ["--force", "-de", text("--eval=", t[0])]); assert(res.status == 0, res.output); @@ -270,7 +270,7 @@ void runTests(string rdmdApp, string compiler, string model) auto testLines = "foo\nbar\ndoo".split("\n"); // Test --loop with automatic writeln - foreach (loopArg; ["--loop=writeln(line);", "--loop=line"]) + foreach (loopArg; ["--loop=writeln(line);", "--loop=@line"]) { auto pipes = pipeProcess(rdmdArgs ~ ["--force", loopArg], Redirect.stdin | Redirect.stdout); foreach (input; testLines)