379 changes: 203 additions & 176 deletions compiler/src/dmd/frontend.h

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions compiler/src/dmd/hdrgen.d
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,37 @@
buf.writeByte(e.postfix);
}

void visitInterpolation(InterpExp e)
{
buf.writeByte('i');
buf.writeByte('"');
const o = buf.length;

Check warning on line 2254 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2252-L2254

Added lines #L2252 - L2254 were not covered by tests

foreach (idx, str; e.interpolatedSet.parts)

Check warning on line 2256 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2256

Added line #L2256 was not covered by tests
{
if (idx % 2 == 0)

Check warning on line 2258 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2258

Added line #L2258 was not covered by tests
{
foreach(ch; str)
writeCharLiteral(buf, ch);

Check warning on line 2261 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2260-L2261

Added lines #L2260 - L2261 were not covered by tests
}
else
{
buf.writeByte('$');
buf.writeByte('(');
foreach(ch; str)
buf.writeByte(ch);
buf.writeByte(')');

Check warning on line 2269 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2265-L2269

Added lines #L2265 - L2269 were not covered by tests
}
}

if (hgs.ddoc)
escapeDdocString(buf, o);
buf.writeByte('"');
if (e.postfix)
buf.writeByte(e.postfix);

Check warning on line 2277 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2273-L2277

Added lines #L2273 - L2277 were not covered by tests

}

void visitArrayLiteral(ArrayLiteralExp e)
{
buf.writeByte('[');
Expand Down Expand Up @@ -2827,6 +2858,7 @@
case EXP.super_: return visitSuper(e.isSuperExp());
case EXP.null_: return visitNull(e.isNullExp());
case EXP.string_: return visitString(e.isStringExp());
case EXP.interpolated: return visitInterpolation(e.isInterpExp());

Check warning on line 2861 in compiler/src/dmd/hdrgen.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/hdrgen.d#L2861

Added line #L2861 was not covered by tests
case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp());
case EXP.assocArrayLiteral: return visitAssocArrayLiteral(e.isAssocArrayLiteralExp());
case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp());
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@ immutable Msgtable[] msgtable =
{ "_d_arrayassign_l" },
{ "_d_arrayassign_r" },

{ "imported" },
{ "InterpolationHeader" },
{ "InterpolationFooter" },
{ "InterpolatedLiteral" },
{ "InterpolatedExpression" },

