@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
|
@@ -15,10 +15,14 @@ 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
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// Markers in compiled output, will get optimized away
|
|
19
|
+
export const markers = {
|
|
20
|
+
global: Symbol("global"), // Global reference
|
|
21
|
+
external: Symbol("external"), // External reference
|
|
22
|
+
property: Symbol("property"), // Property access
|
|
23
|
+
reference: Symbol("reference"), // Reference to local, scope, or global
|
|
24
|
+
traverse: Symbol("traverse"), // Path traversal
|
|
25
|
+
};
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* If a parse result is an object that will be evaluated at runtime, attach the
|
|
@@ -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,59 +53,20 @@ export function applyMacro(code, name, macro) {
|
|
|
49
53
|
return code;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return macro;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const applied = code.map((child) => applyMacro(child, name, macro));
|
|
58
|
-
return annotate(applied, code.location);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 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
|
|
64
|
-
* infinite recursion.
|
|
65
|
-
*
|
|
66
|
-
* @param {AnnotatedCode} code
|
|
67
|
-
* @param {string} key
|
|
68
|
-
*/
|
|
69
|
-
function avoidRecursivePropertyCalls(code, key) {
|
|
70
|
-
if (!(code instanceof Array)) {
|
|
71
|
-
return code;
|
|
72
|
-
}
|
|
73
|
-
/** @type {Code} */
|
|
74
|
-
let modified;
|
|
56
|
+
// We're looking for a function call with the given name.
|
|
57
|
+
// For `foo`, the call would be: [[markers.traverse, [markers.reference, "foo"]], undefined]
|
|
75
58
|
if (
|
|
76
|
-
code[0]
|
|
77
|
-
|
|
59
|
+
code[0] instanceof Array &&
|
|
60
|
+
code[0][0] === markers.traverse &&
|
|
61
|
+
code[0][1][0] === markers.reference &&
|
|
62
|
+
code[0][1][1] === name
|
|
78
63
|
) {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
} else if (
|
|
82
|
-
code[0] === ops.lambda &&
|
|
83
|
-
code[1].some((param) => param[1] === key)
|
|
84
|
-
) {
|
|
85
|
-
// Lambda that defines the key; don't rewrite
|
|
86
|
-
return code;
|
|
87
|
-
} else {
|
|
88
|
-
// Process any nested code
|
|
89
|
-
modified = code.map((value) => avoidRecursivePropertyCalls(value, key));
|
|
64
|
+
// Replace the call with the macro
|
|
65
|
+
return annotate(macro, code.location);
|
|
90
66
|
}
|
|
91
|
-
return annotate(modified, code.location);
|
|
92
|
-
}
|
|
93
67
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*
|
|
97
|
-
* @param {AnnotatedCode} code
|
|
98
|
-
*/
|
|
99
|
-
export function downgradeReference(code) {
|
|
100
|
-
if (code && code.length === 2 && code[0] === undetermined) {
|
|
101
|
-
return annotate([ops.scope, code[1]], code.location);
|
|
102
|
-
} else {
|
|
103
|
-
return code;
|
|
104
|
-
}
|
|
68
|
+
const applied = code.map((child) => applyMacro(child, name, macro));
|
|
69
|
+
return annotate(applied, code.location);
|
|
105
70
|
}
|
|
106
71
|
|
|
107
72
|
/**
|
|
@@ -139,7 +104,7 @@ export function makeArray(entries, location) {
|
|
|
139
104
|
|
|
140
105
|
let result;
|
|
141
106
|
if (spreads.length > 1) {
|
|
142
|
-
result = [ops.
|
|
107
|
+
result = [ops.flat, ...spreads];
|
|
143
108
|
} else if (spreads.length === 1) {
|
|
144
109
|
result = spreads[0];
|
|
145
110
|
} else {
|
|
@@ -196,7 +161,7 @@ export function makeBinaryOperation(left, [operatorToken, right]) {
|
|
|
196
161
|
* @param {AnnotatedCode} target
|
|
197
162
|
* @param {any[]} args
|
|
198
163
|
*/
|
|
199
|
-
export function makeCall(target, args) {
|
|
164
|
+
export function makeCall(target, args, location) {
|
|
200
165
|
if (!(target instanceof Array)) {
|
|
201
166
|
const error = new SyntaxError(`Can't call this like a function: ${target}`);
|
|
202
167
|
/** @type {any} */ (error).location = /** @type {any} */ (target).location;
|
|
@@ -204,53 +169,23 @@ export function makeCall(target, args) {
|
|
|
204
169
|
}
|
|
205
170
|
|
|
206
171
|
let fnCall;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (args.length > 1) {
|
|
220
|
-
// Regular traverse
|
|
221
|
-
const keys = args.slice(1);
|
|
222
|
-
fnCall = [ops.traverse, tree, ...keys];
|
|
223
|
-
} else {
|
|
224
|
-
// Traverse without arguments equates to unpack
|
|
225
|
-
fnCall = [ops.unpack, tree];
|
|
226
|
-
}
|
|
227
|
-
} else if (args[0] === ops.template) {
|
|
172
|
+
const op = args[0];
|
|
173
|
+
if (op === markers.traverse || op === ops.optionalTraverse) {
|
|
174
|
+
// Traverse
|
|
175
|
+
const keys = args.slice(1);
|
|
176
|
+
fnCall = [target, ...keys];
|
|
177
|
+
} else if (op === markers.property) {
|
|
178
|
+
// Property access
|
|
179
|
+
const property = args[1];
|
|
180
|
+
fnCall = [target, property];
|
|
181
|
+
} else if (op === ops.templateTree) {
|
|
228
182
|
// Tagged template
|
|
229
183
|
const strings = args[1];
|
|
230
184
|
const values = args.slice(2);
|
|
231
|
-
fnCall = makeTaggedTemplateCall(
|
|
232
|
-
upgradeReference(target),
|
|
233
|
-
strings,
|
|
234
|
-
...values
|
|
235
|
-
);
|
|
185
|
+
fnCall = makeTaggedTemplateCall(target, strings, ...values);
|
|
236
186
|
} else {
|
|
237
187
|
// Function call with explicit or implicit parentheses
|
|
238
|
-
fnCall = [
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Create a location spanning the newly-constructed function call.
|
|
242
|
-
const location = { ...target.location };
|
|
243
|
-
if (args instanceof Array) {
|
|
244
|
-
let end;
|
|
245
|
-
if ("location" in args) {
|
|
246
|
-
end = /** @type {any} */ (args).location.end;
|
|
247
|
-
} else if ("location" in args.at(-1)) {
|
|
248
|
-
end = args.at(-1).location.end;
|
|
249
|
-
}
|
|
250
|
-
if (end === undefined) {
|
|
251
|
-
throw "Internal parser error: no location for function call argument";
|
|
252
|
-
}
|
|
253
|
-
location.end = end;
|
|
188
|
+
fnCall = [target, ...args];
|
|
254
189
|
}
|
|
255
190
|
|
|
256
191
|
return annotate(fnCall, location);
|
|
@@ -275,6 +210,86 @@ export function makeDeferredArguments(args) {
|
|
|
275
210
|
});
|
|
276
211
|
}
|
|
277
212
|
|
|
213
|
+
export function makeDocument(front, body, location) {
|
|
214
|
+
// In order for template expressions to see the front matter properties,
|
|
215
|
+
// we translate the top-level front properties to object entries.
|
|
216
|
+
const entries = Object.entries(front).map(([key, value]) =>
|
|
217
|
+
annotate([key, annotate([ops.literal, value], location)], location)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Add an entry for the body
|
|
221
|
+
// TODO: Deprecate @text
|
|
222
|
+
entries.push(annotate(["(@text)", body], location));
|
|
223
|
+
entries.push(annotate(["_body", body], location));
|
|
224
|
+
|
|
225
|
+
// Return the code for the document object
|
|
226
|
+
return annotate([ops.object, ...entries], location);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* From the given spreads within an object spread, return the merge.
|
|
231
|
+
*
|
|
232
|
+
* Example:
|
|
233
|
+
*
|
|
234
|
+
* {
|
|
235
|
+
* x = { a: 1 }
|
|
236
|
+
* …x
|
|
237
|
+
* y = x
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* will be treated as:
|
|
241
|
+
*
|
|
242
|
+
* {
|
|
243
|
+
* x = { a: 1 }
|
|
244
|
+
* y = x
|
|
245
|
+
* _result: {
|
|
246
|
+
* x
|
|
247
|
+
* …x
|
|
248
|
+
* y
|
|
249
|
+
* }
|
|
250
|
+
* }.result
|
|
251
|
+
*
|
|
252
|
+
* @param {*} spreads
|
|
253
|
+
* @param {CodeLocation} location
|
|
254
|
+
*/
|
|
255
|
+
function makeMerge(spreads, location) {
|
|
256
|
+
const topEntries = [];
|
|
257
|
+
const resultEntries = [];
|
|
258
|
+
for (const spread of spreads) {
|
|
259
|
+
if (spread[0] === ops.object) {
|
|
260
|
+
topEntries.push(...spread.slice(1));
|
|
261
|
+
// Also add an object to the result with indirect references
|
|
262
|
+
const indirectEntries = spread.slice(1).map((entry) => {
|
|
263
|
+
const [key] = entry;
|
|
264
|
+
const context = annotate([ops.context, 1], entry.location);
|
|
265
|
+
const reference = annotate([context, key], entry.location);
|
|
266
|
+
const getter = annotate([ops.getter, reference], entry.location);
|
|
267
|
+
return annotate([key, getter], entry.location);
|
|
268
|
+
});
|
|
269
|
+
const indirectObject = annotate(
|
|
270
|
+
[ops.object, ...indirectEntries],
|
|
271
|
+
location
|
|
272
|
+
);
|
|
273
|
+
resultEntries.push(indirectObject);
|
|
274
|
+
} else {
|
|
275
|
+
resultEntries.push(spread);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Merge to create result
|
|
280
|
+
const result = annotate([ops.merge, ...resultEntries], location);
|
|
281
|
+
|
|
282
|
+
// Add the result to the top-level object as _result
|
|
283
|
+
topEntries.push(annotate(["_result", result], location));
|
|
284
|
+
|
|
285
|
+
// Construct the top-level object
|
|
286
|
+
const topObject = annotate([ops.object, ...topEntries], location);
|
|
287
|
+
|
|
288
|
+
// Get the _result property
|
|
289
|
+
const code = annotate([topObject, "_result"], location);
|
|
290
|
+
return code;
|
|
291
|
+
}
|
|
292
|
+
|
|
278
293
|
/**
|
|
279
294
|
* Make an object.
|
|
280
295
|
*
|
|
@@ -331,7 +346,7 @@ export function makeObject(entries, location) {
|
|
|
331
346
|
let code;
|
|
332
347
|
if (spreads.length > 1) {
|
|
333
348
|
// Merge multiple spreads
|
|
334
|
-
code =
|
|
349
|
+
code = makeMerge(spreads, location);
|
|
335
350
|
} else if (spreads.length === 1) {
|
|
336
351
|
// A single spread can just be the object
|
|
337
352
|
code = spreads[0];
|
|
@@ -343,51 +358,47 @@ export function makeObject(entries, location) {
|
|
|
343
358
|
return annotate(code, location);
|
|
344
359
|
}
|
|
345
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Handle a path with one or more segments separated by slashes.
|
|
363
|
+
*
|
|
364
|
+
* @param {AnnotatedCode} keys
|
|
365
|
+
*/
|
|
366
|
+
export function makePath(keys) {
|
|
367
|
+
// Remove empty segments
|
|
368
|
+
const args = keys.filter(
|
|
369
|
+
(key, index) => index === 0 || (key[1] !== "" && key[1] !== "/")
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Upgrade head to a reference
|
|
373
|
+
const [head, ...tail] = args;
|
|
374
|
+
const headKey = head[1];
|
|
375
|
+
const reference = annotate([markers.reference, headKey], head.location);
|
|
376
|
+
|
|
377
|
+
let code = [markers.traverse, reference, ...tail];
|
|
378
|
+
code.location = spanLocations(code);
|
|
379
|
+
|
|
380
|
+
// Last key has trailing slash implies unpack operation
|
|
381
|
+
if (trailingSlash.has(args.at(-1)[1])) {
|
|
382
|
+
code = annotate([ops.unpack, code], code.location);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return code;
|
|
386
|
+
}
|
|
387
|
+
|
|
346
388
|
/**
|
|
347
389
|
* Make a pipline: similar to a function call, but the order is reversed.
|
|
348
390
|
*
|
|
349
391
|
* @param {AnnotatedCode} arg
|
|
350
392
|
* @param {AnnotatedCode} fn
|
|
351
393
|
*/
|
|
352
|
-
export function makePipeline(arg, fn) {
|
|
353
|
-
const
|
|
354
|
-
const result = makeCall(upgraded, [arg]);
|
|
394
|
+
export function makePipeline(arg, fn, location) {
|
|
395
|
+
const result = makeCall(fn, [arg], location);
|
|
355
396
|
const source = fn.location.source;
|
|
356
397
|
let start = arg.location.start;
|
|
357
398
|
let end = fn.location.end;
|
|
358
399
|
return annotate(result, { start, source, end });
|
|
359
400
|
}
|
|
360
401
|
|
|
361
|
-
// Define a property on an object.
|
|
362
|
-
export function makeProperty(key, value) {
|
|
363
|
-
const modified = avoidRecursivePropertyCalls(value, key);
|
|
364
|
-
return [key, modified];
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function makeJsPropertyAccess(expression, property) {
|
|
368
|
-
const location = {
|
|
369
|
-
source: expression.location.source,
|
|
370
|
-
start: expression.location.start,
|
|
371
|
-
end: property.location.end,
|
|
372
|
-
};
|
|
373
|
-
return annotate([expression, property], location);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export function makeReference(identifier) {
|
|
377
|
-
// We can't know for sure that an identifier is a builtin reference until we
|
|
378
|
-
// see whether it's being called as a function.
|
|
379
|
-
let op;
|
|
380
|
-
if (builtinRegex.test(identifier)) {
|
|
381
|
-
op = identifier.endsWith(":")
|
|
382
|
-
? // Namespace is always a builtin reference
|
|
383
|
-
ops.builtin
|
|
384
|
-
: undetermined;
|
|
385
|
-
} else {
|
|
386
|
-
op = ops.scope;
|
|
387
|
-
}
|
|
388
|
-
return [op, identifier];
|
|
389
|
-
}
|
|
390
|
-
|
|
391
402
|
/**
|
|
392
403
|
* Make a tagged template call
|
|
393
404
|
*
|
|
@@ -495,19 +506,17 @@ export function makeYamlObject(text, location) {
|
|
|
495
506
|
throw error;
|
|
496
507
|
}
|
|
497
508
|
|
|
498
|
-
return
|
|
509
|
+
return parsed;
|
|
499
510
|
}
|
|
500
511
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
return code;
|
|
512
|
-
}
|
|
512
|
+
// Create a locations that spans those in the array. This assumes the locations
|
|
513
|
+
// are in order and non-overlapping.
|
|
514
|
+
function spanLocations(code) {
|
|
515
|
+
const first = code.find((item) => item.location).location;
|
|
516
|
+
const last = code[code.findLastIndex((item) => item.location)].location;
|
|
517
|
+
return {
|
|
518
|
+
source: first.source,
|
|
519
|
+
start: first.start,
|
|
520
|
+
end: last.end,
|
|
521
|
+
};
|
|
513
522
|
}
|
|
@@ -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) {
|
|
@@ -136,17 +136,23 @@ export default async function expressionObject(entries, parent) {
|
|
|
136
136
|
return object;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function entryKey(object, eagerProperties
|
|
140
|
-
|
|
139
|
+
export function entryKey(entry, object = null, eagerProperties = []) {
|
|
140
|
+
let [key, value] = entry;
|
|
141
|
+
|
|
142
|
+
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
143
|
+
// Non-enumerable property, remove parentheses. This doesn't come up in the
|
|
144
|
+
// constructor, but can happen in situations encountered by the compiler's
|
|
145
|
+
// optimizer.
|
|
146
|
+
key = key.slice(1, -1);
|
|
147
|
+
}
|
|
141
148
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Return key as is
|
|
149
|
+
if (trailingSlash.has(key)) {
|
|
150
|
+
// Explicit trailing slash, return as is
|
|
145
151
|
return key;
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
// If eager property value is treelike, add slash to the key
|
|
149
|
-
if (eagerProperties.includes(key) && Tree.isTreelike(object[key])) {
|
|
155
|
+
if (eagerProperties.includes(key) && Tree.isTreelike(object?.[key])) {
|
|
150
156
|
return trailingSlash.add(key);
|
|
151
157
|
}
|
|
152
158
|
|
|
@@ -163,5 +169,5 @@ function entryKey(object, eagerProperties, entry) {
|
|
|
163
169
|
function keys(object, eagerProperties, propertyIsEnumerable, entries) {
|
|
164
170
|
return entries
|
|
165
171
|
.filter(([key]) => propertyIsEnumerable[key])
|
|
166
|
-
.map((entry) => entryKey(object, eagerProperties
|
|
172
|
+
.map((entry) => entryKey(entry, object, eagerProperties));
|
|
167
173
|
}
|
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,20 +17,34 @@ 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);
|
|
75
30
|
}
|
|
76
31
|
|
|
77
|
-
// Special
|
|
32
|
+
// Special cases: `.ori.<ext>` extensions are Origami documents,
|
|
33
|
+
// `.jse.<ext>` are JSE documents.
|
|
78
34
|
const extname = key.match(/\.ori\.\S+$/)
|
|
79
35
|
? ".oridocument"
|
|
36
|
+
: key.match(/\.jse\.\S+$/)
|
|
37
|
+
? ".jsedocument"
|
|
80
38
|
: extension.extname(key);
|
|
81
39
|
if (extname) {
|
|
82
|
-
const
|
|
40
|
+
const handlerName = `${extname.slice(1)}.handler`;
|
|
41
|
+
let handler = await handlers[handlerName];
|
|
83
42
|
if (handler) {
|
|
43
|
+
if (isUnpackable(handler)) {
|
|
44
|
+
// The extension handler itself needs to be unpacked
|
|
45
|
+
handler = await handler.unpack();
|
|
46
|
+
}
|
|
47
|
+
|
|
84
48
|
if (hasSlash && handler.unpack) {
|
|
85
49
|
// Key like `data.json/` ends in slash -- unpack immediately
|
|
86
50
|
return handler.unpack(value, { key, parent });
|