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/src/parser.js CHANGED
@@ -55,164 +55,212 @@ 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');
169
+
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');
132
182
 
133
- if (this.current.type === 'STAR') {
134
- this.eat('STAR');
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
  }
173
211
 
212
+ forStatement() {
213
+ this.eat('FOR');
214
+
215
+ let init = null;
216
+ let test = null;
217
+ let update = null;
174
218
 
175
- forStatement() {
176
- this.eat('FOR');
219
+ if (this.current.type === 'LPAREN') {
177
220
  this.eat('LPAREN');
178
221
 
179
- let init = null;
180
- if (this.current.type !== 'SEMICOLON') {
181
- init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
182
- } else {
183
- this.eat('SEMICOLON');
184
- }
222
+ if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
223
+ else this.eat('SEMICOLON');
185
224
 
186
- let test = null;
187
225
  if (this.current.type !== 'SEMICOLON') test = this.expression();
188
226
  this.eat('SEMICOLON');
189
227
 
190
- let update = null;
191
228
  if (this.current.type !== 'RPAREN') update = this.expression();
192
229
  this.eat('RPAREN');
193
-
194
- const body = this.block();
195
- return { type: 'ForStatement', init, test, update, body };
230
+ } else {
231
+ init = this.expression();
232
+ if (this.current.type === 'IN') {
233
+ this.eat('IN');
234
+ test = this.expression(); // iterable
235
+ }
196
236
  }
197
237
 
198
- breakStatement() {
199
- this.eat('BREAK');
200
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
201
- return { type: 'BreakStatement' };
202
- }
238
+ const body = this.block();
239
+ return { type: 'ForStatement', init, test, update, body };
240
+ }
241
+
242
+ breakStatement() {
243
+ this.eat('BREAK');
244
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
245
+ return { type: 'BreakStatement' };
246
+ }
247
+
203
248
 
204
249
  continueStatement() {
205
- this.eat('CONTINUE');
206
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
207
- return { type: 'ContinueStatement' };
208
- }
250
+ this.eat('CONTINUE');
251
+ // semicolon optional
252
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
253
+ return { type: 'ContinueStatement' };
254
+ }
209
255
 
210
- funcDeclaration() {
211
- this.eat('FUNC');
212
- const name = this.current.value;
213
- this.eat('IDENTIFIER');
256
+ funcDeclaration() {
257
+ this.eat('FUNC');
258
+ const name = this.current.value;
259
+ this.eat('IDENTIFIER');
260
+
261
+ let params = [];
262
+ if (this.current.type === 'LPAREN') {
214
263
  this.eat('LPAREN');
215
- const params = [];
216
264
  if (this.current.type !== 'RPAREN') {
217
265
  params.push(this.current.value);
218
266
  this.eat('IDENTIFIER');
@@ -223,176 +271,203 @@ asyncFuncDeclaration() {
223
271
  }
224
272
  }
225
273
  this.eat('RPAREN');
226
- const body = this.block();
227
- return { type: 'FunctionDeclaration', name, params, body };
274
+ } else {
275
+ // Python-style: single param without parentheses
276
+ if (this.current.type === 'IDENTIFIER') {
277
+ params.push(this.current.value);
278
+ this.eat('IDENTIFIER');
279
+ }
228
280
  }
229
281
 
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 };
282
+ const body = this.block();
283
+ return { type: 'FunctionDeclaration', name, params, body };
284
+ }
285
+
286
+ returnStatement() {
287
+ this.eat('RETURN');
288
+
289
+ let argument = null;
290
+ if (this.current.type !== 'SEMICOLON') {
291
+ argument = this.expression();
236
292
  }
237
293
 
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 };
294
+ // semicolon optional
295
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
296
+
297
+ return { type: 'ReturnStatement', argument };
298
+ }
299
+
300
+ block() {
301
+ this.eat('LBRACE');
302
+ const body = [];
303
+ while (this.current.type !== 'RBRACE') {
304
+ body.push(this.statement());
246
305
  }
306
+ this.eat('RBRACE');
307
+ return { type: 'BlockStatement', body };
308
+ }
309
+
310
+ expressionStatement() {
311
+ const expr = this.expression();
312
+ // semicolon optional
313
+ if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
314
+ return { type: 'ExpressionStatement', expression: expr };
315
+ }
316
+
317
+ expression() {
318
+ return this.assignment();
319
+ }
247
320
 
