starlight-cli 1.1.2 → 1.1.3
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 +46 -0
- package/dist/index.js +133 -61
- package/package.json +1 -1
- package/src/evaluator.js +74 -28
- package/src/lexer.js +1 -1
- package/src/parser.js +57 -31
- package/src/starlight.js +1 -1
package/README.md
CHANGED
|
@@ -237,7 +237,53 @@ server()
|
|
|
237
237
|
```
|
|
238
238
|
|
|
239
239
|
---
|
|
240
|
+
## Start & Race Statements
|
|
240
241
|
|
|
242
|
+
Starlight includes a **`start` / `race`** control structure similar to JavaScript `switch`, with **fall-through behavior**. The `start` statement evaluates a discriminant expression and executes all `race` clauses **starting from the first match** until the end, unless a `break` is used.
|
|
243
|
+
|
|
244
|
+
### Syntax
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
start (<discriminant>) {
|
|
248
|
+
race (<value>) {
|
|
249
|
+
# body
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
race (<value>) {
|
|
253
|
+
# body
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
- `start` – evaluates the discriminant expression.
|
|
261
|
+
- `race` – each clause is checked; execution starts at the first matching `race` and continues to subsequent races (fall-through).
|
|
262
|
+
- Optional `break` can stop execution early (if implemented).
|
|
263
|
+
|
|
264
|
+
### Example
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
define x = 2;
|
|
268
|
+
|
|
269
|
+
start (x) {
|
|
270
|
+
race (1) { sldeploy("Race 1 executed"); }
|
|
271
|
+
race (2) { sldeploy("Race 2 executed"); }
|
|
272
|
+
race (3) { sldeploy("Race 3 executed"); }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
sldeploy("Done with start statement");
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Expected Output:**
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
Race 2 executed
|
|
283
|
+
Race 3 executed
|
|
284
|
+
Done with start statement
|
|
285
|
+
|
|
286
|
+
```
|
|
241
287
|
## Why Starlight?
|
|
242
288
|
|
|
243
289
|
Starlight is designed for developers who want:
|
package/dist/index.js
CHANGED
|
@@ -1392,7 +1392,6 @@ class Environment {
|
|
|
1392
1392
|
if (name in this.store) return this.store[name];
|
|
1393
1393
|
if (this.parent) return this.parent.get(name, node, source);
|
|
1394
1394
|
|
|
1395
|
-
// Only suggest for top-level variable/function names
|
|
1396
1395
|
let suggestion = null;
|
|
1397
1396
|
if (node && source) {
|
|
1398
1397
|
suggestion = this.suggest?.(name, this) || null;
|
|
@@ -1427,7 +1426,6 @@ class Evaluator {
|
|
|
1427
1426
|
this.setupBuiltins();
|
|
1428
1427
|
}
|
|
1429
1428
|
suggest(name, env) {
|
|
1430
|
-
// Collect all variable/function names from the environment chain
|
|
1431
1429
|
const names = new Set();
|
|
1432
1430
|
let current = env;
|
|
1433
1431
|
while (current) {
|
|
@@ -1441,11 +1439,10 @@ suggest(name, env) {
|
|
|
1441
1439
|
let bestScore = Infinity;
|
|
1442
1440
|
|
|
1443
1441
|
for (const item of names) {
|
|
1444
|
-
// simple edit distance approximation
|
|
1445
1442
|
const dist = Math.abs(item.length - name.length) +
|
|
1446
1443
|
[...name].filter((c, i) => c !== item[i]).length;
|
|
1447
1444
|
|
|
1448
|
-
if (dist < bestScore && dist <= 2) {
|
|
1445
|
+
if (dist < bestScore && dist <= 2) {
|
|
1449
1446
|
bestScore = dist;
|
|
1450
1447
|
best = item;
|
|
1451
1448
|
}
|
|
@@ -1457,39 +1454,31 @@ suggest(name, env) {
|
|
|
1457
1454
|
formatValue(value, seen = new Set()) {
|
|
1458
1455
|
const color = __nccwpck_require__(55);
|
|
1459
1456
|
|
|
1460
|
-
// Handle circular references
|
|
1461
1457
|
if (typeof value === 'object' && value !== null) {
|
|
1462
1458
|
if (seen.has(value)) return color.red('[Circular]');
|
|
1463
1459
|
seen.add(value);
|
|
1464
1460
|
}
|
|
1465
1461
|
|
|
1466
|
-
// Python-style null / undefined
|
|
1467
1462
|
if (value === null) return color.yellow('None');
|
|
1468
1463
|
if (value === undefined) return color.yellow('undefined');
|
|
1469
1464
|
|
|
1470
1465
|
const t = typeof value;
|
|
1471
1466
|
|
|
1472
|
-
// Strings (no quotes)
|
|
1473
1467
|
if (t === 'string') return color.cyan(value);
|
|
1474
1468
|
|
|
1475
|
-
// Numbers
|
|
1476
1469
|
if (t === 'number') return color.green(String(value));
|
|
1477
1470
|
|
|
1478
|
-
// Booleans
|
|
1479
1471
|
if (t === 'boolean') return color.yellow(value ? 'true' : 'false');
|
|
1480
1472
|
|
|
1481
|
-
// Native JS functions
|
|
1482
1473
|
if (t === 'function') {
|
|
1483
1474
|
return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
|
|
1484
1475
|
}
|
|
1485
1476
|
|
|
1486
|
-
// Arrays
|
|
1487
1477
|
if (Array.isArray(value)) {
|
|
1488
1478
|
const items = value.map(v => this.formatValue(v, seen));
|
|
1489
1479
|
return color.white('[ ' + items.join(', ') + ' ]');
|
|
1490
1480
|
}
|
|
1491
1481
|
|
|
1492
|
-
// Objects (including user-defined functions)
|
|
1493
1482
|
if (t === 'object') {
|
|
1494
1483
|
if (value.params && value.body) {
|
|
1495
1484
|
return color.magenta(value.name ? `<function ${value.name}>` : '<function>');
|
|
@@ -1501,7 +1490,6 @@ formatValue(value, seen = new Set()) {
|
|
|
1501
1490
|
return color.magenta('{ ') + entries.join(color.magenta(', ')) + color.magenta(' }');
|
|
1502
1491
|
}
|
|
1503
1492
|
|
|
1504
|
-
// Fallback
|
|
1505
1493
|
try {
|
|
1506
1494
|
return String(value);
|
|
1507
1495
|
} catch {
|
|
@@ -1581,7 +1569,6 @@ this.global.define('fetch', async (url, options = {}) => {
|
|
|
1581
1569
|
};
|
|
1582
1570
|
});
|
|
1583
1571
|
|
|
1584
|
-
// GET request
|
|
1585
1572
|
this.global.define('get', async (url) => {
|
|
1586
1573
|
const res = await fetch(url);
|
|
1587
1574
|
return await res.json();
|
|
@@ -1628,6 +1615,11 @@ case 'ForInStatement':
|
|
|
1628
1615
|
return await this.evalFor(node, env);
|
|
1629
1616
|
case 'DoTrackStatement':
|
|
1630
1617
|
return await this.evalDoTrack(node, env);
|
|
1618
|
+
case 'StartStatement':
|
|
1619
|
+
return await this.evalStartStatement(node, env);
|
|
1620
|
+
|
|
1621
|
+
case 'RaceClause':
|
|
1622
|
+
return await this.evalRaceClause(node, env);
|
|
1631
1623
|
|
|
1632
1624
|
case 'BreakStatement': throw new BreakSignal();
|
|
1633
1625
|
case 'ContinueStatement': throw new ContinueSignal();
|
|
@@ -1656,14 +1648,14 @@ case 'DoTrackStatement':
|
|
|
1656
1648
|
const callee = await this.evaluate(node.callee, env);
|
|
1657
1649
|
|
|
1658
1650
|
if (typeof callee === 'object' && callee.body) {
|
|
1659
|
-
const evaluator = this;
|
|
1651
|
+
const evaluator = this;
|
|
1660
1652
|
const Constructor = function(...args) {
|
|
1661
1653
|
const newEnv = new Environment(callee.env);
|
|
1662
1654
|
newEnv.define('this', this);
|
|
1663
1655
|
for (let i = 0; i < callee.params.length; i++) {
|
|
1664
1656
|
newEnv.define(callee.params[i], args[i]);
|
|
1665
1657
|
}
|
|
1666
|
-
return evaluator.evaluate(callee.body, newEnv);
|
|
1658
|
+
return evaluator.evaluate(callee.body, newEnv);
|
|
1667
1659
|
};
|
|
1668
1660
|
|
|
1669
1661
|
const args = [];
|
|
@@ -1701,13 +1693,78 @@ async evalProgram(node, env) {
|
|
|
1701
1693
|
return result;
|
|
1702
1694
|
}
|
|
1703
1695
|
|
|
1696
|
+
async evalStartStatement(node, env) {
|
|
1697
|
+
try {
|
|
1698
|
+
const value = await this.evaluate(node.discriminant, env);
|
|
1699
|
+
let executing = false;
|
|
1700
|
+
for (const c of node.cases) {
|
|
1701
|
+
try {
|
|
1702
|
+
if (!executing) {
|
|
1703
|
+
const testValue = await this.evaluate(c.test, env);
|
|
1704
|
+
if (testValue === value) executing = true;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
if (executing) {
|
|
1708
|
+
await this.evaluate(c.consequent, new Environment(env));
|
|
1709
|
+
}
|
|
1710
|
+
} catch (caseErr) {
|
|
1711
|
+
if (caseErr instanceof BreakSignal) break;
|
|
1712
|
+
if (caseErr instanceof RuntimeError ||
|
|
1713
|
+
caseErr instanceof ReturnValue ||
|
|
1714
|
+
caseErr instanceof ContinueSignal) {
|
|
1715
|
+
throw caseErr; // propagate signals
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
throw new RuntimeError(
|
|
1719
|
+
caseErr.message || 'Error evaluating case in start statement',
|
|
1720
|
+
c,
|
|
1721
|
+
this.source
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
return null;
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
if (err instanceof RuntimeError ||
|
|
1729
|
+
err instanceof ReturnValue ||
|
|
1730
|
+
err instanceof BreakSignal ||
|
|
1731
|
+
err instanceof ContinueSignal) {
|
|
1732
|
+
throw err;
|
|
1733
|
+
}
|
|
1734
|
+
throw new RuntimeError(
|
|
1735
|
+
err.message || 'Error evaluating start statement',
|
|
1736
|
+
node,
|
|
1737
|
+
this.source
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
async evalRaceClause(node, env) {
|
|
1744
|
+
try {
|
|
1745
|
+
const testValue = await this.evaluate(node.test, env);
|
|
1746
|
+
const result = await this.evaluate(node.consequent, new Environment(env));
|
|
1747
|
+
return { testValue, result };
|
|
1748
|
+
} catch (err) {
|
|
1749
|
+
if (err instanceof RuntimeError ||
|
|
1750
|
+
err instanceof ReturnValue ||
|
|
1751
|
+
err instanceof BreakSignal ||
|
|
1752
|
+
err instanceof ContinueSignal) {
|
|
1753
|
+
throw err;
|
|
1754
|
+
}
|
|
1755
|
+
throw new RuntimeError(
|
|
1756
|
+
err.message || 'Error evaluating race clause',
|
|
1757
|
+
node,
|
|
1758
|
+
this.source
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1704
1762
|
|
|
1705
1763
|
async evalDoTrack(node, env) {
|
|
1706
1764
|
try {
|
|
1707
1765
|
return await this.evaluate(node.body, env);
|
|
1708
1766
|
} catch (err) {
|
|
1709
1767
|
if (!node.handler) {
|
|
1710
|
-
// Wrap any raw error into RuntimeError with line info
|
|
1711
1768
|
if (err instanceof RuntimeError) throw err;
|
|
1712
1769
|
throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
|
|
1713
1770
|
}
|
|
@@ -1718,7 +1775,6 @@ async evalDoTrack(node, env) {
|
|
|
1718
1775
|
try {
|
|
1719
1776
|
return await this.evaluate(node.handler, trackEnv);
|
|
1720
1777
|
} catch (handlerErr) {
|
|
1721
|
-
// Wrap handler errors as well
|
|
1722
1778
|
if (handlerErr instanceof RuntimeError) throw handlerErr;
|
|
1723
1779
|
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
|
|
1724
1780
|
}
|
|
@@ -2002,7 +2058,6 @@ async evalWhile(node, env) {
|
|
|
2002
2058
|
return null;
|
|
2003
2059
|
}
|
|
2004
2060
|
async evalFor(node, env) {
|
|
2005
|
-
// Python-style: for x in iterable (with optional 'let')
|
|
2006
2061
|
if (node.type === 'ForInStatement') {
|
|
2007
2062
|
const iterable = await this.evaluate(node.iterable, env);
|
|
2008
2063
|
|
|
@@ -2012,8 +2067,6 @@ async evalFor(node, env) {
|
|
|
2012
2067
|
|
|
2013
2068
|
const loopVar = node.variable; // string name of the loop variable
|
|
2014
2069
|
const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
|
|
2015
|
-
|
|
2016
|
-
// Arrays: iterate over elements
|
|
2017
2070
|
if (Array.isArray(iterable)) {
|
|
2018
2071
|
for (const value of iterable) {
|
|
2019
2072
|
const loopEnv = createLoopEnv();
|
|
@@ -2028,7 +2081,6 @@ async evalFor(node, env) {
|
|
|
2028
2081
|
}
|
|
2029
2082
|
}
|
|
2030
2083
|
}
|
|
2031
|
-
// Objects: iterate over keys
|
|
2032
2084
|
else {
|
|
2033
2085
|
for (const key of Object.keys(iterable)) {
|
|
2034
2086
|
const loopEnv = createLoopEnv();
|
|
@@ -2047,7 +2099,6 @@ async evalFor(node, env) {
|
|
|
2047
2099
|
return null;
|
|
2048
2100
|
}
|
|
2049
2101
|
|
|
2050
|
-
// C-style for loop (classic JS style)
|
|
2051
2102
|
const local = new Environment(env);
|
|
2052
2103
|
|
|
2053
2104
|
if (node.init) await this.evaluate(node.init, local);
|
|
@@ -2133,19 +2184,14 @@ async evalIndex(node, env) {
|
|
|
2133
2184
|
const idx = await this.evaluate(node.indexer, env);
|
|
2134
2185
|
|
|
2135
2186
|
if (obj == null) throw new RuntimeError('Cannot index null or undefined', node, this.source);
|
|
2136
|
-
|
|
2137
|
-
// Array access: return undefined if out of bounds
|
|
2138
2187
|
if (Array.isArray(obj)) {
|
|
2139
2188
|
if (idx < 0 || idx >= obj.length) return undefined;
|
|
2140
2189
|
return obj[idx];
|
|
2141
2190
|
}
|
|
2142
|
-
|
|
2143
|
-
// Object access: return undefined if property missing
|
|
2144
2191
|
if (typeof obj === 'object') {
|
|
2145
2192
|
return obj[idx]; // undefined if missing
|
|
2146
2193
|
}
|
|
2147
2194
|
|
|
2148
|
-
// Fallback for non-object non-array
|
|
2149
2195
|
return undefined;
|
|
2150
2196
|
}
|
|
2151
2197
|
async evalObject(node, env) {
|
|
@@ -2239,7 +2285,7 @@ class Lexer {
|
|
|
2239
2285
|
'break', 'continue', 'func', 'return',
|
|
2240
2286
|
'true', 'false', 'null',
|
|
2241
2287
|
'ask', 'define', 'import', 'from', 'as',
|
|
2242
|
-
'async', 'await', 'new', 'in', 'do', 'track'
|
|
2288
|
+
'async', 'await', 'new', 'in', 'do', 'track', 'start', 'race'
|
|
2243
2289
|
];
|
|
2244
2290
|
}
|
|
2245
2291
|
|
|
@@ -2502,6 +2548,7 @@ class Parser {
|
|
|
2502
2548
|
case 'WHILE': return this.whileStatement();
|
|
2503
2549
|
case 'FOR': return this.forStatement();
|
|
2504
2550
|
case 'DO': return this.doTrackStatement();
|
|
2551
|
+
case 'START': return this.startStatement();
|
|
2505
2552
|
case 'BREAK': return this.breakStatement();
|
|
2506
2553
|
case 'CONTINUE': return this.continueStatement();
|
|
2507
2554
|
case 'FUNC': return this.funcDeclaration();
|
|
@@ -2513,9 +2560,9 @@ class Parser {
|
|
|
2513
2560
|
}
|
|
2514
2561
|
}
|
|
2515
2562
|
varDeclaration() {
|
|
2516
|
-
const t = this.current;
|
|
2563
|
+
const t = this.current;
|
|
2517
2564
|
this.eat('LET');
|
|
2518
|
-
const idToken = this.current;
|
|
2565
|
+
const idToken = this.current;
|
|
2519
2566
|
const id = idToken.value;
|
|
2520
2567
|
this.eat('IDENTIFIER');
|
|
2521
2568
|
|
|
@@ -2525,7 +2572,6 @@ varDeclaration() {
|
|
|
2525
2572
|
expr = this.expression();
|
|
2526
2573
|
}
|
|
2527
2574
|
|
|
2528
|
-
// semicolon optional
|
|
2529
2575
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2530
2576
|
|
|
2531
2577
|
return {
|
|
@@ -2536,9 +2582,49 @@ varDeclaration() {
|
|
|
2536
2582
|
column: t.column
|
|
2537
2583
|
};
|
|
2538
2584
|
}
|
|
2585
|
+
startStatement() {
|
|
2586
|
+
const t = this.current;
|
|
2587
|
+
this.eat('START');
|
|
2588
|
+
|
|
2589
|
+
const discriminant = this.expression();
|
|
2590
|
+
|
|
2591
|
+
this.eat('LBRACE');
|
|
2592
|
+
|
|
2593
|
+
const cases = [];
|
|
2594
|
+
|
|
2595
|
+
while (this.current.type === 'RACE') {
|
|
2596
|
+
cases.push(this.raceClause());
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
this.eat('RBRACE');
|
|
2600
|
+
|
|
2601
|
+
return {
|
|
2602
|
+
type: 'StartStatement',
|
|
2603
|
+
discriminant,
|
|
2604
|
+
cases,
|
|
2605
|
+
line: t.line,
|
|
2606
|
+
column: t.column
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
raceClause() {
|
|
2610
|
+
const t = this.current;
|
|
2611
|
+
this.eat('RACE');
|
|
2612
|
+
|
|
2613
|
+
const test = this.expression();
|
|
2614
|
+
|
|
2615
|
+
const consequent = this.block();
|
|
2616
|
+
|
|
2617
|
+
return {
|
|
2618
|
+
type: 'RaceClause',
|
|
2619
|
+
test,
|
|
2620
|
+
consequent,
|
|
2621
|
+
line: t.line,
|
|
2622
|
+
column: t.column
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2539
2625
|
|
|
2540
2626
|
sldeployStatement() {
|
|
2541
|
-
const t = this.current;
|
|
2627
|
+
const t = this.current;
|
|
2542
2628
|
this.eat('SLDEPLOY');
|
|
2543
2629
|
const expr = this.expression();
|
|
2544
2630
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
@@ -2551,7 +2637,7 @@ sldeployStatement() {
|
|
|
2551
2637
|
}
|
|
2552
2638
|
|
|
2553
2639
|
doTrackStatement() {
|
|
2554
|
-
const t = this.current;
|
|
2640
|
+
const t = this.current;
|
|
2555
2641
|
this.eat('DO');
|
|
2556
2642
|
|
|
2557
2643
|
const body = this.block();
|
|
@@ -2572,9 +2658,9 @@ doTrackStatement() {
|
|
|
2572
2658
|
}
|
|
2573
2659
|
|
|
2574
2660
|
defineStatement() {
|
|
2575
|
-
const t = this.current;
|
|
2661
|
+
const t = this.current;
|
|
2576
2662
|
this.eat('DEFINE');
|
|
2577
|
-
const idToken = this.current;
|
|
2663
|
+
const idToken = this.current;
|
|
2578
2664
|
const id = idToken.value;
|
|
2579
2665
|
this.eat('IDENTIFIER');
|
|
2580
2666
|
|
|
@@ -2596,7 +2682,7 @@ defineStatement() {
|
|
|
2596
2682
|
}
|
|
2597
2683
|
|
|
2598
2684
|
asyncFuncDeclaration() {
|
|
2599
|
-
const t = this.current;
|
|
2685
|
+
const t = this.current;
|
|
2600
2686
|
const name = this.current.value;
|
|
2601
2687
|
this.eat('IDENTIFIER');
|
|
2602
2688
|
|
|
@@ -2614,7 +2700,6 @@ asyncFuncDeclaration() {
|
|
|
2614
2700
|
}
|
|
2615
2701
|
this.eat('RPAREN');
|
|
2616
2702
|
} else {
|
|
2617
|
-
// no parentheses: single param as Python style
|
|
2618
2703
|
if (this.current.type === 'IDENTIFIER') {
|
|
2619
2704
|
params.push(this.current.value);
|
|
2620
2705
|
this.eat('IDENTIFIER');
|
|
@@ -2634,7 +2719,7 @@ asyncFuncDeclaration() {
|
|
|
2634
2719
|
}
|
|
2635
2720
|
|
|
2636
2721
|
ifStatement() {
|
|
2637
|
-
const t = this.current;
|
|
2722
|
+
const t = this.current;
|
|
2638
2723
|
this.eat('IF');
|
|
2639
2724
|
|
|
2640
2725
|
let test;
|
|
@@ -2643,7 +2728,6 @@ ifStatement() {
|
|
|
2643
2728
|
test = this.expression();
|
|
2644
2729
|
this.eat('RPAREN');
|
|
2645
2730
|
} else {
|
|
2646
|
-
// Python style: no parentheses
|
|
2647
2731
|
test = this.expression();
|
|
2648
2732
|
}
|
|
2649
2733
|
|
|
@@ -2667,7 +2751,7 @@ ifStatement() {
|
|
|
2667
2751
|
}
|
|
2668
2752
|
|
|
2669
2753
|
whileStatement() {
|
|
2670
|
-
const t = this.current;
|
|
2754
|
+
const t = this.current;
|
|
2671
2755
|
this.eat('WHILE');
|
|
2672
2756
|
|
|
2673
2757
|
let test;
|
|
@@ -2690,7 +2774,7 @@ whileStatement() {
|
|
|
2690
2774
|
}
|
|
2691
2775
|
|
|
2692
2776
|
importStatement() {
|
|
2693
|
-
const t = this.current;
|
|
2777
|
+
const t = this.current;
|
|
2694
2778
|
this.eat('IMPORT');
|
|
2695
2779
|
|
|
2696
2780
|
let specifiers = [];
|
|
@@ -2749,7 +2833,6 @@ forStatement() {
|
|
|
2749
2833
|
const t = this.current; // FOR token
|
|
2750
2834
|
this.eat('FOR');
|
|
2751
2835
|
|
|
2752
|
-
// --- Python-style: for variable in iterable (supports optional 'let') ---
|
|
2753
2836
|
if ((this.current.type === 'LET' && this.peekType() === 'IDENTIFIER' && this.peekType(2) === 'IN') ||
|
|
2754
2837
|
(this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
|
|
2755
2838
|
|
|
@@ -2790,7 +2873,6 @@ forStatement() {
|
|
|
2790
2873
|
};
|
|
2791
2874
|
}
|
|
2792
2875
|
|
|
2793
|
-
// --- C-style: for(init; test; update) ---
|
|
2794
2876
|
let init = null;
|
|
2795
2877
|
let test = null;
|
|
2796
2878
|
let update = null;
|
|
@@ -2798,22 +2880,18 @@ forStatement() {
|
|
|
2798
2880
|
if (this.current.type === 'LPAREN') {
|
|
2799
2881
|
this.eat('LPAREN');
|
|
2800
2882
|
|
|
2801
|
-
// init
|
|
2802
2883
|
if (this.current.type !== 'SEMICOLON') {
|
|
2803
2884
|
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2804
2885
|
} else {
|
|
2805
2886
|
this.eat('SEMICOLON');
|
|
2806
2887
|
}
|
|
2807
2888
|
|
|
2808
|
-
// test
|
|
2809
2889
|
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
2810
2890
|
this.eat('SEMICOLON');
|
|
2811
2891
|
|
|
2812
|
-
// update
|
|
2813
2892
|
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
2814
2893
|
this.eat('RPAREN');
|
|
2815
2894
|
} else {
|
|
2816
|
-
// fallback: single expression (rare, mostly handled above)
|
|
2817
2895
|
init = this.expression();
|
|
2818
2896
|
if (this.current.type === 'IN') {
|
|
2819
2897
|
this.eat('IN');
|
|
@@ -2836,21 +2914,19 @@ forStatement() {
|
|
|
2836
2914
|
breakStatement() {
|
|
2837
2915
|
const t = this.current; // BREAK token
|
|
2838
2916
|
this.eat('BREAK');
|
|
2839
|
-
// Python-style: no semicolon needed, ignore if present
|
|
2840
2917
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2841
2918
|
return { type: 'BreakStatement', line: t.line, column: t.column };
|
|
2842
2919
|
}
|
|
2843
2920
|
|
|
2844
2921
|
continueStatement() {
|
|
2845
|
-
const t = this.current;
|
|
2922
|
+
const t = this.current;
|
|
2846
2923
|
this.eat('CONTINUE');
|
|
2847
|
-
// Python-style: no semicolon needed, ignore if present
|
|
2848
2924
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2849
2925
|
return { type: 'ContinueStatement', line: t.line, column: t.column };
|
|
2850
2926
|
}
|
|
2851
2927
|
|
|
2852
2928
|
funcDeclaration() {
|
|
2853
|
-
const t = this.current;
|
|
2929
|
+
const t = this.current;
|
|
2854
2930
|
this.eat('FUNC');
|
|
2855
2931
|
const nameToken = this.current;
|
|
2856
2932
|
const name = nameToken.value;
|
|
@@ -2872,7 +2948,6 @@ funcDeclaration() {
|
|
|
2872
2948
|
}
|
|
2873
2949
|
this.eat('RPAREN');
|
|
2874
2950
|
} else {
|
|
2875
|
-
// Python-style: single param without parentheses
|
|
2876
2951
|
if (this.current.type === 'IDENTIFIER') {
|
|
2877
2952
|
const paramToken = this.current;
|
|
2878
2953
|
params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
|
|
@@ -2893,7 +2968,6 @@ returnStatement() {
|
|
|
2893
2968
|
argument = this.expression();
|
|
2894
2969
|
}
|
|
2895
2970
|
|
|
2896
|
-
// semicolon optional
|
|
2897
2971
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2898
2972
|
|
|
2899
2973
|
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
@@ -2911,9 +2985,8 @@ block() {
|
|
|
2911
2985
|
}
|
|
2912
2986
|
|
|
2913
2987
|
expressionStatement() {
|
|
2914
|
-
const exprToken = this.current;
|
|
2988
|
+
const exprToken = this.current;
|
|
2915
2989
|
const expr = this.expression();
|
|
2916
|
-
// semicolon optional
|
|
2917
2990
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2918
2991
|
return { type: 'ExpressionStatement', expression: expr, line: exprToken.line, column: exprToken.column };
|
|
2919
2992
|
}
|
|
@@ -3026,7 +3099,6 @@ unary() {
|
|
|
3026
3099
|
};
|
|
3027
3100
|
}
|
|
3028
3101
|
|
|
3029
|
-
// Python-like: ignore ++ and -- if not used
|
|
3030
3102
|
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
3031
3103
|
const op = t.type;
|
|
3032
3104
|
this.eat(op);
|
|
@@ -3066,7 +3138,7 @@ postfix() {
|
|
|
3066
3138
|
const args = [];
|
|
3067
3139
|
while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
|
|
3068
3140
|
args.push(this.expression());
|
|
3069
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
3141
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
3070
3142
|
}
|
|
3071
3143
|
if (this.current.type === 'RPAREN') this.eat('RPAREN');
|
|
3072
3144
|
node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
|
|
@@ -3243,11 +3315,11 @@ arrowFunction(params) {
|
|
|
3243
3315
|
this.eat('LBRACE');
|
|
3244
3316
|
const props = [];
|
|
3245
3317
|
while (this.current.type !== 'RBRACE') {
|
|
3246
|
-
const key = this.expression();
|
|
3318
|
+
const key = this.expression();
|
|
3247
3319
|
this.eat('COLON');
|
|
3248
3320
|
const value = this.expression();
|
|
3249
3321
|
props.push({ key, value });
|
|
3250
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
3322
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
3251
3323
|
}
|
|
3252
3324
|
this.eat('RBRACE');
|
|
3253
3325
|
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
@@ -3440,7 +3512,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
3440
3512
|
const Parser = __nccwpck_require__(222);
|
|
3441
3513
|
const Evaluator = __nccwpck_require__(112);
|
|
3442
3514
|
|
|
3443
|
-
const VERSION = '1.1.
|
|
3515
|
+
const VERSION = '1.1.3';
|
|
3444
3516
|
|
|
3445
3517
|
const COLOR = {
|
|
3446
3518
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
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
|
}
|
|
@@ -659,7 +715,6 @@ async evalWhile(node, env) {
|
|
|
659
715
|
return null;
|
|
660
716
|
}
|
|
661
717
|
async evalFor(node, env) {
|
|
662
|
-
// Python-style: for x in iterable (with optional 'let')
|
|
663
718
|
if (node.type === 'ForInStatement') {
|
|
664
719
|
const iterable = await this.evaluate(node.iterable, env);
|
|
665
720
|
|
|
@@ -669,8 +724,6 @@ async evalFor(node, env) {
|
|
|
669
724
|
|
|
670
725
|
const loopVar = node.variable; // string name of the loop variable
|
|
671
726
|
const createLoopEnv = () => node.letKeyword ? new Environment(env) : env;
|
|
672
|
-
|
|
673
|
-
// Arrays: iterate over elements
|
|
674
727
|
if (Array.isArray(iterable)) {
|
|
675
728
|
for (const value of iterable) {
|
|
676
729
|
const loopEnv = createLoopEnv();
|
|
@@ -685,7 +738,6 @@ async evalFor(node, env) {
|
|
|
685
738
|
}
|
|
686
739
|
}
|
|
687
740
|
}
|
|
688
|
-
// Objects: iterate over keys
|
|
689
741
|
else {
|
|
690
742
|
for (const key of Object.keys(iterable)) {
|
|
691
743
|
const loopEnv = createLoopEnv();
|
|
@@ -704,7 +756,6 @@ async evalFor(node, env) {
|
|
|
704
756
|
return null;
|
|
705
757
|
}
|
|
706
758
|
|
|
707
|
-
// C-style for loop (classic JS style)
|
|
708
759
|
const local = new Environment(env);
|
|
709
760
|
|
|
710
761
|
if (node.init) await this.evaluate(node.init, local);
|
|
@@ -790,19 +841,14 @@ async evalIndex(node, env) {
|
|
|
790
841
|
const idx = await this.evaluate(node.indexer, env);
|
|
791
842
|
|
|
792
843
|
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
844
|
if (Array.isArray(obj)) {
|
|
796
845
|
if (idx < 0 || idx >= obj.length) return undefined;
|
|
797
846
|
return obj[idx];
|
|
798
847
|
}
|
|
799
|
-
|
|
800
|
-
// Object access: return undefined if property missing
|
|
801
848
|
if (typeof obj === 'object') {
|
|
802
849
|
return obj[idx]; // undefined if missing
|
|
803
850
|
}
|
|
804
851
|
|
|
805
|
-
// Fallback for non-object non-array
|
|
806
852
|
return undefined;
|
|
807
853
|
}
|
|
808
854
|
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 };
|
|
@@ -814,11 +840,11 @@ arrowFunction(params) {
|
|
|
814
840
|
this.eat('LBRACE');
|
|
815
841
|
const props = [];
|
|
816
842
|
while (this.current.type !== 'RBRACE') {
|
|
817
|
-
const key = this.expression();
|
|
843
|
+
const key = this.expression();
|
|
818
844
|
this.eat('COLON');
|
|
819
845
|
const value = this.expression();
|
|
820
846
|
props.push({ key, value });
|
|
821
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
847
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
822
848
|
}
|
|
823
849
|
this.eat('RBRACE');
|
|
824
850
|
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|