Skip to content

Commit 0678dc5

Browse files
committed
Added interpolated strings
1 parent bc85c44 commit 0678dc5

8 files changed

Lines changed: 186 additions & 12 deletions

File tree

src/dmd/astbase.d

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4470,6 +4470,7 @@ struct ASTBase
44704470
size_t len; // number of code units
44714471
ubyte sz = 1; // 1: char, 2: wchar, 4: dchar
44724472
char postfix = 0; // 'c', 'w', 'd'
4473+
char interpolate = 0;
44734474

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

4490-
extern (D) this(Loc loc, void* string, size_t len, char postfix)
4491+
extern (D) this(Loc loc, void* string, size_t len, char postfix, char interpolate = 0)
44914492
{
44924493
super(loc, TOK.string_, __traits(classInstanceSize, StringExp));
44934494
this.string = cast(char*)string;
44944495
this.len = len;
44954496
this.postfix = postfix;
4497+
this.interpolate = interpolate;
44964498
this.sz = 1; // work around LDC bug #1286
44974499
}
44984500

src/dmd/expression.d

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3004,6 +3004,7 @@ extern (C++) final class StringExp : Expression
30043004
ubyte sz = 1; // 1: char, 2: wchar, 4: dchar
30053005
ubyte committed; // !=0 if type is committed
30063006
char postfix = 0; // 'c', 'w', 'd'
3007+
char interpolate = 0;
30073008
OwnedBy ownedByCtfe = OwnedBy.code;
30083009

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

3025-
extern (D) this(const ref Loc loc, void* string, size_t len, char postfix)
3026+
extern (D) this(const ref Loc loc, void* string, size_t len, char postfix, char interpolate = 0)
30263027
{
30273028
super(loc, TOK.string_, __traits(classInstanceSize, StringExp));
30283029
this.string = cast(char*)string;
30293030
this.len = len;
30303031
this.postfix = postfix;
3032+
this.interpolate = interpolate;
30313033
this.sz = 1; // work around LDC bug #1286
30323034
}
30333035

src/dmd/lexer.d

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ class Lexer
308308
Loc startLoc;
309309
t.blockComment = null;
310310
t.lineComment = null;
311+
ubyte interpolate = 0;
311312

312313
while (1)
313314
{
@@ -374,7 +375,8 @@ class Lexer
374375
p++;
375376
goto case '`';
376377
case '`':
377-
t.value = wysiwygStringConstant(t, *p);
378+
t.value = wysiwygStringConstant(t, p[0]);
379+
t.interpolate = interpolate;
378380
return;
379381
case 'x':
380382
if (p[1] != '"')
@@ -388,19 +390,62 @@ class Lexer
388390
{
389391
p++;
390392
t.value = delimitedStringConstant(t);
393+
t.interpolate = interpolate;
391394
return;
392395
}
393396
else if (p[1] == '{')
394397
{
395398
p++;
396399
t.value = tokenStringConstant(t);
400+
t.interpolate = interpolate;
397401
return;
398402
}
399403
else
400404
goto case_ident;
401405
case '"':
402406
t.value = escapeStringConstant(t);
407+
t.interpolate = interpolate;
403408
return;
409+
case 'i':
410+
if (p[1] == 'r')
411+
{
412+
if (p[2] == '"')
413+
{
414+
p += 2;
415+
interpolate = 1;
416+
goto case '`';
417+
}
418+
}
419+
else if (p[1] == '`')
420+
{
421+
p++;
422+
interpolate = 1;
423+
goto case '`';
424+
}
425+
else if (p[1] == '"')
426+
{
427+
p++;
428+
interpolate = 1;
429+
goto case '"';
430+
}
431+
else if (p[1] == 'q')
432+
{
433+
if (p[2] == '"')
434+
{
435+
p += 2;
436+
t.value = delimitedStringConstant(t);
437+
t.interpolate = 1;
438+
return;
439+
}
440+
else if (p[2] == '{')
441+
{
442+
p += 2;
443+
t.value = tokenStringConstant(t);
444+
t.interpolate = 1;
445+
return;
446+
}
447+
}
448+
goto case_ident;
404449
case 'a':
405450
case 'b':
406451
case 'c':
@@ -409,7 +454,6 @@ class Lexer
409454
case 'f':
410455
case 'g':
411456
case 'h':
412-
case 'i':
413457
case 'j':
414458
case 'k':
415459
case 'l':
@@ -513,6 +557,7 @@ class Lexer
513557
Lstr:
514558
t.value = TOK.string_;
515559
t.postfix = 0;
560+
t.interpolate = 0;
516561
t.len = cast(uint)strlen(t.ustring);
517562
}
518563
else if (id == Id.VERSIONX)

