@weborigami/language 0.3.3 → 0.3.4-jse.4
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 +3 -1
- package/package.json +3 -3
- package/src/compiler/compile.js +13 -3
- package/src/compiler/isOrigamiFrontMatter.js +4 -3
- package/src/compiler/optimize.js +273 -106
- package/src/compiler/origami.pegjs +286 -169
- package/src/compiler/parse.js +2069 -1275
- package/src/compiler/parserHelpers.js +155 -146
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +17 -11
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +18 -54
- package/src/runtime/jsGlobals.js +106 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +92 -161
- package/src/runtime/symbols.js +1 -0
- package/src/runtime/{taggedTemplateIndent.js → templateIndent.js} +2 -2
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +60 -30
- package/test/compiler/optimize.test.js +263 -24
- package/test/compiler/parse.test.js +895 -521
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +6 -5
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +103 -82
- package/test/runtime/taggedTemplateIndent.test.js +1 -1
package/main.js
CHANGED
|
@@ -7,13 +7,15 @@ export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
|
7
7
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
|
8
8
|
export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
9
9
|
export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
|
|
10
|
+
export { default as getHandlers } from "./src/runtime/getHandlers.js";
|
|
10
11
|
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
|
11
12
|
export * from "./src/runtime/handlers.js";
|
|
12
13
|
export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
|
|
13
14
|
export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
|
|
15
|
+
export { default as jsGlobals } from "./src/runtime/jsGlobals.js";
|
|
14
16
|
export * as moduleCache from "./src/runtime/moduleCache.js";
|
|
15
17
|
export { default as OrigamiFiles } from "./src/runtime/OrigamiFiles.js";
|
|
16
18
|
export * as symbols from "./src/runtime/symbols.js";
|
|
17
|
-
export { default as taggedTemplateIndent } from "./src/runtime/
|
|
19
|
+
export { default as taggedTemplateIndent } from "./src/runtime/templateIndent.js";
|
|
18
20
|
export { default as TreeEvent } from "./src/runtime/TreeEvent.js";
|
|
19
21
|
export { default as WatchFilesMixin } from "./src/runtime/WatchFilesMixin.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4-jse.4",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"typescript": "5.8.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.3.
|
|
15
|
-
"@weborigami/types": "0.3.
|
|
14
|
+
"@weborigami/async-tree": "0.3.4-jse.4",
|
|
15
|
+
"@weborigami/types": "0.3.4-jse.4",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.7.0"
|
|
18
18
|
},
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
2
|
+
import jsGlobals from "../runtime/jsGlobals.js";
|
|
2
3
|
import optimize from "./optimize.js";
|
|
3
4
|
import { parse } from "./parse.js";
|
|
4
5
|
|
|
5
6
|
function compile(source, options) {
|
|
6
|
-
const {
|
|
7
|
+
const { front, startRule } = options;
|
|
8
|
+
const mode = options.mode ?? "shell";
|
|
9
|
+
const globals = options.globals ?? jsGlobals;
|
|
7
10
|
const enableCaching = options.scopeCaching ?? true;
|
|
8
11
|
if (typeof source === "string") {
|
|
9
12
|
source = { text: source };
|
|
10
13
|
}
|
|
11
|
-
|
|
14
|
+
let code = parse(source.text, {
|
|
15
|
+
front,
|
|
12
16
|
grammarSource: source,
|
|
17
|
+
mode,
|
|
13
18
|
startRule,
|
|
14
19
|
});
|
|
15
|
-
const
|
|
20
|
+
const cache = enableCaching ? {} : null;
|
|
21
|
+
const optimized = optimize(code, {
|
|
22
|
+
cache,
|
|
23
|
+
globals,
|
|
24
|
+
mode,
|
|
25
|
+
});
|
|
16
26
|
const fn = createExpressionFunction(optimized);
|
|
17
27
|
return fn;
|
|
18
28
|
}
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Our heurstic is to see skip any initial alphanumeric or underscore
|
|
5
5
|
* characters, then see if the next character is a parenthesis, dot, slash,
|
|
6
|
-
* curly brace, or equals sign. If so, we assume this is an
|
|
6
|
+
* curly brace, left angle bracket, or equals sign. If so, we assume this is an
|
|
7
|
+
* Origami expression.
|
|
7
8
|
*
|
|
8
9
|
* The goal is to identify Origami front matter like:
|
|
9
10
|
*
|
|
10
11
|
* ```
|
|
11
12
|
* fn(x) function call
|
|
12
13
|
* index.ori() file extension
|
|
13
|
-
* src/data.json
|
|
14
|
+
* <src/data.json> file path
|
|
14
15
|
* // Hello comment
|
|
15
16
|
* { a: 1 } object literal
|
|
16
17
|
* ```
|
|
@@ -22,5 +23,5 @@
|
|
|
22
23
|
* @param {string} text
|
|
23
24
|
*/
|
|
24
25
|
export default function isOrigamiFrontMatter(text) {
|
|
25
|
-
return /^[ \t\r\n]*[A-Za-z0-9_]*[\(\.\/\{
|
|
26
|
+
return /^[ \t\r\n]*[A-Za-z0-9_]*[\(\.\/\{<=]/.test(text);
|
|
26
27
|
}
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,142 +1,309 @@
|
|
|
1
1
|
import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
|
|
2
|
+
import { entryKey } from "../runtime/expressionObject.js";
|
|
2
3
|
import { ops } from "../runtime/internal.js";
|
|
3
|
-
import
|
|
4
|
+
import jsGlobals from "../runtime/jsGlobals.js";
|
|
5
|
+
import { annotate, markers } from "./parserHelpers.js";
|
|
6
|
+
|
|
7
|
+
const REFERENCE_LOCAL = 1;
|
|
8
|
+
const REFERENCE_GLOBAL = 2;
|
|
9
|
+
const REFERENCE_EXTERNAL = 3;
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Optimize an Origami code instruction:
|
|
7
13
|
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
14
|
+
* - Resolve local references to the appropriate context
|
|
15
|
+
* - Resolve global references to the globals object
|
|
16
|
+
* - Resolve other references to external values
|
|
17
|
+
* - Determine whether x.y is a file name or property access
|
|
18
|
+
* - Determine whether x/y is a file path or division operation
|
|
13
19
|
*
|
|
14
20
|
* @typedef {import("./parserHelpers.js").AnnotatedCode} AnnotatedCode
|
|
15
21
|
* @typedef {import("./parserHelpers.js").Code} Code
|
|
16
22
|
*
|
|
17
23
|
* @param {AnnotatedCode} code
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {Record<string, AnnotatedCode>} macros
|
|
20
|
-
* @param {Record<string, AnnotatedCode>} cache
|
|
21
|
-
* @param {Record<string, boolean>} locals
|
|
24
|
+
* @param {any} options
|
|
22
25
|
* @returns {AnnotatedCode}
|
|
23
26
|
*/
|
|
24
|
-
export default function optimize(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
export default function optimize(code, options = {}) {
|
|
28
|
+
const globals = options.globals ?? jsGlobals;
|
|
29
|
+
const cache = options.cache ?? {};
|
|
30
|
+
|
|
31
|
+
// The locals is an array, one item for each function or object context that
|
|
32
|
+
// has been entered. The array grows to the right. The array items are
|
|
33
|
+
// subarrays containing the names of local variables defined in that context.
|
|
34
|
+
const locals = options.locals ? options.locals.slice() : [];
|
|
35
|
+
|
|
31
36
|
// See if we can optimize this level of the code
|
|
32
|
-
const [
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const [op, ...args] = code;
|
|
38
|
+
switch (op) {
|
|
39
|
+
case markers.global:
|
|
40
|
+
// Replace global op with the globals
|
|
41
|
+
return annotate([globals, args[0]], code.location);
|
|
42
|
+
|
|
43
|
+
case markers.traverse:
|
|
44
|
+
return resolvePath(code, globals, locals, cache);
|
|
45
|
+
|
|
35
46
|
case ops.lambda:
|
|
36
47
|
const parameters = args[0];
|
|
37
|
-
|
|
48
|
+
if (parameters.length > 0) {
|
|
49
|
+
const names = parameters.map((param) => param[1]);
|
|
50
|
+
locals.push(names);
|
|
51
|
+
}
|
|
38
52
|
break;
|
|
39
53
|
|
|
40
54
|
case ops.literal:
|
|
41
|
-
|
|
42
|
-
if (!(value instanceof Array)) {
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
55
|
+
return inlineLiteral(code);
|
|
46
56
|
|
|
47
57
|
case ops.object:
|
|
48
58
|
const entries = args;
|
|
49
|
-
|
|
59
|
+
const keys = entries.map(entryKey);
|
|
60
|
+
locals.push(keys);
|
|
50
61
|
break;
|
|
62
|
+
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// Optimize children
|
|
65
|
+
const optimized = annotate(
|
|
66
|
+
code.map((child, index) => {
|
|
67
|
+
// Don't optimize lambda parameter names
|
|
68
|
+
if (op === ops.lambda && index === 1) {
|
|
69
|
+
return child;
|
|
70
|
+
} else if (op === ops.object && index > 0) {
|
|
71
|
+
const [key, value] = child;
|
|
72
|
+
const adjustedLocals = avoidLocalRecursion(locals, key);
|
|
73
|
+
return [
|
|
74
|
+
key,
|
|
75
|
+
optimize(/** @type {AnnotatedCode} */ (value), {
|
|
76
|
+
...options,
|
|
77
|
+
locals: adjustedLocals,
|
|
78
|
+
}),
|
|
79
|
+
];
|
|
80
|
+
} else if (Array.isArray(child) && "location" in child) {
|
|
81
|
+
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
82
|
+
// does this descend into?
|
|
83
|
+
return optimize(/** @type {AnnotatedCode} */ (child), {
|
|
84
|
+
...options,
|
|
85
|
+
locals,
|
|
86
|
+
});
|
|
70
87
|
} else {
|
|
71
|
-
|
|
72
|
-
return code;
|
|
88
|
+
return child;
|
|
73
89
|
}
|
|
90
|
+
}),
|
|
91
|
+
code.location
|
|
92
|
+
);
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.slice(1)
|
|
86
|
-
.every((arg) => arg[0] === ops.literal);
|
|
87
|
-
if (allLiterals) {
|
|
88
|
-
// Convert to ops.external
|
|
89
|
-
const keys = args.map((arg) => arg[1]);
|
|
90
|
-
const path = pathFromKeys(keys);
|
|
91
|
-
/** @type {Code} */
|
|
92
|
-
const optimized = [ops.external, path, code, cache];
|
|
93
|
-
return annotate(optimized, code.location);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
94
|
+
return annotate(optimized, code.location);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// When defining a property named `key` (or `key/` or `(key)`), we need to
|
|
98
|
+
// remove any local variable with that name from the stack of locals to avoid a
|
|
99
|
+
// recursive reference.
|
|
100
|
+
function avoidLocalRecursion(locals, key) {
|
|
101
|
+
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
102
|
+
// Non-enumerable property, remove parentheses
|
|
103
|
+
key = key.slice(1, -1);
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const currentFrame = locals.length - 1;
|
|
107
|
+
const matchingKeyIndex = locals[currentFrame].findIndex(
|
|
108
|
+
(localKey) =>
|
|
109
|
+
// Ignore trailing slashes when comparing keys
|
|
110
|
+
trailingSlash.remove(localKey) === trailingSlash.remove(key)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (matchingKeyIndex >= 0) {
|
|
114
|
+
// Remove the key from the current context's locals
|
|
115
|
+
const adjustedLocals = locals.slice();
|
|
116
|
+
adjustedLocals[currentFrame] = adjustedLocals[currentFrame].slice();
|
|
117
|
+
adjustedLocals[currentFrame].splice(matchingKeyIndex, 1);
|
|
118
|
+
return adjustedLocals;
|
|
108
119
|
} else {
|
|
109
|
-
|
|
120
|
+
return locals;
|
|
110
121
|
}
|
|
122
|
+
}
|
|
111
123
|
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else if (Array.isArray(child) && "location" in child) {
|
|
118
|
-
// Review: This currently descends into arrays that are not instructions,
|
|
119
|
-
// such as the entries of an ops.object. This should be harmless, but it'd
|
|
120
|
-
// be preferable to only descend into instructions. This would require
|
|
121
|
-
// surrounding ops.object entries with ops.array.
|
|
122
|
-
return optimize(
|
|
123
|
-
/** @type {AnnotatedCode} */ (child),
|
|
124
|
-
enableCaching,
|
|
125
|
-
macros,
|
|
126
|
-
cache,
|
|
127
|
-
updatedLocals
|
|
128
|
-
);
|
|
129
|
-
} else {
|
|
130
|
-
return child;
|
|
131
|
-
}
|
|
132
|
-
});
|
|
124
|
+
function cachePath(code, cache) {
|
|
125
|
+
const keys = code.map(keyFromCode).filter((key) => key !== null);
|
|
126
|
+
const path = pathFromKeys(keys);
|
|
127
|
+
return annotate([ops.cache, cache, path, code], code.location);
|
|
128
|
+
}
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
// A reference with periods like x.y.z
|
|
131
|
+
function compoundReference(key, globals, locals, location) {
|
|
132
|
+
const parts = key.split(".");
|
|
133
|
+
if (parts.length === 1) {
|
|
134
|
+
// Not a compound reference
|
|
135
|
+
return { type: REFERENCE_EXTERNAL, result: null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check first part to see if it's a global or local reference
|
|
139
|
+
const [head, ...tail] = parts;
|
|
140
|
+
const type = referenceType(head, globals, locals);
|
|
141
|
+
let result;
|
|
142
|
+
if (type === REFERENCE_GLOBAL) {
|
|
143
|
+
result = globalReference(head, globals, location);
|
|
144
|
+
} else if (type === REFERENCE_LOCAL) {
|
|
145
|
+
result = localReference(head, locals, location);
|
|
146
|
+
} else {
|
|
147
|
+
// Not a compound reference
|
|
148
|
+
return { type: REFERENCE_EXTERNAL, result: null };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Process the remaining parts as property accesses
|
|
152
|
+
while (tail.length > 0) {
|
|
153
|
+
const part = tail.shift();
|
|
154
|
+
result = annotate([result, part], location);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { type, result };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function externalReference(key, locals, location) {
|
|
161
|
+
const scope = scopeCall(locals, location);
|
|
162
|
+
const literal = annotate([ops.literal, key], location);
|
|
163
|
+
return annotate([scope, literal], location);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Determine how many contexts up we need to go for a local
|
|
167
|
+
function getLocalReferenceDepth(locals, key) {
|
|
168
|
+
const contextIndex = locals.findLastIndex((names) =>
|
|
169
|
+
names.some(
|
|
170
|
+
(name) => trailingSlash.remove(name) === trailingSlash.remove(key)
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
if (contextIndex < 0) {
|
|
174
|
+
return -1; // Not a local reference
|
|
175
|
+
}
|
|
176
|
+
const depth = locals.length - contextIndex - 1;
|
|
177
|
+
return depth;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function globalReference(key, globals, location) {
|
|
181
|
+
const normalized = trailingSlash.remove(key);
|
|
182
|
+
return annotate([globals, normalized], location);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function inlineLiteral(code) {
|
|
186
|
+
// If the literal value is an array, it's likely the strings array
|
|
187
|
+
// of a template literal, so return it as is.
|
|
188
|
+
return code[0] === ops.literal && !Array.isArray(code[1]) ? code[1] : code;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function localReference(key, locals, location) {
|
|
192
|
+
const normalized = trailingSlash.remove(key);
|
|
193
|
+
const depth = getLocalReferenceDepth(locals, normalized);
|
|
194
|
+
const context = [ops.context];
|
|
195
|
+
if (depth > 0) {
|
|
196
|
+
context.push(depth);
|
|
197
|
+
}
|
|
198
|
+
const contextCall = annotate(context, location);
|
|
199
|
+
const literal = annotate([ops.literal, key], location);
|
|
200
|
+
return annotate([contextCall, literal], location);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function keyFromCode(code) {
|
|
204
|
+
const op = code instanceof Array ? code[0] : code;
|
|
205
|
+
switch (op) {
|
|
206
|
+
case ops.homeDirectory:
|
|
207
|
+
return "~";
|
|
208
|
+
|
|
209
|
+
case markers.external:
|
|
210
|
+
case markers.global:
|
|
211
|
+
case markers.reference:
|
|
212
|
+
case ops.literal:
|
|
213
|
+
return code[1];
|
|
214
|
+
|
|
215
|
+
case ops.rootDirectory:
|
|
216
|
+
return "/";
|
|
217
|
+
|
|
218
|
+
default:
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
135
221
|
}
|
|
136
222
|
|
|
137
|
-
function
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
223
|
+
function reference(code, globals, locals) {
|
|
224
|
+
const key = keyFromCode(code);
|
|
225
|
+
const normalized = trailingSlash.remove(key);
|
|
226
|
+
const location = code.location;
|
|
227
|
+
|
|
228
|
+
if (normalized === "~") {
|
|
229
|
+
// Special case for home directory
|
|
230
|
+
return {
|
|
231
|
+
type: REFERENCE_EXTERNAL,
|
|
232
|
+
result: annotate([ops.homeDirectory], location),
|
|
233
|
+
};
|
|
234
|
+
} else if (normalized === "") {
|
|
235
|
+
// Special case for root directory
|
|
236
|
+
return {
|
|
237
|
+
type: REFERENCE_EXTERNAL,
|
|
238
|
+
result: annotate([ops.rootDirectory], location),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (code[0] === markers.external) {
|
|
243
|
+
// Explicit external reference
|
|
244
|
+
return {
|
|
245
|
+
type: REFERENCE_EXTERNAL,
|
|
246
|
+
result: externalReference(key, locals, location),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// See if the whole key is a global or local variable
|
|
251
|
+
let type = referenceType(key, globals, locals);
|
|
252
|
+
let result;
|
|
253
|
+
if (type === REFERENCE_GLOBAL) {
|
|
254
|
+
result = globalReference(key, globals, location);
|
|
255
|
+
} else if (type === REFERENCE_LOCAL) {
|
|
256
|
+
result = localReference(key, locals, location);
|
|
257
|
+
} else {
|
|
258
|
+
// Try key as a compound reference x.y.z
|
|
259
|
+
const compound = compoundReference(key, globals, locals, location);
|
|
260
|
+
result = compound?.result;
|
|
261
|
+
type = compound?.type;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!result) {
|
|
265
|
+
// If none of the above worked, it must be an external reference
|
|
266
|
+
result = externalReference(key, locals, location);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { type, result };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function referenceType(key, globals, locals) {
|
|
273
|
+
// Check if the key is a global variable
|
|
274
|
+
const normalized = trailingSlash.remove(key);
|
|
275
|
+
if (getLocalReferenceDepth(locals, normalized) >= 0) {
|
|
276
|
+
return REFERENCE_LOCAL;
|
|
277
|
+
} else if (normalized in globals) {
|
|
278
|
+
return REFERENCE_GLOBAL;
|
|
279
|
+
} else {
|
|
280
|
+
return REFERENCE_EXTERNAL;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolvePath(code, globals, locals, cache) {
|
|
285
|
+
const args = code.slice(1);
|
|
286
|
+
const [head, ...tail] = args;
|
|
287
|
+
|
|
288
|
+
let { type, result } = reference(head, globals, locals);
|
|
289
|
+
|
|
290
|
+
result.push(...tail);
|
|
291
|
+
|
|
292
|
+
if (type === REFERENCE_EXTERNAL && cache !== null) {
|
|
293
|
+
// Cache external path
|
|
294
|
+
return cachePath(result, cache);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function scopeCall(locals, location) {
|
|
301
|
+
const depth = locals.length;
|
|
302
|
+
const code = [ops.scope];
|
|
303
|
+
if (depth > 0) {
|
|
304
|
+
// Add context for appropriate depth to scope call
|
|
305
|
+
const contextCode = annotate([ops.context, depth], location);
|
|
306
|
+
code.push(contextCode);
|
|
307
|
+
}
|
|
308
|
+
return annotate(code, location);
|
|
142
309
|
}
|