starlight-cli 1.0.19 → 1.0.21

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
@@ -1718,7 +1718,7 @@ class Lexer {
1718
1718
  constructor(input) {
1719
1719
  this.input = input;
1720
1720
  this.pos = 0;
1721
- this.currentChar = input[0] || null;
1721
+ this.currentChar = input[this.pos];
1722
1722
  }
1723
1723
 
1724
1724
  advance() {
@@ -1726,206 +1726,155 @@ class Lexer {
1726
1726
  this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
1727
1727
  }
1728
1728
 
1729
- peek(n = 1) {
1730
- return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
1731
- }
1732
-
1733
- error(msg) {
1734
- throw new Error(`LEXER ERROR: ${msg} at position ${this.pos}`);
1729
+ peek() {
1730
+ return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
1735
1731
  }
1736
1732
 
1737
1733
  skipWhitespace() {
1738
1734
  while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
1739
1735
  }
1740
1736
 
1741
- skipComment() {
1742
- if (this.currentChar === '#') {
1743
- if (this.peek() === '*') {
1744
- this.advance(); this.advance(); // skip #*
1745
- while (this.currentChar !== null) {
1746
- if (this.currentChar === '*' && this.peek() === '#') {
1747
- this.advance(); this.advance();
1748
- return;
1749
- }
1750
- this.advance();
1751
- }
1752
- this.error("Unterminated multi-line comment (#* ... *#)");
1753
- } else {
1754
- while (this.currentChar && this.currentChar !== '\n') this.advance();
1755
- }
1756
- }
1737
+ matchKeyword(id) {
1738
+ const keywords = {
1739
+ 'let': 'LET',
1740
+ 'define': 'DEFINE',
1741
+ 'if': 'IF',
1742
+ 'else': 'ELSE',
1743
+ 'while': 'WHILE',
1744
+ 'for': 'FOR',
1745
+ 'break': 'BREAK',
1746
+ 'continue': 'CONTINUE',
1747
+ 'func': 'FUNC',
1748
+ 'return': 'RETURN',
1749
+ 'import': 'IMPORT',
1750
+ 'from': 'FROM',
1751
+ 'as': 'AS',
1752
+ 'true': 'TRUE',
1753
+ 'false': 'FALSE',
1754
+ 'null': 'NULL',
1755
+ 'undefined': 'UNDEFINED',
1756
+ 'ask': 'ASK',
1757
+ 'sldeploy': 'SLDEPLOY'
1758
+ };
1759
+ return keywords[id] || null;
1757
1760
  }
1758
1761
 
1759
1762
  number() {
1760
1763
  let result = '';
1761
- while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1764
+ while (this.currentChar && /[0-9.]/.test(this.currentChar)) {
1762
1765
  result += this.currentChar;
1763
1766
  this.advance();
1764
1767
  }
1765
-
1766
- if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
1767
- result += '.'; this.advance();
1768
- while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1769
- result += this.currentChar;
1770
- this.advance();
1771
- }
1772
- }
1773
-
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
1768
  return { type: 'NUMBER', value: parseFloat(result) };
1787
1769
  }
1788
1770
 
1789
1771
  identifier() {
1790
1772
  let result = '';
1791
- while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
1773
+ while (this.currentChar && /[a-zA-Z0-9_]/.test(this.currentChar)) {
1792
1774
  result += this.currentChar;
1793
1775
  this.advance();
1794
1776
  }
1795
-
1796
- const keywords = [
1797
- 'let', 'sldeploy', 'if', 'else', 'while', 'for',
1798
- 'break', 'continue', 'func', 'return', 'true', 'false', 'null',
1799
- 'ask', 'define', 'import', 'from', 'as', 'undefined'
1800
- ];
1801
-
1802
- if (keywords.includes(result)) {
1803
- return { type: result.toUpperCase(), value: result };
1804
- }
1805
-
1806
- return { type: 'IDENTIFIER', value: result };
1777
+ const type = this.matchKeyword(result) || 'IDENTIFIER';
1778
+ return { type, value: result };
1807
1779
  }
1808
1780
 
1809
- string() {
1810
- const quote = this.currentChar;
1811
- this.advance();
1781
+ string(quoteType) {
1782
+ this.advance(); // skip opening quote
1812
1783
  let result = '';
1813
-
1814
- while (this.currentChar && this.currentChar !== quote) {
1784
+ while (this.currentChar && this.currentChar !== quoteType) {
1815
1785
  if (this.currentChar === '\\') {
1816
1786
  this.advance();
1817
- switch (this.currentChar) {
1818
- case 'n': result += '\n'; break;
1819
- case 't': result += '\t'; break;
1820
- case '"': result += '"'; break;
1821
- case "'": result += "'"; break;
1822
- case '\\': result += '\\'; break;
1823
- default: result += this.currentChar;
1787
+ if (this.currentChar) {
1788
+ const escapeChars = { n: '\n', r: '\r', t: '\t', '\\': '\\', '"': '"', "'": "'" };
1789
+ result += escapeChars[this.currentChar] || this.currentChar;
1790
+ this.advance();
1824
1791
  }
1825
1792
  } else {
1826
1793
  result += this.currentChar;
1794
+ this.advance();
1827
1795
  }
1828
- this.advance();
1829
1796
  }
1830
-
1831
- if (this.currentChar !== quote) {
1832
- this.error('Unterminated string literal');
1833
- }
1834
-
1835
- this.advance();
1797
+ this.advance(); // skip closing quote
1836
1798
  return { type: 'STRING', value: result };
1837
1799
  }
1838
1800
 
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;
1801
+ templateLiteral() {
1802
+ let result = '';
1803
+ const tokens = [];
1804
+ this.advance(); // skip initial backtick
1805
+ while (this.currentChar !== null) {
1806
+ if (this.currentChar === '$' && this.peek() === '{') {
1807
+ if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
1808
+ result = '';
1809
+ tokens.push({ type: 'DOLLAR_LBRACE' });
1810
+ this.advance();
1811
+ this.advance(); // skip "${"
1812
+ } else if (this.currentChar === '`') {
1813
+ if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
1814
+ this.advance();
1815
+ break;
1816
+ } else {
1817
+ result += this.currentChar;
1818
+ this.advance();
1863
1819
  }
1864
- } else {
1865
- buffer += this.currentChar;
1866
1820
  }
1867
- this.advance();
1821
+ return tokens;
1868
1822
  }
1869
1823
 
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
-
1879
1824
  getTokens() {
1880
1825
  const tokens = [];
1881
-
1882
1826
  while (this.currentChar !== null) {
1883
- if (/\s/.test(this.currentChar)) { this.skipWhitespace(); continue; }
1884
- if (this.currentChar === '#') { this.skipComment(); continue; }
1885
- if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
1886
- if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
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
- }
1827
+ this.skipWhitespace();
1893
1828
 
1829
+ if (!this.currentChar) break;
1894
1830
 
1895
- const char = this.currentChar;
1896
- const next = this.peek();
1897
- const next2 = this.peek(2);
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; }
1901
- if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
1902
- if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
1903
- if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
1904
- if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
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; }
1908
- if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
1909
- if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
1910
- if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
1911
- if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
1912
-
1913
- const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
1914
- if (next === '=' && map[char]) { tokens.push({ type: map[char] }); this.advance(); this.advance(); continue; }
1915
-
1916
- const singles = {
1917
- '+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
1918
- '=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
1919
- '(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
1920
- ';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
1921
- ':': 'COLON', '.': 'DOT'
1922
- };
1923
-
1924
- if (singles[char]) { tokens.push({ type: singles[char] }); this.advance(); continue; }
1925
-
1926
- this.error("Unexpected character: " + char);
1831
+ if (/[0-9]/.test(this.currentChar)) tokens.push(this.number());
1832
+ else if (/[a-zA-Z_]/.test(this.currentChar)) tokens.push(this.identifier());
1833
+ else if (this.currentChar === '"' || this.currentChar === "'") tokens.push(this.string(this.currentChar));
1834
+ else if (this.currentChar === '`') {
1835
+ const parts = this.templateLiteral();
1836
+ tokens.push(...parts);
1837
+ }
1838
+ else {
1839
+ const char = this.currentChar;
1840
+ switch (char) {
1841
+ case '+':
1842
+ if (this.peek() === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); }
1843
+ else if (this.peek() === '=') { tokens.push({ type: 'PLUSEQ' }); this.advance(); }
1844
+ else tokens.push({ type: 'PLUS' });
1845
+ break;
1846
+ case '-':
1847
+ if (this.peek() === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); }
1848
+ else if (this.peek() === '=') { tokens.push({ type: 'MINUSEQ' }); this.advance(); }
1849
+ else tokens.push({ type: 'MINUS' });
1850
+ break;
1851
+ case '*': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'STAREQ' }) : { type: 'STAR' }); break;
1852
+ case '/': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'SLASHEQ' }) : { type: 'SLASH' }); break;
1853
+ case '%': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'MODEQ' }) : { type: 'MOD' }); break;
1854
+ case '=': tokens.push(this.peek() === '=' ? (this.advance(), this.peek() === '=' ? (this.advance(), { type: 'STRICT_EQ' }) : { type: 'EQEQ' }) : { type: 'EQUAL' }); break;
1855
+ case '!': tokens.push(this.peek() === '=' ? (this.advance(), this.peek() === '=' ? (this.advance(), { type: 'STRICT_NOTEQ' }) : { type: 'NOTEQ' }) : { type: 'NOT' }); break;
1856
+ case '<': tokens.push(this.peek() === '<' ? (this.advance(), { type: 'LSHIFT' }) : this.peek() === '=' ? (this.advance(), { type: 'LTE' }) : { type: 'LT' }); break;
1857
+ case '>': tokens.push(this.peek() === '>' ? (this.advance(), { type: 'RSHIFT' }) : this.peek() === '=' ? (this.advance(), { type: 'GTE' }) : { type: 'GT' }); break;
1858
+ case '&': tokens.push(this.peek() === '&' ? (this.advance(), { type: 'AND' }) : { type: 'AMP' }); break;
1859
+ case '|': tokens.push(this.peek() === '|' ? (this.advance(), { type: 'OR' }) : { type: 'PIPE' }); break;
1860
+ case '(': tokens.push({ type: 'LPAREN' }); break;
1861
+ case ')': tokens.push({ type: 'RPAREN' }); break;
1862
+ case '{': tokens.push({ type: 'LBRACE' }); break;
1863
+ case '}': tokens.push({ type: 'RBRACE' }); break;
1864
+ case '[': tokens.push({ type: 'LBRACKET' }); break;
1865
+ case ']': tokens.push({ type: 'RBRACKET' }); break;
1866
+ case ',': tokens.push({ type: 'COMMA' }); break;
1867
+ case ';': tokens.push({ type: 'SEMICOLON' }); break;
1868
+ case '.': tokens.push({ type: 'DOT' }); break;
1869
+ case ':': tokens.push({ type: 'COLON' }); break;
1870
+ case '*': tokens.push({ type: 'STAR' }); break;
1871
+ case '$': tokens.push({ type: 'DOLLAR' }); break;
1872
+ case '?': tokens.push({ type: 'QUESTION' }); break;
1873
+ default: throw new Error(`Unexpected character: ${char} at position ${this.pos}`);
1874
+ }
1875
+ this.advance();
1876
+ }
1927
1877
  }
