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.
Files changed (60) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +24 -4
  3. package/dist/artifact.d.mts +47 -0
  4. package/dist/artifact.mjs +139 -0
  5. package/dist/artifact.mjs.map +1 -0
  6. package/dist/builtins/index.d.mts +91 -0
  7. package/dist/builtins/index.mjs +93 -0
  8. package/dist/builtins/index.mjs.map +1 -0
  9. package/dist/check/bind.mjs +117 -0
  10. package/dist/check/bind.mjs.map +1 -0
  11. package/dist/check/checker.mjs +34 -120
  12. package/dist/check/checker.mjs.map +1 -1
  13. package/dist/compile/compiler.mjs +2 -3
  14. package/dist/compile/compiler.mjs.map +1 -1
  15. package/dist/declared.mjs +8 -1
  16. package/dist/declared.mjs.map +1 -1
  17. package/dist/digest.mjs +109 -0
  18. package/dist/digest.mjs.map +1 -0
  19. package/dist/environment.d.mts +14 -1
  20. package/dist/environment.mjs +65 -315
  21. package/dist/environment.mjs.map +1 -1
  22. package/dist/errors.d.mts +21 -2
  23. package/dist/errors.mjs +17 -1
  24. package/dist/errors.mjs.map +1 -1
  25. package/dist/features.mjs +3 -3
  26. package/dist/features.mjs.map +1 -1
  27. package/dist/functions/builtins.mjs +551 -474
  28. package/dist/functions/builtins.mjs.map +1 -1
  29. package/dist/functions/define.mjs +53 -11
  30. package/dist/functions/define.mjs.map +1 -1
  31. package/dist/functions/descriptions.mjs +110 -0
  32. package/dist/functions/descriptions.mjs.map +1 -0
  33. package/dist/index.d.mts +4 -3
  34. package/dist/index.mjs +3 -2
  35. package/dist/lite/index.d.mts +4 -3
  36. package/dist/lite/index.mjs +3 -2
  37. package/dist/lite/index.mjs.map +1 -1
  38. package/dist/load.mjs +307 -0
  39. package/dist/load.mjs.map +1 -0
  40. package/dist/program.mjs +321 -0
  41. package/dist/program.mjs.map +1 -0
  42. package/dist/query/index.mjs +1 -1
  43. package/dist/runtime/core.d.mts +18 -0
  44. package/dist/runtime/core.mjs +31 -0
  45. package/dist/runtime/core.mjs.map +1 -0
  46. package/dist/runtime/create.d.mts +61 -0
  47. package/dist/runtime/create.mjs +76 -0
  48. package/dist/runtime/create.mjs.map +1 -0
  49. package/dist/runtime/index.d.mts +24 -0
  50. package/dist/runtime/index.mjs +34 -0
  51. package/dist/runtime/index.mjs.map +1 -0
  52. package/dist/service/index.mjs +12 -8
  53. package/dist/service/index.mjs.map +1 -1
  54. package/dist/syntax/parser.mjs +1 -1
  55. package/dist/syntax/printer.mjs +1 -1
  56. package/dist/types.mjs +103 -23
  57. package/dist/types.mjs.map +1 -1
  58. package/dist/version.mjs +7 -0
  59. package/dist/version.mjs.map +1 -0
  60. package/package.json +26 -2
@@ -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, formatType, freshLiteral, isAssignable, isExact, isNullable, isProvenAssignable, nonNull, overlaps, sameType, t, unionMembers, unionOf, widen, widenFresh, withFields, withTypeBudget } from "../types.mjs";
4
- import { isItemLambdaPosition, isLambdaPosition } from "../functions/define.mjs";
5
- import { RESULT_REFINERS } from "../functions/builtins.mjs";
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 bound = containsIt(root) ? bind(root) : {
397
- node: root,
398
- free: false
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(bound.node));
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(bound.node), "warning");
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 alias = Object.hasOwn(FUNCTION_ALIASES, node.name) ? FUNCTION_ALIASES[node.name] : void 0;
997
- const suggestion = alias === void 0 ? suggest(node.name, () => env.functionNames()) : /"(?<name>[^"]+)"/u.exec(alias)?.groups?.name;
998
- const hint = alias === void 0 ? didYouMean(suggestion) : `; ${alias}`;
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: bound.node,
1214
+ root: boundRoot,
1272
1215
  type,
1273
1216
  diagnostics,
1274
1217
  calls,
1275
1218
  types,
1276
- async: asyncNodes.has(bound.node),
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, signatureText };
1549
+ export { acceptsArgument, analyze };
1636
1550
 
1637
1551
  //# sourceMappingURL=checker.mjs.map