starlight-cli 1.0.20 → 1.0.22
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 +200 -235
- package/package.json +1 -1
- package/src/evaluator.js +109 -71
- package/src/lexer.js +57 -108
- package/src/parser.js +35 -55
- package/src/starlight.js +1 -1
package/package.json
CHANGED
package/src/evaluator.js
CHANGED
|
@@ -4,7 +4,9 @@ const Lexer = require('./lexer');
|
|
|
4
4
|
const Parser = require('./parser');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
-
class ReturnValue {
|
|
7
|
+
class ReturnValue {
|
|
8
|
+
constructor(value) { this.value = value; }
|
|
9
|
+
}
|
|
8
10
|
class BreakSignal {}
|
|
9
11
|
class ContinueSignal {}
|
|
10
12
|
|
|
@@ -47,27 +49,37 @@ class Evaluator {
|
|
|
47
49
|
|
|
48
50
|
setupBuiltins() {
|
|
49
51
|
this.global.define('len', arg => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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;
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
this.global.define('print', arg => { console.log(arg); return null; });
|
|
56
58
|
this.global.define('type', arg => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (Array.isArray(arg)) return 'array';
|
|
60
|
+
return typeof arg;
|
|
59
61
|
});
|
|
60
62
|
this.global.define('keys', arg => arg && typeof arg === 'object' ? Object.keys(arg) : []);
|
|
61
63
|
this.global.define('values', arg => arg && typeof arg === 'object' ? Object.values(arg) : []);
|
|
62
64
|
|
|
63
|
-
this.global.define('ask', prompt =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (Number.isNaN(n)) throw new Error('Cannot convert value to number');
|
|
67
|
-
return n;
|
|
65
|
+
this.global.define('ask', prompt => {
|
|
66
|
+
const readlineSync = require('readline-sync');
|
|
67
|
+
return readlineSync.question(prompt + ' ');
|
|
68
68
|
});
|
|
69
|
-
this.global.define('
|
|
70
|
-
|
|
69
|
+
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
|
+
|
|
71
83
|
|
|
72
84
|
evaluate(node, env = this.global) {
|
|
73
85
|
switch (node.type) {
|
|
@@ -84,7 +96,6 @@ class Evaluator {
|
|
|
84
96
|
case 'LogicalExpression': return this.evalLogical(node, env);
|
|
85
97
|
case 'UnaryExpression': return this.evalUnary(node, env);
|
|
86
98
|
case 'Literal': return node.value;
|
|
87
|
-
case 'TemplateLiteral': return this.evalTemplateLiteral(node, env);
|
|
88
99
|
case 'Identifier': return env.get(node.name);
|
|
89
100
|
case 'IfStatement': return this.evalIf(node, env);
|
|
90
101
|
case 'WhileStatement': return this.evalWhile(node, env);
|
|
@@ -110,65 +121,72 @@ class Evaluator {
|
|
|
110
121
|
|
|
111
122
|
evalProgram(node, env) {
|
|
112
123
|
let result = null;
|
|
113
|
-
for (const stmt of node.body)
|
|
124
|
+
for (const stmt of node.body) {
|
|
125
|
+
result = this.evaluate(stmt, env);
|
|
126
|
+
}
|
|
114
127
|
return result;
|
|
115
128
|
}
|
|
129
|
+
evalImport(node, env) {
|
|
130
|
+
const spec = node.path;
|
|
131
|
+
let lib;
|
|
116
132
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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}`);
|
|
126
145
|
}
|
|
127
|
-
}
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
146
|
|
|
147
|
+
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
148
|
+
const tokens = new Lexer(code).getTokens();
|
|
149
|
+
const ast = new Parser(tokens).parse();
|
|
150
|
+
|
|
151
|
+
const moduleEnv = new Environment(env);
|
|
152
|
+
this.evaluate(ast, moduleEnv);
|
|
131
153
|
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
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;
|
|
154
|
+
lib = {};
|
|
155
|
+
for (const key of Object.keys(moduleEnv.store)) {
|
|
156
|
+
lib[key] = moduleEnv.store[key];
|
|
154
157
|
}
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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}'`);
|
|
162
172
|
}
|
|
173
|
+
env.define(imp.local, lib[imp.imported]);
|
|
163
174
|
}
|
|
164
|
-
return null;
|
|
165
175
|
}
|
|
166
176
|
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
167
181
|
evalBlock(node, env) {
|
|
168
182
|
let result = null;
|
|
169
183
|
for (const stmt of node.body) {
|
|
170
|
-
try {
|
|
171
|
-
|
|
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
|
+
}
|
|
172
190
|
}
|
|
173
191
|
return result;
|
|
174
192
|
}
|
|
@@ -181,15 +199,27 @@ class Evaluator {
|
|
|
181
199
|
evalAssignment(node, env) {
|
|
182
200
|
const rightVal = this.evaluate(node.right, env);
|
|
183
201
|
const left = node.left;
|
|
202
|
+
|
|
184
203
|
if (left.type === 'Identifier') return env.set(left.name, rightVal);
|
|
185
|
-
if (left.type === 'MemberExpression') {
|
|
186
|
-
|
|
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
|
+
|
|
187
216
|
throw new Error('Invalid assignment target');
|
|
188
217
|
}
|
|
189
218
|
|
|
190
219
|
evalCompoundAssignment(node, env) {
|
|
191
220
|
const left = node.left;
|
|
192
221
|
let current;
|
|
222
|
+
|
|
193
223
|
if (left.type === 'Identifier') current = env.get(left.name);
|
|
194
224
|
else if (left.type === 'MemberExpression') current = this.evalMember(left, env);
|
|
195
225
|
else if (left.type === 'IndexExpression') current = this.evalIndex(left, env);
|
|
@@ -207,7 +237,9 @@ class Evaluator {
|
|
|
207
237
|
}
|
|
208
238
|
|
|
209
239
|
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);
|
|
210
241
|
else this.evalAssignment({ left, right: { type: 'Literal', value: computed }, type: 'AssignmentExpression' }, env);
|
|
242
|
+
|
|
211
243
|
return computed;
|
|
212
244
|
}
|
|
213
245
|
|
|
@@ -219,7 +251,8 @@ class Evaluator {
|
|
|
219
251
|
|
|
220
252
|
evalAsk(node, env) {
|
|
221
253
|
const prompt = this.evaluate(node.prompt, env);
|
|
222
|
-
|
|
254
|
+
const input = readlineSync.question(prompt + ' ');
|
|
255
|
+
return input;
|
|
223
256
|
}
|
|
224
257
|
|
|
225
258
|
evalDefine(node, env) {
|
|
@@ -236,16 +269,12 @@ class Evaluator {
|
|
|
236
269
|
case 'STAR': return l * r;
|
|
237
270
|
case 'SLASH': return l / r;
|
|
238
271
|
case 'MOD': return l % r;
|
|
239
|
-
case 'EQEQ': return l
|
|
240
|
-
case 'NOTEQ': return l
|
|
241
|
-
case 'STRICT_EQ': return l === r;
|
|
242
|
-
case 'STRICT_NOTEQ': return l !== r;
|
|
272
|
+
case 'EQEQ': return l === r;
|
|
273
|
+
case 'NOTEQ': return l !== r;
|
|
243
274
|
case 'LT': return l < r;
|
|
244
275
|
case 'LTE': return l <= r;
|
|
245
276
|
case 'GT': return l > r;
|
|
246
277
|
case 'GTE': return l >= r;
|
|
247
|
-
case 'LSHIFT': return l << r;
|
|
248
|
-
case 'RSHIFT': return l >> r;
|
|
249
278
|
default: throw new Error(`Unknown binary operator ${node.operator}`);
|
|
250
279
|
}
|
|
251
280
|
}
|
|
@@ -313,7 +342,9 @@ class Evaluator {
|
|
|
313
342
|
const args = node.arguments.map(a => this.evaluate(a, env));
|
|
314
343
|
return calleeEvaluated(...args);
|
|
315
344
|
}
|
|
316
|
-
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body)
|
|
345
|
+
if (!calleeEvaluated || typeof calleeEvaluated !== 'object' || !calleeEvaluated.body) {
|
|
346
|
+
throw new Error('Call to non-function');
|
|
347
|
+
}
|
|
317
348
|
const fn = calleeEvaluated;
|
|
318
349
|
const callEnv = new Environment(fn.env);
|
|
319
350
|
fn.params.forEach((p, i) => {
|
|
@@ -353,8 +384,15 @@ class Evaluator {
|
|
|
353
384
|
};
|
|
354
385
|
const setValue = (v) => {
|
|
355
386
|
if (arg.type === 'Identifier') env.set(arg.name, v);
|
|
356
|
-
else if (arg.type === 'MemberExpression') {
|
|
357
|
-
|
|
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
|
+
}
|
|
358
396
|
};
|
|
359
397
|
const current = getCurrent();
|
|
360
398
|
const newVal = (node.operator === 'PLUSPLUS') ? current + 1 : current - 1;
|
|
@@ -363,4 +401,4 @@ class Evaluator {
|
|
|
363
401
|
}
|
|
364
402
|
}
|
|
365
403
|
|
|
366
|
-
module.exports = Evaluator;
|
|
404
|
+
module.exports = Evaluator;
|
package/src/lexer.js
CHANGED
|
@@ -3,19 +3,27 @@ class Lexer {
|
|
|
3
3
|
this.input = input;
|
|
4
4
|
this.pos = 0;
|
|
5
5
|
this.currentChar = input[0] || null;
|
|
6
|
+
this.line = 1; // current line
|
|
7
|
+
this.column = 1; // current column
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
advance() {
|
|
11
|
+
if (this.currentChar === '\n') {
|
|
12
|
+
this.line++;
|
|
13
|
+
this.column = 0;
|
|
14
|
+
} else {
|
|
15
|
+
this.column++;
|
|
16
|
+
}
|
|
9
17
|
this.pos++;
|
|
10
18
|
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
|
-
peek(
|
|
14
|
-
return this.pos +
|
|
21
|
+
peek() {
|
|
22
|
+
return this.pos + 1 < this.input.length ? this.input[this.pos + 1] : null;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
error(msg) {
|
|
18
|
-
throw new Error(
|
|
26
|
+
throw new Error(`${msg} at line ${this.line}, column ${this.column}`);
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
skipWhitespace() {
|
|
@@ -25,7 +33,7 @@ class Lexer {
|
|
|
25
33
|
skipComment() {
|
|
26
34
|
if (this.currentChar === '#') {
|
|
27
35
|
if (this.peek() === '*') {
|
|
28
|
-
this.advance(); this.advance(); // #*
|
|
36
|
+
this.advance(); this.advance(); // skip #*
|
|
29
37
|
while (this.currentChar !== null) {
|
|
30
38
|
if (this.currentChar === '*' && this.peek() === '#') {
|
|
31
39
|
this.advance(); this.advance();
|
|
@@ -33,7 +41,7 @@ class Lexer {
|
|
|
33
41
|
}
|
|
34
42
|
this.advance();
|
|
35
43
|
}
|
|
36
|
-
this.error(
|
|
44
|
+
this.error("Unterminated multi-line comment (#* ... *#)");
|
|
37
45
|
} else {
|
|
38
46
|
while (this.currentChar && this.currentChar !== '\n') this.advance();
|
|
39
47
|
}
|
|
@@ -41,6 +49,8 @@ class Lexer {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
number() {
|
|
52
|
+
const startLine = this.line;
|
|
53
|
+
const startCol = this.column;
|
|
44
54
|
let result = '';
|
|
45
55
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
46
56
|
result += this.currentChar;
|
|
@@ -48,32 +58,20 @@ class Lexer {
|
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
|
|
51
|
-
result += '.';
|
|
52
|
-
this.advance();
|
|
61
|
+
result += '.'; this.advance();
|
|
53
62
|
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
54
63
|
result += this.currentChar;
|
|
55
64
|
this.advance();
|
|
56
65
|
}
|
|
66
|
+
return { type: 'NUMBER', value: parseFloat(result), line: startLine, column: startCol };
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
result += this.currentChar;
|
|
61
|
-
this.advance();
|
|
62
|
-
if (this.currentChar === '+' || this.currentChar === '-') {
|
|
63
|
-
result += this.currentChar;
|
|
64
|
-
this.advance();
|
|
65
|
-
}
|
|
66
|
-
if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
|
|
67
|
-
while (this.currentChar && /[0-9]/.test(this.currentChar)) {
|
|
68
|
-
result += this.currentChar;
|
|
69
|
-
this.advance();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { type: 'NUMBER', value: parseFloat(result) };
|
|
69
|
+
return { type: 'NUMBER', value: parseInt(result), line: startLine, column: startCol };
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
identifier() {
|
|
73
|
+
const startLine = this.line;
|
|
74
|
+
const startCol = this.column;
|
|
77
75
|
let result = '';
|
|
78
76
|
while (this.currentChar && /[A-Za-z0-9_]/.test(this.currentChar)) {
|
|
79
77
|
result += this.currentChar;
|
|
@@ -82,19 +80,20 @@ class Lexer {
|
|
|
82
80
|
|
|
83
81
|
const keywords = [
|
|
84
82
|
'let', 'sldeploy', 'if', 'else', 'while', 'for',
|
|
85
|
-
'break', 'continue', 'func', 'return',
|
|
86
|
-
'true', 'false', 'null', 'undefined',
|
|
83
|
+
'break', 'continue', 'func', 'return', 'true', 'false', 'null',
|
|
87
84
|
'ask', 'define', 'import', 'from', 'as'
|
|
88
85
|
];
|
|
89
86
|
|
|
90
87
|
if (keywords.includes(result)) {
|
|
91
|
-
return { type: result.toUpperCase(), value: result };
|
|
88
|
+
return { type: result.toUpperCase(), value: result, line: startLine, column: startCol };
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
return { type: 'IDENTIFIER', value: result };
|
|
91
|
+
return { type: 'IDENTIFIER', value: result, line: startLine, column: startCol };
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
string() {
|
|
95
|
+
const startLine = this.line;
|
|
96
|
+
const startCol = this.column;
|
|
98
97
|
const quote = this.currentChar;
|
|
99
98
|
this.advance();
|
|
100
99
|
let result = '';
|
|
@@ -102,58 +101,26 @@ class Lexer {
|
|
|
102
101
|
while (this.currentChar && this.currentChar !== quote) {
|
|
103
102
|
if (this.currentChar === '\\') {
|
|
104
103
|
this.advance();
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
switch (this.currentChar) {
|
|
105
|
+
case 'n': result += '\n'; break;
|
|
106
|
+
case 't': result += '\t'; break;
|
|
107
|
+
case '"': result += '"'; break;
|
|
108
|
+
case "'": result += "'"; break;
|
|
109
|
+
case '\\': result += '\\'; break;
|
|
110
|
+
default: result += this.currentChar;
|
|
111
|
+
}
|
|
107
112
|
} else {
|
|
108
113
|
result += this.currentChar;
|
|
109
114
|
}
|
|
110
115
|
this.advance();
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
if (this.currentChar !== quote)
|
|
114
|
-
|
|
115
|
-
this.advance();
|
|
116
|
-
return { type: 'STRING', value: result };
|
|
117
|
-
}
|
|
118
|
-
|
|
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
|
-
|
|
134
|
-
if (this.currentChar === '$' && this.peek() === '{') {
|
|
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;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (this.currentChar === '\\') {
|
|
146
|
-
this.advance();
|
|
147
|
-
buffer += this.currentChar;
|
|
148
|
-
this.advance();
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
buffer += this.currentChar;
|
|
153
|
-
this.advance();
|
|
118
|
+
if (this.currentChar !== quote) {
|
|
119
|
+
this.error('Unterminated string literal');
|
|
154
120
|
}
|
|
155
121
|
|
|
156
|
-
this.
|
|
122
|
+
this.advance();
|
|
123
|
+
return { type: 'STRING', value: result, line: startLine, column: startCol };
|
|
157
124
|
}
|
|
158
125
|
|
|
159
126
|
getTokens() {
|
|
@@ -166,56 +133,38 @@ class Lexer {
|
|
|
166
133
|
if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
|
|
167
134
|
if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
|
|
168
135
|
|
|
169
|
-
if (this.currentChar === '`') {
|
|
170
|
-
this.templateString(tokens);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
136
|
const char = this.currentChar;
|
|
175
137
|
const next = this.peek();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (char === '
|
|
180
|
-
if (char === '=' && next === '
|
|
181
|
-
if (char === '
|
|
182
|
-
if (char === '
|
|
183
|
-
if (char === '
|
|
184
|
-
if (char === '
|
|
185
|
-
if (char === '
|
|
186
|
-
if (char === '
|
|
187
|
-
if (char === '
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
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
|
-
}
|
|
138
|
+
const startLine = this.line;
|
|
139
|
+
const startCol = this.column;
|
|
140
|
+
|
|
141
|
+
if (char === '=' && next === '=') { tokens.push({ type: 'EQEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
142
|
+
if (char === '=' && next === '>') { tokens.push({ type: 'ARROW', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
143
|
+
if (char === '!' && next === '=') { tokens.push({ type: 'NOTEQ', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
144
|
+
if (char === '<' && next === '=') { tokens.push({ type: 'LTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
145
|
+
if (char === '>' && next === '=') { tokens.push({ type: 'GTE', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
146
|
+
if (char === '&' && next === '&') { tokens.push({ type: 'AND', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
147
|
+
if (char === '|' && next === '|') { tokens.push({ type: 'OR', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
148
|
+
if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
149
|
+
if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS', line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
150
|
+
|
|
151
|
+
const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
|
|
152
|
+
if (next === '=' && map[char]) { tokens.push({ type: map[char], line: startLine, column: startCol }); this.advance(); this.advance(); continue; }
|
|
198
153
|
|
|
199
154
|
const singles = {
|
|
200
155
|
'+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
|
|
201
156
|
'=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
|
|
202
|
-
'(': 'LPAREN', ')': 'RPAREN',
|
|
203
|
-
'
|
|
204
|
-
'[': 'LBRACKET', ']': 'RBRACKET',
|
|
205
|
-
';': 'SEMICOLON', ',': 'COMMA',
|
|
157
|
+
'(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
|
|
158
|
+
';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
|
|
206
159
|
':': 'COLON', '.': 'DOT'
|
|
207
160
|
};
|
|
208
161
|
|
|
209
|
-
if (singles[char]) {
|
|
210
|
-
tokens.push({ type: singles[char] });
|
|
211
|
-
this.advance();
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
162
|
+
if (singles[char]) { tokens.push({ type: singles[char], line: startLine, column: startCol }); this.advance(); continue; }
|
|
214
163
|
|
|
215
|
-
this.error(
|
|
164
|
+
this.error("Unexpected character: " + char);
|
|
216
165
|
}
|
|
217
166
|
|
|
218
|
-
tokens.push({ type: 'EOF' });
|
|
167
|
+
tokens.push({ type: 'EOF', line: this.line, column: this.column });
|
|
219
168
|
return tokens;
|
|
220
169
|
}
|
|
221
170
|
}
|