aontu 0.55.0 → 0.57.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.
Files changed (128) hide show
  1. package/README.md +3 -3
  2. package/dist/agentsmd.d.ts +1 -0
  3. package/dist/agentsmd.js +10 -3
  4. package/dist/agentsmd.js.map +1 -1
  5. package/dist/aontu.d.ts +4 -2
  6. package/dist/aontu.js +5 -2
  7. package/dist/aontu.js.map +1 -1
  8. package/dist/cli.d.ts +10 -3
  9. package/dist/cli.js +311 -53
  10. package/dist/cli.js.map +1 -1
  11. package/dist/diff.d.ts +1 -0
  12. package/dist/diff.js +3 -2
  13. package/dist/diff.js.map +1 -1
  14. package/dist/escape.d.ts +5 -0
  15. package/dist/escape.js +455 -0
  16. package/dist/escape.js.map +1 -0
  17. package/dist/format.d.ts +26 -0
  18. package/dist/format.js +1489 -0
  19. package/dist/format.js.map +1 -0
  20. package/dist/hints.js +39 -1
  21. package/dist/hints.js.map +1 -1
  22. package/dist/jsonschema.d.ts +1 -0
  23. package/dist/jsonschema.js +3 -2
  24. package/dist/jsonschema.js.map +1 -1
  25. package/dist/lang.js +84 -6
  26. package/dist/lang.js.map +1 -1
  27. package/dist/lsp.d.ts +2 -1
  28. package/dist/lsp.js +4 -4
  29. package/dist/lsp.js.map +1 -1
  30. package/dist/mcp-server.js +2 -2
  31. package/dist/mcp-server.js.map +1 -1
  32. package/dist/mcp.js +6 -4
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/mod-tool.js +8 -7
  35. package/dist/mod-tool.js.map +1 -1
  36. package/dist/mod.js +6 -6
  37. package/dist/mod.js.map +1 -1
  38. package/dist/patch.d.ts +1 -0
  39. package/dist/patch.js +1 -0
  40. package/dist/patch.js.map +1 -1
  41. package/dist/query.d.ts +1 -0
  42. package/dist/query.js +4 -3
  43. package/dist/query.js.map +1 -1
  44. package/dist/reach.d.ts +1 -0
  45. package/dist/reach.js +3 -2
  46. package/dist/reach.js.map +1 -1
  47. package/dist/relation.d.ts +1 -0
  48. package/dist/relation.js +3 -2
  49. package/dist/relation.js.map +1 -1
  50. package/dist/sigdecl.js +1 -1
  51. package/dist/sigdecl.js.map +1 -1
  52. package/dist/std.js +2 -1
  53. package/dist/std.js.map +1 -1
  54. package/dist/subsume.d.ts +1 -0
  55. package/dist/subsume.js +3 -2
  56. package/dist/subsume.js.map +1 -1
  57. package/dist/trim.d.ts +1 -0
  58. package/dist/trim.js +3 -2
  59. package/dist/trim.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/type.d.ts +1 -0
  62. package/dist/type.js.map +1 -1
  63. package/dist/utility.d.ts +8 -2
  64. package/dist/utility.js +9 -1
  65. package/dist/utility.js.map +1 -1
  66. package/dist/val/EachFuncVal.js +1 -2
  67. package/dist/val/EachFuncVal.js.map +1 -1
  68. package/dist/val/EmitFuncVal.d.ts +23 -0
  69. package/dist/val/EmitFuncVal.js +261 -0
  70. package/dist/val/EmitFuncVal.js.map +1 -0
  71. package/dist/val/FilterFuncVal.js +1 -2
  72. package/dist/val/FilterFuncVal.js.map +1 -1
  73. package/dist/val/FuncBaseVal.d.ts +1 -0
  74. package/dist/val/FuncBaseVal.js +16 -0
  75. package/dist/val/FuncBaseVal.js.map +1 -1
  76. package/dist/val/PackFuncVal.js +1 -2
  77. package/dist/val/PackFuncVal.js.map +1 -1
  78. package/dist/val/PlaceVal.d.ts +3 -1
  79. package/dist/val/PlaceVal.js +8 -6
  80. package/dist/val/PlaceVal.js.map +1 -1
  81. package/dist/val/StrFuncVal.d.ts +32 -0
  82. package/dist/val/StrFuncVal.js +292 -0
  83. package/dist/val/StrFuncVal.js.map +1 -0
  84. package/dist/vet.d.ts +1 -0
  85. package/dist/vet.js +1 -1
  86. package/dist/vet.js.map +1 -1
  87. package/dist/view.d.ts +2 -1
  88. package/dist/view.js +379 -8
  89. package/dist/view.js.map +1 -1
  90. package/grammar/aontu.abnf +156 -0
  91. package/grammar/aontu.gbnf +4 -3
  92. package/grammar/aontu.lark +4 -3
  93. package/grammar/aontu.tmLanguage.json +1 -1
  94. package/package.json +2 -1
  95. package/skill/grammar-card.md +5 -2
  96. package/src/agentsmd.ts +15 -3
  97. package/src/aontu.ts +8 -1
  98. package/src/cli.ts +365 -58
  99. package/src/diff.ts +7 -2
  100. package/src/escape.ts +371 -0
  101. package/src/format.ts +1728 -0
  102. package/src/hints.ts +53 -1
  103. package/src/jsonschema.ts +7 -1
  104. package/src/lang.ts +93 -6
  105. package/src/lsp.ts +8 -6
  106. package/src/mcp-server.ts +3 -2
  107. package/src/mcp.ts +6 -4
  108. package/src/mod-tool.ts +9 -8
  109. package/src/mod.ts +6 -6
  110. package/src/patch.ts +7 -0
  111. package/src/query.ts +8 -4
  112. package/src/reach.ts +7 -2
  113. package/src/relation.ts +7 -2
  114. package/src/sigdecl.ts +1 -1
  115. package/src/std.ts +2 -1
  116. package/src/subsume.ts +7 -2
  117. package/src/trim.ts +7 -2
  118. package/src/type.ts +8 -0
  119. package/src/utility.ts +27 -2
  120. package/src/val/EachFuncVal.ts +1 -3
  121. package/src/val/EmitFuncVal.ts +401 -0
  122. package/src/val/FilterFuncVal.ts +1 -3
  123. package/src/val/FuncBaseVal.ts +18 -0
  124. package/src/val/PackFuncVal.ts +1 -3
  125. package/src/val/PlaceVal.ts +8 -6
  126. package/src/val/StrFuncVal.ts +334 -0
  127. package/src/vet.ts +9 -3
  128. package/src/view.ts +442 -9
