starlight-cli 1.0.34 → 1.0.35

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
@@ -1747,11 +1747,54 @@ async evalWhile(node, env) {
1747
1747
  }
1748
1748
 
1749
1749
  async evalFor(node, env) {
1750
+ // --- Python-style for x in iterable ---
1751
+ if (node.type === 'ForInStatement') {
1752
+ const iterable = await this.evaluate(node.iterable, env);
1753
+
1754
+ if (iterable == null || typeof iterable !== 'object') {
1755
+ throw new Error('Cannot iterate over non-iterable');
1756
+ }
1757
+
1758
+ // Handle arrays
1759
+ if (Array.isArray(iterable)) {
1760
+ for (let i = 0; i < iterable.length; i++) {
1761
+ const loopEnv = new Environment(env);
1762
+ loopEnv.define(node.variable, iterable[i]);
1763
+
1764
+ try {
1765
+ await this.evaluate(node.body, loopEnv);
1766
+ } catch (e) {
1767
+ if (e instanceof BreakSignal) break;
1768
+ if (e instanceof ContinueSignal) continue;
1769
+ throw e;
1770
+ }
1771
+ }
1772
+ } else {
1773
+ // Handle objects: iterate over keys
1774
+ for (const key of Object.keys(iterable)) {
1775
+ const loopEnv = new Environment(env);
1776
+ loopEnv.define(node.variable, key);
1777
+
1778
+ try {
1779
+ await this.evaluate(node.body, loopEnv);
1780
+ } catch (e) {
1781
+ if (e instanceof BreakSignal) break;
1782
+ if (e instanceof ContinueSignal) continue;
1783
+ throw e;
1784
+ }
1785
+ }
1786
+ }
1787
+
1788
+ return null;
1789
+ }
1790
+
1750
1791
  const local = new Environment(env);
1751
1792
  if (node.init) await this.evaluate(node.init, local);
1793
+
1752
1794
  while (!node.test || await this.evaluate(node.test, local)) {
1753
- try { await this.evaluate(node.body, local); }
1754
- catch (e) {
1795
+ try {
1796
+ await this.evaluate(node.body, local);
1797
+ } catch (e) {
1755
1798
  if (e instanceof BreakSignal) break;
1756
1799
  if (e instanceof ContinueSignal) {
1757
1800
  if (node.update) await this.evaluate(node.update, local);
@@ -1759,8 +1802,10 @@ async evalFor(node, env) {
1759
1802
  }
1760
1803
  throw e;
1761
1804
  }
1805
+
1762
1806
  if (node.update) await this.evaluate(node.update, local);
1763
1807
  }
1808
+
1764
1809
  return null;
1765
1810
  }
1766
1811
 
@@ -2266,10 +2311,22 @@ importStatement() {
2266
2311
 
2267
2312
  return { type: 'ImportStatement', path: pathToken.value, specifiers };
2268
2313
  }
2269
-
2270
2314
  forStatement() {
2271
2315
  this.eat('FOR');
2272
2316
 
2317
+ // --- Python-style: for variable in iterable ---
2318
+ if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
2319
+ const variable = this.current.value; // loop variable
2320
+ this.eat('IDENTIFIER');
2321
+
2322
+ this.eat('IN');
2323
+ const iterable = this.expression(); // the array/object to loop over
2324
+
2325
+ const body = this.block();
2326
+ return { type: 'ForInStatement', variable, iterable, body };
2327
+ }
2328
+
2329
+ // --- C-style: for(init; test; update) ---
2273
2330
  let init = null;
2274
2331
  let test = null;
2275
2332
  let update = null;
@@ -2277,16 +2334,23 @@ forStatement() {
2277
2334
  if (this.current.type === 'LPAREN') {
2278
2335
  this.eat('LPAREN');
2279
2336
 
2280
- if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
2281
- else this.eat('SEMICOLON');
2337
+ // init
2338
+ if (this.current.type !== 'SEMICOLON') {
2339
+ init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
2340
+ } else {
2341
+ this.eat('SEMICOLON');
2342
+ }
2282
2343
 
2344
+ // test
2283
2345
  if (this.current.type !== 'SEMICOLON') test = this.expression();
2284
2346
  this.eat('SEMICOLON');
2285
2347
 
2348
+ // update
2286
2349
  if (this.current.type !== 'RPAREN') update = this.expression();
2287
2350
  this.eat('RPAREN');
2288
2351
  } else {
2289
- init = this.expression();
2352
+ // fallback: single expression (could be used for Python-style with "in", already handled above)
2353
+ init = this.expression();
2290
2354
  if (this.current.type === 'IN') {
2291
2355
  this.eat('IN');
2292
2356
  test = this.expression(); // iterable
@@ -2299,15 +2363,15 @@ forStatement() {
2299
2363
 
2300
2364
  breakStatement() {
2301
2365
  this.eat('BREAK');
2302
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2366
+ // Python-style: no semicolon needed, ignore if present
2367
+ if (this.current.type === 'SEMICOLON') this.advance();
2303
2368
  return { type: 'BreakStatement' };
2304
2369
  }
2305
2370
 
2306
-
2307
- continueStatement() {
2371
+ continueStatement() {
2308
2372
  this.eat('CONTINUE');
2309
- // semicolon optional
2310
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2373
+ // Python-style: no semicolon needed, ignore if present
2374
+ if (this.current.type === 'SEMICOLON') this.advance();
2311
2375
  return { type: 'ContinueStatement' };
2312
2376
  }
2313
2377
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -404,11 +404,54 @@ async evalWhile(node, env) {
404
404
  }
405
405
 
406
406
  async evalFor(node, env) {
407
+ // --- Python-style for x in iterable ---
408
+ if (node.type === 'ForInStatement') {
409
+ const iterable = await this.evaluate(node.iterable, env);
410
+
411
+ if (iterable == null || typeof iterable !== 'object') {
412
+ throw new Error('Cannot iterate over non-iterable');
413
+ }
414
+
415
+ // Handle arrays
416
+ if (Array.isArray(iterable)) {
417
+ for (let i = 0; i < iterable.length; i++) {
418
+ const loopEnv = new Environment(env);
419
+ loopEnv.define(node.variable, iterable[i]);
420
+
421
+ try {
422
+ await this.evaluate(node.body, loopEnv);
423
+ } catch (e) {
424
+ if (e instanceof BreakSignal) break;
425
+ if (e instanceof ContinueSignal) continue;
426
+ throw e;
427
+ }
428
+ }
429
+ } else {
430
+ // Handle objects: iterate over keys
431
+ for (const key of Object.keys(iterable)) {
432
+ const loopEnv = new Environment(env);
433
+ loopEnv.define(node.variable, key);
434
+
435
+ try {
436
+ await this.evaluate(node.body, loopEnv);
437
+ } catch (e) {
438
+ if (e instanceof BreakSignal) break;
439
+ if (e instanceof ContinueSignal) continue;
440
+ throw e;
441
+ }
442
+ }
443
+ }
444
+
445
+ return null;
446
+ }
447
+
407
448
  const local = new Environment(env);
408
449
  if (node.init) await this.evaluate(node.init, local);
450
+
409
451
  while (!node.test || await this.evaluate(node.test, local)) {
410
- try { await this.evaluate(node.body, local); }
411
- catch (e) {
452
+ try {
453
+ await this.evaluate(node.body, local);
454
+ } catch (e) {
412
455
  if (e instanceof BreakSignal) break;
413
456
  if (e instanceof ContinueSignal) {
414
457
  if (node.update) await this.evaluate(node.update, local);
@@ -416,8 +459,10 @@ async evalFor(node, env) {
416
459
  }
417
460
  throw e;
418
461
  }
462
+
419
463
  if (node.update) await this.evaluate(node.update, local);
420
464
  }
465
+
421
466
  return null;
422
467
  }
423
468
 
package/src/parser.js CHANGED
@@ -208,10 +208,22 @@ importStatement() {
208
208
 
209
209
  return { type: 'ImportStatement', path: pathToken.value, specifiers };
210
210
  }
211
-
212
211
  forStatement() {
213
212
  this.eat('FOR');
214
213
 
214
+ // --- Python-style: for variable in iterable ---
215
+ if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
216
+ const variable = this.current.value; // loop variable
217
+ this.eat('IDENTIFIER');
218
+
219
+ this.eat('IN');
220
+ const iterable = this.expression(); // the array/object to loop over
221
+
222
+ const body = this.block();
223
+ return { type: 'ForInStatement', variable, iterable, body };
224
+ }
225
+
226
+ // --- C-style: for(init; test; update) ---
215
227
  let init = null;
216
228
  let test = null;
217
229
  let update = null;
@@ -219,16 +231,23 @@ forStatement() {
219
231
  if (this.current.type === 'LPAREN') {
220
232
  this.eat('LPAREN');
221
233
 
222
- if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
223
- else this.eat('SEMICOLON');
234
+ // init
235
+ if (this.current.type !== 'SEMICOLON') {
236
+ init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
237
+ } else {
238
+ this.eat('SEMICOLON');
239
+ }
224
240
 
241
+ // test
225
242
  if (this.current.type !== 'SEMICOLON') test = this.expression();
226
243
  this.eat('SEMICOLON');
227
244
 
245
+ // update
228
246
  if (this.current.type !== 'RPAREN') update = this.expression();
229
247
  this.eat('RPAREN');
230
248
  } else {
231
- init = this.expression();
249
+ // fallback: single expression (could be used for Python-style with "in", already handled above)
250
+ init = this.expression();
232
251
  if (this.current.type === 'IN') {
233
252
  this.eat('IN');
234
253
  test = this.expression(); // iterable
@@ -241,15 +260,15 @@ forStatement() {
241
260
 
242
261
  breakStatement() {
243
262
  this.eat('BREAK');
244
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
263
+ // Python-style: no semicolon needed, ignore if present
264
+ if (this.current.type === 'SEMICOLON') this.advance();
245
265
  return { type: 'BreakStatement' };
246
266
  }
247
267
 
248
-
249
- continueStatement() {
268
+ continueStatement() {
250
269
  this.eat('CONTINUE');
251
- // semicolon optional
252
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
270
+ // Python-style: no semicolon needed, ignore if present
271
+ if (this.current.type === 'SEMICOLON') this.advance();
253
272
  return { type: 'ContinueStatement' };
254
273
  }
255
274