Prevent empty statements at the end of --eval or --loop code#303
Prevent empty statements at the end of --eval or --loop code#303wilzbach merged 2 commits intodlang:masterfrom
Conversation
|
Thanks for your pull request, @WebDrake! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
|
For reference: the new function should produce identical results to the old ad-hoc implementations, apart from the trailing |
rdmd.d
Outdated
| string code = eval.join("\n"); | ||
| if (stripRight(eval[$ - 1])[$ - 1] != ';') | ||
| code ~= ';'; | ||
| yap("[inner-code]\n", code); |
There was a problem hiding this comment.
Oh, whoops. This shouldn't be there, it was a debugging check. I'll remove.
90d5f45 to
41be9b0
Compare
|
Removed the unintended debug statement: diff --git a/rdmd.d b/rdmd.d
index d87ac76..118471c 100755
--- a/rdmd.d
+++ b/rdmd.d
@@ -855,7 +855,6 @@ string innerEvalCode(string[] eval)
string code = eval.join("\n");
if (stripRight(eval[$ - 1])[$ - 1] != ';')
code ~= ';';
- yap("[inner-code]\n", code);
return code;
} |
marler8997
left a comment
There was a problem hiding this comment.
Other than comments, looks good.
rdmd.d
Outdated
| to the inner code of either the program or | ||
| the loop | ||
| */ | ||
| string makeEvalCode(string[] eval, bool loop = false) |
rdmd.d
Outdated
| { | ||
| import std.string: join, stripRight; | ||
| string code = eval.join("\n"); | ||
| if (stripRight(eval[$ - 1])[$ - 1] != ';') |
There was a problem hiding this comment.
Should you handle the case where eval.length == 0, or the case where eval[$-1].length == 0 rather than causing an overflow?
rdmd.d
Outdated
| "; | ||
|
|
||
| /** | ||
| Joins together the code provided via an `--eval` |
There was a problem hiding this comment.
I believe the agreed upon convention is to have the ddoc content aligned with the comment, which in this case means no leading whitespace.
rdmd.d
Outdated
| "program file ('" ~ args[programPos] ~ "')."); | ||
| root = makeEvalFile(importWorld ~ "void main(char[][] args) {\n" | ||
| ~ std.string.join(eval, "\n") ~ ";\n}"); | ||
| root = makeEvalFile(makeEvalCode(eval)); |
There was a problem hiding this comment.
makeEvalCode(eval, No.loop) seems better
rdmd.d
Outdated
| ~ "foreach (line; std.stdio.stdin.byLine()) {\n" | ||
| ~ std.string.join(loop, "\n") | ||
| ~ ";\n} }"); | ||
| root = makeEvalFile(makeEvalCode(loop, true)); |
There was a problem hiding this comment.
makeEvalCode(eval, Yes.loop) seems better
rdmd.d
Outdated
| */ | ||
| string innerEvalCode(string[] eval) | ||
| { | ||
| import std.string: join, stripRight; |
There was a problem hiding this comment.
Nit: space before and after : for selective imports (DStyle for official code).
| ~ "void main(char[][] args) {%s%s\n%s}"; | ||
|
|
||
| immutable innerCodeOpening = | ||
| loop ? " foreach (line; std.stdio.stdin.byLine()) {\n" |
There was a problem hiding this comment.
Idiomatic D would just use .byLine - you could use this opportunity to change this bit.
| { | ||
| import std.format : format; | ||
| immutable codeFormat = importWorld | ||
| ~ "void main(char[][] args) {%s%s\n%s}"; |
There was a problem hiding this comment.
edit: DMD also emits the same in the default main method: https://github.com/dlang/dmd/blob/master/src/dmd/mars.d#L130
| innerCodeClosing); | ||
| } | ||
|
|
||
| unittest |
There was a problem hiding this comment.
@safe won't go with the use of assumeSafeAppend inside innerEvalCode. That might be possible to drop, but OTOH, I'm not sure I care to overly restrict how anyone edits or revises these functions in future. It's not like rdmd.d makes common use of these kinds of constraints.
|
@wilzbach I don't think it makes sense to bundle changes to how the code is generated, with changes to what code is generated (beyond the base goal of fixing the trailing-empty-statement issue). I don't want to assume that there were not deliberate motivations behind the choices made here, and I don't want to risk breaking anyone's use-case by changing things that do not need to be changed. |
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: dlang#297 (comment) ... 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.
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 dlang#297 (comment) for an example.
41be9b0 to
1a29cdb
Compare
|
I've updated the ddoc, replaced the Overall diff: diff --git a/rdmd.d b/rdmd.d
index 118471c..250f3f2 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)
{
@@ -180,14 +180,14 @@ int main(string[] args)
{
enforce(programPos == args.length, "Cannot have both --loop and a " ~
"program file ('" ~ args[programPos] ~ "').");
- root = makeEvalFile(makeEvalCode(loop, true));
+ 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(makeEvalCode(eval));
+ root = makeEvalFile(makeEvalCode(eval, No.loop));
argsBeforeProgram ~= "-d";
}
else if (programPos < args.length)
@@ -836,24 +836,24 @@ import std.stdio, std.algorithm, std.array, std.ascii, std.base64,
";
/**
- 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
+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;
- string code = eval.join("\n");
- if (stripRight(eval[$ - 1])[$ - 1] != ';')
+ 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;
}
@@ -864,34 +864,33 @@ unittest
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!");`);
+ 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!\"); ");
+ == "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 `true` if this code comes
- from a `--loop` flag, `false` (the
- default) otherwise
-
- Returns:
- string of code to be evaluated, corresponding
- to the inner code of either the program or
- the loop
+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, bool loop = false)
+string makeEvalCode(string[] eval, Flag!"loop" loop)
{
import std.format : format;
immutable codeFormat = importWorld
@@ -914,21 +913,21 @@ 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!") `]) ==
+ assert(makeEvalCode([`writeln("Hello!") `], No.loop) ==
importWorld
~ "void main(char[][] args) {\n"
- ~ "writeln(\"Hello!\") ;\n}");
+ ~ "writeln(\"Hello!\");\n}");
- assert(makeEvalCode([`writeln("What!"); `], false) ==
+ assert(makeEvalCode([`writeln("What!"); `], No.loop) ==
importWorld
~ "void main(char[][] args) {\n"
- ~ "writeln(\"What!\"); \n}");
+ ~ "writeln(\"What!\");\n}");
- assert(makeEvalCode([`writeln("Loop!") ; `], true) ==
+ assert(makeEvalCode([`writeln("Loop!") ; `], Yes.loop) ==
importWorld
~ "void main(char[][] args) { "
~ "foreach (line; std.stdio.stdin.byLine()) {\n"
- ~ "writeln(\"Loop!\") ; \n} }");
+ ~ "writeln(\"Loop!\") ;\n} }");
}
string makeEvalFile(string todo) |
Fair enough,
Are you aware of these two handy features from GH? https://github.com/dlang/tools/pull/303.diff |
Nice, but if I understand correctly, that's the overall diff of the PR. I posted the diff with between the original and versions of the PR. ;-) |
|
Ping on this? Is there anything that remains to do (e.g. a changelog entry or Bugzilla issue)? |
This patch is something of a proof-of-concept for using rdmd_test's new `--test-compilers` flag to test rdmd using multiple different compilers. The `install.sh` script is used to install GDC and LDC (including the `gdmd` and `ldmd2` DMD-alike compiler interfaces), and specify all three of `dmd`, `gdmd` and `ldmd2` with the `RDMD_TEST_COMPILERS` environment variable when invoking `make -f posix.mak test`. A little trickery with `find` is used to work around the problem that the install locations of `gdmd` and` ldmd2` are not added to `PATH`. The `VERBOSE_RDMD_TEST` flag is set to ensure that the Travis build logs will contain a full summary of the individual `rdmd` calls from the test suite, for each of the test compilers. This should probably not be the final form for setting up multi compiler tests, but serves as an initial stage which should at a minimum flag up issues like that observed with dlang#271. While in development it has already shown up at least one `rdmd` issue (fixed in dlang#303), so we can reasonably hope that it will prevent others from arising in the first place.
This patch is something of a proof-of-concept for using rdmd_test's new `--test-compilers` flag to test rdmd using multiple different compilers. The `install.sh` script is used to install LDC (including the DMD-alike `ldmd2` compiler interface interface), and the `make -f posix.mak test` call is updated to specify both `dmd` and `ldmd2` as test compilers for the `rdmd_test` test suite. A little trickery with `find` is necessary to work around the problem that the install location of` ldmd2` is not added to `PATH`. The `VERBOSE_RDMD_TEST` flag is set to ensure that the Travis build logs will contain a full summary of the individual `rdmd` calls from the test suite, for each of the test compilers. This should probably not be the final form for setting up multi compiler tests, but serves as an initial stage which should at a minimum flag up issues like that observed with dlang#271. While in development it has already shown up at least one `rdmd` issue (fixed in dlang#303), so we can reasonably hope that it will prevent others from arising in the first place.
This patch is something of a proof-of-concept for using rdmd_test's new `--test-compilers` flag to test rdmd using multiple different compilers. The `install.sh` script is used to install LDC (including the DMD-alike `ldmd2` compiler interface interface), and the `make -f posix.mak test` call is updated to specify both `dmd` and `ldmd2` as test compilers for the `rdmd_test` test suite. A little trickery with `find` is necessary to work around the problem that the install location of` ldmd2` is not added to `PATH`. The `VERBOSE_RDMD_TEST` flag is set to ensure that the Travis build logs will contain a full summary of the individual `rdmd` calls from the test suite, for each of the test compilers. This should probably not be the final form for setting up multi compiler tests, but serves as an initial stage which should at a minimum flag up issues like that observed with dlang#271. While in development it has already shown up at least one `rdmd` issue (fixed in dlang#303), so we can reasonably hope that it will prevent others from arising in the first place.
The
--evaland--loopflags allow the user to specify small snippets of code that are placed inside the body of an auto-generatedmainthat 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: #297 (comment)... meaning that
rdmdwill fail to successfully evaluate the code.These patches rework the implementation around a new
makeEvalCodefunction taking two parameters: thestring[]of code snippets to be evaluated, and a boolean flag indicating whether or not the code should be treated as the body of a loop (in other words, whether the code snippets derived from an--evalor a--loopflag).Generation of the innermost code (the common part for both
--evaland--loopoptions) is implemented in a second new function,innerEvalCode, which takes care of joining the different entries in thestring[], appending a terminating;only if one is not already present. In consequence, the generated code should never end in a blank statement (at least, not unless it was explicitly put there by the user).Unittests have been provided with both the new functions to ensure that they produce the expected outcomes. The first patch provides the functions themselves; the second uses them to replace the old implementation.