aontu 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +4 -2
  7. package/dist/aontu.js +11 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +475 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +2 -0
  13. package/dist/ctx.js +1 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/format.js +8 -2
  16. package/dist/format.js.map +1 -1
  17. package/dist/hints.js +40 -9
  18. package/dist/hints.js.map +1 -1
  19. package/dist/lang.js +354 -57
  20. package/dist/lang.js.map +1 -1
  21. package/dist/lower.d.ts +20 -0
  22. package/dist/lower.js +575 -0
  23. package/dist/lower.js.map +1 -0
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +1 -1
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp-server.js +1 -1
  28. package/dist/mcp.d.ts +1 -0
  29. package/dist/mcp.js +40 -3
  30. package/dist/mcp.js.map +1 -1
  31. package/dist/render.d.ts +53 -0
  32. package/dist/render.js +542 -0
  33. package/dist/render.js.map +1 -0
  34. package/dist/sigdecl.js +1 -1
  35. package/dist/sigdecl.js.map +1 -1
  36. package/dist/std.d.ts +2 -0
  37. package/dist/std.js +498 -2
  38. package/dist/std.js.map +1 -1
  39. package/dist/template.d.ts +5 -0
  40. package/dist/template.js +257 -0
  41. package/dist/template.js.map +1 -0
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/dist/unify.js +43 -0
  44. package/dist/unify.js.map +1 -1
  45. package/dist/val/AggFuncVal.d.ts +1 -1
  46. package/dist/val/AggFuncVal.js +10 -21
  47. package/dist/val/AggFuncVal.js.map +1 -1
  48. package/dist/val/BagVal.js +1 -1
  49. package/dist/val/BagVal.js.map +1 -1
  50. package/dist/val/ConstraintVal.js +1 -1
  51. package/dist/val/EachFuncVal.d.ts +1 -1
  52. package/dist/val/EachFuncVal.js +8 -13
  53. package/dist/val/EachFuncVal.js.map +1 -1
  54. package/dist/val/EmitFuncVal.d.ts +23 -4
  55. package/dist/val/EmitFuncVal.js +298 -28
  56. package/dist/val/EmitFuncVal.js.map +1 -1
  57. package/dist/val/FilterFuncVal.js +8 -4
  58. package/dist/val/FilterFuncVal.js.map +1 -1
  59. package/dist/val/FormFuncVal.d.ts +14 -0
  60. package/dist/val/FormFuncVal.js +55 -0
  61. package/dist/val/FormFuncVal.js.map +1 -0
  62. package/dist/val/MapVal.d.ts +2 -1
  63. package/dist/val/MapVal.js +2 -1
  64. package/dist/val/MapVal.js.map +1 -1
  65. package/dist/val/PackFuncVal.d.ts +1 -1
  66. package/dist/val/PackFuncVal.js +22 -18
  67. package/dist/val/PackFuncVal.js.map +1 -1
  68. package/dist/val/PlaceVal.js +2 -1
  69. package/dist/val/PlaceVal.js.map +1 -1
  70. package/dist/val/RefVal.d.ts +3 -0
  71. package/dist/val/RefVal.js +156 -17
  72. package/dist/val/RefVal.js.map +1 -1
  73. package/dist/val/Val.d.ts +7 -1
  74. package/dist/val/Val.js +12 -1
  75. package/dist/val/Val.js.map +1 -1
  76. package/dist/val/members.d.ts +9 -0
  77. package/dist/val/members.js +52 -0
  78. package/dist/val/members.js.map +1 -0
  79. package/grammar/aontu.abnf +1 -1
  80. package/grammar/aontu.gbnf +1 -1
  81. package/grammar/aontu.lark +1 -1
  82. package/grammar/aontu.tmLanguage.json +1 -1
  83. package/package.json +1 -1
  84. package/skill/SKILL.md +4 -4
  85. package/skill/error-codes.md +1 -1
  86. package/skill/examples.md +1 -1
  87. package/skill/grammar-card.md +1 -1
  88. package/src/agentsmd.ts +1 -1
  89. package/src/alias.ts +112 -0
  90. package/src/aontu.ts +19 -2
  91. package/src/cli.ts +517 -3
  92. package/src/ctx.ts +13 -0
  93. package/src/format.ts +12 -2
  94. package/src/hints.ts +47 -9
  95. package/src/lang.ts +406 -62
  96. package/src/lower.ts +636 -0
  97. package/src/lsp.ts +1 -1
  98. package/src/mcp-server.ts +1 -1
  99. package/src/mcp.ts +43 -4
  100. package/src/render.ts +727 -0
  101. package/src/sigdecl.ts +1 -1
  102. package/src/std.ts +506 -1
  103. package/src/template.ts +291 -0
  104. package/src/unify.ts +47 -0
  105. package/src/val/AggFuncVal.ts +10 -21
  106. package/src/val/BagVal.ts +1 -1
  107. package/src/val/ConstraintVal.ts +1 -1
  108. package/src/val/EachFuncVal.ts +8 -16
  109. package/src/val/EmitFuncVal.ts +376 -39
  110. package/src/val/FilterFuncVal.ts +11 -4
  111. package/src/val/FormFuncVal.ts +119 -0
  112. package/src/val/MapVal.ts +3 -2
  113. package/src/val/PackFuncVal.ts +23 -19
  114. package/src/val/PlaceVal.ts +2 -1
  115. package/src/val/RefVal.ts +167 -18
  116. package/src/val/Val.ts +42 -1
  117. package/src/val/members.ts +86 -0
