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) {
|
|
|
|
console.log("invalid instruction");
|
|
|
|
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) {
|
|
|
|
// console.log(`Parameter count not fulfilled. Found new parameter ${current_byte}`);
|
|
|
|
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.Print: {
|
|
|
|
const [register_no] = inst.params;
|
|
|
|
const value = this.registers[register_no];
|
|
|
|
console.log(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Instr.Goto: {
|
|
|
|
const [parameter] = inst.params;
|
|
|
|
console.log(`Goto ${parameter}`);
|
|
|
|
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`);
|
|
|
|
}
|
|
|
|
console.log(`Set register ${register_no} to ${new_value}`);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
case Instr.PrintASCII: {
|
|
|
|
const [register_num] = inst.params;
|
|
|
|
const ASCIIbyte = this.registers[register_num];
|
|
|
|
|
|
|
|
const char = String.fromCharCode(ASCIIbyte);
|
|
|
|
|
|
|
|
console.log(char);
|
|
|
|
$("printout").textContent += char;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|