@optionfactory/fml 8.0.3 → 9.0.0-rc10

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/LICENSE.md +7 -0
  2. package/README.md +87 -0
  3. package/dist/client-errors.iife.js +30 -9
  4. package/dist/client-errors.iife.js.map +1 -1
  5. package/dist/client-errors.iife.min.js +1 -1
  6. package/dist/client-errors.iife.min.js.map +1 -1
  7. package/dist/custom-elements.json +1622 -415
  8. package/dist/fml.css +21 -10
  9. package/dist/fml.css.map +1 -1
  10. package/dist/fml.d.mts +3 -1322
  11. package/dist/fml.iife.js +5767 -2313
  12. package/dist/fml.iife.js.map +1 -1
  13. package/dist/fml.iife.min.js +1 -1
  14. package/dist/fml.iife.min.js.map +1 -1
  15. package/dist/fml.min.mjs +1 -1
  16. package/dist/fml.min.mjs.map +1 -1
  17. package/dist/fml.mjs +6 -8737
  18. package/dist/fml.mjs.map +1 -1
  19. package/dist/ftl.d.mts +430 -92
  20. package/dist/ftl.iife.js +1314 -809
  21. package/dist/ftl.iife.js.map +1 -1
  22. package/dist/ftl.iife.min.js +1 -1
  23. package/dist/ftl.iife.min.js.map +1 -1
  24. package/dist/ftl.min.mjs +1 -1
  25. package/dist/ftl.min.mjs.map +1 -1
  26. package/dist/ftl.mjs +1313 -810
  27. package/dist/ftl.mjs.map +1 -1
  28. package/dist/ful.css +21 -10
  29. package/dist/ful.css.map +1 -1
  30. package/dist/ful.d.mts +973 -252
  31. package/dist/ful.iife.js +4176 -1416
  32. package/dist/ful.iife.js.map +1 -1
  33. package/dist/ful.iife.min.js +1 -1
  34. package/dist/ful.iife.min.js.map +1 -1
  35. package/dist/ful.min.mjs +1 -1
  36. package/dist/ful.min.mjs.map +1 -1
  37. package/dist/ful.mjs +4163 -1416
  38. package/dist/ful.mjs.map +1 -1
  39. package/dist/httpc.d.mts +114 -19
  40. package/dist/httpc.iife.js +253 -83
  41. package/dist/httpc.iife.js.map +1 -1
  42. package/dist/httpc.iife.min.js +1 -1
  43. package/dist/httpc.iife.min.js.map +1 -1
  44. package/dist/httpc.min.mjs +1 -1
  45. package/dist/httpc.min.mjs.map +1 -1
  46. package/dist/httpc.mjs +250 -84
  47. package/dist/httpc.mjs.map +1 -1
  48. package/dist/vscode.html-custom-data.json +660 -58
  49. package/dist/web-types.json +1577 -354
  50. package/package.json +16 -8
