Files
meadow/uxn/uxn.ha
T

1839 lines
55 KiB
Hare

use fmt;
use os;
use fs;
use io;
use bufio;
use bytes;
use time;
use time::date;
use strings;
// let console_vector: u16 = 0;
// let ram: [0x10000] u8 = [0...];
// let dev: [0x100] u8 = [0...];
// let ptr: [2] u8 = [0...];
// let stk: [2][0x100] u8 = [[0...],[0...]];
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 = 0x10;
def BANKSIZE = 0x10000;
def BANKS_CAP = NUMBANKS * BANKSIZE;
const banksize: u32 = BANKSIZE;
const numbanks: u16 = NUMBANKS;
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;
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];
// fmt::printfln("Read {:x} from port {:x}", val, port)!;
};
};
fn emu_deo(port: u8, value: u8, state: *uxn) void = {
// fmt::printfln("Writing {:x} to port {:x}", value, port)!;
state.dev[port] = value;
switch(port) {
case 0x04 =>
state.ptr[0] = value;
case 0x05 =>
state.ptr[1] = value;
case 0x11 =>
let high = state.dev[0x10];
let low = value;
state.console_vector = short_from_bytes(high,low);
// fmt::printfln("Setting console_vector to: {:x}", console_vector)!;
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 generate_dir_string(name: str) str = {
// fmt::println("Generating dir string!")!;
let list: []u8 = [];
const fs: *fs::fs = match(os::diropen(name)){
case let f: *fs::fs =>
yield f;
case let err: fs::error =>
fmt::fatalf("Error opening directory: {}", fs::strerror(err));
};
let iter: *fs::iterator = match(fs::iter(fs,".")){
case let i: *fs::iterator =>
yield i;
case let err: fs::error =>
fmt::fatalf("No iterator in directory: {}", fs::strerror(err));
// case null =>
// fmt::fatalf("Null pointer when opening directory: {}", name);
};
for(let item => fs::next(iter)){
match(item){
case let d: fs::dirent =>
//001a file.txt
//???? large_file.mp4
// 0-f A file
// - A directory
// ? A file that is larger than 64kb
// ! A missing file
let leftstring = "!!!!";
let namestring = d.name;
if(d.ftype == fs::mode::DIR){
leftstring = "----";
namestring = strings::concat(d.name,"/")!;
}else{
const stat = fs::stat(fs,d.name)!;
const fsize = stat.sz: u32;
if(fsize <= 0xFFFF){
leftstring = fmt::asprintf("{:x.4}",fsize)!;
}else {
leftstring = "????";
};
};
const wholestring = strings::concat(strings::join(" ", leftstring, d.name)!, "\n")!;
const utf8 = strings::toutf8(wholestring);
for( let c .. utf8 ){
append(list,c)!;
};
case let err: fs::error =>
fmt::fatalf("Error looking in directory: {}", fs::strerror(err));
};
};
return strings::fromutf8(list)!;
};
fn read_file_to_addr(fi: u8, state: *uxn) void = {
const length = if(fi == 0) { yield short_from_bytes(state.dev[0xaa],state.dev[0xab]); }
else { yield short_from_bytes(state.dev[0xba],state.dev[0xbb]); };
const destaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xac],state.dev[0xad]); }
else { yield short_from_bytes(state.dev[0xbc],state.dev[0xbd]); };
// fmt::printfln("Reading {} bytes to 0x{:x}", length, destaddr)!;
let bytesread = 0;
if(!state.files[fi].dir){
for(let i: u16 =0; i<length; i+=1){
match (bufio::read_byte(state.files[fi].file)){
case let b: u8 =>
bytesread += 1;
// fmt::printfln("Reading byte {}: from file",b)!;
state.ram[destaddr+i] = b;
case let eof: io::EOF =>
break;
case let err: io::error =>
fmt::fatalf("Error reading: {}", io::strerror(err));
};
};
}else {
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
let dirstring = generate_dir_string(name);
let dirstringiter = strings::iter(dirstring);
for(let i: u16 =0; i<length; i+=1){
let char: rune = match(strings::next(&dirstringiter)){
case let r: rune =>
yield r;
case done =>
break;
};
bytesread += 1;
state.ram[destaddr+i] = char: u8;
};
// fmt::printf(dirstring)!;
};
if(fi == 0){
state.dev[0xa2] = (bytesread >> 8): u8;
state.dev[0xa3] = (bytesread): u8;
}else if(fi ==1){
state.dev[0xb2] = (bytesread >> 8): u8;
state.dev[0xb3] = (bytesread): u8;
};
return;
};
fn filestat_to_addr(fi: u8, state: *uxn) void = {
const length = if(fi == 0) { yield short_from_bytes(state.dev[0xaa],state.dev[0xab]); }
else { yield short_from_bytes(state.dev[0xba],state.dev[0xbb]); };
const destaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa4],state.dev[0xa5]); }
else { yield short_from_bytes(state.dev[0xb4],state.dev[0xb5]); };
// fmt::printfln("Reading {} bytes to 0x{:x}", length, destaddr)!;
let bytesread = 0;
if(!state.files[fi].dir){
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
const stat = os::fstat(state.files[fi].file: io::file)!;
const fsize = stat.sz: u32;
const modestring = if(fsize <= 0xFFFF){
yield fmt::asprintf("{:x.4}",fsize)!;
}else {
yield "????";
};
let modestringiter = strings::iter(modestring);
for(let i: u16 =0; i<length; i+=1){
let char: rune = match(strings::next(&modestringiter)){
case let r: rune =>
yield r;
case done =>
break;
};
bytesread += 1;
state.ram[destaddr+i] = char: u8;
};
for(let i: u16 =0; i<length; i+=1){
match (bufio::read_byte(state.files[fi].file)){
case let b: u8 =>
bytesread += 1;
// fmt::printfln("Reading byte {}: from file",b)!;
state.ram[destaddr+i] = b;
case let eof: io::EOF =>
state.ram[destaddr+i] = 0; //This might need to be done differently
case let err: io::error =>
fmt::fatalf("Error reading: {}", io::strerror(err));
};
};
}else {
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
let dfs = os::diropen(name)!;
let df = os::dirfile(dfs);
const stat = os::fstat(df)!;
const fsize = stat.sz: u32;
const modestring = if(fsize <= 0xFFFF){
yield fmt::asprintf("{:x.4}",fsize)!;
}else {
yield "????";
};
let modestringiter = strings::iter(modestring);
for(let i: u16 =0; i<length; i+=1){
let char: rune = match(strings::next(&modestringiter)){
case let r: rune =>
yield r;
case done =>
break;
};
bytesread += 1;
state.ram[destaddr+i] = char: u8;
};
for(let i: u16 =0; i<length; i+=1){
match (bufio::read_byte(state.files[fi].file)){
case let b: u8 =>
bytesread += 1;
// fmt::printfln("Reading byte {}: from file",b)!;
state.ram[destaddr+i] = b;
case let eof: io::EOF =>
state.ram[destaddr+i] = 0; //This might need to be done differently
case let err: io::error =>
fmt::fatalf("Error reading: {}", io::strerror(err));
};
};
};
if(fi == 0){
state.dev[0xa2] = (bytesread >> 8): u8;
state.dev[0xa3] = (bytesread): u8;
}else if(fi ==1){
state.dev[0xb2] = (bytesread >> 8): u8;
state.dev[0xb3] = (bytesread): u8;
};
return;
};
fn write_file_from_addr(fi: u8,state: *uxn) void = {
const length = if(fi == 0) { yield short_from_bytes(state.dev[0xaa],state.dev[0xab]); }
else { yield short_from_bytes(state.dev[0xba],state.dev[0xbb]); };
const srcaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xae],state.dev[0xaf]); }
else { yield short_from_bytes(state.dev[0xbe],state.dev[0xbf]); };
const endaddr = srcaddr + length;
const appendf: bool = if(fi == 0) { yield state.dev[0xa7] != 0; }
else { yield state.dev[0xb7] != 0; };
fmt::printfln("Writing {} bytes from 0x{:x}", length, srcaddr)!;
const bytes: []u8 = state.ram[srcaddr..endaddr];
let byteswritten: u16 = 0;
if(!state.files[fi].exists){
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
const flags = if(appendf) {
state.files[fi].fappend = true;
yield (fs::flag::RDWR | fs::flag::APPEND);
} else {
yield fs::flag::RDWR;
};
state.files[fi].file = match (os::create(name,fs::mode::USER_RW,flags)){
case let f: io::file =>
state.files[fi].exists = true;
yield f;
case let err: fs::error =>
fmt::fatalf("Error creating file file: {}", fs::strerror(err));
};
};
if(appendf && (state.files[fi].fappend == false)){
//Rewrite File or adjust pointer
io::close(state.files[fi].file)!;
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
state.files[fi].file = match (os::open(name,fs::flag::RDWR | fs::flag::APPEND)) {
case let file: io::file =>
state.files[fi].exists = true;
state.files[fi].dir = false;
yield file;
case let err: fs::error =>
//Try opening as dir
yield match(os::diropen(name)){
case let f: *fs::fs =>
let dfs = os::diropen(name)!;
let df = os::dirfile(dfs);
const stat = os::fstat(df)!;
state.files[fi].dir = fs::isdir(stat.mode);
if(state.files[fi].dir) {
state.files[fi].exists = true;
state.files[fi].dir = true;
};
io::close(df)!;
yield io::empty;
case let err: fs::error =>
// fmt::printfln("Error opening {}: {}", name, fs::strerror(err))!;
if(fi == 0){
state.dev[0xa2] = 0;
state.dev[0xa3] = 0;
}else if(fi ==1){
state.dev[0xb2] = 0;
state.dev[0xb3] = 0;
};
state.files[fi].exists = false;
state.files[fi].dir = false;
yield io::empty;
};
};
state.files[fi].fappend = true;
};
if(!appendf && (state.files[fi].fappend == true)){
io::close(state.files[fi].file)!;
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
state.files[fi].file = match (os::open(name,fs::flag::RDWR)) {
case let file: io::file =>
state.files[fi].exists = true;
state.files[fi].dir = false;
yield file;
case let err: fs::error =>
//Try opening as dir
yield match(os::diropen(name)){
case let f: *fs::fs =>
let dfs = os::diropen(name)!;
let df = os::dirfile(dfs);
const stat = os::fstat(df)!;
state.files[fi].dir = fs::isdir(stat.mode);
if(state.files[fi].dir) {
state.files[fi].exists = true;
state.files[fi].dir = true;
};
io::close(df)!;
yield io::empty;
case let err: fs::error =>
// fmt::printfln("Error opening {}: {}", name, fs::strerror(err))!;
if(fi == 0){
state.dev[0xa2] = 0;
state.dev[0xa3] = 0;
}else if(fi ==1){
state.dev[0xb2] = 0;
state.dev[0xb3] = 0;
};
state.files[fi].exists = false;
state.files[fi].dir = false;
yield io::empty;
};
};
state.files[fi].fappend = false;
};
match (io::write(state.files[fi].file,bytes)){
case let s: size =>
byteswritten += s: u16;
fmt::printfln("Writing {} bytes to file",s)!;
case let err: io::error =>
fmt::fatalf("Error writing file: {}", io::strerror(err));
};
if(fi == 0){
state.dev[0xa2] = (byteswritten >> 8): u8;
state.dev[0xa3] = (byteswritten): u8;
}else if(fi ==1){
state.dev[0xb2] = (byteswritten >> 8): u8;
state.dev[0xb3] = (byteswritten): u8;
};
return;
};
fn set_file_name(fi: u8, state: *uxn) void = {
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
io::close(state.files[fi].file)!;
state.files[fi].fappend = false;
// fmt::printfln("Opening file: {}",name)!;
state.files[fi].file = match (os::open(name,fs::flag::RDWR)) {
case let file: io::file =>
state.files[fi].exists = true;
state.files[fi].dir = false;
yield file;
case let err: fs::error =>
//Try opening as dir
yield match(os::diropen(name)){
case let f: *fs::fs =>
let dfs = os::diropen(name)!;
let df = os::dirfile(dfs);
const stat = os::fstat(df)!;
state.files[fi].dir = fs::isdir(stat.mode);
if(state.files[fi].dir) {
state.files[fi].exists = true;
state.files[fi].dir = true;
};
io::close(df)!;
yield io::empty;
case let err: fs::error =>
// fmt::printfln("Error opening {}: {}", name, fs::strerror(err))!;
if(fi == 0){
state.dev[0xa2] = 0;
state.dev[0xa3] = 0;
}else if(fi ==1){
state.dev[0xb2] = 0;
state.dev[0xb3] = 0;
};
state.files[fi].exists = false;
state.files[fi].dir = false;
yield io::empty;
};
};
return;
};
fn delete_file(fi: u8, state: *uxn) void = {
let success: u8 = 0;
if(state.files[fi].exists){
const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); }
else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); };
const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0;
const name: str = match(strings::fromutf8(nameslice)){
case let s: str =>
yield s;
case =>
fmt::fatal("Bad String encoding in filename");
};
if(!state.files[fi].dir){
match(os::remove(name)) {
case void =>
success = 1;
yield;
case let err: fs::error =>
fmt::printf("Error deleting file: {}", fs::strerror(err))!;
};
}else{
match(os::rmdir(name)) {
case void =>
success = 1;
yield;
case let err: fs::error =>
fmt::printf("Error deleting dir: {}", fs::strerror(err))!;
};
};
};
if(fi == 0){
state.dev[0xa2] = 0;
state.dev[0xa3] = success;
}else if(fi ==1){
state.dev[0xb2] = 0;
state.dev[0xb3] = success;
};
};
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]);
const addr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]);
const value = state.ram[addr+7];
if(bank < numbanks) for(let i: u16 =0; i < length; i+=1){
state.ram[bank * banksize + addr + i] = value;
};
case 0x01 => //cpyl
const srcbank = short_from_bytes(state.ram[addr+3],state.ram[addr+4]);
const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]);
const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]);
const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]);
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]);
const srcaddr = short_from_bytes(state.ram[addr+5],state.ram[addr+6]);
const dstbank = short_from_bytes(state.ram[addr+7],state.ram[addr+8]);
const dstaddr = short_from_bytes(state.ram[addr+9],state.ram[addr+10]);
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 =>
fmt::fatalf("Unknown expansion op: 0x{:x}",op);
};
};
export fn set_mouse_motion(x: u16, y: u16, state: *uxn) void = {
state.dev[0x92] = (x >> 8): u8;
state.dev[0x93] = (x): u8;
state.dev[0x94] = (y >> 8): u8;
state.dev[0x95] = (y): u8;
};
export fn set_mouse_down(button: u8, state: *uxn) void = {
const cur = state.dev[0x96];
const new = cur | button;
state.dev[0x96] = new;
};
export fn set_mouse_up(button: u8, state: *uxn) void = {
const cur = state.dev[0x96];
const invbutton = 0xff ^ button;
const new = cur & invbutton;
state.dev[0x96] = new;
};
export fn set_controller_down(button: u8, state: *uxn) void = {
const cur = state.dev[0x82];
const new = cur | button;
state.dev[0x82] = new;
};
export fn set_controller_up(button: u8, state: *uxn) void = {
const cur = state.dev[0x82];
const invbutton = 0xff ^ button;
const new = cur & invbutton;
state.dev[0x82] = new;
};
export fn set_key_down(key: u8, state: *uxn) void = {
state.dev[0x83] = key;
};
export fn clear_key_down(state: *uxn) void = {
state.dev[0x83] = 0;
};
fn regenerate_palettes(state: *uxn) void = {
const r = short_from_bytes(state.dev[0x08],state.dev[0x09]);
const g = short_from_bytes(state.dev[0x0a],state.dev[0x0b]);
const b = short_from_bytes(state.dev[0x0c],state.dev[0x0d]);
state.palette[0] = ((r & 0xf000) >> 4) | ((g & 0xf000) >> 8) | ((b & 0xf000) >> 12);
state.palette[1] = ((r & 0x0f00) >> 0) | ((g & 0x0f00) >> 4) | ((b & 0x0f00) >> 8);
state.palette[2] = ((r & 0x00f0) << 4) | ((g & 0x00f0) >> 0) | ((b & 0x00f0) >> 4);
state.palette[3] = ((r & 0x000f) << 8) | ((g & 0x000f) << 4) | ((b & 0x000f) >> 0);
// fmt::printfln("Palette Color0: {:x} Color1: {:x} Color2: {:x} Color3: {:x}", state.palette[0], state.palette[1],state.palette[2],state.palette[3])!;
};
fn draw_pixel(value: u8, state: *uxn) void = {
//TODO handle fill
const color = value & 0b00000011;
const x = short_from_bytes(state.dev[0x28],state.dev[0x29]);
const y = short_from_bytes(state.dev[0x2a],state.dev[0x2b]);
const fill: bool = (value & 0b10000000) != 0;
const layer1: bool = (value & 0b01000000) != 0;
const flipy: bool = (value & 0b00100000) != 0;
const flipx: bool = (value & 0b00010000) != 0;
if(!fill){
// fmt::printfln("Pixel at x: {:x} y: {:x} color: {:x}", x, y, color)!;
const auto = state.dev[0x26];
let count = ((auto & 0xf0) >> 4): i8;
const auto_x = (auto & 0b00000001) != 0;
const auto_y = (auto & 0b00000010) != 0;
const auto_a = (auto & 0b00000100) != 0;
if(!layer1){
state.screen.0[x][y] = color;
} else {
state.screen.1[x][y] = color;
};
if(auto_x) {
const new_x = x + 1;
state.dev[0x28] = (new_x >> 8): u8;
state.dev[0x29] = (new_x): u8;
};
if(auto_y) {
const new_y = y + 1;
state.dev[0x2a] = (new_y >> 8): u8;
state.dev[0x2b] = (new_y): u8;
};
}else{
//TODO see if auto still happens with fill
// fmt::println("Doing Fill")!;
const dims = get_window_size(state);
const startx: u16 = if(flipx) {yield 0; }else{ yield x; };
const starty: u16 = if(flipy) {yield 0; }else{ yield y; };
const endx: u16 = if(flipx) {yield x; }else{ yield dims.width; };
const endy: u16 = if(flipy) {yield y; }else{ yield dims.height; };
// fmt::printfln("Fill startx: {:x} endx: {:x} starty: {:x} endy: {:x}", startx, endx, starty, endy)!;
for(let xl = startx; xl < endx; xl+=1){
for(let yl = starty; yl < endy; yl+=1){
// fmt::printfln("Filling at x: {:x} y: {:x} color: {:x}", xl, yl, color)!;
if(!layer1){
state.screen.0[xl][yl] = color;
} else {
state.screen.1[xl][yl] = color;
};
};
};
};
state.screen_update = true;
};
fn draw_sprite(value: u8, state: *uxn) void = {
//TODO handle fill
const colors = value & 0b00001111;
const auto = state.dev[0x26];
let count = ((auto & 0xf0) >> 4): u8;
const auto_x = (auto & 0b00000001) != 0;
const auto_y = (auto & 0b00000010) != 0;
const auto_a = (auto & 0b00000100) != 0;
const bpp2: bool = (value & 0b10000000) != 0;
const layer1: bool = (value & 0b01000000) != 0;
const flipy: bool = (value & 0b00100000) != 0;
const flipx: bool = (value & 0b00010000) != 0;
const x = short_from_bytes(state.dev[0x28],state.dev[0x29]);
const y = short_from_bytes(state.dev[0x2a],state.dev[0x2b]);
if(count == 0){
const addr = short_from_bytes(state.dev[0x2c],state.dev[0x2d]);
// const x = if(flipx) { yield x - i * 8; } else { yield x + i * 8; };
copy_sprite_to_screen(bpp2,colors,x,y,addr,flipx,flipy,layer1,state);
if(auto_a){
const new_addr = if(bpp2) { yield addr + 16; } else { yield addr + 8; };
state.dev[0x2c] = (new_addr >> 8): u8;
state.dev[0x2d] = (new_addr): u8;
};
if(auto_x) {
const new_x = if(flipx) { yield x - 8; } else { yield x + 8; };
state.dev[0x28] = (new_x >> 8): u8;
state.dev[0x29] = (new_x): u8;
};
if(auto_y) {
const new_y = if(flipy) { yield y - 8; } else { yield y + 8; };
state.dev[0x2a] = (new_y >> 8): u8;
state.dev[0x2b] = (new_y): u8;
};
}else {
// fmt::printfln("Multi-Sprite! Auto-x: {} Auto-y: {} Flip-x: {} Flip-y: {}",auto_x,auto_y,flipx,flipy)!;
for(let i: u16 = 0; i <= count; i+=1){
const addr = short_from_bytes(state.dev[0x2c],state.dev[0x2d]);
let x = x;
let y = y;
if(auto_y) {
x = if(flipx) { yield x - i * 8; } else { yield x + i * 8; };
}else if(auto_x) {
y = if(flipy) { yield y - i * 8; } else { yield y + i * 8; };
};
copy_sprite_to_screen(bpp2,colors,x,y,addr,flipx,flipy,layer1,state);
if(auto_a){
const new_addr = if(bpp2) { yield addr + 16; } else { yield addr + 8; };
state.dev[0x2c] = (new_addr >> 8): u8;
state.dev[0x2d] = (new_addr): u8;
};
};
if(auto_y) {
const new_y = if(flipy){ yield y - 8; } else { yield y + 8; };
state.dev[0x2a] = (new_y >> 8): u8;
state.dev[0x2b] = (new_y): u8;
};
if(auto_x) {
const new_x = x + 8;
state.dev[0x28] = (new_x >> 8): u8;
state.dev[0x29] = (new_x): u8;
};
};
state.screen_update = true;
};
fn copy_sprite_to_screen(bpp2: bool, colors: u8, x: u16, y: u16, addr: u16, flipx: bool, flipy: bool, layer1: bool, state: *uxn) void = {
let dims = get_window_size(state);
let xoff: u16 = if(flipx){ yield 7; } else { yield 0; };
let yoff: u16 = if(flipy){ yield 7; } else { yield 0; };
const drawZero = ((colors%5 != 0));
const pixels: []u8 = if(bpp2) { yield get_pixels_from_2bpp_sprite(addr,state); }
else {yield get_pixels_from_1bpp_sprite(addr,state);};
for(let p .. pixels){
let color = colormodes[p][colors];
if(drawZero || p > 0){
if((x < MAXWIDTH) && (y < MAXHEIGHT)){
if(layer1) {
state.screen.1[x+xoff][y+yoff] = color;
} else {
state.screen.0[x+xoff][y+yoff] = color;
};
};
};
if(flipx){
xoff -= 1;
}else {
xoff += 1;
};
if(xoff >= 8){
xoff = if(flipx){ yield 7; } else { yield 0; };
if(flipy){
yoff -=1;
}else{
yoff += 1;
};
};
};
};
fn get_pixels_from_1bpp_sprite(addr: u16, state: *uxn) []u8 = {
let pixels: [64]u8 = [0...];
for(let i = 0; i < 8; i+=1){
let data: u8 = state.ram[addr + i: u16];
pixels[i*8 +0] = (data & 0b10000000) >> 7;
pixels[i*8 +1] = (data & 0b01000000) >> 6;
pixels[i*8 +2] = (data & 0b00100000) >> 5;
pixels[i*8 +3] = (data & 0b00010000) >> 4;
pixels[i*8 +4] = (data & 0b00001000) >> 3;
pixels[i*8 +5] = (data & 0b00000100) >> 2;
pixels[i*8 +6] = (data & 0b00000010) >> 1;
pixels[i*8 +7] = (data & 0b00000001) >> 0;
};
return pixels;
};
fn get_pixels_from_2bpp_sprite(addr: u16, state: *uxn) []u8 = {
let pixels: [64]u8 = [0...];
for(let i = 0; i < 8; i+=1){
const data1: u8 = state.ram[addr + i: u16];
const data2: u8 = state.ram[addr + i:u16 + 8];
pixels[i*8 +0] = ((data2 & 0b10000000) >> 6) | ((data1 & 0b10000000) >> 7);
pixels[i*8 +1] = ((data2 & 0b01000000) >> 5) | ((data1 & 0b01000000) >> 6);
pixels[i*8 +2] = ((data2 & 0b00100000) >> 4) | ((data1 & 0b00100000) >> 5);
pixels[i*8 +3] = ((data2 & 0b00010000) >> 3) | ((data1 & 0b00010000) >> 4);
pixels[i*8 +4] = ((data2 & 0b00001000) >> 2) | ((data1 & 0b00001000) >> 3);
pixels[i*8 +5] = ((data2 & 0b00000100) >> 1) | ((data1 & 0b00000100) >> 2);
pixels[i*8 +6] = ((data2 & 0b00000010) >> 0) | ((data1 & 0b00000010) >> 1);
pixels[i*8 +7] = ((data2 & 0b00000001) << 1) | ((data1 & 0b00000001) >> 0);
};
return pixels;
};
export fn get_color(x: u16, y: u16, state: *uxn) u16 = {
// check layer 1
let color = state.screen.1[x][y] & 0b00000011;
if(color == 0){
color = state.screen.0[x][y] & 0b00000011;
};
return state.palette[color];
};
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;
};
};
fn short_from_bytes(high: u8, low: u8) u16 = {
return (high: u16) << 8 | (low: u16);
};
fn push_to_stack(ret: bool, val: (u8 | u16), state: *uxn) void = {
let 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.
fn get_stack_val(ret: bool, short: bool, off: u8, state: *uxn) (u8 | u16) = {
let 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);
};
};
fn increment_stack(ret: bool, short: bool, state: *uxn) void = {
let 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 = {
let 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) = {
let 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);
};
};
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)!;
let readval = state.ram[state.pc];
let inst: instruction = get_instruction(readval)?;
state.pc += 1; //TODO verify all pc changes
match(inst) {
// /* BRK */ case 0x00: return 1;
case BRK =>
// fmt::println("Break!")!;
state.running = false;
return done;
// /* JCI */ case 0x20: if(DEC(0)) { IMM state.pc += a; } else pc += 2; break;
case JCI =>
let 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;
};
// /* JMI */ case 0x40: IMM pc += a; break;
case JMI =>
state.pc = state.pc + 2 + short_from_bytes(state.ram[state.pc],state.ram[state.pc+1]); //TODO is this signed?
// /* JSI */ case 0x60: IMM PUx(pc, 1, 1) pc += a; break;
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?
// /* LIT */ case 0x80: INC(0) = ram[pc++]; break;
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;
};
// /* INC */ OPC(0x01,POx(a,d),PUx(a + 1,d,r))
case let inc_inst: INC =>
let 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);
// /* POP */ OPC(0x02,ptr[r] -= 1 + d;,{})
case let pop_inst: POP =>
if(!pop_inst.keep){
pop_from_stack(false,pop_inst.ret,pop_inst.short,state);
};
// /* NIP */ OPC(0x03,GOT(x) ptr[r] -= 1 + d;,PUT(x,r))
case let nip_inst: NIP =>
let top = pop_or_get(nip_inst,0,state);
let bot = pop_or_get(nip_inst,1,state);
push_to_stack(nip_inst.ret, top, state);
// /* SWP */ OPC(0x04,GOT(x) GOT(y),PUT(x,r) PUT(y,r))
case let swp_inst: SWP =>
let top = pop_or_get(swp_inst,0,state);
let bot = pop_or_get(swp_inst,1,state);
push_to_stack(swp_inst.ret, top, state);
push_to_stack(swp_inst.ret, bot, state);
// /* ROT */ OPC(0x05,GOT(x) GOT(y) GOT(z),PUT(y,r) PUT(x,r) PUT(z,r))
case let rot_inst: ROT =>
let top = pop_or_get(rot_inst,0,state);
let mid = pop_or_get(rot_inst,1,state);
let 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);
// /* DUP */ OPC(0x06,GOT(x),PUT(x,r) PUT(x,r))
case let dup_inst: DUP =>
let 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);
// /* OVR */ OPC(0x07,GOT(x) GOT(y),PUT(y,r) PUT(x,r) PUT(y,r))
case let ovr_inst: OVR =>
let top = pop_or_get(ovr_inst,0,state);
let 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);
// /* EQU */ OPC(0x08,POx(a,d) POx(b,d),PUx(b == a,0,r))
case let equ_inst: EQU =>
let top = pop_or_get(equ_inst,0,state);
let bot = pop_or_get(equ_inst,1,state);
//TODO, I think this works, but it feels unclean with the casting
// let val: u8 = if(top: u16 == bot: u16){
// yield 1;
// } else {
// yield 0;
// };
let val: u8 = if(!equ_inst.short){
yield if(bot: u8 == top: u8){
yield 1;
} else {
yield 0;
};
} else {
yield if(bot: u16 == top: u16){
yield 1;
} else {
yield 0;
};
};
push_to_stack(equ_inst.ret, val, state);
// /* NEQ */ OPC(0x09,POx(a,d) POx(b,d),PUx(b != a,0,r))
case let neq_inst: NEQ =>
let top = pop_or_get(neq_inst,0,state);
let bot = pop_or_get(neq_inst,1,state);
//TODO, I think this works, but it feels unclean with the casting
// let val: u8 = if(top: u16 != bot: u16){
// yield 1;
// } else {
// yield 0;
// };
let val: u8 = if(!neq_inst.short){
yield if(bot: u8 != top: u8){
yield 1;
} else {
yield 0;
};
} else {
yield if(bot: u16 != top: u16){
yield 1;
} else {
yield 0;
};
};
push_to_stack(neq_inst.ret, val, state);
// /* GTH */ OPC(0x0a,POx(a,d) POx(b,d),PUx(b > a,0,r))
case let gth_inst: GTH =>
let top = pop_or_get(gth_inst,0,state);
let bot = pop_or_get(gth_inst,1,state);
//TODO, I think this works, but it feels unclean with the casting
// let val: u8 = if(bot: u16 > top: u16){
// yield 1;
// } else {
// yield 0;
// };
let val: u8 = if(!gth_inst.short){
yield if(bot: u8 > top: u8){
yield 1;
} else {
yield 0;
};
} else {
yield if(bot: u16 > top: u16){
yield 1;
} else {
yield 0;
};
};
push_to_stack(gth_inst.ret, val, state);
// /* LTH */ OPC(0x0b,POx(a,d) POx(b,d),PUx(b < a,0,r))
case let lth_inst: LTH =>
let top = pop_or_get(lth_inst,0,state);
let bot = pop_or_get(lth_inst,1,state);
//TODO, I think this works, but it feels unclean with the casting
// let val: u8 = if(bot: u16 < top: u16){
// yield 1;
// } else {
// yield 0;
// };
let val: u8 = if(!lth_inst.short){
yield if(bot: u8 < top: u8){
yield 1;
} else {
yield 0;
};
} else {
yield if(bot: u16 < top: u16){
yield 1;
} else {
yield 0;
};
};
push_to_stack(lth_inst.ret, val, state);
// /* JMP */ OPC(0x0c,POx(a,d),MOV)
case let jmp_inst: JMP =>
let addr = pop_or_get(jmp_inst,0,state);
match(addr) {
case let b: u8 =>
state.pc = state.pc + b: u16; //TODO verify if pc changes are right
case let s: u16 =>
state.pc = s;
};
// /* JCN */ OPC(0x0d,POx(a,d) POx(b,0),if(b) MOV)
case let jcn_inst: JCN =>
let addr = pop_or_get(jcn_inst,0,state);
let cond: u8 = pop_or_get(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;
};
};
// /* JSR */ OPC(0x0e,POx(a,d),PUx(pc,1,!r) MOV)
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;
};
// /* STH */ OPC(0x0f,GOT(x),PUT(x,!r))
case let sth_inst: STH =>
let val = pop_or_get(sth_inst,0,state);
push_to_stack(!sth_inst.ret, val, state);
// /* LDZ */ OPC(0x10,POx(a,0),PEK(a, x, 0xff))
case let ldz_inst: LDZ =>
let addr: u16 = pop_from_stack(ldz_inst.keep, ldz_inst.ret, false, state): u16 & 0x00FF;
let val = if(ldz_inst.short){
yield short_from_bytes(state.ram[addr],state.ram[addr+1 & 0x00FF]);
} else {
yield state.ram[addr];
};
push_to_stack(ldz_inst.ret, val, state);
// /* STZ */ OPC(0x11,POx(a,0) GOT(y),POK(a, y, 0xff))
case let stz_inst: STZ =>
let addr: u16 = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 0, state): u16 & 0x00FF;
let val = if(stz_inst.short){
let low = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 1, state): u8;
let high = pop_or_get(normal_op{short = false, keep = stz_inst.keep, ret = stz_inst.ret}, 2, state): u8;
yield short_from_bytes(high,low);
}else{
yield pop_or_get(stz_inst, 1, state): u8;
};
match (val) {
case let b: u8 =>
state.ram[addr] = b;
case let s: u16 =>
let high = (s >> 8 ): u8;
let low = s: u8;
state.ram[addr] = high;
state.ram[addr+1 & 0x00FF] = low;
//TODO verify correct endianness
};
// /* LDR */ OPC(0x12,POx(a,0),PEK(pc + (Sint8)a, x, 0xffff))
case let ldr_inst: LDR =>
let reladdr = (pop_from_stack(ldr_inst.keep, ldr_inst.ret, false, state): u16): i8;
let addr: u16 = (state.pc: u32: i32 + reladdr): u16;
let val = if(ldr_inst.short){
yield short_from_bytes(state.ram[addr],state.ram[addr+1]);
} else {
yield state.ram[addr];
};
push_to_stack(ldr_inst.ret, val, state);
// /* STR */ OPC(0x13,POx(a,0) GOT(y),POK(pc + (Sint8)a, y, 0xffff))
case let str_inst: STR =>
let reladdr = (pop_from_stack(str_inst.keep, str_inst.ret, false, state): u16): i8;
let addr: u16 = (state.pc: u32: i32 + reladdr): u16;
let val = pop_from_stack(str_inst.keep, str_inst.ret, str_inst.short, state);
match (val) {
case let b: u8 =>
state.ram[addr] = b;
case let s: u16 =>
let high = (s >> 8 ): u8;
let low = s: u8;
state.ram[addr] = high;
state.ram[addr+1] = low;
//TODO verify correct endianness
};
// /* LDA */ OPC(0x14,POx(a,1),PEK(a, x, 0xffff))
case let lda_inst: LDA =>
let addr: u16 = pop_from_stack(lda_inst.keep, lda_inst.ret, true, state): u16;
let val = if(lda_inst.short){
yield short_from_bytes(state.ram[addr],state.ram[addr+1]);
} else {
yield state.ram[addr];
};
push_to_stack(lda_inst.ret, val, state);
// /* STA */ OPC(0x15,POx(a,1) GOT(y),POK(a, y, 0xffff))
case let sta_inst: STA =>
let addr: u16 = pop_from_stack(sta_inst.keep, sta_inst.ret, true, state): u16;
let val = pop_from_stack(sta_inst.keep, sta_inst.ret, sta_inst.short, state);
match (val) {
case let b: u8 =>
state.ram[addr] = b;
case let s: u16 =>
let high = (s >> 8 ): u8;
let low = s: u8;
state.ram[addr] = high;
state.ram[addr+1] = low;
//TODO verify correct endianness
};
// /* DEI */ OPC(0x16,POx(a,0),DEI(a, x))
case let dei_inst: DEI =>
// fmt::println("DEI Instruction")!;
let port: u8 = pop_from_stack(dei_inst.keep, dei_inst.ret, false, state): u8;
let 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);
// /* DEO */ OPC(0x17,POx(a,0) GOT(y),DEO(a, y))
case let deo_inst: DEO =>
let port: u8 = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 0, state): u8;
if(deo_inst.short){
let low = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 1, state): u8;
let high = pop_or_get(normal_op{short = false, keep = deo_inst.keep, ret = deo_inst.ret}, 2, state): u8;
emu_deo(port,high,state);
emu_deo(port+1,low,state);
}else{
let val = pop_or_get(deo_inst, 1, state): u8;
emu_deo(port,val,state);
};
// /* ADD */ OPC(0x18,POx(a,d) POx(b,d),PUx(b + a, d,r))
case let add_inst: ADD =>
let top = pop_or_get(add_inst,0,state);
let bot = pop_or_get(add_inst,1,state);
let res = if(!add_inst.short){
yield (top: u8 + bot: u8);
} else {
// fmt::printfln("ADD2 0x{:x} + 0x{:x} = 0x{:x}",bot,top,top: u16 + bot: u16)!;
yield (top: u16 + bot: u16);
};
push_to_stack(add_inst.ret, res, state);
// /* SUB */ OPC(0x19,POx(a,d) POx(b,d),PUx(b - a, d,r))
case let sub_inst: SUB =>
let top = pop_or_get(sub_inst,0,state);
let bot = pop_or_get(sub_inst,1,state);
let res = if(!sub_inst.short){
yield (bot: u8 - top: u8);
} else {
yield (bot: u16 - top: u16);
};
push_to_stack(sub_inst.ret, res, state);
// /* MUL */ OPC(0x1a,POx(a,d) POx(b,d),PUx(b * a, d,r))
case let mul_inst: MUL =>
let top = pop_or_get(mul_inst,0,state);
let bot = pop_or_get(mul_inst,1,state);
let res = if(!mul_inst.short){
yield (bot: u8 * top: u8);
} else {
yield (bot: u16 * top: u16);
};
push_to_stack(mul_inst.ret, res, state);
// /* DIV */ OPC(0x1b,POx(a,d) POx(b,d),PUx(a ? b / a : 0, d,r))
case let div_inst: DIV =>
let top = pop_or_get(div_inst,0,state);
let bot = pop_or_get(div_inst,1,state);
let 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;
};//TODO handle rounding and edge cases with division
push_to_stack(div_inst.ret, res, state);
// /* AND */ OPC(0x1c,POx(a,d) POx(b,d),PUx(b & a, d,r))
case let and_inst: AND =>
let top = pop_or_get(and_inst,0,state);
let bot = pop_or_get(and_inst,1,state);
let res = if(!and_inst.short){
yield (bot: u8 & top: u8);
} else {
yield (bot: u16 & top: u16);
};
push_to_stack(and_inst.ret, res, state);
// /* ORA */ OPC(0x1d,POx(a,d) POx(b,d),PUx(b | a, d,r))
case let ora_inst: ORA =>
let top = pop_or_get(ora_inst,0,state);
let bot = pop_or_get(ora_inst,1,state);
let res = if(!ora_inst.short){
yield (bot: u8 | top: u8);
} else {
yield (bot: u16 | top: u16);
};
push_to_stack(ora_inst.ret, res, state);
// /* EOR */ OPC(0x1e,POx(a,d) POx(b,d),PUx(b ^ a, d,r))
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);
// /* SFT */ OPC(0x1f,POx(a,0) POx(b,d),PUx(b >> (a & 0xf) << (a >> 4), d,r))
case let sft_inst: SFT =>
let shifts: u8 = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 0, state): u8;
let rightshifts = shifts & 0b00001111;
let leftshifts = (shifts & 0b11110000) >> 4;
if(sft_inst.short){ //TODO, this seems like it can be cleaned up a bit
let low = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 1, state): u8;
let high = pop_or_get(normal_op{short = false, keep = sft_inst.keep, ret = sft_inst.ret}, 2, state): u8;
let val = short_from_bytes(high,low);
let res = (val >> rightshifts ) << leftshifts;
push_to_stack(sft_inst.ret, res, state);
}else{
let val = pop_or_get(sft_inst, 1, state): u8;
let res = (val >> rightshifts ) << leftshifts;
push_to_stack(sft_inst.ret, res, state);
};
case =>
return readval: unhandled;
};
return done;
};
export fn console_input(c: u8, ctype: u8, state: *uxn) (done | error) = {
state.dev[0x12] = c;
state.dev[0x17] = ctype;
if(state.console_vector != 0){
// fmt::println("Evaluating Console Vector")!;
uxn_eval(state.console_vector, state)?;
// fmt::println("Done Evaluating Console Vector")!;
}; //TODO implement eval
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 ) = {
let 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));
};
let state: *uxn = initialize_uxn_mem();
//Copy rom into ram
let 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)!;
};
export fn uxnrun() void = {
if(len(os::args) < 2){
fmt::printf("usage: %s file.rom [args..]\n")!;
return;
};
let path = os::args[1];
//Open rom file
let 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));
};
let state: *uxn = initialize_uxn_mem();
//Copy rom into ram
let bytesread = match (io::read(romfile,state.ram[0x100..0xff00])){
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?
// fread(&ram[0x100], 0xff00, 1, f), fclose(f);
let hasargs = len(os::args) > 2;
if(hasargs){
state.dev[0x17] = 1;
};
// dev[0x17] = argc > 2;
let eval = match (uxn_eval(0x100, state)) {
case done =>
yield done;
case let val: u8 =>
fmt::fatalf("Unhandled Opcode: {:x}", val);
};
//TODO see if the above needs to not run if console vector not set
if(state.console_vector != 0){
let args = os::args[2..];
let i: u8 = 0;
let argcount = len(args): u8;
for (let arg .. args){
// fmt::println("Console input args")!;
for(let char: u8 .. strings::toutf8(arg)){
// fmt::printfln("Console input arg char: {:x}", char)!;
match (console_input(char,2,state)) {
case done =>
yield done;
case let val: u8 =>
fmt::fatalf("Unhandled Opcode: {:x}", val);
};
};
let ctype: u8 = if(i == (argcount - 1)){
yield 4;
} else {
yield 3;
};
match (console_input('\n',ctype,state)) {
case done =>
yield done;
case let val: u8 =>
fmt::fatalf("Unhandled Opcode: {:x}", val);
};
i+=1; //TODO using i here seems inelegant
};
for( state.dev[0x0f] == 0; i+=1 ){
let buf: [1]u8 = [0];
let count = io::read(os::stdin, buf)!;
// fmt::println("Read input")!;
if(count != 0) {
match (console_input(buf[0],1,state)) {
case done =>
yield done;
case let val: u8 =>
fmt::fatalf("Unhandled Opcode: {:x}", val);
};
// fmt::println("Retuned from console_input")!;
};
};
// match (console_input('\n',4,state)) { //TODO should this run?
// case done =>
// yield done;
// case let val: u8 =>
// fmt::fatalf("Unhandled Opcode: {:x}", val);
// };
};
fmt::println("Done with uxn")!;
return;
};