@weborigami/language 0.2.6 → 0.2.8
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 +17 -9
- package/package.json +3 -3
- package/src/compiler/optimize.js +36 -20
- package/src/compiler/origami.pegjs +48 -37
- package/src/compiler/parse.d.ts +2 -2
- package/src/compiler/parse.js +299 -284
- package/src/compiler/parserHelpers.js +132 -89
- package/src/runtime/errors.js +5 -2
- package/src/runtime/evaluate.js +8 -11
- package/src/runtime/expressionFunction.js +1 -1
- package/src/runtime/expressionObject.js +12 -4
- package/src/runtime/mergeTrees.js +2 -0
- package/src/runtime/ops.js +63 -18
- package/src/runtime/typos.js +6 -0
- package/test/compiler/{stripCodeLocations.js → codeHelpers.js} +24 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/optimize.test.js +9 -11
- package/test/compiler/parse.test.js +27 -8
- package/test/runtime/OrigamiFiles.test.js +0 -2
- package/test/runtime/evaluate.test.js +1 -13
- package/test/runtime/mergeTrees.test.js +0 -1
- package/test/runtime/ops.test.js +40 -13
- package/test/runtime/typos.test.js +1 -0
package/src/runtime/ops.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("
|
|
2
|
+
* @typedef {import("../../index.ts").AnnotatedCode} AnnotatedCode
|
|
3
3
|
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
|
4
4
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
5
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import {
|
|
@@ -33,6 +34,17 @@ export function addition(a, b) {
|
|
|
33
34
|
}
|
|
34
35
|
addOpLabel(addition, "«ops.addition»");
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Construct an array.
|
|
39
|
+
*
|
|
40
|
+
* @this {AsyncTree|null}
|
|
41
|
+
* @param {any[]} items
|
|
42
|
+
*/
|
|
43
|
+
export async function array(...items) {
|
|
44
|
+
return items;
|
|
45
|
+
}
|
|
46
|
+
addOpLabel(array, "«ops.array»");
|
|
47
|
+
|
|
36
48
|
export function bitwiseAnd(a, b) {
|
|
37
49
|
return a & b;
|
|
38
50
|
}
|
|
@@ -53,17 +65,6 @@ export function bitwiseXor(a, b) {
|
|
|
53
65
|
}
|
|
54
66
|
addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
|
55
67
|
|
|
56
|
-
/**
|
|
57
|
-
* Construct an array.
|
|
58
|
-
*
|
|
59
|
-
* @this {AsyncTree|null}
|
|
60
|
-
* @param {any[]} items
|
|
61
|
-
*/
|
|
62
|
-
export async function array(...items) {
|
|
63
|
-
return items;
|
|
64
|
-
}
|
|
65
|
-
addOpLabel(array, "«ops.array»");
|
|
66
|
-
|
|
67
68
|
/**
|
|
68
69
|
* Like ops.scope, but only searches for a builtin at the top of the scope
|
|
69
70
|
* chain.
|
|
@@ -83,6 +84,7 @@ export async function builtin(key) {
|
|
|
83
84
|
|
|
84
85
|
return value;
|
|
85
86
|
}
|
|
87
|
+
addOpLabel(builtin, "«ops.builtin»");
|
|
86
88
|
|
|
87
89
|
/**
|
|
88
90
|
* JavaScript comma operator, returns the last argument.
|
|
@@ -107,7 +109,8 @@ export async function concat(...args) {
|
|
|
107
109
|
addOpLabel(concat, "«ops.concat»");
|
|
108
110
|
|
|
109
111
|
export async function conditional(condition, truthy, falsy) {
|
|
110
|
-
|
|
112
|
+
const value = condition ? truthy : falsy;
|
|
113
|
+
return value instanceof Function ? await value() : value;
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
export function division(a, b) {
|
|
@@ -154,6 +157,7 @@ export async function external(path, code, cache) {
|
|
|
154
157
|
|
|
155
158
|
return value;
|
|
156
159
|
}
|
|
160
|
+
addOpLabel(external, "«ops.external»");
|
|
157
161
|
|
|
158
162
|
/**
|
|
159
163
|
* This op is only used during parsing. It signals to ops.object that the
|
|
@@ -181,6 +185,7 @@ export async function homeDirectory() {
|
|
|
181
185
|
tree.parent = this ? Tree.root(this) : null;
|
|
182
186
|
return tree;
|
|
183
187
|
}
|
|
188
|
+
addOpLabel(homeDirectory, "«ops.homeDirectory»");
|
|
184
189
|
|
|
185
190
|
/**
|
|
186
191
|
* Search the parent's scope -- i.e., exclude the current tree -- for the given
|
|
@@ -201,10 +206,9 @@ addOpLabel(inherited, "«ops.inherited»");
|
|
|
201
206
|
/**
|
|
202
207
|
* Return a function that will invoke the given code.
|
|
203
208
|
*
|
|
204
|
-
* @typedef {import("../../index.ts").Code} Code
|
|
205
209
|
* @this {AsyncTree|null}
|
|
206
210
|
* @param {string[]} parameters
|
|
207
|
-
* @param {
|
|
211
|
+
* @param {AnnotatedCode} code
|
|
208
212
|
*/
|
|
209
213
|
export function lambda(parameters, code) {
|
|
210
214
|
const context = this;
|
|
@@ -271,6 +275,9 @@ addOpLabel(lessThanOrEqual, "«ops.lessThanOrEqual»");
|
|
|
271
275
|
|
|
272
276
|
/**
|
|
273
277
|
* Return a primitive value
|
|
278
|
+
*
|
|
279
|
+
* This op is optimized away during compilation, with the exception of array
|
|
280
|
+
* literals.
|
|
274
281
|
*/
|
|
275
282
|
export async function literal(value) {
|
|
276
283
|
return value;
|
|
@@ -296,6 +303,7 @@ export async function logicalAnd(head, ...tail) {
|
|
|
296
303
|
// Return the last value (not `true`)
|
|
297
304
|
return lastValue;
|
|
298
305
|
}
|
|
306
|
+
addOpLabel(logicalAnd, "«ops.logicalAnd»");
|
|
299
307
|
|
|
300
308
|
/**
|
|
301
309
|
* Logical NOT operator
|
|
@@ -303,6 +311,7 @@ export async function logicalAnd(head, ...tail) {
|
|
|
303
311
|
export async function logicalNot(value) {
|
|
304
312
|
return !value;
|
|
305
313
|
}
|
|
314
|
+
addOpLabel(logicalNot, "«ops.logicalNot»");
|
|
306
315
|
|
|
307
316
|
/**
|
|
308
317
|
* Logical OR operator
|
|
@@ -323,14 +332,47 @@ export async function logicalOr(head, ...tail) {
|
|
|
323
332
|
|
|
324
333
|
return lastValue;
|
|
325
334
|
}
|
|
335
|
+
addOpLabel(logicalOr, "«ops.logicalOr»");
|
|
326
336
|
|
|
327
337
|
/**
|
|
328
338
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
329
339
|
*
|
|
330
340
|
* @this {AsyncTree|null}
|
|
331
|
-
* @param {
|
|
341
|
+
* @param {AnnotatedCode[]} codes
|
|
332
342
|
*/
|
|
333
|
-
export async function merge(...
|
|
343
|
+
export async function merge(...codes) {
|
|
344
|
+
// First pass: evaluate the direct property entries in a single object
|
|
345
|
+
let treeSpreads = false;
|
|
346
|
+
const directEntries = [];
|
|
347
|
+
for (const code of codes) {
|
|
348
|
+
if (code[0] === object) {
|
|
349
|
+
directEntries.push(...code.slice(1));
|
|
350
|
+
} else {
|
|
351
|
+
treeSpreads = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const directObject = directEntries
|
|
356
|
+
? await expressionObject(directEntries, this)
|
|
357
|
+
: null;
|
|
358
|
+
if (!treeSpreads) {
|
|
359
|
+
// No tree spreads, we're done
|
|
360
|
+
return directObject;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Second pass: evaluate the trees with the direct properties object in scope
|
|
364
|
+
let context;
|
|
365
|
+
if (directObject) {
|
|
366
|
+
context = Tree.from(directObject);
|
|
367
|
+
context.parent = this;
|
|
368
|
+
} else {
|
|
369
|
+
context = this;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const trees = await Promise.all(
|
|
373
|
+
codes.map(async (code) => evaluate.call(context, code))
|
|
374
|
+
);
|
|
375
|
+
|
|
334
376
|
return mergeTrees.call(this, ...trees);
|
|
335
377
|
}
|
|
336
378
|
addOpLabel(merge, "«ops.merge»");
|
|
@@ -368,6 +410,7 @@ export async function nullishCoalescing(head, ...tail) {
|
|
|
368
410
|
|
|
369
411
|
return lastValue;
|
|
370
412
|
}
|
|
413
|
+
addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
|
|
371
414
|
|
|
372
415
|
/**
|
|
373
416
|
* Construct an object. The keys will be the same as the given `obj`
|
|
@@ -400,6 +443,7 @@ export async function rootDirectory(key) {
|
|
|
400
443
|
tree.parent = this ? Tree.root(this) : null;
|
|
401
444
|
return key ? tree.get(key) : tree;
|
|
402
445
|
}
|
|
446
|
+
addOpLabel(rootDirectory, "«ops.rootDirectory»");
|
|
403
447
|
|
|
404
448
|
/**
|
|
405
449
|
* Look up the given key in the scope for the current tree.
|
|
@@ -438,7 +482,7 @@ addOpLabel(shiftRightUnsigned, "«ops.shiftRightUnsigned»");
|
|
|
438
482
|
* The spread operator is a placeholder during parsing. It should be replaced
|
|
439
483
|
* with an object merge.
|
|
440
484
|
*/
|
|
441
|
-
export function spread(
|
|
485
|
+
export function spread(arg) {
|
|
442
486
|
throw new Error(
|
|
443
487
|
"Internal error: a spread operation wasn't compiled correctly."
|
|
444
488
|
);
|
|
@@ -495,3 +539,4 @@ addOpLabel(unaryPlus, "«ops.unaryPlus»");
|
|
|
495
539
|
export async function unpack(value) {
|
|
496
540
|
return isUnpackable(value) ? value.unpack() : value;
|
|
497
541
|
}
|
|
542
|
+
addOpLabel(unpack, "«ops.unpack»");
|
package/src/runtime/typos.js
CHANGED
|
@@ -15,6 +15,12 @@ export function isTypo(s1, s2) {
|
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// If strings are both a single character, we don't want to consider them
|
|
19
|
+
// typos.
|
|
20
|
+
if (length1 === 1 && length2 === 1) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
// If length difference is more than 1, distance can't be 1
|
|
19
25
|
if (Math.abs(length1 - length2) > 1) {
|
|
20
26
|
return false;
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import { isPlainObject } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
|
|
4
|
+
export function assertCodeEqual(actual, expected) {
|
|
5
|
+
const actualStripped = stripCodeLocations(actual);
|
|
6
|
+
const expectedStripped = stripCodeLocations(expected);
|
|
7
|
+
assert.deepEqual(actualStripped, expectedStripped);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds a fake source to code.
|
|
12
|
+
*
|
|
13
|
+
* @returns {import("../../index.ts").AnnotatedCode}
|
|
14
|
+
*/
|
|
15
|
+
export function createCode(array) {
|
|
16
|
+
const code = array;
|
|
17
|
+
/** @type {any} */ (code).location = {
|
|
18
|
+
end: 0,
|
|
19
|
+
source: {
|
|
20
|
+
text: "",
|
|
21
|
+
},
|
|
22
|
+
start: 0,
|
|
23
|
+
};
|
|
24
|
+
return code;
|
|
25
|
+
}
|
|
2
26
|
|
|
3
27
|
// For comparison purposes, strip the `location` property added by the parser.
|
|
4
28
|
export function stripCodeLocations(parseResult) {
|
|
@@ -3,7 +3,7 @@ import assert from "node:assert";
|
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
import * as compile from "../../src/compiler/compile.js";
|
|
5
5
|
import { ops } from "../../src/runtime/internal.js";
|
|
6
|
-
import {
|
|
6
|
+
import { assertCodeEqual } from "./codeHelpers.js";
|
|
7
7
|
|
|
8
8
|
const shared = new ObjectTree({
|
|
9
9
|
greet: (name) => `Hello, ${name}!`,
|
|
@@ -70,7 +70,7 @@ describe("compile", () => {
|
|
|
70
70
|
let saved;
|
|
71
71
|
const scope = new ObjectTree({
|
|
72
72
|
tag: (strings, ...values) => {
|
|
73
|
-
|
|
73
|
+
assertCodeEqual(strings, ["Hello, ", "!"]);
|
|
74
74
|
if (saved) {
|
|
75
75
|
assert.equal(strings, saved);
|
|
76
76
|
} else {
|
|
@@ -96,7 +96,7 @@ describe("compile", () => {
|
|
|
96
96
|
},
|
|
97
97
|
});
|
|
98
98
|
const code = fn.code;
|
|
99
|
-
|
|
99
|
+
assertCodeEqual(code, [ops.object, ["a", 1]]);
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
1
|
import { describe, test } from "node:test";
|
|
3
2
|
import * as compile from "../../src/compiler/compile.js";
|
|
4
3
|
import optimize from "../../src/compiler/optimize.js";
|
|
5
4
|
import { ops } from "../../src/runtime/internal.js";
|
|
6
|
-
import {
|
|
5
|
+
import { assertCodeEqual, createCode } from "./codeHelpers.js";
|
|
7
6
|
|
|
8
7
|
describe("optimize", () => {
|
|
9
8
|
test("optimize non-local ops.scope calls to ops.external", async () => {
|
|
@@ -17,12 +16,12 @@ describe("optimize", () => {
|
|
|
17
16
|
`;
|
|
18
17
|
const fn = compile.expression(expression);
|
|
19
18
|
const code = fn.code;
|
|
20
|
-
|
|
19
|
+
assertCodeEqual(code, [
|
|
21
20
|
ops.lambda,
|
|
22
21
|
["name"],
|
|
23
22
|
[
|
|
24
23
|
ops.object,
|
|
25
|
-
["a",
|
|
24
|
+
["a", 1],
|
|
26
25
|
["b", [ops.scope, "a"]],
|
|
27
26
|
["c", [ops.external, "elsewhere", [ops.scope, "elsewhere"], {}]],
|
|
28
27
|
["d", [ops.scope, "name"]],
|
|
@@ -32,13 +31,12 @@ describe("optimize", () => {
|
|
|
32
31
|
|
|
33
32
|
test("optimize scope traversals with all literal keys", async () => {
|
|
34
33
|
// Compilation of `x/y.js`
|
|
35
|
-
const code = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
ops.
|
|
39
|
-
"x/y.js",
|
|
40
|
-
code,
|
|
41
|
-
{},
|
|
34
|
+
const code = createCode([
|
|
35
|
+
ops.traverse,
|
|
36
|
+
[ops.scope, "x/"],
|
|
37
|
+
[ops.literal, "y.js"],
|
|
42
38
|
]);
|
|
39
|
+
const optimized = optimize(code);
|
|
40
|
+
assertCodeEqual(optimized, [ops.external, "x/y.js", code, {}]);
|
|
43
41
|
});
|
|
44
42
|
});
|
|
@@ -3,7 +3,7 @@ import { describe, test } from "node:test";
|
|
|
3
3
|
import { parse } from "../../src/compiler/parse.js";
|
|
4
4
|
import { undetermined } from "../../src/compiler/parserHelpers.js";
|
|
5
5
|
import * as ops from "../../src/runtime/ops.js";
|
|
6
|
-
import {
|
|
6
|
+
import { assertCodeEqual } from "./codeHelpers.js";
|
|
7
7
|
|
|
8
8
|
describe("Origami parser", () => {
|
|
9
9
|
test("additiveExpression", () => {
|
|
@@ -247,20 +247,20 @@ describe("Origami parser", () => {
|
|
|
247
247
|
assertParse("conditionalExpression", "true ? 1 : 0", [
|
|
248
248
|
ops.conditional,
|
|
249
249
|
[ops.scope, "true"],
|
|
250
|
-
[ops.
|
|
251
|
-
[ops.
|
|
250
|
+
[ops.literal, "1"],
|
|
251
|
+
[ops.literal, "0"],
|
|
252
252
|
]);
|
|
253
253
|
assertParse("conditionalExpression", "false ? () => 1 : 0", [
|
|
254
254
|
ops.conditional,
|
|
255
255
|
[ops.scope, "false"],
|
|
256
256
|
[ops.lambda, [], [ops.lambda, [], [ops.literal, "1"]]],
|
|
257
|
-
[ops.
|
|
257
|
+
[ops.literal, "0"],
|
|
258
258
|
]);
|
|
259
259
|
assertParse("conditionalExpression", "false ? =1 : 0", [
|
|
260
260
|
ops.conditional,
|
|
261
261
|
[ops.scope, "false"],
|
|
262
262
|
[ops.lambda, [], [ops.lambda, ["_"], [ops.literal, "1"]]],
|
|
263
|
-
[ops.
|
|
263
|
+
[ops.literal, "0"],
|
|
264
264
|
]);
|
|
265
265
|
});
|
|
266
266
|
|
|
@@ -701,6 +701,11 @@ describe("Origami parser", () => {
|
|
|
701
701
|
[ops.object, ["a", [ops.literal, 1]]],
|
|
702
702
|
[ops.scope, "b"],
|
|
703
703
|
]);
|
|
704
|
+
assertParse("objectLiteral", "{ a: 1, ...{ b: 2 } }", [
|
|
705
|
+
ops.object,
|
|
706
|
+
["a", [ops.literal, 1]],
|
|
707
|
+
["b", [ops.literal, 2]],
|
|
708
|
+
]);
|
|
704
709
|
assertParse("objectLiteral", "{ (a): 1 }", [
|
|
705
710
|
ops.object,
|
|
706
711
|
["(a)", [ops.literal, 1]],
|
|
@@ -868,6 +873,12 @@ describe("Origami parser", () => {
|
|
|
868
873
|
[ops.literal, "localhost:5000/"],
|
|
869
874
|
[ops.literal, "foo"],
|
|
870
875
|
]);
|
|
876
|
+
assertParse("protocolExpression", "files:///foo/bar.txt", [
|
|
877
|
+
[ops.builtin, "files:"],
|
|
878
|
+
[ops.literal, "/"],
|
|
879
|
+
[ops.literal, "foo/"],
|
|
880
|
+
[ops.literal, "bar.txt"],
|
|
881
|
+
]);
|
|
871
882
|
});
|
|
872
883
|
|
|
873
884
|
test("qualifiedReference", () => {
|
|
@@ -1071,7 +1082,7 @@ function assertParse(startRule, source, expected, checkLocation = true) {
|
|
|
1071
1082
|
// entire source. We skip this check in cases where the source starts or ends
|
|
1072
1083
|
// with a comment; the parser will strip those.
|
|
1073
1084
|
if (checkLocation) {
|
|
1074
|
-
|
|
1085
|
+
assertCodeLocations(code);
|
|
1075
1086
|
const resultSource = code.location.source.text.slice(
|
|
1076
1087
|
code.location.start.offset,
|
|
1077
1088
|
code.location.end.offset
|
|
@@ -1079,6 +1090,14 @@ function assertParse(startRule, source, expected, checkLocation = true) {
|
|
|
1079
1090
|
assert.equal(resultSource, source.trim());
|
|
1080
1091
|
}
|
|
1081
1092
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1093
|
+
assertCodeEqual(code, expected);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function assertCodeLocations(code) {
|
|
1097
|
+
assert(code.location, "no location");
|
|
1098
|
+
for (const item of code) {
|
|
1099
|
+
if (Array.isArray(item)) {
|
|
1100
|
+
assertCodeLocations(item);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1084
1103
|
}
|
|
@@ -13,11 +13,9 @@ describe("OrigamiFiles", () => {
|
|
|
13
13
|
await createTempDirectory();
|
|
14
14
|
const tempFiles = new OrigamiFiles(tempDirectory);
|
|
15
15
|
const changedFileName = await new Promise(async (resolve) => {
|
|
16
|
-
// @ts-ignore
|
|
17
16
|
tempFiles.addEventListener("change", (event) => {
|
|
18
17
|
resolve(/** @type {any} */ (event).options.key);
|
|
19
18
|
});
|
|
20
|
-
// @ts-ignore
|
|
21
19
|
await tempFiles.set(
|
|
22
20
|
"foo.txt",
|
|
23
21
|
"This file is left over from testing and can be removed."
|
|
@@ -4,6 +4,7 @@ import { describe, test } from "node:test";
|
|
|
4
4
|
import * as ops from "../../src/runtime/ops.js";
|
|
5
5
|
|
|
6
6
|
import evaluate from "../../src/runtime/evaluate.js";
|
|
7
|
+
import { createCode } from "../compiler/codeHelpers.js";
|
|
7
8
|
|
|
8
9
|
describe("evaluate", () => {
|
|
9
10
|
test("can retrieve values from scope", async () => {
|
|
@@ -70,16 +71,3 @@ describe("evaluate", () => {
|
|
|
70
71
|
assert.equal(result.parent, tree);
|
|
71
72
|
});
|
|
72
73
|
});
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @returns {import("../../index.ts").Code}
|
|
76
|
-
*/
|
|
77
|
-
function createCode(array) {
|
|
78
|
-
const code = array;
|
|
79
|
-
/** @type {any} */ (code).location = {
|
|
80
|
-
source: {
|
|
81
|
-
text: "",
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
return code;
|
|
85
|
-
}
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -3,6 +3,7 @@ import assert from "node:assert";
|
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
|
|
5
5
|
import { evaluate, ops } from "../../src/runtime/internal.js";
|
|
6
|
+
import { createCode } from "../compiler/codeHelpers.js";
|
|
6
7
|
|
|
7
8
|
describe("ops", () => {
|
|
8
9
|
test("ops.addition adds two numbers", async () => {
|
|
@@ -220,6 +221,45 @@ describe("ops", () => {
|
|
|
220
221
|
assert.strictEqual(await ops.logicalOr(true, errorFn), true);
|
|
221
222
|
});
|
|
222
223
|
|
|
224
|
+
test("ops.merge", async () => {
|
|
225
|
+
// {
|
|
226
|
+
// a: 1
|
|
227
|
+
// …fn(a)
|
|
228
|
+
// }
|
|
229
|
+
const scope = new ObjectTree({
|
|
230
|
+
fn: (a) => ({ b: 2 * a }),
|
|
231
|
+
});
|
|
232
|
+
const code = createCode([
|
|
233
|
+
ops.merge,
|
|
234
|
+
[ops.object, ["a", [ops.literal, 1]]],
|
|
235
|
+
[
|
|
236
|
+
[ops.builtin, "fn"],
|
|
237
|
+
[ops.scope, "a"],
|
|
238
|
+
],
|
|
239
|
+
]);
|
|
240
|
+
const result = await evaluate.call(scope, code);
|
|
241
|
+
assert.deepEqual(result, { a: 1, b: 2 });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("ops.merge lets all direct properties see each other", async () => {
|
|
245
|
+
// {
|
|
246
|
+
// a: 1
|
|
247
|
+
// ...more
|
|
248
|
+
// c: a
|
|
249
|
+
// }
|
|
250
|
+
const scope = new ObjectTree({
|
|
251
|
+
more: { b: 2 },
|
|
252
|
+
});
|
|
253
|
+
const code = createCode([
|
|
254
|
+
ops.merge,
|
|
255
|
+
[ops.object, ["a", [ops.literal, 1]]],
|
|
256
|
+
[ops.scope, "more"],
|
|
257
|
+
[ops.object, ["c", [ops.scope, "a"]]],
|
|
258
|
+
]);
|
|
259
|
+
const result = await evaluate.call(scope, code);
|
|
260
|
+
assert.deepEqual(result, { a: 1, b: 2, c: 1 });
|
|
261
|
+
});
|
|
262
|
+
|
|
223
263
|
test("ops.multiplication multiplies two numbers", async () => {
|
|
224
264
|
assert.strictEqual(ops.multiplication(3, 4), 12);
|
|
225
265
|
assert.strictEqual(ops.multiplication(-3, 4), -12);
|
|
@@ -339,19 +379,6 @@ describe("ops", () => {
|
|
|
339
379
|
});
|
|
340
380
|
});
|
|
341
381
|
|
|
342
|
-
/**
|
|
343
|
-
* @returns {import("../../index.ts").Code}
|
|
344
|
-
*/
|
|
345
|
-
function createCode(array) {
|
|
346
|
-
const code = array;
|
|
347
|
-
/** @type {any} */ (code).location = {
|
|
348
|
-
source: {
|
|
349
|
-
text: "",
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
return code;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
382
|
function errorFn() {
|
|
356
383
|
throw new Error("This should not be called");
|
|
357
384
|
}
|
|
@@ -12,6 +12,7 @@ describe("typos", () => {
|
|
|
12
12
|
assert(isTypo("cat", "cta")); // transposition
|
|
13
13
|
assert(isTypo("cat", "act")); // transposition
|
|
14
14
|
assert(!isTypo("cat", "dog")); // more than 1 edit
|
|
15
|
+
assert(!isTypo("a", "b")); // single character
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
test("typos", () => {
|