{ "name": "Assembly Emulator", "icon": `build_circle`, "desc": "An assembly emulator that uses a simple assembly language. Great for learning the basics of assembly!", "id": "asm-emu", "globalID": "GopalOS.AssemblyEmulator", "defaultRelease": "alpha", "releases": { "alpha": { "version": "v1.0.0-alpha.12", "app-files": { "asm-emu": { type: "pgm", content: function() { if (document.getElementById("app_asm-emu_window")) { document.getElementById("app_asm-emu_window").style.display = "block"; } else { document.getElementById("windows").insertAdjacentHTML("beforeend", getRef("/apps/asm-emu/window")); getRef("/apps/asm-emu/load")(); } focusWin(document.getElementById("app_asm-emu_window")); regTbarIcon("/apps/asm-emu/asm-emu", "app_asm-emu_window", os.apps["asm-emu"].icon, true); refreshWindows(); } }, "load": { type: "js", content: function() { createRef("/home/appdata/asm-emu", "dir", { "files": { type: "dir", content: { "stl": { type: "dir", content: { "testlib": { type: "asm", content: `; Test library .strout "Test library loaded", 0` } } }, "demo": { type: "asm", content: `; Demo for GopalOS Assembly Emulator jmp _main _main: mov eax, message mov ebx, 2 mov ecx, 10 jmp part mov eax, 100 part: mov ebx, part int 0x10 message: .db "Hello, World!", 0` }, "test": { type: "asm", content: `; Test file jmp _main oof: .strout "oof", 0 .strout "hello again", 0 jmp end _main: .strout "Hello!", 0 jmp befend loop: .strout "A", 0 .strout "B", 0 .strout "C", 0 jmp loop befend: .strout "up!", 0 jmp oof end: .strout "Bye!", 0` } } } }); let devices = getRef("/apps/asm-emu/device-templates"); for (let deviceId of Object.keys(devices)) { document.getElementById("app_asm-emu_device-templates").insertAdjacentHTML("beforeend", `
${deviceId.replace(/(\d{4})(.*)/g, `$1$2`)}
${devices[deviceId].desc}
`); } } }, "cpustate": { type: "json", content: { eax: null, ebx: null, ecx: null, edx: null, eip: 0, zf: 0 } }, "stack": { type: "var", content: null }, "peripherals": { type: "var", content: { "mem": new Uint8Array([0, 0, 0, 0]) } }, "bytecode-guide": { type: "txt", content: `Opcodes for Gopal's Simple Assembly By: Gopal Othayoth Header: 47 4F 41 53 4D XX - "GOASM" version 0xXX (current version: 0x02) NN = 16 or 32 Input (code) formats: regNN - Register (e.g. eax) immNN - Literal value (including an absolute address) mem - Memory reference (denoted by [...], e.g. [eax + 8]) Output (bytecode) formats: (bit size in parentheses) IMMNN (NN) - Immediate value (including an absolute address) RELNN (NN) - Signed number indicating address offset REG (8) - Register (see below) MEM (depends) - Memory reference (see below) MEMF (8) - format of MEM (see below) MEMF values followed by MEM formats: [reg32]: 10 REG [reg32 + reg32]: 20 REG REG [reg16 + reg16]: 21 REG REG [reg32 + reg32 * imm32]: 30 REG REG IMM32 [reg32 + reg32 * imm16]: 31 REG REG IMM16 [reg16 + reg16 * imm16]: 31 REG REG IMM16 [reg32 + imm32]: 40 REG IMM32 [reg32 + imm16]: 41 REG IMM16 [reg32 + imm32 * imm32]: 50 REG IMM32 IMM32 [reg32 + imm16 * imm16]: 51 REG IMM16 IMM16 [imm32]: 60 IMM32 [imm32 + imm32]: 61 IMM32 [imm32 + imm32 * imm32]: 62 IMM32 IMM32 IMM32 [imm32 + imm16 * imm16]: 63 IMM32 IMM16 IMM16 (Only the formats [regNN], [regNN + regNN], [regNN + regNN * immNN], and [imm32] are currently supported.) (Memory references using labels (rel) are currently unsupported.) Register specifiers (REG): 01 - Instruction Pointer: (e)ip 02 - Zero Flag: zf 03 - Carry Flag: cf 0A - Register: (e)ax 0B - Register: (e)bx 0C - Register: (e)cx 0D - Register: (e)dx (32-bit or 16-bit register is chosen based on instruction operand size) Instructions: == MOV == mov reg32, imm32: 10 01 REG IMM32 mov reg32, imm16: 11 01 REG IMM16 mov reg16, imm16: 12 01 REG IMM16 mov reg32, reg32: 10 03 REG REG mov reg16, reg16: 11 03 REG REG mov reg32, mem: 10 04 MEMF REG MEM mov reg16, mem: 11 04 MEMF REG MEM mov mem, reg32: 10 05 MEMF MEM REG mov mem, reg16: 11 05 MEMF MEM REG == JUMP == (rel32: jmp = 20, je = 21, jne = 22) jmp rel32: 20 REL32 (rel16: jmp = 23, je = 24, jne = 25) jmp rel16: 23 REL16 == CMP == cmp reg32, imm32: 40 01 REG IMM32 cmp reg32, imm16: 41 01 REG IMM16 cmp reg16, imm16: 42 01 REG IMM16 == ARITHMETIC == add reg32, imm32: 40 01 REG IMM32 add reg32, imm16: 41 01 REG IMM16 add reg16, imm16: 42 01 REG IMM16 add reg32, reg32: 40 03 REG REG add reg16, reg16: 41 03 REG REG add reg32, mem: 40 04 MEMF REG MEM add reg16, mem: 41 04 MEMF REG MEM add mem, reg32: 40 05 MEMF MEM REG add mem, reg16: 41 05 MEMF MEM REG inc reg32: 50 01 REG inc reg16: 51 01 REG inc mem: 50 02 MEMF MEM incw mem: 51 02 MEMF MEM == STACK == push reg32: A0 REG push reg16: A1 REG pop reg32: B0 REG pop reg16: B1 REG == CONTROL FLOW == call rel32: C0 REL32 call rel16: C1 REL16 ret: C3 == SYSTEM INTERRUPTS == int imm8 D0 IMM8` }, "old_bytecode-guide": { type: "txt", content: `OLD Opcodes for Gopal's Simple Assembly By: Gopal Othayoth *** WILL BE REMOVED SOON, USE THE OTHER FILE *** Header: 47 4F 41 53 4D XX - "GOASM" version 0xXX (current version: 0x01) Location specifiers: 01 XX XX XX XX - Address or numeric literal: 0xXXXXXXXX 02 XX XX - Address or numeric literal: 0x0000XXXX 03 - Instruction Pointer: ip 04 - Zero Flag: zf 0A - Register: eax 0B - Register: ebx 0C - Register: ecx 0D - Register: edx Reserved: 0E - (reserved) 0F - (reserved) Note: Every instance of each label name will be replaced with the location (relative to the beginning of the file) it was found at when assembled. Note: Hereafter, capital letters represent any of the location specifier formats above. mov X, Y: 10 X Y jmp X: 1A X je X: 1B X jne X: 1C X push X: 12 X pop X: 13 X call X: 20 X ret: 21` }, "curr-label-values": { type: "var", content: {} }, "num-to-twoscomp": { type: "js", content: function(num, size = null) { let bitStr; if (size) { bitStr = num.toString(2).replace(/-/, "").slice(-size).padStart(size, "0"); } else if (Math.abs(num) < 0x10000) { bitStr = num.toString(2).replace(/-/, "").slice(-16).padStart(16, "0"); } else { bitStr = num.toString(2).replace(/-/, "").slice(-32).padStart(32, "0"); } if (num >= 0) { return parseInt(bitStr, 2); } else { bitStr = bitStr.replace(/0/g, "Z").replace(/1/g, "0").replace(/Z/g, "1"); return parseInt(bitStr, 2) + 1; } } }, "twoscomp-to-num": { type: "js", content: function(num, size = null) { let bitStr; if (size) { bitStr = num.toString(2).replace(/-/, "").slice(-size).padStart(size, "0"); } else if (Math.abs(num) < 0x10000) { bitStr = num.toString(2).replace(/-/, "").slice(-16).padStart(16, "0"); } else { bitStr = num.toString(2).replace(/-/, "").slice(-32).padStart(32, "0"); } if (bitStr[0] == "0") { return parseInt(bitStr, 2); } else { bitStr = bitStr.replace(/0/g, "Z").replace(/1/g, "0").replace(/Z/g, "1"); return (parseInt(bitStr, 2) + 1) * -1; } } }, "tokenize-asm": { type: "js", content: function(asm_code) { asm_code = asm_code.replace(/;(.+?)(?:\n|$)/g, ""); /*Regex without escaped quotes: /"[^"]+"|(\s)/g */ let lines = asm_code .split("\n") .map(line => line.trim()) .map(line => line.replace(/\[.+?\]/g, ".strout \"aaaaaaaa\", 0")) .map(line => line.replace(/\\"|"(?:\\"|[^"])*"|(\s)/g, (match, p1) => p1 ? "__SPACE_TEMP_PLACEHOLDER" : match) .split("__SPACE_TEMP_PLACEHOLDER")); /*Remove commas after arguments*/ lines = lines.map(line => line.map(part => part.slice(-1) == "," ? part.slice(0, -1) : part)); /*Remove empty lines*/ lines = lines.filter(line => line.length != 1 || line[0] != ""); /*Remove empty segments*/ lines = lines.map(line => line.filter(segment => segment != "")); return lines; } }, "run-asm": { type: "js", content: function(asm_code) { document.getElementById("app_asm-emu_statbar").innerHTML = "Assembling ASM\u2026"; const output = getRef("/apps/asm-emu/output"); const convToAddr = getRef("/apps/asm-emu/conv-to-addr"); const outErr = getRef("/apps/asm-emu/assemble-error"); const val = getRef("/apps/asm-emu/process-val"); const valType = getRef("/apps/asm-emu/get-val-type"); const toBytes = getRef("/apps/asm-emu/token-to-bytes"); const toToken = getRef("/apps/asm-emu/str-to-token"); const tokenize = getRef("/apps/asm-emu/tokenize-asm"); writeRef("/apps/asm-emu/curr-label-values", {}); const labels = getRef("/apps/asm-emu/curr-label-values"); const ntot = getRef("/apps/asm-emu/num-to-twoscomp"); getRef("/apps/asm-emu/clear-output")(); output("Beginning assembling..."); let lines = tokenize(asm_code); document.getElementById("app_asm-emu_statbar").innerHTML = "Running ASM\u2026"; let cpuState = getRef("/apps/asm-emu/cpustate"); let counter = 0; /*Byte counter*/ /*Initialize output array*/ let resArr = []; /*Define header*/ let header = [0x47, 0x4F, 0x41, 0x53, 0x4D, 0x01]; /*Add header*/ resArr = header.concat(resArr); let defines = {}; for (let line of lines) { line = line.map(token => defines[token] || token); if (line[0] == ".include") { if (line.length != 2) { outErr("NUM_ARGS"); break; } output(`Including ${line[1].slice(1, -1)}.asm`); lines.splice(1, 0, ...tokenize(getRef(`/home/appdata/asm-emu/files/${line[1].slice(1, -1)}`))); } else if (line[0] == ".define") { if (line.length != 3) { outErr("NUM_ARGS"); break; } output(`New define: ${line[1]} -> ${line[2]}`); defines[line[1]] = isNaN(Number(line[2])) ? line[2] : Number(line[2]); } else if (line[0].slice(-1) == ":" && line[0].indexOf(":") == line[0].length - 1 && line.length == 1) { /*TODO: Replace with its location in memory*/ let thisLabel = line[0].slice(0, -1); output(`Label found: ${thisLabel}, setting to ${convToAddr(counter)}`); labels[thisLabel] = counter; } else if (line[0] == "mov") { if (line.length != 3) { outErr("NUM_ARGS", line); break; } if (valType(line[1]) == "reg32") { /*mov reg32, ???*/ if (valType(line[2]) == "imm32") { resArr.push(0x10, 0x01, ...val(line[1]), ...val(line[2])); } else if (valType(line[2]) == "imm16") { resArr.push(0x11, 0x01, ...val(line[1]), ...val(line[2])); } else if (valType(line[2]) == "reg32") { resArr.push(0x10, 0x03, ...val(line[1]), ...val(line[2])); } else if (valType(line[2]) == "mem") { resArr.push(0x10, 0x05, ...val(line[1]), ...val(line[2])); } else { outErr(`Instruction mov(reg32, ${valType(line[2])}) is invalid`); } } else if (valType(line[1]) == "reg16") { /*mov reg16, ???*/ if (valType(line[2]) == "imm16") { resArr.push(0x12, 0x01, ...val(line[1]), ...val(line[2])); } else if (valType(line[2]) == "reg16") { resArr.push(0x11, 0x03, ...val(line[1]), ...val(line[2])); } else { outErr(`Instruction mov(reg16, ${valType(line[2])}) is invalid`); } } else if (valType(line[1]) == "mem") { if (valType(line[2]) == "reg32") { resArr.push(0x10, 0x04, ...val(line[1]), ...val(line[2])); } else { outErr(`Instruction mov(mem, ${valType(line[2])}) is invalid`); } } else { outErr("UNFINISHED"); } } else if (line[0] == "add") { if (line.length != 3) { outErr("NUM_ARGS", line); break; } if (valType(line[1]) == "reg32") { /*mov reg, ???*/ if (valType(line[2]) == "imm32") { resArr.push(0x40, 0x01, ...val(line[1]), ...val(line[2])); } else if (valType(line[2]) == "imm16") { resArr.push(0x41, 0x01, ...val(line[1]), ...val(line[2])); } } else { outErr("UNFINISHED"); } } else if (line[0] == "jmp") { if (line.length != 2) { outErr("NUM_ARGS", line); break; } if (valType(line[1]) == "rel") { /*Only supporting jmp rel16 for now*/ resArr.push(0x23, ...val(line[1])); } else { outErr("Error"); break; } } else if (line[0] == "push") { if (line.length != 2) { outErr("NUM_ARGS", line); break; } resArr.push(0x12, ...val(line[1])); } else if (line[0] == "pop") { if (line.length != 2) { outErr("NUM_ARGS", line); break; } resArr.push(0x13, ...val(line[1])); } else if (line[0] == ".db") { output(`Dumping bytes at line: ${line.join(" \u00B7 ")}`); for (let token of line.slice(1)) { resArr.push(...toBytes(toToken(token))); } } else if (line[0] == "int") { if (line.length < 2) { outErr("NUM_ARGS", line); break; } resArr.push(0xD0, ...val(line[1], 8)); } else if (line[0] == ".strout") { if (line.length < 2) { outErr("NUM_ARGS", line); break; } resArr.push(0xFF); for (let token of line.slice(1)) { resArr.push(...toBytes(toToken(token))); } } else { outErr("NO_SUCH_CMD", line); } counter = resArr.length; } /*Resolving labels*/ output("Resolving labels..."); console.info(resArr.map(item => { return (typeof item == "number") ? item.toString(16).padStart(2, "0") : item })); for (let i = 0; i < resArr.length; i++) { if (typeof resArr[i] == "string" && resArr[i].startsWith("LABEL:")) { /*Only supporting jmp rel16 for now*/ let offset = Number(labels[resArr[i].slice(6)]) - (i - 1); console.log(`Label ${resArr[i].slice(6)} position is ${offset + i}, position is ${i}, offset is ${offset}`); let offsetBytes = toBytes(ntot(offset, 16), 2); resArr[i] = offsetBytes[0]; resArr[i + 1] = offsetBytes[1]; } } console.info(resArr.map(item => { return (typeof item == "number") ? item.toString(16).padStart(2, "0") : item })); /*Debug version*/ console.log(resArr); createRef("/home/appdata/asm-emu/files/out-debug", "txt", ""); writeRef("/home/appdata/asm-emu/files/out-debug", resArr.map(x => (typeof x == "number") ? x.toString(16).padStart(2, "0") : x).join(" ")); output("Writing to file..."); createRef("/home/appdata/asm-emu/files/out", "bin", new Uint8Array()); writeRef("/home/appdata/asm-emu/files/out", new Uint8Array(resArr)); /*TODO: Output binary file instead of text file (after all basic assembling code is done)*/ output("Assembling complete"); document.getElementById("app_asm-emu_statbar").innerHTML = "Ready"; } }, "signal-names": { type: "var", content: { 2: "SIGINT", 4: "SIGILL", 9: "SIGKILL", 11: "SIGSEGV", 15: "SIGTERM" } }, "get-range": { type: "js", content: function(byteArr, base, start, end) { return byteArr.slice(base + start, base + end); } }, "get-range-num": { type: "js", content: function(byteArr, base, start, end) { return Number(`0x${byteArr.slice(base + start, base + end).map(b => b.toString(16).padStart(2, "0")).join("")}`); } }, "run-bin": { type: "js", content: function(binData) { const out = getRef("/apps/asm-emu/output"); const err = getRef("/apps/asm-emu/runtime-error"); const sigNames = getRef("/apps/asm-emu/signal-names"); const ntot = getRef("/apps/asm-emu/num-to-twoscomp"); const tton = getRef("/apps/asm-emu/twoscomp-to-num"); const getRange = getRef("/apps/asm-emu/get-range"); const getRangeNum = getRef("/apps/asm-emu/get-range-num"); let byteArr = Array.from(binData); let memory = Array(1024).fill(0); getRef("/apps/asm-emu/clear-output")(); out("Loading program into memory at address 0x00000100"); if (byteArr.length > (memory.length - 0x100)) { err("ERROR: Program is too large to fit in memory, exiting"); return; } for (let i = 0; i < byteArr.length; i++) { memory[0x100 + i] = byteArr[i]; } out("Running binary..."); /*toString() comparison is fine for this simple array*/ if (memory.slice(0x100, 0x105).toString() != [0x47, 0x4F, 0x41, 0x53, 0x4D].toString()) { err("BAD_HEADER"); return; } let cpuState = getRef("/apps/asm-emu/cpustate"); cpuState.eax = new Uint8Array([0, 0, 0, 0]); cpuState.ebx = new Uint8Array([0, 0, 0, 0]); cpuState.ecx = new Uint8Array([0, 0, 0, 0]); cpuState.edx = new Uint8Array([0, 0, 0, 0]); cpuState.eip = 0x100; cpuState.zf = 0; let signal = 0; let opscount = 0; cpuState.eip += 6; /*Skip past header*/ while (!signal) { let inst = memory[cpuState.eip]; console.log(`Current instruction byte: ${memory[cpuState.eip].toString(16).padStart(2, "0")} at ${cpuState.eip}`); if (inst == 0xD0) { switch (memory[cpuState.eip + 1]) { case 0x10: out("Halt"); signal = 15; break; default: err(`Invalid interrupt ${memory[cpuState.eip + 1].toString(16).padStart(2, "0")}`); signal = 9; break; } cpuState.eip += 2; } else if (inst == 0xFF) { /*.strout*/ let str = ""; cpuState.eip += 1; for (; memory[cpuState.eip] != 0x00 && opscount < 1000; cpuState.eip++, opscount++) { str += String.fromCharCode(memory[cpuState.eip]); } cpuState.eip += 1; out("strout: " + str); } /*mov dword*/ else if (inst == 0x10) { /*mov reg32, imm32*/ if (memory[cpuState.eip + 1] == 0x01) { let range = getRange(memory, cpuState.eip, 3, 7); switch (memory[cpuState.eip + 2]) { case 0x0A: cpuState.eax[0] = range[0]; cpuState.eax[1] = range[1]; cpuState.eax[2] = range[2]; cpuState.eax[3] = range[3]; break; case 0x0B: cpuState.ebx[0] = range[0]; cpuState.ebx[1] = range[1]; cpuState.ebx[2] = range[2]; cpuState.ebx[3] = range[3]; break; case 0x0C: cpuState.ecx[0] = range[0]; cpuState.ecx[1] = range[1]; cpuState.ecx[2] = range[2]; cpuState.ecx[3] = range[3]; break; case 0x0D: cpuState.edx[0] = range[0]; cpuState.edx[1] = range[1]; cpuState.edx[2] = range[2]; cpuState.edx[3] = range[3]; break; default: err("Error"); break; } cpuState.eip += 7; } /*mov reg, reg*/ if (memory[cpuState.eip + 1] == 0x03) { err("Not implemented yet!"); signal = 9; } } /*mov word*/ else if (inst == 0x11) { /*mov reg32, imm16*/ if (memory[cpuState.eip + 1] == 0x01) { let range = getRange(memory, cpuState.eip, 3, 5); switch (memory[cpuState.eip + 2]) { case 0x0A: cpuState.eax[2] = range[0]; cpuState.eax[3] = range[1]; if (range[0].toString(2)[0] == "1") { cpuState.eax[0] = 0xFF; cpuState.eax[1] = 0xFF; } else { cpuState.eax[0] = 0; cpuState.eax[1] = 0; } break; case 0x0B: cpuState.ebx[2] = range[0]; cpuState.ebx[3] = range[1]; if (range[0].toString(2)[0] == "1") { cpuState.ebx[0] = 0xFF; cpuState.ebx[1] = 0xFF; } else { cpuState.ebx[0] = 0; cpuState.ebx[1] = 0; } break; case 0x0C: cpuState.ecx[2] = range[0]; cpuState.ecx[3] = range[1]; if (range[0].toString(2)[0] == "1") { cpuState.ecx[0] = 0xFF; cpuState.ecx[1] = 0xFF; } else { cpuState.ecx[0] = 0; cpuState.ecx[1] = 0; } break; case 0x0D: cpuState.edx[2] = range[0]; cpuState.edx[3] = range[1]; if (range[0].toString(2)[0] == "1") { cpuState.edx[0] = 0xFF; cpuState.edx[1] = 0xFF; } else { cpuState.edx[0] = 0; cpuState.edx[1] = 0; } break; default: err("Error"); break; } cpuState.eip += 5; } /*mov reg, reg*/ if (memory[cpuState.eip + 1] == 0x03) { err("Not implemented yet!"); signal = 9; } } else if ([0x20, 0x21, 0x22].includes(inst)) { /*jmp rel32*/ err("jmp rel32 not implemented yet!"); signal = 4; } else if ([0x23, 0x24, 0x25].includes(inst)) { /*jmp rel16*/ if (inst == 0x23 || (inst == 0x24 && cpuState.zf) || (inst == 0x25 && !cpuState.zf)) { console.log(`Jumping (rel16) by ${tton(getRangeNum(memory, cpuState.eip, 1, 3))} bytes, eip is now ${cpuState.eip}`); console.log(memory.slice(cpuState.eip + 1, cpuState.eip + 3)); cpuState.eip += tton(getRangeNum(memory, cpuState.eip, 1, 3)); } else { cpuState.eip += 3; } } else { err(`Illegal instruction ${inst.toString(16).padStart(2, "0")}, or not implemented yet!`); signal = 4; } if (cpuState.eip >= memory.length) { signal = 15; } opscount++; if (opscount >= 1000) { err("MAX_OPS"); signal = 9; } } if (signal) { out(`Program received signal ${sigNames[signal]}`); if (signal != 15) { err(`fail: Program received signal ${sigNames[signal]}`); } } out("Done"); } }, "str-to-token": { type: "js", content: function(str) { const outErr = getRef("/apps/asm-emu/assemble-error"); if (str[0] == "\"" && str[str.length - 1] == "\"") { return str.slice(1, -1); } else if (!isNaN(Number(str))) { return Number(str); } else { outErr("NOT_TOKEN", str); return null; } } }, "token-to-bytes": { type: "js", content: function(token, minBytes = 0) { let res; if (typeof token == "number") { let str = token.toString(16); if (str.length % 2 != 0) { str = "0" + str; } res = str.match(/.{2}/g).map(n => parseInt(n, 16)); } else if (typeof token == "string") { res = token.split("").map(s => s.charCodeAt(0)); } while (res.length < minBytes) { res.unshift(0); } return res; } }, "reg-code": { type: "js", content: function(regName) { return ({ "eax": 0x0A, "ebx": 0x0B, "ecx": 0x0C, "edx": 0x0D })[regName]; } }, "process-val": { type: "js", content: function(item, size = null) { item = String(item); const outErr = getRef("/apps/asm-emu/assemble-error"); const labels = getRef("/apps/asm-emu/curr-label-values"); const toBytes = getRef("/apps/asm-emu/token-to-bytes"); const ntot = getRef("/apps/asm-emu/num-to-twoscomp"); const tton = getRef("/apps/asm-emu/twoscomp-to-num"); if (/^[0-9a-f]+h/.test(item.toLowerCase())) { item = item.toLowerCase().replace(/^([0-9a-f]+)h/, "0x$1"); } if (!isNaN(Number(item))) { const num = Number(item); if (size == 8 && num >= 0 && num <= 0xFF) { return [num]; } else if (size == 8 && num >= -0x8000 && num < 0) { return toBytes(ntot(num, 8), 1); } if (num >= 0 && num <= 0xFFFF) { return toBytes(num, 2); } else if (num >= 0 && num <= 0xFFFFFFFF) { return toBytes(num, 4); } else if (num >= -0x8000 && num < 0) { return toBytes(ntot(num), 2); } else if (num >= -0x80000000 && num < 0) { return toBytes(ntot(num), 4); } else { outErr("NUM_LIT", item); } } else if (item == "eax") { return [0x0A]; } else if (item == "ebx") { return [0x0B]; } else if (item == "ecx") { return [0x0C]; } else if (item == "edx") { return [0x0D]; } else if (/^\[.*\]$/.test(item)) { if (/^\[[^\s+]+\]$/.test(item)) { let regName = item.replace(/\[|\]/g, ""); let regCode = getRef("/apps/asm-emu/reg-codes"); return [0x10, regCode(regName)]; } else if (/^\[[^+]+\+[^+]+\]$/.test(item)) { let regNames = item.match(/^\[([^+]+)\+([^+]+)\]$/); regNames.shift(); return [0x20, regCode(regNames[0]), regCode(regNames[1])]; } else if (/^\[[^+]+\+[^+]+\*[^+]+\]$/.test(item)) { let regNames = item.match(/^\[([^+]+)\+([^+]+)\*([^+]+)\]$/); regNames.shift(); return [0x30, regCode(regNames[0]), regCode(regNames[1]), regCode(regNames[2])]; } else { outErr("ERROR!"); } } else if (/^[a-z_][a-z0-9_]+$/gi.test(item)) { /*Possible label (could be a label defined later)*/ if (!labels[item]) { labels[item] = null; } /*Only supporting jmp rel16 for now*/ return [`LABEL:${item}`, 0x00]; } else { return []; } } }, "get-val-type": { type: "js", content: function(item) { item = String(item); const outErr = getRef("/apps/asm-emu/assemble-error"); const labels = getRef("/apps/asm-emu/curr-label-values"); const toBytes = getRef("/apps/asm-emu/token-to-bytes"); const ntot = getRef("/apps/asm-emu/num-to-twoscomp"); const tton = getRef("/apps/asm-emu/twoscomp-to-num"); if (!isNaN(Number(item))) { const num = Number(item); if (num >= -0x8000 && num <= 0xFFFF) { return "imm16"; } else if (num >= 0 && num <= 0xFFFFFFFF) { return "imm32"; } else if (num >= -0x8000 && num < 0) { return "imm16"; } else if (num >= -0x80000000 && num < 0) { return "imm32"; } else { outErr("NUM_LIT", item); } } else if (["eax", "ebx", "ecx", "edx"].includes(item)) { return "reg32"; } else if (/^\[.*\]$/.test(item)) { return "mem"; } else if (/^[a-z_][a-z0-9_]+$/gi.test(item)) { /*Possible label (could be a label defined later)*/ return "rel"; } } }, "output": { type: "js", content: function(msg, outputDevice = "stdout", newlineAfter = true) { let stdoutElem = document.getElementById("app_asm-emu_stdout"); stdoutElem.insertAdjacentHTML("beforeend", msg + (newlineAfter ? "
" : "")); } }, "assemble-error": { type: "js", content: function(errType, text = "(line unavailable)") { let stderrElem = document.getElementById("app_asm-emu_stderr"); let errMsg = `ERROR at: ${text}
  `; switch (errType) { case "NUM_ARGS": errMsg += "Incorrect number of arguments"; break; case "NO_SUCH_CMD": errMsg += "No such command"; break; case "NUM_LIT": errMsg += "Invalid numeric literal (must be between -2147483648 and 2147483647 (0x80000000 to 0x7FFFFFFF))"; break; case "NOT_TOKEN": errMsg += "Invalid token"; break; default: errMsg += "Unknown Error"; break; } stderrElem.insertAdjacentHTML("beforeend", `${errMsg}
`); stderrElem.scrollTop = stderrElem.scrollHeight; } }, "runtime-error": { type: "js", content: function(errType) { let stderrElem = document.getElementById("app_asm-emu_stderr"); let errMsg = `ERROR at file offset ${getRef("/apps/asm-emu/conv-to-addr")(getRef("/apps/asm-emu/cpustate").eip)}
  `; switch (errType) { case "BAD_HEADER": errMsg += "Invalid header \"magic number\""; break; case "MEM_VIOL": errMsg += "Memory access violation"; break; case "MAX_OPS": errMsg += "Too many operations (reached 1000 ops)"; break; default: errMsg += errType; break; } stderrElem.insertAdjacentHTML("beforeend", `${errMsg}
`); stderrElem.scrollTop = stderrElem.scrollHeight; } }, "clear-output": { type: "js", content: function() { document.getElementById("app_asm-emu_stdout").innerHTML = ""; document.getElementById("app_asm-emu_stderr").innerHTML = ""; } }, "conv-to-addr": { type: "js", content: function(num) { num = Number(num); return "0x" + num.toString(16).padStart(8, "0").toUpperCase() } }, "switch-tab": { type: "js", content: function(pageId) { ["assemble", "devices"].forEach(id => document.getElementById(`app_asm-emu_${id}`).style.display = "none"); document.getElementById(`app_asm-emu_${pageId}`).style.display = "block"; } }, "devices": { type: "var", content: [] }, "device-templates": { type: "var", content: { "P1200": { desc: "Processor, single-thread; runs GOASM v2.0 bytecode", asmVer: 2 }, "P2200": { desc: "Processor, double-thread; runs GOASM v2.0 bytecode", asmVer: 2 }, "R1000": { desc: "RAM, 1 KiB (1024 B)", mem: 1024 }, "R2000": { desc: "RAM, 16 KiB (16\u2009384 B)", mem: 16384 }, "R1000RO": { desc: "ROM, 1 KiB (1024 B)", mem: 1024 }, "S1000T": { desc: "Screen, text-only (ASCII), 128\u00D7128 chars
16\u2009384 B addressable memory (16\u2009384 chars \u00D7 1 B/char)", mem: 16384 }, "S1000C": { desc: "Screen, 8-bit color graphics, 128\u00D7128 pixels
16\u2009384 B addressable memory (16\u2009384 pixels \u00D7 1 B/pixel)", mem: 16384 }, "S1000TC": { desc: "Screen, 24-bit color graphics, 128\u00D7128 pixels
49\u2009152 B addressable memory (16\u2009384 pixels \u00D7 3 B/pixel)", mem: 49152 } } }, "window": { type: "html", content: `
Assembly Emulator
🗕
🗖
❌︎
   
Standard output (stdout)
Standard error (stderr)
Standard input (stdin)
Ready
` } } } } }