@rohal12/spindle 0.58.0 → 0.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +428 -277
- package/dist/pkg/macro-registry.json +29 -11
- package/dist/pkg/story-variables.js +330 -207
- package/package.json +1 -1
- package/src/automation/runner.ts +2 -1
- package/src/code-check.ts +158 -83
- package/src/components/macros/Button.tsx +3 -1
- package/src/components/macros/Checkbox.tsx +4 -1
- package/src/components/macros/Dialog.tsx +1 -1
- package/src/components/macros/MacroLink.tsx +3 -2
- package/src/components/macros/Meter.tsx +6 -2
- package/src/components/macros/Radiobutton.tsx +2 -2
- package/src/components/macros/Watch.tsx +11 -6
- package/src/components/macros/arg-utils.ts +0 -37
- package/src/components/macros/input-macro.ts +4 -1
- package/src/components/macros/macro-args.ts +93 -52
- package/src/components/macros/option-utils.ts +1 -1
- package/src/index.tsx +2 -4
- package/src/js-lexer.ts +19 -0
- package/src/markup/parse.ts +22 -4
- package/src/markup/spindle.peggy +21 -9
- package/src/markup/tokens.ts +3 -0
- package/src/markup/validate.ts +13 -10
- package/src/registry.ts +63 -6
- package/src/saves/storage.ts +14 -1
- package/src/store.ts +29 -7
- package/src/story-variables.ts +31 -65
- package/src/structural.ts +67 -21
- package/src/tooling.ts +15 -16
- package/src/widgets/widget-def.ts +2 -2
- package/types/index.d.ts +23 -0
- package/types/tooling.d.ts +28 -2
|
@@ -68,24 +68,41 @@ var LINK_SEPARATORS = [
|
|
|
68
68
|
["<-", true]
|
|
69
69
|
];
|
|
70
70
|
var NO_SELECTORS = {};
|
|
71
|
-
|
|
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
|
+
};
|
|
72
87
|
for (const [separator, targetFirst] of LINK_SEPARATORS) {
|
|
73
88
|
const idx = inner.indexOf(separator);
|
|
74
89
|
if (idx === -1) continue;
|
|
75
|
-
const before =
|
|
76
|
-
const after =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
98
|
};
|
|
84
99
|
}
|
|
85
|
-
const
|
|
100
|
+
const whole = part(0, inner.length);
|
|
86
101
|
return {
|
|
87
|
-
display:
|
|
88
|
-
target:
|
|
102
|
+
display: whole.value,
|
|
103
|
+
target: whole.value,
|
|
104
|
+
targetStart: whole.start,
|
|
105
|
+
targetEnd: whole.end
|
|
89
106
|
};
|
|
90
107
|
}
|
|
91
108
|
function quoteArg(value) {
|
|
@@ -167,6 +184,8 @@ function tokenOf(x) {
|
|
|
167
184
|
type: "link",
|
|
168
185
|
display: x.display,
|
|
169
186
|
target: x.target,
|
|
187
|
+
targetStart: x.targetStart,
|
|
188
|
+
targetEnd: x.targetEnd,
|
|
170
189
|
...span
|
|
171
190
|
}, x);
|
|
172
191
|
case "variable": return withSelectors({
|
|
@@ -698,8 +717,8 @@ function peg$parse(input, options) {
|
|
|
698
717
|
function peg$f48() {
|
|
699
718
|
return !textMode;
|
|
700
719
|
}
|
|
701
|
-
function peg$f49(s, inner) {
|
|
702
|
-
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);
|
|
703
722
|
}
|
|
704
723
|
function peg$f50() {
|
|
705
724
|
return !textMode;
|
|
@@ -1748,7 +1767,7 @@ function peg$parse(input, options) {
|
|
|
1748
1767
|
return s0;
|
|
1749
1768
|
}
|
|
1750
1769
|
function peg$parseLinkLexeme() {
|
|
1751
|
-
let s0, s1, s2, s3, s4, s5;
|
|
1770
|
+
let s0, s1, s2, s3, s4, s5, s6;
|
|
1752
1771
|
s0 = peg$currPos;
|
|
1753
1772
|
peg$savedPos = peg$currPos;
|
|
1754
1773
|
s1 = peg$f48();
|
|
@@ -1765,19 +1784,20 @@ function peg$parse(input, options) {
|
|
|
1765
1784
|
if (s2 !== peg$FAILED) {
|
|
1766
1785
|
s3 = peg$parseSelectors();
|
|
1767
1786
|
if (s3 !== peg$FAILED) {
|
|
1768
|
-
s4 = peg$
|
|
1769
|
-
s5 = peg$
|
|
1770
|
-
|
|
1787
|
+
s4 = peg$parseHere();
|
|
1788
|
+
s5 = peg$currPos;
|
|
1789
|
+
s6 = peg$parseLinkText();
|
|
1790
|
+
s5 = input.substring(s5, peg$currPos);
|
|
1771
1791
|
if (input.substr(peg$currPos, 2) === peg$c5) {
|
|
1772
|
-
|
|
1792
|
+
s6 = peg$c5;
|
|
1773
1793
|
peg$currPos += 2;
|
|
1774
1794
|
} else {
|
|
1775
|
-
|
|
1795
|
+
s6 = peg$FAILED;
|
|
1776
1796
|
if (peg$silentFails === 0) peg$fail(peg$e9);
|
|
1777
1797
|
}
|
|
1778
|
-
if (
|
|
1798
|
+
if (s6 !== peg$FAILED) {
|
|
1779
1799
|
peg$savedPos = s0;
|
|
1780
|
-
s0 = peg$f49(s3, s4);
|
|
1800
|
+
s0 = peg$f49(s3, s4, s5);
|
|
1781
1801
|
} else {
|
|
1782
1802
|
peg$currPos = s0;
|
|
1783
1803
|
s0 = peg$FAILED;
|
|
@@ -9410,6 +9430,22 @@ function regexEnd(src, start) {
|
|
|
9410
9430
|
return -1;
|
|
9411
9431
|
}
|
|
9412
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
|
+
/**
|
|
9413
9449
|
* Scan the `"…"` or `'…'` string literal opening at `start`. `end` is the
|
|
9414
9450
|
* index just past its closing quote, or `src.length` when it is unterminated
|
|
9415
9451
|
* (`closed` false). A backslash escapes the character after it, so a quote
|
|
@@ -9668,10 +9704,22 @@ function parseSelectors(source, at = 0) {
|
|
|
9668
9704
|
function tokenizeMarkupTolerant(source, options = {}) {
|
|
9669
9705
|
const tokens = [];
|
|
9670
9706
|
const errors = [];
|
|
9707
|
+
const lastBrace = source.lastIndexOf("}");
|
|
9708
|
+
const { closeBrace } = {
|
|
9709
|
+
...defaultHooks,
|
|
9710
|
+
...options.hooks
|
|
9711
|
+
};
|
|
9671
9712
|
/** `text` shifted by `base`, tokenized: the tokens or the error. */
|
|
9672
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
|
+
};
|
|
9673
9718
|
try {
|
|
9674
|
-
return tokenizeMarkup(text,
|
|
9719
|
+
return tokenizeMarkup(text, {
|
|
9720
|
+
...options,
|
|
9721
|
+
hooks
|
|
9722
|
+
}).map((token) => shift(token, base));
|
|
9675
9723
|
} catch (error) {
|
|
9676
9724
|
if (!(error instanceof MarkupError)) throw error;
|
|
9677
9725
|
return error;
|
|
@@ -9713,11 +9761,17 @@ function tokenizeMarkupTolerant(source, options = {}) {
|
|
|
9713
9761
|
}
|
|
9714
9762
|
/** `token` with its offsets moved `by` on. */
|
|
9715
9763
|
function shift(token, by) {
|
|
9716
|
-
|
|
9764
|
+
if (by === 0) return token;
|
|
9765
|
+
const moved = {
|
|
9717
9766
|
...token,
|
|
9718
9767
|
start: token.start + by,
|
|
9719
9768
|
end: token.end + by
|
|
9720
9769
|
};
|
|
9770
|
+
if (moved.type === "link") {
|
|
9771
|
+
moved.targetStart += by;
|
|
9772
|
+
moved.targetEnd += by;
|
|
9773
|
+
}
|
|
9774
|
+
return moved;
|
|
9721
9775
|
}
|
|
9722
9776
|
//#endregion
|
|
9723
9777
|
//#region src/utils/parse-delay.ts
|
|
@@ -9805,37 +9859,6 @@ function readWholeQuoted(src) {
|
|
|
9805
9859
|
const quoted = readQuoted(src, 0);
|
|
9806
9860
|
return quoted && quoted.end === src.length ? quoted.value : null;
|
|
9807
9861
|
}
|
|
9808
|
-
var JS_SIMPLE_ESCAPES = {
|
|
9809
|
-
n: "\n",
|
|
9810
|
-
t: " ",
|
|
9811
|
-
r: "\r",
|
|
9812
|
-
b: "\b",
|
|
9813
|
-
f: "\f",
|
|
9814
|
-
v: "\v",
|
|
9815
|
-
"0": "\0"
|
|
9816
|
-
};
|
|
9817
|
-
/** Undo every escape of a JavaScript string literal's body. */
|
|
9818
|
-
function decodeJsEscapes(body) {
|
|
9819
|
-
return body.replace(/\\(?:u\{([0-9a-fA-F]+)\}|u([0-9a-fA-F]{4})|x([0-9a-fA-F]{2})|(\r\n|[\n\r\u2028\u2029])|([\s\S]))/g, (_, braced, unicode, hex, lineBreak, other) => {
|
|
9820
|
-
if (lineBreak !== void 0) return "";
|
|
9821
|
-
const code = braced ?? unicode ?? hex;
|
|
9822
|
-
if (code !== void 0) {
|
|
9823
|
-
const point = parseInt(code, 16);
|
|
9824
|
-
return point <= 1114111 ? String.fromCodePoint(point) : "";
|
|
9825
|
-
}
|
|
9826
|
-
return JS_SIMPLE_ESCAPES[other] ?? other;
|
|
9827
|
-
});
|
|
9828
|
-
}
|
|
9829
|
-
/**
|
|
9830
|
-
* The value of `src` when it is exactly one `"…"` or `'…'` string, read as
|
|
9831
|
-
* the JavaScript literal it is (`"\u0048all"` is `Hall`), otherwise `null`.
|
|
9832
|
-
* `readWholeQuoted` keeps other escapes as written, for labels and code.
|
|
9833
|
-
*/
|
|
9834
|
-
function readWholeJsString(src) {
|
|
9835
|
-
const quoted = readQuoted(src, 0);
|
|
9836
|
-
if (!quoted || quoted.end !== src.length) return null;
|
|
9837
|
-
return decodeJsEscapes(src.slice(1, -1));
|
|
9838
|
-
}
|
|
9839
9862
|
/**
|
|
9840
9863
|
* Strip an optional quote from each end of an unquoted-or-loosely-quoted
|
|
9841
9864
|
* argument (`"Red`, `Red'`), leaving at least one character. This is the
|
|
@@ -9995,7 +10018,9 @@ var MacroArgumentError = class extends Error {
|
|
|
9995
10018
|
this.name = "MacroArgumentError";
|
|
9996
10019
|
}
|
|
9997
10020
|
};
|
|
9998
|
-
|
|
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]);
|
|
9999
10024
|
switch (param.type) {
|
|
10000
10025
|
case "string": {
|
|
10001
10026
|
const value = readWholeQuoted(src);
|
|
@@ -10006,7 +10031,7 @@ function readValue(param, src) {
|
|
|
10006
10031
|
case "names": return src.split(",").map((name) => name.trim());
|
|
10007
10032
|
case "delay": return parseDelay(src);
|
|
10008
10033
|
case "number": return Number(src);
|
|
10009
|
-
case "options": return readOptions(param.parameters ?? [], src);
|
|
10034
|
+
case "options": return readOptions(param.parameters ?? [], src, place);
|
|
10010
10035
|
default: return src;
|
|
10011
10036
|
}
|
|
10012
10037
|
}
|
|
@@ -10024,11 +10049,12 @@ function matchAt(re, src, pos) {
|
|
|
10024
10049
|
}
|
|
10025
10050
|
/**
|
|
10026
10051
|
* Read keyword options (`goto "X" priority 5 once`). A keyword takes the
|
|
10027
|
-
* quoted string or
|
|
10028
|
-
* declared, and their values, are
|
|
10029
|
-
* must be followed by a quoted
|
|
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.
|
|
10030
10056
|
*/
|
|
10031
|
-
function readOptions(params, src) {
|
|
10057
|
+
function readOptions(params, src, { at, spans }) {
|
|
10032
10058
|
const options = {};
|
|
10033
10059
|
let i = 0;
|
|
10034
10060
|
while (i < src.length) {
|
|
@@ -10039,22 +10065,20 @@ function readOptions(params, src) {
|
|
|
10039
10065
|
}
|
|
10040
10066
|
i += key.length;
|
|
10041
10067
|
let val;
|
|
10042
|
-
let quoted = null;
|
|
10043
10068
|
let j = i;
|
|
10044
10069
|
while (j < src.length && isWhitespace(src[j])) j++;
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
i = j + digits.length;
|
|
10054
|
-
}
|
|
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;
|
|
10055
10078
|
}
|
|
10056
10079
|
const param = params.find((p) => p.name === key);
|
|
10057
|
-
if (param
|
|
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) ?? "");
|
|
10058
10082
|
if (param?.type === "flag") options[key] = true;
|
|
10059
10083
|
else if (param?.type === "number") options[key] = Number(val ?? 0);
|
|
10060
10084
|
else if (param) options[key] = val;
|
|
@@ -10064,21 +10088,21 @@ function readOptions(params, src) {
|
|
|
10064
10088
|
/** `inline %name` reads a transient variable; `inline % 2` is modulo. */
|
|
10065
10089
|
var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
|
|
10066
10090
|
/**
|
|
10067
|
-
*
|
|
10068
|
-
* `null` if it has no such flag. Next to a binary operator the
|
|
10069
|
-
* operand (`"a" + inline`), not a flag; the closing `/` of a
|
|
10070
|
-
* 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.
|
|
10071
10095
|
*/
|
|
10072
10096
|
function takeFlag(src, word) {
|
|
10073
10097
|
const spans = terms(src);
|
|
10074
10098
|
if (spans.length < 2) return null;
|
|
10075
10099
|
if (slice(src, spans[spans.length - 1]) === word) {
|
|
10076
|
-
const rest =
|
|
10077
|
-
if (!endsWithOperator(rest)) return rest;
|
|
10100
|
+
const rest = [0, spans[spans.length - 2][1]];
|
|
10101
|
+
if (!endsWithOperator(slice(src, rest))) return rest;
|
|
10078
10102
|
}
|
|
10079
10103
|
if (slice(src, spans[0]) === word) {
|
|
10080
|
-
const rest =
|
|
10081
|
-
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;
|
|
10082
10106
|
}
|
|
10083
10107
|
return null;
|
|
10084
10108
|
}
|
|
@@ -10106,69 +10130,93 @@ function findSeparator(src, word) {
|
|
|
10106
10130
|
return null;
|
|
10107
10131
|
}
|
|
10108
10132
|
var takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
|
|
10109
|
-
/**
|
|
10110
|
-
function
|
|
10111
|
-
|
|
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);
|
|
10112
10144
|
for (const flag of params.filter((p) => p.type === "flag")) {
|
|
10113
10145
|
const rest = src === null ? null : takeFlag(text, flag.name);
|
|
10114
10146
|
args[flag.name] = rest !== null;
|
|
10115
|
-
text = rest
|
|
10147
|
+
if (rest) [text, at] = trimmed(slice(text, rest), at + rest[0]);
|
|
10116
10148
|
}
|
|
10117
10149
|
const positional = params.filter((p) => p.type !== "flag");
|
|
10118
10150
|
if (positional.length === 0) return;
|
|
10119
10151
|
const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
|
|
10120
10152
|
const restIndex = lastText < 0 ? positional.length - 1 : lastText;
|
|
10121
|
-
|
|
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
|
+
});
|
|
10122
10158
|
if (positional.length === 1) {
|
|
10123
|
-
args[positional[0].name] = read(positional[0], text);
|
|
10159
|
+
args[positional[0].name] = read(positional[0], [0, text.length]);
|
|
10124
10160
|
return;
|
|
10125
10161
|
}
|
|
10126
|
-
const
|
|
10162
|
+
const parts = terms(text);
|
|
10127
10163
|
let from = 0;
|
|
10128
|
-
let to =
|
|
10129
|
-
for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param,
|
|
10130
|
-
const readTrailing = (param,
|
|
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) => {
|
|
10131
10167
|
try {
|
|
10132
|
-
return read(param,
|
|
10168
|
+
return read(param, span);
|
|
10133
10169
|
} catch (error) {
|
|
10134
10170
|
if (error instanceof MacroArgumentError) return void 0;
|
|
10135
10171
|
throw error;
|
|
10136
10172
|
}
|
|
10137
10173
|
};
|
|
10138
10174
|
for (const param of positional.slice(restIndex + 1).reverse()) {
|
|
10139
|
-
const value = from < to && !endsWithOperator(text.slice(0,
|
|
10175
|
+
const value = from < to && !endsWithOperator(text.slice(0, parts[to - 1][0])) ? readTrailing(param, parts[to - 1]) : void 0;
|
|
10140
10176
|
args[param.name] = value ?? unset(param);
|
|
10141
10177
|
if (value !== void 0) to--;
|
|
10178
|
+
else spans?.delete(param);
|
|
10142
10179
|
}
|
|
10143
10180
|
const rest = positional[restIndex];
|
|
10144
|
-
args[rest.name] = from < to ? read(rest,
|
|
10181
|
+
args[rest.name] = from < to ? read(rest, [parts[from][0], parts[to - 1][1]]) : unset(rest);
|
|
10145
10182
|
}
|
|
10146
|
-
/**
|
|
10147
|
-
|
|
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) {
|
|
10148
10188
|
const args = {};
|
|
10149
|
-
let rest = rawArgs
|
|
10189
|
+
let [rest, restAt] = trimmed(rawArgs, 0);
|
|
10150
10190
|
let groupStart = 0;
|
|
10151
10191
|
parameters.forEach((separator, i) => {
|
|
10152
10192
|
if (separator.type !== "separator") return;
|
|
10153
10193
|
const at = rest === null ? null : findSeparator(rest, separator.name);
|
|
10154
10194
|
args[separator.name] = at !== null;
|
|
10155
10195
|
const before = at ? rest.slice(0, at[0]) : rest;
|
|
10156
|
-
readGroup(parameters.slice(groupStart, i), before, args
|
|
10196
|
+
readGroup(parameters.slice(groupStart, i), before, args, {
|
|
10197
|
+
at: restAt,
|
|
10198
|
+
spans
|
|
10199
|
+
});
|
|
10157
10200
|
rest = at ? rest.slice(at[1]) : null;
|
|
10201
|
+
if (at) restAt += at[1];
|
|
10158
10202
|
groupStart = i + 1;
|
|
10159
10203
|
});
|
|
10160
|
-
readGroup(parameters.slice(groupStart), rest, args
|
|
10204
|
+
readGroup(parameters.slice(groupStart), rest, args, {
|
|
10205
|
+
at: restAt,
|
|
10206
|
+
spans
|
|
10207
|
+
});
|
|
10161
10208
|
return args;
|
|
10162
10209
|
}
|
|
10163
10210
|
/**
|
|
10164
10211
|
* Read a `passage` argument as written (`{goto "Hall"}`, `{goto $room}`): a
|
|
10165
|
-
*
|
|
10166
|
-
* `Hall`), anything else
|
|
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
|
|
10167
10215
|
* runs (see evaluatePassageName).
|
|
10168
10216
|
*/
|
|
10169
10217
|
function passageTarget(arg) {
|
|
10170
10218
|
const expression = arg.trim();
|
|
10171
|
-
const name =
|
|
10219
|
+
const name = stringLiteralValue(expression);
|
|
10172
10220
|
return name === null ? {
|
|
10173
10221
|
kind: "expression",
|
|
10174
10222
|
expression
|
|
@@ -10198,10 +10246,12 @@ function evaluatePassageName(expr, evaluate, { storyData, currentPassage }) {
|
|
|
10198
10246
|
var WIDGET_PARAMETERS = [{
|
|
10199
10247
|
name: "name",
|
|
10200
10248
|
type: "text",
|
|
10249
|
+
holds: "text",
|
|
10201
10250
|
required: true
|
|
10202
10251
|
}, {
|
|
10203
10252
|
name: "parameters",
|
|
10204
|
-
type: "text"
|
|
10253
|
+
type: "text",
|
|
10254
|
+
holds: "text"
|
|
10205
10255
|
}];
|
|
10206
10256
|
/**
|
|
10207
10257
|
* The widget a {widget} definition's arguments declare: its name and its
|
|
@@ -10233,6 +10283,17 @@ function blockWidgetNames(passages) {
|
|
|
10233
10283
|
}
|
|
10234
10284
|
return names;
|
|
10235
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
|
+
}
|
|
10236
10297
|
//#endregion
|
|
10237
10298
|
//#region src/code-check.ts
|
|
10238
10299
|
/** While a pass runs: the code it parsed so far, by goal and source. */
|
|
@@ -10270,30 +10331,59 @@ var CONDITION_MACROS = new Set([
|
|
|
10270
10331
|
"elseif",
|
|
10271
10332
|
"case"
|
|
10272
10333
|
]);
|
|
10273
|
-
/** Code inside the quoted strings of a macro's arguments, by parameter. */
|
|
10274
|
-
var CODE_IN_STRINGS = new Map([["watch", {
|
|
10275
|
-
condition: "expression",
|
|
10276
|
-
run: "statements"
|
|
10277
|
-
}]]);
|
|
10278
|
-
/** The `string` parameters of a macro that name a passage. */
|
|
10279
|
-
var PASSAGE_STRINGS = new Map([["watch", ["goto", "dialog"]]]);
|
|
10280
|
-
/**
|
|
10281
|
-
* The `text` and `string` parameters of a macro that the runtime keeps as
|
|
10282
|
-
* written, not as markup: an option's value, the name of a watcher.
|
|
10283
|
-
*/
|
|
10284
|
-
var LITERAL_STRINGS = new Map([
|
|
10285
|
-
["option", ["value"]],
|
|
10286
|
-
["watch", ["name"]],
|
|
10287
|
-
["unwatch", ["name"]]
|
|
10288
|
-
]);
|
|
10289
10334
|
/** Block macros whose body is the name of a passage. */
|
|
10290
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";
|
|
10291
10373
|
/** The goal of the code an argument of this type holds, if it is code. */
|
|
10292
10374
|
function codeGoal(param) {
|
|
10293
10375
|
if (param.type === "expression" || param.type === "passage") return "expression";
|
|
10294
10376
|
if (param.type === "statements") return "statements";
|
|
10295
10377
|
}
|
|
10296
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
|
+
/**
|
|
10297
10387
|
* The code and the markup-holding text in `tokens`, the tokens of `src`.
|
|
10298
10388
|
* `parametersOf` gives the declared parameters of a macro, if it has any.
|
|
10299
10389
|
*/
|
|
@@ -10305,8 +10395,8 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
10305
10395
|
if (token.type === "link") yield {
|
|
10306
10396
|
kind: "passage",
|
|
10307
10397
|
name: token.target,
|
|
10308
|
-
offset:
|
|
10309
|
-
length: token.
|
|
10398
|
+
offset: token.targetStart,
|
|
10399
|
+
length: token.targetEnd - token.targetStart,
|
|
10310
10400
|
macro: "link",
|
|
10311
10401
|
label: src.slice(token.start, token.end)
|
|
10312
10402
|
};
|
|
@@ -10317,32 +10407,35 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
10317
10407
|
goal: "expression",
|
|
10318
10408
|
label: `{${token.expression}}`
|
|
10319
10409
|
};
|
|
10320
|
-
else if (token.type === "html")
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
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
|
+
}
|
|
10343
10437
|
}
|
|
10344
|
-
}
|
|
10345
|
-
else if (token.type === "macro" && !token.isClose) {
|
|
10438
|
+
} else if (token.type === "macro" && !token.isClose) {
|
|
10346
10439
|
const name = token.name.toLowerCase();
|
|
10347
10440
|
if (name === "do") {
|
|
10348
10441
|
const close = closeOf(t, name);
|
|
@@ -10362,12 +10455,13 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
10362
10455
|
const close = closeOf(t, name);
|
|
10363
10456
|
const body = tokens.slice(t + 1, close);
|
|
10364
10457
|
if (close > t + 1 && body.every((b) => b.type === "text")) {
|
|
10365
|
-
const
|
|
10458
|
+
const text = src.slice(token.end, tokens[close].start);
|
|
10459
|
+
const written = text.trim();
|
|
10366
10460
|
yield {
|
|
10367
10461
|
kind: "passage",
|
|
10368
|
-
name:
|
|
10369
|
-
offset:
|
|
10370
|
-
length:
|
|
10462
|
+
name: written.replace(/^["']|["']$/g, ""),
|
|
10463
|
+
offset: token.end + text.indexOf(written),
|
|
10464
|
+
length: written.length,
|
|
10371
10465
|
macro: name,
|
|
10372
10466
|
label: src.slice(token.start, token.end)
|
|
10373
10467
|
};
|
|
@@ -10375,7 +10469,8 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
10375
10469
|
}
|
|
10376
10470
|
if (!args) continue;
|
|
10377
10471
|
const label = `{${token.name} ${args}}`;
|
|
10378
|
-
const
|
|
10472
|
+
const tag = src.slice(token.start, token.end - 1);
|
|
10473
|
+
const argsAt = token.start + tag.trimEnd().length - args.length;
|
|
10379
10474
|
if (CONDITION_MACROS.has(name)) {
|
|
10380
10475
|
yield {
|
|
10381
10476
|
kind: "code",
|
|
@@ -10394,8 +10489,9 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
10394
10489
|
/** The code and text in the arguments `args` (at `offset`) of `macro`. */
|
|
10395
10490
|
function* argPieces(args, offset, params, macro, label) {
|
|
10396
10491
|
let values;
|
|
10492
|
+
const spans = /* @__PURE__ */ new Map();
|
|
10397
10493
|
try {
|
|
10398
|
-
values = parseMacroArgs(args, params);
|
|
10494
|
+
values = parseMacroArgs(args, params, spans);
|
|
10399
10495
|
} catch (error) {
|
|
10400
10496
|
if (!(error instanceof MacroArgumentError)) throw error;
|
|
10401
10497
|
yield {
|
|
@@ -10406,11 +10502,7 @@ function* argPieces(args, offset, params, macro, label) {
|
|
|
10406
10502
|
};
|
|
10407
10503
|
return;
|
|
10408
10504
|
}
|
|
10409
|
-
const
|
|
10410
|
-
const inStrings = CODE_IN_STRINGS.get(key) ?? {};
|
|
10411
|
-
const passageStrings = PASSAGE_STRINGS.get(key) ?? [];
|
|
10412
|
-
const literalStrings = LITERAL_STRINGS.get(key) ?? [];
|
|
10413
|
-
let cursor = 0;
|
|
10505
|
+
const name = macro.toLowerCase();
|
|
10414
10506
|
/** The pieces of the parameters `list`, with their values in `from`. */
|
|
10415
10507
|
function* visit(list, from) {
|
|
10416
10508
|
for (const param of list) {
|
|
@@ -10419,47 +10511,62 @@ function* argPieces(args, offset, params, macro, label) {
|
|
|
10419
10511
|
yield* visit(param.parameters, value ?? {});
|
|
10420
10512
|
continue;
|
|
10421
10513
|
}
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
const
|
|
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
|
+
}
|
|
10426
10535
|
if (goal) {
|
|
10427
10536
|
const piece = {
|
|
10428
10537
|
kind: "code",
|
|
10429
10538
|
code: value,
|
|
10430
|
-
offset: offset +
|
|
10539
|
+
offset: offset + start,
|
|
10431
10540
|
goal,
|
|
10432
10541
|
label
|
|
10433
10542
|
};
|
|
10434
10543
|
if (param.type === "passage") {
|
|
10435
10544
|
piece.passage = true;
|
|
10436
|
-
piece.macro =
|
|
10545
|
+
piece.macro = name;
|
|
10437
10546
|
}
|
|
10438
|
-
if (!codeGoal(param)) piece.inString = true;
|
|
10439
10547
|
yield piece;
|
|
10548
|
+
continue;
|
|
10440
10549
|
}
|
|
10441
|
-
const
|
|
10442
|
-
const
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
length: written.length,
|
|
10451
|
-
macro: macro.toLowerCase(),
|
|
10452
|
-
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
|
|
10453
10559
|
};
|
|
10454
|
-
if (
|
|
10560
|
+
else if (holds === "passage") yield passage(value);
|
|
10561
|
+
else if (holds === "markup") yield {
|
|
10455
10562
|
kind: "text",
|
|
10456
10563
|
text: value,
|
|
10457
|
-
offset:
|
|
10564
|
+
offset: at,
|
|
10458
10565
|
where: `In the ${param.name} of {${macro}}: `
|
|
10459
10566
|
};
|
|
10460
10567
|
}
|
|
10461
10568
|
}
|
|
10462
|
-
yield* visit(params, values);
|
|
10569
|
+
yield* [...visit(params, values)].sort((a, b) => a.offset - b.offset);
|
|
10463
10570
|
}
|
|
10464
10571
|
/**
|
|
10465
10572
|
* Index of `part` in `text` from `from` on, else anywhere, else `from`: the
|
|
@@ -10475,9 +10582,10 @@ function locate(text, part, from) {
|
|
|
10475
10582
|
* The passages the markup `src` (with the `tokens` it tokenizes to) names:
|
|
10476
10583
|
* `[[…]]` links, the passage argument of `{goto}`, `{include}` and `{link}`
|
|
10477
10584
|
* (and of macros that declare one), the `goto` and `dialog` actions of
|
|
10478
|
-
* `{watch}` and the
|
|
10479
|
-
*
|
|
10480
|
-
*
|
|
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.
|
|
10481
10589
|
*/
|
|
10482
10590
|
function collectPassageReferences(src, tokens, parametersOf) {
|
|
10483
10591
|
const refs = [];
|
|
@@ -10492,21 +10600,14 @@ function collectPassageReferences(src, tokens, parametersOf) {
|
|
|
10492
10600
|
});
|
|
10493
10601
|
else if (piece.kind === "code" && piece.passage) refs.push({
|
|
10494
10602
|
macro: piece.macro,
|
|
10495
|
-
target:
|
|
10603
|
+
target: {
|
|
10604
|
+
kind: "expression",
|
|
10605
|
+
expression: piece.code
|
|
10606
|
+
},
|
|
10496
10607
|
start: piece.offset,
|
|
10497
10608
|
end: piece.offset + piece.code.length
|
|
10498
10609
|
});
|
|
10499
|
-
return refs
|
|
10500
|
-
}
|
|
10501
|
-
/** The parameters of the built-in sub-macros that take arguments. */
|
|
10502
|
-
var SUB_MACRO_PARAMETERS = new Map([["option", [{
|
|
10503
|
-
name: "value",
|
|
10504
|
-
type: "string",
|
|
10505
|
-
required: true
|
|
10506
|
-
}]]]);
|
|
10507
|
-
/** The parameters of the built-in sub-macro `name`, if it takes any. */
|
|
10508
|
-
function subMacroParameters(name) {
|
|
10509
|
-
return SUB_MACRO_PARAMETERS.get(name.toLowerCase());
|
|
10610
|
+
return refs;
|
|
10510
10611
|
}
|
|
10511
10612
|
//#endregion
|
|
10512
10613
|
//#region src/markup/validate.ts
|
|
@@ -10636,7 +10737,7 @@ function validateMarkup(passages, options) {
|
|
|
10636
10737
|
report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
|
|
10637
10738
|
}
|
|
10638
10739
|
};
|
|
10639
|
-
const parametersOf =
|
|
10740
|
+
const parametersOf = options.parametersOf ?? (() => void 0);
|
|
10640
10741
|
/**
|
|
10641
10742
|
* Check `tokens`, the tokens of `src`, which starts at `base` in the
|
|
10642
10743
|
* passage: their macros, the code in them and the markup in their text.
|
|
@@ -10719,20 +10820,45 @@ var PARAMETER_TYPES = [
|
|
|
10719
10820
|
"options"
|
|
10720
10821
|
];
|
|
10721
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
|
+
/**
|
|
10722
10843
|
* Throw if a parameter `macro` declares (or an option of one) has no type,
|
|
10723
10844
|
* or one that isn't a ParameterType: arguments are read by their type, and
|
|
10724
|
-
* there is no default.
|
|
10845
|
+
* there is no default. Throw too if it declares what it `holds` wrongly.
|
|
10725
10846
|
*/
|
|
10726
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).`;
|
|
10727
10849
|
for (const param of parameters) {
|
|
10728
10850
|
const type = param.type;
|
|
10729
|
-
if (!PARAMETER_TYPES.includes(type)) {
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
}
|
|
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);
|
|
10733
10855
|
if (param.parameters) checkParameterTypes(macro, param.parameters);
|
|
10734
10856
|
}
|
|
10735
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
|
+
}
|
|
10736
10862
|
new NameMap();
|
|
10737
10863
|
//#endregion
|
|
10738
10864
|
//#region src/utils/namespace.ts
|
|
@@ -10918,20 +11044,19 @@ var transformCache = /* @__PURE__ */ new Map();
|
|
|
10918
11044
|
*/
|
|
10919
11045
|
function validateStoryMarkup(passages, macros, options = {}) {
|
|
10920
11046
|
const list = [...passages];
|
|
11047
|
+
const all = [...macros];
|
|
10921
11048
|
const known = /* @__PURE__ */ new Set();
|
|
10922
|
-
const parameters = /* @__PURE__ */ new Map();
|
|
10923
11049
|
const blocks = new Set(blockWidgetNames(list).map((n) => n.toLowerCase()));
|
|
10924
|
-
for (const macro of
|
|
11050
|
+
for (const macro of all) {
|
|
10925
11051
|
const name = macro.name.toLowerCase();
|
|
10926
11052
|
known.add(name);
|
|
10927
|
-
parameters.set(name, macro.parameters);
|
|
10928
11053
|
for (const sub of macro.subMacros) known.add(sub.toLowerCase());
|
|
10929
11054
|
if (macro.block) blocks.add(name);
|
|
10930
11055
|
}
|
|
10931
11056
|
return validateMarkup(list, {
|
|
10932
11057
|
isKnownMacro: (name) => known.has(name),
|
|
10933
11058
|
macroNames: known,
|
|
10934
|
-
parametersOf: (
|
|
11059
|
+
parametersOf: parameterLookup(all),
|
|
10935
11060
|
isBlockMacro: (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name),
|
|
10936
11061
|
checkPassageNames: options.checkPassageNames
|
|
10937
11062
|
});
|
|
@@ -10942,10 +11067,8 @@ function validateStoryMarkup(passages, macros, options = {}) {
|
|
|
10942
11067
|
* skipped, so it reads half-typed markup.
|
|
10943
11068
|
*/
|
|
10944
11069
|
function collectStoryPassageReferences(source, macros) {
|
|
10945
|
-
const parameters = /* @__PURE__ */ new Map();
|
|
10946
|
-
for (const macro of macros) parameters.set(macro.name.toLowerCase(), macro.parameters);
|
|
10947
11070
|
const { tokens } = tokenizeMarkupTolerant(source);
|
|
10948
|
-
return collectPassageReferences(source, tokens, (
|
|
11071
|
+
return collectPassageReferences(source, tokens, parameterLookup(macros));
|
|
10949
11072
|
}
|
|
10950
11073
|
//#endregion
|
|
10951
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 };
|