@spintax/core 0.5.3 → 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/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
- detectCycle(name, defs, refsOf, reachesCycle, defPos.get(name) ?? somewhere, out);
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) reaches.add(top.name);
799
+ if (c === GREY) mark(top.name, ref);
789
800
  else if (c === BLACK) {
790
- if (reaches.has(ref)) reaches.add(top.name);
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)) reaches.add(stack[stack.length - 1].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
- function detectCycle(root, defs, refsOf, reachesCycle, rootPos, out) {
805
- if (!reachesCycle.has(root)) return;
806
- const path = [root];
807
- const onPath = /* @__PURE__ */ new Set([root]);
808
- const stack = [{ name: root, refs: refsOf.get(root) ?? [], i: 0 }];
809
- const leave = (frame) => {
810
- stack.pop();
811
- onPath.delete(frame.name);
812
- path.pop();
813
- };
814
- while (stack.length > 0) {
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
- const ref = top.refs[top.i];
821
- top.i += 1;
822
- if (ref === top.name) continue;
823
- if (onPath.has(ref)) {
824
- out.push(err("variable.circular-reference", `Circular variable reference: ${[...path, ref].join(" \u2192 ")}.`, rootPos));
825
- leave(top);
826
- continue;
827
- }
828
- if (defs.has(ref) && reachesCycle.has(ref)) {
829
- stack.push({ name: ref, refs: refsOf.get(ref) ?? [], i: 0 });
830
- onPath.add(ref);
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);