@tabnas/transduce 0.2.8 → 0.2.10

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.
@@ -0,0 +1,17 @@
1
+ import { AbortFlag, Datum, Limits, Metrics } from '@tabnas/alchemy/shared';
2
+ import type { Flow, ParserSink } from '@tabnas/alchemy/shared';
3
+ import type { Tabnas } from '@tabnas/parser';
4
+ export declare class ParserEventSource {
5
+ private parser;
6
+ private text;
7
+ private sourceLimits;
8
+ private abortFlag;
9
+ private sourceMetrics;
10
+ private used;
11
+ constructor(parser: Tabnas, text: string);
12
+ limits(limits: Limits): this;
13
+ abort(abort: AbortFlag): this;
14
+ metrics(metrics: Metrics): this;
15
+ run(sink: ParserSink): Flow;
16
+ }
17
+ export declare function scalarOf(value: unknown, lexeme: string | undefined): Datum;
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /* Copyright (c) 2026 tabnas, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ParserEventSource = void 0;
5
+ exports.scalarOf = scalarOf;
6
+ // `ParserEventSource`: `ParserEvents/1` from a tabnas parse of one text,
7
+ // the input a transducer part reads (rs/src/source/parser_events.rs).
8
+ //
9
+ // The source installs one `ruleDone` subscriber, which relays each rule
10
+ // pass the engine completes as a `ParserEvent`, on the thread that parses,
11
+ // into a `ParserSink`: an `open` when a rule's open pass has run, a `close`
12
+ // when its close pass has. It knows no grammar. When the engine has
13
+ // returned the parse's value it sends one `end`, so a document is complete
14
+ // only once it validated, as every source's is.
15
+ //
16
+ // What each pass carries is alchemy's `RulePass`:
17
+ //
18
+ // - the rule's `name`, its depth `d` and instance `i` (the engine's, which
19
+ // counts from 1 here and from 0 in Go and Rust: DIVERGENCE.md 6), the
20
+ // alternate that matched (`alt`: `p`, `r`, `b`, and its groups `g`,
21
+ // sorted, since they are tags whose order the runtimes' engines keep
22
+ // differently), whether recovery forced the close, and its named counters
23
+ // `n`, sorted by name;
24
+ // - the `tokens` the pass consumed: those its alternate matched (the
25
+ // engine's `o[:oN]` or `c[:cN]`), less the `b` it gave back from their
26
+ // end, which are the next rule's to consume, and none when `b` is more
27
+ // than it recorded, since a close alternate can back up a token it did
28
+ // not record. A token is its name, its value, its source text and its
29
+ // 1-based row and column; a token with no value (the end of the source,
30
+ // a fixed token such as `[`, which Rust's engine gives its text:
31
+ // DIVERGENCE.md 7) has `null`, and a number carries its source text as its lexeme when
32
+ // that text is a JSON number spelling the value (RFC 8259), the rule the
33
+ // incremental source keeps lexemes by, so a renderer can write it as it
34
+ // stands;
35
+ // - on a close pass, the rule's node when that is a scalar (`value`), a
36
+ // number with the lexeme of the rule's first open token as above;
37
+ // nothing on an open pass, nor for a container.
38
+ //
39
+ // A pass made while a probe is open is speculative: the grammar parses
40
+ // ahead and rewinds past it (tabnas-bnf's probe, the engine's own for a
41
+ // serialized grammar's `pd_` keys), so it is never the document's. The
42
+ // engine flags it (`RuleDone.probe`), and the source drops it before any
43
+ // sink sees it.
44
+ //
45
+ // The source holds its limits as it builds each pass, before the sink sees
46
+ // it: a token's value or source text, or a pass's value, longer than
47
+ // `max_scalar_bytes` is refused with that limit's name, as a JSON-events
48
+ // source refuses a scalar. `max_depth` and `max_key_bytes` hold where a
49
+ // part's outputs become `JsonEvents/1`, on the events it writes. The abort
50
+ // flag is polled per pass, and `Metrics.events` counts the events
51
+ // delivered.
52
+ //
53
+ // Failure mapping is `ParserSource`'s: a sink failure is thrown as it was;
54
+ // a sink that stopped is `'stop'`; a parse cancelled through the caller's
55
+ // `AbortFlag` is `ABORTED`; any other engine error is `INPUT_INVALID` with
56
+ // the engine's code and position. A source runs once, as `ParserSource`
57
+ // does: its parser carries the run's subscriber and budget.
58
+ const shared_1 = require("@tabnas/alchemy/shared");
59
+ const rule_events_1 = require("./rule-events");
60
+ const source_1 = require("./source");
61
+ // A tabnas parser applied to one text, as a source of its rule passes.
62
+ class ParserEventSource {
63
+ // A source with default limits, its own abort flag and fresh metrics; the
64
+ // builder methods change each.
65
+ constructor(parser, text) {
66
+ this.sourceLimits = shared_1.Limits.default();
67
+ this.abortFlag = new shared_1.AbortFlag();
68
+ this.sourceMetrics = new shared_1.Metrics();
69
+ this.used = false;
70
+ this.parser = parser;
71
+ this.text = text;
72
+ }
73
+ limits(limits) {
74
+ this.sourceLimits = limits;
75
+ return this;
76
+ }
77
+ abort(abort) {
78
+ this.abortFlag = abort;
79
+ return this;
80
+ }
81
+ metrics(metrics) {
82
+ this.sourceMetrics = metrics;
83
+ return this;
84
+ }
85
+ // Parse, relaying each pass into `sink`, then `end`.
86
+ run(sink) {
87
+ if (this.used) {
88
+ throw new shared_1.Fail('STREAM_REUSED', "a ParserEventSource runs once: its parser carries the run's subscriber and budget; " +
89
+ 'make a new source, with a new parser, for another run');
90
+ }
91
+ this.used = true;
92
+ const abort = this.abortFlag;
93
+ const stop = new shared_1.AbortFlag();
94
+ const relay = new Relay(sink, this.sourceLimits.max_scalar_bytes, abort, stop);
95
+ (0, rule_events_1.prepare)(this.parser, () => !abort.isAborted() && !stop.isAborted(), (rule, done) => relay.onDone(rule, done));
96
+ let parsed;
97
+ try {
98
+ this.parser.parse(this.text);
99
+ parsed = { ok: true };
100
+ }
101
+ catch (error) {
102
+ parsed = { ok: false, error };
103
+ }
104
+ try {
105
+ const status = relay.status;
106
+ if ('failed' === status.type)
107
+ throw status.error;
108
+ if ('stopped' === status.type)
109
+ return 'stop';
110
+ if (!parsed.ok)
111
+ throw (0, source_1.engineFailure)(parsed.error, abort);
112
+ return relay.send({ type: 'end' });
113
+ }
114
+ finally {
115
+ this.sourceMetrics.events += relay.events;
116
+ }
117
+ }
118
+ }
119
+ exports.ParserEventSource = ParserEventSource;
120
+ // The subscriber's state: the sink and how the run stands.
121
+ class Relay {
122
+ constructor(sink, maxScalarBytes, abort,
123
+ // Set to make the parse guard cancel the parse.
124
+ stop) {
125
+ this.sink = sink;
126
+ this.maxScalarBytes = maxScalarBytes;
127
+ this.abort = abort;
128
+ this.stop = stop;
129
+ this.status = { type: 'running' };
130
+ this.events = 0;
131
+ }
132
+ send(ev) {
133
+ this.events++;
134
+ return this.sink.event(ev);
135
+ }
136
+ fail(error) {
137
+ this.status = { type: 'failed', error };
138
+ this.stop.abort();
139
+ }
140
+ // One pass the engine completed.
141
+ onDone(rule, done) {
142
+ if (true === done.probe || 'running' !== this.status.type)
143
+ return;
144
+ if (this.abort.isAborted()) {
145
+ this.fail(shared_1.Fail.aborted());
146
+ return;
147
+ }
148
+ const open = 'o' === done.state;
149
+ let ev;
150
+ try {
151
+ const pass = rulePass(rule, done, open, this.maxScalarBytes);
152
+ ev = open ? { type: 'open', pass } : { type: 'close', pass };
153
+ }
154
+ catch (error) {
155
+ this.fail(error);
156
+ return;
157
+ }
158
+ try {
159
+ if ('stop' === this.send(ev)) {
160
+ this.status = { type: 'stopped' };
161
+ this.stop.abort();
162
+ }
163
+ }
164
+ catch (error) {
165
+ this.fail(error);
166
+ }
167
+ }
168
+ }
169
+ // The pass as alchemy's `RulePass`, its sizes held to `max_scalar_bytes`.
170
+ function rulePass(rule, done, open, max) {
171
+ const r = rule;
172
+ const recorded = open ? r.o.slice(0, r.oN) : r.c.slice(0, r.cN);
173
+ const b = null == done.alt ? 0 : done.alt.b;
174
+ const kept = recorded.slice(0, Math.max(0, recorded.length - b));
175
+ const tokens = kept.map((t) => {
176
+ const src = 'string' === typeof t.src ? t.src : String(t.src ?? '');
177
+ held((0, shared_1.utf8Bytes)(src), "a token's source text", max);
178
+ const val = scalarOf(t.val, src);
179
+ if ('string' === val.type)
180
+ held((0, shared_1.utf8Bytes)(val.value), "a token's value", max);
181
+ return { name: t.name, val, src, row: t.rI, col: t.cI };
182
+ });
183
+ let value;
184
+ if (!open && undefined !== r.node && undefined !== (0, shared_1.engineScalar)(r.node)) {
185
+ const first = r.o[0];
186
+ value = scalarOf(r.node, 0 < r.oN && 'string' === typeof first?.src ? first.src : undefined);
187
+ if ('string' === value.type)
188
+ held((0, shared_1.utf8Bytes)(value.value), "a rule's value", max);
189
+ }
190
+ const n = Object.entries((r.n ?? {}))
191
+ .map(([k, v]) => [k, Math.trunc(v)])
192
+ .sort(([a], [b]) => byCodeUnit(a, b));
193
+ const alt = null == done.alt
194
+ ? undefined
195
+ : { p: done.alt.p, r: done.alt.r, b: done.alt.b, g: [...done.alt.g].sort(byCodeUnit) };
196
+ return {
197
+ name: rule.name,
198
+ d: rule.d,
199
+ i: rule.i,
200
+ ...(undefined === alt ? {} : { alt }),
201
+ tokens,
202
+ forced: true === done.forced,
203
+ n,
204
+ ...(undefined === value ? {} : { value }),
205
+ };
206
+ }
207
+ // Strings in code-unit order, as Rust's `sort` orders ASCII names.
208
+ function byCodeUnit(a, b) {
209
+ return a < b ? -1 : a > b ? 1 : 0;
210
+ }
211
+ // An engine value as a pass carries it: `undefined` as null, and a number
212
+ // with `lexeme` when that text is a JSON number spelling it.
213
+ function scalarOf(value, lexeme) {
214
+ const scalar = (0, shared_1.engineScalar)(value);
215
+ if (undefined !== scalar && 'number' === scalar.type) {
216
+ const keep = undefined !== lexeme && (0, shared_1.isJsonNumber)(lexeme) && Number(lexeme) === scalar.value;
217
+ return shared_1.Datum.number(scalar.value, keep ? lexeme : undefined);
218
+ }
219
+ return (0, shared_1.fromTabnas)(value);
220
+ }
221
+ // `max_scalar_bytes` held on one text a pass carries.
222
+ function held(bytes, what, max) {
223
+ if (bytes > max) {
224
+ throw shared_1.Fail.limit('max_scalar_bytes', max, `${what} of ${bytes} bytes is larger than ${max}`);
225
+ }
226
+ }
227
+ //# sourceMappingURL=parser-event-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser-event-source.js","sourceRoot":"","sources":["../src/parser-event-source.ts"],"names":[],"mappings":"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"}
package/dist/routers.js CHANGED
@@ -6,6 +6,7 @@ const guard_1 = require("./guard");
6
6
  const route_1 = require("./route");
7
7
  const scan_1 = require("./scan");
8
8
  const table_from_json_1 = require("./table-from-json");
9
+ const transduce_tree_1 = require("./transduce-tree");
9
10
  // Each method constructs the stage it names with the same parameters.
10
11
  exports.routers = Object.freeze({
11
12
  router(specs, limits, duplicates, metrics, downstream) {
@@ -20,5 +21,13 @@ exports.routers = Object.freeze({
20
21
  guarded(inner, limits, abort, metrics) {
21
22
  return new guard_1.Guarded(inner, limits, abort, metrics);
22
23
  },
24
+ transduceTree(spec, downstream) {
25
+ return new transduce_tree_1.TransduceTree(spec, downstream);
26
+ },
27
+ // TransduceTree reads a spec's `:member` and `:text`, and holds a text to
28
+ // the spec's `maxTextBytes` as it grows.
29
+ transduceTreeReadsMemberAndText() {
30
+ return true;
31
+ },
23
32
  });
24
33
  //# sourceMappingURL=routers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"routers.js","sourceRoot":"","sources":["../src/routers.ts"],"names":[],"mappings":";AAAA,4CAA4C;;;AAwB5C,mCAAiC;AACjC,mCAAgC;AAChC,iCAAiC;AACjC,uDAAiD;AAEjD,sEAAsE;AACzD,QAAA,OAAO,GAAY,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,CACJ,KAA6B,EAC7B,MAAc,EACd,UAAsB,EACtB,OAAgB,EAChB,UAAqB;QAErB,OAAO,IAAI,cAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;IACnE,CAAC;IAED,aAAa,CACX,OAAqB,EACrB,MAAc,EACd,UAAsB,EACtB,OAAgB,EAChB,IAAe;QAEf,OAAO,IAAI,+BAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACtE,CAAC;IAED,QAAQ,CACN,OAAU,EACV,IAA6C,EAC7C,MAAyB,EACzB,GAAwB;QAExB,OAAO,IAAI,eAAQ,CAAU,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO,CAAC,KAAW,EAAE,MAAc,EAAE,KAAgB,EAAE,OAAgB;QACrE,OAAO,IAAI,eAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;IACnD,CAAC;CACF,CAAC,CAAA"}
1
+ {"version":3,"file":"routers.js","sourceRoot":"","sources":["../src/routers.ts"],"names":[],"mappings":";AAAA,4CAA4C;;;AA2B5C,mCAAiC;AACjC,mCAAgC;AAChC,iCAAiC;AACjC,uDAAiD;AACjD,qDAAgD;AAEhD,sEAAsE;AACzD,QAAA,OAAO,GAAY,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,CACJ,KAA6B,EAC7B,MAAc,EACd,UAAsB,EACtB,OAAgB,EAChB,UAAqB;QAErB,OAAO,IAAI,cAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC,CAAA;IACnE,CAAC;IAED,aAAa,CACX,OAAqB,EACrB,MAAc,EACd,UAAsB,EACtB,OAAgB,EAChB,IAAe;QAEf,OAAO,IAAI,+BAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACtE,CAAC;IAED,QAAQ,CACN,OAAU,EACV,IAA6C,EAC7C,MAAyB,EACzB,GAAwB;QAExB,OAAO,IAAI,eAAQ,CAAU,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO,CAAC,KAAW,EAAE,MAAc,EAAE,KAAgB,EAAE,OAAgB;QACrE,OAAO,IAAI,eAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;IACnD,CAAC;IAED,aAAa,CAAC,IAAc,EAAE,UAAgB;QAC5C,OAAO,IAAI,8BAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IAC5C,CAAC;IAED,0EAA0E;IAC1E,yCAAyC;IACzC,+BAA+B;QAC7B,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ import type { Flow, ParserEvent, ParserSink, Sink, TreeSpec } from '@tabnas/alchemy/shared';
2
+ export declare class TransduceTree implements ParserSink {
3
+ readonly spec: TreeSpec;
4
+ readonly down: Sink;
5
+ open: number;
6
+ carry: boolean;
7
+ readonly texts: string[];
8
+ readonly bytes: number[];
9
+ constructor(spec: TreeSpec, down: Sink);
10
+ event(ev: ParserEvent): Flow;
11
+ private get max();
12
+ private spell;
13
+ private write;
14
+ private openPass;
15
+ private closePass;
16
+ }
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ /* Copyright (c) 2026 tabnas, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TransduceTree = void 0;
5
+ // `TransduceTree`: alchemy's `transduce-tree` run natively, the parser's
6
+ // rule passes (`ParserEvents/1`) in and a tree's `JsonEvents/1` out, as a
7
+ // `TreeSpec` keyed by rule name says (rs/src/transduce_tree.rs).
8
+ //
9
+ // The library's text (`stdlib/transduce.alc`) is the specification, and
10
+ // alchemy's `TreeSpec` documents what a native must do: each pass is looked
11
+ // up by its rule's name, one lookup per pass whatever the spec's size; an
12
+ // open pass announces its key, then begins its container or yields its
13
+ // scalar from one of its tokens, then announces its member; a close pass
14
+ // yields its text, when its rule is a `:text` rule, then ends its
15
+ // container, or yields its own value; and a container whose rule is
16
+ // replaced by a rule the spec names a container is one container, so that
17
+ // close ends nothing and the replacing open begins nothing. The state is
18
+ // the count of open containers, whether a close handed its container on,
19
+ // and the text each open `:text` rule has gathered, to which every pass
20
+ // writes its tokens' source text, so the native retains the document's
21
+ // nesting and an open rule's text, and nothing else of its width or
22
+ // length, as the library's step does. alchemy-cli's differential test
23
+ // holds the two to the same bytes.
24
+ //
25
+ // A text is held to the spec's `maxTextBytes` as it grows, at the joins
26
+ // the library's `string-join` makes and in their order: a pass's tokens
27
+ // together, then the innermost text with them, and a closed text with the
28
+ // text around it, each refused past the bound with alchemy's
29
+ // `treeTextRefusal`. The routers say this native reads `:member` and
30
+ // `:text` (routers.ts), which is what lets the lowering hand it a spec
31
+ // that uses either.
32
+ //
33
+ // A pass's events are worked out before the first of them is written, as
34
+ // the library's step returns them together: a pass that fails writes none.
35
+ // The failures are the library's, with its codes and texts, and in its
36
+ // order: an open pass's key, then what it begins or yields, then its
37
+ // tokens' text, then its member; a close pass's text, then what it ends or
38
+ // yields.
39
+ const shared_1 = require("@tabnas/alchemy/shared");
40
+ // The library's `transduce-tree`, natively, writing to `down`.
41
+ class TransduceTree {
42
+ constructor(spec, down) {
43
+ this.spec = spec;
44
+ this.down = down;
45
+ // Containers open.
46
+ this.open = 0;
47
+ // A close handed its container on to the rule replacing it.
48
+ this.carry = false;
49
+ // The text each open `:text` rule has gathered, the innermost last, and
50
+ // its length in UTF-8 bytes, which the bound is held to.
51
+ this.texts = [];
52
+ this.bytes = [];
53
+ }
54
+ event(ev) {
55
+ switch (ev.type) {
56
+ case 'open':
57
+ return this.openPass(ev.pass);
58
+ case 'close':
59
+ return this.closePass(ev.pass);
60
+ case 'end':
61
+ if (0 < this.open) {
62
+ throw new shared_1.Fail('PROTOCOL_ORDER_ERROR', "the rule passes ended inside a container, which a tree's never do");
63
+ }
64
+ return this.down.event(shared_1.Ev.end);
65
+ }
66
+ }
67
+ // The most bytes a text may hold.
68
+ get max() {
69
+ return this.spec.maxTextBytes ?? Infinity;
70
+ }
71
+ // The pass's tokens written to the innermost open text, if one is and the
72
+ // pass consumed any, as the library's `transduce-spell` writes them: the
73
+ // tokens' text joined, then written to the innermost, each join held to
74
+ // the bound.
75
+ spell(pass) {
76
+ if (0 === this.texts.length || 0 === pass.tokens.length)
77
+ return;
78
+ let more = '';
79
+ let n = 0;
80
+ for (const token of pass.tokens) {
81
+ more += token.src;
82
+ n += (0, shared_1.utf8Bytes)(token.src);
83
+ }
84
+ if (n > this.max)
85
+ throw (0, shared_1.treeTextRefusal)(n, this.max);
86
+ this.write(more, n);
87
+ }
88
+ // `more`, of `n` bytes, written at the end of the innermost open text, as
89
+ // the library's `transduce-write` joins the two, held to the bound.
90
+ write(more, n) {
91
+ const last = this.texts.length - 1;
92
+ const len = this.bytes[last] + n;
93
+ if (len > this.max)
94
+ throw (0, shared_1.treeTextRefusal)(len, this.max);
95
+ this.texts[last] += more;
96
+ this.bytes[last] = len;
97
+ }
98
+ openPass(pass) {
99
+ const rule = this.spec.rules.get(pass.name);
100
+ if (undefined === rule) {
101
+ this.spell(pass);
102
+ return 'continue';
103
+ }
104
+ const key = named(rule.key, pass);
105
+ let then;
106
+ let begins = false;
107
+ if (undefined !== rule.container) {
108
+ if (!this.carry) {
109
+ begins = true;
110
+ then = 'object' === rule.container ? shared_1.Ev.objectStart : shared_1.Ev.arrayStart;
111
+ }
112
+ }
113
+ else if (undefined !== rule.scalar && 'value' !== rule.scalar) {
114
+ const token = pass.tokens[rule.scalar.token];
115
+ if (undefined !== token)
116
+ then = scalar(token.val);
117
+ }
118
+ this.spell(pass);
119
+ const member = named(rule.member, pass);
120
+ if (undefined !== rule.container) {
121
+ if (begins)
122
+ this.open++;
123
+ else
124
+ this.carry = false;
125
+ }
126
+ if (true === rule.text) {
127
+ this.texts.push('');
128
+ this.bytes.push(0);
129
+ }
130
+ for (const ev of [
131
+ undefined === key ? undefined : shared_1.Ev.key(key),
132
+ then,
133
+ undefined === member ? undefined : shared_1.Ev.key(member),
134
+ ]) {
135
+ if (undefined !== ev && 'stop' === this.down.event(ev))
136
+ return 'stop';
137
+ }
138
+ return 'continue';
139
+ }
140
+ closePass(pass) {
141
+ const rule = this.spec.rules.get(pass.name);
142
+ if (undefined === rule) {
143
+ this.spell(pass);
144
+ return 'continue';
145
+ }
146
+ // A `:text` rule's text closes first, and joins the one around it
147
+ // before this pass's own tokens do; it is yielded before anything
148
+ // else, once the rest of the pass is known not to fail.
149
+ let text;
150
+ if (true === rule.text) {
151
+ text = this.texts.pop();
152
+ if (undefined === text)
153
+ throw typeError('top: the vector is empty; there is no last item');
154
+ const n = this.bytes.pop();
155
+ if (0 < this.texts.length)
156
+ this.write(text, n);
157
+ }
158
+ this.spell(pass);
159
+ let then;
160
+ if (undefined === rule.container) {
161
+ if ('value' === rule.scalar && undefined !== pass.value)
162
+ then = scalar(pass.value);
163
+ }
164
+ else if (undefined !== pass.alt && (0, shared_1.isTreeContainer)(this.spec, pass.alt.r)) {
165
+ this.carry = true;
166
+ }
167
+ else {
168
+ if (0 === this.open)
169
+ throw typeError('pop: the vector is empty; there is no last item to remove');
170
+ this.open--;
171
+ this.carry = false;
172
+ then = 'object' === rule.container ? shared_1.Ev.objectEnd : shared_1.Ev.arrayEnd;
173
+ }
174
+ if (undefined !== text && 'stop' === this.down.event(shared_1.Ev.string(text)))
175
+ return 'stop';
176
+ return undefined === then ? 'continue' : this.down.event(then);
177
+ }
178
+ }
179
+ exports.TransduceTree = TransduceTree;
180
+ // The member name a field of a rule's entry names for a pass: the string,
181
+ // or the `:val` of the pass's token at that index, nothing when the pass
182
+ // consumed no such token; the library's `key` refuses a name that is not a
183
+ // string.
184
+ function named(field, pass) {
185
+ if (undefined === field)
186
+ return undefined;
187
+ if ('name' in field)
188
+ return field.name;
189
+ const token = pass.tokens[field.token];
190
+ if (undefined === token)
191
+ return undefined;
192
+ if ('string' !== token.val.type) {
193
+ throw typeError(`key: the name must be a string, not ${kind(token.val)}`);
194
+ }
195
+ return token.val.value;
196
+ }
197
+ // A value as the library's `scalar` takes it, or its refusal.
198
+ function scalar(value) {
199
+ switch (value.type) {
200
+ case 'null':
201
+ return shared_1.Ev.null;
202
+ case 'bool':
203
+ return shared_1.Ev.bool(value.value);
204
+ case 'number':
205
+ return shared_1.Ev.number(value.value, value.lexeme);
206
+ case 'string':
207
+ return shared_1.Ev.string(value.value);
208
+ default:
209
+ throw typeError(`scalar: the value must be null, a boolean, a number or a string, not ${kind(value)}`);
210
+ }
211
+ }
212
+ // What a value is, in the runtime's words.
213
+ function kind(value) {
214
+ switch (value.type) {
215
+ case 'null':
216
+ return 'null';
217
+ case 'bool':
218
+ return 'a boolean';
219
+ case 'number':
220
+ return 'a number';
221
+ case 'string':
222
+ return 'a string';
223
+ case 'array':
224
+ return 'a vector';
225
+ case 'object':
226
+ return 'a record';
227
+ }
228
+ }
229
+ // The runtime's type error: `DSL_TYPE_ERROR`, the `type_mismatch` word
230
+ // first.
231
+ function typeError(message) {
232
+ return new shared_1.Fail('DSL_TYPE_ERROR', `type_mismatch: ${message}`);
233
+ }
234
+ //# sourceMappingURL=transduce-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transduce-tree.js","sourceRoot":"","sources":["../src/transduce-tree.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.2.8";
1
+ export declare const VERSION = "0.2.10";
2
2
  export { Datum, DatumBuilder, byteSize, fromJSON, fromTabnas, getPath, takePath, toJSON, toText, walkDatum, } from '@tabnas/alchemy/shared';
3
3
  export type { Duplicates } from '@tabnas/alchemy/shared';
4
4
  export { Code, Fail, isFail } from '@tabnas/alchemy/shared';
@@ -23,6 +23,10 @@ export { Prune, SourceMode, ValueSource, engineFailure, walkValue } from './sour
23
23
  export type { Source } from './source';
24
24
  export { Guarded } from './guard';
25
25
  export { ParserSource } from './parser-source';
26
+ export { ParserEventSource } from './parser-event-source';
27
+ export { TransduceTree } from './transduce-tree';
28
+ export { isTreeContainer, parserEventFromJSON, parserEventToJSON, treeTextRefusal } from '@tabnas/alchemy/shared';
29
+ export type { ParserEvent, ParserSink, PassAlt, PassToken, RulePass, TreeContainer, TreeKey, TreeRule, TreeScalar, TreeSpec, } from '@tabnas/alchemy/shared';
26
30
  export { DEFAULT_CHUNK_BYTES, LineFormat, LinesSource } from './lines';
27
31
  export type { LinesChunk, LinesInput, LinesWriter } from './lines';
28
32
  export { INCREMENTAL, capability, isIncremental } from './capability';
package/dist/transduce.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /* Copyright (c) 2026 tabnas, MIT License */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.engineFailure = exports.ValueSource = exports.SourceMode = exports.Prune = exports.replay = exports.TreeContract = exports.FnSink = exports.EventRecorder = exports.CountSink = exports.pathText = exports.keyText = exports.Selector = exports.Path = exports.Transition = exports.ScanEmit = exports.CaptureSpec = exports.SelectedRecorder = exports.Router = exports.FnRoute = exports.Matcher = exports.utf8Bytes = exports.NODE_BYTES = exports.Metrics = exports.Limits = exports.LIMIT_NAMES = exports.AbortFlag = exports.numberText = exports.jsonString = exports.jsonNumber = exports.isJsonNumber = exports.isStart = exports.isScalar = exports.isEnd = exports.eventText = exports.eventEquals = exports.Ev = exports.isFail = exports.Fail = exports.Code = exports.walkDatum = exports.toText = exports.toJSON = exports.takePath = exports.getPath = exports.fromTabnas = exports.fromJSON = exports.byteSize = exports.DatumBuilder = exports.Datum = exports.VERSION = void 0;
5
- exports.routers = exports.TableFromJson = exports.columnFromMeta = exports.boundColumn = exports.Table = exports.Schema = exports.Cell = exports.isIncremental = exports.capability = exports.INCREMENTAL = exports.LinesSource = exports.LineFormat = exports.DEFAULT_CHUNK_BYTES = exports.ParserSource = exports.Guarded = exports.walkValue = void 0;
5
+ exports.routers = exports.TableFromJson = exports.columnFromMeta = exports.boundColumn = exports.Table = exports.Schema = exports.Cell = exports.isIncremental = exports.capability = exports.INCREMENTAL = exports.LinesSource = exports.LineFormat = exports.DEFAULT_CHUNK_BYTES = exports.treeTextRefusal = exports.parserEventToJSON = exports.parserEventFromJSON = exports.isTreeContainer = exports.TransduceTree = exports.ParserEventSource = exports.ParserSource = exports.Guarded = exports.walkValue = void 0;
6
6
  // Streaming transducers over any tabnas parser.
