MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1const STACK_SIZE = 256;
2const PROGRAM_SIZE = 1024;
3
4const Opcode = {
5 OP_PUSH: 0,
6 OP_POP: 1,
7 OP_ADD: 2,
8 OP_SUB: 3,
9 OP_MUL: 4,
10 OP_DIV: 5,
11 OP_PRINT: 6,
12 OP_JMP: 7,
13 OP_JZ: 8,
14 OP_LT: 9,
15 OP_EQ: 10,
16 OP_NEG: 11,
17 OP_HALT: 12
18};
19
20const opcodeNames = {
21 [Opcode.OP_PUSH]: 'OP_PUSH',
22 [Opcode.OP_POP]: 'OP_POP',
23 [Opcode.OP_ADD]: 'OP_ADD',
24 [Opcode.OP_SUB]: 'OP_SUB',
25 [Opcode.OP_MUL]: 'OP_MUL',
26 [Opcode.OP_DIV]: 'OP_DIV',
27 [Opcode.OP_PRINT]: 'OP_PRINT',
28 [Opcode.OP_JMP]: 'OP_JMP',
29 [Opcode.OP_JZ]: 'OP_JZ',
30 [Opcode.OP_LT]: 'OP_LT',
31 [Opcode.OP_EQ]: 'OP_EQ',
32 [Opcode.OP_NEG]: 'OP_NEG',
33 [Opcode.OP_HALT]: 'OP_HALT'
34};
35
36// Debug settings
37const DEBUG_ENABLED = true;
38
39class VM {
40 constructor() {
41 this.sp = -1;
42 this.pc = 0;
43 this.stack = new Array(STACK_SIZE).fill(0);
44 this.program = new Uint8Array(PROGRAM_SIZE);
45 }
46
47 free() {
48 this.sp = -1;
49 this.pc = 0;
50 }
51
52 checkStackHasValues(count) {
53 if (this.sp < count - 1) {
54 throw new Error(`Stack Underflow! Need ${count} value(s), but stack has ${this.sp + 1}`);
55 }
56 }
57
58 push(value) {
59 if (this.sp >= STACK_SIZE - 1) {
60 throw new Error('Stack Overflow!');
61 }
62 this.sp++;
63 this.stack[this.sp] = value;
64 this.tracePush(value);
65 }
66
67 pop() {
68 this.checkStackHasValues(1);
69 const result = this.stack[this.sp];
70 this.sp--;
71 this.tracePop(result);
72 return result;
73 }
74
75 performBinary(op) {
76 this.checkStackHasValues(2);
77 const b = this.pop();
78 const a = this.pop();
79 let result;
80 switch (op) {
81 case '+':
82 result = a + b;
83 break;
84 case '-':
85 result = a - b;
86 break;
87 case '*':
88 result = a * b;
89 break;
90 case '/':
91 result = Math.trunc(a / b);
92 break;
93 }
94 this.push(result);
95 }
96
97 performUnary(op) {
98 this.checkStackHasValues(1);
99 const a = this.pop();
100 let result;
101 switch (op) {
102 case '-':
103 result = -a;
104 break;
105 }
106 this.push(result);
107 }
108
109 execute() {
110 this.traceHeader();
111
112 while (true) {
113 const op = this.program[this.pc];
114 this.traceInstruction(this.pc, op);
115 this.pc++;
116
117 switch (op) {
118 case Opcode.OP_PUSH: {
119 const val = this.readInt64();
120 this.push(val);
121 break;
122 }
123 case Opcode.OP_POP:
124 this.pop();
125 break;
126 case Opcode.OP_HALT:
127 this.traceHalt();
128 return;
129 case Opcode.OP_PRINT:
130 this.checkStackHasValues(1);
131 console.log(this.stack[this.sp]);
132 break;
133 case Opcode.OP_ADD:
134 this.performBinary('+');
135 break;
136 case Opcode.OP_SUB:
137 this.performBinary('-');
138 break;
139 case Opcode.OP_MUL:
140 this.performBinary('*');
141 break;
142 case Opcode.OP_DIV:
143 this.performBinary('/');
144 break;
145 case Opcode.OP_NEG:
146 this.performUnary('-');
147 break;
148 default:
149 throw new Error('Unknown Opcode!');
150 }
151
152 this.printStack();
153 this.traceSeparator();
154 }
155 }
156
157 readInt64() {
158 // Read 8 bytes as little-endian 64-bit integer
159 // JS doesn't handle 64-bit ints natively, but for small values this works
160 let val = 0;
161 for (let i = 0; i < 8; i++) {
162 val |= this.program[this.pc + i] << (i * 8);
163 }
164 this.pc += 8;
165 return val;
166 }
167
168 // Debug/trace methods
169 traceHeader() {
170 if (!DEBUG_ENABLED) return;
171 console.log('\n\x1b[96m\x1b[1m=== VM Execution Trace ===\x1b[0m\n');
172 }
173
174 traceInstruction(pc, opcode) {
175 if (!DEBUG_ENABLED) return;
176 const name = opcodeNames[opcode] || 'UNKNOWN';
177 console.log(`\x1b[94m[PC=${pc}]\x1b[0m \x1b[32m\x1b[1m${name}\x1b[0m`);
178 }
179
180 tracePush(value) {
181 if (!DEBUG_ENABLED) return;
182 console.log(`\x1b[2m \x1b[35mPUSH\x1b[0m \x1b[93m${value}\x1b[0m`);
183 }
184
185 tracePop(value) {
186 if (!DEBUG_ENABLED) return;
187 console.log(`\x1b[2m \x1b[31mPOP \x1b[0m \x1b[93m${value}\x1b[0m`);
188 }
189
190 traceHalt() {
191 if (!DEBUG_ENABLED) return;
192 console.log('\n\x1b[91m\x1b[1m=== Execution Halted ===\x1b[0m');
193 }
194
195 traceSeparator() {
196 if (!DEBUG_ENABLED) return;
197 console.log();
198 }
199
200 printStack() {
201 if (!DEBUG_ENABLED) return;
202 let stackStr = this.stack.slice(0, this.sp + 1).join(' ');
203 console.log(`\x1b[2m Stack \x1b[36m[sp=${this.sp}]\x1b[0m: [ \x1b[33m${stackStr}\x1b[0m ]`);
204 }
205}
206
207// Helper to write int64 to program
208function writeInt(program, pc, val) {
209 for (let i = 0; i < 8; i++) {
210 program[pc.value] = (val >> (i * 8)) & 0xff;
211 pc.value++;
212 }
213}
214
215// Main
216function main() {
217 const v = new VM();
218 const values = [10, 13, 6, 25, 42];
219 const pc = { value: 0 };
220
221 for (const val of values) {
222 v.program[pc.value++] = Opcode.OP_PUSH;
223 writeInt(v.program, pc, val);
224 }
225
226 v.program[pc.value++] = Opcode.OP_MUL;
227 v.program[pc.value++] = Opcode.OP_ADD;
228 v.program[pc.value++] = Opcode.OP_NEG;
229 v.program[pc.value++] = Opcode.OP_DIV;
230 v.program[pc.value++] = Opcode.OP_SUB;
231 v.program[pc.value++] = Opcode.OP_PRINT;
232 v.program[pc.value++] = Opcode.OP_HALT;
233
234 v.execute();
235 v.free();
236}
237
238main();