@weborigami/language 0.3.3-jse.3 → 0.3.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 -3
- package/package.json +3 -3
- package/src/compiler/compile.js +2 -10
- package/src/compiler/optimize.js +93 -147
- package/src/compiler/origami.pegjs +67 -171
- package/src/compiler/parse.js +946 -1810
- package/src/compiler/parserHelpers.js +58 -159
- package/src/runtime/HandleExtensionsTransform.js +1 -10
- package/src/runtime/evaluate.js +35 -28
- package/src/runtime/expressionObject.js +4 -4
- package/src/runtime/handlers.js +54 -18
- package/src/runtime/mergeTrees.js +5 -0
- package/src/runtime/ops.js +156 -101
- package/src/runtime/symbols.js +0 -1
- package/src/runtime/{templateIndent.js → taggedTemplateIndent.js} +2 -2
- package/test/compiler/codeHelpers.js +1 -3
- package/test/compiler/compile.test.js +27 -52
- package/test/compiler/optimize.test.js +23 -92
- package/test/compiler/parse.test.js +342 -574
- package/test/runtime/evaluate.test.js +20 -4
- package/test/runtime/expressionObject.test.js +4 -5
- package/test/runtime/handlers.test.js +10 -19
- package/test/runtime/mergeTrees.test.js +5 -0
- package/test/runtime/ops.test.js +82 -103
- package/test/runtime/taggedTemplateIndent.test.js +1 -1
- package/src/runtime/getHandlers.js +0 -10
- package/src/runtime/jsGlobals.js +0 -99
- package/src/runtime/templateStandard.js +0 -13
- package/test/runtime/templateText.test.js +0 -18
package/main.js
CHANGED
|
@@ -7,15 +7,13 @@ 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";
|
|
11
10
|
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
|
12
11
|
export * from "./src/runtime/handlers.js";
|
|
13
12
|
export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
|
|
14
13
|
export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
|
|
15
|
-
export { default as jsGlobals } from "./src/runtime/jsGlobals.js";
|
|
16
14
|
export * as moduleCache from "./src/runtime/moduleCache.js";
|
|
17
15
|
export { default as OrigamiFiles } from "./src/runtime/OrigamiFiles.js";
|
|
18
16
|
export * as symbols from "./src/runtime/symbols.js";
|
|
19
|
-
export { default as taggedTemplateIndent } from "./src/runtime/
|
|
17
|
+
export { default as taggedTemplateIndent } from "./src/runtime/taggedTemplateIndent.js";
|
|
20
18
|
export { default as TreeEvent } from "./src/runtime/TreeEvent.js";
|
|
21
19
|
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
|
|
3
|
+
"version": "0.3.3",
|
|
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.3
|
|
15
|
-
"@weborigami/types": "0.3.3
|
|
14
|
+
"@weborigami/async-tree": "0.3.3",
|
|
15
|
+
"@weborigami/types": "0.3.3",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.7.0"
|
|
18
18
|
},
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
2
|
-
import jsGlobals from "../runtime/jsGlobals.js";
|
|
3
2
|
import optimize from "./optimize.js";
|
|
4
3
|
import { parse } from "./parse.js";
|
|
5
4
|
|
|
6
5
|
function compile(source, options) {
|
|
7
|
-
const { startRule } = options;
|
|
8
|
-
const mode = options.mode ?? "shell";
|
|
9
|
-
const globals = options.globals ?? jsGlobals;
|
|
6
|
+
const { macros, startRule } = options;
|
|
10
7
|
const enableCaching = options.scopeCaching ?? true;
|
|
11
8
|
if (typeof source === "string") {
|
|
12
9
|
source = { text: source };
|
|
13
10
|
}
|
|
14
11
|
const code = parse(source.text, {
|
|
15
12
|
grammarSource: source,
|
|
16
|
-
mode,
|
|
17
13
|
startRule,
|
|
18
14
|
});
|
|
19
|
-
const optimized = optimize(code,
|
|
20
|
-
enableCaching,
|
|
21
|
-
globals,
|
|
22
|
-
mode,
|
|
23
|
-
});
|
|
15
|
+
const optimized = optimize(code, enableCaching, macros);
|
|
24
16
|
const fn = createExpressionFunction(optimized);
|
|
25
17
|
return fn;
|
|
26
18
|
}
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,60 +1,40 @@
|
|
|
1
1
|
import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import { ops } from "../runtime/internal.js";
|
|
3
|
-
import
|
|
4
|
-
import { annotate, markers } from "./parserHelpers.js";
|
|
3
|
+
import { annotate, undetermined } from "./parserHelpers.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Optimize an Origami code instruction:
|
|
8
7
|
*
|
|
9
|
-
* - Transform any remaining
|
|
8
|
+
* - Transform any remaining undetermined references to scope references.
|
|
10
9
|
* - Transform those or explicit ops.scope calls to ops.external calls unless
|
|
11
10
|
* they refer to local variables (variables defined by object literals or
|
|
12
11
|
* lambda parameters).
|
|
12
|
+
* - Apply any macros to the code.
|
|
13
13
|
*
|
|
14
14
|
* @typedef {import("./parserHelpers.js").AnnotatedCode} AnnotatedCode
|
|
15
15
|
* @typedef {import("./parserHelpers.js").Code} Code
|
|
16
16
|
*
|
|
17
17
|
* @param {AnnotatedCode} code
|
|
18
|
-
* @param {
|
|
18
|
+
* @param {boolean} enableCaching
|
|
19
|
+
* @param {Record<string, AnnotatedCode>} macros
|
|
20
|
+
* @param {Record<string, AnnotatedCode>} cache
|
|
21
|
+
* @param {Record<string, boolean>} locals
|
|
19
22
|
* @returns {AnnotatedCode}
|
|
20
23
|
*/
|
|
21
|
-
export default function optimize(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// has been entered. The array grows to the right. The array items are
|
|
29
|
-
// subarrays containing the names of local variables defined in that context.
|
|
30
|
-
const locals = options.locals ? options.locals.slice() : [];
|
|
31
|
-
|
|
32
|
-
const externalScope =
|
|
33
|
-
mode === "shell"
|
|
34
|
-
? // External scope is parent scope + globals
|
|
35
|
-
annotate(
|
|
36
|
-
[ops.merge, globals, annotate([ops.scope], code.location)],
|
|
37
|
-
code.location
|
|
38
|
-
)
|
|
39
|
-
: annotate([ops.scope], code.location);
|
|
40
|
-
|
|
24
|
+
export default function optimize(
|
|
25
|
+
code,
|
|
26
|
+
enableCaching = true,
|
|
27
|
+
macros = {},
|
|
28
|
+
cache = {},
|
|
29
|
+
locals = {}
|
|
30
|
+
) {
|
|
41
31
|
// See if we can optimize this level of the code
|
|
42
32
|
const [fn, ...args] = code;
|
|
43
|
-
let
|
|
44
|
-
let externalReference = fn instanceof Array && fn[0] === ops.scope;
|
|
45
|
-
let depth;
|
|
33
|
+
let additionalLocalNames;
|
|
46
34
|
switch (fn) {
|
|
47
|
-
case markers.global:
|
|
48
|
-
// Replace global op with the globals
|
|
49
|
-
optimized = annotate([globals, args[0]], code.location);
|
|
50
|
-
break;
|
|
51
|
-
|
|
52
35
|
case ops.lambda:
|
|
53
36
|
const parameters = args[0];
|
|
54
|
-
|
|
55
|
-
const names = parameters.map((param) => param[1]);
|
|
56
|
-
locals.push(names);
|
|
57
|
-
}
|
|
37
|
+
additionalLocalNames = parameters.map((param) => param[1]);
|
|
58
38
|
break;
|
|
59
39
|
|
|
60
40
|
case ops.literal:
|
|
@@ -66,131 +46,97 @@ export default function optimize(code, options = {}) {
|
|
|
66
46
|
|
|
67
47
|
case ops.object:
|
|
68
48
|
const entries = args;
|
|
69
|
-
|
|
70
|
-
locals.push(keys);
|
|
49
|
+
additionalLocalNames = entries.map(([key]) => trailingSlash.remove(key));
|
|
71
50
|
break;
|
|
72
51
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (key instanceof Array && key[0] === ops.literal) {
|
|
78
|
-
key = key[1];
|
|
79
|
-
}
|
|
52
|
+
// Both of these are handled the same way
|
|
53
|
+
case undetermined:
|
|
54
|
+
case ops.scope:
|
|
55
|
+
const key = args[0];
|
|
80
56
|
const normalizedKey = trailingSlash.remove(key);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
} else
|
|
95
|
-
|
|
57
|
+
if (macros?.[normalizedKey]) {
|
|
58
|
+
// Apply macro
|
|
59
|
+
const macro = macros?.[normalizedKey];
|
|
60
|
+
return applyMacro(macro, code, enableCaching, macros, cache, locals);
|
|
61
|
+
} else if (enableCaching && !locals[normalizedKey]) {
|
|
62
|
+
// Upgrade to cached external scope reference
|
|
63
|
+
return annotate(
|
|
64
|
+
[ops.external, key, annotate([ops.scope, key], code.location), cache],
|
|
65
|
+
code.location
|
|
66
|
+
);
|
|
67
|
+
} else if (fn === undetermined) {
|
|
68
|
+
// Transform undetermined reference to regular scope call
|
|
69
|
+
return annotate([ops.scope, key], code.location);
|
|
70
|
+
} else {
|
|
71
|
+
// Internal ops.scope call; leave as is
|
|
72
|
+
return code;
|
|
96
73
|
}
|
|
97
|
-
optimized = annotate([target, ...args], code.location);
|
|
98
|
-
break;
|
|
99
74
|
|
|
100
|
-
case ops.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
75
|
+
case ops.traverse:
|
|
76
|
+
// Is the first argument a nonscope/undetermined reference?
|
|
77
|
+
const isScopeRef =
|
|
78
|
+
args[0]?.[0] === ops.scope || args[0]?.[0] === undetermined;
|
|
79
|
+
if (enableCaching && isScopeRef) {
|
|
80
|
+
// Is the first argument a nonlocal reference?
|
|
81
|
+
const normalizedKey = trailingSlash.remove(args[0][1]);
|
|
82
|
+
if (!locals[normalizedKey]) {
|
|
83
|
+
// Are the remaining arguments all literals?
|
|
84
|
+
const allLiterals = args
|
|
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
|
+
}
|
|
109
96
|
}
|
|
110
97
|
break;
|
|
111
98
|
}
|
|
112
99
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let adjustedLocals;
|
|
125
|
-
if (locals.at(-1)?.includes(normalizedKey)) {
|
|
126
|
-
adjustedLocals = locals.slice();
|
|
127
|
-
// Remove the key from the current context's locals
|
|
128
|
-
adjustedLocals[adjustedLocals.length - 1] = locals
|
|
129
|
-
.at(-1)
|
|
130
|
-
.filter((name) => name !== normalizedKey);
|
|
131
|
-
} else {
|
|
132
|
-
adjustedLocals = locals;
|
|
133
|
-
}
|
|
134
|
-
return [
|
|
135
|
-
key,
|
|
136
|
-
optimize(/** @type {AnnotatedCode} */ (value), {
|
|
137
|
-
...options,
|
|
138
|
-
locals: adjustedLocals,
|
|
139
|
-
}),
|
|
140
|
-
];
|
|
141
|
-
} else if (Array.isArray(child) && "location" in child) {
|
|
142
|
-
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
143
|
-
// does this descend into?
|
|
144
|
-
return optimize(/** @type {AnnotatedCode} */ (child), {
|
|
145
|
-
...options,
|
|
146
|
-
locals,
|
|
147
|
-
});
|
|
148
|
-
} else {
|
|
149
|
-
return child;
|
|
150
|
-
}
|
|
151
|
-
}),
|
|
152
|
-
optimized.location
|
|
153
|
-
);
|
|
100
|
+
// Add any locals introduced by this code to the list that will be consulted
|
|
101
|
+
// when we descend into child nodes.
|
|
102
|
+
let updatedLocals;
|
|
103
|
+
if (additionalLocalNames) {
|
|
104
|
+
updatedLocals = { ...locals };
|
|
105
|
+
for (const key of additionalLocalNames) {
|
|
106
|
+
updatedLocals[key] = true;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
updatedLocals = locals;
|
|
110
|
+
}
|
|
154
111
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
112
|
+
// Optimize children
|
|
113
|
+
const optimized = code.map((child, index) => {
|
|
114
|
+
// Don't optimize lambda parameter names
|
|
115
|
+
if (fn === ops.lambda && index === 1) {
|
|
116
|
+
return child;
|
|
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
|
|
166
128
|
);
|
|
167
|
-
|
|
168
|
-
|
|
129
|
+
} else {
|
|
130
|
+
return child;
|
|
169
131
|
}
|
|
170
|
-
|
|
171
|
-
optimized = annotate(
|
|
172
|
-
[ops.cache, cache, path, optimized],
|
|
173
|
-
optimized.location
|
|
174
|
-
);
|
|
175
|
-
}
|
|
132
|
+
});
|
|
176
133
|
|
|
177
134
|
return annotate(optimized, code.location);
|
|
178
135
|
}
|
|
179
136
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
const depth = locals.length - contextIndex - 1;
|
|
187
|
-
return depth;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function propertyName(key) {
|
|
191
|
-
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
192
|
-
// Non-enumerable property, remove parentheses
|
|
193
|
-
key = key.slice(1, -1);
|
|
194
|
-
}
|
|
195
|
-
return trailingSlash.remove(key);
|
|
137
|
+
function applyMacro(macro, code, enableCaching, macros, cache, locals) {
|
|
138
|
+
const optimized = optimize(macro, enableCaching, macros, cache, locals);
|
|
139
|
+
return optimized instanceof Array
|
|
140
|
+
? annotate(optimized, code.location)
|
|
141
|
+
: optimized;
|
|
196
142
|
}
|