// For pragma's
{ "Pinline", "inline" },
{ "lib" },
Expand Down
166 changes: 155 additions & 11 deletions compiler/src/dmd/lexer.d
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,29 @@
}
else
goto case_ident;
case 'i':
if (Ccompile)
goto case_ident;
if (p[1] == '"')
{
p++; // skip the i
escapeStringConstant(t, true);
return;
}
else if (p[1] == '`')
{
p++; // skip the i
wysiwygStringConstant(t, true);
return;
}
else if (p[1] == 'q' && p[2] == '{')
{
p += 2; // skip the i and q
tokenStringConstant(t, true);
return;
}
else
goto case_ident;
case '"':
escapeStringConstant(t);
return;
Expand All @@ -517,7 +540,7 @@
case 'f':
case 'g':
case 'h':
case 'i':
/*case 'i':*/
case 'j':
case 'k':
case 'l':
Expand Down Expand Up @@ -1429,9 +1452,18 @@
Params:
result = pointer to the token that accepts the result
*/
private void wysiwygStringConstant(Token* result)
private void wysiwygStringConstant(Token* result, bool supportInterpolation = false)
{
result.value = TOK.string_;
if (supportInterpolation)
{
result.value = TOK.interpolated;
result.interpolatedSet = null;
}
else
{
result.value = TOK.string_;
}

Loc start = loc();
auto terminator = p[0];
p++;
Expand All @@ -1451,6 +1483,14 @@
c = '\n'; // treat EndOfLine as \n character
endOfLine();
break;
case '$':
if (!supportInterpolation)
goto default;

if (!handleInterpolatedSegment(result, start))
goto default;

Check warning on line 1491 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1491

Added line #L1491 was not covered by tests

continue;
case 0:
case 0x1A:
error("unterminated string constant starting at %s", start.toChars());
Expand All @@ -1461,7 +1501,11 @@
default:
if (c == terminator)
{
result.setString(stringbuffer);
if (supportInterpolation)
result.appendInterpolatedPart(stringbuffer);
else
result.setString(stringbuffer);

stringPostfix(result);
return;
}
Expand Down Expand Up @@ -1736,13 +1780,21 @@
Params:
result = pointer to the token that accepts the result
*/
private void tokenStringConstant(Token* result)
private void tokenStringConstant(Token* result, bool supportInterpolation = false)
{
result.value = TOK.string_;
if (supportInterpolation)
{
result.value = TOK.interpolated;
result.interpolatedSet = null;
}
else
{
result.value = TOK.string_;
}

uint nest = 1;
const start = loc();
const pstart = ++p;
auto pstart = ++p;
inTokenStringConstant++;
scope(exit) inTokenStringConstant--;
while (1)
Expand All @@ -1757,10 +1809,28 @@
case TOK.rightCurly:
if (--nest == 0)
{
result.setString(pstart, p - 1 - pstart);
if (supportInterpolation)
result.appendInterpolatedPart(pstart, p - 1 - pstart);
else
result.setString(pstart, p - 1 - pstart);

stringPostfix(result);
return;
}
continue;
case TOK.dollar:
if (!supportInterpolation)
goto default;

stringbuffer.setsize(0);
stringbuffer.write(pstart, p - 1 - pstart);
if (!handleInterpolatedSegment(result, start))
goto default;

Check warning on line 1828 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1828

Added line #L1828 was not covered by tests

stringbuffer.setsize(0);

pstart = p;

continue;
case TOK.endOfFile:
error("unterminated token string constant starting at %s", start.toChars());
Expand All @@ -1772,6 +1842,52 @@
}
}

// returns true if it got special treatment as an interpolated segment
// otherwise returns false, indicating to treat it as just part of a normal string
private bool handleInterpolatedSegment(Token* token, Loc start)
{
switch(*p)
{
case '(':
// expression, at this level we need to scan until the closing ')'

// always put the string part in first
token.appendInterpolatedPart(stringbuffer);
stringbuffer.setsize(0);

int openParenCount = 1;
p++; // skip the first open paren
auto pstart = p;
while (openParenCount > 0)
{
// need to scan with the lexer to support embedded strings and other complex cases
Token tok;
scan(&tok);
if (tok.value == TOK.leftParenthesis)
openParenCount++;

Check warning on line 1867 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1867

Added line #L1867 was not covered by tests
if (tok.value == TOK.rightParenthesis)
openParenCount--;
if (tok.value == TOK.endOfFile)
{
// FIXME: make this error better, it spams a lot
error("unterminated interpolated string constant starting at %s", start.toChars());
return false;

Check warning on line 1874 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1873-L1874

Added lines #L1873 - L1874 were not covered by tests
}
}

// then put the interpolated string segment
token.appendInterpolatedPart(pstart[0 .. p - 1 - pstart]);

stringbuffer.setsize(0); // make sure this is reset from the last token scan
// otherwise something like i"$(func("thing")) stuff" can still include it

return true;
default:
// nothing special
return false;
}
}

/**
Scan a quoted string while building the processed string value by
handling escape sequences. The result is returned in the given `t` token.
Expand All @@ -1783,9 +1899,17 @@
* D https://dlang.org/spec/lex.html#double_quoted_strings
* ImportC C11 6.4.5
*/
private void escapeStringConstant(Token* t)
private void escapeStringConstant(Token* t, bool supportInterpolation = false)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably better to split this into two functions (with a bit of repetition) instead. Should help with compilation speed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have measurements of compilation speed difference?

Of course, such internal implementation details can be changed at any time.

