@weborigami/language 0.7.0-beta.2 → 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.
- package/main.js +1 -0
- package/package.json +2 -2
- package/src/compiler/compile.js +0 -3
- package/src/compiler/optimize.js +56 -23
- package/src/compiler/parserHelpers.js +11 -8
- package/src/handlers/getSource.js +1 -0
- package/src/handlers/ori_handler.js +2 -2
- package/src/handlers/oridocument_handler.js +2 -2
- package/src/handlers/{processOriExport.js → processOrigamiExport.js} +1 -1
- package/src/runtime/AsyncCacheTransform.d.ts +5 -1
- package/src/runtime/AsyncCacheTransform.js +22 -25
- package/src/runtime/OrigamiFileMap.js +3 -3
- package/src/runtime/SyncCacheTransform.d.ts +2 -1
- package/src/runtime/SyncCacheTransform.js +31 -34
- package/src/runtime/SystemCacheMap.js +28 -17
- package/src/runtime/counters.js +3 -0
- package/src/runtime/enableValueCaching.js +8 -7
- package/src/runtime/errors.js +33 -1
- package/src/runtime/execute.js +3 -0
- package/src/runtime/explainReferenceError.js +11 -24
- package/src/runtime/explainTraverseError.js +15 -6
- package/src/runtime/expressionObject.js +53 -34
- package/src/runtime/handleExtension.js +18 -9
- package/src/runtime/ops.js +4 -3
- package/test/compiler/compile.test.js +0 -1
- package/test/compiler/optimize.test.js +60 -15
- package/test/compiler/parse.test.js +38 -9
- package/test/runtime/AsyncCacheTransform.test.js +9 -4
- package/test/runtime/SyncCacheTransform.test.js +4 -0
- package/test/runtime/asyncCalcs.js +26 -4
- package/test/runtime/errors.test.js +4 -4
- package/test/runtime/expressionObject.test.js +23 -12
- package/test/runtime/handleExtension.test.js +0 -2
- package/test/runtime/ops.test.js +5 -3
- package/test/runtime/syncCalcs.js +26 -3
- package/test/runtime/systemCache.test.js +4 -0
package/main.js
CHANGED
|
@@ -15,6 +15,7 @@ export { default as projectRoot } from "./src/project/projectRoot.js";
|
|
|
15
15
|
export { default as projectRootFromPath } from "./src/project/projectRootFromPath.js";
|
|
16
16
|
export { default as fetchAndHandleExtension } from "./src/protocols/fetchAndHandleExtension.js";
|
|
17
17
|
export * as Protocols from "./src/protocols/protocols.js";
|
|
18
|
+
export { default as counters } from "./src/runtime/counters.js";
|
|
18
19
|
export { formatError, highlightError, lineInfo } from "./src/runtime/errors.js";
|
|
19
20
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
20
21
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.3",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"typescript": "6.0.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.7.0-beta.
|
|
14
|
+
"@weborigami/async-tree": "0.7.0-beta.3",
|
|
15
15
|
"adm-zip": "0.5.17",
|
|
16
16
|
"exif-parser": "0.1.12",
|
|
17
17
|
"watcher": "2.3.1",
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
3
2
|
import optimize from "./optimize.js";
|
|
4
3
|
import { parse } from "./parse.js";
|
|
@@ -33,13 +32,11 @@ function compile(source, options) {
|
|
|
33
32
|
|
|
34
33
|
// Select a path the code will use for caching
|
|
35
34
|
const cachePath = source.cachePath ?? `_compile${count++}`;
|
|
36
|
-
const objectCachePath = cachePath ? trailingSlash.add(cachePath) : null;
|
|
37
35
|
|
|
38
36
|
// Optimize the code
|
|
39
37
|
const optimized = optimize(code, {
|
|
40
38
|
cachePath,
|
|
41
39
|
globals,
|
|
42
|
-
objectCachePath,
|
|
43
40
|
});
|
|
44
41
|
|
|
45
42
|
// Create a function that executes the optimized code.
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import jsGlobals from "../project/jsGlobals.js";
|
|
3
3
|
import {
|
|
4
4
|
KEY_TYPE,
|
|
@@ -31,9 +31,13 @@ export const REFERENCE_EXTERNAL = 4;
|
|
|
31
31
|
* @returns {AnnotatedCode}
|
|
32
32
|
*/
|
|
33
33
|
export default function optimize(code, options = {}) {
|
|
34
|
-
// Cache path for this source file
|
|
35
|
-
const cachePath = options.cachePath ?? null;
|
|
36
34
|
const globals = options.globals ?? jsGlobals;
|
|
35
|
+
const cachePath = options.cachePath; // top-level cache path for this code
|
|
36
|
+
// Cache path for attached objects
|
|
37
|
+
const objectCachePath =
|
|
38
|
+
options.objectCachePath ??
|
|
39
|
+
(cachePath ? trailingSlash.add(cachePath) : null);
|
|
40
|
+
const attached = options.attached ?? true;
|
|
37
41
|
|
|
38
42
|
// The locals is an array, one item for each function or object context that
|
|
39
43
|
// has been entered. The array grows to the right. Array items are objects
|
|
@@ -66,7 +70,8 @@ export default function optimize(code, options = {}) {
|
|
|
66
70
|
return inlineLiteral(code);
|
|
67
71
|
|
|
68
72
|
case ops.object:
|
|
69
|
-
const
|
|
73
|
+
const entries = args.slice(1);
|
|
74
|
+
const propertyNames = getPropertyNames(entries);
|
|
70
75
|
locals.push({
|
|
71
76
|
type: REFERENCE_INHERITED,
|
|
72
77
|
names: propertyNames,
|
|
@@ -80,14 +85,24 @@ export default function optimize(code, options = {}) {
|
|
|
80
85
|
if (op === ops.object) {
|
|
81
86
|
if (index === 0) {
|
|
82
87
|
return child; // return op as is
|
|
88
|
+
} else if (index === 1) {
|
|
89
|
+
// Replace null cache path with actual value
|
|
90
|
+
return objectCachePath;
|
|
83
91
|
} else {
|
|
84
92
|
// Object entry
|
|
85
93
|
const [key, value] = child;
|
|
86
94
|
const adjustedLocals = avoidLocalRecursion(locals, key);
|
|
87
95
|
const isStringKey = typeof key === "string";
|
|
96
|
+
const cachePathKey = isStringKey
|
|
97
|
+
? baseKeyName(key)
|
|
98
|
+
: `_arg${index - 2}`;
|
|
99
|
+
const childObjectCachePath = attached
|
|
100
|
+
? SystemCacheMap.joinPath(objectCachePath, cachePathKey + "/")
|
|
101
|
+
: objectCachePath;
|
|
88
102
|
const childOptions = {
|
|
89
103
|
...options,
|
|
90
104
|
locals: adjustedLocals,
|
|
105
|
+
objectCachePath: childObjectCachePath,
|
|
91
106
|
};
|
|
92
107
|
const optimizedKey = isStringKey
|
|
93
108
|
? key
|
|
@@ -98,12 +113,30 @@ export default function optimize(code, options = {}) {
|
|
|
98
113
|
);
|
|
99
114
|
return annotate([optimizedKey, optimizedValue], child.location);
|
|
100
115
|
}
|
|
116
|
+
} else if (op === ops.getter) {
|
|
117
|
+
if (index === 0) {
|
|
118
|
+
return child; // return op as is
|
|
119
|
+
} else {
|
|
120
|
+
// Already set up to optimize the (single) child
|
|
121
|
+
return optimize(/** @type {AnnotatedCode} */ (child), options);
|
|
122
|
+
}
|
|
101
123
|
} else if (Array.isArray(child) && "location" in child) {
|
|
102
124
|
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
103
125
|
// does this descend into?
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
let childObjectCachePath = objectCachePath;
|
|
127
|
+
if (attached) {
|
|
128
|
+
// Objects below this point are detached, use `_objects` cache path
|
|
129
|
+
childObjectCachePath = SystemCacheMap.joinPath(
|
|
130
|
+
objectCachePath,
|
|
131
|
+
`_objects/`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const childOptions = {
|
|
135
|
+
...options,
|
|
136
|
+
attached: false,
|
|
137
|
+
locals,
|
|
138
|
+
objectCachePath: childObjectCachePath,
|
|
139
|
+
};
|
|
107
140
|
return optimize(/** @type {AnnotatedCode} */ (child), childOptions);
|
|
108
141
|
} else {
|
|
109
142
|
return child;
|
|
@@ -124,23 +157,20 @@ export default function optimize(code, options = {}) {
|
|
|
124
157
|
// remove any local variable with that name from the stack of locals to avoid a
|
|
125
158
|
// recursive reference.
|
|
126
159
|
function avoidLocalRecursion(locals, key) {
|
|
127
|
-
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
128
|
-
// Non-enumerable property, remove parentheses
|
|
129
|
-
key = key.slice(1, -1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
160
|
const currentFrameIndex = locals.length - 1;
|
|
133
161
|
if (locals[currentFrameIndex]?.type !== REFERENCE_INHERITED) {
|
|
134
162
|
// Not an inherited context, nothing to do
|
|
135
163
|
return locals;
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
const baseKey = baseKeyName(key);
|
|
167
|
+
|
|
138
168
|
// See if the key matches any of the local variable names in the current
|
|
139
169
|
// context (ignoring trailing slashes)
|
|
140
170
|
const matchingKeyIndex = locals[currentFrameIndex].names.findIndex(
|
|
141
171
|
(name) =>
|
|
142
172
|
// Ignore trailing slashes when comparing keys
|
|
143
|
-
trailingSlash.remove(name) === trailingSlash.remove(
|
|
173
|
+
trailingSlash.remove(name) === trailingSlash.remove(baseKey),
|
|
144
174
|
);
|
|
145
175
|
|
|
146
176
|
if (matchingKeyIndex >= 0) {
|
|
@@ -158,11 +188,12 @@ function avoidLocalRecursion(locals, key) {
|
|
|
158
188
|
}
|
|
159
189
|
}
|
|
160
190
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
function baseKeyName(key) {
|
|
192
|
+
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
193
|
+
// Non-enumerable property, remove parentheses
|
|
194
|
+
key = key.slice(1, -1);
|
|
195
|
+
}
|
|
196
|
+
return key;
|
|
166
197
|
}
|
|
167
198
|
|
|
168
199
|
// A reference with periods like x.y.z
|
|
@@ -366,6 +397,13 @@ function resolvePath(code, globals, locals, cachePath) {
|
|
|
366
397
|
|
|
367
398
|
let { type, result } = reference(head, globals, locals);
|
|
368
399
|
|
|
400
|
+
if (type === REFERENCE_EXTERNAL && cachePath) {
|
|
401
|
+
// External references should be cached
|
|
402
|
+
const headKey = keyFromCode(head);
|
|
403
|
+
const refCachePath = SystemCacheMap.joinPath(cachePath, "_refs", headKey);
|
|
404
|
+
result = annotate([ops.cache, refCachePath, result], head.location);
|
|
405
|
+
}
|
|
406
|
+
|
|
369
407
|
if (tail.length > 0) {
|
|
370
408
|
// If the result is a traversal, we can safely extend it
|
|
371
409
|
const extendResult =
|
|
@@ -383,10 +421,5 @@ function resolvePath(code, globals, locals, cachePath) {
|
|
|
383
421
|
}
|
|
384
422
|
}
|
|
385
423
|
|
|
386
|
-
if (type === REFERENCE_EXTERNAL && cachePath) {
|
|
387
|
-
// External references should be cached
|
|
388
|
-
return cacheExternalPath(result, cachePath);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
424
|
return result;
|
|
392
425
|
}
|
|
@@ -272,7 +272,7 @@ export function makeDocument(front, body, location) {
|
|
|
272
272
|
entries.push(annotate(["_body", body], location));
|
|
273
273
|
|
|
274
274
|
// Return the code for the document object
|
|
275
|
-
return annotate([ops.object, ...entries], location);
|
|
275
|
+
return annotate([ops.object, null, ...entries], location);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
@@ -341,7 +341,7 @@ function makeMerge(spreads, location) {
|
|
|
341
341
|
|
|
342
342
|
for (const spread of spreads) {
|
|
343
343
|
if (spread[0] === ops.object) {
|
|
344
|
-
const spreadEntries = spread.slice(
|
|
344
|
+
const spreadEntries = spread.slice(2);
|
|
345
345
|
topEntries.push(...spreadEntries);
|
|
346
346
|
// Also add an object to the result with indirect references
|
|
347
347
|
const indirectEntries = spreadEntries.map((entry) => {
|
|
@@ -352,7 +352,7 @@ function makeMerge(spreads, location) {
|
|
|
352
352
|
return annotate([key, getter], entry.location);
|
|
353
353
|
});
|
|
354
354
|
const indirectObject = annotate(
|
|
355
|
-
[ops.object, ...indirectEntries],
|
|
355
|
+
[ops.object, null, ...indirectEntries],
|
|
356
356
|
location,
|
|
357
357
|
);
|
|
358
358
|
resultEntries.push(indirectObject);
|
|
@@ -368,7 +368,7 @@ function makeMerge(spreads, location) {
|
|
|
368
368
|
topEntries.push(annotate(["_result", result], location));
|
|
369
369
|
|
|
370
370
|
// Construct the top-level object
|
|
371
|
-
const topObject = annotate([ops.object, ...topEntries], location);
|
|
371
|
+
const topObject = annotate([ops.object, null, ...topEntries], location);
|
|
372
372
|
|
|
373
373
|
// Get the _result property
|
|
374
374
|
const code = annotate([topObject, "_result"], location);
|
|
@@ -390,14 +390,17 @@ export function makeObject(entries, location) {
|
|
|
390
390
|
if (key === markers.spread) {
|
|
391
391
|
if (value[0] === ops.object) {
|
|
392
392
|
// Spread of an object; fold into current object
|
|
393
|
-
const spreadEntries = value.slice(
|
|
393
|
+
const spreadEntries = value.slice(2);
|
|
394
394
|
currentEntries.push(...spreadEntries);
|
|
395
395
|
} else {
|
|
396
396
|
// Spread of a tree; accumulate
|
|
397
397
|
if (currentEntries.length > 0) {
|
|
398
398
|
const location = { ...currentEntries[0].location };
|
|
399
399
|
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
400
|
-
const spread = annotate(
|
|
400
|
+
const spread = annotate(
|
|
401
|
+
[ops.object, null, ...currentEntries],
|
|
402
|
+
location,
|
|
403
|
+
);
|
|
401
404
|
spreads.push(spread);
|
|
402
405
|
currentEntries = [];
|
|
403
406
|
}
|
|
@@ -424,7 +427,7 @@ export function makeObject(entries, location) {
|
|
|
424
427
|
if (currentEntries.length > 0) {
|
|
425
428
|
const location = { ...currentEntries[0].location };
|
|
426
429
|
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
427
|
-
const spread = annotate([ops.object, ...currentEntries], location);
|
|
430
|
+
const spread = annotate([ops.object, null, ...currentEntries], location);
|
|
428
431
|
spreads.push(spread);
|
|
429
432
|
currentEntries = [];
|
|
430
433
|
}
|
|
@@ -442,7 +445,7 @@ export function makeObject(entries, location) {
|
|
|
442
445
|
}
|
|
443
446
|
} else {
|
|
444
447
|
// Empty object
|
|
445
|
-
code = [ops.object];
|
|
448
|
+
code = [ops.object, null];
|
|
446
449
|
}
|
|
447
450
|
|
|
448
451
|
return annotate(code, location);
|
|
@@ -30,6 +30,7 @@ export default function getSource(packed, options = {}) {
|
|
|
30
30
|
const parentPath = /** @type {any} */ (parent).path;
|
|
31
31
|
cachePath = SystemCacheMap.joinPath(parentPath, sourceName);
|
|
32
32
|
} else {
|
|
33
|
+
// TODO: Avoid cache path collisions in this case
|
|
33
34
|
cachePath = sourceName;
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -3,7 +3,7 @@ import * as compile from "../compiler/compile.js";
|
|
|
3
3
|
import coreGlobals from "../project/coreGlobals.js";
|
|
4
4
|
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
5
5
|
import getSource from "./getSource.js";
|
|
6
|
-
import
|
|
6
|
+
import processOrigamiExport from "./processOrigamiExport.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* An Origami expression file
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
// Evaluate the program
|
|
32
32
|
let result = await fn();
|
|
33
33
|
|
|
34
|
-
result =
|
|
34
|
+
result = processOrigamiExport(result, source, parent);
|
|
35
35
|
|
|
36
36
|
return result;
|
|
37
37
|
},
|
|
@@ -4,7 +4,7 @@ import coreGlobals from "../project/coreGlobals.js";
|
|
|
4
4
|
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
5
5
|
import addExtensionKeyFn from "./addExtensionKeyFn.js";
|
|
6
6
|
import getSource from "./getSource.js";
|
|
7
|
-
import
|
|
7
|
+
import processOrigamiExport from "./processOrigamiExport.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* An Origami template document: a plain text file that contains Origami
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
// Invoke the definition to get back the template function or object
|
|
32
32
|
let result = await fn();
|
|
33
33
|
|
|
34
|
-
result =
|
|
34
|
+
result = processOrigamiExport(result, source, parent);
|
|
35
35
|
|
|
36
36
|
if (result instanceof Function) {
|
|
37
37
|
const key = options.key;
|
|
@@ -5,7 +5,7 @@ import enableValueCaching from "../runtime/enableValueCaching.js";
|
|
|
5
5
|
* Given an object that's the top-level result of an Origami file, perform any
|
|
6
6
|
* necessary processing.
|
|
7
7
|
*/
|
|
8
|
-
export default function
|
|
8
|
+
export default function processOrigamiExport(value, source, parent) {
|
|
9
9
|
setParent(value, parent);
|
|
10
10
|
|
|
11
11
|
if (source.cachePath) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Mixin } from "../../index.ts";
|
|
2
2
|
|
|
3
|
-
declare const AsyncCacheTransform: Mixin<{
|
|
3
|
+
declare const AsyncCacheTransform: Mixin<{
|
|
4
|
+
cachePathForKey(key: string): string;
|
|
5
|
+
onKeysChange(key: string): void;
|
|
6
|
+
onValueChange(key: string): void;
|
|
7
|
+
}>
|
|
4
8
|
|
|
5
9
|
export default AsyncCacheTransform;
|
|
@@ -45,24 +45,21 @@ export default function AsyncCacheTransform(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
|
-
?
|
|
56
|
-
|
|
50
|
+
? // @ts-ignore
|
|
51
|
+
this[cachePathSymbol]
|
|
52
|
+
: // @ts-ignore
|
|
53
|
+
SystemCacheMap.joinPath(this[cachePathSymbol], key);
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
async delete(key) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
56
|
+
// async delete(key) {
|
|
57
|
+
// const deleted = await super.delete(key);
|
|
58
|
+
// if (typeof key === "string") {
|
|
59
|
+
// systemCache.delete(this.cachePathForKey(key));
|
|
60
|
+
// }
|
|
61
|
+
// return deleted;
|
|
62
|
+
// }
|
|
66
63
|
|
|
67
64
|
async get(key) {
|
|
68
65
|
if (typeof key !== "string" || key.length === 0) {
|
|
@@ -119,16 +116,16 @@ export default function AsyncCacheTransform(Base) {
|
|
|
119
116
|
systemCache.delete(this.cachePathForKey(key));
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
async set(key, value) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
119
|
+
// async set(key, value) {
|
|
120
|
+
// if (typeof key !== "string") {
|
|
121
|
+
// return super.set(key, value);
|
|
122
|
+
// }
|
|
123
|
+
// systemCache.delete(this.cachePathForKey(key));
|
|
124
|
+
// if (!this.has(key)) {
|
|
125
|
+
// // Adding a new key, need to invalidate cached keys
|
|
126
|
+
// this.invalidateKeys();
|
|
127
|
+
// }
|
|
128
|
+
// super.set(key, value);
|
|
129
|
+
// }
|
|
133
130
|
};
|
|
134
131
|
}
|
|
@@ -4,15 +4,15 @@ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
|
4
4
|
import ImportModulesMixin from "./ImportModulesMixin.js";
|
|
5
5
|
import SyncCacheTransform from "./SyncCacheTransform.js";
|
|
6
6
|
import WatchFilesMixin from "./WatchFilesMixin.js";
|
|
7
|
-
import { noCacheSymbol } from "./symbols.js";
|
|
7
|
+
import { cachePathSymbol, noCacheSymbol } from "./symbols.js";
|
|
8
8
|
|
|
9
9
|
export default class OrigamiFileMap extends SyncCacheTransform(
|
|
10
10
|
HandleExtensionsTransform(
|
|
11
11
|
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap))),
|
|
12
12
|
),
|
|
13
13
|
) {
|
|
14
|
-
get
|
|
15
|
-
return
|
|
14
|
+
get [cachePathSymbol]() {
|
|
15
|
+
return this.path;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// Workaround to register file paths in the system cache without trailing
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Mixin } from "../../index.ts";
|
|
2
2
|
|
|
3
3
|
declare const SyncCacheTransform: Mixin<{
|
|
4
|
-
get cachePath(): string;
|
|
5
4
|
cachePathForKey(key: string): string;
|
|
5
|
+
onKeysChange(key: string): void;
|
|
6
|
+
onValueChange(key: string): void;
|
|
6
7
|
}>
|
|
7
8
|
|
|
8
9
|
export default SyncCacheTransform;
|
|
@@ -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
|
-
?
|
|
56
|
-
|
|
50
|
+
? // @ts-ignore
|
|
51
|
+
this[cachePathSymbol]
|
|
52
|
+
: // @ts-ignore
|
|
53
|
+
SystemCacheMap.joinPath(this[cachePathSymbol], key);
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
delete(key) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
31
|
+
const dependentEntries = new Map();
|
|
32
|
+
this.visitDependentEntries(path, dependentEntries);
|
|
32
33
|
|
|
33
34
|
// Delete those dependent entries
|
|
34
|
-
for (const deletePath of
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
pathAdditions.set(otherPath, otherEntry);
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
//
|
|
73
|
-
for (const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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,
|
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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?
|