@@ -0,0 +1,291 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+
4
+ // THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8).
5
+ // A generator written in the TARGET's own syntax: a marked line is
6
+ // aontu source, and every other line is a line of output.
7
+ //
8
+ // //- code: units: [{ path: "hi.ts", lang: "typescript", decls: [{
9
+ // //- k: "frag", of: emit($.svc, { match: { n: string }, body: [
10
+ // export const NAME = "world"
11
+ // //- ]}) }] }]
12
+ //
13
+ // That is the whole desugaring, and it is LINE-ORIENTED: nothing here
14
+ // parses aontu, and nothing here reads the target's syntax beyond its
15
+ // comment token. A marker line contributes its text to the document; an
16
+ // unmarked line contributes one quoted string, which is a body element
17
+ // where the marker lines left a list open.
18
+ //
19
+ // WHY A COMMENT TOKEN PLUS A DASH (D2). The generator file must stay
20
+ // valid in its own language -- so an editor highlights it, a compiler
21
+ // parses it, and a reviewer reads it -- and the one thing every
22
+ // language has that the target's own tools already ignore is a
23
+ // comment. Adding a dash distinguishes a marker from an ordinary
24
+ // comment, and derives for a language this table has never seen: the
25
+ // caller passes the token.
26
+ //
27
+ // A MARKER IS RECOGNISED AFTER LEADING WHITESPACE AND KEEPS ITS OWN
28
+ // INDENTATION (D2). That is what lets a template be written exactly
29
+ // where its output appears (D7): a body line is verbatim, so a line
30
+ // indented two spaces in the template is indented two spaces in the
31
+ // generated file, and the marker lines around it are indented to match
32
+ // the code they sit in rather than to the aontu they carry.
33
+ //
34
+ // THE SUGAR IS THE FIXPOINT OF THE TWO TRANSFORMS (D6), and that is
35
+ // the whole of the resugaring's safety. A canonical line that looks
36
+ // like a body element is rebuilt as a target line, the rebuild is
37
+ // desugared, and the rebuild is taken ONLY if that reproduces the
38
+ // canonical line. A line no target line can carry -- one that would
39
+ // itself read as a marker -- fails that test and stays where it is, so
40
+ // there is no table of escapes to get wrong and no line the round trip
41
+ // can lose.
42
+ //
43
+ // WHITESPACE CONTROL IS ABSENT AND UNNEEDED (D8): a marker line is not
44
+ // an output line at all, so no construct can leak a newline into the
45
+ // output. What IS significant is the template's own trailing
46
+ // whitespace -- a line of two spaces is two spaces of output -- which
47
+ // is why the canonical form quotes every body line explicitly. Note
48
+ // what `template --check` does and does not catch: it holds the FILE
49
+ // to the spelling these two transforms answer, so a marker written
50
+ // without its space or with its aontu indented after it is named; a
51
+ // body line an editor trimmed is still a valid template, and what
52
+ // names that is `render --check` against the committed output.
53
+
54
+
55
+ // A marker is a comment token plus a dash. These are the tokens the
56
+ // verb derives from a file's extension; any other language passes its
57
+ // own, which is why the surface needs no table of languages.
58
+ const MARKERS: Record<string, string> = {
59
+ c: '//-',
60
+ cc: '//-',
61
+ cpp: '//-',
62
+ cs: '//-',
63
+ css: '/*-',
64
+ go: '//-',
65
+ h: '//-',
66
+ hs: '---',
67
+ java: '//-',
68
+ js: '//-',
69
+ jsx: '//-',
70
+ kt: '//-',
71
+ lua: '---',
72
+ php: '//-',
73
+ pl: '#-',
74
+ py: '#-',
75
+ rb: '#-',
76
+ rs: '//-',
77
+ scala: '//-',
78
+ sh: '#-',
79
+ sql: '---',
80
+ swift: '//-',
81
+ toml: '#-',
82
+ ts: '//-',
83
+ tsx: '//-',
84
+ yaml: '#-',
85
+ yml: '#-',
86
+ }
87
+
88
+ // The default marker, for a file whose extension names no token: the
89
+ // C-family line comment, which is the one the note is written in.
90
+ const DEFAULT_MARKER = '//-'
91
+
92
+ // A BLOCK MARKER CLOSES ITSELF. A language with no line comment (CSS
93
+ // is the one in the table) marks with `/*- … */`, and the closer is
94
+ // implied by the opener rather than named separately: there is one
95
+ // block form, and a marker that opens `/*-` ends `*/`.
96
+ const BLOCK_CLOSE = '*/'
97
+
98
+
99
+ // The marker for a file, by its extension, or the C-family default.
100
+ // The extension decides, exactly as it decides what an include is
101
+ // (ADR-012) -- one rule, and no flag to remember for the common case.
102
+ function markerFor(path: string): string {
103
+ const dot = path.lastIndexOf('.')
104
+ const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
105
+ const ext = dot > slash ? path.slice(dot + 1).toLowerCase() : ''
106
+ return MARKERS[ext] ?? DEFAULT_MARKER
107
+ }
108
+
109
+
110
+ // Is this marker the block form?
111
+ function isBlock(marker: string): boolean {
112
+ return marker.startsWith('/*')
113
+ }
114
+
115
+
116
+ // INDENTATION IS SPACES AND TABS, and nothing else. A host trim is not
117
+ // the same set in the two ports -- JavaScript's trims every Unicode
118
+ // space, Go's `TrimSpace` trims a different list -- and a line of a
119
+ // template file that began with one of the characters they disagree
120
+ // about would be a marker line in one engine and a line of output in
121
+ // the other. Twin of templateIndent in go/template.go.
122
+ function indentOf(line: string): number {
123
+ let i = 0
124
+ while (i < line.length && (' ' === line[i] || '\t' === line[i])) {
125
+ i++
126
+ }
127
+ return i
128
+ }
129
+
130
+
131
+ // The line without its indentation or its trailing spaces and tabs.
132
+ function trimLine(line: string): string {
133
+ let end = line.length
134
+ while (0 < end && (' ' === line[end - 1] || '\t' === line[end - 1])) {
135
+ end--
136
+ }
137
+ return line.slice(indentOf(line), end)
138
+ }
139
+
140
+
141
+ // One line of a template, read: its indentation, whether it is a
142
+ // marker, and the text it carries -- the aontu source for a marker,
143
+ // the output line for anything else.
144
+ type Line = {
145
+ marker: boolean
146
+ indent: string
147
+ text: string
148
+ }
149
+
150
+
151
+ function readLine(line: string, marker: string): Line {
152
+ const cut = indentOf(line)
153
+ const indent = line.slice(0, cut)
154
+ const rest = line.slice(cut)
155
+ if (!rest.startsWith(marker)) {
156
+ return { marker: false, indent: '', text: line }
157
+ }
158
+ let body = rest.slice(marker.length)
159
+ // The block form's closer is part of the marker, not of the aontu.
160
+ // A block marker line that never closes is not a marker line: the
161
+ // language's own parser would not read it as a comment either.
162
+ if (isBlock(marker)) {
163
+ const end = body.lastIndexOf(BLOCK_CLOSE)
164
+ if (end < 0) {
165
+ return { marker: false, indent: '', text: line }
166
+ }
167
+ body = trimLine(body.slice(0, end))
168
+ }
169
+ // ONE SPACE AFTER THE MARKER IS THE MARKER'S, so `//- x: 1` carries
170
+ // `x: 1` and the resugaring writes the space back. A marker written
171
+ // without it carries the same aontu and is normalised on the round
172
+ // trip, which `--check` reports as the drift it is.
173
+ if (body.startsWith(' ')) {
174
+ body = body.slice(1)
175
+ }
176
+ return { marker: true, indent, text: body }
177
+ }
178
+
179
+
180
+ // THE CANONICAL QUOTE IS CHOSEN PER LINE (D6). A backtick string
181
+ // carries `"` and `'` unescaped, which is most of what target code
182
+ // holds, so a line takes one unless it holds a backtick itself.
183
+ //
184
+ // A BACKSLASH IS ESCAPED IN EITHER QUOTE, and that is not what the
185
+ // design note assumed: this engine reads the escapes of a backtick
186
+ // string exactly as it reads a quoted one, and an unknown escape drops
187
+ // its backslash (`\p` is `p`), so a target line holding `\n` inside a
188
+ // string of its own would arrive in the output as a newline. Only the
189
+ // delimiter differs between the two forms.
190
+ function quoteLine(text: string): string {
191
+ const escaped = text.replace(/\\/g, '\\\\')
192
+ return text.includes('`') ?
193
+ '"' + escaped.replace(/"/g, '\\"') + '"' :
194
+ '`' + escaped + '`'
195
+ }
196
+
197
+
198
+ // The line a quoted body element carries, or undefined when the line
199
+ // is not one: anything but a lone string literal, and any escape the
200
+ // quoting above does not write. The fixpoint test below is what makes
201
+ // being conservative here safe -- a line this refuses stays aontu.
202
+ function unquoteLine(text: string): string | undefined {
203
+ const t = trimLine(text)
204
+ if (2 > t.length) {
205
+ return undefined
206
+ }
207
+ const quote = t[0]
208
+ if (('`' !== quote && '"' !== quote) || t[t.length - 1] !== quote) {
209
+ return undefined
210
+ }
211
+ const inner = t.slice(1, -1)
212
+ let out = ''
213
+ for (let i = 0; i < inner.length; i++) {
214
+ const c = inner[i]
215
+ if (quote === c) {
216
+ return undefined
217
+ }
218
+ if ('\\' !== c) {
219
+ out += c
220
+ continue
221
+ }
222
+ const n = inner[++i]
223
+ if ('\\' === n || (('"' === n) && '"' === quote)) {
224
+ out += n
225
+ continue
226
+ }
227
+ return undefined
228
+ }
229
+ return out
230
+ }
231
+
232
+
233
+ // DESUGAR: a template file becomes the canonical aontu document.
234
+ // A marker line is its own text, at its own indentation; every other
235
+ // line is one quoted string, which lands wherever the marker lines
236
+ // left a list open.
237
+ function desugarTemplate(src: string, marker?: string): string {
238
+ const mark = marker ?? DEFAULT_MARKER
239
+ const lines = src.split('\n')
240
+ // A trailing newline is the file's, not a line of output: a text
241
+ // file ends with one, and the round trip must not grow an empty
242
+ // output line each pass.
243
+ const tail = 1 < lines.length && '' === lines[lines.length - 1]
244
+ if (tail) {
245
+ lines.pop()
246
+ }
247
+ const out = lines.map((line) => {
248
+ const read = readLine(line, mark)
249
+ return read.marker ? read.indent + read.text : quoteLine(read.text)
250
+ })
251
+ return out.join('\n') + (tail ? '\n' : '')
252
+ }
253
+
254
+
255
+ // RESUGAR: the canonical document becomes a template file. Every line
256
+ // is a marker line unless it is a body element that survives the
257
+ // fixpoint -- rebuild the target line, desugar the rebuild, and take
258
+ // it only if that answers the canonical line back.
259
+ function resugarTemplate(src: string, marker?: string): string {
260
+ const mark = marker ?? DEFAULT_MARKER
261
+ const lines = src.split('\n')
262
+ const tail = 1 < lines.length && '' === lines[lines.length - 1]
263
+ if (tail) {
264
+ lines.pop()
265
+ }
266
+ const out = lines.map((line) => {
267
+ // THE ELEMENT'S OWN INDENTATION IS NOT PART OF IT: the target line
268
+ // is the string's content, and where the canonical form puts the
269
+ // element on the page is the formatter's business. Comparing the
270
+ // whole line instead would refuse every body element of a document
271
+ // `aontu fmt` had indented, which is every document it has seen.
272
+ const target = unquoteLine(line)
273
+ if (undefined !== target && desugarTemplate(target, mark) === trimLine(line)) {
274
+ return target
275
+ }
276
+ const cut = indentOf(line)
277
+ const text = line.slice(cut)
278
+ const open = line.slice(0, cut) + mark
279
+ const close = isBlock(mark) ? ' ' + BLOCK_CLOSE : ''
280
+ return '' === text ? open + close : open + ' ' + text + close
281
+ })
282
+ return out.join('\n') + (tail ? '\n' : '')
283
+ } /* node:coverage ignore next 8 */
284
+
285
+
286
+ export {
287
+ desugarTemplate,
288
+ resugarTemplate,
289
+ markerFor,
290
+ DEFAULT_MARKER,
291
+ }
package/src/unify.ts CHANGED
@@ -12,6 +12,7 @@ import { findAt } from './val/ReferFuncVal'
12
12
 
13
13
  import { NilVal } from './val/NilVal'
14
14
  import { hasPlace } from './val/PlaceVal'
15
+ import { expandAliases } from './alias'
15
16
 
16
17
  import {
17
18
  Lang
@@ -330,10 +331,51 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
330
331
  }
331
332
  }
