Skip to content

Commit 37a6f8f

Browse files
author
Jonathan Marler
committed
Added interpolated strings
1 parent bc85c44 commit 37a6f8f

9 files changed

Lines changed: 197 additions & 13 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+
bool interpolate = false; // true if prefixed with 'i'
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, bool interpolate = false)
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+
bool interpolate = false; // true if prefixed with 'i'
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, bool interpolate = false)
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/expression.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,11 @@ class StringExp : public Expression
346346
unsigned char sz; // 1: char, 2: wchar, 4: dchar
347347
unsigned char committed; // !=0 if type is committed
348348
utf8_t postfix; // 'c', 'w', 'd'
349+
bool interpolate; // true if prefixed with 'i'
349350
OwnedBy ownedByCtfe;
350351

351352
static StringExp *create(Loc loc, char *s);
352-
static StringExp *create(Loc loc, void *s, size_t len);
353+
static StringExp *create(Loc loc, void *s, size_t len, bool interpolate = false);
353354
bool equals(RootObject *o);
354355
StringExp *toStringExp();
355356
StringExp *toUTF8(Scope *sc);

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+
bool interpolate = false;
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 = true;
416+
goto case '`';
417+
}
418+
}
419+
else if (p[1] == '`')
420+
{
421+
p++;
422+
interpolate = true;
423+
goto case '`';
424+
}
425+
else if (p[1] == '"')
426+
{
427+
p++;
428+
interpolate = true;
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 = true;
438+
return;
439+
}
440+
else if (p[2] == '{')
441+
{
442+
p += 2;
443+
t.value = tokenStringConstant(t);
444+
t.interpolate = true;
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 = false;
516561
t.len = cast(uint)strlen(t.ustring);
517562
}
518563
else if (id == Id.VERSIONX)

src/dmd/parse.d

Lines changed: 102 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,98 @@ 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+
loc = the location of the interpolated string
8578+
str = the interpolated string to parse
8579+
len = the length of the interpolated string
8580+
postfix = the interpolated string postix, i.e 'c', 'w' or 'd'
8581+
8582+
Returns:
8583+
An array of expressions representing the interpolated string.
8584+
*/
8585+
AST.Expressions* parseInterpolatedString(ref const(Loc) loc, const(char)* str, uint len, ubyte postfix)
8586+
{
8587+
//printf("parseInterpolatedString '%.*s'\n", len, str);
8588+
auto parts = new AST.Expressions();
8589+
8590+
auto mark = 0;
8591+
auto next = 0;
8592+
void addMarkToNext()
8593+
{
8594+
if (next > mark)
8595+
parts.push(new AST.StringExp(loc, cast(char*)str + mark, next - mark, postfix));
8596+
}
8597+
MainLoop:
8598+
for(; next < len;)
8599+
{
8600+
//printf("[DEBUG] str[%d] = '%c'\n", next, str[next]);
8601+
if (str[next] != '$')
8602+
{
8603+
next++;
8604+
}
8605+
else
8606+
{
8607+
addMarkToNext();
8608+
if (next + 1 >= len)
8609+
{
8610+
error("unfinished interpolated string expression '$'");
8611+
mark = next;
8612+
break;
8613+
}
8614+
if (str[next + 1] == '(')
8615+
{
8616+
next += 2;
8617+
mark = next;
8618+
for(uint depth = 1;; next++)
8619+
{
8620+
if (next >= len)
8621+
{
8622+
error("unfinished interpolated string expression '$(...)'");
8623+
mark = next;
8624+
break MainLoop;
8625+
}
8626+
auto c = str[next];
8627+
if (c == ')')
8628+
{
8629+
depth--;
8630+
if (depth == 0)
8631+
break;
8632+
}
8633+
else if (c == '(')
8634+
{
8635+
depth++;
8636+
}
8637+
}
8638+
{
8639+
auto expr = str[mark .. next];
8640+
//printf("[DEBUG] parsing the expression '%.*s'\n", expr.length, expr.ptr);
8641+
scope tempParser = new Parser!AST(/*loc, */mod, expr, false);
8642+
tempParser.nextToken();
8643+
auto result = tempParser.parseExpression();
8644+
//printf("[DEBUG] parsed to '%s'\n", result.toChars());
8645+
if (tempParser.token.value != TOK.rightParentheses)
8646+
{
8647+
error("invalid expression '%.*s' inside interpolated string", expr.length, expr.ptr);
8648+
}
8649+
parts.push(result);
8650+
}
8651+
next++;
8652+
mark = next;
8653+
}
8654+
else
8655+
{
8656+
assert(0, "not implemented");
8657+
}
8658+
}
8659+
}
8660+
addMarkToNext();
8661+
8662+
return parts;
8663+
}
85638664
}
85648665

85658666
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+
bool 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+
bool 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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
11+
// test each type of string literal
12+
int b = 93;
13+
assert("42 + 93 = 135" == text( i"$(a) + $(b) = $(a + b)")); // double-quote
14+
assert("42 + 93 = 135" == text( ir"$(a) + $(b) = $(a + b)")); // wysiwyg
15+
assert("42 + 93 = 135" == text( i`$(a) + $(b) = $(a + b)`)); // wysiwyg (alt)
16+
assert("42 + 93 = 135" == text( iq{$(a) + $(b) = $(a + b)})); // token
17+
assert("42 + 93 = 135" == text(iq"!$(a) + $(b) = $(a + b)!")); // delimited
18+
19+
assert(928 == add(900, 28));
20+
}
21+
22+
string funcCode(string attributes, string returnType, string name, string args, string body)
23+
{
24+
return text(iq{
25+
$(attributes) $(returnType) $(name)($(args))
26+
{
27+
$(body)
28+
}
29+
});
30+
}
31+
mixin(funcCode("pragma(inline)", "int", "add", "int a, int b", "return a + b;"));

0 commit comments

Comments
 (0)