@weborigami/language 0.0.59 → 0.0.61
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 -2
- package/package.json +3 -3
- package/src/compiler/origami.pegjs +8 -1
- package/src/compiler/parse.js +373 -277
- 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 +46 -29
- package/test/compiler/compile.test.js +4 -4
- package/test/compiler/parse.test.js +1 -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 -26
- 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/test/runtime/InheritScopeMixin.test.js +0 -29
- package/test/runtime/Scope.test.js +0 -37
- package/test/runtime/fixtures/programs/context.yaml +0 -4
- package/test/runtime/fixtures/programs/files.yaml +0 -2
- package/test/runtime/fixtures/programs/obj.yaml +0 -3
- package/test/runtime/fixtures/programs/simple.yaml +0 -2
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DeepObjectTree } from "@weborigami/async-tree";
|
|
2
2
|
import InvokeFunctionsTransform from "./InvokeFunctionsTransform.js";
|
|
3
|
-
import { expressionFunction } from "./internal.js";
|
|
4
3
|
|
|
5
4
|
export default class ExpressionTree extends InvokeFunctionsTransform(
|
|
6
|
-
|
|
7
|
-
) {
|
|
8
|
-
// Return the unevaluated expressions in the original object.
|
|
9
|
-
expressions() {
|
|
10
|
-
const obj = /** @type {any} */ (this).object;
|
|
11
|
-
const result = {};
|
|
12
|
-
for (const key in obj) {
|
|
13
|
-
const value = obj[key];
|
|
14
|
-
if (expressionFunction.isExpressionFunction(value)) {
|
|
15
|
-
result[key] = value.code;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
5
|
+
DeepObjectTree
|
|
6
|
+
) {}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isStringLike } from "@weborigami/async-tree";
|
|
2
|
-
import Scope from "./Scope.js";
|
|
3
2
|
import handleExtension from "./handleExtension.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -17,8 +16,7 @@ export default function HandleExtensionsTransform(Base) {
|
|
|
17
16
|
// If the key is string-like and has an extension, attach a loader (if one
|
|
18
17
|
// exists) that handles that extension.
|
|
19
18
|
if (value && isStringLike(key)) {
|
|
20
|
-
|
|
21
|
-
value = await handleExtension(scope, String(key), value, this);
|
|
19
|
+
value = await handleExtension(this, String(key), value);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
return value;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import Scope from "./Scope.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* When using `get` to retrieve a value from a tree, if the value is a
|
|
@@ -14,10 +13,9 @@ export default function InvokeFunctionsTransform(Base) {
|
|
|
14
13
|
async get(key) {
|
|
15
14
|
let value = await super.get(key);
|
|
16
15
|
if (typeof value === "function") {
|
|
17
|
-
|
|
18
|
-
value = await value.call(scope);
|
|
16
|
+
value = await value.call(this);
|
|
19
17
|
|
|
20
|
-
if (Tree.isAsyncTree(value)) {
|
|
18
|
+
if (Tree.isAsyncTree(value) && !value.parent) {
|
|
21
19
|
value.parent = this;
|
|
22
20
|
}
|
|
23
21
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { Mixin } from "../../index.ts";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// TODO: Figure out how to import declarations from InheritScopeMixin and
|
|
6
|
-
// HandleExtensionsTransform and apply them here.
|
|
7
|
-
declare const OrigamiTransform: Mixin<{
|
|
8
|
-
scope: AsyncTree|null;
|
|
9
|
-
}>;
|
|
3
|
+
declare const OrigamiTransform: Mixin<{}>;
|
|
10
4
|
|
|
11
5
|
export default OrigamiTransform;
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
+
// Right now this just applies HandleExtensionsTransform.
|
|
1
2
|
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
6
|
-
* @typedef {import("../../index.js").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
7
|
-
* @param {AsyncTreeConstructor} Base
|
|
8
|
-
*/
|
|
9
|
-
export default function OrigamiTransform(Base) {
|
|
10
|
-
return class Origami extends InheritScopeMixin(
|
|
11
|
-
HandleExtensionsTransform(Base)
|
|
12
|
-
) {};
|
|
13
|
-
}
|
|
3
|
+
export default HandleExtensionsTransform;
|
package/src/runtime/evaluate.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Tree,
|
|
3
|
+
isPlainObject,
|
|
4
|
+
isUnpackable,
|
|
5
|
+
scope,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
2
7
|
import { ops } from "./internal.js";
|
|
3
8
|
|
|
4
9
|
const codeSymbol = Symbol("code");
|
|
10
|
+
const scopeSymbol = Symbol("scope");
|
|
5
11
|
const sourceSymbol = Symbol("source");
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Evaluate the given code and return the result.
|
|
9
15
|
*
|
|
10
|
-
* `this` should be the
|
|
16
|
+
* `this` should be the tree used as the context for the evaluation.
|
|
11
17
|
*
|
|
12
|
-
* @
|
|
13
|
-
*
|
|
14
|
-
* @this {Treelike|null}
|
|
18
|
+
* @this {import("@weborigami/types").AsyncTree|null}
|
|
15
19
|
* @param {any} code
|
|
16
20
|
*/
|
|
17
21
|
export default async function evaluate(code) {
|
|
18
|
-
const
|
|
22
|
+
const tree = this;
|
|
19
23
|
|
|
20
|
-
if (code
|
|
21
|
-
// ops.scope is a placeholder for the context's scope.
|
|
22
|
-
return scope;
|
|
23
|
-
} else if (!(code instanceof Array)) {
|
|
24
|
+
if (!(code instanceof Array)) {
|
|
24
25
|
// Simple scalar; return as is.
|
|
25
26
|
return code;
|
|
26
27
|
}
|
|
@@ -33,7 +34,7 @@ export default async function evaluate(code) {
|
|
|
33
34
|
} else {
|
|
34
35
|
// Evaluate each instruction in the code.
|
|
35
36
|
evaluated = await Promise.all(
|
|
36
|
-
code.map((instruction) => evaluate.call(
|
|
37
|
+
code.map((instruction) => evaluate.call(tree, instruction))
|
|
37
38
|
);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -61,7 +62,7 @@ export default async function evaluate(code) {
|
|
|
61
62
|
try {
|
|
62
63
|
result =
|
|
63
64
|
fn instanceof Function
|
|
64
|
-
? await fn.call(
|
|
65
|
+
? await fn.call(tree, ...args) // Invoke the function
|
|
65
66
|
: await Tree.traverseOrThrow(fn, ...args); // Traverse the tree.
|
|
66
67
|
} catch (/** @type {any} */ error) {
|
|
67
68
|
if (!error.location) {
|
|
@@ -71,6 +72,12 @@ export default async function evaluate(code) {
|
|
|
71
72
|
throw error;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// If the result is a tree, then the default parent of the tree is the current
|
|
76
|
+
// tree.
|
|
77
|
+
if (Tree.isAsyncTree(result) && !result.parent) {
|
|
78
|
+
result.parent = tree;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
// To aid debugging, add the code to the result.
|
|
75
82
|
if (
|
|
76
83
|
result &&
|
|
@@ -83,6 +90,14 @@ export default async function evaluate(code) {
|
|
|
83
90
|
if (/** @type {any} */ (code).location) {
|
|
84
91
|
result[sourceSymbol] = codeFragment(code);
|
|
85
92
|
}
|
|
93
|
+
if (!result[scopeSymbol]) {
|
|
94
|
+
Object.defineProperty(result, scopeSymbol, {
|
|
95
|
+
get() {
|
|
96
|
+
return scope(result).trees;
|
|
97
|
+
},
|
|
98
|
+
enumerable: false,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
86
101
|
} catch (/** @type {any} */ error) {
|
|
87
102
|
// Ignore errors.
|
|
88
103
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { map, Tree } from "@weborigami/async-tree";
|
|
2
|
-
import Scope from "./Scope.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* When using `get` to retrieve a value from a tree, if the value is a
|
|
@@ -12,9 +11,8 @@ export default function functionResultsMap(treelike) {
|
|
|
12
11
|
value: async (sourceValue, sourceKey, tree) => {
|
|
13
12
|
let resultValue;
|
|
14
13
|
if (typeof sourceValue === "function") {
|
|
15
|
-
|
|
16
|
-
resultValue
|
|
17
|
-
if (Tree.isAsyncTree(resultValue)) {
|
|
14
|
+
resultValue = await sourceValue.call(tree);
|
|
15
|
+
if (Tree.isAsyncTree(resultValue) && !resultValue.parent) {
|
|
18
16
|
resultValue.parent = tree;
|
|
19
17
|
}
|
|
20
18
|
} else {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isUnpackable, symbols } from "@weborigami/async-tree";
|
|
1
|
+
import { isUnpackable, scope, symbols } from "@weborigami/async-tree";
|
|
2
2
|
import extname from "./extname.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -8,18 +8,18 @@ import extname from "./extname.js";
|
|
|
8
8
|
*
|
|
9
9
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
10
|
*
|
|
11
|
-
* @param {AsyncTree
|
|
11
|
+
* @param {AsyncTree} parent
|
|
12
12
|
* @param {any} key
|
|
13
13
|
* @param {any} value
|
|
14
|
-
* @param {AsyncTree|null} parent
|
|
15
14
|
*/
|
|
16
|
-
export default async function handleExtension(
|
|
15
|
+
export default async function handleExtension(parent, key, value) {
|
|
17
16
|
const extension = extname(key);
|
|
18
17
|
let result = value;
|
|
19
18
|
if (extension) {
|
|
20
19
|
const handlerName = `${extension.slice(1)}_handler`;
|
|
20
|
+
const parentScope = scope(parent);
|
|
21
21
|
/** @type {import("../../index.ts").ExtensionHandler} */
|
|
22
|
-
let extensionHandler = await
|
|
22
|
+
let extensionHandler = await parentScope?.get(handlerName);
|
|
23
23
|
if (isUnpackable(extensionHandler)) {
|
|
24
24
|
// The extension handler itself needs to be unpacked. E.g., if it's a
|
|
25
25
|
// buffer containing JavaScript file, we need to unpack it to get its
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isPlainObject, isUnpackable, merge } from "@weborigami/async-tree";
|
|
2
|
-
import Scope from "./Scope.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Create a tree that's the result of merging the given trees.
|
|
@@ -38,21 +37,7 @@ export default async function mergeTrees(...trees) {
|
|
|
38
37
|
return unpacked.flat();
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
// If a tree can take a scope, give it one that includes the other trees and
|
|
42
|
-
// the current scope.
|
|
43
|
-
const scopedTrees = unpacked.map((tree) => {
|
|
44
|
-
const otherTrees = unpacked.filter((g) => g !== tree);
|
|
45
|
-
const scope = new Scope(...otherTrees, this);
|
|
46
|
-
// Each tree will be included first in its own scope.
|
|
47
|
-
return Scope.treeWithScope(tree, scope);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
40
|
// Merge the trees.
|
|
51
|
-
const result = merge(...
|
|
52
|
-
|
|
53
|
-
// Give the overall mixed tree a scope that includes the component trees and
|
|
54
|
-
// the current scope.
|
|
55
|
-
/** @type {any} */ (result).scope = new Scope(result, this);
|
|
56
|
-
|
|
41
|
+
const result = merge(...unpacked);
|
|
57
42
|
return result;
|
|
58
43
|
}
|
package/src/runtime/ops.js
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
SiteTree,
|
|
9
9
|
Tree,
|
|
10
10
|
isUnpackable,
|
|
11
|
+
scope as scopeFn,
|
|
11
12
|
concat as treeConcat,
|
|
12
13
|
} from "@weborigami/async-tree";
|
|
13
14
|
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
14
15
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
15
|
-
import Scope from "./Scope.js";
|
|
16
16
|
import handleExtension from "./handleExtension.js";
|
|
17
17
|
import { OrigamiTree, evaluate, expressionFunction } from "./internal.js";
|
|
18
18
|
import mergeTrees from "./mergeTrees.js";
|
|
@@ -72,12 +72,18 @@ function constructHref(protocol, host, ...keys) {
|
|
|
72
72
|
* @param {...any} keys
|
|
73
73
|
*/
|
|
74
74
|
export async function constructor(...keys) {
|
|
75
|
-
const
|
|
75
|
+
const tree = this;
|
|
76
|
+
const scope = scopeFn(tree);
|
|
76
77
|
let constructor = await Tree.traverseOrThrow(scope, ...keys);
|
|
77
78
|
if (isUnpackable(constructor)) {
|
|
78
79
|
constructor = await constructor.unpack();
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
// Origami may pass `undefined` as the first argument to the constructor. We
|
|
82
|
+
// don't pass that along, because constructors like `Date` don't like it.
|
|
83
|
+
return (...args) =>
|
|
84
|
+
args.length === 1 && args[0] === undefined
|
|
85
|
+
? new constructor()
|
|
86
|
+
: new constructor(...args);
|
|
81
87
|
}
|
|
82
88
|
constructor.toString = () => "«ops.constructor»";
|
|
83
89
|
|
|
@@ -97,8 +103,8 @@ async function fetchResponse(href) {
|
|
|
97
103
|
// Attach any loader defined for the file type.
|
|
98
104
|
const url = new URL(href);
|
|
99
105
|
const filename = url.pathname.split("/").pop();
|
|
100
|
-
if (filename) {
|
|
101
|
-
buffer = await handleExtension(this, filename, buffer
|
|
106
|
+
if (this && filename) {
|
|
107
|
+
buffer = await handleExtension(this, filename, buffer);
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
return buffer;
|
|
@@ -110,13 +116,12 @@ async function fetchResponse(href) {
|
|
|
110
116
|
* @this {AsyncTree|null}
|
|
111
117
|
*/
|
|
112
118
|
export async function filesRoot() {
|
|
113
|
-
/** @type {AsyncTree} */
|
|
114
119
|
let root = new OrigamiFiles("/");
|
|
115
120
|
|
|
116
|
-
// The root itself needs a
|
|
121
|
+
// The root itself needs a parent so that expressions evaluated within it
|
|
117
122
|
// (e.g., Origami expressions loaded from .ori files) will have access to
|
|
118
123
|
// things like the built-in functions.
|
|
119
|
-
root =
|
|
124
|
+
root.parent = this;
|
|
120
125
|
|
|
121
126
|
return root;
|
|
122
127
|
}
|
|
@@ -148,17 +153,18 @@ export function https(host, ...keys) {
|
|
|
148
153
|
https.toString = () => "«ops.https»";
|
|
149
154
|
|
|
150
155
|
/**
|
|
151
|
-
* Search the
|
|
152
|
-
*
|
|
156
|
+
* Search the parent's scope -- i.e., exclude the current tree -- for the given
|
|
157
|
+
* key.
|
|
153
158
|
*
|
|
154
159
|
* @this {AsyncTree|null}
|
|
155
160
|
* @param {*} key
|
|
156
161
|
*/
|
|
157
162
|
export async function inherited(key) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
if (!this?.parent) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const parentScope = scopeFn(this.parent);
|
|
167
|
+
return parentScope.get(key);
|
|
162
168
|
}
|
|
163
169
|
inherited.toString = () => "«ops.inherited»";
|
|
164
170
|
|
|
@@ -170,6 +176,7 @@ inherited.toString = () => "«ops.inherited»";
|
|
|
170
176
|
* @param {string[]} parameters
|
|
171
177
|
* @param {Code} code
|
|
172
178
|
*/
|
|
179
|
+
|
|
173
180
|
export function lambda(parameters, code) {
|
|
174
181
|
if (lambdaFnMap.has(code)) {
|
|
175
182
|
return lambdaFnMap.get(code);
|
|
@@ -186,15 +193,16 @@ export function lambda(parameters, code) {
|
|
|
186
193
|
ambients[parameter] = args.shift();
|
|
187
194
|
}
|
|
188
195
|
ambients["@recurse"] = invoke;
|
|
189
|
-
const
|
|
196
|
+
const ambientTree = new ObjectTree(ambients);
|
|
197
|
+
ambientTree.parent = this;
|
|
190
198
|
|
|
191
|
-
let result = await evaluate.call(
|
|
199
|
+
let result = await evaluate.call(ambientTree, code);
|
|
192
200
|
|
|
193
|
-
// Bind a function result to the
|
|
201
|
+
// Bind a function result to the ambients so that it has access to the
|
|
194
202
|
// parameter values -- i.e., like a closure.
|
|
195
203
|
if (result instanceof Function) {
|
|
196
204
|
const resultCode = result.code;
|
|
197
|
-
result = result.bind(
|
|
205
|
+
result = result.bind(ambientTree);
|
|
198
206
|
if (code) {
|
|
199
207
|
// Copy over Origami code
|
|
200
208
|
result.code = resultCode;
|
|
@@ -216,7 +224,7 @@ export function lambda(parameters, code) {
|
|
|
216
224
|
lambdaFnMap.set(code, invoke);
|
|
217
225
|
return invoke;
|
|
218
226
|
}
|
|
219
|
-
lambda.toString = () => "«ops.lambda
|
|
227
|
+
lambda.toString = () => "«ops.lambda";
|
|
220
228
|
|
|
221
229
|
/**
|
|
222
230
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
@@ -238,16 +246,30 @@ merge.toString = () => "«ops.merge»";
|
|
|
238
246
|
* @param {any[]} entries
|
|
239
247
|
*/
|
|
240
248
|
export async function object(...entries) {
|
|
241
|
-
const
|
|
249
|
+
const tree = this;
|
|
242
250
|
const promises = entries.map(async ([key, value]) => [
|
|
243
251
|
key,
|
|
244
|
-
await evaluate.call(
|
|
252
|
+
await evaluate.call(tree, value),
|
|
245
253
|
]);
|
|
246
254
|
const evaluated = await Promise.all(promises);
|
|
247
255
|
return Object.fromEntries(evaluated);
|
|
248
256
|
}
|
|
249
257
|
object.toString = () => "«ops.object»";
|
|
250
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Look up the given key in the scope for the current tree.
|
|
261
|
+
*
|
|
262
|
+
* @this {AsyncTree|null}
|
|
263
|
+
*/
|
|
264
|
+
export async function scope(key) {
|
|
265
|
+
if (!this) {
|
|
266
|
+
throw new Error("Tried to get the scope of a null or undefined tree.");
|
|
267
|
+
}
|
|
268
|
+
const scope = scopeFn(this);
|
|
269
|
+
return scope.get(key);
|
|
270
|
+
}
|
|
271
|
+
scope.toString = () => "«ops.scope»";
|
|
272
|
+
|
|
251
273
|
/**
|
|
252
274
|
* The spread operator is a placeholder during parsing. It should be replaced
|
|
253
275
|
* with an object merge.
|
|
@@ -282,7 +304,7 @@ export async function tree(...entries) {
|
|
|
282
304
|
});
|
|
283
305
|
const object = Object.fromEntries(fns);
|
|
284
306
|
const result = new OrigamiTree(object);
|
|
285
|
-
result.
|
|
307
|
+
result.parent = this;
|
|
286
308
|
return result;
|
|
287
309
|
}
|
|
288
310
|
tree.toString = () => "«ops.tree»";
|
|
@@ -296,9 +318,8 @@ tree.toString = () => "«ops.tree»";
|
|
|
296
318
|
*/
|
|
297
319
|
export function treeHttp(host, ...keys) {
|
|
298
320
|
const href = constructHref("http:", host, ...keys);
|
|
299
|
-
/** @type {AsyncTree} */
|
|
300
321
|
let result = new (HandleExtensionsTransform(SiteTree))(href);
|
|
301
|
-
result =
|
|
322
|
+
result.parent = this;
|
|
302
323
|
return result;
|
|
303
324
|
}
|
|
304
325
|
treeHttp.toString = () => "«ops.treeHttp»";
|
|
@@ -312,12 +333,8 @@ treeHttp.toString = () => "«ops.treeHttp»";
|
|
|
312
333
|
*/
|
|
313
334
|
export function treeHttps(host, ...keys) {
|
|
314
335
|
const href = constructHref("https:", host, ...keys);
|
|
315
|
-
/** @type {AsyncTree} */
|
|
316
336
|
let result = new (HandleExtensionsTransform(SiteTree))(href);
|
|
317
|
-
result =
|
|
337
|
+
result.parent = this;
|
|
318
338
|
return result;
|
|
319
339
|
}
|
|
320
340
|
treeHttps.toString = () => "«ops.treeHttps»";
|
|
321
|
-
|
|
322
|
-
// The scope op is a placeholder for the tree's scope.
|
|
323
|
-
export const scope = "«ops.scope»";
|
|
@@ -3,7 +3,7 @@ import assert from "node:assert";
|
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
import * as compile from "../../src/compiler/compile.js";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const shared = new ObjectTree({
|
|
7
7
|
greet: (name) => `Hello, ${name}!`,
|
|
8
8
|
name: "Alice",
|
|
9
9
|
});
|
|
@@ -24,7 +24,7 @@ describe("compile", () => {
|
|
|
24
24
|
test("tree", async () => {
|
|
25
25
|
const fn = compile.expression("{ message = greet(name) }");
|
|
26
26
|
const tree = await fn.call(null);
|
|
27
|
-
tree.
|
|
27
|
+
tree.parent = shared;
|
|
28
28
|
assert.deepEqual(await Tree.plain(tree), {
|
|
29
29
|
message: "Hello, Alice!",
|
|
30
30
|
});
|
|
@@ -43,7 +43,7 @@ describe("compile", () => {
|
|
|
43
43
|
|
|
44
44
|
test("templateDocument", async () => {
|
|
45
45
|
const fn = compile.templateDocument("Documents can contain ` backticks");
|
|
46
|
-
const templateFn = await fn.call(
|
|
46
|
+
const templateFn = await fn.call(shared);
|
|
47
47
|
const value = await templateFn.call(null);
|
|
48
48
|
assert.deepEqual(value, "Documents can contain ` backticks");
|
|
49
49
|
});
|
|
@@ -59,6 +59,6 @@ describe("compile", () => {
|
|
|
59
59
|
|
|
60
60
|
async function assertCompile(text, expected) {
|
|
61
61
|
const fn = compile.expression(text);
|
|
62
|
-
const result = await fn.call(
|
|
62
|
+
const result = await fn.call(shared);
|
|
63
63
|
assert.deepEqual(result, expected);
|
|
64
64
|
}
|
|
@@ -438,6 +438,7 @@ describe("Origami parser", () => {
|
|
|
438
438
|
assertParse("string", "'bar baz'", "bar baz");
|
|
439
439
|
assertParse("string", `"foo\\"s bar"`, `foo"s bar`);
|
|
440
440
|
assertParse("string", `'bar\\'s baz'`, `bar's baz`);
|
|
441
|
+
assertParse("string", `«string»`, "string");
|
|
441
442
|
});
|
|
442
443
|
|
|
443
444
|
test("templateDocument", () => {
|
|
@@ -2,7 +2,6 @@ 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 HandleExtensionsTransform from "../../src/runtime/HandleExtensionsTransform.js";
|
|
5
|
-
import Scope from "../../src/runtime/Scope.js";
|
|
6
5
|
|
|
7
6
|
describe("HandleExtensionsTransform", () => {
|
|
8
7
|
test("invokes an appropriate loader for a .json file extension", async () => {
|
|
@@ -25,17 +24,17 @@ describe("HandleExtensionsTransform", () => {
|
|
|
25
24
|
});
|
|
26
25
|
|
|
27
26
|
function createFixture() {
|
|
28
|
-
/** @type {import("@weborigami/types").AsyncTree} */
|
|
29
|
-
let tree = new (HandleExtensionsTransform(ObjectTree))({
|
|
30
|
-
foo: 1, // No extension, should be left alone
|
|
31
|
-
"bar.json": `{ "bar": 2 }`,
|
|
32
|
-
});
|
|
33
27
|
/** @type {any} */
|
|
34
|
-
const
|
|
28
|
+
const parent = new ObjectTree({
|
|
35
29
|
json_handler: {
|
|
36
30
|
unpack: (buffer) => JSON.parse(String(buffer)),
|
|
37
31
|
},
|
|
38
32
|
});
|
|
39
|
-
|
|
33
|
+
/** @type {import("@weborigami/types").AsyncTree} */
|
|
34
|
+
let tree = new (HandleExtensionsTransform(ObjectTree))({
|
|
35
|
+
foo: 1, // No extension, should be left alone
|
|
36
|
+
"bar.json": `{ "bar": 2 }`,
|
|
37
|
+
});
|
|
38
|
+
tree.parent = parent;
|
|
40
39
|
return tree;
|
|
41
40
|
}
|
|
@@ -8,10 +8,12 @@ import evaluate from "../../src/runtime/evaluate.js";
|
|
|
8
8
|
describe("evaluate", () => {
|
|
9
9
|
test("can retrieve values from scope", async () => {
|
|
10
10
|
const code = [ops.scope, "message"];
|
|
11
|
-
const
|
|
11
|
+
const parent = new ObjectTree({
|
|
12
12
|
message: "Hello",
|
|
13
|
-
};
|
|
14
|
-
const
|
|
13
|
+
});
|
|
14
|
+
const tree = new ObjectTree({});
|
|
15
|
+
tree.parent = parent;
|
|
16
|
+
const result = await evaluate.call(tree, code);
|
|
15
17
|
assert.equal(result, "Hello");
|
|
16
18
|
});
|
|
17
19
|
|
|
@@ -22,25 +24,25 @@ describe("evaluate", () => {
|
|
|
22
24
|
[ops.scope, "name"],
|
|
23
25
|
];
|
|
24
26
|
|
|
25
|
-
const
|
|
27
|
+
const tree = new ObjectTree({
|
|
26
28
|
async greet(name) {
|
|
27
29
|
return `Hello ${name}`;
|
|
28
30
|
},
|
|
29
31
|
name: "world",
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
const result = await evaluate.call(
|
|
34
|
+
const result = await evaluate.call(tree, code);
|
|
33
35
|
assert.equal(result, "Hello world");
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
test("passes context to invoked functions", async () => {
|
|
37
39
|
const code = [ops.scope, "fn"];
|
|
38
|
-
const
|
|
40
|
+
const tree = new ObjectTree({
|
|
39
41
|
async fn() {
|
|
40
|
-
assert.equal(this,
|
|
42
|
+
assert.equal(this, tree);
|
|
41
43
|
},
|
|
42
|
-
};
|
|
43
|
-
await evaluate.call(
|
|
44
|
+
});
|
|
45
|
+
await evaluate.call(tree, code);
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
test("evaluates a function with fixed number of arguments", async () => {
|
|
@@ -59,4 +61,12 @@ describe("evaluate", () => {
|
|
|
59
61
|
const result = await evaluate.call(null, code);
|
|
60
62
|
assert.equal(result, "a,b,c");
|
|
61
63
|
});
|
|
64
|
+
|
|
65
|
+
test("by defalut sets the parent of a returned tree to the current tree", async () => {
|
|
66
|
+
const fn = () => new ObjectTree({});
|
|
67
|
+
const code = [fn];
|
|
68
|
+
const tree = new ObjectTree({});
|
|
69
|
+
const result = await evaluate.call(tree, code);
|
|
70
|
+
assert.equal(result.parent, tree);
|
|
71
|
+
});
|
|
62
72
|
});
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
1
|
+
import { ObjectTree, Tree, scope } 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
|
import functionResultsMap from "../../src/runtime/functionResultsMap.js";
|
|
6
5
|
|
|
7
6
|
describe("functionResultsMap", () => {
|
|
8
7
|
test("get() invokes functions using scope, returns other values as is", async () => {
|
|
9
|
-
const
|
|
8
|
+
const parent = new ObjectTree({
|
|
10
9
|
message: "Hello",
|
|
11
|
-
};
|
|
12
|
-
const tree =
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
return this.get("message");
|
|
16
|
-
},
|
|
17
|
-
string: "string",
|
|
10
|
+
});
|
|
11
|
+
const tree = new ObjectTree({
|
|
12
|
+
fn: /** @this {import("@weborigami/types").AsyncTree} */ function () {
|
|
13
|
+
return scope(this).get("message");
|
|
18
14
|
},
|
|
19
|
-
|
|
20
|
-
);
|
|
15
|
+
string: "string",
|
|
16
|
+
});
|
|
17
|
+
tree.parent = parent;
|
|
21
18
|
const fixture = functionResultsMap(tree);
|
|
22
19
|
assert.deepEqual(await Tree.plain(fixture), {
|
|
23
20
|
fn: "Hello",
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
|
-
import {
|
|
5
|
-
ExpressionTree,
|
|
6
|
-
expressionFunction,
|
|
7
|
-
ops,
|
|
8
|
-
} from "../../src/runtime/internal.js";
|
|
9
4
|
import mergeTrees from "../../src/runtime/mergeTrees.js";
|
|
10
5
|
|
|
11
6
|
describe("mergeTrees", () => {
|
|
@@ -29,27 +24,6 @@ describe("mergeTrees", () => {
|
|
|
29
24
|
});
|
|
30
25
|
});
|
|
31
26
|
|
|
32
|
-
test("puts all trees in scope", async () => {
|
|
33
|
-
const tree = await mergeTrees.call(
|
|
34
|
-
null,
|
|
35
|
-
new ExpressionTree({
|
|
36
|
-
a: 1,
|
|
37
|
-
b: expressionFunction.createExpressionFunction([ops.scope, "c"]),
|
|
38
|
-
}),
|
|
39
|
-
new ExpressionTree({
|
|
40
|
-
c: 2,
|
|
41
|
-
d: expressionFunction.createExpressionFunction([ops.scope, "a"]),
|
|
42
|
-
})
|
|
43
|
-
);
|
|
44
|
-
// @ts-ignore
|
|
45
|
-
assert.deepEqual(await Tree.plain(tree), {
|
|
46
|
-
a: 1,
|
|
47
|
-
b: 2,
|
|
48
|
-
c: 2,
|
|
49
|
-
d: 1,
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
27
|
test("if all arguments are plain objects, result is a plain object", async () => {
|
|
54
28
|
const result = await mergeTrees.call(
|
|
55
29
|
null,
|