Enhanced version of dip1036 implementation (#15715)

This implements the Enhanced Interpolated Expression Sequence proposal:

i"" or iq{} or q`` with a $(expression) in the middle are converted to a tuple of druntime types for future processing by library code.
This commit is contained in:
Adam D. Ruppe 2024-01-20 01:21:40 -05:00 committed by GitHub
parent 2efb88b8b3
commit d8dcb940ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 827 additions and 190 deletions

18
changelog/dmd.ies.dd Normal file
View file

@ -0,0 +1,18 @@
Add support for Interpolated Expression Sequences
Interpolated Expression Sequences are a way to implement things like string interpolation in library code. Three forms of literals are added:
```
i"Content $(a + 4)"
i`Content $(a + 4)`
iq{Content $(a + 4)}
```
all provide the same thing: a tuple that can be passed to other functions, like `writeln` from `std.stdio` and `text` from `std.conv`:
```
int a = 6;
writeln(i"Content $(a + 4)"); // prints "Content 10"
```
You can also pass them to other functions which understand the types in the new `core.interpolation` module. Numerous examples can be found documentation of that module or in this repository: https://github.com/adamdruppe/interpolation-examples/

View file

@ -4575,6 +4575,7 @@ struct ASTBase
inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; } inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; }
inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; } inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; }
inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; } inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; }
inout(InterpExp) isInterpExp() { return op == EXP.interpolated ? cast(typeof(return))this : null; }
inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; } inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; }
inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; } inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; }
inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; } inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; }
@ -4907,6 +4908,25 @@ struct ASTBase
} }
} }
extern (C++) final class InterpExp : Expression
{
InterpolatedSet* interpolatedSet;
char postfix = 0; // 'c', 'w', 'd'
extern (D) this(const ref Loc loc, InterpolatedSet* interpolatedSet, char postfix = 0)
{
super(loc, EXP.interpolated, __traits(classInstanceSize, InterpExp));
this.interpolatedSet = interpolatedSet;
this.postfix = postfix;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
extern (C++) final class StringExp : Expression extern (C++) final class StringExp : Expression
{ {
union union

View file

@ -5204,6 +5204,7 @@ void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
highlight = "$(D_COMMENT "; highlight = "$(D_COMMENT ";
break; break;
case TOK.string_: case TOK.string_:
case TOK.interpolated:
highlight = "$(D_STRING "; highlight = "$(D_STRING ";
break; break;
default: default:
@ -5216,7 +5217,7 @@ void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
res.writestring(highlight); res.writestring(highlight);
size_t o = res.length; size_t o = res.length;
highlightCode3(sc, res, tok.ptr, lex.p); highlightCode3(sc, res, tok.ptr, lex.p);
if (tok.value == TOK.comment || tok.value == TOK.string_) if (tok.value == TOK.comment || tok.value == TOK.string_ || tok.value == TOK.interpolated)
/* https://issues.dlang.org/show_bug.cgi?id=7656 /* https://issues.dlang.org/show_bug.cgi?id=7656
* https://issues.dlang.org/show_bug.cgi?id=7715 * https://issues.dlang.org/show_bug.cgi?id=7715
* https://issues.dlang.org/show_bug.cgi?id=10519 * https://issues.dlang.org/show_bug.cgi?id=10519

View file

@ -721,6 +721,7 @@ extern (C++) abstract class Expression : ASTNode
inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; } inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; }
inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; } inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; }
inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; } inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; }
inout(InterpExp) isInterpExp() { return op == EXP.interpolated ? cast(typeof(return))this : null; }
inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; } inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; }
inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; } inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; }
inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; } inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; }
@ -1847,6 +1848,28 @@ extern (C++) final class StringExp : Expression
} }
} }
extern (C++) final class InterpExp : Expression
{
char postfix = NoPostfix; // 'c', 'w', 'd'
OwnedBy ownedByCtfe = OwnedBy.code;
InterpolatedSet* interpolatedSet;
enum char NoPostfix = 0;
extern (D) this(const ref Loc loc, InterpolatedSet* set, char postfix = NoPostfix) scope
{
super(loc, EXP.interpolated);
this.interpolatedSet = set;
this.postfix = postfix;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/*********************************************************** /***********************************************************
* A sequence of expressions * A sequence of expressions
* *
@ -5494,6 +5517,7 @@ private immutable ubyte[EXP.max+1] expSize = [
EXP.preMinusMinus: __traits(classInstanceSize, PreExp), EXP.preMinusMinus: __traits(classInstanceSize, PreExp),
EXP.identifier: __traits(classInstanceSize, IdentifierExp), EXP.identifier: __traits(classInstanceSize, IdentifierExp),
EXP.string_: __traits(classInstanceSize, StringExp), EXP.string_: __traits(classInstanceSize, StringExp),
EXP.interpolated: __traits(classInstanceSize, InterpExp),
EXP.this_: __traits(classInstanceSize, ThisExp), EXP.this_: __traits(classInstanceSize, ThisExp),
EXP.super_: __traits(classInstanceSize, SuperExp), EXP.super_: __traits(classInstanceSize, SuperExp),
EXP.halt: __traits(classInstanceSize, HaltExp), EXP.halt: __traits(classInstanceSize, HaltExp),

View file

@ -38,6 +38,7 @@ class TemplateDeclaration;
class ClassDeclaration; class ClassDeclaration;
class OverloadSet; class OverloadSet;
class StringExp; class StringExp;
class InterpExp;
class LoweredAssignExp; class LoweredAssignExp;
#ifdef IN_GCC #ifdef IN_GCC
typedef union tree_node Symbol; typedef union tree_node Symbol;
@ -129,6 +130,7 @@ public:
SuperExp* isSuperExp(); SuperExp* isSuperExp();
NullExp* isNullExp(); NullExp* isNullExp();
StringExp* isStringExp(); StringExp* isStringExp();
InterpExp* isInterpExp();
TupleExp* isTupleExp(); TupleExp* isTupleExp();
ArrayLiteralExp* isArrayLiteralExp(); ArrayLiteralExp* isArrayLiteralExp();
AssocArrayLiteralExp* isAssocArrayLiteralExp(); AssocArrayLiteralExp* isAssocArrayLiteralExp();
@ -370,6 +372,16 @@ public:
void writeTo(void* dest, bool zero, int tyto = 0) const; void writeTo(void* dest, bool zero, int tyto = 0) const;
}; };
class InterpExp final : public Expression
{
public:
utf8_t postfix; // 'c', 'w', 'd'
OwnedBy ownedByCtfe;
void* interpolatedSet;
void accept(Visitor* v) override { v->visit(this); }
};
// Tuple // Tuple
class TupleExp final : public Expression class TupleExp final : public Expression

View file

@ -4145,6 +4145,84 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
result = e; result = e;
} }
override void visit(InterpExp e)
{
// the lexer breaks up into an odd/even array of literals and expression code
// we need to turn that into:
/+
tuple(
.object.imported!"core.interpolation".InterpolationHeader(),
...
.object.imported!"core.interpolation".InterpolationFooter()
)
There the ... loops through them all, making the even ones
.object.imported!"core.interpolation".InterpolatedLiteral!str()
and making the odd ones
.object.imported!"core.interpolation".InterpolatedExpression!str(),
the code represented by str
Empty string literals are skipped as they provide no additional information.
+/
if (e.postfix)
error(e.loc, "String postfixes on interpolated expression sequences are not allowed.");
Expression makeNonTemplateItem(Identifier which) {
Expression id = new IdentifierExp(e.loc, Id.empty);
id = new DotIdExp(e.loc, id, Id.object);
auto moduleNameArgs = new Objects();
moduleNameArgs.push(new StringExp(e.loc, "core.interpolation"));
id = new DotTemplateInstanceExp(e.loc, id, Id.imported, moduleNameArgs);
id = new DotIdExp(e.loc, id, which);
id = new CallExp(e.loc, id, new Expressions());
return id;
}
Expression makeTemplateItem(Identifier which, string arg) {
Expression id = new IdentifierExp(e.loc, Id.empty);
id = new DotIdExp(e.loc, id, Id.object);
auto moduleNameArgs = new Objects();
moduleNameArgs.push(new StringExp(e.loc, "core.interpolation"));
id = new DotTemplateInstanceExp(e.loc, id, Id.imported, moduleNameArgs);
auto tiargs = new Objects();
auto templateStringArg = new StringExp(e.loc, arg);
// banning those instead of forwarding them
// templateStringArg.postfix = e.postfix; // forward the postfix to these literals
tiargs.push(templateStringArg);
id = new DotTemplateInstanceExp(e.loc, id, which, tiargs);
id = new CallExp(e.loc, id, new Expressions());
return id;
}
auto arguments = new Expressions();
arguments.push(makeNonTemplateItem(Id.InterpolationHeader));
foreach (idx, str; e.interpolatedSet.parts)
{
if (idx % 2 == 0)
{
if (str.length > 0)
arguments.push(makeTemplateItem(Id.InterpolatedLiteral, str));
}
else
{
arguments.push(makeTemplateItem(Id.InterpolatedExpression, str));
Expressions* mix = new Expressions();
mix.push(new StringExp(e.loc, str));
// FIXME: i'd rather not use MixinExp but idk how to do it lol
arguments.push(new MixinExp(e.loc, mix));
}
}
arguments.push(makeNonTemplateItem(Id.InterpolationFooter));
auto loweredTo = new TupleExp(e.loc, arguments);
visit(loweredTo);
result = loweredTo;
}
override void visit(StringExp e) override void visit(StringExp e)
{ {
static if (LOGSEMANTIC) static if (LOGSEMANTIC)

View file

@ -183,6 +183,7 @@ class DsymbolExp;
class ThisExp; class ThisExp;
class SuperExp; class SuperExp;
class NullExp; class NullExp;
class InterpExp;
class TupleExp; class TupleExp;
class ArrayLiteralExp; class ArrayLiteralExp;
class AssocArrayLiteralExp; class AssocArrayLiteralExp;
@ -285,6 +286,7 @@ class ThrownExceptionExp;
class UnaExp; class UnaExp;
class BinExp; class BinExp;
class BinAssignExp; class BinAssignExp;
struct InterpolatedSet;
struct ContractInfo; struct ContractInfo;
struct ObjcSelector; struct ObjcSelector;
class PeelStatement; class PeelStatement;
@ -1091,6 +1093,7 @@ public:
virtual void visit(typename AST::TypeidExp e); virtual void visit(typename AST::TypeidExp e);
virtual void visit(typename AST::TraitsExp e); virtual void visit(typename AST::TraitsExp e);
virtual void visit(typename AST::StringExp e); virtual void visit(typename AST::StringExp e);
virtual void visit(typename AST::InterpExp e);
virtual void visit(typename AST::NewExp e); virtual void visit(typename AST::NewExp e);
virtual void visit(typename AST::AssocArrayLiteralExp e); virtual void visit(typename AST::AssocArrayLiteralExp e);
virtual void visit(typename AST::ArrayLiteralExp e); virtual void visit(typename AST::ArrayLiteralExp e);
@ -2049,44 +2052,45 @@ enum class EXP : uint8_t
preMinusMinus = 86u, preMinusMinus = 86u,
identifier = 87u, identifier = 87u,
string_ = 88u, string_ = 88u,
this_ = 89u, interpolated = 89u,
super_ = 90u, this_ = 90u,
halt = 91u, super_ = 91u,
tuple = 92u, halt = 92u,
error = 93u, tuple = 93u,
void_ = 94u, error = 94u,
int64 = 95u, void_ = 95u,
float64 = 96u, int64 = 96u,
complex80 = 97u, float64 = 97u,
import_ = 98u, complex80 = 98u,
delegate_ = 99u, import_ = 99u,
function_ = 100u, delegate_ = 100u,
mixin_ = 101u, function_ = 101u,
in_ = 102u, mixin_ = 102u,
break_ = 103u, in_ = 103u,
continue_ = 104u, break_ = 104u,
goto_ = 105u, continue_ = 105u,
scope_ = 106u, goto_ = 106u,
traits = 107u, scope_ = 107u,
overloadSet = 108u, traits = 108u,
line = 109u, overloadSet = 109u,
file = 110u, line = 110u,
fileFullPath = 111u, file = 111u,
moduleString = 112u, fileFullPath = 112u,
functionString = 113u, moduleString = 113u,
prettyFunction = 114u, functionString = 114u,
pow = 115u, prettyFunction = 115u,
powAssign = 116u, pow = 116u,
vector = 117u, powAssign = 117u,
voidExpression = 118u, vector = 118u,
cantExpression = 119u, voidExpression = 119u,
showCtfeContext = 120u, cantExpression = 120u,
objcClassReference = 121u, showCtfeContext = 121u,
vectorArray = 122u, objcClassReference = 122u,
compoundLiteral = 123u, vectorArray = 123u,
_Generic_ = 124u, compoundLiteral = 124u,
interval = 125u, _Generic_ = 125u,
loweredAssignExp = 126u, interval = 126u,
loweredAssignExp = 127u,
}; };
struct complex_t final struct complex_t final
@ -2154,6 +2158,7 @@ public:
SuperExp* isSuperExp(); SuperExp* isSuperExp();
NullExp* isNullExp(); NullExp* isNullExp();
StringExp* isStringExp(); StringExp* isStringExp();
InterpExp* isInterpExp();
TupleExp* isTupleExp(); TupleExp* isTupleExp();
ArrayLiteralExp* isArrayLiteralExp(); ArrayLiteralExp* isArrayLiteralExp();
AssocArrayLiteralExp* isAssocArrayLiteralExp(); AssocArrayLiteralExp* isAssocArrayLiteralExp();
@ -2792,143 +2797,144 @@ enum class TOK : uint8_t
dcharLiteral = 85u, dcharLiteral = 85u,
identifier = 86u, identifier = 86u,
string_ = 87u, string_ = 87u,
hexadecimalString = 88u, interpolated = 88u,
this_ = 89u, hexadecimalString = 89u,
super_ = 90u, this_ = 90u,
error = 91u, super_ = 91u,
void_ = 92u, error = 92u,
int8 = 93u, void_ = 93u,
uns8 = 94u, int8 = 94u,
int16 = 95u, uns8 = 95u,
uns16 = 96u, int16 = 96u,
int32 = 97u, uns16 = 97u,
uns32 = 98u, int32 = 98u,
int64 = 99u, uns32 = 99u,
uns64 = 100u, int64 = 100u,
int128 = 101u, uns64 = 101u,
uns128 = 102u, int128 = 102u,
float32 = 103u, uns128 = 103u,
float64 = 104u, float32 = 104u,
float80 = 105u, float64 = 105u,
imaginary32 = 106u, float80 = 106u,
imaginary64 = 107u, imaginary32 = 107u,
imaginary80 = 108u, imaginary64 = 108u,
complex32 = 109u, imaginary80 = 109u,
complex64 = 110u, complex32 = 110u,
complex80 = 111u, complex64 = 111u,
char_ = 112u, complex80 = 112u,
wchar_ = 113u, char_ = 113u,
dchar_ = 114u, wchar_ = 114u,
bool_ = 115u, dchar_ = 115u,
struct_ = 116u, bool_ = 116u,
class_ = 117u, struct_ = 117u,
interface_ = 118u, class_ = 118u,
union_ = 119u, interface_ = 119u,
enum_ = 120u, union_ = 120u,
import_ = 121u, enum_ = 121u,
alias_ = 122u, import_ = 122u,
override_ = 123u, alias_ = 123u,
delegate_ = 124u, override_ = 124u,
function_ = 125u, delegate_ = 125u,
mixin_ = 126u, function_ = 126u,
align_ = 127u, mixin_ = 127u,
extern_ = 128u, align_ = 128u,
private_ = 129u, extern_ = 129u,
protected_ = 130u, private_ = 130u,
public_ = 131u, protected_ = 131u,
export_ = 132u, public_ = 132u,
static_ = 133u, export_ = 133u,
final_ = 134u, static_ = 134u,
const_ = 135u, final_ = 135u,
abstract_ = 136u, const_ = 136u,
debug_ = 137u, abstract_ = 137u,
deprecated_ = 138u, debug_ = 138u,
in_ = 139u, deprecated_ = 139u,
out_ = 140u, in_ = 140u,
inout_ = 141u, out_ = 141u,
lazy_ = 142u, inout_ = 142u,
auto_ = 143u, lazy_ = 143u,
package_ = 144u, auto_ = 144u,
immutable_ = 145u, package_ = 145u,
if_ = 146u, immutable_ = 146u,
else_ = 147u, if_ = 147u,
while_ = 148u, else_ = 148u,
for_ = 149u, while_ = 149u,
do_ = 150u, for_ = 150u,
switch_ = 151u, do_ = 151u,
case_ = 152u, switch_ = 152u,
default_ = 153u, case_ = 153u,
break_ = 154u, default_ = 154u,
continue_ = 155u, break_ = 155u,
with_ = 156u, continue_ = 156u,
synchronized_ = 157u, with_ = 157u,
return_ = 158u, synchronized_ = 158u,
goto_ = 159u, return_ = 159u,
try_ = 160u, goto_ = 160u,
catch_ = 161u, try_ = 161u,
finally_ = 162u, catch_ = 162u,
asm_ = 163u, finally_ = 163u,
foreach_ = 164u, asm_ = 164u,
foreach_reverse_ = 165u, foreach_ = 165u,
scope_ = 166u, foreach_reverse_ = 166u,
onScopeExit = 167u, scope_ = 167u,
onScopeFailure = 168u, onScopeExit = 168u,
onScopeSuccess = 169u, onScopeFailure = 169u,
invariant_ = 170u, onScopeSuccess = 170u,
unittest_ = 171u, invariant_ = 171u,
argumentTypes = 172u, unittest_ = 172u,
ref_ = 173u, argumentTypes = 173u,
macro_ = 174u, ref_ = 174u,
parameters = 175u, macro_ = 175u,
traits = 176u, parameters = 176u,
pure_ = 177u, traits = 177u,
nothrow_ = 178u, pure_ = 178u,
gshared = 179u, nothrow_ = 179u,
line = 180u, gshared = 180u,
file = 181u, line = 181u,
fileFullPath = 182u, file = 182u,
moduleString = 183u, fileFullPath = 183u,
functionString = 184u, moduleString = 184u,
prettyFunction = 185u, functionString = 185u,
shared_ = 186u, prettyFunction = 186u,
at = 187u, shared_ = 187u,
pow = 188u, at = 188u,
powAssign = 189u, pow = 189u,
goesTo = 190u, powAssign = 190u,
vector = 191u, goesTo = 191u,
pound = 192u, vector = 192u,
arrow = 193u, pound = 193u,
colonColon = 194u, arrow = 194u,
wchar_tLiteral = 195u, colonColon = 195u,
endOfLine = 196u, wchar_tLiteral = 196u,
whitespace = 197u, endOfLine = 197u,
inline_ = 198u, whitespace = 198u,
register_ = 199u, inline_ = 199u,
restrict_ = 200u, register_ = 200u,
signed_ = 201u, restrict_ = 201u,
sizeof_ = 202u, signed_ = 202u,
typedef_ = 203u, sizeof_ = 203u,
unsigned_ = 204u, typedef_ = 204u,
volatile_ = 205u, unsigned_ = 205u,
_Alignas_ = 206u, volatile_ = 206u,
_Alignof_ = 207u, _Alignas_ = 207u,
_Atomic_ = 208u, _Alignof_ = 208u,
_Bool_ = 209u, _Atomic_ = 209u,
_Complex_ = 210u, _Bool_ = 210u,
_Generic_ = 211u, _Complex_ = 211u,
_Imaginary_ = 212u, _Generic_ = 212u,
_Noreturn_ = 213u, _Imaginary_ = 213u,
_Static_assert_ = 214u, _Noreturn_ = 214u,
_Thread_local_ = 215u, _Static_assert_ = 215u,
_assert_ = 216u, _Thread_local_ = 216u,
_import_ = 217u, _assert_ = 217u,
__cdecl_ = 218u, _import_ = 218u,
__declspec_ = 219u, __cdecl_ = 219u,
__stdcall_ = 220u, __declspec_ = 220u,
__thread_ = 221u, __stdcall_ = 221u,
__pragma_ = 222u, __thread_ = 222u,
__int128_ = 223u, __pragma_ = 223u,
__attribute___ = 224u, __int128_ = 224u,
__attribute___ = 225u,
}; };
class FuncExp final : public Expression class FuncExp final : public Expression
@ -3013,6 +3019,17 @@ public:
static IntegerExp* createBool(bool b); static IntegerExp* createBool(bool b);
}; };
class InterpExp final : public Expression
{
public:
char postfix;
OwnedBy ownedByCtfe;
InterpolatedSet* interpolatedSet;
enum : char { NoPostfix = 0u };
void accept(Visitor* v) override;
};
class IntervalExp final : public Expression class IntervalExp final : public Expression
{ {
public: public:
@ -5730,6 +5747,7 @@ struct ASTCodegen final
using InExp = ::InExp; using InExp = ::InExp;
using IndexExp = ::IndexExp; using IndexExp = ::IndexExp;
using IntegerExp = ::IntegerExp; using IntegerExp = ::IntegerExp;
using InterpExp = ::InterpExp;
using IntervalExp = ::IntervalExp; using IntervalExp = ::IntervalExp;
using IsExp = ::IsExp; using IsExp = ::IsExp;
using LineInitExp = ::LineInitExp; using LineInitExp = ::LineInitExp;
@ -8662,6 +8680,11 @@ struct Id final
static Identifier* _d_arraysetassign; static Identifier* _d_arraysetassign;
static Identifier* _d_arrayassign_l; static Identifier* _d_arrayassign_l;
static Identifier* _d_arrayassign_r; static Identifier* _d_arrayassign_r;
static Identifier* imported;
static Identifier* InterpolationHeader;
static Identifier* InterpolationFooter;
static Identifier* InterpolatedLiteral;
static Identifier* InterpolatedExpression;
static Identifier* Pinline; static Identifier* Pinline;
static Identifier* lib; static Identifier* lib;
static Identifier* linkerDirective; static Identifier* linkerDirective;
@ -8889,7 +8912,11 @@ struct Token final
_d_real floatvalue; _d_real floatvalue;
struct struct
{ {
const char* ustring; union
{
const char* ustring;
InterpolatedSet* interpolatedSet;
};
uint32_t len; uint32_t len;
uint8_t postfix; uint8_t postfix;
}; };

View file

@ -2247,6 +2247,37 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt
buf.writeByte(e.postfix); buf.writeByte(e.postfix);
} }
void visitInterpolation(InterpExp e)
{
buf.writeByte('i');
buf.writeByte('"');
const o = buf.length;
foreach (idx, str; e.interpolatedSet.parts)
{
if (idx % 2 == 0)
{
foreach(ch; str)
writeCharLiteral(buf, ch);
}
else
{
buf.writeByte('$');
buf.writeByte('(');
foreach(ch; str)
buf.writeByte(ch);
buf.writeByte(')');
}
}
if (hgs.ddoc)
escapeDdocString(buf, o);
buf.writeByte('"');
if (e.postfix)
buf.writeByte(e.postfix);
}
void visitArrayLiteral(ArrayLiteralExp e) void visitArrayLiteral(ArrayLiteralExp e)
{ {
buf.writeByte('['); buf.writeByte('[');
@ -2827,6 +2858,7 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt
case EXP.super_: return visitSuper(e.isSuperExp()); case EXP.super_: return visitSuper(e.isSuperExp());
case EXP.null_: return visitNull(e.isNullExp()); case EXP.null_: return visitNull(e.isNullExp());
case EXP.string_: return visitString(e.isStringExp()); case EXP.string_: return visitString(e.isStringExp());
case EXP.interpolated: return visitInterpolation(e.isInterpExp());
case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp()); case EXP.arrayLiteral: return visitArrayLiteral(e.isArrayLiteralExp());
case EXP.assocArrayLiteral: return visitAssocArrayLiteral(e.isAssocArrayLiteralExp()); case EXP.assocArrayLiteral: return visitAssocArrayLiteral(e.isAssocArrayLiteralExp());
case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp()); case EXP.structLiteral: return visitStructLiteral(e.isStructLiteralExp());

View file

@ -335,6 +335,12 @@ immutable Msgtable[] msgtable =
{ "_d_arrayassign_l" }, { "_d_arrayassign_l" },
{ "_d_arrayassign_r" }, { "_d_arrayassign_r" },
{ "imported" },
{ "InterpolationHeader" },
{ "InterpolationFooter" },
{ "InterpolatedLiteral" },
{ "InterpolatedExpression" },
// For pragma's // For pragma's
{ "Pinline", "inline" }, { "Pinline", "inline" },
{ "lib" }, { "lib" },

View file

@ -506,6 +506,29 @@ class Lexer
} }
else else
goto case_ident; 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 '"': case '"':
escapeStringConstant(t); escapeStringConstant(t);
return; return;
@ -517,7 +540,7 @@ class Lexer
case 'f': case 'f':
case 'g': case 'g':
case 'h': case 'h':
case 'i': /*case 'i':*/
case 'j': case 'j':
case 'k': case 'k':
case 'l': case 'l':
@ -1429,9 +1452,18 @@ class Lexer
Params: Params:
result = pointer to the token that accepts the result 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(); Loc start = loc();
auto terminator = p[0]; auto terminator = p[0];
p++; p++;
@ -1451,6 +1483,14 @@ class Lexer
c = '\n'; // treat EndOfLine as \n character c = '\n'; // treat EndOfLine as \n character
endOfLine(); endOfLine();
break; break;
case '$':
if (!supportInterpolation)
goto default;
if (!handleInterpolatedSegment(result, start))
goto default;
continue;
case 0: case 0:
case 0x1A: case 0x1A:
error("unterminated string constant starting at %s", start.toChars()); error("unterminated string constant starting at %s", start.toChars());
@ -1461,7 +1501,11 @@ class Lexer
default: default:
if (c == terminator) if (c == terminator)
{ {
result.setString(stringbuffer); if (supportInterpolation)
result.appendInterpolatedPart(stringbuffer);
else
result.setString(stringbuffer);
stringPostfix(result); stringPostfix(result);
return; return;
} }
@ -1736,13 +1780,21 @@ class Lexer
Params: Params:
result = pointer to the token that accepts the result 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; uint nest = 1;
const start = loc(); const start = loc();
const pstart = ++p; auto pstart = ++p;
inTokenStringConstant++; inTokenStringConstant++;
scope(exit) inTokenStringConstant--; scope(exit) inTokenStringConstant--;
while (1) while (1)
@ -1757,10 +1809,28 @@ class Lexer
case TOK.rightCurly: case TOK.rightCurly:
if (--nest == 0) 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); stringPostfix(result);
return; return;
} }
continue;
case TOK.dollar:
if (!supportInterpolation)
goto default;
stringbuffer.setsize(0);
stringbuffer.write(pstart, p - 1 - pstart);
if (!handleInterpolatedSegment(result, start))
goto default;
stringbuffer.setsize(0);
pstart = p;
continue; continue;
case TOK.endOfFile: case TOK.endOfFile:
error("unterminated token string constant starting at %s", start.toChars()); error("unterminated token string constant starting at %s", start.toChars());
@ -1772,6 +1842,52 @@ class Lexer
} }
} }
// 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++;
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;
}
}
// 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 Scan a quoted string while building the processed string value by
handling escape sequences. The result is returned in the given `t` token. handling escape sequences. The result is returned in the given `t` token.
@ -1783,9 +1899,17 @@ class Lexer
* D https://dlang.org/spec/lex.html#double_quoted_strings * D https://dlang.org/spec/lex.html#double_quoted_strings
* ImportC C11 6.4.5 * ImportC C11 6.4.5
*/ */
private void escapeStringConstant(Token* t) private void escapeStringConstant(Token* t, bool supportInterpolation = false)
{ {
t.value = TOK.string_; if (supportInterpolation)
{
t.value = TOK.interpolated;
t.interpolatedSet = null;
}
else
{
t.value = TOK.string_;
}
const start = loc(); const start = loc();
const tc = *p++; // opening quote const tc = *p++; // opening quote
@ -1813,11 +1937,28 @@ class Lexer
c = escapeSequence(c2); c = escapeSequence(c2);
stringbuffer.writeUTF8(c); stringbuffer.writeUTF8(c);
continue; continue;
case '$':
if (supportInterpolation)
{
p++; // skip escaped $
stringbuffer.writeByte('$');
continue;
}
else
goto default;
default: default:
c = escapeSequence(c2); c = escapeSequence(c2);
break; break;
} }
break; break;
case '$':
if (!supportInterpolation)
goto default;
if (!handleInterpolatedSegment(t, start))
goto default;
continue;
case '\n': case '\n':
endOfLine(); endOfLine();
if (Ccompile) if (Ccompile)
@ -1835,7 +1976,10 @@ class Lexer
case '"': case '"':
if (c != tc) if (c != tc)
goto default; goto default;
t.setString(stringbuffer); if (supportInterpolation)
t.appendInterpolatedPart(stringbuffer);
else
t.setString(stringbuffer);
if (!Ccompile) if (!Ccompile)
stringPostfix(t); stringPostfix(t);
return; return;

