prefix-safe-json 0.0.1-alpha.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +72 -33
  3. package/package.json +8 -3
  4. package/dist/coordinator/coordinator.d.ts +0 -53
  5. package/dist/coordinator/coordinator.d.ts.map +0 -1
  6. package/dist/coordinator/coordinator.js +0 -448
  7. package/dist/coordinator/coordinator.js.map +0 -1
  8. package/dist/coordinator/diagnostic-codes.d.ts +0 -11
  9. package/dist/coordinator/diagnostic-codes.d.ts.map +0 -1
  10. package/dist/coordinator/diagnostic-codes.js +0 -22
  11. package/dist/coordinator/diagnostic-codes.js.map +0 -1
  12. package/dist/coordinator/protocol.d.ts +0 -61
  13. package/dist/coordinator/protocol.d.ts.map +0 -1
  14. package/dist/coordinator/protocol.js +0 -2
  15. package/dist/coordinator/protocol.js.map +0 -1
  16. package/dist/coordinator/types.d.ts +0 -104
  17. package/dist/coordinator/types.d.ts.map +0 -1
  18. package/dist/coordinator/types.js +0 -7
  19. package/dist/coordinator/types.js.map +0 -1
  20. package/dist/diagnostics/codes.d.ts +0 -24
  21. package/dist/diagnostics/codes.d.ts.map +0 -1
  22. package/dist/diagnostics/codes.js +0 -28
  23. package/dist/diagnostics/codes.js.map +0 -1
  24. package/dist/diagnostics/factory.d.ts +0 -8
  25. package/dist/diagnostics/factory.d.ts.map +0 -1
  26. package/dist/diagnostics/factory.js +0 -17
  27. package/dist/diagnostics/factory.js.map +0 -1
  28. package/dist/gate/decide.d.ts +0 -20
  29. package/dist/gate/decide.d.ts.map +0 -1
  30. package/dist/gate/decide.js +0 -127
  31. package/dist/gate/decide.js.map +0 -1
  32. package/dist/gate/gate.d.ts +0 -25
  33. package/dist/gate/gate.d.ts.map +0 -1
  34. package/dist/gate/gate.js +0 -80
  35. package/dist/gate/gate.js.map +0 -1
  36. package/dist/gate/types.d.ts +0 -122
  37. package/dist/gate/types.d.ts.map +0 -1
  38. package/dist/gate/types.js +0 -2
  39. package/dist/gate/types.js.map +0 -1
  40. package/dist/grammar/frame.d.ts +0 -58
  41. package/dist/grammar/frame.d.ts.map +0 -1
  42. package/dist/grammar/frame.js +0 -87
  43. package/dist/grammar/frame.js.map +0 -1
  44. package/dist/grammar/pointer.d.ts +0 -10
  45. package/dist/grammar/pointer.d.ts.map +0 -1
  46. package/dist/grammar/pointer.js +0 -20
  47. package/dist/grammar/pointer.js.map +0 -1
  48. package/dist/grammar/stack.d.ts +0 -73
  49. package/dist/grammar/stack.d.ts.map +0 -1
  50. package/dist/grammar/stack.js +0 -168
  51. package/dist/grammar/stack.js.map +0 -1
  52. package/dist/index.d.ts +0 -37
  53. package/dist/index.d.ts.map +0 -1
  54. package/dist/index.js +0 -37
  55. package/dist/index.js.map +0 -1
  56. package/dist/lexer/scanner.d.ts +0 -108
  57. package/dist/lexer/scanner.d.ts.map +0 -1
  58. package/dist/lexer/scanner.js +0 -676
  59. package/dist/lexer/scanner.js.map +0 -1
  60. package/dist/lexer/states.d.ts +0 -35
  61. package/dist/lexer/states.d.ts.map +0 -1
  62. package/dist/lexer/states.js +0 -39
  63. package/dist/lexer/states.js.map +0 -1
  64. package/dist/lexer/tokens.d.ts +0 -22
  65. package/dist/lexer/tokens.d.ts.map +0 -1
  66. package/dist/lexer/tokens.js +0 -22
  67. package/dist/lexer/tokens.js.map +0 -1
  68. package/dist/limits.d.ts +0 -14
  69. package/dist/limits.d.ts.map +0 -1
  70. package/dist/limits.js +0 -23
  71. package/dist/limits.js.map +0 -1
  72. package/dist/parser.d.ts +0 -6
  73. package/dist/parser.d.ts.map +0 -1
  74. package/dist/parser.js +0 -949
  75. package/dist/parser.js.map +0 -1
  76. package/dist/providers/adapter.d.ts +0 -10
  77. package/dist/providers/adapter.d.ts.map +0 -1
  78. package/dist/providers/adapter.js +0 -2
  79. package/dist/providers/adapter.js.map +0 -1
  80. package/dist/providers/ai-sdk.d.ts +0 -54
  81. package/dist/providers/ai-sdk.d.ts.map +0 -1
  82. package/dist/providers/ai-sdk.js +0 -191
  83. package/dist/providers/ai-sdk.js.map +0 -1
  84. package/dist/providers/anthropic.d.ts +0 -45
  85. package/dist/providers/anthropic.d.ts.map +0 -1
  86. package/dist/providers/anthropic.js +0 -112
  87. package/dist/providers/anthropic.js.map +0 -1
  88. package/dist/providers/gemini.d.ts +0 -31
  89. package/dist/providers/gemini.d.ts.map +0 -1
  90. package/dist/providers/gemini.js +0 -94
  91. package/dist/providers/gemini.js.map +0 -1
  92. package/dist/providers/openai-compatible.d.ts +0 -33
  93. package/dist/providers/openai-compatible.d.ts.map +0 -1
  94. package/dist/providers/openai-compatible.js +0 -160
  95. package/dist/providers/openai-compatible.js.map +0 -1
  96. package/dist/providers/openai.d.ts +0 -48
  97. package/dist/providers/openai.d.ts.map +0 -1
  98. package/dist/providers/openai.js +0 -205
  99. package/dist/providers/openai.js.map +0 -1
  100. package/dist/providers/openrouter.d.ts +0 -35
  101. package/dist/providers/openrouter.d.ts.map +0 -1
  102. package/dist/providers/openrouter.js +0 -55
  103. package/dist/providers/openrouter.js.map +0 -1
  104. package/dist/semantic/builder.d.ts +0 -55
  105. package/dist/semantic/builder.d.ts.map +0 -1
  106. package/dist/semantic/builder.js +0 -140
  107. package/dist/semantic/builder.js.map +0 -1
  108. package/dist/semantic/events.d.ts +0 -2
  109. package/dist/semantic/events.d.ts.map +0 -1
  110. package/dist/semantic/events.js +0 -5
  111. package/dist/semantic/events.js.map +0 -1
  112. package/dist/semantic/snapshot.d.ts +0 -49
  113. package/dist/semantic/snapshot.d.ts.map +0 -1
  114. package/dist/semantic/snapshot.js +0 -228
  115. package/dist/semantic/snapshot.js.map +0 -1
  116. package/dist/types.d.ts +0 -156
  117. package/dist/types.d.ts.map +0 -1
  118. package/dist/types.js +0 -5
  119. package/dist/types.js.map +0 -1
  120. package/dist/utf8/decoder.d.ts +0 -53
  121. package/dist/utf8/decoder.d.ts.map +0 -1
  122. package/dist/utf8/decoder.js +0 -323
  123. package/dist/utf8/decoder.js.map +0 -1
