agentfootprint 9.114.0 → 9.114.2
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/CAPABILITIES.md +82 -0
- package/ai-instructions/setup.sh +4 -4
- package/dist/core/agent/findings/answerText.js +847 -0
- package/dist/core/agent/findings/answerText.js.map +1 -0
- package/dist/core/agent/findings/reserved.js +55 -16
- package/dist/core/agent/findings/reserved.js.map +1 -1
- package/dist/core/agent/stages/callLLM.js +33 -6
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/route.js +95 -55
- package/dist/core/agent/stages/route.js.map +1 -1
- package/dist/esm/core/agent/findings/answerText.d.ts +78 -0
- package/dist/esm/core/agent/findings/answerText.js +842 -0
- package/dist/esm/core/agent/findings/answerText.js.map +1 -0
- package/dist/esm/core/agent/findings/reserved.d.ts +18 -5
- package/dist/esm/core/agent/findings/reserved.js +55 -16
- package/dist/esm/core/agent/findings/reserved.js.map +1 -1
- package/dist/esm/core/agent/stages/callLLM.d.ts +7 -4
- package/dist/esm/core/agent/stages/callLLM.js +33 -6
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/route.d.ts +6 -4
- package/dist/esm/core/agent/stages/route.js +95 -55
- package/dist/esm/core/agent/stages/route.js.map +1 -1
- package/dist/types/core/agent/findings/answerText.d.ts +79 -0
- package/dist/types/core/agent/findings/answerText.d.ts.map +1 -0
- package/dist/types/core/agent/findings/reserved.d.ts +18 -5
- package/dist/types/core/agent/findings/reserved.d.ts.map +1 -1
- package/dist/types/core/agent/stages/callLLM.d.ts +7 -4
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/route.d.ts +6 -4
- package/dist/types/core/agent/stages/route.d.ts.map +1 -1
- package/package.json +10 -3
- package/CHANGELOG.md +0 -24738
- package/CLAUDE.md +0 -217
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* findings/answerText — the ONE reader of the reserved key in an answer's
|
|
4
|
+
* TEXT (9.114.2).
|
|
5
|
+
*
|
|
6
|
+
* Pattern: an incremental scanner — a character state machine with no scope
|
|
7
|
+
* and no I/O, fed chunk by chunk (`reservedMemberFilter`) or whole
|
|
8
|
+
* (`withoutReservedMembers`), with the same bytes out either way.
|
|
9
|
+
* Role: core/ layer leaf. `stages/callLLM.ts` feeds it the provider's
|
|
10
|
+
* stream under the arm, so `agentfootprint.stream.token` never
|
|
11
|
+
* carries the model's notes; `reserved.ts · peelAnswerFindings` feeds
|
|
12
|
+
* it the whole answer, so the answer the run returns is exactly the
|
|
13
|
+
* text the stream showed.
|
|
14
|
+
*
|
|
15
|
+
* THE RULE
|
|
16
|
+
* Every JSON object written in the text — the whole answer, one in a code
|
|
17
|
+
* block, one in the prose, one in a list — loses its own `_findings` member:
|
|
18
|
+
* the key, the value and the separator that joined it to its neighbours,
|
|
19
|
+
* and nothing else. A `_findings` inside another object's VALUE is data and
|
|
20
|
+
* stays. Each removed value is handed back (`removed`), as written, for the
|
|
21
|
+
* peel to read as the answer's declaration.
|
|
22
|
+
*
|
|
23
|
+
* - An object left with nothing in it goes whole — its line too — when it
|
|
24
|
+
* stood on a line of its own outside any list: the whole answer, a
|
|
25
|
+
* paragraph of its own, a code block of its own. A code block left empty
|
|
26
|
+
* goes with its fences. The removed line's indentation and the whole
|
|
27
|
+
* blank lines after it go; the next line keeps its own indentation. No
|
|
28
|
+
* blank lines are left at either end of the text. The whole answer, left with nothing, is `{}` (a JSON answer stays
|
|
29
|
+
* JSON). Anywhere else an emptied object stays as `{}`, so the JSON or
|
|
30
|
+
* code around it keeps its shape. A string inside a list is read as a
|
|
31
|
+
* string, so a `]` or `{` in it neither ends the list nor opens an object.
|
|
32
|
+
* - An answer that held nothing but notes — a code block of them, or
|
|
33
|
+
* several objects of them — is left empty; only an answer that was one
|
|
34
|
+
* bare JSON object becomes `{}`.
|
|
35
|
+
* - Text that stops being JSON part way (`{see below}`, a stray quote or a
|
|
36
|
+
* raw line break inside a `_findings` value) is given back exactly as it
|
|
37
|
+
* was written; nothing is removed from it except a `_findings` member
|
|
38
|
+
* that was already complete. An object that held nothing but such
|
|
39
|
+
* complete notes when the text stopped being JSON (a model one closing
|
|
40
|
+
* brace short) ends there, and goes as an emptied object does. Keys and
|
|
41
|
+
* values are read by JSON's own grammar (`JsonValue`), so the first
|
|
42
|
+
* character JSON cannot take decides it — not a count of brackets, which
|
|
43
|
+
* prose may never balance.
|
|
44
|
+
* - Text that ends inside a `_findings` value that is JSON so far (a
|
|
45
|
+
* cut-off stream) keeps it hidden and reports it `complete: false`.
|
|
46
|
+
* - Nothing removed ⇒ the text comes back byte for byte, whatever the
|
|
47
|
+
* chunking. What is held back while undecided is bounded by a line:
|
|
48
|
+
* whitespace, a key (a raw line break ends one — JSON allows none), `{`
|
|
49
|
+
* until its first key is read, a separator, a code fence line, the rest
|
|
50
|
+
* of an emptied object's line. The one exception is a `_findings` value:
|
|
51
|
+
* it is never shown while it is JSON so far, and one that stops being
|
|
52
|
+
* JSON is held until the character that breaks it and then given back as
|
|
53
|
+
* written — so that hold can span as many lines as the notes do.
|
|
54
|
+
*/
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.withoutReservedMembers = exports.reservedMemberFilter = void 0;
|
|
57
|
+
const types_js_1 = require("./types.js");
|
|
58
|
+
/** A fresh scanner for one text (one provider call's content). */
|
|
59
|
+
function reservedMemberFilter(key = types_js_1.RESERVED_ANSWER_KEY) {
|
|
60
|
+
return new Scanner(key);
|
|
61
|
+
}
|
|
62
|
+
exports.reservedMemberFilter = reservedMemberFilter;
|
|
63
|
+
/** The whole-text form: the text without its reserved members, and what was removed. */
|
|
64
|
+
function withoutReservedMembers(text, key = types_js_1.RESERVED_ANSWER_KEY) {
|
|
65
|
+
const scanner = new Scanner(key);
|
|
66
|
+
const out = scanner.push(text) + scanner.end();
|
|
67
|
+
return { text: out, removed: scanner.removed };
|
|
68
|
+
}
|
|
69
|
+
exports.withoutReservedMembers = withoutReservedMembers;
|
|
70
|
+
// ─── The machine ───────────────────────────────────────────────────────
|
|
71
|
+
const WS = new Set([' ', '\t', '\n', '\r']);
|
|
72
|
+
/** A JSON number or literal — what a value that is not a string, object or array must be. */
|
|
73
|
+
const SCALAR = /^(?:-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)$/;
|
|
74
|
+
/** The start of one: a scalar that stops matching this cannot become JSON. */
|
|
75
|
+
const SCALAR_PREFIX = /^(?:-|-?(?:0|[1-9]\d*)(?:\.(?:\d+(?:[eE][+-]?\d*)?)?|[eE][+-]?\d*)?|t(?:r(?:ue?)?)?|f(?:a(?:l(?:se?)?)?)?|n(?:u(?:ll?)?)?)$/;
|
|
76
|
+
/** Characters that can continue a scalar; anything else ends it. */
|
|
77
|
+
const SCALAR_CHAR = /[0-9a-zA-Z.+-]/;
|
|
78
|
+
/** What may follow `\` in a JSON string (`u` takes four hex digits). */
|
|
79
|
+
const ESCAPES = new Set(['"', '\\', '/', 'b', 'f', 'n', 'r', 't']);
|
|
80
|
+
const HEX = /^[0-9a-fA-F]$/;
|
|
81
|
+
/**
|
|
82
|
+
* One character of a JSON string after its opening quote. `end` at the
|
|
83
|
+
* closing quote; `bad` where JSON cannot continue — a raw newline or other
|
|
84
|
+
* control character, or an escape JSON does not have.
|
|
85
|
+
*/
|
|
86
|
+
function stringChar(s, ch) {
|
|
87
|
+
if (ch < ' ')
|
|
88
|
+
return 'bad';
|
|
89
|
+
if (s.esc === -1) {
|
|
90
|
+
if (ch === 'u')
|
|
91
|
+
s.esc = 4;
|
|
92
|
+
else if (ESCAPES.has(ch))
|
|
93
|
+
s.esc = 0;
|
|
94
|
+
else
|
|
95
|
+
return 'bad';
|
|
96
|
+
return 'more';
|
|
97
|
+
}
|
|
98
|
+
if (s.esc > 0) {
|
|
99
|
+
if (!HEX.test(ch))
|
|
100
|
+
return 'bad';
|
|
101
|
+
s.esc -= 1;
|
|
102
|
+
return 'more';
|
|
103
|
+
}
|
|
104
|
+
if (ch === '\\') {
|
|
105
|
+
s.esc = -1;
|
|
106
|
+
return 'more';
|
|
107
|
+
}
|
|
108
|
+
return ch === '"' ? 'end' : 'more';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One JSON value read character by character by JSON's own grammar, so a
|
|
112
|
+
* value that stops being JSON is known at its first wrong character — not
|
|
113
|
+
* only when its brackets fail to balance, which may never happen.
|
|
114
|
+
*/
|
|
115
|
+
class JsonValue {
|
|
116
|
+
frames = [];
|
|
117
|
+
str = { esc: 0 };
|
|
118
|
+
inString = false;
|
|
119
|
+
scalar = '';
|
|
120
|
+
step(ch) {
|
|
121
|
+
if (this.inString)
|
|
122
|
+
return this.stringStep(ch);
|
|
123
|
+
if (this.scalar !== '') {
|
|
124
|
+
if (SCALAR_CHAR.test(ch))
|
|
125
|
+
return this.scalarStep(ch);
|
|
126
|
+
if (!SCALAR.test(this.scalar))
|
|
127
|
+
return 'bad';
|
|
128
|
+
this.scalar = '';
|
|
129
|
+
if (this.frames.length === 0)
|
|
130
|
+
return 'after';
|
|
131
|
+
}
|
|
132
|
+
const top = this.frames[this.frames.length - 1];
|
|
133
|
+
// Nothing open: this is the value's first character.
|
|
134
|
+
if (top === undefined)
|
|
135
|
+
return this.startValue(ch);
|
|
136
|
+
if (WS.has(ch))
|
|
137
|
+
return 'take';
|
|
138
|
+
if (ch === ',')
|
|
139
|
+
return this.expecting(top, 'commaOrClose', top.close === '}' ? 'key' : 'value');
|
|
140
|
+
if (ch === ':')
|
|
141
|
+
return this.expecting(top, 'colon', 'value');
|
|
142
|
+
if (ch === '}' || ch === ']')
|
|
143
|
+
return this.close(top, ch);
|
|
144
|
+
if (ch === '"' && (top.expect === 'keyOrClose' || top.expect === 'key')) {
|
|
145
|
+
top.expect = 'colon';
|
|
146
|
+
return this.openString();
|
|
147
|
+
}
|
|
148
|
+
return this.startValue(ch);
|
|
149
|
+
}
|
|
150
|
+
stringStep(ch) {
|
|
151
|
+
const r = stringChar(this.str, ch);
|
|
152
|
+
if (r !== 'end')
|
|
153
|
+
return r === 'bad' ? 'bad' : 'take';
|
|
154
|
+
this.inString = false;
|
|
155
|
+
return this.frames.length === 0 ? 'end' : 'take';
|
|
156
|
+
}
|
|
157
|
+
scalarStep(ch) {
|
|
158
|
+
if (!SCALAR_PREFIX.test(this.scalar + ch))
|
|
159
|
+
return 'bad';
|
|
160
|
+
this.scalar += ch;
|
|
161
|
+
return 'take';
|
|
162
|
+
}
|
|
163
|
+
expecting(top, want, next) {
|
|
164
|
+
if (top.expect !== want)
|
|
165
|
+
return 'bad';
|
|
166
|
+
top.expect = next;
|
|
167
|
+
return 'take';
|
|
168
|
+
}
|
|
169
|
+
close(top, ch) {
|
|
170
|
+
const closable = top.expect === 'keyOrClose' || top.expect === 'valueOrClose' || top.expect === 'commaOrClose';
|
|
171
|
+
if (top.close !== ch || !closable)
|
|
172
|
+
return 'bad';
|
|
173
|
+
this.frames.pop();
|
|
174
|
+
return this.frames.length === 0 ? 'end' : 'take';
|
|
175
|
+
}
|
|
176
|
+
openString() {
|
|
177
|
+
this.inString = true;
|
|
178
|
+
this.str.esc = 0;
|
|
179
|
+
return 'take';
|
|
180
|
+
}
|
|
181
|
+
/** A value begins — only where one is expected. */
|
|
182
|
+
startValue(ch) {
|
|
183
|
+
const top = this.frames[this.frames.length - 1];
|
|
184
|
+
if (top !== undefined) {
|
|
185
|
+
if (top.expect !== 'value' && top.expect !== 'valueOrClose')
|
|
186
|
+
return 'bad';
|
|
187
|
+
top.expect = 'commaOrClose';
|
|
188
|
+
}
|
|
189
|
+
if (ch === '"')
|
|
190
|
+
return this.openString();
|
|
191
|
+
if (ch === '{' || ch === '[') {
|
|
192
|
+
const obj = ch === '{';
|
|
193
|
+
this.frames.push({ close: obj ? '}' : ']', expect: obj ? 'keyOrClose' : 'valueOrClose' });
|
|
194
|
+
return 'take';
|
|
195
|
+
}
|
|
196
|
+
if (!SCALAR_PREFIX.test(ch))
|
|
197
|
+
return 'bad';
|
|
198
|
+
this.scalar = ch;
|
|
199
|
+
return 'take';
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
class Scanner {
|
|
203
|
+
key;
|
|
204
|
+
removed = [];
|
|
205
|
+
out = '';
|
|
206
|
+
ended = false;
|
|
207
|
+
mode = 'prose';
|
|
208
|
+
// Prose.
|
|
209
|
+
pendingWs = '';
|
|
210
|
+
/** A block was removed after `pendingWs` began: that whitespace is kept only if more text follows. */
|
|
211
|
+
pendingBeforeRemoved = false;
|
|
212
|
+
/** Line breaks dropped after the last removed block (`dropWs`). */
|
|
213
|
+
droppedNewlines = 0;
|
|
214
|
+
/** The whitespace right after a removed block is dropped — whole blank lines only. */
|
|
215
|
+
dropWs = false;
|
|
216
|
+
/**
|
|
217
|
+
* While `dropWs` is on: the spaces and tabs since the last newline — the
|
|
218
|
+
* next line's own indentation, which is not the removed block's to take.
|
|
219
|
+
*/
|
|
220
|
+
indentWs = '';
|
|
221
|
+
lineStart = true;
|
|
222
|
+
textStart = true;
|
|
223
|
+
/** `[` written in the text and not yet closed — an object inside a list keeps its place. */
|
|
224
|
+
listDepth = 0;
|
|
225
|
+
/** Inside a string in a list (`listDepth > 0`). */
|
|
226
|
+
listString = false;
|
|
227
|
+
listStr = { esc: 0 };
|
|
228
|
+
/** The text began with an object that went whole — `{}` if nothing else follows. */
|
|
229
|
+
emptiedInitial = false;
|
|
230
|
+
// Code blocks.
|
|
231
|
+
inFence = false;
|
|
232
|
+
fenceTicks = 0;
|
|
233
|
+
ticks = '';
|
|
234
|
+
opener = '';
|
|
235
|
+
tail = '';
|
|
236
|
+
// Objects.
|
|
237
|
+
obj;
|
|
238
|
+
/** The emptied object whose line is being read (`lineRest`). */
|
|
239
|
+
emptied;
|
|
240
|
+
objects = 0;
|
|
241
|
+
constructor(key) {
|
|
242
|
+
this.key = key;
|
|
243
|
+
}
|
|
244
|
+
push(chunk) {
|
|
245
|
+
if (this.ended)
|
|
246
|
+
return '';
|
|
247
|
+
for (const ch of chunk)
|
|
248
|
+
this.step(ch);
|
|
249
|
+
return this.flush();
|
|
250
|
+
}
|
|
251
|
+
end() {
|
|
252
|
+
if (this.ended)
|
|
253
|
+
return '';
|
|
254
|
+
this.ended = true;
|
|
255
|
+
switch (this.mode) {
|
|
256
|
+
case 'ticks':
|
|
257
|
+
this.mode = 'prose';
|
|
258
|
+
this.text(this.takeTicks());
|
|
259
|
+
break;
|
|
260
|
+
case 'fenceInfo':
|
|
261
|
+
case 'fenceStart':
|
|
262
|
+
this.releaseWs();
|
|
263
|
+
this.emit(this.opener);
|
|
264
|
+
this.opener = '';
|
|
265
|
+
break;
|
|
266
|
+
case 'object':
|
|
267
|
+
this.endInObject();
|
|
268
|
+
break;
|
|
269
|
+
case 'lineRest': {
|
|
270
|
+
// The emptied object's line ended with the text: it stood alone — and
|
|
271
|
+
// a code block it began never closed, so the block goes too.
|
|
272
|
+
const o = this.emptied;
|
|
273
|
+
this.goesWhole(o);
|
|
274
|
+
if (o.fence !== undefined)
|
|
275
|
+
this.removeFencedBlock();
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case 'fenceAfterTicks':
|
|
279
|
+
if (this.ticks.length >= this.fenceTicks)
|
|
280
|
+
this.removeFencedBlock();
|
|
281
|
+
else
|
|
282
|
+
this.keepFencedBlock(this.takeTicks());
|
|
283
|
+
break;
|
|
284
|
+
case 'fenceAfter':
|
|
285
|
+
// The block never closed and held only the removed object.
|
|
286
|
+
this.removeFencedBlock();
|
|
287
|
+
break;
|
|
288
|
+
default:
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
if (this.emptiedInitial) {
|
|
292
|
+
this.dropHeldWs();
|
|
293
|
+
this.emit('{}');
|
|
294
|
+
}
|
|
295
|
+
else if (this.pendingBeforeRemoved) {
|
|
296
|
+
this.dropHeldWs();
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
this.releaseWs();
|
|
300
|
+
}
|
|
301
|
+
return this.flush();
|
|
302
|
+
}
|
|
303
|
+
// ─── Output ────────────────────────────────────────────────────────
|
|
304
|
+
flush() {
|
|
305
|
+
const out = this.out;
|
|
306
|
+
this.out = '';
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
emit(s) {
|
|
310
|
+
this.out += s;
|
|
311
|
+
}
|
|
312
|
+
/** Whitespace held before now goes out. */
|
|
313
|
+
releaseWs() {
|
|
314
|
+
// After a removed line, the blank line that followed it still keeps the
|
|
315
|
+
// text on either side apart — at most one blank line, and only where text
|
|
316
|
+
// came before the removed line.
|
|
317
|
+
let breaks = '';
|
|
318
|
+
if (this.pendingBeforeRemoved && this.pendingWs.includes('\n')) {
|
|
319
|
+
const have = this.pendingWs.split('\n').length - 1;
|
|
320
|
+
breaks = '\n'.repeat(Math.min(this.droppedNewlines, Math.max(0, 2 - have)));
|
|
321
|
+
}
|
|
322
|
+
this.emit(this.pendingWs + breaks + this.indentWs);
|
|
323
|
+
this.pendingWs = '';
|
|
324
|
+
this.indentWs = '';
|
|
325
|
+
this.droppedNewlines = 0;
|
|
326
|
+
this.pendingBeforeRemoved = false;
|
|
327
|
+
this.dropWs = false;
|
|
328
|
+
}
|
|
329
|
+
/** The text ended after a removed block: the whitespace held goes with it. */
|
|
330
|
+
dropHeldWs() {
|
|
331
|
+
this.pendingWs = '';
|
|
332
|
+
this.indentWs = '';
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* A removed line's indentation goes with it, so it is not carried onto the
|
|
336
|
+
* next line (four spaces there would make it a code block). The held
|
|
337
|
+
* whitespace is cut back to its last newline.
|
|
338
|
+
*/
|
|
339
|
+
dropRemovedIndent() {
|
|
340
|
+
this.pendingWs = this.pendingWs.slice(0, this.pendingWs.lastIndexOf('\n') + 1);
|
|
341
|
+
this.indentWs = '';
|
|
342
|
+
}
|
|
343
|
+
/** Visible text: everything held before it goes out first. */
|
|
344
|
+
text(s) {
|
|
345
|
+
this.releaseWs();
|
|
346
|
+
this.emit(s);
|
|
347
|
+
this.lineStart = false;
|
|
348
|
+
this.textStart = false;
|
|
349
|
+
this.emptiedInitial = false;
|
|
350
|
+
}
|
|
351
|
+
takeTicks() {
|
|
352
|
+
const t = this.ticks;
|
|
353
|
+
this.ticks = '';
|
|
354
|
+
return t;
|
|
355
|
+
}
|
|
356
|
+
blockRemoved() {
|
|
357
|
+
this.pendingBeforeRemoved = true;
|
|
358
|
+
this.dropWs = true;
|
|
359
|
+
this.droppedNewlines = 0;
|
|
360
|
+
}
|
|
361
|
+
// ─── Dispatch ──────────────────────────────────────────────────────
|
|
362
|
+
step(ch) {
|
|
363
|
+
switch (this.mode) {
|
|
364
|
+
case 'prose':
|
|
365
|
+
return this.prose(ch);
|
|
366
|
+
case 'ticks':
|
|
367
|
+
return this.ticksStep(ch);
|
|
368
|
+
case 'fenceInfo':
|
|
369
|
+
return this.fenceInfo(ch);
|
|
370
|
+
case 'fenceStart':
|
|
371
|
+
return this.fenceStart(ch);
|
|
372
|
+
case 'object':
|
|
373
|
+
return this.objectStep(ch);
|
|
374
|
+
case 'lineRest':
|
|
375
|
+
return this.lineRest(ch);
|
|
376
|
+
case 'fenceAfter':
|
|
377
|
+
return this.fenceAfter(ch);
|
|
378
|
+
case 'fenceAfterTicks':
|
|
379
|
+
return this.fenceAfterTicks(ch);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ─── Prose (a code block's body included) ──────────────────────────
|
|
383
|
+
prose(ch) {
|
|
384
|
+
if (this.listString)
|
|
385
|
+
return this.listStringStep(ch);
|
|
386
|
+
if (WS.has(ch)) {
|
|
387
|
+
if (ch === '\n')
|
|
388
|
+
this.lineStart = true;
|
|
389
|
+
if (!this.dropWs)
|
|
390
|
+
this.pendingWs += ch;
|
|
391
|
+
else if (ch === '\n' || ch === '\r') {
|
|
392
|
+
this.indentWs = '';
|
|
393
|
+
if (ch === '\n')
|
|
394
|
+
this.droppedNewlines += 1;
|
|
395
|
+
}
|
|
396
|
+
else if (this.lineStart)
|
|
397
|
+
this.indentWs += ch;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (ch === '`' && this.lineStart) {
|
|
401
|
+
this.mode = 'ticks';
|
|
402
|
+
this.ticks = ch;
|
|
403
|
+
this.textStart = false;
|
|
404
|
+
this.emptiedInitial = false;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (ch === '{')
|
|
408
|
+
return this.openObject(undefined);
|
|
409
|
+
if (ch === '[')
|
|
410
|
+
this.listDepth += 1;
|
|
411
|
+
else if (ch === ']' && this.listDepth > 0)
|
|
412
|
+
this.listDepth -= 1;
|
|
413
|
+
else if (ch === '"' && this.listDepth > 0) {
|
|
414
|
+
this.listString = true;
|
|
415
|
+
this.listStr.esc = 0;
|
|
416
|
+
}
|
|
417
|
+
this.text(ch);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* A string inside a list: its `[`, `]` and `{` are words, not structure, so
|
|
421
|
+
* the list keeps its depth. A character no JSON string can hold (a raw
|
|
422
|
+
* newline) means this was never a JSON list: it is read as prose from here.
|
|
423
|
+
*/
|
|
424
|
+
listStringStep(ch) {
|
|
425
|
+
const r = stringChar(this.listStr, ch);
|
|
426
|
+
if (r === 'bad') {
|
|
427
|
+
this.listString = false;
|
|
428
|
+
this.listDepth = 0;
|
|
429
|
+
return this.prose(ch);
|
|
430
|
+
}
|
|
431
|
+
if (r === 'end')
|
|
432
|
+
this.listString = false;
|
|
433
|
+
this.text(ch);
|
|
434
|
+
}
|
|
435
|
+
/** A run of backticks at the start of a line: a fence opens (or closes, inside one). */
|
|
436
|
+
ticksStep(ch) {
|
|
437
|
+
if (ch === '`') {
|
|
438
|
+
this.ticks += ch;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const run = this.takeTicks();
|
|
442
|
+
this.mode = 'prose';
|
|
443
|
+
if (this.inFence) {
|
|
444
|
+
this.text(run);
|
|
445
|
+
if (run.length >= this.fenceTicks && WS.has(ch)) {
|
|
446
|
+
this.inFence = false;
|
|
447
|
+
this.fenceTicks = 0;
|
|
448
|
+
}
|
|
449
|
+
return this.prose(ch);
|
|
450
|
+
}
|
|
451
|
+
if (run.length >= 3) {
|
|
452
|
+
// A code block opens; its opening line is held until its first character.
|
|
453
|
+
this.fenceTicks = run.length;
|
|
454
|
+
this.opener = run;
|
|
455
|
+
this.mode = 'fenceInfo';
|
|
456
|
+
return this.fenceInfo(ch);
|
|
457
|
+
}
|
|
458
|
+
this.text(run);
|
|
459
|
+
this.prose(ch);
|
|
460
|
+
}
|
|
461
|
+
fenceInfo(ch) {
|
|
462
|
+
this.opener += ch;
|
|
463
|
+
if (ch === '\n')
|
|
464
|
+
this.mode = 'fenceStart';
|
|
465
|
+
}
|
|
466
|
+
fenceStart(ch) {
|
|
467
|
+
if (WS.has(ch)) {
|
|
468
|
+
this.opener += ch;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (ch === '{') {
|
|
472
|
+
const opener = this.opener;
|
|
473
|
+
this.opener = '';
|
|
474
|
+
this.lineStart = true;
|
|
475
|
+
return this.openObject(opener);
|
|
476
|
+
}
|
|
477
|
+
// The block does not open with an object: its opening line goes out as
|
|
478
|
+
// written, and its body is read as text.
|
|
479
|
+
this.releaseWs();
|
|
480
|
+
this.emit(this.opener);
|
|
481
|
+
this.opener = '';
|
|
482
|
+
this.inFence = true;
|
|
483
|
+
this.mode = 'prose';
|
|
484
|
+
this.lineStart = true;
|
|
485
|
+
this.prose(ch);
|
|
486
|
+
}
|
|
487
|
+
// ─── After an emptied object ───────────────────────────────────────
|
|
488
|
+
/** The rest of an emptied object's line: only whitespace up to the newline ⇒ it goes whole. */
|
|
489
|
+
lineRest(ch) {
|
|
490
|
+
const o = this.emptied;
|
|
491
|
+
if (ch === '\n') {
|
|
492
|
+
// Its line goes with it, newline and all.
|
|
493
|
+
this.goesWhole(o);
|
|
494
|
+
this.lineStart = true;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (WS.has(ch)) {
|
|
498
|
+
this.tail += ch;
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
// Something else shares its line: it stays, as `{}`.
|
|
502
|
+
this.staysEmpty(o);
|
|
503
|
+
this.step(ch);
|
|
504
|
+
}
|
|
505
|
+
/** The emptied object stood alone on its line: it goes, and its line with it. */
|
|
506
|
+
goesWhole(o) {
|
|
507
|
+
this.emptied = undefined;
|
|
508
|
+
this.tail = '';
|
|
509
|
+
if (o.fence !== undefined) {
|
|
510
|
+
// The block's opening line stays held — without the removed line's indentation.
|
|
511
|
+
this.opener = o.fence.slice(0, o.fence.lastIndexOf('\n') + 1);
|
|
512
|
+
this.mode = 'fenceAfter';
|
|
513
|
+
this.lineStart = false;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.dropRemovedIndent();
|
|
517
|
+
if (o.initial)
|
|
518
|
+
this.emptiedInitial = true;
|
|
519
|
+
this.mode = 'prose';
|
|
520
|
+
this.lineStart = false;
|
|
521
|
+
this.blockRemoved();
|
|
522
|
+
}
|
|
523
|
+
/** The emptied object shares its line or sits in a list: it stays as `{}`. */
|
|
524
|
+
staysEmpty(o) {
|
|
525
|
+
this.emptied = undefined;
|
|
526
|
+
this.releaseWs();
|
|
527
|
+
if (o.fence !== undefined) {
|
|
528
|
+
this.emit(o.fence);
|
|
529
|
+
this.inFence = true;
|
|
530
|
+
}
|
|
531
|
+
this.emit('{}' + this.tail);
|
|
532
|
+
this.tail = '';
|
|
533
|
+
this.mode = 'prose';
|
|
534
|
+
this.lineStart = false;
|
|
535
|
+
this.textStart = false;
|
|
536
|
+
this.emptiedInitial = false;
|
|
537
|
+
}
|
|
538
|
+
fenceAfter(ch) {
|
|
539
|
+
if (WS.has(ch)) {
|
|
540
|
+
this.tail += ch;
|
|
541
|
+
if (ch === '\n')
|
|
542
|
+
this.lineStart = true;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (ch === '`' && this.lineStart) {
|
|
546
|
+
this.mode = 'fenceAfterTicks';
|
|
547
|
+
this.ticks = ch;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (ch === '{') {
|
|
551
|
+
// Another object in the same block: the block's opening line stays held
|
|
552
|
+
// with it, so a block of nothing but notes goes whole, fences and all.
|
|
553
|
+
const fence = this.opener + this.tail;
|
|
554
|
+
this.opener = '';
|
|
555
|
+
this.tail = '';
|
|
556
|
+
return this.openObject(fence);
|
|
557
|
+
}
|
|
558
|
+
this.keepFencedBlock('');
|
|
559
|
+
this.prose(ch);
|
|
560
|
+
}
|
|
561
|
+
fenceAfterTicks(ch) {
|
|
562
|
+
if (ch === '`') {
|
|
563
|
+
this.ticks += ch;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (this.ticks.length >= this.fenceTicks && WS.has(ch)) {
|
|
567
|
+
this.ticks = '';
|
|
568
|
+
this.removeFencedBlock();
|
|
569
|
+
return this.prose(ch);
|
|
570
|
+
}
|
|
571
|
+
this.keepFencedBlock(this.takeTicks());
|
|
572
|
+
this.prose(ch);
|
|
573
|
+
}
|
|
574
|
+
/** The code block held only the removed object: it goes, fences and all. */
|
|
575
|
+
removeFencedBlock() {
|
|
576
|
+
this.dropRemovedIndent();
|
|
577
|
+
this.opener = '';
|
|
578
|
+
this.tail = '';
|
|
579
|
+
this.inFence = false;
|
|
580
|
+
this.fenceTicks = 0;
|
|
581
|
+
this.mode = 'prose';
|
|
582
|
+
this.lineStart = false;
|
|
583
|
+
this.blockRemoved();
|
|
584
|
+
}
|
|
585
|
+
/** The code block holds more than the removed object's line: it stays, and is read on. */
|
|
586
|
+
keepFencedBlock(ticks) {
|
|
587
|
+
this.releaseWs();
|
|
588
|
+
this.emit(this.opener + this.tail + ticks);
|
|
589
|
+
this.opener = '';
|
|
590
|
+
this.tail = '';
|
|
591
|
+
this.inFence = true;
|
|
592
|
+
this.mode = 'prose';
|
|
593
|
+
this.lineStart = ticks === '' && this.lineStart;
|
|
594
|
+
this.textStart = false;
|
|
595
|
+
}
|
|
596
|
+
// ─── Objects ───────────────────────────────────────────────────────
|
|
597
|
+
openObject(fence) {
|
|
598
|
+
this.obj = {
|
|
599
|
+
index: this.objects++,
|
|
600
|
+
initial: this.textStart && fence === undefined,
|
|
601
|
+
ownLine: this.lineStart && this.listDepth === 0,
|
|
602
|
+
fence,
|
|
603
|
+
open: '{',
|
|
604
|
+
openHeld: true,
|
|
605
|
+
kept: 0,
|
|
606
|
+
removed: 0,
|
|
607
|
+
state: 'key?',
|
|
608
|
+
sep: '',
|
|
609
|
+
key: '',
|
|
610
|
+
keyStr: { esc: 0 },
|
|
611
|
+
reserved: false,
|
|
612
|
+
raw: '',
|
|
613
|
+
rawSep: 0,
|
|
614
|
+
value: '',
|
|
615
|
+
reader: undefined,
|
|
616
|
+
};
|
|
617
|
+
this.mode = 'object';
|
|
618
|
+
this.textStart = false;
|
|
619
|
+
this.lineStart = false;
|
|
620
|
+
this.emptiedInitial = false;
|
|
621
|
+
}
|
|
622
|
+
/** `{`, and the code block's opening line before it, go out. */
|
|
623
|
+
releaseOpen(o) {
|
|
624
|
+
if (!o.openHeld)
|
|
625
|
+
return;
|
|
626
|
+
this.releaseWs();
|
|
627
|
+
if (o.fence !== undefined) {
|
|
628
|
+
this.emit(o.fence);
|
|
629
|
+
this.inFence = true;
|
|
630
|
+
}
|
|
631
|
+
this.emit(o.open);
|
|
632
|
+
o.openHeld = false;
|
|
633
|
+
}
|
|
634
|
+
/** A character of the current member: shown when it is kept, held when it is reserved. */
|
|
635
|
+
member(o, ch, value) {
|
|
636
|
+
if (!o.reserved)
|
|
637
|
+
return this.emit(ch);
|
|
638
|
+
o.raw += ch;
|
|
639
|
+
if (value)
|
|
640
|
+
o.value += ch;
|
|
641
|
+
}
|
|
642
|
+
objectStep(ch) {
|
|
643
|
+
const o = this.obj;
|
|
644
|
+
switch (o.state) {
|
|
645
|
+
case 'key?':
|
|
646
|
+
if (WS.has(ch)) {
|
|
647
|
+
if (o.kept + o.removed === 0)
|
|
648
|
+
o.open += ch;
|
|
649
|
+
else
|
|
650
|
+
o.sep += ch;
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (ch === '"') {
|
|
654
|
+
o.key = ch;
|
|
655
|
+
o.keyStr.esc = 0;
|
|
656
|
+
o.state = 'key';
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (ch === '}' && o.kept + o.removed === 0) {
|
|
660
|
+
// `{}` — nothing to read, and nothing to remove.
|
|
661
|
+
this.releaseOpen(o);
|
|
662
|
+
this.emit(ch);
|
|
663
|
+
return this.finishObject();
|
|
664
|
+
}
|
|
665
|
+
return this.abort(o, ch);
|
|
666
|
+
case 'key': {
|
|
667
|
+
const r = stringChar(o.keyStr, ch);
|
|
668
|
+
if (r === 'bad')
|
|
669
|
+
return this.abort(o, ch);
|
|
670
|
+
o.key += ch;
|
|
671
|
+
if (r === 'end')
|
|
672
|
+
this.resolveKey(o);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
case 'colon':
|
|
676
|
+
if (WS.has(ch))
|
|
677
|
+
return this.member(o, ch, false);
|
|
678
|
+
if (ch === ':') {
|
|
679
|
+
this.member(o, ch, false);
|
|
680
|
+
o.state = 'value?';
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
return this.abort(o, ch);
|
|
684
|
+
case 'value?':
|
|
685
|
+
if (WS.has(ch))
|
|
686
|
+
return this.member(o, ch, false);
|
|
687
|
+
return this.startValue(o, ch);
|
|
688
|
+
case 'value':
|
|
689
|
+
return this.valueStep(o, ch);
|
|
690
|
+
case 'after':
|
|
691
|
+
if (WS.has(ch)) {
|
|
692
|
+
o.sep += ch;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (ch === ',') {
|
|
696
|
+
o.sep += ch;
|
|
697
|
+
o.state = 'key?';
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (ch === '}')
|
|
701
|
+
return this.closeObject(o);
|
|
702
|
+
return this.abort(o, ch);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
resolveKey(o) {
|
|
706
|
+
let name;
|
|
707
|
+
try {
|
|
708
|
+
name = JSON.parse(o.key);
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
name = undefined;
|
|
712
|
+
}
|
|
713
|
+
if (name === this.key) {
|
|
714
|
+
// The separator that joined it to the member before goes with it.
|
|
715
|
+
o.reserved = true;
|
|
716
|
+
o.removed += 1;
|
|
717
|
+
o.raw = o.sep + o.key;
|
|
718
|
+
o.rawSep = o.sep.length;
|
|
719
|
+
o.value = '';
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
o.reserved = false;
|
|
723
|
+
// The first member kept takes `{` and the whitespace after it; a
|
|
724
|
+
// separator left behind by removed members before it goes.
|
|
725
|
+
if (o.kept === 0)
|
|
726
|
+
this.releaseOpen(o);
|
|
727
|
+
else
|
|
728
|
+
this.emit(o.sep);
|
|
729
|
+
o.kept += 1;
|
|
730
|
+
this.emit(o.key);
|
|
731
|
+
}
|
|
732
|
+
o.sep = '';
|
|
733
|
+
o.key = '';
|
|
734
|
+
o.state = 'colon';
|
|
735
|
+
}
|
|
736
|
+
startValue(o, ch) {
|
|
737
|
+
o.reader = new JsonValue();
|
|
738
|
+
o.state = 'value';
|
|
739
|
+
this.valueStep(o, ch);
|
|
740
|
+
}
|
|
741
|
+
/** A character of a member's value, read by JSON's grammar: the first one JSON cannot take gives the text back. */
|
|
742
|
+
valueStep(o, ch) {
|
|
743
|
+
const r = o.reader.step(ch);
|
|
744
|
+
if (r === 'bad')
|
|
745
|
+
return this.abort(o, ch);
|
|
746
|
+
if (r === 'after') {
|
|
747
|
+
this.endValue(o);
|
|
748
|
+
return this.objectStep(ch);
|
|
749
|
+
}
|
|
750
|
+
this.member(o, ch, true);
|
|
751
|
+
if (r === 'end')
|
|
752
|
+
this.endValue(o);
|
|
753
|
+
}
|
|
754
|
+
endValue(o) {
|
|
755
|
+
if (o.reserved) {
|
|
756
|
+
this.removed.push({ object: o.index, valueText: o.value, complete: true });
|
|
757
|
+
o.reserved = false;
|
|
758
|
+
o.raw = '';
|
|
759
|
+
o.value = '';
|
|
760
|
+
}
|
|
761
|
+
o.reader = undefined;
|
|
762
|
+
o.state = 'after';
|
|
763
|
+
}
|
|
764
|
+
closeObject(o) {
|
|
765
|
+
if (o.kept > 0) {
|
|
766
|
+
this.emit(o.sep);
|
|
767
|
+
this.emit('}');
|
|
768
|
+
return this.finishObject();
|
|
769
|
+
}
|
|
770
|
+
// Every member was `_findings`: the object is empty now.
|
|
771
|
+
this.emptyObjectEnded(o);
|
|
772
|
+
}
|
|
773
|
+
/** An object that held nothing but notes is over: whole if it stood on its own line, else `{}`. */
|
|
774
|
+
emptyObjectEnded(o) {
|
|
775
|
+
this.obj = undefined;
|
|
776
|
+
this.lineStart = false;
|
|
777
|
+
this.tail = '';
|
|
778
|
+
if (o.ownLine) {
|
|
779
|
+
// Whether it goes whole depends on the rest of its line.
|
|
780
|
+
this.emptied = o;
|
|
781
|
+
this.mode = 'lineRest';
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
this.staysEmpty(o);
|
|
785
|
+
}
|
|
786
|
+
finishObject() {
|
|
787
|
+
this.obj = undefined;
|
|
788
|
+
this.mode = 'prose';
|
|
789
|
+
this.lineStart = false;
|
|
790
|
+
}
|
|
791
|
+
/** Not JSON after all: everything held goes out as written, and the character is read again. */
|
|
792
|
+
abort(o, ch) {
|
|
793
|
+
// A `"` straight after a member is a missing comma before the next key,
|
|
794
|
+
// not a missing closing brace: that object goes on, so it is given back.
|
|
795
|
+
const missingComma = o.state === 'after' && ch === '"';
|
|
796
|
+
if (o.kept === 0 && o.removed > 0 && !o.reserved && !missingComma) {
|
|
797
|
+
// It held nothing but complete notes when the text stopped being JSON —
|
|
798
|
+
// a model a closing brace short. The notes object ends here and goes as
|
|
799
|
+
// an emptied one does; a trailing comma was its own, and the whitespace
|
|
800
|
+
// after it (and a key it had begun) is read as the text that follows.
|
|
801
|
+
const after = o.sep.slice(o.sep.lastIndexOf(',') + 1) + (o.state === 'key' ? o.key : '');
|
|
802
|
+
this.emptyObjectEnded(o);
|
|
803
|
+
for (const c of after)
|
|
804
|
+
this.step(c);
|
|
805
|
+
return this.step(ch);
|
|
806
|
+
}
|
|
807
|
+
this.releaseOpen(o);
|
|
808
|
+
// A separator after removed members with nothing kept before it is theirs
|
|
809
|
+
// (the rule `resolveKey` applies to the first kept member).
|
|
810
|
+
if (o.kept > 0)
|
|
811
|
+
this.emit(o.sep);
|
|
812
|
+
if (o.state === 'key')
|
|
813
|
+
this.emit(o.key);
|
|
814
|
+
// A reserved member given back: with nothing kept before it, the
|
|
815
|
+
// separator before it was a removed member's (the `resolveKey` rule).
|
|
816
|
+
if (o.reserved)
|
|
817
|
+
this.emit(o.kept > 0 ? o.raw : o.raw.slice(o.rawSep));
|
|
818
|
+
this.finishObject();
|
|
819
|
+
this.step(ch);
|
|
820
|
+
}
|
|
821
|
+
/** The text ended inside an object. */
|
|
822
|
+
endInObject() {
|
|
823
|
+
const o = this.obj;
|
|
824
|
+
this.obj = undefined;
|
|
825
|
+
this.mode = 'prose';
|
|
826
|
+
if (o.reserved) {
|
|
827
|
+
// Cut off inside `_findings`: its words stay hidden.
|
|
828
|
+
this.removed.push({ object: o.index, valueText: o.value, complete: false });
|
|
829
|
+
}
|
|
830
|
+
if (o.kept === 0 && o.removed > 0 && o.state !== 'key') {
|
|
831
|
+
// Nothing kept, something removed: as if it had closed there.
|
|
832
|
+
if (!o.ownLine)
|
|
833
|
+
return this.staysEmpty(o);
|
|
834
|
+
this.goesWhole(o);
|
|
835
|
+
if (o.fence !== undefined)
|
|
836
|
+
this.removeFencedBlock();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
// Everything else held goes out as written.
|
|
840
|
+
this.releaseOpen(o);
|
|
841
|
+
if (o.kept > 0)
|
|
842
|
+
this.emit(o.sep);
|
|
843
|
+
if (o.state === 'key')
|
|
844
|
+
this.emit(o.key);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
//# sourceMappingURL=answerText.js.map
|