{
t.value = TOK.string_;
if (supportInterpolation)
{
t.value = TOK.interpolated;
t.interpolatedSet = null;
}
else
{
t.value = TOK.string_;
}

const start = loc();
const tc = *p++; // opening quote
Expand Down Expand Up @@ -1813,11 +1937,28 @@
c = escapeSequence(c2);
stringbuffer.writeUTF8(c);
continue;
case '$':
if (supportInterpolation)

Check warning on line 1941 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1940-L1941

Added lines #L1940 - L1941 were not covered by tests
{
p++; // skip escaped $
stringbuffer.writeByte('$');
continue;

Check warning on line 1945 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1943-L1945

Added lines #L1943 - L1945 were not covered by tests
}
else
goto default;

Check warning on line 1948 in compiler/src/dmd/lexer.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/lexer.d#L1948

Added line #L1948 was not covered by tests
default:
c = escapeSequence(c2);
break;
}
break;
case '$':
if (!supportInterpolation)
goto default;

if (!handleInterpolatedSegment(t, start))
goto default;

continue;
case '\n':
endOfLine();
if (Ccompile)
Expand All @@ -1835,7 +1976,10 @@
case '"':
if (c != tc)
goto default;
t.setString(stringbuffer);
if (supportInterpolation)
t.appendInterpolatedPart(stringbuffer);
else
t.setString(stringbuffer);
if (!Ccompile)
stringPostfix(t);
return;
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dmd/parse.d
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString:
case TOK.file:
case TOK.fileFullPath:
Expand Down Expand Up @@ -5820,6 +5821,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.true_:
case TOK.false_:
case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString:
case TOK.leftParenthesis:
case TOK.cast_:
Expand Down Expand Up @@ -7313,6 +7315,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString:
case TOK.file:
case TOK.fileFullPath:
Expand Down Expand Up @@ -8177,6 +8180,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
nextToken();
break;

case TOK.interpolated:
e = new AST.InterpExp(loc, token.interpolatedSet, token.postfix);
nextToken();
break;

case TOK.string_:
case TOK.hexadecimalString:
const bool hexString = token.value == TOK.hexadecimalString;
Expand Down Expand Up @@ -8810,6 +8818,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral:
case TOK.dcharLiteral:
case TOK.string_:
case TOK.interpolated:
case TOK.function_:
case TOK.delegate_:
case TOK.typeof_:
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/parsetimevisitor.d
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
void visit(AST.TypeidExp e) { visit(cast(AST.Expression)e); }
void visit(AST.TraitsExp e) { visit(cast(AST.Expression)e); }
void visit(AST.StringExp e) { visit(cast(AST.Expression)e); }
void visit(AST.InterpExp e) { visit(cast(AST.Expression)e); }

Check warning on line 186 in compiler/src/dmd/parsetimevisitor.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/parsetimevisitor.d#L186

Added line #L186 was not covered by tests
void visit(AST.NewExp e) { visit(cast(AST.Expression)e); }
void visit(AST.AssocArrayLiteralExp e) { visit(cast(AST.Expression)e); }
void visit(AST.ArrayLiteralExp e) { visit(cast(AST.Expression)e); }
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/strictvisitor.d
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST
override void visit(AST.TypeidExp) { assert(0); }
override void visit(AST.TraitsExp) { assert(0); }
override void visit(AST.StringExp) { assert(0); }
override void visit(AST.InterpExp) { assert(0); }
override void visit(AST.NewExp) { assert(0); }
override void visit(AST.AssocArrayLiteralExp) { assert(0); }
override void visit(AST.ArrayLiteralExp) { assert(0); }
Expand Down
33 changes: 32 additions & 1 deletion compiler/src/dmd/tokens.d
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ enum TOK : ubyte
// Leaf operators
identifier,
string_,
interpolated,
hexadecimalString,
this_,
super_,
Expand Down Expand Up @@ -380,6 +381,7 @@ enum EXP : ubyte
// Leaf operators
identifier,
string_,
interpolated,
this_,
super_,
halt,
Expand Down Expand Up @@ -623,6 +625,10 @@ static immutable TOK[TOK.max + 1] Ckeywords =
}
} ();

