aontu 0.57.0 → 0.58.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 (117) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +4 -2
  7. package/dist/aontu.js +11 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +475 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +2 -0
  13. package/dist/ctx.js +1 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/format.js +8 -2
  16. package/dist/format.js.map +1 -1
  17. package/dist/hints.js +40 -9
  18. package/dist/hints.js.map +1 -1
  19. package/dist/lang.js +354 -57
  20. package/dist/lang.js.map +1 -1
  21. package/dist/lower.d.ts +20 -0
  22. package/dist/lower.js +575 -0
  23. package/dist/lower.js.map +1 -0
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +1 -1
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp-server.js +1 -1
  28. package/dist/mcp.d.ts +1 -0
  29. package/dist/mcp.js +40 -3
  30. package/dist/mcp.js.map +1 -1
  31. package/dist/render.d.ts +53 -0
  32. package/dist/render.js +542 -0
  33. package/dist/render.js.map +1 -0
  34. package/dist/sigdecl.js +1 -1
  35. package/dist/sigdecl.js.map +1 -1
  36. package/dist/std.d.ts +2 -0
  37. package/dist/std.js +498 -2
  38. package/dist/std.js.map +1 -1
  39. package/dist/template.d.ts +5 -0
  40. package/dist/template.js +257 -0
  41. package/dist/template.js.map +1 -0
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/dist/unify.js +43 -0
  44. package/dist/unify.js.map +1 -1
  45. package/dist/val/AggFuncVal.d.ts +1 -1
  46. package/dist/val/AggFuncVal.js +10 -21
  47. package/dist/val/AggFuncVal.js.map +1 -1
  48. package/dist/val/BagVal.js +1 -1
  49. package/dist/val/BagVal.js.map +1 -1
  50. package/dist/val/ConstraintVal.js +1 -1
  51. package/dist/val/EachFuncVal.d.ts +1 -1
  52. package/dist/val/EachFuncVal.js +8 -13
  53. package/dist/val/EachFuncVal.js.map +1 -1
  54. package/dist/val/EmitFuncVal.d.ts +23 -4
  55. package/dist/val/EmitFuncVal.js +298 -28
  56. package/dist/val/EmitFuncVal.js.map +1 -1
  57. package/dist/val/FilterFuncVal.js +8 -4
  58. package/dist/val/FilterFuncVal.js.map +1 -1
  59. package/dist/val/FormFuncVal.d.ts +14 -0
  60. package/dist/val/FormFuncVal.js +55 -0
  61. package/dist/val/FormFuncVal.js.map +1 -0
  62. package/dist/val/MapVal.d.ts +2 -1
  63. package/dist/val/MapVal.js +2 -1
  64. package/dist/val/MapVal.js.map +1 -1
  65. package/dist/val/PackFuncVal.d.ts +1 -1
  66. package/dist/val/PackFuncVal.js +22 -18
  67. package/dist/val/PackFuncVal.js.map +1 -1
  68. package/dist/val/PlaceVal.js +2 -1
  69. package/dist/val/PlaceVal.js.map +1 -1
  70. package/dist/val/RefVal.d.ts +3 -0
  71. package/dist/val/RefVal.js +156 -17
  72. package/dist/val/RefVal.js.map +1 -1
  73. package/dist/val/Val.d.ts +7 -1
  74. package/dist/val/Val.js +12 -1
  75. package/dist/val/Val.js.map +1 -1
  76. package/dist/val/members.d.ts +9 -0
  77. package/dist/val/members.js +52 -0
  78. package/dist/val/members.js.map +1 -0
  79. package/grammar/aontu.abnf +1 -1
  80. package/grammar/aontu.gbnf +1 -1
  81. package/grammar/aontu.lark +1 -1
  82. package/grammar/aontu.tmLanguage.json +1 -1
  83. package/package.json +1 -1
  84. package/skill/SKILL.md +4 -4
  85. package/skill/error-codes.md +1 -1
  86. package/skill/examples.md +1 -1
  87. package/skill/grammar-card.md +1 -1
  88. package/src/agentsmd.ts +1 -1
  89. package/src/alias.ts +112 -0
  90. package/src/aontu.ts +19 -2
  91. package/src/cli.ts +517 -3
  92. package/src/ctx.ts +13 -0
  93. package/src/format.ts +12 -2
  94. package/src/hints.ts +47 -9
  95. package/src/lang.ts +406 -62
  96. package/src/lower.ts +636 -0
  97. package/src/lsp.ts +1 -1
  98. package/src/mcp-server.ts +1 -1
  99. package/src/mcp.ts +43 -4
  100. package/src/render.ts +727 -0
  101. package/src/sigdecl.ts +1 -1
  102. package/src/std.ts +506 -1
  103. package/src/template.ts +291 -0
  104. package/src/unify.ts +47 -0
  105. package/src/val/AggFuncVal.ts +10 -21
  106. package/src/val/BagVal.ts +1 -1
  107. package/src/val/ConstraintVal.ts +1 -1
  108. package/src/val/EachFuncVal.ts +8 -16
  109. package/src/val/EmitFuncVal.ts +376 -39
  110. package/src/val/FilterFuncVal.ts +11 -4
  111. package/src/val/FormFuncVal.ts +119 -0
  112. package/src/val/MapVal.ts +3 -2
  113. package/src/val/PackFuncVal.ts +23 -19
  114. package/src/val/PlaceVal.ts +2 -1
  115. package/src/val/RefVal.ts +167 -18
  116. package/src/val/Val.ts +42 -1
  117. package/src/val/members.ts +86 -0
