bonsai-js 1.0.0 → 1.2.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/CHANGELOG.md +20 -0
- package/README.md +24 -4
- package/dist/artifact.d.mts +47 -0
- package/dist/artifact.mjs +139 -0
- package/dist/artifact.mjs.map +1 -0
- package/dist/builtins/index.d.mts +91 -0
- package/dist/builtins/index.mjs +93 -0
- package/dist/builtins/index.mjs.map +1 -0
- package/dist/check/bind.mjs +117 -0
- package/dist/check/bind.mjs.map +1 -0
- package/dist/check/checker.mjs +34 -120
- package/dist/check/checker.mjs.map +1 -1
- package/dist/compile/compiler.mjs +2 -3
- package/dist/compile/compiler.mjs.map +1 -1
- package/dist/declared.mjs +8 -1
- package/dist/declared.mjs.map +1 -1
- package/dist/digest.mjs +109 -0
- package/dist/digest.mjs.map +1 -0
- package/dist/environment.d.mts +14 -1
- package/dist/environment.mjs +65 -315
- package/dist/environment.mjs.map +1 -1
- package/dist/errors.d.mts +21 -2
- package/dist/errors.mjs +17 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/features.mjs +3 -3
- package/dist/features.mjs.map +1 -1
- package/dist/functions/builtins.mjs +551 -474
- package/dist/functions/builtins.mjs.map +1 -1
- package/dist/functions/define.mjs +53 -11
- package/dist/functions/define.mjs.map +1 -1
- package/dist/functions/descriptions.mjs +110 -0
- package/dist/functions/descriptions.mjs.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +3 -2
- package/dist/lite/index.d.mts +4 -3
- package/dist/lite/index.mjs +3 -2
- package/dist/lite/index.mjs.map +1 -1
- package/dist/load.mjs +307 -0
- package/dist/load.mjs.map +1 -0
- package/dist/program.mjs +321 -0
- package/dist/program.mjs.map +1 -0
- package/dist/query/index.mjs +1 -1
- package/dist/runtime/core.d.mts +18 -0
- package/dist/runtime/core.mjs +31 -0
- package/dist/runtime/core.mjs.map +1 -0
- package/dist/runtime/create.d.mts +61 -0
- package/dist/runtime/create.mjs +76 -0
- package/dist/runtime/create.mjs.map +1 -0
- package/dist/runtime/index.d.mts +24 -0
- package/dist/runtime/index.mjs +34 -0
- package/dist/runtime/index.mjs.map +1 -0
- package/dist/service/index.mjs +12 -8
- package/dist/service/index.mjs.map +1 -1
- package/dist/syntax/parser.mjs +1 -1
- package/dist/syntax/printer.mjs +1 -1
- package/dist/types.mjs +103 -23
- package/dist/types.mjs.map +1 -1
- package/dist/version.mjs +7 -0
- package/dist/version.mjs.map +1 -0
- package/package.json +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ From 1.0.0, entries are generated at release time from the bump files in
|
|
|
7
7
|
[`.bumpy/`](./.bumpy/); entries up to 0.5.0 follow
|
|
8
8
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## 1.2.0
|
|
13
|
+
<sub>2026-10-25</sub>
|
|
14
|
+
|
|
15
|
+
- *(minor)*
|
|
16
|
+
Add `bonsai-js/runtime/core` and `bonsai-js/builtins`: a runtime with only the built-in functions passed in `builtins`, so a browser bundle carries just what its rules call (about 30 KB gzipped for typical rules, against 43 KB for `bonsai-js/runtime`). `bonsai()` takes the same `builtins` option, and a call to a built-in left out is a CHECK error, so rules are checked against what the browser can run.
|
|
17
|
+
- *(patch)*
|
|
18
|
+
Built-in function descriptions, which only `describeFunction()`, `listFunctions()`, and the language service read, are no longer bundled with `bonsai-js/runtime`, which is about 1.2 KB smaller gzipped (43 KB). Making each built-in separately removable adds about 0.8 KB to `bonsai-js` (69 KB), `bonsai-js/lite` (57 KB), and the language service (47 KB).
|
|
19
|
+
|
|
20
|
+
## 1.1.0
|
|
21
|
+
<sub>2026-10-24</sub>
|
|
22
|
+
|
|
23
|
+
- *(minor)*
|
|
24
|
+
Add `bonsai-js/runtime` and `toArtifact()`: check and compile rules where the full toolchain runs, and evaluate them in the browser without the checker (about 44 KB minified and gzipped), with exactly the results the compiled programs give. A runtime whose definitions differ refuses an artifact with `BonsaiArtifactError` (code `ARTIFACT`) and a `reason`.
|
|
25
|
+
- *(patch)*
|
|
26
|
+
Charge the step budget for each union member tried when a result is checked against its expected type or a host function's declared type. A list result checked against a large enum could previously run far past `maxSteps` and `timeout`.
|
|
27
|
+
- *(patch)*
|
|
28
|
+
Check and cache a program against a private copy of its `expect` type. A type object with a `toJSON` method could previously be served a cached program checked against a different type, and a type object changed after compiling changed what the program's results were checked against. The language service's `expect` option is kept the same way. Types built with `t` are used as given.
|
|
29
|
+
|
|
10
30
|
## 1.0.0
|
|
11
31
|
<sub>2026-10-19</sub>
|
|
12
32
|
|
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Try it in the [Playground](https://danfry1.github.io/bonsai-js/playground).
|
|
|
62
62
|
| Evaluation budget for untrusted input (as documented) | Yes, on by default | Parse-time limits only | No expression budget | No | No | No | No |
|
|
63
63
|
| SQL or MongoDB translation | Yes | No | No | No | No | No | No |
|
|
64
64
|
|
|
65
|
-
Bonsai is the largest of these in the browser (about
|
|
65
|
+
Bonsai is the largest of these in the browser (about 57 KB from `bonsai-js/lite`, without explaining and partial evaluation, and about 30 KB from `bonsai-js/runtime/core` for rules checked and compiled on a server, with only the built-in functions they call) and the newest. If rules must also run in Go or Java, CEL or ZEN fit better; if rules are JSON produced by a form builder and size matters most, json-logic-js does. The [full comparison](https://danfry1.github.io/bonsai-js/guide/comparison) covers more libraries, throughput, and the method behind every number.
|
|
66
66
|
|
|
67
67
|
## Install
|
|
68
68
|
|
|
@@ -74,7 +74,7 @@ Upgrading from 0.x? The [migration guide](https://danfry1.github.io/bonsai-js/gu
|
|
|
74
74
|
|
|
75
75
|
Requires Node.js 22 or newer, current Bun, or a modern browser. The package is ESM; `require('bonsai-js')` works where Node can load ES modules synchronously (22.12 and newer); for the TypeScript settings that allow it, see [Install](https://danfry1.github.io/bonsai-js/guide/install#typescript-settings).
|
|
76
76
|
|
|
77
|
-
In a browser bundle, an app that evaluates expressions adds about
|
|
77
|
+
In a browser bundle, an app that evaluates expressions adds about 69 KB minified and gzipped: the parser, type checker, compiler, and built-in library, with explaining and partial evaluation included. `bonsai-js/lite` leaves those two out, for about 57 KB, and `bonsai-js/runtime`, which evaluates rules checked and compiled elsewhere, leaves out the checker too, for about 43 KB, or about 30 KB from `bonsai-js/runtime/core` with only the built-in functions your rules call. `bonsai-js/query` and `bonsai-js/service` are separate entry points; the language service on its own is about 47 KB, since it includes the type checker. Unused modules are tree-shaken: importing only `t` adds under 1 KB, and all the error classes about 1.6 KB. CI enforces these budgets (`bun run check:size`).
|
|
78
78
|
|
|
79
79
|
## The language in one screen
|
|
80
80
|
|
|
@@ -94,7 +94,7 @@ startOfDay(order.placedAt, "Europe/Berlin") // calendar math in time
|
|
|
94
94
|
// comments work too
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
The full reference is [docs/language.md](https://github.com/danfry1/bonsai-js/blob/v1.
|
|
97
|
+
The full reference is [docs/language.md](https://github.com/danfry1/bonsai-js/blob/v1.2.0/docs/language.md). The rules that matter most:
|
|
98
98
|
|
|
99
99
|
- There is one absent value, `null`. Missing properties and host `undefined` read as `null`, so `x == null` means "missing or null". A key holding `undefined` is absent everywhere: `{ a: 1, b: undefined }` equals `{ a: 1 }`, and `"b" in` it is false.
|
|
100
100
|
- `==` compares by value (lists and maps deeply). There is no type coercion: `1 == "1"` is `false` and `"a" + 1` is an error.
|
|
@@ -201,7 +201,7 @@ Every limit except `timeout` is on by default; `limits` changes the budget. `0`
|
|
|
201
201
|
| `maxPatternLength` | 4,096 | regular expression patterns passed to `matches` |
|
|
202
202
|
| `timeout` | none | wall-clock milliseconds per evaluation |
|
|
203
203
|
|
|
204
|
-
The step budget is deterministic: the same expression over the same data uses the same steps on every machine, and at the default budget any expression finishes or fails within about 100 ms on Node (about 150 ms for the slowest shapes we have found). Host functions are trusted code: limits cannot interrupt a synchronous host function that is already running, and a getter or Proxy you put in the context runs when it is read. Set a `timeout` when expressions call slow host functions. See [SECURITY.md](https://github.com/danfry1/bonsai-js/blob/main/SECURITY.md) and [docs/threat-model.md](https://github.com/danfry1/bonsai-js/blob/v1.
|
|
204
|
+
The step budget is deterministic: the same expression over the same data uses the same steps on every machine, and at the default budget any expression finishes or fails within about 100 ms on Node (about 150 ms for the slowest shapes we have found). Host functions are trusted code: limits cannot interrupt a synchronous host function that is already running, and a getter or Proxy you put in the context runs when it is read. Set a `timeout` when expressions call slow host functions. See [SECURITY.md](https://github.com/danfry1/bonsai-js/blob/main/SECURITY.md) and [docs/threat-model.md](https://github.com/danfry1/bonsai-js/blob/v1.2.0/docs/threat-model.md).
|
|
205
205
|
|
|
206
206
|
## Built-in functions
|
|
207
207
|
|
|
@@ -299,6 +299,26 @@ toMongo(filter, { row: 'order', fields: columns, known: { minTotal: 100 } }) //
|
|
|
299
299
|
|
|
300
300
|
The query selects exactly the records for which the filter evaluates to `true` in Bonsai, including null handling (SQL's three-valued logic is made two-valued) and records where the filter would fail (they are excluded). One exception: in Postgres, a record whose arithmetic overflows a double (or whose nonzero product rounds to zero) makes the whole query fail with `value out of range`, an error rather than a different set of rows, where SQLite excludes the record; keep stored numbers within range to avoid it (see the query guide's caveats). Only declared columns can be queried, values are always parameters, and anything without an exact database equivalent throws a `BonsaiTranslationError` pointing at it. This is checked by differential tests against real SQLite, Postgres, and a MongoDB query engine.
|
|
301
301
|
|
|
302
|
+
## Rules in the browser
|
|
303
|
+
|
|
304
|
+
Check and compile where the full toolchain runs, then evaluate in the browser with `bonsai-js/runtime`, which does not bundle the checker. `toArtifact()` turns a compiled program into plain JSON:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { bonsai, t, toArtifact } from 'bonsai-js'
|
|
308
|
+
import { runtime } from 'bonsai-js/runtime'
|
|
309
|
+
|
|
310
|
+
const variables = { user: t.object({ age: t.number() }) }
|
|
311
|
+
|
|
312
|
+
// On the server, when the rule is saved.
|
|
313
|
+
const artifact = JSON.stringify(toArtifact(bonsai({ variables }).compile('user.age >= 18', { expect: t.boolean() })))
|
|
314
|
+
|
|
315
|
+
// In the browser.
|
|
316
|
+
const rule = runtime({ variables }).load(JSON.parse(artifact), { expect: t.boolean() })
|
|
317
|
+
rule.evaluateSync({ user: { age: 36 } }) // => true
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
An artifact records the checker's choices that affect results and a digest of the definitions they depend on, so it evaluates exactly as the compiled program does, or a runtime with different definitions refuses it with a `BonsaiArtifactError` saying why. See [Runtime](https://danfry1.github.io/bonsai-js/api/runtime).
|
|
321
|
+
|
|
302
322
|
## Syntax trees and visual editors
|
|
303
323
|
|
|
304
324
|
`env.parse(source)` returns a JSON syntax tree with source offsets on every node, and `print(tree)` turns a tree back into source. Printing is deterministic and round-trips: `print(env.parse(print(tree)))` is the same text, with the fewest parentheses that keep the meaning. That is what a visual rule builder needs: parse, edit the tree, print, save.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Type } from "./types.mjs";
|
|
2
|
+
//#region src/artifact.d.ts
|
|
3
|
+
/** The artifact format this release writes and reads. */
|
|
4
|
+
export declare const ARTIFACT_FORMAT = 1;
|
|
5
|
+
/**
|
|
6
|
+
* A call whose checked candidates are narrower than its function's overloads:
|
|
7
|
+
* `[nameStart, name, ...candidate overload indices]`.
|
|
8
|
+
*/
|
|
9
|
+
export type Plan = readonly [nameStart: number, name: string, ...candidates: number[]];
|
|
10
|
+
/**
|
|
11
|
+
* A checked, compiled expression as plain JSON data, for `bonsai-js/runtime`
|
|
12
|
+
* to load where the checker is not bundled. The source stays the record: a
|
|
13
|
+
* full environment can always compile it again.
|
|
14
|
+
*/
|
|
15
|
+
export interface Artifact {
|
|
16
|
+
/** Format version. */
|
|
17
|
+
readonly bonsai: typeof ARTIFACT_FORMAT;
|
|
18
|
+
/** The bonsai-js version that produced it (informational). */
|
|
19
|
+
readonly by: string;
|
|
20
|
+
readonly source: string;
|
|
21
|
+
/** The checker's overload choices where they are narrower than all overloads, by call. */
|
|
22
|
+
readonly plans: readonly Plan[];
|
|
23
|
+
/** The type the program was compiled to produce, if any. */
|
|
24
|
+
readonly expect?: Type;
|
|
25
|
+
/** Whether the result must be checked against `expect` at run time. */
|
|
26
|
+
readonly guard: boolean;
|
|
27
|
+
/** Whether evaluation may call an async host function. */
|
|
28
|
+
readonly async: boolean;
|
|
29
|
+
/** Binds the artifact to the definitions the checker saw; see artifactDigest. */
|
|
30
|
+
readonly digest: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A compiled program as plain JSON data, for `bonsai-js/runtime` to load and
|
|
34
|
+
* evaluate without the checker. It records the source, the checker's overload
|
|
35
|
+
* choices, the expected type, and a digest of the definitions they depend on,
|
|
36
|
+
* so a runtime with different definitions refuses it instead of running it
|
|
37
|
+
* differently. Throws TypeError for anything that is not a program compiled
|
|
38
|
+
* by an environment, and RangeError when the expected type is too large for a
|
|
39
|
+
* runtime to load (more than 100,000 type nodes).
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const rule = env.compile('user.age >= 18', { expect: t.boolean() })
|
|
43
|
+
* const artifact = toArtifact(rule) // store it, or send it to the browser
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function toArtifact(program: object): Artifact;
|
|
47
|
+
//#endregion
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { typeData } from "./types.mjs";
|
|
2
|
+
import { forEachChild } from "./syntax/ast.mjs";
|
|
3
|
+
import { compiledFacts } from "./declared.mjs";
|
|
4
|
+
import { encodeFunction, encodeType, hash64, lengthPrefixed } from "./digest.mjs";
|
|
5
|
+
import { VERSION } from "./version.mjs";
|
|
6
|
+
/**
|
|
7
|
+
* The most type nodes an artifact's `expect` may hold, counted along every
|
|
8
|
+
* path (shared subtrees count each time they are reached), so a type that
|
|
9
|
+
* repeats one subtree cannot hide its size. Its own constant rather than
|
|
10
|
+
* maxNodes: a large `t.enum` compiles on a default environment and must load
|
|
11
|
+
* on a default runtime.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_EXPECT_NODES = 1e5;
|
|
14
|
+
/** Throws RangeError when a type holds more than MAX_EXPECT_NODES nodes along all its paths. */
|
|
15
|
+
function assertExpectSize(type, what) {
|
|
16
|
+
let count = 0;
|
|
17
|
+
const stack = [type];
|
|
18
|
+
while (stack.length > 0) {
|
|
19
|
+
const next = stack.pop();
|
|
20
|
+
if (++count > 1e5) throw new RangeError(`${what} has more than ${MAX_EXPECT_NODES} type nodes`);
|
|
21
|
+
if (next.kind === "list") stack.push(next.element);
|
|
22
|
+
else if (next.kind === "union") stack.push(...next.types);
|
|
23
|
+
else if (next.kind === "map") {
|
|
24
|
+
stack.push(...Object.values(next.fields));
|
|
25
|
+
if (next.rest !== void 0) stack.push(next.rest);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Names of the functions a bound tree calls and the context variables it reads. */
|
|
30
|
+
function treeNames(root) {
|
|
31
|
+
const functions = /* @__PURE__ */ new Set();
|
|
32
|
+
const variables = /* @__PURE__ */ new Set();
|
|
33
|
+
const stack = [root];
|
|
34
|
+
while (stack.length > 0) {
|
|
35
|
+
const node = stack.pop();
|
|
36
|
+
if (node.type === "Call") functions.add(node.name);
|
|
37
|
+
else if (node.type === "Variable") variables.add(node.name);
|
|
38
|
+
forEachChild(node, (child) => {
|
|
39
|
+
stack.push(child);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
functions: [...functions].sort(),
|
|
44
|
+
variables: [...variables].sort()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The digest of an artifact: its fields (all but `digest`), every function the
|
|
49
|
+
* bound tree calls, and the declared type of every variable it reads, since
|
|
50
|
+
* the checker's overload choices and result guard depend on all of them. The
|
|
51
|
+
* producer and the runtime compute it the same way, so a stale or edited
|
|
52
|
+
* artifact fails to load instead of running with choices made for something
|
|
53
|
+
* else.
|
|
54
|
+
*/
|
|
55
|
+
function artifactDigest(fields, root, lookup, variables) {
|
|
56
|
+
const names = treeNames(root);
|
|
57
|
+
const plans = fields.plans.map(([start, name, ...candidates]) => lengthPrefixed(`${start}:${lengthPrefixed(name)}${candidates.join(",")}`));
|
|
58
|
+
const functions = names.functions.map((name) => {
|
|
59
|
+
const def = lookup(name);
|
|
60
|
+
return def === void 0 ? lengthPrefixed(`?${name}`) : lengthPrefixed(encodeFunction(def));
|
|
61
|
+
});
|
|
62
|
+
const declared = names.variables.map((name) => {
|
|
63
|
+
const type = variables !== void 0 && Object.hasOwn(variables, name) ? variables[name] : void 0;
|
|
64
|
+
return lengthPrefixed(`${lengthPrefixed(name)}${type === void 0 ? "?" : encodeType(type)}`);
|
|
65
|
+
});
|
|
66
|
+
return hash64([
|
|
67
|
+
`bonsai${fields.bonsai}`,
|
|
68
|
+
lengthPrefixed(fields.by),
|
|
69
|
+
lengthPrefixed(fields.source),
|
|
70
|
+
lengthPrefixed(plans.join("")),
|
|
71
|
+
fields.expect === void 0 ? "-" : lengthPrefixed(encodeType(fields.expect)),
|
|
72
|
+
fields.guard ? "g" : "-",
|
|
73
|
+
fields.async ? "a" : "-",
|
|
74
|
+
lengthPrefixed(functions.join("")),
|
|
75
|
+
lengthPrefixed(declared.join(""))
|
|
76
|
+
].join(""));
|
|
77
|
+
}
|
|
78
|
+
const artifacts = /* @__PURE__ */ new WeakMap();
|
|
79
|
+
/**
|
|
80
|
+
* A compiled program as plain JSON data, for `bonsai-js/runtime` to load and
|
|
81
|
+
* evaluate without the checker. It records the source, the checker's overload
|
|
82
|
+
* choices, the expected type, and a digest of the definitions they depend on,
|
|
83
|
+
* so a runtime with different definitions refuses it instead of running it
|
|
84
|
+
* differently. Throws TypeError for anything that is not a program compiled
|
|
85
|
+
* by an environment, and RangeError when the expected type is too large for a
|
|
86
|
+
* runtime to load (more than 100,000 type nodes).
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* const rule = env.compile('user.age >= 18', { expect: t.boolean() })
|
|
90
|
+
* const artifact = toArtifact(rule) // store it, or send it to the browser
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function toArtifact(program) {
|
|
94
|
+
const known = artifacts.get(program);
|
|
95
|
+
if (known !== void 0) return known;
|
|
96
|
+
const facts = compiledFacts(program);
|
|
97
|
+
if (facts === void 0) throw new TypeError("Not a program compiled by a Bonsai environment");
|
|
98
|
+
const artifact = makeArtifact(facts);
|
|
99
|
+
artifacts.set(program, artifact);
|
|
100
|
+
return artifact;
|
|
101
|
+
}
|
|
102
|
+
function makeArtifact({ source, analysis, expect, lookup, variables }) {
|
|
103
|
+
if (expect !== void 0) assertExpectSize(expect, "The expected type");
|
|
104
|
+
const plans = [];
|
|
105
|
+
for (const [call, plan] of analysis.calls) {
|
|
106
|
+
if (plan.candidates.length === plan.def.overloads.length) continue;
|
|
107
|
+
plans.push([
|
|
108
|
+
call.nameStart,
|
|
109
|
+
call.name,
|
|
110
|
+
...plan.candidates
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
plans.sort((a, b) => a[0] - b[0]);
|
|
114
|
+
const fields = {
|
|
115
|
+
bonsai: 1,
|
|
116
|
+
by: VERSION,
|
|
117
|
+
source,
|
|
118
|
+
plans,
|
|
119
|
+
...expect === void 0 ? {} : { expect: typeData(expect) },
|
|
120
|
+
guard: expect !== void 0 && analysis.checkResult,
|
|
121
|
+
async: analysis.async
|
|
122
|
+
};
|
|
123
|
+
const digest = artifactDigest(fields, analysis.root, lookup, variables);
|
|
124
|
+
return deepFreeze({
|
|
125
|
+
...fields,
|
|
126
|
+
digest
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function deepFreeze(value) {
|
|
130
|
+
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
131
|
+
Object.freeze(value);
|
|
132
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { MAX_EXPECT_NODES, artifactDigest, assertExpectSize, toArtifact, treeNames };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=artifact.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact.mjs","names":[],"sources":["../src/artifact.ts"],"sourcesContent":["import { compiledFacts, type CompiledFacts } from './declared.js'\nimport { encodeFunction, encodeType, hash64, lengthPrefixed } from './digest.js'\nimport type { FunctionDef } from './functions/define.js'\nimport { forEachChild, type Node } from './syntax/ast.js'\nimport { typeData, type Type } from './types.js'\nimport { VERSION } from './version.js'\n\n/** The artifact format this release writes and reads. */\nexport const ARTIFACT_FORMAT = 1\n\n/**\n * The most type nodes an artifact's `expect` may hold, counted along every\n * path (shared subtrees count each time they are reached), so a type that\n * repeats one subtree cannot hide its size. Its own constant rather than\n * maxNodes: a large `t.enum` compiles on a default environment and must load\n * on a default runtime.\n */\nexport const MAX_EXPECT_NODES = 100_000\n\n/**\n * A call whose checked candidates are narrower than its function's overloads:\n * `[nameStart, name, ...candidate overload indices]`.\n */\nexport type Plan = readonly [nameStart: number, name: string, ...candidates: number[]]\n\n/**\n * A checked, compiled expression as plain JSON data, for `bonsai-js/runtime`\n * to load where the checker is not bundled. The source stays the record: a\n * full environment can always compile it again.\n */\nexport interface Artifact {\n /** Format version. */\n readonly bonsai: typeof ARTIFACT_FORMAT\n /** The bonsai-js version that produced it (informational). */\n readonly by: string\n readonly source: string\n /** The checker's overload choices where they are narrower than all overloads, by call. */\n readonly plans: readonly Plan[]\n /** The type the program was compiled to produce, if any. */\n readonly expect?: Type\n /** Whether the result must be checked against `expect` at run time. */\n readonly guard: boolean\n /** Whether evaluation may call an async host function. */\n readonly async: boolean\n /** Binds the artifact to the definitions the checker saw; see artifactDigest. */\n readonly digest: string\n}\n\n/** Throws RangeError when a type holds more than MAX_EXPECT_NODES nodes along all its paths. */\nexport function assertExpectSize(type: Type, what: string): void {\n let count = 0\n const stack: Type[] = [type]\n while (stack.length > 0) {\n const next = stack.pop() as Type\n if (++count > MAX_EXPECT_NODES)\n throw new RangeError(`${what} has more than ${MAX_EXPECT_NODES} type nodes`)\n if (next.kind === 'list') stack.push(next.element)\n else if (next.kind === 'union') stack.push(...next.types)\n else if (next.kind === 'map') {\n stack.push(...Object.values(next.fields))\n if (next.rest !== undefined) stack.push(next.rest)\n }\n }\n}\n\n/** Names of the functions a bound tree calls and the context variables it reads. */\nexport function treeNames(root: Node): { functions: string[]; variables: string[] } {\n const functions = new Set<string>()\n const variables = new Set<string>()\n const stack: Node[] = [root]\n while (stack.length > 0) {\n const node = stack.pop() as Node\n if (node.type === 'Call') functions.add(node.name)\n else if (node.type === 'Variable') variables.add(node.name)\n forEachChild(node, (child) => {\n stack.push(child)\n })\n }\n return { functions: [...functions].sort(), variables: [...variables].sort() }\n}\n\n/**\n * The digest of an artifact: its fields (all but `digest`), every function the\n * bound tree calls, and the declared type of every variable it reads, since\n * the checker's overload choices and result guard depend on all of them. The\n * producer and the runtime compute it the same way, so a stale or edited\n * artifact fails to load instead of running with choices made for something\n * else.\n */\nexport function artifactDigest(\n fields: Omit<Artifact, 'digest'>,\n root: Node,\n lookup: (name: string) => FunctionDef | undefined,\n variables: Readonly<Record<string, Type>> | undefined,\n): string {\n const names = treeNames(root)\n const plans = fields.plans.map(([start, name, ...candidates]) =>\n lengthPrefixed(`${start}:${lengthPrefixed(name)}${candidates.join(',')}`),\n )\n const functions = names.functions.map((name) => {\n const def = lookup(name)\n return def === undefined ? lengthPrefixed(`?${name}`) : lengthPrefixed(encodeFunction(def))\n })\n const declared = names.variables.map((name) => {\n const type =\n variables !== undefined && Object.hasOwn(variables, name) ? variables[name] : undefined\n return lengthPrefixed(`${lengthPrefixed(name)}${type === undefined ? '?' : encodeType(type)}`)\n })\n return hash64(\n [\n `bonsai${fields.bonsai}`,\n lengthPrefixed(fields.by),\n lengthPrefixed(fields.source),\n lengthPrefixed(plans.join('')),\n fields.expect === undefined ? '-' : lengthPrefixed(encodeType(fields.expect)),\n fields.guard ? 'g' : '-',\n fields.async ? 'a' : '-',\n lengthPrefixed(functions.join('')),\n lengthPrefixed(declared.join('')),\n ].join(''),\n )\n}\n\nconst artifacts = new WeakMap<object, Artifact>()\n\n/**\n * A compiled program as plain JSON data, for `bonsai-js/runtime` to load and\n * evaluate without the checker. It records the source, the checker's overload\n * choices, the expected type, and a digest of the definitions they depend on,\n * so a runtime with different definitions refuses it instead of running it\n * differently. Throws TypeError for anything that is not a program compiled\n * by an environment, and RangeError when the expected type is too large for a\n * runtime to load (more than 100,000 type nodes).\n *\n * ```ts\n * const rule = env.compile('user.age >= 18', { expect: t.boolean() })\n * const artifact = toArtifact(rule) // store it, or send it to the browser\n * ```\n */\nexport function toArtifact(program: object): Artifact {\n const known = artifacts.get(program)\n if (known !== undefined) return known\n const facts = compiledFacts(program)\n if (facts === undefined) throw new TypeError('Not a program compiled by a Bonsai environment')\n const artifact = makeArtifact(facts)\n artifacts.set(program, artifact)\n return artifact\n}\n\nfunction makeArtifact({ source, analysis, expect, lookup, variables }: CompiledFacts): Artifact {\n if (expect !== undefined) assertExpectSize(expect, 'The expected type')\n const plans: Plan[] = []\n for (const [call, plan] of analysis.calls) {\n if (plan.candidates.length === plan.def.overloads.length) continue\n // The parser sets nameStart on every call it builds.\n plans.push([call.nameStart as number, call.name, ...plan.candidates])\n }\n plans.sort((a, b) => a[0] - b[0])\n const fields: Omit<Artifact, 'digest'> = {\n bonsai: ARTIFACT_FORMAT,\n by: VERSION,\n source,\n plans,\n // Frozen plain data, as the artifact must be; a type built by t already is.\n ...(expect === undefined ? {} : { expect: typeData(expect) }),\n guard: expect !== undefined && analysis.checkResult,\n async: analysis.async,\n }\n const digest = artifactDigest(fields, analysis.root, lookup, variables)\n return deepFreeze({ ...fields, digest })\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value)\n for (const child of Object.values(value)) deepFreeze(child)\n }\n return value\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Builtin } from "../environment.mjs";
|
|
2
|
+
//#region src/builtins/index.d.ts
|
|
3
|
+
export declare const toUpperCase: Builtin;
|
|
4
|
+
export declare const toLowerCase: Builtin;
|
|
5
|
+
export declare const trim: Builtin;
|
|
6
|
+
export declare const trimStart: Builtin;
|
|
7
|
+
export declare const trimEnd: Builtin;
|
|
8
|
+
export declare const startsWith: Builtin;
|
|
9
|
+
export declare const endsWith: Builtin;
|
|
10
|
+
export declare const includes: Builtin;
|
|
11
|
+
export declare const indexOf: Builtin;
|
|
12
|
+
export declare const lastIndexOf: Builtin;
|
|
13
|
+
export declare const slice: Builtin;
|
|
14
|
+
export declare const split: Builtin;
|
|
15
|
+
export declare const replace: Builtin;
|
|
16
|
+
export declare const replaceAll: Builtin;
|
|
17
|
+
export declare const padStart: Builtin;
|
|
18
|
+
export declare const padEnd: Builtin;
|
|
19
|
+
export declare const repeat: Builtin;
|
|
20
|
+
export declare const at: Builtin;
|
|
21
|
+
export declare const matches: Builtin;
|
|
22
|
+
export declare const toString: Builtin;
|
|
23
|
+
export declare const toNumber: Builtin;
|
|
24
|
+
export declare const round: Builtin;
|
|
25
|
+
export declare const floor: Builtin;
|
|
26
|
+
export declare const ceil: Builtin;
|
|
27
|
+
export declare const trunc: Builtin;
|
|
28
|
+
export declare const abs: Builtin;
|
|
29
|
+
export declare const sqrt: Builtin;
|
|
30
|
+
export declare const clamp: Builtin;
|
|
31
|
+
export declare const toFixed: Builtin;
|
|
32
|
+
export declare const formatNumber: Builtin;
|
|
33
|
+
export declare const formatCurrency: Builtin;
|
|
34
|
+
export declare const min: Builtin;
|
|
35
|
+
export declare const max: Builtin;
|
|
36
|
+
export declare const sum: Builtin;
|
|
37
|
+
export declare const avg: Builtin;
|
|
38
|
+
export declare const map: Builtin;
|
|
39
|
+
export declare const filter: Builtin;
|
|
40
|
+
export declare const find: Builtin;
|
|
41
|
+
export declare const findIndex: Builtin;
|
|
42
|
+
export declare const some: Builtin;
|
|
43
|
+
export declare const every: Builtin;
|
|
44
|
+
export declare const none: Builtin;
|
|
45
|
+
export declare const flatMap: Builtin;
|
|
46
|
+
export declare const sortBy: Builtin;
|
|
47
|
+
export declare const groupBy: Builtin;
|
|
48
|
+
export declare const count: Builtin;
|
|
49
|
+
export declare const reduce: Builtin;
|
|
50
|
+
export declare const sort: Builtin;
|
|
51
|
+
export declare const reverse: Builtin;
|
|
52
|
+
export declare const unique: Builtin;
|
|
53
|
+
export declare const flat: Builtin;
|
|
54
|
+
export declare const first: Builtin;
|
|
55
|
+
export declare const last: Builtin;
|
|
56
|
+
export declare const join: Builtin;
|
|
57
|
+
export declare const isEmpty: Builtin;
|
|
58
|
+
export declare const keys: Builtin;
|
|
59
|
+
export declare const values: Builtin;
|
|
60
|
+
export declare const entries: Builtin;
|
|
61
|
+
export declare const type: Builtin;
|
|
62
|
+
export declare const now: Builtin;
|
|
63
|
+
export declare const timestamp: Builtin;
|
|
64
|
+
export declare const duration: Builtin;
|
|
65
|
+
export declare const weeks: Builtin;
|
|
66
|
+
export declare const days: Builtin;
|
|
67
|
+
export declare const hours: Builtin;
|
|
68
|
+
export declare const minutes: Builtin;
|
|
69
|
+
export declare const seconds: Builtin;
|
|
70
|
+
export declare const milliseconds: Builtin;
|
|
71
|
+
export declare const inDays: Builtin;
|
|
72
|
+
export declare const inHours: Builtin;
|
|
73
|
+
export declare const inMinutes: Builtin;
|
|
74
|
+
export declare const inSeconds: Builtin;
|
|
75
|
+
export declare const inMilliseconds: Builtin;
|
|
76
|
+
export declare const year: Builtin;
|
|
77
|
+
export declare const month: Builtin;
|
|
78
|
+
export declare const day: Builtin;
|
|
79
|
+
export declare const hour: Builtin;
|
|
80
|
+
export declare const minute: Builtin;
|
|
81
|
+
export declare const second: Builtin;
|
|
82
|
+
export declare const dayOfWeek: Builtin;
|
|
83
|
+
export declare const startOfDay: Builtin;
|
|
84
|
+
export declare const startOfMonth: Builtin;
|
|
85
|
+
export declare const startOfYear: Builtin;
|
|
86
|
+
export declare const addDays: Builtin;
|
|
87
|
+
export declare const addMonths: Builtin;
|
|
88
|
+
export declare const addYears: Builtin;
|
|
89
|
+
export declare const formatDate: Builtin;
|
|
90
|
+
//#endregion
|
|
91
|
+
export type { Builtin };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { abs as abs$1, addDays as addDays$1, addMonths as addMonths$1, addYears as addYears$1, at as at$1, avg as avg$1, ceil as ceil$1, clamp as clamp$1, count as count$1, day as day$1, dayOfWeek as dayOfWeek$1, days as days$1, duration as duration$1, endsWith as endsWith$1, entries as entries$1, every as every$1, filter as filter$1, find as find$1, findIndex as findIndex$1, first as first$1, flat as flat$1, flatMap as flatMap$1, floor as floor$1, formatCurrency as formatCurrency$1, formatDate as formatDate$1, formatNumber as formatNumber$1, groupBy as groupBy$1, hour as hour$1, hours as hours$1, inDays as inDays$1, inHours as inHours$1, inMilliseconds as inMilliseconds$1, inMinutes as inMinutes$1, inSeconds as inSeconds$1, includes as includes$1, indexOf as indexOf$1, isEmpty as isEmpty$1, join as join$1, keys as keys$1, last as last$1, lastIndexOf as lastIndexOf$1, map as map$1, matches as matches$1, max as max$1, milliseconds as milliseconds$1, min as min$1, minute as minute$1, minutes as minutes$1, month as month$1, none as none$1, now as now$1, padEnd as padEnd$1, padStart as padStart$1, reduce as reduce$1, repeat as repeat$1, replace as replace$1, replaceAll as replaceAll$1, reverse as reverse$1, round as round$1, second as second$1, seconds as seconds$1, slice as slice$1, some as some$1, sort as sort$1, sortBy as sortBy$1, split as split$1, sqrt as sqrt$1, startOfDay as startOfDay$1, startOfMonth as startOfMonth$1, startOfYear as startOfYear$1, startsWith as startsWith$1, sum as sum$1, timestamp as timestamp$1, toFixed as toFixed$1, toLowerCase as toLowerCase$1, toNumber as toNumber$1, toString as toString$1, toUpperCase as toUpperCase$1, trim as trim$1, trimEnd as trimEnd$1, trimStart as trimStart$1, trunc as trunc$1, type as type$1, unique as unique$1, values as values$1, weeks as weeks$1, year as year$1 } from "../functions/builtins.mjs";
|
|
2
|
+
//#region src/builtins/index.ts
|
|
3
|
+
const toUpperCase = toUpperCase$1;
|
|
4
|
+
const toLowerCase = toLowerCase$1;
|
|
5
|
+
const trim = trim$1;
|
|
6
|
+
const trimStart = trimStart$1;
|
|
7
|
+
const trimEnd = trimEnd$1;
|
|
8
|
+
const startsWith = startsWith$1;
|
|
9
|
+
const endsWith = endsWith$1;
|
|
10
|
+
const includes = includes$1;
|
|
11
|
+
const indexOf = indexOf$1;
|
|
12
|
+
const lastIndexOf = lastIndexOf$1;
|
|
13
|
+
const slice = slice$1;
|
|
14
|
+
const split = split$1;
|
|
15
|
+
const replace = replace$1;
|
|
16
|
+
const replaceAll = replaceAll$1;
|
|
17
|
+
const padStart = padStart$1;
|
|
18
|
+
const padEnd = padEnd$1;
|
|
19
|
+
const repeat = repeat$1;
|
|
20
|
+
const at = at$1;
|
|
21
|
+
const matches = matches$1;
|
|
22
|
+
const toString = toString$1;
|
|
23
|
+
const toNumber = toNumber$1;
|
|
24
|
+
const round = round$1;
|
|
25
|
+
const floor = floor$1;
|
|
26
|
+
const ceil = ceil$1;
|
|
27
|
+
const trunc = trunc$1;
|
|
28
|
+
const abs = abs$1;
|
|
29
|
+
const sqrt = sqrt$1;
|
|
30
|
+
const clamp = clamp$1;
|
|
31
|
+
const toFixed = toFixed$1;
|
|
32
|
+
const formatNumber = formatNumber$1;
|
|
33
|
+
const formatCurrency = formatCurrency$1;
|
|
34
|
+
const min = min$1;
|
|
35
|
+
const max = max$1;
|
|
36
|
+
const sum = sum$1;
|
|
37
|
+
const avg = avg$1;
|
|
38
|
+
const map = map$1;
|
|
39
|
+
const filter = filter$1;
|
|
40
|
+
const find = find$1;
|
|
41
|
+
const findIndex = findIndex$1;
|
|
42
|
+
const some = some$1;
|
|
43
|
+
const every = every$1;
|
|
44
|
+
const none = none$1;
|
|
45
|
+
const flatMap = flatMap$1;
|
|
46
|
+
const sortBy = sortBy$1;
|
|
47
|
+
const groupBy = groupBy$1;
|
|
48
|
+
const count = count$1;
|
|
49
|
+
const reduce = reduce$1;
|
|
50
|
+
const sort = sort$1;
|
|
51
|
+
const reverse = reverse$1;
|
|
52
|
+
const unique = unique$1;
|
|
53
|
+
const flat = flat$1;
|
|
54
|
+
const first = first$1;
|
|
55
|
+
const last = last$1;
|
|
56
|
+
const join = join$1;
|
|
57
|
+
const isEmpty = isEmpty$1;
|
|
58
|
+
const keys = keys$1;
|
|
59
|
+
const values = values$1;
|
|
60
|
+
const entries = entries$1;
|
|
61
|
+
const type = type$1;
|
|
62
|
+
const now = now$1;
|
|
63
|
+
const timestamp = timestamp$1;
|
|
64
|
+
const duration = duration$1;
|
|
65
|
+
const weeks = weeks$1;
|
|
66
|
+
const days = days$1;
|
|
67
|
+
const hours = hours$1;
|
|
68
|
+
const minutes = minutes$1;
|
|
69
|
+
const seconds = seconds$1;
|
|
70
|
+
const milliseconds = milliseconds$1;
|
|
71
|
+
const inDays = inDays$1;
|
|
72
|
+
const inHours = inHours$1;
|
|
73
|
+
const inMinutes = inMinutes$1;
|
|
74
|
+
const inSeconds = inSeconds$1;
|
|
75
|
+
const inMilliseconds = inMilliseconds$1;
|
|
76
|
+
const year = year$1;
|
|
77
|
+
const month = month$1;
|
|
78
|
+
const day = day$1;
|
|
79
|
+
const hour = hour$1;
|
|
80
|
+
const minute = minute$1;
|
|
81
|
+
const second = second$1;
|
|
82
|
+
const dayOfWeek = dayOfWeek$1;
|
|
83
|
+
const startOfDay = startOfDay$1;
|
|
84
|
+
const startOfMonth = startOfMonth$1;
|
|
85
|
+
const startOfYear = startOfYear$1;
|
|
86
|
+
const addDays = addDays$1;
|
|
87
|
+
const addMonths = addMonths$1;
|
|
88
|
+
const addYears = addYears$1;
|
|
89
|
+
const formatDate = formatDate$1;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { abs, addDays, addMonths, addYears, at, avg, ceil, clamp, count, day, dayOfWeek, days, duration, endsWith, entries, every, filter, find, findIndex, first, flat, flatMap, floor, formatCurrency, formatDate, formatNumber, groupBy, hour, hours, inDays, inHours, inMilliseconds, inMinutes, inSeconds, includes, indexOf, isEmpty, join, keys, last, lastIndexOf, map, matches, max, milliseconds, min, minute, minutes, month, none, now, padEnd, padStart, reduce, repeat, replace, replaceAll, reverse, round, second, seconds, slice, some, sort, sortBy, split, sqrt, startOfDay, startOfMonth, startOfYear, startsWith, sum, timestamp, toFixed, toLowerCase, toNumber, toString, toUpperCase, trim, trimEnd, trimStart, trunc, type, unique, values, weeks, year };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["defs.toUpperCase","defs.toLowerCase","defs.trim","defs.trimStart","defs.trimEnd","defs.startsWith","defs.endsWith","defs.includes","defs.indexOf","defs.lastIndexOf","defs.slice","defs.split","defs.replace","defs.replaceAll","defs.padStart","defs.padEnd","defs.repeat","defs.at","defs.matches","defs.toString","defs.toNumber","defs.round","defs.floor","defs.ceil","defs.trunc","defs.abs","defs.sqrt","defs.clamp","defs.toFixed","defs.formatNumber","defs.formatCurrency","defs.min","defs.max","defs.sum","defs.avg","defs.map","defs.filter","defs.find","defs.findIndex","defs.some","defs.every","defs.none","defs.flatMap","defs.sortBy","defs.groupBy","defs.count","defs.reduce","defs.sort","defs.reverse","defs.unique","defs.flat","defs.first","defs.last","defs.join","defs.isEmpty","defs.keys","defs.values","defs.entries","defs.type","defs.now","defs.timestamp","defs.duration","defs.weeks","defs.days","defs.hours","defs.minutes","defs.seconds","defs.milliseconds","defs.inDays","defs.inHours","defs.inMinutes","defs.inSeconds","defs.inMilliseconds","defs.year","defs.month","defs.day","defs.hour","defs.minute","defs.second","defs.dayOfWeek","defs.startOfDay","defs.startOfMonth","defs.startOfYear","defs.addDays","defs.addMonths","defs.addYears","defs.formatDate"],"sources":["../../src/builtins/index.ts"],"sourcesContent":["/**\n * `bonsai-js/builtins`: every built-in function as its own export, for the\n * `builtins` option of bonsai-js/runtime/core's runtime() and of bonsai(). A\n * bundle keeps only the built-ins it imports. Namespace imports avoid clashes\n * with names such as `map` and `keys` in your own code:\n *\n * ```ts\n * import * as b from 'bonsai-js/builtins'\n * const builtins = [b.includes, b.some, b.now, b.days]\n * ```\n *\n * It lists every built-in in src/functions/builtins.ts; tests/runtime-core.test.ts\n * checks that it names each one.\n */\nimport type { Builtin } from '../environment.js'\nimport * as defs from '../functions/builtins.js'\n\nexport const toUpperCase = defs.toUpperCase as unknown as Builtin\nexport const toLowerCase = defs.toLowerCase as unknown as Builtin\nexport const trim = defs.trim as unknown as Builtin\nexport const trimStart = defs.trimStart as unknown as Builtin\nexport const trimEnd = defs.trimEnd as unknown as Builtin\nexport const startsWith = defs.startsWith as unknown as Builtin\nexport const endsWith = defs.endsWith as unknown as Builtin\nexport const includes = defs.includes as unknown as Builtin\nexport const indexOf = defs.indexOf as unknown as Builtin\nexport const lastIndexOf = defs.lastIndexOf as unknown as Builtin\nexport const slice = defs.slice as unknown as Builtin\nexport const split = defs.split as unknown as Builtin\nexport const replace = defs.replace as unknown as Builtin\nexport const replaceAll = defs.replaceAll as unknown as Builtin\nexport const padStart = defs.padStart as unknown as Builtin\nexport const padEnd = defs.padEnd as unknown as Builtin\nexport const repeat = defs.repeat as unknown as Builtin\nexport const at = defs.at as unknown as Builtin\nexport const matches = defs.matches as unknown as Builtin\nexport const toString = defs.toString as unknown as Builtin\nexport const toNumber = defs.toNumber as unknown as Builtin\nexport const round = defs.round as unknown as Builtin\nexport const floor = defs.floor as unknown as Builtin\nexport const ceil = defs.ceil as unknown as Builtin\nexport const trunc = defs.trunc as unknown as Builtin\nexport const abs = defs.abs as unknown as Builtin\nexport const sqrt = defs.sqrt as unknown as Builtin\nexport const clamp = defs.clamp as unknown as Builtin\nexport const toFixed = defs.toFixed as unknown as Builtin\nexport const formatNumber = defs.formatNumber as unknown as Builtin\nexport const formatCurrency = defs.formatCurrency as unknown as Builtin\nexport const min = defs.min as unknown as Builtin\nexport const max = defs.max as unknown as Builtin\nexport const sum = defs.sum as unknown as Builtin\nexport const avg = defs.avg as unknown as Builtin\nexport const map = defs.map as unknown as Builtin\nexport const filter = defs.filter as unknown as Builtin\nexport const find = defs.find as unknown as Builtin\nexport const findIndex = defs.findIndex as unknown as Builtin\nexport const some = defs.some as unknown as Builtin\nexport const every = defs.every as unknown as Builtin\nexport const none = defs.none as unknown as Builtin\nexport const flatMap = defs.flatMap as unknown as Builtin\nexport const sortBy = defs.sortBy as unknown as Builtin\nexport const groupBy = defs.groupBy as unknown as Builtin\nexport const count = defs.count as unknown as Builtin\nexport const reduce = defs.reduce as unknown as Builtin\nexport const sort = defs.sort as unknown as Builtin\nexport const reverse = defs.reverse as unknown as Builtin\nexport const unique = defs.unique as unknown as Builtin\nexport const flat = defs.flat as unknown as Builtin\nexport const first = defs.first as unknown as Builtin\nexport const last = defs.last as unknown as Builtin\nexport const join = defs.join as unknown as Builtin\nexport const isEmpty = defs.isEmpty as unknown as Builtin\nexport const keys = defs.keys as unknown as Builtin\nexport const values = defs.values as unknown as Builtin\nexport const entries = defs.entries as unknown as Builtin\nexport const type = defs.type as unknown as Builtin\nexport const now = defs.now as unknown as Builtin\nexport const timestamp = defs.timestamp as unknown as Builtin\nexport const duration = defs.duration as unknown as Builtin\nexport const weeks = defs.weeks as unknown as Builtin\nexport const days = defs.days as unknown as Builtin\nexport const hours = defs.hours as unknown as Builtin\nexport const minutes = defs.minutes as unknown as Builtin\nexport const seconds = defs.seconds as unknown as Builtin\nexport const milliseconds = defs.milliseconds as unknown as Builtin\nexport const inDays = defs.inDays as unknown as Builtin\nexport const inHours = defs.inHours as unknown as Builtin\nexport const inMinutes = defs.inMinutes as unknown as Builtin\nexport const inSeconds = defs.inSeconds as unknown as Builtin\nexport const inMilliseconds = defs.inMilliseconds as unknown as Builtin\nexport const year = defs.year as unknown as Builtin\nexport const month = defs.month as unknown as Builtin\nexport const day = defs.day as unknown as Builtin\nexport const hour = defs.hour as unknown as Builtin\nexport const minute = defs.minute as unknown as Builtin\nexport const second = defs.second as unknown as Builtin\nexport const dayOfWeek = defs.dayOfWeek as unknown as Builtin\nexport const startOfDay = defs.startOfDay as unknown as Builtin\nexport const startOfMonth = defs.startOfMonth as unknown as Builtin\nexport const startOfYear = defs.startOfYear as unknown as Builtin\nexport const addDays = defs.addDays as unknown as Builtin\nexport const addMonths = defs.addMonths as unknown as Builtin\nexport const addYears = defs.addYears as unknown as Builtin\nexport const formatDate = defs.formatDate as unknown as Builtin\n\nexport type { Builtin } from '../environment.js'\n"],"mappings":";;AAiBA,MAAa,cAAcA;AAC3B,MAAa,cAAcC;AAC3B,MAAa,OAAOC;AACpB,MAAa,YAAYC;AACzB,MAAa,UAAUC;AACvB,MAAa,aAAaC;AAC1B,MAAa,WAAWC;AACxB,MAAa,WAAWC;AACxB,MAAa,UAAUC;AACvB,MAAa,cAAcC;AAC3B,MAAa,QAAQC;AACrB,MAAa,QAAQC;AACrB,MAAa,UAAUC;AACvB,MAAa,aAAaC;AAC1B,MAAa,WAAWC;AACxB,MAAa,SAASC;AACtB,MAAa,SAASC;AACtB,MAAa,KAAKC;AAClB,MAAa,UAAUC;AACvB,MAAa,WAAWC;AACxB,MAAa,WAAWC;AACxB,MAAa,QAAQC;AACrB,MAAa,QAAQC;AACrB,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,MAAMC;AACnB,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,UAAUC;AACvB,MAAa,eAAeC;AAC5B,MAAa,iBAAiBC;AAC9B,MAAa,MAAMC;AACnB,MAAa,MAAMC;AACnB,MAAa,MAAMC;AACnB,MAAa,MAAMC;AACnB,MAAa,MAAMC;AACnB,MAAa,SAASC;AACtB,MAAa,OAAOC;AACpB,MAAa,YAAYC;AACzB,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,OAAOC;AACpB,MAAa,UAAUC;AACvB,MAAa,SAASC;AACtB,MAAa,UAAUC;AACvB,MAAa,QAAQC;AACrB,MAAa,SAASC;AACtB,MAAa,OAAOC;AACpB,MAAa,UAAUC;AACvB,MAAa,SAASC;AACtB,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,OAAOC;AACpB,MAAa,OAAOC;AACpB,MAAa,UAAUC;AACvB,MAAa,OAAOC;AACpB,MAAa,SAASC;AACtB,MAAa,UAAUC;AACvB,MAAa,OAAOC;AACpB,MAAa,MAAMC;AACnB,MAAa,YAAYC;AACzB,MAAa,WAAWC;AACxB,MAAa,QAAQC;AACrB,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,UAAUC;AACvB,MAAa,UAAUC;AACvB,MAAa,eAAeC;AAC5B,MAAa,SAASC;AACtB,MAAa,UAAUC;AACvB,MAAa,YAAYC;AACzB,MAAa,YAAYC;AACzB,MAAa,iBAAiBC;AAC9B,MAAa,OAAOC;AACpB,MAAa,QAAQC;AACrB,MAAa,MAAMC;AACnB,MAAa,OAAOC;AACpB,MAAa,SAASC;AACtB,MAAa,SAASC;AACtB,MAAa,YAAYC;AACzB,MAAa,aAAaC;AAC1B,MAAa,eAAeC;AAC5B,MAAa,cAAcC;AAC3B,MAAa,UAAUC;AACvB,MAAa,YAAYC;AACzB,MAAa,WAAWC;AACxB,MAAa,aAAaC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { isItemLambdaPosition, isLambdaPosition } from "../functions/define.mjs";
|
|
2
|
+
import { forEachChild, mapChildren } from "../syntax/ast.mjs";
|
|
3
|
+
//#region src/check/bind.ts
|
|
4
|
+
/**
|
|
5
|
+
* Binds the implicit parameter: an argument in a lambda position of the called
|
|
6
|
+
* function (per its overloads, see isLambdaPosition) that contains a free `.`
|
|
7
|
+
* becomes an implicit Lambda. Depends only on the tree and the signatures of
|
|
8
|
+
* the functions it calls, so the checker and code that compiles without it
|
|
9
|
+
* bind every source the same way.
|
|
10
|
+
*/
|
|
11
|
+
function bindImplicit(root, lookup) {
|
|
12
|
+
const problems = [];
|
|
13
|
+
if (!containsIt(root)) return {
|
|
14
|
+
root,
|
|
15
|
+
problems
|
|
16
|
+
};
|
|
17
|
+
/** Rewrites implicit lambdas as explicit ones; `free` when a `.` is left unbound. */
|
|
18
|
+
function bind(node) {
|
|
19
|
+
if (node.type === "It") return {
|
|
20
|
+
node,
|
|
21
|
+
free: true
|
|
22
|
+
};
|
|
23
|
+
if (node.type === "Call") return bindCall(node);
|
|
24
|
+
let free = false;
|
|
25
|
+
return {
|
|
26
|
+
node: mapChildren(node, (child) => {
|
|
27
|
+
const r = bind(child);
|
|
28
|
+
free ||= r.free;
|
|
29
|
+
return r.node;
|
|
30
|
+
}),
|
|
31
|
+
free: node.type === "Lambda" ? false : free
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function bindCall(node) {
|
|
35
|
+
const def = lookup(node.name);
|
|
36
|
+
let free = false;
|
|
37
|
+
let sawSpread = false;
|
|
38
|
+
const args = node.args.map((arg, index) => {
|
|
39
|
+
if (arg.type === "Spread") {
|
|
40
|
+
sawSpread = true;
|
|
41
|
+
const r = bind(arg.argument);
|
|
42
|
+
free ||= r.free;
|
|
43
|
+
return {
|
|
44
|
+
...arg,
|
|
45
|
+
argument: r.node
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const lambdaPosition = !sawSpread && (def === void 0 || isLambdaPosition(def, index));
|
|
49
|
+
if (arg.type === "Lambda") {
|
|
50
|
+
const body = bind(arg.body);
|
|
51
|
+
if (body.free) problems.push({
|
|
52
|
+
message: "Use the lambda parameter instead of \".\" inside an explicit lambda",
|
|
53
|
+
at: findIt(body.node) ?? arg
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
...arg,
|
|
57
|
+
body: body.node
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const r = bind(arg);
|
|
61
|
+
if (lambdaPosition && r.free) {
|
|
62
|
+
if (def !== void 0 && !isItemLambdaPosition(def, index)) problems.push({
|
|
63
|
+
message: `"." is the current item, but the first parameter of ${def.name}'s lambda is not; write the parameters, e.g. (acc, x) => acc + x`,
|
|
64
|
+
at: findIt(r.node) ?? arg
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
type: "Lambda",
|
|
68
|
+
params: [],
|
|
69
|
+
implicit: true,
|
|
70
|
+
body: r.node,
|
|
71
|
+
start: r.node.start,
|
|
72
|
+
end: r.node.end
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
free ||= r.free;
|
|
76
|
+
return r.node;
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
node: {
|
|
80
|
+
...node,
|
|
81
|
+
args
|
|
82
|
+
},
|
|
83
|
+
free
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const bound = bind(root);
|
|
87
|
+
if (bound.free) problems.push({
|
|
88
|
+
message: "\".\" (the current item) must be inside an argument of a function that takes a lambda, e.g. items.filter(.active)",
|
|
89
|
+
at: findIt(bound.node) ?? bound.node
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
root: bound.node,
|
|
93
|
+
problems
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function containsIt(node) {
|
|
97
|
+
if (node.type === "It") return true;
|
|
98
|
+
let found = false;
|
|
99
|
+
forEachChild(node, (child) => {
|
|
100
|
+
if (!found && containsIt(child)) found = true;
|
|
101
|
+
});
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
function findIt(node) {
|
|
105
|
+
if (node.type === "It") return node;
|
|
106
|
+
let found;
|
|
107
|
+
const visit = (child) => {
|
|
108
|
+
found ??= findIt(child);
|
|
109
|
+
};
|
|
110
|
+
if (node.type === "Lambda") return void 0;
|
|
111
|
+
forEachChild(node, visit);
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { bindImplicit };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=bind.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.mjs","names":[],"sources":["../../src/check/bind.ts"],"sourcesContent":["import { isItemLambdaPosition, isLambdaPosition, type FunctionDef } from '../functions/define.js'\nimport {\n forEachChild,\n mapChildren,\n type CallNode,\n type LambdaNode,\n type Node,\n type SpreadNode,\n} from '../syntax/ast.js'\n\n/** A `.` that binds to no lambda, or to the wrong parameter: an INVALID_LAMBDA diagnostic. */\nexport interface BindProblem {\n readonly message: string\n readonly at: { readonly start: number; readonly end: number }\n}\n\nexport interface Bound {\n /** The tree with implicit lambdas made explicit. */\n readonly root: Node\n /** In the order the checker reports them. */\n readonly problems: readonly BindProblem[]\n}\n\n/**\n * Binds the implicit parameter: an argument in a lambda position of the called\n * function (per its overloads, see isLambdaPosition) that contains a free `.`\n * becomes an implicit Lambda. Depends only on the tree and the signatures of\n * the functions it calls, so the checker and code that compiles without it\n * bind every source the same way.\n */\nexport function bindImplicit(root: Node, lookup: (name: string) => FunctionDef | undefined): Bound {\n const problems: BindProblem[] = []\n // Binding only rewrites the tree around `.`; most expressions have none.\n if (!containsIt(root)) return { root, problems }\n\n /** Rewrites implicit lambdas as explicit ones; `free` when a `.` is left unbound. */\n function bind(node: Node): { node: Node; free: boolean } {\n if (node.type === 'It') return { node, free: true }\n if (node.type === 'Call') return bindCall(node)\n let free = false\n const out = mapChildren(node, (child) => {\n const r = bind(child)\n free ||= r.free\n return r.node\n })\n // A lambda outside a call argument position (the parser only allows\n // arguments, so this is a lambda in a non-function parameter).\n return { node: out, free: node.type === 'Lambda' ? false : free }\n }\n\n function bindCall(node: CallNode): { node: Node; free: boolean } {\n const def = lookup(node.name)\n let free = false\n let sawSpread = false\n const args = node.args.map((arg, index): Node | SpreadNode => {\n if (arg.type === 'Spread') {\n sawSpread = true\n const r = bind(arg.argument)\n free ||= r.free\n return { ...arg, argument: r.node }\n }\n // An unknown function reports itself; treat its arguments as lambdas so a\n // misspelled filter(.x) does not also report a stray `.`.\n const lambdaPosition = !sawSpread && (def === undefined || isLambdaPosition(def, index))\n if (arg.type === 'Lambda') {\n const body = bind(arg.body)\n if (body.free) {\n problems.push({\n message: 'Use the lambda parameter instead of \".\" inside an explicit lambda',\n at: findIt(body.node) ?? arg,\n })\n }\n return { ...arg, body: body.node }\n }\n const r = bind(arg)\n if (lambdaPosition && r.free) {\n if (def !== undefined && !isItemLambdaPosition(def, index)) {\n problems.push({\n message: `\".\" is the current item, but the first parameter of ${def.name}'s lambda is not; write the parameters, e.g. (acc, x) => acc + x`,\n at: findIt(r.node) ?? arg,\n })\n }\n const lambda: LambdaNode = {\n type: 'Lambda',\n params: [],\n implicit: true,\n body: r.node,\n start: r.node.start,\n end: r.node.end,\n }\n return lambda\n }\n free ||= r.free\n return r.node\n })\n return { node: { ...node, args }, free }\n }\n\n const bound = bind(root)\n if (bound.free) {\n problems.push({\n message:\n '\".\" (the current item) must be inside an argument of a function that takes a lambda, e.g. items.filter(.active)',\n at: findIt(bound.node) ?? bound.node,\n })\n }\n return { root: bound.node, problems }\n}\n\nfunction containsIt(node: Node): boolean {\n if (node.type === 'It') return true\n let found = false\n forEachChild(node, (child) => {\n if (!found && containsIt(child)) found = true\n })\n return found\n}\n\nfunction findIt(node: Node): Node | undefined {\n if (node.type === 'It') return node\n let found: Node | undefined\n const visit = (child: Node): void => {\n found ??= findIt(child)\n }\n if (node.type === 'Lambda') return undefined\n forEachChild(node, visit)\n return found\n}\n"],"mappings":";;;;;;;;;;AA8BA,SAAgB,aAAa,MAAY,QAA0D;CACjG,MAAM,WAA0B,CAAC;CAEjC,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO;EAAE;EAAM;CAAS;;CAG/C,SAAS,KAAK,MAA2C;EACvD,IAAI,KAAK,SAAS,MAAM,OAAO;GAAE;GAAM,MAAM;EAAK;EAClD,IAAI,KAAK,SAAS,QAAQ,OAAO,SAAS,IAAI;EAC9C,IAAI,OAAO;EAQX,OAAO;GAAE,MAPG,YAAY,OAAO,UAAU;IACvC,MAAM,IAAI,KAAK,KAAK;IACpB,SAAS,EAAE;IACX,OAAO,EAAE;GACX,CAGiB;GAAG,MAAM,KAAK,SAAS,WAAW,QAAQ;EAAK;CAClE;CAEA,SAAS,SAAS,MAA+C;EAC/D,MAAM,MAAM,OAAO,KAAK,IAAI;EAC5B,IAAI,OAAO;EACX,IAAI,YAAY;EAChB,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,UAA6B;GAC5D,IAAI,IAAI,SAAS,UAAU;IACzB,YAAY;IACZ,MAAM,IAAI,KAAK,IAAI,QAAQ;IAC3B,SAAS,EAAE;IACX,OAAO;KAAE,GAAG;KAAK,UAAU,EAAE;IAAK;GACpC;GAGA,MAAM,iBAAiB,CAAC,cAAc,QAAQ,KAAA,KAAa,iBAAiB,KAAK,KAAK;GACtF,IAAI,IAAI,SAAS,UAAU;IACzB,MAAM,OAAO,KAAK,IAAI,IAAI;IAC1B,IAAI,KAAK,MACP,SAAS,KAAK;KACZ,SAAS;KACT,IAAI,OAAO,KAAK,IAAI,KAAK;IAC3B,CAAC;IAEH,OAAO;KAAE,GAAG;KAAK,MAAM,KAAK;IAAK;GACnC;GACA,MAAM,IAAI,KAAK,GAAG;GAClB,IAAI,kBAAkB,EAAE,MAAM;IAC5B,IAAI,QAAQ,KAAA,KAAa,CAAC,qBAAqB,KAAK,KAAK,GACvD,SAAS,KAAK;KACZ,SAAS,uDAAuD,IAAI,KAAK;KACzE,IAAI,OAAO,EAAE,IAAI,KAAK;IACxB,CAAC;IAUH,OAAO;KAPL,MAAM;KACN,QAAQ,CAAC;KACT,UAAU;KACV,MAAM,EAAE;KACR,OAAO,EAAE,KAAK;KACd,KAAK,EAAE,KAAK;IAEF;GACd;GACA,SAAS,EAAE;GACX,OAAO,EAAE;EACX,CAAC;EACD,OAAO;GAAE,MAAM;IAAE,GAAG;IAAM;GAAK;GAAG;EAAK;CACzC;CAEA,MAAM,QAAQ,KAAK,IAAI;CACvB,IAAI,MAAM,MACR,SAAS,KAAK;EACZ,SACE;EACF,IAAI,OAAO,MAAM,IAAI,KAAK,MAAM;CAClC,CAAC;CAEH,OAAO;EAAE,MAAM,MAAM;EAAM;CAAS;AACtC;AAEA,SAAS,WAAW,MAAqB;CACvC,IAAI,KAAK,SAAS,MAAM,OAAO;CAC/B,IAAI,QAAQ;CACZ,aAAa,OAAO,UAAU;EAC5B,IAAI,CAAC,SAAS,WAAW,KAAK,GAAG,QAAQ;CAC3C,CAAC;CACD,OAAO;AACT;AAEA,SAAS,OAAO,MAA8B;CAC5C,IAAI,KAAK,SAAS,MAAM,OAAO;CAC/B,IAAI;CACJ,MAAM,SAAS,UAAsB;EACnC,UAAU,OAAO,KAAK;CACxB;CACA,IAAI,KAAK,SAAS,UAAU,OAAO,KAAA;CACnC,aAAa,MAAM,KAAK;CACxB,OAAO;AACT"}
|