aontu 0.53.0 → 0.55.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 (197) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +500 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +94 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +153 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +75 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.d.ts +1 -0
  132. package/dist/vet.js +32 -1
  133. package/dist/vet.js.map +1 -1
  134. package/dist/view.d.ts +90 -0
  135. package/dist/view.js +2168 -0
  136. package/dist/view.js.map +1 -0
  137. package/grammar/aontu.gbnf +18 -10
  138. package/grammar/aontu.lark +15 -10
  139. package/grammar/aontu.tmLanguage.json +184 -0
  140. package/package.json +10 -3
  141. package/skill/grammar-card.md +1 -2
  142. package/src/aontu.ts +37 -7
  143. package/src/cli.ts +553 -8
  144. package/src/ctx.ts +20 -0
  145. package/src/diff.ts +4 -2
  146. package/src/err.ts +8 -1
  147. package/src/graph.ts +125 -75
  148. package/src/hcanon.ts +9 -11
  149. package/src/hints.ts +112 -29
  150. package/src/jsonschema.ts +41 -0
  151. package/src/lang.ts +642 -203
  152. package/src/lsp.ts +75 -6
  153. package/src/mcp.ts +164 -6
  154. package/src/mod-tool.ts +48 -9
  155. package/src/mod.ts +110 -1
  156. package/src/patch.ts +31 -27
  157. package/src/provenance.ts +8 -1
  158. package/src/query.ts +4 -2
  159. package/src/reach.ts +52 -23
  160. package/src/relation.ts +139 -236
  161. package/src/sig.ts +345 -0
  162. package/src/sigdecl.ts +11 -0
  163. package/src/siggate.ts +144 -0
  164. package/src/std.ts +77 -16
  165. package/src/subsume.ts +59 -10
  166. package/src/unify.ts +102 -125
  167. package/src/utility.ts +7 -67
  168. package/src/val/AggFuncVal.ts +231 -4
  169. package/src/val/BagVal.ts +58 -9
  170. package/src/val/ConstraintVal.ts +34 -5
  171. package/src/val/ContainerKindVal.ts +158 -0
  172. package/src/val/CopyFuncVal.ts +0 -7
  173. package/src/val/DisjunctVal.ts +152 -40
  174. package/src/val/ExpectVal.ts +39 -4
  175. package/src/val/FuncBaseVal.ts +21 -8
  176. package/src/val/GraphAtomVal.ts +264 -0
  177. package/src/val/JunctionVal.ts +23 -6
  178. package/src/val/ListVal.ts +30 -37
  179. package/src/val/MapVal.ts +87 -39
  180. package/src/val/PathFuncVal.ts +107 -19
  181. package/src/val/PathVal.ts +221 -0
  182. package/src/val/PlusOpVal.ts +56 -37
  183. package/src/val/PrefVal.ts +186 -38
  184. package/src/val/RecurseVal.ts +285 -0
  185. package/src/val/RefVal.ts +105 -83
  186. package/src/val/ReferFuncVal.ts +445 -100
  187. package/src/val/ScalarKindVal.ts +12 -0
  188. package/src/val/SuperFuncVal.ts +137 -13
  189. package/src/val/TopVal.ts +1 -1
  190. package/src/val/Val.ts +44 -35
  191. package/src/val/arith.ts +4 -1
  192. package/src/vet.ts +41 -4
  193. package/src/view.ts +2882 -0
  194. package/dist/val/IdFuncVal.d.ts +0 -13
  195. package/dist/val/IdFuncVal.js +0 -54
  196. package/dist/val/IdFuncVal.js.map +0 -1
  197. package/src/val/IdFuncVal.ts +0 -91
