diff --git a/package.json b/package.json index 515d454..f2e6cbe 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "scripts": { "build": "webpack --mode=production", + "build-dev": "webpack --mode=development", "watch": "webpack --mode=development --watch" } } diff --git a/src/computer.ts b/src/computer.ts index 682e29b..d011734 100644 --- a/src/computer.ts +++ b/src/computer.ts @@ -1,8 +1,7 @@ import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; import { byte_array_to_js_source, format_hex } from "./etc"; -import { EventHandler } from "./eventHandler"; import { Instruction, ISA } from "./instructionSet"; -import { m256, u8 } from "./num"; +import { m256, u1, u3, u8 } from "./num"; export type TempInstrState = { pos: u8; @@ -11,14 +10,25 @@ export type TempInstrState = { params: Array; }; +// It would be a shame not to use the `Uint8Array` type JS provides to store memory and other byte arrays. +// Unfortunately Typescript defines indexing a `Uint8Array` to return a generic `number`, not the constrained `u8`. +// This redefines it. +declare global { + interface Uint8Array { + [key: number]: u8; + } +} + +const bank_count = 1; // 1 additional bank: video memory + export class Computer { - private memory = new Array(256); - private registers = new Array(256); + private memory: Uint8Array = new Uint8Array(256 + 256 * bank_count); + private registers: Uint8Array = new Uint8Array(8); private call_stack: Array = []; private program_counter: u8 = 0; - private bank: u8 = 0; + private bank: u1 = 0; private current_instr: TempInstrState | null = null; - events: CpuEventHandler = new EventHandler() as CpuEventHandler; + events: CpuEventHandler = new CpuEventHandler(); constructor() { // Add events @@ -29,7 +39,8 @@ export class Computer { } cycle(): void { - const current_byte = this.memory[this.program_counter]; + const current_byte = this.getMemory(this.program_counter, 0); + if (this.current_instr === null) { const parsed_instruction = ISA.getInstruction(current_byte); if (parsed_instruction === null) { @@ -39,7 +50,7 @@ export class Computer { }); console.log(`Invalid instruction: ${format_hex(current_byte)}`); this.step_forward(); - this.events.dispatch(CpuEvent.ClockCycle, null); + this.events.dispatch(CpuEvent.Cycle); return; } this.current_instr = { @@ -57,7 +68,7 @@ export class Computer { if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) { this.step_forward(); - this.events.dispatch(CpuEvent.ClockCycle, null); + this.events.dispatch(CpuEvent.Cycle); return; } @@ -73,7 +84,7 @@ export class Computer { this.current_instr.params_found += 1; if (this.current_instr.params.length !== this.current_instr.params_found) { this.step_forward(); - this.events.dispatch(CpuEvent.ClockCycle, null); + this.events.dispatch(CpuEvent.Cycle); return; } } @@ -91,21 +102,28 @@ export class Computer { if (execution_post_action_state.should_step) { this.step_forward(); } - this.events.dispatch(CpuEvent.ClockCycle, null); + this.events.dispatch(CpuEvent.Cycle); } - getMemory(address: u8): u8 { - return this.memory[address]; + getMemory(address: u8, bank_override?: u1): u8 { + if (bank_override !== undefined) { + const value = this.memory[address + 256 * bank_override] as u8; + return value; + } + const value = this.memory[address + 256 * this.bank] as u8; + return value; } + setMemory(address: u8, value: u8): void { this.events.dispatch(CpuEvent.MemoryChanged, { address, value }); - this.memory[address] = value; + this.memory[address + 256 * bank_count] = value; } - getRegister(register_no: u8): u8 { - return this.registers[register_no]; + getRegister(register_no: u3): u8 { + return this.registers[register_no] as u8; } - setRegister(register_no: u8, value: u8): void { + + setRegister(register_no: u3, value: u8): void { this.events.dispatch(CpuEvent.RegisterChanged, { register_no, value }); this.registers[register_no] = value; } @@ -113,6 +131,7 @@ export class Computer { getProgramCounter(): u8 { return this.program_counter; } + setProgramCounter(new_value: u8): void { this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: new_value }); this.program_counter = new_value; @@ -128,22 +147,22 @@ export class Computer { return this.call_stack.pop() ?? null; } - setBank(bank_no: u8): void { + setBank(bank_no: u1): void { this.bank = bank_no; } reset(): void { - this.events.dispatch(CpuEvent.Reset, null); - this.memory = new Array(256); - this.registers = new Array(8); + this.events.dispatch(CpuEvent.Reset); + this.memory = new Uint8Array(256); + this.registers = new Uint8Array(8); this.call_stack = []; this.current_instr = null; this.program_counter = 0; } init_events(ui: UiEventHandler): void { - ui.listen(UiEvent.RequestCpuCycle, (n) => { - for (let i = 0; i < n; i++) this.cycle(); + ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => { + for (let i = 0; i < cycle_count; i++) this.cycle(); }); ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value)); } @@ -161,7 +180,7 @@ export class Computer { this.program_counter = 0; } - dump_memory(): Array { + dump_memory(): Uint8Array { return this.memory; } diff --git a/src/etc.ts b/src/etc.ts index ffdd553..d3bc452 100644 --- a/src/etc.ts +++ b/src/etc.ts @@ -1,24 +1,38 @@ +/** + * @file Assorted small functions to be used throughout this program. + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ import { u8 } from "./num"; -// 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; - -// Jquery lite -export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement; +/** + * Alias to `document.getElementById(id)`. Jquery lite. + * @param id id of element to be located in DOM + */ +export const $ = (id: string): HTMLElement => document.getElementById(id) as HTMLElement; export const format_hex = (n: u8): string => n.toString(16).toUpperCase().padStart(2, "0"); -export const byte_array_to_js_source = (a: Array): string => { +/** + * Converts array of bytes to a JavaScript syntax array of hexadecimal literals + * @param bytes + */ +export const byte_array_to_js_source = (bytes: Array): string => { let str = "["; - for (const b of a) { + for (const b of bytes) { str += `0x${format_hex(b)},`; } str += "]"; return str; }; -export function el(type: string, id?: string): HTMLElement { +/** + * Create an html element + * @param type + * @param id id attribute to set + */ +export function el(type: E, id?: string): HTMLElementTagNameMap[E]; +export function el(type: string, id?: string): HTMLElement | undefined { const element = document.createElement(type); if (id === undefined) { return element; diff --git a/src/eventHandler.ts b/src/eventHandler.ts index 42eb749..2876f48 100644 --- a/src/eventHandler.ts +++ b/src/eventHandler.ts @@ -1,3 +1,9 @@ +/** + * @file Generic Event handler similar to the DOM event handlers + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ + export class Event { identifier: T; callbacks: Array<(event_data: unknown) => void> = []; diff --git a/src/events.ts b/src/events.ts index 5e16025..7b1f737 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,7 +1,15 @@ +/** + * @file Specific definitions of the event handlers (CPU & UI) used within this program + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ import { EventHandler } from "./eventHandler"; import { Instruction, ParameterType } from "./instructionSet"; -import { u8 } from "./num.js"; +import { u3, u8 } from "./num"; +// +// CPU Event Handler Definition +// export enum CpuEvent { MemoryChanged, RegisterChanged, @@ -10,20 +18,25 @@ export enum CpuEvent { ParameterParsed, InvalidParsed, InstructionExecuted, - ClockCycle, + Cycle, Print, Reset, Halt, + ClockStarted, + ClockStopped, } -// Handily explained in https://www.cgjennings.ca/articles/typescript-events/ +type VoidDataCpuEventList = + | CpuEvent.Halt + | CpuEvent.Reset + | CpuEvent.Cycle + | CpuEvent.ClockStarted + | CpuEvent.ClockStopped; + interface CpuEventMap { [CpuEvent.MemoryChanged]: { address: u8; value: u8 }; - [CpuEvent.RegisterChanged]: { register_no: u8; value: u8 }; + [CpuEvent.RegisterChanged]: { register_no: u3; value: u8 }; [CpuEvent.ProgramCounterChanged]: { counter: u8 }; - [CpuEvent.Halt]: null; - [CpuEvent.Reset]: null; - [CpuEvent.ClockCycle]: null; [CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction }; [CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType }; [CpuEvent.InvalidParsed]: { pos: u8; code: u8 }; @@ -32,10 +45,22 @@ interface CpuEventMap { } export interface CpuEventHandler extends EventHandler { + listen(type: E, listener: () => void): void; + dispatch(type: E): void; listen(type: E, listener: (ev: CpuEventMap[E]) => void): void; dispatch(type: E, data: CpuEventMap[E]): void; } +interface CpuEventHandlerConstructor { + new (): CpuEventHandler; +} + +export const CpuEventHandler = EventHandler as CpuEventHandlerConstructor; + +// +// Ui Event Handler Definition +// + export enum UiEvent { RequestCpuCycle, RequestMemoryChange, @@ -50,3 +75,9 @@ export interface UiEventHandler extends EventHandler { listen(type: E, listener: (ev: UiEventMap[E]) => void): void; dispatch(type: E, data: UiEventMap[E]): void; } + +interface UiEventHandlerConstructor { + new (): UiEventHandler; +} + +export const UiEventHandler = EventHandler as UiEventHandlerConstructor; diff --git a/src/index.ts b/src/index.ts index d88c835..34fc5c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,8 @@ +/** + * @file Virtual 8-Bit Computer + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ import { Computer } from "./computer"; import { $ } from "./etc"; import { ISA } from "./instructionSet"; @@ -7,6 +12,13 @@ import { u8 } from "./num"; import "./style.scss"; +declare global { + interface Window { + comp: Computer; + ui: UI; + } +} + function main(): void { const program: Array = [ 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03, @@ -48,20 +60,23 @@ function main(): void { ui.init_events(computer.events); computer.load_memory(program); computer.init_events(ui.events); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window).comp = computer; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window).ui = ui; + + window.comp = computer; + window.ui = ui; $("ISA").textContent = generate_isa(ISA); $("binary_upload").addEventListener("change", (e) => { - if (e.target === null) { + const t = e.target; + if (t === null) { return; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const file: File = (e.target).files[0]; + const file: File | undefined = (t as HTMLInputElement).files?.[0]; + if (file === undefined) { + console.log("No files attribute on file input"); + return; + } const reader = new FileReader(); console.log(file); reader.addEventListener("load", (e) => { @@ -70,7 +85,6 @@ function main(): void { if (data instanceof ArrayBuffer) { const view = new Uint8Array(data); const array = [...view] as Array; - ui.stop_auto(); computer.reset(); computer.load_memory(array); } else { @@ -83,8 +97,7 @@ function main(): void { $("save_button").addEventListener("click", () => { const memory = computer.dump_memory(); - const buffer = new Uint8Array(memory); - const blob = new Blob([buffer], { type: "application/octet-stream" }); + const blob = new Blob([memory], { type: "application/octet-stream" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); diff --git a/src/instructionSet.ts b/src/instructionSet.ts index 2af2b42..9f926f4 100644 --- a/src/instructionSet.ts +++ b/src/instructionSet.ts @@ -1,3 +1,8 @@ +/** + * @file CPU instruction definitions & type definitions for parameters and instructions + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ import { CpuEvent, CpuEventHandler } from "./events"; import { format_hex } from "./etc"; import { isU3, m256, u1, u3, u8 } from "./num"; @@ -77,6 +82,7 @@ export class InstructionSet { } getInstruction(hexCode: u8): Instruction | null { + // console.log(format_hex(hexCode)); return this.instructions.get(hexCode) ?? null; } } @@ -111,8 +117,9 @@ ISA.insertInstruction(0x20, { const [register_no, register_2] = p; if (!isU3(register_no)) throw new Error("TODO"); if (!isU3(register_2)) throw new Error("TODO"); + const mem_value = c.getMemory(c.getRegister(register_2)); - c.setRegister(register_no, c.getMemory(c.getRegister(register_2))); + c.setRegister(register_no, mem_value); }, }); @@ -273,7 +280,7 @@ ISA.insertInstruction(0x66, { desc: "Stops program execu..... Fire! FIRE EVERYWHERE!", params: [], execute(c, p, a) { - a.dispatch(CpuEvent.Halt, null); + a.dispatch(CpuEvent.Halt); }, }); diff --git a/src/isaGenerator.ts b/src/isaGenerator.ts index d14147e..d7888af 100644 --- a/src/isaGenerator.ts +++ b/src/isaGenerator.ts @@ -1,3 +1,8 @@ +/** + * @file Automatic generation of instruction set description + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ import { format_hex } from "./etc"; import { Instruction, InstructionSet } from "./instructionSet"; import { u8 } from "./num.js"; diff --git a/src/num.ts b/src/num.ts index d1ec48c..e9c6083 100644 --- a/src/num.ts +++ b/src/num.ts @@ -1,260 +1,27 @@ +/** + * @file Constrained integer types and validation functions + * @copyright Alexander Bass 2024 + * @license GPL-3.0 + */ + +// prettier-ignore export type u8 = - | 0 - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 17 - | 18 - | 19 - | 20 - | 21 - | 22 - | 23 - | 24 - | 25 - | 26 - | 27 - | 28 - | 29 - | 30 - | 31 - | 32 - | 33 - | 34 - | 35 - | 36 - | 37 - | 38 - | 39 - | 40 - | 41 - | 42 - | 43 - | 44 - | 45 - | 46 - | 47 - | 48 - | 49 - | 50 - | 51 - | 52 - | 53 - | 54 - | 55 - | 56 - | 57 - | 58 - | 59 - | 60 - | 61 - | 62 - | 63 - | 64 - | 65 - | 66 - | 67 - | 68 - | 69 - | 70 - | 71 - | 72 - | 73 - | 74 - | 75 - | 76 - | 77 - | 78 - | 79 - | 80 - | 81 - | 82 - | 83 - | 84 - | 85 - | 86 - | 87 - | 88 - | 89 - | 90 - | 91 - | 92 - | 93 - | 94 - | 95 - | 96 - | 97 - | 98 - | 99 - | 100 - | 101 - | 102 - | 103 - | 104 - | 105 - | 106 - | 107 - | 108 - | 109 - | 110 - | 111 - | 112 - | 113 - | 114 - | 115 - | 116 - | 117 - | 118 - | 119 - | 120 - | 121 - | 122 - | 123 - | 124 - | 125 - | 126 - | 127 - | 128 - | 129 - | 130 - | 131 - | 132 - | 133 - | 134 - | 135 - | 136 - | 137 - | 138 - | 139 - | 140 - | 141 - | 142 - | 143 - | 144 - | 145 - | 146 - | 147 - | 148 - | 149 - | 150 - | 151 - | 152 - | 153 - | 154 - | 155 - | 156 - | 157 - | 158 - | 159 - | 160 - | 161 - | 162 - | 163 - | 164 - | 165 - | 166 - | 167 - | 168 - | 169 - | 170 - | 171 - | 172 - | 173 - | 174 - | 175 - | 176 - | 177 - | 178 - | 179 - | 180 - | 181 - | 182 - | 183 - | 184 - | 185 - | 186 - | 187 - | 188 - | 189 - | 190 - | 191 - | 192 - | 193 - | 194 - | 195 - | 196 - | 197 - | 198 - | 199 - | 200 - | 201 - | 202 - | 203 - | 204 - | 205 - | 206 - | 207 - | 208 - | 209 - | 210 - | 211 - | 212 - | 213 - | 214 - | 215 - | 216 - | 217 - | 218 - | 219 - | 220 - | 221 - | 222 - | 223 - | 224 - | 225 - | 226 - | 227 - | 228 - | 229 - | 230 - | 231 - | 232 - | 233 - | 234 - | 235 - | 236 - | 237 - | 238 - | 239 - | 240 - | 241 - | 242 - | 243 - | 244 - | 245 - | 246 - | 247 - | 248 - | 249 - | 250 - | 251 - | 252 - | 253 - | 254 - | 255; + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 + | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 + | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 + | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 + | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 + | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 + | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 + | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 + | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 + | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 + | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 + | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 + | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 + | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 + | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 + | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255; export type u1 = 0 | 1; export type u2 = 0 | 1 | 2 | 3; diff --git a/src/ui.ts b/src/ui.ts index d1f3eb5..47ae139 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,26 +1,25 @@ import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; import { $, el, format_hex } from "./etc"; -import { EventHandler } from "./eventHandler"; import { InstructionExplainer } from "./ui/instructionExplainer"; import { MemoryView } from "./ui/memoryView"; -import { ParamType } from "./instructionSet"; import { frequencyIndicator } from "./ui/frequencyIndicator"; +import { RegisterView } from "./ui/registerView"; // Certainly the messiest portion of this program // Needs to be broken into components +// Breaking up into components has started but has yet to conclude let delay = 100; export class UI { - registers: HTMLElement; printout: HTMLElement; - register_cells: Array = []; auto_running: boolean; - events: UiEventHandler = new EventHandler() as UiEventHandler; + events: UiEventHandler = new UiEventHandler(); frequencyIndicator: frequencyIndicator; memory: MemoryView; + registers: RegisterView; instruction_explainer: InstructionExplainer; constructor() { @@ -29,23 +28,13 @@ export class UI { } this.events.seal(); - this.memory = new MemoryView($("memory")); - this.frequencyIndicator = new frequencyIndicator($("cycles")); - - this.instruction_explainer = new InstructionExplainer($("instruction_explainer")); + this.memory = new MemoryView($("memory"), this.events); + this.frequencyIndicator = new frequencyIndicator($("cycles"), this.events); + this.instruction_explainer = new InstructionExplainer($("instruction_explainer"), this.events); + this.registers = new RegisterView($("registers"), this.events); this.printout = $("printout"); - const registers = $("registers"); - for (let i = 0; i < 8; i++) { - const reg_cell = el("div", `r_${i}`); - reg_cell.textContent = "00"; - registers.appendChild(reg_cell); - this.register_cells.push(reg_cell); - } - - this.registers = registers; - this.auto_running = false; const pp_button = $("pause_play_button"); if (pp_button === null) { @@ -75,13 +64,14 @@ export class UI { } init_events(cpu_events: CpuEventHandler): void { - cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => { - this.register_cells[register_no].textContent = format_hex(value); - }); cpu_events.listen(CpuEvent.Print, (char) => { this.printout.textContent = (this.printout.textContent ?? "") + char; }); + cpu_events.listen(CpuEvent.Reset, () => { + this.reset(); + }); + this.registers.init_cpu_events(cpu_events); this.frequencyIndicator.init_cpu_events(cpu_events); this.memory.init_cpu_events(cpu_events); this.instruction_explainer.init_cpu_events(cpu_events); @@ -89,9 +79,7 @@ export class UI { reset(): void { this.stop_auto(); - this.register_cells.forEach((r) => { - r.textContent = "00"; - }); + this.registers.reset(); this.frequencyIndicator.reset(); this.instruction_explainer.reset(); this.memory.reset(); diff --git a/src/ui/frequencyIndicator.ts b/src/ui/frequencyIndicator.ts index b61d013..0006fc5 100644 --- a/src/ui/frequencyIndicator.ts +++ b/src/ui/frequencyIndicator.ts @@ -7,8 +7,10 @@ export class frequencyIndicator implements UiComponent { private count: number = 0; private last_value: number = 0; private last_time: number = 0; - constructor(element: HTMLElement) { + events: UiEventHandler; + constructor(element: HTMLElement, e: UiEventHandler) { this.element = element; + this.events = e; this.start(); } @@ -39,9 +41,6 @@ export class frequencyIndicator implements UiComponent { this.count = 0; } - init_events(eh: UiEventHandler): void { - this; - } clock_cycle(): void { this.count += 1; } @@ -51,7 +50,7 @@ export class frequencyIndicator implements UiComponent { this.last_value = 0; } init_cpu_events(c: CpuEventHandler): void { - c.listen(CpuEvent.ClockCycle, () => { + c.listen(CpuEvent.Cycle, () => { this.count += 1; }); } diff --git a/src/ui/instructionExplainer.ts b/src/ui/instructionExplainer.ts index c3bc75f..422d1cd 100644 --- a/src/ui/instructionExplainer.ts +++ b/src/ui/instructionExplainer.ts @@ -6,8 +6,10 @@ import { UiComponent } from "./uiComponent"; export class InstructionExplainer implements UiComponent { element: HTMLElement; - constructor(element: HTMLElement) { + events: UiEventHandler; + constructor(element: HTMLElement, e: UiEventHandler) { this.element = element; + this.events = e; } add_instruction(instr: Instruction, pos: u8, byte: u8): void { this.reset(); @@ -47,7 +49,6 @@ export class InstructionExplainer implements UiComponent { this.add_box(format_hex(byte), "Invalid Instruction", "invalid"); } - init_events(eh: UiEventHandler): void {} init_cpu_events(c: CpuEventHandler): void { c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => { this.add_param(param, pos, code); diff --git a/src/ui/memoryView.ts b/src/ui/memoryView.ts index 616be61..2612eae 100644 --- a/src/ui/memoryView.ts +++ b/src/ui/memoryView.ts @@ -5,15 +5,17 @@ import { u8 } from "../num.js"; import { UiComponent } from "./uiComponent"; type MemoryCell = { - el: HTMLElement; + el: HTMLDivElement; }; export class MemoryView implements UiComponent { element: HTMLElement; cells: Array = []; program_counter: number = 0; - constructor(element: HTMLElement) { + events: UiEventHandler; + constructor(element: HTMLElement, e: UiEventHandler) { this.element = element; + this.events = e; for (let i = 0; i < 256; i++) { const mem_cell_el = el("div"); mem_cell_el.textContent = "00"; @@ -58,20 +60,13 @@ export class MemoryView implements UiComponent { } reset(): void { - this.element.innerHTML = ""; for (let i = 0; i < 256; i++) { - const mem_cell_el = el("div"); - mem_cell_el.textContent = "00"; - this.element.appendChild(mem_cell_el); - const mem_cell = { el: mem_cell_el }; - this.cells.push(mem_cell); + this.cells[i].el.textContent = "00"; + this.cells[i].el.className = ""; } this.set_program_counter(0); } - init_events(eh: UiEventHandler): void { - this; - } init_cpu_events(c: CpuEventHandler): void { c.listen(CpuEvent.MemoryChanged, ({ address, value }) => { this.set_cell_value(address, value); diff --git a/src/ui/registerView.ts b/src/ui/registerView.ts new file mode 100644 index 0000000..92a1016 --- /dev/null +++ b/src/ui/registerView.ts @@ -0,0 +1,68 @@ +import { el, format_hex } from "../etc"; +import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events"; +import { u8 } from "../num.js"; +import { UiComponent } from "./uiComponent"; + +type MemoryCell = { + el: HTMLDivElement; +}; + +const REGISTER_COUNT = 8; + +export class RegisterView implements UiComponent { + element: HTMLElement; + cells: Array = []; + program_counter: number = 0; + events: UiEventHandler; + constructor(element: HTMLElement, e: UiEventHandler) { + this.element = element; + this.events = e; + for (let i = 0; i < REGISTER_COUNT; i++) { + const mem_cell_el = el("div"); + mem_cell_el.textContent = "00"; + element.appendChild(mem_cell_el); + const mem_cell = { el: mem_cell_el, tags: [] }; + this.cells.push(mem_cell); + } + } + + add_cell_class(address: u8, ...css_class: string[]): void { + for (const str of css_class) { + this.cells[address].el.classList.add(str); + } + } + + remove_cell_class(address: u8, ...css_class: string[]): void { + for (const str of css_class) { + this.cells[address].el.classList.remove(str); + } + } + + remove_all_cell_class(css_class: string): void { + for (const cell of this.cells) { + cell.el.classList.remove(css_class); + } + } + + add_cell_class_exclusive(address: u8, css_class: string): void { + this.remove_all_cell_class(css_class); + this.add_cell_class(address, css_class); + } + + set_cell_value(address: u8, value: u8): void { + this.cells[address].el.textContent = format_hex(value); + } + + reset(): void { + for (let i = 0; i < REGISTER_COUNT; i++) { + this.cells[i].el.textContent = "00"; + this.cells[i].el.className = ""; + } + } + + init_cpu_events(c: CpuEventHandler): void { + c.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => { + this.set_cell_value(register_no, value); + }); + } +} diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts index da43929..1a3ef80 100644 --- a/src/ui/uiComponent.ts +++ b/src/ui/uiComponent.ts @@ -2,7 +2,8 @@ import { CpuEventHandler, UiEventHandler } from "../events"; export interface UiComponent { element: HTMLElement; + events: UiEventHandler; reset: () => void; - init_events: (ui: UiEventHandler) => void; + // init_events: (ui: UiEventHandler) => void; init_cpu_events: (c: CpuEventHandler) => void; } diff --git a/tsconfig.json b/tsconfig.json index 635cf07..cdd13b6 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "lib": [ - "esnext", + "es2021", "dom", "DOM.Iterable" ],