@rohal12/spindle 0.57.0 → 0.59.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",
@@ -41,34 +68,46 @@ var LINK_SEPARATORS = [
41
68
  ["<-", true]
42
69
  ];
43
70
  var NO_SELECTORS = {};
44
- function parseLink(inner) {
71
+ /**
72
+ * A link's display text and target, from its text `inner` (at `at` in the
73
+ * input), with where the target is written.
74
+ */
75
+ function parseLink(inner, at) {
76
+ /** The trimmed text from `start` to `end` of inner, and where it is. */
77
+ const part = (start, end) => {
78
+ const raw = inner.slice(start, end);
79
+ const value = raw.trim();
80
+ const from = at + start + (value ? raw.indexOf(value) : 0);
81
+ return {
82
+ value,
83
+ start: from,
84
+ end: from + value.length
85
+ };
86
+ };
45
87
  for (const [separator, targetFirst] of LINK_SEPARATORS) {
46
88
  const idx = inner.indexOf(separator);
47
89
  if (idx === -1) continue;
48
- const before = inner.slice(0, idx).trim();
49
- const after = inner.slice(idx + separator.length).trim();
50
- return targetFirst ? {
51
- display: after,
52
- target: before
53
- } : {
54
- display: before,
55
- target: after
90
+ const before = part(0, idx);
91
+ const after = part(idx + separator.length, inner.length);
92
+ const [display, target] = targetFirst ? [after, before] : [before, after];
93
+ return {
94
+ display: display.value,
95
+ target: target.value,
96
+ targetStart: target.start,
97
+ targetEnd: target.end
56
98
  };
57
99
  }
58
- const trimmed = inner.trim();
100
+ const whole = part(0, inner.length);
59
101
  return {
60
- display: trimmed,
61
- target: trimmed
102
+ display: whole.value,
103
+ target: whole.value,
104
+ targetStart: whole.start,
105
+ targetEnd: whole.end
62
106
  };
63
107
  }
64
108
  function quoteArg(value) {
65
109
  return "\"" + value.replace(/[\\"]/g, "\\$&") + "\"";
66
110
  }
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
111
  function selectorsOf(parts) {
73
112
  const classes = [];
74
113
  let id = "";
@@ -145,6 +184,8 @@ function tokenOf(x) {
145
184
  type: "link",
146
185
  display: x.display,
147
186
  target: x.target,
187
+ targetStart: x.targetStart,
188
+ targetEnd: x.targetEnd,
148
189
  ...span
149
190
  }, x);
150
191
  case "variable": return withSelectors({
@@ -288,7 +329,8 @@ function peg$parse(input, options) {
288
329
  const peg$source = options.grammarSource;
289
330
  const peg$startRuleFunctions = {
290
331
  Markup: peg$parseMarkup,
291
- Tokens: peg$parseTokens
332
+ Tokens: peg$parseTokens,
333
+ SelectorsPrefix: peg$parseSelectorsPrefix
292
334
  };
293
335
  let peg$startRuleFunction = peg$parseMarkup;
294
336
  const peg$c0 = "\\";
@@ -675,8 +717,8 @@ function peg$parse(input, options) {
675
717
  function peg$f48() {
676
718
  return !textMode;
677
719
  }
678
- function peg$f49(s, inner) {
679
- return withSelectors(lexeme("link", location(), parseLink(inner)), s);
720
+ function peg$f49(s, at, inner) {
721
+ return withSelectors(lexeme("link", location(), parseLink(inner, at)), s);
680
722
  }
681
723
  function peg$f50() {
682
724
  return !textMode;
@@ -690,40 +732,46 @@ function peg$parse(input, options) {
690
732
  function peg$f53() {
691
733
  return NO_SELECTORS;
692
734
  }
693
- function peg$f54(prefix, name) {
735
+ function peg$f54(s, end) {
736
+ return {
737
+ ...s,
738
+ end
739
+ };
740
+ }
741
+ function peg$f55(prefix, name) {
694
742
  return [prefix, name];
695
743
  }
696
- function peg$f55(s, sigil, name) {
744
+ function peg$f56(s, sigil, name) {
697
745
  return withSelectors(lexeme("variable", location(), {
698
746
  name,
699
747
  scope: SIGIL_SCOPES$1[sigil]
700
748
  }), s);
701
749
  }
702
- function peg$f56(s, expression) {
750
+ function peg$f57(s, expression) {
703
751
  return withSelectors(lexeme("expression", location(), { expression }), s);
704
752
  }
705
- function peg$f57() {
753
+ function peg$f58() {
706
754
  fail("Unclosed " + LB + input.substr(location().end.offset, 1) + "…: no } ends it", location());
707
755
  }
708
- function peg$f58(start) {
756
+ function peg$f59(start) {
709
757
  return skipTo(hooks.closeBrace(input, start, start));
710
758
  }
711
- function peg$f59(start) {
759
+ function peg$f60(start) {
712
760
  return skipEnd - offset();
713
761
  }
714
- function peg$f60(start, code) {
762
+ function peg$f61(start, code) {
715
763
  return code;
716
764
  }
717
- function peg$f61(s, start, close, name, nameEnd) {
765
+ function peg$f62(s, start, close, name, nameEnd) {
718
766
  return skipTo(hooks.closeBrace(input, nameEnd, start));
719
767
  }
720
- function peg$f62(s, start, close, name, nameEnd) {
768
+ function peg$f63(s, start, close, name, nameEnd) {
721
769
  return skipEnd - offset();
722
770
  }
723
- function peg$f63(s, start, close, name, nameEnd, args) {
771
+ function peg$f64(s, start, close, name, nameEnd, args) {
724
772
  return args;
725
773
  }
726
- function peg$f64(s, start, close, name, nameEnd, tail) {
774
+ function peg$f65(s, start, close, name, nameEnd, tail) {
727
775
  const loc = location();
728
776
  const lower = name.toLowerCase();
729
777
  const tag = lexeme("macro", loc, {
@@ -744,19 +792,19 @@ function peg$parse(input, options) {
744
792
  }
745
793
  return tag;
746
794
  }
747
- function peg$f65(s) {
795
+ function peg$f66(s) {
748
796
  fail("A closing tag starts with a letter after " + LB + "/", location());
749
797
  }
750
- function peg$f66() {
798
+ function peg$f67() {
751
799
  return !textMode;
752
800
  }
753
- function peg$f67(tag) {
801
+ function peg$f68(tag) {
754
802
  return VOID_TAGS.has(tag.lower);
755
803
  }
756
- function peg$f68(tag) {
804
+ function peg$f69(tag) {
757
805
  return null;
758
806
  }
759
- function peg$f69(tag, list, slash) {
807
+ function peg$f70(tag, list, slash) {
760
808
  const lower = tag.toLowerCase();
761
809
  return lexeme("html", location(), {
762
810
  tag,
@@ -766,7 +814,7 @@ function peg$parse(input, options) {
766
814
  isSelfClose: !!slash || VOID_TAGS.has(lower)
767
815
  });
768
816
  }
769
- function peg$f70(tag, bad) {
817
+ function peg$f71(tag, bad) {
770
818
  const c = input.charAt(bad);
771
819
  if (!c) fail("Unclosed tag <" + tag + ": no > ends it (write &lt; for a < that starts no tag)", location());
772
820
  const at = {
@@ -784,7 +832,7 @@ function peg$parse(input, options) {
784
832
  };
785
833
  fail("Unexpected " + JSON.stringify(c) + " in the tag <" + tag + ">: expected an attribute, or > to end the tag", at);
786
834
  }
787
- function peg$f71(tag) {
835
+ function peg$f72(tag) {
788
836
  return lexeme("html", location(), {
789
837
  tag,
790
838
  lower: tag.toLowerCase(),
@@ -793,44 +841,44 @@ function peg$parse(input, options) {
793
841
  isSelfClose: false
794
842
  });
795
843
  }
796
- function peg$f72(name) {
844
+ function peg$f73(name) {
797
845
  attrCode = hooks.isCodeAttribute(name);
798
846
  return true;
799
847
  }
800
- function peg$f73(name, v) {
848
+ function peg$f74(name, v) {
801
849
  return v;
802
850
  }
803
- function peg$f74(name, value) {
851
+ function peg$f75(name, value) {
804
852
  return [name, value ?? ""];
805
853
  }
806
- function peg$f75(v) {
854
+ function peg$f76(v) {
807
855
  return v;
808
856
  }
809
- function peg$f76(v) {
857
+ function peg$f77(v) {
810
858
  return v;
811
859
  }
812
- function peg$f77(q) {
860
+ function peg$f78(q) {
813
861
  fail("Unclosed attribute value: the " + q + " here has no closing " + q, location());
814
862
  }
815
- function peg$f78() {
863
+ function peg$f79() {
816
864
  return !attrCode;
817
865
  }
818
- function peg$f79(bs) {
866
+ function peg$f80(bs) {
819
867
  return bs.length % 2 === 1;
820
868
  }
821
- function peg$f80() {
869
+ function peg$f81() {
822
870
  return !attrCode;
823
871
  }
824
- function peg$f81(start, nameEnd) {
872
+ function peg$f82(start, nameEnd) {
825
873
  return skipTo(hooks.closeBrace(input, nameEnd, start));
826
874
  }
827
- function peg$f82(start, nameEnd) {
875
+ function peg$f83(start, nameEnd) {
828
876
  return skipEnd - offset();
829
877
  }
830
- function peg$f83() {
878
+ function peg$f84() {
831
879
  return !attrCode;
832
880
  }
833
- function peg$f84() {
881
+ function peg$f85() {
834
882
  return attrCode;
835
883
  }
836
884
  let peg$currPos = options.peg$currPos | 0;
@@ -1719,7 +1767,7 @@ function peg$parse(input, options) {
1719
1767
  return s0;
1720
1768
  }
1721
1769
  function peg$parseLinkLexeme() {
1722
- let s0, s1, s2, s3, s4, s5;
1770
+ let s0, s1, s2, s3, s4, s5, s6;
1723
1771
  s0 = peg$currPos;
1724
1772
  peg$savedPos = peg$currPos;
1725
1773
  s1 = peg$f48();
@@ -1736,19 +1784,20 @@ function peg$parse(input, options) {
1736
1784
  if (s2 !== peg$FAILED) {
1737
1785
  s3 = peg$parseSelectors();
1738
1786
  if (s3 !== peg$FAILED) {
1739
- s4 = peg$currPos;
1740
- s5 = peg$parseLinkText();
1741
- s4 = input.substring(s4, peg$currPos);
1787
+ s4 = peg$parseHere();
1788
+ s5 = peg$currPos;
1789
+ s6 = peg$parseLinkText();
1790
+ s5 = input.substring(s5, peg$currPos);
1742
1791
  if (input.substr(peg$currPos, 2) === peg$c5) {
1743
- s5 = peg$c5;
1792
+ s6 = peg$c5;
1744
1793
  peg$currPos += 2;
1745
1794
  } else {
1746
- s5 = peg$FAILED;
1795
+ s6 = peg$FAILED;
1747
1796
  if (peg$silentFails === 0) peg$fail(peg$e9);
1748
1797
  }
1749
- if (s5 !== peg$FAILED) {
1798
+ if (s6 !== peg$FAILED) {
1750
1799
  peg$savedPos = s0;
1751
- s0 = peg$f49(s3, s4);
1800
+ s0 = peg$f49(s3, s4, s5);
1752
1801
  } else {
1753
1802
  peg$currPos = s0;
1754
1803
  s0 = peg$FAILED;
@@ -1919,6 +1968,38 @@ function peg$parse(input, options) {
1919
1968
  }
1920
1969
  return s0;
1921
1970
  }
1971
+ function peg$parseSelectorsPrefix() {
1972
+ let s0, s1, s2, s3, s4;
1973
+ s0 = peg$currPos;
1974
+ s1 = peg$parseSelectors();
1975
+ if (s1 !== peg$FAILED) {
1976
+ s2 = peg$parseHere();
1977
+ s3 = [];
1978
+ if (input.length > peg$currPos) {
1979
+ s4 = input.charAt(peg$currPos);
1980
+ peg$currPos++;
1981
+ } else {
1982
+ s4 = peg$FAILED;
1983
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1984
+ }
1985
+ while (s4 !== peg$FAILED) {
1986
+ s3.push(s4);
1987
+ if (input.length > peg$currPos) {
1988
+ s4 = input.charAt(peg$currPos);
1989
+ peg$currPos++;
1990
+ } else {
1991
+ s4 = peg$FAILED;
1992
+ if (peg$silentFails === 0) peg$fail(peg$e0);
1993
+ }
1994
+ }
1995
+ peg$savedPos = s0;
1996
+ s0 = peg$f54(s1, s2);
1997
+ } else {
1998
+ peg$currPos = s0;
1999
+ s0 = peg$FAILED;
2000
+ }
2001
+ return s0;
2002
+ }
1922
2003
  function peg$parseSelectorPart() {
1923
2004
  let s0, s1, s2, s3, s4;
1924
2005
  s0 = peg$currPos;
@@ -1938,7 +2019,7 @@ function peg$parse(input, options) {
1938
2019
  }
1939
2020
  s2 = input.substring(s2, peg$currPos);
1940
2021
  peg$savedPos = s0;
1941
- s0 = peg$f54(s1, s2);
2022
+ s0 = peg$f55(s1, s2);
1942
2023
  } else {
1943
2024
  peg$currPos = s0;
1944
2025
  s0 = peg$FAILED;
@@ -2063,7 +2144,7 @@ function peg$parse(input, options) {
2063
2144
  }
2064
2145
  if (s5 !== peg$FAILED) {
2065
2146
  peg$savedPos = s0;
2066
- s0 = peg$f55(s2, s3, s4);
2147
+ s0 = peg$f56(s2, s3, s4);
2067
2148
  } else {
2068
2149
  peg$currPos = s0;
2069
2150
  s0 = peg$FAILED;
@@ -2117,7 +2198,7 @@ function peg$parse(input, options) {
2117
2198
  }
2118
2199
  if (s5 !== peg$FAILED) {
2119
2200
  peg$savedPos = s0;
2120
- s0 = peg$f56(s2, s4);
2201
+ s0 = peg$f57(s2, s4);
2121
2202
  } else {
2122
2203
  peg$currPos = s0;
2123
2204
  s0 = peg$FAILED;
@@ -2165,7 +2246,7 @@ function peg$parse(input, options) {
2165
2246
  } else s3 = peg$FAILED;
2166
2247
  if (s3 !== peg$FAILED) {
2167
2248
  peg$savedPos = s0;
2168
- s0 = peg$f57();
2249
+ s0 = peg$f58();
2169
2250
  } else {
2170
2251
  peg$currPos = s0;
2171
2252
  s0 = peg$FAILED;
@@ -2187,12 +2268,12 @@ function peg$parse(input, options) {
2187
2268
  s0 = peg$currPos;
2188
2269
  s1 = peg$parseHere();
2189
2270
  peg$savedPos = peg$currPos;
2190
- s2 = peg$f58(s1);
2271
+ s2 = peg$f59(s1);
2191
2272
  if (s2) s2 = void 0;
2192
2273
  else s2 = peg$FAILED;
2193
2274
  if (s2 !== peg$FAILED) {
2194
2275
  s3 = peg$currPos;
2195
- s4 = peg$f59(s1);
2276
+ s4 = peg$f60(s1);
2196
2277
  s5 = peg$currPos;
2197
2278
  s6 = [];
2198
2279
  if (s6.length >= (s4 | 0)) s7 = peg$FAILED;
@@ -2223,7 +2304,7 @@ function peg$parse(input, options) {
2223
2304
  else s3 = s4;
2224
2305
  if (s3 !== peg$FAILED) {
2225
2306
  peg$savedPos = s0;
2226
- s0 = peg$f60(s1, s3);
2307
+ s0 = peg$f61(s1, s3);
2227
2308
  } else {
2228
2309
  peg$currPos = s0;
2229
2310
  s0 = peg$FAILED;
@@ -2261,12 +2342,12 @@ function peg$parse(input, options) {
2261
2342
  s6 = peg$parseHere();
2262
2343
  s7 = peg$currPos;
2263
2344
  peg$savedPos = peg$currPos;
2264
- s8 = peg$f61(s2, s3, s4, s5, s6);
2345
+ s8 = peg$f62(s2, s3, s4, s5, s6);
2265
2346
  if (s8) s8 = void 0;
2266
2347
  else s8 = peg$FAILED;
2267
2348
  if (s8 !== peg$FAILED) {
2268
2349
  s9 = peg$currPos;
2269
- s10 = peg$f62(s2, s3, s4, s5, s6);
2350
+ s10 = peg$f63(s2, s3, s4, s5, s6);
2270
2351
  s11 = peg$currPos;
2271
2352
  s12 = [];
2272
2353
  if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
@@ -2305,7 +2386,7 @@ function peg$parse(input, options) {
2305
2386
  }
2306
2387
  if (s10 !== peg$FAILED) {
2307
2388
  peg$savedPos = s7;
2308
- s7 = peg$f63(s2, s3, s4, s5, s6, s9);
2389
+ s7 = peg$f64(s2, s3, s4, s5, s6, s9);
2309
2390
  } else {
2310
2391
  peg$currPos = s7;
2311
2392
  s7 = peg$FAILED;
@@ -2320,7 +2401,7 @@ function peg$parse(input, options) {
2320
2401
  }
2321
2402
  if (s7 === peg$FAILED) s7 = null;
2322
2403
  peg$savedPos = s0;
2323
- s0 = peg$f64(s2, s3, s4, s5, s6, s7);
2404
+ s0 = peg$f65(s2, s3, s4, s5, s6, s7);
2324
2405
  } else {
2325
2406
  peg$currPos = s0;
2326
2407
  s0 = peg$FAILED;
@@ -2354,7 +2435,7 @@ function peg$parse(input, options) {
2354
2435
  }
2355
2436
  if (s3 !== peg$FAILED) {
2356
2437
  peg$savedPos = s0;
2357
- s0 = peg$f65(s2);
2438
+ s0 = peg$f66(s2);
2358
2439
  } else {
2359
2440
  peg$currPos = s0;
2360
2441
  s0 = peg$FAILED;
@@ -2411,19 +2492,19 @@ function peg$parse(input, options) {
2411
2492
  let s0, s1, s2, s3;
2412
2493
  s0 = peg$currPos;
2413
2494
  peg$savedPos = peg$currPos;
2414
- s1 = peg$f66();
2495
+ s1 = peg$f67();
2415
2496
  if (s1) s1 = void 0;
2416
2497
  else s1 = peg$FAILED;
2417
2498
  if (s1 !== peg$FAILED) {
2418
2499
  s2 = peg$parseHtmlClose();
2419
2500
  if (s2 !== peg$FAILED) {
2420
2501
  peg$savedPos = peg$currPos;
2421
- s3 = peg$f67(s2);
2502
+ s3 = peg$f68(s2);
2422
2503
  if (s3) s3 = void 0;
2423
2504
  else s3 = peg$FAILED;
2424
2505
  if (s3 !== peg$FAILED) {
2425
2506
  peg$savedPos = s0;
2426
- s0 = peg$f68(s2);
2507
+ s0 = peg$f69(s2);
2427
2508
  } else {
2428
2509
  peg$currPos = s0;
2429
2510
  s0 = peg$FAILED;
@@ -2475,7 +2556,7 @@ function peg$parse(input, options) {
2475
2556
  }
2476
2557
  if (s6 !== peg$FAILED) {
2477
2558
  peg$savedPos = s0;
2478
- s0 = peg$f69(s2, s3, s5);
2559
+ s0 = peg$f70(s2, s3, s5);
2479
2560
  } else {
2480
2561
  peg$currPos = s0;
2481
2562
  s0 = peg$FAILED;
@@ -2540,7 +2621,7 @@ function peg$parse(input, options) {
2540
2621
  s5 = peg$parse_();
2541
2622
  s6 = peg$parseHere();
2542
2623
  peg$savedPos = s0;
2543
- s0 = peg$f70(s2, s6);
2624
+ s0 = peg$f71(s2, s6);
2544
2625
  } else {
2545
2626
  peg$currPos = s0;
2546
2627
  s0 = peg$FAILED;
@@ -2579,7 +2660,7 @@ function peg$parse(input, options) {
2579
2660
  }
2580
2661
  if (s4 !== peg$FAILED) {
2581
2662
  peg$savedPos = s0;
2582
- s0 = peg$f71(s2);
2663
+ s0 = peg$f72(s2);
2583
2664
  } else {
2584
2665
  peg$currPos = s0;
2585
2666
  s0 = peg$FAILED;
@@ -2713,7 +2794,7 @@ function peg$parse(input, options) {
2713
2794
  else s2 = s3;
2714
2795
  if (s2 !== peg$FAILED) {
2715
2796
  peg$savedPos = peg$currPos;
2716
- s3 = peg$f72(s2);
2797
+ s3 = peg$f73(s2);
2717
2798
  if (s3) s3 = void 0;
2718
2799
  else s3 = peg$FAILED;
2719
2800
  if (s3 !== peg$FAILED) {
@@ -2731,7 +2812,7 @@ function peg$parse(input, options) {
2731
2812
  s8 = peg$parseAttrValue();
2732
2813
  if (s8 !== peg$FAILED) {
2733
2814
  peg$savedPos = s4;
2734
- s4 = peg$f73(s2, s8);
2815
+ s4 = peg$f74(s2, s8);
2735
2816
  } else {
2736
2817
  peg$currPos = s4;
2737
2818
  s4 = peg$FAILED;
@@ -2742,7 +2823,7 @@ function peg$parse(input, options) {
2742
2823
  }
2743
2824
  if (s4 === peg$FAILED) s4 = null;
2744
2825
  peg$savedPos = s0;
2745
- s0 = peg$f74(s2, s4);
2826
+ s0 = peg$f75(s2, s4);
2746
2827
  } else {
2747
2828
  peg$currPos = s0;
2748
2829
  s0 = peg$FAILED;
@@ -2797,7 +2878,7 @@ function peg$parse(input, options) {
2797
2878
  }
2798
2879
  if (s3 !== peg$FAILED) {
2799
2880
  peg$savedPos = s0;
2800
- s0 = peg$f75(s2);
2881
+ s0 = peg$f76(s2);
2801
2882
  } else {
2802
2883
  peg$currPos = s0;
2803
2884
  s0 = peg$FAILED;
@@ -2849,7 +2930,7 @@ function peg$parse(input, options) {
2849
2930
  }
2850
2931
  if (s3 !== peg$FAILED) {
2851
2932
  peg$savedPos = s0;
2852
- s0 = peg$f76(s2);
2933
+ s0 = peg$f77(s2);
2853
2934
  } else {
2854
2935
  peg$currPos = s0;
2855
2936
  s0 = peg$FAILED;
@@ -2868,7 +2949,7 @@ function peg$parse(input, options) {
2868
2949
  }
2869
2950
  if (s1 !== peg$FAILED) {
2870
2951
  peg$savedPos = s0;
2871
- s1 = peg$f77(s1);
2952
+ s1 = peg$f78(s1);
2872
2953
  }
2873
2954
  s0 = s1;
2874
2955
  if (s0 === peg$FAILED) {
@@ -2899,7 +2980,7 @@ function peg$parse(input, options) {
2899
2980
  let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
2900
2981
  s0 = peg$currPos;
2901
2982
  peg$savedPos = peg$currPos;
2902
- s1 = peg$f78();
2983
+ s1 = peg$f79();
2903
2984
  if (s1) s1 = void 0;
2904
2985
  else s1 = peg$FAILED;
2905
2986
  if (s1 !== peg$FAILED) {
@@ -2928,7 +3009,7 @@ function peg$parse(input, options) {
2928
3009
  if (s2 !== peg$FAILED) {
2929
3010
  s3 = peg$currPos;
2930
3011
  peg$savedPos = peg$currPos;
2931
- s4 = peg$f79(s2);
3012
+ s4 = peg$f80(s2);
2932
3013
  if (s4) s4 = void 0;
2933
3014
  else s4 = peg$FAILED;
2934
3015
  if (s4 !== peg$FAILED) {
@@ -2967,7 +3048,7 @@ function peg$parse(input, options) {
2967
3048
  if (s0 === peg$FAILED) {
2968
3049
  s0 = peg$currPos;
2969
3050
  peg$savedPos = peg$currPos;
2970
- s1 = peg$f80();
3051
+ s1 = peg$f81();
2971
3052
  if (s1) s1 = void 0;
2972
3053
  else s1 = peg$FAILED;
2973
3054
  if (s1 !== peg$FAILED) {
@@ -2994,12 +3075,12 @@ function peg$parse(input, options) {
2994
3075
  if (s6 !== peg$FAILED) {
2995
3076
  s7 = peg$parseHere();
2996
3077
  peg$savedPos = peg$currPos;
2997
- s8 = peg$f81(s4, s7);
3078
+ s8 = peg$f82(s4, s7);
2998
3079
  if (s8) s8 = void 0;
2999
3080
  else s8 = peg$FAILED;
3000
3081
  if (s8 !== peg$FAILED) {
3001
3082
  s9 = peg$currPos;
3002
- s10 = peg$f82(s4, s7);
3083
+ s10 = peg$f83(s4, s7);
3003
3084
  s11 = peg$currPos;
3004
3085
  s12 = [];
3005
3086
  if (s12.length >= (s10 | 0)) s13 = peg$FAILED;
@@ -3081,7 +3162,7 @@ function peg$parse(input, options) {
3081
3162
  if (s0 === peg$FAILED) {
3082
3163
  s0 = peg$currPos;
3083
3164
  peg$savedPos = peg$currPos;
3084
- s1 = peg$f83();
3165
+ s1 = peg$f84();
3085
3166
  if (s1) s1 = void 0;
3086
3167
  else s1 = peg$FAILED;
3087
3168
  if (s1 !== peg$FAILED) {
@@ -3172,7 +3253,7 @@ function peg$parse(input, options) {
3172
3253
  if (s0 === peg$FAILED) {
3173
3254
  s0 = peg$currPos;
3174
3255
  peg$savedPos = peg$currPos;
3175
- s1 = peg$f84();
3256
+ s1 = peg$f85();
3176
3257
  if (s1) s1 = void 0;
3177
3258
  else s1 = peg$FAILED;
3178
3259
  if (s1 !== peg$FAILED) {
@@ -3403,21 +3484,6 @@ function isBlockMacro(name) {
3403
3484
  return BLOCK_MACROS.has(name);
3404
3485
  }
3405
3486
  //#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
3487
  //#region node_modules/acorn/dist/acorn.mjs
3422
3488
  var astralIdentifierCodes = [
3423
3489
  509,
@@ -9239,6 +9305,14 @@ function* tokens(src, start, goal, options = {}) {
9239
9305
  function lexJs(src, handlers, goal = "expression") {
9240
9306
  return walkTokens(src, 0, handlers, 0, goal, false);
9241
9307
  }
9308
+ /**
9309
+ * Lex the template literal opening at `start` (a backtick) as `lexJs` does,
9310
+ * its interpolations one nesting level deeper. Returns the index just past
9311
+ * its closing backtick, or `src.length` if it is unterminated.
9312
+ */
9313
+ function lexTemplate(src, start, handlers = {}, nesting = 0) {
9314
+ return walkTokens(src, start, handlers, nesting, "expression", true);
9315
+ }
9242
9316
  function walkTokens(src, from, handlers, nesting, goal, oneTemplate) {
9243
9317
  const comments = [];
9244
9318
  const onComment = (_block, _text, s, e) => {
@@ -9356,6 +9430,22 @@ function regexEnd(src, start) {
9356
9430
  return -1;
9357
9431
  }
9358
9432
  /**
9433
+ * The value of `src` when it is exactly one `"…"` or `'…'` string literal,
9434
+ * as JavaScript reads it (`"\u0048all"` is `Hall`, `"\1"` is U+0001 in
9435
+ * the non-strict code passages run), otherwise `null`: other code, or a
9436
+ * literal that is not well-formed (`"\u{110000}"`).
9437
+ */
9438
+ function stringLiteralValue(src) {
9439
+ if (src[0] !== "\"" && src[0] !== "'") return null;
9440
+ try {
9441
+ const p = tokenizerAt(src, 0, "expression");
9442
+ p.nextToken();
9443
+ return p.type === types$1.string && p.end === src.length ? p.value : null;
9444
+ } catch {
9445
+ return null;
9446
+ }
9447
+ }
9448
+ /**
9359
9449
  * Scan the `"…"` or `'…'` string literal opening at `start`. `end` is the
9360
9450
  * index just past its closing quote, or `src.length` when it is unterminated
9361
9451
  * (`closed` false). A backslash escapes the character after it, so a quote
@@ -9591,6 +9681,98 @@ function parseMarkup(source, options = {}) {
9591
9681
  function tokenizeMarkup(source, options = {}) {
9592
9682
  return run(source, "Tokens", options);
9593
9683
  }
9684
+ /**
9685
+ * The `.class#id` selectors that start `source` at `at`, with the index just
9686
+ * past them and the one space that may follow (`end`; `at` if there are
9687
+ * none). A selector name may hold `{$name}` interpolations, as in markup.
9688
+ */
9689
+ function parseSelectors(source, at = 0) {
9690
+ const found = run(source.slice(at), "SelectorsPrefix", {});
9691
+ return {
9692
+ ...found,
9693
+ end: at + found.end
9694
+ };
9695
+ }
9696
+ /**
9697
+ * `tokenizeMarkup` for half-typed markup: the tokens it can read, and every
9698
+ * error, instead of throwing at the first. At a malformed tag, the tokens
9699
+ * before it are kept, its first character is read as text and tokenizing
9700
+ * resumes after it, so what follows is read as if the tag had not been
9701
+ * started. Tokens and errors have offsets in `source`; for well-formed
9702
+ * markup the tokens are those of `tokenizeMarkup` and there are no errors.
9703
+ */
9704
+ function tokenizeMarkupTolerant(source, options = {}) {
9705
+ const tokens = [];
9706
+ const errors = [];
9707
+ const lastBrace = source.lastIndexOf("}");
9708
+ const { closeBrace } = {
9709
+ ...defaultHooks,
9710
+ ...options.hooks
9711
+ };
9712
+ /** `text` shifted by `base`, tokenized: the tokens or the error. */
9713
+ const attempt = (text, base) => {
9714
+ const hooks = {
9715
+ ...options.hooks,
9716
+ closeBrace: (input, codeStart, lenientStart) => base + Math.min(codeStart, lenientStart) > lastBrace ? -1 : closeBrace(input, codeStart, lenientStart)
9717
+ };
9718
+ try {
9719
+ return tokenizeMarkup(text, {
9720
+ ...options,
9721
+ hooks
9722
+ }).map((token) => shift(token, base));
9723
+ } catch (error) {
9724
+ if (!(error instanceof MarkupError)) throw error;
9725
+ return error;
9726
+ }
9727
+ };
9728
+ let base = 0;
9729
+ while (base <= source.length) {
9730
+ const rest = source.slice(base);
9731
+ const result = attempt(rest, base);
9732
+ if (Array.isArray(result)) {
9733
+ tokens.push(...result);
9734
+ break;
9735
+ }
9736
+ const at = base + result.offset;
9737
+ const { line, column } = lineColumn(source, at);
9738
+ errors.push(new MarkupError(result.reason, at, line, column));
9739
+ let cut = result.offset;
9740
+ let prefix = attempt(rest.slice(0, cut), base);
9741
+ while (!Array.isArray(prefix)) {
9742
+ cut = prefix.offset;
9743
+ prefix = attempt(rest.slice(0, cut), base);
9744
+ }
9745
+ tokens.push(...prefix);
9746
+ if (cut < rest.length) {
9747
+ const start = base + cut;
9748
+ tokens.push({
9749
+ type: "text",
9750
+ value: rest[cut],
9751
+ start,
9752
+ end: start + 1
9753
+ });
9754
+ }
9755
+ base += cut + 1;
9756
+ }
9757
+ return {
9758
+ tokens,
9759
+ errors
9760
+ };
9761
+ }
9762
+ /** `token` with its offsets moved `by` on. */
9763
+ function shift(token, by) {
9764
+ if (by === 0) return token;
9765
+ const moved = {
9766
+ ...token,
9767
+ start: token.start + by,
9768
+ end: token.end + by
9769
+ };
9770
+ if (moved.type === "link") {
9771
+ moved.targetStart += by;
9772
+ moved.targetEnd += by;
9773
+ }
9774
+ return moved;
9775
+ }
9594
9776
  //#endregion
9595
9777
  //#region src/utils/parse-delay.ts
9596
9778
  /**
@@ -9618,6 +9800,15 @@ function errorMessage(err, fallback) {
9618
9800
  return fallback ?? String(err);
9619
9801
  }
9620
9802
  //#endregion
9803
+ //#region src/runtime-errors.ts
9804
+ /**
9805
+ * The error for `name`, a passage name that names no passage, used in the
9806
+ * passage `from`.
9807
+ */
9808
+ function noPassageError(name, from) {
9809
+ return /* @__PURE__ */ new Error(`No passage named ${JSON.stringify(name)} (in passage ${JSON.stringify(from)})`);
9810
+ }
9811
+ //#endregion
9621
9812
  //#region src/components/macros/arg-utils.ts
9622
9813
  /**
9623
9814
  * Shared helpers for macros that split or read their raw argument text by
@@ -9720,6 +9911,67 @@ function topLevelIndices(src, isSeparator) {
9720
9911
  } });
9721
9912
  return indices;
9722
9913
  }
9914
+ /**
9915
+ * Split `src` at every depth-0 separator (see `topLevelIndices`). Segments
9916
+ * are returned as written, untrimmed, and empty segments are kept, so
9917
+ * `n` separators always give `n + 1` segments.
9918
+ */
9919
+ function splitTopLevel(src, isSeparator) {
9920
+ const segments = [];
9921
+ let from = 0;
9922
+ for (const i of topLevelIndices(src, isSeparator)) {
9923
+ segments.push(src.slice(from, i));
9924
+ from = i + 1;
9925
+ }
9926
+ segments.push(src.slice(from));
9927
+ return segments;
9928
+ }
9929
+ /**
9930
+ * Check whether a whitespace-delimited token looks like a standalone value
9931
+ * (not an operator or partial expression): it starts like a value and does
9932
+ * not end with an operator still waiting for its operand.
9933
+ */
9934
+ function isStandaloneValue(token) {
9935
+ return startsLikeValue(token) && !endsWithOperator(token);
9936
+ }
9937
+ function startsLikeValue(token) {
9938
+ const first = token[0];
9939
+ if (first === "\"" || first === "'" || first === "`") return true;
9940
+ if (/^[$_@]\w|^%[A-Za-z_]/.test(token)) return true;
9941
+ if (/\d/.test(first)) return true;
9942
+ if ((first === "-" || first === "+") && token.length > 1 && /\d/.test(token[1])) return true;
9943
+ if (first === "(" || first === "[" || first === "{") return true;
9944
+ if (/^(?:true|false|null|undefined)(?![\w$])/.test(token)) return true;
9945
+ if (first === "!" && token.length > 1 && token[1] !== "=") return true;
9946
+ return false;
9947
+ }
9948
+ /**
9949
+ * Try to split a raw string on whitespace at depth 0 (respecting strings,
9950
+ * template literals, parentheses, brackets, and braces). Each resulting token
9951
+ * must pass `isStandaloneValue()` or the split is rejected and `null` is
9952
+ * returned.
9953
+ */
9954
+ function trySplitOnWhitespace(raw) {
9955
+ const args = splitTopLevel(raw, isWhitespace).filter(Boolean);
9956
+ if (args.length < 2) return null;
9957
+ for (const arg of args) if (!isStandaloneValue(arg)) return null;
9958
+ return args;
9959
+ }
9960
+ /**
9961
+ * Split rawArgs by commas, respecting parentheses, brackets, braces, and
9962
+ * strings. When no top-level commas are present, also supports adjacent quoted
9963
+ * string literals separated by whitespace (e.g. `"Label" "target"`).
9964
+ */
9965
+ function splitArgs(raw) {
9966
+ const args = splitTopLevel(raw, (ch) => ch === ",").map((a) => a.trim());
9967
+ const hasComma = args.length > 1;
9968
+ if (args[args.length - 1] === "") args.pop();
9969
+ if (!hasComma && args.length === 1) {
9970
+ const split = trySplitOnWhitespace(args[0]);
9971
+ if (split) return split;
9972
+ }
9973
+ return args;
9974
+ }
9723
9975
  //#endregion
9724
9976
  //#region src/components/macros/macro-args.ts
9725
9977
  /**
@@ -9762,11 +10014,13 @@ function readText(src) {
9762
10014
  */
9763
10015
  var MacroArgumentError = class extends Error {
9764
10016
  constructor(param, src) {
9765
- super(`The ${param.name} must be a quoted string ("…" or '…'), not ${src}`);
10017
+ super(`The ${param.name} must be a quoted string ("…" or '…'), ${src ? `not ${src}` : "but it has no value"}`);
9766
10018
  this.name = "MacroArgumentError";
9767
10019
  }
9768
10020
  };
9769
- function readValue(param, src) {
10021
+ /** Read `src`, at `place`, as `param` says. */
10022
+ function readValue(param, src, place) {
10023
+ place.spans?.set(param, [place.at, place.at + src.length]);
9770
10024
  switch (param.type) {
9771
10025
  case "string": {
9772
10026
  const value = readWholeQuoted(src);
@@ -9777,7 +10031,7 @@ function readValue(param, src) {
9777
10031
  case "names": return src.split(",").map((name) => name.trim());
9778
10032
  case "delay": return parseDelay(src);
9779
10033
  case "number": return Number(src);
9780
- case "options": return readOptions(param.parameters ?? [], src);
10034
+ case "options": return readOptions(param.parameters ?? [], src, place);
9781
10035
  default: return src;
9782
10036
  }
9783
10037
  }
@@ -9787,6 +10041,7 @@ function unset(param) {
9787
10041
  }
9788
10042
  var WORD_RE = /\w+/y;
9789
10043
  var DIGITS_RE = /\d+/y;
10044
+ var WORD_OR_PUNCT_RE = /\S+/y;
9790
10045
  /** Match the sticky regex `re` at `pos`, returning the matched text. */
9791
10046
  function matchAt(re, src, pos) {
9792
10047
  re.lastIndex = pos;
@@ -9794,10 +10049,12 @@ function matchAt(re, src, pos) {
9794
10049
  }
9795
10050
  /**
9796
10051
  * Read keyword options (`goto "X" priority 5 once`). A keyword takes the
9797
- * quoted string or digit run after it as its value; keywords that aren't
9798
- * declared, and their values, are skipped.
10052
+ * quoted string (with or without whitespace before it) or digit run after
10053
+ * it as its value; keywords that aren't declared, and their values, are
10054
+ * skipped. A keyword of a `string` parameter must be followed by a quoted
10055
+ * string.
9799
10056
  */
9800
- function readOptions(params, src) {
10057
+ function readOptions(params, src, { at, spans }) {
9801
10058
  const options = {};
9802
10059
  let i = 0;
9803
10060
  while (i < src.length) {
@@ -9810,18 +10067,18 @@ function readOptions(params, src) {
9810
10067
  let val;
9811
10068
  let j = i;
9812
10069
  while (j < src.length && isWhitespace(src[j])) j++;
9813
- if (j > i) {
9814
- const quoted = readQuoted(src, j);
9815
- const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
9816
- if (quoted) {
9817
- val = quoted.value;
9818
- i = quoted.end;
9819
- } else if (digits) {
9820
- val = digits;
9821
- i = j + digits.length;
9822
- }
10070
+ const quoted = readQuoted(src, j);
10071
+ const digits = quoted || j === i ? null : matchAt(DIGITS_RE, src, j);
10072
+ if (quoted) {
10073
+ val = quoted.value;
10074
+ i = quoted.end;
10075
+ } else if (digits) {
10076
+ val = digits;
10077
+ i = j + digits.length;
9823
10078
  }
9824
10079
  const param = params.find((p) => p.name === key);
10080
+ if (param && param.type !== "flag" && val !== void 0) spans?.set(param, [at + j, at + i]);
10081
+ if (param?.type === "string" && !quoted) throw new MacroArgumentError(param, matchAt(WORD_OR_PUNCT_RE, src, j) ?? "");
9825
10082
  if (param?.type === "flag") options[key] = true;
9826
10083
  else if (param?.type === "number") options[key] = Number(val ?? 0);
9827
10084
  else if (param) options[key] = val;
@@ -9831,21 +10088,21 @@ function readOptions(params, src) {
9831
10088
  /** `inline %name` reads a transient variable; `inline % 2` is modulo. */
9832
10089
  var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
9833
10090
  /**
9834
- * The rest of `src` without the flag `word` as its last or first term, or
9835
- * `null` if it has no such flag. Next to a binary operator the word is an
9836
- * operand (`"a" + inline`), not a flag; the closing `/` of a regex literal
9837
- * is not one.
10091
+ * Where the rest of `src` is without the flag `word` as its last or first
10092
+ * term, or `null` if it has no such flag. Next to a binary operator the
10093
+ * word is an operand (`"a" + inline`), not a flag; the closing `/` of a
10094
+ * regex literal is not one.
9838
10095
  */
9839
10096
  function takeFlag(src, word) {
9840
10097
  const spans = terms(src);
9841
10098
  if (spans.length < 2) return null;
9842
10099
  if (slice(src, spans[spans.length - 1]) === word) {
9843
- const rest = src.slice(0, spans[spans.length - 2][1]);
9844
- if (!endsWithOperator(rest)) return rest;
10100
+ const rest = [0, spans[spans.length - 2][1]];
10101
+ if (!endsWithOperator(slice(src, rest))) return rest;
9845
10102
  }
9846
10103
  if (slice(src, spans[0]) === word) {
9847
- const rest = src.slice(spans[1][0]);
9848
- if (!LEADING_OPERATOR_RE.test(rest)) return rest;
10104
+ const rest = [spans[1][0], src.length];
10105
+ if (!LEADING_OPERATOR_RE.test(slice(src, rest))) return rest;
9849
10106
  }
9850
10107
  return null;
9851
10108
  }
@@ -9873,60 +10130,112 @@ function findSeparator(src, word) {
9873
10130
  return null;
9874
10131
  }
9875
10132
  var takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
9876
- /** Read the parameters of one group from `src` (`null`: no text) into `args`. */
9877
- function readGroup(params, src, args) {
9878
- let text = src?.trim() ?? "";
10133
+ /** The text of `src` without the whitespace around it, and where it is. */
10134
+ function trimmed(src, at) {
10135
+ const text = src.trim();
10136
+ return [text, text ? at + src.indexOf(text) : at];
10137
+ }
10138
+ /**
10139
+ * Read the parameters of one group from `src` (`null`: no text), which is
10140
+ * at `at` in the raw arguments, into `args`.
10141
+ */
10142
+ function readGroup(params, src, args, { at: srcAt, spans }) {
10143
+ let [text, at] = trimmed(src ?? "", srcAt);
9879
10144
  for (const flag of params.filter((p) => p.type === "flag")) {
9880
10145
  const rest = src === null ? null : takeFlag(text, flag.name);
9881
10146
  args[flag.name] = rest !== null;
9882
- text = rest ?? text;
10147
+ if (rest) [text, at] = trimmed(slice(text, rest), at + rest[0]);
9883
10148
  }
9884
10149
  const positional = params.filter((p) => p.type !== "flag");
9885
10150
  if (positional.length === 0) return;
9886
10151
  const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
9887
10152
  const restIndex = lastText < 0 ? positional.length - 1 : lastText;
9888
- const read = (param, value) => value === "" ? unset(param) : readValue(param, value);
10153
+ /** Read `param` from the text from `start` to `end`. */
10154
+ const read = (param, [start, end]) => start === end ? unset(param) : readValue(param, text.slice(start, end), {
10155
+ at: at + start,
10156
+ spans
10157
+ });
9889
10158
  if (positional.length === 1) {
9890
- args[positional[0].name] = read(positional[0], text);
10159
+ args[positional[0].name] = read(positional[0], [0, text.length]);
9891
10160
  return;
9892
10161
  }
9893
- const spans = terms(text);
10162
+ const parts = terms(text);
9894
10163
  let from = 0;
9895
- let to = spans.length;
9896
- for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, slice(text, spans[from++])) : unset(param);
9897
- const readTrailing = (param, value) => {
10164
+ let to = parts.length;
10165
+ for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, parts[from++]) : unset(param);
10166
+ const readTrailing = (param, span) => {
9898
10167
  try {
9899
- return read(param, value);
10168
+ return read(param, span);
9900
10169
  } catch (error) {
9901
10170
  if (error instanceof MacroArgumentError) return void 0;
9902
10171
  throw error;
9903
10172
  }
9904
10173
  };
9905
10174
  for (const param of positional.slice(restIndex + 1).reverse()) {
9906
- const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? readTrailing(param, slice(text, spans[to - 1])) : void 0;
10175
+ const value = from < to && !endsWithOperator(text.slice(0, parts[to - 1][0])) ? readTrailing(param, parts[to - 1]) : void 0;
9907
10176
  args[param.name] = value ?? unset(param);
9908
10177
  if (value !== void 0) to--;
10178
+ else spans?.delete(param);
9909
10179
  }
9910
10180
  const rest = positional[restIndex];
9911
- args[rest.name] = from < to ? read(rest, text.slice(spans[from][0], spans[to - 1][1])) : unset(rest);
10181
+ args[rest.name] = from < to ? read(rest, [parts[from][0], parts[to - 1][1]]) : unset(rest);
9912
10182
  }
9913
- /** Read `rawArgs` into the declared `parameters` (see above). */
9914
- function parseMacroArgs(rawArgs, parameters) {
10183
+ /**
10184
+ * Read `rawArgs` into the declared `parameters` (see above). With `spans`,
10185
+ * note there where each argument set was read.
10186
+ */
10187
+ function parseMacroArgs(rawArgs, parameters, spans) {
9915
10188
  const args = {};
9916
- let rest = rawArgs.trim();
10189
+ let [rest, restAt] = trimmed(rawArgs, 0);
9917
10190
  let groupStart = 0;
9918
10191
  parameters.forEach((separator, i) => {
9919
10192
  if (separator.type !== "separator") return;
9920
10193
  const at = rest === null ? null : findSeparator(rest, separator.name);
9921
10194
  args[separator.name] = at !== null;
9922
10195
  const before = at ? rest.slice(0, at[0]) : rest;
9923
- readGroup(parameters.slice(groupStart, i), before, args);
10196
+ readGroup(parameters.slice(groupStart, i), before, args, {
10197
+ at: restAt,
10198
+ spans
10199
+ });
9924
10200
  rest = at ? rest.slice(at[1]) : null;
10201
+ if (at) restAt += at[1];
9925
10202
  groupStart = i + 1;
9926
10203
  });
9927
- readGroup(parameters.slice(groupStart), rest, args);
10204
+ readGroup(parameters.slice(groupStart), rest, args, {
10205
+ at: restAt,
10206
+ spans
10207
+ });
9928
10208
  return args;
9929
10209
  }
10210
+ /**
10211
+ * Read a `passage` argument as written (`{goto "Hall"}`, `{goto $room}`): a
10212
+ * string literal is the name JavaScript reads from it (`"\u0048all"` is
10213
+ * `Hall`), anything else (a literal that is not well-formed too) an
10214
+ * expression, whose value is the name when it
10215
+ * runs (see evaluatePassageName).
10216
+ */
10217
+ function passageTarget(arg) {
10218
+ const expression = arg.trim();
10219
+ const name = stringLiteralValue(expression);
10220
+ return name === null ? {
10221
+ kind: "expression",
10222
+ expression
10223
+ } : {
10224
+ kind: "name",
10225
+ name
10226
+ };
10227
+ }
10228
+ /**
10229
+ * The passage a `passage` argument names (`{goto "Hall"}`, `{goto $room}`):
10230
+ * the value of its expression. Throws what evaluating it throws, and if no
10231
+ * passage of the story `state` holds has that name, an error naming it and
10232
+ * the current passage.
10233
+ */
10234
+ function evaluatePassageName(expr, evaluate, { storyData, currentPassage }) {
10235
+ const name = String(evaluate(expr ?? ""));
10236
+ if (storyData && !storyData.passages.has(name)) throw noPassageError(name, currentPassage);
10237
+ return name;
10238
+ }
9930
10239
  //#endregion
9931
10240
  //#region src/widgets/widget-def.ts
9932
10241
  /**
@@ -9937,10 +10246,12 @@ function parseMacroArgs(rawArgs, parameters) {
9937
10246
  var WIDGET_PARAMETERS = [{
9938
10247
  name: "name",
9939
10248
  type: "text",
10249
+ holds: "text",
9940
10250
  required: true
9941
10251
  }, {
9942
10252
  name: "parameters",
9943
- type: "text"
10253
+ type: "text",
10254
+ holds: "text"
9944
10255
  }];
9945
10256
  /**
9946
10257
  * The widget a {widget} definition's arguments declare: its name and its
@@ -9972,6 +10283,17 @@ function blockWidgetNames(passages) {
9972
10283
  }
9973
10284
  return names;
9974
10285
  }
10286
+ /** The parameters of the built-in sub-macros that take arguments. */
10287
+ var SUB_MACRO_PARAMETERS = new Map([["option", [{
10288
+ name: "value",
10289
+ type: "string",
10290
+ holds: "text",
10291
+ required: true
10292
+ }]]]);
10293
+ /** The parameters of the built-in sub-macro `name`, if it takes any. */
10294
+ function subMacroParameters(name) {
10295
+ return SUB_MACRO_PARAMETERS.get(name.toLowerCase());
10296
+ }
9975
10297
  //#endregion
9976
10298
  //#region src/code-check.ts
9977
10299
  /** While a pass runs: the code it parsed so far, by goal and source. */
@@ -10009,21 +10331,59 @@ var CONDITION_MACROS = new Set([
10009
10331
  "elseif",
10010
10332
  "case"
10011
10333
  ]);
10012
- /** Code inside the quoted strings of a macro's arguments, by parameter. */
10013
- var CODE_IN_STRINGS = { watch: {
10014
- condition: "expression",
10015
- run: "statements"
10016
- } };
10017
- /** The `string` parameters of a macro that name a passage. */
10018
- var PASSAGE_STRINGS = { watch: ["goto", "dialog"] };
10019
10334
  /** Block macros whose body is the name of a passage. */
10020
10335
  var PASSAGE_BODIES = new Set(["dialog"]);
10336
+ /**
10337
+ * `params` with each `string` and `text` parameter (options too) saying
10338
+ * what it holds: what it declares, else `holds`.
10339
+ */
10340
+ function withHolds(params, holds) {
10341
+ return params.map((param) => {
10342
+ if (param.parameters) return {
10343
+ ...param,
10344
+ parameters: withHolds(param.parameters, holds)
10345
+ };
10346
+ return (param.type === "string" || param.type === "text") && !param.holds ? {
10347
+ ...param,
10348
+ holds
10349
+ } : param;
10350
+ });
10351
+ }
10352
+ /**
10353
+ * The declared parameters of `macros`, by name in any case, and of the
10354
+ * built-in sub-macros (`{option}`): what the story-start check, reference
10355
+ * collection and tooling read arguments by. Each `string` and `text`
10356
+ * parameter says what it holds: what it declares, else `markup` for a macro
10357
+ * with `interpolate` (which can resolve markup) and `text` for any other.
10358
+ */
10359
+ function parameterLookup(macros) {
10360
+ const parameters = new NameMap();
10361
+ for (const { name, parameters: params, interpolate } of macros) if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
10362
+ return (name) => {
10363
+ let params = parameters.get(name);
10364
+ if (!params) {
10365
+ const sub = subMacroParameters(name);
10366
+ if (sub) parameters.set(name, params = withHolds(sub, "text"));
10367
+ }
10368
+ return params;
10369
+ };
10370
+ }
10371
+ /** What a `string` or `text` argument holds when its parameter doesn't say. */
10372
+ var holdsByDefault = (interpolate) => interpolate ? "markup" : "text";
10021
10373
  /** The goal of the code an argument of this type holds, if it is code. */
10022
10374
  function codeGoal(param) {
10023
10375
  if (param.type === "expression" || param.type === "passage") return "expression";
10024
10376
  if (param.type === "statements") return "statements";
10025
10377
  }
10026
10378
  /**
10379
+ * What a `string` or `text` argument holds: what its parameter says (see
10380
+ * parameterLookup), else markup, which is checked.
10381
+ */
10382
+ function stringHolds(param) {
10383
+ if (param.type !== "string" && param.type !== "text") return void 0;
10384
+ return param.holds ?? "markup";
10385
+ }
10386
+ /**
10027
10387
  * The code and the markup-holding text in `tokens`, the tokens of `src`.
10028
10388
  * `parametersOf` gives the declared parameters of a macro, if it has any.
10029
10389
  */
@@ -10035,7 +10395,9 @@ function* codeAndText(src, tokens, parametersOf) {
10035
10395
  if (token.type === "link") yield {
10036
10396
  kind: "passage",
10037
10397
  name: token.target,
10038
- offset: locate(src, token.target, token.start),
10398
+ offset: token.targetStart,
10399
+ length: token.targetEnd - token.targetStart,
10400
+ macro: "link",
10039
10401
  label: src.slice(token.start, token.end)
10040
10402
  };
10041
10403
  else if (token.type === "expression") yield {
@@ -10045,32 +10407,35 @@ function* codeAndText(src, tokens, parametersOf) {
10045
10407
  goal: "expression",
10046
10408
  label: `{${token.expression}}`
10047
10409
  };
10048
- else if (token.type === "html") for (const [name, value] of Object.entries(token.attributes)) {
10049
- const at = locate(src, value, token.start);
10050
- if (!isCodeAttribute(name)) {
10051
- yield {
10052
- kind: "text",
10053
- text: value,
10054
- offset: at,
10055
- where: `In the ${name} attribute of <${token.tag}>: `
10056
- };
10057
- continue;
10058
- }
10059
- let from = 0;
10060
- for (const part of splitSigilTemplate(value)) {
10061
- if (!("expr" in part)) continue;
10062
- const k = locate(value, part.expr, from);
10063
- from = k + part.expr.length;
10064
- yield {
10065
- kind: "code",
10066
- code: part.expr,
10067
- offset: at + k,
10068
- goal: "expression",
10069
- label: `{${part.expr}} in the ${name} attribute of <${token.tag}>`
10070
- };
10410
+ else if (token.type === "html") {
10411
+ let cursor = token.start;
10412
+ for (const [name, value] of Object.entries(token.attributes)) {
10413
+ const at = locate(src, value, cursor);
10414
+ cursor = at + value.length;
10415
+ if (!isCodeAttribute(name)) {
10416
+ yield {
10417
+ kind: "text",
10418
+ text: value,
10419
+ offset: at,
10420
+ where: `In the ${name} attribute of <${token.tag}>: `
10421
+ };
10422
+ continue;
10423
+ }
10424
+ let from = 0;
10425
+ for (const part of splitSigilTemplate(value)) {
10426
+ if (!("expr" in part)) continue;
10427
+ const k = locate(value, part.expr, from);
10428
+ from = k + part.expr.length;
10429
+ yield {
10430
+ kind: "code",
10431
+ code: part.expr,
10432
+ offset: at + k,
10433
+ goal: "expression",
10434
+ label: `{${part.expr}} in the ${name} attribute of <${token.tag}>`
10435
+ };
10436
+ }
10071
10437
  }
10072
- }
10073
- else if (token.type === "macro" && !token.isClose) {
10438
+ } else if (token.type === "macro" && !token.isClose) {
10074
10439
  const name = token.name.toLowerCase();
10075
10440
  if (name === "do") {
10076
10441
  const close = closeOf(t, name);
@@ -10090,18 +10455,22 @@ function* codeAndText(src, tokens, parametersOf) {
10090
10455
  const close = closeOf(t, name);
10091
10456
  const body = tokens.slice(t + 1, close);
10092
10457
  if (close > t + 1 && body.every((b) => b.type === "text")) {
10093
- const passage = src.slice(token.end, tokens[close].start).trim().replace(/^["']|["']$/g, "");
10458
+ const text = src.slice(token.end, tokens[close].start);
10459
+ const written = text.trim();
10094
10460
  yield {
10095
10461
  kind: "passage",
10096
- name: passage,
10097
- offset: locate(src, passage, token.end),
10462
+ name: written.replace(/^["']|["']$/g, ""),
10463
+ offset: token.end + text.indexOf(written),
10464
+ length: written.length,
10465
+ macro: name,
10098
10466
  label: src.slice(token.start, token.end)
10099
10467
  };
10100
10468
  }
10101
10469
  }
10102
10470
  if (!args) continue;
10103
10471
  const label = `{${token.name} ${args}}`;
10104
- const argsAt = locate(src, args, token.start + 1);
10472
+ const tag = src.slice(token.start, token.end - 1);
10473
+ const argsAt = token.start + tag.trimEnd().length - args.length;
10105
10474
  if (CONDITION_MACROS.has(name)) {
10106
10475
  yield {
10107
10476
  kind: "code",
@@ -10120,8 +10489,9 @@ function* codeAndText(src, tokens, parametersOf) {
10120
10489
  /** The code and text in the arguments `args` (at `offset`) of `macro`. */
10121
10490
  function* argPieces(args, offset, params, macro, label) {
10122
10491
  let values;
10492
+ const spans = /* @__PURE__ */ new Map();
10123
10493
  try {
10124
- values = parseMacroArgs(args, params);
10494
+ values = parseMacroArgs(args, params, spans);
10125
10495
  } catch (error) {
10126
10496
  if (!(error instanceof MacroArgumentError)) throw error;
10127
10497
  yield {
@@ -10132,9 +10502,7 @@ function* argPieces(args, offset, params, macro, label) {
10132
10502
  };
10133
10503
  return;
10134
10504
  }
10135
- const inStrings = CODE_IN_STRINGS[macro.toLowerCase()] ?? {};
10136
- const passageStrings = PASSAGE_STRINGS[macro.toLowerCase()] ?? [];
10137
- let cursor = 0;
10505
+ const name = macro.toLowerCase();
10138
10506
  /** The pieces of the parameters `list`, with their values in `from`. */
10139
10507
  function* visit(list, from) {
10140
10508
  for (const param of list) {
@@ -10143,32 +10511,57 @@ function* argPieces(args, offset, params, macro, label) {
10143
10511
  yield* visit(param.parameters, value ?? {});
10144
10512
  continue;
10145
10513
  }
10146
- if (typeof value !== "string" || !value.trim()) continue;
10147
- const at = locate(args, value, cursor);
10148
- if (at >= cursor) cursor = at + value.length;
10149
- const goal = codeGoal(param) ?? inStrings[param.name];
10514
+ const span = spans.get(param);
10515
+ if (typeof value !== "string" || !value.trim() || !span) continue;
10516
+ const [start, end] = span;
10517
+ const written = args.slice(start, end);
10518
+ /** The name as written (quotes included), at the argument. */
10519
+ const passage = (passageName) => ({
10520
+ kind: "passage",
10521
+ name: passageName,
10522
+ offset: offset + start,
10523
+ length: end - start,
10524
+ macro: name,
10525
+ label
10526
+ });
10527
+ const goal = codeGoal(param);
10528
+ if (param.type === "passage") {
10529
+ const target = passageTarget(value);
10530
+ if (target.kind === "name") {
10531
+ yield passage(target.name);
10532
+ continue;
10533
+ }
10534
+ }
10150
10535
  if (goal) {
10151
10536
  const piece = {
10152
10537
  kind: "code",
10153
10538
  code: value,
10154
- offset: offset + at,
10539
+ offset: offset + start,
10155
10540
  goal,
10156
10541
  label
10157
10542
  };
10158
- if (param.type === "passage") piece.passage = true;
10543
+ if (param.type === "passage") {
10544
+ piece.passage = true;
10545
+ piece.macro = name;
10546
+ }
10159
10547
  yield piece;
10548
+ continue;
10160
10549
  }
10161
- const name = param.type === "passage" ? readWholeQuoted(value.trim()) : passageStrings.includes(param.name) ? value : null;
10162
- if (name !== null) yield {
10163
- kind: "passage",
10164
- name,
10165
- offset: offset + at,
10166
- label
10550
+ const at = offset + start + (value !== written && /^["']/.test(written) ? 1 : 0);
10551
+ const holds = stringHolds(param);
10552
+ if (holds === "expression" || holds === "statements") yield {
10553
+ kind: "code",
10554
+ code: value,
10555
+ offset: at,
10556
+ goal: holds,
10557
+ label,
10558
+ inString: true
10167
10559
  };
10168
- if (!goal && (param.type === "text" || param.type === "string")) yield {
10560
+ else if (holds === "passage") yield passage(value);
10561
+ else if (holds === "markup") yield {
10169
10562
  kind: "text",
10170
10563
  text: value,
10171
- offset: offset + at,
10564
+ offset: at,
10172
10565
  where: `In the ${param.name} of {${macro}}: `
10173
10566
  };
10174
10567
  }
@@ -10185,12 +10578,37 @@ function locate(text, part, from) {
10185
10578
  const anywhere = text.indexOf(part);
10186
10579
  return anywhere >= 0 ? anywhere : from;
10187
10580
  }
10188
- /** The parameters of the built-in sub-macros that take arguments. */
10189
- var SUB_MACRO_PARAMETERS = { option: [{
10190
- name: "value",
10191
- type: "string",
10192
- required: true
10193
- }] };
10581
+ /**
10582
+ * The passages the markup `src` (with the `tokens` it tokenizes to) names:
10583
+ * `[[…]]` links, the passage argument of `{goto}`, `{include}` and `{link}`
10584
+ * (and of macros that declare one), the `goto` and `dialog` actions of
10585
+ * `{watch}` (and the `string` and `text` arguments of macros that declare
10586
+ * they hold one) and the body of `{dialog}`, in source order. A name written
10587
+ * out is a literal; the others are expressions, which name a passage when
10588
+ * they run.
10589
+ */
10590
+ function collectPassageReferences(src, tokens, parametersOf) {
10591
+ const refs = [];
10592
+ for (const piece of codeAndText(src, tokens, parametersOf)) if (piece.kind === "passage") refs.push({
10593
+ macro: piece.macro,
10594
+ target: {
10595
+ kind: "name",
10596
+ name: piece.name
10597
+ },
10598
+ start: piece.offset,
10599
+ end: piece.offset + piece.length
10600
+ });
10601
+ else if (piece.kind === "code" && piece.passage) refs.push({
10602
+ macro: piece.macro,
10603
+ target: {
10604
+ kind: "expression",
10605
+ expression: piece.code
10606
+ },
10607
+ start: piece.offset,
10608
+ end: piece.offset + piece.code.length
10609
+ });
10610
+ return refs;
10611
+ }
10194
10612
  //#endregion
10195
10613
  //#region src/markup/validate.ts
10196
10614
  /**
@@ -10200,8 +10618,15 @@ var SUB_MACRO_PARAMETERS = { option: [{
10200
10618
  * line and column they are at, instead of failing when the passage renders.
10201
10619
  * Tooling runs it too (see src/tooling.ts).
10202
10620
  */
10203
- /** Passages that hold no markup. */
10204
- var NOT_MARKUP = new Set(["StoryVariables", "StoryTransients"]);
10621
+ /**
10622
+ * Passages that hold no markup: declarations, and the JavaScript function
10623
+ * body of SaveTitle.
10624
+ */
10625
+ var NOT_MARKUP = new Set([
10626
+ "StoryVariables",
10627
+ "StoryTransients",
10628
+ "SaveTitle"
10629
+ ]);
10205
10630
  var NOT_MARKUP_TAGS = ["script", "stylesheet"];
10206
10631
  /** Branch macros, which their parent macro renders. */
10207
10632
  var BRANCH_MACROS = new Set([
@@ -10312,7 +10737,7 @@ function validateMarkup(passages, options) {
10312
10737
  report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
10313
10738
  }
10314
10739
  };
10315
- const parametersOf = (name) => options.parametersOf?.(name) ?? SUB_MACRO_PARAMETERS[name.toLowerCase()];
10740
+ const parametersOf = options.parametersOf ?? (() => void 0);
10316
10741
  /**
10317
10742
  * Check `tokens`, the tokens of `src`, which starts at `base` in the
10318
10743
  * passage: their macros, the code in them and the markup in their text.
@@ -10358,6 +10783,83 @@ function validateMarkup(passages, options) {
10358
10783
  for (const [passage, tokens] of parsed) withParseCache(() => checkTokens(passage, passage.content, tokens, 0, ""));
10359
10784
  return diagnostics;
10360
10785
  }
10786
+ new NameMap();
10787
+ new NameMap();
10788
+ new NameSet();
10789
+ /**
10790
+ * How a macro argument is read (see components/macros/macro-args.ts). Every
10791
+ * declared parameter names one: there is no default.
10792
+ * Quoted strings accept `\"`, `\'` and `\\` escapes.
10793
+ * - `expression`: code, as written.
10794
+ * - `statements`: code run as statements (`{set}`), as written.
10795
+ * - `passage`: a passage name: a quoted string or an expression, as written.
10796
+ * - `variable`: a variable reference such as `$name` or `"$name"`, as written.
10797
+ * - `string`: one quoted string; anything else leaves the argument unset.
10798
+ * - `text`: one quoted string, or text with any loose quotes stripped.
10799
+ * - `names`: a comma-separated list of names (`@item, @i`).
10800
+ * - `delay`: a duration (`2s`, `500ms`, `300`) in milliseconds.
10801
+ * - `number`: a number.
10802
+ * - `flag`: a keyword, the parameter's name, at the start or end; a boolean.
10803
+ * - `separator`: a word (`of`) or `=` separating the parameters before it
10804
+ * from those after it; a boolean.
10805
+ * - `options`: keywords, the names of its `parameters`, each followed by a
10806
+ * quoted string or a number unless it is a flag.
10807
+ */
10808
+ var PARAMETER_TYPES = [
10809
+ "expression",
10810
+ "statements",
10811
+ "passage",
10812
+ "variable",
10813
+ "string",
10814
+ "text",
10815
+ "names",
10816
+ "delay",
10817
+ "number",
10818
+ "flag",
10819
+ "separator",
10820
+ "options"
10821
+ ];
10822
+ /**
10823
+ * What the value of a `string` or `text` argument holds, for the check at
10824
+ * story start and for tooling (see code-check.ts):
10825
+ * - `markup`: markup the macro renders (`{button}`'s label): its markup
10826
+ * is checked.
10827
+ * - `text`: plain text the macro uses as written (`{checkbox}`'s label).
10828
+ * - `passage`: a passage name (`{watch}`'s `goto`): the passage must exist.
10829
+ * - `expression`, `statements`: code (`{watch}`'s condition and `run`): it
10830
+ * is checked as code, and its variable references against the schema.
10831
+ *
10832
+ * Without it, the argument of a macro with `interpolate` holds `markup`,
10833
+ * any other `text`.
10834
+ */
10835
+ var STRING_HOLDS = [
10836
+ "markup",
10837
+ "text",
10838
+ "passage",
10839
+ "expression",
10840
+ "statements"
10841
+ ];
10842
+ /**
10843
+ * Throw if a parameter `macro` declares (or an option of one) has no type,
10844
+ * or one that isn't a ParameterType: arguments are read by their type, and
10845
+ * there is no default. Throw too if it declares what it `holds` wrongly.
10846
+ */
10847
+ function checkParameterTypes(macro, parameters) {
10848
+ const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
10849
+ for (const param of parameters) {
10850
+ const type = param.type;
10851
+ if (!PARAMETER_TYPES.includes(type)) throw parameterError(macro, param, type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`, `Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
10852
+ const holds = param.holds;
10853
+ if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
10854
+ if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
10855
+ if (param.parameters) checkParameterTypes(macro, param.parameters);
10856
+ }
10857
+ }
10858
+ /** The error for the parameter `param` of `macro`: what is wrong, and the fix. */
10859
+ function parameterError(macro, param, problem, fix) {
10860
+ return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
10861
+ }
10862
+ new NameMap();
10361
10863
  //#endregion
10362
10864
  //#region src/utils/namespace.ts
10363
10865
  /** The variable name no namespace can hold. */
@@ -10448,58 +10950,84 @@ function parseStoryVariables(content, sigil = "$") {
10448
10950
  }
10449
10951
  return schema;
10450
10952
  }
10451
- new NameMap();
10452
- new NameMap();
10453
- new NameSet();
10953
+ //#endregion
10954
+ //#region src/components/macros/include-args.ts
10454
10955
  /**
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.
10956
+ * The parameters of `{include}`. A standalone `inline` flag only counts
10957
+ * outside quotes and brackets, so passage names and expressions containing
10958
+ * the word stay intact (#201).
10472
10959
  */
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
- ];
10960
+ var INCLUDE_PARAMETERS = [{
10961
+ name: "inline",
10962
+ type: "flag"
10963
+ }, {
10964
+ name: "passage",
10965
+ type: "passage",
10966
+ required: true
10967
+ }];
10487
10968
  /**
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.
10969
+ * The arguments of `{include}`: whether it has the `inline` flag, as its
10970
+ * first or last word, and the passage expression that is left.
10491
10971
  */
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
- }
10972
+ function splitIncludeFlag(rawArgs) {
10973
+ const { inline, passage } = parseMacroArgs(rawArgs, INCLUDE_PARAMETERS);
10974
+ return {
10975
+ inline,
10976
+ passage
10977
+ };
10501
10978
  }
10502
- new NameMap();
10979
+ //#endregion
10980
+ //#region src/transform.ts
10981
+ /**
10982
+ * The sigil transform: the story's code with its variable references
10983
+ * (`$var`, `_var`, `@var`, `%var`) turned into the namespace lookups the
10984
+ * expression engine runs. Pure string-to-string, so tooling can use it
10985
+ * (see src/tooling.ts).
10986
+ */
10987
+ var TRANSFORM_CACHE_MAX = 500;
10988
+ var NAMESPACES = {
10989
+ $: "variables",
10990
+ _: "temporary",
10991
+ "@": "locals",
10992
+ "%": "transient"
10993
+ };
10994
+ /** Ends with an identifier character. */
10995
+ var IDENT_END_RE = /[\p{ID_Continue}$\u200c\u200d]$/u;
10996
+ /**
10997
+ * Transform expression: $var → variables["var"], _var → temporary["var"],
10998
+ * @var → locals["var"], %var → transient["var"].
10999
+ * The lexer (`lexJs`) finds the references: only in code (string, template
11000
+ * and regex literal text and comments are left untouched), only where an
11001
+ * identifier starts (not in `a$b`, nor as the property name in `obj._x`),
11002
+ * and `%var` only where an operand is expected, since `%` is also the
11003
+ * modulo operator. `goal` tells whether `expr` is an expression or a list of
11004
+ * statements, as that decides whether a leading `{` opens an object literal
11005
+ * or a block. A reference to a variable named `__proto__` throws a
11006
+ * SyntaxError: no namespace can hold one (see utils/namespace.ts).
11007
+ * */
11008
+ function transform(expr, goal = "expression") {
11009
+ const key = goal === "statements" ? "s" + expr : "e" + expr;
11010
+ const cached = transformCache.get(key);
11011
+ if (cached !== void 0) return cached;
11012
+ const { refs } = parseCode(expr, goal);
11013
+ let result = "";
11014
+ let at = 0;
11015
+ for (const ref of refs) {
11016
+ const error = variableNameError(ref.name, ref.sigil + ref.name);
11017
+ if (error) throw new SyntaxError(`spindle: ${error}`);
11018
+ result += expr.slice(at, ref.start);
11019
+ if (ref.shorthand) result += `${ref.name}: `;
11020
+ else if (IDENT_END_RE.test(result.slice(-2))) result += " ";
11021
+ result += `${NAMESPACES[ref.sigil]}["${ref.name}"]`;
11022
+ at = ref.end;
11023
+ }
11024
+ result += expr.slice(at);
11025
+ if (transformCache.size >= TRANSFORM_CACHE_MAX) transformCache.delete(transformCache.keys().next().value);
11026
+ transformCache.set(key, result);
11027
+ return result;
11028
+ }
11029
+ /** Transformed code by goal and source. */
11030
+ var transformCache = /* @__PURE__ */ new Map();
10503
11031
  //#endregion
10504
11032
  //#region src/tooling.ts
10505
11033
  /**
@@ -10516,23 +11044,31 @@ new NameMap();
10516
11044
  */
10517
11045
  function validateStoryMarkup(passages, macros, options = {}) {
10518
11046
  const list = [...passages];
11047
+ const all = [...macros];
10519
11048
  const known = /* @__PURE__ */ new Set();
10520
- const parameters = /* @__PURE__ */ new Map();
10521
11049
  const blocks = new Set(blockWidgetNames(list).map((n) => n.toLowerCase()));
10522
- for (const macro of macros) {
11050
+ for (const macro of all) {
10523
11051
  const name = macro.name.toLowerCase();
10524
11052
  known.add(name);
10525
- parameters.set(name, macro.parameters);
10526
11053
  for (const sub of macro.subMacros) known.add(sub.toLowerCase());
10527
11054
  if (macro.block) blocks.add(name);
10528
11055
  }
10529
11056
  return validateMarkup(list, {
10530
11057
  isKnownMacro: (name) => known.has(name),
10531
11058
  macroNames: known,
10532
- parametersOf: (name) => parameters.get(name),
11059
+ parametersOf: parameterLookup(all),
10533
11060
  isBlockMacro: (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name),
10534
11061
  checkPassageNames: options.checkPassageNames
10535
11062
  });
10536
11063
  }
11064
+ /**
11065
+ * The passages the markup of `source` names (see PassageReference), against
11066
+ * the given macros: what the story-start check looks up. Malformed tags are
11067
+ * skipped, so it reads half-typed markup.
11068
+ */
11069
+ function collectStoryPassageReferences(source, macros) {
11070
+ const { tokens } = tokenizeMarkupTolerant(source);
11071
+ return collectPassageReferences(source, tokens, parameterLookup(macros));
11072
+ }
10537
11073
  //#endregion
10538
- export { checkParameterTypes, formatDiagnostic, parseStoryVariables, validateStoryMarkup };
11074
+ 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 };