starlight-cli 1.1.2 → 1.1.4
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/README.md +121 -122
- package/dist/index.js +170 -70
- package/package.json +1 -1
- package/src/evaluator.js +97 -34
- package/src/lexer.js +1 -1
- package/src/parser.js +71 -34
- package/src/starlight.js +1 -1
package/src/evaluator.js
CHANGED
|
@@ -49,7 +49,6 @@ class Environment {
|
|
|
49
49
|
if (name in this.store) return this.store[name];
|
|
50
50
|
if (this.parent) return this.parent.get(name, node, source);
|
|
51
51
|
|
|
52
|
-
// Only suggest for top-level variable/function names
|
|
53
52
|
let suggestion = null;
|
|
54
53
|
if (node && source) {
|
|
55
54
|
suggestion = this.suggest?.(name, this) || null;
|
|
@@ -84,7 +83,6 @@ class Evaluator {
|
|
|
84
83
|
this.setupBuiltins();
|
|
85
84
|
}
|
|
86
85
|
suggest(name, env) {
|
|
87
|
-
// Collect all variable/function names from the environment chain
|
|
88
86
|
const names = new Set();
|
|
89
87
|
let current = env;
|
|
90
88
|
while (current) {
|
|
@@ -98,11 +96,10 @@ suggest(name, env) {
|
|
|
98
96
|
let bestScore = Infinity;
|
|
99
97
|
|
|
100
98
|
for (const item of names) {
|
|
101
|
-
// simple edit distance approximation
|
|
102
99
|
const dist = Math.abs(item.length - name.length) +
|
|
103
100
|
[...name].filter((c, i) => c !== item[i]).length;
|
|
104
101
|
|
|
105
|
-
if (dist < bestScore && dist <= 2) {
|
|
102
|
+
if (dist < bestScore && dist <= 2) {
|
|
106
103
|
bestScore = dist;
|
|
107
104
|
best = item;
|
|
108
105
|
}
|
|
@@ -114,39 +111,31 @@ suggest(name, env) {
|
|
|
114
111
|
formatValue(value, seen = new Set()) {
|
|
115
112
|
const color = require('starlight-color');
|
|
116
113
|
|
|
117
|
-
// Handle circular references
|
|
118
114
|
if (typeof value === 'object' && value !== null) {
|
|
119
115
|
if (seen.has(value)) return color.red('[Circular]');
|
|
120
116
|
seen.add(value);
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
// Python-style null / undefined
|
|
124
119
|
if (value === null) return color.yellow('None');
|
|
125
120
|
if (value === undefined) return color.yellow('undefined');
|
|
126
121
|
|
|
127
122
|
const t = typeof value;
|
|
128
123
|
|
|
129
|
-
// Strings (no quotes)
|
|
130
124
|
if (t === 'string') return color.cyan(value);
|
|
131
125
|
|
|
132
|
-
// Numbers
|
|
133
126
|
if (t === 'number') return color.green(String(value));
|
|
134
127
|
|
|
135
|
-
// Booleans
|
|
136
128
|
if (t === 'boolean') return color.yellow(value ? 'true' : 'false');
|
|
137
129
|
|
|
138
|
-
// Native JS functions
|
|
139
130
|
if (t === 'function') {
|
|
140
131
|
return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
|
|
141
132
|
}
|
|
142
133
|
|
|
143
|
-
// Arrays
|
|
144
134
|
if (Array.isArray(value)) {
|
|
145
135
|
const items = value.map(v => this.formatValue(v, seen));
|
|
146
136
|
return color.white('[ ' + items.join(', ') + ' ]');
|
|
147
137
|
}
|
|
148
138
|
|
|
149
|
-
// Objects (including user-defined functions)
|
|
150
139
|
if (t === 'object') {
|
|
151
140
|
if (value.params && value.body) {
|
|
152
141
|
return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
|
|
@@ -158,7 +147,6 @@ formatValue(value, seen = new Set()) {
|
|
|
158
147
|
return color.magenta('{ ') + entries.join(color.magenta(', ')) + color.magenta(' }');
|
|
159
148
|
}
|
|
160
149
|
|
|
161
|
-
// Fallback
|
|
162
150
|
try {
|
|
163
151
|
return String(value);
|
|
164
152
|
} catch {
|
|
@@ -238,7 +226,6 @@ this.global.define('fetch', async (url, options = {}) => {
|
|
|
238
226
|
};
|
|
239
227
|
});
|
|
240
228
|
|
|
241
|
-
// GET request
|
|
242
229
|
this.global.define('get', async (url) => {
|
|
243
230
|
const res = await fetch(url);
|
|
244
231
|
return await res.json();
|
|
@@ -285,6 +272,11 @@ case 'ForInStatement':
|
|
|
285
272
|
return await this.evalFor(node, env);
|
|
286
273
|
case 'DoTrackStatement':
|
|
287
274
|
return await this.evalDoTrack(node, env);
|
|
275
|
+
case 'StartStatement':
|
|
276
|
+
return await this.evalStartStatement(node, env);
|
|
277
|
+
|
|
278
|
+
case 'RaceClause':
|
|
279
|
+
return await this.evalRaceClause(node, env);
|
|
288
280
|
|
|
289
281
|
case 'BreakStatement': throw new BreakSignal();
|
|
290
282
|
case 'ContinueStatement': throw new ContinueSignal();
|
|
@@ -313,14 +305,14 @@ case 'DoTrackStatement':
|
|
|
313
305
|
const callee = await this.evaluate(node.callee, env);
|
|
314
306
|
|
|
315
307
|
if (typeof callee === 'object' && callee.body) {
|
|
316
|
-
const evaluator = this;
|
|
308
|
+
const evaluator = this;
|
|
317
309
|
const Constructor = function(...args) {
|
|
318
310
|
const newEnv = new Environment(callee.env);
|
|
319
311
|
newEnv.define('this', this);
|
|
320
312
|
for (let i = 0; i < callee.params.length; i++) {
|
|
321
313
|
newEnv.define(callee.params[i], args[i]);
|
|
322
314
|
}
|
|
323
|
-
return evaluator.evaluate(callee.body, newEnv);
|
|
315
|
+
return evaluator.evaluate(callee.body, newEnv);
|
|
324
316
|
};
|
|
325
317
|
|
|
326
318
|
const args = [];
|
|
@@ -358,13 +350,78 @@ async evalProgram(node, env) {
|
|
|
358
350
|
return result;
|
|
359
351
|
}
|
|
360
352
|
|
|
353
|
+
async evalStartStatement(node, env) {
|
|
354
|
+
try {
|
|
355
|
+
const value = await this.evaluate(node.discriminant, env);
|
|
356
|
+
let executing = false;
|
|
357
|
+
for (const c of node.cases) {
|
|
358
|
+
try {
|
|
359
|
+
if (!executing) {
|
|
360
|
+
const testValue = await this.evaluate(c.test, env);
|
|
361
|
+
if (testValue === value) executing = true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (executing) {
|
|
365
|
+
await this.evaluate(c.consequent, new Environment(env));
|
|
366
|
+
}
|
|
367
|
+
} catch (caseErr) {
|
|
368
|
+
if (caseErr instanceof BreakSignal) break;
|
|
369
|
+
if (caseErr instanceof RuntimeError ||
|
|
370
|
+
caseErr instanceof ReturnValue ||
|
|
371
|
+
caseErr instanceof ContinueSignal) {
|
|
372
|
+
throw caseErr; // propagate signals
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
throw new RuntimeError(
|
|
376
|
+
caseErr.message || 'Error evaluating case in start statement',
|
|
377
|
+
c,
|
|
378
|
+
this.source
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null;
|
|
384
|
+
} catch (err) {
|
|
385
|
+
if (err instanceof RuntimeError ||
|
|
386
|
+
err instanceof ReturnValue ||
|
|
387
|
+
err instanceof BreakSignal ||
|
|
388
|
+
err instanceof ContinueSignal) {
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
throw new RuntimeError(
|
|
392
|
+
err.message || 'Error evaluating start statement',
|
|
393
|
+
node,
|
|
394
|
+
this.source
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
async evalRaceClause(node, env) {
|
|
401
|
+
try {
|
|
402
|
+
const testValue = await this.evaluate(node.test, env);
|
|
403
|
+
const result = await this.evaluate(node.consequent, new Environment(env));
|
|
404
|
+
return { testValue, result };
|
|
405
|
+
} catch (err) {
|
|
406
|
+
if (err instanceof RuntimeError ||
|
|
407
|
+
err instanceof ReturnValue ||
|
|
408
|
+
err instanceof BreakSignal ||
|
|
409
|
+
err instanceof ContinueSignal) {
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
throw new RuntimeError(
|
|
413
|
+
err.message || 'Error evaluating race clause',
|
|
414
|
+
node,
|
|
415
|
+
this.source
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
361
419
|
|
|
362
420
|
async evalDoTrack(node, env) {
|
|
363
421
|
try {
|
|
364
422
|
return await this.evaluate(node.body, env);
|
|
365
423
|
} catch (err) {
|
|
366
424
|
if (!node.handler) {
|
|
367
|
-
// Wrap any raw error into RuntimeError with line info
|
|
368
425
|
if (err instanceof RuntimeError) throw err;
|
|
369
426
|
throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
|
|
370
427
|
}
|
|
@@ -375,7 +432,6 @@ async evalDoTrack(node, env) {
|
|
|
375
432
|
try {
|
|
376
433
|
return await this.evaluate(node.handler, trackEnv);
|
|
377
434
|
} catch (handlerErr) {
|
|
378
|
-
// Wrap handler errors as well
|
|
379
435
|
if (handlerErr instanceof RuntimeError) throw handlerErr;
|
|
380
436
|
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
|
|
381
437
|
}
|
|
@@ -475,16 +531,33 @@ evalArrowFunction(node, env) {
|
|
|
475
531
|
if (!node.body) {
|
|
476
532
|
throw new RuntimeError('Arrow function missing body', node, this.source);
|
|
477
533
|
}
|
|
534
|
+
|
|
478
535
|
if (!Array.isArray(node.params)) {
|
|
479
536
|
throw new RuntimeError('Invalid arrow function parameters', node, this.source);
|
|
480
537
|
}
|
|
481
538
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
539
|
+
const evaluator = this;
|
|
540
|
+
|
|
541
|
+
return async function (...args) {
|
|
542
|
+
const localEnv = new Environment(env);
|
|
543
|
+
|
|
544
|
+
node.params.forEach((p, i) => {
|
|
545
|
+
localEnv.define(p.name, args[i]);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
if (node.isBlock) {
|
|
550
|
+
const result = await evaluator.evaluate(node.body, localEnv);
|
|
551
|
+
return result ?? null;
|
|
552
|
+
} else {
|
|
553
|
+
return await evaluator.evaluate(node.body, localEnv);
|
|
554
|
+
}
|
|
555
|
+
} catch (err) {
|
|
556
|
+
if (err instanceof ReturnValue) {
|
|
557
|
+
return err.value;
|
|
558
|
+
}
|
|
559
|
+
throw err;
|
|
560
|
+
}
|
|
488
561
|
};
|
|
489
562
|
}
|
|
490
563
|
|
|
@@ -659,7 +732,6 @@ async evalWhile(node, env) {
|
|
|
659
732
|
return null;
|
|
660
733
|
}
|
|
661
734
|
async evalFor(node, env) {
|
|
662
|
-
// Python-style: for x in iterable (with optional 'let')
|
|
663
735
|
if (node.type === 'ForInStatement') {
|
|
664
736
|
const iterable = await this.evaluate(node.iterable, env);
|
|
665
737
|
|
|
@@ -669,8 +741,6 @@ async evalFor(node, env) {
|
|
|
669
741
|
|
|
670
742
|
const loopVar = node.variable; // string name of the loop variable
|
|
671
743
|
const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
|
|
672
|
-
|
|
673
|
-
// Arrays: iterate over elements
|
|
674
744
|
if (Array.isArray(iterable)) {
|
|
675
745
|
for (const value of iterable) {
|
|
676
746
|
const loopEnv = createLoopEnv();
|
|
@@ -685,7 +755,6 @@ async evalFor(node, env) {
|
|
|
685
755
|
}
|
|
686
756
|
}
|
|
687
757
|
}
|
|
688
|
-
// Objects: iterate over keys
|
|
689
758
|
else {
|
|
690
759
|
for (const key of Object.keys(iterable)) {
|
|
691
760
|
const loopEnv = createLoopEnv();
|
|
@@ -704,7 +773,6 @@ async evalFor(node, env) {
|
|
|
704
773
|
return null;
|
|
705
774
|
}
|
|
706
775
|
|
|
707
|
-
// C-style for loop (classic JS style)
|
|
708
776
|
const local = new Environment(env);
|
|
709
777
|
|
|
710
778
|
if (node.init) await this.evaluate(node.init, local);
|
|
@@ -790,19 +858,14 @@ async evalIndex(node, env) {
|
|
|
790
858
|
const idx = await this.evaluate(node.indexer, env);
|
|
791
859
|
|
|
792
860
|
if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
|
|
793
|
-
|
|
794
|
-
// Array access: return undefined if out of bounds
|
|
795
861
|
if (Array.isArray(obj)) {
|
|
796
862
|
if (idx < 0 || idx >= obj.length) return undefined;
|
|
797
863
|
return obj[idx];
|
|
798
864
|
}
|
|
799
|
-
|
|
800
|
-
// Object access: return undefined if property missing
|
|
801
865
|
if (typeof obj === 'object') {
|
|
802
866
|
return obj[idx]; // undefined if missing
|
|
803
867
|
}
|
|
804
868
|
|
|
805
|
-
// Fallback for non-object non-array
|
|
806
869
|
return undefined;
|
|
807
870
|
}
|
|
808
871
|
async evalObject(node, env) {
|
package/src/lexer.js
CHANGED
package/src/parser.js
CHANGED
|
@@ -73,6 +73,7 @@ class Parser {
|
|
|
73
73
|
case 'WHILE': return this.whileStatement();
|
|
74
74
|
case 'FOR': return this.forStatement();
|
|
75
75
|
case 'DO': return this.doTrackStatement();
|
|
76
|
+
case 'START': return this.startStatement();
|
|
76
77
|
case 'BREAK': return this.breakStatement();
|
|
77
78
|
case 'CONTINUE': return this.continueStatement();
|
|
78
79
|
case 'FUNC': return this.funcDeclaration();
|
|
@@ -84,9 +85,9 @@ class Parser {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
varDeclaration() {
|
|
87
|
-
const t = this.current;
|
|
88
|
+
const t = this.current;
|
|
88
89
|
this.eat('LET');
|
|
89
|
-
const idToken = this.current;
|
|
90
|
+
const idToken = this.current;
|
|
90
91
|
const id = idToken.value;
|
|
91
92
|
this.eat('IDENTIFIER');
|
|
92
93
|
|
|
@@ -96,7 +97,6 @@ varDeclaration() {
|
|
|
96
97
|
expr = this.expression();
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
// semicolon optional
|
|
100
100
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
101
101
|
|
|
102
102
|
return {
|
|
@@ -107,9 +107,49 @@ varDeclaration() {
|
|
|
107
107
|
column: t.column
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
+
startStatement() {
|
|
111
|
+
const t = this.current;
|
|
112
|
+
this.eat('START');
|
|
113
|
+
|
|
114
|
+
const discriminant = this.expression();
|
|
115
|
+
|
|
116
|
+
this.eat('LBRACE');
|
|
117
|
+
|
|
118
|
+
const cases = [];
|
|
119
|
+
|
|
120
|
+
while (this.current.type === 'RACE') {
|
|
121
|
+
cases.push(this.raceClause());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.eat('RBRACE');
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
type: 'StartStatement',
|
|
128
|
+
discriminant,
|
|
129
|
+
cases,
|
|
130
|
+
line: t.line,
|
|
131
|
+
column: t.column
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
raceClause() {
|
|
135
|
+
const t = this.current;
|
|
136
|
+
this.eat('RACE');
|
|
137
|
+
|
|
138
|
+
const test = this.expression();
|
|
139
|
+
|
|
140
|
+
const consequent = this.block();
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
type: 'RaceClause',
|
|
144
|
+
test,
|
|
145
|
+
consequent,
|
|
146
|
+
line: t.line,
|
|
147
|
+
column: t.column
|
|
148
|
+
};
|
|
149
|
+
}
|
|
110
150
|
|
|
111
151
|
sldeployStatement() {
|
|
112
|
-
const t = this.current;
|
|
152
|
+
const t = this.current;
|
|
113
153
|
this.eat('SLDEPLOY');
|
|
114
154
|
const expr = this.expression();
|
|
115
155
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
@@ -122,7 +162,7 @@ sldeployStatement() {
|
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
doTrackStatement() {
|
|
125
|
-
const t = this.current;
|
|
165
|
+
const t = this.current;
|
|
126
166
|
this.eat('DO');
|
|
127
167
|
|
|
128
168
|
const body = this.block();
|
|
@@ -143,9 +183,9 @@ doTrackStatement() {
|
|
|
143
183
|
}
|
|
144
184
|
|
|
145
185
|
defineStatement() {
|
|
146
|
-
const t = this.current;
|
|
186
|
+
const t = this.current;
|
|
147
187
|
this.eat('DEFINE');
|
|
148
|
-
const idToken = this.current;
|
|
188
|
+
const idToken = this.current;
|
|
149
189
|
const id = idToken.value;
|
|
150
190
|
this.eat('IDENTIFIER');
|
|
151
191
|
|
|
@@ -167,7 +207,7 @@ defineStatement() {
|
|
|
167
207
|
}
|
|
168
208
|
|
|
169
209
|
asyncFuncDeclaration() {
|
|
170
|
-
const t = this.current;
|
|
210
|
+
const t = this.current;
|
|
171
211
|
const name = this.current.value;
|
|
172
212
|
this.eat('IDENTIFIER');
|
|
173
213
|
|
|
@@ -185,7 +225,6 @@ asyncFuncDeclaration() {
|
|
|
185
225
|
}
|
|
186
226
|
this.eat('RPAREN');
|
|
187
227
|
} else {
|
|
188
|
-
// no parentheses: single param as Python style
|
|
189
228
|
if (this.current.type === 'IDENTIFIER') {
|
|
190
229
|
params.push(this.current.value);
|
|
191
230
|
this.eat('IDENTIFIER');
|
|
@@ -205,7 +244,7 @@ asyncFuncDeclaration() {
|
|
|
205
244
|
}
|
|
206
245
|
|
|
207
246
|
ifStatement() {
|
|
208
|
-
const t = this.current;
|
|
247
|
+
const t = this.current;
|
|
209
248
|
this.eat('IF');
|
|
210
249
|
|
|
211
250
|
let test;
|
|
@@ -214,7 +253,6 @@ ifStatement() {
|
|
|
214
253
|
test = this.expression();
|
|
215
254
|
this.eat('RPAREN');
|
|
216
255
|
} else {
|
|
217
|
-
// Python style: no parentheses
|
|
218
256
|
test = this.expression();
|
|
219
257
|
}
|
|
220
258
|
|
|
@@ -238,7 +276,7 @@ ifStatement() {
|
|
|
238
276
|
}
|
|
239
277
|
|
|
240
278
|
whileStatement() {
|
|
241
|
-
const t = this.current;
|
|
279
|
+
const t = this.current;
|
|
242
280
|
this.eat('WHILE');
|
|
243
281
|
|
|
244
282
|
let test;
|
|
@@ -261,7 +299,7 @@ whileStatement() {
|
|
|
261
299
|
}
|
|
262
300
|
|
|
263
301
|
importStatement() {
|
|
264
|
-
const t = this.current;
|
|
302
|
+
const t = this.current;
|
|
265
303
|
this.eat('IMPORT');
|
|
266
304
|
|
|
267
305
|
let specifiers = [];
|
|
@@ -320,7 +358,6 @@ forStatement() {
|
|
|
320
358
|
const t = this.current; // FOR token
|
|
321
359
|
this.eat('FOR');
|
|
322
360
|
|
|
323
|
-
// --- Python-style: for variable in iterable (supports optional 'let') ---
|
|
324
361
|
if ((this.current.type === 'LET' && this.peekType() === 'IDENTIFIER' && this.peekType(2) === 'IN') ||
|
|
325
362
|
(this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
|
|
326
363
|
|
|
@@ -361,7 +398,6 @@ forStatement() {
|
|
|
361
398
|
};
|
|
362
399
|
}
|
|
363
400
|
|
|
364
|
-
// --- C-style: for(init; test; update) ---
|
|
365
401
|
let init = null;
|
|
366
402
|
let test = null;
|
|
367
403
|
let update = null;
|
|
@@ -369,22 +405,18 @@ forStatement() {
|
|
|
369
405
|
if (this.current.type === 'LPAREN') {
|
|
370
406
|
this.eat('LPAREN');
|
|
371
407
|
|
|
372
|
-
// init
|
|
373
408
|
if (this.current.type !== 'SEMICOLON') {
|
|
374
409
|
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
375
410
|
} else {
|
|
376
411
|
this.eat('SEMICOLON');
|
|
377
412
|
}
|
|
378
413
|
|
|
379
|
-
// test
|
|
380
414
|
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
381
415
|
this.eat('SEMICOLON');
|
|
382
416
|
|
|
383
|
-
// update
|
|
384
417
|
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
385
418
|
this.eat('RPAREN');
|
|
386
419
|
} else {
|
|
387
|
-
// fallback: single expression (rare, mostly handled above)
|
|
388
420
|
init = this.expression();
|
|
389
421
|
if (this.current.type === 'IN') {
|
|
390
422
|
this.eat('IN');
|
|
@@ -407,21 +439,19 @@ forStatement() {
|
|
|
407
439
|
breakStatement() {
|
|
408
440
|
const t = this.current; // BREAK token
|
|
409
441
|
this.eat('BREAK');
|
|
410
|
-
// Python-style: no semicolon needed, ignore if present
|
|
411
442
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
412
443
|
return { type: 'BreakStatement', line: t.line, column: t.column };
|
|
413
444
|
}
|
|
414
445
|
|
|
415
446
|
continueStatement() {
|
|
416
|
-
const t = this.current;
|
|
447
|
+
const t = this.current;
|
|
417
448
|
this.eat('CONTINUE');
|
|
418
|
-
// Python-style: no semicolon needed, ignore if present
|
|
419
449
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
420
450
|
return { type: 'ContinueStatement', line: t.line, column: t.column };
|
|
421
451
|
}
|
|
422
452
|
|
|
423
453
|
funcDeclaration() {
|
|
424
|
-
const t = this.current;
|
|
454
|
+
const t = this.current;
|
|
425
455
|
this.eat('FUNC');
|
|
426
456
|
const nameToken = this.current;
|
|
427
457
|
const name = nameToken.value;
|
|
@@ -443,7 +473,6 @@ funcDeclaration() {
|
|
|
443
473
|
}
|
|
444
474
|
this.eat('RPAREN');
|
|
445
475
|
} else {
|
|
446
|
-
// Python-style: single param without parentheses
|
|
447
476
|
if (this.current.type === 'IDENTIFIER') {
|
|
448
477
|
const paramToken = this.current;
|
|
449
478
|
params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
|
|
@@ -464,7 +493,6 @@ returnStatement() {
|
|
|
464
493
|
argument = this.expression();
|
|
465
494
|
}
|
|
466
495
|
|
|
467
|
-
// semicolon optional
|
|
468
496
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
469
497
|
|
|
470
498
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
@@ -482,9 +510,8 @@ block() {
|
|
|
482
510
|
}
|
|
483
511
|
|
|
484
512
|
expressionStatement() {
|
|
485
|
-
const exprToken = this.current;
|
|
513
|
+
const exprToken = this.current;
|
|
486
514
|
const expr = this.expression();
|
|
487
|
-
// semicolon optional
|
|
488
515
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
489
516
|
return { type: 'ExpressionStatement', expression: expr, line: exprToken.line, column: exprToken.column };
|
|
490
517
|
}
|
|
@@ -597,7 +624,6 @@ unary() {
|
|
|
597
624
|
};
|
|
598
625
|
}
|
|
599
626
|
|
|
600
|
-
// Python-like: ignore ++ and -- if not used
|
|
601
627
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
602
628
|
const op = t.type;
|
|
603
629
|
this.eat(op);
|
|
@@ -637,7 +663,7 @@ postfix() {
|
|
|
637
663
|
const args = [];
|
|
638
664
|
while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
|
|
639
665
|
args.push(this.expression());
|
|
640
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
666
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
641
667
|
}
|
|
642
668
|
if (this.current.type === 'RPAREN') this.eat('RPAREN');
|
|
643
669
|
node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
|
|
@@ -674,15 +700,26 @@ postfix() {
|
|
|
674
700
|
|
|
675
701
|
arrowFunction(params) {
|
|
676
702
|
const t = this.current;
|
|
677
|
-
|
|
678
|
-
|
|
703
|
+
this.eat('ARROW');
|
|
704
|
+
|
|
705
|
+
let body;
|
|
706
|
+
let isBlock = false;
|
|
707
|
+
|
|
708
|
+
if (this.current.type === 'LBRACE') {
|
|
709
|
+
body = this.block();
|
|
710
|
+
isBlock = true;
|
|
711
|
+
} else {
|
|
712
|
+
body = this.expression();
|
|
713
|
+
}
|
|
714
|
+
|
|
679
715
|
const startLine = params.length > 0 ? params[0].line : t.line;
|
|
680
|
-
const startCol
|
|
716
|
+
const startCol = params.length > 0 ? params[0].column : t.column;
|
|
681
717
|
|
|
682
718
|
return {
|
|
683
719
|
type: 'ArrowFunctionExpression',
|
|
684
720
|
params,
|
|
685
721
|
body,
|
|
722
|
+
isBlock,
|
|
686
723
|
line: startLine,
|
|
687
724
|
column: startCol
|
|
688
725
|
};
|
|
@@ -814,11 +851,11 @@ arrowFunction(params) {
|
|
|
814
851
|
this.eat('LBRACE');
|
|
815
852
|
const props = [];
|
|
816
853
|
while (this.current.type !== 'RBRACE') {
|
|
817
|
-
const key = this.expression();
|
|
854
|
+
const key = this.expression();
|
|
818
855
|
this.eat('COLON');
|
|
819
856
|
const value = this.expression();
|
|
820
857
|
props.push({ key, value });
|
|
821
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
858
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
822
859
|
}
|
|
823
860
|
this.eat('RBRACE');
|
|
824
861
|
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|