@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
package/dist/pkg/headless.js
CHANGED
|
@@ -103,24 +103,41 @@ var LINK_SEPARATORS = [
|
|
|
103
103
|
["<-", true]
|
|
104
104
|
];
|
|
105
105
|
var NO_SELECTORS = {};
|
|
106
|
-
|
|
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 =
|
|
111
|
-
const after =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
135
|
+
const whole = part(0, inner.length);
|
|
121
136
|
return {
|
|
122
|
-
display:
|
|
123
|
-
target:
|
|
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$
|
|
1804
|
-
s5 = peg$
|
|
1805
|
-
|
|
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
|
-
|
|
1827
|
+
s6 = peg$c5;
|
|
1808
1828
|
peg$currPos += 2;
|
|
1809
1829
|
} else {
|
|
1810
|
-
|
|
1830
|
+
s6 = peg$FAILED;
|
|
1811
1831
|
if (peg$silentFails === 0) peg$fail(peg$e9);
|
|
1812
1832
|
}
|
|
1813
|
-
if (
|
|
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
|
-
|
|
10158
|
-
|
|
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
|
|
@@ -13050,7 +13080,15 @@ function createIDBBackend() {
|
|
|
13050
13080
|
const rows = name === "meta";
|
|
13051
13081
|
/** Run one request in a transaction of its own. */
|
|
13052
13082
|
const request = async (mode, make) => {
|
|
13053
|
-
|
|
13083
|
+
const tx = (await openDB()).transaction(name, mode);
|
|
13084
|
+
const result = idbReq(make(tx.objectStore(name)));
|
|
13085
|
+
if (mode === "readonly") return result;
|
|
13086
|
+
const committed = new Promise((resolve, reject) => {
|
|
13087
|
+
tx.oncomplete = () => resolve();
|
|
13088
|
+
tx.onabort = () => reject(tx.error ?? new DOMException("Transaction aborted", "AbortError"));
|
|
13089
|
+
});
|
|
13090
|
+
const [value] = await Promise.all([result, committed]);
|
|
13091
|
+
return value;
|
|
13054
13092
|
};
|
|
13055
13093
|
return {
|
|
13056
13094
|
async get(key) {
|
|
@@ -13289,15 +13327,22 @@ var ERROR_HIDDEN_KEYS = [
|
|
|
13289
13327
|
"errors"
|
|
13290
13328
|
];
|
|
13291
13329
|
function deepClone(value, options = {}) {
|
|
13292
|
-
const seen = /* @__PURE__ */ new Map();
|
|
13330
|
+
const seen = options.seen ?? /* @__PURE__ */ new Map();
|
|
13293
13331
|
/** Record `copy` as the copy of `obj` before its contents are copied. */
|
|
13294
13332
|
const keep = (obj, copy) => {
|
|
13295
13333
|
seen.set(obj, copy);
|
|
13296
13334
|
return copy;
|
|
13297
13335
|
};
|
|
13298
|
-
/**
|
|
13336
|
+
/**
|
|
13337
|
+
* Copy the own (enumerable) keys of `obj` into `copy`, symbol keys too: a
|
|
13338
|
+
* save refuses them, which a copy must not hide.
|
|
13339
|
+
*/
|
|
13299
13340
|
function copyKeys(obj, copy) {
|
|
13300
13341
|
for (const key of Object.keys(obj)) setOwn(copy, key, clone(obj[key]));
|
|
13342
|
+
for (const sym of Object.getOwnPropertySymbols(obj)) {
|
|
13343
|
+
if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) continue;
|
|
13344
|
+
copy[sym] = clone(obj[sym]);
|
|
13345
|
+
}
|
|
13301
13346
|
return copy;
|
|
13302
13347
|
}
|
|
13303
13348
|
function cloneBuiltin(val) {
|
|
@@ -13380,65 +13425,83 @@ function loosePairs() {
|
|
|
13380
13425
|
/**
|
|
13381
13426
|
* Pairs that also require the same reference structure: each object of one
|
|
13382
13427
|
* value pairs with exactly one of the other, so shared references and
|
|
13383
|
-
* cycles are shared at the same places.
|
|
13428
|
+
* cycles are shared at the same places. `ab` and `ba` hold the pairs (and
|
|
13429
|
+
* may hold some already); those made are listed in `added`.
|
|
13384
13430
|
*/
|
|
13385
|
-
function strictPairs() {
|
|
13386
|
-
const
|
|
13387
|
-
const
|
|
13388
|
-
const pairs = (a, b) => {
|
|
13431
|
+
function strictPairs(ab = /* @__PURE__ */ new Map(), ba = /* @__PURE__ */ new Map()) {
|
|
13432
|
+
const added = [];
|
|
13433
|
+
const pairs = ((a, b) => {
|
|
13389
13434
|
const known = ab.get(a);
|
|
13390
13435
|
if (known !== void 0) return known === b;
|
|
13391
13436
|
if (ba.has(b)) return false;
|
|
13392
13437
|
ab.set(a, b);
|
|
13393
13438
|
ba.set(b, a);
|
|
13394
|
-
|
|
13439
|
+
added.push([a, b]);
|
|
13440
|
+
});
|
|
13395
13441
|
pairs.strict = true;
|
|
13442
|
+
pairs.added = added;
|
|
13396
13443
|
return pairs;
|
|
13397
13444
|
}
|
|
13398
13445
|
/**
|
|
13399
|
-
* Whether `a` and `b` are equal as deepEqual() has it and also share
|
|
13400
|
-
* references at the same places (a save of one loads as the other).
|
|
13401
|
-
*/
|
|
13402
|
-
function deepEqualStrict(a, b) {
|
|
13403
|
-
return equal(a, b, strictPairs());
|
|
13404
|
-
}
|
|
13405
|
-
/**
|
|
13406
13446
|
* `curr` with each part that equals (see deepEqualStrict) the part of
|
|
13407
13447
|
* `prev` at the same place replaced by `prev`'s: the history moments of a
|
|
13408
13448
|
* save then share what did not change between them, which a save stores
|
|
13409
13449
|
* once. Containers on the way to a change are copied; neither value is
|
|
13410
|
-
* changed.
|
|
13450
|
+
* changed. The result has the reference structure of `curr`: what `curr`
|
|
13451
|
+
* shares or cycles through stays so, and what it does not share stays
|
|
13452
|
+
* apart, whichever parts are reused.
|
|
13411
13453
|
*/
|
|
13412
13454
|
function shareEqual(prev, curr) {
|
|
13455
|
+
if (!isObjectValue(prev) || !isObjectValue(curr)) return curr;
|
|
13456
|
+
/** The object of the result standing for each object of `curr` met. */
|
|
13413
13457
|
const done = /* @__PURE__ */ new Map();
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13458
|
+
/** The object of `curr` each object of `prev` in the result stands for. */
|
|
13459
|
+
const used = /* @__PURE__ */ new Map();
|
|
13460
|
+
/** `value` for the result: copied, as far as it is not met already. */
|
|
13461
|
+
const fresh = (value) => isObjectValue(value) ? deepClone(value, {
|
|
13462
|
+
keepUnregistered: true,
|
|
13463
|
+
seen: done
|
|
13464
|
+
}) : value;
|
|
13465
|
+
/**
|
|
13466
|
+
* Whether `p` and `c` are equal and so can stand for each other, which
|
|
13467
|
+
* needs each object in them to stand for one object only, in the whole
|
|
13468
|
+
* result. Then every pair of objects they hold is placed in the result.
|
|
13469
|
+
*/
|
|
13470
|
+
function reuse(p, c) {
|
|
13471
|
+
const pairs = strictPairs(used, done);
|
|
13472
|
+
if (equal(p, c, pairs)) return true;
|
|
13473
|
+
for (const [a, b] of pairs.added) {
|
|
13474
|
+
used.delete(a);
|
|
13475
|
+
done.delete(b);
|
|
13420
13476
|
}
|
|
13477
|
+
return false;
|
|
13478
|
+
}
|
|
13479
|
+
function share(p, c) {
|
|
13480
|
+
if (!isObjectValue(c)) return c;
|
|
13481
|
+
const placed = done.get(c);
|
|
13482
|
+
if (placed !== void 0) return placed;
|
|
13483
|
+
if (!isObjectValue(p)) return fresh(c);
|
|
13484
|
+
if (reuse(p, c)) return p;
|
|
13421
13485
|
if (c instanceof Map && p instanceof Map) {
|
|
13422
13486
|
const out = /* @__PURE__ */ new Map();
|
|
13423
13487
|
done.set(c, out);
|
|
13424
|
-
for (const [k, v] of c) out.set(
|
|
13488
|
+
for (const [k, v] of c) out.set(fresh(k), share(p.get(k), v));
|
|
13425
13489
|
return out;
|
|
13426
13490
|
}
|
|
13427
13491
|
if (Array.isArray(c) && Array.isArray(p)) {
|
|
13428
13492
|
const out = new Array(c.length);
|
|
13429
13493
|
done.set(c, out);
|
|
13430
|
-
for (let i = 0; i < c.length; i++) if (i in c) out[i] =
|
|
13494
|
+
for (let i = 0; i < c.length; i++) if (i in c) out[i] = share(p[i], c[i]);
|
|
13431
13495
|
return out;
|
|
13432
13496
|
}
|
|
13433
13497
|
if (mergesWith(p, c, false)) {
|
|
13434
13498
|
const out = Object.create(Object.getPrototypeOf(c));
|
|
13435
13499
|
done.set(c, out);
|
|
13436
13500
|
const cr = c;
|
|
13437
|
-
for (const key of Object.keys(cr)) setOwn(out, key, hasOwn(p, key) ?
|
|
13501
|
+
for (const key of Object.keys(cr)) setOwn(out, key, share(hasOwn(p, key) ? p[key] : void 0, cr[key]));
|
|
13438
13502
|
return out;
|
|
13439
13503
|
}
|
|
13440
|
-
|
|
13441
|
-
return c;
|
|
13504
|
+
return fresh(c);
|
|
13442
13505
|
}
|
|
13443
13506
|
return share(prev, curr);
|
|
13444
13507
|
}
|
|
@@ -14912,7 +14975,10 @@ var lastNavigationVars = createNamespace();
|
|
|
14912
14975
|
* object) or deleted. A recorded value is a deep copy, so that changing a
|
|
14913
14976
|
* live value in place (a class instance is not frozen) cannot change
|
|
14914
14977
|
* history; instances of unregistered classes are kept, as the store keeps
|
|
14915
|
-
* them (a save then refuses them).
|
|
14978
|
+
* them (a save then refuses them). The copies are made together, so what
|
|
14979
|
+
* variables share (an object, a cycle) stays shared in them, and a variable
|
|
14980
|
+
* left as it is that shares with a replaced one is recorded again, to share
|
|
14981
|
+
* with its copy.
|
|
14916
14982
|
*/
|
|
14917
14983
|
function computeVarPatches(prev, curr) {
|
|
14918
14984
|
const forward = [];
|
|
@@ -14920,11 +14986,33 @@ function computeVarPatches(prev, curr) {
|
|
|
14920
14986
|
key,
|
|
14921
14987
|
deleted: true
|
|
14922
14988
|
});
|
|
14923
|
-
|
|
14989
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14990
|
+
const record = (key) => forward.push({
|
|
14924
14991
|
key,
|
|
14925
14992
|
deleted: false,
|
|
14926
|
-
value: deepClone(curr[key], {
|
|
14993
|
+
value: deepClone(curr[key], {
|
|
14994
|
+
keepUnregistered: true,
|
|
14995
|
+
seen
|
|
14996
|
+
})
|
|
14927
14997
|
});
|
|
14998
|
+
const unchanged = [];
|
|
14999
|
+
for (const key of Object.keys(curr)) if (!hasOwn(prev, key) || !Object.is(prev[key], curr[key])) record(key);
|
|
15000
|
+
else if (typeof curr[key] === "object" && curr[key] !== null) unchanged.push(key);
|
|
15001
|
+
for (let found = seen.size > 0; found;) {
|
|
15002
|
+
found = false;
|
|
15003
|
+
for (const key of unchanged) {
|
|
15004
|
+
const probe = /* @__PURE__ */ new Map();
|
|
15005
|
+
deepClone(curr[key], {
|
|
15006
|
+
keepUnregistered: true,
|
|
15007
|
+
seen: probe
|
|
15008
|
+
});
|
|
15009
|
+
if (![...probe.keys()].some((object) => seen.has(object))) continue;
|
|
15010
|
+
record(key);
|
|
15011
|
+
unchanged.splice(unchanged.indexOf(key), 1);
|
|
15012
|
+
found = true;
|
|
15013
|
+
break;
|
|
15014
|
+
}
|
|
15015
|
+
}
|
|
14928
15016
|
return { forward };
|
|
14929
15017
|
}
|
|
14930
15018
|
/**
|
|
@@ -16567,7 +16655,9 @@ var MacroArgumentError = class extends Error {
|
|
|
16567
16655
|
this.name = "MacroArgumentError";
|
|
16568
16656
|
}
|
|
16569
16657
|
};
|
|
16570
|
-
|
|
16658
|
+
/** Read `src`, at `place`, as `param` says. */
|
|
16659
|
+
function readValue(param, src, place) {
|
|
16660
|
+
place.spans?.set(param, [place.at, place.at + src.length]);
|
|
16571
16661
|
switch (param.type) {
|
|
16572
16662
|
case "string": {
|
|
16573
16663
|
const value = readWholeQuoted(src);
|
|
@@ -16578,7 +16668,7 @@ function readValue(param, src) {
|
|
|
16578
16668
|
case "names": return src.split(",").map((name) => name.trim());
|
|
16579
16669
|
case "delay": return parseDelay(src);
|
|
16580
16670
|
case "number": return Number(src);
|
|
16581
|
-
case "options": return readOptions(param.parameters ?? [], src);
|
|
16671
|
+
case "options": return readOptions(param.parameters ?? [], src, place);
|
|
16582
16672
|
default: return src;
|
|
16583
16673
|
}
|
|
16584
16674
|
}
|
|
@@ -16596,11 +16686,12 @@ function matchAt(re, src, pos) {
|
|
|
16596
16686
|
}
|
|
16597
16687
|
/**
|
|
16598
16688
|
* Read keyword options (`goto "X" priority 5 once`). A keyword takes the
|
|
16599
|
-
* quoted string or
|
|
16600
|
-
* declared, and their values, are
|
|
16601
|
-
* must be followed by a quoted
|
|
16689
|
+
* quoted string (with or without whitespace before it) or digit run after
|
|
16690
|
+
* it as its value; keywords that aren't declared, and their values, are
|
|
16691
|
+
* skipped. A keyword of a `string` parameter must be followed by a quoted
|
|
16692
|
+
* string.
|
|
16602
16693
|
*/
|
|
16603
|
-
function readOptions(params, src) {
|
|
16694
|
+
function readOptions(params, src, { at, spans }) {
|
|
16604
16695
|
const options = {};
|
|
16605
16696
|
let i = 0;
|
|
16606
16697
|
while (i < src.length) {
|
|
@@ -16611,22 +16702,20 @@ function readOptions(params, src) {
|
|
|
16611
16702
|
}
|
|
16612
16703
|
i += key.length;
|
|
16613
16704
|
let val;
|
|
16614
|
-
let quoted = null;
|
|
16615
16705
|
let j = i;
|
|
16616
16706
|
while (j < src.length && isWhitespace(src[j])) j++;
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
i = j + digits.length;
|
|
16626
|
-
}
|
|
16707
|
+
const quoted = readQuoted(src, j);
|
|
16708
|
+
const digits = quoted || j === i ? null : matchAt(DIGITS_RE, src, j);
|
|
16709
|
+
if (quoted) {
|
|
16710
|
+
val = quoted.value;
|
|
16711
|
+
i = quoted.end;
|
|
16712
|
+
} else if (digits) {
|
|
16713
|
+
val = digits;
|
|
16714
|
+
i = j + digits.length;
|
|
16627
16715
|
}
|
|
16628
16716
|
const param = params.find((p) => p.name === key);
|
|
16629
|
-
if (param
|
|
16717
|
+
if (param && param.type !== "flag" && val !== void 0) spans?.set(param, [at + j, at + i]);
|
|
16718
|
+
if (param?.type === "string" && !quoted) throw new MacroArgumentError(param, matchAt(WORD_OR_PUNCT_RE, src, j) ?? "");
|
|
16630
16719
|
if (param?.type === "flag") options[key] = true;
|
|
16631
16720
|
else if (param?.type === "number") options[key] = Number(val ?? 0);
|
|
16632
16721
|
else if (param) options[key] = val;
|
|
@@ -16636,21 +16725,21 @@ function readOptions(params, src) {
|
|
|
16636
16725
|
/** `inline %name` reads a transient variable; `inline % 2` is modulo. */
|
|
16637
16726
|
var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
|
|
16638
16727
|
/**
|
|
16639
|
-
*
|
|
16640
|
-
* `null` if it has no such flag. Next to a binary operator the
|
|
16641
|
-
* operand (`"a" + inline`), not a flag; the closing `/` of a
|
|
16642
|
-
* is not one.
|
|
16728
|
+
* Where the rest of `src` is without the flag `word` as its last or first
|
|
16729
|
+
* term, or `null` if it has no such flag. Next to a binary operator the
|
|
16730
|
+
* word is an operand (`"a" + inline`), not a flag; the closing `/` of a
|
|
16731
|
+
* regex literal is not one.
|
|
16643
16732
|
*/
|
|
16644
16733
|
function takeFlag(src, word) {
|
|
16645
16734
|
const spans = terms(src);
|
|
16646
16735
|
if (spans.length < 2) return null;
|
|
16647
16736
|
if (slice(src, spans[spans.length - 1]) === word) {
|
|
16648
|
-
const rest =
|
|
16649
|
-
if (!endsWithOperator(rest)) return rest;
|
|
16737
|
+
const rest = [0, spans[spans.length - 2][1]];
|
|
16738
|
+
if (!endsWithOperator(slice(src, rest))) return rest;
|
|
16650
16739
|
}
|
|
16651
16740
|
if (slice(src, spans[0]) === word) {
|
|
16652
|
-
const rest =
|
|
16653
|
-
if (!LEADING_OPERATOR_RE.test(rest)) return rest;
|
|
16741
|
+
const rest = [spans[1][0], src.length];
|
|
16742
|
+
if (!LEADING_OPERATOR_RE.test(slice(src, rest))) return rest;
|
|
16654
16743
|
}
|
|
16655
16744
|
return null;
|
|
16656
16745
|
}
|
|
@@ -16678,42 +16767,55 @@ function findSeparator(src, word) {
|
|
|
16678
16767
|
return null;
|
|
16679
16768
|
}
|
|
16680
16769
|
var takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
|
|
16681
|
-
/**
|
|
16682
|
-
function
|
|
16683
|
-
|
|
16770
|
+
/** The text of `src` without the whitespace around it, and where it is. */
|
|
16771
|
+
function trimmed(src, at) {
|
|
16772
|
+
const text = src.trim();
|
|
16773
|
+
return [text, text ? at + src.indexOf(text) : at];
|
|
16774
|
+
}
|
|
16775
|
+
/**
|
|
16776
|
+
* Read the parameters of one group from `src` (`null`: no text), which is
|
|
16777
|
+
* at `at` in the raw arguments, into `args`.
|
|
16778
|
+
*/
|
|
16779
|
+
function readGroup(params, src, args, { at: srcAt, spans }) {
|
|
16780
|
+
let [text, at] = trimmed(src ?? "", srcAt);
|
|
16684
16781
|
for (const flag of params.filter((p) => p.type === "flag")) {
|
|
16685
16782
|
const rest = src === null ? null : takeFlag(text, flag.name);
|
|
16686
16783
|
args[flag.name] = rest !== null;
|
|
16687
|
-
text = rest
|
|
16784
|
+
if (rest) [text, at] = trimmed(slice(text, rest), at + rest[0]);
|
|
16688
16785
|
}
|
|
16689
16786
|
const positional = params.filter((p) => p.type !== "flag");
|
|
16690
16787
|
if (positional.length === 0) return;
|
|
16691
16788
|
const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
|
|
16692
16789
|
const restIndex = lastText < 0 ? positional.length - 1 : lastText;
|
|
16693
|
-
|
|
16790
|
+
/** Read `param` from the text from `start` to `end`. */
|
|
16791
|
+
const read = (param, [start, end]) => start === end ? unset(param) : readValue(param, text.slice(start, end), {
|
|
16792
|
+
at: at + start,
|
|
16793
|
+
spans
|
|
16794
|
+
});
|
|
16694
16795
|
if (positional.length === 1) {
|
|
16695
|
-
args[positional[0].name] = read(positional[0], text);
|
|
16796
|
+
args[positional[0].name] = read(positional[0], [0, text.length]);
|
|
16696
16797
|
return;
|
|
16697
16798
|
}
|
|
16698
|
-
const
|
|
16799
|
+
const parts = terms(text);
|
|
16699
16800
|
let from = 0;
|
|
16700
|
-
let to =
|
|
16701
|
-
for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param,
|
|
16702
|
-
const readTrailing = (param,
|
|
16801
|
+
let to = parts.length;
|
|
16802
|
+
for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, parts[from++]) : unset(param);
|
|
16803
|
+
const readTrailing = (param, span) => {
|
|
16703
16804
|
try {
|
|
16704
|
-
return read(param,
|
|
16805
|
+
return read(param, span);
|
|
16705
16806
|
} catch (error) {
|
|
16706
16807
|
if (error instanceof MacroArgumentError) return void 0;
|
|
16707
16808
|
throw error;
|
|
16708
16809
|
}
|
|
16709
16810
|
};
|
|
16710
16811
|
for (const param of positional.slice(restIndex + 1).reverse()) {
|
|
16711
|
-
const value = from < to && !endsWithOperator(text.slice(0,
|
|
16812
|
+
const value = from < to && !endsWithOperator(text.slice(0, parts[to - 1][0])) ? readTrailing(param, parts[to - 1]) : void 0;
|
|
16712
16813
|
args[param.name] = value ?? unset(param);
|
|
16713
16814
|
if (value !== void 0) to--;
|
|
16815
|
+
else spans?.delete(param);
|
|
16714
16816
|
}
|
|
16715
16817
|
const rest = positional[restIndex];
|
|
16716
|
-
args[rest.name] = from < to ? read(rest,
|
|
16818
|
+
args[rest.name] = from < to ? read(rest, [parts[from][0], parts[to - 1][1]]) : unset(rest);
|
|
16717
16819
|
}
|
|
16718
16820
|
/** The duration a timing macro ({repeat}, {type}, {timed}/{next}) waits. */
|
|
16719
16821
|
var DELAY_PARAMETER = {
|
|
@@ -16721,32 +16823,43 @@ var DELAY_PARAMETER = {
|
|
|
16721
16823
|
type: "delay",
|
|
16722
16824
|
required: true
|
|
16723
16825
|
};
|
|
16724
|
-
/**
|
|
16725
|
-
|
|
16826
|
+
/**
|
|
16827
|
+
* Read `rawArgs` into the declared `parameters` (see above). With `spans`,
|
|
16828
|
+
* note there where each argument set was read.
|
|
16829
|
+
*/
|
|
16830
|
+
function parseMacroArgs(rawArgs, parameters, spans) {
|
|
16726
16831
|
const args = {};
|
|
16727
|
-
let rest = rawArgs
|
|
16832
|
+
let [rest, restAt] = trimmed(rawArgs, 0);
|
|
16728
16833
|
let groupStart = 0;
|
|
16729
16834
|
parameters.forEach((separator, i) => {
|
|
16730
16835
|
if (separator.type !== "separator") return;
|
|
16731
16836
|
const at = rest === null ? null : findSeparator(rest, separator.name);
|
|
16732
16837
|
args[separator.name] = at !== null;
|
|
16733
16838
|
const before = at ? rest.slice(0, at[0]) : rest;
|
|
16734
|
-
readGroup(parameters.slice(groupStart, i), before, args
|
|
16839
|
+
readGroup(parameters.slice(groupStart, i), before, args, {
|
|
16840
|
+
at: restAt,
|
|
16841
|
+
spans
|
|
16842
|
+
});
|
|
16735
16843
|
rest = at ? rest.slice(at[1]) : null;
|
|
16844
|
+
if (at) restAt += at[1];
|
|
16736
16845
|
groupStart = i + 1;
|
|
16737
16846
|
});
|
|
16738
|
-
readGroup(parameters.slice(groupStart), rest, args
|
|
16847
|
+
readGroup(parameters.slice(groupStart), rest, args, {
|
|
16848
|
+
at: restAt,
|
|
16849
|
+
spans
|
|
16850
|
+
});
|
|
16739
16851
|
return args;
|
|
16740
16852
|
}
|
|
16741
16853
|
/**
|
|
16742
16854
|
* Read a `passage` argument as written (`{goto "Hall"}`, `{goto $room}`): a
|
|
16743
|
-
*
|
|
16744
|
-
* `Hall`), anything else
|
|
16855
|
+
* string literal is the name JavaScript reads from it (`"\u0048all"` is
|
|
16856
|
+
* `Hall`), anything else (a literal that is not well-formed too) an
|
|
16857
|
+
* expression, whose value is the name when it
|
|
16745
16858
|
* runs (see evaluatePassageName).
|
|
16746
16859
|
*/
|
|
16747
16860
|
function passageTarget(arg) {
|
|
16748
16861
|
const expression = arg.trim();
|
|
16749
|
-
const name =
|
|
16862
|
+
const name = stringLiteralValue(expression);
|
|
16750
16863
|
return name === null ? {
|
|
16751
16864
|
kind: "expression",
|
|
16752
16865
|
expression
|
|
@@ -16795,6 +16908,7 @@ function parseVarArgs(rawArgs) {
|
|
|
16795
16908
|
var OPTION_PARAMETERS = [{
|
|
16796
16909
|
name: "value",
|
|
16797
16910
|
type: "string",
|
|
16911
|
+
holds: "text",
|
|
16798
16912
|
required: true
|
|
16799
16913
|
}];
|
|
16800
16914
|
/** The parameters of the built-in sub-macros that take arguments. */
|
|
@@ -17269,30 +17383,59 @@ var CONDITION_MACROS = new Set([
|
|
|
17269
17383
|
"elseif",
|
|
17270
17384
|
"case"
|
|
17271
17385
|
]);
|
|
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
17386
|
/** Block macros whose body is the name of a passage. */
|
|
17289
17387
|
var PASSAGE_BODIES = new Set(["dialog"]);
|
|
17388
|
+
/**
|
|
17389
|
+
* `params` with each `string` and `text` parameter (options too) saying
|
|
17390
|
+
* what it holds: what it declares, else `holds`.
|
|
17391
|
+
*/
|
|
17392
|
+
function withHolds(params, holds) {
|
|
17393
|
+
return params.map((param) => {
|
|
17394
|
+
if (param.parameters) return {
|
|
17395
|
+
...param,
|
|
17396
|
+
parameters: withHolds(param.parameters, holds)
|
|
17397
|
+
};
|
|
17398
|
+
return (param.type === "string" || param.type === "text") && !param.holds ? {
|
|
17399
|
+
...param,
|
|
17400
|
+
holds
|
|
17401
|
+
} : param;
|
|
17402
|
+
});
|
|
17403
|
+
}
|
|
17404
|
+
/**
|
|
17405
|
+
* The declared parameters of `macros`, by name in any case, and of the
|
|
17406
|
+
* built-in sub-macros (`{option}`): what the story-start check, reference
|
|
17407
|
+
* collection and tooling read arguments by. Each `string` and `text`
|
|
17408
|
+
* parameter says what it holds: what it declares, else `markup` for a macro
|
|
17409
|
+
* with `interpolate` (which can resolve markup) and `text` for any other.
|
|
17410
|
+
*/
|
|
17411
|
+
function parameterLookup(macros) {
|
|
17412
|
+
const parameters = new NameMap();
|
|
17413
|
+
for (const { name, parameters: params, interpolate } of macros) if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
|
|
17414
|
+
return (name) => {
|
|
17415
|
+
let params = parameters.get(name);
|
|
17416
|
+
if (!params) {
|
|
17417
|
+
const sub = subMacroParameters(name);
|
|
17418
|
+
if (sub) parameters.set(name, params = withHolds(sub, "text"));
|
|
17419
|
+
}
|
|
17420
|
+
return params;
|
|
17421
|
+
};
|
|
17422
|
+
}
|
|
17423
|
+
/** What a `string` or `text` argument holds when its parameter doesn't say. */
|
|
17424
|
+
var holdsByDefault = (interpolate) => interpolate ? "markup" : "text";
|
|
17290
17425
|
/** The goal of the code an argument of this type holds, if it is code. */
|
|
17291
17426
|
function codeGoal(param) {
|
|
17292
17427
|
if (param.type === "expression" || param.type === "passage") return "expression";
|
|
17293
17428
|
if (param.type === "statements") return "statements";
|
|
17294
17429
|
}
|
|
17295
17430
|
/**
|
|
17431
|
+
* What a `string` or `text` argument holds: what its parameter says (see
|
|
17432
|
+
* parameterLookup), else markup, which is checked.
|
|
17433
|
+
*/
|
|
17434
|
+
function stringHolds(param) {
|
|
17435
|
+
if (param.type !== "string" && param.type !== "text") return void 0;
|
|
17436
|
+
return param.holds ?? "markup";
|
|
17437
|
+
}
|
|
17438
|
+
/**
|
|
17296
17439
|
* The code and the markup-holding text in `tokens`, the tokens of `src`.
|
|
17297
17440
|
* `parametersOf` gives the declared parameters of a macro, if it has any.
|
|
17298
17441
|
*/
|
|
@@ -17304,8 +17447,8 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
17304
17447
|
if (token.type === "link") yield {
|
|
17305
17448
|
kind: "passage",
|
|
17306
17449
|
name: token.target,
|
|
17307
|
-
offset:
|
|
17308
|
-
length: token.
|
|
17450
|
+
offset: token.targetStart,
|
|
17451
|
+
length: token.targetEnd - token.targetStart,
|
|
17309
17452
|
macro: "link",
|
|
17310
17453
|
label: src.slice(token.start, token.end)
|
|
17311
17454
|
};
|
|
@@ -17316,32 +17459,35 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
17316
17459
|
goal: "expression",
|
|
17317
17460
|
label: `{${token.expression}}`
|
|
17318
17461
|
};
|
|
17319
|
-
else if (token.type === "html")
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17462
|
+
else if (token.type === "html") {
|
|
17463
|
+
let cursor = token.start;
|
|
17464
|
+
for (const [name, value] of Object.entries(token.attributes)) {
|
|
17465
|
+
const at = locate(src, value, cursor);
|
|
17466
|
+
cursor = at + value.length;
|
|
17467
|
+
if (!isCodeAttribute(name)) {
|
|
17468
|
+
yield {
|
|
17469
|
+
kind: "text",
|
|
17470
|
+
text: value,
|
|
17471
|
+
offset: at,
|
|
17472
|
+
where: `In the ${name} attribute of <${token.tag}>: `
|
|
17473
|
+
};
|
|
17474
|
+
continue;
|
|
17475
|
+
}
|
|
17476
|
+
let from = 0;
|
|
17477
|
+
for (const part of splitSigilTemplate(value)) {
|
|
17478
|
+
if (!("expr" in part)) continue;
|
|
17479
|
+
const k = locate(value, part.expr, from);
|
|
17480
|
+
from = k + part.expr.length;
|
|
17481
|
+
yield {
|
|
17482
|
+
kind: "code",
|
|
17483
|
+
code: part.expr,
|
|
17484
|
+
offset: at + k,
|
|
17485
|
+
goal: "expression",
|
|
17486
|
+
label: `{${part.expr}} in the ${name} attribute of <${token.tag}>`
|
|
17487
|
+
};
|
|
17488
|
+
}
|
|
17342
17489
|
}
|
|
17343
|
-
}
|
|
17344
|
-
else if (token.type === "macro" && !token.isClose) {
|
|
17490
|
+
} else if (token.type === "macro" && !token.isClose) {
|
|
17345
17491
|
const name = token.name.toLowerCase();
|
|
17346
17492
|
if (name === "do") {
|
|
17347
17493
|
const close = closeOf(t, name);
|
|
@@ -17361,12 +17507,13 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
17361
17507
|
const close = closeOf(t, name);
|
|
17362
17508
|
const body = tokens.slice(t + 1, close);
|
|
17363
17509
|
if (close > t + 1 && body.every((b) => b.type === "text")) {
|
|
17364
|
-
const
|
|
17510
|
+
const text = src.slice(token.end, tokens[close].start);
|
|
17511
|
+
const written = text.trim();
|
|
17365
17512
|
yield {
|
|
17366
17513
|
kind: "passage",
|
|
17367
|
-
name:
|
|
17368
|
-
offset:
|
|
17369
|
-
length:
|
|
17514
|
+
name: written.replace(/^["']|["']$/g, ""),
|
|
17515
|
+
offset: token.end + text.indexOf(written),
|
|
17516
|
+
length: written.length,
|
|
17370
17517
|
macro: name,
|
|
17371
17518
|
label: src.slice(token.start, token.end)
|
|
17372
17519
|
};
|
|
@@ -17374,7 +17521,8 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
17374
17521
|
}
|
|
17375
17522
|
if (!args) continue;
|
|
17376
17523
|
const label = `{${token.name} ${args}}`;
|
|
17377
|
-
const
|
|
17524
|
+
const tag = src.slice(token.start, token.end - 1);
|
|
17525
|
+
const argsAt = token.start + tag.trimEnd().length - args.length;
|
|
17378
17526
|
if (CONDITION_MACROS.has(name)) {
|
|
17379
17527
|
yield {
|
|
17380
17528
|
kind: "code",
|
|
@@ -17393,8 +17541,9 @@ function* codeAndText(src, tokens, parametersOf) {
|
|
|
17393
17541
|
/** The code and text in the arguments `args` (at `offset`) of `macro`. */
|
|
17394
17542
|
function* argPieces(args, offset, params, macro, label) {
|
|
17395
17543
|
let values;
|
|
17544
|
+
const spans = /* @__PURE__ */ new Map();
|
|
17396
17545
|
try {
|
|
17397
|
-
values = parseMacroArgs(args, params);
|
|
17546
|
+
values = parseMacroArgs(args, params, spans);
|
|
17398
17547
|
} catch (error) {
|
|
17399
17548
|
if (!(error instanceof MacroArgumentError)) throw error;
|
|
17400
17549
|
yield {
|
|
@@ -17405,11 +17554,7 @@ function* argPieces(args, offset, params, macro, label) {
|
|
|
17405
17554
|
};
|
|
17406
17555
|
return;
|
|
17407
17556
|
}
|
|
17408
|
-
const
|
|
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;
|
|
17557
|
+
const name = macro.toLowerCase();
|
|
17413
17558
|
/** The pieces of the parameters `list`, with their values in `from`. */
|
|
17414
17559
|
function* visit(list, from) {
|
|
17415
17560
|
for (const param of list) {
|
|
@@ -17418,47 +17563,62 @@ function* argPieces(args, offset, params, macro, label) {
|
|
|
17418
17563
|
yield* visit(param.parameters, value ?? {});
|
|
17419
17564
|
continue;
|
|
17420
17565
|
}
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
const
|
|
17566
|
+
const span = spans.get(param);
|
|
17567
|
+
if (typeof value !== "string" || !value.trim() || !span) continue;
|
|
17568
|
+
const [start, end] = span;
|
|
17569
|
+
const written = args.slice(start, end);
|
|
17570
|
+
/** The name as written (quotes included), at the argument. */
|
|
17571
|
+
const passage = (passageName) => ({
|
|
17572
|
+
kind: "passage",
|
|
17573
|
+
name: passageName,
|
|
17574
|
+
offset: offset + start,
|
|
17575
|
+
length: end - start,
|
|
17576
|
+
macro: name,
|
|
17577
|
+
label
|
|
17578
|
+
});
|
|
17579
|
+
const goal = codeGoal(param);
|
|
17580
|
+
if (param.type === "passage") {
|
|
17581
|
+
const target = passageTarget(value);
|
|
17582
|
+
if (target.kind === "name") {
|
|
17583
|
+
yield passage(target.name);
|
|
17584
|
+
continue;
|
|
17585
|
+
}
|
|
17586
|
+
}
|
|
17425
17587
|
if (goal) {
|
|
17426
17588
|
const piece = {
|
|
17427
17589
|
kind: "code",
|
|
17428
17590
|
code: value,
|
|
17429
|
-
offset: offset +
|
|
17591
|
+
offset: offset + start,
|
|
17430
17592
|
goal,
|
|
17431
17593
|
label
|
|
17432
17594
|
};
|
|
17433
17595
|
if (param.type === "passage") {
|
|
17434
17596
|
piece.passage = true;
|
|
17435
|
-
piece.macro =
|
|
17597
|
+
piece.macro = name;
|
|
17436
17598
|
}
|
|
17437
|
-
if (!codeGoal(param)) piece.inString = true;
|
|
17438
17599
|
yield piece;
|
|
17600
|
+
continue;
|
|
17439
17601
|
}
|
|
17440
|
-
const
|
|
17441
|
-
const
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
length: written.length,
|
|
17450
|
-
macro: macro.toLowerCase(),
|
|
17451
|
-
label
|
|
17602
|
+
const at = offset + start + (value !== written && /^["']/.test(written) ? 1 : 0);
|
|
17603
|
+
const holds = stringHolds(param);
|
|
17604
|
+
if (holds === "expression" || holds === "statements") yield {
|
|
17605
|
+
kind: "code",
|
|
17606
|
+
code: value,
|
|
17607
|
+
offset: at,
|
|
17608
|
+
goal: holds,
|
|
17609
|
+
label,
|
|
17610
|
+
inString: true
|
|
17452
17611
|
};
|
|
17453
|
-
if (
|
|
17612
|
+
else if (holds === "passage") yield passage(value);
|
|
17613
|
+
else if (holds === "markup") yield {
|
|
17454
17614
|
kind: "text",
|
|
17455
17615
|
text: value,
|
|
17456
|
-
offset:
|
|
17616
|
+
offset: at,
|
|
17457
17617
|
where: `In the ${param.name} of {${macro}}: `
|
|
17458
17618
|
};
|
|
17459
17619
|
}
|
|
17460
17620
|
}
|
|
17461
|
-
yield* visit(params, values);
|
|
17621
|
+
yield* [...visit(params, values)].sort((a, b) => a.offset - b.offset);
|
|
17462
17622
|
}
|
|
17463
17623
|
/**
|
|
17464
17624
|
* Index of `part` in `text` from `from` on, else anywhere, else `from`: the
|
|
@@ -17480,10 +17640,12 @@ function locate(text, part, from) {
|
|
|
17480
17640
|
var WIDGET_PARAMETERS = [{
|
|
17481
17641
|
name: "name",
|
|
17482
17642
|
type: "text",
|
|
17643
|
+
holds: "text",
|
|
17483
17644
|
required: true
|
|
17484
17645
|
}, {
|
|
17485
17646
|
name: "parameters",
|
|
17486
|
-
type: "text"
|
|
17647
|
+
type: "text",
|
|
17648
|
+
holds: "text"
|
|
17487
17649
|
}];
|
|
17488
17650
|
/**
|
|
17489
17651
|
* The widget a {widget} definition's arguments declare: its name and its
|
|
@@ -17643,7 +17805,7 @@ function validateMarkup(passages, options) {
|
|
|
17643
17805
|
report(passage, base + token.start, `${where}Unknown macro {${token.name}}.${didYouMean(token.name, names, (n) => `{${n}}`)}`);
|
|
17644
17806
|
}
|
|
17645
17807
|
};
|
|
17646
|
-
const parametersOf =
|
|
17808
|
+
const parametersOf = options.parametersOf ?? (() => void 0);
|
|
17647
17809
|
/**
|
|
17648
17810
|
* Check `tokens`, the tokens of `src`, which starts at `base` in the
|
|
17649
17811
|
* passage: their macros, the code in them and the markup in their text.
|
|
@@ -17711,7 +17873,6 @@ function declarationRegex(sigil) {
|
|
|
17711
17873
|
var VAR_PATH_RE = /\$(\w+(?:\.\w+)*)/y;
|
|
17712
17874
|
/** Quoted first argument of an input macro naming a story variable. */
|
|
17713
17875
|
var QUOTED_VAR_ARG_RE = /^["']\$(\w+(?:\.\w+)*)["']?$/;
|
|
17714
|
-
var FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
|
|
17715
17876
|
var VALID_VAR_TYPES = new Set([
|
|
17716
17877
|
"number",
|
|
17717
17878
|
"string",
|
|
@@ -17774,30 +17935,14 @@ function parseStoryVariables(content, sigil = "$") {
|
|
|
17774
17935
|
return schema;
|
|
17775
17936
|
}
|
|
17776
17937
|
/**
|
|
17777
|
-
* Extract for-loop local variable names from passage content.
|
|
17778
|
-
* `{for @item of ...}` → "item"
|
|
17779
|
-
* `{for @index, @item of ...}` → "index", "item"
|
|
17780
|
-
*/
|
|
17781
|
-
function extractForLocals(content) {
|
|
17782
|
-
const locals = /* @__PURE__ */ new Set();
|
|
17783
|
-
let match;
|
|
17784
|
-
FOR_LOCAL_RE.lastIndex = 0;
|
|
17785
|
-
while ((match = FOR_LOCAL_RE.exec(content)) !== null) {
|
|
17786
|
-
locals.add(match[1]);
|
|
17787
|
-
if (match[2]) locals.add(match[2]);
|
|
17788
|
-
}
|
|
17789
|
-
return locals;
|
|
17790
|
-
}
|
|
17791
|
-
/**
|
|
17792
17938
|
* Validate a single variable reference path (e.g. "player.health") against
|
|
17793
17939
|
* the schema. Returns an error message or null if valid.
|
|
17794
17940
|
*/
|
|
17795
|
-
function validateRef(ref, schema
|
|
17941
|
+
function validateRef(ref, schema) {
|
|
17796
17942
|
const parts = ref.split(".");
|
|
17797
17943
|
const rootName = parts[0];
|
|
17798
17944
|
const nameError = variableNameError(rootName, "$" + rootName);
|
|
17799
17945
|
if (nameError) return nameError;
|
|
17800
|
-
if (forLocals.has(rootName)) return null;
|
|
17801
17946
|
const rootSchema = schema.get(rootName);
|
|
17802
17947
|
if (!rootSchema) return `Undeclared variable: $${ref}`;
|
|
17803
17948
|
let current = rootSchema;
|
|
@@ -17853,8 +17998,9 @@ function scanInterpolations(text, onRef, storeVarMacros = NO_STORE_VAR_MACROS) {
|
|
|
17853
17998
|
* Report `$var` references in JavaScript code, lexed as the expression
|
|
17854
17999
|
* engine lexes it (`lexJs`): references in code, but none inside string or
|
|
17855
18000
|
* regex literals or comments, nor property names (`a.$b`, `{ $b: 1 }`).
|
|
17856
|
-
* The text of string and template literals is scanned
|
|
17857
|
-
*
|
|
18001
|
+
* The text of string and template literals is not scanned: a string in code
|
|
18002
|
+
* is literal text unless a macro argument that holds markup is read as such
|
|
18003
|
+
* (see scanArgs); the `${…}` parts of template literals are code.
|
|
17858
18004
|
*/
|
|
17859
18005
|
function scanCode(code, onRef, goal = "expression") {
|
|
17860
18006
|
const parsed = parseOrError(code, goal);
|
|
@@ -17867,37 +18013,17 @@ function scanCode(code, onRef, goal = "expression") {
|
|
|
17867
18013
|
VAR_PATH_RE.lastIndex = ref.start;
|
|
17868
18014
|
onRef(VAR_PATH_RE.exec(code)[1]);
|
|
17869
18015
|
}
|
|
17870
|
-
for (const text of parsed.strings) scanInterpolations(text, onRef);
|
|
17871
18016
|
}
|
|
17872
18017
|
/** `scanCode` for code acorn can't parse: lexed leniently. */
|
|
17873
18018
|
function scanCodeLeniently(code, onRef, goal) {
|
|
17874
|
-
/** Open template literals: their text so far, null in an interpolation. */
|
|
17875
|
-
const templates = [];
|
|
17876
18019
|
lexJs(code, {
|
|
17877
18020
|
variable(sigil, _name, index) {
|
|
17878
18021
|
if (sigil !== "$") return;
|
|
17879
18022
|
VAR_PATH_RE.lastIndex = index;
|
|
17880
18023
|
onRef(VAR_PATH_RE.exec(code)[1]);
|
|
17881
18024
|
},
|
|
17882
|
-
literal(
|
|
17883
|
-
const template = templates[templates.length - 1];
|
|
17884
|
-
if (template?.nesting === nesting) if (template.text === null) template.text = "";
|
|
17885
|
-
else if (text === "`" || text === "${") {
|
|
17886
|
-
scanInterpolations(template.text, onRef);
|
|
17887
|
-
if (text === "`") templates.pop();
|
|
17888
|
-
else template.text = null;
|
|
17889
|
-
} else template.text += text;
|
|
17890
|
-
else if (text === "`") templates.push({
|
|
17891
|
-
nesting,
|
|
17892
|
-
text: ""
|
|
17893
|
-
});
|
|
17894
|
-
else if (text[0] === "\"" || text[0] === "'") {
|
|
17895
|
-
const { closed } = scanStringLiteral(text, 0);
|
|
17896
|
-
scanInterpolations(text.slice(1, closed ? -1 : void 0), onRef);
|
|
17897
|
-
}
|
|
17898
|
-
}
|
|
18025
|
+
literal() {}
|
|
17899
18026
|
}, goal);
|
|
17900
|
-
for (const t of templates) if (t.text) scanInterpolations(t.text, onRef);
|
|
17901
18027
|
}
|
|
17902
18028
|
/**
|
|
17903
18029
|
* Report the `$var` references a passage evaluates at runtime: `{$var}`
|
|
@@ -17909,13 +18035,23 @@ function collectPassageRefs(content, storeVarMacros, onRef, parametersOf) {
|
|
|
17909
18035
|
withParseCache(() => collectTokenRefs(content, tokensOf(content, false), storeVarMacros, onRef, parametersOf));
|
|
17910
18036
|
}
|
|
17911
18037
|
/**
|
|
17912
|
-
* Report the references in the
|
|
17913
|
-
*
|
|
17914
|
-
*
|
|
18038
|
+
* Report the references in the arguments of a macro by what its parameters
|
|
18039
|
+
* say each holds (see ParameterDef.holds): code is scanned as code and
|
|
18040
|
+
* markup as markup, but literal text (a placeholder, a passage name) holds
|
|
18041
|
+
* none. A macro without declared parameters, or arguments that do not
|
|
18042
|
+
* parse, are scanned as code.
|
|
17915
18043
|
*/
|
|
17916
|
-
function
|
|
17917
|
-
if (!
|
|
17918
|
-
|
|
18044
|
+
function scanArgs(token, params, onRef) {
|
|
18045
|
+
if (!token.rawArgs) return;
|
|
18046
|
+
if (params) {
|
|
18047
|
+
const pieces = [...argPieces(token.rawArgs, 0, params, token.name, "")];
|
|
18048
|
+
if (!pieces.some((piece) => piece.kind === "argument-error")) {
|
|
18049
|
+
for (const piece of pieces) if (piece.kind === "code") scanCode(piece.code, onRef, piece.goal);
|
|
18050
|
+
else if (piece.kind === "text") scanInterpolations(piece.text, onRef);
|
|
18051
|
+
return;
|
|
18052
|
+
}
|
|
18053
|
+
}
|
|
18054
|
+
scanCode(token.rawArgs, onRef);
|
|
17919
18055
|
}
|
|
17920
18056
|
/** Report the `$var` references in the tokens of `content`. */
|
|
17921
18057
|
function collectTokenRefs(content, tokens, storeVarMacros, onRef, parametersOf = () => void 0) {
|
|
@@ -17927,12 +18063,12 @@ function collectTokenRefs(content, tokens, storeVarMacros, onRef, parametersOf =
|
|
|
17927
18063
|
else if (token.type === "html") for (const [name, value] of Object.entries(token.attributes)) if (isCodeAttribute(name)) scanSigilReferences(value, onRef);
|
|
17928
18064
|
else scanInterpolations(value, onRef, storeVarMacros);
|
|
17929
18065
|
else if (token.type === "macro" && !token.isClose) {
|
|
17930
|
-
|
|
17931
|
-
scanStringCode(token, parametersOf(token.name), onRef);
|
|
18066
|
+
scanArgs(token, parametersOf(token.name), onRef);
|
|
17932
18067
|
if (storeVarMacros.has(token.name.toLowerCase())) {
|
|
17933
18068
|
const first = token.rawArgs.trim().split(/\s+/)[0] ?? "";
|
|
17934
18069
|
const quoted = QUOTED_VAR_ARG_RE.exec(first);
|
|
17935
18070
|
if (quoted) onRef(quoted[1]);
|
|
18071
|
+
else if (!/^["'`]/.test(first)) scanCode(first, onRef);
|
|
17936
18072
|
}
|
|
17937
18073
|
if (token.name === "do") {
|
|
17938
18074
|
let close = t + 1;
|
|
@@ -17962,9 +18098,8 @@ function validatePassages(passages, schema, storeVarMacros = BUILTIN_STORE_VAR_M
|
|
|
17962
18098
|
const storeVarSet = new Set(Array.from(storeVarMacros, (m) => m.toLowerCase()));
|
|
17963
18099
|
for (const [name, passage] of passages) {
|
|
17964
18100
|
if (NOT_MARKUP.has(name)) continue;
|
|
17965
|
-
const forLocals = extractForLocals(passage.content);
|
|
17966
18101
|
collectPassageRefs(passage.content, storeVarSet, (ref) => {
|
|
17967
|
-
const error = validateRef(ref, schema
|
|
18102
|
+
const error = validateRef(ref, schema);
|
|
17968
18103
|
if (error) errors.push(`Passage "${name}": ${error}`);
|
|
17969
18104
|
}, parametersOf);
|
|
17970
18105
|
}
|
|
@@ -18221,12 +18356,16 @@ defineMacro({
|
|
|
18221
18356
|
},
|
|
18222
18357
|
{
|
|
18223
18358
|
name: "label",
|
|
18224
|
-
type: "string"
|
|
18359
|
+
type: "string",
|
|
18360
|
+
holds: "markup"
|
|
18225
18361
|
}
|
|
18226
18362
|
],
|
|
18227
18363
|
render(_props, ctx) {
|
|
18228
18364
|
try {
|
|
18229
|
-
const { currentExpr, maxExpr, labelMode } = meterArgs(
|
|
18365
|
+
const { currentExpr, maxExpr, labelMode } = meterArgs({
|
|
18366
|
+
...ctx.args,
|
|
18367
|
+
label: ctx.resolve?.(ctx.args.label)
|
|
18368
|
+
});
|
|
18230
18369
|
const current = Number(ctx.evaluate(currentExpr));
|
|
18231
18370
|
const max = Number(ctx.evaluate(maxExpr));
|
|
18232
18371
|
const pct = max === 0 ? 0 : Math.max(0, Math.min(100, current / max * 100));
|
|
@@ -18588,6 +18727,7 @@ defineMacro({
|
|
|
18588
18727
|
parameters: [{
|
|
18589
18728
|
name: "label",
|
|
18590
18729
|
type: "text",
|
|
18730
|
+
holds: "markup",
|
|
18591
18731
|
required: true
|
|
18592
18732
|
}],
|
|
18593
18733
|
render({ rawArgs, children = [] }, ctx) {
|
|
@@ -18619,13 +18759,14 @@ defineMacro({
|
|
|
18619
18759
|
parameters: [{
|
|
18620
18760
|
name: "text",
|
|
18621
18761
|
type: "string",
|
|
18762
|
+
holds: "markup",
|
|
18622
18763
|
required: true
|
|
18623
18764
|
}, {
|
|
18624
18765
|
name: "passage",
|
|
18625
18766
|
type: "passage"
|
|
18626
18767
|
}],
|
|
18627
18768
|
render({ children = [] }, ctx) {
|
|
18628
|
-
const display = ctx.args.text ?? "";
|
|
18769
|
+
const display = ctx.resolve?.(ctx.args.text ?? "") ?? "";
|
|
18629
18770
|
let passage = null;
|
|
18630
18771
|
let failure;
|
|
18631
18772
|
if (ctx.args.passage !== void 0) try {
|
|
@@ -18948,6 +19089,7 @@ defineMacro({
|
|
|
18948
19089
|
parameters: [{
|
|
18949
19090
|
name: "label",
|
|
18950
19091
|
type: "text",
|
|
19092
|
+
holds: "markup",
|
|
18951
19093
|
required: true
|
|
18952
19094
|
}, {
|
|
18953
19095
|
name: "noclose",
|
|
@@ -19494,7 +19636,8 @@ function defineInputMacro(name, type, toValue = (input) => input !== void 0 ? St
|
|
|
19494
19636
|
storeVar: true,
|
|
19495
19637
|
parameters: [VARIABLE_PARAMETER, {
|
|
19496
19638
|
name: "placeholder",
|
|
19497
|
-
type: "string"
|
|
19639
|
+
type: "string",
|
|
19640
|
+
holds: "text"
|
|
19498
19641
|
}],
|
|
19499
19642
|
render(_props, ctx) {
|
|
19500
19643
|
const placeholder = ctx.args.placeholder ?? "";
|
|
@@ -19530,7 +19673,8 @@ defineMacro({
|
|
|
19530
19673
|
storeVar: true,
|
|
19531
19674
|
parameters: [VARIABLE_PARAMETER, {
|
|
19532
19675
|
name: "label",
|
|
19533
|
-
type: "text"
|
|
19676
|
+
type: "text",
|
|
19677
|
+
holds: "text"
|
|
19534
19678
|
}],
|
|
19535
19679
|
render(_props, ctx) {
|
|
19536
19680
|
const label = ctx.args.label ?? "";
|
|
@@ -19561,11 +19705,13 @@ defineMacro({
|
|
|
19561
19705
|
{
|
|
19562
19706
|
name: "value",
|
|
19563
19707
|
type: "text",
|
|
19708
|
+
holds: "text",
|
|
19564
19709
|
required: true
|
|
19565
19710
|
},
|
|
19566
19711
|
{
|
|
19567
19712
|
name: "label",
|
|
19568
|
-
type: "text"
|
|
19713
|
+
type: "text",
|
|
19714
|
+
holds: "text"
|
|
19569
19715
|
}
|
|
19570
19716
|
],
|
|
19571
19717
|
render(_props, ctx) {
|
|
@@ -19985,6 +20131,7 @@ defineMacro({
|
|
|
19985
20131
|
parameters: [{
|
|
19986
20132
|
name: "condition",
|
|
19987
20133
|
type: "string",
|
|
20134
|
+
holds: "expression",
|
|
19988
20135
|
required: true
|
|
19989
20136
|
}, {
|
|
19990
20137
|
name: "options",
|
|
@@ -19992,19 +20139,23 @@ defineMacro({
|
|
|
19992
20139
|
parameters: [
|
|
19993
20140
|
{
|
|
19994
20141
|
name: "goto",
|
|
19995
|
-
type: "string"
|
|
20142
|
+
type: "string",
|
|
20143
|
+
holds: "passage"
|
|
19996
20144
|
},
|
|
19997
20145
|
{
|
|
19998
20146
|
name: "dialog",
|
|
19999
|
-
type: "string"
|
|
20147
|
+
type: "string",
|
|
20148
|
+
holds: "passage"
|
|
20000
20149
|
},
|
|
20001
20150
|
{
|
|
20002
20151
|
name: "run",
|
|
20003
|
-
type: "string"
|
|
20152
|
+
type: "string",
|
|
20153
|
+
holds: "statements"
|
|
20004
20154
|
},
|
|
20005
20155
|
{
|
|
20006
20156
|
name: "name",
|
|
20007
|
-
type: "string"
|
|
20157
|
+
type: "string",
|
|
20158
|
+
holds: "text"
|
|
20008
20159
|
},
|
|
20009
20160
|
{
|
|
20010
20161
|
name: "priority",
|
|
@@ -20033,6 +20184,7 @@ defineMacro({
|
|
|
20033
20184
|
parameters: [{
|
|
20034
20185
|
name: "name",
|
|
20035
20186
|
type: "text",
|
|
20187
|
+
holds: "text",
|
|
20036
20188
|
required: true
|
|
20037
20189
|
}],
|
|
20038
20190
|
render(_props, ctx) {
|
|
@@ -20081,11 +20233,10 @@ function stopWithErrors(errors) {
|
|
|
20081
20233
|
*/
|
|
20082
20234
|
function markupErrors(storyData, only) {
|
|
20083
20235
|
const macros = getMacroRegistry();
|
|
20084
|
-
const parameters = new Map(macros.map((m) => [m.name.toLowerCase(), m.parameters]));
|
|
20085
20236
|
return validateMarkup(storyData.passages.values(), {
|
|
20086
20237
|
isKnownMacro: (name) => !!getMacro(name) || isSubMacro(name) || !!getWidget(name),
|
|
20087
20238
|
macroNames: macros.map((m) => m.name),
|
|
20088
|
-
parametersOf: (
|
|
20239
|
+
parametersOf: parameterLookup(macros),
|
|
20089
20240
|
only
|
|
20090
20241
|
}).map(formatDiagnostic);
|
|
20091
20242
|
}
|