SPICE-0028: Add support for multi-line string line continuations (#1507)

SPICE: https://github.com/apple/pkl-evolution/pull/31
This commit is contained in:
Jen Basch
2026-04-21 10:29:52 -07:00
committed by GitHub
parent d85f06be27
commit e07abb7311
24 changed files with 185 additions and 10 deletions
@@ -978,6 +978,8 @@ class GenericParserImpl {
STRING_ESCAPE_RETURN,
STRING_ESCAPE_UNICODE ->
children.add(make(NodeType.STRING_ESCAPE, next().span));
case STRING_ESCAPE_CONTINUATION ->
throw parserError("invalidLineContinuationEscapeSequence");
case INTERPOLATION_START -> {
children.add(makeTerminal(next()));
ff(children);
@@ -1011,6 +1013,8 @@ class GenericParserImpl {
}
}
case STRING_NEWLINE -> children.add(make(NodeType.STRING_NEWLINE, next().span));
case STRING_ESCAPE_CONTINUATION ->
children.add(make(NodeType.STRING_CONTINUATION, next().span));
case STRING_ESCAPE_NEWLINE,
STRING_ESCAPE_TAB,
STRING_ESCAPE_QUOTE,
@@ -1060,7 +1064,8 @@ class GenericParserImpl {
throw parserError(ErrorMessages.create("stringIndentationMustMatchLastLine"), child.span);
}
}
previousNewline = child.type == NodeType.STRING_NEWLINE;
previousNewline =
child.type == NodeType.STRING_NEWLINE || child.type == NodeType.STRING_CONTINUATION;
}
}
@@ -460,6 +460,22 @@ public final class Lexer {
yield Token.INTERPOLATION_START;
}
case 'u' -> lexUnicodeEscape();
case '\n' -> Token.STRING_ESCAPE_CONTINUATION;
case ' ', '\t' -> {
var c = cursor;
var next = nextChar();
while (next == ' ' || next == '\t') next = nextChar();
if (next == '\n')
throw lexError(
ErrorMessages.create("invalidLineContinuationEscapeSequenceWhitespace"),
c - 2,
cursor - c + 2);
throw lexError(
ErrorMessages.create("invalidCharacterEscapeSequence", "\\" + (char) ch, "\\"),
c - 2,
2);
}
default ->
throw lexError(
ErrorMessages.create("invalidCharacterEscapeSequence", "\\" + (char) ch, "\\"),
@@ -1131,6 +1131,8 @@ final class ParserImpl {
end = tk.span;
builder.append(parseUnicodeEscape(tk));
}
case STRING_ESCAPE_CONTINUATION ->
throw parserError("invalidLineContinuationEscapeSequence");
case INTERPOLATION_START -> {
var istart = next().span;
if (!builder.isEmpty()) {
@@ -1167,7 +1169,8 @@ final class ParserImpl {
STRING_ESCAPE_QUOTE,
STRING_ESCAPE_BACKSLASH,
STRING_ESCAPE_RETURN,
STRING_ESCAPE_UNICODE ->
STRING_ESCAPE_UNICODE,
STRING_ESCAPE_CONTINUATION ->
stringTokens.add(new TempNode(next(), null));
case INTERPOLATION_START -> {
var istart = next();
@@ -1236,6 +1239,7 @@ final class ParserImpl {
builder.append('\n');
isNewLine = true;
}
case STRING_ESCAPE_CONTINUATION -> isNewLine = true;
case STRING_PART -> {
var text = token.text(lexer);
if (isNewLine) {
@@ -1642,6 +1646,7 @@ final class ParserImpl {
case STRING_ESCAPE_BACKSLASH -> "\\";
case STRING_ESCAPE_TAB -> "\t";
case STRING_ESCAPE_RETURN -> "\r";
case STRING_ESCAPE_CONTINUATION -> "";
case STRING_ESCAPE_UNICODE -> parseUnicodeEscape(tk);
default -> throw new RuntimeException("Unreacheable code");
};
@@ -129,6 +129,7 @@ public enum Token {
STRING_ESCAPE_QUOTE,
STRING_ESCAPE_BACKSLASH,
STRING_ESCAPE_UNICODE,
STRING_ESCAPE_CONTINUATION,
STRING_END,
STRING_PART;
@@ -1,5 +1,5 @@
/*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,6 +70,7 @@ public enum NodeType {
STRING_CHARS,
OPERATOR,
STRING_NEWLINE,
STRING_CONTINUATION,
STRING_ESCAPE,
// members
@@ -44,13 +44,23 @@ The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a nu
invalidCharacterEscapeSequence=\
Invalid character escape sequence `{0}`.\n\
\n\
Valid character escape sequences are: {1}n {1}r {1}t {1}" {1}\\
Valid character escape sequences are: {1}n {1}r {1}t {1}" {1}\\ {1}<newline>
invalidUnicodeEscapeSequence=\
Invalid Unicode escape sequence `{0}`.\n\
\n\
Valid Unicode escape sequences are {1}'{'0'}' to {1}'{'10FFFF'}' (1-6 hexadecimal characters).
invalidLineContinuationEscapeSequence=\
Invalid line continuation escape sequence.\n\
\n\
Line continuations are only allowed in multi-line strings.
invalidLineContinuationEscapeSequenceWhitespace=\
Invalid line continuation escape sequence.\n\
\n\
Whitespace between the continuation escape and following newline is not allowed.
missingDelimiter=\
Missing `{0}` delimiter.
@@ -197,6 +197,7 @@ class GenericSexpRenderer(code: String) {
NodeType.TERMINAL,
NodeType.OPERATOR,
NodeType.STRING_NEWLINE,
NodeType.STRING_CONTINUATION,
)
private val UNPACK_CHILDREN =
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,6 +97,7 @@ class ParserComparisonTest {
"errors/parser18.pkl",
"errors/nested1.pkl",
"errors/invalidCharacterEscape.pkl",
"errors/invalidCharacterEscape2.pkl",
"errors/invalidUnicodeEscape.pkl",
"errors/unterminatedUnicodeEscape.pkl",
"errors/keywordNotAllowedHere1.pkl",