@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 CHANGED
@@ -1,4 +1,4 @@
1
- import { Packed } from "@weborigami/async-tree";
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.1",
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.1",
15
- "@weborigami/types": "0.5.1",
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
- = key:objectPublicKey {
553
- const reference = annotate([markers.reference, key], location());
554
- const traverse = annotate([markers.traverse, reference], location());
555
- return annotate([key, traverse], location());
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
- let lastKey = path.at(-1);
559
- if (lastKey instanceof Array) {
560
- lastKey = lastKey[1]; // get scope identifier or literal
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
- return text();
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
- return annotate([ops.optionalTraverse, property], location());
577
- }
577
+ return annotate([ops.optionalTraverse, property], location());
578
+ }
578
579
 
579
580
  // Name of a unction parameter
580
581
  parameter
@@ -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(key) {
765
- const reference = annotate([markers.reference, key], location());
766
- const traverse = annotate([markers.traverse, reference], location());
767
- return annotate([key, traverse], location());
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
- let lastKey = path.at(-1);
771
- if (lastKey instanceof Array) {
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$parseobjectPublicKey();
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("../../index.ts").UnpackFunction} FileUnpackFunction
7
+ * @typedef {import("@weborigami/async-tree").UnpackFunction} FileUnpackFunction
8
8
  *
9
9
  * @param {AsyncTreeConstructor} Base
10
10
  */
@@ -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
- test("objectEntry", () => {
1023
- assertParse("objectEntry", "foo", [
1024
- "foo",
1025
- [markers.traverse, [markers.reference, "foo"]],
1026
- ]);
1027
- assertParse("objectEntry", "index.html: x", [
1028
- "index.html",
1029
- [markers.traverse, [markers.reference, "x"]],
1030
- ]);
1031
- assertParse("objectEntry", "a: a", [
1032
- "a",
1033
- [markers.traverse, [markers.reference, "a"]],
1034
- ]);
1035
- assertParse("objectEntry", "<path/to/file.txt>", [
1036
- "file.txt",
1037
- [
1038
- markers.traverse,
1039
- [markers.external, "path/"],
1040
- [ops.literal, "to/"],
1041
- [ops.literal, "file.txt"],
1042
- ],
1043
- ]);
1044
- assertParse("objectEntry", "a: (a) => a", [
1045
- "a",
1046
- [
1047
- ops.lambda,
1048
- [[ops.literal, "a"]],
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
- assertParse("objectEntry", "posts/: map(posts, post.ori)", [
1053
- "posts/",
1054
- [
1055
- [markers.traverse, [markers.reference, "map"]],
1056
- [markers.traverse, [markers.reference, "posts"]],
1057
- [markers.traverse, [markers.reference, "post.ori"]],
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
+ });