vm: implemented reading/writing files in diskio

This commit is contained in:
Irmen de Jong 2024-10-29 01:09:25 +01:00
parent 76b05cb5fd
commit 483d193ced
5 changed files with 279 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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<IRCodeChunk>
@ -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<Boolean, UByte> {
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<UByte>.pushw(value: UShort) {