starlight-cli 1.0.41 → 1.0.43

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/dist/index.js CHANGED
@@ -1370,6 +1370,37 @@ class Environment {
1370
1370
  if (this.parent) return this.parent.get(name);
1371
1371
  throw new Error(`Undefined variable: ${name}`);
1372
1372
  }
1373
+ formatValue(value, seen = new Set()) {
1374
+ if (typeof value === 'object' && value !== null) {
1375
+ if (seen.has(value)) return '[Circular]';
1376
+ seen.add(value);
1377
+ }
1378
+
1379
+ if (value === null) return 'null';
1380
+ if (value === undefined) return 'undefined';
1381
+
1382
+ const t = typeof value;
1383
+
1384
+ if (t === 'string') return value;
1385
+ if (t === 'number' || t === 'boolean') return String(value);
1386
+
1387
+ if (t === 'function') {
1388
+ return '[function]';
1389
+ }
1390
+
1391
+ if (Array.isArray(value)) {
1392
+ return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
1393
+ }
1394
+
1395
+ if (t === 'object') {
1396
+ const entries = Object.entries(value).map(
1397
+ ([k, v]) => `${k}: ${this.formatValue(v, seen)}`
1398
+ );
1399
+ return '{ ' + entries.join(', ') + ' }';
1400
+ }
1401
+
1402
+ return String(value);
1403
+ }
1373
1404
 
