{"record":{"id":"d1e3cb92001cffaa","repo":"apple/pkl","slug":"invalidseparatorposition","errorCode":"invalidSeparatorPosition","errorMessage":"Unexpected separator character.\n\nThe separator character (`_`) cannot follow `0x`, `0b`, `.`, `e`, or 'E' in a number literal.","messagePattern":"Unexpected separator character\\.\n\nThe separator character \\(`_`\\) cannot follow `0x`, `0b`, `\\.`, `e`, or 'E' in a number literal\\.","errorType":"exception","errorClass":"ParserError","httpStatus":null,"severity":"error","filePath":"pkl-parser/src/main/java/org/pkl/parser/Lexer.java","lineNumber":588,"sourceCode":"        return Token.FLOAT;\n      }\n    } else if (start == '.') {\n      lexDotNumber();\n      return Token.FLOAT;\n    }\n\n    while ((lookahead >= '0' && lookahead <= '9') || lookahead == '_') {\n      nextChar();\n    }\n\n    if (lookahead == 'e' || lookahead == 'E') {\n      nextChar();\n      lexExponent();\n      return Token.FLOAT;\n    } else if (lookahead == '.') {\n      nextChar();\n      if (lookahead == '_') {\n        throw lexError(\"invalidSeparatorPosition\");\n      }\n      if (lookahead < '0' || lookahead > '9') {\n        backup();\n        return Token.INT;\n      }\n      lexDotNumber();\n      return Token.FLOAT;\n    }\n    return Token.INT;\n  }\n\n  private Token lexSlash() {\n    switch (lookahead) {\n      case '/':\n        {\n          nextChar();\n          var token = lookahead == '/' ? Token.DOC_COMMENT : Token.LINE_COMMENT;\n          while (lookahead != '\\n' && lookahead != '\\r' && lookahead != EOF) {","sourceCodeStart":570,"sourceCodeEnd":606,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-parser/src/main/java/org/pkl/parser/Lexer.java#L570-L606","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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`."],"exampleFix":"// before\nmask = 0x_FF00\n// after\nmask = 0xFF_00","handlingStrategy":"validation","validationCode":"function badSeparator(src) { return /0[xX]_|0[bB]_|\\._|\\d[eE]_/.test(src); }\nif (badSeparator(src)) throw new Error('Digit separator _ cannot follow 0x, 0b, ., e, or E');","typeGuard":"null","tryCatchPattern":"try { tokens = lexer.next(); } catch (e) { if (e.code === 'invalidSeparatorPosition') { console.error('Place _ only between digits'); } throw e; }","preventionTips":["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."],"tags":["lexer","pkl","number-literal","separator"],"backgroundTag":"invalid-argument-format","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}