@@ -42,6 +42,21 @@
42
42
  // returning a tree would undo that ruling. A body element that is
43
43
  // itself a list SPLICES, which is what makes a nested emit compose.
44
44
  //
45
+ // A VALUE REACHES THE BODY THROUGH `replace`, NOT A DELIMITER
46
+ // (docs/design/TEMPLATE.0.md D3, D4; RENDER.0.md P6). A template may
47
+ // carry a `replace` map whose key is an exact string the body already
48
+ // holds as ordinary target text and whose value is evaluated against
49
+ // the matched node, and an `esc` naming the convention every value is
50
+ // escaped by -- the C/JSON escape when absent, `none` the one opt-out.
51
+ // Three rules: a single left-to-right scan of a literal line taking
52
+ // the longest key at each position; a substituted value is never
53
+ // re-scanned, so no value can introduce a key; and a template's
54
+ // replacements touch its own literal text only, never a result spliced
55
+ // in from a nested dispatch. Two checks run on the template before any
56
+ // node: a key inside another key (replace_overlap) and a key the body
57
+ // does not hold (replace_unused). There is no hole syntax, and that is
58
+ // the point: any inline delimiter is somebody's syntax.
59
+ //
45
60
  // NO MATCH IS AN ERROR (`emit_none`). XSLT's built-in rule copies an
46
61
  // unhandled node's string value into the result, which for code output
47
62
  // means model data landing silently in the middle of a source file.
@@ -49,7 +64,7 @@
49
64
  // An EMPTY selection, by contrast, emits nothing -- which is the whole
50
65
  // conditional mechanism, and why no `when` directive exists.
51
66
  //
52
- // A NAMED TABLE IS A PLACEHELD `emit` (`%wire: emit(_, T)`). A table
67
+ // A NAMED TABLE IS A PLACEHELD `emit` (`%wire = emit(_, T)`). A table
53
68
  // written at a document position is DRIVEN there, so its bodies'
54
69
  // relative references resolve against wherever it sits and miss;
55
70
  // nothing in the language holds a value unevaluated at such a
