@spintax/core 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +2 -1
- package/dist/index.cjs +222 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +222 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -29,8 +29,12 @@
|
|
|
29
29
|
* 4 — `raw` is kept on more constructs: one holding a `{?…}` conditional, and a permutation with a
|
|
30
30
|
* reference anywhere in its `<config>` (#80). The shape is 3's, but an `Ast` cached by version 3
|
|
31
31
|
* lacks `raw` exactly where the new rule needs it, and would render `[{?f?a|b|x}|c]` with a raw `|`.
|
|
32
|
+
*
|
|
33
|
+
* 5 — `/! … !/` verbatim spans (#92, ADR 0001) are encoded into the text as tokens at parse time.
|
|
34
|
+
* The shape is 4's, but an `Ast` parsed by version 4 carries no tokens, and would spin `/!{a|b}!/`
|
|
35
|
+
* instead of emitting it.
|
|
32
36
|
*/
|
|
33
|
-
declare const AST_VERSION =
|
|
37
|
+
declare const AST_VERSION = 5;
|
|
34
38
|
/** Opaque public handle (re-exported as `Ast` from the package index). */
|
|
35
39
|
interface Ast {
|
|
36
40
|
readonly astVersion: typeof AST_VERSION;
|
|
@@ -95,7 +99,8 @@ interface PluralIssue {
|
|
|
95
99
|
* lets the deterministic parity fixtures assert exact output.
|
|
96
100
|
*
|
|
97
101
|
* Order: strip stray sentinels → build vars, roll #def, tree-walk (+ #include) →
|
|
98
|
-
* cosmetic post-process (if on) → mandatory neutralize safety-restore (always)
|
|
102
|
+
* cosmetic post-process (if on) → mandatory neutralize safety-restore (always) → decode the
|
|
103
|
+
* `/! … !/` spans (always).
|
|
99
104
|
*/
|
|
100
105
|
|
|
101
106
|
/** Default for {@link PipelineOptions.maxDepth} (#include + parse-nesting guard). */
|
package/dist/index.d.ts
CHANGED
|
@@ -29,8 +29,12 @@
|
|
|
29
29
|
* 4 — `raw` is kept on more constructs: one holding a `{?…}` conditional, and a permutation with a
|
|
30
30
|
* reference anywhere in its `<config>` (#80). The shape is 3's, but an `Ast` cached by version 3
|
|
31
31
|
* lacks `raw` exactly where the new rule needs it, and would render `[{?f?a|b|x}|c]` with a raw `|`.
|
|
32
|
+
*
|
|
33
|
+
* 5 — `/! … !/` verbatim spans (#92, ADR 0001) are encoded into the text as tokens at parse time.
|
|
34
|
+
* The shape is 4's, but an `Ast` parsed by version 4 carries no tokens, and would spin `/!{a|b}!/`
|
|
35
|
+
* instead of emitting it.
|
|
32
36
|
*/
|
|
33
|
-
declare const AST_VERSION =
|
|
37
|
+
declare const AST_VERSION = 5;
|
|
34
38
|
/** Opaque public handle (re-exported as `Ast` from the package index). */
|
|
35
39
|
interface Ast {
|
|
36
40
|
readonly astVersion: typeof AST_VERSION;
|
|
@@ -95,7 +99,8 @@ interface PluralIssue {
|
|
|
95
99
|
* lets the deterministic parity fixtures assert exact output.
|
|
96
100
|
*
|
|
97
101
|
* Order: strip stray sentinels → build vars, roll #def, tree-walk (+ #include) →
|
|
98
|
-
* cosmetic post-process (if on) → mandatory neutralize safety-restore (always)
|
|
102
|
+
* cosmetic post-process (if on) → mandatory neutralize safety-restore (always) → decode the
|
|
103
|
+
* `/! … !/` spans (always).
|
|
99
104
|
*/
|
|
100
105
|
|
|
101
106
|
/** Default for {@link PipelineOptions.maxDepth} (#include + parse-nesting guard). */
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/internal/ast.ts
|
|
2
|
-
var AST_VERSION =
|
|
2
|
+
var AST_VERSION = 5;
|
|
3
3
|
function walk(nodes, visit) {
|
|
4
4
|
const stack = [{ list: nodes, i: 0 }];
|
|
5
5
|
while (stack.length > 0) {
|
|
@@ -43,31 +43,6 @@ function nameMap(...sources) {
|
|
|
43
43
|
return map;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// src/internal/neutralize.ts
|
|
47
|
-
var STRUCTURAL = ["{", "}", "[", "]", "%", "#"];
|
|
48
|
-
var SENTINEL_BASE = 57344;
|
|
49
|
-
var SHIELD = /* @__PURE__ */ new Map();
|
|
50
|
-
var RESTORE = /* @__PURE__ */ new Map();
|
|
51
|
-
STRUCTURAL.forEach((ch, i) => {
|
|
52
|
-
const sentinel = String.fromCharCode(SENTINEL_BASE + i);
|
|
53
|
-
SHIELD.set(ch, sentinel);
|
|
54
|
-
RESTORE.set(sentinel, ch);
|
|
55
|
-
});
|
|
56
|
-
var SHIELD_RE = /[{}[\]%#]/gu;
|
|
57
|
-
var RESTORE_RE = new RegExp(
|
|
58
|
-
`[${String.fromCharCode(SENTINEL_BASE)}-${String.fromCharCode(SENTINEL_BASE + STRUCTURAL.length - 1)}]`,
|
|
59
|
-
"gu"
|
|
60
|
-
);
|
|
61
|
-
function neutralize(value) {
|
|
62
|
-
return value.replace(SHIELD_RE, (ch) => SHIELD.get(ch) ?? ch);
|
|
63
|
-
}
|
|
64
|
-
function safetyRestore(text) {
|
|
65
|
-
return text.replace(RESTORE_RE, (ch) => RESTORE.get(ch) ?? ch);
|
|
66
|
-
}
|
|
67
|
-
function stripSentinels(text) {
|
|
68
|
-
return text.replace(RESTORE_RE, "");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
46
|
// src/internal/pairs.ts
|
|
72
47
|
var BRACE_OPEN = 123;
|
|
73
48
|
var BRACE_CLOSE = 125;
|
|
@@ -345,6 +320,126 @@ var TextIndex = class {
|
|
|
345
320
|
}
|
|
346
321
|
};
|
|
347
322
|
|
|
323
|
+
// src/internal/verbatim.ts
|
|
324
|
+
var OPEN = 983040;
|
|
325
|
+
var CLOSE = 983041;
|
|
326
|
+
var DIGIT = 987136;
|
|
327
|
+
var RESERVED_RE = /[-\u{F0000}-\u{F1FFF}]/gu;
|
|
328
|
+
var SPAN_RANGE_RE = /[\u{F0000}-\u{F1FFF}]/gu;
|
|
329
|
+
var TOKEN_RE = /\u{F0000}((?:[\u{F1000}-\u{F1FFF}]{2})*)\u{F0001}/gu;
|
|
330
|
+
var SPAN_HIGH_RE = /[\uDB80-\uDB87]/;
|
|
331
|
+
function encodeVerbatim(content) {
|
|
332
|
+
let out = String.fromCodePoint(OPEN);
|
|
333
|
+
for (const ch of content) {
|
|
334
|
+
const c = ch.codePointAt(0);
|
|
335
|
+
out += String.fromCodePoint(DIGIT + (c >> 12), DIGIT + (c & 4095));
|
|
336
|
+
}
|
|
337
|
+
return out + String.fromCodePoint(CLOSE);
|
|
338
|
+
}
|
|
339
|
+
function decodeVerbatim(text) {
|
|
340
|
+
if (!SPAN_HIGH_RE.test(text)) return text;
|
|
341
|
+
TOKEN_RE.lastIndex = 0;
|
|
342
|
+
return text.replace(TOKEN_RE, (_m, digits) => {
|
|
343
|
+
let out = "";
|
|
344
|
+
const units = [...digits];
|
|
345
|
+
for (let i = 0; i + 1 < units.length; i += 2) {
|
|
346
|
+
out += String.fromCodePoint(units[i].codePointAt(0) - DIGIT << 12 | units[i + 1].codePointAt(0) - DIGIT);
|
|
347
|
+
}
|
|
348
|
+
return out;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function budgetLength(value) {
|
|
352
|
+
return SPAN_HIGH_RE.test(value) ? decodeVerbatim(value).length : value.length;
|
|
353
|
+
}
|
|
354
|
+
function stripSpanRange(value) {
|
|
355
|
+
return SPAN_HIGH_RE.test(value) ? value.replace(SPAN_RANGE_RE, "") : value;
|
|
356
|
+
}
|
|
357
|
+
function projectSource(src) {
|
|
358
|
+
const outStarts = [];
|
|
359
|
+
const srcStarts = [];
|
|
360
|
+
const srcEnds = [];
|
|
361
|
+
const tokens = [];
|
|
362
|
+
let out = "";
|
|
363
|
+
let spans = 0;
|
|
364
|
+
let unclosed = -1;
|
|
365
|
+
const copy = (start, end) => {
|
|
366
|
+
if (start >= end) return;
|
|
367
|
+
const chunk = src.slice(start, end);
|
|
368
|
+
let from2 = 0;
|
|
369
|
+
for (const m of chunk.matchAll(RESERVED_RE)) {
|
|
370
|
+
if (m.index > from2) push(start + from2, start + m.index, chunk.slice(from2, m.index), false);
|
|
371
|
+
from2 = m.index + m[0].length;
|
|
372
|
+
}
|
|
373
|
+
if (from2 < chunk.length) push(start + from2, end, chunk.slice(from2), false);
|
|
374
|
+
};
|
|
375
|
+
const push = (srcStart, srcEnd, piece, token) => {
|
|
376
|
+
outStarts.push(out.length);
|
|
377
|
+
srcStarts.push(srcStart);
|
|
378
|
+
srcEnds.push(srcEnd);
|
|
379
|
+
tokens.push(token);
|
|
380
|
+
out += piece;
|
|
381
|
+
};
|
|
382
|
+
let from = 0;
|
|
383
|
+
let at = 0;
|
|
384
|
+
let nextComment = -2;
|
|
385
|
+
let nextSpan = -2;
|
|
386
|
+
let commentCloserGone = false;
|
|
387
|
+
let spanCloserGone = false;
|
|
388
|
+
for (; ; ) {
|
|
389
|
+
if (nextComment !== -1 && nextComment < at) nextComment = src.indexOf("/#", at);
|
|
390
|
+
if (nextSpan !== -1 && nextSpan < at) nextSpan = src.indexOf("/!", at);
|
|
391
|
+
if (nextComment === -1 && nextSpan === -1) break;
|
|
392
|
+
const isSpan = nextComment === -1 || nextSpan !== -1 && nextSpan < nextComment;
|
|
393
|
+
const open = isSpan ? nextSpan : nextComment;
|
|
394
|
+
const gone = isSpan ? spanCloserGone : commentCloserGone;
|
|
395
|
+
const close = gone ? -1 : src.indexOf(isSpan ? "!/" : "#/", open + 2);
|
|
396
|
+
if (close === -1) {
|
|
397
|
+
if (isSpan) {
|
|
398
|
+
spanCloserGone = true;
|
|
399
|
+
if (unclosed === -1) unclosed = open;
|
|
400
|
+
} else {
|
|
401
|
+
commentCloserGone = true;
|
|
402
|
+
}
|
|
403
|
+
at = open + 2;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
copy(from, open);
|
|
407
|
+
if (isSpan) {
|
|
408
|
+
spans += 1;
|
|
409
|
+
if (close > open + 2) push(open, close + 2, encodeVerbatim(src.slice(open + 2, close)), true);
|
|
410
|
+
}
|
|
411
|
+
from = at = close + 2;
|
|
412
|
+
}
|
|
413
|
+
copy(from, src.length);
|
|
414
|
+
const segmentAt = (offset) => {
|
|
415
|
+
let lo = 0;
|
|
416
|
+
let hi = outStarts.length - 1;
|
|
417
|
+
if (hi < 0 || outStarts[0] > offset) return -1;
|
|
418
|
+
while (lo < hi) {
|
|
419
|
+
const mid = lo + hi + 1 >> 1;
|
|
420
|
+
if (outStarts[mid] <= offset) lo = mid;
|
|
421
|
+
else hi = mid - 1;
|
|
422
|
+
}
|
|
423
|
+
return lo;
|
|
424
|
+
};
|
|
425
|
+
const toSource = (offset) => {
|
|
426
|
+
const at2 = segmentAt(offset);
|
|
427
|
+
if (at2 === -1) return 0;
|
|
428
|
+
const delta = offset - outStarts[at2];
|
|
429
|
+
if (delta <= 0) return srcStarts[at2];
|
|
430
|
+
if (tokens[at2]) return srcEnds[at2];
|
|
431
|
+
return Math.min(srcStarts[at2] + delta, srcEnds[at2]);
|
|
432
|
+
};
|
|
433
|
+
const toSourceEnd = (offset) => {
|
|
434
|
+
if (offset <= 0) return toSource(0);
|
|
435
|
+
const at2 = segmentAt(offset - 1);
|
|
436
|
+
if (at2 === -1) return 0;
|
|
437
|
+
if (tokens[at2]) return srcEnds[at2];
|
|
438
|
+
return Math.min(srcStarts[at2] + (offset - outStarts[at2]), srcEnds[at2]);
|
|
439
|
+
};
|
|
440
|
+
return { text: out, spans, unclosed, toSource, toSourceEnd };
|
|
441
|
+
}
|
|
442
|
+
|
|
348
443
|
// src/internal/parser.ts
|
|
349
444
|
var VARIABLE_RE = /%(\w+)%/y;
|
|
350
445
|
var PERCENT = 37;
|
|
@@ -356,7 +451,7 @@ var DIRECTIVE_RE = /^[ \t]*#(set|def)[ \t]+%(\w+)%[ \t]*=[ \t]*((?:.*[^ \t\n\r\u
|
|
|
356
451
|
var CONDITIONAL_NAME_RE2 = /[A-Za-z_]\w*/y;
|
|
357
452
|
var PLURAL_PREFIX = "plural ";
|
|
358
453
|
function parseTemplate(src) {
|
|
359
|
-
const { body, setDefs, defDefs } = extractDirectives(
|
|
454
|
+
const { body, setDefs, defDefs } = extractDirectives(projectSource(src).text);
|
|
360
455
|
return { astVersion: AST_VERSION, source: src, setDefs, defDefs, nodes: parseSequence(body) };
|
|
361
456
|
}
|
|
362
457
|
function extractDirectives(text) {
|
|
@@ -390,19 +485,6 @@ function extractDirectives(text) {
|
|
|
390
485
|
);
|
|
391
486
|
return { body: stripped.replace(/\n{3,}/gu, "\n\n"), setDefs, defDefs, occurrences };
|
|
392
487
|
}
|
|
393
|
-
function stripComments(text) {
|
|
394
|
-
let out = "";
|
|
395
|
-
let from = 0;
|
|
396
|
-
for (; ; ) {
|
|
397
|
-
const open = text.indexOf("/#", from);
|
|
398
|
-
if (open === -1) break;
|
|
399
|
-
const close = text.indexOf("#/", open + 2);
|
|
400
|
-
if (close === -1) break;
|
|
401
|
-
out += text.slice(from, open);
|
|
402
|
-
from = close + 2;
|
|
403
|
-
}
|
|
404
|
-
return from === 0 ? text : out + text.slice(from);
|
|
405
|
-
}
|
|
406
488
|
function parseSequence(text) {
|
|
407
489
|
const index = new TextIndex(text);
|
|
408
490
|
const frame = (start, end) => ({
|
|
@@ -787,7 +869,8 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set(["minsize", "maxsize", "sep", "l
|
|
|
787
869
|
var INCLUDE_RE = /^[ \t]*#include[ \t\n\r\f\x0B]+"([^"]+)"[ \t\n\r\f\x0B]*$/gmu;
|
|
788
870
|
function validateTemplate(src, opts = {}) {
|
|
789
871
|
const diagnostics = [];
|
|
790
|
-
const
|
|
872
|
+
const projection = projectSource(src);
|
|
873
|
+
const text = projection.text;
|
|
791
874
|
const idx = buildLineIndex(text);
|
|
792
875
|
checkBrackets(text, diagnostics);
|
|
793
876
|
checkDirectives(text, diagnostics);
|
|
@@ -797,7 +880,62 @@ function validateTemplate(src, opts = {}) {
|
|
|
797
880
|
if (opts.knownIncludes && opts.knownIncludes.length > 0) {
|
|
798
881
|
checkIncludeTargets(text, idx, opts.knownIncludes, diagnostics);
|
|
799
882
|
}
|
|
800
|
-
|
|
883
|
+
const srcIdx = text === src ? idx : buildLineIndex(src);
|
|
884
|
+
const remap = { text, idx, src, srcIdx, toSource: projection.toSource, toSourceEnd: projection.toSourceEnd };
|
|
885
|
+
const placed = text === src ? diagnostics : diagnostics.map((d) => toSourcePositions(d, remap));
|
|
886
|
+
if (projection.unclosed !== -1) {
|
|
887
|
+
placed.push(warn("verbatim.unclosed", "Unclosed '/!' \u2014 the text after it is parsed as spintax.", span(srcIdx, projection.unclosed, 2)));
|
|
888
|
+
}
|
|
889
|
+
return placed;
|
|
890
|
+
}
|
|
891
|
+
function toSourcePositions(d, r) {
|
|
892
|
+
const codePoints = d.code.startsWith("bracket.");
|
|
893
|
+
const move = (line, column) => {
|
|
894
|
+
const offset = r.toSource(lineColToOffset(r, line, column, codePoints));
|
|
895
|
+
return lineColAt(r, offset, codePoints);
|
|
896
|
+
};
|
|
897
|
+
const start = move(d.line, d.column);
|
|
898
|
+
const data = d.data === void 0 ? void 0 : Object.fromEntries(
|
|
899
|
+
Object.entries(d.data).map(([key, value]) => [key, typeof value === "string" ? decodeVerbatim(value) : value])
|
|
900
|
+
);
|
|
901
|
+
const moved = { ...d, message: decodeVerbatim(d.message), line: start.line, column: start.column, ...data === void 0 ? {} : { data } };
|
|
902
|
+
if (d.endLine !== void 0 && d.endColumn !== void 0) {
|
|
903
|
+
const end = lineColAt(r, r.toSourceEnd(lineColToOffset(r, d.endLine, d.endColumn, codePoints)), codePoints);
|
|
904
|
+
return { ...moved, endLine: end.line, endColumn: end.column };
|
|
905
|
+
}
|
|
906
|
+
return moved;
|
|
907
|
+
}
|
|
908
|
+
function astralPrefix(text) {
|
|
909
|
+
const prefix = new Uint32Array(text.length + 1);
|
|
910
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
911
|
+
const c = text.charCodeAt(i);
|
|
912
|
+
const pair = c >= 55296 && c <= 56319 && (text.charCodeAt(i + 1) & 64512) === 56320;
|
|
913
|
+
prefix[i + 1] = prefix[i] + (pair ? 1 : 0);
|
|
914
|
+
}
|
|
915
|
+
return prefix;
|
|
916
|
+
}
|
|
917
|
+
function codePointsBetween(prefix, from, to) {
|
|
918
|
+
return to - from - (prefix[to] - prefix[from]);
|
|
919
|
+
}
|
|
920
|
+
function lineColToOffset(r, line, column, codePoints) {
|
|
921
|
+
const start = r.idx.starts[Math.min(Math.max(line, 1), r.idx.starts.length) - 1] ?? 0;
|
|
922
|
+
if (!codePoints) return Math.min(start + column - 1, r.text.length);
|
|
923
|
+
const prefix = r.textAstral ??= astralPrefix(r.text);
|
|
924
|
+
let lo = start;
|
|
925
|
+
let hi = Math.min(start + 2 * (column - 1), r.text.length);
|
|
926
|
+
while (lo < hi) {
|
|
927
|
+
const mid = lo + hi >> 1;
|
|
928
|
+
if (codePointsBetween(prefix, start, mid) < column - 1) lo = mid + 1;
|
|
929
|
+
else hi = mid;
|
|
930
|
+
}
|
|
931
|
+
return lo;
|
|
932
|
+
}
|
|
933
|
+
function lineColAt(r, offset, codePoints) {
|
|
934
|
+
const pos = offsetToLineCol(r.srcIdx, offset);
|
|
935
|
+
if (!codePoints) return pos;
|
|
936
|
+
const start = r.srcIdx.starts[pos.line - 1] ?? 0;
|
|
937
|
+
const prefix = r.srcAstral ??= astralPrefix(r.src);
|
|
938
|
+
return { line: pos.line, column: codePointsBetween(prefix, start, Math.min(offset, r.src.length)) + 1 };
|
|
801
939
|
}
|
|
802
940
|
function err(code, message, pos) {
|
|
803
941
|
return { severity: "error", code, message, ...pos };
|
|
@@ -1223,13 +1361,14 @@ function checkIncludeTargets(text, idx, known, out) {
|
|
|
1223
1361
|
const set = new Set(known);
|
|
1224
1362
|
INCLUDE_RE.lastIndex = 0;
|
|
1225
1363
|
for (const m of text.matchAll(INCLUDE_RE)) {
|
|
1226
|
-
const
|
|
1364
|
+
const raw = m[1] ?? "";
|
|
1365
|
+
const ref = decodeVerbatim(raw);
|
|
1227
1366
|
if (!set.has(ref)) {
|
|
1228
1367
|
const refOffset = (m.index ?? 0) + m[0].indexOf('"') + 1;
|
|
1229
1368
|
out.push(err(
|
|
1230
1369
|
"include.unknown-target",
|
|
1231
1370
|
`#include target '${ref}' does not match any known template.`,
|
|
1232
|
-
{ ...span(idx, refOffset,
|
|
1371
|
+
{ ...span(idx, refOffset, raw.length), data: { target: ref } }
|
|
1233
1372
|
));
|
|
1234
1373
|
}
|
|
1235
1374
|
}
|
|
@@ -1243,10 +1382,10 @@ var INCLUDE_RE2 = /^[ \t]*#include[ \t\n\r\f\x0B]+"([^"]+)"[ \t\n\r\f\x0B]*$/gmu
|
|
|
1243
1382
|
var VARIABLE_RE2 = /%(\w+)%/gu;
|
|
1244
1383
|
var CONDITIONAL_REF_RE = /\{\?!?([A-Za-z_]\w*)\?/gu;
|
|
1245
1384
|
function extractFromSource(src) {
|
|
1246
|
-
const text =
|
|
1385
|
+
const text = projectSource(src).text;
|
|
1247
1386
|
const sets = collect(text, SET_DEF_RE, true);
|
|
1248
1387
|
const defs = collect(text, DEF_DEF_RE, true);
|
|
1249
|
-
const includes = collect(text, INCLUDE_RE2, false);
|
|
1388
|
+
const includes = [...new Set(collect(text, INCLUDE_RE2, false).map(decodeVerbatim))];
|
|
1250
1389
|
const body = text.replace(DEFINITION_LHS_RE, "");
|
|
1251
1390
|
const refs = /* @__PURE__ */ new Set();
|
|
1252
1391
|
addAll(refs, body, VARIABLE_RE2, true);
|
|
@@ -1465,7 +1604,8 @@ function renderAst(ast, ctx) {
|
|
|
1465
1604
|
}
|
|
1466
1605
|
function resolveIncludes(text, ctx) {
|
|
1467
1606
|
INCLUDE_LINE_RE.lastIndex = 0;
|
|
1468
|
-
return text.replace(INCLUDE_LINE_RE, (_m,
|
|
1607
|
+
return text.replace(INCLUDE_LINE_RE, (_m, raw) => {
|
|
1608
|
+
const ref = decodeVerbatim(raw);
|
|
1469
1609
|
if (ctx.includeStack.includes(ref) || ctx.includeStack.length >= ctx.maxDepth) return "";
|
|
1470
1610
|
let included;
|
|
1471
1611
|
try {
|
|
@@ -1619,7 +1759,7 @@ function resolveVariable(name, opts) {
|
|
|
1619
1759
|
const value = opts.vars[name.toLowerCase()];
|
|
1620
1760
|
if (value === void 0 || opts.frozen === true) return `%${name}%`;
|
|
1621
1761
|
if (opts.budget.left <= 0) return `%${name}%`;
|
|
1622
|
-
opts.budget.left -= value
|
|
1762
|
+
opts.budget.left -= budgetLength(value);
|
|
1623
1763
|
if (opts.depth >= MAX_VARIABLE_DEPTH || !/[{[%]/u.test(value)) return value;
|
|
1624
1764
|
return renderFragment(parseSequence(value), { ...opts, depth: opts.depth + 1 });
|
|
1625
1765
|
}
|
|
@@ -1636,7 +1776,7 @@ function expandVarsFixpoint(text, opts, passes) {
|
|
|
1636
1776
|
const value = opts.vars[name.toLowerCase()];
|
|
1637
1777
|
if (value === void 0) return m;
|
|
1638
1778
|
if (opts.budget.left <= 0) return m;
|
|
1639
|
-
opts.budget.left -= value
|
|
1779
|
+
opts.budget.left -= budgetLength(value);
|
|
1640
1780
|
changed = true;
|
|
1641
1781
|
return value;
|
|
1642
1782
|
});
|
|
@@ -2056,10 +2196,10 @@ var afterLineBreak = (text, from) => {
|
|
|
2056
2196
|
return at === -1 ? null : { leadStart: at + 1, resume: at + 1 };
|
|
2057
2197
|
};
|
|
2058
2198
|
var SHIELD_PREFIXES = ["URL", "URI", "EMAIL", "DOTTOK", "DOM", "NUM", "ABBR"];
|
|
2059
|
-
var
|
|
2199
|
+
var RESTORE_RE = new RegExp(`\\x00(?:${SHIELD_PREFIXES.join("|")})_\\d+\\x00`, "gu");
|
|
2060
2200
|
function restore(text, input, placeholders) {
|
|
2061
2201
|
if (!input.includes("\0")) {
|
|
2062
|
-
return text.replace(
|
|
2202
|
+
return text.replace(RESTORE_RE, (key) => placeholders.get(key) ?? key);
|
|
2063
2203
|
}
|
|
2064
2204
|
return restoreAsTheLoopDoes(text, placeholders);
|
|
2065
2205
|
}
|
|
@@ -2129,11 +2269,33 @@ function postProcess(input, locale = "") {
|
|
|
2129
2269
|
return restore(text, input, placeholders).trim();
|
|
2130
2270
|
}
|
|
2131
2271
|
|
|
2272
|
+
// src/internal/neutralize.ts
|
|
2273
|
+
var STRUCTURAL = ["{", "}", "[", "]", "%", "#"];
|
|
2274
|
+
var SENTINEL_BASE = 57344;
|
|
2275
|
+
var SHIELD = /* @__PURE__ */ new Map();
|
|
2276
|
+
var RESTORE = /* @__PURE__ */ new Map();
|
|
2277
|
+
STRUCTURAL.forEach((ch, i) => {
|
|
2278
|
+
const sentinel = String.fromCharCode(SENTINEL_BASE + i);
|
|
2279
|
+
SHIELD.set(ch, sentinel);
|
|
2280
|
+
RESTORE.set(sentinel, ch);
|
|
2281
|
+
});
|
|
2282
|
+
var SHIELD_RE = /[{}[\]%#]/gu;
|
|
2283
|
+
var RESTORE_RE2 = new RegExp(
|
|
2284
|
+
`[${String.fromCharCode(SENTINEL_BASE)}-${String.fromCharCode(SENTINEL_BASE + STRUCTURAL.length - 1)}]`,
|
|
2285
|
+
"gu"
|
|
2286
|
+
);
|
|
2287
|
+
function neutralize(value) {
|
|
2288
|
+
return value.replace(SHIELD_RE, (ch) => SHIELD.get(ch) ?? ch);
|
|
2289
|
+
}
|
|
2290
|
+
function safetyRestore(text) {
|
|
2291
|
+
return text.replace(RESTORE_RE2, (ch) => RESTORE.get(ch) ?? ch);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2132
2294
|
// src/internal/pipeline.ts
|
|
2133
2295
|
var DEFAULT_MAX_DEPTH = 20;
|
|
2134
2296
|
function renderWith(input, rng, opts = {}) {
|
|
2135
2297
|
const ctx = {
|
|
2136
|
-
runtimeContext: opts.context
|
|
2298
|
+
runtimeContext: runtimeValues(opts.context),
|
|
2137
2299
|
rng,
|
|
2138
2300
|
locale: opts.locale ?? "",
|
|
2139
2301
|
resolver: opts.includeResolver,
|
|
@@ -2147,7 +2309,13 @@ function renderWith(input, rng, opts = {}) {
|
|
|
2147
2309
|
const ast = resolveAst(input);
|
|
2148
2310
|
let out = renderAst(ast, ctx);
|
|
2149
2311
|
if (opts.postProcess !== false) out = postProcess(out, ctx.locale);
|
|
2150
|
-
return safetyRestore(out);
|
|
2312
|
+
return decodeVerbatim(safetyRestore(out));
|
|
2313
|
+
}
|
|
2314
|
+
function runtimeValues(context) {
|
|
2315
|
+
if (context === void 0) return {};
|
|
2316
|
+
const entries = Object.entries(context);
|
|
2317
|
+
if (entries.every(([, value]) => stripSpanRange(value) === value)) return context;
|
|
2318
|
+
return Object.fromEntries(entries.map(([key, value]) => [key, stripSpanRange(value)]));
|
|
2151
2319
|
}
|
|
2152
2320
|
function resolveAst(input) {
|
|
2153
2321
|
if (typeof input === "string") return parseTemplate(input);
|
|
@@ -2229,6 +2397,7 @@ function countConstructs(ast, includeCount) {
|
|
|
2229
2397
|
});
|
|
2230
2398
|
counts.set = Object.keys(ast.setDefs).length;
|
|
2231
2399
|
counts.include = includeCount;
|
|
2400
|
+
counts.verbatim = projectSource(ast.source).spans;
|
|
2232
2401
|
return counts;
|
|
2233
2402
|
}
|
|
2234
2403
|
function neutralize2(value) {
|