Now with >1 example
This commit is contained in:
parent
3cf5ac90e9
commit
114baa07b4
|
@ -56,7 +56,11 @@ function Hexlify(buf) {
|
||||||
* @returns {Uint8Array} Decoded array
|
* @returns {Uint8Array} Decoded array
|
||||||
*/
|
*/
|
||||||
function Unhexlify(str) {
|
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)
|
return new Uint8Array(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
25
index.html
25
index.html
|
@ -2,11 +2,12 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Triscit</title>
|
<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/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">
|
<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>
|
<script src="ui.mjs" type="module"></script>
|
||||||
<link rel="stylesheet" href="triscit.css">
|
<link rel="stylesheet" href="ui.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
|
@ -26,29 +27,25 @@
|
||||||
<button class="button is-primary" data-control="step"><i class="mdi mdi-skip-next"></i></button>
|
<button class="button is-primary" data-control="step"><i class="mdi mdi-skip-next"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="box">
|
<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">
|
<div class="field">
|
||||||
<label class="label">Input</label>
|
<label class="label">Input</label>
|
||||||
<input class="input" data-control="input" placeholder="\x00 style escapes accepted" value="hello">
|
<input class="input" data-control="input" placeholder="\x00 style escapes accepted" value="hello">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Output</label>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
161
triscit.mjs
161
triscit.mjs
|
@ -86,13 +86,14 @@ class CPU {
|
||||||
Step() {
|
Step() {
|
||||||
let prog = this.Program.slice(this.PC)
|
let prog = this.Program.slice(this.PC)
|
||||||
let inst = disassemble(prog)
|
let inst = disassemble(prog)
|
||||||
|
let flags = this.Flags
|
||||||
this.Flags = 0
|
this.Flags = 0
|
||||||
switch (inst.name) {
|
switch (inst.name) {
|
||||||
case "JUMP":
|
case "JUMP":
|
||||||
this.PC = inst.args[0]
|
this.PC = inst.args[0]
|
||||||
return
|
return
|
||||||
case "JNEQ":
|
case "JNEQ":
|
||||||
if (this.Flags && FLAG_NEGATIVE) {
|
if (flags && FLAG_NEGATIVE) {
|
||||||
this.PC = inst.args[0]
|
this.PC = inst.args[0]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -119,15 +120,16 @@ class CPU {
|
||||||
let addr = inst.args[0]
|
let addr = inst.args[0]
|
||||||
let prog = this.Program.slice(addr)
|
let prog = this.Program.slice(addr)
|
||||||
this.Output = Binutils.CString(prog)
|
this.Output = Binutils.CString(prog)
|
||||||
console.log(addr, this.Output)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "CMPS": {
|
case "CMPS": {
|
||||||
let a = Binutils.CString(this.Program.slice(inst.args[0]))
|
let decoder = new TextDecoder()
|
||||||
let b = Binutils.CString(this.Program.slice(inst.args[1]))
|
let a = decoder.decode(Binutils.CString(this.Program.slice(inst.args[0])))
|
||||||
if (a == b) {
|
let b = decoder.decode(Binutils.CString(this.Program.slice(inst.args[1])))
|
||||||
|
if (a != b) {
|
||||||
this.Flags = FLAG_NEGATIVE
|
this.Flags = FLAG_NEGATIVE
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
case "HALT":
|
case "HALT":
|
||||||
// Don't modify PC
|
// Don't modify PC
|
||||||
|
@ -145,149 +147,8 @@ class CPU {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Triscit {
|
export {
|
||||||
constructor(program=[]) {
|
FLAG_NEGATIVE, FLAG_HALT, FLAG_ABLAZE,
|
||||||
this.program = program
|
Instructions,
|
||||||
this.Init()
|
CPU,
|
||||||
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()
|
|
||||||
}
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
|
.may-be-tall {
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
#instructions th.addr {
|
#instructions th.addr {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: brown;
|
color: brown;
|
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in New Issue