@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.
- package/index.ts +1 -6
- package/package.json +3 -3
- package/src/compiler/origami.pegjs +33 -25
- package/src/compiler/parse.js +585 -454
- package/src/compiler/parserHelpers.js +5 -1
- package/src/runtime/HandleExtensionsTransform.js +1 -1
- package/src/runtime/jsGlobals.js +63 -4
- package/src/runtime/ops.js +22 -0
- package/test/compiler/parse.test.js +67 -37
- package/test/runtime/jsGlobals.test.js +23 -0
- package/test/runtime/ops.test.js +17 -0
|
@@ -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
|
-
|
|
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("
|
|
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,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;
|
package/src/runtime/ops.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
+
});
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -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() {
|