@weborigami/language 0.6.17 → 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.
- package/index.ts +1 -0
- package/main.js +7 -1
- package/package.json +6 -6
- package/src/compiler/compile.js +10 -3
- package/src/compiler/optimize.js +71 -40
- package/src/compiler/parse.js +1 -1
- package/src/compiler/parserHelpers.js +5 -3
- package/src/handlers/getSource.js +11 -0
- package/src/handlers/htm_handler.js +1 -1
- package/src/handlers/js_handler.js +13 -4
- package/src/handlers/mediaTypeExtensions.json +15 -0
- package/src/handlers/ori_handler.js +8 -7
- package/src/handlers/oridocument_handler.js +17 -10
- package/src/handlers/processOriExport.js +17 -0
- package/src/handlers/tsv_handler.js +1 -1
- package/src/handlers/txt_handler.js +4 -2
- package/src/handlers/xhtml_handler.js +1 -1
- package/src/handlers/yaml_handler.js +6 -3
- package/src/project/activeProjectRoot.js +9 -0
- package/src/project/getGlobalsForTree.js +5 -0
- package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
- package/src/project/jsGlobals.js +1 -0
- package/src/project/projectConfig.js +2 -2
- package/src/project/projectRootFromPath.js +2 -0
- package/src/protocols/constructHref.js +3 -3
- package/src/protocols/constructSiteTree.js +11 -2
- package/src/protocols/explore.js +1 -1
- package/src/protocols/explorehttp.js +1 -1
- package/src/protocols/fetchAndHandleExtension.js +23 -11
- package/src/protocols/files.js +1 -0
- package/src/protocols/http.js +4 -1
- package/src/protocols/https.js +4 -1
- package/src/protocols/httpstree.js +1 -1
- package/src/protocols/httptree.js +1 -1
- package/src/protocols/package.js +15 -3
- package/src/runtime/AsyncCacheTransform.d.ts +5 -0
- package/src/runtime/AsyncCacheTransform.js +134 -0
- package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
- package/src/runtime/HandleExtensionsTransform.js +18 -2
- package/src/runtime/OrigamiFileMap.d.ts +5 -2
- package/src/runtime/OrigamiFileMap.js +27 -4
- package/src/runtime/ScopeMap.js +72 -0
- package/src/runtime/SyncCacheTransform.d.ts +8 -0
- package/src/runtime/SyncCacheTransform.js +133 -0
- package/src/runtime/SystemCacheMap.js +259 -0
- package/src/runtime/WatchFilesMixin.js +52 -19
- package/src/runtime/enableValueCaching.js +192 -0
- package/src/runtime/execute.js +2 -2
- package/src/runtime/executionContext.js +7 -0
- package/src/runtime/explainReferenceError.js +7 -2
- package/src/runtime/expressionObject.js +54 -46
- package/src/runtime/handleExtension.js +65 -34
- package/src/runtime/interop.js +2 -2
- package/src/runtime/mergeTrees.js +1 -1
- package/src/runtime/ops.js +28 -33
- package/src/runtime/symbols.js +3 -0
- package/src/runtime/systemCache.js +3 -0
- package/src/runtime/volatile.js +14 -0
- package/test/compiler/codeHelpers.js +2 -1
- package/test/compiler/optimize.test.js +62 -54
- package/test/handlers/ori_handler.test.js +22 -3
- package/test/handlers/oridocument_handler.test.js +1 -1
- package/test/protocols/https.test.js +19 -0
- package/test/protocols/package.test.js +7 -2
- package/test/runtime/AsyncCacheTransform.test.js +91 -0
- package/test/runtime/OrigamiFileMap.test.js +26 -23
- package/test/runtime/ScopeMap.test.js +49 -0
- package/test/runtime/SyncCacheTransform.test.js +93 -0
- package/test/runtime/SystemCacheMap.test.js +239 -0
- package/test/runtime/asyncCalcs.js +28 -0
- package/test/runtime/enableValueCaching.test.js +55 -0
- package/test/runtime/errors.test.js +53 -30
- package/test/runtime/evaluate.test.js +9 -4
- package/test/runtime/execute.test.js +6 -1
- package/test/runtime/expressionObject.test.js +55 -15
- package/test/runtime/fetchAndHandleExtension.test.js +24 -0
- package/test/runtime/fixtures/unpack/hello.json +1 -0
- package/test/runtime/handleExtension.test.js +12 -1
- package/test/runtime/ops.test.js +70 -65
- package/test/runtime/syncCalcs.js +27 -0
- package/test/runtime/systemCache.test.js +66 -0
- package/src/runtime/assignPropertyDescriptors.js +0 -23
- package/src/runtime/asyncStorage.js +0 -7
package/src/runtime/execute.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isUnpackable, Tree } from "@weborigami/async-tree";
|
|
2
|
-
import
|
|
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
|
|
71
|
+
result = await executionContext.run(
|
|
72
72
|
context,
|
|
73
73
|
async () =>
|
|
74
74
|
fn instanceof Function
|
|
@@ -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
|
-
|
|
43
|
-
|
|
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:
|
|
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
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
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
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
36
|
+
let extname = normalized.match(/\.ori\.\S+$/)
|
|
31
37
|
? ".oridocument"
|
|
32
|
-
: extension.extname(
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
}
|
package/src/runtime/interop.js
CHANGED
|
@@ -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 =
|
|
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.
|
package/src/runtime/ops.js
CHANGED
|
@@ -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 {
|
|
85
|
-
* @param {string} path
|
|
86
|
+
* @param {string} cachePath
|
|
86
87
|
* @param {AnnotatedCode} code
|
|
88
|
+
* @param {RuntimeState} state
|
|
87
89
|
*/
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
526
|
+
* @param {RuntimeState} state
|
|
533
527
|
*/
|
|
534
|
-
export async function scope(
|
|
528
|
+
export async function scope(state = {}) {
|
|
529
|
+
const { parent } = state;
|
|
535
530
|
if (!parent) {
|
|
536
|
-
|
|
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
|
-
|
|
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;
|
package/src/runtime/symbols.js
CHANGED
|
@@ -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,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,
|