commonMain.org.luaj.vm2.Globals.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) 2012 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
import org.luaj.vm2.internal.*
import org.luaj.vm2.io.*
import org.luaj.vm2.lib.BaseLib
import org.luaj.vm2.lib.DebugLib
import org.luaj.vm2.lib.IoLib
import org.luaj.vm2.lib.PackageLib
import org.luaj.vm2.lib.ResourceFinder
import kotlin.jvm.*
import kotlin.math.*
/**
* Global environment used by luaj. Contains global variables referenced by executing lua.
*
*
*
* Constructing and Initializing Instances
* Typically, this is constructed indirectly by a call to
* [org.luaj.vm2.lib.jse.JsePlatform.standardGlobals] or
* [org.luaj.vm2.lib.jme.JmePlatform.standardGlobals],
* and then used to load lua scripts for execution as in the following example.
* `Globals globals = JsePlatform.standardGlobals();
* globals.load( new StringReader("print 'hello'"), "main.lua" ).call();
` *
* The creates a complete global environment with the standard libraries loaded.
*
*
* For specialized circumstances, the Globals may be constructed directly and loaded
* with only those libraries that are needed, for example.
* `Globals globals = new Globals();
* globals.load( new BaseLib() );
` *
*
* Loading and Executing Lua Code
* Globals contains convenience functions to load and execute lua source code given a Reader.
* A simple example is:
* `globals.load( new StringReader("print 'hello'"), "main.lua" ).call();
` *
*
* Fine-Grained Control of Compiling and Loading Lua
* Executable LuaFunctions are created from lua code in several steps
*
* * find the resource using the platform's [ResourceFinder]
* * compile lua to lua bytecode using [Compiler]
* * load lua bytecode to a [Prototype] using [Undumper]
* * construct [LuaClosure] from [Prototype] with [Globals] using [Loader]
*
*
*
* There are alternate flows when the direct lua-to-Java bytecode compiling [org.luaj.vm2.luajc.LuaJC] is used.
*
* * compile lua to lua bytecode using [Compiler] or load precompiled code using [Undumper]
* * convert lua bytecode to equivalent Java bytecode using [org.luaj.vm2.luajc.LuaJC] that implements [Loader] directly
*
*
* Java Field
* Certain public fields are provided that contain the current values of important global state:
*
* * [.STDIN] Current value for standard input in the laaded [IoLib], if any.
* * [.STDOUT] Current value for standard output in the loaded [IoLib], if any.
* * [.STDERR] Current value for standard error in the loaded [IoLib], if any.
* * [.finder] Current loaded [ResourceFinder], if any.
* * [.compiler] Current loaded [Compiler], if any.
* * [.undumper] Current loaded [Undumper], if any.
* * [.loader] Current loaded [Loader], if any.
*
*
* Lua Environment Variables
* When using [org.luaj.vm2.lib.jse.JsePlatform] or [org.luaj.vm2.lib.jme.JmePlatform],
* these environment variables are created within the Globals.
*
* * "_G" Pointer to this Globals.
* * "_VERSION" String containing the version of luaj.
*
*
* Use in Multithreaded Environments
* In a multi-threaded server environment, each server thread should create one Globals instance,
* which will be logically distinct and not interfere with each other, but share certain
* static immutable resources such as class data and string data.
*
*
*
* @see org.luaj.vm2.lib.jse.JsePlatform
*
* @see org.luaj.vm2.lib.jme.JmePlatform
*
* @see LuaValue
*
* @see Compiler
*
* @see Loader
*
* @see Undumper
*
* @see ResourceFinder
*
* @see org.luaj.vm2.compiler.LuaC
*
* @see org.luaj.vm2.luajc.LuaJC
*/
open class Globals(
val runtime: LuaRuntime = LuaRuntime()
) : LuaTable() {
/** The current default input stream. */
@kotlin.jvm.JvmField var STDIN: LuaBinInput = JSystem.`in`
/** The current default output stream. */
@kotlin.jvm.JvmField var STDOUT: LuaWriter = JSystem.out
/** The current default error stream. */
@kotlin.jvm.JvmField var STDERR: LuaWriter = JSystem.err
/** The installed ResourceFinder for looking files by name. */
@kotlin.jvm.JvmField var finder: ResourceFinder? = null
/** The currently running thread. Should not be changed by non-library code. */
@kotlin.jvm.JvmField var running: LuaThread = LuaThread(this)
/** The BaseLib instance loaded into this Globals */
@kotlin.jvm.JvmField var baselib: BaseLib? = null
/** The PackageLib instance loaded into this Globals */
@kotlin.jvm.JvmField var package_: PackageLib? = null
/** The DebugLib instance loaded into this Globals, or null if debugging is not enabled */
@kotlin.jvm.JvmField var debuglib: DebugLib? = null
/** The installed loader.
* @see Loader
*/
@kotlin.jvm.JvmField var loader: Loader? = null
/** The installed compiler.
* @see Compiler
*/
@kotlin.jvm.JvmField var compiler: Compiler? = null
/** The installed undumper.
* @see Undumper
*/
@kotlin.jvm.JvmField var undumper: Undumper? = null
/** Interface for module that converts a Prototype into a LuaFunction with an environment. */
interface Loader {
/** Convert the prototype into a LuaFunction with the supplied environment. */
fun load(prototype: Prototype, chunkname: String, env: LuaValue): LuaFunction
}
/** Interface for module that converts lua source text into a prototype. */
interface Compiler {
/** Compile lua source into a Prototype. The InputStream is assumed to be in UTF-8. */
fun compile(stream: LuaBinInput, chunkname: String): Prototype
}
/** Interface for module that loads lua binary chunk into a prototype. */
interface Undumper {
/** Load the supplied input stream into a prototype. */
fun undump(stream: LuaBinInput, chunkname: String): Prototype?
}
/** Check that this object is a Globals object, and return it, otherwise throw an error. */
override fun checkglobals(): Globals = this
/** Convenience function for loading a file that is either binary lua or lua source.
* @param filename Name of the file to load.
* @return LuaValue that can be call()'ed or invoke()'ed.
* @com.soywiz.luak.compat.java.Throws LuaError if the file could not be loaded.
*/
fun loadfile(filename: String): LuaValue = try {
load(finder!!.findResource(filename)!!, "@$filename", "bt", this)
} catch (e: Exception) {
LuaValue.error("load $filename: $e")
}
/** Convenience function to load a string value as a script. Must be lua source.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @com.soywiz.luak.compat.java.Throws LuaError if the script could not be compiled.
*/
open fun load(script: String, chunkname: String): LuaValue = load(StrLuaReader(script), chunkname)
/** Convenience function to load a string value as a script. Must be lua source.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @com.soywiz.luak.compat.java.Throws LuaError if the script could not be compiled.
*/
open fun load(script: String): LuaValue = load(StrLuaReader(script), script)
/** Convenience function to load a string value as a script with a custom environment.
* Must be lua source.
* @param script Contents of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @param environment LuaTable to be used as the environment for the loaded function.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @com.soywiz.luak.compat.java.Throws LuaError if the script could not be compiled.
*/
fun load(script: String, chunkname: String, environment: LuaTable): LuaValue =
load(StrLuaReader(script), chunkname, environment)
/** Load the content form a reader as a text file. Must be lua source.
* The source is converted to UTF-8, so any characters appearing in quoted literals
* above the range 128 will be converted into multiple bytes.
* @param reader Reader containing text of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @com.soywiz.luak.compat.java.Throws LuaError if the script could not be compiled.
*/
fun load(reader: LuaReader, chunkname: String): LuaValue = load(UTF8Stream(reader), chunkname, "t", this)
/** Load the content form a reader as a text file, supplying a custom environment.
* Must be lua source. The source is converted to UTF-8, so any characters
* appearing in quoted literals above the range 128 will be converted into
* multiple bytes.
* @param reader Reader containing text of a lua script, such as "print 'hello, world.'"
* @param chunkname Name that will be used within the chunk as the source.
* @param environment LuaTable to be used as the environment for the loaded function.
* @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
* @com.soywiz.luak.compat.java.Throws LuaError if the script could not be compiled.
*/
fun load(reader: LuaReader, chunkname: String, environment: LuaTable): LuaValue =
load(UTF8Stream(reader), chunkname, "t", environment)
/** Load the content form an input stream as a binary chunk or text file.
* @param is InputStream containing a lua script or compiled lua"
* @param chunkname Name that will be used within the chunk as the source.
* @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
* @param environment LuaTable to be used as the environment for the loaded function.
*/
fun load(`is`: LuaBinInput, chunkname: String, mode: String, environment: LuaValue): LuaValue {
try {
return loader!!.load(loadPrototype(`is`, chunkname, mode), chunkname, environment)
} catch (l: LuaError) {
throw l
} catch (e: Exception) {
e.printStackTrace()
return LuaValue.error("load $chunkname: $e")
}
}
/** Load lua source or lua binary from an input stream into a Prototype.
* The InputStream is either a binary lua chunk starting with the lua binary chunk signature,
* or a text input file. If it is a text input file, it is interpreted as a UTF-8 byte sequence.
* @param is Input stream containing a lua script or compiled lua"
* @param chunkname Name that will be used within the chunk as the source.
* @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
*/
fun loadPrototype(`is`: LuaBinInput, chunkname: String, mode: String): Prototype {
var `is` = `is`
if (mode.indexOf('b') >= 0) {
if (undumper == null) LuaValue.error("No undumper.")
if (!`is`.markSupported()) `is` = BufferedStream(`is`)
`is`.mark(4)
val p = undumper!!.undump(`is`, chunkname)
if (p != null) return p
`is`.reset()
}
if (mode.indexOf('t') >= 0) return compilePrototype(`is`, chunkname)
LuaValue.error("Failed to load prototype $chunkname using mode '$mode'")
//return null
kotlin.error("Failed to load prototype $chunkname using mode '$mode'")
}
/** Compile lua source from a Reader into a Prototype. The characters in the reader
* are converted to bytes using the UTF-8 encoding, so a string literal containing
* characters with codepoints 128 or above will be converted into multiple bytes.
*/
fun compilePrototype(reader: LuaReader, chunkname: String): Prototype = compilePrototype(UTF8Stream(reader), chunkname)
/** Compile lua source from an InputStream into a Prototype.
* The input is assumed to be UTf-8, but since bytes in the range 128-255 are passed along as
* literal bytes, any ASCII-compatible encoding such as ISO 8859-1 may also be used.
*/
fun compilePrototype(stream: LuaBinInput, chunkname: String): Prototype {
if (compiler == null) LuaValue.error("No compiler.")
return compiler!!.compile(stream, chunkname)
}
/** Function which yields the current thread.
* @param args Arguments to supply as return values in the resume function of the resuming thread.
* @return Values supplied as arguments to the resume() call that reactivates this thread.
*/
fun yield(args: Varargs): Varargs {
if (running.isMainThread) throw LuaError("cannot yield main thread")
val s = running.state
return s.lua_yield(args)
}
/* Abstract base class to provide basic buffered input storage and delivery.
* This class may be moved to its own package in the future.
*/
internal abstract class AbstractBufferedStream protected constructor(buflen: Int) : LuaBinInput() {
protected var b: ByteArray = ByteArray(buflen)
protected var i = 0
protected var j = 0
protected abstract fun avail(): Int
override fun read(): Int = avail().let { a -> if (a <= 0) -1 else 0xff and b[i++].toInt() and 0xFF }
override fun read(b: ByteArray, off: Int, len: Int): Int {
val a = avail()
if (a <= 0) return -1
val n_read = min(a, len)
arraycopy(this.b, i, b, off, n_read)
i += n_read
return n_read
}
override fun skip(n: Long): Long = min(n, (j - i).toLong()).also { i += it.toInt() }
override fun available(): Int = j - i
}
/** Simple converter from Reader to InputStream using UTF8 encoding that will work
* on both JME and JSE.
* This class may be moved to its own package in the future.
*/
internal class UTF8Stream(private val r: LuaReader) : AbstractBufferedStream(96) {
private val c = CharArray(32)
override fun avail(): Int {
if (i < j) return j - i
var n = r.read(c)
if (n < 0) return -1
if (n == 0) {
val u = r.read()
if (u < 0) return -1
c[0] = u.toChar()
n = 1
}
j = LuaString.encodeToUtf8(c, n, b, run { i = 0; i })
return j
}
override fun close() { r.close() }
}
/** Simple buffered InputStream that supports mark.
* Used to examine an InputStream for a 4-byte binary lua signature,
* and fall back to text input when the signature is not found,
* as well as speed up normal compilation and reading of lua scripts.
* This class may be moved to its own package in the future.
*/
internal class BufferedStream(buflen: Int, private val s: LuaBinInput) : AbstractBufferedStream(buflen) {
constructor(s: LuaBinInput) : this(128, s)
override fun avail(): Int {
if (i < j) return j - i
if (j >= b.size) {
j = 0
i = j
}
// leave previous bytes in place to implement mark()/reset().
var n = s.read(b, j, b.size - j)
if (n < 0) return -1
if (n == 0) {
val u = s.read()
if (u < 0) return -1
b[j] = u.toByte()
n = 1
}
j += n
return n
}
override fun close() { s.close() }
@Synchronized
override fun mark(n: Int) {
if (i > 0 || n > b.size) {
val dest = if (n > b.size) ByteArray(n) else b
arraycopy(b, i, dest, 0, j - i)
j -= i
i = 0
b = dest
}
}
override fun markSupported(): Boolean = true
@Synchronized
override fun reset() { i = 0 }
}
}