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