516 lines
9.2 KiB
TypeScript
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();
|
|
})();
|