@weborigami/language 0.6.16 → 0.7.0-beta.1

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 (83) hide show
  1. package/index.ts +1 -0
  2. package/main.js +7 -1
  3. package/package.json +6 -6
  4. package/src/compiler/compile.js +10 -3
  5. package/src/compiler/optimize.js +71 -40
  6. package/src/compiler/parse.js +1 -1
  7. package/src/compiler/parserHelpers.js +5 -3
  8. package/src/handlers/getSource.js +11 -0
  9. package/src/handlers/htm_handler.js +1 -1
  10. package/src/handlers/js_handler.js +13 -4
  11. package/src/handlers/mediaTypeExtensions.json +15 -0
  12. package/src/handlers/ori_handler.js +8 -7
  13. package/src/handlers/oridocument_handler.js +20 -11
  14. package/src/handlers/processOriExport.js +17 -0
  15. package/src/handlers/tsv_handler.js +1 -1
  16. package/src/handlers/txt_handler.js +4 -2
  17. package/src/handlers/xhtml_handler.js +1 -1
  18. package/src/handlers/yaml_handler.js +6 -3
  19. package/src/project/activeProjectRoot.js +9 -0
  20. package/src/project/getGlobalsForTree.js +5 -0
  21. package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
  22. package/src/project/jsGlobals.js +1 -0
  23. package/src/project/projectConfig.js +2 -2
  24. package/src/project/projectRootFromPath.js +2 -0
  25. package/src/protocols/constructHref.js +3 -3
  26. package/src/protocols/constructSiteTree.js +11 -2
  27. package/src/protocols/explore.js +1 -1
  28. package/src/protocols/explorehttp.js +1 -1
  29. package/src/protocols/fetchAndHandleExtension.js +23 -11
  30. package/src/protocols/files.js +1 -0
  31. package/src/protocols/http.js +4 -1
  32. package/src/protocols/https.js +4 -1
  33. package/src/protocols/httpstree.js +1 -1
  34. package/src/protocols/httptree.js +1 -1
  35. package/src/protocols/package.js +15 -3
  36. package/src/runtime/AsyncCacheTransform.d.ts +5 -0
  37. package/src/runtime/AsyncCacheTransform.js +134 -0
  38. package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
  39. package/src/runtime/HandleExtensionsTransform.js +18 -2
  40. package/src/runtime/OrigamiFileMap.d.ts +5 -2
  41. package/src/runtime/OrigamiFileMap.js +27 -4
  42. package/src/runtime/ScopeMap.js +72 -0
  43. package/src/runtime/SyncCacheTransform.d.ts +8 -0
  44. package/src/runtime/SyncCacheTransform.js +133 -0
  45. package/src/runtime/SystemCacheMap.js +259 -0
  46. package/src/runtime/WatchFilesMixin.js +52 -19
  47. package/src/runtime/enableValueCaching.js +192 -0
  48. package/src/runtime/execute.js +2 -2
  49. package/src/runtime/executionContext.js +7 -0
  50. package/src/runtime/explainReferenceError.js +7 -2
  51. package/src/runtime/expressionObject.js +54 -46
  52. package/src/runtime/handleExtension.js +65 -34
  53. package/src/runtime/interop.js +2 -2
  54. package/src/runtime/mergeTrees.js +1 -1
  55. package/src/runtime/ops.js +28 -33
  56. package/src/runtime/symbols.js +3 -0
  57. package/src/runtime/systemCache.js +3 -0
  58. package/src/runtime/volatile.js +14 -0
  59. package/test/compiler/codeHelpers.js +2 -1
  60. package/test/compiler/optimize.test.js +62 -54
  61. package/test/handlers/ori_handler.test.js +22 -3
  62. package/test/handlers/oridocument_handler.test.js +1 -1
  63. package/test/protocols/https.test.js +19 -0
  64. package/test/protocols/package.test.js +7 -2
  65. package/test/runtime/AsyncCacheTransform.test.js +91 -0
  66. package/test/runtime/OrigamiFileMap.test.js +26 -23
  67. package/test/runtime/ScopeMap.test.js +49 -0
  68. package/test/runtime/SyncCacheTransform.test.js +93 -0
  69. package/test/runtime/SystemCacheMap.test.js +239 -0
  70. package/test/runtime/asyncCalcs.js +28 -0
  71. package/test/runtime/enableValueCaching.test.js +55 -0
  72. package/test/runtime/errors.test.js +53 -30
  73. package/test/runtime/evaluate.test.js +9 -4
  74. package/test/runtime/execute.test.js +6 -1
  75. package/test/runtime/expressionObject.test.js +55 -15
  76. package/test/runtime/fetchAndHandleExtension.test.js +24 -0
  77. package/test/runtime/fixtures/unpack/hello.json +1 -0
  78. package/test/runtime/handleExtension.test.js +12 -1
  79. package/test/runtime/ops.test.js +70 -65
  80. package/test/runtime/syncCalcs.js +27 -0
  81. package/test/runtime/systemCache.test.js +66 -0
  82. package/src/runtime/assignPropertyDescriptors.js +0 -23
  83. package/src/runtime/asyncStorage.js +0 -7
