VM: implemented a few core routines in diskio (load/save)

textelite can now load and save your progress like it already could in the real version
This commit is contained in:
Irmen de Jong 2024-03-06 23:21:01 +01:00
parent 1137e57393
commit bdfb01f6a0
5 changed files with 297 additions and 9 deletions

View File

@ -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")
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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()
}

View File

@ -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..<data.size-2) {
vm.memory.setUB(addr+i, data[i+2].toUByte())
}
vm.registers.setUW(0, (addr+data.size-2).toUShort())
}
Syscall.LOAD_RAW -> {
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..<data.size) {
vm.memory.setUB(addr+i, data[i].toUByte())
}
vm.registers.setUW(0, (addr+data.size).toUShort())
}
Syscall.SAVE -> {
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..<size) {
data[i+2] = vm.memory.getUB(startPtr+i).toByte()
}
val filename = vm.memory.getString((filenamePtr as UShort).toInt())
File(filename).writeBytes(data)
}
Syscall.DELETE -> {
val filenamePtr = getArgValues(callspec.arguments, vm).single() as UShort
val filename = vm.memory.getString(filenamePtr.toInt())
File(filename).delete()
}
}
}
}