@@ -78,25 +93,59 @@ import {
78
93
 
79
94
  import { unite } from '../unify'
80
95
  import { makeNilErr } from '../err'
96
+ import { isEscVariant, escapeText } from '../escape'
97
+ import { cmpCodePoint } from '../keyorder'
81
98
  import { top } from './top'
82
99
  import { ListVal } from './ListVal'
100
+ import { StringVal } from './StringVal'
83
101
  import { FuncBaseVal, trialUnify } from './FuncBaseVal'
84
102
  import { repathInstance } from './Val'
103
+ import type { EmitOrigin } from './Val'
85
104
  import { boundArgStart, fillPlace, rebuild } from './PlaceVal'
86
- import { dataValues } from './EachFuncVal'
105
+ import { plusText } from './PlusOpVal'
106
+ import { bagMembers } from './members'
107
+
108
+
109
+ // One entry of the rule table: the pattern to try, the body to
110
+ // instantiate, and -- TEMPLATE.0.md D3 and D4 -- the `replace` map and
111
+ // the `esc` convention its values take, with the literal spots of the
112
+ // body the replacements are written into. A table is a LIST of these;
113
+ // a single template map is that list of one, told apart by kind,
114
+ // exactly as match() tells its patterns apart.
115
+ type Template = {
116
+ match: Val,
117
+ body: Val,
118
+ replace?: Val,
119
+ esc: string,
120
+ lits: LitSpot[],
121
+ // The rule's index in its table, which with the table's own address
122
+ // is the address the trace names it by (RENDER.0.md P7).
123
+ idx: number,
124
+ }
125
+
126
+
127
+ // One literal string of a body: element `i`, and within a map element
128
+ // the `of` index or the `text` key, with the text itself.
129
+ type LitSpot = { i: number, of?: number, text?: boolean, s: string }
130
+
131
+
132
+ // What is wrong with a table, with the detail the message carries.
133
+ type Refusal = { code: string, details?: Record<string, string> }
87
134
 
88
135
 
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 }
136
+ // One replacement: the key, and the text it becomes at a node.
137
+ type Pair = [string, string]
138
+
139
+
140
+ function isRefusal(x: any): x is Refusal {
141
+ return 'string' === typeof x.code
142
+ }
94
143
 
95
144
 
96
145
  // Read the table. A map is one template; a list is many. The shape is
97
146
  // checked here rather than at the call, because a table is ordinary
98
147
  // data and may be computed.
