starlight-cli 1.1.6 → 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 +36 -0
- package/dist/index.js +46 -6
- package/package.json +12 -2
- package/src/evaluator.js +11 -3
- package/src/lexer.js +12 -0
- package/src/parser.js +22 -2
- package/src/starlight.js +1 -1
package/README.md
CHANGED
|
@@ -364,6 +364,42 @@ Starlight keeps `map`, `filter`, and `reduce` as **language-level utilities** in
|
|
|
364
364
|
|
|
365
365
|
---
|
|
366
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
|
+
---
|
|
367
403
|
|
|
368
404
|
## Why Starlight?
|
|
369
405
|
|
package/dist/index.js
CHANGED
|
@@ -10942,12 +10942,20 @@ async evalBinary(node, env) {
|
|
|
10942
10942
|
|
|
10943
10943
|
async evalLogical(node, env) {
|
|
10944
10944
|
const l = await this.evaluate(node.left, env);
|
|
10945
|
-
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
10946
|
-
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
10947
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
10948
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
|
+
}
|
|
10949
10956
|
}
|
|
10950
10957
|
|
|
10958
|
+
|
|
10951
10959
|
async evalUnary(node, env) {
|
|
10952
10960
|
const val = await this.evaluate(node.argument, env);
|
|
10953
10961
|
switch (node.operator) {
|
|
@@ -11377,6 +11385,18 @@ if (char === '-' && next === '>') {
|
|
|
11377
11385
|
this.advance(); this.advance();
|
|
11378
11386
|
continue;
|
|
11379
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
|
+
}
|
|
11380
11400
|
|
|
11381
11401
|
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
11382
11402
|
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
@@ -11937,7 +11957,7 @@ expression() {
|
|
|
11937
11957
|
}
|
|
11938
11958
|
|
|
11939
11959
|
assignment() {
|
|
11940
|
-
const node = this.
|
|
11960
|
+
const node = this.ternary();
|
|
11941
11961
|
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
11942
11962
|
|
|
11943
11963
|
const t = this.current;
|
|
@@ -11957,7 +11977,27 @@ assignment() {
|
|
|
11957
11977
|
|
|
11958
11978
|
return node;
|
|
11959
11979
|
}
|
|
11960
|
-
|
|
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
|
+
}
|
|
11961
12001
|
logicalOr() {
|
|
11962
12002
|
let node = this.logicalAnd();
|
|
11963
12003
|
while (this.current.type === 'OR') {
|
|
@@ -12467,7 +12507,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
12467
12507
|
const Parser = __nccwpck_require__(222);
|
|
12468
12508
|
const Evaluator = __nccwpck_require__(112);
|
|
12469
12509
|
|
|
12470
|
-
const VERSION = '1.1.
|
|
12510
|
+
const VERSION = '1.1.7';
|
|
12471
12511
|
|
|
12472
12512
|
const COLOR = {
|
|
12473
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
|
@@ -732,12 +732,20 @@ async evalBinary(node, env) {
|
|
|
732
732
|
|
|
733
733
|
async evalLogical(node, env) {
|
|
734
734
|
const l = await this.evaluate(node.left, env);
|
|
735
|
-
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
736
|
-
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
737
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
738
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
|
+
}
|
|
739
746
|
}
|
|
740
747
|
|
|
748
|
+
|
|
741
749
|
async evalUnary(node, env) {
|
|
742
750
|
const val = await this.evaluate(node.argument, env);
|
|
743
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') {
|