Skip to content

Commit

Permalink
support i", i`, iq{ and istring postfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
WebFreak001 committed Oct 29, 2023
1 parent 3c665c0 commit bdd87a6
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 8 deletions.
18 changes: 17 additions & 1 deletion src/dparse/ast.d
Original file line number Diff line number Diff line change
Expand Up @@ -2363,7 +2363,23 @@ final class InterpolatedString : BaseNode
: Token.init;
}

mixin OpEquals!("startQuote.text");
/// '\0'/'c'/'w'/'d' for `i""`, `i""c`, `i""w` and `i""d` respectively.
char postfixType() inout pure nothrow @nogc @safe scope
{
auto end = endQuote.text;
auto endChar = end.length ? end[$ - 1] : ' ';
switch (endChar)
{
case 'c':
case 'w':
case 'd':
return endChar;
default:
return '\0';
}
}

mixin OpEquals!("startQuote.text", "postfixType");
}

///
Expand Down
142 changes: 135 additions & 7 deletions src/dparse/lexer.d
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ private immutable pseudoTokenHandlers = [
"8", "lexDecimal",
"9", "lexDecimal",
"i\"", "lexInterpolatedString",
"i`", "lexInterpolatedString",
"iq{", "lexInterpolatedString",
"q\"", "lexDelimitedString",
"q{", "lexTokenString",
"r\"", "lexWysiwygString",
Expand Down Expand Up @@ -1415,6 +1417,12 @@ private pure nothrow @safe:
index);
}

private ubyte lexStringSuffix() pure nothrow @safe
{
IdType t;
return lexStringSuffix(t);
}

private ubyte lexStringSuffix(ref IdType type) pure nothrow @safe
{
if (range.index >= range.bytes.length)
Expand All @@ -1437,10 +1445,22 @@ private pure nothrow @safe:
void lexInterpolatedString(ref Token token)
{
mixin (tokenStart);
range.index += 2;
range.column += 2;
IstringState.Type type;
range.popFront();
switch (range.bytes[range.index])
{
case '"': type = IstringState.type.quote; break;
case '`': type = IstringState.type.backtick; break;
case 'q':
type = IstringState.type.tokenString;
range.popFront();
break;
default:
assert(false);
}
range.popFront();
token = Token(tok!"istringLiteralStart", cache.intern(range.slice(mark)), line, column, index);
istringStack ~= IstringState.init;
istringStack ~= IstringState(0, 0, type);
}

void lexIstringContent(ref Token token)
Expand Down Expand Up @@ -1476,9 +1496,15 @@ private pure nothrow @safe:
}
else
goto default;
case '}':
case '"':
case '`':
if (range.front != istringStack[$ - 1].type || istringStack[$ - 1].braces)
goto default;

istringStack.length--;
range.popFront();
lexStringSuffix();
token = Token(tok!"istringLiteralEnd", cache.intern(range.slice(mark)), line,
column, index);
break;
Expand All @@ -1493,18 +1519,36 @@ private pure nothrow @safe:
mixin (tokenStart);
Loop: while (!range.empty)
{
switch (range.bytes[range.index])
char c = range.bytes[range.index];
switch (c)
{
case '\\':
lexEscapeSequence();
if (istringStack[$ - 1].type == IstringState.Type.quote)
lexEscapeSequence();
else
goto default;
break;
case '$':
if (isAtIstringExpression)
break Loop;
else
goto default;
case '{':
if (istringStack[$ - 1].type == IstringState.Type.tokenString)
istringStack[$ - 1].braces++;
goto default;
case '}':
if (istringStack[$ - 1].type == IstringState.Type.tokenString)
{
assert(istringStack[$ - 1].braces > 0);
istringStack[$ - 1].braces--;
}
goto default;
case '"':
break Loop;
case '`':
if (c == istringStack[$ - 1].type)
break Loop;
goto default;
default:
popFrontWhitespaceAware();
break;
Expand Down Expand Up @@ -2078,7 +2122,16 @@ private pure nothrow @safe:

static struct IstringState
{
int parens;
enum Type : ubyte
{
quote = '"',
backtick = '`',
tokenString = '}',
}

ushort parens;
ushort braces;
Type type;
bool dollar;
}
}
Expand Down Expand Up @@ -2778,3 +2831,78 @@ void main() {
checkInvalidTrailingString(getTokensForParser(`i"$(q{`, cf, &ca), 4);
checkInvalidTrailingString(getTokensForParser(`i"$(q{foo`, cf, &ca), 4);
}

unittest
{
import std.conv;

auto test(string content, bool debugPrint = false)
{
LexerConfig cf;
StringCache ca = StringCache(16);

const tokens = getTokensForParser(content, cf, &ca);
if (debugPrint)
return tokens.to!(char[][]).join("\n");

char[] ret = new char[content.length];
ret[] = ' ';
foreach_reverse (t; tokens)
{
ret[t.index .. t.index + max(1, t.text.length)] =
t.type == tok!"$" ? '$' :
t.type == tok!"(" ? '(' :
t.type == tok!")" ? ')' :
t.type == tok!"identifier" ? 'i' :
t.type == tok!"istringLiteralStart" ? 'S' :
t.type == tok!"istringLiteralText" ? '.' :
t.type == tok!"istringLiteralEnd" ? 'E' :
t.type == tok!"" ? '%' :
'?';
}
return ret;
}

// dfmt off

assert(test(`i"$name"`)
== `SS$iiiiE`);

assert(test(`i"\$plain\0"`)
== `SS.........E`);

assert(test(`i"$(expression)"w`)
== `SS$(iiiiiiiiii)EE`);

assert(test(`i"$(expression"c`)
== `SS$(iiiiiiiiii `);

assert(test(`i"$name "`)
== `SS$iiii.E`);

assert(test(`i"$ {}plain"`)
== `SS.........E`);

assert(test("i\"$ ``plain\"")
== `SS.........E`);

assert(test(`i"$0 plain"`)
== `SS........E`);

assert(test(`i"$$0 plain"`)
== `SS.........E`);

assert(test(`i"$.1 plain"`)
== `SS.........E`);

assert(test(`i"I have $$money"`)
== `SS........$iiiiiE`);

assert(test("i`I \"have\" $$money`")
== "SS..........$iiiiiE", test("i`I \"have\" $$money`", true));

assert(test("iq{I `\"have\"` $$money}")
== "SSS............$iiiiiE");

// dfmt on
}

0 comments on commit bdd87a6

Please sign in to comment.