starlight-cli 1.1.15 → 1.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -3,7 +3,6 @@ const fs = require('fs');
3
3
  const Lexer = require('./lexer');
4
4
  const Parser = require('./parser');
5
5
  const path = require('path');
6
-
7
6
  class ReturnValue {
8
7
  constructor(value) { this.value = value; }
9
8
  }
@@ -204,7 +203,51 @@ formatValue(value, seen = new Set()) {
204
203
  if (arg && typeof arg === 'object') return Object.keys(arg).length;
205
204
  return 0;
206
205
  });
206
+ this.global.define('lower', arg => {
207
+ if (typeof arg !== 'string') return String(arg).toLowerCase();
208
+ return arg.toLowerCase();
209
+ });
210
+ this.global.define('toStr', arg => String(arg));
211
+
212
+ this.global.define('upper', arg => {
213
+ if (typeof arg !== 'string') return String(arg).toUpperCase();
214
+ return arg.toUpperCase();
215
+ });
216
+
217
+ this.global.define('trim', arg => {
218
+ if (typeof arg !== 'string') return String(arg).trim();
219
+ return arg.trim();
220
+ });
221
+
222
+ this.global.define('startsWith', (str, prefix) => {
223
+ if (typeof str !== 'string') str = String(str);
224
+ if (typeof prefix !== 'string') prefix = String(prefix);
225
+ return str.startsWith(prefix);
226
+ });
227
+
228
+ this.global.define('endsWith', (str, suffix) => {
229
+ if (typeof str !== 'string') str = String(str);
230
+ if (typeof suffix !== 'string') suffix = String(suffix);
231
+ return str.endsWith(suffix);
232
+ });
233
+
234
+ this.global.define('includes', (str, substr) => {
235
+ if (typeof str !== 'string') str = String(str);
236
+ if (typeof substr !== 'string') substr = String(substr);
237
+ return str.includes(substr);
238
+ });
207
239
 
240
+ this.global.define('repeat', (str, times) => {
241
+ if (typeof str !== 'string') str = String(str);
242
+ return str.repeat(Number(times) || 0);
243
+ });
244
+
245
+ this.global.define('replace', (str, search, replacement) => {
246
+ if (typeof str !== 'string') str = String(str);
247
+ if (typeof search !== 'string') search = String(search);
248
+ if (typeof replacement !== 'string') replacement = String(replacement);
249
+ return str.split(search).join(replacement);
250
+ });
208
251
  this.global.define('print', arg => { console.log(arg); return null; });
209
252
  this.global.define('type', arg => {
210
253
  if (Array.isArray(arg)) return 'array';
@@ -353,6 +396,72 @@ this.global.define('range', (...args) => {
353
396
 
354
397
  return result;
355
398
  });
399
+ this.global.define('floor', arg => Math.floor(Number(arg)));
400
+ this.global.define('ceil', arg => Math.ceil(Number(arg)));
401
+ this.global.define('round', arg => Math.round(Number(arg)));
402
+ this.global.define('abs', arg => Math.abs(Number(arg)));
403
+ this.global.define('pow', (base, exp) => Math.pow(Number(base), Number(exp)));
404
+ this.global.define('sqrt', arg => Math.sqrt(Number(arg)));
405
+ this.global.define('min', (...args) => Math.min(...args.map(Number)));
406
+ this.global.define('max', (...args) => Math.max(...args.map(Number)));
407
+ this.global.define('randomFloat', (min, max) => {
408
+ min = Number(min); max = Number(max);
409
+ if (isNaN(min) || isNaN(max)) return 0;
410
+ return Math.random() * (max - min) + min;
411
+ });
412
+
413
+ this.global.define('push', (arr, val) => {
414
+ if (!Array.isArray(arr)) throw new RuntimeError('push() expects an array', null, this.source);
415
+ arr.push(val); return arr.length;
416
+ });
417
+ this.global.define('pop', arr => {
418
+ if (!Array.isArray(arr)) throw new RuntimeError('pop() expects an array', null, this.source);
419
+ return arr.pop();
420
+ });
421
+ this.global.define('shift', arr => {
422
+ if (!Array.isArray(arr)) throw new RuntimeError('shift() expects an array', null, this.source);
423
+ return arr.shift();
424
+ });
425
+ this.global.define('unshift', (arr, val) => {
426
+ if (!Array.isArray(arr)) throw new RuntimeError('unshift() expects an array', null, this.source);
427
+ arr.unshift(val); return arr.length;
428
+ });
429
+ this.global.define('sort', (arr, fn) => {
430
+ if (!Array.isArray(arr)) throw new RuntimeError('sort() expects an array', null, this.source);
431
+ if (fn && typeof fn === 'function') return arr.sort(fn);
432
+ return arr.sort();
433
+ });
434
+ this.global.define('reverse', arr => {
435
+ if (!Array.isArray(arr)) throw new RuntimeError('reverse() expects an array', null, this.source);
436
+ return arr.reverse();
437
+ });
438
+
439
+ this.global.define('has', (obj, key) => obj && typeof obj === 'object' ? obj.hasOwnProperty(key) : false);
440
+ this.global.define('merge', (obj1, obj2) => {
441
+ if (!obj1 || typeof obj1 !== 'object') obj1 = {};
442
+ if (!obj2 || typeof obj2 !== 'object') obj2 = {};
443
+ return { ...obj1, ...obj2 };
444
+ });
445
+
446
+ this.global.define('uuid', () => {
447
+ // simple random UUID v4
448
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
449
+ const r = Math.random() * 16 | 0;
450
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
451
+ return v.toString(16);
452
+ });
453
+ });
454
+ this.global.define('timestamp', () => Date.now());
455
+ this.global.define('clone', arg => {
456
+ if (Array.isArray(arg)) return [...arg];
457
+ if (arg && typeof arg === 'object') return { ...arg };
458
+ return arg; // primitive types
459
+ });
460
+ this.global.define('typeOf', arg => {
461
+ if (arg === null) return 'null';
462
+ if (Array.isArray(arg)) return 'array';
463
+ return typeof arg;
464
+ });
356
465
 
