apple/pkl · error · ParserError

unterminatedUnicodeEscapeSequence

unterminatedUnicodeEscapeSequence

Error message

Unterminated Unicode escape sequence `{0}`.

Unicode escape sequences must end with `}`.

What it means

A unicode escape `\u{...}` was opened but the closing `}` was never found: the lexer scanned letters/digits after `{` and hit EOF (or a non-alphanumeric character that stopped the loop without reaching `}`). The message reports the partially-read escape text and states the sequence must end with `}`.

Source

Thrown at pkl-parser/src/main/java/org/pkl/parser/Lexer.java:504

          throw lexError(
              ErrorMessages.create("invalidCharacterEscapeSequence", "\\" + (char) ch, "\\"),
              cursor - 2,
              2);
    };
  }

  private Token lexUnicodeEscape() {
    if (lookahead != '{') {
      throw unexpectedChar(lookahead, "{");
    }
    do {
      nextChar();
    } while (lookahead != '}' && lookahead != EOF && Character.isLetterOrDigit(lookahead));
    if (lookahead == '}') {
      // consume the close bracket
      nextChar();
    } else {
      throw lexError(ErrorMessages.create("unterminatedUnicodeEscapeSequence", text()), span());
    }
    return Token.STRING_ESCAPE_UNICODE;
  }

  private Token lexIdentifier() {
    while (isIdentifierPart(lookahead)) {
      nextChar();
    }

    var identifierStr = text();
    var identifier = getKeywordOrIdentifier(identifierStr);
    return switch (identifier) {
      case IMPORT -> {
        if (lookahead == '*') {
          nextChar();
          yield Token.IMPORT_STAR;
        } else yield Token.IMPORT;
      }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Add the closing `}` to the unicode escape: `\u{1F600}`.
  2. Remove any non-alphanumeric characters (spaces, hyphens) inside the braces — codepoints are hex digits only.
  3. If the escape is at end of file, restore the truncated file content.
  4. If a literal `{` should follow `\u`, escape the backslash: `\\u{`.

Example fix

// before
emoji = "\u{1F600"
// after
emoji = "\u{1F600}"
Defensive patterns

Strategy: validation

Validate before calling

function unterminatedUnicode(src) { return /\\u\{[0-9A-Fa-f]*$/.test(src) || /\\u\{[^}]*\s/.test(src); }
if (unterminatedUnicode(src)) throw new Error('Unicode escape missing closing } or contains invalid chars');

Type guard

null

Try / catch

try { tokens = lexer.next(); } catch (e) { if (e.code === 'unterminatedUnicodeEscapeSequence') { console.error('Add closing } to the unicode escape'); } throw e; }

Prevention

When it happens

Trigger: lexEscape → lexUnicodeEscape: after consuming `\u{`, the loop `while (lookahead != '}' && lookahead != EOF && isLetterOrDigit(lookahead))` exits with lookahead == EOF (or a non-alphanumeric char that is not `}`), e.g. `"\u{1F600"` at end of string/file, or `"\u{12 34}"` where a space breaks the scan before `}`.

Common situations: Copy-paste truncation cutting off the closing brace; typing `\u{41}` and forgetting `}`; inserting a space or punctuation inside the codepoint like `\u{1F 600}`; a template generator emitting partial escapes.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/46b6f9e5deb9d2b2. Report an issue: GitHub.