From 72bea2b2e1b9f5a27181ce6bae924d669dc38a38 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Wed, 23 Apr 2025 18:57:33 +0300 Subject: [PATCH] Web implementation appears --- README.md | 32 +++++++ mu8web/icon.png | Bin 0 -> 8187 bytes mu8web/index.html | 52 +++++++++++ mu8web/mu808-core.js | 183 ++++++++++++++++++++++++++++++++++++++ mu8web/mu808-ui.js | 203 +++++++++++++++++++++++++++++++++++++++++++ mu8web/style.css | 83 ++++++++++++++++++ 6 files changed, 553 insertions(+) create mode 100644 mu8web/icon.png create mode 100644 mu8web/index.html create mode 100644 mu8web/mu808-core.js create mode 100644 mu8web/mu808-ui.js create mode 100644 mu8web/style.css diff --git a/README.md b/README.md index a5789dc..9cc3b73 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,15 @@ Reference implementations program file name is optional and used for preloading MU8 program files into VM's memory. The Busybox AWK implementation is compact but quite slow, so use it as a last resort solution when no other programming environment is available. +* [Web (HTML5/JS) implementation](https://mu808.luxferre.top) aka mu8web: + supports the entire mu808 spec except the I/O port 2 and offers a way to + load program files in the same MU8 format via a convenient UI. + Uses a convoluted flow to achieve true synchronous input. + Since there's no exiting to the OS environment, the -5 instruction number + resets the VM instead (by zeroing out both memory areas and the counter). + Entry conventions within UI are pretty much the same as for 808UL. Tested + on various devices with physical keypads and touch screens. The source code + can be found in the [mu8web](mu8web/) directory of this repo. ### Assembler reference implementations @@ -293,6 +302,29 @@ Reference implementations These lists are going to be expanded as soon as new reference implementations appear. +### Web reference implementation keyboard controls + +The virtual keyboard (unavailable on the smallest screens) is duplicated with +the following keys at all times in the mu808 Web reference implementation: + +* `0`-`9`: decimal digits input; +* `*` (star), comma, dot: decimal point input; +* Space, `+`, `/`, down arrow, Call key (if present): whitespace input; +* Backspace, Delete, left arrow, `-`: minus or backspace input (as the minus sign + can only be entered at the beginning of the instruction or data input line); +* `#` (pound, hash sign), Enter: newline input; +* `l`, `L`, up arrow, Left Soft key (if present): open a program file loading dialogue. + +### 12-key (DTMF) input method proposal + +For hobbyist projects and other environments where all input is limited to 12 +keys of the standard phone keypad, the following convention is recommended: + +* the `*` (star) key is inputting a minus if this is the first position in + the entry, otherwise it's inputting a decimal point; +* the `#` (pound) key actually confirms entering a single number field, and + all instructions are required to contain exactly five numbers. + FAQ --- diff --git a/mu8web/icon.png b/mu8web/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..73714717167874b86f9f7c908f1a872b26d2ca36 GIT binary patch literal 8187 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 literal 0 HcmV?d00001 diff --git a/mu8web/index.html b/mu8web/index.html new file mode 100644 index 0000000..46c42d7 --- /dev/null +++ b/mu8web/index.html @@ -0,0 +1,52 @@ + + + + + + mu808 VM (Web version) + + + + +
+

mu808

+
+
+
+
+