357
466
  this.global.define('ask', prompt => {
358
467
  const readlineSync = require('readline-sync');
@@ -409,6 +518,271 @@ this.global.define('sleep', async (ms) => {
409
518
  this.global.define('str', arg => {
410
519
  return String(arg);
411
520
  });
521
+ this.global.define('now', () => Date.now());
522
+ this.global.define('formatDate', (timestamp, locale = 'en-US', options = {}) => {
523
+ if (timestamp === null || timestamp === undefined) timestamp = Date.now();
524
+ const t = typeof timestamp === 'number' ? timestamp : Number(timestamp);
525
+ if (isNaN(t)) t = Date.now();
526
+ return new Date(t).toLocaleString(locale, options);
527
+ });
528
+
529
+ this.global.define('readFile', path => {
530
+ if (typeof path !== 'string') path = String(path);
531
+ try {
532
+ return fs.readFileSync(path, 'utf-8');
533
+ } catch (e) {
534
+ throw new RuntimeError('readFile error: ' + e.message, null, this.source);
535
+ }
536
+ });
537
+ this.global.define('writeFile', (path, content) => {
538
+ if (typeof path !== 'string') path = String(path);
539
+ if (typeof content !== 'string') content = String(content);
540
+ try {
541
+ fs.writeFileSync(path, content, 'utf-8');
542
+ return true;
543
+ } catch (e) {
544
+ throw new RuntimeError('writeFile error: ' + e.message, null, this.source);
545
+ }
546
+ });
547
+
548
+ this.global.define('split', (str, separator) => {
549
+ if (typeof str !== 'string') str = String(str);
550
+ if (separator === undefined) separator = '';
551
+ return str.split(separator);
552
+ });
553
+
554
+ this.global.define('join', (arr, separator = '') => {
555
+ if (!Array.isArray(arr)) throw new RuntimeError('join() expects an array', null, this.source);
556
+ return arr.join(separator);
557
+ });
558
+
559
+ this.global.define('substring', (str, start, end) => {
560
+ if (typeof str !== 'string') str = String(str);
561
+ start = Number(start) || 0;
562
+ end = end !== undefined ? Number(end) : undefined;
563
+ return str.substring(start, end);
564
+ });
565
+
566
+ this.global.define('padStart', (str, targetLength, padString = ' ') => {
567
+ if (typeof str !== 'string') str = String(str);
568
+ targetLength = Number(targetLength) || 0;
569
+ return str.padStart(targetLength, padString);
570
+ });
571
+
572
+ this.global.define('padEnd', (str, targetLength, padString = ' ') => {
573
+ if (typeof str !== 'string') str = String(str);
574
+ targetLength = Number(targetLength) || 0;
575
+ return str.padEnd(targetLength, padString);
576
+ });
577
+ this.global.define('unique', arr => {
578
+ if (!Array.isArray(arr)) throw new RuntimeError('unique() expects an array', null, this.source);
579
+ return [...new Set(arr)];
580
+ });
581
+
582
+ this.global.define('indexOf', (arr, val) => {
583
+ if (!Array.isArray(arr)) throw new RuntimeError('indexOf() expects an array', null, this.source);
584
+ return arr.indexOf(val);
585
+ });
586
+
587
+ this.global.define('includesArr', (arr, val) => {
588
+ if (!Array.isArray(arr)) throw new RuntimeError('includesArr() expects an array', null, this.source);
589
+ return arr.includes(val);
590
+ });
591
+
592
+ this.global.define('flatten', arr => {
593
+ if (!Array.isArray(arr)) throw new RuntimeError('flatten() expects an array', null, this.source);
594
+ return arr.flat(Infinity);
595
+ });
596
+
597
+ this.global.define('randomChoice', arr => {
598
+ if (!Array.isArray(arr)) throw new RuntimeError('randomChoice() expects an array', null, this.source);
599
+ return arr[Math.floor(Math.random() * arr.length)];
600
+ });
601
+
602
+ this.global.define('entries', obj => {
603
+ if (!obj || typeof obj !== 'object') throw new RuntimeError('entries() expects an object', null, this.source);
604
+ return Object.entries(obj);
605
+ });
606
+
607
+ this.global.define('invert', obj => {
608
+ if (!obj || typeof obj !== 'object') throw new RuntimeError('invert() expects an object', null, this.source);
609
+ const result = {};
610
+ for (const key in obj) result[obj[key]] = key;
611
+ return result;
612
+ });
613
+
614
+ this.global.define('isEmpty', arg => {
615
+ if (arg == null) return true;
616
+ if (Array.isArray(arg) || typeof arg === 'string') return arg.length === 0;
617
+ if (typeof arg === 'object') return Object.keys(arg).length === 0;
618
+ return false;
619
+ });
620
+
621
+ this.global.define('deepClone', arg => {
622
+ return JSON.parse(JSON.stringify(arg));
623
+ });
624
+
625
+ this.global.define('capitalize', str => {
626
+ if (typeof str !== 'string') str = String(str);
627
+ if (str.length === 0) return '';
628
+ return str[0].toUpperCase() + str.slice(1);
629
+ });
630
+
631
+ this.global.define('reverseStr', str => {
632
+ if (typeof str !== 'string') str = String(str);
633
+ return str.split('').reverse().join('');
634
+ });
635
+
636
+ this.global.define('trimStart', str => {
637
+ if (typeof str !== 'string') str = String(str);
638
+ return str.trimStart();
639
+ });
640
+
641
+ this.global.define('trimEnd', str => {
642
+ if (typeof str !== 'string') str = String(str);
643
+ return str.trimEnd();
644
+ });
645
+
646
+ this.global.define('clamp', (value, min, max) => {
647
+ value = Number(value); min = Number(min); max = Number(max);
648
+ return Math.min(Math.max(value, min), max);
649
+ });
650
+
651
+ this.global.define('sign', value => {
652
+ value = Number(value);
653
+ return Math.sign(value);
654
+ });
655
+
656
+ this.global.define('appendFile', (path, content) => {
657
+ if (typeof path !== 'string') path = String(path);
658
+ if (typeof content !== 'string') content = String(content);
659
+ try {
660
+ fs.appendFileSync(path, content, 'utf-8');
661
+ return true;
662
+ } catch (e) {
663
+ throw new RuntimeError('appendFile error: ' + e.message, null, this.source);
664
+ }
665
+ });
666
+
667
+ this.global.define('exists', path => {
668
+ if (typeof path !== 'string') path = String(path);
669
+ return fs.existsSync(path);
670
+ });
671
+
672
+ this.global.define('mkdir', path => {
673
+ if (typeof path !== 'string') path = String(path);
674
+ if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true });
675
+ return true;
676
+ });
677
+ this.global.define('count', (arr, value) => {
678
+ if (!Array.isArray(arr)) throw new RuntimeError('count() expects an array', null, this.source);
679
+ return arr.filter(x => x === value).length;
680
+ });
681
+
682
+ this.global.define('uniqueBy', (arr, fn) => {
683
+ if (!Array.isArray(arr)) throw new RuntimeError('uniqueBy() expects an array', null, this.source);
684
+ const seen = new Set();
685
+ const result = [];
686
+ for (const item of arr) {
687
+ const key = fn ? fn(item) : item;
688
+ if (!seen.has(key)) {
689
+ seen.add(key);
690
+ result.push(item);
691
+ }
692
+ }
693
+ return result;
694
+ });
695
+ this.global.define('getProp', (obj, key, defaultValue = null) => {
696
+ if (!obj || typeof obj !== 'object') return defaultValue;
697
+ const keys = key.split('.');
698
+ let current = obj;
699
+ for (const k of keys) {
700
+ if (current[k] === undefined) return defaultValue;
701
+ current = current[k];
702
+ }
703
+ return current;
704
+ });
705
+
706
+ this.global.define('setProp', (obj, key, value) => {
707
+ if (!obj || typeof obj !== 'object') throw new RuntimeError('setProp() expects an object', null, this.source);
708
+ const keys = key.split('.');
709
+ let current = obj;
710
+ for (let i = 0; i < keys.length - 1; i++) {
711
+ if (!current[keys[i]] || typeof current[keys[i]] !== 'object') current[keys[i]] = {};
712
+ current = current[keys[i]];
713
+ }
714
+ current[keys[keys.length - 1]] = value;
715
+ return obj;
716
+ });
717
+
718
+ this.global.define('mergeDeep', (obj1, obj2) => {
719
+ const isObject = val => val && typeof val === 'object';
720
+ const result = {...obj1};
721
+ for (const key in obj2) {
722
+ if (isObject(obj2[key]) && isObject(result[key])) {
723
+ result[key] = this.global.get('mergeDeep')(result[key], obj2[key]);
724
+ } else {
725
+ result[key] = obj2[key];
726
+ }
727
+ }
728
+ return result;
729
+ });
730
+ this.global.define('camelCase', str => {
731
+ if (typeof str !== 'string') str = String(str);
732
+ return str
733
+ .replace(/[-_ ]+./g, s => s.charAt(s.length - 1).toUpperCase())
734
+ .replace(/^[A-Z]/, s => s.toLowerCase());
735
+ });
736
+
737
+ this.global.define('kebabCase', str => {
738
+ if (typeof str !== 'string') str = String(str);
739
+ return str
740
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
741
+ .replace(/\s+/g, '-')
742
+ .replace(/_+/g, '-')
743
+ .toLowerCase();
744
+ });
745
+
746
+ this.global.define('repeatStr', (str, times) => {
747
+ if (typeof str !== 'string') str = String(str);
748
+ return str.repeat(Number(times) || 0);
749
+ });
750
+ this.global.define('randomInt', (min, max) => {
751
+ min = Number(min); max = Number(max);
752
+ if (isNaN(min) || isNaN(max)) return 0;
753
+ return Math.floor(Math.random() * (max - min + 1)) + min;
754
+ });
755
+
756
+ this.global.define('lerp', (a, b, t) => {
757
+ a = Number(a); b = Number(b); t = Number(t);
758
+ return a + (b - a) * t;
759
+ });
760
+
761
+ this.global.define('degToRad', deg => Number(deg) * (Math.PI / 180));
762
+ this.global.define('radToDeg', rad => Number(rad) * (180 / Math.PI));
763
+ this.global.define('readJSON', path => {
764
+ const content = this.global.get('readFile')(path);
765
+ return JSON.parse(content);
766
+ });
767
+
768
+ this.global.define('writeJSON', (path, obj) => {
769
+ const content = JSON.stringify(obj, null, 2);
770
+ return this.global.get('writeFile')(path, content);
771
+ });
772
+
773
+ this.global.define('deleteFile', path => {
774
+ if (typeof path !== 'string') path = String(path);
775
+ if (!fs.existsSync(path)) return false;
776
+ fs.unlinkSync(path);
777
+ return true;
778
+ });
779
+
780
+ this.global.define('rmdir', path => {
781
+ if (typeof path !== 'string') path = String(path);
782
+ if (!fs.existsSync(path)) return false;
783
+ fs.rmSync(path, { recursive: true, force: true });
784
+ return true;
785
+ });
412
786
 
413
787
  }
414
788
 
@@ -422,10 +796,17 @@ async evaluate(node, env = this.global) {
422
796
  case 'SldeployStatement': return await this.evalSldeploy(node, env);
423
797
  case 'AskStatement': return await this.evalAsk(node, env);
424
798
  case 'DefineStatement': return await this.evalDefine(node, env);
799
+ case 'FunctionExpression':
800
+ return this.evalFunctionExpression(node, env);
801
+ case 'SliceExpression':
802
+ return await this.evalSlice(node, env);
803
+
425
804
  case 'ExpressionStatement': return await this.evaluate(node.expression, env);
426
805
  case 'BinaryExpression': return await this.evalBinary(node, env);
427
806
  case 'LogicalExpression': return await this.evalLogical(node, env);
428
807
  case 'UnaryExpression': return await this.evalUnary(node, env);
808
+ case 'ConditionalExpression':
809
+ return await this.evalConditional(node, env);
429
810
  case 'Literal': return node.value;
430
811
  case 'Identifier': return env.get(node.name, node, this.source);
431
812
  case 'IfStatement': return await this.evalIf(node, env);
@@ -528,6 +909,36 @@ async evalProgram(node, env) {
528
909
  }
529
910
  return result;
530
911
  }
912
+ async evalSlice(node, env) {
913
+ try {
914
+ const arr = await this.evaluate(node.object, env);
915
+ const start = node.start ? await this.evaluate(node.start, env) : 0;
916
+ const end = node.end ? await this.evaluate(node.end, env) : arr.length;
917
+
918
+ if (!Array.isArray(arr)) {
919
+ throw new RuntimeError(
920
+ 'Slice target must be an array',
921
+ node,
922
+ this.source,
923
+ env
924
+ );
925
+ }
926
+
927
+ const s = start < 0 ? Math.max(arr.length + start, 0) : Math.min(start, arr.length);
928
+ const e = end < 0 ? Math.max(arr.length + end, 0) : Math.min(end, arr.length);
929
+
930
+ return arr.slice(s, e);
931
+
932
+ } catch (err) {
933
+ if (err instanceof RuntimeError) throw err;
934
+ throw new RuntimeError(
935
+ err.message || 'Error evaluating slice expression',
936
+ node,
937
+ this.source,
938
+ env
939
+ );
940
+ }
941
+ }
531
942
 
532
943
  async evalStartStatement(node, env) {
533
944
  try {
@@ -576,6 +987,24 @@ async evalStartStatement(node, env) {
576
987
  );
577
988
  }
578
989
  }
990
+ async evalConditional(node, env) {
991
+ try {
992
+ const test = await this.evaluate(node.test, env);
993
+ if (test) {
994
+ return await this.evaluate(node.consequent, env);
995
+ } else {
996
+ return await this.evaluate(node.alternate, env);
997
+ }
998
+ } catch (e) {
999
+ if (e instanceof RuntimeError) throw e;
1000
+ throw new RuntimeError(
1001
+ e.message || 'Error evaluating conditional expression',
1002
+ node,
1003
+ this.source,
1004
+ env
1005
+ );
1006
+ }
1007
+ }
579
1008
 
580
1009
  async evalRaceClause(node, env) {
581
1010
  try {
@@ -1186,6 +1615,41 @@ async evalFor(node, env) {
1186
1615
  );
1187
1616
  }
1188
1617
  }
1618
+ evalFunctionExpression(node, env) {
1619
+ if (!node.body || !Array.isArray(node.params)) {
1620
+ throw new RuntimeError(
1621
+ 'Invalid function expression',
1622
+ node,
1623
+ this.source,
1624
+ env
1625
+ );
1626
+ }
1627
+
1628
+ const evaluator = this;
1629
+
1630
+ const fn = async function (...args) {
1631
+ const localEnv = new Environment(env);
1632
+
1633
+ for (let i = 0; i < node.params.length; i++) {
1634
+ const param = node.params[i];
1635
+ const paramName = typeof param === 'string' ? param : param.name;
1636
+ localEnv.define(paramName, args[i]);
1637
+ }
1638
+
1639
+ try {
1640
+ const result = await evaluator.evaluate(node.body, localEnv);
1641
+ return result === undefined ? null : result;
1642
+ } catch (e) {
1643
+ if (e instanceof ReturnValue) return e.value === undefined ? null : e.value;
1644
+ throw e;
1645
+ }
1646
+ };
1647
+ fn.params = node.params;
1648
+ fn.body = node.body;
1649
+ fn.env = env;
1650
+
1651
+ return fn;
1652
+ }
1189
1653
 
1190
1654
  evalFunctionDeclaration(node, env) {
1191
1655
  try {
@@ -1273,7 +1737,6 @@ async evalCall(node, env) {
1273
1737
  );
1274
1738
  }
1275
1739
  }
1276
-
1277
1740
  async evalIndex(node, env) {
1278
1741
  try {
1279
1742
  const obj = await this.evaluate(node.object, env);
@@ -1306,7 +1769,6 @@ async evalIndex(node, env) {
1306
1769
  );
1307
1770
  }
1308
1771
  }
1309
-
1310
1772
  async evalObject(node, env) {
1311
1773
  try {
1312
1774
  const out = {};