src/dmd/parse.d

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7312,6 +7312,7 @@ final class Parser(AST) : Lexer
73127312
auto s = token.ustring;
73137313
auto len = token.len;
73147314
auto postfix = token.postfix;
7315+
auto interpolate = token.interpolate;
73157316
while (1)
73167317
{
73177318
const prev = token;
@@ -7324,6 +7325,11 @@ final class Parser(AST) : Lexer
73247325
error("mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix);
73257326
postfix = token.postfix;
73267327
}
7328+
if (interpolate != token.interpolate)
7329+
{
7330+
error("cannot concatenate interpolated strings with non-interpolated strings");
7331+
interpolate = true;
7332+
}
73277333

73287334
deprecation("Implicit string concatenation is deprecated, use %s ~ %s instead",
73297335
prev.toChars(), token.toChars());
@@ -7339,7 +7345,10 @@ final class Parser(AST) : Lexer
73397345
else
73407346
break;
73417347
}
7342-
e = new AST.StringExp(loc, cast(char*)s, len, postfix);
7348+
if (interpolate)
7349+
e = new AST.TupleExp(loc, parseInterpolatedString(loc, s, len, postfix));
7350+
else
7351+
e = new AST.StringExp(loc, cast(char*)s, len, postfix);
73437352
break;
73447353
}
73457354
case TOK.void_:
@@ -8560,6 +8569,95 @@ final class Parser(AST) : Lexer
85608569
token.lineComment = null;
85618570
}
85628571
}
8572+
8573+
/**
8574+
Parse the given interpolated string `str` into an array of expressions.
8575+
8576+
Params:
8577+
str = the interpolated string to parse
8578+
8579+
Returns:
8580+
An array of expressions representing the interpolated string.
8581+
*/
8582+
AST.Expressions* parseInterpolatedString(ref const(Loc) loc, const(char)* str, uint len, ubyte postfix)
8583+
{
8584+
//printf("parseInterpolatedString '%.*s'\n", len, str);
8585+
auto parts = new AST.Expressions();
8586+
8587+
auto mark = 0;
8588+
auto next = 0;
8589+
void addMarkToNext()
8590+
{
8591+
if (next > mark)
8592+
parts.push(new AST.StringExp(loc, cast(char*)str + mark, next - mark, postfix));
8593+
}
8594+
MainLoop:
8595+
for(; next < len;)
8596+
{
8597+
//printf("[DEBUG] str[%d] = '%c'\n", next, str[next]);
8598+
if (str[next] != '$')
8599+
{
8600+
next++;
8601+
}
8602+
else
8603+
{
8604+
addMarkToNext();
8605+
if (next + 1 >= len)
8606+
{
8607+
error("unfinished interpolated string expression '$'");
8608+
mark = next;
8609+
break;
8610+
}
8611+
if (str[next + 1] == '(')
8612+
{
8613+
next += 2;
8614+
mark = next;
8615+
for(uint depth = 1;; next++)
8616+
{
8617+
if (next >= len)
8618+
{
8619+
error("unfinished interpolated string expression '$(...)'");
8620+
mark = next;
8621+
break MainLoop;
8622+
}
8623+
auto c = str[next];
8624+
if (c == ')')
8625+
{
8626+
depth--;
8627+
if (depth == 0)
8628+
break;
8629+
}
8630+
else if (c == '(')
8631+
{
8632+
depth++;
8633+
}
8634+
}
8635+
{
8636+
auto expr = str[mark .. next];
8637+
//printf("[DEBUG] parsing the expression '%.*s'\n", expr.length, expr.ptr);
8638+
scope tempParser = new Parser!AST(/*loc, */mod, expr, false);
8639+
tempParser.nextToken();
8640+
auto result = tempParser.parseExpression();
8641+
//printf("[DEBUG] parsed to '%s'\n", result.toChars());
8642+
if (tempParser.token.value != TOK.rightParentheses)
8643+
{
8644+
error("invalid expression '%.*s' inside interpolated string", expr.length, expr.ptr);
8645+
}
8646+
parts.push(result);
8647+
}
8648+
next++;
8649+
mark = next;
8650+
}
8651+
else
8652+
{
8653+
assert(0, "not implemented");
8654+
}
8655+
}
8656+
}
8657+
addMarkToNext();
8658+
8659+
return parts;
8660+
}
85638661
}
85648662

