@weborigami/language 0.5.5 → 0.5.6
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/index.ts +16 -6
- package/main.js +9 -4
- package/package.json +4 -3
- package/src/compiler/compile.js +10 -4
- package/src/compiler/optimize.js +115 -97
- package/src/compiler/origami.pegjs +1 -4
- package/src/compiler/parse.js +568 -588
- package/src/compiler/parserHelpers.js +2 -2
- package/src/handlers/css_handler.js +7 -0
- package/src/handlers/csv_handler.js +129 -0
- package/src/handlers/handlers.js +33 -0
- package/src/handlers/htm_handler.js +2 -0
- package/src/handlers/html_handler.js +7 -0
- package/src/handlers/jpeg_handler.js +62 -0
- package/src/handlers/jpg_handler.js +2 -0
- package/src/handlers/js_handler.js +51 -0
- package/src/handlers/json_handler.js +26 -0
- package/src/handlers/md_handler.js +7 -0
- package/src/handlers/mjs_handler.js +2 -0
- package/src/handlers/ori_handler.js +47 -0
- package/src/handlers/oridocument_handler.js +77 -0
- package/src/handlers/parseFrontMatter.js +16 -0
- package/src/handlers/ts_handler.js +1 -0
- package/src/handlers/txt_handler.js +108 -0
- package/src/handlers/wasm_handler.js +15 -0
- package/src/handlers/xhtml_handler.js +2 -0
- package/src/handlers/yaml_handler.js +33 -0
- package/src/handlers/yml_handler.js +2 -0
- package/src/project/builtins.js +5 -0
- package/src/project/coreGlobals.js +17 -0
- package/src/{runtime → project}/jsGlobals.js +3 -1
- package/src/project/projectConfig.js +36 -0
- package/src/project/projectGlobals.js +19 -0
- package/src/project/projectRoot.js +59 -0
- package/src/protocols/constructHref.js +20 -0
- package/src/protocols/constructSiteTree.js +26 -0
- package/src/protocols/explore.js +14 -0
- package/src/protocols/fetchAndHandleExtension.js +25 -0
- package/src/protocols/files.js +26 -0
- package/src/protocols/http.js +15 -0
- package/src/protocols/https.js +15 -0
- package/src/protocols/httpstree.js +14 -0
- package/src/protocols/httptree.js +14 -0
- package/src/protocols/node.js +13 -0
- package/src/protocols/package.js +67 -0
- package/src/protocols/protocolGlobals.js +12 -0
- package/src/protocols/protocols.js +8 -0
- package/src/runtime/EventTargetMixin.js +1 -1
- package/src/runtime/HandleExtensionsTransform.js +3 -12
- package/src/runtime/ImportModulesMixin.js +4 -10
- package/src/runtime/InvokeFunctionsTransform.js +1 -1
- package/src/runtime/evaluate.js +15 -8
- package/src/runtime/expressionFunction.js +5 -7
- package/src/runtime/expressionObject.js +10 -20
- package/src/runtime/functionResultsMap.js +1 -3
- package/src/runtime/{handlers.js → handleExtension.js} +13 -11
- package/src/runtime/mergeTrees.js +1 -8
- package/src/runtime/ops.js +83 -90
- package/test/compiler/compile.test.js +20 -19
- package/test/compiler/optimize.test.js +60 -25
- package/test/compiler/parse.test.js +4 -4
- package/test/generator/oriEval.js +4 -5
- package/test/handlers/csv.handler.test.js +36 -0
- package/test/handlers/fixtures/add.wasm +0 -0
- package/test/handlers/fixtures/exif.jpeg +0 -0
- package/test/handlers/fixtures/frontMatter.md +5 -0
- package/test/handlers/fixtures/list.js +4 -0
- package/test/handlers/fixtures/multiple.js +4 -0
- package/test/handlers/fixtures/obj.js +3 -0
- package/test/handlers/fixtures/site.ori +5 -0
- package/test/handlers/fixtures/string.js +1 -0
- package/test/handlers/fixtures/tag.yaml +5 -0
- package/test/handlers/fixtures/test.ori +9 -0
- package/test/handlers/jpeg.handler.test.js +18 -0
- package/test/handlers/js.handler.test.js +46 -0
- package/test/handlers/json.handler.test.js +14 -0
- package/test/handlers/ori.handler.test.js +87 -0
- package/test/handlers/oridocument.handler.test.js +68 -0
- package/test/handlers/txt.handler.test.js +41 -0
- package/test/handlers/wasm.handler.test.js +20 -0
- package/test/handlers/yaml.handler.test.js +17 -0
- package/test/project/fixtures/withConfig/config.ori +4 -0
- package/test/project/fixtures/withConfig/subfolder/greet.js +1 -0
- package/test/project/fixtures/withPackageJson/package.json +0 -0
- package/test/project/jsGlobals.test.js +21 -0
- package/test/project/projectConfig.test.js +28 -0
- package/test/project/projectRoot.test.js +40 -0
- package/test/protocols/package.test.js +11 -0
- package/test/runtime/evaluate.test.js +26 -42
- package/test/runtime/expressionObject.test.js +16 -20
- package/test/runtime/{handlers.test.js → handleExtension.test.js} +4 -20
- package/test/runtime/jsGlobals.test.js +4 -6
- package/test/runtime/mergeTrees.test.js +2 -4
- package/test/runtime/ops.test.js +66 -68
- package/src/runtime/getHandlers.js +0 -10
|
@@ -7,23 +7,22 @@ import {
|
|
|
7
7
|
setParent,
|
|
8
8
|
trailingSlash,
|
|
9
9
|
} from "@weborigami/async-tree";
|
|
10
|
+
import globals from "../project/projectGlobals.js";
|
|
11
|
+
|
|
12
|
+
let projectGlobals;
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* If the given value is packed (e.g., buffer) and the key is a string-like path
|
|
13
16
|
* that ends in an extension, search for a handler for that extension and, if
|
|
14
17
|
* found, attach it to the value.
|
|
15
18
|
*
|
|
16
|
-
* @param {import("@weborigami/types").AsyncTree} parent
|
|
17
19
|
* @param {any} value
|
|
18
20
|
* @param {any} key
|
|
21
|
+
* @param {import("@weborigami/types").AsyncTree} [parent]
|
|
19
22
|
*/
|
|
20
|
-
export async function handleExtension(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
isPacked(value) &&
|
|
24
|
-
isStringlike(key) &&
|
|
25
|
-
value.unpack === undefined
|
|
26
|
-
) {
|
|
23
|
+
export default async function handleExtension(value, key, parent) {
|
|
24
|
+
projectGlobals ??= await globals();
|
|
25
|
+
if (isPacked(value) && isStringlike(key) && value.unpack === undefined) {
|
|
27
26
|
const hasSlash = trailingSlash.has(key);
|
|
28
27
|
if (hasSlash) {
|
|
29
28
|
key = trailingSlash.remove(key);
|
|
@@ -38,8 +37,8 @@ export async function handleExtension(parent, value, key, handlers) {
|
|
|
38
37
|
? ".jsedocument"
|
|
39
38
|
: extension.extname(key);
|
|
40
39
|
if (extname) {
|
|
41
|
-
const handlerName = `${extname.slice(1)}
|
|
42
|
-
let handler = await
|
|
40
|
+
const handlerName = `${extname.slice(1)}_handler`;
|
|
41
|
+
let handler = await projectGlobals[handlerName];
|
|
43
42
|
if (handler) {
|
|
44
43
|
if (isUnpackable(handler)) {
|
|
45
44
|
// The extension handler itself needs to be unpacked
|
|
@@ -57,7 +56,10 @@ export async function handleExtension(parent, value, key, handlers) {
|
|
|
57
56
|
if (handler.mediaType) {
|
|
58
57
|
value.mediaType = handler.mediaType;
|
|
59
58
|
}
|
|
60
|
-
|
|
59
|
+
|
|
60
|
+
if (parent) {
|
|
61
|
+
setParent(value, parent);
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
const unpack = handler.unpack;
|
|
63
65
|
if (unpack) {
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isPlainObject,
|
|
3
|
-
isUnpackable,
|
|
4
|
-
setParent,
|
|
5
|
-
Tree,
|
|
6
|
-
} from "@weborigami/async-tree";
|
|
1
|
+
import { isPlainObject, isUnpackable, Tree } from "@weborigami/async-tree";
|
|
7
2
|
|
|
8
3
|
/**
|
|
9
4
|
* Create a tree that's the result of merging the given trees.
|
|
@@ -11,7 +6,6 @@ import {
|
|
|
11
6
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
12
7
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
13
8
|
*
|
|
14
|
-
* @this {AsyncTree|null}
|
|
15
9
|
* @param {(Treelike|null)[]} trees
|
|
16
10
|
*/
|
|
17
11
|
export default async function mergeTrees(...trees) {
|
|
@@ -54,6 +48,5 @@ export default async function mergeTrees(...trees) {
|
|
|
54
48
|
|
|
55
49
|
// Merge the trees.
|
|
56
50
|
const result = Tree.merge(...unpacked);
|
|
57
|
-
setParent(result, this);
|
|
58
51
|
return result;
|
|
59
52
|
}
|
package/src/runtime/ops.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
|
4
4
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
5
5
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
6
|
+
* @typedef {import("../../index.ts").RuntimeState} RuntimeState
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { isUnpackable,
|
|
9
|
+
import { isUnpackable, symbols, Tree } from "@weborigami/async-tree";
|
|
9
10
|
import os from "node:os";
|
|
10
11
|
import expressionObject from "./expressionObject.js";
|
|
11
|
-
import getHandlers from "./getHandlers.js";
|
|
12
12
|
import { evaluate } from "./internal.js";
|
|
13
13
|
import mergeTrees from "./mergeTrees.js";
|
|
14
14
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
@@ -29,7 +29,6 @@ addOpLabel(addition, "«ops.addition»");
|
|
|
29
29
|
/**
|
|
30
30
|
* Construct an array.
|
|
31
31
|
*
|
|
32
|
-
* @this {AsyncTree|null}
|
|
33
32
|
* @param {any[]} items
|
|
34
33
|
*/
|
|
35
34
|
export async function array(...items) {
|
|
@@ -60,7 +59,6 @@ addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
|
|
60
59
|
/**
|
|
61
60
|
* Cache the value of the code for an external reference
|
|
62
61
|
*
|
|
63
|
-
* @this {AsyncTree|null}
|
|
64
62
|
* @param {any} cache
|
|
65
63
|
* @param {string} path
|
|
66
64
|
* @param {AnnotatedCode} code
|
|
@@ -72,7 +70,7 @@ export async function cache(cache, path, code) {
|
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
// Don't await: might get another request for this before promise resolves
|
|
75
|
-
const promise = await evaluate
|
|
73
|
+
const promise = await evaluate(code);
|
|
76
74
|
|
|
77
75
|
// Save promise so another request will get the same promise
|
|
78
76
|
cache[path] = promise;
|
|
@@ -91,13 +89,12 @@ cache.unevaluatedArgs = true;
|
|
|
91
89
|
/**
|
|
92
90
|
* JavaScript comma operator, returns the last argument.
|
|
93
91
|
*
|
|
94
|
-
* @this {AsyncTree|null}
|
|
95
92
|
* @param {...AnnotatedCode} args
|
|
96
93
|
*/
|
|
97
94
|
export async function comma(...args) {
|
|
98
95
|
let result;
|
|
99
96
|
for (const arg of args) {
|
|
100
|
-
result = await evaluate
|
|
97
|
+
result = await evaluate(arg);
|
|
101
98
|
}
|
|
102
99
|
return result;
|
|
103
100
|
}
|
|
@@ -107,11 +104,10 @@ comma.unevaluatedArgs = true;
|
|
|
107
104
|
/**
|
|
108
105
|
* Concatenate the given arguments.
|
|
109
106
|
*
|
|
110
|
-
* @this {AsyncTree|null}
|
|
111
107
|
* @param {any[]} args
|
|
112
108
|
*/
|
|
113
109
|
export async function concat(...args) {
|
|
114
|
-
return Tree.deepText
|
|
110
|
+
return Tree.deepText(args);
|
|
115
111
|
}
|
|
116
112
|
addOpLabel(concat, "«ops.concat»");
|
|
117
113
|
|
|
@@ -127,23 +123,6 @@ export async function construct(constructor, ...args) {
|
|
|
127
123
|
return Reflect.construct(constructor, args);
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
/**
|
|
131
|
-
* Return the nth parent of the current tree
|
|
132
|
-
*
|
|
133
|
-
* @this {AsyncTree|null|undefined}
|
|
134
|
-
*/
|
|
135
|
-
export function context(n = 0) {
|
|
136
|
-
let tree = this;
|
|
137
|
-
for (let i = 0; i < n; i++) {
|
|
138
|
-
if (!tree) {
|
|
139
|
-
throw new Error("Internal error: couldn't find tree ancestor.");
|
|
140
|
-
}
|
|
141
|
-
tree = tree.parent;
|
|
142
|
-
}
|
|
143
|
-
return tree;
|
|
144
|
-
}
|
|
145
|
-
addOpLabel(context, "«ops.context»");
|
|
146
|
-
|
|
147
126
|
export function division(a, b) {
|
|
148
127
|
return a / b;
|
|
149
128
|
}
|
|
@@ -195,65 +174,74 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
|
|
195
174
|
|
|
196
175
|
/**
|
|
197
176
|
* Files tree for the user's home directory.
|
|
198
|
-
*
|
|
199
|
-
* @this {AsyncTree|null}
|
|
200
177
|
*/
|
|
201
178
|
export async function homeDirectory(...keys) {
|
|
202
179
|
const tree = new OrigamiFiles(os.homedir());
|
|
203
|
-
// Use the same handlers as the current tree
|
|
204
|
-
/** @type {any} */ (tree).handlers = getHandlers(this);
|
|
205
180
|
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
206
181
|
}
|
|
207
182
|
addOpLabel(homeDirectory, "«ops.homeDirectory»");
|
|
208
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Given the tree currently be using as the context for the runtime, walk up the
|
|
186
|
+
* parent chain `depth` levels and return that tree.
|
|
187
|
+
*
|
|
188
|
+
* @param {number} depth
|
|
189
|
+
* @param {RuntimeState} state
|
|
190
|
+
*/
|
|
191
|
+
export async function inherited(depth, state) {
|
|
192
|
+
let current = state.object;
|
|
193
|
+
for (let i = 0; i < depth; i++) {
|
|
194
|
+
if (!current) {
|
|
195
|
+
throw new ReferenceError(
|
|
196
|
+
`Origami internal error: Can't find context object`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
current = current.parent ?? current[symbols.parent];
|
|
200
|
+
}
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
addOpLabel(inherited, "«ops.inherited»");
|
|
204
|
+
inherited.needsState = true;
|
|
205
|
+
|
|
209
206
|
/**
|
|
210
207
|
* Return a function that will invoke the given code.
|
|
211
208
|
*
|
|
212
|
-
* @this {AsyncTree|null}
|
|
213
209
|
* @param {string[]} parameters
|
|
214
210
|
* @param {AnnotatedCode} code
|
|
215
211
|
*/
|
|
216
|
-
export function lambda(parameters, code) {
|
|
217
|
-
const
|
|
212
|
+
export function lambda(parameters, code, state = {}) {
|
|
213
|
+
const stack = state.stack ?? [];
|
|
218
214
|
|
|
219
|
-
/** @this {Treelike|null} */
|
|
220
215
|
async function invoke(...args) {
|
|
221
|
-
let
|
|
216
|
+
let newState;
|
|
222
217
|
if (parameters.length === 0) {
|
|
223
218
|
// No parameters
|
|
224
|
-
|
|
219
|
+
newState = state;
|
|
225
220
|
} else {
|
|
226
|
-
//
|
|
227
|
-
const
|
|
221
|
+
// Create a stack frame for the parameters
|
|
222
|
+
const frame = {};
|
|
228
223
|
for (const parameter of parameters) {
|
|
229
224
|
const parameterName = parameter[1];
|
|
230
|
-
|
|
225
|
+
frame[parameterName] = args.shift();
|
|
231
226
|
}
|
|
232
|
-
|
|
227
|
+
// Record which code this stack frame is associated with
|
|
228
|
+
Object.defineProperty(frame, codeSymbol, {
|
|
233
229
|
value: code,
|
|
234
230
|
enumerable: false,
|
|
235
231
|
});
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
let result = await evaluate.call(target, code);
|
|
242
|
-
|
|
243
|
-
// Bind a function result to the ambients so that it has access to the
|
|
244
|
-
// parameter values -- i.e., like a closure.
|
|
245
|
-
if (result instanceof Function) {
|
|
246
|
-
const resultCode = result.code;
|
|
247
|
-
result = result.bind(target);
|
|
248
|
-
if (code) {
|
|
249
|
-
// Copy over Origami code
|
|
250
|
-
result.code = resultCode;
|
|
251
|
-
}
|
|
232
|
+
const newStack = stack.slice();
|
|
233
|
+
newStack.push(frame);
|
|
234
|
+
newState = Object.assign({}, state, { stack: newStack });
|
|
252
235
|
}
|
|
253
236
|
|
|
237
|
+
const result = await evaluate(code, newState);
|
|
254
238
|
return result;
|
|
255
239
|
}
|
|
256
240
|
|
|
241
|
+
// Retain a reference to the original code for debugging, and so that functions
|
|
242
|
+
// can be compared by their code (e.g., for caching purposes).
|
|
243
|
+
invoke.code = code;
|
|
244
|
+
|
|
257
245
|
// We set the `length` property on the function so that Tree.traverseOrThrow()
|
|
258
246
|
// will correctly identify how many parameters it wants. This is unorthodox
|
|
259
247
|
// but doesn't appear to affect other behavior.
|
|
@@ -262,11 +250,11 @@ export function lambda(parameters, code) {
|
|
|
262
250
|
value: fnLength,
|
|
263
251
|
});
|
|
264
252
|
|
|
265
|
-
invoke.code = code;
|
|
266
253
|
return invoke;
|
|
267
254
|
}
|
|
268
255
|
addOpLabel(lambda, "«ops.lambda»");
|
|
269
256
|
lambda.unevaluatedArgs = true;
|
|
257
|
+
lambda.needsState = true;
|
|
270
258
|
|
|
271
259
|
export function lessThan(a, b) {
|
|
272
260
|
return a < b;
|
|
@@ -348,11 +336,10 @@ addOpLabel(logicalOr, "«ops.logicalOr»");
|
|
|
348
336
|
/**
|
|
349
337
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
350
338
|
*
|
|
351
|
-
* @this {AsyncTree|null}
|
|
352
339
|
* @param {any[]} trees
|
|
353
340
|
*/
|
|
354
341
|
export async function merge(...trees) {
|
|
355
|
-
return mergeTrees
|
|
342
|
+
return mergeTrees(...trees);
|
|
356
343
|
}
|
|
357
344
|
addOpLabel(merge, "«ops.merge»");
|
|
358
345
|
|
|
@@ -396,14 +383,28 @@ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
|
|
|
396
383
|
* parameter's, and the values will be the results of evaluating the
|
|
397
384
|
* corresponding code values in `obj`.
|
|
398
385
|
*
|
|
399
|
-
* @this {AsyncTree|null}
|
|
400
386
|
* @param {any[]} entries
|
|
401
387
|
*/
|
|
402
388
|
export async function object(...entries) {
|
|
403
|
-
|
|
389
|
+
const state = entries.pop();
|
|
390
|
+
return expressionObject(entries, state);
|
|
404
391
|
}
|
|
405
392
|
addOpLabel(object, "«ops.object»");
|
|
406
393
|
object.unevaluatedArgs = true;
|
|
394
|
+
object.needsState = true;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Return the stack frame that's `depth` levels up the stack.
|
|
398
|
+
*
|
|
399
|
+
* @param {number} depth
|
|
400
|
+
* @param {RuntimeState} state
|
|
401
|
+
*/
|
|
402
|
+
export async function params(depth, state = {}) {
|
|
403
|
+
const stack = state.stack ?? [];
|
|
404
|
+
return stack[stack.length - 1 - depth];
|
|
405
|
+
}
|
|
406
|
+
addOpLabel(params, "«ops.params»");
|
|
407
|
+
params.needsState = true;
|
|
407
408
|
|
|
408
409
|
// export function optionalTraverse(treelike, key) {
|
|
409
410
|
// if (!treelike) {
|
|
@@ -416,39 +417,37 @@ object.unevaluatedArgs = true;
|
|
|
416
417
|
/**
|
|
417
418
|
* Return the indicated property
|
|
418
419
|
*
|
|
419
|
-
* @param {any}
|
|
420
|
+
* @param {any} object
|
|
420
421
|
* @param {string} key
|
|
421
422
|
*/
|
|
422
|
-
export async function property(
|
|
423
|
-
if (
|
|
423
|
+
export async function property(object, key) {
|
|
424
|
+
if (object == null) {
|
|
424
425
|
throw new ReferenceError();
|
|
425
426
|
}
|
|
426
427
|
|
|
427
|
-
if (isUnpackable(
|
|
428
|
-
|
|
429
|
-
} else if (typeof
|
|
430
|
-
|
|
431
|
-
} else if (typeof
|
|
432
|
-
|
|
428
|
+
if (isUnpackable(object)) {
|
|
429
|
+
object = await object.unpack();
|
|
430
|
+
} else if (typeof object === "string") {
|
|
431
|
+
object = new String(object);
|
|
432
|
+
} else if (typeof object === "number") {
|
|
433
|
+
object = new Number(object);
|
|
433
434
|
}
|
|
434
435
|
|
|
435
|
-
if (key in
|
|
436
|
+
if (key in object) {
|
|
436
437
|
// Object defines the property, get it
|
|
437
|
-
let value =
|
|
438
|
+
let value = object[key];
|
|
438
439
|
// Is value an instance method? Copied from ObjectTree.
|
|
439
440
|
const isInstanceMethod =
|
|
440
|
-
|
|
441
|
-
value instanceof Function &&
|
|
442
|
-
!Object.hasOwn(obj, key);
|
|
441
|
+
value instanceof Function && !Object.hasOwn(object, key);
|
|
443
442
|
if (isInstanceMethod) {
|
|
444
443
|
// Bind it to the object
|
|
445
|
-
value = value.bind(
|
|
444
|
+
value = value.bind(object);
|
|
446
445
|
}
|
|
447
446
|
return value;
|
|
448
447
|
}
|
|
449
448
|
|
|
450
449
|
// Handle as tree traversal
|
|
451
|
-
return Tree.traverseOrThrow(
|
|
450
|
+
return Tree.traverseOrThrow(object, key);
|
|
452
451
|
}
|
|
453
452
|
addOpLabel(property, "«ops.property»");
|
|
454
453
|
|
|
@@ -459,13 +458,9 @@ addOpLabel(remainder, "«ops.remainder»");
|
|
|
459
458
|
|
|
460
459
|
/**
|
|
461
460
|
* Files tree for the filesystem root.
|
|
462
|
-
*
|
|
463
|
-
* @this {AsyncTree|null}
|
|
464
461
|
*/
|
|
465
462
|
export async function rootDirectory(...keys) {
|
|
466
463
|
const tree = new OrigamiFiles("/");
|
|
467
|
-
// Use the same handlers as the current tree
|
|
468
|
-
/** @type {any} */ (tree).handlers = getHandlers(this);
|
|
469
464
|
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
470
465
|
}
|
|
471
466
|
addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
@@ -473,17 +468,15 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
|
473
468
|
/**
|
|
474
469
|
* Return the scope of the current tree
|
|
475
470
|
*
|
|
476
|
-
* @
|
|
477
|
-
* @param {AsyncTree|null} [context]
|
|
471
|
+
* @param {AsyncTree} parent
|
|
478
472
|
*/
|
|
479
|
-
export async function scope(
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return null;
|
|
473
|
+
export async function scope(parent) {
|
|
474
|
+
if (!parent) {
|
|
475
|
+
throw new ReferenceError(
|
|
476
|
+
"Tried to find a value in scope, but no container was provided as the parent."
|
|
477
|
+
);
|
|
485
478
|
}
|
|
486
|
-
return Tree.scope(
|
|
479
|
+
return Tree.scope(parent);
|
|
487
480
|
}
|
|
488
481
|
addOpLabel(scope, "«ops.scope»");
|
|
489
482
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { 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
5
|
import { assertCodeEqual } from "./codeHelpers.js";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const globals = {
|
|
8
8
|
greet: (name) => `Hello, ${name}!`,
|
|
9
9
|
name: "Alice",
|
|
10
10
|
};
|
|
@@ -43,21 +43,24 @@ describe("compile", () => {
|
|
|
43
43
|
);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test
|
|
46
|
+
test("merge", async () => {
|
|
47
47
|
{
|
|
48
|
-
const
|
|
48
|
+
const globals = {
|
|
49
49
|
more: {
|
|
50
50
|
b: 2,
|
|
51
51
|
},
|
|
52
|
-
}
|
|
53
|
-
const fn = compile.expression(
|
|
52
|
+
};
|
|
53
|
+
const fn = compile.expression(
|
|
54
|
+
`
|
|
54
55
|
{
|
|
55
56
|
a: 1
|
|
56
57
|
...more
|
|
57
58
|
c: a
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
`,
|
|
61
|
+
{ globals }
|
|
62
|
+
);
|
|
63
|
+
const result = await fn();
|
|
61
64
|
assert.deepEqual(await Tree.plain(result), {
|
|
62
65
|
a: 1,
|
|
63
66
|
b: 2,
|
|
@@ -78,10 +81,8 @@ describe("compile", () => {
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
test("async object", async () => {
|
|
81
|
-
const fn = compile.expression("{ a: { b = name }}", {
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
const object = await fn.call(null);
|
|
84
|
+
const fn = compile.expression("{ a: { b = name }}", { globals });
|
|
85
|
+
const object = await fn();
|
|
85
86
|
assert.deepEqual(await object.a.b, "Alice");
|
|
86
87
|
});
|
|
87
88
|
|
|
@@ -89,8 +90,8 @@ describe("compile", () => {
|
|
|
89
90
|
const defineTemplateFn = compile.templateDocument(
|
|
90
91
|
"Documents can contain ` backticks"
|
|
91
92
|
);
|
|
92
|
-
const templateFn = await defineTemplateFn
|
|
93
|
-
const value = await templateFn
|
|
93
|
+
const templateFn = await defineTemplateFn();
|
|
94
|
+
const value = await templateFn();
|
|
94
95
|
assert.deepEqual(value, "Documents can contain ` backticks");
|
|
95
96
|
});
|
|
96
97
|
|
|
@@ -115,8 +116,8 @@ describe("compile", () => {
|
|
|
115
116
|
return strings[0] + values[0] + strings[1];
|
|
116
117
|
},
|
|
117
118
|
};
|
|
118
|
-
const program = compile.expression("
|
|
119
|
-
const lambda = await program
|
|
119
|
+
const program = compile.expression("(_) => tag`Hello, ${_}!`", { globals });
|
|
120
|
+
const lambda = await program();
|
|
120
121
|
const alice = await lambda("Alice");
|
|
121
122
|
assert.equal(alice, "Hello, Alice!");
|
|
122
123
|
const bob = await lambda("Bob");
|
|
@@ -126,9 +127,9 @@ describe("compile", () => {
|
|
|
126
127
|
|
|
127
128
|
async function assertCompile(text, expected, options = {}) {
|
|
128
129
|
const mode = options.mode ?? "program";
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
let result = await fn
|
|
130
|
+
const parent = options.target ?? null;
|
|
131
|
+
const fn = compile.expression(text, { globals, mode, parent });
|
|
132
|
+
let result = await fn();
|
|
132
133
|
if (Tree.isTreelike(result)) {
|
|
133
134
|
result = await Tree.plain(result);
|
|
134
135
|
}
|