@linxiraos/pi-utils 1.1.7 → 1.1.9

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 (188) hide show
  1. package/CHANGELOG.md +2 -8
  2. package/README.md +18 -18
  3. package/package.json +57 -63
  4. package/src/acp/connection.ts +6 -10
  5. package/src/ar/entries.ts +1 -1
  6. package/src/ar/sevenzip.ts +17 -2
  7. package/src/async.ts +25 -4
  8. package/src/browsers.ts +11 -5
  9. package/src/dirs.ts +15 -10
  10. package/src/dom/core.ts +4 -4
  11. package/src/env.ts +12 -0
  12. package/src/fetch-retry.ts +36 -1
  13. package/src/format.ts +1 -1
  14. package/src/frontmatter.ts +1 -0
  15. package/src/incoming-json.ts +856 -0
  16. package/src/index.ts +2 -0
  17. package/src/json-lexer.ts +413 -0
  18. package/src/json-parse.ts +93 -350
  19. package/src/marked/core.ts +8 -1
  20. package/src/materialize-string.ts +10 -0
  21. package/src/path.ts +14 -0
  22. package/src/postmortem.ts +33 -6
  23. package/src/prompt.ts +1 -0
  24. package/src/ring.ts +1 -0
  25. package/src/temp.ts +16 -4
  26. package/src/template.ts +2 -2
  27. package/src/tls-fetch.ts +2 -16
  28. package/src/turndown/html.ts +1 -1
  29. package/src/vterm/query-responder.ts +76 -0
  30. package/src/vterm.ts +1 -0
  31. package/THIRD-PARTY-NOTICES.txt +0 -22909
  32. package/dist/types/abortable.d.ts +0 -32
  33. package/dist/types/acp/connection.d.ts +0 -118
  34. package/dist/types/acp/protocol.d.ts +0 -526
  35. package/dist/types/acp/schema.d.ts +0 -41
  36. package/dist/types/acp/stream.d.ts +0 -8
  37. package/dist/types/acp/transport.d.ts +0 -88
  38. package/dist/types/acp.d.ts +0 -6
  39. package/dist/types/ar/arj.d.ts +0 -5
  40. package/dist/types/ar/asar.d.ts +0 -7
  41. package/dist/types/ar/bytes.d.ts +0 -20
  42. package/dist/types/ar/cab.d.ts +0 -5
  43. package/dist/types/ar/checksums.d.ts +0 -10
  44. package/dist/types/ar/codecs/bzip2.d.ts +0 -4
  45. package/dist/types/ar/codecs/gzip.d.ts +0 -6
  46. package/dist/types/ar/codecs/lzma.d.ts +0 -6
  47. package/dist/types/ar/codecs/lzw.d.ts +0 -4
  48. package/dist/types/ar/codecs/lzx.d.ts +0 -7
  49. package/dist/types/ar/codecs/xz.d.ts +0 -4
  50. package/dist/types/ar/codecs/zstd.d.ts +0 -6
  51. package/dist/types/ar/cpio.d.ts +0 -7
  52. package/dist/types/ar/deb.d.ts +0 -5
  53. package/dist/types/ar/entries.d.ts +0 -22
  54. package/dist/types/ar/error.d.ts +0 -8
  55. package/dist/types/ar/index.d.ts +0 -11
  56. package/dist/types/ar/iso.d.ts +0 -5
  57. package/dist/types/ar/limits.d.ts +0 -33
  58. package/dist/types/ar/lzh.d.ts +0 -7
  59. package/dist/types/ar/open.d.ts +0 -45
  60. package/dist/types/ar/paths.d.ts +0 -18
  61. package/dist/types/ar/rar/rar4-decoder.d.ts +0 -6
  62. package/dist/types/ar/rar/rar5-decoder.d.ts +0 -6
  63. package/dist/types/ar/rar.d.ts +0 -5
  64. package/dist/types/ar/reader.d.ts +0 -28
  65. package/dist/types/ar/registry.d.ts +0 -17
  66. package/dist/types/ar/rpm.d.ts +0 -5
  67. package/dist/types/ar/sevenzip/decode.d.ts +0 -33
  68. package/dist/types/ar/sevenzip.d.ts +0 -5
  69. package/dist/types/ar/source.d.ts +0 -54
  70. package/dist/types/ar/tar.d.ts +0 -9
  71. package/dist/types/ar/types.d.ts +0 -99
  72. package/dist/types/ar/unix-ar.d.ts +0 -7
  73. package/dist/types/ar/write.d.ts +0 -10
  74. package/dist/types/ar/zip.d.ts +0 -10
  75. package/dist/types/async.d.ts +0 -22
  76. package/dist/types/binary.d.ts +0 -23
  77. package/dist/types/browsers.d.ts +0 -68
  78. package/dist/types/chalk.d.ts +0 -125
  79. package/dist/types/cli.d.ts +0 -134
  80. package/dist/types/color.d.ts +0 -136
  81. package/dist/types/dates.d.ts +0 -7
  82. package/dist/types/dirs.d.ts +0 -325
  83. package/dist/types/docx/converter.d.ts +0 -46
  84. package/dist/types/docx/xml.d.ts +0 -26
  85. package/dist/types/docx.d.ts +0 -11
  86. package/dist/types/dom/core.d.ts +0 -431
  87. package/dist/types/dom/parser.d.ts +0 -7
  88. package/dist/types/dom/selector.d.ts +0 -5
  89. package/dist/types/dom.d.ts +0 -5
  90. package/dist/types/env.d.ts +0 -124
  91. package/dist/types/fetch-retry.d.ts +0 -96
  92. package/dist/types/file-lock.d.ts +0 -23
  93. package/dist/types/format.d.ts +0 -37
  94. package/dist/types/frontmatter.d.ts +0 -46
  95. package/dist/types/fs-error.d.ts +0 -31
  96. package/dist/types/glob.d.ts +0 -28
  97. package/dist/types/headers.d.ts +0 -34
  98. package/dist/types/index.d.ts +0 -41
  99. package/dist/types/json-parse.d.ts +0 -83
  100. package/dist/types/json.d.ts +0 -20
  101. package/dist/types/logger/rotating-file.d.ts +0 -18
  102. package/dist/types/logger.d.ts +0 -96
  103. package/dist/types/loop-phase.d.ts +0 -10
  104. package/dist/types/lru.d.ts +0 -46
  105. package/dist/types/marked/core.d.ts +0 -445
  106. package/dist/types/marked.d.ts +0 -2
  107. package/dist/types/math-delimiters.d.ts +0 -45
  108. package/dist/types/mermaid-ascii.d.ts +0 -11
  109. package/dist/types/mime.d.ts +0 -29
  110. package/dist/types/module-timer.d.ts +0 -1
  111. package/dist/types/path-tree.d.ts +0 -76
  112. package/dist/types/path.d.ts +0 -2
  113. package/dist/types/peek-file.d.ts +0 -29
  114. package/dist/types/postmortem.d.ts +0 -174
  115. package/dist/types/process-name.d.ts +0 -7
  116. package/dist/types/procmgr.d.ts +0 -74
  117. package/dist/types/prompt.d.ts +0 -18
  118. package/dist/types/ptree.d.ts +0 -124
  119. package/dist/types/readability/readability.d.ts +0 -9
  120. package/dist/types/readability/readerable.d.ts +0 -10
  121. package/dist/types/readability/types.d.ts +0 -70
  122. package/dist/types/readability.d.ts +0 -4
  123. package/dist/types/ring.d.ts +0 -93
  124. package/dist/types/runtime-install.d.ts +0 -85
  125. package/dist/types/sanitize-text.d.ts +0 -29
  126. package/dist/types/snowflake.d.ts +0 -25
  127. package/dist/types/sqlite.d.ts +0 -26
  128. package/dist/types/stderr-guard.d.ts +0 -22
  129. package/dist/types/stream.d.ts +0 -75
  130. package/dist/types/tab-spacing.d.ts +0 -24
  131. package/dist/types/temp.d.ts +0 -17
  132. package/dist/types/template.d.ts +0 -62
  133. package/dist/types/timing-buffer.d.ts +0 -22
  134. package/dist/types/tls-fetch.d.ts +0 -37
  135. package/dist/types/turndown/gfm.d.ts +0 -11
  136. package/dist/types/turndown/html.d.ts +0 -5
  137. package/dist/types/turndown/service.d.ts +0 -21
  138. package/dist/types/turndown/types.d.ts +0 -70
  139. package/dist/types/turndown.d.ts +0 -4
  140. package/dist/types/type-guards.d.ts +0 -5
  141. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
  142. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
  143. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
  144. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
  145. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
  146. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
  147. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
  148. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
  149. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
  150. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
  151. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
  152. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
  153. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
  154. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
  155. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
  156. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
  157. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
  158. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
  159. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
  160. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
  161. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
  162. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
  163. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
  164. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
  165. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
  166. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
  167. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
  168. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
  169. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
  170. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
  171. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
  172. package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
  173. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
  174. package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
  175. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
  176. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
  177. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -23
  178. package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
  179. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
  180. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
  181. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
  182. package/dist/types/version.d.ts +0 -18
  183. package/dist/types/vterm/buffer.d.ts +0 -99
  184. package/dist/types/vterm/terminal.d.ts +0 -44
  185. package/dist/types/vterm.d.ts +0 -8
  186. package/dist/types/which.d.ts +0 -37
  187. package/dist/types/worker-host.d.ts +0 -51
  188. package/dist/types/xml.d.ts +0 -31
