Files
esop/web/uxncore.js
T

189 lines
6.7 KiB
JavaScript

/**
* Uxn core JS implementation for ESOP VM
* @version 0.0.1
* @author Luxferre
* @license Unlicense <https://unlicense.org>
*/
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
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) {
var p, p2
if(bs) {
p = mem.dev[x]
p2 = mem.dev[x+1]
mem.dev[x] = y>>8
if(x in devWriteHooks)
devWriteHooks[x](x, mem.dev[x], p)
mem.dev[x+1] = y&255
if((x+1) in devWriteHooks)
devWriteHooks[x+1](x+1, mem.dev[x+1], p2)
}
else {
p = mem.dev[x]
mem.dev[x] = y
if(x in devWriteHooks)
devWriteHooks[x](x, y, p)
}
}
// main eval loop
function uxnstart(startaddr, vmTrace) {
pc = startaddr & 65535
var instr, src, dst, a, b, c, tmpTrace = !!vmTrace
if(!pc) {
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, 0|(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<l;i++)
mem.ram[addr+i] = prog[i]
},
start: uxnstart,
setdev: function(port, val) {
mem.dev[port] = val
if(this.memDevOffset)
mem.ram[this.memDevOffset+port] = val
},
getdev: function(port) {return mem.dev[port]},
getram: function(start, length) {return mem.ram.subarray(start, start+length)},
setram: function(addr, bVal) {mem.ram[addr] = bVal},
setWriteHook: function(port, cb) {
if(!cb) cb = function(){}
devWriteHooks[port] = cb
},
setReadHook: function(devPort, cb) {
if(!cb) cb = function(){}
devReadHooks[devPort] = cb
},
triggerVector: function(devId) {
var basePos = (devId >> 4) << 4
var targetAddr = (mem.dev[basePos] << 8) | mem.dev[basePos + 1]
if(targetAddr) uxnstart(targetAddr)
}
}
}