332
333
 
334
+ // THE RENDER RIDERS SURVIVE EVERY MEET (RENDER.0.md P7), and here
335
+ // for the deprecation record's reason: a reference resolves by
336
+ // MEETING its target with the peer at the referring position, and an
337
+ // emitted piece meets the vocabulary's alternative before the fold
338
+ // ever sees it, so a record lost in one meet shape is a line the
339
+ // trace cannot name. Both are absent unless the run is instrumented.
340
+ if (undefined !== ctx.reads &&
341
+ null != out && true === (out as any).isVal && !out.isTop && !out.isNil) {
342
+ riders(a, b, out)
343
+ }
344
+
333
345
  return out
334
346
  }
335
347
 
336
348
 
349
+ // The two P7 riders, carried from the operands to the result: the read
350
+ // address a value was found at, and the dispatch that emitted it.
351
+ // First record wins, as the deprecation record's does. Reached only
352
+ // from an instrumented run (AontuContext.reads), so an ordinary meet
353
+ // pays the one property load its call site makes and nothing else.
354
+ //
355
+ // AN OPERAND MAY BE ABSENT and reading through it is not a decision:
356
+ // `unite` is called with no peer wherever a bag holds a key its peer
357
+ // does not, and `Object(undefined)` is an empty object, so both riders
358
+ // are read the same way whether the operand is there or not. Written
359
+ // with a test first, that test had an arm no instrumented document
360
+ // reached -- true of the reading, not of the engine.
361
+ function riders(a: any, b: any, to: any): void {
362
+ const av = Object(a)
363
+ const bv = Object(b)
364
+ if (null == to.origin) {
365
+ const org = av.origin ?? bv.origin
366
+ if (null != org) {
367
+ to.origin = org
368
+ }
369
+ }
370
+ if (null == to.emitted) {
371
+ const emt = av.emitted ?? bv.emitted
372
+ if (null != emt) {
373
+ to.emitted = emt
374
+ }
375
+ }
376
+ }
377
+
378
+
337
379
  function update(x: Val, _y: Val) {
338
380
  // TODO: update x with y.site
339
381
  return x
@@ -598,6 +640,11 @@ class Unify {
598
640
  })
