@weborigami/language 0.7.0-beta.1 → 0.7.0-beta.3

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 (43) hide show
  1. package/main.js +1 -0
  2. package/package.json +3 -2
  3. package/src/compiler/compile.js +0 -3
  4. package/src/compiler/optimize.js +56 -23
  5. package/src/compiler/parserHelpers.js +11 -8
  6. package/src/handlers/addExtensionKeyFn.js +18 -0
  7. package/src/handlers/epub_handler.js +54 -0
  8. package/src/handlers/getSource.js +1 -0
  9. package/src/handlers/handlers.js +2 -0
  10. package/src/handlers/ori_handler.js +2 -2
  11. package/src/handlers/oridocument_handler.js +5 -21
  12. package/src/handlers/{processOriExport.js → processOrigamiExport.js} +1 -1
  13. package/src/handlers/zip_handler.js +112 -0
  14. package/src/runtime/AsyncCacheTransform.d.ts +5 -1
  15. package/src/runtime/AsyncCacheTransform.js +22 -25
  16. package/src/runtime/OrigamiFileMap.js +3 -3
  17. package/src/runtime/SyncCacheTransform.d.ts +2 -1
  18. package/src/runtime/SyncCacheTransform.js +31 -34
  19. package/src/runtime/SystemCacheMap.js +28 -17
  20. package/src/runtime/counters.js +3 -0
  21. package/src/runtime/enableValueCaching.js +8 -7
  22. package/src/runtime/errors.js +33 -1
  23. package/src/runtime/execute.js +3 -0
  24. package/src/runtime/explainReferenceError.js +11 -24
  25. package/src/runtime/explainTraverseError.js +15 -6
  26. package/src/runtime/expressionObject.js +53 -34
  27. package/src/runtime/handleExtension.js +18 -9
  28. package/src/runtime/ops.js +4 -3
  29. package/test/compiler/compile.test.js +0 -1
  30. package/test/compiler/optimize.test.js +60 -15
  31. package/test/compiler/parse.test.js +38 -9
  32. package/test/handlers/epub_handler.test.js +27 -0
  33. package/test/handlers/fixtures/test.zip +0 -0
  34. package/test/handlers/zip_handler.test.js +45 -0
  35. package/test/runtime/AsyncCacheTransform.test.js +9 -4
  36. package/test/runtime/SyncCacheTransform.test.js +4 -0
  37. package/test/runtime/asyncCalcs.js +26 -4
  38. package/test/runtime/errors.test.js +4 -4
  39. package/test/runtime/expressionObject.test.js +23 -12
  40. package/test/runtime/handleExtension.test.js +0 -2
  41. package/test/runtime/ops.test.js +5 -3
  42. package/test/runtime/syncCalcs.js +26 -3
  43. package/test/runtime/systemCache.test.js +4 -0
@@ -45,28 +45,25 @@ export default function SyncCacheTransform(Base) {
45
45
  this.cache = systemCache;
46
46
  }
47
47
 
48
- get cachePath() {
49
- // @ts-ignore
50
- return this[cachePathSymbol];
51
- }
52
-
53
48
  cachePathForKey(key) {
54
49
  return key === "."
55
- ? this.cachePath
56
- : SystemCacheMap.joinPath(this.cachePath, key);
50
+ ? // @ts-ignore
51
+ this[cachePathSymbol]
52
+ : // @ts-ignore
53
+ SystemCacheMap.joinPath(this[cachePathSymbol], key);
57
54
  }
58
55
 
59
- delete(key) {
60
- const deleted = super.delete(key);
61
- if (typeof key === "string") {
62
- systemCache.delete(this.cachePathForKey(key));
63
- if (deleted) {
64
- // Deleted an existing key, need to invalidate cached keys
65
- this.invalidateKeys();
66
- }
67
- }
68
- return deleted;
69
- }
56
+ // delete(key) {
57
+ // const deleted = super.delete(key);
58
+ // if (typeof key === "string") {
59
+ // systemCache.delete(this.cachePathForKey(key));
60
+ // if (deleted) {
61
+ // // Deleted an existing key, need to invalidate cached keys
62
+ // this.invalidateKeys();
63
+ // }
64
+ // }
65
+ // return deleted;
66
+ // }
70
67
 
