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.
- package/README.md +2 -2
- package/dist/agentsmd.js +1 -1
- package/dist/alias.d.ts +3 -0
- package/dist/alias.js +59 -0
- package/dist/alias.js.map +1 -0
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +11 -3
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +475 -3
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +2 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/format.js +8 -2
- package/dist/format.js.map +1 -1
- package/dist/hints.js +40 -9
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +354 -57
- package/dist/lang.js.map +1 -1
- package/dist/lower.d.ts +20 -0
- package/dist/lower.js +575 -0
- package/dist/lower.js.map +1 -0
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +1 -1
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +40 -3
- package/dist/mcp.js.map +1 -1
- package/dist/render.d.ts +53 -0
- package/dist/render.js +542 -0
- package/dist/render.js.map +1 -0
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/std.d.ts +2 -0
- package/dist/std.js +498 -2
- package/dist/std.js.map +1 -1
- package/dist/template.d.ts +5 -0
- package/dist/template.js +257 -0
- package/dist/template.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.js +43 -0
- package/dist/unify.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +1 -1
- package/dist/val/AggFuncVal.js +10 -21
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/BagVal.js +1 -1
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +1 -1
- package/dist/val/EachFuncVal.d.ts +1 -1
- package/dist/val/EachFuncVal.js +8 -13
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +23 -4
- package/dist/val/EmitFuncVal.js +298 -28
- package/dist/val/EmitFuncVal.js.map +1 -1
- package/dist/val/FilterFuncVal.js +8 -4
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FormFuncVal.d.ts +14 -0
- package/dist/val/FormFuncVal.js +55 -0
- package/dist/val/FormFuncVal.js.map +1 -0
- package/dist/val/MapVal.d.ts +2 -1
- package/dist/val/MapVal.js +2 -1
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +1 -1
- package/dist/val/PackFuncVal.js +22 -18
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.js +2 -1
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +3 -0
- package/dist/val/RefVal.js +156 -17
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/Val.d.ts +7 -1
- package/dist/val/Val.js +12 -1
- package/dist/val/Val.js.map +1 -1
- package/dist/val/members.d.ts +9 -0
- package/dist/val/members.js +52 -0
- package/dist/val/members.js.map +1 -0
- package/grammar/aontu.abnf +1 -1
- package/grammar/aontu.gbnf +1 -1
- package/grammar/aontu.lark +1 -1
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +4 -4
- package/skill/error-codes.md +1 -1
- package/skill/examples.md +1 -1
- package/skill/grammar-card.md +1 -1
- package/src/agentsmd.ts +1 -1
- package/src/alias.ts +112 -0
- package/src/aontu.ts +19 -2
- package/src/cli.ts +517 -3
- package/src/ctx.ts +13 -0
- package/src/format.ts +12 -2
- package/src/hints.ts +47 -9
- package/src/lang.ts +406 -62
- package/src/lower.ts +636 -0
- package/src/lsp.ts +1 -1
- package/src/mcp-server.ts +1 -1
- package/src/mcp.ts +43 -4
- package/src/render.ts +727 -0
- package/src/sigdecl.ts +1 -1
- package/src/std.ts +506 -1
- package/src/template.ts +291 -0
- package/src/unify.ts +47 -0
- package/src/val/AggFuncVal.ts +10 -21
- package/src/val/BagVal.ts +1 -1
- package/src/val/ConstraintVal.ts +1 -1
- package/src/val/EachFuncVal.ts +8 -16
- package/src/val/EmitFuncVal.ts +376 -39
- package/src/val/FilterFuncVal.ts +11 -4
- package/src/val/FormFuncVal.ts +119 -0
- package/src/val/MapVal.ts +3 -2
- package/src/val/PackFuncVal.ts +23 -19
- package/src/val/PlaceVal.ts +2 -1
- package/src/val/RefVal.ts +167 -18
- package/src/val/Val.ts +42 -1
- package/src/val/members.ts +86 -0
package/src/val/EmitFuncVal.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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[] |
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 (
|
|
312
|
-
return
|
|
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
|
|
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:
|
|
326
|
-
|
|
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;
|
|
369
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
|
package/src/val/FilterFuncVal.ts
CHANGED
|
@@ -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
|
|
127
|
+
for (const { key, val } of bagMembers(data, ctx) as Member[]) {
|
|
121
128
|
const kctx = ctx.descend(key)
|
|
122
|
-
if (keeps(
|
|
123
|
-
peg[key] =
|
|
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
|
|
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
|