pure-dango 1.8.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.md +21 -0
- package/package.json +45 -0
- package/src/core/compiler.ts +1269 -0
- package/src/core/interpreter.ts +2042 -0
- package/src/core/parser/handlers.ts +1091 -0
- package/src/core/parser/helpers.ts +157 -0
- package/src/core/parser/main.ts +620 -0
- package/src/core/tokenizer/funnies.ts +43 -0
- package/src/core/tokenizer/tokenizer.ts +334 -0
- package/src/core/utils.ts +534 -0
- package/src/index.ts +190 -0
- package/src/runtime/errors.ts +239 -0
- package/src/runtime/globals.ts +11 -0
- package/src/runtime/libs/ai.pds +56 -0
- package/src/runtime/libs/errors.pds +74 -0
- package/src/runtime/libs/gambling.pds +93 -0
- package/src/runtime/libs/io.pds +371 -0
- package/src/runtime/libs/math.pds +86 -0
- package/src/runtime/libs/std.pds +2 -0
- package/src/runtime/libs/types.pds +1232 -0
- package/src/runtime/stdlib.ts +1483 -0
|
@@ -0,0 +1,2042 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {init} from "gmp-wasm";
|
|
4
|
+
|
|
5
|
+
import {parser} from "./parser/main";
|
|
6
|
+
import {buildBytecode} from "./compiler";
|
|
7
|
+
import {tokenizer} from "./tokenizer/tokenizer";
|
|
8
|
+
|
|
9
|
+
import {constants, isConstant} from "../runtime/globals";
|
|
10
|
+
import {runtimeErrors} from "../runtime/errors";
|
|
11
|
+
|
|
12
|
+
import {syncFunctions, asyncFunctions, errorTemplate} from "../runtime/stdlib";
|
|
13
|
+
|
|
14
|
+
import {loadBytecode, saveBytecode} from "./utils";
|
|
15
|
+
|
|
16
|
+
type PureNumber = number | bigint;
|
|
17
|
+
type BinaryFunction<T> = (left: T, right: T) => any;
|
|
18
|
+
type Bytecode = Array<number | string | null | undefined>;
|
|
19
|
+
|
|
20
|
+
type CallFrame =
|
|
21
|
+
{
|
|
22
|
+
returnType? : string | null,
|
|
23
|
+
bytecode : Bytecode,
|
|
24
|
+
pointer : number,
|
|
25
|
+
savedScope : Scope,
|
|
26
|
+
returnMode : "function" | "super" | "constructor" | "execute",
|
|
27
|
+
instance? : any,
|
|
28
|
+
functionName : string,
|
|
29
|
+
file : string,
|
|
30
|
+
importer? : string,
|
|
31
|
+
|
|
32
|
+
pendingMethod? :
|
|
33
|
+
{
|
|
34
|
+
methodBytecode : Bytecode,
|
|
35
|
+
methodKey : string,
|
|
36
|
+
methodParameters : string[],
|
|
37
|
+
methodArgs : any[],
|
|
38
|
+
methodScope : Scope,
|
|
39
|
+
returnBytecode? : Bytecode,
|
|
40
|
+
returnPointer? : number,
|
|
41
|
+
returnScope? : Scope,
|
|
42
|
+
isStaticCall? : true,
|
|
43
|
+
}
|
|
44
|
+
savedBaseDir? : string,
|
|
45
|
+
|
|
46
|
+
line : number,
|
|
47
|
+
column : number,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let tryStack: Array<{
|
|
51
|
+
catchPosition : number,
|
|
52
|
+
finallyPosition : number,
|
|
53
|
+
savedPointer : number,
|
|
54
|
+
savedBytecode : Bytecode,
|
|
55
|
+
savedScope : Scope,
|
|
56
|
+
savedStack : any[],
|
|
57
|
+
savedCallStack : CallFrame[],
|
|
58
|
+
catchExecuted : boolean,
|
|
59
|
+
finallyExecuted? : boolean,
|
|
60
|
+
pendingError? : any,
|
|
61
|
+
file : string,
|
|
62
|
+
line : number,
|
|
63
|
+
column : number,
|
|
64
|
+
}> = [];
|
|
65
|
+
|
|
66
|
+
let g: any;
|
|
67
|
+
export let gmpInstance: any;
|
|
68
|
+
export let gmpPrecision: number = 128;
|
|
69
|
+
|
|
70
|
+
export async function initGMP() // default
|
|
71
|
+
{
|
|
72
|
+
gmpInstance = await init();
|
|
73
|
+
g = gmpInstance.getContext({precisionBits: gmpPrecision});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function setPrecision(bits: number)
|
|
77
|
+
{
|
|
78
|
+
gmpPrecision = bits;
|
|
79
|
+
if (gmpInstance)
|
|
80
|
+
{
|
|
81
|
+
g = gmpInstance.getContext({precisionBits: bits});
|
|
82
|
+
try
|
|
83
|
+
{
|
|
84
|
+
let test = g.Float(13.1);
|
|
85
|
+
}
|
|
86
|
+
catch(e)
|
|
87
|
+
{
|
|
88
|
+
errorTemplate(`setPrecision`, `float failed with precision ${bits}, got "${e}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const GF = (x: any): GFloat =>
|
|
94
|
+
{
|
|
95
|
+
if (x instanceof GFloat)
|
|
96
|
+
{
|
|
97
|
+
if (x.inner.precisionBits === gmpPrecision)
|
|
98
|
+
return x;
|
|
99
|
+
const digits = Math.ceil(x.inner.precisionBits / 3.32);
|
|
100
|
+
return new GFloat(g.Float(x.inner.toFixed(digits)));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new GFloat(g.Float(String(x)));
|
|
104
|
+
};
|
|
105
|
+
export const isGFloat = (x : any) : x is GFloat => x instanceof GFloat;
|
|
106
|
+
export const isGWrapper = (x : any) : boolean =>
|
|
107
|
+
typeof x.rndMode === "number" &&
|
|
108
|
+
typeof x.precisionBits === "number" &&
|
|
109
|
+
typeof x.radix === "number" &&
|
|
110
|
+
typeof x.mpfr_t === "number";
|
|
111
|
+
|
|
112
|
+
export function BigIntToGFloat(x : bigint) : GFloat
|
|
113
|
+
{
|
|
114
|
+
if (x === 0n)
|
|
115
|
+
return new GFloat(g.Float("0"));
|
|
116
|
+
|
|
117
|
+
const isNegative = x < 0n;
|
|
118
|
+
const abs = isNegative ? -x : x;
|
|
119
|
+
|
|
120
|
+
function convert(n: bigint): any
|
|
121
|
+
{
|
|
122
|
+
if (n < 1000000000000000n)
|
|
123
|
+
return g.Float(n.toString());
|
|
124
|
+
|
|
125
|
+
const bits = n.toString(2).length;
|
|
126
|
+
const half = BigInt(bits >> 1);
|
|
127
|
+
const shift = 1n << half;
|
|
128
|
+
|
|
129
|
+
const hi = n >> half;
|
|
130
|
+
const lo = n & (shift - 1n);
|
|
131
|
+
|
|
132
|
+
const hiF = convert(hi);
|
|
133
|
+
const loF = convert(lo);
|
|
134
|
+
const shiftF = g.Float("2").pow(g.Float(half.toString()));
|
|
135
|
+
|
|
136
|
+
return hiF.mul(shiftF).add(loF);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = convert(abs);
|
|
140
|
+
return new GFloat(isNegative ? result.neg() : result);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function BigIntDivToGFloat(numerator: bigint, denominator: bigint): GFloat
|
|
144
|
+
{
|
|
145
|
+
if (denominator === 0n)
|
|
146
|
+
throw new runtimeErrors.DivisionByZero(numerator, denominator);
|
|
147
|
+
|
|
148
|
+
const isNegative = (numerator < 0n) !== (denominator < 0n);
|
|
149
|
+
const absNum = numerator < 0n ? -numerator : numerator;
|
|
150
|
+
const absDen = denominator < 0n ? -denominator : denominator;
|
|
151
|
+
|
|
152
|
+
const binding = gmpInstance.binding;
|
|
153
|
+
|
|
154
|
+
function toMpz(n: bigint): number
|
|
155
|
+
{
|
|
156
|
+
let hex = n.toString(16);
|
|
157
|
+
if (hex.length % 2)
|
|
158
|
+
hex = "0" + hex;
|
|
159
|
+
|
|
160
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
161
|
+
for (let i = 0; i < bytes.length; i++)
|
|
162
|
+
bytes[i] = parseInt(hex.slice(i*2, i*2+2), 16);
|
|
163
|
+
|
|
164
|
+
const wasmBuf = binding.malloc(bytes.length);
|
|
165
|
+
binding.mem.set(bytes, wasmBuf);
|
|
166
|
+
|
|
167
|
+
const mpz = binding.mpz_t();
|
|
168
|
+
binding.mpz_init(mpz);
|
|
169
|
+
binding.mpz_import(mpz, bytes.length, 1, 1, 1, 0, wasmBuf);
|
|
170
|
+
|
|
171
|
+
binding.free(wasmBuf);
|
|
172
|
+
return mpz;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const mpzNum = toMpz(absNum);
|
|
176
|
+
const mpzDen = toMpz(absDen);
|
|
177
|
+
|
|
178
|
+
const numBits = BigInt(binding.mpz_sizeinbase(mpzNum, 2));
|
|
179
|
+
const denBits = BigInt(binding.mpz_sizeinbase(mpzDen, 2));
|
|
180
|
+
const shift = (denBits > numBits ? denBits - numBits : 0n) + BigInt(gmpPrecision);
|
|
181
|
+
|
|
182
|
+
const mpzShifted = binding.mpz_t();
|
|
183
|
+
binding.mpz_init(mpzShifted);
|
|
184
|
+
binding.mpz_mul_2exp(mpzShifted, mpzNum, Number(shift));
|
|
185
|
+
|
|
186
|
+
const mpzResult = binding.mpz_t();
|
|
187
|
+
binding.mpz_init(mpzResult);
|
|
188
|
+
binding.mpz_tdiv_q(mpzResult, mpzShifted, mpzDen);
|
|
189
|
+
|
|
190
|
+
binding.mpz_clear(mpzNum); binding.mpz_t_free(mpzNum);
|
|
191
|
+
binding.mpz_clear(mpzDen); binding.mpz_t_free(mpzDen);
|
|
192
|
+
binding.mpz_clear(mpzShifted); binding.mpz_t_free(mpzShifted);
|
|
193
|
+
|
|
194
|
+
const mpfr = binding.mpfr_t();
|
|
195
|
+
binding.mpfr_init2(mpfr, Number(shift) + 64);
|
|
196
|
+
binding.mpfr_set_z(mpfr, mpzResult, 0); // load integer
|
|
197
|
+
binding.mpfr_div_2ui(mpfr, mpfr, Number(shift), 0); // divide by 2^shift
|
|
198
|
+
|
|
199
|
+
binding.mpz_clear(mpzResult); binding.mpz_t_free(mpzResult);
|
|
200
|
+
|
|
201
|
+
const result = g.Float(0);
|
|
202
|
+
binding.mpfr_set(result.mpfr_t, mpfr, 0);
|
|
203
|
+
binding.mpfr_clear(mpfr); binding.mpfr_t_free(mpfr);
|
|
204
|
+
|
|
205
|
+
return new GFloat(isNegative ? result.neg() : result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let callStack : CallFrame[] = [];
|
|
209
|
+
let activeBytecode : Bytecode = [];
|
|
210
|
+
|
|
211
|
+
function next(bytecode: Bytecode) : any
|
|
212
|
+
{
|
|
213
|
+
return bytecode[pointer++];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getTrueValue(value : any)
|
|
217
|
+
{
|
|
218
|
+
if (isGFloat(value))
|
|
219
|
+
return value;
|
|
220
|
+
|
|
221
|
+
if (value === "__INIT__")
|
|
222
|
+
return 0;
|
|
223
|
+
|
|
224
|
+
if (typeof value === "string" && /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(value.trim()) && !isNaN(Number(value)))
|
|
225
|
+
{
|
|
226
|
+
if (value.includes("."))
|
|
227
|
+
return GF(value);
|
|
228
|
+
if (/^[+-]?\d+$/.test(value))
|
|
229
|
+
return BigInt(value);
|
|
230
|
+
|
|
231
|
+
return GF(value); // fallback
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function simpleStack(value : any) : void
|
|
238
|
+
{
|
|
239
|
+
if (value === null || value === undefined)
|
|
240
|
+
stack.push(value);
|
|
241
|
+
|
|
242
|
+
else if (typeof value === "boolean")
|
|
243
|
+
stack.push(value);
|
|
244
|
+
else if (typeof value === "bigint")
|
|
245
|
+
stack.push(value);
|
|
246
|
+
|
|
247
|
+
else if (!isGFloat(value) && !Number.isInteger(value))
|
|
248
|
+
stack.push(GF(value));
|
|
249
|
+
|
|
250
|
+
else
|
|
251
|
+
stack.push(value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function unwrapInstance(value: any): any
|
|
255
|
+
{
|
|
256
|
+
if (value?.type === "class" && value?.isInstance && value?.properties?.__INIT__ !== undefined)
|
|
257
|
+
return unwrapInstance(value.properties.__INIT__);
|
|
258
|
+
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function BinaryOperator(type : string) : Array<any>
|
|
263
|
+
{
|
|
264
|
+
if (stack.length < 2)
|
|
265
|
+
errorTemplate(`BinaryOperator`, `there must be two numbers pushed before using an operator on them.`);
|
|
266
|
+
|
|
267
|
+
const r = stack.pop();
|
|
268
|
+
const l = stack.pop();
|
|
269
|
+
|
|
270
|
+
if (typeof l === "bigint" && typeof r === "bigint")
|
|
271
|
+
return [l, r];
|
|
272
|
+
|
|
273
|
+
if (isGFloat(l) && isGFloat(r))
|
|
274
|
+
{
|
|
275
|
+
if (l.inner.precisionBits !== gmpPrecision || r.inner.precisionBits !== gmpPrecision)
|
|
276
|
+
return [GF(l), GF(r)];
|
|
277
|
+
return [l, r];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let right : any = unwrapInstance(getTrueValue(r));
|
|
281
|
+
let left : any = unwrapInstance(getTrueValue(l));
|
|
282
|
+
|
|
283
|
+
if (left === null || left === undefined || right === null || right === undefined)
|
|
284
|
+
return [left, right];
|
|
285
|
+
|
|
286
|
+
if ((typeof left === "string" || typeof right === "string") && type !== "comparison")
|
|
287
|
+
{
|
|
288
|
+
if (type === "bitwise")
|
|
289
|
+
errorTemplate("BinaryOperator", `bitwise operators cannot be used on strings`);
|
|
290
|
+
|
|
291
|
+
const toStr = (value : any): string => isGFloat(value) ? value.inner.toString() : value === null || value === undefined ? "" : String(value);
|
|
292
|
+
return [toStr(left), toStr(right)];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (left === Infinity)
|
|
296
|
+
left = BigInt(Number.MAX_SAFE_INTEGER);
|
|
297
|
+
if (right === Infinity)
|
|
298
|
+
right = BigInt(Number.MAX_SAFE_INTEGER);
|
|
299
|
+
|
|
300
|
+
if (isGFloat(left)|| isGFloat(right))
|
|
301
|
+
{
|
|
302
|
+
left = GF(left);
|
|
303
|
+
right = GF(right);
|
|
304
|
+
return [left, right];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
(typeof left === "number" && !Number.isInteger(left)) ||
|
|
309
|
+
(typeof right === "number" && !Number.isInteger(right))
|
|
310
|
+
)
|
|
311
|
+
{
|
|
312
|
+
if (typeof left === "string" || typeof right === "string") return [String(left), String(right)];
|
|
313
|
+
left = GF(left);
|
|
314
|
+
right = GF(right);
|
|
315
|
+
return [left, right];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (typeof left === "bigint" || typeof right === "bigint")
|
|
319
|
+
{
|
|
320
|
+
if (typeof left === "string" || typeof right === "string") return [String(left), String(right)];
|
|
321
|
+
if (typeof left !== "bigint")
|
|
322
|
+
left = BigInt(left);
|
|
323
|
+
if (typeof right !== "bigint")
|
|
324
|
+
right = BigInt(right);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return [left, right];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function commandMapBinaryOperators(
|
|
331
|
+
func : BinaryFunction<number | bigint>,
|
|
332
|
+
GMPFunc : BinaryFunction<any>,
|
|
333
|
+
type : string
|
|
334
|
+
)
|
|
335
|
+
{
|
|
336
|
+
return () =>
|
|
337
|
+
{
|
|
338
|
+
const r = stack.pop();
|
|
339
|
+
const l = stack.pop();
|
|
340
|
+
|
|
341
|
+
if (typeof l === "bigint" && typeof r === "bigint")
|
|
342
|
+
{
|
|
343
|
+
stack.push(func(l, r));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (isGFloat(l) && isGFloat(r))
|
|
347
|
+
{
|
|
348
|
+
stack.push(GMPFunc(l, r));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
stack.push(l);
|
|
353
|
+
stack.push(r);
|
|
354
|
+
let [left, right] : Array<any> = BinaryOperator(type);
|
|
355
|
+
|
|
356
|
+
if (left === null || left === undefined || right === null || right === undefined)
|
|
357
|
+
{
|
|
358
|
+
stack.push(func(left, right));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const result = isGFloat(left) || isGFloat(right) ? GMPFunc(left, right) : func(left, right);
|
|
363
|
+
stack.push(result);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const methodCache = new WeakMap<object, Record<string, any>>();
|
|
368
|
+
function collectMethods(classDefinition: any): Record<string, any>
|
|
369
|
+
{
|
|
370
|
+
if (methodCache.has(classDefinition))
|
|
371
|
+
return methodCache.get(classDefinition)!;
|
|
372
|
+
|
|
373
|
+
let methods: Record<string, any> = {};
|
|
374
|
+
if (classDefinition.superclass)
|
|
375
|
+
{
|
|
376
|
+
try
|
|
377
|
+
{
|
|
378
|
+
const superDef = currentScope.get(classDefinition.superclass);
|
|
379
|
+
if (superDef?.type === "class")
|
|
380
|
+
methods = { ...collectMethods(superDef) };
|
|
381
|
+
}
|
|
382
|
+
catch {}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const method of Object.values(classDefinition.methods ?? {}) as any[])
|
|
386
|
+
methods[method.name] = method;
|
|
387
|
+
|
|
388
|
+
methodCache.set(classDefinition, methods);
|
|
389
|
+
return methods;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function evaluateDefault(defaultBytecode : Bytecode): any
|
|
393
|
+
{
|
|
394
|
+
const savedPointer = pointer;
|
|
395
|
+
const savedStack = [...stack];
|
|
396
|
+
const savedBytecode = activeBytecode;
|
|
397
|
+
const savedScope = currentScope;
|
|
398
|
+
|
|
399
|
+
stack = [];
|
|
400
|
+
pointer = 0;
|
|
401
|
+
activeBytecode = defaultBytecode;
|
|
402
|
+
|
|
403
|
+
while (pointer < defaultBytecode.length)
|
|
404
|
+
{
|
|
405
|
+
const operator = defaultBytecode[pointer++];
|
|
406
|
+
if (typeof operator === "number" && commands[operator])
|
|
407
|
+
commands[operator]!(defaultBytecode);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const result = stack.pop();
|
|
411
|
+
|
|
412
|
+
stack = savedStack;
|
|
413
|
+
pointer = savedPointer;
|
|
414
|
+
activeBytecode = savedBytecode;
|
|
415
|
+
currentScope = savedScope;
|
|
416
|
+
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export class GFloat
|
|
421
|
+
{
|
|
422
|
+
readonly inner: any;
|
|
423
|
+
constructor(inner: any)
|
|
424
|
+
{
|
|
425
|
+
this.inner = inner;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export class Scope
|
|
430
|
+
{
|
|
431
|
+
variables : Map<string, any>;
|
|
432
|
+
slotMap : Map<string, number>;
|
|
433
|
+
slots : any[];
|
|
434
|
+
parent : Scope | undefined;
|
|
435
|
+
|
|
436
|
+
constructor(parent : Scope | undefined = undefined)
|
|
437
|
+
{
|
|
438
|
+
this.variables = new Map();
|
|
439
|
+
this.slotMap = new Map(); // fast lookup
|
|
440
|
+
this.slots = [];
|
|
441
|
+
this.parent = parent;
|
|
442
|
+
|
|
443
|
+
if (!parent)
|
|
444
|
+
{
|
|
445
|
+
this.declare("true");
|
|
446
|
+
this.set("true", true);
|
|
447
|
+
|
|
448
|
+
this.declare("false");
|
|
449
|
+
this.set("false", false);
|
|
450
|
+
|
|
451
|
+
this.declare("null");
|
|
452
|
+
this.set("null", null);
|
|
453
|
+
|
|
454
|
+
this.declare("undefined");
|
|
455
|
+
this.set("undefined", undefined);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
declare(name: string) : void
|
|
460
|
+
{
|
|
461
|
+
if (this.variables.has(name))
|
|
462
|
+
errorTemplate(`declare`, `identifier "${name}" has already been declared in this scope`);
|
|
463
|
+
|
|
464
|
+
const slotIndex = this.slots.length;
|
|
465
|
+
|
|
466
|
+
this.slots.push(undefined); // push the default value
|
|
467
|
+
this.slotMap.set(name, slotIndex);
|
|
468
|
+
|
|
469
|
+
this.variables.set(name, undefined);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
get(name: string) : any
|
|
473
|
+
{
|
|
474
|
+
let scope: Scope | undefined = this;
|
|
475
|
+
while (scope)
|
|
476
|
+
{
|
|
477
|
+
const slot = scope.slotMap.get(name);
|
|
478
|
+
if (slot !== undefined)
|
|
479
|
+
return scope.slots[slot];
|
|
480
|
+
|
|
481
|
+
scope = scope.parent;
|
|
482
|
+
}
|
|
483
|
+
errorTemplate("get", `"${name}" is not defined`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
set(name: string, value: any): void
|
|
487
|
+
{
|
|
488
|
+
let scope: Scope | undefined = this;
|
|
489
|
+
while (scope)
|
|
490
|
+
{
|
|
491
|
+
const slot = scope.slotMap.get(name);
|
|
492
|
+
if (slot !== undefined)
|
|
493
|
+
{
|
|
494
|
+
scope.slots[slot] = value;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
scope = scope.parent;
|
|
498
|
+
}
|
|
499
|
+
errorTemplate("set", `"${name}" is not defined`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function declareVariable(name: string) : void {return currentScope.declare(name);}
|
|
504
|
+
function setVariable(name: string, value: any) : void {isConstant(name); currentScope.set(name, value);}
|
|
505
|
+
function getVariable(name: string) : any {return currentScope.get(name);}
|
|
506
|
+
|
|
507
|
+
function checkParameterType(parameterName: string, typeAnnotation: string | null, value: any): void
|
|
508
|
+
{
|
|
509
|
+
if (!typeAnnotation)
|
|
510
|
+
return;
|
|
511
|
+
|
|
512
|
+
// bigint and dfloat are numeric siblings IMO :D
|
|
513
|
+
const numericCompat = (expected: string, actual: string) =>
|
|
514
|
+
(expected === "float" && actual === "bigint") ||
|
|
515
|
+
(expected === "bigint" && actual === "float");
|
|
516
|
+
|
|
517
|
+
// Array<T, ...>
|
|
518
|
+
const arrayMatch = typeAnnotation.match(/^Array<(.+)>$/);
|
|
519
|
+
if (arrayMatch)
|
|
520
|
+
{
|
|
521
|
+
const allowed = arrayMatch[1].split(",").map(s => s.trim());
|
|
522
|
+
const valueType = syncFunctions.typeof([], () => "", value);
|
|
523
|
+
if (!allowed.includes(valueType) && !allowed.some(t => numericCompat(t, valueType)))
|
|
524
|
+
errorTemplate("checkParameterType", `type mismatch for parameter "${parameterName}", expected one of [${allowed.join(", ")}], got ${valueType}`);
|
|
525
|
+
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Tuple<T, ...>
|
|
530
|
+
const tupleMatch = typeAnnotation.match(/^Tuple<(.+)>$/);
|
|
531
|
+
if (tupleMatch)
|
|
532
|
+
{
|
|
533
|
+
const expected = tupleMatch[1].split(",").map(s => s.trim());
|
|
534
|
+
if (!Array.isArray(value))
|
|
535
|
+
{
|
|
536
|
+
const valueType = syncFunctions.typeof([], () => "", value);
|
|
537
|
+
errorTemplate("checkParameterType", `type mismatch for parameter "${parameterName}", expected Tuple, got ${valueType}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (value.length !== expected.length)
|
|
541
|
+
errorTemplate("checkParameterType", `tuple length mismatch for parameter "${parameterName}", expected ${expected.length} elements, got ${value.length}`);
|
|
542
|
+
|
|
543
|
+
expected.forEach((type: string, i: number) =>
|
|
544
|
+
{
|
|
545
|
+
const elementType = syncFunctions.typeof([], () => "", value[i]);
|
|
546
|
+
if (elementType !== type && !numericCompat(type, elementType))
|
|
547
|
+
errorTemplate("checkParameterType", `tuple element ${i} mismatch for parameter "${parameterName}", expected ${type}, got ${elementType}`);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const valueType = syncFunctions.typeof([], () => "", value);
|
|
554
|
+
if (valueType !== typeAnnotation && !numericCompat(typeAnnotation, valueType))
|
|
555
|
+
errorTemplate("checkParameterType", `type mismatch for parameter "${parameterName}", expected ${typeAnnotation}, got ${valueType}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function checkReturnType(functionName : string | null, returnType : string | null, value : any) : void
|
|
559
|
+
{
|
|
560
|
+
if (!returnType)
|
|
561
|
+
return;
|
|
562
|
+
|
|
563
|
+
const valueType = syncFunctions.typeof([], () => "", value);
|
|
564
|
+
if (valueType !== returnType)
|
|
565
|
+
errorTemplate("checkParameterType", `return type mismatch in "${functionName ?? "<anonymous>"}", expected ${returnType}, got ${valueType}`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function pushScope() : void {currentScope = new Scope(currentScope)} // make a new scope that is the child of the currentScope
|
|
569
|
+
function popScope() : void
|
|
570
|
+
{
|
|
571
|
+
if (currentScope.parent === undefined)
|
|
572
|
+
errorTemplate(`popScope`, "cannot pop global scope");
|
|
573
|
+
|
|
574
|
+
currentScope = currentScope.parent;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let currentScope : Scope = new Scope();
|
|
578
|
+
|
|
579
|
+
let line : number = 0;
|
|
580
|
+
let column : number = 0;
|
|
581
|
+
|
|
582
|
+
let file : string = "";
|
|
583
|
+
|
|
584
|
+
// these 3 are set in interpret
|
|
585
|
+
let currentBaseDir : string = "";
|
|
586
|
+
let pointer : number = 0;
|
|
587
|
+
let stack : any[] = [];
|
|
588
|
+
|
|
589
|
+
export const getBaseDir = () => currentBaseDir;
|
|
590
|
+
|
|
591
|
+
const importedFiles = new Set(); // used for opcode 26
|
|
592
|
+
|
|
593
|
+
const commands : Array<Function | undefined> =
|
|
594
|
+
[
|
|
595
|
+
undefined,
|
|
596
|
+
|
|
597
|
+
(bytecode : Bytecode) : void =>
|
|
598
|
+
{
|
|
599
|
+
let value : any = next(bytecode);
|
|
600
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value))
|
|
601
|
+
{
|
|
602
|
+
value = BigInt(value);
|
|
603
|
+
stack.push(value);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
else if (typeof value === "string" && /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(value.trim()) && !isNaN(Number(value)))
|
|
608
|
+
value = GF(value);
|
|
609
|
+
|
|
610
|
+
if (value === undefined)
|
|
611
|
+
throw new runtimeErrors.MissingStackTokenError("PUSH");
|
|
612
|
+
|
|
613
|
+
stack.push(value);
|
|
614
|
+
}, // PUSH
|
|
615
|
+
|
|
616
|
+
(bytecode : Bytecode) : void =>
|
|
617
|
+
{
|
|
618
|
+
const name : any = next(bytecode);
|
|
619
|
+
const slot : any = currentScope.slotMap.get(name);
|
|
620
|
+
const result = slot !== undefined ? currentScope.slots[slot] : getVariable(name);
|
|
621
|
+
|
|
622
|
+
if (typeof result === "number")
|
|
623
|
+
stack.push(String(result).includes(".") ? GF(result) : BigInt(result));
|
|
624
|
+
else
|
|
625
|
+
stack.push(result);
|
|
626
|
+
}, // LOAD
|
|
627
|
+
|
|
628
|
+
(bytecode : Bytecode) : void =>
|
|
629
|
+
{
|
|
630
|
+
const name : any = next(bytecode);
|
|
631
|
+
const value : any = stack.pop();
|
|
632
|
+
|
|
633
|
+
if (constants[name])
|
|
634
|
+
errorTemplate("STORE", `cannot reassign constant "${name}"`);
|
|
635
|
+
|
|
636
|
+
const slot : any = currentScope.slotMap.get(name);
|
|
637
|
+
if (slot !== undefined)
|
|
638
|
+
currentScope.slots[slot] = value;
|
|
639
|
+
else
|
|
640
|
+
setVariable(name, value); // fallback
|
|
641
|
+
}, // STORE
|
|
642
|
+
|
|
643
|
+
(bytecode : Bytecode) : void =>
|
|
644
|
+
{
|
|
645
|
+
const name : string = (next(bytecode)) as string;
|
|
646
|
+
declareVariable(name);
|
|
647
|
+
}, // ALLOC, make the value undefined when created
|
|
648
|
+
|
|
649
|
+
commandMapBinaryOperators
|
|
650
|
+
(
|
|
651
|
+
(left, right) => BigInt(left) + BigInt(right),
|
|
652
|
+
(left, right) => new GFloat(left.inner.add(right.inner)),
|
|
653
|
+
"arithmetic"
|
|
654
|
+
), // ADD
|
|
655
|
+
|
|
656
|
+
commandMapBinaryOperators
|
|
657
|
+
(
|
|
658
|
+
(left, right) => BigInt(left) - BigInt(right),
|
|
659
|
+
(left, right) => new GFloat(left.inner.sub(right.inner)),
|
|
660
|
+
"arithmetic"
|
|
661
|
+
), // SUB
|
|
662
|
+
|
|
663
|
+
commandMapBinaryOperators
|
|
664
|
+
(
|
|
665
|
+
(left, right) => BigInt(left) * BigInt(right),
|
|
666
|
+
(left, right) => new GFloat(left.inner.mul(right.inner)),
|
|
667
|
+
"arithmetic"
|
|
668
|
+
), // MUL
|
|
669
|
+
|
|
670
|
+
commandMapBinaryOperators
|
|
671
|
+
(
|
|
672
|
+
(left, right) =>
|
|
673
|
+
{
|
|
674
|
+
if (right === 0n || (isGFloat(right) && right.inner.isZero()))
|
|
675
|
+
throw new runtimeErrors.DivisionByZero(left, right);
|
|
676
|
+
|
|
677
|
+
return BigInt(left) / BigInt(right);
|
|
678
|
+
},
|
|
679
|
+
(left, right) =>
|
|
680
|
+
{
|
|
681
|
+
if (right.inner.isZero())
|
|
682
|
+
throw new runtimeErrors.DivisionByZero(left, right);
|
|
683
|
+
|
|
684
|
+
return new GFloat(left.inner.div(right.inner));
|
|
685
|
+
},
|
|
686
|
+
"arithmetic"
|
|
687
|
+
), // DIV
|
|
688
|
+
|
|
689
|
+
commandMapBinaryOperators
|
|
690
|
+
(
|
|
691
|
+
(left, right) => BigInt(left) % BigInt(right),
|
|
692
|
+
(left, right) => new GFloat(left.inner.fmod(right.inner)),
|
|
693
|
+
"arithmetic"
|
|
694
|
+
), // MOD
|
|
695
|
+
|
|
696
|
+
async (bytecode : Bytecode) : Promise<void> =>
|
|
697
|
+
{
|
|
698
|
+
const callLine : number = line;
|
|
699
|
+
const callColumn : number = column;
|
|
700
|
+
|
|
701
|
+
let rawNextBytecode = next(bytecode);
|
|
702
|
+
if (typeof rawNextBytecode !== "string")
|
|
703
|
+
errorTemplate("CALL", `the name of a function must be a String, got "${rawNextBytecode}"`);
|
|
704
|
+
|
|
705
|
+
const functionName: string = rawNextBytecode as string;
|
|
706
|
+
|
|
707
|
+
rawNextBytecode = next(bytecode);
|
|
708
|
+
if (rawNextBytecode === undefined || rawNextBytecode === null || typeof rawNextBytecode === "string")
|
|
709
|
+
errorTemplate("CALL", `the amount of arguments must be type Float or BigInt, got "${rawNextBytecode}"`);
|
|
710
|
+
|
|
711
|
+
const argumentAmount : PureNumber = rawNextBytecode as PureNumber;
|
|
712
|
+
const args : any[] = [];
|
|
713
|
+
for (let i = 0; i < (argumentAmount as number); i++)
|
|
714
|
+
{
|
|
715
|
+
const value : any = stack.pop();
|
|
716
|
+
if (value?.__spread__)
|
|
717
|
+
args.unshift(...value.value);
|
|
718
|
+
else
|
|
719
|
+
args.unshift(value);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (functionName === "super")
|
|
723
|
+
{
|
|
724
|
+
const superClassName : any = currentScope.get("1345__superClass");
|
|
725
|
+
if (!superClassName)
|
|
726
|
+
errorTemplate("CALL", `"super" called outside of a class constructor`);
|
|
727
|
+
|
|
728
|
+
const superClass : any = currentScope.get(superClassName);
|
|
729
|
+
if (!superClass || superClass.type !== "class")
|
|
730
|
+
errorTemplate("CALL", `superclass "${superClassName}" is not a class`);
|
|
731
|
+
|
|
732
|
+
const instance : any = currentScope.get("this");
|
|
733
|
+
|
|
734
|
+
if (superClass.methods["constructor"])
|
|
735
|
+
{
|
|
736
|
+
const superConstructor : any = superClass.methods["constructor"];
|
|
737
|
+
|
|
738
|
+
const savedPointer : number = pointer;
|
|
739
|
+
const savedScope : Scope = currentScope;
|
|
740
|
+
|
|
741
|
+
currentScope = new Scope(superConstructor.closureScope ?? currentScope);
|
|
742
|
+
currentScope.declare("this");
|
|
743
|
+
currentScope.set("this", instance);
|
|
744
|
+
|
|
745
|
+
const superMethods = collectMethods(superClass);
|
|
746
|
+
for (const method of Object.values(superMethods) as any[])
|
|
747
|
+
{
|
|
748
|
+
if (!instance.methods[method.name] || instance.methods[method.name].bytecode === method.bytecode)
|
|
749
|
+
{
|
|
750
|
+
instance.methods[method.name] =
|
|
751
|
+
{
|
|
752
|
+
...method,
|
|
753
|
+
thisContext: instance
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
superConstructor.parameters.forEach
|
|
759
|
+
(
|
|
760
|
+
(parameter: any, i: number) =>
|
|
761
|
+
{
|
|
762
|
+
declareVariable(parameter.name);
|
|
763
|
+
const value = args[i] !== undefined
|
|
764
|
+
? args[i]
|
|
765
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined);
|
|
766
|
+
checkParameterType(parameter.name, parameter.typeAnnotation, value);
|
|
767
|
+
|
|
768
|
+
setVariable(parameter.name, value);
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
callStack.push
|
|
773
|
+
(
|
|
774
|
+
{
|
|
775
|
+
bytecode : activeBytecode,
|
|
776
|
+
pointer : savedPointer,
|
|
777
|
+
savedScope,
|
|
778
|
+
returnMode : "super",
|
|
779
|
+
functionName : functionName || "<anonymous>",
|
|
780
|
+
file,
|
|
781
|
+
line,
|
|
782
|
+
column
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
activeBytecode = superConstructor.bytecode;
|
|
787
|
+
pointer = 0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
let func = syncFunctions[functionName as keyof typeof syncFunctions]; // as keyof typeof syncFunctions makes TS stop complaining adfjs;lkjf
|
|
794
|
+
if (func)
|
|
795
|
+
{
|
|
796
|
+
const result = func(stack, getTrueValue, ...args);
|
|
797
|
+
|
|
798
|
+
if (result !== undefined)
|
|
799
|
+
stack.push(result);
|
|
800
|
+
else
|
|
801
|
+
stack.push(undefined); // return something
|
|
802
|
+
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
func = asyncFunctions[functionName as keyof typeof asyncFunctions];
|
|
807
|
+
if (func)
|
|
808
|
+
{
|
|
809
|
+
const result = await func(stack, getTrueValue, ...args);
|
|
810
|
+
|
|
811
|
+
if (result !== undefined)
|
|
812
|
+
stack.push(result);
|
|
813
|
+
else
|
|
814
|
+
stack.push(undefined);
|
|
815
|
+
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
let functionObject = undefined;
|
|
820
|
+
if (typeof functionName === "string")
|
|
821
|
+
{
|
|
822
|
+
try {functionObject = currentScope.get(functionName);}
|
|
823
|
+
catch {}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (functionObject === undefined)
|
|
827
|
+
{
|
|
828
|
+
const top : any = stack[stack.length - 1]; // peek
|
|
829
|
+
|
|
830
|
+
if (top !== null && top !== undefined && typeof top === "object" && top.bytecode)
|
|
831
|
+
functionObject = stack.pop();
|
|
832
|
+
else
|
|
833
|
+
throw new runtimeErrors.FunctionError(functionName);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (functionObject.type === "class" && !functionObject.isMethod)
|
|
837
|
+
errorTemplate("CALL", `"${functionObject.name}" is a class and must be instantiated with "inst"`);
|
|
838
|
+
|
|
839
|
+
const savedPointer : number = pointer;
|
|
840
|
+
const savedScope : Scope = currentScope;
|
|
841
|
+
|
|
842
|
+
// if functionObject has a closure scope, use that as the parent for currentScope. Else, use currentScope as the parent for the new Scope Object
|
|
843
|
+
currentScope = new Scope(functionObject.closureScope ?? currentScope);
|
|
844
|
+
|
|
845
|
+
if (functionObject.staticClassName && !functionObject.thisContext)
|
|
846
|
+
{
|
|
847
|
+
const classDefinition = currentScope.get(functionObject.staticClassName);
|
|
848
|
+
const allMethods = collectMethods(classDefinition);
|
|
849
|
+
const hasConstructor = !!classDefinition.methods["constructor"];
|
|
850
|
+
|
|
851
|
+
const init = args[0];
|
|
852
|
+
|
|
853
|
+
const tempInstance : any =
|
|
854
|
+
{
|
|
855
|
+
type : "class",
|
|
856
|
+
methods : {},
|
|
857
|
+
properties : hasConstructor ? {__INIT__: init} : {},
|
|
858
|
+
isInstance : true,
|
|
859
|
+
class : classDefinition.name,
|
|
860
|
+
superclass : classDefinition.superclass
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
for (const method of Object.values(allMethods) as any[])
|
|
864
|
+
{
|
|
865
|
+
tempInstance.methods[method.name] =
|
|
866
|
+
{
|
|
867
|
+
...method,
|
|
868
|
+
thisContext: tempInstance
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
functionObject =
|
|
873
|
+
{
|
|
874
|
+
...functionObject,
|
|
875
|
+
thisContext: tempInstance
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
args.shift();
|
|
879
|
+
|
|
880
|
+
currentScope.declare("this");
|
|
881
|
+
currentScope.set("this", init);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (functionObject.thisContext && !functionObject.staticClassName)
|
|
885
|
+
{
|
|
886
|
+
currentScope.declare("this");
|
|
887
|
+
currentScope.set("this", functionObject.thisContext);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
functionObject.parameters.forEach((parameter : any, i : number) =>
|
|
891
|
+
{
|
|
892
|
+
if (parameter.rest)
|
|
893
|
+
{
|
|
894
|
+
declareVariable(parameter.name);
|
|
895
|
+
|
|
896
|
+
const restArguments = args.slice(i);
|
|
897
|
+
if (parameter.typeAnnotation)
|
|
898
|
+
restArguments.forEach((val: any) => checkParameterType(parameter.name, parameter.typeAnnotation, val));
|
|
899
|
+
|
|
900
|
+
setVariable(parameter.name, restArguments);
|
|
901
|
+
}
|
|
902
|
+
else
|
|
903
|
+
{
|
|
904
|
+
declareVariable(parameter.name);
|
|
905
|
+
|
|
906
|
+
const value = args[i] !== undefined
|
|
907
|
+
? args[i]
|
|
908
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined);
|
|
909
|
+
checkParameterType(parameter.name, parameter.typeAnnotation, value);
|
|
910
|
+
|
|
911
|
+
setVariable(parameter.name, value);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
const resolvedName = functionName || functionObject?.name || "<anonymous>";
|
|
916
|
+
callStack.push
|
|
917
|
+
(
|
|
918
|
+
{
|
|
919
|
+
bytecode : activeBytecode,
|
|
920
|
+
pointer : savedPointer,
|
|
921
|
+
savedScope,
|
|
922
|
+
returnMode : "function",
|
|
923
|
+
returnType : functionObject.returnType ?? null,
|
|
924
|
+
functionName : resolvedName,
|
|
925
|
+
file,
|
|
926
|
+
line : callLine,
|
|
927
|
+
column : callColumn
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
if (functionObject.definedFile)
|
|
932
|
+
file = functionObject.definedFile;
|
|
933
|
+
activeBytecode = functionObject.bytecode;
|
|
934
|
+
pointer = 0;
|
|
935
|
+
}, // CALL
|
|
936
|
+
|
|
937
|
+
() : void =>
|
|
938
|
+
{
|
|
939
|
+
let value = unwrapInstance(stack.pop());
|
|
940
|
+
if (isGFloat(value))
|
|
941
|
+
{
|
|
942
|
+
simpleStack(new GFloat(value.inner.neg()));
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
simpleStack(-value);
|
|
946
|
+
}, // NEG
|
|
947
|
+
|
|
948
|
+
() : void =>
|
|
949
|
+
{
|
|
950
|
+
let value = unwrapInstance(stack.pop());
|
|
951
|
+
if (isGFloat(value))
|
|
952
|
+
value = value.inner.isZero();
|
|
953
|
+
|
|
954
|
+
stack.push(value);
|
|
955
|
+
}, // NOT
|
|
956
|
+
|
|
957
|
+
() : void =>
|
|
958
|
+
{
|
|
959
|
+
let value = unwrapInstance(stack.pop());
|
|
960
|
+
if (isGFloat(value))
|
|
961
|
+
value = BigInt(value.inner.toFixed(0)); // ~ can't take floats
|
|
962
|
+
|
|
963
|
+
simpleStack(~value);
|
|
964
|
+
}, // BITNOT
|
|
965
|
+
|
|
966
|
+
(bytecode : Bytecode) : void =>
|
|
967
|
+
{
|
|
968
|
+
const target : any = next(bytecode);
|
|
969
|
+
if (typeof target !== "number")
|
|
970
|
+
throw new runtimeErrors.InternalError(`target after opcode 14 (JMP) should be type "Number" but got "${target}"`);
|
|
971
|
+
|
|
972
|
+
pointer = target;
|
|
973
|
+
}, // JMP
|
|
974
|
+
|
|
975
|
+
(bytecode : Bytecode) : void =>
|
|
976
|
+
{
|
|
977
|
+
const target : any = next(bytecode);
|
|
978
|
+
if (typeof target !== "number")
|
|
979
|
+
throw new runtimeErrors.InternalError(`target after opcode 15 (JZ) should be type "Number" but got "${target}"`);
|
|
980
|
+
|
|
981
|
+
const raw : any = stack.pop();
|
|
982
|
+
const value : any = getTrueValue(raw);
|
|
983
|
+
if (!value || value === "undefined" || value === "null") pointer = target; // skip the loop or statement if the condition is false
|
|
984
|
+
}, // JZ
|
|
985
|
+
|
|
986
|
+
commandMapBinaryOperators((l, r) => l === r, (l, r) => l.inner.isEqual(r.inner), "comparison"), // EQ
|
|
987
|
+
commandMapBinaryOperators((l, r) => l !== r, (l, r) => !l.inner.isEqual(r.inner), "comparison"), // NE
|
|
988
|
+
commandMapBinaryOperators((l, r) => l > r, (l, r) => l.inner.greaterThan(r.inner), "comparison"), // GT
|
|
989
|
+
commandMapBinaryOperators((l, r) => l < r, (l, r) => l.inner.lessThan(r.inner), "comparison"), // LT
|
|
990
|
+
commandMapBinaryOperators((l, r) => l >= r, (l, r) => l.inner.greaterOrEqual(r.inner), "comparison"), // GTE
|
|
991
|
+
commandMapBinaryOperators((l, r) => l <= r, (l, r) => l.inner.lessOrEqual(r.inner), "comparison"), // LTE
|
|
992
|
+
|
|
993
|
+
() : void =>
|
|
994
|
+
{
|
|
995
|
+
if (stack.length === 0)
|
|
996
|
+
throw new runtimeErrors.StackError();
|
|
997
|
+
stack.pop()
|
|
998
|
+
}, // POP
|
|
999
|
+
|
|
1000
|
+
() : void => pushScope(), // PUSHSCP
|
|
1001
|
+
() : void => popScope(), // POPSCP
|
|
1002
|
+
|
|
1003
|
+
() : void =>
|
|
1004
|
+
{
|
|
1005
|
+
throw new runtimeErrors.InternalError(`found opcode 25 (RETURN) that wasn't handled by opcode 10 (CALL)`);
|
|
1006
|
+
}, // RETURN (handled in CALL/opcode 10)
|
|
1007
|
+
|
|
1008
|
+
async () : Promise<void> =>
|
|
1009
|
+
{
|
|
1010
|
+
let absolutePath : any = stack.pop();
|
|
1011
|
+
if (typeof absolutePath !== "string")
|
|
1012
|
+
errorTemplate("EXEC", `path after keyword "import" must be type String, got "${absolutePath}"`);
|
|
1013
|
+
|
|
1014
|
+
absolutePath = path.resolve(currentBaseDir, absolutePath);
|
|
1015
|
+
|
|
1016
|
+
if (importedFiles.
|
|
1017
|
+
has(absolutePath)) return;
|
|
1018
|
+
importedFiles.add(absolutePath);
|
|
1019
|
+
let fileContent : string;
|
|
1020
|
+
|
|
1021
|
+
fileContent = fs.readFileSync(absolutePath, "utf8");
|
|
1022
|
+
|
|
1023
|
+
if (!fileContent?.trim())
|
|
1024
|
+
{
|
|
1025
|
+
console.warn(`Warning: imported file "${absolutePath}" is empty`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const cacheDirectory = path.join(
|
|
1030
|
+
process.env.LOCALAPPDATA || // Windows
|
|
1031
|
+
process.env.HOME && path.join(process.env.HOME, ".cache") || // MacOS/Linux
|
|
1032
|
+
process.cwd(),
|
|
1033
|
+
"pure-dango", ".pdbccache"
|
|
1034
|
+
);
|
|
1035
|
+
let importedBytecode = loadBytecode(cacheDirectory, absolutePath);
|
|
1036
|
+
|
|
1037
|
+
if (!importedBytecode)
|
|
1038
|
+
{
|
|
1039
|
+
const tokens = tokenizer(fileContent);
|
|
1040
|
+
const ast = parser(tokens);
|
|
1041
|
+
importedBytecode = buildBytecode(ast, absolutePath);
|
|
1042
|
+
|
|
1043
|
+
const srcMTime = fs.statSync(absolutePath).mtimeMs;
|
|
1044
|
+
saveBytecode(cacheDirectory, importedBytecode, absolutePath, srcMTime);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const savedBaseDir = currentBaseDir;
|
|
1048
|
+
|
|
1049
|
+
callStack.push
|
|
1050
|
+
(
|
|
1051
|
+
{
|
|
1052
|
+
bytecode : activeBytecode,
|
|
1053
|
+
pointer : pointer,
|
|
1054
|
+
savedScope : currentScope,
|
|
1055
|
+
returnMode : "execute",
|
|
1056
|
+
functionName : absolutePath,
|
|
1057
|
+
file,
|
|
1058
|
+
importer : file,
|
|
1059
|
+
line,
|
|
1060
|
+
column,
|
|
1061
|
+
savedBaseDir
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
activeBytecode = importedBytecode;
|
|
1066
|
+
pointer = 0;
|
|
1067
|
+
currentBaseDir = path.dirname(absolutePath);
|
|
1068
|
+
}, // EXEC
|
|
1069
|
+
|
|
1070
|
+
() : void =>
|
|
1071
|
+
{
|
|
1072
|
+
const template : any = stack.pop(); // get the current function
|
|
1073
|
+
stack.push
|
|
1074
|
+
(
|
|
1075
|
+
{
|
|
1076
|
+
...template,
|
|
1077
|
+
closureScope: currentScope, // add closureScope to know the function's scope
|
|
1078
|
+
definedFile: file // track which source file defined this function
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
1081
|
+
}, // MKFUNC
|
|
1082
|
+
|
|
1083
|
+
(bytecode: Bytecode) : void =>
|
|
1084
|
+
{
|
|
1085
|
+
const count : number = next(bytecode) as number;
|
|
1086
|
+
const elements : any[] = [];
|
|
1087
|
+
|
|
1088
|
+
for (let i = 0; i < count; i++)
|
|
1089
|
+
{
|
|
1090
|
+
const value = stack.pop();
|
|
1091
|
+
if (value?.__spread__)
|
|
1092
|
+
elements.unshift(...value.value);
|
|
1093
|
+
else
|
|
1094
|
+
elements.unshift(value);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
stack.push(elements);
|
|
1098
|
+
}, // MKARR
|
|
1099
|
+
|
|
1100
|
+
() : void =>
|
|
1101
|
+
{
|
|
1102
|
+
const index : any = getTrueValue(stack.pop());
|
|
1103
|
+
const array : any = stack.pop();
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
if (typeof array === "string")
|
|
1107
|
+
{
|
|
1108
|
+
if (typeof index !== "bigint" && !isGFloat(index) && typeof index !== "number")
|
|
1109
|
+
errorTemplate("ARRGET", `String index must be a Float or BigInt, got "${index}"`);
|
|
1110
|
+
|
|
1111
|
+
let idx : number;
|
|
1112
|
+
if (isGFloat(index))
|
|
1113
|
+
idx = parseInt(index.inner.toFixed(0), 10);
|
|
1114
|
+
else
|
|
1115
|
+
idx = Number(index);
|
|
1116
|
+
|
|
1117
|
+
const character = array[idx] ?? null;
|
|
1118
|
+
stack.push(character !== null ? character : null);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (array?.type === "class")
|
|
1123
|
+
{
|
|
1124
|
+
const key = String(index);
|
|
1125
|
+
|
|
1126
|
+
if (array.properties && Object.prototype.hasOwnProperty.call(array.properties, key))
|
|
1127
|
+
{
|
|
1128
|
+
stack.push(array.properties[key]);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const methods = array.isInstance ? array.methods : collectMethods(array);
|
|
1133
|
+
|
|
1134
|
+
if (array.methods === undefined)
|
|
1135
|
+
throw new runtimeErrors.ClassError(array?.name);
|
|
1136
|
+
|
|
1137
|
+
if (!Object.prototype.hasOwnProperty.call(methods, key))
|
|
1138
|
+
throw new runtimeErrors.MethodError(key);
|
|
1139
|
+
|
|
1140
|
+
const method = methods[key];
|
|
1141
|
+
stack.push
|
|
1142
|
+
(
|
|
1143
|
+
{
|
|
1144
|
+
...method,
|
|
1145
|
+
isMethod : true,
|
|
1146
|
+
parameters : method.parameters,
|
|
1147
|
+
staticClassName : array.isInstance ? null : array.name
|
|
1148
|
+
}
|
|
1149
|
+
);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (array?.type === "object")
|
|
1154
|
+
{
|
|
1155
|
+
stack.push(array.value[String(index)] ?? undefined);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (!Array.isArray(array) && typeof array !== "string")
|
|
1160
|
+
errorTemplate("ARRGET", `cannot index non-array value, got "${array}"`);
|
|
1161
|
+
if (typeof index !== "bigint" && !isGFloat(index))
|
|
1162
|
+
errorTemplate("ARRGET", `Array index must be a Float or BigInt, got "${index}"`);
|
|
1163
|
+
|
|
1164
|
+
const result = array[isGFloat(index) ? parseInt(index.inner.toFixed(0), 10) : Number(index)] ?? undefined;
|
|
1165
|
+
stack.push(result);
|
|
1166
|
+
}, // ARRGET
|
|
1167
|
+
|
|
1168
|
+
() : void =>
|
|
1169
|
+
{
|
|
1170
|
+
const index : any = getTrueValue(stack.pop());
|
|
1171
|
+
const array : any = getTrueValue(stack.pop());
|
|
1172
|
+
const value : any = stack.pop();
|
|
1173
|
+
const key = String(index);
|
|
1174
|
+
|
|
1175
|
+
if (array?.type === "class")
|
|
1176
|
+
{
|
|
1177
|
+
if (array.isInstance)
|
|
1178
|
+
array.properties[key] = value;
|
|
1179
|
+
else
|
|
1180
|
+
{
|
|
1181
|
+
if (!array.properties)
|
|
1182
|
+
array.properties = {};
|
|
1183
|
+
array.properties[key] = value;
|
|
1184
|
+
}
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if (array?.type === "object")
|
|
1189
|
+
{
|
|
1190
|
+
array.value[key] = value;
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (!Array.isArray(array))
|
|
1195
|
+
errorTemplate("ARRSET", `cannot index non-array value, got "${array}"`);
|
|
1196
|
+
if (typeof index !== "bigint" && !isGFloat(index))
|
|
1197
|
+
errorTemplate("ARRSET", `Array index must be a Float or BigInt, got "${index}"`);
|
|
1198
|
+
|
|
1199
|
+
array[isGFloat(index) ? index.inner.toNumber() : Number(index)] = value;
|
|
1200
|
+
}, // ARRSET
|
|
1201
|
+
|
|
1202
|
+
(bytecode: Bytecode) : void =>
|
|
1203
|
+
{
|
|
1204
|
+
const count : number = next(bytecode) as number;
|
|
1205
|
+
const object : Record<string, any> =
|
|
1206
|
+
{
|
|
1207
|
+
type: "object",
|
|
1208
|
+
value: {}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
for (let i = 0; i < count; i++)
|
|
1212
|
+
{
|
|
1213
|
+
const value : any = stack.pop();
|
|
1214
|
+
const key : any = stack.pop();
|
|
1215
|
+
|
|
1216
|
+
object.value[String(key)] = value;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
stack.push(object);
|
|
1220
|
+
}, // MKOBJ
|
|
1221
|
+
|
|
1222
|
+
() : void =>
|
|
1223
|
+
{
|
|
1224
|
+
const descriptor : any = stack.pop();
|
|
1225
|
+
|
|
1226
|
+
const classObject : {type : "class", name : string, superclass : Record<string, any>, methods : any[]} =
|
|
1227
|
+
{
|
|
1228
|
+
type : "class",
|
|
1229
|
+
name : descriptor.name,
|
|
1230
|
+
superclass : descriptor.superclass,
|
|
1231
|
+
methods : descriptor.methods
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
stack.push(classObject);
|
|
1235
|
+
}, // MKCLASS
|
|
1236
|
+
|
|
1237
|
+
async (bytecode : Bytecode) : Promise<void> =>
|
|
1238
|
+
{
|
|
1239
|
+
const className : any = next(bytecode);
|
|
1240
|
+
const argumentCount : any = next(bytecode);
|
|
1241
|
+
|
|
1242
|
+
const args : any[] = [];
|
|
1243
|
+
for (let i = 0; i < argumentCount; i++)
|
|
1244
|
+
args.unshift(stack.pop());
|
|
1245
|
+
|
|
1246
|
+
const classDefinition : any = currentScope.get(className);
|
|
1247
|
+
if (!classDefinition || classDefinition.type !== "class")
|
|
1248
|
+
errorTemplate("MKINST", `"${className}" is not a class`);
|
|
1249
|
+
|
|
1250
|
+
const instance : {type : string, methods : Record<string, any>, properties : Record<string, any>, isInstance : boolean, class : string, superclass : any} =
|
|
1251
|
+
{
|
|
1252
|
+
type : "class",
|
|
1253
|
+
methods : {},
|
|
1254
|
+
properties : {},
|
|
1255
|
+
isInstance : true,
|
|
1256
|
+
class : classDefinition.name,
|
|
1257
|
+
superclass : classDefinition.superclass
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const allMethods = collectMethods(classDefinition);
|
|
1261
|
+
|
|
1262
|
+
for (const method of Object.values(allMethods) as any[])
|
|
1263
|
+
{
|
|
1264
|
+
instance.methods[method.name] =
|
|
1265
|
+
{
|
|
1266
|
+
...method,
|
|
1267
|
+
thisContext : instance
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
if (Object.prototype.hasOwnProperty.call(classDefinition.methods, "constructor"))
|
|
1272
|
+
{
|
|
1273
|
+
const constructor : any = classDefinition.methods["constructor"];
|
|
1274
|
+
|
|
1275
|
+
const savedPointer : number = pointer;
|
|
1276
|
+
const savedScope : Scope = currentScope;
|
|
1277
|
+
|
|
1278
|
+
currentScope = new Scope(constructor.closureScope ?? currentScope);
|
|
1279
|
+
currentScope.declare("this");
|
|
1280
|
+
currentScope.set("this", instance);
|
|
1281
|
+
|
|
1282
|
+
if (classDefinition.superclass)
|
|
1283
|
+
{
|
|
1284
|
+
currentScope.declare("1345__superClass");
|
|
1285
|
+
currentScope.set("1345__superClass", classDefinition.superclass);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
(constructor.parameters ?? [])
|
|
1289
|
+
.forEach
|
|
1290
|
+
(
|
|
1291
|
+
(parameter: any, i: number) =>
|
|
1292
|
+
{
|
|
1293
|
+
if (parameter.rest)
|
|
1294
|
+
{
|
|
1295
|
+
declareVariable(parameter.name);
|
|
1296
|
+
|
|
1297
|
+
const restArguments = args.slice(i);
|
|
1298
|
+
if (parameter.typeAnnotation)
|
|
1299
|
+
restArguments.forEach((value: any) => checkParameterType(parameter.name, parameter.typeAnnotation, value));
|
|
1300
|
+
|
|
1301
|
+
setVariable(parameter.name, restArguments);
|
|
1302
|
+
}
|
|
1303
|
+
else
|
|
1304
|
+
{
|
|
1305
|
+
declareVariable(parameter.name);
|
|
1306
|
+
|
|
1307
|
+
const value = args[i] !== undefined
|
|
1308
|
+
? args[i]
|
|
1309
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined);
|
|
1310
|
+
checkParameterType(parameter.name, parameter.typeAnnotation, value);
|
|
1311
|
+
|
|
1312
|
+
setVariable(parameter.name, value);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
callStack.push
|
|
1318
|
+
(
|
|
1319
|
+
{
|
|
1320
|
+
bytecode : activeBytecode,
|
|
1321
|
+
pointer : savedPointer,
|
|
1322
|
+
savedScope,
|
|
1323
|
+
returnMode : "constructor",
|
|
1324
|
+
instance,
|
|
1325
|
+
functionName : `inst ${className}`,
|
|
1326
|
+
file,
|
|
1327
|
+
line,
|
|
1328
|
+
column
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1331
|
+
|
|
1332
|
+
activeBytecode = constructor.bytecode;
|
|
1333
|
+
pointer = 0;
|
|
1334
|
+
}
|
|
1335
|
+
else
|
|
1336
|
+
stack.push(instance);
|
|
1337
|
+
}, // MKINST
|
|
1338
|
+
|
|
1339
|
+
(bytecode: Bytecode) : void =>
|
|
1340
|
+
{
|
|
1341
|
+
line = next(bytecode) as number;
|
|
1342
|
+
column = next(bytecode) as number;
|
|
1343
|
+
}, // SETLINE
|
|
1344
|
+
|
|
1345
|
+
(bytecode: Bytecode) : void =>
|
|
1346
|
+
{
|
|
1347
|
+
file = next(bytecode) as string;
|
|
1348
|
+
}, // SETFILE
|
|
1349
|
+
|
|
1350
|
+
() : void =>
|
|
1351
|
+
{
|
|
1352
|
+
const array : any = getTrueValue(stack.pop());
|
|
1353
|
+
if (!Array.isArray(array))
|
|
1354
|
+
errorTemplate("SPREAD", `spread operator an only be used on arrays`);
|
|
1355
|
+
stack.push
|
|
1356
|
+
(
|
|
1357
|
+
{
|
|
1358
|
+
__spread__: true,
|
|
1359
|
+
value: array
|
|
1360
|
+
}
|
|
1361
|
+
)
|
|
1362
|
+
}, // SPREAD
|
|
1363
|
+
|
|
1364
|
+
async (bytecode: Bytecode) : Promise<void> =>
|
|
1365
|
+
{
|
|
1366
|
+
const argumentCount : number = next(bytecode) as number;
|
|
1367
|
+
|
|
1368
|
+
const args : any[] = [];
|
|
1369
|
+
for (let i = 0; i < argumentCount; i++)
|
|
1370
|
+
{
|
|
1371
|
+
const value = stack.pop();
|
|
1372
|
+
if (value?.__spread__)
|
|
1373
|
+
args.unshift(...value.value);
|
|
1374
|
+
else
|
|
1375
|
+
args.unshift(value);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const property : string = stack.pop() as string;
|
|
1379
|
+
const object : any = stack.pop();
|
|
1380
|
+
const key : string = String(property); // define key
|
|
1381
|
+
|
|
1382
|
+
let functionObject : any;
|
|
1383
|
+
|
|
1384
|
+
if (object?.type === "class")
|
|
1385
|
+
{
|
|
1386
|
+
if (object.properties && Object.prototype.hasOwnProperty.call(object.properties, key))
|
|
1387
|
+
functionObject = object.properties[key];
|
|
1388
|
+
else
|
|
1389
|
+
{
|
|
1390
|
+
const methods = object.isInstance ? object.methods : collectMethods(object);
|
|
1391
|
+
if (!Object.prototype.hasOwnProperty.call(methods, key))
|
|
1392
|
+
throw new runtimeErrors.MethodError(key);
|
|
1393
|
+
|
|
1394
|
+
const method = methods[key];
|
|
1395
|
+
functionObject =
|
|
1396
|
+
{
|
|
1397
|
+
...method,
|
|
1398
|
+
isMethod : true,
|
|
1399
|
+
staticClassName : object.isInstance ? null : object.name,
|
|
1400
|
+
thisContext : object.isInstance ? object : null
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
else
|
|
1405
|
+
throw new runtimeErrors.MethodError(key);
|
|
1406
|
+
|
|
1407
|
+
const savedPointer = pointer;
|
|
1408
|
+
const savedScope = currentScope;
|
|
1409
|
+
|
|
1410
|
+
currentScope = new Scope(functionObject.closureScope ?? currentScope);
|
|
1411
|
+
|
|
1412
|
+
if (functionObject.staticClassName && !functionObject.thisContext)
|
|
1413
|
+
{
|
|
1414
|
+
const classDefinition = currentScope.get(functionObject.staticClassName);
|
|
1415
|
+
const allMethods = collectMethods(classDefinition);
|
|
1416
|
+
const hasConstructor = !!classDefinition.methods["constructor"];
|
|
1417
|
+
|
|
1418
|
+
const init = args[0];
|
|
1419
|
+
|
|
1420
|
+
const tempInstance : any =
|
|
1421
|
+
{
|
|
1422
|
+
type : "class",
|
|
1423
|
+
methods : {},
|
|
1424
|
+
properties : hasConstructor ? {__INIT__: init} : {},
|
|
1425
|
+
isInstance : true,
|
|
1426
|
+
class : classDefinition.name,
|
|
1427
|
+
superclass : classDefinition.superclass
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
for (const method of Object.values(allMethods) as any[])
|
|
1431
|
+
tempInstance.methods[method.name] = { ...method, thisContext: tempInstance };
|
|
1432
|
+
|
|
1433
|
+
functionObject = { ...functionObject, thisContext: tempInstance };
|
|
1434
|
+
|
|
1435
|
+
if (hasConstructor)
|
|
1436
|
+
{
|
|
1437
|
+
const constructor = classDefinition.methods["constructor"];
|
|
1438
|
+
const constructorArg = args[0];
|
|
1439
|
+
args.shift();
|
|
1440
|
+
|
|
1441
|
+
currentScope.declare("this");
|
|
1442
|
+
currentScope.set("this", tempInstance);
|
|
1443
|
+
|
|
1444
|
+
if (classDefinition.superclass)
|
|
1445
|
+
{
|
|
1446
|
+
currentScope.declare("1345__superClass");
|
|
1447
|
+
currentScope.set("1345__superClass", classDefinition.superclass);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
constructor.parameters.forEach((parameter: any, i: number) =>
|
|
1451
|
+
{
|
|
1452
|
+
currentScope.declare(parameter.name);
|
|
1453
|
+
currentScope.set(parameter.name, i === 0 ? constructorArg : undefined);
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
// push constructor frame on top (runs first)
|
|
1457
|
+
callStack.push
|
|
1458
|
+
(
|
|
1459
|
+
{
|
|
1460
|
+
bytecode : constructor.bytecode,
|
|
1461
|
+
pointer : 0,
|
|
1462
|
+
savedScope : currentScope,
|
|
1463
|
+
returnMode : "constructor",
|
|
1464
|
+
instance : tempInstance,
|
|
1465
|
+
functionName : `inst ${functionObject.staticClassName}`,
|
|
1466
|
+
file,
|
|
1467
|
+
|
|
1468
|
+
pendingMethod :
|
|
1469
|
+
{
|
|
1470
|
+
methodBytecode : functionObject.bytecode,
|
|
1471
|
+
methodKey : key,
|
|
1472
|
+
methodArgs : args,
|
|
1473
|
+
methodParameters : functionObject.parameters,
|
|
1474
|
+
methodScope : new Scope(functionObject.closureScope ?? savedScope),
|
|
1475
|
+
returnBytecode : activeBytecode,
|
|
1476
|
+
returnPointer : savedPointer,
|
|
1477
|
+
returnScope : savedScope,
|
|
1478
|
+
isStaticCall: true
|
|
1479
|
+
},
|
|
1480
|
+
|
|
1481
|
+
line,
|
|
1482
|
+
column
|
|
1483
|
+
}
|
|
1484
|
+
);
|
|
1485
|
+
|
|
1486
|
+
activeBytecode = constructor.bytecode;
|
|
1487
|
+
pointer = 0;
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
args.shift();
|
|
1492
|
+
|
|
1493
|
+
currentScope.declare("this");
|
|
1494
|
+
currentScope.set("this", init);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
if (functionObject.thisContext && !functionObject.staticClassName)
|
|
1498
|
+
{
|
|
1499
|
+
currentScope.declare("this");
|
|
1500
|
+
currentScope.set("this", functionObject.thisContext);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
functionObject.parameters.forEach
|
|
1504
|
+
(
|
|
1505
|
+
(parameter: any, i: number) =>
|
|
1506
|
+
{
|
|
1507
|
+
if (parameter.rest)
|
|
1508
|
+
{
|
|
1509
|
+
declareVariable(parameter.name);
|
|
1510
|
+
|
|
1511
|
+
const restArguments = args.slice(i);
|
|
1512
|
+
if (parameter.typeAnnotation)
|
|
1513
|
+
restArguments.forEach((val: any) => checkParameterType(parameter.name, parameter.typeAnnotation, val));
|
|
1514
|
+
|
|
1515
|
+
setVariable(parameter.name, restArguments);
|
|
1516
|
+
}
|
|
1517
|
+
else
|
|
1518
|
+
{
|
|
1519
|
+
declareVariable(parameter.name);
|
|
1520
|
+
const value = args[i] !== undefined
|
|
1521
|
+
? args[i]
|
|
1522
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined);
|
|
1523
|
+
checkParameterType(parameter.name, parameter.typeAnnotation, value);
|
|
1524
|
+
|
|
1525
|
+
setVariable(parameter.name, value);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
);
|
|
1529
|
+
|
|
1530
|
+
callStack.push
|
|
1531
|
+
(
|
|
1532
|
+
{
|
|
1533
|
+
bytecode : activeBytecode,
|
|
1534
|
+
pointer : savedPointer,
|
|
1535
|
+
savedScope,
|
|
1536
|
+
returnMode : "function",
|
|
1537
|
+
returnType : functionObject.returnType ?? null,
|
|
1538
|
+
functionName : key,
|
|
1539
|
+
file,
|
|
1540
|
+
line,
|
|
1541
|
+
column
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
if (functionObject.definedFile)
|
|
1546
|
+
file = functionObject.definedFile;
|
|
1547
|
+
activeBytecode = functionObject.bytecode;
|
|
1548
|
+
pointer = 0;
|
|
1549
|
+
}, // CALLMETHOD
|
|
1550
|
+
|
|
1551
|
+
(bytecode : Bytecode) : void =>
|
|
1552
|
+
{
|
|
1553
|
+
const catchPosition = next(bytecode);
|
|
1554
|
+
const finallyPosition = next(bytecode);
|
|
1555
|
+
|
|
1556
|
+
tryStack.push({
|
|
1557
|
+
catchPosition,
|
|
1558
|
+
finallyPosition,
|
|
1559
|
+
savedPointer : pointer,
|
|
1560
|
+
savedBytecode : activeBytecode,
|
|
1561
|
+
savedScope : currentScope,
|
|
1562
|
+
savedStack : [...stack],
|
|
1563
|
+
savedCallStack: [...callStack],
|
|
1564
|
+
catchExecuted : false,
|
|
1565
|
+
file,
|
|
1566
|
+
line,
|
|
1567
|
+
column
|
|
1568
|
+
});
|
|
1569
|
+
}, // TRY
|
|
1570
|
+
|
|
1571
|
+
() : void =>
|
|
1572
|
+
{
|
|
1573
|
+
if (tryStack.length > 0)
|
|
1574
|
+
{
|
|
1575
|
+
const frame = tryStack[tryStack.length - 1];
|
|
1576
|
+
|
|
1577
|
+
if (frame.finallyPosition && !frame.finallyExecuted)
|
|
1578
|
+
{
|
|
1579
|
+
frame.finallyExecuted = true;
|
|
1580
|
+
pointer = frame.finallyPosition;
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const pendingError = frame.pendingError;
|
|
1585
|
+
tryStack.pop();
|
|
1586
|
+
|
|
1587
|
+
// If finally ran after a catch, nothing more to do.
|
|
1588
|
+
// If finally ran after an ERROR (pendingError set), re-throw now.
|
|
1589
|
+
if (pendingError !== undefined)
|
|
1590
|
+
throw pendingError;
|
|
1591
|
+
}
|
|
1592
|
+
}, // ENDTRY
|
|
1593
|
+
|
|
1594
|
+
() : void =>
|
|
1595
|
+
{
|
|
1596
|
+
const obj : any = stack.pop();
|
|
1597
|
+
if (obj?.type === "object")
|
|
1598
|
+
stack.push(Object.keys(obj.value));
|
|
1599
|
+
else if (Array.isArray(obj))
|
|
1600
|
+
stack.push(obj.map((_ : any, i : number) => String(i)));
|
|
1601
|
+
else
|
|
1602
|
+
errorTemplate("OBJKEYS", `expected an Object or Array, got "${obj}"`);
|
|
1603
|
+
}, // OBJKEYS
|
|
1604
|
+
|
|
1605
|
+
() : void =>
|
|
1606
|
+
{
|
|
1607
|
+
const arr : any = stack.pop();
|
|
1608
|
+
if (!Array.isArray(arr))
|
|
1609
|
+
errorTemplate("ARRLEN", `expected an Array, got "${arr}"`);
|
|
1610
|
+
stack.push(arr.length);
|
|
1611
|
+
}, // ARRLEN
|
|
1612
|
+
|
|
1613
|
+
(bytecode : Bytecode) : void =>
|
|
1614
|
+
{
|
|
1615
|
+
const name : string = (next(bytecode)) as string;
|
|
1616
|
+
const value = stack.pop();
|
|
1617
|
+
|
|
1618
|
+
declareVariable(name);
|
|
1619
|
+
setVariable(name, value);
|
|
1620
|
+
constants[name] = true;
|
|
1621
|
+
}, // ALLOCCONST
|
|
1622
|
+
|
|
1623
|
+
(bytecode : Bytecode) : void =>
|
|
1624
|
+
{
|
|
1625
|
+
const variableName : string = (next(bytecode)) as string;
|
|
1626
|
+
const expectedType : string = (next(bytecode)) as string;
|
|
1627
|
+
const value = stack[stack.length - 1];
|
|
1628
|
+
|
|
1629
|
+
const valueType : string = syncFunctions.typeof([], () => "", value);
|
|
1630
|
+
|
|
1631
|
+
if (valueType !== expectedType)
|
|
1632
|
+
errorTemplate("TYPECHECK", `type mismatch for "${variableName}", expected ${expectedType}, got ${valueType}`);
|
|
1633
|
+
}, // TYPECHECK
|
|
1634
|
+
|
|
1635
|
+
commandMapBinaryOperators
|
|
1636
|
+
(
|
|
1637
|
+
(left, right) => BigInt(left) & BigInt(right),
|
|
1638
|
+
() => errorTemplate(`AND`, "bitwise AND not supported on floats"),
|
|
1639
|
+
"bitwise"
|
|
1640
|
+
), // AND
|
|
1641
|
+
|
|
1642
|
+
commandMapBinaryOperators
|
|
1643
|
+
(
|
|
1644
|
+
(left, right) => BigInt(left) | BigInt(right),
|
|
1645
|
+
() => errorTemplate(`OR`, "bitwise OR not supported on floats"),
|
|
1646
|
+
"bitwise"
|
|
1647
|
+
), // OR
|
|
1648
|
+
|
|
1649
|
+
commandMapBinaryOperators
|
|
1650
|
+
(
|
|
1651
|
+
(left, right) => BigInt(left) ^ BigInt(right),
|
|
1652
|
+
() => errorTemplate(`XOR`, "bitwise XOR not supported on floats"),
|
|
1653
|
+
"bitwise"
|
|
1654
|
+
), // XOR
|
|
1655
|
+
|
|
1656
|
+
commandMapBinaryOperators
|
|
1657
|
+
(
|
|
1658
|
+
(left, right) => BigInt(left) << BigInt(right),
|
|
1659
|
+
() => errorTemplate(`SHL`, "bitwise SHL not supported on floats"),
|
|
1660
|
+
"bitwise"
|
|
1661
|
+
), // SHL
|
|
1662
|
+
|
|
1663
|
+
commandMapBinaryOperators
|
|
1664
|
+
(
|
|
1665
|
+
(left, right) => BigInt(left) >> BigInt(right),
|
|
1666
|
+
() => errorTemplate(`SHR`, "bitwise SHR not supported on floats"),
|
|
1667
|
+
"bitwise"
|
|
1668
|
+
), // SHR
|
|
1669
|
+
|
|
1670
|
+
commandMapBinaryOperators
|
|
1671
|
+
(
|
|
1672
|
+
(left, right) => BigInt(left) >> BigInt(right) & 0xFFFFFFFFFFFFFFFFn,
|
|
1673
|
+
() => errorTemplate(`USHR`, "bitwise USHR not supported on floats"),
|
|
1674
|
+
"bitwise"
|
|
1675
|
+
), // USHR
|
|
1676
|
+
];
|
|
1677
|
+
|
|
1678
|
+
const asyncOpcodes = new Set([5, 10, 13, 26, 33, 35, 37]);
|
|
1679
|
+
export async function interpret(
|
|
1680
|
+
bytecode : Bytecode,
|
|
1681
|
+
baseDir : string = process.cwd(),
|
|
1682
|
+
filename : string = "<anonymous>"
|
|
1683
|
+
)
|
|
1684
|
+
{
|
|
1685
|
+
pointer = 0;
|
|
1686
|
+
stack = [];
|
|
1687
|
+
tryStack = [];
|
|
1688
|
+
activeBytecode = bytecode;
|
|
1689
|
+
currentBaseDir = baseDir;
|
|
1690
|
+
currentScope = new Scope();
|
|
1691
|
+
callStack = [];
|
|
1692
|
+
file = filename;
|
|
1693
|
+
line = 0;
|
|
1694
|
+
column = 0;
|
|
1695
|
+
importedFiles.clear();
|
|
1696
|
+
|
|
1697
|
+
let steps = 0;
|
|
1698
|
+
|
|
1699
|
+
while (true)
|
|
1700
|
+
{
|
|
1701
|
+
let caughtError: any = undefined;
|
|
1702
|
+
|
|
1703
|
+
try
|
|
1704
|
+
{
|
|
1705
|
+
while (true)
|
|
1706
|
+
{
|
|
1707
|
+
if (pointer >= activeBytecode.length)
|
|
1708
|
+
{
|
|
1709
|
+
if (callStack.length === 0)
|
|
1710
|
+
break;
|
|
1711
|
+
|
|
1712
|
+
const frame: any = callStack.pop();
|
|
1713
|
+
activeBytecode = frame.bytecode;
|
|
1714
|
+
pointer = frame.pointer;
|
|
1715
|
+
currentScope = frame.savedScope;
|
|
1716
|
+
|
|
1717
|
+
if (frame.returnMode === "constructor")
|
|
1718
|
+
{
|
|
1719
|
+
if (!frame.pendingMethod)
|
|
1720
|
+
stack.push(frame.instance);
|
|
1721
|
+
|
|
1722
|
+
if (frame.pendingMethod)
|
|
1723
|
+
{
|
|
1724
|
+
const {methodBytecode, methodKey, methodArgs} = frame.pendingMethod;
|
|
1725
|
+
|
|
1726
|
+
const methodScope = new Scope(currentScope);
|
|
1727
|
+
methodScope.declare("this");
|
|
1728
|
+
methodScope.set("this", frame.instance);
|
|
1729
|
+
|
|
1730
|
+
frame.pendingMethod.methodParameters.forEach(
|
|
1731
|
+
(parameter: any, i: number) =>
|
|
1732
|
+
{
|
|
1733
|
+
methodScope.declare(parameter.name);
|
|
1734
|
+
methodScope.set(
|
|
1735
|
+
parameter.name,
|
|
1736
|
+
methodArgs[i] !== undefined
|
|
1737
|
+
? methodArgs[i]
|
|
1738
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined)
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
callStack.push({
|
|
1744
|
+
bytecode : activeBytecode,
|
|
1745
|
+
pointer : pointer,
|
|
1746
|
+
savedScope : currentScope,
|
|
1747
|
+
returnMode : "function",
|
|
1748
|
+
functionName : methodKey,
|
|
1749
|
+
file, line, column
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
currentScope = methodScope;
|
|
1753
|
+
activeBytecode = methodBytecode;
|
|
1754
|
+
pointer = 0;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
else if (frame.returnMode === "super")
|
|
1758
|
+
{
|
|
1759
|
+
const nextFrame = callStack[callStack.length - 1];
|
|
1760
|
+
if (!nextFrame?.pendingMethod)
|
|
1761
|
+
stack.push(undefined);
|
|
1762
|
+
}
|
|
1763
|
+
else if (frame.returnMode === "execute")
|
|
1764
|
+
{
|
|
1765
|
+
file = frame.file;
|
|
1766
|
+
line = frame.line;
|
|
1767
|
+
column = frame.column;
|
|
1768
|
+
currentBaseDir = frame.savedBaseDir ?? currentBaseDir;
|
|
1769
|
+
}
|
|
1770
|
+
else
|
|
1771
|
+
{
|
|
1772
|
+
if (stack[stack.length - 1] === undefined)
|
|
1773
|
+
stack.push(undefined);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const operator: any = activeBytecode[pointer++];
|
|
1780
|
+
|
|
1781
|
+
if (operator === 25) // RETURN
|
|
1782
|
+
{
|
|
1783
|
+
const frame : any = callStack.pop();
|
|
1784
|
+
|
|
1785
|
+
// check return type if annotated (skip for constructors tho since they return the instance)
|
|
1786
|
+
if (frame.returnType && frame.returnMode !== "constructor" && frame.returnMode !== "super")
|
|
1787
|
+
checkReturnType(frame.functionName, frame.returnType, stack[stack.length - 1]);
|
|
1788
|
+
|
|
1789
|
+
activeBytecode = frame.bytecode;
|
|
1790
|
+
pointer = frame.pointer;
|
|
1791
|
+
currentScope = frame.savedScope;
|
|
1792
|
+
file = frame.file;
|
|
1793
|
+
|
|
1794
|
+
if (frame.returnMode === "constructor")
|
|
1795
|
+
{
|
|
1796
|
+
stack.pop();
|
|
1797
|
+
stack.push(frame.instance);
|
|
1798
|
+
|
|
1799
|
+
if (frame.pendingMethod)
|
|
1800
|
+
{
|
|
1801
|
+
const {methodBytecode, methodKey, methodArgs, methodParameters} = frame.pendingMethod;
|
|
1802
|
+
|
|
1803
|
+
const methodScope = new Scope(currentScope);
|
|
1804
|
+
methodScope.declare("this");
|
|
1805
|
+
methodScope.set("this", frame.instance);
|
|
1806
|
+
|
|
1807
|
+
methodParameters.forEach((parameter: any, i: number) =>
|
|
1808
|
+
{
|
|
1809
|
+
methodScope.declare(parameter.name);
|
|
1810
|
+
methodScope.set(
|
|
1811
|
+
parameter.name,
|
|
1812
|
+
methodArgs[i] !== undefined
|
|
1813
|
+
? methodArgs[i]
|
|
1814
|
+
: (parameter.default !== null ? evaluateDefault(parameter.default) : undefined)
|
|
1815
|
+
);
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
callStack.push({
|
|
1819
|
+
bytecode : frame.pendingMethod.returnBytecode,
|
|
1820
|
+
pointer : frame.pendingMethod.returnPointer,
|
|
1821
|
+
savedScope : frame.pendingMethod.returnScope,
|
|
1822
|
+
returnMode : "function",
|
|
1823
|
+
functionName : methodKey,
|
|
1824
|
+
file, line, column
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
stack.pop();
|
|
1828
|
+
currentScope = methodScope;
|
|
1829
|
+
activeBytecode = methodBytecode;
|
|
1830
|
+
pointer = 0;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
else if (frame.returnMode === "super")
|
|
1834
|
+
{
|
|
1835
|
+
const nextFrame = callStack[callStack.length - 1];
|
|
1836
|
+
if (!nextFrame?.pendingMethod)
|
|
1837
|
+
stack.push(undefined);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
if (typeof operator !== "number")
|
|
1844
|
+
throw new runtimeErrors.InternalError(
|
|
1845
|
+
`operator should be a number but got "${operator}"`
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
const command = commands[operator];
|
|
1849
|
+
if (command === undefined)
|
|
1850
|
+
throw new runtimeErrors.InternalError(`unknown operator code: "${operator}"`);
|
|
1851
|
+
|
|
1852
|
+
if (asyncOpcodes.has(operator))
|
|
1853
|
+
await command(activeBytecode);
|
|
1854
|
+
else
|
|
1855
|
+
command(activeBytecode);
|
|
1856
|
+
|
|
1857
|
+
if (++steps % 1_000_000 === 0)
|
|
1858
|
+
await new Promise(setImmediate);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
break; // clean exit from inner while(true) — we're done
|
|
1862
|
+
}
|
|
1863
|
+
catch (error: any)
|
|
1864
|
+
{
|
|
1865
|
+
caughtError = error;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
if (caughtError === undefined)
|
|
1869
|
+
break;
|
|
1870
|
+
|
|
1871
|
+
let handled = false;
|
|
1872
|
+
|
|
1873
|
+
while (tryStack.length > 0)
|
|
1874
|
+
{
|
|
1875
|
+
const frame = tryStack[tryStack.length - 1];
|
|
1876
|
+
|
|
1877
|
+
if (frame.finallyPosition && !frame.finallyExecuted)
|
|
1878
|
+
{
|
|
1879
|
+
frame.finallyExecuted = true;
|
|
1880
|
+
frame.pendingError = caughtError;
|
|
1881
|
+
|
|
1882
|
+
pointer = frame.finallyPosition;
|
|
1883
|
+
activeBytecode = frame.savedBytecode;
|
|
1884
|
+
currentScope = frame.savedScope;
|
|
1885
|
+
callStack = [...frame.savedCallStack];
|
|
1886
|
+
|
|
1887
|
+
handled = true;
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// Catch block.
|
|
1892
|
+
if (frame.catchPosition && !frame.catchExecuted)
|
|
1893
|
+
{
|
|
1894
|
+
frame.catchExecuted = true;
|
|
1895
|
+
|
|
1896
|
+
pointer = frame.catchPosition;
|
|
1897
|
+
activeBytecode = frame.savedBytecode;
|
|
1898
|
+
currentScope = frame.savedScope;
|
|
1899
|
+
stack = [...frame.savedStack];
|
|
1900
|
+
callStack = [...frame.savedCallStack];
|
|
1901
|
+
|
|
1902
|
+
const errorMessage = caughtError instanceof Error
|
|
1903
|
+
? (caughtError.name && caughtError.name !== "Error"
|
|
1904
|
+
? caughtError.name + (caughtError.message ? ": " + caughtError.message : "")
|
|
1905
|
+
: caughtError.message)
|
|
1906
|
+
: String(caughtError);
|
|
1907
|
+
stack.push(errorMessage);
|
|
1908
|
+
|
|
1909
|
+
tryStack.pop();
|
|
1910
|
+
|
|
1911
|
+
handled = true;
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
tryStack.pop();
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
if (handled)
|
|
1919
|
+
continue;
|
|
1920
|
+
|
|
1921
|
+
if (!caughtError.hasLocation)
|
|
1922
|
+
{
|
|
1923
|
+
const fileShortName = file.split(/[\\/]/).pop() ?? file;
|
|
1924
|
+
caughtError.message += `\n at ${fileShortName} (${file}:${line}:${column})`;
|
|
1925
|
+
|
|
1926
|
+
for (let i = callStack.length - 1; i >= 0; i--)
|
|
1927
|
+
{
|
|
1928
|
+
const frame = callStack[i];
|
|
1929
|
+
|
|
1930
|
+
if (frame.returnMode === "execute")
|
|
1931
|
+
{
|
|
1932
|
+
const importerFile = frame.importer ?? frame.file;
|
|
1933
|
+
const importerShort = importerFile.split(/[\\/]/).pop() ?? importerFile;
|
|
1934
|
+
caughtError.message += `\n at ${importerShort} (${importerFile}:${frame.line}:${frame.column})`;
|
|
1935
|
+
}
|
|
1936
|
+
else
|
|
1937
|
+
{
|
|
1938
|
+
const frameShort = frame.file.split(/[\\/]/).pop() ?? frame.file;
|
|
1939
|
+
caughtError.message += `\n at ${frame.functionName} (${frameShort}:${frame.line}:${frame.column})`;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
caughtError.hasLocation = true;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
throw caughtError;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
return {stack, scopes: currentScope};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
export async function executeInCurrentContext(code : string, isolateScope : boolean = false) : Promise<any>
|
|
1953
|
+
{
|
|
1954
|
+
if (!code?.trim())
|
|
1955
|
+
return undefined;
|
|
1956
|
+
|
|
1957
|
+
const tokens = tokenizer(code);
|
|
1958
|
+
const ast = parser(tokens);
|
|
1959
|
+
const execBytecode = buildBytecode(ast, "<exec>");
|
|
1960
|
+
|
|
1961
|
+
const savedPointer = pointer;
|
|
1962
|
+
const savedStack = [...stack];
|
|
1963
|
+
const savedBytecode = activeBytecode;
|
|
1964
|
+
const savedScope = currentScope;
|
|
1965
|
+
|
|
1966
|
+
if (isolateScope)
|
|
1967
|
+
currentScope = new Scope(currentScope);
|
|
1968
|
+
|
|
1969
|
+
activeBytecode = execBytecode;
|
|
1970
|
+
pointer = 0;
|
|
1971
|
+
stack = [];
|
|
1972
|
+
|
|
1973
|
+
while (true)
|
|
1974
|
+
{
|
|
1975
|
+
if (pointer >= activeBytecode.length)
|
|
1976
|
+
{
|
|
1977
|
+
if (callStack.length === 0 || callStack[callStack.length - 1].returnMode === "execute")
|
|
1978
|
+
break;
|
|
1979
|
+
|
|
1980
|
+
const frame : any = callStack.pop();
|
|
1981
|
+
activeBytecode = frame.bytecode;
|
|
1982
|
+
pointer = frame.pointer;
|
|
1983
|
+
currentScope = frame.savedScope;
|
|
1984
|
+
|
|
1985
|
+
if (frame.returnMode === "constructor")
|
|
1986
|
+
stack.push(frame.instance);
|
|
1987
|
+
else if (frame.returnMode === "function")
|
|
1988
|
+
{
|
|
1989
|
+
if (stack[stack.length - 1] === undefined)
|
|
1990
|
+
stack.push(undefined);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
const operator = activeBytecode[pointer++];
|
|
1997
|
+
|
|
1998
|
+
// RETURN
|
|
1999
|
+
if (operator === 25)
|
|
2000
|
+
{
|
|
2001
|
+
if (callStack.length === 0)
|
|
2002
|
+
break;
|
|
2003
|
+
|
|
2004
|
+
const frame : any = callStack.pop();
|
|
2005
|
+
activeBytecode = frame.bytecode;
|
|
2006
|
+
pointer = frame.pointer;
|
|
2007
|
+
currentScope = frame.savedScope;
|
|
2008
|
+
|
|
2009
|
+
if (frame.returnMode === "constructor")
|
|
2010
|
+
{
|
|
2011
|
+
stack.pop();
|
|
2012
|
+
stack.push(frame.instance);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
if (frame.returnMode === "execute")
|
|
2016
|
+
break;
|
|
2017
|
+
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (typeof operator === "number" && commands[operator])
|
|
2022
|
+
{
|
|
2023
|
+
if (asyncOpcodes.has(operator))
|
|
2024
|
+
await commands[operator](activeBytecode);
|
|
2025
|
+
else
|
|
2026
|
+
commands[operator](activeBytecode);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
const result = stack.length > 0
|
|
2031
|
+
? stack.pop()
|
|
2032
|
+
: undefined;
|
|
2033
|
+
|
|
2034
|
+
stack = savedStack;
|
|
2035
|
+
pointer = savedPointer;
|
|
2036
|
+
activeBytecode = savedBytecode;
|
|
2037
|
+
|
|
2038
|
+
if (isolateScope)
|
|
2039
|
+
currentScope = savedScope;
|
|
2040
|
+
|
|
2041
|
+
return result;
|
|
2042
|
+
}
|