@rohal12/spindle 0.52.9 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +5 -1
  2. package/dist/pkg/format.js +1 -1
  3. package/dist/pkg/headless.js +6484 -2558
  4. package/dist/pkg/story-variables.js +4009 -940
  5. package/dist/pkg/tooling.js +13 -1
  6. package/package.json +6 -1
  7. package/src/class-registry.ts +252 -250
  8. package/src/components/App.tsx +2 -0
  9. package/src/components/Passage.tsx +3 -3
  10. package/src/components/PassageDialog.tsx +2 -4
  11. package/src/components/RuntimeErrors.tsx +46 -0
  12. package/src/components/StoryInterface.tsx +2 -4
  13. package/src/components/macros/Computed.tsx +5 -2
  14. package/src/components/macros/Do.tsx +4 -2
  15. package/src/components/macros/Goto.tsx +13 -3
  16. package/src/components/macros/Include.tsx +2 -3
  17. package/src/components/macros/MacroError.tsx +14 -7
  18. package/src/components/macros/SaveManager.tsx +2 -2
  19. package/src/components/macros/VarDisplay.tsx +1 -1
  20. package/src/components/macros/Widget.tsx +5 -33
  21. package/src/index.tsx +57 -37
  22. package/src/interpolation.ts +4 -5
  23. package/src/markup/ast.ts +11 -226
  24. package/src/markup/code-attributes.ts +3 -3
  25. package/src/markup/code-end.ts +121 -0
  26. package/src/markup/parse.ts +118 -0
  27. package/src/markup/render.tsx +1 -1
  28. package/src/markup/spindle.d.peggy.ts +24 -0
  29. package/src/markup/spindle.peggy +463 -0
  30. package/src/markup/tokens.ts +95 -0
  31. package/src/markup/validate.ts +219 -0
  32. package/src/parser.ts +19 -4
  33. package/src/runtime-errors.ts +65 -0
  34. package/src/saves/format.ts +199 -0
  35. package/src/saves/save-manager.ts +45 -63
  36. package/src/saves/types.ts +71 -88
  37. package/src/store.ts +167 -87
  38. package/src/story-init.ts +3 -5
  39. package/src/story-variables.ts +18 -4
  40. package/src/structural.ts +266 -57
  41. package/src/styles.css +45 -0
  42. package/src/tooling.ts +50 -0
  43. package/src/types-drift-check.ts +28 -1
  44. package/src/utils/object-path.ts +13 -12
  45. package/src/utils/stable-key.ts +2 -1
  46. package/src/utils/value-kinds.ts +45 -0
  47. package/src/widgets/widget-def.ts +60 -0
  48. package/types/index.d.ts +32 -8
  49. package/types/tooling.d.ts +42 -0
  50. package/src/markup/tokenizer.ts +0 -1112
