From 61b21a595f0a0f1e7fa7cefc7930337b945fa184 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Thu, 23 Feb 2023 14:19:02 +0700
Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD?=
 =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9?=
 =?UTF-8?q?=20=D0=B8=D0=B7=20Lua?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../ru/dbotthepony/kstarbound/lua/LuaJNR.java |  20 ++
 .../kotlin/ru/dbotthepony/kstarbound/Main.kt  |  13 ++
 .../io/json/InternedJsonElementAdapter.kt     |   8 +-
 .../ru/dbotthepony/kstarbound/lua/LuaState.kt | 197 +++++++++++++++++-
 .../ru/dbotthepony/kstarbound/lua/LuaType.kt  |  15 ++
 5 files changed, 246 insertions(+), 7 deletions(-)
 create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaType.kt

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<String>) : 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<String>) : 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;
+}