View file

@ -2015,6 +2015,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral: case TOK.wcharLiteral:
case TOK.dcharLiteral: case TOK.dcharLiteral:
case TOK.string_: case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString: case TOK.hexadecimalString:
case TOK.file: case TOK.file:
case TOK.fileFullPath: case TOK.fileFullPath:
@ -5820,6 +5821,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.true_: case TOK.true_:
case TOK.false_: case TOK.false_:
case TOK.string_: case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString: case TOK.hexadecimalString:
case TOK.leftParenthesis: case TOK.leftParenthesis:
case TOK.cast_: case TOK.cast_:
@ -7313,6 +7315,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral: case TOK.wcharLiteral:
case TOK.dcharLiteral: case TOK.dcharLiteral:
case TOK.string_: case TOK.string_:
case TOK.interpolated:
case TOK.hexadecimalString: case TOK.hexadecimalString:
case TOK.file: case TOK.file:
case TOK.fileFullPath: case TOK.fileFullPath:
@ -8177,6 +8180,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
nextToken(); nextToken();
break; break;
case TOK.interpolated:
e = new AST.InterpExp(loc, token.interpolatedSet, token.postfix);
nextToken();
break;
case TOK.string_: case TOK.string_:
case TOK.hexadecimalString: case TOK.hexadecimalString:
const bool hexString = token.value == TOK.hexadecimalString; const bool hexString = token.value == TOK.hexadecimalString;
@ -8810,6 +8818,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.wcharLiteral: case TOK.wcharLiteral:
case TOK.dcharLiteral: case TOK.dcharLiteral:
case TOK.string_: case TOK.string_:
case TOK.interpolated:
case TOK.function_: case TOK.function_:
case TOK.delegate_: case TOK.delegate_:
case TOK.typeof_: case TOK.typeof_:

