/* ilo.ts, an ilo in TypeScript. (c) Rick Carlino */ /* Run via `npx ts-node ilo.ts`. If you get errors, try running `npx tsc --init` first. */ type OutputCallback = (char: string) => void; function enforce32BitSignedInteger(value: number) { const int32Max = Math.pow(2, 31) - 1; const int32Min = -Math.pow(2, 31); value = value | 0; value = ((value - int32Min) % (int32Max - int32Min + 1)) + int32Min; return value; } export class Konilo { private m: Int32Array; private blocks: Int32Array; private ds: Int32Array; private as: Int32Array; private ip: number; private sp: number; private rp: number; private a: number; private b: number; private f: number; private s: number; private d: number; private l: number; private cycles: number; private input: string; private outputHandler: OutputCallback; constructor(public romURL: string, public blockURL: string) { this.m = new Int32Array(65536); this.blocks = new Int32Array(1024 * 1024); this.ds = new Int32Array(33); this.as = new Int32Array(257); this.ip = 0; this.sp = 0; this.rp = 0; this.a = 0; this.b = 0; this.f = 0; this.s = 0; this.d = 0; this.l = 0; this.cycles = 25000; this.input = ""; this.outputHandler = (char: string) => {}; } async initialize(): Promise { await Promise.all([ this.loadFile(this.romURL, this.m), this.loadFile(this.blockURL, this.blocks), ]); } async loadFile(url: string, target: Int32Array): Promise { await fetch(url) .then((response) => response.arrayBuffer()) .then((buffer) => { const dataView = new DataView(buffer); for (let i = 0; i < target.length; i++) { target[i] = dataView.getInt32(i * 4, true); } }) .catch((error) => { console.error("Error loading the file:", error); }); } async execute(): Promise { console.log("Hello from Konilo!"); let ticks = 0; while (this.ip < 65536 && this.input.length >= 1 && !isNaN(this.ip)) { const o = this.m[this.ip]; this.process(o & 0xff); this.process((o >> 8) & 0xff); this.process((o >> 16) & 0xff); this.process((o >> 24) & 0xff); this.ip = this.ip + 1; ticks += 1; if (ticks % this.cycles === 0) { ticks = 0; await this.sleep(0); } } await this.sleep(0); console.log("Goodbye from Konilo!"); } async sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } loadROM(): void { this.loadFile(this.romURL, this.m); } loadBlocks(): void { this.loadFile(this.blockURL, this.blocks); } setOutputHandler(callback: OutputCallback): void { this.outputHandler = callback; } send(str: string): void { this.input += str; } process(o: number) { switch (o) { case 0: break; case 1: this.li(); break; case 2: this.du(); break; case 3: this.dr(); break; case 4: this.sw(); break; case 5: this.pu(); break; case 6: this.po(); break; case 7: this.ju(); break; case 8: this.ca(); break; case 9: this.cc(); break; case 10: this.cj(); break; case 11: this.re(); break; case 12: this.eq(); break; case 13: this.ne(); break; case 14: this.lt(); break; case 15: this.gt(); break; case 16: this.fe(); break; case 17: this.st(); break; case 18: this.ad(); break; case 19: this.su(); break; case 20: this.mu(); break; case 21: this.di(); break; case 22: this.an(); break; case 23: this.or(); break; case 24: this.xo(); break; case 25: this.sl(); break; case 26: this.sr(); break; case 27: this.cp(); break; case 28: this.cy(); break; case 29: this.io(); break; default: break; } this.guard(); } guard() { this.ds[this.sp] = enforce32BitSignedInteger(this.ds[this.sp]); if (this.sp >= 2) { this.ds[this.sp - 1] = enforce32BitSignedInteger(this.ds[this.sp - 1]); } } save_ip() { this.rp += 1; this.as[this.rp] = this.ip; } pop() { this.sp -= 1; return this.ds[this.sp + 1]; } push(v: number) { this.ds[this.sp + 1] = v; this.sp += 1; } symmetric() { if (this.b >= 0 && this.ds[this.sp - 1] < 0) { this.ds[this.sp] += 1; this.ds[this.sp - 1] -= this.b; } } li() { this.ip += 1; this.push(this.m[this.ip]); } du() { this.push(this.ds[this.sp]); } dr() { this.ds[this.sp] = 0; this.sp -= 1; } sw() { const a = this.ds[this.sp]; this.ds[this.sp] = this.ds[this.sp - 1]; this.ds[this.sp - 1] = a; } pu() { this.rp += 1; this.as[this.rp] = this.pop(); } po() { this.push(this.as[this.rp]); this.rp -= 1; } ju() { this.ip = this.pop() - 1; } ca() { this.save_ip(); this.ip = this.pop() - 1; } cc() { const a = this.pop(); if (this.pop()) { this.save_ip(); this.ip = a - 1; } } cj() { const a = this.pop(); if (this.pop()) { this.ip = a - 1; } } re() { this.ip = this.as[this.rp]; this.rp -= 1; } eq() { this.ds[this.sp - 1] = this.ds[this.sp - 1] == this.ds[this.sp] ? -1 : 0; this.sp -= 1; } ne() { this.ds[this.sp - 1] = this.ds[this.sp - 1] != this.ds[this.sp] ? -1 : 0; this.sp -= 1; } lt() { this.ds[this.sp - 1] = this.ds[this.sp - 1] < this.ds[this.sp] ? -1 : 0; this.sp -= 1; } gt() { this.ds[this.sp - 1] = this.ds[this.sp - 1] > this.ds[this.sp] ? -1 : 0; this.sp -= 1; } fe() { this.ds[this.sp] = this.m[this.ds[this.sp]]; } st() { this.m[this.ds[this.sp]] = this.ds[this.sp - 1]; this.sp -= 2; } ad() { this.ds[this.sp - 1] += this.ds[this.sp]; this.sp -= 1; } su() { this.ds[this.sp - 1] -= this.ds[this.sp]; this.sp -= 1; } mu() { this.ds[this.sp - 1] *= this.ds[this.sp]; this.sp -= 1; } di() { this.a = this.ds[this.sp]; this.b = this.ds[this.sp - 1]; this.ds[this.sp] = Math.floor(this.b / this.a); this.ds[this.sp - 1] = this.b % this.a; this.symmetric(); } an() { this.ds[this.sp - 1] = this.ds[this.sp] & this.ds[this.sp - 1]; this.sp -= 1; } or() { this.ds[this.sp - 1] = this.ds[this.sp] | this.ds[this.sp - 1]; this.sp -= 1; } xo() { this.ds[this.sp - 1] = this.ds[this.sp] ^ this.ds[this.sp - 1]; this.sp -= 1; } sl() { this.ds[this.sp - 1] = this.ds[this.sp - 1] << this.ds[this.sp]; this.sp -= 1; } sr() { this.ds[this.sp - 1] = this.ds[this.sp - 1] >> this.ds[this.sp]; this.sp -= 1; } cp() { let l = this.pop(); let d = this.pop(); let s = this.ds[this.sp]; this.ds[this.sp] = -1; while (l) { if (this.m[d] !== this.m[s]) { this.ds[this.sp] = 0; } l -= 1; s += 1; d += 1; } } io() { const dev = this.pop(); switch (dev) { case 0: this.ioa(); break; case 1: this.iob(); break; case 2: this.ioc(); break; case 3: this.iod(); break; case 4: this.ioe(); break; case 5: this.iof(); break; case 6: this.iog(); break; case 7: this.ioh(); break; default: console.log("wtf? " + dev); break; } } cy() { let l = this.pop(); let d = this.pop(); let s = this.pop(); while (l) { this.m[d] = this.m[s]; l -= 1; s += 1; d += 1; } } ioa() { const c = String.fromCharCode(this.pop()); this.outputHandler(c); } iob() { this.a = this.input.charCodeAt(0); this.input = this.input.slice(1); this.push(this.a); } ioc() { const buf = this.pop(); const blk = this.pop(); let i = 0; while (i < 1024) { this.m[buf + i] = this.blocks[blk * 1024 + i]; i += 1; } } iod() { const buf = this.pop(); const blk = this.pop(); let i = 0; while (i < 1024) { this.blocks[blk * 1024 + i] = this.m[buf + i]; i += 1; } } ioe() {} iof() { this.ip = -1; this.sp = 0; this.rp = 0; this.loadFile(this.romURL, this.m); } iog() { this.ip = 65536; this.input = ""; } ioh() { this.push(this.sp); this.push(this.rp); } } (async () => { const vm = new Konilo( "http://forth.works/demo/ilo.rom", "http://forth.works/demo/ilo.blocks" ); let outputBuffer = ""; vm.setOutputHandler((char: string) => { if (char === "\n") { console.log(outputBuffer); outputBuffer = ""; } else { outputBuffer += char; } }); await vm.initialize(); vm.send("list\n\n"); vm.execute(); })();