diff --git a/compiler/res/prog8lib/virtual/diskio.p8 b/compiler/res/prog8lib/virtual/diskio.p8 index 4583a7fcc..5d4707ee9 100644 --- a/compiler/res/prog8lib/virtual/diskio.p8 +++ b/compiler/res/prog8lib/virtual/diskio.p8 @@ -1 +1,172 @@ -; there is no diskio yet for the VM target. +; file I/O routines. (partially implemented) + +%import textio +%import syslib + +diskio { + %option no_symbol_prefixing, ignore_unused + + sub directory() -> bool { + ; -- Prints the directory contents to the screen. Returns success. + txt.print("@TODO: directory\n") + return false + } + + sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte { + ; -- fill the provided buffer with the names of the files on the disk (until buffer is full). + ; Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against. + ; After the last filename one additional 0 byte is placed to indicate the end of the list. + ; Returns number of files (it skips 'dir' entries i.e. subdirectories). + ; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer. + txt.print("@TODO: list_flienames\n") + sys.clear_carry() + return 0 + } + + ; ----- iterative file lister functions (uses the read io channel) ----- + + sub lf_start_list(uword pattern_ptr) -> bool { + ; -- start an iterative file listing with optional pattern matching. + ; note: only a single iteration loop can be active at a time! + txt.print("@TODO: lf_start_list\n") + return false + } + + sub lf_next_entry() -> bool { + ; -- retrieve the next entry from an iterative file listing session. + ; results will be found in list_blocks, list_filename, and list_filetype. + ; if it returns false though, there are no more entries (or an error occurred). + txt.print("@TODO: lf_next_entry\n") + return false + } + + sub lf_end_list() { + txt.print("@TODO: lf_end_list\n") + } + + + ; ----- iterative file loader functions (uses the input io channel) ----- + + sub f_open(uword filenameptr) -> bool { + ; -- open a file for iterative reading with f_read + ; note: only a single iteration loop can be active at a time! + ; Returns true if the file is successfully opened and readable. + ; No need to check status(), unlike f_open_w() ! + ; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this, + ; if you're going to read from it yourself instead of using f_read()! + + txt.print("@TODO: f_open\n") + return false + } + + sub f_read(uword bufferpointer, uword num_bytes) -> uword { + ; -- read from the currently open file, up to the given number of bytes. + ; returns the actual number of bytes read. (checks for End-of-file and error conditions) + txt.print("@TODO: f_read\n") + return 0 + } + + sub f_read_all(uword bufferpointer) -> uword { + ; -- read the full contents of the file, returns number of bytes read. + txt.print("@TODO: f_read_all\n") + return 0 + } + + sub f_readline(uword bufptr) -> ubyte { + ; Routine to read text lines from a text file. Lines must be less than 255 characters. + ; Reads characters from the input file UNTIL a newline or return character (or EOF). + ; The line read will be 0-terminated in the buffer (and not contain the end of line character). + ; The length of the line is returned in Y. Note that an empty line is okay and is length 0! + ; I/O error status should be checked by the caller itself via READST() routine. + txt.print("@TODO: f_readline\n") + return 0 + } + + sub f_close() { + ; -- end an iterative file loading session (close channels). + txt.print("@TODO: f_close\n") + } + + + ; ----- iterative file writing functions (uses write io channel) ----- + + sub f_open_w(uword filenameptr) -> bool { + ; -- open a file for iterative writing with f_write + ; WARNING: returns true if the open command was received by the device, + ; but this can still mean the file wasn't successfully opened for writing! + ; (for example, if it already exists). This is different than f_open()! + ; To be 100% sure if this call was successful, you have to use status() + ; and check the drive's status message! + txt.print("@TODO: f_open_w\n") + return false + } + + sub f_write(uword bufferpointer, uword num_bytes) -> bool { + ; -- write the given number of bytes to the currently open file + txt.print("@TODO: f_write\n") + return false + } + + sub f_close_w() { + ; -- end an iterative file writing session (close channels). + txt.print("@TODO: f_close_w\n") + } + + + ; ---- other functions ---- + + sub status() -> str { + ; -- retrieve the disk drive's current status message + return "ok" + } + + sub save(uword filenameptr, uword start_address, uword savesize) -> bool { + %ir {{ + loadm.w r65533,diskio.save.filenameptr + loadm.w r65534,diskio.save.start_address + loadm.w r65535,diskio.save.savesize + syscall 58 (r65533.w, r65534.w, r65535.w): r0.b + returnr.b r0 + }} + } + + ; Use kernal LOAD routine to load the given program file in memory. + ; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1 + ; If you don't give an address_override, the location in memory is taken from the 2-byte file header. + ; If you specify a custom address_override, the first 2 bytes in the file are ignored + ; and the rest is loaded at the given location in memory. + ; Returns the end load address+1 if successful or 0 if a load error occurred. + sub load(uword filenameptr, uword address_override) -> uword { + %ir {{ + loadm.w r65534,diskio.load.filenameptr + loadm.w r65535,diskio.load.address_override + syscall 56 (r65534.w, r65535.w): r0.w + returnr.w r0 + }} + } + + ; Identical to load(), but DOES INCLUDE the first 2 bytes in the file. + ; No program header is assumed in the file. Everything is loaded. + ; See comments on load() for more details. + sub load_raw(uword filenameptr, uword start_address) -> uword { + %ir {{ + loadm.w r65534,diskio.load_raw.filenameptr + loadm.w r65535,diskio.load_raw.address_override + syscall 57 (r65534.w, r65535.w): r0.w + returnr.w r0 + }} + } + + sub delete(uword filenameptr) { + ; -- delete a file on the drive + %ir {{ + loadm.w r65535,diskio.delete.filenameptr + syscall 59 (r65535.w) + }} + } + + sub rename(uword oldfileptr, uword newfileptr) { + ; -- rename a file on the drive + txt.print("@TODO: rename\n") + } +} diff --git a/compiler/res/prog8lib/virtual/textio.p8 b/compiler/res/prog8lib/virtual/textio.p8 index fdb59ec00..61560bb13 100644 --- a/compiler/res/prog8lib/virtual/textio.p8 +++ b/compiler/res/prog8lib/virtual/textio.p8 @@ -29,6 +29,10 @@ sub spc() { chrout(' ') } +sub home() { + print("\x1b[H") +} + sub lowercase() { ; not supported } @@ -134,6 +138,14 @@ sub input_chars (uword buffer) -> ubyte { }} } +sub column(ubyte col) { + txt.chrout(27) + txt.chrout('[') + txt.print_ub(col+1) + txt.chrout(';') + txt.chrout('G') +} + sub plot (ubyte col, ubyte row) { ; use ANSI escape sequence to position the cursor txt.chrout(27) diff --git a/examples/textelite.p8 b/examples/textelite.p8 index a2c452ac7..244fb5275 100644 --- a/examples/textelite.p8 +++ b/examples/textelite.p8 @@ -20,7 +20,8 @@ main { sub start() { txt.lowercase() - txt.print("\u000c\n --- TextElite v1.2 ---\n") + txt.clear_screen() + txt.print("\n --- TextElite v1.2 ---\n") planet.set_seed(0, 0) galaxy.travel_to(1, numforLave) diff --git a/examples/vm/textelite.p8 b/examples/vm/textelite.p8 index b27698ad5..65aa039cc 100644 --- a/examples/vm/textelite.p8 +++ b/examples/vm/textelite.p8 @@ -1,10 +1,14 @@ %import textio +%import diskio %import conv %import string ; Prog8 adaptation of the Text-Elite galaxy system trading simulation engine. ; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm +; This version is almost identical to the "original" textelite.p8 example, +; except for a few tiny changes specific to running this in the prog8 VM. + main { const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one @@ -14,8 +18,8 @@ main { sub start() { txt.lowercase() + txt.clear_screen() txt.print("\n--- TextElite v1.2 ---\n") - txt.print("VirtualMachine edition: no disk saving, bad market table layout!\n") planet.set_seed(0, 0) galaxy.travel_to(1, numforLave) @@ -34,9 +38,9 @@ main { when input[0] { '?' -> { txt.print("\nCommands are:\n"+ - "buy jump info map quit\n"+ - "sell teleport market cash\n"+ - "fuel galhyp local hold\n") + "buy jump info map >=save\n"+ + "sell teleport market cash <=load\n"+ + "fuel galhyp local hold quit\n") } 'q' -> break 'b' -> trader.do_buy() @@ -55,6 +59,8 @@ main { 'l' -> trader.do_local() 'c' -> trader.do_cash() 'h' -> trader.do_hold() + '<' -> trader.do_load() + '>' -> trader.do_save() } } } @@ -62,9 +68,59 @@ main { } trader { + str Savegame = "_commander.save" str input = "??????????" ubyte num_chars + ubyte[23] savedata + ; format: + ; 0 ubyte galaxy + ; 1 ubyte planet + ; 2-18 ubyte cargo0..cargo16 + ; 19 uword cash + ; 21 ubyte max_cargo + ; 22 ubyte fuel + + sub do_load() { + txt.print("\nLoading universe...") + if diskio.load(Savegame, &savedata) { + txt.print("ok\n") + } else { + txt.print("\ni/o error: ") + txt.print(diskio.status()) + txt.nl() + return + } + + ship.cash = mkword(savedata[20], savedata[19]) + ship.Max_cargo = savedata[21] + ship.fuel = savedata[22] + sys.memcopy(&savedata + 2, ship.cargohold, len(ship.cargohold)) + galaxy.travel_to(savedata[0], savedata[1]) + + planet.display(false, 0) + } + + sub do_save() { + savedata[0] = galaxy.number + savedata[1] = planet.number + savedata[19] = lsb(ship.cash) + savedata[20] = msb(ship.cash) + savedata[21] = ship.Max_cargo + savedata[22] = ship.fuel + sys.memcopy(ship.cargohold, &savedata + 2, len(ship.cargohold)) + + txt.print("\nSaving universe...") + diskio.delete(Savegame) + if diskio.save(Savegame, &savedata, sizeof(savedata)) { + txt.print("ok\n") + } else { + txt.print("\ni/o error: ") + txt.print(diskio.status()) + txt.nl() + } + } + sub do_jump() { txt.print("\nJump to what system? ") jump_to_system() @@ -301,7 +357,7 @@ market { util.print_right(13, names[ci]) txt.print(" ") util.print_10s(current_price[ci]) - txt.print(" ") + txt.column(24) txt.print_ub(current_quantity[ci]) txt.chrout(' ') when units[ci] { @@ -309,7 +365,7 @@ market { 1 -> txt.print("kg") 2 -> txt.chrout('g') } - txt.print(" ") + txt.column(32) txt.print_ub(ship.cargohold[ci]) txt.nl() } diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index dccf5cebb..0cbb2e9d9 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -2,6 +2,7 @@ package prog8.vm import prog8.intermediate.FunctionCallArgs import prog8.intermediate.IRDataType +import java.io.File import kotlin.math.* /* @@ -63,6 +64,10 @@ SYSCALLS: 53 = ARRAYCOPY_SPLITW_TO_NORMAL 54 = ARRAYCOPY_NORMAL_TO_SPLITW 55 = memcopy_small +56 = load +57 = load_raw +58 = save +59 = delete */ enum class Syscall { @@ -121,7 +126,11 @@ enum class Syscall { STRINGCOPY, ARRAYCOPY_SPLITW_TO_NORMAL, ARRAYCOPY_NORMAL_TO_SPLITW, - MEMCOPY_SMALL + MEMCOPY_SMALL, + LOAD, + LOAD_RAW, + SAVE, + DELETE ; companion object { @@ -619,6 +628,45 @@ object SysCalls { vm.memory.setUB(targetMsb+offset, vm.memory.getUB(from+offset*2+1)) } } + + Syscall.LOAD -> { + val (filenameA, addrA) = getArgValues(callspec.arguments, vm) + val filename = vm.memory.getString((filenameA as UShort).toInt()) + val data = File(filename).readBytes() + val addr = if(addrA==0) data[0] + data[1]*256 else (addrA as UShort).toInt() + for(i in 0.. { + val (filenameA, addrA) = getArgValues(callspec.arguments, vm) + val filename = vm.memory.getString((filenameA as UShort).toInt()) + val addr = (addrA as UShort).toInt() + val data = File(filename).readBytes() + for(i in 0.. { + val (filenamePtr, startA, sizeA) = getArgValues(callspec.arguments, vm) + val size = (sizeA as UShort).toInt() + val data = ByteArray(size+2) + val startPtr = (startA as UShort).toInt() + data[0] = (startPtr and 255).toByte() + data[1] = (startPtr shr 8).toByte() + for(i in 0.. { + val filenamePtr = getArgValues(callspec.arguments, vm).single() as UShort + val filename = vm.memory.getString(filenamePtr.toInt()) + File(filename).delete() + } } } }