@spintax/core 0.2.0 → 0.3.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.d.cts CHANGED
@@ -15,8 +15,14 @@
15
15
  * the AST here.
16
16
  * Permutation's `<config>` + per-element separators are fully parsed (PR-11b).
17
17
  */
18
- /** Bumped only on a breaking change to the node shape (independent of syntax v1). */
19
- declare const AST_VERSION = 1;
18
+ /**
19
+ * Bumped only on a breaking change to the node shape (independent of syntax v1).
20
+ *
21
+ * 2 — `ParsedAst` gained `defDefs`. An `Ast` cached by an older version carries no `#def` map, so
22
+ * rendering it would silently drop every definition; the version guard turns that into an
23
+ * `AstVersionError` instead.
24
+ */
25
+ declare const AST_VERSION = 2;
20
26
  /** Opaque public handle (re-exported as `Ast` from the package index). */
21
27
  interface Ast {
22
28
  readonly astVersion: typeof AST_VERSION;
@@ -75,7 +81,7 @@ interface PluralIssue {
75
81
  * (which injects a fixture's `rng` strategy). Keeping the rng a parameter is what
76
82
  * lets the deterministic parity fixtures assert exact output.
77
83
  *
78
- * Order: strip stray sentinels → collapse-once #set + tree-walk (+ #include) →
84
+ * Order: strip stray sentinels → build vars, roll #def, tree-walk (+ #include) →
79
85
  * cosmetic post-process (if on) → mandatory neutralize safety-restore (always).
80
86
  */
81
87
 
@@ -166,7 +172,10 @@ interface Diagnostic {
166
172
  }
167
173
  interface ExtractResult {
168
174
  refs: string[];
175
+ /** Names defined by `#set` — macros, re-picked at every reference. */
169
176
  sets: string[];
177
+ /** Names defined by `#def` — rolled once per render and held. */
178
+ defs: string[];
170
179
  includes: string[];
171
180
  }
172
181
  interface Analysis extends ExtractResult {
package/dist/index.d.ts CHANGED
@@ -15,8 +15,14 @@
15
15
  * the AST here.
16
16
  * Permutation's `<config>` + per-element separators are fully parsed (PR-11b).
17
17
  */
18
- /** Bumped only on a breaking change to the node shape (independent of syntax v1). */
19
- declare const AST_VERSION = 1;
18
+ /**
19
+ * Bumped only on a breaking change to the node shape (independent of syntax v1).
20
+ *
21
+ * 2 — `ParsedAst` gained `defDefs`. An `Ast` cached by an older version carries no `#def` map, so
22
+ * rendering it would silently drop every definition; the version guard turns that into an
23
+ * `AstVersionError` instead.
24
+ */
25
+ declare const AST_VERSION = 2;
20
26
  /** Opaque public handle (re-exported as `Ast` from the package index). */
21
27
  interface Ast {
22
28
  readonly astVersion: typeof AST_VERSION;
@@ -75,7 +81,7 @@ interface PluralIssue {
75
81
  * (which injects a fixture's `rng` strategy). Keeping the rng a parameter is what
76
82
  * lets the deterministic parity fixtures assert exact output.
77
83
  *
78
- * Order: strip stray sentinels → collapse-once #set + tree-walk (+ #include) →
84
+ * Order: strip stray sentinels → build vars, roll #def, tree-walk (+ #include) →
79
85
  * cosmetic post-process (if on) → mandatory neutralize safety-restore (always).
80
86
  */
81
87
 
@@ -166,7 +172,10 @@ interface Diagnostic {
166
172
  }
167
173
  interface ExtractResult {
168
174
  refs: string[];
175
+ /** Names defined by `#set` — macros, re-picked at every reference. */
169
176
  sets: string[];
177
+ /** Names defined by `#def` — rolled once per render and held. */
178
+ defs: string[];
170
179
  includes: string[];
171
180
  }
172
181
  interface Analysis extends ExtractResult {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/internal/ast.ts
2
- var AST_VERSION = 1;
2
+ var AST_VERSION = 2;
3
3
  function walk(nodes, visit) {
4
4
  for (const n of nodes) {
5
5
  visit(n);
@@ -21,23 +21,66 @@ function isParsedAst(value) {
21
21
  return typeof value === "object" && value !== null && value.astVersion === AST_VERSION && typeof value.source === "string" && Array.isArray(value.nodes);
22
22
  }
23
23
 
24
+ // src/internal/neutralize.ts
25
+ var STRUCTURAL = ["{", "}", "[", "]", "%", "#"];
26
+ var SENTINEL_BASE = 57344;
27
+ var SHIELD = /* @__PURE__ */ new Map();
28
+ var RESTORE = /* @__PURE__ */ new Map();
29
+ STRUCTURAL.forEach((ch, i) => {
30
+ const sentinel = String.fromCharCode(SENTINEL_BASE + i);
31
+ SHIELD.set(ch, sentinel);
32
+ RESTORE.set(sentinel, ch);
33
+ });
34
+ var SHIELD_RE = /[{}[\]%#]/gu;
35
+ var RESTORE_RE = new RegExp(
36
+ `[${String.fromCharCode(SENTINEL_BASE)}-${String.fromCharCode(SENTINEL_BASE + STRUCTURAL.length - 1)}]`,
37
+ "gu"
38
+ );
39
+ function neutralize(value) {
40
+ return value.replace(SHIELD_RE, (ch) => SHIELD.get(ch) ?? ch);
41
+ }
42
+ function safetyRestore(text) {
43
+ return text.replace(RESTORE_RE, (ch) => RESTORE.get(ch) ?? ch);
44
+ }
45
+ function stripSentinels(text) {
46
+ return text.replace(RESTORE_RE, "");
47
+ }
48
+
24
49
  // src/internal/parser.ts
25
50
  var VARIABLE_RE = /^%(\w+)%/;
26
- var SET_DIRECTIVE_RE = /^[ \t]*#set[ \t]+%(\w+)%[ \t]*=[ \t]*(.*?)[ \t]*\r?$/gmu;
51
+ var DIRECTIVE_RE = /^[ \t]*#(set|def)[ \t]+%(\w+)%[ \t]*=[ \t]*(.*?)[ \t]*\r?$/gmu;
27
52
  var CONDITIONAL_NAME_RE = /^[A-Za-z_]\w*/;
28
53
  var PLURAL_PREFIX = "plural ";
29
54
  function parseTemplate(src) {
30
- const { body, setDefs } = extractSetDirectives(stripComments(src));
31
- return { astVersion: AST_VERSION, source: src, setDefs, nodes: parseSequence(body) };
55
+ const { body, setDefs, defDefs } = extractDirectives(stripComments(stripSentinels(src)));
56
+ return { astVersion: AST_VERSION, source: src, setDefs, defDefs, nodes: parseSequence(body) };
32
57
  }
33
- function extractSetDirectives(text) {
58
+ function extractDirectives(text) {
34
59
  const setDefs = {};
35
- SET_DIRECTIVE_RE.lastIndex = 0;
36
- const stripped = text.replace(SET_DIRECTIVE_RE, (_full, name, value) => {
37
- setDefs[name.toLowerCase()] = value;
38
- return "";
39
- });
40
- return { body: stripped.replace(/\n{3,}/gu, "\n\n"), setDefs };
60
+ const defDefs = {};
61
+ const occurrences = [];
62
+ DIRECTIVE_RE.lastIndex = 0;
63
+ const stripped = text.replace(
64
+ DIRECTIVE_RE,
65
+ (full, kind, rawName, value, offset) => {
66
+ const name = rawName.toLowerCase();
67
+ occurrences.push({
68
+ kind,
69
+ name,
70
+ value,
71
+ line: countLines(text, offset)
72
+ });
73
+ if (kind === "def") defDefs[name] = value;
74
+ else setDefs[name] = value;
75
+ return "";
76
+ }
77
+ );
78
+ return { body: stripped.replace(/\n{3,}/gu, "\n\n"), setDefs, defDefs, occurrences };
79
+ }
80
+ function countLines(text, offset) {
81
+ let line = 1;
82
+ for (let i = 0; i < offset; i += 1) if (text[i] === "\n") line += 1;
83
+ return line;
41
84
  }
42
85
  function stripComments(text) {
43
86
  return text.replace(/\/#[\s\S]*?#\//g, "");
@@ -376,7 +419,7 @@ function validateTemplate(src, opts = {}) {
376
419
  const diagnostics = [];
377
420
  const text = stripComments(src);
378
421
  checkBrackets(text, diagnostics);
379
- checkSetDirectives(text, diagnostics);
422
+ checkDirectives(text, diagnostics);
380
423
  checkPermutationConfigs(text, diagnostics);
381
424
  checkPlurals(text, opts.locale, diagnostics);
382
425
  checkVariableReferences(text, opts.knownVariables, diagnostics);
@@ -435,17 +478,43 @@ function checkBrackets(text, out) {
435
478
  out.push(err("bracket.unclosed", `Unclosed '${unclosed.char}'.`, { line: unclosed.line, column: unclosed.column, endLine: unclosed.line, endColumn: unclosed.column + 1, data: { bracket: unclosed.char } }));
436
479
  }
437
480
  }
438
- function checkSetDirectives(text, out) {
481
+ function checkDirectives(text, out) {
439
482
  const lines = text.split("\n");
440
483
  lines.forEach((lineText, idx) => {
441
484
  const trimmed = lineText.replace(/^[ \t]+/, "");
442
- if (!trimmed.startsWith("#set ") && !trimmed.startsWith("#set ")) return;
443
- if (!/^#set\s+%(\w+)%\s*=\s*(.+)$/u.test(trimmed)) {
485
+ const kind = ["#set", "#def"].find(
486
+ (candidate) => trimmed.startsWith(`${candidate} `) || trimmed.startsWith(`${candidate} `)
487
+ );
488
+ if (!kind) return;
489
+ DIRECTIVE_RE.lastIndex = 0;
490
+ if (!DIRECTIVE_RE.test(trimmed)) {
444
491
  const column = lineText.length - trimmed.length + 1;
445
492
  const line = idx + 1;
446
- out.push(err("set.malformed", "Malformed #set. Expected: #set %name% = value", { line, column, endLine: line, endColumn: lineText.length + 1 }));
493
+ const code = kind === "#def" ? "def.malformed" : "set.malformed";
494
+ out.push(err(code, `Malformed ${kind}. Expected: ${kind} %name% = value`, { line, column, endLine: line, endColumn: lineText.length + 1 }));
447
495
  }
448
496
  });
497
+ const { occurrences } = extractDirectives(text);
498
+ const seen = /* @__PURE__ */ new Map();
499
+ for (const occurrence of occurrences) {
500
+ const first = seen.get(occurrence.name);
501
+ if (first !== void 0) {
502
+ out.push(err(
503
+ "definition.duplicate-name",
504
+ `Variable '${occurrence.name}' is defined more than once (first on line ${first}). A name belongs to one directive, once.`,
505
+ { line: occurrence.line, column: 1 }
506
+ ));
507
+ } else {
508
+ seen.set(occurrence.name, occurrence.line);
509
+ }
510
+ if (occurrence.kind === "def" && /#include\b/u.test(occurrence.value)) {
511
+ out.push(err(
512
+ "def.include-in-value",
513
+ `#include cannot appear in a #def value ('${occurrence.name}'): includes resolve after the value is frozen. Use #set, or put the #include in the body.`,
514
+ { line: occurrence.line, column: 1 }
515
+ ));
516
+ }
517
+ }
449
518
  }
450
519
  function checkPermutationConfigs(text, out) {
451
520
  for (const m of text.matchAll(/\[<([^>]*?)>/gu)) {
@@ -483,10 +552,20 @@ function checkPermutationConfigs(text, out) {
483
552
  function checkPlurals(text, locale, out) {
484
553
  const base = locale && locale !== "" ? normalizeBaseLang(locale) : "";
485
554
  const arity = base !== "" ? pluralArity(base) : 0;
555
+ const tainted = macroTaintedNames(text);
486
556
  for (const block of findPluralBlocks(text)) {
487
557
  const at = span(text, block.start, block.end - block.start);
558
+ for (const m of block.countSlot.matchAll(/%(\w+)%/gu)) {
559
+ const name = (m[1] ?? "").toLowerCase();
560
+ if (!tainted.has(name)) continue;
561
+ out.push(err(
562
+ "plural.count-macro",
563
+ `{plural ...}: the count '${m[1]}' is a #set macro, so it is still unresolved spintax when the plural is decided and the block renders empty. Define it with #def instead.`,
564
+ at
565
+ ));
566
+ }
488
567
  if (/[{}[\]]/.test(block.formsRaw)) {
489
- out.push(err("plural.nested-brackets", "{plural ...}: forms must not contain nested spintax brackets.", at));
568
+ out.push(err("plural.nested-brackets", "{plural ...}: forms must not contain nested spintax brackets. Extract via #def first \u2014 a #set is substituted verbatim and would put the brackets straight back.", at));
490
569
  continue;
491
570
  }
492
571
  if (arity > 0) {
@@ -501,11 +580,33 @@ function checkPlurals(text, locale, out) {
501
580
  }
502
581
  }
503
582
  }
583
+ var UNRESOLVED_AT_PLURAL_TIME = /\[|\{(?!\?)/u;
584
+ function macroTaintedNames(text) {
585
+ const macros = extractDirectives(text).setDefs;
586
+ const tainted = /* @__PURE__ */ new Set();
587
+ for (const [name, value] of Object.entries(macros)) {
588
+ if (UNRESOLVED_AT_PLURAL_TIME.test(value)) tainted.add(name);
589
+ }
590
+ let grew = true;
591
+ while (grew) {
592
+ grew = false;
593
+ for (const [name, value] of Object.entries(macros)) {
594
+ if (tainted.has(name)) continue;
595
+ for (const m of value.matchAll(/%(\w+)%/gu)) {
596
+ if (!tainted.has((m[1] ?? "").toLowerCase())) continue;
597
+ tainted.add(name);
598
+ grew = true;
599
+ break;
600
+ }
601
+ }
602
+ }
603
+ return tainted;
604
+ }
504
605
  function checkVariableReferences(text, known, out) {
505
606
  const knownSet = new Set((known ?? []).map((n) => n.toLowerCase()));
506
607
  const defs = /* @__PURE__ */ new Map();
507
608
  const defPos = /* @__PURE__ */ new Map();
508
- for (const m of text.matchAll(/^[ \t]*#set[ \t]+%(\w+)%[ \t]*=[ \t]*(.*?)$/gmu)) {
609
+ for (const m of text.matchAll(/^[ \t]*#(?:set|def)[ \t]+%(\w+)%[ \t]*=[ \t]*(.*?)$/gmu)) {
509
610
  const name = (m[1] ?? "").toLowerCase();
510
611
  defs.set(name, m[2] ?? "");
511
612
  const nameOffset = (m.index ?? 0) + m[0].indexOf("%");
@@ -520,7 +621,7 @@ function checkVariableReferences(text, known, out) {
520
621
  for (const name of defs.keys()) {
521
622
  detectCycle(name, defs, [name], defPos.get(name) ?? somewhere, out);
522
623
  }
523
- const body = text.replace(/^[ \t]*#set[ \t]+%\w+%[ \t]*=[ \t]*.*?$/gmu, (m) => m.replace(/[^\n]/g, " "));
624
+ const body = text.replace(/^[ \t]*#(?:set|def)[ \t]+%\w+%[ \t]*=[ \t]*.*?$/gmu, (m) => m.replace(/[^\n]/g, " "));
524
625
  const seen = /* @__PURE__ */ new Set();
525
626
  const undefinedAt = (name, offset, length) => {
526
627
  const key = name.toLowerCase();
@@ -567,19 +668,21 @@ function checkIncludeTargets(text, known, out) {
567
668
 
568
669
  // src/internal/extract.ts
569
670
  var SET_DEF_RE = /^[ \t]*#set[ \t]+%(\w+)%[ \t]*=/gmu;
570
- var SET_LHS_RE = /^[ \t]*#set[ \t]+%\w+%[ \t]*=/gmu;
671
+ var DEF_DEF_RE = /^[ \t]*#def[ \t]+%(\w+)%[ \t]*=/gmu;
672
+ var DEFINITION_LHS_RE = /^[ \t]*#(?:set|def)[ \t]+%\w+%[ \t]*=/gmu;
571
673
  var INCLUDE_RE2 = /^[ \t]*#include[ \t\n\r\f\x0B]+"([^"]+)"[ \t\n\r\f\x0B]*$/gmu;
572
674
  var VARIABLE_RE2 = /%(\w+)%/gu;
573
675
  var CONDITIONAL_REF_RE = /\{\?!?([A-Za-z_]\w*)\?/gu;
574
676
  function extractFromSource(src) {
575
677
  const text = stripComments(src);
576
678
  const sets = collect(text, SET_DEF_RE, true);
679
+ const defs = collect(text, DEF_DEF_RE, true);
577
680
  const includes = collect(text, INCLUDE_RE2, false);
578
- const body = text.replace(SET_LHS_RE, "");
681
+ const body = text.replace(DEFINITION_LHS_RE, "");
579
682
  const refs = /* @__PURE__ */ new Set();
580
683
  addAll(refs, body, VARIABLE_RE2, true);
581
684
  addAll(refs, body, CONDITIONAL_REF_RE, true);
582
- return { refs: [...refs], sets, includes };
685
+ return { refs: [...refs], sets, defs, includes };
583
686
  }
584
687
  function collect(text, re, fold) {
585
688
  const seen = /* @__PURE__ */ new Set();
@@ -622,43 +725,19 @@ var NotImplementedError = class extends SpintaxError {
622
725
  }
623
726
  };
624
727
 
625
- // src/internal/neutralize.ts
626
- var STRUCTURAL = ["{", "}", "[", "]", "%", "#"];
627
- var SENTINEL_BASE = 57344;
628
- var SHIELD = /* @__PURE__ */ new Map();
629
- var RESTORE = /* @__PURE__ */ new Map();
630
- STRUCTURAL.forEach((ch, i) => {
631
- const sentinel = String.fromCharCode(SENTINEL_BASE + i);
632
- SHIELD.set(ch, sentinel);
633
- RESTORE.set(sentinel, ch);
634
- });
635
- var SHIELD_RE = /[{}[\]%#]/gu;
636
- var RESTORE_RE = new RegExp(
637
- `[${String.fromCharCode(SENTINEL_BASE)}-${String.fromCharCode(SENTINEL_BASE + STRUCTURAL.length - 1)}]`,
638
- "gu"
639
- );
640
- function neutralize(value) {
641
- return value.replace(SHIELD_RE, (ch) => SHIELD.get(ch) ?? ch);
642
- }
643
- function safetyRestore(text) {
644
- return text.replace(RESTORE_RE, (ch) => RESTORE.get(ch) ?? ch);
645
- }
646
- function stripSentinels(text) {
647
- return text.replace(RESTORE_RE, "");
648
- }
649
-
650
728
  // src/internal/render.ts
651
729
  var MAX_VARIABLE_DEPTH = 50;
652
730
  var INCLUDE_LINE_RE = /^[ \t]*#include[ \t\n\r\f\x0B]+"([^"]+)"[ \t\n\r\f\x0B]*$/gmu;
653
731
  function renderAst(ast, ctx) {
654
- const vars = buildVars(ast.setDefs, ctx.runtimeContext, ctx.rng);
655
- const text = renderNodes(ast.nodes, {
656
- vars,
732
+ const base = buildVars(ast.setDefs, ctx.runtimeContext);
733
+ const walkOpts = {
657
734
  rng: ctx.rng,
658
735
  locale: ctx.locale,
659
736
  depth: 0,
660
737
  onPluralError: ctx.onPluralError
661
- });
738
+ };
739
+ const vars = Object.keys(ast.defDefs).length > 0 ? { ...base, ...rollDefinitions(ast.defDefs, base, ctx.runtimeContext, walkOpts) } : base;
740
+ const text = renderNodes(ast.nodes, { ...walkOpts, vars });
662
741
  return ctx.resolver ? resolveIncludes(text, ctx) : text;
663
742
  }
664
743
  function resolveIncludes(text, ctx) {
@@ -672,7 +751,7 @@ function resolveIncludes(text, ctx) {
672
751
  throw new IncludeResolverError(`includeResolver threw for "${ref}"`, { cause });
673
752
  }
674
753
  if (included === null) return "";
675
- return renderAst(parseTemplate(stripSentinels(included)), {
754
+ return renderAst(parseTemplate(included), {
676
755
  ...ctx,
677
756
  runtimeContext: ctx.runtimeContext,
678
757
  // child inherits runtime, not parent #set
@@ -680,33 +759,65 @@ function resolveIncludes(text, ctx) {
680
759
  });
681
760
  });
682
761
  }
683
- function buildVars(setDefs, context, rng) {
762
+ function buildVars(setDefs, context) {
684
763
  const vars = {};
685
764
  for (const [name, value] of Object.entries(setDefs)) {
686
- vars[name] = collapseSetValue(value, rng);
765
+ vars[name] = value;
687
766
  }
688
767
  for (const [name, value] of Object.entries(context)) {
689
768
  vars[name.toLowerCase()] = value;
690
769
  }
691
770
  return vars;
692
771
  }
693
- function collapseSetValue(value, rng) {
694
- if (!value.includes("{")) return value;
695
- if (value.includes("{?") || value.includes("{plural ")) return value;
696
- return resolveEnumerationsString(value, rng);
697
- }
698
- function resolveEnumerationsString(text, rng) {
699
- let out = text;
700
- for (let guard = 0; guard < 1e3; guard += 1) {
701
- let changed = false;
702
- out = out.replace(/\{([^{}]*)\}/gu, (_m, inner) => {
703
- changed = true;
704
- const options = splitTopLevelPipes(inner);
705
- return options.length === 0 ? "" : options[randomInt(rng, 0, options.length - 1)] ?? "";
772
+ function rollDefinitions(defDefs, vars, context, opts) {
773
+ const outranked = new Set(Object.keys(context).map((key) => key.toLowerCase()));
774
+ const rolled = {};
775
+ const aliases = {};
776
+ for (const [name, value] of Object.entries(vars)) {
777
+ if (name in defDefs && !outranked.has(name)) continue;
778
+ aliases[name] = value;
779
+ }
780
+ for (const name of orderDefinitions(defDefs, aliases)) {
781
+ if (outranked.has(name)) continue;
782
+ const value = defDefs[name] ?? "";
783
+ rolled[name] = renderNodes(parseSequence(value), { ...opts, vars: { ...vars, ...rolled } });
784
+ }
785
+ return rolled;
786
+ }
787
+ function orderDefinitions(defDefs, aliases) {
788
+ const names = Object.keys(defDefs);
789
+ const blocked = /* @__PURE__ */ new Map();
790
+ for (const name of names) {
791
+ const reached = referencedNames(defDefs[name] ?? "", aliases);
792
+ blocked.set(name, new Set(names.filter((candidate) => reached.has(candidate))));
793
+ }
794
+ const ordered = [];
795
+ let pending = names;
796
+ while (pending.length > 0) {
797
+ const ready = pending.filter((name) => {
798
+ const deps = blocked.get(name);
799
+ return !deps || ![...deps].some((dep) => dep !== name && pending.includes(dep));
706
800
  });
707
- if (!changed) break;
801
+ if (ready.length === 0) return [...ordered, ...pending];
802
+ ordered.push(...ready);
803
+ pending = pending.filter((name) => !ready.includes(name));
708
804
  }
709
- return out;
805
+ return ordered;
806
+ }
807
+ function referencedNames(value, aliases) {
808
+ const seen = /* @__PURE__ */ new Set();
809
+ const queue = directReferences(value);
810
+ while (queue.length > 0) {
811
+ const name = queue.shift();
812
+ if (seen.has(name)) continue;
813
+ seen.add(name);
814
+ const alias = aliases[name];
815
+ if (alias !== void 0) queue.push(...directReferences(alias));
816
+ }
817
+ return seen;
818
+ }
819
+ function directReferences(text) {
820
+ return [...text.matchAll(/%(\w+)%/gu)].map((match) => (match[1] ?? "").toLowerCase());
710
821
  }
711
822
  function renderNodes(nodes, opts) {
712
823
  let out = "";
@@ -766,7 +877,7 @@ function renderPlural(node, opts) {
766
877
  if (/[{}[\]]/u.test(formsRaw)) {
767
878
  report({
768
879
  code: "plural.nested-brackets",
769
- message: "Plural form slot contains nested spintax brackets; extract via #set first.",
880
+ message: "Plural form slot contains nested spintax brackets; extract via #def first \u2014 a #set is substituted verbatim and would put the brackets straight back.",
770
881
  construct: rawConstruct(countRaw, formsRaw),
771
882
  locale: base
772
883
  });
@@ -865,26 +976,6 @@ function padSeparator(sep) {
865
976
  if (/^\p{L}+$/u.test(trimmed)) return ` ${trimmed} `;
866
977
  return sep;
867
978
  }
868
- function splitTopLevelPipes(inner) {
869
- const parts = [];
870
- let brace = 0;
871
- let bracket = 0;
872
- let cur = "";
873
- for (const ch of inner) {
874
- if (ch === "{") brace += 1;
875
- else if (ch === "}") brace -= 1;
876
- else if (ch === "[") bracket += 1;
877
- else if (ch === "]") bracket -= 1;
878
- if (ch === "|" && brace === 0 && bracket === 0) {
879
- parts.push(cur);
880
- cur = "";
881
- } else {
882
- cur += ch;
883
- }
884
- }
885
- parts.push(cur);
886
- return parts;
887
- }
888
979
  var PHP_LTRIM_RE2 = /^[ \t\n\r\0\x0B]+/u;
889
980
  var PHP_RTRIM_RE2 = /[ \t\n\r\0\x0B]+$/u;
890
981
  function phpTrim2(s) {
@@ -1020,7 +1111,7 @@ function renderWith(input, rng, opts = {}) {
1020
1111
  includeStack: [],
1021
1112
  onPluralError: opts.onPluralError
1022
1113
  };
1023
- const ast = resolveAst(typeof input === "string" ? stripSentinels(input) : input);
1114
+ const ast = resolveAst(input);
1024
1115
  let out = renderAst(ast, ctx);
1025
1116
  if (opts.postProcess !== false) out = postProcess(out);
1026
1117
  return safetyRestore(out);