starlight-cli 1.0.17 → 1.0.19

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,65 @@ 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}`);
1460
+ evalTemplateLiteral(node, env) {
1461
+ // node.parts is an array from parser: Literal or expression nodes
1462
+ let result = '';
1463
+ for (const part of node.parts) {
1464
+ if (part.type === 'Literal') {
1465
+ result += part.value;
1466
+ } else {
1467
+ const val = this.evaluate(part, env);
1468
+ result += val != null ? val.toString() : '';
1488
1469
  }
1470
+ }
1471
+ return result;
1472
+ }
1489
1473
 
1490
- const code = fs.readFileSync(fullPath, 'utf-8');
1491
- const tokens = new Lexer(code).getTokens();
1492
- const ast = new Parser(tokens).parse();
1493
1474
 
1494
- const moduleEnv = new Environment(env);
1495
- this.evaluate(ast, moduleEnv);
1475
+ evalImport(node, env) {
1476
+ const spec = node.path;
1477
+ let lib;
1478
+ try {
1479
+ const resolved = require.resolve(spec, { paths: [process.cwd()] });
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');
1496
1485
 
1497
- lib = {};
1498
- for (const key of Object.keys(moduleEnv.store)) {
1499
- lib[key] = moduleEnv.store[key];
1500
- }
1486
+ if (!fs.existsSync(fullPath)) throw new Error(`Import not found: ${spec}`);
1501
1487
 
1502
- lib.default = lib;
1503
- }
1488
+ const code = fs.readFileSync(fullPath, 'utf-8');
1489
+ const tokens = new Lexer(code).getTokens();
1490
+ const ast = new Parser(tokens).parse();
1491
+ const moduleEnv = new Environment(env);
1492
+ this.evaluate(ast, moduleEnv);
1504
1493
 
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);
1494
+ lib = {};
1495
+ for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
1496
+ lib.default = lib;
1511
1497
  }
1512
- if (imp.type === 'NamedImport') {
1513
- if (!(imp.imported in lib)) {
1514
- throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1498
+
1499
+ for (const imp of node.specifiers) {
1500
+ if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
1501
+ if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
1502
+ if (imp.type === 'NamedImport') {
1503
+ if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
1504
+ env.define(imp.local, lib[imp.imported]);
1515
1505
  }
1516
- env.define(imp.local, lib[imp.imported]);
1517
1506
  }
1507
+ return null;
1518
1508
  }
1519
1509
 
1520
- return null;
1521
- }
1522
-
1523
-
1524
1510
  evalBlock(node, env) {
1525
1511
  let result = null;
1526
1512
  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
- }
1513
+ try { result = this.evaluate(stmt, env); }
1514
+ catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
1533
1515
  }
1534
1516
  return result;
1535
1517
  }
@@ -1542,27 +1524,15 @@ evalImport(node, env) {
1542
1524
  evalAssignment(node, env) {
1543
1525
  const rightVal = this.evaluate(node.right, env);
1544
1526
  const left = node.left;
1545
-
1546
1527
  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
-
1528
+ if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
1529
+ if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
1559
1530
  throw new Error('Invalid assignment target');
1560
1531
  }
1561
1532
 
1562
1533
  evalCompoundAssignment(node, env) {
1563
1534
  const left = node.left;
1564
1535
  let current;
1565
-
1566
1536
  if (left.type === 'Identifier') current = env.get(left.name);
1567
1537
  else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
1568
1538
  else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
@@ -1580,9 +1550,7 @@ evalImport(node, env) {
1580
1550
  }
1581
1551
 
1582
1552
  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
1553
  else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
1585
-
1586
1554
  return computed;
1587
1555
  }
1588
1556
 
@@ -1594,8 +1562,7 @@ evalImport(node, env) {
1594
1562
 
1595
1563
  evalAsk(node, env) {
1596
1564
  const prompt = this.evaluate(node.prompt, env);
1597
- const input = readlineSync.question(prompt + ' ');
1598
- return input;
1565
+ return readlineSync.question(prompt + ' ');
1599
1566
  }
1600
1567
 
1601
1568
  evalDefine(node, env) {
@@ -1612,12 +1579,16 @@ evalImport(node, env) {
1612
1579
  case 'STAR': return l * r;
1613
1580
  case 'SLASH': return l / r;
1614
1581
  case 'MOD': return l % r;
1615
- case 'EQEQ': return l === r;
1616
- case 'NOTEQ': return l !== r;
1582
+ case 'EQEQ': return l == r;
1583
+ case 'NOTEQ': return l != r;
1584
+ case 'STRICT_EQ': return l === r;
1585
+ case 'STRICT_NOTEQ': return l !== r;
1617
1586
  case 'LT': return l < r;
1618
1587
  case 'LTE': return l <= r;
1619
1588
  case 'GT': return l > r;
1620
1589
  case 'GTE': return l >= r;
1590
+ case 'LSHIFT': return l << r;
1591
+ case 'RSHIFT': return l >> r;
1621
1592
  default: throw new Error(`Unknown binary operator ${node.operator}`);
1622
1593
  }
1623
1594
  }
@@ -1685,9 +1656,7 @@ evalImport(node, env) {
1685
1656
  const args = node.arguments.map(a => this.evaluate(a, env));
1686
1657
  return calleeEvaluated(...args);
1687
1658
  }
1688
- if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
1689
- throw new Error('Call to non-function');
1690
- }
1659
+ if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
1691
1660
  const fn = calleeEvaluated;
1692
1661
  const callEnv = new Environment(fn.env);
1693
1662
  fn.params.forEach((p, i) => {
@@ -1727,15 +1696,8 @@ evalImport(node, env) {
1727
1696
  };
1728
1697
  const setValue = (v) => {
1729
1698
  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
- }
1699
+ else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
1700
+ else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
1739
1701
  };
1740
1702
  const current = getCurrent();
1741
1703
  const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
@@ -1744,7 +1706,8 @@ evalImport(node, env) {
1744
1706
  }
1745
1707
  }
1746
1708
 
1747
- module.exports = Evaluator;
1709
+ module.exports = Evaluator;
1710
+
1748
1711
 
1749
1712
  /***/ }),
1750
1713
 
@@ -1763,8 +1726,8 @@ class Lexer {
1763
1726
  this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
1764
1727
  }
1765
1728
 
1766
- peek() {
1767
- return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
1729
+ peek(n = 1) {
1730
+ return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
1768
1731
  }
1769
1732
 
1770
1733
  error(msg) {
@@ -1806,10 +1769,21 @@ class Lexer {
1806
1769
  result += this.currentChar;
1807
1770
  this.advance();
1808
1771
  }
1809
- return { type: 'NUMBER', value: parseFloat(result) };
1810
1772
  }
1811
1773
 
1812
- return { type: 'NUMBER', value: parseInt(result) };
1774
+ if (this.currentChar && /[eE]/.test(this.currentChar)) {
1775
+ result += this.currentChar; this.advance();
1776
+ if (this.currentChar === '+' || this.currentChar === '-') {
1777
+ result += this.currentChar; this.advance();
1778
+ }
1779
+ if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
1780
+ while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1781
+ result += this.currentChar;
1782
+ this.advance();
1783
+ }
1784
+ }
1785
+
1786
+ return { type: 'NUMBER', value: parseFloat(result) };
1813
1787
  }
1814
1788
 
1815
1789
  identifier() {
@@ -1822,7 +1796,7 @@ class Lexer {
1822
1796
  const keywords = [
1823
1797
  'let', 'sldeploy', 'if', 'else', 'while', 'for',
1824
1798
  'break', 'continue', 'func', 'return', 'true', 'false', 'null',
1825
- 'ask', 'define', 'import', 'from', 'as'
1799
+ 'ask', 'define', 'import', 'from', 'as', 'undefined'
1826
1800
  ];
1827
1801
 
1828
1802
  if (keywords.includes(result)) {
@@ -1830,8 +1804,7 @@ class Lexer {
1830
1804
  }
1831
1805
 
1832
1806
  return { type: 'IDENTIFIER', value: result };
1833
- }
1834
-
1807
+ }
1835
1808
 
1836
1809
  string() {
1837
1810
  const quote = this.currentChar;
@@ -1863,6 +1836,46 @@ class Lexer {
1863
1836
  return { type: 'STRING', value: result };
1864
1837
  }
1865
1838
 
1839
+ templateString() {
1840
+ this.advance(); // skip initial backtick `
1841
+ let buffer = '';
1842
+ const tokens = [];
1843
+
1844
+ while (this.currentChar && this.currentChar !== '`') {
1845
+ if (this.currentChar === '$' && this.peek() === '{') {
1846
+ if (buffer.length > 0) {
1847
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
1848
+ buffer = '';
1849
+ }
1850
+ tokens.push({ type: 'DOLLAR_LBRACE' }); // ${
1851
+ this.advance(); this.advance();
1852
+ continue;
1853
+ }
1854
+
1855
+ if (this.currentChar === '\\') {
1856
+ this.advance();
1857
+ switch (this.currentChar) {
1858
+ case 'n': buffer += '\n'; break;
1859
+ case 't': buffer += '\t'; break;
1860
+ case '`': buffer += '`'; break;
1861
+ case '\\': buffer += '\\'; break;
1862
+ default: buffer += this.currentChar;
1863
+ }
1864
+ } else {
1865
+ buffer += this.currentChar;
1866
+ }
1867
+ this.advance();
1868
+ }
1869
+
1870
+ if (buffer.length > 0) tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
1871
+
1872
+ if (this.currentChar !== '`') this.error('Unterminated template string');
1873
+ this.advance(); // skip closing backtick
1874
+
1875
+ return tokens; // return array of tokens
1876
+ }
1877
+
1878
+
1866
1879
  getTokens() {
1867
1880
  const tokens = [];
1868
1881
 
@@ -1872,15 +1885,26 @@ class Lexer {
1872
1885
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
1873
1886
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
1874
1887
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
1888
+ if (this.currentChar === '`') {
1889
+ const tTokens = this.templateString();
1890
+ tokens.push(...tTokens); // push all tokens returned from templateString
1891
+ continue;
1892
+ }
1893
+
1875
1894
 
1876
1895
  const char = this.currentChar;
1877
1896
  const next = this.peek();
1897
+ const next2 = this.peek(2);
1878
1898
 
1899
+ if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
1900
+ if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
1879
1901
  if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
1880
1902
  if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
1881
1903
  if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
1882
1904
  if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
1883
1905
  if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
1906
+ if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
1907
+ if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
1884
1908
  if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
1885
1909
  if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
1886
1910
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
@@ -1907,7 +1931,8 @@ class Lexer {
1907
1931
  }
1908
1932
  }
1909
1933
 
1910
- module.exports = Lexer;
1934
+ module.exports = Lexer;
1935
+
1911
1936
 
1912
1937
  /***/ }),
