diff --git a/binutils.mjs b/binutils.mjs index e4dd990..a2a8001 100644 --- a/binutils.mjs +++ b/binutils.mjs @@ -56,7 +56,11 @@ function Hexlify(buf) { * @returns {Uint8Array} Decoded array */ function Unhexlify(str) { - let a = str.match(/[0-9a-fA-F]{2}/g).map(v => parseInt(v, 16)) + let a = [] + let match = str.match(/[0-9a-fA-F]{2}/g) || [] + for (let m of match) { + a.push(parseInt(m, 16)) + } return new Uint8Array(a) } diff --git a/index.html b/index.html index f96ca3a..6ab62a3 100644 --- a/index.html +++ b/index.html @@ -2,11 +2,12 @@ Triscit + - - + +
@@ -26,30 +27,26 @@ -
+
+
+
-
- - -
- +
+
+ + +
diff --git a/triscit.mjs b/triscit.mjs index 58a3e7e..ba62625 100644 --- a/triscit.mjs +++ b/triscit.mjs @@ -86,13 +86,14 @@ class CPU { Step() { let prog = this.Program.slice(this.PC) let inst = disassemble(prog) + let flags = this.Flags this.Flags = 0 switch (inst.name) { case "JUMP": this.PC = inst.args[0] return case "JNEQ": - if (this.Flags && FLAG_NEGATIVE) { + if (flags && FLAG_NEGATIVE) { this.PC = inst.args[0] return } @@ -119,15 +120,16 @@ class CPU { let addr = inst.args[0] let prog = this.Program.slice(addr) this.Output = Binutils.CString(prog) - console.log(addr, this.Output) break } case "CMPS": { - let a = Binutils.CString(this.Program.slice(inst.args[0])) - let b = Binutils.CString(this.Program.slice(inst.args[1])) - if (a == b) { + let decoder = new TextDecoder() + let a = decoder.decode(Binutils.CString(this.Program.slice(inst.args[0]))) + let b = decoder.decode(Binutils.CString(this.Program.slice(inst.args[1]))) + if (a != b) { this.Flags = FLAG_NEGATIVE } + break } case "HALT": // Don't modify PC @@ -145,149 +147,8 @@ class CPU { } } -class Triscit { - constructor(program=[]) { - this.program = program - this.Init() - this.Reset() - } - - Init() { - for (let e of document.querySelectorAll("[data-control]")) { - switch (e.dataset.control) { - case "step": - e.addEventListener("click", () => this.Step()) - break - case "back": - e.addEventListener("click", () => this.Unstep()) - break - case "reset": - e.addEventListener("click", () => this.Reset()) - break - case "input": - e.addEventListener("input", () => this.SetInput()) - break - case "program": - e.addEventListener("input", () => this.SetProgram()) - } - } - - let ih = document.querySelector("#instructions-help") - for (let i = 0; i < Instructions.length; i++) { - let inst = Instructions[i] - let doc = document.querySelector("template#instruction-help").content.cloneNode(true) - let tr = doc.firstElementChild - tr.querySelector(".num").textContent = i - tr.querySelector(".name").textContent = inst.Name - tr.querySelector(".description").textContent = inst.Description - ih.appendChild(doc) - } - - this.SetInput() - this.SetProgram() - } - - Reset() { - this.cpu = new CPU(this.program, this.input) - this.stack = [] - this.Refresh() - } - - Refresh() { - let inste = document.querySelector("#instructions") - while (inste.firstChild) inste.firstChild.remove() - for (let i of this.cpu.DisassembleProgram()) { - let doc = document.querySelector("template#instruction").content.cloneNode(true) - let tr = doc.firstElementChild - tr.querySelector(".addr").textContent = i.addr - tr.querySelector(".name").textContent = i.name - tr.querySelector(".hex").textContent = Binutils.Hexlify(i.buf) - - let args = tr.querySelector(".args") - for (let a of i.args) { - args.textContent += `${a} ` - } - - if (i.addr == this.cpu.PC) { - tr.classList.add("is-selected") - } - inste.appendChild(tr) - } - - for (let e of document.querySelectorAll("[data-flag]")) { - let mask = 0 - let className = "is-info" - switch (e.dataset.flag) { - case "negative": - mask = FLAG_NEGATIVE - break - case "halt": - mask = FLAG_HALT - break - case "fire": - mask = FLAG_ABLAZE - className = "is-danger" - break - } - if (this.cpu.Flags & mask) { - e.classList.add(className) - } else { - e.classList.remove(className) - } - } - - for (let e of document.querySelectorAll("[data-value]")) { - switch (e.dataset.value) { - case "pc": - e.textContent = this.cpu.PC - break - case "output": - console.log(this.cpu.Output) - e.value = Binutils.Stringify(this.cpu.Output) - break - } - } - } - - Step() { - if (this.cpu.Flags & FLAG_HALT) { - return - } - this.stack.push(this.cpu) - this.cpu = this.cpu.Clone() - this.cpu.Step() - this.Refresh() - } - - Unstep() { - if (this.stack.length == 0) { - return - } - this.cpu = this.stack.pop() - this.Refresh() - } - - SetProgram() { - let e =document.querySelector('[data-control="program"]') - this.program = Binutils.Unhexlify(e.value || "") - this.Reset() - } - - SetInput() { - let e = document.querySelector('[data-control="input"]') - let v = e.value || "" - this.input = Binutils.Unescape(v) - this.Reset() - } -} - -function init() { - let app = new Triscit() - window.app = app -} - -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init) -} else { - init() -} +export { + FLAG_NEGATIVE, FLAG_HALT, FLAG_ABLAZE, + Instructions, + CPU, +} \ No newline at end of file diff --git a/triscit.css b/ui.css similarity index 71% rename from triscit.css rename to ui.css index b3b48c8..6c1e3d7 100644 --- a/triscit.css +++ b/ui.css @@ -1,3 +1,8 @@ +.may-be-tall { + overflow-y: scroll; + max-height: 80vh; +} + #instructions th.addr { text-align: right; color: brown; diff --git a/ui.mjs b/ui.mjs new file mode 100644 index 0000000..481de35 --- /dev/null +++ b/ui.mjs @@ -0,0 +1,176 @@ +import * as Binutils from "./binutils.mjs" +import * as Triscit from "./triscit.mjs" + +const SamplePrograms = [ + `05 0d +61 62 63 64 65 66 67 68 69 6a 00 +02 02 +01 02 +06 +`, + `05 31 + 61 62 63 64 65 66 67 68 69 6a 00 + 70 61 73 73 77 6f 72 64 00 + 53 75 70 65 72 20 53 65 63 72 65 74 00 + 53 6f 72 72 79 2c 20 77 72 6f 6e 67 2e 00 + 02 02 + 03 02 0d + 04 3b + 01 16 + 06 + 01 23 + 06 +` +] + +class UI { + constructor(program=[]) { + this.program = program + this.Init() + this.Reset() + } + + Init() { + for (let e of document.querySelectorAll("[data-control]")) { + switch (e.dataset.control) { + case "step": + e.addEventListener("click", () => this.Step()) + break + case "back": + e.addEventListener("click", () => this.Unstep()) + break + case "reset": + e.addEventListener("click", () => this.Reset()) + break + case "input": + e.addEventListener("input", () => this.SetInput()) + break + case "program": + e.addEventListener("input", () => this.SetProgram()) + } + } + + let ih = document.querySelector("#instructions-help") + for (let i = 0; i < Triscit.Instructions.length; i++) { + let inst = Triscit.Instructions[i] + let doc = document.querySelector("template#instruction-help").content.cloneNode(true) + let tr = doc.firstElementChild + tr.querySelector(".num").textContent = i + tr.querySelector(".name").textContent = inst.Name + tr.querySelector(".description").textContent = inst.Description + ih.appendChild(doc) + } + + let fields = new URLSearchParams(window.location.search); + let progNumber = fields.get("p") || 0 + let progElement = document.querySelector('[data-control="program"]') + progElement.value = SamplePrograms[progNumber] + + this.SetInput() + this.SetProgram() + } + + Reset() { + this.cpu = new Triscit.CPU(this.program, this.input) + this.stack = [] + this.Refresh() + } + + Refresh() { + let inste = document.querySelector("#instructions") + while (inste.firstChild) inste.firstChild.remove() + for (let i of this.cpu.DisassembleProgram()) { + let doc = document.querySelector("template#instruction").content.cloneNode(true) + let tr = doc.firstElementChild + tr.querySelector(".addr").textContent = i.addr + tr.querySelector(".name").textContent = i.name + tr.querySelector(".hex").textContent = Binutils.Hexlify(i.buf) + + let args = tr.querySelector(".args") + for (let a of i.args) { + args.textContent += `${a} ` + } + + inste.appendChild(tr) + if (i.addr == this.cpu.PC) { + tr.classList.add("is-selected") + tr.scrollIntoView(false) + } + } + + for (let e of document.querySelectorAll("[data-flag]")) { + let mask = 0 + let className = "is-info" + switch (e.dataset.flag) { + case "negative": + mask = Triscit.FLAG_NEGATIVE + break + case "halt": + mask = Triscit.FLAG_HALT + break + case "fire": + mask = Triscit.FLAG_ABLAZE + className = "is-danger" + break + } + if (this.cpu.Flags & mask) { + e.classList.add(className) + } else { + e.classList.remove(className) + } + } + + for (let e of document.querySelectorAll("[data-value]")) { + switch (e.dataset.value) { + case "pc": + e.textContent = this.cpu.PC + break + case "output": + e.textContent = Binutils.Stringify(this.cpu.Output) + break + } + } + } + + Step() { + if (this.cpu.Flags & Triscit.FLAG_HALT) { + return + } + this.stack.push(this.cpu) + this.cpu = this.cpu.Clone() + this.cpu.Step() + this.Refresh() + } + + Unstep() { + if (this.stack.length == 0) { + return + } + this.cpu = this.stack.pop() + this.Refresh() + } + + SetProgram() { + let e =document.querySelector('[data-control="program"]') + this.program = Binutils.Unhexlify(e.value || "") + this.Reset() + } + + SetInput() { + let e = document.querySelector('[data-control="input"]') + let v = e.value || "" + this.input = Binutils.Unescape(v) + this.Reset() + } +} + +function init() { + let app = new UI() + window.app = app +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +}