aontu 0.50.1 → 0.52.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/bin/aontu-lsp.js +6 -0
  2. package/bin/aontu.js +7 -0
  3. package/dist/aontu.d.ts +4 -2
  4. package/dist/aontu.js +21 -8
  5. package/dist/aontu.js.map +1 -1
  6. package/dist/cli.d.ts +0 -1
  7. package/dist/cli.js +43 -53
  8. package/dist/cli.js.map +1 -1
  9. package/dist/ctx.js +1 -1
  10. package/dist/ctx.js.map +1 -1
  11. package/dist/err.js +3 -2
  12. package/dist/err.js.map +1 -1
  13. package/dist/exactjson.d.ts +37 -0
  14. package/dist/exactjson.js +228 -0
  15. package/dist/exactjson.js.map +1 -0
  16. package/dist/hints.d.ts +3 -1
  17. package/dist/hints.js +187 -5
  18. package/dist/hints.js.map +1 -1
  19. package/dist/keyorder.d.ts +9 -0
  20. package/dist/keyorder.js +65 -0
  21. package/dist/keyorder.js.map +1 -0
  22. package/dist/lang.js +263 -30
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp-server.d.ts +2 -2
  25. package/dist/lsp-server.js +10 -10
  26. package/dist/lsp-server.js.map +1 -1
  27. package/dist/lsp.js +39 -21
  28. package/dist/lsp.js.map +1 -1
  29. package/dist/site.js +1 -1
  30. package/dist/site.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/unify.js +48 -1
  33. package/dist/unify.js.map +1 -1
  34. package/dist/utility.js +3 -8
  35. package/dist/utility.js.map +1 -1
  36. package/dist/val/BagVal.js +9 -6
  37. package/dist/val/BagVal.js.map +1 -1
  38. package/dist/val/BigDecimalVal.d.ts +13 -0
  39. package/dist/val/BigDecimalVal.js +80 -0
  40. package/dist/val/BigDecimalVal.js.map +1 -0
  41. package/dist/val/BigIntegerVal.d.ts +11 -0
  42. package/dist/val/BigIntegerVal.js +75 -0
  43. package/dist/val/BigIntegerVal.js.map +1 -0
  44. package/dist/val/BooleanVal.js +1 -1
  45. package/dist/val/BooleanVal.js.map +1 -1
  46. package/dist/val/CloseFuncVal.js +1 -1
  47. package/dist/val/CloseFuncVal.js.map +1 -1
  48. package/dist/val/ConjunctVal.js +1 -1
  49. package/dist/val/ConjunctVal.js.map +1 -1
  50. package/dist/val/ConstraintVal.d.ts +56 -0
  51. package/dist/val/ConstraintVal.js +407 -0
  52. package/dist/val/ConstraintVal.js.map +1 -0
  53. package/dist/val/CopyFuncVal.js +1 -1
  54. package/dist/val/CopyFuncVal.js.map +1 -1
  55. package/dist/val/Decimal.d.ts +32 -0
  56. package/dist/val/Decimal.js +318 -0
  57. package/dist/val/Decimal.js.map +1 -0
  58. package/dist/val/DisjunctVal.js +1 -29
  59. package/dist/val/DisjunctVal.js.map +1 -1
  60. package/dist/val/ExpectVal.js +1 -1
  61. package/dist/val/ExpectVal.js.map +1 -1
  62. package/dist/val/FeatureVal.js +1 -1
  63. package/dist/val/FeatureVal.js.map +1 -1
  64. package/dist/val/FuncBaseVal.js +1 -1
  65. package/dist/val/FuncBaseVal.js.map +1 -1
  66. package/dist/val/HideFuncVal.js +1 -1
  67. package/dist/val/HideFuncVal.js.map +1 -1
  68. package/dist/val/IntegerVal.d.ts +2 -0
  69. package/dist/val/IntegerVal.js +77 -5
  70. package/dist/val/IntegerVal.js.map +1 -1
  71. package/dist/val/JunctionVal.js +1 -1
  72. package/dist/val/JunctionVal.js.map +1 -1
  73. package/dist/val/KeyFuncVal.d.ts +1 -1
  74. package/dist/val/KeyFuncVal.js +34 -4
  75. package/dist/val/KeyFuncVal.js.map +1 -1
  76. package/dist/val/ListVal.js +7 -7
  77. package/dist/val/ListVal.js.map +1 -1
  78. package/dist/val/LowerFuncVal.js +21 -4
  79. package/dist/val/LowerFuncVal.js.map +1 -1
  80. package/dist/val/MapVal.js +17 -13
  81. package/dist/val/MapVal.js.map +1 -1
  82. package/dist/val/MoveFuncVal.js +26 -9
  83. package/dist/val/MoveFuncVal.js.map +1 -1
  84. package/dist/val/NilVal.d.ts +1 -0
  85. package/dist/val/NilVal.js +10 -3
  86. package/dist/val/NilVal.js.map +1 -1
  87. package/dist/val/NullVal.js +1 -1
  88. package/dist/val/NullVal.js.map +1 -1
  89. package/dist/val/NumberVal.d.ts +1 -0
  90. package/dist/val/NumberVal.js +25 -4
  91. package/dist/val/NumberVal.js.map +1 -1
  92. package/dist/val/OpBaseVal.js +10 -8
  93. package/dist/val/OpBaseVal.js.map +1 -1
  94. package/dist/val/OpenFuncVal.js +1 -1
  95. package/dist/val/OpenFuncVal.js.map +1 -1
  96. package/dist/val/PathFuncVal.js +10 -2
  97. package/dist/val/PathFuncVal.js.map +1 -1
  98. package/dist/val/PlusOpVal.d.ts +3 -1
  99. package/dist/val/PlusOpVal.js +140 -29
  100. package/dist/val/PlusOpVal.js.map +1 -1
  101. package/dist/val/PrefFuncVal.js +1 -1
  102. package/dist/val/PrefFuncVal.js.map +1 -1
  103. package/dist/val/PrefVal.d.ts +2 -0
  104. package/dist/val/PrefVal.js +44 -7
  105. package/dist/val/PrefVal.js.map +1 -1
  106. package/dist/val/RefVal.d.ts +2 -0
  107. package/dist/val/RefVal.js +135 -11
  108. package/dist/val/RefVal.js.map +1 -1
  109. package/dist/val/ScalarKindVal.d.ts +12 -2
  110. package/dist/val/ScalarKindVal.js +108 -13
  111. package/dist/val/ScalarKindVal.js.map +1 -1
  112. package/dist/val/ScalarVal.d.ts +1 -0
  113. package/dist/val/ScalarVal.js +18 -3
  114. package/dist/val/ScalarVal.js.map +1 -1
  115. package/dist/val/StringVal.js +1 -1
  116. package/dist/val/StringVal.js.map +1 -1
  117. package/dist/val/SuperFuncVal.d.ts +1 -1
  118. package/dist/val/SuperFuncVal.js +15 -2
  119. package/dist/val/SuperFuncVal.js.map +1 -1
  120. package/dist/val/TopVal.js +1 -1
  121. package/dist/val/TopVal.js.map +1 -1
  122. package/dist/val/TypeFuncVal.js +1 -1
  123. package/dist/val/TypeFuncVal.js.map +1 -1
  124. package/dist/val/UpperFuncVal.js +21 -4
  125. package/dist/val/UpperFuncVal.js.map +1 -1
  126. package/dist/val/Val.d.ts +1 -1
  127. package/dist/val/Val.js +11 -21
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/VarVal.js +8 -2
  130. package/dist/val/VarVal.js.map +1 -1
  131. package/dist/val/numcmp.d.ts +14 -0
  132. package/dist/val/numcmp.js +134 -0
  133. package/dist/val/numcmp.js.map +1 -0
  134. package/dist/val/numkind.d.ts +6 -0
  135. package/dist/val/numkind.js +224 -0
  136. package/dist/val/numkind.js.map +1 -0
  137. package/dist/val/valutil.d.ts +1 -0
  138. package/dist/val/valutil.js +35 -0
  139. package/dist/val/valutil.js.map +1 -1
  140. package/package.json +7 -5
  141. package/src/aontu.ts +26 -9
  142. package/src/cli.ts +48 -21
  143. package/src/ctx.ts +1 -1
  144. package/src/err.ts +3 -2
  145. package/src/exactjson.ts +254 -0
  146. package/src/hints.ts +213 -4
  147. package/src/keyorder.ts +73 -0
  148. package/src/lang.ts +303 -31
  149. package/src/lsp-server.ts +17 -15
  150. package/src/lsp.ts +37 -14
  151. package/src/site.ts +1 -1
  152. package/src/type.ts +1 -1
  153. package/src/unify.ts +52 -1
  154. package/src/utility.ts +3 -9
  155. package/src/val/BagVal.ts +9 -6
  156. package/src/val/BigDecimalVal.ts +117 -0
  157. package/src/val/BigIntegerVal.ts +110 -0
  158. package/src/val/BooleanVal.ts +1 -1
  159. package/src/val/CloseFuncVal.ts +1 -1
  160. package/src/val/ConjunctVal.ts +1 -1
  161. package/src/val/ConstraintVal.ts +561 -0
  162. package/src/val/CopyFuncVal.ts +1 -1
  163. package/src/val/Decimal.ts +385 -0
  164. package/src/val/DisjunctVal.ts +1 -37
  165. package/src/val/ExpectVal.ts +1 -1
  166. package/src/val/FeatureVal.ts +1 -1
  167. package/src/val/FuncBaseVal.ts +1 -1
  168. package/src/val/HideFuncVal.ts +1 -1
  169. package/src/val/IntegerVal.ts +81 -5
  170. package/src/val/JunctionVal.ts +1 -1
  171. package/src/val/KeyFuncVal.ts +36 -4
  172. package/src/val/ListVal.ts +3 -5
  173. package/src/val/LowerFuncVal.ts +22 -5
  174. package/src/val/MapVal.ts +13 -9
  175. package/src/val/MoveFuncVal.ts +27 -10
  176. package/src/val/NilVal.ts +13 -3
  177. package/src/val/NullVal.ts +1 -1
  178. package/src/val/NumberVal.ts +27 -4
  179. package/src/val/OpBaseVal.ts +10 -10
  180. package/src/val/OpenFuncVal.ts +1 -1
  181. package/src/val/PathFuncVal.ts +10 -2
  182. package/src/val/PlusOpVal.ts +168 -32
  183. package/src/val/PrefFuncVal.ts +1 -1
  184. package/src/val/PrefVal.ts +70 -8
  185. package/src/val/RefVal.ts +143 -11
  186. package/src/val/ScalarKindVal.ts +123 -12
  187. package/src/val/ScalarVal.ts +20 -3
  188. package/src/val/StringVal.ts +1 -1
  189. package/src/val/SuperFuncVal.ts +15 -2
  190. package/src/val/TopVal.ts +1 -1
  191. package/src/val/TypeFuncVal.ts +1 -1
  192. package/src/val/UpperFuncVal.ts +22 -5
  193. package/src/val/Val.ts +13 -8
  194. package/src/val/VarVal.ts +8 -2
  195. package/src/val/numcmp.ts +189 -0
  196. package/src/val/numkind.ts +256 -0
  197. package/src/val/valutil.ts +36 -0
