starlight-cli 1.0.3 → 1.0.5
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 +2556 -0
- package/dist/read.cs.js +123 -0
- package/dist/read.ps1 +128 -0
- package/dist/read.sh +137 -0
- package/index.js +1 -5
- package/package.json +10 -3
- package/src/evaluator.js +400 -0
- package/src/lexer.js +157 -0
- package/src/parser.js +441 -0
- package/src/starlight.js +117 -0
- package/install.js +0 -64
package/src/parser.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
class Parser {
|
|
2
|
+
constructor(tokens) {
|
|
3
|
+
this.tokens = tokens;
|
|
4
|
+
this.pos = 0;
|
|
5
|
+
this.current = this.tokens[this.pos];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
advance() {
|
|
9
|
+
this.pos++;
|
|
10
|
+
this.current = this.pos < this.tokens.length ? this.tokens[this.pos] : { type: 'EOF' };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
eat(type) {
|
|
14
|
+
if (this.current.type === type) this.advance();
|
|
15
|
+
else throw new Error(`Expected ${type}, got ${this.current.type}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
peekType(offset = 1) {
|
|
19
|
+
return (this.tokens[this.pos + offset] || { type: 'EOF' }).type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
parse() {
|
|
23
|
+
const body = [];
|
|
24
|
+
while (this.current.type !== 'EOF') {
|
|
25
|
+
body.push(this.statement());
|
|
26
|
+
}
|
|
27
|
+
return { type: 'Program', body };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
statement() {
|
|
31
|
+
switch (this.current.type) {
|
|
32
|
+
case 'LET': return this.varDeclaration();
|
|
33
|
+
case 'SLDEPLOY': return this.sldeployStatement();
|
|
34
|
+
case 'DEFINE': return this.defineStatement();
|
|
35
|
+
case 'IF': return this.ifStatement();
|
|
36
|
+
case 'WHILE': return this.whileStatement();
|
|
37
|
+
case 'FOR': return this.forStatement();
|
|
38
|
+
case 'BREAK': return this.breakStatement();
|
|
39
|
+
case 'CONTINUE': return this.continueStatement();
|
|
40
|
+
case 'FUNC': return this.funcDeclaration();
|
|
41
|
+
case 'RETURN': return this.returnStatement();
|
|
42
|
+
case 'IMPORT': return this.importStatement();
|
|
43
|
+
case 'LBRACE': return this.block();
|
|
44
|
+
default:
|
|
45
|
+
return this.expressionStatement();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
varDeclaration() {
|
|
50
|
+
this.eat('LET');
|
|
51
|
+
const id = this.current.value;
|
|
52
|
+
this.eat('IDENTIFIER');
|
|
53
|
+
this.eat('EQUAL');
|
|
54
|
+
const expr = this.expression();
|
|
55
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
56
|
+
return { type: 'VarDeclaration', id, expr };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
sldeployStatement() {
|
|
60
|
+
this.eat('SLDEPLOY');
|
|
61
|
+
const expr = this.expression();
|
|
62
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
63
|
+
return { type: 'SldeployStatement', expr };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
defineStatement() {
|
|
67
|
+
this.eat('DEFINE');
|
|
68
|
+
const id = this.current.value;
|
|
69
|
+
this.eat('IDENTIFIER');
|
|
70
|
+
let expr = null;
|
|
71
|
+
if (this.current.type === 'EQUAL') {
|
|
72
|
+
this.eat('EQUAL');
|
|
73
|
+
expr = this.expression();
|
|
74
|
+
}
|
|
75
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
76
|
+
return { type: 'DefineStatement', id, expr };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ifStatement() {
|
|
80
|
+
this.eat('IF');
|
|
81
|
+
this.eat('LPAREN');
|
|
82
|
+
const test = this.expression();
|
|
83
|
+
this.eat('RPAREN');
|
|
84
|
+
const consequent = this.block();
|
|
85
|
+
let alternate = null;
|
|
86
|
+
if (this.current.type === 'ELSE') {
|
|
87
|
+
this.eat('ELSE');
|
|
88
|
+
if (this.current.type === 'IF') alternate = this.ifStatement();
|
|
89
|
+
else alternate = this.block();
|
|
90
|
+
}
|
|
91
|
+
return { type: 'IfStatement', test, consequent, alternate };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
whileStatement() {
|
|
95
|
+
this.eat('WHILE');
|
|
96
|
+
this.eat('LPAREN');
|
|
97
|
+
const test = this.expression();
|
|
98
|
+
this.eat('RPAREN');
|
|
99
|
+
const body = this.block();
|
|
100
|
+
return { type: 'WhileStatement', test, body };
|
|
101
|
+
}
|
|
102
|
+
importStatement() {
|
|
103
|
+
this.eat('IMPORT');
|
|
104
|
+
|
|
105
|
+
let specifiers = [];
|
|
106
|
+
|
|
107
|
+
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 });
|
|
113
|
+
}
|
|
114
|
+
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');
|
|
127
|
+
}
|
|
128
|
+
this.eat('RBRACE');
|
|
129
|
+
}
|
|
130
|
+
else if (this.current.type === 'IDENTIFIER') {
|
|
131
|
+
const localName = this.current.value;
|
|
132
|
+
this.eat('IDENTIFIER');
|
|
133
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
134
|
+
} else {
|
|
135
|
+
throw new Error('Unexpected token in import statement');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.eat('FROM');
|
|
139
|
+
const pathToken = this.current;
|
|
140
|
+
if (pathToken.type !== 'STRING') throw new Error('Expected string literal after from in import');
|
|
141
|
+
this.eat('STRING');
|
|
142
|
+
|
|
143
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
144
|
+
|
|
145
|
+
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
forStatement() {
|
|
150
|
+
this.eat('FOR');
|
|
151
|
+
this.eat('LPAREN');
|
|
152
|
+
|
|
153
|
+
let init = null;
|
|
154
|
+
if (this.current.type !== 'SEMICOLON') {
|
|
155
|
+
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
156
|
+
} else {
|
|
157
|
+
this.eat('SEMICOLON');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let test = null;
|
|
161
|
+
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
162
|
+
this.eat('SEMICOLON');
|
|
163
|
+
|
|
164
|
+
let update = null;
|
|
165
|
+
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
166
|
+
this.eat('RPAREN');
|
|
167
|
+
|
|
168
|
+
const body = this.block();
|
|
169
|
+
return { type: 'ForStatement', init, test, update, body };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
breakStatement() {
|
|
173
|
+
this.eat('BREAK');
|
|
174
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
175
|
+
return { type: 'BreakStatement' };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
continueStatement() {
|
|
179
|
+
this.eat('CONTINUE');
|
|
180
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
181
|
+
return { type: 'ContinueStatement' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
funcDeclaration() {
|
|
185
|
+
this.eat('FUNC');
|
|
186
|
+
const name = this.current.value;
|
|
187
|
+
this.eat('IDENTIFIER');
|
|
188
|
+
this.eat('LPAREN');
|
|
189
|
+
const params = [];
|
|
190
|
+
if (this.current.type !== 'RPAREN') {
|
|
191
|
+
params.push(this.current.value);
|
|
192
|
+
this.eat('IDENTIFIER');
|
|
193
|
+
while (this.current.type === 'COMMA') {
|
|
194
|
+
this.eat('COMMA');
|
|
195
|
+
params.push(this.current.value);
|
|
196
|
+
this.eat('IDENTIFIER');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.eat('RPAREN');
|
|
200
|
+
const body = this.block();
|
|
201
|
+
return { type: 'FunctionDeclaration', name, params, body };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
returnStatement() {
|
|
205
|
+
this.eat('RETURN');
|
|
206
|
+
let argument = null;
|
|
207
|
+
if (this.current.type !== 'SEMICOLON') argument = this.expression();
|
|
208
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
209
|
+
return { type: 'ReturnStatement', argument };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
block() {
|
|
213
|
+
this.eat('LBRACE');
|
|
214
|
+
const body = [];
|
|
215
|
+
while (this.current.type !== 'RBRACE') {
|
|
216
|
+
body.push(this.statement());
|
|
217
|
+
}
|
|
218
|
+
this.eat('RBRACE');
|
|
219
|
+
return { type: 'BlockStatement', body };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
expressionStatement() {
|
|
223
|
+
const expr = this.expression();
|
|
224
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
225
|
+
return { type: 'ExpressionStatement', expression: expr };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
expression() {
|
|
229
|
+
return this.assignment();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
assignment() {
|
|
233
|
+
const node = this.logicalOr();
|
|
234
|
+
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
235
|
+
if (compoundOps.includes(this.current.type)) {
|
|
236
|
+
const op = this.current.type;
|
|
237
|
+
this.eat(op);
|
|
238
|
+
const right = this.assignment();
|
|
239
|
+
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this.current.type === 'EQUAL') {
|
|
243
|
+
this.eat('EQUAL');
|
|
244
|
+
const right = this.assignment();
|
|
245
|
+
return { type: 'AssignmentExpression', left: node, right };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return node;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logicalOr() {
|
|
252
|
+
let node = this.logicalAnd();
|
|
253
|
+
while (this.current.type === 'OR') {
|
|
254
|
+
const op = this.current.type;
|
|
255
|
+
this.eat(op);
|
|
256
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
|
|
257
|
+
}
|
|
258
|
+
return node;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
logicalAnd() {
|
|
262
|
+
let node = this.equality();
|
|
263
|
+
while (this.current.type === 'AND') {
|
|
264
|
+
const op = this.current.type;
|
|
265
|
+
this.eat(op);
|
|
266
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
|
|
267
|
+
}
|
|
268
|
+
return node;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
equality() {
|
|
272
|
+
let node = this.comparison();
|
|
273
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
274
|
+
const op = this.current.type;
|
|
275
|
+
this.eat(op);
|
|
276
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
277
|
+
}
|
|
278
|
+
return node;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
comparison() {
|
|
282
|
+
let node = this.term();
|
|
283
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
284
|
+
const op = this.current.type;
|
|
285
|
+
this.eat(op);
|
|
286
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
287
|
+
}
|
|
288
|
+
return node;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
term() {
|
|
292
|
+
let node = this.factor();
|
|
293
|
+
while (['PLUS', 'MINUS'].includes(this.current.type)) {
|
|
294
|
+
const op = this.current.type;
|
|
295
|
+
this.eat(op);
|
|
296
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
|
|
297
|
+
}
|
|
298
|
+
return node;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
factor() {
|
|
302
|
+
let node = this.unary();
|
|
303
|
+
while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
|
|
304
|
+
const op = this.current.type;
|
|
305
|
+
this.eat(op);
|
|
306
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
|
|
307
|
+
}
|
|
308
|
+
return node;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
unary() {
|
|
312
|
+
if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
|
|
313
|
+
const op = this.current.type;
|
|
314
|
+
this.eat(op);
|
|
315
|
+
return { type: 'UnaryExpression', operator: op, argument: this.unary() };
|
|
316
|
+
}
|
|
317
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
318
|
+
const op = this.current.type;
|
|
319
|
+
this.eat(op);
|
|
320
|
+
const argument = this.unary();
|
|
321
|
+
return { type: 'UpdateExpression', operator: op, argument, prefix: true };
|
|
322
|
+
}
|
|
323
|
+
return this.postfix();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
postfix() {
|
|
327
|
+
let node = this.primary();
|
|
328
|
+
while (true) {
|
|
329
|
+
if (this.current.type === 'LBRACKET') {
|
|
330
|
+
this.eat('LBRACKET');
|
|
331
|
+
const index = this.expression();
|
|
332
|
+
this.eat('RBRACKET');
|
|
333
|
+
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (this.current.type === 'LPAREN') {
|
|
337
|
+
this.eat('LPAREN');
|
|
338
|
+
const args = [];
|
|
339
|
+
if (this.current.type !== 'RPAREN') {
|
|
340
|
+
args.push(this.expression());
|
|
341
|
+
while (this.current.type === 'COMMA') {
|
|
342
|
+
this.eat('COMMA');
|
|
343
|
+
args.push(this.expression());
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
this.eat('RPAREN');
|
|
347
|
+
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (this.current.type === 'DOT') {
|
|
351
|
+
this.eat('DOT');
|
|
352
|
+
if (this.current.type !== 'IDENTIFIER') throw new Error('Expected property name after dot');
|
|
353
|
+
const property = this.current.value;
|
|
354
|
+
this.eat('IDENTIFIER');
|
|
355
|
+
node = { type: 'MemberExpression', object: node, property };
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
359
|
+
const op = this.current.type;
|
|
360
|
+
this.eat(op);
|
|
361
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
return node;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
primary() {
|
|
370
|
+
const t = this.current;
|
|
371
|
+
|
|
372
|
+
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
373
|
+
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
374
|
+
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
375
|
+
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
376
|
+
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
377
|
+
|
|
378
|
+
if (t.type === 'ASK') {
|
|
379
|
+
this.eat('ASK');
|
|
380
|
+
this.eat('LPAREN');
|
|
381
|
+
const args = [];
|
|
382
|
+
if (this.current.type !== 'RPAREN') {
|
|
383
|
+
args.push(this.expression());
|
|
384
|
+
while (this.current.type === 'COMMA') {
|
|
385
|
+
this.eat('COMMA');
|
|
386
|
+
args.push(this.expression());
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.eat('RPAREN');
|
|
390
|
+
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (t.type === 'IDENTIFIER') {
|
|
394
|
+
const name = t.value;
|
|
395
|
+
this.eat('IDENTIFIER');
|
|
396
|
+
return { type: 'Identifier', name };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (t.type === 'LPAREN') {
|
|
400
|
+
this.eat('LPAREN');
|
|
401
|
+
const expr = this.expression();
|
|
402
|
+
this.eat('RPAREN');
|
|
403
|
+
return expr;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (t.type === 'LBRACKET') {
|
|
407
|
+
this.eat('LBRACKET');
|
|
408
|
+
const elements = [];
|
|
409
|
+
if (this.current.type !== 'RBRACKET') {
|
|
410
|
+
elements.push(this.expression());
|
|
411
|
+
while (this.current.type === 'COMMA') {
|
|
412
|
+
this.eat('COMMA');
|
|
413
|
+
elements.push(this.expression());
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
this.eat('RBRACKET');
|
|
417
|
+
return { type: 'ArrayExpression', elements };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (t.type === 'LBRACE') {
|
|
421
|
+
this.eat('LBRACE');
|
|
422
|
+
const props = [];
|
|
423
|
+
while (this.current.type !== 'RBRACE') {
|
|
424
|
+
let key;
|
|
425
|
+
if (this.current.type === 'STRING') { key = this.current.value; this.eat('STRING'); }
|
|
426
|
+
else if (this.current.type === 'IDENTIFIER') { key = this.current.value; this.eat('IDENTIFIER'); }
|
|
427
|
+
else throw new Error('Invalid object key');
|
|
428
|
+
this.eat('COLON');
|
|
429
|
+
const value = this.expression();
|
|
430
|
+
props.push({ key, value });
|
|
431
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
432
|
+
}
|
|
433
|
+
this.eat('RBRACE');
|
|
434
|
+
return { type: 'ObjectExpression', props };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
throw new Error(`Unexpected token in primary: ${t.type}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = Parser;
|
package/src/starlight.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const Lexer = require('./lexer');
|
|
5
|
+
const Parser = require('./parser');
|
|
6
|
+
const Evaluator = require('./evaluator');
|
|
7
|
+
|
|
8
|
+
const COLOR = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
red: '\x1b[31m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
blue: '\x1b[34m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
gray: '\x1b[90m',
|
|
16
|
+
green: '\x1b[32m'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function waitAndExit(code = 1) {
|
|
20
|
+
console.error(COLOR.gray + '\nPress any key to exit...' + COLOR.reset);
|
|
21
|
+
process.stdin.setRawMode(true);
|
|
22
|
+
process.stdin.resume();
|
|
23
|
+
process.stdin.once('data', () => process.exit(code));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fatal(msg) {
|
|
27
|
+
console.error(COLOR.red + msg + COLOR.reset);
|
|
28
|
+
return waitAndExit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function printSourceContext(code, line, column) {
|
|
32
|
+
const lines = code.split('\n');
|
|
33
|
+
const srcLine = lines[line - 1];
|
|
34
|
+
if (!srcLine) return;
|
|
35
|
+
|
|
36
|
+
console.error(
|
|
37
|
+
COLOR.gray + `\n ${line} | ` + COLOR.reset + srcLine
|
|
38
|
+
);
|
|
39
|
+
console.error(
|
|
40
|
+
COLOR.gray + ' | ' +
|
|
41
|
+
COLOR.red + ' '.repeat(Math.max(0, column - 1)) + '^' +
|
|
42
|
+
COLOR.reset
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const args = process.argv.slice(2);
|
|
47
|
+
|
|
48
|
+
if (args.length === 0) {
|
|
49
|
+
return fatal('Usage: starlight <file.sl>');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const filePath = path.resolve(args[0]);
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(filePath)) {
|
|
55
|
+
return fatal(`File not found: ${filePath}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let code;
|
|
59
|
+
try {
|
|
60
|
+
code = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return fatal(`Failed to read file: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let tokens;
|
|
66
|
+
try {
|
|
67
|
+
const lexer = new Lexer(code);
|
|
68
|
+
tokens = lexer.getTokens();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Lexer Error' + COLOR.reset);
|
|
71
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
72
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
73
|
+
|
|
74
|
+
if (err.line && err.column) {
|
|
75
|
+
printSourceContext(code, err.line, err.column);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return waitAndExit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let ast;
|
|
82
|
+
try {
|
|
83
|
+
const parser = new Parser(tokens);
|
|
84
|
+
ast = parser.parse();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Parser Error' + COLOR.reset);
|
|
87
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
88
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
89
|
+
|
|
90
|
+
if (err.token && err.token.line && err.token.column) {
|
|
91
|
+
printSourceContext(code, err.token.line, err.token.column);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return waitAndExit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const evaluator = new Evaluator();
|
|
99
|
+
evaluator.evaluate(ast);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error(COLOR.red + COLOR.bold + 'Starlight Runtime Error' + COLOR.reset);
|
|
102
|
+
console.error(COLOR.cyan + `File: ${filePath}` + COLOR.reset);
|
|
103
|
+
console.error(COLOR.yellow + `Message: ${err.message}` + COLOR.reset);
|
|
104
|
+
|
|
105
|
+
if (err.line && err.column) {
|
|
106
|
+
printSourceContext(code, err.line, err.column);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return waitAndExit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(
|
|
113
|
+
COLOR.green +
|
|
114
|
+
'\nProgram finished successfully.' +
|
|
115
|
+
COLOR.reset
|
|
116
|
+
);
|
|
117
|
+
waitAndExit(0);
|
package/install.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const https = require('https');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
const DOWNLOAD_URL =
|
|
8
|
-
'https://github.com/developerdominex/dominexmacedon/releases/download/programming/starlight.exe';
|
|
9
|
-
|
|
10
|
-
const INSTALL_DIR = 'C:\\Starlight\\bin';
|
|
11
|
-
const EXE_PATH = path.join(INSTALL_DIR, 'starlight.exe');
|
|
12
|
-
|
|
13
|
-
// Ensure install directory exists
|
|
14
|
-
fs.mkdirSync(INSTALL_DIR, { recursive: true });
|
|
15
|
-
|
|
16
|
-
console.log('Downloading Starlight...');
|
|
17
|
-
|
|
18
|
-
function download(url, dest) {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
https.get(url, res => {
|
|
21
|
-
if ([301, 302].includes(res.statusCode)) {
|
|
22
|
-
return download(res.headers.location, dest).then(resolve).catch(reject);
|
|
23
|
-
}
|
|
24
|
-
if (res.statusCode !== 200) {
|
|
25
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const file = fs.createWriteStream(dest);
|
|
30
|
-
res.pipe(file);
|
|
31
|
-
|
|
32
|
-
file.on('finish', () => file.close(resolve));
|
|
33
|
-
file.on('error', err => {
|
|
34
|
-
fs.unlinkSync(dest);
|
|
35
|
-
reject(err);
|
|
36
|
-
});
|
|
37
|
-
}).on('error', reject);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
(async () => {
|
|
42
|
-
try {
|
|
43
|
-
await download(DOWNLOAD_URL, EXE_PATH);
|
|
44
|
-
console.log(`Downloaded to: ${EXE_PATH}`);
|
|
45
|
-
|
|
46
|
-
// Add to PATH permanently
|
|
47
|
-
try {
|
|
48
|
-
execSync(`setx PATH "%PATH%;${INSTALL_DIR}"`, { stdio: 'ignore' });
|
|
49
|
-
console.log('PATH updated for future sessions.');
|
|
50
|
-
} catch {
|
|
51
|
-
console.warn('Could not update PATH permanently (may already exist or too long).');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Add to PATH for current session
|
|
55
|
-
process.env.PATH = `${INSTALL_DIR};${process.env.PATH}`;
|
|
56
|
-
console.log('PATH updated for current session.');
|
|
57
|
-
|
|
58
|
-
console.log('Starlight installation completed successfully!');
|
|
59
|
-
process.exit(0);
|
|
60
|
-
} catch (err) {
|
|
61
|
-
console.error('Download or installation failed:', err.message);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
})();
|