ilo-vm/source/ilo.ts

516 lines
9.2 KiB
TypeScript

/* 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<void> {
await Promise.all([
this.loadFile(this.romURL, this.m),
this.loadFile(this.blockURL, this.blocks),
]);
}
async loadFile(url: string, target: Int32Array): Promise<void> {
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<void> {
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<void> {
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();
})();