@@ -20,7 +20,6 @@ import {
20
20
  explainOpen,
21
21
  ec,
22
22
  explainClose,
23
- items,
24
23
  } from '../utility'
25
24
 
26
25
  import { makeNilErr, AontuError } from '../err'
@@ -33,7 +32,6 @@ import {
33
32
  import { ConjunctVal } from './ConjunctVal'
34
33
  import { NilVal } from './NilVal'
35
34
  import { BagVal } from './BagVal'
36
- import { empty } from './Val'
37
35
 
38
36
 
39
37
  class ListVal extends BagVal {
@@ -121,9 +119,9 @@ class ListVal extends BagVal {
121
119
 
122
120
  propagateMarks(this, child)
123
121
 
122
+ // child is non-nullish: propagateMarks above dereferences it.
124
123
  out.peg[key] =
125
- undefined === child ? key_spread_cj :
126
- child.isNil ? child :
124
+ child.isNil ? child :
127
125
  key_spread_cj.isNil ? key_spread_cj :
128
126
  key_spread_cj.isTop && child.done ? child :
129
127
  child.isTop && key_spread_cj.done ? key_spread_cj :
@@ -271,7 +269,7 @@ class ListVal extends BagVal {
271
269
  this.peg[k].canon).join(',') +
272
270
  ']'
273
271
  }
274
- }
272
+ } /* node:coverage ignore next 8 */
275
273
 
