aontu 0.50.0 → 0.51.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 (102) hide show
  1. package/dist/aontu.d.ts +4 -2
  2. package/dist/aontu.js +18 -2
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.js +33 -7
  5. package/dist/cli.js.map +1 -1
  6. package/dist/exactjson.d.ts +37 -0
  7. package/dist/exactjson.js +228 -0
  8. package/dist/exactjson.js.map +1 -0
  9. package/dist/hints.js +61 -4
  10. package/dist/hints.js.map +1 -1
  11. package/dist/keyorder.d.ts +9 -0
  12. package/dist/keyorder.js +65 -0
  13. package/dist/keyorder.js.map +1 -0
  14. package/dist/lang.js +200 -4
  15. package/dist/lang.js.map +1 -1
  16. package/dist/lsp.js +12 -2
  17. package/dist/lsp.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/val/BagVal.js +8 -5
  20. package/dist/val/BagVal.js.map +1 -1
  21. package/dist/val/BigDecimalVal.d.ts +13 -0
  22. package/dist/val/BigDecimalVal.js +80 -0
  23. package/dist/val/BigDecimalVal.js.map +1 -0
  24. package/dist/val/BigIntegerVal.d.ts +11 -0
  25. package/dist/val/BigIntegerVal.js +75 -0
  26. package/dist/val/BigIntegerVal.js.map +1 -0
  27. package/dist/val/Decimal.d.ts +32 -0
  28. package/dist/val/Decimal.js +318 -0
  29. package/dist/val/Decimal.js.map +1 -0
  30. package/dist/val/IntegerVal.d.ts +2 -0
  31. package/dist/val/IntegerVal.js +75 -3
  32. package/dist/val/IntegerVal.js.map +1 -1
  33. package/dist/val/KeyFuncVal.d.ts +1 -1
  34. package/dist/val/KeyFuncVal.js +33 -3
  35. package/dist/val/KeyFuncVal.js.map +1 -1
  36. package/dist/val/LowerFuncVal.js +20 -3
  37. package/dist/val/LowerFuncVal.js.map +1 -1
  38. package/dist/val/MapVal.js +6 -4
  39. package/dist/val/MapVal.js.map +1 -1
  40. package/dist/val/MoveFuncVal.js +25 -8
  41. package/dist/val/MoveFuncVal.js.map +1 -1
  42. package/dist/val/NumberVal.d.ts +1 -0
  43. package/dist/val/NumberVal.js +23 -2
  44. package/dist/val/NumberVal.js.map +1 -1
  45. package/dist/val/PathFuncVal.js +9 -1
  46. package/dist/val/PathFuncVal.js.map +1 -1
  47. package/dist/val/PlusOpVal.d.ts +3 -1
  48. package/dist/val/PlusOpVal.js +140 -29
  49. package/dist/val/PlusOpVal.js.map +1 -1
  50. package/dist/val/PrefVal.d.ts +2 -0
  51. package/dist/val/PrefVal.js +40 -4
  52. package/dist/val/PrefVal.js.map +1 -1
  53. package/dist/val/RefVal.js +57 -2
  54. package/dist/val/RefVal.js.map +1 -1
  55. package/dist/val/ScalarKindVal.d.ts +12 -2
  56. package/dist/val/ScalarKindVal.js +100 -11
  57. package/dist/val/ScalarKindVal.js.map +1 -1
  58. package/dist/val/ScalarVal.d.ts +1 -0
  59. package/dist/val/ScalarVal.js +16 -1
  60. package/dist/val/ScalarVal.js.map +1 -1
  61. package/dist/val/SuperFuncVal.d.ts +1 -1
  62. package/dist/val/SuperFuncVal.js +14 -1
  63. package/dist/val/SuperFuncVal.js.map +1 -1
  64. package/dist/val/UpperFuncVal.js +20 -3
  65. package/dist/val/UpperFuncVal.js.map +1 -1
  66. package/dist/val/VarVal.js +7 -1
  67. package/dist/val/VarVal.js.map +1 -1
  68. package/dist/val/numkind.d.ts +6 -0
  69. package/dist/val/numkind.js +224 -0
  70. package/dist/val/numkind.js.map +1 -0
  71. package/dist/val/valutil.d.ts +1 -0
  72. package/dist/val/valutil.js +35 -0
  73. package/dist/val/valutil.js.map +1 -1
  74. package/package.json +7 -7
  75. package/src/aontu.ts +23 -2
  76. package/src/cli.ts +36 -8
  77. package/src/exactjson.ts +254 -0
  78. package/src/hints.ts +73 -4
  79. package/src/keyorder.ts +73 -0
  80. package/src/lang.ts +229 -5
  81. package/src/lsp.ts +10 -2
  82. package/src/val/BagVal.ts +8 -5
  83. package/src/val/BigDecimalVal.ts +117 -0
  84. package/src/val/BigIntegerVal.ts +110 -0
  85. package/src/val/Decimal.ts +385 -0
  86. package/src/val/IntegerVal.ts +79 -3
  87. package/src/val/KeyFuncVal.ts +35 -3
  88. package/src/val/LowerFuncVal.ts +21 -4
  89. package/src/val/MapVal.ts +6 -4
  90. package/src/val/MoveFuncVal.ts +26 -9
  91. package/src/val/NumberVal.ts +25 -2
  92. package/src/val/PathFuncVal.ts +9 -1
  93. package/src/val/PlusOpVal.ts +168 -32
  94. package/src/val/PrefVal.ts +66 -5
  95. package/src/val/RefVal.ts +61 -2
  96. package/src/val/ScalarKindVal.ts +115 -10
  97. package/src/val/ScalarVal.ts +18 -1
  98. package/src/val/SuperFuncVal.ts +14 -1
  99. package/src/val/UpperFuncVal.ts +21 -4
  100. package/src/val/VarVal.ts +7 -1
  101. package/src/val/numkind.ts +256 -0
  102. package/src/val/valutil.ts +36 -0
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.integerDigits = integerDigits;
5
+ exports.isExactInBinary64 = isExactInBinary64;
6
+ exports.isIntegerKind = isIntegerKind;
7
+ exports.isIntegerStorable = isIntegerStorable;
8
+ exports.isLossyIntegerLiteral = isLossyIntegerLiteral;
9
+ /*
10
+ * Numeric kind classification, shared by every site that turns a raw
11
+ * JavaScript number into an IntegerVal or a NumberVal (the aontu val
12
+ * rule, rawToVal, VarVal, the `+` operator, upper()/lower()). Keeping
13
+ * the test in one place stops those sites from drifting apart, and
14
+ * stops them drifting from the Go port (see isIntegerKind in
15
+ * go/lang.go).
16
+ *
17
+ * This module deliberately imports nothing: IntegerVal itself needs the
18
+ * test for its constructor guard, so any import here risks a cycle.
19
+ */
20
+ // The int64 range, as exact float64 bounds. -2^63 and 2^63 are both
21
+ // exactly representable; 2^63-1 is NOT (it rounds up to 2^63), which is
22
+ // why the upper bound is exclusive and 0x7fffffffffffffff correctly
23
+ // falls outside.
24
+ const INT64_MIN = -9223372036854775808.0;
25
+ const INT64_LIMIT = 9223372036854775808.0;
26
+ // True when a numeric literal has *integer* kind. All three conditions
27
+ // must hold:
28
+ // (a) the source text, when there is any, contains no '.'
29
+ // (b) the value is integral
30
+ // (c) the value lies within the int64 range
31
+ //
32
+ // The range test is written against the float64 bounds rather than as
33
+ // `n === Number(BigInt(n))` or Go's `n == float64(int64(n))`: converting
34
+ // an out-of-range float64 to int64 is implementation-defined in Go, and
35
+ // that accident is exactly what the two ports used to disagree about.
36
+ //
37
+ // `src` is undefined at construction sites with no source text (raw
38
+ // values from an implicit top-level list, or a $var binding); there
39
+ // condition (a) is vacuous and (b)+(c) decide.
40
+ function isIntegerKind(n, src) {
41
+ if (null != src && src.includes('.')) {
42
+ return false;
43
+ }
44
+ // Number.isInteger is false for NaN and both infinities, so a
45
+ // non-finite value never reaches the range test.
46
+ return Number.isInteger(n) && INT64_MIN <= n && n < INT64_LIMIT;
47
+ }
48
+ // The same int64 window, exactly, for values that were computed as
49
+ // bigints and have not been through a double yet.
50
+ const INT64_MIN_EXACT = -9223372036854775808n;
51
+ const INT64_LIMIT_EXACT = 9223372036854775808n;
52
+ // D6 -- THE STORAGE CONTRACT FOR AN EXACTLY COMPUTED INTEGER.
53
+ //
54
+ // `integer + integer` is computed exactly (in bigint here, in checked
55
+ // int64 in Go) and the exact answer is only then offered to the integer
56
+ // leaf, which accepts it when it is integral (a bigint always is),
57
+ // within the int64 window, AND exactly representable in binary64.
58
+ //
59
+ // The binary64 half is the parity-critical one. Go's int64 can hold sums
60
+ // this port's double cannot -- 2^53+1 among them -- so a test written
61
+ // only against the int64 window would let Go store `9007199254740993`
62
+ // while TypeScript silently stored `...992`. The round-trip through
63
+ // Number() is that test: it is true exactly when nothing was lost. Both
64
+ // ports must refuse the same sums, which is why this is the shared
65
+ // contract and not a local convenience.
66
+ //
67
+ // Refusal, not rounding: a sum that fails here is a located error whose
68
+ // hint points at `0d` for an exact integer, per R1's own storage rule
69
+ // applied to a result rather than a literal.
70
+ function isIntegerStorable(n) {
71
+ return INT64_MIN_EXACT <= n && n < INT64_LIMIT_EXACT &&
72
+ isExactInBinary64(n);
73
+ }
74
+ // The binary64 half of the storage contract, on its own: true when an
75
+ // exact integer survives a round trip through a double unchanged.
76
+ //
77
+ // D7's literal rule needs exactly this half and NOT the int64 window --
78
+ // 10^20 is outside int64 and is still a perfectly good float-kind value,
79
+ // because it lands exactly on a binary64. THE RULE IS EXACTNESS, NOT
80
+ // MAGNITUDE: `0x10000000000000000000000000000000` is 2^124, a power of
81
+ // two, so it is exact and stays a value, while the very much smaller
82
+ // `0x7fffffffffffffff` (2^63-1) is not and is refused.
83
+ //
84
+ // It is factored out rather than reimplemented so a LITERAL and a
85
+ // COMPUTED SUM cannot disagree about what "exact" means: both ask this
86
+ // one function, in both ports.
87
+ //
88
+ // The Number.isFinite guard is what makes it total. A bigint far beyond
89
+ // binary64's range converts to Infinity, and BigInt(Infinity) THROWS --
90
+ // so a literal like 10^400 (which the lexer already turns into a
91
+ // non-finite `not_number`) would crash the test rather than fail it.
92
+ function isExactInBinary64(n) {
93
+ const d = Number(n);
94
+ return Number.isFinite(d) && BigInt(d) === n;
95
+ }
96
+ // D7 -- LOSSY INTEGER LITERALS ARE REFUSED.
97
+ //
98
+ // True when `src` is an integer-source literal whose exact value cannot
99
+ // be held in binary64, so storing it would silently substitute a
100
+ // DIFFERENT number. Aontu refuses instead: the caller turns this into a
101
+ // located parse error whose hint names the escape (`0d<digits>`).
102
+ //
103
+ // WHICH LITERALS ARE IN SCOPE. An integer-source literal is one that
104
+ // denotes an exact integer:
105
+ //
106
+ // - plain decimal digits (`9007199254740993`), with the landed `_`
107
+ // separator rule;
108
+ // - a base-prefixed run (`0x…`, `0o…`, `0b…`);
109
+ // - either of those with a NON-NEGATIVE exponent (`1e21`), which still
110
+ // denotes an integer.
111
+ //
112
+ // Everything else is out of scope and unchanged. A `.` in the source
113
+ // makes it a float literal by R1, so it is not an integer source at all;
114
+ // a NEGATIVE exponent (`2e-1`, `1e-400`) denotes a fraction, and testing
115
+ // those for binary64 exactness would refuse `2e-1` -- 0.2 is not exactly
116
+ // representable either -- which is emphatically not what D7 is about.
117
+ // The `0d` family never reaches here: it has its own matcher and its own
118
+ // exact leaves.
119
+ //
120
+ // The value is re-derived from the SOURCE TEXT rather than read off the
121
+ // lexed double `n`, because that double is the rounded value this rule
122
+ // exists to detect; comparing it with itself would always agree. `n` is
123
+ // passed only for the fast path, which is why the signature mirrors
124
+ // isIntegerKind's.
125
+ function isLossyIntegerLiteral(n, src) {
126
+ // No source text (a raw value from an implicit top-level list, a $var
127
+ // binding) means there is no literal to judge -- the same way R1's
128
+ // "no `.` in the source" condition is vacuous at those sites.
129
+ if (null == src) {
130
+ return false;
131
+ }
132
+ // FAST PATH, and the one every ordinary document takes: below 2^53
133
+ // every integer is representable, so an integer literal whose ROUNDED
134
+ // value is strictly inside that window cannot have had a different
135
+ // exact value -- a different integer would be at least 1 away, and
136
+ // rounding here is accurate to half a unit. This runs at every numeric
137
+ // literal in every parse, so it must not allocate a bigint (2^53+1 has
138
+ // rounded value 2^53 and is deliberately NOT inside the window).
139
+ if (Number.isInteger(n) && -POW53 < n && n < POW53) {
140
+ return false;
141
+ }
142
+ // A `.` makes it a float source (R1), out of scope: checked here, and
143
+ // not left to the patterns below, so the other common literal shape
144
+ // also leaves without touching a regexp.
145
+ if (-1 !== src.indexOf('.')) {
146
+ return false;
147
+ }
148
+ let exact;
149
+ const dec = DEC_INT_LITERAL_RE.exec(src);
150
+ if (null != dec) {
151
+ // A negative exponent does not denote an integer -- out of scope.
152
+ const exp = null == dec[3] ? 0 : Number(stripSep(dec[3]));
153
+ if (!(0 <= exp) || !Number.isFinite(exp)) {
154
+ return false;
155
+ }
156
+ const coeff = BigInt(stripSep(dec[2]));
157
+ // Zero at any exponent is zero, and zero is exact -- test it before
158
+ // the exponent bound, which would otherwise refuse `0e500`.
159
+ if (0n === coeff) {
160
+ return false;
161
+ }
162
+ // Beyond this the value cannot be finite in binary64, so it cannot
163
+ // be exact either -- and 10n ** BigInt(exp) must not be built.
164
+ if (MAX_EXPONENT < exp) {
165
+ return true;
166
+ }
167
+ exact = coeff * 10n ** BigInt(exp);
168
+ }
169
+ else {
170
+ const base = BASE_INT_LITERAL_RE.exec(src);
171
+ if (null == base) {
172
+ return false;
173
+ }
174
+ exact = BigInt('0' + base[2].toLowerCase() + stripSep(base[3]));
175
+ }
176
+ // The sign is irrelevant to exactness (binary64 is sign-symmetric) and
177
+ // is anyway lexed as the unary prefix, but accept it so the test does
178
+ // not depend on that.
179
+ return !isExactInBinary64(exact);
180
+ }
181
+ // `[+-]? digits [ (e|E) [+-] digits ]`, with the landed single-`_`-
182
+ // between-digits separator rule. No `.`: that is a float source (R1).
183
+ const DEC_INT_LITERAL_RE = /^([-+]?)([0-9](?:_?[0-9])*)(?:[eE]([-+]?[0-9](?:_?[0-9])*))?$/;
184
+ // `[+-]? 0(x|o|b) digits`. Hex digits are the widest set, and a bad
185
+ // digit for the base simply makes BigInt throw -- which cannot happen,
186
+ // since the lexer only produced a token for a well-formed run.
187
+ const BASE_INT_LITERAL_RE = /^([-+]?)0([xXoObB])([0-9a-fA-F](?:_?[0-9a-fA-F])*)$/;
188
+ // Above this the value cannot be finite in binary64, so it cannot be
189
+ // exact either, and 10n ** BigInt(exp) must not be built.
190
+ const MAX_EXPONENT = 400;
191
+ // 2^53, the point at which the integers stop being contiguous in
192
+ // binary64 and so the first magnitude at which a literal can be lossy.
193
+ const POW53 = 9007199254740992;
194
+ function stripSep(s) {
195
+ return -1 === s.indexOf('_') ? s : s.replace(/_/g, '');
196
+ }
197
+ // THE SINGLE RENDERING OF AN INTEGER-KIND PEG. Every site that turns one
198
+ // into text must call this -- so that "how many rendering sites are there?"
199
+ // is answerable by grepping this function's callers.
200
+ //
201
+ // That question has been answered wrongly twice. Issue #21 was the
202
+ // divergence where TypeScript printed 2^60 as 1152921504606847000, a
203
+ // DIFFERENT integer that merely rounds to the same double, because a JS
204
+ // number's toString emits the shortest decimal that round-trips (at most 17
205
+ // significant digits) while Go printed the exact value from its int64. It
206
+ // was closed having fixed the two sites known at the time, canon and
207
+ // generate. A differential audit then found a THIRD (`+`'s string
208
+ // coercion) and, while fixing that, a FOURTH (a variable used as a path
209
+ // segment). Both were the same one-character mistake -- `String(peg)` or
210
+ // `'' + peg` on a JS number -- at a site nobody had enumerated.
211
+ //
212
+ // BigInt is exact here by construction: an integer-kind peg is integral and
213
+ // inside the int64 window (isIntegerKind, enforced by IntegerVal's
214
+ // constructor), so the conversion cannot throw or lose anything. Below 2^53
215
+ // nothing moves -- BigInt(n).toString() === String(n) for every safe
216
+ // integer, and both give "0" for negative zero.
217
+ //
218
+ // FLOAT KIND MUST NOT COME HERE. A float's shortest form IS the right
219
+ // answer and is what Go prints too (1e21 -> "1e+21"), so a caller must
220
+ // select on kind, not merely on the peg being a JS number.
221
+ function integerDigits(peg) {
222
+ return BigInt(peg).toString();
223
+ }
224
+ //# sourceMappingURL=numkind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numkind.js","sourceRoot":"","sources":["../../src/val/numkind.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  import { ScalarVal } from './ScalarVal';
2
2
  export declare function makeScalar(scalar: any): ScalarVal;
