@blackwell-systems/gcf 2.1.0 → 2.1.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/README.md +20 -20
- package/dist/cjs/browser.d.ts +9 -0
- package/dist/cjs/browser.js +25 -0
- package/dist/cjs/browser.js.map +1 -0
- package/dist/cjs/cli.d.ts +5 -0
- package/dist/cjs/cli.js +136 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/constants.d.ts +8 -0
- package/dist/cjs/constants.js +46 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/decode.d.ts +5 -0
- package/dist/cjs/decode.js +197 -0
- package/dist/cjs/decode.js.map +1 -0
- package/dist/cjs/decode_generic.d.ts +4 -0
- package/dist/cjs/decode_generic.js +678 -0
- package/dist/cjs/decode_generic.js.map +1 -0
- package/dist/cjs/delta.d.ts +15 -0
- package/dist/cjs/delta.js +73 -0
- package/dist/cjs/delta.js.map +1 -0
- package/dist/cjs/encode.d.ts +5 -0
- package/dist/cjs/encode.js +89 -0
- package/dist/cjs/encode.js.map +1 -0
- package/dist/cjs/generic.d.ts +1 -0
- package/dist/cjs/generic.js +332 -0
- package/dist/cjs/generic.js.map +1 -0
- package/dist/cjs/index.cjs +1847 -0
- package/dist/cjs/index.d.ts +11 -0
- package/dist/cjs/index.js +32 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/packroot.d.ts +13 -0
- package/dist/cjs/packroot.js +50 -0
- package/dist/cjs/packroot.js.map +1 -0
- package/dist/cjs/scalar.d.ts +26 -0
- package/dist/cjs/scalar.js +339 -0
- package/dist/cjs/scalar.js.map +1 -0
- package/dist/cjs/session.d.ts +30 -0
- package/dist/cjs/session.js +140 -0
- package/dist/cjs/session.js.map +1 -0
- package/dist/cjs/stream.d.ts +66 -0
- package/dist/cjs/stream.js +127 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/cjs/stream_generic.d.ts +37 -0
- package/dist/cjs/stream_generic.js +87 -0
- package/dist/cjs/stream_generic.js.map +1 -0
- package/dist/cjs/types.d.ts +75 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cli.js +0 -0
- package/dist/decode_generic.js +21 -11
- package/dist/decode_generic.js.map +1 -1
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +3 -0
- package/dist/scalar.js.map +1 -1
- package/package.json +5 -4
- package/src/decode_generic.ts +20 -12
- package/src/scalar.ts +2 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeGeneric = decodeGeneric;
|
|
4
|
+
const decode_js_1 = require("./decode.js");
|
|
5
|
+
const scalar_js_1 = require("./scalar.js");
|
|
6
|
+
/**
|
|
7
|
+
* Decode GCF generic or graph profile text into a JS value.
|
|
8
|
+
*/
|
|
9
|
+
function decodeGeneric(input) {
|
|
10
|
+
input = input.trimEnd();
|
|
11
|
+
if (!input)
|
|
12
|
+
throw new Error('missing_header: empty input');
|
|
13
|
+
const lines = input.split('\n');
|
|
14
|
+
const header = lines[0].replace(/\r$/, '');
|
|
15
|
+
if (!header.startsWith('GCF '))
|
|
16
|
+
throw new Error('missing_header: first line does not begin with GCF');
|
|
17
|
+
const profile = parseHeaderProfile(header);
|
|
18
|
+
if (profile === 'graph') {
|
|
19
|
+
const p = (0, decode_js_1.decode)(input);
|
|
20
|
+
return {
|
|
21
|
+
tool: p.tool,
|
|
22
|
+
tokenBudget: p.tokenBudget,
|
|
23
|
+
tokensUsed: p.tokensUsed,
|
|
24
|
+
packRoot: p.packRoot ?? '',
|
|
25
|
+
symbols: p.symbols.map(s => ({
|
|
26
|
+
qualifiedName: s.qualifiedName,
|
|
27
|
+
kind: s.kind,
|
|
28
|
+
score: s.score,
|
|
29
|
+
provenance: s.provenance,
|
|
30
|
+
distance: s.distance,
|
|
31
|
+
})),
|
|
32
|
+
edges: p.edges.map(e => ({
|
|
33
|
+
source: e.source,
|
|
34
|
+
target: e.target,
|
|
35
|
+
edgeType: e.edgeType,
|
|
36
|
+
status: e.status ?? '',
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (profile !== 'generic')
|
|
41
|
+
throw new Error(`unknown_profile: ${profile}`);
|
|
42
|
+
// Filter body.
|
|
43
|
+
const contentLines = [];
|
|
44
|
+
let summaryLine = '';
|
|
45
|
+
let deferredSectionCount = 0;
|
|
46
|
+
for (let i = 1; i < lines.length; i++) {
|
|
47
|
+
const l = lines[i].replace(/\r$/, '');
|
|
48
|
+
if (l === '')
|
|
49
|
+
continue;
|
|
50
|
+
// Tab check.
|
|
51
|
+
for (let j = 0; j < l.length; j++) {
|
|
52
|
+
if (l[j] === '\t')
|
|
53
|
+
throw new Error('tab_indentation: tabs in leading whitespace');
|
|
54
|
+
if (l[j] !== ' ')
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const trimmed = l.trimStart();
|
|
58
|
+
if (trimmed.startsWith('# '))
|
|
59
|
+
continue;
|
|
60
|
+
if (trimmed.startsWith('##! ')) {
|
|
61
|
+
summaryLine = trimmed;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (trimmed.startsWith('## ') && trimmed.includes('[?]'))
|
|
65
|
+
deferredSectionCount++;
|
|
66
|
+
contentLines.push(l);
|
|
67
|
+
}
|
|
68
|
+
// Validate ##! summary counts.
|
|
69
|
+
if (summaryLine && deferredSectionCount > 0) {
|
|
70
|
+
validateSummaryCounts(summaryLine, deferredSectionCount, contentLines);
|
|
71
|
+
}
|
|
72
|
+
if (contentLines.length === 0)
|
|
73
|
+
return {};
|
|
74
|
+
const first = contentLines[0].trimStart();
|
|
75
|
+
// Root scalar.
|
|
76
|
+
if (first.startsWith('=')) {
|
|
77
|
+
if (contentLines.length > 1)
|
|
78
|
+
throw new Error('trailing_characters: extra lines after root scalar');
|
|
79
|
+
return (0, scalar_js_1.parseScalar)(first.slice(1), false);
|
|
80
|
+
}
|
|
81
|
+
// Root array.
|
|
82
|
+
if (first.startsWith('## [')) {
|
|
83
|
+
const [arr] = parseArrayFromHeader(contentLines, 0, 0, first.slice(3));
|
|
84
|
+
return arr;
|
|
85
|
+
}
|
|
86
|
+
// Root object.
|
|
87
|
+
const result = {};
|
|
88
|
+
parseObjectBody(contentLines, 0, 0, result);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function parseHeaderProfile(header) {
|
|
92
|
+
const parts = header.split(/\s+/);
|
|
93
|
+
if (parts.length < 2)
|
|
94
|
+
throw new Error('missing_profile');
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
let profile = '';
|
|
97
|
+
for (let i = 1; i < parts.length; i++) {
|
|
98
|
+
const eq = parts[i].indexOf('=');
|
|
99
|
+
if (eq < 0)
|
|
100
|
+
throw new Error(`malformed_header_field: ${parts[i]}`);
|
|
101
|
+
const key = parts[i].slice(0, eq);
|
|
102
|
+
if (seen.has(key))
|
|
103
|
+
throw new Error(`duplicate_header_field: ${key}`);
|
|
104
|
+
seen.add(key);
|
|
105
|
+
if (key === 'profile')
|
|
106
|
+
profile = parts[i].slice(eq + 1);
|
|
107
|
+
}
|
|
108
|
+
if (!profile)
|
|
109
|
+
throw new Error('missing_profile');
|
|
110
|
+
return profile;
|
|
111
|
+
}
|
|
112
|
+
function parseObjectBody(lines, start, depth, out) {
|
|
113
|
+
const ind = ' '.repeat(depth);
|
|
114
|
+
let i = start;
|
|
115
|
+
while (i < lines.length) {
|
|
116
|
+
const line = lines[i];
|
|
117
|
+
if (depth > 0 && !line.startsWith(ind))
|
|
118
|
+
break;
|
|
119
|
+
const content = depth > 0 ? line.slice(ind.length) : line;
|
|
120
|
+
if (content.length > 0 && content[0] === ' ') {
|
|
121
|
+
throw new Error('invalid_indent: indentation increases by more than one level');
|
|
122
|
+
}
|
|
123
|
+
// Array section.
|
|
124
|
+
if (content.startsWith('## ')) {
|
|
125
|
+
const hdr = content.slice(3);
|
|
126
|
+
const bi = hdr.indexOf(' [');
|
|
127
|
+
if (bi >= 0) {
|
|
128
|
+
const name = parseKeyFromHeader(hdr.slice(0, bi));
|
|
129
|
+
checkDup(out, name);
|
|
130
|
+
const [arr, consumed] = parseArrayFromHeader(lines, i, depth, hdr.slice(bi));
|
|
131
|
+
out[name] = arr;
|
|
132
|
+
i += consumed;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const name = parseKeyFromHeader(hdr);
|
|
136
|
+
checkDup(out, name);
|
|
137
|
+
i++;
|
|
138
|
+
const nested = {};
|
|
139
|
+
const consumed = parseObjectBody(lines, i, depth + 1, nested);
|
|
140
|
+
out[name] = nested;
|
|
141
|
+
i += consumed;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Inline array.
|
|
145
|
+
if (!content.startsWith('@') && !content.startsWith('##')) {
|
|
146
|
+
const bracketIdx = content.indexOf('[');
|
|
147
|
+
if (bracketIdx > 0) {
|
|
148
|
+
const rest = content.slice(bracketIdx);
|
|
149
|
+
const closeIdx = rest.indexOf(']');
|
|
150
|
+
if (closeIdx >= 0) {
|
|
151
|
+
const after = rest.slice(closeIdx + 1);
|
|
152
|
+
if (after.startsWith(': ') || after === ':') {
|
|
153
|
+
const name = parseKeyFromHeader(content.slice(0, bracketIdx));
|
|
154
|
+
checkDup(out, name);
|
|
155
|
+
const [arr] = parseArrayFromHeader(lines, i, depth, rest);
|
|
156
|
+
out[name] = arr;
|
|
157
|
+
i++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Key=value.
|
|
164
|
+
const eqIdx = findKeyValueSplit(content);
|
|
165
|
+
if (eqIdx > 0) {
|
|
166
|
+
const name = parseKeyFromHeader(content.slice(0, eqIdx));
|
|
167
|
+
checkDup(out, name);
|
|
168
|
+
out[name] = (0, scalar_js_1.parseScalar)(content.slice(eqIdx + 1), false);
|
|
169
|
+
i++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
i++;
|
|
173
|
+
}
|
|
174
|
+
return i - start;
|
|
175
|
+
}
|
|
176
|
+
function findKeyValueSplit(s) {
|
|
177
|
+
if (!s.length)
|
|
178
|
+
return -1;
|
|
179
|
+
if (s[0] === '"') {
|
|
180
|
+
for (let i = 1; i < s.length; i++) {
|
|
181
|
+
if (s[i] === '\\') {
|
|
182
|
+
i++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (s[i] === '"')
|
|
186
|
+
return (i + 1 < s.length && s[i + 1] === '=') ? i + 1 : -1;
|
|
187
|
+
}
|
|
188
|
+
return -1;
|
|
189
|
+
}
|
|
190
|
+
return s.indexOf('=');
|
|
191
|
+
}
|
|
192
|
+
function parseKeyFromHeader(s) {
|
|
193
|
+
s = s.trim();
|
|
194
|
+
if (s.length >= 2 && s[0] === '"')
|
|
195
|
+
return (0, scalar_js_1.parseQuotedString)(s);
|
|
196
|
+
return s;
|
|
197
|
+
}
|
|
198
|
+
function checkDup(obj, key) {
|
|
199
|
+
if (key in obj)
|
|
200
|
+
throw new Error(`duplicate_key: ${key}`);
|
|
201
|
+
}
|
|
202
|
+
function parseArrayFromHeader(lines, headerLine, depth, bracketPart) {
|
|
203
|
+
const bp = bracketPart.trimStart();
|
|
204
|
+
if (!bp.startsWith('['))
|
|
205
|
+
throw new Error('invalid_count');
|
|
206
|
+
const closeIdx = bp.indexOf(']');
|
|
207
|
+
if (closeIdx < 0)
|
|
208
|
+
throw new Error('invalid_count');
|
|
209
|
+
const countStr = bp.slice(1, closeIdx);
|
|
210
|
+
const afterBracket = bp.slice(closeIdx + 1);
|
|
211
|
+
let count = -1;
|
|
212
|
+
if (countStr !== '?')
|
|
213
|
+
count = parseCount(countStr);
|
|
214
|
+
if (count === 0 && !afterBracket.startsWith('{') && !afterBracket.startsWith(':')) {
|
|
215
|
+
return [[], 1];
|
|
216
|
+
}
|
|
217
|
+
// Inline.
|
|
218
|
+
if (afterBracket.startsWith(': ') || afterBracket === ':') {
|
|
219
|
+
const valsStr = afterBracket.startsWith(': ') ? afterBracket.slice(2) : '';
|
|
220
|
+
if (!valsStr) {
|
|
221
|
+
if (count >= 0 && count !== 0)
|
|
222
|
+
throw new Error(`count_mismatch: declared ${count}, got 0`);
|
|
223
|
+
return [[], 1];
|
|
224
|
+
}
|
|
225
|
+
const vals = (0, scalar_js_1.splitRespectingQuotes)(valsStr, ',');
|
|
226
|
+
if (count >= 0 && vals.length !== count)
|
|
227
|
+
throw new Error(`count_mismatch: declared ${count}, got ${vals.length}`);
|
|
228
|
+
return [vals.map(v => (0, scalar_js_1.parseScalar)(v.trim(), false)), 1];
|
|
229
|
+
}
|
|
230
|
+
// Tabular.
|
|
231
|
+
if (afterBracket.startsWith('{')) {
|
|
232
|
+
const braceEnd = findClosingBrace(afterBracket);
|
|
233
|
+
if (braceEnd < 0)
|
|
234
|
+
throw new Error('invalid field declaration');
|
|
235
|
+
const fields = (0, scalar_js_1.splitFieldDecl)(afterBracket.slice(0, braceEnd + 1));
|
|
236
|
+
const [rows, consumed] = parseTabularBody(lines, headerLine + 1, depth, fields, count);
|
|
237
|
+
if (count >= 0 && rows.length !== count)
|
|
238
|
+
throw new Error(`count_mismatch: declared ${count}, got ${rows.length}`);
|
|
239
|
+
return [rows, consumed + 1];
|
|
240
|
+
}
|
|
241
|
+
// Expanded.
|
|
242
|
+
const [items, consumed] = parseExpandedBody(lines, headerLine + 1, depth);
|
|
243
|
+
if (count >= 0 && items.length !== count)
|
|
244
|
+
throw new Error(`count_mismatch: declared ${count}, got ${items.length}`);
|
|
245
|
+
return [items, consumed + 1];
|
|
246
|
+
}
|
|
247
|
+
function findClosingBrace(s) {
|
|
248
|
+
let inQuote = false, escaped = false;
|
|
249
|
+
for (let i = 0; i < s.length; i++) {
|
|
250
|
+
if (escaped) {
|
|
251
|
+
escaped = false;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (s[i] === '\\' && inQuote) {
|
|
255
|
+
escaped = true;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (s[i] === '"') {
|
|
259
|
+
inQuote = !inQuote;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (s[i] === '}' && !inQuote)
|
|
263
|
+
return i;
|
|
264
|
+
}
|
|
265
|
+
return -1;
|
|
266
|
+
}
|
|
267
|
+
function parseTabularBody(lines, start, depth, fields, expectedCount) {
|
|
268
|
+
const ind = ' '.repeat(depth);
|
|
269
|
+
const rows = [];
|
|
270
|
+
let i = start;
|
|
271
|
+
// Track inline schemas and shared array schemas.
|
|
272
|
+
const inlineSchemas = new Map();
|
|
273
|
+
const sharedArraySchemas = new Map();
|
|
274
|
+
while (i < lines.length) {
|
|
275
|
+
const line = lines[i];
|
|
276
|
+
const content = depth > 0 ? (line.startsWith(ind) ? line.slice(ind.length) : null) : line;
|
|
277
|
+
if (content === null)
|
|
278
|
+
break;
|
|
279
|
+
if (content.startsWith('## ') || content.startsWith('##!'))
|
|
280
|
+
break;
|
|
281
|
+
if (content.length > 0 && content[0] === ' ') {
|
|
282
|
+
const trimmed = content.trimStart();
|
|
283
|
+
if (trimmed.startsWith('.'))
|
|
284
|
+
break; // attachment lines handled below (v2 indented or v3)
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
// Strip @N prefix (must be @digits).
|
|
288
|
+
let rowData = content;
|
|
289
|
+
let rowHasID = false;
|
|
290
|
+
if (rowData.startsWith('@')) {
|
|
291
|
+
const sp = rowData.indexOf(' ');
|
|
292
|
+
if (sp > 0) {
|
|
293
|
+
const idStr = rowData.slice(1, sp);
|
|
294
|
+
if (/^\d+$/.test(idStr)) {
|
|
295
|
+
rowData = rowData.slice(sp + 1);
|
|
296
|
+
rowHasID = true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const vals = (0, scalar_js_1.splitRespectingQuotes)(rowData, '|');
|
|
301
|
+
if (vals.length !== fields.length)
|
|
302
|
+
throw new Error(`row_width_mismatch: expected ${fields.length}, got ${vals.length}`);
|
|
303
|
+
// Parse cells: scalars, traditional attachments, and inline schema attachments.
|
|
304
|
+
const cellValues = new Map();
|
|
305
|
+
const traditionalAttFields = [];
|
|
306
|
+
const inlineAttFields = [];
|
|
307
|
+
const inlineAttOrder = [];
|
|
308
|
+
const missingFields = new Set();
|
|
309
|
+
for (let j = 0; j < fields.length; j++) {
|
|
310
|
+
const cellVal = vals[j];
|
|
311
|
+
// Check for ^{fields} inline schema declaration.
|
|
312
|
+
if (cellVal.startsWith('^{') && cellVal.endsWith('}')) {
|
|
313
|
+
const schemaStr = cellVal.slice(1);
|
|
314
|
+
const ifs = (0, scalar_js_1.splitFieldDecl)(schemaStr);
|
|
315
|
+
inlineSchemas.set(fields[j], ifs);
|
|
316
|
+
inlineAttFields.push(fields[j]);
|
|
317
|
+
inlineAttOrder.push(fields[j]);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const parsed = (0, scalar_js_1.parseScalar)(cellVal, true);
|
|
321
|
+
if (parsed === scalar_js_1.MISSING) {
|
|
322
|
+
missingFields.add(fields[j]);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (parsed === scalar_js_1.ATTACHMENT) {
|
|
326
|
+
// Check if this field has a stored inline schema.
|
|
327
|
+
if (inlineSchemas.has(fields[j])) {
|
|
328
|
+
inlineAttFields.push(fields[j]);
|
|
329
|
+
inlineAttOrder.push(fields[j]);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
traditionalAttFields.push(fields[j]);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
// Handle inline schema objects returned by parseScalar (for ^{...} that got through).
|
|
337
|
+
if (parsed && typeof parsed === 'object' && parsed.__inlineSchema) {
|
|
338
|
+
const ifs = (0, scalar_js_1.splitFieldDecl)(parsed.__inlineSchema);
|
|
339
|
+
inlineSchemas.set(fields[j], ifs);
|
|
340
|
+
inlineAttFields.push(fields[j]);
|
|
341
|
+
inlineAttOrder.push(fields[j]);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
cellValues.set(fields[j], parsed);
|
|
345
|
+
}
|
|
346
|
+
i++;
|
|
347
|
+
// Parse attachments in line order.
|
|
348
|
+
const allAttFields = [...traditionalAttFields, ...inlineAttFields];
|
|
349
|
+
const attachmentValues = new Map();
|
|
350
|
+
if (rowHasID && allAttFields.length > 0) {
|
|
351
|
+
let inlineIdx = 0;
|
|
352
|
+
while (i < lines.length && attachmentValues.size < allAttFields.length) {
|
|
353
|
+
const aLine = lines[i];
|
|
354
|
+
let aContent = null;
|
|
355
|
+
if (depth === 0 || aLine.startsWith(ind)) {
|
|
356
|
+
aContent = depth > 0 ? aLine.slice(ind.length) : aLine;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
if (aContent === null)
|
|
362
|
+
break;
|
|
363
|
+
// Line starts with ".": traditional or prefixed inline attachment.
|
|
364
|
+
// Also handle v2-format indented attachments (" .field ...").
|
|
365
|
+
let attContent = aContent;
|
|
366
|
+
if (!attContent.startsWith('.') && attContent.startsWith(' .')) {
|
|
367
|
+
attContent = attContent.slice(2); // strip v2 indent
|
|
368
|
+
}
|
|
369
|
+
if (attContent.startsWith('.')) {
|
|
370
|
+
const rest = attContent.slice(1);
|
|
371
|
+
const [attName, afterName] = parseAttachmentName(rest);
|
|
372
|
+
// Check if this is an inline schema field with pipe-delimited data.
|
|
373
|
+
const ifs = inlineSchemas.get(attName);
|
|
374
|
+
if (ifs && !afterName.trimStart().startsWith('{}') && !afterName.trimStart().startsWith('[')) {
|
|
375
|
+
const data = afterName.trimStart();
|
|
376
|
+
const inlineVals = (0, scalar_js_1.splitRespectingQuotes)(data, '|');
|
|
377
|
+
if (inlineVals.length !== ifs.length)
|
|
378
|
+
throw new Error(`inline_width_mismatch: ${attName} expected ${ifs.length}, got ${inlineVals.length}`);
|
|
379
|
+
const obj = {};
|
|
380
|
+
for (let k = 0; k < ifs.length; k++) {
|
|
381
|
+
const p = (0, scalar_js_1.parseScalar)(inlineVals[k], true);
|
|
382
|
+
if (p !== scalar_js_1.MISSING)
|
|
383
|
+
obj[ifs[k]] = p;
|
|
384
|
+
}
|
|
385
|
+
if (attachmentValues.has(attName))
|
|
386
|
+
throw new Error(`duplicate_attachment: ${attName}`);
|
|
387
|
+
attachmentValues.set(attName, obj);
|
|
388
|
+
i++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
// Traditional attachment.
|
|
392
|
+
const [name, val, consumed, parsedFields] = parseAttachment(lines, i, rest, depth + 2, sharedArraySchemas);
|
|
393
|
+
if (attachmentValues.has(name))
|
|
394
|
+
throw new Error(`duplicate_attachment: ${name}`);
|
|
395
|
+
// Store shared array schema from first row.
|
|
396
|
+
if (rows.length === 0 && parsedFields) {
|
|
397
|
+
sharedArraySchemas.set(name, parsedFields);
|
|
398
|
+
}
|
|
399
|
+
attachmentValues.set(name, val);
|
|
400
|
+
i += consumed;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
// No-prefix line: positional inline data.
|
|
404
|
+
let foundInline = false;
|
|
405
|
+
let nextInlineField = '';
|
|
406
|
+
while (inlineIdx < inlineAttOrder.length) {
|
|
407
|
+
const candidate = inlineAttOrder[inlineIdx];
|
|
408
|
+
if (!attachmentValues.has(candidate)) {
|
|
409
|
+
nextInlineField = candidate;
|
|
410
|
+
foundInline = true;
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
inlineIdx++;
|
|
414
|
+
}
|
|
415
|
+
if (!foundInline)
|
|
416
|
+
break;
|
|
417
|
+
const ifs = inlineSchemas.get(nextInlineField);
|
|
418
|
+
const inlineVals = (0, scalar_js_1.splitRespectingQuotes)(aContent, '|');
|
|
419
|
+
if (inlineVals.length !== ifs.length)
|
|
420
|
+
throw new Error(`inline_width_mismatch: ${nextInlineField} expected ${ifs.length}, got ${inlineVals.length}`);
|
|
421
|
+
const obj = {};
|
|
422
|
+
for (let k = 0; k < ifs.length; k++) {
|
|
423
|
+
const p = (0, scalar_js_1.parseScalar)(inlineVals[k], true);
|
|
424
|
+
if (p !== scalar_js_1.MISSING)
|
|
425
|
+
obj[ifs[k]] = p;
|
|
426
|
+
}
|
|
427
|
+
attachmentValues.set(nextInlineField, obj);
|
|
428
|
+
inlineIdx++;
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
for (const f of allAttFields) {
|
|
432
|
+
if (!attachmentValues.has(f))
|
|
433
|
+
throw new Error(`missing_attachment: ${f}`);
|
|
434
|
+
}
|
|
435
|
+
// Check for duplicate attachments: if the next line is also an attachment
|
|
436
|
+
// line at this depth, it means there's a second attachment for a field
|
|
437
|
+
// that was already resolved.
|
|
438
|
+
if (i < lines.length) {
|
|
439
|
+
let peekContent = null;
|
|
440
|
+
if (depth === 0 || lines[i].startsWith(ind)) {
|
|
441
|
+
peekContent = depth > 0 ? lines[i].slice(ind.length) : lines[i];
|
|
442
|
+
}
|
|
443
|
+
if (peekContent !== null) {
|
|
444
|
+
let peekAtt = peekContent;
|
|
445
|
+
if (!peekAtt.startsWith('.') && peekAtt.startsWith(' .')) {
|
|
446
|
+
peekAtt = peekAtt.slice(2);
|
|
447
|
+
}
|
|
448
|
+
if (peekAtt.startsWith('.')) {
|
|
449
|
+
const peekRest = peekAtt.slice(1);
|
|
450
|
+
const [peekName] = parseAttachmentName(peekRest);
|
|
451
|
+
if (attachmentValues.has(peekName)) {
|
|
452
|
+
throw new Error(`duplicate_attachment: ${peekName}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Build row in field declaration order.
|
|
459
|
+
const row = {};
|
|
460
|
+
for (const f of fields) {
|
|
461
|
+
if (missingFields.has(f))
|
|
462
|
+
continue;
|
|
463
|
+
if (cellValues.has(f)) {
|
|
464
|
+
row[f] = cellValues.get(f);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (attachmentValues.has(f)) {
|
|
468
|
+
row[f] = attachmentValues.get(f);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (!rowHasID || allAttFields.length === 0) {
|
|
473
|
+
const attIndent = ind + ' ';
|
|
474
|
+
if (i < lines.length && lines[i].startsWith(attIndent)) {
|
|
475
|
+
const peek = lines[i].slice(attIndent.length);
|
|
476
|
+
if (peek.startsWith('.'))
|
|
477
|
+
throw new Error(`orphan_attachment: ${peek}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
rows.push(row);
|
|
481
|
+
if (expectedCount >= 0 && rows.length >= expectedCount)
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
return [rows, i - start];
|
|
485
|
+
}
|
|
486
|
+
function parseAttachmentName(rest) {
|
|
487
|
+
if (rest[0] === '"') {
|
|
488
|
+
for (let j = 1; j < rest.length; j++) {
|
|
489
|
+
if (rest[j] === '\\') {
|
|
490
|
+
j++;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (rest[j] === '"') {
|
|
494
|
+
const name = (0, scalar_js_1.parseQuotedString)(rest.slice(0, j + 1));
|
|
495
|
+
return [name, rest.slice(j + 1)];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return ['', rest];
|
|
499
|
+
}
|
|
500
|
+
const sp = rest.indexOf(' ');
|
|
501
|
+
if (sp >= 0)
|
|
502
|
+
return [rest.slice(0, sp), rest.slice(sp)];
|
|
503
|
+
return [rest, ''];
|
|
504
|
+
}
|
|
505
|
+
/** Attachment parser: returns [name, value, consumed, parsedFields]. parsedFields is set for tabular arrays with explicit {fields}. */
|
|
506
|
+
function parseAttachment(lines, lineIdx, rest, depth, sharedSchemas) {
|
|
507
|
+
const [name, afterNameRaw] = parseAttachmentName(rest);
|
|
508
|
+
const afterName = afterNameRaw.trimStart();
|
|
509
|
+
if (afterName.startsWith('{}')) {
|
|
510
|
+
const nested = {};
|
|
511
|
+
const consumed = parseObjectBody(lines, lineIdx + 1, depth, nested);
|
|
512
|
+
return [name, nested, consumed + 1, null];
|
|
513
|
+
}
|
|
514
|
+
if (afterName.startsWith('[')) {
|
|
515
|
+
const closeBracket = afterName.indexOf(']');
|
|
516
|
+
if (closeBracket < 0)
|
|
517
|
+
throw new Error('invalid_count: missing ]');
|
|
518
|
+
const afterClose = afterName.slice(closeBracket + 1);
|
|
519
|
+
// [N]{fields}: has its own schema.
|
|
520
|
+
if (afterClose.startsWith('{')) {
|
|
521
|
+
const endBrace = findClosingBrace(afterClose);
|
|
522
|
+
let parsedFields = null;
|
|
523
|
+
if (endBrace >= 0) {
|
|
524
|
+
try {
|
|
525
|
+
parsedFields = (0, scalar_js_1.splitFieldDecl)(afterClose.slice(0, endBrace + 1));
|
|
526
|
+
}
|
|
527
|
+
catch { }
|
|
528
|
+
}
|
|
529
|
+
const [arr, consumed] = parseArrayFromHeader(lines, lineIdx, depth, afterName);
|
|
530
|
+
return [name, arr, consumed, parsedFields];
|
|
531
|
+
}
|
|
532
|
+
// [N]: values or [N] (check for inline primitive array first).
|
|
533
|
+
const afterCloseForInline = afterName.slice(closeBracket + 1);
|
|
534
|
+
if (afterCloseForInline.startsWith(': ') || afterCloseForInline === ':') {
|
|
535
|
+
// Inline primitive array: don't use shared schema.
|
|
536
|
+
const [arr, consumed] = parseArrayFromHeader(lines, lineIdx, depth, afterName);
|
|
537
|
+
return [name, arr, consumed, null];
|
|
538
|
+
}
|
|
539
|
+
// [N] without {fields}: check for shared schema.
|
|
540
|
+
if (sharedSchemas.has(name)) {
|
|
541
|
+
const sf = sharedSchemas.get(name);
|
|
542
|
+
const countStr = afterName.slice(1, closeBracket);
|
|
543
|
+
let count = -1;
|
|
544
|
+
if (countStr !== '?')
|
|
545
|
+
count = parseInt(countStr, 10);
|
|
546
|
+
if (count === 0)
|
|
547
|
+
return [name, [], 1, null];
|
|
548
|
+
// Peek: if next line starts with @, it's expanded, not tabular.
|
|
549
|
+
const nextIdx = lineIdx + 1;
|
|
550
|
+
const ind = ' '.repeat(depth);
|
|
551
|
+
let useShared = true;
|
|
552
|
+
if (nextIdx < lines.length) {
|
|
553
|
+
let nextContent = lines[nextIdx];
|
|
554
|
+
if (depth > 0 && nextContent.startsWith(ind))
|
|
555
|
+
nextContent = nextContent.slice(ind.length);
|
|
556
|
+
if (nextContent.trimStart().startsWith('@'))
|
|
557
|
+
useShared = false;
|
|
558
|
+
}
|
|
559
|
+
if (useShared) {
|
|
560
|
+
const [rows, consumed] = parseTabularBody(lines, lineIdx + 1, depth, sf, count);
|
|
561
|
+
if (count >= 0 && rows.length !== count)
|
|
562
|
+
throw new Error(`count_mismatch: declared ${count}, got ${rows.length}`);
|
|
563
|
+
return [name, rows, consumed + 1, null];
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// No shared schema: standard parsing.
|
|
567
|
+
const [arr, consumed] = parseArrayFromHeader(lines, lineIdx, depth, afterName);
|
|
568
|
+
return [name, arr, consumed, null];
|
|
569
|
+
}
|
|
570
|
+
throw new Error(`invalid attachment form: ${afterName}`);
|
|
571
|
+
}
|
|
572
|
+
function parseExpandedBody(lines, start, depth) {
|
|
573
|
+
const ind = ' '.repeat(depth);
|
|
574
|
+
const items = [];
|
|
575
|
+
let i = start;
|
|
576
|
+
while (i < lines.length) {
|
|
577
|
+
const line = lines[i];
|
|
578
|
+
const content = depth > 0 ? (line.startsWith(ind) ? line.slice(ind.length) : null) : line;
|
|
579
|
+
if (content === null)
|
|
580
|
+
break;
|
|
581
|
+
if (content.startsWith('## ') || content.startsWith('##!'))
|
|
582
|
+
break;
|
|
583
|
+
if (!content.startsWith('@'))
|
|
584
|
+
break;
|
|
585
|
+
const sp = content.indexOf(' ');
|
|
586
|
+
if (sp < 0)
|
|
587
|
+
break;
|
|
588
|
+
const idStr = content.slice(1, sp);
|
|
589
|
+
const id = parseInt(idStr, 10);
|
|
590
|
+
if (!isNaN(id) && id !== items.length) {
|
|
591
|
+
throw new Error(`invalid_item_id: expected @${items.length}, got @${idStr}`);
|
|
592
|
+
}
|
|
593
|
+
const marker = content.slice(sp + 1);
|
|
594
|
+
if (marker.startsWith('=')) {
|
|
595
|
+
items.push((0, scalar_js_1.parseScalar)(marker.slice(1), false));
|
|
596
|
+
i++;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (marker.startsWith('{}')) {
|
|
600
|
+
const nested = {};
|
|
601
|
+
i++;
|
|
602
|
+
const consumed = parseObjectBody(lines, i, depth + 1, nested);
|
|
603
|
+
items.push(nested);
|
|
604
|
+
i += consumed;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (marker.startsWith('[')) {
|
|
608
|
+
const [arr, consumed] = parseArrayFromHeader(lines, i, depth + 1, marker);
|
|
609
|
+
items.push(arr);
|
|
610
|
+
i += consumed;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
return [items, i - start];
|
|
616
|
+
}
|
|
617
|
+
function parseCount(s) {
|
|
618
|
+
if (s === '0')
|
|
619
|
+
return 0;
|
|
620
|
+
if (!s.length || s[0] === '0')
|
|
621
|
+
throw new Error(`invalid_count: ${s}`);
|
|
622
|
+
const n = parseInt(s, 10);
|
|
623
|
+
if (isNaN(n) || String(n) !== s)
|
|
624
|
+
throw new Error(`invalid_count: ${s}`);
|
|
625
|
+
return n;
|
|
626
|
+
}
|
|
627
|
+
function validateSummaryCounts(summaryLine, deferredCount, contentLines) {
|
|
628
|
+
// Parse counts from "##! summary counts=N,M,..."
|
|
629
|
+
const parts = summaryLine.split(/\s+/);
|
|
630
|
+
let countsStr = '';
|
|
631
|
+
for (const p of parts) {
|
|
632
|
+
if (p.startsWith('counts=')) {
|
|
633
|
+
countsStr = p.slice(7);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (!countsStr)
|
|
638
|
+
return;
|
|
639
|
+
const countVals = countsStr.split(',');
|
|
640
|
+
if (countVals.length !== deferredCount) {
|
|
641
|
+
throw new Error(`count_mismatch: summary has ${countVals.length} count entries but ${deferredCount} deferred sections`);
|
|
642
|
+
}
|
|
643
|
+
// Count actual items per deferred section.
|
|
644
|
+
const actualCounts = [];
|
|
645
|
+
let inDeferred = false;
|
|
646
|
+
let currentCount = 0;
|
|
647
|
+
for (const l of contentLines) {
|
|
648
|
+
const trimmed = l.trimStart();
|
|
649
|
+
if (trimmed.startsWith('## ') && trimmed.includes('[?]')) {
|
|
650
|
+
if (inDeferred)
|
|
651
|
+
actualCounts.push(currentCount);
|
|
652
|
+
inDeferred = true;
|
|
653
|
+
currentCount = 0;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (trimmed.startsWith('## ')) {
|
|
657
|
+
if (inDeferred) {
|
|
658
|
+
actualCounts.push(currentCount);
|
|
659
|
+
inDeferred = false;
|
|
660
|
+
}
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (inDeferred && !trimmed.startsWith(' ') && !trimmed.startsWith('.')) {
|
|
664
|
+
currentCount++;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (inDeferred)
|
|
668
|
+
actualCounts.push(currentCount);
|
|
669
|
+
for (let i = 0; i < countVals.length; i++) {
|
|
670
|
+
const declared = parseInt(countVals[i], 10);
|
|
671
|
+
if (isNaN(declared))
|
|
672
|
+
throw new Error(`count_mismatch: invalid count value "${countVals[i]}"`);
|
|
673
|
+
if (i < actualCounts.length && declared !== actualCounts[i]) {
|
|
674
|
+
throw new Error(`count_mismatch: section ${i} declared ${declared} in summary, actual ${actualCounts[i]}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
//# sourceMappingURL=decode_generic.js.map
|