@jigs.design/compiler 0.0.0-stage → 0.1.1

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/dist/parse.js ADDED
@@ -0,0 +1,690 @@
1
+ import { extendMap, sliceMap, sourceOffset, sourceRange } from "./offsets.js";
2
+ //#region src/parse.ts
3
+ const TAG_NAME = /[A-Za-z][\w-]*/y;
4
+ const ATTR_NAME = /[A-Za-z_][\w-]*/y;
5
+ const QUOTES = /* @__PURE__ */ new Set([
6
+ "\"",
7
+ "'",
8
+ "`"
9
+ ]);
10
+ const isSpace = (char) => /\s/.test(char);
11
+ function matchAt(pattern, source, index) {
12
+ pattern.lastIndex = index;
13
+ return pattern.exec(source)?.[0] ?? null;
14
+ }
15
+ function skipSpace(source, index) {
16
+ let cursor = index;
17
+ while (cursor < source.length && isSpace(source.charAt(cursor))) cursor++;
18
+ return cursor;
19
+ }
20
+ /** Code in braces that is only comments, such as `{/* note *\/}`. */
21
+ const ONLY_COMMENTS = /^(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*$/;
22
+ /**
23
+ * Index of the `}` in a line comment that closes the outermost brace, as in
24
+ * `{@body const total = 3 // it's the total}`, or -1. Braces the comment opens
25
+ * itself, as in `// sort by {value}`, are its own text.
26
+ */
27
+ function commentCloser(source, start, end) {
28
+ let depth = 0;
29
+ for (let index = start; index < end; index++) {
30
+ const char = source.charAt(index);
31
+ if (char === "{") depth++;
32
+ else if (char === "}") {
33
+ depth--;
34
+ if (depth < 0) return index;
35
+ }
36
+ }
37
+ return -1;
38
+ }
39
+ /**
40
+ * Index just past the `}` matching the `{` at `start`, or -1 if unbalanced.
41
+ * Quotes inside comments don't open strings. A line comment ends at its
42
+ * newline, or at an unmatched `}` that closes the outermost brace (see
43
+ * `commentCloser`).
44
+ */
45
+ function scanBalanced(source, start) {
46
+ let depth = 0;
47
+ let quote = null;
48
+ for (let index = start; index < source.length; index++) {
49
+ const char = source.charAt(index);
50
+ if (quote !== null) {
51
+ if (char === "\\") index++;
52
+ else if (char === quote) quote = null;
53
+ continue;
54
+ }
55
+ if (char === "/" && source.charAt(index + 1) === "/") {
56
+ const newline = source.indexOf("\n", index);
57
+ const lineEnd = newline === -1 ? source.length : newline;
58
+ const closer = depth === 1 ? commentCloser(source, index + 2, lineEnd) : -1;
59
+ if (closer !== -1) return closer + 1;
60
+ index = lineEnd - 1;
61
+ continue;
62
+ }
63
+ if (char === "/" && source.charAt(index + 1) === "*") {
64
+ const end = source.indexOf("*/", index + 2);
65
+ if (end === -1) return -1;
66
+ index = end + 1;
67
+ continue;
68
+ }
69
+ if (QUOTES.has(char)) quote = char;
70
+ else if (char === "{") depth++;
71
+ else if (char === "}") {
72
+ depth--;
73
+ if (depth === 0) return index + 1;
74
+ }
75
+ }
76
+ return -1;
77
+ }
78
+ /** The code between `from` and `to`, trimmed, as a prop value. */
79
+ function readBraced(source, from, to, map) {
80
+ const code = source.slice(from, to);
81
+ const trimmed = code.trim();
82
+ if (trimmed === "") return null;
83
+ if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return {
84
+ kind: "literal",
85
+ value: Number(trimmed)
86
+ };
87
+ if (trimmed === "true" || trimmed === "false") return {
88
+ kind: "literal",
89
+ value: trimmed === "true"
90
+ };
91
+ const quoted = /^"([^"\\]*)"$|^'([^'\\]*)'$/.exec(trimmed);
92
+ if (quoted) return {
93
+ kind: "literal",
94
+ value: quoted[1] ?? quoted[2] ?? ""
95
+ };
96
+ return {
97
+ at: sliceMap(map, from + code.length - code.trimStart().length),
98
+ code: trimmed,
99
+ kind: "expr"
100
+ };
101
+ }
102
+ /** `map` gives the source offsets of `source`, for the tag's props. */
103
+ function scanOpenTag(source, start, map) {
104
+ const name = matchAt(TAG_NAME, source, start + 1);
105
+ if (name === null) return "invalid";
106
+ const props = [];
107
+ let cursor = start + 1 + name.length;
108
+ for (;;) {
109
+ cursor = skipSpace(source, cursor);
110
+ if (cursor >= source.length) return "incomplete";
111
+ if (source.startsWith("/>", cursor) || source.charAt(cursor) === ">") {
112
+ const selfClosing = source.charAt(cursor) === "/";
113
+ const end = cursor + (selfClosing ? 2 : 1);
114
+ const range = sourceRange(map, start, end);
115
+ return {
116
+ end,
117
+ token: {
118
+ end: range.end,
119
+ name,
120
+ props,
121
+ selfClosing,
122
+ start: range.start,
123
+ type: "open"
124
+ }
125
+ };
126
+ }
127
+ if (source.charAt(cursor) === "/" && cursor + 1 >= source.length) return "incomplete";
128
+ const attr = matchAt(ATTR_NAME, source, cursor);
129
+ if (attr === null) return "invalid";
130
+ const attrStart = cursor;
131
+ cursor = skipSpace(source, cursor + attr.length);
132
+ if (source.charAt(cursor) !== "=") {
133
+ const range = sourceRange(map, attrStart, attrStart + attr.length);
134
+ props.push({
135
+ end: range.end,
136
+ name: attr,
137
+ shorthand: true,
138
+ start: range.start,
139
+ value: {
140
+ kind: "literal",
141
+ value: true
142
+ }
143
+ });
144
+ continue;
145
+ }
146
+ cursor = skipSpace(source, cursor + 1);
147
+ const opener = source.charAt(cursor);
148
+ if (opener === "") return "incomplete";
149
+ if (opener === "\"" || opener === "'") {
150
+ const close = source.indexOf(opener, cursor + 1);
151
+ if (close === -1) return "incomplete";
152
+ const range = sourceRange(map, attrStart, close + 1);
153
+ props.push({
154
+ end: range.end,
155
+ name: attr,
156
+ start: range.start,
157
+ value: {
158
+ kind: "literal",
159
+ value: source.slice(cursor + 1, close)
160
+ },
161
+ written: source.slice(attrStart, close + 1)
162
+ });
163
+ cursor = close + 1;
164
+ continue;
165
+ }
166
+ if (opener === "{") {
167
+ const end = scanBalanced(source, cursor);
168
+ if (end === -1) return "braces";
169
+ const value = readBraced(source, cursor + 1, end - 1, map);
170
+ if (value !== null) {
171
+ const range = sourceRange(map, attrStart, end);
172
+ props.push({
173
+ end: range.end,
174
+ name: attr,
175
+ start: range.start,
176
+ value,
177
+ written: source.slice(attrStart, end)
178
+ });
179
+ }
180
+ cursor = end;
181
+ continue;
182
+ }
183
+ return "invalid";
184
+ }
185
+ }
186
+ function scanCloseTag(source, start, map) {
187
+ const name = matchAt(TAG_NAME, source, start + 2);
188
+ if (name === null) return start + 2 >= source.length ? "incomplete" : "invalid";
189
+ const cursor = skipSpace(source, start + 2 + name.length);
190
+ if (cursor >= source.length) return "incomplete";
191
+ if (source.charAt(cursor) !== ">") return "invalid";
192
+ const range = sourceRange(map, start, cursor + 1);
193
+ return {
194
+ end: cursor + 1,
195
+ token: {
196
+ end: range.end,
197
+ name,
198
+ start: range.start,
199
+ type: "close"
200
+ }
201
+ };
202
+ }
203
+ /**
204
+ * Splits a run of Pickle into tags, `{expressions}` and text. While more input
205
+ * may follow, an unclosed tag or `{` ends the run as `incomplete`. Once
206
+ * `final`, nothing more can arrive: an unclosed `{` is kept as text and an
207
+ * unclosed tag is dropped, each with a problem. `map` gives the source
208
+ * offsets of `source`.
209
+ */
210
+ function tokenizeInline(source, final, map) {
211
+ const tokens = [];
212
+ const problems = [];
213
+ let cursor = 0;
214
+ let text = "";
215
+ const flush = () => {
216
+ if (text !== "") {
217
+ tokens.push({
218
+ index: cursor - text.length,
219
+ type: "text",
220
+ value: text
221
+ });
222
+ text = "";
223
+ }
224
+ };
225
+ while (cursor < source.length) {
226
+ const char = source.charAt(cursor);
227
+ if (char === "<") {
228
+ const next = source.charAt(cursor + 1);
229
+ if (next === "" && !final) {
230
+ flush();
231
+ return {
232
+ inBraces: false,
233
+ incomplete: "tag",
234
+ problems,
235
+ tokens
236
+ };
237
+ }
238
+ if (next === "/" || /[A-Za-z]/.test(next)) {
239
+ const scan = next === "/" ? scanCloseTag(source, cursor, map) : scanOpenTag(source, cursor, map);
240
+ if (scan === "incomplete" || scan === "braces") {
241
+ flush();
242
+ if (!final) return {
243
+ inBraces: scan === "braces",
244
+ incomplete: "tag",
245
+ problems,
246
+ tokens
247
+ };
248
+ const slash = next === "/" ? "/" : "";
249
+ const name = matchAt(TAG_NAME, source, cursor + 1 + slash.length);
250
+ problems.push(`<${slash}${name ?? ""}> is unfinished: a closing quote, \`}\` or \`>\` is missing, so it and the rest of the line were removed`);
251
+ return {
252
+ inBraces: false,
253
+ incomplete: null,
254
+ problems,
255
+ tokens
256
+ };
257
+ }
258
+ if (scan !== "invalid") {
259
+ flush();
260
+ tokens.push(scan.token);
261
+ cursor = scan.end;
262
+ continue;
263
+ }
264
+ }
265
+ } else if (char === "{") {
266
+ const end = scanBalanced(source, cursor);
267
+ if (end === -1 && !final) {
268
+ flush();
269
+ return {
270
+ inBraces: false,
271
+ incomplete: "expression",
272
+ problems,
273
+ tokens
274
+ };
275
+ }
276
+ if (end === -1) {
277
+ problems.push("`{` has no matching `}`, so it was shown as text. Close the expression, or write {\"{\"} for a brace in text");
278
+ text += char;
279
+ cursor++;
280
+ continue;
281
+ }
282
+ flush();
283
+ const inner = source.slice(cursor + 1, end - 1);
284
+ const code = inner.trim();
285
+ if (!ONLY_COMMENTS.test(code)) {
286
+ const from = cursor + 1 + inner.length - inner.trimStart().length;
287
+ tokens.push({
288
+ at: sliceMap(map, from),
289
+ code,
290
+ type: "expr"
291
+ });
292
+ }
293
+ cursor = end;
294
+ continue;
295
+ }
296
+ text += char;
297
+ cursor++;
298
+ }
299
+ flush();
300
+ return {
301
+ inBraces: false,
302
+ incomplete: null,
303
+ problems,
304
+ tokens
305
+ };
306
+ }
307
+ function element(name, props, line, range, children = []) {
308
+ return {
309
+ children,
310
+ end: range.end,
311
+ line,
312
+ name,
313
+ props,
314
+ start: range.start,
315
+ type: "element"
316
+ };
317
+ }
318
+ function createTreeBuilder(repairs, errors) {
319
+ const root = element("#root", [], 0, {
320
+ end: 0,
321
+ start: 0
322
+ });
323
+ const stack = [];
324
+ const append = (node) => {
325
+ (stack.at(-1)?.node ?? root).children.push(node);
326
+ };
327
+ const open = (node, kind) => {
328
+ append(node);
329
+ const frame = {
330
+ kind,
331
+ node
332
+ };
333
+ stack.push(frame);
334
+ return frame;
335
+ };
336
+ const isOpen = (frame) => stack.includes(frame);
337
+ const insideTag = () => stack.some((frame) => frame.kind === "tag");
338
+ /** Closes `frame` and anything opened after it. */
339
+ const close = (frame) => {
340
+ const index = stack.indexOf(frame);
341
+ if (index === -1) return;
342
+ for (const above of stack.slice(index + 1)) if (above.kind === "tag") repairs.push({
343
+ end: above.node.end,
344
+ line: above.node.line,
345
+ message: `Closed unclosed <${above.node.name}>`,
346
+ start: above.node.start
347
+ });
348
+ stack.length = index;
349
+ };
350
+ const closeEmphasis = () => {
351
+ while (stack.at(-1)?.kind === "emphasis") stack.pop();
352
+ };
353
+ /** `index` is where `value` starts in the run `map` describes. */
354
+ const feedText = (value, line, map, index) => {
355
+ let buffer = "";
356
+ const flush = () => {
357
+ if (buffer !== "") {
358
+ append({
359
+ type: "text",
360
+ value: buffer
361
+ });
362
+ buffer = "";
363
+ }
364
+ };
365
+ let cursor = 0;
366
+ while (cursor < value.length) {
367
+ const char = value.charAt(cursor);
368
+ if (char === "`") {
369
+ const end = value.indexOf("`", cursor + 1);
370
+ if (end > cursor + 1) {
371
+ flush();
372
+ append(element("code", [], line, sourceRange(map, index + cursor, index + end + 1), [{
373
+ type: "text",
374
+ value: value.slice(cursor + 1, end)
375
+ }]));
376
+ cursor = end + 1;
377
+ continue;
378
+ }
379
+ }
380
+ if (char === "*") {
381
+ const name = value.startsWith("**", cursor) ? "bold" : "italic";
382
+ const width = name === "bold" ? 2 : 1;
383
+ const current = stack.findLast((frame) => frame.kind === "emphasis" && frame.node.name === name);
384
+ if (current && !isSpace(value.charAt(cursor - 1))) {
385
+ flush();
386
+ close(current);
387
+ cursor += width;
388
+ continue;
389
+ }
390
+ if (!current && !isSpace(value.charAt(cursor + width))) {
391
+ flush();
392
+ open(element(name, [], line, sourceRange(map, index + cursor, index + cursor + width)), "emphasis");
393
+ cursor += width;
394
+ continue;
395
+ }
396
+ }
397
+ buffer += char;
398
+ cursor++;
399
+ }
400
+ flush();
401
+ };
402
+ const feed = (tokens, line, dropBlankText, map) => {
403
+ const isExprAt = (index) => tokens[index]?.type === "expr";
404
+ for (const [index, token] of tokens.entries()) switch (token.type) {
405
+ case "open": {
406
+ const node = element(token.name, token.props, line, token);
407
+ if (token.selfClosing) {
408
+ node.closeEnd = token.end;
409
+ append(node);
410
+ } else open(node, "tag");
411
+ break;
412
+ }
413
+ case "close": {
414
+ const frame = stack.findLast((candidate) => candidate.kind === "tag" && candidate.node.name === token.name);
415
+ if (frame) {
416
+ frame.node.closeEnd = token.end;
417
+ close(frame);
418
+ } else repairs.push({
419
+ end: token.end,
420
+ line,
421
+ message: `Dropped stray </${token.name}>`,
422
+ start: token.start
423
+ });
424
+ break;
425
+ }
426
+ case "expr":
427
+ append({
428
+ at: token.at,
429
+ code: token.code,
430
+ line,
431
+ type: "expr"
432
+ });
433
+ break;
434
+ case "text": if (!dropBlankText || token.value.trim() !== "" || isExprAt(index - 1) && isExprAt(index + 1)) feedText(token.value, line, map, token.index);
435
+ }
436
+ };
437
+ /**
438
+ * A Markdown block (heading, paragraph, list item) whose content is inline
439
+ * Pickle. `map` gives the source offsets of `content`.
440
+ */
441
+ const inlineBlock = (name, props, content, line, final, map) => {
442
+ const range = sourceRange(map, 0, content.length);
443
+ const frame = open(element(name, props, line, range), "block");
444
+ const inline = tokenizeInline(content, final, map);
445
+ for (const message of inline.problems) errors.push({
446
+ end: range.end,
447
+ line,
448
+ message,
449
+ start: range.start
450
+ });
451
+ if (inline.incomplete !== null) repairs.push({
452
+ line,
453
+ message: `Dropped incomplete ${inline.incomplete}`
454
+ });
455
+ feed(inline.tokens, line, false, map);
456
+ close(frame);
457
+ };
458
+ const finish = () => {
459
+ for (const frame of stack) if (frame.kind === "tag") repairs.push({
460
+ end: frame.node.end,
461
+ line: frame.node.line,
462
+ message: `Closed unclosed <${frame.node.name}>`,
463
+ start: frame.node.start
464
+ });
465
+ stack.length = 0;
466
+ return root.children;
467
+ };
468
+ return {
469
+ append,
470
+ close,
471
+ closeEmphasis,
472
+ feed,
473
+ finish,
474
+ inlineBlock,
475
+ insideTag,
476
+ isOpen,
477
+ open
478
+ };
479
+ }
480
+ const HEADING = /^(#{1,6})\s+(.*)$/;
481
+ const LIST_ITEM = /^([-*+]|\d+[.)])\s+(.*)$/;
482
+ const DIVIDER = /^(?:-{3,}|\*{3,})$/;
483
+ const TAG_LINE = /^<\/?[A-Za-z]/;
484
+ const STATEMENT_PREFIX = "{@body";
485
+ const headingSizes = [
486
+ "xl",
487
+ "lg",
488
+ "md"
489
+ ];
490
+ /**
491
+ * Parses Pickle: Markdown blocks, catalog tags and `{@body ...}` statements.
492
+ *
493
+ * Never throws. Partial input (a response that is still being written) is
494
+ * repaired: open elements are closed and a trailing incomplete tag, expression
495
+ * or statement is dropped. Each fix is recorded in `repairs`.
496
+ *
497
+ * An unclosed tag, `{` or statement stops at the next tag line or `{@body`
498
+ * line rather than swallowing the rest of the document; a tag also stops at a
499
+ * blank line, unless one of its attributes has a `{` still open. Since that text can't be
500
+ * finished by more streaming, it is reported in `errors`.
501
+ */
502
+ function parsePickle(source) {
503
+ const lines = source.split(/\r?\n/);
504
+ const lineStarts = [0];
505
+ for (let newline = source.indexOf("\n"); newline !== -1; newline = source.indexOf("\n", newline + 1)) lineStarts.push(newline + 1);
506
+ /** Where line `at` (0-based) starts once trimmed. */
507
+ const trimmedStart = (at) => {
508
+ const raw = lines[at] ?? "";
509
+ return (lineStarts[at] ?? 0) + raw.length - raw.trimStart().length;
510
+ };
511
+ const repairs = [];
512
+ const errors = [];
513
+ const statements = [];
514
+ const builder = createTreeBuilder(repairs, errors);
515
+ let paragraph = null;
516
+ let list = null;
517
+ /** `final` unless the paragraph ends the input, where more may still stream in. */
518
+ const flushParagraph = (final = true) => {
519
+ if (paragraph !== null) {
520
+ builder.inlineBlock("text", [], paragraph.lines.join(" "), paragraph.line, final, paragraph.map);
521
+ paragraph = null;
522
+ }
523
+ };
524
+ const endList = () => {
525
+ if (list !== null) {
526
+ builder.close(list.frame);
527
+ list = null;
528
+ }
529
+ };
530
+ const endBlocks = (final = true) => {
531
+ flushParagraph(final);
532
+ endList();
533
+ };
534
+ /** Whether line `at` can continue an unclosed tag or statement: a new block can't. */
535
+ const continues = (at, blankLines) => {
536
+ const next = (lines[at] ?? "").trim();
537
+ return (blankLines || next !== "") && !next.startsWith(STATEMENT_PREFIX) && !TAG_LINE.test(next);
538
+ };
539
+ let index = 0;
540
+ while (index < lines.length) {
541
+ const line = index + 1;
542
+ const trimmed = (lines[index] ?? "").trim();
543
+ const lineMap = [[0, trimmedStart(index)]];
544
+ if (trimmed === "") {
545
+ endBlocks();
546
+ index++;
547
+ continue;
548
+ }
549
+ if (trimmed.startsWith(STATEMENT_PREFIX)) {
550
+ endBlocks();
551
+ let text = trimmed;
552
+ const map = [...lineMap];
553
+ let end = scanBalanced(text, 0);
554
+ while (end === -1 && index + 1 < lines.length && continues(index + 1, true)) {
555
+ index++;
556
+ extendMap(map, text.length + 1, lineStarts[index] ?? 0);
557
+ text += `\n${lines[index] ?? ""}`;
558
+ end = scanBalanced(text, 0);
559
+ }
560
+ index++;
561
+ if (end === -1 && index < lines.length) {
562
+ const range = sourceRange(map, 0, text.length);
563
+ errors.push({
564
+ end: range.end,
565
+ line,
566
+ message: "{@body} line has no closing `}` before the next tag or {@body} line, so it was removed. Close it on its own line",
567
+ start: range.start
568
+ });
569
+ continue;
570
+ }
571
+ if (end === -1) {
572
+ repairs.push({
573
+ line,
574
+ message: "Dropped incomplete {@body} statement"
575
+ });
576
+ continue;
577
+ }
578
+ const inner = text.slice(6, end - 1);
579
+ const code = inner.trim();
580
+ const at = sliceMap(map, 6 + inner.length - inner.trimStart().length);
581
+ const range = sourceRange(map, 0, end);
582
+ if (code !== "" && builder.insideTag()) builder.append({
583
+ at,
584
+ code,
585
+ line,
586
+ ...range,
587
+ type: "statement"
588
+ });
589
+ else if (code !== "") statements.push({
590
+ at,
591
+ code,
592
+ line,
593
+ ...range
594
+ });
595
+ continue;
596
+ }
597
+ if (TAG_LINE.test(trimmed)) {
598
+ endBlocks();
599
+ let text = trimmed;
600
+ const map = [...lineMap];
601
+ let result = tokenizeInline(text, false, map);
602
+ while (result.incomplete !== null && index + 1 < lines.length && continues(index + 1, result.inBraces)) {
603
+ index++;
604
+ extendMap(map, text.length + 1, trimmedStart(index));
605
+ text += `\n${(lines[index] ?? "").trim()}`;
606
+ result = tokenizeInline(text, false, map);
607
+ }
608
+ index++;
609
+ if (result.incomplete !== null && index < lines.length) result = tokenizeInline(text, true, map);
610
+ const range = sourceRange(map, 0, text.length);
611
+ for (const message of result.problems) errors.push({
612
+ end: range.end,
613
+ line,
614
+ message,
615
+ start: range.start
616
+ });
617
+ if (result.incomplete !== null) repairs.push({
618
+ line,
619
+ message: `Dropped incomplete ${result.incomplete}`
620
+ });
621
+ builder.feed(result.tokens, line, true, map);
622
+ builder.closeEmphasis();
623
+ continue;
624
+ }
625
+ index++;
626
+ /** The map of `content`, which ends the trimmed line. */
627
+ const contentMap = (content) => sliceMap(lineMap, trimmed.length - content.length);
628
+ const heading = HEADING.exec(trimmed);
629
+ if (heading) {
630
+ endBlocks();
631
+ const level = (heading[1] ?? "#").length;
632
+ const size = headingSizes[Math.min(level, headingSizes.length) - 1];
633
+ const content = heading[2] ?? "";
634
+ builder.inlineBlock("title", [{
635
+ name: "size",
636
+ value: {
637
+ kind: "literal",
638
+ value: size ?? "md"
639
+ }
640
+ }], content, line, true, contentMap(content));
641
+ continue;
642
+ }
643
+ if (DIVIDER.test(trimmed)) {
644
+ endBlocks();
645
+ builder.append(element("divider", [], line, sourceRange(lineMap, 0, trimmed.length)));
646
+ continue;
647
+ }
648
+ const item = LIST_ITEM.exec(trimmed);
649
+ if (item) {
650
+ flushParagraph();
651
+ const ordered = /\d/.test(item[1] ?? "");
652
+ if (list === null || list.ordered !== ordered || !builder.isOpen(list.frame)) {
653
+ endList();
654
+ const props = ordered ? [{
655
+ name: "ordered",
656
+ value: {
657
+ kind: "literal",
658
+ value: true
659
+ }
660
+ }] : [];
661
+ list = {
662
+ frame: builder.open(element("list", props, line, sourceRange(lineMap, 0, trimmed.length)), "block"),
663
+ ordered
664
+ };
665
+ }
666
+ const content = item[2] ?? "";
667
+ builder.inlineBlock("item", [], content, line, true, contentMap(content));
668
+ continue;
669
+ }
670
+ endList();
671
+ paragraph ??= {
672
+ line,
673
+ lines: [],
674
+ map: [],
675
+ width: 0
676
+ };
677
+ extendMap(paragraph.map, paragraph.width + (paragraph.lines.length > 0 ? 1 : 0), sourceOffset(lineMap, 0));
678
+ paragraph.width += trimmed.length + (paragraph.lines.length > 0 ? 1 : 0);
679
+ paragraph.lines.push(trimmed);
680
+ }
681
+ endBlocks(false);
682
+ return {
683
+ children: builder.finish(),
684
+ errors,
685
+ repairs,
686
+ statements
687
+ };
688
+ }
689
+ //#endregion
690
+ export { parsePickle };