@weborigami/language 0.5.5 → 0.5.6

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 (95) hide show
  1. package/index.ts +16 -6
  2. package/main.js +9 -4
  3. package/package.json +4 -3
  4. package/src/compiler/compile.js +10 -4
  5. package/src/compiler/optimize.js +115 -97
  6. package/src/compiler/origami.pegjs +1 -4
  7. package/src/compiler/parse.js +568 -588
  8. package/src/compiler/parserHelpers.js +2 -2
  9. package/src/handlers/css_handler.js +7 -0
  10. package/src/handlers/csv_handler.js +129 -0
  11. package/src/handlers/handlers.js +33 -0
  12. package/src/handlers/htm_handler.js +2 -0
  13. package/src/handlers/html_handler.js +7 -0
  14. package/src/handlers/jpeg_handler.js +62 -0
  15. package/src/handlers/jpg_handler.js +2 -0
  16. package/src/handlers/js_handler.js +51 -0
  17. package/src/handlers/json_handler.js +26 -0
  18. package/src/handlers/md_handler.js +7 -0
  19. package/src/handlers/mjs_handler.js +2 -0
  20. package/src/handlers/ori_handler.js +47 -0
  21. package/src/handlers/oridocument_handler.js +77 -0
  22. package/src/handlers/parseFrontMatter.js +16 -0
  23. package/src/handlers/ts_handler.js +1 -0
  24. package/src/handlers/txt_handler.js +108 -0
  25. package/src/handlers/wasm_handler.js +15 -0
  26. package/src/handlers/xhtml_handler.js +2 -0
  27. package/src/handlers/yaml_handler.js +33 -0
  28. package/src/handlers/yml_handler.js +2 -0
  29. package/src/project/builtins.js +5 -0
  30. package/src/project/coreGlobals.js +17 -0
  31. package/src/{runtime → project}/jsGlobals.js +3 -1
  32. package/src/project/projectConfig.js +36 -0
  33. package/src/project/projectGlobals.js +19 -0
  34. package/src/project/projectRoot.js +59 -0
  35. package/src/protocols/constructHref.js +20 -0
  36. package/src/protocols/constructSiteTree.js +26 -0
  37. package/src/protocols/explore.js +14 -0
  38. package/src/protocols/fetchAndHandleExtension.js +25 -0
  39. package/src/protocols/files.js +26 -0
  40. package/src/protocols/http.js +15 -0
  41. package/src/protocols/https.js +15 -0
  42. package/src/protocols/httpstree.js +14 -0
  43. package/src/protocols/httptree.js +14 -0
  44. package/src/protocols/node.js +13 -0
  45. package/src/protocols/package.js +67 -0
  46. package/src/protocols/protocolGlobals.js +12 -0
  47. package/src/protocols/protocols.js +8 -0
  48. package/src/runtime/EventTargetMixin.js +1 -1
  49. package/src/runtime/HandleExtensionsTransform.js +3 -12
  50. package/src/runtime/ImportModulesMixin.js +4 -10
  51. package/src/runtime/InvokeFunctionsTransform.js +1 -1
  52. package/src/runtime/evaluate.js +15 -8
  53. package/src/runtime/expressionFunction.js +5 -7
  54. package/src/runtime/expressionObject.js +10 -20
  55. package/src/runtime/functionResultsMap.js +1 -3
  56. package/src/runtime/{handlers.js → handleExtension.js} +13 -11
  57. package/src/runtime/mergeTrees.js +1 -8
  58. package/src/runtime/ops.js +83 -90
  59. package/test/compiler/compile.test.js +20 -19
  60. package/test/compiler/optimize.test.js +60 -25
  61. package/test/compiler/parse.test.js +4 -4
  62. package/test/generator/oriEval.js +4 -5
  63. package/test/handlers/csv.handler.test.js +36 -0
  64. package/test/handlers/fixtures/add.wasm +0 -0
  65. package/test/handlers/fixtures/exif.jpeg +0 -0
  66. package/test/handlers/fixtures/frontMatter.md +5 -0
  67. package/test/handlers/fixtures/list.js +4 -0
  68. package/test/handlers/fixtures/multiple.js +4 -0
  69. package/test/handlers/fixtures/obj.js +3 -0
  70. package/test/handlers/fixtures/site.ori +5 -0
  71. package/test/handlers/fixtures/string.js +1 -0
  72. package/test/handlers/fixtures/tag.yaml +5 -0
  73. package/test/handlers/fixtures/test.ori +9 -0
  74. package/test/handlers/jpeg.handler.test.js +18 -0
  75. package/test/handlers/js.handler.test.js +46 -0
  76. package/test/handlers/json.handler.test.js +14 -0
  77. package/test/handlers/ori.handler.test.js +87 -0
  78. package/test/handlers/oridocument.handler.test.js +68 -0
  79. package/test/handlers/txt.handler.test.js +41 -0
  80. package/test/handlers/wasm.handler.test.js +20 -0
  81. package/test/handlers/yaml.handler.test.js +17 -0
  82. package/test/project/fixtures/withConfig/config.ori +4 -0
  83. package/test/project/fixtures/withConfig/subfolder/greet.js +1 -0
  84. package/test/project/fixtures/withPackageJson/package.json +0 -0
  85. package/test/project/jsGlobals.test.js +21 -0
  86. package/test/project/projectConfig.test.js +28 -0
  87. package/test/project/projectRoot.test.js +40 -0
  88. package/test/protocols/package.test.js +11 -0
  89. package/test/runtime/evaluate.test.js +26 -42
  90. package/test/runtime/expressionObject.test.js +16 -20
  91. package/test/runtime/{handlers.test.js → handleExtension.test.js} +4 -20
  92. package/test/runtime/jsGlobals.test.js +4 -6
  93. package/test/runtime/mergeTrees.test.js +2 -4
  94. package/test/runtime/ops.test.js +66 -68
  95. package/src/runtime/getHandlers.js +0 -10
