starlight-cli 1.0.17 → 1.0.18

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
@@ -1347,9 +1347,7 @@ const Lexer = __nccwpck_require__(211);
1347
1347
  const Parser = __nccwpck_require__(222);
1348
1348
  const path = __nccwpck_require__(928);
1349
1349
 
1350
- class ReturnValue {
1351
- constructor(value) { this.value = value; }
1352
- }
1350
+ class ReturnValue { constructor(value) { this.value = value; } }
1353
1351
  class BreakSignal {}
1354
1352
  class ContinueSignal {}
1355
1353
 
@@ -1392,37 +1390,27 @@ class Evaluator {
1392
1390
 
1393
1391
  setupBuiltins() {
1394
1392
  this.global.define('len', arg => {
1395
- if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
1396
- if (arg && typeof arg === 'object') return Object.keys(arg).length;
1397
- return 0;
1393
+ if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
1394
+ if (arg && typeof arg === 'object') return Object.keys(arg).length;
1395
+ return 0;
1398
1396
  });
1399
1397
 
1400
1398
  this.global.define('print', arg => { console.log(arg); return null; });
1401
1399
  this.global.define('type', arg => {
1402
- if (Array.isArray(arg)) return 'array';
1403
- return typeof arg;
1400
+ if (Array.isArray(arg)) return 'array';
1401
+ return typeof arg;
1404
1402
  });
1405
1403
  this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
1406
1404
  this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
1407
1405
 
1408
- this.global.define('ask', prompt => {
1409
- const readlineSync = __nccwpck_require__(552);
1410
- return readlineSync.question(prompt + ' ');
1411
- });
1406
+ this.global.define('ask', prompt => readlineSync.question(prompt + ' '));
1412
1407
  this.global.define('num', arg => {
1413
- const n = Number(arg);
1414
- if (Number.isNaN(n)) {
1415
- throw new Error('Cannot convert value to number');
1416
- }
1417
- return n;
1418
- });
1419
-
1420
- this.global.define('str', arg => {
1421
- return String(arg);
1422
- });
1423
-
1424
- }
1425
-
1408
+ const n = Number(arg);
1409
+ if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
1410
+ return n;
1411
+ });
1412
+ this.global.define('str', arg => String(arg));
1413
+ }
1426
1414
 
