commonMain.org.luaj.vm2.lib.IoLib.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luak Show documentation
Show all versions of luak Show documentation
Multiplatform Kotlin LuaJ port (LUA interpreter)
/*******************************************************************************
* Copyright (c) 2009-2011 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.luaj.vm2.lib
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaString
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
import org.luaj.vm2.Varargs
import org.luaj.vm2.internal.*
import org.luaj.vm2.io.*
/**
* Abstract base class extending [LibFunction] which implements the
* core of the lua standard `io` library.
*
*
* It contains the implementation of the io library support that is common to
* the JSE and JME platforms.
* In practice on of the concrete IOLib subclasses is chosen:
* [org.luaj.vm2.lib.jse.JseIoLib] for the JSE platform, and
* [org.luaj.vm2.lib.jme.JmeIoLib] for the JME platform.
*
*
* The JSE implementation conforms almost completely to the C-based lua library,
* while the JME implementation follows closely except in the area of random-access files,
* which are difficult to support properly on JME.
*
*
* Typically, this library is included as part of a call to either
* [org.luaj.vm2.lib.jse.JsePlatform.standardGlobals] or [org.luaj.vm2.lib.jme.JmePlatform.standardGlobals]
* `Globals globals = JsePlatform.standardGlobals();
* globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
` *
* In this example the platform-specific [org.luaj.vm2.lib.jse.JseIoLib] library will be loaded, which will include
* the base functionality provided by this class, whereas the [org.luaj.vm2.lib.jse.JsePlatform] would load the
* [org.luaj.vm2.lib.jse.JseIoLib].
*
*
* To instantiate and use it directly,
* link it into your globals table via [LuaValue.load] using code such as:
* `Globals globals = new Globals();
* globals.load(new JseBaseLib());
* globals.load(new PackageLib());
* globals.load(new OsLib());
* globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
` *
*
*
* This has been implemented to match as closely as possible the behavior in the corresponding library in C.
* @see LibFunction
*
* @see org.luaj.vm2.lib.jse.JsePlatform
*
* @see org.luaj.vm2.lib.jme.JmePlatform
*
* @see org.luaj.vm2.lib.jse.JseIoLib
*
* @see org.luaj.vm2.lib.jme.JmeIoLib
*
* @see [http://www.lua.org/manual/5.1/manual.html.5.7](http://www.lua.org/manual/5.1/manual.html.5.7)
*/
abstract class IoLib : TwoArgFunction() {
private var infile: File? = null
private var outfile: File? = null
private var errfile: File? = null
@kotlin.jvm.JvmField internal var filemethods: LuaTable = LuaTable()
@kotlin.jvm.JvmField
protected var globals: Globals? = null
abstract inner class File : LuaValue() {
abstract fun write(string: LuaString?)
abstract fun flush()
abstract fun isstdfile(): Boolean
abstract fun close()
abstract fun isclosed(): Boolean
// returns new position
abstract fun seek(option: String?, bytecount: Int): Int
abstract fun setvbuf(mode: String?, size: Int)
// get length remaining to read
abstract fun remaining(): Int
// peek ahead one character
abstract fun peek(): Int
// return char if read, -1 if eof, throw IOException on other exception
abstract fun read(): Int
// return number of bytes read if positive, false if eof, throw IOException on other exception
abstract fun read(bytes: ByteArray, offset: Int, length: Int): Int
// delegate method access to file methods table
override fun get(key: LuaValue): LuaValue {
return filemethods[key]
}
// essentially a userdata instance
override fun type(): Int {
return LuaValue.TUSERDATA
}
override fun typename(): String {
return "userdata"
}
// displays as "file" type
override fun tojstring(): String {
return "file: " + hashCode().toHexString()
}
}
/**
* Wrap the standard input.
* @return File
* @com.soywiz.luak.compat.java.Throws IOException
*/
protected abstract fun wrapStdin(): File
/**
* Wrap the standard output.
* @return File
* @com.soywiz.luak.compat.java.Throws IOException
*/
protected abstract fun wrapStdout(): File
/**
* Wrap the standard error output.
* @return File
* @com.soywiz.luak.compat.java.Throws IOException
*/
protected abstract fun wrapStderr(): File
/**
* Open a file in a particular mode.
* @param filename
* @param readMode true if opening in read mode
* @param appendMode true if opening in append mode
* @param updateMode true if opening in update mode
* @param binaryMode true if opening in binary mode
* @return File object if successful
* @com.soywiz.luak.compat.java.Throws IOException if could not be opened
*/
protected abstract fun openFile(
filename: String?,
readMode: Boolean,
appendMode: Boolean,
updateMode: Boolean,
binaryMode: Boolean
): File
/**
* Open a temporary file.
* @return File object if successful
* @com.soywiz.luak.compat.java.Throws IOException if could not be opened
*/
protected abstract fun tmpFile(): File
/**
* Start a new process and return a file for input or output
* @param prog the program to execute
* @param mode "r" to read, "w" to write
* @return File to read to or write from
* @com.soywiz.luak.compat.java.Throws IOException if an i/o exception occurs
*/
protected abstract fun openProgram(prog: String?, mode: String?): File
override fun call(modname: LuaValue, env: LuaValue): LuaValue {
globals = env.checkglobals()
// io lib functions
val t = LuaTable()
bind(t, { IoLibV() }, IO_NAMES)
// create file methods table
filemethods = LuaTable()
bind(filemethods, { IoLibV() }, FILE_NAMES, FILE_CLOSE)
// set up file metatable
val mt = LuaTable()
bind(mt, { IoLibV() }, arrayOf("__index"), IO_INDEX)
t.setmetatable(mt)
// all functions link to library instance
setLibInstance(t)
setLibInstance(filemethods)
setLibInstance(mt)
// return the table
env["io"] = t
env["package"]["loaded"]["io"] = t
return t
}
private fun setLibInstance(t: LuaTable) {
val k = t.keys()
var i = 0
val n = k.size
while (i < n) {
(t[k[i]] as IoLibV).iolib = this
i++
}
}
open class IoLibV : VarArgFunction {
lateinit private var f: File
lateinit var iolib: IoLib
constructor()
constructor(f: File, name: String, opcode: Int, iolib: IoLib) : super() {
this.f = f
this.name = name
this.opcode = opcode
this.iolib = iolib
}
override fun invoke(args: Varargs): Varargs {
try {
when (opcode) {
IO_FLUSH -> return iolib._io_flush()
IO_TMPFILE -> return iolib._io_tmpfile()
IO_CLOSE -> return iolib._io_close(args.arg1())
IO_INPUT -> return iolib._io_input(args.arg1())
IO_OUTPUT -> return iolib._io_output(args.arg1())
IO_TYPE -> return iolib._io_type(args.arg1())
IO_POPEN -> return iolib._io_popen(args.checkjstring(1), args.optjstring(2, "r"))
IO_OPEN -> return iolib._io_open(args.checkjstring(1), args.optjstring(2, "r"))
IO_LINES -> return iolib._io_lines(if (args.isvalue(1)) args.checkjstring(1) else null)
IO_READ -> return iolib._io_read(args)
IO_WRITE -> return iolib._io_write(args)
FILE_CLOSE -> return iolib._file_close(args.arg1())
FILE_FLUSH -> return iolib._file_flush(args.arg1())
FILE_SETVBUF -> return iolib._file_setvbuf(args.arg1(), args.checkjstring(2), args.optint(3, 1024))
FILE_LINES -> return iolib._file_lines(args.arg1())
FILE_READ -> return iolib._file_read(args.arg1(), args.subargs(2))
FILE_SEEK -> return iolib._file_seek(args.arg1(), args.optjstring(2, "cur"), args.optint(3, 0))
FILE_WRITE -> return iolib._file_write(args.arg1(), args.subargs(2))
IO_INDEX -> return iolib._io_index(args.arg(2))
LINES_ITER -> return iolib._lines_iter(f)
}
} catch (ioe: IOException) {
return errorresult(ioe)
}
return LuaValue.NONE
}
}
private fun input(): File {
return if (infile != null) infile!! else run { infile = ioopenfile(FTYPE_STDIN, "-", "r"); infile!! }
}
// io.flush() -> bool
fun _io_flush(): Varargs {
checkopen(output())
outfile!!.flush()
return TRUE
}
// io.tmpfile() -> file
fun _io_tmpfile(): Varargs {
return tmpFile()
}
// io.close([file]) -> void
fun _io_close(file: LuaValue): Varargs {
val f = if (file.isnil()) output() else checkfile(file)
checkopen(f)
return ioclose(f)
}
// io.input([file]) -> file
fun _io_input(file: LuaValue): Varargs {
infile = if (file.isnil())
input()
else if (file.isstring())
ioopenfile(FTYPE_NAMED, file.checkjstring(), "r")
else
checkfile(file)
return infile!!
}
// io.output(filename) -> file
fun _io_output(filename: LuaValue): Varargs {
outfile = if (filename.isnil())
output()
else if (filename.isstring())
ioopenfile(FTYPE_NAMED, filename.checkjstring(), "w")
else
checkfile(filename)
return outfile!!
}
// io.type(obj) -> "file" | "closed file" | nil
fun _io_type(obj: LuaValue): Varargs {
val f = optfile(obj)
return if (f != null)
if (f.isclosed()) CLOSED_FILE else FILE
else
LuaValue.NIL
}
// io.popen(prog, [mode]) -> file
fun _io_popen(prog: String?, mode: String?): Varargs {
return openProgram(prog, mode)
}
// io.open(filename, [mode]) -> file | nil,err
fun _io_open(filename: String?, mode: String?): Varargs {
return rawopenfile(FTYPE_NAMED, filename, mode)
}
// io.lines(filename) -> iterator
fun _io_lines(filename: String?): Varargs {
infile = if (filename == null) input() else ioopenfile(FTYPE_NAMED, filename, "r")
checkopen(infile!!)
return lines(infile!!)
}
// io.read(...) -> (...)
fun _io_read(args: Varargs): Varargs {
checkopen(input())
return ioread(infile!!, args)
}
// io.write(...) -> void
fun _io_write(args: Varargs): Varargs {
checkopen(output())
return iowrite(outfile, args)
}
// file:close() -> void
fun _file_close(file: LuaValue): Varargs {
return ioclose(checkfile(file))
}
// file:flush() -> void
fun _file_flush(file: LuaValue): Varargs {
checkfile(file).flush()
return TRUE
}
// file:setvbuf(mode,[size]) -> void
fun _file_setvbuf(file: LuaValue, mode: String?, size: Int): Varargs {
checkfile(file).setvbuf(mode, size)
return TRUE
}
// file:lines() -> iterator
fun _file_lines(file: LuaValue): Varargs {
return lines(checkfile(file))
}
// file:read(...) -> (...)
fun _file_read(file: LuaValue, subargs: Varargs): Varargs {
return ioread(checkfile(file), subargs)
}
// file:seek([whence][,offset]) -> pos | nil,error
fun _file_seek(file: LuaValue, whence: String?, offset: Int): Varargs {
return LuaValue.valueOf(checkfile(file).seek(whence, offset))
}
// file:write(...) -> void
fun _file_write(file: LuaValue, subargs: Varargs): Varargs {
return iowrite(checkfile(file), subargs)
}
// __index, returns a field
fun _io_index(v: LuaValue): Varargs {
return if (v == STDOUT)
output()
else if (v == STDIN)
input()
else if (v == STDERR) errput() else LuaValue.NIL
}
// lines iterator(s,var) -> var'
fun _lines_iter(file: LuaValue): Varargs {
return freadline(checkfile(file))
}
private fun output(): File {
return if (outfile != null) outfile!! else run { outfile = ioopenfile(FTYPE_STDOUT, "-", "w"); outfile!! }
}
private fun errput(): File {
return if (errfile != null) errfile!! else run { errfile = ioopenfile(FTYPE_STDERR, "-", "w"); errfile!! }
}
private fun ioopenfile(filetype: Int, filename: String?, mode: String): File? {
try {
return rawopenfile(filetype, filename, mode)
} catch (e: Exception) {
LuaValue.error("io error: " + e.message)
return null
}
}
private fun lines(f: File): Varargs {
try {
return IoLibV(f, "lnext", LINES_ITER, this)
} catch (e: Exception) {
return LuaValue.error("lines: $e")
}
}
private fun ioread(f: File, args: Varargs): Varargs {
var i: Int
val n = args.narg()
val v = arrayOfNulls(n)
var ai: LuaValue
var vi: LuaValue
var fmt: LuaString?
i = 0
while (i < n) {
item@do {
when ((run { ai = args.arg(i + 1); ai }).type()) {
LuaValue.TNUMBER -> {
vi = freadbytes(f, ai.toint())
break@item
}
LuaValue.TSTRING -> {
fmt = ai.checkstring()
if (fmt.m_length == 2 && fmt.m_bytes[fmt.m_offset] == '*'.toByte()) {
when (fmt.m_bytes[fmt.m_offset + 1].toChar()) {
'n' -> {
vi = freadnumber(f)
break@item
}
'l' -> {
vi = freadline(f)
break@item
}
'a' -> {
vi = freadall(f)
break@item
}
}
}
return LuaValue.argerror(i + 1, "(invalid format)")
}
else -> return LuaValue.argerror(i + 1, "(invalid format)")
}
} while (false)
if ((run { v[i++] = vi; vi }).isnil())
break
}
return if (i == 0) LuaValue.NIL else LuaValue.varargsOf(v as Array, 0, i)
}
private fun rawopenfile(filetype: Int, filename: String?, mode: String?): File {
when (filetype) {
FTYPE_STDIN -> return wrapStdin()
FTYPE_STDOUT -> return wrapStdout()
FTYPE_STDERR -> return wrapStderr()
}
val isreadmode = mode!!.startsWith("r")
val isappend = mode.startsWith("a")
val isupdate = mode.indexOf("+") > 0
val isbinary = mode.endsWith("b")
return openFile(filename, isreadmode, isappend, isupdate, isbinary)
}
companion object {
/** Enumerated value representing stdin */
const val FTYPE_STDIN = 0
/** Enumerated value representing stdout */
const val FTYPE_STDOUT = 1
/** Enumerated value representing stderr */
const val FTYPE_STDERR = 2
/** Enumerated value representing a file type for a named file */
const val FTYPE_NAMED = 3
private val STDIN = LuaValue.valueOf("stdin")
private val STDOUT = LuaValue.valueOf("stdout")
private val STDERR = LuaValue.valueOf("stderr")
private val FILE = LuaValue.valueOf("file")
private val CLOSED_FILE = LuaValue.valueOf("closed file")
private const val IO_CLOSE = 0
private const val IO_FLUSH = 1
private const val IO_INPUT = 2
private const val IO_LINES = 3
private const val IO_OPEN = 4
private const val IO_OUTPUT = 5
private const val IO_POPEN = 6
private const val IO_READ = 7
private const val IO_TMPFILE = 8
private const val IO_TYPE = 9
private const val IO_WRITE = 10
private const val FILE_CLOSE = 11
private const val FILE_FLUSH = 12
private const val FILE_LINES = 13
private const val FILE_READ = 14
private const val FILE_SEEK = 15
private const val FILE_SETVBUF = 16
private const val FILE_WRITE = 17
private const val IO_INDEX = 18
private const val LINES_ITER = 19
val IO_NAMES =
arrayOf("close", "flush", "input", "lines", "open", "output", "popen", "read", "tmpfile", "type", "write")
val FILE_NAMES = arrayOf("close", "flush", "lines", "read", "seek", "setvbuf", "write")
private fun ioclose(f: File): Varargs {
if (f.isstdfile())
return errorresult("cannot close standard file")
else {
f.close()
return successresult()
}
}
private fun successresult(): Varargs {
return TRUE
}
private fun errorresult(ioe: Exception): Varargs {
val s = ioe.message
return errorresult("io error: " + (s ?: ioe.toString()))
}
private fun errorresult(errortext: String): Varargs {
return LuaValue.varargsOf(LuaValue.NIL, LuaValue.valueOf(errortext))
}
private fun iowrite(f: File?, args: Varargs): Varargs {
var i = 1
val n = args.narg()
while (i <= n) {
f!!.write(args.checkstring(i))
i++
}
return f!!
}
private fun checkfile(`val`: LuaValue): File {
val f = optfile(`val`)
if (f == null)
LuaValue.argerror(1, "file")
checkopen(f!!)
return f
}
private fun optfile(`val`: LuaValue): File? {
return if (`val` is File) `val` else null
}
private fun checkopen(file: File): File {
if (file.isclosed())
LuaValue.error("attempt to use a closed file")
return file
}
// ------------- file reading utilitied ------------------
fun freadbytes(f: File, count: Int): LuaValue {
val b = ByteArray(count)
val r: Int
return if ((run { r = f.read(b, 0, b.size); r }) < 0) LuaValue.NIL else LuaString.valueUsing(b, 0, r)
}
fun freaduntil(f: File, lineonly: Boolean): LuaValue {
val baos = ByteArrayLuaBinOutput()
var c: Int
try {
if (lineonly) {
loop@ while ((run { c = f.read(); c }) > 0) {
when (c.toChar()) {
'\r' -> Unit
'\n' -> break@loop
else -> baos.write(c)
}
}
} else {
while ((run { c = f.read(); c }) > 0)
baos.write(c)
}
} catch (e: EOFException) {
c = -1
}
return if (c < 0 && baos.size() == 0)
LuaValue.NIL
else
LuaString.valueUsing(baos.toByteArray())
}
fun freadline(f: File): LuaValue {
return freaduntil(f, true)
}
fun freadall(f: File): LuaValue {
val n = f.remaining()
return if (n >= 0) {
freadbytes(f, n)
} else {
freaduntil(f, false)
}
}
fun freadnumber(f: File?): LuaValue {
val baos = ByteArrayLuaBinOutput()
freadchars(f!!, " \t\r\n", null)
freadchars(f, "-+", baos)
//freadchars(f,"0",baos);
//freadchars(f,"xX",baos);
freadchars(f, "0123456789", baos)
freadchars(f, ".", baos)
freadchars(f, "0123456789", baos)
//freadchars(f,"eEfFgG",baos);
// freadchars(f,"+-",baos);
//freadchars(f,"0123456789",baos);
val s = baos.toString()
return if (s.length > 0) LuaValue.valueOf(s.toDouble()) else LuaValue.NIL
}
private fun freadchars(f: File, chars: String, baos: ByteArrayLuaBinOutput?) {
var c: Int
while (true) {
c = f.peek()
if (chars.indexOf(c.toChar()) < 0) {
return
}
f.read()
baos?.write(c)
}
}
}
}