init
This commit is contained in:
commit
e53d40d5be
64
.eslintrc.json
Executable file
64
.eslintrc.json
Executable file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
"tab",
|
||||
{"SwitchCase": 1}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"warn",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"space-before-blocks":["warn","always"],
|
||||
"quote-props" : ["warn","as-needed"],
|
||||
"dot-notation":"warn",
|
||||
"one-var":["warn","never"],
|
||||
"no-use-before-define":"warn",
|
||||
"no-multi-assign":"warn",
|
||||
"no-else-return":"warn",
|
||||
"spaced-comment":"warn",
|
||||
"prefer-destructuring":"warn",
|
||||
"no-restricted-globals":"warn",
|
||||
"prefer-template":"warn",
|
||||
"class-methods-use-this":"warn",
|
||||
"template-curly-spacing": ["warn", "never"],
|
||||
"no-useless-rename":"warn",
|
||||
"no-useless-escape":"warn",
|
||||
"no-duplicate-imports":"warn",
|
||||
"no-useless-constructor":"warn",
|
||||
"no-loop-func":"warn",
|
||||
"no-param-reassign":"warn",
|
||||
"prefer-arrow-callback":"warn",
|
||||
"no-array-constructor": "warn",
|
||||
"object-shorthand": "warn",
|
||||
"no-empty": "off",
|
||||
"no-self-compare": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "warn"
|
||||
}
|
||||
}
|
3
.gitignore
vendored
Executable file
3
.gitignore
vendored
Executable file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
out/
|
||||
dist/
|
9
.prettierrc.json
Executable file
9
.prettierrc.json
Executable file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"endOfLine": "lf",
|
||||
"singleQuote": false,
|
||||
"printWidth": 100
|
||||
|
||||
}
|
26
ISA.txt
Normal file
26
ISA.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
INSTRUCTIONS
|
||||
--- Control Flow ---
|
||||
0x00: NoOp - 0 Parameter
|
||||
0x10: Goto - 1 Parameter - moves the program counter to (P1) in program memory
|
||||
0x11: Goto if low bit - 2 Parameter - Moves the program counter to (P1) if register (P2)'s least significant bit is 1.
|
||||
--- Memory and register management ---
|
||||
0x20: Load to register - 2 Parameter - Loads into register (P1) the byte at processing memory location (P2)
|
||||
0x21: Write register to memory - 2 Parameter - Writes the byte in register (P1) to the processing memory location (P2)
|
||||
0x28: Copy Register -> Register - 2 Parameter - Copies byte from register (P1) to register (P2)
|
||||
0x2F: Assign value to register - 2 Parameter - Assigns register (P1) to value (P2)
|
||||
--- Operations ---
|
||||
0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)
|
||||
0x41: Reserved
|
||||
--- Bit Operations ---
|
||||
0x48: Bitwise and - 2 Parameter - Ands & each bit of register (P1) and register (P2) and stores result to register (P1)
|
||||
0x49: Bitwise or - you get the point
|
||||
0x4A: Bitwise not - 1 Parameter - Inverts each bit of register (P1)
|
||||
0x4B: Left bit shift - 2 Parameter - Shifts bits in register (P1) to the left by (P2) and stores result to register (P1)
|
||||
0x4C: right bit shift- 2 Parameter - same as left
|
||||
--- Comparison Operations ---
|
||||
0x50: Equals - 3 Parameter - If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01
|
||||
0x51: Less than - 3 Parameter - If byte in register (P1) less than byte in register (P2), set byte in register (P3) to 0x01
|
||||
0x52: Greater than - 3 Parameter - If byte in register (P1) greater than byte in register (P2), set byte in register (P3) to 0x01
|
||||
--- Development ---
|
||||
0xFE: Print byte as ASCII from register (invalid does nothing) - 1 Parameter - Prints the ASCII byte in register (P1) to console
|
||||
0xFF: Print byte from register - 1 Parameter - Prints the byte in register (P1) to console
|
5
TODO
Normal file
5
TODO
Normal file
|
@ -0,0 +1,5 @@
|
|||
Highlight memory cells based on what they have been used for
|
||||
add screen (VRAM?)
|
||||
live memory and register editing
|
||||
draw lines between registers and memory when used
|
||||
add description for the current instruction
|
19
index.html
Normal file
19
index.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="./dist/main.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="container"></div>
|
||||
<div id="printout"></div>
|
||||
</div>
|
||||
<button type="button" id="pause_play_button">Start</button>
|
||||
<button type="button" id="step_button">step</button>
|
||||
<input type="file" name="binary_upload" id="binary_upload" />
|
||||
</body>
|
||||
</html>
|
2852
package-lock.json
generated
Normal file
2852
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
16
package.json
Normal file
16
package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.44.0",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production",
|
||||
"watch": "webpack --mode=development --watch"
|
||||
}
|
||||
}
|
291
src/computer.ts
Normal file
291
src/computer.ts
Normal file
|
@ -0,0 +1,291 @@
|
|||
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,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
}
|
12
src/etc.ts
Normal file
12
src/etc.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
// The u8 type represents an unsigned 8bit integer: byte. It does not add any safety, other than as a hint to the programmer.
|
||||
export type u8 = number;
|
||||
|
||||
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
|
||||
export function el(type: string, id?: string): HTMLElement {
|
||||
const div = document.createElement(type);
|
||||
if (id === undefined) {
|
||||
return div;
|
||||
}
|
||||
div.id = id;
|
||||
return div;
|
||||
}
|
52
src/index.ts
Normal file
52
src/index.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Computer } from "./computer";
|
||||
import { $ } from "./etc";
|
||||
import { UI } from "./ui";
|
||||
|
||||
function main(): void {
|
||||
// const program = [0x2f, 0x01, 0x01, 0x40, 0x00, 0x01, 0x21, 0x00, 0x02, 0x10, 0x00];
|
||||
const program = [0x2f, 0x00, 0x49, 0xfe, 0x00, 0x10, 0x03];
|
||||
|
||||
const container = document.getElementById("container");
|
||||
if (container === null) {
|
||||
throw new Error("no");
|
||||
}
|
||||
|
||||
const ui = new UI(container);
|
||||
|
||||
const computer = new Computer(ui.stateUpdateEvent.bind(ui));
|
||||
|
||||
computer.load_program(program);
|
||||
ui.set_step_func(computer.cycle.bind(computer));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any>window).comp = computer;
|
||||
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
$("binary_upload").addEventListener("change", function (e) {
|
||||
if (e.target === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-destructuring
|
||||
const file: File = (<any>e.target).files[0];
|
||||
const reader = new FileReader();
|
||||
console.log(file);
|
||||
reader.addEventListener("load", (e) => {
|
||||
if (e.target !== null) {
|
||||
const data = e.target.result;
|
||||
if (data instanceof ArrayBuffer) {
|
||||
const view = new Uint8Array(data);
|
||||
const array = [...view];
|
||||
ui.stop_auto();
|
||||
computer.load_program(array);
|
||||
} else {
|
||||
console.log("not array");
|
||||
}
|
||||
}
|
||||
});
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
main();
|
||||
});
|
151
src/ui.ts
Normal file
151
src/ui.ts
Normal file
|
@ -0,0 +1,151 @@
|
|||
import { ComputerState } from "./computer";
|
||||
import { $, el } from "./etc";
|
||||
|
||||
export class UI {
|
||||
container: HTMLElement;
|
||||
program_memory: HTMLElement;
|
||||
program_memory_cells: Array<HTMLElement>;
|
||||
registers: HTMLElement;
|
||||
register_cells: Array<HTMLElement>;
|
||||
step_func: null | (() => void);
|
||||
auto_running: boolean;
|
||||
constructor(parent: HTMLElement) {
|
||||
this.container = parent;
|
||||
|
||||
const program_mem = el("div", "program_memory");
|
||||
this.program_memory_cells = [];
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const mem_cell = el("div", `p_${i}`);
|
||||
mem_cell.textContent = "0x00";
|
||||
program_mem.appendChild(mem_cell);
|
||||
this.program_memory_cells.push(mem_cell);
|
||||
}
|
||||
this.program_memory_cells[0].classList.add("div", "program_counter");
|
||||
this.program_memory = program_mem;
|
||||
|
||||
this.register_cells = [];
|
||||
const registers = el("div", "registers");
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const reg_cell = el("div", `R_${i}`);
|
||||
reg_cell.textContent = "00";
|
||||
reg_cell.setAttribute("contenteditable", "true");
|
||||
reg_cell.setAttribute("spellcheck", "false");
|
||||
registers.appendChild(reg_cell);
|
||||
this.register_cells.push(reg_cell);
|
||||
}
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
registers.addEventListener("input", function (e) {
|
||||
const allowed_chars = "0123456789ABCDEFG";
|
||||
const r = e.target as HTMLElement;
|
||||
let data = (r.textContent as string).toUpperCase();
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (!allowed_chars.includes(data[i])) {
|
||||
data = "00";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length > 2) {
|
||||
// data = r.textContent?.substring(0, 2) ?? "00";
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
// r.textContent = ;
|
||||
});
|
||||
|
||||
registers.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
(e.target as HTMLElement).blur();
|
||||
}
|
||||
});
|
||||
registers.addEventListener("blur", (e) => {
|
||||
const allowed_chars = "0123456789ABCDEFG";
|
||||
const r = e.target as HTMLElement;
|
||||
const data = (r.textContent as string).toUpperCase();
|
||||
});
|
||||
|
||||
this.registers = registers;
|
||||
|
||||
this.container.append(registers, program_mem);
|
||||
this.step_func = null;
|
||||
this.auto_running = false;
|
||||
const pp_button = $("pause_play_button");
|
||||
if (pp_button === null) {
|
||||
throw new Error("Cant find pause_play button");
|
||||
}
|
||||
pp_button.addEventListener("click", () => {
|
||||
if (this.auto_running) {
|
||||
this.stop_auto();
|
||||
pp_button.textContent = "Starp";
|
||||
} else {
|
||||
this.start_auto();
|
||||
pp_button.textContent = "Storp";
|
||||
}
|
||||
});
|
||||
document.getElementById("step_button")?.addEventListener("click", () => {
|
||||
if (this.auto_running) {
|
||||
this.stop_auto();
|
||||
}
|
||||
if (this.step_func === null) {
|
||||
return;
|
||||
}
|
||||
this.step_func();
|
||||
});
|
||||
}
|
||||
|
||||
start_auto(speed: number = 0): void {
|
||||
if (this.step_func === null) {
|
||||
return;
|
||||
}
|
||||
if (this.auto_running) {
|
||||
return;
|
||||
}
|
||||
this.auto_running = true;
|
||||
const loop = (): void => {
|
||||
if (this.step_func === null) {
|
||||
this.auto_running = false;
|
||||
return;
|
||||
}
|
||||
if (this.auto_running === false) {
|
||||
return;
|
||||
}
|
||||
this.step_func();
|
||||
setTimeout(loop, speed);
|
||||
};
|
||||
loop();
|
||||
}
|
||||
|
||||
stop_auto(): void {
|
||||
this.auto_running = false;
|
||||
}
|
||||
|
||||
set_step_func(f: () => void): void {
|
||||
this.step_func = f;
|
||||
}
|
||||
|
||||
stateUpdateEvent(state: ComputerState): void {
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const current = this.program_memory_cells[i];
|
||||
current.className = "";
|
||||
current.textContent = state.memory[i].toString(16).toUpperCase().padStart(2, "0");
|
||||
}
|
||||
this.program_memory_cells[state.program_counter].classList.add("program_counter");
|
||||
const current_instr = state.current_instruction;
|
||||
if (current_instr !== null) {
|
||||
this.program_memory_cells[current_instr.pos].classList.add("current_instruction");
|
||||
for (let i = 0; i < current_instr.params_found; i++) {
|
||||
const offset = i + 1 + current_instr.pos;
|
||||
this.program_memory_cells[offset].classList.add("instruction_argument");
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < state.registers.length; i++) {
|
||||
const new_text = state.registers[i].toString(16).toUpperCase().padStart(2, "0");
|
||||
const old = this.register_cells[i].textContent;
|
||||
if (new_text !== old) {
|
||||
this.register_cells[i].textContent = new_text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
style.css
Normal file
61
style.css
Normal file
|
@ -0,0 +1,61 @@
|
|||
#program_memory {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-gap: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#program_memory div {
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
color: gray;
|
||||
background-color: black;
|
||||
font-size: 2.5em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#printout {
|
||||
border: 4px dashed yellow;
|
||||
width: 1000px;
|
||||
padding: 10px;
|
||||
margin-left: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#program_memory div.program_counter {
|
||||
outline: 3px solid orange;
|
||||
}
|
||||
|
||||
#program_memory {
|
||||
border: 5px solid yellow;
|
||||
}
|
||||
|
||||
#program_memory div.instruction_argument {
|
||||
outline: 3px dashed purple;
|
||||
}
|
||||
#program_memory div.current_instruction {
|
||||
outline: 3px dashed greenyellow;
|
||||
}
|
||||
|
||||
#registers {
|
||||
border: 5px solid yellow;
|
||||
max-width: fit-content;
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#container {
|
||||
font-family: monospace;
|
||||
max-width: min-content;
|
||||
max-height: min-content;
|
||||
}
|
21
tsconfig.json
Executable file
21
tsconfig.json
Executable file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"isolatedModules": true,
|
||||
"target": "ES6",
|
||||
"module": "ES6",
|
||||
"rootDir": "src/",
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
35
webpack.config.js
Executable file
35
webpack.config.js
Executable file
|
@ -0,0 +1,35 @@
|
|||
const path = require("path");
|
||||
|
||||
const config = {
|
||||
entry: "./src/index.ts",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.html/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts"],
|
||||
},
|
||||
output: {
|
||||
filename: "main.js",
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
},
|
||||
};
|
||||
module.exports = (env, argv) => {
|
||||
if (argv.mode === "development") {
|
||||
config.devtool = "source-map";
|
||||
}
|
||||
|
||||
if (argv.mode === "production") {
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
Loading…
Reference in a new issue