@spintax/core 0.1.6 → 0.3.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.d.cts CHANGED
@@ -15,13 +15,76 @@
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;
23
29
  }
24
30
 
31
+ /**
32
+ * Tree-walk renderer with the plugin's staged semantics layered on (parity with
33
+ * Renderer::process_template). Not a naive walk — the deterministic stages are
34
+ * reproduced in order:
35
+ * - #set is a global pre-pass (parser) → setDefs; those enum values are
36
+ * COLLAPSED ONCE here (buildVars), skipping values with `{?`/`{plural ` (they
37
+ * reference vars and defer to the body) — plugin Stage 4b.
38
+ * - variables resolve against the merged map (runtime context > #set); a value
39
+ * that itself contains constructs is re-parsed and rendered (recursive, depth
40
+ * capped) — this covers conditionals/plurals introduced by a variable value
41
+ * (Stages 6b–6c), so a separate pre/post conditional pass isn't needed.
42
+ * - conditionals test truthiness against the raw var map; plurals resolve the
43
+ * count (vars already expanded) then pick the bucket, lenient fullwidth
44
+ * fallback (Stage 6d, after vars).
45
+ * - #include (post-tree string pass) and post-process are later PRs.
46
+ *
47
+ * RNG note: cross-engine RNG-sequence parity is a non-goal (§3.2). Enumerations
48
+ * render outer-first and LAZILY (only the picked branch's nested RNG is used) vs
49
+ * the plugin's eager innermost-out; corpus-safe because deterministic nested-enum
50
+ * cases use order-independent sequences. Permutation matches the plugin's exact
51
+ * pick→Fisher-Yates, so its rng-strategy cases are exact.
52
+ */
53
+
54
+ /** Document-level render context (threads through nested #include resolution). */
55
+ /**
56
+ * A `{plural …}` block the renderer could not resolve, reported through
57
+ * {@link RenderCtx.onPluralError}. Observation only — the render still degrades
58
+ * exactly as it always has (§0.1 lenient contract); the host decides whether a
59
+ * report is fatal. Mirrors the plugin's `on_error` callable.
60
+ */
61
+ interface PluralIssue {
62
+ /**
63
+ * `plural.nested-brackets` and `plural.arity` are the same codes `validate()`
64
+ * emits. `plural.count` has no validate counterpart on purpose — an unresolved
65
+ * count is a runtime-value fact, invisible to static analysis.
66
+ */
67
+ readonly code: 'plural.nested-brackets' | 'plural.arity' | 'plural.count';
68
+ readonly message: string;
69
+ /** The construct as the renderer saw it, AFTER variable expansion. */
70
+ readonly construct: string;
71
+ /** Normalized base language the arity was judged against. */
72
+ readonly locale: string;
73
+ /** Arity verdicts only. */
74
+ readonly expected?: number;
75
+ readonly got?: number;
76
+ }
77
+
78
+ /**
79
+ * The full render pipeline with an INJECTED rng — the seam shared by the public
80
+ * render() (which seeds the rng from RenderOptions.seed) and the corpus harness
81
+ * (which injects a fixture's `rng` strategy). Keeping the rng a parameter is what
82
+ * lets the deterministic parity fixtures assert exact output.
83
+ *
84
+ * Order: strip stray sentinels → collapse-once #set + tree-walk (+ #include) →
85
+ * cosmetic post-process (if on) → mandatory neutralize safety-restore (always).
86
+ */
87
+
25
88
  /** Default for {@link PipelineOptions.maxDepth} (#include + parse-nesting guard). */
26
89
  declare const DEFAULT_MAX_DEPTH = 20;
27
90
 
