starlight-cli 1.0.18 → 1.0.20
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 +101 -52
- package/package.json +1 -1
- package/src/evaluator.js +13 -2
- package/src/lexer.js +65 -48
- package/src/parser.js +22 -1
- package/src/starlight.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1457,9 +1457,20 @@ class Evaluator {
|
|
|
1457
1457
|
return result;
|
|
1458
1458
|
}
|
|
1459
1459
|
|
|
1460
|
-
|
|
1461
|
-
|
|
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() : '';
|
|
1469
|
+
}
|
|
1462
1470
|
}
|
|
1471
|
+
return result;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1463
1474
|
|
|
1464
1475
|
evalImport(node, env) {
|
|
1465
1476
|
const spec = node.path;
|
|
@@ -1730,7 +1741,7 @@ class Lexer {
|
|
|
1730
1741
|
skipComment() {
|
|
1731
1742
|
if (this.currentChar === '#') {
|
|
1732
1743
|
if (this.peek() === '*') {
|
|
1733
|
-
this.advance(); this.advance(); //
|
|
1744
|
+
this.advance(); this.advance(); // #*
|
|
1734
1745
|
while (this.currentChar !== null) {
|
|
1735
1746
|
if (this.currentChar === '*' && this.peek() === '#') {
|
|
1736
1747
|
this.advance(); this.advance();
|
|
@@ -1738,7 +1749,7 @@ class Lexer {
|
|
|
1738
1749
|
}
|
|
1739
1750
|
this.advance();
|
|
1740
1751
|
}
|
|
1741
|
-
this.error(
|
|
1752
|
+
this.error('Unterminated multi-line comment (#* ... *#)');
|
|
1742
1753
|
} else {
|
|
1743
1754
|
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
1744
1755
|
}
|
|
@@ -1753,7 +1764,8 @@ class Lexer {
|
|
|
1753
1764
|
}
|
|
1754
1765
|
|
|
1755
1766
|
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
1756
|
-
result += '.';
|
|
1767
|
+
result += '.';
|
|
1768
|
+
this.advance();
|
|
1757
1769
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1758
1770
|
result += this.currentChar;
|
|
1759
1771
|
this.advance();
|
|
@@ -1761,11 +1773,13 @@ class Lexer {
|
|
|
1761
1773
|
}
|
|
1762
1774
|
|
|
1763
1775
|
if (this.currentChar && /[eE]/.test(this.currentChar)) {
|
|
1764
|
-
result += this.currentChar;
|
|
1776
|
+
result += this.currentChar;
|
|
1777
|
+
this.advance();
|
|
1765
1778
|
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
1766
|
-
result += this.currentChar;
|
|
1779
|
+
result += this.currentChar;
|
|
1780
|
+
this.advance();
|
|
1767
1781
|
}
|
|
1768
|
-
if (!/[0-9]/.test(this.currentChar)) this.error(
|
|
1782
|
+
if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
|
|
1769
1783
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
1770
1784
|
result += this.currentChar;
|
|
1771
1785
|
this.advance();
|
|
@@ -1784,8 +1798,9 @@ class Lexer {
|
|
|
1784
1798
|
|
|
1785
1799
|
const keywords = [
|
|
1786
1800
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
1787
|
-
'break', 'continue', 'func', 'return',
|
|
1788
|
-
'
|
|
1801
|
+
'break', 'continue', 'func', 'return',
|
|
1802
|
+
'true', 'false', 'null', 'undefined',
|
|
1803
|
+
'ask', 'define', 'import', 'from', 'as'
|
|
1789
1804
|
];
|
|
1790
1805
|
|
|
1791
1806
|
if (keywords.includes(result)) {
|
|
@@ -1803,59 +1818,58 @@ class Lexer {
|
|
|
1803
1818
|
while (this.currentChar && this.currentChar !== quote) {
|
|
1804
1819
|
if (this.currentChar === '\\') {
|
|
1805
1820
|
this.advance();
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
case 't': result += '\t'; break;
|
|
1809
|
-
case '"': result += '"'; break;
|
|
1810
|
-
case "'": result += "'"; break;
|
|
1811
|
-
case '\\': result += '\\'; break;
|
|
1812
|
-
default: result += this.currentChar;
|
|
1813
|
-
}
|
|
1821
|
+
const map = { n: '\n', t: '\t', '"': '"', "'": "'", '\\': '\\' };
|
|
1822
|
+
result += map[this.currentChar] ?? this.currentChar;
|
|
1814
1823
|
} else {
|
|
1815
1824
|
result += this.currentChar;
|
|
1816
1825
|
}
|
|
1817
1826
|
this.advance();
|
|
1818
1827
|
}
|
|
1819
1828
|
|
|
1820
|
-
if (this.currentChar !== quote)
|
|
1821
|
-
this.error('Unterminated string literal');
|
|
1822
|
-
}
|
|
1829
|
+
if (this.currentChar !== quote) this.error('Unterminated string literal');
|
|
1823
1830
|
|
|
1824
1831
|
this.advance();
|
|
1825
1832
|
return { type: 'STRING', value: result };
|
|
1826
1833
|
}
|
|
1827
1834
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1835
|
+
|
|
1836
|
+
templateString(tokens) {
|
|
1837
|
+
this.advance(); // skip opening `
|
|
1838
|
+
|
|
1839
|
+
let buffer = '';
|
|
1840
|
+
|
|
1841
|
+
while (this.currentChar !== null) {
|
|
1842
|
+
if (this.currentChar === '`') {
|
|
1843
|
+
if (buffer.length > 0) {
|
|
1844
|
+
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
1845
|
+
}
|
|
1846
|
+
this.advance();
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1832
1850
|
if (this.currentChar === '$' && this.peek() === '{') {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1851
|
+
if (buffer.length > 0) {
|
|
1852
|
+
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
1853
|
+
buffer = '';
|
|
1854
|
+
}
|
|
1855
|
+
this.advance(); // $
|
|
1856
|
+
this.advance(); // {
|
|
1857
|
+
tokens.push({ type: 'DOLLAR_LBRACE' });
|
|
1858
|
+
return;
|
|
1836
1859
|
}
|
|
1837
1860
|
|
|
1838
1861
|
if (this.currentChar === '\\') {
|
|
1839
1862
|
this.advance();
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
case '`': result += '`'; break;
|
|
1844
|
-
case '\\': result += '\\'; break;
|
|
1845
|
-
default: result += this.currentChar;
|
|
1846
|
-
}
|
|
1847
|
-
} else {
|
|
1848
|
-
result += this.currentChar;
|
|
1863
|
+
buffer += this.currentChar;
|
|
1864
|
+
this.advance();
|
|
1865
|
+
continue;
|
|
1849
1866
|
}
|
|
1850
|
-
this.advance();
|
|
1851
|
-
}
|
|
1852
1867
|
|
|
1853
|
-
|
|
1854
|
-
this.
|
|
1868
|
+
buffer += this.currentChar;
|
|
1869
|
+
this.advance();
|
|
1855
1870
|
}
|
|
1856
1871
|
|
|
1857
|
-
this.
|
|
1858
|
-
return { type: 'TEMPLATE_STRING', value: result };
|
|
1872
|
+
this.error('Unterminated template string');
|
|
1859
1873
|
}
|
|
1860
1874
|
|
|
1861
1875
|
getTokens() {
|
|
@@ -1867,7 +1881,11 @@ class Lexer {
|
|
|
1867
1881
|
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
1868
1882
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
1869
1883
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
1870
|
-
|
|
1884
|
+
|
|
1885
|
+
if (this.currentChar === '`') {
|
|
1886
|
+
this.templateString(tokens);
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1871
1889
|
|
|
1872
1890
|
const char = this.currentChar;
|
|
1873
1891
|
const next = this.peek();
|
|
@@ -1887,20 +1905,30 @@ class Lexer {
|
|
|
1887
1905
|
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
|
|
1888
1906
|
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
|
|
1889
1907
|
|
|
1890
|
-
const
|
|
1891
|
-
if (next === '=' &&
|
|
1908
|
+
const compound = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
1909
|
+
if (next === '=' && compound[char]) {
|
|
1910
|
+
tokens.push({ type: compound[char] });
|
|
1911
|
+
this.advance(); this.advance();
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1892
1914
|
|
|
1893
1915
|
const singles = {
|
|
1894
1916
|
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
1895
1917
|
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
1896
|
-
'(': 'LPAREN', ')': 'RPAREN',
|
|
1897
|
-
'
|
|
1918
|
+
'(': 'LPAREN', ')': 'RPAREN',
|
|
1919
|
+
'{': 'LBRACE', '}': 'RBRACE',
|
|
1920
|
+
'[': 'LBRACKET', ']': 'RBRACKET',
|
|
1921
|
+
';': 'SEMICOLON', ',': 'COMMA',
|
|
1898
1922
|
':': 'COLON', '.': 'DOT'
|
|
1899
1923
|
};
|
|
1900
1924
|
|
|
1901
|
-
if (singles[char]) {
|
|
1925
|
+
if (singles[char]) {
|
|
1926
|
+
tokens.push({ type: singles[char] });
|
|
1927
|
+
this.advance();
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1902
1930
|
|
|
1903
|
-
this.error(
|
|
1931
|
+
this.error(`Unexpected character: ${char}`);
|
|
1904
1932
|
}
|
|
1905
1933
|
|
|
1906
1934
|
tokens.push({ type: 'EOF' });
|
|
@@ -2142,6 +2170,23 @@ class Parser {
|
|
|
2142
2170
|
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2143
2171
|
return { type: 'ExpressionStatement', expression: expr };
|
|
2144
2172
|
}
|
|
2173
|
+
parseTemplateLiteral() {
|
|
2174
|
+
const parts = [];
|
|
2175
|
+
while (true) {
|
|
2176
|
+
if (this.current.type === 'TEMPLATE_STRING') {
|
|
2177
|
+
parts.push({ type: 'Literal', value: this.current.value });
|
|
2178
|
+
this.eat('TEMPLATE_STRING');
|
|
2179
|
+
} else if (this.current.type === 'DOLLAR_LBRACE') {
|
|
2180
|
+
this.eat('DOLLAR_LBRACE');
|
|
2181
|
+
const expr = this.expression();
|
|
2182
|
+
parts.push(expr);
|
|
2183
|
+
this.eat('RBRACE');
|
|
2184
|
+
} else {
|
|
2185
|
+
break;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
return { type: 'TemplateLiteral', parts };
|
|
2189
|
+
}
|
|
2145
2190
|
|
|
2146
2191
|
expression() {
|
|
2147
2192
|
return this.assignment();
|
|
@@ -2289,7 +2334,11 @@ class Parser {
|
|
|
2289
2334
|
|
|
2290
2335
|
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2291
2336
|
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2292
|
-
|
|
2337
|
+
if (t.type === 'TEMPLATE_STRING') {
|
|
2338
|
+
return this.parseTemplateLiteral();
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
|
|
2293
2342
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2294
2343
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2295
2344
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
@@ -2461,7 +2510,7 @@ const Lexer = __nccwpck_require__(211);
|
|
|
2461
2510
|
const Parser = __nccwpck_require__(222);
|
|
2462
2511
|
const Evaluator = __nccwpck_require__(112);
|
|
2463
2512
|
|
|
2464
|
-
const VERSION = '1.0.
|
|
2513
|
+
const VERSION = '1.0.20';
|
|
2465
2514
|
|
|
2466
2515
|
const COLOR = {
|
|
2467
2516
|
reset: '\x1b[0m',
|
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -114,9 +114,20 @@ class Evaluator {
|
|
|
114
114
|
return result;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
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() : '';
|
|
126
|
+
}
|
|
119
127
|
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
|
|
121
132
|
evalImport(node, env) {
|
|
122
133
|
const spec = node.path;
|
package/src/lexer.js
CHANGED
|
@@ -25,7 +25,7 @@ class Lexer {
|
|
|
25
25
|
skipComment() {
|
|
26
26
|
if (this.currentChar === '#') {
|
|
27
27
|
if (this.peek() === '*') {
|
|
28
|
-
this.advance(); this.advance(); //
|
|
28
|
+
this.advance(); this.advance(); // #*
|
|
29
29
|
while (this.currentChar !== null) {
|
|
30
30
|
if (this.currentChar === '*' && this.peek() === '#') {
|
|
31
31
|
this.advance(); this.advance();
|
|
@@ -33,7 +33,7 @@ class Lexer {
|
|
|
33
33
|
}
|
|
34
34
|
this.advance();
|
|
35
35
|
}
|
|
36
|
-
this.error(
|
|
36
|
+
this.error('Unterminated multi-line comment (#* ... *#)');
|
|
37
37
|
} else {
|
|
38
38
|
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
39
39
|
}
|
|
@@ -48,7 +48,8 @@ class Lexer {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
51
|
-
result += '.';
|
|
51
|
+
result += '.';
|
|
52
|
+
this.advance();
|
|
52
53
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
53
54
|
result += this.currentChar;
|
|
54
55
|
this.advance();
|
|
@@ -56,11 +57,13 @@ class Lexer {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (this.currentChar && /[eE]/.test(this.currentChar)) {
|
|
59
|
-
result += this.currentChar;
|
|
60
|
+
result += this.currentChar;
|
|
61
|
+
this.advance();
|
|
60
62
|
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
61
|
-
result += this.currentChar;
|
|
63
|
+
result += this.currentChar;
|
|
64
|
+
this.advance();
|
|
62
65
|
}
|
|
63
|
-
if (!/[0-9]/.test(this.currentChar)) this.error(
|
|
66
|
+
if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
|
|
64
67
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
65
68
|
result += this.currentChar;
|
|
66
69
|
this.advance();
|
|
@@ -79,8 +82,9 @@ class Lexer {
|
|
|
79
82
|
|
|
80
83
|
const keywords = [
|
|
81
84
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
82
|
-
'break', 'continue', 'func', 'return',
|
|
83
|
-
'
|
|
85
|
+
'break', 'continue', 'func', 'return',
|
|
86
|
+
'true', 'false', 'null', 'undefined',
|
|
87
|
+
'ask', 'define', 'import', 'from', 'as'
|
|
84
88
|
];
|
|
85
89
|
|
|
86
90
|
if (keywords.includes(result)) {
|
|
@@ -98,59 +102,58 @@ class Lexer {
|
|
|
98
102
|
while (this.currentChar && this.currentChar !== quote) {
|
|
99
103
|
if (this.currentChar === '\\') {
|
|
100
104
|
this.advance();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
case 't': result += '\t'; break;
|
|
104
|
-
case '"': result += '"'; break;
|
|
105
|
-
case "'": result += "'"; break;
|
|
106
|
-
case '\\': result += '\\'; break;
|
|
107
|
-
default: result += this.currentChar;
|
|
108
|
-
}
|
|
105
|
+
const map = { n: '\n', t: '\t', '"': '"', "'": "'", '\\': '\\' };
|
|
106
|
+
result += map[this.currentChar] ?? this.currentChar;
|
|
109
107
|
} else {
|
|
110
108
|
result += this.currentChar;
|
|
111
109
|
}
|
|
112
110
|
this.advance();
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
if (this.currentChar !== quote)
|
|
116
|
-
this.error('Unterminated string literal');
|
|
117
|
-
}
|
|
113
|
+
if (this.currentChar !== quote) this.error('Unterminated string literal');
|
|
118
114
|
|
|
119
115
|
this.advance();
|
|
120
116
|
return { type: 'STRING', value: result };
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
|
|
120
|
+
templateString(tokens) {
|
|
121
|
+
this.advance(); // skip opening `
|
|
122
|
+
|
|
123
|
+
let buffer = '';
|
|
124
|
+
|
|
125
|
+
while (this.currentChar !== null) {
|
|
126
|
+
if (this.currentChar === '`') {
|
|
127
|
+
if (buffer.length > 0) {
|
|
128
|
+
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
129
|
+
}
|
|
130
|
+
this.advance();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
127
134
|
if (this.currentChar === '$' && this.peek() === '{') {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
if (buffer.length > 0) {
|
|
136
|
+
tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
|
|
137
|
+
buffer = '';
|
|
138
|
+
}
|
|
139
|
+
this.advance(); // $
|
|
140
|
+
this.advance(); // {
|
|
141
|
+
tokens.push({ type: 'DOLLAR_LBRACE' });
|
|
142
|
+
return;
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
if (this.currentChar === '\\') {
|
|
134
146
|
this.advance();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
case '`': result += '`'; break;
|
|
139
|
-
case '\\': result += '\\'; break;
|
|
140
|
-
default: result += this.currentChar;
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
result += this.currentChar;
|
|
147
|
+
buffer += this.currentChar;
|
|
148
|
+
this.advance();
|
|
149
|
+
continue;
|
|
144
150
|
}
|
|
145
|
-
this.advance();
|
|
146
|
-
}
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
this.
|
|
152
|
+
buffer += this.currentChar;
|
|
153
|
+
this.advance();
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
this.
|
|
153
|
-
return { type: 'TEMPLATE_STRING', value: result };
|
|
156
|
+
this.error('Unterminated template string');
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
getTokens() {
|
|
@@ -162,7 +165,11 @@ class Lexer {
|
|
|
162
165
|
if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
|
|
163
166
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
164
167
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
165
|
-
|
|
168
|
+
|
|
169
|
+
if (this.currentChar === '`') {
|
|
170
|
+
this.templateString(tokens);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
166
173
|
|
|
167
174
|
const char = this.currentChar;
|
|
168
175
|
const next = this.peek();
|
|
@@ -182,20 +189,30 @@ class Lexer {
|
|
|
182
189
|
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
|
|
183
190
|
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
|
|
184
191
|
|
|
185
|
-
const
|
|
186
|
-
if (next === '=' &&
|
|
192
|
+
const compound = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
193
|
+
if (next === '=' && compound[char]) {
|
|
194
|
+
tokens.push({ type: compound[char] });
|
|
195
|
+
this.advance(); this.advance();
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
187
198
|
|
|
188
199
|
const singles = {
|
|
189
200
|
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
190
201
|
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
191
|
-
'(': 'LPAREN', ')': 'RPAREN',
|
|
192
|
-
'
|
|
202
|
+
'(': 'LPAREN', ')': 'RPAREN',
|
|
203
|
+
'{': 'LBRACE', '}': 'RBRACE',
|
|
204
|
+
'[': 'LBRACKET', ']': 'RBRACKET',
|
|
205
|
+
';': 'SEMICOLON', ',': 'COMMA',
|
|
193
206
|
':': 'COLON', '.': 'DOT'
|
|
194
207
|
};
|
|
195
208
|
|
|
196
|
-
if (singles[char]) {
|
|
209
|
+
if (singles[char]) {
|
|
210
|
+
tokens.push({ type: singles[char] });
|
|
211
|
+
this.advance();
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
197
214
|
|
|
198
|
-
this.error(
|
|
215
|
+
this.error(`Unexpected character: ${char}`);
|
|
199
216
|
}
|
|
200
217
|
|
|
201
218
|
tokens.push({ type: 'EOF' });
|
package/src/parser.js
CHANGED
|
@@ -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();
|
|
@@ -371,7 +388,11 @@ 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 }; }
|
|
374
|
-
|
|
391
|
+
if (t.type === 'TEMPLATE_STRING') {
|
|
392
|
+
return this.parseTemplateLiteral();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
375
396
|
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
376
397
|
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
377
398
|
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|