@dseict/psc-interpreter 0.0.2 → 1.0.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/dist/PSCLexerBase.js +6 -8
- package/dist/PSCLexerBase.js.map +1 -1
- package/dist/_antlr/PSCLexer.d.ts +1 -1
- package/dist/_antlr/PSCLexer.d.ts.map +1 -1
- package/dist/_antlr/PSCLexer.js +2 -2
- package/dist/_antlr/PSCLexer.js.map +1 -1
- package/dist/_antlr/PSCParser.d.ts.map +1 -1
- package/dist/_antlr/PSCParser.js +1 -1
- package/dist/_antlr/PSCParser.js.map +1 -1
- package/dist/_antlr/PSCParserVisitor.d.ts +35 -35
- package/dist/_antlr/PSCParserVisitor.d.ts.map +1 -1
- package/dist/_antlr/PSCParserVisitor.js +36 -36
- package/dist/_antlr/PSCParserVisitor.js.map +1 -1
- package/dist/error.d.ts +9 -9
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +9 -9
- package/dist/error.js.map +1 -1
- package/dist/events.d.ts +56 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +22 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -680
- package/dist/index.js.map +1 -1
- package/dist/visitor.d.ts +53 -0
- package/dist/visitor.d.ts.map +1 -0
- package/dist/visitor.js +766 -0
- package/dist/visitor.js.map +1 -0
- package/package.json +15 -4
package/dist/index.js
CHANGED
|
@@ -1,688 +1,31 @@
|
|
|
1
|
-
import { CharStream, CommonTokenStream, ErrorListener,
|
|
1
|
+
import { CharStream, CommonTokenStream, ErrorListener, Token } from "antlr4";
|
|
2
2
|
import PSCLexer from "./_antlr/PSCLexer";
|
|
3
|
-
import PSCParser
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
const lexer = new PSCLexer(new CharStream(code));
|
|
9
|
-
lexer.removeErrorListeners();
|
|
10
|
-
lexer.addErrorListener(new PSCErrorListener());
|
|
11
|
-
const parser = new PSCParser(new CommonTokenStream(lexer));
|
|
12
|
-
parser.removeErrorListeners(); // Remove default error listeners
|
|
13
|
-
parser.addErrorListener(new PSCErrorListener());
|
|
14
|
-
const tree = parser.program();
|
|
15
|
-
// Execute the code
|
|
16
|
-
const interpreter = new PSCInterpreter({
|
|
17
|
-
strictVariableScope: false,
|
|
18
|
-
arrayStartIndex: 1,
|
|
19
|
-
...options, // Override default options with user-provided options
|
|
20
|
-
});
|
|
21
|
-
await interpreter.visit(tree);
|
|
22
|
-
}
|
|
23
|
-
function stringSmartCast(value) {
|
|
24
|
-
if (typeof value === "string") {
|
|
25
|
-
// Try to cast to boolean
|
|
26
|
-
if (value.toLowerCase() === "true") {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
if (value.toLowerCase() === "false") {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
if (value.toLowerCase() === "null") {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
// Handle ridiculous edge cases for JS built-in casting v_v
|
|
36
|
-
if (value.trim() === "" || !/^-?\d*(\.\d+)?$/.test(value)) {
|
|
37
|
-
return value; // Return as is
|
|
38
|
-
}
|
|
39
|
-
// Try to cast to number
|
|
40
|
-
const numValue = Number(value);
|
|
41
|
-
if (!isNaN(numValue)) {
|
|
42
|
-
return numValue;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return value; // Return as is if no casting is possible
|
|
46
|
-
}
|
|
47
|
-
class PSCInterpreter extends PSCParserVisitor {
|
|
48
|
-
options;
|
|
49
|
-
// Stack of variable scopes
|
|
50
|
-
variableStack = [{}];
|
|
51
|
-
// Current return value, undefined means currently not in returning state
|
|
52
|
-
currentReturn = undefined;
|
|
3
|
+
import PSCParser from "./_antlr/PSCParser";
|
|
4
|
+
import { PSCInterpretVisitor } from "./visitor";
|
|
5
|
+
import { PSCSyntaxError } from "./error";
|
|
6
|
+
export class PSCInterpreter {
|
|
7
|
+
#visitor;
|
|
53
8
|
constructor(options) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const stack of this.variableStack.slice().reverse()) {
|
|
60
|
-
if (stack[name] !== undefined) {
|
|
61
|
-
stack[name] = value;
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// If variable not found declared, assign it to the current scope
|
|
66
|
-
this.variableStack[this.variableStack.length - 1][name] = value;
|
|
67
|
-
}
|
|
68
|
-
readVariable(ctx, name) {
|
|
69
|
-
for (const stack of this.variableStack.slice().reverse()) {
|
|
70
|
-
if (stack[name] !== undefined) {
|
|
71
|
-
return stack[name];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
throw new AccessNonExistingVariableError(ctx, name);
|
|
75
|
-
}
|
|
76
|
-
deleteVariable(ctx, name) {
|
|
77
|
-
for (const stack of this.variableStack.slice().reverse()) {
|
|
78
|
-
if (stack[name] !== undefined) {
|
|
79
|
-
delete stack[name];
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
throw new ImpossibleError(ctx, `Cannot delete variable '${name}' because it does not exist.`);
|
|
84
|
-
}
|
|
85
|
-
variableExists(name) {
|
|
86
|
-
for (const stack of this.variableStack.slice().reverse()) {
|
|
87
|
-
if (stack[name] !== undefined) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
newVariableStack(_ctx) {
|
|
94
|
-
this.variableStack.push({});
|
|
95
|
-
}
|
|
96
|
-
popVariableStack(ctx) {
|
|
97
|
-
if (this.variableStack.length === 1) {
|
|
98
|
-
throw new ImpossibleError(ctx, "Cannot pop the global variable stack.");
|
|
99
|
-
}
|
|
100
|
-
this.variableStack.pop();
|
|
101
|
-
}
|
|
102
|
-
initiateReturn(value) {
|
|
103
|
-
this.currentReturn = value;
|
|
104
|
-
}
|
|
105
|
-
isReturning() {
|
|
106
|
-
return this.currentReturn !== undefined;
|
|
107
|
-
}
|
|
108
|
-
terminateReturn() {
|
|
109
|
-
const ret = this.currentReturn;
|
|
110
|
-
this.currentReturn = undefined;
|
|
111
|
-
return ret;
|
|
9
|
+
this.#visitor = new PSCInterpretVisitor({
|
|
10
|
+
strictVariableScope: false,
|
|
11
|
+
arrayStartIndex: 1,
|
|
12
|
+
...options, // Override default options with user-provided options
|
|
13
|
+
});
|
|
112
14
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
else if (typeof value === "string") {
|
|
124
|
-
return `"` + value + `"`; // Wrap string in quotes
|
|
125
|
-
}
|
|
126
|
-
else if (value === undefined) {
|
|
127
|
-
return "";
|
|
128
|
-
}
|
|
129
|
-
else if (value === null) {
|
|
130
|
-
return "null";
|
|
131
|
-
}
|
|
132
|
-
else if (value instanceof Function) {
|
|
133
|
-
return "[Function]";
|
|
134
|
-
}
|
|
135
|
-
throw new ImpossibleError(ctx, `Cannot convert value of type ${typeof value} to string.`);
|
|
15
|
+
async interpret(code) {
|
|
16
|
+
// Parse the code
|
|
17
|
+
const lexer = new PSCLexer(new CharStream(code));
|
|
18
|
+
lexer.removeErrorListeners();
|
|
19
|
+
lexer.addErrorListener(new PSCErrorListener());
|
|
20
|
+
const parser = new PSCParser(new CommonTokenStream(lexer));
|
|
21
|
+
parser.removeErrorListeners(); // Remove default error listeners
|
|
22
|
+
parser.addErrorListener(new PSCErrorListener());
|
|
23
|
+
const tree = parser.program();
|
|
24
|
+
await this.#visitor.visit(tree);
|
|
136
25
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
!Number.isInteger(index - this.options.arrayStartIndex) ||
|
|
140
|
-
index - this.options.arrayStartIndex < 0) {
|
|
141
|
-
throw new InvalidArrayIndexError(ctx, this.asString(ctx, index));
|
|
142
|
-
}
|
|
143
|
-
return index - this.options.arrayStartIndex;
|
|
26
|
+
on(eventType, handler) {
|
|
27
|
+
this.#visitor.on(eventType, handler);
|
|
144
28
|
}
|
|
145
|
-
visitProgram = async (ctx) => {
|
|
146
|
-
for (const subprogramCtx of ctx.subprogram_list()) {
|
|
147
|
-
await this.visitSubprogram(subprogramCtx);
|
|
148
|
-
}
|
|
149
|
-
await this.visitStmts(ctx.stmts());
|
|
150
|
-
};
|
|
151
|
-
// Expression and literals
|
|
152
|
-
visitExpr = async (ctx) => {
|
|
153
|
-
return await this.visitOrExpr(ctx.orExpr());
|
|
154
|
-
};
|
|
155
|
-
visitOrExpr = async (ctx) => {
|
|
156
|
-
let result = stringSmartCast(await this.visitAndExpr(ctx.andExpr(0)));
|
|
157
|
-
for (let i = 1; i < ctx.andExpr_list().length; i++) {
|
|
158
|
-
const right = stringSmartCast(await this.visitAndExpr(ctx.andExpr(i)));
|
|
159
|
-
const resultBool = typeof result == "boolean";
|
|
160
|
-
const rightBool = typeof right == "boolean";
|
|
161
|
-
if (!resultBool || !rightBool) {
|
|
162
|
-
throw new OperationValueTypeMismatchError(ctx, "OR", "boolean", this.asString(ctx, !resultBool ? result : right));
|
|
163
|
-
}
|
|
164
|
-
result = result || right;
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
167
|
-
};
|
|
168
|
-
visitAndExpr = async (ctx) => {
|
|
169
|
-
let result = stringSmartCast(await this.visitCompExpr(ctx.compExpr(0)));
|
|
170
|
-
for (let i = 1; i < ctx.compExpr_list().length; i++) {
|
|
171
|
-
const right = stringSmartCast(await this.visitCompExpr(ctx.compExpr(i)));
|
|
172
|
-
const resultBool = typeof result == "boolean";
|
|
173
|
-
const rightBool = typeof right == "boolean";
|
|
174
|
-
if (!resultBool || !rightBool) {
|
|
175
|
-
throw new OperationValueTypeMismatchError(ctx, "AND", "boolean", this.asString(ctx, !resultBool ? result : right));
|
|
176
|
-
}
|
|
177
|
-
result = result && right;
|
|
178
|
-
}
|
|
179
|
-
return result;
|
|
180
|
-
};
|
|
181
|
-
visitCompExpr = async (ctx) => {
|
|
182
|
-
let result = stringSmartCast(await this.visitAddExpr(ctx.addExpr(0)));
|
|
183
|
-
for (let i = 1; i < ctx.addExpr_list().length; i++) {
|
|
184
|
-
const right = stringSmartCast(await this.visitAddExpr(ctx.addExpr(i)));
|
|
185
|
-
const operator = ctx.compOp(i - 1).getText();
|
|
186
|
-
switch (operator) {
|
|
187
|
-
case "=":
|
|
188
|
-
result = result === right;
|
|
189
|
-
break;
|
|
190
|
-
case "<>":
|
|
191
|
-
result = result !== right;
|
|
192
|
-
break;
|
|
193
|
-
case ">":
|
|
194
|
-
if ((typeof result == "string" && typeof right == "string") ||
|
|
195
|
-
(typeof result == "number" && typeof right == "number")) {
|
|
196
|
-
result = result > right;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
throw new OperationValueTypeMismatchError(ctx, ">", "string or number", `${result} > ${right}`);
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
case "<":
|
|
203
|
-
if ((typeof result == "string" && typeof right == "string") ||
|
|
204
|
-
(typeof result == "number" && typeof right == "number")) {
|
|
205
|
-
result = result < right;
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
throw new OperationValueTypeMismatchError(ctx, "<", "string or number", `${result} < ${right}`);
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
case ">=":
|
|
212
|
-
if ((typeof result == "string" && typeof right == "string") ||
|
|
213
|
-
(typeof result == "number" && typeof right == "number")) {
|
|
214
|
-
result = result >= right;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
throw new OperationValueTypeMismatchError(ctx, ">=", "string or number", `${result} >= ${right}`);
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
case "<=":
|
|
221
|
-
if ((typeof result == "string" && typeof right == "string") ||
|
|
222
|
-
(typeof result == "number" && typeof right == "number")) {
|
|
223
|
-
result = result <= right;
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
throw new OperationValueTypeMismatchError(ctx, "<=", "string or number", `${result} <= ${right}`);
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
default:
|
|
230
|
-
throw new ImpossibleError(ctx, `Unknown comparison operator: ${operator}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return result;
|
|
234
|
-
};
|
|
235
|
-
visitAddExpr = async (ctx) => {
|
|
236
|
-
let result = stringSmartCast(await this.visitMulExpr(ctx.mulExpr(0)));
|
|
237
|
-
for (let i = 1; i < ctx.mulExpr_list().length; i++) {
|
|
238
|
-
const right = stringSmartCast(await this.visitMulExpr(ctx.mulExpr(i)));
|
|
239
|
-
if (typeof result !== "number" || typeof right !== "number") {
|
|
240
|
-
throw new OperationValueTypeMismatchError(ctx, "Addition or subtraction", "number", `${this.asString(ctx, result)} and ${this.asString(ctx, right)}`);
|
|
241
|
-
}
|
|
242
|
-
const operator = ctx.addOp(i - 1).getText();
|
|
243
|
-
switch (operator) {
|
|
244
|
-
case "+":
|
|
245
|
-
result += right;
|
|
246
|
-
break;
|
|
247
|
-
case "-":
|
|
248
|
-
result -= right;
|
|
249
|
-
break;
|
|
250
|
-
default:
|
|
251
|
-
throw new ImpossibleError(ctx, `Unknown addition/subtraction operator: ${operator}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return result;
|
|
255
|
-
};
|
|
256
|
-
visitMulExpr = async (ctx) => {
|
|
257
|
-
let result = stringSmartCast(await this.visitExpExpr(ctx.expExpr(0)));
|
|
258
|
-
for (let i = 1; i < ctx.expExpr_list().length; i++) {
|
|
259
|
-
const right = stringSmartCast(await this.visitExpExpr(ctx.expExpr(i)));
|
|
260
|
-
if (typeof result !== "number" || typeof right !== "number") {
|
|
261
|
-
throw new OperationValueTypeMismatchError(ctx, "Multiplication/division/modulo", "number", `${this.asString(ctx, result)} and ${this.asString(ctx, right)}`);
|
|
262
|
-
}
|
|
263
|
-
const operator = ctx.mulOp(i - 1).getText();
|
|
264
|
-
switch (operator) {
|
|
265
|
-
case "*":
|
|
266
|
-
result *= right;
|
|
267
|
-
break;
|
|
268
|
-
case "/":
|
|
269
|
-
result /= right;
|
|
270
|
-
break;
|
|
271
|
-
case "mod":
|
|
272
|
-
case "%":
|
|
273
|
-
result %= right;
|
|
274
|
-
break;
|
|
275
|
-
default:
|
|
276
|
-
throw new ImpossibleError(ctx, `Unknown multiplication operator: ${operator}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return result;
|
|
280
|
-
};
|
|
281
|
-
visitExpExpr = async (ctx) => {
|
|
282
|
-
let result = await this.visitUnaryExpr(ctx.unaryExpr(0));
|
|
283
|
-
for (let i = 1; i < ctx.unaryExpr_list().length; i++) {
|
|
284
|
-
const right = stringSmartCast(await this.visitUnaryExpr(ctx.unaryExpr(i)));
|
|
285
|
-
if (typeof result !== "number" || typeof right !== "number") {
|
|
286
|
-
throw new OperationValueTypeMismatchError(ctx, "Exponential", "number", `${this.asString(ctx, result)} and ${this.asString(ctx, right)}`);
|
|
287
|
-
}
|
|
288
|
-
const operator = ctx.expOp(i - 1).getText();
|
|
289
|
-
switch (operator) {
|
|
290
|
-
case "**":
|
|
291
|
-
case "^":
|
|
292
|
-
result **= right;
|
|
293
|
-
break;
|
|
294
|
-
default:
|
|
295
|
-
throw new ImpossibleError(ctx, `Unknown exponentiation operator: ${operator}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return result;
|
|
299
|
-
};
|
|
300
|
-
visitUnaryExpr = async (ctx) => {
|
|
301
|
-
const minusCount = ctx.MINUS_list().length;
|
|
302
|
-
const plusCount = ctx.PLUS_list().length;
|
|
303
|
-
const value = stringSmartCast(await this.visitNotExpr(ctx.notExpr()));
|
|
304
|
-
if (typeof value !== "number" && (minusCount > 0 || plusCount > 0)) {
|
|
305
|
-
throw new OperationValueTypeMismatchError(ctx, "Unary negation/affirmation operator", "number", this.asString(ctx, value));
|
|
306
|
-
}
|
|
307
|
-
if (minusCount % 2 === 0) {
|
|
308
|
-
return value;
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
if (typeof value !== "number") {
|
|
312
|
-
// This should never happen, just for type safety
|
|
313
|
-
throw new OperationValueTypeMismatchError(ctx, "Negative sign operator", "number", this.asString(ctx, value));
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
return -value;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
visitNotExpr = async (ctx) => {
|
|
321
|
-
const notCount = ctx.NOT_list().length;
|
|
322
|
-
const value = stringSmartCast(await this.visitPrimaryExpr(ctx.primaryExpr()));
|
|
323
|
-
if (typeof value !== "boolean" && notCount > 0) {
|
|
324
|
-
throw new OperationValueTypeMismatchError(ctx, "NOT", "boolean", this.asString(ctx, value));
|
|
325
|
-
}
|
|
326
|
-
if (notCount % 2 === 0) {
|
|
327
|
-
return value;
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
if (typeof value !== "boolean") {
|
|
331
|
-
// This should never happen, just for type safety
|
|
332
|
-
throw new OperationValueTypeMismatchError(ctx, "NOT", "boolean", this.asString(ctx, value));
|
|
333
|
-
}
|
|
334
|
-
return !value;
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
visitPrimaryExpr = async (ctx) => {
|
|
338
|
-
if (ctx.LPAREN() && ctx.RPAREN()) {
|
|
339
|
-
const args = await Promise.all(ctx.expr_list().map((expr) => this.visitExpr(expr)));
|
|
340
|
-
const func = await this.visitPrimaryExpr(ctx.primaryExpr());
|
|
341
|
-
if (typeof func !== "function") {
|
|
342
|
-
throw new OperationValueTypeMismatchError(ctx, "Function call", "function", this.asString(ctx, func));
|
|
343
|
-
}
|
|
344
|
-
// Function must return a value
|
|
345
|
-
return await func(args);
|
|
346
|
-
}
|
|
347
|
-
else if (ctx.LSQUARE() && ctx.RSQUARE()) {
|
|
348
|
-
const indices = await Promise.all(ctx.expr_list().map((expr) => this.visitExpr(expr)));
|
|
349
|
-
const leftArr = await this.visitPrimaryExpr(ctx.primaryExpr());
|
|
350
|
-
return indices.reduce((arr, index) => {
|
|
351
|
-
if (!Array.isArray(arr)) {
|
|
352
|
-
throw new ArrayAccessNotArrayError(ctx, this.asString(ctx, arr));
|
|
353
|
-
}
|
|
354
|
-
const normalizedIndex = this.normalizeArrayIndexOrThrow(ctx, index);
|
|
355
|
-
if (normalizedIndex > arr.length - 1) {
|
|
356
|
-
throw new InvalidArrayIndexError(ctx, this.asString(ctx, index));
|
|
357
|
-
}
|
|
358
|
-
return arr[normalizedIndex];
|
|
359
|
-
}, leftArr);
|
|
360
|
-
}
|
|
361
|
-
else if (ctx.groupExpr()) {
|
|
362
|
-
return await this.visitGroupExpr(ctx.groupExpr());
|
|
363
|
-
}
|
|
364
|
-
throw new ImpossibleError(ctx, "Invalid primary");
|
|
365
|
-
};
|
|
366
|
-
visitGroupExpr = async (ctx) => {
|
|
367
|
-
if (ctx.expr()) {
|
|
368
|
-
return await this.visitExpr(ctx.expr());
|
|
369
|
-
}
|
|
370
|
-
else if (ctx.atom()) {
|
|
371
|
-
return await this.visitAtom(ctx.atom());
|
|
372
|
-
}
|
|
373
|
-
throw new ImpossibleError(ctx, "Invalid group expression");
|
|
374
|
-
};
|
|
375
|
-
visitAtom = async (ctx) => {
|
|
376
|
-
if (ctx.lits()) {
|
|
377
|
-
return await this.visitLits(ctx.lits());
|
|
378
|
-
}
|
|
379
|
-
else if (ctx.ID()) {
|
|
380
|
-
// Handle variable lookup here
|
|
381
|
-
return this.readVariable(ctx, ctx.ID().getText());
|
|
382
|
-
}
|
|
383
|
-
throw new ImpossibleError(ctx, "Invalid atom");
|
|
384
|
-
};
|
|
385
|
-
visitLits = async (ctx) => {
|
|
386
|
-
if (ctx.floatLits()) {
|
|
387
|
-
return await this.visitFloatLits(ctx.floatLits());
|
|
388
|
-
}
|
|
389
|
-
else if (ctx.intLits()) {
|
|
390
|
-
return await this.visitIntLits(ctx.intLits());
|
|
391
|
-
}
|
|
392
|
-
else if (ctx.arrayLits()) {
|
|
393
|
-
return await this.visitArrayLits(ctx.arrayLits());
|
|
394
|
-
}
|
|
395
|
-
else if (ctx.STRING()) {
|
|
396
|
-
return stringSmartCast(ctx.STRING().getText().slice(1, -1)); // Remove quotes
|
|
397
|
-
}
|
|
398
|
-
else if (ctx.BOOLEAN()) {
|
|
399
|
-
return ctx.BOOLEAN().getText().toLowerCase() === "true";
|
|
400
|
-
}
|
|
401
|
-
else if (ctx.NULL()) {
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
throw new ImpossibleError(ctx, "Invalid literal");
|
|
405
|
-
};
|
|
406
|
-
visitIntLits = async (ctx) => {
|
|
407
|
-
const sign = ctx.MINUS() !== null ? -1 : 1;
|
|
408
|
-
return parseInt(ctx.INTEGER().getText(), 10) * sign;
|
|
409
|
-
};
|
|
410
|
-
visitFloatLits = async (ctx) => {
|
|
411
|
-
const sign = ctx.MINUS() !== null ? -1 : 1;
|
|
412
|
-
return parseFloat(ctx.FLOAT().getText()) * sign;
|
|
413
|
-
};
|
|
414
|
-
visitArrayLits = async (ctx) => {
|
|
415
|
-
const elements = [];
|
|
416
|
-
if (ctx.expr_list()) {
|
|
417
|
-
for (const expr of ctx.expr_list()) {
|
|
418
|
-
elements.push(await this.visitExpr(expr));
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
return elements;
|
|
422
|
-
};
|
|
423
|
-
visitStmts = async (ctx) => {
|
|
424
|
-
for (const stmt of ctx.stmt_list()) {
|
|
425
|
-
await this.visitStmt(stmt);
|
|
426
|
-
if (this.isReturning()) {
|
|
427
|
-
// If a return statement was executed, stop executing further statements
|
|
428
|
-
break;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
visitStmt = async (ctx) => {
|
|
433
|
-
for (const child of ctx.children ?? []) {
|
|
434
|
-
// Dispatch appropriate visit method
|
|
435
|
-
await this.visit(child);
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
visitBlock = async (ctx) => {
|
|
439
|
-
if (this.options.strictVariableScope) {
|
|
440
|
-
this.newVariableStack(ctx);
|
|
441
|
-
}
|
|
442
|
-
await this.visitStmts(ctx.stmts());
|
|
443
|
-
if (this.options.strictVariableScope) {
|
|
444
|
-
this.popVariableStack(ctx);
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
visitIfStmt = async (ctx) => {
|
|
448
|
-
// Validate statement structure
|
|
449
|
-
if (ctx.expr() === null) {
|
|
450
|
-
throw new ImpossibleError(ctx, "If statement must have a condition expression.");
|
|
451
|
-
}
|
|
452
|
-
if (ctx.block(0) === null) {
|
|
453
|
-
throw new ImpossibleError(ctx, "If statement must have a 'then' block.");
|
|
454
|
-
}
|
|
455
|
-
if (ctx.ELSE() && ctx.block(1) === null && ctx.ifStmt() === null) {
|
|
456
|
-
throw new ImpossibleError(ctx, "If statement with 'else' must have an 'else' block or an 'else if' statement.");
|
|
457
|
-
}
|
|
458
|
-
const condition = ctx.expr();
|
|
459
|
-
if (condition) {
|
|
460
|
-
// Evaluate the condition
|
|
461
|
-
const result = await this.visitExpr(condition);
|
|
462
|
-
// Ensure the result is evaluated to a boolean value
|
|
463
|
-
if (typeof result !== "boolean") {
|
|
464
|
-
throw new ConditionNotBooleanError(ctx.expr(), this.asString(ctx, result));
|
|
465
|
-
}
|
|
466
|
-
if (result) {
|
|
467
|
-
// Execute the 'then' block
|
|
468
|
-
await this.visitBlock(ctx.block(0));
|
|
469
|
-
}
|
|
470
|
-
else if (ctx.ELSE()) {
|
|
471
|
-
if (ctx.block_list().length == 2) {
|
|
472
|
-
// Execute the block after else
|
|
473
|
-
// Execute the 'else' block if it exists
|
|
474
|
-
await this.visitBlock(ctx.block(1));
|
|
475
|
-
}
|
|
476
|
-
else if (ctx.ifStmt()) {
|
|
477
|
-
// Execute the ifStmt after else
|
|
478
|
-
await this.visitIfStmt(ctx.ifStmt());
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
visitWhileStmt = async (ctx) => {
|
|
484
|
-
// Validate statement structure
|
|
485
|
-
if (ctx.expr() === null) {
|
|
486
|
-
throw new ImpossibleError(ctx, "While statement must have a condition expression.");
|
|
487
|
-
}
|
|
488
|
-
if (ctx.block() === null) {
|
|
489
|
-
throw new ImpossibleError(ctx, "While statement must have a block to execute.");
|
|
490
|
-
}
|
|
491
|
-
const condition = ctx.expr();
|
|
492
|
-
while (true) {
|
|
493
|
-
const result = await this.visitExpr(condition);
|
|
494
|
-
// Ensure the result is evaluated to a boolean value
|
|
495
|
-
if (typeof result !== "boolean") {
|
|
496
|
-
throw new ConditionNotBooleanError(ctx.expr(), `Condition must evaluate to a boolean value, got: ${result}`);
|
|
497
|
-
}
|
|
498
|
-
if (!result) {
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
await this.visitBlock(ctx.block());
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
visitDoWhileStmt = async (ctx) => {
|
|
505
|
-
if (ctx.expr() === null) {
|
|
506
|
-
throw new ImpossibleError(ctx, "Do-While statement must have a condition expression.");
|
|
507
|
-
}
|
|
508
|
-
if (ctx.block() === null) {
|
|
509
|
-
throw new ImpossibleError(ctx, "Do-While statement must have a block to execute.");
|
|
510
|
-
}
|
|
511
|
-
const condition = ctx.expr();
|
|
512
|
-
let result;
|
|
513
|
-
do {
|
|
514
|
-
await this.visitBlock(ctx.block());
|
|
515
|
-
result = await this.visitExpr(condition);
|
|
516
|
-
// Ensure the result is evaluated to a boolean value
|
|
517
|
-
if (typeof result !== "boolean") {
|
|
518
|
-
throw new ConditionNotBooleanError(ctx.expr(), `Condition must evaluate to a boolean value, got: ${result}`);
|
|
519
|
-
}
|
|
520
|
-
} while (result);
|
|
521
|
-
};
|
|
522
|
-
visitRepeatUntilStmt = async (ctx) => {
|
|
523
|
-
if (ctx.expr() === null) {
|
|
524
|
-
throw new ImpossibleError(ctx, "Do-While statement must have a condition expression.");
|
|
525
|
-
}
|
|
526
|
-
if (ctx.block() === null) {
|
|
527
|
-
throw new ImpossibleError(ctx, "Do-While statement must have a block to execute.");
|
|
528
|
-
}
|
|
529
|
-
const condition = ctx.expr();
|
|
530
|
-
let result;
|
|
531
|
-
do {
|
|
532
|
-
await this.visitBlock(ctx.block());
|
|
533
|
-
result = await this.visitExpr(condition);
|
|
534
|
-
// Ensure the result is evaluated to a boolean value
|
|
535
|
-
if (typeof result !== "boolean") {
|
|
536
|
-
throw new ConditionNotBooleanError(ctx.expr(), `Condition must evaluate to a boolean value, got: ${result}`);
|
|
537
|
-
}
|
|
538
|
-
} while (!result);
|
|
539
|
-
};
|
|
540
|
-
visitForStmt = async (ctx) => {
|
|
541
|
-
if (ctx.ID() === null) {
|
|
542
|
-
throw new ImpossibleError(ctx, "For statement must have a loop variable.");
|
|
543
|
-
}
|
|
544
|
-
if (ctx.expr(0) === null || ctx.expr(1) === null) {
|
|
545
|
-
throw new ImpossibleError(ctx, "For statement must have both 'from' and 'to' expressions.");
|
|
546
|
-
}
|
|
547
|
-
if (ctx.block() === null) {
|
|
548
|
-
throw new ImpossibleError(ctx, "For statement must have a block to execute.");
|
|
549
|
-
}
|
|
550
|
-
const loopVar = ctx.ID().getText();
|
|
551
|
-
const fromValue = await this.visitExpr(ctx.expr(0));
|
|
552
|
-
const toValue = await this.visitExpr(ctx.expr(1));
|
|
553
|
-
const isDown = ctx.DOWN() !== null;
|
|
554
|
-
if (this.variableExists(loopVar)) {
|
|
555
|
-
throw new ForVariableReuseError(ctx, loopVar);
|
|
556
|
-
}
|
|
557
|
-
if (typeof fromValue !== "number" ||
|
|
558
|
-
typeof toValue !== "number" ||
|
|
559
|
-
!Number.isInteger(fromValue) ||
|
|
560
|
-
!Number.isInteger(toValue)) {
|
|
561
|
-
throw new ForRangeNotIntegerError(ctx, `${fromValue} and ${toValue}`);
|
|
562
|
-
}
|
|
563
|
-
for (let i = fromValue; isDown ? i >= toValue : i <= toValue; isDown ? i-- : i++) {
|
|
564
|
-
this.assignVariable(ctx, loopVar, i);
|
|
565
|
-
await this.visitBlock(ctx.block());
|
|
566
|
-
this.deleteVariable(ctx, loopVar);
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
visitAsmStmt = async (ctx) => {
|
|
570
|
-
const ref = await this.visitLvalue(ctx.lvalue());
|
|
571
|
-
const value = await this.visitExpr(ctx.expr());
|
|
572
|
-
ref.set(value);
|
|
573
|
-
};
|
|
574
|
-
visitLvalue = async (ctx) => {
|
|
575
|
-
if (ctx.ID()) {
|
|
576
|
-
const varName = ctx.ID().getText();
|
|
577
|
-
return {
|
|
578
|
-
get: () => this.variableExists(varName)
|
|
579
|
-
? this.readVariable(ctx, varName)
|
|
580
|
-
: undefined,
|
|
581
|
-
set: (value) => this.assignVariable(ctx, varName, value),
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
else if (ctx.lvalue() && ctx.LSQUARE() && ctx.RSQUARE()) {
|
|
585
|
-
const leftRef = await this.visitLvalue(ctx.lvalue());
|
|
586
|
-
const leftVal = leftRef.get();
|
|
587
|
-
const unnormalizedIndices = await Promise.all(ctx.expr_list().map((expr) => this.visitExpr(expr)));
|
|
588
|
-
return {
|
|
589
|
-
get: () => {
|
|
590
|
-
// In lvalue array access, since we allow implicit array creation,
|
|
591
|
-
// if leftVal is undefined, we return undefined instead of throwing.
|
|
592
|
-
if (leftVal === undefined) {
|
|
593
|
-
return undefined;
|
|
594
|
-
}
|
|
595
|
-
let current = leftVal;
|
|
596
|
-
for (const unnormalizedIndex of unnormalizedIndices) {
|
|
597
|
-
if (!Array.isArray(current)) {
|
|
598
|
-
throw new ArrayAccessNotArrayError(ctx, this.asString(ctx, current));
|
|
599
|
-
}
|
|
600
|
-
const normalizedIndex = this.normalizeArrayIndexOrThrow(ctx, unnormalizedIndex);
|
|
601
|
-
// Since we allow implicit array extension,
|
|
602
|
-
// if the index is out of bounds, we return undefined instead of throwing.
|
|
603
|
-
if (normalizedIndex >= current.length) {
|
|
604
|
-
return undefined;
|
|
605
|
-
}
|
|
606
|
-
current = current[normalizedIndex];
|
|
607
|
-
}
|
|
608
|
-
return current;
|
|
609
|
-
},
|
|
610
|
-
set: (value) => {
|
|
611
|
-
const arr = leftVal != undefined ? leftVal : [];
|
|
612
|
-
if (!Array.isArray(arr)) {
|
|
613
|
-
throw new ArrayAccessNotArrayError(ctx, this.asString(ctx, arr));
|
|
614
|
-
}
|
|
615
|
-
// Walk to the parent of the final index, creating intermediate arrays as needed
|
|
616
|
-
let current = arr;
|
|
617
|
-
for (let i = 0; i < unnormalizedIndices.length - 1; i++) {
|
|
618
|
-
const normalizedIndex = this.normalizeArrayIndexOrThrow(ctx, unnormalizedIndices[i]);
|
|
619
|
-
// Implicit extension of the array if the index is out of bounds
|
|
620
|
-
while (current.length <= normalizedIndex) {
|
|
621
|
-
current.push(undefined);
|
|
622
|
-
}
|
|
623
|
-
if (current[normalizedIndex] === undefined) {
|
|
624
|
-
current[normalizedIndex] = [];
|
|
625
|
-
}
|
|
626
|
-
if (!Array.isArray(current[normalizedIndex])) {
|
|
627
|
-
throw new ArrayAccessNotArrayError(ctx, this.asString(ctx, current[normalizedIndex]));
|
|
628
|
-
}
|
|
629
|
-
current = current[normalizedIndex];
|
|
630
|
-
}
|
|
631
|
-
// Set the final index
|
|
632
|
-
const lastNormalizedIndex = this.normalizeArrayIndexOrThrow(ctx, unnormalizedIndices[unnormalizedIndices.length - 1]);
|
|
633
|
-
while (current.length <= lastNormalizedIndex) {
|
|
634
|
-
current.push(undefined);
|
|
635
|
-
}
|
|
636
|
-
current[lastNormalizedIndex] = value;
|
|
637
|
-
leftRef.set(arr);
|
|
638
|
-
},
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
throw new ImpossibleError(ctx, "Invalid assignment left-hand side.");
|
|
642
|
-
};
|
|
643
|
-
visitInputStmt = async (ctx) => {
|
|
644
|
-
const ref = await this.visitLvalue(ctx.lvalue());
|
|
645
|
-
const value = stringSmartCast(await this.options.inputFunction?.());
|
|
646
|
-
ref.set(value);
|
|
647
|
-
};
|
|
648
|
-
visitOutputStmt = async (ctx) => {
|
|
649
|
-
const val = this.asString(ctx, await this.visitExpr(ctx.expr()));
|
|
650
|
-
this.options.outputFunction?.(val);
|
|
651
|
-
};
|
|
652
|
-
visitSubprogram = async (ctx) => {
|
|
653
|
-
const name = ctx.ID(0).getText();
|
|
654
|
-
const paramNames = ctx
|
|
655
|
-
.ID_list()
|
|
656
|
-
?.slice(1, ctx.ID_list().length)
|
|
657
|
-
.map((id) => id.getText()) ?? [];
|
|
658
|
-
// Subprogram is stored as a variable in the current variable stack
|
|
659
|
-
this.assignVariable(ctx, name, async (params) => {
|
|
660
|
-
if (params.length !== paramNames.length) {
|
|
661
|
-
throw new UnmatchedArgumentsError(ctx, name, paramNames.length, params.length);
|
|
662
|
-
}
|
|
663
|
-
// Create a new variable stack for the subprogram execution ignoring strictVariableScope option.
|
|
664
|
-
// This ensures that variables defined within the subprogram do not interfere with those in the calling context.
|
|
665
|
-
// If strictVariableScope is true, there is no need to create a new variable stack here
|
|
666
|
-
// since that is automatically handled by the visitBlock method
|
|
667
|
-
if (!this.options.strictVariableScope) {
|
|
668
|
-
this.newVariableStack(ctx);
|
|
669
|
-
}
|
|
670
|
-
// Assign arguments to the new variable stack
|
|
671
|
-
for (let i = 0; i < paramNames.length; i++) {
|
|
672
|
-
this.assignVariable(ctx, paramNames[i], params[i]);
|
|
673
|
-
}
|
|
674
|
-
await this.visitBlock(ctx.block());
|
|
675
|
-
if (!this.options.strictVariableScope) {
|
|
676
|
-
this.popVariableStack(ctx);
|
|
677
|
-
}
|
|
678
|
-
// Terminate the returning state here
|
|
679
|
-
return this.terminateReturn() ?? null;
|
|
680
|
-
});
|
|
681
|
-
};
|
|
682
|
-
visitReturnStmt = async (ctx) => {
|
|
683
|
-
const value = await this.visitExpr(ctx.expr());
|
|
684
|
-
this.initiateReturn(value);
|
|
685
|
-
};
|
|
686
29
|
}
|
|
687
30
|
class PSCErrorListener extends ErrorListener {
|
|
688
31
|
syntaxError(recognizer, offendingSymbol, line, column, msg) {
|