View file

@ -183,6 +183,7 @@ public:
void visit(AST.TypeidExp e) { visit(cast(AST.Expression)e); } void visit(AST.TypeidExp e) { visit(cast(AST.Expression)e); }
void visit(AST.TraitsExp 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.StringExp e) { visit(cast(AST.Expression)e); }
void visit(AST.InterpExp e) { visit(cast(AST.Expression)e); }
void visit(AST.NewExp e) { visit(cast(AST.Expression)e); } void visit(AST.NewExp e) { visit(cast(AST.Expression)e); }
void visit(AST.AssocArrayLiteralExp 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); } void visit(AST.ArrayLiteralExp e) { visit(cast(AST.Expression)e); }

View file

@ -138,6 +138,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST
override void visit(AST.TypeidExp) { assert(0); } override void visit(AST.TypeidExp) { assert(0); }
override void visit(AST.TraitsExp) { assert(0); } override void visit(AST.TraitsExp) { assert(0); }
override void visit(AST.StringExp) { 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.NewExp) { assert(0); }
override void visit(AST.AssocArrayLiteralExp) { assert(0); } override void visit(AST.AssocArrayLiteralExp) { assert(0); }
override void visit(AST.ArrayLiteralExp) { assert(0); } override void visit(AST.ArrayLiteralExp) { assert(0); }