@@ -1,3 +1,3419 @@
1
+ //#region src/markup/spindle.peggy
2
+ var LB = String.fromCharCode(123);
3
+ var RB = String.fromCharCode(125);
4
+ var SIGIL_SCOPES$1 = {
5
+ $: "variable",
6
+ _: "temporary",
7
+ "@": "local",
8
+ "%": "transient"
9
+ };
10
+ var VOID_TAGS = new Set([
11
+ "area",
12
+ "base",
13
+ "br",
14
+ "col",
15
+ "embed",
16
+ "hr",
17
+ "img",
18
+ "input",
19
+ "link",
20
+ "meta",
21
+ "param",
22
+ "source",
23
+ "track",
24
+ "wbr"
25
+ ]);
26
+ var BRANCH_PARENT = {
27
+ elseif: "if",
28
+ else: "if",
29
+ case: "switch",
30
+ default: "switch",
31
+ next: "timed"
32
+ };
33
+ var BRANCHING = new Set([
34
+ "if",
35
+ "switch",
36
+ "timed"
37
+ ]);
38
+ var LINK_SEPARATORS = [
39
+ ["|", false],
40
+ ["->", false],
41
+ ["<-", true]
42
+ ];
43
+ var NO_SELECTORS = {};
44
+ function parseLink(inner) {
45
+ for (const [separator, targetFirst] of LINK_SEPARATORS) {
46
+ const idx = inner.indexOf(separator);
47
+ if (idx === -1) continue;
48
+ const before = inner.slice(0, idx).trim();
49
+ const after = inner.slice(idx + separator.length).trim();
50
+ return targetFirst ? {
51
+ display: after,
52
+ target: before
53
+ } : {
54
+ display: before,
55
+ target: after
56
+ };
57
+ }
58
+ const trimmed = inner.trim();
59
+ return {
60
+ display: trimmed,
61
+ target: trimmed
62
+ };
63
+ }
64
+ function quoteArg(value) {
65
+ return "\"" + value.replace(/[\\"]/g, "\\$&") + "\"";
66
+ }
67
+ function withSelectors(target, from) {
68
+ if (from.className) target.className = from.className;
69
+ if (from.id) target.id = from.id;
70
+ return target;
71
+ }
72
+ function selectorsOf(parts) {
73
+ const classes = [];
74
+ let id = "";
75
+ for (const [prefix, name] of parts) {
76
+ if (!name) continue;
77
+ if (prefix === ".") classes.push(name);
78
+ else id = name;
79
+ }
80
+ return withSelectors({}, {
81
+ className: classes.join(" "),
82
+ id
83
+ });
84
+ }
85
+ /** How a tag reads in an error message. */
86
+ function label(tag) {
87
+ if (tag.lex === "html") return (tag.isClose ? "</" : "<") + tag.tag + ">";
88
+ return LB + (tag.isClose ? "/" : "") + tag.name + RB;
89
+ }
90
+ function closerOf(tag) {
91
+ return tag.lex === "html" ? "</" + tag.tag + ">" : LB + "/" + tag.name + RB;
92
+ }
93
+ function at(loc) {
94
+ return "line " + loc.start.line + ", column " + loc.start.column;
95
+ }
96
+ function sameName(open, close) {
97
+ return open.lex === close.lex && open.lower === close.lower;
98
+ }
99
+ /** A lexeme: what one token of the flat stream holds, with its span. */
100
+ function lexeme(lex, loc, fields) {
101
+ fields.lex = lex;
102
+ fields.start = loc.start.offset;
103
+ fields.end = loc.end.offset;
104
+ fields.loc = loc;
105
+ return fields;
106
+ }
107
+ /** The AST node of a lexeme that stands alone (not a macro or element). */
108
+ function nodeOf(x) {
109
+ switch (x.lex) {
110
+ case "text": return {
111
+ type: "text",
112
+ value: x.value
113
+ };
114
+ case "link": return withSelectors({
115
+ type: "macro",
116
+ name: "link",
117
+ rawArgs: quoteArg(x.display) + " " + quoteArg(x.target),
118
+ children: []
119
+ }, x);
120
+ case "variable": return withSelectors({
121
+ type: "variable",
122
+ name: x.name,
123
+ scope: x.scope
124
+ }, x);
125
+ case "expression": return withSelectors({
126
+ type: "expression",
127
+ expression: x.expression
128
+ }, x);
129
+ default: return x;
130
+ }
131
+ }
132
+ /** The token of a lexeme (tokens.ts Token). */
133
+ function tokenOf(x) {
134
+ const span = {
135
+ start: x.start,
136
+ end: x.end
137
+ };
138
+ switch (x.lex) {
139
+ case "text": return {
140
+ type: "text",
141
+ value: x.value,
142
+ ...span
143
+ };
144
+ case "link": return withSelectors({
145
+ type: "link",
146
+ display: x.display,
147
+ target: x.target,
148
+ ...span
149
+ }, x);
150
+ case "variable": return withSelectors({
151
+ type: "variable",
152
+ name: x.name,
153
+ scope: x.scope,
154
+ ...span
155
+ }, x);
156
+ case "expression": return withSelectors({
157
+ type: "expression",
158
+ expression: x.expression,
159
+ ...span
160
+ }, x);
161
+ case "macro": return withSelectors({
162
+ type: "macro",
163
+ name: x.name,
164
+ rawArgs: x.rawArgs,
165
+ isClose: x.isClose,
166
+ ...span
167
+ }, x);
168
+ case "html": return {
169
+ type: "html",
170
+ tag: x.tag,
171
+ attributes: x.attributes,
172
+ isClose: x.isClose,
173
+ isSelfClose: x.isSelfClose,
174
+ ...span
175
+ };
176
+ }
177
+ }
178
+ /**
179
+ * Flatten items (lexemes, nodes, arrays of them, nulls) and join adjacent
180
+ * text, except escaped braces, which stay a text item of their own.
181
+ */
182
+ function joinText(items, convert) {
183
+ const out = [];
184
+ let last = null;
185
+ const add = (item) => {
186
+ if (item === null || item === void 0) return;
187
+ if (Array.isArray(item)) return item.forEach(add);
188
+ if (item.lex === "text" && !item.escape && last && last.lex === "text" && !last.escape && last.end === item.start) {
189
+ last.value += item.value;
190
+ last.end = item.end;
191
+ return;
192
+ }
193
+ out.push(item);
194
+ last = item;
195
+ };
196
+ add(items);
197
+ return convert ? out.map(convert) : out;
198
+ }
199
+ var peg$SyntaxError = class extends SyntaxError {
200
+ constructor(message, expected, found, location) {
201
+ super(message);
202
+ this.expected = expected;
203
+ this.found = found;
204
+ this.location = location;
205
+ this.name = "SyntaxError";
206
+ }
207
+ format(sources) {
208
+ let str = "Error: " + this.message;
209
+ if (this.location) {
210
+ let src = null;
211
+ const st = sources.find((s) => s.source === this.location.source);
212
+ if (st) src = st.text.split(/\r\n|\n|\r/g);
213
+ const s = this.location.start;
214
+ const offset_s = this.location.source && typeof this.location.source.offset === "function" ? this.location.source.offset(s) : s;
215
+ const loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
216
+ if (src) {
217
+ const e = this.location.end;
218
+ const filler = "".padEnd(offset_s.line.toString().length, " ");
219
+ const line = src[s.line - 1];
220
+ const hatLen = (s.line === e.line ? e.column : line.length + 1) - s.column || 1;
221
+ str += "\n --> " + loc + "\n" + filler + " |\n" + offset_s.line + " | " + line + "\n" + filler + " | " + "".padEnd(s.column - 1, " ") + "".padEnd(hatLen, "^");
222
+ } else str += "\n at " + loc;
223
+ }
224
+ return str;
225
+ }
226
+ static buildMessage(expected, found) {
227
+ function hex(ch) {
228
+ return ch.codePointAt(0).toString(16).toUpperCase();
229
+ }
230
+ const nonPrintable = Object.prototype.hasOwnProperty.call(RegExp.prototype, "unicode") ? /* @__PURE__ */ new RegExp("[\\p{C}\\p{Mn}\\p{Mc}]", "gu") : null;
231
+ function unicodeEscape(s) {
232
+ if (nonPrintable) return s.replace(nonPrintable, (ch) => "\\u{" + hex(ch) + "}");
233
+ return s;
234
+ }
235
+ function literalEscape(s) {
236
+ return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
237
+ }
238
+ function classEscape(s) {
239
+ return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
240
+ }
241
+ const DESCRIBE_EXPECTATION_FNS = {
242
+ literal(expectation) {
243
+ return "\"" + literalEscape(expectation.text) + "\"";
244
+ },
245
+ class(expectation) {
246
+ const escapedParts = expectation.parts.map((part) => Array.isArray(part) ? classEscape(part[0]) + "-" + classEscape(part[1]) : classEscape(part));
247
+ return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]" + (expectation.unicode ? "u" : "");
248
+ },
249
+ any() {
250
+ return "any character";
251
+ },
252
+ end() {
253
+ return "end of input";
254
+ },
255
+ other(expectation) {
256
+ return expectation.description;
257
+ }
258
+ };
259
+ function describeExpectation(expectation) {
260
+ return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
261
+ }
262
+ function describeExpected(expected) {
263
+ const descriptions = expected.map(describeExpectation);
264
+ descriptions.sort();
265
+ if (descriptions.length > 0) {
266
+ let j = 1;
267
+ for (let i = 1; i < descriptions.length; i++) if (descriptions[i - 1] !== descriptions[i]) {
268
+ descriptions[j] = descriptions[i];
269
+ j++;
270
+ }
271
+ descriptions.length = j;
272
+ }
273
+ switch (descriptions.length) {
274
+ case 1: return descriptions[0];
275
+ case 2: return descriptions[0] + " or " + descriptions[1];
276
+ default: return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1];
277
+ }
278
+ }
279
+ function describeFound(found) {
280
+ return found ? "\"" + literalEscape(found) + "\"" : "end of input";
281
+ }
282
+ return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
283
+ }
284
+ };
285
+ function peg$parse(input, options) {
286
+ options = options !== void 0 ? options : {};
287
+ const peg$FAILED = {};
288
+ const peg$source = options.grammarSource;
289
+ const peg$startRuleFunctions = {
290
+ Markup: peg$parseMarkup,
291
+ Tokens: peg$parseTokens
292
+ };
293
+ let peg$startRuleFunction = peg$parseMarkup;
294
+ const peg$c0 = "\\";
295
+ const peg$c1 = "{";
296
+ const peg$c2 = "<";
297
+ const peg$c3 = "[";
298
+ const peg$c4 = "[[";
299
+ const peg$c5 = "]]";
300
+ const peg$c6 = " ";
301
+ const peg$c7 = "}";
302
+ const peg$c8 = "/";
303
+ const peg$c9 = ">";
304
+ const peg$c10 = "</";
305
+ const peg$c11 = "=";
306
+ const peg$c12 = "\"";
307
+ const peg$c13 = "'";
308
+ const peg$r0 = /^[^\\{[<]/;
309
+ const peg$r1 = /^[{}]/;
310
+ const peg$r2 = /^[$_@%(!\/a-zA-Z]/;
311
+ const peg$r3 = /^[.#]/;
312
+ const peg$r4 = /^[a-zA-Z0-9_\-]/;
313
+ const peg$r5 = /^[$_@%]/;
314
+ const peg$r6 = /^[a-zA-Z0-9_.]/;
315
+ const peg$r7 = /^[$_@%(!]/;
316
+ const peg$r8 = /^[a-zA-Z]/;
317
+ const peg$r9 = /^[^ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF}]/;
318
+ const peg$r10 = /^[\t-\r \/>\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
319
+ const peg$r11 = /^[a-zA-Z0-9\-]/;
320
+ const peg$r12 = /^[\t-\r "'\/<->{}\xA0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\uFEFF]/;
321
+ const peg$r13 = /^[^"]/;
322
+ const peg$r14 = /^[^']/;
323
+ const peg$r15 = /^["']/;
324
+ const peg$r16 = /^[^ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF>]/;
325
+ const peg$r17 = /^[ \t\n\r\v\f\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
326
+ const peg$e0 = peg$anyExpectation();
327
+ const peg$e1 = peg$classExpectation([
328
+ "\\",
329
+ "{",
330
+ "[",
331
+ "<"
332
+ ], true, false, false);
333
+ const peg$e2 = peg$literalExpectation("\\", false);
334
+ const peg$e3 = peg$classExpectation(["{", "}"], false, false, false);
335
+ const peg$e4 = peg$literalExpectation("{", false);
336
+ const peg$e5 = peg$classExpectation([
337
+ "$",
338
+ "_",
339
+ "@",
340
+ "%",
341
+ "(",
342
+ "!",
343
+ "/",
344
+ ["a", "z"],
345
+ ["A", "Z"]
346
+ ], false, false, false);
347
+ const peg$e6 = peg$literalExpectation("<", false);
348
+ const peg$e7 = peg$literalExpectation("[", false);
349
+ const peg$e8 = peg$literalExpectation("[[", false);
350
+ const peg$e9 = peg$literalExpectation("]]", false);
351
+ const peg$e10 = peg$literalExpectation(" ", false);
352
+ const peg$e11 = peg$classExpectation([".", "#"], false, false, false);
353
+ const peg$e12 = peg$classExpectation([
354
+ ["a", "z"],
355
+ ["A", "Z"],
356
+ ["0", "9"],
357
+ "_",
358
+ "-"
359
+ ], false, false, false);
360
+ const peg$e13 = peg$classExpectation([
361
+ "$",
362
+ "_",
363
+ "@",
364
+ "%"
365
+ ], false, false, false);
366
+ const peg$e14 = peg$classExpectation([
367
+ ["a", "z"],
368
+ ["A", "Z"],
369
+ ["0", "9"],
370
+ "_",
371
+ "."
372
+ ], false, false, false);
373
+ const peg$e15 = peg$literalExpectation("}", false);
374
+ const peg$e16 = peg$classExpectation([
375
+ "$",
376
+ "_",
377
+ "@",
378
+ "%",
379
+ "(",
380
+ "!"
381
+ ], false, false, false);
382
+ const peg$e17 = peg$literalExpectation("/", false);
383
+ const peg$e18 = peg$classExpectation([["a", "z"], ["A", "Z"]], false, false, false);
384
+ const peg$e19 = peg$classExpectation([
385
+ " ",
386
+ " ",
387
+ "\n",
388
+ "\r",
389
+ "\v",
390
+ "\f",
391
+ "\xA0",
392
+ " ",
393
+ [" ", " "],
394
+ "\u2028",
395
+ "\u2029",
396
+ " ",
397
+ " ",
398
+ " ",
399
+ "",
400
+ "}"
401
+ ], true, false, false);
402
+ const peg$e20 = peg$literalExpectation(">", false);
403
+ const peg$e21 = peg$classExpectation([
404
+ [" ", "\r"],
405
+ " ",
406
+ "/",
407
+ ">",
408
+ "\xA0",
409
+ " ",
410
+ [" ", " "],
411
+ ["\u2028", "\u2029"],
412
+ " ",
413
+ " ",
414
+ " ",
415
+ ""
416
+ ], false, false, false);
417
+ const peg$e22 = peg$literalExpectation("</", false);
418
+ const peg$e23 = peg$classExpectation([
419
+ ["a", "z"],
420
+ ["A", "Z"],
421
+ ["0", "9"],
422
+ "-"
423
+ ], false, false, false);
424
+ const peg$e24 = peg$classExpectation([
425
+ [" ", "\r"],
426
+ " ",
427
+ "\"",
428
+ "'",
429
+ "/",
430
+ ["<", ">"],
431
+ "{",
432
+ "}",
433
+ "\xA0",
434
+ " ",
435
+ [" ", " "],
436
+ ["\u2028", "\u2029"],
437
+ " ",
438
+ " ",
439
+ " ",
440
+ ""
441
+ ], false, false, false);
442
+ const peg$e25 = peg$literalExpectation("=", false);
443
+ const peg$e26 = peg$literalExpectation("\"", false);
444
+ const peg$e27 = peg$classExpectation(["\""], true, false, false);
445
+ const peg$e28 = peg$literalExpectation("'", false);
446
+ const peg$e29 = peg$classExpectation(["'"], true, false, false);
447
+ const peg$e30 = peg$classExpectation(["\"", "'"], false, false, false);
448
+ const peg$e31 = peg$classExpectation([
449
+ " ",
450
+ " ",
451
+ "\n",
452
+ "\r",
453
+ "\v",
454
+ "\f",
455
+ "\xA0",
456
+ " ",
457
+ [" ", " "],
458
+ "\u2028",
459
+ "\u2029",
460
+ " ",
461
+ " ",
462
+ " ",
463
+ "",
464
+ ">"
465
+ ], true, false, false);
466
+ const peg$e32 = peg$classExpectation([
467
+ " ",
468
+ " ",
469
+ "\n",
470
+ "\r",
471
+ "\v",
472
+ "\f",
473
+ "\xA0",
474
+ " ",
475
+ [" ", " "],
476
+ "\u2028",
477
+ "\u2029",
478
+ " ",
479
+ " ",
480
+ " ",
481
+ ""
482
+ ], false, false, false);
483
+ function peg$f0(nodes) {
484
+ return nodes;
485
+ }
486
+ function peg$f1(tag) {
487
+ fail(label(tag) + " must be directly inside {" + BRANCH_PARENT[tag.lower] + RB, tag.loc);
488
+ }
489
+ function peg$f2(tag) {
490
+ fail(label(tag) + " closes nothing: no " + (tag.lex === "html" ? "<" + tag.tag + ">" : LB + tag.name + RB) + " is open here", tag.loc);
491
+ }
492
+ function peg$f3(items) {
493
+ return joinText(items, nodeOf);
494
+ }
495
+ function peg$f4(tag) {
496
+ return tag.kind === "open";
497
+ }
498
+ function peg$f5(tag) {
499
+ rawName = tag.lower;
500
+ return hooks.isRaw(rawName);
501
+ }
502
+ function peg$f6(tag, b) {
503
+ return b;
504
+ }
505
+ function peg$f7(tag) {
506
+ return hooks.isBlock(tag.lower);
507
+ }
508
+ function peg$f8(tag, b) {
509
+ return b;
510
+ }
511
+ function peg$f9(tag) {
512
+ return null;
513
+ }
514
+ function peg$f10(tag, body) {
515
+ return macroNode(tag, body);
516
+ }
517
+ function peg$f11() {
518
+ return !textMode;
519
+ }
520
+ function peg$f12(open) {
521
+ return !open.isSelfClose;
522
+ }
523
+ function peg$f13(open, b) {
524
+ return b;
525
+ }
526
+ function peg$f14(open, body) {
527
+ const node = {
528
+ type: "html",
529
+ tag: open.tag,
530
+ attributes: open.attributes,
531
+ children: []
532
+ };
533
+ if (body) {
534
+ checkBody(open, body);
535
+ node.children = body.children;
536
+ }
537
+ return node;
538
+ }
539
+ function peg$f15(children, branches, end) {
540
+ return {
541
+ children,
542
+ branches,
543
+ end
544
+ };
545
+ }
546
+ function peg$f16(tag, children) {
547
+ return {
548
+ tag,
549
+ children
550
+ };
551
+ }
552
+ function peg$f17() {
553
+ return null;
554
+ }
555
+ function peg$f18(tag) {
556
+ return tag.kind === "branch";
557
+ }
558
+ function peg$f19(tag) {
559
+ return tag;
560
+ }
561
+ function peg$f20(tag) {
562
+ return tag.kind === "close";
563
+ }
564
+ function peg$f21(tag) {
565
+ return tag;
566
+ }
567
+ function peg$f22() {
568
+ return !textMode;
569
+ }
570
+ function peg$f23(tag) {
571
+ return tag;
572
+ }
573
+ function peg$f24(start) {
574
+ return skipTo(hooks.rawBodyEnd(input, start, rawName));
575
+ }
576
+ function peg$f25(start) {
577
+ return skipEnd - offset();
578
+ }
579
+ function peg$f26(start, body, end) {
580
+ return {
581
+ children: body ? [{
582
+ type: "text",
583
+ value: body
584
+ }] : [],
585
+ branches: [],
586
+ end
587
+ };
588
+ }
589
+ function peg$f27() {
590
+ return {
591
+ children: [],
592
+ branches: [],
593
+ end: null
594
+ };
595
+ }
596
+ function peg$f28(items) {
597
+ return joinText(items, tokenOf);
598
+ }
599
+ function peg$f29() {
600
+ return !textMode;
601
+ }
602
+ function peg$f30(tag) {
603
+ return tag;
604
+ }
605
+ function peg$f31() {
606
+ return !textMode;
607
+ }
608
+ function peg$f32(tag) {
609
+ return VOID_TAGS.has(tag.lower) ? null : tag;
610
+ }
611
+ function peg$f33(tag) {
612
+ rawName = tag.lower;
613
+ return tag.kind === "open" && hooks.isRaw(rawName);
614
+ }
615
+ function peg$f34(tag, b) {
616
+ return b;
617
+ }
618
+ function peg$f35(tag, body) {
619
+ return body ? [tag, body] : tag;
620
+ }
621
+ function peg$f36(start) {
622
+ return skipTo(hooks.rawBodyEnd(input, start, rawName));
623
+ }
624
+ function peg$f37(start) {
625
+ return skipEnd - offset();
626
+ }
627
+ function peg$f38(start, body, end) {
628
+ const loc = {
629
+ start: { offset: start },
630
+ end: { offset: start + body.length }
631
+ };
632
+ return [body ? lexeme("text", loc, { value: body }) : null, end];
633
+ }
634
+ function peg$f39() {
635
+ return offset();
636
+ }
637
+ function peg$f40(value) {
638
+ return lexeme("text", location(), { value });
639
+ }
640
+ function peg$f41(bs) {
641
+ return bs.length % 2 === 1;
642
+ }
643
+ function peg$f42(bs, c) {
644
+ return c;
645
+ }
646
+ function peg$f43(bs, brace) {
647
+ const loc = location();
648
+ const next = input.charAt(loc.end.offset);
649
+ if (textMode) {
650
+ if (!brace && next !== LB && next !== RB) return lexeme("text", loc, { value: bs });
651
+ const node = lexeme("text", loc, { value: "\\".repeat(bs.length >> 1) + (brace || "") });
652
+ node.escape = true;
653
+ return node;
654
+ }
655
+ if (!brace) return lexeme("text", loc, { value: bs });
656
+ const before = lexeme("text", loc, { value: bs.slice(1) });
657
+ before.end = loc.end.offset - 2;
658
+ const escaped = lexeme("text", loc, { value: brace });
659
+ escaped.start = loc.end.offset - 2;
660
+ escaped.escape = true;
661
+ return bs.length > 1 ? [before, escaped] : escaped;
662
+ }
663
+ function peg$f44() {
664
+ return lexeme("text", location(), { value: LB });
665
+ }
666
+ function peg$f45() {
667
+ return !textMode;
668
+ }
669
+ function peg$f46() {
670
+ return lexeme("text", location(), { value: "<" });
671
+ }
672
+ function peg$f47() {
673
+ return lexeme("text", location(), { value: "[" });
674
+ }
675
+ function peg$f48() {
676
+ return !textMode;
677
+ }
678
+ function peg$f49(s, inner) {
679
+ return withSelectors(lexeme("link", location(), parseLink(inner)), s);
680
+ }
681
+ function peg$f50() {
682
+ return !textMode;
683
+ }
684
+ function peg$f51() {
685
+ fail("Unclosed link: [[ without ]]", location());
686
+ }
687
+ function peg$f52(parts) {
688
+ return selectorsOf(parts);
689
+ }
690
+ function peg$f53() {
691
+ return NO_SELECTORS;
692
+ }
693
+ function peg$f54(prefix, name) {
694
+ return [prefix, name];
695
+ }
696
+ function peg$f55(s, sigil, name) {
697
+ return withSelectors(lexeme("variable", location(), {
698
+ name,
699
+ scope: SIGIL_SCOPES$1[sigil]
700
+ }), s);
701
+ }
702
+ function peg$f56(s, expression) {
703
+ return withSelectors(lexeme("expression", location(), { expression }), s);
704
+ }
705
+ function peg$f57() {
706
+ fail("Unclosed " + LB + input.substr(location().end.offset, 1) + "…: no } ends it", location());
707
+ }
708
+ function peg$f58(start) {
709
+ return skipTo(hooks.closeBrace(input, start, start));
710
+ }
711
+ function peg$f59(start) {
712
+ return skipEnd - offset();
713
+ }
714
+ function peg$f60(start, code) {
715
+ return code;
716
+ }
717
+ function peg$f61(s, start, close, name, nameEnd) {
718
+ return skipTo(hooks.closeBrace(input, nameEnd, start));
719
+ }
720
+ function peg$f62(s, start, close, name, nameEnd) {
721
+ return skipEnd - offset();
722
+ }
723
+ function peg$f63(s, start, close, name, nameEnd, args) {
724
+ return args;
725
+ }
726
+ function peg$f64(s, start, close, name, nameEnd, tail) {
727
+ const loc = location();
728
+ const lower = name.toLowerCase();
729
+ const tag = lexeme("macro", loc, {
730
+ name,
731
+ lower,
732
+ rawArgs: "",
733
+ isClose: !!close
734
+ });
735
+ if (tail === null) fail("Unclosed " + LB + (close ? "/" : "") + name + "…: no } ends the macro", loc);
736
+ tag.rawArgs = tail.trim();
737
+ if (close) {
738
+ if (s !== NO_SELECTORS) fail("A closing tag takes no selectors: " + label(tag), loc);
739
+ if (tag.rawArgs) fail("A closing tag takes no arguments: " + LB + "/" + name + " " + tag.rawArgs + RB, loc);
740
+ tag.kind = "close";
741
+ } else {
742
+ withSelectors(tag, s);
743
+ tag.kind = Object.prototype.hasOwnProperty.call(BRANCH_PARENT, lower) ? "branch" : "open";
744
+ }
745
+ return tag;
746
+ }
747
+ function peg$f65(s) {
748
+ fail("A closing tag starts with a letter after " + LB + "/", location());
749
+ }
750
+ function peg$f66() {
751
+ return !textMode;
752
+ }
753
+ function peg$f67(tag) {
754
+ return VOID_TAGS.has(tag.lower);
755
+ }
756
+ function peg$f68(tag) {
757
+ return null;
758
+ }
759
+ function peg$f69(tag, list, slash) {
760
+ const lower = tag.toLowerCase();
761
+ return lexeme("html", location(), {
762
+ tag,
763
+ lower,
764
+ attributes: attrs(list),
765
+ isClose: false,
766
+ isSelfClose: !!slash || VOID_TAGS.has(lower)
767
+ });
768
+ }
769
+ function peg$f70(tag, bad) {
770
+ const c = input.charAt(bad);
771
+ if (!c) fail("Unclosed tag <" + tag + ": no > ends it (write &lt; for a < that starts no tag)", location());
772
+ const at = {
773
+ source: location().source,
774
+ start: {
775
+ offset: bad,
776
+ line: 0,
777
+ column: 0
778
+ },
779
+ end: {
780
+ offset: bad + 1,
781
+ line: 0,
782
+ column: 0
783
+ }
784
+ };
785
+ fail("Unexpected " + JSON.stringify(c) + " in the tag <" + tag + ">: expected an attribute, or > to end the tag", at);
786
+ }
787
+ function peg$f71(tag) {
788
+ return lexeme("html", location(), {
789
+ tag,
790
+ lower: tag.toLowerCase(),
791
+ attributes: {},
792
+ isClose: true,
793
+ isSelfClose: false
794
+ });
795
+ }
796
+ function peg$f72(name) {
797
+ attrCode = hooks.isCodeAttribute(name);
798
+ return true;
799
+ }
800
+ function peg$f73(name, v) {
801
+ return v;
802
+ }
803
+ function peg$f74(name, value) {
804
+ return [name, value ?? ""];
805
+ }
806
+ function peg$f75(v) {
807
+ return v;
808
+ }
809
+ function peg$f76(v) {
810
+ return v;
811
+ }
812
+ function peg$f77(q) {
813
+ fail("Unclosed attribute value: the " + q + " here has no closing " + q, location());
814
+ }
815
+ function peg$f78() {
816
+ return !attrCode;
817
+ }
818
+ function peg$f79(bs) {
819
+ return bs.length % 2 === 1;
820
+ }
821
+ function peg$f80() {
822
+ return !attrCode;
823
+ }
824
+ function peg$f81(start, nameEnd) {
825
+ return skipTo(hooks.closeBrace(input, nameEnd, start));
826
+ }
827
+ function peg$f82(start, nameEnd) {
828
+ return skipEnd - offset();
829
+ }
830
+ function peg$f83() {
831
+ return !attrCode;
832
+ }
833
+ function peg$f84() {
834
+ return attrCode;
835
+ }
836
+ let peg$currPos = options.peg$currPos | 0;
837
+ let peg$savedPos = peg$currPos;
838
+ const peg$posDetailsCache = [{
839
+ line: 1,
840
+ column: 1
841
+ }];
842
+ let peg$maxFailPos = peg$currPos;
843
+ let peg$maxFailExpected = options.peg$maxFailExpected || [];
844
+ let peg$silentFails = options.peg$silentFails | 0;
845
+ let peg$result;
846
+ if (options.startRule) {
847
+ if (!(options.startRule in peg$startRuleFunctions)) throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
848
+ peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
849
+ }
850
+ function offset() {
851
+ return peg$savedPos;
852
+ }
853
+ function location() {
854
+ return peg$computeLocation(peg$savedPos, peg$currPos);
855
+ }
856
+ function error(message, location) {
857
+ location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos);
858
+ throw peg$buildSimpleError(message, location);
859
+ }
860
+ function peg$getUnicode(pos = peg$currPos) {
861
+ const cp = input.codePointAt(pos);
862
+ if (cp === void 0) return "";
863
+ return String.fromCodePoint(cp);
864
+ }
865
+ function peg$literalExpectation(text, ignoreCase) {
866
+ return {
867
+ type: "literal",
868
+ text,
869
+ ignoreCase
870
+ };
871
+ }
872
+ function peg$classExpectation(parts, inverted, ignoreCase, unicode) {
873
+ return {
874
+ type: "class",
875
+ parts,
876
+ inverted,
877
+ ignoreCase,
878
+ unicode
879
+ };
880
+ }
881
+ function peg$anyExpectation() {
882
+ return { type: "any" };
883
+ }
884
+ function peg$endExpectation() {
885
+ return { type: "end" };
886
+ }
887
+ function peg$computePosDetails(pos) {
888
+ let details = peg$posDetailsCache[pos];
889
+ let p;
890
+ if (details) return details;
891
+ else {
892
+ if (pos >= peg$posDetailsCache.length) p = peg$posDetailsCache.length - 1;
893
+ else {
894
+ p = pos;
895
+ while (!peg$posDetailsCache[--p]);
896
+ }
897
+ details = peg$posDetailsCache[p];
898
+ details = {
899
+ line: details.line,
900
+ column: details.column
901
+ };
902
+ while (p < pos) {
903
+ if (input.charCodeAt(p) === 10) {
904
+ details.line++;
905
+ details.column = 1;
906
+ } else details.column++;
907
+ p++;
908
+ }
909
+ peg$posDetailsCache[pos] = details;
910
+ return details;
911
+ }
912
+ }
913
+ function peg$computeLocation(startPos, endPos, offset) {
914
+ const startPosDetails = peg$computePosDetails(startPos);
915
+ const endPosDetails = peg$computePosDetails(endPos);
916
+ const res = {
917
+ source: peg$source,
918
+ start: {
919
+ offset: startPos,
920
+ line: startPosDetails.line,
921
+ column: startPosDetails.column
922
+ },
923
+ end: {
924
+ offset: endPos,
925
+ line: endPosDetails.line,
926
+ column: endPosDetails.column
927
+ }
928
+ };
929
+ if (offset && peg$source && typeof peg$source.offset === "function") {
930
+ res.start = peg$source.offset(res.start);
931
+ res.end = peg$source.offset(res.end);
932
+ }
933
+ return res;
934
+ }
935
+ function peg$fail(expected) {
936
+ if (peg$currPos < peg$maxFailPos) return;
937
+ if (peg$currPos > peg$maxFailPos) {
938
+ peg$maxFailPos = peg$currPos;
939
+ peg$maxFailExpected = [];
940
+ }
941
+ peg$maxFailExpected.push(expected);
942
+ }
943
+ function peg$buildSimpleError(message, location) {
944
+ return new peg$SyntaxError(message, null, null, location);
945
+ }
946
+ function peg$buildStructuredError(expected, found, location) {
947
+ return new peg$SyntaxError(peg$SyntaxError.buildMessage(expected, found), expected, found, location);
948
+ }
949
+ function peg$parseMarkup() {
950
+ let s0, s1, s2;
951
+ s0 = peg$currPos;
952
+ s1 = peg$parseContent();
953
+ s2 = peg$parseTail();
954
+ if (s2 !== peg$FAILED) {
955
+ peg$savedPos = s0;
956
+ s0 = peg$f0(s1);
957
+ } else {
958
+ peg$currPos = s0;
959
+ s0 = peg$FAILED;
960
+ }
961
+ return s0;
962
+ }
963
+ function peg$parseTail() {
964
+ let s0, s1;
965
+ s0 = peg$currPos;
966
+ peg$silentFails++;
967
+ if (input.length > peg$currPos) {
968
+ s1 = input.charAt(peg$currPos);
969
+ peg$currPos++;
970
+ } else {
971
+ s1 = peg$FAILED;
972
+ if (peg$silentFails === 0) peg$fail(peg$e0);
973
+ }
974
+ peg$silentFails--;
975
+ if (s1 === peg$FAILED) s0 = void 0;
976
+ else {
977
+ peg$currPos = s0;
978
+ s0 = peg$FAILED;
979
+ }
980
+ if (s0 === peg$FAILED) {
981
+ s0 = peg$currPos;
982
+ s1 = peg$parseBranchTag();
983
+ if (s1 !== peg$FAILED) {
984
+ peg$savedPos = s0;
985
+ s1 = peg$f1(s1);
986
+ }
987
+ s0 = s1;
988
+ if (s0 === peg$FAILED) {
989
+ s0 = peg$currPos;
990
+ s1 = peg$parseCloseTag();
991
+ if (s1 !== peg$FAILED) {
992
+ peg$savedPos = s0;
993
+ s1 = peg$f2(s1);
994
+ }
995
+ s0 = s1;
996
+ }
997
+ }
998
+ return s0;
999
+ }
1000
+ function peg$parseContent() {
1001
+ let s0, s1, s2;
1002
+ s0 = peg$currPos;
1003
+ s1 = [];
1004
+ s2 = peg$parseItem();
1005
+ while (s2 !== peg$FAILED) {
1006
+ s1.push(s2);
1007
+ s2 = peg$parseItem();
1008
+ }
1009
+ peg$savedPos = s0;
1010
+ s1 = peg$f3(s1);
1011
+ s0 = s1;
1012
+ return s0;
1013
+ }
1014
+ function peg$parseItem() {
1015
+ let s0;
1016
+ s0 = peg$parseTextRun();
1017
+ if (s0 === peg$FAILED) {
1018
+ s0 = peg$parseEscape();
1019
+ if (s0 === peg$FAILED) {
1020
+ s0 = peg$parseLinkLexeme();
1021
+ if (s0 === peg$FAILED) {
1022
+ s0 = peg$parseHtmlElement();
1023
+ if (s0 === peg$FAILED) {
1024
+ s0 = peg$parseVoidCloser();
1025
+ if (s0 === peg$FAILED) {
1026
+ s0 = peg$parseValue();
1027
+ if (s0 === peg$FAILED) {
1028
+ s0 = peg$parseMacroElement();
1029
+ if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1030
+ }
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ return s0;
1037
+ }
1038
+ function peg$parseMacroElement() {
1039
+ let s0, s1, s2, s3, s4, s5;
1040
+ s0 = peg$currPos;
1041
+ s1 = peg$parseMacroTag();
1042
+ if (s1 !== peg$FAILED) {
1043
+ peg$savedPos = peg$currPos;
1044
+ s2 = peg$f4(s1);
1045
+ if (s2) s2 = void 0;
1046
+ else s2 = peg$FAILED;
1047
+ if (s2 !== peg$FAILED) {
1048
+ s3 = peg$currPos;
1049
+ peg$savedPos = peg$currPos;
1050
+ s4 = peg$f5(s1);
1051
+ if (s4) s4 = void 0;
1052
+ else s4 = peg$FAILED;
1053
+ if (s4 !== peg$FAILED) {
1054
+ s5 = peg$parseRawBody();
1055
+ if (s5 !== peg$FAILED) {
1056
+ peg$savedPos = s3;
1057
+ s3 = peg$f6(s1, s5);
1058
+ } else {
1059
+ peg$currPos = s3;
1060
+ s3 = peg$FAILED;
1061
+ }
1062
+ } else {
1063
+ peg$currPos = s3;
1064
+ s3 = peg$FAILED;
1065
+ }
1066
+ if (s3 === peg$FAILED) {
1067
+ s3 = peg$currPos;
1068
+ peg$savedPos = peg$currPos;
1069
+ s4 = peg$f7(s1);
1070
+ if (s4) s4 = void 0;
1071
+ else s4 = peg$FAILED;
1072
+ if (s4 !== peg$FAILED) {
1073
+ s5 = peg$parseBlockBody();
1074
+ if (s5 !== peg$FAILED) {
1075
+ peg$savedPos = s3;
1076
+ s3 = peg$f8(s1, s5);
1077
+ } else {
1078
+ peg$currPos = s3;
1079
+ s3 = peg$FAILED;
1080
+ }
1081
+ } else {
1082
+ peg$currPos = s3;
1083
+ s3 = peg$FAILED;
1084
+ }
1085
+ if (s3 === peg$FAILED) {
1086
+ s3 = peg$currPos;
1087
+ s4 = "";
1088
+ peg$savedPos = s3;
1089
+ s4 = peg$f9(s1);
1090
+ s3 = s4;
1091
+ }
1092
+ }
1093
+ if (s3 !== peg$FAILED) {
1094
+ peg$savedPos = s0;
1095
+ s0 = peg$f10(s1, s3);
1096
+ } else {
1097
+ peg$currPos = s0;
1098
+ s0 = peg$FAILED;
1099
+ }
1100
+ } else {
1101
+ peg$currPos = s0;
1102
+ s0 = peg$FAILED;
1103
+ }
1104
+ } else {
1105
+ peg$currPos = s0;
1106
+ s0 = peg$FAILED;
1107
+ }
1108
+ return s0;
1109
+ }
1110
+ function peg$parseHtmlElement() {
1111
+ let s0, s1, s2, s3, s4, s5;
1112
+ s0 = peg$currPos;
1113
+ peg$savedPos = peg$currPos;
1114
+ s1 = peg$f11();
1115
+ if (s1) s1 = void 0;
1116
+ else s1 = peg$FAILED;
1117
+ if (s1 !== peg$FAILED) {
1118
+ s2 = peg$parseHtmlOpen();
1119
+ if (s2 !== peg$FAILED) {
1120
+ s3 = peg$currPos;
1121
+ peg$savedPos = peg$currPos;
1122
+ s4 = peg$f12(s2);
1123
+ if (s4) s4 = void 0;
1124
+ else s4 = peg$FAILED;
1125
+ if (s4 !== peg$FAILED) {
1126
+ s5 = peg$parseBlockBody();
1127
+ if (s5 !== peg$FAILED) {
1128
+ peg$savedPos = s3;
1129
+ s3 = peg$f13(s2, s5);
1130
+ } else {
1131
+ peg$currPos = s3;
1132
+ s3 = peg$FAILED;
1133
+ }
1134
+ } else {
1135
+ peg$currPos = s3;
1136
+ s3 = peg$FAILED;
1137
+ }
1138
+ if (s3 === peg$FAILED) s3 = null;
1139
+ peg$savedPos = s0;
1140
+ s0 = peg$f14(s2, s3);
1141
+ } else {
1142
+ peg$currPos = s0;
1143
+ s0 = peg$FAILED;
1144
+ }
1145
+ } else {
1146
+ peg$currPos = s0;
1147
+ s0 = peg$FAILED;
1148
+ }
1149
+ return s0;
1150
+ }
1151
+ function peg$parseBlockBody() {
1152
+ let s0, s1, s2, s3;
1153
+ s0 = peg$currPos;
1154
+ s1 = peg$parseContent();
1155
+ if (s1 !== peg$FAILED) {
1156
+ s2 = [];
1157
+ s3 = peg$parseBranchPart();
1158
+ while (s3 !== peg$FAILED) {
1159
+ s2.push(s3);
1160
+ s3 = peg$parseBranchPart();
1161
+ }
1162
+ s3 = peg$parseBlockEnd();
1163
+ if (s3 !== peg$FAILED) {
1164
+ peg$savedPos = s0;
1165
+ s0 = peg$f15(s1, s2, s3);
1166
+ } else {
1167
+ peg$currPos = s0;
1168
+ s0 = peg$FAILED;
1169
+ }
1170
+ } else {
1171
+ peg$currPos = s0;
1172
+ s0 = peg$FAILED;
1173
+ }
1174
+ return s0;
1175
+ }
1176
+ function peg$parseBranchPart() {
1177
+ let s0, s1, s2;
1178
+ s0 = peg$currPos;
1179
+ s1 = peg$parseBranchTag();
1180
+ if (s1 !== peg$FAILED) {
1181
+ s2 = peg$parseContent();
1182
+ if (s2 !== peg$FAILED) {
1183
+ peg$savedPos = s0;
1184
+ s0 = peg$f16(s1, s2);
1185
+ } else {
1186
+ peg$currPos = s0;
1187
+ s0 = peg$FAILED;
1188
+ }
1189
+ } else {
1190
+ peg$currPos = s0;
1191
+ s0 = peg$FAILED;
1192
+ }
1193
+ return s0;
1194
+ }
1195
+ function peg$parseBlockEnd() {
1196
+ let s0, s1, s2;
1197
+ s0 = peg$parseCloseTag();
1198
+ if (s0 === peg$FAILED) {
1199
+ s0 = peg$currPos;
1200
+ s1 = peg$currPos;
1201
+ peg$silentFails++;
1202
+ if (input.length > peg$currPos) {
1203
+ s2 = input.charAt(peg$currPos);
1204
+ peg$currPos++;
1205
+ } else {
1206
+ s2 = peg$FAILED;
1207
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1208
+ }
1209
+ peg$silentFails--;
1210
+ if (s2 === peg$FAILED) s1 = void 0;
1211
+ else {
1212
+ peg$currPos = s1;
1213
+ s1 = peg$FAILED;
1214
+ }
1215
+ if (s1 !== peg$FAILED) {
1216
+ peg$savedPos = s0;
1217
+ s1 = peg$f17();
1218
+ }
1219
+ s0 = s1;
1220
+ }
1221
+ return s0;
1222
+ }
1223
+ function peg$parseBranchTag() {
1224
+ let s0, s1, s2;
1225
+ s0 = peg$currPos;
1226
+ s1 = peg$parseMacroTag();
1227
+ if (s1 !== peg$FAILED) {
1228
+ peg$savedPos = peg$currPos;
1229
+ s2 = peg$f18(s1);
1230
+ if (s2) s2 = void 0;
1231
+ else s2 = peg$FAILED;
1232
+ if (s2 !== peg$FAILED) {
1233
+ peg$savedPos = s0;
1234
+ s0 = peg$f19(s1);
1235
+ } else {
1236
+ peg$currPos = s0;
1237
+ s0 = peg$FAILED;
1238
+ }
1239
+ } else {
1240
+ peg$currPos = s0;
1241
+ s0 = peg$FAILED;
1242
+ }
1243
+ return s0;
1244
+ }
1245
+ function peg$parseCloseTag() {
1246
+ let s0, s1, s2;
1247
+ s0 = peg$currPos;
1248
+ s1 = peg$parseMacroTag();
1249
+ if (s1 !== peg$FAILED) {
1250
+ peg$savedPos = peg$currPos;
1251
+ s2 = peg$f20(s1);
1252
+ if (s2) s2 = void 0;
1253
+ else s2 = peg$FAILED;
1254
+ if (s2 !== peg$FAILED) {
1255
+ peg$savedPos = s0;
1256
+ s0 = peg$f21(s1);
1257
+ } else {
1258
+ peg$currPos = s0;
1259
+ s0 = peg$FAILED;
1260
+ }
1261
+ } else {
1262
+ peg$currPos = s0;
1263
+ s0 = peg$FAILED;
1264
+ }
1265
+ if (s0 === peg$FAILED) {
1266
+ s0 = peg$currPos;
1267
+ peg$savedPos = peg$currPos;
1268
+ s1 = peg$f22();
1269
+ if (s1) s1 = void 0;
1270
+ else s1 = peg$FAILED;
1271
+ if (s1 !== peg$FAILED) {
1272
+ s2 = peg$parseHtmlClose();
1273
+ if (s2 !== peg$FAILED) {
1274
+ peg$savedPos = s0;
1275
+ s0 = peg$f23(s2);
1276
+ } else {
1277
+ peg$currPos = s0;
1278
+ s0 = peg$FAILED;
1279
+ }
1280
+ } else {
1281
+ peg$currPos = s0;
1282
+ s0 = peg$FAILED;
1283
+ }
1284
+ }
1285
+ return s0;
1286
+ }
1287
+ function peg$parseRawBody() {
1288
+ let s0, s1, s2, s3, s4, s5, s6, s7;
1289
+ s0 = peg$currPos;
1290
+ s1 = peg$parseHere();
1291
+ peg$savedPos = peg$currPos;
1292
+ s2 = peg$f24(s1);
1293
+ if (s2) s2 = void 0;
1294
+ else s2 = peg$FAILED;
1295
+ if (s2 !== peg$FAILED) {
1296
+ s3 = peg$currPos;
1297
+ s4 = peg$f25(s1);
1298
+ s5 = peg$currPos;
1299
+ s6 = [];
1300
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
1301
+ else if (input.length > peg$currPos) {
1302
+ s7 = input.charAt(peg$currPos);
1303
+ peg$currPos++;
1304
+ } else {
1305
+ s7 = peg$FAILED;
1306
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1307
+ }
1308
+ while (s7 !== peg$FAILED) {
1309
+ s6.push(s7);
1310
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
1311
+ else if (input.length > peg$currPos) {
1312
+ s7 = input.charAt(peg$currPos);
1313
+ peg$currPos++;
1314
+ } else {
1315
+ s7 = peg$FAILED;
1316
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1317
+ }
1318
+ }
1319
+ if (s6.length < (s4 | 0)) {
1320
+ peg$currPos = s5;
1321
+ s5 = peg$FAILED;
1322
+ } else s5 = s6;
1323
+ s4 = s5;
1324
+ if (s4 !== peg$FAILED) s3 = input.substring(s3, peg$currPos);
1325
+ else s3 = s4;
1326
+ if (s3 !== peg$FAILED) {
1327
+ s4 = peg$parseCloseTag();
1328
+ if (s4 !== peg$FAILED) {
1329
+ peg$savedPos = s0;
1330
+ s0 = peg$f26(s1, s3, s4);
1331
+ } else {
1332
+ peg$currPos = s0;
1333
+ s0 = peg$FAILED;
1334
+ }
1335
+ } else {
1336
+ peg$currPos = s0;
1337
+ s0 = peg$FAILED;
1338
+ }
1339
+ } else {
1340
+ peg$currPos = s0;
1341
+ s0 = peg$FAILED;
1342
+ }
1343
+ if (s0 === peg$FAILED) {
1344
+ s0 = peg$currPos;
1345
+ s1 = "";
1346
+ peg$savedPos = s0;
1347
+ s1 = peg$f27();
1348
+ s0 = s1;
1349
+ }
1350
+ return s0;
1351
+ }
1352
+ function peg$parseTokens() {
1353
+ let s0, s1, s2;
1354
+ s0 = peg$currPos;
1355
+ s1 = [];
1356
+ s2 = peg$parseTokenItem();
1357
+ while (s2 !== peg$FAILED) {
1358
+ s1.push(s2);
1359
+ s2 = peg$parseTokenItem();
1360
+ }
1361
+ peg$savedPos = s0;
1362
+ s1 = peg$f28(s1);
1363
+ s0 = s1;
1364
+ return s0;
1365
+ }
1366
+ function peg$parseTokenItem() {
1367
+ let s0, s1, s2, s3, s4;
1368
+ s0 = peg$parseTextRun();
1369
+ if (s0 === peg$FAILED) {
1370
+ s0 = peg$parseEscape();
1371
+ if (s0 === peg$FAILED) {
1372
+ s0 = peg$parseLinkLexeme();
1373
+ if (s0 === peg$FAILED) {
1374
+ s0 = peg$currPos;
1375
+ peg$savedPos = peg$currPos;
1376
+ s1 = peg$f29();
1377
+ if (s1) s1 = void 0;
1378
+ else s1 = peg$FAILED;
1379
+ if (s1 !== peg$FAILED) {
1380
+ s2 = peg$parseHtmlOpen();
1381
+ if (s2 !== peg$FAILED) {
1382
+ peg$savedPos = s0;
1383
+ s0 = peg$f30(s2);
1384
+ } else {
1385
+ peg$currPos = s0;
1386
+ s0 = peg$FAILED;
1387
+ }
1388
+ } else {
1389
+ peg$currPos = s0;
1390
+ s0 = peg$FAILED;
1391
+ }
1392
+ if (s0 === peg$FAILED) {
1393
+ s0 = peg$currPos;
1394
+ peg$savedPos = peg$currPos;
1395
+ s1 = peg$f31();
1396
+ if (s1) s1 = void 0;
1397
+ else s1 = peg$FAILED;
1398
+ if (s1 !== peg$FAILED) {
1399
+ s2 = peg$parseHtmlClose();
1400
+ if (s2 !== peg$FAILED) {
1401
+ peg$savedPos = s0;
1402
+ s0 = peg$f32(s2);
1403
+ } else {
1404
+ peg$currPos = s0;
1405
+ s0 = peg$FAILED;
1406
+ }
1407
+ } else {
1408
+ peg$currPos = s0;
1409
+ s0 = peg$FAILED;
1410
+ }
1411
+ if (s0 === peg$FAILED) {
1412
+ s0 = peg$parseValue();
1413
+ if (s0 === peg$FAILED) {
1414
+ s0 = peg$currPos;
1415
+ s1 = peg$parseMacroTag();
1416
+ if (s1 !== peg$FAILED) {
1417
+ s2 = peg$currPos;
1418
+ peg$savedPos = peg$currPos;
1419
+ s3 = peg$f33(s1);
1420
+ if (s3) s3 = void 0;
1421
+ else s3 = peg$FAILED;
1422
+ if (s3 !== peg$FAILED) {
1423
+ s4 = peg$parseRawTokens();
1424
+ if (s4 !== peg$FAILED) {
1425
+ peg$savedPos = s2;
1426
+ s2 = peg$f34(s1, s4);
1427
+ } else {
1428
+ peg$currPos = s2;
1429
+ s2 = peg$FAILED;
1430
+ }
1431
+ } else {
1432
+ peg$currPos = s2;
1433
+ s2 = peg$FAILED;
1434
+ }
1435
+ if (s2 === peg$FAILED) s2 = null;
1436
+ peg$savedPos = s0;
1437
+ s0 = peg$f35(s1, s2);
1438
+ } else {
1439
+ peg$currPos = s0;
1440
+ s0 = peg$FAILED;
1441
+ }
1442
+ if (s0 === peg$FAILED) s0 = peg$parseLiteral();
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+ }
1448
+ }
1449
+ return s0;
1450
+ }
1451
+ function peg$parseRawTokens() {
1452
+ let s0, s1, s2, s3, s4, s5, s6, s7;
1453
+ s0 = peg$currPos;
1454
+ s1 = peg$parseHere();
1455
+ peg$savedPos = peg$currPos;
1456
+ s2 = peg$f36(s1);
1457
+ if (s2) s2 = void 0;
1458
+ else s2 = peg$FAILED;
1459
+ if (s2 !== peg$FAILED) {
1460
+ s3 = peg$currPos;
1461
+ s4 = peg$f37(s1);
1462
+ s5 = peg$currPos;
1463
+ s6 = [];
1464
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
1465
+ else if (input.length > peg$currPos) {
1466
+ s7 = input.charAt(peg$currPos);
1467
+ peg$currPos++;
1468
+ } else {
1469
+ s7 = peg$FAILED;
1470
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1471
+ }
1472
+ while (s7 !== peg$FAILED) {
1473
+ s6.push(s7);
1474
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
1475
+ else if (input.length > peg$currPos) {
1476
+ s7 = input.charAt(peg$currPos);
1477
+ peg$currPos++;
1478
+ } else {
1479
+ s7 = peg$FAILED;
1480
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1481
+ }
1482
+ }
1483
+ if (s6.length < (s4 | 0)) {
1484
+ peg$currPos = s5;
1485
+ s5 = peg$FAILED;
1486
+ } else s5 = s6;
1487
+ s4 = s5;
1488
+ if (s4 !== peg$FAILED) s3 = input.substring(s3, peg$currPos);
1489
+ else s3 = s4;
1490
+ if (s3 !== peg$FAILED) {
1491
+ s4 = peg$parseCloseTag();
1492
+ if (s4 !== peg$FAILED) {
1493
+ peg$savedPos = s0;
1494
+ s0 = peg$f38(s1, s3, s4);
1495
+ } else {
1496
+ peg$currPos = s0;
1497
+ s0 = peg$FAILED;
1498
+ }
1499
+ } else {
1500
+ peg$currPos = s0;
1501
+ s0 = peg$FAILED;
1502
+ }
1503
+ } else {
1504
+ peg$currPos = s0;
1505
+ s0 = peg$FAILED;
1506
+ }
1507
+ return s0;
1508
+ }
1509
+ function peg$parseHere() {
1510
+ let s0, s1;
1511
+ s0 = peg$currPos;
1512
+ s1 = "";
1513
+ peg$savedPos = s0;
1514
+ s1 = peg$f39();
1515
+ s0 = s1;
1516
+ return s0;
1517
+ }
1518
+ function peg$parseTextRun() {
1519
+ let s0, s1, s2, s3;
1520
+ s0 = peg$currPos;
1521
+ s1 = peg$currPos;
1522
+ s2 = [];
1523
+ s3 = input.charAt(peg$currPos);
1524
+ if (peg$r0.test(s3)) peg$currPos++;
1525
+ else {
1526
+ s3 = peg$FAILED;
1527
+ if (peg$silentFails === 0) peg$fail(peg$e1);
1528
+ }
1529
+ if (s3 !== peg$FAILED) while (s3 !== peg$FAILED) {
1530
+ s2.push(s3);
1531
+ s3 = input.charAt(peg$currPos);
1532
+ if (peg$r0.test(s3)) peg$currPos++;
1533
+ else {
1534
+ s3 = peg$FAILED;
1535
+ if (peg$silentFails === 0) peg$fail(peg$e1);
1536
+ }
1537
+ }
1538
+ else s2 = peg$FAILED;
1539
+ if (s2 !== peg$FAILED) s1 = input.substring(s1, peg$currPos);
1540
+ else s1 = s2;
1541
+ if (s1 !== peg$FAILED) {
1542
+ peg$savedPos = s0;
1543
+ s1 = peg$f40(s1);
1544
+ }
1545
+ s0 = s1;
1546
+ return s0;
1547
+ }
1548
+ function peg$parseEscape() {
1549
+ let s0, s1, s2, s3, s4;
1550
+ s0 = peg$currPos;
1551
+ s1 = peg$currPos;
1552
+ s2 = [];
1553
+ if (input.charCodeAt(peg$currPos) === 92) {
1554
+ s3 = peg$c0;
1555
+ peg$currPos++;
1556
+ } else {
1557
+ s3 = peg$FAILED;
1558
+ if (peg$silentFails === 0) peg$fail(peg$e2);
1559
+ }
1560
+ if (s3 !== peg$FAILED) while (s3 !== peg$FAILED) {
1561
+ s2.push(s3);
1562
+ if (input.charCodeAt(peg$currPos) === 92) {
1563
+ s3 = peg$c0;
1564
+ peg$currPos++;
1565
+ } else {
1566
+ s3 = peg$FAILED;
1567
+ if (peg$silentFails === 0) peg$fail(peg$e2);
1568
+ }
1569
+ }
1570
+ else s2 = peg$FAILED;
1571
+ if (s2 !== peg$FAILED) s1 = input.substring(s1, peg$currPos);
1572
+ else s1 = s2;
1573
+ if (s1 !== peg$FAILED) {
1574
+ s2 = peg$currPos;
1575
+ peg$savedPos = peg$currPos;
1576
+ s3 = peg$f41(s1);
1577
+ if (s3) s3 = void 0;
1578
+ else s3 = peg$FAILED;
1579
+ if (s3 !== peg$FAILED) {
1580
+ s4 = input.charAt(peg$currPos);
1581
+ if (peg$r1.test(s4)) peg$currPos++;
1582
+ else {
1583
+ s4 = peg$FAILED;
1584
+ if (peg$silentFails === 0) peg$fail(peg$e3);
1585
+ }
1586
+ if (s4 !== peg$FAILED) {
1587
+ peg$savedPos = s2;
1588
+ s2 = peg$f42(s1, s4);
1589
+ } else {
1590
+ peg$currPos = s2;
1591
+ s2 = peg$FAILED;
1592
+ }
1593
+ } else {
1594
+ peg$currPos = s2;
1595
+ s2 = peg$FAILED;
1596
+ }
1597
+ if (s2 === peg$FAILED) s2 = null;
1598
+ peg$savedPos = s0;
1599
+ s0 = peg$f43(s1, s2);
1600
+ } else {
1601
+ peg$currPos = s0;
1602
+ s0 = peg$FAILED;
1603
+ }
1604
+ return s0;
1605
+ }
1606
+ function peg$parseLiteral() {
1607
+ let s0, s1, s2, s3, s4, s5;
1608
+ s0 = peg$currPos;
1609
+ if (input.charCodeAt(peg$currPos) === 123) {
1610
+ s1 = peg$c1;
1611
+ peg$currPos++;
1612
+ } else {
1613
+ s1 = peg$FAILED;
1614
+ if (peg$silentFails === 0) peg$fail(peg$e4);
1615
+ }
1616
+ if (s1 !== peg$FAILED) {
1617
+ s2 = peg$currPos;
1618
+ peg$silentFails++;
1619
+ s3 = peg$currPos;
1620
+ s4 = peg$parseSelectors();
1621
+ if (s4 !== peg$FAILED) {
1622
+ s5 = input.charAt(peg$currPos);
1623
+ if (peg$r2.test(s5)) peg$currPos++;
1624
+ else {
1625
+ s5 = peg$FAILED;
1626
+ if (peg$silentFails === 0) peg$fail(peg$e5);
1627
+ }
1628
+ if (s5 !== peg$FAILED) {
1629
+ s4 = [s4, s5];
1630
+ s3 = s4;
1631
+ } else {
1632
+ peg$currPos = s3;
1633
+ s3 = peg$FAILED;
1634
+ }
1635
+ } else {
1636
+ peg$currPos = s3;
1637
+ s3 = peg$FAILED;
1638
+ }
1639
+ peg$silentFails--;
1640
+ if (s3 === peg$FAILED) s2 = void 0;
1641
+ else {
1642
+ peg$currPos = s2;
1643
+ s2 = peg$FAILED;
1644
+ }
1645
+ if (s2 !== peg$FAILED) {
1646
+ peg$savedPos = s0;
1647
+ s0 = peg$f44();
1648
+ } else {
1649
+ peg$currPos = s0;
1650
+ s0 = peg$FAILED;
1651
+ }
1652
+ } else {
1653
+ peg$currPos = s0;
1654
+ s0 = peg$FAILED;
1655
+ }
1656
+ if (s0 === peg$FAILED) {
1657
+ s0 = peg$currPos;
1658
+ s1 = peg$currPos;
1659
+ peg$silentFails++;
1660
+ s2 = peg$currPos;
1661
+ peg$savedPos = peg$currPos;
1662
+ s3 = peg$f45();
1663
+ if (s3) s3 = void 0;
1664
+ else s3 = peg$FAILED;
1665
+ if (s3 !== peg$FAILED) {
1666
+ s4 = peg$parseHtmlClose();
1667
+ if (s4 !== peg$FAILED) {
1668
+ s3 = [s3, s4];
1669
+ s2 = s3;
1670
+ } else {
1671
+ peg$currPos = s2;
1672
+ s2 = peg$FAILED;
1673
+ }
1674
+ } else {
1675
+ peg$currPos = s2;
1676
+ s2 = peg$FAILED;
1677
+ }
1678
+ peg$silentFails--;
1679
+ if (s2 === peg$FAILED) s1 = void 0;
1680
+ else {
1681
+ peg$currPos = s1;
1682
+ s1 = peg$FAILED;
1683
+ }
1684
+ if (s1 !== peg$FAILED) {
1685
+ if (input.charCodeAt(peg$currPos) === 60) {
1686
+ s2 = peg$c2;
1687
+ peg$currPos++;
1688
+ } else {
1689
+ s2 = peg$FAILED;
1690
+ if (peg$silentFails === 0) peg$fail(peg$e6);
1691
+ }
1692
+ if (s2 !== peg$FAILED) {
1693
+ peg$savedPos = s0;
1694
+ s0 = peg$f46();
1695
+ } else {
1696
+ peg$currPos = s0;
1697
+ s0 = peg$FAILED;
1698
+ }
1699
+ } else {
1700
+ peg$currPos = s0;
1701
+ s0 = peg$FAILED;
1702
+ }
1703
+ if (s0 === peg$FAILED) {
1704
+ s0 = peg$currPos;
1705
+ if (input.charCodeAt(peg$currPos) === 91) {
1706
+ s1 = peg$c3;
1707
+ peg$currPos++;
1708
+ } else {
1709
+ s1 = peg$FAILED;
1710
+ if (peg$silentFails === 0) peg$fail(peg$e7);
1711
+ }
1712
+ if (s1 !== peg$FAILED) {
1713
+ peg$savedPos = s0;
1714
+ s1 = peg$f47();
1715
+ }
1716
+ s0 = s1;
1717
+ }
1718
+ }
1719
+ return s0;
1720
+ }
1721
+ function peg$parseLinkLexeme() {
1722
+ let s0, s1, s2, s3, s4, s5;
1723
+ s0 = peg$currPos;
1724
+ peg$savedPos = peg$currPos;
1725
+ s1 = peg$f48();
1726
+ if (s1) s1 = void 0;
1727
+ else s1 = peg$FAILED;
1728
+ if (s1 !== peg$FAILED) {
1729
+ if (input.substr(peg$currPos, 2) === peg$c4) {
1730
+ s2 = peg$c4;
1731
+ peg$currPos += 2;
1732
+ } else {
1733
+ s2 = peg$FAILED;
1734
+ if (peg$silentFails === 0) peg$fail(peg$e8);
1735
+ }
1736
+ if (s2 !== peg$FAILED) {
1737
+ s3 = peg$parseSelectors();
1738
+ if (s3 !== peg$FAILED) {
1739
+ s4 = peg$currPos;
1740
+ s5 = peg$parseLinkText();
1741
+ s4 = input.substring(s4, peg$currPos);
1742
+ if (input.substr(peg$currPos, 2) === peg$c5) {
1743
+ s5 = peg$c5;
1744
+ peg$currPos += 2;
1745
+ } else {
1746
+ s5 = peg$FAILED;
1747
+ if (peg$silentFails === 0) peg$fail(peg$e9);
1748
+ }
1749
+ if (s5 !== peg$FAILED) {
1750
+ peg$savedPos = s0;
1751
+ s0 = peg$f49(s3, s4);
1752
+ } else {
1753
+ peg$currPos = s0;
1754
+ s0 = peg$FAILED;
1755
+ }
1756
+ } else {
1757
+ peg$currPos = s0;
1758
+ s0 = peg$FAILED;
1759
+ }
1760
+ } else {
1761
+ peg$currPos = s0;
1762
+ s0 = peg$FAILED;
1763
+ }
1764
+ } else {
1765
+ peg$currPos = s0;
1766
+ s0 = peg$FAILED;
1767
+ }
1768
+ if (s0 === peg$FAILED) {
1769
+ s0 = peg$currPos;
1770
+ peg$savedPos = peg$currPos;
1771
+ s1 = peg$f50();
1772
+ if (s1) s1 = void 0;
1773
+ else s1 = peg$FAILED;
1774
+ if (s1 !== peg$FAILED) {
1775
+ if (input.substr(peg$currPos, 2) === peg$c4) {
1776
+ s2 = peg$c4;
1777
+ peg$currPos += 2;
1778
+ } else {
1779
+ s2 = peg$FAILED;
1780
+ if (peg$silentFails === 0) peg$fail(peg$e8);
1781
+ }
1782
+ if (s2 !== peg$FAILED) {
1783
+ peg$savedPos = s0;
1784
+ s0 = peg$f51();
1785
+ } else {
1786
+ peg$currPos = s0;
1787
+ s0 = peg$FAILED;
1788
+ }
1789
+ } else {
1790
+ peg$currPos = s0;
1791
+ s0 = peg$FAILED;
1792
+ }
1793
+ }
1794
+ return s0;
1795
+ }
1796
+ function peg$parseLinkText() {
1797
+ let s0, s1, s2, s3;
1798
+ s0 = [];
1799
+ s1 = peg$currPos;
1800
+ s2 = peg$currPos;
1801
+ peg$silentFails++;
1802
+ if (input.substr(peg$currPos, 2) === peg$c5) {
1803
+ s3 = peg$c5;
1804
+ peg$currPos += 2;
1805
+ } else {
1806
+ s3 = peg$FAILED;
1807
+ if (peg$silentFails === 0) peg$fail(peg$e9);
1808
+ }
1809
+ if (s3 === peg$FAILED) if (input.substr(peg$currPos, 2) === peg$c4) {
1810
+ s3 = peg$c4;
1811
+ peg$currPos += 2;
1812
+ } else {
1813
+ s3 = peg$FAILED;
1814
+ if (peg$silentFails === 0) peg$fail(peg$e8);
1815
+ }
1816
+ peg$silentFails--;
1817
+ if (s3 === peg$FAILED) s2 = void 0;
1818
+ else {
1819
+ peg$currPos = s2;
1820
+ s2 = peg$FAILED;
1821
+ }
1822
+ if (s2 !== peg$FAILED) {
1823
+ if (input.length > peg$currPos) {
1824
+ s3 = input.charAt(peg$currPos);
1825
+ peg$currPos++;
1826
+ } else {
1827
+ s3 = peg$FAILED;
1828
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1829
+ }
1830
+ if (s3 !== peg$FAILED) {
1831
+ s2 = [s2, s3];
1832
+ s1 = s2;
1833
+ } else {
1834
+ peg$currPos = s1;
1835
+ s1 = peg$FAILED;
1836
+ }
1837
+ } else {
1838
+ peg$currPos = s1;
1839
+ s1 = peg$FAILED;
1840
+ }
1841
+ while (s1 !== peg$FAILED) {
1842
+ s0.push(s1);
1843
+ s1 = peg$currPos;
1844
+ s2 = peg$currPos;
1845
+ peg$silentFails++;
1846
+ if (input.substr(peg$currPos, 2) === peg$c5) {
1847
+ s3 = peg$c5;
1848
+ peg$currPos += 2;
1849
+ } else {
1850
+ s3 = peg$FAILED;
1851
+ if (peg$silentFails === 0) peg$fail(peg$e9);
1852
+ }
1853
+ if (s3 === peg$FAILED) if (input.substr(peg$currPos, 2) === peg$c4) {
1854
+ s3 = peg$c4;
1855
+ peg$currPos += 2;
1856
+ } else {
1857
+ s3 = peg$FAILED;
1858
+ if (peg$silentFails === 0) peg$fail(peg$e8);
1859
+ }
1860
+ peg$silentFails--;
1861
+ if (s3 === peg$FAILED) s2 = void 0;
1862
+ else {
1863
+ peg$currPos = s2;
1864
+ s2 = peg$FAILED;
1865
+ }
1866
+ if (s2 !== peg$FAILED) {
1867
+ if (input.length > peg$currPos) {
1868
+ s3 = input.charAt(peg$currPos);
1869
+ peg$currPos++;
1870
+ } else {
1871
+ s3 = peg$FAILED;
1872
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1873
+ }
1874
+ if (s3 !== peg$FAILED) {
1875
+ s2 = [s2, s3];
1876
+ s1 = s2;
1877
+ } else {
1878
+ peg$currPos = s1;
1879
+ s1 = peg$FAILED;
1880
+ }
1881
+ } else {
1882
+ peg$currPos = s1;
1883
+ s1 = peg$FAILED;
1884
+ }
1885
+ }
1886
+ return s0;
1887
+ }
1888
+ function peg$parseSelectors() {
1889
+ let s0, s1, s2;
1890
+ s0 = peg$currPos;
1891
+ s1 = [];
1892
+ s2 = peg$parseSelectorPart();
1893
+ if (s2 !== peg$FAILED) while (s2 !== peg$FAILED) {
1894
+ s1.push(s2);
1895
+ s2 = peg$parseSelectorPart();
1896
+ }
1897
+ else s1 = peg$FAILED;
1898
+ if (s1 !== peg$FAILED) {
1899
+ if (input.charCodeAt(peg$currPos) === 32) {
1900
+ s2 = peg$c6;
1901
+ peg$currPos++;
1902
+ } else {
1903
+ s2 = peg$FAILED;
1904
+ if (peg$silentFails === 0) peg$fail(peg$e10);
1905
+ }
1906
+ if (s2 === peg$FAILED) s2 = null;
1907
+ peg$savedPos = s0;
1908
+ s0 = peg$f52(s1);
1909
+ } else {
1910
+ peg$currPos = s0;
1911
+ s0 = peg$FAILED;
1912
+ }
1913
+ if (s0 === peg$FAILED) {
1914
+ s0 = peg$currPos;
1915
+ s1 = "";
1916
+ peg$savedPos = s0;
1917
+ s1 = peg$f53();
1918
+ s0 = s1;
1919
+ }
1920
+ return s0;
1921
+ }
1922
+ function peg$parseSelectorPart() {
1923
+ let s0, s1, s2, s3, s4;
1924
+ s0 = peg$currPos;
1925
+ s1 = input.charAt(peg$currPos);
1926
+ if (peg$r3.test(s1)) peg$currPos++;
1927
+ else {
1928
+ s1 = peg$FAILED;
1929
+ if (peg$silentFails === 0) peg$fail(peg$e11);
1930
+ }
1931
+ if (s1 !== peg$FAILED) {
1932
+ s2 = peg$currPos;
1933
+ s3 = [];
1934
+ s4 = peg$parseSelectorChar();
1935
+ while (s4 !== peg$FAILED) {
1936
+ s3.push(s4);
1937
+ s4 = peg$parseSelectorChar();
1938
+ }
1939
+ s2 = input.substring(s2, peg$currPos);
1940
+ peg$savedPos = s0;
1941
+ s0 = peg$f54(s1, s2);
1942
+ } else {
1943
+ peg$currPos = s0;
1944
+ s0 = peg$FAILED;
1945
+ }
1946
+ return s0;
1947
+ }
1948
+ function peg$parseSelectorChar() {
1949
+ let s0, s1, s2, s3, s4;
1950
+ s0 = input.charAt(peg$currPos);
1951
+ if (peg$r4.test(s0)) peg$currPos++;
1952
+ else {
1953
+ s0 = peg$FAILED;
1954
+ if (peg$silentFails === 0) peg$fail(peg$e12);
1955
+ }
1956
+ if (s0 === peg$FAILED) {
1957
+ s0 = peg$currPos;
1958
+ if (input.charCodeAt(peg$currPos) === 123) {
1959
+ s1 = peg$c1;
1960
+ peg$currPos++;
1961
+ } else {
1962
+ s1 = peg$FAILED;
1963
+ if (peg$silentFails === 0) peg$fail(peg$e4);
1964
+ }
1965
+ if (s1 !== peg$FAILED) {
1966
+ s2 = input.charAt(peg$currPos);
1967
+ if (peg$r5.test(s2)) peg$currPos++;
1968
+ else {
1969
+ s2 = peg$FAILED;
1970
+ if (peg$silentFails === 0) peg$fail(peg$e13);
1971
+ }
1972
+ if (s2 !== peg$FAILED) {
1973
+ s3 = [];
1974
+ s4 = input.charAt(peg$currPos);
1975
+ if (peg$r6.test(s4)) peg$currPos++;
1976
+ else {
1977
+ s4 = peg$FAILED;
1978
+ if (peg$silentFails === 0) peg$fail(peg$e14);
1979
+ }
1980
+ while (s4 !== peg$FAILED) {
1981
+ s3.push(s4);
1982
+ s4 = input.charAt(peg$currPos);
1983
+ if (peg$r6.test(s4)) peg$currPos++;
1984
+ else {
1985
+ s4 = peg$FAILED;
1986
+ if (peg$silentFails === 0) peg$fail(peg$e14);
1987
+ }
1988
+ }
1989
+ if (input.charCodeAt(peg$currPos) === 125) {
1990
+ s4 = peg$c7;
1991
+ peg$currPos++;
1992
+ } else {
1993
+ s4 = peg$FAILED;
1994
+ if (peg$silentFails === 0) peg$fail(peg$e15);
1995
+ }
1996
+ if (s4 !== peg$FAILED) {
1997
+ s1 = [
1998
+ s1,
1999
+ s2,
2000
+ s3,
2001
+ s4
2002
+ ];
2003
+ s0 = s1;
2004
+ } else {
2005
+ peg$currPos = s0;
2006
+ s0 = peg$FAILED;
2007
+ }
2008
+ } else {
2009
+ peg$currPos = s0;
2010
+ s0 = peg$FAILED;
2011
+ }
2012
+ } else {
2013
+ peg$currPos = s0;
2014
+ s0 = peg$FAILED;
2015
+ }
2016
+ }
2017
+ return s0;
2018
+ }
2019
+ function peg$parseValue() {
2020
+ let s0, s1, s2, s3, s4, s5, s6;
2021
+ s0 = peg$currPos;
2022
+ if (input.charCodeAt(peg$currPos) === 123) {
2023
+ s1 = peg$c1;
2024
+ peg$currPos++;
2025
+ } else {
2026
+ s1 = peg$FAILED;
2027
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2028
+ }
2029
+ if (s1 !== peg$FAILED) {
2030
+ s2 = peg$parseSelectors();
2031
+ if (s2 !== peg$FAILED) {
2032
+ s3 = input.charAt(peg$currPos);
2033
+ if (peg$r5.test(s3)) peg$currPos++;
2034
+ else {
2035
+ s3 = peg$FAILED;
2036
+ if (peg$silentFails === 0) peg$fail(peg$e13);
2037
+ }
2038
+ if (s3 !== peg$FAILED) {
2039
+ s4 = peg$currPos;
2040
+ s5 = [];
2041
+ s6 = input.charAt(peg$currPos);
2042
+ if (peg$r6.test(s6)) peg$currPos++;
2043
+ else {
2044
+ s6 = peg$FAILED;
2045
+ if (peg$silentFails === 0) peg$fail(peg$e14);
2046
+ }
2047
+ while (s6 !== peg$FAILED) {
2048
+ s5.push(s6);
2049
+ s6 = input.charAt(peg$currPos);
2050
+ if (peg$r6.test(s6)) peg$currPos++;
2051
+ else {
2052
+ s6 = peg$FAILED;
2053
+ if (peg$silentFails === 0) peg$fail(peg$e14);
2054
+ }
2055
+ }
2056
+ s4 = input.substring(s4, peg$currPos);
2057
+ if (input.charCodeAt(peg$currPos) === 125) {
2058
+ s5 = peg$c7;
2059
+ peg$currPos++;
2060
+ } else {
2061
+ s5 = peg$FAILED;
2062
+ if (peg$silentFails === 0) peg$fail(peg$e15);
2063
+ }
2064
+ if (s5 !== peg$FAILED) {
2065
+ peg$savedPos = s0;
2066
+ s0 = peg$f55(s2, s3, s4);
2067
+ } else {
2068
+ peg$currPos = s0;
2069
+ s0 = peg$FAILED;
2070
+ }
2071
+ } else {
2072
+ peg$currPos = s0;
2073
+ s0 = peg$FAILED;
2074
+ }
2075
+ } else {
2076
+ peg$currPos = s0;
2077
+ s0 = peg$FAILED;
2078
+ }
2079
+ } else {
2080
+ peg$currPos = s0;
2081
+ s0 = peg$FAILED;
2082
+ }
2083
+ if (s0 === peg$FAILED) {
2084
+ s0 = peg$currPos;
2085
+ if (input.charCodeAt(peg$currPos) === 123) {
2086
+ s1 = peg$c1;
2087
+ peg$currPos++;
2088
+ } else {
2089
+ s1 = peg$FAILED;
2090
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2091
+ }
2092
+ if (s1 !== peg$FAILED) {
2093
+ s2 = peg$parseSelectors();
2094
+ if (s2 !== peg$FAILED) {
2095
+ s3 = peg$currPos;
2096
+ peg$silentFails++;
2097
+ s4 = input.charAt(peg$currPos);
2098
+ if (peg$r7.test(s4)) peg$currPos++;
2099
+ else {
2100
+ s4 = peg$FAILED;
2101
+ if (peg$silentFails === 0) peg$fail(peg$e16);
2102
+ }
2103
+ peg$silentFails--;
2104
+ if (s4 !== peg$FAILED) {
2105
+ peg$currPos = s3;
2106
+ s3 = void 0;
2107
+ } else s3 = peg$FAILED;
2108
+ if (s3 !== peg$FAILED) {
2109
+ s4 = peg$parseCodeToBrace();
2110
+ if (s4 !== peg$FAILED) {
2111
+ if (input.charCodeAt(peg$currPos) === 125) {
2112
+ s5 = peg$c7;
2113
+ peg$currPos++;
2114
+ } else {
2115
+ s5 = peg$FAILED;
2116
+ if (peg$silentFails === 0) peg$fail(peg$e15);
2117
+ }
2118
+ if (s5 !== peg$FAILED) {
2119
+ peg$savedPos = s0;
2120
+ s0 = peg$f56(s2, s4);
2121
+ } else {
2122
+ peg$currPos = s0;
2123
+ s0 = peg$FAILED;
2124
+ }
2125
+ } else {
2126
+ peg$currPos = s0;
2127
+ s0 = peg$FAILED;
2128
+ }
2129
+ } else {
2130
+ peg$currPos = s0;
2131
+ s0 = peg$FAILED;
2132
+ }
2133
+ } else {
2134
+ peg$currPos = s0;
2135
+ s0 = peg$FAILED;
2136
+ }
2137
+ } else {
2138
+ peg$currPos = s0;
2139
+ s0 = peg$FAILED;
2140
+ }
2141
+ if (s0 === peg$FAILED) {
2142
+ s0 = peg$currPos;
2143
+ if (input.charCodeAt(peg$currPos) === 123) {
2144
+ s1 = peg$c1;
2145
+ peg$currPos++;
2146
+ } else {
2147
+ s1 = peg$FAILED;
2148
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2149
+ }
2150
+ if (s1 !== peg$FAILED) {
2151
+ s2 = peg$parseSelectors();
2152
+ if (s2 !== peg$FAILED) {
2153
+ s3 = peg$currPos;
2154
+ peg$silentFails++;
2155
+ s4 = input.charAt(peg$currPos);
2156
+ if (peg$r7.test(s4)) peg$currPos++;
2157
+ else {
2158
+ s4 = peg$FAILED;
2159
+ if (peg$silentFails === 0) peg$fail(peg$e16);
2160
+ }
2161
+ peg$silentFails--;
2162
+ if (s4 !== peg$FAILED) {
2163
+ peg$currPos = s3;
2164
+ s3 = void 0;
2165
+ } else s3 = peg$FAILED;
2166
+ if (s3 !== peg$FAILED) {
2167
+ peg$savedPos = s0;
2168
+ s0 = peg$f57();
2169
+ } else {
2170
+ peg$currPos = s0;
2171
+ s0 = peg$FAILED;
2172
+ }
2173
+ } else {
2174
+ peg$currPos = s0;
2175
+ s0 = peg$FAILED;
2176
+ }
2177
+ } else {
2178
+ peg$currPos = s0;
2179
+ s0 = peg$FAILED;
2180
+ }
2181
+ }
2182
+ }
2183
+ return s0;
2184
+ }
2185
+ function peg$parseCodeToBrace() {
2186
+ let s0, s1, s2, s3, s4, s5, s6, s7;
2187
+ s0 = peg$currPos;
2188
+ s1 = peg$parseHere();
2189
+ peg$savedPos = peg$currPos;
2190
+ s2 = peg$f58(s1);
2191
+ if (s2) s2 = void 0;
2192
+ else s2 = peg$FAILED;
2193
+ if (s2 !== peg$FAILED) {
2194
+ s3 = peg$currPos;
2195
+ s4 = peg$f59(s1);
2196
+ s5 = peg$currPos;
2197
+ s6 = [];
2198
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
2199
+ else if (input.length > peg$currPos) {
2200
+ s7 = input.charAt(peg$currPos);
2201
+ peg$currPos++;
2202
+ } else {
2203
+ s7 = peg$FAILED;
2204
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2205
+ }
2206
+ while (s7 !== peg$FAILED) {
2207
+ s6.push(s7);
2208
+ if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
2209
+ else if (input.length > peg$currPos) {
2210
+ s7 = input.charAt(peg$currPos);
2211
+ peg$currPos++;
2212
+ } else {
2213
+ s7 = peg$FAILED;
2214
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2215
+ }
2216
+ }
2217
+ if (s6.length < (s4 | 0)) {
2218
+ peg$currPos = s5;
2219
+ s5 = peg$FAILED;
2220
+ } else s5 = s6;
2221
+ s4 = s5;
2222
+ if (s4 !== peg$FAILED) s3 = input.substring(s3, peg$currPos);
2223
+ else s3 = s4;
2224
+ if (s3 !== peg$FAILED) {
2225
+ peg$savedPos = s0;
2226
+ s0 = peg$f60(s1, s3);
2227
+ } else {
2228
+ peg$currPos = s0;
2229
+ s0 = peg$FAILED;
2230
+ }
2231
+ } else {
2232
+ peg$currPos = s0;
2233
+ s0 = peg$FAILED;
2234
+ }
2235
+ return s0;
2236
+ }
2237
+ function peg$parseMacroTag() {
2238
+ let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
2239
+ s0 = peg$currPos;
2240
+ if (input.charCodeAt(peg$currPos) === 123) {
2241
+ s1 = peg$c1;
2242
+ peg$currPos++;
2243
+ } else {
2244
+ s1 = peg$FAILED;
2245
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2246
+ }
2247
+ if (s1 !== peg$FAILED) {
2248
+ s2 = peg$parseSelectors();
2249
+ if (s2 !== peg$FAILED) {
2250
+ s3 = peg$parseHere();
2251
+ if (input.charCodeAt(peg$currPos) === 47) {
2252
+ s4 = peg$c8;
2253
+ peg$currPos++;
2254
+ } else {
2255
+ s4 = peg$FAILED;
2256
+ if (peg$silentFails === 0) peg$fail(peg$e17);
2257
+ }
2258
+ if (s4 === peg$FAILED) s4 = null;
2259
+ s5 = peg$parseMacroName();
2260
+ if (s5 !== peg$FAILED) {
2261
+ s6 = peg$parseHere();
2262
+ s7 = peg$currPos;
2263
+ peg$savedPos = peg$currPos;
2264
+ s8 = peg$f61(s2, s3, s4, s5, s6);
2265
+ if (s8) s8 = void 0;
2266
+ else s8 = peg$FAILED;
2267
+ if (s8 !== peg$FAILED) {
2268
+ s9 = peg$currPos;
2269
+ s10 = peg$f62(s2, s3, s4, s5, s6);
2270
+ s11 = peg$currPos;
2271
+ s12 = [];
2272
+ if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
2273
+ else if (input.length > peg$currPos) {
2274
+ s13 = input.charAt(peg$currPos);
2275
+ peg$currPos++;
2276
+ } else {
2277
+ s13 = peg$FAILED;
2278
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2279
+ }
2280
+ while (s13 !== peg$FAILED) {
2281
+ s12.push(s13);
2282
+ if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
2283
+ else if (input.length > peg$currPos) {
2284
+ s13 = input.charAt(peg$currPos);
2285
+ peg$currPos++;
2286
+ } else {
2287
+ s13 = peg$FAILED;
2288
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2289
+ }
2290
+ }
2291
+ if (s12.length < (s10 | 0)) {
2292
+ peg$currPos = s11;
2293
+ s11 = peg$FAILED;
2294
+ } else s11 = s12;
2295
+ s10 = s11;
2296
+ if (s10 !== peg$FAILED) s9 = input.substring(s9, peg$currPos);
2297
+ else s9 = s10;
2298
+ if (s9 !== peg$FAILED) {
2299
+ if (input.charCodeAt(peg$currPos) === 125) {
2300
+ s10 = peg$c7;
2301
+ peg$currPos++;
2302
+ } else {
2303
+ s10 = peg$FAILED;
2304
+ if (peg$silentFails === 0) peg$fail(peg$e15);
2305
+ }
2306
+ if (s10 !== peg$FAILED) {
2307
+ peg$savedPos = s7;
2308
+ s7 = peg$f63(s2, s3, s4, s5, s6, s9);
2309
+ } else {
2310
+ peg$currPos = s7;
2311
+ s7 = peg$FAILED;
2312
+ }
2313
+ } else {
2314
+ peg$currPos = s7;
2315
+ s7 = peg$FAILED;
2316
+ }
2317
+ } else {
2318
+ peg$currPos = s7;
2319
+ s7 = peg$FAILED;
2320
+ }
2321
+ if (s7 === peg$FAILED) s7 = null;
2322
+ peg$savedPos = s0;
2323
+ s0 = peg$f64(s2, s3, s4, s5, s6, s7);
2324
+ } else {
2325
+ peg$currPos = s0;
2326
+ s0 = peg$FAILED;
2327
+ }
2328
+ } else {
2329
+ peg$currPos = s0;
2330
+ s0 = peg$FAILED;
2331
+ }
2332
+ } else {
2333
+ peg$currPos = s0;
2334
+ s0 = peg$FAILED;
2335
+ }
2336
+ if (s0 === peg$FAILED) {
2337
+ s0 = peg$currPos;
2338
+ if (input.charCodeAt(peg$currPos) === 123) {
2339
+ s1 = peg$c1;
2340
+ peg$currPos++;
2341
+ } else {
2342
+ s1 = peg$FAILED;
2343
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2344
+ }
2345
+ if (s1 !== peg$FAILED) {
2346
+ s2 = peg$parseSelectors();
2347
+ if (s2 !== peg$FAILED) {
2348
+ if (input.charCodeAt(peg$currPos) === 47) {
2349
+ s3 = peg$c8;
2350
+ peg$currPos++;
2351
+ } else {
2352
+ s3 = peg$FAILED;
2353
+ if (peg$silentFails === 0) peg$fail(peg$e17);
2354
+ }
2355
+ if (s3 !== peg$FAILED) {
2356
+ peg$savedPos = s0;
2357
+ s0 = peg$f65(s2);
2358
+ } else {
2359
+ peg$currPos = s0;
2360
+ s0 = peg$FAILED;
2361
+ }
2362
+ } else {
2363
+ peg$currPos = s0;
2364
+ s0 = peg$FAILED;
2365
+ }
2366
+ } else {
2367
+ peg$currPos = s0;
2368
+ s0 = peg$FAILED;
2369
+ }
2370
+ }
2371
+ return s0;
2372
+ }
2373
+ function peg$parseMacroName() {
2374
+ let s0, s1, s2, s3, s4;
2375
+ s0 = peg$currPos;
2376
+ s1 = peg$currPos;
2377
+ s2 = input.charAt(peg$currPos);
2378
+ if (peg$r8.test(s2)) peg$currPos++;
2379
+ else {
2380
+ s2 = peg$FAILED;
2381
+ if (peg$silentFails === 0) peg$fail(peg$e18);
2382
+ }
2383
+ if (s2 !== peg$FAILED) {
2384
+ s3 = [];
2385
+ s4 = input.charAt(peg$currPos);
2386
+ if (peg$r9.test(s4)) peg$currPos++;
2387
+ else {
2388
+ s4 = peg$FAILED;
2389
+ if (peg$silentFails === 0) peg$fail(peg$e19);
2390
+ }
2391
+ while (s4 !== peg$FAILED) {
2392
+ s3.push(s4);
2393
+ s4 = input.charAt(peg$currPos);
2394
+ if (peg$r9.test(s4)) peg$currPos++;
2395
+ else {
2396
+ s4 = peg$FAILED;
2397
+ if (peg$silentFails === 0) peg$fail(peg$e19);
2398
+ }
2399
+ }
2400
+ s2 = [s2, s3];
2401
+ s1 = s2;
2402
+ } else {
2403
+ peg$currPos = s1;
2404
+ s1 = peg$FAILED;
2405
+ }
2406
+ if (s1 !== peg$FAILED) s0 = input.substring(s0, peg$currPos);
2407
+ else s0 = s1;
2408
+ return s0;
2409
+ }
2410
+ function peg$parseVoidCloser() {
2411
+ let s0, s1, s2, s3;
2412
+ s0 = peg$currPos;
2413
+ peg$savedPos = peg$currPos;
2414
+ s1 = peg$f66();
2415
+ if (s1) s1 = void 0;
2416
+ else s1 = peg$FAILED;
2417
+ if (s1 !== peg$FAILED) {
2418
+ s2 = peg$parseHtmlClose();
2419
+ if (s2 !== peg$FAILED) {
2420
+ peg$savedPos = peg$currPos;
2421
+ s3 = peg$f67(s2);
2422
+ if (s3) s3 = void 0;
2423
+ else s3 = peg$FAILED;
2424
+ if (s3 !== peg$FAILED) {
2425
+ peg$savedPos = s0;
2426
+ s0 = peg$f68(s2);
2427
+ } else {
2428
+ peg$currPos = s0;
2429
+ s0 = peg$FAILED;
2430
+ }
2431
+ } else {
2432
+ peg$currPos = s0;
2433
+ s0 = peg$FAILED;
2434
+ }
2435
+ } else {
2436
+ peg$currPos = s0;
2437
+ s0 = peg$FAILED;
2438
+ }
2439
+ return s0;
2440
+ }
2441
+ function peg$parseHtmlOpen() {
2442
+ let s0, s1, s2, s3, s4, s5, s6;
2443
+ s0 = peg$currPos;
2444
+ if (input.charCodeAt(peg$currPos) === 60) {
2445
+ s1 = peg$c2;
2446
+ peg$currPos++;
2447
+ } else {
2448
+ s1 = peg$FAILED;
2449
+ if (peg$silentFails === 0) peg$fail(peg$e6);
2450
+ }
2451
+ if (s1 !== peg$FAILED) {
2452
+ s2 = peg$parseTagName();
2453
+ if (s2 !== peg$FAILED) {
2454
+ s3 = [];
2455
+ s4 = peg$parseAttribute();
2456
+ while (s4 !== peg$FAILED) {
2457
+ s3.push(s4);
2458
+ s4 = peg$parseAttribute();
2459
+ }
2460
+ s4 = peg$parse_();
2461
+ if (input.charCodeAt(peg$currPos) === 47) {
2462
+ s5 = peg$c8;
2463
+ peg$currPos++;
2464
+ } else {
2465
+ s5 = peg$FAILED;
2466
+ if (peg$silentFails === 0) peg$fail(peg$e17);
2467
+ }
2468
+ if (s5 === peg$FAILED) s5 = null;
2469
+ if (input.charCodeAt(peg$currPos) === 62) {
2470
+ s6 = peg$c9;
2471
+ peg$currPos++;
2472
+ } else {
2473
+ s6 = peg$FAILED;
2474
+ if (peg$silentFails === 0) peg$fail(peg$e20);
2475
+ }
2476
+ if (s6 !== peg$FAILED) {
2477
+ peg$savedPos = s0;
2478
+ s0 = peg$f69(s2, s3, s5);
2479
+ } else {
2480
+ peg$currPos = s0;
2481
+ s0 = peg$FAILED;
2482
+ }
2483
+ } else {
2484
+ peg$currPos = s0;
2485
+ s0 = peg$FAILED;
2486
+ }
2487
+ } else {
2488
+ peg$currPos = s0;
2489
+ s0 = peg$FAILED;
2490
+ }
2491
+ if (s0 === peg$FAILED) {
2492
+ s0 = peg$currPos;
2493
+ if (input.charCodeAt(peg$currPos) === 60) {
2494
+ s1 = peg$c2;
2495
+ peg$currPos++;
2496
+ } else {
2497
+ s1 = peg$FAILED;
2498
+ if (peg$silentFails === 0) peg$fail(peg$e6);
2499
+ }
2500
+ if (s1 !== peg$FAILED) {
2501
+ s2 = peg$parseTagName();
2502
+ if (s2 !== peg$FAILED) {
2503
+ s3 = peg$currPos;
2504
+ peg$silentFails++;
2505
+ s4 = input.charAt(peg$currPos);
2506
+ if (peg$r10.test(s4)) peg$currPos++;
2507
+ else {
2508
+ s4 = peg$FAILED;
2509
+ if (peg$silentFails === 0) peg$fail(peg$e21);
2510
+ }
2511
+ if (s4 === peg$FAILED) {
2512
+ s4 = peg$currPos;
2513
+ peg$silentFails++;
2514
+ if (input.length > peg$currPos) {
2515
+ s5 = input.charAt(peg$currPos);
2516
+ peg$currPos++;
2517
+ } else {
2518
+ s5 = peg$FAILED;
2519
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2520
+ }
2521
+ peg$silentFails--;
2522
+ if (s5 === peg$FAILED) s4 = void 0;
2523
+ else {
2524
+ peg$currPos = s4;
2525
+ s4 = peg$FAILED;
2526
+ }
2527
+ }
2528
+ peg$silentFails--;
2529
+ if (s4 !== peg$FAILED) {
2530
+ peg$currPos = s3;
2531
+ s3 = void 0;
2532
+ } else s3 = peg$FAILED;
2533
+ if (s3 !== peg$FAILED) {
2534
+ s4 = [];
2535
+ s5 = peg$parseAttribute();
2536
+ while (s5 !== peg$FAILED) {
2537
+ s4.push(s5);
2538
+ s5 = peg$parseAttribute();
2539
+ }
2540
+ s5 = peg$parse_();
2541
+ s6 = peg$parseHere();
2542
+ peg$savedPos = s0;
2543
+ s0 = peg$f70(s2, s6);
2544
+ } else {
2545
+ peg$currPos = s0;
2546
+ s0 = peg$FAILED;
2547
+ }
2548
+ } else {
2549
+ peg$currPos = s0;
2550
+ s0 = peg$FAILED;
2551
+ }
2552
+ } else {
2553
+ peg$currPos = s0;
2554
+ s0 = peg$FAILED;
2555
+ }
2556
+ }
2557
+ return s0;
2558
+ }
2559
+ function peg$parseHtmlClose() {
2560
+ let s0, s1, s2, s4;
2561
+ s0 = peg$currPos;
2562
+ if (input.substr(peg$currPos, 2) === peg$c10) {
2563
+ s1 = peg$c10;
2564
+ peg$currPos += 2;
2565
+ } else {
2566
+ s1 = peg$FAILED;
2567
+ if (peg$silentFails === 0) peg$fail(peg$e22);
2568
+ }
2569
+ if (s1 !== peg$FAILED) {
2570
+ s2 = peg$parseTagName();
2571
+ if (s2 !== peg$FAILED) {
2572
+ peg$parse_();
2573
+ if (input.charCodeAt(peg$currPos) === 62) {
2574
+ s4 = peg$c9;
2575
+ peg$currPos++;
2576
+ } else {
2577
+ s4 = peg$FAILED;
2578
+ if (peg$silentFails === 0) peg$fail(peg$e20);
2579
+ }
2580
+ if (s4 !== peg$FAILED) {
2581
+ peg$savedPos = s0;
2582
+ s0 = peg$f71(s2);
2583
+ } else {
2584
+ peg$currPos = s0;
2585
+ s0 = peg$FAILED;
2586
+ }
2587
+ } else {
2588
+ peg$currPos = s0;
2589
+ s0 = peg$FAILED;
2590
+ }
2591
+ } else {
2592
+ peg$currPos = s0;
2593
+ s0 = peg$FAILED;
2594
+ }
2595
+ return s0;
2596
+ }
2597
+ function peg$parseTagName() {
2598
+ let s0, s1, s2, s3, s4;
2599
+ s0 = peg$currPos;
2600
+ s1 = peg$currPos;
2601
+ s2 = input.charAt(peg$currPos);
2602
+ if (peg$r8.test(s2)) peg$currPos++;
2603
+ else {
2604
+ s2 = peg$FAILED;
2605
+ if (peg$silentFails === 0) peg$fail(peg$e18);
2606
+ }
2607
+ if (s2 !== peg$FAILED) {
2608
+ s3 = [];
2609
+ s4 = input.charAt(peg$currPos);
2610
+ if (peg$r11.test(s4)) peg$currPos++;
2611
+ else {
2612
+ s4 = peg$FAILED;
2613
+ if (peg$silentFails === 0) peg$fail(peg$e23);
2614
+ }
2615
+ while (s4 !== peg$FAILED) {
2616
+ s3.push(s4);
2617
+ s4 = input.charAt(peg$currPos);
2618
+ if (peg$r11.test(s4)) peg$currPos++;
2619
+ else {
2620
+ s4 = peg$FAILED;
2621
+ if (peg$silentFails === 0) peg$fail(peg$e23);
2622
+ }
2623
+ }
2624
+ s2 = [s2, s3];
2625
+ s1 = s2;
2626
+ } else {
2627
+ peg$currPos = s1;
2628
+ s1 = peg$FAILED;
2629
+ }
2630
+ if (s1 !== peg$FAILED) s0 = input.substring(s0, peg$currPos);
2631
+ else s0 = s1;
2632
+ return s0;
2633
+ }
2634
+ function peg$parseAttribute() {
2635
+ let s0, s2, s3, s4, s5, s6, s8;
2636
+ s0 = peg$currPos;
2637
+ peg$parse_();
2638
+ s2 = peg$currPos;
2639
+ s3 = [];
2640
+ s4 = peg$currPos;
2641
+ s5 = peg$currPos;
2642
+ peg$silentFails++;
2643
+ s6 = input.charAt(peg$currPos);
2644
+ if (peg$r12.test(s6)) peg$currPos++;
2645
+ else {
2646
+ s6 = peg$FAILED;
2647
+ if (peg$silentFails === 0) peg$fail(peg$e24);
2648
+ }
2649
+ peg$silentFails--;
2650
+ if (s6 === peg$FAILED) s5 = void 0;
2651
+ else {
2652
+ peg$currPos = s5;
2653
+ s5 = peg$FAILED;
2654
+ }
2655
+ if (s5 !== peg$FAILED) {
2656
+ if (input.length > peg$currPos) {
2657
+ s6 = input.charAt(peg$currPos);
2658
+ peg$currPos++;
2659
+ } else {
2660
+ s6 = peg$FAILED;
2661
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2662
+ }
2663
+ if (s6 !== peg$FAILED) {
2664
+ s5 = [s5, s6];
2665
+ s4 = s5;
2666
+ } else {
2667
+ peg$currPos = s4;
2668
+ s4 = peg$FAILED;
2669
+ }
2670
+ } else {
2671
+ peg$currPos = s4;
2672
+ s4 = peg$FAILED;
2673
+ }
2674
+ if (s4 !== peg$FAILED) while (s4 !== peg$FAILED) {
2675
+ s3.push(s4);
2676
+ s4 = peg$currPos;
2677
+ s5 = peg$currPos;
2678
+ peg$silentFails++;
2679
+ s6 = input.charAt(peg$currPos);
2680
+ if (peg$r12.test(s6)) peg$currPos++;
2681
+ else {
2682
+ s6 = peg$FAILED;
2683
+ if (peg$silentFails === 0) peg$fail(peg$e24);
2684
+ }
2685
+ peg$silentFails--;
2686
+ if (s6 === peg$FAILED) s5 = void 0;
2687
+ else {
2688
+ peg$currPos = s5;
2689
+ s5 = peg$FAILED;
2690
+ }
2691
+ if (s5 !== peg$FAILED) {
2692
+ if (input.length > peg$currPos) {
2693
+ s6 = input.charAt(peg$currPos);
2694
+ peg$currPos++;
2695
+ } else {
2696
+ s6 = peg$FAILED;
2697
+ if (peg$silentFails === 0) peg$fail(peg$e0);
2698
+ }
2699
+ if (s6 !== peg$FAILED) {
2700
+ s5 = [s5, s6];
2701
+ s4 = s5;
2702
+ } else {
2703
+ peg$currPos = s4;
2704
+ s4 = peg$FAILED;
2705
+ }
2706
+ } else {
2707
+ peg$currPos = s4;
2708
+ s4 = peg$FAILED;
2709
+ }
2710
+ }
2711
+ else s3 = peg$FAILED;
2712
+ if (s3 !== peg$FAILED) s2 = input.substring(s2, peg$currPos);
2713
+ else s2 = s3;
2714
+ if (s2 !== peg$FAILED) {
2715
+ peg$savedPos = peg$currPos;
2716
+ s3 = peg$f72(s2);
2717
+ if (s3) s3 = void 0;
2718
+ else s3 = peg$FAILED;
2719
+ if (s3 !== peg$FAILED) {
2720
+ s4 = peg$currPos;
2721
+ s5 = peg$parse_();
2722
+ if (input.charCodeAt(peg$currPos) === 61) {
2723
+ s6 = peg$c11;
2724
+ peg$currPos++;
2725
+ } else {
2726
+ s6 = peg$FAILED;
2727
+ if (peg$silentFails === 0) peg$fail(peg$e25);
2728
+ }
2729
+ if (s6 !== peg$FAILED) {
2730
+ peg$parse_();
2731
+ s8 = peg$parseAttrValue();
2732
+ if (s8 !== peg$FAILED) {
2733
+ peg$savedPos = s4;
2734
+ s4 = peg$f73(s2, s8);
2735
+ } else {
2736
+ peg$currPos = s4;
2737
+ s4 = peg$FAILED;
2738
+ }
2739
+ } else {
2740
+ peg$currPos = s4;
2741
+ s4 = peg$FAILED;
2742
+ }
2743
+ if (s4 === peg$FAILED) s4 = null;
2744
+ peg$savedPos = s0;
2745
+ s0 = peg$f74(s2, s4);
2746
+ } else {
2747
+ peg$currPos = s0;
2748
+ s0 = peg$FAILED;
2749
+ }
2750
+ } else {
2751
+ peg$currPos = s0;
2752
+ s0 = peg$FAILED;
2753
+ }
2754
+ return s0;
2755
+ }
2756
+ function peg$parseAttrValue() {
2757
+ let s0, s1, s2, s3, s4;
2758
+ s0 = peg$currPos;
2759
+ if (input.charCodeAt(peg$currPos) === 34) {
2760
+ s1 = peg$c12;
2761
+ peg$currPos++;
2762
+ } else {
2763
+ s1 = peg$FAILED;
2764
+ if (peg$silentFails === 0) peg$fail(peg$e26);
2765
+ }
2766
+ if (s1 !== peg$FAILED) {
2767
+ s2 = peg$currPos;
2768
+ s3 = [];
2769
+ s4 = peg$parseValuePart();
2770
+ if (s4 === peg$FAILED) {
2771
+ s4 = input.charAt(peg$currPos);
2772
+ if (peg$r13.test(s4)) peg$currPos++;
2773
+ else {
2774
+ s4 = peg$FAILED;
2775
+ if (peg$silentFails === 0) peg$fail(peg$e27);
2776
+ }
2777
+ }
2778
+ while (s4 !== peg$FAILED) {
2779
+ s3.push(s4);
2780
+ s4 = peg$parseValuePart();
2781
+ if (s4 === peg$FAILED) {
2782
+ s4 = input.charAt(peg$currPos);
2783
+ if (peg$r13.test(s4)) peg$currPos++;
2784
+ else {
2785
+ s4 = peg$FAILED;
2786
+ if (peg$silentFails === 0) peg$fail(peg$e27);
2787
+ }
2788
+ }
2789
+ }
2790
+ s2 = input.substring(s2, peg$currPos);
2791
+ if (input.charCodeAt(peg$currPos) === 34) {
2792
+ s3 = peg$c12;
2793
+ peg$currPos++;
2794
+ } else {
2795
+ s3 = peg$FAILED;
2796
+ if (peg$silentFails === 0) peg$fail(peg$e26);
2797
+ }
2798
+ if (s3 !== peg$FAILED) {
2799
+ peg$savedPos = s0;
2800
+ s0 = peg$f75(s2);
2801
+ } else {
2802
+ peg$currPos = s0;
2803
+ s0 = peg$FAILED;
2804
+ }
2805
+ } else {
2806
+ peg$currPos = s0;
2807
+ s0 = peg$FAILED;
2808
+ }
2809
+ if (s0 === peg$FAILED) {
2810
+ s0 = peg$currPos;
2811
+ if (input.charCodeAt(peg$currPos) === 39) {
2812
+ s1 = peg$c13;
2813
+ peg$currPos++;
2814
+ } else {
2815
+ s1 = peg$FAILED;
2816
+ if (peg$silentFails === 0) peg$fail(peg$e28);
2817
+ }
2818
+ if (s1 !== peg$FAILED) {
2819
+ s2 = peg$currPos;
2820
+ s3 = [];
2821
+ s4 = peg$parseValuePart();
2822
+ if (s4 === peg$FAILED) {
2823
+ s4 = input.charAt(peg$currPos);
2824
+ if (peg$r14.test(s4)) peg$currPos++;
2825
+ else {
2826
+ s4 = peg$FAILED;
2827
+ if (peg$silentFails === 0) peg$fail(peg$e29);
2828
+ }
2829
+ }
2830
+ while (s4 !== peg$FAILED) {
2831
+ s3.push(s4);
2832
+ s4 = peg$parseValuePart();
2833
+ if (s4 === peg$FAILED) {
2834
+ s4 = input.charAt(peg$currPos);
2835
+ if (peg$r14.test(s4)) peg$currPos++;
2836
+ else {
2837
+ s4 = peg$FAILED;
2838
+ if (peg$silentFails === 0) peg$fail(peg$e29);
2839
+ }
2840
+ }
2841
+ }
2842
+ s2 = input.substring(s2, peg$currPos);
2843
+ if (input.charCodeAt(peg$currPos) === 39) {
2844
+ s3 = peg$c13;
2845
+ peg$currPos++;
2846
+ } else {
2847
+ s3 = peg$FAILED;
2848
+ if (peg$silentFails === 0) peg$fail(peg$e28);
2849
+ }
2850
+ if (s3 !== peg$FAILED) {
2851
+ peg$savedPos = s0;
2852
+ s0 = peg$f76(s2);
2853
+ } else {
2854
+ peg$currPos = s0;
2855
+ s0 = peg$FAILED;
2856
+ }
2857
+ } else {
2858
+ peg$currPos = s0;
2859
+ s0 = peg$FAILED;
2860
+ }
2861
+ if (s0 === peg$FAILED) {
2862
+ s0 = peg$currPos;
2863
+ s1 = input.charAt(peg$currPos);
2864
+ if (peg$r15.test(s1)) peg$currPos++;
2865
+ else {
2866
+ s1 = peg$FAILED;
2867
+ if (peg$silentFails === 0) peg$fail(peg$e30);
2868
+ }
2869
+ if (s1 !== peg$FAILED) {
2870
+ peg$savedPos = s0;
2871
+ s1 = peg$f77(s1);
2872
+ }
2873
+ s0 = s1;
2874
+ if (s0 === peg$FAILED) {
2875
+ s0 = peg$currPos;
2876
+ s1 = [];
2877
+ s2 = input.charAt(peg$currPos);
2878
+ if (peg$r16.test(s2)) peg$currPos++;
2879
+ else {
2880
+ s2 = peg$FAILED;
2881
+ if (peg$silentFails === 0) peg$fail(peg$e31);
2882
+ }
2883
+ while (s2 !== peg$FAILED) {
2884
+ s1.push(s2);
2885
+ s2 = input.charAt(peg$currPos);
2886
+ if (peg$r16.test(s2)) peg$currPos++;
2887
+ else {
2888
+ s2 = peg$FAILED;
2889
+ if (peg$silentFails === 0) peg$fail(peg$e31);
2890
+ }
2891
+ }
2892
+ s0 = input.substring(s0, peg$currPos);
2893
+ }
2894
+ }
2895
+ }
2896
+ return s0;
2897
+ }
2898
+ function peg$parseValuePart() {
2899
+ let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
2900
+ s0 = peg$currPos;
2901
+ peg$savedPos = peg$currPos;
2902
+ s1 = peg$f78();
2903
+ if (s1) s1 = void 0;
2904
+ else s1 = peg$FAILED;
2905
+ if (s1 !== peg$FAILED) {
2906
+ s2 = peg$currPos;
2907
+ s3 = [];
2908
+ if (input.charCodeAt(peg$currPos) === 92) {
2909
+ s4 = peg$c0;
2910
+ peg$currPos++;
2911
+ } else {
2912
+ s4 = peg$FAILED;
2913
+ if (peg$silentFails === 0) peg$fail(peg$e2);
2914
+ }
2915
+ if (s4 !== peg$FAILED) while (s4 !== peg$FAILED) {
2916
+ s3.push(s4);
2917
+ if (input.charCodeAt(peg$currPos) === 92) {
2918
+ s4 = peg$c0;
2919
+ peg$currPos++;
2920
+ } else {
2921
+ s4 = peg$FAILED;
2922
+ if (peg$silentFails === 0) peg$fail(peg$e2);
2923
+ }
2924
+ }
2925
+ else s3 = peg$FAILED;
2926
+ if (s3 !== peg$FAILED) s2 = input.substring(s2, peg$currPos);
2927
+ else s2 = s3;
2928
+ if (s2 !== peg$FAILED) {
2929
+ s3 = peg$currPos;
2930
+ peg$savedPos = peg$currPos;
2931
+ s4 = peg$f79(s2);
2932
+ if (s4) s4 = void 0;
2933
+ else s4 = peg$FAILED;
2934
+ if (s4 !== peg$FAILED) {
2935
+ s5 = input.charAt(peg$currPos);
2936
+ if (peg$r1.test(s5)) peg$currPos++;
2937
+ else {
2938
+ s5 = peg$FAILED;
2939
+ if (peg$silentFails === 0) peg$fail(peg$e3);
2940
+ }
2941
+ if (s5 !== peg$FAILED) {
2942
+ s4 = [s4, s5];
2943
+ s3 = s4;
2944
+ } else {
2945
+ peg$currPos = s3;
2946
+ s3 = peg$FAILED;
2947
+ }
2948
+ } else {
2949
+ peg$currPos = s3;
2950
+ s3 = peg$FAILED;
2951
+ }
2952
+ if (s3 === peg$FAILED) s3 = null;
2953
+ s1 = [
2954
+ s1,
2955
+ s2,
2956
+ s3
2957
+ ];
2958
+ s0 = s1;
2959
+ } else {
2960
+ peg$currPos = s0;
2961
+ s0 = peg$FAILED;
2962
+ }
2963
+ } else {
2964
+ peg$currPos = s0;
2965
+ s0 = peg$FAILED;
2966
+ }
2967
+ if (s0 === peg$FAILED) {
2968
+ s0 = peg$currPos;
2969
+ peg$savedPos = peg$currPos;
2970
+ s1 = peg$f80();
2971
+ if (s1) s1 = void 0;
2972
+ else s1 = peg$FAILED;
2973
+ if (s1 !== peg$FAILED) {
2974
+ if (input.charCodeAt(peg$currPos) === 123) {
2975
+ s2 = peg$c1;
2976
+ peg$currPos++;
2977
+ } else {
2978
+ s2 = peg$FAILED;
2979
+ if (peg$silentFails === 0) peg$fail(peg$e4);
2980
+ }
2981
+ if (s2 !== peg$FAILED) {
2982
+ s3 = peg$parseSelectors();
2983
+ if (s3 !== peg$FAILED) {
2984
+ s4 = peg$parseHere();
2985
+ if (input.charCodeAt(peg$currPos) === 47) {
2986
+ s5 = peg$c8;
2987
+ peg$currPos++;
2988
+ } else {
2989
+ s5 = peg$FAILED;
2990
+ if (peg$silentFails === 0) peg$fail(peg$e17);
2991
+ }
2992
+ if (s5 === peg$FAILED) s5 = null;
2993
+ s6 = peg$parseMacroName();
2994
+ if (s6 !== peg$FAILED) {
2995
+ s7 = peg$parseHere();
2996
+ peg$savedPos = peg$currPos;
2997
+ s8 = peg$f81(s4, s7);
2998
+ if (s8) s8 = void 0;
2999
+ else s8 = peg$FAILED;
3000
+ if (s8 !== peg$FAILED) {
3001
+ s9 = peg$currPos;
3002
+ s10 = peg$f82(s4, s7);
3003
+ s11 = peg$currPos;
3004
+ s12 = [];
3005
+ if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
3006
+ else if (input.length > peg$currPos) {
3007
+ s13 = input.charAt(peg$currPos);
3008
+ peg$currPos++;
3009
+ } else {
3010
+ s13 = peg$FAILED;
3011
+ if (peg$silentFails === 0) peg$fail(peg$e0);
3012
+ }
3013
+ while (s13 !== peg$FAILED) {
3014
+ s12.push(s13);
3015
+ if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
3016
+ else if (input.length > peg$currPos) {
3017
+ s13 = input.charAt(peg$currPos);
3018
+ peg$currPos++;
3019
+ } else {
3020
+ s13 = peg$FAILED;
3021
+ if (peg$silentFails === 0) peg$fail(peg$e0);
3022
+ }
3023
+ }
3024
+ if (s12.length < (s10 | 0)) {
3025
+ peg$currPos = s11;
3026
+ s11 = peg$FAILED;
3027
+ } else s11 = s12;
3028
+ s10 = s11;
3029
+ if (s10 !== peg$FAILED) s9 = input.substring(s9, peg$currPos);
3030
+ else s9 = s10;
3031
+ if (s9 !== peg$FAILED) {
3032
+ if (input.charCodeAt(peg$currPos) === 125) {
3033
+ s10 = peg$c7;
3034
+ peg$currPos++;
3035
+ } else {
3036
+ s10 = peg$FAILED;
3037
+ if (peg$silentFails === 0) peg$fail(peg$e15);
3038
+ }
3039
+ if (s10 !== peg$FAILED) {
3040
+ s1 = [
3041
+ s1,
3042
+ s2,
3043
+ s3,
3044
+ s4,
3045
+ s5,
3046
+ s6,
3047
+ s7,
3048
+ s8,
3049
+ s9,
3050
+ s10
3051
+ ];
3052
+ s0 = s1;
3053
+ } else {
3054
+ peg$currPos = s0;
3055
+ s0 = peg$FAILED;
3056
+ }
3057
+ } else {
3058
+ peg$currPos = s0;
3059
+ s0 = peg$FAILED;
3060
+ }
3061
+ } else {
3062
+ peg$currPos = s0;
3063
+ s0 = peg$FAILED;
3064
+ }
3065
+ } else {
3066
+ peg$currPos = s0;
3067
+ s0 = peg$FAILED;
3068
+ }
3069
+ } else {
3070
+ peg$currPos = s0;
3071
+ s0 = peg$FAILED;
3072
+ }
3073
+ } else {
3074
+ peg$currPos = s0;
3075
+ s0 = peg$FAILED;
3076
+ }
3077
+ } else {
3078
+ peg$currPos = s0;
3079
+ s0 = peg$FAILED;
3080
+ }
3081
+ if (s0 === peg$FAILED) {
3082
+ s0 = peg$currPos;
3083
+ peg$savedPos = peg$currPos;
3084
+ s1 = peg$f83();
3085
+ if (s1) s1 = void 0;
3086
+ else s1 = peg$FAILED;
3087
+ if (s1 !== peg$FAILED) {
3088
+ if (input.charCodeAt(peg$currPos) === 123) {
3089
+ s2 = peg$c1;
3090
+ peg$currPos++;
3091
+ } else {
3092
+ s2 = peg$FAILED;
3093
+ if (peg$silentFails === 0) peg$fail(peg$e4);
3094
+ }
3095
+ if (s2 !== peg$FAILED) {
3096
+ s3 = peg$parseSelectors();
3097
+ if (s3 !== peg$FAILED) {
3098
+ s4 = peg$currPos;
3099
+ peg$silentFails++;
3100
+ s5 = peg$currPos;
3101
+ if (input.charCodeAt(peg$currPos) === 47) {
3102
+ s6 = peg$c8;
3103
+ peg$currPos++;
3104
+ } else {
3105
+ s6 = peg$FAILED;
3106
+ if (peg$silentFails === 0) peg$fail(peg$e17);
3107
+ }
3108
+ if (s6 === peg$FAILED) s6 = null;
3109
+ s7 = input.charAt(peg$currPos);
3110
+ if (peg$r8.test(s7)) peg$currPos++;
3111
+ else {
3112
+ s7 = peg$FAILED;
3113
+ if (peg$silentFails === 0) peg$fail(peg$e18);
3114
+ }
3115
+ if (s7 !== peg$FAILED) {
3116
+ s6 = [s6, s7];
3117
+ s5 = s6;
3118
+ } else {
3119
+ peg$currPos = s5;
3120
+ s5 = peg$FAILED;
3121
+ }
3122
+ peg$silentFails--;
3123
+ if (s5 === peg$FAILED) s4 = void 0;
3124
+ else {
3125
+ peg$currPos = s4;
3126
+ s4 = peg$FAILED;
3127
+ }
3128
+ if (s4 !== peg$FAILED) {
3129
+ s5 = peg$parseCodeToBrace();
3130
+ if (s5 !== peg$FAILED) {
3131
+ if (input.charCodeAt(peg$currPos) === 125) {
3132
+ s6 = peg$c7;
3133
+ peg$currPos++;
3134
+ } else {
3135
+ s6 = peg$FAILED;
3136
+ if (peg$silentFails === 0) peg$fail(peg$e15);
3137
+ }
3138
+ if (s6 !== peg$FAILED) {
3139
+ s1 = [
3140
+ s1,
3141
+ s2,
3142
+ s3,
3143
+ s4,
3144
+ s5,
3145
+ s6
3146
+ ];
3147
+ s0 = s1;
3148
+ } else {
3149
+ peg$currPos = s0;
3150
+ s0 = peg$FAILED;
3151
+ }
3152
+ } else {
3153
+ peg$currPos = s0;
3154
+ s0 = peg$FAILED;
3155
+ }
3156
+ } else {
3157
+ peg$currPos = s0;
3158
+ s0 = peg$FAILED;
3159
+ }
3160
+ } else {
3161
+ peg$currPos = s0;
3162
+ s0 = peg$FAILED;
3163
+ }
3164
+ } else {
3165
+ peg$currPos = s0;
3166
+ s0 = peg$FAILED;
3167
+ }
3168
+ } else {
3169
+ peg$currPos = s0;
3170
+ s0 = peg$FAILED;
3171
+ }
3172
+ if (s0 === peg$FAILED) {
3173
+ s0 = peg$currPos;
3174
+ peg$savedPos = peg$currPos;
3175
+ s1 = peg$f84();
3176
+ if (s1) s1 = void 0;
3177
+ else s1 = peg$FAILED;
3178
+ if (s1 !== peg$FAILED) {
3179
+ if (input.charCodeAt(peg$currPos) === 123) {
3180
+ s2 = peg$c1;
3181
+ peg$currPos++;
3182
+ } else {
3183
+ s2 = peg$FAILED;
3184
+ if (peg$silentFails === 0) peg$fail(peg$e4);
3185
+ }
3186
+ if (s2 !== peg$FAILED) {
3187
+ s3 = peg$currPos;
3188
+ peg$silentFails++;
3189
+ s4 = input.charAt(peg$currPos);
3190
+ if (peg$r5.test(s4)) peg$currPos++;
3191
+ else {
3192
+ s4 = peg$FAILED;
3193
+ if (peg$silentFails === 0) peg$fail(peg$e13);
3194
+ }
3195
+ peg$silentFails--;
3196
+ if (s4 !== peg$FAILED) {
3197
+ peg$currPos = s3;
3198
+ s3 = void 0;
3199
+ } else s3 = peg$FAILED;
3200
+ if (s3 !== peg$FAILED) {
3201
+ s4 = peg$parseCodeToBrace();
3202
+ if (s4 !== peg$FAILED) {
3203
+ if (input.charCodeAt(peg$currPos) === 125) {
3204
+ s5 = peg$c7;
3205
+ peg$currPos++;
3206
+ } else {
3207
+ s5 = peg$FAILED;
3208
+ if (peg$silentFails === 0) peg$fail(peg$e15);
3209
+ }
3210
+ if (s5 !== peg$FAILED) {
3211
+ s1 = [
3212
+ s1,
3213
+ s2,
3214
+ s3,
3215
+ s4,
3216
+ s5
3217
+ ];
3218
+ s0 = s1;
3219
+ } else {
3220
+ peg$currPos = s0;
3221
+ s0 = peg$FAILED;
3222
+ }
3223
+ } else {
3224
+ peg$currPos = s0;
3225
+ s0 = peg$FAILED;
3226
+ }
3227
+ } else {
3228
+ peg$currPos = s0;
3229
+ s0 = peg$FAILED;
3230
+ }
3231
+ } else {
3232
+ peg$currPos = s0;
3233
+ s0 = peg$FAILED;
3234
+ }
3235
+ } else {
3236
+ peg$currPos = s0;
3237
+ s0 = peg$FAILED;
3238
+ }
3239
+ }
3240
+ }
3241
+ }
3242
+ return s0;
3243
+ }
3244
+ function peg$parse_() {
3245
+ let s0, s1;
3246
+ s0 = [];
3247
+ s1 = peg$parse_c();
3248
+ while (s1 !== peg$FAILED) {
3249
+ s0.push(s1);
3250
+ s1 = peg$parse_c();
3251
+ }
3252
+ return s0;
3253
+ }
3254
+ function peg$parse_c() {
3255
+ let s0;
3256
+ s0 = input.charAt(peg$currPos);
3257
+ if (peg$r17.test(s0)) peg$currPos++;
3258
+ else {
3259
+ s0 = peg$FAILED;
3260
+ if (peg$silentFails === 0) peg$fail(peg$e32);
3261
+ }
3262
+ return s0;
3263
+ }
3264
+ const textMode = options.text === true;
3265
+ const hooks = options.hooks;
3266
+ /** End of the stretch a `.|n|` repetition consumes (see skipTo). */
3267
+ let skipEnd = -1;
3268
+ /** Whether the attribute whose value is being read holds code. */
3269
+ let attrCode = false;
3270
+ /** The macro whose raw body is being read. */
3271
+ let rawName = "";
3272
+ function skipTo(end) {
3273
+ skipEnd = end;
3274
+ return end >= 0;
3275
+ }
3276
+ function fail(message, loc) {
3277
+ error(message, loc);
3278
+ }
3279
+ function attrs(list) {
3280
+ const attributes = {};
3281
+ const seen = /* @__PURE__ */ new Set();
3282
+ for (const [name, value] of list) {
3283
+ const lower = name.toLowerCase();
3284
+ if (seen.has(lower)) continue;
3285
+ seen.add(lower);
3286
+ Object.defineProperty(attributes, name, {
3287
+ value,
3288
+ enumerable: true,
3289
+ writable: true,
3290
+ configurable: true
3291
+ });
3292
+ }
3293
+ return attributes;
3294
+ }
3295
+ /** Check the body of a macro or element against its opening tag. */
3296
+ function checkBody(open, body) {
3297
+ for (const branch of body.branches) {
3298
+ const parent = BRANCH_PARENT[branch.tag.lower];
3299
+ if (open.lex !== "macro" || open.lower !== parent) fail(label(branch.tag) + " must be directly inside {" + parent + RB + ", not inside " + label(open), branch.tag.loc);
3300
+ }
3301
+ if (!body.end) fail("Unclosed " + label(open) + ": no " + closerOf(open) + " closes it", open.loc);
3302
+ if (!sameName(open, body.end)) fail(label(body.end) + " found where " + closerOf(open) + " should close the " + label(open) + " opened at " + at(open.loc), body.end.loc);
3303
+ }
3304
+ function macroNode(tag, body) {
3305
+ const node = {
3306
+ type: "macro",
3307
+ name: tag.lower,
3308
+ rawArgs: tag.rawArgs,
3309
+ children: []
3310
+ };
3311
+ if (!body) return withSelectors(node, tag);
3312
+ checkBody(tag, body);
3313
+ node.children = body.children;
3314
+ if (BRANCHING.has(tag.lower)) {
3315
+ node.branches = [withSelectors({
3316
+ rawArgs: tag.rawArgs,
3317
+ children: body.children
3318
+ }, tag)];
3319
+ for (const b of body.branches) node.branches.push(withSelectors({
3320
+ rawArgs: b.tag.rawArgs,
3321
+ children: b.children
3322
+ }, b.tag));
3323
+ node.children = [];
3324
+ } else withSelectors(node, tag);
3325
+ return node;
3326
+ }
3327
+ peg$result = peg$startRuleFunction();
3328
+ const peg$success = peg$result !== peg$FAILED && peg$currPos === input.length;
3329
+ function peg$throw() {
3330
+ if (peg$result !== peg$FAILED && peg$currPos < input.length) peg$fail(peg$endExpectation());
3331
+ throw peg$buildStructuredError(peg$maxFailExpected, peg$maxFailPos < input.length ? peg$getUnicode(peg$maxFailPos) : null, peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos));
3332
+ }
3333
+ if (options.peg$library) return {
3334
+ peg$result,
3335
+ peg$currPos,
3336
+ peg$FAILED,
3337
+ peg$maxFailExpected,
3338
+ peg$maxFailPos,
3339
+ peg$success,
3340
+ peg$throw: peg$success ? void 0 : peg$throw
3341
+ };
3342
+ if (peg$success) return peg$result;
3343
+ else peg$throw();
3344
+ }
3345
+ //#endregion
3346
+ //#region src/utils/macro-names.ts
3347
+ /**
3348
+ * Macro and widget names are case-insensitive: `{StatusBar}` invokes the
3349
+ * widget defined as `statusbar`. A NameMap keys its entries by the lowercased
3350
+ * name.
3351
+ */
3352
+ var NameMap = class extends Map {
3353
+ get(name) {
3354
+ return super.get(name.toLowerCase());
3355
+ }
3356
+ set(name, value) {
3357
+ return super.set(name.toLowerCase(), value);
3358
+ }
3359
+ has(name) {
3360
+ return super.has(name.toLowerCase());
3361
+ }
3362
+ delete(name) {
3363
+ return super.delete(name.toLowerCase());
3364
+ }
3365
+ };
3366
+ /** A set of macro or widget names (see NameMap). */
3367
+ var NameSet = class {
3368
+ constructor(names = []) {
3369
+ this.names = new NameMap();
3370
+ for (const name of names) this.add(name);
3371
+ }
3372
+ add(name) {
3373
+ this.names.set(name, true);
3374
+ }
3375
+ has(name) {
3376
+ return this.names.has(name);
3377
+ }
3378
+ delete(name) {
3379
+ this.names.delete(name);
3380
+ }
3381
+ };
3382
+ //#endregion
3383
+ //#region src/markup/ast.ts
3384
+ /** Macros that require a closing tag and can contain children */
3385
+ var BLOCK_MACROS = new NameSet([
3386
+ "if",
3387
+ "for",
3388
+ "do",
3389
+ "button",
3390
+ "link",
3391
+ "listbox",
3392
+ "cycle",
3393
+ "switch",
3394
+ "timed",
3395
+ "repeat",
3396
+ "type",
3397
+ "widget",
3398
+ "span",
3399
+ "nobr"
3400
+ ]);
3401
+ /** Whether a macro takes a body closed by `{/name}`. */
3402
+ function isBlockMacro(name) {
3403
+ return BLOCK_MACROS.has(name);
3404
+ }
3405
+ //#endregion
3406
+ //#region src/markup/tokens.ts
3407
+ /** Variable sigils: story ($), temporary (_), local (@), transient (%). */
3408
+ var SIGIL_SCOPES = {
3409
+ $: "variable",
3410
+ _: "temporary",
3411
+ "@": "local",
3412
+ "%": "transient"
3413
+ };
3414
+ Object.fromEntries(Object.entries(SIGIL_SCOPES).map(([sigil, scope]) => [scope, sigil]));
3415
+ new Set(Object.keys(SIGIL_SCOPES));
3416
+ //#endregion
1
3417
  //#region src/js-lexer.ts
2
3418
  /** Transient name after `%`: an identifier, so `%3` is never a reference. */
3
3419
  var TRANS_NAME_RE = /[A-Za-z_]\w*/y;
@@ -277,26 +3693,6 @@ function frameKey(f) {
277
3693
  const kind = f.interpolation ? 4 : FRAME_KINDS.indexOf(f.kind);
278
3694
  return kind < 0 ? void 0 : f.open * 5 + kind;
279
3695
  }
280
- function createJsScanCache() {
281
- return {
282
- checkpoints: /* @__PURE__ */ new Map(),
283
- stacks: /* @__PURE__ */ new Map(),
284
- parens: /* @__PURE__ */ new Map(),
285
- shareAfter: SHARE_AFTER,
286
- braces: /* @__PURE__ */ new Map(),
287
- brackets: /* @__PURE__ */ new Map(),
288
- classes: /* @__PURE__ */ new Map(),
289
- regexes: /* @__PURE__ */ new Map(),
290
- lineEnd: {
291
- from: Infinity,
292
- at: -1
293
- },
294
- commentClose: {
295
- from: Infinity,
296
- at: -1
297
- }
298
- };
299
- }
300
3696
  var newContext = () => ({
301
3697
  lookahead: true,
302
3698
  brackets: /* @__PURE__ */ new Map()
@@ -1105,6 +4501,95 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1105
4501
  }
1106
4502
  }
1107
4503
  //#endregion
4504
+ //#region src/markup/code-end.ts
4505
+ /**
4506
+ * A quote directly after a letter/digit is an apostrophe (don't), not a
4507
+ * string; after a backslash it is an escaped attribute delimiter (\").
4508
+ */
4509
+ var NON_STRING_QUOTE_PREFIX = /[\p{L}\p{N}_\\]/u;
4510
+ /**
4511
+ * Skip the literal opening at `i`: a '…' or "…" string, which must close on
4512
+ * its line, or a `…` template, whose `${…}` parts are read as braces.
4513
+ * Returns the index just past its closing quote or backtick, or -1.
4514
+ */
4515
+ function skipLiteral(input, i) {
4516
+ const quote = input[i];
4517
+ const template = quote === "`";
4518
+ for (let j = i + 1; j < input.length;) {
4519
+ const c = input[j];
4520
+ if (c === "\\") j += 2;
4521
+ else if (c === quote) return j + 1;
4522
+ else if (!template && c === "\n") return -1;
4523
+ else if (template && c === "$" && input[j + 1] === "{") {
4524
+ const close = lenientClose(input, j + 2);
4525
+ if (close === -1) return -1;
4526
+ j = close + 1;
4527
+ } else j++;
4528
+ }
4529
+ return -1;
4530
+ }
4531
+ /**
4532
+ * The closing `}` of braces read leniently from `i` (just past a `{`): braces
4533
+ * count except inside string and template literals, and a quote that can't
4534
+ * start a string (an apostrophe, or one not closed on its line) is text, as
4535
+ * is a backtick without a closing one.
4536
+ */
4537
+ function lenientClose(input, i) {
4538
+ if (memoInput !== input) {
4539
+ memoInput = input;
4540
+ memo.clear();
4541
+ }
4542
+ let end = memo.get(i);
4543
+ if (end === void 0) {
4544
+ end = scanLenient(input, i);
4545
+ memo.set(i, end);
4546
+ }
4547
+ return end;
4548
+ }
4549
+ var memoInput = "";
4550
+ var memo = /* @__PURE__ */ new Map();
4551
+ function scanLenient(input, i) {
4552
+ let depth = 0;
4553
+ while (i < input.length) {
4554
+ const c = input[i];
4555
+ if (c === "{") {
4556
+ depth++;
4557
+ i++;
4558
+ } else if (c === "}") {
4559
+ if (depth === 0) return i;
4560
+ depth--;
4561
+ i++;
4562
+ } else if (c === "`" || (c === "\"" || c === "'") && !(i > 0 && NON_STRING_QUOTE_PREFIX.test(input[i - 1]))) {
4563
+ const close = skipLiteral(input, i);
4564
+ i = close === -1 ? i + 1 : close;
4565
+ } else i++;
4566
+ }
4567
+ return -1;
4568
+ }
4569
+ /** The JavaScript reader Spindle uses today: js-lexer, then a lenient scan. */
4570
+ var defaultCodeEnd = {
4571
+ closeBrace(input, codeStart, lenientStart) {
4572
+ const end = findCodeEnd(input, codeStart);
4573
+ return end !== -1 ? end : lenientClose(input, lenientStart);
4574
+ },
4575
+ rawBodyEnd(input, bodyStart, name) {
4576
+ const closer = `\\{/${name}\\s*\\}`;
4577
+ const first = new RegExp(closer, "gi");
4578
+ first.lastIndex = bodyStart;
4579
+ const firstAt = first.exec(input)?.index ?? -1;
4580
+ if (firstAt === -1) return -1;
4581
+ const at = new RegExp(closer, "iy");
4582
+ const end = findCodeEnd(input, bodyStart, {
4583
+ goal: "statements",
4584
+ stop: (k) => {
4585
+ at.lastIndex = k;
4586
+ return at.test(input);
4587
+ }
4588
+ });
4589
+ return end === -1 ? firstAt : end;
4590
+ }
4591
+ };
4592
+ //#endregion
1108
4593
  //#region src/markup/code-attributes.ts
1109
4594
  /**
1110
4595
  * Attributes whose value is code with braces of its own, not text: event
@@ -1118,811 +4603,558 @@ function isCodeAttribute(name) {
1118
4603
  const lower = name.toLowerCase();
1119
4604
  return lower.startsWith("on") || lower === "pattern" || lower === "srcdoc";
1120
4605
  }
4606
+ //#endregion
4607
+ //#region src/markup/parse.ts
1121
4608
  /**
1122
- * Split a code attribute value into literal text and its `{…}` blocks that
1123
- * start with a sigil. Braces inside strings don't close a block; an
1124
- * unclosed block, any other brace and backslashes are text. A block whose
1125
- * sigil is not followed by a word character (`{$(…)}`) is code, kept
1126
- * verbatim: not resolved, and not decoded like text.
4609
+ * Passage markup parser: the Peggy grammar (spindle.peggy) with Spindle's
4610
+ * hooks plugged in. It builds the AST directly and reports malformed markup
4611
+ * as a MarkupError carrying its line and column.
1127
4612
  */
1128
- function splitSigilTemplate(template) {
1129
- const parts = [];
1130
- const memo = createScanMemo();
1131
- let text = "";
1132
- let i = 0;
1133
- while (i < template.length) {
1134
- const brace = template.indexOf("{", i);
1135
- if (brace === -1) {
1136
- text += template.slice(i);
1137
- break;
1138
- }
1139
- text += template.slice(i, brace);
1140
- i = brace + 1;
1141
- const end = isSigil(template[i]) ? scanBalancedBrace(template, i, memo) : -1;
1142
- if (end === -1) {
1143
- text += "{";
1144
- continue;
1145
- }
1146
- if (text) parts.push({ text });
1147
- text = "";
1148
- const block = template.slice(i, end);
1149
- parts.push(/^.\w/.test(block) ? { expr: block } : { verbatim: `{${block}}` });
1150
- i = end + 1;
4613
+ /** Macros whose body is JavaScript source, kept verbatim. */
4614
+ var RAW_BODY_MACROS = new Set(["do"]);
4615
+ var defaultHooks = {
4616
+ ...defaultCodeEnd,
4617
+ isBlock: isBlockMacro,
4618
+ isRaw: (name) => RAW_BODY_MACROS.has(name),
4619
+ isCodeAttribute
4620
+ };
4621
+ /** Malformed markup, with where it starts (1-based line and column). */
4622
+ var MarkupError = class extends Error {
4623
+ constructor(reason, offset, line, column) {
4624
+ super(`${reason} (line ${line}, column ${column})`);
4625
+ this.reason = reason;
4626
+ this.offset = offset;
4627
+ this.line = line;
4628
+ this.column = column;
4629
+ this.name = "MarkupError";
1151
4630
  }
1152
- if (text) parts.push({ text });
1153
- return parts;
1154
- }
1155
- //#endregion
1156
- //#region src/markup/tokenizer.ts
1157
- /** Variable sigils: story ($), temporary (_), local (@), transient (%). */
1158
- var SIGIL_SCOPES = {
1159
- $: "variable",
1160
- _: "temporary",
1161
- "@": "local",
1162
- "%": "transient"
1163
4631
  };
1164
- Object.fromEntries(Object.entries(SIGIL_SCOPES).map(([sigil, scope]) => [scope, sigil]));
1165
- var SIGIL_CHARS = new Set(Object.keys(SIGIL_SCOPES));
1166
- /** Whether `c` is a variable sigil. */
1167
- function isSigil(c) {
1168
- return c !== void 0 && SIGIL_CHARS.has(c);
1169
- }
1170
- /** Copy the selectors set in `from` onto `target`, and return it. */
1171
- function withSelectors(target, from) {
1172
- if (from.className) target.className = from.className;
1173
- if (from.id) target.id = from.id;
1174
- return target;
4632
+ /** 1-based line and column of `offset` in `text`. */
4633
+ function lineColumn(text, offset) {
4634
+ let line = 1;
4635
+ let lineStart = 0;
4636
+ for (let i = text.indexOf("\n"); i !== -1 && i < offset;) {
4637
+ line++;
4638
+ lineStart = i + 1;
4639
+ i = text.indexOf("\n", lineStart);
4640
+ }
4641
+ return {
4642
+ line,
4643
+ column: offset - lineStart + 1
4644
+ };
4645
+ }
4646
+ function run(source, startRule, options) {
4647
+ const hooks = options.hooks ? {
4648
+ ...defaultHooks,
4649
+ ...options.hooks
4650
+ } : defaultHooks;
4651
+ try {
4652
+ return peg$parse(source, {
4653
+ startRule,
4654
+ text: options.text === true,
4655
+ hooks
4656
+ });
4657
+ } catch (err) {
4658
+ const location = err.location;
4659
+ if (err instanceof Error && err.name === "SyntaxError" && location) {
4660
+ const { offset } = location.start;
4661
+ const { line, column } = lineColumn(source, offset);
4662
+ throw new MarkupError(err.message, offset, line, column);
4663
+ }
4664
+ throw err;
4665
+ }
1175
4666
  }
1176
- /** Tag name must start with a letter (covers standard and custom elements). */
1177
- var VALID_TAG_START = /[a-zA-Z]/;
1178
- /** HTML void elements: never have children or a closing tag. */
1179
- var HTML_VOID_TAGS = new Set([
1180
- "area",
1181
- "base",
1182
- "br",
1183
- "col",
1184
- "embed",
1185
- "hr",
1186
- "img",
1187
- "input",
1188
- "link",
1189
- "meta",
1190
- "param",
1191
- "source",
1192
- "track",
1193
- "wbr"
1194
- ]);
1195
4667
  /**
1196
- * Characters other than a sigil that open an expression after `{`:
1197
- * `{(Math.max($a, 0))}`, `{!$done}`. Other characters that can start an
1198
- * expression (quotes, digits, `[`, `-`) are left out, because braces around
1199
- * them are common as literal text (JSON, regex quantifiers, `{[[link]]}`).
4668
+ * Parse markup into its AST: macros with their bodies and branches, HTML
4669
+ * elements with their children. Throws a MarkupError for malformed markup.
1200
4670
  */
1201
- var EXPRESSION_START = new Set(["(", "!"]);
1202
- /** Macros whose body is JavaScript source, kept verbatim instead of tokenized. */
1203
- var RAW_BODY_MACROS = new Set(["do"]);
4671
+ function parseMarkup(source, options = {}) {
4672
+ return run(source, "Markup", options);
4673
+ }
1204
4674
  /**
1205
- * Link separators, in the order they are tried, and whether the target
1206
- * comes first: display|target, display->target, target<-display.
4675
+ * The flat tokens of markup, without nesting, so unclosed or mismatched
4676
+ * macros and elements are no error here. Throws a MarkupError for a
4677
+ * malformed tag, such as an unclosed `{`, `[[` or attribute value.
1207
4678
  */
1208
- var LINK_SEPARATORS = [
1209
- ["|", false],
1210
- ["->", false],
1211
- ["<-", true]
1212
- ];
4679
+ function tokenizeMarkup(source, options = {}) {
4680
+ return run(source, "Tokens", options);
4681
+ }
4682
+ //#endregion
4683
+ //#region src/utils/parse-delay.ts
1213
4684
  /**
1214
- * Parse a Twine link interior into display and target.
1215
- * Supports: display|target, display->target, target<-display, plain
4685
+ * Parse a delay string into milliseconds.
4686
+ * Supports: "2s" → 2000, "500ms" → 500, bare number → ms
1216
4687
  */
1217
- function parseLink(inner) {
1218
- for (const [separator, targetFirst] of LINK_SEPARATORS) {
1219
- const idx = inner.indexOf(separator);
1220
- if (idx === -1) continue;
1221
- const before = inner.slice(0, idx).trim();
1222
- const after = inner.slice(idx + separator.length).trim();
1223
- return targetFirst ? {
1224
- display: after,
1225
- target: before
1226
- } : {
1227
- display: before,
1228
- target: after
1229
- };
1230
- }
1231
- const trimmed = inner.trim();
1232
- return {
1233
- display: trimmed,
1234
- target: trimmed
1235
- };
4688
+ function parseDelay(str) {
4689
+ const trimmed = str.trim();
4690
+ if (trimmed.endsWith("ms")) return parseFloat(trimmed.slice(0, -2));
4691
+ if (trimmed.endsWith("s")) return parseFloat(trimmed.slice(0, -1)) * 1e3;
4692
+ return parseFloat(trimmed);
1236
4693
  }
4694
+ //#endregion
4695
+ //#region src/components/macros/arg-utils.ts
1237
4696
  /**
1238
- * Parse a macro opening: extract name and rawArgs.
1239
- * e.g. "set $x = 5" → { name: "set", rawArgs: "$x = 5" }
1240
- * e.g. "/if" → { name: "if", rawArgs: "", isClose: true }
1241
- * e.g. "elseif $x > 3" → { name: "elseif", rawArgs: "$x > 3" }
4697
+ * Shared helpers for macros that split or read their raw argument text by
4698
+ * hand. Their lexical model is the expression lexer's (`js-lexer.ts`):
4699
+ * string, template and regex literals and comments are opaque (a backslash
4700
+ * inside a literal escapes the character after it, and `${…}` parts of a
4701
+ * template are code), `/` opens a regex only where an operand is expected,
4702
+ * and `(`/`[`/`{` nest.
1242
4703
  */
1243
- function parseMacroContent(content) {
1244
- const trimmed = content.trim();
1245
- const isClose = trimmed.startsWith("/");
1246
- const rest = isClose ? trimmed.slice(1) : trimmed;
1247
- const spaceIdx = rest.search(/\s/);
1248
- if (spaceIdx === -1) return {
1249
- name: rest,
1250
- rawArgs: "",
1251
- isClose
1252
- };
1253
- return {
1254
- name: rest.slice(0, spaceIdx),
1255
- rawArgs: rest.slice(spaceIdx + 1).trim(),
1256
- isClose
1257
- };
4704
+ /** True for a whitespace character. */
4705
+ function isWhitespace(ch) {
4706
+ return /\s/.test(ch);
4707
+ }
4708
+ function isOpener(ch) {
4709
+ return ch === "(" || ch === "[" || ch === "{";
4710
+ }
4711
+ function isCloser(ch) {
4712
+ return ch === ")" || ch === "]" || ch === "}";
1258
4713
  }
1259
4714
  /**
1260
- * Parse CSS selectors: .foo.bar#baz → { className: "foo bar", id: "baz" }
1261
- * Scans .[a-zA-Z0-9_-]+ and #[a-zA-Z0-9_-]+ segments in any order, and one
1262
- * space after them. Returns space-joined class string, last id wins (each
1263
- * left out if empty), and the position after them: `startIdx` if there are
1264
- * none.
4715
+ * Undo the escapes allowed in a literal quoted macro argument: `\"`, `\'`
4716
+ * and `\\` become `"`, `'` and `\`. Other backslash sequences (`\n`, `\d`)
4717
+ * are kept as written, so code and regex text inside the string survive.
1265
4718
  */
1266
- function parseSelectors(input, startIdx) {
1267
- const classes = [];
1268
- let id = "";
1269
- let i = startIdx;
1270
- while (i < input.length && (input[i] === "." || input[i] === "#")) {
1271
- const prefix = input[i];
1272
- i++;
1273
- let name = "";
1274
- while (i < input.length) if (/[a-zA-Z0-9_-]/.test(input[i])) {
1275
- name += input[i];
1276
- i++;
1277
- } else if (input[i] === "{" && isSigil(input[i + 1])) {
1278
- const braceStart = i;
1279
- i += 2;
1280
- while (i < input.length && /[\w.]/.test(input[i])) i++;
1281
- if (i < input.length && input[i] === "}") {
1282
- i++;
1283
- name += input.slice(braceStart, i);
1284
- } else {
1285
- i = braceStart;
1286
- break;
1287
- }
1288
- } else break;
1289
- if (name) if (prefix === ".") classes.push(name);
1290
- else id = name;
1291
- }
1292
- if (i > startIdx && input[i] === " ") i++;
1293
- return {
1294
- selectors: withSelectors({}, {
1295
- className: classes.join(" "),
1296
- id
1297
- }),
1298
- end: i
1299
- };
4719
+ function unescapeQuoted(body) {
4720
+ return body.replace(/\\(["'\\])/g, "$1");
1300
4721
  }
1301
4722
  /**
1302
- * Parse HTML attributes from a string starting at position j.
1303
- * Returns the attributes and the position after the last attribute.
4723
+ * Read the `"…"` or `'…'` string whose opening quote is at `start`.
4724
+ * Returns its unescaped value and the index just past the closing quote, or
4725
+ * `null` if there is no such quote at `start` or the string is unterminated.
1304
4726
  */
1305
- function parseHtmlAttributes(input, j, memo) {
1306
- const attributes = {};
1307
- const seen = /* @__PURE__ */ new Set();
4727
+ function readQuoted(src, start) {
4728
+ const quote = src[start];
4729
+ if (quote !== "\"" && quote !== "'") return null;
4730
+ const { end, closed } = scanStringLiteral(src, start);
4731
+ if (!closed) return null;
1308
4732
  return {
1309
- attributes,
1310
- endIdx: scanAttributes(input, j, memo, (name, value) => {
1311
- const lower = name.toLowerCase();
1312
- if (seen.has(lower)) return;
1313
- seen.add(lower);
1314
- Object.defineProperty(attributes, name, {
1315
- value,
1316
- enumerable: true,
1317
- writable: true,
1318
- configurable: true
1319
- });
1320
- })
4733
+ value: unescapeQuoted(src.slice(start + 1, end - 1)),
4734
+ end
1321
4735
  };
1322
4736
  }
1323
4737
  /**
1324
- * Scan the attributes of a tag from position j, handing each to `add` when
1325
- * given. Returns the position after the last attribute.
1326
- *
1327
- * Where the attributes end depends only on where an attribute starts, so
1328
- * without `add` the result is recorded for every attribute start passed (in
1329
- * `memo.tag`) and a recorded one is used instead of scanning on. An unquoted
1330
- * value takes in a `<`, so in `<a x=<a x=<a x=…` each opener's scan would
1331
- * otherwise run over all the openers after it, taking quadratic time. A tag
1332
- * whose attributes end in its `>` is consumed, so collecting the attributes
1333
- * of those (with `add`, unrecorded) reads each character once.
4738
+ * The unescaped value of `src` when it is exactly one `"…"` or `'…'` string
4739
+ * (as with `readQuoted`), otherwise `null`.
1334
4740
  */
1335
- function scanAttributes(input, j, memo, add) {
1336
- const passed = [];
1337
- while (j < input.length) {
1338
- while (j < input.length && /\s/.test(input[j])) j++;
1339
- if (!add) {
1340
- const known = memo.tag.get(j);
1341
- if (known !== void 0) {
1342
- j = known;
1343
- break;
1344
- }
1345
- passed.push(j);
1346
- }
1347
- if (j >= input.length || input[j] === ">" || input[j] === "/" && input[j + 1] === ">") break;
1348
- const attrStart = j;
1349
- while (j < input.length && /[a-zA-Z0-9_\-:@]/.test(input[j])) j++;
1350
- const attrName = input.slice(attrStart, j);
1351
- if (!attrName) break;
1352
- let eqIdx = j;
1353
- while (eqIdx < input.length && /\s/.test(input[eqIdx])) eqIdx++;
1354
- if (input[eqIdx] === "=") {
1355
- j = eqIdx + 1;
1356
- while (j < input.length && /\s/.test(input[j])) j++;
1357
- if (input[j] === "\"" || input[j] === "'") {
1358
- const quote = input[j];
1359
- j++;
1360
- const valStart = j;
1361
- j = scanQuotedValue(input, j, quote, isCodeAttribute(attrName), memo);
1362
- add?.(attrName, input.slice(valStart, j));
1363
- if (j < input.length) j++;
1364
- } else {
1365
- const valStart = j;
1366
- const run = memo.unquoted;
1367
- if (j < run.from || j > run.to) {
1368
- run.from = j;
1369
- while (j < input.length && /[^\s>]/.test(input[j])) j++;
1370
- run.to = j;
1371
- }
1372
- j = run.to;
1373
- add?.(attrName, input.slice(valStart, j));
1374
- }
1375
- } else add?.(attrName, "");
1376
- }
1377
- for (const at of passed) memo.tag.set(at, j);
1378
- return j;
4741
+ function readWholeQuoted(src) {
4742
+ const quoted = readQuoted(src, 0);
4743
+ return quoted && quoted.end === src.length ? quoted.value : null;
1379
4744
  }
1380
4745
  /**
1381
- * Index of the quote ending the attribute value that starts at `j`, or the
1382
- * end of the input. A `{…}` interpolation in it is skipped whole, so quotes
1383
- * inside it don't end the value. A code attribute's value is no markup
1384
- * (`isCodeAttribute`): only `{` and a sigil open a reference, other braces
1385
- * and backslashes are text, as `splitSigilTemplate` reads them.
1386
- *
1387
- * The value reads the same from just past an interpolation, however the scan
1388
- * got there, so the end is recorded there (in `memo.value`), and a recorded
1389
- * one is used. An unclosed value that skips interpolations to the end of
1390
- * the input (`<a x="}<a x={<a x="}…`) is then read once, not once per tag.
4746
+ * Strip an optional quote from each end of an unquoted-or-loosely-quoted
4747
+ * argument (`"Red`, `Red'`), leaving at least one character. This is the
4748
+ * lenient fallback for labels that are not one well-formed quoted string.
1391
4749
  */
1392
- function scanQuotedValue(input, j, quote, code, memo) {
1393
- const variant = (quote === "\"" ? 0 : 2) + (code ? 1 : 0);
1394
- const passed = [];
1395
- let checkpoint = true;
1396
- while (j < input.length) {
1397
- if (checkpoint) {
1398
- const known = memo.value.get(j * 4 + variant);
1399
- if (known !== void 0) {
1400
- j = known;
1401
- break;
1402
- }
1403
- passed.push(j * 4 + variant);
1404
- checkpoint = false;
1405
- }
1406
- if (!code && input[j] === "\\") {
1407
- let k = j + 1;
1408
- while (input[k] === "\\") k++;
1409
- j = (input[k] === "{" || input[k] === "}") && (k - j) % 2 === 1 ? k + 1 : k;
1410
- continue;
1411
- }
1412
- if (input[j] === "{") {
1413
- const closeIdx = !code ? scanBlockClose(input, j, memo) : isSigil(input[j + 1]) ? scanBalancedBrace(input, j + 1, memo) : -1;
1414
- if (closeIdx !== -1) {
1415
- j = closeIdx + 1;
1416
- checkpoint = true;
1417
- continue;
1418
- }
1419
- } else if (input[j] === quote) break;
1420
- j++;
1421
- }
1422
- for (const at of passed) memo.value.set(at, j);
1423
- return j;
4750
+ function stripLooseQuotes(src) {
4751
+ return /^["']?(.+?)["']?$/s.exec(src)?.[1] ?? src;
1424
4752
  }
1425
- function createScanMemo() {
1426
- return {
1427
- code: /* @__PURE__ */ new Map(),
1428
- js: createJsScanCache(),
1429
- brace: /* @__PURE__ */ new Map(),
1430
- template: /* @__PURE__ */ new Map(),
1431
- templateText: /* @__PURE__ */ new Map(),
1432
- name: {
1433
- from: 0,
1434
- to: -1
4753
+ /** An operator character, or an operator keyword, at the end of code. */
4754
+ var TRAILING_OPERATOR_RE = /(?:[-+*/%&|^!~=<>?:,.]|(?:^|[^\w$@.])(?:typeof|void|new|delete|in|instanceof|await|yield))$/;
4755
+ /**
4756
+ * Whether `src` ends with an operator that still needs an operand (`$a +`,
4757
+ * `$a+`, `$x ==`, `typeof`), so it cannot be a complete value on its own.
4758
+ * Only code counts: the closing `/` of a regex literal is not division, a
4759
+ * variable reference (`$a`, `%name`, ...) is an operand, trailing comments are skipped,
4760
+ * and a postfix `++` or `--` completes its operand.
4761
+ */
4762
+ function endsWithOperator(src) {
4763
+ let end = -1;
4764
+ lexJs(src, {
4765
+ code(ch, i, nesting) {
4766
+ if (nesting === 0 && !isWhitespace(ch)) end = i + 1;
1435
4767
  },
1436
- unquoted: {
1437
- from: 0,
1438
- to: -1
4768
+ literal(text, _i, nesting) {
4769
+ const comment = text.startsWith("//") || text.startsWith("/*");
4770
+ if (nesting === 0 && !comment) end = -1;
1439
4771
  },
1440
- link: /* @__PURE__ */ new Map(),
1441
- tag: /* @__PURE__ */ new Map(),
1442
- value: /* @__PURE__ */ new Map(),
1443
- rawClose: /* @__PURE__ */ new Map()
1444
- };
4772
+ variable(_sigil, _name, _i, nesting) {
4773
+ if (nesting === 0) end = -1;
4774
+ }
4775
+ });
4776
+ if (end < 0) return false;
4777
+ const code = src.slice(0, end);
4778
+ if (/(?:\+\+|--)$/.test(code)) return false;
4779
+ return TRAILING_OPERATOR_RE.test(code);
1445
4780
  }
1446
4781
  /**
1447
- * Find the } closing the code that starts at position i: a `{$…}`
1448
- * expression from its sigil on, an attribute interpolation. The code is
1449
- * lexed as JavaScript, so only a } in code outside the brackets it opened
1450
- * counts — not one in a string, template or regex literal or a comment —
1451
- * and `/` after an operand divides.
1452
- *
1453
- * Text that is not well-formed JavaScript (an apostrophe, an unterminated
1454
- * literal or comment, unbalanced brackets) is scanned leniently instead, as
1455
- * prose-like macro arguments are: braces count except inside string and
1456
- * template literals, and a quote that can't start a string (apostrophe,
1457
- * escaped, not closed on its line) is text.
1458
- *
1459
- * Returns the index of the closing } or -1 if there is none. Pass the same
1460
- * memo to repeated scans of one input to share their work.
4782
+ * Indices of the characters in `src` that satisfy `isSeparator` and sit at
4783
+ * depth 0: in code outside string, template and regex literals and comments,
4784
+ * and outside `()`, `[]` and `{}` pairs.
1461
4785
  */
1462
- function scanBalancedBrace(input, i, memo = createScanMemo()) {
1463
- return scanClose(input, i, i, memo);
4786
+ function topLevelIndices(src, isSeparator) {
4787
+ const indices = [];
4788
+ let depth = 0;
4789
+ lexJs(src, { code(ch, i, nesting) {
4790
+ if (nesting > 0) return;
4791
+ if (isOpener(ch)) depth++;
4792
+ else if (isCloser(ch)) depth--;
4793
+ else if (depth === 0 && isSeparator(ch)) indices.push(i);
4794
+ } });
4795
+ return indices;
1464
4796
  }
4797
+ //#endregion
4798
+ //#region src/components/macros/macro-args.ts
1465
4799
  /**
1466
- * Index of the } closing the macro whose content (name, then arguments)
1467
- * starts at `contentStart`, or -1. The name runs up to whitespace or the }
1468
- * (as `parseMacroContent` reads it); the arguments after it are code. The
1469
- * lenient scan covers the whole content, as it always has.
4800
+ * The macro argument layer: reads a macro's raw argument text into the
4801
+ * parameters its definition declares (see ParameterDef), so every macro
4802
+ * splits and unquotes its arguments the same way. defineMacro() passes the
4803
+ * result to the macro as `ctx.args`.
4804
+ *
4805
+ * Arguments are read in groups, split at `separator` parameters. A group's
4806
+ * `flag` parameters are taken off its start or end first. Its last
4807
+ * `expression` or `text` parameter (without one, its last parameter) reads
4808
+ * the text the others leave; each parameter before it reads one term from
4809
+ * the start, and each after it one term from the end. Terms are separated by
4810
+ * whitespace outside literals and brackets (see arg-utils.ts), and a term
4811
+ * that follows an operator belongs to the expression before it. A parameter
4812
+ * with nothing to read is unset (a flag or separator is false, options are
4813
+ * empty).
1470
4814
  */
1471
- function scanMacroClose(input, contentStart, memo) {
1472
- const run = memo.name;
1473
- if (contentStart < run.from || contentStart > run.to) {
1474
- let k = contentStart;
1475
- while (k < input.length && input[k] !== "}" && !/\s/.test(input[k])) k++;
1476
- run.from = contentStart;
1477
- run.to = k;
4815
+ /** The terms of `src`: the runs of text between its depth-0 whitespace. */
4816
+ function terms(src) {
4817
+ const spans = [];
4818
+ let start = 0;
4819
+ for (const i of [...topLevelIndices(src, isWhitespace), src.length]) {
4820
+ if (i > start) spans.push([start, i]);
4821
+ start = i + 1;
1478
4822
  }
1479
- return scanClose(input, run.to, contentStart, memo);
4823
+ return spans;
1480
4824
  }
4825
+ var slice = (src, [start, end]) => src.slice(start, end);
1481
4826
  /**
1482
- * Index of the } closing the `{…}` block opened at `open`, read as passage
1483
- * text reads it: a macro (`{name args}`, `{/name}`) has its arguments lexed
1484
- * after its name, an expression (`{$…}`, `{(…)}`, `{!…}`) from its first
1485
- * character, after any `.class#id` selectors. Any other block is scanned as
1486
- * code from just past the {. Returns -1 if it is unclosed.
4827
+ * The value of a `text` argument: one quoted string, or the text with any
4828
+ * loose quotes stripped (`"Red` → `Red`).
1487
4829
  */
1488
- function scanBlockClose(input, open, memo) {
1489
- const at = parseSelectors(input, open + 1).end;
1490
- const first = input[at];
1491
- if (first !== void 0 && (first === "/" || /[a-zA-Z]/.test(first))) return scanMacroClose(input, at, memo);
1492
- return scanBalancedBrace(input, at, memo);
1493
- }
1494
- /** Lex the code from `codeStart`, else scan leniently from `lenientStart`. */
1495
- function scanClose(input, codeStart, lenientStart, memo) {
1496
- let end = memo.code.get(lenientStart);
1497
- if (end === void 0) {
1498
- end = findCodeEnd(input, codeStart, { cache: memo.js });
1499
- if (end === -1) end = scanBraceLenient(input, lenientStart, memo);
1500
- memo.code.set(lenientStart, end);
4830
+ function readText(src) {
4831
+ return readWholeQuoted(src) ?? stripLooseQuotes(src);
4832
+ }
4833
+ function readValue(param, src) {
4834
+ switch (param.type) {
4835
+ case "string": return readWholeQuoted(src) ?? void 0;
4836
+ case "text": return readText(src);
4837
+ case "names": return src.split(",").map((name) => name.trim());
4838
+ case "delay": return parseDelay(src);
4839
+ case "number": return Number(src);
4840
+ case "options": return readOptions(param.parameters ?? [], src);
4841
+ default: return src;
1501
4842
  }
1502
- return end;
1503
4843
  }
1504
- /**
1505
- * Index of the ]] closing the link whose text starts at `from`, or -1. A
1506
- * [[ inside opens a nested pair. Each nested [[ starts a scan that reads the
1507
- * rest the same way, so its result is recorded too: unclosed [[s don't each
1508
- * scan to the end of the input.
1509
- */
1510
- function scanLinkClose(input, from, memo) {
1511
- const known = memo.link.get(from);
1512
- if (known !== void 0) return known;
1513
- const opens = [from];
1514
- let i = from;
1515
- while (i < input.length) if (input[i] === "[" && input[i + 1] === "[") {
1516
- i += 2;
1517
- const inner = memo.link.get(i);
1518
- if (inner === void 0) opens.push(i);
1519
- else if (inner === -1) break;
1520
- else i = inner + 2;
1521
- } else if (input[i] === "]" && input[i + 1] === "]") {
1522
- memo.link.set(opens.pop(), i);
1523
- if (!opens.length) return i;
1524
- i += 2;
1525
- } else i++;
1526
- for (const open of opens) memo.link.set(open, -1);
1527
- return -1;
4844
+ function unset(param) {
4845
+ if (param.type === "flag" || param.type === "separator") return false;
4846
+ return param.type === "options" ? {} : void 0;
4847
+ }
4848
+ var WORD_RE = /\w+/y;
4849
+ var DIGITS_RE = /\d+/y;
4850
+ /** Match the sticky regex `re` at `pos`, returning the matched text. */
4851
+ function matchAt(re, src, pos) {
4852
+ re.lastIndex = pos;
4853
+ return re.exec(src)?.[0] ?? null;
1528
4854
  }
1529
4855
  /**
1530
- * Skip a '…' or "…" string literal opening at i.
1531
- * Returns the index just past the closing quote, or -1 if the string is
1532
- * not closed on the same line (JS strings can't span lines unescaped).
4856
+ * Read keyword options (`goto "X" priority 5 once`). A keyword takes the
4857
+ * quoted string or digit run after it as its value; keywords that aren't
4858
+ * declared, and their values, are skipped.
1533
4859
  */
1534
- function skipQuoted(input, i) {
1535
- const quote = input[i];
1536
- let j = i + 1;
1537
- while (j < input.length) {
1538
- const c = input[j];
1539
- if (c === "\\") j += 2;
1540
- else if (c === quote) return j + 1;
1541
- else if (c === "\n") return -1;
1542
- else j++;
4860
+ function readOptions(params, src) {
4861
+ const options = {};
4862
+ let i = 0;
4863
+ while (i < src.length) {
4864
+ const key = matchAt(WORD_RE, src, i);
4865
+ if (!key) {
4866
+ i = readQuoted(src, i)?.end ?? i + 1;
4867
+ continue;
4868
+ }
4869
+ i += key.length;
4870
+ let val;
4871
+ let j = i;
4872
+ while (j < src.length && isWhitespace(src[j])) j++;
4873
+ if (j > i) {
4874
+ const quoted = readQuoted(src, j);
4875
+ const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
4876
+ if (quoted) {
4877
+ val = quoted.value;
4878
+ i = quoted.end;
4879
+ } else if (digits) {
4880
+ val = digits;
4881
+ i = j + digits.length;
4882
+ }
4883
+ }
4884
+ const param = params.find((p) => p.name === key);
4885
+ if (param?.type === "flag") options[key] = true;
4886
+ else if (param?.type === "number") options[key] = Number(val ?? 0);
4887
+ else if (param) options[key] = val;
1543
4888
  }
1544
- return -1;
4889
+ return options;
1545
4890
  }
4891
+ /** `inline %name` reads a transient variable; `inline % 2` is modulo. */
4892
+ var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
1546
4893
  /**
1547
- * A quote directly after a letter/digit is an apostrophe (don't), not a
1548
- * string; after a backslash it is an escaped attribute delimiter (\").
4894
+ * The rest of `src` without the flag `word` as its last or first term, or
4895
+ * `null` if it has no such flag. Next to a binary operator the word is an
4896
+ * operand (`"a" + inline`), not a flag; the closing `/` of a regex literal
4897
+ * is not one.
1549
4898
  */
1550
- var NON_STRING_QUOTE_PREFIX = /[\p{L}\p{N}_\\]/u;
4899
+ function takeFlag(src, word) {
4900
+ const spans = terms(src);
4901
+ if (spans.length < 2) return null;
4902
+ if (slice(src, spans[spans.length - 1]) === word) {
4903
+ const rest = src.slice(0, spans[spans.length - 2][1]);
4904
+ if (!endsWithOperator(rest)) return rest;
4905
+ }
4906
+ if (slice(src, spans[0]) === word) {
4907
+ const rest = src.slice(spans[1][0]);
4908
+ if (!LEADING_OPERATOR_RE.test(rest)) return rest;
4909
+ }
4910
+ return null;
4911
+ }
1551
4912
  /**
1552
- * Scan for the balanced closing } starting at position i (just past the {).
1553
- * Braces inside string and template literals are ignored. A quote that
1554
- * can't start a string (apostrophe, escaped, unterminated) counts as text,
1555
- * and so does a backtick without a closing one.
1556
- * Returns the index of the closing } or -1 if unbalanced.
1557
- *
1558
- * Template literals and their ${…} parts are scans on a stack, not
1559
- * recursive calls, so deep nesting can't overflow the call stack. How the
1560
- * text reads depends only on where a scan is, so a scan passing a point —
1561
- * just past a {, a string or a template literal — goes on as a scan
1562
- * starting there would: its result there is recorded too, and a recorded
1563
- * result is used instead of scanning on. Scans from many starts in one
1564
- * input then take about linear time.
4913
+ * Where the separator `word` splits `src`, as the end of the text before it
4914
+ * and the start of the text after it, or `null` if it doesn't. A word is a
4915
+ * term with terms on both sides; `=` is an assignment at depth 0, not part
4916
+ * of `==` or `!=`.
1565
4917
  */
1566
- function scanBraceLenient(input, i, memo) {
1567
- const known = memo.brace.get(i);
1568
- if (known !== void 0) return known;
1569
- const stack = [{
1570
- template: false,
1571
- start: i,
1572
- levels: [[]]
1573
- }];
1574
- for (;;) {
1575
- const scan = stack[stack.length - 1];
1576
- let end;
1577
- if (scan.template) {
1578
- let point = true;
1579
- while (end === void 0 && i < input.length) {
1580
- if (point) {
1581
- const known = memo.templateText.get(i);
1582
- if (known !== void 0) {
1583
- end = known;
1584
- break;
1585
- }
1586
- scan.passed.push(i);
1587
- point = false;
1588
- }
1589
- const c = input[i];
1590
- if (c === "\\") {
1591
- i += 2;
1592
- point = true;
1593
- } else if (c === "`") end = i + 1;
1594
- else if (c === "$" && input[i + 1] === "{") {
1595
- const inner = memo.brace.get(i + 2);
1596
- if (inner === void 0) break;
1597
- if (inner === -1) end = -1;
1598
- else {
1599
- i = inner + 1;
1600
- point = true;
1601
- }
1602
- } else i++;
1603
- }
1604
- if (end === void 0 && i < input.length) {
1605
- stack.push({
1606
- template: false,
1607
- start: i + 2,
1608
- levels: [[]]
1609
- });
1610
- i += 2;
1611
- continue;
1612
- }
1613
- end ?? (end = -1);
1614
- } else {
1615
- const { levels } = scan;
1616
- /** The } at `close` ends the innermost level. */
1617
- const closeLevel = (close) => {
1618
- for (const at of levels.pop()) memo.brace.set(at, close);
1619
- if (!levels.length) end = close;
1620
- };
1621
- while (end === void 0 && i < input.length) {
1622
- const c = input[i];
1623
- let at = -1;
1624
- if (c === "{") {
1625
- levels.push([]);
1626
- at = ++i;
1627
- } else if (c === "}") closeLevel(i++);
1628
- else if ((c === "\"" || c === "'") && !(i > 0 && NON_STRING_QUOTE_PREFIX.test(input[i - 1]))) {
1629
- const close = skipQuoted(input, i);
1630
- if (close === -1) i++;
1631
- else at = i = close;
1632
- } else if (c === "`") {
1633
- const close = memo.template.get(i);
1634
- if (close === void 0) break;
1635
- if (close === -1) i++;
1636
- else at = i = close;
1637
- } else i++;
1638
- if (at === -1) continue;
1639
- const known = memo.brace.get(at);
1640
- if (known === void 0) levels[levels.length - 1].push(at);
1641
- else if (known === -1) i = input.length;
1642
- else {
1643
- closeLevel(known);
1644
- i = known + 1;
1645
- }
1646
- }
1647
- if (end === void 0 && i < input.length) {
1648
- stack.push({
1649
- template: true,
1650
- start: i,
1651
- levels: [],
1652
- passed: []
1653
- });
1654
- i++;
4918
+ function findSeparator(src, word) {
4919
+ if (word === "=") {
4920
+ const indices = topLevelIndices(src, (ch) => ch === "=");
4921
+ for (let k = 0; k < indices.length; k++) {
4922
+ const i = indices[k];
4923
+ if (src[i + 1] === "=") {
4924
+ k++;
1655
4925
  continue;
1656
4926
  }
1657
- if (end === void 0) {
1658
- end = -1;
1659
- for (const level of levels) for (const at of level) memo.brace.set(at, -1);
1660
- }
4927
+ if (src[i - 1] !== "!") return [i, i + 1];
1661
4928
  }
1662
- stack.pop();
1663
- (scan.template ? memo.template : memo.brace).set(scan.start, end);
1664
- for (const at of scan.passed ?? []) memo.templateText.set(at, end);
1665
- const parent = stack[stack.length - 1];
1666
- if (!parent) return end;
1667
- if (parent.template) i = end === -1 ? input.length : end + 1;
1668
- else i = end === -1 ? scan.start + 1 : end;
4929
+ return null;
1669
4930
  }
4931
+ const spans = terms(src);
4932
+ for (let k = 1; k < spans.length - 1; k++) if (slice(src, spans[k]) === word) return [spans[k - 1][1], spans[k + 1][0]];
4933
+ return null;
1670
4934
  }
1671
- /**
1672
- * Single-pass tokenizer for Twine passage content.
1673
- * Recognizes: [[links]], {$variable}, {_temporary}, {macroName args}
1674
- */
1675
- function tokenize(input, options = {}) {
1676
- const textMode = options.text === true;
1677
- const tokens = [];
1678
- const memo = createScanMemo();
1679
- let i = 0;
1680
- let textStart = 0;
1681
- /** Push a text token for the text not yet tokenized before `end`. */
1682
- function flushText(end) {
1683
- if (end > textStart) {
1684
- tokens.push({
1685
- type: "text",
1686
- value: input.slice(textStart, end),
1687
- start: textStart,
1688
- end
1689
- });
1690
- textStart = end;
1691
- }
4935
+ var takesRest = (param) => param.type === void 0 || param.type === "expression" || param.type === "text";
4936
+ /** Read the parameters of one group from `src` (`null`: no text) into `args`. */
4937
+ function readGroup(params, src, args) {
4938
+ let text = src?.trim() ?? "";
4939
+ for (const flag of params.filter((p) => p.type === "flag")) {
4940
+ const rest = src === null ? null : takeFlag(text, flag.name);
4941
+ args[flag.name] = rest !== null;
4942
+ text = rest ?? text;
1692
4943
  }
1693
- /** Push `token` and go on after it. */
1694
- function emit(token) {
1695
- tokens.push(token);
1696
- i = textStart = token.end;
4944
+ const positional = params.filter((p) => p.type !== "flag");
4945
+ if (positional.length === 0) return;
4946
+ const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
4947
+ const restIndex = lastText < 0 ? positional.length - 1 : lastText;
4948
+ const read = (param, value) => value === "" ? unset(param) : readValue(param, value);
4949
+ if (positional.length === 1) {
4950
+ args[positional[0].name] = read(positional[0], text);
4951
+ return;
1697
4952
  }
1698
- /**
1699
- * After an opening raw-body macro ({do}), emit everything up to its
1700
- * {/name} as a single text token so JavaScript source is not parsed as
1701
- * markup. The body is lexed as JavaScript statements: a {/do} in code ends
1702
- * it, one inside a string, template or regex literal or a comment does
1703
- * not. If the body is not well-formed JavaScript up to a {/do} in code,
1704
- * the first {/do} ends it. Without a closer, nothing is consumed and the
1705
- * AST builder reports it.
1706
- */
1707
- function consumeRawBody(name, isClose) {
1708
- const lower = name.toLowerCase();
1709
- if (isClose || !RAW_BODY_MACROS.has(lower)) return;
1710
- const closer = `\\{/${lower}\\s*\\}`;
1711
- let last = memo.rawClose.get(lower);
1712
- if (!last || i < last.from || last.at >= 0 && i > last.at) {
1713
- const firstRe = new RegExp(closer, "gi");
1714
- firstRe.lastIndex = i;
1715
- last = {
1716
- from: i,
1717
- at: firstRe.exec(input)?.index ?? -1
1718
- };
1719
- memo.rawClose.set(lower, last);
1720
- }
1721
- const first = last.at;
1722
- if (first === -1) return;
1723
- const atRe = new RegExp(closer, "iy");
1724
- const closerAt = (k) => {
1725
- atRe.lastIndex = k;
1726
- return atRe.test(input);
1727
- };
1728
- let closeStart = findCodeEnd(input, i, {
1729
- goal: "statements",
1730
- stop: closerAt,
1731
- stopKey: lower,
1732
- cache: memo.js
1733
- });
1734
- if (closeStart === -1) closeStart = first;
1735
- atRe.lastIndex = closeStart;
1736
- const closeEnd = closeStart + atRe.exec(input)[0].length;
1737
- flushText(closeStart);
1738
- emit({
1739
- type: "macro",
1740
- ...parseMacroContent(input.slice(closeStart + 1, closeEnd - 1)),
1741
- start: closeStart,
1742
- end: closeEnd
1743
- });
4953
+ const spans = terms(text);
4954
+ let from = 0;
4955
+ let to = spans.length;
4956
+ for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, slice(text, spans[from++])) : unset(param);
4957
+ for (const param of positional.slice(restIndex + 1).reverse()) {
4958
+ const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? read(param, slice(text, spans[to - 1])) : void 0;
4959
+ args[param.name] = value ?? unset(param);
4960
+ if (value !== void 0) to--;
1744
4961
  }
1745
- /**
1746
- * Push an expression token for the `{…}` block opened at `start` whose
1747
- * expression starts at `exprStart`, flushing the text before it first.
1748
- * Returns false, consuming nothing, if the block is unclosed.
1749
- */
1750
- function pushExpression(exprStart, start, selectors) {
1751
- const closeIdx = scanBalancedBrace(input, exprStart, memo);
1752
- if (closeIdx === -1) return false;
1753
- flushText(start);
1754
- emit(withSelectors({
1755
- type: "expression",
1756
- expression: input.slice(exprStart, closeIdx),
1757
- start,
1758
- end: closeIdx + 1
1759
- }, selectors));
1760
- return true;
4962
+ const rest = positional[restIndex];
4963
+ args[rest.name] = from < to ? read(rest, text.slice(spans[from][0], spans[to - 1][1])) : unset(rest);
4964
+ }
4965
+ /** Read `rawArgs` into the declared `parameters` (see above). */
4966
+ function parseMacroArgs(rawArgs, parameters) {
4967
+ const args = {};
4968
+ let rest = rawArgs.trim();
4969
+ let groupStart = 0;
4970
+ parameters.forEach((separator, i) => {
4971
+ if (separator.type !== "separator") return;
4972
+ const at = rest === null ? null : findSeparator(rest, separator.name);
4973
+ args[separator.name] = at !== null;
4974
+ const before = at ? rest.slice(0, at[0]) : rest;
4975
+ readGroup(parameters.slice(groupStart, i), before, args);
4976
+ rest = at ? rest.slice(at[1]) : null;
4977
+ groupStart = i + 1;
4978
+ });
4979
+ readGroup(parameters.slice(groupStart), rest, args);
4980
+ return args;
4981
+ }
4982
+ //#endregion
4983
+ //#region src/widgets/widget-def.ts
4984
+ /**
4985
+ * Reading widget definitions without rendering anything: the `{widget}`
4986
+ * macro, startup and markup validation (also in tooling) share these.
4987
+ */
4988
+ /** A {widget} definition's name, then its `@` parameters. */
4989
+ var WIDGET_PARAMETERS = [{
4990
+ name: "name",
4991
+ type: "text",
4992
+ required: true
4993
+ }, {
4994
+ name: "parameters",
4995
+ type: "expression"
4996
+ }];
4997
+ /**
4998
+ * The widget a {widget} definition's arguments declare: its name and its
4999
+ * parameters, the words after it that start with `@` (docs/widgets.md).
5000
+ * Other words, such as `$name`, are not parameters.
5001
+ */
5002
+ function widgetDef({ name = "", parameters = "" }) {
5003
+ return {
5004
+ name,
5005
+ params: parameters.split(/\s+/).filter((word) => word.startsWith("@"))
5006
+ };
5007
+ }
5008
+ /** Read the arguments of a {widget} definition (see widgetDef). */
5009
+ function parseWidgetDef(rawArgs) {
5010
+ return widgetDef(parseMacroArgs(rawArgs, WIDGET_PARAMETERS));
5011
+ }
5012
+ var BLOCK_WIDGET = /\{widget\s+["']?(\w+)["']?[^}]*\}([\s\S]*?)\{\/widget\}/g;
5013
+ /**
5014
+ * The names of the block widgets (those whose body renders `{@children}`)
5015
+ * that StoryInit and the passages tagged `widget` define. They must be
5016
+ * known as block macros before any passage is parsed, so that passages
5017
+ * invoking them nest their content whatever the passage order.
5018
+ */
5019
+ function blockWidgetNames(passages) {
5020
+ const names = [];
5021
+ for (const passage of passages) {
5022
+ if (passage.name !== "StoryInit" && !passage.tags?.includes("widget")) continue;
5023
+ for (const match of passage.content.matchAll(BLOCK_WIDGET)) if (/\{@children\}/.test(match[2])) names.push(match[1]);
1761
5024
  }
1762
- /**
1763
- * Push a variable token for the `{…}` block opened at `start` whose sigil
1764
- * is at `sigilAt`: `{$name}`, `{_name.field.subfield}`. Anything else
1765
- * after the name makes the block an expression from the sigil on
1766
- * (`{$expr[...]}`). Returns false, consuming nothing, if it is unclosed.
1767
- */
1768
- function pushVariable(sigilAt, start, selectors) {
1769
- let nameEnd = sigilAt + 1;
1770
- while (nameEnd < input.length && /[\w.]/.test(input[nameEnd])) nameEnd++;
1771
- if (input[nameEnd] !== "}") return pushExpression(sigilAt, start, selectors);
1772
- emit(withSelectors({
1773
- type: "variable",
1774
- name: input.slice(sigilAt + 1, nameEnd),
1775
- scope: SIGIL_SCOPES[input[sigilAt]],
1776
- start,
1777
- end: nameEnd + 1
1778
- }, selectors));
1779
- return true;
5025
+ return names;
5026
+ }
5027
+ //#endregion
5028
+ //#region src/markup/validate.ts
5029
+ /**
5030
+ * Markup validation: parse every passage when the story starts and report
5031
+ * malformed markup and unknown macros with the passage, line and column
5032
+ * they are at, instead of failing when the passage renders. Tooling runs it
5033
+ * too (see src/tooling.ts).
5034
+ */
5035
+ /** Passages that hold no markup. */
5036
+ var NOT_MARKUP = new Set(["StoryVariables", "StoryTransients"]);
5037
+ var NOT_MARKUP_TAGS = ["script", "stylesheet"];
5038
+ /** Branch macros, which their parent macro renders. */
5039
+ var BRANCH_MACROS = new Set([
5040
+ "elseif",
5041
+ "else",
5042
+ "case",
5043
+ "default",
5044
+ "next"
5045
+ ]);
5046
+ /** A diagnostic as one line of text. */
5047
+ function formatDiagnostic(d) {
5048
+ const source = d.file ? ` (${d.file}${d.fileLine ? `:${d.fileLine}` : ""})` : "";
5049
+ return `Passage "${d.passage}", line ${d.line}, column ${d.column}${source}: ${d.message}`;
5050
+ }
5051
+ /** Edit distance, for "did you mean" suggestions. */
5052
+ function distance(a, b) {
5053
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
5054
+ for (let i = 1; i <= a.length; i++) {
5055
+ const row = [i];
5056
+ for (let j = 1; j <= b.length; j++) row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
5057
+ prev = row;
1780
5058
  }
1781
- /**
1782
- * Push a macro token for the `{…}` block opened at `start` whose content
1783
- * (name, then arguments) starts at `contentStart`, then consume the body
1784
- * of a raw-body macro. Returns false, consuming nothing, if the block is
1785
- * unclosed.
1786
- */
1787
- function pushMacro(contentStart, start, selectors) {
1788
- const closeIdx = scanMacroClose(input, contentStart, memo);
1789
- if (closeIdx === -1) return false;
1790
- const token = withSelectors({
1791
- type: "macro",
1792
- ...parseMacroContent(input.slice(contentStart, closeIdx)),
1793
- start,
1794
- end: closeIdx + 1
1795
- }, selectors);
1796
- emit(token);
1797
- consumeRawBody(token.name, token.isClose);
1798
- return true;
5059
+ return prev[b.length];
5060
+ }
5061
+ /** " Did you mean {name}?" for the closest known name, if one is close. */
5062
+ function suggestion(name, names) {
5063
+ const lower = name.toLowerCase();
5064
+ let best = "";
5065
+ let bestDistance = Math.max(2, Math.floor(lower.length / 3)) + 1;
5066
+ for (const candidate of names) {
5067
+ const d = distance(lower, candidate.toLowerCase());
5068
+ if (d < bestDistance) {
5069
+ best = candidate;
5070
+ bestDistance = d;
5071
+ }
1799
5072
  }
1800
- while (i < input.length) {
1801
- if (input[i] === "\\") {
1802
- let k = i + 1;
1803
- while (input[k] === "\\") k++;
1804
- const next = input[k];
1805
- if (textMode && (next === "{" || next === "}")) {
1806
- const escaped = (k - i) % 2 === 1;
1807
- const end = escaped ? k + 1 : k;
1808
- const value = "\\".repeat(k - i >> 1) + (escaped ? next : "");
1809
- flushText(i);
1810
- if (value) tokens.push({
1811
- type: "text",
1812
- value,
1813
- start: i,
1814
- end
1815
- });
1816
- i = textStart = end;
1817
- continue;
1818
- }
1819
- if ((next === "{" || next === "}") && (k - i) % 2 === 1) {
1820
- flushText(k - 1);
1821
- emit({
1822
- type: "text",
1823
- value: next,
1824
- start: k - 1,
1825
- end: k + 1
1826
- });
1827
- continue;
1828
- }
1829
- i = k;
1830
- continue;
5073
+ return best ? ` Did you mean {${best}}?` : "";
5074
+ }
5075
+ /** The name of the widget a `{widget}` with these arguments defines. */
5076
+ function widgetName(rawArgs) {
5077
+ try {
5078
+ return parseWidgetDef(rawArgs).name.toLowerCase() || void 0;
5079
+ } catch {
5080
+ return;
5081
+ }
5082
+ }
5083
+ /** Validate the markup of every passage. */
5084
+ function validateMarkup(passages, options) {
5085
+ const diagnostics = [];
5086
+ const hooks = options.isBlockMacro ? { isBlock: options.isBlockMacro } : void 0;
5087
+ const report = (passage, offset, message) => {
5088
+ const { line, column } = lineColumn(passage.content, offset);
5089
+ const d = {
5090
+ passage: passage.name,
5091
+ line,
5092
+ column,
5093
+ message
5094
+ };
5095
+ const file = passage.metadata?.["data-source-file"];
5096
+ if (file) {
5097
+ d.file = file;
5098
+ const header = Number(passage.metadata?.["data-source-line"]);
5099
+ if (header) d.fileLine = header + line;
1831
5100
  }
1832
- if (!textMode && input[i] === "[" && input[i + 1] === "[") {
1833
- flushText(i);
1834
- const start = i;
1835
- const { selectors, end: innerStart } = parseSelectors(input, i + 2);
1836
- const closeIdx = scanLinkClose(input, innerStart, memo);
1837
- if (closeIdx === -1) {
1838
- i = start + 2;
1839
- continue;
5101
+ diagnostics.push(d);
5102
+ };
5103
+ const parsed = [];
5104
+ const widgets = /* @__PURE__ */ new Set();
5105
+ for (const passage of passages) {
5106
+ if (NOT_MARKUP.has(passage.name)) continue;
5107
+ if (passage.tags?.some((tag) => NOT_MARKUP_TAGS.includes(tag))) continue;
5108
+ const reported = !options.only || options.only(passage);
5109
+ try {
5110
+ const tokens = tokenizeMarkup(passage.content);
5111
+ for (const token of tokens) {
5112
+ if (token.type !== "macro" || token.isClose) continue;
5113
+ if (token.name.toLowerCase() !== "widget") continue;
5114
+ const name = widgetName(token.rawArgs);
5115
+ if (name) widgets.add(name);
1840
5116
  }
1841
- emit(withSelectors({
1842
- type: "link",
1843
- ...parseLink(input.slice(innerStart, closeIdx)),
1844
- start,
1845
- end: closeIdx + 2
1846
- }, selectors));
1847
- continue;
5117
+ if (!reported) continue;
5118
+ parseMarkup(passage.content, { hooks });
5119
+ parsed.push([passage, tokens]);
5120
+ } catch (err) {
5121
+ if (!(err instanceof MarkupError)) throw err;
5122
+ if (reported) report(passage, err.offset, err.reason);
1848
5123
  }
1849
- if (input[i] === "{") {
1850
- const start = i;
1851
- const { selectors, end: at } = parseSelectors(input, i + 1);
1852
- const prefixed = at > i + 1;
1853
- if (prefixed) flushText(start);
1854
- const c = input[at];
1855
- if (isSigil(c)) {
1856
- flushText(start);
1857
- if (pushVariable(at, start, selectors)) continue;
1858
- } else if (EXPRESSION_START.has(c)) {
1859
- if (pushExpression(at, start, selectors)) continue;
1860
- } else if (c !== void 0 && (/[a-zA-Z]/.test(c) || c === "/" && !prefixed)) {
1861
- flushText(start);
1862
- if (pushMacro(at, start, selectors)) continue;
1863
- }
1864
- i = start + 1;
1865
- continue;
5124
+ }
5125
+ const names = [...options.macroNames ?? [], ...widgets];
5126
+ /** Report the unknown macros among `tokens`, which start at `base`. */
5127
+ const checkMacros = (passage, tokens, base, where) => {
5128
+ for (const token of tokens) {
5129
+ if (token.type !== "macro" || token.isClose) continue;
5130
+ const lower = token.name.toLowerCase();
5131
+ if (BRANCH_MACROS.has(lower) || widgets.has(lower) || options.isKnownMacro(lower)) continue;
5132
+ report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${suggestion(token.name, names)}`);
1866
5133
  }
1867
- if (!textMode && input[i] === "<") {
1868
- const start = i;
1869
- let j = i + 1;
1870
- const isClose = input[j] === "/";
1871
- if (isClose) j++;
1872
- const tagStart = j;
1873
- while (j < input.length && /[a-zA-Z0-9-]/.test(input[j])) j++;
1874
- const tag = input.slice(tagStart, j);
1875
- const tagLower = tag.toLowerCase();
1876
- if (tag && VALID_TAG_START.test(tag[0])) if (isClose) {
1877
- while (j < input.length && /\s/.test(input[j])) j++;
1878
- if (input[j] === ">") {
1879
- j++;
1880
- flushText(start);
1881
- if (HTML_VOID_TAGS.has(tagLower)) {
1882
- i = textStart = j;
1883
- continue;
1884
- }
1885
- emit({
1886
- type: "html",
1887
- tag,
1888
- attributes: {},
1889
- isClose: true,
1890
- isSelfClose: false,
1891
- start,
1892
- end: j
1893
- });
1894
- continue;
1895
- }
1896
- } else {
1897
- const attrsStart = j;
1898
- j = scanAttributes(input, attrsStart, memo);
1899
- let isSelfClose = HTML_VOID_TAGS.has(tagLower);
1900
- if (input[j] === "/") {
1901
- isSelfClose = true;
1902
- j++;
1903
- }
1904
- if (input[j] === ">") {
1905
- j++;
1906
- flushText(start);
1907
- emit({
1908
- type: "html",
1909
- tag,
1910
- attributes: parseHtmlAttributes(input, attrsStart, memo).attributes,
1911
- isClose: false,
1912
- isSelfClose,
1913
- start,
1914
- end: j
5134
+ };
5135
+ for (const [passage, tokens] of parsed) {
5136
+ checkMacros(passage, tokens, 0, "");
5137
+ for (const token of tokens) {
5138
+ if (token.type !== "html") continue;
5139
+ for (const [attr, value] of Object.entries(token.attributes)) {
5140
+ if (isCodeAttribute(attr) || !value.includes("{")) continue;
5141
+ const found = passage.content.indexOf(value, token.start);
5142
+ const at = found === -1 ? token.start : found;
5143
+ const where = `In the ${attr} attribute of <${token.tag}>: `;
5144
+ try {
5145
+ parseMarkup(value, {
5146
+ text: true,
5147
+ hooks
1915
5148
  });
1916
- continue;
5149
+ checkMacros(passage, tokenizeMarkup(value, { text: true }), at, where);
5150
+ } catch (err) {
5151
+ if (!(err instanceof MarkupError)) throw err;
5152
+ report(passage, found === -1 ? at : at + err.offset, where + err.reason);
1917
5153
  }
1918
5154
  }
1919
- i++;
1920
- continue;
1921
5155
  }
1922
- i++;
1923
5156
  }
1924
- flushText(input.length);
1925
- return tokens;
5157
+ return diagnostics;
1926
5158
  }
1927
5159
  //#endregion
1928
5160
  //#region src/utils/error-message.ts
@@ -1973,22 +5205,11 @@ function declarationRegex(sigil) {
1973
5205
  const escaped = sigil.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1974
5206
  return new RegExp(`^${escaped}(\\w+)\\s*=\\s*(.+)$`);
1975
5207
  }
1976
- /** A `$name` reference with its dot path, at a `$` the lexer found. */
1977
- var VAR_PATH_RE = /\$(\w+(?:\.\w+)*)/y;
1978
- /** Quoted first argument of an input macro naming a story variable. */
1979
- var QUOTED_VAR_ARG_RE = /^["']\$(\w+(?:\.\w+)*)["']?$/;
1980
- var FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
1981
5208
  var VALID_VAR_TYPES = new Set([
1982
5209
  "number",
1983
5210
  "string",
1984
5211
  "boolean"
1985
5212
  ]);
1986
- /** Boxed sample values whose members a primitive of each type can access. */
1987
- var PRIMITIVE_SAMPLES = {
1988
- string: Object(""),
1989
- number: Object(0),
1990
- boolean: Object(false)
1991
- };
1992
5213
  function inferSchema(value) {
1993
5214
  if (Array.isArray(value)) return { type: "array" };
1994
5215
  if (value !== null && typeof value === "object") {
@@ -2039,186 +5260,34 @@ function parseStoryVariables(content, sigil = "$") {
2039
5260
  }
2040
5261
  return schema;
2041
5262
  }
5263
+ //#endregion
5264
+ //#region src/tooling.ts
2042
5265
  /**
2043
- * Extract for-loop local variable names from passage content.
2044
- * `{for @item of ...}` → "item"
2045
- * `{for @index, @item of ...}` → "index", "item"
2046
- */
2047
- function extractForLocals(content) {
2048
- const locals = /* @__PURE__ */ new Set();
2049
- let match;
2050
- FOR_LOCAL_RE.lastIndex = 0;
2051
- while ((match = FOR_LOCAL_RE.exec(content)) !== null) {
2052
- locals.add(match[1]);
2053
- if (match[2]) locals.add(match[2]);
2054
- }
2055
- return locals;
2056
- }
2057
- /**
2058
- * Validate a single variable reference path (e.g. "player.health") against
2059
- * the schema. Returns an error message or null if valid.
2060
- */
2061
- function validateRef(ref, schema, forLocals) {
2062
- const parts = ref.split(".");
2063
- const rootName = parts[0];
2064
- const nameError = variableNameError(rootName, "$" + rootName);
2065
- if (nameError) return nameError;
2066
- if (forLocals.has(rootName)) return null;
2067
- const rootSchema = schema.get(rootName);
2068
- if (!rootSchema) return `Undeclared variable: $${ref}`;
2069
- let current = rootSchema;
2070
- for (let i = 1; i < parts.length; i++) {
2071
- const part = parts[i];
2072
- if (current.type === "array") return null;
2073
- const sample = PRIMITIVE_SAMPLES[current.type];
2074
- if (sample && part in sample) {
2075
- const memberType = typeof sample[part];
2076
- if (!VALID_VAR_TYPES.has(memberType)) return null;
2077
- current = { type: memberType };
2078
- continue;
2079
- }
2080
- if (current.type !== "object" || !current.fields) return `Cannot access field "${part}" on $${parts.slice(0, i).join(".")} (type: ${current.type})`;
2081
- const fieldSchema = current.fields.get(part);
2082
- if (!fieldSchema) return null;
2083
- current = fieldSchema;
2084
- }
2085
- return null;
2086
- }
2087
- /**
2088
- * Built-in input macros whose first argument names the bound story variable,
2089
- * quoted or not (e.g. `{textbox "$name"}`).
2090
- */
2091
- var BUILTIN_STORE_VAR_MACROS = [
2092
- "checkbox",
2093
- "cycle",
2094
- "listbox",
2095
- "numberbox",
2096
- "radiobutton",
2097
- "textarea",
2098
- "textbox"
2099
- ];
2100
- var NO_STORE_VAR_MACROS = /* @__PURE__ */ new Set();
2101
- /**
2102
- * Scan the value of a code attribute (`onclick`, see isCodeAttribute) for
2103
- * the `{$…}` references resolved in it; its other braces are code.
2104
- */
2105
- function scanSigilReferences(value, onRef) {
2106
- for (const part of splitSigilTemplate(value)) if ("expr" in part) scanCode(part.expr, onRef);
2107
- }
2108
- /**
2109
- * Scan literal text (string contents, HTML attribute values) for the markup
2110
- * that labels and HTML attributes evaluate at runtime: `{$…}` displays,
2111
- * expressions and macros, read as in passage text. A bare `$word` in
2112
- * literal text is not a reference.
2113
- */
2114
- function scanInterpolations(text, onRef, storeVarMacros = NO_STORE_VAR_MACROS) {
2115
- if (!text.includes("{")) return;
2116
- collectTokenRefs(text, tokenize(text, { text: true }), storeVarMacros, onRef);
2117
- }
2118
- /**
2119
- * Report `$var` references in JavaScript code, lexed as the expression
2120
- * engine lexes it (`lexJs`): references in code, but none inside string or
2121
- * regex literals or comments, nor property names (`a.$b`, `{ $b: 1 }`).
2122
- * The text of string and template literals is scanned for the markup that
2123
- * labels evaluate (`{$…}`); the `${…}` parts of template literals are code.
2124
- */
2125
- function scanCode(code, onRef, goal = "expression") {
2126
- /** Open template literals: their text so far, null in an interpolation. */
2127
- const templates = [];
2128
- lexJs(code, {
2129
- variable(sigil, _name, index) {
2130
- if (sigil !== "$") return;
2131
- VAR_PATH_RE.lastIndex = index;
2132
- onRef(VAR_PATH_RE.exec(code)[1]);
2133
- },
2134
- literal(text, _index, nesting) {
2135
- const template = templates[templates.length - 1];
2136
- if (template?.nesting === nesting) if (template.text === null) template.text = "";
2137
- else if (text === "`" || text === "${") {
2138
- scanInterpolations(template.text, onRef);
2139
- if (text === "`") templates.pop();
2140
- else template.text = null;
2141
- } else template.text += text;
2142
- else if (text === "`") templates.push({
2143
- nesting,
2144
- text: ""
2145
- });
2146
- else if (text[0] === "\"" || text[0] === "'") {
2147
- const { closed } = scanStringLiteral(text, 0);
2148
- scanInterpolations(text.slice(1, closed ? -1 : void 0), onRef);
2149
- }
2150
- }
2151
- }, goal);
2152
- for (const t of templates) if (t.text) scanInterpolations(t.text, onRef);
2153
- }
2154
- /**
2155
- * Report the `$var` references a passage evaluates at runtime: `{$var}`
2156
- * displays, `{$expr}` expressions, macro arguments and `{do}` bodies (as
2157
- * code), quoted variable names bound by input macros, and `{$…}`
2158
- * interpolations in HTML attributes. Prose is literal text and not scanned.
5266
+ * What the tooling entry point (`@rohal12/spindle/tooling`) bundles as
5267
+ * dist/pkg/story-variables.js: the StoryVariables parser and the markup
5268
+ * validator, with no Preact or DOM dependencies. pkg/tooling.js re-exports
5269
+ * them, binding the validator to its macro metadata.
2159
5270
  */
2160
- function collectPassageRefs(content, storeVarMacros, onRef) {
2161
- collectTokenRefs(content, tokenize(content), storeVarMacros, onRef);
2162
- }
2163
- /** Report the `$var` references in the tokens of `content`. */
2164
- function collectTokenRefs(content, tokens, storeVarMacros, onRef) {
2165
- for (let t = 0; t < tokens.length; t++) {
2166
- const token = tokens[t];
2167
- if (token.type === "variable") {
2168
- if (token.scope === "variable" && token.name) onRef(token.name);
2169
- } else if (token.type === "expression") scanCode(token.expression, onRef);
2170
- else if (token.type === "html") for (const [name, value] of Object.entries(token.attributes)) if (isCodeAttribute(name)) scanSigilReferences(value, onRef);
2171
- else scanInterpolations(value, onRef, storeVarMacros);
2172
- else if (token.type === "macro" && !token.isClose) {
2173
- scanCode(token.rawArgs, onRef);
2174
- if (storeVarMacros.has(token.name.toLowerCase())) {
2175
- const first = token.rawArgs.trim().split(/\s+/)[0] ?? "";
2176
- const quoted = QUOTED_VAR_ARG_RE.exec(first);
2177
- if (quoted) onRef(quoted[1]);
2178
- }
2179
- if (token.name === "do") {
2180
- let close = t + 1;
2181
- while (close < tokens.length) {
2182
- const c = tokens[close];
2183
- if (c.type === "macro" && c.isClose && c.name === "do") break;
2184
- close++;
2185
- }
2186
- if (close < tokens.length) {
2187
- scanCode(content.slice(token.end, tokens[close].start), onRef, "statements");
2188
- t = close;
2189
- }
2190
- }
2191
- }
2192
- }
2193
- }
2194
5271
  /**
2195
- * Scan all passages for $var references, check against schema.
2196
- * Returns list of error messages (empty = valid).
2197
- *
2198
- * `storeVarMacros` lists the input macros whose first argument names a bound
2199
- * variable; it defaults to the built-in ones.
5272
+ * Validate the markup of a story's passages as Spindle does when the story
5273
+ * starts, against the given macros (built-in and user-defined): malformed
5274
+ * markup and unknown macros, with their passage, line and column.
2200
5275
  */
2201
- function validatePassages(passages, schema, storeVarMacros = BUILTIN_STORE_VAR_MACROS) {
2202
- const errors = [];
2203
- const storeVarSet = new Set(Array.from(storeVarMacros, (m) => m.toLowerCase()));
2204
- for (const [name, passage] of passages) {
2205
- if (name === "StoryVariables" || name === "StoryTransients") continue;
2206
- const forLocals = extractForLocals(passage.content);
2207
- collectPassageRefs(passage.content, storeVarSet, (ref) => {
2208
- const error = validateRef(ref, schema, forLocals);
2209
- if (error) errors.push(`Passage "${name}": ${error}`);
2210
- });
5276
+ function validateStoryMarkup(passages, macros) {
5277
+ const list = [...passages];
5278
+ const known = /* @__PURE__ */ new Set();
5279
+ const blocks = new Set(blockWidgetNames(list).map((n) => n.toLowerCase()));
5280
+ for (const macro of macros) {
5281
+ const name = macro.name.toLowerCase();
5282
+ known.add(name);
5283
+ for (const sub of macro.subMacros) known.add(sub.toLowerCase());
5284
+ if (macro.block) blocks.add(name);
2211
5285
  }
2212
- return errors;
2213
- }
2214
- /**
2215
- * Extract default values from the schema as a record without a prototype,
2216
- * like the namespaces it initializes (see utils/namespace.ts).
2217
- */
2218
- function extractDefaults(schema) {
2219
- const defaults = createNamespace();
2220
- for (const [name, varSchema] of schema) defaults[name] = varSchema.default;
2221
- return defaults;
5286
+ return validateMarkup(list, {
5287
+ isKnownMacro: (name) => known.has(name),
5288
+ macroNames: known,
5289
+ isBlockMacro: (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name)
5290
+ });
2222
5291
  }
2223
5292
  //#endregion
2224
- export { extractDefaults, parseStoryVariables, validatePassages };
5293
+ export { formatDiagnostic, parseStoryVariables, validateStoryMarkup };