/** * Uxn core JS implementation for ESOP VM * @version 0.0.1 * @author Luxferre * @license Unlicense */ function UxnCore() { function initMem() { return { ram: new Uint8Array(65536), //main RAM wst: {ptr: 0, pk: 0, dat: new Uint8Array(256)}, //working stack rst: {ptr: 0, pk: 0, dat: new Uint8Array(256)}, //return stack dev: new Uint8Array(256) //device memory } } var mem = null, bs = 0, //byte/short flag km = 0, //keep mode flag pc = 0, //program counter devWriteHooks = {}, devReadHooks = {} //helper methods function trapout(errcode) { //error reporting with vector execution var vec = (mem.dev[0] << 8) | mem.dev[1] if(vec) uxnstart(vec) else { console.error('Error code: '+errcode+' at instruction #'+pc) console.log('Working stack: ', mem.wst.dat) console.log('Return stack: ', mem.rst.dat) } pc = 0 } function rel(x) {return x > 0x80 ? x - 256 : x} function PUSH8(s, x) {if(s.ptr === 255) trapout(2);s.dat[s.ptr++] = x} function PUSH16(s, x) {PUSH8(s, x>>8);PUSH8(s, x&255)} function PUSH(s, x) {if(bs) PUSH16(s, x);else PUSH8(s, x)} function POP8(s) { var ptr = km ? --s.pk : --s.ptr if(ptr < 0) trapout(1) return s.dat[ptr] } function POP16(s) {return POP8(s) | (POP8(s)<<8)} function POP(s) {return bs ? POP16(s) : POP8 (s)} function JUMP(x, pc) {return bs ? x : (pc + rel(x))} function POKE(x, y) { if(bs) { mem.ram[x] = y>>8 mem.ram[x+1] = y&255 } else mem.ram[x] = y } function PEEK16(x) {return (mem.ram[x]<<8) | mem.ram[x+1]} function PEEK(x) {return bs ? PEEK16(x) : mem.ram[x]} function DEVR(x) {if(x in devReadHooks) devReadHooks[x]();return bs ? (mem.dev[x] << 8)|mem.dev[x+1] : mem.dev[x]} function DEVW(x, y) { if(bs) { mem.dev[x] = y>>8 if(x in devWriteHooks) devWriteHooks[x](x, mem.dev[x]) mem.dev[x+1] = y&255 if((x+1) in devWriteHooks) devWriteHooks[x+1](x+1, mem.dev[x+1]) } else { mem.dev[x] = y if(x in devWriteHooks) devWriteHooks[x](x, y) } } // main eval loop function uxnstart(startaddr, vmTrace) { pc = startaddr & 65535 var instr, src, dst, a, b, c, tmpTrace = !!vmTrace if(!pc || mem.dev[0x0f]) { this.active = false return 0 } this.active = true while(instr = mem.ram[pc++]) { bs = (instr & 0x20) ? 1 : 0 //short mode if(instr & 0x40) { //return mode src = mem.rst dst = mem.wst } else { src = mem.wst dst = mem.rst } if(instr & 0x80) { //keep mode km = 1 src.pk = src.ptr dst.pk = dst.ptr } else km = 0 // main instruction selector switch(instr & 0x1f) { // Stack case 0x00: /* LIT */ PUSH(src, PEEK(pc)); pc += bs + 1; break; case 0x01: /* INC */ PUSH(src, POP(src) + 1); break; case 0x02: /* POP */ POP(src); break; case 0x03: /* NIP */ a = POP(src); POP(src); PUSH(src, a); break; case 0x04: /* SWP */ a = POP(src); b = POP(src); PUSH(src, a); PUSH(src, b); break; case 0x05: /* ROT */ a = POP(src); b = POP(src); c = POP(src); PUSH(src, b); PUSH(src, a); PUSH(src, c); break; case 0x06: /* DUP */ a = POP(src); PUSH(src, a); PUSH(src, a); break; case 0x07: /* OVR */ a = POP(src); b = POP(src); PUSH(src, b); PUSH(src, a); PUSH(src, b); break; // Logic case 0x08: /* EQU */ a = POP(src); b = POP(src); PUSH8(src, 0|(b == a)); break; case 0x09: /* NEQ */ a = POP(src); b = POP(src); PUSH8(src, 0|(b != a)); break; case 0x0a: /* GTH */ a = POP(src); b = POP(src); PUSH8(src, 0|(b > a)); break; case 0x0b: /* LTH */ a = POP(src); b = POP(src); PUSH8(src, 0|(b < a)); break; case 0x0c: /* JMP */ pc = JUMP(POP(src), pc); break; case 0x0d: /* JCN */ a = POP(src); if(POP8(src)) pc = JUMP(a, pc); break; case 0x0e: /* JSR */ PUSH16(dst, pc); pc = JUMP(POP(src), pc); break; case 0x0f: /* STH */ PUSH(dst, POP(src)); break; // Memory case 0x10: /* LDZ */ PUSH(src, PEEK(POP8(src))); break; case 0x11: /* STZ */ POKE(POP8(src), POP(src)); break; case 0x12: /* LDR */ PUSH(src, PEEK(pc + rel(POP8(src)))); break; case 0x13: /* STR */ POKE(pc + rel(POP8(src)), POP(src)); break; case 0x14: /* LDA */ PUSH(src, PEEK(POP16(src))); break; case 0x15: /* STA */ POKE(POP16(src), POP(src)); break; case 0x16: /* DEI */ PUSH(src, DEVR(POP8(src))); break; case 0x17: /* DEO */ a = POP8(src); b = POP(src); if(a===this.tracePort) tmpTrace=true;else DEVW(a, b); break; // Arithmetic case 0x18: /* ADD */ a = POP(src); b = POP(src); PUSH(src, b + a); break; case 0x19: /* SUB */ a = POP(src); b = POP(src); PUSH(src, b - a); break; case 0x1a: /* MUL */ a = POP(src); b = POP(src); PUSH(src, b * a); break; case 0x1b: /* DIV */ a = POP(src); b = POP(src); if(!a) trapout(3); PUSH(src, b / a); break; case 0x1c: /* AND */ a = POP(src); b = POP(src); PUSH(src, b & a); break; case 0x1d: /* ORA */ a = POP(src); b = POP(src); PUSH(src, b | a); break; case 0x1e: /* EOR */ a = POP(src); b = POP(src); PUSH(src, b ^ a); break; case 0x1f: /* SFT */ a = POP8(src); b = POP(src); PUSH(src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)); break; } if(vmTrace || tmpTrace) { console.log('Step: ', pc, 'Instruction:', instr.toString(16), 'Base instr:', (instr&0x1f).toString(16), 'stackptr:', src.ptr, 'stack:', src.dat) tmpTrace = vmTrace } } } return { active: false, boot: function() { this.active = false this.tracePort = 0x0e this.memDevOffset = 0 mem = initMem() bs = 0 pc = 0 km = 0 devWriteHooks = {} devReadHooks = {} }, load: function(prog, addr) { if(!addr) addr = 0x100 for(var i=0, l=prog.length;i> 4) << 4 var targetAddr = (mem.dev[basePos] << 8) | mem.dev[basePos + 1] if(targetAddr) uxnstart(targetAddr) } } }