1374
1405
  set(name, value) {
1375
1406
  if (name in this.store) { this.store[name] = value; return value; }
@@ -1720,16 +1751,11 @@ async evalCompoundAssignment(node, env) {
1720
1751
 
1721
1752
  async evalSldeploy(node, env) {
1722
1753
  const val = await this.evaluate(node.expr, env);
1723
-
1724
- if (typeof val === 'object' && val !== null) {
1725
- console.log(JSON.stringify(val, null, 2));
1726
- } else {
1727
- console.log(val);
1728
- }
1729
-
1754
+ console.log(this.formatValue(val));
1730
1755
  return val;
1731
1756
  }
1732
1757
 
1758
+
1733
1759
  async evalAsk(node, env) {
1734
1760
  const prompt = await this.evaluate(node.prompt, env);
1735
1761
  const input = readlineSync.question(prompt + ' ');
@@ -1801,9 +1827,8 @@ async evalWhile(node, env) {
1801
1827
  return null;
1802
1828
  }
1803
1829
  async evalFor(node, env) {
1804
-
1805
1830
  // -------------------------------
1806
- // Python-style: for x in iterable
1831
+ // Python-style: for x in iterable (with optional 'let')
1807
1832
  // -------------------------------
1808
1833
  if (node.type === 'ForInStatement') {
1809
1834
  const iterable = await this.evaluate(node.iterable, env);
@@ -1814,10 +1839,12 @@ async evalFor(node, env) {
1814
1839
 
1815
1840
  const loopVar = node.variable; // STRING from parser
1816
1841
 
1842
+ const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
1843
+
1817
1844
  // Arrays
1818
1845
  if (Array.isArray(iterable)) {
1819
1846
  for (const value of iterable) {
1820
- const loopEnv = new Environment(env);
1847
+ const loopEnv = createLoopEnv();
1821
1848
  loopEnv.define(loopVar, value);
1822
1849
 
1823
1850
  try {
@@ -1832,7 +1859,7 @@ async evalFor(node, env) {
1832
1859
  // Objects (keys)
1833
1860
  else {
1834
1861
  for (const key of Object.keys(iterable)) {
1835
- const loopEnv = new Environment(env);
1862
+ const loopEnv = createLoopEnv();
1836
1863
  loopEnv.define(loopVar, key);
1837
1864
 
1838
1865
  try {
@@ -2396,16 +2423,30 @@ importStatement() {
2396
2423
  forStatement() {
2397
2424
  this.eat('FOR');
2398
2425
 
2399
- // --- Python-style: for variable in iterable ---
2400
- if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
2401
- const variable = this.current.value; // loop variable
2402
- this.eat('IDENTIFIER');
2426
+ // --- Python-style: for variable in iterable (supports optional 'let') ---
2427
+ if ((this.current.type === 'LET' && this.peekType() === 'IDENTIFIER' && this.peekType(2) === 'IN') ||
2428
+ (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
2429
+
2430
+ let variable;
2431
+ let iterable;
2432
+ let letKeyword = false;
2403
2433
 
2404
- this.eat('IN');
2405
- const iterable = this.expression(); // the array/object to loop over
2434
+ if (this.current.type === 'LET') {
2435
+ letKeyword = true;
2436
+ this.eat('LET');
2437
+ variable = this.current.value;
2438
+ this.eat('IDENTIFIER');
2439
+ this.eat('IN');
2440
+ iterable = this.expression();
2441
+ } else {
2442
+ variable = this.current.value;
2443
+ this.eat('IDENTIFIER');
2444
+ this.eat('IN');
2445
+ iterable = this.expression();
2446
+ }
2406
2447
 
2407
2448
  const body = this.block();
2408
- return { type: 'ForInStatement', variable, iterable, body };
2449
+ return { type: 'ForInStatement', variable, iterable, letKeyword, body };
2409
2450
  }
2410
2451
 
2411
2452
  // --- C-style: for(init; test; update) ---
@@ -2431,7 +2472,7 @@ forStatement() {
2431
2472
  if (this.current.type !== 'RPAREN') update = this.expression();
2432
2473
  this.eat('RPAREN');
2433
2474
  } else {
2434
- // fallback: single expression (could be used for Python-style with "in", already handled above)
2475
+ // fallback: single expression (rare, mostly handled above)
2435
2476
  init = this.expression();
2436
2477
  if (this.current.type === 'IN') {
2437
2478
  this.eat('IN');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -27,6 +27,37 @@ class Environment {
27
27
  if (this.parent) return this.parent.get(name);
28
28
  throw new Error(`Undefined variable: ${name}`);
29
29
  }
30
+ formatValue(value, seen = new Set()) {
31
+ if (typeof value === 'object' && value !== null) {
32
+ if (seen.has(value)) return '[Circular]';
33
+ seen.add(value);
34
+ }
35
+
36
+ if (value === null) return 'null';
37
+ if (value === undefined) return 'undefined';
38
+
39
+ const t = typeof value;
40
+
41
+ if (t === 'string') return value;
42
+ if (t === 'number' || t === 'boolean') return String(value);
43
+
44
+ if (t === 'function') {
45
+ return '[function]';
46
+ }
47
+
48
+ if (Array.isArray(value)) {
49
+ return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
50
+ }
51
+
52
+ if (t === 'object') {
53
+ const entries = Object.entries(value).map(
54
+ ([k, v]) => `${k}: ${this.formatValue(v, seen)}`
55
+ );
56
+ return '{ ' + entries.join(', ') + ' }';
57
+ }
58
+
59
+ return String(value);
60
+ }
30
61
 
31
62
  set(name, value) {
32
63
  if (name in this.store) { this.store[name] = value; return value; }
@@ -377,16 +408,11 @@ async evalCompoundAssignment(node, env) {
377
408
 
378
409
  async evalSldeploy(node, env) {
379
410
  const val = await this.evaluate(node.expr, env);
380
-
381
- if (typeof val === 'object' && val !== null) {
382
- console.log(JSON.stringify(val, null, 2));
383
- } else {
384
- console.log(val);
385
- }
386
-
411
+ console.log(this.formatValue(val));
387
412
  return val;
388
413
  }
389
414
 
415
+
390
416
  async evalAsk(node, env) {
391
417
  const prompt = await this.evaluate(node.prompt, env);
392
418
  const input = readlineSync.question(prompt + ' ');
@@ -458,9 +484,8 @@ async evalWhile(node, env) {
458
484
  return null;
459
485
  }
460
486
  async evalFor(node, env) {
461
-
462
487
  // -------------------------------
463
- // Python-style: for x in iterable
488
+ // Python-style: for x in iterable (with optional 'let')
464
489
  // -------------------------------
465
490
  if (node.type === 'ForInStatement') {
466
491
  const iterable = await this.evaluate(node.iterable, env);
@@ -471,10 +496,12 @@ async evalFor(node, env) {
471
496
 
472
497
  const loopVar = node.variable; // STRING from parser
473
498
 
499
+ const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
500
+
474
501
  // Arrays
475
502
  if (Array.isArray(iterable)) {
476
503
  for (const value of iterable) {
477
- const loopEnv = new Environment(env);
504
+ const loopEnv = createLoopEnv();
478
505
  loopEnv.define(loopVar, value);
479
506
 
480
507
  try {
@@ -489,7 +516,7 @@ async evalFor(node, env) {
489
516
  // Objects (keys)
490
517
  else {
491
518
  for (const key of Object.keys(iterable)) {
492
- const loopEnv = new Environment(env);
519
+ const loopEnv = createLoopEnv();
493
520
  loopEnv.define(loopVar, key);
494
521
 
495
522
  try {
package/src/parser.js CHANGED
@@ -229,16 +229,30 @@ importStatement() {
229
229
  forStatement() {
230
230
  this.eat('FOR');
231
231
 
232
- // --- Python-style: for variable in iterable ---
233
- if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
234
- const variable = this.current.value; // loop variable
235
- this.eat('IDENTIFIER');
236
-
237
- this.eat('IN');
238
- const iterable = this.expression(); // the array/object to loop over
232
+ // --- Python-style: for variable in iterable (supports optional 'let') ---
233
+ if ((this.current.type === 'LET' && this.peekType() === 'IDENTIFIER' && this.peekType(2) === 'IN') ||
234
+ (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
235
+
236
+ let variable;
237
+ let iterable;
238
+ let letKeyword = false;
239
+
240
+ if (this.current.type === 'LET') {
241
+ letKeyword = true;
242
+ this.eat('LET');
243
+ variable = this.current.value;
244
+ this.eat('IDENTIFIER');
245
+ this.eat('IN');
246
+ iterable = this.expression();
247
+ } else {
248
+ variable = this.current.value;
249
+ this.eat('IDENTIFIER');
250
+ this.eat('IN');
251
+ iterable = this.expression();
252
+ }
239
253
 
240
254
  const body = this.block();
241
- return { type: 'ForInStatement', variable, iterable, body };
255
+ return { type: 'ForInStatement', variable, iterable, letKeyword, body };
242
256
  }
243
257
 
244
258
  // --- C-style: for(init; test; update) ---
@@ -264,7 +278,7 @@ forStatement() {
264
278
  if (this.current.type !== 'RPAREN') update = this.expression();
265
279
  this.eat('RPAREN');
266
280
  } else {
267
- // fallback: single expression (could be used for Python-style with "in", already handled above)
281
+ // fallback: single expression (rare, mostly handled above)
268
282
  init = this.expression();
269
283
  if (this.current.type === 'IN') {
270
284
  this.eat('IN');