7
7
  //
8
8
  // A transducer decides what a source contributes to an output model; a
@@ -12,7 +12,11 @@ exports.routers = exports.TableFromJson = exports.columnFromMeta = exports.bound
12
12
  // - the source protocol `JsonEvent` (`JsonEvents/1`) and the push boundary
13
13
  // `Sink`;
14
14
  // - sources that produce it from a parsed value, from a live tabnas parse
15
- // through the engine's rule events, or from line-delimited input;
15
+ // through the engine's rule events, or from line-delimited input, and
16
+ // the source of the parser's rule passes themselves (`ParserEventSource`,
17
+ // `ParserEvents/1`), what a transducer part reads;
18
+ // - `TransduceTree`, alchemy's `transduce-tree` run natively over those
19
+ // passes;
16
20
  // - `Selector`s and the matcher and router that recognize selected scopes
17
21
  // in one pass and materialize them under a byte limit;
18
22
  // - the table protocol `TableEvent` (`TableRows/1`) and the standard
@@ -30,7 +34,7 @@ exports.routers = exports.TableFromJson = exports.columnFromMeta = exports.bound
30
34
  // under the names it always exported them by. `routers` is this package's
31
35
  // stages as alchemy's `Routers`, for a host to pass to alchemy's `compile`.
32
36
  // This package's version, as package.json declares it.
