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/dist/index.js +530 -173
- package/package.json +1 -1
- package/src/evaluator.js +185 -62
- package/src/lexer.js +33 -8
- package/src/parser.js +289 -92
- package/src/starlight.js +23 -11
package/src/parser.js
CHANGED
|
@@ -1,22 +1,51 @@
|
|
|
1
|
-
class
|
|
2
|
-
constructor(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
86
|
+
varDeclaration() {
|
|
87
|
+
const t = this.current; // LET token
|
|
59
88
|
this.eat('LET');
|
|
60
|
-
const
|
|
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 {
|
|
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 {
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
-
//
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
453
|
-
|
|
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 {
|
|
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 (
|
|
460
|
-
const op =
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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') {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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 {
|
|
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
|
|
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;
|