3
+ export declare function makeScalarLike(scalar: any, like: any): ScalarVal;
@@ -2,11 +2,17 @@
2
2
  /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.makeScalar = makeScalar;
5
+ exports.makeScalarLike = makeScalarLike;
5
6
  const err_1 = require("../err");
6
7
  const NumberVal_1 = require("./NumberVal");
8
+ const IntegerVal_1 = require("./IntegerVal");
7
9
  const StringVal_1 = require("./StringVal");
8
10
  const BooleanVal_1 = require("./BooleanVal");
9
11
  const NullVal_1 = require("./NullVal");
12
+ const BigIntegerVal_1 = require("./BigIntegerVal");
13
+ const BigDecimalVal_1 = require("./BigDecimalVal");
14
+ const Decimal_1 = require("./Decimal");
15
+ const numkind_1 = require("./numkind");
10
16
  // TODO: move to FuncBaseVal
11
17
  function makeScalar(scalar) {
12
18
  const st = typeof scalar;
@@ -27,4 +33,33 @@ function makeScalar(scalar) {
27
33
  throw new err_1.AontuError('Not a scalar: ' + scalar);
28
34
  }
29
35
  }
36
+ // Like makeScalar, but for a numeric result that must not narrow the
37
+ // kind of the value it was derived from (kind contagion: upper(2) is an
38
+ // integer 2, upper(1.1) is a number 2). `like` is the source Val whose
39
+ // kind is being carried over; anything that is not integer kind — and
40
+ // any result that has left the int64 range — yields a NumberVal.
41
+ //
42
+ // The two EXACT leaves need no `like`: unlike integer and float, which
43
+ // share the JavaScript `number` type and so can only be told apart by
44
+ // the value they came from, a bigint is a biginteger and a Decimal is a
45
+ // bigdecimal. Carrying the kind is automatic because the exact result
46
+ // types ARE the kinds — an exact ceiling of a bigdecimal is a Decimal,
47
+ // so upper(0d1.1) is bigdecimal 0d2.0 and upper(0d5) is biginteger 0d5.
48
+ //
49
+ // makeScalar keeps its own contract (every number becomes a NumberVal)
50
+ // for callers that have no kind to preserve.
51
+ function makeScalarLike(scalar, like) {
52
+ if ('bigint' === typeof scalar) {
53
+ return new BigIntegerVal_1.BigIntegerVal({ peg: scalar });
54
+ }
55
+ if (scalar instanceof Decimal_1.Decimal) {
56
+ return new BigDecimalVal_1.BigDecimalVal({ peg: scalar });
57
+ }
58
+ if ('number' === typeof scalar &&
59
+ true === like?.isInteger &&
60
+ (0, numkind_1.isIntegerKind)(scalar)) {
61
+ return new IntegerVal_1.IntegerVal({ peg: scalar });
62
+ }
63
+ return makeScalar(scalar);
64
+ }
30
65
  //# sourceMappingURL=valutil.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"valutil.js","sourceRoot":"","sources":["../../src/val/valutil.ts"],"names":[],"mappings":";AAAA,yDAAyD;;;AAEzD,gCAAmC;AAEnC,2CAAuC;AACvC,2CAAuC;AACvC,6CAAyC;AACzC,uCAAmC;AAGnC,4BAA4B;AAC5B,oBAA2B,MAAW;IACpC,MAAM,EAAE,GAAG,OAAO,MAAM,CAAA;IACxB,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;IAE5B,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;SACI,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACzB,OAAO,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;SACI,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;QAC1B,OAAO,IAAI,uBAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;SACI,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,IAAI,iBAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;SACI,CAAC;QACJ,MAAM,IAAI,gBAAU,CAAC,gBAAgB,GAAG,MAAM,CAAC,CAAA;IACjD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"valutil.js","sourceRoot":"","sources":["../../src/val/valutil.ts"],"names":[],"mappings":";AAAA,yDAAyD;;;;AAEzD,gCAAmC;AAEnC,2CAAuC;AACvC,6CAAyC;AACzC,2CAAuC;AACvC,6CAAyC;AACzC,uCAAmC;AACnC,mDAA+C;AAC/C,mDAA+C;AAC/C,uCAAmC;AACnC,uCAAyC;AAGzC,4BAA4B;AAC5B,oBAA2B,MAAW;IACpC,MAAM,EAAE,GAAG,OAAO,MAAM,CAAA;IACxB,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;IAE5B,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;SACI,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACzB,OAAO,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;SACI,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;QAC1B,OAAO,IAAI,uBAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;SACI,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,IAAI,iBAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;SACI,CAAC;QACJ,MAAM,IAAI,gBAAU,CAAC,gBAAgB,GAAG,MAAM,CAAC,CAAA;IACjD,CAAC;AACH,CAAC;AAGD,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,iEAAiE;AACjE,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,sEAAsE;AACtE,uEAAuE;AACvE,wEAAwE;AACxE,EAAE;AACF,uEAAuE;AACvE,6CAA6C;AAC7C,wBAA+B,MAAW,EAAE,IAAS;IACnD,IAAI,QAAQ,KAAK,OAAO,MAAM,EAAE,CAAC;QAC/B,OAAO,IAAI,6BAAa,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IAC3C,CAAC;IACD,IAAI,MAAM,YAAY,iBAAO,EAAE,CAAC;QAC9B,OAAO,IAAI,6BAAa,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IAC3C,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,MAAM;QAC5B,IAAI,KAAK,IAAI,EAAE,SAAS;QACxB,IAAA,uBAAa,EAAC,MAAM,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,uBAAU,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;AAC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aontu",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "main": "dist/aontu.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/aontu.d.ts",