@@ -1,5 +1,5 @@
1
1
  import { isUnpackable, Tree } from "@weborigami/async-tree";
2
- import asyncStorage from "./asyncStorage.js";
2
+ import executionContext from "./executionContext.js";
3
3
  import "./interop.js";
4
4
 
5
5
  /**
@@ -68,7 +68,7 @@ export default async function execute(code, state = {}) {
68
68
  // Execute the function or traverse the map.
69
69
  let result;
70
70
  try {
71
- result = await asyncStorage.run(
71
+ result = await executionContext.run(
72
72
  context,
73
73
  async () =>
74
74
  fn instanceof Function
@@ -0,0 +1,7 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ /**
4
+ * The execute() function's context made available to async functions called
5
+ * during evaluation.
6
+ */
7
+ export default new AsyncLocalStorage();
@@ -39,8 +39,13 @@ export default async function explainReferenceError(code, state) {
39
39
  let key;
40
40
  if (code[0] === ops.cache) {
41
41
  // External scope reference
42
- const scopeCall = code[3].slice(1); // drop the ops.scope
43
- const keys = scopeCall.map((part) => part[1]);
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]);
44
49
  const path = pathFromKeys(keys);
45
50
 
46
51
  if (keys.length > 1) {
@@ -6,9 +6,13 @@ import {
6
6
  trailingSlash,
7
7
  Tree,
8
8
  } from "@weborigami/async-tree";
9
+ import enableValueCaching from "./enableValueCaching.js";
9
10
  import execute from "./execute.js";
10
11
  import handleExtension from "./handleExtension.js";
11
12
  import { ops } from "./internal.js";
13
+ import { cachePathSymbol } from "./symbols.js";
14
+ import systemCache from "./systemCache.js";
15
+ import SystemCacheMap from "./SystemCacheMap.js";
12
16
 
13
17
  export const KEY_TYPE = {
14
18
  STRING: 0, // Simple string key: `a: 1`
@@ -36,6 +40,7 @@ export default async function expressionObject(entries, state = {}) {
36
40
  if (parent !== null && !Tree.isMap(parent)) {
37
41
  throw new TypeError(`Parent must be a map or null`);
38
42
  }
43
+
39
44
  setParent(object, parent);
40
45
 
41
46
  // The object in Map form for use on the stack
@@ -51,17 +56,7 @@ export default async function expressionObject(entries, state = {}) {
51
56
  }
52
57
  }
53
58
 
54
- // Second pass: redefine eager string-keyed properties with actual values.
55
- for (const info of infos) {
56
- if (
57
- info.keyType === KEY_TYPE.STRING &&
58
- info.valueType === VALUE_TYPE.EAGER
59
- ) {
60
- await redefineProperty(object, info);
61
- }
62
- }
63
-
64
- // Third pass: define all computed properties. These may refer to the
59
+ // Second pass: define all computed properties. These may refer to the
65
60
  // properties we just defined.
66
61
  for (const info of infos) {
67
62
  if (info.keyType === KEY_TYPE.COMPUTED) {
@@ -73,15 +68,11 @@ export default async function expressionObject(entries, state = {}) {
73
68
  }
74
69
  }
75
70
 
76
- // Fourth pass: redefine eager computed-keyed properties with actual values.
77
- for (const info of infos) {
78
- if (
79
- info.keyType === KEY_TYPE.COMPUTED &&
80
- info.valueType === VALUE_TYPE.EAGER
81
- ) {
82
- await redefineProperty(object, info);
83
- }
84
- }
71
+ // Third pass: retrieve eager properties, memoizing them on the object
72
+ const eagerKeys = infos
73
+ .filter((info) => info.valueType === VALUE_TYPE.EAGER)
74
+ .map((info) => info.key);
75
+ await Promise.all(eagerKeys.map((key) => object[key]));
85
76
 
86
77
  // Attach a keys method, where keys for primitive/eager properties with
87
78
  // maplike values get a trailing slash.
@@ -95,16 +86,6 @@ export default async function expressionObject(entries, state = {}) {
95
86
  writable: true,
96
87
  });
97
88
 
98
- // TODO: If there are any getters, mark the object as async. Note: this code
99
- // was added so that Tree.from() could know whether to return an ObjectMap or
100
- // a hypothetical AsyncObjectMap, which in turn would let a map operation know
101
- // whether to expect async property values. const hasGetters =
102
- // infos.some((info) => info.valueType === VALUE_TYPE.GETTER); if (hasGetters)
103
- // { Object.defineProperty(object, symbols.async, { configurable: true,
104
- // enumerable: false, value: true, writable: true,
105
- // });
106
- // }
107
-
108
89
  return object;
109
90
  }
110
91
 
@@ -112,6 +93,7 @@ export default async function expressionObject(entries, state = {}) {
112
93
  * Define a single property on the object
113
94
  */
114
95
  function defineProperty(object, propertyInfo, state, map) {
96
+ const { globals } = state;
115
97
  let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
116
98
  if (valueType == VALUE_TYPE.PRIMITIVE) {
117
99
  // Define simple property
@@ -127,14 +109,54 @@ function defineProperty(object, propertyInfo, state, map) {
127
109
  configurable: true,
128
110
  enumerable,
129
111
  get: async () => {
112
+ // Execute the code to get the value of the property
113
+ const propertyCachePath = getPropertyCachePath(object, key);
114
+
130
115
  const newState = Object.assign({}, state, { object: map });
131
- const result = await execute(value, newState);
132
- return hasExtension ? handleExtension(result, key, map) : result;
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;
133
140
  },
134
141
  });
135
142
  }
136
143
  }
137
144
 
145
+ function getPropertyCachePath(object, key) {
146
+ // Follow parent chain looking for a parent that has caching enabled
147
+ let current = object;
148
+ while (current[cachePathSymbol] === undefined) {
149
+ current = current[symbols.parent];
150
+ if (!current) {
151
+ // Caching isn't enabled on this object tree
152
+ return null;
153
+ }
154
+ }
155
+
156
+ const cachePath = SystemCacheMap.joinPath(current[cachePathSymbol], key);
157
+ return cachePath;
158
+ }
159
+
138
160
  /**
139
161
  * Return a normalized version of the property key for use in the keys() method.
140
162
  * Among other things, this adds trailing slashes to keys that correspond to
@@ -227,17 +249,3 @@ export function propertyInfo(key, value) {
227
249
 
228
250
  return { enumerable, hasExtension, key, keyType, value, valueType };
229
251
  }
230
-
231
- /**
232
- * Get the value of the indicated eager property and overwrite the property
233
- * definition with the actual value.
234
- */
235
- async function redefineProperty(object, info) {
236
- const value = await object[info.key];
237
- Object.defineProperty(object, info.key, {
238
- configurable: true,
239
- enumerable: info.enumerable,
240
- value,
241
- writable: true,
242
- });
243
- }
@@ -8,7 +8,10 @@ import {
8
8
  trailingSlash,
9
9
  } from "@weborigami/async-tree";
10
10
  import getPackedPath from "../handlers/getPackedPath.js";
11
- import projectGlobals from "../project/projectGlobals.js";
11
+ import mediaTypeExtensions from "../handlers/mediaTypeExtensions.json" with { type: "json" };
12
+ import { cachePathSymbol } from "./symbols.js";
13
+ import systemCache from "./systemCache.js";
14
+ import SystemCacheMap from "./SystemCacheMap.js";
12
15
 
13
16
  /**
14
17
  * If the given value is packed (e.g., buffer) and the key is a string-like path
@@ -17,28 +20,33 @@ import projectGlobals from "../project/projectGlobals.js";
17
20
  *
18
21
  * @param {any} value
19
22
  * @param {any} key
23
+ * @param {any} handlers
20
24
  * @param {import("@weborigami/async-tree").SyncOrAsyncMap|null} [parent]
21
25
  */
22
- export default async function handleExtension(value, key, parent = null) {
23
- if (isPacked(value) && isStringlike(key) && value.unpack === undefined) {
24
- const hasSlash = trailingSlash.has(key);
25
- if (hasSlash) {
26
- key = trailingSlash.remove(key);
27
- }
26
+ export default function handleExtension(value, key, handlers, parent = null) {
27
+ if (
28
+ isPacked(value) &&
29
+ isStringlike(key) &&
30
+ value.unpack === undefined &&
31
+ handlers
32
+ ) {
33
+ const normalized = trailingSlash.remove(key);
28
34
 
29
35
  // Special cases: `.ori.<ext>` extensions are Origami documents
30
- const extname = key.match(/\.ori\.\S+$/)
36
+ let extname = normalized.match(/\.ori\.\S+$/)
31
37
  ? ".oridocument"
32
- : extension.extname(key);
38
+ : extension.extname(normalized);
39
+
40
+ if (!extname && /** @type {any} */ (value)?.mediaType) {
41
+ extname = extensionFromMediaType(/** @type {any} */ (value).mediaType);
42
+ }
43
+
33
44
  if (extname) {
34
45
  const handlerName = `${extname.slice(1)}_handler`;
35
- const handlers = await projectGlobals(parent);
36
- let handler = await handlers[handlerName];
37
- if (handler) {
38
- if (isUnpackable(handler)) {
39
- // The extension handler itself needs to be unpacked
40
- handler = await handler.unpack();
41
- }
46
+ // Use `in` to look for handle so that, if the handler is a promise, we
47
+ // can still find it without awaiting it here.
48
+ if (handlerName in handlers) {
49
+ let handler = handlers[handlerName];
42
50
 
43
51
  // If the value is a primitive, box it so we can attach data to it.
44
52
  value = box(value);
@@ -51,9 +59,45 @@ export default async function handleExtension(value, key, parent = null) {
51
59
  setParent(value, parent);
52
60
  }
53
61
 
54
- if (handler.unpack) {
55
- value.unpack = wrapUnpack(handler.unpack, value, key, parent);
62
+ // Wrap the unpack function so it caches the unpacked value, and so we
63
+ // can add the file path to any errors the unpack function throws.
64
+ const filePath = getPackedPath(value, { key: normalized, parent });
65
+ let fileCachePath;
66
+ if (parent?.[cachePathSymbol]) {
67
+ fileCachePath = SystemCacheMap.joinPath(
68
+ parent[cachePathSymbol],
69
+ normalized,
70
+ );
71
+ } else {
72
+ fileCachePath = filePath;
56
73
  }
74
+ const unpackCachePath = trailingSlash.add(fileCachePath);
75
+ value.unpack = async () =>
76
+ systemCache.getOrInsertComputedAsync(unpackCachePath, async () => {
77
+ if (handler instanceof Promise) {
78
+ handler = await handler;
79
+ }
80
+ if (isUnpackable(handler)) {
81
+ // The extension handler itself needs to be unpacked
82
+ handler = await handler.unpack();
83
+ }
84
+
85
+ const unpacked = await handler.unpack(value, {
86
+ key: normalized,
87
+ parent,
88
+ });
89
+
90
+ // Now that we know the file was unpacked, we cache the file value
91
+ // itself so that subsequent requests for the file can be fulfilled
92
+ // from the cache. Doing this sort of manipulation outside of the
93
+ // cache doesn't feel great, but works.
94
+ const fileCacheEntry = systemCache.get(fileCachePath);
95
+ if (fileCacheEntry) {
96
+ fileCacheEntry.value = value;
97
+ }
98
+
99
+ return unpacked;
100
+ });
57
101
  }
58
102
  }
59
103
  }
@@ -61,20 +105,7 @@ export default async function handleExtension(value, key, parent = null) {
61
105
  return value;
62
106
  }
63
107
 
64
- // Wrap the unpack function so it's only called once per value, and so we can
65
- // add the file path to any errors it throws.
66
- function wrapUnpack(unpack, value, key, parent) {
67
- let result;
68
- return async () => {
69
- if (!result) {
70
- try {
71
- result = await unpack(value, { key, parent });
72
- } catch (/** @type {any} */ error) {
73
- const filePath = getPackedPath(value, { key, parent });
74
- const message = `Can't unpack ${filePath}\n${error.message}`;
75
- throw new error.constructor(message, { cause: error });
76
- }
77
- }
78
- return result;
79
- };
108
+ function extensionFromMediaType(mediaType) {
109
+ const essence = mediaType.split(";")[0].trim();
110
+ return mediaTypeExtensions[essence];
80
111
  }
@@ -1,13 +1,13 @@
1
1
  import { interop } from "@weborigami/async-tree";
2
- import asyncStorage from "./asyncStorage.js";
3
2
  import { lineInfo } from "./errors.js";
3
+ import executionContext from "./executionContext.js";
4
4
 
5
5
  /**
6
6
  * Inject our warning function into async-tree calls
7
7
  */
8
8
  interop.warn = function warn(...args) {
9
9
  console.warn(...args);
10
- const context = asyncStorage.getStore();
10
+ const context = executionContext.getStore();
11
11
  const location = context?.code?.location;
12
12
  if (location) {
13
13
  console.warn(lineInfo(location));
@@ -1,11 +1,11 @@
1
1
  import {
2
+ assignPropertyDescriptors,
2
3
  isPlainObject,
3
4
  isUnpackable,
4
5
  symbols,
5
6
  trailingSlash,
6
7
  Tree,
7
8
  } from "@weborigami/async-tree";
8
- import assignPropertyDescriptors from "./assignPropertyDescriptors.js";
9
9
 
10
10
  /**
11
11
  * Create a tree that's the result of merging the given trees.
@@ -6,13 +6,15 @@
6
6
  * @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
7
7
  */
8
8
 
9
- import { getParent, isUnpackable, Tree } from "@weborigami/async-tree";
9
+ import { getParent, isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
10
10
  import os from "node:os";
11
11
  import execute from "./execute.js";
12
12
  import expressionObject from "./expressionObject.js";
13
13
  import mergeTrees from "./mergeTrees.js";
14
14
  import OrigamiFileMap from "./OrigamiFileMap.js";
15
+ import ScopeMap from "./ScopeMap.js";
15
16
  import { codeSymbol } from "./symbols.js";
17
+ import systemCache from "./systemCache.js";
16
18
 
17
19
  function addOpLabel(op, label) {
18
20
  Object.defineProperty(op, "toString", {
@@ -81,31 +83,18 @@ addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
81
83
  /**
82
84
  * Cache the value of the code for an external reference
83
85
  *
84
- * @param {any} cache
85
- * @param {string} path
86
+ * @param {string} cachePath
86
87
  * @param {AnnotatedCode} code
88
+ * @param {RuntimeState} state
87
89
  */
88
- export async function cache(cache, path, code) {
89
- if (path in cache) {
90
- // Cache hit
91
- return cache[path];
92
- }
93
-
94
- // Don't await: might get another request for this before promise resolves
95
- const promise = execute(code);
96
-
97
- // Save promise so another request will get the same promise
98
- cache[path] = promise;
99
-
100
- // Now wait for the value
101
- const value = await promise;
102
-
103
- // Update the cache with the actual value
104
- cache[path] = value;
105
-
106
- return value;
90
+ export function cache(cachePath, code, state) {
91
+ const result = systemCache.getOrInsertComputedAsync(cachePath, () =>
92
+ execute(code, state),
93
+ );
94
+ return result;
107
95
  }
108
96
  addOpLabel(cache, "«ops.cache»");
97
+ cache.needsState = true;
109
98
  cache.unevaluatedArgs = true;
110
99
 
111
100
  /**
@@ -114,13 +103,17 @@ cache.unevaluatedArgs = true;
114
103
  * @param {...AnnotatedCode} args
115
104
  */
116
105
  export async function comma(...args) {
106
+ /** @type {RuntimeState} */
107
+ // @ts-ignore
108
+ const state = args.pop(); // The runtime state is passed as the last argument
117
109
  let result;
118
110
  for (const arg of args) {
119
- result = await execute(arg);
111
+ result = await execute(arg, state);
120
112
  }
121
113
  return result;
122
114
  }
123
115
  addOpLabel(comma, "«ops.comma»");
116
+ comma.needsState = true;
124
117
  comma.unevaluatedArgs = true;
125
118
 
126
119
  export async function conditional(condition, truthy, falsy) {
@@ -143,7 +136,7 @@ export async function construct(constructor, ...args) {
143
136
  export async function deepText(...args) {
144
137
  return Tree.deepText(args);
145
138
  }
146
- addOpLabel(deepText, "«ops.deepText");
139
+ addOpLabel(deepText, "«ops.deepText»");
147
140
 
148
141
  /**
149
142
  * Default value for a parameter: if the value is defined, return that;
@@ -180,8 +173,7 @@ addOpLabel(exponentiation, "«ops.exponentiation»");
180
173
  export async function flat(...args) {
181
174
  // Unpack packed arguments so they can be flattened
182
175
  const unpacked = args.map((arg) => (isUnpackable(arg) ? arg.unpack() : arg));
183
- // Flatten to depth 2: 1 for args array, 1 for flattening
184
- return Tree.flat(unpacked, 2);
176
+ return Tree.flat(unpacked);
185
177
  }
186
178
  addOpLabel(flat, "«ops.flat»");
187
179
 
@@ -206,6 +198,7 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
206
198
  */
207
199
  export async function homeDirectory(...keys) {
208
200
  const tree = new OrigamiFileMap(os.homedir());
201
+ await tree.initializeGlobals();
209
202
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
210
203
  }
211
204
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
@@ -488,7 +481,8 @@ export async function property(object, key) {
488
481
 
489
482
  if (isUnpackable(object)) {
490
483
  object = await object.unpack();
491
- } else if (typeof object === "string") {
484
+ }
485
+ if (typeof object === "string") {
492
486
  object = new String(object);
493
487
  } else if (typeof object === "number") {
494
488
  object = new Number(object);
@@ -529,17 +523,18 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
529
523
  /**
530
524
  * Return the scope of the current tree
531
525
  *
532
- * @param {SyncOrAsyncMap} parent
526
+ * @param {RuntimeState} state
533
527
  */
534
- export async function scope(parent) {
528
+ export async function scope(state = {}) {
529
+ const { parent } = state;
535
530
  if (!parent) {
536
- throw new ReferenceError(
537
- "Tried to find a value in scope, but no container was provided as the parent.",
538
- );
531
+ return new SyncMap(); // empty scope if there's no parent
539
532
  }
540
- return Tree.scope(parent);
533
+ const scopeMap = new ScopeMap(parent);
534
+ return scopeMap;
541
535
  }
542
536
  addOpLabel(scope, "«ops.scope»");
537
+ scope.needsState = true;
543
538
 
544
539
  export function shiftLeft(a, b) {
545
540
  return a << b;
@@ -1,4 +1,7 @@
1
+ export const cachePathSymbol = Symbol("cachePath");
1
2
  export const codeSymbol = Symbol("code");
2
3
  export const configSymbol = Symbol("config");
4
+ export const noCacheSymbol = Symbol("noCache");
3
5
  export const scopeSymbol = Symbol("scope");
4
6
  export const sourceSymbol = Symbol("source");
7
+ export const volatileSymbol = Symbol("volatile");
@@ -0,0 +1,3 @@
1
+ import SystemCacheMap from "./SystemCacheMap.js";
2
+
3
+ export default new SystemCacheMap();
@@ -0,0 +1,14 @@
1
+ import { box } from "@weborigami/async-tree";
2
+ import { volatileSymbol } from "./symbols.js";
3
+
4
+ /**
5
+ * Mark the indicated value as volatile so it won't be cached.
6
+ */
7
+ export default function volatile(value) {
8
+ const boxed = box(value);
9
+ Object.defineProperty(boxed, volatileSymbol, {
10
+ value: true,
11
+ enumerable: false,
12
+ });
13
+ return boxed;
14
+ }
@@ -23,11 +23,12 @@ export function assertCodeLocations(code) {
23
23
  */
24
24
  export function createCode(array) {
25
25
  const code = array.map((item) =>
26
- item instanceof Array ? createCode(item) : item
26
+ item instanceof Array ? createCode(item) : item,
27
27
  );
28
28
  /** @type {any} */ (code).location = {
29
29
  end: 0,
30
30
  source: {
31
+ relativePath: "test.ori",
31
32
  text: "",
32
33
  },
33
34
  start: 0,