@weborigami/language 0.5.2-test.1 → 0.5.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.
@@ -455,8 +455,12 @@ export function makeUnaryOperation(operator, value, location) {
455
455
  "+": ops.unaryPlus,
456
456
  "-": ops.unaryMinus,
457
457
  "~": ops.bitwiseNot,
458
+ await: null, // no-op
459
+ typeof: ops.typeOf,
460
+ void: ops.voidOp,
458
461
  };
459
- return annotate([operators[operator], value], location);
462
+ const op = operators[operator];
463
+ return op ? annotate([op, value], location) : annotate(value, location);
460
464
  }
461
465
 
462
466
  /**
@@ -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,64 @@ 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 object has no static
195
+ * methods, it's returned as is. If it does have static methods, this returns an
196
+ * extension of the object that overrides the static methods with ones that are
197
+ * bound to the object.
198
+ */
199
+ function bindStaticMethods(obj) {
200
+ if (typeof obj !== "function" && (typeof obj !== "object" || obj === null)) {
201
+ // Something like `NaN` or `null`
202
+ return obj;
203
+ }
204
+
205
+ const staticMethodDescriptors = Object.entries(
206
+ Object.getOwnPropertyDescriptors(obj)
207
+ )
208
+ .filter(([key, descriptor]) => descriptor.value instanceof Function)
209
+ .map(([key, descriptor]) => [
210
+ key,
211
+ {
212
+ ...descriptor,
213
+ value: descriptor.value.bind(obj),
214
+ },
215
+ ]);
216
+ if (staticMethodDescriptors.length === 0) {
217
+ // No static methods
218
+ return obj;
219
+ }
220
+
221
+ let extended;
222
+ // A regular object or an oddball like Proxy with no prototype
223
+ if (typeof obj === "object" || !obj.prototype) {
224
+ extended = Object.create(obj);
225
+ } else {
226
+ /** @this {any} */
227
+ extended = function (...args) {
228
+ const calledWithNew = this instanceof extended;
229
+ return calledWithNew ? new obj(...args) : obj(...args);
230
+ };
231
+ }
232
+
233
+ Object.defineProperties(
234
+ extended,
235
+ Object.fromEntries(staticMethodDescriptors)
236
+ );
237
+
238
+ return extended;
239
+ }
240
+
241
+ function bindStaticMethodsForGlobals(objects) {
242
+ const entries = Object.entries(objects);
243
+ const bound = entries.map(([key, value]) => [key, bindStaticMethods(value)]);
244
+ return Object.fromEntries(bound);
245
+ }
246
+
247
+ export default globals;
@@ -547,6 +547,18 @@ export async function templateText(strings, ...values) {
547
547
  }
548
548
  addOpLabel(templateText, "«ops.templateText»");
549
549
 
550
+ /**
551
+ * Emulate the JavaScript `typeof` operator
552
+ *
553
+ * Note the name is `typeOf` (uppercase "O"), as `typeof` is a reserved word
554
+ *
555
+ * @param {any} value
556
+ */
557
+ export function typeOf(value) {
558
+ return typeof value;
559
+ }
560
+ addOpLabel(typeOf, "«ops.typeOf»");
561
+
550
562
  export function unaryMinus(a) {
551
563
  return -a;
552
564
  }
@@ -567,3 +579,13 @@ export async function unpack(value) {
567
579
  return isUnpackable(value) ? value.unpack() : value;
568
580
  }
569
581
  addOpLabel(unpack, "«ops.unpack»");
582
+
583
+ /**
584
+ * Emulate JavaScript's rarely-used `void` operator
585
+ *
586
+ * @param {any} value
587
+ */
588
+ export function voidOp(value) {
589
+ return undefined;
590
+ }
591
+ addOpLabel(voidOp, "«ops.voidOp»");
@@ -155,6 +155,11 @@ describe("Origami parser", () => {
155
155
  ],
156
156
  ],
157
157
  ]);
158
+ assertParse("arrowFunction", "async (x) => x", [
159
+ ops.lambda,
160
+ [[ops.literal, "x"]],
161
+ [markers.traverse, [markers.reference, "x"]],
162
+ ]);
158
163
  });
159
164
 
