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/src/parser.js CHANGED
@@ -55,164 +55,231 @@ class Parser {
55
55
  }
56
56
 
57
57
  varDeclaration() {
58
- this.eat('LET');
59
- const id = this.current.value;
60
- this.eat('IDENTIFIER');
58
+ this.eat('LET');
59
+ const id = this.current.value;
60
+ this.eat('IDENTIFIER');
61
+
62
+ let expr = null;
63
+ if (this.current.type === 'EQUAL') {
61
64
  this.eat('EQUAL');
62
- const expr = this.expression();
63
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
64
- return { type: 'VarDeclaration', id, expr };
65
+ expr = this.expression();
65
66
  }
66
67
 
67
- sldeployStatement() {
68
- this.eat('SLDEPLOY');
69
- const expr = this.expression();
70
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
71
- return { type: 'SldeployStatement', expr };
72
- }
68
+ // semicolon optional
69
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
73
70
 
74
- defineStatement() {
75
- this.eat('DEFINE');
76
- const id = this.current.value;
77
- this.eat('IDENTIFIER');
78
- let expr = null;
79
- if (this.current.type === 'EQUAL') {
80
- this.eat('EQUAL');
81
- expr = this.expression();
82
- }
83
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
84
- return { type: 'DefineStatement', id, expr };
71
+ return { type: 'VarDeclaration', id, expr };
72
+ }
73
+
74
+ sldeployStatement() {
75
+ this.eat('SLDEPLOY');
76
+ const expr = this.expression();
77
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
78
+ return { type: 'SldeployStatement', expr };
79
+ }
80
+
81
+ defineStatement() {
82
+ this.eat('DEFINE');
83
+ const id = this.current.value;
84
+ this.eat('IDENTIFIER');
85
+
86
+ let expr = null;
87
+ if (this.current.type === 'EQUAL') {
88
+ this.eat('EQUAL');
89
+ expr = this.expression();
85
90
  }
91
+
92
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
93
+
94
+ return { type: 'DefineStatement', id, expr };
95
+ }
96
+
86
97
  asyncFuncDeclaration() {
87
98
  const name = this.current.value;
88
99
  this.eat('IDENTIFIER');
89
- this.eat('LPAREN');
90
- const params = [];
91
- if (this.current.type !== 'RPAREN') {
92
- params.push(this.current.value);
93
- this.eat('IDENTIFIER');
94
- while (this.current.type === 'COMMA') {
95
- this.eat('COMMA');
100
+
101
+ let params = [];
102
+ if (this.current.type === 'LPAREN') {
103
+ this.eat('LPAREN');
104
+ if (this.current.type !== 'RPAREN') {
105
+ params.push(this.current.value);
106
+ this.eat('IDENTIFIER');
107
+ while (this.current.type === 'COMMA') {
108
+ this.eat('COMMA');
109
+ params.push(this.current.value);
110
+ this.eat('IDENTIFIER');
111
+ }
112
+ }
113
+ this.eat('RPAREN');
114
+ } else {
115
+ // no parentheses: single param as Python style
116
+ if (this.current.type === 'IDENTIFIER') {
96
117
  params.push(this.current.value);
97
118
  this.eat('IDENTIFIER');
98
119
  }
99
120
  }
100
- this.eat('RPAREN');
121
+
101
122
  const body = this.block();
102
123
  return { type: 'FunctionDeclaration', name, params, body, async: true };
103
124
  }
104
125
 
105
- ifStatement() {
106
- this.eat('IF');
126
+ ifStatement() {
127
+ this.eat('IF');
128
+
129
+ let test;
130
+ if (this.current.type === 'LPAREN') {
107
131
  this.eat('LPAREN');
108
- const test = this.expression();
132
+ test = this.expression();
109
133
  this.eat('RPAREN');
110
- const consequent = this.block();
111
- let alternate = null;
112
- if (this.current.type === 'ELSE') {
113
- this.eat('ELSE');
114
- if (this.current.type === 'IF') alternate = this.ifStatement();
115
- else alternate = this.block();
116
- }
117
- return { type: 'IfStatement', test, consequent, alternate };
134
+ } else {
135
+ // python style: no parentheses
136
+ test = this.expression();
118
137
  }
119
138
 
120
- whileStatement() {
121
- this.eat('WHILE');
139
+ const consequent = this.block();
140
+
141
+ let alternate = null;
142
+ if (this.current.type === 'ELSE') {
143
+ this.eat('ELSE');
144
+ if (this.current.type === 'IF') alternate = this.ifStatement();
145
+ else alternate = this.block();
146
+ }
147
+
148
+ return { type: 'IfStatement', test, consequent, alternate };
149
+ }
150
+
151
+ whileStatement() {
152
+ this.eat('WHILE');
153
+
154
+ let test;
155
+ if (this.current.type === 'LPAREN') {
122
156
  this.eat('LPAREN');
123
- const test = this.expression();
157
+ test = this.expression();
124
158
  this.eat('RPAREN');
125
- const body = this.block();
126
- return { type: 'WhileStatement', test, body };
159
+ } else {
160
+ test = this.expression();
127
161
  }
128
- importStatement() {
129
- this.eat('IMPORT');
130
162
 
131
- let specifiers = [];
163
+ const body = this.block();
164
+ return { type: 'WhileStatement', test, body };
165
+ }
166
+
167
+ importStatement() {
168
+ this.eat('IMPORT');
132
169
 
133
- if (this.current.type === 'STAR') {
134
- this.eat('STAR');
170
+ let specifiers = [];
171
+ if (this.current.type === 'STAR') {
172
+ this.eat('STAR');
173
+ this.eat('AS');
174
+ const name = this.current.value;
175
+ this.eat('IDENTIFIER');
176
+ specifiers.push({ type: 'NamespaceImport', local: name });
177
+ } else if (this.current.type === 'LBRACE') {
178
+ this.eat('LBRACE');
179
+ while (this.current.type !== 'RBRACE') {
180
+ const importedName = this.current.value;
181
+ this.eat('IDENTIFIER');
182
+
183
+ let localName = importedName;
184
+ if (this.current.type === 'AS') {
135
185
  this.eat('AS');
136
- const name = this.current.value;
137
- this.eat('IDENTIFIER');
138
- specifiers.push({ type: 'NamespaceImport', local: name });
139
- }
140
- else if (this.current.type === 'LBRACE') {
141
- this.eat('LBRACE');
142
- while (this.current.type !== 'RBRACE') {
143
- const importedName = this.current.value;
144
- this.eat('IDENTIFIER');
145
- let localName = importedName;
146
- if (this.current.type === 'AS') {
147
- this.eat('AS');
148
- localName = this.current.value;
149
- this.eat('IDENTIFIER');
150
- }
151
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
152
- if (this.current.type === 'COMMA') this.eat('COMMA');
153
- }
154
- this.eat('RBRACE');
155
- }
156
- else if (this.current.type === 'IDENTIFIER') {
157
- const localName = this.current.value;
186
+ localName = this.current.value;
158
187
  this.eat('IDENTIFIER');
159
- specifiers.push({ type: 'DefaultImport', local: localName });
160
- } else {
161
- throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
188
+ }
189
+
190
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
191
+ if (this.current.type === 'COMMA') this.eat('COMMA');
162
192
  }
193
+ this.eat('RBRACE');
194
+ } else if (this.current.type === 'IDENTIFIER') {
195
+ const localName = this.current.value;
196
+ this.eat('IDENTIFIER');
197
+ specifiers.push({ type: 'DefaultImport', local: localName });
198
+ } else {
199
+ throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
200
+ }
163
201
 
164
- this.eat('FROM');
165
- const pathToken = this.current;
166
- if (pathToken.type !== 'STRING') throw new Error(`Expected string literal after from in import at line ${this.current.line}, column ${this.current.column}`);
167
- this.eat('STRING');
202
+ this.eat('FROM');
203
+ const pathToken = this.current;
204
+ if (pathToken.type !== 'STRING') throw new Error(`Expected string after FROM at line ${this.current.line}, column ${this.current.column}`);
205
+ this.eat('STRING');
168
206
 
169
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
207
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
170
208
 
171
- return { type: 'ImportStatement', path: pathToken.value, specifiers };
209
+ return { type: 'ImportStatement', path: pathToken.value, specifiers };
172
210
  }
211
+ forStatement() {
212
+ this.eat('FOR');
173
213
 
214
+ // --- Python-style: for variable in iterable ---
215
+ if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
216
+ const variable = this.current.value; // loop variable
217
+ this.eat('IDENTIFIER');
174
218
 
175
- forStatement() {
176
- this.eat('FOR');
219
+ this.eat('IN');
220
+ const iterable = this.expression(); // the array/object to loop over
221
+
222
+ const body = this.block();
223
+ return { type: 'ForInStatement', variable, iterable, body };
224
+ }
225
+
226
+ // --- C-style: for(init; test; update) ---
227
+ let init = null;
228
+ let test = null;
229
+ let update = null;
230
+
231
+ if (this.current.type === 'LPAREN') {
177
232
  this.eat('LPAREN');
178
233
 
179
- let init = null;
234
+ // init
180
235
  if (this.current.type !== 'SEMICOLON') {
181
236
  init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
182
237
  } else {
183
238
  this.eat('SEMICOLON');
184
239
  }
185
240
 
186
- let test = null;
241
+ // test
187
242
  if (this.current.type !== 'SEMICOLON') test = this.expression();
188
243
  this.eat('SEMICOLON');
189
244
 
190
- let update = null;
245
+ // update
191
246
  if (this.current.type !== 'RPAREN') update = this.expression();
192
247
  this.eat('RPAREN');
193
-
194
- const body = this.block();
195
- return { type: 'ForStatement', init, test, update, body };
248
+ } else {
249
+ // fallback: single expression (could be used for Python-style with "in", already handled above)
250
+ init = this.expression();
251
+ if (this.current.type === 'IN') {
252
+ this.eat('IN');
253
+ test = this.expression(); // iterable
254
+ }
196
255
  }
197
256
 
198
- breakStatement() {
199
- this.eat('BREAK');
200
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
201
- return { type: 'BreakStatement' };
202
- }
257
+ const body = this.block();
258
+ return { type: 'ForStatement', init, test, update, body };
259
+ }
203
260
 
204
- continueStatement() {
205
- this.eat('CONTINUE');
206
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
207
- return { type: 'ContinueStatement' };
208
- }
261
+ breakStatement() {
262
+ this.eat('BREAK');
263
+ // Python-style: no semicolon needed, ignore if present
264
+ if (this.current.type === 'SEMICOLON') this.advance();
265
+ return { type: 'BreakStatement' };
266
+ }
209
267
 
210
- funcDeclaration() {
211
- this.eat('FUNC');
212
- const name = this.current.value;
213
- this.eat('IDENTIFIER');
268
+ continueStatement() {
269
+ this.eat('CONTINUE');
270
+ // Python-style: no semicolon needed, ignore if present
271
+ if (this.current.type === 'SEMICOLON') this.advance();
272
+ return { type: 'ContinueStatement' };
273
+ }
274
+
275
+ funcDeclaration() {
276
+ this.eat('FUNC');
277
+ const name = this.current.value;
278
+ this.eat('IDENTIFIER');
279
+
280
+ let params = [];
281
+ if (this.current.type === 'LPAREN') {
214
282
  this.eat('LPAREN');
215
- const params = [];
216
283
  if (this.current.type !== 'RPAREN') {
217
284
  params.push(this.current.value);
218
285
  this.eat('IDENTIFIER');
@@ -223,176 +290,203 @@ asyncFuncDeclaration() {
223
290
  }
224
291
  }
225
292
  this.eat('RPAREN');
226
- const body = this.block();
227
- return { type: 'FunctionDeclaration', name, params, body };
293
+ } else {
294
+ // Python-style: single param without parentheses
295
+ if (this.current.type === 'IDENTIFIER') {
296
+ params.push(this.current.value);
297
+ this.eat('IDENTIFIER');
298
+ }
228
299
  }
229
300
 
230
- returnStatement() {
231
- this.eat('RETURN');
232
- let argument = null;
233
- if (this.current.type !== 'SEMICOLON') argument = this.expression();
234
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
235
- return { type: 'ReturnStatement', argument };
301
+ const body = this.block();
302
+ return { type: 'FunctionDeclaration', name, params, body };
303
+ }
304
+
305
+ returnStatement() {
306
+ this.eat('RETURN');
307
+
308
+ let argument = null;
309
+ if (this.current.type !== 'SEMICOLON') {
310
+ argument = this.expression();
236
311
  }
237
312
 
238
- block() {
239
- this.eat('LBRACE');
240
- const body = [];
241
- while (this.current.type !== 'RBRACE') {
242
- body.push(this.statement());
243
- }
244
- this.eat('RBRACE');
245
- return { type: 'BlockStatement', body };
313
+ // semicolon optional
314
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
315
+
316
+ return { type: 'ReturnStatement', argument };
317
+ }
318
+
319
+ block() {
320
+ this.eat('LBRACE');
321
+ const body = [];
322
+ while (this.current.type !== 'RBRACE') {
323
+ body.push(this.statement());
246
324
  }
325
+ this.eat('RBRACE');
326
+ return { type: 'BlockStatement', body };
327
+ }
247
328
 
248
- expressionStatement() {
249
- const expr = this.expression();
250
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
251
- return { type: 'ExpressionStatement', expression: expr };
329
+ expressionStatement() {
330
+ const expr = this.expression();
331
+ // semicolon optional
332
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
333
+ return { type: 'ExpressionStatement', expression: expr };
334
+ }
335
+
336
+ expression() {
337
+ return this.assignment();
338
+ }
339
+
340
+ assignment() {
341
+ const node = this.logicalOr();
342
+ const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
343
+
344
+ if (compoundOps.includes(this.current.type)) {
345
+ const op = this.current.type;
346
+ this.eat(op);
347
+ const right = this.assignment();
348
+ return { type: 'CompoundAssignment', operator: op, left: node, right };
252
349
  }
253
350
 
254
- expression() {
255
- return this.assignment();
351
+ if (this.current.type === 'EQUAL') {
352
+ this.eat('EQUAL');
353
+ const right = this.assignment();
354
+ return { type: 'AssignmentExpression', left: node, right };
256
355
  }
257
356
 
258
- assignment() {
259
- const node = this.logicalOr();
260
- const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
261
- if (compoundOps.includes(this.current.type)) {
262
- const op = this.current.type;
263
- this.eat(op);
264
- const right = this.assignment();
265
- return { type: 'CompoundAssignment', operator: op, left: node, right };
266
- }
357
+ return node;
358
+ }
267
359
 
268
- if (this.current.type === 'EQUAL') {
269
- this.eat('EQUAL');
270
- const right = this.assignment();
271
- return { type: 'AssignmentExpression', left: node, right };
272
- }
360
+ logicalOr() {
361
+ let node = this.logicalAnd();
362
+ while (this.current.type === 'OR') {
363
+ const op = this.current.type;
364
+ this.eat(op);
365
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
366
+ }
367
+ return node;
368
+ }
273
369
 
274
- return node;
370
+ logicalAnd() {
371
+ let node = this.equality();
372
+ while (this.current.type === 'AND') {
373
+ const op = this.current.type;
374
+ this.eat(op);
375
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
275
376
  }
377
+ return node;
378
+ }
379
+ equality() {
380
+ let node = this.comparison();
381
+ while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
382
+ const op = this.current.type;
383
+ this.eat(op);
384
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
385
+ }
386
+ return node;
387
+ }
276
388
 
277
- logicalOr() {
278
- let node = this.logicalAnd();
279
- while (this.current.type === 'OR') {
280
- const op = this.current.type;
281
- this.eat(op);
282
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
283
- }
284
- return node;
389
+ comparison() {
390
+ let node = this.term();
391
+ while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
392
+ const op = this.current.type;
393
+ this.eat(op);
394
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
285
395
  }
396
+ return node;
397
+ }
286
398
 
287
- logicalAnd() {
288
- let node = this.equality();
289
- while (this.current.type === 'AND') {
290
- const op = this.current.type;
291
- this.eat(op);
292
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
293
- }
294
- return node;
399
+ term() {
400
+ let node = this.factor();
401
+ while (['PLUS', 'MINUS'].includes(this.current.type)) {
402
+ const op = this.current.type;
403
+ this.eat(op);
404
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
295
405
  }
406
+ return node;
407
+ }
296
408
 
297
- equality() {
298
- let node = this.comparison();
299
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
300
- const op = this.current.type;
301
- this.eat(op);
302
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
303
- }
304
- return node;
409
+ factor() {
410
+ let node = this.unary();
411
+ while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
412
+ const op = this.current.type;
413
+ this.eat(op);
414
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
305
415
  }
416
+ return node;
417
+ }
306
418
 
307
- comparison() {
308
- let node = this.term();
309
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
310
- const op = this.current.type;
311
- this.eat(op);
312
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
313
- }
314
- return node;
419
+ unary() {
420
+ if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
421
+ const op = this.current.type;
422
+ this.eat(op);
423
+ return { type: 'UnaryExpression', operator: op, argument: this.unary() };
315
424
  }
316
425
 
317
- term() {
318
- let node = this.factor();
319
- while (['PLUS', 'MINUS'].includes(this.current.type)) {
320
- const op = this.current.type;
321
- this.eat(op);
322
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
323
- }
324
- return node;
426
+ // Python-like: ignore ++ and -- if not used
427
+ if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
428
+ const op = this.current.type;
429
+ this.eat(op);
430
+ const argument = this.unary();
431
+ return { type: 'UpdateExpression', operator: op, argument, prefix: true };
325
432
  }
326
433
 
327
- factor() {
328
- let node = this.unary();
329
- while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
330
- const op = this.current.type;
331
- this.eat(op);
332
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
434
+ return this.postfix();
435
+ }
436
+
437
+ postfix() {
438
+ let node = this.primary();
439
+ while (true) {
440
+ if (this.current.type === 'LBRACKET') {
441
+ this.eat('LBRACKET');
442
+ const index = this.expression();
443
+ if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
444
+ node = { type: 'IndexExpression', object: node, indexer: index };
445
+ continue;
333
446
  }
334
- return node;
335
- }
336
447
 
337
- unary() {
338
- if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
339
- const op = this.current.type;
340
- this.eat(op);
341
- return { type: 'UnaryExpression', operator: op, argument: this.unary() };
448
+ if (this.current.type === 'LPAREN') {
449
+ this.eat('LPAREN');
450
+ const args = [];
451
+ while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
452
+ args.push(this.expression());
453
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
454
+ }
455
+ if (this.current.type === 'RPAREN') this.eat('RPAREN');
456
+ node = { type: 'CallExpression', callee: node, arguments: args };
457
+ continue;
342
458
  }
459
+
460
+ if (this.current.type === 'DOT') {
461
+ this.eat('DOT');
462
+ const property = this.current.value || '';
463
+ if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
464
+ node = { type: 'MemberExpression', object: node, property };
465
+ continue;
466
+ }
467
+
343
468
  if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
344
469
  const op = this.current.type;
345
470
  this.eat(op);
346
- const argument = this.unary();
347
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
471
+ node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
472
+ continue;
348
473
  }
349
- return this.postfix();
350
- }
351
-
352
- postfix() {
353
- let node = this.primary();
354
- while (true) {
355
- if (this.current.type === 'LBRACKET') {
356
- this.eat('LBRACKET');
357
- const index = this.expression();
358
- this.eat('RBRACKET');
359
- node = { type: 'IndexExpression', object: node, indexer: index };
360
- continue;
361
- }
362
- if (this.current.type === 'LPAREN') {
363
- this.eat('LPAREN');
364
- const args = [];
365
- if (this.current.type !== 'RPAREN') {
366
- args.push(this.expression());
367
- while (this.current.type === 'COMMA') {
368
- this.eat('COMMA');
369
- args.push(this.expression());
370
- }
371
- }
372
- this.eat('RPAREN');
373
- node = { type: 'CallExpression', callee: node, arguments: args };
374
- continue;
375
- }
376
- if (this.current.type === 'DOT') {
377
- this.eat('DOT');
378
- if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
379
- const property = this.current.value;
380
- this.eat('IDENTIFIER');
381
- node = { type: 'MemberExpression', object: node, property };
382
- continue;
383
- }
384
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
385
- const op = this.current.type;
386
- this.eat(op);
387
- node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
388
- continue;
389
- }
390
- break;
474
+
475
+ // Python-style: implicit function calls (if identifier followed by identifier)
476
+ if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
477
+ const argNode = { type: 'Identifier', name: this.current.value };
478
+ this.eat('IDENTIFIER');
479
+ node = { type: 'CallExpression', callee: node, arguments: [argNode] };
480
+ continue;
391
481
  }
392
- return node;
482
+
483
+ break;
393
484
  }
485
+ return node;
486
+ }
487
+
394
488
  arrowFunction(params) {
395
- this.eat('ARROW');
489
+ if (this.current.type === 'ARROW') this.eat('ARROW');
396
490
  const body = this.expression();
397
491
  return {
398
492
  type: 'ArrowFunctionExpression',
@@ -401,126 +495,114 @@ arrowFunction(params) {
401
495
  };
402
496
  }
403
497
 
498
+
404
499
  primary() {
405
- const t = this.current;
406
-
407
- if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
408
- if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
409
- if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
410
- if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
411
- if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
412
- if (t.type === 'AWAIT') {
500
+ const t = this.current;
501
+
502
+ if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
503
+ if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
504
+ if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
505
+ if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
506
+ if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
507
+
508
+ if (t.type === 'AWAIT') {
413
509
  this.eat('AWAIT');
414
510
  const argument = this.expression();
415
511
  return { type: 'AwaitExpression', argument };
416
512
  }
417
- if (t.type === 'NEW') {
418
- this.eat('NEW');
419
- const callee = this.primary(); // the class or function being called
420
- this.eat('LPAREN');
421
- const args = [];
422
- if (this.current.type !== 'RPAREN') {
423
- args.push(this.expression());
424
- while (this.current.type === 'COMMA') {
425
- this.eat('COMMA');
426
- args.push(this.expression());
427
- }
428
- }
429
- this.eat('RPAREN');
430
- return { type: 'NewExpression', callee, arguments: args };
431
- }
432
513
 
433
- if (t.type === 'ASK') {
434
- this.eat('ASK');
435
- this.eat('LPAREN');
436
- const args = [];
437
- if (this.current.type !== 'RPAREN') {
438
- args.push(this.expression());
439
- while (this.current.type === 'COMMA') {
440
- this.eat('COMMA');
441
- args.push(this.expression());
442
- }
514
+ if (t.type === 'NEW') {
515
+ this.eat('NEW');
516
+ const callee = this.primary();
517
+ this.eat('LPAREN');
518
+ const args = [];
519
+ if (this.current.type !== 'RPAREN') {
520
+ args.push(this.expression());
521
+ while (this.current.type === 'COMMA') {
522
+ this.eat('COMMA');
523
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
443
524
  }
444
- this.eat('RPAREN');
445
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
446
525
  }
526
+ this.eat('RPAREN');
527
+ return { type: 'NewExpression', callee, arguments: args };
528
+ }
447
529
 
448
- if (t.type === 'IDENTIFIER') {
449
- const name = t.value;
450
- this.eat('IDENTIFIER');
451
-
452
- if (this.current.type === 'ARROW') {
453
- return this.arrowFunction([name]);
530
+ if (t.type === 'ASK') {
531
+ this.eat('ASK');
532
+ this.eat('LPAREN');
533
+ const args = [];
534
+ if (this.current.type !== 'RPAREN') {
535
+ args.push(this.expression());
536
+ while (this.current.type === 'COMMA') {
537
+ this.eat('COMMA');
538
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
539
+ }
540
+ }
541
+ this.eat('RPAREN');
542
+ return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
454
543
  }
455
544
 
456
- return { type: 'Identifier', name };
457
- }
545
+ if (t.type === 'IDENTIFIER') {
546
+ const name = t.value;
547
+ this.eat('IDENTIFIER');
458
548
 
549
+ if (this.current.type === 'ARROW') {
550
+ return this.arrowFunction([name]);
551
+ }
459
552
 
460
- if (t.type === 'LPAREN') {
461
- this.eat('LPAREN');
553
+ return { type: 'Identifier', name };
554
+ }
462
555
 
463
- const params = [];
556
+ if (t.type === 'LPAREN') {
557
+ this.eat('LPAREN');
558
+ const elements = [];
464
559
 
465
- if (this.current.type !== 'RPAREN') {
466
- params.push(this.current.value);
467
- this.eat('IDENTIFIER');
468
- while (this.current.type === 'COMMA') {
469
- this.eat('COMMA');
470
- params.push(this.current.value);
471
- this.eat('IDENTIFIER');
560
+ if (this.current.type !== 'RPAREN') {
561
+ elements.push(this.expression());
562
+ while (this.current.type === 'COMMA') {
563
+ this.eat('COMMA');
564
+ if (this.current.type !== 'RPAREN') elements.push(this.expression());
565
+ }
472
566
  }
473
- }
474
567
 
475
- this.eat('RPAREN');
568
+ this.eat('RPAREN');
476
569
 
477
- if (this.current.type === 'ARROW') {
478
- return this.arrowFunction(params);
479
- }
570
+ if (this.current.type === 'ARROW') return this.arrowFunction(elements);
480
571
 
481
- if (params.length === 1) {
482
- return { type: 'Identifier', name: params[0] };
572
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
483
573
  }
484
574
 
485
- throw new Error(
486
- `Invalid grouped expression at line ${this.current.line}, column ${this.current.column}`
487
- );
488
- }
489
-
490
-
491
- if (t.type === 'LBRACKET') {
492
- this.eat('LBRACKET');
493
- const elements = [];
494
- if (this.current.type !== 'RBRACKET') {
495
- elements.push(this.expression());
496
- while (this.current.type === 'COMMA') {
497
- this.eat('COMMA');
498
- elements.push(this.expression());
499
- }
575
+ if (t.type === 'LBRACKET') {
576
+ this.eat('LBRACKET');
577
+ const elements = [];
578
+ if (this.current.type !== 'RBRACKET') {
579
+ elements.push(this.expression());
580
+ while (this.current.type === 'COMMA') {
581
+ this.eat('COMMA');
582
+ if (this.current.type !== 'RBRACKET') elements.push(this.expression());
500
583
  }
501
- this.eat('RBRACKET');
502
- return { type: 'ArrayExpression', elements };
503
584
  }
585
+ this.eat('RBRACKET');
586
+ return { type: 'ArrayExpression', elements };
587
+ }
504
588
 
505
- if (t.type === 'LBRACE') {
506
- this.eat('LBRACE');
507
- const props = [];
508
- while (this.current.type !== 'RBRACE') {
509
- let key;
510
- if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
511
- else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
512
- else throw new Error(`Invalid object key at line ${this.current.line}, column ${this.current.column}`);
513
- this.eat('COLON');
514
- const value = this.expression();
515
- props.push({ key, value });
516
- if (this.current.type === 'COMMA') this.eat('COMMA');
517
- }
518
- this.eat('RBRACE');
519
- return { type: 'ObjectExpression', props };
589
+ if (t.type === 'LBRACE') {
590
+ this.eat('LBRACE');
591
+ const props = [];
592
+ while (this.current.type !== 'RBRACE') {
593
+ const key = this.expression(); // Flexible key: can be any expression
594
+ this.eat('COLON');
595
+ const value = this.expression();
596
+ props.push({ key, value });
597
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
520
598
  }
521
-
522
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
599
+ this.eat('RBRACE');
600
+ return { type: 'ObjectExpression', props };
523
601
  }
602
+
603
+ throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
604
+ }
605
+
524
606
  }
525
607
 
526
608
  module.exports = Parser;