starlight-cli 1.0.47 → 1.0.49
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 +421 -169
- package/package.json +1 -1
- package/src/evaluator.js +156 -70
- package/src/parser.js +248 -85
- package/src/starlight.js +17 -14
package/dist/index.js
CHANGED
|
@@ -1353,14 +1353,29 @@ class ReturnValue {
|
|
|
1353
1353
|
class BreakSignal {}
|
|
1354
1354
|
class ContinueSignal {}
|
|
1355
1355
|
class RuntimeError extends Error {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1356
|
+
constructor(message, node, source) {
|
|
1357
|
+
const line = node?.line ?? '?';
|
|
1358
|
+
const column = node?.column ?? '?';
|
|
1359
|
+
|
|
1360
|
+
let output = ` ${message} at line ${line}, column ${column}\n`;
|
|
1361
|
+
|
|
1362
|
+
if (source && node?.line != null) {
|
|
1363
|
+
const lines = source.split('\n');
|
|
1364
|
+
const srcLine = lines[node.line - 1] || '';
|
|
1365
|
+
output += ` ${srcLine}\n`;
|
|
1366
|
+
output += ` ${' '.repeat(column - 1)}^\n`;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
super(output);
|
|
1370
|
+
|
|
1371
|
+
this.name = 'RuntimeError';
|
|
1372
|
+
this.line = line;
|
|
1373
|
+
this.column = column;
|
|
1374
|
+
}
|
|
1362
1375
|
}
|
|
1363
1376
|
|
|
1377
|
+
|
|
1378
|
+
|
|
1364
1379
|
class Environment {
|
|
1365
1380
|
constructor(parent = null) {
|
|
1366
1381
|
this.store = Object.create(null);
|
|
@@ -1373,11 +1388,12 @@ class Environment {
|
|
|
1373
1388
|
return false;
|
|
1374
1389
|
}
|
|
1375
1390
|
|
|
1376
|
-
get(name) {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1391
|
+
get(name, node, source) {
|
|
1392
|
+
if (name in this.store) return this.store[name];
|
|
1393
|
+
if (this.parent) return this.parent.get(name, node, source);
|
|
1394
|
+
throw new RuntimeError(`Undefined variable: ${name}`, node, source);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1381
1397
|
|
|
1382
1398
|
set(name, value) {
|
|
1383
1399
|
if (name in this.store) { this.store[name] = value; return value; }
|
|
@@ -1393,7 +1409,8 @@ class Environment {
|
|
|
1393
1409
|
}
|
|
1394
1410
|
|
|
1395
1411
|
class Evaluator {
|
|
1396
|
-
constructor() {
|
|
1412
|
+
constructor(source = '') {
|
|
1413
|
+
this.source = source;
|
|
1397
1414
|
this.global = new Environment();
|
|
1398
1415
|
this.setupBuiltins();
|
|
1399
1416
|
}
|
|
@@ -1480,11 +1497,11 @@ this.global.define('range', (...args) => {
|
|
|
1480
1497
|
end = Number(args[1]);
|
|
1481
1498
|
step = Number(args[2]);
|
|
1482
1499
|
} else {
|
|
1483
|
-
throw new RuntimeError('range() expects 1 to 3 arguments');
|
|
1500
|
+
throw new RuntimeError('range() expects 1 to 3 arguments', null, this.source);
|
|
1484
1501
|
}
|
|
1485
1502
|
|
|
1486
1503
|
if (step === 0) {
|
|
1487
|
-
throw new RuntimeError('range() step cannot be 0');
|
|
1504
|
+
throw new RuntimeError('range() step cannot be 0', null, this.source);
|
|
1488
1505
|
}
|
|
1489
1506
|
|
|
1490
1507
|
const result = [];
|
|
@@ -1508,7 +1525,7 @@ this.global.define('range', (...args) => {
|
|
|
1508
1525
|
this.global.define('num', arg => {
|
|
1509
1526
|
const n = Number(arg);
|
|
1510
1527
|
if (Number.isNaN(n)) {
|
|
1511
|
-
throw new RuntimeError('Cannot convert value to number');
|
|
1528
|
+
throw new RuntimeError('Cannot convert value to number', null, this.source);
|
|
1512
1529
|
}
|
|
1513
1530
|
return n;
|
|
1514
1531
|
});
|
|
@@ -1561,7 +1578,7 @@ async evaluate(node, env = this.global) {
|
|
|
1561
1578
|
case 'LogicalExpression': return await this.evalLogical(node, env);
|
|
1562
1579
|
case 'UnaryExpression': return await this.evalUnary(node, env);
|
|
1563
1580
|
case 'Literal': return node.value;
|
|
1564
|
-
case 'Identifier': return env.get(node.name);
|
|
1581
|
+
case 'Identifier': return env.get(node.name, node, this.source);
|
|
1565
1582
|
case 'IfStatement': return await this.evalIf(node, env);
|
|
1566
1583
|
case 'WhileStatement': return await this.evalWhile(node, env);
|
|
1567
1584
|
case 'ForStatement':
|
|
@@ -1584,14 +1601,8 @@ case 'DoTrackStatement':
|
|
|
1584
1601
|
case 'ArrayExpression':
|
|
1585
1602
|
return await Promise.all(node.elements.map(el => this.evaluate(el, env)));
|
|
1586
1603
|
case 'IndexExpression': return await this.evalIndex(node, env);
|
|
1587
|
-
case 'ObjectExpression':
|
|
1588
|
-
|
|
1589
|
-
for (const p of node.props) {
|
|
1590
|
-
const key = await this.evaluate(p.key, env);
|
|
1591
|
-
out[key] = await this.evaluate(p.value, env);
|
|
1592
|
-
}
|
|
1593
|
-
return out;
|
|
1594
|
-
}
|
|
1604
|
+
case 'ObjectExpression': return await this.evalObject(node, env);
|
|
1605
|
+
|
|
1595
1606
|
|
|
1596
1607
|
case 'MemberExpression': return await this.evalMember(node, env);
|
|
1597
1608
|
case 'UpdateExpression': return await this.evalUpdate(node, env);
|
|
@@ -1619,7 +1630,7 @@ case 'DoTrackStatement':
|
|
|
1619
1630
|
}
|
|
1620
1631
|
|
|
1621
1632
|
if (typeof callee !== 'function') {
|
|
1622
|
-
throw new RuntimeError('NewExpression callee is not a function', node);
|
|
1633
|
+
throw new RuntimeError('NewExpression callee is not a function', node, this.source);
|
|
1623
1634
|
}
|
|
1624
1635
|
|
|
1625
1636
|
const args = [];
|
|
@@ -1628,7 +1639,7 @@ case 'DoTrackStatement':
|
|
|
1628
1639
|
}
|
|
1629
1640
|
|
|
1630
1641
|
default:
|
|
1631
|
-
throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node);
|
|
1642
|
+
throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node, this.source);
|
|
1632
1643
|
|
|
1633
1644
|
}
|
|
1634
1645
|
}
|
|
@@ -1636,11 +1647,19 @@ case 'DoTrackStatement':
|
|
|
1636
1647
|
async evalProgram(node, env) {
|
|
1637
1648
|
let result = null;
|
|
1638
1649
|
for (const stmt of node.body) {
|
|
1639
|
-
|
|
1650
|
+
try {
|
|
1651
|
+
result = await this.evaluate(stmt, env);
|
|
1652
|
+
} catch (e) {
|
|
1653
|
+
if (e instanceof RuntimeError || e instanceof BreakSignal || e instanceof ContinueSignal || e instanceof ReturnValue) {
|
|
1654
|
+
throw e;
|
|
1655
|
+
}
|
|
1656
|
+
throw new RuntimeError(e.message || 'Error in program', stmt, this.source);
|
|
1657
|
+
}
|
|
1640
1658
|
}
|
|
1641
1659
|
return result;
|
|
1642
1660
|
}
|
|
1643
1661
|
|
|
1662
|
+
|
|
1644
1663
|
async evalDoTrack(node, env) {
|
|
1645
1664
|
try {
|
|
1646
1665
|
return await this.evaluate(node.body, env);
|
|
@@ -1648,7 +1667,7 @@ async evalDoTrack(node, env) {
|
|
|
1648
1667
|
if (!node.handler) {
|
|
1649
1668
|
// Wrap any raw error into RuntimeError with line info
|
|
1650
1669
|
if (err instanceof RuntimeError) throw err;
|
|
1651
|
-
throw new RuntimeError(err.message || 'Error in doTrack body', node.body);
|
|
1670
|
+
throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
|
|
1652
1671
|
}
|
|
1653
1672
|
|
|
1654
1673
|
const trackEnv = new Environment(env);
|
|
@@ -1659,7 +1678,7 @@ async evalDoTrack(node, env) {
|
|
|
1659
1678
|
} catch (handlerErr) {
|
|
1660
1679
|
// Wrap handler errors as well
|
|
1661
1680
|
if (handlerErr instanceof RuntimeError) throw handlerErr;
|
|
1662
|
-
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler);
|
|
1681
|
+
throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
|
|
1663
1682
|
}
|
|
1664
1683
|
}
|
|
1665
1684
|
}
|
|
@@ -1680,7 +1699,7 @@ async evalImport(node, env) {
|
|
|
1680
1699
|
: path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
|
|
1681
1700
|
|
|
1682
1701
|
if (!fs.existsSync(fullPath)) {
|
|
1683
|
-
throw new RuntimeError(`Import not found: ${spec}`, node);
|
|
1702
|
+
throw new RuntimeError(`Import not found: ${spec}`, node, this.source);
|
|
1684
1703
|
}
|
|
1685
1704
|
|
|
1686
1705
|
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -1707,7 +1726,7 @@ async evalImport(node, env) {
|
|
|
1707
1726
|
}
|
|
1708
1727
|
if (imp.type === 'NamedImport') {
|
|
1709
1728
|
if (!(imp.imported in lib)) {
|
|
1710
|
-
throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node);
|
|
1729
|
+
throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node, this.source);
|
|
1711
1730
|
}
|
|
1712
1731
|
env.define(imp.local, lib[imp.imported]);
|
|
1713
1732
|
}
|
|
@@ -1724,7 +1743,7 @@ async evalBlock(node, env) {
|
|
|
1724
1743
|
} catch (e) {
|
|
1725
1744
|
if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
|
|
1726
1745
|
// Wrap any other error in RuntimeError with the current block node
|
|
1727
|
-
throw new RuntimeError(e.message || 'Error in block', stmt);
|
|
1746
|
+
throw new RuntimeError(e.message || 'Error in block', stmt, this.source);
|
|
1728
1747
|
}
|
|
1729
1748
|
}
|
|
1730
1749
|
return result;
|
|
@@ -1732,11 +1751,23 @@ async evalBlock(node, env) {
|
|
|
1732
1751
|
|
|
1733
1752
|
|
|
1734
1753
|
async evalVarDeclaration(node, env) {
|
|
1754
|
+
if (!node.expr) {
|
|
1755
|
+
throw new RuntimeError('Variable declaration requires an initializer', node, this.source);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1735
1758
|
const val = await this.evaluate(node.expr, env);
|
|
1736
1759
|
return env.define(node.id, val);
|
|
1737
1760
|
}
|
|
1738
1761
|
|
|
1762
|
+
|
|
1739
1763
|
evalArrowFunction(node, env) {
|
|
1764
|
+
if (!node.body) {
|
|
1765
|
+
throw new RuntimeError('Arrow function missing body', node, this.source);
|
|
1766
|
+
}
|
|
1767
|
+
if (!Array.isArray(node.params)) {
|
|
1768
|
+
throw new RuntimeError('Invalid arrow function parameters', node, this.source);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1740
1771
|
return {
|
|
1741
1772
|
params: node.params,
|
|
1742
1773
|
body: node.body,
|
|
@@ -1746,24 +1777,28 @@ evalArrowFunction(node, env) {
|
|
|
1746
1777
|
};
|
|
1747
1778
|
}
|
|
1748
1779
|
|
|
1780
|
+
|
|
1749
1781
|
async evalAssignment(node, env) {
|
|
1750
1782
|
const rightVal = await this.evaluate(node.right, env);
|
|
1751
1783
|
const left = node.left;
|
|
1752
1784
|
|
|
1753
1785
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
1754
1786
|
if (left.type === 'MemberExpression') {
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1787
|
+
const obj = await this.evaluate(left.object, env);
|
|
1788
|
+
if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
|
|
1789
|
+
obj[left.property] = rightVal;
|
|
1790
|
+
return rightVal;
|
|
1791
|
+
}
|
|
1792
|
+
if (left.type === 'IndexExpression') {
|
|
1793
|
+
const obj = await this.evaluate(left.object, env);
|
|
1794
|
+
const idx = await this.evaluate(left.indexer, env);
|
|
1795
|
+
if (obj == null) throw new RuntimeError('Cannot assign to null or undefined', node, this.source);
|
|
1796
|
+
obj[idx] = rightVal;
|
|
1797
|
+
return rightVal;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1765
1800
|
|
|
1766
|
-
throw new RuntimeError('Invalid assignment target', node);
|
|
1801
|
+
throw new RuntimeError('Invalid assignment target', node, this.source);
|
|
1767
1802
|
|
|
1768
1803
|
}
|
|
1769
1804
|
|
|
@@ -1771,10 +1806,10 @@ async evalCompoundAssignment(node, env) {
|
|
|
1771
1806
|
const left = node.left;
|
|
1772
1807
|
let current;
|
|
1773
1808
|
|
|
1774
|
-
if (left.type === 'Identifier') current = env.get(left.name);
|
|
1809
|
+
if (left.type === 'Identifier') current = env.get(left.name, left, this.source);
|
|
1775
1810
|
else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
|
|
1776
1811
|
else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
|
|
1777
|
-
else throw new RuntimeError('Invalid compound assignment target', node);
|
|
1812
|
+
else throw new RuntimeError('Invalid compound assignment target', node, this.source);
|
|
1778
1813
|
|
|
1779
1814
|
|
|
1780
1815
|
const rhs = await this.evaluate(node.right, env);
|
|
@@ -1785,7 +1820,7 @@ async evalCompoundAssignment(node, env) {
|
|
|
1785
1820
|
case 'STAREQ': computed = current * rhs; break;
|
|
1786
1821
|
case 'SLASHEQ': computed = current / rhs; break;
|
|
1787
1822
|
case 'MODEQ': computed = current % rhs; break;
|
|
1788
|
-
default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node);
|
|
1823
|
+
default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
|
|
1789
1824
|
|
|
1790
1825
|
}
|
|
1791
1826
|
|
|
@@ -1805,21 +1840,33 @@ async evalSldeploy(node, env) {
|
|
|
1805
1840
|
|
|
1806
1841
|
async evalAsk(node, env) {
|
|
1807
1842
|
const prompt = await this.evaluate(node.prompt, env);
|
|
1843
|
+
|
|
1844
|
+
if (typeof prompt !== 'string') {
|
|
1845
|
+
throw new RuntimeError('ask() prompt must be a string', node, this.source);
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1808
1848
|
const input = readlineSync.question(prompt + ' ');
|
|
1809
1849
|
return input;
|
|
1810
1850
|
}
|
|
1811
1851
|
|
|
1852
|
+
|
|
1812
1853
|
async evalDefine(node, env) {
|
|
1854
|
+
if (!node.id || typeof node.id !== 'string') {
|
|
1855
|
+
throw new RuntimeError('Invalid identifier in define statement', node, this.source);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1813
1858
|
const val = node.expr ? await this.evaluate(node.expr, env) : null;
|
|
1814
|
-
return
|
|
1859
|
+
return env.define(node.id, val);
|
|
1860
|
+
|
|
1815
1861
|
}
|
|
1816
1862
|
|
|
1863
|
+
|
|
1817
1864
|
async evalBinary(node, env) {
|
|
1818
1865
|
const l = await this.evaluate(node.left, env);
|
|
1819
1866
|
const r = await this.evaluate(node.right, env);
|
|
1820
1867
|
|
|
1821
1868
|
if (node.operator === 'SLASH' && r === 0) {
|
|
1822
|
-
throw new RuntimeError('Division by zero', node);
|
|
1869
|
+
throw new RuntimeError('Division by zero', node, this.source);
|
|
1823
1870
|
}
|
|
1824
1871
|
|
|
1825
1872
|
switch (node.operator) {
|
|
@@ -1834,7 +1881,7 @@ async evalBinary(node, env) {
|
|
|
1834
1881
|
case 'LTE': return l <= r;
|
|
1835
1882
|
case 'GT': return l > r;
|
|
1836
1883
|
case 'GTE': return l >= r;
|
|
1837
|
-
default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node);
|
|
1884
|
+
default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node, this.source);
|
|
1838
1885
|
|
|
1839
1886
|
}
|
|
1840
1887
|
}
|
|
@@ -1843,7 +1890,7 @@ async evalLogical(node, env) {
|
|
|
1843
1890
|
const l = await this.evaluate(node.left, env);
|
|
1844
1891
|
if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
|
|
1845
1892
|
if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
|
|
1846
|
-
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node);
|
|
1893
|
+
throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
|
|
1847
1894
|
|
|
1848
1895
|
}
|
|
1849
1896
|
|
|
@@ -1853,22 +1900,43 @@ async evalUnary(node, env) {
|
|
|
1853
1900
|
case 'NOT': return !val;
|
|
1854
1901
|
case 'MINUS': return -val;
|
|
1855
1902
|
case 'PLUS': return +val;
|
|
1856
|
-
default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node);
|
|
1903
|
+
default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node, this.source);
|
|
1857
1904
|
|
|
1858
1905
|
}
|
|
1859
1906
|
}
|
|
1860
1907
|
|
|
1861
1908
|
async evalIf(node, env) {
|
|
1862
1909
|
const test = await this.evaluate(node.test, env);
|
|
1863
|
-
|
|
1864
|
-
if (
|
|
1910
|
+
|
|
1911
|
+
if (typeof test !== 'boolean') {
|
|
1912
|
+
throw new RuntimeError('If condition must evaluate to a boolean', node.test, this.source);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
if (test) {
|
|
1916
|
+
return await this.evaluate(node.consequent, env);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
if (node.alternate) {
|
|
1920
|
+
return await this.evaluate(node.alternate, env);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1865
1923
|
return null;
|
|
1866
1924
|
}
|
|
1867
1925
|
|
|
1926
|
+
|
|
1868
1927
|
async evalWhile(node, env) {
|
|
1869
|
-
while (
|
|
1870
|
-
|
|
1871
|
-
|
|
1928
|
+
while (true) {
|
|
1929
|
+
const test = await this.evaluate(node.test, env);
|
|
1930
|
+
|
|
1931
|
+
if (typeof test !== 'boolean') {
|
|
1932
|
+
throw new RuntimeError('While condition must evaluate to a boolean', node.test, this.source);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
if (!test) break;
|
|
1936
|
+
|
|
1937
|
+
try {
|
|
1938
|
+
await this.evaluate(node.body, env);
|
|
1939
|
+
} catch (e) {
|
|
1872
1940
|
if (e instanceof BreakSignal) break;
|
|
1873
1941
|
if (e instanceof ContinueSignal) continue;
|
|
1874
1942
|
throw e;
|
|
@@ -1876,6 +1944,7 @@ async evalWhile(node, env) {
|
|
|
1876
1944
|
}
|
|
1877
1945
|
return null;
|
|
1878
1946
|
}
|
|
1947
|
+
|
|
1879
1948
|
async evalFor(node, env) {
|
|
1880
1949
|
// -------------------------------
|
|
1881
1950
|
// Python-style: for x in iterable (with optional 'let')
|
|
@@ -1884,7 +1953,7 @@ async evalFor(node, env) {
|
|
|
1884
1953
|
const iterable = await this.evaluate(node.iterable, env);
|
|
1885
1954
|
|
|
1886
1955
|
if (iterable == null || typeof iterable !== 'object') {
|
|
1887
|
-
throw new RuntimeError('Cannot iterate over non-iterable', node);
|
|
1956
|
+
throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
|
|
1888
1957
|
}
|
|
1889
1958
|
|
|
1890
1959
|
const loopVar = node.variable; // STRING from parser
|
|
@@ -1949,9 +2018,26 @@ async evalFor(node, env) {
|
|
|
1949
2018
|
|
|
1950
2019
|
return null;
|
|
1951
2020
|
}
|
|
1952
|
-
|
|
1953
2021
|
evalFunctionDeclaration(node, env) {
|
|
1954
|
-
|
|
2022
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
2023
|
+
throw new RuntimeError('Function declaration requires a valid name', node, this.source);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
if (!Array.isArray(node.params)) {
|
|
2027
|
+
throw new RuntimeError(`Invalid parameter list in function '${node.name}'`, node, this.source);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
if (!node.body) {
|
|
2031
|
+
throw new RuntimeError(`Function '${node.name}' has no body`, node, this.source);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
const fn = {
|
|
2035
|
+
params: node.params,
|
|
2036
|
+
body: node.body,
|
|
2037
|
+
env,
|
|
2038
|
+
async: node.async || false
|
|
2039
|
+
};
|
|
2040
|
+
|
|
1955
2041
|
env.define(node.name, fn);
|
|
1956
2042
|
return null;
|
|
1957
2043
|
}
|
|
@@ -1961,10 +2047,10 @@ async evalCall(node, env) {
|
|
|
1961
2047
|
if (typeof calleeEvaluated === 'function') {
|
|
1962
2048
|
const args = [];
|
|
1963
2049
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
1964
|
-
return calleeEvaluated(...args);
|
|
2050
|
+
return await calleeEvaluated(...args);
|
|
1965
2051
|
}
|
|
1966
2052
|
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
1967
|
-
throw new RuntimeError('Call to non-function', node);
|
|
2053
|
+
throw new RuntimeError('Call to non-function', node, this.source);
|
|
1968
2054
|
}
|
|
1969
2055
|
|
|
1970
2056
|
const fn = calleeEvaluated;
|
|
@@ -1977,7 +2063,7 @@ async evalCall(node, env) {
|
|
|
1977
2063
|
|
|
1978
2064
|
try {
|
|
1979
2065
|
const result = await this.evaluate(fn.body, callEnv);
|
|
1980
|
-
return
|
|
2066
|
+
return result;
|
|
1981
2067
|
} catch (e) {
|
|
1982
2068
|
if (e instanceof ReturnValue) return e.value;
|
|
1983
2069
|
throw e;
|
|
@@ -1988,13 +2074,13 @@ async evalIndex(node, env) {
|
|
|
1988
2074
|
const obj = await this.evaluate(node.object, env);
|
|
1989
2075
|
const idx = await this.evaluate(node.indexer, env);
|
|
1990
2076
|
|
|
1991
|
-
if (obj == null) throw new RuntimeError('Indexing null or undefined', node);
|
|
2077
|
+
if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
|
|
1992
2078
|
|
|
1993
2079
|
if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
|
|
1994
|
-
throw new RuntimeError('Array index out of bounds', node);
|
|
2080
|
+
throw new RuntimeError('Array index out of bounds', node, this.source);
|
|
1995
2081
|
}
|
|
1996
2082
|
if (typeof obj === 'object' && !(idx in obj)) {
|
|
1997
|
-
throw new RuntimeError(`Property '${idx}' does not exist`, node);
|
|
2083
|
+
throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
|
|
1998
2084
|
}
|
|
1999
2085
|
|
|
2000
2086
|
return obj[idx];
|
|
@@ -2004,7 +2090,7 @@ async evalObject(node, env) {
|
|
|
2004
2090
|
const out = {};
|
|
2005
2091
|
for (const p of node.props) {
|
|
2006
2092
|
if (!p.key || !p.value) {
|
|
2007
|
-
throw new RuntimeError('Invalid object property', node);
|
|
2093
|
+
throw new RuntimeError('Invalid object property', node, this.source);
|
|
2008
2094
|
}
|
|
2009
2095
|
const key = await this.evaluate(p.key, env);
|
|
2010
2096
|
const value = await this.evaluate(p.value, env);
|
|
@@ -2016,18 +2102,18 @@ async evalObject(node, env) {
|
|
|
2016
2102
|
|
|
2017
2103
|
async evalMember(node, env) {
|
|
2018
2104
|
const obj = await this.evaluate(node.object, env);
|
|
2019
|
-
if (obj == null) throw new RuntimeError('Member access of null or undefined', node);
|
|
2020
|
-
if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node);
|
|
2105
|
+
if (obj == null) throw new RuntimeError('Member access of null or undefined', node, this.source);
|
|
2106
|
+
if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node, this.source);
|
|
2021
2107
|
return obj[node.property];
|
|
2022
2108
|
}
|
|
2023
2109
|
|
|
2024
2110
|
async evalUpdate(node, env) {
|
|
2025
2111
|
const arg = node.argument;
|
|
2026
2112
|
const getCurrent = async () => {
|
|
2027
|
-
if (arg.type === 'Identifier') return env.get(arg.name);
|
|
2113
|
+
if (arg.type === 'Identifier') return env.get(arg.name, arg, this.source);
|
|
2028
2114
|
if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
|
|
2029
2115
|
if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
|
|
2030
|
-
throw new RuntimeError('Invalid update target', node);
|
|
2116
|
+
throw new RuntimeError('Invalid update target', node, this.source);
|
|
2031
2117
|
|
|
2032
2118
|
};
|
|
2033
2119
|
const setValue = async (v) => {
|
|
@@ -2362,10 +2448,11 @@ class Parser {
|
|
|
2362
2448
|
return this.expressionStatement();
|
|
2363
2449
|
}
|
|
2364
2450
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2451
|
+
varDeclaration() {
|
|
2452
|
+
const t = this.current; // LET token
|
|
2367
2453
|
this.eat('LET');
|
|
2368
|
-
const
|
|
2454
|
+
const idToken = this.current; // identifier token
|
|
2455
|
+
const id = idToken.value;
|
|
2369
2456
|
this.eat('IDENTIFIER');
|
|
2370
2457
|
|
|
2371
2458
|
let expr = null;
|
|
@@ -2377,16 +2464,30 @@ class Parser {
|
|
|
2377
2464
|
// semicolon optional
|
|
2378
2465
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2379
2466
|
|
|
2380
|
-
return {
|
|
2467
|
+
return {
|
|
2468
|
+
type: 'VarDeclaration',
|
|
2469
|
+
id,
|
|
2470
|
+
expr,
|
|
2471
|
+
line: t.line,
|
|
2472
|
+
column: t.column
|
|
2473
|
+
};
|
|
2381
2474
|
}
|
|
2382
2475
|
|
|
2383
2476
|
sldeployStatement() {
|
|
2477
|
+
const t = this.current; // SLDEPLOY token
|
|
2384
2478
|
this.eat('SLDEPLOY');
|
|
2385
2479
|
const expr = this.expression();
|
|
2386
2480
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2387
|
-
return {
|
|
2481
|
+
return {
|
|
2482
|
+
type: 'SldeployStatement',
|
|
2483
|
+
expr,
|
|
2484
|
+
line: t.line,
|
|
2485
|
+
column: t.column
|
|
2486
|
+
};
|
|
2388
2487
|
}
|
|
2488
|
+
|
|
2389
2489
|
doTrackStatement() {
|
|
2490
|
+
const t = this.current; // DO token
|
|
2390
2491
|
this.eat('DO');
|
|
2391
2492
|
|
|
2392
2493
|
const body = this.block();
|
|
@@ -2400,13 +2501,17 @@ doTrackStatement() {
|
|
|
2400
2501
|
return {
|
|
2401
2502
|
type: 'DoTrackStatement',
|
|
2402
2503
|
body,
|
|
2403
|
-
handler
|
|
2504
|
+
handler,
|
|
2505
|
+
line: t.line,
|
|
2506
|
+
column: t.column
|
|
2404
2507
|
};
|
|
2405
2508
|
}
|
|
2406
2509
|
|
|
2407
2510
|
defineStatement() {
|
|
2511
|
+
const t = this.current; // DEFINE token
|
|
2408
2512
|
this.eat('DEFINE');
|
|
2409
|
-
const
|
|
2513
|
+
const idToken = this.current; // identifier token
|
|
2514
|
+
const id = idToken.value;
|
|
2410
2515
|
this.eat('IDENTIFIER');
|
|
2411
2516
|
|
|
2412
2517
|
let expr = null;
|
|
@@ -2417,10 +2522,17 @@ defineStatement() {
|
|
|
2417
2522
|
|
|
2418
2523
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2419
2524
|
|
|
2420
|
-
return {
|
|
2525
|
+
return {
|
|
2526
|
+
type: 'DefineStatement',
|
|
2527
|
+
id,
|
|
2528
|
+
expr,
|
|
2529
|
+
line: t.line,
|
|
2530
|
+
column: t.column
|
|
2531
|
+
};
|
|
2421
2532
|
}
|
|
2422
2533
|
|
|
2423
2534
|
asyncFuncDeclaration() {
|
|
2535
|
+
const t = this.current; // first token of function (identifier)
|
|
2424
2536
|
const name = this.current.value;
|
|
2425
2537
|
this.eat('IDENTIFIER');
|
|
2426
2538
|
|
|
@@ -2446,10 +2558,19 @@ asyncFuncDeclaration() {
|
|
|
2446
2558
|
}
|
|
2447
2559
|
|
|
2448
2560
|
const body = this.block();
|
|
2449
|
-
return {
|
|
2561
|
+
return {
|
|
2562
|
+
type: 'FunctionDeclaration',
|
|
2563
|
+
name,
|
|
2564
|
+
params,
|
|
2565
|
+
body,
|
|
2566
|
+
async: true,
|
|
2567
|
+
line: t.line,
|
|
2568
|
+
column: t.column
|
|
2569
|
+
};
|
|
2450
2570
|
}
|
|
2451
2571
|
|
|
2452
2572
|
ifStatement() {
|
|
2573
|
+
const t = this.current; // IF token
|
|
2453
2574
|
this.eat('IF');
|
|
2454
2575
|
|
|
2455
2576
|
let test;
|
|
@@ -2458,7 +2579,7 @@ ifStatement() {
|
|
|
2458
2579
|
test = this.expression();
|
|
2459
2580
|
this.eat('RPAREN');
|
|
2460
2581
|
} else {
|
|
2461
|
-
//
|
|
2582
|
+
// Python style: no parentheses
|
|
2462
2583
|
test = this.expression();
|
|
2463
2584
|
}
|
|
2464
2585
|
|
|
@@ -2471,10 +2592,18 @@ ifStatement() {
|
|
|
2471
2592
|
else alternate = this.block();
|
|
2472
2593
|
}
|
|
2473
2594
|
|
|
2474
|
-
return {
|
|
2595
|
+
return {
|
|
2596
|
+
type: 'IfStatement',
|
|
2597
|
+
test,
|
|
2598
|
+
consequent,
|
|
2599
|
+
alternate,
|
|
2600
|
+
line: t.line,
|
|
2601
|
+
column: t.column
|
|
2602
|
+
};
|
|
2475
2603
|
}
|
|
2476
2604
|
|
|
2477
2605
|
whileStatement() {
|
|
2606
|
+
const t = this.current; // WHILE token
|
|
2478
2607
|
this.eat('WHILE');
|
|
2479
2608
|
|
|
2480
2609
|
let test;
|
|
@@ -2487,10 +2616,17 @@ whileStatement() {
|
|
|
2487
2616
|
}
|
|
2488
2617
|
|
|
2489
2618
|
const body = this.block();
|
|
2490
|
-
return {
|
|
2619
|
+
return {
|
|
2620
|
+
type: 'WhileStatement',
|
|
2621
|
+
test,
|
|
2622
|
+
body,
|
|
2623
|
+
line: t.line,
|
|
2624
|
+
column: t.column
|
|
2625
|
+
};
|
|
2491
2626
|
}
|
|
2492
2627
|
|
|
2493
2628
|
importStatement() {
|
|
2629
|
+
const t = this.current; // IMPORT token
|
|
2494
2630
|
this.eat('IMPORT');
|
|
2495
2631
|
|
|
2496
2632
|
let specifiers = [];
|
|
@@ -2499,11 +2635,13 @@ importStatement() {
|
|
|
2499
2635
|
this.eat('AS');
|
|
2500
2636
|
const name = this.current.value;
|
|
2501
2637
|
this.eat('IDENTIFIER');
|
|
2502
|
-
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2638
|
+
specifiers.push({ type: 'NamespaceImport', local: name, line: t.line, column: t.column });
|
|
2503
2639
|
} else if (this.current.type === 'LBRACE') {
|
|
2504
2640
|
this.eat('LBRACE');
|
|
2505
2641
|
while (this.current.type !== 'RBRACE') {
|
|
2506
2642
|
const importedName = this.current.value;
|
|
2643
|
+
const importedLine = this.current.line;
|
|
2644
|
+
const importedColumn = this.current.column;
|
|
2507
2645
|
this.eat('IDENTIFIER');
|
|
2508
2646
|
|
|
2509
2647
|
let localName = importedName;
|
|
@@ -2513,14 +2651,16 @@ importStatement() {
|
|
|
2513
2651
|
this.eat('IDENTIFIER');
|
|
2514
2652
|
}
|
|
2515
2653
|
|
|
2516
|
-
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2654
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName, line: importedLine, column: importedColumn });
|
|
2517
2655
|
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2518
2656
|
}
|
|
2519
2657
|
this.eat('RBRACE');
|
|
2520
2658
|
} else if (this.current.type === 'IDENTIFIER') {
|
|
2521
2659
|
const localName = this.current.value;
|
|
2660
|
+
const localLine = this.current.line;
|
|
2661
|
+
const localColumn = this.current.column;
|
|
2522
2662
|
this.eat('IDENTIFIER');
|
|
2523
|
-
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2663
|
+
specifiers.push({ type: 'DefaultImport', local: localName, line: localLine, column: localColumn });
|
|
2524
2664
|
} else {
|
|
2525
2665
|
throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
|
|
2526
2666
|
}
|
|
@@ -2532,9 +2672,17 @@ importStatement() {
|
|
|
2532
2672
|
|
|
2533
2673
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2534
2674
|
|
|
2535
|
-
return {
|
|
2675
|
+
return {
|
|
2676
|
+
type: 'ImportStatement',
|
|
2677
|
+
path: pathToken.value,
|
|
2678
|
+
specifiers,
|
|
2679
|
+
line: t.line,
|
|
2680
|
+
column: t.column
|
|
2681
|
+
};
|
|
2536
2682
|
}
|
|
2683
|
+
|
|
2537
2684
|
forStatement() {
|
|
2685
|
+
const t = this.current; // FOR token
|
|
2538
2686
|
this.eat('FOR');
|
|
2539
2687
|
|
|
2540
2688
|
// --- Python-style: for variable in iterable (supports optional 'let') ---
|
|
@@ -2542,17 +2690,22 @@ forStatement() {
|
|
|
2542
2690
|
(this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
|
|
2543
2691
|
|
|
2544
2692
|
let variable;
|
|
2693
|
+
let variableLine, variableColumn;
|
|
2545
2694
|
let iterable;
|
|
2546
2695
|
let letKeyword = false;
|
|
2547
2696
|
|
|
2548
2697
|
if (this.current.type === 'LET') {
|
|
2549
2698
|
letKeyword = true;
|
|
2550
2699
|
this.eat('LET');
|
|
2700
|
+
variableLine = this.current.line;
|
|
2701
|
+
variableColumn = this.current.column;
|
|
2551
2702
|
variable = this.current.value;
|
|
2552
2703
|
this.eat('IDENTIFIER');
|
|
2553
2704
|
this.eat('IN');
|
|
2554
2705
|
iterable = this.expression();
|
|
2555
2706
|
} else {
|
|
2707
|
+
variableLine = this.current.line;
|
|
2708
|
+
variableColumn = this.current.column;
|
|
2556
2709
|
variable = this.current.value;
|
|
2557
2710
|
this.eat('IDENTIFIER');
|
|
2558
2711
|
this.eat('IN');
|
|
@@ -2560,7 +2713,17 @@ forStatement() {
|
|
|
2560
2713
|
}
|
|
2561
2714
|
|
|
2562
2715
|
const body = this.block();
|
|
2563
|
-
return {
|
|
2716
|
+
return {
|
|
2717
|
+
type: 'ForInStatement',
|
|
2718
|
+
variable,
|
|
2719
|
+
variableLine,
|
|
2720
|
+
variableColumn,
|
|
2721
|
+
iterable,
|
|
2722
|
+
letKeyword,
|
|
2723
|
+
body,
|
|
2724
|
+
line: t.line,
|
|
2725
|
+
column: t.column
|
|
2726
|
+
};
|
|
2564
2727
|
}
|
|
2565
2728
|
|
|
2566
2729
|
// --- C-style: for(init; test; update) ---
|
|
@@ -2595,37 +2758,51 @@ forStatement() {
|
|
|
2595
2758
|
}
|
|
2596
2759
|
|
|
2597
2760
|
const body = this.block();
|
|
2598
|
-
return {
|
|
2761
|
+
return {
|
|
2762
|
+
type: 'ForStatement',
|
|
2763
|
+
init,
|
|
2764
|
+
test,
|
|
2765
|
+
update,
|
|
2766
|
+
body,
|
|
2767
|
+
line: t.line,
|
|
2768
|
+
column: t.column
|
|
2769
|
+
};
|
|
2599
2770
|
}
|
|
2600
2771
|
|
|
2601
2772
|
breakStatement() {
|
|
2773
|
+
const t = this.current; // BREAK token
|
|
2602
2774
|
this.eat('BREAK');
|
|
2603
2775
|
// Python-style: no semicolon needed, ignore if present
|
|
2604
2776
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2605
|
-
return { type: 'BreakStatement' };
|
|
2777
|
+
return { type: 'BreakStatement', line: t.line, column: t.column };
|
|
2606
2778
|
}
|
|
2607
2779
|
|
|
2608
2780
|
continueStatement() {
|
|
2781
|
+
const t = this.current; // CONTINUE token
|
|
2609
2782
|
this.eat('CONTINUE');
|
|
2610
2783
|
// Python-style: no semicolon needed, ignore if present
|
|
2611
2784
|
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2612
|
-
return { type: 'ContinueStatement' };
|
|
2785
|
+
return { type: 'ContinueStatement', line: t.line, column: t.column };
|
|
2613
2786
|
}
|
|
2614
2787
|
|
|
2615
2788
|
funcDeclaration() {
|
|
2789
|
+
const t = this.current; // FUNC token
|
|
2616
2790
|
this.eat('FUNC');
|
|
2617
|
-
const
|
|
2791
|
+
const nameToken = this.current;
|
|
2792
|
+
const name = nameToken.value;
|
|
2618
2793
|
this.eat('IDENTIFIER');
|
|
2619
2794
|
|
|
2620
2795
|
let params = [];
|
|
2621
2796
|
if (this.current.type === 'LPAREN') {
|
|
2622
2797
|
this.eat('LPAREN');
|
|
2623
2798
|
if (this.current.type !== 'RPAREN') {
|
|
2624
|
-
|
|
2799
|
+
const paramToken = this.current;
|
|
2800
|
+
params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
|
|
2625
2801
|
this.eat('IDENTIFIER');
|
|
2626
2802
|
while (this.current.type === 'COMMA') {
|
|
2627
2803
|
this.eat('COMMA');
|
|
2628
|
-
|
|
2804
|
+
const paramToken2 = this.current;
|
|
2805
|
+
params.push({ name: paramToken2.value, line: paramToken2.line, column: paramToken2.column });
|
|
2629
2806
|
this.eat('IDENTIFIER');
|
|
2630
2807
|
}
|
|
2631
2808
|
}
|
|
@@ -2633,16 +2810,18 @@ funcDeclaration() {
|
|
|
2633
2810
|
} else {
|
|
2634
2811
|
// Python-style: single param without parentheses
|
|
2635
2812
|
if (this.current.type === 'IDENTIFIER') {
|
|
2636
|
-
|
|
2813
|
+
const paramToken = this.current;
|
|
2814
|
+
params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
|
|
2637
2815
|
this.eat('IDENTIFIER');
|
|
2638
2816
|
}
|
|
2639
2817
|
}
|
|
2640
2818
|
|
|
2641
2819
|
const body = this.block();
|
|
2642
|
-
return { type: 'FunctionDeclaration', name, params, body };
|
|
2820
|
+
return { type: 'FunctionDeclaration', name, params, body, line: t.line, column: t.column };
|
|
2643
2821
|
}
|
|
2644
2822
|
|
|
2645
2823
|
returnStatement() {
|
|
2824
|
+
const t = this.current; // RETURN token
|
|
2646
2825
|
this.eat('RETURN');
|
|
2647
2826
|
|
|
2648
2827
|
let argument = null;
|
|
@@ -2653,24 +2832,26 @@ returnStatement() {
|
|
|
2653
2832
|
// semicolon optional
|
|
2654
2833
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2655
2834
|
|
|
2656
|
-
return { type: 'ReturnStatement', argument };
|
|
2835
|
+
return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
|
|
2657
2836
|
}
|
|
2658
2837
|
|
|
2659
2838
|
block() {
|
|
2839
|
+
const t = this.current; // LBRACE token
|
|
2660
2840
|
this.eat('LBRACE');
|
|
2661
2841
|
const body = [];
|
|
2662
2842
|
while (this.current.type !== 'RBRACE') {
|
|
2663
2843
|
body.push(this.statement());
|
|
2664
2844
|
}
|
|
2665
2845
|
this.eat('RBRACE');
|
|
2666
|
-
return { type: 'BlockStatement', body };
|
|
2846
|
+
return { type: 'BlockStatement', body, line: t.line, column: t.column };
|
|
2667
2847
|
}
|
|
2668
2848
|
|
|
2669
2849
|
expressionStatement() {
|
|
2850
|
+
const exprToken = this.current; // first token of the expression
|
|
2670
2851
|
const expr = this.expression();
|
|
2671
2852
|
// semicolon optional
|
|
2672
2853
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2673
|
-
return { type: 'ExpressionStatement', expression: expr };
|
|
2854
|
+
return { type: 'ExpressionStatement', expression: expr, line: exprToken.line, column: exprToken.column };
|
|
2674
2855
|
}
|
|
2675
2856
|
|
|
2676
2857
|
expression() {
|
|
@@ -2681,17 +2862,19 @@ assignment() {
|
|
|
2681
2862
|
const node = this.logicalOr();
|
|
2682
2863
|
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2683
2864
|
|
|
2684
|
-
|
|
2685
|
-
|
|
2865
|
+
const t = this.current;
|
|
2866
|
+
|
|
2867
|
+
if (compoundOps.includes(t.type)) {
|
|
2868
|
+
const op = t.type;
|
|
2686
2869
|
this.eat(op);
|
|
2687
2870
|
const right = this.assignment();
|
|
2688
|
-
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2871
|
+
return { type: 'CompoundAssignment', operator: op, left: node, right, line: t.line, column: t.column };
|
|
2689
2872
|
}
|
|
2690
2873
|
|
|
2691
|
-
if (
|
|
2874
|
+
if (t.type === 'EQUAL') {
|
|
2692
2875
|
this.eat('EQUAL');
|
|
2693
2876
|
const right = this.assignment();
|
|
2694
|
-
return { type: 'AssignmentExpression', left: node, right };
|
|
2877
|
+
return { type: 'AssignmentExpression', left: node, right, line: t.line, column: t.column };
|
|
2695
2878
|
}
|
|
2696
2879
|
|
|
2697
2880
|
return node;
|
|
@@ -2700,9 +2883,10 @@ assignment() {
|
|
|
2700
2883
|
logicalOr() {
|
|
2701
2884
|
let node = this.logicalAnd();
|
|
2702
2885
|
while (this.current.type === 'OR') {
|
|
2703
|
-
const
|
|
2886
|
+
const t = this.current;
|
|
2887
|
+
const op = t.type;
|
|
2704
2888
|
this.eat(op);
|
|
2705
|
-
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
|
|
2889
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd(), line: t.line, column: t.column };
|
|
2706
2890
|
}
|
|
2707
2891
|
return node;
|
|
2708
2892
|
}
|
|
@@ -2710,18 +2894,21 @@ logicalOr() {
|
|
|
2710
2894
|
logicalAnd() {
|
|
2711
2895
|
let node = this.equality();
|
|
2712
2896
|
while (this.current.type === 'AND') {
|
|
2713
|
-
const
|
|
2897
|
+
const t = this.current;
|
|
2898
|
+
const op = t.type;
|
|
2714
2899
|
this.eat(op);
|
|
2715
|
-
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
|
|
2900
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality(), line: t.line, column: t.column };
|
|
2716
2901
|
}
|
|
2717
2902
|
return node;
|
|
2718
2903
|
}
|
|
2904
|
+
|
|
2719
2905
|
equality() {
|
|
2720
2906
|
let node = this.comparison();
|
|
2721
2907
|
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2722
|
-
const
|
|
2908
|
+
const t = this.current;
|
|
2909
|
+
const op = t.type;
|
|
2723
2910
|
this.eat(op);
|
|
2724
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
2911
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison(), line: t.line, column: t.column };
|
|
2725
2912
|
}
|
|
2726
2913
|
return node;
|
|
2727
2914
|
}
|
|
@@ -2729,9 +2916,10 @@ equality() {
|
|
|
2729
2916
|
comparison() {
|
|
2730
2917
|
let node = this.term();
|
|
2731
2918
|
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2732
|
-
const
|
|
2919
|
+
const t = this.current;
|
|
2920
|
+
const op = t.type;
|
|
2733
2921
|
this.eat(op);
|
|
2734
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2922
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term(), line: t.line, column: t.column };
|
|
2735
2923
|
}
|
|
2736
2924
|
return node;
|
|
2737
2925
|
}
|
|
@@ -2739,9 +2927,10 @@ comparison() {
|
|
|
2739
2927
|
term() {
|
|
2740
2928
|
let node = this.factor();
|
|
2741
2929
|
while (['PLUS', 'MINUS'].includes(this.current.type)) {
|
|
2742
|
-
const
|
|
2930
|
+
const t = this.current;
|
|
2931
|
+
const op = t.type;
|
|
2743
2932
|
this.eat(op);
|
|
2744
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
|
|
2933
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor(), line: t.line, column: t.column };
|
|
2745
2934
|
}
|
|
2746
2935
|
return node;
|
|
2747
2936
|
}
|
|
@@ -2749,26 +2938,43 @@ term() {
|
|
|
2749
2938
|
factor() {
|
|
2750
2939
|
let node = this.unary();
|
|
2751
2940
|
while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
|
|
2752
|
-
const
|
|
2941
|
+
const t = this.current;
|
|
2942
|
+
const op = t.type;
|
|
2753
2943
|
this.eat(op);
|
|
2754
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
|
|
2944
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary(), line: t.line, column: t.column };
|
|
2755
2945
|
}
|
|
2756
2946
|
return node;
|
|
2757
2947
|
}
|
|
2758
2948
|
|
|
2949
|
+
|
|
2759
2950
|
unary() {
|
|
2760
|
-
|
|
2761
|
-
|
|
2951
|
+
const t = this.current;
|
|
2952
|
+
|
|
2953
|
+
if (['NOT', 'MINUS', 'PLUS'].includes(t.type)) {
|
|
2954
|
+
const op = t.type;
|
|
2762
2955
|
this.eat(op);
|
|
2763
|
-
return {
|
|
2956
|
+
return {
|
|
2957
|
+
type: 'UnaryExpression',
|
|
2958
|
+
operator: op,
|
|
2959
|
+
argument: this.unary(),
|
|
2960
|
+
line: t.line,
|
|
2961
|
+
column: t.column
|
|
2962
|
+
};
|
|
2764
2963
|
}
|
|
2765
2964
|
|
|
2766
2965
|
// Python-like: ignore ++ and -- if not used
|
|
2767
|
-
if (
|
|
2768
|
-
const op =
|
|
2966
|
+
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
2967
|
+
const op = t.type;
|
|
2769
2968
|
this.eat(op);
|
|
2770
2969
|
const argument = this.unary();
|
|
2771
|
-
return {
|
|
2970
|
+
return {
|
|
2971
|
+
type: 'UpdateExpression',
|
|
2972
|
+
operator: op,
|
|
2973
|
+
argument,
|
|
2974
|
+
prefix: true,
|
|
2975
|
+
line: t.line,
|
|
2976
|
+
column: t.column
|
|
2977
|
+
};
|
|
2772
2978
|
}
|
|
2773
2979
|
|
|
2774
2980
|
return this.postfix();
|
|
@@ -2777,15 +2983,21 @@ unary() {
|
|
|
2777
2983
|
postfix() {
|
|
2778
2984
|
let node = this.primary();
|
|
2779
2985
|
while (true) {
|
|
2780
|
-
|
|
2986
|
+
const t = this.current;
|
|
2987
|
+
|
|
2988
|
+
if (t.type === 'LBRACKET') {
|
|
2989
|
+
const startLine = t.line;
|
|
2990
|
+
const startCol = t.column;
|
|
2781
2991
|
this.eat('LBRACKET');
|
|
2782
2992
|
const index = this.expression();
|
|
2783
2993
|
if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
|
|
2784
|
-
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2994
|
+
node = { type: 'IndexExpression', object: node, indexer: index, line: startLine, column: startCol };
|
|
2785
2995
|
continue;
|
|
2786
2996
|
}
|
|
2787
2997
|
|
|
2788
|
-
if (
|
|
2998
|
+
if (t.type === 'LPAREN') {
|
|
2999
|
+
const startLine = t.line;
|
|
3000
|
+
const startCol = t.column;
|
|
2789
3001
|
this.eat('LPAREN');
|
|
2790
3002
|
const args = [];
|
|
2791
3003
|
while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
|
|
@@ -2793,30 +3005,30 @@ postfix() {
|
|
|
2793
3005
|
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
|
|
2794
3006
|
}
|
|
2795
3007
|
if (this.current.type === 'RPAREN') this.eat('RPAREN');
|
|
2796
|
-
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
3008
|
+
node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
|
|
2797
3009
|
continue;
|
|
2798
3010
|
}
|
|
2799
3011
|
|
|
2800
|
-
if (
|
|
3012
|
+
if (t.type === 'DOT') {
|
|
3013
|
+
const startLine = t.line;
|
|
3014
|
+
const startCol = t.column;
|
|
2801
3015
|
this.eat('DOT');
|
|
2802
3016
|
const property = this.current.value || '';
|
|
2803
3017
|
if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
|
|
2804
|
-
node = { type: 'MemberExpression', object: node, property };
|
|
3018
|
+
node = { type: 'MemberExpression', object: node, property, line: startLine, column: startCol };
|
|
2805
3019
|
continue;
|
|
2806
3020
|
}
|
|
2807
3021
|
|
|
2808
|
-
if (
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
3022
|
+
if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
|
|
3023
|
+
this.eat(t.type);
|
|
3024
|
+
node = { type: 'UpdateExpression', operator: t.type, argument: node, prefix: false, line: t.line, column: t.column };
|
|
2812
3025
|
continue;
|
|
2813
3026
|
}
|
|
2814
3027
|
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
const argNode = { type: 'Identifier', name: this.current.value };
|
|
3028
|
+
if (node.type === 'Identifier' && t.type === 'IDENTIFIER') {
|
|
3029
|
+
const argNode = { type: 'Identifier', name: t.value, line: t.line, column: t.column };
|
|
2818
3030
|
this.eat('IDENTIFIER');
|
|
2819
|
-
node = { type: 'CallExpression', callee: node, arguments: [argNode] };
|
|
3031
|
+
node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
|
|
2820
3032
|
continue;
|
|
2821
3033
|
}
|
|
2822
3034
|
|
|
@@ -2826,12 +3038,18 @@ postfix() {
|
|
|
2826
3038
|
}
|
|
2827
3039
|
|
|
2828
3040
|
arrowFunction(params) {
|
|
2829
|
-
|
|
3041
|
+
const t = this.current;
|
|
3042
|
+
if (t.type === 'ARROW') this.eat('ARROW');
|
|
2830
3043
|
const body = this.expression();
|
|
3044
|
+
const startLine = params.length > 0 ? params[0].line : t.line;
|
|
3045
|
+
const startCol = params.length > 0 ? params[0].column : t.column;
|
|
3046
|
+
|
|
2831
3047
|
return {
|
|
2832
3048
|
type: 'ArrowFunctionExpression',
|
|
2833
3049
|
params,
|
|
2834
|
-
body
|
|
3050
|
+
body,
|
|
3051
|
+
line: startLine,
|
|
3052
|
+
column: startCol
|
|
2835
3053
|
};
|
|
2836
3054
|
}
|
|
2837
3055
|
|
|
@@ -2839,16 +3057,35 @@ arrowFunction(params) {
|
|
|
2839
3057
|
primary() {
|
|
2840
3058
|
const t = this.current;
|
|
2841
3059
|
|
|
2842
|
-
if (t.type === 'NUMBER') {
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3060
|
+
if (t.type === 'NUMBER') {
|
|
3061
|
+
this.eat('NUMBER');
|
|
3062
|
+
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
if (t.type === 'STRING') {
|
|
3066
|
+
this.eat('STRING');
|
|
3067
|
+
return { type: 'Literal', value: t.value, line: t.line, column: t.column };
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
if (t.type === 'TRUE') {
|
|
3071
|
+
this.eat('TRUE');
|
|
3072
|
+
return { type: 'Literal', value: true, line: t.line, column: t.column };
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
if (t.type === 'FALSE') {
|
|
3076
|
+
this.eat('FALSE');
|
|
3077
|
+
return { type: 'Literal', value: false, line: t.line, column: t.column };
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
if (t.type === 'NULL') {
|
|
3081
|
+
this.eat('NULL');
|
|
3082
|
+
return { type: 'Literal', value: null, line: t.line, column: t.column };
|
|
3083
|
+
}
|
|
2847
3084
|
|
|
2848
3085
|
if (t.type === 'AWAIT') {
|
|
2849
3086
|
this.eat('AWAIT');
|
|
2850
3087
|
const argument = this.expression();
|
|
2851
|
-
return { type: 'AwaitExpression', argument };
|
|
3088
|
+
return { type: 'AwaitExpression', argument, line: t.line, column: t.column };
|
|
2852
3089
|
}
|
|
2853
3090
|
|
|
2854
3091
|
if (t.type === 'NEW') {
|
|
@@ -2864,7 +3101,7 @@ arrowFunction(params) {
|
|
|
2864
3101
|
}
|
|
2865
3102
|
}
|
|
2866
3103
|
this.eat('RPAREN');
|
|
2867
|
-
return { type: 'NewExpression', callee, arguments: args };
|
|
3104
|
+
return { type: 'NewExpression', callee, arguments: args, line: t.line, column: t.column };
|
|
2868
3105
|
}
|
|
2869
3106
|
|
|
2870
3107
|
if (t.type === 'ASK') {
|
|
@@ -2879,7 +3116,13 @@ arrowFunction(params) {
|
|
|
2879
3116
|
}
|
|
2880
3117
|
}
|
|
2881
3118
|
this.eat('RPAREN');
|
|
2882
|
-
return {
|
|
3119
|
+
return {
|
|
3120
|
+
type: 'CallExpression',
|
|
3121
|
+
callee: { type: 'Identifier', name: 'ask', line: t.line, column: t.column },
|
|
3122
|
+
arguments: args,
|
|
3123
|
+
line: t.line,
|
|
3124
|
+
column: t.column
|
|
3125
|
+
};
|
|
2883
3126
|
}
|
|
2884
3127
|
|
|
2885
3128
|
if (t.type === 'IDENTIFIER') {
|
|
@@ -2887,13 +3130,15 @@ arrowFunction(params) {
|
|
|
2887
3130
|
this.eat('IDENTIFIER');
|
|
2888
3131
|
|
|
2889
3132
|
if (this.current.type === 'ARROW') {
|
|
2890
|
-
return this.arrowFunction([name]);
|
|
3133
|
+
return this.arrowFunction([{ type: 'Identifier', name, line: t.line, column: t.column }]);
|
|
2891
3134
|
}
|
|
2892
3135
|
|
|
2893
|
-
return { type: 'Identifier', name };
|
|
3136
|
+
return { type: 'Identifier', name, line: t.line, column: t.column };
|
|
2894
3137
|
}
|
|
2895
3138
|
|
|
2896
3139
|
if (t.type === 'LPAREN') {
|
|
3140
|
+
const startLine = t.line;
|
|
3141
|
+
const startCol = t.column;
|
|
2897
3142
|
this.eat('LPAREN');
|
|
2898
3143
|
const elements = [];
|
|
2899
3144
|
|
|
@@ -2909,10 +3154,12 @@ arrowFunction(params) {
|
|
|
2909
3154
|
|
|
2910
3155
|
if (this.current.type === 'ARROW') return this.arrowFunction(elements);
|
|
2911
3156
|
|
|
2912
|
-
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
|
|
3157
|
+
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements, line: startLine, column: startCol };
|
|
2913
3158
|
}
|
|
2914
3159
|
|
|
2915
3160
|
if (t.type === 'LBRACKET') {
|
|
3161
|
+
const startLine = t.line;
|
|
3162
|
+
const startCol = t.column;
|
|
2916
3163
|
this.eat('LBRACKET');
|
|
2917
3164
|
const elements = [];
|
|
2918
3165
|
if (this.current.type !== 'RBRACKET') {
|
|
@@ -2923,10 +3170,12 @@ arrowFunction(params) {
|
|
|
2923
3170
|
}
|
|
2924
3171
|
}
|
|
2925
3172
|
this.eat('RBRACKET');
|
|
2926
|
-
return { type: 'ArrayExpression', elements };
|
|
3173
|
+
return { type: 'ArrayExpression', elements, line: startLine, column: startCol };
|
|
2927
3174
|
}
|
|
2928
3175
|
|
|
2929
3176
|
if (t.type === 'LBRACE') {
|
|
3177
|
+
const startLine = t.line;
|
|
3178
|
+
const startCol = t.column;
|
|
2930
3179
|
this.eat('LBRACE');
|
|
2931
3180
|
const props = [];
|
|
2932
3181
|
while (this.current.type !== 'RBRACE') {
|
|
@@ -2937,17 +3186,17 @@ arrowFunction(params) {
|
|
|
2937
3186
|
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
|
|
2938
3187
|
}
|
|
2939
3188
|
this.eat('RBRACE');
|
|
2940
|
-
return { type: 'ObjectExpression', props };
|
|
3189
|
+
return { type: 'ObjectExpression', props, line: startLine, column: startCol };
|
|
2941
3190
|
}
|
|
2942
3191
|
|
|
2943
3192
|
throw new ParseError(
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
);
|
|
2948
|
-
|
|
3193
|
+
`Unexpected token '${t.type}'`,
|
|
3194
|
+
t,
|
|
3195
|
+
this.source
|
|
3196
|
+
);
|
|
2949
3197
|
}
|
|
2950
3198
|
|
|
3199
|
+
|
|
2951
3200
|
}
|
|
2952
3201
|
|
|
2953
3202
|
module.exports = Parser;
|
|
@@ -3052,7 +3301,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
3052
3301
|
const Parser = __nccwpck_require__(222);
|
|
3053
3302
|
const Evaluator = __nccwpck_require__(112);
|
|
3054
3303
|
|
|
3055
|
-
const VERSION = '1.0.
|
|
3304
|
+
const VERSION = '1.0.49';
|
|
3056
3305
|
|
|
3057
3306
|
const COLOR = {
|
|
3058
3307
|
reset: '\x1b[0m',
|
|
@@ -3063,7 +3312,8 @@ const COLOR = {
|
|
|
3063
3312
|
cyan: '\x1b[36m',
|
|
3064
3313
|
gray: '\x1b[90m',
|
|
3065
3314
|
green: '\x1b[32m',
|
|
3066
|
-
magenta: '\x1b[35m'
|
|
3315
|
+
magenta: '\x1b[35m',
|
|
3316
|
+
white: '\x1b[37m'
|
|
3067
3317
|
};
|
|
3068
3318
|
|
|
3069
3319
|
function waitAndExit(code = 0) {
|
|
@@ -3074,7 +3324,7 @@ function waitAndExit(code = 0) {
|
|
|
3074
3324
|
}
|
|
3075
3325
|
|
|
3076
3326
|
function fatal(msg) {
|
|
3077
|
-
console.error(COLOR.
|
|
3327
|
+
console.error(COLOR.white + msg + COLOR.reset);
|
|
3078
3328
|
waitAndExit(1);
|
|
3079
3329
|
}
|
|
3080
3330
|
|
|
@@ -3215,33 +3465,35 @@ function savePrompt(lines) {
|
|
|
3215
3465
|
waitAndExit(0);
|
|
3216
3466
|
}
|
|
3217
3467
|
|
|
3218
|
-
function runFile(filePath, isTemp = false, callback) {
|
|
3468
|
+
async function runFile(filePath, isTemp = false, callback) {
|
|
3219
3469
|
const code = fs.readFileSync(filePath, 'utf8');
|
|
3220
3470
|
|
|
3471
|
+
const lexer = new Lexer(code);
|
|
3472
|
+
const tokens = lexer.getTokens();
|
|
3473
|
+
const parser = new Parser(tokens, code);
|
|
3474
|
+
const ast = parser.parse();
|
|
3475
|
+
const evaluator = new Evaluator(code);
|
|
3476
|
+
|
|
3221
3477
|
try {
|
|
3222
|
-
|
|
3223
|
-
const tokens = lexer.getTokens();
|
|
3224
|
-
const parser = new Parser(tokens);
|
|
3225
|
-
const ast = parser.parse();
|
|
3226
|
-
new Evaluator().evaluate(ast);
|
|
3478
|
+
await evaluator.evaluate(ast);
|
|
3227
3479
|
} catch (e) {
|
|
3228
|
-
if (e.name === 'RuntimeError') {
|
|
3229
|
-
console.error(COLOR.
|
|
3230
|
-
} else if (e instanceof SyntaxError) {
|
|
3231
|
-
console.error(COLOR.red + `SyntaxError: ${e.message}` + COLOR.reset);
|
|
3480
|
+
if (e.name === 'RuntimeError' || e.name === 'SyntaxError') {
|
|
3481
|
+
console.error(COLOR.white + e.message + COLOR.reset);
|
|
3232
3482
|
} else {
|
|
3233
|
-
console.error(COLOR.
|
|
3483
|
+
console.error(COLOR.white + `Unexpected Error: ${e.message || e}` + COLOR.reset);
|
|
3234
3484
|
}
|
|
3235
3485
|
return waitAndExit(1);
|
|
3236
3486
|
}
|
|
3237
3487
|
|
|
3238
3488
|
if (callback) callback();
|
|
3489
|
+
|
|
3239
3490
|
if (isTemp) {
|
|
3240
3491
|
try { fs.unlinkSync(filePath); } catch {}
|
|
3241
3492
|
}
|
|
3242
3493
|
}
|
|
3243
3494
|
|
|
3244
3495
|
|
|
3496
|
+
|
|
3245
3497
|
if (!args[0].startsWith('--')) {
|
|
3246
3498
|
runFile(path.resolve(args[0]));
|
|
3247
3499
|
}
|