starlight-cli 1.0.33 → 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
 
@@ -2113,164 +2158,231 @@ class Parser {
2113
2158
  }
2114
2159
 
2115
2160
  varDeclaration() {
2116
- this.eat('LET');
2117
- const id = this.current.value;
2118
- this.eat('IDENTIFIER');
2161
+ this.eat('LET');
2162
+ const id = this.current.value;
2163
+ this.eat('IDENTIFIER');
2164
+
2165
+ let expr = null;
2166
+ if (this.current.type === 'EQUAL') {
2119
2167
  this.eat('EQUAL');
2120
- const expr = this.expression();
2121
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2122
- return { type: 'VarDeclaration', id, expr };
2168
+ expr = this.expression();
2123
2169
  }
2124
2170
 
2125
- sldeployStatement() {
2126
- this.eat('SLDEPLOY');
2127
- const expr = this.expression();
2128
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2129
- return { type: 'SldeployStatement', expr };
2130
- }
2171
+ // semicolon optional
2172
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2131
2173
 
2132
- defineStatement() {
2133
- this.eat('DEFINE');
2134
- const id = this.current.value;
2135
- this.eat('IDENTIFIER');
2136
- let expr = null;
2137
- if (this.current.type === 'EQUAL') {
2138
- this.eat('EQUAL');
2139
- expr = this.expression();
2140
- }
2141
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2142
- return { type: 'DefineStatement', id, expr };
2174
+ return { type: 'VarDeclaration', id, expr };
2175
+ }
2176
+
2177
+ sldeployStatement() {
2178
+ this.eat('SLDEPLOY');
2179
+ const expr = this.expression();
2180
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2181
+ return { type: 'SldeployStatement', expr };
2182
+ }
2183
+
2184
+ defineStatement() {
2185
+ this.eat('DEFINE');
2186
+ const id = this.current.value;
2187
+ this.eat('IDENTIFIER');
2188
+
2189
+ let expr = null;
2190
+ if (this.current.type === 'EQUAL') {
2191
+ this.eat('EQUAL');
2192
+ expr = this.expression();
2143
2193
  }
2194
+
2195
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2196
+
2197
+ return { type: 'DefineStatement', id, expr };
2198
+ }
2199
+
2144
2200
  asyncFuncDeclaration() {
2145
2201
  const name = this.current.value;
2146
2202
  this.eat('IDENTIFIER');
2147
- this.eat('LPAREN');
2148
- const params = [];
2149
- if (this.current.type !== 'RPAREN') {
2150
- params.push(this.current.value);
2151
- this.eat('IDENTIFIER');
2152
- while (this.current.type === 'COMMA') {
2153
- this.eat('COMMA');
2203
+
2204
+ let params = [];
2205
+ if (this.current.type === 'LPAREN') {
2206
+ this.eat('LPAREN');
2207
+ if (this.current.type !== 'RPAREN') {
2208
+ params.push(this.current.value);
2209
+ this.eat('IDENTIFIER');
2210
+ while (this.current.type === 'COMMA') {
2211
+ this.eat('COMMA');
2212
+ params.push(this.current.value);
2213
+ this.eat('IDENTIFIER');
2214
+ }
2215
+ }
2216
+ this.eat('RPAREN');
2217
+ } else {
2218
+ // no parentheses: single param as Python style
2219
+ if (this.current.type === 'IDENTIFIER') {
2154
2220
  params.push(this.current.value);
2155
2221
  this.eat('IDENTIFIER');
2156
2222
  }
2157
2223
  }
2158
- this.eat('RPAREN');
2224
+
2159
2225
  const body = this.block();
2160
2226
  return { type: 'FunctionDeclaration', name, params, body, async: true };
2161
2227
  }
2162
2228
 
2163
- ifStatement() {
2164
- this.eat('IF');
2229
+ ifStatement() {
2230
+ this.eat('IF');
2231
+
2232
+ let test;
2233
+ if (this.current.type === 'LPAREN') {
2165
2234
  this.eat('LPAREN');
2166
- const test = this.expression();
2235
+ test = this.expression();
2167
2236
  this.eat('RPAREN');
2168
- const consequent = this.block();
2169
- let alternate = null;
2170
- if (this.current.type === 'ELSE') {
2171
- this.eat('ELSE');
2172
- if (this.current.type === 'IF') alternate = this.ifStatement();
2173
- else alternate = this.block();
2174
- }
2175
- return { type: 'IfStatement', test, consequent, alternate };
2237
+ } else {
2238
+ // python style: no parentheses
2239
+ test = this.expression();
2176
2240
  }
2177
2241
 
2178
- whileStatement() {
2179
- this.eat('WHILE');
2242
+ const consequent = this.block();
2243
+
2244
+ let alternate = null;
2245
+ if (this.current.type === 'ELSE') {
2246
+ this.eat('ELSE');
2247
+ if (this.current.type === 'IF') alternate = this.ifStatement();
2248
+ else alternate = this.block();
2249
+ }
2250
+
2251
+ return { type: 'IfStatement', test, consequent, alternate };
2252
+ }
2253
+
2254
+ whileStatement() {
2255
+ this.eat('WHILE');
2256
+
2257
+ let test;
2258
+ if (this.current.type === 'LPAREN') {
2180
2259
  this.eat('LPAREN');
2181
- const test = this.expression();
2260
+ test = this.expression();
2182
2261
  this.eat('RPAREN');
2183
- const body = this.block();
2184
- return { type: 'WhileStatement', test, body };
2262
+ } else {
2263
+ test = this.expression();
2185
2264
  }
2186
- importStatement() {
2187
- this.eat('IMPORT');
2188
2265
 
2189
- let specifiers = [];
2266
+ const body = this.block();
2267
+ return { type: 'WhileStatement', test, body };
2268
+ }
2269
+
2270
+ importStatement() {
2271
+ this.eat('IMPORT');
2272
+
2273
+ let specifiers = [];
2274
+ if (this.current.type === 'STAR') {
2275
+ this.eat('STAR');
2276
+ this.eat('AS');
2277
+ const name = this.current.value;
2278
+ this.eat('IDENTIFIER');
2279
+ specifiers.push({ type: 'NamespaceImport', local: name });
2280
+ } else if (this.current.type === 'LBRACE') {
2281
+ this.eat('LBRACE');
2282
+ while (this.current.type !== 'RBRACE') {
2283
+ const importedName = this.current.value;
2284
+ this.eat('IDENTIFIER');
2190
2285
 
2191
- if (this.current.type === 'STAR') {
2192
- this.eat('STAR');
2286
+ let localName = importedName;
2287
+ if (this.current.type === 'AS') {
2193
2288
  this.eat('AS');
2194
- const name = this.current.value;
2195
- this.eat('IDENTIFIER');
2196
- specifiers.push({ type: 'NamespaceImport', local: name });
2197
- }
2198
- else if (this.current.type === 'LBRACE') {
2199
- this.eat('LBRACE');
2200
- while (this.current.type !== 'RBRACE') {
2201
- const importedName = this.current.value;
2202
- this.eat('IDENTIFIER');
2203
- let localName = importedName;
2204
- if (this.current.type === 'AS') {
2205
- this.eat('AS');
2206
- localName = this.current.value;
2207
- this.eat('IDENTIFIER');
2208
- }
2209
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2210
- if (this.current.type === 'COMMA') this.eat('COMMA');
2211
- }
2212
- this.eat('RBRACE');
2213
- }
2214
- else if (this.current.type === 'IDENTIFIER') {
2215
- const localName = this.current.value;
2289
+ localName = this.current.value;
2216
2290
  this.eat('IDENTIFIER');
2217
- specifiers.push({ type: 'DefaultImport', local: localName });
2218
- } else {
2219
- throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
2291
+ }
2292
+
2293
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2294
+ if (this.current.type === 'COMMA') this.eat('COMMA');
2220
2295
  }
2296
+ this.eat('RBRACE');
2297
+ } else if (this.current.type === 'IDENTIFIER') {
2298
+ const localName = this.current.value;
2299
+ this.eat('IDENTIFIER');
2300
+ specifiers.push({ type: 'DefaultImport', local: localName });
2301
+ } else {
2302
+ throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
2303
+ }
2221
2304
 
2222
- this.eat('FROM');
2223
- const pathToken = this.current;
2224
- if (pathToken.type !== 'STRING') throw new Error(`Expected string literal after from in import at line ${this.current.line}, column ${this.current.column}`);
2225
- this.eat('STRING');
2305
+ this.eat('FROM');
2306
+ const pathToken = this.current;
2307
+ if (pathToken.type !== 'STRING') throw new Error(`Expected string after FROM at line ${this.current.line}, column ${this.current.column}`);
2308
+ this.eat('STRING');
2226
2309
 
2227
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2310
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2228
2311
 
2229
- return { type: 'ImportStatement', path: pathToken.value, specifiers };
2312
+ return { type: 'ImportStatement', path: pathToken.value, specifiers };
2230
2313
  }
2314
+ forStatement() {
2315
+ this.eat('FOR');
2231
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
2232
2324
 
2233
- forStatement() {
2234
- this.eat('FOR');
2325
+ const body = this.block();
2326
+ return { type: 'ForInStatement', variable, iterable, body };
2327
+ }
2328
+
2329
+ // --- C-style: for(init; test; update) ---
2330
+ let init = null;
2331
+ let test = null;
2332
+ let update = null;
2333
+
2334
+ if (this.current.type === 'LPAREN') {
2235
2335
  this.eat('LPAREN');
2236
2336
 
2237
- let init = null;
2337
+ // init
2238
2338
  if (this.current.type !== 'SEMICOLON') {
2239
2339
  init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
2240
2340
  } else {
2241
2341
  this.eat('SEMICOLON');
2242
2342
  }
2243
2343
 
2244
- let test = null;
2344
+ // test
2245
2345
  if (this.current.type !== 'SEMICOLON') test = this.expression();
2246
2346
  this.eat('SEMICOLON');
2247
2347
 
2248
- let update = null;
2348
+ // update
2249
2349
  if (this.current.type !== 'RPAREN') update = this.expression();
2250
2350
  this.eat('RPAREN');
2251
-
2252
- const body = this.block();
2253
- return { type: 'ForStatement', init, test, update, body };
2351
+ } else {
2352
+ // fallback: single expression (could be used for Python-style with "in", already handled above)
2353
+ init = this.expression();
2354
+ if (this.current.type === 'IN') {
2355
+ this.eat('IN');
2356
+ test = this.expression(); // iterable
2357
+ }
2254
2358
  }
2255
2359
 
2256
- breakStatement() {
2257
- this.eat('BREAK');
2258
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2259
- return { type: 'BreakStatement' };
2260
- }
2360
+ const body = this.block();
2361
+ return { type: 'ForStatement', init, test, update, body };
2362
+ }
2261
2363
 
2262
- continueStatement() {
2263
- this.eat('CONTINUE');
2264
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2265
- return { type: 'ContinueStatement' };
2266
- }
2364
+ breakStatement() {
2365
+ this.eat('BREAK');
2366
+ // Python-style: no semicolon needed, ignore if present
2367
+ if (this.current.type === 'SEMICOLON') this.advance();
2368
+ return { type: 'BreakStatement' };
2369
+ }
2267
2370
 
2268
- funcDeclaration() {
2269
- this.eat('FUNC');
2270
- const name = this.current.value;
2271
- this.eat('IDENTIFIER');
2371
+ continueStatement() {
2372
+ this.eat('CONTINUE');
2373
+ // Python-style: no semicolon needed, ignore if present
2374
+ if (this.current.type === 'SEMICOLON') this.advance();
2375
+ return { type: 'ContinueStatement' };
2376
+ }
2377
+
2378
+ funcDeclaration() {
2379
+ this.eat('FUNC');
2380
+ const name = this.current.value;
2381
+ this.eat('IDENTIFIER');
2382
+
2383
+ let params = [];
2384
+ if (this.current.type === 'LPAREN') {
2272
2385
  this.eat('LPAREN');
2273
- const params = [];
2274
2386
  if (this.current.type !== 'RPAREN') {
2275
2387
  params.push(this.current.value);
2276
2388
  this.eat('IDENTIFIER');
@@ -2281,176 +2393,203 @@ asyncFuncDeclaration() {
2281
2393
  }
2282
2394
  }
2283
2395
  this.eat('RPAREN');
2284
- const body = this.block();
2285
- return { type: 'FunctionDeclaration', name, params, body };
2396
+ } else {
2397
+ // Python-style: single param without parentheses
2398
+ if (this.current.type === 'IDENTIFIER') {
2399
+ params.push(this.current.value);
2400
+ this.eat('IDENTIFIER');
2401
+ }
2286
2402
  }
2287
2403
 
2288
- returnStatement() {
2289
- this.eat('RETURN');
2290
- let argument = null;
2291
- if (this.current.type !== 'SEMICOLON') argument = this.expression();
2292
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2293
- return { type: 'ReturnStatement', argument };
2404
+ const body = this.block();
2405
+ return { type: 'FunctionDeclaration', name, params, body };
2406
+ }
2407
+
2408
+ returnStatement() {
2409
+ this.eat('RETURN');
2410
+
2411
+ let argument = null;
2412
+ if (this.current.type !== 'SEMICOLON') {
2413
+ argument = this.expression();
2294
2414
  }
2295
2415
 
2296
- block() {
2297
- this.eat('LBRACE');
2298
- const body = [];
2299
- while (this.current.type !== 'RBRACE') {
2300
- body.push(this.statement());
2301
- }
2302
- this.eat('RBRACE');
2303
- return { type: 'BlockStatement', body };
2416
+ // semicolon optional
2417
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2418
+
2419
+ return { type: 'ReturnStatement', argument };
2420
+ }
2421
+
2422
+ block() {
2423
+ this.eat('LBRACE');
2424
+ const body = [];
2425
+ while (this.current.type !== 'RBRACE') {
2426
+ body.push(this.statement());
2304
2427
  }
2428
+ this.eat('RBRACE');
2429
+ return { type: 'BlockStatement', body };
2430
+ }
2431
+
2432
+ expressionStatement() {
2433
+ const expr = this.expression();
2434
+ // semicolon optional
2435
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2436
+ return { type: 'ExpressionStatement', expression: expr };
2437
+ }
2438
+
2439
+ expression() {
2440
+ return this.assignment();
2441
+ }
2442
+
2443
+ assignment() {
2444
+ const node = this.logicalOr();
2445
+ const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
2305
2446
 
2306
- expressionStatement() {
2307
- const expr = this.expression();
2308
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2309
- return { type: 'ExpressionStatement', expression: expr };
2447
+ if (compoundOps.includes(this.current.type)) {
2448
+ const op = this.current.type;
2449
+ this.eat(op);
2450
+ const right = this.assignment();
2451
+ return { type: 'CompoundAssignment', operator: op, left: node, right };
2310
2452
  }
2311
2453
 
2312
- expression() {
2313
- return this.assignment();
2454
+ if (this.current.type === 'EQUAL') {
2455
+ this.eat('EQUAL');
2456
+ const right = this.assignment();
2457
+ return { type: 'AssignmentExpression', left: node, right };
2314
2458
  }
2315
2459
 
2316
- assignment() {
2317
- const node = this.logicalOr();
2318
- const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
2319
- if (compoundOps.includes(this.current.type)) {
2320
- const op = this.current.type;
2321
- this.eat(op);
2322
- const right = this.assignment();
2323
- return { type: 'CompoundAssignment', operator: op, left: node, right };
2324
- }
2460
+ return node;
2461
+ }
2325
2462
 
2326
- if (this.current.type === 'EQUAL') {
2327
- this.eat('EQUAL');
2328
- const right = this.assignment();
2329
- return { type: 'AssignmentExpression', left: node, right };
2330
- }
2463
+ logicalOr() {
2464
+ let node = this.logicalAnd();
2465
+ while (this.current.type === 'OR') {
2466
+ const op = this.current.type;
2467
+ this.eat(op);
2468
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
2469
+ }
2470
+ return node;
2471
+ }
2331
2472
 
2332
- return node;
2473
+ logicalAnd() {
2474
+ let node = this.equality();
2475
+ while (this.current.type === 'AND') {
2476
+ const op = this.current.type;
2477
+ this.eat(op);
2478
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
2333
2479
  }
2480
+ return node;
2481
+ }
2482
+ equality() {
2483
+ let node = this.comparison();
2484
+ while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
2485
+ const op = this.current.type;
2486
+ this.eat(op);
2487
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
2488
+ }
2489
+ return node;
2490
+ }
2334
2491
 
2335
- logicalOr() {
2336
- let node = this.logicalAnd();
2337
- while (this.current.type === 'OR') {
2338
- const op = this.current.type;
2339
- this.eat(op);
2340
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
2341
- }
2342
- return node;
2492
+ comparison() {
2493
+ let node = this.term();
2494
+ while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
2495
+ const op = this.current.type;
2496
+ this.eat(op);
2497
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
2343
2498
  }
2499
+ return node;
2500
+ }
2344
2501
 
2345
- logicalAnd() {
2346
- let node = this.equality();
2347
- while (this.current.type === 'AND') {
2348
- const op = this.current.type;
2349
- this.eat(op);
2350
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
2351
- }
2352
- return node;
2502
+ term() {
2503
+ let node = this.factor();
2504
+ while (['PLUS', 'MINUS'].includes(this.current.type)) {
2505
+ const op = this.current.type;
2506
+ this.eat(op);
2507
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
2353
2508
  }
2509
+ return node;
2510
+ }
2354
2511
 
2355
- equality() {
2356
- let node = this.comparison();
2357
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
2358
- const op = this.current.type;
2359
- this.eat(op);
2360
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
2361
- }
2362
- return node;
2512
+ factor() {
2513
+ let node = this.unary();
2514
+ while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
2515
+ const op = this.current.type;
2516
+ this.eat(op);
2517
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
2363
2518
  }
2519
+ return node;
2520
+ }
2364
2521
 
2365
- comparison() {
2366
- let node = this.term();
2367
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
2368
- const op = this.current.type;
2369
- this.eat(op);
2370
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
2371
- }
2372
- return node;
2522
+ unary() {
2523
+ if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
2524
+ const op = this.current.type;
2525
+ this.eat(op);
2526
+ return { type: 'UnaryExpression', operator: op, argument: this.unary() };
2373
2527
  }
2374
2528
 
2375
- term() {
2376
- let node = this.factor();
2377
- while (['PLUS', 'MINUS'].includes(this.current.type)) {
2378
- const op = this.current.type;
2379
- this.eat(op);
2380
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
2381
- }
2382
- return node;
2529
+ // Python-like: ignore ++ and -- if not used
2530
+ if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2531
+ const op = this.current.type;
2532
+ this.eat(op);
2533
+ const argument = this.unary();
2534
+ return { type: 'UpdateExpression', operator: op, argument, prefix: true };
2383
2535
  }
2384
2536
 
2385
- factor() {
2386
- let node = this.unary();
2387
- while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
2388
- const op = this.current.type;
2389
- this.eat(op);
2390
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
2537
+ return this.postfix();
2538
+ }
2539
+
2540
+ postfix() {
2541
+ let node = this.primary();
2542
+ while (true) {
2543
+ if (this.current.type === 'LBRACKET') {
2544
+ this.eat('LBRACKET');
2545
+ const index = this.expression();
2546
+ if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
2547
+ node = { type: 'IndexExpression', object: node, indexer: index };
2548
+ continue;
2391
2549
  }
2392
- return node;
2393
- }
2394
2550
 
2395
- unary() {
2396
- if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
2397
- const op = this.current.type;
2398
- this.eat(op);
2399
- return { type: 'UnaryExpression', operator: op, argument: this.unary() };
2551
+ if (this.current.type === 'LPAREN') {
2552
+ this.eat('LPAREN');
2553
+ const args = [];
2554
+ while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
2555
+ args.push(this.expression());
2556
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
2557
+ }
2558
+ if (this.current.type === 'RPAREN') this.eat('RPAREN');
2559
+ node = { type: 'CallExpression', callee: node, arguments: args };
2560
+ continue;
2400
2561
  }
2562
+
2563
+ if (this.current.type === 'DOT') {
2564
+ this.eat('DOT');
2565
+ const property = this.current.value || '';
2566
+ if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
2567
+ node = { type: 'MemberExpression', object: node, property };
2568
+ continue;
2569
+ }
2570
+
2401
2571
  if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2402
2572
  const op = this.current.type;
2403
2573
  this.eat(op);
2404
- const argument = this.unary();
2405
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
2574
+ node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
2575
+ continue;
2406
2576
  }
2407
- return this.postfix();
2408
- }
2409
2577
 
2410
- postfix() {
2411
- let node = this.primary();
2412
- while (true) {
2413
- if (this.current.type === 'LBRACKET') {
2414
- this.eat('LBRACKET');
2415
- const index = this.expression();
2416
- this.eat('RBRACKET');
2417
- node = { type: 'IndexExpression', object: node, indexer: index };
2418
- continue;
2419
- }
2420
- if (this.current.type === 'LPAREN') {
2421
- this.eat('LPAREN');
2422
- const args = [];
2423
- if (this.current.type !== 'RPAREN') {
2424
- args.push(this.expression());
2425
- while (this.current.type === 'COMMA') {
2426
- this.eat('COMMA');
2427
- args.push(this.expression());
2428
- }
2429
- }
2430
- this.eat('RPAREN');
2431
- node = { type: 'CallExpression', callee: node, arguments: args };
2432
- continue;
2433
- }
2434
- if (this.current.type === 'DOT') {
2435
- this.eat('DOT');
2436
- if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
2437
- const property = this.current.value;
2438
- this.eat('IDENTIFIER');
2439
- node = { type: 'MemberExpression', object: node, property };
2440
- continue;
2441
- }
2442
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2443
- const op = this.current.type;
2444
- this.eat(op);
2445
- node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
2446
- continue;
2447
- }
2448
- break;
2578
+ // Python-style: implicit function calls (if identifier followed by identifier)
2579
+ if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
2580
+ const argNode = { type: 'Identifier', name: this.current.value };
2581
+ this.eat('IDENTIFIER');
2582
+ node = { type: 'CallExpression', callee: node, arguments: [argNode] };
2583
+ continue;
2449
2584
  }
2450
- return node;
2585
+
2586
+ break;
2451
2587
  }
2588
+ return node;
2589
+ }
2590
+
2452
2591
  arrowFunction(params) {
2453
- this.eat('ARROW');
2592
+ if (this.current.type === 'ARROW') this.eat('ARROW');
2454
2593
  const body = this.expression();
2455
2594
  return {
2456
2595
  type: 'ArrowFunctionExpression',
@@ -2459,126 +2598,114 @@ arrowFunction(params) {
2459
2598
  };
2460
2599
  }
2461
2600
 
2601
+
2462
2602
  primary() {
2463
- const t = this.current;
2464
-
2465
- if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2466
- if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2467
- if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2468
- if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2469
- if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
2470
- if (t.type === 'AWAIT') {
2603
+ const t = this.current;
2604
+
2605
+ if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2606
+ if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2607
+ if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2608
+ if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2609
+ if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
2610
+
2611
+ if (t.type === 'AWAIT') {
2471
2612
  this.eat('AWAIT');
2472
2613
  const argument = this.expression();
2473
2614
  return { type: 'AwaitExpression', argument };
2474
2615
  }
2475
- if (t.type === 'NEW') {
2476
- this.eat('NEW');
2477
- const callee = this.primary(); // the class or function being called
2478
- this.eat('LPAREN');
2479
- const args = [];
2480
- if (this.current.type !== 'RPAREN') {
2481
- args.push(this.expression());
2482
- while (this.current.type === 'COMMA') {
2483
- this.eat('COMMA');
2484
- args.push(this.expression());
2485
- }
2486
- }
2487
- this.eat('RPAREN');
2488
- return { type: 'NewExpression', callee, arguments: args };
2489
- }
2490
2616
 
2491
- if (t.type === 'ASK') {
2492
- this.eat('ASK');
2493
- this.eat('LPAREN');
2494
- const args = [];
2495
- if (this.current.type !== 'RPAREN') {
2496
- args.push(this.expression());
2497
- while (this.current.type === 'COMMA') {
2498
- this.eat('COMMA');
2499
- args.push(this.expression());
2500
- }
2617
+ if (t.type === 'NEW') {
2618
+ this.eat('NEW');
2619
+ const callee = this.primary();
2620
+ this.eat('LPAREN');
2621
+ const args = [];
2622
+ if (this.current.type !== 'RPAREN') {
2623
+ args.push(this.expression());
2624
+ while (this.current.type === 'COMMA') {
2625
+ this.eat('COMMA');
2626
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
2501
2627
  }
2502
- this.eat('RPAREN');
2503
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
2504
2628
  }
2629
+ this.eat('RPAREN');
2630
+ return { type: 'NewExpression', callee, arguments: args };
2631
+ }
2505
2632
 
2506
- if (t.type === 'IDENTIFIER') {
2507
- const name = t.value;
2508
- this.eat('IDENTIFIER');
2509
-
2510
- if (this.current.type === 'ARROW') {
2511
- return this.arrowFunction([name]);
2633
+ if (t.type === 'ASK') {
2634
+ this.eat('ASK');
2635
+ this.eat('LPAREN');
2636
+ const args = [];
2637
+ if (this.current.type !== 'RPAREN') {
2638
+ args.push(this.expression());
2639
+ while (this.current.type === 'COMMA') {
2640
+ this.eat('COMMA');
2641
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
2642
+ }
2643
+ }
2644
+ this.eat('RPAREN');
2645
+ return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
2512
2646
  }
2513
2647
 
2514
- return { type: 'Identifier', name };
2515
- }
2648
+ if (t.type === 'IDENTIFIER') {
2649
+ const name = t.value;
2650
+ this.eat('IDENTIFIER');
2516
2651
 
2652
+ if (this.current.type === 'ARROW') {
2653
+ return this.arrowFunction([name]);
2654
+ }
2517
2655
 
2518
- if (t.type === 'LPAREN') {
2519
- this.eat('LPAREN');
2656
+ return { type: 'Identifier', name };
2657
+ }
2520
2658
 
2521
- const params = [];
2659
+ if (t.type === 'LPAREN') {
2660
+ this.eat('LPAREN');
2661
+ const elements = [];
2522
2662
 
2523
- if (this.current.type !== 'RPAREN') {
2524
- params.push(this.current.value);
2525
- this.eat('IDENTIFIER');
2526
- while (this.current.type === 'COMMA') {
2527
- this.eat('COMMA');
2528
- params.push(this.current.value);
2529
- this.eat('IDENTIFIER');
2663
+ if (this.current.type !== 'RPAREN') {
2664
+ elements.push(this.expression());
2665
+ while (this.current.type === 'COMMA') {
2666
+ this.eat('COMMA');
2667
+ if (this.current.type !== 'RPAREN') elements.push(this.expression());
2668
+ }
2530
2669
  }
2531
- }
2532
2670
 
2533
- this.eat('RPAREN');
2671
+ this.eat('RPAREN');
2534
2672
 
2535
- if (this.current.type === 'ARROW') {
2536
- return this.arrowFunction(params);
2537
- }
2673
+ if (this.current.type === 'ARROW') return this.arrowFunction(elements);
2538
2674
 
2539
- if (params.length === 1) {
2540
- return { type: 'Identifier', name: params[0] };
2675
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
2541
2676
  }
2542
2677
 
2543
- throw new Error(
2544
- `Invalid grouped expression at line ${this.current.line}, column ${this.current.column}`
2545
- );
2546
- }
2547
-
2548
-
2549
- if (t.type === 'LBRACKET') {
2550
- this.eat('LBRACKET');
2551
- const elements = [];
2552
- if (this.current.type !== 'RBRACKET') {
2553
- elements.push(this.expression());
2554
- while (this.current.type === 'COMMA') {
2555
- this.eat('COMMA');
2556
- elements.push(this.expression());
2557
- }
2678
+ if (t.type === 'LBRACKET') {
2679
+ this.eat('LBRACKET');
2680
+ const elements = [];
2681
+ if (this.current.type !== 'RBRACKET') {
2682
+ elements.push(this.expression());
2683
+ while (this.current.type === 'COMMA') {
2684
+ this.eat('COMMA');
2685
+ if (this.current.type !== 'RBRACKET') elements.push(this.expression());
2558
2686
  }
2559
- this.eat('RBRACKET');
2560
- return { type: 'ArrayExpression', elements };
2561
2687
  }
2688
+ this.eat('RBRACKET');
2689
+ return { type: 'ArrayExpression', elements };
2690
+ }
2562
2691
 
2563
- if (t.type === 'LBRACE') {
2564
- this.eat('LBRACE');
2565
- const props = [];
2566
- while (this.current.type !== 'RBRACE') {
2567
- let key;
2568
- if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
2569
- else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
2570
- else throw new Error(`Invalid object key at line ${this.current.line}, column ${this.current.column}`);
2571
- this.eat('COLON');
2572
- const value = this.expression();
2573
- props.push({ key, value });
2574
- if (this.current.type === 'COMMA') this.eat('COMMA');
2575
- }
2576
- this.eat('RBRACE');
2577
- return { type: 'ObjectExpression', props };
2692
+ if (t.type === 'LBRACE') {
2693
+ this.eat('LBRACE');
2694
+ const props = [];
2695
+ while (this.current.type !== 'RBRACE') {
2696
+ const key = this.expression(); // Flexible key: can be any expression
2697
+ this.eat('COLON');
2698
+ const value = this.expression();
2699
+ props.push({ key, value });
2700
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
2578
2701
  }
2579
-
2580
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
2702
+ this.eat('RBRACE');
2703
+ return { type: 'ObjectExpression', props };
2581
2704
  }
2705
+
2706
+ throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
2707
+ }
2708
+
2582
2709
  }
2583
2710
 
2584
2711
  module.exports = Parser;