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,1269 @@
1
+ import {runtimeErrors} from "../runtime/errors";
2
+ import {errorTemplate} from "../runtime/stdlib";
3
+
4
+ type Bytecode = Array<any>;
5
+ type LoopInfo =
6
+ {
7
+ start : number,
8
+ continueTarget : number | null,
9
+ end : number | null,
10
+ breakPositions : number[],
11
+ continuePositions : number[]
12
+ }
13
+ type TypeMap = Map<string, (node : any, bytecode : Bytecode, keepValue : boolean) => void>;
14
+
15
+ function storeArgument(argument : any, bytecode : Bytecode) : void
16
+ {
17
+ if (argument.type === "VariableReference")
18
+ bytecode.push(operators.STORE, argument.value);
19
+ else if (argument.type === "MemberExpression")
20
+ {
21
+ parseObject(argument.object, bytecode, true);
22
+ bytecode.push(operators.PUSH, argument.property);
23
+ bytecode.push(operators.ARRSET);
24
+ }
25
+ else if (argument.type === "ArrayAccess")
26
+ {
27
+ parseObject(argument.object, bytecode, true);
28
+ parseObject(argument.index, bytecode, true);
29
+ bytecode.push(operators.ARRSET);
30
+ }
31
+ else
32
+ throw new Error(`Invalid assignment target: "${argument.type}" at line ${argument.line}:${argument.column}`);
33
+ }
34
+
35
+ function handleDecrementAndIncrementOperator(node : any, type : number, bytecode : Bytecode, keepValue : boolean) : void
36
+ {
37
+ parseObject(node.argument, bytecode, true);
38
+ bytecode.push(operators.PUSH, 1);
39
+ bytecode.push(type);
40
+ storeArgument(node.argument, bytecode); // update variable
41
+
42
+ if (keepValue)
43
+ parseObject(node.argument, bytecode, true) // return the updated if keepValue is true
44
+ }
45
+
46
+ function simpleUnary(node : any, bytecode : Bytecode, operator : number, keepValue : boolean) : void
47
+ {
48
+ parseObject(node.argument, bytecode, true);
49
+ bytecode.push(operator);
50
+
51
+ if (!keepValue)
52
+ bytecode.push(operators.POP);
53
+ }
54
+
55
+ function parseObject(node : any, bytecode : Bytecode = [], keepValue : boolean = false) : Bytecode
56
+ {
57
+ const typeFunction = typeMap.get(node?.type);
58
+
59
+ if (!typeFunction)
60
+ errorTemplate(`parseObject`, `unknown node type "${node?.type}" with value ${node?.value} at line ${node?.row}:${node?.column}`);
61
+
62
+ typeFunction(node, bytecode, keepValue);
63
+ return bytecode;
64
+ }
65
+
66
+ function loopStatementHandler(statementName : string, bytecode : Bytecode, child : string) : void
67
+ {
68
+ if (loopStack.length < 1)
69
+ {
70
+ if (statementName === "break")
71
+ throw new runtimeErrors.BreakError();
72
+ if (statementName === "continue")
73
+ throw new runtimeErrors.ContinueError();
74
+ }
75
+ const currentLoop = loopStack[loopStack.length - 1];
76
+
77
+ if (child === "start")
78
+ {
79
+ const continuePosition = bytecode.length;
80
+ bytecode.push(operators.JMP, 0);
81
+
82
+ if (!currentLoop.continuePositions) currentLoop.continuePositions = [];
83
+
84
+ currentLoop.continuePositions.push(continuePosition);
85
+ }
86
+
87
+ else
88
+ {
89
+ const breakPosition = bytecode.length;
90
+ bytecode.push(operators.JMP, 0);
91
+
92
+ if (!currentLoop.breakPositions) currentLoop.breakPositions = [];
93
+
94
+ currentLoop.breakPositions.push(breakPosition);
95
+ }
96
+ }
97
+
98
+ // Object.freeze makes this impossible to edit
99
+ const operators : Record<string, number> = Object.freeze({
100
+ PUSH: 1, // pushes a value on a stack
101
+ LOAD: 2, // loads a variable
102
+ STORE: 3, // stores a value in a variable
103
+ ALLOC: 4, // makes a new variable
104
+ ADD: 5, // short for addition
105
+ SUB: 6, // short for subtract
106
+ MUL: 7, // short for multiply
107
+ DIV: 8, // short for divide
108
+ MOD: 9, // short for modulo
109
+ CALL: 10, // calls a function
110
+ NEG: 11, // short for negative
111
+ NOT: 12,
112
+ BITNOT: 13, // flips all bits (~x = -(x+1))
113
+ JMP: 14, // jumps
114
+ JZ: 15, // jump if zero
115
+ EQ: 16, // ==
116
+ NE: 17, // !=
117
+ GT: 18, // >
118
+ LT: 19, // <
119
+ GTE: 20, // >=
120
+ LTE: 21, // <=
121
+ POP: 22, // pops one item from the stack
122
+ PUSHSCP: 23, // short for push scope
123
+ POPSCP: 24, // short for pop scope
124
+ RETURN: 25, // doesn't do anything
125
+ EXEC: 26, // runs the stack.pop()'s code
126
+ MKFUNC: 27, // stamps closure
127
+ MKARR: 28, // creates an array from n items on the stack
128
+ ARRGET: 29, // array index get
129
+ ARRSET: 30, // array index set
130
+ MKOBJ: 31, // creates an object from n items on the stack
131
+ MKCLASS: 32, // creates a class object
132
+ MKINST: 33, // creates an instance (new classname())
133
+ SETLINE: 34, // this GENUINELY doesn't need a comment but: sets the row and column
134
+ SETFILE: 35, // sets the current file name
135
+ SPREAD: 36, // spreaded arguments
136
+ CALLMETHOD: 37,
137
+ TRY: 38, // start try block
138
+ ENDTRY: 39, // end try-catch
139
+ OBJKEYS: 40, // pops object, pushes Object.keys() as plain array
140
+ ARRLEN: 41, // pops array, pushes its .length
141
+ ALLOCCONST: 42, // allocates a variable as a constant
142
+ TYPECHECK: 43, // checks the type of stack top, use: TYPECHECK variableName typeName
143
+ AND: 44, // &
144
+ OR: 45, // |
145
+ XOR: 46, // ^
146
+ SHL: 47, // <<
147
+ SHR: 48, // >>
148
+ USHR: 49, // >>>
149
+ })
150
+
151
+ const binaryOperators : Record<string, number> = Object.freeze({
152
+ "+": operators.ADD,
153
+ "-": operators.SUB,
154
+ "*": operators.MUL,
155
+ "/": operators.DIV,
156
+ "%": operators.MOD,
157
+
158
+ "==": operators.EQ,
159
+ "!=": operators.NE,
160
+ ">": operators.GT,
161
+ "<": operators.LT,
162
+ ">=": operators.GTE,
163
+ "<=": operators.LTE,
164
+
165
+ "&": operators.AND,
166
+ "|": operators.OR,
167
+ "^": operators.XOR,
168
+ "<<": operators.SHL,
169
+ ">>": operators.SHR,
170
+ ">>>": operators.USHR,
171
+ });
172
+
173
+ const unaryOperators : Map<string, (node : any, bytecode : Bytecode, keepValue : boolean) => void> = new Map([
174
+ ["++", (node : any, bytecode : Bytecode, keepValue : boolean) => handleDecrementAndIncrementOperator(node, operators.ADD, bytecode, keepValue)],
175
+ ["--", (node : any, bytecode : Bytecode, keepValue : boolean) => handleDecrementAndIncrementOperator(node, operators.SUB, bytecode, keepValue)],
176
+
177
+ ["!", (node : any, bytecode : Bytecode, keepValue : boolean) => simpleUnary(node, bytecode, operators.NOT, keepValue)],
178
+ ["~", (node : any, bytecode : Bytecode, keepValue : boolean) => simpleUnary(node, bytecode, operators.BITNOT, keepValue)],
179
+
180
+ ["-u", (node : any, bytecode : Bytecode, keepValue : boolean) => simpleUnary(node, bytecode, operators.NEG, keepValue)],
181
+ ["+u", (node : any, bytecode : Bytecode, keepValue : boolean) => parseObject(node.argument, bytecode, keepValue)],
182
+ ]);
183
+
184
+ const loopStack : LoopInfo[] = [];
185
+
186
+ let tempCounter = 0;
187
+ function getTempName() : string
188
+ {
189
+ return `__$temp${tempCounter++}`;
190
+ }
191
+
192
+ const typeMap : TypeMap = new Map([
193
+ ["Program",
194
+ (node : any, bytecode : Bytecode) : void =>
195
+ node.body.forEach(
196
+ (n : any, i : number) =>
197
+ parseObject(n, bytecode, i === node.body.length - 1)
198
+ )
199
+ ],
200
+
201
+ ["Literal", (node : any, bytecode : Bytecode) : void => {bytecode.push(operators.PUSH, node.value)}],
202
+ ["StringLiteral", (node : any, bytecode : Bytecode) : void => {bytecode.push(operators.PUSH, node.value)}],
203
+ ["VariableReference",
204
+ (node : any, bytecode : Bytecode) : void =>
205
+ {
206
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
207
+ bytecode.push(operators.LOAD, node.value)
208
+ }
209
+ ],
210
+
211
+ ["ArrayLiteral",
212
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
213
+ {
214
+ node.elements.forEach((element : any) => parseObject(element, bytecode, true));
215
+ bytecode.push(operators.MKARR, node.elements.length);
216
+
217
+ if (!keepValue) bytecode.push(operators.POP);
218
+ }
219
+ ],
220
+ ["ObjectLiteral",
221
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
222
+ {
223
+ for (const property of node.properties)
224
+ {
225
+ bytecode.push(operators.PUSH, property.key);
226
+ parseObject(property.value, bytecode, true);
227
+ }
228
+ bytecode.push(operators.MKOBJ, node.properties.length);
229
+
230
+ if (!keepValue) bytecode.push(operators.POP);
231
+ }
232
+ ],
233
+
234
+ ["ClassDeclaration",
235
+ (node : any, bytecode : Bytecode) : void =>
236
+ {
237
+ const methods : Record<string, any> = {};
238
+
239
+ for (const method of node.methods)
240
+ {
241
+ const methodBytecode : Bytecode = [];
242
+ (method.body ?? []).forEach((n : any) => parseObject(n, methodBytecode));
243
+ methodBytecode.push(operators.PUSH, null);
244
+ methodBytecode.push(operators.RETURN);
245
+
246
+ const parameters = method.parameters.map
247
+ (
248
+ (parameter: any) =>
249
+ (
250
+ {
251
+ name : parameter.name,
252
+ rest : parameter.rest,
253
+ default : parameter.default ? parseObject(parameter.default, [], true) : null,
254
+ typeAnnotation : parameter.typeAnnotation ?? null
255
+ }
256
+ )
257
+ );
258
+
259
+ methods[method.name] =
260
+ {
261
+ name: method.name,
262
+ bytecode: methodBytecode,
263
+ parameters,
264
+ ast: method
265
+ }
266
+ }
267
+
268
+ bytecode.push(operators.ALLOC, node.name);
269
+ bytecode.push(operators.PUSH,
270
+ {
271
+ name : node.name,
272
+ superclass : node.superclass,
273
+ methods
274
+ }
275
+ );
276
+
277
+ bytecode.push(operators.MKCLASS);
278
+ bytecode.push(operators.STORE, node.name);
279
+ }
280
+ ],
281
+
282
+ ["ClassInstantiation",
283
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
284
+ {
285
+ const len = bytecode.length;
286
+ node.args.forEach((argument : any) => parseObject(argument, bytecode, true));
287
+ bytecode.push(operators.MKINST, node.className, node.args.length);
288
+
289
+ if (!keepValue)
290
+ bytecode.push(operators.POP);
291
+ }
292
+ ],
293
+
294
+ ["ArrayAccess",
295
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
296
+ {
297
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
298
+
299
+ parseObject(node.object, bytecode, true);
300
+ parseObject(node.index, bytecode, true);
301
+ bytecode.push(operators.ARRGET);
302
+
303
+ if (!keepValue) bytecode.push(operators.POP);
304
+ }
305
+ ],
306
+
307
+ ["MemberExpression",
308
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
309
+ {
310
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
311
+
312
+ parseObject(node.object, bytecode, true);
313
+ bytecode.push(operators.PUSH, node.property);
314
+ bytecode.push(operators.ARRGET);
315
+
316
+ if (!keepValue) bytecode.push(operators.POP);
317
+ }
318
+ ],
319
+
320
+ ["BinaryExpression",
321
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
322
+ {
323
+ parseObject(node.left, bytecode, true);
324
+ parseObject(node.right, bytecode, true);
325
+
326
+ const operator : number = binaryOperators[node.operator];
327
+
328
+ if (!operator)
329
+ errorTemplate(`BinaryExpression`, `Unknown operator "${node.operator}"`);
330
+
331
+ bytecode.push(operator);
332
+
333
+ if (!keepValue) bytecode.push(operators.POP);
334
+ }
335
+ ],
336
+
337
+ ["LogicalExpression",
338
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
339
+ {
340
+ parseObject(node.left, bytecode, true);
341
+
342
+ if (node.operator === "&&")
343
+ {
344
+ const jumpToFalse : number = bytecode.length;
345
+ bytecode.push(operators.JZ, 0);
346
+
347
+ parseObject(node.right, bytecode, true);
348
+
349
+ const jumpToEnd : number = bytecode.length;
350
+ bytecode.push(operators.JMP, 0);
351
+
352
+ const falseLabel : number = bytecode.length;
353
+ bytecode[jumpToFalse + 1] = falseLabel;
354
+ bytecode.push(operators.PUSH, 0);
355
+
356
+ const end : number = bytecode.length;
357
+ bytecode[jumpToEnd + 1] = end;
358
+ }
359
+
360
+ else if (node.operator === "??")
361
+ {
362
+ const tempName = `__nullish_${tempCounter++}__`;
363
+ bytecode.push(operators.ALLOC, tempName);
364
+ bytecode.push(operators.STORE, tempName);
365
+ bytecode.push(operators.LOAD, tempName);
366
+
367
+ const jumpIfNull : number = bytecode.length;
368
+ bytecode.push(operators.JZ, 0); // if null/undefined, go to right
369
+
370
+ bytecode.push(operators.LOAD, tempName);
371
+ const jumpToEnd : number = bytecode.length;
372
+ bytecode.push(operators.JMP, 0);
373
+
374
+ const rightLabel : number = bytecode.length;
375
+ bytecode[jumpIfNull + 1] = rightLabel;
376
+ parseObject(node.right, bytecode, true);
377
+
378
+ const end : number = bytecode.length;
379
+ bytecode[jumpToEnd + 1] = end;
380
+ }
381
+
382
+ else
383
+ {
384
+ const jumpIfFalse : number = bytecode.length;
385
+ bytecode.push(operators.JZ, 0);
386
+
387
+ bytecode.push(operators.PUSH, 1);
388
+
389
+ const jumpToEnd : number = bytecode.length;
390
+ bytecode.push(operators.JMP, 0);
391
+
392
+ const falseLabel : number = bytecode.length;
393
+ bytecode[jumpIfFalse + 1] = falseLabel;
394
+ parseObject(node.right, bytecode, true);
395
+
396
+ const end : number = bytecode.length;
397
+ bytecode[jumpToEnd + 1] = end;
398
+ }
399
+
400
+ if (!keepValue) bytecode.push(operators.POP);
401
+ }
402
+ ],
403
+
404
+ ["ContinueStatement", (node : any, bytecode : Bytecode) : void => loopStatementHandler("continue", bytecode, "start")],
405
+ ["BreakStatement", (node : any, bytecode : Bytecode) : void => loopStatementHandler("break", bytecode, "end")],
406
+
407
+ ["NewDeclaration", (node : any, bytecode : Bytecode) : number => bytecode.push(operators.ALLOC, node.name)],
408
+
409
+ ["NewAssignment",
410
+ (node : any, bytecode) =>
411
+ {
412
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
413
+
414
+ if (node.constant)
415
+ {
416
+ parseObject(node.value, bytecode, true);
417
+
418
+ if (node.typeAnnotation)
419
+ bytecode.push(operators.TYPECHECK, node.name, node.typeAnnotation);
420
+
421
+ bytecode.push(operators.ALLOCCONST, node.name);
422
+ }
423
+ else
424
+ {
425
+ bytecode.push(operators.ALLOC, node.name);
426
+ if (node.value !== null)
427
+ parseObject(node.value, bytecode, true);
428
+
429
+ if (node.typeAnnotation)
430
+ bytecode.push(operators.TYPECHECK, node.name, node.typeAnnotation);
431
+
432
+ bytecode.push(operators.STORE, node.name);
433
+ }
434
+ }
435
+ ],
436
+
437
+ ["ArrayDestructure",
438
+ (node : any, bytecode : Bytecode) : void =>
439
+ {
440
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
441
+
442
+ parseObject(node.value, bytecode, true);
443
+
444
+ // store in a hidden temp so we can reload per element
445
+ const temp = `__destructure_tmp_${tempCounter++}__`;
446
+ bytecode.push(operators.ALLOC, temp);
447
+ bytecode.push(operators.STORE, temp);
448
+
449
+ // alloc all target variables first (for non-const)
450
+ for (const name of node.names)
451
+ {
452
+ if (!node.constant)
453
+ bytecode.push(operators.ALLOC, name);
454
+ }
455
+
456
+ for (let i = 0; i < node.names.length; i++)
457
+ {
458
+ const name = node.names[i];
459
+ bytecode.push(operators.LOAD, temp);
460
+ bytecode.push(operators.PUSH, String(i));
461
+ bytecode.push(operators.ARRGET);
462
+
463
+ if (node.constant)
464
+ bytecode.push(operators.ALLOCCONST, name);
465
+ else
466
+ bytecode.push(operators.STORE, name)
467
+ }
468
+
469
+ // clear temp to avoid stray BigInt in scope at the end of the program
470
+ bytecode.push(operators.PUSH, null);
471
+ bytecode.push(operators.STORE, temp);
472
+ }
473
+ ],
474
+
475
+ ["ObjectDestructure",
476
+ (node : any, bytecode : Bytecode) : void =>
477
+ {
478
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
479
+
480
+ parseObject(node.value, bytecode, true);
481
+
482
+ const temp = `__destructure_tmp_${tempCounter++}__`;
483
+ bytecode.push(operators.ALLOC, temp);
484
+ bytecode.push(operators.STORE, temp);
485
+
486
+ for (const name of node.names)
487
+ {
488
+ if (!node.constant)
489
+ bytecode.push(operators.ALLOC, name);
490
+ }
491
+
492
+ for (const name of node.names)
493
+ {
494
+ bytecode.push(operators.LOAD, temp);
495
+ bytecode.push(operators.PUSH, name); // string key
496
+ bytecode.push(operators.ARRGET); // object[key]
497
+
498
+ if (node.constant)
499
+ bytecode.push(operators.ALLOCCONST, name);
500
+ else
501
+ bytecode.push(operators.STORE, name);
502
+ }
503
+
504
+ bytecode.push(operators.PUSH, null);
505
+ bytecode.push(operators.STORE, temp);
506
+ }
507
+ ],
508
+
509
+ ["Assignment",
510
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
511
+ {
512
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
513
+
514
+ parseObject(node.value, bytecode, true);
515
+ if (typeof node.name === "string")
516
+ {
517
+ bytecode.push(operators.STORE, node.name);
518
+ if (keepValue)
519
+ bytecode.push(operators.LOAD, node.name);
520
+ }
521
+ else if (node.name.type === "MemberExpression")
522
+ {
523
+ parseObject(node.name.object, bytecode, true);
524
+ bytecode.push(operators.PUSH, node.name.property);
525
+ bytecode.push(operators.ARRSET);
526
+ }
527
+ else
528
+ {
529
+ parseObject(node.name.object, bytecode, true);
530
+ parseObject(node.name.index, bytecode, true);
531
+ bytecode.push(operators.ARRSET);
532
+ }
533
+ }
534
+ ],
535
+
536
+ ["FunctionCall",
537
+ (node : any, bytecode : Bytecode, keepValue : boolean = false) : void =>
538
+ {
539
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
540
+
541
+ node.args.forEach
542
+ (
543
+ (argument : any) =>
544
+ {
545
+ if (argument.type === "SpreadElement")
546
+ {
547
+ parseObject(argument.argument, bytecode, true);
548
+ bytecode.push(operators.SPREAD);
549
+ }
550
+ else
551
+ parseObject(argument, bytecode, true);
552
+ }
553
+ );
554
+
555
+ bytecode.push(operators.CALL, node.name, node.args.length);
556
+ if (!keepValue) bytecode.push(operators.POP);
557
+ }
558
+ ],
559
+
560
+ ["UnaryExpression",
561
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
562
+ {
563
+ const operatorFunction = unaryOperators.get(node.value);
564
+ if (!operatorFunction) throw new Error(`Unknown unary operator: "${node.value}"`);
565
+ operatorFunction(node, bytecode, keepValue)
566
+ }
567
+ ],
568
+
569
+ ["PostfixUnaryExpression",
570
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
571
+ {
572
+ if (keepValue)
573
+ {
574
+ const tempName : string = getTempName();
575
+ bytecode.push(operators.ALLOC, tempName); // make the temp variable
576
+
577
+ parseObject(node.argument, bytecode, true);
578
+ bytecode.push(operators.STORE, tempName); // store the current value
579
+
580
+ parseObject(node.argument, bytecode, true);
581
+ bytecode.push(operators.PUSH, 1);
582
+ bytecode.push(node.operator === "++" ? operators.ADD : operators.SUB);
583
+ storeArgument(node.argument, bytecode); // minus the variable by one
584
+
585
+ bytecode.push(operators.LOAD, tempName); // load the previous value if keepValue is true
586
+ }
587
+
588
+ else
589
+ {
590
+ parseObject(node.argument, bytecode, true);
591
+ bytecode.push(operators.PUSH, 1);
592
+ bytecode.push(node.operator === "++" ? operators.ADD : operators.SUB);
593
+ storeArgument(node.argument, bytecode); // minus the variable by one
594
+ }
595
+ }
596
+ ],
597
+
598
+ ["IfStatement",
599
+ (node : any, bytecode : Bytecode) : void =>
600
+ {
601
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
602
+
603
+ parseObject(node.condition, bytecode, true);
604
+ const elseLabel : number = 0;
605
+ const endLabel : number = 1;
606
+
607
+ const JZPosition : number = bytecode.length;
608
+ bytecode.push(operators.JZ, elseLabel);
609
+
610
+ bytecode.push(operators.PUSHSCP); // pushes an object to store local variables in
611
+ node.body.forEach((n : any) => parseObject(n, bytecode)); // parse the body which is a list
612
+ bytecode.push(operators.POPSCP); // delete the local variables
613
+
614
+ const JMPPosition : number = bytecode.length;
615
+ bytecode.push(operators.JMP, endLabel);
616
+
617
+ const elsePosition : number = bytecode.length;
618
+ bytecode[JZPosition + 1] = elsePosition;
619
+
620
+ if (node.else)
621
+ {
622
+ bytecode.push(operators.PUSHSCP);
623
+ node.else.forEach((n : any) => parseObject(n, bytecode)); // parse the else
624
+ bytecode.push(operators.POPSCP);
625
+ }
626
+
627
+ const endPosition : number = bytecode.length;
628
+ bytecode[JMPPosition + 1] = endPosition;
629
+ }
630
+ ],
631
+
632
+ ["TernaryExpression",
633
+ (node : any, bytecode : Bytecode, keepValue : boolean) : void =>
634
+ {
635
+ parseObject(node.condition, bytecode, true);
636
+
637
+ const JZPosition : number = bytecode.length;
638
+ bytecode.push(operators.JZ, 0);
639
+
640
+ parseObject(node.then, bytecode, true);
641
+
642
+ const JMPPosition : number = bytecode.length;
643
+ bytecode.push(operators.JMP, 0);
644
+
645
+ const elsePosition : number = bytecode.length;
646
+ bytecode[JZPosition + 1] = elsePosition;
647
+
648
+ parseObject(node.else, bytecode, true);
649
+
650
+ const endPosition : number = bytecode.length;
651
+ bytecode[JMPPosition + 1] = endPosition;
652
+
653
+ if (!keepValue) bytecode.push(operators.POP);
654
+ }
655
+ ],
656
+
657
+ ["ForStatement",
658
+ (node : any, bytecode : Bytecode) : void =>
659
+ {
660
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
661
+
662
+ bytecode.push(operators.PUSHSCP);
663
+ if (node.initial) parseObject(node.initial, bytecode);
664
+
665
+ const start : number = bytecode.length;
666
+
667
+ const loopInfo : LoopInfo =
668
+ {
669
+ start,
670
+ continueTarget: null,
671
+ end: null,
672
+ breakPositions: [],
673
+ continuePositions: []
674
+ };
675
+ loopStack.push(loopInfo);
676
+
677
+ if (node.condition) parseObject(node.condition, bytecode, true);
678
+ const jumpValueToEndPosition = bytecode.length;
679
+ bytecode.push(operators.JZ, 0);
680
+
681
+ bytecode.push(operators.PUSHSCP);
682
+ node.body.forEach((n : any) => parseObject(n, bytecode));
683
+ bytecode.push(operators.POPSCP);
684
+
685
+ loopInfo.continueTarget = bytecode.length;
686
+
687
+ if (node.update) parseObject(node.update, bytecode);
688
+
689
+ bytecode.push(operators.JMP, start);
690
+
691
+ const end : number = bytecode.length;
692
+ loopInfo.end = end;
693
+
694
+ bytecode[jumpValueToEndPosition + 1] = end;
695
+
696
+ const positions : Array<{position : number, target : number | null}> =
697
+ [
698
+ ...loopInfo.breakPositions.map(position => ({
699
+ position,
700
+ target: end
701
+ })),
702
+
703
+ ...loopInfo.continuePositions.map(position => ({
704
+ position,
705
+ target: loopInfo.continueTarget
706
+ }))
707
+ ]
708
+
709
+ for (const {position, target} of positions) bytecode[position + 1] = target;
710
+
711
+ loopStack.pop();
712
+ bytecode.push(operators.POPSCP);
713
+ }
714
+ ],
715
+
716
+ ["ForInStatement",
717
+ (node : any, bytecode : Bytecode) : void =>
718
+ {
719
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
720
+
721
+ bytecode.push(operators.PUSHSCP);
722
+
723
+ // evaluate the object, extract its keys as a plain JS array
724
+ parseObject(node.right, bytecode, true);
725
+ bytecode.push(operators.OBJKEYS);
726
+
727
+ // store the keys array
728
+ const keysVar = `__keys_${node.row}_${node.column}__`;
729
+ bytecode.push(operators.ALLOC, keysVar);
730
+ bytecode.push(operators.STORE, keysVar);
731
+
732
+ // allocate and initialize counter
733
+ const counterVar = `__i_${node.row}_${node.column}__`;
734
+ bytecode.push(operators.ALLOC, counterVar);
735
+ bytecode.push(operators.PUSH, 0);
736
+ bytecode.push(operators.STORE, counterVar);
737
+
738
+ // allocate the loop variable
739
+ bytecode.push(operators.ALLOC, node.left);
740
+
741
+ const start : number = bytecode.length;
742
+
743
+ const loopInfo : LoopInfo =
744
+ {
745
+ start,
746
+ continueTarget: null,
747
+ end: null,
748
+ breakPositions: [],
749
+ continuePositions: []
750
+ };
751
+ loopStack.push(loopInfo);
752
+
753
+ // loop condition: counter < keys.length
754
+ bytecode.push(operators.LOAD, counterVar);
755
+ bytecode.push(operators.LOAD, keysVar);
756
+ bytecode.push(operators.ARRLEN);
757
+ bytecode.push(operators.LT);
758
+
759
+ const jumpValueToEndPosition = bytecode.length;
760
+ bytecode.push(operators.JZ, 0);
761
+
762
+ bytecode.push(operators.PUSHSCP);
763
+
764
+ // get current key: keys[counter]
765
+ bytecode.push(operators.LOAD, keysVar);
766
+ bytecode.push(operators.LOAD, counterVar);
767
+ bytecode.push(operators.ARRGET);
768
+
769
+ // store in loop variable
770
+ bytecode.push(operators.STORE, node.left);
771
+
772
+ // execute loop body
773
+ node.body.forEach((n : any) => parseObject(n, bytecode));
774
+ bytecode.push(operators.POPSCP);
775
+
776
+ loopInfo.continueTarget = bytecode.length;
777
+
778
+ // increment counter
779
+ bytecode.push(operators.LOAD, counterVar);
780
+ bytecode.push(operators.PUSH, 1);
781
+ bytecode.push(operators.ADD);
782
+ bytecode.push(operators.STORE, counterVar);
783
+
784
+ bytecode.push(operators.JMP, start);
785
+
786
+ const end : number = bytecode.length;
787
+ loopInfo.end = end;
788
+
789
+ bytecode[jumpValueToEndPosition + 1] = end;
790
+
791
+ const positions : Array<{position : number, target : number | null}> =
792
+ [
793
+ ...loopInfo.breakPositions.map(position => ({
794
+ position,
795
+ target: end
796
+ })),
797
+
798
+ ...loopInfo.continuePositions.map(position => ({
799
+ position,
800
+ target: loopInfo.continueTarget
801
+ }))
802
+ ]
803
+
804
+ for (const {position, target} of positions) bytecode[position + 1] = target;
805
+
806
+ loopStack.pop();
807
+ bytecode.push(operators.POPSCP);
808
+ }
809
+ ],
810
+
811
+ ["ForOfStatement",
812
+ (node : any, bytecode : Bytecode) : void =>
813
+ {
814
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
815
+
816
+ // parse iterable (array)
817
+ bytecode.push(operators.PUSHSCP);
818
+ parseObject(node.right, bytecode, true);
819
+
820
+ // alloc and store the array in a temporary variable
821
+ const arrayVariable = `__array_${node.row}_${node.column}_`;
822
+ bytecode.push(operators.ALLOC, arrayVariable);
823
+ bytecode.push(operators.STORE, arrayVariable);
824
+
825
+ // alloc and initialize counter
826
+ const counterVariable = `__i_${node.row}_${node.column}__`;
827
+ bytecode.push(operators.ALLOC, counterVariable);
828
+ bytecode.push(operators.PUSH, 0);
829
+ bytecode.push(operators.STORE, counterVariable);
830
+
831
+ // alloc the loop variable(s)
832
+ if (Array.isArray(node.left))
833
+ {
834
+ for (const name of node.left)
835
+ bytecode.push(operators.ALLOC, name);
836
+ }
837
+ else
838
+ bytecode.push(operators.ALLOC, node.left);
839
+
840
+ const start : number = bytecode.length;
841
+
842
+ const loopInfo : LoopInfo =
843
+ {
844
+ start,
845
+ end : null,
846
+
847
+ continueTarget : null,
848
+
849
+ breakPositions : [],
850
+ continuePositions : []
851
+ }
852
+ loopStack.push(loopInfo);
853
+
854
+ bytecode.push(operators.LOAD, counterVariable);
855
+ bytecode.push(operators.LOAD, arrayVariable);
856
+ bytecode.push(operators.ARRLEN);
857
+ bytecode.push(operators.LT);
858
+
859
+ const jumpValueToEndPosition = bytecode.length;
860
+ bytecode.push(operators.JZ, 0);
861
+
862
+ bytecode.push(operators.PUSHSCP);
863
+
864
+ // get current value (array[counter])
865
+ bytecode.push(operators.LOAD, arrayVariable);
866
+ bytecode.push(operators.LOAD, counterVariable);
867
+ bytecode.push(operators.ARRGET);
868
+
869
+ // store in loop variable
870
+ if (Array.isArray(node.left))
871
+ {
872
+ const elementVar = `__elem_${node.row}_${node.column}__`;
873
+ bytecode.push(operators.ALLOC, elementVar);
874
+ bytecode.push(operators.STORE, elementVar);
875
+
876
+ for (let i = 0; i < node.left.length; i++)
877
+ {
878
+ bytecode.push(operators.LOAD, elementVar);
879
+ bytecode.push(operators.PUSH, String(i));
880
+ bytecode.push(operators.ARRGET);
881
+ bytecode.push(operators.STORE, node.left[i]);
882
+ }
883
+ }
884
+ else
885
+ bytecode.push(operators.STORE, node.left);
886
+
887
+ // execute loop body
888
+ node.body.forEach((n : any) => parseObject(n, bytecode));
889
+ bytecode.push(operators.POPSCP);
890
+
891
+ loopInfo.continueTarget = bytecode.length;
892
+
893
+ // increment counter
894
+ bytecode.push(operators.LOAD, counterVariable);
895
+ bytecode.push(operators.PUSH, 1);
896
+ bytecode.push(operators.ADD);
897
+ bytecode.push(operators.STORE, counterVariable);
898
+
899
+ bytecode.push(operators.JMP, start);
900
+
901
+ const end : number = bytecode.length;
902
+ loopInfo.end = end;
903
+
904
+ bytecode[jumpValueToEndPosition + 1] = end;
905
+
906
+ const positions : Array<{position : number, target : number | null}> =
907
+ [
908
+ ...loopInfo.breakPositions.map(position => ({
909
+ position,
910
+ target: end
911
+ })),
912
+
913
+ ...loopInfo.continuePositions.map(position => ({
914
+ position,
915
+ target: loopInfo.continueTarget
916
+ }))
917
+ ]
918
+
919
+ for (const {position, target} of positions)
920
+ bytecode[position + 1] = target;
921
+
922
+ loopStack.pop();
923
+ bytecode.push(operators.POPSCP);
924
+ }
925
+ ],
926
+
927
+ ["WhileStatement",
928
+ (node : any, bytecode : Bytecode) : void =>
929
+ {
930
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
931
+
932
+ const start : number = bytecode.length;
933
+
934
+ const loopInfo : LoopInfo = {
935
+ start,
936
+ continueTarget: start,
937
+ end: null,
938
+ breakPositions: [],
939
+ continuePositions: []
940
+ };
941
+ loopStack.push(loopInfo);
942
+
943
+ parseObject(node.condition, bytecode, true); // keepValue has to be true to push the condition result on the stack for JZ to read
944
+
945
+ const jumpValueToEndPosition : number = bytecode.length;
946
+ bytecode.push(operators.JZ, 0);
947
+
948
+ bytecode.push(operators.PUSHSCP);
949
+ node.body.forEach((n : any) => parseObject(n, bytecode));
950
+ bytecode.push(operators.POPSCP);
951
+
952
+ bytecode.push(operators.JMP, start);
953
+
954
+ const end : number = bytecode.length;
955
+ loopInfo.end = end;
956
+
957
+ bytecode[jumpValueToEndPosition + 1] = end;
958
+
959
+ const positions : Array<{position : number, target : number | null}> =
960
+ [
961
+ ...loopInfo.breakPositions.map(position => ({
962
+ position,
963
+ target: end
964
+ })),
965
+
966
+ ...loopInfo.continuePositions.map(position => ({
967
+ position,
968
+ target: loopInfo.continueTarget
969
+ }))
970
+ ]
971
+
972
+ for (const {position, target} of positions) bytecode[position + 1] = target;
973
+
974
+ loopStack.pop();
975
+ }
976
+ ],
977
+
978
+ ["DoWhileStatement",
979
+ (node : any, bytecode : Bytecode) : void =>
980
+ {
981
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
982
+
983
+ const start : number = bytecode.length;
984
+
985
+ const loopInfo : LoopInfo =
986
+ {
987
+ start,
988
+ continueTarget: null,
989
+ end: null,
990
+ breakPositions: [],
991
+ continuePositions: []
992
+ };
993
+ loopStack.push(loopInfo);
994
+
995
+ bytecode.push(operators.PUSHSCP);
996
+ node.body.forEach((n : any) => parseObject(n, bytecode));
997
+ bytecode.push(operators.POPSCP);
998
+
999
+ loopInfo.continueTarget = bytecode.length;
1000
+
1001
+ parseObject(node.condition, bytecode, true);
1002
+ const jumpToEndPosition : number = bytecode.length;
1003
+ bytecode.push(operators.JZ, 0); // jump to end if condition is falsy
1004
+ bytecode.push(operators.JMP, start);
1005
+
1006
+ const end : number = bytecode.length;
1007
+ loopInfo.end = end;
1008
+ bytecode[jumpToEndPosition + 1] = end;
1009
+
1010
+ const positions : Array<{position : number, target : number | null}> =
1011
+ [
1012
+ ...loopInfo.breakPositions.map(position => ({
1013
+ position,
1014
+ target: end
1015
+ })),
1016
+
1017
+ ...loopInfo.continuePositions.map(position => ({
1018
+ position,
1019
+ target: loopInfo.continueTarget
1020
+ }))
1021
+ ]
1022
+
1023
+ for (const {position, target} of positions) bytecode[position + 1] = target;
1024
+
1025
+ loopStack.pop();
1026
+ }
1027
+ ],
1028
+
1029
+ ["TryStatement",
1030
+ (node : any, bytecode : Bytecode) : void =>
1031
+ {
1032
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1033
+
1034
+ const tryStartPosition = bytecode.length;
1035
+ bytecode.push(operators.TRY, 0, 0); // [TRY, catchPosition, finallyPosition]
1036
+
1037
+ // parse try body
1038
+ bytecode.push(operators.PUSHSCP);
1039
+ node.tryBlock.forEach((n: any) => parseObject(n, bytecode));
1040
+ bytecode.push(operators.POPSCP);
1041
+
1042
+ // jump past catch block if no error occurred
1043
+ const skipCatchPosition = bytecode.length;
1044
+ bytecode.push(operators.JMP, 0);
1045
+
1046
+ // catch block
1047
+ const catchPosition = bytecode.length;
1048
+ bytecode[tryStartPosition + 1] = catchPosition;
1049
+
1050
+ bytecode.push(operators.PUSHSCP);
1051
+ bytecode.push(operators.ALLOC, node.errorVariable);
1052
+ bytecode.push(operators.STORE, node.errorVariable);
1053
+
1054
+ node.catchBlock.forEach((n: any) => parseObject(n, bytecode));
1055
+ bytecode.push(operators.POPSCP);
1056
+
1057
+ // end of catch block
1058
+ const afterCatchPosition = bytecode.length;
1059
+ bytecode[skipCatchPosition + 1] = afterCatchPosition;
1060
+
1061
+ if (node.finallyBlock)
1062
+ {
1063
+ const finallyPosition = bytecode.length;
1064
+ bytecode[tryStartPosition + 2] = finallyPosition;
1065
+
1066
+ bytecode.push(operators.PUSHSCP);
1067
+ node.finallyBlock.forEach((n: any) => parseObject(n, bytecode));
1068
+ bytecode.push(operators.POPSCP);
1069
+ }
1070
+
1071
+ bytecode.push(operators.ENDTRY);
1072
+ }
1073
+ ],
1074
+
1075
+ ["FunctionDeclaration",
1076
+ (node : any, bytecode : Bytecode) : void =>
1077
+ {
1078
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1079
+
1080
+ const newBytecode : Bytecode = [];
1081
+
1082
+ (node.body ?? []).forEach((n : any) => parseObject(n, newBytecode));
1083
+
1084
+ // make sure the function returns smth
1085
+ newBytecode.push(operators.PUSH, null);
1086
+ newBytecode.push(operators.RETURN);
1087
+
1088
+ bytecode.push(operators.ALLOC, node.name);
1089
+
1090
+ const parameters = node.parameters.map
1091
+ (
1092
+ (parameter: any) => (
1093
+ {
1094
+ name : parameter.name,
1095
+ rest : parameter.rest,
1096
+ default : parameter.default ? parseObject(parameter.default, [], true) : null,
1097
+ typeAnnotation : parameter.typeAnnotation ?? null
1098
+ }
1099
+ )
1100
+ );
1101
+
1102
+ const functionObject =
1103
+ {
1104
+ bytecode : newBytecode,
1105
+ parameters,
1106
+ returnType : node.returnType ?? null,
1107
+ ast : node
1108
+ };
1109
+
1110
+ bytecode.push(operators.PUSH, functionObject);
1111
+ bytecode.push(operators.MKFUNC);
1112
+ bytecode.push(operators.STORE, node.name);
1113
+ }
1114
+ ],
1115
+
1116
+ ["MethodCall",
1117
+ (node : any, bytecode : Bytecode, keepValue : boolean) =>
1118
+ {
1119
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1120
+
1121
+ parseObject(node.object, bytecode, true);
1122
+ bytecode.push(operators.PUSH, node.property);
1123
+
1124
+ node.args.forEach
1125
+ (
1126
+ (argument : any) =>
1127
+ {
1128
+ if (argument.type === "SpreadElement")
1129
+ {
1130
+ parseObject(argument.argument, bytecode, true);
1131
+ bytecode.push(operators.SPREAD);
1132
+ }
1133
+ else
1134
+ parseObject(argument, bytecode, true);
1135
+ }
1136
+ );
1137
+
1138
+ bytecode.push(operators.CALLMETHOD, node.args.length);
1139
+
1140
+ if (!keepValue)
1141
+ bytecode.push(operators.POP);
1142
+ }
1143
+ ],
1144
+
1145
+ ["FunctionExpression",
1146
+ (node : any, bytecode : Bytecode, keepValue : boolean = true) : void =>
1147
+ {
1148
+ const newBytecode : Bytecode = [];
1149
+
1150
+ (node.body ?? []).forEach((n : any) => parseObject(n, newBytecode));
1151
+
1152
+ newBytecode.push(operators.PUSH, null);
1153
+ newBytecode.push(operators.RETURN);
1154
+
1155
+ const parameters = node.parameters.map
1156
+ (
1157
+ (parameter: any) => (
1158
+ {
1159
+ name : parameter.name,
1160
+ rest : parameter.rest,
1161
+ default : parameter.default ? parseObject(parameter.default, [], true) : null,
1162
+ typeAnnotation : parameter.typeAnnotation ?? null
1163
+ }
1164
+ )
1165
+ );
1166
+
1167
+ const functionObject =
1168
+ {
1169
+ bytecode : newBytecode,
1170
+ parameters,
1171
+ returnType : node.returnType ?? null,
1172
+ ast : node
1173
+ }
1174
+
1175
+ bytecode.push(operators.PUSH, functionObject);
1176
+ bytecode.push(operators.MKFUNC);
1177
+ if (node.name)
1178
+ bytecode.push(operators.STORE, node.name);
1179
+
1180
+ if (!keepValue)
1181
+ bytecode.push(operators.POP);
1182
+ }
1183
+ ],
1184
+
1185
+ ["SwitchStatement",
1186
+ (node : any, bytecode : Bytecode) : void =>
1187
+ {
1188
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1189
+
1190
+ parseObject(node.discriminant, bytecode, true);
1191
+ const temp = `__switch_temp_${tempCounter++}`;
1192
+ bytecode.push(operators.ALLOC, temp);
1193
+ bytecode.push(operators.STORE, temp);
1194
+
1195
+ const endJumps : number[] = [];
1196
+
1197
+ for (let i = 0; i < node.cases.length; i++)
1198
+ {
1199
+ const switchCase = node.cases[i];
1200
+
1201
+ bytecode.push(operators.LOAD, temp);
1202
+ parseObject(switchCase.test, bytecode, true);
1203
+ bytecode.push(operators.EQ);
1204
+
1205
+ const nextCaseJump = bytecode.length;
1206
+ bytecode.push(operators.JZ, 0);
1207
+
1208
+ // execute consequent
1209
+ bytecode.push(operators.PUSHSCP);
1210
+ switchCase.consequent.forEach((statement : any) => parseObject(statement, bytecode));
1211
+ bytecode.push(operators.POPSCP);
1212
+
1213
+ const endJump = bytecode.length;
1214
+ bytecode.push(operators.JMP, 0);
1215
+ endJumps.push(endJump);
1216
+
1217
+ // set next case position
1218
+ bytecode[nextCaseJump + 1] = bytecode.length;
1219
+ }
1220
+
1221
+ if (node.defaultCase)
1222
+ {
1223
+ bytecode.push(operators.PUSHSCP);
1224
+ node.defaultCase.consequent.forEach((statement : any) => parseObject(statement, bytecode));
1225
+ bytecode.push(operators.POPSCP);
1226
+ }
1227
+
1228
+ // set all end jumps to point here
1229
+ const endPosition = bytecode.length;
1230
+ endJumps.forEach(position => {
1231
+ bytecode[position + 1] = endPosition;
1232
+ });
1233
+ }
1234
+ ],
1235
+
1236
+ ["ReturnStatement",
1237
+ (node : any, bytecode : Bytecode) : void =>
1238
+ {
1239
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1240
+
1241
+ if (node.argument)
1242
+ parseObject(node.argument, bytecode, true);
1243
+ bytecode.push(operators.RETURN);
1244
+ }
1245
+ ],
1246
+
1247
+ ["ImportStatement",
1248
+ (node : any, bytecode : Bytecode) : void =>
1249
+ {
1250
+ bytecode.push(operators.SETLINE, node.row ?? 0, node.column ?? 0);
1251
+ bytecode.push(operators.PUSH, node.path);
1252
+ bytecode.push(operators.EXEC);
1253
+ }
1254
+ ],
1255
+ ]);
1256
+
1257
+ export function buildBytecode(ast : any, filename : string = "<anonymous>")
1258
+ {
1259
+ tempCounter = 0;
1260
+ const bytecode : Bytecode = [];
1261
+
1262
+ bytecode.push(operators.SETFILE, filename);
1263
+
1264
+ if (ast?.body?.length > 0)
1265
+ parseObject(ast, bytecode);
1266
+
1267
+ //console.dir(bytecode, {depth: null, colors: true});
1268
+ return bytecode;
1269
+ }