commonMain.org.luaj.vm2.LoadState.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 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.io.*
/**
* Class to undump compiled lua bytecode into a [Prototype] instances.
*
*
* The [LoadState] class provides the default [Globals.Undumper]
* which is used to undump a string of bytes that represent a lua binary file
* using either the C-based lua compiler, or luaj's
* [org.luaj.vm2.compiler.LuaC] compiler.
*
*
* The canonical method to load and execute code is done
* indirectly using the Globals:
* `Globals globals = JsePlatform.standardGlobals();
* LuaValue chunk = globasl.load("print('hello, world')", "main.lua");
* chunk.call();
` *
* This should work regardless of which [Globals.Compiler] or [Globals.Undumper]
* have been installed.
*
*
* By default, when using [org.luaj.vm2.lib.jse.JsePlatform] or
* [org.luaj.vm2.lib.jme.JmePlatform]
* to construct globals, the [LoadState] default undumper is installed
* as the default [Globals.Undumper].
*
*
*
* A lua binary file is created via the [org.luaj.vm2.compiler.DumpState] class
* :
* `Globals globals = JsePlatform.standardGlobals();
* Prototype p = globals.compilePrototype(new StringReader("print('hello, world')"), "main.lua");
* ByteArrayOutputStream o = new ByteArrayOutputStream();
* org.luaj.vm2.compiler.DumpState.dump(p, o, false);
* byte[] lua_binary_file_bytes = o.toByteArray();
` *
*
* The [LoadState]'s default undumper [.instance]
* may be used directly to undump these bytes:
* `Prototypep = LoadState.instance.undump(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua");
* LuaClosure c = new LuaClosure(p, globals);
* c.call();
` *
*
*
* More commonly, the [Globals.Undumper] may be used to undump them:
* `Prototype p = globals.loadPrototype(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua", "b");
* LuaClosure c = new LuaClosure(p, globals);
* c.call();
` *
*
* @see Globals.Compiler
*
* @see Globals.Undumper
*
* @see LuaClosure
*
* @see LuaFunction
*
* @see org.luaj.vm2.compiler.LuaC
*
* @see org.luaj.vm2.luajc.LuaJC
*
* @see Globals.compiler
*
* @see Globals.load
*/
class LoadState
/** Private constructor for create a load state */
private constructor(
stream: LuaBinInput,
/** Name of what is being loaded? */
internal var name: String
) {
// values read from the header
private var luacVersion: Int = 0
private var luacFormat: Int = 0
private var luacLittleEndian: Boolean = false
private var luacSizeofInt: Int = 0
private var luacSizeofSizeT: Int = 0
private var luacSizeofInstruction: Int = 0
private var luacSizeofLuaNumber: Int = 0
private var luacNumberFormat: Int = 0
/** input stream from which we are loading */
val `is`: LuaBinInput = stream
/** Read buffer */
private var buf = ByteArray(512)
/** Load a 4-byte int value from the input stream
* @return the int value laoded.
*/
internal fun loadInt(): Int {
`is`.readFully(buf, 0, 4)
return if (luacLittleEndian)
buf[3].toInt() shl 24 or (0xff and buf[2].toInt() shl 16) or (0xff and buf[1].toInt() shl 8) or (0xff and buf[0].toInt())
else
buf[0].toInt() shl 24 or (0xff and buf[1].toInt() shl 16) or (0xff and buf[2].toInt() shl 8) or (0xff and buf[3].toInt())
}
/** Load an array of int values from the input stream
* @return the array of int values laoded.
*/
internal fun loadIntArray(): IntArray {
val n = loadInt()
if (n == 0)
return NOINTS
// read all data at once
val m = n shl 2
if (buf.size < m)
buf = ByteArray(m)
`is`.readFully(buf, 0, m)
val array = IntArray(n)
var i = 0
var j = 0
while (i < n) {
array[i] = if (luacLittleEndian)
buf[j + 3].toInt() shl 24 or (0xff and buf[j + 2].toInt() shl 16) or (0xff and buf[j + 1].toInt() shl 8) or (0xff and buf[j + 0].toInt())
else
buf[j + 0].toInt() shl 24 or (0xff and buf[j + 1].toInt() shl 16) or (0xff and buf[j + 2].toInt() shl 8) or (0xff and buf[j + 3].toInt())
++i
j += 4
}
return array
}
/** Load a long value from the input stream
* @return the long value laoded.
*/
internal fun loadInt64(): Long {
val a: Int
val b: Int
if (this.luacLittleEndian) {
a = loadInt()
b = loadInt()
} else {
b = loadInt()
a = loadInt()
}
return b.toLong() shl 32 or (a.toLong() and 0xffffffffL)
}
/** Load a lua strin gvalue from the input stream
* @return the [LuaString] value laoded.
*/
internal fun loadString(): LuaString? {
val size = if (this.luacSizeofSizeT == 8) loadInt64().toInt() else loadInt()
if (size == 0)
return null
val bytes = ByteArray(size)
`is`.readFully(bytes, 0, size)
return LuaString.valueUsing(bytes, 0, bytes.size - 1)
}
/**
* Load a number from a binary chunk
* @return the [LuaValue] loaded
* @com.soywiz.luak.compat.java.Throws IOException if an i/o exception occurs
*/
internal fun loadNumber(): LuaValue {
return if (luacNumberFormat == NUMBER_FORMAT_INTS_ONLY) {
LuaInteger.valueOf(loadInt())
} else {
longBitsToLuaNumber(loadInt64())
}
}
/**
* Load a list of constants from a binary chunk
* @param f the function prototype
* @com.soywiz.luak.compat.java.Throws IOException if an i/o exception occurs
*/
internal fun loadConstants(f: Prototype) {
var n = loadInt()
val values: Array = if (n > 0) arrayOfNulls(n) else NOVALUES
for (i in 0 until n) {
when (`is`.readByte().toInt()) {
LUA_TNIL -> values[i] = LuaValue.NIL
LUA_TBOOLEAN -> values[i] = (if (0 != `is`.readUnsignedByte()) LuaValue.TRUE else LuaValue.FALSE)
LUA_TINT -> values[i] = LuaInteger.valueOf(loadInt())
LUA_TNUMBER -> values[i] = loadNumber()
LUA_TSTRING -> values[i] = loadString()
else -> throw IllegalStateException("bad constant")
}
}
f.k = values as Array
n = loadInt()
val protos: Array = if (n > 0) arrayOfNulls(n) else NOPROTOS as Array
for (i in 0 until n)
protos[i] = loadFunction(f.source)
f.p = protos as Array
}
internal fun loadUpvalues(f: Prototype) {
val n = loadInt()
f.upvalues = if (n > 0) arrayOfNulls(n) as Array else NOUPVALDESCS
for (i in 0 until n) {
val instack = `is`.readByte().toInt() != 0
val idx = `is`.readByte().toInt() and 0xff
f.upvalues[i] = Upvaldesc(null, instack, idx)
}
}
/**
* Load the debug info for a function prototype
* @param f the function Prototype
* @com.soywiz.luak.compat.java.Throws IOException if there is an i/o exception
*/
internal fun loadDebug(f: Prototype) {
f.source = loadString() ?: LuaString.valueOf("Unknown")
f.lineinfo = loadIntArray()
var n = loadInt()
f.locvars = if (n > 0) arrayOfNulls(n) as Array else NOLOCVARS
for (i in 0 until n) {
val varname = loadString()
val startpc = loadInt()
val endpc = loadInt()
f.locvars[i] = LocVars(varname!!, startpc, endpc)
}
n = loadInt()
for (i in 0 until n)
f.upvalues[i].name = loadString()
}
/**
* Load a function prototype from the input stream
* @param p name of the source
* @return [Prototype] instance that was loaded
* @com.soywiz.luak.compat.java.Throws IOException
*/
fun loadFunction(p: LuaString): Prototype {
val f = Prototype()
//// this.L.push(f);
// f.source = loadString();
// if ( f.source == null )
// f.source = p;
f.linedefined = loadInt()
f.lastlinedefined = loadInt()
f.numparams = `is`.readUnsignedByte()
f.is_vararg = `is`.readUnsignedByte()
f.maxstacksize = `is`.readUnsignedByte()
f.code = loadIntArray()
loadConstants(f)
loadUpvalues(f)
loadDebug(f)
// TODO: add check here, for debugging purposes, I believe
// see ldebug.c
// IF (!luaG_checkcode(f), "bad code");
// this.L.pop();
return f
}
/**
* Load the lua chunk header values.
* @com.soywiz.luak.compat.java.Throws IOException if an i/o exception occurs.
*/
fun loadHeader() {
luacVersion = `is`.readByte().toInt()
luacFormat = `is`.readByte().toInt()
luacLittleEndian = 0 != `is`.readByte().toInt()
luacSizeofInt = `is`.readByte().toInt()
luacSizeofSizeT = `is`.readByte().toInt()
luacSizeofInstruction = `is`.readByte().toInt()
luacSizeofLuaNumber = `is`.readByte().toInt()
luacNumberFormat = `is`.readByte().toInt()
for (i in LUAC_TAIL.indices)
if (`is`.readByte() != LUAC_TAIL[i])
throw LuaError("Unexpeted byte in luac tail of header, index=$i")
}
private class GlobalsUndumper : Globals.Undumper {
override fun undump(stream: LuaBinInput, chunkname: String): Prototype? {
return LoadState.undump(stream, chunkname)
}
}
companion object {
/** Shared instance of Globals.Undumper to use loading prototypes from binary lua files */
@kotlin.jvm.JvmField val instance: Globals.Undumper = GlobalsUndumper()
/** format corresponding to non-number-patched lua, all numbers are floats or doubles */
@kotlin.jvm.JvmField val NUMBER_FORMAT_FLOATS_OR_DOUBLES = 0
/** format corresponding to non-number-patched lua, all numbers are ints */
@kotlin.jvm.JvmField val NUMBER_FORMAT_INTS_ONLY = 1
/** format corresponding to number-patched lua, all numbers are 32-bit (4 byte) ints */
@kotlin.jvm.JvmField val NUMBER_FORMAT_NUM_PATCH_INT32 = 4
// type constants
@kotlin.jvm.JvmField val LUA_TINT = -2
@kotlin.jvm.JvmField val LUA_TNONE = -1
@kotlin.jvm.JvmField val LUA_TNIL = 0
@kotlin.jvm.JvmField val LUA_TBOOLEAN = 1
@kotlin.jvm.JvmField val LUA_TLIGHTUSERDATA = 2
@kotlin.jvm.JvmField val LUA_TNUMBER = 3
@kotlin.jvm.JvmField val LUA_TSTRING = 4
@kotlin.jvm.JvmField val LUA_TTABLE = 5
@kotlin.jvm.JvmField val LUA_TFUNCTION = 6
@kotlin.jvm.JvmField val LUA_TUSERDATA = 7
@kotlin.jvm.JvmField val LUA_TTHREAD = 8
@kotlin.jvm.JvmField val LUA_TVALUE = 9
/** The character encoding to use for file encoding. Null means the default encoding */
@kotlin.jvm.JvmField var encoding: String? = null
/** Signature byte indicating the file is a compiled binary chunk */
@kotlin.jvm.JvmField val LUA_SIGNATURE = byteArrayOf('\u001b'.toByte(), 'L'.toByte(), 'u'.toByte(), 'a'.toByte())
/** Data to catch conversion errors */
@kotlin.jvm.JvmField val LUAC_TAIL =
byteArrayOf(0x19.toByte(), 0x93.toByte(), '\r'.toByte(), '\n'.toByte(), 0x1a.toByte(), '\n'.toByte())
/** Name for compiled chunks */
@kotlin.jvm.JvmField val SOURCE_BINARY_STRING = "binary string"
/** for header of binary files -- this is Lua 5.2 */
@kotlin.jvm.JvmField val LUAC_VERSION = 0x52
/** for header of binary files -- this is the official format */
@kotlin.jvm.JvmField val LUAC_FORMAT = 0
/** size of header of binary files */
@kotlin.jvm.JvmField val LUAC_HEADERSIZE = 12
private val NOVALUES = arrayOf()
private val NOPROTOS = arrayOf()
private val NOLOCVARS = arrayOf()
private val NOSTRVALUES = arrayOf()
private val NOUPVALDESCS = arrayOf()
private val NOINTS = intArrayOf()
/** Install this class as the standard Globals.Undumper for the supplied Globals */
fun install(globals: Globals) {
globals.undumper = instance
}
/**
* Convert bits in a long value to a [LuaValue].
* @param bits long value containing the bits
* @return [LuaInteger] or [LuaDouble] whose value corresponds to the bits provided.
*/
fun longBitsToLuaNumber(bits: Long): LuaValue {
if (bits and (1L shl 63) - 1 == 0L) {
return LuaValue.ZERO
}
val e = (bits shr 52 and 0x7ffL).toInt() - 1023
if (e >= 0 && e < 31) {
val f = bits and 0xFFFFFFFFFFFFFL
val shift = 52 - e
val intPrecMask = (1L shl shift) - 1
if (f and intPrecMask == 0L) {
val intValue = (f shr shift).toInt() or (1 shl e)
return LuaInteger.valueOf(if (bits shr 63 != 0L) -intValue else intValue)
}
}
return LuaValue.valueOf(Double.fromBits(bits))
}
/**
* Load input stream as a lua binary chunk if the first 4 bytes are the lua binary signature.
* @param stream InputStream to read, after having read the first byte already
* @param chunkname Name to apply to the loaded chunk
* @return [Prototype] that was loaded, or null if the first 4 bytes were not the lua signature.
* @com.soywiz.luak.compat.java.Throws IOException if an IOException occurs
*/
fun undump(stream: LuaBinInput, chunkname: String): Prototype? {
// check rest of signature
if (stream.read() != LUA_SIGNATURE[0].toInt()
|| stream.read() != LUA_SIGNATURE[1].toInt()
|| stream.read() != LUA_SIGNATURE[2].toInt()
|| stream.read() != LUA_SIGNATURE[3].toInt()
)
return null
// load file as a compiled chunk
val sname = getSourceName(chunkname)
val s = LoadState(stream, sname)
s.loadHeader()
// check format
when (s.luacNumberFormat) {
NUMBER_FORMAT_FLOATS_OR_DOUBLES, NUMBER_FORMAT_INTS_ONLY, NUMBER_FORMAT_NUM_PATCH_INT32 -> {
}
else -> throw LuaError("unsupported int size")
}
return s.loadFunction(LuaString.valueOf(sname))
}
/**
* Construct a source name from a supplied chunk name
* @param name String name that appears in the chunk
* @return source file name
*/
fun getSourceName(name: String): String {
var sname = name
if (name.startsWith("@") || name.startsWith("="))
sname = name.substring(1)
else if (name.startsWith("\u001b"))
sname = SOURCE_BINARY_STRING
return sname
}
}
}