@rohal12/spindle 0.57.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
  /**
@@ -9762,7 +9991,7 @@ function readText(src) {
9762
9991
  */
9763
9992
  var MacroArgumentError = class extends Error {
9764
9993
  constructor(param, src) {
9765
- super(`The ${param.name} must be a quoted string ("…" or '…'), not ${src}`);
9994
+ super(`The ${param.name} must be a quoted string ("…" or '…'), ${src ? `not ${src}` : "but it has no value"}`);
9766
9995
  this.name = "MacroArgumentError";
9767
9996
  }
9768
9997
  };
@@ -9787,6 +10016,7 @@ function unset(param) {
9787
10016
  }
9788
10017
  var WORD_RE = /\w+/y;
9789
10018
  var DIGITS_RE = /\d+/y;
10019
+ var WORD_OR_PUNCT_RE = /\S+/y;
9790
10020
  /** Match the sticky regex `re` at `pos`, returning the matched text. */
9791
10021
  function matchAt(re, src, pos) {
9792
10022
  re.lastIndex = pos;
@@ -9795,7 +10025,8 @@ function matchAt(re, src, pos) {
9795
10025
  /**
9796
10026
  * Read keyword options (`goto "X" priority 5 once`). A keyword takes the
9797
10027
  * quoted string or digit run after it as its value; keywords that aren't
9798
- * 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.
9799
10030
  */
9800
10031
  function readOptions(params, src) {
9801
10032
  const options = {};
@@ -9808,10 +10039,11 @@ function readOptions(params, src) {
9808
10039
  }
9809
10040
  i += key.length;
9810
10041
  let val;
10042
+ let quoted = null;
9811
10043
  let j = i;
9812
10044
  while (j < src.length && isWhitespace(src[j])) j++;
9813
10045
  if (j > i) {
9814
- const quoted = readQuoted(src, j);
10046
+ quoted = readQuoted(src, j);
9815
10047
  const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
9816
10048
  if (quoted) {
9817
10049
  val = quoted.value;
@@ -9822,6 +10054,7 @@ function readOptions(params, src) {
9822
10054
  }
9823
10055
  }
9824
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) ?? "");
9825
10058
  if (param?.type === "flag") options[key] = true;
9826
10059
  else if (param?.type === "number") options[key] = Number(val ?? 0);
9827
10060
  else if (param) options[key] = val;
@@ -9927,6 +10160,34 @@ function parseMacroArgs(rawArgs, parameters) {
9927
10160
  readGroup(parameters.slice(groupStart), rest, args);
9928
10161
  return args;
9929
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
+ }
9930
10191
  //#endregion
9931
10192
  //#region src/widgets/widget-def.ts
9932
10193
  /**
@@ -10010,12 +10271,21 @@ var CONDITION_MACROS = new Set([
10010
10271
  "case"
10011
10272
  ]);
10012
10273
  /** Code inside the quoted strings of a macro's arguments, by parameter. */
10013
- var CODE_IN_STRINGS = { watch: {
10274
+ var CODE_IN_STRINGS = new Map([["watch", {
10014
10275
  condition: "expression",
10015
10276
  run: "statements"
10016
- } };
10277
+ }]]);
10017
10278
  /** The `string` parameters of a macro that name a passage. */
10018
- var PASSAGE_STRINGS = { watch: ["goto", "dialog"] };
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
+ ]);
10019
10289
  /** Block macros whose body is the name of a passage. */
10020
10290
  var PASSAGE_BODIES = new Set(["dialog"]);
10021
10291
  /** The goal of the code an argument of this type holds, if it is code. */
@@ -10036,6 +10306,8 @@ function* codeAndText(src, tokens, parametersOf) {
10036
10306
  kind: "passage",
10037
10307
  name: token.target,
10038
10308
  offset: locate(src, token.target, token.start),
10309
+ length: token.target.length,
10310
+ macro: "link",
10039
10311
  label: src.slice(token.start, token.end)
10040
10312
  };
10041
10313
  else if (token.type === "expression") yield {
@@ -10095,6 +10367,8 @@ function* codeAndText(src, tokens, parametersOf) {
10095
10367
  kind: "passage",
10096
10368
  name: passage,
10097
10369
  offset: locate(src, passage, token.end),
10370
+ length: passage.length,
10371
+ macro: name,
10098
10372
  label: src.slice(token.start, token.end)
10099
10373
  };
10100
10374
  }
@@ -10132,8 +10406,10 @@ function* argPieces(args, offset, params, macro, label) {
10132
10406
  };
10133
10407
  return;
10134
10408
  }
10135
- const inStrings = CODE_IN_STRINGS[macro.toLowerCase()] ?? {};
10136
- const passageStrings = PASSAGE_STRINGS[macro.toLowerCase()] ?? [];
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) ?? [];
10137
10413
  let cursor = 0;
10138
10414
  /** The pieces of the parameters `list`, with their values in `from`. */
10139
10415
  function* visit(list, from) {
@@ -10155,17 +10431,27 @@ function* argPieces(args, offset, params, macro, label) {
10155
10431
  goal,
10156
10432
  label
10157
10433
  };
10158
- 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;
10159
10439
  yield piece;
10160
10440
  }
10161
- const name = param.type === "passage" ? readWholeQuoted(value.trim()) : passageStrings.includes(param.name) ? value : null;
10162
- 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 {
10163
10447
  kind: "passage",
10164
- name,
10165
- offset: offset + at,
10448
+ name: target.name,
10449
+ offset: offset + at + value.indexOf(written),
10450
+ length: written.length,
10451
+ macro: macro.toLowerCase(),
10166
10452
  label
10167
10453
  };