package/src/std.ts CHANGED
@@ -87,7 +87,8 @@ view: {
87
87
  # it belongs; everything else narrows the drawing, and each option
88
88
  # belongs to the kinds that read it.
89
89
  Figure: type({
90
- kind: doc | tree | matrix | graph | layer | sets | layers | ladder
90
+ kind: doc | lattice | tree | matrix | graph | layer | sets | layers
91
+ | ladder
91
92
  out: string
92
93
 
93
94
  # Every kind.
package/src/subsume.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+ import { includeOpts } from './utility'
2
3
 
3
4
  // Subsumption as a first-class query (G3 phases 1-2,
4
5
  // docs/capability-review/g3-subsumption-evolution.md): does the GENERAL
@@ -55,6 +56,11 @@ export type SubsumeOptions = {
55
56
  // docs/trust.md). vet's precedent: the verb passes the profile the
56
57
  // caller asked for, and an absent option means today's default.
57
58
  trust?: TrustOptions
59
+
60
+ // Extensions additionally read as text (the CLI's `--text-ext`).
61
+ // Rides beside `trust` because it is the other half of what an
62
+ // include may read.
63
+ textExt?: string[]
58
64
  }
59
65
 
60
66
  export type SubsumeReport = {
@@ -699,8 +705,7 @@ export function subsume(
699
705
  }
700
706
 
701
707
  const load = (src: string, path?: string): any => {
702
- const aontu = new Aontu(
703
- null == options.trust ? undefined : { trust: options.trust })
708
+ const aontu = new Aontu(includeOpts(options))
704
709
  const ctx = aontu.ctx({ collect: true })
705
710
  const v: any = aontu.unify(
706
711
  src, null == path ? undefined : { path }, ctx)
package/src/trim.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+ import { includeOpts } from './utility'
2
3
 
3
4
  // The trim reporter (G3 phase 6,
4
5
  // docs/capability-review/g3-subsumption-evolution.md): report REDUNDANT
@@ -50,6 +51,11 @@ export type TrimOptions = {
50
51
  // docs/trust.md). vet's precedent: the verb passes the profile the
51
52
  // caller asked for, and an absent option means today's default.
52
53
  trust?: TrustOptions
54
+
55
+ // Extensions additionally read as text (the CLI's `--text-ext`).
56
+ // Rides beside `trust` because it is the other half of what an
57
+ // include may read.
58
+ textExt?: string[]
53
59
  }
54
60
 
55
61
 
@@ -119,8 +125,7 @@ export function deleteAt(root: any, path: string[]): boolean {
119
125
  export function evalCanon(
120
126
  src: string, opts: TrimOptions, delPath?: string[],
121
127
  sink?: { ctx?: any, failed?: any }): string | undefined {
122
- const aontu = new Aontu(
123
- null == opts.trust ? undefined : { trust: opts.trust })
128
+ const aontu = new Aontu(includeOpts(opts))
124
129
  const ctx = aontu.ctx({ collect: true })
125
130
  const parseOpts = null == opts.path ? undefined : { path: opts.path }
126
131
  // WHY the run failed, for the one caller that reports it. The
package/src/type.ts CHANGED
@@ -69,6 +69,14 @@ type AontuOptions = {
69
69
  explain?: any[]
70
70
  trust?: TrustOptions // Trust profile (G5, docs/trust.md)
71
71
 
72
+ // EXTENSIONS ADDITIONALLY READ AS TEXT, without their dots. `.txt`
73
+ // is read as text with no option at all; this widens that set for a
74
+ // host that keeps its prose in `.md`, its queries in `.sql`, or its
75
+ // templates under some name only it knows. The file's bytes become
76
+ // one string scalar -- no parser is chosen for it, which is what
77
+ // makes widening the set safe to offer.
78
+ textExt?: string[]
79
+
72
80
  // The staged-flip warning window (G5 phase 6, CLI only): under the
73
81
  // default 'system' capability the CLI supplies these, and the
74
82
  // resolver calls trustWarn for every resolution that escapes
package/src/utility.ts CHANGED
@@ -2,7 +2,29 @@
2
2
 
3
3
 
4
4
 
5
- import type { Val } from './type'
5
+ import type { AontuOptions, TrustOptions, Val } from './type'
6
+
7
+
8
+ // THE INCLUDE OPTIONS AN ENGINE HANDS ITS Aontu INSTANCE. Every verb
9
+ // engine builds one, and until there were two such options each did it
10
+ // inline -- `null == options.trust ? undefined : { trust: options.trust }`,
11
+ // written out twelve times. That is fine while there is one option and
12
+ // a latent bug the moment there are two: `textExt` had to reach the
13
+ // same twelve places, and the one it missed refused a `.md` include
14
+ // under a flag the bare command honoured. One function now, so a third
15
+ // include option is threaded once.
16
+ type IncludeOptions = {
17
+ trust?: TrustOptions
18
+ textExt?: string[]
19
+ }
20
+
21
+ function includeOpts(options: IncludeOptions): Partial<AontuOptions> {
22
+ return {
23
+ ...(null == options.trust ? {} : { trust: options.trust }),
24
+ ...(null == options.textExt || 0 === options.textExt.length
25
+ ? {} : { textExt: options.textExt }),
26
+ }
27
+ }
6
28
 
7
29
 
8
30
  // Default walk() depth limit. High enough that real configs are never
@@ -262,10 +284,13 @@ function items(o: any) {
262
284
  else {
263
285
  return []
264
286
  }
265
- } /* node:coverage ignore next 18 */
287
+ } /* node:coverage ignore next 20 */
288
+
266
289
 
290
+ export type { IncludeOptions }
267
291
 
268
292
  export {
293
+ includeOpts,
269
294
  items,
270
295
  propagateMarks,
271
296
  canonRiders,
@@ -86,9 +86,7 @@ class EachFuncVal extends FuncBaseVal {
86
86
  // ONE argument is driven: the data. The template is not (see
87
87
  // prepare above), and driveStagedArgs answers whether the data has
88
88
  // settled -- the other half of "ready to fire".
89
- const ready = this.driveStagedArgs(ctx, 1)
90
-
91
- if (!ready || !ctx.settle) {
89
+ if (!this.stagedReady(peer, ctx, 1)) {
92
90
  return this.residuate(peer, ctx)
93
91
  }
94
92
 
@@ -0,0 +1,401 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // TRANSFORMATION: `emit(select, table)` (G9 phase 6,
4
+ // docs/design/EMIT.0.md). Apply-templates, with the dispatch in the
5
+ // engine and none of it in user space.
6
+ //
7
+ // emit($.services, [
8
+ // {match: {kind: sqs}, body: [`listen(` + .pin + `)`]}
9
+ // {match: {kind: http}, body: [`serve(` + .path + `)`]}
10
+ // ])
11
+ //
12
+ // For every node of `select`, in order, the first template whose
13
+ // `match` the node unifies with is taken and its `body` instantiated
14
+ // AGAINST THAT NODE. The result is one flat list.
15
+ //
16
+ // WHY THIS CANNOT BE WRITTEN IN THE DOCUMENT (EMIT.0.md, "What it
17
+ // takes"). A rule table held as a value cannot be dispatched against:
18
+ // a body referenced by path resolves its relative references AT THE
19
+ // DEFINITION SITE, and the positional resolution that does exist is a
20
+ // dot COUNT that does not survive being consumed by a second dispatch.
21
+ // The builtin instantiates a body at the node it matched -- a NAMED
22
+ // origin -- which is the whole capability.
23
+ //
24
+ // THE BODY'S RELATIVE REFERENCES ARE BOUND TO THE NODE. `.pin` inside
25
+ // a body is the matched node's `pin`, and `_` is the node itself, as
26
+ // it is in every other generator. The binding is done HERE rather than
27
+ // left to path resolution: a relative path is a COUNT taken wherever
28
+ // the value comes to rest, and the nodes of a computed selection
29
+ // (`filter(...)`) come to rest nowhere -- there is no position for a
30
+ // count to be taken from. An ABSOLUTE reference is untouched and still
31
+ // reads the document root.
32
+ //
33
+ // The binding stops at a nested generator's own binding argument
34
+ // (boundArgStart): a rule table nested in a body is the INNER emit's
35
+ // to bind, so `.x` inside it is the inner node. What crosses the
36
+ // boundary is the nested call's SELECTOR, which is argument 0 and is
37
+ // bound here -- the selector is the channel.
38
+ //
39
+ // THE RESULT IS FLAT, and that is a constraint rather than a
40
+ // convenience: the fragment algebra is flat because the nested
41
+ // spelling refuses even a valid instance in both ports, so a dispatch
42
+ // returning a tree would undo that ruling. A body element that is
43
+ // itself a list SPLICES, which is what makes a nested emit compose.
44
+ //
45
+ // NO MATCH IS AN ERROR (`emit_none`). XSLT's built-in rule copies an
46
+ // unhandled node's string value into the result, which for code output
47
+ // means model data landing silently in the middle of a source file.
48
+ // That is the single worst default in the prior art and it is refused.
49
+ // An EMPTY selection, by contrast, emits nothing -- which is the whole
50
+ // conditional mechanism, and why no `when` directive exists.
51
+ //
52
+ // A NAMED TABLE IS A PLACEHELD `emit` (`%wire: emit(_, T)`). A table
53
+ // written at a document position is DRIVEN there, so its bodies'
54
+ // relative references resolve against wherever it sits and miss;
55
+ // nothing in the language holds a value unevaluated at such a
56
+ // position, and what does hold one is a CALL's template argument.
57
+ // `emit(.listen, %wire)` follows the reference and reads the table out
58
+ // of the placeheld call; `.listen & %wire` fills the hole. Both are
59
+ // the same dispatch, and it is what lets a rule set name ITSELF.
60
+ //
61
+ // TERMINATION IS THE SELECTION'S. Unlike `pack` and `each`, this one
62
+ // recurses -- a nested model walked into nested output is the
63
+ // capability the rule layer exists to add -- so the bound is not "it
64
+ // cannot call itself" but "each dispatch descends into a finite bag
65
+ // that already exists, and a selection that empties emits nothing". A
66
+ // rule set that walks into itself WITHOUT descending is charged to the
67
+ // depth budget and refused as `unify_cycle`, like any other runaway
68
+ // descent.
69
+
70
+ import type {
71
+ Val,
72
+ ValSpec,
73
+ } from '../type'
74
+
75
+ import {
76
+ AontuContext,
77
+ } from '../ctx'
78
+
79
+ import { unite } from '../unify'
80
+ import { makeNilErr } from '../err'
81
+ import { top } from './top'
82
+ import { ListVal } from './ListVal'
83
+ import { FuncBaseVal, trialUnify } from './FuncBaseVal'
84
+ import { repathInstance } from './Val'
85
+ import { boundArgStart, fillPlace, rebuild } from './PlaceVal'
86
+ import { dataValues } from './EachFuncVal'
87
+
88
+
89
+ // One entry of the rule table: the pattern to try and the body to
90
+ // instantiate. A table is a LIST of these; a single template map is
91
+ // that list of one, told apart by kind, exactly as match() tells its
92
+ // patterns apart.
93
+ type Template = { match: Val, body: Val }
94
+
95
+
96
+ // Read the table. A map is one template; a list is many. The shape is
97
+ // checked here rather than at the call, because a table is ordinary
98
+ // data and may be computed.
99
+ function tableTemplates(table: Val | undefined): Template[] | string {
100
+ const t: any = table
101
+
102
+ // A NAMED TABLE IS A PLACEHELD `emit`, and its table is the table.
103
+ // A table written at a document position is DRIVEN there -- a body's
104
+ // relative references resolve against wherever it sits and miss --
105
+ // so the position that holds one unevaluated is the one position the
106
+ // language already never drives: a call's template argument.
107
+ // `%wire: emit(_, [ … ])` is that position with the selection left
108
+ // open, and it reads as what it is, an apply-templates waiting for
109
+ // its nodes: `emit(.listen, %wire)` passes them, `.listen & %wire`
110
+ // fills the hole, and both are the same dispatch.
111
+ if (true === t?.isEmitFunc) {
112
+ return tableTemplates(t.peg[1])
113
+ }
114
+
115
+ if (true === t?.isMap) {
116
+ const one = oneTemplate(t)
117
+ return 'string' === typeof one ? one : [one]
118
+ }
119
+
120
+ if (true === t?.isList) {
121
+ const out: Template[] = []
122
+ for (const el of t.peg as Val[]) {
123
+ const e: any = el
124
+ if (true !== e?.isMap) {
125
+ return 'emit_template'
126
+ }
127
+ const one = oneTemplate(e)
128
+ if ('string' === typeof one) {
129
+ return one
130
+ }
131
+ out.push(one)
132
+ }
133
+ return out
134
+ }
135
+
136
+ return 'emit_table'
137
+ }
138
+
139
+
140
+ function oneTemplate(m: any): Template | string {
141
+ const match: Val = m.peg.match
142
+ const body: any = m.peg.body
143
+ // Both keys are required: a template with no pattern would match
144
+ // everything by accident, and one with no body would emit nothing
145
+ // while claiming a node.
146
+ if (null == match || null == body) {
147
+ return 'emit_template'
148
+ }
149
+ if (true !== body.isList) {
150
+ return 'emit_body'
151
+ }
152
+ return { match, body }
153
+ }
154
+
155
+
156
+ // The reference a body named that the node could not answer, kept by
157
+ // the walk so `resolve` can report the first one against the node it
158
+ // was tried on.
159
+ type BindFail = { ref?: string }
160
+
161
+
162
+ // Every relative reference in `v` replaced by the field of `node` it
163
+ // names. Answers `v` unchanged when it holds none, so a body with no
164
+ // substitutions is never needlessly rebuilt -- the identity test
165
+ // `fillPlace` already relies on.
166
+ function bindNode(v: any, node: Val, ctx: AontuContext, fail: BindFail): Val {
167
+ if (true === v?.isRef && true !== v.absolute) {
168
+ const found = nodeField(v, node)
169
+ if (undefined === found) {
170
+ fail.ref = undefined === fail.ref ? v.canon : fail.ref
171
+ return v
172
+ }
173
+ return found.clone(ctx)
174
+ }
175
+
176
+ const peg: any = v?.peg
177
+ const bound = boundArgStart(v)
178
+
179
+ if (Array.isArray(peg)) {
180
+ let changed = false
181
+ const out = peg.map((c: any, cI: number) => {
182
+ if (true !== c?.isVal || bound <= cI) {
183
+ return c
184
+ }
185
+ const b = bindNode(c, node, ctx, fail)
186
+ changed = changed || b !== c
187
+ return b
188
+ })
189
+ return changed ? rebuild(v, out, ctx) : v
190
+ }
191
+
192
+ if (true === peg?.isVal) {
193
+ const b = bindNode(peg, node, ctx, fail)
194
+ return b === peg ? v : rebuild(v, b, ctx)
195
+ }
196
+
197
+ if (null != peg && 'object' === typeof peg) {
198
+ let changed = false
199
+ const out: Record<string, Val> = {}
200
+ for (const k of Object.keys(peg)) {
201
+ const c = peg[k]
202
+ const b = true === c?.isVal ? bindNode(c, node, ctx, fail) : c
203
+ changed = changed || b !== c
204
+ out[k] = b
205
+ }
206
+ return changed ? rebuild(v, out, ctx) : v
207
+ }
208
+
209
+ return v
210
+ }
211
+
212
+
213
+ // The field of `node` a reference names, or undefined when it names
214
+ // none. Only a chain of plain NAMES is a field: a parent step has no
215
+ // answer at a node that is an origin rather than a position, and a
216
+ // variable segment is not a name until something resolves it -- both
217
+ // are refused here rather than left to resolve somewhere else, which
218
+ // is the failure mode the binding exists to remove.
219
+ function nodeField(ref: any, node: Val): Val | undefined {
220
+ let cur: any = node
221
+ for (const seg of ref.peg as any[]) {
222
+ if ('string' !== typeof seg || '.' === seg) {
223
+ return undefined
224
+ }
225
+ const peg: any = cur?.peg
226
+ if (true !== cur?.isBag || null == peg) {
227
+ return undefined
228
+ }
229
+ cur = true === cur.isList ? peg[Number(seg)] : peg[seg]
230
+ if (true !== cur?.isVal) {
231
+ return undefined
232
+ }
233
+ }
234
+ return cur
235
+ }
236
+
237
+
238
+ // A body element that is itself a list splices, which is what makes a
239
+ // nested emit compose into one flat sequence.
240
+ function splice(v: Val, out: Val[]): void {
241
+ if (true === (v as any)?.isList) {
242
+ for (const el of (v as any).peg as Val[]) {
243
+ splice(el, out)
244
+ }
245
+ return
246
+ }
247
+ out.push(v)
248
+ }
249
+
250
+
251
+ class EmitFuncVal extends FuncBaseVal {
252
+ isEmitFunc = true
253
+
254
+ // THE STAGING RULE (G8 phase 0, see AontuContext.settle). The
255
+ // selection is not settled merely by being `done` once -- a sibling
256
+ // conjunct, an include or a spread can still merge nodes into it,
257
+ // and pieces emitted from the half-merged bag would be missing.
258
+ staged = true
259
+
260
+ constructor(
261
+ spec: ValSpec,
262
+ ctx?: AontuContext
263
+ ) {
264
+ super(spec, ctx)
265
+ }
266
+
267
+
268
+ funcname() {
269
+ return 'emit'
270
+ }
271
+
272
+
273
+ // NEITHER ARGUMENT IS DRIVEN BY THE BASE. The selection is driven by
274
+ // hand below; the TABLE is not driven at all, because a body is a
275
+ // template and driving it would resolve its references at the call
276
+ // site -- the one position a body is never used at.
277
+ prepare(_ctx: AontuContext, _args: Val[]) {
278
+ return null
279
+ }
280
+
281
+
282
+ unify(peer: Val, ctx: AontuContext): Val {
283
+ // ONE argument is driven: the selection. The table holds bodies,
284
+ // which are templates (see prepare above).
285
+ if (!this.stagedReady(peer, ctx, 1)) {
286
+ return this.residuate(peer, ctx)
287
+ }
288
+
289
+ return super.unify(peer, ctx)
290
+ }
291
+
292
+
293
+ resolve(ctx: AontuContext, args: Val[]) {
294
+ const nodes = dataValues(args?.[0])
295
+ if ('string' === typeof nodes) {
296
+ // dataValues names the each_data code; emit answers for itself.
297
+ return makeNilErr(ctx, 'emit_data', this)
298
+ }
299
+
300
+ // A NAMED TABLE IS REACHED BY REFERENCE, and the reference -- not
301
+ // the table -- is what is followed. Followed HERE rather than in
302
+ // the staged drive, which waits for a SETTLED target: a table is a
303
+ // template, a template holding a hole never settles, and waiting
304
+ // for one would mean the dispatch never fires.
305
+ let table: any = args?.[1]
306
+ if (true === table?.isRef) {
307
+ table = table.unify(top(), ctx)
308
+ }
309
+
310
+ const templates = tableTemplates(table)
311
+ if ('string' === typeof templates) {
312
+ return makeNilErr(ctx, templates, this)
313
+ }
314
+
315
+ const peg: Val[] = []
316
+
317
+ for (const node of nodes) {
318
+ const tmpl = this.dispatch(ctx, node, templates)
319
+ if ('string' === typeof tmpl) {
320
+ return makeNilErr(ctx, 'emit_none', this, undefined, 'resolve', {
321
+ value: node.canon,
322
+ tried: tmpl,
323
+ })
324
+ }
325
+ const fail: BindFail = {}
326
+ this.instantiate(ctx, node, tmpl, peg, fail)
327
+ if (undefined !== fail.ref) {
328
+ return makeNilErr(ctx, 'emit_ref', this, undefined, 'resolve', {
329
+ ref: fail.ref,
330
+ value: node.canon,
331
+ })
332
+ }
333
+ }
334
+
335
+ // THE PIECES ARE PATHED WHERE THEY LAND, once the splicing has
336
+ // settled how many there are. A piece keeps no trace of the body
337
+ // it was written in: the body is a template, and a template's
338
+ // parse position is the one place it is never used.
339
+ for (let i = 0; i < peg.length; i++) {
340
+ repathInstance(peg[i], [...ctx.path, String(i)])
341
+ }
342
+
343
+ return new ListVal({ peg }, ctx)
344
+ }
345
+
346
+
347
+ // First match wins, in table order, by unifiability -- the same
348
+ // question `match` and `filter` ask, answered the same way. Returns
349
+ // the patterns tried when nothing matched, for the located error.
350
+ dispatch(ctx: AontuContext, node: Val, templates: Template[]): Template | string {
351
+ const tried: string[] = []
352
+ for (const tmpl of templates) {
353
+ tried.push(tmpl.match.canon)
354
+ // The trial is against CLONES: `unite` refines a bag in place
355
+ // against a TOP peer, and a pattern that failed must be untouched
356
+ // for the next node.
357
+ if (undefined !== trialUnify(ctx, node.clone(ctx), tmpl.match.clone(ctx))) {
358
+ return tmpl
359
+ }
360
+ }
361
+ return tried.join(' ')
362
+ }
363
+
364
+
365
+ // Instantiate one body at the node and SPLICE its pieces into the
366
+ // output. A full instance to the leaves (`dup`, ADR-005), because a
367
+ // bare clone shares the inner structure of any call in the body and
368
+ // the first node's resolution would answer for every node; then the
369
+ // two bindings, relative references and the hole, both to the node.
370
+ instantiate(ctx: AontuContext, node: Val, tmpl: Template,
371
+ out: Val[], fail: BindFail): void {
372
+ const elems: Val[] = (tmpl.body as any).peg
373
+
374
+ for (let i = 0; i < elems.length; i++) {
375
+ const elctx = ctx.descend(String(out.length))
376
+ const inst = elems[i].clone(elctx, { dup: true })
377
+ let piece = fillPlace(bindNode(inst, node, elctx, fail), node, elctx)
378
+
379
+ // A NESTED DISPATCH IS DRIVEN HERE, not left for the next pass.
380
+ // Its selection is bound and the model has settled, so it has
381
+ // everything it needs -- and it must answer NOW, because what
382
+ // makes the result flat is splicing its pieces into this one.
383
+ // Left standing, a nested `emit` resolved a pass later, as a
384
+ // list INSIDE the list, and the fragment algebra is flat.
385
+ // Through `unite` rather than by hand: a rule set that walks
386
+ // into itself for ever is charged to the depth budget and
387
+ // refused as `unify_cycle`, like any other runaway descent.
388
+ if (!piece.done) {
389
+ piece = unite(elctx, piece, top(), 'emit')
390
+ }
391
+
392
+ splice(piece, out)
393
+ }
394
+ }
395
+
396
+ } /* node:coverage ignore next 6 */
397
+
398
+
399
+ export {
400
+ EmitFuncVal,
401
+ }
@@ -83,9 +83,7 @@ class FilterFuncVal extends FuncBaseVal {
83
83
 
84
84
 
85
85
  unify(peer: Val, ctx: AontuContext): Val {
86
- const ready = this.driveStagedArgs(ctx, 1)
87
-
88
- if (!ready || !ctx.settle) {
86
+ if (!this.stagedReady(peer, ctx, 1)) {
89
87
  return this.residuate(peer, ctx)
90
88
  }
91
89
 
@@ -139,6 +139,24 @@ class FuncBaseVal extends FeatureVal {
139
139
  }
140
140
 
141
141
 
142
+ // THE STAGED READINESS GATE, stated once for the generators that
143
+ // drive their own data argument. Such a func fires when the model
144
+ // has settled AND that argument is done -- EXCEPT when the argument
145
+ // is a HOLE and a PEER has arrived to fill it. `_` is never done: it
146
+ // is FILLED, and the peer is what fills it, so gating on doneness
147
+ // alone held every placeheld generator residual for ever and the
148
+ // fill in `unify` below was never reached (`["a"] & pack(_, {x:1})`
149
+ // was `*_no_gen`, while the unstaged `"hello" & upper(_)` filled as
150
+ // documented -- an undriven `_` makes an ordinary call's `pegdone`
151
+ // false, and a generator's prepare() forces it true). Against TOP
152
+ // there is nothing to fill with, so a placeheld generator waits
153
+ // exactly as the hole itself does.
154
+ stagedReady(peer: Val, ctx: AontuContext, count: number): boolean {
155
+ const ready = this.driveStagedArgs(ctx, count)
156
+ return (ready || (!peer.isTop && hasPlace(this))) && true === ctx.settle
157
+ }
158
+
159
+
142
160
  // THE PER-DESTINATION INSTANTIATION RULE (ADR-005). The default
143
161
  // clone shares the argument array AND the argument Vals — pinned
144
162
  // sharing for the move()/copy() ghost artifacts (test/spec/func.tsv,
@@ -114,9 +114,7 @@ class PackFuncVal extends FuncBaseVal {
114
114
  // ONE argument is driven: the data. The template is not (see
115
115
  // prepare above), and driveStagedArgs answers whether the data has
116
116
  // settled -- the other half of "ready to fire".
117
- const ready = this.driveStagedArgs(ctx, 1)
118
-
119
- if (!ready || !ctx.settle) {
117
+ if (!this.stagedReady(peer, ctx, 1)) {
120
118
  return this.residuate(peer, ctx)
121
119
  }
122
120
 
@@ -73,10 +73,10 @@ class PlaceVal extends ValBase {
73
73
 
74
74
 
75
75
  // A HOLE BELONGS TO ITS NEAREST ENCLOSING GENERATOR. A `_` inside a
76
- // generator's template (pack/each, arg 1) or condition (filter, arg 1)
77
- // is that generator's to bind — "_ is the source child" — so neither
78
- // the hole test nor the fill walk may cross into those arguments from
79
- // outside. Before this boundary, `close(pack(d, _ & t))` reported a
76
+ // generator's template (pack/each, arg 1), condition (filter, arg 1)
77
+ // or rule table (emit, arg 1) is that generator's to bind — "_ is the
78
+ // source child" — so neither the hole test nor the fill walk may cross
79
+ // into those arguments from outside. Before this boundary, `close(pack(d, _ & t))` reported a
80
80
  // hole to the OUTER call, so an ordinary overlay statement was
81
81
  // absorbed into the template instead of merging with the generated
82
82
  // child (use-cases/BUGS.md §10), and an outer pack's fill pass
@@ -85,7 +85,7 @@ class PlaceVal extends ValBase {
85
85
  // so it stays visible: a hole there is an outer hole as before.
86
86
  function boundArgStart(v: any): number {
87
87
  return true === v.isPackFunc || true === v.isEachFunc ||
88
- true === v.isFilterFunc ? 1 : Infinity
88
+ true === v.isFilterFunc || true === v.isEmitFunc ? 1 : Infinity
89
89
  }
90
90
 
91
91
 
@@ -183,10 +183,12 @@ function rebuild(v: Val, peg: any, ctx: AontuContext): Val {
183
183
  out.peg = peg
184
184
  out.dc = 0
185
185
  return out
186
- } /* node:coverage ignore next 8 */
186
+ } /* node:coverage ignore next 10 */
187
187
 
188
188
 
189
189
  export {
190
+ boundArgStart,
191
+ rebuild,
190
192
  hasPlace,
191
193
  fillPlace,
192
194
  PlaceVal,