littlewing 0.1.0
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/LICENSE +21 -0
- package/README.md +414 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +673 -0
- package/package.json +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/ast.ts
|
|
14
|
+
var exports_ast = {};
|
|
15
|
+
__export(exports_ast, {
|
|
16
|
+
unaryOp: () => unaryOp,
|
|
17
|
+
subtract: () => subtract,
|
|
18
|
+
number: () => number,
|
|
19
|
+
negate: () => negate,
|
|
20
|
+
multiply: () => multiply,
|
|
21
|
+
modulo: () => modulo,
|
|
22
|
+
identifier: () => identifier,
|
|
23
|
+
functionCall: () => functionCall,
|
|
24
|
+
exponentiate: () => exponentiate,
|
|
25
|
+
divide: () => divide,
|
|
26
|
+
binaryOp: () => binaryOp,
|
|
27
|
+
assign: () => assign,
|
|
28
|
+
add: () => add
|
|
29
|
+
});
|
|
30
|
+
function number(value) {
|
|
31
|
+
return {
|
|
32
|
+
type: "NumberLiteral",
|
|
33
|
+
value
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function identifier(name) {
|
|
37
|
+
return {
|
|
38
|
+
type: "Identifier",
|
|
39
|
+
name
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function binaryOp(left, operator, right) {
|
|
43
|
+
return {
|
|
44
|
+
type: "BinaryOp",
|
|
45
|
+
left,
|
|
46
|
+
operator,
|
|
47
|
+
right
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function unaryOp(argument) {
|
|
51
|
+
return {
|
|
52
|
+
type: "UnaryOp",
|
|
53
|
+
operator: "-",
|
|
54
|
+
argument
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function functionCall(name, args = []) {
|
|
58
|
+
return {
|
|
59
|
+
type: "FunctionCall",
|
|
60
|
+
name,
|
|
61
|
+
arguments: args
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function assign(name, value) {
|
|
65
|
+
return {
|
|
66
|
+
type: "Assignment",
|
|
67
|
+
name,
|
|
68
|
+
value
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function add(left, right) {
|
|
72
|
+
return binaryOp(left, "+", right);
|
|
73
|
+
}
|
|
74
|
+
function subtract(left, right) {
|
|
75
|
+
return binaryOp(left, "-", right);
|
|
76
|
+
}
|
|
77
|
+
function multiply(left, right) {
|
|
78
|
+
return binaryOp(left, "*", right);
|
|
79
|
+
}
|
|
80
|
+
function divide(left, right) {
|
|
81
|
+
return binaryOp(left, "/", right);
|
|
82
|
+
}
|
|
83
|
+
function modulo(left, right) {
|
|
84
|
+
return binaryOp(left, "%", right);
|
|
85
|
+
}
|
|
86
|
+
function exponentiate(left, right) {
|
|
87
|
+
return binaryOp(left, "^", right);
|
|
88
|
+
}
|
|
89
|
+
function negate(argument) {
|
|
90
|
+
return unaryOp(argument);
|
|
91
|
+
}
|
|
92
|
+
// src/defaults.ts
|
|
93
|
+
var defaultContext = {
|
|
94
|
+
functions: {
|
|
95
|
+
abs: Math.abs,
|
|
96
|
+
ceil: Math.ceil,
|
|
97
|
+
floor: Math.floor,
|
|
98
|
+
round: Math.round,
|
|
99
|
+
sqrt: Math.sqrt,
|
|
100
|
+
min: Math.min,
|
|
101
|
+
max: Math.max,
|
|
102
|
+
sin: Math.sin,
|
|
103
|
+
cos: Math.cos,
|
|
104
|
+
tan: Math.tan,
|
|
105
|
+
log: Math.log,
|
|
106
|
+
log10: Math.log10,
|
|
107
|
+
exp: Math.exp,
|
|
108
|
+
now: () => new Date,
|
|
109
|
+
date: (...args) => new Date(...args),
|
|
110
|
+
milliseconds: (ms) => ms,
|
|
111
|
+
seconds: (s) => s * 1000,
|
|
112
|
+
minutes: (m) => m * 60 * 1000,
|
|
113
|
+
hours: (h) => h * 60 * 60 * 1000,
|
|
114
|
+
days: (d) => d * 24 * 60 * 60 * 1000
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// src/types.ts
|
|
118
|
+
var TokenType;
|
|
119
|
+
((TokenType2) => {
|
|
120
|
+
TokenType2["NUMBER"] = "NUMBER";
|
|
121
|
+
TokenType2["STRING"] = "STRING";
|
|
122
|
+
TokenType2["IDENTIFIER"] = "IDENTIFIER";
|
|
123
|
+
TokenType2["PLUS"] = "PLUS";
|
|
124
|
+
TokenType2["MINUS"] = "MINUS";
|
|
125
|
+
TokenType2["STAR"] = "STAR";
|
|
126
|
+
TokenType2["SLASH"] = "SLASH";
|
|
127
|
+
TokenType2["PERCENT"] = "PERCENT";
|
|
128
|
+
TokenType2["CARET"] = "CARET";
|
|
129
|
+
TokenType2["LPAREN"] = "LPAREN";
|
|
130
|
+
TokenType2["RPAREN"] = "RPAREN";
|
|
131
|
+
TokenType2["EQUALS"] = "EQUALS";
|
|
132
|
+
TokenType2["COMMA"] = "COMMA";
|
|
133
|
+
TokenType2["EOF"] = "EOF";
|
|
134
|
+
})(TokenType ||= {});
|
|
135
|
+
|
|
136
|
+
// src/lexer.ts
|
|
137
|
+
class Lexer {
|
|
138
|
+
source;
|
|
139
|
+
position = 0;
|
|
140
|
+
constructor(source) {
|
|
141
|
+
this.source = source;
|
|
142
|
+
}
|
|
143
|
+
tokenize() {
|
|
144
|
+
const tokens = [];
|
|
145
|
+
while (true) {
|
|
146
|
+
const token = this.nextToken();
|
|
147
|
+
tokens.push(token);
|
|
148
|
+
if (token.type === "EOF" /* EOF */) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return tokens;
|
|
153
|
+
}
|
|
154
|
+
nextToken() {
|
|
155
|
+
this.skipWhitespaceAndComments();
|
|
156
|
+
if (this.position >= this.source.length) {
|
|
157
|
+
return { type: "EOF" /* EOF */, value: "", position: this.position };
|
|
158
|
+
}
|
|
159
|
+
const char = this.getCharAt(this.position);
|
|
160
|
+
const start = this.position;
|
|
161
|
+
if (this.isDigit(char)) {
|
|
162
|
+
return this.readNumber();
|
|
163
|
+
}
|
|
164
|
+
if (char === "'") {
|
|
165
|
+
return this.readString();
|
|
166
|
+
}
|
|
167
|
+
if (this.isLetter(char) || char === "_") {
|
|
168
|
+
return this.readIdentifier();
|
|
169
|
+
}
|
|
170
|
+
switch (char) {
|
|
171
|
+
case "+":
|
|
172
|
+
this.position++;
|
|
173
|
+
return { type: "PLUS" /* PLUS */, value: "+", position: start };
|
|
174
|
+
case "-":
|
|
175
|
+
this.position++;
|
|
176
|
+
return { type: "MINUS" /* MINUS */, value: "-", position: start };
|
|
177
|
+
case "*":
|
|
178
|
+
this.position++;
|
|
179
|
+
return { type: "STAR" /* STAR */, value: "*", position: start };
|
|
180
|
+
case "/":
|
|
181
|
+
this.position++;
|
|
182
|
+
return { type: "SLASH" /* SLASH */, value: "/", position: start };
|
|
183
|
+
case "%":
|
|
184
|
+
this.position++;
|
|
185
|
+
return { type: "PERCENT" /* PERCENT */, value: "%", position: start };
|
|
186
|
+
case "^":
|
|
187
|
+
this.position++;
|
|
188
|
+
return { type: "CARET" /* CARET */, value: "^", position: start };
|
|
189
|
+
case "(":
|
|
190
|
+
this.position++;
|
|
191
|
+
return { type: "LPAREN" /* LPAREN */, value: "(", position: start };
|
|
192
|
+
case ")":
|
|
193
|
+
this.position++;
|
|
194
|
+
return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
|
|
195
|
+
case "=":
|
|
196
|
+
this.position++;
|
|
197
|
+
return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
|
|
198
|
+
case ",":
|
|
199
|
+
this.position++;
|
|
200
|
+
return { type: "COMMA" /* COMMA */, value: ",", position: start };
|
|
201
|
+
case ";":
|
|
202
|
+
this.position++;
|
|
203
|
+
return this.nextToken();
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unexpected character '${char}' at position ${start}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
skipWhitespaceAndComments() {
|
|
209
|
+
while (this.position < this.source.length) {
|
|
210
|
+
const char = this.getCharAt(this.position);
|
|
211
|
+
if (this.isWhitespace(char)) {
|
|
212
|
+
this.position++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (char === "/" && this.peek() === "/") {
|
|
216
|
+
while (this.position < this.source.length && this.getCharAt(this.position) !== `
|
|
217
|
+
`) {
|
|
218
|
+
this.position++;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
readNumber() {
|
|
226
|
+
const start = this.position;
|
|
227
|
+
let hasDecimal = false;
|
|
228
|
+
while (this.position < this.source.length) {
|
|
229
|
+
const char = this.getCharAt(this.position);
|
|
230
|
+
if (this.isDigit(char)) {
|
|
231
|
+
this.position++;
|
|
232
|
+
} else if (char === "." && !hasDecimal) {
|
|
233
|
+
hasDecimal = true;
|
|
234
|
+
this.position++;
|
|
235
|
+
} else {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const value = parseFloat(this.source.slice(start, this.position));
|
|
240
|
+
return { type: "NUMBER" /* NUMBER */, value, position: start };
|
|
241
|
+
}
|
|
242
|
+
readIdentifier() {
|
|
243
|
+
const start = this.position;
|
|
244
|
+
while (this.position < this.source.length) {
|
|
245
|
+
const char = this.getCharAt(this.position);
|
|
246
|
+
if (this.isLetter(char) || this.isDigit(char) || char === "_") {
|
|
247
|
+
this.position++;
|
|
248
|
+
} else {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const name = this.source.slice(start, this.position);
|
|
253
|
+
return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
|
|
254
|
+
}
|
|
255
|
+
readString() {
|
|
256
|
+
const start = this.position;
|
|
257
|
+
this.position++;
|
|
258
|
+
let value = "";
|
|
259
|
+
while (this.position < this.source.length) {
|
|
260
|
+
const char = this.getCharAt(this.position);
|
|
261
|
+
if (char === "'") {
|
|
262
|
+
this.position++;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (char === "\\" && this.position + 1 < this.source.length) {
|
|
266
|
+
this.position++;
|
|
267
|
+
const escaped = this.getCharAt(this.position);
|
|
268
|
+
switch (escaped) {
|
|
269
|
+
case "n":
|
|
270
|
+
value += `
|
|
271
|
+
`;
|
|
272
|
+
break;
|
|
273
|
+
case "t":
|
|
274
|
+
value += "\t";
|
|
275
|
+
break;
|
|
276
|
+
case "r":
|
|
277
|
+
value += "\r";
|
|
278
|
+
break;
|
|
279
|
+
case "'":
|
|
280
|
+
value += "'";
|
|
281
|
+
break;
|
|
282
|
+
case "\\":
|
|
283
|
+
value += "\\";
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
value += escaped;
|
|
287
|
+
}
|
|
288
|
+
this.position++;
|
|
289
|
+
} else {
|
|
290
|
+
value += char;
|
|
291
|
+
this.position++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { type: "STRING" /* STRING */, value, position: start };
|
|
295
|
+
}
|
|
296
|
+
getCharAt(pos) {
|
|
297
|
+
return pos < this.source.length ? this.source[pos] || "" : "";
|
|
298
|
+
}
|
|
299
|
+
peek() {
|
|
300
|
+
return this.getCharAt(this.position + 1);
|
|
301
|
+
}
|
|
302
|
+
isDigit(char) {
|
|
303
|
+
return char >= "0" && char <= "9";
|
|
304
|
+
}
|
|
305
|
+
isLetter(char) {
|
|
306
|
+
return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
|
|
307
|
+
}
|
|
308
|
+
isWhitespace(char) {
|
|
309
|
+
return char === " " || char === "\t" || char === `
|
|
310
|
+
` || char === "\r";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/parser.ts
|
|
315
|
+
class Parser {
|
|
316
|
+
tokens;
|
|
317
|
+
current = 0;
|
|
318
|
+
constructor(tokens) {
|
|
319
|
+
this.tokens = tokens;
|
|
320
|
+
}
|
|
321
|
+
parse() {
|
|
322
|
+
const statements = [];
|
|
323
|
+
while (this.peek().type !== "EOF" /* EOF */) {
|
|
324
|
+
statements.push(this.parseExpression(0));
|
|
325
|
+
}
|
|
326
|
+
if (statements.length === 0) {
|
|
327
|
+
throw new Error("Empty program");
|
|
328
|
+
}
|
|
329
|
+
if (statements.length === 1) {
|
|
330
|
+
const stmt = statements[0];
|
|
331
|
+
if (stmt === undefined) {
|
|
332
|
+
throw new Error("Unexpected undefined statement");
|
|
333
|
+
}
|
|
334
|
+
return stmt;
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
type: "Program",
|
|
338
|
+
statements
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
parseExpression(minPrecedence) {
|
|
342
|
+
let left = this.parsePrefix();
|
|
343
|
+
while (true) {
|
|
344
|
+
const token = this.peek();
|
|
345
|
+
const precedence = this.getPrecedence(token.type);
|
|
346
|
+
if (precedence < minPrecedence) {
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
if (token.type === "EQUALS" /* EQUALS */) {
|
|
350
|
+
if (left.type !== "Identifier") {
|
|
351
|
+
throw new Error("Invalid assignment target");
|
|
352
|
+
}
|
|
353
|
+
const identName = left.name;
|
|
354
|
+
this.advance();
|
|
355
|
+
const value = this.parseExpression(precedence + 1);
|
|
356
|
+
left = {
|
|
357
|
+
type: "Assignment",
|
|
358
|
+
name: identName,
|
|
359
|
+
value
|
|
360
|
+
};
|
|
361
|
+
} else if (this.isBinaryOperator(token.type)) {
|
|
362
|
+
const operator = token.value;
|
|
363
|
+
this.advance();
|
|
364
|
+
const right = this.parseExpression(precedence + 1);
|
|
365
|
+
left = {
|
|
366
|
+
type: "BinaryOp",
|
|
367
|
+
left,
|
|
368
|
+
operator,
|
|
369
|
+
right
|
|
370
|
+
};
|
|
371
|
+
} else {
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return left;
|
|
376
|
+
}
|
|
377
|
+
parsePrefix() {
|
|
378
|
+
const token = this.peek();
|
|
379
|
+
if (token.type === "MINUS" /* MINUS */) {
|
|
380
|
+
this.advance();
|
|
381
|
+
const argument = this.parseExpression(this.getUnaryPrecedence());
|
|
382
|
+
return {
|
|
383
|
+
type: "UnaryOp",
|
|
384
|
+
operator: "-",
|
|
385
|
+
argument
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (token.type === "LPAREN" /* LPAREN */) {
|
|
389
|
+
this.advance();
|
|
390
|
+
const expr = this.parseExpression(0);
|
|
391
|
+
if (this.peek().type !== "RPAREN" /* RPAREN */) {
|
|
392
|
+
throw new Error("Expected closing parenthesis");
|
|
393
|
+
}
|
|
394
|
+
this.advance();
|
|
395
|
+
return expr;
|
|
396
|
+
}
|
|
397
|
+
if (token.type === "NUMBER" /* NUMBER */) {
|
|
398
|
+
this.advance();
|
|
399
|
+
return {
|
|
400
|
+
type: "NumberLiteral",
|
|
401
|
+
value: token.value
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (token.type === "STRING" /* STRING */) {
|
|
405
|
+
this.advance();
|
|
406
|
+
return {
|
|
407
|
+
type: "StringLiteral",
|
|
408
|
+
value: token.value
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
|
|
412
|
+
const name = token.value;
|
|
413
|
+
this.advance();
|
|
414
|
+
if (this.peek().type === "LPAREN" /* LPAREN */) {
|
|
415
|
+
this.advance();
|
|
416
|
+
const args = this.parseFunctionArguments();
|
|
417
|
+
if (this.peek().type !== "RPAREN" /* RPAREN */) {
|
|
418
|
+
throw new Error("Expected closing parenthesis");
|
|
419
|
+
}
|
|
420
|
+
this.advance();
|
|
421
|
+
return {
|
|
422
|
+
type: "FunctionCall",
|
|
423
|
+
name,
|
|
424
|
+
arguments: args
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
type: "Identifier",
|
|
429
|
+
name
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
throw new Error(`Unexpected token: ${token.value}`);
|
|
433
|
+
}
|
|
434
|
+
parseFunctionArguments() {
|
|
435
|
+
const args = [];
|
|
436
|
+
if (this.peek().type === "RPAREN" /* RPAREN */) {
|
|
437
|
+
return args;
|
|
438
|
+
}
|
|
439
|
+
args.push(this.parseExpression(0));
|
|
440
|
+
while (this.peek().type === "COMMA" /* COMMA */) {
|
|
441
|
+
this.advance();
|
|
442
|
+
args.push(this.parseExpression(0));
|
|
443
|
+
}
|
|
444
|
+
return args;
|
|
445
|
+
}
|
|
446
|
+
getPrecedence(type) {
|
|
447
|
+
switch (type) {
|
|
448
|
+
case "EQUALS" /* EQUALS */:
|
|
449
|
+
return 1;
|
|
450
|
+
case "PLUS" /* PLUS */:
|
|
451
|
+
case "MINUS" /* MINUS */:
|
|
452
|
+
return 2;
|
|
453
|
+
case "STAR" /* STAR */:
|
|
454
|
+
case "SLASH" /* SLASH */:
|
|
455
|
+
case "PERCENT" /* PERCENT */:
|
|
456
|
+
return 3;
|
|
457
|
+
case "CARET" /* CARET */:
|
|
458
|
+
return 4;
|
|
459
|
+
default:
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
getUnaryPrecedence() {
|
|
464
|
+
return 5;
|
|
465
|
+
}
|
|
466
|
+
isBinaryOperator(type) {
|
|
467
|
+
return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */;
|
|
468
|
+
}
|
|
469
|
+
peek() {
|
|
470
|
+
if (this.current >= this.tokens.length) {
|
|
471
|
+
return { type: "EOF" /* EOF */, value: "", position: -1 };
|
|
472
|
+
}
|
|
473
|
+
const token = this.tokens[this.current];
|
|
474
|
+
if (token === undefined) {
|
|
475
|
+
return { type: "EOF" /* EOF */, value: "", position: -1 };
|
|
476
|
+
}
|
|
477
|
+
return token;
|
|
478
|
+
}
|
|
479
|
+
advance() {
|
|
480
|
+
this.current++;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function parseSource(source) {
|
|
484
|
+
const lexer = new Lexer(source);
|
|
485
|
+
const tokens = lexer.tokenize();
|
|
486
|
+
const parser = new Parser(tokens);
|
|
487
|
+
return parser.parse();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/executor.ts
|
|
491
|
+
class Executor {
|
|
492
|
+
context;
|
|
493
|
+
variables;
|
|
494
|
+
constructor(context = {}) {
|
|
495
|
+
this.context = context;
|
|
496
|
+
this.variables = new Map(Object.entries(context.variables || {}));
|
|
497
|
+
}
|
|
498
|
+
execute(node) {
|
|
499
|
+
switch (node.type) {
|
|
500
|
+
case "Program":
|
|
501
|
+
return this.executeProgram(node);
|
|
502
|
+
case "NumberLiteral":
|
|
503
|
+
return this.executeNumberLiteral(node);
|
|
504
|
+
case "StringLiteral":
|
|
505
|
+
return this.executeStringLiteral(node);
|
|
506
|
+
case "Identifier":
|
|
507
|
+
return this.executeIdentifier(node);
|
|
508
|
+
case "BinaryOp":
|
|
509
|
+
return this.executeBinaryOp(node);
|
|
510
|
+
case "UnaryOp":
|
|
511
|
+
return this.executeUnaryOp(node);
|
|
512
|
+
case "FunctionCall":
|
|
513
|
+
return this.executeFunctionCall(node);
|
|
514
|
+
case "Assignment":
|
|
515
|
+
return this.executeAssignment(node);
|
|
516
|
+
default:
|
|
517
|
+
throw new Error(`Unknown node type`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
executeProgram(node) {
|
|
521
|
+
let result;
|
|
522
|
+
for (const statement of node.statements) {
|
|
523
|
+
result = this.execute(statement);
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
executeNumberLiteral(node) {
|
|
528
|
+
return node.value;
|
|
529
|
+
}
|
|
530
|
+
executeStringLiteral(node) {
|
|
531
|
+
return node.value;
|
|
532
|
+
}
|
|
533
|
+
executeIdentifier(node) {
|
|
534
|
+
const value = this.variables.get(node.name);
|
|
535
|
+
if (value === undefined) {
|
|
536
|
+
throw new Error(`Undefined variable: ${node.name}`);
|
|
537
|
+
}
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
executeBinaryOp(node) {
|
|
541
|
+
const left = this.execute(node.left);
|
|
542
|
+
const right = this.execute(node.right);
|
|
543
|
+
switch (node.operator) {
|
|
544
|
+
case "+":
|
|
545
|
+
return this.add(left, right);
|
|
546
|
+
case "-":
|
|
547
|
+
return this.subtract(left, right);
|
|
548
|
+
case "*":
|
|
549
|
+
return this.multiply(left, right);
|
|
550
|
+
case "/":
|
|
551
|
+
return this.divide(left, right);
|
|
552
|
+
case "%":
|
|
553
|
+
return this.modulo(left, right);
|
|
554
|
+
case "^":
|
|
555
|
+
return this.exponentiate(left, right);
|
|
556
|
+
default:
|
|
557
|
+
throw new Error(`Unknown operator: ${node.operator}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
executeUnaryOp(node) {
|
|
561
|
+
const arg = this.execute(node.argument);
|
|
562
|
+
if (node.operator === "-") {
|
|
563
|
+
if (typeof arg === "number") {
|
|
564
|
+
return -arg;
|
|
565
|
+
}
|
|
566
|
+
if (arg instanceof Date) {
|
|
567
|
+
return new Date(-arg.getTime());
|
|
568
|
+
}
|
|
569
|
+
throw new Error(`Cannot negate ${typeof arg}`);
|
|
570
|
+
}
|
|
571
|
+
throw new Error(`Unknown unary operator: ${node.operator}`);
|
|
572
|
+
}
|
|
573
|
+
executeFunctionCall(node) {
|
|
574
|
+
const fn = this.context.functions?.[node.name];
|
|
575
|
+
if (fn === undefined) {
|
|
576
|
+
throw new Error(`Undefined function: ${node.name}`);
|
|
577
|
+
}
|
|
578
|
+
if (typeof fn !== "function") {
|
|
579
|
+
throw new Error(`${node.name} is not a function`);
|
|
580
|
+
}
|
|
581
|
+
const args = node.arguments.map((arg) => this.execute(arg));
|
|
582
|
+
return fn(...args);
|
|
583
|
+
}
|
|
584
|
+
executeAssignment(node) {
|
|
585
|
+
const value = this.execute(node.value);
|
|
586
|
+
if (typeof value !== "number" && !(value instanceof Date)) {
|
|
587
|
+
throw new Error(`Cannot assign ${typeof value} to variable. Only numbers and Dates are allowed.`);
|
|
588
|
+
}
|
|
589
|
+
this.variables.set(node.name, value);
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
add(left, right) {
|
|
593
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
594
|
+
return left + right;
|
|
595
|
+
}
|
|
596
|
+
if (left instanceof Date && typeof right === "number") {
|
|
597
|
+
return new Date(left.getTime() + right);
|
|
598
|
+
}
|
|
599
|
+
if (typeof left === "number" && right instanceof Date) {
|
|
600
|
+
return new Date(right.getTime() + left);
|
|
601
|
+
}
|
|
602
|
+
if (left instanceof Date && right instanceof Date) {
|
|
603
|
+
return new Date(left.getTime() + right.getTime());
|
|
604
|
+
}
|
|
605
|
+
throw new Error(`Cannot add ${this.typeOf(left)} and ${this.typeOf(right)}`);
|
|
606
|
+
}
|
|
607
|
+
subtract(left, right) {
|
|
608
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
609
|
+
return left - right;
|
|
610
|
+
}
|
|
611
|
+
if (left instanceof Date && typeof right === "number") {
|
|
612
|
+
return new Date(left.getTime() - right);
|
|
613
|
+
}
|
|
614
|
+
if (typeof left === "number" && right instanceof Date) {
|
|
615
|
+
return new Date(left - right.getTime());
|
|
616
|
+
}
|
|
617
|
+
if (left instanceof Date && right instanceof Date) {
|
|
618
|
+
return left.getTime() - right.getTime();
|
|
619
|
+
}
|
|
620
|
+
throw new Error(`Cannot subtract ${this.typeOf(right)} from ${this.typeOf(left)}`);
|
|
621
|
+
}
|
|
622
|
+
multiply(left, right) {
|
|
623
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
624
|
+
return left * right;
|
|
625
|
+
}
|
|
626
|
+
throw new Error(`Cannot multiply ${this.typeOf(left)} and ${this.typeOf(right)}`);
|
|
627
|
+
}
|
|
628
|
+
divide(left, right) {
|
|
629
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
630
|
+
if (right === 0) {
|
|
631
|
+
throw new Error("Division by zero");
|
|
632
|
+
}
|
|
633
|
+
return left / right;
|
|
634
|
+
}
|
|
635
|
+
throw new Error(`Cannot divide ${this.typeOf(left)} by ${this.typeOf(right)}`);
|
|
636
|
+
}
|
|
637
|
+
modulo(left, right) {
|
|
638
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
639
|
+
if (right === 0) {
|
|
640
|
+
throw new Error("Division by zero");
|
|
641
|
+
}
|
|
642
|
+
return left % right;
|
|
643
|
+
}
|
|
644
|
+
throw new Error(`Cannot compute ${this.typeOf(left)} modulo ${this.typeOf(right)}`);
|
|
645
|
+
}
|
|
646
|
+
exponentiate(left, right) {
|
|
647
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
648
|
+
return left ** right;
|
|
649
|
+
}
|
|
650
|
+
throw new Error(`Cannot exponentiate ${this.typeOf(left)} by ${this.typeOf(right)}`);
|
|
651
|
+
}
|
|
652
|
+
typeOf(value) {
|
|
653
|
+
if (value instanceof Date) {
|
|
654
|
+
return "Date";
|
|
655
|
+
}
|
|
656
|
+
return typeof value;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function execute(source, context) {
|
|
660
|
+
const ast = parseSource(source);
|
|
661
|
+
const executor = new Executor(context);
|
|
662
|
+
return executor.execute(ast);
|
|
663
|
+
}
|
|
664
|
+
export {
|
|
665
|
+
parseSource,
|
|
666
|
+
execute,
|
|
667
|
+
defaultContext,
|
|
668
|
+
exports_ast as ast,
|
|
669
|
+
TokenType,
|
|
670
|
+
Parser,
|
|
671
|
+
Lexer,
|
|
672
|
+
Executor
|
|
673
|
+
};
|