85658663
enum PREC : int

src/dmd/tokens.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ extern (C++) struct Token
455455
const(char)* ustring; // UTF8 string
456456
uint len;
457457
ubyte postfix; // 'c', 'w', 'd'
458+
ubyte interpolate;
458459
}
459460

460461
Identifier ident;

src/dmd/tokens.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ struct Token
212212
{ utf8_t *ustring; // UTF8 string
213213
unsigned len;
214214
unsigned char postfix; // 'c', 'w', 'd'
215+
unsigned char interpolate;
215216
};
216217

217218
Identifier *ident;

test/d_do_test.d

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -688,13 +688,13 @@ int tryMain(string[] args)
688688
string objfile = output_dir ~ envData.sep ~ test_name ~ "_" ~ to!string(permuteIndex) ~ envData.obj;
689689
toCleanup ~= objfile;
690690

691-
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,
692-
reqArgs, permutedArgs, output_dir,
693-
(testArgs.mode == TestMode.RUN || testArgs.link ? test_app_dmd : objfile),
694-
argSet,
695-
(testArgs.mode == TestMode.RUN || testArgs.link ? "" : "-c "),
696-
join(testArgs.sources, " "),
697-
(autoCompileImports ? "-i" : join(testArgs.compiledImports, " ")));
691+
string command = text(
692+
i"$(envData.dmd) -conf= -m$(envData.model) -I$(input_dir) $(reqArgs) ",
693+
i"$(permutedArgs) -od$(output_dir) -of",
694+
(testArgs.mode == TestMode.RUN || testArgs.link) ? test_app_dmd : objfile,
695+
i` $(argSet) $(testArgs.mode == TestMode.RUN || testArgs.link ? "" : "-c ") `,
696+
join(testArgs.sources, " "), " ",
697+
(autoCompileImports ? "-i" : join(testArgs.compiledImports, " ")));
698698
version(Windows) command ~= " -map nul.map";
699699

700700
compile_output = execute(fThisRun, command, testArgs.mode != TestMode.FAIL_COMPILE, result_path);

test/runnable/istring.d

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
PERMUTE_ARGS:
3+
*/
4+
import std.conv : text;
5+
void main()
6+
{
7+
int a = 42;
8+
assert("a is 42" == text(i"a is $(a)"));
9+
assert("a + 23 is 65" == text(i"a + 23 is $(a + 23)"));
10+
int b = 93;
11+
assert("42 + 93 = 135" == text(i"$(a) + $(b) = $(a + b)"));
12+
13+
assert(928 == add(900, 28));
14+
}
15+
16+
string funcCode(string attributes, string returnType, string name, string args, string body)
17+
{
18+
return text(iq{
19+
$(attributes) $(returnType) $(name)($(args))
20+
{
21+
$(body)
22+
}
23+
});
24+
}
25+
mixin(funcCode("pragma(inline)", "int", "add", "int a, int b", "return a + b;"));

0 commit comments

Comments
 (0)