starlight-cli 1.0.33 → 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.
Files changed (3) hide show
  1. package/dist/index.js +402 -339
  2. package/package.json +1 -1
  3. package/src/parser.js +403 -340
package/dist/index.js CHANGED
@@ -2113,164 +2113,212 @@ class Parser {
2113
2113
  }
2114
2114
 
2115
2115
  varDeclaration() {
2116
- this.eat('LET');
2117
- const id = this.current.value;
2118
- 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') {
2119
2122
  this.eat('EQUAL');
2120
- const expr = this.expression();
2121
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2122
- return { type: 'VarDeclaration', id, expr };
2123
+ expr = this.expression();
2123
2124
  }
2124
2125
 
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
- }
2126
+ // semicolon optional
2127
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2131
2128
 
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 };
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();
2143
2148
  }
2149
+
2150
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2151
+
2152
+ return { type: 'DefineStatement', id, expr };
2153
+ }
2154
+
2144
2155
  asyncFuncDeclaration() {
2145
2156
  const name = this.current.value;
2146
2157
  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');
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') {
2154
2175
  params.push(this.current.value);
2155
2176
  this.eat('IDENTIFIER');
2156
2177
  }
2157
2178
  }
2158
- this.eat('RPAREN');
2179
+
2159
2180
  const body = this.block();
2160
2181
  return { type: 'FunctionDeclaration', name, params, body, async: true };
2161
2182
  }
2162
2183
 
2163
- ifStatement() {
2164
- this.eat('IF');
2184
+ ifStatement() {
2185
+ this.eat('IF');
2186
+
2187
+ let test;
2188
+ if (this.current.type === 'LPAREN') {
2165
2189
  this.eat('LPAREN');
2166
- const test = this.expression();
2190
+ test = this.expression();
2167
2191
  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 };
2192
+ } else {
2193
+ // python style: no parentheses
2194
+ test = this.expression();
2176
2195
  }
2177
2196
 
2178
- whileStatement() {
2179
- this.eat('WHILE');
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();
2204
+ }
2205
+
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') {
2180
2214
  this.eat('LPAREN');
2181
- const test = this.expression();
2215
+ test = this.expression();
2182
2216
  this.eat('RPAREN');
2183
- const body = this.block();
2184
- return { type: 'WhileStatement', test, body };
2217
+ } else {
2218
+ test = this.expression();
2185
2219
  }