View file

@ -124,6 +124,7 @@ enum TOK : ubyte
// Leaf operators // Leaf operators
identifier, identifier,
string_, string_,
interpolated,
hexadecimalString, hexadecimalString,
this_, this_,
super_, super_,
@ -380,6 +381,7 @@ enum EXP : ubyte
// Leaf operators // Leaf operators
identifier, identifier,
string_, string_,
interpolated,
this_, this_,
super_, super_,
halt, halt,
@ -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;
}
/*********************************************************** /***********************************************************
*/ */
@ -645,7 +651,11 @@ extern (C++) struct Token
struct struct
{ {
const(char)* ustring; // UTF8 string union
{
const(char)* ustring; // UTF8 string
InterpolatedSet* interpolatedSet;
}
uint len; uint len;
ubyte postfix; // 'c', 'w', 'd' ubyte postfix; // 'c', 'w', 'd'
} }
@ -833,6 +843,7 @@ extern (C++) struct Token
// For debugging // For debugging
TOK.error: "error", TOK.error: "error",
TOK.string_: "string", TOK.string_: "string",
TOK.interpolated: "interpolated string",
TOK.onScopeExit: "scope(exit)", TOK.onScopeExit: "scope(exit)",
TOK.onScopeSuccess: "scope(success)", TOK.onScopeSuccess: "scope(success)",
TOK.onScopeFailure: "scope(failure)", TOK.onScopeFailure: "scope(failure)",
@ -910,6 +921,24 @@ nothrow:
return 0; 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] * Set to contents of ptr[0..length]
* Params: * Params:
@ -918,6 +947,7 @@ nothrow:
*/ */
void setString(const(char)* ptr, size_t length) void setString(const(char)* ptr, size_t length)
{ {
value = TOK.string_;
auto s = cast(char*)mem.xmalloc_noscan(length + 1); auto s = cast(char*)mem.xmalloc_noscan(length + 1);
memcpy(s, ptr, length); memcpy(s, ptr, length);
s[length] = 0; s[length] = 0;
@ -941,6 +971,7 @@ nothrow:
*/ */
void setString() void setString()
{ {
value = TOK.string_;
ustring = ""; ustring = "";
len = 0; len = 0;
postfix = 0; postfix = 0;

View file

@ -133,6 +133,7 @@ enum class TOK : unsigned char
// Leaf operators // Leaf operators
identifier, identifier,
string_, string_,
interpolated,
hexadecimalString, hexadecimalString,
this_, this_,
super_, super_,
@ -390,6 +391,7 @@ enum class EXP : unsigned char
// Leaf operators // Leaf operators
identifier, identifier,
string_, string_,
interpolated,
this_, this_,
super_, super_,
halt, halt,
@ -461,7 +463,12 @@ struct Token
real_t floatvalue; real_t floatvalue;
struct struct
{ utf8_t *ustring; // UTF8 string {
union
{
utf8_t *ustring; // UTF8 string
void *interpolatedSet;
};
unsigned len; unsigned len;
unsigned char postfix; // 'c', 'w', 'd' unsigned char postfix; // 'c', 'w', 'd'
}; };

View file

@ -195,6 +195,7 @@ class ThisExp;
class SuperExp; class SuperExp;
class NullExp; class NullExp;
class StringExp; class StringExp;
class InterpExp;
class TupleExp; class TupleExp;
class ArrayLiteralExp; class ArrayLiteralExp;
class AssocArrayLiteralExp; class AssocArrayLiteralExp;
@ -480,6 +481,7 @@ public:
virtual void visit(TypeidExp *e) { visit((Expression *)e); } virtual void visit(TypeidExp *e) { visit((Expression *)e); }
virtual void visit(TraitsExp *e) { visit((Expression *)e); } virtual void visit(TraitsExp *e) { visit((Expression *)e); }
virtual void visit(StringExp *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(NewExp *e) { visit((Expression *)e); }
virtual void visit(AssocArrayLiteralExp *e) { visit((Expression *)e); } virtual void visit(AssocArrayLiteralExp *e) { visit((Expression *)e); }
virtual void visit(ArrayLiteralExp *e) { visit((Expression *)e); } virtual void visit(ArrayLiteralExp *e) { visit((Expression *)e); }

View file

@ -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;
}

View file

@ -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
}

View file

@ -515,6 +515,7 @@ enum ignoreTokens
showCtfeContext, showCtfeContext,
objcClassReference, objcClassReference,
vectorArray, vectorArray,
interpolated,
wchar_tLiteral, wchar_tLiteral,
endOfLine, endOfLine,

View file

@ -17,6 +17,7 @@ COPY=\
$(IMPDIR)\core\exception.d \ $(IMPDIR)\core\exception.d \
$(IMPDIR)\core\factory.d \ $(IMPDIR)\core\factory.d \
$(IMPDIR)\core\int128.d \ $(IMPDIR)\core\int128.d \
$(IMPDIR)\core\interpolation.d \
$(IMPDIR)\core\lifetime.d \ $(IMPDIR)\core\lifetime.d \
$(IMPDIR)\core\math.d \ $(IMPDIR)\core\math.d \
$(IMPDIR)\core\memory.d \ $(IMPDIR)\core\memory.d \

View file

@ -5,6 +5,7 @@ DOCS=\
$(DOCDIR)\core_checkedint.html \ $(DOCDIR)\core_checkedint.html \
$(DOCDIR)\core_exception.html \ $(DOCDIR)\core_exception.html \
$(DOCDIR)\core_int128.html \ $(DOCDIR)\core_int128.html \
$(DOCDIR)\core_interpolation.html \
$(DOCDIR)\core_math.html \ $(DOCDIR)\core_math.html \
$(DOCDIR)\core_vararg.html \ $(DOCDIR)\core_vararg.html \
$(DOCDIR)\core_volatile.html \ $(DOCDIR)\core_volatile.html \

View file

@ -11,6 +11,7 @@ SRCS=\
src\core\exception.d \ src\core\exception.d \
src\core\factory.d \ src\core\factory.d \
src\core\int128.d \ src\core\int128.d \
src\core\interpolation.d \
src\core\lifetime.d \ src\core\lifetime.d \
src\core\math.d \ src\core\math.d \
src\core\memory.d \ src\core\memory.d \

View file

@ -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;
}
}