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.
@@ -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
+ }