99
- function tableTemplates(table: Val | undefined): Template[] | string {
148
+ function tableTemplates(table: Val | undefined): Template[] | Refusal {
100
149
  const t: any = table
101
150
 
102
151
  // A NAMED TABLE IS A PLACEHELD `emit`, and its table is the table.
@@ -104,7 +153,7 @@ function tableTemplates(table: Val | undefined): Template[] | string {
104
153
  // relative references resolve against wherever it sits and miss --
105
154
  // so the position that holds one unevaluated is the one position the
106
155
  // language already never drives: a call's template argument.
107
- // `%wire: emit(_, [ … ])` is that position with the selection left
156
+ // `%wire = emit(_, [ … ])` is that position with the selection left
108
157
  // open, and it reads as what it is, an apply-templates waiting for
109
158
  // its nodes: `emit(.listen, %wire)` passes them, `.listen & %wire`
110
159
  // fills the hole, and both are the same dispatch.
@@ -113,8 +162,8 @@ function tableTemplates(table: Val | undefined): Template[] | string {
113
162
  }
114
163
 
115
164
  if (true === t?.isMap) {
116
- const one = oneTemplate(t)
117
- return 'string' === typeof one ? one : [one]
165
+ const one = oneTemplate(t, 0)
166
+ return isRefusal(one) ? one : [one]
118
167
  }
119
168
 
120
169
  if (true === t?.isList) {
@@ -122,10 +171,10 @@ function tableTemplates(table: Val | undefined): Template[] | string {
122
171
  for (const el of t.peg as Val[]) {
123
172
  const e: any = el
124
173
  if (true !== e?.isMap) {
125
- return 'emit_template'
174
+ return { code: 'emit_template' }
126
175
  }
127
- const one = oneTemplate(e)
128
- if ('string' === typeof one) {
176
+ const one = oneTemplate(e, out.length)
177
+ if (isRefusal(one)) {
129
178
  return one
130
179
  }
131
180
  out.push(one)
@@ -133,44 +182,211 @@ function tableTemplates(table: Val | undefined): Template[] | string {
133
182
  return out
134
183
  }
135
184
 
136
- return 'emit_table'
185
+ return { code: 'emit_table' }
137
186
  }
138
187
 
139
188
 
140
- function oneTemplate(m: any): Template | string {
189
+ // One rule. Both keys are required: a template with no pattern would
190
+ // match everything by accident, and one with no body would emit
191
+ // nothing while claiming a node. The two optional keys -- a `replace`
192
+ // map and an `esc` naming the convention its values are escaped by,
193
+ // `none` the one opt-out -- are the template's shape too, and D3's two
194
+ // static checks run here, on the template alone, before any node.
195
+ function oneTemplate(m: any, idx: number): Template | Refusal {
141
196
  const match: Val = m.peg.match
142
197
  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
198
  if (null == match || null == body) {
147
- return 'emit_template'
199
+ return { code: 'emit_template' }
148
200
  }
149
201
  if (true !== body.isList) {
150
- return 'emit_body'
202
+ return { code: 'emit_body' }
203
+ }
204
+
205
+ const replace: any = m.peg.replace
206
+ if (null != replace && true !== replace.isMap) {
207
+ return { code: 'emit_template' }
208
+ }
209
+
210
+ const escv: any = m.peg.esc
211
+ let esc = ''
212
+ if (null != escv) {
213
+ const name = textOf(escv)
214
+ if (undefined === name || ('none' !== name && !isEscVariant(name))) {
215
+ return { code: 'esc_variant' }
216
+ }
217
+ esc = name
218
+ }
219
+
220
+ const lits = literalSpots(body.peg)
221
+ if (null != replace) {
222
+ const bad = checkReplace(Object.keys(replace.peg), lits)
223
+ if (undefined !== bad) {
224
+ return bad
225
+ }
226
+ }
227
+
228
+ return { match, body, replace, esc, lits, idx }
229
+ }
230
+
231
+
232
+ // The string a value carries, or undefined when it is not a string.
233
+ function textOf(v: any): string | undefined {
234
+ return true === v?.isScalar && 'string' === typeof v.peg ? v.peg : undefined
235
+ }
236
+
237
+
238
+ // The literal strings of a body -- a string element, and the strings
239
+ // written directly in a map element's `of` list or `text` -- which are
240
+ // the text the template wrote. A string an expression or a nested
241
+ // dispatch computes is not one: D3's third rule (a spliced result is
242
+ // finished) and its second (a substituted value is never re-scanned)
243
+ // both follow from substituting at these spots and nowhere else.
244
+ function literalSpots(elems: Val[]): LitSpot[] {
245
+ const out: LitSpot[] = []
246
+ elems.forEach((el: any, i: number) => {
247
+ const s = textOf(el)
248
+ if (undefined !== s) {
249
+ out.push({ i, s })
250
+ return
251
+ }
252
+ if (true !== el?.isMap) {
253
+ return
254
+ }
255
+ const of: any = el.peg.of
256
+ if (true === of?.isList) {
257
+ (of.peg as any[]).forEach((p: any, j: number) => {
258
+ const ps = textOf(p)
259
+ if (undefined !== ps) {
260
+ out.push({ i, of: j, s: ps })
261
+ }
262
+ })
263
+ }
264
+ const ts = textOf(el.peg.text)
265
+ if (undefined !== ts) {
266
+ out.push({ i, text: true, s: ts })
267
+ }
268
+ })
269
+ return out
270
+ }
271
+
272
+
273
+ // D3's two static checks, on the template alone and before any node:
274
+ // a key inside another is ambiguous whatever the order
275
+ // (replace_overlap), and a key no literal holds means the template
276
+ // drifted from its map (replace_unused). Keys are visited in code
277
+ // point order, so both ports name the same pair.
278
+ function checkReplace(keys: string[], lits: LitSpot[]): Refusal | undefined {
279
+ const sorted = [...keys].sort(cmpCodePoint)
280
+ for (const a of sorted) {
281
+ for (const b of sorted) {
282
+ if (a !== b && b.includes(a)) {
283
+ return { code: 'replace_overlap', details: { key: quoted(a), other: quoted(b) } }
284
+ }
285
+ }
286
+ }
287
+ for (const k of sorted) {
288
+ if ('' === k || !lits.some((l) => l.s.includes(k))) {
289
+ return { code: 'replace_unused', details: { key: quoted(k) } }
290
+ }
291
+ }
292
+ return undefined
293
+ }
294
+
295
+
296
+ // D3's first two rules as one scan: at each position the longest key
297
+ // that matches is taken and its value written out whole, and the scan
298
+ // moves past the KEY -- the value is never looked at again, so no
299
+ // value can introduce a key.
300
+ function substitute(text: string, pairs: Pair[]): string {
301
+ let out = ''
302
+ let i = 0
303
+ while (i < text.length) {
304
+ const hit = pairs.find((p) => text.startsWith(p[0], i))
305
+ if (undefined === hit) {
306
+ out += text[i]
307
+ i += 1
308
+ }
309
+ else {
310
+ out += hit[1]
311
+ i += hit[0].length
312
+ }
151
313
  }
152
- return { match, body }
314
+ return out
315
+ }
316
+
317
+
318
+ // The instance with the template's literal text at element `i`
319
+ // rewritten through the pairs -- on the fresh instance, where the
320
+ // structure is exactly the template's, and before any binding, so a
321
+ // value written in is never scanned again and a spliced result is
322
+ // never touched.
323
+ function substituted(
324
+ inst: Val, i: number, lits: LitSpot[], pairs: Pair[], ctx: AontuContext
325
+ ): Val {
326
+ for (const l of lits) {
327
+ if (l.i !== i) {
328
+ continue
329
+ }
330
+ const sv = new StringVal({ peg: substitute(l.s, pairs) }, ctx)
331
+ if (undefined !== l.of) {
332
+ (inst as any).peg.of.peg[l.of] = sv
333
+ }
334
+ else if (true === l.text) {
335
+ (inst as any).peg.text = sv
336
+ }
337
+ else {
338
+ inst = sv
339
+ }
340
+ }
341
+ return inst
342
+ }
343
+
344
+
345
+ // A key as the message writes it, quoted so an empty key is visible.
346
+ function quoted(s: string): string {
347
+ return '"' + s + '"'
348
+ }
349
+
350
+
351
+ function unpref(v: any): any {
352
+ while (true === v.isPref) {
353
+ v = v.peg
354
+ }
355
+ return v
153
356
  }
154
357
 
155
358
 
156
359
  // The reference a body named that the node could not answer, kept by
157
360
  // the walk so `resolve` can report the first one against the node it
158
- // was tried on.
159
- type BindFail = { ref?: string }
361
+ // was tried on -- and a refusal a replacement raised.
362
+ type Fail = { ref?: string, code?: string, details?: Record<string, string> }
160
363
 
161
364
 
162
365
  // Every relative reference in `v` replaced by the field of `node` it
163
366
  // names. Answers `v` unchanged when it holds none, so a body with no
164
367
  // substitutions is never needlessly rebuilt -- the identity test
165
368
  // `fillPlace` already relies on.
166
- function bindNode(v: any, node: Val, ctx: AontuContext, fail: BindFail): Val {
369
+ function bindNode(v: any, node: Val, ctx: AontuContext, fail: Fail): Val {
167
370
  if (true === v?.isRef && true !== v.absolute) {
168
371
  const found = nodeField(v, node)
169
372
  if (undefined === found) {
170
373
  fail.ref = undefined === fail.ref ? v.canon : fail.ref
171
374
  return v
172
375
  }
173
- return found.clone(ctx)
376
+ const out = found.clone(ctx)
377
+ // A RELATIVE REFERENCE IS A READ TOO (RENDER.0.md P7), and the one
378
+ // read no reference resolution sees: the binding answers it here,
379
+ // from the matched node, rather than letting a path resolve at a
380
+ // position the body never occupies. Without this a nested rule set
381
+ // whose selection is `.handlers` reported its nodes at the address
382
+ // they came to rest, which is in the OUTPUT. A node carries an
383
+ // address only under an instrumented run, which is what makes the
384
+ // second test the whole guard.
385
+ if (null == (out as any).origin && null != (node as any).origin) {
386
+ ; (out as any).origin = (node as any).origin +
387
+ (v.peg as string[]).map((seg: string) => '.' + seg).join('')
388
+ }
389
+ return out
174
390
  }
175
391
 
176
392
  const peg: any = v?.peg
@@ -199,7 +415,11 @@ function bindNode(v: any, node: Val, ctx: AontuContext, fail: BindFail): Val {
199
415
  const out: Record<string, Val> = {}
200
416
  for (const k of Object.keys(peg)) {
201
417
  const c = peg[k]
202
- const b = true === c?.isVal ? bindNode(c, node, ctx, fail) : c
418
+ // No isVal guard, for the reason fillPlace gives: a slot holding
419
+ // something that is not a Val answers itself, because the tests
420
+ // above -- is it a reference, has it a peg -- are both false for
421
+ // one.
422
+ const b = bindNode(c, node, ctx, fail)
203
423
  changed = changed || b !== c
204
424
  out[k] = b
205
425
  }
@@ -235,6 +455,29 @@ function nodeField(ref: any, node: Val): Val | undefined {
235
455
  }
236
456
 
237
457
 
458
+ // The address of one matched node: its own read address when it has
459
+ // one, else the SELECTION's read address and the node's key under it
460
+ // -- a selection is read once and walked, so its members carry no read
461
+ // of their own.
462
+ //
463
+ // A COMPUTED SELECTION HAS NO ADDRESS, AND THE TRACE SAYS SO: an empty
464
+ // node. `filter(...)` builds a bag no path in the document names, and
465
+ // the only other thing to report is where the bag came to REST -- a
466
+ // position inside a template instance, which is not in the document,
467
+ // and which the two ports number differently. Publishing that would
468
+ // have made the trace a parity break as well as a fiction. The rule
469
+ // address answers the same way: `<table>#<index>` for a table a
470
+ // reference reached, and `#<index>` alone for one written inline at the
471
+ // call site, which has no address of its own. `#` is in no path, so a
472
+ // rule's address can never be read as one.
473
+ function nodeAddr(sel: string | undefined, key: string, node: any): string {
474
+ if (null != node.origin) {
475
+ return node.origin
476
+ }
477
+ return undefined === sel ? '' : sel + '.' + key
478
+ }
479
+
480
+
238
481
  // A body element that is itself a list splices, which is what makes a
239
482
  // nested emit compose into one flat sequence.
240
483
  function splice(v: Val, out: Val[]): void {
@@ -291,9 +534,14 @@ class EmitFuncVal extends FuncBaseVal {
291
534
 
292
535
 
293
536
  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.
537
+ // THE MEMBERS WITH THEIR KEYS, read through the one helper every
538
+ // fold reads a bag by (./members.ts): source order for a list,
539
+ // sorted-key order for a map, a hidden child and an unfilled
540
+ // optional left out. The KEY is what the trace addresses a node by
541
+ // -- it is the node's key IN THE SELECTION, which is the only
542
+ // thing a walk of a computed bag knows about where a node sits.
543
+ const nodes = bagMembers(args?.[0], ctx)
544
+ if (undefined === nodes) {
297
545
  return makeNilErr(ctx, 'emit_data', this)
298
546
  }
299
547
 
@@ -308,13 +556,22 @@ class EmitFuncVal extends FuncBaseVal {
308
556
  }
309
557
 
310
558
  const templates = tableTemplates(table)
311
- if ('string' === typeof templates) {
312
- return makeNilErr(ctx, templates, this)
559
+ if (isRefusal(templates)) {
560
+ return this.refuse(ctx, templates)
313
561
  }
314
562
 
563
+ // THE TRACE'S TWO ADDRESSES (RENDER.0.md D11, P7), computed once
564
+ // per dispatch and only when the run is instrumented: the table's
565
+ // own, which every rule of it is numbered under, and the
566
+ // selection's, which every node of it is keyed under.
567
+ const rec = undefined !== ctx.reads
568
+ const tableAddr = rec ? ((table as any)?.origin ?? '') : ''
569
+ const selAddr = rec ? (args?.[0] as any)?.origin : undefined
570
+
315
571
  const peg: Val[] = []
316
572
 
317
- for (const node of nodes) {
573
+ for (const member of nodes) {
574
+ const node = member.val
318
575
  const tmpl = this.dispatch(ctx, node, templates)
319
576
  if ('string' === typeof tmpl) {
320
577
  return makeNilErr(ctx, 'emit_none', this, undefined, 'resolve', {
@@ -322,14 +579,30 @@ class EmitFuncVal extends FuncBaseVal {
322
579
  tried: tmpl,
323
580
  })
324
581
  }
325
- const fail: BindFail = {}
326
- this.instantiate(ctx, node, tmpl, peg, fail)
582
+ const fail: Fail = {}
583
+ let mark: EmitOrigin | undefined = undefined
584
+ if (rec) {
585
+ // THE NODE KEEPS ITS ADDRESS (P7). A body passes the node on
586
+ // through `_`, and a nested rule set dispatching over it can
587
+ // then say where it came from -- otherwise the node arrives as
588
+ // an element of a list the body wrote, and the only address
589
+ // left is where that list came to rest.
590
+ const naddr = nodeAddr(selAddr, member.key, node)
591
+ if ('' !== naddr && null == (node as any).origin) {
592
+ ; (node as any).origin = naddr
593
+ }
594
+ mark = { node: naddr, rule: tableAddr + '#' + tmpl.idx }
595
+ }
596
+ this.instantiate(ctx, node, tmpl, peg, fail, mark)
327
597
  if (undefined !== fail.ref) {
328
598
  return makeNilErr(ctx, 'emit_ref', this, undefined, 'resolve', {
329
599
  ref: fail.ref,
330
600
  value: node.canon,
331
601
  })
332
602
  }
603
+ if (undefined !== fail.code) {
604
+ return this.refuse(ctx, { code: fail.code, details: fail.details })
605
+ }
333
606
  }
334
607
 
335
608
  // THE PIECES ARE PATHED WHERE THEY LAND, once the splicing has
@@ -344,6 +617,14 @@ class EmitFuncVal extends FuncBaseVal {
344
617
  }
345
618
 
346
619
 
620
+ // The located error for a refusal, with the message's details when
621
+ // the refusal carries them.
622
+ refuse(ctx: AontuContext, r: Refusal): Val {
623
+ return undefined === r.details ? makeNilErr(ctx, r.code, this)
624
+ : makeNilErr(ctx, r.code, this, undefined, 'resolve', r.details)
625
+ }
626
+
627
+
347
628
  // First match wins, in table order, by unifiability -- the same
348
629
  // question `match` and `filter` ask, answered the same way. Returns
349
630
  // the patterns tried when nothing matched, for the located error.
@@ -362,18 +643,61 @@ class EmitFuncVal extends FuncBaseVal {
362
643
  }
363
644
 
364
645
 
646
+ // The replacement pairs for one node: the template's `replace` map
647
+ // instantiated at the node -- bound, filled and driven as a body is
648
+ // -- each value as text by the one number-to-text rule (`plusText`,
649
+ // the rule `+` and `join` share), escaped by the template's
650
+ // convention unless that is `none`, and sorted longest key first so
651
+ // the scan takes the longest match at every position (D3's first
652
+ // rule). A value that is not text, or has not settled, is
653
+ // replace_value.
654
+ replacements(ctx: AontuContext, node: Val, tmpl: Template, fail: Fail): Pair[] | undefined {
655
+ if (undefined === tmpl.replace) {
656
+ return undefined
657
+ }
658
+ let inst: any = tmpl.replace.clone(ctx, { dup: true })
659
+ inst = fillPlace(bindNode(inst, node, ctx, fail), node, ctx)
660
+ if (!inst.done) {
661
+ inst = unite(ctx, inst, top(), 'emit')
662
+ }
663
+ const pairs: Pair[] = []
664
+ for (const key of Object.keys(inst.peg)) {
665
+ const v: any = unpref(inst.peg[key])
666
+ const text = plusText(v)
667
+ if (undefined === text) {
668
+ fail.code = 'replace_value'
669
+ fail.details = { key: quoted(key), value: String(v?.canon) }
670
+ return undefined
671
+ }
672
+ pairs.push([key, 'none' === tmpl.esc ? text : escapeText(text, tmpl.esc)])
673
+ }
674
+ pairs.sort((a, b) => b[0].length - a[0].length || cmpCodePoint(a[0], b[0]))
675
+ return pairs
676
+ }
677
+
678
+
365
679
  // Instantiate one body at the node and SPLICE its pieces into the
366
680
  // output. A full instance to the leaves (`dup`, ADR-005), because a
367
681
  // 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.
682
+ // the first node's resolution would answer for every node; the
683
+ // template's replacements written into the instance's literal text;
684
+ // then the two bindings, relative references and the hole, both to
685
+ // the node.
370
686
  instantiate(ctx: AontuContext, node: Val, tmpl: Template,
371
- out: Val[], fail: BindFail): void {
687
+ out: Val[], fail: Fail, mark?: EmitOrigin): void {
688
+ const pairs = this.replacements(ctx, node, tmpl, fail)
689
+ if (undefined !== fail.code) {
690
+ return
691
+ }
692
+
372
693
  const elems: Val[] = (tmpl.body as any).peg
373
694
 
374
695
  for (let i = 0; i < elems.length; i++) {
375
696
  const elctx = ctx.descend(String(out.length))
376
- const inst = elems[i].clone(elctx, { dup: true })
697
+ let inst = elems[i].clone(elctx, { dup: true })
698
+ if (undefined !== pairs) {
699
+ inst = substituted(inst, i, tmpl.lits, pairs, elctx)
700
+ }
377
701
  let piece = fillPlace(bindNode(inst, node, elctx, fail), node, elctx)
378
702
 
379
703
  // A NESTED DISPATCH IS DRIVEN HERE, not left for the next pass.
@@ -389,7 +713,20 @@ class EmitFuncVal extends FuncBaseVal {
389
713
  piece = unite(elctx, piece, top(), 'emit')
390
714
  }
391
715
 
716
+ // THE INNERMOST DISPATCH OWNS THE PIECE (P7). A body element
717
+ // that is a nested rule set has already stamped what it emitted,
718
+ // and those pieces are spliced into this result here: the rule
719
+ // that WROTE a line is the one the trace names, so a stamp is
720
+ // written only where there is none.
721
+ const at = out.length
392
722
  splice(piece, out)
723
+ if (undefined !== mark) {
724
+ for (let k = at; k < out.length; k++) {
725
+ if (null == (out[k] as any).emitted) {
726
+ ; (out[k] as any).emitted = mark
727
+ }
728
+ }
729
+ }
393
730
  }
394
731
  }
395
732
 
@@ -46,6 +46,10 @@ import { ListVal } from './ListVal'
46
46
  import { FuncBaseVal, trialUnify } from './FuncBaseVal'
47
47
  import { repathInstance } from './Val'
48
48
  import { fillPlace } from './PlaceVal'
49
+ import { bagMembers } from './members'
50
+
51
+
52
+ type Member = { key: string, val: Val }
49
53
 
50
54
 
51
55
  class FilterFuncVal extends FuncBaseVal {
@@ -115,12 +119,15 @@ class FilterFuncVal extends FuncBaseVal {
115
119
  return undefined !== met && met.canon === child.canon
116
120
  }
117
121
 
122
+ // The candidates are the bag's MEMBERS -- what generation would
123
+ // emit (./members.ts, BUGS.md §79) -- so a hidden child is never
124
+ // selected into the result.
118
125
  if (true === data?.isMap) {
119
126
  const peg: Record<string, Val> = {}
120
- for (const key of Object.keys(data.peg)) {
127
+ for (const { key, val } of bagMembers(data, ctx) as Member[]) {
121
128
  const kctx = ctx.descend(key)
122
- if (keeps(data.peg[key], kctx)) {
123
- peg[key] = data.peg[key].clone(kctx)
129
+ if (keeps(val, kctx)) {
130
+ peg[key] = val.clone(kctx)
124
131
  }
125
132
  }
126
133
  return new MapVal({ peg }, ctx)
@@ -128,7 +135,7 @@ class FilterFuncVal extends FuncBaseVal {
128
135
 
129
136
  if (true === data?.isList) {
130
137
  const peg: Val[] = []
131
- for (const el of data.peg as Val[]) {
138
+ for (const { val: el } of bagMembers(data, ctx) as Member[]) {
132
139
  // The element context is the position it will END UP at, which
133
140
  // is its index in the RESULT: dropping the third of five moves
134
141
  // the fourth up, and a kept element must be pathed where it