Skip to content

Commit c04ae03

Browse files
committed
Enhanced version of dip1036 implementation
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.
1 parent 21a3756 commit c04ae03

23 files changed

+827
-190
lines changed

changelog/dmd.ies.dd

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Add support for Interpolated Expression Sequences
2+
3+
Interpolated Expression Sequences are a way to implement things like string interpolation in library code. Three forms of literals are added:
4+
5+
```
6+
i"Content $(a + 4)"
7+
i`Content $(a + 4)`
8+
iq{Content $(a + 4)}
9+
```
10+
11+
all provide the same thing: a tuple that can be passed to other functions, like `writeln` from `std.stdio` and `text` from `std.conv`:
12+
13+
```
14+
int a = 6;
15+
writeln(i"Content $(a + 4)"); // prints "Content 10"
16+
```
17+
18+
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/

compiler/src/dmd/astbase.d

+20
Original file line numberDiff line numberDiff line change
@@ -4575,6 +4575,7 @@ struct ASTBase
45754575
inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; }
45764576
inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; }
45774577
inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; }
4578+
inout(InterpExp) isInterpExp() { return op == EXP.interpolated ? cast(typeof(return))this : null; }
45784579
inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; }
45794580
inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; }
45804581
inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; }
@@ -4907,6 +4908,25 @@ struct ASTBase
49074908
}
49084909
}
49094910

