@weborigami/language 0.3.3 → 0.3.4-jse.5
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 +3 -1
- package/package.json +3 -3
- package/src/compiler/compile.js +13 -3
- package/src/compiler/isOrigamiFrontMatter.js +4 -3
- package/src/compiler/optimize.js +273 -106
- package/src/compiler/origami.pegjs +286 -169
- package/src/compiler/parse.js +2069 -1275
- package/src/compiler/parserHelpers.js +154 -145
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +17 -11
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +18 -54
- package/src/runtime/jsGlobals.js +106 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +92 -161
- package/src/runtime/symbols.js +1 -0
- package/src/runtime/{taggedTemplateIndent.js → templateIndent.js} +2 -2
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +60 -30
- package/test/compiler/optimize.test.js +263 -24
- package/test/compiler/parse.test.js +895 -521
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +6 -5
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +103 -82
- package/test/runtime/taggedTemplateIndent.test.js +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The complete set of support JavaScript globals and global-like values.
|
|
5
|
+
*
|
|
6
|
+
* See
|
|
7
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects.
|
|
8
|
+
* That page lists some things like `TypedArrays` which are not globals so are
|
|
9
|
+
* omitted here.
|
|
10
|
+
*
|
|
11
|
+
* Also includes
|
|
12
|
+
* Fetch API
|
|
13
|
+
* URL API
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
AggregateError,
|
|
17
|
+
Array,
|
|
18
|
+
ArrayBuffer,
|
|
19
|
+
Atomics,
|
|
20
|
+
BigInt,
|
|
21
|
+
BigInt64Array,
|
|
22
|
+
BigUint64Array,
|
|
23
|
+
Boolean,
|
|
24
|
+
DataView,
|
|
25
|
+
Date,
|
|
26
|
+
Error,
|
|
27
|
+
EvalError,
|
|
28
|
+
FinalizationRegistry,
|
|
29
|
+
Float32Array,
|
|
30
|
+
Float64Array,
|
|
31
|
+
Function,
|
|
32
|
+
Headers,
|
|
33
|
+
Infinity,
|
|
34
|
+
Int16Array,
|
|
35
|
+
Int32Array,
|
|
36
|
+
Int8Array,
|
|
37
|
+
Intl,
|
|
38
|
+
// @ts-ignore Iterator does exist despite what TypeScript thinks
|
|
39
|
+
Iterator,
|
|
40
|
+
JSON,
|
|
41
|
+
Map,
|
|
42
|
+
Math,
|
|
43
|
+
NaN,
|
|
44
|
+
Number,
|
|
45
|
+
Object,
|
|
46
|
+
Promise,
|
|
47
|
+
Proxy,
|
|
48
|
+
RangeError,
|
|
49
|
+
ReferenceError,
|
|
50
|
+
Reflect,
|
|
51
|
+
RegExp,
|
|
52
|
+
Request,
|
|
53
|
+
Response,
|
|
54
|
+
Set,
|
|
55
|
+
SharedArrayBuffer,
|
|
56
|
+
String,
|
|
57
|
+
Symbol,
|
|
58
|
+
SyntaxError,
|
|
59
|
+
TypeError,
|
|
60
|
+
URIError,
|
|
61
|
+
Uint16Array,
|
|
62
|
+
Uint32Array,
|
|
63
|
+
Uint8Array,
|
|
64
|
+
Uint8ClampedArray,
|
|
65
|
+
WeakMap,
|
|
66
|
+
WeakRef,
|
|
67
|
+
WeakSet,
|
|
68
|
+
decodeURI,
|
|
69
|
+
decodeURIComponent,
|
|
70
|
+
encodeURI,
|
|
71
|
+
encodeURIComponent,
|
|
72
|
+
eval,
|
|
73
|
+
false: false, // treat like a global
|
|
74
|
+
fetch: fetchWrapper, // special case
|
|
75
|
+
globalThis,
|
|
76
|
+
import: importWrapper, // not a function in JS but acts like one
|
|
77
|
+
isFinite,
|
|
78
|
+
isNaN,
|
|
79
|
+
null: null, // treat like a global
|
|
80
|
+
parseFloat,
|
|
81
|
+
parseInt,
|
|
82
|
+
true: true, // treat like a global
|
|
83
|
+
undefined,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
async function fetchWrapper(resource, options) {
|
|
87
|
+
const response = await fetch(resource, options);
|
|
88
|
+
return response.ok ? await response.arrayBuffer() : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @this {import("@weborigami/types").AsyncTree|null|undefined} */
|
|
92
|
+
async function importWrapper(modulePath) {
|
|
93
|
+
// Walk up parent tree looking for a FileTree or other object with a `path`
|
|
94
|
+
/** @type {any} */
|
|
95
|
+
let current = this;
|
|
96
|
+
while (current && !("path" in current)) {
|
|
97
|
+
current = current.parent;
|
|
98
|
+
}
|
|
99
|
+
if (!current) {
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
"Modules can only be imported from a folder or other object with a path property."
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const filePath = path.resolve(current.path, modulePath);
|
|
105
|
+
return import(filePath);
|
|
106
|
+
}
|
|
@@ -53,11 +53,6 @@ export default async function mergeTrees(...trees) {
|
|
|
53
53
|
return result;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// If all trees are arrays, return an array.
|
|
57
|
-
if (unpacked.every((tree) => Array.isArray(tree))) {
|
|
58
|
-
return unpacked.flat();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
56
|
// Merge the trees.
|
|
62
57
|
const result = merge(...unpacked);
|
|
63
58
|
setParent(result, this);
|
package/src/runtime/ops.js
CHANGED
|
@@ -11,17 +11,16 @@ import {
|
|
|
11
11
|
deepText,
|
|
12
12
|
isUnpackable,
|
|
13
13
|
scope as scopeFn,
|
|
14
|
-
|
|
15
|
-
concat as treeConcat,
|
|
14
|
+
text as templateFunctionTree,
|
|
16
15
|
} from "@weborigami/async-tree";
|
|
17
16
|
import os from "node:os";
|
|
18
|
-
import taggedTemplateIndent from "../../src/runtime/taggedTemplateIndent.js";
|
|
19
|
-
import { builtinReferenceError, scopeReferenceError } from "./errors.js";
|
|
20
17
|
import expressionObject from "./expressionObject.js";
|
|
18
|
+
import getHandlers from "./getHandlers.js";
|
|
21
19
|
import { evaluate } from "./internal.js";
|
|
22
20
|
import mergeTrees from "./mergeTrees.js";
|
|
23
21
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
24
22
|
import { codeSymbol } from "./symbols.js";
|
|
23
|
+
import templateFunctionIndent from "./templateIndent.js";
|
|
25
24
|
|
|
26
25
|
function addOpLabel(op, label) {
|
|
27
26
|
Object.defineProperty(op, "toString", {
|
|
@@ -67,25 +66,35 @@ export function bitwiseXor(a, b) {
|
|
|
67
66
|
addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
|
68
67
|
|
|
69
68
|
/**
|
|
70
|
-
*
|
|
71
|
-
* chain.
|
|
69
|
+
* Cache the value of the code for an external reference
|
|
72
70
|
*
|
|
73
71
|
* @this {AsyncTree|null}
|
|
72
|
+
* @param {any} cache
|
|
73
|
+
* @param {string} path
|
|
74
|
+
* @param {AnnotatedCode} code
|
|
74
75
|
*/
|
|
75
|
-
export async function
|
|
76
|
-
if (
|
|
77
|
-
|
|
76
|
+
export async function cache(cache, path, code) {
|
|
77
|
+
if (path in cache) {
|
|
78
|
+
// Cache hit
|
|
79
|
+
return cache[path];
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Don't await: might get another request for this before promise resolves
|
|
83
|
+
const promise = await evaluate.call(this, code);
|
|
84
|
+
|
|
85
|
+
// Save promise so another request will get the same promise
|
|
86
|
+
cache[path] = promise;
|
|
87
|
+
|
|
88
|
+
// Now wait for the value
|
|
89
|
+
const value = await promise;
|
|
90
|
+
|
|
91
|
+
// Update the cache with the actual value
|
|
92
|
+
cache[path] = value;
|
|
85
93
|
|
|
86
94
|
return value;
|
|
87
95
|
}
|
|
88
|
-
addOpLabel(
|
|
96
|
+
addOpLabel(cache, "«ops.cache»");
|
|
97
|
+
cache.unevaluatedArgs = true;
|
|
89
98
|
|
|
90
99
|
/**
|
|
91
100
|
* JavaScript comma operator, returns the last argument.
|
|
@@ -105,7 +114,7 @@ addOpLabel(comma, "«ops.comma»");
|
|
|
105
114
|
* @param {any[]} args
|
|
106
115
|
*/
|
|
107
116
|
export async function concat(...args) {
|
|
108
|
-
return
|
|
117
|
+
return deepText.call(this, args);
|
|
109
118
|
}
|
|
110
119
|
addOpLabel(concat, "«ops.concat»");
|
|
111
120
|
|
|
@@ -114,28 +123,29 @@ export async function conditional(condition, truthy, falsy) {
|
|
|
114
123
|
return value instanceof Function ? await value() : value;
|
|
115
124
|
}
|
|
116
125
|
|
|
126
|
+
export async function construct(constructor, ...args) {
|
|
127
|
+
if (isUnpackable(constructor)) {
|
|
128
|
+
constructor = await constructor.unpack();
|
|
129
|
+
}
|
|
130
|
+
return Reflect.construct(constructor, args);
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
/**
|
|
118
|
-
*
|
|
119
|
-
* the resulting text to the front data.
|
|
134
|
+
* Return the nth parent of the current tree
|
|
120
135
|
*
|
|
121
|
-
* @this {AsyncTree|null}
|
|
122
|
-
* @param {any} frontData
|
|
123
|
-
* @param {AnnotatedCode} bodyCode
|
|
136
|
+
* @this {AsyncTree|null|undefined}
|
|
124
137
|
*/
|
|
125
|
-
export
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
addOpLabel(document, "«ops.document");
|
|
138
|
-
document.unevaluatedArgs = true;
|
|
138
|
+
export function context(n = 0) {
|
|
139
|
+
let tree = this;
|
|
140
|
+
for (let i = 0; i < n; i++) {
|
|
141
|
+
if (!tree) {
|
|
142
|
+
throw new Error("Internal error: couldn't find tree ancestor.");
|
|
143
|
+
}
|
|
144
|
+
tree = tree.parent;
|
|
145
|
+
}
|
|
146
|
+
return tree;
|
|
147
|
+
}
|
|
148
|
+
addOpLabel(context, "«ops.context»");
|
|
139
149
|
|
|
140
150
|
export function division(a, b) {
|
|
141
151
|
return a / b;
|
|
@@ -153,36 +163,22 @@ export function exponentiation(a, b) {
|
|
|
153
163
|
addOpLabel(exponentiation, "«ops.exponentiation»");
|
|
154
164
|
|
|
155
165
|
/**
|
|
156
|
-
*
|
|
157
|
-
* requests.
|
|
166
|
+
* Flatten the values of the given trees
|
|
158
167
|
*
|
|
159
|
-
* @
|
|
168
|
+
* @param {...any} args
|
|
160
169
|
*/
|
|
161
|
-
export async function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Don't await: might get another request for this before promise resolves
|
|
172
|
-
const promise = evaluate.call(this, code);
|
|
173
|
-
// Save promise so another request will get the same promise
|
|
174
|
-
cache[path] = promise;
|
|
175
|
-
|
|
176
|
-
// Now wait for the value
|
|
177
|
-
const value = await promise;
|
|
178
|
-
|
|
179
|
-
// Update the cache with the actual value
|
|
180
|
-
cache[path] = value;
|
|
170
|
+
export async function flat(...args) {
|
|
171
|
+
const arrays = await Promise.all(
|
|
172
|
+
args.map(async (arg) =>
|
|
173
|
+
arg instanceof Array || typeof arg !== "object"
|
|
174
|
+
? arg
|
|
175
|
+
: await Tree.values(arg)
|
|
176
|
+
)
|
|
177
|
+
);
|
|
181
178
|
|
|
182
|
-
return
|
|
179
|
+
return arrays.flat();
|
|
183
180
|
}
|
|
184
|
-
addOpLabel(
|
|
185
|
-
external.unevaluatedArgs = true;
|
|
181
|
+
addOpLabel(flat, "«ops.flat»");
|
|
186
182
|
|
|
187
183
|
/**
|
|
188
184
|
* This op is only used during parsing. It signals to ops.object that the
|
|
@@ -205,30 +201,14 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
|
|
205
201
|
*
|
|
206
202
|
* @this {AsyncTree|null}
|
|
207
203
|
*/
|
|
208
|
-
export async function homeDirectory() {
|
|
204
|
+
export async function homeDirectory(...keys) {
|
|
209
205
|
const tree = new OrigamiFiles(os.homedir());
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
// Use the same handlers as the current tree
|
|
207
|
+
tree.handlers = getHandlers(this);
|
|
208
|
+
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
212
209
|
}
|
|
213
210
|
addOpLabel(homeDirectory, "«ops.homeDirectory»");
|
|
214
211
|
|
|
215
|
-
/**
|
|
216
|
-
* Search the parent's scope -- i.e., exclude the current tree -- for the given
|
|
217
|
-
* key.
|
|
218
|
-
*
|
|
219
|
-
* @this {AsyncTree|null}
|
|
220
|
-
* @param {*} key
|
|
221
|
-
*/
|
|
222
|
-
export async function inherited(key) {
|
|
223
|
-
if (!this?.parent) {
|
|
224
|
-
return undefined;
|
|
225
|
-
}
|
|
226
|
-
const parentScope = scopeFn(this.parent);
|
|
227
|
-
const value = await parentScope.get(key);
|
|
228
|
-
return value;
|
|
229
|
-
}
|
|
230
|
-
addOpLabel(inherited, "«ops.inherited»");
|
|
231
|
-
|
|
232
212
|
/**
|
|
233
213
|
* Return a function that will invoke the given code.
|
|
234
214
|
*
|
|
@@ -367,61 +347,12 @@ addOpLabel(logicalOr, "«ops.logicalOr»");
|
|
|
367
347
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
368
348
|
*
|
|
369
349
|
* @this {AsyncTree|null}
|
|
370
|
-
* @param {
|
|
350
|
+
* @param {any[]} trees
|
|
371
351
|
*/
|
|
372
|
-
export async function merge(...
|
|
373
|
-
// First pass: evaluate the direct property entries in a single object
|
|
374
|
-
let treeSpreads = false;
|
|
375
|
-
const directEntries = [];
|
|
376
|
-
for (const code of codes) {
|
|
377
|
-
if (code[0] === object) {
|
|
378
|
-
directEntries.push(...code.slice(1));
|
|
379
|
-
} else {
|
|
380
|
-
treeSpreads = true;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const directObject = directEntries
|
|
385
|
-
? await expressionObject(directEntries, this)
|
|
386
|
-
: null;
|
|
387
|
-
if (!treeSpreads) {
|
|
388
|
-
// No tree spreads, we're done
|
|
389
|
-
return directObject;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// If we have direct property entries, create a context for them. The
|
|
393
|
-
// `expressionObject` function will set the object's parent symbol to `this`.
|
|
394
|
-
// Tree.from will call the ObjectTree constructor, which will use that symbol
|
|
395
|
-
// to set the parent for the new tree to `this`.
|
|
396
|
-
const context = directObject ? Tree.from(directObject) : this;
|
|
397
|
-
|
|
398
|
-
// Second pass: evaluate the trees. For the trees which are direct property
|
|
399
|
-
// entries, we'll copy over the values we've already calculated. We can't
|
|
400
|
-
// reuse the `directObject` as is because in a merge we need to respect the
|
|
401
|
-
// order in which the properties are defined. Trees that aren't direct
|
|
402
|
-
// property entries are evaluated with the direct property entries in scope.
|
|
403
|
-
const trees = await Promise.all(
|
|
404
|
-
codes.map(async (code) => {
|
|
405
|
-
if (code[0] === object) {
|
|
406
|
-
// Using the code as reference, create a new object with the direct
|
|
407
|
-
// property values we've already calculated.
|
|
408
|
-
const object = {};
|
|
409
|
-
for (const [key] of code.slice(1)) {
|
|
410
|
-
// @ts-ignore directObject will always be defined
|
|
411
|
-
object[key] = directObject[key];
|
|
412
|
-
}
|
|
413
|
-
setParent(object, this);
|
|
414
|
-
return object;
|
|
415
|
-
} else {
|
|
416
|
-
return evaluate.call(context, code);
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
);
|
|
420
|
-
|
|
352
|
+
export async function merge(...trees) {
|
|
421
353
|
return mergeTrees.call(this, ...trees);
|
|
422
354
|
}
|
|
423
355
|
addOpLabel(merge, "«ops.merge»");
|
|
424
|
-
merge.unevaluatedArgs = true;
|
|
425
356
|
|
|
426
357
|
export function multiplication(a, b) {
|
|
427
358
|
return a * b;
|
|
@@ -472,6 +403,14 @@ export async function object(...entries) {
|
|
|
472
403
|
addOpLabel(object, "«ops.object»");
|
|
473
404
|
object.unevaluatedArgs = true;
|
|
474
405
|
|
|
406
|
+
export function optionalTraverse(treelike, key) {
|
|
407
|
+
if (!treelike) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
return Tree.traverseOrThrow(treelike, key);
|
|
411
|
+
}
|
|
412
|
+
addOpLabel(optionalTraverse, "«ops.optionalTraverse");
|
|
413
|
+
|
|
475
414
|
export function remainder(a, b) {
|
|
476
415
|
return a % b;
|
|
477
416
|
}
|
|
@@ -482,31 +421,28 @@ addOpLabel(remainder, "«ops.remainder»");
|
|
|
482
421
|
*
|
|
483
422
|
* @this {AsyncTree|null}
|
|
484
423
|
*/
|
|
485
|
-
export async function rootDirectory(
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
tree.parent = this ? Tree.root(this) : null;
|
|
491
|
-
return key ? tree.get(key) : tree;
|
|
424
|
+
export async function rootDirectory(...keys) {
|
|
425
|
+
const tree = new OrigamiFiles("/");
|
|
426
|
+
// Use the same handlers as the current tree
|
|
427
|
+
tree.handlers = getHandlers(this);
|
|
428
|
+
return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
|
|
492
429
|
}
|
|
493
430
|
addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
494
431
|
|
|
495
432
|
/**
|
|
496
|
-
*
|
|
433
|
+
* Return the scope of the current tree
|
|
497
434
|
*
|
|
498
435
|
* @this {AsyncTree|null}
|
|
436
|
+
* @param {AsyncTree|null} [context]
|
|
499
437
|
*/
|
|
500
|
-
export async function scope(
|
|
501
|
-
if (
|
|
502
|
-
|
|
438
|
+
export async function scope(context) {
|
|
439
|
+
if (context === undefined) {
|
|
440
|
+
context = this;
|
|
503
441
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (value === undefined && key !== "undefined") {
|
|
507
|
-
throw await scopeReferenceError(scope, key);
|
|
442
|
+
if (!context) {
|
|
443
|
+
return null;
|
|
508
444
|
}
|
|
509
|
-
return
|
|
445
|
+
return scopeFn(context);
|
|
510
446
|
}
|
|
511
447
|
addOpLabel(scope, "«ops.scope»");
|
|
512
448
|
|
|
@@ -547,25 +483,20 @@ export function subtraction(a, b) {
|
|
|
547
483
|
addOpLabel(subtraction, "«ops.subtraction»");
|
|
548
484
|
|
|
549
485
|
/**
|
|
550
|
-
* Apply the
|
|
551
|
-
*/
|
|
552
|
-
export async function template(strings, ...values) {
|
|
553
|
-
return deepText(strings, ...values);
|
|
554
|
-
}
|
|
555
|
-
addOpLabel(template, "«ops.template»");
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Apply the tagged template indent function.
|
|
486
|
+
* Apply the tree indent tagged template function.
|
|
559
487
|
*/
|
|
560
488
|
export async function templateIndent(strings, ...values) {
|
|
561
|
-
return
|
|
489
|
+
return templateFunctionIndent(strings, ...values);
|
|
562
490
|
}
|
|
563
|
-
addOpLabel(templateIndent, "«ops.templateIndent");
|
|
491
|
+
addOpLabel(templateIndent, "«ops.templateIndent»");
|
|
564
492
|
|
|
565
493
|
/**
|
|
566
|
-
*
|
|
494
|
+
* Apply the tree tagged template function.
|
|
567
495
|
*/
|
|
568
|
-
export
|
|
496
|
+
export async function templateTree(strings, ...values) {
|
|
497
|
+
return templateFunctionTree(strings, ...values);
|
|
498
|
+
}
|
|
499
|
+
addOpLabel(templateTree, "«ops.templateTree»");
|
|
569
500
|
|
|
570
501
|
export function unaryMinus(a) {
|
|
571
502
|
return -a;
|
package/src/runtime/symbols.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deepText, toString, Tree } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
3
|
const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
|
|
4
4
|
|
|
@@ -19,7 +19,7 @@ export default async function indent(strings, ...values) {
|
|
|
19
19
|
}
|
|
20
20
|
const { blockIndentations, strings: modifiedStrings } = modified;
|
|
21
21
|
const valueTexts = await Promise.all(
|
|
22
|
-
values.map((value) => (Tree.isTreelike(value) ?
|
|
22
|
+
values.map((value) => (Tree.isTreelike(value) ? deepText(value) : value))
|
|
23
23
|
);
|
|
24
24
|
return joinBlocks(modifiedStrings, valueTexts, blockIndentations);
|
|
25
25
|
}
|
|
@@ -13,7 +13,9 @@ export function assertCodeEqual(actual, expected) {
|
|
|
13
13
|
* @returns {import("../../index.ts").AnnotatedCode}
|
|
14
14
|
*/
|
|
15
15
|
export function createCode(array) {
|
|
16
|
-
const code = array
|
|
16
|
+
const code = array.map((item) =>
|
|
17
|
+
item instanceof Array ? createCode(item) : item
|
|
18
|
+
);
|
|
17
19
|
/** @type {any} */ (code).location = {
|
|
18
20
|
end: 0,
|
|
19
21
|
source: {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { ObjectTree,
|
|
1
|
+
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 * as compile from "../../src/compiler/compile.js";
|
|
5
|
-
import { ops } from "../../src/runtime/internal.js";
|
|
6
5
|
import { assertCodeEqual } from "./codeHelpers.js";
|
|
7
6
|
|
|
8
|
-
const
|
|
7
|
+
const sharedGlobals = {
|
|
9
8
|
greet: (name) => `Hello, ${name}!`,
|
|
10
9
|
name: "Alice",
|
|
11
|
-
}
|
|
10
|
+
};
|
|
12
11
|
|
|
13
12
|
describe("compile", () => {
|
|
14
13
|
test("array", async () => {
|
|
@@ -20,16 +19,52 @@ describe("compile", () => {
|
|
|
20
19
|
test("functionComposition", async () => {
|
|
21
20
|
await assertCompile("greet()", "Hello, undefined!");
|
|
22
21
|
await assertCompile("greet(name)", "Hello, Alice!");
|
|
22
|
+
await assertCompile("greet(name)", "Hello, Alice!", { mode: "jse" });
|
|
23
23
|
await assertCompile("greet 'world'", "Hello, world!");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test("
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
test("angle bracket path", async () => {
|
|
27
|
+
await assertCompile("<data>", "Bob", {
|
|
28
|
+
target: {
|
|
29
|
+
data: "Bob",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("object literal", async () => {
|
|
35
|
+
await assertCompile("{ message = greet(name) }", {
|
|
31
36
|
message: "Hello, Alice!",
|
|
32
37
|
});
|
|
38
|
+
await assertCompile(
|
|
39
|
+
"{ message = greet(name) }",
|
|
40
|
+
{
|
|
41
|
+
message: "Hello, Alice!",
|
|
42
|
+
},
|
|
43
|
+
{ mode: "jse" }
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test.skip("merge", async () => {
|
|
48
|
+
{
|
|
49
|
+
const scope = new ObjectTree({
|
|
50
|
+
more: {
|
|
51
|
+
b: 2,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const fn = compile.expression(`
|
|
55
|
+
{
|
|
56
|
+
a: 1
|
|
57
|
+
...more
|
|
58
|
+
c: a
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
const result = await fn.call(scope);
|
|
62
|
+
assert.deepEqual(await Tree.plain(result), {
|
|
63
|
+
a: 1,
|
|
64
|
+
b: 2,
|
|
65
|
+
c: 1,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
33
68
|
});
|
|
34
69
|
|
|
35
70
|
test("number", async () => {
|
|
@@ -44,8 +79,10 @@ describe("compile", () => {
|
|
|
44
79
|
});
|
|
45
80
|
|
|
46
81
|
test("async object", async () => {
|
|
47
|
-
const fn = compile.expression("{ a: { b = name }}"
|
|
48
|
-
|
|
82
|
+
const fn = compile.expression("{ a: { b = name }}", {
|
|
83
|
+
globals: sharedGlobals,
|
|
84
|
+
});
|
|
85
|
+
const object = await fn.call(null);
|
|
49
86
|
assert.deepEqual(await object.a.b, "Alice");
|
|
50
87
|
});
|
|
51
88
|
|
|
@@ -68,7 +105,7 @@ describe("compile", () => {
|
|
|
68
105
|
|
|
69
106
|
test("tagged template string array is identical across calls", async () => {
|
|
70
107
|
let saved;
|
|
71
|
-
const
|
|
108
|
+
const globals = {
|
|
72
109
|
tag: (strings, ...values) => {
|
|
73
110
|
assertCodeEqual(strings, ["Hello, ", "!"]);
|
|
74
111
|
if (saved) {
|
|
@@ -78,30 +115,23 @@ describe("compile", () => {
|
|
|
78
115
|
}
|
|
79
116
|
return strings[0] + values[0] + strings[1];
|
|
80
117
|
},
|
|
81
|
-
}
|
|
82
|
-
const program = compile.expression("=tag`Hello, ${_}!`");
|
|
83
|
-
const lambda = await program.call(
|
|
118
|
+
};
|
|
119
|
+
const program = compile.expression("=tag`Hello, ${_}!`", { globals });
|
|
120
|
+
const lambda = await program.call(null);
|
|
84
121
|
const alice = await lambda("Alice");
|
|
85
122
|
assert.equal(alice, "Hello, Alice!");
|
|
86
123
|
const bob = await lambda("Bob");
|
|
87
124
|
assert.equal(bob, "Hello, Bob!");
|
|
88
125
|
});
|
|
89
|
-
|
|
90
|
-
test("can apply a macro", async () => {
|
|
91
|
-
const literal = [ops.literal, 1];
|
|
92
|
-
const expression = `{ a: literal }`;
|
|
93
|
-
const fn = compile.expression(expression, {
|
|
94
|
-
macros: {
|
|
95
|
-
literal,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
const code = fn.code;
|
|
99
|
-
assertCodeEqual(code, [ops.object, ["a", 1]]);
|
|
100
|
-
});
|
|
101
126
|
});
|
|
102
127
|
|
|
103
|
-
async function assertCompile(text, expected) {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
128
|
+
async function assertCompile(text, expected, options = {}) {
|
|
129
|
+
const mode = options.mode ?? "shell";
|
|
130
|
+
const fn = compile.expression(text, { globals: sharedGlobals, mode });
|
|
131
|
+
const target = options.target ?? null;
|
|
132
|
+
let result = await fn.call(target);
|
|
133
|
+
if (Tree.isTreelike(result)) {
|
|
134
|
+
result = await Tree.plain(result);
|
|
135
|
+
}
|
|
106
136
|
assert.deepEqual(result, expected);
|
|
107
137
|
}
|