1913
1938
 
@@ -2015,40 +2040,41 @@ class Parser {
2015
2040
  const body = this.block();
2016
2041
  return { type: 'WhileStatement', test, body };
2017
2042
  }
2043
+
2018
2044
  importStatement() {
2019
2045
  this.eat('IMPORT');
2020
2046
 
2021
2047
  let specifiers = [];
2022
2048
 
2023
2049
  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 });
2050
+ this.eat('STAR');
2051
+ this.eat('AS');
2052
+ const name = this.current.value;
2053
+ this.eat('IDENTIFIER');
2054
+ specifiers.push({ type: 'NamespaceImport', local: name });
2029
2055
  }
2030
2056
  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');
2057
+ this.eat('LBRACE');
2058
+ while (this.current.type !== 'RBRACE') {
2059
+ const importedName = this.current.value;
2060
+ this.eat('IDENTIFIER');
2061
+ let localName = importedName;
2062
+ if (this.current.type === 'AS') {
2063
+ this.eat('AS');
2064
+ localName = this.current.value;
2065
+ this.eat('IDENTIFIER');
2043
2066
  }
2044
- this.eat('RBRACE');
2067
+ specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
2068
+ if (this.current.type === 'COMMA') this.eat('COMMA');
2069
+ }
2070
+ this.eat('RBRACE');
2045
2071
  }