+    
+  
+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + +
+
+
+ + + + + diff --git a/mu8web/mu808-core.js b/mu8web/mu808-core.js new file mode 100644 index 0000000..80f36e9 --- /dev/null +++ b/mu8web/mu808-core.js @@ -0,0 +1,183 @@ +/* + * mu808: an ultralight numeric-only VM + * This is the core JS file for the reference implementation + * of mu808 VM for the Web and other ES5 engines + * See the README.md file in the repo root for all documentation + * Created by Luxferre in 2025, released into public domain + */ + +/* Due to the modular nature, this file only defines the mu808 VM kernel + * which accepts all I/O routines as the parameters for its construction */ +function createmu808(lineOut, charOut, lineIn) { + var MEMLIMIT = 16384, + PMEM = new Uint16Array(MEMLIMIT << 2), /* program memory */ + DMEM = new Float64Array(MEMLIMIT), /* data memory */ + runlimit = MEMLIMIT, + traceflag = false, + runcount = 0 + + /* memory range input function */ + /* accepts the continuation callback as the last parameter */ + function dataRangeInput(start, end, contfunc) { + (function inp(loc) { + lineIn(function(v) { + DMEM[loc] = parseFloat(v) || 0.0 + if(loc < end) inp(loc+1) + else { + if(contfunc) contfunc() + } + }) + })(start) + } + + /* port output function */ + function portout(port, data) { + if(port == 0) lineOut(data + "\n") + else if(port == 1) charOut(data & 255) + } + + /* instruction line execution function (the main 808UL logic defined here) */ + /* accepts the continuation callback as the last parameter */ + function ilexec(lno, cmd, x, y, z, contfunc) { + var halt = 0, data_override = 0, bcheck, i, v1, v2, v3 + while(halt == 0) { + v1 = v2 = v3 = data_override = DMEM[0] = 0 /* force the value at 0 to be always 0 */ + if(lno == 0) halt = 1 /* immediate instruction */ + if(runlimit > 0 && runcount > runlimit) { + halt = 1 + if(traceflag) lineOut("Runlimit hit, halting...\n") + } else runcount++ + if(traceflag) + lineOut("PC: " + lno + " INSTR: " + [cmd, x, y, z].join(" ") + "\n") + bcheck = (x < MEMLIMIT) && (y < MEMLIMIT) && (z < MEMLIMIT) + if(bcheck) { /* boundary checks passed */ + v1 = DMEM[x]; v2 = DMEM[y]; v3 = DMEM[z] /* prefetch the memory values */ + if(cmd == 1) { /* JMP */ + if((v2 == 0 && x == 0) || (v2 > 0 && x == 1) || (v2 < 0 && x == 2) + || (v2 >= 0 && x == 3) || (v2 <= 0 && x == 4) || (v2 != 0 && x == 5) + || x == 6) { halt = 0; lno = z - 1 } + else if((v2 == 0 && x == 7) || (v2 > 0 && x == 8) || (v2 < 0 && x == 9) + || (v2 >= 0 && x == 10) || (v2 <= 0 && x == 11) || (v2 != 0 && x == 12) + || x == 13) { halt = 0; lno = (v3|0) - 1 } + } else if(cmd == 2) data_override = 1 /* IAT */ + else if(cmd == 3) for(i=x;i<=y;i++) portout(z, DMEM[i]) /* OUT */ + else if(cmd == 4) { /* INP */ + dataRangeInput(x, y, function() { + /* continuation function duplicating what's after the switch */ + /* except the data override branch */ + lno++ /* increment the program counter */ + if(lno >= MEMLIMIT || lno < 1 || (runlimit > 0 && runcount > runlimit)) { + if(traceflag) lineOut("Memory limit or runlimit hit, halting...\n") + halt = 1 + } else { /* fetch the next instruction and execute it */ + cmd = PMEM[lno << 2] + x = PMEM[(lno << 2) + 1] + y = PMEM[(lno << 2) + 2] + z = PMEM[(lno << 2) + 3] + ilexec(lno, cmd, x, y, z, contfunc) + } + }) + return + } else if(cmd == 5) DMEM[z] = (x % 10000) + (y % 10000) / 10000.0 /* SET */ + else if(cmd == 6) { /* CPY */ + if(x == 0) DMEM[z] = y + else if(x == 1) DMEM[z] = v2 + else if(v2 < MEMLIMIT && v3 < MEMLIMIT) DMEM[0|v3] = DMEM[0|v2] + } else if(cmd == 7) DMEM[z] = v1 + v2 * v3 /* FMA */ + else if(cmd == 8) DMEM[z] = v1 - v2 /* SUB */ + else if(cmd == 9) DMEM[z] = (v2 == 0) ? 0 : (v1 / v2) /* DIV */ + else if(cmd == 10) DMEM[z] = (v2 == 0) ? (0|v1) : ((0|v1) % (0|v2)) /* MDF */ + else if(cmd == 11) DMEM[z] = Math.abs(v2) /* ABS */ + else if(cmd == 12) DMEM[z] = Math.sqrt(Math.abs(v2)) /* SQR */ + else if(cmd == 13) { /* NEL */ + if(x == 0) DMEM[z] = Math.exp(v2) + else DMEM[z] = (v2 == 0) ? 0 : Math.log(Math.abs(v2)) + } else if(cmd == 14) { /* TRI */ + if(x == 0) DMEM[z] = Math.sin(v2) + else if(x == 1) DMEM[z] = Math.cos(v2) + else DMEM[z] = Math.atan(v2) + } else if(cmd == 15) { /* RND */ + v1 = 0|v1 + DMEM[z] = v1 + (0|(Math.random() * ((0|v2) - v1 + 1))) + } + } /* command switch ends here */ + lno++ /* increment the program counter */ + if(lno >= MEMLIMIT || lno < 1 || (runlimit > 0 && runcount > runlimit)) { + if(traceflag) lineOut("Memory limit or runlimit hit, halting...\n") + halt = 1 + } else { /* fetch the next instruction */ + if(data_override && bcheck) { + cmd = PMEM[lno << 2] + x = (0|v1) % MEMLIMIT + y = (0|v2) % MEMLIMIT + z = (0|v3) % MEMLIMIT + } else { + cmd = PMEM[lno << 2] + x = PMEM[(lno << 2) + 1] + y = PMEM[(lno << 2) + 2] + z = PMEM[(lno << 2) + 3] + } + } + } + if(contfunc) contfunc() + } + + /* instruction line entry function (interactive mode) */ + /* accepts the continuation callback as the last parameter */ + function ilenter(lno, cmd, x, y, z, contfunc) { + if(lno > 0) { /* record the instruction in memory */ + PMEM[lno << 2] = cmd + PMEM[(lno << 2) + 1] = x + PMEM[(lno << 2) + 2] = y + PMEM[(lno << 2) + 3] = z + if(contfunc) contfunc() + } else if(lno == 0) { /* immediate execution */ + runcount = 0 + ilexec(lno, cmd, x, y, z, contfunc) + } else if(lno == -1) { /* display a range of instructions from cmd to x */ + if(cmd < MEMLIMIT && x < MEMLIMIT) for(y=cmd;y<=x;y++) { + z = y << 2 + lineOut("@" + y + ":\t" + [PMEM[z], PMEM[z+1], PMEM[z+2], PMEM[z+3]].join(' ') + '\n') + } + if(contfunc) contfunc() + } else if(lno == -2) { /* clear a range of data or instructions from x to y */ + if(cmd < MEMLIMIT && x < MEMLIMIT) for(z=x;z<=y;z++) { + if(cmd == 0) PMEM[z*4] = PMEM[z*4+1] = PMEM[z*4+2] = PMEM[z*4+3] = 0 + else DMEM[z] = 0.0 + } + if(contfunc) contfunc() + } else if(lno == -3) { /* tracing on/off */ + if(cmd == 0) {traceflag = 0; lineOut("Tracing off\n")} + else {traceflag = 1; lineOut("Tracing on\n")} + if(contfunc) contfunc() + } else if(lno == -4) { /* set the runlimit to the value of cmd */ + runlimit = cmd + lineOut("Runlimit set to " + cmd + "\n") + if(contfunc) contfunc() + } else if(lno == -5) { /* can't exit to the environment, reset instead */ + lineOut("Resetting the machine...\n") + for(y=0;y 0) { + var fobj = e.target.files[0], rdr = new FileReader() + rdr.onload = function() { + var progsrc = rdr.result + if(progsrc.length > 0) { + var instrs = progsrc.split(/\s+/), i, l = instrs.length, + instr, lno, cmd, x, y, z, icount = 0 + for(i=0;i ") + } + } + if(fobj) rdr.readAsText(fobj) + } + } + + /* main input routine: + * digits 0 to 9, 10 is space, + * 11 is minus/backspace, 12 is enter, 13 is decimal point, + * 14 is program load action */ + function doInput(number) { + if(number >= 0 && number < 10) /* normal digit */ + inputBuffer += ("" + number) + else if(number == 10) /* space */ + inputBuffer += " " + else if(number == 11) { /* backspace or minus */ + if(inputBuffer.length == 0) + inputBuffer += "-" + else + inputBuffer = inputBuffer.slice(0, inputBuffer.length - 1) + } else if(number == 12) { /* enter */ + screenBuffer += inputBuffer + "\n" + inputRes = inputBuffer + inputBuffer = "" + } else if(number == 13) /* decimal point */ + inputBuffer += "." + else if(number == 14) floader.click() /* trigger program loader */ + renderScreen() + } + + function setupInput() { + /* setup physical keys */ + w.addEventListener("keydown", function(e) { + switch(e.key) { + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + e.preventDefault() + doInput(parseInt(e.key)) + break + case "+": + case "/": + case " ": + case "Call": + case "ArrowDown": + e.preventDefault() + doInput(10) + break + case "Backspace": + case "Back": + case "ArrowLeft": + case "Delete": + case "-": + e.preventDefault() + e.stopPropagation() + doInput(11) + break + case "#": + case "Enter": + e.preventDefault() + doInput(12) + break + case "*": + case ",": + case ".": + e.preventDefault() + doInput(13) + break + case "l": + case "L": + case "ArrowUp": + case "SoftLeft": + e.preventDefault() + doInput(14) + break + } + }, false) + /* setup virtual keys */ + w.addEventListener("click", function(e) { + switch(e.target.id) { + case "b0": + case "b1": + case "b2": + case "b3": + case "b4": + case "b5": + case "b6": + case "b7": + case "b8": + case "b9": + e.preventDefault() + doInput(parseInt(e.target.id.slice(1))) + break + case "bspc": + e.preventDefault() + doInput(10) + break + case "bbksp": + e.preventDefault() + doInput(11) + break + case "benter": + e.preventDefault() + doInput(12) + break + case "bpoint": + e.preventDefault() + doInput(13) + break + case "bprogload": + e.preventDefault() + doInput(14) + break + } + }, false) + } + + function lineOut(s) { /* line output routine */ + screenBuffer += s + renderScreen() + } + + function charOut(c) { /* character output routine */ + screenBuffer += String.fromCharCode(+c) + renderScreen() + } + + function lineIn(cb) { + inputRes = "" + var inv = setInterval(function resWait() { + if(inputRes != "") { + clearInterval(inv) + cb(inputRes) + inputRes = "" + } + }, 100) + } + + function main() { /* UI entry point */ + screenDom = d.getElementById("screenfield") + floader = d.getElementById("programloadfield") + floader.addEventListener("change", loadProgFileHandler, false) + setupInput() + prompt = "> " + vm = createmu808(lineOut, charOut, lineIn) + lineOut("mu808 v1 by Luxferre\nPROGMEM: 16383 steps\nDATAMEM: 16384 floats\n") + ;(function repl() { + lineOut(prompt) + lineIn(function(entryString) { + var eparts = entryString.split(/[^\-0-9]+/).slice(0, 5), + lno = parseInt(eparts[0] || 0), + cmd = parseInt(eparts[1] || 0), + x = parseInt(eparts[2] || 0), + y = parseInt(eparts[3] || 0), + z = parseInt(eparts[4] || 0) + vm.ilenter(lno, cmd, x, y, z, repl) + }) + })() + } + + w.addEventListener("DOMContentLoaded", main, false) +})(window, document) diff --git a/mu8web/style.css b/mu8web/style.css new file mode 100644 index 0000000..f79d2a8 --- /dev/null +++ b/mu8web/style.css @@ -0,0 +1,83 @@ +* {box-sizing: border-box; margin: 0; padding: 0} +html, body { + width: 100%; + height: 100%; + font-family: "Noto Sans", Roboto, "Droid Sans", sans-serif; + font-size: 12pt; + text-align: center; + background: #111; + color: #eee; +} + +#workspace { + padding: 1rem 0.6rem; +} + +#screenfield { + display: inline-block; + min-width: 240px; + height: 50vh; + width: 100%; + max-width: 800px; + font-family: "Fira Code", "Droid Sans Mono", monospace; + font-size: inherit; + border: 1px solid #ccc; + border-radius: 2px; + overflow-y: scroll; + text-align: left; + text-wrap: auto; + white-space: pre-wrap; +} + +#keypad { + display: inline-block; + width: 50vw; + min-width: 206px; + max-width: 400px; + height: 20vh; + min-height: 180px; +} + +@media only screen and (max-width: 480px) { + #keypad, #keypad * { + display:none!important; + } +#workspace { + padding: 0.5rem 0!important; + } +} + +#keypad div { + padding: 1px 0; + height: 25%; +} + +#keypad button { + background: inherit; + color: inherit; + font-family: inherit; + font-size: inherit; + border: 1px solid #ddd; + border-radius: 3px; + width: calc(25% - 4px); + height: calc(100% - 4px); + cursor: pointer; +} + +#keypad button:hover { + color: cyan; + border-color: cyan; +} + +#keypad #bspc { + width: calc(50% - 4px); +} + +header {padding: 0.6rem 0} +footer {padding-top: 0.6rem} +footer div {padding: 0.6em 0} + +#programloadfield {display:none} + +a,a:active,a:visited {color: inherit} +a:hover {color:cyan}