599
641
  }
600
642
 
643
+ // The settled tree's alias references canon as the values they
644
+ // name (ts/src/alias.ts): attached here, once, after the last
645
+ // pass, from the snapshot store this run kept.
646
+ expandAliases(res, (uctx as any).snapmap)
647
+
601
648
  uctx.explain && explainClose(te, res)
602
649
  }
603
650
 
@@ -59,7 +59,7 @@ import { StringVal } from './StringVal'
59
59
  import { FuncBaseVal } from './FuncBaseVal'
60
60
  import { arith } from './arith'
61
61
  import { cmpNumeric } from './numcmp'
62
- import { cmpCodePoint } from '../keyorder'
62
+ import { memberVals } from './members'
63
63
  import { plusText } from './PlusOpVal'
64
64
 
65
65
 
@@ -70,21 +70,10 @@ type AggOp = 'sum' | 'least' | 'greatest'
70
70
  // order for a list, sorted-key order for a map -- `each`'s order, and
71
71
  // for the same reason (a map has no order of its own, so the language
72
72
  // picks one and states it).
73
- function bagChildren(data: any): Val[] | undefined {
74
- if (true === data?.isList) {
75
- return data.peg as Val[]
76
- }
77
- if (true === data?.isMap) {
78
- // THE ONE MAP-KEY ORDER (../keyorder.ts), not a bare `.sort()`:
79
- // JavaScript compares by UTF-16 code unit, so an astral key's
80
- // leading surrogate sorts BELOW U+E000-U+FFFF and `pick` answered
81
- // in a different order from `each`, from canon, and from Go --
82
- // which sorts UTF-8 bytes, i.e. code points. `pick` is the
83
- // order-preserving projection, so that was one model producing two
84
- // different generated files (BUGS.md 62).
85
- return Object.keys(data.peg).sort(cmpCodePoint).map((k: string) => data.peg[k])
86
- }
87
- return undefined
73
+ // The members of the bag a fold reads: what generation would emit
74
+ // (./members.ts, BUGS.md §79), in the one map-key order (BUGS.md §62).
75
+ function bagChildren(data: any, ctx: AontuContext): Val[] | undefined {
76
+ return memberVals(data, ctx)
88
77
  }
89
78
 
90
79
 
@@ -140,7 +129,7 @@ class AggFuncVal extends FuncBaseVal {
140
129
 
141
130
 
142
131
  resolve(ctx: AontuContext, args: Val[]) {
143
- const children = bagChildren(args?.[0])
132
+ const children = bagChildren(args?.[0], ctx)
144
133
 
145
134
  if (undefined === children) {
146
135
  return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
@@ -265,7 +254,7 @@ class PickFuncVal extends FuncBaseVal {
265
254
 
266
255
 
267
256
  resolve(ctx: AontuContext, args: Val[]) {
268
- const children = bagChildren(args?.[0])
257
+ const children = bagChildren(args?.[0], ctx)
269
258
  const key: any = args?.[1]
270
259
 
271
260
  if (undefined === children) {
@@ -455,8 +444,8 @@ class JoinFuncVal extends FuncBaseVal {
455
444
  // arguments settled but a member still a kind, the call rides the
456
445
  // ordinary args-not-done path and residuates, which is what makes an
457
446
  // unresolved member ordinary incompleteness rather than a refusal.
458
- deferResolve(_ctx: AontuContext, args?: Val[]): boolean {
459
- const children = bagChildren(args?.[0])
447
+ deferResolve(ctx: AontuContext, args?: Val[]): boolean {
448
+ const children = bagChildren(args?.[0], ctx)
460
449
  if (undefined === children) {
461
450
  // Not a bag at all: let `resolve` say so rather than waiting for
462
451
  // a settling that has already happened.
@@ -471,7 +460,7 @@ class JoinFuncVal extends FuncBaseVal {
471
460
 
472
461
 
473
462
  resolve(ctx: AontuContext, args: Val[]) {
474
- const children = bagChildren(args?.[0])
463
+ const children = bagChildren(args?.[0], ctx)
475
464
 
476
465
  if (undefined === children) {
477
466
  return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
package/src/val/BagVal.ts CHANGED
@@ -30,7 +30,7 @@ abstract class BagVal extends FeatureVal {
30
30
  closed: boolean = false
31
31
  optionalKeys: string[] = []
32
32
 
33
- // ALIAS DECLARATIONS, by key. `%uint8: …` binds a name for this file
33
+ // ALIAS DECLARATIONS, by key. `%uint8 = …` binds a name for this file
34
34
  // and is not a field of the document: it does not generate and does
35
35
  // not appear in canon, so a document using aliases and its expanded
36
36
  // twin are the SAME document and hash identically
@@ -300,7 +300,7 @@ function normaliseEscape(
300
300
  }
301
301
  if ('p' === n || 'P' === n) {
302
302
  return ['', 'a Unicode class (\\' + n + '), which JavaScript reads as a' +
303
- ' literal "' + n + '" without a flag Aontu does not set', 0]
303
+ ' literal "' + n + '" without a flag aontu does not set', 0]
304
304
  }
305
305
  if ('Z' === n) {
306
306
  return ['', '\\Z, which RE2 does not accept and JavaScript reads as a' +
@@ -36,23 +36,15 @@ import { ListVal } from './ListVal'
36
36
  import { FuncBaseVal } from './FuncBaseVal'
37
37
  import { repathInstance } from './Val'
38
38
  import { fillPlace } from './PlaceVal'
39
- import { cmpCodePoint } from '../keyorder'
39
+ import { memberVals } from './members'
40
40
 
41
41
 
42
- // The children a data bag holds, in the order the result must carry
43
- // them, or the code naming what is wrong with the argument.
44
- function dataValues(data: Val | undefined): Val[] | string {
45
- const d: any = data
46
-
47
- if (true === d?.isMap) {
48
- return Object.keys(d.peg).sort(cmpCodePoint).map((k: string) => d.peg[k])
49
- }
50
-
51
- if (true === d?.isList) {
52
- return [...(d.peg as Val[])]
53
- }
54
-
55
- return 'each_data'
42
+ // The members a data bag holds, in the order the result must carry
43
+ // them -- what generation would emit (./members.ts, BUGS.md §79) --
44
+ // or the code naming what is wrong with the argument.
45
+ function dataValues(data: Val | undefined, ctx: AontuContext): Val[] | string {
46
+ const vals = memberVals(data, ctx)
47
+ return undefined === vals ? 'each_data' : vals
56
48
  }
57
49
 
58
50
 
@@ -95,7 +87,7 @@ class EachFuncVal extends FuncBaseVal {
95
87
 
96
88
 
97
89
  resolve(ctx: AontuContext, args: Val[]) {
98
- const vals = dataValues(args?.[0])
90
+ const vals = dataValues(args?.[0], ctx)
99
91
  if ('string' === typeof vals) {
100
92
  return makeNilErr(ctx, vals, this)
101
93
  }