@spintax/core 0.12.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 +67 -0
- package/README.md +2 -1
- package/dist/index.cjs +237 -63
- 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 +237 -63
- 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
|
});
|
|
@@ -1917,7 +2057,7 @@ var TLD_UPPER = "\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}";
|
|
|
1917
2057
|
var TLD = `(?:${XN}[a-zA-Z0-9\\-\\u017F\\u212A]{2,59}|[${TLD_LOWER}][${TLD_LOWER}\\p{N}-]{1,62}|[${TLD_UPPER}][${TLD_UPPER}\\p{N}-]{1,62})`;
|
|
1918
2058
|
var DOMAIN_PART = `(?:${LABEL}\\.)+${TLD}`;
|
|
1919
2059
|
var URI_BODY = `[^\\x00${WS}<>"')\\]]`;
|
|
1920
|
-
var URI_RE = new RegExp(`(?:(?:https?|ftp)
|
|
2060
|
+
var URI_RE = new RegExp(`(?:(?:https?|ftp):\\/\\/${URI_BODY}*|(?:mailto|tel):${URI_BODY}+)`, "giu");
|
|
1921
2061
|
var MAILTEL_PREFIX_RE = /^(?:mailto|tel):/iu;
|
|
1922
2062
|
var DOMAIN_AT_RE = new RegExp(`${DOMAIN_PART}${WORD_BOUNDARY}`, "uy");
|
|
1923
2063
|
var DOMAIN_SCAN_RE = new RegExp(`${AFTER_NON_WORD}(?:(${DOMAIN_PART}${WORD_BOUNDARY})|(?:${LABEL}\\.)*${LABEL})`, "gu");
|
|
@@ -1966,12 +2106,15 @@ var SENTENCE_OPENERS = "\xBF\xA1";
|
|
|
1966
2106
|
var LEAD = `(?:<[^>]+>|[${SENTENCE_OPENERS}]|${S})*`;
|
|
1967
2107
|
var QUOTES = `"'\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019`;
|
|
1968
2108
|
var CLOSER = `[)\\]]|[${QUOTES}]+(?=${S}|$|<|\\/?>|[.,;:!?\u2026)\\]\u2014\u2013])`;
|
|
2109
|
+
var DOT_TOKEN_RE = new RegExp(`(?<=^|${S}|[(\\[{>/\\\\${QUOTES}])\\.[\\p{L}\\p{N}][\\p{L}\\p{N}_.\\-]*`, "gu");
|
|
1969
2110
|
var SPACE_BEFORE_PUNCT_RE = new RegExp(`(?<!${S})${S}+([,;:!?.])`, "gu");
|
|
1970
2111
|
var SPACE_AFTER_COMMA_RE = new RegExp(`([,;:])(?!\\p{Nd})(?!${S}|$|<|${CLOSER})`, "gu");
|
|
1971
2112
|
var SPACE_AFTER_SENTENCE_RE = new RegExp(`(?<![.!?])([.!?]+)(?![.!?])(?!\\p{Nd})(?!${S}|$|<|${CLOSER})`, "gu");
|
|
1972
2113
|
var SPACE_AFTER_OPENER_RE = new RegExp(`([${SENTENCE_OPENERS}])${S}+`, "gu");
|
|
1973
2114
|
var CAP_FIRST_RE = new RegExp(`^(${LEAD})(\\p{Ll})`, "u");
|
|
1974
2115
|
var up = (ch) => ch.toUpperCase();
|
|
2116
|
+
var upTurkic = (ch) => ch === "i" ? "\u0130" : ch.toUpperCase();
|
|
2117
|
+
var TURKIC_LANGS = /* @__PURE__ */ new Set(["tr", "az"]);
|
|
1975
2118
|
function indexLeads(text) {
|
|
1976
2119
|
const n = text.length;
|
|
1977
2120
|
const leadEnd = new Int32Array(n + 1);
|
|
@@ -2004,7 +2147,7 @@ function leadEndFrom(text, i, leads) {
|
|
|
2004
2147
|
}
|
|
2005
2148
|
return j < text.length ? leads().leadEnd[j] : j;
|
|
2006
2149
|
}
|
|
2007
|
-
function capitalizeAfter(text, lower, next, context) {
|
|
2150
|
+
function capitalizeAfter(text, lower, next, context, upper) {
|
|
2008
2151
|
const leads = () => {
|
|
2009
2152
|
if (context.leads === null || context.leads.leadEnd.length !== text.length + 1) context.leads = indexLeads(text);
|
|
2010
2153
|
return context.leads;
|
|
@@ -2019,7 +2162,7 @@ function capitalizeAfter(text, lower, next, context) {
|
|
|
2019
2162
|
if (cp === void 0) continue;
|
|
2020
2163
|
const ch = String.fromCodePoint(cp);
|
|
2021
2164
|
if (!lower.test(ch)) continue;
|
|
2022
|
-
out += text.slice(emitted, at) +
|
|
2165
|
+
out += text.slice(emitted, at) + upper(ch);
|
|
2023
2166
|
emitted = at + ch.length;
|
|
2024
2167
|
from = emitted;
|
|
2025
2168
|
}
|
|
@@ -2052,11 +2195,11 @@ var afterLineBreak = (text, from) => {
|
|
|
2052
2195
|
const at = text.indexOf("\n", from);
|
|
2053
2196
|
return at === -1 ? null : { leadStart: at + 1, resume: at + 1 };
|
|
2054
2197
|
};
|
|
2055
|
-
var SHIELD_PREFIXES = ["URL", "URI", "EMAIL", "DOM", "NUM", "ABBR"];
|
|
2056
|
-
var
|
|
2198
|
+
var SHIELD_PREFIXES = ["URL", "URI", "EMAIL", "DOTTOK", "DOM", "NUM", "ABBR"];
|
|
2199
|
+
var RESTORE_RE = new RegExp(`\\x00(?:${SHIELD_PREFIXES.join("|")})_\\d+\\x00`, "gu");
|
|
2057
2200
|
function restore(text, input, placeholders) {
|
|
2058
2201
|
if (!input.includes("\0")) {
|
|
2059
|
-
return text.replace(
|
|
2202
|
+
return text.replace(RESTORE_RE, (key) => placeholders.get(key) ?? key);
|
|
2060
2203
|
}
|
|
2061
2204
|
return restoreAsTheLoopDoes(text, placeholders);
|
|
2062
2205
|
}
|
|
@@ -2087,7 +2230,8 @@ function restoreAsTheLoopDoes(text, placeholders) {
|
|
|
2087
2230
|
}
|
|
2088
2231
|
return out;
|
|
2089
2232
|
}
|
|
2090
|
-
function postProcess(input) {
|
|
2233
|
+
function postProcess(input, locale = "") {
|
|
2234
|
+
const upper = TURKIC_LANGS.has(normalizeBaseLang(locale)) ? upTurkic : up;
|
|
2091
2235
|
const placeholders = /* @__PURE__ */ new Map();
|
|
2092
2236
|
let counter = 0;
|
|
2093
2237
|
const store = (value, prefix) => {
|
|
@@ -2107,6 +2251,7 @@ function postProcess(input) {
|
|
|
2107
2251
|
(m) => storeWithTrailingPunct(m, MAILTEL_PREFIX_RE.test(m) ? "URI" : "URL")
|
|
2108
2252
|
);
|
|
2109
2253
|
text = shieldEmails(text, (m) => store(m, "EMAIL"));
|
|
2254
|
+
text = text.replace(DOT_TOKEN_RE, (m) => storeWithTrailingPunct(m, "DOTTOK"));
|
|
2110
2255
|
text = shieldDomains(text, (m) => store(m, "DOM"));
|
|
2111
2256
|
text = text.replace(DECIMAL_RE, (m) => store(m, "NUM"));
|
|
2112
2257
|
text = text.replace(MULTI_ABBR_RE, (m) => store(m, "ABBR"));
|
|
@@ -2116,19 +2261,41 @@ function postProcess(input) {
|
|
|
2116
2261
|
text = text.replace(SPACE_AFTER_COMMA_RE, "$1 ");
|
|
2117
2262
|
text = text.replace(SPACE_AFTER_SENTENCE_RE, "$1 ");
|
|
2118
2263
|
text = text.replace(SPACE_AFTER_OPENER_RE, "$1");
|
|
2119
|
-
text = text.replace(CAP_FIRST_RE, (_m, lead, ch) => lead +
|
|
2264
|
+
text = text.replace(CAP_FIRST_RE, (_m, lead, ch) => lead + upper(ch));
|
|
2120
2265
|
const leadContext = { leads: null };
|
|
2121
|
-
text = capitalizeAfter(text, LOWER_RE, afterSentenceEnd, leadContext);
|
|
2122
|
-
text = capitalizeAfter(text, LOWER_RE, afterBlockTag(), leadContext);
|
|
2123
|
-
text = capitalizeAfter(text, LOWER_RE, afterLineBreak, leadContext);
|
|
2266
|
+
text = capitalizeAfter(text, LOWER_RE, afterSentenceEnd, leadContext, upper);
|
|
2267
|
+
text = capitalizeAfter(text, LOWER_RE, afterBlockTag(), leadContext, upper);
|
|
2268
|
+
text = capitalizeAfter(text, LOWER_RE, afterLineBreak, leadContext, upper);
|
|
2124
2269
|
return restore(text, input, placeholders).trim();
|
|
2125
2270
|
}
|
|
2126
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
|
+
|
|
2127
2294
|
// src/internal/pipeline.ts
|
|
2128
2295
|
var DEFAULT_MAX_DEPTH = 20;
|
|
2129
2296
|
function renderWith(input, rng, opts = {}) {
|
|
2130
2297
|
const ctx = {
|
|
2131
|
-
runtimeContext: opts.context
|
|
2298
|
+
runtimeContext: runtimeValues(opts.context),
|
|
2132
2299
|
rng,
|
|
2133
2300
|
locale: opts.locale ?? "",
|
|
2134
2301
|
resolver: opts.includeResolver,
|
|
@@ -2141,8 +2308,14 @@ function renderWith(input, rng, opts = {}) {
|
|
|
2141
2308
|
};
|
|
2142
2309
|
const ast = resolveAst(input);
|
|
2143
2310
|
let out = renderAst(ast, ctx);
|
|
2144
|
-
if (opts.postProcess !== false) out = postProcess(out);
|
|
2145
|
-
return safetyRestore(out);
|
|
2311
|
+
if (opts.postProcess !== false) out = postProcess(out, ctx.locale);
|
|
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)]));
|
|
2146
2319
|
}
|
|
2147
2320
|
function resolveAst(input) {
|
|
2148
2321
|
if (typeof input === "string") return parseTemplate(input);
|
|
@@ -2224,6 +2397,7 @@ function countConstructs(ast, includeCount) {
|
|
|
2224
2397
|
});
|
|
2225
2398
|
counts.set = Object.keys(ast.setDefs).length;
|
|
2226
2399
|
counts.include = includeCount;
|
|
2400
|
+
counts.verbatim = projectSource(ast.source).spans;
|
|
2227
2401
|
return counts;
|
|
2228
2402
|
}
|
|
2229
2403
|
function neutralize2(value) {
|