2046
2072
  else if (this.current.type === 'IDENTIFIER') {
2047
- const localName = this.current.value;
2048
- this.eat('IDENTIFIER');
2049
- specifiers.push({ type: 'DefaultImport', local: localName });
2073
+ const localName = this.current.value;
2074
+ this.eat('IDENTIFIER');
2075
+ specifiers.push({ type: 'DefaultImport', local: localName });
2050
2076
  } else {
2051
- throw new Error('Unexpected token in import statement');
2077
+ throw new Error('Unexpected token in import statement');
2052
2078
  }
2053
2079
 
2054
2080
  this.eat('FROM');
@@ -2059,8 +2085,7 @@ class Parser {
2059
2085
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2060
2086
 
2061
2087
  return { type: 'ImportStatement', path: pathToken.value, specifiers };
2062
- }
2063
-
2088
+ }
2064
2089
 
2065
2090
  forStatement() {
2066
2091
  this.eat('FOR');
@@ -2140,6 +2165,23 @@ class Parser {
2140
2165
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2141
2166
  return { type: 'ExpressionStatement', expression: expr };
2142
2167
  }
2168
+ parseTemplateLiteral() {
2169
+ const parts = [];
2170
+ while (true) {
2171
+ if (this.current.type === 'TEMPLATE_STRING') {
2172
+ parts.push({ type: 'Literal', value: this.current.value });
2173
+ this.eat('TEMPLATE_STRING');
2174
+ } else if (this.current.type === 'DOLLAR_LBRACE') {
2175
+ this.eat('DOLLAR_LBRACE');
2176
+ const expr = this.expression();
2177
+ parts.push(expr);
2178
+ this.eat('RBRACE');
2179
+ } else {
2180
+ break;
2181
+ }
2182
+ }
2183
+ return { type: 'TemplateLiteral', parts };
2184
+ }
2143
2185
 
2144
2186
  expression() {
2145
2187
  return this.assignment();
@@ -2186,7 +2228,7 @@ class Parser {
2186
2228
 
2187
2229
  equality() {
2188
2230
  let node = this.comparison();
2189
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
2231
+ while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
2190
2232
  const op = this.current.type;
2191
2233
  this.eat(op);
2192
2234
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
@@ -2196,7 +2238,7 @@ class Parser {
2196
2238
 
2197
2239
  comparison() {
2198
2240
  let node = this.term();
2199
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
2241
+ while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
2200
2242
  const op = this.current.type;
2201
2243
  this.eat(op);
2202
2244
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
@@ -2287,9 +2329,14 @@ class Parser {
2287
2329
 
2288
2330
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2289
2331
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2332
+ if (t.type === 'TEMPLATE_STRING' || t.type === 'DOLLAR_LBRACE') {
2333
+ return this.parseTemplateLiteral();
2334
+ }
2335
+
2290
2336
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2291
2337
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2292
2338
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
2339
+ if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
2293
2340
 
2294
2341
  if (t.type === 'ASK') {
2295
2342
  this.eat('ASK');
@@ -2354,7 +2401,8 @@ class Parser {
2354
2401
  }
2355
2402
  }
2356
2403
 
2357
- module.exports = Parser;
2404
+ module.exports = Parser;
2405
+
2358
2406
 
2359
2407
  /***/ }),
2360
2408
 
@@ -2456,7 +2504,7 @@ const Lexer = __nccwpck_require__(211);
2456
2504
  const Parser = __nccwpck_require__(222);
2457
2505
  const Evaluator = __nccwpck_require__(112);
2458
2506
 
2459
- const VERSION = '1.0.17';
2507
+ const VERSION = '1.0.19';
2460
2508
 
2461
2509
  const COLOR = {
2462
2510
  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.19",
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,65 @@ 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
116
 
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}`);
117
+ evalTemplateLiteral(node, env) {
118
+ // node.parts is an array from parser: Literal or expression nodes
119
+ let result = '';
120
+ for (const part of node.parts) {
121
+ if (part.type === 'Literal') {
122
+ result += part.value;
123
+ } else {
124
+ const val = this.evaluate(part, env);
125
+ result += val != null ? val.toString() : '';
145
126
  }
127
+ }
128
+ return result;
129
+ }
146
130
 
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
131
 
154
- lib = {};
155
- for (const key of Object.keys(moduleEnv.store)) {
156
- lib[key] = moduleEnv.store[key];
132
+ evalImport(node, env) {
133
+ const spec = node.path;
134
+ let lib;
135
+ try {
136
+ const resolved = require.resolve(spec, { paths: [process.cwd()] });
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)) throw new Error(`Import not found: ${spec}`);
144
+
145
+ const code = fs.readFileSync(fullPath, 'utf-8');
146
+ const tokens = new Lexer(code).getTokens();
147
+ const ast = new Parser(tokens).parse();
148
+ const moduleEnv = new Environment(env);
149
+ this.evaluate(ast, moduleEnv);
150
+
151
+ lib = {};
152
+ for (const key of Object.keys(moduleEnv.store)) lib[key] = moduleEnv.store[key];
153
+ lib.default = lib;
157
154
  }
158
155
 
159
- lib.default = lib;
160
- }
161
-
162
- for (const imp of node.specifiers) {
163
- if (imp.type === 'DefaultImport') {
164
- env.define(imp.local, lib.default ?? lib);
165
- }
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}'`);
156
+ for (const imp of node.specifiers) {
157
+ if (imp.type === 'DefaultImport') env.define(imp.local, lib.default ?? lib);
158
+ if (imp.type === 'NamespaceImport') env.define(imp.local, lib);
159
+ if (imp.type === 'NamedImport') {
160
+ if (!(imp.imported in lib)) throw new Error(`Module '${spec}' has no export '${imp.imported}'`);
161
+ env.define(imp.local, lib[imp.imported]);
172
162
  }
173
- env.define(imp.local, lib[imp.imported]);
174
163
  }
164
+ return null;
175
165
  }
176
166
 
177
- return null;
178
- }
179
-
180
-
181
167
  evalBlock(node, env) {
182
168
  let result = null;
183
169
  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
- }
170
+ try { result = this.evaluate(stmt, env); }
171
+ catch (e) { if (e instanceof ReturnValue || e instanceof BreakSignal || e instanceof ContinueSignal) throw e; else throw e; }
190
172
  }
191
173
  return result;
192
174
  }
