@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/src/runtime/errors.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/runtime/execute.js
CHANGED
|
@@ -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
|
-
|
|
39
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
31
|
-
const
|
|
32
|
-
let message
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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();
|
package/src/runtime/ops.js
CHANGED
|
@@ -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();
|
|
@@ -15,6 +15,16 @@ import {
|
|
|
15
15
|
} from "./codeHelpers.js";
|
|
16
16
|
|
|
17
17
|
describe("optimize", () => {
|
|
18
|
+
test("define cache path based on source", () => {
|
|
19
|
+
const expression = "{ a = { b: 1 } }";
|
|
20
|
+
const expected = [
|
|
21
|
+
ops.object,
|
|
22
|
+
"/folder/test.ori/",
|
|
23
|
+
["a", [ops.getter, [ops.object, "/folder/test.ori/a/", ["b", 1]]]],
|
|
24
|
+
];
|
|
25
|
+
assertCompile(expression, expected);
|
|
26
|
+
});
|
|
27
|
+
|
|
18
28
|
test("change local references to context references", () => {
|
|
19
29
|
const expression = `(name) => {
|
|
20
30
|
a: name,
|
|
@@ -26,6 +36,7 @@ describe("optimize", () => {
|
|
|
26
36
|
[["name", [[ops.params, 0], 0]]],
|
|
27
37
|
[
|
|
28
38
|
ops.object,
|
|
39
|
+
"/folder/test.ori/_objects/",
|
|
29
40
|
[
|
|
30
41
|
"a",
|
|
31
42
|
[
|
|
@@ -49,11 +60,13 @@ describe("optimize", () => {
|
|
|
49
60
|
const expression = `{ a: 1, more: { a } }`;
|
|
50
61
|
const expected = [
|
|
51
62
|
ops.object,
|
|
63
|
+
"/folder/test.ori/",
|
|
52
64
|
["a", 1],
|
|
53
65
|
[
|
|
54
66
|
"more",
|
|
55
67
|
[
|
|
56
68
|
ops.object,
|
|
69
|
+
"/folder/test.ori/more/",
|
|
57
70
|
[
|
|
58
71
|
"a",
|
|
59
72
|
[
|
|
@@ -76,11 +89,13 @@ describe("optimize", () => {
|
|
|
76
89
|
}`;
|
|
77
90
|
const expected = [
|
|
78
91
|
ops.object,
|
|
92
|
+
"/folder/test.ori/",
|
|
79
93
|
["name", "Alice"],
|
|
80
94
|
[
|
|
81
95
|
"user",
|
|
82
96
|
[
|
|
83
97
|
ops.object,
|
|
98
|
+
"/folder/test.ori/user/",
|
|
84
99
|
[
|
|
85
100
|
"name",
|
|
86
101
|
[
|
|
@@ -95,7 +110,7 @@ describe("optimize", () => {
|
|
|
95
110
|
assertCompile(expression, expected, "shell");
|
|
96
111
|
});
|
|
97
112
|
|
|
98
|
-
describe
|
|
113
|
+
describe("resolve reference", () => {
|
|
99
114
|
test("external reference", () => {
|
|
100
115
|
// Compilation of `folder` where folder isn't a variable
|
|
101
116
|
const code = createCode([
|
|
@@ -115,6 +130,31 @@ describe("optimize", () => {
|
|
|
115
130
|
);
|
|
116
131
|
});
|
|
117
132
|
|
|
133
|
+
test("external reference with path", () => {
|
|
134
|
+
// Compilation of `src/templates/page.ori` where src isn't a variable
|
|
135
|
+
const code = createCode([
|
|
136
|
+
markers.traverse,
|
|
137
|
+
[markers.reference, "src/"],
|
|
138
|
+
[ops.literal, "templates/"],
|
|
139
|
+
[ops.literal, "page.ori"],
|
|
140
|
+
]);
|
|
141
|
+
const parent = {};
|
|
142
|
+
const expected = [
|
|
143
|
+
[
|
|
144
|
+
ops.cache,
|
|
145
|
+
"test.ori/_refs/src/",
|
|
146
|
+
[[ops.scope], [ops.literal, "src/"]],
|
|
147
|
+
],
|
|
148
|
+
[ops.literal, "templates/"],
|
|
149
|
+
[ops.literal, "page.ori"],
|
|
150
|
+
];
|
|
151
|
+
const globals = {};
|
|
152
|
+
assertCodeEqual(
|
|
153
|
+
optimize(code, { cachePath: "test.ori", globals, parent }),
|
|
154
|
+
expected,
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
118
158
|
test("external reference with implied unpack", () => {
|
|
119
159
|
// Compilation of `greet.ori("world")`
|
|
120
160
|
const code = createCode([
|
|
@@ -141,6 +181,7 @@ describe("optimize", () => {
|
|
|
141
181
|
// Compilation of `{ (posts) = posts.txt }`
|
|
142
182
|
const code = createCode([
|
|
143
183
|
ops.object,
|
|
184
|
+
null,
|
|
144
185
|
[
|
|
145
186
|
"(posts)",
|
|
146
187
|
[ops.getter, [markers.traverse, [markers.reference, "posts.txt"]]],
|
|
@@ -149,13 +190,14 @@ describe("optimize", () => {
|
|
|
149
190
|
const parent = {};
|
|
150
191
|
const expected = [
|
|
151
192
|
ops.object,
|
|
193
|
+
"/folder/test.ori/",
|
|
152
194
|
[
|
|
153
195
|
"(posts)",
|
|
154
196
|
[
|
|
155
197
|
ops.getter,
|
|
156
198
|
[
|
|
157
199
|
ops.cache,
|
|
158
|
-
"test.ori/_refs/posts.txt",
|
|
200
|
+
"/folder/test.ori/_refs/posts.txt",
|
|
159
201
|
[[ops.scope], [ops.literal, "posts.txt"]],
|
|
160
202
|
],
|
|
161
203
|
],
|
|
@@ -163,7 +205,7 @@ describe("optimize", () => {
|
|
|
163
205
|
];
|
|
164
206
|
const globals = {};
|
|
165
207
|
assertCodeEqual(
|
|
166
|
-
optimize(code, { cachePath: "test.ori", globals, parent }),
|
|
208
|
+
optimize(code, { cachePath: "/folder/test.ori", globals, parent }),
|
|
167
209
|
expected,
|
|
168
210
|
);
|
|
169
211
|
});
|
|
@@ -233,9 +275,9 @@ describe("optimize", () => {
|
|
|
233
275
|
[ops.literal, "passwd"],
|
|
234
276
|
]);
|
|
235
277
|
const expected = [
|
|
236
|
-
ops.cache,
|
|
237
|
-
"
|
|
238
|
-
[
|
|
278
|
+
[ops.cache, "test.ori/_refs//", [ops.rootDirectory]],
|
|
279
|
+
[ops.literal, "etc/"],
|
|
280
|
+
[ops.literal, "passwd"],
|
|
239
281
|
];
|
|
240
282
|
assertCodeEqual(optimize(code, { cachePath: "test.ori" }), expected);
|
|
241
283
|
});
|
|
@@ -259,14 +301,13 @@ describe("optimize", () => {
|
|
|
259
301
|
]);
|
|
260
302
|
const parent = {};
|
|
261
303
|
const expected = [
|
|
262
|
-
ops.cache,
|
|
263
|
-
"test.ori/_refs/path/to/file",
|
|
264
304
|
[
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
[ops.literal, "
|
|
268
|
-
[ops.literal, "file"],
|
|
305
|
+
ops.cache,
|
|
306
|
+
"test.ori/_refs/path/",
|
|
307
|
+
[[ops.scope], [ops.literal, "path/"]],
|
|
269
308
|
],
|
|
309
|
+
[ops.literal, "to/"],
|
|
310
|
+
[ops.literal, "file"],
|
|
270
311
|
];
|
|
271
312
|
assertCodeEqual(
|
|
272
313
|
optimize(code, { cachePath: "test.ori", parent }),
|
|
@@ -284,9 +325,12 @@ describe("optimize", () => {
|
|
|
284
325
|
const globals = {};
|
|
285
326
|
const parent = {};
|
|
286
327
|
const expected = [
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
328
|
+
[
|
|
329
|
+
ops.cache,
|
|
330
|
+
"test.ori/_refs/package.json/",
|
|
331
|
+
[[ops.scope], [ops.literal, "package.json/"]],
|
|
332
|
+
],
|
|
333
|
+
[ops.literal, "name"],
|
|
290
334
|
];
|
|
291
335
|
assertCodeEqual(
|
|
292
336
|
optimize(code, { cachePath: "test.ori", globals, parent }),
|
|
@@ -319,6 +363,7 @@ function assertCompile(expression, expected, mode = "shell") {
|
|
|
319
363
|
typeof expression !== "string"
|
|
320
364
|
? expression
|
|
321
365
|
: {
|
|
366
|
+
cachePath: "/folder/test.ori",
|
|
322
367
|
text: expression,
|
|
323
368
|
relativePath: "test.ori",
|
|
324
369
|
};
|