@weborigami/language 0.1.0 → 0.2.1
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 +309 -195
- package/src/compiler/parse.js +2753 -1658
- package/src/compiler/parserHelpers.js +187 -45
- package/src/runtime/ops.js +246 -54
- 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 +588 -359
- 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 +242 -20
package/src/runtime/ops.js
CHANGED
|
@@ -27,6 +27,31 @@ function addOpLabel(op, label) {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export function addition(a, b) {
|
|
31
|
+
return a + b;
|
|
32
|
+
}
|
|
33
|
+
addOpLabel(addition, "«ops.addition»");
|
|
34
|
+
|
|
35
|
+
export function bitwiseAnd(a, b) {
|
|
36
|
+
return a & b;
|
|
37
|
+
}
|
|
38
|
+
addOpLabel(bitwiseAnd, "«ops.bitwiseAnd»");
|
|
39
|
+
|
|
40
|
+
export function bitwiseNot(a) {
|
|
41
|
+
return ~a;
|
|
42
|
+
}
|
|
43
|
+
addOpLabel(bitwiseNot, "«ops.bitwiseNot»");
|
|
44
|
+
|
|
45
|
+
export function bitwiseOr(a, b) {
|
|
46
|
+
return a | b;
|
|
47
|
+
}
|
|
48
|
+
addOpLabel(bitwiseOr, "«ops.bitwiseOr»");
|
|
49
|
+
|
|
50
|
+
export function bitwiseXor(a, b) {
|
|
51
|
+
return a ^ b;
|
|
52
|
+
}
|
|
53
|
+
addOpLabel(bitwiseXor, "«ops.bitwiseXor»");
|
|
54
|
+
|
|
30
55
|
/**
|
|
31
56
|
* Construct an array.
|
|
32
57
|
*
|
|
@@ -48,37 +73,26 @@ export async function builtin(key) {
|
|
|
48
73
|
if (!this) {
|
|
49
74
|
throw new Error("Tried to get the scope of a null or undefined tree.");
|
|
50
75
|
}
|
|
51
|
-
let current = this;
|
|
52
|
-
while (current.parent) {
|
|
53
|
-
current = current.parent;
|
|
54
|
-
}
|
|
55
76
|
|
|
56
|
-
const
|
|
77
|
+
const builtins = Tree.root(this);
|
|
78
|
+
const value = await builtins.get(key);
|
|
57
79
|
if (value === undefined) {
|
|
58
|
-
throw await builtinReferenceError(this,
|
|
80
|
+
throw await builtinReferenceError(this, builtins, key);
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
return value;
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
/**
|
|
65
|
-
*
|
|
66
|
-
* the key is requested, holding on to the value for future requests.
|
|
87
|
+
* JavaScript comma operator, returns the last argument.
|
|
67
88
|
*
|
|
68
|
-
* @
|
|
89
|
+
* @param {...any} args
|
|
90
|
+
* @returns
|
|
69
91
|
*/
|
|
70
|
-
export
|
|
71
|
-
|
|
72
|
-
return cache[key];
|
|
73
|
-
}
|
|
74
|
-
// First save a promise for the value
|
|
75
|
-
const promise = scope.call(this, key);
|
|
76
|
-
cache[key] = promise;
|
|
77
|
-
const value = await promise;
|
|
78
|
-
// Now update with the actual value
|
|
79
|
-
cache[key] = value;
|
|
80
|
-
return value;
|
|
92
|
+
export function comma(...args) {
|
|
93
|
+
return args.at(-1);
|
|
81
94
|
}
|
|
95
|
+
addOpLabel(comma, "«ops.comma»");
|
|
82
96
|
|
|
83
97
|
/**
|
|
84
98
|
* Concatenate the given arguments.
|
|
@@ -91,31 +105,68 @@ export async function concat(...args) {
|
|
|
91
105
|
}
|
|
92
106
|
addOpLabel(concat, "«ops.concat»");
|
|
93
107
|
|
|
108
|
+
export async function conditional(condition, truthy, falsy) {
|
|
109
|
+
return condition ? truthy() : falsy();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function division(a, b) {
|
|
113
|
+
return a / b;
|
|
114
|
+
}
|
|
115
|
+
addOpLabel(division, "«ops.division»");
|
|
116
|
+
|
|
117
|
+
export function equal(a, b) {
|
|
118
|
+
return a == b;
|
|
119
|
+
}
|
|
120
|
+
addOpLabel(equal, "«ops.equal»");
|
|
121
|
+
|
|
122
|
+
export function exponentiation(a, b) {
|
|
123
|
+
return a ** b;
|
|
124
|
+
}
|
|
125
|
+
addOpLabel(exponentiation, "«ops.exponentiation»");
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Look up the given key as an external reference and cache the value for future
|
|
129
|
+
* requests.
|
|
130
|
+
*
|
|
131
|
+
* @this {AsyncTree|null}
|
|
132
|
+
*/
|
|
133
|
+
export async function external(key, cache) {
|
|
134
|
+
if (key in cache) {
|
|
135
|
+
return cache[key];
|
|
136
|
+
}
|
|
137
|
+
// First save a promise for the value
|
|
138
|
+
const promise = scope.call(this, key);
|
|
139
|
+
cache[key] = promise;
|
|
140
|
+
const value = await promise;
|
|
141
|
+
// Now update with the actual value
|
|
142
|
+
cache[key] = value;
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
94
146
|
/**
|
|
95
147
|
* This op is only used during parsing. It signals to ops.object that the
|
|
96
148
|
* "arguments" of the expression should be used to define a property getter.
|
|
97
149
|
*/
|
|
98
150
|
export const getter = new String("«ops.getter»");
|
|
99
151
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* @this {AsyncTree|null}
|
|
104
|
-
*/
|
|
105
|
-
export async function filesRoot() {
|
|
106
|
-
let tree = new OrigamiFiles("/");
|
|
107
|
-
tree.parent = root(this);
|
|
108
|
-
return tree;
|
|
152
|
+
export function greaterThan(a, b) {
|
|
153
|
+
return a > b;
|
|
109
154
|
}
|
|
155
|
+
addOpLabel(greaterThan, "«ops.greaterThan»");
|
|
156
|
+
|
|
157
|
+
export function greaterThanOrEqual(a, b) {
|
|
158
|
+
return a >= b;
|
|
159
|
+
}
|
|
160
|
+
addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
|
|
110
161
|
|
|
111
162
|
/**
|
|
112
163
|
* Files tree for the user's home directory.
|
|
113
164
|
*
|
|
114
165
|
* @this {AsyncTree|null}
|
|
115
166
|
*/
|
|
116
|
-
export async function
|
|
167
|
+
export async function homeDirectory() {
|
|
117
168
|
const tree = new OrigamiFiles(os.homedir());
|
|
118
|
-
tree.parent = root(this);
|
|
169
|
+
tree.parent = this ? Tree.root(this) : null;
|
|
119
170
|
return tree;
|
|
120
171
|
}
|
|
121
172
|
|
|
@@ -143,31 +194,37 @@ addOpLabel(inherited, "«ops.inherited»");
|
|
|
143
194
|
* @param {string[]} parameters
|
|
144
195
|
* @param {Code} code
|
|
145
196
|
*/
|
|
146
|
-
|
|
147
197
|
export function lambda(parameters, code) {
|
|
148
198
|
const context = this;
|
|
149
199
|
|
|
150
200
|
/** @this {Treelike|null} */
|
|
151
201
|
async function invoke(...args) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
202
|
+
let target;
|
|
203
|
+
if (parameters.length === 0) {
|
|
204
|
+
// No parameters
|
|
205
|
+
target = context;
|
|
206
|
+
} else {
|
|
207
|
+
// Add arguments to scope.
|
|
208
|
+
const ambients = {};
|
|
209
|
+
for (const parameter of parameters) {
|
|
210
|
+
ambients[parameter] = args.shift();
|
|
211
|
+
}
|
|
212
|
+
Object.defineProperty(ambients, codeSymbol, {
|
|
213
|
+
value: code,
|
|
214
|
+
enumerable: false,
|
|
215
|
+
});
|
|
216
|
+
const ambientTree = new ObjectTree(ambients);
|
|
217
|
+
ambientTree.parent = context;
|
|
218
|
+
target = ambientTree;
|
|
156
219
|
}
|
|
157
|
-
Object.defineProperty(ambients, codeSymbol, {
|
|
158
|
-
value: code,
|
|
159
|
-
enumerable: false,
|
|
160
|
-
});
|
|
161
|
-
const ambientTree = new ObjectTree(ambients);
|
|
162
|
-
ambientTree.parent = context;
|
|
163
220
|
|
|
164
|
-
let result = await evaluate.call(
|
|
221
|
+
let result = await evaluate.call(target, code);
|
|
165
222
|
|
|
166
223
|
// Bind a function result to the ambients so that it has access to the
|
|
167
224
|
// parameter values -- i.e., like a closure.
|
|
168
225
|
if (result instanceof Function) {
|
|
169
226
|
const resultCode = result.code;
|
|
170
|
-
result = result.bind(
|
|
227
|
+
result = result.bind(target);
|
|
171
228
|
if (code) {
|
|
172
229
|
// Copy over Origami code
|
|
173
230
|
result.code = resultCode;
|
|
@@ -190,6 +247,16 @@ export function lambda(parameters, code) {
|
|
|
190
247
|
}
|
|
191
248
|
addOpLabel(lambda, "«ops.lambda");
|
|
192
249
|
|
|
250
|
+
export function lessThan(a, b) {
|
|
251
|
+
return a < b;
|
|
252
|
+
}
|
|
253
|
+
addOpLabel(lessThan, "«ops.lessThan»");
|
|
254
|
+
|
|
255
|
+
export function lessThanOrEqual(a, b) {
|
|
256
|
+
return a <= b;
|
|
257
|
+
}
|
|
258
|
+
addOpLabel(lessThanOrEqual, "«ops.lessThanOrEqual»");
|
|
259
|
+
|
|
193
260
|
/**
|
|
194
261
|
* Return a primitive value
|
|
195
262
|
*/
|
|
@@ -198,6 +265,53 @@ export async function literal(value) {
|
|
|
198
265
|
}
|
|
199
266
|
addOpLabel(literal, "«ops.literal»");
|
|
200
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Logical AND operator
|
|
270
|
+
*/
|
|
271
|
+
export async function logicalAnd(head, ...tail) {
|
|
272
|
+
if (!head) {
|
|
273
|
+
return head;
|
|
274
|
+
}
|
|
275
|
+
// Evaluate the tail arguments in order, short-circuiting if any are falsy.
|
|
276
|
+
let lastValue;
|
|
277
|
+
for (const arg of tail) {
|
|
278
|
+
lastValue = arg instanceof Function ? await arg() : arg;
|
|
279
|
+
if (!lastValue) {
|
|
280
|
+
return lastValue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Return the last value (not `true`)
|
|
285
|
+
return lastValue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Logical NOT operator
|
|
290
|
+
*/
|
|
291
|
+
export async function logicalNot(value) {
|
|
292
|
+
return !value;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Logical OR operator
|
|
297
|
+
*/
|
|
298
|
+
export async function logicalOr(head, ...tail) {
|
|
299
|
+
if (head) {
|
|
300
|
+
return head;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Evaluate the tail arguments in order, short-circuiting if any are truthy.
|
|
304
|
+
let lastValue;
|
|
305
|
+
for (const arg of tail) {
|
|
306
|
+
lastValue = arg instanceof Function ? await arg() : arg;
|
|
307
|
+
if (lastValue) {
|
|
308
|
+
return lastValue;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return lastValue;
|
|
313
|
+
}
|
|
314
|
+
|
|
201
315
|
/**
|
|
202
316
|
* Merge the given trees. If they are all plain objects, return a plain object.
|
|
203
317
|
*
|
|
@@ -209,6 +323,40 @@ export async function merge(...trees) {
|
|
|
209
323
|
}
|
|
210
324
|
addOpLabel(merge, "«ops.merge»");
|
|
211
325
|
|
|
326
|
+
export function multiplication(a, b) {
|
|
327
|
+
return a * b;
|
|
328
|
+
}
|
|
329
|
+
addOpLabel(multiplication, "«ops.multiplication»");
|
|
330
|
+
|
|
331
|
+
export function notEqual(a, b) {
|
|
332
|
+
return a != b;
|
|
333
|
+
}
|
|
334
|
+
addOpLabel(notEqual, "«ops.notEqual»");
|
|
335
|
+
|
|
336
|
+
export function notStrictEqual(a, b) {
|
|
337
|
+
return a !== b;
|
|
338
|
+
}
|
|
339
|
+
addOpLabel(notStrictEqual, "«ops.notStrictEqual»");
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Nullish coalescing operator
|
|
343
|
+
*/
|
|
344
|
+
export async function nullishCoalescing(head, ...tail) {
|
|
345
|
+
if (head != null) {
|
|
346
|
+
return head;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let lastValue;
|
|
350
|
+
for (const arg of tail) {
|
|
351
|
+
lastValue = arg instanceof Function ? await arg() : arg;
|
|
352
|
+
if (lastValue != null) {
|
|
353
|
+
return lastValue;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return lastValue;
|
|
358
|
+
}
|
|
359
|
+
|
|
212
360
|
/**
|
|
213
361
|
* Construct an object. The keys will be the same as the given `obj`
|
|
214
362
|
* parameter's, and the values will be the results of evaluating the
|
|
@@ -222,14 +370,23 @@ export async function object(...entries) {
|
|
|
222
370
|
}
|
|
223
371
|
addOpLabel(object, "«ops.object»");
|
|
224
372
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
373
|
+
export function remainder(a, b) {
|
|
374
|
+
return a % b;
|
|
375
|
+
}
|
|
376
|
+
addOpLabel(remainder, "«ops.remainder»");
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Files tree for the filesystem root.
|
|
380
|
+
*
|
|
381
|
+
* @this {AsyncTree|null}
|
|
382
|
+
*/
|
|
383
|
+
export async function rootDirectory(key) {
|
|
384
|
+
let tree = new OrigamiFiles("/");
|
|
385
|
+
// We set the builtins as the parent because logically the filesystem root is
|
|
386
|
+
// outside the project. This ignores the edge case where the project itself is
|
|
387
|
+
// the root of the filesystem and has a config file.
|
|
388
|
+
tree.parent = this ? Tree.root(this) : null;
|
|
389
|
+
return key ? tree.get(key) : tree;
|
|
233
390
|
}
|
|
234
391
|
|
|
235
392
|
/**
|
|
@@ -243,24 +400,49 @@ export async function scope(key) {
|
|
|
243
400
|
}
|
|
244
401
|
const scope = scopeFn(this);
|
|
245
402
|
const value = await scope.get(key);
|
|
246
|
-
if (value === undefined) {
|
|
403
|
+
if (value === undefined && key !== "undefined") {
|
|
247
404
|
throw await scopeReferenceError(scope, key);
|
|
248
405
|
}
|
|
249
406
|
return value;
|
|
250
407
|
}
|
|
251
408
|
addOpLabel(scope, "«ops.scope»");
|
|
252
409
|
|
|
410
|
+
export function shiftLeft(a, b) {
|
|
411
|
+
return a << b;
|
|
412
|
+
}
|
|
413
|
+
addOpLabel(shiftLeft, "«ops.shiftLeft»");
|
|
414
|
+
|
|
415
|
+
export function shiftRightSigned(a, b) {
|
|
416
|
+
return a >> b;
|
|
417
|
+
}
|
|
418
|
+
addOpLabel(shiftRightSigned, "«ops.shiftRightSigned»");
|
|
419
|
+
|
|
420
|
+
export function shiftRightUnsigned(a, b) {
|
|
421
|
+
return a >>> b;
|
|
422
|
+
}
|
|
423
|
+
addOpLabel(shiftRightUnsigned, "«ops.shiftRightUnsigned»");
|
|
424
|
+
|
|
253
425
|
/**
|
|
254
426
|
* The spread operator is a placeholder during parsing. It should be replaced
|
|
255
427
|
* with an object merge.
|
|
256
428
|
*/
|
|
257
429
|
export function spread(...args) {
|
|
258
430
|
throw new Error(
|
|
259
|
-
"
|
|
431
|
+
"Internal error: a spread operation wasn't compiled correctly."
|
|
260
432
|
);
|
|
261
433
|
}
|
|
262
434
|
addOpLabel(spread, "«ops.spread»");
|
|
263
435
|
|
|
436
|
+
export function strictEqual(a, b) {
|
|
437
|
+
return a === b;
|
|
438
|
+
}
|
|
439
|
+
addOpLabel(strictEqual, "«ops.strictEqual»");
|
|
440
|
+
|
|
441
|
+
export function subtraction(a, b) {
|
|
442
|
+
return a - b;
|
|
443
|
+
}
|
|
444
|
+
addOpLabel(subtraction, "«ops.subtraction»");
|
|
445
|
+
|
|
264
446
|
/**
|
|
265
447
|
* Apply the default tagged template function.
|
|
266
448
|
*/
|
|
@@ -274,6 +456,16 @@ addOpLabel(template, "«ops.template»");
|
|
|
274
456
|
*/
|
|
275
457
|
export const traverse = Tree.traverseOrThrow;
|
|
276
458
|
|
|
459
|
+
export function unaryMinus(a) {
|
|
460
|
+
return -a;
|
|
461
|
+
}
|
|
462
|
+
addOpLabel(unaryMinus, "«ops.unaryMinus»");
|
|
463
|
+
|
|
464
|
+
export function unaryPlus(a) {
|
|
465
|
+
return +a;
|
|
466
|
+
}
|
|
467
|
+
addOpLabel(unaryPlus, "«ops.unaryPlus»");
|
|
468
|
+
|
|
277
469
|
/**
|
|
278
470
|
* If the value is packed but has an unpack method, call it and return that as
|
|
279
471
|
* the result; otherwise, return the value as is.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This folder defines expression tests in YAML files so that we can programmatically test the evaluation of the expressions in both JavaScript and Origami.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Conditional (ternary) expression tests
|
|
2
|
+
|
|
3
|
+
- source: "true ? 42 : 0"
|
|
4
|
+
expected: 42
|
|
5
|
+
description: "Condition is true, evaluates and returns the first operand"
|
|
6
|
+
|
|
7
|
+
- source: "false ? 42 : 0"
|
|
8
|
+
expected: 0
|
|
9
|
+
description: "Condition is false, evaluates and returns the second operand"
|
|
10
|
+
|
|
11
|
+
- source: "1 ? 'yes' : 'no'"
|
|
12
|
+
expected: "yes"
|
|
13
|
+
description: "Truthy condition with string operands"
|
|
14
|
+
|
|
15
|
+
- source: "0 ? 'yes' : 'no'"
|
|
16
|
+
expected: "no"
|
|
17
|
+
description: "Falsy condition with string operands"
|
|
18
|
+
|
|
19
|
+
- source: "'non-empty' ? 1 : 2"
|
|
20
|
+
expected: 1
|
|
21
|
+
description: "Truthy string condition with numeric operands"
|
|
22
|
+
|
|
23
|
+
- source: "'' ? 1 : 2"
|
|
24
|
+
expected: 2
|
|
25
|
+
description: "Falsy string condition with numeric operands"
|
|
26
|
+
|
|
27
|
+
- source: "null ? 'a' : 'b'"
|
|
28
|
+
expected: "b"
|
|
29
|
+
description: "Falsy null condition"
|
|
30
|
+
|
|
31
|
+
- source: "undefined ? 'a' : 'b'"
|
|
32
|
+
expected: "b"
|
|
33
|
+
description: "Falsy undefined condition"
|
|
34
|
+
|
|
35
|
+
- source: "NaN ? 'a' : 'b'"
|
|
36
|
+
expected: "b"
|
|
37
|
+
description: "Falsy NaN condition"
|
|
38
|
+
|
|
39
|
+
- source: "42 ? true : false"
|
|
40
|
+
expected: true
|
|
41
|
+
description: "Truthy numeric condition with boolean operands"
|
|
42
|
+
|
|
43
|
+
- source: "0 ? true : false"
|
|
44
|
+
expected: false
|
|
45
|
+
description: "Falsy numeric condition with boolean operands"
|
|
46
|
+
|
|
47
|
+
- source: "[] ? 'array' : 'no array'"
|
|
48
|
+
expected: "array"
|
|
49
|
+
description: "Truthy array condition"
|
|
50
|
+
|
|
51
|
+
- source: "{} ? 'object' : 'no object'"
|
|
52
|
+
expected: "object"
|
|
53
|
+
description: "Truthy object condition"
|
|
54
|
+
|
|
55
|
+
- source: "false ? null : undefined"
|
|
56
|
+
expected: __undefined__
|
|
57
|
+
description: "Condition is false, returns undefined"
|
|
58
|
+
|
|
59
|
+
- source: "null ? null : null"
|
|
60
|
+
expected: __null__
|
|
61
|
+
description: "Condition is falsy, returns null"
|
|
62
|
+
|
|
63
|
+
- source: "true ? NaN : 42"
|
|
64
|
+
expected: __NaN__
|
|
65
|
+
description: "Condition is true, evaluates and returns NaN"
|
|
66
|
+
|
|
67
|
+
- source: "(true ? 1 : 2) ? 3 : 4"
|
|
68
|
+
expected: 3
|
|
69
|
+
description: "Nested ternary where first expression evaluates to 1, which is truthy"
|
|
70
|
+
|
|
71
|
+
- source: "(false ? 1 : 2) ? 3 : 4"
|
|
72
|
+
expected: 3
|
|
73
|
+
description: "Nested ternary where first expression evaluates to 2, which is truthy"
|
|
74
|
+
|
|
75
|
+
- source: "(false ? 1 : 0) ? 3 : 4"
|
|
76
|
+
expected: 4
|
|
77
|
+
description: "Nested ternary where first expression evaluates to 0, which is falsy"
|
|
78
|
+
|
|
79
|
+
- source: "true ? (false ? 10 : 20) : 30"
|
|
80
|
+
expected: 20
|
|
81
|
+
description: "Nested ternary in the true branch of outer ternary"
|
|
82
|
+
|
|
83
|
+
- source: "false ? (false ? 10 : 20) : 30"
|
|
84
|
+
expected: 30
|
|
85
|
+
description: "Nested ternary in the false branch of outer ternary"
|
|
86
|
+
|
|
87
|
+
# - source: "'truthy' ? 1 + 2 : 3 + 4"
|
|
88
|
+
# expected: 3
|
|
89
|
+
# description: "Evaluates and returns the true branch with an arithmetic expression"
|
|
90
|
+
|
|
91
|
+
# - source: "'' ? 1 + 2 : 3 + 4"
|
|
92
|
+
# expected: 7
|
|
93
|
+
# description: "Evaluates and returns the false branch with an arithmetic expression"
|
|
94
|
+
|
|
95
|
+
- source: "undefined ? undefined : null"
|
|
96
|
+
expected: __null__
|
|
97
|
+
description: "Condition is falsy, returns null"
|
|
98
|
+
|
|
99
|
+
- source: "null ? undefined : undefined"
|
|
100
|
+
expected: __undefined__
|
|
101
|
+
description: "Condition is falsy, returns undefined"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Logical AND expression tests
|
|
2
|
+
|
|
3
|
+
- source: "true && true"
|
|
4
|
+
expected: true
|
|
5
|
+
description: "Both operands are true"
|
|
6
|
+
|
|
7
|
+
- source: "true && false"
|
|
8
|
+
expected: false
|
|
9
|
+
description: "First operand is true, second is false"
|
|
10
|
+
|
|
11
|
+
- source: "false && true"
|
|
12
|
+
expected: false
|
|
13
|
+
description: "First operand is false, second is true"
|
|
14
|
+
|
|
15
|
+
- source: "false && false"
|
|
16
|
+
expected: false
|
|
17
|
+
description: "Both operands are false"
|
|
18
|
+
|
|
19
|
+
- source: "false && (1 / 0)"
|
|
20
|
+
expected: false
|
|
21
|
+
description: "Short-circuit evaluation: first operand false, second not evaluated"
|
|
22
|
+
|
|
23
|
+
- source: "true && 42"
|
|
24
|
+
expected: 42
|
|
25
|
+
description: "Short-circuit evaluation: first operand true, evaluates second"
|
|
26
|
+
|
|
27
|
+
- source: "0 && true"
|
|
28
|
+
expected: 0
|
|
29
|
+
description: "Short-circuiting with falsy value (0)"
|
|
30
|
+
|
|
31
|
+
- source: "true && 'string'"
|
|
32
|
+
expected: "string"
|
|
33
|
+
description: "Truthy value with string"
|
|
34
|
+
|
|
35
|
+
- source: "false && 'string'"
|
|
36
|
+
expected: false
|
|
37
|
+
description: "Falsy value with string"
|
|
38
|
+
|
|
39
|
+
- source: "1 && 0"
|
|
40
|
+
expected: 0
|
|
41
|
+
description: "Truthy numeric value with falsy numeric value"
|
|
42
|
+
|
|
43
|
+
- source: "0 && 1"
|
|
44
|
+
expected: 0
|
|
45
|
+
description: "Falsy numeric value with truthy numeric value"
|
|
46
|
+
|
|
47
|
+
- source: "'' && 'non-empty string'"
|
|
48
|
+
expected: ""
|
|
49
|
+
description: "Falsy string value with truthy string"
|
|
50
|
+
|
|
51
|
+
- source: "'non-empty string' && ''"
|
|
52
|
+
expected: ""
|
|
53
|
+
description: "Truthy string with falsy string"
|
|
54
|
+
|
|
55
|
+
- source: "{} && true"
|
|
56
|
+
expected: true
|
|
57
|
+
description: "Empty object as first operand"
|
|
58
|
+
|
|
59
|
+
- source: "true && {}"
|
|
60
|
+
expected: {}
|
|
61
|
+
description: "Empty object as second operand"
|
|
62
|
+
|
|
63
|
+
- source: "[] && true"
|
|
64
|
+
expected: true
|
|
65
|
+
description: "Array as first operand"
|
|
66
|
+
|
|
67
|
+
- source: "true && []"
|
|
68
|
+
expected: []
|
|
69
|
+
description: "Array as second operand"
|
|
70
|
+
|
|
71
|
+
- source: "null && true"
|
|
72
|
+
expected: null
|
|
73
|
+
description: "Null as first operand"
|
|
74
|
+
|
|
75
|
+
- source: "true && null"
|
|
76
|
+
expected: null
|
|
77
|
+
description: "Null as second operand"
|
|
78
|
+
|
|
79
|
+
- source: "undefined && true"
|
|
80
|
+
expected: __undefined__
|
|
81
|
+
description: "Undefined as first operand"
|
|
82
|
+
|
|
83
|
+
- source: "true && undefined"
|
|
84
|
+
expected: __undefined__
|
|
85
|
+
description: "Undefined as second operand"
|
|
86
|
+
|
|
87
|
+
- source: "NaN && true"
|
|
88
|
+
expected: __NaN__
|
|
89
|
+
description: "NaN as first operand"
|
|
90
|
+
|
|
91
|
+
- source: "true && NaN"
|
|
92
|
+
expected: __NaN__
|
|
93
|
+
description: "NaN as second operand"
|
|
94
|
+
|
|
95
|
+
- source: "(true && false) && true"
|
|
96
|
+
expected: false
|
|
97
|
+
description: "Nested logical ANDs with a false in the middle"
|
|
98
|
+
|
|
99
|
+
- source: "(true && true) && true"
|
|
100
|
+
expected: true
|
|
101
|
+
description: "Nested logical ANDs with all true"
|
|
102
|
+
|
|
103
|
+
- source: "true && (true && false)"
|
|
104
|
+
expected: false
|
|
105
|
+
description: "Nested logical ANDs with false in inner"
|
|
106
|
+
|
|
107
|
+
- source: "(true && (false && true))"
|
|
108
|
+
expected: false
|
|
109
|
+
description: "Complex nesting with false at inner-most"
|
|
110
|
+
|
|
111
|
+
# TODO: Uncomment when we can do math
|
|
112
|
+
# - source: "true && (1 + 1 === 2)"
|
|
113
|
+
# expected: true
|
|
114
|
+
# description: "Combines logical AND with equality comparison"
|
|
115
|
+
|
|
116
|
+
# - source: "false && (5 > 2)"
|
|
117
|
+
# expected: false
|
|
118
|
+
# description: "Logical AND with greater-than comparison"
|
|
119
|
+
|
|
120
|
+
- source: "true && (3 || 0)"
|
|
121
|
+
expected: 3
|
|
122
|
+
description: "Logical AND with logical OR"
|
|
123
|
+
|
|
124
|
+
- source: "true && (0 || 3)"
|
|
125
|
+
expected: 3
|
|
126
|
+
description: "Logical AND with logical OR and falsy values"
|
|
127
|
+
|
|
128
|
+
- source: "'' && false"
|
|
129
|
+
expected: ""
|
|
130
|
+
description: "Falsy string and false"
|
|
131
|
+
|
|
132
|
+
- source: "false && ''"
|
|
133
|
+
expected: false
|
|
134
|
+
description: "False and falsy string"
|
|
135
|
+
|
|
136
|
+
- source: "undefined && null"
|
|
137
|
+
expected: __undefined__
|
|
138
|
+
description: "Undefined and null"
|
|
139
|
+
|
|
140
|
+
- source: "null && undefined"
|
|
141
|
+
expected: null
|
|
142
|
+
description: "Null and undefined"
|
|
143
|
+
|
|
144
|
+
- source: "(false && true) && undefined"
|
|
145
|
+
expected: false
|
|
146
|
+
description: "Short-circuiting nested AND with undefined"
|