@weborigami/language 0.0.66-beta.1 → 0.0.66-beta.2
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/package.json +5 -5
- package/src/compiler/origami.pegjs +40 -22
- package/src/compiler/parse.js +282 -204
- package/src/compiler/parserHelpers.js +39 -4
- package/src/runtime/HandleExtensionsTransform.js +3 -4
- package/src/runtime/ImportModulesMixin.js +13 -10
- package/src/runtime/InvokeFunctionsTransform.js +0 -6
- package/src/runtime/expressionObject.js +3 -3
- package/src/runtime/extensions.js +55 -47
- package/src/runtime/formatError.js +5 -1
- package/src/runtime/ops.js +71 -28
- package/test/compiler/compile.test.js +7 -1
- package/test/compiler/parse.test.js +72 -29
- package/test/runtime/extensions.test.js +9 -6
- package/test/runtime/ops.test.js +38 -31
|
@@ -8,7 +8,7 @@ describe("Origami parser", () => {
|
|
|
8
8
|
test("absoluteFilePath", () => {
|
|
9
9
|
assertParse("absoluteFilePath", "/foo/bar", [
|
|
10
10
|
[ops.filesRoot],
|
|
11
|
-
[ops.primitive, "foo"],
|
|
11
|
+
[ops.primitive, "foo/"],
|
|
12
12
|
[ops.primitive, "bar"],
|
|
13
13
|
]);
|
|
14
14
|
});
|
|
@@ -134,11 +134,10 @@ describe("Origami parser", () => {
|
|
|
134
134
|
]
|
|
135
135
|
);
|
|
136
136
|
|
|
137
|
-
// Consecutive slahes
|
|
137
|
+
// Consecutive slahes in a path are removed
|
|
138
138
|
assertParse("expression", "path//key", [
|
|
139
139
|
ops.traverse,
|
|
140
|
-
[ops.scope, "path"],
|
|
141
|
-
[ops.primitive, ""],
|
|
140
|
+
[ops.scope, "path/"],
|
|
142
141
|
[ops.primitive, "key"],
|
|
143
142
|
]);
|
|
144
143
|
// Single slash at start of something = absolute file path
|
|
@@ -177,24 +176,27 @@ describe("Origami parser", () => {
|
|
|
177
176
|
]);
|
|
178
177
|
assertParse("functionComposition", "tree/", [
|
|
179
178
|
ops.traverse,
|
|
180
|
-
[ops.scope, "tree"],
|
|
181
|
-
[ops.primitive, ""],
|
|
179
|
+
[ops.scope, "tree/"],
|
|
182
180
|
]);
|
|
183
181
|
assertParse("functionComposition", "tree/key", [
|
|
184
182
|
ops.traverse,
|
|
185
|
-
[ops.scope, "tree"],
|
|
183
|
+
[ops.scope, "tree/"],
|
|
186
184
|
[ops.primitive, "key"],
|
|
187
185
|
]);
|
|
188
186
|
assertParse("functionComposition", "tree/foo/bar", [
|
|
189
187
|
ops.traverse,
|
|
190
|
-
[ops.scope, "tree"],
|
|
191
|
-
[ops.primitive, "foo"],
|
|
188
|
+
[ops.scope, "tree/"],
|
|
189
|
+
[ops.primitive, "foo/"],
|
|
192
190
|
[ops.primitive, "bar"],
|
|
193
191
|
]);
|
|
194
192
|
assertParse("functionComposition", "tree/key()", [
|
|
195
|
-
[ops.traverse, [ops.scope, "tree"], [ops.primitive, "key"]],
|
|
193
|
+
[ops.traverse, [ops.scope, "tree/"], [ops.primitive, "key"]],
|
|
196
194
|
undefined,
|
|
197
195
|
]);
|
|
196
|
+
assertParse("functionComposition", "(tree)/", [
|
|
197
|
+
ops.unpack,
|
|
198
|
+
[ops.scope, "tree"],
|
|
199
|
+
]);
|
|
198
200
|
assertParse("functionComposition", "fn()/key()", [
|
|
199
201
|
[ops.traverse, [[ops.scope, "fn"], undefined], [ops.primitive, "key"]],
|
|
200
202
|
undefined,
|
|
@@ -249,7 +251,7 @@ describe("Origami parser", () => {
|
|
|
249
251
|
[ops.primitive, "arg"],
|
|
250
252
|
]);
|
|
251
253
|
assertParse("functionComposition", "tree/key arg", [
|
|
252
|
-
[ops.traverse, [ops.scope, "tree"], [ops.primitive, "key"]],
|
|
254
|
+
[ops.traverse, [ops.scope, "tree/"], [ops.primitive, "key"]],
|
|
253
255
|
[ops.scope, "arg"],
|
|
254
256
|
]);
|
|
255
257
|
assertParse("functionComposition", "https://example.com/tree.yaml 'key'", [
|
|
@@ -267,6 +269,7 @@ describe("Origami parser", () => {
|
|
|
267
269
|
test("host", () => {
|
|
268
270
|
assertParse("host", "abc", [ops.primitive, "abc"]);
|
|
269
271
|
assertParse("host", "abc:123", [ops.primitive, "abc:123"]);
|
|
272
|
+
assertParse("host", "foo\\ bar", [ops.primitive, "foo bar"]);
|
|
270
273
|
});
|
|
271
274
|
|
|
272
275
|
test("identifier", () => {
|
|
@@ -295,10 +298,9 @@ describe("Origami parser", () => {
|
|
|
295
298
|
});
|
|
296
299
|
|
|
297
300
|
test("leadingSlashPath", () => {
|
|
298
|
-
assertParse("leadingSlashPath", "/
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
]);
|
|
301
|
+
assertParse("leadingSlashPath", "/", []);
|
|
302
|
+
assertParse("leadingSlashPath", "/tree", [[ops.primitive, "tree"]]);
|
|
303
|
+
assertParse("leadingSlashPath", "/tree/", [[ops.primitive, "tree/"]]);
|
|
302
304
|
});
|
|
303
305
|
|
|
304
306
|
test("list", () => {
|
|
@@ -358,16 +360,36 @@ describe("Origami parser", () => {
|
|
|
358
360
|
["a", [ops.primitive, 1]],
|
|
359
361
|
["b", [ops.inherited, "b"]],
|
|
360
362
|
]);
|
|
363
|
+
assertParse("object", "{ sub: { a: 1 } }", [
|
|
364
|
+
ops.object,
|
|
365
|
+
["sub", [ops.object, ["a", [ops.primitive, 1]]]],
|
|
366
|
+
]);
|
|
367
|
+
assertParse("object", "{ sub: { a/: 1 } }", [
|
|
368
|
+
ops.object,
|
|
369
|
+
["sub", [ops.object, ["a/", [ops.primitive, 1]]]],
|
|
370
|
+
]);
|
|
361
371
|
assertParse("object", `{ "a": 1, "b": 2 }`, [
|
|
362
372
|
ops.object,
|
|
363
373
|
["a", [ops.primitive, 1]],
|
|
364
374
|
["b", [ops.primitive, 2]],
|
|
365
375
|
]);
|
|
366
|
-
assertParse("object", "{ a = b, b
|
|
376
|
+
assertParse("object", "{ a = b, b = 2 }", [
|
|
367
377
|
ops.object,
|
|
368
378
|
["a", [ops.getter, [ops.scope, "b"]]],
|
|
369
379
|
["b", [ops.primitive, 2]],
|
|
370
380
|
]);
|
|
381
|
+
assertParse("object", "{ a: { b: 1 } }", [
|
|
382
|
+
ops.object,
|
|
383
|
+
["a", [ops.object, ["b", [ops.primitive, 1]]]],
|
|
384
|
+
]);
|
|
385
|
+
assertParse("object", "{ a: { b = 1 } }", [
|
|
386
|
+
ops.object,
|
|
387
|
+
["a", [ops.object, ["b", [ops.primitive, 1]]]],
|
|
388
|
+
]);
|
|
389
|
+
assertParse("object", "{ a: { b = fn() } }", [
|
|
390
|
+
ops.object,
|
|
391
|
+
["a/", [ops.object, ["b", [ops.getter, [[ops.scope, "fn"], undefined]]]]],
|
|
392
|
+
]);
|
|
371
393
|
assertParse("object", "{ x = fn('a') }", [
|
|
372
394
|
ops.object,
|
|
373
395
|
[
|
|
@@ -429,6 +451,12 @@ describe("Origami parser", () => {
|
|
|
429
451
|
]);
|
|
430
452
|
});
|
|
431
453
|
|
|
454
|
+
test("objectPublicKey", () => {
|
|
455
|
+
assertParse("objectPublicKey", "a", "a", false);
|
|
456
|
+
assertParse("objectPublicKey", "markdown/", "markdown/", false);
|
|
457
|
+
assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
|
|
458
|
+
});
|
|
459
|
+
|
|
432
460
|
test("parameterizedLambda", () => {
|
|
433
461
|
assertParse("parameterizedLambda", "() => foo", [
|
|
434
462
|
ops.lambda,
|
|
@@ -470,19 +498,20 @@ describe("Origami parser", () => {
|
|
|
470
498
|
});
|
|
471
499
|
|
|
472
500
|
test("path", () => {
|
|
473
|
-
assertParse("path", "tree/", [
|
|
474
|
-
[ops.primitive, "tree"],
|
|
475
|
-
[ops.primitive, ""],
|
|
476
|
-
]);
|
|
501
|
+
assertParse("path", "tree/", [[ops.primitive, "tree/"]]);
|
|
477
502
|
assertParse("path", "month/12", [
|
|
478
|
-
[ops.primitive, "month"],
|
|
503
|
+
[ops.primitive, "month/"],
|
|
479
504
|
[ops.primitive, "12"],
|
|
480
505
|
]);
|
|
481
506
|
assertParse("path", "tree/foo/bar", [
|
|
482
|
-
[ops.primitive, "tree"],
|
|
483
|
-
[ops.primitive, "foo"],
|
|
507
|
+
[ops.primitive, "tree/"],
|
|
508
|
+
[ops.primitive, "foo/"],
|
|
484
509
|
[ops.primitive, "bar"],
|
|
485
510
|
]);
|
|
511
|
+
assertParse("path", "a///b", [
|
|
512
|
+
[ops.primitive, "a/"],
|
|
513
|
+
[ops.primitive, "b"],
|
|
514
|
+
]);
|
|
486
515
|
});
|
|
487
516
|
|
|
488
517
|
test("pipeline", () => {
|
|
@@ -514,8 +543,7 @@ describe("Origami parser", () => {
|
|
|
514
543
|
assertParse("protocolCall", "https://example.com/foo/", [
|
|
515
544
|
ops.https,
|
|
516
545
|
[ops.primitive, "example.com"],
|
|
517
|
-
[ops.primitive, "foo"],
|
|
518
|
-
[ops.primitive, ""],
|
|
546
|
+
[ops.primitive, "foo/"],
|
|
519
547
|
]);
|
|
520
548
|
assertParse("protocolCall", "http:example.com", [
|
|
521
549
|
ops.http,
|
|
@@ -528,6 +556,25 @@ describe("Origami parser", () => {
|
|
|
528
556
|
]);
|
|
529
557
|
});
|
|
530
558
|
|
|
559
|
+
test("scopeReference", () => {
|
|
560
|
+
assertParse("scopeReference", "x", [ops.scope, "x"]);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("scopeTraverse", () => {
|
|
564
|
+
assertParse("scopeTraverse", "tree/foo/bar", [
|
|
565
|
+
ops.traverse,
|
|
566
|
+
[ops.scope, "tree/"],
|
|
567
|
+
[ops.primitive, "foo/"],
|
|
568
|
+
[ops.primitive, "bar"],
|
|
569
|
+
]);
|
|
570
|
+
assertParse("scopeTraverse", "tree/foo/bar/", [
|
|
571
|
+
ops.traverse,
|
|
572
|
+
[ops.scope, "tree/"],
|
|
573
|
+
[ops.primitive, "foo/"],
|
|
574
|
+
[ops.primitive, "bar/"],
|
|
575
|
+
]);
|
|
576
|
+
});
|
|
577
|
+
|
|
531
578
|
test("shebang", () => {
|
|
532
579
|
assertParse(
|
|
533
580
|
"expression",
|
|
@@ -543,10 +590,6 @@ describe("Origami parser", () => {
|
|
|
543
590
|
assertParse("singleLineComment", "// Hello, world!", null, false);
|
|
544
591
|
});
|
|
545
592
|
|
|
546
|
-
test("scopeReference", () => {
|
|
547
|
-
assertParse("scopeReference", "x", [ops.scope, "x"]);
|
|
548
|
-
});
|
|
549
|
-
|
|
550
593
|
test("spread", () => {
|
|
551
594
|
assertParse("spread", "...a", [ops.spread, [ops.scope, "a"]]);
|
|
552
595
|
assertParse("spread", "…a", [ops.spread, [ops.scope, "a"]]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ObjectTree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
|
-
import {
|
|
4
|
+
import { handleExtension } from "../../src/runtime/extensions.js";
|
|
5
5
|
|
|
6
6
|
describe("extensions", () => {
|
|
7
7
|
test("attaches an unpack method to a value with an extension", async () => {
|
|
@@ -10,15 +10,18 @@ describe("extensions", () => {
|
|
|
10
10
|
assert(typeof numberValue === "number");
|
|
11
11
|
assert.equal(numberValue, 1);
|
|
12
12
|
const jsonFile = await fixture.get("bar.json");
|
|
13
|
-
const withHandler = await
|
|
14
|
-
fixture,
|
|
15
|
-
jsonFile,
|
|
16
|
-
"bar.json"
|
|
17
|
-
);
|
|
13
|
+
const withHandler = await handleExtension(fixture, jsonFile, "bar.json");
|
|
18
14
|
assert.equal(String(withHandler), `{ "bar": 2 }`);
|
|
19
15
|
const data = await withHandler.unpack();
|
|
20
16
|
assert.deepEqual(data, { bar: 2 });
|
|
21
17
|
});
|
|
18
|
+
|
|
19
|
+
test("immediately unpacks if key ends in slash", async () => {
|
|
20
|
+
const fixture = createFixture();
|
|
21
|
+
const jsonFile = await fixture.get("bar.json");
|
|
22
|
+
const data = await handleExtension(fixture, jsonFile, "bar.json/");
|
|
23
|
+
assert.deepEqual(data, { bar: 2 });
|
|
24
|
+
});
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
function createFixture() {
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -5,7 +5,7 @@ import { describe, test } from "node:test";
|
|
|
5
5
|
import { evaluate, ops } from "../../src/runtime/internal.js";
|
|
6
6
|
|
|
7
7
|
describe("ops", () => {
|
|
8
|
-
test("
|
|
8
|
+
test("ops.concat concatenates tree value text", async () => {
|
|
9
9
|
const scope = new ObjectTree({
|
|
10
10
|
name: "world",
|
|
11
11
|
});
|
|
@@ -16,7 +16,33 @@ describe("ops", () => {
|
|
|
16
16
|
assert.equal(result, "Hello, world.");
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
test("
|
|
19
|
+
test("ops.constructor returns a constructor", async () => {
|
|
20
|
+
const scope = new ObjectTree({
|
|
21
|
+
"@js": {
|
|
22
|
+
Number: Number,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const fn = await ops.constructor.call(scope, "@js", "Number");
|
|
26
|
+
const number = fn("1");
|
|
27
|
+
assert(number instanceof Number);
|
|
28
|
+
assert.equal(number, 1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("ops.inherited searches inherited scope", async () => {
|
|
32
|
+
const parent = new ObjectTree({
|
|
33
|
+
a: 1, // This is the inherited value we want
|
|
34
|
+
});
|
|
35
|
+
/** @type {any} */
|
|
36
|
+
const child = new ObjectTree({
|
|
37
|
+
a: 2, // Should be ignored
|
|
38
|
+
});
|
|
39
|
+
child.parent = parent;
|
|
40
|
+
const code = createCode([ops.inherited, "a"]);
|
|
41
|
+
const result = await evaluate.call(child, code);
|
|
42
|
+
assert.equal(result, 1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("ops.lambda defines a function", async () => {
|
|
20
46
|
const scope = new ObjectTree({
|
|
21
47
|
message: "Hello",
|
|
22
48
|
});
|
|
@@ -28,14 +54,14 @@ describe("ops", () => {
|
|
|
28
54
|
assert.equal(result, "Hello");
|
|
29
55
|
});
|
|
30
56
|
|
|
31
|
-
test("lambda adds input to scope as `_`", async () => {
|
|
57
|
+
test("ops.lambda adds input to scope as `_`", async () => {
|
|
32
58
|
const code = createCode([ops.lambda, null, [ops.scope, "_"]]);
|
|
33
59
|
const fn = await evaluate.call(null, code);
|
|
34
60
|
const result = await fn("Hello");
|
|
35
61
|
assert.equal(result, "Hello");
|
|
36
62
|
});
|
|
37
63
|
|
|
38
|
-
test("
|
|
64
|
+
test("ops.lambda adds input parameters to scope", async () => {
|
|
39
65
|
const code = createCode([
|
|
40
66
|
ops.lambda,
|
|
41
67
|
["a", "b"],
|
|
@@ -46,7 +72,7 @@ describe("ops", () => {
|
|
|
46
72
|
assert.equal(result, "yx");
|
|
47
73
|
});
|
|
48
74
|
|
|
49
|
-
test("
|
|
75
|
+
test("ops.lambda function can reference itself with @recurse", async () => {
|
|
50
76
|
const code = createCode([ops.lambda, null, [ops.scope, "@recurse"]]);
|
|
51
77
|
const fn = await evaluate.call(null, code);
|
|
52
78
|
const result = await fn();
|
|
@@ -55,7 +81,7 @@ describe("ops", () => {
|
|
|
55
81
|
assert.equal(result.code, fn.code);
|
|
56
82
|
});
|
|
57
83
|
|
|
58
|
-
test("
|
|
84
|
+
test("ops.object instantiates an object", async () => {
|
|
59
85
|
const scope = new ObjectTree({
|
|
60
86
|
upper: (s) => s.toUpperCase(),
|
|
61
87
|
});
|
|
@@ -71,7 +97,7 @@ describe("ops", () => {
|
|
|
71
97
|
assert.equal(result.world, "WORLD");
|
|
72
98
|
});
|
|
73
99
|
|
|
74
|
-
test("
|
|
100
|
+
test("ops.object instantiates an array", async () => {
|
|
75
101
|
const scope = new ObjectTree({
|
|
76
102
|
upper: (s) => s.toUpperCase(),
|
|
77
103
|
});
|
|
@@ -85,30 +111,11 @@ describe("ops", () => {
|
|
|
85
111
|
assert.deepEqual(result, ["Hello", 1, "WORLD"]);
|
|
86
112
|
});
|
|
87
113
|
|
|
88
|
-
test("
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const child = new ObjectTree({
|
|
94
|
-
a: 2, // Should be ignored
|
|
95
|
-
});
|
|
96
|
-
child.parent = parent;
|
|
97
|
-
const code = createCode([ops.inherited, "a"]);
|
|
98
|
-
const result = await evaluate.call(child, code);
|
|
99
|
-
assert.equal(result, 1);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("returns a constructor", async () => {
|
|
103
|
-
const scope = new ObjectTree({
|
|
104
|
-
"@js": {
|
|
105
|
-
Number: Number,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
const fn = await ops.constructor.call(scope, "@js", "Number");
|
|
109
|
-
const number = fn("1");
|
|
110
|
-
assert(number instanceof Number);
|
|
111
|
-
assert.equal(number, 1);
|
|
114
|
+
test("ops.unpack unpacks a value", async () => {
|
|
115
|
+
const fixture = new String("packed");
|
|
116
|
+
/** @type {any} */ (fixture).unpack = async () => "unpacked";
|
|
117
|
+
const result = await ops.unpack.call(null, fixture);
|
|
118
|
+
assert.equal(result, "unpacked");
|
|
112
119
|
});
|
|
113
120
|
});
|
|
114
121
|
|