160
165
  test("bitwiseAndExpression", () => {
@@ -1019,44 +1024,66 @@ Body`,
1019
1024
  });
1020
1025
  });
1021
1026
 
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"]],
1027
+ describe("objectEntry", () => {
1028
+ test("shorthand", () => {
1029
+ assertParse("objectEntry", "foo", [
1030
+ "foo",
1031
+ [markers.traverse, [markers.reference, "foo"]],
1032
+ ]);
1033
+ assertParse("objectEntry", "folder/", [
1034
+ "folder/",
1035
+ [ops.unpack, [markers.traverse, [markers.reference, "folder/"]]],
1036
+ ]);
1037
+ assertParse("objectEntry", "path/to/file.txt", [
1038
+ "file.txt",
1039
+ [
1040
+ markers.traverse,
1041
+ [markers.reference, "path/"],
1042
+ [ops.literal, "to/"],
1043
+ [ops.literal, "file.txt"],
1044
+ ],
1045
+ ]);
1046
+ assertParse("objectEntry", "<folder/>", [
1047
+ "folder/",
1048
+ [markers.traverse, [markers.external, "folder/"]],
1049
+ ]);
1050
+ assertParse("objectEntry", "<path/to/file.txt>", [
1051
+ "file.txt",
1052
+ [
1053
+ markers.traverse,
1054
+ [markers.external, "path/"],
1055
+ [ops.literal, "to/"],
1056
+ [ops.literal, "file.txt"],
1057
+ ],
1058
+ ]);
1059
+ });
1060
+
1061
+ test("key: value", () => {
1062
+ assertParse("objectEntry", "index.html: x", [
1063
+ "index.html",
1064
+ [markers.traverse, [markers.reference, "x"]],
1065
+ ]);
1066
+ assertParse("objectEntry", "a: a", [
1067
+ "a",
1049
1068
  [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
- ]);
1069
+ ]);
1070
+ assertParse("objectEntry", "a: (a) => a", [
1071
+ "a",
1072
+ [
1073
+ ops.lambda,
1074
+ [[ops.literal, "a"]],
1075
+ [markers.traverse, [markers.reference, "a"]],
1076
+ ],
1077
+ ]);
1078
+ assertParse("objectEntry", "posts/: map(posts, post.ori)", [
1079
+ "posts/",
1080
+ [
1081
+ [markers.traverse, [markers.reference, "map"]],
1082
+ [markers.traverse, [markers.reference, "posts"]],
1083
+ [markers.traverse, [markers.reference, "post.ori"]],
1084
+ ],
1085
+ ]);
1086
+ });
1060
1087
  });
1061
1088
 
1062
1089
  test("objectGetter", () => {
@@ -1543,6 +1570,9 @@ Body text`,
1543
1570
  assertParse("unaryExpression", "+1", [ops.unaryPlus, [ops.literal, 1]]);
1544
1571
  assertParse("unaryExpression", "-2", [ops.unaryMinus, [ops.literal, 2]]);
1545
1572
  assertParse("unaryExpression", "~3", [ops.bitwiseNot, [ops.literal, 3]]);
1573
+ assertParse("unaryExpression", "typeof 1", [ops.typeOf, [ops.literal, 1]]);
1574
+ assertParse("unaryExpression", "void 0", [ops.voidOp, [ops.literal, 0]]);
1575
+ assertParse("unaryExpression", "await 2", [ops.literal, 2]);
1546
1576
  });
1547
1577
 
1548
1578
  test("unaryOperator", () => {
@@ -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
+ });
@@ -414,6 +414,17 @@ describe("ops", () => {
414
414
  assert.strictEqual(ops.subtraction(5, true), 4);
415
415
  });
416
416
 
417
+ test("ops.typeOf emulates JavaScript typeof", () => {
418
+ assert.strictEqual(ops.typeOf(true), "boolean");
419
+ assert.strictEqual(ops.typeOf(1), "number");
420
+ assert.strictEqual(ops.typeOf("hi"), "string");
421
+ assert.strictEqual(ops.typeOf(undefined), "undefined");
422
+ assert.strictEqual(
423
+ ops.typeOf(() => null),
424
+ "function"
425
+ );
426
+ });
427
+
417
428
  test("ops.unaryMinus", () => {
418
429
  assert.strictEqual(ops.unaryMinus(4), -4);
419
430
  assert.strictEqual(ops.unaryMinus(-4), 4);
@@ -431,6 +442,12 @@ describe("ops", () => {
431
442
  const result = await ops.unpack.call(null, fixture);
432
443
  assert.strictEqual(result, "unpacked");
433
444
  });
445
+
446
+ test("ops.voidOp returns undefined", () => {
447
+ assert.strictEqual(ops.voidOp(123), undefined);
448
+ assert.strictEqual(ops.voidOp("hello"), undefined);
449
+ assert.strictEqual(ops.voidOp(null), undefined);
450
+ });
434
451
  });
435
452
 
436
453
  function errorFn() {