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 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.logicalOr();
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.6';
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.6",
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.logicalOr();
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') {
package/src/starlight.js CHANGED
@@ -12,7 +12,7 @@ const Lexer = require('./lexer');
12
12
  const Parser = require('./parser');
13
13
  const Evaluator = require('./evaluator');
14
14
 
15
- const VERSION = '1.1.6';
15
+ const VERSION = '1.1.7';
16
16
 
17
17
  const COLOR = {
18
18
  reset: '\x1b[0m',