33
- exports.VERSION = '0.2.8';
37
+ exports.VERSION = '0.2.10';
34
38
  var shared_1 = require("@tabnas/alchemy/shared");
35
39
  Object.defineProperty(exports, "Datum", { enumerable: true, get: function () { return shared_1.Datum; } });
36
40
  Object.defineProperty(exports, "DatumBuilder", { enumerable: true, get: function () { return shared_1.DatumBuilder; } });
@@ -98,6 +102,15 @@ var guard_1 = require("./guard");
98
102
  Object.defineProperty(exports, "Guarded", { enumerable: true, get: function () { return guard_1.Guarded; } });
99
103
  var parser_source_1 = require("./parser-source");
100
104
  Object.defineProperty(exports, "ParserSource", { enumerable: true, get: function () { return parser_source_1.ParserSource; } });
105
+ var parser_event_source_1 = require("./parser-event-source");
106
+ Object.defineProperty(exports, "ParserEventSource", { enumerable: true, get: function () { return parser_event_source_1.ParserEventSource; } });
107
+ var transduce_tree_1 = require("./transduce-tree");
108
+ Object.defineProperty(exports, "TransduceTree", { enumerable: true, get: function () { return transduce_tree_1.TransduceTree; } });
109
+ var shared_10 = require("@tabnas/alchemy/shared");
110
+ Object.defineProperty(exports, "isTreeContainer", { enumerable: true, get: function () { return shared_10.isTreeContainer; } });
111
+ Object.defineProperty(exports, "parserEventFromJSON", { enumerable: true, get: function () { return shared_10.parserEventFromJSON; } });
112
+ Object.defineProperty(exports, "parserEventToJSON", { enumerable: true, get: function () { return shared_10.parserEventToJSON; } });
113
+ Object.defineProperty(exports, "treeTextRefusal", { enumerable: true, get: function () { return shared_10.treeTextRefusal; } });
101
114
  var lines_1 = require("./lines");
