@rohal12/spindle 0.58.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.
@@ -103,24 +103,41 @@ var LINK_SEPARATORS = [
103
103
  ["<-", true]
104
104
  ];
105
105
  var NO_SELECTORS = {};
106
- function parseLink(inner) {
106
+ /**
107
+ * A link's display text and target, from its text `inner` (at `at` in the
108
+ * input), with where the target is written.
109
+ */
110
+ function parseLink(inner, at) {
111
+ /** The trimmed text from `start` to `end` of inner, and where it is. */
112
+ const part = (start, end) => {
113
+ const raw = inner.slice(start, end);
114
+ const value = raw.trim();
115
+ const from = at + start + (value ? raw.indexOf(value) : 0);
116
+ return {
117
+ value,
118
+ start: from,
119
+ end: from + value.length
120
+ };
121
+ };
107
122
  for (const [separator, targetFirst] of LINK_SEPARATORS) {
108
123
  const idx = inner.indexOf(separator);
109
124
  if (idx === -1) continue;
110
- const before = inner.slice(0, idx).trim();
111
- const after = inner.slice(idx + separator.length).trim();
112
- return targetFirst ? {
113
- display: after,
114
- target: before
115
- } : {
116
- display: before,
117
- target: after
125
+ const before = part(0, idx);
126
+ const after = part(idx + separator.length, inner.length);
127
+ const [display, target] = targetFirst ? [after, before] : [before, after];
128
+ return {
129
+ display: display.value,
130
+ target: target.value,
131
+ targetStart: target.start,
132
+ targetEnd: target.end
118
133
  };
119
134
  }
120
- const trimmed = inner.trim();
135
+ const whole = part(0, inner.length);
121
136
  return {
122
- display: trimmed,
123
- target: trimmed
137
+ display: whole.value,
138
+ target: whole.value,
139
+ targetStart: whole.start,
140
+ targetEnd: whole.end
124
141
  };
125
142
  }
126
143
  function quoteArg(value) {
@@ -202,6 +219,8 @@ function tokenOf(x) {
202
219
  type: "link",
203
220
  display: x.display,
204
221
  target: x.target,
222
+ targetStart: x.targetStart,
223
+ targetEnd: x.targetEnd,
205
224
  ...span
206
225
  }, x);
207
226
  case "variable": return withSelectors({
@@ -733,8 +752,8 @@ function peg$parse(input, options) {
733
752
  function peg$f48() {
734
753
  return !textMode;
735
754
  }
736
- function peg$f49(s, inner) {
737
- return withSelectors(lexeme("link", location(), parseLink(inner)), s);
755
+ function peg$f49(s, at, inner) {
756
+ return withSelectors(lexeme("link", location(), parseLink(inner, at)), s);
738
757
  }
739
758
  function peg$f50() {
740
759
  return !textMode;
@@ -1783,7 +1802,7 @@ function peg$parse(input, options) {
1783
1802
  return s0;
1784
1803
  }
1785
1804
  function peg$parseLinkLexeme() {
1786
- let s0, s1, s2, s3, s4, s5;
1805
+ let s0, s1, s2, s3, s4, s5, s6;
1787
1806
  s0 = peg$currPos;
1788
1807
  peg$savedPos = peg$currPos;
1789
1808
  s1 = peg$f48();
@@ -1800,19 +1819,20 @@ function peg$parse(input, options) {
1800
1819
  if (s2 !== peg$FAILED) {
1801
1820
  s3 = peg$parseSelectors();
1802
1821
  if (s3 !== peg$FAILED) {
1803
- s4 = peg$currPos;
1804
- s5 = peg$parseLinkText();
1805
- s4 = input.substring(s4, peg$currPos);
1822
+ s4 = peg$parseHere();
1823
+ s5 = peg$currPos;
1824
+ s6 = peg$parseLinkText();
1825
+ s5 = input.substring(s5, peg$currPos);
1806
1826
  if (input.substr(peg$currPos, 2) === peg$c5) {
1807
- s5 = peg$c5;
1827
+ s6 = peg$c5;
1808
1828
  peg$currPos += 2;
1809
1829
  } else {
1810
- s5 = peg$FAILED;
1830
+ s6 = peg$FAILED;
1811
1831
  if (peg$silentFails === 0) peg$fail(peg$e9);
1812
1832
  }
1813
- if (s5 !== peg$FAILED) {
1833
+ if (s6 !== peg$FAILED) {
1814
1834
  peg$savedPos = s0;
1815
- s0 = peg$f49(s3, s4);
1835
+ s0 = peg$f49(s3, s4, s5);
1816
1836
  } else {
1817
1837
  peg$currPos = s0;
1818
1838
  s0 = peg$FAILED;
@@ -9441,6 +9461,22 @@ function regexEnd(src, start) {
9441
9461
  return -1;
9442
9462
  }
9443
9463
  /**
9464
+ * The value of `src` when it is exactly one `"…"` or `'…'` string literal,
9465
+ * as JavaScript reads it (`"\u0048all"` is `Hall`, `"\1"` is U+0001 in
9466
+ * the non-strict code passages run), otherwise `null`: other code, or a
9467
+ * literal that is not well-formed (`"\u{110000}"`).
9468
+ */
9469
+ function stringLiteralValue(src) {
9470
+ if (src[0] !== "\"" && src[0] !== "'") return null;
9471
+ try {
9472
+ const p = tokenizerAt(src, 0, "expression");
9473
+ p.nextToken();
9474
+ return p.type === types$1.string && p.end === src.length ? p.value : null;
9475
+ } catch {
9476
+ return null;
9477
+ }
9478
+ }
9479
+ /**
9444
9480
  * Scan the `"…"` or `'…'` string literal opening at `start`. `end` is the
9445
9481
  * index just past its closing quote, or `src.length` when it is unterminated
9446
9482
  * (`closed` false). A backslash escapes the character after it, so a quote
@@ -10146,20 +10182,45 @@ var PARAMETER_TYPES = [
10146
10182
  "options"
10147
10183
  ];
10148
10184
  /**
10185
+ * What the value of a `string` or `text` argument holds, for the check at
10186
+ * story start and for tooling (see code-check.ts):
10187
+ * - `markup`: markup the macro renders (`{button}`'s label): its markup
10188
+ * is checked.
10189
+ * - `text`: plain text the macro uses as written (`{checkbox}`'s label).
10190
+ * - `passage`: a passage name (`{watch}`'s `goto`): the passage must exist.
10191
+ * - `expression`, `statements`: code (`{watch}`'s condition and `run`): it
10192
+ * is checked as code, and its variable references against the schema.
10193
+ *
10194
+ * Without it, the argument of a macro with `interpolate` holds `markup`,
10195
+ * any other `text`.
10196
+ */
10197
+ var STRING_HOLDS = [
10198
+ "markup",
10199
+ "text",
10200
+ "passage",
10201
+ "expression",
10202
+ "statements"
10203
+ ];
10204
+ /**
10149
10205
  * Throw if a parameter `macro` declares (or an option of one) has no type,
10150
10206
  * or one that isn't a ParameterType: arguments are read by their type, and
10151
- * there is no default.
10207
+ * there is no default. Throw too if it declares what it `holds` wrongly.
10152
10208
  */
10153
10209
  function checkParameterTypes(macro, parameters) {
10210
+ const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
10154
10211
  for (const param of parameters) {
10155
10212
  const type = param.type;
10156
- if (!PARAMETER_TYPES.includes(type)) {
10157
- const problem = type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`;
10158
- 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.`);
10159
- }
10213
+ 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.`);
10214
+ const holds = param.holds;
10215
+ if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
10216
+ if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
10160
10217
  if (param.parameters) checkParameterTypes(macro, param.parameters);
10161
10218
  }
10162
10219
  }
10220
+ /** The error for the parameter `param` of `macro`: what is wrong, and the fix. */
10221
+ function parameterError(macro, param, problem, fix) {
10222
+ return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
10223
+ }
10163
10224
  var metadataRegistry = new NameMap();
10164
10225
  function registerMacroMetadata(name, metadata) {
10165
10226
  metadataRegistry.set(name, metadata);
@@ -10242,37 +10303,6 @@ function readWholeQuoted(src) {
10242
10303
  const quoted = readQuoted(src, 0);
10243
10304
  return quoted && quoted.end === src.length ? quoted.value : null;
10244
10305
  }
10245
- var JS_SIMPLE_ESCAPES = {
10246
- n: "\n",
10247
- t: " ",
10248
- r: "\r",
10249
- b: "\b",
10250
- f: "\f",
10251
- v: "\v",
10252
- "0": "\0"
10253
- };
10254
- /** Undo every escape of a JavaScript string literal's body. */
10255
- function decodeJsEscapes(body) {
10256
- 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) => {
10257
- if (lineBreak !== void 0) return "";
10258
- const code = braced ?? unicode ?? hex;
10259
- if (code !== void 0) {
10260
- const point = parseInt(code, 16);
10261
- return point <= 1114111 ? String.fromCodePoint(point) : "";
10262
- }
10263
- return JS_SIMPLE_ESCAPES[other] ?? other;
10264
- });
10265
- }
10266
- /**
10267
- * The value of `src` when it is exactly one `"…"` or `'…'` string, read as
10268
- * the JavaScript literal it is (`"\u0048all"` is `Hall`), otherwise `null`.
10269
- * `readWholeQuoted` keeps other escapes as written, for labels and code.
10270
- */
10271
- function readWholeJsString(src) {
10272
- const quoted = readQuoted(src, 0);
10273
- if (!quoted || quoted.end !== src.length) return null;
10274
- return decodeJsEscapes(src.slice(1, -1));
10275
- }
10276
10306
  /**
10277
10307
  * Strip an optional quote from each end of an unquoted-or-loosely-quoted
10278
10308
  * argument (`"Red`, `Red'`), leaving at least one character. This is the
@@ -16567,7 +16597,9 @@ var MacroArgumentError = class extends Error {
16567
16597
  this.name = "MacroArgumentError";
16568
16598
  }
16569
16599
  };
16570
- function readValue(param, src) {
16600
+ /** Read `src`, at `place`, as `param` says. */
16601
+ function readValue(param, src, place) {
16602
+ place.spans?.set(param, [place.at, place.at + src.length]);
16571
16603
  switch (param.type) {
16572
16604
  case "string": {
16573
16605
  const value = readWholeQuoted(src);
@@ -16578,7 +16610,7 @@ function readValue(param, src) {
16578
16610
  case "names": return src.split(",").map((name) => name.trim());
16579
16611
  case "delay": return parseDelay(src);
16580
16612
  case "number": return Number(src);
16581
- case "options": return readOptions(param.parameters ?? [], src);
16613
+ case "options": return readOptions(param.parameters ?? [], src, place);
16582
16614
  default: return src;
16583
16615
  }
16584
16616
  }
@@ -16596,11 +16628,12 @@ function matchAt(re, src, pos) {
16596
16628
  }
16597
16629
  /**
16598
16630
  * Read keyword options (`goto "X" priority 5 once`). A keyword takes the
16599
- * quoted string or digit run after it as its value; keywords that aren't
16600
- * declared, and their values, are skipped. A keyword of a `string` parameter
16601
- * must be followed by a quoted string.
16631
+ * quoted string (with or without whitespace before it) or digit run after
16632
+ * it as its value; keywords that aren't declared, and their values, are
16633
+ * skipped. A keyword of a `string` parameter must be followed by a quoted
16634
+ * string.
16602
16635
  */
16603
- function readOptions(params, src) {
16636
+ function readOptions(params, src, { at, spans }) {
16604
16637
  const options = {};
16605
16638
  let i = 0;
16606
16639
  while (i < src.length) {
@@ -16611,22 +16644,20 @@ function readOptions(params, src) {
16611
16644
  }
16612
16645
  i += key.length;
16613
16646
  let val;
16614
- let quoted = null;
16615
16647
  let j = i;
16616
16648
  while (j < src.length && isWhitespace(src[j])) j++;
16617
- if (j > i) {
16618
- quoted = readQuoted(src, j);
16619
- const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
16620
- if (quoted) {
16621
- val = quoted.value;
16622
- i = quoted.end;
16623
- } else if (digits) {
16624
- val = digits;
16625
- i = j + digits.length;
16626
- }
16649
+ const quoted = readQuoted(src, j);
16650
+ const digits = quoted || j === i ? null : matchAt(DIGITS_RE, src, j);
16651
+ if (quoted) {
16652
+ val = quoted.value;
16653
+ i = quoted.end;
16654
+ } else if (digits) {
16655
+ val = digits;
16656
+ i = j + digits.length;
16627
16657
  }
16628
16658
  const param = params.find((p) => p.name === key);
16629
- if (param?.type === "string" && (!quoted || val === void 0)) throw new MacroArgumentError(param, matchAt(WORD_OR_PUNCT_RE, src, j) ?? "");
16659
+ if (param && param.type !== "flag" && val !== void 0) spans?.set(param, [at + j, at + i]);
16660
+ if (param?.type === "string" && !quoted) throw new MacroArgumentError(param, matchAt(WORD_OR_PUNCT_RE, src, j) ?? "");
16630
16661
  if (param?.type === "flag") options[key] = true;
16631
16662
  else if (param?.type === "number") options[key] = Number(val ?? 0);
16632
16663
  else if (param) options[key] = val;
@@ -16636,21 +16667,21 @@ function readOptions(params, src) {
16636
16667
  /** `inline %name` reads a transient variable; `inline % 2` is modulo. */
16637
16668
  var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
16638
16669
  /**
16639
- * The rest of `src` without the flag `word` as its last or first term, or
16640
- * `null` if it has no such flag. Next to a binary operator the word is an
16641
- * operand (`"a" + inline`), not a flag; the closing `/` of a regex literal
16642
- * is not one.
16670
+ * Where the rest of `src` is without the flag `word` as its last or first
16671
+ * term, or `null` if it has no such flag. Next to a binary operator the
16672
+ * word is an operand (`"a" + inline`), not a flag; the closing `/` of a
16673
+ * regex literal is not one.
16643
16674
  */
16644
16675
  function takeFlag(src, word) {
16645
16676
  const spans = terms(src);
16646
16677
  if (spans.length < 2) return null;
16647
16678
  if (slice(src, spans[spans.length - 1]) === word) {
16648
- const rest = src.slice(0, spans[spans.length - 2][1]);
16649
- if (!endsWithOperator(rest)) return rest;
16679
+ const rest = [0, spans[spans.length - 2][1]];
16680
+ if (!endsWithOperator(slice(src, rest))) return rest;
16650
16681
  }
16651
16682
  if (slice(src, spans[0]) === word) {
16652
- const rest = src.slice(spans[1][0]);
16653
- if (!LEADING_OPERATOR_RE.test(rest)) return rest;
16683
+ const rest = [spans[1][0], src.length];
16684
+ if (!LEADING_OPERATOR_RE.test(slice(src, rest))) return rest;
16654
16685
  }
16655
16686
  return null;
16656
16687
  }
@@ -16678,42 +16709,55 @@ function findSeparator(src, word) {
16678
16709
  return null;
16679
16710
  }
16680
16711
  var takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
16681
- /** Read the parameters of one group from `src` (`null`: no text) into `args`. */
16682
- function readGroup(params, src, args) {
16683
- let text = src?.trim() ?? "";
16712
+ /** The text of `src` without the whitespace around it, and where it is. */
16713
+ function trimmed(src, at) {
16714
+ const text = src.trim();
16715
+ return [text, text ? at + src.indexOf(text) : at];
16716
+ }
16717
+ /**
16718
+ * Read the parameters of one group from `src` (`null`: no text), which is
16719
+ * at `at` in the raw arguments, into `args`.
16720
+ */
16721
+ function readGroup(params, src, args, { at: srcAt, spans }) {
16722
+ let [text, at] = trimmed(src ?? "", srcAt);
16684
16723
  for (const flag of params.filter((p) => p.type === "flag")) {
16685
16724
  const rest = src === null ? null : takeFlag(text, flag.name);
16686
16725
  args[flag.name] = rest !== null;
16687
- text = rest ?? text;
16726
+ if (rest) [text, at] = trimmed(slice(text, rest), at + rest[0]);
16688
16727
  }
16689
16728
  const positional = params.filter((p) => p.type !== "flag");
16690
16729
  if (positional.length === 0) return;
16691
16730
  const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
16692
16731
  const restIndex = lastText < 0 ? positional.length - 1 : lastText;
16693
- const read = (param, value) => value === "" ? unset(param) : readValue(param, value);
16732
+ /** Read `param` from the text from `start` to `end`. */
16733
+ const read = (param, [start, end]) => start === end ? unset(param) : readValue(param, text.slice(start, end), {
16734
+ at: at + start,
16735
+ spans
16736
+ });
16694
16737
  if (positional.length === 1) {
16695
- args[positional[0].name] = read(positional[0], text);
16738
+ args[positional[0].name] = read(positional[0], [0, text.length]);
16696
16739
  return;
16697
16740
  }
16698
- const spans = terms(text);
16741
+ const parts = terms(text);
16699
16742
  let from = 0;
16700
- let to = spans.length;
16701
- for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, slice(text, spans[from++])) : unset(param);
16702
- const readTrailing = (param, value) => {
16743
+ let to = parts.length;
16744
+ for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, parts[from++]) : unset(param);
16745
+ const readTrailing = (param, span) => {
16703
16746
  try {
16704
- return read(param, value);
16747
+ return read(param, span);
16705
16748
  } catch (error) {
16706
16749
  if (error instanceof MacroArgumentError) return void 0;
16707
16750
  throw error;
16708
16751
  }
16709
16752
  };
16710
16753
  for (const param of positional.slice(restIndex + 1).reverse()) {
16711
- const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? readTrailing(param, slice(text, spans[to - 1])) : void 0;
16754
+ const value = from < to && !endsWithOperator(text.slice(0, parts[to - 1][0])) ? readTrailing(param, parts[to - 1]) : void 0;
16712
16755
  args[param.name] = value ?? unset(param);
16713
16756
  if (value !== void 0) to--;
16757
+ else spans?.delete(param);
16714
16758
  }
16715
16759
  const rest = positional[restIndex];
16716
- args[rest.name] = from < to ? read(rest, text.slice(spans[from][0], spans[to - 1][1])) : unset(rest);
16760
+ args[rest.name] = from < to ? read(rest, [parts[from][0], parts[to - 1][1]]) : unset(rest);
16717
16761
  }
16718
16762
  /** The duration a timing macro ({repeat}, {type}, {timed}/{next}) waits. */
16719
16763
  var DELAY_PARAMETER = {
@@ -16721,32 +16765,43 @@ var DELAY_PARAMETER = {
16721
16765
  type: "delay",
16722
16766
  required: true
16723
16767
  };
16724
- /** Read `rawArgs` into the declared `parameters` (see above). */
16725
- function parseMacroArgs(rawArgs, parameters) {
16768
+ /**
16769
+ * Read `rawArgs` into the declared `parameters` (see above). With `spans`,
16770
+ * note there where each argument set was read.
16771
+ */
16772
+ function parseMacroArgs(rawArgs, parameters, spans) {
16726
16773
  const args = {};
16727
- let rest = rawArgs.trim();
16774
+ let [rest, restAt] = trimmed(rawArgs, 0);
16728
16775
  let groupStart = 0;
16729
16776
  parameters.forEach((separator, i) => {
16730
16777
  if (separator.type !== "separator") return;
16731
16778
  const at = rest === null ? null : findSeparator(rest, separator.name);
16732
16779
  args[separator.name] = at !== null;
16733
16780
  const before = at ? rest.slice(0, at[0]) : rest;
16734
- readGroup(parameters.slice(groupStart, i), before, args);
16781
+ readGroup(parameters.slice(groupStart, i), before, args, {
16782
+ at: restAt,
16783
+ spans
16784
+ });
16735
16785
  rest = at ? rest.slice(at[1]) : null;
16786
+ if (at) restAt += at[1];
16736
16787
  groupStart = i + 1;
16737
16788
  });
16738
- readGroup(parameters.slice(groupStart), rest, args);
16789
+ readGroup(parameters.slice(groupStart), rest, args, {
16790
+ at: restAt,
16791
+ spans
16792
+ });
16739
16793
  return args;
16740
16794
  }
16741
16795
  /**
16742
16796
  * Read a `passage` argument as written (`{goto "Hall"}`, `{goto $room}`): a
16743
- * quoted string is the name its JavaScript literal has (`"\u0048all"` is
16744
- * `Hall`), anything else an expression, whose value is the name when it
16797
+ * string literal is the name JavaScript reads from it (`"\u0048all"` is
16798
+ * `Hall`), anything else (a literal that is not well-formed too) an
16799
+ * expression, whose value is the name when it
16745
16800
  * runs (see evaluatePassageName).
16746
16801
  */
16747
16802
  function passageTarget(arg) {
16748
16803
  const expression = arg.trim();
16749
- const name = readWholeJsString(expression);
16804
+ const name = stringLiteralValue(expression);
16750
16805
  return name === null ? {
16751
16806
  kind: "expression",
16752
16807
  expression
@@ -16795,6 +16850,7 @@ function parseVarArgs(rawArgs) {
16795
16850
  var OPTION_PARAMETERS = [{
16796
16851
  name: "value",
16797
16852
  type: "string",
16853
+ holds: "text",
16798
16854
  required: true
16799
16855
  }];
16800
16856
  /** The parameters of the built-in sub-macros that take arguments. */
@@ -17269,30 +17325,63 @@ var CONDITION_MACROS = new Set([
17269
17325
  "elseif",
17270
17326
  "case"
17271
17327
  ]);
17272
- /** Code inside the quoted strings of a macro's arguments, by parameter. */
17273
- var CODE_IN_STRINGS = new Map([["watch", {
17274
- condition: "expression",
17275
- run: "statements"
17276
- }]]);
17277
- /** The `string` parameters of a macro that name a passage. */
17278
- var PASSAGE_STRINGS = new Map([["watch", ["goto", "dialog"]]]);
17279
- /**
17280
- * The `text` and `string` parameters of a macro that the runtime keeps as
17281
- * written, not as markup: an option's value, the name of a watcher.
17282
- */
17283
- var LITERAL_STRINGS = new Map([
17284
- ["option", ["value"]],
17285
- ["watch", ["name"]],
17286
- ["unwatch", ["name"]]
17287
- ]);
17288
17328
  /** Block macros whose body is the name of a passage. */
17289
17329
  var PASSAGE_BODIES = new Set(["dialog"]);
17330
+ /**
17331
+ * `params` with each `string` and `text` parameter (options too) saying
17332
+ * what it holds: what it declares, else `holds`.
17333
+ */
17334
+ function withHolds(params, holds) {
17335
+ return params.map((param) => {
17336
+ if (param.parameters) return {
17337
+ ...param,
17338
+ parameters: withHolds(param.parameters, holds)
17339
+ };
17340
+ return (param.type === "string" || param.type === "text") && !param.holds ? {
17341
+ ...param,
17342
+ holds
17343
+ } : param;
17344
+ });
17345
+ }
17346
+ /**
17347
+ * The declared parameters of `macros`, by name in any case, and of the
17348
+ * built-in sub-macros (`{option}`): what the story-start check, reference
17349
+ * collection and tooling read arguments by. Each `string` and `text`
17350
+ * parameter says what it holds: what it declares, else `markup` for a macro
17351
+ * with `interpolate` (which can resolve markup) and `text` for any other.
17352
+ */
17353
+ function parameterLookup(macros) {
17354
+ const parameters = new NameMap();
17355
+ for (const { name, parameters: params, interpolate } of macros) if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
17356
+ return (name) => {
17357
+ let params = parameters.get(name);
17358
+ if (!params) {
17359
+ const sub = subMacroParameters(name);
17360
+ if (sub) parameters.set(name, params = withHolds(sub, "text"));
17361
+ }
17362
+ return params;
17363
+ };
17364
+ }
17365
+ /** What a `string` or `text` argument holds when its parameter doesn't say. */
17366
+ var holdsByDefault = (interpolate) => interpolate ? "markup" : "text";
17367
+ /** Whether a parameter in `params` (options too) holds code in a string. */
17368
+ function holdsCode(params) {
17369
+ return params.some((p) => p.holds === "expression" || p.holds === "statements" || !!p.parameters && holdsCode(p.parameters));
17370
+ }
17290
17371
  /** The goal of the code an argument of this type holds, if it is code. */
17291
17372
  function codeGoal(param) {
17292
17373
  if (param.type === "expression" || param.type === "passage") return "expression";
17293
17374
  if (param.type === "statements") return "statements";
17294
17375
  }
17295
17376
  /**
17377
+ * What a `string` or `text` argument holds: what its parameter says (see
17378
+ * parameterLookup), else markup, which is checked.
17379
+ */
17380
+ function stringHolds(param) {
17381
+ if (param.type !== "string" && param.type !== "text") return void 0;
17382
+ return param.holds ?? "markup";
17383
+ }
17384
+ /**
17296
17385
  * The code and the markup-holding text in `tokens`, the tokens of `src`.
17297
17386
  * `parametersOf` gives the declared parameters of a macro, if it has any.
17298
17387
  */
@@ -17304,8 +17393,8 @@ function* codeAndText(src, tokens, parametersOf) {
17304
17393
  if (token.type === "link") yield {
17305
17394
  kind: "passage",
17306
17395
  name: token.target,
17307
- offset: locate(src, token.target, token.start),
17308
- length: token.target.length,
17396
+ offset: token.targetStart,
17397
+ length: token.targetEnd - token.targetStart,
17309
17398
  macro: "link",
17310
17399
  label: src.slice(token.start, token.end)
17311
17400
  };
@@ -17316,32 +17405,35 @@ function* codeAndText(src, tokens, parametersOf) {
17316
17405
  goal: "expression",
17317
17406
  label: `{${token.expression}}`
17318
17407
  };
17319
- else if (token.type === "html") for (const [name, value] of Object.entries(token.attributes)) {
17320
- const at = locate(src, value, token.start);
17321
- if (!isCodeAttribute(name)) {
17322
- yield {
17323
- kind: "text",
17324
- text: value,
17325
- offset: at,
17326
- where: `In the ${name} attribute of <${token.tag}>: `
17327
- };
17328
- continue;
17329
- }
17330
- let from = 0;
17331
- for (const part of splitSigilTemplate(value)) {
17332
- if (!("expr" in part)) continue;
17333
- const k = locate(value, part.expr, from);
17334
- from = k + part.expr.length;
17335
- yield {
17336
- kind: "code",
17337
- code: part.expr,
17338
- offset: at + k,
17339
- goal: "expression",
17340
- label: `{${part.expr}} in the ${name} attribute of <${token.tag}>`
17341
- };
17408
+ else if (token.type === "html") {
17409
+ let cursor = token.start;
17410
+ for (const [name, value] of Object.entries(token.attributes)) {
17411
+ const at = locate(src, value, cursor);
17412
+ cursor = at + value.length;
17413
+ if (!isCodeAttribute(name)) {
17414
+ yield {
17415
+ kind: "text",
17416
+ text: value,
17417
+ offset: at,
17418
+ where: `In the ${name} attribute of <${token.tag}>: `
17419
+ };
17420
+ continue;
17421
+ }
17422
+ let from = 0;
17423
+ for (const part of splitSigilTemplate(value)) {
17424
+ if (!("expr" in part)) continue;
17425
+ const k = locate(value, part.expr, from);
17426
+ from = k + part.expr.length;
17427
+ yield {
17428
+ kind: "code",
17429
+ code: part.expr,
17430
+ offset: at + k,
17431
+ goal: "expression",
17432
+ label: `{${part.expr}} in the ${name} attribute of <${token.tag}>`
17433
+ };
17434
+ }
17342
17435
  }
17343
- }
17344
- else if (token.type === "macro" && !token.isClose) {
17436
+ } else if (token.type === "macro" && !token.isClose) {
17345
17437
  const name = token.name.toLowerCase();
17346
17438
  if (name === "do") {
17347
17439
  const close = closeOf(t, name);
@@ -17361,12 +17453,13 @@ function* codeAndText(src, tokens, parametersOf) {
17361
17453
  const close = closeOf(t, name);
17362
17454
  const body = tokens.slice(t + 1, close);
17363
17455
  if (close > t + 1 && body.every((b) => b.type === "text")) {
17364
- const passage = src.slice(token.end, tokens[close].start).trim().replace(/^["']|["']$/g, "");
17456
+ const text = src.slice(token.end, tokens[close].start);
17457
+ const written = text.trim();
17365
17458
  yield {
17366
17459
  kind: "passage",
17367
- name: passage,
17368
- offset: locate(src, passage, token.end),
17369
- length: passage.length,
17460
+ name: written.replace(/^["']|["']$/g, ""),
17461
+ offset: token.end + text.indexOf(written),
17462
+ length: written.length,
17370
17463
  macro: name,
17371
17464
  label: src.slice(token.start, token.end)
17372
17465
  };
@@ -17374,7 +17467,8 @@ function* codeAndText(src, tokens, parametersOf) {
17374
17467
  }
17375
17468
  if (!args) continue;
17376
17469
  const label = `{${token.name} ${args}}`;
17377
- const argsAt = locate(src, args, token.start + 1);
17470
+ const tag = src.slice(token.start, token.end - 1);
17471
+ const argsAt = token.start + tag.trimEnd().length - args.length;
17378
17472
  if (CONDITION_MACROS.has(name)) {
17379
17473
  yield {
17380
17474
  kind: "code",
@@ -17393,8 +17487,9 @@ function* codeAndText(src, tokens, parametersOf) {
17393
17487
  /** The code and text in the arguments `args` (at `offset`) of `macro`. */
17394
17488
  function* argPieces(args, offset, params, macro, label) {
17395
17489
  let values;
17490
+ const spans = /* @__PURE__ */ new Map();
17396
17491
  try {
17397
- values = parseMacroArgs(args, params);
17492
+ values = parseMacroArgs(args, params, spans);
17398
17493
  } catch (error) {
17399
17494
  if (!(error instanceof MacroArgumentError)) throw error;
17400
17495
  yield {
@@ -17405,11 +17500,7 @@ function* argPieces(args, offset, params, macro, label) {
17405
17500
  };
17406
17501
  return;
17407
17502
  }
17408
- const key = macro.toLowerCase();
17409
- const inStrings = CODE_IN_STRINGS.get(key) ?? {};
17410
- const passageStrings = PASSAGE_STRINGS.get(key) ?? [];
17411
- const literalStrings = LITERAL_STRINGS.get(key) ?? [];
17412
- let cursor = 0;
17503
+ const name = macro.toLowerCase();
17413
17504
  /** The pieces of the parameters `list`, with their values in `from`. */
17414
17505
  function* visit(list, from) {
17415
17506
  for (const param of list) {
@@ -17418,42 +17509,57 @@ function* argPieces(args, offset, params, macro, label) {
17418
17509
  yield* visit(param.parameters, value ?? {});
17419
17510
  continue;
17420
17511
  }
17421
- if (typeof value !== "string" || !value.trim()) continue;
17422
- const at = locate(args, value, cursor);
17423
- if (at >= cursor) cursor = at + value.length;
17424
- const goal = codeGoal(param) ?? inStrings[param.name];
17512
+ const span = spans.get(param);
17513
+ if (typeof value !== "string" || !value.trim() || !span) continue;
17514
+ const [start, end] = span;
17515
+ const written = args.slice(start, end);
17516
+ /** The name as written (quotes included), at the argument. */
17517
+ const passage = (passageName) => ({
17518
+ kind: "passage",
17519
+ name: passageName,
17520
+ offset: offset + start,
17521
+ length: end - start,
17522
+ macro: name,
17523
+ label
17524
+ });
17525
+ const goal = codeGoal(param);
17526
+ if (param.type === "passage") {
17527
+ const target = passageTarget(value);
17528
+ if (target.kind === "name") {
17529
+ yield passage(target.name);
17530
+ continue;
17531
+ }
17532
+ }
17425
17533
  if (goal) {
17426
17534
  const piece = {
17427
17535
  kind: "code",
17428
17536
  code: value,
17429
- offset: offset + at,
17537
+ offset: offset + start,
17430
17538
  goal,
17431
17539
  label
17432
17540
  };
17433
17541
  if (param.type === "passage") {
17434
17542
  piece.passage = true;
17435
- piece.macro = macro.toLowerCase();
17543
+ piece.macro = name;
17436
17544
  }
17437
- if (!codeGoal(param)) piece.inString = true;
17438
17545
  yield piece;
17546
+ continue;
17439
17547
  }
17440
- const written = param.type === "passage" ? value.trim() : value;
17441
- const target = param.type === "passage" ? passageTarget(value) : passageStrings.includes(param.name) ? {
17442
- kind: "name",
17443
- name: value
17444
- } : null;
17445
- if (target?.kind === "name") yield {
17446
- kind: "passage",
17447
- name: target.name,
17448
- offset: offset + at + value.indexOf(written),
17449
- length: written.length,
17450
- macro: macro.toLowerCase(),
17451
- label
17548
+ const at = offset + start + (value !== written && /^["']/.test(written) ? 1 : 0);
17549
+ const holds = stringHolds(param);
17550
+ if (holds === "expression" || holds === "statements") yield {
17551
+ kind: "code",
17552
+ code: value,
17553
+ offset: at,
17554
+ goal: holds,
17555
+ label,
17556
+ inString: true
17452
17557
  };
17453
- if (!goal && (param.type === "text" || param.type === "string") && !literalStrings.includes(param.name)) yield {
17558
+ else if (holds === "passage") yield passage(value);
17559
+ else if (holds === "markup") yield {
17454
17560
  kind: "text",
17455
17561
  text: value,
17456
- offset: offset + at,
17562
+ offset: at,
17457
17563
  where: `In the ${param.name} of {${macro}}: `
17458
17564
  };
17459
17565
  }
@@ -17480,10 +17586,12 @@ function locate(text, part, from) {
17480
17586
  var WIDGET_PARAMETERS = [{
17481
17587
  name: "name",
17482
17588
  type: "text",
17589
+ holds: "text",
17483
17590
  required: true
17484
17591
  }, {
17485
17592
  name: "parameters",
17486
- type: "text"
17593
+ type: "text",
17594
+ holds: "text"
17487
17595
  }];
17488
17596
  /**
17489
17597
  * The widget a {widget} definition's arguments declare: its name and its
@@ -17643,7 +17751,7 @@ function validateMarkup(passages, options) {
17643
17751
  report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
17644
17752
  }
17645
17753
  };
17646
- const parametersOf = (name) => options.parametersOf?.(name) ?? subMacroParameters(name);
17754
+ const parametersOf = options.parametersOf ?? (() => void 0);
17647
17755
  /**
17648
17756
  * Check `tokens`, the tokens of `src`, which starts at `base` in the
17649
17757
  * passage: their macros, the code in them and the markup in their text.
@@ -17909,12 +18017,13 @@ function collectPassageRefs(content, storeVarMacros, onRef, parametersOf) {
17909
18017
  withParseCache(() => collectTokenRefs(content, tokensOf(content, false), storeVarMacros, onRef, parametersOf));
17910
18018
  }
17911
18019
  /**
17912
- * Report the references in the code that a macro's quoted arguments hold,
17913
- * the condition and `run` action of `{watch}`: the argument scan sees only
17914
- * the strings.
18020
+ * Report the references in the code that a macro's quoted arguments hold
18021
+ * (see ParameterDef.holds), the condition and `run` action of `{watch}`:
18022
+ * the argument scan sees only the strings. Only the macros that declare
18023
+ * code in a string have their arguments read.
17915
18024
  */
17916
18025
  function scanStringCode(token, params, onRef) {
17917
- if (!params || !token.rawArgs) return;
18026
+ if (!params || !token.rawArgs || !holdsCode(params)) return;
17918
18027
  for (const piece of argPieces(token.rawArgs, 0, params, token.name, "")) if (piece.kind === "code" && piece.inString) scanCode(piece.code, onRef, piece.goal);
17919
18028
  }
17920
18029
  /** Report the `$var` references in the tokens of `content`. */
@@ -18221,7 +18330,8 @@ defineMacro({
18221
18330
  },
18222
18331
  {
18223
18332
  name: "label",
18224
- type: "string"
18333
+ type: "string",
18334
+ holds: "markup"
18225
18335
  }
18226
18336
  ],
18227
18337
  render(_props, ctx) {
@@ -18588,6 +18698,7 @@ defineMacro({
18588
18698
  parameters: [{
18589
18699
  name: "label",
18590
18700
  type: "text",
18701
+ holds: "markup",
18591
18702
  required: true
18592
18703
  }],
18593
18704
  render({ rawArgs, children = [] }, ctx) {
@@ -18619,6 +18730,7 @@ defineMacro({
18619
18730
  parameters: [{
18620
18731
  name: "text",
18621
18732
  type: "string",
18733
+ holds: "markup",
18622
18734
  required: true
18623
18735
  }, {
18624
18736
  name: "passage",
@@ -18948,6 +19060,7 @@ defineMacro({
18948
19060
  parameters: [{
18949
19061
  name: "label",
18950
19062
  type: "text",
19063
+ holds: "markup",
18951
19064
  required: true
18952
19065
  }, {
18953
19066
  name: "noclose",
@@ -19494,7 +19607,8 @@ function defineInputMacro(name, type, toValue = (input) => input !== void 0 ? St
19494
19607
  storeVar: true,
19495
19608
  parameters: [VARIABLE_PARAMETER, {
19496
19609
  name: "placeholder",
19497
- type: "string"
19610
+ type: "string",
19611
+ holds: "text"
19498
19612
  }],
19499
19613
  render(_props, ctx) {
19500
19614
  const placeholder = ctx.args.placeholder ?? "";
@@ -19530,7 +19644,8 @@ defineMacro({
19530
19644
  storeVar: true,
19531
19645
  parameters: [VARIABLE_PARAMETER, {
19532
19646
  name: "label",
19533
- type: "text"
19647
+ type: "text",
19648
+ holds: "text"
19534
19649
  }],
19535
19650
  render(_props, ctx) {
19536
19651
  const label = ctx.args.label ?? "";
@@ -19561,11 +19676,13 @@ defineMacro({
19561
19676
  {
19562
19677
  name: "value",
19563
19678
  type: "text",
19679
+ holds: "text",
19564
19680
  required: true
19565
19681
  },
19566
19682
  {
19567
19683
  name: "label",
19568
- type: "text"
19684
+ type: "text",
19685
+ holds: "text"
19569
19686
  }
19570
19687
  ],
19571
19688
  render(_props, ctx) {
@@ -19985,6 +20102,7 @@ defineMacro({
19985
20102
  parameters: [{
19986
20103
  name: "condition",
19987
20104
  type: "string",
20105
+ holds: "expression",
19988
20106
  required: true
19989
20107
  }, {
19990
20108
  name: "options",
@@ -19992,19 +20110,23 @@ defineMacro({
19992
20110
  parameters: [
19993
20111
  {
19994
20112
  name: "goto",
19995
- type: "string"
20113
+ type: "string",
20114
+ holds: "passage"
19996
20115
  },
19997
20116
  {
19998
20117
  name: "dialog",
19999
- type: "string"
20118
+ type: "string",
20119
+ holds: "passage"
20000
20120
  },
20001
20121
  {
20002
20122
  name: "run",
20003
- type: "string"
20123
+ type: "string",
20124
+ holds: "statements"
20004
20125
  },
20005
20126
  {
20006
20127
  name: "name",
20007
- type: "string"
20128
+ type: "string",
20129
+ holds: "text"
20008
20130
  },
20009
20131
  {
20010
20132
  name: "priority",
@@ -20033,6 +20155,7 @@ defineMacro({
20033
20155
  parameters: [{
20034
20156
  name: "name",
20035
20157
  type: "text",
20158
+ holds: "text",
20036
20159
  required: true
20037
20160
  }],
20038
20161
  render(_props, ctx) {
@@ -20081,11 +20204,10 @@ function stopWithErrors(errors) {
20081
20204
  */
20082
20205
  function markupErrors(storyData, only) {
20083
20206
  const macros = getMacroRegistry();
20084
- const parameters = new Map(macros.map((m) => [m.name.toLowerCase(), m.parameters]));
20085
20207
  return validateMarkup(storyData.passages.values(), {
20086
20208
  isKnownMacro: (name) => !!getMacro(name) || isSubMacro(name) || !!getWidget(name),
20087
20209
  macroNames: macros.map((m) => m.name),
20088
- parametersOf: (name) => parameters.get(name),
20210
+ parametersOf: parameterLookup(macros),
20089
20211
  only
20090
20212
  }).map(formatDiagnostic);
20091
20213
  }