@weborigami/language 0.1.0 → 0.2.0
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 -0
- package/package.json +6 -4
- package/src/compiler/compile.js +33 -15
- package/src/compiler/origami.pegjs +222 -193
- package/src/compiler/parse.js +1380 -1040
- package/src/compiler/parserHelpers.js +171 -45
- package/src/runtime/ops.js +137 -55
- package/test/cases/ReadMe.md +1 -0
- package/test/cases/conditionalExpression.yaml +101 -0
- package/test/cases/logicalAndExpression.yaml +146 -0
- package/test/cases/logicalOrExpression.yaml +145 -0
- package/test/cases/nullishCoalescingExpression.yaml +105 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/parse.test.js +447 -363
- package/test/generated/conditionalExpression.test.js +58 -0
- package/test/generated/logicalAndExpression.test.js +80 -0
- package/test/generated/logicalOrExpression.test.js +78 -0
- package/test/generated/nullishCoalescingExpression.test.js +64 -0
- package/test/generator/generateTests.js +80 -0
- package/test/generator/oriEval.js +15 -0
- package/test/runtime/ops.test.js +127 -23
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Generated tests -- do not edit directly
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { describe } from "node:test";
|
|
6
|
+
import oriEval from "../generator/oriEval.js";
|
|
7
|
+
|
|
8
|
+
describe("conditionalExpression - JavaScript", () => {
|
|
9
|
+
assert.strictEqual(true ? 42 : 0, 42, "Condition is true, evaluates and returns the first operand");
|
|
10
|
+
assert.strictEqual(false ? 42 : 0, 0, "Condition is false, evaluates and returns the second operand");
|
|
11
|
+
assert.strictEqual(1 ? 'yes' : 'no', "yes", "Truthy condition with string operands");
|
|
12
|
+
assert.strictEqual(0 ? 'yes' : 'no', "no", "Falsy condition with string operands");
|
|
13
|
+
assert.strictEqual('non-empty' ? 1 : 2, 1, "Truthy string condition with numeric operands");
|
|
14
|
+
assert.strictEqual('' ? 1 : 2, 2, "Falsy string condition with numeric operands");
|
|
15
|
+
assert.strictEqual(null ? 'a' : 'b', "b", "Falsy null condition");
|
|
16
|
+
assert.strictEqual(undefined ? 'a' : 'b', "b", "Falsy undefined condition");
|
|
17
|
+
assert.strictEqual(NaN ? 'a' : 'b', "b", "Falsy NaN condition");
|
|
18
|
+
assert.strictEqual(42 ? true : false, true, "Truthy numeric condition with boolean operands");
|
|
19
|
+
assert.strictEqual(0 ? true : false, false, "Falsy numeric condition with boolean operands");
|
|
20
|
+
assert.strictEqual([] ? 'array' : 'no array', "array", "Truthy array condition");
|
|
21
|
+
assert.strictEqual({} ? 'object' : 'no object', "object", "Truthy object condition");
|
|
22
|
+
assert.strictEqual(false ? null : undefined, undefined, "Condition is false, returns undefined");
|
|
23
|
+
assert.deepEqual(null ? null : null, null, "Condition is falsy, returns null");
|
|
24
|
+
assert.strictEqual(true ? NaN : 42, NaN, "Condition is true, evaluates and returns NaN");
|
|
25
|
+
assert.strictEqual((true ? 1 : 2) ? 3 : 4, 3, "Nested ternary where first expression evaluates to 1, which is truthy");
|
|
26
|
+
assert.strictEqual((false ? 1 : 2) ? 3 : 4, 3, "Nested ternary where first expression evaluates to 2, which is truthy");
|
|
27
|
+
assert.strictEqual((false ? 1 : 0) ? 3 : 4, 4, "Nested ternary where first expression evaluates to 0, which is falsy");
|
|
28
|
+
assert.strictEqual(true ? (false ? 10 : 20) : 30, 20, "Nested ternary in the true branch of outer ternary");
|
|
29
|
+
assert.strictEqual(false ? (false ? 10 : 20) : 30, 30, "Nested ternary in the false branch of outer ternary");
|
|
30
|
+
assert.deepEqual(undefined ? undefined : null, null, "Condition is falsy, returns null");
|
|
31
|
+
assert.strictEqual(null ? undefined : undefined, undefined, "Condition is falsy, returns undefined");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("conditionalExpression - Origami", async() => {
|
|
35
|
+
assert.strictEqual(await oriEval("true ? 42 : 0"), 42, "Condition is true, evaluates and returns the first operand");
|
|
36
|
+
assert.strictEqual(await oriEval("false ? 42 : 0"), 0, "Condition is false, evaluates and returns the second operand");
|
|
37
|
+
assert.strictEqual(await oriEval("1 ? 'yes' : 'no'"), "yes", "Truthy condition with string operands");
|
|
38
|
+
assert.strictEqual(await oriEval("0 ? 'yes' : 'no'"), "no", "Falsy condition with string operands");
|
|
39
|
+
assert.strictEqual(await oriEval("'non-empty' ? 1 : 2"), 1, "Truthy string condition with numeric operands");
|
|
40
|
+
assert.strictEqual(await oriEval("'' ? 1 : 2"), 2, "Falsy string condition with numeric operands");
|
|
41
|
+
assert.strictEqual(await oriEval("null ? 'a' : 'b'"), "b", "Falsy null condition");
|
|
42
|
+
assert.strictEqual(await oriEval("undefined ? 'a' : 'b'"), "b", "Falsy undefined condition");
|
|
43
|
+
assert.strictEqual(await oriEval("NaN ? 'a' : 'b'"), "b", "Falsy NaN condition");
|
|
44
|
+
assert.strictEqual(await oriEval("42 ? true : false"), true, "Truthy numeric condition with boolean operands");
|
|
45
|
+
assert.strictEqual(await oriEval("0 ? true : false"), false, "Falsy numeric condition with boolean operands");
|
|
46
|
+
assert.strictEqual(await oriEval("[] ? 'array' : 'no array'"), "array", "Truthy array condition");
|
|
47
|
+
assert.strictEqual(await oriEval("{} ? 'object' : 'no object'"), "object", "Truthy object condition");
|
|
48
|
+
assert.strictEqual(await oriEval("false ? null : undefined"), undefined, "Condition is false, returns undefined");
|
|
49
|
+
assert.deepEqual(await oriEval("null ? null : null"), null, "Condition is falsy, returns null");
|
|
50
|
+
assert.strictEqual(await oriEval("true ? NaN : 42"), NaN, "Condition is true, evaluates and returns NaN");
|
|
51
|
+
assert.strictEqual(await oriEval("(true ? 1 : 2) ? 3 : 4"), 3, "Nested ternary where first expression evaluates to 1, which is truthy");
|
|
52
|
+
assert.strictEqual(await oriEval("(false ? 1 : 2) ? 3 : 4"), 3, "Nested ternary where first expression evaluates to 2, which is truthy");
|
|
53
|
+
assert.strictEqual(await oriEval("(false ? 1 : 0) ? 3 : 4"), 4, "Nested ternary where first expression evaluates to 0, which is falsy");
|
|
54
|
+
assert.strictEqual(await oriEval("true ? (false ? 10 : 20) : 30"), 20, "Nested ternary in the true branch of outer ternary");
|
|
55
|
+
assert.strictEqual(await oriEval("false ? (false ? 10 : 20) : 30"), 30, "Nested ternary in the false branch of outer ternary");
|
|
56
|
+
assert.deepEqual(await oriEval("undefined ? undefined : null"), null, "Condition is falsy, returns null");
|
|
57
|
+
assert.strictEqual(await oriEval("null ? undefined : undefined"), undefined, "Condition is falsy, returns undefined");
|
|
58
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Generated tests -- do not edit directly
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { describe } from "node:test";
|
|
6
|
+
import oriEval from "../generator/oriEval.js";
|
|
7
|
+
|
|
8
|
+
describe("logicalAndExpression - JavaScript", () => {
|
|
9
|
+
assert.strictEqual(true && true, true, "Both operands are true");
|
|
10
|
+
assert.strictEqual(true && false, false, "First operand is true, second is false");
|
|
11
|
+
assert.strictEqual(false && true, false, "First operand is false, second is true");
|
|
12
|
+
assert.strictEqual(false && false, false, "Both operands are false");
|
|
13
|
+
assert.strictEqual(false && (1 / 0), false, "Short-circuit evaluation: first operand false, second not evaluated");
|
|
14
|
+
assert.strictEqual(true && 42, 42, "Short-circuit evaluation: first operand true, evaluates second");
|
|
15
|
+
assert.strictEqual(0 && true, 0, "Short-circuiting with falsy value (0)");
|
|
16
|
+
assert.strictEqual(true && 'string', "string", "Truthy value with string");
|
|
17
|
+
assert.strictEqual(false && 'string', false, "Falsy value with string");
|
|
18
|
+
assert.strictEqual(1 && 0, 0, "Truthy numeric value with falsy numeric value");
|
|
19
|
+
assert.strictEqual(0 && 1, 0, "Falsy numeric value with truthy numeric value");
|
|
20
|
+
assert.strictEqual('' && 'non-empty string', "", "Falsy string value with truthy string");
|
|
21
|
+
assert.strictEqual('non-empty string' && '', "", "Truthy string with falsy string");
|
|
22
|
+
assert.strictEqual({} && true, true, "Empty object as first operand");
|
|
23
|
+
assert.deepEqual(true && {}, {}, "Empty object as second operand");
|
|
24
|
+
assert.strictEqual([] && true, true, "Array as first operand");
|
|
25
|
+
assert.deepEqual(true && [], [], "Array as second operand");
|
|
26
|
+
assert.deepEqual(null && true, null, "Null as first operand");
|
|
27
|
+
assert.deepEqual(true && null, null, "Null as second operand");
|
|
28
|
+
assert.strictEqual(undefined && true, undefined, "Undefined as first operand");
|
|
29
|
+
assert.strictEqual(true && undefined, undefined, "Undefined as second operand");
|
|
30
|
+
assert.strictEqual(NaN && true, NaN, "NaN as first operand");
|
|
31
|
+
assert.strictEqual(true && NaN, NaN, "NaN as second operand");
|
|
32
|
+
assert.strictEqual((true && false) && true, false, "Nested logical ANDs with a false in the middle");
|
|
33
|
+
assert.strictEqual((true && true) && true, true, "Nested logical ANDs with all true");
|
|
34
|
+
assert.strictEqual(true && (true && false), false, "Nested logical ANDs with false in inner");
|
|
35
|
+
assert.strictEqual((true && (false && true)), false, "Complex nesting with false at inner-most");
|
|
36
|
+
assert.strictEqual(true && (3 || 0), 3, "Logical AND with logical OR");
|
|
37
|
+
assert.strictEqual(true && (0 || 3), 3, "Logical AND with logical OR and falsy values");
|
|
38
|
+
assert.strictEqual('' && false, "", "Falsy string and false");
|
|
39
|
+
assert.strictEqual(false && '', false, "False and falsy string");
|
|
40
|
+
assert.strictEqual(undefined && null, undefined, "Undefined and null");
|
|
41
|
+
assert.deepEqual(null && undefined, null, "Null and undefined");
|
|
42
|
+
assert.strictEqual((false && true) && undefined, false, "Short-circuiting nested AND with undefined");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("logicalAndExpression - Origami", async() => {
|
|
46
|
+
assert.strictEqual(await oriEval("true && true"), true, "Both operands are true");
|
|
47
|
+
assert.strictEqual(await oriEval("true && false"), false, "First operand is true, second is false");
|
|
48
|
+
assert.strictEqual(await oriEval("false && true"), false, "First operand is false, second is true");
|
|
49
|
+
assert.strictEqual(await oriEval("false && false"), false, "Both operands are false");
|
|
50
|
+
assert.strictEqual(await oriEval("false && (1 / 0)"), false, "Short-circuit evaluation: first operand false, second not evaluated");
|
|
51
|
+
assert.strictEqual(await oriEval("true && 42"), 42, "Short-circuit evaluation: first operand true, evaluates second");
|
|
52
|
+
assert.strictEqual(await oriEval("0 && true"), 0, "Short-circuiting with falsy value (0)");
|
|
53
|
+
assert.strictEqual(await oriEval("true && 'string'"), "string", "Truthy value with string");
|
|
54
|
+
assert.strictEqual(await oriEval("false && 'string'"), false, "Falsy value with string");
|
|
55
|
+
assert.strictEqual(await oriEval("1 && 0"), 0, "Truthy numeric value with falsy numeric value");
|
|
56
|
+
assert.strictEqual(await oriEval("0 && 1"), 0, "Falsy numeric value with truthy numeric value");
|
|
57
|
+
assert.strictEqual(await oriEval("'' && 'non-empty string'"), "", "Falsy string value with truthy string");
|
|
58
|
+
assert.strictEqual(await oriEval("'non-empty string' && ''"), "", "Truthy string with falsy string");
|
|
59
|
+
assert.strictEqual(await oriEval("{} && true"), true, "Empty object as first operand");
|
|
60
|
+
assert.deepEqual(await oriEval("true && {}"), {}, "Empty object as second operand");
|
|
61
|
+
assert.strictEqual(await oriEval("[] && true"), true, "Array as first operand");
|
|
62
|
+
assert.deepEqual(await oriEval("true && []"), [], "Array as second operand");
|
|
63
|
+
assert.deepEqual(await oriEval("null && true"), null, "Null as first operand");
|
|
64
|
+
assert.deepEqual(await oriEval("true && null"), null, "Null as second operand");
|
|
65
|
+
assert.strictEqual(await oriEval("undefined && true"), undefined, "Undefined as first operand");
|
|
66
|
+
assert.strictEqual(await oriEval("true && undefined"), undefined, "Undefined as second operand");
|
|
67
|
+
assert.strictEqual(await oriEval("NaN && true"), NaN, "NaN as first operand");
|
|
68
|
+
assert.strictEqual(await oriEval("true && NaN"), NaN, "NaN as second operand");
|
|
69
|
+
assert.strictEqual(await oriEval("(true && false) && true"), false, "Nested logical ANDs with a false in the middle");
|
|
70
|
+
assert.strictEqual(await oriEval("(true && true) && true"), true, "Nested logical ANDs with all true");
|
|
71
|
+
assert.strictEqual(await oriEval("true && (true && false)"), false, "Nested logical ANDs with false in inner");
|
|
72
|
+
assert.strictEqual(await oriEval("(true && (false && true))"), false, "Complex nesting with false at inner-most");
|
|
73
|
+
assert.strictEqual(await oriEval("true && (3 || 0)"), 3, "Logical AND with logical OR");
|
|
74
|
+
assert.strictEqual(await oriEval("true && (0 || 3)"), 3, "Logical AND with logical OR and falsy values");
|
|
75
|
+
assert.strictEqual(await oriEval("'' && false"), "", "Falsy string and false");
|
|
76
|
+
assert.strictEqual(await oriEval("false && ''"), false, "False and falsy string");
|
|
77
|
+
assert.strictEqual(await oriEval("undefined && null"), undefined, "Undefined and null");
|
|
78
|
+
assert.deepEqual(await oriEval("null && undefined"), null, "Null and undefined");
|
|
79
|
+
assert.strictEqual(await oriEval("(false && true) && undefined"), false, "Short-circuiting nested AND with undefined");
|
|
80
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Generated tests -- do not edit directly
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { describe } from "node:test";
|
|
6
|
+
import oriEval from "../generator/oriEval.js";
|
|
7
|
+
|
|
8
|
+
describe("logicalOrExpression - JavaScript", () => {
|
|
9
|
+
assert.strictEqual(true || true, true, "Both operands are true");
|
|
10
|
+
assert.strictEqual(true || false, true, "First operand is true, second is false");
|
|
11
|
+
assert.strictEqual(false || true, true, "First operand is false, second is true");
|
|
12
|
+
assert.strictEqual(false || false, false, "Both operands are false");
|
|
13
|
+
assert.strictEqual(false || 42, 42, "Short-circuit evaluation: first operand false, evaluates second");
|
|
14
|
+
assert.strictEqual(0 || true, true, "Falsy value (0) with truthy second operand");
|
|
15
|
+
assert.strictEqual(true || 'string', true, "Truthy first operand, string second operand not evaluated");
|
|
16
|
+
assert.strictEqual(false || 'string', "string", "Falsy first operand, evaluates string second operand");
|
|
17
|
+
assert.strictEqual(1 || 0, 1, "Truthy numeric value with falsy numeric value");
|
|
18
|
+
assert.strictEqual(0 || 1, 1, "Falsy numeric value with truthy numeric value");
|
|
19
|
+
assert.strictEqual('' || 'non-empty string', "non-empty string", "Falsy string value with truthy string");
|
|
20
|
+
assert.strictEqual('non-empty string' || '', "non-empty string", "Truthy string with falsy string");
|
|
21
|
+
assert.deepEqual({} || true, {}, "Empty object as first operand");
|
|
22
|
+
assert.strictEqual(true || {}, true, "True as first operand, object not evaluated");
|
|
23
|
+
assert.deepEqual([] || true, [], "Array as first operand");
|
|
24
|
+
assert.strictEqual(true || [], true, "True as first operand, array not evaluated");
|
|
25
|
+
assert.strictEqual(null || true, true, "Null as first operand");
|
|
26
|
+
assert.strictEqual(true || null, true, "True as first operand, null not evaluated");
|
|
27
|
+
assert.strictEqual(undefined || true, true, "Undefined as first operand");
|
|
28
|
+
assert.strictEqual(true || undefined, true, "True as first operand, undefined not evaluated");
|
|
29
|
+
assert.strictEqual(NaN || true, true, "NaN as first operand");
|
|
30
|
+
assert.strictEqual(true || NaN, true, "True as first operand, NaN not evaluated");
|
|
31
|
+
assert.strictEqual((false || true) || false, true, "Nested logical ORs with a true in the middle");
|
|
32
|
+
assert.strictEqual((false || false) || true, true, "Nested logical ORs with a true at the end");
|
|
33
|
+
assert.strictEqual(false || (false || true), true, "Nested logical ORs with true in inner");
|
|
34
|
+
assert.strictEqual((false || (true || false)), true, "Complex nesting with true at inner-most");
|
|
35
|
+
assert.strictEqual(false || (3 && 0), 0, "Logical OR with logical AND and falsy result");
|
|
36
|
+
assert.strictEqual(false || (0 && 3), 0, "Logical OR with logical AND and falsy first operand");
|
|
37
|
+
assert.strictEqual('' || false, false, "Falsy string and false");
|
|
38
|
+
assert.strictEqual(false || '', "", "False and falsy string");
|
|
39
|
+
assert.deepEqual(undefined || null, null, "Undefined and null");
|
|
40
|
+
assert.strictEqual(null || undefined, undefined, "Null and undefined");
|
|
41
|
+
assert.strictEqual((true || false) || undefined, true, "Short-circuiting nested OR with undefined");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("logicalOrExpression - Origami", async() => {
|
|
45
|
+
assert.strictEqual(await oriEval("true || true"), true, "Both operands are true");
|
|
46
|
+
assert.strictEqual(await oriEval("true || false"), true, "First operand is true, second is false");
|
|
47
|
+
assert.strictEqual(await oriEval("false || true"), true, "First operand is false, second is true");
|
|
48
|
+
assert.strictEqual(await oriEval("false || false"), false, "Both operands are false");
|
|
49
|
+
assert.strictEqual(await oriEval("false || 42"), 42, "Short-circuit evaluation: first operand false, evaluates second");
|
|
50
|
+
assert.strictEqual(await oriEval("0 || true"), true, "Falsy value (0) with truthy second operand");
|
|
51
|
+
assert.strictEqual(await oriEval("true || 'string'"), true, "Truthy first operand, string second operand not evaluated");
|
|
52
|
+
assert.strictEqual(await oriEval("false || 'string'"), "string", "Falsy first operand, evaluates string second operand");
|
|
53
|
+
assert.strictEqual(await oriEval("1 || 0"), 1, "Truthy numeric value with falsy numeric value");
|
|
54
|
+
assert.strictEqual(await oriEval("0 || 1"), 1, "Falsy numeric value with truthy numeric value");
|
|
55
|
+
assert.strictEqual(await oriEval("'' || 'non-empty string'"), "non-empty string", "Falsy string value with truthy string");
|
|
56
|
+
assert.strictEqual(await oriEval("'non-empty string' || ''"), "non-empty string", "Truthy string with falsy string");
|
|
57
|
+
assert.deepEqual(await oriEval("{} || true"), {}, "Empty object as first operand");
|
|
58
|
+
assert.strictEqual(await oriEval("true || {}"), true, "True as first operand, object not evaluated");
|
|
59
|
+
assert.deepEqual(await oriEval("[] || true"), [], "Array as first operand");
|
|
60
|
+
assert.strictEqual(await oriEval("true || []"), true, "True as first operand, array not evaluated");
|
|
61
|
+
assert.strictEqual(await oriEval("null || true"), true, "Null as first operand");
|
|
62
|
+
assert.strictEqual(await oriEval("true || null"), true, "True as first operand, null not evaluated");
|
|
63
|
+
assert.strictEqual(await oriEval("undefined || true"), true, "Undefined as first operand");
|
|
64
|
+
assert.strictEqual(await oriEval("true || undefined"), true, "True as first operand, undefined not evaluated");
|
|
65
|
+
assert.strictEqual(await oriEval("NaN || true"), true, "NaN as first operand");
|
|
66
|
+
assert.strictEqual(await oriEval("true || NaN"), true, "True as first operand, NaN not evaluated");
|
|
67
|
+
assert.strictEqual(await oriEval("(false || true) || false"), true, "Nested logical ORs with a true in the middle");
|
|
68
|
+
assert.strictEqual(await oriEval("(false || false) || true"), true, "Nested logical ORs with a true at the end");
|
|
69
|
+
assert.strictEqual(await oriEval("false || (false || true)"), true, "Nested logical ORs with true in inner");
|
|
70
|
+
assert.strictEqual(await oriEval("(false || (true || false))"), true, "Complex nesting with true at inner-most");
|
|
71
|
+
assert.strictEqual(await oriEval("false || (3 && 0)"), 0, "Logical OR with logical AND and falsy result");
|
|
72
|
+
assert.strictEqual(await oriEval("false || (0 && 3)"), 0, "Logical OR with logical AND and falsy first operand");
|
|
73
|
+
assert.strictEqual(await oriEval("'' || false"), false, "Falsy string and false");
|
|
74
|
+
assert.strictEqual(await oriEval("false || ''"), "", "False and falsy string");
|
|
75
|
+
assert.deepEqual(await oriEval("undefined || null"), null, "Undefined and null");
|
|
76
|
+
assert.strictEqual(await oriEval("null || undefined"), undefined, "Null and undefined");
|
|
77
|
+
assert.strictEqual(await oriEval("(true || false) || undefined"), true, "Short-circuiting nested OR with undefined");
|
|
78
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated tests -- do not edit directly
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { describe } from "node:test";
|
|
6
|
+
import oriEval from "../generator/oriEval.js";
|
|
7
|
+
|
|
8
|
+
describe("nullishCoalescingExpression - JavaScript", () => {
|
|
9
|
+
assert.strictEqual(null ?? 42, 42, "Left operand is null, returns right operand");
|
|
10
|
+
assert.strictEqual(undefined ?? 42, 42, "Left operand is undefined, returns right operand");
|
|
11
|
+
assert.strictEqual(0 ?? 42, 0, "Left operand is 0 (falsy but not nullish), returns left operand");
|
|
12
|
+
assert.strictEqual('' ?? 'default', "", "Left operand is an empty string (falsy but not nullish), returns left operand");
|
|
13
|
+
assert.strictEqual(false ?? true, false, "Left operand is false (falsy but not nullish), returns left operand");
|
|
14
|
+
assert.strictEqual(42 ?? 0, 42, "Left operand is a non-nullish truthy value, returns left operand");
|
|
15
|
+
assert.strictEqual(null ?? undefined, undefined, "Left operand is null, returns right operand which is undefined");
|
|
16
|
+
assert.deepEqual(undefined ?? null, null, "Left operand is undefined, returns right operand which is null");
|
|
17
|
+
assert.strictEqual(NaN ?? 42, NaN, "Left operand is NaN (not nullish), returns left operand");
|
|
18
|
+
assert.deepEqual([] ?? 'default', [], "Left operand is an empty array (not nullish), returns left operand");
|
|
19
|
+
assert.deepEqual({} ?? 'default', {}, "Left operand is an empty object (not nullish), returns left operand");
|
|
20
|
+
assert.strictEqual((null ?? 42) ?? 50, 42, "Nested nullish coalescing, first nullish operand replaced, second ignored");
|
|
21
|
+
assert.strictEqual((undefined ?? null) ?? 'fallback', "fallback", "Nested nullish coalescing");
|
|
22
|
+
assert.strictEqual((0 ?? null) ?? 'fallback', 0, "Nested nullish coalescing with falsy but non-nullish value");
|
|
23
|
+
assert.strictEqual(null ?? (undefined ?? 42), 42, "Nullish coalescing in the right operand");
|
|
24
|
+
assert.strictEqual(null ?? null ?? null ?? 'fallback', "fallback", "Chained nullish coalescing, resolves to the last non-nullish value");
|
|
25
|
+
assert.strictEqual(null ?? (false ?? 'default'), false, "Right operand evaluates to non-nullish falsy value");
|
|
26
|
+
assert.strictEqual(null ?? (true ?? 'default'), true, "Right operand evaluates to truthy value");
|
|
27
|
+
assert.strictEqual(42 ?? (null ?? 0), 42, "Left operand is not nullish, ignores right operand");
|
|
28
|
+
assert.strictEqual(undefined ?? null ?? 'value', "value", "Chained nullish coalescing with undefined and null");
|
|
29
|
+
assert.strictEqual((NaN ?? null) ?? 42, NaN, "Left operand is NaN, not nullish, returns NaN");
|
|
30
|
+
assert.strictEqual((undefined ?? NaN) ?? 42, NaN, "Right operand resolves to NaN");
|
|
31
|
+
assert.strictEqual(null ?? 'default' ?? 42, "default", "Chained nullish coalescing, resolves to first non-nullish value");
|
|
32
|
+
assert.strictEqual('' ?? 'default' ?? 42, "", "Falsy but non-nullish value, returns left operand");
|
|
33
|
+
assert.strictEqual(null ?? undefined ?? NaN, NaN, "Chained nullish coalescing, resolves to NaN as the first non-nullish value");
|
|
34
|
+
assert.strictEqual((null ?? null) ?? undefined, undefined, "Nested nullish coalescing resolves to undefined");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("nullishCoalescingExpression - Origami", async() => {
|
|
38
|
+
assert.strictEqual(await oriEval("null ?? 42"), 42, "Left operand is null, returns right operand");
|
|
39
|
+
assert.strictEqual(await oriEval("undefined ?? 42"), 42, "Left operand is undefined, returns right operand");
|
|
40
|
+
assert.strictEqual(await oriEval("0 ?? 42"), 0, "Left operand is 0 (falsy but not nullish), returns left operand");
|
|
41
|
+
assert.strictEqual(await oriEval("'' ?? 'default'"), "", "Left operand is an empty string (falsy but not nullish), returns left operand");
|
|
42
|
+
assert.strictEqual(await oriEval("false ?? true"), false, "Left operand is false (falsy but not nullish), returns left operand");
|
|
43
|
+
assert.strictEqual(await oriEval("42 ?? 0"), 42, "Left operand is a non-nullish truthy value, returns left operand");
|
|
44
|
+
assert.strictEqual(await oriEval("null ?? undefined"), undefined, "Left operand is null, returns right operand which is undefined");
|
|
45
|
+
assert.deepEqual(await oriEval("undefined ?? null"), null, "Left operand is undefined, returns right operand which is null");
|
|
46
|
+
assert.strictEqual(await oriEval("NaN ?? 42"), NaN, "Left operand is NaN (not nullish), returns left operand");
|
|
47
|
+
assert.deepEqual(await oriEval("[] ?? 'default'"), [], "Left operand is an empty array (not nullish), returns left operand");
|
|
48
|
+
assert.deepEqual(await oriEval("{} ?? 'default'"), {}, "Left operand is an empty object (not nullish), returns left operand");
|
|
49
|
+
assert.strictEqual(await oriEval("(null ?? 42) ?? 50"), 42, "Nested nullish coalescing, first nullish operand replaced, second ignored");
|
|
50
|
+
assert.strictEqual(await oriEval("(undefined ?? null) ?? 'fallback'"), "fallback", "Nested nullish coalescing");
|
|
51
|
+
assert.strictEqual(await oriEval("(0 ?? null) ?? 'fallback'"), 0, "Nested nullish coalescing with falsy but non-nullish value");
|
|
52
|
+
assert.strictEqual(await oriEval("null ?? (undefined ?? 42)"), 42, "Nullish coalescing in the right operand");
|
|
53
|
+
assert.strictEqual(await oriEval("null ?? null ?? null ?? 'fallback'"), "fallback", "Chained nullish coalescing, resolves to the last non-nullish value");
|
|
54
|
+
assert.strictEqual(await oriEval("null ?? (false ?? 'default')"), false, "Right operand evaluates to non-nullish falsy value");
|
|
55
|
+
assert.strictEqual(await oriEval("null ?? (true ?? 'default')"), true, "Right operand evaluates to truthy value");
|
|
56
|
+
assert.strictEqual(await oriEval("42 ?? (null ?? 0)"), 42, "Left operand is not nullish, ignores right operand");
|
|
57
|
+
assert.strictEqual(await oriEval("undefined ?? null ?? 'value'"), "value", "Chained nullish coalescing with undefined and null");
|
|
58
|
+
assert.strictEqual(await oriEval("(NaN ?? null) ?? 42"), NaN, "Left operand is NaN, not nullish, returns NaN");
|
|
59
|
+
assert.strictEqual(await oriEval("(undefined ?? NaN) ?? 42"), NaN, "Right operand resolves to NaN");
|
|
60
|
+
assert.strictEqual(await oriEval("null ?? 'default' ?? 42"), "default", "Chained nullish coalescing, resolves to first non-nullish value");
|
|
61
|
+
assert.strictEqual(await oriEval("'' ?? 'default' ?? 42"), "", "Falsy but non-nullish value, returns left operand");
|
|
62
|
+
assert.strictEqual(await oriEval("null ?? undefined ?? NaN"), NaN, "Chained nullish coalescing, resolves to NaN as the first non-nullish value");
|
|
63
|
+
assert.strictEqual(await oriEval("(null ?? null) ?? undefined"), undefined, "Nested nullish coalescing resolves to undefined");
|
|
64
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Validate that the tests produce the expected results in JavaScript itself.
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import * as YAMLModule from "yaml";
|
|
7
|
+
|
|
8
|
+
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
9
|
+
// recognize, so we have to handle two ways to accommodate Node and the browser.
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
12
|
+
|
|
13
|
+
export default async function generateTests(inputDirectory, outputDirectory) {
|
|
14
|
+
const filenames = await fs.readdir(inputDirectory);
|
|
15
|
+
const yamlFilenames = filenames.filter((filename) =>
|
|
16
|
+
filename.endsWith(".yaml")
|
|
17
|
+
);
|
|
18
|
+
for (const yamlFilename of yamlFilenames) {
|
|
19
|
+
const basename = path.basename(yamlFilename, ".yaml");
|
|
20
|
+
|
|
21
|
+
const casesPath = path.join(inputDirectory, yamlFilename);
|
|
22
|
+
const text = String(await fs.readFile(casesPath));
|
|
23
|
+
const cases = YAML.parse(text);
|
|
24
|
+
const transformed = cases.map(transformCase);
|
|
25
|
+
const result = tests(basename, transformed);
|
|
26
|
+
|
|
27
|
+
const outputName = basename + ".test.js";
|
|
28
|
+
const outputPath = path.join(outputDirectory, outputName);
|
|
29
|
+
await fs.writeFile(outputPath, result);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function javaScriptTest({ assertType, source, expectedJs, description }) {
|
|
34
|
+
return ` assert.${assertType}(${source}, ${expectedJs}, "${description}");`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function origamiTest({ assertType, source, expectedJs, description }) {
|
|
38
|
+
return ` assert.${assertType}(await oriEval("${source}"), ${expectedJs}, "${description}");`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function tests(suiteName, cases) {
|
|
42
|
+
return `// Generated tests -- do not edit directly
|
|
43
|
+
// @ts-nocheck
|
|
44
|
+
|
|
45
|
+
import assert from "node:assert";
|
|
46
|
+
import { describe } from "node:test";
|
|
47
|
+
import oriEval from "../generator/oriEval.js";
|
|
48
|
+
|
|
49
|
+
describe("${suiteName} - JavaScript", () => {
|
|
50
|
+
${cases.map(javaScriptTest).join("\n")}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("${suiteName} - Origami", async() => {
|
|
54
|
+
${cases.map(origamiTest).join("\n")}
|
|
55
|
+
});`;
|
|
56
|
+
}
|
|
57
|
+
// Transform parsed YAML values into values suitable for testing
|
|
58
|
+
function transformCase({ description, expected, source }) {
|
|
59
|
+
const markers = {
|
|
60
|
+
__null__: null,
|
|
61
|
+
__undefined__: undefined,
|
|
62
|
+
__NaN__: NaN,
|
|
63
|
+
};
|
|
64
|
+
if (expected in markers) {
|
|
65
|
+
expected = markers[expected];
|
|
66
|
+
}
|
|
67
|
+
const assertType = typeof expected === "object" ? "deepEqual" : "strictEqual";
|
|
68
|
+
const expectedJs =
|
|
69
|
+
typeof expected === "string"
|
|
70
|
+
? `"${expected}"`
|
|
71
|
+
: typeof expected === "object" && expected !== null
|
|
72
|
+
? JSON.stringify(expected)
|
|
73
|
+
: expected;
|
|
74
|
+
return { assertType, description, expected, expectedJs, source };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
78
|
+
const casesDirectory = path.join(dirname, "../cases");
|
|
79
|
+
const generatedDirectory = path.join(dirname, "../generated");
|
|
80
|
+
await generateTests(casesDirectory, generatedDirectory);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ObjectTree } from "@weborigami/async-tree";
|
|
2
|
+
import * as compile from "../../src/compiler/compile.js";
|
|
3
|
+
|
|
4
|
+
export default async function oriEval(source) {
|
|
5
|
+
const builtins = new ObjectTree({
|
|
6
|
+
false: false,
|
|
7
|
+
NaN: NaN,
|
|
8
|
+
null: null,
|
|
9
|
+
true: true,
|
|
10
|
+
undefined: undefined,
|
|
11
|
+
});
|
|
12
|
+
const compiled = compile.program(source);
|
|
13
|
+
const result = await compiled.call(builtins);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -19,21 +19,7 @@ describe("ops", () => {
|
|
|
19
19
|
tree.parent = root;
|
|
20
20
|
const code = createCode([ops.builtin, "a"]);
|
|
21
21
|
const result = await evaluate.call(tree, code);
|
|
22
|
-
assert.
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("ops.cache looks up a value in scope and memoizes it", async () => {
|
|
26
|
-
let count = 0;
|
|
27
|
-
const tree = new ObjectTree({
|
|
28
|
-
get count() {
|
|
29
|
-
return ++count;
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
const code = createCode([ops.cache, "count", {}]);
|
|
33
|
-
const result = await evaluate.call(tree, code);
|
|
34
|
-
assert.equal(result, 1);
|
|
35
|
-
const result2 = await evaluate.call(tree, code);
|
|
36
|
-
assert.equal(result2, 1);
|
|
22
|
+
assert.strictEqual(result, 1);
|
|
37
23
|
});
|
|
38
24
|
|
|
39
25
|
test("ops.concat concatenates tree value text", async () => {
|
|
@@ -44,7 +30,39 @@ describe("ops", () => {
|
|
|
44
30
|
const code = createCode([ops.concat, "Hello, ", [ops.scope, "name"], "."]);
|
|
45
31
|
|
|
46
32
|
const result = await evaluate.call(scope, code);
|
|
47
|
-
assert.
|
|
33
|
+
assert.strictEqual(result, "Hello, world.");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("ops.conditional", async () => {
|
|
37
|
+
assert.strictEqual(await ops.conditional(true, trueFn, falseFn), true);
|
|
38
|
+
assert.strictEqual(await ops.conditional(true, falseFn, trueFn), false);
|
|
39
|
+
assert.strictEqual(await ops.conditional(false, trueFn, falseFn), false);
|
|
40
|
+
assert.strictEqual(await ops.conditional(false, falseFn, trueFn), true);
|
|
41
|
+
|
|
42
|
+
// Short-circuiting
|
|
43
|
+
assert.strictEqual(await ops.conditional(false, errorFn, trueFn), true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("ops.equal", async () => {
|
|
47
|
+
assert.strictEqual(await ops.equal(1, 1), true);
|
|
48
|
+
assert.strictEqual(await ops.equal(1, 2), false);
|
|
49
|
+
assert.strictEqual(await ops.equal("1", 1), true);
|
|
50
|
+
assert.strictEqual(await ops.equal("1", "1"), true);
|
|
51
|
+
assert.strictEqual(await ops.equal(null, undefined), true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("ops.external looks up a value in scope and memoizes it", async () => {
|
|
55
|
+
let count = 0;
|
|
56
|
+
const tree = new ObjectTree({
|
|
57
|
+
get count() {
|
|
58
|
+
return ++count;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const code = createCode([ops.external, "count", {}]);
|
|
62
|
+
const result = await evaluate.call(tree, code);
|
|
63
|
+
assert.strictEqual(result, 1);
|
|
64
|
+
const result2 = await evaluate.call(tree, code);
|
|
65
|
+
assert.strictEqual(result2, 1);
|
|
48
66
|
});
|
|
49
67
|
|
|
50
68
|
test("ops.inherited searches inherited scope", async () => {
|
|
@@ -58,10 +76,17 @@ describe("ops", () => {
|
|
|
58
76
|
child.parent = parent;
|
|
59
77
|
const code = createCode([ops.inherited, "a"]);
|
|
60
78
|
const result = await evaluate.call(child, code);
|
|
61
|
-
assert.
|
|
79
|
+
assert.strictEqual(result, 1);
|
|
62
80
|
});
|
|
63
81
|
|
|
64
|
-
test("ops.lambda defines a function", async () => {
|
|
82
|
+
test("ops.lambda defines a function with no inputs", async () => {
|
|
83
|
+
const code = createCode([ops.lambda, [], [ops.literal, "result"]]);
|
|
84
|
+
const fn = await evaluate.call(null, code);
|
|
85
|
+
const result = await fn.call();
|
|
86
|
+
assert.strictEqual(result, "result");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("ops.lambda defines a function with underscore input", async () => {
|
|
65
90
|
const scope = new ObjectTree({
|
|
66
91
|
message: "Hello",
|
|
67
92
|
});
|
|
@@ -70,7 +95,7 @@ describe("ops", () => {
|
|
|
70
95
|
|
|
71
96
|
const fn = await evaluate.call(scope, code);
|
|
72
97
|
const result = await fn.call(scope);
|
|
73
|
-
assert.
|
|
98
|
+
assert.strictEqual(result, "Hello");
|
|
74
99
|
});
|
|
75
100
|
|
|
76
101
|
test("ops.lambda adds input parameters to scope", async () => {
|
|
@@ -81,7 +106,64 @@ describe("ops", () => {
|
|
|
81
106
|
]);
|
|
82
107
|
const fn = await evaluate.call(null, code);
|
|
83
108
|
const result = await fn("x", "y");
|
|
84
|
-
assert.
|
|
109
|
+
assert.strictEqual(result, "yx");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("ops.logicalAnd", async () => {
|
|
113
|
+
assert.strictEqual(await ops.logicalAnd(true, trueFn), true);
|
|
114
|
+
assert.strictEqual(await ops.logicalAnd(true, falseFn), false);
|
|
115
|
+
assert.strictEqual(await ops.logicalAnd(false, trueFn), false);
|
|
116
|
+
assert.strictEqual(await ops.logicalAnd(false, falseFn), false);
|
|
117
|
+
|
|
118
|
+
assert.strictEqual(await ops.logicalAnd(true, "hi"), "hi");
|
|
119
|
+
|
|
120
|
+
// Short-circuiting
|
|
121
|
+
assert.strictEqual(await ops.logicalAnd(false, errorFn), false);
|
|
122
|
+
assert.strictEqual(await ops.logicalAnd(0, true), 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("ops.logicalNot", async () => {
|
|
126
|
+
assert.strictEqual(await ops.logicalNot(true), false);
|
|
127
|
+
assert.strictEqual(await ops.logicalNot(false), true);
|
|
128
|
+
assert.strictEqual(await ops.logicalNot(0), true);
|
|
129
|
+
assert.strictEqual(await ops.logicalNot(1), false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("ops.logicalOr", async () => {
|
|
133
|
+
assert.strictEqual(await ops.logicalOr(true, trueFn), true);
|
|
134
|
+
assert.strictEqual(await ops.logicalOr(true, falseFn), true);
|
|
135
|
+
assert.strictEqual(await ops.logicalOr(false, trueFn), true);
|
|
136
|
+
assert.strictEqual(await ops.logicalOr(false, falseFn), false);
|
|
137
|
+
|
|
138
|
+
assert.strictEqual(await ops.logicalOr(false, "hi"), "hi");
|
|
139
|
+
|
|
140
|
+
// Short-circuiting
|
|
141
|
+
assert.strictEqual(await ops.logicalOr(true, errorFn), true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("ops.notEqual", async () => {
|
|
145
|
+
assert.strictEqual(await ops.notEqual(1, 1), false);
|
|
146
|
+
assert.strictEqual(await ops.notEqual(1, 2), true);
|
|
147
|
+
assert.strictEqual(await ops.notEqual("1", 1), false);
|
|
148
|
+
assert.strictEqual(await ops.notEqual("1", "1"), false);
|
|
149
|
+
assert.strictEqual(await ops.notEqual(null, undefined), false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("ops.notStrictEqual", async () => {
|
|
153
|
+
assert.strictEqual(await ops.notStrictEqual(1, 1), false);
|
|
154
|
+
assert.strictEqual(await ops.notStrictEqual(1, 2), true);
|
|
155
|
+
assert.strictEqual(await ops.notStrictEqual("1", 1), true);
|
|
156
|
+
assert.strictEqual(await ops.notStrictEqual("1", "1"), false);
|
|
157
|
+
assert.strictEqual(await ops.notStrictEqual(null, undefined), true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("ops.nullishCoalescing", async () => {
|
|
161
|
+
assert.strictEqual(await ops.nullishCoalescing(1, falseFn), 1);
|
|
162
|
+
assert.strictEqual(await ops.nullishCoalescing(null, trueFn), true);
|
|
163
|
+
assert.strictEqual(await ops.nullishCoalescing(undefined, trueFn), true);
|
|
164
|
+
|
|
165
|
+
// Short-circuiting
|
|
166
|
+
assert.strictEqual(await ops.nullishCoalescing(1, errorFn), 1);
|
|
85
167
|
});
|
|
86
168
|
|
|
87
169
|
test("ops.object instantiates an object", async () => {
|
|
@@ -96,8 +178,8 @@ describe("ops", () => {
|
|
|
96
178
|
]);
|
|
97
179
|
|
|
98
180
|
const result = await evaluate.call(scope, code);
|
|
99
|
-
assert.
|
|
100
|
-
assert.
|
|
181
|
+
assert.strictEqual(result.hello, "HELLO");
|
|
182
|
+
assert.strictEqual(result.world, "WORLD");
|
|
101
183
|
});
|
|
102
184
|
|
|
103
185
|
test("ops.object instantiates an array", async () => {
|
|
@@ -114,11 +196,21 @@ describe("ops", () => {
|
|
|
114
196
|
assert.deepEqual(result, ["Hello", 1, "WORLD"]);
|
|
115
197
|
});
|
|
116
198
|
|
|
199
|
+
test("ops.strictEqual", async () => {
|
|
200
|
+
assert.strictEqual(await ops.strictEqual(1, 1), true);
|
|
201
|
+
assert.strictEqual(await ops.strictEqual(1, 2), false);
|
|
202
|
+
assert.strictEqual(await ops.strictEqual("1", 1), false);
|
|
203
|
+
assert.strictEqual(await ops.strictEqual("1", "1"), true);
|
|
204
|
+
assert.strictEqual(await ops.strictEqual(null, undefined), false);
|
|
205
|
+
assert.strictEqual(await ops.strictEqual(null, null), true);
|
|
206
|
+
assert.strictEqual(await ops.strictEqual(undefined, undefined), true);
|
|
207
|
+
});
|
|
208
|
+
|
|
117
209
|
test("ops.unpack unpacks a value", async () => {
|
|
118
210
|
const fixture = new String("packed");
|
|
119
211
|
/** @type {any} */ (fixture).unpack = async () => "unpacked";
|
|
120
212
|
const result = await ops.unpack.call(null, fixture);
|
|
121
|
-
assert.
|
|
213
|
+
assert.strictEqual(result, "unpacked");
|
|
122
214
|
});
|
|
123
215
|
});
|
|
124
216
|
|
|
@@ -134,3 +226,15 @@ function createCode(array) {
|
|
|
134
226
|
};
|
|
135
227
|
return code;
|
|
136
228
|
}
|
|
229
|
+
|
|
230
|
+
function errorFn() {
|
|
231
|
+
throw new Error("This should not be called");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function falseFn() {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function trueFn() {
|
|
239
|
+
return true;
|
|
240
|
+
}
|