Skip to content

Commit

Permalink
Added interpolated strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Marler committed Mar 8, 2018
1 parent bc85c44 commit 2b93840
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 22 deletions.
4 changes: 3 additions & 1 deletion src/dmd/astbase.d
Original file line number Diff line number Diff line change
Expand Up @@ -4470,6 +4470,7 @@ struct ASTBase
size_t len; // number of code units
ubyte sz = 1; // 1: char, 2: wchar, 4: dchar
char postfix = 0; // 'c', 'w', 'd'
bool interpolate = false; /// true if it's an interpolated string, i.e. prefixed with 'i'

extern (D) this(Loc loc, char* string)
{
Expand All @@ -4487,12 +4488,13 @@ struct ASTBase
this.sz = 1; // work around LDC bug #1286
}

extern (D) this(Loc loc, void* string, size_t len, char postfix)
extern (D) this(Loc loc, void* string, size_t len, char postfix, bool interpolate = false)
{
super(loc, TOK.string_, __traits(classInstanceSize, StringExp));
this.string = cast(char*)string;
this.len = len;
this.postfix = postfix;
this.interpolate = interpolate;
this.sz = 1; // work around LDC bug #1286
}

Expand Down
4 changes: 3 additions & 1 deletion src/dmd/expression.d
Original file line number Diff line number Diff line change
Expand Up @@ -3004,6 +3004,7 @@ extern (C++) final class StringExp : Expression
ubyte sz = 1; // 1: char, 2: wchar, 4: dchar
ubyte committed; // !=0 if type is committed
char postfix = 0; // 'c', 'w', 'd'
bool interpolate = false; /// true if it's an interpolated string, i.e. prefixed with 'i'
OwnedBy ownedByCtfe = OwnedBy.code;

extern (D) this(const ref Loc loc, char* string)
Expand All @@ -3022,12 +3023,13 @@ extern (C++) final class StringExp : Expression
this.sz = 1; // work around LDC bug #1286
}

extern (D) this(const ref Loc loc, void* string, size_t len, char postfix)
extern (D) this(const ref Loc loc, void* string, size_t len, char postfix, bool interpolate = false)
{
super(loc, TOK.string_, __traits(classInstanceSize, StringExp));
this.string = cast(char*)string;
this.len = len;
this.postfix = postfix;
this.interpolate = interpolate;
this.sz = 1; // work around LDC bug #1286
}

Expand Down
3 changes: 2 additions & 1 deletion src/dmd/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,11 @@ class StringExp : public Expression
unsigned char sz; // 1: char, 2: wchar, 4: dchar
unsigned char committed; // !=0 if type is committed
utf8_t postfix; // 'c', 'w', 'd'
bool interpolate; // true if prefixed with 'i'
OwnedBy ownedByCtfe;

static StringExp *create(Loc loc, char *s);
static StringExp *create(Loc loc, void *s, size_t len);
static StringExp *create(Loc loc, void *s, size_t len, bool interpolate = false);
bool equals(RootObject *o);
StringExp *toStringExp();
StringExp *toUTF8(Scope *sc);
Expand Down
49 changes: 47 additions & 2 deletions src/dmd/lexer.d
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ class Lexer
Loc startLoc;
t.blockComment = null;
t.lineComment = null;
bool interpolate = false;