102
115
  Object.defineProperty(exports, "DEFAULT_CHUNK_BYTES", { enumerable: true, get: function () { return lines_1.DEFAULT_CHUNK_BYTES; } });
103
116
  Object.defineProperty(exports, "LineFormat", { enumerable: true, get: function () { return lines_1.LineFormat; } });
@@ -106,12 +119,12 @@ var capability_1 = require("./capability");
106
119
  Object.defineProperty(exports, "INCREMENTAL", { enumerable: true, get: function () { return capability_1.INCREMENTAL; } });
107
120
  Object.defineProperty(exports, "capability", { enumerable: true, get: function () { return capability_1.capability; } });
108
121
  Object.defineProperty(exports, "isIncremental", { enumerable: true, get: function () { return capability_1.isIncremental; } });
109
- var shared_10 = require("@tabnas/alchemy/shared");
110
- Object.defineProperty(exports, "Cell", { enumerable: true, get: function () { return shared_10.Cell; } });
111
- Object.defineProperty(exports, "Schema", { enumerable: true, get: function () { return shared_10.Schema; } });
112
- Object.defineProperty(exports, "Table", { enumerable: true, get: function () { return shared_10.Table; } });
113
- Object.defineProperty(exports, "boundColumn", { enumerable: true, get: function () { return shared_10.boundColumn; } });
114
- Object.defineProperty(exports, "columnFromMeta", { enumerable: true, get: function () { return shared_10.columnFromMeta; } });
122
+ var shared_11 = require("@tabnas/alchemy/shared");
123
+ Object.defineProperty(exports, "Cell", { enumerable: true, get: function () { return shared_11.Cell; } });
124
+ Object.defineProperty(exports, "Schema", { enumerable: true, get: function () { return shared_11.Schema; } });
125
+ Object.defineProperty(exports, "Table", { enumerable: true, get: function () { return shared_11.Table; } });
126
+ Object.defineProperty(exports, "boundColumn", { enumerable: true, get: function () { return shared_11.boundColumn; } });
127
+ Object.defineProperty(exports, "columnFromMeta", { enumerable: true, get: function () { return shared_11.columnFromMeta; } });
115
128
  var table_from_json_1 = require("./table-from-json");
116
129
  Object.defineProperty(exports, "TableFromJson", { enumerable: true, get: function () { return table_from_json_1.TableFromJson; } });
117
130
  var routers_1 = require("./routers");