248
- expressionStatement() {
249
- const expr = this.expression();
250
- if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
251
- return { type: 'ExpressionStatement', expression: expr };
321
+ assignment() {
322
+ const node = this.logicalOr();
323
+ const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
324
+
325
+ if (compoundOps.includes(this.current.type)) {
326
+ const op = this.current.type;
327
+ this.eat(op);
328
+ const right = this.assignment();
329
+ return { type: 'CompoundAssignment', operator: op, left: node, right };
252
330
  }
253
331
 
254
- expression() {
255
- return this.assignment();
332
+ if (this.current.type === 'EQUAL') {
333
+ this.eat('EQUAL');
334
+ const right = this.assignment();
335
+ return { type: 'AssignmentExpression', left: node, right };
256
336
  }
257
337
 
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
- }
338
+ return node;
339
+ }
267
340
 
268
- if (this.current.type === 'EQUAL') {
269
- this.eat('EQUAL');
270
- const right = this.assignment();
271
- return { type: 'AssignmentExpression', left: node, right };
272
- }
341
+ logicalOr() {
342
+ let node = this.logicalAnd();
343
+ while (this.current.type === 'OR') {
344
+ const op = this.current.type;
345
+ this.eat(op);
346
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
347
+ }
348
+ return node;
349
+ }
273
350
 
274
- return node;
351
+ logicalAnd() {
352
+ let node = this.equality();
353
+ while (this.current.type === 'AND') {
354
+ const op = this.current.type;
355
+ this.eat(op);
356
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
275
357
  }
358
+ return node;
359
+ }
360
+ equality() {
361
+ let node = this.comparison();
362
+ while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
363
+ const op = this.current.type;
364
+ this.eat(op);
365
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
366
+ }
367
+ return node;
368
+ }
276
369
 
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;
370
+ comparison() {
371
+ let node = this.term();
372
+ while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
373
+ const op = this.current.type;
374
+ this.eat(op);
375
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
285
376
  }
377
+ return node;
378
+ }
286
379
 
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;
380
+ term() {
381
+ let node = this.factor();
382
+ while (['PLUS', 'MINUS'].includes(this.current.type)) {
383
+ const op = this.current.type;
384
+ this.eat(op);
385
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
295
386
  }
387
+ return node;
388
+ }
296
389
 
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;
390
+ factor() {
391
+ let node = this.unary();
392
+ while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
393
+ const op = this.current.type;
394
+ this.eat(op);
395
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
305
396
  }
397
+ return node;
398
+ }
306
399
 
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;
400
+ unary() {
401
+ if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
402
+ const op = this.current.type;
403
+ this.eat(op);
404
+ return { type: 'UnaryExpression', operator: op, argument: this.unary() };
315
405
  }
316
406
 
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;
407
+ // Python-like: ignore ++ and -- if not used
408
+ if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
409
+ const op = this.current.type;
410
+ this.eat(op);
411
+ const argument = this.unary();
412
+ return { type: 'UpdateExpression', operator: op, argument, prefix: true };
325
413
  }
326
414
 
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() };
415
+ return this.postfix();
416
+ }
417
+
418
+ postfix() {
419
+ let node = this.primary();
420
+ while (true) {
421
+ if (this.current.type === 'LBRACKET') {
422
+ this.eat('LBRACKET');
423
+ const index = this.expression();
424
+ if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
425
+ node = { type: 'IndexExpression', object: node, indexer: index };
426
+ continue;
333
427
  }
334
- return node;
335
- }
336
428
 
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() };
429
+ if (this.current.type === 'LPAREN') {
430
+ this.eat('LPAREN');
431
+ const args = [];
432
+ while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
433
+ args.push(this.expression());
434
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
435
+ }
436
+ if (this.current.type === 'RPAREN') this.eat('RPAREN');
437
+ node = { type: 'CallExpression', callee: node, arguments: args };
438
+ continue;
342
439
  }
440
+
441
+ if (this.current.type === 'DOT') {
442
+ this.eat('DOT');
443
+ const property = this.current.value || '';
444
+ if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
445
+ node = { type: 'MemberExpression', object: node, property };
446
+ continue;
447
+ }
448
+
343
449
  if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
344
450
  const op = this.current.type;
345
451
  this.eat(op);
346
- const argument = this.unary();
347
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
452
+ node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
453
+ continue;
348
454
  }
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;
455
+
456
+ // Python-style: implicit function calls (if identifier followed by identifier)
457
+ if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
458
+ const argNode = { type: 'Identifier', name: this.current.value };
459
+ this.eat('IDENTIFIER');
460
+ node = { type: 'CallExpression', callee: node, arguments: [argNode] };
461
+ continue;
391
462
  }
392
- return node;
463
+
464
+ break;
393
465
  }
