@rohal12/spindle 0.56.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,39 @@
1
- //#region src/markup/spindle.peggy
2
- var LB = String.fromCharCode(123);
3
- var RB = String.fromCharCode(125);
4
- var SIGIL_SCOPES$1 = {
1
+ //#region src/markup/tokens.ts
2
+ /** Variable sigils: story ($), temporary (_), local (@), transient (%). */
3
+ var SIGIL_SCOPES = {
5
4
  $: "variable",
6
5
  _: "temporary",
7
6
  "@": "local",
8
7
  "%": "transient"
9
8
  };
9
+ Object.fromEntries(Object.entries(SIGIL_SCOPES).map(([sigil, scope]) => [scope, sigil]));
10
+ var SIGIL_CHARS = new Set(Object.keys(SIGIL_SCOPES));
11
+ /** Whether `c` is a variable sigil. */
12
+ function isSigil(c) {
13
+ return c !== void 0 && SIGIL_CHARS.has(c);
14
+ }
15
+ /** Copy the selectors set in `from` onto `target`, and return it. */
16
+ function withSelectors$1(target, from) {
17
+ if (from.className) target.className = from.className;
18
+ if (from.id) target.id = from.id;
19
+ return target;
20
+ }
21
+ //#endregion
22
+ //#region src/markup/grammar-shared.ts
23
+ /**
24
+ * What the grammar (spindle.peggy) imports from the rest of Spindle, so that
25
+ * it holds no second copy: the generated parser imports this module's
26
+ * default export as `shared` (see scripts/peggy.ts).
27
+ */
28
+ var grammar_shared_default = {
29
+ SIGIL_SCOPES,
30
+ withSelectors: withSelectors$1
31
+ };
32
+ //#endregion
33
+ //#region src/markup/spindle.peggy
34
+ var LB = String.fromCharCode(123);
35
+ var RB = String.fromCharCode(125);
36
+ var { SIGIL_SCOPES: SIGIL_SCOPES$1, withSelectors } = grammar_shared_default;
10
37
  var VOID_TAGS = new Set([
11
38
  "area",
12
39
  "base",
@@ -64,11 +91,6 @@ function parseLink(inner) {
64
91
  function quoteArg(value) {
65
92
  return "\"" + value.replace(/[\\"]/g, "\\$&") + "\"";
66
93
  }
67
- function withSelectors(target, from) {
68
- if (from.className) target.className = from.className;
69
- if (from.id) target.id = from.id;
70
- return target;
71
- }
72
94
  function selectorsOf(parts) {
73
95
  const classes = [];
74
96
  let id = "";
@@ -288,7 +310,8 @@ function peg$parse(input, options) {
288
310
  const peg$source = options.grammarSource;
289
311
  const peg$startRuleFunctions = {
290
312
  Markup: peg$parseMarkup,
291
- Tokens: peg$parseTokens
313
+ Tokens: peg$parseTokens,
314
+ SelectorsPrefix: peg$parseSelectorsPrefix
292
315
  };
293
316
  let peg$startRuleFunction = peg$parseMarkup;
294
317
  const peg$c0 = "\\";
@@ -690,40 +713,46 @@ function peg$parse(input, options) {
690
713
  function peg$f53() {
691
714
  return NO_SELECTORS;
692
715
  }
693
- function peg$f54(prefix, name) {
716
+ function peg$f54(s, end) {
717
+ return {
718
+ ...s,
719
+ end
720
+ };
721
+ }
722
+ function peg$f55(prefix, name) {
694
723
  return [prefix, name];
695
724
  }
696
- function peg$f55(s, sigil, name) {
725
+ function peg$f56(s, sigil, name) {
697
726
  return withSelectors(lexeme("variable", location(), {
698
727
  name,
699
728
  scope: SIGIL_SCOPES$1[sigil]
700
729
  }), s);
701
730
  }
702
- function peg$f56(s, expression) {
731
+ function peg$f57(s, expression) {
703
732
  return withSelectors(lexeme("expression", location(), { expression }), s);
704
733
  }
705
- function peg$f57() {
734
+ function peg$f58() {
706
735
  fail("Unclosed " + LB + input.substr(location().end.offset, 1) + "…: no } ends it", location());
707
736
  }
708
- function peg$f58(start) {
737
+ function peg$f59(start) {
709
738
  return skipTo(hooks.closeBrace(input, start, start));
710
739
  }
711
- function peg$f59(start) {
740
+ function peg$f60(start) {
712
741
  return skipEnd - offset();
713
742
  }
714
- function peg$f60(start, code) {
743
+ function peg$f61(start, code) {
715
744
  return code;
716
745
  }
717
- function peg$f61(s, start, close, name, nameEnd) {
746
+ function peg$f62(s, start, close, name, nameEnd) {
718
747
  return skipTo(hooks.closeBrace(input, nameEnd, start));
719
748
  }
720
- function peg$f62(s, start, close, name, nameEnd) {
749
+ function peg$f63(s, start, close, name, nameEnd) {
721
750
  return skipEnd - offset();
722
751
  }
723
- function peg$f63(s, start, close, name, nameEnd, args) {
752
+ function peg$f64(s, start, close, name, nameEnd, args) {
724
753
  return args;
725
754
  }
726
- function peg$f64(s, start, close, name, nameEnd, tail) {
755
+ function peg$f65(s, start, close, name, nameEnd, tail) {
727
756
  const loc = location();
728
757
  const lower = name.toLowerCase();
729
758
  const tag = lexeme("macro", loc, {
@@ -744,19 +773,19 @@ function peg$parse(input, options) {
744
773
  }
745
774
  return tag;
746
775
  }
747
- function peg$f65(s) {
776
+ function peg$f66(s) {
748
777
  fail("A closing tag starts with a letter after " + LB + "/", location());
749
778
  }
750
- function peg$f66() {
779
+ function peg$f67() {
751
780
  return !textMode;
752
781
  }
753
- function peg$f67(tag) {
782
+ function peg$f68(tag) {
754
783
  return VOID_TAGS.has(tag.lower);
755
784
  }
756
- function peg$f68(tag) {
785
+ function peg$f69(tag) {
757
786
  return null;
758
787
  }
759
- function peg$f69(tag, list, slash) {
788
+ function peg$f70(tag, list, slash) {
760
789
  const lower = tag.toLowerCase();
761
790
  return lexeme("html", location(), {
762
791
  tag,
@@ -766,7 +795,7 @@ function peg$parse(input, options) {
766
795
  isSelfClose: !!slash || VOID_TAGS.has(lower)
767
796
  });
768
797
  }
769
- function peg$f70(tag, bad) {
798
+ function peg$f71(tag, bad) {
770
799
  const c = input.charAt(bad);
771
800
  if (!c) fail("Unclosed tag <" + tag + ": no > ends it (write &lt; for a < that starts no tag)", location());
772
801
  const at = {
@@ -784,7 +813,7 @@ function peg$parse(input, options) {
784
813
  };
785
814
  fail("Unexpected " + JSON.stringify(c) + " in the tag <" + tag + ">: expected an attribute, or > to end the tag", at);
786
815
  }
787
- function peg$f71(tag) {
816
+ function peg$f72(tag) {
788
817
  return lexeme("html", location(), {
789
818
  tag,
790
819
  lower: tag.toLowerCase(),
@@ -793,44 +822,44 @@ function peg$parse(input, options) {
793
822
  isSelfClose: false
794
823
  });
795
824
  }
796
- function peg$f72(name) {
825
+ function peg$f73(name) {
797
826
  attrCode = hooks.isCodeAttribute(name);
798
827
  return true;
799
828
  }
800
- function peg$f73(name, v) {
829
+ function peg$f74(name, v) {
801
830
  return v;
802
831
  }
803
- function peg$f74(name, value) {
832
+ function peg$f75(name, value) {
804
833
  return [name, value ?? ""];
805
834
  }
806
- function peg$f75(v) {
835
+ function peg$f76(v) {
807
836
  return v;
808
837
  }
809
- function peg$f76(v) {
838
+ function peg$f77(v) {
810
839
  return v;
811
840
  }
812
- function peg$f77(q) {
841
+ function peg$f78(q) {
813
842
  fail("Unclosed attribute value: the " + q + " here has no closing " + q, location());
814
843
  }
815
- function peg$f78() {
844
+ function peg$f79() {
816
845
  return !attrCode;
817
846
  }
818
- function peg$f79(bs) {
847
+ function peg$f80(bs) {
819
848
  return bs.length % 2 === 1;
820
849
  }
821
- function peg$f80() {
850
+ function peg$f81() {
822
851
  return !attrCode;
823
852
  }
824
- function peg$f81(start, nameEnd) {
853
+ function peg$f82(start, nameEnd) {
825
854
  return skipTo(hooks.closeBrace(input, nameEnd, start));
826
855
  }
827
- function peg$f82(start, nameEnd) {
856
+ function peg$f83(start, nameEnd) {
828
857
  return skipEnd - offset();
829
858
  }
830
- function peg$f83() {
859
+ function peg$f84() {
831
860
  return !attrCode;
832
861
  }
833
- function peg$f84() {
862
+ function peg$f85() {
834
863
  return attrCode;
835
864
  }
836
865
  let peg$currPos = options.peg$currPos | 0;
@@ -1919,6 +1948,38 @@ function peg$parse(input, options) {
1919
1948
  }
1920
1949
  return s0;
1921
1950
  }
1951
+ function peg$parseSelectorsPrefix() {
1952
+ let s0, s1, s2, s3, s4;
1953
+ s0 = peg$currPos;
1954
+ s1 = peg$parseSelectors();
1955
+ if (s1 !== peg$FAILED) {
1956
+ s2 = peg$parseHere();
1957
+ s3 = [];
1958
+ if (input.length > peg$currPos) {
1959
+ s4 = input.charAt(peg$currPos);
1960
+ peg$currPos++;
1961
+ } else {
1962
+ s4 = peg$FAILED;
1963
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1964
+ }
1965
+ while (s4 !== peg$FAILED) {
1966
+ s3.push(s4);
1967
+ if (input.length > peg$currPos) {
1968
+ s4 = input.charAt(peg$currPos);
1969
+ peg$currPos++;
1970
+ } else {
1971
+ s4 = peg$FAILED;
1972
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1973
+ }
1974
+ }
1975
+ peg$savedPos = s0;
1976
+ s0 = peg$f54(s1, s2);
1977
+ } else {
1978
+ peg$currPos = s0;
1979
+ s0 = peg$FAILED;
1980
+ }
1981
+ return s0;
1982
+ }
1922
1983
  function peg$parseSelectorPart() {
1923
1984
  let s0, s1, s2, s3, s4;
1924
1985
  s0 = peg$currPos;
@@ -1938,7 +1999,7 @@ function peg$parse(input, options) {
1938
1999
  }
1939
2000
  s2 = input.substring(s2, peg$currPos);
1940
2001
  peg$savedPos = s0;
1941
- s0 = peg$f54(s1, s2);
2002
+ s0 = peg$f55(s1, s2);
1942
2003
  } else {
1943
2004
  peg$currPos = s0;
1944
2005
  s0 = peg$FAILED;
@@ -2063,7 +2124,7 @@ function peg$parse(input, options) {
2063
2124
  }
2064
2125
  if (s5 !== peg$FAILED) {
2065
2126
  peg$savedPos = s0;
2066
- s0 = peg$f55(s2, s3, s4);
2127
+ s0 = peg$f56(s2, s3, s4);
2067
2128
  } else {
2068
2129
  peg$currPos = s0;
2069
2130
  s0 = peg$FAILED;
@@ -2117,7 +2178,7 @@ function peg$parse(input, options) {
2117
2178
  }
2118
2179
  if (s5 !== peg$FAILED) {
2119
2180
  peg$savedPos = s0;
2120
- s0 = peg$f56(s2, s4);
2181
+ s0 = peg$f57(s2, s4);
2121
2182
  } else {
2122
2183
  peg$currPos = s0;
2123
2184
  s0 = peg$FAILED;
@@ -2165,7 +2226,7 @@ function peg$parse(input, options) {
2165
2226
  } else s3 = peg$FAILED;
2166
2227
  if (s3 !== peg$FAILED) {
2167
2228
  peg$savedPos = s0;
2168
- s0 = peg$f57();
2229
+ s0 = peg$f58();
2169
2230
  } else {
2170
2231
  peg$currPos = s0;
2171
2232
  s0 = peg$FAILED;
@@ -2187,12 +2248,12 @@ function peg$parse(input, options) {
2187
2248
  s0 = peg$currPos;
2188
2249
  s1 = peg$parseHere();
2189
2250
  peg$savedPos = peg$currPos;
2190
- s2 = peg$f58(s1);
2251
+ s2 = peg$f59(s1);
2191
2252
  if (s2) s2 = void 0;
2192
2253
  else s2 = peg$FAILED;
2193
2254
  if (s2 !== peg$FAILED) {
2194
2255
  s3 = peg$currPos;
2195
- s4 = peg$f59(s1);
2256
+ s4 = peg$f60(s1);
2196
2257
  s5 = peg$currPos;
2197
2258
  s6 = [];
2198
2259
  if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
@@ -2223,7 +2284,7 @@ function peg$parse(input, options) {
2223
2284
  else s3 = s4;
2224
2285
  if (s3 !== peg$FAILED) {
2225
2286
  peg$savedPos = s0;
2226
- s0 = peg$f60(s1, s3);
2287
+ s0 = peg$f61(s1, s3);
2227
2288
  } else {
2228
2289
  peg$currPos = s0;
2229
2290
  s0 = peg$FAILED;
@@ -2261,12 +2322,12 @@ function peg$parse(input, options) {
2261
2322
  s6 = peg$parseHere();
2262
2323
  s7 = peg$currPos;
2263
2324
  peg$savedPos = peg$currPos;
2264
- s8 = peg$f61(s2, s3, s4, s5, s6);
2325
+ s8 = peg$f62(s2, s3, s4, s5, s6);
2265
2326
  if (s8) s8 = void 0;
2266
2327
  else s8 = peg$FAILED;
2267
2328
  if (s8 !== peg$FAILED) {
2268
2329
  s9 = peg$currPos;
2269
- s10 = peg$f62(s2, s3, s4, s5, s6);
2330
+ s10 = peg$f63(s2, s3, s4, s5, s6);
2270
2331
  s11 = peg$currPos;
2271
2332
  s12 = [];
2272
2333
  if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
@@ -2305,7 +2366,7 @@ function peg$parse(input, options) {
2305
2366
  }
2306
2367
  if (s10 !== peg$FAILED) {
2307
2368
  peg$savedPos = s7;
2308
- s7 = peg$f63(s2, s3, s4, s5, s6, s9);
2369
+ s7 = peg$f64(s2, s3, s4, s5, s6, s9);
2309
2370
  } else {
2310
2371
  peg$currPos = s7;
2311
2372
  s7 = peg$FAILED;
@@ -2320,7 +2381,7 @@ function peg$parse(input, options) {
2320
2381
  }
2321
2382
  if (s7 === peg$FAILED) s7 = null;
2322
2383
  peg$savedPos = s0;
2323
- s0 = peg$f64(s2, s3, s4, s5, s6, s7);
2384
+ s0 = peg$f65(s2, s3, s4, s5, s6, s7);
2324
2385
  } else {
2325
2386
  peg$currPos = s0;
2326
2387
  s0 = peg$FAILED;
@@ -2354,7 +2415,7 @@ function peg$parse(input, options) {
2354
2415
  }
2355
2416
  if (s3 !== peg$FAILED) {
2356
2417
  peg$savedPos = s0;
2357
- s0 = peg$f65(s2);
2418
+ s0 = peg$f66(s2);
2358
2419
  } else {
2359
2420
  peg$currPos = s0;
2360
2421
  s0 = peg$FAILED;
@@ -2411,19 +2472,19 @@ function peg$parse(input, options) {
2411
2472
  let s0, s1, s2, s3;
2412
2473
  s0 = peg$currPos;
2413
2474
  peg$savedPos = peg$currPos;
2414
- s1 = peg$f66();
2475
+ s1 = peg$f67();
2415
2476
  if (s1) s1 = void 0;
2416
2477
  else s1 = peg$FAILED;
2417
2478
  if (s1 !== peg$FAILED) {
2418
2479
  s2 = peg$parseHtmlClose();
2419
2480
  if (s2 !== peg$FAILED) {
2420
2481
  peg$savedPos = peg$currPos;
2421
- s3 = peg$f67(s2);
2482
+ s3 = peg$f68(s2);
2422
2483
  if (s3) s3 = void 0;
2423
2484
  else s3 = peg$FAILED;
2424
2485
  if (s3 !== peg$FAILED) {
2425
2486
  peg$savedPos = s0;
2426
- s0 = peg$f68(s2);
2487
+ s0 = peg$f69(s2);
2427
2488
  } else {
2428
2489
  peg$currPos = s0;
2429
2490
  s0 = peg$FAILED;
@@ -2475,7 +2536,7 @@ function peg$parse(input, options) {
2475
2536
  }
2476
2537
  if (s6 !== peg$FAILED) {
2477
2538
  peg$savedPos = s0;
2478
- s0 = peg$f69(s2, s3, s5);
2539
+ s0 = peg$f70(s2, s3, s5);
2479
2540
  } else {
2480
2541
  peg$currPos = s0;
2481
2542
  s0 = peg$FAILED;
@@ -2540,7 +2601,7 @@ function peg$parse(input, options) {
2540
2601
  s5 = peg$parse_();
2541
2602
  s6 = peg$parseHere();
2542
2603
  peg$savedPos = s0;
2543
- s0 = peg$f70(s2, s6);
2604
+ s0 = peg$f71(s2, s6);
2544
2605
  } else {
2545
2606
  peg$currPos = s0;
2546
2607
  s0 = peg$FAILED;
@@ -2579,7 +2640,7 @@ function peg$parse(input, options) {
2579
2640
  }
2580
2641
  if (s4 !== peg$FAILED) {
2581
2642
  peg$savedPos = s0;
2582
- s0 = peg$f71(s2);
2643
+ s0 = peg$f72(s2);
2583
2644
  } else {
2584
2645
  peg$currPos = s0;
2585
2646
  s0 = peg$FAILED;
@@ -2713,7 +2774,7 @@ function peg$parse(input, options) {
2713
2774
  else s2 = s3;
2714
2775
  if (s2 !== peg$FAILED) {
2715
2776
  peg$savedPos = peg$currPos;
2716
- s3 = peg$f72(s2);
2777
+ s3 = peg$f73(s2);
2717
2778
  if (s3) s3 = void 0;
2718
2779
  else s3 = peg$FAILED;
2719
2780
  if (s3 !== peg$FAILED) {
@@ -2731,7 +2792,7 @@ function peg$parse(input, options) {
2731
2792
  s8 = peg$parseAttrValue();
2732
2793
  if (s8 !== peg$FAILED) {
2733
2794
  peg$savedPos = s4;
2734
- s4 = peg$f73(s2, s8);
2795
+ s4 = peg$f74(s2, s8);
2735
2796
  } else {
2736
2797
  peg$currPos = s4;
2737
2798
  s4 = peg$FAILED;
@@ -2742,7 +2803,7 @@ function peg$parse(input, options) {
2742
2803
  }
2743
2804
  if (s4 === peg$FAILED) s4 = null;
2744
2805
  peg$savedPos = s0;
2745
- s0 = peg$f74(s2, s4);
2806
+ s0 = peg$f75(s2, s4);
2746
2807
  } else {
2747
2808
  peg$currPos = s0;
2748
2809
  s0 = peg$FAILED;
@@ -2797,7 +2858,7 @@ function peg$parse(input, options) {
2797
2858
  }
2798
2859
  if (s3 !== peg$FAILED) {
2799
2860
  peg$savedPos = s0;
2800
- s0 = peg$f75(s2);
2861
+ s0 = peg$f76(s2);
2801
2862
  } else {
2802
2863
  peg$currPos = s0;
2803
2864
  s0 = peg$FAILED;
@@ -2849,7 +2910,7 @@ function peg$parse(input, options) {
2849
2910
  }
2850
2911
  if (s3 !== peg$FAILED) {
2851
2912
  peg$savedPos = s0;
2852
- s0 = peg$f76(s2);
2913
+ s0 = peg$f77(s2);
2853
2914
  } else {
2854
2915
  peg$currPos = s0;
2855
2916
  s0 = peg$FAILED;
@@ -2868,7 +2929,7 @@ function peg$parse(input, options) {
2868
2929
  }
2869
2930
  if (s1 !== peg$FAILED) {
2870
2931
  peg$savedPos = s0;
2871
- s1 = peg$f77(s1);
2932
+ s1 = peg$f78(s1);
2872
2933
  }
2873
2934
  s0 = s1;
2874
2935
  if (s0 === peg$FAILED) {
@@ -2899,7 +2960,7 @@ function peg$parse(input, options) {
2899
2960
  let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
2900
2961
  s0 = peg$currPos;
2901
2962
  peg$savedPos = peg$currPos;
2902
- s1 = peg$f78();
2963
+ s1 = peg$f79();
2903
2964
  if (s1) s1 = void 0;
2904
2965
  else s1 = peg$FAILED;
2905
2966
  if (s1 !== peg$FAILED) {
@@ -2928,7 +2989,7 @@ function peg$parse(input, options) {
2928
2989
  if (s2 !== peg$FAILED) {
2929
2990
  s3 = peg$currPos;
2930
2991
  peg$savedPos = peg$currPos;
2931
- s4 = peg$f79(s2);
2992
+ s4 = peg$f80(s2);
2932
2993
  if (s4) s4 = void 0;
2933
2994
  else s4 = peg$FAILED;
2934
2995
  if (s4 !== peg$FAILED) {
@@ -2967,7 +3028,7 @@ function peg$parse(input, options) {
2967
3028
  if (s0 === peg$FAILED) {
2968
3029
  s0 = peg$currPos;
2969
3030
  peg$savedPos = peg$currPos;
2970
- s1 = peg$f80();
3031
+ s1 = peg$f81();
2971
3032
  if (s1) s1 = void 0;
2972
3033
  else s1 = peg$FAILED;
2973
3034
  if (s1 !== peg$FAILED) {
@@ -2994,12 +3055,12 @@ function peg$parse(input, options) {
2994
3055
  if (s6 !== peg$FAILED) {
2995
3056
  s7 = peg$parseHere();
2996
3057
  peg$savedPos = peg$currPos;
2997
- s8 = peg$f81(s4, s7);
3058
+ s8 = peg$f82(s4, s7);
2998
3059
  if (s8) s8 = void 0;
2999
3060
  else s8 = peg$FAILED;
3000
3061
  if (s8 !== peg$FAILED) {
3001
3062
  s9 = peg$currPos;
3002
- s10 = peg$f82(s4, s7);
3063
+ s10 = peg$f83(s4, s7);
3003
3064
  s11 = peg$currPos;
3004
3065
  s12 = [];
3005
3066
  if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
@@ -3081,7 +3142,7 @@ function peg$parse(input, options) {
3081
3142
  if (s0 === peg$FAILED) {
3082
3143
  s0 = peg$currPos;
3083
3144
  peg$savedPos = peg$currPos;
3084
- s1 = peg$f83();
3145
+ s1 = peg$f84();
3085
3146
  if (s1) s1 = void 0;
3086
3147
  else s1 = peg$FAILED;
3087
3148
  if (s1 !== peg$FAILED) {
@@ -3172,7 +3233,7 @@ function peg$parse(input, options) {
3172
3233
  if (s0 === peg$FAILED) {
3173
3234
  s0 = peg$currPos;
3174
3235
  peg$savedPos = peg$currPos;
3175
- s1 = peg$f84();
3236
+ s1 = peg$f85();
3176
3237
  if (s1) s1 = void 0;
3177
3238
  else s1 = peg$FAILED;
3178
3239
  if (s1 !== peg$FAILED) {
@@ -3403,21 +3464,6 @@ function isBlockMacro(name) {
3403
3464
  return BLOCK_MACROS.has(name);
3404
3465
  }
3405
3466
  //#endregion
3406
- //#region src/markup/tokens.ts
3407
- /** Variable sigils: story ($), temporary (_), local (@), transient (%). */
3408
- var SIGIL_SCOPES = {
3409
- $: "variable",
3410
- _: "temporary",
3411
- "@": "local",
3412
- "%": "transient"
3413
- };
3414
- Object.fromEntries(Object.entries(SIGIL_SCOPES).map(([sigil, scope]) => [scope, sigil]));
3415
- var SIGIL_CHARS = new Set(Object.keys(SIGIL_SCOPES));
3416
- /** Whether `c` is a variable sigil. */
3417
- function isSigil(c) {
3418
- return c !== void 0 && SIGIL_CHARS.has(c);
3419
- }
3420
- //#endregion
3421
3467
  //#region node_modules/acorn/dist/acorn.mjs
3422
3468
  var astralIdentifierCodes = [
3423
3469
  509,
@@ -9239,6 +9285,14 @@ function* tokens(src, start, goal, options = {}) {
9239
9285
  function lexJs(src, handlers, goal = "expression") {
9240
9286
  return walkTokens(src, 0, handlers, 0, goal, false);
9241
9287
  }
9288
+ /**
9289
+ * Lex the template literal opening at `start` (a backtick) as `lexJs` does,
9290
+ * its interpolations one nesting level deeper. Returns the index just past
9291
+ * its closing backtick, or `src.length` if it is unterminated.
9292
+ */
9293
+ function lexTemplate(src, start, handlers = {}, nesting = 0) {
9294
+ return walkTokens(src, start, handlers, nesting, "expression", true);
9295
+ }
9242
9296
  function walkTokens(src, from, handlers, nesting, goal, oneTemplate) {
9243
9297
  const comments = [];
9244
9298
  const onComment = (_block, _text, s, e) => {
@@ -9591,6 +9645,80 @@ function parseMarkup(source, options = {}) {
9591
9645
  function tokenizeMarkup(source, options = {}) {
9592
9646
  return run(source, "Tokens", options);
9593
9647
  }
9648
+ /**
9649
+ * The `.class#id` selectors that start `source` at `at`, with the index just
9650
+ * past them and the one space that may follow (`end`; `at` if there are
9651
+ * none). A selector name may hold `{$name}` interpolations, as in markup.
9652
+ */
9653
+ function parseSelectors(source, at = 0) {
9654
+ const found = run(source.slice(at), "SelectorsPrefix", {});
9655
+ return {
9656
+ ...found,
9657
+ end: at + found.end
9658
+ };
9659
+ }
9660
+ /**
9661
+ * `tokenizeMarkup` for half-typed markup: the tokens it can read, and every
9662
+ * error, instead of throwing at the first. At a malformed tag, the tokens
9663
+ * before it are kept, its first character is read as text and tokenizing
9664
+ * resumes after it, so what follows is read as if the tag had not been
9665
+ * started. Tokens and errors have offsets in `source`; for well-formed
9666
+ * markup the tokens are those of `tokenizeMarkup` and there are no errors.
9667
+ */
9668
+ function tokenizeMarkupTolerant(source, options = {}) {
9669
+ const tokens = [];
9670
+ const errors = [];
9671
+ /** `text` shifted by `base`, tokenized: the tokens or the error. */
9672
+ const attempt = (text, base) => {
9673
+ try {
9674
+ return tokenizeMarkup(text, options).map((token) => shift(token, base));
9675
+ } catch (error) {
9676
+ if (!(error instanceof MarkupError)) throw error;
9677
+ return error;
9678
+ }
9679
+ };
9680
+ let base = 0;
9681
+ while (base <= source.length) {
9682
+ const rest = source.slice(base);
9683
+ const result = attempt(rest, base);
9684
+ if (Array.isArray(result)) {
9685
+ tokens.push(...result);
9686
+ break;
9687
+ }
9688
+ const at = base + result.offset;
9689
+ const { line, column } = lineColumn(source, at);
9690
+ errors.push(new MarkupError(result.reason, at, line, column));
9691
+ let cut = result.offset;
9692
+ let prefix = attempt(rest.slice(0, cut), base);
9693
+ while (!Array.isArray(prefix)) {
9694
+ cut = prefix.offset;
9695
+ prefix = attempt(rest.slice(0, cut), base);
9696
+ }
9697
+ tokens.push(...prefix);
9698
+ if (cut < rest.length) {
9699
+ const start = base + cut;
9700
+ tokens.push({
9701
+ type: "text",
9702
+ value: rest[cut],
9703
+ start,
9704
+ end: start + 1
9705
+ });
9706
+ }
9707
+ base += cut + 1;
9708
+ }
9709
+ return {
9710
+ tokens,
9711
+ errors
9712
+ };
9713
+ }
9714
+ /** `token` with its offsets moved `by` on. */
9715
+ function shift(token, by) {
9716
+ return by === 0 ? token : {
9717
+ ...token,
9718
+ start: token.start + by,
9719
+ end: token.end + by
9720
+ };
9721
+ }
9594
9722
  //#endregion
9595
9723
  //#region src/utils/parse-delay.ts
9596
9724
  /**
@@ -9618,6 +9746,15 @@ function errorMessage(err, fallback) {
9618
9746
  return fallback ?? String(err);
9619
9747
  }
9620
9748
  //#endregion
9749
+ //#region src/runtime-errors.ts
9750
+ /**
9751
+ * The error for `name`, a passage name that names no passage, used in the
9752
+ * passage `from`.
9753
+ */
9754
+ function noPassageError(name, from) {
9755
+ return /* @__PURE__ */ new Error(`No passage named ${JSON.stringify(name)} (in passage ${JSON.stringify(from)})`);
9756
+ }
9757
+ //#endregion
9621
9758
  //#region src/components/macros/arg-utils.ts
9622
9759
  /**
9623
9760
  * Shared helpers for macros that split or read their raw argument text by
@@ -9668,6 +9805,37 @@ function readWholeQuoted(src) {
9668
9805
  const quoted = readQuoted(src, 0);
9669
9806
  return quoted && quoted.end === src.length ? quoted.value : null;
9670
9807
  }
9808
+ var JS_SIMPLE_ESCAPES = {
9809
+ n: "\n",
9810
+ t: " ",
9811
+ r: "\r",
9812
+ b: "\b",
9813
+ f: "\f",
9814
+ v: "\v",
9815
+ "0": "\0"
9816
+ };
9817
+ /** Undo every escape of a JavaScript string literal's body. */
9818
+ function decodeJsEscapes(body) {
9819
+ return body.replace(/\\(?:u\{([0-9a-fA-F]+)\}|u([0-9a-fA-F]{4})|x([0-9a-fA-F]{2})|(\r\n|[\n\r\u2028\u2029])|([\s\S]))/g, (_, braced, unicode, hex, lineBreak, other) => {
9820
+ if (lineBreak !== void 0) return "";
9821
+ const code = braced ?? unicode ?? hex;
9822
+ if (code !== void 0) {
9823
+ const point = parseInt(code, 16);
9824
+ return point <= 1114111 ? String.fromCodePoint(point) : "";
9825
+ }
9826
+ return JS_SIMPLE_ESCAPES[other] ?? other;
9827
+ });
9828
+ }
9829
+ /**
9830
+ * The value of `src` when it is exactly one `"…"` or `'…'` string, read as
9831
+ * the JavaScript literal it is (`"\u0048all"` is `Hall`), otherwise `null`.
9832
+ * `readWholeQuoted` keeps other escapes as written, for labels and code.
9833
+ */
9834
+ function readWholeJsString(src) {
9835
+ const quoted = readQuoted(src, 0);
9836
+ if (!quoted || quoted.end !== src.length) return null;
9837
+ return decodeJsEscapes(src.slice(1, -1));
9838
+ }
9671
9839
  /**
9672
9840
  * Strip an optional quote from each end of an unquoted-or-loosely-quoted
9673
9841
  * argument (`"Red`, `Red'`), leaving at least one character. This is the
@@ -9720,6 +9888,67 @@ function topLevelIndices(src, isSeparator) {
9720
9888
  } });
9721
9889
  return indices;
9722
9890
  }
9891
+ /**
9892
+ * Split `src` at every depth-0 separator (see `topLevelIndices`). Segments
9893
+ * are returned as written, untrimmed, and empty segments are kept, so
9894
+ * `n` separators always give `n + 1` segments.
9895
+ */
9896
+ function splitTopLevel(src, isSeparator) {
9897
+ const segments = [];
9898
+ let from = 0;
9899
+ for (const i of topLevelIndices(src, isSeparator)) {
9900
+ segments.push(src.slice(from, i));
9901
+ from = i + 1;
9902
+ }
9903
+ segments.push(src.slice(from));
9904
+ return segments;
9905
+ }
9906
+ /**
9907
+ * Check whether a whitespace-delimited token looks like a standalone value
9908
+ * (not an operator or partial expression): it starts like a value and does
9909
+ * not end with an operator still waiting for its operand.
9910
+ */
9911
+ function isStandaloneValue(token) {
9912
+ return startsLikeValue(token) && !endsWithOperator(token);
9913
+ }
9914
+ function startsLikeValue(token) {
9915
+ const first = token[0];
9916
+ if (first === "\"" || first === "'" || first === "`") return true;
9917
+ if (/^[$_@]\w|^%[A-Za-z_]/.test(token)) return true;
9918
+ if (/\d/.test(first)) return true;
9919
+ if ((first === "-" || first === "+") && token.length > 1 && /\d/.test(token[1])) return true;
9920
+ if (first === "(" || first === "[" || first === "{") return true;
9921
+ if (/^(?:true|false|null|undefined)(?![\w$])/.test(token)) return true;
9922
+ if (first === "!" && token.length > 1 && token[1] !== "=") return true;
9923
+ return false;
9924
+ }
9925
+ /**
9926
+ * Try to split a raw string on whitespace at depth 0 (respecting strings,
9927
+ * template literals, parentheses, brackets, and braces). Each resulting token
9928
+ * must pass `isStandaloneValue()` or the split is rejected and `null` is
9929
+ * returned.
9930
+ */
9931
+ function trySplitOnWhitespace(raw) {
9932
+ const args = splitTopLevel(raw, isWhitespace).filter(Boolean);
9933
+ if (args.length < 2) return null;
9934
+ for (const arg of args) if (!isStandaloneValue(arg)) return null;
9935
+ return args;
9936
+ }
9937
+ /**
9938
+ * Split rawArgs by commas, respecting parentheses, brackets, braces, and
9939
+ * strings. When no top-level commas are present, also supports adjacent quoted
9940
+ * string literals separated by whitespace (e.g. `"Label" "target"`).
9941
+ */
9942
+ function splitArgs(raw) {
9943
+ const args = splitTopLevel(raw, (ch) => ch === ",").map((a) => a.trim());
9944
+ const hasComma = args.length > 1;
9945
+ if (args[args.length - 1] === "") args.pop();
9946
+ if (!hasComma && args.length === 1) {
9947
+ const split = trySplitOnWhitespace(args[0]);
9948
+ if (split) return split;
9949
+ }
9950
+ return args;
9951
+ }
9723
9952
  //#endregion
9724
9953
  //#region src/components/macros/macro-args.ts
9725
9954
  /**
@@ -9756,9 +9985,23 @@ var slice = (src, [start, end]) => src.slice(start, end);
9756
9985
  function readText(src) {
9757
9986
  return readWholeQuoted(src) ?? stripLooseQuotes(src);
9758
9987
  }
9988
+ /**
9989
+ * An argument that doesn't have its parameter's form: a `string` parameter
9990
+ * given anything but one quoted string.
9991
+ */
9992
+ var MacroArgumentError = class extends Error {
9993
+ constructor(param, src) {
9994
+ super(`The ${param.name} must be a quoted string ("…" or '…'), ${src ? `not ${src}` : "but it has no value"}`);
9995
+ this.name = "MacroArgumentError";
9996
+ }
9997
+ };
9759
9998
  function readValue(param, src) {
9760
9999
  switch (param.type) {
9761
- case "string": return readWholeQuoted(src) ?? void 0;
10000
+ case "string": {
10001
+ const value = readWholeQuoted(src);
10002
+ if (value === null) throw new MacroArgumentError(param, src);
10003
+ return value;
10004
+ }
9762
10005
  case "text": return readText(src);
9763
10006
  case "names": return src.split(",").map((name) => name.trim());
9764
10007
  case "delay": return parseDelay(src);
@@ -9773,6 +10016,7 @@ function unset(param) {
9773
10016
  }
9774
10017
  var WORD_RE = /\w+/y;
9775
10018
  var DIGITS_RE = /\d+/y;
10019
+ var WORD_OR_PUNCT_RE = /\S+/y;
9776
10020
  /** Match the sticky regex `re` at `pos`, returning the matched text. */
9777
10021
  function matchAt(re, src, pos) {
9778
10022
  re.lastIndex = pos;
@@ -9781,7 +10025,8 @@ function matchAt(re, src, pos) {
9781
10025
  /**
9782
10026
  * Read keyword options (`goto "X" priority 5 once`). A keyword takes the
9783
10027
  * quoted string or digit run after it as its value; keywords that aren't
9784
- * declared, and their values, are skipped.
10028
+ * declared, and their values, are skipped. A keyword of a `string` parameter
10029
+ * must be followed by a quoted string.
9785
10030
  */
9786
10031
  function readOptions(params, src) {
9787
10032
  const options = {};
@@ -9794,10 +10039,11 @@ function readOptions(params, src) {
9794
10039
  }
9795
10040
  i += key.length;
9796
10041
  let val;
10042
+ let quoted = null;
9797
10043
  let j = i;
9798
10044
  while (j < src.length && isWhitespace(src[j])) j++;
9799
10045
  if (j > i) {
9800
- const quoted = readQuoted(src, j);
10046
+ quoted = readQuoted(src, j);
9801
10047
  const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
9802
10048
  if (quoted) {
9803
10049
  val = quoted.value;
@@ -9808,6 +10054,7 @@ function readOptions(params, src) {
9808
10054
  }
9809
10055
  }
9810
10056
  const param = params.find((p) => p.name === key);
10057
+ if (param?.type === "string" && (!quoted || val === void 0)) throw new MacroArgumentError(param, matchAt(WORD_OR_PUNCT_RE, src, j) ?? "");
9811
10058
  if (param?.type === "flag") options[key] = true;
9812
10059
  else if (param?.type === "number") options[key] = Number(val ?? 0);
9813
10060
  else if (param) options[key] = val;
@@ -9880,8 +10127,16 @@ function readGroup(params, src, args) {
9880
10127
  let from = 0;
9881
10128
  let to = spans.length;
9882
10129
  for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, slice(text, spans[from++])) : unset(param);
10130
+ const readTrailing = (param, value) => {
10131
+ try {
10132
+ return read(param, value);
10133
+ } catch (error) {
10134
+ if (error instanceof MacroArgumentError) return void 0;
10135
+ throw error;
10136
+ }
10137
+ };
9883
10138
  for (const param of positional.slice(restIndex + 1).reverse()) {
9884
- const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? read(param, slice(text, spans[to - 1])) : void 0;
10139
+ const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? readTrailing(param, slice(text, spans[to - 1])) : void 0;
9885
10140
  args[param.name] = value ?? unset(param);
9886
10141
  if (value !== void 0) to--;
9887
10142
  }
@@ -9905,6 +10160,34 @@ function parseMacroArgs(rawArgs, parameters) {
9905
10160
  readGroup(parameters.slice(groupStart), rest, args);
9906
10161
  return args;
9907
10162
  }
10163
+ /**
10164
+ * Read a `passage` argument as written (`{goto "Hall"}`, `{goto $room}`): a
10165
+ * quoted string is the name its JavaScript literal has (`"\u0048all"` is
10166
+ * `Hall`), anything else an expression, whose value is the name when it
10167
+ * runs (see evaluatePassageName).
10168
+ */
10169
+ function passageTarget(arg) {
10170
+ const expression = arg.trim();
10171
+ const name = readWholeJsString(expression);
10172
+ return name === null ? {
10173
+ kind: "expression",
10174
+ expression
10175
+ } : {
10176
+ kind: "name",
10177
+ name
10178
+ };
10179
+ }
10180
+ /**
10181
+ * The passage a `passage` argument names (`{goto "Hall"}`, `{goto $room}`):
10182
+ * the value of its expression. Throws what evaluating it throws, and if no
10183
+ * passage of the story `state` holds has that name, an error naming it and
10184
+ * the current passage.
10185
+ */
10186
+ function evaluatePassageName(expr, evaluate, { storyData, currentPassage }) {
10187
+ const name = String(evaluate(expr ?? ""));
10188
+ if (storyData && !storyData.passages.has(name)) throw noPassageError(name, currentPassage);
10189
+ return name;
10190
+ }
9908
10191
  //#endregion
9909
10192
  //#region src/widgets/widget-def.ts
9910
10193
  /**
@@ -9988,15 +10271,21 @@ var CONDITION_MACROS = new Set([
9988
10271
  "case"
9989
10272
  ]);
9990
10273
  /** Code inside the quoted strings of a macro's arguments, by parameter. */
9991
- var CODE_IN_STRINGS = { watch: {
10274
+ var CODE_IN_STRINGS = new Map([["watch", {
9992
10275
  condition: "expression",
9993
10276
  run: "statements"
9994
- } };
10277
+ }]]);
9995
10278
  /** The `string` parameters of a macro that name a passage. */
9996
- var PASSAGE_STRINGS = {
9997
- link: ["passage"],
9998
- watch: ["goto", "dialog"]
9999
- };
10279
+ var PASSAGE_STRINGS = new Map([["watch", ["goto", "dialog"]]]);
10280
+ /**
10281
+ * The `text` and `string` parameters of a macro that the runtime keeps as
10282
+ * written, not as markup: an option's value, the name of a watcher.
10283
+ */
10284
+ var LITERAL_STRINGS = new Map([
10285
+ ["option", ["value"]],
10286
+ ["watch", ["name"]],
10287
+ ["unwatch", ["name"]]
10288
+ ]);
10000
10289
  /** Block macros whose body is the name of a passage. */
10001
10290
  var PASSAGE_BODIES = new Set(["dialog"]);
10002
10291
  /** The goal of the code an argument of this type holds, if it is code. */
@@ -10017,6 +10306,8 @@ function* codeAndText(src, tokens, parametersOf) {
10017
10306
  kind: "passage",
10018
10307
  name: token.target,
10019
10308
  offset: locate(src, token.target, token.start),
10309
+ length: token.target.length,
10310
+ macro: "link",
10020
10311
  label: src.slice(token.start, token.end)
10021
10312
  };
10022
10313
  else if (token.type === "expression") yield {
@@ -10076,6 +10367,8 @@ function* codeAndText(src, tokens, parametersOf) {
10076
10367
  kind: "passage",
10077
10368
  name: passage,
10078
10369
  offset: locate(src, passage, token.end),
10370
+ length: passage.length,
10371
+ macro: name,
10079
10372
  label: src.slice(token.start, token.end)
10080
10373
  };
10081
10374
  }
@@ -10100,9 +10393,23 @@ function* codeAndText(src, tokens, parametersOf) {
10100
10393
  }
10101
10394
  /** The code and text in the arguments `args` (at `offset`) of `macro`. */
10102
10395
  function* argPieces(args, offset, params, macro, label) {
10103
- const values = parseMacroArgs(args, params);
10104
- const inStrings = CODE_IN_STRINGS[macro.toLowerCase()] ?? {};
10105
- const passageStrings = PASSAGE_STRINGS[macro.toLowerCase()] ?? [];
10396
+ let values;
10397
+ try {
10398
+ values = parseMacroArgs(args, params);
10399
+ } catch (error) {
10400
+ if (!(error instanceof MacroArgumentError)) throw error;
10401
+ yield {
10402
+ kind: "argument-error",
10403
+ message: error.message,
10404
+ offset,
10405
+ label
10406
+ };
10407
+ return;
10408
+ }
10409
+ const key = macro.toLowerCase();
10410
+ const inStrings = CODE_IN_STRINGS.get(key) ?? {};
10411
+ const passageStrings = PASSAGE_STRINGS.get(key) ?? [];
10412
+ const literalStrings = LITERAL_STRINGS.get(key) ?? [];
10106
10413
  let cursor = 0;
10107
10414
  /** The pieces of the parameters `list`, with their values in `from`. */
10108
10415
  function* visit(list, from) {
@@ -10124,17 +10431,27 @@ function* argPieces(args, offset, params, macro, label) {
10124
10431
  goal,
10125
10432
  label
10126
10433
  };
10127
- if (param.type === "passage") piece.passage = true;
10434
+ if (param.type === "passage") {
10435
+ piece.passage = true;
10436
+ piece.macro = macro.toLowerCase();
10437
+ }
10438
+ if (!codeGoal(param)) piece.inString = true;
10128
10439
  yield piece;
10129
10440
  }
10130
- const name = param.type === "passage" ? readWholeQuoted(value.trim()) : passageStrings.includes(param.name) ? value : null;
10131
- if (name !== null) yield {
10441
+ const written = param.type === "passage" ? value.trim() : value;
10442
+ const target = param.type === "passage" ? passageTarget(value) : passageStrings.includes(param.name) ? {
10443
+ kind: "name",
10444
+ name: value
10445
+ } : null;
10446
+ if (target?.kind === "name") yield {
10132
10447
  kind: "passage",
10133
- name,
10134
- offset: offset + at,
10448
+ name: target.name,
10449
+ offset: offset + at + value.indexOf(written),
10450
+ length: written.length,
10451
+ macro: macro.toLowerCase(),
10135
10452
  label
10136
10453
  };
10137
- if (!goal && (param.type === "text" || param.type === "string")) yield {
10454
+ if (!goal && (param.type === "text" || param.type === "string") && !literalStrings.includes(param.name)) yield {
10138
10455
  kind: "text",
10139
10456
  text: value,
10140
10457
  offset: offset + at,
@@ -10154,6 +10471,43 @@ function locate(text, part, from) {
10154
10471
  const anywhere = text.indexOf(part);
10155
10472
  return anywhere >= 0 ? anywhere : from;
10156
10473
  }
10474
+ /**
10475
+ * The passages the markup `src` (with the `tokens` it tokenizes to) names:
10476
+ * `[[…]]` links, the passage argument of `{goto}`, `{include}` and `{link}`
10477
+ * (and of macros that declare one), the `goto` and `dialog` actions of
10478
+ * `{watch}` and the body of `{dialog}`, in source order. A name written out
10479
+ * is a literal; the others are expressions, which name a passage when they
10480
+ * run.
10481
+ */
10482
+ function collectPassageReferences(src, tokens, parametersOf) {
10483
+ const refs = [];
10484
+ for (const piece of codeAndText(src, tokens, parametersOf)) if (piece.kind === "passage") refs.push({
10485
+ macro: piece.macro,
10486
+ target: {
10487
+ kind: "name",
10488
+ name: piece.name
10489
+ },
10490
+ start: piece.offset,
10491
+ end: piece.offset + piece.length
10492
+ });
10493
+ else if (piece.kind === "code" && piece.passage) refs.push({
10494
+ macro: piece.macro,
10495
+ target: passageTarget(piece.code),
10496
+ start: piece.offset,
10497
+ end: piece.offset + piece.code.length
10498
+ });
10499
+ return refs.filter((ref, i) => refs.findIndex((r) => r.start === ref.start && r.macro === ref.macro) === i);
10500
+ }
10501
+ /** The parameters of the built-in sub-macros that take arguments. */
10502
+ var SUB_MACRO_PARAMETERS = new Map([["option", [{
10503
+ name: "value",
10504
+ type: "string",
10505
+ required: true
10506
+ }]]]);
10507
+ /** The parameters of the built-in sub-macro `name`, if it takes any. */
10508
+ function subMacroParameters(name) {
10509
+ return SUB_MACRO_PARAMETERS.get(name.toLowerCase());
10510
+ }
10157
10511
  //#endregion
10158
10512
  //#region src/markup/validate.ts
10159
10513
  /**
@@ -10163,8 +10517,15 @@ function locate(text, part, from) {
10163
10517
  * line and column they are at, instead of failing when the passage renders.
10164
10518
  * Tooling runs it too (see src/tooling.ts).
10165
10519
  */
10166
- /** Passages that hold no markup. */
10167
- var NOT_MARKUP = new Set(["StoryVariables", "StoryTransients"]);
10520
+ /**
10521
+ * Passages that hold no markup: declarations, and the JavaScript function
10522
+ * body of SaveTitle.
10523
+ */
10524
+ var NOT_MARKUP = new Set([
10525
+ "StoryVariables",
10526
+ "StoryTransients",
10527
+ "SaveTitle"
10528
+ ]);
10168
10529
  var NOT_MARKUP_TAGS = ["script", "stylesheet"];
10169
10530
  /** Branch macros, which their parent macro renders. */
10170
10531
  var BRANCH_MACROS = new Set([
@@ -10275,7 +10636,7 @@ function validateMarkup(passages, options) {
10275
10636
  report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
10276
10637
  }
10277
10638
  };
10278
- const parametersOf = options.parametersOf ?? (() => void 0);
10639
+ const parametersOf = (name) => options.parametersOf?.(name) ?? subMacroParameters(name);
10279
10640
  /**
10280
10641
  * Check `tokens`, the tokens of `src`, which starts at `base` in the
10281
10642
  * passage: their macros, the code in them and the markup in their text.
@@ -10284,6 +10645,10 @@ function validateMarkup(passages, options) {
10284
10645
  checkMacros(passage, tokens, base, where);
10285
10646
  for (const piece of codeAndText(src, tokens, parametersOf)) {
10286
10647
  const at = base + piece.offset;
10648
+ if (piece.kind === "argument-error") {
10649
+ report(passage, at, `${where}${piece.message} in ${piece.label}`);
10650
+ continue;
10651
+ }
10287
10652
  if (piece.kind === "passage") {
10288
10653
  if (options.checkPassageNames !== false && !passageNames.has(piece.name)) {
10289
10654
  const hint = didYouMean(piece.name, passageNames, JSON.stringify);
@@ -10317,6 +10682,58 @@ function validateMarkup(passages, options) {
10317
10682
  for (const [passage, tokens] of parsed) withParseCache(() => checkTokens(passage, passage.content, tokens, 0, ""));
10318
10683
  return diagnostics;
10319
10684
  }
10685
+ new NameMap();
10686
+ new NameMap();
10687
+ new NameSet();
10688
+ /**
10689
+ * How a macro argument is read (see components/macros/macro-args.ts). Every
10690
+ * declared parameter names one: there is no default.
10691
+ * Quoted strings accept `\"`, `\'` and `\\` escapes.
10692
+ * - `expression`: code, as written.
10693
+ * - `statements`: code run as statements (`{set}`), as written.
10694
+ * - `passage`: a passage name: a quoted string or an expression, as written.
10695
+ * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
10696
+ * - `string`: one quoted string; anything else leaves the argument unset.
10697
+ * - `text`: one quoted string, or text with any loose quotes stripped.
10698
+ * - `names`: a comma-separated list of names (`@item, @i`).
10699
+ * - `delay`: a duration (`2s`, `500ms`, `300`) in milliseconds.
10700
+ * - `number`: a number.
10701
+ * - `flag`: a keyword, the parameter's name, at the start or end; a boolean.
10702
+ * - `separator`: a word (`of`) or `=` separating the parameters before it
10703
+ * from those after it; a boolean.
10704
+ * - `options`: keywords, the names of its `parameters`, each followed by a
10705
+ * quoted string or a number unless it is a flag.
10706
+ */
10707
+ var PARAMETER_TYPES = [
10708
+ "expression",
10709
+ "statements",
10710
+ "passage",
10711
+ "variable",
10712
+ "string",
10713
+ "text",
10714
+ "names",
10715
+ "delay",
10716
+ "number",
10717
+ "flag",
10718
+ "separator",
10719
+ "options"
10720
+ ];
10721
+ /**
10722
+ * Throw if a parameter `macro` declares (or an option of one) has no type,
10723
+ * or one that isn't a ParameterType: arguments are read by their type, and
10724
+ * there is no default.
10725
+ */
10726
+ function checkParameterTypes(macro, parameters) {
10727
+ for (const param of parameters) {
10728
+ const type = param.type;
10729
+ if (!PARAMETER_TYPES.includes(type)) {
10730
+ const problem = type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`;
10731
+ throw new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
10732
+ }
10733
+ if (param.parameters) checkParameterTypes(macro, param.parameters);
10734
+ }
10735
+ }
10736
+ new NameMap();
10320
10737
  //#endregion
10321
10738
  //#region src/utils/namespace.ts
10322
10739
  /** The variable name no namespace can hold. */
@@ -10407,58 +10824,84 @@ function parseStoryVariables(content, sigil = "$") {
10407
10824
  }
10408
10825
  return schema;
10409
10826
  }
10410
- new NameMap();
10411
- new NameMap();
10412
- new NameSet();
10827
+ //#endregion
10828
+ //#region src/components/macros/include-args.ts
10413
10829
  /**
10414
- * How a macro argument is read (see components/macros/macro-args.ts). Every
10415
- * declared parameter names one: there is no default.
10416
- * Quoted strings accept `\"`, `\'` and `\\` escapes.
10417
- * - `expression`: code, as written.
10418
- * - `statements`: code run as statements (`{set}`), as written.
10419
- * - `passage`: a passage name: a quoted string or an expression, as written.
10420
- * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
10421
- * - `string`: one quoted string; anything else leaves the argument unset.
10422
- * - `text`: one quoted string, or text with any loose quotes stripped.
10423
- * - `names`: a comma-separated list of names (`@item, @i`).
10424
- * - `delay`: a duration (`2s`, `500ms`, `300`) in milliseconds.
10425
- * - `number`: a number.
10426
- * - `flag`: a keyword, the parameter's name, at the start or end; a boolean.
10427
- * - `separator`: a word (`of`) or `=` separating the parameters before it
10428
- * from those after it; a boolean.
10429
- * - `options`: keywords, the names of its `parameters`, each followed by a
10430
- * quoted string or a number unless it is a flag.
10830
+ * The parameters of `{include}`. A standalone `inline` flag only counts
10831
+ * outside quotes and brackets, so passage names and expressions containing
10832
+ * the word stay intact (#201).
10431
10833
  */
10432
- var PARAMETER_TYPES = [
10433
- "expression",
10434
- "statements",
10435
- "passage",
10436
- "variable",
10437
- "string",
10438
- "text",
10439
- "names",
10440
- "delay",
10441
- "number",
10442
- "flag",
10443
- "separator",
10444
- "options"
10445
- ];
10834
+ var INCLUDE_PARAMETERS = [{
10835
+ name: "inline",
10836
+ type: "flag"
10837
+ }, {
10838
+ name: "passage",
10839
+ type: "passage",
10840
+ required: true
10841
+ }];
10446
10842
  /**
10447
- * Throw if a parameter `macro` declares (or an option of one) has no type,
10448
- * or one that isn't a ParameterType: arguments are read by their type, and
10449
- * there is no default.
10843
+ * The arguments of `{include}`: whether it has the `inline` flag, as its
10844
+ * first or last word, and the passage expression that is left.
10450
10845
  */
10451
- function checkParameterTypes(macro, parameters) {
10452
- for (const param of parameters) {
10453
- const type = param.type;
10454
- if (!PARAMETER_TYPES.includes(type)) {
10455
- const problem = type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`;
10456
- throw new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
10457
- }
10458
- if (param.parameters) checkParameterTypes(macro, param.parameters);
10459
- }
10846
+ function splitIncludeFlag(rawArgs) {
10847
+ const { inline, passage } = parseMacroArgs(rawArgs, INCLUDE_PARAMETERS);
10848
+ return {
10849
+ inline,
10850
+ passage
10851
+ };
10460
10852
  }
10461
- new NameMap();
10853
+ //#endregion
10854
+ //#region src/transform.ts
10855
+ /**
10856
+ * The sigil transform: the story's code with its variable references
10857
+ * (`$var`, `_var`, `@var`, `%var`) turned into the namespace lookups the
10858
+ * expression engine runs. Pure string-to-string, so tooling can use it
10859
+ * (see src/tooling.ts).
10860
+ */
10861
+ var TRANSFORM_CACHE_MAX = 500;
10862
+ var NAMESPACES = {
10863
+ $: "variables",
10864
+ _: "temporary",
10865
+ "@": "locals",
10866
+ "%": "transient"
10867
+ };
10868
+ /** Ends with an identifier character. */
10869
+ var IDENT_END_RE = /[\p{ID_Continue}$\u200c\u200d]$/u;
10870
+ /**
10871
+ * Transform expression: $var → variables["var"], _var → temporary["var"],
10872
+ * @var → locals["var"], %var → transient["var"].
10873
+ * The lexer (`lexJs`) finds the references: only in code (string, template
10874
+ * and regex literal text and comments are left untouched), only where an
10875
+ * identifier starts (not in `a$b`, nor as the property name in `obj._x`),
10876
+ * and `%var` only where an operand is expected, since `%` is also the
10877
+ * modulo operator. `goal` tells whether `expr` is an expression or a list of
10878
+ * statements, as that decides whether a leading `{` opens an object literal
10879
+ * or a block. A reference to a variable named `__proto__` throws a
10880
+ * SyntaxError: no namespace can hold one (see utils/namespace.ts).
10881
+ * */
10882
+ function transform(expr, goal = "expression") {
10883
+ const key = goal === "statements" ? "s" + expr : "e" + expr;
10884
+ const cached = transformCache.get(key);
10885
+ if (cached !== void 0) return cached;
10886
+ const { refs } = parseCode(expr, goal);
10887
+ let result = "";
10888
+ let at = 0;
10889
+ for (const ref of refs) {
10890
+ const error = variableNameError(ref.name, ref.sigil + ref.name);
10891
+ if (error) throw new SyntaxError(`spindle: ${error}`);
10892
+ result += expr.slice(at, ref.start);
10893
+ if (ref.shorthand) result += `${ref.name}: `;
10894
+ else if (IDENT_END_RE.test(result.slice(-2))) result += " ";
10895
+ result += `${NAMESPACES[ref.sigil]}["${ref.name}"]`;
10896
+ at = ref.end;
10897
+ }
10898
+ result += expr.slice(at);
10899
+ if (transformCache.size >= TRANSFORM_CACHE_MAX) transformCache.delete(transformCache.keys().next().value);
10900
+ transformCache.set(key, result);
10901
+ return result;
10902
+ }
10903
+ /** Transformed code by goal and source. */
10904
+ var transformCache = /* @__PURE__ */ new Map();
10462
10905
  //#endregion
10463
10906
  //#region src/tooling.ts
10464
10907
  /**
@@ -10493,5 +10936,16 @@ function validateStoryMarkup(passages, macros, options = {}) {
10493
10936
  checkPassageNames: options.checkPassageNames
10494
10937
  });
10495
10938
  }
10939
+ /**
10940
+ * The passages the markup of `source` names (see PassageReference), against
10941
+ * the given macros: what the story-start check looks up. Malformed tags are
10942
+ * skipped, so it reads half-typed markup.
10943
+ */
10944
+ function collectStoryPassageReferences(source, macros) {
10945
+ const parameters = /* @__PURE__ */ new Map();
10946
+ for (const macro of macros) parameters.set(macro.name.toLowerCase(), macro.parameters);
10947
+ const { tokens } = tokenizeMarkupTolerant(source);
10948
+ return collectPassageReferences(source, tokens, (name) => parameters.get(name.toLowerCase()) ?? subMacroParameters(name));
10949
+ }
10496
10950
  //#endregion
10497
- export { checkParameterTypes, formatDiagnostic, parseStoryVariables, validateStoryMarkup };
10951
+ export { MarkupError, SIGIL_SCOPES, checkParameterTypes, collectStoryPassageReferences, endsWithOperator, evaluatePassageName, findCodeEnd, formatDiagnostic, isSigil, lexJs, lexTemplate, parseSelectors, parseStoryVariables, passageTarget, readQuoted, scanStringLiteral, splitArgs, splitIncludeFlag, splitTopLevel, stripLooseQuotes, tokenizeMarkup, tokenizeMarkupTolerant, transform, unescapeQuoted, validateStoryMarkup };