4911+
extern (C++) final class InterpExp : Expression
4912+
{
4913+
InterpolatedSet* interpolatedSet;
4914+
char postfix = 0; // 'c', 'w', 'd'
4915+
4916+
extern (D) this(const ref Loc loc, InterpolatedSet* interpolatedSet, char postfix = 0)
4917+
{
4918+
super(loc, EXP.interpolated, __traits(classInstanceSize, InterpExp));
4919+
this.interpolatedSet = interpolatedSet;
4920+
this.postfix = postfix;
4921+
}
4922+
4923+
override void accept(Visitor v)
4924+
{
4925+
v.visit(this);
4926+
}
4927+
}
4928+
4929+
49104930
extern (C++) final class StringExp : Expression
49114931
{
49124932
union

compiler/src/dmd/doc.d

+2-1
Original file line numberDiff line numberDiff line change
@@ -5203,6 +5203,7 @@ void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
52035203
highlight = "$(D_COMMENT ";
52045204
break;
52055205
case TOK.string_:
5206+
case TOK.interpolated:
52065207
highlight = "$(D_STRING ";
52075208
break;
52085209
default:
@@ -5215,7 +5216,7 @@ void highlightCode2(Scope* sc, Dsymbols* a, ref OutBuffer buf, size_t offset)
52155216
res.writestring(highlight);
52165217
size_t o = res.length;
52175218
highlightCode3(sc, res, tok.ptr, lex.p);
5218-
if (tok.value == TOK.comment || tok.value == TOK.string_)
5219+
if (tok.value == TOK.comment || tok.value == TOK.string_ || tok.value == TOK.interpolated)
52195220
/* https://issues.dlang.org/show_bug.cgi?id=7656
52205221
* https://issues.dlang.org/show_bug.cgi?id=7715
52215222
* https://issues.dlang.org/show_bug.cgi?id=10519

compiler/src/dmd/expression.d

+24
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ extern (C++) abstract class Expression : ASTNode
754754
inout(SuperExp) isSuperExp() { return op == EXP.super_ ? cast(typeof(return))this : null; }
755755
inout(NullExp) isNullExp() { return op == EXP.null_ ? cast(typeof(return))this : null; }
756756
inout(StringExp) isStringExp() { return op == EXP.string_ ? cast(typeof(return))this : null; }
757+
inout(InterpExp) isInterpExp() { return op == EXP.interpolated ? cast(typeof(return))this : null; }
757758
inout(TupleExp) isTupleExp() { return op == EXP.tuple ? cast(typeof(return))this : null; }
758759
inout(ArrayLiteralExp) isArrayLiteralExp() { return op == EXP.arrayLiteral ? cast(typeof(return))this : null; }
759760
inout(AssocArrayLiteralExp) isAssocArrayLiteralExp() { return op == EXP.assocArrayLiteral ? cast(typeof(return))this : null; }
@@ -1880,6 +1881,28 @@ extern (C++) final class StringExp : Expression
18801881
}
18811882
}
18821883

1884+
extern (C++) final class InterpExp : Expression
1885+
{
1886+
char postfix = NoPostfix; // 'c', 'w', 'd'
1887+
OwnedBy ownedByCtfe = OwnedBy.code;
1888+
InterpolatedSet* interpolatedSet;
1889+
1890+
enum char NoPostfix = 0;
1891+
1892+
extern (D) this(const ref Loc loc, InterpolatedSet* set, char postfix = NoPostfix) scope
1893+
{
1894+
super(loc, EXP.interpolated);
1895+
this.interpolatedSet = set;
1896+
this.postfix = postfix;
1897+
}
1898+
1899+
override void accept(Visitor v)
1900+
{
1901+
v.visit(this);
1902+
}
1903+
}
1904+
1905+
18831906
/***********************************************************
18841907
* A sequence of expressions
18851908
*
@@ -5527,6 +5550,7 @@ private immutable ubyte[EXP.max+1] expSize = [
55275550
EXP.preMinusMinus: __traits(classInstanceSize, PreExp),
55285551
EXP.identifier: __traits(classInstanceSize, IdentifierExp),
55295552
EXP.string_: __traits(classInstanceSize, StringExp),
5553+
EXP.interpolated: __traits(classInstanceSize, InterpExp),
55305554
EXP.this_: __traits(classInstanceSize, ThisExp),
55315555
EXP.super_: __traits(classInstanceSize, SuperExp),
55325556
EXP.halt: __traits(classInstanceSize, HaltExp),

compiler/src/dmd/expression.h

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class TemplateDeclaration;
3838
class ClassDeclaration;
3939
class OverloadSet;
4040
class StringExp;
41+
class InterpExp;
4142
class LoweredAssignExp;
4243
#ifdef IN_GCC
4344
typedef union tree_node Symbol;
@@ -129,6 +130,7 @@ class Expression : public ASTNode
129130
SuperExp* isSuperExp();
130131
NullExp* isNullExp();
131132
StringExp* isStringExp();
133+
InterpExp* isInterpExp();
132134
TupleExp* isTupleExp();
133135
ArrayLiteralExp* isArrayLiteralExp();
134136
AssocArrayLiteralExp* isAssocArrayLiteralExp();
@@ -370,6 +372,16 @@ class StringExp final : public Expression
370372
void writeTo(void* dest, bool zero, int tyto = 0) const;
371373
};
372374

375+
class InterpExp final : public Expression
376+
{
377+
public:
378+
utf8_t postfix; // 'c', 'w', 'd'
379+
OwnedBy ownedByCtfe;
380+
void* interpolatedSet;
381+
382+
void accept(Visitor* v) override { v->visit(this); }
383+
};
384+
373385
// Tuple
374386

375387
class TupleExp final : public Expression

compiler/src/dmd/expressionsem.d

+78
Original file line numberDiff line numberDiff line change
@@ -4113,6 +4113,84 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
41134113
result = e;
41144114
}
41154115

4116+
override void visit(InterpExp e)
4117+
{
4118+
// the lexer breaks up into an odd/even array of literals and expression code
4119+
// we need to turn that into:
4120+
/+
4121+
tuple(
4122+
.object.imported!"core.interpolation".InterpolationHeader(),
4123+
...
4124+
.object.imported!"core.interpolation".InterpolationFooter()
4125+
)
4126+
4127+
There the ... loops through them all, making the even ones
4128+
.object.imported!"core.interpolation".InterpolatedLiteral!str()
4129+
and making the odd ones
4130+
.object.imported!"core.interpolation".InterpolatedExpression!str(),
4131+
the code represented by str
4132+
4133+
Empty string literals are skipped as they provide no additional information.
4134+
+/
4135+
4136+
if (e.postfix)
4137+
error(e.loc, "String postfixes on interpolated expression sequences are not allowed.");
4138+
4139+
Expression makeNonTemplateItem(Identifier which) {
4140+
Expression id = new IdentifierExp(e.loc, Id.empty);
4141+
id = new DotIdExp(e.loc, id, Id.object);
4142+
auto moduleNameArgs = new Objects();
4143+
moduleNameArgs.push(new StringExp(e.loc, "core.interpolation"));
4144+
id = new DotTemplateInstanceExp(e.loc, id, Id.imported, moduleNameArgs);
4145+
id = new DotIdExp(e.loc, id, which);
4146+
id = new CallExp(e.loc, id, new Expressions());
4147+
return id;
4148+
}
4149+
4150+
Expression makeTemplateItem(Identifier which, string arg) {
4151+
Expression id = new IdentifierExp(e.loc, Id.empty);
4152+
id = new DotIdExp(e.loc, id, Id.object);
4153+
auto moduleNameArgs = new Objects();
4154+
moduleNameArgs.push(new StringExp(e.loc, "core.interpolation"));
4155+
id = new DotTemplateInstanceExp(e.loc, id, Id.imported, moduleNameArgs);
4156+
auto tiargs = new Objects();
4157+
auto templateStringArg = new StringExp(e.loc, arg);
4158+
// banning those instead of forwarding them
4159+
// templateStringArg.postfix = e.postfix; // forward the postfix to these literals
4160+
tiargs.push(templateStringArg);
4161+
id = new DotTemplateInstanceExp(e.loc, id, which, tiargs);
4162+
id = new CallExp(e.loc, id, new Expressions());
4163+
return id;
4164+
}
4165+
4166+
auto arguments = new Expressions();
4167+
arguments.push(makeNonTemplateItem(Id.InterpolationHeader));
4168+
4169+
foreach (idx, str; e.interpolatedSet.parts)
4170+
{
4171+
if (idx % 2 == 0)
4172+
{
4173+
if (str.length > 0)
4174+
arguments.push(makeTemplateItem(Id.InterpolatedLiteral, str));
4175+
}
4176+
else
4177+
{
4178+
arguments.push(makeTemplateItem(Id.InterpolatedExpression, str));
4179+
Expressions* mix = new Expressions();
4180+
mix.push(new StringExp(e.loc, str));
4181+
// FIXME: i'd rather not use MixinExp but idk how to do it lol
4182+
arguments.push(new MixinExp(e.loc, mix));
4183+
}
4184+
}
4185+
4186+
arguments.push(makeNonTemplateItem(Id.InterpolationFooter));
4187+
4188+
auto loweredTo = new TupleExp(e.loc, arguments);
4189+
visit(loweredTo);
4190+
4191+
result = loweredTo;
4192+
}
4193+
41164194
override void visit(StringExp e)
41174195
{
41184196
static if (LOGSEMANTIC)

0 commit comments

Comments
 (0)