@spintax/core 0.5.2 → 0.6.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/dist/index.cjs +49 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +49 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -748,9 +748,14 @@ function checkVariableReferences(text, idx, known, out) {
|
|
|
748
748
|
for (const m of value.matchAll(/%(\w+)%/gu)) refs.push((m[1] ?? "").toLowerCase());
|
|
749
749
|
refsOf.set(name, refs);
|
|
750
750
|
}
|
|
751
|
-
const reachesCycle = namesThatReachACycle(defs, refsOf);
|
|
751
|
+
const { reaches: reachesCycle, via } = namesThatReachACycle(defs, refsOf);
|
|
752
752
|
for (const name of defs.keys()) {
|
|
753
|
-
|
|
753
|
+
if (!reachesCycle.has(name)) continue;
|
|
754
|
+
out.push(err(
|
|
755
|
+
"variable.circular-reference",
|
|
756
|
+
`Circular variable reference: ${cyclePath(name, via)}.`,
|
|
757
|
+
{ ...defPos.get(name) ?? somewhere, data: { name } }
|
|
758
|
+
));
|
|
754
759
|
}
|
|
755
760
|
const body = text.replace(/^[ \t]*#(?:set|def)[ \t]+%\w+%[ \t]*=[ \t]*.*?$/gmu, (m) => m.replace(/[^\n]/g, " "));
|
|
756
761
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -774,6 +779,12 @@ function namesThatReachACycle(defs, refsOf) {
|
|
|
774
779
|
const BLACK = 2;
|
|
775
780
|
const color = /* @__PURE__ */ new Map();
|
|
776
781
|
const reaches = /* @__PURE__ */ new Set();
|
|
782
|
+
const via = /* @__PURE__ */ new Map();
|
|
783
|
+
const mark = (name, witness) => {
|
|
784
|
+
if (reaches.has(name)) return;
|
|
785
|
+
reaches.add(name);
|
|
786
|
+
via.set(name, witness);
|
|
787
|
+
};
|
|
777
788
|
for (const root of defs.keys()) {
|
|
778
789
|
if (color.has(root)) continue;
|
|
779
790
|
const stack = [{ name: root, refs: refsOf.get(root) ?? [], i: 0 }];
|
|
@@ -785,9 +796,9 @@ function namesThatReachACycle(defs, refsOf) {
|
|
|
785
796
|
top.i += 1;
|
|
786
797
|
if (ref === top.name || !defs.has(ref)) continue;
|
|
787
798
|
const c = color.get(ref);
|
|
788
|
-
if (c === GREY)
|
|
799
|
+
if (c === GREY) mark(top.name, ref);
|
|
789
800
|
else if (c === BLACK) {
|
|
790
|
-
if (reaches.has(ref))
|
|
801
|
+
if (reaches.has(ref)) mark(top.name, ref);
|
|
791
802
|
} else {
|
|
792
803
|
stack.push({ name: ref, refs: refsOf.get(ref) ?? [], i: 0 });
|
|
793
804
|
color.set(ref, GREY);
|
|
@@ -795,42 +806,41 @@ function namesThatReachACycle(defs, refsOf) {
|
|
|
795
806
|
} else {
|
|
796
807
|
stack.pop();
|
|
797
808
|
color.set(top.name, BLACK);
|
|
798
|
-
if (stack.length > 0 && reaches.has(top.name))
|
|
809
|
+
if (stack.length > 0 && reaches.has(top.name)) mark(stack[stack.length - 1].name, top.name);
|
|
799
810
|
}
|
|
800
811
|
}
|
|
801
812
|
}
|
|
802
|
-
return reaches;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const top = stack[stack.length - 1];
|
|
816
|
-
if (top.i >= top.refs.length) {
|
|
817
|
-
leave(top);
|
|
818
|
-
continue;
|
|
813
|
+
return { reaches, via };
|
|
814
|
+
}
|
|
815
|
+
var CYCLE_PATH_LIMIT = 8;
|
|
816
|
+
function cyclePath(name, via) {
|
|
817
|
+
const seen = /* @__PURE__ */ new Set([name]);
|
|
818
|
+
const shown = [name];
|
|
819
|
+
let current = name;
|
|
820
|
+
for (; ; ) {
|
|
821
|
+
const next = via.get(current);
|
|
822
|
+
if (next === void 0) break;
|
|
823
|
+
if (seen.has(next)) {
|
|
824
|
+
shown.push(next);
|
|
825
|
+
return shown.join(" \u2192 ");
|
|
819
826
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
path.push(ref);
|
|
827
|
+
if (shown.length >= CYCLE_PATH_LIMIT) {
|
|
828
|
+
let more = 0;
|
|
829
|
+
let walk2 = next;
|
|
830
|
+
while (!seen.has(walk2)) {
|
|
831
|
+
seen.add(walk2);
|
|
832
|
+
more += 1;
|
|
833
|
+
const step = via.get(walk2);
|
|
834
|
+
if (step === void 0) break;
|
|
835
|
+
walk2 = step;
|
|
836
|
+
}
|
|
837
|
+
return `${shown.join(" \u2192 ")} \u2192 \u2026 (${more} more)`;
|
|
832
838
|
}
|
|
839
|
+
seen.add(next);
|
|
840
|
+
shown.push(next);
|
|
841
|
+
current = next;
|
|
833
842
|
}
|
|
843
|
+
return shown.join(" \u2192 ");
|
|
834
844
|
}
|
|
835
845
|
function checkIncludeTargets(text, idx, known, out) {
|
|
836
846
|
const set = new Set(known);
|
|
@@ -918,7 +928,7 @@ function renderAst(ast, ctx) {
|
|
|
918
928
|
locale: ctx.locale,
|
|
919
929
|
depth: 0,
|
|
920
930
|
onPluralError: ctx.onPluralError,
|
|
921
|
-
budget:
|
|
931
|
+
budget: ctx.budget
|
|
922
932
|
};
|
|
923
933
|
const vars = Object.keys(ast.defDefs).length > 0 ? { ...base, ...rollDefinitions(ast.defDefs, base, ctx.runtimeContext, walkOpts) } : base;
|
|
924
934
|
const text = renderNodes(ast.nodes, { ...walkOpts, vars });
|
|
@@ -1358,7 +1368,10 @@ function renderWith(input, rng, opts = {}) {
|
|
|
1358
1368
|
resolver: opts.includeResolver,
|
|
1359
1369
|
maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
1360
1370
|
includeStack: [],
|
|
1361
|
-
onPluralError: opts.onPluralError
|
|
1371
|
+
onPluralError: opts.onPluralError,
|
|
1372
|
+
// One allowance for the whole call. Created here rather than in renderAst because
|
|
1373
|
+
// includes re-enter renderAst, and a budget made there is a budget per subtree.
|
|
1374
|
+
budget: { left: MAX_EXPANSION_CHARS }
|
|
1362
1375
|
};
|
|
1363
1376
|
const ast = resolveAst(input);
|
|
1364
1377
|
let out = renderAst(ast, ctx);
|