@weborigami/language 0.3.3-jse.1 → 0.3.3-jse.3
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 +2 -0
- package/package.json +3 -3
- package/src/compiler/compile.js +8 -2
- package/src/compiler/optimize.js +147 -93
- package/src/compiler/origami.pegjs +74 -60
- package/src/compiler/parse.js +946 -766
- package/src/compiler/parserHelpers.js +148 -48
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +4 -4
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +14 -53
- package/src/runtime/jsGlobals.js +99 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +69 -148
- package/src/runtime/symbols.js +1 -0
- package/src/runtime/templateIndent.js +2 -2
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +52 -27
- package/test/compiler/optimize.test.js +92 -23
- package/test/compiler/parse.test.js +480 -366
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +5 -4
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +94 -82
|
@@ -15,8 +15,12 @@ const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
|
15
15
|
/** @typedef {import("../../index.ts").CodeLocation} CodeLocation */
|
|
16
16
|
/** @typedef {import("../../index.ts").Code} Code */
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
export const
|
|
18
|
+
// Markers in compiled output, will get optimized away
|
|
19
|
+
export const markers = {
|
|
20
|
+
global: Symbol("global"), // Global reference
|
|
21
|
+
traverse: Symbol("traverse"), // Continuation of path traversal
|
|
22
|
+
reference: Symbol("reference"), // Reference to local, scope, or global
|
|
23
|
+
};
|
|
20
24
|
|
|
21
25
|
const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
22
26
|
|
|
@@ -37,7 +41,7 @@ export function annotate(code, location) {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
|
-
* In the given code, replace all
|
|
44
|
+
* In the given code, replace all function calls to the given name with the
|
|
41
45
|
* given macro code.
|
|
42
46
|
*
|
|
43
47
|
* @param {AnnotatedCode} code
|
|
@@ -49,8 +53,16 @@ export function applyMacro(code, name, macro) {
|
|
|
49
53
|
return code;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
// We're looking for a function call with the given name.
|
|
57
|
+
// For `foo`, the call would be: [[reference, [ops.literal, "foo"]], undefined]
|
|
58
|
+
if (
|
|
59
|
+
code[0] &&
|
|
60
|
+
code[0][0] === markers.reference &&
|
|
61
|
+
code[0][1] instanceof Array &&
|
|
62
|
+
code[0][1][0] === ops.literal &&
|
|
63
|
+
code[0][1][1] === name &&
|
|
64
|
+
code[1] === undefined
|
|
65
|
+
) {
|
|
54
66
|
return macro;
|
|
55
67
|
}
|
|
56
68
|
|
|
@@ -60,7 +72,7 @@ export function applyMacro(code, name, macro) {
|
|
|
60
72
|
|
|
61
73
|
/**
|
|
62
74
|
* The indicated code is being used to define a property named by the given key.
|
|
63
|
-
* Rewrite any [ops.scope, key] calls to be [ops.inherited, key] to avoid
|
|
75
|
+
* Rewrite any [[ops.scope], key] calls to be [ops.inherited, key] to avoid
|
|
64
76
|
* infinite recursion.
|
|
65
77
|
*
|
|
66
78
|
* @param {AnnotatedCode} code
|
|
@@ -73,11 +85,12 @@ function avoidRecursivePropertyCalls(code, key) {
|
|
|
73
85
|
/** @type {Code} */
|
|
74
86
|
let modified;
|
|
75
87
|
if (
|
|
76
|
-
code[0]
|
|
77
|
-
|
|
88
|
+
code[0] instanceof Array &&
|
|
89
|
+
code[0][0] === ops.scope &&
|
|
90
|
+
trailingSlash.remove(code[1][1]) === trailingSlash.remove(key)
|
|
78
91
|
) {
|
|
79
92
|
// Rewrite to avoid recursion
|
|
80
|
-
modified = [ops.inherited, code[1]];
|
|
93
|
+
modified = [ops.inherited, code[1][1]];
|
|
81
94
|
} else if (
|
|
82
95
|
code[0] === ops.lambda &&
|
|
83
96
|
code[1].some((param) => param[1] === key)
|
|
@@ -92,13 +105,13 @@ function avoidRecursivePropertyCalls(code, key) {
|
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
/**
|
|
95
|
-
* Downgrade a potential
|
|
108
|
+
* Downgrade a potential global reference to a reference.
|
|
96
109
|
*
|
|
97
110
|
* @param {AnnotatedCode} code
|
|
98
111
|
*/
|
|
99
112
|
export function downgradeReference(code) {
|
|
100
|
-
if (code && code.length === 2 && code[0] ===
|
|
101
|
-
return annotate([
|
|
113
|
+
if (code && code.length === 2 && code[0] === markers.reference) {
|
|
114
|
+
return annotate([markers.reference, code[1]], code.location);
|
|
102
115
|
} else {
|
|
103
116
|
return code;
|
|
104
117
|
}
|
|
@@ -139,7 +152,7 @@ export function makeArray(entries, location) {
|
|
|
139
152
|
|
|
140
153
|
let result;
|
|
141
154
|
if (spreads.length > 1) {
|
|
142
|
-
result = [ops.
|
|
155
|
+
result = [ops.flat, ...spreads];
|
|
143
156
|
} else if (spreads.length === 1) {
|
|
144
157
|
result = spreads[0];
|
|
145
158
|
} else {
|
|
@@ -196,7 +209,7 @@ export function makeBinaryOperation(left, [operatorToken, right]) {
|
|
|
196
209
|
* @param {AnnotatedCode} target
|
|
197
210
|
* @param {any[]} args
|
|
198
211
|
*/
|
|
199
|
-
export function makeCall(target, args) {
|
|
212
|
+
export function makeCall(target, args, mode) {
|
|
200
213
|
if (!(target instanceof Array)) {
|
|
201
214
|
const error = new SyntaxError(`Can't call this like a function: ${target}`);
|
|
202
215
|
/** @type {any} */ (error).location = /** @type {any} */ (target).location;
|
|
@@ -205,38 +218,55 @@ export function makeCall(target, args) {
|
|
|
205
218
|
|
|
206
219
|
let fnCall;
|
|
207
220
|
const op = args[0];
|
|
208
|
-
if (op ===
|
|
221
|
+
if (op === markers.traverse || op === ops.optionalTraverse) {
|
|
209
222
|
let tree = target;
|
|
210
223
|
|
|
211
|
-
if (tree[0] ===
|
|
212
|
-
//
|
|
213
|
-
tree =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
if (tree[0] === markers.reference && !trailingSlash.has(tree[1][1])) {
|
|
225
|
+
// Target didn't parse with a trailing slash; add one
|
|
226
|
+
tree[1][1] = trailingSlash.add(tree[1][1]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Is the target an existing traversal that can be extended? It should be a
|
|
230
|
+
// reference or global where all the args are literals.
|
|
231
|
+
const extend =
|
|
232
|
+
(tree[0] === markers.reference ||
|
|
233
|
+
(tree[0] instanceof Array && tree[0][0] === markers.global)) &&
|
|
234
|
+
!tree
|
|
235
|
+
.slice(1)
|
|
236
|
+
.some((arg) => !(arg instanceof Array && arg[0] === ops.literal));
|
|
237
|
+
if (extend) {
|
|
238
|
+
fnCall = tree;
|
|
239
|
+
// If last key doesn't end with slash, add one
|
|
240
|
+
const last = tree.at(-1);
|
|
241
|
+
if (last instanceof Array && last[0] === ops.literal) {
|
|
242
|
+
last[1] = trailingSlash.add(last[1]);
|
|
217
243
|
}
|
|
244
|
+
} else {
|
|
245
|
+
fnCall = [tree];
|
|
218
246
|
}
|
|
219
247
|
|
|
220
248
|
if (args.length > 1) {
|
|
221
249
|
// Regular traverse
|
|
222
250
|
const keys = args.slice(1);
|
|
223
|
-
fnCall
|
|
224
|
-
} else {
|
|
251
|
+
fnCall.push(...keys);
|
|
252
|
+
} else if (tree[0] !== ops.rootDirectory) {
|
|
225
253
|
// Traverse without arguments equates to unpack
|
|
226
254
|
fnCall = [ops.unpack, tree];
|
|
255
|
+
} else {
|
|
256
|
+
fnCall = tree;
|
|
227
257
|
}
|
|
228
258
|
} else if (op === ops.templateStandard || op === ops.templateTree) {
|
|
229
259
|
// Tagged template
|
|
230
260
|
const strings = args[1];
|
|
231
261
|
const values = args.slice(2);
|
|
232
262
|
fnCall = makeTaggedTemplateCall(
|
|
233
|
-
upgradeReference(target),
|
|
263
|
+
upgradeReference(target, mode),
|
|
234
264
|
strings,
|
|
235
265
|
...values
|
|
236
266
|
);
|
|
237
267
|
} else {
|
|
238
268
|
// Function call with explicit or implicit parentheses
|
|
239
|
-
fnCall = [upgradeReference(target), ...args];
|
|
269
|
+
fnCall = [upgradeReference(target, mode), ...args];
|
|
240
270
|
}
|
|
241
271
|
|
|
242
272
|
// Create a location spanning the newly-constructed function call.
|
|
@@ -276,6 +306,21 @@ export function makeDeferredArguments(args) {
|
|
|
276
306
|
});
|
|
277
307
|
}
|
|
278
308
|
|
|
309
|
+
export function makeDocument(mode, front, body, location) {
|
|
310
|
+
// In order for template expressions to see the front matter properties,
|
|
311
|
+
// we translate the top-level front properties to object entries.
|
|
312
|
+
const entries = Object.entries(front).map(([key, value]) =>
|
|
313
|
+
annotate([key, annotate([ops.literal, value], location)], location)
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Add an entry for the body
|
|
317
|
+
const bodyKey = mode === "jse" ? "_body" : "@text";
|
|
318
|
+
entries.push(annotate([bodyKey, body], location));
|
|
319
|
+
|
|
320
|
+
// Return the code for the document object
|
|
321
|
+
return annotate([ops.object, ...entries], location);
|
|
322
|
+
}
|
|
323
|
+
|
|
279
324
|
export function makeJsPropertyAccess(expression, property) {
|
|
280
325
|
const location = {
|
|
281
326
|
source: expression.location.source,
|
|
@@ -285,6 +330,70 @@ export function makeJsPropertyAccess(expression, property) {
|
|
|
285
330
|
return annotate([expression, property], location);
|
|
286
331
|
}
|
|
287
332
|
|
|
333
|
+
/**
|
|
334
|
+
* From the given spreads within an object spread, return the merge.
|
|
335
|
+
*
|
|
336
|
+
* Example:
|
|
337
|
+
*
|
|
338
|
+
* {
|
|
339
|
+
* x = { a: 1 }
|
|
340
|
+
* …x
|
|
341
|
+
* y = x
|
|
342
|
+
* }
|
|
343
|
+
*
|
|
344
|
+
* will be treated as:
|
|
345
|
+
*
|
|
346
|
+
* {
|
|
347
|
+
* x = { a: 1 }
|
|
348
|
+
* y = x
|
|
349
|
+
* _result: {
|
|
350
|
+
* x
|
|
351
|
+
* …x
|
|
352
|
+
* y
|
|
353
|
+
* }
|
|
354
|
+
* }.result
|
|
355
|
+
*
|
|
356
|
+
* @param {*} spreads
|
|
357
|
+
* @param {CodeLocation} location
|
|
358
|
+
*/
|
|
359
|
+
function makeMerge(spreads, location) {
|
|
360
|
+
const topEntries = [];
|
|
361
|
+
const resultEntries = [];
|
|
362
|
+
for (const spread of spreads) {
|
|
363
|
+
if (spread[0] === ops.object) {
|
|
364
|
+
topEntries.push(...spread.slice(1));
|
|
365
|
+
// Also add an object to the result with indirect references
|
|
366
|
+
const indirectEntries = spread.slice(1).map((entry) => {
|
|
367
|
+
const [key] = entry;
|
|
368
|
+
const context = annotate([ops.context, 1], entry.location);
|
|
369
|
+
const reference = annotate([context, key], entry.location);
|
|
370
|
+
const getter = annotate([ops.getter, reference], entry.location);
|
|
371
|
+
return annotate([key, getter], entry.location);
|
|
372
|
+
});
|
|
373
|
+
const indirectObject = annotate(
|
|
374
|
+
[ops.object, ...indirectEntries],
|
|
375
|
+
location
|
|
376
|
+
);
|
|
377
|
+
resultEntries.push(indirectObject);
|
|
378
|
+
} else {
|
|
379
|
+
resultEntries.push(spread);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Merge to create result
|
|
384
|
+
const result = annotate([ops.merge, ...resultEntries], location);
|
|
385
|
+
|
|
386
|
+
// Add the result to the top-level object as _result
|
|
387
|
+
topEntries.push(annotate(["_result", result], location));
|
|
388
|
+
|
|
389
|
+
// Construct the top-level object
|
|
390
|
+
const topObject = annotate([ops.object, ...topEntries], location);
|
|
391
|
+
|
|
392
|
+
// Get the _result property
|
|
393
|
+
const code = annotate([topObject, "_result"], location);
|
|
394
|
+
return code;
|
|
395
|
+
}
|
|
396
|
+
|
|
288
397
|
/**
|
|
289
398
|
* Make an object.
|
|
290
399
|
*
|
|
@@ -341,7 +450,7 @@ export function makeObject(entries, location) {
|
|
|
341
450
|
let code;
|
|
342
451
|
if (spreads.length > 1) {
|
|
343
452
|
// Merge multiple spreads
|
|
344
|
-
code =
|
|
453
|
+
code = makeMerge(spreads, location);
|
|
345
454
|
} else if (spreads.length === 1) {
|
|
346
455
|
// A single spread can just be the object
|
|
347
456
|
code = spreads[0];
|
|
@@ -358,10 +467,11 @@ export function makeObject(entries, location) {
|
|
|
358
467
|
*
|
|
359
468
|
* @param {AnnotatedCode} arg
|
|
360
469
|
* @param {AnnotatedCode} fn
|
|
470
|
+
* @param {string} mode
|
|
361
471
|
*/
|
|
362
|
-
export function makePipeline(arg, fn) {
|
|
363
|
-
const upgraded = upgradeReference(fn);
|
|
364
|
-
const result = makeCall(upgraded, [arg]);
|
|
472
|
+
export function makePipeline(arg, fn, mode) {
|
|
473
|
+
const upgraded = upgradeReference(fn, mode);
|
|
474
|
+
const result = makeCall(upgraded, [arg], mode);
|
|
365
475
|
const source = fn.location.source;
|
|
366
476
|
let start = arg.location.start;
|
|
367
477
|
let end = fn.location.end;
|
|
@@ -374,21 +484,6 @@ export function makeProperty(key, value) {
|
|
|
374
484
|
return [key, modified];
|
|
375
485
|
}
|
|
376
486
|
|
|
377
|
-
export function makeReference(identifier) {
|
|
378
|
-
// We can't know for sure that an identifier is a builtin reference until we
|
|
379
|
-
// see whether it's being called as a function.
|
|
380
|
-
let op;
|
|
381
|
-
if (builtinRegex.test(identifier)) {
|
|
382
|
-
op = identifier.endsWith(":")
|
|
383
|
-
? // Namespace is always a builtin reference
|
|
384
|
-
ops.builtin
|
|
385
|
-
: undetermined;
|
|
386
|
-
} else {
|
|
387
|
-
op = ops.scope;
|
|
388
|
-
}
|
|
389
|
-
return [op, identifier];
|
|
390
|
-
}
|
|
391
|
-
|
|
392
487
|
/**
|
|
393
488
|
* Make a tagged template call
|
|
394
489
|
*
|
|
@@ -496,7 +591,7 @@ export function makeYamlObject(text, location) {
|
|
|
496
591
|
throw error;
|
|
497
592
|
}
|
|
498
593
|
|
|
499
|
-
return
|
|
594
|
+
return parsed;
|
|
500
595
|
}
|
|
501
596
|
|
|
502
597
|
/**
|
|
@@ -504,9 +599,14 @@ export function makeYamlObject(text, location) {
|
|
|
504
599
|
*
|
|
505
600
|
* @param {AnnotatedCode} code
|
|
506
601
|
*/
|
|
507
|
-
export function upgradeReference(code) {
|
|
508
|
-
if (
|
|
509
|
-
|
|
602
|
+
export function upgradeReference(code, mode) {
|
|
603
|
+
if (
|
|
604
|
+
mode === "shell" &&
|
|
605
|
+
code.length === 2 &&
|
|
606
|
+
code[0] === markers.reference &&
|
|
607
|
+
builtinRegex.exec(code[1][1])
|
|
608
|
+
) {
|
|
609
|
+
const result = [markers.global, code[1][1]];
|
|
510
610
|
return annotate(result, code.location);
|
|
511
611
|
} else {
|
|
512
612
|
return code;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import getHandlers from "./getHandlers.js";
|
|
1
2
|
import { handleExtension } from "./handlers.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -9,9 +10,17 @@ import { handleExtension } from "./handlers.js";
|
|
|
9
10
|
*/
|
|
10
11
|
export default function HandleExtensionsTransform(Base) {
|
|
11
12
|
return class FileLoaders extends Base {
|
|
13
|
+
constructor(...args) {
|
|
14
|
+
super(...args);
|
|
15
|
+
|
|
16
|
+
// Callers should set this to the set of supported extension handlers
|
|
17
|
+
this.handlers = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
async get(key) {
|
|
13
21
|
const value = await super.get(key);
|
|
14
|
-
|
|
22
|
+
const handlers = getHandlers(this);
|
|
23
|
+
return handleExtension(this, value, key, handlers);
|
|
15
24
|
}
|
|
16
25
|
};
|
|
17
26
|
}
|
package/src/runtime/evaluate.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Tree, isUnpackable
|
|
1
|
+
import { Tree, isUnpackable } from "@weborigami/async-tree";
|
|
2
2
|
import codeFragment from "./codeFragment.js";
|
|
3
|
-
import { codeSymbol, scopeSymbol, sourceSymbol } from "./symbols.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Evaluate the given code and return the result.
|
|
@@ -52,7 +51,7 @@ export default async function evaluate(code) {
|
|
|
52
51
|
result =
|
|
53
52
|
fn instanceof Function
|
|
54
53
|
? await fn.call(tree, ...args) // Invoke the function
|
|
55
|
-
: await Tree.traverseOrThrow(fn, ...args); // Traverse the tree.
|
|
54
|
+
: await Tree.traverseOrThrow.call(tree, fn, ...args); // Traverse the tree.
|
|
56
55
|
} catch (/** @type {any} */ error) {
|
|
57
56
|
if (!error.location) {
|
|
58
57
|
// Attach the location of the code we tried to evaluate.
|
|
@@ -67,39 +66,33 @@ export default async function evaluate(code) {
|
|
|
67
66
|
throw error;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
// If the result is a tree, then the default parent of the tree is the current
|
|
71
|
-
// tree.
|
|
72
|
-
if (Tree.isAsyncTree(result) && !result.parent) {
|
|
73
|
-
result.parent = tree;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
69
|
// To aid debugging, add the code to the result.
|
|
77
|
-
if (Object.isExtensible(result)) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
70
|
+
// if (Object.isExtensible(result)) {
|
|
71
|
+
// try {
|
|
72
|
+
// if (code.location && !result[sourceSymbol]) {
|
|
73
|
+
// Object.defineProperty(result, sourceSymbol, {
|
|
74
|
+
// value: codeFragment(code.location),
|
|
75
|
+
// enumerable: false,
|
|
76
|
+
// });
|
|
77
|
+
// }
|
|
78
|
+
// if (!result[codeSymbol]) {
|
|
79
|
+
// Object.defineProperty(result, codeSymbol, {
|
|
80
|
+
// value: code,
|
|
81
|
+
// enumerable: false,
|
|
82
|
+
// });
|
|
83
|
+
// }
|
|
84
|
+
// if (!result[scopeSymbol]) {
|
|
85
|
+
// Object.defineProperty(result, scopeSymbol, {
|
|
86
|
+
// get() {
|
|
87
|
+
// return scope(this).trees;
|
|
88
|
+
// },
|
|
89
|
+
// enumerable: false,
|
|
90
|
+
// });
|
|
91
|
+
// }
|
|
92
|
+
// } catch (/** @type {any} */ error) {
|
|
93
|
+
// // Ignore errors.
|
|
94
|
+
// }
|
|
95
|
+
// }
|
|
103
96
|
|
|
104
97
|
return result;
|
|
105
98
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
trailingSlash,
|
|
7
7
|
Tree,
|
|
8
8
|
} from "@weborigami/async-tree";
|
|
9
|
+
import getHandlers from "./getHandlers.js";
|
|
9
10
|
import { handleExtension } from "./handlers.js";
|
|
10
11
|
import { evaluate, ops } from "./internal.js";
|
|
11
12
|
|
|
@@ -32,6 +33,7 @@ export default async function expressionObject(entries, parent) {
|
|
|
32
33
|
if (parent !== null && !Tree.isAsyncTree(parent)) {
|
|
33
34
|
throw new TypeError(`Parent must be an AsyncTree or null`);
|
|
34
35
|
}
|
|
36
|
+
setParent(object, parent);
|
|
35
37
|
|
|
36
38
|
let tree;
|
|
37
39
|
const eagerProperties = [];
|
|
@@ -90,7 +92,8 @@ export default async function expressionObject(entries, parent) {
|
|
|
90
92
|
get = async () => {
|
|
91
93
|
tree ??= new ObjectTree(object);
|
|
92
94
|
const result = await evaluate.call(tree, code);
|
|
93
|
-
|
|
95
|
+
const handlers = getHandlers(tree);
|
|
96
|
+
return handleExtension(tree, result, key, handlers);
|
|
94
97
|
};
|
|
95
98
|
} else {
|
|
96
99
|
// No extension, so getter just invokes code.
|
|
@@ -116,9 +119,6 @@ export default async function expressionObject(entries, parent) {
|
|
|
116
119
|
writable: true,
|
|
117
120
|
});
|
|
118
121
|
|
|
119
|
-
// Attach the parent
|
|
120
|
-
setParent(object, parent);
|
|
121
|
-
|
|
122
122
|
// Evaluate any properties that were declared as immediate: get their value
|
|
123
123
|
// and overwrite the property getter with the actual value.
|
|
124
124
|
for (const key of eagerProperties) {
|
package/src/runtime/handlers.js
CHANGED
|
@@ -4,60 +4,10 @@ import {
|
|
|
4
4
|
isPacked,
|
|
5
5
|
isStringLike,
|
|
6
6
|
isUnpackable,
|
|
7
|
-
scope,
|
|
8
7
|
setParent,
|
|
9
8
|
trailingSlash,
|
|
10
9
|
} from "@weborigami/async-tree";
|
|
11
10
|
|
|
12
|
-
/** @typedef {import("../../index.ts").ExtensionHandler} ExtensionHandler */
|
|
13
|
-
|
|
14
|
-
// Track extensions handlers for a given containing tree.
|
|
15
|
-
const handlersForContainer = new Map();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Find an extension handler for a file in the given container.
|
|
19
|
-
*
|
|
20
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
21
|
-
*
|
|
22
|
-
* @param {AsyncTree} parent
|
|
23
|
-
* @param {string} extension
|
|
24
|
-
*/
|
|
25
|
-
export async function getExtensionHandler(parent, extension) {
|
|
26
|
-
let handlers = handlersForContainer.get(parent);
|
|
27
|
-
if (handlers) {
|
|
28
|
-
if (handlers[extension]) {
|
|
29
|
-
return handlers[extension];
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
handlers = {};
|
|
33
|
-
handlersForContainer.set(parent, handlers);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const handlerName = `${extension.slice(1)}.handler`;
|
|
37
|
-
const parentScope = scope(parent);
|
|
38
|
-
|
|
39
|
-
/** @type {Promise<ExtensionHandler>} */
|
|
40
|
-
let handlerPromise = parentScope
|
|
41
|
-
?.get(handlerName)
|
|
42
|
-
.then(async (extensionHandler) => {
|
|
43
|
-
if (isUnpackable(extensionHandler)) {
|
|
44
|
-
// The extension handler itself needs to be unpacked. E.g., if it's a
|
|
45
|
-
// buffer containing JavaScript file, we need to unpack it to get its
|
|
46
|
-
// default export.
|
|
47
|
-
// @ts-ignore
|
|
48
|
-
extensionHandler = await extensionHandler.unpack();
|
|
49
|
-
}
|
|
50
|
-
// Update cache with actual handler
|
|
51
|
-
handlers[extension] = extensionHandler;
|
|
52
|
-
return extensionHandler;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Cache handler even if it's undefined so we don't look it up again
|
|
56
|
-
handlers[extension] = handlerPromise;
|
|
57
|
-
|
|
58
|
-
return handlerPromise;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
11
|
/**
|
|
62
12
|
* If the given value is packed (e.g., buffer) and the key is a string-like path
|
|
63
13
|
* that ends in an extension, search for a handler for that extension and, if
|
|
@@ -67,8 +17,13 @@ export async function getExtensionHandler(parent, extension) {
|
|
|
67
17
|
* @param {any} value
|
|
68
18
|
* @param {any} key
|
|
69
19
|
*/
|
|
70
|
-
export async function handleExtension(parent, value, key) {
|
|
71
|
-
if (
|
|
20
|
+
export async function handleExtension(parent, value, key, handlers) {
|
|
21
|
+
if (
|
|
22
|
+
handlers &&
|
|
23
|
+
isPacked(value) &&
|
|
24
|
+
isStringLike(key) &&
|
|
25
|
+
value.unpack === undefined
|
|
26
|
+
) {
|
|
72
27
|
const hasSlash = trailingSlash.has(key);
|
|
73
28
|
if (hasSlash) {
|
|
74
29
|
key = trailingSlash.remove(key);
|
|
@@ -82,8 +37,14 @@ export async function handleExtension(parent, value, key) {
|
|
|
82
37
|
? ".jsedocument"
|
|
83
38
|
: extension.extname(key);
|
|
84
39
|
if (extname) {
|
|
85
|
-
const
|
|
40
|
+
const handlerName = `${extname.slice(1)}.handler`;
|
|
41
|
+
let handler = await handlers.get(handlerName);
|
|
86
42
|
if (handler) {
|
|
43
|
+
if (isUnpackable(handler)) {
|
|
44
|
+
// The extension handler itself needs to be unpacked
|
|
45
|
+
handler = await handler.unpack();
|
|
46
|
+
}
|
|
47
|
+
|
|
87
48
|
if (hasSlash && handler.unpack) {
|
|
88
49
|
// Key like `data.json/` ends in slash -- unpack immediately
|
|
89
50
|
return handler.unpack(value, { key, parent });
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
export default {
|
|
12
|
+
AggregateError,
|
|
13
|
+
Array,
|
|
14
|
+
ArrayBuffer,
|
|
15
|
+
Atomics,
|
|
16
|
+
BigInt,
|
|
17
|
+
BigInt64Array,
|
|
18
|
+
BigUint64Array,
|
|
19
|
+
Boolean,
|
|
20
|
+
DataView,
|
|
21
|
+
Date,
|
|
22
|
+
Error,
|
|
23
|
+
EvalError,
|
|
24
|
+
FinalizationRegistry,
|
|
25
|
+
Float32Array,
|
|
26
|
+
Float64Array,
|
|
27
|
+
Function,
|
|
28
|
+
Infinity,
|
|
29
|
+
Int16Array,
|
|
30
|
+
Int32Array,
|
|
31
|
+
Int8Array,
|
|
32
|
+
Intl,
|
|
33
|
+
// @ts-ignore Iterator does exist despite what TypeScript thinks
|
|
34
|
+
Iterator,
|
|
35
|
+
JSON,
|
|
36
|
+
Map,
|
|
37
|
+
Math,
|
|
38
|
+
NaN,
|
|
39
|
+
Number,
|
|
40
|
+
Object,
|
|
41
|
+
Promise,
|
|
42
|
+
Proxy,
|
|
43
|
+
RangeError,
|
|
44
|
+
ReferenceError,
|
|
45
|
+
Reflect,
|
|
46
|
+
RegExp,
|
|
47
|
+
Set,
|
|
48
|
+
SharedArrayBuffer,
|
|
49
|
+
String,
|
|
50
|
+
Symbol,
|
|
51
|
+
SyntaxError,
|
|
52
|
+
TypeError,
|
|
53
|
+
URIError,
|
|
54
|
+
Uint16Array,
|
|
55
|
+
Uint32Array,
|
|
56
|
+
Uint8Array,
|
|
57
|
+
Uint8ClampedArray,
|
|
58
|
+
WeakMap,
|
|
59
|
+
WeakRef,
|
|
60
|
+
WeakSet,
|
|
61
|
+
decodeURI,
|
|
62
|
+
decodeURIComponent,
|
|
63
|
+
encodeURI,
|
|
64
|
+
encodeURIComponent,
|
|
65
|
+
eval,
|
|
66
|
+
false: false, // treat like a global
|
|
67
|
+
fetch: fetchWrapper, // special case
|
|
68
|
+
globalThis,
|
|
69
|
+
import: importWrapper, // not a function in JS but acts like one
|
|
70
|
+
isFinite,
|
|
71
|
+
isNaN,
|
|
72
|
+
null: null, // treat like a global
|
|
73
|
+
parseFloat,
|
|
74
|
+
parseInt,
|
|
75
|
+
true: true, // treat like a global
|
|
76
|
+
undefined,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
async function fetchWrapper(resource, options) {
|
|
80
|
+
const response = await fetch(resource, options);
|
|
81
|
+
return response.ok ? await response.arrayBuffer() : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @this {import("@weborigami/types").AsyncTree|null|undefined} */
|
|
85
|
+
async function importWrapper(modulePath) {
|
|
86
|
+
// Walk up parent tree looking for a FileTree or other object with a `path`
|
|
87
|
+
/** @type {any} */
|
|
88
|
+
let current = this;
|
|
89
|
+
while (current && !("path" in current)) {
|
|
90
|
+
current = current.parent;
|
|
91
|
+
}
|
|
92
|
+
if (!current) {
|
|
93
|
+
throw new TypeError(
|
|
94
|
+
"Modules can only be imported from a folder or other object with a path property."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const filePath = path.resolve(current.path, modulePath);
|
|
98
|
+
return import(filePath);
|
|
99
|
+
}
|
|
@@ -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);
|