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/dist/check/checker.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { BonsaiLimitError } from "../errors.mjs";
|
|
2
2
|
import { BLOCKED_NAMES } from "../syntax/lexer.mjs";
|
|
3
|
-
import { TypeBudgetExceeded, chargeTypeWork, exactObject, fieldOf,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { TypeBudgetExceeded, chargeTypeWork, exactObject, fieldOf, freshLiteral, isAssignable, isExact, isNullable, isProvenAssignable, nonNull, overlaps, sameType, t, typeText, unionMembers, unionOf, widen, widenFresh, withFields, withTypeBudget } from "../types.mjs";
|
|
4
|
+
import { signatureText } from "../functions/define.mjs";
|
|
5
|
+
import { forEachChild } from "../syntax/ast.mjs";
|
|
6
|
+
import { bindImplicit } from "./bind.mjs";
|
|
7
|
+
import { BUILTINS, RESULT_REFINERS } from "../functions/builtins.mjs";
|
|
6
8
|
import { isStackOverflow, tooDeep } from "../runtime/overflow.mjs";
|
|
7
9
|
import { closest, didYouMean } from "../suggest.mjs";
|
|
8
|
-
import { forEachChild, mapChildren } from "../syntax/ast.mjs";
|
|
9
10
|
import { writtenWithExponent } from "../syntax/parser.mjs";
|
|
10
11
|
//#region src/check/checker.ts
|
|
11
12
|
function hasFact(facts, key) {
|
|
@@ -21,14 +22,6 @@ function hasFact(facts, key) {
|
|
|
21
22
|
* limits admit, small enough that a pathological one fails fast.
|
|
22
23
|
*/
|
|
23
24
|
const CHECK_BUDGET = 5e5;
|
|
24
|
-
function containsIt(node) {
|
|
25
|
-
if (node.type === "It") return true;
|
|
26
|
-
let found = false;
|
|
27
|
-
forEachChild(node, (child) => {
|
|
28
|
-
if (!found && containsIt(child)) found = true;
|
|
29
|
-
});
|
|
30
|
-
return found;
|
|
31
|
-
}
|
|
32
25
|
/** Whether a type explicitly admits null (unlike `any`, which admits everything). */
|
|
33
26
|
function mayBeNull(type) {
|
|
34
27
|
return type.kind === "null" || type.kind === "union" && type.types.some(mayBeNull);
|
|
@@ -393,85 +386,19 @@ function analyzeWithin(root, env, options) {
|
|
|
393
386
|
* this node writes the entries for its subtree.
|
|
394
387
|
*/
|
|
395
388
|
const callMemo = /* @__PURE__ */ new WeakMap();
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (bound.free) report("INVALID_LAMBDA", "\".\" (the current item) must be inside an argument of a function that takes a lambda, e.g. items.filter(.active)", findIt(bound.node) ?? bound.node);
|
|
401
|
-
/** Rewrites implicit lambdas as explicit ones; `free` when a `.` is left unbound. */
|
|
402
|
-
function bind(node) {
|
|
403
|
-
if (node.type === "It") return {
|
|
404
|
-
node,
|
|
405
|
-
free: true
|
|
406
|
-
};
|
|
407
|
-
if (node.type === "Call") return bindCall(node);
|
|
408
|
-
let free = false;
|
|
409
|
-
return {
|
|
410
|
-
node: mapChildren(node, (child) => {
|
|
411
|
-
const r = bind(child);
|
|
412
|
-
free ||= r.free;
|
|
413
|
-
return r.node;
|
|
414
|
-
}),
|
|
415
|
-
free: node.type === "Lambda" ? false : free
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function bindCall(node) {
|
|
419
|
-
const def = env.lookup(node.name);
|
|
420
|
-
let free = false;
|
|
421
|
-
let sawSpread = false;
|
|
422
|
-
const args = node.args.map((arg, index) => {
|
|
423
|
-
if (arg.type === "Spread") {
|
|
424
|
-
sawSpread = true;
|
|
425
|
-
const r = bind(arg.argument);
|
|
426
|
-
free ||= r.free;
|
|
427
|
-
return {
|
|
428
|
-
...arg,
|
|
429
|
-
argument: r.node
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
const lambdaPosition = !sawSpread && (def === void 0 || isLambdaPosition(def, index));
|
|
433
|
-
if (arg.type === "Lambda") {
|
|
434
|
-
const body = bind(arg.body);
|
|
435
|
-
if (body.free) report("INVALID_LAMBDA", "Use the lambda parameter instead of \".\" inside an explicit lambda", findIt(body.node) ?? arg);
|
|
436
|
-
return {
|
|
437
|
-
...arg,
|
|
438
|
-
body: body.node
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
const r = bind(arg);
|
|
442
|
-
if (lambdaPosition && r.free) {
|
|
443
|
-
if (def !== void 0 && !isItemLambdaPosition(def, index)) report("INVALID_LAMBDA", `"." is the current item, but the first parameter of ${def.name}'s lambda is not; write the parameters, e.g. (acc, x) => acc + x`, findIt(r.node) ?? arg);
|
|
444
|
-
return {
|
|
445
|
-
type: "Lambda",
|
|
446
|
-
params: [],
|
|
447
|
-
implicit: true,
|
|
448
|
-
body: r.node,
|
|
449
|
-
start: r.node.start,
|
|
450
|
-
end: r.node.end
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
free ||= r.free;
|
|
454
|
-
return r.node;
|
|
455
|
-
});
|
|
456
|
-
return {
|
|
457
|
-
node: {
|
|
458
|
-
...node,
|
|
459
|
-
args
|
|
460
|
-
},
|
|
461
|
-
free
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
const rootScope = {
|
|
389
|
+
const binding = bindImplicit(root, (name) => env.lookup(name));
|
|
390
|
+
for (const problem of binding.problems) report("INVALID_LAMBDA", problem.message, problem.at);
|
|
391
|
+
const boundRoot = binding.root;
|
|
392
|
+
const raw = check(boundRoot, {
|
|
465
393
|
locals: /* @__PURE__ */ new Map(),
|
|
466
394
|
it: void 0
|
|
467
|
-
};
|
|
468
|
-
const raw = check(bound.node, rootScope, options.expected);
|
|
395
|
+
}, options.expected);
|
|
469
396
|
let checkResult = false;
|
|
470
397
|
if (options.expected !== void 0) {
|
|
471
398
|
const problem = expectationProblem(raw, options.expected);
|
|
472
|
-
if (problem !== void 0) report("EXPECTED_TYPE", problem, resultSpan(
|
|
399
|
+
if (problem !== void 0) report("EXPECTED_TYPE", problem, resultSpan(boundRoot));
|
|
473
400
|
const extra = unexpectedFields(raw, options.expected);
|
|
474
|
-
if (extra !== void 0) report("EXPECTED_TYPE", extra, resultSpan(
|
|
401
|
+
if (extra !== void 0) report("EXPECTED_TYPE", extra, resultSpan(boundRoot), "warning");
|
|
475
402
|
checkResult = containsAny(raw) || !isProvenAssignable(raw, options.expected);
|
|
476
403
|
}
|
|
477
404
|
const type = widen(raw);
|
|
@@ -993,9 +920,25 @@ function analyzeWithin(root, env, options) {
|
|
|
993
920
|
return argType;
|
|
994
921
|
});
|
|
995
922
|
if (def === void 0) {
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
const
|
|
923
|
+
const excluded = BUILTINS.has(node.name);
|
|
924
|
+
let alias = !excluded && Object.hasOwn(FUNCTION_ALIASES, node.name) ? FUNCTION_ALIASES[node.name] : void 0;
|
|
925
|
+
const replacement = alias === void 0 ? void 0 : /"(?<name>[^"]+)"/u.exec(alias)?.groups?.name;
|
|
926
|
+
let leftOut;
|
|
927
|
+
if (replacement !== void 0 && env.lookup(replacement) === void 0) {
|
|
928
|
+
alias = void 0;
|
|
929
|
+
if (BUILTINS.has(replacement)) leftOut = replacement;
|
|
930
|
+
}
|
|
931
|
+
let suggestion;
|
|
932
|
+
let hint;
|
|
933
|
+
if (excluded) hint = `; ${node.name} is a built-in this environment does not include (add it to builtins)`;
|
|
934
|
+
else if (leftOut !== void 0) hint = `; the built-in for this is ${leftOut}, which this environment does not include (add it to builtins)`;
|
|
935
|
+
else if (alias === void 0) {
|
|
936
|
+
suggestion = suggest(node.name, () => env.functionNames());
|
|
937
|
+
hint = didYouMean(suggestion);
|
|
938
|
+
} else {
|
|
939
|
+
suggestion = replacement;
|
|
940
|
+
hint = `; ${alias}`;
|
|
941
|
+
}
|
|
999
942
|
report("UNKNOWN_FUNCTION", `Unknown function "${node.name}"${hint}`, {
|
|
1000
943
|
start: node.nameStart ?? node.start,
|
|
1001
944
|
end: node.nameEnd ?? node.end
|
|
@@ -1268,12 +1211,12 @@ function analyzeWithin(root, env, options) {
|
|
|
1268
1211
|
report("NO_OVERLOAD", `${node.name}(${shown}) does not match ${def.overloads.length > 1 ? "any of " : ""}${signatures}`, node);
|
|
1269
1212
|
}
|
|
1270
1213
|
return {
|
|
1271
|
-
root:
|
|
1214
|
+
root: boundRoot,
|
|
1272
1215
|
type,
|
|
1273
1216
|
diagnostics,
|
|
1274
1217
|
calls,
|
|
1275
1218
|
types,
|
|
1276
|
-
async: asyncNodes.has(
|
|
1219
|
+
async: asyncNodes.has(boundRoot),
|
|
1277
1220
|
checkResult,
|
|
1278
1221
|
references: {
|
|
1279
1222
|
variables: [...variables],
|
|
@@ -1504,22 +1447,6 @@ function substitute(type, b) {
|
|
|
1504
1447
|
default: return type;
|
|
1505
1448
|
}
|
|
1506
1449
|
}
|
|
1507
|
-
function signatureText(name, candidate) {
|
|
1508
|
-
const required = candidate.required ?? candidate.params.length;
|
|
1509
|
-
const params = candidate.params.map((param, i) => `${typeText(param)}${i >= required ? "?" : ""}`);
|
|
1510
|
-
if (candidate.rest !== void 0) params.push(`...${typeText(candidate.rest)}`);
|
|
1511
|
-
return `${name}(${params.join(", ")}): ${typeText(candidate.result)}`;
|
|
1512
|
-
}
|
|
1513
|
-
function findIt(node) {
|
|
1514
|
-
if (node.type === "It") return node;
|
|
1515
|
-
let found;
|
|
1516
|
-
const visit = (child) => {
|
|
1517
|
-
found ??= findIt(child);
|
|
1518
|
-
};
|
|
1519
|
-
if (node.type === "Lambda") return void 0;
|
|
1520
|
-
forEachChild(node, visit);
|
|
1521
|
-
return found;
|
|
1522
|
-
}
|
|
1523
1450
|
/** Names from other languages, with the Bonsai spelling. */
|
|
1524
1451
|
const FUNCTION_ALIASES = {
|
|
1525
1452
|
length: "length is a property: write x.length",
|
|
@@ -1566,19 +1493,6 @@ const JS_GLOBALS = {
|
|
|
1566
1493
|
String: "use toString(x)",
|
|
1567
1494
|
Array: "use a list literal [...]"
|
|
1568
1495
|
};
|
|
1569
|
-
/**
|
|
1570
|
-
* Longest type text in a diagnostic message. A message can name a large object
|
|
1571
|
-
* type, and a source can repeat the mistake thousands of times, so a short cap
|
|
1572
|
-
* keeps diagnostics (and their JSON) near the size of the source.
|
|
1573
|
-
*/
|
|
1574
|
-
const MESSAGE_TYPE_TEXT = 120;
|
|
1575
|
-
const typeTexts = /* @__PURE__ */ new WeakMap();
|
|
1576
|
-
/** A type's text for a diagnostic message, shortened with an ellipsis. */
|
|
1577
|
-
function typeText(type) {
|
|
1578
|
-
let text = typeTexts.get(type);
|
|
1579
|
-
if (text === void 0) typeTexts.set(type, text = formatType(type, MESSAGE_TYPE_TEXT));
|
|
1580
|
-
return text;
|
|
1581
|
-
}
|
|
1582
1496
|
/** Longest receiver path a diagnostic message names instead of its type. */
|
|
1583
1497
|
const MESSAGE_PATH_TEXT = 60;
|
|
1584
1498
|
/**
|
|
@@ -1632,6 +1546,6 @@ function suggest(name, candidatesOf) {
|
|
|
1632
1546
|
return closest(name, candidatesOf());
|
|
1633
1547
|
}
|
|
1634
1548
|
//#endregion
|
|
1635
|
-
export { acceptsArgument, analyze
|
|
1549
|
+
export { acceptsArgument, analyze };
|
|
1636
1550
|
|
|
1637
1551
|
//# sourceMappingURL=checker.mjs.map
|