@@ -0,0 +1,221 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // FIRST-CLASS PATHS (docs/design/PATHS.0.md). A PathVal is the value
4
+ // `path(p)` captures: a tree address as DATA -- the spelling, never
5
+ // the resolution. It is a scalar whose peg is the address string in
6
+ // exactly the grammar `refer` reads (`$.a.b` from the root, `.b` from
7
+ // the sibling scope, one more leading dot per parent step), which is
8
+ // what lets a captured path meet the checking machinery unchanged.
9
+ //
10
+ // Meets are SYNTACTIC, by the PREFIX rule (amended, ADR-016): two
11
+ // path values meet when one spells a prefix of the other -- same
12
+ // anchor, the shorter's segments opening the longer's -- and the
13
+ // result is the LONGER: a path can always be told more precisely.
14
+ // Incomparable spellings refuse as any two unequal scalars do.
15
+ // Resolving during a meet would make the meet depend on the value's
16
+ // position, which is the property the staging machinery exists to
17
+ // quarantine -- resolution stays the business of `refer`, `rel` and
18
+ // the graph.
19
+ //
20
+ // The kind sits UNDER string (ScalarKindVal.KIND_PARENT), so `string`
21
+ // admits a path value and the string constraints keep working; a
22
+ // plain string LITERAL and a path value refuse each other, exactly as
23
+ // the number tower's leaves do. A bare string is NEVER a path
24
+ // (ADR-016): `path("...")` -- the call's own string argument -- is
25
+ // the one conversion the language has.
26
+
27
+ import type {
28
+ Val,
29
+ ValSpec,
30
+ } from '../type'
31
+
32
+ import {
33
+ AontuContext,
34
+ } from '../ctx'
35
+
36
+ import { makeNilErr } from '../err'
37
+
38
+ import { propagateMarks } from '../utility'
39
+
40
+ import { ScalarVal } from './ScalarVal'
41
+ import { ScalarKindVal, Path } from './ScalarKindVal'
42
+
43
+
44
+
45
+ // A segment of a tree path: a map key or a list index. The same
46
+ // grammar the rest of the engine spells keys with, and a leading digit
47
+ // is legitimate because a list index is one.
48
+ const ADDR_SEGMENT = /^[A-Za-z0-9_-]+$/
49
+
50
+ export type Address = {
51
+ // Anchored at the document root (`$.a.b`) rather than at the link's
52
+ // own position (`.a.b`).
53
+ absolute: boolean
54
+ // Parent steps, for a relative address that climbs (`..a` is one).
55
+ up: number
56
+ // The written segments, below the anchor.
57
+ parts: string[]
58
+ }
59
+
60
+
61
+ // The address a string spells, or undefined when it does not spell
62
+ // one. An address is a TREE PATH, in exactly the two spellings a
63
+ // reference uses: `$.services.auth` from the root, `.auth` from the
64
+ // link's own sibling scope. The tree is the only namespace -- which is
65
+ // what makes a model instantiable more than once, each instance
66
+ // resolving its relative links inside itself (ADR-014).
67
+ export function parseAddress(s: string): Address | undefined {
68
+ if ('$' === s) {
69
+ // The whole document is not a relation's target: an address must
70
+ // name something with a position to be written back into.
71
+ return undefined
72
+ }
73
+ if (s.startsWith('$.')) {
74
+ const parts = s.slice(2).split('.')
75
+ for (const seg of parts) {
76
+ if (!ADDR_SEGMENT.test(seg)) {
77
+ return undefined
78
+ }
79
+ }
80
+ return { absolute: true, up: 0, parts }
81
+ }
82
+ if (!s.startsWith('.')) {
83
+ return undefined
84
+ }
85
+ // A relative address: the leading dot anchors it at the sibling
86
+ // scope, and every FURTHER leading dot is one step up from there --
87
+ // the same reduction a relative reference's `.` segments perform.
88
+ let up = 0
89
+ let rest = s.slice(1)
90
+ while (rest.startsWith('.')) {
91
+ up++
92
+ rest = rest.slice(1)
93
+ }
94
+ if ('' === rest) {
95
+ return undefined
96
+ }
97
+ const parts = rest.split('.')
98
+ for (const seg of parts) {
99
+ if (!ADDR_SEGMENT.test(seg)) {
100
+ return undefined
101
+ }
102
+ }
103
+ return { absolute: false, up, parts }
104
+ }
105
+
106
+
107
+ // The spelling string TEXT converts by, inside a `path(...)` call:
108
+ // text that carries no anchor is RELATIVE (`"a.b"` is the address
109
+ // `.a.b`), matching the raw form (`path(a.b)` captures `.a.b`). Only
110
+ // the anchor is supplied -- the result still has to parse, so
111
+ // malformed text (`""`, `"a..b"`, a bad `$` spelling) refuses as
112
+ // before. The prefix is not applied to text that claims an anchor:
113
+ // `"$x"` is a broken absolute address, not a relative one.
114
+ export function textAddress(s: string): string {
115
+ return ('$' === s[0] || '.' === s[0]) ? s : '.' + s
116
+ }
117
+
118
+
119
+ // The LONGER of two addresses when one spells a prefix of the other
120
+ // (docs/design/PATHS.0.md, amended): same anchor -- absolute or the
121
+ // same number of parent steps -- and the shorter's segments open the
122
+ // longer's. The meet of two path values, and of a refer's address
123
+ // with a later path peer: a path can always be told more precisely,
124
+ // and the more precise spelling is the result. Undefined when the two
125
+ // are not comparable, which refuses as any two unequal scalars do.
126
+ // Both arguments must already be valid addresses: every caller hands
127
+ // over a PathVal peg or a refer addrsrc, and both are validated at
128
+ // capture or conversion -- the same trust `unify`'s own address arm
129
+ // extends (`parseAddress(p.peg) as Address`).
130
+ export function prefixMeet(a: string, b: string): string | undefined {
131
+ const pa = parseAddress(a) as Address
132
+ const pb = parseAddress(b) as Address
133
+ if (pa.absolute !== pb.absolute || pa.up !== pb.up) {
134
+ return undefined
135
+ }
136
+ const short = pa.parts.length <= pb.parts.length ? pa : pb
137
+ const long = short === pa ? pb : pa
138
+ for (let i = 0; i < short.parts.length; i++) {
139
+ if (short.parts[i] !== long.parts[i]) {
140
+ return undefined
141
+ }
142
+ }
143
+ return short === pa ? b : a
144
+ }
145
+
146
+
147
+
148
+ class PathVal extends ScalarVal {
149
+ isPath = true
150
+
151
+ constructor(
152
+ spec: ValSpec,
153
+ ctx?: AontuContext
154
+ ) {
155
+ super({ peg: spec.peg, kind: Path }, ctx)
156
+ }
157
+
158
+ // Two path values meet by the PREFIX rule (ADR-016): the longer
159
+ // when one opens the other, refusal otherwise. Exactly equal pegs
160
+ // are absorbed by unite's fast path before this runs, so the arm
161
+ // sees the unequal pairs; the winner carries both sides' marks, as
162
+ // the equal-scalar arm has always ratcheted them.
163
+ unify(peer: Val, ctx: AontuContext): Val {
164
+ const p: any = peer
165
+ if (true === p.isPath) {
166
+ const merged = prefixMeet(this.peg, p.peg)
167
+ if (undefined === merged) {
168
+ return makeNilErr(ctx, 'scalar_value', this, peer)
169
+ }
170
+ const out = merged === this.peg ? this : p
171
+ const other = out === this ? p : this
172
+ propagateMarks(other, out)
173
+ return out
174
+ }
175
+ return super.unify(peer, ctx)
176
+ }
177
+
178
+ // Reparses to the same VALUE: the call form is the literal syntax
179
+ // for this kind, so canon renders it back. The peg is already the
180
+ // address grammar, which the argument grammar also accepts.
181
+ get canon() {
182
+ return 'path(' + this.peg + ')'
183
+ }
184
+
185
+ // The super() ladder lifts a path value to its own kind, and the
186
+ // kind must render as `path()` -- the bare word `path` is an
187
+ // ordinary string. ScalarVal.superior would mint the plain
188
+ // ScalarKindVal, whose canon is the bare word.
189
+ superior() {
190
+ return this.place(new PathKindVal({}))
191
+ }
192
+
193
+ } /* node:coverage ignore next 4 */
194
+
195
+
196
+ // The path KIND, `path()`: admits every path value and defaults to
197
+ // nothing, as `string` does. It does NOT promote (ADR-016): a bare
198
+ // string meeting the kind refuses through the generic kind ladder,
199
+ // exactly as `integer & "x"` does -- `path("...")` is the one string
200
+ // conversion, and it happens at the call.
201
+ class PathKindVal extends ScalarKindVal {
202
+ isPathKind = true
203
+
204
+ constructor(
205
+ spec: ValSpec,
206
+ ctx?: AontuContext
207
+ ) {
208
+ super({ ...spec, peg: Path }, ctx)
209
+ }
210
+
211
+ get canon() {
212
+ return 'path()'
213
+ }
214
+
215
+ } /* node:coverage ignore next 6 */
216
+
217
+
218
+ export {
219
+ PathVal,
220
+ PathKindVal,
221
+ }
@@ -59,6 +59,61 @@ function isBig(k: OpKind): boolean {
59
59
  }
