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