@spintax/core 0.5.0 → 0.5.1

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
@@ -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
- for (const m of source.matchAll(/%(\w+)%/gu)) {
643
- const name = (m[1] ?? "").toLowerCase();
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
- const deeper = walkMacros(macro);
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
- text = text.replace(/%(\w+)%/gu, (whole, raw) => {
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 = raw.toLowerCase();
665
- if (hostNames.has(name)) {
666
- bailed = true;
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
- return whole;
681
+ break;
677
682
  }
678
- return value;
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
  }