60
60
 
61
61
 
62
+ // Only concrete scalar operands are valid: anything else (kinds, maps,
63
+ // lists, null, top, funcs) must not coerce — the JS `+` would leak
64
+ // internals like "[object Object]" into output. A non-scalar operand
65
+ // leaves the op unresolved, which generate() reports.
66
+ //
67
+ // A pref operand contributes its preferred value (`pref(1)+2`), and
68
+ // therefore that value's kind too.
69
+ function operand(v: any) {
70
+ while (v?.isPref) {
71
+ v = v.peg
72
+ }
73
+ return v
74
+ }
75
+
76
+
77
+ // The operand's LEAF, not the JavaScript type of its peg: integer and
78
+ // float share `number`, while the exact leaves are told apart from each
79
+ // other and from everything else by their own flags. The whole ladder in
80
+ // `operate` dispatches on this and never on `typeof`.
81
+ function opkind(v: any): OpKind | undefined {
82
+ if (!(v?.isVal && v.isScalar)) {
83
+ return undefined
84
+ }
85
+ if (v.isBigInteger) {
86
+ return 'biginteger'
87
+ }
88
+ if (v.isBigDecimal) {
89
+ return 'bigdecimal'
90
+ }
91
+ if (v.isInteger) {
92
+ return 'integer'
93
+ }
94
+ const t = typeof v.peg
95
+ return 'number' === t ? 'float' :
96
+ 'string' === t ? 'string' :
97
+ 'boolean' === t ? 'boolean' :
98
+ undefined
99
+ }
100
+
101
+
102
+ // THE TEXT `+` WOULD MAKE OF THIS OPERAND, or undefined if `+` would not
103
+ // take it at all.
104
+ //
105
+ // `join` folds with `+` seeded with `""`, so every member goes through
106
+ // concatenation's string branch — and this is the function that branch
107
+ // calls. Exported so that the fold and the operator cannot drift into
108
+ // two answers to "how does a number become text": there is one
109
+ // rendering, `digits` below, and both reach it here.
110
+ function plusText(v: Val): string | undefined {
111
+ const o: any = operand(v)
112
+ const k = opkind(o)
113
+ return undefined === k ? undefined : digits(o, k)
114
+ }
115
+
116
+
62
117
  class PlusOpVal extends OpBaseVal {
63
118
  isPlusOp = true
64
119
 
@@ -80,43 +135,6 @@ class PlusOpVal extends OpBaseVal {
80
135
 
81
136
 
82
137
  operate(ctx: AontuContext, args: Val[]) {
83
- // Only concrete scalar operands are valid: anything else (kinds,
84
- // maps, lists, null, top, funcs) must not coerce — the JS `+` would
85
- // leak internals like "[object Object]" into output. A non-scalar
86
- // operand leaves the op unresolved, which generate() reports.
87
- const operand = (v: any) => {
88
- // A pref operand contributes its preferred value (`pref(1)+2`),
89
- // and therefore that value's kind too.
90
- while (v?.isPref) {
91
- v = v.peg
92
- }
93
- return v
94
- }
95
-
96
- // The operand's LEAF, not the JavaScript type of its peg: integer
97
- // and float share `number`, while the exact leaves are told apart
98
- // from each other and from everything else by their own flags. The
99
- // whole ladder below dispatches on this and never on `typeof`.
100
- const opkind = (v: any): OpKind | undefined => {
101
- if (!(v?.isVal && v.isScalar)) {
102
- return undefined
103
- }
104
- if (v.isBigInteger) {
105
- return 'biginteger'
106
- }
107
- if (v.isBigDecimal) {
108
- return 'bigdecimal'
109
- }
110
- if (v.isInteger) {
111
- return 'integer'
112
- }
113
- const t = typeof v.peg
114
- return 'number' === t ? 'float' :
115
- 'string' === t ? 'string' :
116
- 'boolean' === t ? 'boolean' :
117
- undefined
118
- }
119
-
120
138
  const av: any = operand(args[0])
121
139
  const bv: any = operand(args[1])
122
140
  const ak = opkind(av)
@@ -251,4 +269,5 @@ function decimal(v: any, k: OpKind): Decimal {
251
269
 
252
270
  export {
253
271
  PlusOpVal,
272
+ plusText,
254
273
  }
@@ -11,18 +11,20 @@ import {
11
11
 
12
12
  import { AontuContext } from '../ctx'
13
13
  import { unite } from '../unify'
14
- import { AontuError } from '../err'
14
+ import { AontuError, makeNilErr } from '../err'
15
15
 
16
16
  import {
17
17
  explainOpen,
18
18
  ec,
19
- explainClose,
19
+ explainClose
20
20
  } from '../utility'
21
21
 
22
22
 
23
23
  import { top } from './top'
24
24
 
25
25
  import { FeatureVal } from './FeatureVal'
26
+ import { trialUnify } from './FuncBaseVal'
27
+ import { superOf } from './SuperFuncVal'
26
28
 
27
29
 
28
30
  // The innermost preferred value under every pref layer: the value a
@@ -39,6 +41,22 @@ function prefInnerPeg(v: Val): Val {
39
41
  }
40
42
 
41
43
 
44
+ // A CONTAINER DEFAULT IS LEAFWISE (ADR-011 R3,
45
+ // docs/design/DEFAULTS.0.md): `*{p:1}` MEANS `{p: *1}` and `*[1]`
46
+ // means `[*1]`, so there is no such thing as a preference whose
47
+ // preferred value is a bag. Shape is not a value and takes no star --
48
+ // key optionality, closedness and a `&:` spread template ride through
49
+ // untouched; only the values a reader could override are defaulted.
50
+ //
51
+ // This is the rule `pref()` already had (its resolve walked and
52
+ // wrapped every scalar child) and the star prefix did not, so the two
53
+ // spellings of one operator disagreed: `pref({p:1}) & {q:2}` kept the
54
+ // `p` default and `*{p:1} & {q:2}` dropped it. Both spellings and the
55
+ // resolve-time case (`*$.shape`) come through here now, so they
56
+ // cannot drift apart again. Wrapping the non-bag arm rather than
57
+ // walking for scalars also closes the other half of that gap:
58
+ // `pref(integer)` and `pref(min(3))` used to answer the bare value,
59
+ // losing the preference outright.
42
60
  class PrefVal extends FeatureVal {
43
61
  isPref = true
44
62
  isGenable = true
@@ -75,6 +93,15 @@ class PrefVal extends FeatureVal {
75
93
 
76
94
  rank: number = 0
77
95
 
96
+ // THE OVERRIDE SPACE, NARROWED (ADR-011 R1). The second arm of the
97
+ // distribution -- `super(x) & every peer met so far` -- kept here as
98
+ // well as in `superpeg`, because resuper() recomputes the gate from
99
+ // the peg whenever the peg resolves and would otherwise widen it
100
+ // back to `super(x)`: a rank>=2 default, whose peg is itself a
101
+ // preference and so is re-driven, lost its narrowing that way and
102
+ // let a pinned `***false & false` be overridden by `true`.
103
+ narrowed?: Val
104
+
78
105
  constructor(
79
106
  spec: ValSpec,
80
107
  ctx?: AontuContext
@@ -88,14 +115,23 @@ class PrefVal extends FeatureVal {
88
115
  this.rank = 1 + spec.peg.rank
89
116
  }
90
117
 
91
- this.resuper()
118
+ this.resuper(ctx)
92
119
  // console.log('PVC', this.peg.canon, this.superpeg.canon)
93
120
  }
94
121
 
95
122
 
96
123
  // Recompute the type yardstick and the override gate from the current
97
124
  // peg. Called again whenever the peg resolves (e.g. a ref).
98
- private resuper() {
125
+ //
126
+ // THE GATE IS super() (ADR-011 R4, docs/design/DEFAULTS.0.md). `*x`
127
+ // is sugar for `*x | super(x)`, so the type an overriding peer must
128
+ // pass is the one the long form spells out loud -- one function, not
129
+ // a second implementation that agrees with it on the common case.
130
+ // Two special cases retired with the switch: a KIND peg gated
131
+ // nothing (`*integer` was overridden by `"s"`) and a CONSTRAINT peg
132
+ // had no gate at all, both written when a kind's superior was top.
133
+ // `super(integer)` is `number`, so `7` still wins and `"s"` refuses.
134
+ private resuper(ctx?: AontuContext) {
99
135
  // THE RANK-UNIFORM MEET (ADR-004). The yardstick is the INNERMOST
100
136
  // preferred value's kind, whatever the preference's rank: `**1.5`
101
137
  // defends `float` exactly as `*1.5` does. The old rule read the
@@ -107,25 +143,38 @@ class PrefVal extends FeatureVal {
107
143
  // rank-1 spelling of the same document kept it. One rule, every
108
144
  // rank. Pinned by test/spec/pref.tsv (pref-rank2-* rows, and the
109
145
  // flipped pref-nested-concrete-wins).
146
+ // THE RANK-UNIFORM MEET (ADR-004) is unchanged: the yardstick is
147
+ // the INNERMOST preferred value, whatever the rank, so `**1.5`
148
+ // defends `float` exactly as `*1.5` does.
110
149
  let peg: any = this.peg
111
150
  while (true === peg?.isPref) {
112
151
  peg = peg.peg
113
152
  }
114
153
 
115
- // A preference whose peg is ITSELF a kind (`*integer`) constrains
116
- // nothing: there is no type-of-a-type in this lattice, so any peer
117
- // wins. (Pinned by test/spec/var.tsv:var-pref-kind-narrow. Before
118
- // the tower this fell out of a ScalarKindVal's superior being top;
119
- // now that a leaf kind lifts to `number`, it has to be said.)
120
- if (true === peg.isScalarKind) {
121
- this.superpeg = top()
122
- return
123
- }
154
+ const base = superOf(ctx as AontuContext, peg)
155
+
156
+ // A gate that a meet has already narrowed stays narrowed: the
157
+ // override space only ever shrinks.
158
+ this.superpeg = null == this.narrowed ? base
159
+ : unite(ctx as AontuContext, base, this.narrowed,
160
+ 'pref-narrow/' + this.id)
161
+ }
162
+
124
163
 
125
- // No optional chain: superior() is contractually non-null (every
126
- // Val returns one, a NilVal returning itself), so guarding against
127
- // nullish here would claim a possibility the type does not have.
128
- this.superpeg = peg.superior()
164
+ // The default, standing but NARROWED: `*integer & 7` is `*7`, which
165
+ // is what the long form answers (`(integer&7) | (number&7)` keeps
166
+ // the star on the arm that survived). The rank rides across -- a
167
+ // ladder rung that narrows is still that rung.
168
+ // The rank is REBUILT as nesting rather than stamped: canon renders
169
+ // one star per layer (`'*' + peg.canon`), so a rank set directly on
170
+ // a single layer would print `*x` for a rank-2 default and the
171
+ // document would no longer round-trip.
172
+ private restand(met: Val, ctx: AontuContext): Val {
173
+ let out: Val = met
174
+ for (let rI = 0; rI <= this.rank; rI++) {
175
+ out = new PrefVal({ peg: out }, ctx)
176
+ }
177
+ return this.place(out)
129
178
  }
130
179
 
131
180
 
@@ -143,9 +192,27 @@ class PrefVal extends FeatureVal {
143
192
  this.peg, top(), 'pref/resolve')
144
193
  // console.log('PREF-RESOLVED', this.peg.canon, '->', resolved)
145
194
  this.peg = resolved
146
- this.resuper()
195
+ this.resuper(ctx)
147
196
  }
148
197
 
198
+ // A CONTAINER DEFAULT IS LEAFWISE IN EFFECT (ADR-011 R3), and it
199
+ // gets there through the meet above rather than through a rewrite.
200
+ // `*{p:1}` keeps its written shape -- canon prints `*{"p":1}` and
201
+ // reparses to itself, which a rewrite to `{p:*1}` would break, and
202
+ // an alternative still has a star for a disjunction to choose by.
203
+ // What makes it leafwise is that the two arms decide:
204
+ //
205
+ // `& {q:2}` the preferred value ITSELF admits the peer (maps
206
+ // merge), so the default stands as `*{p:1,q:2}` and
207
+ // `p` survives -- it used to be REPLACED outright.
208
+ // `& {p:2}` the preferred value refuses, so the gate answers:
209
+ // `super({p:1})` is `{p:integer}`, which admits it.
210
+ // `& "s"` both arms are empty, so the whole default is --
211
+ // a string used to override a map default silently.
212
+ //
213
+ // Which is every rule R3 asks for, with no bag ever standing where
214
+ // the author wrote a scalar and no canon that fails to round-trip.
215
+
149
216
  if (peer instanceof PrefVal) {
150
217
  why += 'pref-'
151
218
  if (this.id === peer.id) {
@@ -173,9 +240,20 @@ class PrefVal extends FeatureVal {
173
240
  // peer.peg.id, peer.peg, peer.peg.done,
174
241
  // )
175
242
 
176
- let peg = unite(te ? ctx.clone({ explain: ec(te, 'PREF-PEER') }) : ctx,
177
- this.peg, peer.peg, 'pref-peer/' + this.id)
178
- out = new PrefVal({ peg }, ctx)
243
+ const peg = trialUnify(ctx, prefInnerPeg(this).clone(ctx),
244
+ prefInnerPeg(peer))
245
+
246
+ // TWO DEFAULTS OF EQUAL RANK THAT CANNOT AGREE (ADR-011 R2):
247
+ // the refusal is about the DEFAULTS, not about the values they
248
+ // happen to hold, and its hint names the fix -- rank one of
249
+ // them. Compatible pegs still fold (`*1 & *integer` is `*1`),
250
+ // and identical ones collapse, so only a real disagreement
251
+ // reaches this arm. The three spellings of it -- `*1 & *7`,
252
+ // `(*1|integer) & *7` and `*1|*7` -- used to answer a value
253
+ // conflict, the newcomer, and the newcomer again.
254
+ out = undefined === peg
255
+ ? makeNilErr(ctx, 'pref_rank_clash', this, peer, 'unify')
256
+ : this.restand(peg, ctx)
179
257
  // console.log('PREF-RANK-SAME-OUT', peg, peg.done, out, out.done)
180
258
  why += 'rank-same'
181
259
  }
@@ -183,25 +261,84 @@ class PrefVal extends FeatureVal {
183
261
  else if (!peer.isTop) {
184
262
  why += 'super-'
185
263
 
186
- out = unite(te ? ctx.clone({ explain: ec(te, 'SUPER') }) : ctx,
187
- this.superpeg, peer, 'pref-super/' + this.id)
188
-
189
- // The peer added nothing beyond a type the preferred value already
190
- // satisfies (`*1 & integer`, `*1 & number`), so the preference
191
- // stands — as ITSELF, rank intact (ADR-004). Returning the peg
192
- // here (the old rule) demoted the preference to a concrete value
193
- // at rank 0 and to a lower rank above it, which both destroyed
194
- // overridability (`*1 & integer` then `2` was a conflict) and
195
- // broke the layered-defaults ladder: a team's `**debug|string`
196
- // meeting an env's `string` branch produced a rank-0 `*debug`
197
- // that then fought the env's own rank-0 `*warn` as an equal.
198
- // Anything else is a concrete override and wins (subject to the
199
- // disjunct admission gate in DisjunctVal).
200
- if (out.same(this.superpeg)) {
201
- out = this
202
- why += 'same'
264
+ // THE MEET IS THE DESUGARING, DISTRIBUTED (ADR-011 R1,
265
+ // docs/design/DEFAULTS.0.md). `*x` stands for `*x | super(x)`,
266
+ // and a peer meets a disjunction arm by arm:
267
+ //
268
+ // (x & peer) | (super(x) & peer)
269
+ //
270
+ // THE FIRST ARM DECIDES. When the preferred value itself still
271
+ // satisfies the peer the default STANDS -- `*1 & integer`,
272
+ // `*8080 & min(1024)`, `**2 & neq(1)`: the peer narrowed the
273
+ // type without ruling the default out, which is the whole point
274
+ // of writing one. Only when that arm is empty does the second
275
+ // answer, and that is the override. When BOTH are empty nothing
276
+ // remains of the disjunction the star stands for -- `empty`,
277
+ // the same refusal the written-out long form gives.
278
+ //
279
+ // The old rule asked instead whether the peer resolved to
280
+ // exactly `super(x)`, so any narrowing at all counted as an
281
+ // override: `*1 & integer` stood (the peer WAS the gate) but
282
+ // `*8080 & min(1024)` silently dropped the default and answered
283
+ // the bare constraint, and a rank ladder lost its weaker arm to
284
+ // the same rule the moment anything narrowed it.
285
+ //
286
+ // Trialled against a CLONE, on the innermost value (the
287
+ // rank-uniform meet): the preferred value must stay pristine for
288
+ // the arm that stands, and a failed trial must not leave its
289
+ // errors on the context -- the DisjunctVal admission gate's own
290
+ // precedent, and its mechanism.
291
+ const met = trialUnify(ctx, prefInnerPeg(this).clone(ctx), peer)
292
+
293
+ if (undefined !== met) {
294
+ // THE SECOND ARM IS CARRIED FORWARD, not discarded. It is the
295
+ // override space -- everything the peer would still admit
296
+ // INSTEAD of the default -- and meeting a peer narrows it just
297
+ // as it narrows the default: two successive meets compose to
298
+ // `(x & p1 & p2) | (super(x) & p1 & p2)`, which is the
299
+ // distribution over both. Dropping it let a constraint that
300
+ // arrived beside a default vanish: `r:*2` with `r:max(20)`
301
+ // stood as a bare `*2`, and `r:40` then overrode it through a
302
+ // gate that no longer remembered the bound.
303
+ //
304
+ // It cannot fail: the first arm succeeded, so `x & peer` has a
305
+ // value, and that value satisfies `super(x)` and `peer` both.
306
+ const gate = unite(te ? ctx.clone({ explain: ec(te, 'GATE') }) : ctx,
307
+ this.superpeg.clone(ctx), peer, 'pref-gate/' + this.id)
308
+
309
+ // Unchanged on both counts is the SAME preference, returned as
310
+ // itself: minting a new one every pass would keep the fixpoint
311
+ // moving for ever.
312
+ if (met.same(prefInnerPeg(this)) && gate.same(this.superpeg)) {
313
+ out = this
314
+ }
315
+ else {
316
+ const stood = this.restand(met, ctx) as PrefVal
317
+ stood.narrowed = gate
318
+ stood.superpeg = gate
319
+ out = stood
320
+ }
321
+
322
+ why += 'stands'
323
+ explainClose(te, out)
324
+ out.dc = DONE
325
+ return out
203
326
  }
204
327
 
328
+ // The override arm is trialled too: its failure is not the
329
+ // answer, it is half of the reason the answer is `empty`, and a
330
+ // recorded `no_scalar_unify` would be the code the reader sees
331
+ // however the refusal is relabelled afterwards.
332
+ const over = trialUnify(ctx, this.superpeg.clone(ctx), peer)
333
+
334
+ out = undefined !== over ? over
335
+ // A peer that arrived already failed keeps its own refusal:
336
+ // that is its failure, not the default's.
337
+ : peer.isNil ? peer
338
+ : makeNilErr(ctx, 'empty', this, peer, 'unify')
339
+
340
+
341
+
205
342
  // }
206
343
  }
207
344
  else {
@@ -234,6 +371,17 @@ class PrefVal extends FeatureVal {
234
371
 
235
372
  clone(ctx: AontuContext, spec?: ValSpec): Val {
236
373
  let out = (super.clone(ctx, spec) as PrefVal)
374
+
375
+ // THE NARROWED OVERRIDE SPACE TRAVELS WITH THE COPY (ADR-011 R1).
376
+ // A default a meet has pinned stays pinned through a reference:
377
+ // without this, `T:{e:***false & false}` copied by `f:$.T` handed
378
+ // back a default whose gate had widened again to `boolean`, and
379
+ // `f:{e:true}` overrode a value the author had pinned. The Go
380
+ // twin carries the same two fields explicitly (clone.go).
381
+ if (null != this.narrowed) {
382
+ out.narrowed = this.narrowed
383
+ out.superpeg = this.superpeg
384
+ }
237
385
  // THE PER-DESTINATION INSTANTIATION RULE (ADR-005). The default
238
386
  // clone shares the preferred value (`peg: this.peg` in Val.clone)
239
387
  // — a cloned pref spread template resolves its inner value at the