1928
-
1929
1878
  tokens.push({ type: 'EOF' });
1930
1879
  return tokens;
1931
1880
  }
@@ -2329,10 +2278,11 @@ class Parser {
2329
2278
 
2330
2279
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2331
2280
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2332
- if (t.type === 'TEMPLATE_STRING' || t.type === 'DOLLAR_LBRACE') {
2281
+ if (t.type === 'TEMPLATE_STRING') {
2333
2282
  return this.parseTemplateLiteral();
2334
2283
  }
2335
2284
 
2285
+
2336
2286
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2337
2287
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2338
2288
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
@@ -2504,7 +2454,7 @@ const Lexer = __nccwpck_require__(211);
2504
2454
  const Parser = __nccwpck_require__(222);
2505
2455
  const Evaluator = __nccwpck_require__(112);
2506
2456
 
2507
- const VERSION = '1.0.19';
2457
+ const VERSION = '1.0.21';
2508
2458
 
2509
2459
  const COLOR = {
2510
2460
  reset: '\x1b[0m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/lexer.js CHANGED
@@ -2,7 +2,7 @@ class Lexer {
2
2
  constructor(input) {
3
3
  this.input = input;
4
4
  this.pos = 0;
5
- this.currentChar = input[0] || null;
5
+ this.currentChar = input[this.pos];
6
6
  }
7
7
 
8
8
  advance() {
@@ -10,206 +10,155 @@ class Lexer {
10
10
  this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
11
11
  }
12
12
 
13
- peek(n = 1) {
14
- return this.pos + n < this.input.length ? this.input[this.pos + n] : null;
15
- }
16
-
17
- error(msg) {
18
- throw new Error(`LEXER ERROR: ${msg} at position ${this.pos}`);
13
+ peek() {
14
+ return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
19
15
  }
20
16
 
21
17
  skipWhitespace() {
22
18
  while (this.currentChar && /\s/.test(this.currentChar)) this.advance();
23
19
  }
24
20
 
25
- skipComment() {
26
- if (this.currentChar === '#') {
27
- if (this.peek() === '*') {
28
- this.advance(); this.advance(); // skip #*
29
- while (this.currentChar !== null) {
30
- if (this.currentChar === '*' && this.peek() === '#') {
31
- this.advance(); this.advance();
32
- return;
33
- }
34
- this.advance();
35
- }
36
- this.error("Unterminated multi-line comment (#* ... *#)");
37
- } else {
38
- while (this.currentChar && this.currentChar !== '\n') this.advance();
39
- }
40
- }
21
+ matchKeyword(id) {
22
+ const keywords = {
23
+ 'let': 'LET',
24
+ 'define': 'DEFINE',
25
+ 'if': 'IF',
26
+ 'else': 'ELSE',
27
+ 'while': 'WHILE',
28
+ 'for': 'FOR',
29
+ 'break': 'BREAK',
30
+ 'continue': 'CONTINUE',
31
+ 'func': 'FUNC',
32
+ 'return': 'RETURN',
33
+ 'import': 'IMPORT',
34
+ 'from': 'FROM',
35
+ 'as': 'AS',
36
+ 'true': 'TRUE',
37
+ 'false': 'FALSE',
38
+ 'null': 'NULL',
39
+ 'undefined': 'UNDEFINED',
40
+ 'ask': 'ASK',
41
+ 'sldeploy': 'SLDEPLOY'
42
+ };
43
+ return keywords[id] || null;
41
44
  }
42
45
 
43
46
  number() {
44
47
  let result = '';
45
- while (this.currentChar && /[0-9]/.test(this.currentChar)) {
48
+ while (this.currentChar && /[0-9.]/.test(this.currentChar)) {
46
49
  result += this.currentChar;
47
50
  this.advance();
48
51
  }
49
-
50
- if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
51
- result += '.'; this.advance();
52
- while (this.currentChar && /[0-9]/.test(this.currentChar)) {
53
- result += this.currentChar;
54
- this.advance();
55
- }
56
- }
57
-
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
52
  return { type: 'NUMBER', value: parseFloat(result) };
71
53
  }
72
54
 
73
55
  identifier() {
74
56
  let result = '';
75
- while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
57
+ while (this.currentChar && /[a-zA-Z0-9_]/.test(this.currentChar)) {
76
58
  result += this.currentChar;
77
59
  this.advance();
78
60
  }
79
-
80
- const keywords = [
81
- 'let', 'sldeploy', 'if', 'else', 'while', 'for',
82
- 'break', 'continue', 'func', 'return', 'true', 'false', 'null',
83
- 'ask', 'define', 'import', 'from', 'as', 'undefined'
84
- ];
85
-
86
- if (keywords.includes(result)) {
87
- return { type: result.toUpperCase(), value: result };
88
- }
89
-
90
- return { type: 'IDENTIFIER', value: result };
61
+ const type = this.matchKeyword(result) || 'IDENTIFIER';
62
+ return { type, value: result };
91
63
  }
92
64
 
93
- string() {
94
- const quote = this.currentChar;
95
- this.advance();
65
+ string(quoteType) {
66
+ this.advance(); // skip opening quote
96
67
  let result = '';
97
-
98
- while (this.currentChar && this.currentChar !== quote) {
68
+ while (this.currentChar && this.currentChar !== quoteType) {
99
69
  if (this.currentChar === '\\') {
100
70
  this.advance();
101
- switch (this.currentChar) {
102
- case 'n': result += '\n'; break;
103
- case 't': result += '\t'; break;
104
- case '"': result += '"'; break;
105
- case "'": result += "'"; break;
106
- case '\\': result += '\\'; break;
107
- default: result += this.currentChar;
71
+ if (this.currentChar) {
72
+ const escapeChars = { n: '\n', r: '\r', t: '\t', '\\': '\\', '"': '"', "'": "'" };
73
+ result += escapeChars[this.currentChar] || this.currentChar;
74
+ this.advance();
108
75
  }
109
76
  } else {
110
77
  result += this.currentChar;
78
+ this.advance();
111
79
  }
112
- this.advance();
113
80
  }
114
-
115
- if (this.currentChar !== quote) {
116
- this.error('Unterminated string literal');
117
- }
118
-
119
- this.advance();
81
+ this.advance(); // skip closing quote
120
82
  return { type: 'STRING', value: result };
121
83
  }
122
84
 
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;
85
+ templateLiteral() {
86
+ let result = '';
87
+ const tokens = [];
88
+ this.advance(); // skip initial backtick
89
+ while (this.currentChar !== null) {
90
+ if (this.currentChar === '$' && this.peek() === '{') {
91
+ if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
92
+ result = '';
93
+ tokens.push({ type: 'DOLLAR_LBRACE' });
94
+ this.advance();
95
+ this.advance(); // skip "${"
96
+ } else if (this.currentChar === '`') {
97
+ if (result) tokens.push({ type: 'TEMPLATE_STRING', value: result });
98
+ this.advance();
99
+ break;
100
+ } else {
101
+ result += this.currentChar;
102
+ this.advance();
147
103
  }
148
- } else {
149
- buffer += this.currentChar;
150
104
  }
151
- this.advance();
105
+ return tokens;
152
106
  }
153
107
 
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
-
163
108
  getTokens() {
164
109
  const tokens = [];
165
-
166
110
  while (this.currentChar !== null) {
167
- if (/\s/.test(this.currentChar)) { this.skipWhitespace(); continue; }
168
- if (this.currentChar === '#') { this.skipComment(); continue; }
169
- if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
170
- if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
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
-
178
-
179
- const char = this.currentChar;
180
- const next = this.peek();
181
- const next2 = this.peek(2);
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; }
185
- if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ' }); this.advance(); this.advance(); continue; }
186
- if (char === '=' && next === '>') { tokens.push({ type: 'ARROW' }); this.advance(); this.advance(); continue; }
187
- if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ' }); this.advance(); this.advance(); continue; }
188
- if (char === '<' && next === '=') { tokens.push({ type: 'LTE' }); this.advance(); this.advance(); continue; }
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; }
192
- if (char === '&' && next === '&') { tokens.push({ type: 'AND' }); this.advance(); this.advance(); continue; }
193
- if (char === '|' && next === '|') { tokens.push({ type: 'OR' }); this.advance(); this.advance(); continue; }
194
- if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
195
- if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
111
+ this.skipWhitespace();
196
112
 
197
- const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
198
- if (next === '=' && map[char]) { tokens.push({ type: map[char] }); this.advance(); this.advance(); continue; }
113
+ if (!this.currentChar) break;
199
114
 
200
- const singles = {
201
- '+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
202
- '=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
203
- '(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
204
- ';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
205
- ':': 'COLON', '.': 'DOT'
206
- };
207
-
208
- if (singles[char]) { tokens.push({ type: singles[char] }); this.advance(); continue; }
209
-
210
- this.error("Unexpected character: " + char);
115
+ if (/[0-9]/.test(this.currentChar)) tokens.push(this.number());
116
+ else if (/[a-zA-Z_]/.test(this.currentChar)) tokens.push(this.identifier());
117
+ else if (this.currentChar === '"' || this.currentChar === "'") tokens.push(this.string(this.currentChar));
118
+ else if (this.currentChar === '`') {
119
+ const parts = this.templateLiteral();
120
+ tokens.push(...parts);
121
+ }
122
+ else {
123
+ const char = this.currentChar;
124
+ switch (char) {
125
+ case '+':
126
+ if (this.peek() === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); }
127
+ else if (this.peek() === '=') { tokens.push({ type: 'PLUSEQ' }); this.advance(); }
128
+ else tokens.push({ type: 'PLUS' });
129
+ break;
130
+ case '-':
131
+ if (this.peek() === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); }
132
+ else if (this.peek() === '=') { tokens.push({ type: 'MINUSEQ' }); this.advance(); }
133
+ else tokens.push({ type: 'MINUS' });
134
+ break;
135
+ case '*': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'STAREQ' }) : { type: 'STAR' }); break;
136
+ case '/': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'SLASHEQ' }) : { type: 'SLASH' }); break;
137
+ case '%': tokens.push(this.peek() === '=' ? (this.advance(), { type: 'MODEQ' }) : { type: 'MOD' }); break;
138
+ case '=': tokens.push(this.peek() === '=' ? (this.advance(), this.peek() === '=' ? (this.advance(), { type: 'STRICT_EQ' }) : { type: 'EQEQ' }) : { type: 'EQUAL' }); break;
139
+ case '!': tokens.push(this.peek() === '=' ? (this.advance(), this.peek() === '=' ? (this.advance(), { type: 'STRICT_NOTEQ' }) : { type: 'NOTEQ' }) : { type: 'NOT' }); break;
140
+ case '<': tokens.push(this.peek() === '<' ? (this.advance(), { type: 'LSHIFT' }) : this.peek() === '=' ? (this.advance(), { type: 'LTE' }) : { type: 'LT' }); break;
141
+ case '>': tokens.push(this.peek() === '>' ? (this.advance(), { type: 'RSHIFT' }) : this.peek() === '=' ? (this.advance(), { type: 'GTE' }) : { type: 'GT' }); break;
142
+ case '&': tokens.push(this.peek() === '&' ? (this.advance(), { type: 'AND' }) : { type: 'AMP' }); break;
143
+ case '|': tokens.push(this.peek() === '|' ? (this.advance(), { type: 'OR' }) : { type: 'PIPE' }); break;
144
+ case '(': tokens.push({ type: 'LPAREN' }); break;
145
+ case ')': tokens.push({ type: 'RPAREN' }); break;
146
+ case '{': tokens.push({ type: 'LBRACE' }); break;
147
+ case '}': tokens.push({ type: 'RBRACE' }); break;
148
+ case '[': tokens.push({ type: 'LBRACKET' }); break;
149
+ case ']': tokens.push({ type: 'RBRACKET' }); break;
150
+ case ',': tokens.push({ type: 'COMMA' }); break;
151
+ case ';': tokens.push({ type: 'SEMICOLON' }); break;
152
+ case '.': tokens.push({ type: 'DOT' }); break;
153
+ case ':': tokens.push({ type: 'COLON' }); break;
154
+ case '*': tokens.push({ type: 'STAR' }); break;
155
+ case '$': tokens.push({ type: 'DOLLAR' }); break;
156
+ case '?': tokens.push({ type: 'QUESTION' }); break;
157
+ default: throw new Error(`Unexpected character: ${char} at position ${this.pos}`);
158
+ }
159
+ this.advance();
160
+ }
211
161
  }
212
-
213
162
  tokens.push({ type: 'EOF' });
214
163
  return tokens;
215
164
  }
package/src/parser.js CHANGED
@@ -388,10 +388,11 @@ class Parser {
388
388
 
389
389
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
390
390
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
391
- if (t.type === 'TEMPLATE_STRING' || t.type === 'DOLLAR_LBRACE') {
391
+ if (t.type === 'TEMPLATE_STRING') {
392
392
  return this.parseTemplateLiteral();
393
393
  }
394
394
 
395
+
395
396
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
396
397
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
397
398
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
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.19';
12
+ const VERSION = '1.0.21';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',