@weborigami/language 0.3.3-jse.2 → 0.3.3-jse.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 +2 -0
- package/package.json +3 -3
- package/src/compiler/compile.js +8 -2
- package/src/compiler/optimize.js +147 -93
- package/src/compiler/origami.pegjs +74 -60
- package/src/compiler/parse.js +946 -766
- package/src/compiler/parserHelpers.js +148 -48
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +4 -4
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +14 -53
- package/src/runtime/jsGlobals.js +99 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +69 -148
- package/src/runtime/symbols.js +1 -0
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +52 -27
- package/test/compiler/optimize.test.js +92 -23
- package/test/compiler/parse.test.js +480 -366
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +5 -4
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +94 -82
package/src/runtime/ops.js
CHANGED
|
@@ -11,12 +11,11 @@ import {
|
|
|
11
11
|
deepText,
|
|
12
12
|
isUnpackable,
|
|
13
13
|
scope as scopeFn,
|
|
14
|
-
setParent,
|
|
15
14
|
text as templateFunctionTree,
|
|
16
15
|
} from "@weborigami/async-tree";
|
|
17
16
|
import os from "node:os";
|
|
18
|
-
import { builtinReferenceError, scopeReferenceError } from "./errors.js";
|
|
19
17
|
import expressionObject from "./expressionObject.js";
|
|
18
|
+
import getHandlers from "./getHandlers.js";
|
|
20
19
|
import { evaluate } from "./internal.js";
|
|
21
20
|
import mergeTrees from "./mergeTrees.js";
|
|
22
21
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
@@ -68,25 +67,35 @@ export function bitwiseXor(a, b) {
|
|
|
68
67
|
addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
|
69
68
|
|
|
70
69
|
/**
|
|
71
|
-
*
|
|
72
|
-
* chain.
|
|
70
|
+
* Cache the value of the code for an external reference
|
|
73
71
|
*
|
|
74
72
|
* @this {AsyncTree|null}
|
|
73
|
+
* @param {any} cache
|
|
74
|
+
* @param {string} path
|
|
75
|
+
* @param {AnnotatedCode} code
|
|
75
76
|
*/
|
|
76
|
-
export async function
|
|
77
|
-
if (
|
|
78
|
-
|
|
77
|
+
export async function cache(cache, path, code) {
|
|
78
|
+
if (path in cache) {
|
|
79
|
+
// Cache hit
|
|
80
|
+
return cache[path];
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// Don't await: might get another request for this before promise resolves
|
|
84
|
+
const promise = await evaluate.call(this, code);
|
|
85
|
+
|
|
86
|
+
// Save promise so another request will get the same promise
|
|
87
|
+
cache[path] = promise;
|
|
88
|
+
|
|
89
|
+
// Now wait for the value
|
|
90
|
+
const value = await promise;
|
|
91
|
+
|
|
92
|
+
// Update the cache with the actual value
|
|
93
|
+
cache[path] = value;
|
|
86
94
|
|
|
87
95
|
return value;
|
|
88
96
|
}
|
|
89
|
-
addOpLabel(
|
|
97
|
+
addOpLabel(cache, "«ops.cache»");
|
|
98
|
+
cache.unevaluatedArgs = true;
|
|
90
99
|
|
|
91
100
|
/**
|
|
92
101
|
* JavaScript comma operator, returns the last argument.
|
|
@@ -123,27 +132,21 @@ export async function construct(constructor, ...args) {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
/**
|
|
126
|
-
*
|
|
127
|
-
* the resulting text to the front data.
|
|
135
|
+
* Return the nth parent of the current tree
|
|
128
136
|
*
|
|
129
|
-
* @this {AsyncTree|null}
|
|
130
|
-
* @param {any} frontData
|
|
131
|
-
* @param {AnnotatedCode} bodyCode
|
|
137
|
+
* @this {AsyncTree|null|undefined}
|
|
132
138
|
*/
|
|
133
|
-
export
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
addOpLabel(document, "«ops.document");
|
|
146
|
-
document.unevaluatedArgs = true;
|
|
139
|
+
export function context(n = 0) {
|
|
140
|
+
let tree = this;
|
|
141
|
+
for (let i = 0; i < n; i++) {
|
|
142
|
+
if (!tree) {
|
|
143
|
+
throw new Error("Internal error: couldn't find tree ancestor.");
|
|
144
|
+
}
|
|
145
|
+
tree = tree.parent;
|
|
146
|
+
}
|
|
147
|
+
return tree;
|
|
148
|
+
}
|
|
149
|
+
addOpLabel(context, "«ops.context»");
|
|
147
150
|
|
|
148
151
|
export function division(a, b) {
|
|
149
152
|
return a / b;
|
|
@@ -161,36 +164,27 @@ export function exponentiation(a, b) {
|
|
|
161
164
|
addOpLabel(exponentiation, "«ops.exponentiation»");
|
|
162
165
|
|
|
163
166
|
/**
|
|
164
|
-
*
|
|
165
|
-
* requests.
|
|
167
|
+
* Flatten the values of the given trees
|
|
166
168
|
*
|
|
167
|
-
* @
|
|
169
|
+
* @param {...any} args
|
|
168
170
|
*/
|
|
169
|
-
export async function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Don't await: might get another request for this before promise resolves
|
|
180
|
-
const promise = evaluate.call(this, code);
|
|
181
|
-
// Save promise so another request will get the same promise
|
|
182
|
-
cache[path] = promise;
|
|
183
|
-
|
|
184
|
-
// Now wait for the value
|
|
185
|
-
const value = await promise;
|
|
186
|
-
|
|
187
|
-
// Update the cache with the actual value
|
|
188
|
-
cache[path] = value;
|
|
171
|
+
export async function flat(...args) {
|
|
172
|
+
const arrays = await Promise.all(
|
|
173
|
+
args.map(async (arg) =>
|
|
174
|
+
arg instanceof Array || typeof arg !== "object"
|
|
175
|
+
? arg
|
|
176
|
+
: await Tree.values(arg)
|
|
177
|
+
)
|
|
178
|
+
);
|
|
189
179
|
|
|
190
|
-
return
|
|
180
|
+
return arrays.flat();
|
|
191
181
|
}
|
|
192
|
-
addOpLabel(
|
|
193
|
-
|
|
182
|
+
addOpLabel(flat, "«ops.flat»");
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* This op is only used during parsing for an explicit to a global.
|
|
186
|
+
*/
|
|
187
|
+
export const global = new String("«global");
|
|
194
188
|
|
|
195
189
|
/**
|
|
196
190
|
* This op is only used during parsing. It signals to ops.object that the
|
|
@@ -215,28 +209,12 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
|
|
215
209
|
*/
|
|
216
210
|
export async function homeDirectory() {
|
|
217
211
|
const tree = new OrigamiFiles(os.homedir());
|
|
218
|
-
|
|
212
|
+
// Use the same handlers as the current tree
|
|
213
|
+
tree.handlers = getHandlers(this);
|
|
219
214
|
return tree;
|
|
220
215
|
}
|
|
221
216
|
addOpLabel(homeDirectory, "«ops.homeDirectory»");
|
|
222
217
|
|
|
223
|
-
/**
|
|
224
|
-
* Search the parent's scope -- i.e., exclude the current tree -- for the given
|
|
225
|
-
* key.
|
|
226
|
-
*
|
|
227
|
-
* @this {AsyncTree|null}
|
|
228
|
-
* @param {*} key
|
|
229
|
-
*/
|
|
230
|
-
export async function inherited(key) {
|
|
231
|
-
if (!this?.parent) {
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
const parentScope = scopeFn(this.parent);
|
|
235
|
-
const value = await parentScope.get(key);
|
|
236
|
-
return value;
|
|
237
|
-
}
|
|
238
|
-
addOpLabel(inherited, "«ops.inherited»");
|
|
239
|
-
|
|
240
218
|
/**
|
|
241
219
|
* Return a function that will invoke the given code.
|
|
242
220
|
*
|
|
@@ -375,61 +353,12 @@ addOpLabel(logicalOr, "«ops.logicalOr»");
|
|
|
375
353
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
376
354
|
*
|
|
377
355
|
* @this {AsyncTree|null}
|
|
378
|
-
* @param {
|
|
356
|
+
* @param {any[]} trees
|
|
379
357
|
*/
|
|
380
|
-
export async function merge(...
|
|
381
|
-
// First pass: evaluate the direct property entries in a single object
|
|
382
|
-
let treeSpreads = false;
|
|
383
|
-
const directEntries = [];
|
|
384
|
-
for (const code of codes) {
|
|
385
|
-
if (code[0] === object) {
|
|
386
|
-
directEntries.push(...code.slice(1));
|
|
387
|
-
} else {
|
|
388
|
-
treeSpreads = true;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const directObject = directEntries
|
|
393
|
-
? await expressionObject(directEntries, this)
|
|
394
|
-
: null;
|
|
395
|
-
if (!treeSpreads) {
|
|
396
|
-
// No tree spreads, we're done
|
|
397
|
-
return directObject;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// If we have direct property entries, create a context for them. The
|
|
401
|
-
// `expressionObject` function will set the object's parent symbol to `this`.
|
|
402
|
-
// Tree.from will call the ObjectTree constructor, which will use that symbol
|
|
403
|
-
// to set the parent for the new tree to `this`.
|
|
404
|
-
const context = directObject ? Tree.from(directObject) : this;
|
|
405
|
-
|
|
406
|
-
// Second pass: evaluate the trees. For the trees which are direct property
|
|
407
|
-
// entries, we'll copy over the values we've already calculated. We can't
|
|
408
|
-
// reuse the `directObject` as is because in a merge we need to respect the
|
|
409
|
-
// order in which the properties are defined. Trees that aren't direct
|
|
410
|
-
// property entries are evaluated with the direct property entries in scope.
|
|
411
|
-
const trees = await Promise.all(
|
|
412
|
-
codes.map(async (code) => {
|
|
413
|
-
if (code[0] === object) {
|
|
414
|
-
// Using the code as reference, create a new object with the direct
|
|
415
|
-
// property values we've already calculated.
|
|
416
|
-
const object = {};
|
|
417
|
-
for (const [key] of code.slice(1)) {
|
|
418
|
-
// @ts-ignore directObject will always be defined
|
|
419
|
-
object[key] = directObject[key];
|
|
420
|
-
}
|
|
421
|
-
setParent(object, this);
|
|
422
|
-
return object;
|
|
423
|
-
} else {
|
|
424
|
-
return evaluate.call(context, code);
|
|
425
|
-
}
|
|
426
|
-
})
|
|
427
|
-
);
|
|
428
|
-
|
|
358
|
+
export async function merge(...trees) {
|
|
429
359
|
return mergeTrees.call(this, ...trees);
|
|
430
360
|
}
|
|
431
361
|
addOpLabel(merge, "«ops.merge»");
|
|
432
|
-
merge.unevaluatedArgs = true;
|
|
433
362
|
|
|
434
363
|
export function multiplication(a, b) {
|
|
435
364
|
return a * b;
|
|
@@ -498,31 +427,28 @@ addOpLabel(remainder, "«ops.remainder»");
|
|
|
498
427
|
*
|
|
499
428
|
* @this {AsyncTree|null}
|
|
500
429
|
*/
|
|
501
|
-
export async function rootDirectory(
|
|
502
|
-
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
tree.parent = this ? Tree.root(this) : null;
|
|
507
|
-
return key ? tree.get(key) : tree;
|
|
430
|
+
export async function rootDirectory() {
|
|
431
|
+
const tree = new OrigamiFiles("/");
|
|
432
|
+
// Use the same handlers as the current tree
|
|
433
|
+
tree.handlers = getHandlers(this);
|
|
434
|
+
return tree;
|
|
508
435
|
}
|
|
509
436
|
addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
510
437
|
|
|
511
438
|
/**
|
|
512
|
-
*
|
|
439
|
+
* Return the scope of the current tree
|
|
513
440
|
*
|
|
514
441
|
* @this {AsyncTree|null}
|
|
442
|
+
* @param {AsyncTree|null} [context]
|
|
515
443
|
*/
|
|
516
|
-
export async function scope(
|
|
517
|
-
if (
|
|
518
|
-
|
|
444
|
+
export async function scope(context) {
|
|
445
|
+
if (context === undefined) {
|
|
446
|
+
context = this;
|
|
519
447
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (value === undefined && key !== "undefined") {
|
|
523
|
-
throw await scopeReferenceError(scope, key);
|
|
448
|
+
if (!context) {
|
|
449
|
+
return null;
|
|
524
450
|
}
|
|
525
|
-
return
|
|
451
|
+
return scopeFn(context);
|
|
526
452
|
}
|
|
527
453
|
addOpLabel(scope, "«ops.scope»");
|
|
528
454
|
|
|
@@ -586,11 +512,6 @@ export async function templateTree(strings, ...values) {
|
|
|
586
512
|
}
|
|
587
513
|
addOpLabel(templateTree, "«ops.templateTree»");
|
|
588
514
|
|
|
589
|
-
/**
|
|
590
|
-
* Traverse a path of keys through a tree.
|
|
591
|
-
*/
|
|
592
|
-
export const traverse = Tree.traverseOrThrow;
|
|
593
|
-
|
|
594
515
|
export function unaryMinus(a) {
|
|
595
516
|
return -a;
|
|
596
517
|
}
|
package/src/runtime/symbols.js
CHANGED
|
@@ -13,7 +13,9 @@ export function assertCodeEqual(actual, expected) {
|
|
|
13
13
|
* @returns {import("../../index.ts").AnnotatedCode}
|
|
14
14
|
*/
|
|
15
15
|
export function createCode(array) {
|
|
16
|
-
const code = array
|
|
16
|
+
const code = array.map((item) =>
|
|
17
|
+
item instanceof Array ? createCode(item) : item
|
|
18
|
+
);
|
|
17
19
|
/** @type {any} */ (code).location = {
|
|
18
20
|
end: 0,
|
|
19
21
|
source: {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ObjectTree,
|
|
1
|
+
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
import * as compile from "../../src/compiler/compile.js";
|
|
5
|
-
import { ops } from "../../src/runtime/internal.js";
|
|
6
5
|
import { assertCodeEqual } from "./codeHelpers.js";
|
|
7
6
|
|
|
8
7
|
const shared = new ObjectTree({
|
|
@@ -20,16 +19,48 @@ describe("compile", () => {
|
|
|
20
19
|
test("functionComposition", async () => {
|
|
21
20
|
await assertCompile("greet()", "Hello, undefined!");
|
|
22
21
|
await assertCompile("greet(name)", "Hello, Alice!");
|
|
22
|
+
await assertCompile("greet(name)", "Hello, Alice!", { mode: "jse" });
|
|
23
23
|
await assertCompile("greet 'world'", "Hello, world!");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test("
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
test("angle bracket path", async () => {
|
|
27
|
+
await assertCompile("<name>", "Alice", { mode: "jse", target: shared });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("object literal", async () => {
|
|
31
|
+
await assertCompile("{ message = greet(name) }", {
|
|
31
32
|
message: "Hello, Alice!",
|
|
32
33
|
});
|
|
34
|
+
await assertCompile(
|
|
35
|
+
"{ message = greet(name) }",
|
|
36
|
+
{
|
|
37
|
+
message: "Hello, Alice!",
|
|
38
|
+
},
|
|
39
|
+
{ mode: "jse" }
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test.skip("merge", async () => {
|
|
44
|
+
{
|
|
45
|
+
const scope = new ObjectTree({
|
|
46
|
+
more: {
|
|
47
|
+
b: 2,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const fn = compile.expression(`
|
|
51
|
+
{
|
|
52
|
+
a: 1
|
|
53
|
+
...more
|
|
54
|
+
c: a
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
const result = await fn.call(scope);
|
|
58
|
+
assert.deepEqual(await Tree.plain(result), {
|
|
59
|
+
a: 1,
|
|
60
|
+
b: 2,
|
|
61
|
+
c: 1,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
33
64
|
});
|
|
34
65
|
|
|
35
66
|
test("number", async () => {
|
|
@@ -44,8 +75,8 @@ describe("compile", () => {
|
|
|
44
75
|
});
|
|
45
76
|
|
|
46
77
|
test("async object", async () => {
|
|
47
|
-
const fn = compile.expression("{ a: { b = name }}");
|
|
48
|
-
const object = await fn.call(
|
|
78
|
+
const fn = compile.expression("{ a: { b = name }}", { globals: shared });
|
|
79
|
+
const object = await fn.call(null);
|
|
49
80
|
assert.deepEqual(await object.a.b, "Alice");
|
|
50
81
|
});
|
|
51
82
|
|
|
@@ -68,7 +99,7 @@ describe("compile", () => {
|
|
|
68
99
|
|
|
69
100
|
test("tagged template string array is identical across calls", async () => {
|
|
70
101
|
let saved;
|
|
71
|
-
const
|
|
102
|
+
const globals = new ObjectTree({
|
|
72
103
|
tag: (strings, ...values) => {
|
|
73
104
|
assertCodeEqual(strings, ["Hello, ", "!"]);
|
|
74
105
|
if (saved) {
|
|
@@ -79,29 +110,23 @@ describe("compile", () => {
|
|
|
79
110
|
return strings[0] + values[0] + strings[1];
|
|
80
111
|
},
|
|
81
112
|
});
|
|
82
|
-
const program = compile.expression("=tag`Hello, ${_}!`");
|
|
83
|
-
const lambda = await program.call(
|
|
113
|
+
const program = compile.expression("=tag`Hello, ${_}!`", { globals });
|
|
114
|
+
const lambda = await program.call(null);
|
|
84
115
|
const alice = await lambda("Alice");
|
|
85
116
|
assert.equal(alice, "Hello, Alice!");
|
|
86
117
|
const bob = await lambda("Bob");
|
|
87
118
|
assert.equal(bob, "Hello, Bob!");
|
|
88
119
|
});
|
|
89
|
-
|
|
90
|
-
test("can apply a macro", async () => {
|
|
91
|
-
const literal = [ops.literal, 1];
|
|
92
|
-
const expression = `{ a: literal }`;
|
|
93
|
-
const fn = compile.expression(expression, {
|
|
94
|
-
macros: {
|
|
95
|
-
literal,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
const code = fn.code;
|
|
99
|
-
assertCodeEqual(code, [ops.object, ["a", 1]]);
|
|
100
|
-
});
|
|
101
120
|
});
|
|
102
121
|
|
|
103
|
-
async function assertCompile(text, expected) {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
122
|
+
async function assertCompile(text, expected, options = {}) {
|
|
123
|
+
const mode = options.mode ?? "shell";
|
|
124
|
+
const fn = compile.expression(text, { globals: shared, mode });
|
|
125
|
+
// For shell mode, use globals as scope too
|
|
126
|
+
const target = options.target ?? mode === "shell" ? shared : null;
|
|
127
|
+
let result = await fn.call(target);
|
|
128
|
+
if (Tree.isTreelike(result)) {
|
|
129
|
+
result = await Tree.plain(result);
|
|
130
|
+
}
|
|
106
131
|
assert.deepEqual(result, expected);
|
|
107
132
|
}
|
|
@@ -1,42 +1,111 @@
|
|
|
1
|
+
import { ObjectTree } from "@weborigami/async-tree";
|
|
1
2
|
import { describe, test } from "node:test";
|
|
2
3
|
import * as compile from "../../src/compiler/compile.js";
|
|
3
4
|
import optimize from "../../src/compiler/optimize.js";
|
|
5
|
+
import { markers } from "../../src/compiler/parserHelpers.js";
|
|
4
6
|
import { ops } from "../../src/runtime/internal.js";
|
|
5
7
|
import { assertCodeEqual, createCode } from "./codeHelpers.js";
|
|
6
8
|
|
|
7
9
|
describe("optimize", () => {
|
|
8
|
-
test("
|
|
9
|
-
const expression = `
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
d: name // local, should be left as ops.scope
|
|
15
|
-
}
|
|
16
|
-
`;
|
|
17
|
-
const fn = compile.expression(expression);
|
|
18
|
-
const code = fn.code;
|
|
19
|
-
assertCodeEqual(code, [
|
|
10
|
+
test("change local references to context references", async () => {
|
|
11
|
+
const expression = `(name) => {
|
|
12
|
+
a: name,
|
|
13
|
+
b: a
|
|
14
|
+
}`;
|
|
15
|
+
const expected = [
|
|
20
16
|
ops.lambda,
|
|
21
17
|
[[ops.literal, "name"]],
|
|
22
18
|
[
|
|
23
19
|
ops.object,
|
|
24
|
-
["a", 1],
|
|
25
|
-
["b", [ops.
|
|
26
|
-
["c", [ops.external, "elsewhere", [ops.scope, "elsewhere"], {}]],
|
|
27
|
-
["d", [ops.scope, "name"]],
|
|
20
|
+
["a", [[ops.context, 1], "name"]],
|
|
21
|
+
["b", [[ops.context], "a"]],
|
|
28
22
|
],
|
|
23
|
+
];
|
|
24
|
+
await assertCompile(expression, expected);
|
|
25
|
+
await assertCompile(expression, expected, "jse");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("when defining a property, avoid recursive references", async () => {
|
|
29
|
+
const expression = `{
|
|
30
|
+
name: "Alice",
|
|
31
|
+
user: {
|
|
32
|
+
name: name
|
|
33
|
+
}
|
|
34
|
+
}`;
|
|
35
|
+
const expected = [
|
|
36
|
+
ops.object,
|
|
37
|
+
["name", "Alice"],
|
|
38
|
+
["user", [ops.object, ["name", [[ops.context, 1], "name"]]]],
|
|
39
|
+
];
|
|
40
|
+
await assertCompile(expression, expected);
|
|
41
|
+
await assertCompile(expression, expected, "jse");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("cache shell non-local references to globals+scope calls", async () => {
|
|
45
|
+
// Compilation of `x/y/z.js`
|
|
46
|
+
const code = createCode([
|
|
47
|
+
markers.reference,
|
|
48
|
+
[ops.literal, "x/"],
|
|
49
|
+
[ops.literal, "y/"],
|
|
50
|
+
[ops.literal, "z.js"],
|
|
51
|
+
]);
|
|
52
|
+
const globals = {};
|
|
53
|
+
const expected = [
|
|
54
|
+
ops.cache,
|
|
55
|
+
{},
|
|
56
|
+
"x/y/z.js",
|
|
57
|
+
[[ops.merge, globals, [ops.scope]], "x/", "y/", "z.js"],
|
|
58
|
+
];
|
|
59
|
+
assertCodeEqual(optimize(code, { globals }), expected);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("change jse non-local references to globals", async () => {
|
|
63
|
+
// Compilation of `x/y`
|
|
64
|
+
const code = createCode([
|
|
65
|
+
markers.reference,
|
|
66
|
+
[ops.literal, "x/"],
|
|
67
|
+
[ops.literal, "y"],
|
|
68
|
+
]);
|
|
69
|
+
const globals = {};
|
|
70
|
+
const expected = [globals, "x/", "y"];
|
|
71
|
+
assertCodeEqual(optimize(code, { globals, mode: "jse" }), expected);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("cache jse top-level scope references", async () => {
|
|
75
|
+
// Compilation of `x/y/z.js`
|
|
76
|
+
const code = createCode([
|
|
77
|
+
[ops.scope],
|
|
78
|
+
[ops.literal, "x/"],
|
|
79
|
+
[ops.literal, "y/"],
|
|
80
|
+
[ops.literal, "z.js"],
|
|
29
81
|
]);
|
|
82
|
+
const expected = [
|
|
83
|
+
ops.cache,
|
|
84
|
+
{},
|
|
85
|
+
"x/y/z.js",
|
|
86
|
+
[[ops.scope], "x/", "y/", "z.js"],
|
|
87
|
+
];
|
|
88
|
+
assertCodeEqual(optimize(code, { mode: "jse" }), expected);
|
|
30
89
|
});
|
|
31
90
|
|
|
32
|
-
test("
|
|
33
|
-
// Compilation of `x
|
|
91
|
+
test("cache jse deeper scope references", async () => {
|
|
92
|
+
// Compilation of `{ property: <x> }`
|
|
34
93
|
const code = createCode([
|
|
35
|
-
ops.
|
|
36
|
-
[ops.scope, "x
|
|
37
|
-
[ops.literal, "y.js"],
|
|
94
|
+
ops.object,
|
|
95
|
+
["property", [[ops.scope], [ops.literal, "x"]]],
|
|
38
96
|
]);
|
|
39
|
-
const
|
|
40
|
-
|
|
97
|
+
const expected = [
|
|
98
|
+
ops.object,
|
|
99
|
+
["property", [ops.cache, {}, "x", [[ops.scope, [ops.context, 1]], "x"]]],
|
|
100
|
+
];
|
|
101
|
+
assertCodeEqual(optimize(code, { mode: "jse" }), expected);
|
|
41
102
|
});
|
|
42
103
|
});
|
|
104
|
+
|
|
105
|
+
async function assertCompile(expression, expected, mode = "shell") {
|
|
106
|
+
const parent = new ObjectTree({});
|
|
107
|
+
const globals = new ObjectTree({});
|
|
108
|
+
const fn = compile.expression(expression, { globals, mode, parent });
|
|
109
|
+
const actual = fn.code;
|
|
110
|
+
assertCodeEqual(actual, expected);
|
|
111
|
+
}
|