@@ -1,676 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Incremental JSON Lexical Scanner
3
- // ---------------------------------------------------------------------------
4
- //
5
- // A character-at-a-time state machine that tokenizes JSON incrementally.
6
- // Each call to `feedChar()` advances the state machine by one character.
7
- // Complete tokens are collected via `takeTokens()`.
8
- //
9
- // The scanner does NOT re-scan previous input. It maintains its state
10
- // between characters and across chunk boundaries.
11
- // ---------------------------------------------------------------------------
12
- import { ScannerState } from "./states.js";
13
- import { TokenType } from "./tokens.js";
14
- import { DiagnosticCode } from "../diagnostics/codes.js";
15
- import { createDiagnostic } from "../diagnostics/factory.js";
16
- import { DEFAULT_LIMITS, sanitizeLimit } from "../limits.js";
17
- /** Whitespace characters in JSON. */
18
- function isWhitespace(ch) {
19
- return ch === " " || ch === "\t" || ch === "\n" || ch === "\r";
20
- }
21
- function isDigit(ch) {
22
- return ch >= "0" && ch <= "9";
23
- }
24
- function isHexDigit(ch) {
25
- return ((ch >= "0" && ch <= "9") ||
26
- (ch >= "a" && ch <= "f") ||
27
- (ch >= "A" && ch <= "F"));
28
- }
29
- /**
30
- * Incremental JSON scanner.
31
- * Feed characters one at a time via feedChar().
32
- * Collect tokens via takeTokens().
33
- */
34
- export class Scanner {
35
- options;
36
- maxStringBytes;
37
- constructor(options) {
38
- this.options = options ?? {};
39
- this.maxStringBytes = sanitizeLimit(options?.limits?.maxStringBytes, DEFAULT_LIMITS.maxStringBytes);
40
- }
41
- state = ScannerState.Structural;
42
- tokens = [];
43
- diagnostics = [];
44
- repairs = [];
45
- // --- String state ---
46
- stringBuffer = "";
47
- stringIsKey = false;
48
- stringByteStart = 0;
49
- // --- Unicode escape state ---
50
- unicodeHexBuffer = "";
51
- highSurrogate = 0;
52
- /** Tracks position in surrogate pair sequence: 0=initial, 1=saw backslash, 2=saw 'u' */
53
- surrogatePhase = 0;
54
- // --- Number state ---
55
- numberBuffer = "";
56
- numberByteStart = 0;
57
- numberHasDigit = false;
58
- // --- Literal state ---
59
- literalTarget = "";
60
- literalIndex = 0;
61
- literalByteStart = 0;
62
- // --- Byte offset tracking ---
63
- /** Byte offset of the current character being fed. Set by the caller. */
64
- currentByteOffset = 0;
65
- // --- Depth tracking for string context ---
66
- /** Whether we're in a key position for string parsing. */
67
- nextStringIsKey = false;
68
- get currentState() {
69
- return this.state;
70
- }
71
- /**
72
- * Feed a single character to the scanner.
73
- * @param ch - The character to process.
74
- * @param byteOffset - Absolute byte offset of this character in the input stream.
75
- * @param charByteLength - Number of bytes this character occupies in UTF-8.
76
- */
77
- feedChar(ch, byteOffset, charByteLength) {
78
- this.currentByteOffset = byteOffset;
79
- switch (this.state) {
80
- case ScannerState.Structural:
81
- this.processStructural(ch, byteOffset, charByteLength);
82
- break;
83
- case ScannerState.InString:
84
- this.processString(ch, byteOffset, charByteLength);
85
- break;
86
- case ScannerState.Escape:
87
- this.processEscape(ch, byteOffset, charByteLength);
88
- break;
89
- case ScannerState.UnicodeEscape:
90
- this.processUnicodeEscape(ch, byteOffset, charByteLength);
91
- break;
92
- case ScannerState.UnicodeSurrogatePending:
93
- this.processSurrogatePending(ch, byteOffset, charByteLength);
94
- break;
95
- case ScannerState.NumberInteger:
96
- case ScannerState.NumberFraction:
97
- case ScannerState.NumberExponentStart:
98
- case ScannerState.NumberExponent:
99
- this.processNumber(ch, byteOffset, charByteLength);
100
- break;
101
- case ScannerState.LiteralTrue:
102
- case ScannerState.LiteralFalse:
103
- case ScannerState.LiteralNull:
104
- this.processLiteral(ch, byteOffset, charByteLength);
105
- break;
106
- case ScannerState.TrailingWhitespace:
107
- this.processTrailingWhitespace(ch, byteOffset);
108
- break;
109
- case ScannerState.TrailingData:
110
- // Consume all trailing data silently — diagnostic was already emitted
111
- break;
112
- case ScannerState.Invalid:
113
- case ScannerState.Finished:
114
- // No-op — terminal states
115
- break;
116
- }
117
- }
118
- /**
119
- * Take all tokens accumulated since the last call.
120
- * Clears the internal token buffer.
121
- */
122
- takeTokens() {
123
- const result = this.tokens;
124
- this.tokens = [];
125
- return result;
126
- }
127
- /**
128
- * Take all diagnostics accumulated since the last call.
129
- * Clears the internal diagnostic buffer.
130
- */
131
- takeDiagnostics() {
132
- const result = this.diagnostics;
133
- this.diagnostics = [];
134
- return result;
135
- }
136
- takeRepairs() {
137
- const result = this.repairs;
138
- this.repairs = [];
139
- return result;
140
- }
141
- /**
142
- * Set whether the next string to be scanned should be treated as an object key.
143
- */
144
- setNextStringIsKey(isKey) {
145
- this.nextStringIsKey = isKey;
146
- }
147
- /**
148
- * Get info about any pending (incomplete) value.
149
- */
150
- getPendingInfo() {
151
- switch (this.state) {
152
- case ScannerState.InString:
153
- case ScannerState.Escape:
154
- return {
155
- type: this.stringIsKey ? "object_key" : "string",
156
- buffer: this.stringBuffer,
157
- byteStart: this.stringByteStart,
158
- isKey: this.stringIsKey,
159
- };
160
- case ScannerState.UnicodeEscape:
161
- return {
162
- type: "unicode_escape",
163
- buffer: this.unicodeHexBuffer,
164
- byteStart: this.stringByteStart,
165
- isKey: this.stringIsKey,
166
- };
167
- case ScannerState.UnicodeSurrogatePending:
168
- return {
169
- type: "surrogate",
170
- buffer: this.stringBuffer,
171
- byteStart: this.stringByteStart,
172
- isKey: this.stringIsKey,
173
- };
174
- case ScannerState.NumberInteger:
175
- case ScannerState.NumberFraction:
176
- case ScannerState.NumberExponentStart:
177
- case ScannerState.NumberExponent:
178
- return {
179
- type: "number",
180
- buffer: this.numberBuffer,
181
- byteStart: this.numberByteStart,
182
- isKey: false,
183
- };
184
- case ScannerState.LiteralTrue:
185
- case ScannerState.LiteralFalse:
186
- case ScannerState.LiteralNull:
187
- return {
188
- type: "literal",
189
- buffer: this.literalTarget.slice(0, this.literalIndex),
190
- byteStart: this.literalByteStart,
191
- isKey: false,
192
- };
193
- default:
194
- return { type: "none", buffer: "", byteStart: 0, isKey: false };
195
- }
196
- }
197
- /**
198
- * Check numberBuffer against the parts of RFC 8259's number grammar that
199
- * aren't already enforced by the state machine's character-by-character
200
- * transitions: no leading zero in the integer part, and no trailing
201
- * "." or exponent marker with no digit following it. Returns an error
202
- * message if invalid, or null if the buffer is a well-formed number.
203
- */
204
- numberGrammarError() {
205
- const buf = this.numberBuffer;
206
- const last = buf[buf.length - 1];
207
- if (last === ".") {
208
- return "Number has trailing decimal point with no digits";
209
- }
210
- if (last === "e" || last === "E" || last === "+" || last === "-") {
211
- return "Number has exponent with no digits";
212
- }
213
- const unsigned = buf[0] === "-" ? buf.slice(1) : buf;
214
- let intPart = "";
215
- for (const c of unsigned) {
216
- if (c >= "0" && c <= "9") {
217
- intPart += c;
218
- }
219
- else {
220
- break;
221
- }
222
- }
223
- if (intPart.length > 1 && intPart[0] === "0") {
224
- return "Number has leading zero";
225
- }
226
- return null;
227
- }
228
- /**
229
- * Attempt to finalize a pending number (at end of input or finish()).
230
- * - "finalized": a valid number token was emitted.
231
- * - "invalid": there was a complete-looking buffer, but it violates JSON
232
- * number grammar (leading zero, trailing "." or exponent marker with
233
- * no digit after it). Nothing is emitted; numberBuffer is left as-is.
234
- * - "not_ready": scanner isn't in a finalizable number state at all
235
- * (e.g. buffer is just "-", or ends right at a bare "e"/"E" with no
236
- * sign or digit yet) — unchanged pre-existing behavior, callers should
237
- * not newly diagnose this case.
238
- */
239
- finalizeNumber() {
240
- if (this.state === ScannerState.NumberInteger ||
241
- this.state === ScannerState.NumberFraction ||
242
- this.state === ScannerState.NumberExponent) {
243
- if (this.numberHasDigit) {
244
- if (this.numberGrammarError() !== null) {
245
- return "invalid";
246
- }
247
- this.emitToken(TokenType.Number, this.numberBuffer, this.numberByteStart, this.currentByteOffset + 1);
248
- this.state = ScannerState.TrailingWhitespace;
249
- return "finalized";
250
- }
251
- }
252
- return "not_ready";
253
- }
254
- /**
255
- * Attempt to finalize a pending literal (at end of input or finish).
256
- * Returns true if a literal token was emitted.
257
- */
258
- finalizeLiteral() {
259
- if (this.state === ScannerState.LiteralTrue ||
260
- this.state === ScannerState.LiteralFalse ||
261
- this.state === ScannerState.LiteralNull) {
262
- if (this.literalIndex === this.literalTarget.length) {
263
- const type = this.literalTarget === "true"
264
- ? TokenType.True
265
- : this.literalTarget === "false"
266
- ? TokenType.False
267
- : TokenType.Null;
268
- this.emitToken(type, this.literalTarget, this.literalByteStart, this.currentByteOffset + 1);
269
- this.state = ScannerState.TrailingWhitespace;
270
- return true;
271
- }
272
- }
273
- return false;
274
- }
275
- // -----------------------------------------------------------------------
276
- // State handlers
277
- // -----------------------------------------------------------------------
278
- processStructural(ch, byteOffset, charByteLen) {
279
- if (isWhitespace(ch))
280
- return;
281
- if (ch === "{") {
282
- this.emitToken(TokenType.ObjectStart, "{", byteOffset, byteOffset + charByteLen);
283
- this.state = ScannerState.Structural;
284
- }
285
- else if (ch === "[") {
286
- this.emitToken(TokenType.ArrayStart, "[", byteOffset, byteOffset + charByteLen);
287
- this.state = ScannerState.Structural;
288
- }
289
- else if (ch === "}") {
290
- this.emitToken(TokenType.ObjectEnd, "}", byteOffset, byteOffset + charByteLen);
291
- }
292
- else if (ch === "]") {
293
- this.emitToken(TokenType.ArrayEnd, "]", byteOffset, byteOffset + charByteLen);
294
- }
295
- else if (ch === ",") {
296
- this.emitToken(TokenType.Comma, ",", byteOffset, byteOffset + charByteLen);
297
- }
298
- else if (ch === ":") {
299
- this.emitToken(TokenType.Colon, ":", byteOffset, byteOffset + charByteLen);
300
- }
301
- else if (ch === '"') {
302
- this.beginString(byteOffset);
303
- }
304
- else if (ch === "-" || isDigit(ch)) {
305
- this.beginNumber(ch, byteOffset);
306
- }
307
- else if (ch === "t") {
308
- this.beginLiteral("true", byteOffset);
309
- }
310
- else if (ch === "f") {
311
- this.beginLiteral("false", byteOffset);
312
- }
313
- else if (ch === "n") {
314
- this.beginLiteral("null", byteOffset);
315
- }
316
- else {
317
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Unexpected character: ${JSON.stringify(ch)}`, false);
318
- this.state = ScannerState.Invalid;
319
- }
320
- }
321
- processString(ch, byteOffset, charByteLen) {
322
- // Checked for every character except the closing quote itself (checking
323
- // here rather than only at termination means a string that never closes
324
- // can't grow unboundedly before being caught; still checked on "\\" so
325
- // an all-escape-sequence string can't bypass it either, since escaped
326
- // characters are consumed by processEscape, not this function).
327
- // byteOffset - stringByteStart counts bytes from the *opening* quote, so
328
- // it equals the content length so far for any content character — but
329
- // for the closing quote specifically it equals content length + 1 (the
330
- // opening quote's own byte), which would wrongly reject a string of
331
- // exactly maxStringBytes. Excluding '"' here fixes that off-by-one.
332
- if (ch !== '"' && byteOffset - this.stringByteStart > this.maxStringBytes) {
333
- this.emitDiagnostic(DiagnosticCode.E_LIMIT_STRING_BYTES, "fatal", byteOffset, `String exceeds maximum of ${this.maxStringBytes} bytes`, false);
334
- this.state = ScannerState.Invalid;
335
- return;
336
- }
337
- if (ch === "\\") {
338
- this.state = ScannerState.Escape;
339
- }
340
- else if (ch === '"') {
341
- // String complete
342
- this.emitToken(TokenType.String, this.stringBuffer, this.stringByteStart, byteOffset + charByteLen);
343
- this.stringBuffer = "";
344
- this.state = ScannerState.Structural;
345
- }
346
- else {
347
- // Check for raw control characters
348
- const code = ch.codePointAt(0) ?? 0;
349
- if (code < 0x20) {
350
- if (this.options.repairs?.rawControlCharacters === "escape") {
351
- this.stringBuffer += ch; // Note: semantic character value preserved!
352
- this.repairs.push({
353
- code: "R_ESCAPE_RAW_CONTROL",
354
- byteRange: [byteOffset, byteOffset + charByteLen],
355
- impact: "representation_preserving",
356
- description: `Escaped raw control character U+${code.toString(16).padStart(4, "0")}`,
357
- });
358
- }
359
- else {
360
- this.emitDiagnostic(DiagnosticCode.W_RAW_CONTROL_CHARACTER, "error", // Reject mode makes this an error
361
- byteOffset, `Raw control character U+${code.toString(16).padStart(4, "0")} in string`, false);
362
- this.state = ScannerState.Invalid;
363
- return;
364
- }
365
- }
366
- else {
367
- this.stringBuffer += ch;
368
- }
369
- }
370
- }
371
- processEscape(ch, byteOffset, _charByteLen) {
372
- switch (ch) {
373
- case '"':
374
- case "\\":
375
- case "/":
376
- this.stringBuffer += ch;
377
- this.state = ScannerState.InString;
378
- break;
379
- case "b":
380
- this.stringBuffer += "\b";
381
- this.state = ScannerState.InString;
382
- break;
383
- case "f":
384
- this.stringBuffer += "\f";
385
- this.state = ScannerState.InString;
386
- break;
387
- case "n":
388
- this.stringBuffer += "\n";
389
- this.state = ScannerState.InString;
390
- break;
391
- case "r":
392
- this.stringBuffer += "\r";
393
- this.state = ScannerState.InString;
394
- break;
395
- case "t":
396
- this.stringBuffer += "\t";
397
- this.state = ScannerState.InString;
398
- break;
399
- case "u":
400
- this.unicodeHexBuffer = "";
401
- this.state = ScannerState.UnicodeEscape;
402
- break;
403
- default:
404
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Invalid escape sequence: \\${ch}`, false);
405
- this.state = ScannerState.Invalid;
406
- break;
407
- }
408
- }
409
- processUnicodeEscape(ch, _byteOffset, _charByteLen) {
410
- if (isHexDigit(ch)) {
411
- this.unicodeHexBuffer += ch;
412
- if (this.unicodeHexBuffer.length === 4) {
413
- const codeUnit = parseInt(this.unicodeHexBuffer, 16);
414
- if (this.highSurrogate !== 0) {
415
- // We expect a low surrogate
416
- if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
417
- // Valid surrogate pair
418
- const fullCodePoint = 0x10000 +
419
- ((this.highSurrogate - 0xd800) << 10) +
420
- (codeUnit - 0xdc00);
421
- this.stringBuffer += String.fromCodePoint(fullCodePoint);
422
- this.highSurrogate = 0;
423
- this.state = ScannerState.InString;
424
- }
425
- else {
426
- // Expected low surrogate, got something else
427
- this.emitDiagnostic(DiagnosticCode.E_UNPAIRED_SURROGATE, "error", this.currentByteOffset, `Expected low surrogate after \\u${this.highSurrogate.toString(16).padStart(4, "0")}, got \\u${this.unicodeHexBuffer}`, false);
428
- this.highSurrogate = 0;
429
- this.state = ScannerState.Invalid;
430
- }
431
- }
432
- else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
433
- // High surrogate — need to see \uDC00-\uDFFF next
434
- this.highSurrogate = codeUnit;
435
- this.surrogatePhase = 0;
436
- this.state = ScannerState.UnicodeSurrogatePending;
437
- }
438
- else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
439
- // Unexpected low surrogate without high
440
- this.emitDiagnostic(DiagnosticCode.E_UNPAIRED_SURROGATE, "error", this.currentByteOffset, `Unexpected low surrogate: \\u${this.unicodeHexBuffer}`, false);
441
- this.state = ScannerState.Invalid;
442
- }
443
- else {
444
- this.stringBuffer += String.fromCharCode(codeUnit);
445
- this.state = ScannerState.InString;
446
- }
447
- this.unicodeHexBuffer = "";
448
- }
449
- }
450
- else {
451
- this.emitDiagnostic(DiagnosticCode.E_INVALID_UNICODE_ESCAPE, "error", this.currentByteOffset, `Invalid hex digit in unicode escape: ${ch}`, false);
452
- this.state = ScannerState.Invalid;
453
- }
454
- }
455
- processSurrogatePending(ch, byteOffset, _charByteLen) {
456
- // Expecting \uDCxx sequence
457
- // surrogatePhase: 0 = expecting \, 1 = expecting u
458
- if (this.surrogatePhase === 0) {
459
- if (ch === "\\") {
460
- this.surrogatePhase = 1;
461
- }
462
- else {
463
- // Not a surrogate pair continuation — unpaired high surrogate
464
- this.emitDiagnostic(DiagnosticCode.E_UNPAIRED_SURROGATE, "error", byteOffset, `Expected \\u for low surrogate, got: ${JSON.stringify(ch)}`, false);
465
- this.highSurrogate = 0;
466
- this.state = ScannerState.Invalid;
467
- }
468
- }
469
- else if (this.surrogatePhase === 1) {
470
- if (ch === "u") {
471
- this.unicodeHexBuffer = "";
472
- this.surrogatePhase = 0;
473
- this.state = ScannerState.UnicodeEscape;
474
- }
475
- else {
476
- this.emitDiagnostic(DiagnosticCode.E_UNPAIRED_SURROGATE, "error", byteOffset, `Expected 'u' after '\\' for low surrogate, got: ${JSON.stringify(ch)}`, false);
477
- this.highSurrogate = 0;
478
- this.state = ScannerState.Invalid;
479
- }
480
- }
481
- }
482
- processNumber(ch, byteOffset, charByteLen) {
483
- switch (this.state) {
484
- case ScannerState.NumberInteger:
485
- if (isDigit(ch)) {
486
- this.numberBuffer += ch;
487
- this.numberHasDigit = true;
488
- }
489
- else if (ch === "." && this.numberHasDigit) {
490
- this.numberBuffer += ch;
491
- this.state = ScannerState.NumberFraction;
492
- }
493
- else if ((ch === "e" || ch === "E") && this.numberHasDigit) {
494
- this.numberBuffer += ch;
495
- this.state = ScannerState.NumberExponentStart;
496
- }
497
- else if (this.numberHasDigit && this.isValueTerminator(ch)) {
498
- this.commitNumber(byteOffset);
499
- this.reprocessChar(ch, byteOffset, charByteLen);
500
- }
501
- else {
502
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Invalid character in number: ${JSON.stringify(ch)}`, false);
503
- this.state = ScannerState.Invalid;
504
- }
505
- break;
506
- case ScannerState.NumberFraction:
507
- if (isDigit(ch)) {
508
- this.numberBuffer += ch;
509
- }
510
- else if (ch === "e" || ch === "E") {
511
- this.numberBuffer += ch;
512
- this.state = ScannerState.NumberExponentStart;
513
- }
514
- else if (this.isValueTerminator(ch)) {
515
- // Validate that we have at least one fraction digit
516
- const lastChar = this.numberBuffer[this.numberBuffer.length - 1];
517
- if (lastChar === ".") {
518
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, "Number has trailing decimal point with no digits", false);
519
- this.state = ScannerState.Invalid;
520
- }
521
- else {
522
- this.commitNumber(byteOffset);
523
- this.reprocessChar(ch, byteOffset, charByteLen);
524
- }
525
- }
526
- else {
527
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Invalid character in number fraction: ${JSON.stringify(ch)}`, false);
528
- this.state = ScannerState.Invalid;
529
- }
530
- break;
531
- case ScannerState.NumberExponentStart:
532
- if (isDigit(ch)) {
533
- this.numberBuffer += ch;
534
- this.state = ScannerState.NumberExponent;
535
- }
536
- else if (ch === "+" || ch === "-") {
537
- this.numberBuffer += ch;
538
- this.state = ScannerState.NumberExponent;
539
- }
540
- else {
541
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Expected digit or sign after exponent, got: ${JSON.stringify(ch)}`, false);
542
- this.state = ScannerState.Invalid;
543
- }
544
- break;
545
- case ScannerState.NumberExponent:
546
- if (isDigit(ch)) {
547
- this.numberBuffer += ch;
548
- }
549
- else if (this.isValueTerminator(ch)) {
550
- // Validate we have at least one exponent digit
551
- const last = this.numberBuffer[this.numberBuffer.length - 1];
552
- if (last === "+" || last === "-" || last === "e" || last === "E") {
553
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, "Number has exponent with no digits", false);
554
- this.state = ScannerState.Invalid;
555
- }
556
- else {
557
- this.commitNumber(byteOffset);
558
- this.reprocessChar(ch, byteOffset, charByteLen);
559
- }
560
- }
561
- else {
562
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Invalid character in number exponent: ${JSON.stringify(ch)}`, false);
563
- this.state = ScannerState.Invalid;
564
- }
565
- break;
566
- }
567
- }
568
- processLiteral(ch, byteOffset, charByteLen) {
569
- const expected = this.literalTarget[this.literalIndex];
570
- if (ch === expected) {
571
- this.literalIndex++;
572
- if (this.literalIndex === this.literalTarget.length) {
573
- // Literal is fully matched, but we can't emit until we see a terminator.
574
- // Stay in the literal state — the next character will either be a terminator
575
- // or invalid. We handle this by checking if we've reached the full length
576
- // in the next call.
577
- // Actually, we need to wait for a terminator to commit the literal,
578
- // similar to numbers. Let's transition to a "literal complete, awaiting terminator" state.
579
- // For simplicity, we'll check on the next character.
580
- }
581
- }
582
- else if (this.literalIndex === this.literalTarget.length) {
583
- // Literal was fully matched — this character is the terminator
584
- if (this.isValueTerminator(ch)) {
585
- const type = this.literalTarget === "true"
586
- ? TokenType.True
587
- : this.literalTarget === "false"
588
- ? TokenType.False
589
- : TokenType.Null;
590
- this.emitToken(type, this.literalTarget, this.literalByteStart, byteOffset);
591
- this.state = ScannerState.Structural;
592
- this.reprocessChar(ch, byteOffset, charByteLen);
593
- }
594
- else {
595
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Expected value terminator after '${this.literalTarget}', got: ${JSON.stringify(ch)}`, false);
596
- this.state = ScannerState.Invalid;
597
- }
598
- }
599
- else {
600
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", byteOffset, `Invalid literal: expected '${this.literalTarget[this.literalIndex]}' but got '${ch}' (parsing '${this.literalTarget}')`, false);
601
- this.state = ScannerState.Invalid;
602
- }
603
- }
604
- processTrailingWhitespace(ch, byteOffset) {
605
- if (isWhitespace(ch))
606
- return;
607
- // Non-whitespace after root value
608
- this.emitDiagnostic(DiagnosticCode.E_TRAILING_DATA, "error", byteOffset, `Unexpected data after root JSON value: ${JSON.stringify(ch)}`, false);
609
- this.state = ScannerState.TrailingData;
610
- }
611
- // -----------------------------------------------------------------------
612
- // Helpers
613
- // -----------------------------------------------------------------------
614
- beginString(byteOffset) {
615
- this.stringBuffer = "";
616
- this.stringByteStart = byteOffset;
617
- this.stringIsKey = this.nextStringIsKey;
618
- this.nextStringIsKey = false;
619
- this.highSurrogate = 0;
620
- this.state = ScannerState.InString;
621
- }
622
- beginNumber(firstChar, byteOffset) {
623
- this.numberBuffer = firstChar;
624
- this.numberByteStart = byteOffset;
625
- this.numberHasDigit = firstChar !== "-";
626
- this.state = ScannerState.NumberInteger;
627
- }
628
- beginLiteral(target, byteOffset) {
629
- this.literalTarget = target;
630
- this.literalIndex = 1; // first char already matched
631
- this.literalByteStart = byteOffset;
632
- if (target === "true") {
633
- this.state = ScannerState.LiteralTrue;
634
- }
635
- else if (target === "false") {
636
- this.state = ScannerState.LiteralFalse;
637
- }
638
- else {
639
- this.state = ScannerState.LiteralNull;
640
- }
641
- }
642
- commitNumber(endByteOffset) {
643
- // NumberFraction/NumberExponent's terminator branches already reject
644
- // trailing "."/exponent-with-no-digit before calling this; leading zero
645
- // is not caught by any state transition, so it's checked uniformly here
646
- // for all three call sites (integer, fraction, exponent terminators).
647
- const error = this.numberGrammarError();
648
- if (error !== null) {
649
- this.emitDiagnostic(DiagnosticCode.E_UNEXPECTED_TOKEN, "error", this.numberByteStart, error, false);
650
- this.state = ScannerState.Invalid;
651
- return;
652
- }
653
- this.emitToken(TokenType.Number, this.numberBuffer, this.numberByteStart, endByteOffset);
654
- this.numberBuffer = "";
655
- this.state = ScannerState.Structural;
656
- }
657
- isValueTerminator(ch) {
658
- return (isWhitespace(ch) ||
659
- ch === "," ||
660
- ch === "}" ||
661
- ch === "]" ||
662
- ch === ":");
663
- }
664
- reprocessChar(ch, byteOffset, charByteLen) {
665
- // After committing a number/literal, state has been set to Structural.
666
- // Reprocess the terminating character in the new state.
667
- this.feedChar(ch, byteOffset, charByteLen);
668
- }
669
- emitToken(type, value, byteStart, byteEnd) {
670
- this.tokens.push({ type, value, byteStart, byteEnd });
671
- }
672
- emitDiagnostic(code, severity, byteOffset, message, recoverable) {
673
- this.diagnostics.push(createDiagnostic(code, severity, byteOffset, message, recoverable));
674
- }
675
- }
676
- //# sourceMappingURL=scanner.js.map