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 +75 -11
- package/package.json +1 -1
- package/src/evaluator.js +47 -2
- package/src/parser.js +28 -9
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 {
|
|
1754
|
-
|
|
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
|
-
|
|
2281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2310
|
-
if (this.current.type === 'SEMICOLON') this.
|
|
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
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 {
|
|
411
|
-
|
|
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
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
252
|
-
if (this.current.type === 'SEMICOLON') this.
|
|
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
|
|