71
68
  get(key) {
72
69
  if (typeof key !== "string" || key.length === 0) {
@@ -113,21 +110,21 @@ export default function SyncCacheTransform(Base) {
113
110
  systemCache.delete(this.cachePathForKey(key));
114
111
  }
115
112
 
116
- set(key, value) {
117
- if (!this._self) {
118
- // Initializing in constructor
119
- super.set(key, value);
120
- return;
121
- }
122
- if (typeof key !== "string") {
123
- return super.set(key, value);
124
- }
125
- systemCache.delete(this.cachePathForKey(key));
126
- if (!this.has(key)) {
127
- // Adding a new key, need to invalidate cached keys
128
- this.invalidateKeys();
129
- }
130
- super.set(key, value);
131
- }
113
+ // set(key, value) {
114
+ // if (!this._self) {
115
+ // // Initializing in constructor
116
+ // super.set(key, value);
117
+ // return;
118
+ // }
119
+ // if (typeof key !== "string") {
120
+ // return super.set(key, value);
121
+ // }
122
+ // systemCache.delete(this.cachePathForKey(key));
123
+ // if (!this.has(key)) {
124
+ // // Adding a new key, need to invalidate cached keys
125
+ // this.invalidateKeys();
126
+ // }
127
+ // super.set(key, value);
128
+ // }
132
129
  };
133
130
  }