while (1)
{
Expand Down Expand Up @@ -374,7 +375,8 @@ class Lexer
p++;
goto case '`';
case '`':
t.value = wysiwygStringConstant(t, *p);
t.value = wysiwygStringConstant(t, p[0]);
t.interpolate = interpolate;
return;
case 'x':
if (p[1] != '"')
Expand All @@ -388,19 +390,62 @@ class Lexer
{
p++;
t.value = delimitedStringConstant(t);
t.interpolate = interpolate;
return;
}
else if (p[1] == '{')
{
p++;
t.value = tokenStringConstant(t);
t.interpolate = interpolate;
return;
}
else
goto case_ident;
case '"':
t.value = escapeStringConstant(t);
t.interpolate = interpolate;
return;
case 'i':
if (p[1] == 'r')
{
if (p[2] == '"')
{
p += 2;
interpolate = true;
goto case '`';
}
}
else if (p[1] == '`')
{
p++;
interpolate = true;
goto case '`';
}
else if (p[1] == '"')
{
p++;
interpolate = true;
goto case '"';
}
else if (p[1] == 'q')
{
if (p[2] == '"')
{
p += 2;
t.value = delimitedStringConstant(t);
t.interpolate = true;
return;
}
else if (p[2] == '{')
{
p += 2;
t.value = tokenStringConstant(t);
t.interpolate = true;
return;
}
}
goto case_ident;
case 'a':
case 'b':
case 'c':
Expand All @@ -409,7 +454,6 @@ class Lexer
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
Expand Down Expand Up @@ -513,6 +557,7 @@ class Lexer
Lstr:
t.value = TOK.string_;
t.postfix = 0;
t.interpolate = false;
t.len = cast(uint)strlen(t.ustring);
}
else if (id == Id.VERSIONX)
Expand Down
110 changes: 109 additions & 1 deletion src/dmd/parse.d
Original file line number Diff line number Diff line change
Expand Up @@ -7312,6 +7312,7 @@ final class Parser(AST) : Lexer
auto s = token.ustring;
auto len = token.len;
auto postfix = token.postfix;
auto interpolate = token.interpolate;
while (1)
{
const prev = token;
Expand All @@ -7324,6 +7325,11 @@ final class Parser(AST) : Lexer
error("mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix);
postfix = token.postfix;
}
if (interpolate != token.interpolate)
{
error("cannot concatenate interpolated strings with non-interpolated strings");
interpolate = true;
}

deprecation("Implicit string concatenation is deprecated, use %s ~ %s instead",
prev.toChars(), token.toChars());
Expand All @@ -7339,7 +7345,10 @@ final class Parser(AST) : Lexer
else
break;
}
e = new AST.StringExp(loc, cast(char*)s, len, postfix);
if (interpolate)
e = new AST.TupleExp(loc, parseInterpolatedString(loc, s, len, postfix));
else
e = new AST.StringExp(loc, cast(char*)s, len, postfix);
break;
}
case TOK.void_:
Expand Down Expand Up @@ -8560,6 +8569,105 @@ final class Parser(AST) : Lexer
token.lineComment = null;
}
}

/**
Parse the given interpolated string `str` into an array of expressions.
Params:
loc = the location of the interpolated string
str = the interpolated string to parse
len = the length of the interpolated string
postfix = the interpolated string postix, i.e 'c', 'w' or 'd'
Returns:
An array of expressions representing the interpolated string.
*/
AST.Expressions* parseInterpolatedString(ref const(Loc) loc, const(char)* str, uint len, ubyte postfix)
{
//printf("parseInterpolatedString '%.*s'\n", len, str);
auto parts = new AST.Expressions();

auto mark = 0;
auto next = 0;
void addMarkToNext()
{
if (next > mark)
parts.push(new AST.StringExp(loc, cast(char*)str + mark, next - mark, postfix));
}
MainLoop:
for(; next < len;)
{
//printf("[DEBUG] str[%d] = '%c'\n", next, str[next]);
if (str[next] != '$')
{
next++;
}
else
{
addMarkToNext();
if (next + 1 >= len)
{
error("unfinished interpolated string expression '$'");
mark = next;
break;
}
if (str[next + 1] == '(')
{
next += 2;
mark = next;
for(uint depth = 1;; next++)
{
if (next >= len)
{
error("unfinished interpolated string expression '$(...)'");
mark = next;
break MainLoop;
}
auto c = str[next];
if (c == ')')
{
depth--;
if (depth == 0)
break;
}
else if (c == '(')
{
depth++;
}
}
{
auto writeableStr = cast(char*)str;
writeableStr[next] = '\0';
scope(exit) writeableStr[next] = ')';

auto expr = str[mark .. next];
//printf("[DEBUG] parsing the expression '%s'\n", expr.ptr);
scope tempParser = new Parser!AST(/*loc, */mod, expr, false);
tempParser.scanloc = loc;
tempParser.nextToken();
auto result = tempParser.parseExpression();
//printf("[DEBUG] parsed to '%s'\n", result.toChars());
if (tempParser.token.value != TOK.endOfFile)
{
error("invalid expression '%s' inside interpolated string", expr.ptr);
}
parts.push(result);
}
next++;
mark = next;
}
else
{
// TODO: maybe we do support this
// maybe a good grammar node for this would be DotIdentifier
error("missing parentheses in interpolated string expression '$(...)'");
}
}
}
addMarkToNext();

return parts;
}
}

enum PREC : int
Expand Down
1 change: 1 addition & 0 deletions src/dmd/tokens.d
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ extern (C++) struct Token
const(char)* ustring; // UTF8 string
uint len;
ubyte postfix; // 'c', 'w', 'd'
bool interpolate;
}