package/dist/ftl.mjs CHANGED
@@ -1,3 +1,43 @@
1
+ /**
2
+ * A Map bounded by entry count, evicting in insertion order: not an LRU (a hit
3
+ * does not refresh an entry), just a cap keeping unbounded key spaces (parsed
4
+ * expressions, compiled masks, formatter instances) from growing forever. The
5
+ * capacity is sized so eviction never happens on a sane page: hitting it means
6
+ * dynamically generated keys, where FIFO's worst case (evicting a hot entry)
7
+ * costs one recomputation.
8
+ */
9
+ class BoundedCache {
10
+ #max;
11
+ #entries = new Map();
12
+ /** @param {number} max */
13
+ constructor(max) {
14
+ this.#max = max;
15
+ }
16
+ /**
17
+ * The cached value for the key, computing and caching it on a miss. A
18
+ * computed null or undefined is cached like any value; a throwing compute
19
+ * caches nothing.
20
+ * @template K, V
21
+ * @param {K} key
22
+ * @param {(key: K) => V} compute
23
+ * @returns {V}
24
+ */
25
+ getOrCompute(key, compute) {
26
+ if (this.#entries.has(key)) {
27
+ return this.#entries.get(key);
28
+ }
29
+ const value = compute(key);
30
+ if (this.#entries.size >= this.#max) {
31
+ this.#entries.delete(this.#entries.keys().next().value);
32
+ }
33
+ this.#entries.set(key, value);
34
+ return value;
35
+ }
36
+ get size() {
37
+ return this.#entries.size;
38
+ }
39
+ }
40
+
1
41
  const nodes = {
2
42
  ter: Symbol('ternary'),
3
43
  elv: Symbol('elvis'),
@@ -35,6 +75,16 @@ const nodes = {
35
75
  //
36
76
  // https://peggyjs.org/
37
77
 
78
+ //keyword classification lives here, not in the grammar's ordered choice: the
79
+ //identifier rule consumes the whole word and the action decides, so no keyword
80
+ //can grab a longer identifier's prefix. A Map, not a plain object: `in` on the
81
+ //latter would classify inherited names such as `constructor` as keywords
82
+ const keywords = new Map([
83
+ ['true', true],
84
+ ['false', false],
85
+ ['null', null],
86
+ ['undefined', undefined],
87
+ ]);
38
88
 
39
89
  class peg$SyntaxError extends SyntaxError {
40
90
  constructor(message, expected, found, location) {
@@ -219,22 +269,18 @@ function peg$parse(input, options) {
219
269
  const peg$c32 = "(";
220
270
  const peg$c34 = ",";
221
271
  const peg$c36 = ")";
222
- const peg$c39 = "true";
223
- const peg$c40 = "false";
224
- const peg$c41 = "null";
225
- const peg$c42 = "undefined";
226
- const peg$c43 = "-";
227
- const peg$c44 = "'";
228
- const peg$c45 = "\"";
229
- const peg$c46 = "`";
230
- const peg$c47 = "}";
231
- const peg$c49 = "#";
232
- const peg$c53 = "==";
233
- const peg$c54 = "!=";
234
- const peg$c55 = ">=";
235
- const peg$c56 = ">";
236
- const peg$c57 = "<=";
237
- const peg$c58 = "<";
272
+ const peg$c39 = "-";
273
+ const peg$c40 = "'";
274
+ const peg$c41 = "\"";
275
+ const peg$c42 = "`";
276
+ const peg$c43 = "}";
277
+ const peg$c45 = "#";
278
+ const peg$c49 = "==";
279
+ const peg$c50 = "!=";
280
+ const peg$c51 = ">=";
281
+ const peg$c52 = ">";
282
+ const peg$c53 = "<=";
283
+ const peg$c54 = "<";
238
284
 
239
285
  const peg$r0 = /^[^\\{]/;
240
286
  const peg$r1 = /^[0-9]/;
@@ -242,7 +288,7 @@ function peg$parse(input, options) {
242
288
  const peg$r3 = /^[^"]/;
243
289
  const peg$r4 = /^[^\\`{]/;
244
290
  const peg$r5 = /^[a-zA-Z$_]/;
245
- const peg$r6 = /^[a-zA-Z$_0-9_]/;
291
+ const peg$r6 = /^[a-zA-Z$_0-9]/;
246
292
  const peg$r7 = /^[ \t\n\r]/;
247
293
 
248
294
  const peg$e0 = peg$literalExpectation("{{{{", false);
@@ -273,40 +319,33 @@ function peg$parse(input, options) {
273
319
  const peg$e25 = peg$literalExpectation(",", false);
274
320
  const peg$e26 = peg$literalExpectation(")", false);
275
321
  const peg$e27 = peg$otherExpectation("module-function-call");
276
- const peg$e28 = peg$otherExpectation("boolean-literal");
277
- const peg$e29 = peg$literalExpectation("true", false);
278
- const peg$e30 = peg$literalExpectation("false", false);
279
- const peg$e31 = peg$otherExpectation("null-literal");
280
- const peg$e32 = peg$literalExpectation("null", false);
281
- const peg$e33 = peg$otherExpectation("undefined-literal");
282
- const peg$e34 = peg$literalExpectation("undefined", false);
283
- const peg$e35 = peg$otherExpectation("number-literal");
284
- const peg$e36 = peg$literalExpectation("-", false);
285
- const peg$e37 = peg$classExpectation([["0", "9"]], false, false, false);
286
- const peg$e38 = peg$otherExpectation("string-literal");
287
- const peg$e39 = peg$literalExpectation("'", false);
288
- const peg$e40 = peg$classExpectation(["'"], true, false, false);
289
- const peg$e41 = peg$literalExpectation("\"", false);
290
- const peg$e42 = peg$classExpectation(["\""], true, false, false);
291
- const peg$e43 = peg$literalExpectation("`", false);
292
- const peg$e44 = peg$otherExpectation("tstring-expression");
293
- const peg$e45 = peg$literalExpectation("}", false);
294
- const peg$e46 = peg$otherExpectation("tstring-literal");
295
- const peg$e47 = peg$classExpectation(["\\", "`", "{"], true, false, false);
296
- const peg$e48 = peg$otherExpectation("array-literal");
297
- const peg$e49 = peg$otherExpectation("dict-literal");
298
- const peg$e50 = peg$otherExpectation("module-function");
299
- const peg$e51 = peg$literalExpectation("#", false);
300
- const peg$e52 = peg$classExpectation([["a", "z"], ["A", "Z"], "$", "_"], false, false, false);
301
- const peg$e53 = peg$classExpectation([["a", "z"], ["A", "Z"], "$", "_", ["0", "9"], "_"], false, false, false);
302
- const peg$e54 = peg$otherExpectation("symbol");
303
- const peg$e55 = peg$literalExpectation("==", false);
304
- const peg$e56 = peg$literalExpectation("!=", false);
305
- const peg$e57 = peg$literalExpectation(">=", false);
306
- const peg$e58 = peg$literalExpectation(">", false);
307
- const peg$e59 = peg$literalExpectation("<=", false);
308
- const peg$e60 = peg$literalExpectation("<", false);
309
- const peg$e61 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
322
+ const peg$e28 = peg$otherExpectation("number-literal");
323
+ const peg$e29 = peg$literalExpectation("-", false);
324
+ const peg$e30 = peg$classExpectation([["0", "9"]], false, false, false);
325
+ const peg$e31 = peg$otherExpectation("string-literal");
326
+ const peg$e32 = peg$literalExpectation("'", false);
327
+ const peg$e33 = peg$classExpectation(["'"], true, false, false);
328
+ const peg$e34 = peg$literalExpectation("\"", false);
329
+ const peg$e35 = peg$classExpectation(["\""], true, false, false);
330
+ const peg$e36 = peg$literalExpectation("`", false);
331
+ const peg$e37 = peg$otherExpectation("tstring-expression");
332
+ const peg$e38 = peg$literalExpectation("}", false);
333
+ const peg$e39 = peg$otherExpectation("tstring-literal");
334
+ const peg$e40 = peg$classExpectation(["\\", "`", "{"], true, false, false);
335
+ const peg$e41 = peg$otherExpectation("array-literal");
336
+ const peg$e42 = peg$otherExpectation("dict-literal");
337
+ const peg$e43 = peg$otherExpectation("module-function");
338
+ const peg$e44 = peg$literalExpectation("#", false);
339
+ const peg$e45 = peg$classExpectation([["a", "z"], ["A", "Z"], "$", "_"], false, false, false);
340
+ const peg$e46 = peg$classExpectation([["a", "z"], ["A", "Z"], "$", "_", ["0", "9"]], false, false, false);
341
+ const peg$e47 = peg$otherExpectation("identifier-or-keyword");
342
+ const peg$e48 = peg$literalExpectation("==", false);
343
+ const peg$e49 = peg$literalExpectation("!=", false);
344
+ const peg$e50 = peg$literalExpectation(">=", false);
345
+ const peg$e51 = peg$literalExpectation(">", false);
346
+ const peg$e52 = peg$literalExpectation("<=", false);
347
+ const peg$e53 = peg$literalExpectation("<", false);
348
+ const peg$e54 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
310
349
 
311
350
  function peg$f0(value) { return {type:nodes.templated.ten, value} }
312
351
  function peg$f1(value) { return {type:nodes.templated.teh, value} }
@@ -350,8 +389,8 @@ function peg$parse(input, options) {
350
389
  }
351
390
  function peg$f16(expr) { return {type:nodes.not, expr} }
352
391
  function peg$f17(lhs, rhs) { return {type: nodes.access, lhs, rhs} }
353
- function peg$f18(op, rhs) {
354
- return {type: nodes.member, ns: op === '?.', rhs: rhs.value};
392
+ function peg$f18(op, rhs) {
393
+ return {type: nodes.member, ns: op === '?.', rhs: rhs};
355
394
  }
356
395
  function peg$f19(op, rhs) {
357
396
  return {type: nodes.subscript, ns: op !== null, rhs};
@@ -364,41 +403,29 @@ function peg$parse(input, options) {
364
403
  const args = h === null ? [] : [h, ...t];
365
404
  return {type: nodes.call, value: fn, args};
366
405
  }
367
- function peg$f23() {
368
- return {type: nodes.literal, value: true};
369
- }
370
- function peg$f24() {
371
- return {type: nodes.literal, value: false};
372
- }
373
- function peg$f25() {
374
- return {type: nodes.literal, value: null};
375
- }
376
- function peg$f26() {
377
- return {type: nodes.literal, value: undefined};
378
- }
379
- function peg$f27(value) {
406
+ function peg$f23(value) {
380
407
  return { type: nodes.literal, value: parseFloat(value) };
381
408
  }
382
- function peg$f28(value) { return {type: nodes.literal, value}; }
383
- function peg$f29(value) { return {type: nodes.literal, value}; }
384
- function peg$f30(parts) {
409
+ function peg$f24(value) { return {type: nodes.literal, value}; }
410
+ function peg$f25(value) { return {type: nodes.literal, value}; }
411
+ function peg$f26(parts) {
385
412
  return {type: nodes.tstring, parts};
386
413
  }
387
- function peg$f31(expr) {
414
+ function peg$f27(expr) {
388
415
  return expr;
389
416
  }
390
- function peg$f32(c) { return c; }
391
- function peg$f33(parts) {
417
+ function peg$f28(c) { return c; }
418
+ function peg$f29(parts) {
392
419
  return { type: nodes.literal, value: parts.join('') };
393
420
  }
394
- function peg$f34(char) {
421
+ function peg$f30(char) {
395
422
  return { type: nodes.literal, value: char };
396
423
  }
397
- function peg$f35(h, t) {
424
+ function peg$f31(h, t) {
398
425
  const value = h === null ? [] : [h, ...t];
399
426
  return {type: nodes.array, value};
400
427
  }
401
- function peg$f36(h, t) {
428
+ function peg$f32(h, t) {
402
429
  var value = [];
403
430
  if (h !== null){
404
431
  value.push(h);
@@ -406,11 +433,13 @@ function peg$parse(input, options) {
406
433
  value.push.apply(value, t);
407
434
  return {type: nodes.dict, value};
408
435
  }
409
- function peg$f37(module, v) {
436
+ function peg$f33(module, v) {
410
437
  return {type: nodes.function, module, value: v}
411
438
  }
412
- function peg$f38(s) {
413
- return {type: nodes.symbol, value: s};
439
+ function peg$f34(s) {
440
+ return keywords.has(s)
441
+ ? {type: nodes.literal, value: keywords.get(s)}
442
+ : {type: nodes.symbol, value: s};
414
443
  }
415
444
  let peg$currPos = options.peg$currPos | 0;
416
445
  const peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -538,7 +567,7 @@ function peg$parse(input, options) {
538
567
  function peg$parseTemplatedRoot() {
539
568
  let s0, s1;
540
569
 
541
- const key = peg$currPos * 40 + 0;
570
+ const key = peg$currPos * 38 + 0;
542
571
  const cached = peg$resultsCache[key];
543
572
 
544
573
  if (cached) {
@@ -562,7 +591,7 @@ function peg$parse(input, options) {
562
591
  function peg$parseTemplatedExpression() {
563
592
  let s0, s1, s2, s3, s4;
564
593
 
565
- const key = peg$currPos * 40 + 1;
594
+ const key = peg$currPos * 38 + 1;
566
595
  const cached = peg$resultsCache[key];
567
596
 
568
597
  if (cached) {
@@ -667,7 +696,7 @@ function peg$parse(input, options) {
667
696
  function peg$parseBeginNode() {
668
697
  let s0;
669
698
 
670
- const key = peg$currPos * 40 + 2;
699
+ const key = peg$currPos * 38 + 2;
671
700
  const cached = peg$resultsCache[key];
672
701
 
673
702
  if (cached) {
@@ -692,7 +721,7 @@ function peg$parse(input, options) {
692
721
  function peg$parseEndNode() {
693
722
  let s0;
694
723
 
695
- const key = peg$currPos * 40 + 3;
724
+ const key = peg$currPos * 38 + 3;
696
725
  const cached = peg$resultsCache[key];
697
726
 
698
727
  if (cached) {
@@ -717,7 +746,7 @@ function peg$parse(input, options) {
717
746
  function peg$parseBeginHtml() {
718
747
  let s0;
719
748
 
720
- const key = peg$currPos * 40 + 4;
749
+ const key = peg$currPos * 38 + 4;
721
750
  const cached = peg$resultsCache[key];
722
751
 
723
752
  if (cached) {
@@ -742,7 +771,7 @@ function peg$parse(input, options) {
742
771
  function peg$parseEndHtml() {
743
772
  let s0;
744
773
 
745
- const key = peg$currPos * 40 + 5;
774
+ const key = peg$currPos * 38 + 5;
746
775
  const cached = peg$resultsCache[key];
747
776
 
748
777
  if (cached) {
@@ -767,7 +796,7 @@ function peg$parse(input, options) {
767
796
  function peg$parseBeginText() {
768
797
  let s0;
769
798
 
770
- const key = peg$currPos * 40 + 6;
799
+ const key = peg$currPos * 38 + 6;
771
800
  const cached = peg$resultsCache[key];
772
801
 
773
802
  if (cached) {
@@ -792,7 +821,7 @@ function peg$parse(input, options) {
792
821
  function peg$parseEndText() {
793
822
  let s0;
794
823
 
795
- const key = peg$currPos * 40 + 7;
824
+ const key = peg$currPos * 38 + 7;
796
825
  const cached = peg$resultsCache[key];
797
826
 
798
827
  if (cached) {
@@ -815,9 +844,9 @@ function peg$parse(input, options) {
815
844
  }
816
845
 
817
846
  function peg$parseTemplatedText() {
818
- let s0, s1, s2, s3, s4, s5;
847
+ let s0, s1, s2, s3, s4, s5, s6;
819
848
 
820
- const key = peg$currPos * 40 + 8;
849
+ const key = peg$currPos * 38 + 8;
821
850
  const cached = peg$resultsCache[key];
822
851
 
823
852
  if (cached) {
@@ -885,40 +914,46 @@ function peg$parse(input, options) {
885
914
  }
886
915
  if (s2 === peg$FAILED) {
887
916
  s2 = peg$currPos;
917
+ s3 = peg$currPos;
888
918
  if (input.charCodeAt(peg$currPos) === 123) {
889
- s3 = peg$c9;
919
+ s4 = peg$c9;
890
920
  peg$currPos++;
891
921
  } else {
892
- s3 = peg$FAILED;
922
+ s4 = peg$FAILED;
893
923
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
894
924
  }
895
- if (s3 !== peg$FAILED) {
896
- s4 = peg$currPos;
925
+ if (s4 !== peg$FAILED) {
926
+ s5 = peg$currPos;
897
927
  peg$silentFails++;
898
928
  if (input.charCodeAt(peg$currPos) === 123) {
899
- s5 = peg$c9;
929
+ s6 = peg$c9;
900
930
  peg$currPos++;
901
931
  } else {
902
- s5 = peg$FAILED;
932
+ s6 = peg$FAILED;
903
933
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
904
934
  }
905
935
  peg$silentFails--;
906
- if (s5 === peg$FAILED) {
907
- s4 = undefined;
936
+ if (s6 === peg$FAILED) {
937
+ s5 = undefined;
908
938
  } else {
909
- peg$currPos = s4;
910
- s4 = peg$FAILED;
939
+ peg$currPos = s5;
940
+ s5 = peg$FAILED;
911
941
  }
912
- if (s4 !== peg$FAILED) {
913
- s3 = [s3, s4];
914
- s2 = s3;
942
+ if (s5 !== peg$FAILED) {
943
+ s4 = [s4, s5];
944
+ s3 = s4;
915
945
  } else {
916
- peg$currPos = s2;
917
- s2 = peg$FAILED;
946
+ peg$currPos = s3;
947
+ s3 = peg$FAILED;
918
948
  }
919
949
  } else {
920
- peg$currPos = s2;
921
- s2 = peg$FAILED;
950
+ peg$currPos = s3;
951
+ s3 = peg$FAILED;
952
+ }
953
+ if (s3 !== peg$FAILED) {
954
+ s2 = input.substring(s2, peg$currPos);
955
+ } else {
956
+ s2 = s3;
922
957
  }
923
958
  }
924
959
  }
@@ -982,40 +1017,46 @@ function peg$parse(input, options) {
982
1017
  }
983
1018
  if (s2 === peg$FAILED) {
984
1019
  s2 = peg$currPos;
1020
+ s3 = peg$currPos;
985
1021
  if (input.charCodeAt(peg$currPos) === 123) {
986
- s3 = peg$c9;
1022
+ s4 = peg$c9;
987
1023
  peg$currPos++;
988
1024
  } else {
989
- s3 = peg$FAILED;
1025
+ s4 = peg$FAILED;
990
1026
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
991
1027
  }
992
- if (s3 !== peg$FAILED) {
993
- s4 = peg$currPos;
1028
+ if (s4 !== peg$FAILED) {
1029
+ s5 = peg$currPos;
994
1030
  peg$silentFails++;
995
1031
  if (input.charCodeAt(peg$currPos) === 123) {
996
- s5 = peg$c9;
1032
+ s6 = peg$c9;
997
1033
  peg$currPos++;
998
1034
  } else {
999
- s5 = peg$FAILED;
1035
+ s6 = peg$FAILED;
1000
1036
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
1001
1037
  }
1002
1038
  peg$silentFails--;
1003
- if (s5 === peg$FAILED) {
1004
- s4 = undefined;
1039
+ if (s6 === peg$FAILED) {
1040
+ s5 = undefined;
1005
1041
  } else {
1006
- peg$currPos = s4;
1007
- s4 = peg$FAILED;
1042
+ peg$currPos = s5;
1043
+ s5 = peg$FAILED;
1008
1044
  }
1009
- if (s4 !== peg$FAILED) {
1010
- s3 = [s3, s4];
1011
- s2 = s3;
1045
+ if (s5 !== peg$FAILED) {
1046
+ s4 = [s4, s5];
1047
+ s3 = s4;
1012
1048
  } else {
1013
- peg$currPos = s2;
1014
- s2 = peg$FAILED;
1049
+ peg$currPos = s3;
1050
+ s3 = peg$FAILED;
1015
1051
  }
1016
1052
  } else {
1017
- peg$currPos = s2;
1018
- s2 = peg$FAILED;
1053
+ peg$currPos = s3;
1054
+ s3 = peg$FAILED;
1055
+ }
1056
+ if (s3 !== peg$FAILED) {
1057
+ s2 = input.substring(s2, peg$currPos);
1058
+ } else {
1059
+ s2 = s3;
1019
1060
  }
1020
1061
  }
1021
1062
  }
@@ -1036,7 +1077,7 @@ function peg$parse(input, options) {
1036
1077
  function peg$parseExpressionRoot() {
1037
1078
  let s0, s2;
1038
1079
 
1039
- const key = peg$currPos * 40 + 9;
1080
+ const key = peg$currPos * 38 + 9;
1040
1081
  const cached = peg$resultsCache[key];
1041
1082
 
1042
1083
  if (cached) {
@@ -1064,7 +1105,7 @@ function peg$parse(input, options) {
1064
1105
  function peg$parseTernaryExpression() {
1065
1106
  let s0, s1, s3, s5, s7, s9;
1066
1107
 
1067
- const key = peg$currPos * 40 + 10;
1108
+ const key = peg$currPos * 38 + 10;
1068
1109
  const cached = peg$resultsCache[key];
1069
1110
 
1070
1111
  if (cached) {
@@ -1133,7 +1174,7 @@ function peg$parse(input, options) {
1133
1174
  function peg$parseElvisExpression() {
1134
1175
  let s0, s1, s3, s5;
1135
1176
 
1136
- const key = peg$currPos * 40 + 11;
1177
+ const key = peg$currPos * 38 + 11;
1137
1178
  const cached = peg$resultsCache[key];
1138
1179
 
1139
1180
  if (cached) {
@@ -1182,7 +1223,7 @@ function peg$parse(input, options) {
1182
1223
  function peg$parseNullCoalescingExpression() {
1183
1224
  let s0, s1, s3, s5;
1184
1225
 
1185
- const key = peg$currPos * 40 + 12;
1226
+ const key = peg$currPos * 38 + 12;
1186
1227
  const cached = peg$resultsCache[key];
1187
1228
 
1188
1229
  if (cached) {
@@ -1231,7 +1272,7 @@ function peg$parse(input, options) {
1231
1272
  function peg$parseOrExpression() {
1232
1273
  let s0, s1, s2, s3, s5, s7;
1233
1274
 
1234
- const key = peg$currPos * 40 + 13;
1275
+ const key = peg$currPos * 38 + 13;
1235
1276
  const cached = peg$resultsCache[key];
1236
1277
 
1237
1278
  if (cached) {
@@ -1305,7 +1346,7 @@ function peg$parse(input, options) {
1305
1346
  function peg$parseAndExpression() {
1306
1347
  let s0, s1, s2, s3, s5, s7;
1307
1348
 
1308
- const key = peg$currPos * 40 + 14;
1349
+ const key = peg$currPos * 38 + 14;
1309
1350
  const cached = peg$resultsCache[key];
1310
1351
 
1311
1352
  if (cached) {
@@ -1379,7 +1420,7 @@ function peg$parse(input, options) {
1379
1420
  function peg$parseEqExpression() {
1380
1421
  let s0, s1, s2, s3, s5, s7;
1381
1422
 
1382
- const key = peg$currPos * 40 + 15;
1423
+ const key = peg$currPos * 38 + 15;
1383
1424
  const cached = peg$resultsCache[key];
1384
1425
 
1385
1426
  if (cached) {
@@ -1441,7 +1482,7 @@ function peg$parse(input, options) {
1441
1482
  function peg$parseRelExpression() {
1442
1483
  let s0, s1, s2, s3, s5, s7;
1443
1484
 
1444
- const key = peg$currPos * 40 + 16;
1485
+ const key = peg$currPos * 38 + 16;
1445
1486
  const cached = peg$resultsCache[key];
1446
1487
 
1447
1488
  if (cached) {
@@ -1503,7 +1544,7 @@ function peg$parse(input, options) {
1503
1544
  function peg$parseNotExpression() {
1504
1545
  let s0, s1, s3;
1505
1546
 
1506
- const key = peg$currPos * 40 + 17;
1547
+ const key = peg$currPos * 38 + 17;
1507
1548
  const cached = peg$resultsCache[key];
1508
1549
 
1509
1550
  if (cached) {
@@ -1545,7 +1586,7 @@ function peg$parse(input, options) {
1545
1586
  function peg$parseAccess() {
1546
1587
  let s0, s1, s3, s4, s5;
1547
1588
 
1548
- const key = peg$currPos * 40 + 18;
1589
+ const key = peg$currPos * 38 + 18;
1549
1590
  const cached = peg$resultsCache[key];
1550
1591
 
1551
1592
  if (cached) {
@@ -1606,7 +1647,7 @@ function peg$parse(input, options) {
1606
1647
  function peg$parseAccessExpression() {
1607
1648
  let s0;
1608
1649
 
1609
- const key = peg$currPos * 40 + 19;
1650
+ const key = peg$currPos * 38 + 19;
1610
1651
  const cached = peg$resultsCache[key];
1611
1652
 
1612
1653
  if (cached) {
@@ -1631,7 +1672,7 @@ function peg$parse(input, options) {
1631
1672
  function peg$parseAccessMember() {
1632
1673
  let s0, s1, s3;
1633
1674
 
1634
- const key = peg$currPos * 40 + 20;
1675
+ const key = peg$currPos * 38 + 20;
1635
1676
  const cached = peg$resultsCache[key];
1636
1677
 
1637
1678
  if (cached) {
@@ -1660,7 +1701,7 @@ function peg$parse(input, options) {
1660
1701
  }
1661
1702
  if (s1 !== peg$FAILED) {
1662
1703
  peg$parse_();
1663
- s3 = peg$parseSymbol();
1704
+ s3 = peg$parseWord();
1664
1705
  if (s3 !== peg$FAILED) {
1665
1706
  s0 = peg$f18(s1, s3);
1666
1707
  } else {
@@ -1685,7 +1726,7 @@ function peg$parse(input, options) {
1685
1726
  function peg$parseAccessSubscript() {
1686
1727
  let s0, s1, s2, s4, s6;
1687
1728
 
1688
- const key = peg$currPos * 40 + 21;
1729
+ const key = peg$currPos * 38 + 21;
1689
1730
  const cached = peg$resultsCache[key];
1690
1731
 
1691
1732
  if (cached) {
@@ -1753,7 +1794,7 @@ function peg$parse(input, options) {
1753
1794
  function peg$parseAccessMethodCall() {
1754
1795
  let s0, s1, s2, s4, s5, s6, s7, s9;
1755
1796
 
1756
- const key = peg$currPos * 40 + 22;
1797
+ const key = peg$currPos * 38 + 22;
1757
1798
  const cached = peg$resultsCache[key];
1758
1799
 
1759
1800
  if (cached) {
@@ -1874,7 +1915,7 @@ function peg$parse(input, options) {
1874
1915
  function peg$parseGroupingExpression() {
1875
1916
  let s0, s1, s3, s5;
1876
1917
 
1877
- const key = peg$currPos * 40 + 23;
1918
+ const key = peg$currPos * 38 + 23;
1878
1919
  const cached = peg$resultsCache[key];
1879
1920
 
1880
1921
  if (cached) {
@@ -1922,7 +1963,7 @@ function peg$parse(input, options) {
1922
1963
  if (s0 === peg$FAILED) {
1923
1964
  s0 = peg$parseAnyLiteral();
1924
1965
  if (s0 === peg$FAILED) {
1925
- s0 = peg$parseSymbol();
1966
+ s0 = peg$parseIdentifier();
1926
1967
  }
1927
1968
  }
1928
1969
  }
@@ -1935,7 +1976,7 @@ function peg$parse(input, options) {
1935
1976
  function peg$parseModuleFunctionCall() {
1936
1977
  let s0, s1, s2, s4, s5, s6, s7, s9;
1937
1978
 
1938
- const key = peg$currPos * 40 + 24;
1979
+ const key = peg$currPos * 38 + 24;
1939
1980
  const cached = peg$resultsCache[key];
1940
1981
 
1941
1982
  if (cached) {
@@ -2052,7 +2093,7 @@ function peg$parse(input, options) {
2052
2093
  function peg$parseAnyLiteral() {
2053
2094
  let s0;
2054
2095
 
2055
- const key = peg$currPos * 40 + 25;
2096
+ const key = peg$currPos * 38 + 25;
2056
2097
  const cached = peg$resultsCache[key];
2057
2098
 
2058
2099
  if (cached) {
@@ -2063,20 +2104,11 @@ function peg$parse(input, options) {
2063
2104
 
2064
2105
  s0 = peg$parseNumberLiteral();
2065
2106
  if (s0 === peg$FAILED) {
2066
- s0 = peg$parseBooleanLiteral();
2107
+ s0 = peg$parseStringLiteral();
2067
2108
  if (s0 === peg$FAILED) {
2068
- s0 = peg$parseNullLiteral();
2109
+ s0 = peg$parseArrayLiteral();
2069
2110
  if (s0 === peg$FAILED) {
2070
- s0 = peg$parseUndefinedLiteral();
2071
- if (s0 === peg$FAILED) {
2072
- s0 = peg$parseStringLiteral();
2073
- if (s0 === peg$FAILED) {
2074
- s0 = peg$parseArrayLiteral();
2075
- if (s0 === peg$FAILED) {
2076
- s0 = peg$parseDictLiteral();
2077
- }
2078
- }
2079
- }
2111
+ s0 = peg$parseDictLiteral();
2080
2112
  }
2081
2113
  }
2082
2114
  }
@@ -2086,132 +2118,10 @@ function peg$parse(input, options) {
2086
2118
  return s0;
2087
2119
  }
2088
2120
 
2089
- function peg$parseBooleanLiteral() {
2090
- let s0, s1;
2091
-
2092
- const key = peg$currPos * 40 + 26;
2093
- const cached = peg$resultsCache[key];
2094
-
2095
- if (cached) {
2096
- peg$currPos = cached.nextPos;
2097
-
2098
- return cached.result;
2099
- }
2100
-
2101
- peg$silentFails++;
2102
- s0 = peg$currPos;
2103
- if (input.substr(peg$currPos, 4) === peg$c39) {
2104
- s1 = peg$c39;
2105
- peg$currPos += 4;
2106
- } else {
2107
- s1 = peg$FAILED;
2108
- if (peg$silentFails === 0) { peg$fail(peg$e29); }
2109
- }
2110
- if (s1 !== peg$FAILED) {
2111
- s1 = peg$f23();
2112
- }
2113
- s0 = s1;
2114
- if (s0 === peg$FAILED) {
2115
- s0 = peg$currPos;
2116
- if (input.substr(peg$currPos, 5) === peg$c40) {
2117
- s1 = peg$c40;
2118
- peg$currPos += 5;
2119
- } else {
2120
- s1 = peg$FAILED;
2121
- if (peg$silentFails === 0) { peg$fail(peg$e30); }
2122
- }
2123
- if (s1 !== peg$FAILED) {
2124
- s1 = peg$f24();
2125
- }
2126
- s0 = s1;
2127
- }
2128
- peg$silentFails--;
2129
- if (s0 === peg$FAILED) {
2130
- s1 = peg$FAILED;
2131
- if (peg$silentFails === 0) { peg$fail(peg$e28); }
2132
- }
2133
-
2134
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2135
-
2136
- return s0;
2137
- }
2138
-
2139
- function peg$parseNullLiteral() {
2140
- let s0, s1;
2141
-
2142
- const key = peg$currPos * 40 + 27;
2143
- const cached = peg$resultsCache[key];
2144
-
2145
- if (cached) {
2146
- peg$currPos = cached.nextPos;
2147
-
2148
- return cached.result;
2149
- }
2150
-
2151
- peg$silentFails++;
2152
- s0 = peg$currPos;
2153
- if (input.substr(peg$currPos, 4) === peg$c41) {
2154
- s1 = peg$c41;
2155
- peg$currPos += 4;
2156
- } else {
2157
- s1 = peg$FAILED;
2158
- if (peg$silentFails === 0) { peg$fail(peg$e32); }
2159
- }
2160
- if (s1 !== peg$FAILED) {
2161
- s1 = peg$f25();
2162
- }
2163
- s0 = s1;
2164
- peg$silentFails--;
2165
- if (s0 === peg$FAILED) {
2166
- s1 = peg$FAILED;
2167
- if (peg$silentFails === 0) { peg$fail(peg$e31); }
2168
- }
2169
-
2170
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2171
-
2172
- return s0;
2173
- }
2174
-
2175
- function peg$parseUndefinedLiteral() {
2176
- let s0, s1;
2177
-
2178
- const key = peg$currPos * 40 + 28;
2179
- const cached = peg$resultsCache[key];
2180
-
2181
- if (cached) {
2182
- peg$currPos = cached.nextPos;
2183
-
2184
- return cached.result;
2185
- }
2186
-
2187
- peg$silentFails++;
2188
- s0 = peg$currPos;
2189
- if (input.substr(peg$currPos, 9) === peg$c42) {
2190
- s1 = peg$c42;
2191
- peg$currPos += 9;
2192
- } else {
2193
- s1 = peg$FAILED;
2194
- if (peg$silentFails === 0) { peg$fail(peg$e34); }
2195
- }
2196
- if (s1 !== peg$FAILED) {
2197
- s1 = peg$f26();
2198
- }
2199
- s0 = s1;
2200
- peg$silentFails--;
2201
- if (s0 === peg$FAILED) {
2202
- s1 = peg$FAILED;
2203
- if (peg$silentFails === 0) { peg$fail(peg$e33); }
2204
- }
2205
-
2206
- peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
2207
-
2208
- return s0;
2209
- }
2210
-
2211
2121
  function peg$parseNumberLiteral() {
2212
2122
  let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
2213
2123
 
2214
- const key = peg$currPos * 40 + 29;
2124
+ const key = peg$currPos * 38 + 26;
2215
2125
  const cached = peg$resultsCache[key];
2216
2126
 
2217
2127
  if (cached) {
@@ -2225,11 +2135,11 @@ function peg$parse(input, options) {
2225
2135
  s1 = peg$currPos;
2226
2136
  s2 = peg$currPos;
2227
2137
  if (input.charCodeAt(peg$currPos) === 45) {
2228
- s3 = peg$c43;
2138
+ s3 = peg$c39;
2229
2139
  peg$currPos++;
2230
2140
  } else {
2231
2141
  s3 = peg$FAILED;
2232
- if (peg$silentFails === 0) { peg$fail(peg$e36); }
2142
+ if (peg$silentFails === 0) { peg$fail(peg$e29); }
2233
2143
  }
2234
2144
  if (s3 === peg$FAILED) {
2235
2145
  s3 = null;
@@ -2241,7 +2151,7 @@ function peg$parse(input, options) {
2241
2151
  peg$currPos++;
2242
2152
  } else {
2243
2153
  s6 = peg$FAILED;
2244
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2154
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2245
2155
  }
2246
2156
  if (s6 !== peg$FAILED) {
2247
2157
  while (s6 !== peg$FAILED) {
@@ -2251,7 +2161,7 @@ function peg$parse(input, options) {
2251
2161
  peg$currPos++;
2252
2162
  } else {
2253
2163
  s6 = peg$FAILED;
2254
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2164
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2255
2165
  }
2256
2166
  }
2257
2167
  } else {
@@ -2273,7 +2183,7 @@ function peg$parse(input, options) {
2273
2183
  peg$currPos++;
2274
2184
  } else {
2275
2185
  s9 = peg$FAILED;
2276
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2186
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2277
2187
  }
2278
2188
  while (s9 !== peg$FAILED) {
2279
2189
  s8.push(s9);
@@ -2282,7 +2192,7 @@ function peg$parse(input, options) {
2282
2192
  peg$currPos++;
2283
2193
  } else {
2284
2194
  s9 = peg$FAILED;
2285
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2195
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2286
2196
  }
2287
2197
  }
2288
2198
  s7 = [s7, s8];
@@ -2316,7 +2226,7 @@ function peg$parse(input, options) {
2316
2226
  peg$currPos++;
2317
2227
  } else {
2318
2228
  s7 = peg$FAILED;
2319
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2229
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2320
2230
  }
2321
2231
  if (s7 !== peg$FAILED) {
2322
2232
  while (s7 !== peg$FAILED) {
@@ -2326,7 +2236,7 @@ function peg$parse(input, options) {
2326
2236
  peg$currPos++;
2327
2237
  } else {
2328
2238
  s7 = peg$FAILED;
2329
- if (peg$silentFails === 0) { peg$fail(peg$e37); }
2239
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
2330
2240
  }
2331
2241
  }
2332
2242
  } else {
@@ -2357,13 +2267,13 @@ function peg$parse(input, options) {
2357
2267
  s1 = s2;
2358
2268
  }
2359
2269
  if (s1 !== peg$FAILED) {
2360
- s1 = peg$f27(s1);
2270
+ s1 = peg$f23(s1);
2361
2271
  }
2362
2272
  s0 = s1;
2363
2273
  peg$silentFails--;
2364
2274
  if (s0 === peg$FAILED) {
2365
2275
  s1 = peg$FAILED;
2366
- if (peg$silentFails === 0) { peg$fail(peg$e35); }
2276
+ if (peg$silentFails === 0) { peg$fail(peg$e28); }
2367
2277
  }
2368
2278
 
2369
2279
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -2374,7 +2284,7 @@ function peg$parse(input, options) {
2374
2284
  function peg$parseStringLiteral() {
2375
2285
  let s0, s1, s2, s3, s4;
2376
2286
 
2377
- const key = peg$currPos * 40 + 30;
2287
+ const key = peg$currPos * 38 + 27;
2378
2288
  const cached = peg$resultsCache[key];
2379
2289
 
2380
2290
  if (cached) {
@@ -2386,11 +2296,11 @@ function peg$parse(input, options) {
2386
2296
  peg$silentFails++;
2387
2297
  s0 = peg$currPos;
2388
2298
  if (input.charCodeAt(peg$currPos) === 39) {
2389
- s1 = peg$c44;
2299
+ s1 = peg$c40;
2390
2300
  peg$currPos++;
2391
2301
  } else {
2392
2302
  s1 = peg$FAILED;
2393
- if (peg$silentFails === 0) { peg$fail(peg$e39); }
2303
+ if (peg$silentFails === 0) { peg$fail(peg$e32); }
2394
2304
  }
2395
2305
  if (s1 !== peg$FAILED) {
2396
2306
  s2 = peg$currPos;
@@ -2400,7 +2310,7 @@ function peg$parse(input, options) {
2400
2310
  peg$currPos++;
2401
2311
  } else {
2402
2312
  s4 = peg$FAILED;
2403
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
2313
+ if (peg$silentFails === 0) { peg$fail(peg$e33); }
2404
2314
  }
2405
2315
  while (s4 !== peg$FAILED) {
2406
2316
  s3.push(s4);
@@ -2409,19 +2319,19 @@ function peg$parse(input, options) {
2409
2319
  peg$currPos++;
2410
2320
  } else {
2411
2321
  s4 = peg$FAILED;
2412
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
2322
+ if (peg$silentFails === 0) { peg$fail(peg$e33); }
2413
2323
  }
2414
2324
  }
2415
2325
  s2 = input.substring(s2, peg$currPos);
2416
2326
  if (input.charCodeAt(peg$currPos) === 39) {
2417
- s3 = peg$c44;
2327
+ s3 = peg$c40;
2418
2328
  peg$currPos++;
2419
2329
  } else {
2420
2330
  s3 = peg$FAILED;
2421
- if (peg$silentFails === 0) { peg$fail(peg$e39); }
2331
+ if (peg$silentFails === 0) { peg$fail(peg$e32); }
2422
2332
  }
2423
2333
  if (s3 !== peg$FAILED) {
2424
- s0 = peg$f28(s2);
2334
+ s0 = peg$f24(s2);
2425
2335
  } else {
2426
2336
  peg$currPos = s0;
2427
2337
  s0 = peg$FAILED;
@@ -2433,11 +2343,11 @@ function peg$parse(input, options) {
2433
2343
  if (s0 === peg$FAILED) {
2434
2344
  s0 = peg$currPos;
2435
2345
  if (input.charCodeAt(peg$currPos) === 34) {
2436
- s1 = peg$c45;
2346
+ s1 = peg$c41;
2437
2347
  peg$currPos++;
2438
2348
  } else {
2439
2349
  s1 = peg$FAILED;
2440
- if (peg$silentFails === 0) { peg$fail(peg$e41); }
2350
+ if (peg$silentFails === 0) { peg$fail(peg$e34); }
2441
2351
  }
2442
2352
  if (s1 !== peg$FAILED) {
2443
2353
  s2 = peg$currPos;
@@ -2447,7 +2357,7 @@ function peg$parse(input, options) {
2447
2357
  peg$currPos++;
2448
2358
  } else {
2449
2359
  s4 = peg$FAILED;
2450
- if (peg$silentFails === 0) { peg$fail(peg$e42); }
2360
+ if (peg$silentFails === 0) { peg$fail(peg$e35); }
2451
2361
  }
2452
2362
  while (s4 !== peg$FAILED) {
2453
2363
  s3.push(s4);
@@ -2456,19 +2366,19 @@ function peg$parse(input, options) {
2456
2366
  peg$currPos++;
2457
2367
  } else {
2458
2368
  s4 = peg$FAILED;
2459
- if (peg$silentFails === 0) { peg$fail(peg$e42); }
2369
+ if (peg$silentFails === 0) { peg$fail(peg$e35); }
2460
2370
  }
2461
2371
  }
2462
2372
  s2 = input.substring(s2, peg$currPos);
2463
2373
  if (input.charCodeAt(peg$currPos) === 34) {
2464
- s3 = peg$c45;
2374
+ s3 = peg$c41;
2465
2375
  peg$currPos++;
2466
2376
  } else {
2467
2377
  s3 = peg$FAILED;
2468
- if (peg$silentFails === 0) { peg$fail(peg$e41); }
2378
+ if (peg$silentFails === 0) { peg$fail(peg$e34); }
2469
2379
  }
2470
2380
  if (s3 !== peg$FAILED) {
2471
- s0 = peg$f29(s2);
2381
+ s0 = peg$f25(s2);
2472
2382
  } else {
2473
2383
  peg$currPos = s0;
2474
2384
  s0 = peg$FAILED;
@@ -2480,11 +2390,11 @@ function peg$parse(input, options) {
2480
2390
  if (s0 === peg$FAILED) {
2481
2391
  s0 = peg$currPos;
2482
2392
  if (input.charCodeAt(peg$currPos) === 96) {
2483
- s1 = peg$c46;
2393
+ s1 = peg$c42;
2484
2394
  peg$currPos++;
2485
2395
  } else {
2486
2396
  s1 = peg$FAILED;
2487
- if (peg$silentFails === 0) { peg$fail(peg$e43); }
2397
+ if (peg$silentFails === 0) { peg$fail(peg$e36); }
2488
2398
  }
2489
2399
  if (s1 !== peg$FAILED) {
2490
2400
  s2 = [];
@@ -2500,14 +2410,14 @@ function peg$parse(input, options) {
2500
2410
  }
2501
2411
  }
2502
2412
  if (input.charCodeAt(peg$currPos) === 96) {
2503
- s3 = peg$c46;
2413
+ s3 = peg$c42;
2504
2414
  peg$currPos++;
2505
2415
  } else {
2506
2416
  s3 = peg$FAILED;
2507
- if (peg$silentFails === 0) { peg$fail(peg$e43); }
2417
+ if (peg$silentFails === 0) { peg$fail(peg$e36); }
2508
2418
  }
2509
2419
  if (s3 !== peg$FAILED) {
2510
- s0 = peg$f30(s2);
2420
+ s0 = peg$f26(s2);
2511
2421
  } else {
2512
2422
  peg$currPos = s0;
2513
2423
  s0 = peg$FAILED;
@@ -2521,7 +2431,7 @@ function peg$parse(input, options) {
2521
2431
  peg$silentFails--;
2522
2432
  if (s0 === peg$FAILED) {
2523
2433
  s1 = peg$FAILED;
2524
- if (peg$silentFails === 0) { peg$fail(peg$e38); }
2434
+ if (peg$silentFails === 0) { peg$fail(peg$e31); }
2525
2435
  }
2526
2436
 
2527
2437
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -2532,7 +2442,7 @@ function peg$parse(input, options) {
2532
2442
  function peg$parseTemplateStringExpression() {
2533
2443
  let s0, s1, s3, s5;
2534
2444
 
2535
- const key = peg$currPos * 40 + 31;
2445
+ const key = peg$currPos * 38 + 28;
2536
2446
  const cached = peg$resultsCache[key];
2537
2447
 
2538
2448
  if (cached) {
@@ -2556,14 +2466,14 @@ function peg$parse(input, options) {
2556
2466
  if (s3 !== peg$FAILED) {
2557
2467
  peg$parse_();
2558
2468
  if (input.charCodeAt(peg$currPos) === 125) {
2559
- s5 = peg$c47;
2469
+ s5 = peg$c43;
2560
2470
  peg$currPos++;
2561
2471
  } else {
2562
2472
  s5 = peg$FAILED;
2563
- if (peg$silentFails === 0) { peg$fail(peg$e45); }
2473
+ if (peg$silentFails === 0) { peg$fail(peg$e38); }
2564
2474
  }
2565
2475
  if (s5 !== peg$FAILED) {
2566
- s0 = peg$f31(s3);
2476
+ s0 = peg$f27(s3);
2567
2477
  } else {
2568
2478
  peg$currPos = s0;
2569
2479
  s0 = peg$FAILED;
@@ -2579,7 +2489,7 @@ function peg$parse(input, options) {
2579
2489
  peg$silentFails--;
2580
2490
  if (s0 === peg$FAILED) {
2581
2491
  s1 = peg$FAILED;
2582
- if (peg$silentFails === 0) { peg$fail(peg$e44); }
2492
+ if (peg$silentFails === 0) { peg$fail(peg$e37); }
2583
2493
  }
2584
2494
 
2585
2495
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -2590,7 +2500,7 @@ function peg$parse(input, options) {
2590
2500
  function peg$parseTemplateStringLiteral() {
2591
2501
  let s0, s1, s2, s3, s4;
2592
2502
 
2593
- const key = peg$currPos * 40 + 32;
2503
+ const key = peg$currPos * 38 + 29;
2594
2504
  const cached = peg$resultsCache[key];
2595
2505
 
2596
2506
  if (cached) {
@@ -2609,7 +2519,7 @@ function peg$parse(input, options) {
2609
2519
  peg$currPos++;
2610
2520
  } else {
2611
2521
  s4 = peg$FAILED;
2612
- if (peg$silentFails === 0) { peg$fail(peg$e47); }
2522
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
2613
2523
  }
2614
2524
  if (s4 !== peg$FAILED) {
2615
2525
  while (s4 !== peg$FAILED) {
@@ -2619,7 +2529,7 @@ function peg$parse(input, options) {
2619
2529
  peg$currPos++;
2620
2530
  } else {
2621
2531
  s4 = peg$FAILED;
2622
- if (peg$silentFails === 0) { peg$fail(peg$e47); }
2532
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
2623
2533
  }
2624
2534
  }
2625
2535
  } else {
@@ -2648,7 +2558,7 @@ function peg$parse(input, options) {
2648
2558
  if (peg$silentFails === 0) { peg$fail(peg$e8); }
2649
2559
  }
2650
2560
  if (s4 !== peg$FAILED) {
2651
- s2 = peg$f32(s4);
2561
+ s2 = peg$f28(s4);
2652
2562
  } else {
2653
2563
  peg$currPos = s2;
2654
2564
  s2 = peg$FAILED;
@@ -2668,7 +2578,7 @@ function peg$parse(input, options) {
2668
2578
  peg$currPos++;
2669
2579
  } else {
2670
2580
  s4 = peg$FAILED;
2671
- if (peg$silentFails === 0) { peg$fail(peg$e47); }
2581
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
2672
2582
  }
2673
2583
  if (s4 !== peg$FAILED) {
2674
2584
  while (s4 !== peg$FAILED) {
@@ -2678,7 +2588,7 @@ function peg$parse(input, options) {
2678
2588
  peg$currPos++;
2679
2589
  } else {
2680
2590
  s4 = peg$FAILED;
2681
- if (peg$silentFails === 0) { peg$fail(peg$e47); }
2591
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
2682
2592
  }
2683
2593
  }
2684
2594
  } else {
@@ -2707,7 +2617,7 @@ function peg$parse(input, options) {
2707
2617
  if (peg$silentFails === 0) { peg$fail(peg$e8); }
2708
2618
  }
2709
2619
  if (s4 !== peg$FAILED) {
2710
- s2 = peg$f32(s4);
2620
+ s2 = peg$f28(s4);
2711
2621
  } else {
2712
2622
  peg$currPos = s2;
2713
2623
  s2 = peg$FAILED;
@@ -2722,7 +2632,7 @@ function peg$parse(input, options) {
2722
2632
  s1 = peg$FAILED;
2723
2633
  }
2724
2634
  if (s1 !== peg$FAILED) {
2725
- s1 = peg$f33(s1);
2635
+ s1 = peg$f29(s1);
2726
2636
  }
2727
2637
  s0 = s1;
2728
2638
  if (s0 === peg$FAILED) {
@@ -2735,14 +2645,14 @@ function peg$parse(input, options) {
2735
2645
  if (peg$silentFails === 0) { peg$fail(peg$e9); }
2736
2646
  }
2737
2647
  if (s1 !== peg$FAILED) {
2738
- s1 = peg$f34(s1);
2648
+ s1 = peg$f30(s1);
2739
2649
  }
2740
2650
  s0 = s1;
2741
2651
  }
2742
2652
  peg$silentFails--;
2743
2653
  if (s0 === peg$FAILED) {
2744
2654
  s1 = peg$FAILED;
2745
- if (peg$silentFails === 0) { peg$fail(peg$e46); }
2655
+ if (peg$silentFails === 0) { peg$fail(peg$e39); }
2746
2656
  }
2747
2657
 
2748
2658
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -2753,7 +2663,7 @@ function peg$parse(input, options) {
2753
2663
  function peg$parseArrayLiteral() {
2754
2664
  let s0, s1, s3, s5, s6, s7, s9;
2755
2665
 
2756
- const key = peg$currPos * 40 + 33;
2666
+ const key = peg$currPos * 38 + 30;
2757
2667
  const cached = peg$resultsCache[key];
2758
2668
 
2759
2669
  if (cached) {
@@ -2834,7 +2744,7 @@ function peg$parse(input, options) {
2834
2744
  if (peg$silentFails === 0) { peg$fail(peg$e22); }
2835
2745
  }
2836
2746
  if (s6 !== peg$FAILED) {
2837
- s0 = peg$f35(s3, s5);
2747
+ s0 = peg$f31(s3, s5);
2838
2748
  } else {
2839
2749
  peg$currPos = s0;
2840
2750
  s0 = peg$FAILED;
@@ -2846,7 +2756,7 @@ function peg$parse(input, options) {
2846
2756
  peg$silentFails--;
2847
2757
  if (s0 === peg$FAILED) {
2848
2758
  s1 = peg$FAILED;
2849
- if (peg$silentFails === 0) { peg$fail(peg$e48); }
2759
+ if (peg$silentFails === 0) { peg$fail(peg$e41); }
2850
2760
  }
2851
2761
 
2852
2762
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -2857,7 +2767,7 @@ function peg$parse(input, options) {
2857
2767
  function peg$parseDictLiteral() {
2858
2768
  let s0, s1, s3, s4, s5, s6, s7, s8, s9, s11, s13;
2859
2769
 
2860
- const key = peg$currPos * 40 + 34;
2770
+ const key = peg$currPos * 38 + 31;
2861
2771
  const cached = peg$resultsCache[key];
2862
2772
 
2863
2773
  if (cached) {
@@ -2998,14 +2908,14 @@ function peg$parse(input, options) {
2998
2908
  }
2999
2909
  }
3000
2910
  if (input.charCodeAt(peg$currPos) === 125) {
3001
- s6 = peg$c47;
2911
+ s6 = peg$c43;
3002
2912
  peg$currPos++;
3003
2913
  } else {
3004
2914
  s6 = peg$FAILED;
3005
- if (peg$silentFails === 0) { peg$fail(peg$e45); }
2915
+ if (peg$silentFails === 0) { peg$fail(peg$e38); }
3006
2916
  }
3007
2917
  if (s6 !== peg$FAILED) {
3008
- s0 = peg$f36(s3, s5);
2918
+ s0 = peg$f32(s3, s5);
3009
2919
  } else {
3010
2920
  peg$currPos = s0;
3011
2921
  s0 = peg$FAILED;
@@ -3017,7 +2927,7 @@ function peg$parse(input, options) {
3017
2927
  peg$silentFails--;
3018
2928
  if (s0 === peg$FAILED) {
3019
2929
  s1 = peg$FAILED;
3020
- if (peg$silentFails === 0) { peg$fail(peg$e49); }
2930
+ if (peg$silentFails === 0) { peg$fail(peg$e42); }
3021
2931
  }
3022
2932
 
3023
2933
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -3026,9 +2936,9 @@ function peg$parse(input, options) {
3026
2936
  }
3027
2937
 
3028
2938
  function peg$parseModuleFunction() {
3029
- let s0, s1, s2, s3, s4, s5, s6, s7;
2939
+ let s0, s1, s2, s3, s4;
3030
2940
 
3031
- const key = peg$currPos * 40 + 35;
2941
+ const key = peg$currPos * 38 + 32;
3032
2942
  const cached = peg$resultsCache[key];
3033
2943
 
3034
2944
  if (cached) {
@@ -3040,53 +2950,15 @@ function peg$parse(input, options) {
3040
2950
  peg$silentFails++;
3041
2951
  s0 = peg$currPos;
3042
2952
  if (input.charCodeAt(peg$currPos) === 35) {
3043
- s1 = peg$c49;
2953
+ s1 = peg$c45;
3044
2954
  peg$currPos++;
3045
2955
  } else {
3046
2956
  s1 = peg$FAILED;
3047
- if (peg$silentFails === 0) { peg$fail(peg$e51); }
2957
+ if (peg$silentFails === 0) { peg$fail(peg$e44); }
3048
2958
  }
3049
2959
  if (s1 !== peg$FAILED) {
3050
2960
  s2 = peg$currPos;
3051
- s3 = peg$currPos;
3052
- s4 = peg$currPos;
3053
- s5 = input.charAt(peg$currPos);
3054
- if (peg$r5.test(s5)) {
3055
- peg$currPos++;
3056
- } else {
3057
- s5 = peg$FAILED;
3058
- if (peg$silentFails === 0) { peg$fail(peg$e52); }
3059
- }
3060
- if (s5 !== peg$FAILED) {
3061
- s6 = [];
3062
- s7 = input.charAt(peg$currPos);
3063
- if (peg$r6.test(s7)) {
3064
- peg$currPos++;
3065
- } else {
3066
- s7 = peg$FAILED;
3067
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3068
- }
3069
- while (s7 !== peg$FAILED) {
3070
- s6.push(s7);
3071
- s7 = input.charAt(peg$currPos);
3072
- if (peg$r6.test(s7)) {
3073
- peg$currPos++;
3074
- } else {
3075
- s7 = peg$FAILED;
3076
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3077
- }
3078
- }
3079
- s5 = [s5, s6];
3080
- s4 = s5;
3081
- } else {
3082
- peg$currPos = s4;
3083
- s4 = peg$FAILED;
3084
- }
3085
- if (s4 !== peg$FAILED) {
3086
- s3 = input.substring(s3, peg$currPos);
3087
- } else {
3088
- s3 = s4;
3089
- }
2961
+ s3 = peg$parseWord();
3090
2962
  if (s3 !== peg$FAILED) {
3091
2963
  if (input.charCodeAt(peg$currPos) === 58) {
3092
2964
  s4 = peg$c15;
@@ -3108,47 +2980,9 @@ function peg$parse(input, options) {
3108
2980
  if (s2 === peg$FAILED) {
3109
2981
  s2 = null;
3110
2982
  }
3111
- s3 = peg$currPos;
3112
- s4 = peg$currPos;
3113
- s5 = input.charAt(peg$currPos);
3114
- if (peg$r5.test(s5)) {
3115
- peg$currPos++;
3116
- } else {
3117
- s5 = peg$FAILED;
3118
- if (peg$silentFails === 0) { peg$fail(peg$e52); }
3119
- }
3120
- if (s5 !== peg$FAILED) {
3121
- s6 = [];
3122
- s7 = input.charAt(peg$currPos);
3123
- if (peg$r6.test(s7)) {
3124
- peg$currPos++;
3125
- } else {
3126
- s7 = peg$FAILED;
3127
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3128
- }
3129
- while (s7 !== peg$FAILED) {
3130
- s6.push(s7);
3131
- s7 = input.charAt(peg$currPos);
3132
- if (peg$r6.test(s7)) {
3133
- peg$currPos++;
3134
- } else {
3135
- s7 = peg$FAILED;
3136
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3137
- }
3138
- }
3139
- s5 = [s5, s6];
3140
- s4 = s5;
3141
- } else {
3142
- peg$currPos = s4;
3143
- s4 = peg$FAILED;
3144
- }
3145
- if (s4 !== peg$FAILED) {
3146
- s3 = input.substring(s3, peg$currPos);
3147
- } else {
3148
- s3 = s4;
3149
- }
2983
+ s3 = peg$parseWord();
3150
2984
  if (s3 !== peg$FAILED) {
3151
- s0 = peg$f37(s2, s3);
2985
+ s0 = peg$f33(s2, s3);
3152
2986
  } else {
3153
2987
  peg$currPos = s0;
3154
2988
  s0 = peg$FAILED;
@@ -3160,7 +2994,7 @@ function peg$parse(input, options) {
3160
2994
  peg$silentFails--;
3161
2995
  if (s0 === peg$FAILED) {
3162
2996
  s1 = peg$FAILED;
3163
- if (peg$silentFails === 0) { peg$fail(peg$e50); }
2997
+ if (peg$silentFails === 0) { peg$fail(peg$e43); }
3164
2998
  }
3165
2999
 
3166
3000
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -3168,10 +3002,10 @@ function peg$parse(input, options) {
3168
3002
  return s0;
3169
3003
  }
3170
3004
 
3171
- function peg$parseSymbol() {
3172
- let s0, s1, s2, s3, s4, s5;
3005
+ function peg$parseWord() {
3006
+ let s0, s1, s2, s3, s4;
3173
3007
 
3174
- const key = peg$currPos * 40 + 36;
3008
+ const key = peg$currPos * 38 + 33;
3175
3009
  const cached = peg$resultsCache[key];
3176
3010
 
3177
3011
  if (cached) {
@@ -3180,55 +3014,74 @@ function peg$parse(input, options) {
3180
3014
  return cached.result;
3181
3015
  }
3182
3016
 
3183
- peg$silentFails++;
3184
3017
  s0 = peg$currPos;
3185
3018
  s1 = peg$currPos;
3186
- s2 = peg$currPos;
3187
- s3 = input.charAt(peg$currPos);
3188
- if (peg$r5.test(s3)) {
3019
+ s2 = input.charAt(peg$currPos);
3020
+ if (peg$r5.test(s2)) {
3189
3021
  peg$currPos++;
3190
3022
  } else {
3191
- s3 = peg$FAILED;
3192
- if (peg$silentFails === 0) { peg$fail(peg$e52); }
3023
+ s2 = peg$FAILED;
3024
+ if (peg$silentFails === 0) { peg$fail(peg$e45); }
3193
3025
  }
3194
- if (s3 !== peg$FAILED) {
3195
- s4 = [];
3196
- s5 = input.charAt(peg$currPos);
3197
- if (peg$r6.test(s5)) {
3026
+ if (s2 !== peg$FAILED) {
3027
+ s3 = [];
3028
+ s4 = input.charAt(peg$currPos);
3029
+ if (peg$r6.test(s4)) {
3198
3030
  peg$currPos++;
3199
3031
  } else {
3200
- s5 = peg$FAILED;
3201
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3032
+ s4 = peg$FAILED;
3033
+ if (peg$silentFails === 0) { peg$fail(peg$e46); }
3202
3034
  }
3203
- while (s5 !== peg$FAILED) {
3204
- s4.push(s5);
3205
- s5 = input.charAt(peg$currPos);
3206
- if (peg$r6.test(s5)) {
3035
+ while (s4 !== peg$FAILED) {
3036
+ s3.push(s4);
3037
+ s4 = input.charAt(peg$currPos);
3038
+ if (peg$r6.test(s4)) {
3207
3039
  peg$currPos++;
3208
3040
  } else {
3209
- s5 = peg$FAILED;
3210
- if (peg$silentFails === 0) { peg$fail(peg$e53); }
3041
+ s4 = peg$FAILED;
3042
+ if (peg$silentFails === 0) { peg$fail(peg$e46); }
3211
3043
  }
3212
3044
  }
3213
- s3 = [s3, s4];
3214
- s2 = s3;
3045
+ s2 = [s2, s3];
3046
+ s1 = s2;
3215
3047
  } else {
3216
- peg$currPos = s2;
3217
- s2 = peg$FAILED;
3048
+ peg$currPos = s1;
3049
+ s1 = peg$FAILED;
3218
3050
  }
3219
- if (s2 !== peg$FAILED) {
3220
- s1 = input.substring(s1, peg$currPos);
3051
+ if (s1 !== peg$FAILED) {
3052
+ s0 = input.substring(s0, peg$currPos);
3221
3053
  } else {
3222
- s1 = s2;
3054
+ s0 = s1;
3223
3055
  }
3224
- if (s1 !== peg$FAILED) {
3225
- s1 = peg$f38(s1);
3056
+
3057
+ peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
3058
+
3059
+ return s0;
3060
+ }
3061
+
3062
+ function peg$parseIdentifier() {
3063
+ let s0, s1;
3064
+
3065
+ const key = peg$currPos * 38 + 34;
3066
+ const cached = peg$resultsCache[key];
3067
+
3068
+ if (cached) {
3069
+ peg$currPos = cached.nextPos;
3070
+
3071
+ return cached.result;
3072
+ }
3073
+
3074
+ peg$silentFails++;
3075
+ s0 = peg$currPos;
3076
+ s1 = peg$parseWord();
3077
+ if (s1 !== peg$FAILED) {
3078
+ s1 = peg$f34(s1);
3226
3079
  }
3227
3080
  s0 = s1;
3228
3081
  peg$silentFails--;
3229
3082
  if (s0 === peg$FAILED) {
3230
3083
  s1 = peg$FAILED;
3231
- if (peg$silentFails === 0) { peg$fail(peg$e54); }
3084
+ if (peg$silentFails === 0) { peg$fail(peg$e47); }
3232
3085
  }
3233
3086
 
3234
3087
  peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 };
@@ -3239,7 +3092,7 @@ function peg$parse(input, options) {
3239
3092
  function peg$parseEqualityOp() {
3240
3093
  let s0;
3241
3094
 
3242
- const key = peg$currPos * 40 + 37;
3095
+ const key = peg$currPos * 38 + 35;
3243
3096
  const cached = peg$resultsCache[key];
3244
3097
 
3245
3098
  if (cached) {
@@ -3248,20 +3101,20 @@ function peg$parse(input, options) {
3248
3101
  return cached.result;
3249
3102
  }
3250
3103
 
3251
- if (input.substr(peg$currPos, 2) === peg$c53) {
3252
- s0 = peg$c53;
3104
+ if (input.substr(peg$currPos, 2) === peg$c49) {
3105
+ s0 = peg$c49;
3253
3106
  peg$currPos += 2;
3254
3107
  } else {
3255
3108
  s0 = peg$FAILED;
3256
- if (peg$silentFails === 0) { peg$fail(peg$e55); }
3109
+ if (peg$silentFails === 0) { peg$fail(peg$e48); }
3257
3110
  }
3258
3111
  if (s0 === peg$FAILED) {
3259
- if (input.substr(peg$currPos, 2) === peg$c54) {
3260
- s0 = peg$c54;
3112
+ if (input.substr(peg$currPos, 2) === peg$c50) {
3113
+ s0 = peg$c50;
3261
3114
  peg$currPos += 2;
3262
3115
  } else {
3263
3116
  s0 = peg$FAILED;
3264
- if (peg$silentFails === 0) { peg$fail(peg$e56); }
3117
+ if (peg$silentFails === 0) { peg$fail(peg$e49); }
3265
3118
  }
3266
3119
  }
3267
3120
 
@@ -3273,7 +3126,7 @@ function peg$parse(input, options) {
3273
3126
  function peg$parseRelationalOp() {
3274
3127
  let s0;
3275
3128
 
3276
- const key = peg$currPos * 40 + 38;
3129
+ const key = peg$currPos * 38 + 36;
3277
3130
  const cached = peg$resultsCache[key];
3278
3131
 
3279
3132
  if (cached) {
@@ -3282,36 +3135,36 @@ function peg$parse(input, options) {
3282
3135
  return cached.result;
3283
3136
  }
3284
3137
 
3285
- if (input.substr(peg$currPos, 2) === peg$c55) {
3286
- s0 = peg$c55;
3138
+ if (input.substr(peg$currPos, 2) === peg$c51) {
3139
+ s0 = peg$c51;
3287
3140
  peg$currPos += 2;
3288
3141
  } else {
3289
3142
  s0 = peg$FAILED;
3290
- if (peg$silentFails === 0) { peg$fail(peg$e57); }
3143
+ if (peg$silentFails === 0) { peg$fail(peg$e50); }
3291
3144
  }
3292
3145
  if (s0 === peg$FAILED) {
3293
3146
  if (input.charCodeAt(peg$currPos) === 62) {
3294
- s0 = peg$c56;
3147
+ s0 = peg$c52;
3295
3148
  peg$currPos++;
3296
3149
  } else {
3297
3150
  s0 = peg$FAILED;
3298
- if (peg$silentFails === 0) { peg$fail(peg$e58); }
3151
+ if (peg$silentFails === 0) { peg$fail(peg$e51); }
3299
3152
  }
3300
3153
  if (s0 === peg$FAILED) {
3301
- if (input.substr(peg$currPos, 2) === peg$c57) {
3302
- s0 = peg$c57;
3154
+ if (input.substr(peg$currPos, 2) === peg$c53) {
3155
+ s0 = peg$c53;
3303
3156
  peg$currPos += 2;
3304
3157
  } else {
3305
3158
  s0 = peg$FAILED;
3306
- if (peg$silentFails === 0) { peg$fail(peg$e59); }
3159
+ if (peg$silentFails === 0) { peg$fail(peg$e52); }
3307
3160
  }
3308
3161
  if (s0 === peg$FAILED) {
3309
3162
  if (input.charCodeAt(peg$currPos) === 60) {
3310
- s0 = peg$c58;
3163
+ s0 = peg$c54;
3311
3164
  peg$currPos++;
3312
3165
  } else {
3313
3166
  s0 = peg$FAILED;
3314
- if (peg$silentFails === 0) { peg$fail(peg$e60); }
3167
+ if (peg$silentFails === 0) { peg$fail(peg$e53); }
3315
3168
  }
3316
3169
  }
3317
3170
  }
@@ -3325,7 +3178,7 @@ function peg$parse(input, options) {
3325
3178
  function peg$parse_() {
3326
3179
  let s0, s1;
3327
3180
 
3328
- const key = peg$currPos * 40 + 39;
3181
+ const key = peg$currPos * 38 + 37;
3329
3182
  const cached = peg$resultsCache[key];
3330
3183
 
3331
3184
  if (cached) {
@@ -3341,7 +3194,7 @@ function peg$parse(input, options) {
3341
3194
  peg$currPos++;
3342
3195
  } else {
3343
3196
  s1 = peg$FAILED;
3344
- if (peg$silentFails === 0) { peg$fail(peg$e61); }
3197
+ if (peg$silentFails === 0) { peg$fail(peg$e54); }
3345
3198
  }
3346
3199
  while (s1 !== peg$FAILED) {
3347
3200
  s0.push(s1);
@@ -3350,7 +3203,7 @@ function peg$parse(input, options) {
3350
3203
  peg$currPos++;
3351
3204
  } else {
3352
3205
  s1 = peg$FAILED;
3353
- if (peg$silentFails === 0) { peg$fail(peg$e61); }
3206
+ if (peg$silentFails === 0) { peg$fail(peg$e54); }
3354
3207
  }
3355
3208
  }
3356
3209
  peg$silentFails--;
@@ -3394,6 +3247,34 @@ function peg$parse(input, options) {
3394
3247
  }
3395
3248
  }
3396
3249
 
3250
+ /**
3251
+ * The one lookup against a data stack: the innermost overlay carrying the name
3252
+ * wins, `self` is the innermost overlay itself, and a function overlay counts
3253
+ * like an object one. Every lookup that resolves a name goes through this, so the
3254
+ * imperative facades cannot drift from what a template sees.
3255
+ * @param {any[]} dataStack
3256
+ * @param {string|symbol} prop
3257
+ */
3258
+ const resolveInStack = (dataStack, prop) => {
3259
+ if (prop === 'self') {
3260
+ return dataStack[dataStack.length - 1];
3261
+ }
3262
+ for (let i = dataStack.length - 1; i >= 0; i--) {
3263
+ const overlay = dataStack[i];
3264
+ if (overlay != null && (typeof overlay === 'object' || typeof overlay === 'function')) {
3265
+ if (prop in overlay) {
3266
+ return overlay[prop];
3267
+ }
3268
+ }
3269
+ }
3270
+ return undefined;
3271
+ };
3272
+
3273
+ /**
3274
+ * Evaluates a parsed expression against a scope. One instance per evaluation:
3275
+ * it holds the modules and the data stack that names resolve against, and keeps
3276
+ * no state between visits.
3277
+ */
3397
3278
  class EvaluatingVisitor {
3398
3279
  #modules;
3399
3280
  #dataStack;
@@ -3402,25 +3283,29 @@ class EvaluatingVisitor {
3402
3283
  this.#dataStack = dataStack;
3403
3284
  }
3404
3285
  #resolve(prop) {
3405
- if (prop === 'self') {
3406
- return this.#dataStack[this.#dataStack.length - 1];
3407
- }
3408
- for (let i = this.#dataStack.length - 1; i >= 0; i--) {
3409
- const overlay = this.#dataStack[i];
3410
- if (overlay != null && (typeof overlay === 'object' || typeof overlay === 'function')) {
3411
- if (prop in overlay) {
3412
- return overlay[prop];
3413
- }
3414
- }
3286
+ return resolveInStack(this.#dataStack, prop);
3287
+ }
3288
+ /**
3289
+ * The name a missing-method report can carry: the called symbol for a bare
3290
+ * `boom()`, the member for a dotted `a.boom()`, nothing for anything else
3291
+ * (a subscript or a grouped left-hand side has no static name).
3292
+ */
3293
+ #reportableName(node, index) {
3294
+ const source = index === 0 ? node.lhs : node.rhs[index - 1];
3295
+ if (source.type === nodes.symbol) {
3296
+ return source.value;
3415
3297
  }
3416
- return undefined;
3298
+ if (source.type === nodes.member) {
3299
+ return source.rhs;
3300
+ }
3301
+ return null;
3417
3302
  }
3418
3303
 
3419
3304
  #cached_resolve_proxy;
3420
3305
  #resolve_proxy() {
3421
3306
  if (!this.#cached_resolve_proxy) {
3422
3307
  this.#cached_resolve_proxy = new Proxy(this.#dataStack, {
3423
- get: (target, prop) => this.#resolve(prop)
3308
+ get: (target, prop) => this.#resolve(prop),
3424
3309
  });
3425
3310
  }
3426
3311
  return this.#cached_resolve_proxy;
@@ -3491,7 +3376,7 @@ class EvaluatingVisitor {
3491
3376
  return this.#resolve(node.value);
3492
3377
  }
3493
3378
  [nodes.dict](node) {
3494
- return Object.fromEntries(node.value.map((entry) => [entry[0].value, this.visit(entry[1])]));
3379
+ return Object.fromEntries(node.value.map((entry) => [this.visit(entry[0]), this.visit(entry[1])]));
3495
3380
  }
3496
3381
  [nodes.array](node) {
3497
3382
  return node.value.map((v) => this.visit(v));
@@ -3504,14 +3389,14 @@ class EvaluatingVisitor {
3504
3389
  return cond ? cond : this.visit(node.ifFalse);
3505
3390
  }
3506
3391
  [nodes.access](node) {
3507
- let prev ;
3392
+ let prev;
3508
3393
  let cur = this.visit(node.lhs);
3509
3394
  for (let i = 0; i !== node.rhs.length; ++i) {
3510
3395
  const rhs = node.rhs[i];
3511
3396
  if (rhs.ns && cur == null) {
3512
3397
  return undefined;
3513
3398
  }
3514
- let value ;
3399
+ let value;
3515
3400
  switch (rhs.type) {
3516
3401
  case nodes.member: {
3517
3402
  value = cur[rhs.rhs];
@@ -3523,7 +3408,8 @@ class EvaluatingVisitor {
3523
3408
  }
3524
3409
  case nodes.method: {
3525
3410
  if (!cur) {
3526
- throw new Error(`Method missing "${node.rhs[i - 1].rhs}"`);
3411
+ const name = this.#reportableName(node, i);
3412
+ throw new Error(name === null ? 'Method missing' : `Method missing "${name}"`);
3527
3413
  }
3528
3414
  const args = rhs.args.map((arg) => this.visit(arg));
3529
3415
  value = cur.apply(prev, args);
@@ -3542,28 +3428,34 @@ class EvaluatingVisitor {
3542
3428
  return !templated
3543
3429
  ? this.visit(ast)
3544
3430
  : ast.map((node) => {
3545
- switch (node.type) {
3546
- case nodes.templated.tel:
3547
- return { type: nodes.dom.t, value: node.value };
3548
- case nodes.templated.tet:
3549
- return { type: nodes.dom.t, value: this.visit(node.value) };
3550
- case nodes.templated.teh:
3551
- return { type: nodes.dom.h, value: this.visit(node.value) };
3552
- case nodes.templated.ten:
3553
- return { type: nodes.dom.n, value: this.visit(node.value) };
3554
- default:
3555
- throw new Error(`unknown node type ${node.type.toString()}`);
3556
- }
3557
- });
3431
+ switch (node.type) {
3432
+ case nodes.templated.tel:
3433
+ return { type: nodes.dom.t, value: node.value };
3434
+ case nodes.templated.tet:
3435
+ return { type: nodes.dom.t, value: this.visit(node.value) };
3436
+ case nodes.templated.teh:
3437
+ return { type: nodes.dom.h, value: this.visit(node.value) };
3438
+ case nodes.templated.ten:
3439
+ return { type: nodes.dom.n, value: this.visit(node.value) };
3440
+ default:
3441
+ throw new Error(`unknown node type ${node.type.toString()}`);
3442
+ }
3443
+ });
3558
3444
  }
3559
3445
  }
3560
3446
 
3447
+ /**
3448
+ * The expression language: parsing to an ast, caching the parses, and
3449
+ * interpreting one against a scope. Member access and calls are unfiltered, so
3450
+ * an expression can do whatever the page's own javascript can. Expressions are
3451
+ * written by the page author; untrusted data is passed in as data and never
3452
+ * spliced into the expression text.
3453
+ */
3561
3454
  class Expressions {
3562
3455
  static MODE_EXPRESSION = Symbol('MODE_EXPRESSION');
3563
3456
  static MODE_TEMPLATED = Symbol('MODE_TEMPLATED');
3564
3457
 
3565
- static #astCache = new Map();
3566
- static #MAX_CACHE_SIZE = 1000;
3458
+ static #astCache = new BoundedCache(1000);
3567
3459
 
3568
3460
  /**
3569
3461
  * Parses an expression.
@@ -3573,18 +3465,11 @@ class Expressions {
3573
3465
  */
3574
3466
  static parse(expression, mode) {
3575
3467
  const key = mode?.toString() + expression;
3576
-
3577
- if (!this.#astCache.has(key)) {
3578
- if (this.#astCache.size >= this.#MAX_CACHE_SIZE) {
3579
- const oldestKey = this.#astCache.keys().next().value;
3580
- this.#astCache.delete(oldestKey);
3581
- }
3582
- this.#astCache.set(key, peg$parse(expression, {
3468
+ return this.#astCache.getOrCompute(key, () =>
3469
+ peg$parse(expression, {
3583
3470
  startRule: mode === Expressions.MODE_TEMPLATED ? 'TemplatedRoot' : 'ExpressionRoot',
3584
- }));
3585
- }
3586
-
3587
- return this.#astCache.get(key);
3471
+ }),
3472
+ );
3588
3473
  }
3589
3474
  /**
3590
3475
  * Evaluates an expression.
@@ -3610,6 +3495,12 @@ class Expressions {
3610
3495
  }
3611
3496
  }
3612
3497
 
3498
+ /**
3499
+ * A scope: the modules that `#name:fn()` resolves against and the data stack
3500
+ * that a bare identifier resolves against, as a single value. Adding an overlay
3501
+ * returns a new evaluator instead of modifying this one, so a template can add
3502
+ * data for one subtree without affecting the rest of the render.
3503
+ */
3613
3504
  class ExpressionEvaluator {
3614
3505
  #modules;
3615
3506
  #dataStack;
@@ -3627,17 +3518,25 @@ class ExpressionEvaluator {
3627
3518
  data.length === 0 ? this.#dataStack : [...this.#dataStack, ...data],
3628
3519
  );
3629
3520
  }
3630
- evaluate(expression, mode) {
3631
- return Expressions.interpret(this.#modules, this.#dataStack, expression, mode);
3521
+ /**
3522
+ * Resolves a name against the data stack, with no parse round trip: the
3523
+ * lookup a template's bare identifier makes, for an imperative caller.
3524
+ * @param {string} name
3525
+ */
3526
+ resolve(name) {
3527
+ return resolveInStack(this.#dataStack, name);
3632
3528
  }
3529
+ /** Evaluates an expression against this scope. */
3633
3530
  evaluateExpression(expression) {
3634
- return this.evaluate(expression, Expressions.MODE_EXPRESSION);
3531
+ return Expressions.interpret(this.#modules, this.#dataStack, expression, Expressions.MODE_EXPRESSION);
3635
3532
  }
3636
- evaluateTemplated(expression) {
3637
- return this.evaluate(expression, Expressions.MODE_TEMPLATED);
3533
+ /** Evaluates a templated text, the `{{ }}` form, against this scope. */
3534
+ evaluateTemplated(text) {
3535
+ return Expressions.interpret(this.#modules, this.#dataStack, text, Expressions.MODE_TEMPLATED);
3638
3536
  }
3639
3537
  }
3640
3538
 
3539
+ /** Creates and inspects the DocumentFragments a Template renders into. */
3641
3540
  class Fragments {
3642
3541
  /**
3643
3542
  * Creates a DocumentFragment from an string.
@@ -3650,7 +3549,9 @@ class Fragments {
3650
3549
  return document.adoptNode(el.content);
3651
3550
  }
3652
3551
  /**
3653
- * Creates a string representation (HTML) of a DocumentFragment.
3552
+ * Creates a string representation (HTML) of a DocumentFragment, consuming it:
3553
+ * the nodes are moved out, not copied, and the fragment is left empty. Pass
3554
+ * `fragment.cloneNode(true)` to keep the original usable.
3654
3555
  * @param {DocumentFragment} fragment
3655
3556
  * @returns {string} the html
3656
3557
  */
@@ -3693,6 +3594,7 @@ class Fragments {
3693
3594
  }
3694
3595
  }
3695
3596
 
3597
+ /** Attribute reads and writes where a nullish value removes the attribute instead of setting it to the string 'null'. */
3696
3598
  class Attributes {
3697
3599
  static id = 0;
3698
3600
  /**
@@ -3728,38 +3630,17 @@ class Attributes {
3728
3630
  .forEach((a) => {
3729
3631
  const target = a.substring(prefix.length);
3730
3632
  if (target === 'class') {
3731
- const classes = from.getAttribute(`${prefix}class`)?.split(/\s+/).filter((a) => a.length) ?? [];
3633
+ const classes =
3634
+ from
3635
+ .getAttribute(`${prefix}class`)
3636
+ ?.split(/\s+/)
3637
+ .filter((a) => a.length) ?? [];
3732
3638
  to.classList.add(...classes);
3733
3639
  return;
3734
3640
  }
3735
- to.setAttribute(target, /** @type {string} */(from.getAttribute(a)));
3641
+ to.setAttribute(target, /** @type {string} */ (from.getAttribute(a)));
3736
3642
  });
3737
3643
  }
3738
- /**
3739
- * Changes the presence of an attribute.
3740
- * @param {Element} el
3741
- * @param {string} attr
3742
- * @param {boolean} value
3743
- */
3744
- static toggle(el, attr, value) {
3745
- if (value) {
3746
- el.setAttribute(attr, '');
3747
- } else {
3748
- el.removeAttribute(attr);
3749
- }
3750
- }
3751
- /**
3752
- * Changes the presence of an attribute based on its current state.
3753
- * @param {Element} el
3754
- * @param {string} attr
3755
- */
3756
- static flip(el, attr) {
3757
- if (el.hasAttribute(attr)) {
3758
- el.removeAttribute(attr);
3759
- } else {
3760
- el.setAttribute(attr, '');
3761
- }
3762
- }
3763
3644
  /**
3764
3645
  * Sets the value of an attribute. nullish values remove the attribute.
3765
3646
  * @param {Element} el
@@ -3775,6 +3656,12 @@ class Attributes {
3775
3656
  }
3776
3657
  }
3777
3658
 
3659
+ /**
3660
+ * Reads an element's light-dom slots: the named `<template slot=…>` and
3661
+ * `[slot=…]` children are removed and collected by name, and the remaining
3662
+ * children become the default slot. fml renders into the light dom, so this
3663
+ * replaces the slotting a shadow root would do.
3664
+ */
3778
3665
  class LightSlots {
3779
3666
  /**
3780
3667
  * Extracts light slots from an element. For non default slots in a template tag, the content is extracted.
@@ -3782,12 +3669,19 @@ class LightSlots {
3782
3669
  * @returns the slots
3783
3670
  */
3784
3671
  static from(el) {
3672
+ //the platform reads slot="" as the default slot: such children stay in
3673
+ //the document order of the default content, their claim stripped like any named one
3674
+ for (const child of el.children) {
3675
+ if (child.matches('[slot=""]')) {
3676
+ child.removeAttribute('slot');
3677
+ }
3678
+ }
3785
3679
  /** @type [string, Element|DocumentFragment][] */
3786
3680
  const namedSlots = Array.from(el.children)
3787
3681
  .filter((el) => el.matches('[slot]'))
3788
3682
  .map((el) => {
3789
3683
  el.remove();
3790
- const slot = el.getAttribute('slot') || 'unnamed';
3684
+ const slot = /** @type {string} */ (el.getAttribute('slot'));
3791
3685
  el.removeAttribute('slot');
3792
3686
  return [slot, LightSlots.slotFromNode(el)];
3793
3687
  });
@@ -3806,13 +3700,14 @@ class LightSlots {
3806
3700
  if (el instanceof HTMLTemplateElement) {
3807
3701
  return document.adoptNode(el.content);
3808
3702
  }
3809
- if (el instanceof HTMLScriptElement && el.type !== '' && el.type !== 'text/javascript') {
3703
+ if (el instanceof HTMLScriptElement && el.type === 'text/html') {
3810
3704
  return Fragments.fromHtml(el.innerHTML);
3811
3705
  }
3812
3706
  return el;
3813
3707
  }
3814
3708
  }
3815
3709
 
3710
+ /** Waits for the parser and for the document, for elements that upgrade before their own markup is complete. */
3816
3711
  class Nodes {
3817
3712
  /**
3818
3713
  * Checks if an element is already parsed.
@@ -3828,26 +3723,78 @@ class Nodes {
3828
3723
  return false;
3829
3724
  }
3830
3725
 
3726
+ /**
3727
+ * Waits for the document's DOMContentLoaded, resolving immediately when
3728
+ * that moment already passed. 'interactive' alone cannot tell a document
3729
+ * still waiting for deferred scripts (the event comes, however late) from
3730
+ * a module imported past it (the event is gone): the two events order
3731
+ * themselves, DOMContentLoaded always precedes load, so racing the two
3732
+ * resolves with DCL whenever it is still coming and with load otherwise,
3733
+ * never early.
3734
+ */
3735
+ static waitDomContentLoaded(doc) {
3736
+ if (doc.readyState === 'loading') {
3737
+ return new Promise((resolve) => {
3738
+ doc.addEventListener('DOMContentLoaded', () => resolve(undefined), { once: true });
3739
+ });
3740
+ }
3741
+ if (doc.readyState === 'complete') {
3742
+ return Promise.resolve();
3743
+ }
3744
+ const win = doc.defaultView;
3745
+ if (win === null) {
3746
+ //a viewless document can receive no event at all
3747
+ return Promise.resolve();
3748
+ }
3749
+ return new Promise((resolve) => {
3750
+ const done = () => {
3751
+ win.removeEventListener('DOMContentLoaded', done);
3752
+ win.removeEventListener('load', done);
3753
+ resolve(undefined);
3754
+ };
3755
+ win.addEventListener('DOMContentLoaded', done);
3756
+ win.addEventListener('load', done);
3757
+ });
3758
+ }
3759
+
3760
+ /**
3761
+ * Waits for the element's closing tag to be parsed: one MutationObserver
3762
+ * resolves once the element, or any of its ancestors, gains a next sibling,
3763
+ * which is the parser having moved past this subtree. The document's
3764
+ * DOMContentLoaded is the deadline. Resolves immediately for an element
3765
+ * that is already parsed.
3766
+ *
3767
+ * Every ancestor is watched, not only the parent, because that is what the
3768
+ * predicate reads: an element last among its siblings becomes parsed when
3769
+ * an ancestor gains one, and a childList observer on the parent never sees
3770
+ * that. Formatted markup usually hides the difference, the whitespace
3771
+ * before a closing tag being a text node the parent does gain, so the gap
3772
+ * shows on whitespace-free markup, where the wait fell back to the
3773
+ * deadline. One observer takes many targets, so the cost is one observe()
3774
+ * per level, and it is disconnected at the first checkpoint past the
3775
+ * element either way.
3776
+ * @param {any} el
3777
+ * @returns {Promise<any>}
3778
+ */
3831
3779
  static waitParsed(el) {
3832
- if (el.ownerDocument.readyState === 'complete' || Nodes.isParsed(el)) {
3780
+ if (Nodes.isParsed(el)) {
3833
3781
  return Promise.resolve(el);
3834
3782
  }
3835
3783
  return new Promise((resolve) => {
3836
- const ac = new AbortController();
3837
- const clearAndQueue = () => {
3838
- ac.abort();
3839
- observer.disconnect();
3840
- resolve(el);
3841
- };
3842
- el.ownerDocument.addEventListener('DOMContentLoaded', clearAndQueue, { signal: ac.signal });
3843
3784
  const observer = new MutationObserver(() => {
3844
3785
  if (!Nodes.isParsed(el)) {
3845
3786
  return;
3846
3787
  }
3847
- clearAndQueue();
3788
+ observer.disconnect();
3789
+ resolve(el);
3790
+ });
3791
+ for (let c = el.parentNode; c; c = c.parentNode) {
3792
+ observer.observe(c, { childList: true });
3793
+ }
3794
+ Nodes.waitDomContentLoaded(el.ownerDocument).then(() => {
3795
+ observer.disconnect();
3796
+ resolve(el);
3848
3797
  });
3849
- const parent = /** @type {Node} */ (el.parentNode);
3850
- observer.observe(parent, { childList: true });
3851
3798
  });
3852
3799
  }
3853
3800
 
@@ -3882,6 +3829,11 @@ class Nodes {
3882
3829
  }
3883
3830
  }
3884
3831
 
3832
+ /**
3833
+ * Collects the changes a render makes to the tree it is traversing, deferring
3834
+ * those that would break the traversal: a node marked for removal stays in
3835
+ * place until the render finishes, so the iteration containing it completes.
3836
+ */
3885
3837
  class NodeOperations {
3886
3838
  #forRemoval = new Set();
3887
3839
  removed(node) {
@@ -3919,7 +3871,11 @@ class NodeOperations {
3919
3871
  }
3920
3872
  }
3921
3873
 
3874
+ /** The `data-tpl-*` commands, each taking the node it is written on and the scope it evaluates in. */
3922
3875
  class CommandsHandler {
3876
+ //the order is the semantics: each command sees the node as the ones before
3877
+ //it left it, so tplIf gates before tplWith/tplEach push their overlay and
3878
+ //tplWhen gates after (inside the sub-render, where the overlay is in scope)
3923
3879
  static ORDERED_COMMANDS = [
3924
3880
  'tplIf',
3925
3881
  'tplWith',
@@ -3927,48 +3883,98 @@ class CommandsHandler {
3927
3883
  'tplWhen',
3928
3884
  'tplClassAppend',
3929
3885
  'tplAttrAppend',
3930
- 'tplText',
3931
- 'tplHtml',
3932
3886
  'tplRemove',
3933
3887
  'tplVerbatim',
3934
3888
  ];
3935
- static tplIf(node, expression, ops, modules, dataStack) {
3936
- const accept = Expressions.interpret(modules, dataStack, expression);
3889
+ //same body as tplWhen: the two differ only in ORDERED_COMMANDS position,
3890
+ //if evaluates in the outer scope, before with/each
3891
+ static tplIf(node, expression, ops, evaluator) {
3892
+ const accept = evaluator.evaluateExpression(expression);
3937
3893
  if (!accept) {
3938
3894
  ops.remove(node);
3939
3895
  }
3940
3896
  }
3941
- static tplWith(node, expression, ops, modules, dataStack) {
3942
- const evaluated = Expressions.interpret(modules, dataStack, expression);
3897
+ static tplWith(node, expression, ops, evaluator) {
3898
+ const evaluated = evaluator.evaluateExpression(expression);
3943
3899
  const varName = ops.popData(node, 'tplVar');
3944
- const newNode = new Template(node, modules, dataStack)
3900
+ const newNode = new Template(node, evaluator)
3945
3901
  .withOverlay(varName ? { [varName]: evaluated } : evaluated)
3946
3902
  .render();
3947
3903
  ops.replace(node, newNode);
3948
3904
  }
3949
- static tplEach(node, expression, ops, modules, dataStack) {
3905
+ static tplEach(node, expression, ops, evaluator) {
3950
3906
  const varName = ops.popData(node, 'tplVar');
3951
- const template = new Template(node, modules, dataStack);
3952
- const evaluated = Expressions.interpret(modules, dataStack, expression);
3953
- if (!evaluated?.[Symbol.iterator]) {
3907
+ const statName = ops.popData(node, 'tplStat');
3908
+ const template = new Template(node, evaluator);
3909
+ const evaluated = evaluator.evaluateExpression(expression);
3910
+ //keyed collections iterate as {key, value} entries: a Map in its own
3911
+ //order, a non-iterable plain dict in Object.entries order. Plain alone:
3912
+ //a class instance or a response wrapper standing where an array was
3913
+ //expected still fails loudly below, and any other iterable (a Set, a
3914
+ //generator, an entries() iterator) iterates as itself
3915
+ const proto = evaluated === null || typeof evaluated !== 'object' ? undefined : Object.getPrototypeOf(evaluated);
3916
+ const keyed =
3917
+ evaluated instanceof Map
3918
+ ? [...evaluated]
3919
+ : !evaluated?.[Symbol.iterator] && (proto === Object.prototype || proto === null)
3920
+ ? Object.entries(evaluated)
3921
+ : null;
3922
+ const entries = keyed === null ? evaluated : keyed.map(([key, value]) => ({ key, value }));
3923
+ if (!entries?.[Symbol.iterator]) {
3954
3924
  throw new Error(`Expected an iterable got '${evaluated}'`);
3955
3925
  }
3956
- for (const v of evaluated) {
3957
- ops.prepend(node, template.withOverlay(varName ? { [varName]: v } : v).render());
3926
+ if (!statName) {
3927
+ for (const v of entries) {
3928
+ ops.prepend(node, template.withOverlay(varName ? { [varName]: v } : v).render());
3929
+ }
3930
+ ops.remove(node);
3931
+ return;
3932
+ }
3933
+ //the stat rides grouped under its declared name, overlaid below the
3934
+ //item, so data can never collide with its fields and an item property
3935
+ //sharing the stat's very name wins, as data always does. The size is
3936
+ //read where the collection already knows it (arrays and keyed entries
3937
+ //by length, sets and the like by size): an arbitrary iterator is never
3938
+ //consumed to learn it, so its size and last read null, the unknown
3939
+ const size = Array.isArray(entries)
3940
+ ? entries.length
3941
+ : typeof entries.length === 'number'
3942
+ ? entries.length
3943
+ : typeof entries.size === 'number'
3944
+ ? entries.size
3945
+ : null;
3946
+ let index = 0;
3947
+ for (const v of entries) {
3948
+ const stat = {
3949
+ index,
3950
+ count: index + 1,
3951
+ size,
3952
+ first: index === 0,
3953
+ last: size === null ? null : index === size - 1,
3954
+ even: index % 2 === 0,
3955
+ odd: index % 2 === 1,
3956
+ };
3957
+ ops.prepend(
3958
+ node,
3959
+ template.withOverlay({ [statName]: stat }, varName ? { [varName]: v } : v).render(),
3960
+ );
3961
+ ++index;
3958
3962
  }
3959
3963
  ops.remove(node);
3960
3964
  }
3961
- static tplWhen(node, expression, ops, modules, dataStack) {
3962
- const accept = Expressions.interpret(modules, dataStack, expression);
3965
+ //same body as tplIf: the two differ only in ORDERED_COMMANDS position,
3966
+ //when evaluates after with/each, in the scope their overlay opened
3967
+ static tplWhen(node, expression, ops, evaluator) {
3968
+ const accept = evaluator.evaluateExpression(expression);
3963
3969
  if (!accept) {
3964
3970
  ops.remove(node);
3965
3971
  }
3966
3972
  }
3967
- static tplVerbatim(node, expression, ops, modules, dataStack) {
3973
+ static tplVerbatim(node, expression, ops, evaluator) {
3968
3974
  const newNode = node.cloneNode(true);
3969
3975
  ops.replace(node, newNode);
3970
3976
  }
3971
- static tplRemove(node, value, ops, modules, dataStack) {
3977
+ static tplRemove(node, value, ops, evaluator) {
3972
3978
  switch (value.toLowerCase()) {
3973
3979
  case 'tag': {
3974
3980
  const fragment = new DocumentFragment();
@@ -3993,34 +3999,20 @@ class CommandsHandler {
3993
3999
  break;
3994
4000
  }
3995
4001
  }
3996
- static tplText(node, expression, ops, modules, dataStack) {
3997
- const text = Expressions.interpret(modules, dataStack, expression);
3998
- const newNode = node.cloneNode();
3999
- newNode.replaceChildren(text == null ? '' : text);
4000
- ops.replace(node, newNode);
4001
- }
4002
- static tplHtml(node, expression, ops, modules, dataStack) {
4003
- const html = Expressions.interpret(modules, dataStack, expression);
4004
- const newNode = node.cloneNode();
4005
- newNode.innerHTML = html == null ? '' : html;
4006
- ops.replace(node, newNode);
4007
- }
4008
- static tplClassAppend(node, expression, ops, modules, dataStack) {
4009
- const classes = Expressions.interpret(modules, dataStack, expression);
4002
+ static tplClassAppend(node, expression, ops, evaluator) {
4003
+ const classes = evaluator.evaluateExpression(expression);
4010
4004
  if (!classes) {
4011
4005
  return;
4012
4006
  }
4013
4007
  const classesAsArray = Array.isArray(classes) ? classes : [classes];
4014
- const cleanClasses = classesAsArray
4015
- .flatMap(c => typeof c === 'string' ? c.split(' ') : c)
4016
- .filter(Boolean);
4008
+ const cleanClasses = classesAsArray.flatMap((c) => (typeof c === 'string' ? c.split(' ') : c)).filter(Boolean);
4017
4009
  if (cleanClasses.length === 0) {
4018
4010
  return;
4019
4011
  }
4020
4012
  node.classList.add(...cleanClasses);
4021
4013
  }
4022
- static tplAttrAppend(node, expression, ops, modules, dataStack) {
4023
- const attributesAndValues = Expressions.interpret(modules, dataStack, expression);
4014
+ static tplAttrAppend(node, expression, ops, evaluator) {
4015
+ const attributesAndValues = evaluator.evaluateExpression(expression);
4024
4016
  if (!attributesAndValues || attributesAndValues.length === 0) {
4025
4017
  return;
4026
4018
  }
@@ -4029,8 +4021,8 @@ class CommandsHandler {
4029
4021
  node.setAttribute(k, v);
4030
4022
  });
4031
4023
  }
4032
- static textNode(node, expression, ops, modules, dataStack) {
4033
- for (const v of Expressions.interpret(modules, dataStack, expression, Expressions.MODE_TEMPLATED)) {
4024
+ static textNode(node, expression, ops, evaluator) {
4025
+ for (const v of evaluator.evaluateTemplated(expression)) {
4034
4026
  if (v.value == null) {
4035
4027
  continue;
4036
4028
  }
@@ -4039,9 +4031,12 @@ class CommandsHandler {
4039
4031
  ops.prepend(node, document.createTextNode(v.value));
4040
4032
  break;
4041
4033
  case nodes.dom.h:
4042
- ops.prepend(node, Fragments.fromHtml(v.value));
4034
+ ops.prepend(node, Fragments.fromHtml(typeof v.value === 'string' ? v.value : String(v.value)));
4043
4035
  break;
4044
4036
  case nodes.dom.n:
4037
+ if (!(v.value instanceof Node)) {
4038
+ throw new TypeError(`Expected a Node, got '${typeof v.value}'`);
4039
+ }
4045
4040
  ops.prepend(node, v.value);
4046
4041
  break;
4047
4042
  }
@@ -4050,10 +4045,8 @@ class CommandsHandler {
4050
4045
  }
4051
4046
  }
4052
4047
 
4053
- // Module-isolated string cache for dataset-to-attribute conversions, bounded with
4054
- // FIFO eviction like the expression ast cache
4055
- const attributeCache = new Map();
4056
- const ATTRIBUTE_CACHE_MAX_SIZE = 1000;
4048
+ // Module-isolated string cache for dataset-to-attribute conversions
4049
+ const attributeCache = new BoundedCache(1000);
4057
4050
 
4058
4051
  /**
4059
4052
  * Converts a tpl camelCase dataset key into a kebab-case attribute name.
@@ -4062,21 +4055,21 @@ const ATTRIBUTE_CACHE_MAX_SIZE = 1000;
4062
4055
  * @returns {string}
4063
4056
  */
4064
4057
  function toAttr(dataSetKey) {
4065
- let cached = attributeCache.get(dataSetKey);
4066
- if (!cached) {
4067
- if (attributeCache.size >= ATTRIBUTE_CACHE_MAX_SIZE) {
4068
- attributeCache.delete(attributeCache.keys().next().value);
4069
- }
4070
- cached = dataSetKey
4058
+ return attributeCache.getOrCompute(dataSetKey, (k) =>
4059
+ k
4071
4060
  .substring(3)
4072
4061
  .split(/(?=[A-Z])/)
4073
4062
  .join('-')
4074
- .toLowerCase();
4075
- attributeCache.set(dataSetKey, cached);
4076
- }
4077
- return cached;
4063
+ .toLowerCase(),
4064
+ );
4078
4065
  }
4079
4066
 
4067
+ /**
4068
+ * A compiled fragment together with the scope it renders in. Both are
4069
+ * immutable: adding data or replacing the scope returns a new Template, so one
4070
+ * piece of compiled markup can be rendered against any number of scopes and a
4071
+ * registry can reuse a single template for every element that requests it.
4072
+ */
4080
4073
  class Template {
4081
4074
  /**
4082
4075
  * Creates a template from a string.
@@ -4086,7 +4079,7 @@ class Template {
4086
4079
  * @returns the template
4087
4080
  */
4088
4081
  static fromHtml(html, modules, ...data) {
4089
- return new Template(Fragments.fromHtml(html), modules, data);
4082
+ return new Template(Fragments.fromHtml(html), new ExpressionEvaluator(modules, data));
4090
4083
  }
4091
4084
 
4092
4085
  /**
@@ -4102,7 +4095,7 @@ class Template {
4102
4095
  throw new Error('template selector does not match any template tag');
4103
4096
  }
4104
4097
  const fragment = document.adoptNode(templateEl.content);
4105
- return new Template(fragment, modules, data);
4098
+ return new Template(fragment, new ExpressionEvaluator(modules, data));
4106
4099
  }
4107
4100
 
4108
4101
  /**
@@ -4114,7 +4107,7 @@ class Template {
4114
4107
  */
4115
4108
  static fromTemplate(templateEl, modules, ...data) {
4116
4109
  const fragment = document.adoptNode(templateEl.content);
4117
- return new Template(fragment, modules, data);
4110
+ return new Template(fragment, new ExpressionEvaluator(modules, data));
4118
4111
  }
4119
4112
 
4120
4113
  /**
@@ -4125,42 +4118,33 @@ class Template {
4125
4118
  * @returns the template
4126
4119
  */
4127
4120
  static fromFragment(fragment, modules, ...data) {
4128
- return new Template(fragment, modules, data);
4121
+ return new Template(fragment, new ExpressionEvaluator(modules, data));
4129
4122
  }
4130
4123
  #fragment;
4131
- #modules;
4132
- #dataStack;
4124
+ #evaluator;
4133
4125
  /**
4134
- * Creates a template.
4126
+ * Creates a template: a fragment plus the scope it renders in.
4135
4127
  * @param {DocumentFragment} fragment
4136
- * @param {{ [x: string]: any; } | null | undefined} modules
4137
- * @param {any[]} dataStack
4128
+ * @param {ExpressionEvaluator} evaluator the modules and data stack the expressions resolve against
4138
4129
  */
4139
- constructor(fragment, modules, dataStack) {
4130
+ constructor(fragment, evaluator) {
4140
4131
  this.#fragment = fragment;
4141
- this.#modules = modules;
4142
- this.#dataStack = dataStack;
4132
+ this.#evaluator = evaluator;
4143
4133
  }
4144
4134
  /**
4145
- * Creates a new Template replacing the modules and dataStack from a context.
4146
- * @param {{modules: { [x: string]: any; } | null | undefined, data: any[]}} context
4135
+ * Creates a new Template rendering in another scope: the one way to rebind
4136
+ * a compiled template to a registry's modules and data.
4137
+ * @param {ExpressionEvaluator} evaluator
4147
4138
  */
4148
- withContext({ modules, data }) {
4149
- return new Template(this.#fragment, modules, data);
4150
- }
4151
- /**
4152
- * Creates a new Template replacing the modules and dataStack from a registry.
4153
- * @param any registry
4154
- */
4155
- withContextFrom(registry) {
4156
- return this.withContext(registry.context());
4139
+ withEvaluator(evaluator) {
4140
+ return new Template(this.#fragment, evaluator);
4157
4141
  }
4158
4142
  /**
4159
4143
  * Creates a new Template replacing the fragment.
4160
4144
  * @param {DocumentFragment} fragment
4161
4145
  */
4162
4146
  withFragment(fragment) {
4163
- return new Template(fragment, this.#modules, this.#dataStack);
4147
+ return new Template(fragment, this.#evaluator);
4164
4148
  }
4165
4149
  /**
4166
4150
  * Creates a new Template with a new module added.
@@ -4168,49 +4152,38 @@ class Template {
4168
4152
  * @param {{[k: string]: any}} value
4169
4153
  */
4170
4154
  withModule(name, value) {
4171
- const module = name ? { [name]: value } : value;
4172
- return new Template(this.#fragment, { ...this.#modules, ...module }, this.#dataStack);
4173
- }
4174
- /**
4175
- * Creates a new Template replacing the modules.
4176
- * @param {{ [x: string]: any; }?} modules
4177
- */
4178
- withModules(modules) {
4179
- return new Template(this.#fragment, modules, this.#dataStack);
4180
- }
4181
- /**
4182
- * Creates a new Template replacing the data stack.
4183
- * @param {any[]} dataStack the dataStack
4184
- */
4185
- withData(dataStack) {
4186
- return new Template(this.#fragment, this.#modules, dataStack);
4155
+ return new Template(this.#fragment, this.#evaluator.withModule(name, value));
4187
4156
  }
4188
4157
  /**
4189
4158
  * Creates a new Template with new a data overlay added to the stack.
4190
4159
  * @param {...*} data
4191
4160
  */
4192
4161
  withOverlay(...data) {
4193
- return new Template(
4194
- this.#fragment,
4195
- this.#modules,
4196
- data.length === 0 ? this.#dataStack : [...this.#dataStack, ...data],
4197
- );
4162
+ return new Template(this.#fragment, this.#evaluator.withOverlay(...data));
4198
4163
  }
4199
4164
  /**
4200
- * Evaluates an expression using the configured modules and data.
4165
+ * Evaluates an expression in this template's scope, widened by the overlays.
4201
4166
  * @param {string} expression
4202
- * @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
4203
4167
  * @param {...*} data
4204
4168
  * @returns the evaluated expression result
4205
4169
  */
4206
- evaluate(expression, mode, ...data) {
4207
- return new ExpressionEvaluator(this.#modules, this.#dataStack).withOverlay(...data).evaluate(expression, mode);
4170
+ evaluateExpression(expression, ...data) {
4171
+ return this.#evaluator.withOverlay(...data).evaluateExpression(expression);
4172
+ }
4173
+ /**
4174
+ * Evaluates a templated text, the `{{ }}` form, in this template's scope.
4175
+ * @param {string} text
4176
+ * @param {...*} data
4177
+ * @returns the parts the text evaluates to
4178
+ */
4179
+ evaluateTemplated(text, ...data) {
4180
+ return this.#evaluator.withOverlay(...data).evaluateTemplated(text);
4208
4181
  }
4209
4182
  /**
4210
4183
  * Returns an expression evaluator with bound modules and dataStack.
4211
4184
  */
4212
4185
  evaluator() {
4213
- return new ExpressionEvaluator(this.#modules, this.#dataStack);
4186
+ return this.#evaluator;
4214
4187
  }
4215
4188
  /**
4216
4189
  * Renders the template.
@@ -4224,10 +4197,10 @@ class Template {
4224
4197
  imported.nodeType === Node.DOCUMENT_FRAGMENT_NODE
4225
4198
  ? imported
4226
4199
  : (() => {
4227
- const d = new DocumentFragment();
4228
- d.appendChild(imported);
4229
- return d;
4230
- })();
4200
+ const d = new DocumentFragment();
4201
+ d.appendChild(imported);
4202
+ return d;
4203
+ })();
4231
4204
  const iterator = document.createNodeIterator(
4232
4205
  fragment,
4233
4206
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
@@ -4238,9 +4211,9 @@ class Template {
4238
4211
  ops.cleanup();
4239
4212
  if (node.nodeType === Node.TEXT_NODE) {
4240
4213
  try {
4241
- CommandsHandler.textNode(node, node.nodeValue, ops, this.#modules, this.#dataStack);
4214
+ CommandsHandler.textNode(node, node.nodeValue, ops, this.#evaluator);
4242
4215
  } catch (ex) {
4243
- throw new RenderError('Error evaluating text node', node, ex);
4216
+ throw RenderError.wrap('Error evaluating text node', node, ex);
4244
4217
  }
4245
4218
  continue;
4246
4219
  }
@@ -4251,9 +4224,11 @@ class Template {
4251
4224
  }
4252
4225
  const value = ops.popData(el, command);
4253
4226
  try {
4254
- CommandsHandler[command](el, value, ops, this.#modules, this.#dataStack);
4227
+ CommandsHandler[command](el, value, ops, this.#evaluator);
4255
4228
  } catch (ex) {
4256
- throw new RenderError(`Error evaluating command ${command}`, el, ex);
4229
+ //the directive is popped before it runs, so the open tag no
4230
+ //longer carries it: the frame names it and its expression
4231
+ throw RenderError.wrap(`Error evaluating data-tpl-${toAttr(command)}="${value}"`, el, ex);
4257
4232
  }
4258
4233
  if (ops.removed(el)) {
4259
4234
  break;
@@ -4264,24 +4239,31 @@ class Template {
4264
4239
  continue;
4265
4240
  }
4266
4241
  const attributeName = toAttr(dataSetKey);
4242
+ const expression = ops.popData(el, dataSetKey);
4267
4243
  try {
4268
- const expression = ops.popData(el, dataSetKey);
4269
- const evaluated = Expressions.interpret(this.#modules, this.#dataStack, expression);
4244
+ const evaluated = this.#evaluator.evaluateExpression(expression);
4270
4245
  if (typeof evaluated === 'boolean') {
4271
- Attributes.toggle(el, attributeName, evaluated);
4246
+ el.toggleAttribute(attributeName, evaluated);
4272
4247
  continue;
4273
4248
  }
4274
4249
  if (evaluated !== null && evaluated !== undefined) {
4275
4250
  el.setAttribute(attributeName, evaluated);
4276
4251
  }
4277
4252
  } catch (ex) {
4278
- throw new RenderError(`Error evaluating command ${dataSetKey}`, el, ex);
4253
+ throw RenderError.wrap(`Error evaluating data-tpl-${toAttr(dataSetKey)}="${expression}"`, el, ex);
4279
4254
  }
4280
4255
  }
4281
4256
  }
4282
4257
  ops.cleanup();
4283
4258
  return fragment;
4284
4259
  } catch (ex) {
4260
+ //a command, a text node or a nested render already named the node it
4261
+ //failed on: wrapping again would add a frame for the fragment that
4262
+ //contains it, one per level, serializing the whole template into the
4263
+ //message that survives. Only a failure outside those is framed here
4264
+ if (ex instanceof RenderError) {
4265
+ throw ex;
4266
+ }
4285
4267
  throw new RenderError('Error rendering template', this.#fragment, ex);
4286
4268
  }
4287
4269
  }
@@ -4300,7 +4282,7 @@ class Template {
4300
4282
  el.appendChild(this.render());
4301
4283
  }
4302
4284
  /**
4303
- * Renders this template appending the resulting fragment to the first Element maching the selector, if exists.
4285
+ * Renders this template on the first Element matching the selector (replacing children), if exists.
4304
4286
  * @param {string} selector
4305
4287
  */
4306
4288
  renderToSelector(selector) {
@@ -4310,7 +4292,7 @@ class Template {
4310
4292
  }
4311
4293
  }
4312
4294
  /**
4313
- * Renders this template appending the resulting fragment to the Element maching the selector, if exists.
4295
+ * Renders this template appending the resulting fragment to the Element matching the selector, if exists.
4314
4296
  * @param {string} selector
4315
4297
  */
4316
4298
  appendToSelector(selector) {
@@ -4334,11 +4316,77 @@ class Template {
4334
4316
  }
4335
4317
  }
4336
4318
 
4319
+ /**
4320
+ * A render failure, one frame per nesting level. Each frame names the node it
4321
+ * failed on and what was being evaluated there, so the chain reads as the path
4322
+ * from the template's root down to the offending expression. The frame carries
4323
+ * the node's identification only, an open tag rather than its whole subtree:
4324
+ * the markup is serialized on demand through `html`, and the live node stays on
4325
+ * `node`, so a failure costs no clone and a nested failure does not embed the
4326
+ * page in its own message.
4327
+ */
4337
4328
  class RenderError extends Error {
4329
+ /**
4330
+ * How many frames a chain keeps. The innermost are the specific ones, so a
4331
+ * deeper nesting drops the outer context rather than the failure site: three
4332
+ * frames name the offending node and the two levels that hold it, which is
4333
+ * the path a reader follows without the page arriving with it.
4334
+ */
4335
+ static FRAMES = 3;
4336
+ #node;
4337
+ #depth;
4338
+ /** true when the budget dropped the outer frames of this chain */
4339
+ truncated = false;
4340
+ /**
4341
+ * Frames a failure, unless the chain already spent its budget: then the
4342
+ * cause travels on, marked so a report can say the outer context was
4343
+ * dropped.
4344
+ */
4345
+ static wrap(message, nodeOrFragment, cause) {
4346
+ if (cause instanceof RenderError && cause.depth >= RenderError.FRAMES) {
4347
+ cause.truncated = true;
4348
+ return cause;
4349
+ }
4350
+ return new RenderError(message, nodeOrFragment, cause);
4351
+ }
4338
4352
  constructor(message, nodeOrFragment, cause) {
4339
- super(`${message} in \`${RenderError.stringify(nodeOrFragment)}\``, { cause });
4353
+ super(`${message} in \`${RenderError.describe(nodeOrFragment)}\``, { cause });
4340
4354
  this.name = 'RenderError';
4341
- this.node = nodeOrFragment.cloneNode(true);
4355
+ this.#node = nodeOrFragment;
4356
+ this.#depth = (cause instanceof RenderError ? cause.depth : 0) + 1;
4357
+ }
4358
+ /** How many frames this chain carries, this one included. */
4359
+ get depth() {
4360
+ return this.#depth;
4361
+ }
4362
+ /** The node the render failed on, live: it keeps its place in the fragment being built. */
4363
+ get node() {
4364
+ return this.#node;
4365
+ }
4366
+ /** The node's markup, serialized when asked for rather than on every failure. */
4367
+ get html() {
4368
+ return RenderError.stringify(this.#node);
4369
+ }
4370
+ /**
4371
+ * What identifies a node in a frame: an element by its open tag, a text node
4372
+ * by its source, a fragment by the open tags of the elements it holds.
4373
+ */
4374
+ static describe(nodeOrFragment) {
4375
+ if (nodeOrFragment.nodeType === Node.TEXT_NODE) {
4376
+ return RenderError.#ellipsize(String(nodeOrFragment.nodeValue).trim());
4377
+ }
4378
+ if (nodeOrFragment.nodeType === Node.ELEMENT_NODE) {
4379
+ const el = /** @type Element */ (nodeOrFragment);
4380
+ const attrs = Array.from(el.attributes, (a) => ` ${a.name}="${a.value}"`).join('');
4381
+ return RenderError.#ellipsize(`<${el.localName}${attrs}>`);
4382
+ }
4383
+ const children = Array.from(nodeOrFragment.childNodes)
4384
+ .filter((n) => n.nodeType === Node.ELEMENT_NODE || String(n.nodeValue ?? '').trim().length > 0)
4385
+ .map((n) => RenderError.describe(n));
4386
+ return RenderError.#ellipsize(children.join(''));
4387
+ }
4388
+ static #ellipsize(text) {
4389
+ return text.length > 120 ? `${text.slice(0, 120)}…` : text;
4342
4390
  }
4343
4391
  static stringify(nodeOrFragment) {
4344
4392
  const t = document.createElement('template');
@@ -4365,59 +4413,107 @@ class RenderError extends Error {
4365
4413
  }
4366
4414
  }
4367
4415
 
4416
+ /**
4417
+ * Tracks the elements waiting to render. `ready` resolves once the queue has
4418
+ * drained and never rejects; the promise kept per element resolves when that
4419
+ * element has rendered and rejects with whatever its upgrade threw.
4420
+ */
4368
4421
  class UpgradeQueue {
4369
4422
  #q = new Map();
4423
+ #readyResolve;
4424
+ #ready = new Promise((resolve) => {
4425
+ this.#readyResolve = resolve;
4426
+ });
4370
4427
  constructor() {
4371
- document.addEventListener('DOMContentLoaded', async () => {
4372
- await this.settle();
4373
- document.dispatchEvent(
4374
- new CustomEvent('ftl:ready', {
4375
- bubbles: false,
4376
- cancelable: false,
4377
- }),
4378
- );
4428
+ Nodes.waitDomContentLoaded(document).then(() => {
4429
+ this.#start();
4379
4430
  });
4380
4431
  }
4381
- #finished = new Map();
4432
+ /**
4433
+ * Waits for the page's readiness: the promise resolves right after the
4434
+ * ftl:ready event is dispatched, and immediately when that moment already
4435
+ * passed.
4436
+ * @returns {Promise<void>}
4437
+ */
4438
+ ready() {
4439
+ return this.#ready;
4440
+ }
4441
+ async #start() {
4442
+ await this.settled();
4443
+ document.dispatchEvent(
4444
+ new CustomEvent('ftl:ready', {
4445
+ bubbles: false,
4446
+ cancelable: false,
4447
+ }),
4448
+ );
4449
+ this.#readyResolve();
4450
+ }
4382
4451
  enqueue(el) {
4383
4452
  if (this.#q.has(el)) {
4384
4453
  //already upgrading, can happen when disconnecting an element
4385
4454
  //while it's already queued for upgrade (e.g.: ful-form)
4386
4455
  return;
4387
4456
  }
4388
- //settling waits on a signal that only ever resolves, so nothing here attaches a
4389
- //rejection handler to the upgrade itself: a component that fails is still
4390
- //reported the way it always was
4457
+ //one entry, two signals: readiness waits on a signal that only ever
4458
+ //resolves, so nothing here attaches a rejection handler to the upgrade
4459
+ //itself and a component that fails is still reported the way it always was
4391
4460
  const { promise: finished, resolve: markFinished } = /** @type {PromiseWithResolvers<void>} */ (
4392
4461
  Promise.withResolvers()
4393
4462
  );
4394
- const promise = Nodes.waitParsed(el)
4463
+ const upgrade = Nodes.waitParsed(el)
4395
4464
  .then(() => el.upgrade())
4396
4465
  .finally(() => {
4397
4466
  this.#q.delete(el);
4398
- this.#finished.delete(el);
4399
4467
  markFinished();
4400
4468
  });
4401
- this.#q.set(el, promise);
4402
- this.#finished.set(el, finished);
4469
+ this.#q.set(el, { upgrade, finished });
4403
4470
  }
4404
4471
  /**
4405
- * Waits for every queued upgrade to settle, including the ones enqueued while
4406
- * waiting: a component is only queued once its parent connects it, so a single pass
4407
- * would miss everything nested. A component that fails to upgrade does not hold the
4408
- * others back, readiness means the queue drained rather than that everything worked.
4472
+ * The one fixed-point loop: drains the accepted entries, including the ones
4473
+ * enqueued while waiting, since a component is only queued once its parent
4474
+ * connects it and a single pass would miss everything nested.
4409
4475
  */
4410
- async settle() {
4411
- while (this.#finished.size !== 0) {
4412
- await Promise.all(Array.from(this.#finished.values()));
4476
+ async #drain(accept, pick) {
4477
+ for (;;) {
4478
+ const pending = Array.from(this.#q)
4479
+ .filter(([el]) => accept(el))
4480
+ .map(([, entry]) => pick(entry));
4481
+ if (pending.length === 0) {
4482
+ return;
4483
+ }
4484
+ await Promise.all(pending);
4413
4485
  }
4414
4486
  }
4415
- get entries() {
4416
- return this.#q.entries();
4487
+ /**
4488
+ * Waits for the whole queue to drain. Never rejects: a component that fails
4489
+ * does not hold the others back, and readiness means the queue drained rather
4490
+ * than that everything worked.
4491
+ */
4492
+ settled() {
4493
+ return this.#drain(() => true, (entry) => entry.finished);
4494
+ }
4495
+ /** Waits for the accepted upgrades, rejecting with the first that failed. */
4496
+ upgraded(accept) {
4497
+ return this.#drain(accept, (entry) => entry.upgrade);
4498
+ }
4499
+ /** The pending upgrade of one element, undefined when it is not queued. */
4500
+ whenUpgraded(el) {
4501
+ return this.#q.get(el)?.upgrade;
4502
+ }
4503
+ /** The elements whose upgrade is still pending, in queue order. */
4504
+ pending() {
4505
+ return Array.from(this.#q.keys());
4417
4506
  }
4418
4507
  }
4419
4508
 
4420
-
4509
+ /**
4510
+ * The page's single source of truth for elements, modules, data, components
4511
+ * and attribute mappers. Elements defined before configure() are deferred and
4512
+ * defined by it; from then on every defineElement takes effect immediately.
4513
+ * The exported `registry` singleton is the page's own; a `new Registry()` is a
4514
+ * separate instance, and Rendering and the ftl:ready machinery wait on the
4515
+ * singleton alone.
4516
+ */
4421
4517
  class Registry {
4422
4518
  #tagToClass = {};
4423
4519
  #configured = false;
@@ -4475,30 +4571,20 @@ class Registry {
4475
4571
  return value == null ? null : value.join(',');
4476
4572
  },
4477
4573
  },
4478
- csvm: {
4479
- unmarshal(str, name, el) {
4480
- if (el.hasAttribute('multiple')) {
4481
- return str === null
4482
- ? []
4483
- : str
4484
- .split(',')
4485
- .map((e) => e.trim())
4486
- .filter((e) => e);
4487
- }
4488
- return str === null || str === '' ? null : str;
4489
- },
4490
- marshal(value, name, el) {
4491
- if (el.hasAttribute('multiple')) {
4492
- return value === null ? null : value.join(',');
4493
- }
4494
- return value == null ? null : String(value);
4495
- },
4496
- },
4497
4574
  };
4498
4575
  #components = {};
4499
4576
  #modules;
4500
4577
  #data = [];
4578
+ #evaluator = new ExpressionEvaluator(undefined, []);
4501
4579
  #upgradeQueue = new UpgradeQueue();
4580
+ /**
4581
+ * Registers a custom element under its tag: the class is augmented with its
4582
+ * BITS (observed attributes, mappers, templates) and handed to the platform.
4583
+ * Before configure() the definition is deferred, so import order never
4584
+ * matters.
4585
+ * @param {string} tag
4586
+ * @param {*} klass a ParsedElement subclass
4587
+ */
4502
4588
  defineElement(tag, klass) {
4503
4589
  if (!this.#configured) {
4504
4590
  this.#tagToClass[tag] = klass;
@@ -4507,16 +4593,69 @@ class Registry {
4507
4593
  this.#augmentAndDefineElement(tag, klass);
4508
4594
  return this;
4509
4595
  }
4596
+ /**
4597
+ * The attribute declarations a class composes along its inheritance chain,
4598
+ * base first: `observed` stay live after the upgrade and drive the property
4599
+ * `propertyOf` names, `attributes` are the configuration read once at it and
4600
+ * drive no property at all. A subclass's entry for a name overrides its
4601
+ * ancestors', so a base class declares what every subclass keeps observing
4602
+ * (a protocol attribute such as Field's disabled claim) and a leaf refines a
4603
+ * mapping, or moves a name's position, without repeating the whole list.
4604
+ *
4605
+ * The walk stops where the platform's own class hierarchy begins: no earlier
4606
+ * stop can work, since a registered ancestor carries an own BITS of its own,
4607
+ * and nothing above the elements declares anything.
4608
+ *
4609
+ * Everything deriving a component's attribute vocabulary reads it here, so
4610
+ * the runtime and whatever documents it cannot walk the chain differently.
4611
+ * @param {*} klass a ParsedElement subclass
4612
+ * @returns {{ observed: string[], attributes: string[] }}
4613
+ */
4614
+ static declarationsOf(klass) {
4615
+ const chain = [];
4616
+ for (let c = klass; c !== null && c !== HTMLElement; c = Object.getPrototypeOf(c)) {
4617
+ chain.unshift(c);
4618
+ }
4619
+ const own = (name) => chain.flatMap((c) => Object.getOwnPropertyDescriptor(c, name)?.value ?? []);
4620
+ return { observed: own('observed'), attributes: own('attributes') };
4621
+ }
4622
+ /**
4623
+ * The property an observed attribute drives, by the platform's own
4624
+ * dash-to-camel rule: the one `dataset` applies, so `clear-invalid-on-change`
4625
+ * would reach `clearInvalidOnChange` the way `data-clear-invalid-on-change`
4626
+ * reaches `dataset.clearInvalidOnChange`. A single-word name is returned
4627
+ * unchanged, which is what every observed attribute in the library is.
4628
+ *
4629
+ * It exists because the observed tier is the one that becomes properties:
4630
+ * without it an attribute could only be observed if its name happened to be
4631
+ * a usable identifier, which is why every multiword observed attribute here
4632
+ * used to be squashed into one word while the configuration tier, which
4633
+ * never becomes a property, spelled the same idea with a dash.
4634
+ *
4635
+ * Only this direction is mapped. A property never derives its attribute: a
4636
+ * setter reflects through `reflectTo`, naming the attribute it writes.
4637
+ * @param {string} attribute
4638
+ * @returns {string}
4639
+ */
4640
+ static propertyOf(attribute) {
4641
+ return attribute.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
4642
+ }
4510
4643
  #augmentAndDefineElement(tag, klass) {
4511
- const { observed, attributes, template, templates, slots, mappers } = klass;
4512
- const observedNames = (observed ?? []).map((a) => a.split(':')[0]);
4513
- const attrToMapper = [...(attributes ?? []), ...(observed ?? [])].reduce((acc, a) => {
4644
+ const { observed, attributes } = Registry.declarationsOf(klass);
4645
+ const { template, templates, slots, mappers, config } = klass;
4646
+ //a name a subclass re-declares takes the subclass's position, which is
4647
+ //how a field whose value setter reads its own shape attributes declares
4648
+ //that its value lands after them: the order the declarations compose in
4649
+ //is the order the base applies them
4650
+ const declaredNames = observed.map((a) => a.split(':')[0]);
4651
+ const observedNames = [...new Set(declaredNames.reverse())].reverse();
4652
+ const attrToMapper = [...attributes, ...observed].reduce((acc, a) => {
4514
4653
  const [attr, maybeType] = a.split(':');
4515
- const type = maybeType?.trim() ?? 'string';
4654
+ const type = maybeType ?? 'string';
4516
4655
  if (!(type in this.#mappers) && !(type in (mappers ?? {}))) {
4517
4656
  throw new Error(`unsupported attribute type: ${type}`);
4518
4657
  }
4519
- acc[attr.trim()] = mappers?.[type] ?? this.#mappers[type];
4658
+ acc[attr] = mappers?.[type] ?? this.#mappers[type];
4520
4659
  return acc;
4521
4660
  }, {});
4522
4661
 
@@ -4526,43 +4665,97 @@ class Registry {
4526
4665
  const nameToTemplate = Object.fromEntries(namesAndTemplates);
4527
4666
 
4528
4667
  klass.BITS = {
4668
+ //the defining registry travels with the definition: an element resolves
4669
+ //its templates and its components through the registry that defined it,
4670
+ //not through whichever one a module happened to import
4671
+ registry: this,
4529
4672
  enqueue: (el) => this.#upgradeQueue.enqueue(el),
4530
4673
  SLOTS: slots,
4674
+ //the class's own constants, overlaid on its templates as `config`:
4675
+ //read here with the rest of the declaration, so a page replacing them
4676
+ //does it before configure() like every other definition-time choice
4677
+ CONFIG: config,
4531
4678
  OBSERVED: observedNames,
4679
+ DECLARED: [...new Set([...observedNames, ...attributes.map((a) => a.split(':')[0])])],
4532
4680
  ATTR_TO_MAPPER: attrToMapper,
4681
+ //resolved once here rather than at every attribute write
4682
+ ATTR_TO_PROPERTY: Object.fromEntries(observedNames.map((a) => [a, Registry.propertyOf(a)])),
4533
4683
  TEMPLATES: nameToTemplate,
4534
4684
  };
4535
4685
  customElements.define(tag, klass);
4536
4686
  }
4687
+ /**
4688
+ * Merges one module under its name, its functions resolving as
4689
+ * `#name:fn`; an empty name merges a whole map, whose keys resolve bare,
4690
+ * as `#fn`.
4691
+ * @param {string} name
4692
+ * @param {object} value
4693
+ */
4537
4694
  defineModule(name, value) {
4538
4695
  const module = name ? { [name]: value } : value;
4539
4696
  this.#modules = { ...this.#modules, ...module };
4697
+ this.#rebind();
4540
4698
  return this;
4541
4699
  }
4700
+ /**
4701
+ * Replaces the whole module map.
4702
+ * @param {object} ms
4703
+ */
4542
4704
  defineModules(ms) {
4543
4705
  this.#modules = ms;
4706
+ this.#rebind();
4544
4707
  return this;
4545
4708
  }
4709
+ /**
4710
+ * Registers a named component (a loader, a response mapper), fetched back
4711
+ * through component().
4712
+ * @param {string} name
4713
+ * @param {*} value
4714
+ */
4546
4715
  defineComponent(name, value) {
4547
4716
  this.#components[name] = value;
4548
4717
  return this;
4549
4718
  }
4719
+ /**
4720
+ * Replaces the data stack the templates evaluate over.
4721
+ * @param {...any} data
4722
+ */
4550
4723
  defineData(...data) {
4551
4724
  this.#data = data;
4725
+ this.#rebind();
4552
4726
  return this;
4553
4727
  }
4728
+ /**
4729
+ * Appends to the data stack, the later entry winning a shared name.
4730
+ * @param {...any} data
4731
+ */
4554
4732
  defineOverlay(...data) {
4555
4733
  this.#data = [...this.#data, ...data];
4734
+ this.#rebind();
4556
4735
  return this;
4557
4736
  }
4737
+ /**
4738
+ * Registers a custom attribute mapper type, available to every later
4739
+ * `name:type` declaration.
4740
+ * @param {string} k
4741
+ * @param {{ unmarshal(str: string|null, name: string, el: Element): any, marshal(value: any, name: string, el: Element): string|null }} v
4742
+ */
4558
4743
  defineMapper(k, v) {
4559
4744
  this.#mappers[k] = v;
4560
4745
  return this;
4561
4746
  }
4747
+ /**
4748
+ * Hands the registry over to the plugin's configure.
4749
+ * @param {{ configure(registry: Registry): void }} p
4750
+ */
4562
4751
  plugin(p) {
4563
4752
  p.configure(this);
4564
4753
  return this;
4565
4754
  }
4755
+ /**
4756
+ * Defines every element deferred so far; from then on, defineElement takes
4757
+ * effect immediately.
4758
+ */
4566
4759
  configure() {
4567
4760
  for (const [tag, klass] of Object.entries(this.#tagToClass)) {
4568
4761
  this.#augmentAndDefineElement(tag, klass);
@@ -4571,15 +4764,42 @@ class Registry {
4571
4764
  this.#configured = true;
4572
4765
  return this;
4573
4766
  }
4574
- get upgrades() {
4575
- return this.#upgradeQueue.entries;
4767
+ /**
4768
+ * Waits for the queued upgrades the filter accepts, rejecting with the first
4769
+ * that failed: the rejecting barrier Rendering is a facade over.
4770
+ * @param {(el: Element) => boolean} accept
4771
+ */
4772
+ settle(accept) {
4773
+ return this.#upgradeQueue.upgraded(accept);
4774
+ }
4775
+ /** The pending upgrade of one element, undefined when it is not queued. */
4776
+ whenUpgraded(el) {
4777
+ return this.#upgradeQueue.whenUpgraded(el);
4778
+ }
4779
+ /** The elements whose upgrade is still pending, in queue order. */
4780
+ pending() {
4781
+ return this.#upgradeQueue.pending();
4782
+ }
4783
+ /**
4784
+ * Waits for the page's readiness: the same moment the ftl:ready event is
4785
+ * dispatched at, resolving immediately when that moment already passed.
4786
+ * @returns {Promise<void>}
4787
+ */
4788
+ ready() {
4789
+ return this.#upgradeQueue.ready();
4576
4790
  }
4577
- context() {
4578
- return { modules: this.#modules, data: this.#data };
4791
+ #rebind() {
4792
+ this.#evaluator = new ExpressionEvaluator(this.#modules, this.#data);
4579
4793
  }
4794
+ /**
4795
+ * The scope every template on this registry renders in: the modules and the
4796
+ * data stack, as one value. Replaced whenever either is defined, so a holder
4797
+ * of an older one keeps rendering against what it was handed.
4798
+ */
4580
4799
  evaluator() {
4581
- return new ExpressionEvaluator(this.#modules, this.#data);
4800
+ return this.#evaluator;
4582
4801
  }
4802
+ /** The component registered under the name, undefined when none is. */
4583
4803
  component(name) {
4584
4804
  return this.#components[name];
4585
4805
  }
@@ -4587,49 +4807,35 @@ class Registry {
4587
4807
 
4588
4808
  const registry = new Registry();
4589
4809
 
4810
+ /** The Template factories, each bound to the page registry's scope. */
4811
+ /** The Template factories bound to the page's own registry: the same four sources, with its modules and data already applied. */
4590
4812
  class Templates {
4591
4813
  static fromHtml(html) {
4592
- const { modules, data } = registry.context();
4593
- return Template.fromHtml(html, modules, ...data);
4814
+ return Template.fromHtml(html).withEvaluator(registry.evaluator());
4594
4815
  }
4595
4816
  static fromSelector(selector) {
4596
- const { modules, data } = registry.context();
4597
- return Template.fromSelector(selector, modules, ...data);
4817
+ return Template.fromSelector(selector).withEvaluator(registry.evaluator());
4598
4818
  }
4599
4819
  static fromTemplate(templateEl) {
4600
- const { modules, data } = registry.context();
4601
- return Template.fromTemplate(templateEl, modules, ...data);
4820
+ return Template.fromTemplate(templateEl).withEvaluator(registry.evaluator());
4602
4821
  }
4603
4822
  static fromFragment(fragment) {
4604
- const { modules, data } = registry.context();
4605
- return Template.fromFragment(fragment, modules, ...data);
4823
+ return Template.fromFragment(fragment).withEvaluator(registry.evaluator());
4606
4824
  }
4607
4825
  }
4608
4826
 
4609
4827
  /**
4610
- * Waits for the queued upgrades matching the filter, including the ones enqueued while
4611
- * waiting: a component is only queued once its parent connects it, so a single pass
4612
- * would miss everything nested.
4613
- * @param {(el: Element) => boolean} accept
4828
+ * Awaitable rendering barriers over the registry's upgrade queue. These are
4829
+ * the rejecting waits: a failed upgrade among the awaited components rejects
4830
+ * the wait, where registry.ready() only ever means the queue drained and
4831
+ * leaves a failed component to its own unhandled-rejection report.
4614
4832
  */
4615
- const settle = async (accept) => {
4616
- for (;;) {
4617
- const pending = Array.from(registry.upgrades)
4618
- .filter(([child]) => accept(child))
4619
- .map(([, promise]) => promise);
4620
- if (pending.length === 0) {
4621
- return;
4622
- }
4623
- await Promise.all(pending);
4624
- }
4625
- };
4626
-
4627
4833
  class Rendering {
4628
- static async waitFor(el) {
4629
- await settle((child) => el.contains(child));
4834
+ static waitFor(el) {
4835
+ return registry.settle((child) => el.contains(child));
4630
4836
  }
4631
- static async waitForChildren(el) {
4632
- await settle((child) => el !== child && el.contains(child));
4837
+ static waitForChildren(el) {
4838
+ return registry.settle((child) => el !== child && el.contains(child));
4633
4839
  }
4634
4840
  }
4635
4841
 
@@ -4641,117 +4847,414 @@ class Rendering {
4641
4847
  * @property {(val: any, name: string, el: Element) => string|null} marshal
4642
4848
  */
4643
4849
 
4644
-
4645
4850
  class ParsedElement extends HTMLElement {
4646
4851
  static BITS = {
4852
+ //an element the registry never defined still answers the page's own
4853
+ registry,
4647
4854
  enqueue: (el) => {},
4648
4855
  SLOTS: false,
4856
+ /** @type {object|undefined} */
4857
+ CONFIG: undefined,
4858
+ /** @type {string[]} */
4649
4859
  OBSERVED: [],
4860
+ /** @type {string[]} */
4861
+ DECLARED: [],
4650
4862
  /** @type {Record<string, Mapper>} */
4651
4863
  ATTR_TO_MAPPER: {},
4864
+ /** @type {Record<string, string>} */
4865
+ ATTR_TO_PROPERTY: {},
4652
4866
  TEMPLATES: {},
4653
4867
  };
4654
4868
  static get observedAttributes() {
4655
4869
  return this.BITS.OBSERVED;
4656
4870
  }
4871
+ constructor() {
4872
+ super();
4873
+ this.internals = this.attachInternals();
4874
+ }
4875
+ /**
4876
+ * The platform's window into the element's own state. The base attaches it
4877
+ * for every element, so a subclass never calls attachInternals itself: the
4878
+ * platform allows it once, and a second call throws. Form association is a
4879
+ * property of the definition rather than of who attached, so a subclass
4880
+ * declaring `static formAssociated` still gets the form apis here.
4881
+ *
4882
+ * A subclass must not redeclare it: a class field with no initializer
4883
+ * assigns undefined after super() returns, which would wipe it.
4884
+ */
4885
+ internals;
4657
4886
  #parsed = false;
4658
- #reflecting = 0;
4887
+ #started = false;
4888
+ /** the attributes whose own reflection is in flight */
4889
+ #reflecting = new Set();
4890
+ /** the observed snapshot between the upgrade's start and its render's end */
4891
+ #pending = /** @type {{ [k: string]: any } | null} */ (null);
4892
+ /** the configuration tier, read once at the upgrade and kept for the element's life */
4893
+ #frozen = /** @type {{ [k: string]: any }} */ ({});
4659
4894
  #bits() {
4660
4895
  return /** @type {typeof ParsedElement} */ (this.constructor).BITS;
4661
4896
  }
4897
+ #mapper(attr) {
4898
+ const mapper = this.#bits().ATTR_TO_MAPPER[attr];
4899
+ if (!mapper) {
4900
+ //an attribute the class never declared has no type to read it as:
4901
+ //say so rather than dying on the missing mapper. Content this
4902
+ //element does not own, such as a custom loader's own configuration,
4903
+ //is read with getAttribute, the platform's own answer
4904
+ throw new Error(
4905
+ `${this.constructor.name} declares no attribute '${attr}': declare it in static observed or static attributes, or read it with getAttribute`,
4906
+ );
4907
+ }
4908
+ return mapper;
4909
+ }
4662
4910
  unmarshal(attr, str) {
4663
- return this.#bits().ATTR_TO_MAPPER[attr].unmarshal(str, attr, this);
4911
+ return this.#mapper(attr).unmarshal(str, attr, this);
4664
4912
  }
4665
4913
  marshal(attr, value) {
4666
- return this.#bits().ATTR_TO_MAPPER[attr].marshal(value, attr, this);
4914
+ return this.#mapper(attr).marshal(value, attr, this);
4667
4915
  }
4668
4916
  /**
4669
4917
  * @param {string} [name] - The name of the template target, defaults to 'default'
4670
4918
  */
4919
+ /** The registry that defined this element, the page's own for an undefined one. */
4920
+ get _registry() {
4921
+ return this.#bits().registry;
4922
+ }
4923
+ /**
4924
+ * The component registered under the name on this element's registry, the
4925
+ * one every ful loader and mapper resolves through.
4926
+ * @param {string} name
4927
+ */
4928
+ component(name) {
4929
+ return this.#bits().registry.component(name);
4930
+ }
4671
4931
  template(name) {
4672
- const { modules, data } = registry.context();
4673
- let t = this.#bits().TEMPLATES[name ?? 'default'].withData(data).withModules(modules);
4674
- for (const k of ['l10n', 'config']) {
4675
- const v = this.constructor[k];
4676
- if (v) {
4677
- t = t.withOverlay({ [k]: v });
4678
- }
4932
+ const target = this.#bits().TEMPLATES[name ?? 'default'];
4933
+ if (!target) {
4934
+ throw new Error(`no template named '${name ?? 'default'}' on ${this.constructor.name}`);
4679
4935
  }
4680
- return t;
4936
+ const t = target.withEvaluator(this.#bits().registry.evaluator());
4937
+ const config = this.#bits().CONFIG;
4938
+ return config ? t.withOverlay({ config }) : t;
4681
4939
  }
4682
4940
  connectedCallback() {
4683
- if (this.#parsed) {
4941
+ if (this.#started) {
4684
4942
  return;
4685
4943
  }
4686
4944
  this.#bits().enqueue(this);
4687
4945
  }
4688
4946
  attributeChangedCallback(attr, oldValue, newValue) {
4689
- if (!this.#parsed || oldValue === newValue) {
4947
+ if (oldValue === newValue) {
4948
+ return;
4949
+ }
4950
+ //the mute is the attribute being reflected, not the element: a component
4951
+ //that legitimately changes another observed attribute while one reflects
4952
+ //is a change like any other, and used to be swallowed
4953
+ if (this.#reflecting.has(attr)) {
4690
4954
  return;
4691
4955
  }
4692
- if (this.#reflecting > 0) {
4956
+ //the properties go live only once the render is done: before that, an
4957
+ //attribute write lands in the observed snapshot and the base applies it
4958
+ //with the rest of the declared state
4959
+ if (!this.#parsed) {
4960
+ if (this.#pending !== null && attr in this.#pending) {
4961
+ this.#pending[attr] = this.unmarshal(attr, newValue);
4962
+ }
4693
4963
  return;
4694
4964
  }
4695
- this[attr] = this.unmarshal(attr, newValue);
4965
+ this[this.#bits().ATTR_TO_PROPERTY[attr]] = this.unmarshal(attr, newValue);
4696
4966
  }
4697
4967
  /**
4698
- * The disabled protocol follows the semantics of a native form control:
4699
- *
4700
- * - the `disabled` attribute on the host is the field's own claim, and nothing
4701
- * but its author ever writes or removes it, in markup or through the property.
4702
- * The framework never claims on the form's behalf, so there is nothing to
4703
- * unclaim and nothing to lose: a field declared disabled inside a disabled
4704
- * `<fieldset>` stays disabled when the fieldset comes back, exactly like a
4705
- * native input keeps its attribute.
4706
- * - the effective state is the claim OR a disabled fieldset ancestry, which the
4707
- * platform maintains on its own: `:disabled` matches both, a disabled field is
4708
- * left out of the submitted values, and the inner native controls are reached
4709
- * by the ancestry as descendants of the fieldset.
4710
- * - the `disabled` property reflects the claim only, like a native input's: a
4711
- * field disabled by its ancestry reads `false` while `matches(':disabled')`
4712
- * tells the effective state. Un-claiming inside a disabled fieldset cannot
4713
- * enable the field.
4714
- * - the inner controls mirror the claim and nothing else: the ancestry state is
4715
- * never written anywhere, so it can never go stale, and the browser composes
4716
- * the two on its own when it disables and re-enables a fieldset's descendants.
4968
+ * Upgrades once: reads the declared attributes, keeps the observed half open
4969
+ * to writes made while the render is pending, applies them to the properties
4970
+ * when the render returns, and only then lets an attribute write forward.
4717
4971
  *
4718
- * Because of this, formDisabledCallback carries nothing the framework needs to
4719
- * apply, and the protocol does not define it.
4972
+ * An observed attribute drives the property the registry's `propertyOf`
4973
+ * names, so a hyphenated attribute is authored with its dashes and read as
4974
+ * a camelCase property. A single-word attribute is its own property name,
4975
+ * which is what every observed attribute in the library is.
4720
4976
  */
4721
4977
  async upgrade() {
4722
- if (this.#parsed) {
4978
+ if (this.#started) {
4723
4979
  return;
4724
4980
  }
4725
- this.#parsed = true;
4981
+ this.#started = true;
4726
4982
  const slots = this.#bits().SLOTS ? LightSlots.from(this) : undefined;
4727
- const observed = Object.fromEntries(
4728
- this.#bits().OBSERVED.map((attribute) => [
4983
+ //both tiers are read here: the observed ones forward to a property once
4984
+ //the render is done, the rest are the element's configuration, read the
4985
+ //one time and never again
4986
+ const declared = Object.fromEntries(
4987
+ this.#bits().DECLARED.map((attribute) => [
4729
4988
  attribute,
4730
4989
  this.unmarshal(attribute, this.getAttribute(attribute)),
4731
4990
  ]),
4732
4991
  );
4733
- //the declared claim is what render receives: the ancestry state is not
4734
- //passed around, it is already where it needs to be
4735
- await this.render({ slots, observed, disabled: this.hasAttribute('disabled') });
4736
- }
4737
- render(c) {}
4738
- reflect(fn) {
4739
- ++this.#reflecting;
4992
+ const observedNames = new Set(this.#bits().OBSERVED);
4993
+ this.#frozen = Object.fromEntries(Object.entries(declared).filter(([name]) => !observedNames.has(name)));
4994
+ this.#pending = declared;
4740
4995
  try {
4741
- fn();
4996
+ await this.render({ slots });
4997
+ //the declared state reaches the properties once the dom the setters
4998
+ //drive exists, in the order the registry composed the declarations:
4999
+ //a base class's attributes before the subclass's own
5000
+ const properties = this.#bits().ATTR_TO_PROPERTY;
5001
+ for (const name of this.#bits().OBSERVED) {
5002
+ this[properties[name]] = declared[name];
5003
+ }
5004
+ //the properties go live once the render is done: from here on, an
5005
+ //attribute write forwards to the property. A render that threw
5006
+ //leaves it shut, so a later attribute write cannot reach setters
5007
+ //that assume pieces the failed render never adopted
5008
+ this.#parsed = true;
5009
+ //the same moment, said to css. :defined is true from the constructor,
5010
+ //which is before the dom exists, so a guard written against it reveals
5011
+ //an element that has nothing in it yet
5012
+ this.internals.states.add('rendered');
4742
5013
  } finally {
4743
- --this.#reflecting;
5014
+ this.#pending = null;
4744
5015
  }
4745
5016
  }
5017
+ /**
5018
+ * Renders the element from its slots alone: it builds the dom its setters
5019
+ * drive, and the base applies the declared state onto the properties as
5020
+ * soon as it returns. A render needing a declared value while it builds
5021
+ * reads it through `declared(name)`. The slots are undefined for an element
5022
+ * declaring no slots.
5023
+ * @param {{ slots: any }} c
5024
+ */
5025
+ render(c) {}
5026
+ /**
5027
+ * The declared value of an attribute, unmarshalled through its mapper.
5028
+ *
5029
+ * A `static attributes` name is the configuration tier: read once when the
5030
+ * upgrade starts and answered unchanged for the element's life, so a later
5031
+ * attribute write does not quietly change how the element behaves. An
5032
+ * observed name answers the snapshot while the render is pending, kept
5033
+ * open to attribute writes landing in that window, and the live attribute
5034
+ * afterwards, the property being live by then.
5035
+ *
5036
+ * The snapshot exists rather than a read of the dom because an element may
5037
+ * write its own observed attributes while it renders, whether a reflection
5038
+ * or a value the platform normalizes on the way in, and what the author
5039
+ * declared is what the base applies, not what the render left behind.
5040
+ * @param {string} name
5041
+ */
5042
+ declared(name) {
5043
+ if (name in this.#frozen) {
5044
+ return this.#frozen[name];
5045
+ }
5046
+ if (this.#pending !== null && name in this.#pending) {
5047
+ return this.#pending[name];
5048
+ }
5049
+ return this.unmarshal(name, this.getAttribute(name));
5050
+ }
5051
+ /** Whether the element's render completed: the moment its properties went live. */
5052
+ get rendered() {
5053
+ return this.#parsed;
5054
+ }
5055
+ /**
5056
+ * Projects a property back onto its observed attribute, marshalled through
5057
+ * the mapper the attribute was declared with: the one place a property
5058
+ * reaches its attribute, so a setter never has to know how its own type
5059
+ * serializes.
5060
+ *
5061
+ * A value the attribute already carries is not written at all, so reflecting
5062
+ * what an attribute write just delivered ends there rather than looping, and
5063
+ * only the attribute being written is muted while it happens.
5064
+ * @param {string} attr
5065
+ * @param {any} value
5066
+ */
4746
5067
  reflectTo(attr, value) {
4747
- ++this.#reflecting;
5068
+ const marshalled = this.marshal(attr, value);
5069
+ if (marshalled === this.getAttribute(attr)) {
5070
+ return;
5071
+ }
5072
+ this.#reflecting.add(attr);
4748
5073
  try {
4749
- Attributes.set(this, attr, this.marshal(attr, value));
5074
+ Attributes.set(this, attr, marshalled);
4750
5075
  } finally {
4751
- --this.#reflecting;
5076
+ this.#reflecting.delete(attr);
4752
5077
  }
4753
5078
  }
4754
5079
  }
4755
5080
 
4756
- export { Attributes, ExpressionEvaluator, Expressions, Fragments, LightSlots, Nodes, ParsedElement, Registry, RenderError, Rendering, Template, Templates, registry };
5081
+ /**
5082
+ * A flat translations map: dotted keys pointing to a message, or to a plural
5083
+ * leaf carrying a CLDR plural category for each of its forms ('other' is
5084
+ * required, the missing categories of a language fall back to it).
5085
+ *
5086
+ * @typedef {Record<string, string | Record<string, string>>} Messages
5087
+ */
5088
+
5089
+ /**
5090
+ * The receiver contract of the module functions: bound to a proxy over the
5091
+ * data stack when called from a template, or to a plain object by Localization.of().
5092
+ *
5093
+ * @typedef {{ l10n?: Messages, locale?: string }} Receiver
5094
+ */
5095
+
5096
+ const PLACEHOLDER = /\{(\w+)\}/g;
5097
+ const POSITIONAL = /\{(\d+)\}/g;
5098
+ /** @param {any} v @returns {v is Record<string, string>} */
5099
+ const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
5100
+
5101
+ /** every l10n problem is reported once per session: a missing key in a row template must not flood the console */
5102
+ const warned = new Set();
5103
+ const warnOnce = (problem) => {
5104
+ if (!warned.has(problem)) {
5105
+ warned.add(problem);
5106
+ console.warn(`l10n: ${problem}`);
5107
+ }
5108
+ };
5109
+
5110
+ const formatters = new BoundedCache(100);
5111
+ /**
5112
+ * Intl construction is the expensive part of formatting (locale parsing, CLDR
5113
+ * lookups), while formatting on a built instance is near free: a list rendering
5114
+ * a size per row must not rebuild a NumberFormat per cell, so instances are
5115
+ * memoized by constructor, locale and options.
5116
+ *
5117
+ * @param {{ new (locale: string | undefined, options: any): any }} ctor
5118
+ * @param {string | undefined} locale
5119
+ * @param {any} [options]
5120
+ */
5121
+ const formatter = (ctor, locale, options) => {
5122
+ const key = `${ctor.name}|${locale ?? ''}|${options === undefined ? '' : JSON.stringify(options)}`;
5123
+ return formatters.getOrCompute(key, () => new ctor(locale, options));
5124
+ };
5125
+
5126
+ /**
5127
+ * The translations as a template module: `#l10n:t()` for messages, plus date,
5128
+ * number and bytes formatting in the page's locale. The template form and the
5129
+ * imperative `of()` facade both resolve through the registry's scope, so they
5130
+ * always produce the same result for the same key.
5131
+ */
5132
+ class Localization {
5133
+ /**
5134
+ * Resolves a message from the translations and interpolates its arguments.
5135
+ * A single plain object argument interpolates named placeholders
5136
+ * ({name}); anything else interpolates positional ones ({0}).
5137
+ * A plural leaf selects its form through Intl.PluralRules over the
5138
+ * numeric {count} named argument.
5139
+ *
5140
+ * @param {string} key
5141
+ * @param {...any} args
5142
+ * @this {Receiver}
5143
+ * @returns {string} the message, or the key itself when the translations do not carry it
5144
+ */
5145
+ static t(key, ...args) {
5146
+ const messages = this.l10n ?? {};
5147
+ let message = messages[key];
5148
+ if (message === undefined) {
5149
+ warnOnce(`missing message "${key}"`);
5150
+ return key;
5151
+ }
5152
+ if (isPlainObject(message)) {
5153
+ const named = args.length === 1 && isPlainObject(args[0]) ? args[0] : {};
5154
+ if (typeof named.count !== 'number') {
5155
+ warnOnce(`plural message "${key}" needs a numeric {count}`);
5156
+ message = message.other;
5157
+ } else {
5158
+ const locale = this.locale ?? navigator?.language ?? 'en';
5159
+ const category = formatter(Intl.PluralRules, locale).select(named.count);
5160
+ message = message[category] ?? message.other;
5161
+ }
5162
+ }
5163
+ if (message === undefined || typeof message === 'object') {
5164
+ warnOnce(`plural message "${key}" has no "other" form`);
5165
+ return key;
5166
+ }
5167
+ if (args.length === 1 && isPlainObject(args[0])) {
5168
+ const named = args[0];
5169
+ return message.replace(PLACEHOLDER, (literal, name) => {
5170
+ if (!(name in named)) {
5171
+ warnOnce(`message "${key}" wants {${name}}`);
5172
+ return literal;
5173
+ }
5174
+ return String(named[name]);
5175
+ });
5176
+ }
5177
+ if (args.length > 0) {
5178
+ return message.replace(POSITIONAL, (literal, index) => {
5179
+ const i = Number(index);
5180
+ if (i >= args.length) {
5181
+ warnOnce(`message "${key}" wants {${index}}`);
5182
+ return literal;
5183
+ }
5184
+ return String(args[i]);
5185
+ });
5186
+ }
5187
+ return message;
5188
+ }
5189
+
5190
+ /**
5191
+ * Formats a date through Intl.DateTimeFormat in the receiver's locale.
5192
+ *
5193
+ * @param {Date | number} value
5194
+ * @param {Intl.DateTimeFormatOptions} [options]
5195
+ * @this {Receiver}
5196
+ */
5197
+ static date(value, options) {
5198
+ return formatter(Intl.DateTimeFormat, this.locale, options).format(value);
5199
+ }
5200
+
5201
+ /**
5202
+ * Formats a number through Intl.NumberFormat in the receiver's locale.
5203
+ *
5204
+ * @param {number} value
5205
+ * @param {Intl.NumberFormatOptions} [options]
5206
+ * @this {Receiver}
5207
+ */
5208
+ static number(value, options) {
5209
+ return formatter(Intl.NumberFormat, this.locale, options).format(value);
5210
+ }
5211
+
5212
+ /**
5213
+ * Formats a byte size with binary thresholds and literal unit suffixes:
5214
+ * the digits honor the locale, the units are the near-universal KiB/MiB/GiB.
5215
+ * A size exactly on a threshold takes the larger unit: 1024 is 1KiB.
5216
+ *
5217
+ * @param {number} value
5218
+ * @this {Receiver}
5219
+ */
5220
+ static bytes(value) {
5221
+ const format = formatter(Intl.NumberFormat, this.locale, { maximumFractionDigits: 2 }).format;
5222
+ if (value >= 1024 * 1024 * 1024) {
5223
+ return `${format(value / 1024 / 1024 / 1024)}GiB`;
5224
+ }
5225
+ if (value >= 1024 * 1024) {
5226
+ return `${format(value / 1024 / 1024)}MiB`;
5227
+ }
5228
+ if (value >= 1024) {
5229
+ return `${format(value / 1024)}KiB`;
5230
+ }
5231
+ return `${format(value)}B`;
5232
+ }
5233
+
5234
+ /**
5235
+ * An imperative facade over the module functions, resolving the translations
5236
+ * and the locale from the registry overlays on every call.
5237
+ *
5238
+ * @param {{ locale?: string }} [overrides] an explicit locale, winning over the registry one
5239
+ */
5240
+ static of(overrides = {}) {
5241
+ //resolved per call, not per facade: a module-scope `Localization.of()` is
5242
+ //bound before the plugin configures. The registry's own evaluator does the
5243
+ //lookup, so this cannot drift from what a template sees
5244
+ const resolve = (prop) => registry.evaluator().resolve(prop);
5245
+ /** @param {any} fn */
5246
+ const bind =
5247
+ (fn) =>
5248
+ (...args) =>
5249
+ fn.apply({ l10n: resolve('l10n'), locale: overrides.locale ?? resolve('locale') }, args);
5250
+ return {
5251
+ t: bind(Localization.t),
5252
+ date: bind(Localization.date),
5253
+ number: bind(Localization.number),
5254
+ bytes: bind(Localization.bytes),
5255
+ };
5256
+ }
5257
+ }
5258
+
5259
+ export { Attributes, BoundedCache, ExpressionEvaluator, Expressions, Fragments, LightSlots, Localization, Nodes, ParsedElement, Registry, RenderError, Rendering, Template, Templates, registry };
4757
5260
  //# sourceMappingURL=ftl.mjs.map