@@ -28,15 +28,16 @@ let nextPathId = 0;
28
28
  export default class SystemCacheMap extends SyncMap {
29
29
  delete(path) {
30
30
  // Find all entries that depend on this path directly or indirectly
31
- const toDelete = this.dependentEntries(path);
31
+ const dependentEntries = new Map();
32
+ this.visitDependentEntries(path, dependentEntries);
32
33
 
33
34
  // Delete those dependent entries
34
- for (const deletePath of toDelete.keys()) {
35
+ for (const deletePath of dependentEntries.keys()) {
35
36
  super.delete(deletePath);
36
37
  }
37
38
 
38
39
  // Remove deleted entries as being downstream from still-existing entries
39
- for (const [deletePath, deleteEntry] of toDelete.entries()) {
40
+ for (const [deletePath, deleteEntry] of dependentEntries.entries()) {
40
41
  for (const upstreamPath of deleteEntry.upstreams ?? []) {
41
42
  const upstreamEntry = this.get(upstreamPath);
42
43
  if (upstreamEntry?.downstreams) {
@@ -44,6 +45,14 @@ export default class SystemCacheMap extends SyncMap {
44
45
  if (upstreamEntry.downstreams.size === 0) {
45
46
  // No more downstream dependencies, clean up entry
46
47
  delete upstreamEntry.downstreams;
48
+ if (upstreamEntry.upstreams?.size === 0) {
49
+ // No more upstream dependencies either, clean up entry
50
+ delete upstreamEntry.upstreams;
51
+ if (!("value" in upstreamEntry)) {
52
+ // No more data in this entry, delete it
53
+ super.delete(upstreamPath);
54
+ }
55
+ }
47
56
  }
48
57
  }
49
58
  }
@@ -53,36 +62,38 @@ export default class SystemCacheMap extends SyncMap {
53
62
  }
54
63
 
55
64
  // Return all entries that directly or indirectly depend on the given path
56
- dependentEntries(path) {
57
- const result = new Map();
65
+ visitDependentEntries(path, dependentEntries) {
66
+ // The entries that will be added for the given path
67
+ const pathAdditions = new Map();
58
68
 
59
69
  const entry = this.get(path);
60
70
  if (entry) {
61
71
  // Path itself has an entry
62
- result.set(path, entry);
72
+ pathAdditions.set(path, entry);
63
73
  }
64
74
 
65
75
  // Add all entries with child paths that implicitly depend on this entry
76
+ // REVIEW: This is expensive: it iterates through the entire cache, and is
77
+ // done for each node visit.
66
78
  for (const [otherPath, otherEntry] of this.entries()) {
67
79
  if (this.isChildPath(path, otherPath)) {
68
- result.set(otherPath, otherEntry);
80
+ pathAdditions.set(otherPath, otherEntry);
69
81
  }
70
82
  }
71
83
 
72
- // For each entry, add all entries downstream of it
73
- for (const entry of result.values()) {
74
- for (const downstreamPath of entry.downstreams ?? []) {
75
- if (!result.has(downstreamPath)) {
76
- for (const [key, value] of this.dependentEntries(downstreamPath)) {
77
- if (!result.has(key)) {
78
- result.set(key, value);
79
- }
84
+ // Add all the direct dependencies
85
+ for (const [dependentPath, dependentEntry] of pathAdditions.entries()) {
86
+ if (!dependentEntries.has(dependentPath)) {
87
+ // Didn't already have this path in the result set
88
+ dependentEntries.set(dependentPath, dependentEntry);
89
+ // Visit all entries downstream of this one
90
+ for (const downstreamPath of dependentEntry.downstreams ?? []) {
91
+ if (!dependentEntries.has(downstreamPath)) {
92
+ this.visitDependentEntries(downstreamPath, dependentEntries);
80
93
  }
81
94
  }
82
95
  }
83
96
  }
84
-
85
- return result;
86
97
  }
87
98
 
88
99
  // REVIEW: This doesn't have the correct signature for getOrInsertComputed,
@@ -0,0 +1,3 @@
1
+ export default {
2
+ executions: 0,
3
+ };
@@ -19,14 +19,15 @@ const AsyncFunction = async function () {}.constructor;
19
19
  * @param {string} cachePath
20
20
  */
21
21
  export default function enableValueCaching(value, cachePath) {
22
- if (!(typeof value === "object" || typeof value === "function")) {
23
- // Don't need to apply caching to primitive value
24
- return value;
25
- }
22
+ // Value must be a defined object or function, and not have already have a
23
+ // cache path, and be maplike.
24
+ const cachable =
25
+ value &&
26
+ (typeof value === "object" || typeof value === "function") &&
27
+ value[cachePathSymbol] === undefined &&
28
+ Tree.isMaplike(value);
26
29
 
27
- const cacheable =
28
- value[cachePathSymbol] === undefined && Tree.isMaplike(value);
29
- if (cacheable) {
30
+ if (cachable) {
30
31
  if (isPlainObject(value)) {
31
32
  // Expression objects do their own caching
32
33
  // TODO: What if it's some other kind of plain object?
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import codeFragment from "./codeFragment.js";
5
5
  import explainReferenceError from "./explainReferenceError.js";
6
6
  import explainTraverseError from "./explainTraverseError.js";
7
+ import { ops } from "./internal.js";
7
8
 
8
9
  // Text we look for in an error stack to guess whether a given line represents a
9
10
  // function in the Origami source code.
@@ -61,7 +62,8 @@ export async function formatError(error) {
61
62
  message += "\n" + explanation;
62
63
  }
63
64
  } else if (error instanceof TraverseError) {
64
- const explanation = await explainTraverseError(error);
65
+ // TraverseError explainer wants the full code
66
+ const explanation = await explainTraverseError(context.code, error);
65
67
  if (explanation) {
66
68
  message += "\n" + explanation;
67
69
  }
@@ -158,3 +160,33 @@ export function lineInfo(location) {
158
160
  export function maybeOrigamiSourceCode(text) {
159
161
  return origamiSourceSignals.some((signal) => text.includes(signal));
160
162
  }
163
+
164
+ // Return the keys traversed by the given code, or null if undetermined.
165
+ export function reconstructTraversalKeys(code) {
166
+ const [op, ...args] = code;
167
+
168
+ switch (op) {
169
+ case ops.cache:
170
+ return reconstructTraversalKeys(code[2]);
171
+
172
+ case ops.unpack:
173
+ return reconstructTraversalKeys(code[1]);
174
+ }
175
+
176
+ if (op instanceof Array) {
177
+ if (op[0] === ops.scope) {
178
+ // Simple scope reference
179
+ const key = args[0][1];
180
+ return [key];
181
+ } else if (op[0] === ops.cache) {
182
+ // Traversal starting with external scope reference
183
+ const head = op[2][1][1];
184
+ const tail = args.map((code) => code[1]);
185
+ const keys = [head, ...tail];
186
+ return keys;
187
+ }
188
+ }
189
+
190
+ // Can't determine keys
191
+ return null;
192
+ }
@@ -1,4 +1,5 @@
1
1
  import { isUnpackable, Tree } from "@weborigami/async-tree";
2
+ import counters from "../runtime/counters.js";
2
3
  import executionContext from "./executionContext.js";
3
4
  import "./interop.js";
4
5
 
@@ -14,6 +15,8 @@ import "./interop.js";
14
15
  * @param {RuntimeState} [state]
15
16
  */
16
17
  export default async function execute(code, state = {}) {
18
+ counters.executions++;
19
+
17
20
  if (!(code instanceof Array)) {
18
21
  // Simple scalar; return as is.
19
22
  return code;
@@ -4,6 +4,7 @@ import {
4
4
  trailingSlash,
5
5
  Tree,
6
6
  } from "@weborigami/async-tree";
7
+ import { reconstructTraversalKeys } from "./errors.js";
7
8
  import { ops } from "./internal.js";
8
9
  import { typos } from "./typos.js";
9
10
 
@@ -35,39 +36,25 @@ export default async function explainReferenceError(code, state) {
35
36
  return explanation;
36
37
  }
37
38
 
38
- // See if the code looks like an external scope reference that failed
39
- let key;
40
- if (code[0] === ops.cache) {
41
- // External scope reference
42
- let refCall = code[2];
43
- if (refCall[0] === ops.unpack) {
44
- // Unwrap implied unpack
45
- refCall = refCall[1];
46
- }
47
- const scopeArgs = refCall.slice(1); // get the scope reference arguments
48
- const keys = scopeArgs.map((part) => part[1]);
49
- const path = pathFromKeys(keys);
50
-
51
- if (keys.length > 1) {
52
- return `This path returned undefined: ${path}`;
53
- }
54
- key = keys[0];
55
- } else if (code[0]?.[0] === ops.scope) {
56
- // Simple scope reference
57
- key = code[1][1];
58
- } else {
59
- // Generic reference error, can't offer help
39
+ const keys = reconstructTraversalKeys(code);
40
+ if (!keys) {
60
41
  return null;
42
+ } else if (keys.length > 1) {
43
+ const path = pathFromKeys(keys);
44
+ return `This path returned undefined: ${path}`;
61
45
  }
62
46
 
63
- key = trailingSlash.remove(key);
47
+ // A single key caused the problem, try to explain it
48
+ let key = keys[0];
64
49
  if (typeof key !== "string") {
65
50
  return null;
66
51
  }
52
+ key = trailingSlash.remove(key);
67
53
 
68
- // Common case of a single key
54
+ // Baseline message
69
55
  let message = `"${key}" is not in scope.`;
70
56
 
57
+ // Various explainers can add suggestions
71
58
  const explainers = [
72
59
  mathExplainer,
73
60
  qualifiedReferenceExplainer,
@@ -6,6 +6,7 @@ import {
6
6
  TraverseError,
7
7
  Tree,
8
8
  } from "@weborigami/async-tree";
9
+ import { reconstructTraversalKeys } from "./errors.js";
9
10
  import { typos } from "./typos.js";
10
11
 
11
12
  /**
@@ -14,7 +15,7 @@ import { typos } from "./typos.js";
14
15
  *
15
16
  * @param {TraverseError} error
16
17
  */
17
- export default async function explainTraverseError(error) {
18
+ export default async function explainTraverseError(code, error) {
18
19
  const { lastValue, keys, position } = error;
19
20
  if (lastValue === undefined || keys === undefined || position === undefined) {
20
21
  // Don't have sufficient information; shouldn't happen
@@ -27,9 +28,15 @@ export default async function explainTraverseError(error) {
27
28
  }
28
29
 
29
30
  // The key that caused the error is the one before the current position
30
- const path = pathFromKeys(keys);
31
- const problemKey = keys[position - 1];
32
- let message = `Tried to traverse path: ${path}\nStopped unexpectedly at: ${problemKey}`;
31
+ const problemKey = error.keys[position - 1];
32
+ const reconstructedKeys = reconstructTraversalKeys(code);
33
+ let message;
34
+ if (reconstructedKeys === null) {
35
+ message = `Path traversal stopped unexpectedly at: ${problemKey}`;
36
+ } else {
37
+ const path = pathFromKeys(reconstructedKeys);
38
+ message = `Tried to traverse path: ${path}\nStopped unexpectedly at: ${problemKey}`;
39
+ }
33
40
 
34
41
  const key = trailingSlash.remove(keys[position - 1]);
35
42
 
@@ -50,10 +57,12 @@ export default async function explainTraverseError(error) {
50
57
  const normalizedKeys = lastValueKeys.map(trailingSlash.remove);
51
58
 
52
59
  const keyAsNumber = Number(key);
53
- if (!isNaN(keyAsNumber)) {
60
+ if (reconstructedKeys && !isNaN(keyAsNumber)) {
54
61
  // See if the string version of the key is present
55
62
  if (lastValueKeys.includes(keyAsNumber)) {
56
- const suggestedPath = `${pathFromKeys(keys.slice(0, position - 1))}(${keyAsNumber})`;
63
+ const suggestedKeys = reconstructedKeys?.slice(0);
64
+ suggestedKeys[position] = `(${keyAsNumber})`;
65
+ const suggestedPath = pathFromKeys(suggestedKeys);
57
66
  message += `\nSlash-separated keys are searched as strings. Here there's no string "${key}" key, but there is a number ${keyAsNumber} key.
58
67
  To get the value for that number key, use parentheses: ${suggestedPath}`;
59
68
  }
@@ -25,24 +25,34 @@ const VALUE_TYPE = {
25
25
  GETTER: 2, // Calculated on demand: `a = fn()`
26
26
  };
27
27
 
28
+ const mapUnattachedObjectToIndex = new Map();
29
+
28
30
  /**
29
31
  * Given an array of entries with string keys and Origami code values (arrays of
30
32
  * ops and operands), return an object with the same keys defining properties
31
33
  * whose getters evaluate the code.
32
-
34
+ *
35
+ * @param {string|null} cachePath
33
36
  * @param {*} entries
34
37
  * @param {import("../../index.ts").RuntimeState} [state]
35
38
  */
36
- export default async function expressionObject(entries, state = {}) {
39
+ export default async function expressionObject(cachePath, entries, state = {}) {
37
40
  // Create the object and set its parent
38
41
  const object = {};
39
42
  const parent = state?.object ?? null;
40
43
  if (parent !== null && !Tree.isMap(parent)) {
41
44
  throw new TypeError(`Parent must be a map or null`);
42
45
  }
43
-
44
46
  setParent(object, parent);
45
47
 
48
+ if (cachePath?.endsWith("/_objects/")) {
49
+ // Unattached object, add a unique index to the cache path
50
+ let index = mapUnattachedObjectToIndex.get(cachePath) ?? 0;
51
+ mapUnattachedObjectToIndex.set(cachePath, index + 1);
52
+ cachePath += index + "/";
53
+ }
54
+ object[cachePathSymbol] = cachePath;
55
+
46
56
  // The object in Map form for use on the stack
47
57
  const map = new ObjectMap(object);
48
58
 
@@ -93,8 +103,7 @@ export default async function expressionObject(entries, state = {}) {
93
103
  * Define a single property on the object
94
104
  */
95
105
  function defineProperty(object, propertyInfo, state, map) {
96
- const { globals } = state;
97
- let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
106
+ let { enumerable, key, value, valueType } = propertyInfo;
98
107
  if (valueType == VALUE_TYPE.PRIMITIVE) {
99
108
  // Define simple property
100
109
  Object.defineProperty(object, key, {
@@ -109,43 +118,53 @@ function defineProperty(object, propertyInfo, state, map) {
109
118
  configurable: true,
110
119
  enumerable,
111
120
  get: async () => {
112
- // Execute the code to get the value of the property
113
- const propertyCachePath = getPropertyCachePath(object, key);
114
-
115
- const newState = Object.assign({}, state, { object: map });
116
- let result = propertyCachePath
117
- ? await systemCache.getOrInsertComputedAsync(propertyCachePath, () =>
118
- execute(value, newState),
119
- )
120
- : await execute(value, newState);
121
-
122
- if (hasExtension) {
123
- // Handle extension
124
- result = handleExtension(result, key, globals, map);
125
- }
126
-
127
- if (valueType === VALUE_TYPE.EAGER) {
128
- // Memoize result on the object itself
129
- Object.defineProperty(object, key, {
130
- configurable: true,
131
- enumerable,
132
- value: result,
133
- writable: true,
134
- });
135
- } else if (propertyCachePath) {
136
- result = enableValueCaching(result, propertyCachePath);
137
- }
138
-
139
- return result;
121
+ return executeProperty(object, propertyInfo, state, map);
140
122
  },
141
123
  });
142
124
  }
143
125
  }
144
126
 
127
+ // Execute the property code to get the value of the property
128
+ async function executeProperty(object, propertyInfo, state, map) {
129
+ const { globals } = state;
130
+ let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
131
+ const propertyCachePath = getPropertyCachePath(object, key);
132
+
133
+ const newState = Object.assign({}, state, { object: map });
134
+ const cacheProperty = valueType === VALUE_TYPE.GETTER && propertyCachePath;
135
+ let result = cacheProperty
136
+ ? await systemCache.getOrInsertComputedAsync(propertyCachePath, () =>
137
+ execute(value, newState),
138
+ )
139
+ : await execute(value, newState);
140
+
141
+ if (hasExtension) {
142
+ // Handle extension
143
+ result = handleExtension(result, key, globals, map);
144
+ }
145
+
146
+ if (propertyCachePath) {
147
+ // Enable caching on value
148
+ result = enableValueCaching(result, propertyCachePath);
149
+ }
150
+
151
+ if (valueType === VALUE_TYPE.EAGER) {
152
+ // Memoize result on the object itself
153
+ Object.defineProperty(object, key, {
154
+ configurable: true,
155
+ enumerable,
156
+ value: result,
157
+ writable: true,
158
+ });
159
+ }
160
+
161
+ return result;
162
+ }
163
+
145
164
  function getPropertyCachePath(object, key) {
146
165
  // Follow parent chain looking for a parent that has caching enabled
147
166
  let current = object;
148
- while (current[cachePathSymbol] === undefined) {
167
+ while (current[cachePathSymbol] == null) {
149
168
  current = current[symbols.parent];
150
169
  if (!current) {
151
170
  // Caching isn't enabled on this object tree
@@ -13,6 +13,9 @@ import { cachePathSymbol } from "./symbols.js";
13
13
  import systemCache from "./systemCache.js";
14
14
  import SystemCacheMap from "./SystemCacheMap.js";
15
15
 
16
+ // Base class for async functions
17
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
18
+
16
19
  /**
17
20
  * If the given value is packed (e.g., buffer) and the key is a string-like path
18
21
  * that ends in an extension, search for a handler for that extension and, if
@@ -46,19 +49,27 @@ export default function handleExtension(value, key, handlers, parent = null) {
46
49
  // Use `in` to look for handle so that, if the handler is a promise, we
47
50
  // can still find it without awaiting it here.
48
51
  if (handlerName in handlers) {
49
- let handler = handlers[handlerName];
50
-
51
52
  // If the value is a primitive, box it so we can attach data to it.
52
53
  value = box(value);
53
54
 
54
- if (handler.mediaType) {
55
- value.mediaType = handler.mediaType;
56
- }
57
-
58
55
  if (parent) {
59
56
  setParent(value, parent);
60
57
  }
61
58
 
59
+ // Avoid triggering the loading of the handler if it's async because
60
+ // we may be doing this work inside a sync function.
61
+ const descriptor = Object.getOwnPropertyDescriptor(
62
+ handlers,
63
+ handlerName,
64
+ );
65
+ const lazyHandler = descriptor?.get instanceof AsyncFunction;
66
+ if (!lazyHandler) {
67
+ const mediaType = handlers[handlerName]?.mediaType;
68
+ if (mediaType) {
69
+ value.mediaType = mediaType;
70
+ }
71
+ }
72
+
62
73
  // Wrap the unpack function so it caches the unpacked value, and so we
63
74
  // can add the file path to any errors the unpack function throws.
64
75
  const filePath = getPackedPath(value, { key: normalized, parent });
@@ -74,9 +85,7 @@ export default function handleExtension(value, key, handlers, parent = null) {
74
85
  const unpackCachePath = trailingSlash.add(fileCachePath);
75
86
  value.unpack = async () =>
76
87
  systemCache.getOrInsertComputedAsync(unpackCachePath, async () => {
77
- if (handler instanceof Promise) {
78
- handler = await handler;
79
- }
88
+ let handler = await handlers[handlerName];
80
89
  if (isUnpackable(handler)) {
81
90
  // The extension handler itself needs to be unpacked
82
91
  handler = await handler.unpack();
@@ -255,7 +255,7 @@ export function lambda(length, parameters, code, state = {}) {
255
255
  const interimStack = stack.slice();
256
256
  interimStack.push(args);
257
257
  const paramState = { ...state, stack: interimStack };
258
- const frame = await expressionObject(parameters, paramState);
258
+ const frame = await expressionObject(null, parameters, paramState);
259
259
  // Record which code this stack frame is associated with
260
260
  Object.defineProperty(frame, codeSymbol, {
261
261
  value: code,
@@ -414,11 +414,12 @@ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
414
414
  * parameter's, and the values will be the results of evaluating the
415
415
  * corresponding code values in `obj`.
416
416
  *
417
+ * @param {string} cachePath
417
418
  * @param {any[]} entries
418
419
  */
419
- export async function object(...entries) {
420
+ export async function object(cachePath, ...entries) {
420
421
  const state = entries.pop();
421
- return expressionObject(entries, state);
422
+ return expressionObject(cachePath, entries, state);
422
423
  }
423
424
  addOpLabel(object, "«ops.object»");
424
425
  object.unevaluatedArgs = true;
@@ -16,7 +16,6 @@ const globals = {
16
16
  * features, which compare the output of evaluate2() against an expected result.
17
17
  */
18
18
  describe("compile", () => {
19
-
20
19
  test("async object", async () => {
21
20
  const fn = compile.expression("{ a: { b = name }}", { globals });
22
21
  const object = await fn();