starlight-cli 1.0.32 → 1.0.34

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