@@ -7,23 +7,22 @@ import {
7
7
  setParent,
8
8
  trailingSlash,
9
9
  } from "@weborigami/async-tree";
10
+ import globals from "../project/projectGlobals.js";
11
+
12
+ let projectGlobals;
10
13
 
11
14
  /**
12
15
  * If the given value is packed (e.g., buffer) and the key is a string-like path
13
16
  * that ends in an extension, search for a handler for that extension and, if
14
17
  * found, attach it to the value.
15
18
  *
16
- * @param {import("@weborigami/types").AsyncTree} parent
17
19
  * @param {any} value
18
20
  * @param {any} key
21
+ * @param {import("@weborigami/types").AsyncTree} [parent]
19
22
  */
20
- export async function handleExtension(parent, value, key, handlers) {
21
- if (
22
- handlers &&
23
- isPacked(value) &&
24
- isStringlike(key) &&
25
- value.unpack === undefined
26
- ) {
23
+ export default async function handleExtension(value, key, parent) {
24
+ projectGlobals ??= await globals();
25
+ if (isPacked(value) && isStringlike(key) && value.unpack === undefined) {
27
26
  const hasSlash = trailingSlash.has(key);
28
27
  if (hasSlash) {
29
28
  key = trailingSlash.remove(key);
@@ -38,8 +37,8 @@ export async function handleExtension(parent, value, key, handlers) {
38
37
  ? ".jsedocument"
39
38
  : extension.extname(key);
40
39
  if (extname) {
41
- const handlerName = `${extname.slice(1)}.handler`;
42
- let handler = await handlers[handlerName];
40
+ const handlerName = `${extname.slice(1)}_handler`;
41
+ let handler = await projectGlobals[handlerName];
43
42
  if (handler) {
44
43
  if (isUnpackable(handler)) {
45
44
  // The extension handler itself needs to be unpacked
@@ -57,7 +56,10 @@ export async function handleExtension(parent, value, key, handlers) {
57
56
  if (handler.mediaType) {
58
57
  value.mediaType = handler.mediaType;
59
58
  }
60
- setParent(value, parent);
59
+
60
+ if (parent) {
61
+ setParent(value, parent);
62
+ }
61
63
 
62
64
  const unpack = handler.unpack;
63
65
  if (unpack) {
@@ -1,9 +1,4 @@
1
- import {
2
- isPlainObject,
3
- isUnpackable,
4
- setParent,
5
- Tree,
6
- } from "@weborigami/async-tree";
1
+ import { isPlainObject, isUnpackable, Tree } from "@weborigami/async-tree";
7
2
 
8
3
  /**
9
4
  * Create a tree that's the result of merging the given trees.
@@ -11,7 +6,6 @@ import {
11
6
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
12
7
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
13
8
  *
14
- * @this {AsyncTree|null}
15
9
  * @param {(Treelike|null)[]} trees
16
10
  */
17
11
  export default async function mergeTrees(...trees) {
@@ -54,6 +48,5 @@ export default async function mergeTrees(...trees) {
54
48
 
55
49
  // Merge the trees.
56
50
  const result = Tree.merge(...unpacked);
57
- setParent(result, this);
58
51
  return result;
59
52
  }
@@ -3,12 +3,12 @@
3
3
  * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
4
4
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
5
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
+ * @typedef {import("../../index.ts").RuntimeState} RuntimeState
6
7
  */
7
8
 
8
- import { isUnpackable, ObjectTree, Tree } from "@weborigami/async-tree";
9
+ import { isUnpackable, symbols, Tree } from "@weborigami/async-tree";
9
10
  import os from "node:os";
10
11
  import expressionObject from "./expressionObject.js";
11
- import getHandlers from "./getHandlers.js";
12
12
  import { evaluate } from "./internal.js";
13
13
  import mergeTrees from "./mergeTrees.js";
14
14
  import OrigamiFiles from "./OrigamiFiles.js";
@@ -29,7 +29,6 @@ addOpLabel(addition, "«ops.addition»");
29
29
  /**
30
30
  * Construct an array.
31
31
  *
32
- * @this {AsyncTree|null}
33
32
  * @param {any[]} items
34
33
  */
35
34
  export async function array(...items) {
@@ -60,7 +59,6 @@ addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
60
59
  /**
61
60
  * Cache the value of the code for an external reference
62
61
  *
63
- * @this {AsyncTree|null}
64
62
  * @param {any} cache
65
63
  * @param {string} path
66
64
  * @param {AnnotatedCode} code
@@ -72,7 +70,7 @@ export async function cache(cache, path, code) {
72
70
  }
73
71
 
74
72
  // Don't await: might get another request for this before promise resolves
75
- const promise = await evaluate.call(this, code);
73
+ const promise = await evaluate(code);
76
74
 
77
75
  // Save promise so another request will get the same promise
78
76
  cache[path] = promise;
@@ -91,13 +89,12 @@ cache.unevaluatedArgs = true;
91
89
  /**
92
90
  * JavaScript comma operator, returns the last argument.
93
91
  *
94
- * @this {AsyncTree|null}
95
92
  * @param {...AnnotatedCode} args
96
93
  */
97
94
  export async function comma(...args) {
98
95
  let result;
99
96
  for (const arg of args) {
100
- result = await evaluate.call(this, arg);
97
+ result = await evaluate(arg);
101
98
  }
102
99
  return result;
103
100
  }
@@ -107,11 +104,10 @@ comma.unevaluatedArgs = true;
107
104
  /**
108
105
  * Concatenate the given arguments.
109
106
  *
110
- * @this {AsyncTree|null}
111
107
  * @param {any[]} args
112
108
  */
113
109
  export async function concat(...args) {
114
- return Tree.deepText.call(this, args);
110
+ return Tree.deepText(args);
115
111
  }
116
112
  addOpLabel(concat, "«ops.concat»");
117
113
 
@@ -127,23 +123,6 @@ export async function construct(constructor, ...args) {
127
123
  return Reflect.construct(constructor, args);
128
124
  }
129
125
 
130
- /**
131
- * Return the nth parent of the current tree
132
- *
133
- * @this {AsyncTree|null|undefined}
134
- */
135
- export function context(n = 0) {
136
- let tree = this;
137
- for (let i = 0; i < n; i++) {
138
- if (!tree) {
139
- throw new Error("Internal error: couldn't find tree ancestor.");
140
- }
141
- tree = tree.parent;
142
- }
143
- return tree;
144
- }
145
- addOpLabel(context, "«ops.context»");
146
-
147
126
  export function division(a, b) {
148
127
  return a / b;
149
128
  }
@@ -195,65 +174,74 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
195
174
 
196
175
  /**
197
176
  * Files tree for the user's home directory.
198
- *
199
- * @this {AsyncTree|null}
200
177
  */
201
178
  export async function homeDirectory(...keys) {
202
179
  const tree = new OrigamiFiles(os.homedir());
203
- // Use the same handlers as the current tree
204
- /** @type {any} */ (tree).handlers = getHandlers(this);
205
180
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
206
181
  }
207
182
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
208
183
 
184
+ /**
185
+ * Given the tree currently be using as the context for the runtime, walk up the
186
+ * parent chain `depth` levels and return that tree.
187
+ *
188
+ * @param {number} depth
189
+ * @param {RuntimeState} state
190
+ */
191
+ export async function inherited(depth, state) {
192
+ let current = state.object;
193
+ for (let i = 0; i < depth; i++) {
194
+ if (!current) {
195
+ throw new ReferenceError(
196
+ `Origami internal error: Can't find context object`
197
+ );
198
+ }
199
+ current = current.parent ?? current[symbols.parent];
200
+ }
201
+ return current;
202
+ }
203
+ addOpLabel(inherited, "«ops.inherited»");
204
+ inherited.needsState = true;
205
+
209
206
  /**
210
207
  * Return a function that will invoke the given code.
211
208
  *
212
- * @this {AsyncTree|null}
213
209
  * @param {string[]} parameters
214
210
  * @param {AnnotatedCode} code
215
211
  */
216
- export function lambda(parameters, code) {
217
- const context = this;
212
+ export function lambda(parameters, code, state = {}) {
213
+ const stack = state.stack ?? [];
218
214
 
219
- /** @this {Treelike|null} */
220
215
  async function invoke(...args) {
221
- let target;
216
+ let newState;
222
217
  if (parameters.length === 0) {
223
218
  // No parameters
224
- target = context;
219
+ newState = state;
225
220
  } else {
226
- // Add arguments to scope.
227
- const ambients = {};
221
+ // Create a stack frame for the parameters
222
+ const frame = {};
228
223
  for (const parameter of parameters) {
229
224
  const parameterName = parameter[1];
230
- ambients[parameterName] = args.shift();
225
+ frame[parameterName] = args.shift();
231
226
  }
232
- Object.defineProperty(ambients, codeSymbol, {
227
+ // Record which code this stack frame is associated with
228
+ Object.defineProperty(frame, codeSymbol, {
233
229
  value: code,
234
230
  enumerable: false,
235
231
  });
236
- const ambientTree = new ObjectTree(ambients);
237
- ambientTree.parent = context;
238
- target = ambientTree;
239
- }
240
-
241
- let result = await evaluate.call(target, code);
242
-
243
- // Bind a function result to the ambients so that it has access to the
244
- // parameter values -- i.e., like a closure.
245
- if (result instanceof Function) {
246
- const resultCode = result.code;
247
- result = result.bind(target);
248
- if (code) {
249
- // Copy over Origami code
250
- result.code = resultCode;
251
- }
232
+ const newStack = stack.slice();
233
+ newStack.push(frame);
234
+ newState = Object.assign({}, state, { stack: newStack });
252
235
  }
253
236
 
237
+ const result = await evaluate(code, newState);
254
238
  return result;
255
239
  }
256
240
 
241
+ // Retain a reference to the original code for debugging, and so that functions
242
+ // can be compared by their code (e.g., for caching purposes).
243
+ invoke.code = code;
244
+
257
245
  // We set the `length` property on the function so that Tree.traverseOrThrow()
258
246
  // will correctly identify how many parameters it wants. This is unorthodox
259
247
  // but doesn't appear to affect other behavior.
@@ -262,11 +250,11 @@ export function lambda(parameters, code) {
262
250
  value: fnLength,
263
251
  });
264
252
 
265
- invoke.code = code;
266
253
  return invoke;
267
254
  }
268
255
  addOpLabel(lambda, "«ops.lambda»");
269
256
  lambda.unevaluatedArgs = true;
257
+ lambda.needsState = true;
270
258
 
271
259
  export function lessThan(a, b) {
272
260
  return a < b;
@@ -348,11 +336,10 @@ addOpLabel(logicalOr, "«ops.logicalOr»");
348
336
  /**
349
337
  * Merge the given trees. If they are all plain objects, return a plain object.
350
338
  *
351
- * @this {AsyncTree|null}
352
339
  * @param {any[]} trees
353
340
  */
354
341
  export async function merge(...trees) {
355
- return mergeTrees.call(this, ...trees);
342
+ return mergeTrees(...trees);
356
343
  }
357
344
  addOpLabel(merge, "«ops.merge»");
358
345
 
@@ -396,14 +383,28 @@ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
396
383
  * parameter's, and the values will be the results of evaluating the
397
384
  * corresponding code values in `obj`.
398
385
  *
399
- * @this {AsyncTree|null}
400
386
  * @param {any[]} entries
401
387
  */
402
388
  export async function object(...entries) {
403
- return expressionObject(entries, this);
389
+ const state = entries.pop();
390
+ return expressionObject(entries, state);
404
391
  }
405
392
  addOpLabel(object, "«ops.object»");
406
393
  object.unevaluatedArgs = true;
394
+ object.needsState = true;
395
+
396
+ /**
397
+ * Return the stack frame that's `depth` levels up the stack.
398
+ *
399
+ * @param {number} depth
400
+ * @param {RuntimeState} state
401
+ */
402
+ export async function params(depth, state = {}) {
403
+ const stack = state.stack ?? [];
404
+ return stack[stack.length - 1 - depth];
405
+ }
406
+ addOpLabel(params, "«ops.params»");
407
+ params.needsState = true;
407
408
 
408
409
  // export function optionalTraverse(treelike, key) {
409
410
  // if (!treelike) {
@@ -416,39 +417,37 @@ object.unevaluatedArgs = true;
416
417
  /**
417
418
  * Return the indicated property
418
419
  *
419
- * @param {any} obj
420
+ * @param {any} object
420
421
  * @param {string} key
421
422
  */
422
- export async function property(obj, key) {
423
- if (obj == null) {
423
+ export async function property(object, key) {
424
+ if (object == null) {
424
425
  throw new ReferenceError();
425
426
  }
426
427
 
427
- if (isUnpackable(obj)) {
428
- obj = await obj.unpack();
429
- } else if (typeof obj === "string") {
430
- obj = new String(obj);
431
- } else if (typeof obj === "number") {
432
- obj = new Number(obj);
428
+ if (isUnpackable(object)) {
429
+ object = await object.unpack();
430
+ } else if (typeof object === "string") {
431
+ object = new String(object);
432
+ } else if (typeof object === "number") {
433
+ object = new Number(object);
433
434
  }
434
435
 
435
- if (key in obj) {
436
+ if (key in object) {
436
437
  // Object defines the property, get it
437
- let value = obj[key];
438
+ let value = object[key];
438
439
  // Is value an instance method? Copied from ObjectTree.
439
440
  const isInstanceMethod =
440
- !(obj instanceof Function) &&
441
- value instanceof Function &&
442
- !Object.hasOwn(obj, key);
441
+ value instanceof Function && !Object.hasOwn(object, key);
443
442
  if (isInstanceMethod) {
444
443
  // Bind it to the object
445
- value = value.bind(obj);
444
+ value = value.bind(object);
446
445
  }
447
446
  return value;
448
447
  }
449
448
 
450
449
  // Handle as tree traversal
451
- return Tree.traverseOrThrow(obj, key);
450
+ return Tree.traverseOrThrow(object, key);
452
451
  }
453
452
  addOpLabel(property, "«ops.property»");
454
453
 
@@ -459,13 +458,9 @@ addOpLabel(remainder, "«ops.remainder»");
459
458
 
460
459
  /**
461
460
  * Files tree for the filesystem root.
462
- *
463
- * @this {AsyncTree|null}
464
461
  */
465
462
  export async function rootDirectory(...keys) {
466
463
  const tree = new OrigamiFiles("/");
467
- // Use the same handlers as the current tree
468
- /** @type {any} */ (tree).handlers = getHandlers(this);
469
464
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
470
465
  }
471
466
  addOpLabel(rootDirectory, "«ops.rootDirectory»");
@@ -473,17 +468,15 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
473
468
  /**
474
469
  * Return the scope of the current tree
475
470
  *
476
- * @this {AsyncTree|null}
477
- * @param {AsyncTree|null} [context]
471
+ * @param {AsyncTree} parent
478
472
  */
479
- export async function scope(context) {
480
- if (context === undefined) {
481
- context = this;
482
- }
483
- if (!context) {
484
- return null;
473
+ export async function scope(parent) {
474
+ if (!parent) {
475
+ throw new ReferenceError(
476
+ "Tried to find a value in scope, but no container was provided as the parent."
477
+ );
485
478
  }
486
- return Tree.scope(context);
479
+ return Tree.scope(parent);
487
480
  }
488
481
  addOpLabel(scope, "«ops.scope»");
489
482
 
@@ -1,10 +1,10 @@
1
- import { ObjectTree, Tree } from "@weborigami/async-tree";
1
+ import { Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import * as compile from "../../src/compiler/compile.js";
5
5
  import { assertCodeEqual } from "./codeHelpers.js";
6
6
 
7
- const sharedGlobals = {
7
+ const globals = {
8
8
  greet: (name) => `Hello, ${name}!`,
9
9
  name: "Alice",
10
10
  };
@@ -43,21 +43,24 @@ describe("compile", () => {
43
43
  );
44
44
  });
45
45
 
46
- test.skip("merge", async () => {
46
+ test("merge", async () => {
47
47
  {
48
- const scope = new ObjectTree({
48
+ const globals = {
49
49
  more: {
50
50
  b: 2,
51
51
  },
52
- });
53
- const fn = compile.expression(`
52
+ };
53
+ const fn = compile.expression(
54
+ `
54
55
  {
55
56
  a: 1
56
57
  ...more
57
58
  c: a
58
59
  }
59
- `);
60
- const result = await fn.call(scope);
60
+ `,
61
+ { globals }
62
+ );
63
+ const result = await fn();
61
64
  assert.deepEqual(await Tree.plain(result), {
62
65
  a: 1,
63
66
  b: 2,
@@ -78,10 +81,8 @@ describe("compile", () => {
78
81
  });
79
82
 
80
83
  test("async object", async () => {
81
- const fn = compile.expression("{ a: { b = name }}", {
82
- globals: sharedGlobals,
83
- });
84
- const object = await fn.call(null);
84
+ const fn = compile.expression("{ a: { b = name }}", { globals });
85
+ const object = await fn();
85
86
  assert.deepEqual(await object.a.b, "Alice");
86
87
  });
87
88
 
@@ -89,8 +90,8 @@ describe("compile", () => {
89
90
  const defineTemplateFn = compile.templateDocument(
90
91
  "Documents can contain ` backticks"
91
92
  );
92
- const templateFn = await defineTemplateFn.call(null);
93
- const value = await templateFn.call(null);
93
+ const templateFn = await defineTemplateFn();
94
+ const value = await templateFn();
94
95
  assert.deepEqual(value, "Documents can contain ` backticks");
95
96
  });
96
97
 
@@ -115,8 +116,8 @@ describe("compile", () => {
115
116
  return strings[0] + values[0] + strings[1];
116
117
  },
117
118
  };
118
- const program = compile.expression("=tag`Hello, ${_}!`", { globals });
119
- const lambda = await program.call(null);
119
+ const program = compile.expression("(_) => tag`Hello, ${_}!`", { globals });
120
+ const lambda = await program();
120
121
  const alice = await lambda("Alice");
121
122
  assert.equal(alice, "Hello, Alice!");
122
123
  const bob = await lambda("Bob");
@@ -126,9 +127,9 @@ describe("compile", () => {
126
127
 
127
128
  async function assertCompile(text, expected, options = {}) {
128
129
  const mode = options.mode ?? "program";
129
- const fn = compile.expression(text, { globals: sharedGlobals, mode });
130
- const target = options.target ?? null;
131
- let result = await fn.call(target);
130
+ const parent = options.target ?? null;
131
+ const fn = compile.expression(text, { globals, mode, parent });
132
+ let result = await fn();
132
133
  if (Tree.isTreelike(result)) {
133
134
  result = await Tree.plain(result);
134
135
  }