starlight-cli 1.0.45 → 1.0.47

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,14 @@ class ReturnValue {
1352
1352
  }
1353
1353
  class BreakSignal {}
1354
1354
  class ContinueSignal {}
1355
+ class RuntimeError extends Error {
1356
+ constructor(message, node) {
1357
+ const line = node?.line ?? '?';
1358
+ const column = node?.column ?? '?';
1359
+ super(`RuntimeError: ${message} at line ${line}, column ${column}`);
1360
+ this.name = 'RuntimeError';
1361
+ }
1362
+ }
1355
1363
 
1356
1364
  class Environment {
1357
1365
  constructor(parent = null) {
@@ -1368,7 +1376,7 @@ class Environment {
1368
1376
  get(name) {
1369
1377
  if (name in this.store) return this.store[name];
1370
1378
  if (this.parent) return this.parent.get(name);
1371
- throw new Error(`Undefined variable: ${name}`);
1379
+ throw new RuntimeError(`Undefined variable: ${name}`);
1372
1380
  }
1373
1381
 
1374
1382
  set(name, value) {
@@ -1402,8 +1410,8 @@ formatValue(value, seen = new Set()) {
1402
1410
 
1403
1411
  const t = typeof value;
1404
1412
 
1405
- // Strings (quoted)
1406
- if (t === 'string') return `"${value}"`;
1413
+ // Strings (no quotes)
1414
+ if (t === 'string') return value;
1407
1415
 
1408
1416
  // Numbers
1409
1417
  if (t === 'number') return String(value);
@@ -1423,7 +1431,7 @@ formatValue(value, seen = new Set()) {
1423
1431
  return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
1424
1432
  }
1425
1433
 
1426
- // Objects (including your language functions)
1434
+ // Objects (including user-defined functions)
1427
1435
  if (t === 'object') {
1428
1436
  // Detect user-defined functions (AST-based)
1429
1437
  if (value.params && value.body) {
@@ -1472,11 +1480,11 @@ this.global.define('range', (...args) => {
1472
1480
  end = Number(args[1]);
1473
1481
  step = Number(args[2]);
1474
1482
  } else {
1475
- throw new Error('range() expects 1 to 3 arguments');
1483
+ throw new RuntimeError('range() expects 1 to 3 arguments');
1476
1484
  }
1477
1485
 
1478
1486
  if (step === 0) {
1479
- throw new Error('range() step cannot be 0');
1487
+ throw new RuntimeError('range() step cannot be 0');
1480
1488
  }
1481
1489
 
1482
1490
  const result = [];
@@ -1500,7 +1508,7 @@ this.global.define('range', (...args) => {
1500
1508
  this.global.define('num', arg => {
1501
1509
  const n = Number(arg);
1502
1510
  if (Number.isNaN(n)) {
1503
- throw new Error('Cannot convert value to number');
1511
+ throw new RuntimeError('Cannot convert value to number');
1504
1512
  }
1505
1513
  return n;
1506
1514
  });
@@ -1611,7 +1619,7 @@ case 'DoTrackStatement':
1611
1619
  }
1612
1620
 
1613
1621
  if (typeof callee !== 'function') {
1614
- throw new Error('NewExpression callee is not a function');
1622
+ throw new RuntimeError('NewExpression callee is not a function', node);
1615
1623
  }
1616
1624
 
1617
1625
  const args = [];
@@ -1620,7 +1628,8 @@ case 'DoTrackStatement':
1620
1628
  }
1621
1629
 
1622
1630
  default:
1623
- throw new Error(`Unknown node type in evaluator: ${node.type}`);
1631
+ throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node);
1632
+
1624
1633
  }
1625
1634
  }
1626
1635
 
@@ -1636,15 +1645,26 @@ async evalDoTrack(node, env) {
1636
1645
  try {
1637
1646
  return await this.evaluate(node.body, env);
1638
1647
  } catch (err) {
1639
- if (!node.handler) throw err;
1648
+ if (!node.handler) {
1649
+ // Wrap any raw error into RuntimeError with line info
1650
+ if (err instanceof RuntimeError) throw err;
1651
+ throw new RuntimeError(err.message || 'Error in doTrack body', node.body);
1652
+ }
1640
1653
 
1641
1654
  const trackEnv = new Environment(env);
1642
1655
  trackEnv.define('error', err);
1643
1656
 
1644
- return await this.evaluate(node.handler, trackEnv);
1657
+ try {
1658
+ return await this.evaluate(node.handler, trackEnv);
1659
+ } catch (handlerErr) {
1660
+ // Wrap handler errors as well
1661
+ if (handlerErr instanceof RuntimeError) throw handlerErr;
1662
+ throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler);
1663
+ }
1645
1664
  }
1646
1665
  }
1647
1666
 
1667
+
1648
1668
  async evalImport(node, env) {
1649
1669
  const spec = node.path;
1650
1670
  let lib;
@@ -1660,7 +1680,7 @@ async evalImport(node, env) {
1660
1680
  : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
1661
1681
 
1662
1682
  if (!fs.existsSync(fullPath)) {
1663
- throw new Error(`Import not found: ${spec}`);
1683
+ throw new RuntimeError(`Import not found: ${spec}`, node);
1664
1684
  }
1665
1685
 
1666
1686
  const code = fs.readFileSync(fullPath, 'utf-8');
@@ -1687,7 +1707,7 @@ async evalImport(node, env) {
1687
1707
  }
1688
1708
  if (imp.type === 'NamedImport') {
1689
1709
  if (!(imp.imported in lib)) {
1690
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1710
+ throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node);
1691
1711
  }
1692
1712
  env.define(imp.local, lib[imp.imported]);
1693
1713
  }
@@ -1703,12 +1723,14 @@ async evalBlock(node, env) {
1703
1723
  result = await this.evaluate(stmt, env);
1704
1724
  } catch (e) {
1705
1725
  if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
1706
- throw e;
1726
+ // Wrap any other error in RuntimeError with the current block node
1727
+ throw new RuntimeError(e.message || 'Error in block', stmt);
1707
1728
  }
1708
1729
  }
1709
1730
  return result;
1710
1731
  }
1711
1732
 
1733
+
1712
1734
  async evalVarDeclaration(node, env) {
1713
1735
  const val = await this.evaluate(node.expr, env);
1714
1736
  return env.define(node.id, val);
@@ -1741,7 +1763,8 @@ async evalAssignment(node, env) {
1741
1763
  return rightVal;
1742
1764
  }
1743
1765
 
1744
- throw new Error('Invalid assignment target');
1766
+ throw new RuntimeError('Invalid assignment target', node);
1767
+
1745
1768
  }
1746
1769
 
1747
1770
  async evalCompoundAssignment(node, env) {
@@ -1751,7 +1774,8 @@ async evalCompoundAssignment(node, env) {
1751
1774
  if (left.type === 'Identifier') current = env.get(left.name);
1752
1775
  else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
1753
1776
  else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
1754
- else throw new Error('Invalid compound assignment target');
1777
+ else throw new RuntimeError('Invalid compound assignment target', node);
1778
+
1755
1779
 
1756
1780
  const rhs = await this.evaluate(node.right, env);
1757
1781
  let computed;
@@ -1761,7 +1785,8 @@ async evalCompoundAssignment(node, env) {
1761
1785
  case 'STAREQ': computed = current * rhs; break;
1762
1786
  case 'SLASHEQ': computed = current / rhs; break;
1763
1787
  case 'MODEQ': computed = current % rhs; break;
1764
- default: throw new Error('Unknown compound operator');
1788
+ default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node);
1789
+
1765
1790
  }
1766
1791
 
1767
1792
  if (left.type === 'Identifier') env.set(left.name, computed);
@@ -1794,7 +1819,7 @@ async evalBinary(node, env) {
1794
1819
  const r = await this.evaluate(node.right, env);
1795
1820
 
1796
1821
  if (node.operator === 'SLASH' && r === 0) {
1797
- throw new Error('Division by zero');
1822
+ throw new RuntimeError('Division by zero', node);
1798
1823
  }
1799
1824
 
1800
1825
  switch (node.operator) {
@@ -1809,7 +1834,8 @@ async evalBinary(node, env) {
1809
1834
  case 'LTE': return l <= r;
1810
1835
  case 'GT': return l > r;
1811
1836
  case 'GTE': return l >= r;
1812
- default: throw new Error(`Unknown binary operator ${node.operator}`);
1837
+ default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node);
1838
+
1813
1839
  }
1814
1840
  }
1815
1841
 
@@ -1817,7 +1843,8 @@ async evalLogical(node, env) {
1817
1843
  const l = await this.evaluate(node.left, env);
1818
1844
  if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
1819
1845
  if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
1820
- throw new Error(`Unknown logical operator ${node.operator}`);
1846
+ throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node);
1847
+
1821
1848
  }
1822
1849
 
1823
1850
  async evalUnary(node, env) {
@@ -1826,7 +1853,8 @@ async evalUnary(node, env) {
1826
1853
  case 'NOT': return !val;
1827
1854
  case 'MINUS': return -val;
1828
1855
  case 'PLUS': return +val;
1829
- default: throw new Error(`Unknown unary operator ${node.operator}`);
1856
+ default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node);
1857
+
1830
1858
  }
1831
1859
  }
1832
1860
 
@@ -1856,7 +1884,7 @@ async evalFor(node, env) {
1856
1884
  const iterable = await this.evaluate(node.iterable, env);
1857
1885
 
1858
1886
  if (iterable == null || typeof iterable !== 'object') {
1859
- throw new Error('Cannot iterate over non-iterable');
1887
+ throw new RuntimeError('Cannot iterate over non-iterable', node);
1860
1888
  }
1861
1889
 
1862
1890
  const loopVar = node.variable; // STRING from parser
@@ -1936,7 +1964,7 @@ async evalCall(node, env) {
1936
1964
  return calleeEvaluated(...args);
1937
1965
  }
1938
1966
  if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
1939
- throw new Error('Call to non-function');
1967
+ throw new RuntimeError('Call to non-function', node);
1940
1968
  }
1941
1969
 
1942
1970
  const fn = calleeEvaluated;
@@ -1960,12 +1988,13 @@ async evalIndex(node, env) {
1960
1988
  const obj = await this.evaluate(node.object, env);
1961
1989
  const idx = await this.evaluate(node.indexer, env);
1962
1990
 
1963
- if (obj == null) throw new Error('Indexing null or undefined');
1991
+ if (obj == null) throw new RuntimeError('Indexing null or undefined', node);
1992
+
1964
1993
  if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
1965
- throw new Error('Array index out of bounds');
1994
+ throw new RuntimeError('Array index out of bounds', node);
1966
1995
  }
1967
1996
  if (typeof obj === 'object' && !(idx in obj)) {
1968
- throw new Error(`Property '${idx}' does not exist`);
1997
+ throw new RuntimeError(`Property '${idx}' does not exist`, node);
1969
1998
  }
1970
1999
 
1971
2000
  return obj[idx];
@@ -1973,14 +2002,22 @@ async evalIndex(node, env) {
1973
2002
 
1974
2003
  async evalObject(node, env) {
1975
2004
  const out = {};
1976
- for (const p of node.props) out[p.key] = await this.evaluate(p.value, env);
2005
+ for (const p of node.props) {
2006
+ if (!p.key || !p.value) {
2007
+ throw new RuntimeError('Invalid object property', node);
2008
+ }
2009
+ const key = await this.evaluate(p.key, env);
2010
+ const value = await this.evaluate(p.value, env);
2011
+ out[key] = value;
2012
+ }
1977
2013
  return out;
1978
2014
  }
1979
2015
 
2016
+
1980
2017
  async evalMember(node, env) {
1981
2018
  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`);
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);
1984
2021
  return obj[node.property];
1985
2022
  }
1986
2023
 
@@ -1990,7 +2027,8 @@ async evalUpdate(node, env) {
1990
2027
  if (arg.type === 'Identifier') return env.get(arg.name);
1991
2028
  if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
1992
2029
  if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
1993
- throw new Error('Invalid update target');
2030
+ throw new RuntimeError('Invalid update target', node);
2031
+
1994
2032
  };
1995
2033
  const setValue = async (v) => {
1996
2034
  if (arg.type === 'Identifier') env.set(arg.name, v);
@@ -2020,14 +2058,33 @@ module.exports = Evaluator;
2020
2058
  /***/ 211:
2021
2059
  /***/ ((module) => {
2022
2060
 
2061
+ class LexerError extends Error {
2062
+ constructor(message, line, column, source = '') {
2063
+ let output = `SyntaxError: ${message}\n`;
2064
+ output += ` at line ${line}, column ${column}\n`;
2065
+
2066
+ if (source && line != null) {
2067
+ const lines = source.split('\n');
2068
+ const srcLine = lines[line - 1] || '';
2069
+ output += ` ${srcLine}\n`;
2070
+ output += ` ${' '.repeat(column - 1)}^\n`;
2071
+ }
2072
+
2073
+ super(output);
2074
+ this.name = 'SyntaxError';
2075
+ }
2076
+ }
2077
+
2023
2078
  class Lexer {
2024
2079
  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
- }
2080
+ this.input = input;
2081
+ this.source = input;
2082
+ this.pos = 0;
2083
+ this.currentChar = input[0] || null;
2084
+ this.line = 1;
2085
+ this.column = 1;
2086
+ }
2087
+
2031
2088
 
2032
2089
  advance() {
2033
2090
  if (this.currentChar === '\n') {
@@ -2045,8 +2102,14 @@ class Lexer {
2045
2102
  }
2046
2103
 
2047
2104
  error(msg) {
2048
- throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
2049
- }
2105
+ throw new LexerError(
2106
+ msg,
2107
+ this.line,
2108
+ this.column,
2109
+ this.source
2110
+ );
2111
+ }
2112
+
2050
2113
 
2051
2114
  skipWhitespace() {
2052
2115
  while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
@@ -2214,25 +2277,54 @@ module.exports = Lexer;
2214
2277
  /***/ 222:
2215
2278
  /***/ ((module) => {
2216
2279
 
2217
- class Parser {
2218
- constructor(tokens) {
2219
- this.tokens = tokens;
2220
- this.pos = 0;
2221
- this.current = this.tokens[this.pos];
2280
+ class ParseError extends Error {
2281
+ constructor(message, token, source) {
2282
+ const line = token?.line ?? '?';
2283
+ const column = token?.column ?? '?';
2284
+
2285
+ let output = `SyntaxError: ${message}\n`;
2286
+
2287
+ if (source && token?.line != null) {
2288
+ const lines = source.split('\n');
2289
+ const srcLine = lines[token.line - 1] || '';
2290
+ output += ` at line ${line}, column ${column}\n`;
2291
+ output += ` ${srcLine}\n`;
2292
+ output += ` ${' '.repeat(column - 1)}^\n`;
2293
+ } else {
2294
+ output += ` at line ${line}, column ${column}\n`;
2295
+ }
2296
+
2297
+ super(output);
2298
+ this.name = 'SyntaxError';
2222
2299
  }
2300
+ }
2301
+
2302
+ class Parser {
2303
+ constructor(tokens, source = '') {
2304
+ this.tokens = tokens;
2305
+ this.source = source;
2306
+ this.pos = 0;
2307
+ this.current = this.tokens[this.pos];
2308
+ }
2309
+
2223
2310
 
2224
2311
  advance() {
2225
2312
  this.pos++;
2226
2313
  this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
2227
2314
  }
2228
2315
 
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
-
2316
+ eat(type) {
2317
+ if (this.current.type === type) {
2318
+ this.advance();
2319
+ } else {
2320
+ throw new ParseError(
2321
+ `Expected '${type}' but got '${this.current.type}'`,
2322
+ this.current,
2323
+ this.source
2324
+ );
2235
2325
  }
2326
+ }
2327
+
2236
2328
 
2237
2329
  peekType(offset = 1) {
2238
2330
  return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
@@ -2848,7 +2940,12 @@ arrowFunction(params) {
2848
2940
  return { type: 'ObjectExpression', props };
2849
2941
  }
2850
2942
 
2851
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
2943
+ throw new ParseError(
2944
+ `Unexpected token '${t.type}'`,
2945
+ t,
2946
+ this.source
2947
+ );
2948
+
2852
2949
  }
2853
2950
 
2854
2951
  }
@@ -2955,7 +3052,7 @@ const Lexer = __nccwpck_require__(211);
2955
3052
  const Parser = __nccwpck_require__(222);
2956
3053
  const Evaluator = __nccwpck_require__(112);
2957
3054
 
2958
- const VERSION = '1.0.28';
3055
+ const VERSION = '1.0.47';
2959
3056
 
2960
3057
  const COLOR = {
2961
3058
  reset: '\x1b[0m',
@@ -3123,19 +3220,28 @@ function runFile(filePath, isTemp = false, callback) {
3123
3220
 
3124
3221
  try {
3125
3222
  const lexer = new Lexer(code);
3126
- const parser = new Parser(lexer.getTokens());
3223
+ const tokens = lexer.getTokens();
3224
+ const parser = new Parser(tokens);
3127
3225
  const ast = parser.parse();
3128
3226
  new Evaluator().evaluate(ast);
3129
3227
  } catch (e) {
3130
- console.error(COLOR.red + e.message + COLOR.reset);
3228
+ if (e.name === 'RuntimeError') {
3229
+ console.error(COLOR.red + e.message + COLOR.reset);
3230
+ } else if (e instanceof SyntaxError) {
3231
+ console.error(COLOR.red + `SyntaxError: ${e.message}` + COLOR.reset);
3232
+ } else {
3233
+ console.error(COLOR.red + `Error: ${e.message || e}` + COLOR.reset);
3234
+ }
3131
3235
  return waitAndExit(1);
3132
3236
  }
3133
3237
 
3134
-
3135
3238
  if (callback) callback();
3136
- if (isTemp) try { fs.unlinkSync(filePath); } catch {}
3239
+ if (isTemp) {
3240
+ try { fs.unlinkSync(filePath); } catch {}
3241
+ }
3137
3242
  }
3138
3243
 
3244
+
3139
3245
  if (!args[0].startsWith('--')) {
3140
3246
  runFile(path.resolve(args[0]));
3141
3247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -9,6 +9,14 @@ class ReturnValue {
9
9
  }
10
10
  class BreakSignal {}
11
11
  class ContinueSignal {}
12
+ class RuntimeError extends Error {
13
+ constructor(message, node) {
14
+ const line = node?.line ?? '?';
15
+ const column = node?.column ?? '?';
16
+ super(`RuntimeError: ${message} at line ${line}, column ${column}`);
17
+ this.name = 'RuntimeError';
18
+ }
19
+ }
12
20
 
13
21
  class Environment {
14
22
  constructor(parent = null) {
@@ -25,7 +33,7 @@ class Environment {
25
33
  get(name) {
26
34
  if (name in this.store) return this.store[name];
27
35
  if (this.parent) return this.parent.get(name);
28
- throw new Error(`Undefined variable: ${name}`);
36
+ throw new RuntimeError(`Undefined variable: ${name}`);
29
37
  }
30
38
 
31
39
  set(name, value) {
@@ -59,8 +67,8 @@ formatValue(value, seen = new Set()) {
59
67
 
60
68
  const t = typeof value;
61
69
 
62
- // Strings (quoted)
63
- if (t === 'string') return `"${value}"`;
70
+ // Strings (no quotes)
71
+ if (t === 'string') return value;
64
72
 
65
73
  // Numbers
66
74
  if (t === 'number') return String(value);
@@ -80,7 +88,7 @@ formatValue(value, seen = new Set()) {
80
88
  return '[' + value.map(v => this.formatValue(v, seen)).join(', ') + ']';
81
89
  }
82
90
 
83
- // Objects (including your language functions)
91
+ // Objects (including user-defined functions)
84
92
  if (t === 'object') {
85
93
  // Detect user-defined functions (AST-based)
86
94
  if (value.params && value.body) {
@@ -129,11 +137,11 @@ this.global.define('range', (...args) => {
129
137
  end = Number(args[1]);
130
138
  step = Number(args[2]);
131
139
  } else {
132
- throw new Error('range() expects 1 to 3 arguments');
140
+ throw new RuntimeError('range() expects 1 to 3 arguments');
133
141
  }
134
142
 
135
143
  if (step === 0) {
136
- throw new Error('range() step cannot be 0');
144
+ throw new RuntimeError('range() step cannot be 0');
137
145
  }
138
146
 
139
147
  const result = [];
@@ -157,7 +165,7 @@ this.global.define('range', (...args) => {
157
165
  this.global.define('num', arg => {
158
166
  const n = Number(arg);
159
167
  if (Number.isNaN(n)) {
160
- throw new Error('Cannot convert value to number');
168
+ throw new RuntimeError('Cannot convert value to number');
161
169
  }
162
170
  return n;
163
171
  });
@@ -268,7 +276,7 @@ case 'DoTrackStatement':
268
276
  }
269
277
 
270
278
  if (typeof callee !== 'function') {
271
- throw new Error('NewExpression callee is not a function');
279
+ throw new RuntimeError('NewExpression callee is not a function', node);
272
280
  }
273
281
 
274
282
  const args = [];
@@ -277,7 +285,8 @@ case 'DoTrackStatement':
277
285
  }
278
286
 
279
287
  default:
280
- throw new Error(`Unknown node type in evaluator: ${node.type}`);
288
+ throw new RuntimeError(`Unknown node type in evaluator: ${node.type}`, node);
289
+
281
290
  }
282
291
  }
283
292
 
@@ -293,15 +302,26 @@ async evalDoTrack(node, env) {
293
302
  try {
294
303
  return await this.evaluate(node.body, env);
295
304
  } catch (err) {
296
- if (!node.handler) throw err;
305
+ if (!node.handler) {
306
+ // Wrap any raw error into RuntimeError with line info
307
+ if (err instanceof RuntimeError) throw err;
308
+ throw new RuntimeError(err.message || 'Error in doTrack body', node.body);
309
+ }
297
310
 
298
311
  const trackEnv = new Environment(env);
299
312
  trackEnv.define('error', err);
300
313
 
301
- return await this.evaluate(node.handler, trackEnv);
314
+ try {
315
+ return await this.evaluate(node.handler, trackEnv);
316
+ } catch (handlerErr) {
317
+ // Wrap handler errors as well
318
+ if (handlerErr instanceof RuntimeError) throw handlerErr;
319
+ throw new RuntimeError(handlerErr.message || 'Error in doTrack handler', node.handler);
320
+ }
302
321
  }
303
322
  }
304
323
 
324
+
305
325
  async evalImport(node, env) {
306
326
  const spec = node.path;
307
327
  let lib;
@@ -317,7 +337,7 @@ async evalImport(node, env) {
317
337
  : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
318
338
 
319
339
  if (!fs.existsSync(fullPath)) {
320
- throw new Error(`Import not found: ${spec}`);
340
+ throw new RuntimeError(`Import not found: ${spec}`, node);
321
341
  }
322
342
 
323
343
  const code = fs.readFileSync(fullPath, 'utf-8');
@@ -344,7 +364,7 @@ async evalImport(node, env) {
344
364
  }
345
365
  if (imp.type === 'NamedImport') {
346
366
  if (!(imp.imported in lib)) {
347
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
367
+ throw new RuntimeError(`Module '${spec}' has no export '${imp.imported}'`, node);
348
368
  }
349
369
  env.define(imp.local, lib[imp.imported]);
350
370
  }
@@ -360,12 +380,14 @@ async evalBlock(node, env) {
360
380
  result = await this.evaluate(stmt, env);
361
381
  } catch (e) {
362
382
  if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
363
- throw e;
383
+ // Wrap any other error in RuntimeError with the current block node
384
+ throw new RuntimeError(e.message || 'Error in block', stmt);
364
385
  }
365
386
  }
366
387
  return result;
367
388
  }
368
389
 
390
+
369
391
  async evalVarDeclaration(node, env) {
370
392
  const val = await this.evaluate(node.expr, env);
371
393
  return env.define(node.id, val);
@@ -398,7 +420,8 @@ async evalAssignment(node, env) {
398
420
  return rightVal;
399
421
  }
400
422
 
401
- throw new Error('Invalid assignment target');
423
+ throw new RuntimeError('Invalid assignment target', node);
424
+
402
425
  }
403
426
 
404
427
  async evalCompoundAssignment(node, env) {
@@ -408,7 +431,8 @@ async evalCompoundAssignment(node, env) {
408
431
  if (left.type === 'Identifier') current = env.get(left.name);
409
432
  else if (left.type === 'MemberExpression') current = await this.evalMember(left, env);
410
433
  else if (left.type === 'IndexExpression') current = await this.evalIndex(left, env);
411
- else throw new Error('Invalid compound assignment target');
434
+ else throw new RuntimeError('Invalid compound assignment target', node);
435
+
412
436
 
413
437
  const rhs = await this.evaluate(node.right, env);
414
438
  let computed;
@@ -418,7 +442,8 @@ async evalCompoundAssignment(node, env) {
418
442
  case 'STAREQ': computed = current * rhs; break;
419
443
  case 'SLASHEQ': computed = current / rhs; break;
420
444
  case 'MODEQ': computed = current % rhs; break;
421
- default: throw new Error('Unknown compound operator');
445
+ default: throw new RuntimeError(`Unknown compound operator: ${node.operator}`, node);
446
+
422
447
  }
423
448
 
424
449
  if (left.type === 'Identifier') env.set(left.name, computed);
@@ -451,7 +476,7 @@ async evalBinary(node, env) {
451
476
  const r = await this.evaluate(node.right, env);
452
477
 
453
478
  if (node.operator === 'SLASH' && r === 0) {
454
- throw new Error('Division by zero');
479
+ throw new RuntimeError('Division by zero', node);
455
480
  }
456
481
 
457
482
  switch (node.operator) {
@@ -466,7 +491,8 @@ async evalBinary(node, env) {
466
491
  case 'LTE': return l <= r;
467
492
  case 'GT': return l > r;
468
493
  case 'GTE': return l >= r;
469
- default: throw new Error(`Unknown binary operator ${node.operator}`);
494
+ default: throw new RuntimeError(`Unknown binary operator: ${node.operator}`, node);
495
+
470
496
  }
471
497
  }
472
498
 
@@ -474,7 +500,8 @@ async evalLogical(node, env) {
474
500
  const l = await this.evaluate(node.left, env);
475
501
  if (node.operator === 'AND') return l && await this.evaluate(node.right, env);
476
502
  if (node.operator === 'OR') return l || await this.evaluate(node.right, env);
477
- throw new Error(`Unknown logical operator ${node.operator}`);
503
+ throw new RuntimeError(`Unknown logical operator: ${node.operator}`, node);
504
+
478
505
  }
479
506
 
480
507
  async evalUnary(node, env) {
@@ -483,7 +510,8 @@ async evalUnary(node, env) {
483
510
  case 'NOT': return !val;
484
511
  case 'MINUS': return -val;
485
512
  case 'PLUS': return +val;
486
- default: throw new Error(`Unknown unary operator ${node.operator}`);
513
+ default: throw new RuntimeError(`Unknown unary operator: ${node.operator}`, node);
514
+
487
515
  }
488
516
  }
489
517
 
@@ -513,7 +541,7 @@ async evalFor(node, env) {
513
541
  const iterable = await this.evaluate(node.iterable, env);
514
542
 
515
543
  if (iterable == null || typeof iterable !== 'object') {
516
- throw new Error('Cannot iterate over non-iterable');
544
+ throw new RuntimeError('Cannot iterate over non-iterable', node);
517
545
  }
518
546
 
519
547
  const loopVar = node.variable; // STRING from parser
@@ -593,7 +621,7 @@ async evalCall(node, env) {
593
621
  return calleeEvaluated(...args);
594
622
  }
595
623
  if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
596
- throw new Error('Call to non-function');
624
+ throw new RuntimeError('Call to non-function', node);
597
625
  }
598
626
 
599
627
  const fn = calleeEvaluated;
@@ -617,12 +645,13 @@ async evalIndex(node, env) {
617
645
  const obj = await this.evaluate(node.object, env);
618
646
  const idx = await this.evaluate(node.indexer, env);
619
647
 
620
- if (obj == null) throw new Error('Indexing null or undefined');
648
+ if (obj == null) throw new RuntimeError('Indexing null or undefined', node);
649
+
621
650
  if (Array.isArray(obj) && (idx < 0 || idx >= obj.length)) {
622
- throw new Error('Array index out of bounds');
651
+ throw new RuntimeError('Array index out of bounds', node);
623
652
  }
624
653
  if (typeof obj === 'object' && !(idx in obj)) {
625
- throw new Error(`Property '${idx}' does not exist`);
654
+ throw new RuntimeError(`Property '${idx}' does not exist`, node);
626
655
  }
627
656
 
628
657
  return obj[idx];
@@ -630,14 +659,22 @@ async evalIndex(node, env) {
630
659
 
631
660
  async evalObject(node, env) {
632
661
  const out = {};
633
- for (const p of node.props) out[p.key] = await this.evaluate(p.value, env);
662
+ for (const p of node.props) {
663
+ if (!p.key || !p.value) {
664
+ throw new RuntimeError('Invalid object property', node);
665
+ }
666
+ const key = await this.evaluate(p.key, env);
667
+ const value = await this.evaluate(p.value, env);
668
+ out[key] = value;
669
+ }
634
670
  return out;
635
671
  }
636
672
 
673
+
637
674
  async evalMember(node, env) {
638
675
  const obj = await this.evaluate(node.object, env);
639
- if (obj == null) throw new Error('Member access of null or undefined');
640
- if (!(node.property in obj)) throw new Error(`Property '${node.property}' does not exist`);
676
+ if (obj == null) throw new RuntimeError('Member access of null or undefined', node);
677
+ if (!(node.property in obj)) throw new RuntimeError(`Property '${node.property}' does not exist`, node);
641
678
  return obj[node.property];
642
679
  }
643
680
 
@@ -647,7 +684,8 @@ async evalUpdate(node, env) {
647
684
  if (arg.type === 'Identifier') return env.get(arg.name);
648
685
  if (arg.type === 'MemberExpression') return await this.evalMember(arg, env);
649
686
  if (arg.type === 'IndexExpression') return await this.evalIndex(arg, env);
650
- throw new Error('Invalid update target');
687
+ throw new RuntimeError('Invalid update target', node);
688
+
651
689
  };
652
690
  const setValue = async (v) => {
653
691
  if (arg.type === 'Identifier') env.set(arg.name, v);
package/src/lexer.js CHANGED
@@ -1,11 +1,30 @@
1
+ class LexerError extends Error {
2
+ constructor(message, line, column, source = '') {
3
+ let output = `SyntaxError: ${message}\n`;
4
+ output += ` at line ${line}, column ${column}\n`;
5
+
6
+ if (source && line != null) {
7
+ const lines = source.split('\n');
8
+ const srcLine = lines[line - 1] || '';
9
+ output += ` ${srcLine}\n`;
10
+ output += ` ${' '.repeat(column - 1)}^\n`;
11
+ }
12
+
13
+ super(output);
14
+ this.name = 'SyntaxError';
15
+ }
16
+ }
17
+
1
18
  class Lexer {
2
19
  constructor(input) {
3
- this.input = input;
4
- this.pos = 0;
5
- this.currentChar = input[0] || null;
6
- this.line = 1; // current line
7
- this.column = 1; // current column
8
- }
20
+ this.input = input;
21
+ this.source = input;
22
+ this.pos = 0;
23
+ this.currentChar = input[0] || null;
24
+ this.line = 1;
25
+ this.column = 1;
26
+ }
27
+
9
28
 
10
29
  advance() {
11
30
  if (this.currentChar === '\n') {
@@ -23,8 +42,14 @@ class Lexer {
23
42
  }
24
43
 
25
44
  error(msg) {
26
- throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
27
- }
45
+ throw new LexerError(
46
+ msg,
47
+ this.line,
48
+ this.column,
49
+ this.source
50
+ );
51
+ }
52
+
28
53
 
29
54
  skipWhitespace() {
30
55
  while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
package/src/parser.js CHANGED
@@ -1,22 +1,51 @@
1
- class Parser {
2
- constructor(tokens) {
3
- this.tokens = tokens;
4
- this.pos = 0;
5
- this.current = this.tokens[this.pos];
1
+ class ParseError extends Error {
2
+ constructor(message, token, source) {
3
+ const line = token?.line ?? '?';
4
+ const column = token?.column ?? '?';
5
+
6
+ let output = `SyntaxError: ${message}\n`;
7
+
8
+ if (source && token?.line != null) {
9
+ const lines = source.split('\n');
10
+ const srcLine = lines[token.line - 1] || '';
11
+ output += ` at line ${line}, column ${column}\n`;
12
+ output += ` ${srcLine}\n`;
13
+ output += ` ${' '.repeat(column - 1)}^\n`;
14
+ } else {
15
+ output += ` at line ${line}, column ${column}\n`;
16
+ }
17
+
18
+ super(output);
19
+ this.name = 'SyntaxError';
6
20
  }
21
+ }
22
+
23
+ class Parser {
24
+ constructor(tokens, source = '') {
25
+ this.tokens = tokens;
26
+ this.source = source;
27
+ this.pos = 0;
28
+ this.current = this.tokens[this.pos];
29
+ }
30
+
7
31
 
8
32
  advance() {
9
33
  this.pos++;
10
34
  this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
11
35
  }
12
36
 
13
- eat(type) {
14
- if (this.current.type === type) this.advance();
15
- else throw new Error(
16
- `Expected ${type}, got ${this.current.type} at line ${this.current.line}, column ${this.current.column}`
17
- );
18
-
37
+ eat(type) {
38
+ if (this.current.type === type) {
39
+ this.advance();
40
+ } else {
41
+ throw new ParseError(
42
+ `Expected '${type}' but got '${this.current.type}'`,
43
+ this.current,
44
+ this.source
45
+ );
19
46
  }
47
+ }
48
+
20
49
 
21
50
  peekType(offset = 1) {
22
51
  return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
@@ -632,7 +661,12 @@ arrowFunction(params) {
632
661
  return { type: 'ObjectExpression', props };
633
662
  }
634
663
 
635
- throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
664
+ throw new ParseError(
665
+ `Unexpected token '${t.type}'`,
666
+ t,
667
+ this.source
668
+ );
669
+
636
670
  }
637
671
 
638
672
  }
package/src/starlight.js CHANGED
@@ -9,7 +9,7 @@ const Lexer = require('./lexer');
9
9
  const Parser = require('./parser');
10
10
  const Evaluator = require('./evaluator');
11
11
 
12
- const VERSION = '1.0.28';
12
+ const VERSION = '1.0.47';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',
@@ -177,19 +177,28 @@ function runFile(filePath, isTemp = false, callback) {
177
177
 
178
178
  try {
179
179
  const lexer = new Lexer(code);
180
- const parser = new Parser(lexer.getTokens());
180
+ const tokens = lexer.getTokens();
181
+ const parser = new Parser(tokens);
181
182
  const ast = parser.parse();
182
183
  new Evaluator().evaluate(ast);
183
184
  } catch (e) {
184
- console.error(COLOR.red + e.message + COLOR.reset);
185
+ if (e.name === 'RuntimeError') {
186
+ console.error(COLOR.red + e.message + COLOR.reset);
187
+ } else if (e instanceof SyntaxError) {
188
+ console.error(COLOR.red + `SyntaxError: ${e.message}` + COLOR.reset);
189
+ } else {
190
+ console.error(COLOR.red + `Error: ${e.message || e}` + COLOR.reset);
191
+ }
185
192
  return waitAndExit(1);
186
193
  }
187
194
 
188
-
189
195
  if (callback) callback();
190
- if (isTemp) try { fs.unlinkSync(filePath); } catch {}
196
+ if (isTemp) {
197
+ try { fs.unlinkSync(filePath); } catch {}
198
+ }
191
199
  }
192
200
 
201
+
193
202
  if (!args[0].startsWith('--')) {
194
203
  runFile(path.resolve(args[0]));
195
204
  }