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..aa4f6f52cd 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; + auto lastSemicolon = code.lastIndexOf(";"); if (code.length > 0 && code[$ - 1] != ';') - code ~= ';'; + { + 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,14 @@ 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.pow(4)"]) == "writeln(2.pow(4));"); +} + /** 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]);