1427
1415
  evaluate(node, env = this.global) {
1428
1416
  switch (node.type) {
@@ -1439,6 +1427,7 @@ this.global.define('str', arg => {
1439
1427
  case 'LogicalExpression': return this.evalLogical(node, env);
1440
1428
  case 'UnaryExpression': return this.evalUnary(node, env);
1441
1429
  case 'Literal': return node.value;
1430
+ case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
1442
1431
  case 'Identifier': return env.get(node.name);
1443
1432
  case 'IfStatement': return this.evalIf(node, env);
1444
1433
  case 'WhileStatement': return this.evalWhile(node, env);
@@ -1464,72 +1453,54 @@ this.global.define('str', arg => {
1464
1453
 
1465
1454
  evalProgram(node, env) {
1466
1455
  let result = null;
1467
- for (const stmt of node.body) {
1468
- result = this.evaluate(stmt, env);
1469
- }
1456
+ for (const stmt of node.body) result = this.evaluate(stmt, env);
1470
1457
  return result;
1471
1458
  }
1472
- evalImport(node, env) {
1473
- const spec = node.path;
1474
- let lib;
1475
1459
 
1476
- try {
1477
- const resolved = require.resolve(spec, {
1478
- paths: [process.cwd()]
1479
- });
1480
- lib = require(resolved);
1481
- } catch (e) {
1482
- const fullPath = path.isAbsolute(spec)
1483
- ? spec
1484
- : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
1485
-
1486
- if (!fs.existsSync(fullPath)) {
1487
- throw new Error(`Import not found: ${spec}`);
1488
- }
1460
+ evalTemplateLiteral(node, env) {
1461
+ return node.value;
1462
+ }
1489
1463
 
1490
- const code = fs.readFileSync(fullPath, 'utf-8');
1491
- const tokens = new Lexer(code).getTokens();
1492
- const ast = new Parser(tokens).parse();
1464
+ evalImport(node, env) {
1465
+ const spec = node.path;
1466
+ let lib;
1467
+ try {
1468
+ const resolved = require.resolve(spec, { paths: [process.cwd()] });
1469
+ lib = require(resolved);
1470
+ } catch (e) {
1471
+ const fullPath = path.isAbsolute(spec)
1472
+ ? spec
1473
+ : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
1493
1474
 
1494
- const moduleEnv = new Environment(env);
1495
- this.evaluate(ast, moduleEnv);
1475
+ if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
1496
1476
 
1497
- lib = {};
1498
- for (const key of Object.keys(moduleEnv.store)) {
1499
- lib[key] = moduleEnv.store[key];
1500
- }
1477
+ const code = fs.readFileSync(fullPath, 'utf-8');
1478
+ const tokens = new Lexer(code).getTokens();
1479
+ const ast = new Parser(tokens).parse();
1480
+ const moduleEnv = new Environment(env);
1481
+ this.evaluate(ast, moduleEnv);
1501
1482
 
1502
- lib.default = lib;
1503
- }
1504
-
1505
- for (const imp of node.specifiers) {
1506
- if (imp.type === 'DefaultImport') {
1507
- env.define(imp.local, lib.default ?? lib);
1508
- }
1509
- if (imp.type === 'NamespaceImport') {
1510
- env.define(imp.local, lib);
1483
+ lib = {};
1484
+ for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
1485
+ lib.default = lib;
1511
1486
  }
1512
- if (imp.type === 'NamedImport') {
1513
- if (!(imp.imported in lib)) {
1514
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1487
+
1488
+ for (const imp of node.specifiers) {
1489
+ if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
1490
+ if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
1491
+ if (imp.type === 'NamedImport') {
1492
+ if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1493
+ env.define(imp.local, lib[imp.imported]);
1515
1494
  }
1516
- env.define(imp.local, lib[imp.imported]);
1517
1495
  }
1496
+ return null;
1518
1497
  }
1519
1498
 
1520
- return null;
1521
- }
1522
-
1523
-
1524
1499
  evalBlock(node, env) {
1525
1500
  let result = null;
1526
1501
  for (const stmt of node.body) {
1527
- try {
1528
- result = this.evaluate(stmt, env);
1529
- } catch (e) {
1530
- if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
1531
- throw e;
1532
- }
1502
+ try { result = this.evaluate(stmt, env); }
1503
+ catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
1533
1504
  }
1534
1505
  return result;
1535
1506
  }
@@ -1542,27 +1513,15 @@ evalImport(node, env) {
1542
1513
  evalAssignment(node, env) {
1543
1514
  const rightVal = this.evaluate(node.right, env);
1544
1515
  const left = node.left;
1545
-
1546
1516
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
1547
- if (left.type === 'MemberExpression') {
1548
- const obj = this.evaluate(left.object, env);
1549
- obj[left.property] = rightVal;
1550
- return rightVal;
1551
- }
1552
- if (left.type === 'IndexExpression') {
1553
- const obj = this.evaluate(left.object, env);
1554
- const idx = this.evaluate(left.indexer, env);
1555
- obj[idx] = rightVal;
1556
- return rightVal;
1557
- }
1558
-
1517
+ if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
1518
+ if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
1559
1519
  throw new Error('Invalid assignment target');
1560
1520
  }
1561
1521
 
1562
1522
  evalCompoundAssignment(node, env) {
1563
1523
  const left = node.left;
1564
1524
  let current;
1565
-
1566
1525
  if (left.type === 'Identifier') current = env.get(left.name);
1567
1526
  else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
1568
1527
  else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
@@ -1580,9 +1539,7 @@ evalImport(node, env) {
1580
1539
  }
1581
1540
 
1582
1541
  if (left.type === 'Identifier') env.set(left.name, computed);
1583
- else if (left.type === 'MemberExpression') this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1584
1542
  else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1585
-
1586
1543
  return computed;
1587
1544
  }
1588
1545
 
@@ -1594,8 +1551,7 @@ evalImport(node, env) {
1594
1551
 
1595
1552
  evalAsk(node, env) {
1596
1553
  const prompt = this.evaluate(node.prompt, env);
1597
- const input = readlineSync.question(prompt + ' ');
1598
- return input;
1554
+ return readlineSync.question(prompt + ' ');
1599
1555
  }
1600
1556
 
1601
1557
  evalDefine(node, env) {
@@ -1612,12 +1568,16 @@ evalImport(node, env) {
1612
1568
  case 'STAR': return l * r;
1613
1569
  case 'SLASH': return l / r;
1614
1570
  case 'MOD': return l % r;
1615
- case 'EQEQ': return l === r;
1616
- case 'NOTEQ': return l !== r;
1571
+ case 'EQEQ': return l == r;
1572
+ case 'NOTEQ': return l != r;
1573
+ case 'STRICT_EQ': return l === r;
1574
+ case 'STRICT_NOTEQ': return l !== r;
1617
1575
  case 'LT': return l < r;
1618
1576
  case 'LTE': return l <= r;
1619
1577
  case 'GT': return l > r;
1620
1578
  case 'GTE': return l >= r;
1579
+ case 'LSHIFT': return l << r;
1580
+ case 'RSHIFT': return l >> r;
1621
1581
  default: throw new Error(`Unknown binary operator ${node.operator}`);
1622
1582
  }
1623
1583
  }
@@ -1685,9 +1645,7 @@ evalImport(node, env) {
1685
1645
  const args = node.arguments.map(a => this.evaluate(a, env));
1686
1646
  return calleeEvaluated(...args);
1687
1647
  }
1688
- if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
1689
- throw new Error('Call to non-function');
1690
- }
1648
+ if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
1691
1649
  const fn = calleeEvaluated;
1692
1650
  const callEnv = new Environment(fn.env);
1693
1651
  fn.params.forEach((p, i) => {
@@ -1727,15 +1685,8 @@ evalImport(node, env) {
1727
1685
  };
1728
1686
  const setValue = (v) => {
1729
1687
  if (arg.type === 'Identifier') env.set(arg.name, v);
1730
- else if (arg.type === 'MemberExpression') {
1731
- const obj = this.evaluate(arg.object, env);
1732
- obj[arg.property] = v;
1733
- }
1734
- else if (arg.type === 'IndexExpression') {
1735
- const obj = this.evaluate(arg.object, env);
1736
- const idx = this.evaluate(arg.indexer, env);
1737
- obj[idx] = v;
1738
- }
1688
+ else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
1689
+ else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
1739
1690
  };
1740
1691
  const current = getCurrent();
1741
1692
  const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
@@ -1744,7 +1695,8 @@ evalImport(node, env) {
1744
1695
  }
1745
1696
  }
1746
1697
 
1747
- module.exports = Evaluator;
1698
+ module.exports = Evaluator;
1699
+
1748
1700
 
1749
1701
  /***/ }),
1750
1702
 
@@ -1763,8 +1715,8 @@ class Lexer {
1763
1715
  this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
1764
1716
  }
1765
1717
 
1766
- peek() {
1767
- return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
1718
+ peek(n = 1) {
1719
+ return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
1768
1720
  }
1769
1721
 
1770
1722
  error(msg) {
@@ -1806,10 +1758,21 @@ class Lexer {
1806
1758
  result += this.currentChar;
1807
1759
  this.advance();
1808
1760
  }
1809
- return { type: 'NUMBER', value: parseFloat(result) };
1810
1761
  }
1811
1762
 
1812
- return { type: 'NUMBER', value: parseInt(result) };
1763
+ if (this.currentChar && /[eE]/.test(this.currentChar)) {
1764
+ result += this.currentChar; this.advance();
1765
+ if (this.currentChar === '+' || this.currentChar === '-') {
1766
+ result += this.currentChar; this.advance();
1767
+ }
1768
+ if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
1769
+ while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1770
+ result += this.currentChar;
1771
+ this.advance();
1772
+ }
1773
+ }
1774
+
1775
+ return { type: 'NUMBER', value: parseFloat(result) };
1813
1776
  }
1814
1777
 
1815
1778
  identifier() {
@@ -1822,7 +1785,7 @@ class Lexer {
1822
1785
  const keywords = [
1823
1786
  'let', 'sldeploy', 'if', 'else', 'while', 'for',
1824
1787
  'break', 'continue', 'func', 'return', 'true', 'false', 'null',
1825
- 'ask', 'define', 'import', 'from', 'as'
1788
+ 'ask', 'define', 'import', 'from', 'as', 'undefined'
1826
1789
  ];
1827
1790
 
1828
1791
  if (keywords.includes(result)) {
@@ -1830,8 +1793,7 @@ class Lexer {
1830
1793
  }
1831
1794
 
1832
1795
  return { type: 'IDENTIFIER', value: result };
1833
- }
1834
-
1796
+ }
1835
1797
 
1836
1798
  string() {
1837
1799
  const quote = this.currentChar;
@@ -1863,6 +1825,39 @@ class Lexer {
1863
1825
  return { type: 'STRING', value: result };
1864
1826
  }
1865
1827
 
1828
+ templateString() {
1829
+ this.advance();
1830
+ let result = '';
1831
+ while (this.currentChar && this.currentChar !== '`') {
1832
+ if (this.currentChar === '$' && this.peek() === '{') {
1833
+ this.advance(); this.advance(); // skip ${
1834
+ result += '${'; // we leave the interpolation as-is for parser
1835
+ continue;
1836
+ }
1837
+
1838
+ if (this.currentChar === '\\') {
1839
+ this.advance();
1840
+ switch (this.currentChar) {
1841
+ case 'n': result += '\n'; break;
1842
+ case 't': result += '\t'; break;
1843
+ case '`': result += '`'; break;
1844
+ case '\\': result += '\\'; break;
1845
+ default: result += this.currentChar;
1846
+ }
1847
+ } else {
1848
+ result += this.currentChar;
1849
+ }
1850
+ this.advance();
1851
+ }
1852
+
1853
+ if (this.currentChar !== '`') {
1854
+ this.error('Unterminated template string');
1855
+ }
1856
+
1857
+ this.advance();
1858
+ return { type: 'TEMPLATE_STRING', value: result };
1859
+ }
1860
+
1866
1861
  getTokens() {
1867
1862
  const tokens = [];
1868
1863
 
@@ -1872,15 +1867,21 @@ class Lexer {
1872
1867
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
1873
1868
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
1874
1869
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
1870
+ if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
1875
1871
 
1876
1872
  const char = this.currentChar;
1877
1873
  const next = this.peek();
1874
+ const next2 = this.peek(2);
1878
1875
 
1876
+ if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
1877
+ if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
1879
1878
  if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
1880
1879
  if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
1881
1880
  if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
1882
1881
  if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
1883
1882
  if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
1883
+ if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
1884
+ if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
1884
1885
  if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
1885
1886
  if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
1886
1887
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
@@ -1907,7 +1908,8 @@ class Lexer {
1907
1908
  }
1908
1909
  }
1909
1910
 
1910
- module.exports = Lexer;
1911
+ module.exports = Lexer;
1912
+
1911
1913
 
1912
1914
  /***/ }),
1913
1915
 
@@ -2015,40 +2017,41 @@ class Parser {
2015
2017
  const body = this.block();
2016
2018
  return { type: 'WhileStatement', test, body };
2017
2019
  }
2020
+
2018
2021
  importStatement() {
2019
2022
  this.eat('IMPORT');
2020
2023
 
2021
2024
  let specifiers = [];
2022
2025
 
2023
2026
  if (this.current.type === 'STAR') {
2024
- this.eat('STAR');
2025
- this.eat('AS');
2026
- const name = this.current.value;
2027
- this.eat('IDENTIFIER');
2028
- specifiers.push({ type: 'NamespaceImport', local: name });
2027
+ this.eat('STAR');
2028
+ this.eat('AS');
2029
+ const name = this.current.value;
2030
+ this.eat('IDENTIFIER');
2031
+ specifiers.push({ type: 'NamespaceImport', local: name });
2029
2032
  }
2030
2033
  else if (this.current.type === 'LBRACE') {
2031
- this.eat('LBRACE');
2032
- while (this.current.type !== 'RBRACE') {
2033
- const importedName = this.current.value;
2034
- this.eat('IDENTIFIER');
2035
- let localName = importedName;
2036
- if (this.current.type === 'AS') {
2037
- this.eat('AS');
2038
- localName = this.current.value;
2039
- this.eat('IDENTIFIER');
2040
- }
2041
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2042
- if (this.current.type === 'COMMA') this.eat('COMMA');
2034
+ this.eat('LBRACE');
2035
+ while (this.current.type !== 'RBRACE') {
2036
+ const importedName = this.current.value;
2037
+ this.eat('IDENTIFIER');
2038
+ let localName = importedName;
2039
+ if (this.current.type === 'AS') {
2040
+ this.eat('AS');
2041
+ localName = this.current.value;
2042
+ this.eat('IDENTIFIER');
2043
2043
  }
2044
- this.eat('RBRACE');
2044
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2045
+ if (this.current.type === 'COMMA') this.eat('COMMA');
2046
+ }
2047
+ this.eat('RBRACE');
2045
2048
  }
2046
2049
  else if (this.current.type === 'IDENTIFIER') {
2047
- const localName = this.current.value;
2048
- this.eat('IDENTIFIER');
2049
- specifiers.push({ type: 'DefaultImport', local: localName });
2050
+ const localName = this.current.value;
2051
+ this.eat('IDENTIFIER');
2052
+ specifiers.push({ type: 'DefaultImport', local: localName });
2050
2053
  } else {
2051
- throw new Error('Unexpected token in import statement');
2054
+ throw new Error('Unexpected token in import statement');
2052
2055
  }
2053
2056
 
2054
2057
  this.eat('FROM');
@@ -2059,8 +2062,7 @@ class Parser {
2059
2062
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2060
2063
 
2061
2064
  return { type: 'ImportStatement', path: pathToken.value, specifiers };
2062
- }
2063
-
2065
+ }
2064
2066
 
2065
2067
  forStatement() {
2066
2068
  this.eat('FOR');
@@ -2186,7 +2188,7 @@ class Parser {
2186
2188
 
2187
2189
  equality() {
2188
2190
  let node = this.comparison();
2189
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
2191
+ while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
2190
2192
  const op = this.current.type;
2191
2193
  this.eat(op);
2192
2194
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
@@ -2196,7 +2198,7 @@ class Parser {
2196
2198
 
2197
2199
  comparison() {
2198
2200
  let node = this.term();
2199
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
2201
+ while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
2200
2202
  const op = this.current.type;
2201
2203
  this.eat(op);
2202
2204
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
@@ -2287,9 +2289,11 @@ class Parser {
2287
2289
 
2288
2290
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2289
2291
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2292
+ if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
2290
2293
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2291
2294
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2292
2295
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
2296
+ if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
2293
2297
 
2294
2298
  if (t.type === 'ASK') {
2295
2299
  this.eat('ASK');
@@ -2354,7 +2358,8 @@ class Parser {
2354
2358
  }
2355
2359
  }
2356
2360
 
2357
- module.exports = Parser;
2361
+ module.exports = Parser;
2362
+
2358
2363
 
2359
2364
  /***/ }),
2360
2365
 
@@ -2456,7 +2461,7 @@ const Lexer = __nccwpck_require__(211);
2456
2461
  const Parser = __nccwpck_require__(222);
2457
2462
  const Evaluator = __nccwpck_require__(112);
2458
2463
 
2459
- const VERSION = '1.0.17';
2464
+ const VERSION = '1.0.18';
2460
2465
 
2461
2466
  const COLOR = {
2462
2467
  reset: '\x1b[0m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -4,9 +4,7 @@ const Lexer = require('./lexer');
4
4
  const Parser = require('./parser');
5
5
  const path = require('path');
6
6
 
7
- class ReturnValue {
8
- constructor(value) { this.value = value; }
9
- }
7
+ class ReturnValue { constructor(value) { this.value = value; } }
10
8
  class BreakSignal {}
11
9
  class ContinueSignal {}
12
10
 
@@ -49,37 +47,27 @@ class Evaluator {
49
47
 
50
48
  setupBuiltins() {
51
49
  this.global.define('len', arg => {
52
- if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
53
- if (arg && typeof arg === 'object') return Object.keys(arg).length;
54
- return 0;
50
+ if (Array.isArray(arg) || typeof arg === 'string') return arg.length;
51
+ if (arg && typeof arg === 'object') return Object.keys(arg).length;
52
+ return 0;
55
53
  });
56
54
 
57
55
  this.global.define('print', arg => { console.log(arg); return null; });
58
56
  this.global.define('type', arg => {
59
- if (Array.isArray(arg)) return 'array';
60
- return typeof arg;
57
+ if (Array.isArray(arg)) return 'array';
58
+ return typeof arg;
61
59
  });
62
60
  this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
63
61
  this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
64
62
 
65
- this.global.define('ask', prompt => {
66
- const readlineSync = require('readline-sync');
67
- return readlineSync.question(prompt + ' ');
68
- });
63
+ this.global.define('ask', prompt => readlineSync.question(prompt + ' '));
69
64
  this.global.define('num', arg => {
70
- const n = Number(arg);
71
- if (Number.isNaN(n)) {
72
- throw new Error('Cannot convert value to number');
73
- }
74
- return n;
75
- });
76
-
77
- this.global.define('str', arg => {
78
- return String(arg);
79
- });
80
-
81
- }
82
-
65
+ const n = Number(arg);
66
+ if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
67
+ return n;
68
+ });
69
+ this.global.define('str', arg => String(arg));
70
+ }
83
71
 
84
72
  evaluate(node, env = this.global) {
85
73
  switch (node.type) {
@@ -96,6 +84,7 @@ this.global.define('str', arg => {
96
84
  case 'LogicalExpression': return this.evalLogical(node, env);
97
85
  case 'UnaryExpression': return this.evalUnary(node, env);
98
86
  case 'Literal': return node.value;
87
+ case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
99
88
  case 'Identifier': return env.get(node.name);
100
89
  case 'IfStatement': return this.evalIf(node, env);
101
90
  case 'WhileStatement': return this.evalWhile(node, env);
@@ -121,72 +110,54 @@ this.global.define('str', arg => {
121
110
 
122
111
  evalProgram(node, env) {
123
112
  let result = null;
124
- for (const stmt of node.body) {
125
- result = this.evaluate(stmt, env);
126
- }
113
+ for (const stmt of node.body) result = this.evaluate(stmt, env);
127
114
  return result;
128
115
  }
129
- evalImport(node, env) {
130
- const spec = node.path;
131
- let lib;
132
-
133
- try {
134
- const resolved = require.resolve(spec, {
135
- paths: [process.cwd()]
136
- });
137
- lib = require(resolved);
138
- } catch (e) {
139
- const fullPath = path.isAbsolute(spec)
140
- ? spec
141
- : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
142
-
143
- if (!fs.existsSync(fullPath)) {
144
- throw new Error(`Import not found: ${spec}`);
145
- }
146
-
147
- const code = fs.readFileSync(fullPath, 'utf-8');
148
- const tokens = new Lexer(code).getTokens();
149
- const ast = new Parser(tokens).parse();
150
-
151
- const moduleEnv = new Environment(env);
152
- this.evaluate(ast, moduleEnv);
153
-
154
- lib = {};
155
- for (const key of Object.keys(moduleEnv.store)) {
156
- lib[key] = moduleEnv.store[key];
157
- }
158
116
 
159
- lib.default = lib;
117
+ evalTemplateLiteral(node, env) {
118
+ return node.value;
160
119
  }
161
120
 
162
- for (const imp of node.specifiers) {
163
- if (imp.type === 'DefaultImport') {
164
- env.define(imp.local, lib.default ?? lib);
121
+ evalImport(node, env) {
122
+ const spec = node.path;
123
+ let lib;
124
+ try {
125
+ const resolved = require.resolve(spec, { paths: [process.cwd()] });
126
+ lib = require(resolved);
127
+ } catch (e) {
128
+ const fullPath = path.isAbsolute(spec)
129
+ ? spec
130
+ : path.join(process.cwd(), spec.endsWith('.sl') ? spec : spec + '.sl');
131
+
132
+ if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
133
+
134
+ const code = fs.readFileSync(fullPath, 'utf-8');
135
+ const tokens = new Lexer(code).getTokens();
136
+ const ast = new Parser(tokens).parse();
137
+ const moduleEnv = new Environment(env);
138
+ this.evaluate(ast, moduleEnv);
139
+
140
+ lib = {};
141
+ for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
142
+ lib.default = lib;
165
143
  }
166
- if (imp.type === 'NamespaceImport') {
167
- env.define(imp.local, lib);
168
- }
169
- if (imp.type === 'NamedImport') {
170
- if (!(imp.imported in lib)) {
171
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
144
+
145
+ for (const imp of node.specifiers) {
146
+ if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
147
+ if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
148
+ if (imp.type === 'NamedImport') {
149
+ if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
150
+ env.define(imp.local, lib[imp.imported]);
172
151
  }
173
- env.define(imp.local, lib[imp.imported]);
174
152
  }
153
+ return null;
175
154
  }
176
155
 
177
- return null;
178
- }
179
-
180
-
181
156
  evalBlock(node, env) {
182
157
  let result = null;
183
158
  for (const stmt of node.body) {
184
- try {
185
- result = this.evaluate(stmt, env);
186
- } catch (e) {
187
- if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e;
188
- throw e;
189
- }
159
+ try { result = this.evaluate(stmt, env); }
160
+ catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
190
161
  }
191
162
  return result;
192
163
  }
@@ -199,27 +170,15 @@ evalImport(node, env) {
199
170
  evalAssignment(node, env) {
200
171
  const rightVal = this.evaluate(node.right, env);
201
172
  const left = node.left;
202
-
203
173
  if (left.type === 'Identifier') return env.set(left.name, rightVal);
204
- if (left.type === 'MemberExpression') {
205
- const obj = this.evaluate(left.object, env);
206
- obj[left.property] = rightVal;
207
- return rightVal;
208
- }
209
- if (left.type === 'IndexExpression') {
210
- const obj = this.evaluate(left.object, env);
211
- const idx = this.evaluate(left.indexer, env);
212
- obj[idx] = rightVal;
213
- return rightVal;
214
- }
215
-
174
+ if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
175
+ if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
216
176
  throw new Error('Invalid assignment target');
217
177
  }
218
178
 
219
179
  evalCompoundAssignment(node, env) {
220
180
  const left = node.left;
221
181
  let current;
222
-
223
182
  if (left.type === 'Identifier') current = env.get(left.name);
224
183
  else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
225
184
  else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
@@ -237,9 +196,7 @@ evalImport(node, env) {
237
196
  }
238
197
 
239
198
  if (left.type === 'Identifier') env.set(left.name, computed);
240
- else if (left.type === 'MemberExpression') this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
241
199
  else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
242
-
243
200
  return computed;
244
201
  }
245
202
 
@@ -251,8 +208,7 @@ evalImport(node, env) {
251
208
 
252
209
  evalAsk(node, env) {
253
210
  const prompt = this.evaluate(node.prompt, env);
254
- const input = readlineSync.question(prompt + ' ');
255
- return input;
211
+ return readlineSync.question(prompt + ' ');
256
212
  }
257
213
 
258
214
  evalDefine(node, env) {
@@ -269,12 +225,16 @@ evalImport(node, env) {
269
225
  case 'STAR': return l * r;
270
226
  case 'SLASH': return l / r;
271
227
  case 'MOD': return l % r;
272
- case 'EQEQ': return l === r;
273
- case 'NOTEQ': return l !== r;
228
+ case 'EQEQ': return l == r;
229
+ case 'NOTEQ': return l != r;
230
+ case 'STRICT_EQ': return l === r;
231
+ case 'STRICT_NOTEQ': return l !== r;
274
232
  case 'LT': return l < r;
275
233
  case 'LTE': return l <= r;
276
234
  case 'GT': return l > r;
277
235
  case 'GTE': return l >= r;
236
+ case 'LSHIFT': return l << r;
237
+ case 'RSHIFT': return l >> r;
278
238
  default: throw new Error(`Unknown binary operator ${node.operator}`);
279
239
  }
280
240
  }
@@ -342,9 +302,7 @@ evalImport(node, env) {
342
302
  const args = node.arguments.map(a => this.evaluate(a, env));
343
303
  return calleeEvaluated(...args);
344
304
  }
345
- if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
346
- throw new Error('Call to non-function');
347
- }
305
+ if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
348
306
  const fn = calleeEvaluated;
349
307
  const callEnv = new Environment(fn.env);
350
308
  fn.params.forEach((p, i) => {
@@ -384,15 +342,8 @@ evalImport(node, env) {
384
342
  };
385
343
  const setValue = (v) => {
386
344
  if (arg.type === 'Identifier') env.set(arg.name, v);
387
- else if (arg.type === 'MemberExpression') {
388
- const obj = this.evaluate(arg.object, env);
389
- obj[arg.property] = v;
390
- }
391
- else if (arg.type === 'IndexExpression') {
392
- const obj = this.evaluate(arg.object, env);
393
- const idx = this.evaluate(arg.indexer, env);
394
- obj[idx] = v;
395
- }
345
+ else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
346
+ else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
396
347
  };
397
348
  const current = getCurrent();
398
349
  const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
@@ -401,4 +352,4 @@ evalImport(node, env) {
401
352
  }
402
353
  }
403
354
 
404
- module.exports = Evaluator;
355
+ module.exports = Evaluator;
package/src/lexer.js CHANGED
@@ -10,8 +10,8 @@ class Lexer {
10
10
  this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
11
11
  }
12
12
 
13
- peek() {
14
- return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
13
+ peek(n = 1) {
14
+ return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
15
15
  }
16
16
 
17
17
  error(msg) {
@@ -53,10 +53,21 @@ class Lexer {
53
53
  result += this.currentChar;
54
54
  this.advance();
55
55
  }
56
- return { type: 'NUMBER', value: parseFloat(result) };
57
56
  }
58
57
 
59
- return { type: 'NUMBER', value: parseInt(result) };
58
+ if (this.currentChar && /[eE]/.test(this.currentChar)) {
59
+ result += this.currentChar; this.advance();
60
+ if (this.currentChar === '+' || this.currentChar === '-') {
61
+ result += this.currentChar; this.advance();
62
+ }
63
+ if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
64
+ while (this.currentChar && /[0-9]/.test(this.currentChar)) {
65
+ result += this.currentChar;
66
+ this.advance();
67
+ }
68
+ }
69
+
70
+ return { type: 'NUMBER', value: parseFloat(result) };
60
71
  }
61
72
 
62
73
  identifier() {
@@ -69,7 +80,7 @@ class Lexer {
69
80
  const keywords = [
70
81
  'let', 'sldeploy', 'if', 'else', 'while', 'for',
71
82
  'break', 'continue', 'func', 'return', 'true', 'false', 'null',
72
- 'ask', 'define', 'import', 'from', 'as'
83
+ 'ask', 'define', 'import', 'from', 'as', 'undefined'
73
84
  ];
74
85
 
75
86
  if (keywords.includes(result)) {
@@ -77,8 +88,7 @@ class Lexer {
77
88
  }
78
89
 
79
90
  return { type: 'IDENTIFIER', value: result };
80
- }
81
-
91
+ }
82
92
 
83
93
  string() {
84
94
  const quote = this.currentChar;
@@ -110,6 +120,39 @@ class Lexer {
110
120
  return { type: 'STRING', value: result };
111
121
  }
112
122
 
123
+ templateString() {
124
+ this.advance();
125
+ let result = '';
126
+ while (this.currentChar && this.currentChar !== '`') {
127
+ if (this.currentChar === '$' && this.peek() === '{') {
128
+ this.advance(); this.advance(); // skip ${
129
+ result += '${'; // we leave the interpolation as-is for parser
130
+ continue;
131
+ }
132
+
133
+ if (this.currentChar === '\\') {
134
+ this.advance();
135
+ switch (this.currentChar) {
136
+ case 'n': result += '\n'; break;
137
+ case 't': result += '\t'; break;
138
+ case '`': result += '`'; break;
139
+ case '\\': result += '\\'; break;
140
+ default: result += this.currentChar;
141
+ }
142
+ } else {
143
+ result += this.currentChar;
144
+ }
145
+ this.advance();
146
+ }
147
+
148
+ if (this.currentChar !== '`') {
149
+ this.error('Unterminated template string');
150
+ }
151
+
152
+ this.advance();
153
+ return { type: 'TEMPLATE_STRING', value: result };
154
+ }
155
+
113
156
  getTokens() {
114
157
  const tokens = [];
115
158
 
@@ -119,15 +162,21 @@ class Lexer {
119
162
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
120
163
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
121
164
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
165
+ if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
122
166
 
123
167
  const char = this.currentChar;
124
168
  const next = this.peek();
169
+ const next2 = this.peek(2);
125
170
 
171
+ if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
172
+ if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
126
173
  if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
127
174
  if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
128
175
  if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
129
176
  if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
130
177
  if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
178
+ if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
179
+ if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
131
180
  if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
132
181
  if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
133
182
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
@@ -154,4 +203,4 @@ class Lexer {
154
203
  }
155
204
  }
156
205
 
157
- module.exports = Lexer;
206
+ module.exports = Lexer;
package/src/parser.js CHANGED
@@ -99,40 +99,41 @@ class Parser {
99
99
  const body = this.block();
100
100
  return { type: 'WhileStatement', test, body };
101
101
  }
102
+
102
103
  importStatement() {
103
104
  this.eat('IMPORT');
104
105
 
105
106
  let specifiers = [];
106
107
 
107
108
  if (this.current.type === 'STAR') {
108
- this.eat('STAR');
109
- this.eat('AS');
110
- const name = this.current.value;
111
- this.eat('IDENTIFIER');
112
- specifiers.push({ type: 'NamespaceImport', local: name });
109
+ this.eat('STAR');
110
+ this.eat('AS');
111
+ const name = this.current.value;
112
+ this.eat('IDENTIFIER');
113
+ specifiers.push({ type: 'NamespaceImport', local: name });
113
114
  }
114
115
  else if (this.current.type === 'LBRACE') {
115
- this.eat('LBRACE');
116
- while (this.current.type !== 'RBRACE') {
117
- const importedName = this.current.value;
118
- this.eat('IDENTIFIER');
119
- let localName = importedName;
120
- if (this.current.type === 'AS') {
121
- this.eat('AS');
122
- localName = this.current.value;
123
- this.eat('IDENTIFIER');
124
- }
125
- specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
126
- if (this.current.type === 'COMMA') this.eat('COMMA');
116
+ this.eat('LBRACE');
117
+ while (this.current.type !== 'RBRACE') {
118
+ const importedName = this.current.value;
119
+ this.eat('IDENTIFIER');
120
+ let localName = importedName;
121
+ if (this.current.type === 'AS') {
122
+ this.eat('AS');
123
+ localName = this.current.value;
124
+ this.eat('IDENTIFIER');
127
125
  }
128
- this.eat('RBRACE');
126
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
127
+ if (this.current.type === 'COMMA') this.eat('COMMA');
128
+ }
129
+ this.eat('RBRACE');
129
130
  }
130
131
  else if (this.current.type === 'IDENTIFIER') {
131
- const localName = this.current.value;
132
- this.eat('IDENTIFIER');
133
- specifiers.push({ type: 'DefaultImport', local: localName });
132
+ const localName = this.current.value;
133
+ this.eat('IDENTIFIER');
134
+ specifiers.push({ type: 'DefaultImport', local: localName });
134
135
  } else {
135
- throw new Error('Unexpected token in import statement');
136
+ throw new Error('Unexpected token in import statement');
136
137
  }
137
138
 
138
139
  this.eat('FROM');
@@ -143,8 +144,7 @@ class Parser {
143
144
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
144
145
 
145
146
  return { type: 'ImportStatement', path: pathToken.value, specifiers };
146
- }
147
-
147
+ }
148
148
 
149
149
  forStatement() {
150
150
  this.eat('FOR');
@@ -270,7 +270,7 @@ class Parser {
270
270
 
271
271
  equality() {
272
272
  let node = this.comparison();
273
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
273
+ while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
274
274
  const op = this.current.type;
275
275
  this.eat(op);
276
276
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
@@ -280,7 +280,7 @@ class Parser {
280
280
 
281
281
  comparison() {
282
282
  let node = this.term();
283
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
283
+ while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
284
284
  const op = this.current.type;
285
285
  this.eat(op);
286
286
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
@@ -371,9 +371,11 @@ class Parser {
371
371
 
372
372
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
373
373
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
374
+ if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
374
375
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
375
376
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
376
377
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
378
+ if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
377
379
 
378
380
  if (t.type === 'ASK') {
379
381
  this.eat('ASK');
@@ -438,4 +440,4 @@ class Parser {
438
440
  }
439
441
  }
440
442
 
441
- module.exports = Parser;
443
+ module.exports = Parser;
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.17';
12
+ const VERSION = '1.0.18';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',