diff --git a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java index 063deeed..74ef04d6 100644 --- a/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java +++ b/src/main/java/ru/dbotthepony/kstarbound/lua/LuaJNR.java @@ -70,6 +70,7 @@ public interface LuaJNR { public void lua_pushnumber(@NotNull Pointer luaState, double value); public void lua_pushinteger(@NotNull Pointer luaState, @LongLong long value); public void lua_pushboolean(@NotNull Pointer luaState, int value); + public void lua_pushglobaltable(@NotNull Pointer luaState); // NUL терминированная строка public void lua_pushstring(@NotNull Pointer luaState, @NotNull String value); @@ -84,8 +85,27 @@ public interface LuaJNR { public void lua_settable(@NotNull Pointer luaState, int stackIndex); public void lua_setglobal(@NotNull Pointer luaState, @NotNull String name); + // проверка стека public int lua_checkstack(@NotNull Pointer luaState, int value); public int lua_absindex(@NotNull Pointer luaState, int value); + public int lua_iscfunction(@NotNull Pointer luaState, int index); + public int lua_isinteger(@NotNull Pointer luaState, int index); + public int lua_isnumber(@NotNull Pointer luaState, int index); + public int lua_isstring(@NotNull Pointer luaState, int index); + public int lua_isuserdata(@NotNull Pointer luaState, int index); + + public int lua_toboolean(@NotNull Pointer luaState, int index); + public int lua_tocfunction(@NotNull Pointer luaState, int index); + public int lua_toclose(@NotNull Pointer luaState, int index); + + @LongLong + public long lua_tointegerx(@NotNull Pointer luaState, int index, @LongLong long successCode); + public double lua_tonumberx(@NotNull Pointer luaState, int index, @LongLong long successCode); + + public int lua_settop(@NotNull Pointer luaState, int index); + + public int lua_next(@NotNull Pointer luaState, int index); + public int lua_type(@NotNull Pointer luaState, int index); /** * Returns the index of the top element in the stack. diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 6cd850c3..a9b128b8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -28,6 +28,19 @@ import java.util.zip.Inflater private val LOGGER = LogManager.getLogger() fun main() { + if (true) { + val lua = LuaState() + + lua.load("return {lemon = 'Bouncy', [4] = 'four', [3] = 'watermelon', h = {h = 'h!'}}") + lua.call(numResults = 1) + + val a = lua.popTable()!! + + println("${a["lemon"]} $a") + + return + } + val starbound = Starbound() LOGGER.info("Running LWJGL ${Version.getVersion()}") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt index 651ac75e..5dcd1f02 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt @@ -22,7 +22,7 @@ class InternedJsonElementAdapter(val stringInterner: Interner) : TypeAda return when (val p = `in`.peek()) { JsonToken.STRING -> JsonPrimitive(stringInterner.intern(`in`.nextString())) JsonToken.NUMBER -> JsonPrimitive(LazilyParsedNumber(`in`.nextString())) - JsonToken.BOOLEAN -> if (`in`.nextBoolean()) _true else _false + JsonToken.BOOLEAN -> if (`in`.nextBoolean()) TRUE else FALSE JsonToken.NULL -> JsonNull.INSTANCE JsonToken.BEGIN_ARRAY -> { val output = JsonArray() @@ -43,8 +43,10 @@ class InternedJsonElementAdapter(val stringInterner: Interner) : TypeAda } companion object { - private val _true = JsonPrimitive(true) - private val _false = JsonPrimitive(false) + val TRUE = JsonPrimitive(true) + val FALSE = JsonPrimitive(false) + + fun of(value: Boolean) = if (value) TRUE else FALSE } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt index fa767ea1..8dbd97da 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt @@ -14,7 +14,9 @@ import com.kenai.jffi.Type import jnr.ffi.Memory import jnr.ffi.NativeType import org.apache.logging.log4j.LogManager +import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil +import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter import java.io.Closeable import java.io.PrintWriter import java.io.StringWriter @@ -33,12 +35,18 @@ private fun stringToBuffer(str: String): ByteBuffer { return buf } +@Suppress("unused") class LuaState : Closeable { private val pointer = LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState") private val cleanable: Cleaner.Cleanable private val sharedStringBufferPtr = MemoryIO.getInstance().allocateMemory(2L shl 16, false) init { + if (sharedStringBufferPtr == 0L) { + LuaJNR.INSTANCE.lua_close(pointer) + throw OutOfMemoryError("Unable to allocate new string shared buffer") + } + val pointer = pointer val sharedStringBufferPtr = sharedStringBufferPtr @@ -91,7 +99,10 @@ class LuaState : Closeable { /** * Converts the acceptable index idx into an equivalent absolute index (that is, one that does not depend on the stack size). */ - fun absoluteIndex(index: Int): Int { + fun absStackIndex(index: Int): Int { + if (index >= 0) + return index + return LuaJNR.INSTANCE.lua_absindex(pointer, index) } @@ -127,15 +138,15 @@ class LuaState : Closeable { val status = LuaJNR.INSTANCE.lua_pcallk(pointer, numArgs, numResults, 0, 0L, 0L) if (status == LUA_ERRRUN) { - throw LuaRuntimeException(popString()) + throw LuaRuntimeException(getString()) } return status } - fun popString(index: Int = -1, limit: Long = 2 shl 16): String? { + fun getString(stackIndex: Int = -1, limit: Long = 2 shl 16): String? { val len = Memory.allocateDirect(LuaJNR.RUNTIME, NativeType.SLONGLONG) - val p = LuaJNR.INSTANCE.lua_tolstring(pointer, absoluteIndex(index), len) ?: return null + val p = LuaJNR.INSTANCE.lua_tolstring(pointer, absStackIndex(stackIndex), len) ?: return null if (len.getLong(0L) == 0L) { return "" @@ -150,6 +161,169 @@ class LuaState : Closeable { return readBytes.toString(charset = Charsets.UTF_8) } + fun isCFunction(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_iscfunction(pointer, absStackIndex(stackIndex)) > 0 + fun isFunction(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.FUNCTION + fun isInteger(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isinteger(pointer, absStackIndex(stackIndex)) > 0 + fun isLightUserdata(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.LIGHTUSERDATA + fun isNil(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.NIL + fun isNone(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.NONE + fun isNoneOrNil(stackIndex: Int = -1): Boolean = typeAt(stackIndex).let { it == LuaType.NIL || it == LuaType.NONE } + fun isNumber(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isnumber(pointer, absStackIndex(stackIndex)) > 0 + fun isString(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isstring(pointer, absStackIndex(stackIndex)) > 0 + fun isTable(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.TABLE + fun isThread(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.THREAD + fun isUserdata(stackIndex: Int = -1): Boolean = LuaJNR.INSTANCE.lua_isuserdata(pointer, absStackIndex(stackIndex)) > 0 + fun isBoolean(stackIndex: Int = -1): Boolean = typeAt(stackIndex) == LuaType.BOOLEAN + + fun getBoolean(stackIndex: Int = -1): Boolean? { + if (!isBoolean(stackIndex)) + return null + + return LuaJNR.INSTANCE.lua_toboolean(pointer, stackIndex) > 0 + } + + fun getLong(stackIndex: Int = -1): Long? { + if (!isInteger(stackIndex)) + return null + + val stack = MemoryStack.stackPush() + val status = stack.mallocInt(1) + val value = LuaJNR.INSTANCE.lua_tointegerx(pointer, stackIndex, MemoryUtil.memAddress(status)) + val b = status[0] > 0 + stack.close() + + if (!b) + return null + + return value + } + + fun getDouble(stackIndex: Int = -1): Double? { + if (!isNumber(stackIndex)) + return null + + val stack = MemoryStack.stackPush() + val status = stack.mallocInt(1) + val value = LuaJNR.INSTANCE.lua_tonumberx(pointer, stackIndex, MemoryUtil.memAddress(status)) + val b = status[0] > 0 + stack.close() + + if (!b) + return null + + return value + } + + fun typeAt(stackIndex: Int = -1): LuaType { + return when (val value = LuaJNR.INSTANCE.lua_type(pointer, stackIndex)) { + LUA_TNONE -> LuaType.NONE + LUA_TNIL -> LuaType.NIL + LUA_TBOOLEAN -> LuaType.BOOLEAN + LUA_TLIGHTUSERDATA -> LuaType.LIGHTUSERDATA + LUA_TNUMBER -> LuaType.NUMBER + LUA_TSTRING -> LuaType.STRING + LUA_TTABLE -> LuaType.TABLE + LUA_TFUNCTION -> LuaType.FUNCTION + LUA_TUSERDATA -> LuaType.USERDATA + LUA_TTHREAD -> LuaType.THREAD + LUA_NUMTYPES -> LuaType.UMTYPES + else -> throw RuntimeException("Invalid Lua type: $value") + } + } + + fun getValue(stackIndex: Int = -1): JsonElement? { + val abs = absStackIndex(stackIndex) + + if (abs == 0) + return null + + return when (typeAt(abs)) { + LuaType.NONE -> null + LuaType.NIL -> JsonNull.INSTANCE + LuaType.BOOLEAN -> InternedJsonElementAdapter.of(getBoolean(abs)!!) + LuaType.LIGHTUSERDATA -> null + LuaType.NUMBER -> JsonPrimitive(if (isInteger(abs)) getLong(abs)!! else getDouble(abs)!!) + LuaType.STRING -> JsonPrimitive(getString(abs)) + LuaType.TABLE -> getTable(abs)!! + LuaType.FUNCTION -> null + LuaType.USERDATA -> null + LuaType.THREAD -> null + LuaType.UMTYPES -> null + } + } + + fun getTable(stackIndex: Int = -1): JsonObject? { + val abs = absStackIndex(stackIndex) + + if (!isTable(abs)) + return null + + val pairs = JsonObject() + push() + + while (LuaJNR.INSTANCE.lua_next(pointer, abs) != 0) { + val key = getValue(abs + 1) + val value = getValue(abs + 2) + + if (key is JsonPrimitive && value != null) { + pairs.add(key.asString, value) + } + + pop() + } + + return pairs + } + + fun popBoolean(): Boolean? { + try { + return getBoolean() + } finally { + pop() + } + } + + fun popLong(): Long? { + try { + return getLong() + } finally { + pop() + } + } + + fun popDouble(): Double? { + try { + return getDouble() + } finally { + pop() + } + } + + fun popValue(): JsonElement? { + try { + return getValue() + } finally { + pop() + } + } + + fun popTable(): JsonObject? { + try { + return getTable() + } finally { + pop() + } + } + + fun pop(amount: Int = 1): Int { + if (amount == 0) return 0 + check(amount > 0) { "Invalid amount to pop: $amount" } + val old = stackTop + val new = (old - amount).coerceAtLeast(0) + LuaJNR.INSTANCE.lua_settop(pointer, new) + return old - new + } + fun storeGlobal(name: String) { LuaJNR.INSTANCE.lua_setglobal(pointer, name) } @@ -287,5 +461,20 @@ class LuaState : Closeable { thread.priority = 1 thread } + + + const val LUA_TNONE = -1 + + const val LUA_TNIL = 0 + const val LUA_TBOOLEAN = 1 + const val LUA_TLIGHTUSERDATA = 2 + const val LUA_TNUMBER = 3 + const val LUA_TSTRING = 4 + const val LUA_TTABLE = 5 + const val LUA_TFUNCTION = 6 + const val LUA_TUSERDATA = 7 + const val LUA_TTHREAD = 8 + + const val LUA_NUMTYPES = 9 } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt new file mode 100644 index 00000000..ac971293 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt @@ -0,0 +1,15 @@ +package ru.dbotthepony.kstarbound.lua + +enum class LuaType { + NONE, + NIL, + BOOLEAN, + LIGHTUSERDATA, + NUMBER, + STRING, + TABLE, + FUNCTION, + USERDATA, + THREAD, + UMTYPES; +}