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
@@ -46,6 +46,10 @@ import { ListVal } from './ListVal'
46
46
  import { FuncBaseVal, trialUnify } from './FuncBaseVal'
47
47
  import { repathInstance } from './Val'
48
48
  import { fillPlace } from './PlaceVal'
49
+ import { bagMembers } from './members'
50
+
51
+
52
+ type Member = { key: string, val: Val }
49
53
 
50
54
 
51
55
  class FilterFuncVal extends FuncBaseVal {
@@ -83,9 +87,7 @@ class FilterFuncVal extends FuncBaseVal {
83
87
 
84
88
 
85
89
  unify(peer: Val, ctx: AontuContext): Val {
86
- const ready = this.driveStagedArgs(ctx, 1)
87
-
88
- if (!ready || !ctx.settle) {
90
+ if (!this.stagedReady(peer, ctx, 1)) {
89
91
  return this.residuate(peer, ctx)
90
92
  }
91
93
 
@@ -117,12 +119,15 @@ class FilterFuncVal extends FuncBaseVal {
117
119
  return undefined !== met && met.canon === child.canon
118
120
  }
119
121
 
122
+ // The candidates are the bag's MEMBERS -- what generation would
123
+ // emit (./members.ts, BUGS.md §79) -- so a hidden child is never
124
+ // selected into the result.
120
125
  if (true === data?.isMap) {
121
126
  const peg: Record<string, Val> = {}
122
- for (const key of Object.keys(data.peg)) {
127
+ for (const { key, val } of bagMembers(data, ctx) as Member[]) {
123
128
  const kctx = ctx.descend(key)
124
- if (keeps(data.peg[key], kctx)) {
125
- peg[key] = data.peg[key].clone(kctx)
129
+ if (keeps(val, kctx)) {
130
+ peg[key] = val.clone(kctx)
126
131
  }
127
132
  }
128
133
  return new MapVal({ peg }, ctx)
@@ -130,7 +135,7 @@ class FilterFuncVal extends FuncBaseVal {
130
135
 
131
136
  if (true === data?.isList) {
132
137
  const peg: Val[] = []
133
- for (const el of data.peg as Val[]) {
138
+ for (const { val: el } of bagMembers(data, ctx) as Member[]) {
134
139
  // The element context is the position it will END UP at, which
135
140
  // is its index in the RESULT: dropping the third of five moves
136
141
  // the fourth up, and a kept element must be pathed where it
@@ -0,0 +1,119 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // TRANSFORMATION: `form(data, tmpl)` (G9 §4; docs/design/RENDER.0.md
4
+ // D11 and P6). One element of the result list per child of `data`,
5
+ // being `tmpl` instantiated at that position with `_` bound to the
6
+ // source child. It REPLACES; it does not meet.
7
+ //
8
+ // names: [web, auth]
9
+ // units: form($.names, {path: _ + ".ts"}) -> [{path: "web.ts"}, {path: "auth.ts"}]
10
+ //
11
+ // WHY BESIDE `each`. `each($.ports, integer)` is a schema statement
12
+ // and is monotone -- more information about the template narrows the
13
+ // children -- so it is a lattice citizen, and a constructor is not.
14
+ // The language draws this line twice already: `min`/`max` (bounds)
15
+ // against `least`/`greatest` (aggregates), and `filter` (select by
16
+ // unifiability) against `match` (choose a result). `each` is the
17
+ // bound; `form` is the construction. `each` also carries the source
18
+ // child's identity (its clone keeps the entity), where `form`'s
19
+ // element IS the template.
20
+ //
21
+ // WHY IT EXISTS AT ALL: ORDER. `pick(pack(d, {f: t}), f)` maps too,
22
+ // but it goes through a map and re-sorts to code-point order, and it
23
+ // refuses a list of records outright (pack_key). A struct's fields, a
24
+ // DDL's columns and a file's imports are lists whose order is the
25
+ // model's, and silently alphabetising them is wrong output. `form`
26
+ // reads its members through the one ordering helper `each` uses
27
+ // (members.ts), so the two can never disagree about order -- source
28
+ // order for a list, sorted-key order for a map -- and skips what
29
+ // generation would not emit: a hidden child, an unfilled optional.
30
+ //
31
+ // It joins `boundArgStart` (PlaceVal.ts): a `_` inside its template
32
+ // is ITS hole to bind, never an outer generator's. Forgetting that is
33
+ // BUGS.md §34's silent failure -- every existing test passes and the
34
+ // new combinator captures an outer generator's hole -- which is why
35
+ // the shared spec's nesting rows exist.
36
+
37
+ import type {
38
+ Val,
39
+ ValSpec,
40
+ } from '../type'
41
+
42
+ import {
43
+ AontuContext,
44
+ } from '../ctx'
45
+
46
+ import { makeNilErr } from '../err'
47
+ import { ListVal } from './ListVal'
48
+ import { FuncBaseVal } from './FuncBaseVal'
49
+ import { repathInstance } from './Val'
50
+ import { fillPlace } from './PlaceVal'
51
+ import { memberVals } from './members'
52
+
53
+
54
+ class FormFuncVal extends FuncBaseVal {
55
+ isFormFunc = true
56
+
57
+ // THE STAGING RULE, for the reason given in PackFuncVal: the data
58
+ // is not settled merely by being `done` once.
59
+ staged = true
60
+
61
+ constructor(
62
+ spec: ValSpec,
63
+ ctx?: AontuContext
64
+ ) {
65
+ super(spec, ctx)
66
+ }
67
+
68
+
69
+ funcname() {
70
+ return 'form'
71
+ }
72
+
73
+
74
+ // The template is not an argument to drive (see PackFuncVal.prepare).
75
+ prepare(_ctx: AontuContext, _args: Val[]) {
76
+ return null
77
+ }
78
+
79
+
80
+ unify(peer: Val, ctx: AontuContext): Val {
81
+ // ONE argument is driven: the data. The template is not (see
82
+ // prepare above).
83
+ if (!this.stagedReady(peer, ctx, 1)) {
84
+ return this.residuate(peer, ctx)
85
+ }
86
+
87
+ return super.unify(peer, ctx)
88
+ }
89
+
90
+
91
+ resolve(ctx: AontuContext, args: Val[]) {
92
+ const vals = memberVals(args?.[0], ctx)
93
+ if (undefined === vals) {
94
+ return makeNilErr(ctx, 'form_data', this)
95
+ }
96
+
97
+ // Arity is checked at parse (funcArity), so the template is here.
98
+ const tmpl: Val = args[1]
99
+ const peg: Val[] = []
100
+
101
+ for (let i = 0; i < vals.length; i++) {
102
+ const elctx = ctx.descend(String(i))
103
+ // A FULL INSTANCE per element, to the leaves (`dup`, ADR-005),
104
+ // at the element's own position -- see PackFuncVal.resolve --
105
+ // with `_` bound to the source child and NOTHING met into it.
106
+ const inst = tmpl.clone(elctx, { dup: true })
107
+ repathInstance(inst, inst.path)
108
+ peg.push(fillPlace(inst, vals[i], elctx))
109
+ }
110
+
111
+ return new ListVal({ peg }, ctx)
112
+ }
113
+
114
+ } /* node:coverage ignore next 6 */
115
+
116
+
117
+ export {
118
+ FormFuncVal,
119
+ }
@@ -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/MapVal.ts CHANGED
@@ -46,7 +46,7 @@ import { markSpread } from '../provenance'
46
46
  // pass. The map lives on the unify root ctx (see Unify), so it persists
47
47
  // across fixpoint passes and is GC'd with the run.
48
48
  function spreadSnapKey(cj: any): string {
49
- return cj.canon + '~' + cj.site.row + ':' + cj.site.col
49
+ return cj.spelling + '~' + cj.site.row + ':' + cj.site.col
50
50
  }
51
51
 
52
52
  // Snapshot a path-dependent ref spread to its structural target once,
@@ -605,5 +605,6 @@ class MapVal extends BagVal {
605
605
 
606
606
 
607
607
  export {
608
- MapVal
608
+ MapVal,
609
+ spreadSnapKey,
609
610
  }
@@ -44,35 +44,39 @@ import { MapVal } from './MapVal'
44
44
  import { FuncBaseVal } from './FuncBaseVal'
45
45
  import { repathInstance } from './Val'
46
46
  import { fillPlace } from './PlaceVal'
47
+ import { bagMembers } from './members'
47
48
 
48
49
 
49
50
  // The keys a data bag names, in the order the result must carry them,
50
51
  // or a code naming what is wrong with it. Shared with `each`, which
51
52
  // asks the same question of the same argument and answers it with the
52
53
  // values rather than the keys.
53
- function dataKeys(data: Val | undefined): string[] | string {
54
- const d: any = data
54
+ function dataKeys(data: Val | undefined, ctx: AontuContext): string[] | string {
55
+ // The candidates are the bag's MEMBERS -- what generation would
56
+ // emit (./members.ts, BUGS.md §79) -- so a hidden key, or a hidden
57
+ // name in a list of names, packs nothing.
58
+ const members = bagMembers(data, ctx)
59
+
60
+ if (undefined === members) {
61
+ return 'pack_data'
62
+ }
55
63
 
56
- if (true === d?.isMap) {
57
- return Object.keys(d.peg)
64
+ if (true === (data as any).isMap) {
65
+ return members.map((m) => m.key)
58
66
  }
59
67
 
60
- if (true === d?.isList) {
61
- const out: string[] = []
62
- for (const el of d.peg as Val[]) {
63
- const e: any = el
64
- // A key is a NAME, and only a string is one. A number would
65
- // key by position under another spelling, which is the failure
66
- // mode the data-keyed rule exists to refuse.
67
- if (true !== e?.isScalar || 'string' !== typeof e.peg) {
68
- return 'pack_key'
69
- }
70
- out.push(e.peg)
68
+ const out: string[] = []
69
+ for (const { val } of members) {
70
+ const e: any = val
71
+ // A key is a NAME, and only a string is one. A number would
72
+ // key by position under another spelling, which is the failure
73
+ // mode the data-keyed rule exists to refuse.
74
+ if (true !== e?.isScalar || 'string' !== typeof e.peg) {
75
+ return 'pack_key'
71
76
  }
72
- return out
77
+ out.push(e.peg)
73
78
  }
74
-
75
- return 'pack_data'
79
+ return out
76
80
  }
77
81
 
78
82
 
@@ -114,9 +118,7 @@ class PackFuncVal extends FuncBaseVal {
114
118
  // ONE argument is driven: the data. The template is not (see
115
119
  // prepare above), and driveStagedArgs answers whether the data has
116
120
  // settled -- the other half of "ready to fire".
117
- const ready = this.driveStagedArgs(ctx, 1)
118
-
119
- if (!ready || !ctx.settle) {
121
+ if (!this.stagedReady(peer, ctx, 1)) {
120
122
  return this.residuate(peer, ctx)
121
123
  }
122
124
 
@@ -125,7 +127,7 @@ class PackFuncVal extends FuncBaseVal {
125
127
 
126
128
 
127
129
  resolve(ctx: AontuContext, args: Val[]) {
128
- const keys = dataKeys(args?.[0])
130
+ const keys = dataKeys(args?.[0], ctx)
129
131
  if ('string' === typeof keys) {
130
132
  return makeNilErr(ctx, keys, this)
131
133
  }
@@ -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) or condition (filter, arg 1)
77
- // is that generator's to bind — "_ is the source child" — so neither
78
- // the hole test nor the fill walk may cross into those arguments from
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,8 @@ 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 ||
89
+ true === v.isFormFunc ? 1 : Infinity
89
90
  }
90
91
 
91
92
 
@@ -183,10 +184,12 @@ function rebuild(v: Val, peg: any, ctx: AontuContext): Val {
183
184
  out.peg = peg
184
185
  out.dc = 0
185
186
  return out
186
- } /* node:coverage ignore next 8 */
187
+ } /* node:coverage ignore next 10 */
187
188
 
188
189
 
189
190
  export {
191
+ boundArgStart,
192
+ rebuild,
190
193
  hasPlace,
191
194
  fillPlace,
192
195
  PlaceVal,
package/src/val/RefVal.ts CHANGED
@@ -73,12 +73,40 @@ function pendingMarkWrapper(v: any): boolean {
73
73
  }
74
74
 
75
75
 
76
+ // The child a term of the walk can supply for one path segment, or
77
+ // undefined when it has none. A map or a list answers from its own
78
+ // members; a PENDING type()/hide() answers from its argument's, because
79
+ // the wrapper only marks and its argument is the structure the path
80
+ // names (see the call sites in `find`).
81
+ function markedChild(v: any, part: any): Val | undefined {
82
+ if (true === v?.isMap || true === v?.isList) {
83
+ return v.peg[part]
84
+ }
85
+ if (true === v?.isFunc
86
+ && (true === v.isHideFunc || true === v.isTypeFunc)
87
+ && (true === v.peg?.[0]?.isMap || true === v.peg?.[0]?.isList)) {
88
+ return v.peg[0].peg[part]
89
+ }
90
+ return undefined
91
+ }
92
+
93
+
94
+ // An alias name, whole: the sigil and an identifier (the lexer's
95
+ // ALIAS_RE, anchored at both ends, for the canon spelling above).
96
+ const ALIAS_NAME_RE = /^%[A-Za-z_][A-Za-z0-9_]*$/
97
+
98
+
76
99
  class RefVal extends FeatureVal {
77
100
  isRef = true
78
101
  isGenable = true
79
102
  cjo = 32500
80
103
 
81
104
  absolute: boolean = false
105
+
106
+ // The value an alias reference canons as, attached by expandAliases
107
+ // after unification (see `canon` below). Not a ValSpec field: it is
108
+ // a rendering of the settled tree, never a parse-time property.
109
+ expansion: Val | undefined = undefined
82
110
  prefix: boolean = false
83
111
 
84
112
  constructor(
@@ -220,7 +248,8 @@ class RefVal extends FeatureVal {
220
248
  }
221
249
 
222
250
  // same path
223
- else if (this.canon === peer.canon) {
251
+ else if (this.spelling ===
252
+ (true === (peer as any).isRef ? (peer as any).spelling : peer.canon)) {
224
253
  out = this
225
254
  }
226
255
 
@@ -406,6 +435,45 @@ class RefVal extends FeatureVal {
406
435
  || true === (node as any).peg?.[0]?.isList)) {
407
436
  node = (node as any).peg[0].peg[part]
408
437
  }
438
+
439
+ // AND SO IS A CONJUNCT THAT STILL CARRIES ONE (issue #164).
440
+ // Two statements for one key meet, so a key written as
441
+ // `T: type({...})` twice is a CONJUNCT of two wrappers -- and
442
+ // one written once beside a plain `T: {...}` is a conjunct
443
+ // too. The arm above sees a wrapper only when it is the whole
444
+ // node, so a reference into such a key walked into the
445
+ // conjunct and stopped: the wrapper waited for its argument,
446
+ // the argument waited for the reference, and neither moved.
447
+ // The first referring child of every consumer stayed
448
+ // unresolved and generation reported mapval_no_gen at a path
449
+ // that names none of this, which is how a five-file schema
450
+ // spent its evening being bisected.
451
+ //
452
+ // The answer at a segment is the MEET of what each term
453
+ // supplies, so terms that have no such member are skipped and
454
+ // the rest are conjoined -- one term answers as itself, and
455
+ // the ordinary map arm answers once the fold has happened.
456
+ // Restricted to a conjunct that still holds a pending
457
+ // wrapper: every other conjunct folds on its own, and this
458
+ // walk exists only to break the wrapper's deadlock.
459
+ else if (true === (node as any).isConjunct
460
+ && Array.isArray((node as any).peg)
461
+ && pendingMarkWrapper(node)) {
462
+ const kids: Val[] = []
463
+ for (const term of (node as any).peg) {
464
+ const kid = markedChild(term, part)
465
+ if (undefined !== kid && null !== kid) {
466
+ kids.push(kid)
467
+ }
468
+ }
469
+ // No term has it YET. Not a miss: the conjunct is still
470
+ // folding, and the member may arrive with the fold.
471
+ if (0 === kids.length) {
472
+ break
473
+ }
474
+ node = 1 === kids.length ?
475
+ kids[0] : new ConjunctVal({ peg: kids }, ctx)
476
+ }
409
477
  else if (node.done) {
410
478
  nopath = true
411
479
  break;
@@ -464,6 +532,31 @@ class RefVal extends FeatureVal {
464
532
  else if (pI === refpath.length) {
465
533
  out = node
466
534
 
535
+ // THE READ IS RECORDED, AND THE VALUE STAMPED WITH WHERE IT
536
+ // WAS FOUND (RENDER.0.md P7). A resolved reference clones its
537
+ // target into the referring position, so without the stamp a
538
+ // value that arrived by reference knows only where it came to
539
+ // rest -- and `render --coverage` has nothing to measure the
540
+ // model against. Off unless the run is instrumented; the first
541
+ // address wins, and every reference to one node names the same
542
+ // address anyway.
543
+ if (undefined !== ctx.reads && null != node) {
544
+ // The root's own address is `$`, as the coverage walk spells
545
+ // it: a dot with nothing after it would match no path there.
546
+ const addr = '$' + refpath.map((seg: string) => '.' + seg).join('')
547
+ // AN ALIAS IS NOT A PATH. `%wire` names a value the document
548
+ // holds unevaluated and the tree never carries, so it is an
549
+ // address a rule can be reported AT and never a path coverage
550
+ // could call dead: it is stamped, and it is not in the set
551
+ // the model is measured against.
552
+ if (!refpath[0]?.startsWith('%')) {
553
+ ctx.reads.add(addr)
554
+ }
555
+ if (null == (node as any).origin) {
556
+ ; (node as any).origin = addr
557
+ }
558
+ }
559
+
467
560
  // A reference landing on another reference -- or on a FUNCTION,
468
561
  // whose arguments the chase now follows (issue #35) -- may be a
469
562
  // PROVEN mutual cycle (a: $.b, b: $.a; a: $.b, b: upper($.a)).
@@ -544,21 +637,38 @@ class RefVal extends FeatureVal {
544
637
 
545
638
  // console.log('FOUND-B', out)
546
639
 
640
+ // A REFERENCE LIFTS: the copy is concrete, its type and hide
641
+ // marks cleared to the leaves -- a marked target is what the
642
+ // author hid or typed, and a reference to it is the author
643
+ // asking for it in the open. EXCEPT the snapshot a staged
644
+ // verb takes of its data (`argsnap`: each, emit, filter,
645
+ // pack, pick, join, the aggregates), when the target itself
646
+ // is not marked: a member marked inside an unmarked bag was
647
+ // hidden IN ITS OWN RIGHT, and the verb's enumeration
648
+ // (ts/src/val/members.ts, BUGS.md §79) needs to see that
649
+ // mark to leave the member out, as generation does. A marked
650
+ // target lifts even there, or `each($.schema.entities, _)`
651
+ // under `schema: hide({...})` would see every entity as
652
+ // hidden, since hide() marks to the leaves.
653
+ const lifted = true !== (ctx as any).argsnap
654
+ || true === out.mark.type || true === out.mark.hide
655
+
547
656
  out = out.clone(ctx)
548
657
 
549
- // if (this.mark.type || this.mark.hide) {
550
- walk(out, (_key: string | number | undefined, val: Val) => {
551
- val.mark.type = false
552
- val.mark.hide = false
553
- // THE LINK IS NOT CLEARED (G4 phase 3): a link says what a
554
- // value POINTS AT, and a copy of a link points at the same
555
- // thing. An ABSOLUTE address still names the same node from
556
- // the copy; a RELATIVE one is read from the copy's own
557
- // position, which is what makes a referenced model resolve
558
- // its internal links inside the copy (ADR-014).
559
- return val
560
- })
561
- //}
658
+ if (lifted) {
659
+ walk(out, (_key: string | number | undefined, val: Val) => {
660
+ val.mark.type = false
661
+ val.mark.hide = false
662
+ // THE LINK IS NOT CLEARED (G4 phase 3): a link says what
663
+ // a value POINTS AT, and a copy of a link points at the
664
+ // same thing. An ABSOLUTE address still names the same
665
+ // node from the copy; a RELATIVE one is read from the
666
+ // copy's own position, which is what makes a referenced
667
+ // model resolve its internal links inside the copy
668
+ // (ADR-014).
669
+ return val
670
+ })
671
+ }
562
672
 
563
673
  // onsole.log('FOUND-C', out)
564
674
  }
@@ -694,19 +804,58 @@ class RefVal extends FeatureVal {
694
804
  absolute: this.absolute,
695
805
  ...(spec || {})
696
806
  }) as RefVal)
807
+ out.expansion = this.expansion
697
808
  return out
698
809
  }
699
810
 
700
811
 
701
- get canon() {
702
- let str =
703
- (this.absolute ? '$' : '') +
812
+ // THE NAME OF THE ALIAS THIS REFERENCE NAMES, or undefined for a
813
+ // path reference. `%u` is spelled internally as the root reference
814
+ // `$.%u` (docs/design/ALIASES.0.md: the name is a path into the
815
+ // declaration), so an alias reference is an absolute reference of
816
+ // one segment that is an alias name.
817
+ get aliasName(): string | undefined {
818
+ return this.absolute && 1 === this.peg.length &&
819
+ 'string' === typeof this.peg[0] && ALIAS_NAME_RE.test(this.peg[0]) ?
820
+ this.peg[0] : undefined
821
+ }
822
+
823
+
824
+ // THE REFERENCE'S OWN SPELLING: the alias name, or the path. This is
825
+ // the reference's identity (the snapshot key of a ref spread, the
826
+ // same-path test in unify), which `canon` below is not once an
827
+ // expansion is attached.
828
+ get spelling(): string {
829
+ const name = this.aliasName
830
+ if (undefined !== name) {
831
+ return name
832
+ }
833
+ return (this.absolute ? '$' : '') +
704
834
  (0 < this.peg.length ? '.' : '') +
705
835
  // this.peg.join(this.sep)
706
836
  this.peg.map((p: any) => '.' === p ? '' :
707
837
  (p.isVal ? p.canon : '' + p))
708
838
  .join('.')
709
- return str
839
+ }
840
+
841
+
842
+ get canon() {
843
+ // AN ALIAS REFERENCE CANONS AS THE VALUE IT NAMES. A reference left
844
+ // standing after unification is one inside a spread template
845
+ // (`[&: %u]`, `{&: {a: %u}}`): the template applies to children
846
+ // that have not arrived, so it is not resolved in place. Canon
847
+ // erases the declaration (an alias is a name for a value, and
848
+ // nothing more -- ALIASES.0.md §4), so the name alone would not
849
+ // reparse, and the hash of `t: {&: %u}` would differ from the hash
850
+ // of `t: {&: integer}`, which is the same document. The expansion
851
+ // is attached by expandAliases (ts/src/alias.ts) once the tree has
852
+ // settled; without one -- a parse-only tree, an unresolved name, or
853
+ // the KNOT of a recursive alias inside its own template -- the
854
+ // reference spells its name. Twin of RefVal.Canon in go/ref.go.
855
+ if (undefined !== this.expansion) {
856
+ return this.expansion.canon
857
+ }
858
+ return this.spelling
710
859
  }
711
860
 
712
861