use fmt; use os; use fs; use io; use bufio; use bytes; use time; use time::date; use strings; export type uxn = struct { pc: u16, console_vector: u16, screen_vector: u16, mouse_vector: u16, controller_vector: u16, ram: [BANKS_CAP] u8, dev: [0x100] u8, ptr: [2] u8, stk: [2][0x100] u8, running: bool, screen_size_changed: bool, screen_update: bool, screen: uxn_screen, palette: [4] u16, files: [2] uxnFile, }; export def MAXWIDTH = 4096; export def MAXHEIGHT = 2160; export def SCREENRATE = 60; //Screen consists of two layers of 2-bit pixels export type uxn_screen = ([MAXWIDTH][MAXHEIGHT]u8, [MAXWIDTH][MAXHEIGHT]u8); export type uxnFile = struct { file: io::handle, filepath: str, state: filestate, dir: bool, exists: bool, fappend: bool, }; export type filestate = enum { IDLE, FILE_READ, FILE_WRITE, DIR_READ, DIR_WRITE }; def NUMBANKS: u16 = 0x10; def BANKSIZE: u32 = 0x10000; def BANKS_CAP = NUMBANKS * BANKSIZE; const reset_vector: u16 = 0x0100; const colormodes: [4][16] u8 = [ [0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0], [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], [1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1], [2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2]]; fn initialize_uxn_mem() *uxn = { return alloc(uxn{ pc = reset_vector, console_vector = 0, screen_vector = 0, mouse_vector = 0, controller_vector = 0, ram = [0...], dev = [0...], ptr = [0...], stk = [[0...],[0...]], running = false, screen_size_changed = false, screen_update = true, screen = ([[0...]...],[[0...]...]), palette = [0...], files = [uxnFile { file = io::empty, filepath = "", state = filestate::IDLE, dir = false, exists = false, fappend = false, }...], })!; }; export fn get_window_size(state: *uxn) struct{width: u16, height: u16} = { let width_high = state.dev[0x22]; let width_low = state.dev[0x23]; let height_high = state.dev[0x24]; let height_low = state.dev[0x25]; let result = struct {width: u16 = short_from_bytes(width_high,width_low), height: u16 = short_from_bytes(height_high,height_low) }; return result; }; export fn set_window_size(w: u16, h: u16, state: *uxn) void = { state.dev[0x22] = (w >> 8): u8; state.dev[0x23] = (w): u8; state.dev[0x24] = (h >> 8): u8; state.dev[0x25] = (h): u8; }; fn emu_dei(port: u8, state: *uxn) u8 = { switch(port) { case 0x04 => return state.ptr[0]; case 0x05 => return state.ptr[1]; case 0xc0 => const date = time::date::localnow(); const year = time::date::year(&date); return ((year) >> 8): u8; case 0xc1 => const date = time::date::localnow(); const year = time::date::year(&date); return year: u8; case 0xc2 => const date = time::date::localnow(); return time::date::month(&date): u8 - 1; case 0xc3 => const date = time::date::localnow(); return time::date::day(&date): u8; case 0xc4 => const date = time::date::localnow(); return time::date::hour(&date): u8; case 0xc5 => const date = time::date::localnow(); return time::date::minute(&date): u8; case 0xc6 => const date = time::date::localnow(); return time::date::second(&date): u8; case 0xc7 => const date = time::date::localnow(); return (time::date::weekday(&date): u8 + 1) % 7; case 0xc8 => const date = time::date::localnow(); const day = time::date::yearday(&date) - 1; return ((day) >> 8): u8; case 0xc9 => const date = time::date::localnow(); const day = time::date::yearday(&date) - 1; return day: u8; case 0xca => const date = time::date::localnow(); const zone = time::date::zone(&date); return if(zone.dst) {yield 1;} else {yield 0;}; //TODO this isn't tested case => return state.dev[port]; }; }; export fn emu_deo(port: u8, value: u8, state: *uxn) void = { state.dev[port] = value; switch(port) { case 0x03 => const high = state.dev[0x02]; const low = value; const eaddr = short_from_bytes(high,low); deo_expansion(eaddr,state); case 0x04 => state.ptr[0] = value; case 0x05 => state.ptr[1] = value; case 0x0e => print_stack_debug(state); case 0x11 => const high = state.dev[0x10]; const low = value; state.console_vector = short_from_bytes(high,low); case 0x18 => fmt::print(value: rune)!; case 0x19 => fmt::error(value)!; case 0x21 => const high = state.dev[0x20]; const low = value; state.screen_vector = short_from_bytes(high,low); case 0x2e => draw_pixel(value,state); case 0x2f => draw_sprite(value,state); case 0x81 => const high = state.dev[0x80]; const low = value; state.controller_vector = short_from_bytes(high,low); case 0x91 => const high = state.dev[0x90]; const low = value; state.mouse_vector = short_from_bytes(high,low); case 0xa5 => // File/stat [0] filestat_to_addr(0,state); case 0xa6 => // File/delete [0] delete_file(0,state); case 0xa9 => // File/name [0] set_file_name(0,state); case 0xad => // File/read [0] read_file_to_addr(0,state); case 0xaf => //File/write [0] write_file_from_addr(0,state); case 0xb5 => // File/stat [1] filestat_to_addr(1,state); case 0xb6 => // File/delete [1] delete_file(1,state); case 0xb9 => // File/name [1] set_file_name(1,state); case 0xbd => // File/read [1] read_file_to_addr(1,state); case 0xbf => //File/write [1] write_file_from_addr(1,state); case => if( port == 0x22 || port == 0x23 || port == 0x24 || port == 0x25 ) { state.screen_size_changed = true; }else if( port == 0x09 || //TODO maybe also trigger on high bytes port == 0x0b || port == 0x0d ){ regenerate_palettes(state); }; }; }; fn deo_expansion(addr: u16, state: *uxn) void = { const op = state.ram[addr]; const length = short_from_bytes(state.ram[addr+1],state.ram[addr+2]); switch(op) { case 0x00 => //fill const bank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32; const dstaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32; const value = state.ram[addr+7]; // fmt::printfln("expansion fill: bank: {:x} addr: {:x} value: {:x} | length: {:x}",bank,dstaddr,value,length)!; if(bank < NUMBANKS) for(let i: u16 =0; i < length; i+=1){ state.ram[bank * BANKSIZE + dstaddr + i] = value; }; case 0x01 => //cpyl const srcbank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32; const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32; const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]): u32; const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]): u32; // fmt::printfln("Cpyl: src: {:x} <-> {:x} dst: {:x} <-> {:x} | length: {:x}",srcbank,srcaddr,dstbank,dstaddr,length)!; if(srcbank < NUMBANKS && dstbank < NUMBANKS) for(let i: u16 =0; i < length; i+=1){ const readval = state.ram[srcbank * BANKSIZE + srcaddr + i]; state.ram[dstbank * BANKSIZE + dstaddr + i] = readval; }; case 0x02 => //cpr TODO are these actually different?? Either way need to not use for loop const srcbank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]): u32; const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]): u32; const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]): u32; const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]): u32; // fmt::printfln("Cpyr: src: {:x} <-> {:x} dst: {:x} <-> {:x} | length: {:x}",srcbank,srcaddr,dstbank,dstaddr,length)!; if(srcbank < NUMBANKS && dstbank < NUMBANKS) for(let i: u16 =1; i <= length; i+=1){ const readval = state.ram[srcbank * BANKSIZE + srcaddr + length - i]; state.ram[dstbank * BANKSIZE + dstaddr + length - i] = readval; }; case => fmt::fatalf("Unknown expansion op: 0x{:x}",op); }; }; export type evalerror = !void; export type unhandled = !u8; export type quit = void; export type error = !(evalerror | unhandled); type simple_op = void; type literal_op = struct { short: bool, ret: bool}; type normal_op = struct { short: bool, keep: bool, ret: bool}; type BRK = simple_op; type JCI = simple_op; type JMI = simple_op; type JSI = simple_op; type LIT = literal_op; type INC = normal_op; type POP = normal_op; type NIP = normal_op; type SWP = normal_op; type ROT = normal_op; type DUP = normal_op; type OVR = normal_op; type EQU = normal_op; type NEQ = normal_op; type GTH = normal_op; type LTH = normal_op; type JMP = normal_op; type JCN = normal_op; type JSR = normal_op; type STH = normal_op; type LDZ = normal_op; type STZ = normal_op; type LDR = normal_op; type STR = normal_op; type LDA = normal_op; type STA = normal_op; type DEI = normal_op; type DEO = normal_op; type ADD = normal_op; type SUB = normal_op; type MUL = normal_op; type DIV = normal_op; type AND = normal_op; type ORA = normal_op; type EOR = normal_op; type SFT = normal_op; type instruction = (BRK | JCI | JMI | JSI | LIT | INC | POP | NIP | SWP | ROT | DUP | OVR | EQU | NEQ | GTH | LTH | JMP | JCN | JSR | STH | LDZ | STZ | LDR | STR | LDA | STA | DEI | DEO | ADD | SUB | MUL | DIV | AND | ORA | EOR | SFT); fn get_instruction(byte: u8) (instruction | error) = { let k: bool = (byte & (0b10000000) != 0 ); let r: bool = (byte & (0b01000000) != 0 ); let s: bool = (byte & (0b00100000) != 0 ); let opcode: u8 = byte & (0b00011111); switch(opcode) { case 0x00 => switch(byte){ case 0x00 => return void: BRK; case 0x20 => return void: JCI; case 0x40 => return void: JMI; case 0x60 => return void: JSI; case 0x80 => // LIT return LIT {short=false, ret=false}; case 0xa0 => // LIT2 return LIT {short=true, ret=false}; case 0xc0 => // LITr return LIT {short=false, ret=true}; case 0xe0 => // LIT2r return LIT {short=true, ret=true}; case => return byte: unhandled; }; case 0x01 => return INC {short = s, keep = k, ret = r}; case 0x02 => return POP {short = s, keep = k, ret = r}; case 0x03 => return NIP {short = s, keep = k, ret = r}; case 0x04 => return SWP {short = s, keep = k, ret = r}; case 0x05 => return ROT {short = s, keep = k, ret = r}; case 0x06 => return DUP {short = s, keep = k, ret = r}; case 0x07 => return OVR {short = s, keep = k, ret = r}; case 0x08 => return EQU {short = s, keep = k, ret = r}; case 0x09 => return NEQ {short = s, keep = k, ret = r}; case 0x0a => return GTH {short = s, keep = k, ret = r}; case 0x0b => return LTH {short = s, keep = k, ret = r}; case 0x0c => return JMP {short = s, keep = k, ret = r}; case 0x0d => return JCN {short = s, keep = k, ret = r}; case 0x0e => return JSR {short = s, keep = k, ret = r}; case 0x0f => return STH {short = s, keep = k, ret = r}; case 0x10 => return LDZ {short = s, keep = k, ret = r}; case 0x11 => return STZ {short = s, keep = k, ret = r}; case 0x12 => return LDR {short = s, keep = k, ret = r}; case 0x13 => return STR {short = s, keep = k, ret = r}; case 0x14 => return LDA {short = s, keep = k, ret = r}; case 0x15 => return STA {short = s, keep = k, ret = r}; case 0x16 => return DEI {short = s, keep = k, ret = r}; case 0x17 => return DEO {short = s, keep = k, ret = r}; case 0x18 => return ADD {short = s, keep = k, ret = r}; case 0x19 => return SUB {short = s, keep = k, ret = r}; case 0x1a => return MUL {short = s, keep = k, ret = r}; case 0x1b => return DIV {short = s, keep = k, ret = r}; case 0x1c => return AND {short = s, keep = k, ret = r}; case 0x1d => return ORA {short = s, keep = k, ret = r}; case 0x1e => return EOR {short = s, keep = k, ret = r}; case 0x1f => return SFT {short = s, keep = k, ret = r}; case => return byte: unhandled; }; }; export fn print_stack_debug(state: *uxn) void = { //TODO follow standard form fmt::println("Working Stack:")!; print_stack(false,state); fmt::println("Return Stack:")!; print_stack(true,state); }; fn print_stack(ret: bool, state: *uxn) void = { const p = switch(ret){ case true => yield 1; case false => yield 0; }; for(let i = state.ptr[p] - 8; i != state.ptr[p]; i+=1){ fmt::printf("{:x} ",state.stk[p][i])!; }; fmt::printf("\n")!; }; fn short_from_bytes(high: u8, low: u8) u16 = { return (high: u16) << 8 | (low: u16); }; fn read_from_addr(short: bool, addr: u16, state: *uxn) (u8 | u16) = { switch(short){ case true => const high = state.ram[addr]; const low = state.ram[addr+1]; return short_from_bytes(high,low); case false => return state.ram[addr]; }; }; fn write_to_addr(value: (u8 | u16), addr: u16, state: *uxn) void = { match(value){ case let s: u16 => const high = (s >> 8 ): u8; const low = s: u8; state.ram[addr] = high; state.ram[addr+1] = low; case let b: u8 => state.ram[addr] = b; }; }; fn read_from_zaddr(short: bool, zaddr: u8, state: *uxn) (u8 | u16) = { switch(short){ case true => const high = state.ram[zaddr: u16]; const low = state.ram[(zaddr+1): u16]; return short_from_bytes(high,low); case false => return state.ram[zaddr: u16]; }; }; fn write_to_zaddr(value: (u8 | u16), zaddr: u8, state: *uxn) void = { match(value){ case let s: u16 => const high = (s >> 8 ): u8; const low = s: u8; state.ram[zaddr: u16] = high; state.ram[(zaddr+1): u16] = low; case let b: u8 => state.ram[zaddr: u16] = b; }; }; fn push_to_stack(ret: bool, val: (u8 | u16), state: *uxn) void = { const stack = switch(ret){ case true => yield 1; case false => yield 0; }; match(val) { case let bval: u8 => state.stk[stack][state.ptr[stack]] = bval; state.ptr[stack] = state.ptr[stack] + 1; case let sval: u16 => let low: u8 = (sval & 0xFF): u8; let high: u8 = (sval >> 8): u8; state.stk[stack][state.ptr[stack]] = high; state.ptr[stack] = state.ptr[stack] + 1; state.stk[stack][state.ptr[stack]] = low; state.ptr[stack] = state.ptr[stack] + 1; }; }; // Get value from stack, offset 0 is top of stack. offset depends on short or byte fn get_stack_val(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16) = { const stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(!short){ let val: u8 = state.stk[stack][state.ptr[stack] - 1 - off]; return val; } else { let low: u8 = state.stk[stack][state.ptr[stack]-1 - (off * 2)]; let high: u8 = state.stk[stack][state.ptr[stack]-2 - (off * 2)]; return short_from_bytes(high,low); }; }; //peek offset is always byte offset fn peek(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16 ) = { const stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(!short){ let val: u8 = state.stk[stack][state.ptr[stack] - 1 - off]; return val; } else { let low: u8 = state.stk[stack][state.ptr[stack] - 1 - off]; let high: u8 = state.stk[stack][state.ptr[stack] - 2 - off]; return short_from_bytes(high,low); }; }; fn increment_stack(ret: bool, short: bool, state: *uxn) void = { const stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(short){ state.ptr[stack] = state.ptr[stack] + 2; } else { state.ptr[stack] = state.ptr[stack] + 1; }; }; fn decrement_stack(ret: bool, short: bool, state: *uxn) void = { const stack = switch(ret){ case true => yield 1; case false => yield 0; }; if(short){ state.ptr[stack] = state.ptr[stack] - 2; } else { state.ptr[stack] = state.ptr[stack] - 1; }; }; fn pop_from_stack(keep: bool, ret: bool, short: bool, state: *uxn) (u8 | u16) = { const val = get_stack_val(ret, short, 0, state); if(!keep) decrement_stack(ret, short, state); return val; }; fn pop_or_get(inst: normal_op, off: u8, state: *uxn) (u8 | u16) = { return switch(inst.keep) { case false => yield pop_from_stack(false,inst.ret,inst.short,state); case true => yield get_stack_val(inst.ret,inst.short, off,state); }; }; //Offset is always byte aligned in peek fn pop_or_peek(inst: normal_op, off: u8, state: *uxn) (u8 | u16) = { return switch(inst.keep) { case false => yield pop_from_stack(false,inst.ret,inst.short,state); case true => yield peek(inst.ret,inst.short, off,state); }; }; export fn uxn_eval(new_pc: u16, state: *uxn) (done | quit | error ) = { // let a: u16 = 0, b: u16 = 0, c: u16 = 0, x: [2]u16 = [0...], y: [2]u16 = [0...], z: [2]u16 = [0...]; state.pc = new_pc; state.running = true; for(state.running){ uxn_step(state)?; }; if(state.dev[0x0f] != 0) return quit; return done; }; export fn uxn_step(state: *uxn) (done | error) = { // fmt::printfln("Starting eval with pc: {:x}", pc)!; const readval = state.ram[state.pc]; const inst: instruction = get_instruction(readval)?; state.pc += 1; //TODO verify all pc changes match(inst) { case BRK => // fmt::println("Break!")!; state.running = false; return done; case JCI => const val: u8 = pop_from_stack(false, false, false, state): u8; if(val != 0) { state.pc += 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO, is this signed? }else{ state.pc += 2; }; case JMI => state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed? case JSI => push_to_stack(true, state.pc + 2, state); state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed? case let lit_inst: LIT => push_to_stack(lit_inst.ret, state.ram[state.pc], state); state.pc = state.pc + 1; if(lit_inst.short) { push_to_stack(lit_inst.ret, state.ram[state.pc],state); state.pc = state.pc + 1; }; case let inc_inst: INC => const val = match(pop_from_stack(inc_inst.keep,inc_inst.ret,inc_inst.short,state)) { //TODO this whole match statement seems unnecessary case let byte: u8 => yield byte + 1; case let short: u16 => yield short + 1; }; push_to_stack(inc_inst.ret,val,state); case let pop_inst: POP => if(!pop_inst.keep){ pop_from_stack(false,pop_inst.ret,pop_inst.short,state); }; case let nip_inst: NIP => const top = pop_or_get(nip_inst,0,state); const bot = pop_or_get(nip_inst,1,state); push_to_stack(nip_inst.ret, top, state); case let swp_inst: SWP => const top = pop_or_get(swp_inst,0,state); const bot = pop_or_get(swp_inst,1,state); push_to_stack(swp_inst.ret, top, state); push_to_stack(swp_inst.ret, bot, state); case let rot_inst: ROT => const top = pop_or_get(rot_inst,0,state); const mid = pop_or_get(rot_inst,1,state); const bot = pop_or_get(rot_inst,2,state); push_to_stack(rot_inst.ret, mid, state); push_to_stack(rot_inst.ret, top, state); push_to_stack(rot_inst.ret, bot, state); case let dup_inst: DUP => const val = pop_from_stack(dup_inst.keep, dup_inst.ret, dup_inst.short, state); push_to_stack(dup_inst.ret, val, state); push_to_stack(dup_inst.ret, val, state); case let ovr_inst: OVR => const top = pop_or_get(ovr_inst,0,state); const bot = pop_or_get(ovr_inst,1,state); push_to_stack(ovr_inst.ret, bot, state); push_to_stack(ovr_inst.ret, top, state); push_to_stack(ovr_inst.ret, bot, state); case let equ_inst: EQU => const top = pop_or_get(equ_inst,0,state); const bot = pop_or_get(equ_inst,1,state); //TODO, I think this works, but it feels unclean with the casting const val: u8 = if(top: u16 == bot: u16){ yield 1; } else { yield 0; }; push_to_stack(equ_inst.ret, val, state); case let neq_inst: NEQ => const top = pop_or_get(neq_inst,0,state); const bot = pop_or_get(neq_inst,1,state); //TODO, I think this works, but it feels unclean with the casting const val: u8 = if(top: u16 != bot: u16){ yield 1; } else { yield 0; }; push_to_stack(neq_inst.ret, val, state); case let gth_inst: GTH => const top = pop_or_get(gth_inst,0,state); const bot = pop_or_get(gth_inst,1,state); //TODO, I think this works, but it feels unclean with the casting const val: u8 = if(bot: u16 > top: u16){ yield 1; } else { yield 0; }; push_to_stack(gth_inst.ret, val, state); case let lth_inst: LTH => const top = pop_or_get(lth_inst,0,state); const bot = pop_or_get(lth_inst,1,state); //TODO, I think this works, but it feels unclean with the casting const val: u8 = if(bot: u16 < top: u16){ yield 1; } else { yield 0; }; push_to_stack(lth_inst.ret, val, state); case let jmp_inst: JMP => const addr = pop_or_get(jmp_inst,0,state); match(addr) { case let b: u8 => let off = b: i8: i32; state.pc = (state.pc: i32 + off): u16; //TODO verify if pc changes are right case let s: u16 => state.pc = s; }; case let jcn_inst: JCN => const addr = pop_or_get(jcn_inst,0,state); const cond: u8 = pop_or_peek(normal_op {keep = jcn_inst.keep, ret = jcn_inst.ret, short = false}, if(jcn_inst.short){ yield 2;} else { yield 1; }, state ): u8; if( cond != 0) { match(addr) { case let b: u8 => let off = b: i8: i32; state.pc = (state.pc: i32 + off): u16; //TODO verify if pc changes are right case let s: u16 => state.pc = s; }; }; case let jsr_inst: JSR => let addr = pop_or_get(jsr_inst,0,state); push_to_stack(true, state.pc, state); match(addr) { case let b: u8 => let off = b: i8: i32; state.pc = (state.pc: i32 + off): u16; //TODO verify if pc changes are right case let s: u16 => state.pc = s; }; case let sth_inst: STH => const val = pop_from_stack(sth_inst.keep,sth_inst.ret,sth_inst.short,state); push_to_stack(!sth_inst.ret, val, state); case let ldz_inst: LDZ => const addr: u8 = pop_from_stack(ldz_inst.keep, ldz_inst.ret, false, state): u8; const val = read_from_zaddr(ldz_inst.short,addr,state); push_to_stack(ldz_inst.ret, val, state); case let stz_inst: STZ => const addr: u8 = pop_or_peek(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 0, state): u8; const val = pop_or_peek(stz_inst,1,state); write_to_zaddr(val,addr,state); case let ldr_inst: LDR => const reladdr = (pop_from_stack(ldr_inst.keep, ldr_inst.ret, false, state): u16): i8; const addr: u16 = (state.pc: u32: i32 + reladdr): u16; const val = read_from_addr(ldr_inst.short,addr,state); push_to_stack(ldr_inst.ret, val, state); case let str_inst: STR => const reladdr = (pop_or_peek(normal_op{short = false, keep = str_inst.keep, ret = str_inst.ret}, 0, state): u16 & 0x00FF): u8: i8; const addr: u16 = (state.pc: u32: i32 + reladdr): u16; const val = pop_or_peek(str_inst,1,state); write_to_addr(val,addr,state); case let lda_inst: LDA => const addr: u16 = pop_from_stack(lda_inst.keep, lda_inst.ret, true, state): u16; const val = read_from_addr(lda_inst.short,addr,state); push_to_stack(lda_inst.ret, val, state); case let sta_inst: STA => const addr: u16 = pop_or_peek(normal_op{short = true, keep = sta_inst.keep, ret = sta_inst.ret}, 0, state): u16 ; const val = pop_or_peek(sta_inst,2,state); write_to_addr(val,addr,state); case let dei_inst: DEI => const port: u8 = pop_from_stack(dei_inst.keep, dei_inst.ret, false, state): u8; const val = if(dei_inst.short){ yield short_from_bytes(emu_dei(port,state),emu_dei(port+1,state)); }else{ yield emu_dei(port,state); }; push_to_stack(dei_inst.ret,val,state); case let deo_inst: DEO => const port: u8 = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 0, state): u8; const val = pop_or_peek(deo_inst,1,state); match(val){ case let s: u16 => const high: u8 = (s >> 8): u8; const low: u8 = s: u8; emu_deo(port,high,state); emu_deo(port+1,low,state); case let b: u8 => emu_deo(port,b,state); }; case let add_inst: ADD => const top = pop_or_get(add_inst,0,state); const bot = pop_or_get(add_inst,1,state); const res = if(!add_inst.short){ yield (top: u8 + bot: u8); } else { yield (top: u16 + bot: u16); }; push_to_stack(add_inst.ret, res, state); case let sub_inst: SUB => const top = pop_or_get(sub_inst,0,state); const bot = pop_or_get(sub_inst,1,state); const res = if(!sub_inst.short){ yield (bot: u8 - top: u8); } else { yield (bot: u16 - top: u16); }; push_to_stack(sub_inst.ret, res, state); case let mul_inst: MUL => const top = pop_or_get(mul_inst,0,state); const bot = pop_or_get(mul_inst,1,state); const res = if(!mul_inst.short){ yield (bot: u8 * top: u8); } else { yield (bot: u16 * top: u16); }; push_to_stack(mul_inst.ret, res, state); case let div_inst: DIV => const top = pop_or_get(div_inst,0,state); const bot = pop_or_get(div_inst,1,state); const res = if(!div_inst.short){ assert(bot is u8); assert(top is u8); if(top: u8 == 0) yield top; yield (bot: u8 / top: u8): u8; } else { assert(bot is u16); assert(top is u16); if(top: u16 == 0) yield top; yield (bot: u16 / top: u16): u16; }; push_to_stack(div_inst.ret, res, state); case let and_inst: AND => const top = pop_or_get(and_inst,0,state); const bot = pop_or_get(and_inst,1,state); const res = if(!and_inst.short){ yield (bot: u8 & top: u8); } else { yield (bot: u16 & top: u16); }; push_to_stack(and_inst.ret, res, state); case let ora_inst: ORA => const top = pop_or_get(ora_inst,0,state); const bot = pop_or_get(ora_inst,1,state); const res = if(!ora_inst.short){ yield (bot: u8 | top: u8); } else { yield (bot: u16 | top: u16); }; push_to_stack(ora_inst.ret, res, state); case let eor_inst: EOR => let top = pop_or_get(eor_inst,0,state); let bot = pop_or_get(eor_inst,1,state); let res = if(!eor_inst.short){ yield (bot: u8 ^ top: u8); } else { yield (bot: u16 ^ top: u16); }; push_to_stack(eor_inst.ret, res, state); case let sft_inst: SFT => const shifts: u8 = pop_or_peek(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 0, state): u8; const rightshifts = shifts & 0b00001111; const leftshifts = ((shifts & 0b11110000) >> 4) & 0b00001111; const val = pop_or_peek(sft_inst,1,state); const res = match(val) { case let s: u16 => yield (s >> rightshifts ) << leftshifts; case let b: u8 => yield (b >> rightshifts ) << leftshifts; }; push_to_stack(sft_inst.ret, res, state); case => return readval: unhandled; }; return done; }; // Converts an error into a user-friendly string export fn strerror(err: error) str = { match (err) { case evalerror => return "Reached unexpected part of eval code"; case let val: unhandled => return fmt::asprintf("Unknown Opcode read: {:x}", val: u8)!; }; }; export fn uxn_init(path: str) ( *uxn | error ) = { const romfile = match (os::open(path,fs::flag::RDONLY)) { case let file: io::file => yield file; case let err: fs::error => fmt::fatalf("Error opening {}: {}", path, fs::strerror(err)); }; const state: *uxn = initialize_uxn_mem(); //Copy rom into ram const bytesread = match (io::read(romfile,state.ram[0x100..(BANKS_CAP - 0x100)])){ case let bytes: size => yield bytes; case let eof: io::EOF => fmt::fatalf("Empty Rom"); case let err: io::error => fmt::fatalf("Error reading: {}", io::strerror(err)); }; io::close(romfile)!; //TODO, isn't there some kind of oversized roms? //Initialize default size state.dev[0x22] = 0x02; state.dev[0x23] = 0x80; state.dev[0x24] = 0x01; state.dev[0x25] = 0xe0; //Initialize default colors TODO look for .theme files state.dev[0x08] = 0xf0; state.dev[0x09] = 0x7f; state.dev[0x0a] = 0xf0; state.dev[0x0b] = 0xd6; state.dev[0x0c] = 0xf0; state.dev[0x0d] = 0xb2; regenerate_palettes(state); return state; }; export fn uxn_reset(state: *uxn) void = { state.running = true; uxn_eval(reset_vector,state)!; };