@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
|
@@ -8,7 +8,7 @@ import { createCode } from "../compiler/codeHelpers.js";
|
|
|
8
8
|
|
|
9
9
|
describe("evaluate", () => {
|
|
10
10
|
test("can retrieve values from scope", async () => {
|
|
11
|
-
const code = createCode([ops.scope, "message"]);
|
|
11
|
+
const code = createCode([[ops.scope], "message"]);
|
|
12
12
|
const parent = new ObjectTree({
|
|
13
13
|
message: "Hello",
|
|
14
14
|
});
|
|
@@ -21,8 +21,8 @@ describe("evaluate", () => {
|
|
|
21
21
|
test("can invoke functions in scope", async () => {
|
|
22
22
|
// Match the array representation of code generated by the parser.
|
|
23
23
|
const code = createCode([
|
|
24
|
-
[ops.scope, "greet"],
|
|
25
|
-
[ops.scope, "name"],
|
|
24
|
+
[[ops.scope], "greet"],
|
|
25
|
+
[[ops.scope], "name"],
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
28
|
const tree = new ObjectTree({
|
|
@@ -37,7 +37,7 @@ describe("evaluate", () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test("passes context to invoked functions", async () => {
|
|
40
|
-
const code = createCode([ops.scope, "fn"]);
|
|
40
|
+
const code = createCode([[ops.scope], "fn"]);
|
|
41
41
|
const tree = new ObjectTree({
|
|
42
42
|
async fn() {
|
|
43
43
|
assert.equal(this, tree);
|
|
@@ -46,14 +46,6 @@ describe("evaluate", () => {
|
|
|
46
46
|
await evaluate.call(tree, code);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
test("evaluates a function with fixed number of arguments", async () => {
|
|
50
|
-
const fn = (x, y) => ({
|
|
51
|
-
c: `${x}${y}c`,
|
|
52
|
-
});
|
|
53
|
-
const code = createCode([ops.traverse, fn, "a", "b", "c"]);
|
|
54
|
-
assert.equal(await evaluate.call(null, code), "abc");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
49
|
test("if object in function position isn't a function, can unpack it", async () => {
|
|
58
50
|
const fn = (...args) => args.join(",");
|
|
59
51
|
const packed = new String();
|
|
@@ -62,12 +54,4 @@ describe("evaluate", () => {
|
|
|
62
54
|
const result = await evaluate.call(null, code);
|
|
63
55
|
assert.equal(result, "a,b,c");
|
|
64
56
|
});
|
|
65
|
-
|
|
66
|
-
test("by defalut sets the parent of a returned tree to the current tree", async () => {
|
|
67
|
-
const fn = () => new ObjectTree({});
|
|
68
|
-
const code = createCode([fn]);
|
|
69
|
-
const tree = new ObjectTree({});
|
|
70
|
-
const result = await evaluate.call(tree, code);
|
|
71
|
-
assert.equal(result.parent, tree);
|
|
72
|
-
});
|
|
73
57
|
});
|
|
@@ -12,8 +12,8 @@ describe("expressionObject", () => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
const entries = [
|
|
15
|
-
["hello", [[ops.scope, "upper"], "hello"]],
|
|
16
|
-
["world", [[ops.scope, "upper"], "world"]],
|
|
15
|
+
["hello", [[[ops.scope], "upper"], "hello"]],
|
|
16
|
+
["world", [[[ops.scope], "upper"], "world"]],
|
|
17
17
|
];
|
|
18
18
|
|
|
19
19
|
const object = await expressionObject(entries, scope);
|
|
@@ -40,7 +40,7 @@ describe("expressionObject", () => {
|
|
|
40
40
|
test("can instantiate an Origami tree", async () => {
|
|
41
41
|
const entries = [
|
|
42
42
|
["name", "world"],
|
|
43
|
-
["message", [ops.concat, "Hello, ", [ops.scope, "name"], "!"]],
|
|
43
|
+
["message", [ops.concat, "Hello, ", [[ops.scope], "name"], "!"]],
|
|
44
44
|
];
|
|
45
45
|
const parent = new ObjectTree({});
|
|
46
46
|
const object = await expressionObject(entries, parent);
|
|
@@ -53,7 +53,8 @@ describe("expressionObject", () => {
|
|
|
53
53
|
|
|
54
54
|
test("returned object values can be unpacked", async () => {
|
|
55
55
|
const entries = [["data.json", `{ "a": 1 }`]];
|
|
56
|
-
const parent = new ObjectTree({
|
|
56
|
+
const parent = new ObjectTree({});
|
|
57
|
+
parent.handlers = new ObjectTree({
|
|
57
58
|
"json.handler": {
|
|
58
59
|
unpack: JSON.parse,
|
|
59
60
|
},
|
|
@@ -3,6 +3,12 @@ import assert from "node:assert";
|
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
import { handleExtension } from "../../src/runtime/handlers.js";
|
|
5
5
|
|
|
6
|
+
const handlers = new ObjectTree({
|
|
7
|
+
"json.handler": {
|
|
8
|
+
unpack: (buffer) => JSON.parse(String(buffer)),
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
6
12
|
describe("handlers", () => {
|
|
7
13
|
test("attaches an unpack method to a value with an extension", async () => {
|
|
8
14
|
const fixture = createFixture();
|
|
@@ -10,7 +16,12 @@ describe("handlers", () => {
|
|
|
10
16
|
assert(typeof numberValue === "number");
|
|
11
17
|
assert.equal(numberValue, 1);
|
|
12
18
|
const jsonFile = await fixture.get("bar.json");
|
|
13
|
-
const withHandler = await handleExtension(
|
|
19
|
+
const withHandler = await handleExtension(
|
|
20
|
+
fixture,
|
|
21
|
+
jsonFile,
|
|
22
|
+
"bar.json",
|
|
23
|
+
handlers
|
|
24
|
+
);
|
|
14
25
|
assert.equal(String(withHandler), `{ "bar": 2 }`);
|
|
15
26
|
const data = await withHandler.unpack();
|
|
16
27
|
assert.deepEqual(data, { bar: 2 });
|
|
@@ -19,21 +30,19 @@ describe("handlers", () => {
|
|
|
19
30
|
test("immediately unpacks if key ends in slash", async () => {
|
|
20
31
|
const fixture = createFixture();
|
|
21
32
|
const jsonFile = await fixture.get("bar.json");
|
|
22
|
-
const data = await handleExtension(
|
|
33
|
+
const data = await handleExtension(
|
|
34
|
+
fixture,
|
|
35
|
+
jsonFile,
|
|
36
|
+
"bar.json/",
|
|
37
|
+
handlers
|
|
38
|
+
);
|
|
23
39
|
assert.deepEqual(data, { bar: 2 });
|
|
24
40
|
});
|
|
25
41
|
});
|
|
26
42
|
|
|
27
43
|
function createFixture() {
|
|
28
|
-
|
|
29
|
-
"json.handler": {
|
|
30
|
-
unpack: (buffer) => JSON.parse(String(buffer)),
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
let tree = new ObjectTree({
|
|
44
|
+
return new ObjectTree({
|
|
34
45
|
foo: 1, // No extension, should be left alone
|
|
35
46
|
"bar.json": `{ "bar": 2 }`,
|
|
36
47
|
});
|
|
37
|
-
tree.parent = parent;
|
|
38
|
-
return tree;
|
|
39
48
|
}
|
|
@@ -41,11 +41,6 @@ describe("mergeTrees", () => {
|
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
test("if all arguments are arrays, result is an array", async () => {
|
|
45
|
-
const result = await mergeTrees.call(null, [1, 2], [3, 4]);
|
|
46
|
-
assert.deepEqual(result, [1, 2, 3, 4]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
44
|
test("merges heterogenous arguments as trees", async () => {
|
|
50
45
|
const tree = await mergeTrees.call(
|
|
51
46
|
null,
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeepObjectTree, ObjectTree } from "@weborigami/async-tree";
|
|
1
|
+
import { DeepObjectTree, ObjectTree, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
|
|
@@ -42,17 +42,6 @@ describe("ops", () => {
|
|
|
42
42
|
assert.strictEqual(ops.bitwiseXor(5, 3), 6);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
test("ops.builtin gets a value from the top of the scope chain", async () => {
|
|
46
|
-
const root = new ObjectTree({
|
|
47
|
-
a: 1,
|
|
48
|
-
});
|
|
49
|
-
const tree = new ObjectTree({});
|
|
50
|
-
tree.parent = root;
|
|
51
|
-
const code = createCode([ops.builtin, "a"]);
|
|
52
|
-
const result = await evaluate.call(tree, code);
|
|
53
|
-
assert.strictEqual(result, 1);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
45
|
test("ops.comma returns the last value", async () => {
|
|
57
46
|
const code = createCode([ops.comma, 1, 2, 3]);
|
|
58
47
|
const result = await evaluate.call(null, code);
|
|
@@ -64,7 +53,12 @@ describe("ops", () => {
|
|
|
64
53
|
name: "world",
|
|
65
54
|
});
|
|
66
55
|
|
|
67
|
-
const code = createCode([
|
|
56
|
+
const code = createCode([
|
|
57
|
+
ops.concat,
|
|
58
|
+
"Hello, ",
|
|
59
|
+
[[ops.scope], "name"],
|
|
60
|
+
".",
|
|
61
|
+
]);
|
|
68
62
|
|
|
69
63
|
const result = await evaluate.call(scope, code);
|
|
70
64
|
assert.strictEqual(result, "Hello, world.");
|
|
@@ -84,27 +78,18 @@ describe("ops", () => {
|
|
|
84
78
|
assert.equal(await ops.construct(String, "hello"), "hello");
|
|
85
79
|
});
|
|
86
80
|
|
|
87
|
-
test("ops.
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
test("ops.context", async () => {
|
|
82
|
+
const tree = new DeepObjectTree({
|
|
83
|
+
a: {
|
|
84
|
+
b: {
|
|
85
|
+
c: {},
|
|
86
|
+
},
|
|
92
87
|
},
|
|
93
|
-
[
|
|
94
|
-
ops.lambda,
|
|
95
|
-
[["_"]],
|
|
96
|
-
[
|
|
97
|
-
ops.templateIndent,
|
|
98
|
-
[ops.literal, ["a = ", ""]],
|
|
99
|
-
[ops.concat, [ops.scope, "a"]],
|
|
100
|
-
],
|
|
101
|
-
],
|
|
102
|
-
]);
|
|
103
|
-
const result = await evaluate.call(null, code);
|
|
104
|
-
assert.deepEqual(result, {
|
|
105
|
-
a: 1,
|
|
106
|
-
"@text": "a = 1",
|
|
107
88
|
});
|
|
89
|
+
const b = await Tree.traverse(tree, "a", "b");
|
|
90
|
+
const c = await b.get("c");
|
|
91
|
+
assert.equal(ops.context.call(c), c);
|
|
92
|
+
assert.equal(ops.context.call(c, 1), b);
|
|
108
93
|
});
|
|
109
94
|
|
|
110
95
|
test("ops.division divides two numbers", async () => {
|
|
@@ -127,20 +112,21 @@ describe("ops", () => {
|
|
|
127
112
|
assert.strictEqual(ops.exponentiation(2, 0), 1);
|
|
128
113
|
});
|
|
129
114
|
|
|
130
|
-
test("ops.
|
|
115
|
+
test("ops.cache evaluates code and cache its result", async () => {
|
|
131
116
|
let count = 0;
|
|
132
117
|
const tree = new DeepObjectTree({
|
|
133
118
|
group: {
|
|
134
119
|
get count() {
|
|
135
|
-
|
|
120
|
+
// Use promise to test async behavior
|
|
121
|
+
return Promise.resolve(++count);
|
|
136
122
|
},
|
|
137
123
|
},
|
|
138
124
|
});
|
|
139
125
|
const code = createCode([
|
|
140
|
-
ops.
|
|
141
|
-
"group/count",
|
|
142
|
-
[ops.traverse, [ops.scope, "group"], [ops.literal, "count"]],
|
|
126
|
+
ops.cache,
|
|
143
127
|
{},
|
|
128
|
+
"group/count",
|
|
129
|
+
[[ops.scope], [ops.literal, "group"], [ops.literal, "count"]],
|
|
144
130
|
]);
|
|
145
131
|
const result = await evaluate.call(tree, code);
|
|
146
132
|
assert.strictEqual(result, 1);
|
|
@@ -148,6 +134,26 @@ describe("ops", () => {
|
|
|
148
134
|
assert.strictEqual(result2, 1);
|
|
149
135
|
});
|
|
150
136
|
|
|
137
|
+
describe("ops.flat", () => {
|
|
138
|
+
test("flattens arrays", async () => {
|
|
139
|
+
assert.deepEqual(await ops.flat(1, 2, [3]), [1, 2, 3]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("flattens treelike objects", async () => {
|
|
143
|
+
const object = {
|
|
144
|
+
a: 1,
|
|
145
|
+
b: 2,
|
|
146
|
+
};
|
|
147
|
+
const tree = new ObjectTree({
|
|
148
|
+
c: 3,
|
|
149
|
+
d: 4,
|
|
150
|
+
});
|
|
151
|
+
const array = [5, 6];
|
|
152
|
+
const result = await ops.flat(object, tree, array);
|
|
153
|
+
assert.deepEqual(result, [1, 2, 3, 4, 5, 6]);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
151
157
|
test("ops.greaterThan", () => {
|
|
152
158
|
assert(ops.greaterThan(5, 3));
|
|
153
159
|
assert(!ops.greaterThan(3, 3));
|
|
@@ -160,20 +166,6 @@ describe("ops", () => {
|
|
|
160
166
|
assert(ops.greaterThanOrEqual("ab", "aa"));
|
|
161
167
|
});
|
|
162
168
|
|
|
163
|
-
test("ops.inherited searches inherited scope", async () => {
|
|
164
|
-
const parent = new ObjectTree({
|
|
165
|
-
a: 1, // This is the inherited value we want
|
|
166
|
-
});
|
|
167
|
-
/** @type {any} */
|
|
168
|
-
const child = new ObjectTree({
|
|
169
|
-
a: 2, // Should be ignored
|
|
170
|
-
});
|
|
171
|
-
child.parent = parent;
|
|
172
|
-
const code = createCode([ops.inherited, "a"]);
|
|
173
|
-
const result = await evaluate.call(child, code);
|
|
174
|
-
assert.strictEqual(result, 1);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
169
|
test("ops.lambda defines a function with no inputs", async () => {
|
|
178
170
|
const code = createCode([ops.lambda, [], [ops.literal, "result"]]);
|
|
179
171
|
const fn = await evaluate.call(null, code);
|
|
@@ -186,7 +178,7 @@ describe("ops", () => {
|
|
|
186
178
|
message: "Hello",
|
|
187
179
|
});
|
|
188
180
|
|
|
189
|
-
const code = createCode([ops.lambda, ["_"], [ops.scope, "message"]]);
|
|
181
|
+
const code = createCode([ops.lambda, ["_"], [[ops.scope], "message"]]);
|
|
190
182
|
|
|
191
183
|
const fn = await evaluate.call(scope, code);
|
|
192
184
|
const result = await fn.call(scope);
|
|
@@ -200,7 +192,7 @@ describe("ops", () => {
|
|
|
200
192
|
[ops.literal, "a"],
|
|
201
193
|
[ops.literal, "b"],
|
|
202
194
|
],
|
|
203
|
-
[ops.concat, [ops.scope, "b"], [ops.scope, "a"]],
|
|
195
|
+
[ops.concat, [[ops.scope], "b"], [[ops.scope], "a"]],
|
|
204
196
|
]);
|
|
205
197
|
const fn = await evaluate.call(null, code);
|
|
206
198
|
const result = await fn("x", "y");
|
|
@@ -254,40 +246,31 @@ describe("ops", () => {
|
|
|
254
246
|
test("ops.merge", async () => {
|
|
255
247
|
// {
|
|
256
248
|
// a: 1
|
|
257
|
-
// …
|
|
258
|
-
// }
|
|
259
|
-
const scope = new ObjectTree({
|
|
260
|
-
fn: (a) => ({ b: 2 * a }),
|
|
261
|
-
});
|
|
262
|
-
const code = createCode([
|
|
263
|
-
ops.merge,
|
|
264
|
-
[ops.object, ["a", [ops.literal, 1]]],
|
|
265
|
-
[
|
|
266
|
-
[ops.builtin, "fn"],
|
|
267
|
-
[ops.scope, "a"],
|
|
268
|
-
],
|
|
269
|
-
]);
|
|
270
|
-
const result = await evaluate.call(scope, code);
|
|
271
|
-
assert.deepEqual(result, { a: 1, b: 2 });
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("ops.merge lets all direct properties see each other", async () => {
|
|
275
|
-
// {
|
|
276
|
-
// a: 1
|
|
277
|
-
// ...more
|
|
249
|
+
// …more
|
|
278
250
|
// c: a
|
|
279
251
|
// }
|
|
280
252
|
const scope = new ObjectTree({
|
|
281
253
|
more: { b: 2 },
|
|
282
254
|
});
|
|
283
255
|
const code = createCode([
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
256
|
+
[
|
|
257
|
+
ops.object,
|
|
258
|
+
["a", [ops.literal, 1]],
|
|
259
|
+
["c", [[ops.context], "a"]],
|
|
260
|
+
[
|
|
261
|
+
"_result",
|
|
262
|
+
[
|
|
263
|
+
ops.merge,
|
|
264
|
+
[ops.object, ["a", [ops.getter, [[ops.context, 1], "a"]]]],
|
|
265
|
+
[[ops.scope], "more"],
|
|
266
|
+
[ops.object, ["c", [ops.getter, [[ops.context, 1], "c"]]]],
|
|
267
|
+
],
|
|
268
|
+
],
|
|
269
|
+
],
|
|
270
|
+
"_result",
|
|
288
271
|
]);
|
|
289
272
|
const result = await evaluate.call(scope, code);
|
|
290
|
-
assert.deepEqual(result, { a: 1, b: 2, c: 1 });
|
|
273
|
+
assert.deepEqual(await Tree.plain(result), { a: 1, b: 2, c: 1 });
|
|
291
274
|
});
|
|
292
275
|
|
|
293
276
|
test("ops.multiplication multiplies two numbers", async () => {
|
|
@@ -334,8 +317,8 @@ describe("ops", () => {
|
|
|
334
317
|
|
|
335
318
|
const code = createCode([
|
|
336
319
|
ops.object,
|
|
337
|
-
["hello", [[ops.scope, "upper"], "hello"]],
|
|
338
|
-
["world", [[ops.scope, "upper"], "world"]],
|
|
320
|
+
["hello", [[[ops.scope], "upper"], "hello"]],
|
|
321
|
+
["world", [[[ops.scope], "upper"], "world"]],
|
|
339
322
|
]);
|
|
340
323
|
|
|
341
324
|
const result = await evaluate.call(scope, code);
|
|
@@ -351,7 +334,7 @@ describe("ops", () => {
|
|
|
351
334
|
ops.array,
|
|
352
335
|
"Hello",
|
|
353
336
|
1,
|
|
354
|
-
[[ops.scope, "upper"], "world"],
|
|
337
|
+
[[[ops.scope], "upper"], "world"],
|
|
355
338
|
]);
|
|
356
339
|
const result = await evaluate.call(scope, code);
|
|
357
340
|
assert.deepEqual(result, ["Hello", 1, "WORLD"]);
|
|
@@ -364,6 +347,35 @@ describe("ops", () => {
|
|
|
364
347
|
assert.strictEqual(ops.remainder(-4, 2), -0);
|
|
365
348
|
});
|
|
366
349
|
|
|
350
|
+
describe("ops.scope", () => {
|
|
351
|
+
test("returns the scope of the current tree", async () => {
|
|
352
|
+
const tree = new DeepObjectTree({
|
|
353
|
+
a: {
|
|
354
|
+
b: {},
|
|
355
|
+
},
|
|
356
|
+
c: 1,
|
|
357
|
+
});
|
|
358
|
+
const a = await tree.get("a");
|
|
359
|
+
const b = await a.get("b");
|
|
360
|
+
const scope = await ops.scope.call(b);
|
|
361
|
+
assert.equal(await scope.get("c"), 1);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("accepts an optional context", async () => {
|
|
365
|
+
const tree = new DeepObjectTree({
|
|
366
|
+
a: {
|
|
367
|
+
b: {},
|
|
368
|
+
c: 0, // shouldn't get this
|
|
369
|
+
},
|
|
370
|
+
c: 1,
|
|
371
|
+
});
|
|
372
|
+
const a = await tree.get("a");
|
|
373
|
+
const b = await a.get("b");
|
|
374
|
+
const scope = await ops.scope.call(b, tree);
|
|
375
|
+
assert.equal(await scope.get("c"), 1);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
367
379
|
test("ops.shiftLeft", () => {
|
|
368
380
|
assert.strictEqual(ops.shiftLeft(5, 2), 20);
|
|
369
381
|
});
|