@@ -1 +1 @@
1
- {"version":3,"file":"transduce.js","sourceRoot":"","sources":["../src/transduce.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transduce.js","sourceRoot":"","sources":["../src/transduce.ts"],"names":[],"mappings":";AAAA,4CAA4C;;;;AAE5C,gDAAgD;AAChD,EAAE;AACF,uEAAuE;AACvE,oEAAoE;AACpE,mBAAmB;AACnB,EAAE;AACF,2EAA2E;AAC3E,YAAY;AACZ,0EAA0E;AAC1E,wEAAwE;AACxE,4EAA4E;AAC5E,qDAAqD;AACrD,wEAAwE;AACxE,YAAY;AACZ,0EAA0E;AAC1E,yDAAyD;AACzD,qEAAqE;AACrE,qCAAqC;AACrC,0EAA0E;AAC1E,mBAAmB;AACnB,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,gEAAgE;AAChE,EAAE;AACF,wEAAwE;AACxE,6DAA6D;AAC7D,wEAAwE;AACxE,0EAA0E;AAC1E,4EAA4E;AAE5E,uDAAuD;AAC1C,QAAA,OAAO,GAAG,QAAQ,CAAA;AAE/B,iDAW+B;AAV7B,+FAAA,KAAK,OAAA;AACL,sGAAA,YAAY,OAAA;AACZ,kGAAA,QAAQ,OAAA;AACR,kGAAA,QAAQ,OAAA;AACR,oGAAA,UAAU,OAAA;AACV,iGAAA,OAAO,OAAA;AACP,kGAAA,QAAQ,OAAA;AACR,gGAAA,MAAM,OAAA;AACN,gGAAA,MAAM,OAAA;AACN,mGAAA,SAAS,OAAA;AAGX,iDAA2D;AAAlD,8FAAA,IAAI,OAAA;AAAE,8FAAA,IAAI,OAAA;AAAE,gGAAA,MAAM,OAAA;AAE3B,iDAA6F;AAApF,4FAAA,EAAE,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,+FAAA,KAAK,OAAA;AAAE,kGAAA,QAAQ,OAAA;AAAE,iGAAA,OAAO,OAAA;AAE7D,iDAAyF;AAAhF,sGAAA,YAAY,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,oGAAA,UAAU,OAAA;AACzD,iDAO+B;AAN7B,mGAAA,SAAS,OAAA;AACT,qGAAA,WAAW,OAAA;AACX,gGAAA,MAAM,OAAA;AACN,iGAAA,OAAO,OAAA;AACP,oGAAA,UAAU,OAAA;AACV,mGAAA,SAAS,OAAA;AAEX,qCAAmC;AAA1B,kGAAA,OAAO,OAAA;AAGhB,iCAA2D;AAAlD,gGAAA,OAAO,OAAA;AAAE,+FAAA,MAAM,OAAA;AAAE,yGAAA,gBAAgB,OAAA;AAC1C,iDAAoD;AAA3C,qGAAA,WAAW,OAAA;AAEpB,+BAAiC;AAAxB,gGAAA,QAAQ,OAAA;AACjB,iDAAmD;AAA1C,oGAAA,UAAU,OAAA;AACnB,iDAA0E;AAAjE,8FAAA,IAAI,OAAA;AAAE,kGAAA,QAAQ,OAAA;AAAE,iGAAA,OAAO,OAAA;AAAE,kGAAA,QAAQ,OAAA;AAE1C,iDAA+F;AAAtF,mGAAA,SAAS,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,gGAAA,MAAM,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,gGAAA,MAAM,OAAA;AAE/D,mCAAmF;AAA1E,+FAAA,KAAK,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,mGAAA,SAAS,OAAA;AAEjE,iCAAiC;AAAxB,gGAAA,OAAO,OAAA;AAChB,iDAA8C;AAArC,6GAAA,YAAY,OAAA;AACrB,6DAAyD;AAAhD,wHAAA,iBAAiB,OAAA;AAC1B,mDAAgD;AAAvC,+GAAA,aAAa,OAAA;AACtB,kDAAiH;AAAxG,0GAAA,eAAe,OAAA;AAAE,8GAAA,mBAAmB,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAAE,0GAAA,eAAe,OAAA;AAajF,iCAAsE;AAA7D,4GAAA,mBAAmB,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,oGAAA,WAAW,OAAA;AAErD,2CAAqE;AAA5D,yGAAA,WAAW,OAAA;AAAE,wGAAA,UAAU,OAAA;AAAE,2GAAA,aAAa,OAAA;AAC/C,kDAAyF;AAAhF,+FAAA,IAAI,OAAA;AAAE,iGAAA,MAAM,OAAA;AAAE,gGAAA,KAAK,OAAA;AAAE,sGAAA,WAAW,OAAA;AAAE,yGAAA,cAAc,OAAA;AAUzD,qDAAiD;AAAxC,gHAAA,aAAa,OAAA;AACtB,qCAAmC;AAA1B,kGAAA,OAAO,OAAA"}
@@ -1 +1 @@
1
- {"version":"7.0.2","root":["../src/capability.ts","../src/guard.ts","../src/lines.ts","../src/matcher.ts","../src/parser-source.ts","../src/route.ts","../src/routers.ts","../src/rule-events.ts","../src/scan.ts","../src/source.ts","../src/table-from-json.ts","../src/transduce.ts"],"packageJsons":["../node_modules/@tabnas/alchemy/package.json","../node_modules/@tabnas/csv/package.json","../node_modules/@tabnas/json/package.json","../node_modules/@tabnas/jsonic/package.json","../node_modules/@tabnas/parser/package.json","../node_modules/@types/node/package.json","../node_modules/undici-types/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/alchemy/dist/package.json","../node_modules/@tabnas/alchemy/dist/shared/package.json","../node_modules/@tabnas/alchemy/shared/package.json","../node_modules/@tabnas/csv/dist/package.json","../node_modules/@tabnas/json/dist/package.json","../node_modules/@tabnas/parser/dist/package.json","../node_modules/@types/assert/package.json","../node_modules/@types/assert/strict/package.json","../node_modules/@types/async_hooks/package.json","../node_modules/@types/buffer/package.json","../node_modules/@types/child_process/package.json","../node_modules/@types/cluster/package.json","../node_modules/@types/console/package.json","../node_modules/@types/constants/package.json","../node_modules/@types/crypto/package.json","../node_modules/@types/dgram/package.json","../node_modules/@types/diagnostics_channel/package.json","../node_modules/@types/dns/package.json","../node_modules/@types/dns/promises/package.json","../node_modules/@types/domain/package.json","../node_modules/@types/events/package.json","../node_modules/@types/fs/package.json","../node_modules/@types/fs/promises/package.json","../node_modules/@types/http/package.json","../node_modules/@types/http2/package.json","../node_modules/@types/https/package.json","../node_modules/@types/inspector/package.json","../node_modules/@types/inspector/promises/package.json","../node_modules/@types/module/package.json","../node_modules/@types/net/package.json","../node_modules/@types/node/assert/package.json","../node_modules/@types/node/dns/package.json","../node_modules/@types/node/fs/package.json","../node_modules/@types/node/inspector/package.json","../node_modules/@types/node/path/package.json","../node_modules/@types/node/readline/package.json","../node_modules/@types/node/stream/package.json","../node_modules/@types/node/test/package.json","../node_modules/@types/node/timers/package.json","../node_modules/@types/node/util/package.json","../node_modules/@types/node/web-globals/package.json","../node_modules/@types/node/zlib/package.json","../node_modules/@types/os/package.json","../node_modules/@types/path/package.json","../node_modules/@types/path/posix/package.json","../node_modules/@types/path/win32/package.json","../node_modules/@types/perf_hooks/package.json","../node_modules/@types/process/package.json","../node_modules/@types/punycode/package.json","../node_modules/@types/querystring/package.json","../node_modules/@types/readline/package.json","../node_modules/@types/readline/promises/package.json","../node_modules/@types/repl/package.json","../node_modules/@types/stream/consumers/package.json","../node_modules/@types/stream/iter/package.json","../node_modules/@types/stream/package.json","../node_modules/@types/stream/promises/package.json","../node_modules/@types/stream/web/package.json","../node_modules/@types/string_decoder/package.json","../node_modules/@types/timers/package.json","../node_modules/@types/timers/promises/package.json","../node_modules/@types/tls/package.json","../node_modules/@types/trace_events/package.json","../node_modules/@types/tty/package.json","../node_modules/@types/url/package.json","../node_modules/@types/util/package.json","../node_modules/@types/util/types/package.json","../node_modules/@types/v8/package.json","../node_modules/@types/vm/package.json","../node_modules/@types/wasi/package.json","../node_modules/@types/worker_threads/package.json","../node_modules/@types/zlib/package.json","../node_modules/assert/package.json","../node_modules/assert/strict/package.json","../node_modules/async_hooks/package.json","../node_modules/buffer/package.json","../node_modules/child_process/package.json","../node_modules/cluster/package.json","../node_modules/console/package.json","../node_modules/constants/package.json","../node_modules/crypto/package.json","../node_modules/dgram/package.json","../node_modules/diagnostics_channel/package.json","../node_modules/dns/package.json","../node_modules/dns/promises/package.json","../node_modules/domain/package.json","../node_modules/events/package.json","../node_modules/fs/package.json","../node_modules/fs/promises/package.json","../node_modules/http/package.json","../node_modules/http2/package.json","../node_modules/https/package.json","../node_modules/inspector/package.json","../node_modules/inspector/promises/package.json","../node_modules/module/package.json","../node_modules/net/package.json","../node_modules/os/package.json","../node_modules/path/package.json","../node_modules/path/posix/package.json","../node_modules/path/win32/package.json","../node_modules/perf_hooks/package.json","../node_modules/process/package.json","../node_modules/punycode/package.json","../node_modules/querystring/package.json","../node_modules/readline/package.json","../node_modules/readline/promises/package.json","../node_modules/repl/package.json","../node_modules/stream/consumers/package.json","../node_modules/stream/iter/package.json","../node_modules/stream/package.json","../node_modules/stream/promises/package.json","../node_modules/stream/web/package.json","../node_modules/string_decoder/package.json","../node_modules/timers/package.json","../node_modules/timers/promises/package.json","../node_modules/tls/package.json","../node_modules/trace_events/package.json","../node_modules/tty/package.json","../node_modules/url/package.json","../node_modules/util/package.json","../node_modules/util/types/package.json","../node_modules/v8/package.json","../node_modules/vm/package.json","../node_modules/wasi/package.json","../node_modules/worker_threads/package.json","../node_modules/zlib/package.json"]}
1
+ {"version":"7.0.2","root":["../src/capability.ts","../src/guard.ts","../src/lines.ts","../src/matcher.ts","../src/parser-event-source.ts","../src/parser-source.ts","../src/route.ts","../src/routers.ts","../src/rule-events.ts","../src/scan.ts","../src/source.ts","../src/table-from-json.ts","../src/transduce-tree.ts","../src/transduce.ts"],"packageJsons":["../node_modules/@tabnas/alchemy/package.json","../node_modules/@tabnas/csv/package.json","../node_modules/@tabnas/json/package.json","../node_modules/@tabnas/jsonic/package.json","../node_modules/@tabnas/parser/package.json","../node_modules/@types/node/package.json","../node_modules/undici-types/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/alchemy/dist/package.json","../node_modules/@tabnas/alchemy/dist/shared/package.json","../node_modules/@tabnas/alchemy/shared/package.json","../node_modules/@tabnas/csv/dist/package.json","../node_modules/@tabnas/json/dist/package.json","../node_modules/@tabnas/parser/dist/package.json","../node_modules/@types/assert/package.json","../node_modules/@types/assert/strict/package.json","../node_modules/@types/async_hooks/package.json","../node_modules/@types/buffer/package.json","../node_modules/@types/child_process/package.json","../node_modules/@types/cluster/package.json","../node_modules/@types/console/package.json","../node_modules/@types/constants/package.json","../node_modules/@types/crypto/package.json","../node_modules/@types/dgram/package.json","../node_modules/@types/diagnostics_channel/package.json","../node_modules/@types/dns/package.json","../node_modules/@types/dns/promises/package.json","../node_modules/@types/domain/package.json","../node_modules/@types/events/package.json","../node_modules/@types/fs/package.json","../node_modules/@types/fs/promises/package.json","../node_modules/@types/http/package.json","../node_modules/@types/http2/package.json","../node_modules/@types/https/package.json","../node_modules/@types/inspector/package.json","../node_modules/@types/inspector/promises/package.json","../node_modules/@types/module/package.json","../node_modules/@types/net/package.json","../node_modules/@types/node/assert/package.json","../node_modules/@types/node/dns/package.json","../node_modules/@types/node/fs/package.json","../node_modules/@types/node/inspector/package.json","../node_modules/@types/node/path/package.json","../node_modules/@types/node/readline/package.json","../node_modules/@types/node/stream/package.json","../node_modules/@types/node/test/package.json","../node_modules/@types/node/timers/package.json","../node_modules/@types/node/util/package.json","../node_modules/@types/node/web-globals/package.json","../node_modules/@types/node/zlib/package.json","../node_modules/@types/os/package.json","../node_modules/@types/path/package.json","../node_modules/@types/path/posix/package.json","../node_modules/@types/path/win32/package.json","../node_modules/@types/perf_hooks/package.json","../node_modules/@types/process/package.json","../node_modules/@types/punycode/package.json","../node_modules/@types/querystring/package.json","../node_modules/@types/readline/package.json","../node_modules/@types/readline/promises/package.json","../node_modules/@types/repl/package.json","../node_modules/@types/stream/consumers/package.json","../node_modules/@types/stream/iter/package.json","../node_modules/@types/stream/package.json","../node_modules/@types/stream/promises/package.json","../node_modules/@types/stream/web/package.json","../node_modules/@types/string_decoder/package.json","../node_modules/@types/timers/package.json","../node_modules/@types/timers/promises/package.json","../node_modules/@types/tls/package.json","../node_modules/@types/trace_events/package.json","../node_modules/@types/tty/package.json","../node_modules/@types/url/package.json","../node_modules/@types/util/package.json","../node_modules/@types/util/types/package.json","../node_modules/@types/v8/package.json","../node_modules/@types/vm/package.json","../node_modules/@types/wasi/package.json","../node_modules/@types/worker_threads/package.json","../node_modules/@types/zlib/package.json","../node_modules/assert/package.json","../node_modules/assert/strict/package.json","../node_modules/async_hooks/package.json","../node_modules/buffer/package.json","../node_modules/child_process/package.json","../node_modules/cluster/package.json","../node_modules/console/package.json","../node_modules/constants/package.json","../node_modules/crypto/package.json","../node_modules/dgram/package.json","../node_modules/diagnostics_channel/package.json","../node_modules/dns/package.json","../node_modules/dns/promises/package.json","../node_modules/domain/package.json","../node_modules/events/package.json","../node_modules/fs/package.json","../node_modules/fs/promises/package.json","../node_modules/http/package.json","../node_modules/http2/package.json","../node_modules/https/package.json","../node_modules/inspector/package.json","../node_modules/inspector/promises/package.json","../node_modules/module/package.json","../node_modules/net/package.json","../node_modules/os/package.json","../node_modules/path/package.json","../node_modules/path/posix/package.json","../node_modules/path/win32/package.json","../node_modules/perf_hooks/package.json","../node_modules/process/package.json","../node_modules/punycode/package.json","../node_modules/querystring/package.json","../node_modules/readline/package.json","../node_modules/readline/promises/package.json","../node_modules/repl/package.json","../node_modules/stream/consumers/package.json","../node_modules/stream/iter/package.json","../node_modules/stream/package.json","../node_modules/stream/promises/package.json","../node_modules/stream/web/package.json","../node_modules/string_decoder/package.json","../node_modules/timers/package.json","../node_modules/timers/promises/package.json","../node_modules/tls/package.json","../node_modules/trace_events/package.json","../node_modules/tty/package.json","../node_modules/url/package.json","../node_modules/util/package.json","../node_modules/util/types/package.json","../node_modules/v8/package.json","../node_modules/vm/package.json","../node_modules/wasi/package.json","../node_modules/worker_threads/package.json","../node_modules/zlib/package.json"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tabnas/transduce",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "main": "dist/transduce.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/transduce.d.ts",
@@ -0,0 +1,252 @@
1
+ /* Copyright (c) 2026 tabnas, MIT License */
2
+
3
+ // `ParserEventSource`: `ParserEvents/1` from a tabnas parse of one text,
4
+ // the input a transducer part reads (rs/src/source/parser_events.rs).
5
+ //
6
+ // The source installs one `ruleDone` subscriber, which relays each rule
7
+ // pass the engine completes as a `ParserEvent`, on the thread that parses,
8
+ // into a `ParserSink`: an `open` when a rule's open pass has run, a `close`
9
+ // when its close pass has. It knows no grammar. When the engine has
10
+ // returned the parse's value it sends one `end`, so a document is complete
11
+ // only once it validated, as every source's is.
12
+ //
13
+ // What each pass carries is alchemy's `RulePass`:
14
+ //
15
+ // - the rule's `name`, its depth `d` and instance `i` (the engine's, which
16
+ // counts from 1 here and from 0 in Go and Rust: DIVERGENCE.md 6), the
17
+ // alternate that matched (`alt`: `p`, `r`, `b`, and its groups `g`,
18
+ // sorted, since they are tags whose order the runtimes' engines keep
19
+ // differently), whether recovery forced the close, and its named counters
20
+ // `n`, sorted by name;
21
+ // - the `tokens` the pass consumed: those its alternate matched (the
22
+ // engine's `o[:oN]` or `c[:cN]`), less the `b` it gave back from their
23
+ // end, which are the next rule's to consume, and none when `b` is more
24
+ // than it recorded, since a close alternate can back up a token it did
25
+ // not record. A token is its name, its value, its source text and its
26
+ // 1-based row and column; a token with no value (the end of the source,
27
+ // a fixed token such as `[`, which Rust's engine gives its text:
28
+ // DIVERGENCE.md 7) has `null`, and a number carries its source text as its lexeme when
29
+ // that text is a JSON number spelling the value (RFC 8259), the rule the
30
+ // incremental source keeps lexemes by, so a renderer can write it as it
31
+ // stands;
32
+ // - on a close pass, the rule's node when that is a scalar (`value`), a
33
+ // number with the lexeme of the rule's first open token as above;
34
+ // nothing on an open pass, nor for a container.
35
+ //
36
+ // A pass made while a probe is open is speculative: the grammar parses
37
+ // ahead and rewinds past it (tabnas-bnf's probe, the engine's own for a
38
+ // serialized grammar's `pd_` keys), so it is never the document's. The
39
+ // engine flags it (`RuleDone.probe`), and the source drops it before any
40
+ // sink sees it.
41
+ //
42
+ // The source holds its limits as it builds each pass, before the sink sees
43
+ // it: a token's value or source text, or a pass's value, longer than
44
+ // `max_scalar_bytes` is refused with that limit's name, as a JSON-events
45
+ // source refuses a scalar. `max_depth` and `max_key_bytes` hold where a
46
+ // part's outputs become `JsonEvents/1`, on the events it writes. The abort
47
+ // flag is polled per pass, and `Metrics.events` counts the events
48
+ // delivered.
49
+ //
50
+ // Failure mapping is `ParserSource`'s: a sink failure is thrown as it was;
51
+ // a sink that stopped is `'stop'`; a parse cancelled through the caller's
52
+ // `AbortFlag` is `ABORTED`; any other engine error is `INPUT_INVALID` with
53
+ // the engine's code and position. A source runs once, as `ParserSource`
54
+ // does: its parser carries the run's subscriber and budget.
55
+
56
+ import {
57
+ AbortFlag,
58
+ Datum,
59
+ Fail,
60
+ Limits,
61
+ Metrics,
62
+ engineScalar,
63
+ fromTabnas,
64
+ isJsonNumber,
65
+ utf8Bytes,
66
+ } from '@tabnas/alchemy/shared'
67
+ import type { Flow, ParserEvent, ParserSink, PassAlt, PassToken, RulePass } from '@tabnas/alchemy/shared'
68
+ import type { Rule, RuleDone, Tabnas } from '@tabnas/parser'
69
+
70
+ import { prepare } from './rule-events'
71
+ import { engineFailure } from './source'
72
+
73
+ // A tabnas parser applied to one text, as a source of its rule passes.
74
+ export class ParserEventSource {
75
+ private parser: Tabnas
76
+ private text: string
77
+ private sourceLimits: Limits = Limits.default()
78
+ private abortFlag: AbortFlag = new AbortFlag()
79
+ private sourceMetrics: Metrics = new Metrics()
80
+ private used = false
81
+
82
+ // A source with default limits, its own abort flag and fresh metrics; the
83
+ // builder methods change each.
84
+ constructor(parser: Tabnas, text: string) {
85
+ this.parser = parser
86
+ this.text = text
87
+ }
88
+
89
+ limits(limits: Limits): this {
90
+ this.sourceLimits = limits
91
+ return this
92
+ }
93
+
94
+ abort(abort: AbortFlag): this {
95
+ this.abortFlag = abort
96
+ return this
97
+ }
98
+
99
+ metrics(metrics: Metrics): this {
100
+ this.sourceMetrics = metrics
101
+ return this
102
+ }
103
+
104
+ // Parse, relaying each pass into `sink`, then `end`.
105
+ run(sink: ParserSink): Flow {
106
+ if (this.used) {
107
+ throw new Fail(
108
+ 'STREAM_REUSED',
109
+ "a ParserEventSource runs once: its parser carries the run's subscriber and budget; " +
110
+ 'make a new source, with a new parser, for another run',
111
+ )
112
+ }
113
+ this.used = true
114
+ const abort = this.abortFlag
115
+ const stop = new AbortFlag()
116
+ const relay = new Relay(sink, this.sourceLimits.max_scalar_bytes, abort, stop)
117
+ prepare(
118
+ this.parser,
119
+ () => !abort.isAborted() && !stop.isAborted(),
120
+ (rule, done) => relay.onDone(rule, done),
121
+ )
122
+ let parsed: { ok: true } | { ok: false; error: unknown }
123
+ try {
124
+ this.parser.parse(this.text)
125
+ parsed = { ok: true }
126
+ } catch (error) {
127
+ parsed = { ok: false, error }
128
+ }
129
+ try {
130
+ const status = relay.status
131
+ if ('failed' === status.type) throw status.error
132
+ if ('stopped' === status.type) return 'stop'
133
+ if (!parsed.ok) throw engineFailure(parsed.error, abort)
134
+ return relay.send({ type: 'end' })
135
+ } finally {
136
+ this.sourceMetrics.events += relay.events
137
+ }
138
+ }
139
+ }
140
+
141
+ type Status = { type: 'running' } | { type: 'stopped' } | { type: 'failed'; error: unknown }
142
+
143
+ // The subscriber's state: the sink and how the run stands.
144
+ class Relay {
145
+ status: Status = { type: 'running' }
146
+ events = 0
147
+
148
+ constructor(
149
+ readonly sink: ParserSink,
150
+ readonly maxScalarBytes: number,
151
+ readonly abort: AbortFlag,
152
+ // Set to make the parse guard cancel the parse.
153
+ readonly stop: AbortFlag,
154
+ ) {}
155
+
156
+ send(ev: ParserEvent): Flow {
157
+ this.events++
158
+ return this.sink.event(ev)
159
+ }
160
+
161
+ private fail(error: unknown): void {
162
+ this.status = { type: 'failed', error }
163
+ this.stop.abort()
164
+ }
165
+
166
+ // One pass the engine completed.
167
+ onDone(rule: Rule, done: RuleDone): void {
168
+ if (true === done.probe || 'running' !== this.status.type) return
169
+ if (this.abort.isAborted()) {
170
+ this.fail(Fail.aborted())
171
+ return
172
+ }
173
+ const open = 'o' === done.state
174
+ let ev: ParserEvent
175
+ try {
176
+ const pass = rulePass(rule, done, open, this.maxScalarBytes)
177
+ ev = open ? { type: 'open', pass } : { type: 'close', pass }
178
+ } catch (error) {
179
+ this.fail(error)
180
+ return
181
+ }
182
+ try {
183
+ if ('stop' === this.send(ev)) {
184
+ this.status = { type: 'stopped' }
185
+ this.stop.abort()
186
+ }
187
+ } catch (error) {
188
+ this.fail(error)
189
+ }
190
+ }
191
+ }
192
+
193
+ // The pass as alchemy's `RulePass`, its sizes held to `max_scalar_bytes`.
194
+ function rulePass(rule: Rule, done: RuleDone, open: boolean, max: number): RulePass {
195
+ const r: any = rule
196
+ const recorded: any[] = open ? r.o.slice(0, r.oN) : r.c.slice(0, r.cN)
197
+ const b = null == done.alt ? 0 : done.alt.b
198
+ const kept = recorded.slice(0, Math.max(0, recorded.length - b))
199
+ const tokens: PassToken[] = kept.map((t: any) => {
200
+ const src: string = 'string' === typeof t.src ? t.src : String(t.src ?? '')
201
+ held(utf8Bytes(src), "a token's source text", max)
202
+ const val = scalarOf(t.val, src)
203
+ if ('string' === val.type) held(utf8Bytes(val.value), "a token's value", max)
204
+ return { name: t.name, val, src, row: t.rI, col: t.cI }
205
+ })
206
+ let value: Datum | undefined
207
+ if (!open && undefined !== r.node && undefined !== engineScalar(r.node)) {
208
+ const first = r.o[0]
209
+ value = scalarOf(r.node, 0 < r.oN && 'string' === typeof first?.src ? first.src : undefined)
210
+ if ('string' === value.type) held(utf8Bytes(value.value), "a rule's value", max)
211
+ }
212
+ const n = Object.entries((r.n ?? {}) as Record<string, number>)
213
+ .map(([k, v]) => [k, Math.trunc(v)] as const)
214
+ .sort(([a], [b]) => byCodeUnit(a, b))
215
+ const alt: PassAlt | undefined =
216
+ null == done.alt
217
+ ? undefined
218
+ : { p: done.alt.p, r: done.alt.r, b: done.alt.b, g: [...done.alt.g].sort(byCodeUnit) }
219
+ return {
220
+ name: rule.name,
221
+ d: rule.d,
222
+ i: rule.i,
223
+ ...(undefined === alt ? {} : { alt }),
224
+ tokens,
225
+ forced: true === done.forced,
226
+ n,
227
+ ...(undefined === value ? {} : { value }),
228
+ }
229
+ }
230
+
231
+ // Strings in code-unit order, as Rust's `sort` orders ASCII names.
232
+ function byCodeUnit(a: string, b: string): number {
233
+ return a < b ? -1 : a > b ? 1 : 0
234
+ }
235
+
236
+ // An engine value as a pass carries it: `undefined` as null, and a number
237
+ // with `lexeme` when that text is a JSON number spelling it.
238
+ export function scalarOf(value: unknown, lexeme: string | undefined): Datum {
239
+ const scalar = engineScalar(value)
240
+ if (undefined !== scalar && 'number' === scalar.type) {
241
+ const keep = undefined !== lexeme && isJsonNumber(lexeme) && Number(lexeme) === scalar.value
242
+ return Datum.number(scalar.value, keep ? lexeme : undefined)
243
+ }
244
+ return fromTabnas(value)
245
+ }
246
+
247
+ // `max_scalar_bytes` held on one text a pass carries.
248
+ function held(bytes: number, what: string, max: number): void {
249
+ if (bytes > max) {
250
+ throw Fail.limit('max_scalar_bytes', max, `${what} of ${bytes} bytes is larger than ${max}`)
251
+ }
252
+ }
package/src/routers.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /* Copyright (c) 2026 tabnas, MIT License */
2
2
 
3
3
  // This package's stages as alchemy's `Routers`: what an alchemy program's
4
- // runtime builds its routes, tables, scans and guards from. Alchemy
4
+ // runtime builds its routes, tables, scans and guards from, and the native
5
+ // `transduce-tree` it runs over the parser's rule passes. Alchemy
5
6
  // declares the interface (`@tabnas/alchemy/shared`) and imports no
6
7
  // transducer; a host passes `routers` to its `compile`, with render's
7
8
  // `renderers`: `compile(src, file, { routers, renderers })`.
@@ -13,6 +14,7 @@ import {
13
14
  Flow,
14
15
  Limits,
15
16
  Metrics,
17
+ ParserSink,
16
18
  RouteSink,
17
19
  Routers,
18
20
  Scan,
@@ -20,12 +22,14 @@ import {
20
22
  TableBinding,
21
23
  TableSink,
22
24
  Transition,
25
+ TreeSpec,
23
26
  } from '@tabnas/alchemy/shared'
24
27
 
25
28
  import { Guarded } from './guard'
26
29
  import { Router } from './route'
27
30
  import { ScanEmit } from './scan'
28
31
  import { TableFromJson } from './table-from-json'
32
+ import { TransduceTree } from './transduce-tree'
29
33
 
30
34
  // Each method constructs the stage it names with the same parameters.
31
35
  export const routers: Routers = Object.freeze({
@@ -61,4 +65,14 @@ export const routers: Routers = Object.freeze({
61
65
  guarded(inner: Sink, limits: Limits, abort: AbortFlag, metrics: Metrics): Sink {
62
66
  return new Guarded(inner, limits, abort, metrics)
63
67
  },
68
+
69
+ transduceTree(spec: TreeSpec, downstream: Sink): ParserSink {
70
+ return new TransduceTree(spec, downstream)
71
+ },
72
+
73
+ // TransduceTree reads a spec's `:member` and `:text`, and holds a text to
74
+ // the spec's `maxTextBytes` as it grows.
75
+ transduceTreeReadsMemberAndText(): boolean {
76
+ return true
77
+ },
64
78
  })
@@ -0,0 +1,226 @@
1
+ /* Copyright (c) 2026 tabnas, MIT License */
2
+
3
+ // `TransduceTree`: alchemy's `transduce-tree` run natively, the parser's
4
+ // rule passes (`ParserEvents/1`) in and a tree's `JsonEvents/1` out, as a
5
+ // `TreeSpec` keyed by rule name says (rs/src/transduce_tree.rs).
6
+ //
7
+ // The library's text (`stdlib/transduce.alc`) is the specification, and
8
+ // alchemy's `TreeSpec` documents what a native must do: each pass is looked
9
+ // up by its rule's name, one lookup per pass whatever the spec's size; an
10
+ // open pass announces its key, then begins its container or yields its
11
+ // scalar from one of its tokens, then announces its member; a close pass
12
+ // yields its text, when its rule is a `:text` rule, then ends its
13
+ // container, or yields its own value; and a container whose rule is
14
+ // replaced by a rule the spec names a container is one container, so that
15
+ // close ends nothing and the replacing open begins nothing. The state is
16
+ // the count of open containers, whether a close handed its container on,
17
+ // and the text each open `:text` rule has gathered, to which every pass
18
+ // writes its tokens' source text, so the native retains the document's
19
+ // nesting and an open rule's text, and nothing else of its width or
20
+ // length, as the library's step does. alchemy-cli's differential test
21
+ // holds the two to the same bytes.
22
+ //
23
+ // A text is held to the spec's `maxTextBytes` as it grows, at the joins
24
+ // the library's `string-join` makes and in their order: a pass's tokens
25
+ // together, then the innermost text with them, and a closed text with the
26
+ // text around it, each refused past the bound with alchemy's
27
+ // `treeTextRefusal`. The routers say this native reads `:member` and
28
+ // `:text` (routers.ts), which is what lets the lowering hand it a spec
29
+ // that uses either.
30
+ //
31
+ // A pass's events are worked out before the first of them is written, as
32
+ // the library's step returns them together: a pass that fails writes none.
33
+ // The failures are the library's, with its codes and texts, and in its
34
+ // order: an open pass's key, then what it begins or yields, then its
35
+ // tokens' text, then its member; a close pass's text, then what it ends or
36
+ // yields.
37
+
38
+ import { Datum, Ev, Fail, isTreeContainer, treeTextRefusal, utf8Bytes } from '@tabnas/alchemy/shared'
39
+ import type { Flow, JsonEvent, ParserEvent, ParserSink, RulePass, Sink, TreeKey, TreeSpec } from '@tabnas/alchemy/shared'
40
+
41
+ // The library's `transduce-tree`, natively, writing to `down`.
42
+ export class TransduceTree implements ParserSink {
43
+ // Containers open.
44
+ open = 0
45
+ // A close handed its container on to the rule replacing it.
46
+ carry = false
47
+ // The text each open `:text` rule has gathered, the innermost last, and
48
+ // its length in UTF-8 bytes, which the bound is held to.
49
+ readonly texts: string[] = []
50
+ readonly bytes: number[] = []
51
+
52
+ constructor(
53
+ readonly spec: TreeSpec,
54
+ readonly down: Sink,
55
+ ) {}
56
+
57
+ event(ev: ParserEvent): Flow {
58
+ switch (ev.type) {
59
+ case 'open':
60
+ return this.openPass(ev.pass)
61
+ case 'close':
62
+ return this.closePass(ev.pass)
63
+ case 'end':
64
+ if (0 < this.open) {
65
+ throw new Fail('PROTOCOL_ORDER_ERROR', "the rule passes ended inside a container, which a tree's never do")
66
+ }
67
+ return this.down.event(Ev.end)
68
+ }
69
+ }
70
+
71
+ // The most bytes a text may hold.
72
+ private get max(): number {
73
+ return this.spec.maxTextBytes ?? Infinity
74
+ }
75
+
76
+ // The pass's tokens written to the innermost open text, if one is and the
77
+ // pass consumed any, as the library's `transduce-spell` writes them: the
78
+ // tokens' text joined, then written to the innermost, each join held to
79
+ // the bound.
80
+ private spell(pass: RulePass): void {
81
+ if (0 === this.texts.length || 0 === pass.tokens.length) return
82
+ let more = ''
83
+ let n = 0
84
+ for (const token of pass.tokens) {
85
+ more += token.src
86
+ n += utf8Bytes(token.src)
87
+ }
88
+ if (n > this.max) throw treeTextRefusal(n, this.max)
89
+ this.write(more, n)
90
+ }
91
+
92
+ // `more`, of `n` bytes, written at the end of the innermost open text, as
93
+ // the library's `transduce-write` joins the two, held to the bound.
94
+ private write(more: string, n: number): void {
95
+ const last = this.texts.length - 1
96
+ const len = this.bytes[last] + n
97
+ if (len > this.max) throw treeTextRefusal(len, this.max)
98
+ this.texts[last] += more
99
+ this.bytes[last] = len
100
+ }
101
+
102
+ private openPass(pass: RulePass): Flow {
103
+ const rule = this.spec.rules.get(pass.name)
104
+ if (undefined === rule) {
105
+ this.spell(pass)
106
+ return 'continue'
107
+ }
108
+ const key = named(rule.key, pass)
109
+ let then: JsonEvent | undefined
110
+ let begins = false
111
+ if (undefined !== rule.container) {
112
+ if (!this.carry) {
113
+ begins = true
114
+ then = 'object' === rule.container ? Ev.objectStart : Ev.arrayStart
115
+ }
116
+ } else if (undefined !== rule.scalar && 'value' !== rule.scalar) {
117
+ const token = pass.tokens[rule.scalar.token]
118
+ if (undefined !== token) then = scalar(token.val)
119
+ }
120
+ this.spell(pass)
121
+ const member = named(rule.member, pass)
122
+ if (undefined !== rule.container) {
123
+ if (begins) this.open++
124
+ else this.carry = false
125
+ }
126
+ if (true === rule.text) {
127
+ this.texts.push('')
128
+ this.bytes.push(0)
129
+ }
130
+ for (const ev of [
131
+ undefined === key ? undefined : Ev.key(key),
132
+ then,
133
+ undefined === member ? undefined : Ev.key(member),
134
+ ]) {
135
+ if (undefined !== ev && 'stop' === this.down.event(ev)) return 'stop'
136
+ }
137
+ return 'continue'
138
+ }
139
+
140
+ private closePass(pass: RulePass): Flow {
141
+ const rule = this.spec.rules.get(pass.name)
142
+ if (undefined === rule) {
143
+ this.spell(pass)
144
+ return 'continue'
145
+ }
146
+ // A `:text` rule's text closes first, and joins the one around it
147
+ // before this pass's own tokens do; it is yielded before anything
148
+ // else, once the rest of the pass is known not to fail.
149
+ let text: string | undefined
150
+ if (true === rule.text) {
151
+ text = this.texts.pop()
152
+ if (undefined === text) throw typeError('top: the vector is empty; there is no last item')
153
+ const n = this.bytes.pop() as number
154
+ if (0 < this.texts.length) this.write(text, n)
155
+ }
156
+ this.spell(pass)
157
+ let then: JsonEvent | undefined
158
+ if (undefined === rule.container) {
159
+ if ('value' === rule.scalar && undefined !== pass.value) then = scalar(pass.value)
160
+ } else if (undefined !== pass.alt && isTreeContainer(this.spec, pass.alt.r)) {
161
+ this.carry = true
162
+ } else {
163
+ if (0 === this.open) throw typeError('pop: the vector is empty; there is no last item to remove')
164
+ this.open--
165
+ this.carry = false
166
+ then = 'object' === rule.container ? Ev.objectEnd : Ev.arrayEnd
167
+ }
168
+ if (undefined !== text && 'stop' === this.down.event(Ev.string(text))) return 'stop'
169
+ return undefined === then ? 'continue' : this.down.event(then)
170
+ }
171
+ }
172
+
173
+ // The member name a field of a rule's entry names for a pass: the string,
174
+ // or the `:val` of the pass's token at that index, nothing when the pass
175
+ // consumed no such token; the library's `key` refuses a name that is not a
176
+ // string.
177
+ function named(field: TreeKey | undefined, pass: RulePass): string | undefined {
178
+ if (undefined === field) return undefined
179
+ if ('name' in field) return field.name
180
+ const token = pass.tokens[field.token]
181
+ if (undefined === token) return undefined
182
+ if ('string' !== token.val.type) {
183
+ throw typeError(`key: the name must be a string, not ${kind(token.val)}`)
184
+ }
185
+ return token.val.value
186
+ }
187
+
188
+ // A value as the library's `scalar` takes it, or its refusal.
189
+ function scalar(value: Datum): JsonEvent {
190
+ switch (value.type) {
191
+ case 'null':
192
+ return Ev.null
193
+ case 'bool':
194
+ return Ev.bool(value.value)
195
+ case 'number':
196
+ return Ev.number(value.value, value.lexeme)
197
+ case 'string':
198
+ return Ev.string(value.value)
199
+ default:
200
+ throw typeError(`scalar: the value must be null, a boolean, a number or a string, not ${kind(value)}`)
201
+ }
202
+ }
203
+
204
+ // What a value is, in the runtime's words.
205
+ function kind(value: Datum): string {
206
+ switch (value.type) {
207
+ case 'null':
208
+ return 'null'
209
+ case 'bool':
210
+ return 'a boolean'
211
+ case 'number':
212
+ return 'a number'
213
+ case 'string':
214
+ return 'a string'
215
+ case 'array':
216
+ return 'a vector'
217
+ case 'object':
218
+ return 'a record'
219
+ }
220
+ }
221
+
222
+ // The runtime's type error: `DSL_TYPE_ERROR`, the `type_mismatch` word
223
+ // first.
224
+ function typeError(message: string): Fail {
225
+ return new Fail('DSL_TYPE_ERROR', `type_mismatch: ${message}`)
226
+ }
package/src/transduce.ts CHANGED
@@ -9,7 +9,11 @@
9
9
  // - the source protocol `JsonEvent` (`JsonEvents/1`) and the push boundary
10
10
  // `Sink`;
11
11
  // - sources that produce it from a parsed value, from a live tabnas parse
12
- // through the engine's rule events, or from line-delimited input;
12
+ // through the engine's rule events, or from line-delimited input, and
13
+ // the source of the parser's rule passes themselves (`ParserEventSource`,
14
+ // `ParserEvents/1`), what a transducer part reads;
15
+ // - `TransduceTree`, alchemy's `transduce-tree` run natively over those
16
+ // passes;
13
17
  // - `Selector`s and the matcher and router that recognize selected scopes
14
18
  // in one pass and materialize them under a byte limit;
15
19
  // - the table protocol `TableEvent` (`TableRows/1`) and the standard
@@ -28,7 +32,7 @@
28
32
  // stages as alchemy's `Routers`, for a host to pass to alchemy's `compile`.
29
33
 
30
34
  // This package's version, as package.json declares it.
31
- export const VERSION = '0.2.8'
35
+ export const VERSION = '0.2.10'
32
36
 
33
37
  export {
34
38
  Datum,
@@ -72,6 +76,21 @@ export { Prune, SourceMode, ValueSource, engineFailure, walkValue } from './sour
72
76
  export type { Source } from './source'
73
77
  export { Guarded } from './guard'
74
78
  export { ParserSource } from './parser-source'
79
+ export { ParserEventSource } from './parser-event-source'
80
+ export { TransduceTree } from './transduce-tree'
81
+ export { isTreeContainer, parserEventFromJSON, parserEventToJSON, treeTextRefusal } from '@tabnas/alchemy/shared'
82
+ export type {
83
+ ParserEvent,
84
+ ParserSink,
85
+ PassAlt,
86
+ PassToken,
87
+ RulePass,
88
+ TreeContainer,
89
+ TreeKey,
90
+ TreeRule,
91
+ TreeScalar,
92
+ TreeSpec,
93
+ } from '@tabnas/alchemy/shared'
75
94
  export { DEFAULT_CHUNK_BYTES, LineFormat, LinesSource } from './lines'
76
95
  export type { LinesChunk, LinesInput, LinesWriter } from './lines'
77
96
  export { INCREMENTAL, capability, isIncremental } from './capability'