@@ -199,27 +181,15 @@ evalImport(node, env) {
199
181
  evalAssignment(node, env) {
200
182
  const rightVal = this.evaluate(node.right, env);
201
183
  const left = node.left;
202
-
203
184
  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
-
185
+ if (left.type === 'MemberExpression') { const obj = this.evaluate(left.object, env); obj[left.property] = rightVal; return rightVal; }
186
+ if (left.type === 'IndexExpression') { const obj = this.evaluate(left.object, env); const idx = this.evaluate(left.indexer, env); obj[idx] = rightVal; return rightVal; }
216
187
  throw new Error('Invalid assignment target');
217
188
  }
218
189
 
219
190
  evalCompoundAssignment(node, env) {
220
191
  const left = node.left;
221
192
  let current;
222
-
223
193
  if (left.type === 'Identifier') current = env.get(left.name);
224
194
  else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
225
195
  else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
@@ -237,9 +207,7 @@ evalImport(node, env) {
237
207
  }
238
208
 
239
209
  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
210
  else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
242
-
243
211
  return computed;
244
212
  }
245
213
 
@@ -251,8 +219,7 @@ evalImport(node, env) {
251
219
 
252
220
  evalAsk(node, env) {
253
221
  const prompt = this.evaluate(node.prompt, env);
254
- const input = readlineSync.question(prompt + ' ');
255
- return input;
222
+ return readlineSync.question(prompt + ' ');
256
223
  }
257
224
 
258
225
  evalDefine(node, env) {
@@ -269,12 +236,16 @@ evalImport(node, env) {
269
236
  case 'STAR': return l * r;
270
237
  case 'SLASH': return l / r;
271
238
  case 'MOD': return l % r;
272
- case 'EQEQ': return l === r;
273
- case 'NOTEQ': return l !== r;
239
+ case 'EQEQ': return l == r;
240
+ case 'NOTEQ': return l != r;
241
+ case 'STRICT_EQ': return l === r;
242
+ case 'STRICT_NOTEQ': return l !== r;
274
243
  case 'LT': return l < r;
275
244
  case 'LTE': return l <= r;
276
245
  case 'GT': return l > r;
277
246
  case 'GTE': return l >= r;
247
+ case 'LSHIFT': return l << r;
248
+ case 'RSHIFT': return l >> r;
278
249
  default: throw new Error(`Unknown binary operator ${node.operator}`);
279
250
  }
280
251
  }
@@ -342,9 +313,7 @@ evalImport(node, env) {
342
313
  const args = node.arguments.map(a => this.evaluate(a, env));
343
314
  return calleeEvaluated(...args);
344
315
  }
345
- if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
346
- throw new Error('Call to non-function');
347
- }
316
+ if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) throw new Error('Call to non-function');
348
317
  const fn = calleeEvaluated;
