starlight-cli 1.1.5 → 1.1.7
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/README.md +177 -0
- package/dist/index.js +104 -6
- package/package.json +12 -2
- package/src/evaluator.js +69 -3
- package/src/lexer.js +12 -0
- package/src/parser.js +22 -2
- package/src/starlight.js +1 -1
package/README.md
CHANGED
|
@@ -224,6 +224,183 @@ This makes Starlight useful not only as a programming language, but also as a **
|
|
|
224
224
|
|
|
225
225
|
---
|
|
226
226
|
|
|
227
|
+
## Array Helpers: map, filter, reduce
|
|
228
|
+
|
|
229
|
+
Starlight provides built-in **functional helpers** for working with arrays: `map`, `filter`, and `reduce`.
|
|
230
|
+
These helpers are **async-aware**, work seamlessly with **arrow functions**, and integrate fully with Starlight’s runtime and error handling.
|
|
231
|
+
|
|
232
|
+
Unlike JavaScript, these are **global functions** (not methods), keeping the language simple and consistent.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### map(array, fn)
|
|
237
|
+
|
|
238
|
+
Transforms each element of an array using a callback function and returns a **new array**.
|
|
239
|
+
|
|
240
|
+
**Syntax:**
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
map(array, fn)
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Example:**
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
let numbers = [1, 2, 3, 4]
|
|
251
|
+
|
|
252
|
+
let squared = await map(numbers, x => x * x)
|
|
253
|
+
|
|
254
|
+
sldeploy squared
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Output:**
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
[ 1, 4, 9, 16 ]
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- `fn` receives `(value, index, array)`
|
|
266
|
+
- Does **not** modify the original array
|
|
267
|
+
- Supports `async` arrow functions
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### filter(array, fn)
|
|
272
|
+
|
|
273
|
+
Selects elements from an array that satisfy a condition and returns a **new array**.
|
|
274
|
+
|
|
275
|
+
**Syntax:**
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
filter(array, fn)
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Example:**
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
let numbers = [1, 2, 3, 4, 5, 6]
|
|
286
|
+
|
|
287
|
+
let evens = await filter(numbers, x => x % 2 == 0)
|
|
288
|
+
|
|
289
|
+
sldeploy evens
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Output:**
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
[ 2, 4, 6 ]
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
- `fn` must return `true` or `false`
|
|
301
|
+
- `fn` receives `(value, index, array)`
|
|
302
|
+
- Original array remains unchanged
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### reduce(array, fn, initial)
|
|
307
|
+
|
|
308
|
+
Reduces an array to a **single value** by accumulating results using a reducer function.
|
|
309
|
+
|
|
310
|
+
**Syntax:**
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
reduce(array, fn, initial)
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Example:**
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
let numbers = [1, 2, 3, 4, 5]
|
|
321
|
+
|
|
322
|
+
let total = await reduce(numbers, (acc, value) => acc + value, 0)
|
|
323
|
+
|
|
324
|
+
sldeploy total
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Output:**
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
15
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
- `fn` receives `(accumulator, value, index, array)`
|
|
336
|
+
- `initial` is optional, but recommended
|
|
337
|
+
- Throws a runtime error if used on an empty array without an initial value
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
### Async Support
|
|
342
|
+
|
|
343
|
+
All three helpers support `async` functions:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
let results = await map(items, async item => {
|
|
348
|
+
await sleep(100)
|
|
349
|
+
return item * 2
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### Why global helpers?
|
|
357
|
+
|
|
358
|
+
Starlight keeps `map`, `filter`, and `reduce` as **language-level utilities** instead of object methods to:
|
|
359
|
+
|
|
360
|
+
- Keep syntax minimal and readable
|
|
361
|
+
- Avoid prototype complexity
|
|
362
|
+
- Work consistently with all iterables
|
|
363
|
+
- Integrate cleanly with the evaluator and runtime
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### Nullish Coalescing (`??`) Support
|
|
368
|
+
|
|
369
|
+
Our language now supports the **nullish coalescing operator** `??`, which allows you to provide a default value for `null` or `undefined` expressions.
|
|
370
|
+
|
|
371
|
+
**Syntax:**
|
|
372
|
+
|
|
373
|
+
```sl
|
|
374
|
+
value = expression1 ?? expression2
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
* `expression1` is evaluated first.
|
|
378
|
+
* If it is **not null or undefined**, its value is used.
|
|
379
|
+
* Otherwise, the value of `expression2` is used.
|
|
380
|
+
|
|
381
|
+
**Example:**
|
|
382
|
+
|
|
383
|
+
```sl
|
|
384
|
+
define a = null
|
|
385
|
+
define b = a ?? 42
|
|
386
|
+
sldeploy b # Output: 42
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Notes:**
|
|
390
|
+
|
|
391
|
+
* Only `null` and `undefined` are treated as "empty" values. Other falsy values like `0`, `false`, or `""` will **not** trigger the default.
|
|
392
|
+
* This operator is optional. If you prefer, you can achieve the same result using a simple `if` statement:
|
|
393
|
+
|
|
394
|
+
```sl
|
|
395
|
+
define a = null
|
|
396
|
+
define b = if a != null then a else 42
|
|
397
|
+
sldeploy b # Output: 42
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
This operator simplifies code that needs default values and makes your scripts cleaner and more readable.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
227
404
|
## Why Starlight?
|
|
228
405
|
|
|
229
406
|
* A **simple but powerful** scripting language
|
package/dist/index.js
CHANGED
|
@@ -10376,6 +10376,64 @@ formatValue(value, seen = new Set()) {
|
|
|
10376
10376
|
if (Array.isArray(arg)) return 'array';
|
|
10377
10377
|
return typeof arg;
|
|
10378
10378
|
});
|
|
10379
|
+
this.global.define('map', async (array, fn) => {
|
|
10380
|
+
if (!Array.isArray(array)) {
|
|
10381
|
+
throw new RuntimeError('map() expects an array', null, this.source);
|
|
10382
|
+
}
|
|
10383
|
+
if (typeof fn !== 'function') {
|
|
10384
|
+
throw new RuntimeError('map() expects a function', null, this.source);
|
|
10385
|
+
}
|
|
10386
|
+
|
|
10387
|
+
const result = [];
|
|
10388
|
+
for (let i = 0; i < array.length; i++) {
|
|
10389
|
+
result.push(await fn(array[i], i, array));
|
|
10390
|
+
}
|
|
10391
|
+
return result;
|
|
10392
|
+
});
|
|
10393
|
+
this.global.define('filter', async (array, fn) => {
|
|
10394
|
+
if (!Array.isArray(array)) {
|
|
10395
|
+
throw new RuntimeError('filter() expects an array', null, this.source);
|
|
10396
|
+
}
|
|
10397
|
+
if (typeof fn !== 'function') {
|
|
10398
|
+
throw new RuntimeError('filter() expects a function', null, this.source);
|
|
10399
|
+
}
|
|
10400
|
+
|
|
10401
|
+
const result = [];
|
|
10402
|
+
for (let i = 0; i < array.length; i++) {
|
|
10403
|
+
if (await fn(array[i], i, array)) {
|
|
10404
|
+
result.push(array[i]);
|
|
10405
|
+
}
|
|
10406
|
+
}
|
|
10407
|
+
return result;
|
|
10408
|
+
});
|
|
10409
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
10410
|
+
if (!Array.isArray(array)) {
|
|
10411
|
+
throw new RuntimeError('reduce() expects an array', null, this.source);
|
|
10412
|
+
}
|
|
10413
|
+
if (typeof fn !== 'function') {
|
|
10414
|
+
throw new RuntimeError('reduce() expects a function', null, this.source);
|
|
10415
|
+
}
|
|
10416
|
+
|
|
10417
|
+
let acc;
|
|
10418
|
+
let startIndex = 0;
|
|
10419
|
+
|
|
10420
|
+
if (initial !== undefined) {
|
|
10421
|
+
acc = initial;
|
|
10422
|
+
} else {
|
|
10423
|
+
if (array.length === 0) {
|
|
10424
|
+
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
10425
|
+
}
|
|
10426
|
+
acc = array[0];
|
|
10427
|
+
startIndex = 1;
|
|
10428
|
+
}
|
|
10429
|
+
|
|
10430
|
+
for (let i = startIndex; i < array.length; i++) {
|
|
10431
|
+
acc = await fn(acc, array[i], i, array);
|
|
10432
|
+
}
|
|
10433
|
+
|
|
10434
|
+
return acc;
|
|
10435
|
+
});
|
|
10436
|
+
|
|
10379
10437
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
10380
10438
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
10381
10439
|
this.global.define('range', (...args) => {
|
|
@@ -10884,12 +10942,20 @@ async evalBinary(node, env) {
|
|
|
10884
10942
|
|
|
10885
10943
|
async evalLogical(node, env) {
|
|
10886
10944
|
const l = await this.evaluate(node.left, env);
|
|
10887
|
-
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
10888
|
-
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
10889
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
10890
10945
|
|
|
10946
|
+
switch(node.operator) {
|
|
10947
|
+
case 'AND': return l && await this.evaluate(node.right, env);
|
|
10948
|
+
case 'OR': return l || await this.evaluate(node.right, env);
|
|
10949
|
+
case '??': {
|
|
10950
|
+
const r = await this.evaluate(node.right, env);
|
|
10951
|
+
return (l !== null && l !== undefined) ? l : r;
|
|
10952
|
+
}
|
|
10953
|
+
default:
|
|
10954
|
+
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
10955
|
+
}
|
|
10891
10956
|
}
|
|
10892
10957
|
|
|
10958
|
+
|
|
10893
10959
|
async evalUnary(node, env) {
|
|
10894
10960
|
const val = await this.evaluate(node.argument, env);
|
|
10895
10961
|
switch (node.operator) {
|
|
@@ -11319,6 +11385,18 @@ if (char === '-' && next === '>') {
|
|
|
11319
11385
|
this.advance(); this.advance();
|
|
11320
11386
|
continue;
|
|
11321
11387
|
}
|
|
11388
|
+
if (char === '?' && next === '?') {
|
|
11389
|
+
tokens.push({ type: 'NULLISH_COALESCING', value: '??', line: startLine, column: startCol });
|
|
11390
|
+
this.advance();
|
|
11391
|
+
this.advance();
|
|
11392
|
+
continue;
|
|
11393
|
+
}
|
|
11394
|
+
|
|
11395
|
+
if (char === '?') {
|
|
11396
|
+
tokens.push({ type: 'QUESTION', value: '?', line: startLine, column: startCol });
|
|
11397
|
+
this.advance();
|
|
11398
|
+
continue;
|
|
11399
|
+
}
|
|
11322
11400
|
|
|
11323
11401
|
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
11324
11402
|
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
@@ -11879,7 +11957,7 @@ expression() {
|
|
|
11879
11957
|
}
|
|
11880
11958
|
|
|
11881
11959
|
assignment() {
|
|
11882
|
-
const node = this.
|
|
11960
|
+
const node = this.ternary();
|
|
11883
11961
|
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
11884
11962
|
|
|
11885
11963
|
const t = this.current;
|
|
@@ -11899,7 +11977,27 @@ assignment() {
|
|
|
11899
11977
|
|
|
11900
11978
|
return node;
|
|
11901
11979
|
}
|
|
11902
|
-
|
|
11980
|
+
ternary() {
|
|
11981
|
+
let node = this.nullishCoalescing();
|
|
11982
|
+
while (this.current.type === 'QUESTION') {
|
|
11983
|
+
const t = this.current;
|
|
11984
|
+
this.eat('QUESTION');
|
|
11985
|
+
const consequent = this.expression();
|
|
11986
|
+
this.eat('COLON');
|
|
11987
|
+
const alternate = this.expression();
|
|
11988
|
+
node = { type: 'ConditionalExpression', test: node, consequent, alternate, line: t.line, column: t.column };
|
|
11989
|
+
}
|
|
11990
|
+
return node;
|
|
11991
|
+
}
|
|
11992
|
+
nullishCoalescing() {
|
|
11993
|
+
let node = this.logicalOr();
|
|
11994
|
+
while (this.current.type === 'NULLISH_COALESCING') {
|
|
11995
|
+
const t = this.current;
|
|
11996
|
+
this.eat('NULLISH_COALESCING');
|
|
11997
|
+
node = { type: 'LogicalExpression', operator: '??', left: node, right: this.logicalOr(), line: t.line, column: t.column };
|
|
11998
|
+
}
|
|
11999
|
+
return node;
|
|
12000
|
+
}
|
|
11903
12001
|
logicalOr() {
|
|
11904
12002
|
let node = this.logicalAnd();
|
|
11905
12003
|
while (this.current.type === 'OR') {
|
|
@@ -12409,7 +12507,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
12409
12507
|
const Parser = __nccwpck_require__(222);
|
|
12410
12508
|
const Evaluator = __nccwpck_require__(112);
|
|
12411
12509
|
|
|
12412
|
-
const VERSION = '1.1.
|
|
12510
|
+
const VERSION = '1.1.7';
|
|
12413
12511
|
|
|
12414
12512
|
const COLOR = {
|
|
12415
12513
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Starlight Programming Language CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"starlight": "index.js"
|
|
@@ -9,8 +9,18 @@
|
|
|
9
9
|
"build": "ncc build src/starlight.js -o dist",
|
|
10
10
|
"prepublishOnly": "npm run build"
|
|
11
11
|
},
|
|
12
|
-
"author": "Macedon",
|
|
12
|
+
"author": "Dominex Macedon",
|
|
13
13
|
"license": "MIT",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"starlight",
|
|
16
|
+
"starlight-language",
|
|
17
|
+
"programming-language",
|
|
18
|
+
"scripting-language",
|
|
19
|
+
"server-side",
|
|
20
|
+
"cli",
|
|
21
|
+
"interpreter",
|
|
22
|
+
"starlight-cli"
|
|
23
|
+
],
|
|
14
24
|
"dependencies": {
|
|
15
25
|
"blessed": "^0.1.81",
|
|
16
26
|
"markdown-it": "^14.1.0",
|
package/src/evaluator.js
CHANGED
|
@@ -166,6 +166,64 @@ formatValue(value, seen = new Set()) {
|
|
|
166
166
|
if (Array.isArray(arg)) return 'array';
|
|
167
167
|
return typeof arg;
|
|
168
168
|
});
|
|
169
|
+
this.global.define('map', async (array, fn) => {
|
|
170
|
+
if (!Array.isArray(array)) {
|
|
171
|
+
throw new RuntimeError('map() expects an array', null, this.source);
|
|
172
|
+
}
|
|
173
|
+
if (typeof fn !== 'function') {
|
|
174
|
+
throw new RuntimeError('map() expects a function', null, this.source);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = [];
|
|
178
|
+
for (let i = 0; i < array.length; i++) {
|
|
179
|
+
result.push(await fn(array[i], i, array));
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
});
|
|
183
|
+
this.global.define('filter', async (array, fn) => {
|
|
184
|
+
if (!Array.isArray(array)) {
|
|
185
|
+
throw new RuntimeError('filter() expects an array', null, this.source);
|
|
186
|
+
}
|
|
187
|
+
if (typeof fn !== 'function') {
|
|
188
|
+
throw new RuntimeError('filter() expects a function', null, this.source);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = [];
|
|
192
|
+
for (let i = 0; i < array.length; i++) {
|
|
193
|
+
if (await fn(array[i], i, array)) {
|
|
194
|
+
result.push(array[i]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
});
|
|
199
|
+
this.global.define('reduce', async (array, fn, initial) => {
|
|
200
|
+
if (!Array.isArray(array)) {
|
|
201
|
+
throw new RuntimeError('reduce() expects an array', null, this.source);
|
|
202
|
+
}
|
|
203
|
+
if (typeof fn !== 'function') {
|
|
204
|
+
throw new RuntimeError('reduce() expects a function', null, this.source);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let acc;
|
|
208
|
+
let startIndex = 0;
|
|
209
|
+
|
|
210
|
+
if (initial !== undefined) {
|
|
211
|
+
acc = initial;
|
|
212
|
+
} else {
|
|
213
|
+
if (array.length === 0) {
|
|
214
|
+
throw new RuntimeError('reduce() of empty array with no initial value', null, this.source);
|
|
215
|
+
}
|
|
216
|
+
acc = array[0];
|
|
217
|
+
startIndex = 1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (let i = startIndex; i < array.length; i++) {
|
|
221
|
+
acc = await fn(acc, array[i], i, array);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return acc;
|
|
225
|
+
});
|
|
226
|
+
|
|
169
227
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
170
228
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
171
229
|
this.global.define('range', (...args) => {
|
|
@@ -674,12 +732,20 @@ async evalBinary(node, env) {
|
|
|
674
732
|
|
|
675
733
|
async evalLogical(node, env) {
|
|
676
734
|
const l = await this.evaluate(node.left, env);
|
|
677
|
-
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
678
|
-
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
679
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
680
735
|
|
|
736
|
+
switch(node.operator) {
|
|
737
|
+
case 'AND': return l && await this.evaluate(node.right, env);
|
|
738
|
+
case 'OR': return l || await this.evaluate(node.right, env);
|
|
739
|
+
case '??': {
|
|
740
|
+
const r = await this.evaluate(node.right, env);
|
|
741
|
+
return (l !== null && l !== undefined) ? l : r;
|
|
742
|
+
}
|
|
743
|
+
default:
|
|
744
|
+
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
745
|
+
}
|
|
681
746
|
}
|
|
682
747
|
|
|
748
|
+
|
|
683
749
|
async evalUnary(node, env) {
|
|
684
750
|
const val = await this.evaluate(node.argument, env);
|
|
685
751
|
switch (node.operator) {
|
package/src/lexer.js
CHANGED
|
@@ -179,6 +179,18 @@ if (char === '-' && next === '>') {
|
|
|
179
179
|
this.advance(); this.advance();
|
|
180
180
|
continue;
|
|
181
181
|
}
|
|
182
|
+
if (char === '?' && next === '?') {
|
|
183
|
+
tokens.push({ type: 'NULLISH_COALESCING', value: '??', line: startLine, column: startCol });
|
|
184
|
+
this.advance();
|
|
185
|
+
this.advance();
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (char === '?') {
|
|
190
|
+
tokens.push({ type: 'QUESTION', value: '?', line: startLine, column: startCol });
|
|
191
|
+
this.advance();
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
182
194
|
|
|
183
195
|
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
184
196
|
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
package/src/parser.js
CHANGED
|
@@ -521,7 +521,7 @@ expression() {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
assignment() {
|
|
524
|
-
const node = this.
|
|
524
|
+
const node = this.ternary();
|
|
525
525
|
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
526
526
|
|
|
527
527
|
const t = this.current;
|
|
@@ -541,7 +541,27 @@ assignment() {
|
|
|
541
541
|
|
|
542
542
|
return node;
|
|
543
543
|
}
|
|
544
|
-
|
|
544
|
+
ternary() {
|
|
545
|
+
let node = this.nullishCoalescing();
|
|
546
|
+
while (this.current.type === 'QUESTION') {
|
|
547
|
+
const t = this.current;
|
|
548
|
+
this.eat('QUESTION');
|
|
549
|
+
const consequent = this.expression();
|
|
550
|
+
this.eat('COLON');
|
|
551
|
+
const alternate = this.expression();
|
|
552
|
+
node = { type: 'ConditionalExpression', test: node, consequent, alternate, line: t.line, column: t.column };
|
|
553
|
+
}
|
|
554
|
+
return node;
|
|
555
|
+
}
|
|
556
|
+
nullishCoalescing() {
|
|
557
|
+
let node = this.logicalOr();
|
|
558
|
+
while (this.current.type === 'NULLISH_COALESCING') {
|
|
559
|
+
const t = this.current;
|
|
560
|
+
this.eat('NULLISH_COALESCING');
|
|
561
|
+
node = { type: 'LogicalExpression', operator: '??', left: node, right: this.logicalOr(), line: t.line, column: t.column };
|
|
562
|
+
}
|
|
563
|
+
return node;
|
|
564
|
+
}
|
|
545
565
|
logicalOr() {
|
|
546
566
|
let node = this.logicalAnd();
|
|
547
567
|
while (this.current.type === 'OR') {
|