Computer/src/computer.ts

302 lines
8.3 KiB
TypeScript
Raw Normal View History

2024-02-15 03:54:23 +00:00
import { u8, $ } from "./etc";
// Set R1 to 255, then print R1, then go back to beginning
export enum Instr {
NoOp,
Goto,
GotoIfLowBit,
LoadToRegister,
WriteToMem,
CopyRegReg,
AssignRegister,
Add,
And,
Or,
Not,
LeftBitShift,
RightBitShift,
Equals,
LessThan,
GreaterThan,
Print,
PrintASCII,
2024-02-15 04:05:22 +00:00
HaltCatchFire,
2024-02-15 03:54:23 +00:00
}
const InstParamCount = new Map();
InstParamCount.set(Instr.NoOp, 0);
InstParamCount.set(Instr.Goto, 1);
InstParamCount.set(Instr.GotoIfLowBit, 2);
InstParamCount.set(Instr.LoadToRegister, 2);
InstParamCount.set(Instr.WriteToMem, 2);
InstParamCount.set(Instr.CopyRegReg, 2);
InstParamCount.set(Instr.AssignRegister, 2);
InstParamCount.set(Instr.Add, 2);
InstParamCount.set(Instr.And, 2);
InstParamCount.set(Instr.Or, 2);
InstParamCount.set(Instr.Not, 1);
InstParamCount.set(Instr.LeftBitShift, 2);
InstParamCount.set(Instr.RightBitShift, 2);
InstParamCount.set(Instr.Equals, 3);
InstParamCount.set(Instr.LessThan, 3);
InstParamCount.set(Instr.GreaterThan, 3);
InstParamCount.set(Instr.Print, 1);
InstParamCount.set(Instr.PrintASCII, 1);
2024-02-15 04:05:22 +00:00
InstParamCount.set(Instr.HaltCatchFire, 0);
2024-02-15 03:54:23 +00:00
export type TempInstrState = {
pos: u8;
params_found: number;
instr: Instr;
params: Uint8Array;
};
export type ComputerState = {
memory: Uint8Array;
program_counter: u8;
registers: Uint8Array;
current_instruction: TempInstrState | null;
};
export class Computer {
private memory: Uint8Array;
private program_counter: u8;
private registers: Uint8Array;
private current_instr: TempInstrState | null;
private state_change_callback: (c: ComputerState) => void;
constructor(callback: (c: ComputerState) => void) {
// 256 bytes for both program and general purpose memory.
this.memory = new Uint8Array(256);
this.registers = new Uint8Array(8);
this.program_counter = 0;
this.current_instr = null;
this.state_change_callback = callback;
}
cycle(): void {
const current_byte = this.memory[this.program_counter];
if (this.current_instr === null) {
const parsed_instruction = Computer.parse_instruction(current_byte);
if (parsed_instruction === null) {
2024-02-15 04:27:15 +00:00
// console.log("invalid instruction");
2024-02-15 03:54:23 +00:00
this.step_forward();
return;
}
const instr_param_count = InstParamCount.get(parsed_instruction);
this.current_instr = {
pos: this.program_counter,
instr: parsed_instruction,
params_found: 0,
params: new Uint8Array(instr_param_count),
};
}
if (this.current_instr.pos === this.program_counter) {
this.step_forward();
return;
}
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.current_instr.params[this.current_instr.params_found] = current_byte;
this.current_instr.params_found += 1;
}
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.step_forward();
return;
}
const should_step = this.execute_instruction(this.current_instr);
this.current_instr = null;
if (should_step) {
this.step_forward();
} else {
this.state_change_callback(this.get_state());
}
}
private execute_instruction(inst: TempInstrState): boolean {
const instr_param_count = InstParamCount.get(inst.instr);
const current_pram_count = inst.params.length;
if (instr_param_count !== current_pram_count) {
throw new Error(
`Tried executing instruction #${inst.instr} without proper parameters. (has ${current_pram_count}, needs ${instr_param_count})`
);
}
switch (inst.instr) {
case Instr.Goto: {
const [parameter] = inst.params;
2024-02-15 04:27:15 +00:00
// console.log(`Goto ${parameter}`);
2024-02-15 03:54:23 +00:00
this.program_counter = parameter;
return false;
}
case Instr.GotoIfLowBit: {
const [mem_address, register_no] = inst.params;
if (this.registers[register_no] % 2 === 1) {
this.program_counter = mem_address;
return false;
}
return true;
}
case Instr.AssignRegister: {
const [register_no, new_value] = inst.params;
if (register_no >= this.registers.length) {
throw new Error(`Got register number ${register_no} in assign register`);
}
2024-02-15 04:27:15 +00:00
// console.log(`Set register ${register_no} to ${new_value}`);
2024-02-15 03:54:23 +00:00
this.registers[register_no] = new_value;
break;
}
case Instr.LoadToRegister: {
const [register_no, mem_address] = inst.params;
this.registers[register_no] = this.memory[this.registers[mem_address]];
break;
}
case Instr.WriteToMem: {
const [register_no, mem_address] = inst.params;
this.memory[mem_address] = this.memory[this.registers[mem_address]];
break;
}
case Instr.Add: {
const [register_1, register_2] = inst.params;
this.registers[register_1] += this.registers[register_2];
break;
}
case Instr.And: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] &= this.registers[register_no_2];
break;
}
case Instr.Or: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] |= this.registers[register_no_2];
break;
}
case Instr.Not: {
const [register_no_1] = inst.params;
this.registers[register_no_1] = ~this.registers[register_no_1];
break;
}
case Instr.LeftBitShift: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] <<= this.registers[register_no_2];
break;
}
case Instr.RightBitShift: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] >>= this.registers[register_no_2];
break;
}
case Instr.Equals: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] === this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
} else {
this.registers[register_out] = 0x00;
}
break;
}
case Instr.LessThan: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] < this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
}
break;
}
case Instr.GreaterThan: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] > this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
}
break;
}
2024-02-15 05:32:54 +00:00
case Instr.Print: {
const [register_no] = inst.params;
const value = this.registers[register_no];
$("printout").textContent += this.registers[register_no].toString(10);
break;
}
2024-02-15 03:54:23 +00:00
case Instr.PrintASCII: {
const [register_num] = inst.params;
const ASCIIbyte = this.registers[register_num];
const char = String.fromCharCode(ASCIIbyte);
2024-02-15 04:27:15 +00:00
// console.log(char);
2024-02-15 03:54:23 +00:00
$("printout").textContent += char;
break;
}
2024-02-15 04:27:15 +00:00
case Instr.HaltCatchFire: {
throw new Error("FIRE FIRE FIRE FIRE");
}
case Instr.CopyRegReg: {
const [register_no_to, register_no_from] = inst.params;
this.registers[register_no_to] = this.registers[register_no_from];
break;
}
2024-02-15 03:54:23 +00:00
default:
break;
}
return true;
}
load_program(program: Array<u8>): void {
const max_loop = Math.min(this.memory.length, program.length);
for (let i = 0; i < max_loop; i++) {
this.memory[i] = program[i];
}
this.program_counter = 0;
this.state_change_callback(this.get_state());
}
private step_forward(): void {
this.program_counter = (this.program_counter + 1) % 256;
this.state_change_callback(this.get_state());
}
get_state(): ComputerState {
return {
memory: this.memory,
program_counter: this.program_counter,
registers: this.registers,
current_instruction: this.current_instr,
};
}
static parse_instruction(byte: u8): null | Instr {
if (byte === 0x00) return Instr.NoOp;
if (byte === 0x10) return Instr.Goto;
if (byte === 0x11) return Instr.GotoIfLowBit;
if (byte === 0x20) return Instr.LoadToRegister;
if (byte === 0x21) return Instr.WriteToMem;
if (byte === 0x2f) return Instr.AssignRegister;
if (byte === 0x28) return Instr.CopyRegReg;
if (byte === 0x40) return Instr.Add;
if (byte === 0x48) return Instr.And;
if (byte === 0x49) return Instr.Or;
if (byte === 0x4a) return Instr.Not;
if (byte === 0x4b) return Instr.LeftBitShift;
if (byte === 0x4c) return Instr.RightBitShift;
if (byte === 0x50) return Instr.Equals;
if (byte === 0x51) return Instr.LessThan;
if (byte === 0x51) return Instr.GreaterThan;
if (byte === 0xff) return Instr.Print;
if (byte === 0xfe) return Instr.PrintASCII;
2024-02-15 04:05:22 +00:00
if (byte === 0x66) return Instr.HaltCatchFire;
2024-02-15 03:54:23 +00:00
return null;
}
}