276
274
 
277
275
 
@@ -15,7 +15,8 @@ import { makeNilErr } from '../err'
15
15
 
16
16
  import { NilVal } from '../val/NilVal'
17
17
  import { ScalarKindVal } from '../val/ScalarKindVal'
18
- import { makeScalar } from '../val/valutil'
18
+ import { makeScalarLike } from '../val/valutil'
19
+ import { Decimal } from '../val/Decimal'
19
20
 
20
21
 
21
22
 
@@ -43,14 +44,30 @@ class LowerFuncVal extends FuncBaseVal {
43
44
 
44
45
 
45
46
  resolve(ctx: AontuContext | undefined, args: Val[]) {
46
- const oldpeg = args?.[0].peg
47
+ // A missing argument (`lower()`) must produce an invalid-arg error
48
+ // value, as the Go port does — reading .peg off nothing threw a
49
+ // TypeError that the unifier could only report as an opaque
50
+ // internal error.
51
+ const arg = args?.[0]
52
+ const oldpeg = arg?.peg
47
53
  const peg = 'string' === typeof oldpeg ? oldpeg.toLowerCase() :
48
54
  'number' === typeof oldpeg ? Math.floor(oldpeg) :
49
- undefined
55
+ // The exact leaves take an EXACT floor and keep their kind: a
56
+ // biginteger is already integral so it is its own floor, and a
57
+ // bigdecimal floors by coefficient arithmetic. Math.floor is not
58
+ // an option for either — it would round the value into binary64
59
+ // first, which is the loss the `0d` leaves exist to refuse.
60
+ 'bigint' === typeof oldpeg ? oldpeg :
61
+ oldpeg instanceof Decimal ? oldpeg.floor() :
62
+ undefined
50
63
  const out = this.place(
51
64
  null == peg ?
52
65
  makeNilErr(ctx, 'invalid-arg', this) :
53
- makeScalar(peg)
66
+ // The floor keeps the ARGUMENT's kind (lower(2) is an integer 2,
67
+ // lower(1.9) is a number 1) — the function must not narrow
68
+ // number to integer, and this also makes the actual result kind
69
+ // agree with the superior() advertised below.
70
+ makeScalarLike(peg, arg)
54
71
  )
55
72
  return out
56
73
  }
@@ -65,7 +82,7 @@ class LowerFuncVal extends FuncBaseVal {
65
82
  super.superior()
66
83
  }
67
84
 
68
- }
85
+ } /* node:coverage ignore next 6 */
69
86
 
