@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.
- package/CHANGELOG.md +2 -8
- package/README.md +18 -18
- package/package.json +57 -63
- package/src/acp/connection.ts +6 -10
- package/src/ar/entries.ts +1 -1
- package/src/ar/sevenzip.ts +17 -2
- package/src/async.ts +25 -4
- package/src/browsers.ts +11 -5
- package/src/dirs.ts +15 -10
- package/src/dom/core.ts +4 -4
- package/src/env.ts +12 -0
- package/src/fetch-retry.ts +36 -1
- package/src/format.ts +1 -1
- package/src/frontmatter.ts +1 -0
- package/src/incoming-json.ts +856 -0
- package/src/index.ts +2 -0
- package/src/json-lexer.ts +413 -0
- package/src/json-parse.ts +93 -350
- package/src/marked/core.ts +8 -1
- package/src/materialize-string.ts +10 -0
- package/src/path.ts +14 -0
- package/src/postmortem.ts +33 -6
- package/src/prompt.ts +1 -0
- package/src/ring.ts +1 -0
- package/src/temp.ts +16 -4
- package/src/template.ts +2 -2
- package/src/tls-fetch.ts +2 -16
- package/src/turndown/html.ts +1 -1
- package/src/vterm/query-responder.ts +76 -0
- package/src/vterm.ts +1 -0
- package/THIRD-PARTY-NOTICES.txt +0 -22909
- package/dist/types/abortable.d.ts +0 -32
- package/dist/types/acp/connection.d.ts +0 -118
- package/dist/types/acp/protocol.d.ts +0 -526
- package/dist/types/acp/schema.d.ts +0 -41
- package/dist/types/acp/stream.d.ts +0 -8
- package/dist/types/acp/transport.d.ts +0 -88
- package/dist/types/acp.d.ts +0 -6
- package/dist/types/ar/arj.d.ts +0 -5
- package/dist/types/ar/asar.d.ts +0 -7
- package/dist/types/ar/bytes.d.ts +0 -20
- package/dist/types/ar/cab.d.ts +0 -5
- package/dist/types/ar/checksums.d.ts +0 -10
- package/dist/types/ar/codecs/bzip2.d.ts +0 -4
- package/dist/types/ar/codecs/gzip.d.ts +0 -6
- package/dist/types/ar/codecs/lzma.d.ts +0 -6
- package/dist/types/ar/codecs/lzw.d.ts +0 -4
- package/dist/types/ar/codecs/lzx.d.ts +0 -7
- package/dist/types/ar/codecs/xz.d.ts +0 -4
- package/dist/types/ar/codecs/zstd.d.ts +0 -6
- package/dist/types/ar/cpio.d.ts +0 -7
- package/dist/types/ar/deb.d.ts +0 -5
- package/dist/types/ar/entries.d.ts +0 -22
- package/dist/types/ar/error.d.ts +0 -8
- package/dist/types/ar/index.d.ts +0 -11
- package/dist/types/ar/iso.d.ts +0 -5
- package/dist/types/ar/limits.d.ts +0 -33
- package/dist/types/ar/lzh.d.ts +0 -7
- package/dist/types/ar/open.d.ts +0 -45
- package/dist/types/ar/paths.d.ts +0 -18
- package/dist/types/ar/rar/rar4-decoder.d.ts +0 -6
- package/dist/types/ar/rar/rar5-decoder.d.ts +0 -6
- package/dist/types/ar/rar.d.ts +0 -5
- package/dist/types/ar/reader.d.ts +0 -28
- package/dist/types/ar/registry.d.ts +0 -17
- package/dist/types/ar/rpm.d.ts +0 -5
- package/dist/types/ar/sevenzip/decode.d.ts +0 -33
- package/dist/types/ar/sevenzip.d.ts +0 -5
- package/dist/types/ar/source.d.ts +0 -54
- package/dist/types/ar/tar.d.ts +0 -9
- package/dist/types/ar/types.d.ts +0 -99
- package/dist/types/ar/unix-ar.d.ts +0 -7
- package/dist/types/ar/write.d.ts +0 -10
- package/dist/types/ar/zip.d.ts +0 -10
- package/dist/types/async.d.ts +0 -22
- package/dist/types/binary.d.ts +0 -23
- package/dist/types/browsers.d.ts +0 -68
- package/dist/types/chalk.d.ts +0 -125
- package/dist/types/cli.d.ts +0 -134
- package/dist/types/color.d.ts +0 -136
- package/dist/types/dates.d.ts +0 -7
- package/dist/types/dirs.d.ts +0 -325
- package/dist/types/docx/converter.d.ts +0 -46
- package/dist/types/docx/xml.d.ts +0 -26
- package/dist/types/docx.d.ts +0 -11
- package/dist/types/dom/core.d.ts +0 -431
- package/dist/types/dom/parser.d.ts +0 -7
- package/dist/types/dom/selector.d.ts +0 -5
- package/dist/types/dom.d.ts +0 -5
- package/dist/types/env.d.ts +0 -124
- package/dist/types/fetch-retry.d.ts +0 -96
- package/dist/types/file-lock.d.ts +0 -23
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -46
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/headers.d.ts +0 -34
- package/dist/types/index.d.ts +0 -41
- package/dist/types/json-parse.d.ts +0 -83
- package/dist/types/json.d.ts +0 -20
- package/dist/types/logger/rotating-file.d.ts +0 -18
- package/dist/types/logger.d.ts +0 -96
- package/dist/types/loop-phase.d.ts +0 -10
- package/dist/types/lru.d.ts +0 -46
- package/dist/types/marked/core.d.ts +0 -445
- package/dist/types/marked.d.ts +0 -2
- package/dist/types/math-delimiters.d.ts +0 -45
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/module-timer.d.ts +0 -1
- package/dist/types/path-tree.d.ts +0 -76
- package/dist/types/path.d.ts +0 -2
- package/dist/types/peek-file.d.ts +0 -29
- package/dist/types/postmortem.d.ts +0 -174
- package/dist/types/process-name.d.ts +0 -7
- package/dist/types/procmgr.d.ts +0 -74
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -124
- package/dist/types/readability/readability.d.ts +0 -9
- package/dist/types/readability/readerable.d.ts +0 -10
- package/dist/types/readability/types.d.ts +0 -70
- package/dist/types/readability.d.ts +0 -4
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/runtime-install.d.ts +0 -85
- package/dist/types/sanitize-text.d.ts +0 -29
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/sqlite.d.ts +0 -26
- package/dist/types/stderr-guard.d.ts +0 -22
- package/dist/types/stream.d.ts +0 -75
- package/dist/types/tab-spacing.d.ts +0 -24
- package/dist/types/temp.d.ts +0 -17
- package/dist/types/template.d.ts +0 -62
- package/dist/types/timing-buffer.d.ts +0 -22
- package/dist/types/tls-fetch.d.ts +0 -37
- package/dist/types/turndown/gfm.d.ts +0 -11
- package/dist/types/turndown/html.d.ts +0 -5
- package/dist/types/turndown/service.d.ts +0 -21
- package/dist/types/turndown/types.d.ts +0 -70
- package/dist/types/turndown.d.ts +0 -4
- package/dist/types/type-guards.d.ts +0 -5
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
- package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -23
- package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
- package/dist/types/version.d.ts +0 -18
- package/dist/types/vterm/buffer.d.ts +0 -99
- package/dist/types/vterm/terminal.d.ts +0 -44
- package/dist/types/vterm.d.ts +0 -8
- package/dist/types/which.d.ts +0 -37
- package/dist/types/worker-host.d.ts +0 -51
- 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
|
+
}
|