package/src/index.ts CHANGED
@@ -10,10 +10,12 @@ export * from "./format";
10
10
  export * from "./frontmatter";
11
11
  export * from "./fs-error";
12
12
  export * from "./glob";
13
+ export * from "./incoming-json";
13
14
  export * from "./json";
14
15
  export * from "./json-parse";
15
16
  export * as logger from "./logger";
16
17
  export * from "./loop-phase";
18
+ export * from "./materialize-string";
17
19
  export * from "./math-delimiters";
18
20
  export * from "./mermaid-ascii";
19
21
  export * from "./mime";
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Tolerant JSON lexer shared by the final parser (`parseJsonWithRepair`), the
3
+ * streaming partial builder (`parseStreamingJson`), and the incoming cursors
4
+ * (`IncomingDoc`). {@link JsonLexerMode} selects how truncation and unescaped
5
+ * inner double quotes are treated.
6
+ *
7
+ * The grammar is a forgiving superset of JSON covering malformations commonly
8
+ * produced by language models:
9
+ *
10
+ * - single-quoted strings and unquoted object keys (JSON5);
11
+ * - trailing / stray commas, and `//` + block comments;
12
+ * - Python literals `True` / `False` / `None`, plus `0x` / `0b` numbers;
13
+ * - raw control characters and invalid `\x` escapes inside strings (kept literally);
14
+ * - unescaped quotes inside strings — a single quote only closes a string when
15
+ * followed by a value terminator, recovering apostrophes such as `'it's'`;
16
+ * the same recovery applies to double quotes in `streaming` mode only,
17
+ * everywhere else they close strictly;
18
+ * - unquoted string values in value position — an unrecognized bareword such
19
+ * as `{"paths": packages/foo/*}` is recovered as a string up to the next
20
+ * `,` / `}` / `]` / newline.
21
+ */
22
+
23
+ export const QUOTE = 0x22;
24
+ export const SQUOTE = 0x27;
25
+ export const BACKSLASH = 0x5c;
26
+ export const SLASH = 0x2f;
27
+ export const COLON = 0x3a;
28
+ export const COMMA = 0x2c;
29
+ export const LBRACE = 0x7b;
30
+ export const RBRACE = 0x7d;
31
+ export const LBRACKET = 0x5b;
32
+ export const RBRACKET = 0x5d;
33
+ const U = 0x75;
34
+
35
+ /** Valid chars after `\` in a strict JSON escape: `" \ / b f n r t u`. */
36
+ export const VALID_ESCAPE_CHAR = new Uint8Array(128);
37
+ for (const ch of '"\\/bfnrtu') VALID_ESCAPE_CHAR[ch.charCodeAt(0)] = 1;
38
+
39
+ export function isHexDigit(cp: number): boolean {
40
+ return (cp >= 0x30 && cp <= 0x39) || ((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x66);
41
+ }
42
+
43
+ /** JSON insignificant whitespace (RFC 8259 §2). */
44
+ export function isWhitespace(cp: number): boolean {
45
+ return cp === 0x20 || cp === 0x09 || cp === 0x0a || cp === 0x0d;
46
+ }
47
+
48
+ function isIdentChar(cp: number): boolean {
49
+ return (
50
+ (cp >= 0x30 && cp <= 0x39) ||
51
+ ((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x7a) ||
52
+ cp === 0x5f /* _ */ ||
53
+ cp === 0x24 /* $ */
54
+ );
55
+ }
56
+
57
+ /** First char of a numeric token: sign, dot, or digit. */
58
+ export function isNumberStart(cp: number): boolean {
59
+ return cp === 0x2d /* - */ || cp === 0x2b /* + */ || cp === 0x2e /* . */ || (cp >= 0x30 && cp <= 0x39);
60
+ }
61
+
62
+ /** Chars that may continue a relaxed numeric token: digits, sign, dot, exponent, radix prefix, hex digits. */
63
+ function isNumberChar(cp: number): boolean {
64
+ return (
65
+ (cp >= 0x30 && cp <= 0x39) ||
66
+ cp === 0x2d ||
67
+ cp === 0x2b ||
68
+ cp === 0x2e ||
69
+ ((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x66) ||
70
+ (cp | 0x20) === 0x78 /* x */
71
+ );
72
+ }
73
+
74
+ /** Keyword literals: standard JSON plus Python `True`/`False`/`None`. */
75
+ const KEYWORDS: readonly (readonly [string, boolean | null])[] = [
76
+ ["true", true],
77
+ ["false", false],
78
+ ["null", null],
79
+ ["True", true],
80
+ ["False", false],
81
+ ["None", null],
82
+ ];
83
+
84
+ /**
85
+ * JS-only atoms never recovered as bareword strings — a tool must not execute
86
+ * with a non-finite or undefined argument masquerading as a string.
87
+ */
88
+ const NON_RECOVERABLE_BAREWORDS: Record<string, true> = {
89
+ NaN: true,
90
+ Infinity: true,
91
+ "-Infinity": true,
92
+ "+Infinity": true,
93
+ undefined: true,
94
+ };
95
+
96
+ /** Decode four hex digits at `pos`, or `-1` when fewer than four hex digits are available. */
97
+ function hex4(s: string, pos: number): number {
98
+ if (pos + 4 > s.length) return -1;
99
+ let value = 0;
100
+ for (let k = pos; k < pos + 4; k++) {
101
+ const cp = s.charCodeAt(k);
102
+ if (!isHexDigit(cp)) return -1;
103
+ value = (value << 4) | parseInt(s[k], 16);
104
+ }
105
+ return value;
106
+ }
107
+
108
+ /**
109
+ * Index of the first char at or after `i` that is not whitespace or part of a
110
+ * `//` line / `/* *\/` block comment. A lone `/` at the end of input is not a
111
+ * comment and is left in place.
112
+ */
113
+ function skipInsignificant(s: string, i: number): number {
114
+ const n = s.length;
115
+ for (;;) {
116
+ while (i < n && isWhitespace(s.charCodeAt(i))) i++;
117
+ if (i + 1 < n && s.charCodeAt(i) === SLASH) {
118
+ const next = s.charCodeAt(i + 1);
119
+ if (next === SLASH) {
120
+ i += 2;
121
+ while (i < n && s.charCodeAt(i) !== 0x0a) i++;
122
+ continue;
123
+ }
124
+ if (next === 0x2a /* * */) {
125
+ i += 2;
126
+ while (i + 1 < n && !(s.charCodeAt(i) === 0x2a && s.charCodeAt(i + 1) === SLASH)) i++;
127
+ i = Math.min(i + 2, n);
128
+ continue;
129
+ }
130
+ }
131
+ return i;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Grammar tolerance selected by the lexer's consumer.
137
+ *
138
+ * - `strict`: final parse — complete input required, double quotes close strictly.
139
+ * - `streaming`: mid-stream snapshot — incomplete tokens tolerated and unescaped
140
+ * inner double quotes recovered for display.
141
+ * - `incoming`: incremental typed pulls — incomplete tokens tolerated, but double
142
+ * quotes close strictly so pulled values match the final parse.
143
+ */
144
+ export type JsonLexerMode = "strict" | "streaming" | "incoming";
145
+
146
+ /** Decoded state of a string token at the current streaming edge. */
147
+ export interface JsonStringProgress {
148
+ /** Decoded content so far (complete when `complete` is true). */
149
+ value: string;
150
+ /**
151
+ * Length of the prefix of `value` whose meaning cannot change when more
152
+ * input arrives. Excludes a trailing split escape, a high surrogate whose
153
+ * low half may still follow, and everything from a quote whose close/inner
154
+ * reading is still undecidable at the buffer edge.
155
+ */
156
+ stableLen: number;
157
+ /** Whether the closing quote was consumed. */
158
+ complete: boolean;
159
+ }
160
+
161
+ /** Reading of the lookahead past a candidate closing quote. */
162
+ const enum QuoteLook {
163
+ /** A value terminator (or end of input) follows: the quote closes. */
164
+ Closes,
165
+ /** Ordinary content follows: the quote is literal (inner-quote recovery). */
166
+ Inner,
167
+ /** A lone `/` at the buffer edge may still grow into a comment and flip this quote from inner to closing. */
168
+ Undecided,
169
+ }
170
+
171
+ /**
172
+ * Cursor over the input with the tolerant token readers. `pos` is the current
173
+ * offset; readers advance it. In `strict` mode a truncated or malformed token
174
+ * throws `SyntaxError`; the lenient modes report progress or return
175
+ * `undefined` so the caller can roll back.
176
+ */
177
+ export class JsonLexer {
178
+ pos: number;
179
+
180
+ constructor(
181
+ readonly src: string,
182
+ readonly mode: JsonLexerMode,
183
+ pos = 0,
184
+ ) {
185
+ this.pos = pos;
186
+ }
187
+
188
+ get atEnd(): boolean {
189
+ return this.pos >= this.src.length;
190
+ }
191
+
192
+ /** Char code at the cursor; `NaN` at end of input (so every comparison is false). */
193
+ peek(): number {
194
+ return this.src.charCodeAt(this.pos);
195
+ }
196
+
197
+ /** Skip whitespace plus `//` line and `/* *\/` block comments. */
198
+ ws(): void {
199
+ this.pos = skipInsignificant(this.src, this.pos);
200
+ }
201
+
202
+ /**
203
+ * Read a string starting at the opening `quote`, retaining the information
204
+ * an incremental consumer needs. Strict mode throws on an unterminated
205
+ * string; lenient modes consume to the end of input and report progress.
206
+ */
207
+ string(quote: number): JsonStringProgress {
208
+ const s = this.src;
209
+ const n = s.length;
210
+ let i = this.pos + 1; // skip opening quote
211
+ let out = "";
212
+ let runStart = i;
213
+ let unstableFrom = -1;
214
+ // Apostrophe / inner-quote recovery (a quote that isn't followed by a
215
+ // value terminator is literal) is always safe for single quotes; for
216
+ // double quotes it is streaming-only display leniency. Elsewhere double
217
+ // quotes close on the first unescaped quote like standard JSON, so
218
+ // malformed structure fails loudly instead of silently swallowing
219
+ // commas/colons or sibling members.
220
+ const lenient = quote === SQUOTE || this.mode === "streaming";
221
+ while (i < n) {
222
+ const cc = s.charCodeAt(i);
223
+ if (cc !== BACKSLASH && cc !== quote) {
224
+ i++;
225
+ continue;
226
+ }
227
+ if (cc === quote) {
228
+ const look = lenient ? this.#quoteLookahead(i + 1) : QuoteLook.Closes;
229
+ if (look === QuoteLook.Closes) {
230
+ out += s.slice(runStart, i);
231
+ this.pos = i + 1;
232
+ return { value: out, stableLen: out.length, complete: true };
233
+ }
234
+ if (look === QuoteLook.Undecided && unstableFrom < 0) unstableFrom = out.length + (i - runStart);
235
+ i++;
236
+ continue;
237
+ }
238
+ // Backslash escape.
239
+ out += s.slice(runStart, i);
240
+ const escapeStart = out.length;
241
+ i++;
242
+ if (i >= n) {
243
+ if (unstableFrom < 0) unstableFrom = escapeStart;
244
+ out += "\\";
245
+ runStart = i;
246
+ break;
247
+ }
248
+ const esc = s.charCodeAt(i);
249
+ switch (esc) {
250
+ case QUOTE:
251
+ out += '"';
252
+ break;
253
+ case SQUOTE:
254
+ out += "'";
255
+ break;
256
+ case BACKSLASH:
257
+ out += "\\";
258
+ break;
259
+ case SLASH:
260
+ out += "/";
261
+ break;
262
+ case 0x62:
263
+ out += "\b";
264
+ break;
265
+ case 0x66:
266
+ out += "\f";
267
+ break;
268
+ case 0x6e:
269
+ out += "\n";
270
+ break;
271
+ case 0x72:
272
+ out += "\r";
273
+ break;
274
+ case 0x74:
275
+ out += "\t";
276
+ break;
277
+ case U: {
278
+ const unit = hex4(s, i + 1);
279
+ if (unit >= 0) {
280
+ i += 4;
281
+ out += String.fromCharCode(unit);
282
+ // A high surrogate at the streaming edge may acquire its low
283
+ // surrogate in the next fragment, so do not commit it yet.
284
+ if (unit >= 0xd800 && unit < 0xdc00 && this.mode !== "strict" && i + 7 > n && unstableFrom < 0) {
285
+ unstableFrom = escapeStart;
286
+ }
287
+ } else {
288
+ if (i + 5 > n && this.mode !== "strict" && unstableFrom < 0) unstableFrom = escapeStart;
289
+ out += "\\u"; // invalid \u — keep literal
290
+ }
291
+ break;
292
+ }
293
+ default:
294
+ out += `\\${s[i]}`; // invalid escape — keep backslash literal
295
+ }
296
+ i++;
297
+ runStart = i;
298
+ }
299
+ if (this.mode === "strict") throw new SyntaxError("Unterminated string");
300
+ out += s.slice(runStart, n);
301
+ this.pos = i;
302
+ return { value: out, stableLen: unstableFrom < 0 ? out.length : unstableFrom, complete: false };
303
+ }
304
+
305
+ /**
306
+ * Classify the lookahead after a candidate closing quote: a quote closes a
307
+ * string only when the next significant char (past whitespace and comments)
308
+ * ends a value.
309
+ */
310
+ #quoteLookahead(from: number): QuoteLook {
311
+ const s = this.src;
312
+ const k = skipInsignificant(s, from);
313
+ if (k >= s.length) return QuoteLook.Closes;
314
+ const c = s.charCodeAt(k);
315
+ if (c === COMMA || c === RBRACE || c === RBRACKET || c === COLON) return QuoteLook.Closes;
316
+ if (c === SLASH && k + 1 === s.length && this.mode !== "strict") return QuoteLook.Undecided;
317
+ return QuoteLook.Inner;
318
+ }
319
+
320
+ /**
321
+ * Read a numeric token with JS `Number()` semantics (decimal with optional
322
+ * sign / leading or trailing dot / exponent, plus `0x` hex and `0b` binary).
323
+ * Non-finite or malformed tokens throw in strict mode and return
324
+ * `undefined` in lenient modes; the cursor is left past the token either way.
325
+ */
326
+ number(): number | undefined {
327
+ const s = this.src;
328
+ const start = this.pos;
329
+ let i = start;
330
+ while (i < s.length && isNumberChar(s.charCodeAt(i))) i++;
331
+ this.pos = i;
332
+ const token = s.slice(start, i);
333
+ const value = Number(token);
334
+ if (Number.isFinite(value)) return value;
335
+ if (this.mode === "strict") throw new SyntaxError(`Invalid number: ${token}`);
336
+ return undefined;
337
+ }
338
+
339
+ /**
340
+ * Match a keyword literal at the cursor; consumes only on success and
341
+ * returns `undefined` otherwise. Requires a non-identifier boundary so
342
+ * `Truex` / `nullish` are not misread as the keyword followed by junk.
343
+ */
344
+ keyword(): boolean | null | undefined {
345
+ const s = this.src;
346
+ for (const [word, value] of KEYWORDS) {
347
+ if (s.startsWith(word, this.pos) && !isIdentChar(s.charCodeAt(this.pos + word.length))) {
348
+ this.pos += word.length;
349
+ return value;
350
+ }
351
+ }
352
+ return undefined;
353
+ }
354
+
355
+ /** Read an unquoted object key: everything up to `:` / `,` / `}` / whitespace. May be empty. */
356
+ unquotedKey(): string {
357
+ const s = this.src;
358
+ const start = this.pos;
359
+ let i = start;
360
+ while (i < s.length) {
361
+ const cc = s.charCodeAt(i);
362
+ if (cc === COLON || cc === COMMA || cc === RBRACE || isWhitespace(cc)) break;
363
+ i++;
364
+ }
365
+ this.pos = i;
366
+ return s.slice(start, i);
367
+ }
368
+
369
+ /**
370
+ * Recover an unquoted string value, e.g. `{"paths": packages/foo/*}`:
371
+ * consume until `,` / `}` / `]` / newline and trim trailing whitespace.
372
+ * Recovery still fails — so a final parse never accepts a half-formed or
373
+ * non-finite argument — when the token:
374
+ * - hits end-of-input before a delimiter (truncated value);
375
+ * - contains a `"`, `{`, `[`, or a key-like `:` — this grammar accepts
376
+ * unquoted keys, so a missed comma (`{"a": foo "b": 1}`) would otherwise
377
+ * silently swallow the following field. A colon followed by `/` or `\`
378
+ * stays literal so URL and Windows-path values recover;
379
+ * - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
380
+ *
381
+ * Failure throws in strict mode and returns `undefined` in lenient modes.
382
+ */
383
+ bareword(): string | undefined {
384
+ const s = this.src;
385
+ const start = this.pos;
386
+ let i = start;
387
+ while (i < s.length) {
388
+ const cc = s.charCodeAt(i);
389
+ if (cc === COMMA || cc === RBRACE || cc === RBRACKET || cc === 0x0a || cc === 0x0d) break;
390
+ if (
391
+ cc === QUOTE ||
392
+ cc === LBRACE ||
393
+ cc === LBRACKET ||
394
+ (cc === COLON && s.charCodeAt(i + 1) !== SLASH && s.charCodeAt(i + 1) !== BACKSLASH)
395
+ ) {
396
+ return this.#unexpected(start);
397
+ }
398
+ i++;
399
+ }
400
+ if (i >= s.length) return this.#unexpected(start);
401
+ let end = i;
402
+ while (end > start && isWhitespace(s.charCodeAt(end - 1))) end--;
403
+ const word = s.slice(start, end);
404
+ if (NON_RECOVERABLE_BAREWORDS[word]) return this.#unexpected(start);
405
+ this.pos = i;
406
+ return word;
407
+ }
408
+
409
+ #unexpected(at: number): undefined {
410
+ if (this.mode === "strict") throw new SyntaxError(`Unexpected token at position ${at}`);
411
+ return undefined;
412
+ }
413
+ }