starlight-cli 1.1.1 → 1.1.3

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.1",
3
+ "version": "1.1.3",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
@@ -13,7 +13,8 @@
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
15
  "blessed": "^0.1.81",
16
- "readline-sync": "^1.4.10"
16
+ "readline-sync": "^1.4.10",
17
+ "starlight-color": "^1.0.4"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@vercel/ncc": "^0.38.4"
package/src/evaluator.js CHANGED
@@ -49,7 +49,6 @@ class Environment {
49
49
  if (name in this.store) return this.store[name];
50
50
  if (this.parent) return this.parent.get(name, node, source);
51
51
 
52
- // Only suggest for top-level variable/function names
53
52
  let suggestion = null;
54
53
  if (node && source) {
55
54
  suggestion = this.suggest?.(name, this) || null;
@@ -84,7 +83,6 @@ class Evaluator {
84
83
  this.setupBuiltins();
85
84
  }
86
85
  suggest(name, env) {
87
- // Collect all variable/function names from the environment chain
88
86
  const names = new Set();
89
87
  let current = env;
90
88
  while (current) {
@@ -98,11 +96,10 @@ suggest(name, env) {
98
96
  let bestScore = Infinity;
99
97
 
100
98
  for (const item of names) {
101
- // simple edit distance approximation
102
99
  const dist = Math.abs(item.length - name.length) +
103
100
  [...name].filter((c, i) => c !== item[i]).length;
104
101
 
105
- if (dist < bestScore && dist <= 2) { // max distance 2
102
+ if (dist < bestScore && dist <= 2) {
106
103
  bestScore = dist;
107
104
  best = item;
108
105
  }
@@ -111,58 +108,50 @@ suggest(name, env) {
111
108
  return best;
112
109
  }
113
110
 
114
-
115
111
  formatValue(value, seen = new Set()) {
116
- // Circular reference handling
112
+ const color = require('starlight-color');
113
+
117
114
  if (typeof value === 'object' && value !== null) {
118
- if (seen.has(value)) return '[Circular]';
115
+ if (seen.has(value)) return color.red('[Circular]');
119
116
  seen.add(value);
120
117
  }
121
118
 
122
- // Python-style null / undefined
123
- if (value === null) return 'None';
124
- if (value === undefined) return 'undefined';
119
+ if (value === null) return color.yellow('None');
120
+ if (value === undefined) return color.yellow('undefined');
125
121
 
126
122
  const t = typeof value;
127
123
 
128
- // Strings (no quotes)
129
- if (t === 'string') return value;
124
+ if (t === 'string') return color.cyan(value);
130
125
 
131
- // Numbers
132
- if (t === 'number') return String(value);
126
+ if (t === 'number') return color.green(String(value));
133
127
 
134
- // Booleans (Python-style)
135
- if (t === 'boolean') return value ? 'True' : 'False';
128
+ if (t === 'boolean') return color.yellow(value ? 'true' : 'false');
136
129
 
137
- // Native JS functions
138
130
  if (t === 'function') {
139
- return value.name
140
- ? `<function ${value.name}>`
141
- : '<function>';
131
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
142
132
  }
143
133
 
144
- // Arrays
145
134
  if (Array.isArray(value)) {
146
- return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
135
+ const items = value.map(v => this.formatValue(v, seen));
136
+ return color.white('[ ' + items.join(', ') + ' ]');
147
137
  }
148
138
 
149
- // Objects (including user-defined functions)
150
139
  if (t === 'object') {
151
- // Detect user-defined functions (AST-based)
152
140
  if (value.params && value.body) {
153
- return value.name
154
- ? `<function ${value.name}>`
155
- : '<function>';
141
+ return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
156
142
  }
157
143
 
158
144
  const entries = Object.entries(value).map(
159
- ([k, v]) => `${k}: ${this.formatValue(v, seen)}`
145
+ ([k, v]) => color.magenta(`${k}: `) + this.formatValue(v, seen)
160
146
  );
161
- return '{ ' + entries.join(', ') + ' }';
147
+ return color.magenta('{ ') + entries.join(color.magenta(', ')) + color.magenta(' }');
162
148
  }
163
149
 
164
- // Fallback
165
- return String(value);
150
+ try {
151
+ return String(value);
152
+ } catch {
153
+ return color.red('[Unprintable]');
154
+ }
166
155
  }
167
156
 
168
157
 
@@ -237,7 +226,6 @@ this.global.define('fetch', async (url, options = {}) => {
237
226
  };
238
227
  });
239
228
 
240
- // GET request
241
229
  this.global.define('get', async (url) => {
242
230
  const res = await fetch(url);
243
231
  return await res.json();
@@ -284,6 +272,11 @@ case 'ForInStatement':
284
272
  return await this.evalFor(node, env);
285
273
  case 'DoTrackStatement':
286
274
  return await this.evalDoTrack(node, env);
275
+ case 'StartStatement':
276
+ return await this.evalStartStatement(node, env);
277
+
278
+ case 'RaceClause':
279
+ return await this.evalRaceClause(node, env);
287
280
 
288
281
  case 'BreakStatement': throw new BreakSignal();
289
282
  case 'ContinueStatement': throw new ContinueSignal();
@@ -312,14 +305,14 @@ case 'DoTrackStatement':
312
305
  const callee = await this.evaluate(node.callee, env);
313
306
 
314
307
  if (typeof callee === 'object' && callee.body) {
315
- const evaluator = this; // <- capture the current evaluator
308
+ const evaluator = this;
316
309
  const Constructor = function(...args) {
317
310
  const newEnv = new Environment(callee.env);
318
311
  newEnv.define('this', this);
319
312
  for (let i = 0; i < callee.params.length; i++) {
320
313
  newEnv.define(callee.params[i], args[i]);
321
314
  }
322
- return evaluator.evaluate(callee.body, newEnv); // use captured evaluator
315
+ return evaluator.evaluate(callee.body, newEnv);
323
316
  };
324
317
 
325
318
  const args = [];
@@ -357,13 +350,78 @@ async evalProgram(node, env) {
357
350
  return result;
358
351
  }
359
352
 
353
+ async evalStartStatement(node, env) {
354
+ try {
355
+ const value = await this.evaluate(node.discriminant, env);
356
+ let executing = false;
357
+ for (const c of node.cases) {
358
+ try {
359
+ if (!executing) {
360
+ const testValue = await this.evaluate(c.test, env);
361
+ if (testValue === value) executing = true;
362
+ }
363
+
364
+ if (executing) {
365
+ await this.evaluate(c.consequent, new Environment(env));
366
+ }
367
+ } catch (caseErr) {
368
+ if (caseErr instanceof BreakSignal) break;
369
+ if (caseErr instanceof RuntimeError ||
370
+ caseErr instanceof ReturnValue ||
371
+ caseErr instanceof ContinueSignal) {
372
+ throw caseErr; // propagate signals
373
+ }
374
+
375
+ throw new RuntimeError(
376
+ caseErr.message || 'Error evaluating case in start statement',
377
+ c,
378
+ this.source
379
+ );
380
+ }
381
+ }
382
+
383
+ return null;
384
+ } catch (err) {
385
+ if (err instanceof RuntimeError ||
386
+ err instanceof ReturnValue ||
387
+ err instanceof BreakSignal ||
388
+ err instanceof ContinueSignal) {
389
+ throw err;
390
+ }
391
+ throw new RuntimeError(
392
+ err.message || 'Error evaluating start statement',
393
+ node,
394
+ this.source
395
+ );
396
+ }
397
+ }
398
+
399
+
400
+ async evalRaceClause(node, env) {
401
+ try {
402
+ const testValue = await this.evaluate(node.test, env);
403
+ const result = await this.evaluate(node.consequent, new Environment(env));
404
+ return { testValue, result };
405
+ } catch (err) {
406
+ if (err instanceof RuntimeError ||
407
+ err instanceof ReturnValue ||
408
+ err instanceof BreakSignal ||
409
+ err instanceof ContinueSignal) {
410
+ throw err;
411
+ }
412
+ throw new RuntimeError(
413
+ err.message || 'Error evaluating race clause',
414
+ node,
415
+ this.source
416
+ );
417
+ }
418
+ }
360
419
 
361
420
  async evalDoTrack(node, env) {
362
421
  try {
363
422
  return await this.evaluate(node.body, env);
364
423
  } catch (err) {
365
424
  if (!node.handler) {
366
- // Wrap any raw error into RuntimeError with line info
367
425
  if (err instanceof RuntimeError) throw err;
368
426
  throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
369
427
  }
@@ -374,7 +432,6 @@ async evalDoTrack(node, env) {
374
432
  try {
375
433
  return await this.evaluate(node.handler, trackEnv);
376
434
  } catch (handlerErr) {
377
- // Wrap handler errors as well
378
435
  if (handlerErr instanceof RuntimeError) throw handlerErr;
379
436
  throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
380
437
  }
@@ -493,23 +550,23 @@ async evalAssignment(node, env) {
493
550
  const left = node.left;
494
551
 
495
552
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
496
- if (left.type === 'MemberExpression') {
497
- const obj = await this.evaluate(left.object, env);
498
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
499
- obj[left.property] = rightVal;
500
- return rightVal;
501
- }
502
- if (left.type === 'IndexExpression') {
503
- const obj = await this.evaluate(left.object, env);
504
- const idx = await this.evaluate(left.indexer, env);
505
- if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
506
- obj[idx] = rightVal;
507
- return rightVal;
508
- }
509
553
 
554
+ if (left.type === 'MemberExpression') {
555
+ const obj = await this.evaluate(left.object, env);
556
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
557
+ obj[left.property] = rightVal; // dynamic creation of new properties allowed
558
+ return rightVal;
559
+ }
510
560
 
511
- throw new RuntimeError('Invalid assignment target', node, this.source);
561
+ if (left.type === 'IndexExpression') {
562
+ const obj = await this.evaluate(left.object, env);
563
+ const idx = await this.evaluate(left.indexer, env);
564
+ if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
565
+ obj[idx] = rightVal; // dynamic creation allowed
566
+ return rightVal;
567
+ }
512
568
 
569
+ throw new RuntimeError('Invalid assignment target', node, this.source);
513
570
  }
514
571
 
515
572
  async evalCompoundAssignment(node, env) {
@@ -517,13 +574,13 @@ async evalCompoundAssignment(node, env) {
517
574
  let current;
518
575
 
519
576
  if (left.type === 'Identifier') current = env.get(left.name, left, this.source);
520
- else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
521
- else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
577
+ else if (left.type === 'MemberExpression') current = await this.evalMember(left, env) ?? 0;
578
+ else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env) ?? 0;
522
579
  else throw new RuntimeError('Invalid compound assignment target', node, this.source);
523
580
 
524
-
525
581
  const rhs = await this.evaluate(node.right, env);
526
582
  let computed;
583
+
527
584
  switch (node.operator) {
528
585
  case 'PLUSEQ': computed = current + rhs; break;
529
586
  case 'MINUSEQ': computed = current - rhs; break;
@@ -531,16 +588,18 @@ async evalCompoundAssignment(node, env) {
531
588
  case 'SLASHEQ': computed = current / rhs; break;
532
589
  case 'MODEQ': computed = current % rhs; break;
533
590
  default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
534
-
535
591
  }
536
592
 
537
593
  if (left.type === 'Identifier') env.set(left.name, computed);
538
- else if (left.type === 'MemberExpression') await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
539
- else await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
594
+ else if (left.type === 'MemberExpression')
595
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
596
+ else
597
+ await this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
540
598
 
541
599
  return computed;
542
600
  }
543
601
 
602
+
544
603
  async evalSldeploy(node, env) {
545
604
  const val = await this.evaluate(node.expr, env);
546
605
  console.log(this.formatValue(val));
@@ -548,6 +607,7 @@ async evalSldeploy(node, env) {
548
607
  }
549
608
 
550
609
 
610
+
551
611
  async evalAsk(node, env) {
552
612
  const prompt = await this.evaluate(node.prompt, env);
553
613
 
@@ -654,11 +714,7 @@ async evalWhile(node, env) {
654
714
  }
655
715
  return null;
656
716
  }
657
-
658
717
  async evalFor(node, env) {
659
- // -------------------------------
660
- // Python-style: for x in iterable (with optional 'let')
661
- // -------------------------------
662
718
  if (node.type === 'ForInStatement') {
663
719
  const iterable = await this.evaluate(node.iterable, env);
664
720
 
@@ -666,11 +722,8 @@ async evalFor(node, env) {
666
722
  throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
667
723
  }
668
724
 
669
- const loopVar = node.variable; // STRING from parser
670
-
725
+ const loopVar = node.variable; // string name of the loop variable
671
726
  const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
672
-
673
- // Arrays
674
727
  if (Array.isArray(iterable)) {
675
728
  for (const value of iterable) {
676
729
  const loopEnv = createLoopEnv();
@@ -685,7 +738,6 @@ async evalFor(node, env) {
685
738
  }
686
739
  }
687
740
  }
688
- // Objects (keys)
689
741
  else {
690
742
  for (const key of Object.keys(iterable)) {
691
743
  const loopEnv = createLoopEnv();
@@ -704,9 +756,6 @@ async evalFor(node, env) {
704
756
  return null;
705
757
  }
706
758
 
707
- // -------------------------------
708
- // C-style for loop
709
- // -------------------------------
710
759
  const local = new Environment(env);
711
760
 
712
761
  if (node.init) await this.evaluate(node.init, local);
@@ -728,6 +777,7 @@ async evalFor(node, env) {
728
777
 
729
778
  return null;
730
779
  }
780
+
731
781
  evalFunctionDeclaration(node, env) {
732
782
  if (!node.name || typeof node.name !== 'string') {
733
783
  throw new RuntimeError('Function declaration requires a valid name', node, this.source);
@@ -790,18 +840,17 @@ async evalIndex(node, env) {
790
840
  const obj = await this.evaluate(node.object, env);
791
841
  const idx = await this.evaluate(node.indexer, env);
792
842
 
793
- if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
794
-
795
- if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
796
- throw new RuntimeError('Array index out of bounds', node, this.source);
843
+ if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
844
+ if (Array.isArray(obj)) {
845
+ if (idx < 0 || idx >= obj.length) return undefined;
846
+ return obj[idx];
797
847
  }
798
- if (typeof obj === 'object' && !(idx in obj)) {
799
- throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
848
+ if (typeof obj === 'object') {
849
+ return obj[idx]; // undefined if missing
800
850
  }
801
851
 
802
- return obj[idx];
852
+ return undefined;
803
853
  }
804
-
805
854
  async evalObject(node, env) {
806
855
  const out = {};
807
856
  for (const p of node.props) {
@@ -810,19 +859,22 @@ async evalObject(node, env) {
810
859
  }
811
860
  const key = await this.evaluate(p.key, env);
812
861
  const value = await this.evaluate(p.value, env);
813
- out[key] = value;
862
+ out[key] = value; // dynamic property assignment
814
863
  }
815
864
  return out;
816
865
  }
817
866
 
818
867
 
868
+
819
869
  async evalMember(node, env) {
820
870
  const obj = await this.evaluate(node.object, env);
821
- if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
822
- if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node, this.source);
871
+
872
+ if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
873
+
823
874
  return obj[node.property];
824
875
  }
825
876
 
877
+
826
878
  async evalUpdate(node, env) {
827
879
  const arg = node.argument;
828
880
  const getCurrent = async () => {
package/src/lexer.js CHANGED
@@ -28,7 +28,7 @@ class Lexer {
28
28
  'break', 'continue', 'func', 'return',
29
29
  'true', 'false', 'null',
30
30
  'ask', 'define', 'import', 'from', 'as',
31
- 'async', 'await', 'new', 'in', 'do', 'track'
31
+ 'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race'
32
32
  ];
33
33
  }
34
34