Now with >1 example

This commit is contained in:
Neale Pickett 2022-05-27 14:45:22 -06:00
parent 3cf5ac90e9
commit 114baa07b4
5 changed files with 209 additions and 166 deletions

View File

@ -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)
}

View File

@ -2,11 +2,12 @@
<html>
<head>
<title>Triscit</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css">
<script src="triscit.mjs" type="module"></script>
<link rel="stylesheet" href="triscit.css">
<script src="ui.mjs" type="module"></script>
<link rel="stylesheet" href="ui.css">
</head>
<body>
<section class="section">
@ -26,30 +27,26 @@
<button class="button is-primary" data-control="step"><i class="mdi mdi-skip-next"></i></button>
</div>
</div>
<table class="table is-striped is-hoverable" id="instructions"></table>
<div class="block may-be-tall">
<table class="table is-striped is-hoverable" id="instructions"></table>
</div>
</div>
</div>
<div class="column">
<div class="box">
<div class="field">
<label class="label">Program</label>
<textarea class="textarea" data-control="program">
05 0d
61 62 63 64 65 66 67 68 69 6a 00
02 02
01 02
06
</textarea>
</div>
<div class="field">
<label class="label">Input</label>
<input class="input" data-control="input" placeholder="\x00 style escapes accepted" value="hello">
</div>
<div class="field">
<label class="label">Output</label>
<input class="input" data-value="output" readonly>
<output class="input" data-value="output"></output>
</div>
<div class="field">
<label class="label">Program</label>
<textarea class="textarea" data-control="program"></textarea>
</div>
</div>
</div>

View File

@ -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,
}

View File

@ -1,3 +1,8 @@
.may-be-tall {
overflow-y: scroll;
max-height: 80vh;
}
#instructions th.addr {
text-align: right;
color: brown;

176
ui.mjs Normal file
View File

@ -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()
}