apple/pkl · error · ParserError
invalidSeparatorPosition
invalidSeparatorPosition
Error message
Unexpected separator character. The separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal.
What it means
In numeric literals, `_` is a digit separator and may only appear between digits. The lexer rejects a separator in positions where no digit precedes it — specifically immediately after `0x`/`0b` prefixes, after a leading `.`, or after the `e`/`E` exponent marker — because `0x_`, `._`, or `1e_` are not valid numbers.
Solutions
- Move the separator so it sits between digits: `0xFF` → `0xF_F` is fine, `0x_FF` is not.
- Delete the stray `_`: `1e_5` → `1e5`, `1._5` → `1.5`.
- For hex/binary, put the first separator only after at least one digit: `0xFF_FF`.
- For floats, keep separators only within digit runs, never adjacent to `.`, `e`, or `E`: `1_000.000_1e10`.
Example fix
// before mask = 0x_FF00 // after mask = 0xFF_00
Defensive patterns
Strategy: validation
Validate before calling
function badSeparator(src) { return /0[xX]_|0[bB]_|\._|\d[eE]_/.test(src); }
if (badSeparator(src)) throw new Error('Digit separator _ cannot follow 0x, 0b, ., e, or E'); Type guard
null
Try / catch
try { tokens = lexer.next(); } catch (e) { if (e.code === 'invalidSeparatorPosition') { console.error('Place _ only between digits'); } throw e; } Prevention
- Place separators only between digit runs: 0xFF_00, 1_000.000_1.
- Never put _ directly after 0x/0b prefixes or before the first digit.
- Never place _ adjacent to . or exponent e/E in floats.
- Adopt a formatter/linter rule for numeric literal separators.
When it happens
Trigger: lexNumber (via nextDefault): in the fraction branch, after consuming `.` following digits, lookahead is `_` (e.g. `1._5`); equivalent checks reject `0x_FF`, `0b_1`, and `1e_5` forms in the prefix/exponent branches. Any `_` directly following `0x`, `0b`, `.`, `e`, or `E` throws this error.
Common situations: Copy-pasting numbers from languages/specs that allow separators after base prefixes (e.g. Rust `0x_FF`); adding separators for readability in the wrong spot like `1e_6` or `1.5e_3`; typos while formatting large hex or float literals.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalidCharacterEscapeSequence
- invalidLineContinuationEscapeSequenceWhitespace
- missingDelimiter
- unexpectedCharacter
- unterminatedUnicodeEscapeSequence
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/d1e3cb92001cffaa.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-parser/src/main/java/org/pkl/parser/Lexer.java:588
return Token.FLOAT;
}
} else if (start == '.') {
lexDotNumber();
return Token.FLOAT;
}
while ((lookahead >= '0' && lookahead <= '9') || lookahead == '_') {
nextChar();
}
if (lookahead == 'e' || lookahead == 'E') {
nextChar();
lexExponent();
return Token.FLOAT;
} else if (lookahead == '.') {
nextChar();
if (lookahead == '_') {
throw lexError("invalidSeparatorPosition");
}
if (lookahead < '0' || lookahead > '9') {
backup();
return Token.INT;
}
lexDotNumber();
return Token.FLOAT;
}
return Token.INT;
}
private Token lexSlash() {
switch (lookahead) {
case '/':
{
nextChar();
var token = lookahead == '/' ? Token.DOC_COMMENT : Token.LINE_COMMENT;
while (lookahead != '\n' && lookahead != '\r' && lookahead != EOF) {View on GitHub (pinned to f3efcbfc9b)