@optionfactory/fml 8.0.2 → 9.0.0-rc1

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