@weborigami/language 0.5.1 → 0.5.2
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 +1 -6
- package/package.json +3 -3
- package/src/compiler/origami.pegjs +14 -13
- package/src/compiler/parse.js +10 -9
- package/src/runtime/HandleExtensionsTransform.js +1 -1
- package/src/runtime/jsGlobals.js +59 -4
- package/test/compiler/parse.test.js +59 -37
- package/test/runtime/jsGlobals.test.js +23 -0
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UnpackFunction } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
3
|
export * from "./main.js";
|
|
4
4
|
|
|
@@ -64,8 +64,3 @@ export type Source = {
|
|
|
64
64
|
text: string;
|
|
65
65
|
url?: URL;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* A function that converts a value from a persistent form into a live value.
|
|
70
|
-
*/
|
|
71
|
-
export type UnpackFunction = (input: Packed, options?: any) => any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"typescript": "5.9.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.5.
|
|
15
|
-
"@weborigami/types": "0.5.
|
|
14
|
+
"@weborigami/async-tree": "0.5.2",
|
|
15
|
+
"@weborigami/types": "0.5.2",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.8.1"
|
|
18
18
|
},
|
|
@@ -549,23 +549,24 @@ objectProperty "object property"
|
|
|
549
549
|
|
|
550
550
|
// A shorthand reference inside an object literal: `foo`
|
|
551
551
|
objectShorthandProperty "object identifier"
|
|
552
|
-
=
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
552
|
+
= path:pathLiteral {
|
|
553
|
+
const lastKey = path[0] === ops.unpack
|
|
554
|
+
// [ops.unpack, [markers.traverse, [markers.reference, lastKey]]]
|
|
555
|
+
? path[1][1][1]
|
|
556
|
+
// [markers.traverse, ..., [markers.reference, lastKey]
|
|
557
|
+
: path.at(-1)[1];
|
|
558
|
+
return annotate([lastKey, path], location());
|
|
556
559
|
}
|
|
557
560
|
/ path:angleBracketLiteral {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
lastKey
|
|
561
|
+
// [markers.traverse, ..., [markers.reference, lastKey]]
|
|
562
|
+
const lastKey = path.at(-1)[1];
|
|
563
|
+
return annotate([lastKey, path], location());
|
|
561
564
|
}
|
|
562
|
-
return annotate([lastKey, path], location());
|
|
563
|
-
}
|
|
564
565
|
|
|
565
566
|
objectPublicKey
|
|
566
567
|
= key:key slash:"/"? {
|
|
567
|
-
|
|
568
|
-
|
|
568
|
+
return text();
|
|
569
|
+
}
|
|
569
570
|
/ string:stringLiteral {
|
|
570
571
|
// Remove `ops.literal` from the string code
|
|
571
572
|
return string[1];
|
|
@@ -573,8 +574,8 @@ objectPublicKey
|
|
|
573
574
|
|
|
574
575
|
optionalChaining
|
|
575
576
|
= __ "?." __ property:identifier {
|
|
576
|
-
|
|
577
|
-
|
|
577
|
+
return annotate([ops.optionalTraverse, property], location());
|
|
578
|
+
}
|
|
578
579
|
|
|
579
580
|
// Name of a unction parameter
|
|
580
581
|
parameter
|
package/src/compiler/parse.js
CHANGED
|
@@ -761,16 +761,17 @@ function peg$parse(input, options) {
|
|
|
761
761
|
function peg$f69(key, pipeline) {
|
|
762
762
|
return annotate([key, pipeline], location());
|
|
763
763
|
}
|
|
764
|
-
function peg$f70(
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
764
|
+
function peg$f70(path) {
|
|
765
|
+
const lastKey = path[0] === ops.unpack
|
|
766
|
+
// [ops.unpack, [markers.traverse, [markers.reference, lastKey]]]
|
|
767
|
+
? path[1][1][1]
|
|
768
|
+
// [markers.traverse, ..., [markers.reference, lastKey]
|
|
769
|
+
: path.at(-1)[1];
|
|
770
|
+
return annotate([lastKey, path], location());
|
|
768
771
|
}
|
|
769
772
|
function peg$f71(path) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
lastKey = lastKey[1]; // get scope identifier or literal
|
|
773
|
-
}
|
|
773
|
+
// [markers.traverse, ..., [markers.reference, lastKey]]
|
|
774
|
+
const lastKey = path.at(-1)[1];
|
|
774
775
|
return annotate([lastKey, path], location());
|
|
775
776
|
}
|
|
776
777
|
function peg$f72(key, slash) {
|
|
@@ -4539,7 +4540,7 @@ function peg$parse(input, options) {
|
|
|
4539
4540
|
|
|
4540
4541
|
peg$silentFails++;
|
|
4541
4542
|
s0 = peg$currPos;
|
|
4542
|
-
s1 = peg$
|
|
4543
|
+
s1 = peg$parsepathLiteral();
|
|
4543
4544
|
if (s1 !== peg$FAILED) {
|
|
4544
4545
|
peg$savedPos = s0;
|
|
4545
4546
|
s1 = peg$f70(s1);
|
|
@@ -4,7 +4,7 @@ import { handleExtension } from "./handlers.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
6
6
|
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
7
|
-
* @typedef {import("
|
|
7
|
+
* @typedef {import("@weborigami/async-tree").UnpackFunction} FileUnpackFunction
|
|
8
8
|
*
|
|
9
9
|
* @param {AsyncTreeConstructor} Base
|
|
10
10
|
*/
|
package/src/runtime/jsGlobals.js
CHANGED
|
@@ -12,7 +12,7 @@ import path from "node:path";
|
|
|
12
12
|
* Fetch API
|
|
13
13
|
* URL API
|
|
14
14
|
*/
|
|
15
|
-
const globals = {
|
|
15
|
+
const globals = bindStaticMethodsForGlobals({
|
|
16
16
|
AbortController,
|
|
17
17
|
AbortSignal,
|
|
18
18
|
AggregateError,
|
|
@@ -152,7 +152,7 @@ const globals = {
|
|
|
152
152
|
// Special cases
|
|
153
153
|
fetch: fetchWrapper,
|
|
154
154
|
import: importWrapper,
|
|
155
|
-
};
|
|
155
|
+
});
|
|
156
156
|
|
|
157
157
|
// Give access to our own custom globals as `globalThis`
|
|
158
158
|
Object.defineProperty(globals, "globalThis", {
|
|
@@ -160,8 +160,6 @@ Object.defineProperty(globals, "globalThis", {
|
|
|
160
160
|
value: globals,
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
export default globals;
|
|
164
|
-
|
|
165
163
|
async function fetchWrapper(resource, options) {
|
|
166
164
|
console.warn(
|
|
167
165
|
"Warning: A plain `fetch` reference will eventually call the standard JavaScript fetch() function. For Origami's fetch behavior, update your code to call Origami.fetch()."
|
|
@@ -186,3 +184,60 @@ async function importWrapper(modulePath) {
|
|
|
186
184
|
const filePath = path.resolve(current.path, modulePath);
|
|
187
185
|
return import(filePath);
|
|
188
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Some JavaScript globals like Promise have static methods like Promise.all
|
|
190
|
+
* verify that the call target is the class. This creates an issue because the
|
|
191
|
+
* Origami evaluate() function calls all functions with the evaluation context
|
|
192
|
+
* -- the tree in which the code is running -- as the call target.
|
|
193
|
+
*
|
|
194
|
+
* This function works around the problem. If the indicated classFn has no
|
|
195
|
+
* static methods, it's returned as is. If it does have static methods, this
|
|
196
|
+
* returns an extension of the classFn prototype that overrides the static
|
|
197
|
+
* methods with ones that are bound to the class.
|
|
198
|
+
*/
|
|
199
|
+
function bindStaticMethodsForClass(fn) {
|
|
200
|
+
const staticMethodDescriptors = Object.entries(
|
|
201
|
+
Object.getOwnPropertyDescriptors(fn)
|
|
202
|
+
)
|
|
203
|
+
.filter(([key, descriptor]) => descriptor.value instanceof Function)
|
|
204
|
+
.map(([key, descriptor]) => [
|
|
205
|
+
key,
|
|
206
|
+
{
|
|
207
|
+
...descriptor,
|
|
208
|
+
value: descriptor.value.bind(fn),
|
|
209
|
+
},
|
|
210
|
+
]);
|
|
211
|
+
if (staticMethodDescriptors.length === 0) {
|
|
212
|
+
// No static methods
|
|
213
|
+
return fn;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let extended;
|
|
217
|
+
if (!fn.prototype) {
|
|
218
|
+
// A function like Proxy
|
|
219
|
+
extended = Object.create(fn);
|
|
220
|
+
} else {
|
|
221
|
+
/** @this {any} */
|
|
222
|
+
extended = function (...args) {
|
|
223
|
+
const calledWithNew = this instanceof extended;
|
|
224
|
+
return calledWithNew ? new fn(...args) : fn(...args);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return Object.defineProperties(
|
|
229
|
+
extended,
|
|
230
|
+
Object.fromEntries(staticMethodDescriptors)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function bindStaticMethodsForGlobals(objects) {
|
|
235
|
+
const entries = Object.entries(objects);
|
|
236
|
+
const bound = entries.map(([key, value]) => [
|
|
237
|
+
key,
|
|
238
|
+
value instanceof Function ? bindStaticMethodsForClass(value) : value,
|
|
239
|
+
]);
|
|
240
|
+
return Object.fromEntries(bound);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default globals;
|
|
@@ -1019,44 +1019,66 @@ Body`,
|
|
|
1019
1019
|
});
|
|
1020
1020
|
});
|
|
1021
1021
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
"foo",
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
"
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
"
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
"
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1022
|
+
describe("objectEntry", () => {
|
|
1023
|
+
test("shorthand", () => {
|
|
1024
|
+
assertParse("objectEntry", "foo", [
|
|
1025
|
+
"foo",
|
|
1026
|
+
[markers.traverse, [markers.reference, "foo"]],
|
|
1027
|
+
]);
|
|
1028
|
+
assertParse("objectEntry", "folder/", [
|
|
1029
|
+
"folder/",
|
|
1030
|
+
[ops.unpack, [markers.traverse, [markers.reference, "folder/"]]],
|
|
1031
|
+
]);
|
|
1032
|
+
assertParse("objectEntry", "path/to/file.txt", [
|
|
1033
|
+
"file.txt",
|
|
1034
|
+
[
|
|
1035
|
+
markers.traverse,
|
|
1036
|
+
[markers.reference, "path/"],
|
|
1037
|
+
[ops.literal, "to/"],
|
|
1038
|
+
[ops.literal, "file.txt"],
|
|
1039
|
+
],
|
|
1040
|
+
]);
|
|
1041
|
+
assertParse("objectEntry", "<folder/>", [
|
|
1042
|
+
"folder/",
|
|
1043
|
+
[markers.traverse, [markers.external, "folder/"]],
|
|
1044
|
+
]);
|
|
1045
|
+
assertParse("objectEntry", "<path/to/file.txt>", [
|
|
1046
|
+
"file.txt",
|
|
1047
|
+
[
|
|
1048
|
+
markers.traverse,
|
|
1049
|
+
[markers.external, "path/"],
|
|
1050
|
+
[ops.literal, "to/"],
|
|
1051
|
+
[ops.literal, "file.txt"],
|
|
1052
|
+
],
|
|
1053
|
+
]);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test("key: value", () => {
|
|
1057
|
+
assertParse("objectEntry", "index.html: x", [
|
|
1058
|
+
"index.html",
|
|
1059
|
+
[markers.traverse, [markers.reference, "x"]],
|
|
1060
|
+
]);
|
|
1061
|
+
assertParse("objectEntry", "a: a", [
|
|
1062
|
+
"a",
|
|
1049
1063
|
[markers.traverse, [markers.reference, "a"]],
|
|
1050
|
-
]
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
]
|
|
1059
|
-
|
|
1064
|
+
]);
|
|
1065
|
+
assertParse("objectEntry", "a: (a) => a", [
|
|
1066
|
+
"a",
|
|
1067
|
+
[
|
|
1068
|
+
ops.lambda,
|
|
1069
|
+
[[ops.literal, "a"]],
|
|
1070
|
+
[markers.traverse, [markers.reference, "a"]],
|
|
1071
|
+
],
|
|
1072
|
+
]);
|
|
1073
|
+
assertParse("objectEntry", "posts/: map(posts, post.ori)", [
|
|
1074
|
+
"posts/",
|
|
1075
|
+
[
|
|
1076
|
+
[markers.traverse, [markers.reference, "map"]],
|
|
1077
|
+
[markers.traverse, [markers.reference, "posts"]],
|
|
1078
|
+
[markers.traverse, [markers.reference, "post.ori"]],
|
|
1079
|
+
],
|
|
1080
|
+
]);
|
|
1081
|
+
});
|
|
1060
1082
|
});
|
|
1061
1083
|
|
|
1062
1084
|
test("objectGetter", () => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import jsGlobals from "../../src/runtime/jsGlobals.js";
|
|
4
|
+
|
|
5
|
+
describe("jsGlobals", () => {
|
|
6
|
+
test("wraps static methods to drop the call target", async () => {
|
|
7
|
+
const { Promise: fixture } = jsGlobals;
|
|
8
|
+
const target = {};
|
|
9
|
+
const promise = fixture.resolve.call(target, "hi");
|
|
10
|
+
const value = await promise;
|
|
11
|
+
assert.equal(value, "hi");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("can invoke a global constructor", async () => {
|
|
15
|
+
const { Number: fixture } = jsGlobals;
|
|
16
|
+
// Without `new`
|
|
17
|
+
const instance1 = fixture(5);
|
|
18
|
+
assert.equal(instance1, 5);
|
|
19
|
+
// With `new`
|
|
20
|
+
const instance = new fixture();
|
|
21
|
+
assert(instance instanceof Number);
|
|
22
|
+
});
|
|
23
|
+
});
|