@mason-data/cli 0.7.0 → 0.9.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.
- package/README.md +101 -73
- package/dist/src/api/error.js +3 -0
- package/dist/src/api/postgres/catalog-worker.js +49 -2
- package/dist/src/api/postgres/catalog.js +368 -25
- package/dist/src/api/postgres/encode.js +3 -1
- package/dist/src/api/postgres/explain.js +3 -2
- package/dist/src/api/postgres/listener.js +11 -3
- package/dist/src/api/postgres/protocol.js +39 -23
- package/dist/src/api/postgres/proxy.js +283 -0
- package/dist/src/api/postgres/schema.js +6 -0
- package/dist/src/api/postgres/server.js +1617 -245
- package/dist/src/api/postgres/translate.js +127 -10
- package/dist/src/api/request-log.js +18 -5
- package/dist/src/cdc/poll.js +5 -5
- package/dist/src/cli/background.js +47 -25
- package/dist/src/cli/bridge-stdin.js +101 -0
- package/dist/src/cli/cancel.js +75 -0
- package/dist/src/cli/command.js +170 -11
- package/dist/src/cli/commands.js +804 -209
- package/dist/src/cli/control.js +59 -0
- package/dist/src/cli/credentials.js +182 -0
- package/dist/src/cli/demo.js +20 -11
- package/dist/src/cli/deploy.js +574 -0
- package/dist/src/cli/dev.js +491 -130
- package/dist/src/cli/doctor.js +131 -38
- package/dist/src/cli/generated-files.js +143 -0
- package/dist/src/cli/guides.js +175 -36
- package/dist/src/cli/hosted.js +850 -0
- package/dist/src/cli/init.js +162 -65
- package/dist/src/cli/login.js +289 -0
- package/dist/src/cli/machine.js +13 -12
- package/dist/src/cli/main.js +11 -5
- package/dist/src/cli/mason.cjs +17 -0
- package/dist/src/cli/mcp.js +31 -22
- package/dist/src/cli/metrics.js +79 -0
- package/dist/src/cli/output-gate.js +176 -0
- package/dist/src/cli/page-share.js +96 -0
- package/dist/src/cli/pg-tls.js +14 -4
- package/dist/src/cli/pg-url.js +4 -0
- package/dist/src/cli/pin.js +19 -14
- package/dist/src/cli/pool-size.cjs +85 -0
- package/dist/src/cli/ports.js +2 -0
- package/dist/src/cli/preflight.js +138 -46
- package/dist/src/cli/project.js +142 -4
- package/dist/src/cli/readiness.js +76 -6
- package/dist/src/cli/registry.js +65 -38
- package/dist/src/cli/replica-hosted.js +665 -0
- package/dist/src/cli/replica.js +76 -0
- package/dist/src/cli/replication-checks.js +7 -5
- package/dist/src/cli/replication-mode.js +105 -26
- package/dist/src/cli/request-filter.js +134 -0
- package/dist/src/cli/roles.js +585 -0
- package/dist/src/cli/run-server.js +637 -48
- package/dist/src/cli/runs.js +1 -1
- package/dist/src/cli/security.js +67 -18
- package/dist/src/cli/serve.js +204 -46
- package/dist/src/cli/share-run.js +329 -0
- package/dist/src/cli/share.js +286 -0
- package/dist/src/cli/source-edit.js +75 -17
- package/dist/src/cli/source-role.js +344 -0
- package/dist/src/cli/sources.js +306 -47
- package/dist/src/cli/style.js +2 -1
- package/dist/src/cli/supervise.js +27 -24
- package/dist/src/cli/sync.js +55 -25
- package/dist/src/cli/token-hosted.js +337 -0
- package/dist/src/cli/tunnel.js +200 -0
- package/dist/src/cli/ui-ssh.js +460 -0
- package/dist/src/cli/ui.js +124 -17
- package/dist/src/cli/usage.js +128 -0
- package/dist/src/cli/user-hosted.js +299 -0
- package/dist/src/cli/user.js +573 -65
- package/dist/src/cli/version.js +9 -7
- package/dist/src/demo/seed.js +29 -0
- package/dist/src/duck/analyze.js +36 -20
- package/dist/src/duck/cancel.js +10 -1
- package/dist/src/duck/collation.js +266 -0
- package/dist/src/duck/constant-key.js +94 -0
- package/dist/src/duck/distinct-key.js +283 -26
- package/dist/src/duck/events.js +165 -0
- package/dist/src/duck/executor-worker.js +20 -11
- package/dist/src/duck/executor.js +47 -33
- package/dist/src/duck/explain.js +60 -46
- package/dist/src/duck/extensions.js +160 -0
- package/dist/src/duck/functions.js +461 -63
- package/dist/src/duck/gaps.js +48 -21
- package/dist/src/duck/generated-plan.js +1070 -0
- package/dist/src/duck/generated.js +906 -0
- package/dist/src/duck/grants.js +176 -0
- package/dist/src/duck/held-types.js +64 -0
- package/dist/src/duck/history.js +127 -0
- package/dist/src/duck/jsonb-order.js +9 -12
- package/dist/src/duck/kept-route.js +6 -5
- package/dist/src/duck/kept.js +15 -13
- package/dist/src/duck/late.js +1469 -40
- package/dist/src/duck/libc-bound.js +341 -0
- package/dist/src/duck/libc-order.js +892 -126
- package/dist/src/duck/pg-eval-worker.js +7 -1
- package/dist/src/duck/pg-eval.js +6 -4
- package/dist/src/duck/pgtree.js +774 -23
- package/dist/src/duck/plan.js +369 -47
- package/dist/src/duck/publication.js +20 -6
- package/dist/src/duck/requests.js +269 -93
- package/dist/src/duck/resolved.js +117 -2
- package/dist/src/duck/rewrite.js +4584 -580
- package/dist/src/duck/row-filter.js +655 -0
- package/dist/src/duck/statement.js +19 -0
- package/dist/src/duck/store.js +439 -177
- package/dist/src/duck/stream.js +19 -10
- package/dist/src/duck/sync.js +1411 -368
- package/dist/src/duck/token.js +4 -1
- package/dist/src/duck/types.js +69 -12
- package/dist/src/duck/usage.js +326 -0
- package/dist/src/duck/users.js +69 -15
- package/dist/src/duck/verified.js +161 -1
- package/dist/src/duck/views.js +5 -5
- package/dist/src/lib/database-name.js +62 -0
- package/dist/src/lib/database-url.js +55 -10
- package/dist/src/lib/dotenv.js +17 -4
- package/dist/src/lib/instant.js +50 -0
- package/dist/src/lib/log-buffer.js +77 -0
- package/dist/src/lib/log-read.js +196 -0
- package/dist/src/lib/pg-numeric.js +390 -49
- package/dist/src/lib/project-version.js +23 -23
- package/dist/src/lib/secret-mask.js +142 -0
- package/dist/src/lib/table-sync.js +282 -0
- package/dist/src/web/app.css +706 -66
- package/dist/src/web/app.js +331 -409
- package/dist/src/web/charts.js +229 -0
- package/dist/src/web/index.html +82 -18
- package/dist/src/web/pages/queries.js +192 -0
- package/dist/src/web/pages/status.js +611 -0
- package/dist/src/web/pages/syncs.js +155 -0
- package/dist/src/web/pages/tables.js +439 -0
- package/dist/src/web/pages/usage.js +397 -0
- package/dist/src/web/pages/users.js +219 -0
- package/dist/src/web/shared.js +584 -0
- package/dist/src/web/vendor/README.md +25 -0
- package/dist/src/web/vendor/geist-1.7.2/Geist-Variable.woff2 +0 -0
- package/dist/src/web/vendor/geist-1.7.2/GeistMono-Variable.woff2 +0 -0
- package/dist/src/web/vendor/geist-1.7.2/LICENSE.txt +92 -0
- package/dist/src/web/vendor/lucide-1.56.0/LICENSE.txt +43 -0
- package/dist/src/web/vendor/lucide-1.56.0/chart-column.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/layout-dashboard.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/logs.mjs +34 -0
- package/dist/src/web/vendor/lucide-1.56.0/menu.mjs +28 -0
- package/dist/src/web/vendor/lucide-1.56.0/square-terminal.mjs +28 -0
- package/dist/src/web/vendor/lucide-1.56.0/table.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/users.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/x.mjs +27 -0
- package/dist/src/web/vendor/uplot-1.6.32/LICENSE.txt +21 -0
- package/dist/src/web/vendor/uplot-1.6.32/uPlot.esm.js +6140 -0
- package/dist/src/web/vendor/uplot-1.6.32/uPlot.min.css +1 -0
- package/dist/src/web/vendor/zilla-slab-5.3.0/LICENSE.txt +93 -0
- package/dist/src/web/vendor/zilla-slab-5.3.0/zilla-slab-latin-700-normal.woff2 +0 -0
- package/package.json +12 -4
|
@@ -25,17 +25,35 @@
|
|
|
25
25
|
* Pure and dependency-free: tests/pg-numeric.test.ts holds every operation to PGlite's own numeric over generated values.
|
|
26
26
|
*/
|
|
27
27
|
import { Buffer } from "node:buffer";
|
|
28
|
+
import { gapMark } from "../duck/gaps.js";
|
|
28
29
|
/** A value this module cannot compute as Postgres does, or an operation Postgres refuses: with Postgres's SQLSTATE. */
|
|
29
30
|
export class NumericError extends Error {
|
|
30
31
|
sqlstate;
|
|
31
|
-
|
|
32
|
+
/** the registry entry that refused it (src/duck/gaps.ts): the request log's `gap`; null for Postgres's own error */
|
|
33
|
+
gap;
|
|
34
|
+
/** Postgres's DETAIL, apart from its message, as Postgres sends them (`numeric field overflow` and its field's limit) */
|
|
35
|
+
detail;
|
|
36
|
+
constructor(message, sqlstate, gap = null, detail = null) { super(message); this.sqlstate = sqlstate; this.gap = gap; this.detail = detail; }
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A NUMERIC FIELD OVERFLOW IN POSTGRES'S WORDS (22003), the one place they are written: its message, and its DETAIL as
|
|
40
|
+
* `apply_typmod` gives it, for every path that raises one (this module's typmod, the engine's DECIMAL cast in
|
|
41
|
+
* src/duck/store.ts `postgresWording`). An infinite value has its own DETAIL (Postgres 14+).
|
|
42
|
+
*/
|
|
43
|
+
export function fieldOverflow(precision, scale, infinite = false) {
|
|
44
|
+
const digits = precision - scale;
|
|
45
|
+
return { message: "numeric field overflow", detail: infinite ? `A field with precision ${precision}, scale ${scale} cannot hold an infinite value.`
|
|
46
|
+
: `A field with precision ${precision}, scale ${scale} must round to an absolute value less than ${digits === 0 ? "1" : `10^${digits}`}.` };
|
|
32
47
|
}
|
|
33
48
|
/** Postgres's limits on a numeric's scale (numeric.h): a quotient's scale is held between these */
|
|
34
49
|
const NUMERIC_MIN_SIG_DIGITS = 16;
|
|
35
50
|
const NUMERIC_MAX_DISPLAY_SCALE = 1000;
|
|
36
51
|
/** the decimal digits in one base-10000 group (numeric.c's DEC_DIGITS, NBASE = 10^DEC_DIGITS) */
|
|
37
52
|
const DEC_DIGITS = 4;
|
|
38
|
-
|
|
53
|
+
/** 10^n for the scales a value meets most, computed once (issue 665: a GROUP BY's key evaluates a program for every
|
|
54
|
+
* row, and each division asks for several) */
|
|
55
|
+
const POW10 = Array.from({ length: 64 }, (_, i) => 10n ** BigInt(i));
|
|
56
|
+
const pow10 = (n) => POW10[n] ?? 10n ** BigInt(n);
|
|
39
57
|
const abs = (x) => x < 0n ? -x : x;
|
|
40
58
|
/** `x / d` rounded half away from zero (d > 0): how numeric.c's `round_var` and `div_var(…, round = true)` round */
|
|
41
59
|
function divRound(x, d) {
|
|
@@ -47,12 +65,20 @@ function divRound(x, d) {
|
|
|
47
65
|
/** digits, an underscore allowed between two of them (Postgres 16: `1_000`) */
|
|
48
66
|
const DIGITS = String.raw `\d+(?:_\d+)*`;
|
|
49
67
|
const NUMERIC_TEXT = new RegExp(String.raw `^\s*([+-])?(${DIGITS})?(?:\.(${DIGITS})?)?(?:[eE]([+-]?${DIGITS}))?\s*$`);
|
|
68
|
+
/** a sign, digits and decimals, nothing else: what `NUMERIC_TEXT` reads as the same value and scale */
|
|
69
|
+
const PLAIN = /^([+-])?(\d+)(?:\.(\d+))?$/;
|
|
50
70
|
/** Postgres 16's non-decimal integers: `0x1F`, `0o17`, `0b101`, an underscore before any digit (`0x_1F`) */
|
|
51
71
|
const NON_DECIMAL = /^\s*([+-])?0(?:[xX]((?:_?[0-9a-fA-F])+)|[oO]((?:_?[0-7])+)|[bB]((?:_?[01])+))\s*$/;
|
|
52
72
|
/** A numeric from its text, as `numeric_in` reads it (held to Postgres by tests/pg-numeric.test.ts): the scale is the
|
|
53
73
|
* decimals written, less the exponent (never below 0): `1.50` has scale 2, `1.5e3` is 1500 with scale 0, `1e-3` is
|
|
54
74
|
* 0.001 with scale 3; `1_000` is 1000 and `0x1F` is 31 (scale 0). NaN and Infinity are no PgNumeric: refused here. */
|
|
55
75
|
export function parseNumeric(text) {
|
|
76
|
+
// (the plain text DuckDB writes of a DECIMAL or an integer, `-12.50`: read without the general grammar below)
|
|
77
|
+
const plain = PLAIN.exec(text);
|
|
78
|
+
if (plain !== null) {
|
|
79
|
+
const unscaled = BigInt(plain[3] === undefined ? plain[2] : plain[2] + plain[3]);
|
|
80
|
+
return { unscaled: plain[1] === "-" ? -unscaled : unscaled, scale: plain[3]?.length ?? 0 };
|
|
81
|
+
}
|
|
56
82
|
const x = NON_DECIMAL.exec(text);
|
|
57
83
|
if (x !== null) {
|
|
58
84
|
const [prefix, digits] = x[2] !== undefined ? ["0x", x[2]] : x[3] !== undefined ? ["0o", x[3]] : ["0b", x[4]];
|
|
@@ -89,6 +115,121 @@ export function numericText(n) {
|
|
|
89
115
|
const text = n.scale === 0 ? whole : `${whole}.${digits.slice(digits.length - n.scale)}`;
|
|
90
116
|
return n.unscaled < 0n ? `-${text}` : text;
|
|
91
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* WHETHER A NUMERIC'S TEXT IS WIDER THAN DuckDB's DECIMAL(width, scale): THE ONE RULE of what a DECIMAL holds exactly,
|
|
120
|
+
* for the copy (src/duck/sync.ts: a value the stream brings past its synced column's scale re-copies the table) and for
|
|
121
|
+
* a parameter compared with a value the engine holds exactly (`fitsEngineType`). NaN and ±Infinity are wider than every
|
|
122
|
+
* DECIMAL; `significant`: only the decimals a value USES must fit (a declared scale's text has its trailing zeros,
|
|
123
|
+
* 19.990000…), else every decimal written. The 0 of 0.5 counts as an integer digit (conservative: a value said wider
|
|
124
|
+
* is only ever compared by its exact key, never rounded).
|
|
125
|
+
*/
|
|
126
|
+
export function numericWiderThan(text, scale, significant, width = 38) {
|
|
127
|
+
if (text === "NaN" || text === "Infinity" || text === "-Infinity")
|
|
128
|
+
return true;
|
|
129
|
+
const m = /^-?(\d*)(?:\.(\d+))?$/.exec(text);
|
|
130
|
+
if (m === null)
|
|
131
|
+
return true;
|
|
132
|
+
const decimals = significant ? (m[2] ?? "").replace(/0+$/, "") : m[2] ?? "";
|
|
133
|
+
return decimals.length > scale || m[1].length > width - scale;
|
|
134
|
+
}
|
|
135
|
+
/** each integer type DuckDB names (`typeof`), by its range */
|
|
136
|
+
const ENGINE_INTEGERS = {
|
|
137
|
+
TINYINT: [-(2n ** 7n), 2n ** 7n - 1n], SMALLINT: [-(2n ** 15n), 2n ** 15n - 1n], INTEGER: [-(2n ** 31n), 2n ** 31n - 1n],
|
|
138
|
+
BIGINT: [-(2n ** 63n), 2n ** 63n - 1n], HUGEINT: [-(2n ** 127n), 2n ** 127n - 1n],
|
|
139
|
+
UTINYINT: [0n, 2n ** 8n - 1n], USMALLINT: [0n, 2n ** 16n - 1n], UINTEGER: [0n, 2n ** 32n - 1n], UBIGINT: [0n, 2n ** 64n - 1n], UHUGEINT: [0n, 2n ** 128n - 1n],
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* WHETHER THE ENGINE'S TYPE HOLDS A NUMERIC EXACTLY (issue 214): `type` is DuckDB's own name of it (`typeof`, folded
|
|
143
|
+
* by DuckDB to a constant), `text` Postgres's numeric_out of a bound parameter (src/duck/analyze.ts readParameters). A
|
|
144
|
+
* DECIMAL(w, s) holds what is no wider (`numericWiderThan`, its trailing zeros dropped); an integer type a whole number
|
|
145
|
+
* within its range; anything else (a DOUBLE, a text) nothing. A value held exactly is cast into the type by DuckDB
|
|
146
|
+
* without rounding, so a comparison in it is Postgres's; any other is compared by its exact key (src/duck/rewrite.ts
|
|
147
|
+
* `numericParamComparison`).
|
|
148
|
+
*/
|
|
149
|
+
export function fitsEngineType(type, text) {
|
|
150
|
+
const decimal = /^DECIMAL\((\d+),(\d+)\)$/.exec(type);
|
|
151
|
+
if (decimal !== null)
|
|
152
|
+
return !numericWiderThan(text, Number(decimal[2]), true, Number(decimal[1]));
|
|
153
|
+
const range = ENGINE_INTEGERS[type];
|
|
154
|
+
if (range === undefined || numericWiderThan(text, 0, true, 40))
|
|
155
|
+
return false;
|
|
156
|
+
const whole = BigInt(text.replace(/\.0*$/, ""));
|
|
157
|
+
return whole >= range[0] && whole <= range[1];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A numeric's ORDER KEY (issue 252): bytes whose order is numeric_cmp's, for numeric_out's text (or `NaN`, `Infinity`,
|
|
161
|
+
* `-Infinity`): -Infinity < the negatives < 0 < the positives < Infinity < NaN, as Postgres sorts them; equal values
|
|
162
|
+
* (`2.5`, `2.50`) are one key. A finite value is its sign, its decimal exponent and its significant digits; a negative
|
|
163
|
+
* one's exponent and digits are complemented, ended by a byte above every digit, so a longer one sorts first.
|
|
164
|
+
*/
|
|
165
|
+
export function numericOrderKey(text) {
|
|
166
|
+
const t = text.trim();
|
|
167
|
+
if (/^nan$/i.test(t))
|
|
168
|
+
return "5";
|
|
169
|
+
if (/^\+?inf(inity)?$/i.test(t))
|
|
170
|
+
return "4";
|
|
171
|
+
if (/^-inf(inity)?$/i.test(t))
|
|
172
|
+
return "0";
|
|
173
|
+
const n = parseNumeric(t);
|
|
174
|
+
if (n.unscaled === 0n)
|
|
175
|
+
return "2";
|
|
176
|
+
const digits = abs(n.unscaled).toString().replace(/0+$/, "");
|
|
177
|
+
// the value is 0.<digits> × 10^exponent
|
|
178
|
+
const exponent = abs(n.unscaled).toString().length - n.scale;
|
|
179
|
+
// (numeric's exponent lies within ±200000: a fixed width of six digits after an offset keeps it in order)
|
|
180
|
+
const e = String(exponent + 500000).padStart(7, "0");
|
|
181
|
+
if (n.unscaled > 0n)
|
|
182
|
+
return `3${e}${digits}`;
|
|
183
|
+
const complement = (s) => s.replace(/\d/g, (d) => String(9 - Number(d)));
|
|
184
|
+
return `1${complement(e)}${complement(digits)}~`;
|
|
185
|
+
}
|
|
186
|
+
/** A numeric cast to an integer (`numeric_int4`, `numeric_int8`, `numeric_int2`): rounded half away from zero, or
|
|
187
|
+
* 22003 past the type's range, in Postgres's words; NaN and Infinity raise 0A000 as Postgres does */
|
|
188
|
+
export function numericToInteger(text, bits) {
|
|
189
|
+
const t = text.trim();
|
|
190
|
+
const name = bits === 16 ? "smallint" : bits === 32 ? "integer" : "bigint";
|
|
191
|
+
// (Postgres's words, which name the type and never the value)
|
|
192
|
+
if (/^nan$/i.test(t)) {
|
|
193
|
+
if (bits === 16)
|
|
194
|
+
throw new NumericError("cannot convert NaN to smallint", "0A000");
|
|
195
|
+
if (bits === 32)
|
|
196
|
+
throw new NumericError("cannot convert NaN to integer", "0A000");
|
|
197
|
+
throw new NumericError("cannot convert NaN to bigint", "0A000");
|
|
198
|
+
}
|
|
199
|
+
if (/inf/i.test(t)) {
|
|
200
|
+
if (bits === 16)
|
|
201
|
+
throw new NumericError("cannot convert infinity to smallint", "0A000");
|
|
202
|
+
if (bits === 32)
|
|
203
|
+
throw new NumericError("cannot convert infinity to integer", "0A000");
|
|
204
|
+
throw new NumericError("cannot convert infinity to bigint", "0A000");
|
|
205
|
+
}
|
|
206
|
+
const r = round(parseNumeric(t), 0).unscaled;
|
|
207
|
+
const limit = 1n << BigInt(bits - 1);
|
|
208
|
+
if (r < -limit || r >= limit)
|
|
209
|
+
throw new NumericError(`${name} out of range`, "22003");
|
|
210
|
+
return r.toString();
|
|
211
|
+
}
|
|
212
|
+
/** A numeric cast to float8 (`numeric_float8`, which is `float8in(numeric_out(x))`, issue 217): NaN and ±Infinity are
|
|
213
|
+
* themselves; any other text the double nearest it (`Number` of a decimal text rounds correctly, as `strtod` does).
|
|
214
|
+
* Where that double is ±Infinity, or 0 for a value that is not zero, 22003 in Postgres's words (`float8in_internal`),
|
|
215
|
+
* the text quoted whole; a subnormal (1e-310) is returned, as `float8in` returns it */
|
|
216
|
+
export function numericToDouble(text) {
|
|
217
|
+
const t = text.trim();
|
|
218
|
+
if (/^nan$/i.test(t))
|
|
219
|
+
return NaN;
|
|
220
|
+
if (/^\+?inf(inity)?$/i.test(t))
|
|
221
|
+
return Infinity;
|
|
222
|
+
if (/^-inf(inity)?$/i.test(t))
|
|
223
|
+
return -Infinity;
|
|
224
|
+
const d = Number(t);
|
|
225
|
+
if (Number.isNaN(d))
|
|
226
|
+
throw new NumericError(`invalid input syntax for type double precision: "${t}"`, "22P02");
|
|
227
|
+
if (!Number.isFinite(d) || (d === 0 && /[1-9]/.test(t.replace(/[eE].*$/, "")))) {
|
|
228
|
+
throw new NumericError(`"${t}" is out of range for type double precision`, "22003");
|
|
229
|
+
}
|
|
230
|
+
// (a numeric's zero is never negative: numeric_out writes `-0.00` as `0.00`, whose double is +0)
|
|
231
|
+
return d === 0 ? 0 : d;
|
|
232
|
+
}
|
|
92
233
|
/** The value at a larger scale (more trailing zeros), unchanged. */
|
|
93
234
|
const atScale = (n, scale) => n.unscaled * pow10(scale - n.scale);
|
|
94
235
|
export const add = (a, b) => {
|
|
@@ -119,15 +260,26 @@ function groups(n) {
|
|
|
119
260
|
const digits = lsFirst.slice(low).reverse();
|
|
120
261
|
return { digits, weight: lsFirst.length - 1 - fracGroups };
|
|
121
262
|
}
|
|
263
|
+
/** the weight and the value of a value's FIRST base-10000 group (`groups`' weight and digits[0]; 0 and 0 for zero), read
|
|
264
|
+
* off its decimal digits: the most significant digit's place is 10^(digits - 1 - scale), its group the one holding
|
|
265
|
+
* that place, and the group's value the digits down to the group's last place. All `select_div_scale` reads of a value */
|
|
266
|
+
function leadGroup(n) {
|
|
267
|
+
if (n.unscaled === 0n)
|
|
268
|
+
return [0, 0];
|
|
269
|
+
const digits = abs(n.unscaled).toString();
|
|
270
|
+
const place = digits.length - 1 - n.scale;
|
|
271
|
+
const weight = Math.floor(place / DEC_DIGITS);
|
|
272
|
+
const width = place - weight * DEC_DIGITS + 1;
|
|
273
|
+
// (a group past the last digit is padded with its zeros: 0.1 is the group 1000 at weight -1)
|
|
274
|
+
return [weight, Number(digits.slice(0, width).padEnd(width, "0"))];
|
|
275
|
+
}
|
|
122
276
|
/**
|
|
123
277
|
* `select_div_scale` (numeric.c), faithfully: a quotient keeps at least NUMERIC_MIN_SIG_DIGITS significant digits —
|
|
124
278
|
* the scale is read from the WEIGHTS and FIRST GROUPS of the two values, so it depends on the values, not only their
|
|
125
279
|
* types — and never fewer decimals than either input shows, between 0 and NUMERIC_MAX_DISPLAY_SCALE.
|
|
126
280
|
*/
|
|
127
281
|
export function divScale(a, b) {
|
|
128
|
-
const
|
|
129
|
-
const weight1 = ga.digits.length ? ga.weight : 0, first1 = ga.digits[0] ?? 0;
|
|
130
|
-
const weight2 = gb.digits.length ? gb.weight : 0, first2 = gb.digits[0] ?? 0;
|
|
282
|
+
const [weight1, first1] = leadGroup(a), [weight2, first2] = leadGroup(b);
|
|
131
283
|
// the quotient's weight; when the first groups are equal it may be either, and the smaller is assumed
|
|
132
284
|
let qweight = weight1 - weight2;
|
|
133
285
|
if (first1 <= first2)
|
|
@@ -239,7 +391,8 @@ export function withTypmod(a, precision, scale) {
|
|
|
239
391
|
const maxDigits = precision - scale;
|
|
240
392
|
const digits = r.unscaled === 0n ? null : abs(r.unscaled).toString().length - r.scale;
|
|
241
393
|
if (digits !== null && digits > maxDigits) {
|
|
242
|
-
|
|
394
|
+
const o = fieldOverflow(precision, scale);
|
|
395
|
+
throw new NumericError(o.message, "22003", null, o.detail);
|
|
243
396
|
}
|
|
244
397
|
return r;
|
|
245
398
|
}
|
|
@@ -334,16 +487,46 @@ export function aggregateType(agg, arg) {
|
|
|
334
487
|
case "max": return arg;
|
|
335
488
|
}
|
|
336
489
|
}
|
|
337
|
-
/**
|
|
490
|
+
/**
|
|
491
|
+
* A PROGRAM IS AN IMMUTABLE VALUE, AND A NODE MAY HAVE SEVERAL PARENTS (issue 244, #484): a FROM item's column read k
|
|
492
|
+
* times by the level above is ONE node those k references share (src/duck/rewrite.ts `carried`), never k copies, so a
|
|
493
|
+
* chain of levels each reading the one below three times is linear in its depth, not 3^depth. Nothing changes a node
|
|
494
|
+
* once it is made (a shared node changed would change every reader), and every reader here is linear in the DISTINCT
|
|
495
|
+
* nodes: `cellsOf`, `evaluate` (each node at most once a row), `mapCells` (sharing kept) and the one text a program
|
|
496
|
+
* leaves the planner in, `programJson`.
|
|
497
|
+
*/
|
|
498
|
+
/** The cells a program reads, each once, in the order a walk from its root first meets them: over its DISTINCT nodes
|
|
499
|
+
* (a node several parents share is walked once), memoised per node, since a program is never changed once made. */
|
|
338
500
|
export function cellsOf(p) {
|
|
501
|
+
const known = CELLS.get(p);
|
|
502
|
+
if (known !== undefined)
|
|
503
|
+
return known;
|
|
504
|
+
const out = new Set();
|
|
505
|
+
if (p.op === "cell")
|
|
506
|
+
out.add(p.key);
|
|
507
|
+
else
|
|
508
|
+
for (const child of childrenOf(p))
|
|
509
|
+
for (const k of cellsOf(child))
|
|
510
|
+
out.add(k);
|
|
511
|
+
const list = [...out];
|
|
512
|
+
CELLS.set(p, list);
|
|
513
|
+
return list;
|
|
514
|
+
}
|
|
515
|
+
const CELLS = new WeakMap();
|
|
516
|
+
/** a node's children, in the order its fields are written (an `index` before a pick's arms, a rescale's `scale` after its value) */
|
|
517
|
+
function childrenOf(p) {
|
|
339
518
|
switch (p.op) {
|
|
340
|
-
case "cell":
|
|
519
|
+
case "cell":
|
|
341
520
|
case "literal": return [];
|
|
342
521
|
case "add":
|
|
343
522
|
case "sub":
|
|
344
523
|
case "mul":
|
|
345
524
|
case "div":
|
|
346
|
-
case "mod":
|
|
525
|
+
case "mod":
|
|
526
|
+
case "greatest":
|
|
527
|
+
case "least":
|
|
528
|
+
case "coalesce":
|
|
529
|
+
case "stddev": return p.args;
|
|
347
530
|
case "neg":
|
|
348
531
|
case "round":
|
|
349
532
|
case "typmod":
|
|
@@ -351,45 +534,61 @@ export function cellsOf(p) {
|
|
|
351
534
|
case "abs":
|
|
352
535
|
case "floor":
|
|
353
536
|
case "ceil":
|
|
354
|
-
case "pad": return
|
|
355
|
-
case "
|
|
356
|
-
case "
|
|
357
|
-
case "coalesce":
|
|
358
|
-
case "stddev": return p.args.flatMap(cellsOf);
|
|
359
|
-
case "pick": return [...cellsOf(p.index), ...p.args.flatMap(cellsOf)];
|
|
360
|
-
case "rescale": return [...cellsOf(p.arg), ...cellsOf(p.scale)];
|
|
537
|
+
case "pad": return [p.arg];
|
|
538
|
+
case "pick": return [p.index, ...p.args];
|
|
539
|
+
case "rescale": return [p.arg, p.scale];
|
|
361
540
|
}
|
|
362
541
|
}
|
|
363
|
-
/**
|
|
364
|
-
|
|
542
|
+
/** each literal node's value, parsed once (a program is never changed once made, and no value is ever changed) */
|
|
543
|
+
const LITERALS = new WeakMap();
|
|
544
|
+
/** The value of a program over one row, or null; throws NumericError where Postgres raises (a division by zero). Each
|
|
545
|
+
* node several parents share is computed AT MOST ONCE for the row (`memo`, one a row), and only where Postgres
|
|
546
|
+
* computes it: a CASE's untaken arm and a COALESCE's arguments after the first that is not NULL never are. */
|
|
547
|
+
export function evaluate(p, row, memo = new Map()) {
|
|
548
|
+
if (memo.has(p))
|
|
549
|
+
return memo.get(p);
|
|
550
|
+
const v = evaluateNode(p, row, memo);
|
|
551
|
+
memo.set(p, v);
|
|
552
|
+
return v;
|
|
553
|
+
}
|
|
554
|
+
function evaluateNode(p, row, memo) {
|
|
555
|
+
const ev = (x) => evaluate(x, row, memo);
|
|
365
556
|
switch (p.op) {
|
|
366
557
|
case "cell": {
|
|
367
558
|
const v = row[p.key];
|
|
368
559
|
return v === null || v === undefined ? null : numericOf(v);
|
|
369
560
|
}
|
|
370
|
-
case "literal":
|
|
561
|
+
case "literal": {
|
|
562
|
+
// (a literal is the same on every row: read once, `LITERALS`)
|
|
563
|
+
let v = LITERALS.get(p);
|
|
564
|
+
if (v === undefined) {
|
|
565
|
+
v = parseNumeric(p.text);
|
|
566
|
+
LITERALS.set(p, v);
|
|
567
|
+
}
|
|
568
|
+
return v;
|
|
569
|
+
}
|
|
371
570
|
case "neg": {
|
|
372
|
-
const a =
|
|
571
|
+
const a = ev(p.arg);
|
|
373
572
|
return a === null ? null : { unscaled: -a.unscaled, scale: a.scale };
|
|
374
573
|
}
|
|
375
574
|
case "round": {
|
|
376
|
-
const a =
|
|
575
|
+
const a = ev(p.arg);
|
|
377
576
|
return a === null ? null : round(a, p.scale);
|
|
378
577
|
}
|
|
379
578
|
case "typmod": {
|
|
380
|
-
const a =
|
|
579
|
+
const a = ev(p.arg);
|
|
381
580
|
return a === null ? null : withTypmod(a, p.precision, p.scale);
|
|
382
581
|
}
|
|
383
582
|
case "trunc": {
|
|
384
|
-
const a =
|
|
583
|
+
const a = ev(p.arg);
|
|
385
584
|
return a === null ? null : trunc(a, p.scale);
|
|
386
585
|
}
|
|
387
586
|
case "pad": {
|
|
388
|
-
const a =
|
|
587
|
+
const a = ev(p.arg);
|
|
389
588
|
return a === null || a.scale >= p.scale ? a : { unscaled: a.unscaled * pow10(p.scale - a.scale), scale: p.scale };
|
|
390
589
|
}
|
|
391
590
|
case "rescale": {
|
|
392
|
-
const a =
|
|
591
|
+
const a = ev(p.arg), s = ev(p.scale);
|
|
393
592
|
if (a === null || s === null || s.scale !== 0 || s.unscaled < 0n)
|
|
394
593
|
return a;
|
|
395
594
|
const to = Number(s.unscaled);
|
|
@@ -399,20 +598,20 @@ export function evaluate(p, row) {
|
|
|
399
598
|
return a.unscaled % by === 0n ? { unscaled: a.unscaled / by, scale: to } : a;
|
|
400
599
|
}
|
|
401
600
|
case "abs": {
|
|
402
|
-
const a =
|
|
601
|
+
const a = ev(p.arg);
|
|
403
602
|
return a === null ? null : { unscaled: abs(a.unscaled), scale: a.scale };
|
|
404
603
|
}
|
|
405
604
|
case "floor": {
|
|
406
|
-
const a =
|
|
605
|
+
const a = ev(p.arg);
|
|
407
606
|
return a === null ? null : floor(a);
|
|
408
607
|
}
|
|
409
608
|
case "ceil": {
|
|
410
|
-
const a =
|
|
609
|
+
const a = ev(p.arg);
|
|
411
610
|
return a === null ? null : ceil(a);
|
|
412
611
|
}
|
|
413
612
|
case "coalesce": {
|
|
414
613
|
for (const x of p.args) {
|
|
415
|
-
const v =
|
|
614
|
+
const v = ev(x);
|
|
416
615
|
if (v !== null)
|
|
417
616
|
return v;
|
|
418
617
|
}
|
|
@@ -422,68 +621,210 @@ export function evaluate(p, row) {
|
|
|
422
621
|
case "least": {
|
|
423
622
|
let best = null;
|
|
424
623
|
for (const x of p.args) {
|
|
425
|
-
const v =
|
|
624
|
+
const v = ev(x);
|
|
426
625
|
if (v !== null && (best === null || (p.op === "greatest" ? compare(v, best) > 0 : compare(v, best) < 0)))
|
|
427
626
|
best = v;
|
|
428
627
|
}
|
|
429
628
|
return best;
|
|
430
629
|
}
|
|
431
630
|
case "mod": {
|
|
432
|
-
const a =
|
|
631
|
+
const a = ev(p.args[0]), b = ev(p.args[1]);
|
|
433
632
|
return a === null || b === null ? null : mod(a, b);
|
|
434
633
|
}
|
|
435
634
|
case "pick": {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
return arm === undefined ? null : evaluate(arm, row);
|
|
635
|
+
const arm = takenArm(p, ev);
|
|
636
|
+
return arm === undefined ? null : ev(arm);
|
|
439
637
|
}
|
|
440
638
|
case "stddev": {
|
|
441
|
-
const [n, x, x2] = p.args.map(
|
|
639
|
+
const [n, x, x2] = p.args.map(ev);
|
|
442
640
|
return n === null || n === undefined || x === null || x === undefined || x2 === null || x2 === undefined ? null : stddev(n, x, x2, p.sample, p.variance);
|
|
443
641
|
}
|
|
444
642
|
default: {
|
|
445
|
-
const a =
|
|
643
|
+
const a = ev(p.args[0]), b = ev(p.args[1]);
|
|
446
644
|
if (a === null || b === null)
|
|
447
645
|
return null;
|
|
448
646
|
return p.op === "add" ? add(a, b) : p.op === "sub" ? sub(a, b) : p.op === "mul" ? mul(a, b) : div(a, b);
|
|
449
647
|
}
|
|
450
648
|
}
|
|
451
649
|
}
|
|
650
|
+
/** the arm of a CASE the row took, by its index's value; undefined past the arms (a CASE with no ELSE) or for a NULL index */
|
|
651
|
+
function takenArm(p, ev) {
|
|
652
|
+
const i = ev(p.index);
|
|
653
|
+
return i === null || i.scale !== 0 ? undefined : p.args[Number(i.unscaled)];
|
|
654
|
+
}
|
|
452
655
|
/** a numeric's special values, as numeric_out writes them (a text read by numeric_in may be one) */
|
|
453
656
|
const SPECIAL = /^(NaN|-?Infinity)$/;
|
|
454
657
|
/** A special value the program passes through whole (a cell, padded or under a typmod), else undefined; arithmetic on
|
|
455
|
-
* one is refused by name (Postgres's NaN and ±Infinity rules are not this module's).
|
|
456
|
-
|
|
658
|
+
* one is refused by name (Postgres's NaN and ±Infinity rules are not this module's). Once per node for the row
|
|
659
|
+
* (`seen`), as `evaluate` is. */
|
|
660
|
+
function specialOf(p, row, memo, seen) {
|
|
661
|
+
if (seen.has(p))
|
|
662
|
+
return seen.get(p);
|
|
663
|
+
const v = specialNode(p, row, memo, seen);
|
|
664
|
+
seen.set(p, v);
|
|
665
|
+
return v;
|
|
666
|
+
}
|
|
667
|
+
function specialNode(p, row, memo, seen) {
|
|
457
668
|
if (p.op === "cell") {
|
|
458
669
|
const v = row[p.key];
|
|
459
670
|
return typeof v === "string" && SPECIAL.test(v) ? v : undefined;
|
|
460
671
|
}
|
|
461
672
|
if (p.op === "pad" || p.op === "rescale")
|
|
462
|
-
return specialOf(p.arg, row);
|
|
673
|
+
return specialOf(p.arg, row, memo, seen);
|
|
463
674
|
// the arm the row took (a special value in another is never read, as Postgres computes only the taken arm)
|
|
464
675
|
if (p.op === "pick") {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
return arm === undefined ? undefined : specialOf(arm, row);
|
|
676
|
+
const arm = takenArm(p, (x) => evaluate(x, row, memo));
|
|
677
|
+
return arm === undefined ? undefined : specialOf(arm, row, memo, seen);
|
|
468
678
|
}
|
|
469
679
|
if (p.op === "typmod") {
|
|
470
|
-
const v = specialOf(p.arg, row);
|
|
471
|
-
if (v !== undefined && v !== "NaN")
|
|
472
|
-
|
|
680
|
+
const v = specialOf(p.arg, row, memo, seen);
|
|
681
|
+
if (v !== undefined && v !== "NaN") {
|
|
682
|
+
const o = fieldOverflow(p.precision, p.scale, true);
|
|
683
|
+
throw new NumericError(o.message, "22003", null, o.detail);
|
|
684
|
+
}
|
|
473
685
|
return v;
|
|
474
686
|
}
|
|
475
687
|
for (const key of cellsOf(p)) {
|
|
476
688
|
const v = row[key];
|
|
477
689
|
if (typeof v === "string" && SPECIAL.test(v))
|
|
478
|
-
throw new NumericError(`arithmetic on a numeric
|
|
690
|
+
throw new NumericError(`arithmetic on a numeric NaN or Infinity is not supported by Mason (${gapMark("numeric-special")})`, "0A000", "numeric-special");
|
|
479
691
|
}
|
|
480
692
|
return undefined;
|
|
481
693
|
}
|
|
482
|
-
/** The program's value as `numeric_out` writes it, or null. */
|
|
694
|
+
/** The program's value as `numeric_out` writes it, or null: each node computed at most once for the row. */
|
|
483
695
|
export const evaluateText = (p, row) => {
|
|
484
|
-
const
|
|
696
|
+
const memo = new Map();
|
|
697
|
+
const special = specialOf(p, row, memo, new Map());
|
|
485
698
|
if (special !== undefined)
|
|
486
699
|
return special;
|
|
487
|
-
const v = evaluate(p, row);
|
|
700
|
+
const v = evaluate(p, row, memo);
|
|
488
701
|
return v === null ? null : numericText(v);
|
|
489
702
|
};
|
|
703
|
+
/** A program with each cell replaced (a parameter's `$n` too), its SHARING KEPT: a node several parents share is mapped
|
|
704
|
+
* once and its one image shared alike, and `f` is asked once per cell node. */
|
|
705
|
+
export function mapCells(p, f) {
|
|
706
|
+
const memo = new Map();
|
|
707
|
+
const m = (x) => {
|
|
708
|
+
const known = memo.get(x);
|
|
709
|
+
if (known !== undefined)
|
|
710
|
+
return known;
|
|
711
|
+
const y = mapNode(x, m, f);
|
|
712
|
+
memo.set(x, y);
|
|
713
|
+
return y;
|
|
714
|
+
};
|
|
715
|
+
return m(p);
|
|
716
|
+
}
|
|
717
|
+
function mapNode(p, m, f) {
|
|
718
|
+
switch (p.op) {
|
|
719
|
+
case "cell": return f(p.key);
|
|
720
|
+
case "literal": return p;
|
|
721
|
+
case "add":
|
|
722
|
+
case "sub":
|
|
723
|
+
case "mul":
|
|
724
|
+
case "div":
|
|
725
|
+
case "mod": return { op: p.op, args: [m(p.args[0]), m(p.args[1])] };
|
|
726
|
+
case "neg":
|
|
727
|
+
case "abs":
|
|
728
|
+
case "floor":
|
|
729
|
+
case "ceil": return { op: p.op, arg: m(p.arg) };
|
|
730
|
+
case "round":
|
|
731
|
+
case "trunc":
|
|
732
|
+
case "pad": return { op: p.op, arg: m(p.arg), scale: p.scale };
|
|
733
|
+
case "typmod": return { op: "typmod", arg: m(p.arg), precision: p.precision, scale: p.scale };
|
|
734
|
+
case "greatest":
|
|
735
|
+
case "least":
|
|
736
|
+
case "coalesce": return { op: p.op, args: p.args.map(m) };
|
|
737
|
+
case "stddev": return { op: "stddev", args: [m(p.args[0]), m(p.args[1]), m(p.args[2])], sample: p.sample, variance: p.variance };
|
|
738
|
+
case "pick": return { op: "pick", index: m(p.index), args: p.args.map(m) };
|
|
739
|
+
case "rescale": return { op: "rescale", arg: m(p.arg), scale: m(p.scale) };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* A PROGRAM AS TEXT, THE ONE FORMAT a program leaves the planner in (the literal `__mason_numeric_program` is given, a
|
|
744
|
+
* record compared or measured): a table of its nodes, each DISTINCT node written once with its children as their places
|
|
745
|
+
* in the table, children before parents, and the root's place:
|
|
746
|
+
* `{"n":[{"op":"cell","key":"0"},{"op":"add","args":[0,0]}],"r":1}`. Its size is linear in the program's distinct
|
|
747
|
+
* nodes, however many parents share one. It is also the program's STRUCTURAL IDENTITY: each node is interned by its op,
|
|
748
|
+
* its fields and its children's places, so two programs equal as trees are one text whatever their sharing (a tree and
|
|
749
|
+
* the same tree built with sharing; two arms built apart), and two that differ are two.
|
|
750
|
+
*/
|
|
751
|
+
export function programJson(p) {
|
|
752
|
+
const nodes = [], interned = new Map(), at = new Map();
|
|
753
|
+
const place = (x) => {
|
|
754
|
+
const known = at.get(x);
|
|
755
|
+
if (known !== undefined)
|
|
756
|
+
return known;
|
|
757
|
+
const text = nodeJson(x, place);
|
|
758
|
+
let i = interned.get(text);
|
|
759
|
+
if (i === undefined) {
|
|
760
|
+
i = nodes.length;
|
|
761
|
+
nodes.push(text);
|
|
762
|
+
interned.set(text, i);
|
|
763
|
+
}
|
|
764
|
+
at.set(x, i);
|
|
765
|
+
return i;
|
|
766
|
+
};
|
|
767
|
+
const root = place(p);
|
|
768
|
+
return `{"n":[${nodes.join(",")}],"r":${root}}`;
|
|
769
|
+
}
|
|
770
|
+
/** one node's entry in `programJson`'s table: its fields in one order whatever order they were made in, a child as its place */
|
|
771
|
+
function nodeJson(p, place) {
|
|
772
|
+
const j = JSON.stringify;
|
|
773
|
+
const list = (xs) => xs.map(place).join(",");
|
|
774
|
+
switch (p.op) {
|
|
775
|
+
case "cell": return `{"op":"cell","key":${j(p.key)}}`;
|
|
776
|
+
case "literal": return `{"op":"literal","text":${j(p.text)}}`;
|
|
777
|
+
case "add":
|
|
778
|
+
case "sub":
|
|
779
|
+
case "mul":
|
|
780
|
+
case "div":
|
|
781
|
+
case "mod":
|
|
782
|
+
case "greatest":
|
|
783
|
+
case "least":
|
|
784
|
+
case "coalesce":
|
|
785
|
+
return `{"op":${j(p.op)},"args":[${list(p.args)}]}`;
|
|
786
|
+
case "neg":
|
|
787
|
+
case "abs":
|
|
788
|
+
case "floor":
|
|
789
|
+
case "ceil": return `{"op":${j(p.op)},"arg":${place(p.arg)}}`;
|
|
790
|
+
case "round":
|
|
791
|
+
case "trunc":
|
|
792
|
+
case "pad": return `{"op":${j(p.op)},"arg":${place(p.arg)},"scale":${j(p.scale)}}`;
|
|
793
|
+
case "typmod": return `{"op":"typmod","arg":${place(p.arg)},"precision":${j(p.precision)},"scale":${j(p.scale)}}`;
|
|
794
|
+
case "stddev": return `{"op":"stddev","args":[${list(p.args)}],"sample":${j(p.sample)},"variance":${j(p.variance)}}`;
|
|
795
|
+
case "pick": {
|
|
796
|
+
const index = place(p.index);
|
|
797
|
+
return `{"op":"pick","index":${index},"args":[${list(p.args)}]}`;
|
|
798
|
+
}
|
|
799
|
+
case "rescale": {
|
|
800
|
+
const arg = place(p.arg);
|
|
801
|
+
return `{"op":"rescale","arg":${arg},"scale":${place(p.scale)}}`;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/** A program from `programJson`'s text: each node made once, every parent of it sharing it (a child's place is always
|
|
806
|
+
* before its parent's, so one pass makes them). Throws on a text that is not one. */
|
|
807
|
+
export function parseProgram(text) {
|
|
808
|
+
const raw = JSON.parse(text);
|
|
809
|
+
if (!Array.isArray(raw.n) || typeof raw.r !== "number")
|
|
810
|
+
throw new Error("not a numeric program's text");
|
|
811
|
+
const made = [];
|
|
812
|
+
const child = (i) => {
|
|
813
|
+
if (typeof i !== "number" || !(i >= 0 && i < made.length))
|
|
814
|
+
throw new Error("not a numeric program's text: a child after its parent");
|
|
815
|
+
return made[i];
|
|
816
|
+
};
|
|
817
|
+
for (const n of raw.n) {
|
|
818
|
+
const node = { ...n };
|
|
819
|
+
if (Array.isArray(n.args))
|
|
820
|
+
node.args = n.args.map(child);
|
|
821
|
+
if (n.arg !== undefined)
|
|
822
|
+
node.arg = child(n.arg);
|
|
823
|
+
if (n.index !== undefined)
|
|
824
|
+
node.index = child(n.index);
|
|
825
|
+
if (n.op === "rescale")
|
|
826
|
+
node.scale = child(n.scale);
|
|
827
|
+
made.push(node);
|
|
828
|
+
}
|
|
829
|
+
return child(raw.r);
|
|
830
|
+
}
|