starlight-cli 1.0.46 → 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 CHANGED
@@ -1352,6 +1352,29 @@ class ReturnValue {
1352
1352
  }
1353
1353
  class BreakSignal {}
1354
1354
  class ContinueSignal {}
1355
+ class RuntimeError extends Error {
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
+ }
1375
+ }
1376
+
1377
+
1355
1378
 
1356
1379
  class Environment {
1357
1380
  constructor(parent = null) {
@@ -1365,11 +1388,11 @@ class Environment {
1365
1388
  return false;
1366
1389
  }
1367
1390
 
1368
- get(name) {
1369
- if (name in this.store) return this.store[name];
1370
- if (this.parent) return this.parent.get(name);
1371
- throw new Error(`Undefined variable: ${name}`);
1372
- }
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
+ }
1373
1396
 
1374
1397
  set(name, value) {
1375
1398
  if (name in this.store) { this.store[name] = value; return value; }
@@ -1385,7 +1408,8 @@ class Environment {
1385
1408
  }
1386
1409
 
1387
1410
  class Evaluator {
1388
- constructor() {
1411
+ constructor(source = '') {
1412
+ this.source = source;
1389
1413
  this.global = new Environment();
1390
1414
  this.setupBuiltins();
1391
1415
  }
@@ -1472,11 +1496,11 @@ this.global.define('range', (...args) => {
1472
1496
  end = Number(args[1]);
1473
1497
  step = Number(args[2]);
1474
1498
  } else {
1475
- throw new Error('range() expects 1 to 3 arguments');
1499
+ throw new RuntimeError('range() expects 1 to 3 arguments', null, this.source);
1476
1500
  }
1477
1501
 
1478
1502
  if (step === 0) {
1479
- throw new Error('range() step cannot be 0');
1503
+ throw new RuntimeError('range() step cannot be 0', null, this.source);
1480
1504
  }
1481
1505
 
1482
1506
  const result = [];
@@ -1500,7 +1524,7 @@ this.global.define('range', (...args) => {
1500
1524
  this.global.define('num', arg => {
1501
1525
  const n = Number(arg);
1502
1526
  if (Number.isNaN(n)) {
1503
- throw new Error('Cannot convert value to number');
1527
+ throw new RuntimeError('Cannot convert value to number', null, this.source);
1504
1528
  }
1505
1529
  return n;
1506
1530
  });
@@ -1553,7 +1577,7 @@ async evaluate(node, env = this.global) {
1553
1577
  case 'LogicalExpression': return await this.evalLogical(node, env);
1554
1578
  case 'UnaryExpression': return await this.evalUnary(node, env);
1555
1579
  case 'Literal': return node.value;
1556
- case 'Identifier': return env.get(node.name);
1580
+ case 'Identifier': return env.get(node.name, node);
1557
1581
  case 'IfStatement': return await this.evalIf(node, env);
1558
1582
  case 'WhileStatement': return await this.evalWhile(node, env);
1559
1583
  case 'ForStatement':
@@ -1576,14 +1600,8 @@ case 'DoTrackStatement':
1576
1600
  case 'ArrayExpression':
1577
1601
  return await Promise.all(node.elements.map(el => this.evaluate(el, env)));
1578
1602
  case 'IndexExpression': return await this.evalIndex(node, env);
1579
- case 'ObjectExpression': {
1580
- const out = {};
1581
- for (const p of node.props) {
1582
- const key = await this.evaluate(p.key, env);
1583
- out[key] = await this.evaluate(p.value, env);
1584
- }
1585
- return out;
1586
- }
1603
+ case 'ObjectExpression': return await this.evalObject(node, env);
1604
+
1587
1605
 
1588
1606
  case 'MemberExpression': return await this.evalMember(node, env);
1589
1607
  case 'UpdateExpression': return await this.evalUpdate(node, env);
@@ -1611,7 +1629,7 @@ case 'DoTrackStatement':
1611
1629
  }
1612
1630
 
1613
1631
  if (typeof callee !== 'function') {
1614
- throw new Error('NewExpression callee is not a function');
1632
+ throw new RuntimeError('NewExpression callee is not a function', node, this.source);
1615
1633
  }
1616
1634
 
1617
1635
  const args = [];
@@ -1620,31 +1638,51 @@ case 'DoTrackStatement':
1620
1638
  }
1621
1639
 
1622
1640
  default:
1623
- throw new Error(`Unknown node type in evaluator: ${node.type}`);
1641
+ throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node, this.source);
1642
+
1624
1643
  }
1625
1644
  }
1626
1645
 
1627
1646
  async evalProgram(node, env) {
1628
1647
  let result = null;
1629
1648
  for (const stmt of node.body) {
1630
- result = await this.evaluate(stmt, env);
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
+ }
1631
1657
  }
1632
1658
  return result;
1633
1659
  }
1634
1660
 
1661
+
1635
1662
  async evalDoTrack(node, env) {
1636
1663
  try {
1637
1664
  return await this.evaluate(node.body, env);
1638
1665
  } catch (err) {
1639
- if (!node.handler) throw err;
1666
+ if (!node.handler) {
1667
+ // Wrap any raw error into RuntimeError with line info
1668
+ if (err instanceof RuntimeError) throw err;
1669
+ throw new RuntimeError(err.message || 'Error in doTrack body', node.body, this.source);
1670
+ }
1640
1671
 
1641
1672
  const trackEnv = new Environment(env);
1642
1673
  trackEnv.define('error', err);
1643
1674
 
1644
- return await this.evaluate(node.handler, trackEnv);
1675
+ try {
1676
+ return await this.evaluate(node.handler, trackEnv);
1677
+ } catch (handlerErr) {
1678
+ // Wrap handler errors as well
1679
+ if (handlerErr instanceof RuntimeError) throw handlerErr;
1680
+ throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler, this.source);
1681
+ }
1645
1682
  }
1646
1683
  }
1647
1684
 
1685
+
1648
1686
  async evalImport(node, env) {
1649
1687
  const spec = node.path;
1650
1688
  let lib;
@@ -1660,7 +1698,7 @@ async evalImport(node, env) {
1660
1698
  : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
1661
1699
 
1662
1700
  if (!fs.existsSync(fullPath)) {
1663
- throw new Error(`Import not found: ${spec}`);
1701
+ throw new RuntimeError(`Import not found: ${spec}`, node, this.source);
1664
1702
  }
1665
1703
 
1666
1704
  const code = fs.readFileSync(fullPath, 'utf-8');
@@ -1687,7 +1725,7 @@ async evalImport(node, env) {
1687
1725
  }
1688
1726
  if (imp.type === 'NamedImport') {
1689
1727
  if (!(imp.imported in lib)) {
1690
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1728
+ throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node, this.source);
1691
1729
  }
1692
1730
  env.define(imp.local, lib[imp.imported]);
1693
1731
  }
@@ -1703,18 +1741,32 @@ async evalBlock(node, env) {
1703
1741
  result = await this.evaluate(stmt, env);
1704
1742
  } catch (e) {
1705
1743
  if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
1706
- throw e;
1744
+ // Wrap any other error in RuntimeError with the current block node
1745
+ throw new RuntimeError(e.message || 'Error in block', stmt, this.source);
1707
1746
  }
1708
1747
  }
1709
1748
  return result;
1710
1749
  }
