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.
- package/dist/index.js +402 -339
- package/package.json +1 -1
- 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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
64
|
-
return { type: 'VarDeclaration', id, expr };
|
|
65
|
+
expr = this.expression();
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
if (this.current.type
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
this.eat('
|
|
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
|
-
|
|
121
|
+
|
|
101
122
|
const body = this.block();
|
|
102
123
|
return { type: 'FunctionDeclaration', name, params, body, async: true };
|
|
103
124
|
}
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
ifStatement() {
|
|
127
|
+
this.eat('IF');
|
|
128
|
+
|
|
129
|
+
let test;
|
|
130
|
+
if (this.current.type === 'LPAREN') {
|
|
107
131
|
this.eat('LPAREN');
|
|
108
|
-
|
|
132
|
+
test = this.expression();
|
|
109
133
|
this.eat('RPAREN');
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
157
|
+
test = this.expression();
|
|
124
158
|
this.eat('RPAREN');
|
|
125
|
-
|
|
126
|
-
|
|
159
|
+
} else {
|
|
160
|
+
test = this.expression();
|
|
127
161
|
}
|
|
128
|
-
importStatement() {
|
|
129
|
-
this.eat('IMPORT');
|
|
130
162
|
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
183
|
+
let localName = importedName;
|
|
184
|
+
if (this.current.type === 'AS') {
|
|
135
185
|
this.eat('AS');
|
|
136
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
207
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
170
208
|
|
|
171
|
-
|
|
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
|
-
|
|
176
|
-
this.eat('FOR');
|
|
219
|
+
if (this.current.type === 'LPAREN') {
|
|
177
220
|
this.eat('LPAREN');
|
|
178
221
|
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
this.
|
|
341
|
-
|
|
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
|
-
|
|
347
|
-
|
|
452
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
453
|
+
continue;
|
|
348
454
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
461
|
-
|
|
534
|
+
return { type: 'Identifier', name };
|
|
535
|
+
}
|
|
462
536
|
|
|
463
|
-
|
|
537
|
+
if (t.type === 'LPAREN') {
|
|
538
|
+
this.eat('LPAREN');
|
|
539
|
+
const elements = [];
|
|
464
540
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
549
|
+
this.eat('RPAREN');
|
|
476
550
|
|
|
477
|
-
|
|
478
|
-
return this.arrowFunction(params);
|
|
479
|
-
}
|
|
551
|
+
if (this.current.type === 'ARROW') return this.arrowFunction(elements);
|
|
480
552
|
|
|
481
|
-
|
|
482
|
-
return { type: 'Identifier', name: params[0] };
|
|
553
|
+
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
|
|
483
554
|
}
|
|
484
555
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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;
|