starlight-cli 1.0.46 → 1.0.48

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
@@ -1,22 +1,51 @@
1
- class Parser {
2
- constructor(tokens) {
3
- this.tokens = tokens;
4
- this.pos = 0;
5
- this.current = this.tokens[this.pos];
1
+ class ParseError extends Error {
2
+ constructor(message, token, source) {
3
+ const line = token?.line ?? '?';
4
+ const column = token?.column ?? '?';
5
+
6
+ let output = `SyntaxError: ${message}\n`;
7
+
8
+ if (source && token?.line != null) {
9
+ const lines = source.split('\n');
10
+ const srcLine = lines[token.line - 1] || '';
11
+ output += ` at line ${line}, column ${column}\n`;
12
+ output += ` ${srcLine}\n`;
13
+ output += ` ${' '.repeat(column - 1)}^\n`;
14
+ } else {
15
+ output += ` at line ${line}, column ${column}\n`;
16
+ }
17
+
18
+ super(output);
19
+ this.name = 'SyntaxError';
6
20
  }
21
+ }
22
+
23
+ class Parser {
24
+ constructor(tokens, source = '') {
25
+ this.tokens = tokens;
26
+ this.source = source;
27
+ this.pos = 0;
28
+ this.current = this.tokens[this.pos];
29
+ }
30
+
7
31
 
8
32
  advance() {
9
33
  this.pos++;
10
34
  this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
11
35
  }
12
36
 
13
- eat(type) {
14
- if (this.current.type === type) this.advance();
15
- else throw new Error(
16
- `Expected ${type}, got ${this.current.type} at line ${this.current.line}, column ${this.current.column}`
17
- );
18
-
37
+ eat(type) {
38
+ if (this.current.type === type) {
39
+ this.advance();
40
+ } else {
41
+ throw new ParseError(
42
+ `Expected '${type}' but got '${this.current.type}'`,
43
+ this.current,
44
+ this.source
45
+ );
19
46
  }
47
+ }
48
+
20
49
 
21
50
  peekType(offset = 1) {
22
51
  return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
@@ -54,10 +83,11 @@ class Parser {
54
83
  return this.expressionStatement();
55
84
  }
56
85
  }
57
-
58
- varDeclaration() {
86
+ varDeclaration() {
87
+ const t = this.current; // LET token
59
88
  this.eat('LET');
60
- const id = this.current.value;
89
+ const idToken = this.current; // identifier token
90
+ const id = idToken.value;
61
91
  this.eat('IDENTIFIER');
62
92
 
63
93
  let expr = null;
@@ -69,16 +99,30 @@ class Parser {
69
99
  // semicolon optional
70
100
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
71
101
 
72
- return { type: 'VarDeclaration', id, expr };
102
+ return {
103
+ type: 'VarDeclaration',
104
+ id,
105
+ expr,
106
+ line: t.line,
107
+ column: t.column
108
+ };
73
109
  }
74
110
 
75
111
  sldeployStatement() {
112
+ const t = this.current; // SLDEPLOY token
76
113
  this.eat('SLDEPLOY');
77
114
  const expr = this.expression();
78
115
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
79
- return { type: 'SldeployStatement', expr };
116
+ return {
117
+ type: 'SldeployStatement',
118
+ expr,
119
+ line: t.line,
120
+ column: t.column
121
+ };
80
122
  }
123
+
81
124
  doTrackStatement() {
125
+ const t = this.current; // DO token
82
126
  this.eat('DO');
83
127
 
84
128
  const body = this.block();
@@ -92,13 +136,17 @@ doTrackStatement() {
92
136
  return {
93
137
  type: 'DoTrackStatement',
94
138
  body,
95
- handler
139
+ handler,
140
+ line: t.line,
141
+ column: t.column
96
142
  };
97
143
  }
98
144
 
99
145
  defineStatement() {
146
+ const t = this.current; // DEFINE token
100
147
  this.eat('DEFINE');
101
- const id = this.current.value;
148
+ const idToken = this.current; // identifier token
149
+ const id = idToken.value;
102
150
  this.eat('IDENTIFIER');
103
151
 
104
152
  let expr = null;
@@ -109,10 +157,17 @@ defineStatement() {
109
157
 
110
158
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
111
159
 
112
- return { type: 'DefineStatement', id, expr };
160
+ return {
161
+ type: 'DefineStatement',
162
+ id,
163
+ expr,
164
+ line: t.line,
165
+ column: t.column
166
+ };
113
167
  }
114
168
 
115
169
  asyncFuncDeclaration() {
170
+ const t = this.current; // first token of function (identifier)
116
171
  const name = this.current.value;
117
172
  this.eat('IDENTIFIER');
118
173
 
@@ -138,10 +193,19 @@ asyncFuncDeclaration() {
138
193
  }
139
194
 
140
195
  const body = this.block();
141
- return { type: 'FunctionDeclaration', name, params, body, async: true };
196
+ return {
197
+ type: 'FunctionDeclaration',
198
+ name,
199
+ params,
200
+ body,
201
+ async: true,
202
+ line: t.line,
203
+ column: t.column
204
+ };
142
205
  }
143
206
 
144
207
  ifStatement() {
208
+ const t = this.current; // IF token
145
209
  this.eat('IF');
146
210
 
147
211
  let test;
@@ -150,7 +214,7 @@ ifStatement() {
150
214
  test = this.expression();
151
215
  this.eat('RPAREN');
152
216
  } else {
153
- // python style: no parentheses
217
+ // Python style: no parentheses
154
218
  test = this.expression();
155
219
  }
156
220
 
@@ -163,10 +227,18 @@ ifStatement() {
163
227
  else alternate = this.block();
164
228
  }
165
229
 
166
- return { type: 'IfStatement', test, consequent, alternate };
230
+ return {
231
+ type: 'IfStatement',
232
+ test,
233
+ consequent,
234
+ alternate,
235
+ line: t.line,
236
+ column: t.column
237
+ };
167
238
  }
168
239
 
169
240
  whileStatement() {
241
+ const t = this.current; // WHILE token
170
242
  this.eat('WHILE');
171
243
 
172
244
  let test;
@@ -179,10 +251,17 @@ whileStatement() {
179
251
  }
180
252
 
181
253
  const body = this.block();
182
- return { type: 'WhileStatement', test, body };
254
+ return {
255
+ type: 'WhileStatement',
256
+ test,
257
+ body,
258
+ line: t.line,
259
+ column: t.column
260
+ };
183
261
  }
184
262
 
185
263
  importStatement() {
264
+ const t = this.current; // IMPORT token
186
265
  this.eat('IMPORT');
187
266
 
188
267
  let specifiers = [];
@@ -191,11 +270,13 @@ importStatement() {
191
270
  this.eat('AS');
192
271
  const name = this.current.value;
193
272
  this.eat('IDENTIFIER');
194
- specifiers.push({ type: 'NamespaceImport', local: name });
273
+ specifiers.push({ type: 'NamespaceImport', local: name, line: t.line, column: t.column });
195
274
  } else if (this.current.type === 'LBRACE') {
196
275
  this.eat('LBRACE');
197
276
  while (this.current.type !== 'RBRACE') {
198
277
  const importedName = this.current.value;
278
+ const importedLine = this.current.line;
279
+ const importedColumn = this.current.column;
199
280
  this.eat('IDENTIFIER');
200
281
 
201
282
  let localName = importedName;
@@ -205,14 +286,16 @@ importStatement() {
205
286
  this.eat('IDENTIFIER');
206
287
  }
207
288
 
208
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
289
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName, line: importedLine, column: importedColumn });
209
290
  if (this.current.type === 'COMMA') this.eat('COMMA');
210
291
  }
211
292
  this.eat('RBRACE');
212
293
  } else if (this.current.type === 'IDENTIFIER') {
213
294
  const localName = this.current.value;
295
+ const localLine = this.current.line;
296
+ const localColumn = this.current.column;
214
297
  this.eat('IDENTIFIER');
215
- specifiers.push({ type: 'DefaultImport', local: localName });
298
+ specifiers.push({ type: 'DefaultImport', local: localName, line: localLine, column: localColumn });
216
299
  } else {
217
300
  throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
218
301
  }
@@ -224,9 +307,17 @@ importStatement() {
224
307
 
225
308
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
226
309
 
227
- return { type: 'ImportStatement', path: pathToken.value, specifiers };
310
+ return {
311
+ type: 'ImportStatement',
312
+ path: pathToken.value,
313
+ specifiers,
314
+ line: t.line,
315
+ column: t.column
316
+ };
228
317
  }
318
+
229
319
  forStatement() {
320
+ const t = this.current; // FOR token
230
321
  this.eat('FOR');
231
322
 
232
323
  // --- Python-style: for variable in iterable (supports optional 'let') ---
@@ -234,17 +325,22 @@ forStatement() {
234
325
  (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
235
326
 
236
327
  let variable;
328
+ let variableLine, variableColumn;
237
329
  let iterable;
238
330
  let letKeyword = false;
239
331
 
240
332
  if (this.current.type === 'LET') {
241
333
  letKeyword = true;
242
334
  this.eat('LET');
335
+ variableLine = this.current.line;
336
+ variableColumn = this.current.column;
243
337
  variable = this.current.value;
244
338
  this.eat('IDENTIFIER');
245
339
  this.eat('IN');
246
340
  iterable = this.expression();
247
341
  } else {
342
+ variableLine = this.current.line;
343
+ variableColumn = this.current.column;
248
344
  variable = this.current.value;
249
345
  this.eat('IDENTIFIER');
250
346
  this.eat('IN');
@@ -252,7 +348,17 @@ forStatement() {
252
348
  }
253
349
 
254
350
  const body = this.block();
255
- return { type: 'ForInStatement', variable, iterable, letKeyword, body };
351
+ return {
352
+ type: 'ForInStatement',
353
+ variable,
354
+ variableLine,
355
+ variableColumn,
356
+ iterable,
357
+ letKeyword,
358
+ body,
359
+ line: t.line,
360
+ column: t.column
361
+ };
256
362
  }
257
363
 
258
364
  // --- C-style: for(init; test; update) ---
@@ -287,37 +393,51 @@ forStatement() {
287
393
  }
288
394
 
289
395
  const body = this.block();
290
- return { type: 'ForStatement', init, test, update, body };
396
+ return {
397
+ type: 'ForStatement',
398
+ init,
399
+ test,
400
+ update,
401
+ body,
402
+ line: t.line,
403
+ column: t.column
404
+ };
291
405
  }
292
406
 
293
407
  breakStatement() {
408
+ const t = this.current; // BREAK token
294
409
  this.eat('BREAK');
295
410
  // Python-style: no semicolon needed, ignore if present
296
411
  if (this.current.type === 'SEMICOLON') this.advance();
297
- return { type: 'BreakStatement' };
412
+ return { type: 'BreakStatement', line: t.line, column: t.column };
298
413
  }
299
414
 
300
415
  continueStatement() {
416
+ const t = this.current; // CONTINUE token
301
417
  this.eat('CONTINUE');
302
418
  // Python-style: no semicolon needed, ignore if present
303
419
  if (this.current.type === 'SEMICOLON') this.advance();
304
- return { type: 'ContinueStatement' };
420
+ return { type: 'ContinueStatement', line: t.line, column: t.column };
305
421
  }
306
422
 
307
423
  funcDeclaration() {
424
+ const t = this.current; // FUNC token
308
425
  this.eat('FUNC');
309
- const name = this.current.value;
426
+ const nameToken = this.current;
427
+ const name = nameToken.value;
310
428
  this.eat('IDENTIFIER');
311
429
 
312
430
  let params = [];
313
431
  if (this.current.type === 'LPAREN') {
314
432
  this.eat('LPAREN');
315
433
  if (this.current.type !== 'RPAREN') {
316
- params.push(this.current.value);
434
+ const paramToken = this.current;
435
+ params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
317
436
  this.eat('IDENTIFIER');
318
437
  while (this.current.type === 'COMMA') {
319
438
  this.eat('COMMA');
320
- params.push(this.current.value);
439
+ const paramToken2 = this.current;
440
+ params.push({ name: paramToken2.value, line: paramToken2.line, column: paramToken2.column });
321
441
  this.eat('IDENTIFIER');
322
442
  }
323
443
  }
@@ -325,16 +445,18 @@ funcDeclaration() {
325
445
  } else {
326
446
  // Python-style: single param without parentheses
327
447
  if (this.current.type === 'IDENTIFIER') {
328
- params.push(this.current.value);
448
+ const paramToken = this.current;
449
+ params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
329
450
  this.eat('IDENTIFIER');
330
451
  }
331
452
  }
332
453
 
333
454
  const body = this.block();
334
- return { type: 'FunctionDeclaration', name, params, body };
455
+ return { type: 'FunctionDeclaration', name, params, body, line: t.line, column: t.column };
335
456
  }
336
457
 
337
458
  returnStatement() {
459
+ const t = this.current; // RETURN token
338
460
  this.eat('RETURN');
339
461
 
340
462
  let argument = null;
@@ -345,24 +467,26 @@ returnStatement() {
345
467
  // semicolon optional
346
468
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
347
469
 
348
- return { type: 'ReturnStatement', argument };
470
+ return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
349
471
  }
350
472
 
351
473
  block() {
474
+ const t = this.current; // LBRACE token
352
475
  this.eat('LBRACE');
353
476
  const body = [];
354
477
  while (this.current.type !== 'RBRACE') {
355
478
  body.push(this.statement());
356
479
  }
357
480
  this.eat('RBRACE');
358
- return { type: 'BlockStatement', body };
481
+ return { type: 'BlockStatement', body, line: t.line, column: t.column };
359
482
  }
360
483
 
361
484
  expressionStatement() {
485
+ const exprToken = this.current; // first token of the expression
362
486
  const expr = this.expression();
363
487
  // semicolon optional
364
488
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
365
- return { type: 'ExpressionStatement', expression: expr };
489
+ return { type: 'ExpressionStatement', expression: expr, line: exprToken.line, column: exprToken.column };
366
490
  }
367
491
 
368
492
  expression() {
@@ -373,17 +497,19 @@ assignment() {
373
497
  const node = this.logicalOr();
374
498
  const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
375
499
 
376
- if (compoundOps.includes(this.current.type)) {
377
- const op = this.current.type;
500
+ const t = this.current;
501
+
502
+ if (compoundOps.includes(t.type)) {
503
+ const op = t.type;
378
504
  this.eat(op);
379
505
  const right = this.assignment();
380
- return { type: 'CompoundAssignment', operator: op, left: node, right };
506
+ return { type: 'CompoundAssignment', operator: op, left: node, right, line: t.line, column: t.column };
381
507
  }
382
508
 
383
- if (this.current.type === 'EQUAL') {
509
+ if (t.type === 'EQUAL') {
384
510
  this.eat('EQUAL');
385
511
  const right = this.assignment();
386
- return { type: 'AssignmentExpression', left: node, right };
512
+ return { type: 'AssignmentExpression', left: node, right, line: t.line, column: t.column };
387
513
  }
388
514
 
389
515
  return node;
@@ -392,9 +518,10 @@ assignment() {
392
518
  logicalOr() {
393
519
  let node = this.logicalAnd();
394
520
  while (this.current.type === 'OR') {
395
- const op = this.current.type;
521
+ const t = this.current;
522
+ const op = t.type;
396
523
  this.eat(op);
397
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
524
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd(), line: t.line, column: t.column };
398
525
  }
399
526
  return node;
400
527
  }
@@ -402,18 +529,21 @@ logicalOr() {
402
529
  logicalAnd() {
403
530
  let node = this.equality();
404
531
  while (this.current.type === 'AND') {
405
- const op = this.current.type;
532
+ const t = this.current;
533
+ const op = t.type;
406
534
  this.eat(op);
407
- node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
535
+ node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality(), line: t.line, column: t.column };
408
536
  }
409
537
  return node;
410
538
  }
539
+
411
540
  equality() {
412
541
  let node = this.comparison();
413
542
  while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
414
- const op = this.current.type;
543
+ const t = this.current;
544
+ const op = t.type;
415
545
  this.eat(op);
416
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
546
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison(), line: t.line, column: t.column };
417
547
  }
418
548
  return node;
419
549
  }
@@ -421,9 +551,10 @@ equality() {
421
551
  comparison() {
422
552
  let node = this.term();
423
553
  while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
424
- const op = this.current.type;
554
+ const t = this.current;
555
+ const op = t.type;
425
556
  this.eat(op);
426
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
557
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.term(), line: t.line, column: t.column };
427
558
  }
428
559
  return node;
429
560
  }
@@ -431,9 +562,10 @@ comparison() {
431
562
  term() {
432
563
  let node = this.factor();
433
564
  while (['PLUS', 'MINUS'].includes(this.current.type)) {
434
- const op = this.current.type;
565
+ const t = this.current;
566
+ const op = t.type;
435
567
  this.eat(op);
436
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
568
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor(), line: t.line, column: t.column };
437
569
  }
438
570
  return node;
439
571
  }
@@ -441,26 +573,43 @@ term() {
441
573
  factor() {
442
574
  let node = this.unary();
443
575
  while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
444
- const op = this.current.type;
576
+ const t = this.current;
577
+ const op = t.type;
445
578
  this.eat(op);
446
- node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
579
+ node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary(), line: t.line, column: t.column };
447
580
  }
448
581
  return node;
449
582
  }
450
583
 
584
+
451
585
  unary() {
452
- if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
453
- const op = this.current.type;
586
+ const t = this.current;
587
+
588
+ if (['NOT', 'MINUS', 'PLUS'].includes(t.type)) {
589
+ const op = t.type;
454
590
  this.eat(op);
455
- return { type: 'UnaryExpression', operator: op, argument: this.unary() };
591
+ return {
592
+ type: 'UnaryExpression',
593
+ operator: op,
594
+ argument: this.unary(),
595
+ line: t.line,
596
+ column: t.column
597
+ };
456
598
  }
457
599
 
458
600
  // Python-like: ignore ++ and -- if not used
459
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
460
- const op = this.current.type;
601
+ if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
602
+ const op = t.type;
461
603
  this.eat(op);
462
604
  const argument = this.unary();
463
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
605
+ return {
606
+ type: 'UpdateExpression',
607
+ operator: op,
608
+ argument,
609
+ prefix: true,
610
+ line: t.line,
611
+ column: t.column
612
+ };
464
613
  }
465
614
 
466
615
  return this.postfix();
@@ -469,15 +618,21 @@ unary() {
469
618
  postfix() {
470
619
  let node = this.primary();
471
620
  while (true) {
472
- if (this.current.type === 'LBRACKET') {
621
+ const t = this.current;
622
+
623
+ if (t.type === 'LBRACKET') {
624
+ const startLine = t.line;
625
+ const startCol = t.column;
473
626
  this.eat('LBRACKET');
474
627
  const index = this.expression();
475
628
  if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
476
- node = { type: 'IndexExpression', object: node, indexer: index };
629
+ node = { type: 'IndexExpression', object: node, indexer: index, line: startLine, column: startCol };
477
630
  continue;
478
631
  }
479
632
 
480
- if (this.current.type === 'LPAREN') {
633
+ if (t.type === 'LPAREN') {
634
+ const startLine = t.line;
635
+ const startCol = t.column;
481
636
  this.eat('LPAREN');
482
637
  const args = [];
483
638
  while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
@@ -485,30 +640,30 @@ postfix() {
485
640
  if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
486
641
  }
487
642
  if (this.current.type === 'RPAREN') this.eat('RPAREN');
488
- node = { type: 'CallExpression', callee: node, arguments: args };
643
+ node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
489
644
  continue;
490
645
  }
491
646
 
492
- if (this.current.type === 'DOT') {
647
+ if (t.type === 'DOT') {
648
+ const startLine = t.line;
649
+ const startCol = t.column;
493
650
  this.eat('DOT');
494
651
  const property = this.current.value || '';
495
652
  if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
496
- node = { type: 'MemberExpression', object: node, property };
653
+ node = { type: 'MemberExpression', object: node, property, line: startLine, column: startCol };
497
654
  continue;
498
655
  }
499
656
 
500
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
501
- const op = this.current.type;
502
- this.eat(op);
503
- node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
657
+ if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
658
+ this.eat(t.type);
659
+ node = { type: 'UpdateExpression', operator: t.type, argument: node, prefix: false, line: t.line, column: t.column };
504
660
  continue;
505
661
  }
506
662
 
507
- // Python-style: implicit function calls (if identifier followed by identifier)
508
- if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
509
- const argNode = { type: 'Identifier', name: this.current.value };
663
+ if (node.type === 'Identifier' && t.type === 'IDENTIFIER') {
664
+ const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
510
665
  this.eat('IDENTIFIER');
511
- node = { type: 'CallExpression', callee: node, arguments: [argNode] };
666
+ node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
512
667
  continue;
513
668
  }
514
669
 
@@ -518,12 +673,18 @@ postfix() {
518
673
  }
519
674
 
520
675
  arrowFunction(params) {
521
- if (this.current.type === 'ARROW') this.eat('ARROW');
676
+ const t = this.current;
677
+ if (t.type === 'ARROW') this.eat('ARROW');
522
678
  const body = this.expression();
679
+ const startLine = params.length > 0 ? params[0].line : t.line;
680
+ const startCol = params.length > 0 ? params[0].column : t.column;
681
+
523
682
  return {
524
683
  type: 'ArrowFunctionExpression',
525
684
  params,
526
- body
685
+ body,
686
+ line: startLine,
687
+ column: startCol
527
688
  };
528
689
  }
529
690
 
@@ -531,16 +692,35 @@ arrowFunction(params) {
531
692
  primary() {
532
693
  const t = this.current;
533
694
 
534
- if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
535
- if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
536
- if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
537
- if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
538
- if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
695
+ if (t.type === 'NUMBER') {
696
+ this.eat('NUMBER');
697
+ return { type: 'Literal', value: t.value, line: t.line, column: t.column };
698
+ }
699
+
700
+ if (t.type === 'STRING') {
701
+ this.eat('STRING');
702
+ return { type: 'Literal', value: t.value, line: t.line, column: t.column };
703
+ }
704
+
705
+ if (t.type === 'TRUE') {
706
+ this.eat('TRUE');
707
+ return { type: 'Literal', value: true, line: t.line, column: t.column };
708
+ }
709
+
710
+ if (t.type === 'FALSE') {
711
+ this.eat('FALSE');
712
+ return { type: 'Literal', value: false, line: t.line, column: t.column };
713
+ }
714
+
715
+ if (t.type === 'NULL') {
716
+ this.eat('NULL');
717
+ return { type: 'Literal', value: null, line: t.line, column: t.column };
718
+ }
539
719
 
540
720
  if (t.type === 'AWAIT') {
541
721
  this.eat('AWAIT');
542
722
  const argument = this.expression();
543
- return { type: 'AwaitExpression', argument };
723
+ return { type: 'AwaitExpression', argument, line: t.line, column: t.column };
544
724
  }
545
725
 
546
726
  if (t.type === 'NEW') {
@@ -556,7 +736,7 @@ arrowFunction(params) {
556
736
  }
557
737
  }
558
738
  this.eat('RPAREN');
559
- return { type: 'NewExpression', callee, arguments: args };
739
+ return { type: 'NewExpression', callee, arguments: args, line: t.line, column: t.column };
560
740
  }
561
741
 
562
742
  if (t.type === 'ASK') {
@@ -571,7 +751,13 @@ arrowFunction(params) {
571
751
  }
572
752
  }
573
753
  this.eat('RPAREN');
574
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
754
+ return {
755
+ type: 'CallExpression',
756
+ callee: { type: 'Identifier', name: 'ask', line: t.line, column: t.column },
757
+ arguments: args,
758
+ line: t.line,
759
+ column: t.column
760
+ };
575
761
  }
576
762
 
577
763
  if (t.type === 'IDENTIFIER') {
@@ -579,13 +765,15 @@ arrowFunction(params) {
579
765
  this.eat('IDENTIFIER');
580
766
 
581
767
  if (this.current.type === 'ARROW') {
582
- return this.arrowFunction([name]);
768
+ return this.arrowFunction([{ type: 'Identifier', name, line: t.line, column: t.column }]);
583
769
  }
584
770
 
585
- return { type: 'Identifier', name };
771
+ return { type: 'Identifier', name, line: t.line, column: t.column };
586
772
  }
587
773
 
588
774
  if (t.type === 'LPAREN') {
775
+ const startLine = t.line;
776
+ const startCol = t.column;
589
777
  this.eat('LPAREN');
590
778
  const elements = [];
591
779
 
@@ -601,10 +789,12 @@ arrowFunction(params) {
601
789
 
602
790
  if (this.current.type === 'ARROW') return this.arrowFunction(elements);
603
791
 
604
- return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
792
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements, line: startLine, column: startCol };
605
793
  }
606
794
 
607
795
  if (t.type === 'LBRACKET') {
796
+ const startLine = t.line;
797
+ const startCol = t.column;
608
798
  this.eat('LBRACKET');
609
799
  const elements = [];
610
800
  if (this.current.type !== 'RBRACKET') {
@@ -615,10 +805,12 @@ arrowFunction(params) {
615
805
  }
616
806
  }
617
807
  this.eat('RBRACKET');
618
- return { type: 'ArrayExpression', elements };
808
+ return { type: 'ArrayExpression', elements, line: startLine, column: startCol };
619
809
  }
620
810
 
621
811
  if (t.type === 'LBRACE') {
812
+ const startLine = t.line;
813
+ const startCol = t.column;
622
814
  this.eat('LBRACE');
623
815
  const props = [];
624
816
  while (this.current.type !== 'RBRACE') {
@@ -629,12 +821,17 @@ arrowFunction(params) {
629
821
  if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
630
822
  }
631
823
  this.eat('RBRACE');
632
- return { type: 'ObjectExpression', props };
824
+ return { type: 'ObjectExpression', props, line: startLine, column: startCol };
633
825
  }
634
826
 
635
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
827
+ throw new ParseError(
828
+ `Unexpected token '${t.type}'`,
829
+ t,
830
+ this.source
831
+ );
636
832
  }
637
833
 
834
+
638
835
  }
639
836
 
640
837
  module.exports = Parser;