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.
- package/README.md +3 -3
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +10 -3
- package/dist/agentsmd.js.map +1 -1
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +5 -2
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +311 -53
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +3 -2
- package/dist/diff.js.map +1 -1
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +455 -0
- package/dist/escape.js.map +1 -0
- package/dist/format.d.ts +26 -0
- package/dist/format.js +1489 -0
- package/dist/format.js.map +1 -0
- package/dist/hints.js +39 -1
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +1 -0
- package/dist/jsonschema.js +3 -2
- package/dist/jsonschema.js.map +1 -1
- package/dist/lang.js +84 -6
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +2 -1
- package/dist/lsp.js +4 -4
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +6 -4
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +8 -7
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.js +6 -6
- package/dist/mod.js.map +1 -1
- package/dist/patch.d.ts +1 -0
- package/dist/patch.js +1 -0
- package/dist/patch.js.map +1 -1
- package/dist/query.d.ts +1 -0
- package/dist/query.js +4 -3
- package/dist/query.js.map +1 -1
- package/dist/reach.d.ts +1 -0
- package/dist/reach.js +3 -2
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +1 -0
- package/dist/relation.js +3 -2
- package/dist/relation.js.map +1 -1
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/std.js +2 -1
- package/dist/std.js.map +1 -1
- package/dist/subsume.d.ts +1 -0
- package/dist/subsume.js +3 -2
- package/dist/subsume.js.map +1 -1
- package/dist/trim.d.ts +1 -0
- package/dist/trim.js +3 -2
- package/dist/trim.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +1 -0
- package/dist/type.js.map +1 -1
- package/dist/utility.d.ts +8 -2
- package/dist/utility.js +9 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/EachFuncVal.js +1 -2
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +23 -0
- package/dist/val/EmitFuncVal.js +261 -0
- package/dist/val/EmitFuncVal.js.map +1 -0
- package/dist/val/FilterFuncVal.js +1 -2
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +16 -0
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/PackFuncVal.js +1 -2
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +3 -1
- package/dist/val/PlaceVal.js +8 -6
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/StrFuncVal.d.ts +32 -0
- package/dist/val/StrFuncVal.js +292 -0
- package/dist/val/StrFuncVal.js.map +1 -0
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +1 -1
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +2 -1
- package/dist/view.js +379 -8
- package/dist/view.js.map +1 -1
- package/grammar/aontu.abnf +156 -0
- package/grammar/aontu.gbnf +4 -3
- package/grammar/aontu.lark +4 -3
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +2 -1
- package/skill/grammar-card.md +5 -2
- package/src/agentsmd.ts +15 -3
- package/src/aontu.ts +8 -1
- package/src/cli.ts +365 -58
- package/src/diff.ts +7 -2
- package/src/escape.ts +371 -0
- package/src/format.ts +1728 -0
- package/src/hints.ts +53 -1
- package/src/jsonschema.ts +7 -1
- package/src/lang.ts +93 -6
- package/src/lsp.ts +8 -6
- package/src/mcp-server.ts +3 -2
- package/src/mcp.ts +6 -4
- package/src/mod-tool.ts +9 -8
- package/src/mod.ts +6 -6
- package/src/patch.ts +7 -0
- package/src/query.ts +8 -4
- package/src/reach.ts +7 -2
- package/src/relation.ts +7 -2
- package/src/sigdecl.ts +1 -1
- package/src/std.ts +2 -1
- package/src/subsume.ts +7 -2
- package/src/trim.ts +7 -2
- package/src/type.ts +8 -0
- package/src/utility.ts +27 -2
- package/src/val/EachFuncVal.ts +1 -3
- package/src/val/EmitFuncVal.ts +401 -0
- package/src/val/FilterFuncVal.ts +1 -3
- package/src/val/FuncBaseVal.ts +18 -0
- package/src/val/PackFuncVal.ts +1 -3
- package/src/val/PlaceVal.ts +8 -6
- package/src/val/StrFuncVal.ts +334 -0
- package/src/vet.ts +9 -3
- 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
|
|
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
|
|
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,
|
package/src/val/EachFuncVal.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/val/FilterFuncVal.ts
CHANGED
|
@@ -83,9 +83,7 @@ class FilterFuncVal extends FuncBaseVal {
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
unify(peer: Val, ctx: AontuContext): Val {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!ready || !ctx.settle) {
|
|
86
|
+
if (!this.stagedReady(peer, ctx, 1)) {
|
|
89
87
|
return this.residuate(peer, ctx)
|
|
90
88
|
}
|
|
91
89
|
|
package/src/val/FuncBaseVal.ts
CHANGED
|
@@ -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,
|
package/src/val/PackFuncVal.ts
CHANGED
|
@@ -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
|
-
|
|
118
|
-
|
|
119
|
-
if (!ready || !ctx.settle) {
|
|
117
|
+
if (!this.stagedReady(peer, ctx, 1)) {
|
|
120
118
|
return this.residuate(peer, ctx)
|
|
121
119
|
}
|
|
122
120
|
|
package/src/val/PlaceVal.ts
CHANGED
|
@@ -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)
|
|
77
|
-
// is that generator's to bind — "_ is the
|
|
78
|
-
// the hole test nor the fill walk may cross
|
|
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
|
|
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,
|