@@ -47,13 +47,13 @@
47
47
  "LICENSE"
48
48
  ],
49
49
  "dependencies": {
50
- "@tabnas/debug": "0.3.0",
51
- "@tabnas/directive": "0.5.0",
52
- "@tabnas/expr": "0.5.0",
50
+ "@tabnas/debug": "0.3.1",
51
+ "@tabnas/directive": "0.5.1",
52
+ "@tabnas/expr": "0.5.1",
53
53
  "@tabnas/jsonic": "0.6.0",
54
- "@tabnas/multisource": "0.5.0",
55
- "@tabnas/parser": "0.8.0",
56
- "@tabnas/path": "0.3.0"
54
+ "@tabnas/multisource": "0.5.1",
55
+ "@tabnas/parser": "0.8.4",
56
+ "@tabnas/path": "0.3.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "26.1.1",
package/src/aontu.ts CHANGED
@@ -9,6 +9,8 @@ import { Lang } from './lang'
9
9
  import { Unify } from './unify'
10
10
  import { AontuContext, AontuContextConfig } from './ctx'
11
11
  import { MapVal } from './val/MapVal'
12
+ import { Decimal } from './val/Decimal'
13
+ import { exactJSON } from './exactjson'
12
14
  import { formatExplain } from './utility'
13
15
  import { makeNilErr, descErr, AontuError } from './err'
14
16
 
@@ -20,7 +22,7 @@ import { makeNilErr, descErr, AontuError } from './err'
20
22
  // Kept in step with package.json by the `version` npm lifecycle script,
21
23
  // which runs on `npm version` / `npm run repo-bump`. version.test.ts
22
24
  // fails if the two ever drift.
23
- const VERSION = '0.50.0'
25
+ const VERSION = '0.51.0'
24
26
 
25
27
 
26
28
  class Aontu {
@@ -127,6 +129,18 @@ class Aontu {
127
129
 
128
130
 
129
131
  // Generate output structure from source, which must parse and fully unify.
132
+ //
133
+ // The result is made of NATIVE values, and D9 puts two beyond what
134
+ // `JSON.stringify` can write: a `biginteger` (a `0d` literal with no
135
+ // fraction or exponent) generates as a native `bigint`, and a
136
+ // `bigdecimal` generates as a `Decimal`. Both are exact at any
137
+ // magnitude, which is the whole point of the leaves -- and both are
138
+ // confined to documents that opt in, so a `0d`-free document generates
139
+ // exactly what it always did.
140
+ //
141
+ // Serialise the result with `exactJSON` (exported alongside this
142
+ // class), NOT with `JSON.stringify`: the latter throws on a bigint and
143
+ // has no way to write exact digits as a JSON number.
130
144
  generate(src: string, opts?: any, ac?: AontuContext): any {
131
145
  try {
132
146
  let out = undefined
@@ -239,7 +253,14 @@ export {
239
253
  Lang,
240
254
  runparse,
241
255
  util,
242
- formatExplain
256
+ formatExplain,
257
+
258
+ // D9 -- the generate contract. `exactJSON` is the supported way to
259
+ // turn `generate()` output into JSON when a document uses the exact
260
+ // leaves, and `Decimal` is the type a `bigdecimal` generates as (a
261
+ // `biginteger` generates as the language's own `bigint`).
262
+ exactJSON,
263
+ Decimal,
243
264
  }
244
265
 
245
266
 
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ import * as Fs from 'node:fs'
13
13
  import * as Path from 'node:path'
14
14
  import * as Readline from 'node:readline'
15
15
 
16
- import { Aontu, AontuError } from './aontu'
16
+ import { Aontu, AontuError, exactJSON } from './aontu'
17
17
 
18
18
 
19
19
  type Mode = 'json' | 'canon'
@@ -57,9 +57,15 @@ function evalSource(
57
57
  mode: Mode,
58
58
  ): { ok: boolean; text: string } {
59
59
  try {
60
+ // exactJSON, not JSON.stringify: a document using the `0d` exact
61
+ // leaves generates bigints and Decimals, which JSON.stringify cannot
62
+ // write (D9). The CLI prints INDENTED JSON and the shared suite's
63
+ // `gens` mode prints COMPACT JSON, but both go through this one
64
+ // emitter -- an indent argument rather than a second implementation,
65
+ // so the two cannot drift from each other or from the Go port.
60
66
  const text = 'canon' === mode
61
67
  ? aontu.unify(src).canon
62
- : JSON.stringify(aontu.generate(src), null, 2)
68
+ : exactJSON(aontu.generate(src), 2)
63
69
  return { ok: true, text }
64
70
  }
65
71
  catch (err: any) {
@@ -142,11 +148,33 @@ function runRepl(initialMode: Mode): void {
142
148
 
143
149
  rl.on('close', () => {
144
150
  process.stdout.write('\n')
145
- process.exit(0)
151
+ // Same reason as finish(): the REPL requires a TTY stdin, but stdout
152
+ // can still be a pipe (`aontu | cat`), so exiting outright could
153
+ // discard queued output here too.
154
+ process.exitCode = 0
146
155
  })
147
156
  }
148
157
 
149
158
 
159
+ // Exit without truncating output.
160
+ //
161
+ // process.exit() terminates immediately, discarding anything still
162
+ // queued on stdout. A write to a PIPE is asynchronous once it exceeds
163
+ // the pipe buffer, so `write(big); exit(0)` silently truncated output at
164
+ // 65536 bytes — while a write to a TTY or a file, being synchronous,
165
+ // looked fine. Setting exitCode instead lets the process end naturally,
166
+ // after the queue drains.
167
+ //
168
+ // This predates the exact leaves but they make it trivially reachable
169
+ // (one long biginteger canon exceeds the buffer), and it lands squarely
170
+ // on the parity-probe discipline in AGENTS.md, which derives expected
171
+ // spec values by piping BOTH CLIs and comparing. A truncated pipe there
172
+ // reads as a port divergence.
173
+ function finish(code: number): void {
174
+ process.exitCode = code
175
+ }
176
+
177
+
150
178
  function main(argv: string[]): void {
151
179
  let mode: Mode = 'json'
152
180
  let file: string | undefined
@@ -157,15 +185,15 @@ function main(argv: string[]): void {
157
185
  }
158
186
  else if ('-h' === arg || '--help' === arg) {
159
187
  process.stdout.write(HELP)
160
- process.exit(0)
188
+ return finish(0)
161
189
  }
162
190
  else if ('-v' === arg || '--version' === arg) {
163
191
  process.stdout.write(version() + '\n')
164
- process.exit(0)
192
+ return finish(0)
165
193
  }
166
194
  else if (arg.startsWith('-')) {
167
195
  process.stderr.write(`aontu: unknown option ${arg} (try --help)\n`)
168
- process.exit(2)
196
+ return finish(2)
169
197
  }
170
198
  else {
171
199
  file = arg
@@ -173,13 +201,13 @@ function main(argv: string[]): void {
173
201
  }
174
202
 
175
203
  if (null != file) {
176
- process.exit(runFile(file, mode))
204
+ finish(runFile(file, mode))
177
205
  }
178
206
  else if (process.stdin.isTTY) {
179
207
  runRepl(mode)
180
208
  }
181
209
  else {
182
- runStdin(mode).then((code) => process.exit(code))
210
+ runStdin(mode).then((code) => finish(code))
183
211
  }
184
212
  }
185
213