@@ -69,6 +132,17 @@ interface RenderOptions {
69
132
  postProcess?: boolean;
70
133
  /** #include + parse-nesting guard; defaults to {@link DEFAULT_MAX_DEPTH}. */
71
134
  maxDepth?: number;
135
+ /**
136
+ * Observer for `{plural …}` blocks the renderer could not resolve — the port of
137
+ * the plugin's `on_error` callable. Observation ONLY: output degrades exactly as
138
+ * it does without it (§0.1 lenient contract — `render()` never throws on template
139
+ * content), and the host decides whether a report is fatal.
140
+ *
141
+ * Worth wiring when a render is PERSISTED rather than shown: an unresolved count
142
+ * erases its block, which is indistinguishable in the output from copy that was
143
+ * meant to be empty.
144
+ */
145
+ onPluralError?: (issue: PluralIssue) => void;
72
146
  }
73
147
  interface ValidateOptions {
74
148
  /** Plural-bucket locale for arity verdicts (§3.1); omit ⇒ default 2-form. */
@@ -98,7 +172,10 @@ interface Diagnostic {
98
172
  }
99
173
  interface ExtractResult {
100
174
  refs: string[];
175
+ /** Names defined by `#set` — macros, re-picked at every reference. */
101
176
  sets: string[];
177
+ /** Names defined by `#def` — rolled once per render and held. */
178
+ defs: string[];
102
179
  includes: string[];
103
180
  }
104
181
  interface Analysis extends ExtractResult {
@@ -107,6 +184,31 @@ interface Analysis extends ExtractResult {
107
184
  constructs: Record<string, number>;
108
185
  }
109
186
 
187
+ /**
188
+ * A locale's base language tag: `pt-BR`→`pt`, `uk_UA`→`uk`, `RU`→`ru`. Absent or
189
+ * empty gives `''`.
190
+ *
191
+ * Knows nothing of ISO-639-3, so a 3-letter tag is returned whole (`srp`→`srp`)
192
+ * and is NOT recognized as its 2-letter language.
193
+ */
194
+ declare function normalizeBaseLang(locale?: string | null): string;
195
+ /**
196
+ * How many `{plural …}` forms this locale takes — 3 for the Slavic one/few/other
197
+ * family (ru/uk/be + sr/hr/bs), 2 for everything else.
198
+ *
199
+ * Takes a RAW locale and normalizes internally, unlike the internal helper of the
200
+ * same name: a public function that silently answered 2 for `sr-Latn` would be a
201
+ * trap. Accepts an absent locale, and unknown tags fall back to 2 rather than
202
+ * throwing (§3.1), so the same optional value `RenderOptions.locale` carries can be
203
+ * passed straight through.
204
+ *
205
+ * NOTE the asymmetry with {@link validate}: an absent or empty locale makes the
206
+ * validator skip the arity check ENTIRELY (structural verdicts only), whereas this
207
+ * answers the 2-form default that {@link render} would then apply. Ask this what
208
+ * render will do; do not read it as "what validate will enforce".
209
+ */
210
+ declare function pluralArity(locale?: string | null): number;
211
+
110
212
  declare function parse(src: string): Ast;
111
213
  declare function render(input: string | Ast, opts?: RenderOptions): string;
112
214
  declare function validate(input: string | Ast, opts?: ValidateOptions): Diagnostic[];
@@ -114,4 +216,4 @@ declare function extract(input: string | Ast): ExtractResult;
114
216
  declare function analyze(input: string | Ast, opts?: ValidateOptions): Analysis;
115
217
  declare function neutralize(value: string): string;
116
218
 
117
- export { type Analysis, type Ast, AstVersionError, DEFAULT_MAX_DEPTH, type Diagnostic, type ExtractResult, IncludeResolverError, NotImplementedError, type RenderOptions, SpintaxError, type ValidateOptions, analyze, extract, neutralize, parse, render, validate };
219
+ export { type Analysis, type Ast, AstVersionError, DEFAULT_MAX_DEPTH, type Diagnostic, type ExtractResult, IncludeResolverError, NotImplementedError, type PluralIssue, type RenderOptions, SpintaxError, type ValidateOptions, analyze, extract, neutralize, normalizeBaseLang, parse, pluralArity, render, validate };
package/dist/index.d.ts CHANGED
@@ -15,13 +15,76 @@
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;
23
29
  }
24
30
 
31
+ /**
32
+ * Tree-walk renderer with the plugin's staged semantics layered on (parity with
33
+ * Renderer::process_template). Not a naive walk — the deterministic stages are
34
+ * reproduced in order:
35
+ * - #set is a global pre-pass (parser) → setDefs; those enum values are
36
+ * COLLAPSED ONCE here (buildVars), skipping values with `{?`/`{plural ` (they
37
+ * reference vars and defer to the body) — plugin Stage 4b.
38
+ * - variables resolve against the merged map (runtime context > #set); a value
39
+ * that itself contains constructs is re-parsed and rendered (recursive, depth
40
+ * capped) — this covers conditionals/plurals introduced by a variable value
41
+ * (Stages 6b–6c), so a separate pre/post conditional pass isn't needed.
42
+ * - conditionals test truthiness against the raw var map; plurals resolve the
43
+ * count (vars already expanded) then pick the bucket, lenient fullwidth
44
+ * fallback (Stage 6d, after vars).
45
+ * - #include (post-tree string pass) and post-process are later PRs.
46
+ *
47
+ * RNG note: cross-engine RNG-sequence parity is a non-goal (§3.2). Enumerations
48
+ * render outer-first and LAZILY (only the picked branch's nested RNG is used) vs
49
+ * the plugin's eager innermost-out; corpus-safe because deterministic nested-enum
50
+ * cases use order-independent sequences. Permutation matches the plugin's exact
51
+ * pick→Fisher-Yates, so its rng-strategy cases are exact.
52
+ */
53
+
54
+ /** Document-level render context (threads through nested #include resolution). */
55
+ /**
56
+ * A `{plural …}` block the renderer could not resolve, reported through
57
+ * {@link RenderCtx.onPluralError}. Observation only — the render still degrades
58
+ * exactly as it always has (§0.1 lenient contract); the host decides whether a
59
+ * report is fatal. Mirrors the plugin's `on_error` callable.
60
+ */
61
+ interface PluralIssue {
62
+ /**
63
+ * `plural.nested-brackets` and `plural.arity` are the same codes `validate()`
64
+ * emits. `plural.count` has no validate counterpart on purpose — an unresolved
65
+ * count is a runtime-value fact, invisible to static analysis.
66
+ */
67
+ readonly code: 'plural.nested-brackets' | 'plural.arity' | 'plural.count';
68
+ readonly message: string;
69
+ /** The construct as the renderer saw it, AFTER variable expansion. */
70
+ readonly construct: string;
71
+ /** Normalized base language the arity was judged against. */
72
+ readonly locale: string;
73
+ /** Arity verdicts only. */
74
+ readonly expected?: number;
75
+ readonly got?: number;
76
+ }
77
+
78
+ /**
79
+ * The full render pipeline with an INJECTED rng — the seam shared by the public
80
+ * render() (which seeds the rng from RenderOptions.seed) and the corpus harness
81
+ * (which injects a fixture's `rng` strategy). Keeping the rng a parameter is what
82
+ * lets the deterministic parity fixtures assert exact output.
83
+ *
84
+ * Order: strip stray sentinels → collapse-once #set + tree-walk (+ #include) →
85
+ * cosmetic post-process (if on) → mandatory neutralize safety-restore (always).
86
+ */
87
+
25
88
  /** Default for {@link PipelineOptions.maxDepth} (#include + parse-nesting guard). */
26
89
  declare const DEFAULT_MAX_DEPTH = 20;
27
90
 
@@ -69,6 +132,17 @@ interface RenderOptions {
69
132
  postProcess?: boolean;
70
133
  /** #include + parse-nesting guard; defaults to {@link DEFAULT_MAX_DEPTH}. */
71
134
  maxDepth?: number;
135
+ /**
136
+ * Observer for `{plural …}` blocks the renderer could not resolve — the port of
137
+ * the plugin's `on_error` callable. Observation ONLY: output degrades exactly as
138
+ * it does without it (§0.1 lenient contract — `render()` never throws on template
139
+ * content), and the host decides whether a report is fatal.
140
+ *
141
+ * Worth wiring when a render is PERSISTED rather than shown: an unresolved count
142
+ * erases its block, which is indistinguishable in the output from copy that was
143
+ * meant to be empty.
144
+ */
145
+ onPluralError?: (issue: PluralIssue) => void;
72
146
  }
73
147
  interface ValidateOptions {
74
148
  /** Plural-bucket locale for arity verdicts (§3.1); omit ⇒ default 2-form. */
@@ -98,7 +172,10 @@ interface Diagnostic {
98
172
  }
99
173
  interface ExtractResult {
100
174
  refs: string[];
175
+ /** Names defined by `#set` — macros, re-picked at every reference. */
101
176
  sets: string[];
177
+ /** Names defined by `#def` — rolled once per render and held. */
178
+ defs: string[];
102
179
  includes: string[];
103
180
  }
104
181
  interface Analysis extends ExtractResult {
@@ -107,6 +184,31 @@ interface Analysis extends ExtractResult {
107
184
  constructs: Record<string, number>;
108
185
  }
109
186
 
187
+ /**
188
+ * A locale's base language tag: `pt-BR`→`pt`, `uk_UA`→`uk`, `RU`→`ru`. Absent or
189
+ * empty gives `''`.
190
+ *
191
+ * Knows nothing of ISO-639-3, so a 3-letter tag is returned whole (`srp`→`srp`)
192
+ * and is NOT recognized as its 2-letter language.
193
+ */
194
+ declare function normalizeBaseLang(locale?: string | null): string;
195
+ /**
196
+ * How many `{plural …}` forms this locale takes — 3 for the Slavic one/few/other
197
+ * family (ru/uk/be + sr/hr/bs), 2 for everything else.
198
+ *
199
+ * Takes a RAW locale and normalizes internally, unlike the internal helper of the
200
+ * same name: a public function that silently answered 2 for `sr-Latn` would be a
201
+ * trap. Accepts an absent locale, and unknown tags fall back to 2 rather than
202
+ * throwing (§3.1), so the same optional value `RenderOptions.locale` carries can be
203
+ * passed straight through.
204
+ *
205
+ * NOTE the asymmetry with {@link validate}: an absent or empty locale makes the
206
+ * validator skip the arity check ENTIRELY (structural verdicts only), whereas this
207
+ * answers the 2-form default that {@link render} would then apply. Ask this what
208
+ * render will do; do not read it as "what validate will enforce".
209
+ */
210
+ declare function pluralArity(locale?: string | null): number;
211
+
110
212
  declare function parse(src: string): Ast;
111
213
  declare function render(input: string | Ast, opts?: RenderOptions): string;
112
214
  declare function validate(input: string | Ast, opts?: ValidateOptions): Diagnostic[];
@@ -114,4 +216,4 @@ declare function extract(input: string | Ast): ExtractResult;
114
216
  declare function analyze(input: string | Ast, opts?: ValidateOptions): Analysis;
115
217
  declare function neutralize(value: string): string;
116
218
 
117
- export { type Analysis, type Ast, AstVersionError, DEFAULT_MAX_DEPTH, type Diagnostic, type ExtractResult, IncludeResolverError, NotImplementedError, type RenderOptions, SpintaxError, type ValidateOptions, analyze, extract, neutralize, parse, render, validate };
219
+ export { type Analysis, type Ast, AstVersionError, DEFAULT_MAX_DEPTH, type Diagnostic, type ExtractResult, IncludeResolverError, NotImplementedError, type PluralIssue, type RenderOptions, SpintaxError, type ValidateOptions, analyze, extract, neutralize, normalizeBaseLang, parse, pluralArity, render, validate };