struct InterpolatedSet {
// all strings in the parts are zero terminated at length+1
string[] parts;
}

/***********************************************************
*/
Expand All @@ -645,7 +651,11 @@ extern (C++) struct Token

struct
{
const(char)* ustring; // UTF8 string
union
{
const(char)* ustring; // UTF8 string
InterpolatedSet* interpolatedSet;
}
uint len;
ubyte postfix; // 'c', 'w', 'd'
}
Expand Down Expand Up @@ -833,6 +843,7 @@ extern (C++) struct Token
// For debugging
TOK.error: "error",
TOK.string_: "string",
TOK.interpolated: "interpolated string",
TOK.onScopeExit: "scope(exit)",
TOK.onScopeSuccess: "scope(success)",
TOK.onScopeFailure: "scope(failure)",
Expand Down Expand Up @@ -910,6 +921,24 @@ nothrow:
return 0;
}

extern(D) void appendInterpolatedPart(const ref OutBuffer buf) {
appendInterpolatedPart(cast(const(char)*)buf[].ptr, buf.length);
}
extern(D) void appendInterpolatedPart(const(char)[] str) {
appendInterpolatedPart(str.ptr, str.length);
}
extern(D) void appendInterpolatedPart(const(char)* ptr, size_t length) {
assert(value == TOK.interpolated);
if (interpolatedSet is null)
interpolatedSet = new InterpolatedSet;

auto s = cast(char*)mem.xmalloc_noscan(length + 1);
memcpy(s, ptr, length);
s[length] = 0;

interpolatedSet.parts ~= cast(string) s[0 .. length];
}

