@weborigami/language 0.5.4 → 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 (97) 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 +3 -3
  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/errors.js +2 -2
  53. package/src/runtime/evaluate.js +15 -8
  54. package/src/runtime/expressionFunction.js +5 -7
  55. package/src/runtime/expressionObject.js +10 -20
  56. package/src/runtime/functionResultsMap.js +5 -12
  57. package/src/runtime/{handlers.js → handleExtension.js} +14 -12
  58. package/src/runtime/mergeTrees.js +2 -10
  59. package/src/runtime/ops.js +91 -106
  60. package/test/compiler/compile.test.js +20 -19
  61. package/test/compiler/optimize.test.js +60 -25
  62. package/test/compiler/parse.test.js +10 -10
  63. package/test/generator/oriEval.js +4 -5
  64. package/test/handlers/csv.handler.test.js +36 -0
  65. package/test/handlers/fixtures/add.wasm +0 -0
  66. package/test/handlers/fixtures/exif.jpeg +0 -0
  67. package/test/handlers/fixtures/frontMatter.md +5 -0
  68. package/test/handlers/fixtures/list.js +4 -0
  69. package/test/handlers/fixtures/multiple.js +4 -0
  70. package/test/handlers/fixtures/obj.js +3 -0
  71. package/test/handlers/fixtures/site.ori +5 -0
  72. package/test/handlers/fixtures/string.js +1 -0
  73. package/test/handlers/fixtures/tag.yaml +5 -0
  74. package/test/handlers/fixtures/test.ori +9 -0
  75. package/test/handlers/jpeg.handler.test.js +18 -0
  76. package/test/handlers/js.handler.test.js +46 -0
  77. package/test/handlers/json.handler.test.js +14 -0
  78. package/test/handlers/ori.handler.test.js +87 -0
  79. package/test/handlers/oridocument.handler.test.js +68 -0
  80. package/test/handlers/txt.handler.test.js +41 -0
  81. package/test/handlers/wasm.handler.test.js +20 -0
  82. package/test/handlers/yaml.handler.test.js +17 -0
  83. package/test/project/fixtures/withConfig/config.ori +4 -0
  84. package/test/project/fixtures/withConfig/subfolder/greet.js +1 -0
  85. package/test/project/fixtures/withPackageJson/package.json +0 -0
  86. package/test/project/jsGlobals.test.js +21 -0
  87. package/test/project/projectConfig.test.js +28 -0
  88. package/test/project/projectRoot.test.js +40 -0
  89. package/test/protocols/package.test.js +11 -0
  90. package/test/runtime/evaluate.test.js +26 -42
  91. package/test/runtime/expressionObject.test.js +16 -20
  92. package/test/runtime/functionResultsMap.test.js +5 -9
  93. package/test/runtime/{handlers.test.js → handleExtension.test.js} +4 -20
  94. package/test/runtime/jsGlobals.test.js +4 -6
  95. package/test/runtime/mergeTrees.test.js +2 -4
  96. package/test/runtime/ops.test.js +70 -72
  97. package/src/runtime/getHandlers.js +0 -10
@@ -6,8 +6,7 @@ import {
6
6
  trailingSlash,
7
7
  Tree,
8
8
  } from "@weborigami/async-tree";
9
- import getHandlers from "./getHandlers.js";
10
- import { handleExtension } from "./handlers.js";
9
+ import handleExtension from "./handleExtension.js";
11
10
  import { evaluate, ops } from "./internal.js";
12
11
 
13
12
  /**
@@ -25,11 +24,12 @@ import { evaluate, ops } from "./internal.js";
25
24
  * property getter on the object.
26
25
  *
27
26
  * @param {*} entries
28
- * @param {import("@weborigami/types").AsyncTree | null} parent
27
+ * @param {import("../../index.ts").RuntimeState} [state]
29
28
  */