349
318
  const callEnv = new Environment(fn.env);
350
319
  fn.params.forEach((p, i) => {
@@ -384,15 +353,8 @@ evalImport(node, env) {
384
353
  };
385
354
  const setValue = (v) => {
386
355
  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
- }
356
+ else if (arg.type === 'MemberExpression') { const obj = this.evaluate(arg.object, env); obj[arg.property] = v; }
357
+ else if (arg.type === 'IndexExpression') { const obj = this.evaluate(arg.object, env); const idx = this.evaluate(arg.indexer, env); obj[idx] = v; }
396
358
  };
397
359
  const current = getCurrent();
398
360
  const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
@@ -401,4 +363,4 @@ evalImport(node, env) {
401
363
  }
402
364
  }
403
365
 
404
- module.exports = Evaluator;
366
+ 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,46 @@ class Lexer {
110
120
  return { type: 'STRING', value: result };
111
121
  }
112
122
 
123
+ templateString() {
124
+ this.advance(); // skip initial backtick `
125
+ let buffer = '';
126
+ const tokens = [];
127
+
128
+ while (this.currentChar && this.currentChar !== '`') {
129
+ if (this.currentChar === '$' && this.peek() === '{') {
130
+ if (buffer.length > 0) {
131
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
132
+ buffer = '';
133
+ }
134
+ tokens.push({ type: 'DOLLAR_LBRACE' }); // ${
135
+ this.advance(); this.advance();
136
+ continue;
137
+ }
138
+
139
+ if (this.currentChar === '\\') {
140
+ this.advance();
141
+ switch (this.currentChar) {
142
+ case 'n': buffer += '\n'; break;
143
+ case 't': buffer += '\t'; break;
144
+ case '`': buffer += '`'; break;
145
+ case '\\': buffer += '\\'; break;
146
+ default: buffer += this.currentChar;
147
+ }
148
+ } else {
149
+ buffer += this.currentChar;
150
+ }
151
+ this.advance();
152
+ }
153
+
154
+ if (buffer.length > 0) tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
155
+
156
+ if (this.currentChar !== '`') this.error('Unterminated template string');
157
+ this.advance(); // skip closing backtick
158
+
159
+ return tokens; // return array of tokens
160
+ }
161
+
162
+
113
163
  getTokens() {
114
164
  const tokens = [];
115
165
 
@@ -119,15 +169,26 @@ class Lexer {
119
169
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
120
170
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
121
171
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
172
+ if (this.currentChar === '`') {
173
+ const tTokens = this.templateString();
174
+ tokens.push(...tTokens); // push all tokens returned from templateString
175
+ continue;
176
+ }
177
+
122
178
 
123
179
  const char = this.currentChar;
124
180
  const next = this.peek();
181
+ const next2 = this.peek(2);
125
182
 
183
+ if (char === '=' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_EQ' }); this.advance(); this.advance(); this.advance(); continue; }
184
+ if (char === '!' && next === '=' && next2 === '=') { tokens.push({ type: 'STRICT_NOTEQ' }); this.advance(); this.advance(); this.advance(); continue; }
126
185
  if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
127
186
  if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
128
187
  if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
129
188
  if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
130
189
  if (char === '>' && next === '=') { tokens.push({ type: 'GTE' }); this.advance(); this.advance(); continue; }
190
+ if (char === '<' && next === '<') { tokens.push({ type: 'LSHIFT' }); this.advance(); this.advance(); continue; }
191
+ if (char === '>' && next === '>') { tokens.push({ type: 'RSHIFT' }); this.advance(); this.advance(); continue; }
131
192
  if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
132
193
  if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
133
194
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
@@ -154,4 +215,4 @@ class Lexer {
154
215
  }
155
216
  }
156
217
 
157
- module.exports = Lexer;
218
+ 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');
@@ -224,6 +224,23 @@ class Parser {
224
224
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
225
225
  return { type: 'ExpressionStatement', expression: expr };
226
226
  }
227
+ parseTemplateLiteral() {
228
+ const parts = [];
229
+ while (true) {
230
+ if (this.current.type === 'TEMPLATE_STRING') {
231
+ parts.push({ type: 'Literal', value: this.current.value });
232
+ this.eat('TEMPLATE_STRING');
233
+ } else if (this.current.type === 'DOLLAR_LBRACE') {
234
+ this.eat('DOLLAR_LBRACE');
235
+ const expr = this.expression();
236
+ parts.push(expr);
237
+ this.eat('RBRACE');
238
+ } else {
239
+ break;
240
+ }
241
+ }
242
+ return { type: 'TemplateLiteral', parts };
243
+ }
227
244
 
228
245
  expression() {
229
246
  return this.assignment();
@@ -270,7 +287,7 @@ class Parser {
270
287
 
271
288
  equality() {
272
289
  let node = this.comparison();
273
- while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
290
+ while (['EQEQ', 'NOTEQ', 'STRICT_EQ', 'STRICT_NOTEQ'].includes(this.current.type)) {
274
291
  const op = this.current.type;
275
292
  this.eat(op);
276
293
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
@@ -280,7 +297,7 @@ class Parser {
280
297
 
281
298
  comparison() {
282
299
  let node = this.term();
283
- while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
300
+ while (['LT', 'LTE', 'GT', 'GTE', 'LSHIFT', 'RSHIFT'].includes(this.current.type)) {
284
301
  const op = this.current.type;
285
302
  this.eat(op);
286
303
  node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
@@ -371,9 +388,14 @@ class Parser {
371
388
 
372
389
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
373
390
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
391
+ if (t.type === 'TEMPLATE_STRING' || t.type === 'DOLLAR_LBRACE') {
392
+ return this.parseTemplateLiteral();
393
+ }
394
+
374
395
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
375
396
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
376
397
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
398
+ if (t.type === 'UNDEFINED') { this.eat('UNDEFINED'); return { type: 'Literal', value: undefined }; }
377
399
 
378
400
  if (t.type === 'ASK') {
379
401
  this.eat('ASK');
@@ -438,4 +460,4 @@ class Parser {
438
460
  }
439
461
  }
440
462
 
441
- module.exports = Parser;
463
+ 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.19';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',