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