diff --git a/compiler/res/prog8lib/virtual/diskio.p8 b/compiler/res/prog8lib/virtual/diskio.p8 index 3a1c5ec81..baf2be7b8 100644 --- a/compiler/res/prog8lib/virtual/diskio.p8 +++ b/compiler/res/prog8lib/virtual/diskio.p8 @@ -1,4 +1,8 @@ -; file I/O routines. (partially implemented) +; File I/O routines for the VM target +; +; NOTE: not all is implemented. +; NOTE: some calls are slightly different from the "official" diskio library because for example, +; here we cannot deal with multiple return values. %import textio %import syslib @@ -59,37 +63,86 @@ diskio { ; 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 + %ir {{ + loadm.w r65535,diskio.f_open.filenameptr + syscall 52 (r65535.w): r0.b + returnr.b r0 + }} } 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 + uword actual + repeat num_bytes { + %ir {{ + syscall 54 (): r0.w + storem.w r0,$ff02 + }} + if cx16.r0H==0 + return actual + @(bufferpointer) = cx16.r0L + bufferpointer++ + actual++ + } + return actual } sub f_read_all(uword bufferpointer) -> uword { ; -- read the full contents of the file, returns number of bytes read. ; It is assumed the file size is less than 64 K. - txt.print("@TODO: f_read_all\n") - return 0 + uword actual + repeat { + %ir {{ + syscall 54 (): r0.w + storem.w r0,$ff02 + }} + if cx16.r0H==0 + return actual + @(bufferpointer) = cx16.r0L + bufferpointer++ + actual++ + } } 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! - ; This routine is not able here to return the status as well in a secondary return value, so you have to do that yourself. - txt.print("@TODO: f_readline\n") - return 0 + ; The length of the line is returned. Note that an empty line is okay and is length 0! + ; The success status is returned in the Carry flag instead: C set = success, C clear = failure/endoffile + ubyte size + repeat { + %ir {{ + syscall 54 (): r0.w + storem.w r0,$ff02 + }} + + if cx16.r0H==0 { + sys.clear_carry() + return size + } else { + if cx16.r0L == '\n' or cx16.r0L=='\r' { + @(bufptr) = 0 + sys.set_carry() + return size + } + @(bufptr) = cx16.r0L + bufptr++ + size++ + if_z { + @(bufptr) = 0 + return 255 + } + } + } } sub f_close() { ; -- end an iterative file loading session (close channels). - txt.print("@TODO: f_close\n") + %ir {{ + syscall 56 () + }} } @@ -102,20 +155,35 @@ diskio { ; (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 + %ir {{ + loadm.w r65535,diskio.f_open_w.filenameptr + syscall 53 (r65535.w): r0.b + returnr.b r0 + }} } sub f_write(uword bufferpointer, uword num_bytes) -> bool { ; -- write the given number of bytes to the currently open file ; you can call this multiple times to append more data - txt.print("@TODO: f_write\n") - return false + repeat num_bytes { + %ir {{ + loadm.w r0,diskio.f_write.bufferpointer + loadi.b r1,r0 + syscall 55 (r1.b): r0.b + storem.b r0,$ff02 + }} + if cx16.r0L==0 + return false + bufferpointer++ + } + return true } sub f_close_w() { ; -- end an iterative file writing session (close channels). - txt.print("@TODO: f_close_w\n") + %ir {{ + syscall 57 () + }} } @@ -123,23 +191,34 @@ diskio { sub chdir(str path) { ; -- change current directory. - txt.print("@TODO: chdir\n") + %ir {{ + loadm.w r65535,diskio.chdir.path + syscall 50 (r65535.w) + }} } sub mkdir(str name) { ; -- make a new subdirectory. - txt.print("@TODO: mkdir\n") + %ir {{ + loadm.w r65535,diskio.mkdir.name + syscall 49 (r65535.w) + }} } sub rmdir(str name) { ; -- remove a subdirectory. - txt.print("@TODO: rmdir\n") + %ir {{ + loadm.w r65535,diskio.rmdir.name + syscall 51 (r65535.w) + }} } sub curdir() -> uword { ; return current directory name or 0 if error - txt.print("@TODO: curdir\n") - return 0 + %ir {{ + syscall 48 (): r0.w + returnr.w r0 + }} } sub status() -> str { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 886746770..3da514fe3 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -59,7 +59,7 @@ Libraries: - fix the problems in atari target, and flesh out its libraries. - c128 target: make syslib more complete (missing kernal routines)? - pet32 target: make syslib more complete (missing kernal routines)? -- VM: implement more diskio support +- VM: implement the last diskio support (file listings) Optimizations: diff --git a/examples/test.p8 b/examples/test.p8 index 2bd76e070..76690711a 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,11 +1,51 @@ %import textio +%import diskio %option no_sysinit %zeropage basicsafe main { sub start() { - alias print = txt.print - alias zz=print - zz("chained") + txt.lowercase() + + uword buffer = memory("buffer", 16000, 0) + uword line = 0 + + if diskio.f_open("gradlew.bat") { + uword size = diskio.f_read(buffer, 1000) + txt.print_uw(size) + txt.nl() + diskio.f_close() + } + + if diskio.f_open("gradlew.bat") { + size = diskio.f_read_all(buffer) + txt.print_uw(size) + txt.nl() + + diskio.f_close() + } + + if diskio.f_open("gradlew.bat") { + ubyte linesize, status + repeat { + linesize = diskio.f_readline(buffer) + if_cs { + line++ + } else break + } + diskio.f_close() + + txt.print_uw(line) + txt.nl() + } + + if diskio.f_open_w("result.txt") { + void diskio.f_write("line 1\n", 7) + void diskio.f_write("line 2\n", 7) + void diskio.f_write("line 3\n", 7) + diskio.f_close_w() + } } + + } diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index 03711d7d2..4ae9f973c 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -3,12 +3,11 @@ package prog8.vm import prog8.intermediate.FunctionCallArgs import prog8.intermediate.IRDataType import java.io.File -import kotlin.io.path.Path -import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.* import kotlin.math.* /* -SYSCALLS: +SYSCALLS: DO NOT RENUMBER THESE OR YOU WILL BREAK EXISTING CODE 0 = reset ; resets system 1 = exit ; stops program and returns statuscode from r0.w @@ -58,6 +57,16 @@ SYSCALLS: 45 = directory 46 = getconsolesize 47 = memcmp +48 = CURDIR +49 = MKDIR +50 = CHDIR +51 = RMDIR +52 = OPEN_FILE +53 = OPEN_FILE_WRITE +54 = READ_FILE_BYTE +55 = WRITE_FILE_BYTE +56 = CLOSE_FILE +57 = CLOSE_FILE_WRITE */ enum class Syscall { @@ -108,7 +117,17 @@ enum class Syscall { RENAME, DIRECTORY, GETGONSOLESIZE, - MEMCMP + MEMCMP, + CURDIR, + MKDIR, + CHDIR, + RMDIR, + OPEN_FILE, + OPEN_FILE_WRITE, + READ_FILE_BYTE, + WRITE_FILE_BYTE, + CLOSE_FILE, + CLOSE_FILE_WRITE, ; companion object { @@ -517,7 +536,7 @@ object SysCalls { } Syscall.DIRECTORY -> { // no arguments - val directory = Path(".") + val directory = Path("") println("Directory listing for ${directory.toAbsolutePath().normalize()}") directory.listDirectoryEntries().sorted().forEach { println("${it.toFile().length()}\t${it.normalize()}") @@ -552,6 +571,51 @@ object SysCalls { } return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 } + + Syscall.CURDIR -> { + val curdir = Path("").toAbsolutePath().toString() + vm.memory.setString(0xfe00, curdir, true) + return returnValue(callspec.returns.single(), 0xfe00, vm) + } + Syscall.MKDIR -> { + val namePtr = getArgValues(callspec.arguments, vm).single() as UShort + val name = vm.memory.getString(namePtr.toInt()) + Path(name).createDirectory() + } + Syscall.RMDIR -> { + val namePtr = getArgValues(callspec.arguments, vm).single() as UShort + val name = vm.memory.getString(namePtr.toInt()) + Path(name).deleteIfExists() + } + Syscall.CHDIR -> throw NotImplementedError("chdir is not possible in java/kotlin") + Syscall.OPEN_FILE -> { + val namePtr = getArgValues(callspec.arguments, vm).single() as UShort + val name = vm.memory.getString(namePtr.toInt()) + val success = vm.open_file_read(name) + return returnValue(callspec.returns.single(), success, vm) + } + Syscall.OPEN_FILE_WRITE -> { + val namePtr = getArgValues(callspec.arguments, vm).single() as UShort + val name = vm.memory.getString(namePtr.toInt()) + val success = vm.open_file_write(name) + return returnValue(callspec.returns.single(), success, vm) + } + Syscall.READ_FILE_BYTE -> { + val (success, byte) = vm.read_file_byte() + if(success) + return returnValue(callspec.returns.single(), 0x0100 or byte.toInt(), vm) + else + return returnValue(callspec.returns.single(), 0x0000, vm) + } + Syscall.WRITE_FILE_BYTE -> { + val byte = getArgValues(callspec.arguments, vm).single() as UByte + if(vm.write_file_byte(byte)) + return returnValue(callspec.returns.single(), 1, vm) + else + return returnValue(callspec.returns.single(), 0, vm) + } + Syscall.CLOSE_FILE -> vm.close_file_read() + Syscall.CLOSE_FILE_WRITE -> vm.close_file_write() } } } diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index ae866f8ca..1801ea1c0 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -6,7 +6,12 @@ import prog8.code.target.virtual.VirtualMachineDefinition import prog8.intermediate.* import java.awt.Color import java.awt.Toolkit -import kotlin.collections.ArrayDeque +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import kotlin.io.path.Path +import kotlin.io.path.inputStream +import kotlin.io.path.outputStream import kotlin.math.* import kotlin.random.Random @@ -34,6 +39,9 @@ class BreakpointException(val pcChunk: IRCodeChunk, val pcIndex: Int): Exception @Suppress("FunctionName") class VirtualMachine(irProgram: IRProgram) { class CallSiteContext(val returnChunk: IRCodeChunk, val returnIndex: Int, val fcallSpec: FunctionCallArgs) + + private var fileOutputStream: OutputStream? = null + private var fileInputStream: InputStream? = null val memory = Memory() val machinedef = VirtualMachineDefinition() val program: List @@ -2489,6 +2497,62 @@ class VirtualMachine(irProgram: IRProgram) { fun randomSeedFloat(seed: Double) { randomGeneratorFloats = Random(seed.toBits()) } + + fun open_file_read(name: String): Int { + try { + fileInputStream = Path(name).inputStream() + } catch (_: IOException) { + return 0 + } + return 1 + } + + fun open_file_write(name: String): Int { + try { + fileOutputStream = Path(name).outputStream() + } catch (_: IOException) { + return 0 + } + return 1 + } + + fun close_file_read() { + fileInputStream?.close() + fileInputStream=null + } + + fun close_file_write() { + fileOutputStream?.flush() + fileOutputStream?.close() + fileOutputStream=null + } + + fun read_file_byte(): Pair { + return if(fileInputStream==null) + false to 0u + else { + try { + val byte = fileInputStream!!.read() + if(byte>=0) + true to byte.toUByte() + else + false to 0u + } catch(_: IOException) { + false to 0u + } + } + } + + fun write_file_byte(byte: UByte): Boolean { + if(fileOutputStream==null) + return false + try { + fileOutputStream!!.write(byte.toInt()) + return true + } catch(_: IOException) { + return false + } + } } internal fun ArrayDeque.pushw(value: UShort) {