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