/****
* Set to contents of ptr[0..length]
* Params:
Expand All @@ -918,6 +947,7 @@ nothrow:
*/
void setString(const(char)* ptr, size_t length)
{
value = TOK.string_;
auto s = cast(char*)mem.xmalloc_noscan(length + 1);
memcpy(s, ptr, length);
s[length] = 0;
Expand All @@ -941,6 +971,7 @@ nothrow:
*/
void setString()
{
value = TOK.string_;
ustring = "";
len = 0;
postfix = 0;
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dmd/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ enum class TOK : unsigned char
// Leaf operators
identifier,
string_,
interpolated,
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
hexadecimalString,
this_,
super_,
Expand Down Expand Up @@ -390,6 +391,7 @@ enum class EXP : unsigned char
// Leaf operators
identifier,
string_,
interpolated,
this_,
super_,
halt,
Expand Down Expand Up @@ -461,7 +463,12 @@ struct Token
real_t floatvalue;

struct
{ utf8_t *ustring; // UTF8 string
{
union
{
utf8_t *ustring; // UTF8 string
void *interpolatedSet;
};
unsigned len;
unsigned char postfix; // 'c', 'w', 'd'
};
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dmd/visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class ThisExp;
class SuperExp;
class NullExp;
class StringExp;
class InterpExp;
class TupleExp;
class ArrayLiteralExp;
class AssocArrayLiteralExp;
Expand Down Expand Up @@ -480,6 +481,7 @@ class ParseTimeVisitor
virtual void visit(TypeidExp *e) { visit((Expression *)e); }
virtual void visit(TraitsExp *e) { visit((Expression *)e); }
virtual void visit(StringExp *e) { visit((Expression *)e); }
virtual void visit(InterpExp *e) { visit((Expression *)e); }
virtual void visit(NewExp *e) { visit((Expression *)e); }
virtual void visit(AssocArrayLiteralExp *e) { visit((Expression *)e); }
virtual void visit(ArrayLiteralExp *e) { visit((Expression *)e); }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* TEST_OUTPUT:
---
fail_compilation/interpolatedexpressionsequence_postfix.d(10): Error: String postfixes on interpolated expression sequences are not allowed.
fail_compilation/interpolatedexpressionsequence_postfix.d(11): Error: String postfixes on interpolated expression sequences are not allowed.
fail_compilation/interpolatedexpressionsequence_postfix.d(12): Error: String postfixes on interpolated expression sequences are not allowed.
---
*/
void main() {
// all postfixes are banned
auto c = i"foo"c;
auto w = i"foo"w;
auto d = i"foo"d;
}
51 changes: 51 additions & 0 deletions compiler/test/runnable/interpolatedexpressionsequence.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import core.interpolation;

alias AliasSeq(T...) = T;

string simpleToString(T...)(T thing) {
string s;
foreach(item; thing)
// all the items provided by core.interpolation have
// toString to return an appropriate value
//
// then this particular example only has embedded strings
// and chars, to we can append them directly
static if(__traits(hasMember, item, "toString"))
s ~= item.toString();
else
s ~= item;

return s;
}

void main() {
int a = 1;
string b = "one";
// parser won't permit alias = i".." directly; i"..." is meant to
// be used as a function/template parameter at this time.
alias expr = AliasSeq!i"$(a) $(b)";
// elements from the source code are available at compile time, so
// we static assert those, but the values, of course, are different
static assert(expr[0] == InterpolationHeader());
static assert(expr[1] == InterpolatedExpression!"a"());
assert(expr[2] == a); // actual value not available at compile time
static assert(expr[3] == InterpolatedLiteral!" "());
// the parens around the expression are not included
static assert(expr[4] == InterpolatedExpression!"b"());
assert(expr[5] == b); // actual value not available at compile time
static assert(expr[6] == InterpolationFooter());

// it does currently allow `auto` to be used, it creates a value tuple
// you can embed any D expressions inside the parenthesis, and the
// token is not ended until you get the *outer* ) and ".
auto thing = i"$(b) $("$" ~ ')' ~ `"`)";
assert(simpleToString(thing) == "one $)\"");
assert(simpleToString(i"$b") == "$b"); // support for $ident removed by popular demand
// i`` and iq{} should also work
assert(simpleToString(i` $(b) is $(b)!`) == " one is one!");
assert(simpleToString(iq{ $(b) is $(b)!}) == " one is one!");
assert(simpleToString(i`\$('$')`) == "\\$"); // no \ escape there
assert(simpleToString(iq{{$('$')}}) == "{$}"); // {} needs to work
}
1 change: 1 addition & 0 deletions compiler/test/unit/lexer/location_offset.d
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ enum ignoreTokens
showCtfeContext,
objcClassReference,
vectorArray,
interpolated,

wchar_tLiteral,
endOfLine,
Expand Down
1 change: 1 addition & 0 deletions druntime/mak/COPY
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ COPY=\
$(IMPDIR)\core\exception.d \
$(IMPDIR)\core\factory.d \
$(IMPDIR)\core\int128.d \
$(IMPDIR)\core\interpolation.d \
$(IMPDIR)\core\lifetime.d \
$(IMPDIR)\core\math.d \
$(IMPDIR)\core\memory.d \
Expand Down
1 change: 1 addition & 0 deletions druntime/mak/DOCS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ DOCS=\
$(DOCDIR)\core_checkedint.html \
$(DOCDIR)\core_exception.html \
$(DOCDIR)\core_int128.html \
$(DOCDIR)\core_interpolation.html \
$(DOCDIR)\core_math.html \
$(DOCDIR)\core_vararg.html \
$(DOCDIR)\core_volatile.html \
Expand Down
1 change: 1 addition & 0 deletions druntime/mak/SRCS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ SRCS=\
src\core\exception.d \
src\core\factory.d \
src\core\int128.d \
src\core\interpolation.d \
src\core\lifetime.d \
src\core\math.d \
src\core\memory.d \
Expand Down
156 changes: 156 additions & 0 deletions druntime/src/core/interpolation.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/++
This module provides definitions to support D's
interpolated expression sequence literal, sometimes
called string interpolation.
---
string str;
int num;
// the compiler uses this module to implement the
// i"..." literal used here.
auto a = i"$​(str) has $​(num) items.";
---
The variable `a` is a sequence of expressions:
---
a[0] == InterpolationHeader()
a[$-1] == InterpolationFooter()
---
First and last, you see the header and footer, to
clearly indicate where interpolation begins and ends.
Note that there may be nested interpolated sequences too,
each with their own header and footer. Think of them
as a set of balanced parenthesis around the contents.
Inside, you will find three general categories of
content: `InterpolatedLiteral!"string"` for string
expressions, `InterpolatedExpression!"code"` for code
expressions, and then the values themselves as their
own type.
In the example:
---
auto a = i"$​(str) has $​(num) items.";
---
We will find:
---
a[0] == InterpolationHeader()
a[1] == InterpolatedExpression!"str"
a[2] == str
a[3] == InterpolatedLiteral!" has ";
a[4] == InterpolatedExpression!"num";
a[5] == num
a[6] == InterpolatedLiteral!" items.";
a[7] == InterpolationFooter()
a.length == 8;
---
You can see the correspondence with the original
input: when you write `$​(expression)`, the string of the
expression is passed as `InterpolatedExpression!ThatString`,
(excluding any parenthesis around the expression),
and everything else is passed as `InterpolatedLiteral!str`,
in the same sequence as they appeared in the source.
After an `InterpolatedExpression!...`, you will find the
actual value(s) in the tuple. (If the expression expanded
to multiple values - for example, if it was itself a tuple,
there will be multiple values for a single expression.)
Library functions should NOT attempt to mixin the code
from an `InterpolatedExpression` themselves. Doing so
will fail, since it is coming from a different scope anyway.
The string is provided to you only for informational purposes
and as a sentinel to separate things the user wrote.
Your code should be able to handle an empty code string
in `InterpolatedExpression` or even an entirely missing
`InterpolatedExpression`, in case an implementation decides to
not emit these.
The `toString` members on these return `null`, except for
the `InterpolatedLiteral`, which returns the literal string.
This is to ease processing by generic functions like
`std.stdio.write` or `std.conv.text`, making them effectively
transparently skipped.
To extract the string from an `InterpolatedLiteral`, you can
use an `is` expression or the `.toString` method.
To extract the string from a `InterpolatedExpression`, you can
use an `is` expression or the `.expression` member.
None of these structures have runtime state.
History:
Added in dmd 2.10x frontend, released in late 2023.
+/
module core.interpolation;

/++
Sentinel values to indicate the beginning and end of an
interpolated expression sequence.
Note that these can nest, so while processing a sequence,
it may be helpful to keep a nesting count if that knowledge
is important to your application.
+/
struct InterpolationHeader {
/++
Returns `null` for easy compatibility with existing functions
like `std.stdio.writeln` and `std.conv.text`.
+/
string toString() const @nogc pure nothrow @safe {
return null;
}
}

/// ditto
struct InterpolationFooter {
/++
Returns `null` for easy compatibility with existing functions
like `std.stdio.writeln` and `std.conv.text`.
+/
string toString() const @nogc pure nothrow @safe {
return null;
}
}

/++
Represents a fragment of a string literal in between expressions
passed as part of an interpolated expression sequence.
+/
struct InterpolatedLiteral(string text) {
/++
Returns the text of the interpolated string literal for this
segment of the tuple, for easy access and compatibility with
existing functions like `std.stdio.writeln` and `std.conv.text`.
+/
string toString() const @nogc pure nothrow @safe {
return text;
}
}

/++
Represents the source code of an expression passed as part of an
interpolated expression sequence.
+/
struct InterpolatedExpression(string text) {
/++
Returns the text of an interpolated expression used in the
original literal, if provided by the implementation.
+/
enum expression = text;

/++
Returns `null` for easy compatibility with existing functions
like `std.stdio.writeln` and `std.conv.text`.
+/
string toString() const @nogc pure nothrow @safe {
return null;
}
}