Identifier ident;
Expand Down
1 change: 1 addition & 0 deletions src/dmd/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ struct Token
{ utf8_t *ustring; // UTF8 string
unsigned len;
unsigned char postfix; // 'c', 'w', 'd'
bool interpolate;
};

Identifier *ident;
Expand Down
33 changes: 17 additions & 16 deletions test/d_do_test.d
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,7 @@ string envGetRequired(in char[] name)
auto value = environment.get(name);
if(value is null)
{
writefln("Error: missing environment variable '%s', was this called this through the Makefile?",
name);
writeln(i"Error: missing environment variable '$(name)', was this called this through the Makefile?");
throw new SilentQuit();
}
return value;
Expand Down Expand Up @@ -688,13 +687,13 @@ int tryMain(string[] args)
string objfile = output_dir ~ envData.sep ~ test_name ~ "_" ~ to!string(permuteIndex) ~ envData.obj;
toCleanup ~= objfile;

string command = format("%s -conf= -m%s -I%s %s %s -od%s -of%s %s %s%s %s", envData.dmd, envData.model, input_dir,
reqArgs, permutedArgs, output_dir,
(testArgs.mode == TestMode.RUN || testArgs.link ? test_app_dmd : objfile),
argSet,
(testArgs.mode == TestMode.RUN || testArgs.link ? "" : "-c "),
join(testArgs.sources, " "),
(autoCompileImports ? "-i" : join(testArgs.compiledImports, " ")));
string command = text(
i"$(envData.dmd) -conf= -m$(envData.model) -I$(input_dir) $(reqArgs) ",
i"$(permutedArgs) -od$(output_dir) -of",
(testArgs.mode == TestMode.RUN || testArgs.link) ? test_app_dmd : objfile,
i` $(argSet) $(testArgs.mode == TestMode.RUN || testArgs.link ? "" : "-c ") `,
join(testArgs.sources, " "), " ",
(autoCompileImports ? "-i" : join(testArgs.compiledImports, " ")));
version(Windows) command ~= " -map nul.map";

compile_output = execute(fThisRun, command, testArgs.mode != TestMode.FAIL_COMPILE, result_path);
Expand All @@ -706,18 +705,21 @@ int tryMain(string[] args)
string newo= result_path ~ replace(replace(filename, ".d", envData.obj), envData.sep~"imports"~envData.sep, envData.sep);
toCleanup ~= newo;

string command = format("%s -conf= -m%s -I%s %s %s -od%s -c %s %s", envData.dmd, envData.model, input_dir,
reqArgs, permutedArgs, output_dir, argSet, filename);
string command = text(
i"$(envData.dmd) -conf= -m$(envData.model) -I$(input_dir) $(reqArgs) ",
i"$(permutedArgs) -od$(output_dir) -c $(argSet) $(filename)");
compile_output ~= execute(fThisRun, command, testArgs.mode != TestMode.FAIL_COMPILE, result_path);
}

if (testArgs.mode == TestMode.RUN || testArgs.link)
{
// link .o's into an executable
string command = format("%s -conf= -m%s%s%s %s %s -od%s -of%s %s", envData.dmd, envData.model,
string command = text(
i"$(envData.dmd) -conf= -m$(envData.model)",
autoCompileImports ? " -i" : "",
autoCompileImports ? "extraSourceIncludePaths" : "",
envData.required_args, testArgs.requiredArgsForLink, output_dir, test_app_dmd, join(toCleanup, " "));
i" $(envData.required_args) $(testArgs.requiredArgsForLink) -od$(output_dir)",
i" -of$(test_app_dmd) $(join(toCleanup, ` `))");
version(Windows) command ~= " -map nul.map";

execute(fThisRun, command, true, result_path);
Expand Down Expand Up @@ -794,11 +796,10 @@ int tryMain(string[] args)

f.writeln();
f.writeln("==============================");
f.writef("Test %s/%s.%s failed: ", input_dir, test_name, test_extension);
f.writeln(e.msg);
f.writeln(i"Test $(input_dir)/$(test_name).$(test_extension) failed: $(e.msg)");
f.close();

writefln("Test %s/%s.%s failed. The logged output:", input_dir, test_name, test_extension);
writeln(i"Test $(input_dir)/$(test_name).$(test_extension) failed. The logged output:");
writeln(cast(string)std.file.read(output_file));
std.file.remove(output_file);
return Result.return1;
Expand Down
Loading

0 comments on commit 2b93840

Please sign in to comment.