70
87
 
71
88
  export {
package/src/val/MapVal.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  import { ConjunctVal } from './ConjunctVal'
33
33
  import { NilVal } from './NilVal'
34
34
  import { BagVal } from './BagVal'
35
+ import { cmpCodePoint } from '../keyorder'
35
36
 
36
37
 
37
38
  // Structural snapshots of ref spreads (see MapVal.unify), keyed by the
@@ -41,7 +42,7 @@ import { BagVal } from './BagVal'
41
42
  // pass. The map lives on the unify root ctx (see Unify), so it persists
42
43
  // across fixpoint passes and is GC'd with the run.
43
44
  function spreadSnapKey(cj: any): string {
44
- return cj.canon + '~' + (cj.site?.row ?? -1) + ':' + (cj.site?.col ?? -1)
45
+ return cj.canon + '~' + cj.site.row + ':' + cj.site.col
45
46
  }
46
47
 
47
48
  // Snapshot a path-dependent ref spread to its structural target once,
@@ -228,7 +229,9 @@ class MapVal extends BagVal {
228
229
  // but costs O(keys) deep template clones per pass on large
229
230
  // models — the dominant cost on generated-SDK model trees.
230
231
  let oval: Val
231
- if (undefined !== child && !spread_cj.isTop
232
+ // No `undefined !== child` here: propagateMarks above already
233
+ // dereferenced it, so a missing child would have thrown there.
234
+ if (!spread_cj.isTop
232
235
  && (child as any)._spr === (spread_cj as any).id) {
233
236
  oval = child.done ? child :
234
237
  unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
@@ -238,9 +241,9 @@ class MapVal extends BagVal {
238
241
  else {
239
242
  const key_spread_cj = spread_cj.spreadClone(keyctx)
240
243
 
244
+ // child is non-nullish: propagateMarks above dereferences it.
241
245
  oval =
242
- undefined === child ? key_spread_cj :
243
- child.isNil ? child :
246
+ child.isNil ? child :
244
247
  key_spread_cj.isNil ? key_spread_cj :
245
248
  key_spread_cj.isTop && child.done ? child :
246
249
  child.isTop && key_spread_cj.done ? key_spread_cj :
@@ -419,10 +422,11 @@ class MapVal extends BagVal {
419
422
 
420
423
 
421
424
  get canon() {
422
- // Keys are emitted alphabetically so the canonical form is
423
- // independent of insertion/unification order (and matches the Go
424
- // port, whose JSON marshaling also sorts keys).
425
- let keys = Object.keys(this.peg).sort()
425
+ // Keys are emitted in CODE POINT order so the canonical form is
426
+ // independent of insertion/unification order and matches the Go
427
+ // port. A bare .sort() is UTF-16 code-unit order, which puts an
428
+ // astral key ahead of everything in U+E000-U+FFFF -- see cmpCodePoint.
429
+ let keys = Object.keys(this.peg).sort(cmpCodePoint)
426
430
  return '' +
427
431
  // this.errcanon() +
428
432
  // (this.mark.type ? '<type>' : '') +
@@ -447,7 +451,7 @@ class MapVal extends BagVal {
447
451
  return this.spread.cj ? '&:' + this.spread.cj.inspect(null == d ? 0 : d + 1) : ''
448
452
  }
449
453
 
450
- }
454
+ } /* node:coverage ignore next 6 */
451
455
 
452
456
 
453
457
  export {
@@ -57,25 +57,42 @@ class MoveFuncVal extends FuncBaseVal {
57
57
  if (!orig.isNil) {
58
58
  const src = orig.clone(ctx)
59
59
 
60
- if (src.isRef) {
61
- src.mark._hide_found = true
60
+ // THE HIDE-WALK ONLY RUNS WHEN THE CLONE IS A SEPARATE OBJECT.
61
+ //
62
+ // move() is the only function that marks the ORIGINAL rather than
63
+ // the clone -- that is how the value disappears from its old home
64
+ // and reappears at the destination. It relies on clone() returning
65
+ // something distinct to carry to the destination.
66
+ //
67
+ // TopVal.clone is the IDENTITY function, so for `move(top)` the
68
+ // clone and the original are the same object and the walk hid the
69
+ // very value being returned: the key vanished from generated output
70
+ // with no error, while canon still showed `x:top` -- the port
71
+ // disagreeing with itself.
72
+ //
73
+ // With the walk skipped, `move(top)` keeps `top` at the destination
74
+ // and then errors there because top is not generable, which is
75
+ // already what `x:top`, `x:copy(top)` and `x:pref(top)` do in both
76
+ // ports, and what `move(number)` does for the same reason.
77
+ if (src !== orig) {
78
+ if (src.isRef) {
79
+ src.mark._hide_found = true
80
+ }
81
+
82
+ walk(orig, (_key: string | number | undefined, val: Val) => {
83
+ val.mark.hide = true
84
+ return val
85
+ })
62
86
  }
63
87
 
64
- walk(orig, (_key: string | number | undefined, val: Val) => {
65
- val.mark.hide = true
66
- return val
67
- })
68
-
69
- // out = new CopyFuncVal({ peg: [src] }, ctx)
70
88
  out = new PrefFuncVal({ peg: [src] }, ctx)
71
- // out = src
72
89
  }
73
90
 
74
91
  // console.log('MOVE-resolve', orig, out)
75
92
 
76
93
  return out
77
94
  }
78
- }
95
+ } /* node:coverage ignore next 6 */
79
96
 
80
97
 
81
98
  export {
package/src/val/NilVal.ts CHANGED
@@ -18,6 +18,8 @@ import { Val, EMPTY_ERR } from './Val'
18
18
 
19
19
  import { AontuError, descErr } from '../err'
20
20
 
21
+ import { codeClass } from '../hints'
22
+
21
23
 
22
24
  class NilVal extends Val {
23
25
  isNil = true
@@ -98,10 +100,10 @@ class NilVal extends Val {
98
100
  if (spec && 'object' === typeof spec) {
99
101
  this.why = spec?.why
100
102
  this.msg = 'string' === typeof spec?.msg ? spec.msg : this.msg
101
- this.err = spec ?
103
+ this.err =
102
104
  Array.isArray(spec.err) ? [...spec.err] :
103
105
  null != spec.err ? [spec.err] :
104
- EMPTY_ERR : EMPTY_ERR
106
+ EMPTY_ERR
105
107
  }
106
108
 
107
109
  // Nil is always DONE, by definition.
@@ -135,6 +137,14 @@ class NilVal extends Val {
135
137
  }
136
138
 
137
139
 
140
+ // The code's class from the shared registry (test/spec/errcodes.tsv):
141
+ // conflict | incomplete | reference | parse | budget | internal.
142
+ // A why-less nil classifies as its eventual gen-time code, nil_gen.
143
+ get class(): string {
144
+ return codeClass(null == this.why ? 'nil_gen' : String(this.why))
145
+ }
146
+
147
+
138
148
  gen(ctx: AontuContext) {
139
149
  // Unresolved nil cannot be generated, so always an error.
140
150
 
@@ -174,7 +184,7 @@ class NilVal extends Val {
174
184
  //
175
185
  // Do NOT use this sentinel for errors that may surface: those need
176
186
  // real NilVals with proper site/path info for descErr formatting.
177
- const TRIAL_NIL = new NilVal({ why: '|:trial-nil' })
187
+ const TRIAL_NIL = new NilVal({ why: '|:trial-nil' }) /* node:coverage ignore next 7 */
178
188
 
179
189
 
180
190
  export {
@@ -29,7 +29,7 @@ class NullVal extends ScalarVal {
29
29
  unify(peer: Val, ctx: AontuContext): Val {
30
30
  return super.unify(peer, ctx)
31
31
  }
32
- }
32
+ } /* node:coverage ignore next 7 */
33
33
 
34
34
 
35
35
  export {
@@ -12,6 +12,7 @@ import {
12
12
  import { makeNilErr, AontuError } from '../err'
13
13
 
14
14
  import { ScalarVal } from './ScalarVal'
15
+ import { Float } from './ScalarKindVal'
15
16
 
16
17
  import {
17
18
  explainOpen,
@@ -20,6 +21,10 @@ import {
20
21
 
21
22
 
22
23
 
24
+ // The IEEE-754 binary64 leaf of the number tower. Its KIND is Float (the
25
+ // keyword `float`), not the `number` supertype: `number` names the family
26
+ // of numeric leaves and never tags a concrete value. The class name is
27
+ // historical -- `number` used to name this leaf.
23
28
  class NumberVal extends ScalarVal {
24
29
  isNumber = true
25
30
 
@@ -34,7 +39,7 @@ class NumberVal extends ScalarVal {
34
39
  throw new AontuError('not-number: ' + spec.peg)
35
40
  }
36
41
 
37
- super({ ...spec, kind: Number }, ctx)
42
+ super({ ...spec, kind: Float }, ctx)
38
43
  }
39
44
 
40
45
 
@@ -44,7 +49,7 @@ class NumberVal extends ScalarVal {
44
49
  let out: Val = this
45
50
 
46
51
  if (null != peer) {
47
- if (peer.isScalarKind) {
52
+ if (peer.isScalarKind || (peer as any).isConstraint) {
48
53
  out = peer.unify(this, ctx)
49
54
  }
50
55
  else if (
@@ -52,7 +57,7 @@ class NumberVal extends ScalarVal {
52
57
  peer.kind === this.kind &&
53
58
  peer.peg === this.peg
54
59
  ) {
55
- // Same kind (both Number) and equal value: integer/float no
60
+ // Same kind (both Float) and equal value: integer/float no
56
61
  // longer cross-unify, so peer is necessarily a NumberVal here.
57
62
  out = this
58
63
  }
@@ -72,7 +77,25 @@ class NumberVal extends ScalarVal {
72
77
 
73
78
  return out
74
79
  }
75
- }
80
+
81
+
82
+ // Canon must round-trip kind: reparsing the canon of a float-kind
83
+ // value has to yield a float-kind value again. A rendering with no
84
+ // '.' and no exponent (1 -> "1") would reparse as an integer, so it
85
+ // gains a ".0" suffix. Renderings that already carry a fraction, an
86
+ // exponent, or are NaN/Infinity are left alone.
87
+ //
88
+ // This is the CANON path only. String coercion inside `+` keeps plain
89
+ // JS parity ("a"+1.0 is "a1"), so it must not pick up the suffix.
90
+ get canon() {
91
+ const s = super.canon
92
+ return (s.includes('.') ||
93
+ s.includes('e') ||
94
+ s.includes('E') ||
95
+ s.includes('N') ||
96
+ s.includes('I')) ? s : s + '.0'
97
+ }
98
+ } /* node:coverage ignore next 6 */
76
99
 
77
100
 
78
101
  export {
@@ -67,7 +67,11 @@ class OpBaseVal extends FeatureVal {
67
67
 
68
68
  unify(peer: Val, ctx: AontuContext): Val {
69
69
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Op:' + this.opname(), this, peer)
70
- let out: Val = this
70
+ // Declared without an initial value: every arm below assigns it, and
71
+ // seeding it with `this` made the two arms that stand the op read as
72
+ // redundant self-assignments. The arms themselves stay as they are —
73
+ // they mirror the dispatch switch in go/op.go arm for arm (ADR-001).
74
+ let out: Val
71
75
 
72
76
  if (this.id == peer.id) {
73
77
  return this
@@ -88,16 +92,12 @@ class OpBaseVal extends FeatureVal {
88
92
  // console.log('OPVAL', this.id, this.opname(), pegdone, newpeg.map(p => p.canon))
89
93
 
90
94
  if (pegdone) {
91
- let result: Val | undefined = null == ctx ? this : this.operate(ctx, newpeg)
92
-
93
- result = result || this
94
-
95
- if (null == result && this.canon === peer.canon) {
96
- out = this
97
- }
95
+ // `|| this` makes result truthy, so an op that cannot compute yet
96
+ // takes the OpBaseVal arm below rather than a separate null arm.
97
+ let result: Val = this.operate(ctx, newpeg) || this
98
98
 
99
99
  // TODO: should be result.isOp
100
- else if (result instanceof OpBaseVal) {
100
+ if (result instanceof OpBaseVal) {
101
101
  if (peer.isTop) {
102
102
  out = this
103
103
  }
@@ -223,7 +223,7 @@ class OpBaseVal extends FeatureVal {
223
223
 
224
224
  return undefined
225
225
  }
226
- }
226
+ } /* node:coverage ignore next 6 */
227
227
 
228
228
 
229
229
  export {
@@ -52,7 +52,7 @@ class OpenFuncVal extends FuncBaseVal {
52
52
  return argval
53
53
  }
54
54
 
55
- }
55
+ } /* node:coverage ignore next 6 */
56
56
 
57
57
 
58
58
  export {
@@ -45,7 +45,15 @@ class PathFuncVal extends FuncBaseVal {
45
45
  let arg = args[0]
46
46
 
47
47
  if (0 === this.prepared) {
48
- if (arg.isScalar) {
48
+ // A missing argument (`path()`) must produce an invalid-arg error
49
+ // value, as the Go port does -- reading .isScalar off nothing threw
50
+ // a TypeError that the unifier could only report as an opaque
51
+ // internal error. Same discipline breach, and same fix, as the one
52
+ // UpperFuncVal records; path() was missed in that sweep.
53
+ if (null == arg) {
54
+ arg = makeNilErr(ctx, 'invalid-arg', this)
55
+ }
56
+ else if (arg.isScalar) {
49
57
  arg = this.place(new RefVal({ peg: [arg], absolute: false }))
50
58
  }
51
59
  else if (!arg.isRef) {
@@ -67,7 +75,7 @@ class PathFuncVal extends FuncBaseVal {
67
75
  return out
68
76
  }
69
77
 
70
- }
78
+ } /* node:coverage ignore next 6 */
71
79
 
72
80
 
73
81
  export {
@@ -12,14 +12,53 @@ import {
12
12
  } from '../ctx'
13
13
 
14
14
 
15
+ import { makeNilErr } from '../err'
15
16
 
16
17
  import { IntegerVal } from '../val/IntegerVal'
17
18
  import { NumberVal } from '../val/NumberVal'
18
19
  import { StringVal } from '../val/StringVal'
19
20
  import { BooleanVal } from '../val/BooleanVal'
21
+ import { BigIntegerVal } from '../val/BigIntegerVal'
22
+ import { BigDecimalVal } from '../val/BigDecimalVal'
23
+ import { Decimal, decimalOverBudget } from '../val/Decimal'
24
+ import { integerDigits } from '../val/numkind'
25
+ import { isIntegerStorable } from '../val/numkind'
20
26
  import { OpBaseVal } from './OpBaseVal'
21
27
 
22
28
 
29
+ // The operand kinds `+` understands. Anything else (a kind, map, list,
30
+ // null, top, func) is not an operand at all and leaves the op
31
+ // unresolved.
32
+ type OpKind =
33
+ 'string' | 'boolean' | 'integer' | 'float' | 'biginteger' | 'bigdecimal'
34
+
35
+
36
+ // D6 -- THE EXACT LADDER: integer < biginteger < bigdecimal. A mixed
37
+ // operation between exact leaves promotes to the WIDEST operand and is
38
+ // computed exactly, so `1 + 0d2` is a biginteger and `1 + 0d0.5` a
39
+ // bigdecimal.
40
+ //
41
+ // `float` is deliberately absent: it is OFF the ladder, keeping only its
42
+ // classic contagion against `integer`. Its absence from this table is
43
+ // what makes the float-with-big pairs fall through to the hard error
44
+ // below rather than promoting.
45
+ //
46
+ // Results NEVER demote: `0d5 + -0d2` stays a biginteger even though 3
47
+ // would fit an int64. Demotion would make the result kind depend on the
48
+ // values rather than on the operand kinds, and a document could then
49
+ // change leaf under a value edit.
50
+ const EXACT_RANK: Record<string, number> = {
51
+ integer: 1,
52
+ biginteger: 2,
53
+ bigdecimal: 3,
54
+ }
55
+
56
+
57
+ function isBig(k: OpKind): boolean {
58
+ return 'biginteger' === k || 'bigdecimal' === k
59
+ }
60
+
61
+
23
62
  class PlusOpVal extends OpBaseVal {
24
63
  isPlusOp = true
25
64
 
@@ -40,60 +79,122 @@ class PlusOpVal extends OpBaseVal {
40
79
  }
41
80
 
42
81
 
43
- operate(_ctx: AontuContext, args: Val[]) {
82
+ operate(ctx: AontuContext, args: Val[]) {
44
83
  // Only concrete scalar operands are valid: anything else (kinds,
45
84
  // maps, lists, null, top, funcs) must not coerce — the JS `+` would
46
85
  // leak internals like "[object Object]" into output. A non-scalar
47
86
  // operand leaves the op unresolved, which generate() reports.
48
- const prim = (v: any) => {
49
- // A pref operand contributes its preferred value (`pref(1)+2`).
87
+ const operand = (v: any) => {
88
+ // A pref operand contributes its preferred value (`pref(1)+2`),
89
+ // and therefore that value's kind too.
50
90
  while (v?.isPref) {
51
91
  v = v.peg
52
92
  }
53
- const p = v?.isVal && v.isScalar ? v.peg : undefined
54
- const t = typeof p
55
- return 'string' === t || 'number' === t || 'boolean' === t ? p : undefined
93
+ return v
56
94
  }
57
- let a: any = prim(args[0])
58
- let b: any = prim(args[1])
59
95
 
60
- if (undefined === a || undefined === b) {
61
- return undefined
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
62
118
  }
63
119
 
64
- const at = typeof a
65
- const bt = typeof b
120
+ const av: any = operand(args[0])
121
+ const bv: any = operand(args[1])
122
+ const ak = opkind(av)
123
+ const bk = opkind(bv)
66
124
 
67
- let peg = undefined
68
- if ('boolean' === at && 'boolean' === bt) {
69
- peg = a || b
70
- }
71
- else if ('string' === at || 'string' === bt) {
72
- peg = String(a) + String(b)
73
- }
74
- else if ('boolean' === at || 'boolean' === bt) {
75
- // boolean mixed with a number does not coerce (no JS 0/1).
125
+ if (undefined === ak || undefined === bk) {
76
126
  return undefined
77
127
  }
78
- else {
79
- peg = a + b
128
+
129
+ if ('boolean' === ak && 'boolean' === bk) {
130
+ return new BooleanVal({ peg: av.peg || bv.peg })
131
+ }
132
+
133
+ // STRING CONCATENATION RENDERS DIGITS, NOT KIND DECORATION. The `0d`
134
+ // marker is canon's way of naming the leaf and never belongs in a
135
+ // string, exactly as R4's `.0` float suffix does not ("q" + 0d0.1 is
136
+ // "q0.1", and "q" + 1.0 is "q1"). Decimal.toString() is that
137
+ // marker-free rendering, which is why canon() wraps it rather than
138
+ // the other way round.
139
+ if ('string' === ak || 'string' === bk) {
140
+ return new StringVal({ peg: digits(av, ak) + digits(bv, bk) })
80
141
  }
81
142
 
82
- let pegtype = typeof peg
143
+ // A boolean mixed with a number does not coerce (no JS 0/1).
144
+ if ('boolean' === ak || 'boolean' === bk) {
145
+ return undefined
146
+ }
83
147
 
84
- let out = undefined
148
+ // FLOAT IS OFF THE EXACT LADDER, and mixing it with either big leaf
149
+ // is a hard error in BOTH operand orders: a big type never silently
150
+ // becomes a binary float. Promotion the other way is no better —
151
+ // binary64 cannot hold every exact value, so either direction throws
152
+ // away exactness the document explicitly asked for by writing `0d`.
153
+ // The error names both leaves in operand order.
154
+ if (('float' === ak && isBig(bk)) || (isBig(ak) && 'float' === bk)) {
155
+ return makeNilErr(ctx, 'exact_float_mix', this, undefined, 'add',
156
+ { left: ak, right: bk })
157
+ }
85
158
 
86
- if ('string' === pegtype) {
87
- out = new StringVal({ peg })
159
+ // Float with float or integer: unchanged R5 contagion, binary64
160
+ // addition, float result (`1 + 2.0` is `3.0`).
161
+ if ('float' === ak || 'float' === bk) {
162
+ return new NumberVal({ peg: av.peg + bv.peg })
88
163
  }
89
- else if ('boolean' === pegtype) {
90
- out = new BooleanVal({ peg })
164
+
165
+ // Both operands are on the exact ladder: promote to the widest.
166
+ const rank = EXACT_RANK[bk] < EXACT_RANK[ak] ? EXACT_RANK[ak] : EXACT_RANK[bk]
167
+
168
+ if (EXACT_RANK.bigdecimal === rank) {
169
+ // Exact scaled-decimal addition. The budget applies to results as
170
+ // well as literals (D6): an exact sum too wide to hold is refused,
171
+ // never rounded to fit.
172
+ const sum = decimal(av, ak).add(decimal(bv, bk))
173
+ return decimalOverBudget(sum) ?
174
+ makeNilErr(ctx, 'decimal_budget', this, undefined, 'add') :
175
+ new BigDecimalVal({ peg: sum })
91
176
  }
92
- else if ('number' === pegtype) {
93
- out = Number.isInteger(peg) ? new IntegerVal({ peg }) : new NumberVal({ peg })
177
+
178
+ // integer and biginteger both add as exact integers.
179
+ const sum = integer(av, ak) + integer(bv, bk)
180
+
181
+ if (EXACT_RANK.biginteger === rank) {
182
+ // Unbounded and exact: nothing to check, and no demotion to
183
+ // `integer` however small the result.
184
+ return new BigIntegerVal({ peg: sum })
94
185
  }
95
186
 
96
- return out
187
+ // INTEGER + INTEGER IS COMPUTED EXACTLY, then offered to the integer
188
+ // leaf under the same storage contract R1 applies to a literal.
189
+ // Adding through float64 (as this did) silently rounded sums of
190
+ // exact operands — 4503599627370496 + 4503599627370497 came back as
191
+ // …992 — which is precisely the corruption the tower refuses. The
192
+ // sum that will not fit is an error pointing at `0d`, not a rounded
193
+ // answer.
194
+ return isIntegerStorable(sum) ?
195
+ new IntegerVal({ peg: Number(sum) }) :
196
+ makeNilErr(ctx, 'inexact_integer_sum', this, undefined, 'add',
197
+ { sum: sum.toString() })
97
198
  }
98
199
 
99
200
 
@@ -104,6 +205,41 @@ class PlusOpVal extends OpBaseVal {
104
205
  }
105
206
 
106
207
 
208
+ // The digits of an operand for string concatenation: no `0d` marker, no
209
+ // R4 `.0` suffix — the plain rendering of the number, in every leaf.
210
+ //
211
+ // INTEGER KIND GOES THROUGH integerDigits. This was the THIRD site to
212
+ // render an integer-kind peg with JavaScript's shortest round-tripping
213
+ // form, so `"" + 1152921504606846976` produced "1152921504606847000" — a
214
+ // different integer — where Go produced the exact digits. See #21 and
215
+ // integerDigits, which exists so the set of such sites is greppable.
216
+ //
217
+ // The other leaves are already exact or already correct: a bigint
218
+ // stringifies to its exact digits, a Decimal renders its own, and a FLOAT
219
+ // must keep String() because its shortest form is the right answer and is
220
+ // what Go prints too (`"a" + 1.0` is "a1").
221
+ function digits(v: any, k: OpKind): string {
222
+ return 'bigdecimal' === k ? v.peg.toString() :
223
+ 'integer' === k ? integerDigits(v.peg) :
224
+ String(v.peg)
225
+ }
226
+
227
+
228
+ // An exact-ladder operand as an exact integer. Only reached for the two
229
+ // integral leaves; an `integer` peg is integral by construction, so
230
+ // BigInt() is exact.
231
+ function integer(v: any, k: OpKind): bigint {
232
+ return 'biginteger' === k ? v.peg : BigInt(v.peg)
233
+ }
234
+
235
+
236
+ // An exact-ladder operand as a Decimal, for the promotion to bigdecimal.
237
+ // Scale 0 normalises to the one decimal place the leaf keeps.
238
+ function decimal(v: any, k: OpKind): Decimal {
239
+ return 'bigdecimal' === k ? v.peg : new Decimal(integer(v, k), 0)
240
+ } /* node:coverage ignore next 6 */
241
+
242
+
107
243
  export {
108
244
  PlusOpVal,
109
245
  }
@@ -65,7 +65,7 @@ class PrefFuncVal extends FuncBaseVal {
65
65
  return out
66
66
  }
67
67
 
68
- }
68
+ } /* node:coverage ignore next 6 */
69
69
 
70
70
 
71
71
  export {