30
- export default async function expressionObject(entries, parent) {
29
+ export default async function expressionObject(entries, state = {}) {
31
30
  // Create the object and set its parent
32
31
  const object = {};
32
+ const parent = state?.object ?? null;
33
33
  if (parent !== null && !Tree.isAsyncTree(parent)) {
34
34
  throw new TypeError(`Parent must be an AsyncTree or null`);
35
35
  }
@@ -86,22 +86,12 @@ export default async function expressionObject(entries, parent) {
86
86
  code = value;
87
87
  }
88
88
 
89
- let get;
90
- if (extname) {
91
- // Key has extension, getter will invoke code then attach unpack method
92
- get = async () => {
93
- tree ??= new ObjectTree(object);
94
- const result = await evaluate.call(tree, code);
95
- const handlers = getHandlers(tree);
96
- return handleExtension(tree, result, key, handlers);
97
- };
98
- } else {
99
- // No extension, so getter just invokes code.
100
- get = async () => {
101
- tree ??= new ObjectTree(object);
102
- return evaluate.call(tree, code);
103
- };
104
- }
89
+ const get = async () => {
90
+ tree ??= new ObjectTree(object);
91
+ const newState = Object.assign({}, state, { object: tree });
92
+ const result = await evaluate(code, newState);
93
+ return extname ? handleExtension(result, key, tree) : result;
94
+ };
105
95
 
106
96
  Object.defineProperty(object, key, {
107
97
  configurable: true,
@@ -1,23 +1,16 @@
1
- import { map, Tree } from "@weborigami/async-tree";
1
+ import { Tree } from "@weborigami/async-tree";
2
2
 
3
3
  /**
4
4
  * When using `get` to retrieve a value from a tree, if the value is a
5
5
  * function, invoke it and return the result.
6
6
  */
7
- export default function functionResultsMap(treelike) {
8
- return map(treelike, {
7
+ export default async function functionResultsMap(treelike) {
8
+ return Tree.map(treelike, {
9
9
  description: "functionResultsMap",
10
10
 
11
11
  value: async (sourceValue, sourceKey, tree) => {
12
- let resultValue;
13
- if (typeof sourceValue === "function") {
14
- resultValue = await sourceValue.call(tree);
15
- if (Tree.isAsyncTree(resultValue) && !resultValue.parent) {
16
- resultValue.parent = tree;
17
- }
18
- } else {
19
- resultValue = sourceValue;
20
- }
12
+ const resultValue =
13
+ typeof sourceValue === "function" ? await sourceValue() : sourceValue;
21
14
  return resultValue;
22
15
  },
23
16
  });
@@ -2,28 +2,27 @@ import {
2
2
  box,
3
3
  extension,
4
4
  isPacked,
5
- isStringLike,
5
+ isStringlike,
6
6
  isUnpackable,
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,10 +1,4 @@
1
- import {
2
- isPlainObject,
3
- isUnpackable,
4
- merge,
5
- setParent,
6
- Tree,
7
- } from "@weborigami/async-tree";
1
+ import { isPlainObject, isUnpackable, Tree } from "@weborigami/async-tree";
8
2
 
9
3
  /**
10
4
  * Create a tree that's the result of merging the given trees.
@@ -12,7 +6,6 @@ import {
12
6
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
13
7
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
14
8
  *
15
- * @this {AsyncTree|null}
16
9
  * @param {(Treelike|null)[]} trees
17
10
  */
18
11
  export default async function mergeTrees(...trees) {
@@ -54,7 +47,6 @@ export default async function mergeTrees(...trees) {
54
47
  }
55
48
 
56
49
  // Merge the trees.
57
- const result = merge(...unpacked);
58
- setParent(result, this);
50
+ const result = Tree.merge(...unpacked);
59
51
  return result;
60
52
  }
@@ -3,20 +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 {
9
- deepText,
10
- indent,
11
- isUnpackable,
12
- ObjectTree,
13
- scope as scopeFn,
14
- text,
15
- Tree,
16
- } from "@weborigami/async-tree";
9
+ import { isUnpackable, symbols, Tree } from "@weborigami/async-tree";
17
10
  import os from "node:os";
18
11
  import expressionObject from "./expressionObject.js";
19
- import getHandlers from "./getHandlers.js";
20
12
  import { evaluate } from "./internal.js";
21
13
  import mergeTrees from "./mergeTrees.js";
22
14
  import OrigamiFiles from "./OrigamiFiles.js";
@@ -37,7 +29,6 @@ addOpLabel(addition, "«ops.addition»");
37
29
  /**
38
30
  * Construct an array.
39
31
  *
40
- * @this {AsyncTree|null}
41
32
  * @param {any[]} items
42
33
  */
43
34
  export async function array(...items) {
@@ -68,7 +59,6 @@ addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
68
59
  /**
69
60
  * Cache the value of the code for an external reference
70
61
  *
71
- * @this {AsyncTree|null}
72
62
  * @param {any} cache
73
63
  * @param {string} path
74
64
  * @param {AnnotatedCode} code
@@ -80,7 +70,7 @@ export async function cache(cache, path, code) {
80
70
  }
81
71
 
82
72
  // Don't await: might get another request for this before promise resolves
83
- const promise = await evaluate.call(this, code);
73
+ const promise = await evaluate(code);
84
74
 
85
75
  // Save promise so another request will get the same promise
86
76
  cache[path] = promise;
@@ -99,13 +89,12 @@ cache.unevaluatedArgs = true;
99
89
  /**
100
90
  * JavaScript comma operator, returns the last argument.
101
91
  *
102
- * @this {AsyncTree|null}
103
92
  * @param {...AnnotatedCode} args
104
93
  */
105
94
  export async function comma(...args) {
106
95
  let result;
107
96
  for (const arg of args) {
108
- result = await evaluate.call(this, arg);
97
+ result = await evaluate(arg);
109
98
  }
110
99
  return result;
111
100
  }
@@ -115,11 +104,10 @@ comma.unevaluatedArgs = true;
115
104
  /**
116
105
  * Concatenate the given arguments.
117
106
  *
118
- * @this {AsyncTree|null}
119
107
  * @param {any[]} args
120
108
  */
121
109
  export async function concat(...args) {
122
- return deepText.call(this, args);
110
+ return Tree.deepText(args);
123
111
  }
124
112
  addOpLabel(concat, "«ops.concat»");
125
113
 
@@ -135,23 +123,6 @@ export async function construct(constructor, ...args) {
135
123
  return Reflect.construct(constructor, args);
136
124
  }
137
125
 
138
- /**
139
- * Return the nth parent of the current tree
140
- *
141
- * @this {AsyncTree|null|undefined}
142
- */
143
- export function context(n = 0) {
144
- let tree = this;
145
- for (let i = 0; i < n; i++) {
146
- if (!tree) {
147
- throw new Error("Internal error: couldn't find tree ancestor.");
148
- }
149
- tree = tree.parent;
150
- }
151
- return tree;
152
- }
153
- addOpLabel(context, "«ops.context»");
154
-
155
126
  export function division(a, b) {
156
127
  return a / b;
157
128
  }
@@ -203,65 +174,74 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
203
174
 
204
175
  /**
205
176
  * Files tree for the user's home directory.
206
- *
207
- * @this {AsyncTree|null}
208
177
  */
209
178
  export async function homeDirectory(...keys) {
210
179
  const tree = new OrigamiFiles(os.homedir());
211
- // Use the same handlers as the current tree
212
- /** @type {any} */ (tree).handlers = getHandlers(this);
213
180
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
214
181
  }
215
182
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
216
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
+
217
206
  /**
218
207
  * Return a function that will invoke the given code.
219
208
  *
220
- * @this {AsyncTree|null}
221
209
  * @param {string[]} parameters
222
210
  * @param {AnnotatedCode} code
223
211
  */
224
- export function lambda(parameters, code) {
225
- const context = this;
212
+ export function lambda(parameters, code, state = {}) {
213
+ const stack = state.stack ?? [];
226
214
 
227
- /** @this {Treelike|null} */
228
215
  async function invoke(...args) {
229
- let target;
216
+ let newState;
230
217
  if (parameters.length === 0) {
231
218
  // No parameters
232
- target = context;
219
+ newState = state;
233
220
  } else {
234
- // Add arguments to scope.
235
- const ambients = {};
221
+ // Create a stack frame for the parameters
222
+ const frame = {};
236
223
  for (const parameter of parameters) {
237
224
  const parameterName = parameter[1];
238
- ambients[parameterName] = args.shift();
225
+ frame[parameterName] = args.shift();
239
226
  }
240
- Object.defineProperty(ambients, codeSymbol, {
227
+ // Record which code this stack frame is associated with
228
+ Object.defineProperty(frame, codeSymbol, {
241
229
  value: code,
242
230
  enumerable: false,
243
231
  });
244
- const ambientTree = new ObjectTree(ambients);
245
- ambientTree.parent = context;
246
- target = ambientTree;
247
- }
248
-
249
- let result = await evaluate.call(target, code);
250
-
251
- // Bind a function result to the ambients so that it has access to the
252
- // parameter values -- i.e., like a closure.
253
- if (result instanceof Function) {
254
- const resultCode = result.code;
255
- result = result.bind(target);
256
- if (code) {
257
- // Copy over Origami code
258
- result.code = resultCode;
259
- }
232
+ const newStack = stack.slice();
233
+ newStack.push(frame);
234
+ newState = Object.assign({}, state, { stack: newStack });
260
235
  }
261
236
 
237
+ const result = await evaluate(code, newState);
262
238
  return result;
263
239
  }
264
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
+
265
245
  // We set the `length` property on the function so that Tree.traverseOrThrow()
266
246
  // will correctly identify how many parameters it wants. This is unorthodox
267
247
  // but doesn't appear to affect other behavior.
@@ -270,11 +250,11 @@ export function lambda(parameters, code) {
270
250
  value: fnLength,
271
251
  });
272
252
 
273
- invoke.code = code;
274
253
  return invoke;
275
254
  }
276
255
  addOpLabel(lambda, "«ops.lambda»");
277
256
  lambda.unevaluatedArgs = true;
257
+ lambda.needsState = true;
278
258
 
279
259
  export function lessThan(a, b) {
280
260
  return a < b;
@@ -356,11 +336,10 @@ addOpLabel(logicalOr, "«ops.logicalOr»");
356
336
  /**
357
337
  * Merge the given trees. If they are all plain objects, return a plain object.
358
338
  *
359
- * @this {AsyncTree|null}
360
339
  * @param {any[]} trees
361
340
  */
362
341
  export async function merge(...trees) {
363
- return mergeTrees.call(this, ...trees);
342
+ return mergeTrees(...trees);
364
343
  }
365
344
  addOpLabel(merge, "«ops.merge»");
366
345
 
@@ -404,59 +383,71 @@ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
404
383
  * parameter's, and the values will be the results of evaluating the
405
384
  * corresponding code values in `obj`.
406
385
  *
407
- * @this {AsyncTree|null}
408
386
  * @param {any[]} entries
409
387
  */
410
388
  export async function object(...entries) {
411
- return expressionObject(entries, this);
389
+ const state = entries.pop();
390
+ return expressionObject(entries, state);
412
391
  }
413
392
  addOpLabel(object, "«ops.object»");
414
393
  object.unevaluatedArgs = true;
394
+ object.needsState = true;
415
395
 
416
- export function optionalTraverse(treelike, key) {
417
- if (!treelike) {
418
- return undefined;
419
- }
420
- return Tree.traverseOrThrow(treelike, key);
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];
421
405
  }
422
- addOpLabel(optionalTraverse, "«ops.optionalTraverse");
406
+ addOpLabel(params, "«ops.params»");
407
+ params.needsState = true;
408
+
409
+ // export function optionalTraverse(treelike, key) {
410
+ // if (!treelike) {
411
+ // return undefined;
412
+ // }
413
+ // return Tree.traverseOrThrow(treelike, key);
414
+ // }
415
+ // addOpLabel(optionalTraverse, "«ops.optionalTraverse");
423
416
 
424
417
  /**
425
418
  * Return the indicated property
426
419
  *
427
- * @param {any} obj
420
+ * @param {any} object
428
421
  * @param {string} key
429
422
  */
430
- export async function property(obj, key) {
431
- if (obj == null) {
423
+ export async function property(object, key) {
424
+ if (object == null) {
432
425
  throw new ReferenceError();
433
426
  }
434
427
 
435
- if (isUnpackable(obj)) {
436
- obj = await obj.unpack();
437
- } else if (typeof obj === "string") {
438
- obj = new String(obj);
439
- } else if (typeof obj === "number") {
440
- 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);
441
434
  }
442
435
 
443
- if (key in obj) {
436
+ if (key in object) {
444
437
  // Object defines the property, get it
445
- let value = obj[key];
438
+ let value = object[key];
446
439
  // Is value an instance method? Copied from ObjectTree.
447
440
  const isInstanceMethod =
448
- !(obj instanceof Function) &&
449
- value instanceof Function &&
450
- !Object.hasOwn(obj, key);
441
+ value instanceof Function && !Object.hasOwn(object, key);
451
442
  if (isInstanceMethod) {
452
443
  // Bind it to the object
453
- value = value.bind(obj);
444
+ value = value.bind(object);
454
445
  }
455
446
  return value;
456
447
  }
457
448
 
458
449
  // Handle as tree traversal
459
- return Tree.traverseOrThrow(obj, key);
450
+ return Tree.traverseOrThrow(object, key);
460
451
  }
461
452
  addOpLabel(property, "«ops.property»");
462
453
 
@@ -467,13 +458,9 @@ addOpLabel(remainder, "«ops.remainder»");
467
458
 
468
459
  /**
469
460
  * Files tree for the filesystem root.
470
- *
471
- * @this {AsyncTree|null}
472
461
  */
473
462
  export async function rootDirectory(...keys) {
474
463
  const tree = new OrigamiFiles("/");
475
- // Use the same handlers as the current tree
476
- /** @type {any} */ (tree).handlers = getHandlers(this);
477
464
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
478
465
  }
479
466
  addOpLabel(rootDirectory, "«ops.rootDirectory»");
@@ -481,17 +468,15 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
481
468
  /**
482
469
  * Return the scope of the current tree
483
470
  *
484
- * @this {AsyncTree|null}
485
- * @param {AsyncTree|null} [context]
471
+ * @param {AsyncTree} parent
486
472
  */
487
- export async function scope(context) {
488
- if (context === undefined) {
489
- context = this;
490
- }
491
- if (!context) {
492
- 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
+ );
493
478
  }
494
- return scopeFn(context);
479
+ return Tree.scope(parent);
495
480
  }
496
481
  addOpLabel(scope, "«ops.scope»");
497
482
 
@@ -535,7 +520,7 @@ addOpLabel(subtraction, "«ops.subtraction»");
535
520
  * Apply the tree indent tagged template function.
536
521
  */
537
522
  export async function templateIndent(strings, ...values) {
538
- return indent(strings, ...values);
523
+ return Tree.indent(strings, ...values);
539
524
  }
540
525
  addOpLabel(templateIndent, "«ops.templateIndent»");
541
526
 
@@ -543,7 +528,7 @@ addOpLabel(templateIndent, "«ops.templateIndent»");
543
528
  * Apply the tree tagged template function.
544
529
  */
545
530
  export async function templateText(strings, ...values) {
546
- return text(strings, ...values);
531
+ return Tree.text(strings, ...values);
547
532
  }
548
533
  addOpLabel(templateText, "«ops.templateText»");
549
534
 
@@ -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
  }