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/README.md +46 -0
- package/dist/index.js +265 -112
- package/package.json +3 -2
- package/src/evaluator.js +131 -79
- package/src/lexer.js +1 -1
- package/src/parser.js +57 -31
- package/src/starlight.js +1 -1
- package/src/program.sl +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-cli",
|
|
3
|
-
"version": "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) {
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
if (value ===
|
|
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
|
-
|
|
129
|
-
if (t === 'string') return value;
|
|
124
|
+
if (t === 'string') return color.cyan(value);
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
if (t === 'number') return String(value);
|
|
126
|
+
if (t === 'number') return color.green(String(value));
|
|
133
127
|
|
|
134
|
-
|
|
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
|
-
|
|
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}:
|
|
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
|
-
|
|
165
|
-
|
|
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;
|
|
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);
|
|
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
|
-
|
|
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')
|
|
539
|
-
|
|
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; //
|
|
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('
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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'
|
|
799
|
-
|
|
848
|
+
if (typeof obj === 'object') {
|
|
849
|
+
return obj[idx]; // undefined if missing
|
|
800
850
|
}
|
|
801
851
|
|
|
802
|
-
return
|
|
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
|
-
|
|
822
|
-
if (
|
|
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