1711
1750
 
1751
+
1712
1752
  async evalVarDeclaration(node, env) {
1753
+ if (!node.expr) {
1754
+ throw new RuntimeError('Variable declaration requires an initializer', node, this.source);
1755
+ }
1756
+
1713
1757
  const val = await this.evaluate(node.expr, env);
1714
1758
  return env.define(node.id, val);
1715
1759
  }
1716
1760
 
1761
+
1717
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
+
1718
1770
  return {
1719
1771
  params: node.params,
1720
1772
  body: node.body,
@@ -1724,24 +1776,29 @@ evalArrowFunction(node, env) {
1724
1776
  };
1725
1777
  }
1726
1778
 
1779
+
1727
1780
  async evalAssignment(node, env) {
1728
1781
  const rightVal = await this.evaluate(node.right, env);
1729
1782
  const left = node.left;
1730
1783
 
1731
1784
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
1732
1785
  if (left.type === 'MemberExpression') {
1733
- const obj = await this.evaluate(left.object, env);
1734
- obj[left.property] = rightVal;
1735
- return rightVal;
1736
- }
1737
- if (left.type === 'IndexExpression') {
1738
- const obj = await this.evaluate(left.object, env);
1739
- const idx = await this.evaluate(left.indexer, env);
1740
- obj[idx] = rightVal;
1741
- return rightVal;
1742
- }
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
+
1799
+
1800
+ throw new RuntimeError('Invalid assignment target', node, this.source);
1743
1801
 
1744
- throw new Error('Invalid assignment target');
1745
1802
  }
1746
1803
 
1747
1804
  async evalCompoundAssignment(node, env) {
@@ -1751,7 +1808,8 @@ async evalCompoundAssignment(node, env) {
1751
1808
  if (left.type === 'Identifier') current = env.get(left.name);
1752
1809
  else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
1753
1810
  else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
1754
- else throw new Error('Invalid compound assignment target');
1811
+ else throw new RuntimeError('Invalid compound assignment target', node, this.source);
1812
+
1755
1813
 
1756
1814
  const rhs = await this.evaluate(node.right, env);
1757
1815
  let computed;
@@ -1761,7 +1819,8 @@ async evalCompoundAssignment(node, env) {
1761
1819
  case 'STAREQ': computed = current * rhs; break;
1762
1820
  case 'SLASHEQ': computed = current / rhs; break;
1763
1821
  case 'MODEQ': computed = current % rhs; break;
1764
- default: throw new Error('Unknown compound operator');
1822
+ default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node, this.source);
1823
+
1765
1824
  }
1766
1825
 
1767
1826
  if (left.type === 'Identifier') env.set(left.name, computed);
@@ -1780,21 +1839,33 @@ async evalSldeploy(node, env) {
1780
1839
 
1781
1840
  async evalAsk(node, env) {
1782
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
+
1783
1847
  const input = readlineSync.question(prompt + ' ');
1784
1848
  return input;
1785
1849
  }
1786
1850
 
1851
+
1787
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
+
1788
1857
  const val = node.expr ? await this.evaluate(node.expr, env) : null;
1789
- return this.global.define(node.id, val);
1858
+ return env.define(node.id, val);
1859
+
1790
1860
  }
1791
1861
 
1862
+
1792
1863
  async evalBinary(node, env) {
1793
1864
  const l = await this.evaluate(node.left, env);
1794
1865
  const r = await this.evaluate(node.right, env);
1795
1866
 
1796
1867
  if (node.operator === 'SLASH' && r === 0) {
1797
- throw new Error('Division by zero');
1868
+ throw new RuntimeError('Division by zero', node, this.source);
1798
1869
  }
1799
1870
 
1800
1871
  switch (node.operator) {
@@ -1809,7 +1880,8 @@ async evalBinary(node, env) {
1809
1880
  case 'LTE': return l <= r;
1810
1881
  case 'GT': return l > r;
1811
1882
  case 'GTE': return l >= r;
1812
- default: throw new Error(`Unknown binary operator ${node.operator}`);
1883
+ default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node, this.source);
1884
+
1813
1885
  }
1814
1886
  }
1815
1887
 
@@ -1817,7 +1889,8 @@ async evalLogical(node, env) {
1817
1889
  const l = await this.evaluate(node.left, env);
1818
1890
  if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
1819
1891
  if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
1820
- throw new Error(`Unknown logical operator ${node.operator}`);
1892
+ throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node, this.source);
1893
+
1821
1894
  }
1822
1895
 
1823
1896
  async evalUnary(node, env) {
@@ -1826,21 +1899,43 @@ async evalUnary(node, env) {
1826
1899
  case 'NOT': return !val;
1827
1900
  case 'MINUS': return -val;
1828
1901
  case 'PLUS': return +val;
1829
- default: throw new Error(`Unknown unary operator ${node.operator}`);
1902
+ default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node, this.source);
1903
+
1830
1904
  }
1831
1905
  }
1832
1906
 
1833
1907
  async evalIf(node, env) {
1834
1908
  const test = await this.evaluate(node.test, env);
1835
- if (test) return await this.evaluate(node.consequent, env);
1836
- if (node.alternate) return await this.evaluate(node.alternate, env);
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
+
1837
1922
  return null;
1838
1923
  }
1839
1924
 
