Showing with 168 additions and 5 deletions.
  1. +10 −0 src/dmd/astbase.d
  2. +12 −0 src/dmd/expression.d
  3. +22 −0 src/dmd/expressionsem.d
  4. +68 −5 src/dmd/lexer.d
  5. +25 −0 src/dmd/parse.d
  6. +31 −0 src/dmd/tokens.d
10 changes: 10 additions & 0 deletions src/dmd/astbase.d
Original file line number Diff line number Diff line change
Expand Up @@ -5271,6 +5271,16 @@ struct ASTBase
Expression e0;
Expressions* exps;

bool isStringInterp;

extern (D) this(const ref Loc loc, Expressions* exps, bool isStringInterp)
{
assert(isStringInterp);
this.isStringInterp = isStringInterp;

this(loc, exps);
}

extern (D) this(const ref Loc loc, Expression e0, Expressions* exps)
{
super(loc, TOK.tuple, __traits(classInstanceSize, TupleExp));
Expand Down
12 changes: 12 additions & 0 deletions src/dmd/expression.d
Original file line number Diff line number Diff line change
Expand Up @@ -2856,6 +2856,18 @@ extern (C++) final class TupleExp : Expression

Expressions* exps;


bool isStringInterp;

extern (D) this(const ref Loc loc, Expressions* exps, bool isStringInterp)
{
assert(isStringInterp);
this.isStringInterp = isStringInterp;

this(loc, exps);
}


extern (D) this(const ref Loc loc, Expression e0, Expressions* exps)
{
super(loc, TOK.tuple, __traits(classInstanceSize, TupleExp));
Expand Down
22 changes: 22 additions & 0 deletions src/dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -3095,6 +3095,28 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
for (size_t i = 0; i < exp.exps.dim; i++)
{
Expression e = (*exp.exps)[i];

if (exp.isStringInterp)
{
auto values = new Expressions();
if (i % 2 == 0)
{
auto tiargs = new Objects();
tiargs.push(e);

Expression id = new IdentifierExp(exp.loc, Id.empty);
auto dotid = new DotIdExp(exp.loc, id, Id.object);

auto dt = new DotTemplateInstanceExp(exp.loc, dotid, Identifier.idPool("interp"), tiargs);

auto arguments = new Expressions();
Expression ce = new CallExp(exp.loc, dt, arguments);

e = expressionSemantic(ce, sc);
}
// FIXME: the implicit conversion to string should work on this object somehow
}

e = e.expressionSemantic(sc);
if (!e.type)
{
Expand Down
73 changes: 68 additions & 5 deletions src/dmd/lexer.d
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,15 @@ class Lexer
hexString.write(start[0 .. p - start]);
error("Built-in hex string literals are obsolete, use `std.conv.hexString!%s` instead.", hexString.extractChars());
return;
case 'i':
if (p[1] == '"')
{
p++;
interpolatedStringConstant(t);
return;
}
else
goto case_ident;
case 'q':
if (p[1] == '"')
{
Expand All @@ -468,7 +477,7 @@ class Lexer
case 'f':
case 'g':
case 'h':
case 'i':
/*case 'i':*/
case 'j':
case 'k':
case 'l':
Expand Down Expand Up @@ -1603,6 +1612,38 @@ class Lexer
stringPostfix(result);
}

// FIXME: this PoC only does i"", but the DIP says all string literal types should work.
private void interpolatedStringConstant(Token* result)
{
result.value = TOK.interp;
result.interp = InterpolatedToken.init;
const start = loc();
const pstart = ++p;

while (true)
{
if (*p == '"')
{
p++;
Token tmp;
stringPostfix(&tmp);
foreach(idx, ref p; result.interp.components)
if (idx % 2 == 0)
p.postfix = tmp.postfix;
return;
}
Token* part = new Token;
escapeStringConstant(part, true);
result.interp.components ~= part;
if (*p == '{')
{
part = new Token;
tokenStringConstant(part, false);
result.interp.components ~= part;
}
}
}

/**
Lex a token string. Some examples of token strings are:
---
Expand All @@ -1614,7 +1655,7 @@ class Lexer
Params:
result = pointer to the token that accepts the result
*/
private void tokenStringConstant(Token* result)
private void tokenStringConstant(Token* result, bool allowStringPostfix = true)
{
result.value = TOK.string_;

Expand All @@ -1636,7 +1677,8 @@ class Lexer
if (--nest == 0)
{
result.setString(pstart, p - 1 - pstart);
stringPostfix(result);
if (allowStringPostfix)
stringPostfix(result);
return;
}
continue;
Expand All @@ -1657,13 +1699,15 @@ class Lexer
of the string.
Params:
t = the token to set the resulting string to
interpolating = if this is inside an interpolated token, if true it returns upon ${ or "
*/
private void escapeStringConstant(Token* t)
private void escapeStringConstant(Token* t, bool interpolating = false)
{
t.value = TOK.string_;

const start = loc();
p++;
if (!interpolating)
p++;
stringbuffer.setsize(0);
while (1)
{
Expand All @@ -1679,6 +1723,12 @@ class Lexer
c = escapeSequence();
stringbuffer.writeUTF8(c);
continue;
case '$':
if (!interpolating)
goto default;
p++;
stringbuffer.writeUTF8('$');
continue;
default:
c = escapeSequence();
break;
Expand All @@ -1693,8 +1743,21 @@ class Lexer
c = '\n'; // treat EndOfLine as \n character
endOfLine();
break;
case '$':
if (!interpolating)
goto default;
if (*p != '{')
goto default;

t.setString(stringbuffer);
return;
case '"':
t.setString(stringbuffer);
if (interpolating)
{
p--;
return;
}
stringPostfix(t);
return;
case 0:
Expand Down
25 changes: 25 additions & 0 deletions src/dmd/parse.d
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ immutable PREC[TOK.max_] precedence =
TOK.complex80 : PREC.primary,
TOK.null_ : PREC.primary,
TOK.string_ : PREC.primary,
TOK.interp : PREC.primary,
TOK.arrayLiteral : PREC.primary,
TOK.assocArrayLiteral : PREC.primary,
TOK.classReference : PREC.primary,
Expand Down Expand Up @@ -2125,6 +2126,7 @@ final class Parser(AST) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interp:
case TOK.hexadecimalString:
case TOK.file:
case TOK.fileFullPath:
Expand Down Expand Up @@ -5614,6 +5616,7 @@ final class Parser(AST) : Lexer
case TOK.true_:
case TOK.false_:
case TOK.string_:
case TOK.interp:
case TOK.hexadecimalString:
case TOK.leftParentheses:
case TOK.cast_:
Expand Down Expand Up @@ -7078,6 +7081,7 @@ final class Parser(AST) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interp:
case TOK.hexadecimalString:
case TOK.file:
case TOK.fileFullPath:
Expand Down Expand Up @@ -7899,6 +7903,26 @@ final class Parser(AST) : Lexer
nextToken();
break;

case TOK.interp:
auto values = new AST.Expressions();
foreach(idx, part; token.interp.components)
{
if (idx % 2 == 0)
{
values.push(new AST.StringExp(loc, part.ustring[0 .. part.len], part.len, 1, part.postfix));
}
else
{
scope p = new Parser(mod, part.ustring[0 .. part.len], false);
p.nextToken();
values.push(p.parseAssignExp());
}
}
// FIXME: the implicit conversion to string should work on this object somehow
e = new AST.TupleExp(loc, values, true);
nextToken();
break;

case TOK.file:
{
const(char)* s = loc.filename ? loc.filename : mod.ident.toChars();
Expand Down Expand Up @@ -8565,6 +8589,7 @@ final class Parser(AST) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interp:
version (none)
{
case TOK.tilde:
Expand Down
31 changes: 31 additions & 0 deletions src/dmd/tokens.d
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ enum TOK : ubyte
objcClassReference,
vectorArray,

interp,

max_,
}

Expand Down Expand Up @@ -432,6 +434,12 @@ shared static this() nothrow
}
}

struct InterpolatedToken
{
Token*[] components; // all are TOKstring types, indexes 0,2,4... are the strings, indexes 1,3,5... are the interpolated components (stored like token strings)
ubyte postfix;
}

/***********************************************************
*/
extern (C++) struct Token
Expand All @@ -458,6 +466,8 @@ extern (C++) struct Token
ubyte postfix; // 'c', 'w', 'd'
}

InterpolatedToken interp;

Identifier ident;
}

Expand Down Expand Up @@ -673,6 +683,8 @@ extern (C++) struct Token
TOK.onScopeFailure: "scope(failure)",
TOK.delegatePointer: "delegateptr",

TOK.interp: "interpolated",

// Finish up
TOK.reserved: "reserved",
TOK.remove: "remove",
Expand Down Expand Up @@ -807,6 +819,25 @@ nothrow:
CTFloat.sprint(&buffer[0], 'g', floatvalue);
strcat(&buffer[0], "Li");
break;
case TOK.interp:
{
OutBuffer buf;
buf.writeByte('i');
buf.writeByte('"');
foreach(idx, item; interp.components)
{
if(idx % 2 == 0)
buf.printf("%s", item.toChars());
else
buf.printf("${%s}", item.toChars());
}
buf.writeByte('"');
if(interp.postfix)
buf.writeByte(interp.postfix);
buf.writeByte(0);
p = buf.extractSlice().ptr;
}
break;
case TOK.string_:
{
OutBuffer buf;
Expand Down