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