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.
Files changed (138) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +5 -2
  7. package/dist/aontu.js +11 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +8 -2
  10. package/dist/cli.js +564 -21
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +2 -0
  13. package/dist/ctx.js +1 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/escape.d.ts +5 -0
  16. package/dist/escape.js +455 -0
  17. package/dist/escape.js.map +1 -0
  18. package/dist/format.d.ts +9 -0
  19. package/dist/format.js +550 -55
  20. package/dist/format.js.map +1 -1
  21. package/dist/hints.js +74 -9
  22. package/dist/hints.js.map +1 -1
  23. package/dist/lang.js +374 -58
  24. package/dist/lang.js.map +1 -1
  25. package/dist/lower.d.ts +20 -0
  26. package/dist/lower.js +575 -0
  27. package/dist/lower.js.map +1 -0
  28. package/dist/lsp.d.ts +1 -1
  29. package/dist/lsp.js +4 -4
  30. package/dist/lsp.js.map +1 -1
  31. package/dist/mcp-server.js +2 -2
  32. package/dist/mcp-server.js.map +1 -1
  33. package/dist/mcp.d.ts +1 -0
  34. package/dist/mcp.js +40 -3
  35. package/dist/mcp.js.map +1 -1
  36. package/dist/mod-tool.js +8 -7
  37. package/dist/mod-tool.js.map +1 -1
  38. package/dist/mod.js +6 -6
  39. package/dist/mod.js.map +1 -1
  40. package/dist/render.d.ts +53 -0
  41. package/dist/render.js +542 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/sigdecl.js +1 -1
  44. package/dist/sigdecl.js.map +1 -1
  45. package/dist/std.d.ts +2 -0
  46. package/dist/std.js +498 -2
  47. package/dist/std.js.map +1 -1
  48. package/dist/template.d.ts +5 -0
  49. package/dist/template.js +257 -0
  50. package/dist/template.js.map +1 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/dist/unify.js +43 -0
  53. package/dist/unify.js.map +1 -1
  54. package/dist/val/AggFuncVal.d.ts +1 -1
  55. package/dist/val/AggFuncVal.js +10 -21
  56. package/dist/val/AggFuncVal.js.map +1 -1
  57. package/dist/val/BagVal.js +1 -1
  58. package/dist/val/BagVal.js.map +1 -1
  59. package/dist/val/ConstraintVal.js +1 -1
  60. package/dist/val/EachFuncVal.d.ts +1 -1
  61. package/dist/val/EachFuncVal.js +9 -15
  62. package/dist/val/EachFuncVal.js.map +1 -1
  63. package/dist/val/EmitFuncVal.d.ts +42 -0
  64. package/dist/val/EmitFuncVal.js +531 -0
  65. package/dist/val/EmitFuncVal.js.map +1 -0
  66. package/dist/val/FilterFuncVal.js +9 -6
  67. package/dist/val/FilterFuncVal.js.map +1 -1
  68. package/dist/val/FormFuncVal.d.ts +14 -0
  69. package/dist/val/FormFuncVal.js +55 -0
  70. package/dist/val/FormFuncVal.js.map +1 -0
  71. package/dist/val/FuncBaseVal.d.ts +1 -0
  72. package/dist/val/FuncBaseVal.js +16 -0
  73. package/dist/val/FuncBaseVal.js.map +1 -1
  74. package/dist/val/MapVal.d.ts +2 -1
  75. package/dist/val/MapVal.js +2 -1
  76. package/dist/val/MapVal.js.map +1 -1
  77. package/dist/val/PackFuncVal.d.ts +1 -1
  78. package/dist/val/PackFuncVal.js +23 -20
  79. package/dist/val/PackFuncVal.js.map +1 -1
  80. package/dist/val/PlaceVal.d.ts +3 -1
  81. package/dist/val/PlaceVal.js +9 -6
  82. package/dist/val/PlaceVal.js.map +1 -1
  83. package/dist/val/RefVal.d.ts +3 -0
  84. package/dist/val/RefVal.js +156 -17
  85. package/dist/val/RefVal.js.map +1 -1
  86. package/dist/val/StrFuncVal.d.ts +32 -0
  87. package/dist/val/StrFuncVal.js +292 -0
  88. package/dist/val/StrFuncVal.js.map +1 -0
  89. package/dist/val/Val.d.ts +7 -1
  90. package/dist/val/Val.js +12 -1
  91. package/dist/val/Val.js.map +1 -1
  92. package/dist/val/members.d.ts +9 -0
  93. package/dist/val/members.js +52 -0
  94. package/dist/val/members.js.map +1 -0
  95. package/grammar/aontu.abnf +4 -3
  96. package/grammar/aontu.gbnf +4 -3
  97. package/grammar/aontu.lark +4 -3
  98. package/grammar/aontu.tmLanguage.json +1 -1
  99. package/package.json +1 -1
  100. package/skill/SKILL.md +4 -4
  101. package/skill/error-codes.md +1 -1
  102. package/skill/examples.md +1 -1
  103. package/skill/grammar-card.md +1 -1
  104. package/src/agentsmd.ts +1 -1
  105. package/src/alias.ts +112 -0
  106. package/src/aontu.ts +20 -2
  107. package/src/cli.ts +630 -23
  108. package/src/ctx.ts +13 -0
  109. package/src/escape.ts +371 -0
  110. package/src/format.ts +648 -56
  111. package/src/hints.ts +94 -9
  112. package/src/lang.ts +430 -63
  113. package/src/lower.ts +636 -0
  114. package/src/lsp.ts +4 -4
  115. package/src/mcp-server.ts +3 -2
  116. package/src/mcp.ts +43 -4
  117. package/src/mod-tool.ts +9 -8
  118. package/src/mod.ts +6 -6
  119. package/src/render.ts +727 -0
  120. package/src/sigdecl.ts +1 -1
  121. package/src/std.ts +506 -1
  122. package/src/template.ts +291 -0
  123. package/src/unify.ts +47 -0
  124. package/src/val/AggFuncVal.ts +10 -21
  125. package/src/val/BagVal.ts +1 -1
  126. package/src/val/ConstraintVal.ts +1 -1
  127. package/src/val/EachFuncVal.ts +9 -19
  128. package/src/val/EmitFuncVal.ts +738 -0
  129. package/src/val/FilterFuncVal.ts +12 -7
  130. package/src/val/FormFuncVal.ts +119 -0
  131. package/src/val/FuncBaseVal.ts +18 -0
  132. package/src/val/MapVal.ts +3 -2
  133. package/src/val/PackFuncVal.ts +24 -22
  134. package/src/val/PlaceVal.ts +9 -6
  135. package/src/val/RefVal.ts +167 -18
  136. package/src/val/StrFuncVal.ts +334 -0
  137. package/src/val/Val.ts +42 -1
  138. 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 17 */
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
+ }