@spintax/core 0.5.0 → 0.5.2
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 +83 -0
- package/dist/index.cjs +38 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -631,6 +631,7 @@ function checkPlurals(text, idx, locale, knownVariables, out) {
|
|
|
631
631
|
var UNRESOLVED_AT_PLURAL_TIME = /\[|\{(?!\?)/u;
|
|
632
632
|
var ANY_BRACKET = /[[\]{}]/u;
|
|
633
633
|
var FORM_EXPANSION_PASSES = 51;
|
|
634
|
+
var FORM_EXPANSION_MAX_GROWTH = 64 * 1024;
|
|
634
635
|
function expandFormsForCounting(formsRaw, defs, macros, hostNames) {
|
|
635
636
|
const unknown = (directMacroSpintax = false) => ({
|
|
636
637
|
forms: 0,
|
|
@@ -638,17 +639,23 @@ function expandFormsForCounting(formsRaw, defs, macros, hostNames) {
|
|
|
638
639
|
directMacroSpintax
|
|
639
640
|
});
|
|
640
641
|
const seenMacro = /* @__PURE__ */ new Set();
|
|
642
|
+
const refsOf = (text2) => [...text2.matchAll(/%(\w+)%/gu)].map((m) => (m[1] ?? "").toLowerCase());
|
|
641
643
|
const walkMacros = (source) => {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
+
const stack = [{ refs: refsOf(source), i: 0 }];
|
|
645
|
+
while (stack.length > 0) {
|
|
646
|
+
const frame = stack[stack.length - 1];
|
|
647
|
+
if (frame.i >= frame.refs.length) {
|
|
648
|
+
stack.pop();
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const name = frame.refs[frame.i++];
|
|
644
652
|
if (defs[name] !== void 0 || hostNames.has(name)) continue;
|
|
645
653
|
const macro = macros[name];
|
|
646
654
|
if (macro === void 0 || seenMacro.has(name)) continue;
|
|
647
655
|
seenMacro.add(name);
|
|
648
656
|
if (/\{\?/u.test(macro)) return "opaque";
|
|
649
657
|
if (ANY_BRACKET.test(macro)) return "brackets";
|
|
650
|
-
|
|
651
|
-
if (deeper !== "clean") return deeper;
|
|
658
|
+
stack.push({ refs: refsOf(macro), i: 0 });
|
|
652
659
|
}
|
|
653
660
|
return "clean";
|
|
654
661
|
};
|
|
@@ -656,28 +663,33 @@ function expandFormsForCounting(formsRaw, defs, macros, hostNames) {
|
|
|
656
663
|
if (verbatim === "brackets") return unknown(true);
|
|
657
664
|
if (verbatim === "opaque") return unknown();
|
|
658
665
|
let text = formsRaw;
|
|
666
|
+
const budget = formsRaw.length + FORM_EXPANSION_MAX_GROWTH;
|
|
659
667
|
for (let pass = 0; pass < FORM_EXPANSION_PASSES; pass++) {
|
|
660
668
|
let sawReference = false;
|
|
661
669
|
let bailed = false;
|
|
662
|
-
|
|
670
|
+
const parts = [];
|
|
671
|
+
let total = 0;
|
|
672
|
+
let cursor = 0;
|
|
673
|
+
const re = /%(\w+)%/gu;
|
|
674
|
+
let m;
|
|
675
|
+
while ((m = re.exec(text)) !== null) {
|
|
663
676
|
sawReference = true;
|
|
664
|
-
const name =
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
return whole;
|
|
668
|
-
}
|
|
669
|
-
const value = defs[name] ?? macros[name];
|
|
670
|
-
if (value === void 0) {
|
|
671
|
-
bailed = true;
|
|
672
|
-
return whole;
|
|
673
|
-
}
|
|
674
|
-
if (ANY_BRACKET.test(value)) {
|
|
677
|
+
const name = (m[1] ?? "").toLowerCase();
|
|
678
|
+
const value = hostNames.has(name) ? void 0 : defs[name] ?? macros[name];
|
|
679
|
+
if (value === void 0 || ANY_BRACKET.test(value)) {
|
|
675
680
|
bailed = true;
|
|
676
|
-
|
|
681
|
+
break;
|
|
677
682
|
}
|
|
678
|
-
|
|
679
|
-
|
|
683
|
+
parts.push(text.slice(cursor, m.index), value);
|
|
684
|
+
total += m.index - cursor + value.length;
|
|
685
|
+
cursor = m.index + m[0].length;
|
|
686
|
+
if (total > budget) return unknown();
|
|
687
|
+
}
|
|
680
688
|
if (bailed) return unknown();
|
|
689
|
+
parts.push(text.slice(cursor));
|
|
690
|
+
total += text.length - cursor;
|
|
691
|
+
if (total > budget) return unknown();
|
|
692
|
+
text = parts.join("");
|
|
681
693
|
if (!sawReference) {
|
|
682
694
|
return { forms: text.split("|").length, unresolved: false, directMacroSpintax: false };
|
|
683
695
|
}
|
|
@@ -897,6 +909,7 @@ var NotImplementedError = class extends SpintaxError {
|
|
|
897
909
|
|
|
898
910
|
// src/internal/render.ts
|
|
899
911
|
var MAX_VARIABLE_DEPTH = 50;
|
|
912
|
+
var MAX_EXPANSION_CHARS = 1024 * 1024;
|
|
900
913
|
var INCLUDE_LINE_RE = /^[ \t]*#include[ \t\n\r\f\x0B]+"([^"]+)"[ \t\n\r\f\x0B]*$/gmu;
|
|
901
914
|
function renderAst(ast, ctx) {
|
|
902
915
|
const base = buildVars(ast.setDefs, ctx.runtimeContext);
|
|
@@ -904,7 +917,8 @@ function renderAst(ast, ctx) {
|
|
|
904
917
|
rng: ctx.rng,
|
|
905
918
|
locale: ctx.locale,
|
|
906
919
|
depth: 0,
|
|
907
|
-
onPluralError: ctx.onPluralError
|
|
920
|
+
onPluralError: ctx.onPluralError,
|
|
921
|
+
budget: { left: MAX_EXPANSION_CHARS }
|
|
908
922
|
};
|
|
909
923
|
const vars = Object.keys(ast.defDefs).length > 0 ? { ...base, ...rollDefinitions(ast.defDefs, base, ctx.runtimeContext, walkOpts) } : base;
|
|
910
924
|
const text = renderNodes(ast.nodes, { ...walkOpts, vars });
|
|
@@ -1017,6 +1031,8 @@ function resolveVariable(name, opts) {
|
|
|
1017
1031
|
const value = opts.vars[name.toLowerCase()];
|
|
1018
1032
|
if (value === void 0) return `%${name}%`;
|
|
1019
1033
|
if (opts.depth >= MAX_VARIABLE_DEPTH || !/[{[%]/u.test(value)) return value;
|
|
1034
|
+
if (opts.budget.left <= 0) return `%${name}%`;
|
|
1035
|
+
opts.budget.left -= value.length;
|
|
1020
1036
|
return renderNodes(parseSequence(value), { ...opts, depth: opts.depth + 1 });
|
|
1021
1037
|
}
|
|
1022
1038
|
function expandVarsOnly(text, opts) {
|
|
@@ -1026,6 +1042,8 @@ function expandVarsOnly(text, opts) {
|
|
|
1026
1042
|
out = out.replace(/%(\w+)%/gu, (m, name) => {
|
|
1027
1043
|
const value = opts.vars[name.toLowerCase()];
|
|
1028
1044
|
if (value === void 0) return m;
|
|
1045
|
+
if (opts.budget.left <= 0) return m;
|
|
1046
|
+
opts.budget.left -= value.length;
|
|
1029
1047
|
changed = true;
|
|
1030
1048
|
return value;
|
|
1031
1049
|
});
|