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 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.logicalOr();
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.5';
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.5",
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.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.5';
15
+ const VERSION = '1.1.7';
16
16
 
17
17
  const COLOR = {
18
18
  reset: '\x1b[0m',