@weborigami/language 0.0.45 → 0.0.47
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 +15 -9
- package/main.js +1 -2
- package/package.json +4 -4
- package/src/compiler/parse.js +113 -29
- package/src/runtime/HandleExtensionsTransform.d.ts +5 -0
- package/src/runtime/{FileLoadersTransform.js → HandleExtensionsTransform.js} +5 -5
- package/src/runtime/ImportModulesMixin.js +12 -5
- package/src/runtime/InvokeFunctionsTransform.js +6 -0
- package/src/runtime/OrigamiTransform.d.ts +1 -1
- package/src/runtime/OrigamiTransform.js +4 -2
- package/src/runtime/Scope.js +13 -6
- package/src/runtime/concatTreeValues.js +2 -15
- package/src/runtime/evaluate.js +3 -6
- package/src/runtime/functionResultsMap.js +1 -1
- package/src/runtime/handleExtension.js +56 -0
- package/src/runtime/ops.js +7 -7
- package/test/compiler/parse.test.js +2 -2
- package/test/runtime/{FileLoadersTransform.test.js → HandleExtensionsTransform.test.js} +7 -7
- package/test/runtime/evaluate.test.js +2 -3
- package/src/runtime/FileLoadersTransform.d.ts +0 -5
- package/src/runtime/attachFileLoader.js +0 -31
- package/src/runtime/symbols.js +0 -1
- /package/src/runtime/{InheritScopeMixin.ts → InheritScopeMixin.d.ts} +0 -0
package/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Packed } from "@weborigami/async-tree";
|
|
1
2
|
|
|
2
3
|
export * from "./main.js";
|
|
3
4
|
|
|
@@ -17,13 +18,13 @@ export type Code = ArrayWithSource;
|
|
|
17
18
|
export type Constructor<T> = new (...args: any[]) => T;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* A
|
|
21
|
-
*
|
|
21
|
+
* A structure associating a media type and an unpack function with a given file
|
|
22
|
+
* extension.
|
|
22
23
|
*/
|
|
23
|
-
export type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
export type ExtensionHandler = {
|
|
25
|
+
mediaType?: string;
|
|
26
|
+
unpack?: UnpackFunction;
|
|
27
|
+
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* A mixin is a function that takes an existing class and returns a new class.
|
|
@@ -39,9 +40,14 @@ export type Mixin<MixinMembers> = <T>(
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Source code representation used by the parser.
|
|
42
|
-
|
|
43
|
+
*/
|
|
43
44
|
export type Source = {
|
|
44
45
|
name: string;
|
|
45
46
|
text: string;
|
|
46
|
-
url: URL;
|
|
47
|
-
}
|
|
47
|
+
url: URL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A function that converts a value from a persistent form into a live value.
|
|
52
|
+
*/
|
|
53
|
+
export type UnpackFunction = (input: Packed, options?: any) => any;
|
package/main.js
CHANGED
|
@@ -3,7 +3,7 @@ export * from "./src/runtime/internal.js";
|
|
|
3
3
|
export * as compile from "./src/compiler/compile.js";
|
|
4
4
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
|
5
5
|
export { default as ExpressionTree } from "./src/runtime/ExpressionTree.js";
|
|
6
|
-
export { default as
|
|
6
|
+
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
|
7
7
|
export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
|
|
8
8
|
export { default as InheritScopeMixin } from "./src/runtime/InheritScopeMixin.js";
|
|
9
9
|
export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
|
|
@@ -19,4 +19,3 @@ export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
|
19
19
|
export { default as extname } from "./src/runtime/extname.js";
|
|
20
20
|
export { default as formatError } from "./src/runtime/formatError.js";
|
|
21
21
|
export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
|
|
22
|
-
export * as symbols from "./src/runtime/symbols.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"typescript": "5.3.3"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@weborigami/async-tree": "0.0.
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
15
|
-
"peggy": "
|
|
13
|
+
"@weborigami/async-tree": "0.0.47",
|
|
14
|
+
"@weborigami/types": "0.0.47",
|
|
15
|
+
"peggy": "4.0.2",
|
|
16
16
|
"watcher": "2.3.0"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
package/src/compiler/parse.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
//
|
|
1
|
+
// @generated by Peggy 4.0.2.
|
|
2
2
|
//
|
|
3
3
|
// https://peggyjs.org/
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
//
|
|
7
8
|
// Origami language parser
|
|
8
9
|
//
|
|
@@ -406,16 +407,16 @@ function peg$parse(input, options) {
|
|
|
406
407
|
var peg$f39 = function(assignments) {
|
|
407
408
|
return annotate([ops.tree, ...(assignments ?? [])], location());
|
|
408
409
|
};
|
|
409
|
-
var peg$currPos = 0;
|
|
410
|
-
var peg$savedPos =
|
|
410
|
+
var peg$currPos = options.peg$currPos | 0;
|
|
411
|
+
var peg$savedPos = peg$currPos;
|
|
411
412
|
var peg$posDetailsCache = [{ line: 1, column: 1 }];
|
|
412
|
-
var peg$maxFailPos =
|
|
413
|
-
var peg$maxFailExpected = [];
|
|
414
|
-
var peg$silentFails = 0;
|
|
413
|
+
var peg$maxFailPos = peg$currPos;
|
|
414
|
+
var peg$maxFailExpected = options.peg$maxFailExpected || [];
|
|
415
|
+
var peg$silentFails = options.peg$silentFails | 0;
|
|
415
416
|
|
|
416
417
|
var peg$result;
|
|
417
418
|
|
|
418
|
-
if (
|
|
419
|
+
if (options.startRule) {
|
|
419
420
|
if (!(options.startRule in peg$startRuleFunctions)) {
|
|
420
421
|
throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
|
|
421
422
|
}
|
|
@@ -490,9 +491,11 @@ function peg$parse(input, options) {
|
|
|
490
491
|
if (details) {
|
|
491
492
|
return details;
|
|
492
493
|
} else {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
494
|
+
if (pos >= peg$posDetailsCache.length) {
|
|
495
|
+
p = peg$posDetailsCache.length - 1;
|
|
496
|
+
} else {
|
|
497
|
+
p = pos;
|
|
498
|
+
while (!peg$posDetailsCache[--p]) {}
|
|
496
499
|
}
|
|
497
500
|
|
|
498
501
|
details = peg$posDetailsCache[p];
|
|
@@ -909,8 +912,8 @@ function peg$parse(input, options) {
|
|
|
909
912
|
|
|
910
913
|
s0 = peg$currPos;
|
|
911
914
|
s1 = [];
|
|
912
|
-
|
|
913
|
-
|
|
915
|
+
s2 = input.charAt(peg$currPos);
|
|
916
|
+
if (peg$r0.test(s2)) {
|
|
914
917
|
peg$currPos++;
|
|
915
918
|
} else {
|
|
916
919
|
s2 = peg$FAILED;
|
|
@@ -919,8 +922,8 @@ function peg$parse(input, options) {
|
|
|
919
922
|
if (s2 !== peg$FAILED) {
|
|
920
923
|
while (s2 !== peg$FAILED) {
|
|
921
924
|
s1.push(s2);
|
|
922
|
-
|
|
923
|
-
|
|
925
|
+
s2 = input.charAt(peg$currPos);
|
|
926
|
+
if (peg$r0.test(s2)) {
|
|
924
927
|
peg$currPos++;
|
|
925
928
|
} else {
|
|
926
929
|
s2 = peg$FAILED;
|
|
@@ -1311,8 +1314,8 @@ function peg$parse(input, options) {
|
|
|
1311
1314
|
function peg$parseidentifierChar() {
|
|
1312
1315
|
var s0, s1, s2, s3;
|
|
1313
1316
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1317
|
+
s0 = input.charAt(peg$currPos);
|
|
1318
|
+
if (peg$r1.test(s0)) {
|
|
1316
1319
|
peg$currPos++;
|
|
1317
1320
|
} else {
|
|
1318
1321
|
s0 = peg$FAILED;
|
|
@@ -1444,8 +1447,8 @@ function peg$parse(input, options) {
|
|
|
1444
1447
|
function peg$parseinlineSpace() {
|
|
1445
1448
|
var s0;
|
|
1446
1449
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1450
|
+
s0 = input.charAt(peg$currPos);
|
|
1451
|
+
if (peg$r2.test(s0)) {
|
|
1449
1452
|
peg$currPos++;
|
|
1450
1453
|
} else {
|
|
1451
1454
|
s0 = peg$FAILED;
|
|
@@ -2361,8 +2364,8 @@ function peg$parse(input, options) {
|
|
|
2361
2364
|
function peg$parsesign() {
|
|
2362
2365
|
var s0;
|
|
2363
2366
|
|
|
2364
|
-
|
|
2365
|
-
|
|
2367
|
+
s0 = input.charAt(peg$currPos);
|
|
2368
|
+
if (peg$r3.test(s0)) {
|
|
2366
2369
|
peg$currPos++;
|
|
2367
2370
|
} else {
|
|
2368
2371
|
s0 = peg$FAILED;
|
|
@@ -2408,8 +2411,8 @@ function peg$parse(input, options) {
|
|
|
2408
2411
|
}
|
|
2409
2412
|
if (s1 !== peg$FAILED) {
|
|
2410
2413
|
s2 = [];
|
|
2411
|
-
|
|
2412
|
-
|
|
2414
|
+
s3 = input.charAt(peg$currPos);
|
|
2415
|
+
if (peg$r4.test(s3)) {
|
|
2413
2416
|
peg$currPos++;
|
|
2414
2417
|
} else {
|
|
2415
2418
|
s3 = peg$FAILED;
|
|
@@ -2417,8 +2420,8 @@ function peg$parse(input, options) {
|
|
|
2417
2420
|
}
|
|
2418
2421
|
while (s3 !== peg$FAILED) {
|
|
2419
2422
|
s2.push(s3);
|
|
2420
|
-
|
|
2421
|
-
|
|
2423
|
+
s3 = input.charAt(peg$currPos);
|
|
2424
|
+
if (peg$r4.test(s3)) {
|
|
2422
2425
|
peg$currPos++;
|
|
2423
2426
|
} else {
|
|
2424
2427
|
s3 = peg$FAILED;
|
|
@@ -2442,8 +2445,8 @@ function peg$parse(input, options) {
|
|
|
2442
2445
|
}
|
|
2443
2446
|
if (s1 !== peg$FAILED) {
|
|
2444
2447
|
s2 = [];
|
|
2445
|
-
|
|
2446
|
-
|
|
2448
|
+
s3 = input.charAt(peg$currPos);
|
|
2449
|
+
if (peg$r4.test(s3)) {
|
|
2447
2450
|
peg$currPos++;
|
|
2448
2451
|
} else {
|
|
2449
2452
|
s3 = peg$FAILED;
|
|
@@ -2451,8 +2454,8 @@ function peg$parse(input, options) {
|
|
|
2451
2454
|
}
|
|
2452
2455
|
while (s3 !== peg$FAILED) {
|
|
2453
2456
|
s2.push(s3);
|
|
2454
|
-
|
|
2455
|
-
|
|
2457
|
+
s3 = input.charAt(peg$currPos);
|
|
2458
|
+
if (peg$r4.test(s3)) {
|
|
2456
2459
|
peg$currPos++;
|
|
2457
2460
|
} else {
|
|
2458
2461
|
s3 = peg$FAILED;
|
|
@@ -3085,6 +3088,15 @@ function peg$parse(input, options) {
|
|
|
3085
3088
|
|
|
3086
3089
|
peg$result = peg$startRuleFunction();
|
|
3087
3090
|
|
|
3091
|
+
if (options.peg$library) {
|
|
3092
|
+
return /** @type {any} */ ({
|
|
3093
|
+
peg$result,
|
|
3094
|
+
peg$currPos,
|
|
3095
|
+
peg$FAILED,
|
|
3096
|
+
peg$maxFailExpected,
|
|
3097
|
+
peg$maxFailPos
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3088
3100
|
if (peg$result !== peg$FAILED && peg$currPos === input.length) {
|
|
3089
3101
|
return peg$result;
|
|
3090
3102
|
} else {
|
|
@@ -3102,8 +3114,80 @@ function peg$parse(input, options) {
|
|
|
3102
3114
|
}
|
|
3103
3115
|
}
|
|
3104
3116
|
|
|
3117
|
+
const peg$allowedStartRules = [
|
|
3118
|
+
"__",
|
|
3119
|
+
"absoluteFilePath",
|
|
3120
|
+
"args",
|
|
3121
|
+
"array",
|
|
3122
|
+
"assignment",
|
|
3123
|
+
"assignmentOrShorthand",
|
|
3124
|
+
"callTarget",
|
|
3125
|
+
"closingBrace",
|
|
3126
|
+
"closingBracket",
|
|
3127
|
+
"closingParen",
|
|
3128
|
+
"comment",
|
|
3129
|
+
"digits",
|
|
3130
|
+
"doubleArrow",
|
|
3131
|
+
"doubleQuoteString",
|
|
3132
|
+
"doubleQuoteStringChar",
|
|
3133
|
+
"escapedChar",
|
|
3134
|
+
"expr",
|
|
3135
|
+
"expression",
|
|
3136
|
+
"float",
|
|
3137
|
+
"functionComposition",
|
|
3138
|
+
"group",
|
|
3139
|
+
"host",
|
|
3140
|
+
"identifier",
|
|
3141
|
+
"identifierChar",
|
|
3142
|
+
"identifierList",
|
|
3143
|
+
"implicitParensArgs",
|
|
3144
|
+
"inlineSpace",
|
|
3145
|
+
"integer",
|
|
3146
|
+
"lambda",
|
|
3147
|
+
"leadingSlashPath",
|
|
3148
|
+
"list",
|
|
3149
|
+
"multiLineComment",
|
|
3150
|
+
"newLine",
|
|
3151
|
+
"number",
|
|
3152
|
+
"object",
|
|
3153
|
+
"objectProperties",
|
|
3154
|
+
"objectProperty",
|
|
3155
|
+
"objectPropertyOrShorthand",
|
|
3156
|
+
"parameterizedLambda",
|
|
3157
|
+
"parensArgs",
|
|
3158
|
+
"pipeline",
|
|
3159
|
+
"path",
|
|
3160
|
+
"pathKey",
|
|
3161
|
+
"protocolCall",
|
|
3162
|
+
"protocol",
|
|
3163
|
+
"reservedProtocol",
|
|
3164
|
+
"scopeReference",
|
|
3165
|
+
"separator",
|
|
3166
|
+
"sign",
|
|
3167
|
+
"singleArrow",
|
|
3168
|
+
"singleLineComment",
|
|
3169
|
+
"singleQuoteString",
|
|
3170
|
+
"singleQuoteStringChar",
|
|
3171
|
+
"step",
|
|
3172
|
+
"start",
|
|
3173
|
+
"string",
|
|
3174
|
+
"templateDocument",
|
|
3175
|
+
"templateDocumentChar",
|
|
3176
|
+
"templateDocumentContents",
|
|
3177
|
+
"templateDocumentText",
|
|
3178
|
+
"templateLiteral",
|
|
3179
|
+
"templateLiteralChar",
|
|
3180
|
+
"templateLiteralContents",
|
|
3181
|
+
"templateLiteralText",
|
|
3182
|
+
"templateSubstitution",
|
|
3183
|
+
"textChar",
|
|
3184
|
+
"tree",
|
|
3185
|
+
"treeAssignments",
|
|
3186
|
+
"whitespaceWithNewLine"
|
|
3187
|
+
];
|
|
3188
|
+
|
|
3105
3189
|
export {
|
|
3190
|
+
peg$allowedStartRules as StartRules,
|
|
3106
3191
|
peg$SyntaxError as SyntaxError,
|
|
3107
|
-
|
|
3108
3192
|
peg$parse as parse
|
|
3109
3193
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { isStringLike } from "@weborigami/async-tree";
|
|
2
2
|
import Scope from "./Scope.js";
|
|
3
|
-
import
|
|
3
|
+
import handleExtension from "./handleExtension.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
-
* @typedef {import("../../index.
|
|
8
|
-
* @typedef {import("../../index.
|
|
7
|
+
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
8
|
+
* @typedef {import("../../index.ts").UnpackFunction} FileUnpackFunction
|
|
9
9
|
*
|
|
10
10
|
* @param {AsyncTreeConstructor} Base
|
|
11
11
|
*/
|
|
12
|
-
export default function
|
|
12
|
+
export default function HandleExtensionsTransform(Base) {
|
|
13
13
|
return class FileLoaders extends Base {
|
|
14
14
|
async get(key) {
|
|
15
15
|
let value = await super.get(key);
|
|
@@ -18,7 +18,7 @@ export default function FileLoadersTransform(Base) {
|
|
|
18
18
|
// exists) that handles that extension.
|
|
19
19
|
if (value && isStringLike(key)) {
|
|
20
20
|
const scope = Scope.getScope(this);
|
|
21
|
-
value = await
|
|
21
|
+
value = await handleExtension(scope, String(key), value, this);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return value;
|
|
@@ -32,17 +32,24 @@ export default function ImportModulesMixin(Base) {
|
|
|
32
32
|
// Ignore errors.
|
|
33
33
|
}
|
|
34
34
|
if (stats) {
|
|
35
|
-
// Module exists, but we can't load it
|
|
36
|
-
|
|
35
|
+
// Module exists, but we can't load it. This is often due to a syntax
|
|
36
|
+
// error in the target module, so we offer that as a hint.
|
|
37
|
+
const message = `Error loading ${filePath}, possibly due to a syntax error.\n${error.message}`;
|
|
38
|
+
throw new SyntaxError(message);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
// Module doesn't exist.
|
|
40
42
|
return undefined;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
if ("default" in obj) {
|
|
46
|
+
// Module with a default export; return that.
|
|
47
|
+
return obj.default;
|
|
48
|
+
} else {
|
|
49
|
+
// Module with multiple named exports. Cast from a module namespace
|
|
50
|
+
// object to a plain object.
|
|
51
|
+
return { ...obj };
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
54
|
};
|
|
48
55
|
}
|
|
@@ -23,5 +23,11 @@ export default function InvokeFunctionsTransform(Base) {
|
|
|
23
23
|
}
|
|
24
24
|
return value;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
// Need to evaluate the value before checking if it is a tree.
|
|
28
|
+
async isKeyForSubtree(key) {
|
|
29
|
+
const value = await this.get(key);
|
|
30
|
+
return Tree.isAsyncTree(value);
|
|
31
|
+
}
|
|
26
32
|
};
|
|
27
33
|
}
|
|
@@ -3,7 +3,7 @@ import { Mixin } from "../../index.ts";
|
|
|
3
3
|
import type { AsyncTree } from "@weborigami/types";
|
|
4
4
|
|
|
5
5
|
// TODO: Figure out how to import declarations from InheritScopeMixin and
|
|
6
|
-
//
|
|
6
|
+
// HandleExtensionsTransform and apply them here.
|
|
7
7
|
declare const OrigamiTransform: Mixin<{
|
|
8
8
|
scope: AsyncTree|null;
|
|
9
9
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
2
2
|
import InheritScopeMixin from "./InheritScopeMixin.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -7,5 +7,7 @@ import InheritScopeMixin from "./InheritScopeMixin.js";
|
|
|
7
7
|
* @param {AsyncTreeConstructor} Base
|
|
8
8
|
*/
|
|
9
9
|
export default function OrigamiTransform(Base) {
|
|
10
|
-
return class Origami extends InheritScopeMixin(
|
|
10
|
+
return class Origami extends InheritScopeMixin(
|
|
11
|
+
HandleExtensionsTransform(Base)
|
|
12
|
+
) {};
|
|
11
13
|
}
|
package/src/runtime/Scope.js
CHANGED
|
@@ -38,13 +38,20 @@ export default class Scope {
|
|
|
38
38
|
static getScope(tree) {
|
|
39
39
|
if (!tree) {
|
|
40
40
|
return null;
|
|
41
|
-
} else if (
|
|
42
|
-
|
|
43
|
-
} else if (Tree.isAsyncTree(tree)) {
|
|
44
|
-
return new Scope(tree, this.getScope(tree.parent));
|
|
45
|
-
} else {
|
|
46
|
-
return tree;
|
|
41
|
+
} else if (!Tree.isAsyncTree(tree)) {
|
|
42
|
+
throw new Error("Tried to get the scope of something that's not a tree.");
|
|
47
43
|
}
|
|
44
|
+
|
|
45
|
+
if ("scope" in tree) {
|
|
46
|
+
// Ask tree for its scope, use that if defined.
|
|
47
|
+
const scope = /** @type {any} */ (tree).scope;
|
|
48
|
+
if (scope) {
|
|
49
|
+
return scope;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Default scope is tree plus its parent scope.
|
|
54
|
+
return new Scope(tree, this.getScope(tree.parent));
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
async keys() {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Tree,
|
|
3
|
-
getRealmObjectPrototype,
|
|
4
|
-
isPlainObject,
|
|
5
|
-
} from "@weborigami/async-tree";
|
|
1
|
+
import { Tree, getRealmObjectPrototype } from "@weborigami/async-tree";
|
|
6
2
|
|
|
7
3
|
const textDecoder = new TextDecoder();
|
|
8
4
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
@@ -31,16 +27,7 @@ async function getText(value, scope) {
|
|
|
31
27
|
value = await value.call(scope);
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
// an unpack() function as a treelike object. In this case, we don't want to
|
|
36
|
-
// unpack anything that's not already treelike.
|
|
37
|
-
const isTreelike =
|
|
38
|
-
Tree.isAsyncTree(value) ||
|
|
39
|
-
value instanceof Function ||
|
|
40
|
-
value instanceof Array ||
|
|
41
|
-
value instanceof Set ||
|
|
42
|
-
isPlainObject(value);
|
|
43
|
-
if (isTreelike) {
|
|
30
|
+
if (Tree.isTreelike(value)) {
|
|
44
31
|
// The mapReduce operation above only implicit casts its top-level input to
|
|
45
32
|
// a tree. If we're asked for the text of a treelike value, we need to
|
|
46
33
|
// explicitly recurse.
|
package/src/runtime/evaluate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tree, isPlainObject } from "@weborigami/async-tree";
|
|
1
|
+
import { Tree, isPlainObject, isUnpackable } from "@weborigami/async-tree";
|
|
2
2
|
import { ops } from "./internal.js";
|
|
3
3
|
|
|
4
4
|
const codeSymbol = Symbol("code");
|
|
@@ -45,17 +45,14 @@ export default async function evaluate(code) {
|
|
|
45
45
|
throw ReferenceError(`${codeFragment(code[0])} is not defined`);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if (
|
|
49
|
-
!(fn instanceof Function || Tree.isAsyncTree(fn)) &&
|
|
50
|
-
typeof fn.unpack === "function"
|
|
51
|
-
) {
|
|
48
|
+
if (isUnpackable(fn)) {
|
|
52
49
|
// Unpack the object and use the result as the function or tree.
|
|
53
50
|
fn = await fn.unpack();
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
if (!Tree.isTreelike(fn)) {
|
|
57
54
|
throw TypeError(
|
|
58
|
-
`${codeFragment(code[0])} didn't return a function
|
|
55
|
+
`${codeFragment(code[0])} didn't return a function or a treelike object`
|
|
59
56
|
);
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -11,7 +11,7 @@ export default function functionResultsMap(tree) {
|
|
|
11
11
|
return map({
|
|
12
12
|
description: "functionResultsMap",
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
value: async (sourceValue, sourceKey, tree) => {
|
|
15
15
|
let resultValue;
|
|
16
16
|
if (typeof sourceValue === "function") {
|
|
17
17
|
const scope = Scope.getScope(tree);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isUnpackable, symbols } from "@weborigami/async-tree";
|
|
2
|
+
import extname from "./extname.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Given a value that was retrieved using the given key, search in scope for a
|
|
6
|
+
* handler for the file extension on the key (if present). If a handler is
|
|
7
|
+
* found, attach information from it to the value and return the modified value.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
*
|
|
11
|
+
* @param {AsyncTree|null} scope
|
|
12
|
+
* @param {any} key
|
|
13
|
+
* @param {any} value
|
|
14
|
+
* @param {AsyncTree|null} parent
|
|
15
|
+
*/
|
|
16
|
+
export default async function handleExtension(scope, key, value, parent) {
|
|
17
|
+
const extension = extname(key);
|
|
18
|
+
let result = value;
|
|
19
|
+
if (extension) {
|
|
20
|
+
const handlerName = `${extension.slice(1)}_handler`;
|
|
21
|
+
/** @type {import("../../index.ts").ExtensionHandler} */
|
|
22
|
+
let extensionHandler = await scope?.get(handlerName);
|
|
23
|
+
if (isUnpackable(extensionHandler)) {
|
|
24
|
+
// The extension handler itself needs to be unpacked. E.g., if it's a
|
|
25
|
+
// buffer containing JavaScript file, we need to unpack it to get its
|
|
26
|
+
// default export.
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
extensionHandler = await extensionHandler.unpack();
|
|
29
|
+
}
|
|
30
|
+
if (extensionHandler) {
|
|
31
|
+
const input = value;
|
|
32
|
+
|
|
33
|
+
// If the result is a plain string, box it as a String so we can attach
|
|
34
|
+
// data to it.
|
|
35
|
+
if (typeof result === "string") {
|
|
36
|
+
result = new String(result);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (extensionHandler.mediaType) {
|
|
40
|
+
result.mediaType = extensionHandler.mediaType;
|
|
41
|
+
}
|
|
42
|
+
result[symbols.parent] = parent;
|
|
43
|
+
|
|
44
|
+
const unpack = extensionHandler.unpack;
|
|
45
|
+
if (unpack) {
|
|
46
|
+
// Wrap the unpack function so its only called once per value.
|
|
47
|
+
let loaded;
|
|
48
|
+
result.unpack = async () => {
|
|
49
|
+
loaded ??= await unpack(input, { key, parent });
|
|
50
|
+
return loaded;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
package/src/runtime/ops.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { SiteTree, Tree } from "@weborigami/async-tree";
|
|
7
|
-
import
|
|
6
|
+
import { ObjectTree, SiteTree, Tree } from "@weborigami/async-tree";
|
|
7
|
+
import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
8
8
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
9
9
|
import Scope from "./Scope.js";
|
|
10
|
-
import attachFileLoader from "./attachFileLoader.js";
|
|
11
10
|
import concatTreeValues from "./concatTreeValues.js";
|
|
11
|
+
import handleExtension from "./handleExtension.js";
|
|
12
12
|
import { OrigamiTree, evaluate, expressionFunction } from "./internal.js";
|
|
13
13
|
|
|
14
14
|
// For memoizing lambda functions
|
|
@@ -75,7 +75,7 @@ async function fetchResponse(href) {
|
|
|
75
75
|
const url = new URL(href);
|
|
76
76
|
const filename = url.pathname.split("/").pop();
|
|
77
77
|
if (filename) {
|
|
78
|
-
buffer = await
|
|
78
|
+
buffer = await handleExtension(this, filename, buffer, null);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
return buffer;
|
|
@@ -163,7 +163,7 @@ export function lambda(parameters, code) {
|
|
|
163
163
|
ambients[parameter] = args.shift();
|
|
164
164
|
}
|
|
165
165
|
ambients["@recurse"] = invoke;
|
|
166
|
-
const scope = new Scope(ambients, this);
|
|
166
|
+
const scope = new Scope(new ObjectTree(ambients), this);
|
|
167
167
|
|
|
168
168
|
let result = await evaluate.call(scope, code);
|
|
169
169
|
|
|
@@ -252,7 +252,7 @@ tree.toString = () => "«ops.tree»";
|
|
|
252
252
|
export function treeHttp(host, ...keys) {
|
|
253
253
|
const href = constructHref("http:", host, ...keys);
|
|
254
254
|
/** @type {AsyncTree} */
|
|
255
|
-
let result = new (
|
|
255
|
+
let result = new (HandleExtensionsTransform(SiteTree))(href);
|
|
256
256
|
result = Scope.treeWithScope(result, this);
|
|
257
257
|
return result;
|
|
258
258
|
}
|
|
@@ -268,7 +268,7 @@ treeHttp.toString = () => "«ops.treeHttp»";
|
|
|
268
268
|
export function treeHttps(host, ...keys) {
|
|
269
269
|
const href = constructHref("https:", host, ...keys);
|
|
270
270
|
/** @type {AsyncTree} */
|
|
271
|
-
let result = new (
|
|
271
|
+
let result = new (HandleExtensionsTransform(SiteTree))(href);
|
|
272
272
|
result = Scope.treeWithScope(result, this);
|
|
273
273
|
return result;
|
|
274
274
|
}
|
|
@@ -87,7 +87,7 @@ describe("Origami parser", () => {
|
|
|
87
87
|
`
|
|
88
88
|
{
|
|
89
89
|
index.html = index.ori(teamData.yaml)
|
|
90
|
-
thumbnails = @map(images, {
|
|
90
|
+
thumbnails = @map(images, { value: thumbnail.js })
|
|
91
91
|
}
|
|
92
92
|
`,
|
|
93
93
|
[
|
|
@@ -104,7 +104,7 @@ describe("Origami parser", () => {
|
|
|
104
104
|
[
|
|
105
105
|
[ops.scope, "@map"],
|
|
106
106
|
[ops.scope, "images"],
|
|
107
|
-
[ops.object, ["
|
|
107
|
+
[ops.object, ["value", [ops.scope, "thumbnail.js"]]],
|
|
108
108
|
],
|
|
109
109
|
],
|
|
110
110
|
]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
|
-
import
|
|
4
|
+
import HandleExtensionsTransform from "../../src/runtime/HandleExtensionsTransform.js";
|
|
5
5
|
import Scope from "../../src/runtime/Scope.js";
|
|
6
6
|
|
|
7
|
-
describe("
|
|
7
|
+
describe("HandleExtensionsTransform", () => {
|
|
8
8
|
test("invokes an appropriate loader for a .json file extension", async () => {
|
|
9
9
|
const fixture = createFixture();
|
|
10
10
|
const numberValue = await fixture.get("foo");
|
|
@@ -26,16 +26,16 @@ describe("FileLoadersTransform", () => {
|
|
|
26
26
|
|
|
27
27
|
function createFixture() {
|
|
28
28
|
/** @type {import("@weborigami/types").AsyncTree} */
|
|
29
|
-
let tree = new (
|
|
29
|
+
let tree = new (HandleExtensionsTransform(ObjectTree))({
|
|
30
30
|
foo: 1, // No extension, should be left alone
|
|
31
31
|
"bar.json": `{ "bar": 2 }`,
|
|
32
32
|
});
|
|
33
33
|
/** @type {any} */
|
|
34
|
-
const scope = {
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const scope = new ObjectTree({
|
|
35
|
+
json_handler: {
|
|
36
|
+
unpack: (buffer) => JSON.parse(String(buffer)),
|
|
37
37
|
},
|
|
38
|
-
};
|
|
38
|
+
});
|
|
39
39
|
tree = Scope.treeWithScope(tree, scope);
|
|
40
40
|
return tree;
|
|
41
41
|
}
|
|
@@ -53,9 +53,8 @@ describe("evaluate", () => {
|
|
|
53
53
|
|
|
54
54
|
test("if object in function position isn't a function, can unpack it", async () => {
|
|
55
55
|
const fn = (...args) => args.join(",");
|
|
56
|
-
const packed =
|
|
57
|
-
|
|
58
|
-
};
|
|
56
|
+
const packed = new String();
|
|
57
|
+
/** @type {any} */ (packed).unpack = async () => fn;
|
|
59
58
|
const code = [packed, "a", "b", "c"];
|
|
60
59
|
const result = await evaluate.call(null, code);
|
|
61
60
|
assert.equal(result, "a,b,c");
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import extname from "./extname.js";
|
|
3
|
-
import * as symbols from "./symbols.js";
|
|
4
|
-
|
|
5
|
-
export default async function attachFileLoader(scope, key, value, parent) {
|
|
6
|
-
const extension = extname(key);
|
|
7
|
-
let result = value;
|
|
8
|
-
if (extension) {
|
|
9
|
-
const loaderName = extension.slice(1);
|
|
10
|
-
const loader = await Tree.traverse(scope, "@loaders", loaderName);
|
|
11
|
-
if (loader) {
|
|
12
|
-
const input = value;
|
|
13
|
-
|
|
14
|
-
// If the result is a plain string, box it as a String so we can attach
|
|
15
|
-
// data to it.
|
|
16
|
-
if (typeof result === "string") {
|
|
17
|
-
result = new String(result);
|
|
18
|
-
}
|
|
19
|
-
result[symbols.parent] = parent;
|
|
20
|
-
|
|
21
|
-
// Wrap the loader with a function that will only be called once per
|
|
22
|
-
// value.
|
|
23
|
-
let loaded;
|
|
24
|
-
result.unpack = async () => {
|
|
25
|
-
loaded ??= await loader(input, { key, parent });
|
|
26
|
-
return loaded;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
package/src/runtime/symbols.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const parent = Symbol("parent");
|
|
File without changes
|