2186
- importStatement() {
2187
- this.eat('IMPORT');
2188
2220
 
2189
- 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');
2190
2240
 
2191
- if (this.current.type === 'STAR') {
2192
- this.eat('STAR');
2241
+ let localName = importedName;
2242
+ if (this.current.type === 'AS') {
2193
2243
  this.eat('AS');
2194
- const name = this.current.value;
2244
+ localName = this.current.value;
2195
2245
  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;
2216
- 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}`);
2246
+ }
2247
+
2248
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2249
+ if (this.current.type === 'COMMA') this.eat('COMMA');
2220
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
+ }
2221
2259
 
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');
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');
2226
2264
 
2227
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2265
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2228
2266
 
2229
- return { type: 'ImportStatement', path: pathToken.value, specifiers };
2267
+ return { type: 'ImportStatement', path: pathToken.value, specifiers };
2230
2268
  }
2231
2269
 
2270
+ forStatement() {
2271
+ this.eat('FOR');
2272
+
2273
+ let init = null;
2274
+ let test = null;
2275
+ let update = null;
2232
2276
 
2233
- forStatement() {
2234
- this.eat('FOR');
2277
+ if (this.current.type === 'LPAREN') {
2235
2278
  this.eat('LPAREN');
2236
2279
 
2237
- let init = null;
2238
- if (this.current.type !== 'SEMICOLON') {
2239
- init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
2240
- } else {
2241
- this.eat('SEMICOLON');
2242
- }
2280
+ if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
2281
+ else this.eat('SEMICOLON');
2243
2282
 
2244
- let test = null;
2245
2283
  if (this.current.type !== 'SEMICOLON') test = this.expression();
2246
2284
  this.eat('SEMICOLON');
2247
2285
 
2248
- let update = null;
2249
2286
  if (this.current.type !== 'RPAREN') update = this.expression();
2250
2287
  this.eat('RPAREN');
2251
-
2252
- const body = this.block();
2253
- 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
+ }
2254
2294
  }
2255
2295
 
2256
- breakStatement() {
2257
- this.eat('BREAK');
2258
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2259
- return { type: 'BreakStatement' };
2260
- }
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
+
2261
2306
 
2262
2307
  continueStatement() {
2263
- this.eat('CONTINUE');
2264
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2265
- return { type: 'ContinueStatement' };
2266
- }
2308
+ this.eat('CONTINUE');
2309
+ // semicolon optional
2310
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2311
+ return { type: 'ContinueStatement' };
2312
+ }
2267
2313
 
2268
- funcDeclaration() {
2269
- this.eat('FUNC');
2270
- const name = this.current.value;
2271
- 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') {
2272
2321
  this.eat('LPAREN');
2273
- const params = [];
2274
2322
  if (this.current.type !== 'RPAREN') {
2275
2323
  params.push(this.current.value);
2276
2324
  this.eat('IDENTIFIER');
@@ -2281,176 +2329,203 @@ asyncFuncDeclaration() {
2281
2329
  }
2282
2330
  }
2283
2331
  this.eat('RPAREN');
2284
- const body = this.block();
2285
- 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
+ }
2286
2338
  }
2287
2339
 
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 };
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();
2294
2350
  }
2295
2351
 
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 };
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());
2304
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
+ }
2378
+
2379
+ assignment() {
2380
+ const node = this.logicalOr();
2381
+ const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
2305
2382
 
2306
- expressionStatement() {
2307
- const expr = this.expression();
2308
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2309
- return { type: 'ExpressionStatement', expression: expr };
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 };
2310
2388
  }
2311
2389
 
2312
- expression() {
2313
- 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 };
2314
2394
  }
2315
2395
 
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
- }
2396
+ return node;
2397
+ }
2325
2398
 
2326
- if (this.current.type === 'EQUAL') {
2327
- this.eat('EQUAL');
2328
- const right = this.assignment();
2329
- return { type: 'AssignmentExpression', left: node, right };
2330
- }
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
+ }
2331
2408
 
2332
- 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() };
2333
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
+ }
2334
2427
 
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;
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() };
2343
2434
  }
2435
+ return node;
2436
+ }
2344
2437
 
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;
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() };
2353
2444
  }
2445
+ return node;
2446
+ }
2354
2447
 
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;
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() };
2363
2454
  }
2455
+ return node;
2456
+ }
2364
2457
 
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;
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() };
2373
2463
  }
2374
2464
 
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;
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 };
2383
2471
  }
2384
2472
 
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() };
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;
2391
2485
  }
2392
- return node;
2393
- }
2394
2486
 
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() };
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;
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;
2400
2505
  }
2506
+
2401
2507
  if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2402
2508
  const op = this.current.type;
2403
2509
  this.eat(op);
2404
- const argument = this.unary();
2405
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
2510
+ node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
2511
+ continue;
2406
2512
  }
2407
- return this.postfix();
2408
- }
2409
2513
 
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;
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;
2449
2520
  }
2450
- return node;
2521
+
2522
+ break;
2451
2523
  }
2524
+ return node;
2525
+ }
2526
+
2452
2527
  arrowFunction(params) {
2453
- this.eat('ARROW');
2528
+ if (this.current.type === 'ARROW') this.eat('ARROW');
2454
2529
  const body = this.expression();
2455
2530
  return {
2456
2531
  type: 'ArrowFunctionExpression',
@@ -2459,126 +2534,114 @@ arrowFunction(params) {
2459
2534
  };
2460
2535
  }
2461
2536
 
2537
+
2462
2538
  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') {
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') {
2471
2548
  this.eat('AWAIT');
2472
2549
  const argument = this.expression();
2473
2550
  return { type: 'AwaitExpression', argument };
2474
2551
  }
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
2552
 
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
- }
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());
2501
2563
  }
2502
- this.eat('RPAREN');
2503
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
2504
2564
  }
2565
+ this.eat('RPAREN');
2566
+ return { type: 'NewExpression', callee, arguments: args };
2567
+ }
2505
2568
 
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]);
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 };
2512
2582
  }
2513
2583
 
2514
- return { type: 'Identifier', name };
2515
- }
2584
+ if (t.type === 'IDENTIFIER') {
2585
+ const name = t.value;
2586
+ this.eat('IDENTIFIER');
2516
2587
 
2588
+ if (this.current.type === 'ARROW') {
2589
+ return this.arrowFunction([name]);
2590
+ }
2517
2591
 
2518
- if (t.type === 'LPAREN') {
2519
- this.eat('LPAREN');
2592
+ return { type: 'Identifier', name };
2593
+ }
2520
2594
 
2521
- const params = [];
2595
+ if (t.type === 'LPAREN') {
2596
+ this.eat('LPAREN');
2597
+ const elements = [];
2522
2598
 
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');
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
+ }
2530
2605
  }
2531
- }
2532
2606
 
2533
- this.eat('RPAREN');
2607
+ this.eat('RPAREN');
2534
2608
 
2535
- if (this.current.type === 'ARROW') {
2536
- return this.arrowFunction(params);
2537
- }
2609
+ if (this.current.type === 'ARROW') return this.arrowFunction(elements);
2538
2610
 
2539
- if (params.length === 1) {
2540
- return { type: 'Identifier', name: params[0] };
2611
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
2541
2612
  }
2542
2613
 
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
- }
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());
2558
2622
  }
2559
- this.eat('RBRACKET');
2560
- return { type: 'ArrayExpression', elements };
2561
2623
  }
2624
+ this.eat('RBRACKET');
2625
+ return { type: 'ArrayExpression', elements };
2626
+ }
2562
2627
 
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 };
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
2578
2637
  }
2579
-
2580
- 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 };
2581
2640
  }
2641
+
2642
+ throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
2643
+ }
2644
+
2582
2645
  }
2583
2646
 
2584
2647
  module.exports = Parser;