aontu 0.56.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 +5 -2
- package/dist/aontu.js +11 -3
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +8 -2
- package/dist/cli.js +564 -21
- 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/escape.d.ts +5 -0
- package/dist/escape.js +455 -0
- package/dist/escape.js.map +1 -0
- package/dist/format.d.ts +9 -0
- package/dist/format.js +550 -55
- package/dist/format.js.map +1 -1
- package/dist/hints.js +74 -9
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +374 -58
- 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 +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.d.ts +1 -0
- package/dist/mcp.js +40 -3
- 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/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 +9 -15
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +42 -0
- package/dist/val/EmitFuncVal.js +531 -0
- package/dist/val/EmitFuncVal.js.map +1 -0
- package/dist/val/FilterFuncVal.js +9 -6
- 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/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +16 -0
- package/dist/val/FuncBaseVal.js.map +1 -1
- 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 +23 -20
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +3 -1
- package/dist/val/PlaceVal.js +9 -6
- 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/StrFuncVal.d.ts +32 -0
- package/dist/val/StrFuncVal.js +292 -0
- package/dist/val/StrFuncVal.js.map +1 -0
- 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 +4 -3
- package/grammar/aontu.gbnf +4 -3
- package/grammar/aontu.lark +4 -3
- 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 +20 -2
- package/src/cli.ts +630 -23
- package/src/ctx.ts +13 -0
- package/src/escape.ts +371 -0
- package/src/format.ts +648 -56
- package/src/hints.ts +94 -9
- package/src/lang.ts +430 -63
- package/src/lower.ts +636 -0
- package/src/lsp.ts +4 -4
- package/src/mcp-server.ts +3 -2
- package/src/mcp.ts +43 -4
- package/src/mod-tool.ts +9 -8
- package/src/mod.ts +6 -6
- 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 +9 -19
- package/src/val/EmitFuncVal.ts +738 -0
- package/src/val/FilterFuncVal.ts +12 -7
- package/src/val/FormFuncVal.ts +119 -0
- package/src/val/FuncBaseVal.ts +18 -0
- package/src/val/MapVal.ts +3 -2
- package/src/val/PackFuncVal.ts +24 -22
- package/src/val/PlaceVal.ts +9 -6
- package/src/val/RefVal.ts +167 -18
- package/src/val/StrFuncVal.ts +334 -0
- package/src/val/Val.ts +42 -1
- package/src/val/members.ts +86 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// THE STRING BUILTINS the rule layer needs (G9 phase 6,
|
|
4
|
+
// docs/design/TEMPLATE.0.md D4 and D5).
|
|
5
|
+
//
|
|
6
|
+
// esc(s, variant?) make `s` safe inside a literal
|
|
7
|
+
// usc(s, variant?) read it back out
|
|
8
|
+
// rep(s, pattern, sub) replace every match
|
|
9
|
+
// split(s, sep) a list of fields
|
|
10
|
+
//
|
|
11
|
+
// All four are ORDINARY string builtins beside `upper` and `lower`:
|
|
12
|
+
// they return values, compose with `+`, and know nothing about
|
|
13
|
+
// generation, which is why they can land before the renderer does.
|
|
14
|
+
// They are not staged -- a scalar argument that is `done` is done.
|
|
15
|
+
//
|
|
16
|
+
// WHY `rep` AND `split` CARRY THEIR OWN MATCHING LOOPS. The host
|
|
17
|
+
// engines disagree about every part of this. `String.replace` reads
|
|
18
|
+
// `$1x` as group 1 then `x` while Go's Expand reads the name `1x`;
|
|
19
|
+
// `String.split` INSERTS a pattern's capture groups into the result
|
|
20
|
+
// and Go's Split does not; and the two disagree about empty matches at
|
|
21
|
+
// the ends. So the loops below are written once, in the semantics Go's
|
|
22
|
+
// regexp package defines, and go/strfunc.go is their twin -- the same
|
|
23
|
+
// discipline ADR-003 applies to the pattern language itself.
|
|
24
|
+
//
|
|
25
|
+
// THE PATTERN IS THE PORTABLE SUBSET `re()` TAKES, normalised before
|
|
26
|
+
// either engine compiles it. One regexp language in the document, not
|
|
27
|
+
// two -- and the subset's linear-time guarantee matters more here than
|
|
28
|
+
// in a constraint, because a generator runs this over model data.
|
|
29
|
+
|
|
30
|
+
import type {
|
|
31
|
+
Val,
|
|
32
|
+
ValSpec,
|
|
33
|
+
} from '../type'
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
AontuContext,
|
|
37
|
+
} from '../ctx'
|
|
38
|
+
|
|
39
|
+
import { makeNilErr } from '../err'
|
|
40
|
+
import { isEscVariant, escapeText, unescapeText } from '../escape'
|
|
41
|
+
import { StringVal } from './StringVal'
|
|
42
|
+
import { ListVal } from './ListVal'
|
|
43
|
+
import { FuncBaseVal } from './FuncBaseVal'
|
|
44
|
+
import { normaliseRe } from './ConstraintVal'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// The string a value carries, or undefined when it is not a string.
|
|
48
|
+
// Mirrors `stringLeaf` in ConstraintVal: a path is a string too, so a
|
|
49
|
+
// spelled address may be escaped and split like any other text.
|
|
50
|
+
function textOf(v: Val | undefined): string | undefined {
|
|
51
|
+
const s: any = v
|
|
52
|
+
return true === s?.isScalar && 'string' === typeof s.peg ? s.peg : undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// The variant an optional second argument names: '' for the absent
|
|
57
|
+
// argument (the C/JSON convention), or undefined when it is not a
|
|
58
|
+
// variant name.
|
|
59
|
+
function variantOf(v: Val | undefined): string | undefined {
|
|
60
|
+
if (null == v) { return '' }
|
|
61
|
+
const s = textOf(v)
|
|
62
|
+
return undefined !== s && isEscVariant(s) ? s : undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// The number of CAPTURING groups in a normalised pattern. Counted by
|
|
67
|
+
// scanning rather than by asking either host, because the count decides
|
|
68
|
+
// whether a substitution is refused and the two ports must refuse the
|
|
69
|
+
// same ones. The subset admits no named groups, so a capturing group is
|
|
70
|
+
// exactly `(` that is not `(?`.
|
|
71
|
+
function reGroupCount(norm: string): number {
|
|
72
|
+
let count = 0
|
|
73
|
+
let inClass = false
|
|
74
|
+
for (let i = 0; i < norm.length; i++) {
|
|
75
|
+
const c = norm[i]
|
|
76
|
+
if ('\\' === c) { i++; continue }
|
|
77
|
+
if (inClass) {
|
|
78
|
+
if (']' === c) { inClass = false }
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if ('[' === c) { inClass = true; continue }
|
|
82
|
+
if ('(' === c && '?' !== norm[i + 1]) { count++ }
|
|
83
|
+
}
|
|
84
|
+
return count
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
// One code point at `at`, as a count of UTF-16 units: the "advance by
|
|
89
|
+
// one rune" Go's matching loop performs after an empty match.
|
|
90
|
+
function stepAt(src: string, at: number): number {
|
|
91
|
+
const c = src.codePointAt(at)
|
|
92
|
+
return undefined === c ? 1 : (0xFFFF < c ? 2 : 1)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
// Every match of `re` in `src`, as [start, end, ...group pairs], under
|
|
97
|
+
// Go's rule: scanning resumes at the end of each match, and an empty
|
|
98
|
+
// match ADJACENT to the previous match's end is skipped rather than
|
|
99
|
+
// delivered. Written out because JavaScript has no equivalent and the
|
|
100
|
+
// difference is visible in `rep("aa", "a*", "-")`.
|
|
101
|
+
function allMatches(src: string, re: RegExp): RegExpExecArray[] {
|
|
102
|
+
const out: RegExpExecArray[] = []
|
|
103
|
+
// Its own scanner, so `lastIndex` is this walk's to move: the caller's
|
|
104
|
+
// regexp is shared with whatever else holds it.
|
|
105
|
+
const scan = new RegExp(re.source, re.flags)
|
|
106
|
+
|
|
107
|
+
let pos = 0
|
|
108
|
+
let prevEnd = -1
|
|
109
|
+
while (pos <= src.length) {
|
|
110
|
+
scan.lastIndex = pos
|
|
111
|
+
const m = scan.exec(src)
|
|
112
|
+
if (null === m) { break }
|
|
113
|
+
|
|
114
|
+
const start = m.index
|
|
115
|
+
const end = start + m[0].length
|
|
116
|
+
let accept = true
|
|
117
|
+
|
|
118
|
+
if (end === pos) {
|
|
119
|
+
if (start === prevEnd) { accept = false }
|
|
120
|
+
pos += stepAt(src, pos)
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
pos = end
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
prevEnd = end
|
|
127
|
+
if (accept) { out.push(m) }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return out
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
// A substitution template expanded against one match. `$1`..`$9` are
|
|
135
|
+
// the numbered groups, `$&` the whole match and `$$` a literal `$`;
|
|
136
|
+
// anything else after a `$` names nothing, and naming nothing is a
|
|
137
|
+
// REFUSAL rather than a silent literal. A group the pattern does not
|
|
138
|
+
// have is the same refusal -- a generator that expands it to the empty
|
|
139
|
+
// string writes a file with a hole in it and says nothing.
|
|
140
|
+
function expandSub(sub: string, m: RegExpExecArray, groups: number)
|
|
141
|
+
: string | undefined {
|
|
142
|
+
let out = ''
|
|
143
|
+
for (let i = 0; i < sub.length; i++) {
|
|
144
|
+
if ('$' !== sub[i]) { out += sub[i]; continue }
|
|
145
|
+
|
|
146
|
+
const n = sub[i + 1]
|
|
147
|
+
if ('$' === n) { out += '$'; i++; continue }
|
|
148
|
+
if ('&' === n) { out += m[0]; i++; continue }
|
|
149
|
+
|
|
150
|
+
if (undefined === n || '1' > n || '9' < n) { return undefined }
|
|
151
|
+
const g = n.charCodeAt(0) - 0x30
|
|
152
|
+
if (groups < g) { return undefined }
|
|
153
|
+
out += m[g] ?? ''
|
|
154
|
+
i++
|
|
155
|
+
}
|
|
156
|
+
return out
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// The fields `re` cuts `src` into, under Go's Split: an empty match at
|
|
161
|
+
// the very start opens no field, and a match ending at the end of the
|
|
162
|
+
// input closes none.
|
|
163
|
+
function splitRe(src: string, re: RegExp): string[] {
|
|
164
|
+
const out: string[] = []
|
|
165
|
+
let beg = 0
|
|
166
|
+
let end = 0
|
|
167
|
+
for (const m of allMatches(src, re)) {
|
|
168
|
+
end = m.index
|
|
169
|
+
if (0 !== m.index + m[0].length) {
|
|
170
|
+
out.push(src.slice(beg, end))
|
|
171
|
+
}
|
|
172
|
+
beg = m.index + m[0].length
|
|
173
|
+
}
|
|
174
|
+
if (end !== src.length) { out.push(src.slice(beg)) }
|
|
175
|
+
return out
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// An EMPTY separator yields the CODE POINTS, not the UTF-16 units a
|
|
180
|
+
// host split would give: `split("é", "")` is one field in both ports.
|
|
181
|
+
function splitLiteral(src: string, sep: string): string[] {
|
|
182
|
+
if ('' === sep) { return [...src] }
|
|
183
|
+
return src.split(sep)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
// The compiled form of a pattern argument, or the code naming what is
|
|
188
|
+
// wrong with it.
|
|
189
|
+
function compileRe(src: string): RegExp | string {
|
|
190
|
+
const [norm, why] = normaliseRe(src)
|
|
191
|
+
if ('' !== why) { return 'rep_pattern' }
|
|
192
|
+
try {
|
|
193
|
+
// The `u` flag is REQUIRED for parity, exactly as it is for `re()`:
|
|
194
|
+
// without it JavaScript counts UTF-16 units where Go counts code
|
|
195
|
+
// points.
|
|
196
|
+
return new RegExp(norm, 'gu')
|
|
197
|
+
}
|
|
198
|
+
catch (e: any) {
|
|
199
|
+
return 'rep_pattern'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class EscFuncVal extends FuncBaseVal {
|
|
205
|
+
isEscFunc = true
|
|
206
|
+
|
|
207
|
+
constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx) }
|
|
208
|
+
|
|
209
|
+
make(_ctx: AontuContext, spec: ValSpec): Val { return new EscFuncVal(spec) }
|
|
210
|
+
|
|
211
|
+
funcname() { return 'esc' }
|
|
212
|
+
|
|
213
|
+
resolve(ctx: AontuContext, args: Val[]) {
|
|
214
|
+
const src = textOf(args?.[0])
|
|
215
|
+
if (undefined === src) { return makeNilErr(ctx, 'invalid-arg', this) }
|
|
216
|
+
|
|
217
|
+
const variant = variantOf(args?.[1])
|
|
218
|
+
if (undefined === variant) { return makeNilErr(ctx, 'esc_variant', this) }
|
|
219
|
+
|
|
220
|
+
return this.place(new StringVal({ peg: escapeText(src, variant) }, ctx))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
} /* node:coverage ignore next 2 */
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class UscFuncVal extends FuncBaseVal {
|
|
227
|
+
isUscFunc = true
|
|
228
|
+
|
|
229
|
+
constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx) }
|
|
230
|
+
|
|
231
|
+
make(_ctx: AontuContext, spec: ValSpec): Val { return new UscFuncVal(spec) }
|
|
232
|
+
|
|
233
|
+
funcname() { return 'usc' }
|
|
234
|
+
|
|
235
|
+
resolve(ctx: AontuContext, args: Val[]) {
|
|
236
|
+
const src = textOf(args?.[0])
|
|
237
|
+
if (undefined === src) { return makeNilErr(ctx, 'invalid-arg', this) }
|
|
238
|
+
|
|
239
|
+
const variant = variantOf(args?.[1])
|
|
240
|
+
if (undefined === variant) { return makeNilErr(ctx, 'esc_variant', this) }
|
|
241
|
+
|
|
242
|
+
const [out, ok] = unescapeText(src, variant)
|
|
243
|
+
if (!ok) { return makeNilErr(ctx, 'usc_malformed', this) }
|
|
244
|
+
|
|
245
|
+
return this.place(new StringVal({ peg: out }, ctx))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
} /* node:coverage ignore next 2 */
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class RepFuncVal extends FuncBaseVal {
|
|
252
|
+
isRepFunc = true
|
|
253
|
+
|
|
254
|
+
constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx) }
|
|
255
|
+
|
|
256
|
+
make(_ctx: AontuContext, spec: ValSpec): Val { return new RepFuncVal(spec) }
|
|
257
|
+
|
|
258
|
+
funcname() { return 'rep' }
|
|
259
|
+
|
|
260
|
+
resolve(ctx: AontuContext, args: Val[]) {
|
|
261
|
+
const src = textOf(args?.[0])
|
|
262
|
+
const pat = textOf(args?.[1])
|
|
263
|
+
const sub = textOf(args?.[2])
|
|
264
|
+
if (undefined === src || undefined === pat || undefined === sub) {
|
|
265
|
+
return makeNilErr(ctx, 'invalid-arg', this)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const re = compileRe(pat)
|
|
269
|
+
if ('string' === typeof re) { return makeNilErr(ctx, re, this) }
|
|
270
|
+
|
|
271
|
+
const groups = reGroupCount(re.source)
|
|
272
|
+
|
|
273
|
+
let out = ''
|
|
274
|
+
let at = 0
|
|
275
|
+
for (const m of allMatches(src, re)) {
|
|
276
|
+
const piece = expandSub(sub, m, groups)
|
|
277
|
+
if (undefined === piece) { return makeNilErr(ctx, 'rep_sub', this) }
|
|
278
|
+
out += src.slice(at, m.index) + piece
|
|
279
|
+
at = m.index + m[0].length
|
|
280
|
+
}
|
|
281
|
+
out += src.slice(at)
|
|
282
|
+
|
|
283
|
+
return this.place(new StringVal({ peg: out }, ctx))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
} /* node:coverage ignore next 2 */
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class SplitFuncVal extends FuncBaseVal {
|
|
290
|
+
isSplitFunc = true
|
|
291
|
+
|
|
292
|
+
constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx) }
|
|
293
|
+
|
|
294
|
+
make(_ctx: AontuContext, spec: ValSpec): Val { return new SplitFuncVal(spec) }
|
|
295
|
+
|
|
296
|
+
funcname() { return 'split' }
|
|
297
|
+
|
|
298
|
+
resolve(ctx: AontuContext, args: Val[]) {
|
|
299
|
+
const src = textOf(args?.[0])
|
|
300
|
+
if (undefined === src) { return makeNilErr(ctx, 'invalid-arg', this) }
|
|
301
|
+
|
|
302
|
+
// A PLAIN STRING IS A LITERAL and an `re(…)` is a pattern. The
|
|
303
|
+
// asymmetry with `rep` is deliberate: splitting is usually on a
|
|
304
|
+
// literal, replacing is usually by pattern, and it removes the trap
|
|
305
|
+
// where `split(v, ".")` silently cuts between every character.
|
|
306
|
+
const sep: any = args?.[1]
|
|
307
|
+
const lit = textOf(sep)
|
|
308
|
+
|
|
309
|
+
let fields: string[]
|
|
310
|
+
if (undefined !== lit) {
|
|
311
|
+
fields = splitLiteral(src, lit)
|
|
312
|
+
}
|
|
313
|
+
else if (true === sep?.isConstraint && 1 === sep.res?.length) {
|
|
314
|
+
fields = splitRe(src, new RegExp(sep.res[0].re.source, 'gu'))
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
return makeNilErr(ctx, 'split_sep', this)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const peg = fields.map((f, i) =>
|
|
321
|
+
new StringVal({ peg: f }, ctx.descend(String(i))))
|
|
322
|
+
|
|
323
|
+
return this.place(new ListVal({ peg }, ctx))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
} /* node:coverage ignore next 8 */
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
export {
|
|
330
|
+
EscFuncVal,
|
|
331
|
+
UscFuncVal,
|
|
332
|
+
RepFuncVal,
|
|
333
|
+
SplitFuncVal,
|
|
334
|
+
}
|
package/src/val/Val.ts
CHANGED
|
@@ -11,6 +11,16 @@ import {
|
|
|
11
11
|
import { INNER_OF, WRITTEN } from '../provenance'
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
// THE DISPATCH RECORD (RENDER.0.md D11, P7): the address of the node
|
|
15
|
+
// a rule matched, and the address of the rule that matched it -- the
|
|
16
|
+
// table's own address and the rule's index in it, joined by `#`,
|
|
17
|
+
// which no path holds.
|
|
18
|
+
type EmitOrigin = {
|
|
19
|
+
node: string,
|
|
20
|
+
rule: string,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
14
24
|
type ValMark = {
|
|
15
25
|
type: boolean,
|
|
16
26
|
hide: boolean,
|
|
@@ -168,6 +178,24 @@ abstract class Val {
|
|
|
168
178
|
// and the edge set (ts/src/graph.ts) is exactly the set of these.
|
|
169
179
|
link?: string
|
|
170
180
|
|
|
181
|
+
// THE READ ADDRESS (RENDER.0.md P7): the tree path a reference
|
|
182
|
+
// resolved this value AT, stamped where it was found. A resolved
|
|
183
|
+
// reference CLONES its target into the referring position, so a
|
|
184
|
+
// value that arrived by reference otherwise knows only where it came
|
|
185
|
+
// to rest -- and a trace saying a line came from `$.code.units.0`
|
|
186
|
+
// names the output, not the model. Written only by an instrumented
|
|
187
|
+
// run (AontuContext.reads), carried by clone and by the meet rider,
|
|
188
|
+
// exactly as the deprecation record is.
|
|
189
|
+
origin?: string
|
|
190
|
+
|
|
191
|
+
// THE DISPATCH THAT PRODUCED THIS PIECE (RENDER.0.md D11, P7): the
|
|
192
|
+
// node an `emit` matched and the rule it took, stamped on every
|
|
193
|
+
// piece the dispatch instantiated. The INNERMOST dispatch wins: a
|
|
194
|
+
// nested rule set splices its pieces into the outer result, and the
|
|
195
|
+
// rule that wrote a line is the one that wrote it. Written only by
|
|
196
|
+
// an instrumented run, and carried like `origin`.
|
|
197
|
+
emitted?: EmitOrigin
|
|
198
|
+
|
|
171
199
|
// The GRAPH of an evaluated document (G4 phase 3): the edge set,
|
|
172
200
|
// stamped on the result by Aontu.unify the way the include manifest
|
|
173
201
|
// is. Absent on every Val that is not a unify result.
|
|
@@ -301,6 +329,18 @@ abstract class Val {
|
|
|
301
329
|
out.deprecation = this.deprecation
|
|
302
330
|
}
|
|
303
331
|
|
|
332
|
+
// THE RENDER RIDERS TRAVEL WITH THE CLONE (P7), for the reason the
|
|
333
|
+
// deprecation record does: a clone of a value read at `$.schema`
|
|
334
|
+
// was read at `$.schema`, and a clone of an emitted piece is still
|
|
335
|
+
// that dispatch's. Both are absent unless the run is instrumented,
|
|
336
|
+
// so this is two undefined reads otherwise.
|
|
337
|
+
if (null != this.origin) {
|
|
338
|
+
out.origin = this.origin
|
|
339
|
+
}
|
|
340
|
+
if (null != this.emitted) {
|
|
341
|
+
out.emitted = this.emitted
|
|
342
|
+
}
|
|
343
|
+
|
|
304
344
|
// THE APPLY-ONCE MARK TRAVELS WITH THE CLONE. `_spr` records which
|
|
305
345
|
// spread template has already been merged into this value, and the
|
|
306
346
|
// bag loops read it to keep a template from being applied twice
|
|
@@ -670,10 +710,11 @@ function empty(o: any) {
|
|
|
670
710
|
|| (null != o && 'object' === typeof o && 0 === Object.keys(o).length)
|
|
671
711
|
|| false
|
|
672
712
|
)
|
|
673
|
-
} /* node:coverage ignore next
|
|
713
|
+
} /* node:coverage ignore next 18 */
|
|
674
714
|
|
|
675
715
|
|
|
676
716
|
export type {
|
|
717
|
+
EmitOrigin,
|
|
677
718
|
ValMark,
|
|
678
719
|
ValSpec,
|
|
679
720
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// THE MEMBERS OF A BAG, as every fold and every generator over a bag
|
|
5
|
+
// sees them (use-cases/BUGS.md §79; G9 phase 0 item 4). A member is a
|
|
6
|
+
// child that generation would EMIT: a hide()- or type()-marked child
|
|
7
|
+
// is not one, an alias declaration is not one, and an optional key
|
|
8
|
+
// whose value generates nothing -- an unfilled `y?: string`, an absent
|
|
9
|
+
// optional subtree -- is not one. `each`, `emit`, `filter`, `pick`,
|
|
10
|
+
// `join` and the aggregates read a bag through this one function, so a
|
|
11
|
+
// value the document withholds from its output is withheld from every
|
|
12
|
+
// text and every total the document computes from it: a hidden field
|
|
13
|
+
// that reached generated source through `join` is the defect this
|
|
14
|
+
// closes, and it was one behaviour in both ports because each verb
|
|
15
|
+
// had its own enumeration and none of them asked the mark.
|
|
16
|
+
//
|
|
17
|
+
// Maps list their members in code-point key order (../keyorder.ts),
|
|
18
|
+
// lists in index order -- the one order canon, generation and the Go
|
|
19
|
+
// port agree on (BUGS.md §62). Twin of bagMembers in go/members.go.
|
|
20
|
+
|
|
21
|
+
import type { Val } from '../type'
|
|
22
|
+
import { AontuContext } from '../ctx'
|
|
23
|
+
import { cmpCodePoint } from '../keyorder'
|
|
24
|
+
import { empty } from './Val'
|
|
25
|
+
import { bagGenable } from './BagVal'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
type Member = { key: string, val: Val }
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
function bagMembers(data: any, ctx: AontuContext): Member[] | undefined {
|
|
32
|
+
if (true !== data?.isMap && true !== data?.isList) {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const keys: string[] = true === data.isMap ?
|
|
37
|
+
Object.keys(data.peg).sort(cmpCodePoint) :
|
|
38
|
+
(data.peg as Val[]).map((_v: Val, i: number) => '' + i)
|
|
39
|
+
|
|
40
|
+
// A member marked while its bag is not was marked in its own right,
|
|
41
|
+
// and is left out as generation leaves it out. Under a MARKED bag
|
|
42
|
+
// every child carries the mark (hide() and type() mark to the
|
|
43
|
+
// leaves), so there the mark says nothing about the member and every
|
|
44
|
+
// child is one -- the members of a hidden bag are what the bag
|
|
45
|
+
// holds, exactly as a reference to it lifts them.
|
|
46
|
+
const lifted = true === data.mark.hide || true === data.mark.type
|
|
47
|
+
|
|
48
|
+
const out: Member[] = []
|
|
49
|
+
for (const key of keys) {
|
|
50
|
+
const val: any = data.peg[key]
|
|
51
|
+
if ((!lifted && (true === val.mark.hide || true === val.mark.type))
|
|
52
|
+
|| data.aliasKeys.includes(key)) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if (data.optionalKeys.includes(key) && !filled(val, ctx)) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
out.push({ key, val })
|
|
59
|
+
}
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// An optional child is a member when it generates something, decided
|
|
65
|
+
// as BagVal.gen decides it: in an isolated collect context, so residue
|
|
66
|
+
// inside an absent optional subtree is dropped rather than raised.
|
|
67
|
+
function filled(val: any, ctx: AontuContext): boolean {
|
|
68
|
+
if (!bagGenable(val)) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
const cval = val.gen(ctx.clone({ err: [], collect: true }))
|
|
72
|
+
return undefined !== cval && !empty(cval)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// The member values alone, for the verbs that do not need the keys.
|
|
77
|
+
function memberVals(data: any, ctx: AontuContext): Val[] | undefined {
|
|
78
|
+
const members = bagMembers(data, ctx)
|
|
79
|
+
return undefined === members ? undefined : members.map((m) => m.val)
|
|
80
|
+
} /* node:coverage ignore next 6 */
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
bagMembers,
|
|
85
|
+
memberVals,
|
|
86
|
+
}
|