466
+ return node;
467
+ }
468
+
394
469
  arrowFunction(params) {
395
- this.eat('ARROW');
470
+ if (this.current.type === 'ARROW') this.eat('ARROW');
396
471
  const body = this.expression();
397
472
  return {
398
473
  type: 'ArrowFunctionExpression',
@@ -401,126 +476,114 @@ arrowFunction(params) {
401
476
  };
402
477
  }
403
478
 
479
+
404
480
  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') {
481
+ const t = this.current;
482
+
483
+ if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
484
+ if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
485
+ if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
486
+ if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
487
+ if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
488
+
489
+ if (t.type === 'AWAIT') {
413
490
  this.eat('AWAIT');
414
491
  const argument = this.expression();
415
492
  return { type: 'AwaitExpression', argument };
416
493
  }
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
494
 
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
- }
495
+ if (t.type === 'NEW') {
496
+ this.eat('NEW');
497
+ const callee = this.primary();
498
+ this.eat('LPAREN');
499
+ const args = [];
500
+ if (this.current.type !== 'RPAREN') {
501
+ args.push(this.expression());
502
+ while (this.current.type === 'COMMA') {
503
+ this.eat('COMMA');
504
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
443
505
  }
444
- this.eat('RPAREN');
445
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
446
506
  }
507
+ this.eat('RPAREN');
508
+ return { type: 'NewExpression', callee, arguments: args };
509
+ }
447
510
 
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]);
511
+ if (t.type === 'ASK') {
512
+ this.eat('ASK');
513
+ this.eat('LPAREN');
514
+ const args = [];
515
+ if (this.current.type !== 'RPAREN') {
516
+ args.push(this.expression());
517
+ while (this.current.type === 'COMMA') {
518
+ this.eat('COMMA');
519
+ if (this.current.type !== 'RPAREN') args.push(this.expression());
520
+ }
521
+ }
522
+ this.eat('RPAREN');
523
+ return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
454
524
  }
455
525
 
456
- return { type: 'Identifier', name };
457
- }
526
+ if (t.type === 'IDENTIFIER') {
527
+ const name = t.value;
528
+ this.eat('IDENTIFIER');
458
529
 
530
+ if (this.current.type === 'ARROW') {
531
+ return this.arrowFunction([name]);
532
+ }
459
533
 
460
- if (t.type === 'LPAREN') {
461
- this.eat('LPAREN');
534
+ return { type: 'Identifier', name };
535
+ }
462
536
 
463
- const params = [];
537
+ if (t.type === 'LPAREN') {
538
+ this.eat('LPAREN');
539
+ const elements = [];
464
540
 
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');
541
+ if (this.current.type !== 'RPAREN') {
542
+ elements.push(this.expression());
543
+ while (this.current.type === 'COMMA') {
544
+ this.eat('COMMA');
545
+ if (this.current.type !== 'RPAREN') elements.push(this.expression());
546
+ }
472
547
  }
473
- }
474
548
 
475
- this.eat('RPAREN');
549
+ this.eat('RPAREN');
476
550
 
477
- if (this.current.type === 'ARROW') {
478
- return this.arrowFunction(params);
479
- }
551
+ if (this.current.type === 'ARROW') return this.arrowFunction(elements);
480
552
 
481
- if (params.length === 1) {
482
- return { type: 'Identifier', name: params[0] };
553
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
483
554
  }
484
555
 
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
- }
556
+ if (t.type === 'LBRACKET') {
557
+ this.eat('LBRACKET');
558
+ const elements = [];
559
+ if (this.current.type !== 'RBRACKET') {
560
+ elements.push(this.expression());
561
+ while (this.current.type === 'COMMA') {
562
+ this.eat('COMMA');
563
+ if (this.current.type !== 'RBRACKET') elements.push(this.expression());
500
564
  }
501
- this.eat('RBRACKET');
502
- return { type: 'ArrayExpression', elements };
503
565
  }
566
+ this.eat('RBRACKET');
567
+ return { type: 'ArrayExpression', elements };
568
+ }
504
569
 
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 };
570
+ if (t.type === 'LBRACE') {
571
+ this.eat('LBRACE');
572
+ const props = [];
573
+ while (this.current.type !== 'RBRACE') {
574
+ const key = this.expression(); // Flexible key: can be any expression
575
+ this.eat('COLON');
576
+ const value = this.expression();
577
+ props.push({ key, value });
578
+ if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
520
579
  }
521
-
522
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
580
+ this.eat('RBRACE');
581
+ return { type: 'ObjectExpression', props };
523
582
  }
583
+
584
+ throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
585
+ }
586
+
524
587
  }
525
588
 
526
589
  module.exports = Parser;