10168
- if (!goal && (param.type === "text" || param.type === "string")) yield {
10454
+ if (!goal && (param.type === "text" || param.type === "string") && !literalStrings.includes(param.name)) yield {
10169
10455
  kind: "text",
10170
10456
  text: value,
10171
10457
  offset: offset + at,
@@ -10185,12 +10471,43 @@ function locate(text, part, from) {
10185
10471
  const anywhere = text.indexOf(part);
10186
10472
  return anywhere >= 0 ? anywhere : from;
10187
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
+ }
10188
10501
  /** The parameters of the built-in sub-macros that take arguments. */
10189
- var SUB_MACRO_PARAMETERS = { option: [{
10502
+ var SUB_MACRO_PARAMETERS = new Map([["option", [{
10190
10503
  name: "value",
10191
10504
  type: "string",
10192
10505
  required: true
10193
- }] };
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
+ }
10194
10511
  //#endregion
10195
10512
  //#region src/markup/validate.ts
10196
10513
  /**
@@ -10200,8 +10517,15 @@ var SUB_MACRO_PARAMETERS = { option: [{
10200
10517
  * line and column they are at, instead of failing when the passage renders.
10201
10518
  * Tooling runs it too (see src/tooling.ts).
10202
10519
  */
10203
- /** Passages that hold no markup. */
10204
- 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
+ ]);
10205
10529
  var NOT_MARKUP_TAGS = ["script", "stylesheet"];
10206
10530
  /** Branch macros, which their parent macro renders. */
10207
10531
  var BRANCH_MACROS = new Set([
@@ -10312,7 +10636,7 @@ function validateMarkup(passages, options) {
10312
10636
  report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
10313
10637
  }
10314
10638
  };
10315
- const parametersOf = (name) => options.parametersOf?.(name) ?? SUB_MACRO_PARAMETERS[name.toLowerCase()];
10639
+ const parametersOf = (name) => options.parametersOf?.(name) ?? subMacroParameters(name);
10316
10640
  /**
10317
10641
  * Check `tokens`, the tokens of `src`, which starts at `base` in the
10318
10642
  * passage: their macros, the code in them and the markup in their text.
@@ -10358,6 +10682,58 @@ function validateMarkup(passages, options) {
10358
10682
  for (const [passage, tokens] of parsed) withParseCache(() => checkTokens(passage, passage.content, tokens, 0, ""));
10359
10683
  return diagnostics;
10360
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();
10361
10737
  //#endregion
10362
10738
  //#region src/utils/namespace.ts
10363
10739
  /** The variable name no namespace can hold. */
@@ -10448,58 +10824,84 @@ function parseStoryVariables(content, sigil = "$") {
10448
10824
  }
10449
10825
  return schema;
10450
10826
  }
10451
- new NameMap();
10452
- new NameMap();
10453
- new NameSet();
10827
+ //#endregion
10828
+ //#region src/components/macros/include-args.ts
10454
10829
  /**
10455
- * How a macro argument is read (see components/macros/macro-args.ts). Every
10456
- * declared parameter names one: there is no default.
10457
- * Quoted strings accept `\"`, `\'` and `\\` escapes.
10458
- * - `expression`: code, as written.
10459
- * - `statements`: code run as statements (`{set}`), as written.
10460
- * - `passage`: a passage name: a quoted string or an expression, as written.
10461
- * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
10462
- * - `string`: one quoted string; anything else leaves the argument unset.
10463
- * - `text`: one quoted string, or text with any loose quotes stripped.
10464
- * - `names`: a comma-separated list of names (`@item, @i`).
10465
- * - `delay`: a duration (`2s`, `500ms`, `300`) in milliseconds.
10466
- * - `number`: a number.
10467
- * - `flag`: a keyword, the parameter's name, at the start or end; a boolean.
10468
- * - `separator`: a word (`of`) or `=` separating the parameters before it
10469
- * from those after it; a boolean.
10470
- * - `options`: keywords, the names of its `parameters`, each followed by a
10471
- * 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).
10472
10833
  */
10473
- var PARAMETER_TYPES = [
10474
- "expression",
10475
- "statements",
10476
- "passage",
10477
- "variable",
10478
- "string",
10479
- "text",
10480
- "names",
10481
- "delay",
10482
- "number",
10483
- "flag",
10484
- "separator",
10485
- "options"
10486
- ];
10834
+ var INCLUDE_PARAMETERS = [{
10835
+ name: "inline",
10836
+ type: "flag"
10837
+ }, {
10838
+ name: "passage",
10839
+ type: "passage",
10840
+ required: true
10841
+ }];
10487
10842
  /**
10488
- * Throw if a parameter `macro` declares (or an option of one) has no type,
10489
- * or one that isn't a ParameterType: arguments are read by their type, and
10490
- * 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.
10491
10845
  */
10492
- function checkParameterTypes(macro, parameters) {
10493
- for (const param of parameters) {
10494
- const type = param.type;
10495
- if (!PARAMETER_TYPES.includes(type)) {
10496
- const problem = type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`;
10497
- 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.`);
10498
- }
10499
- if (param.parameters) checkParameterTypes(macro, param.parameters);
10500
- }
10846
+ function splitIncludeFlag(rawArgs) {
10847
+ const { inline, passage } = parseMacroArgs(rawArgs, INCLUDE_PARAMETERS);
10848
+ return {
10849
+ inline,
10850
+ passage
10851
+ };
10501
10852
  }
10502
- 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();
10503
10905
  //#endregion
10504
10906
  //#region src/tooling.ts
10505
10907
  /**
@@ -10534,5 +10936,16 @@ function validateStoryMarkup(passages, macros, options = {}) {
10534
10936
  checkPassageNames: options.checkPassageNames
10535
10937
  });
10536
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
+ }
10537
10950
  //#endregion
10538
- 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 };