1925
+
1840
1926
  async evalWhile(node, env) {
1841
- while (await this.evaluate(node.test, env)) {
1842
- try { await this.evaluate(node.body, env); }
1843
- catch (e) {
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) {
1844
1939
  if (e instanceof BreakSignal) break;
1845
1940
  if (e instanceof ContinueSignal) continue;
1846
1941
  throw e;
@@ -1848,6 +1943,7 @@ async evalWhile(node, env) {
1848
1943
  }
1849
1944
  return null;
1850
1945
  }
1946
+
1851
1947
  async evalFor(node, env) {
1852
1948
  // -------------------------------
1853
1949
  // Python-style: for x in iterable (with optional 'let')
@@ -1856,7 +1952,7 @@ async evalFor(node, env) {
1856
1952
  const iterable = await this.evaluate(node.iterable, env);
1857
1953
 
1858
1954
  if (iterable == null || typeof iterable !== 'object') {
1859
- throw new Error('Cannot iterate over non-iterable');
1955
+ throw new RuntimeError('Cannot iterate over non-iterable', node, this.source);
1860
1956
  }
1861
1957
 
1862
1958
  const loopVar = node.variable; // STRING from parser
@@ -1921,9 +2017,26 @@ async evalFor(node, env) {
1921
2017
 
1922
2018
  return null;
1923
2019
  }
1924
-
1925
2020
  evalFunctionDeclaration(node, env) {
1926
- const fn = { params: node.params, body: node.body, env, async: node.async || false };
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
+
1927
2040
  env.define(node.name, fn);
1928
2041
  return null;
1929
2042
  }
@@ -1933,10 +2046,10 @@ async evalCall(node, env) {
1933
2046
  if (typeof calleeEvaluated === 'function') {
1934
2047
  const args = [];
1935
2048
  for (const a of node.arguments) args.push(await this.evaluate(a, env));
1936
- return calleeEvaluated(...args);
2049
+ return await calleeEvaluated(...args);
1937
2050
  }
1938
2051
  if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
1939
- throw new Error('Call to non-function');
2052
+ throw new RuntimeError('Call to non-function', node, this.source);
1940
2053
  }
1941
2054
 
1942
2055
  const fn = calleeEvaluated;
@@ -1949,7 +2062,7 @@ async evalCall(node, env) {
1949
2062
 
1950
2063
  try {
1951
2064
  const result = await this.evaluate(fn.body, callEnv);
1952
- return fn.arrow ? result : result;
2065
+ return result;
1953
2066
  } catch (e) {
1954
2067
  if (e instanceof ReturnValue) return e.value;
1955
2068
  throw e;
@@ -1960,12 +2073,13 @@ async evalIndex(node, env) {
1960
2073
  const obj = await this.evaluate(node.object, env);
1961
2074
  const idx = await this.evaluate(node.indexer, env);
1962
2075
 
1963
- if (obj == null) throw new Error('Indexing null or undefined');
2076
+ if (obj == null) throw new RuntimeError('Indexing null or undefined', node, this.source);
2077
+
1964
2078
  if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
1965
- throw new Error('Array index out of bounds');
2079
+ throw new RuntimeError('Array index out of bounds', node, this.source);
1966
2080
  }
1967
2081
  if (typeof obj === 'object' && !(idx in obj)) {
1968
- throw new Error(`Property '${idx}' does not exist`);
2082
+ throw new RuntimeError(`Property '${idx}' does not exist`, node, this.source);
1969
2083
  }
1970
2084
 
1971
2085
  return obj[idx];
@@ -1973,14 +2087,22 @@ async evalIndex(node, env) {
1973
2087
 
1974
2088
  async evalObject(node, env) {
1975
2089
  const out = {};
1976
- for (const p of node.props) out[p.key] = await this.evaluate(p.value, env);
2090
+ for (const p of node.props) {
2091
+ if (!p.key || !p.value) {
2092
+ throw new RuntimeError('Invalid object property', node, this.source);
2093
+ }
2094
+ const key = await this.evaluate(p.key, env);
2095
+ const value = await this.evaluate(p.value, env);
2096
+ out[key] = value;
2097
+ }
1977
2098
  return out;
1978
2099
  }
1979
2100
 
2101
+
1980
2102
  async evalMember(node, env) {
1981
2103
  const obj = await this.evaluate(node.object, env);
1982
- if (obj == null) throw new Error('Member access of null or undefined');
1983
- if (!(node.property in obj)) throw new Error(`Property '${node.property}' does not exist`);
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);
1984
2106
  return obj[node.property];
1985
2107
  }
1986
2108
 
@@ -1990,7 +2112,8 @@ async evalUpdate(node, env) {
1990
2112
  if (arg.type === 'Identifier') return env.get(arg.name);
1991
2113
  if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
1992
2114
  if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
1993
- throw new Error('Invalid update target');
2115
+ throw new RuntimeError('Invalid update target', node, this.source);
2116
+
1994
2117
  };
1995
2118
  const setValue = async (v) => {
1996
2119
  if (arg.type === 'Identifier') env.set(arg.name, v);
@@ -2020,14 +2143,33 @@ module.exports = Evaluator;
2020
2143
  /***/ 211:
2021
2144
  /***/ ((module) => {
2022
2145
 
2146
+ class LexerError extends Error {
2147
+ constructor(message, line, column, source = '') {
2148
+ let output = `SyntaxError: ${message}\n`;
2149
+ output += ` at line ${line}, column ${column}\n`;
2150
+
2151
+ if (source && line != null) {
2152
+ const lines = source.split('\n');
2153
+ const srcLine = lines[line - 1] || '';
2154
+ output += ` ${srcLine}\n`;
2155
+ output += ` ${' '.repeat(column - 1)}^\n`;
2156
+ }
2157
+
2158
+ super(output);
2159
+ this.name = 'SyntaxError';
2160
+ }
2161
+ }
2162
+
2023
2163
  class Lexer {
2024
2164
  constructor(input) {
2025
- this.input = input;
2026
- this.pos = 0;
2027
- this.currentChar = input[0] || null;
2028
- this.line = 1; // current line
2029
- this.column = 1; // current column
2030
- }
2165
+ this.input = input;
2166
+ this.source = input;
2167
+ this.pos = 0;
2168
+ this.currentChar = input[0] || null;
2169
+ this.line = 1;
2170
+ this.column = 1;
2171
+ }
2172
+
2031
2173
 
2032
2174
  advance() {
2033
2175
  if (this.currentChar === '\n') {
@@ -2045,8 +2187,14 @@ class Lexer {
2045
2187
  }
2046
2188
 
2047
2189
  error(msg) {
2048
- throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
2049
- }
2190
+ throw new LexerError(
2191
+ msg,
2192
+ this.line,
2193
+ this.column,
2194
+ this.source
2195
+ );
2196
+ }
2197
+
2050
2198
 
2051
2199
  skipWhitespace() {
2052
2200
  while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
@@ -2214,25 +2362,54 @@ module.exports = Lexer;
2214
2362
  /***/ 222:
2215
2363
  /***/ ((module) => {
2216
2364
 
2217
- class Parser {
2218
- constructor(tokens) {
2219
- this.tokens = tokens;
2220
- this.pos = 0;
2221
- this.current = this.tokens[this.pos];
2365
+ class ParseError extends Error {
2366
+ constructor(message, token, source) {
2367
+ const line = token?.line ?? '?';
2368
+ const column = token?.column ?? '?';
2369
+
2370
+ let output = `SyntaxError: ${message}\n`;
2371
+
2372
+ if (source && token?.line != null) {
2373
+ const lines = source.split('\n');
2374
+ const srcLine = lines[token.line - 1] || '';
2375
+ output += ` at line ${line}, column ${column}\n`;
2376
+ output += ` ${srcLine}\n`;
2377
+ output += ` ${' '.repeat(column - 1)}^\n`;
2378
+ } else {
2379
+ output += ` at line ${line}, column ${column}\n`;
2380
+ }
2381
+
2382
+ super(output);
2383
+ this.name = 'SyntaxError';
2222
2384
  }
2385
+ }
2386
+
2387
+ class Parser {
2388
+ constructor(tokens, source = '') {
2389
+ this.tokens = tokens;
2390
+ this.source = source;
2391
+ this.pos = 0;
2392
+ this.current = this.tokens[this.pos];
2393
+ }
2394
+
2223
2395
 
2224
2396
  advance() {
2225
2397
  this.pos++;
2226
2398
  this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
2227
2399
  }
2228
2400
 
2229
- eat(type) {
2230
- if (this.current.type === type) this.advance();
2231
- else throw new Error(
2232
- `Expected ${type}, got ${this.current.type} at line ${this.current.line}, column ${this.current.column}`
2233
- );
2234
-
2401
+ eat(type) {
2402
+ if (this.current.type === type) {
2403
+ this.advance();
2404
+ } else {
2405
+ throw new ParseError(
2406
+ `Expected '${type}' but got '${this.current.type}'`,
2407
+ this.current,
2408
+ this.source
2409
+ );
2235
2410
  }
2411
+ }
2412
+
2236
2413
 
2237
2414
  peekType(offset = 1) {
2238
2415
  return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
@@ -2270,10 +2447,11 @@ class Parser {
2270
2447
  return this.expressionStatement();
2271
2448
  }
2272
2449
  }
2273
-
2274
- varDeclaration() {
2450
+ varDeclaration() {
2451
+ const t = this.current; // LET token
2275
2452
  this.eat('LET');
2276
- const id = this.current.value;
2453
+ const idToken = this.current; // identifier token
2454
+ const id = idToken.value;
2277
2455
  this.eat('IDENTIFIER');
2278
2456
 
2279
2457
  let expr = null;
@@ -2285,16 +2463,30 @@ class Parser {
2285
2463
  // semicolon optional
2286
2464
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2287
2465
 
2288
- return { type: 'VarDeclaration', id, expr };
2466
+ return {
2467
+ type: 'VarDeclaration',
2468
+ id,
2469
+ expr,
2470
+ line: t.line,
2471
+ column: t.column
2472
+ };
2289
2473
  }
2290
2474
 
2291
2475
  sldeployStatement() {
2476
+ const t = this.current; // SLDEPLOY token
2292
2477
  this.eat('SLDEPLOY');
2293
2478
  const expr = this.expression();
2294
2479
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2295
- return { type: 'SldeployStatement', expr };
2480
+ return {
2481
+ type: 'SldeployStatement',
2482
+ expr,
2483
+ line: t.line,
2484
+ column: t.column
2485
+ };
2296
2486
  }
2487
+
2297
2488
  doTrackStatement() {
2489
+ const t = this.current; // DO token
2298
2490
  this.eat('DO');
2299
2491
 
2300
2492
  const body = this.block();
@@ -2308,13 +2500,17 @@ doTrackStatement() {
2308
2500
  return {
2309
2501
  type: 'DoTrackStatement',
2310
2502
  body,
2311
- handler
2503
+ handler,
2504
+ line: t.line,
2505
+ column: t.column
2312
2506
  };
2313
2507
  }
2314
2508
 
2315
2509
  defineStatement() {
2510
+ const t = this.current; // DEFINE token
2316
2511
  this.eat('DEFINE');
2317
- const id = this.current.value;
2512
+ const idToken = this.current; // identifier token
2513
+ const id = idToken.value;
2318
2514
  this.eat('IDENTIFIER');
2319
2515
 
2320
2516
  let expr = null;
@@ -2325,10 +2521,17 @@ defineStatement() {
2325
2521
 
2326
2522
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2327
2523
 
2328
- return { type: 'DefineStatement', id, expr };
2524
+ return {
2525
+ type: 'DefineStatement',
2526
+ id,
2527
+ expr,
2528
+ line: t.line,
2529
+ column: t.column
2530
+ };
2329
2531
  }
2330
2532
 
2331
2533
  asyncFuncDeclaration() {
2534
+ const t = this.current; // first token of function (identifier)
2332
2535
  const name = this.current.value;
2333
2536
  this.eat('IDENTIFIER');
2334
2537
 
@@ -2354,10 +2557,19 @@ asyncFuncDeclaration() {
2354
2557
  }
2355
2558
 
2356
2559
  const body = this.block();
2357
- return { type: 'FunctionDeclaration', name, params, body, async: true };
2560
+ return {
2561
+ type: 'FunctionDeclaration',
2562
+ name,
2563
+ params,
2564
+ body,
2565
+ async: true,
2566
+ line: t.line,
2567
+ column: t.column
2568
+ };
2358
2569
  }
2359
2570
 
2360
2571
  ifStatement() {
2572
+ const t = this.current; // IF token
2361
2573
  this.eat('IF');
2362
2574
 
2363
2575
  let test;
@@ -2366,7 +2578,7 @@ ifStatement() {
2366
2578
  test = this.expression();
2367
2579
  this.eat('RPAREN');
2368
2580
  } else {
2369
- // python style: no parentheses
2581
+ // Python style: no parentheses
2370
2582
  test = this.expression();
2371
2583
  }
2372
2584
 
@@ -2379,10 +2591,18 @@ ifStatement() {
2379
2591
  else alternate = this.block();
2380
2592
  }
2381
2593
 
2382
- return { type: 'IfStatement', test, consequent, alternate };
2594
+ return {
2595
+ type: 'IfStatement',
2596
+ test,
2597
+ consequent,
2598
+ alternate,
2599
+ line: t.line,
2600
+ column: t.column
2601
+ };
2383
2602
  }
2384
2603
 
2385
2604
  whileStatement() {
2605
+ const t = this.current; // WHILE token
2386
2606
  this.eat('WHILE');
2387
2607
 
2388
2608
  let test;
@@ -2395,10 +2615,17 @@ whileStatement() {
2395
2615
  }
2396
2616
 
2397
2617
  const body = this.block();
2398
- return { type: 'WhileStatement', test, body };
2618
+ return {
2619
+ type: 'WhileStatement',
2620
+ test,
2621
+ body,
2622
+ line: t.line,
2623
+ column: t.column
2624
+ };
2399
2625
  }
2400
2626
 
2401
2627
  importStatement() {
2628
+ const t = this.current; // IMPORT token
2402
2629
  this.eat('IMPORT');
2403
2630
 
2404
2631
  let specifiers = [];
@@ -2407,11 +2634,13 @@ importStatement() {
2407
2634
  this.eat('AS');
2408
2635
  const name = this.current.value;
2409
2636
  this.eat('IDENTIFIER');
2410
- specifiers.push({ type: 'NamespaceImport', local: name });
2637
+ specifiers.push({ type: 'NamespaceImport', local: name, line: t.line, column: t.column });
2411
2638
  } else if (this.current.type === 'LBRACE') {
2412
2639
  this.eat('LBRACE');
2413
2640
  while (this.current.type !== 'RBRACE') {
2414
2641
  const importedName = this.current.value;
2642
+ const importedLine = this.current.line;
2643
+ const importedColumn = this.current.column;
2415
2644
  this.eat('IDENTIFIER');
2416
2645
 
2417
2646
  let localName = importedName;
@@ -2421,14 +2650,16 @@ importStatement() {
2421
2650
  this.eat('IDENTIFIER');
2422
2651
  }
2423
2652
 
2424
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2653
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName, line: importedLine, column: importedColumn });
2425
2654
  if (this.current.type === 'COMMA') this.eat('COMMA');
2426
2655
  }
2427
2656
  this.eat('RBRACE');
2428
2657
  } else if (this.current.type === 'IDENTIFIER') {
2429
2658
  const localName = this.current.value;
2659
+ const localLine = this.current.line;
2660
+ const localColumn = this.current.column;
2430
2661
  this.eat('IDENTIFIER');
2431
- specifiers.push({ type: 'DefaultImport', local: localName });
2662
+ specifiers.push({ type: 'DefaultImport', local: localName, line: localLine, column: localColumn });
2432
2663
  } else {
2433
2664
  throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
2434
2665
  }
@@ -2440,9 +2671,17 @@ importStatement() {
2440
2671
 
2441
2672
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2442
2673
 
2443
- return { type: 'ImportStatement', path: pathToken.value, specifiers };
2674
+ return {
2675
+ type: 'ImportStatement',
2676
+ path: pathToken.value,
2677
+ specifiers,
2678
+ line: t.line,
2679
+ column: t.column
2680
+ };
2444
2681
  }
2682
+
2445
2683
  forStatement() {
2684
+ const t = this.current; // FOR token
2446
2685
  this.eat('FOR');
2447
2686
 
2448
2687
  // --- Python-style: for variable in iterable (supports optional 'let') ---
@@ -2450,17 +2689,22 @@ forStatement() {
2450
2689
  (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN')) {
2451
2690
 
2452
2691
  let variable;
2692
+ let variableLine, variableColumn;
2453
2693
  let iterable;
2454
2694
  let letKeyword = false;
2455
2695
 
2456
2696
  if (this.current.type === 'LET') {
2457
2697
  letKeyword = true;
2458
2698
  this.eat('LET');
2699
+ variableLine = this.current.line;
2700
+ variableColumn = this.current.column;
2459
2701
  variable = this.current.value;
2460
2702
  this.eat('IDENTIFIER');
2461
2703
  this.eat('IN');
2462
2704
  iterable = this.expression();
2463
2705
  } else {
2706
+ variableLine = this.current.line;
2707
+ variableColumn = this.current.column;
2464
2708
  variable = this.current.value;
2465
2709
  this.eat('IDENTIFIER');
2466
2710
  this.eat('IN');
@@ -2468,7 +2712,17 @@ forStatement() {
2468
2712
  }
2469
2713
 
2470
2714
  const body = this.block();
2471
- return { type: 'ForInStatement', variable, iterable, letKeyword, body };
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
+ };
2472
2726
  }
2473
2727
 
2474
2728
  // --- C-style: for(init; test; update) ---
@@ -2503,37 +2757,51 @@ forStatement() {
2503
2757
  }
2504
2758
 
2505
2759
  const body = this.block();
2506
- return { type: 'ForStatement', init, test, update, body };
2760
+ return {
2761
+ type: 'ForStatement',
2762
+ init,
2763
+ test,
2764
+ update,
2765
+ body,
2766
+ line: t.line,
2767
+ column: t.column
2768
+ };
2507
2769
  }
2508
2770
 
2509
2771
  breakStatement() {
2772
+ const t = this.current; // BREAK token
2510
2773
  this.eat('BREAK');
2511
2774
  // Python-style: no semicolon needed, ignore if present
2512
2775
  if (this.current.type === 'SEMICOLON') this.advance();
2513
- return { type: 'BreakStatement' };
2776
+ return { type: 'BreakStatement', line: t.line, column: t.column };
2514
2777
  }
2515
2778
 
2516
2779
  continueStatement() {
2780
+ const t = this.current; // CONTINUE token
2517
2781
  this.eat('CONTINUE');
2518
2782
  // Python-style: no semicolon needed, ignore if present
2519
2783
  if (this.current.type === 'SEMICOLON') this.advance();
2520
- return { type: 'ContinueStatement' };
2784
+ return { type: 'ContinueStatement', line: t.line, column: t.column };
2521
2785
  }
2522
2786
 
2523
2787
  funcDeclaration() {
2788
+ const t = this.current; // FUNC token
2524
2789
  this.eat('FUNC');
2525
- const name = this.current.value;
2790
+ const nameToken = this.current;
2791
+ const name = nameToken.value;
2526
2792
  this.eat('IDENTIFIER');
2527
2793
 
2528
2794
  let params = [];
2529
2795
  if (this.current.type === 'LPAREN') {
2530
2796
  this.eat('LPAREN');
2531
2797
  if (this.current.type !== 'RPAREN') {
2532
- params.push(this.current.value);
2798
+ const paramToken = this.current;
2799
+ params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
2533
2800
  this.eat('IDENTIFIER');
2534
2801
  while (this.current.type === 'COMMA') {
2535
2802
  this.eat('COMMA');
2536
- params.push(this.current.value);
2803
+ const paramToken2 = this.current;
2804
+ params.push({ name: paramToken2.value, line: paramToken2.line, column: paramToken2.column });
2537
2805
  this.eat('IDENTIFIER');
2538
2806
  }
2539
2807
  }
@@ -2541,16 +2809,18 @@ funcDeclaration() {
2541
2809
  } else {
2542
2810
  // Python-style: single param without parentheses
2543
2811
  if (this.current.type === 'IDENTIFIER') {
2544
- params.push(this.current.value);
2812
+ const paramToken = this.current;
2813
+ params.push({ name: paramToken.value, line: paramToken.line, column: paramToken.column });
2545
2814
  this.eat('IDENTIFIER');
2546
2815
  }
2547
2816
  }
2548
2817
 
2549
2818
  const body = this.block();
2550
- return { type: 'FunctionDeclaration', name, params, body };
2819
+ return { type: 'FunctionDeclaration', name, params, body, line: t.line, column: t.column };
2551
2820
  }
2552
2821
 
2553
2822
  returnStatement() {
2823
+ const t = this.current; // RETURN token
2554
2824
  this.eat('RETURN');
2555
2825
 
2556
2826
  let argument = null;
@@ -2561,24 +2831,26 @@ returnStatement() {
2561
2831
  // semicolon optional
2562
2832
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2563
2833
 
2564
- return { type: 'ReturnStatement', argument };
2834
+ return { type: 'ReturnStatement', argument, line: t.line, column: t.column };
2565
2835
  }
2566
2836
 
2567
2837
  block() {
2838
+ const t = this.current; // LBRACE token
2568
2839
  this.eat('LBRACE');
2569
2840
  const body = [];
2570
2841
  while (this.current.type !== 'RBRACE') {
2571
2842
  body.push(this.statement());
2572
2843
  }
2573
2844
  this.eat('RBRACE');
2574
- return { type: 'BlockStatement', body };
2845
+ return { type: 'BlockStatement', body, line: t.line, column: t.column };
2575
2846
  }
2576
2847
 
2577
2848
  expressionStatement() {
2849
+ const exprToken = this.current; // first token of the expression
2578
2850
  const expr = this.expression();
2579
2851
  // semicolon optional
2580
2852
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2581
- return { type: 'ExpressionStatement', expression: expr };
2853
+ return { type: 'ExpressionStatement', expression: expr, line: exprToken.line, column: exprToken.column };
2582
2854
  }
2583
2855
 
2584
2856
  expression() {
@@ -2589,17 +2861,19 @@ assignment() {
2589
2861
  const node = this.logicalOr();
2590
2862
  const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
2591
2863
 
2592
- if (compoundOps.includes(this.current.type)) {
2593
- const op = this.current.type;
2864
+ const t = this.current;
2865
+
2866
+ if (compoundOps.includes(t.type)) {
2867
+ const op = t.type;
2594
2868
  this.eat(op);
2595
2869
  const right = this.assignment();
2596
- return { type: 'CompoundAssignment', operator: op, left: node, right };
2870
+ return { type: 'CompoundAssignment', operator: op, left: node, right, line: t.line, column: t.column };
2597
2871
  }
2598
2872
 
2599
- if (this.current.type === 'EQUAL') {
2873
+ if (t.type === 'EQUAL') {
2600
2874
  this.eat('EQUAL');
2601
2875
  const right = this.assignment();
2602
- return { type: 'AssignmentExpression', left: node, right };
2876
+ return { type: 'AssignmentExpression', left: node, right, line: t.line, column: t.column };
2603
2877
  }
2604
2878
 
2605
2879
  return node;
@@ -2608,9 +2882,10 @@ assignment() {
2608
2882
  logicalOr() {
2609
2883
  let node = this.logicalAnd();
2610
2884
  while (this.current.type === 'OR') {
2611
- const op = this.current.type;
2885
+ const t = this.current;
2886
+ const op = t.type;
2612
2887
  this.eat(op);
2613
- 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 };
2614
2889
  }
2615
2890
  return node;
2616
2891
  }
@@ -2618,18 +2893,21 @@ logicalOr() {
2618
2893
  logicalAnd() {
2619
2894
  let node = this.equality();
2620
2895
  while (this.current.type === 'AND') {
2621
- const op = this.current.type;
2896
+ const t = this.current;
2897
+ const op = t.type;
2622
2898
  this.eat(op);
2623
- 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 };
2624
2900
  }
2625
2901
  return node;
2626
2902
  }
2903
+
2627
2904
  equality() {
2628
2905
  let node = this.comparison();
2629
2906
  while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
2630
- const op = this.current.type;
2907
+ const t = this.current;
2908
+ const op = t.type;
2631
2909
  this.eat(op);
2632
- 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 };
2633
2911
  }
2634
2912
  return node;
2635
2913
  }
@@ -2637,9 +2915,10 @@ equality() {
2637
2915
  comparison() {
2638
2916
  let node = this.term();
2639
2917
  while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
2640
- const op = this.current.type;
2918
+ const t = this.current;
2919
+ const op = t.type;
2641
2920
  this.eat(op);
2642
- 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 };
2643
2922
  }
2644
2923
  return node;
2645
2924
  }
@@ -2647,9 +2926,10 @@ comparison() {
2647
2926
  term() {
2648
2927
  let node = this.factor();
2649
2928
  while (['PLUS', 'MINUS'].includes(this.current.type)) {
2650
- const op = this.current.type;
2929
+ const t = this.current;
2930
+ const op = t.type;
2651
2931
  this.eat(op);
2652
- 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 };
2653
2933
  }
2654
2934
  return node;
2655
2935
  }
@@ -2657,26 +2937,43 @@ term() {
2657
2937
  factor() {
2658
2938
  let node = this.unary();
2659
2939
  while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
2660
- const op = this.current.type;
2940
+ const t = this.current;
2941
+ const op = t.type;
2661
2942
  this.eat(op);
2662
- 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 };
2663
2944
  }
2664
2945
  return node;
2665
2946
  }
2666
2947
 
2948
+
2667
2949
  unary() {
2668
- if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
2669
- const op = this.current.type;
2950
+ const t = this.current;
2951
+
2952
+ if (['NOT', 'MINUS', 'PLUS'].includes(t.type)) {
2953
+ const op = t.type;
2670
2954
  this.eat(op);
2671
- return { type: 'UnaryExpression', operator: op, argument: this.unary() };
2955
+ return {
2956
+ type: 'UnaryExpression',
2957
+ operator: op,
2958
+ argument: this.unary(),
2959
+ line: t.line,
2960
+ column: t.column
2961
+ };
2672
2962
  }
2673
2963
 
2674
2964
  // Python-like: ignore ++ and -- if not used
2675
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2676
- const op = this.current.type;
2965
+ if (t.type === 'PLUSPLUS' || t.type === 'MINUSMINUS') {
2966
+ const op = t.type;
2677
2967
  this.eat(op);
2678
2968
  const argument = this.unary();
2679
- return { type: 'UpdateExpression', operator: op, argument, prefix: true };
2969
+ return {
2970
+ type: 'UpdateExpression',
2971
+ operator: op,
2972
+ argument,
2973
+ prefix: true,
2974
+ line: t.line,
2975
+ column: t.column
2976
+ };
2680
2977
  }
2681
2978
 
2682
2979
  return this.postfix();
@@ -2685,15 +2982,21 @@ unary() {
2685
2982
  postfix() {
2686
2983
  let node = this.primary();
2687
2984
  while (true) {
2688
- if (this.current.type === 'LBRACKET') {
2985
+ const t = this.current;
2986
+
2987
+ if (t.type === 'LBRACKET') {
2988
+ const startLine = t.line;
2989
+ const startCol = t.column;
2689
2990
  this.eat('LBRACKET');
2690
2991
  const index = this.expression();
2691
2992
  if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
2692
- node = { type: 'IndexExpression', object: node, indexer: index };
2993
+ node = { type: 'IndexExpression', object: node, indexer: index, line: startLine, column: startCol };
2693
2994
  continue;
2694
2995
  }
2695
2996
 
2696
- if (this.current.type === 'LPAREN') {
2997
+ if (t.type === 'LPAREN') {
2998
+ const startLine = t.line;
2999
+ const startCol = t.column;
2697
3000
  this.eat('LPAREN');
2698
3001
  const args = [];
2699
3002
  while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
@@ -2701,30 +3004,30 @@ postfix() {
2701
3004
  if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
2702
3005
  }
2703
3006
  if (this.current.type === 'RPAREN') this.eat('RPAREN');
2704
- node = { type: 'CallExpression', callee: node, arguments: args };
3007
+ node = { type: 'CallExpression', callee: node, arguments: args, line: startLine, column: startCol };
2705
3008
  continue;
2706
3009
  }
2707
3010
 
2708
- if (this.current.type === 'DOT') {
3011
+ if (t.type === 'DOT') {
3012
+ const startLine = t.line;
3013
+ const startCol = t.column;
2709
3014
  this.eat('DOT');
2710
3015
  const property = this.current.value || '';
2711
3016
  if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
2712
- node = { type: 'MemberExpression', object: node, property };
3017
+ node = { type: 'MemberExpression', object: node, property, line: startLine, column: startCol };
2713
3018
  continue;
2714
3019
  }
2715
3020
 
2716
- if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
2717
- const op = this.current.type;
2718
- this.eat(op);
2719
- 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 };
2720
3024
  continue;
2721
3025
  }
2722
3026
 
2723
- // Python-style: implicit function calls (if identifier followed by identifier)
2724
- if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
2725
- 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 };
2726
3029
  this.eat('IDENTIFIER');
2727
- node = { type: 'CallExpression', callee: node, arguments: [argNode] };
3030
+ node = { type: 'CallExpression', callee: node, arguments: [argNode], line: node.line, column: node.column };
2728
3031
  continue;
2729
3032
  }
2730
3033
 
@@ -2734,12 +3037,18 @@ postfix() {
2734
3037
  }
2735
3038
 
2736
3039
  arrowFunction(params) {
2737
- if (this.current.type === 'ARROW') this.eat('ARROW');
3040
+ const t = this.current;
3041
+ if (t.type === 'ARROW') this.eat('ARROW');
2738
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
+
2739
3046
  return {
2740
3047
  type: 'ArrowFunctionExpression',
2741
3048
  params,
2742
- body
3049
+ body,
3050
+ line: startLine,
3051
+ column: startCol
2743
3052
  };
2744
3053
  }
2745
3054
 
@@ -2747,16 +3056,35 @@ arrowFunction(params) {
2747
3056
  primary() {
2748
3057
  const t = this.current;
2749
3058
 
2750
- if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2751
- if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2752
- if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2753
- if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2754
- if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
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
+ }
2755
3083
 
2756
3084
  if (t.type === 'AWAIT') {
2757
3085
  this.eat('AWAIT');
2758
3086
  const argument = this.expression();
2759
- return { type: 'AwaitExpression', argument };
3087
+ return { type: 'AwaitExpression', argument, line: t.line, column: t.column };
2760
3088
  }
2761
3089
 
2762
3090
  if (t.type === 'NEW') {
@@ -2772,7 +3100,7 @@ arrowFunction(params) {
2772
3100
  }
2773
3101
  }
2774
3102
  this.eat('RPAREN');
2775
- return { type: 'NewExpression', callee, arguments: args };
3103
+ return { type: 'NewExpression', callee, arguments: args, line: t.line, column: t.column };
2776
3104
  }
2777
3105
 
2778
3106
  if (t.type === 'ASK') {
@@ -2787,7 +3115,13 @@ arrowFunction(params) {
2787
3115
  }
2788
3116
  }
2789
3117
  this.eat('RPAREN');
2790
- return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
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
+ };
2791
3125
  }
2792
3126
 
2793
3127
  if (t.type === 'IDENTIFIER') {
@@ -2795,13 +3129,15 @@ arrowFunction(params) {
2795
3129
  this.eat('IDENTIFIER');
2796
3130
 
2797
3131
  if (this.current.type === 'ARROW') {
2798
- return this.arrowFunction([name]);
3132
+ return this.arrowFunction([{ type: 'Identifier', name, line: t.line, column: t.column }]);
2799
3133
  }
2800
3134
 
2801
- return { type: 'Identifier', name };
3135
+ return { type: 'Identifier', name, line: t.line, column: t.column };
2802
3136
  }
2803
3137
 
2804
3138
  if (t.type === 'LPAREN') {
3139
+ const startLine = t.line;
3140
+ const startCol = t.column;
2805
3141
  this.eat('LPAREN');
2806
3142
  const elements = [];
2807
3143
 
@@ -2817,10 +3153,12 @@ arrowFunction(params) {
2817
3153
 
2818
3154
  if (this.current.type === 'ARROW') return this.arrowFunction(elements);
2819
3155
 
2820
- return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
3156
+ return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements, line: startLine, column: startCol };
2821
3157
  }
2822
3158
 
2823
3159
  if (t.type === 'LBRACKET') {
3160
+ const startLine = t.line;
3161
+ const startCol = t.column;
2824
3162
  this.eat('LBRACKET');
2825
3163
  const elements = [];
2826
3164
  if (this.current.type !== 'RBRACKET') {
@@ -2831,10 +3169,12 @@ arrowFunction(params) {
2831
3169
  }
2832
3170
  }
2833
3171
  this.eat('RBRACKET');
2834
- return { type: 'ArrayExpression', elements };
3172
+ return { type: 'ArrayExpression', elements, line: startLine, column: startCol };
2835
3173
  }
2836
3174
 
2837
3175
  if (t.type === 'LBRACE') {
3176
+ const startLine = t.line;
3177
+ const startCol = t.column;
2838
3178
  this.eat('LBRACE');
2839
3179
  const props = [];
2840
3180
  while (this.current.type !== 'RBRACE') {
@@ -2845,12 +3185,17 @@ arrowFunction(params) {
2845
3185
  if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
2846
3186
  }
2847
3187
  this.eat('RBRACE');
2848
- return { type: 'ObjectExpression', props };
3188
+ return { type: 'ObjectExpression', props, line: startLine, column: startCol };
2849
3189
  }
2850
3190
 
2851
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
3191
+ throw new ParseError(
3192
+ `Unexpected token '${t.type}'`,
3193
+ t,
3194
+ this.source
3195
+ );
2852
3196
  }
2853
3197
 
3198
+
2854
3199
  }
2855
3200
 
2856
3201
  module.exports = Parser;
@@ -2955,7 +3300,7 @@ const Lexer = __nccwpck_require__(211);
2955
3300
  const Parser = __nccwpck_require__(222);
2956
3301
  const Evaluator = __nccwpck_require__(112);
2957
3302
 
2958
- const VERSION = '1.0.46';
3303
+ const VERSION = '1.0.48';
2959
3304
 
2960
3305
  const COLOR = {
2961
3306
  reset: '\x1b[0m',
@@ -2966,7 +3311,8 @@ const COLOR = {
2966
3311
  cyan: '\x1b[36m',
2967
3312
  gray: '\x1b[90m',
2968
3313
  green: '\x1b[32m',
2969
- magenta: '\x1b[35m'
3314
+ magenta: '\x1b[35m',
3315
+ white: '\x1b[37m'
2970
3316
  };
2971
3317
 
2972
3318
  function waitAndExit(code = 0) {
@@ -2977,7 +3323,7 @@ function waitAndExit(code = 0) {
2977
3323
  }
2978
3324
 
2979
3325
  function fatal(msg) {
2980
- console.error(COLOR.red + msg + COLOR.reset);
3326
+ console.error(COLOR.white + msg + COLOR.reset);
2981
3327
  waitAndExit(1);
2982
3328
  }
2983
3329
 
@@ -3118,24 +3464,35 @@ function savePrompt(lines) {
3118
3464
  waitAndExit(0);
3119
3465
  }
3120
3466
 
3121
- function runFile(filePath, isTemp = false, callback) {
3467
+ async function runFile(filePath, isTemp = false, callback) {
3122
3468
  const code = fs.readFileSync(filePath, 'utf8');
3123
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
+
3124
3476
  try {
3125
- const lexer = new Lexer(code);
3126
- const parser = new Parser(lexer.getTokens());
3127
- const ast = parser.parse();
3128
- new Evaluator().evaluate(ast);
3477
+ await evaluator.evaluate(ast);
3129
3478
  } catch (e) {
3130
- console.error(COLOR.red + e.message + COLOR.reset);
3479
+ if (e.name === 'RuntimeError' || e.name === 'SyntaxError') {
3480
+ console.error(COLOR.white + e.message + COLOR.reset);
3481
+ } else {
3482
+ console.error(COLOR.white + `Unexpected Error: ${e.message || e}` + COLOR.reset);
3483
+ }
3131
3484
  return waitAndExit(1);
3132
3485
  }
3133
3486
 
3134
-
3135
3487
  if (callback) callback();
3136
- if (isTemp) try { fs.unlinkSync(filePath); } catch {}
3488
+
3489
+ if (isTemp) {
3490
+ try { fs.unlinkSync(filePath); } catch {}
3491
+ }
3137
3492
  }
3138
3493
 
3494
+
3495
+
3139
3496
  if (!args[0].startsWith('--')) {
3140
3497
  runFile(path.resolve(args[0]));
3141
3498
  }