@weborigami/language 0.0.58 → 0.0.60
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 +0 -3
- package/package.json +6 -6
- package/src/compiler/origami.pegjs +20 -4
- package/src/compiler/parse.js +440 -294
- package/src/runtime/ExpressionTree.js +3 -17
- package/src/runtime/HandleExtensionsTransform.js +1 -3
- package/src/runtime/InvokeFunctionsTransform.js +2 -4
- package/src/runtime/OrigamiTransform.d.ts +1 -7
- package/src/runtime/OrigamiTransform.js +2 -12
- package/src/runtime/evaluate.js +27 -12
- package/src/runtime/functionResultsMap.js +2 -4
- package/src/runtime/handleExtension.js +5 -5
- package/src/runtime/mergeTrees.js +1 -16
- package/src/runtime/ops.js +48 -31
- package/test/compiler/compile.test.js +4 -4
- package/test/compiler/parse.test.js +8 -0
- package/test/runtime/HandleExtensionsTransform.test.js +7 -8
- package/test/runtime/evaluate.test.js +19 -9
- package/test/runtime/functionResultsMap.test.js +9 -12
- package/test/runtime/mergeTrees.test.js +0 -22
- package/test/runtime/ops.test.js +7 -6
- package/src/runtime/InheritScopeMixin.d.ts +0 -9
- package/src/runtime/InheritScopeMixin.js +0 -34
- package/src/runtime/Scope.js +0 -96
- package/src/runtime/concatTreeValues.js +0 -59
- package/test/runtime/InheritScopeMixin.test.js +0 -29
- package/test/runtime/Scope.test.js +0 -37
- package/test/runtime/concatTreeValues.test.js +0 -33
package/test/runtime/ops.test.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
|
-
import Scope from "../../src/runtime/Scope.js";
|
|
5
4
|
|
|
6
5
|
import {
|
|
7
6
|
OrigamiTree,
|
|
@@ -100,25 +99,27 @@ describe("ops", () => {
|
|
|
100
99
|
]),
|
|
101
100
|
],
|
|
102
101
|
];
|
|
103
|
-
const
|
|
102
|
+
const parent = new ObjectTree({});
|
|
103
|
+
const result = await evaluate.call(parent, code);
|
|
104
104
|
assert(result instanceof OrigamiTree);
|
|
105
105
|
assert.deepEqual(await Tree.plain(result), {
|
|
106
106
|
name: "world",
|
|
107
107
|
message: "Hello, world!",
|
|
108
108
|
});
|
|
109
|
+
assert.equal(result.parent, parent);
|
|
109
110
|
});
|
|
110
111
|
|
|
111
112
|
test("can search inherited scope", async () => {
|
|
112
|
-
const
|
|
113
|
+
const parent = new ObjectTree({
|
|
113
114
|
a: 1, // This is the inherited value we want
|
|
114
115
|
});
|
|
115
116
|
/** @type {any} */
|
|
116
|
-
const
|
|
117
|
+
const child = new ObjectTree({
|
|
117
118
|
a: 2, // Should be ignored
|
|
118
119
|
});
|
|
119
|
-
|
|
120
|
+
child.parent = parent;
|
|
120
121
|
const code = [ops.inherited, "a"];
|
|
121
|
-
const result = await evaluate.call(
|
|
122
|
+
const result = await evaluate.call(child, code);
|
|
122
123
|
assert.equal(result, 1);
|
|
123
124
|
});
|
|
124
125
|
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import Scope from "./Scope.js";
|
|
2
|
-
|
|
3
|
-
const scopeKey = Symbol("scope");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
-
* @typedef {import("../../index.js").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
8
|
-
* @param {AsyncTreeConstructor} Base
|
|
9
|
-
*/
|
|
10
|
-
export default function InheritScopeMixin(Base) {
|
|
11
|
-
return class InheritScope extends Base {
|
|
12
|
-
constructor(...args) {
|
|
13
|
-
super(...args);
|
|
14
|
-
this[scopeKey] = null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** @type {import("@weborigami/types").AsyncTree} */
|
|
18
|
-
get scope() {
|
|
19
|
-
if (this[scopeKey] === null) {
|
|
20
|
-
if (this.parent) {
|
|
21
|
-
// Add parent to this tree's scope.
|
|
22
|
-
this[scopeKey] = new Scope(this, Scope.getScope(this.parent));
|
|
23
|
-
} else {
|
|
24
|
-
// Scope is just the tree itself.
|
|
25
|
-
this[scopeKey] = this;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return this[scopeKey];
|
|
29
|
-
}
|
|
30
|
-
set scope(scope) {
|
|
31
|
-
this[scopeKey] = scope;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
package/src/runtime/Scope.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
5
|
-
* @implements {AsyncTree}
|
|
6
|
-
*/
|
|
7
|
-
export default class Scope {
|
|
8
|
-
constructor(...treelikes) {
|
|
9
|
-
const filtered = treelikes.filter((treelike) => treelike != undefined);
|
|
10
|
-
const trees = filtered.map((treelike) => Tree.from(treelike));
|
|
11
|
-
|
|
12
|
-
// If a tree argument has a `trees` property, use that instead.
|
|
13
|
-
const scopes = trees.flatMap(
|
|
14
|
-
(tree) => /** @type {any} */ (tree).trees ?? tree
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
this.trees = scopes;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async get(key) {
|
|
21
|
-
for (const tree of this.trees) {
|
|
22
|
-
const value = await tree.get(key);
|
|
23
|
-
if (value !== undefined) {
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* If the given tree has a `scope` property, return that. If the tree has a
|
|
32
|
-
* `parent` property, construct a scope for the tree and its parent.
|
|
33
|
-
* Otherwise, return the tree itself.
|
|
34
|
-
*
|
|
35
|
-
* @param {AsyncTree|null|undefined} tree
|
|
36
|
-
* @returns {AsyncTree|null}
|
|
37
|
-
*/
|
|
38
|
-
static getScope(tree) {
|
|
39
|
-
if (!tree) {
|
|
40
|
-
return null;
|
|
41
|
-
} else if (!Tree.isAsyncTree(tree)) {
|
|
42
|
-
throw new Error("Tried to get the scope of something that's not a tree.");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if ("scope" in tree) {
|
|
46
|
-
// Ask tree for its scope, use that if defined.
|
|
47
|
-
const scope = /** @type {any} */ (tree).scope;
|
|
48
|
-
if (scope) {
|
|
49
|
-
return scope;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Default scope is tree plus its parent scope.
|
|
54
|
-
return new Scope(tree, this.getScope(tree.parent));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async keys() {
|
|
58
|
-
const keys = new Set();
|
|
59
|
-
for (const tree of this.trees) {
|
|
60
|
-
for (const key of await tree.keys()) {
|
|
61
|
-
keys.add(key);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return keys;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Return a new tree equivalent to the given tree, but with the given scope.
|
|
69
|
-
*
|
|
70
|
-
* The tree itself will be automatically included at the front of the scope.
|
|
71
|
-
*
|
|
72
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
73
|
-
* @param {Treelike} treelike
|
|
74
|
-
* @param {Treelike|null} scope
|
|
75
|
-
* @returns {AsyncTree & { scope: AsyncTree }}
|
|
76
|
-
*/
|
|
77
|
-
static treeWithScope(treelike, scope) {
|
|
78
|
-
// If the treelike was already a tree, create a copy of it.
|
|
79
|
-
const tree = Tree.isAsyncTree(treelike)
|
|
80
|
-
? Object.create(treelike)
|
|
81
|
-
: Tree.from(treelike);
|
|
82
|
-
tree.scope = new Scope(tree, scope);
|
|
83
|
-
return tree;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async unwatch() {
|
|
87
|
-
for (const tree of this.trees) {
|
|
88
|
-
await /** @type {any} */ (tree).unwatch?.();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async watch() {
|
|
92
|
-
for (const tree of this.trees) {
|
|
93
|
-
await /** @type {any} */ (tree).watch?.();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { Tree, getRealmObjectPrototype } from "@weborigami/async-tree";
|
|
2
|
-
|
|
3
|
-
const textDecoder = new TextDecoder();
|
|
4
|
-
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Concatenate the text values in a tree.
|
|
8
|
-
*
|
|
9
|
-
* This is a map-reduce operation: convert everything to strings, then
|
|
10
|
-
* concatenate the strings.
|
|
11
|
-
*
|
|
12
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
13
|
-
*
|
|
14
|
-
* @this {AsyncTree|null}
|
|
15
|
-
* @param {import("@weborigami/async-tree").Treelike} treelike
|
|
16
|
-
*/
|
|
17
|
-
export default async function concatTreeValues(treelike) {
|
|
18
|
-
const scope = this;
|
|
19
|
-
const mapFn = async (value) => getText(value, scope);
|
|
20
|
-
const reduceFn = (values) => values.join("");
|
|
21
|
-
return Tree.mapReduce(treelike, mapFn, reduceFn);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function getText(value, scope) {
|
|
25
|
-
// If the value is a function (e.g., a lambda), call it and use its result.
|
|
26
|
-
if (typeof value === "function") {
|
|
27
|
-
value = await value.call(scope);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (Tree.isTreelike(value)) {
|
|
31
|
-
// The mapReduce operation above only implicit casts its top-level input to
|
|
32
|
-
// a tree. If we're asked for the text of a treelike value, we need to
|
|
33
|
-
// explicitly recurse.
|
|
34
|
-
return concatTreeValues.call(scope, value);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Convert to text, preferring .toString but avoiding dumb Object.toString.
|
|
38
|
-
// Exception: if the result is an array, we'll concatenate the values.
|
|
39
|
-
let text;
|
|
40
|
-
if (value == null || value === false) {
|
|
41
|
-
// Treat falsy values (but not zero) as the empty string.
|
|
42
|
-
text = "";
|
|
43
|
-
} else if (typeof value === "string") {
|
|
44
|
-
text = value;
|
|
45
|
-
} else if (value instanceof ArrayBuffer || value instanceof TypedArray) {
|
|
46
|
-
// Serialize data as UTF-8.
|
|
47
|
-
text = textDecoder.decode(value);
|
|
48
|
-
} else if (
|
|
49
|
-
!(value instanceof Array) &&
|
|
50
|
-
value.toString !== getRealmObjectPrototype(value)?.toString
|
|
51
|
-
) {
|
|
52
|
-
text = value.toString();
|
|
53
|
-
} else {
|
|
54
|
-
// Anything else maps to the empty string.
|
|
55
|
-
text = "";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return text;
|
|
59
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { DeepObjectTree, ObjectTree } from "@weborigami/async-tree";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import { describe, test } from "node:test";
|
|
4
|
-
import InheritScopeMixin from "../../src/runtime/InheritScopeMixin.js";
|
|
5
|
-
|
|
6
|
-
describe("InheritScopeMixin", () => {
|
|
7
|
-
test("creates a scope that includes a tree and its parent", async () => {
|
|
8
|
-
const fixture = new (InheritScopeMixin(ObjectTree))({
|
|
9
|
-
b: 2,
|
|
10
|
-
});
|
|
11
|
-
fixture.parent = new ObjectTree({
|
|
12
|
-
a: 1,
|
|
13
|
-
});
|
|
14
|
-
assert.deepEqual(await fixture.scope?.get("b"), 2);
|
|
15
|
-
assert.deepEqual(await fixture.scope?.get("a"), 1);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("adds a subtree's parent to the subtrees's scope", async () => {
|
|
19
|
-
const fixture = new (InheritScopeMixin(DeepObjectTree))({
|
|
20
|
-
a: 1,
|
|
21
|
-
subtree: {
|
|
22
|
-
b: 2,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
const subtree = await fixture.get("subtree");
|
|
26
|
-
assert.deepEqual(await subtree.scope.get("b"), 2);
|
|
27
|
-
assert.deepEqual(await subtree.scope.get("a"), 1);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import Scope from "../../src/runtime/Scope.js";
|
|
4
|
-
|
|
5
|
-
describe("Scope", () => {
|
|
6
|
-
test("composes and flattens scopes and trees passed to it", async () => {
|
|
7
|
-
const treeA = {
|
|
8
|
-
a: 1,
|
|
9
|
-
};
|
|
10
|
-
const treeB = {
|
|
11
|
-
b: 2,
|
|
12
|
-
};
|
|
13
|
-
const treeC = {
|
|
14
|
-
c: 3,
|
|
15
|
-
};
|
|
16
|
-
const scope1 = new Scope(treeA, treeB);
|
|
17
|
-
const scope2 = new Scope(scope1, treeC);
|
|
18
|
-
const objects = scope2.trees.map(
|
|
19
|
-
(tree) => /** @type {any} */ (tree).object
|
|
20
|
-
);
|
|
21
|
-
assert.deepEqual(objects, [treeA, treeB, treeC]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("gets the first defined value from the scope trees", async () => {
|
|
25
|
-
const scope = new Scope(
|
|
26
|
-
{
|
|
27
|
-
a: 1,
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
a: 2,
|
|
31
|
-
b: 3,
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
assert.equal(await scope.get("a"), 1);
|
|
35
|
-
assert.equal(await scope.get("b"), 3);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { FunctionTree, Tree } from "@weborigami/async-tree";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import { describe, test } from "node:test";
|
|
4
|
-
import concatTreeValues from "../../src/runtime/concatTreeValues.js";
|
|
5
|
-
|
|
6
|
-
describe("concatTreeValues", () => {
|
|
7
|
-
test("concatenates deep tree values", async () => {
|
|
8
|
-
const tree = Tree.from({
|
|
9
|
-
a: "A",
|
|
10
|
-
b: "B",
|
|
11
|
-
c: "C",
|
|
12
|
-
more: {
|
|
13
|
-
d: "D",
|
|
14
|
-
e: "E",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
const result = await concatTreeValues.call(null, tree);
|
|
18
|
-
assert.equal(result, "ABCDE");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("concatenates deep tree-like values", async () => {
|
|
22
|
-
const letters = ["a", "b", "c"];
|
|
23
|
-
const specimens = new FunctionTree(
|
|
24
|
-
(letter) => ({
|
|
25
|
-
lowercase: letter,
|
|
26
|
-
uppercase: letter.toUpperCase(),
|
|
27
|
-
}),
|
|
28
|
-
letters
|
|
29
|
-
);
|
|
30
|
-
const result = await concatTreeValues.call(null, specimens);
|
|
31
|
-
assert.equal(result, "aAbBcC");
|
|
32
|
-
});
|
|
33
|
-
});
|