@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/CHANGELOG.md +148 -0
- package/README.md +4 -3
- package/dist/index.cjs +209 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -3
- package/dist/index.d.ts +105 -3
- package/dist/index.js +208 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
19
|
-
|
|
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
|
-
/**
|
|
19
|
-
|
|
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 };
|