+ * 本文件由 {@code native/gen_capi.py} 产出。idl 与 C 侧的 + * {@code luashim_stubs.inc} 同源,改符号集要重跑脚本;重跑会覆盖这里, + * 所以实现只能写在本文件里({@code unimplemented} 的那批逐个替换), + * 不要另开一个类去写,否则下次生成就丢了。 + *
+ * 约定: + *
+ * 由 {@code native/gen_capi.py} 初次生成,之后手改:脚本见文件已存在就 + * 跳过,不会覆盖。分发表 {@link CApi} 每次都重新生成,两边靠方法名对上, + * 所以这里的方法签名不能改。 + *
+ * 下标约定:{@code L} 是句柄(经 {@link NativeBridge#state} 换成
+ * {@link LuaState}),其余整数下标就是 C 的那套——正数从 {@code ci.func+1} 起,
+ * 负数从栈顶倒数,还有 {@code LUA_REGISTRYINDEX} 和 upvalue 两个伪索引。
+ */
+final class Api {
+
+ private Api() {
+ }
+
+ static RuntimeException unimplemented(String what) {
+ return new UnsupportedOperationException("C API 未实现: " + what);
+ }
+
+'''
+
+if __name__ == "__main__":
+ main()
diff --git a/native/lua/lauxlib.h b/native/lua/lauxlib.h
new file mode 100644
index 0000000..5b977e2
--- /dev/null
+++ b/native/lua/lauxlib.h
@@ -0,0 +1,301 @@
+/*
+** $Id: lauxlib.h $
+** Auxiliary functions for building Lua libraries
+** See Copyright Notice in lua.h
+*/
+
+
+#ifndef lauxlib_h
+#define lauxlib_h
+
+
+#include
+ * {@code slot_ptr(i)} 交出去的是 {@code base + (i-1)%CHUNK*STRIDE + STRIDE}——
+ * 那个 {@code + STRIDE} 是给 LUA_EXTRASPACE 留的({@code lua_getextraspace}
+ * 是 {@code (char*)L - LUA_EXTRASPACE},越界一格就是野指针)。于是第
+ * {@code SLOT_CHUNK} 号句柄(每块的最后一格)落在 {@code base + CHUNK*STRIDE},
+ * 正好在块尾之外:{@code slot_idx} 认不出它,交回 0,C 侧就拿到 NULL 的
+ * lua_State*。
+ *
+ * 实测:跑到第 256 个句柄时 {@code luaopen_nat} 收到 NULL,紧接着
+ * {@code luaL_newmetatable(NULL, ...)} 在 Java 侧 NPE。主线程上只有 79 条
+ * 用例时句柄用不到 256,一直全绿;把语料塞进协程再跑一遍(158 条)就撞上了。
+ */
+#define SLOT_BYTES ((SLOT_CHUNK + 1) * SLOT_STRIDE)
+
+static char *g_chunks[SLOT_MAXCHUNK];
+static int g_nchunks = 0;
+static pthread_mutex_t g_slotlock = PTHREAD_MUTEX_INITIALIZER;
+
+static char *slot_ptr(jlong idx) {
+ char *base;
+ if (idx <= 0) {
+ return NULL;
+ }
+ jlong c = (idx - 1) / SLOT_CHUNK;
+ jlong o = (idx - 1) % SLOT_CHUNK;
+ if (c >= SLOT_MAXCHUNK) {
+ return NULL;
+ }
+ pthread_mutex_lock(&g_slotlock);
+ while (c >= g_nchunks) {
+ g_chunks[g_nchunks++] = (char *)calloc(SLOT_BYTES, 1);
+ }
+ base = g_chunks[c];
+ pthread_mutex_unlock(&g_slotlock);
+ return base + o * SLOT_STRIDE + SLOT_STRIDE;
+}
+
+static jlong slot_idx(void *L) {
+ if (L == NULL) {
+ return 0;
+ }
+ for (int c = 0; c < g_nchunks; c++) {
+ char *b = g_chunks[c];
+ if ((char *)L < b || (char *)L >= b + SLOT_BYTES) {
+ continue;
+ }
+ jlong off = (char *)L - b;
+ return c * SLOT_CHUNK + off / SLOT_STRIDE; /* 1 起 */
+ }
+ return 0;
+}
+
+#define LUASHIM_HANDLE(L) ((jlong)slot_idx((void *)(L)))
+#define LUASHIM_STATE(i) ((lua_State *)slot_ptr(i))
+
+/* ==================== 字符串回传 ==================== */
+/*
+ * lua_tostring 要交一个真的 const char* 出去。真 Lua 返回的是串内部地址(存活
+ * 到 GC),我们只能给一块环形缓冲——C 代码跨好几个 lua_* 调用攥着这个
+ * 指针就会读到后面的串。真要攥住,得自己 strdup。
+ */
+#define STR_RING 32
+static _Thread_local char *g_str[STR_RING];
+static _Thread_local size_t g_strcap[STR_RING];
+static _Thread_local int g_stri = 0;
+
+static char *ring_put(const char *b, size_t n) {
+ int i = g_stri;
+ g_stri = (g_stri + 1) % STR_RING;
+ if (g_strcap[i] < n + 1) {
+ free(g_str[i]);
+ g_strcap[i] = (n + 1 < 64) ? 64 : n + 1;
+ g_str[i] = (char *)malloc(g_strcap[i]);
+ }
+ memcpy(g_str[i], b, n);
+ g_str[i][n] = '\0';
+ return g_str[i];
+}
+
+/* ==================== JNI 入口 ==================== */
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+ (void)reserved;
+ g_vm = vm;
+ return JNI_VERSION_1_8;
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_init(JNIEnv *env, jclass cls) {
+ g_cls = (*env)->NewGlobalRef(env, cls);
+ g_dispatch = (*env)->GetStaticMethodID(env, cls, "dispatch",
+ "(IJJJJJJD)J");
+ g_dispatchD = (*env)->GetStaticMethodID(env, cls, "dispatchD",
+ "(IJJJJJJD)D");
+ {
+ jclass y = (*env)->FindClass(env,
+ "com/github/esrrhs/javalua/LuaYield");
+ if (y != NULL) {
+ g_clsYield = (*env)->NewGlobalRef(env, y);
+ (*env)->DeleteLocalRef(env, y);
+ }
+ }
+}
+
+/**
+ * 「刚捕获的 Java 异常是不是 yield」。
+ *
+ * C 里 {@code luaD_throw(L, LUA_YIELD)} 和抛错走的是同一条 longjmp,靠
+ * {@code g_yielding} 区分。但本实现多一种情形:yield 是被调函数在 Java 侧
+ * 抛出来的({@code coroutine.yield} 经由 {@code lua_callk} 一路冒上来),
+ * C 函数里那一句 {@code lua_yieldk} 压根没执行到,{@code g_yielding} 就是 0,
+ * 旧代码会把它当成 error——{@code coroutine.wrap} 里 C 函数调用 Lua 回调再
+ * yield 的场景必错。所以出口处再问一次 Java。
+ */
+static int pending_is_yield(void) {
+ JNIEnv *env;
+ int r = 0;
+ if (g_pending == NULL || g_clsYield == NULL) {
+ return 0;
+ }
+ env = env_now();
+ if (env != NULL) {
+ r = (*env)->IsInstanceOf(env, g_pending, g_clsYield) ? 1 : 0;
+ }
+ return r;
+}
+
+JNIEXPORT jobject JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_takePending(JNIEnv *env,
+ jclass cls) {
+ jobject r = NULL;
+ (void)cls;
+ /* 交出去的是局部引用,全局引用当场删——不然每次报错都漏一个 */
+ if (g_pending != NULL) {
+ r = (*env)->NewLocalRef(env, g_pending);
+ (*env)->DeleteGlobalRef(env, g_pending);
+ g_pending = NULL;
+ }
+ return r;
+}
+
+/*
+ * 把警告消息交给 {@code lua_WarnFunction}。
+ *
+ * 消息是一次性的:回调同步跑完就返回,所以给一块栈上的缓冲足够;
+ * 超长才落到堆上。
+ */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_invokeWarn(JNIEnv *env,
+ jclass cls, jlong fn, jlong ud, jbyteArray b, jboolean tocont) {
+ char small[512];
+ char *buf = small;
+ jsize n;
+ jbyte *tmp;
+ void (*f)(void *, const char *, int);
+ (void)cls;
+ if (fn == 0 || b == NULL) {
+ return;
+ }
+ n = (*env)->GetArrayLength(env, b);
+ if (n < 0) {
+ return;
+ }
+ if ((size_t)n + 1 > sizeof(small)) {
+ buf = (char *)malloc((size_t)n + 1);
+ if (buf == NULL) {
+ return;
+ }
+ }
+ tmp = (*env)->GetByteArrayElements(env, b, NULL);
+ if (tmp == NULL) {
+ if (buf != small) {
+ free(buf);
+ }
+ return;
+ }
+ memcpy(buf, tmp, (size_t)n);
+ buf[n] = '\0';
+ (*env)->ReleaseByteArrayElements(env, b, tmp, JNI_ABORT);
+ f = (void (*)(void *, const char *, int))(intptr_t)fn;
+ f((void *)(intptr_t)ud, buf, tocont ? 1 : 0);
+ if (buf != small) {
+ free(buf);
+ }
+}
+
+/** 把本库提升进全局命名空间:System.load 用的是 RTLD_LOCAL,不提升扩展就找不到 */
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dlopen(JNIEnv *env,
+ jclass cls, jstring path, jboolean global) {
+ (void)cls;
+ const char *p = (*env)->GetStringUTFChars(env, path, NULL);
+ void *h = dlopen(p, RTLD_NOW | (global ? RTLD_GLOBAL : RTLD_LOCAL));
+ (*env)->ReleaseStringUTFChars(env, path, p);
+ return (jlong)(intptr_t)h;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dlsym(JNIEnv *env,
+ jclass cls, jlong h, jstring name) {
+ (void)cls;
+ const char *n = (*env)->GetStringUTFChars(env, name, NULL);
+ void *s = dlsym((void *)(intptr_t)h, n);
+ (*env)->ReleaseStringUTFChars(env, name, n);
+ return (jlong)(intptr_t)s;
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dlerror(JNIEnv *env,
+ jclass cls) {
+ (void)cls;
+ const char *e = dlerror();
+ return e == NULL ? NULL : (*env)->NewStringUTF(env, e);
+}
+
+/*
+ * 调一个 lua_CFunction。setjmp 就设在这里——整个 C 侧唯一的落点。
+ * 返回:>=0 是结果个数;-1 是 longjmp(lua_error);-2 是 yield。
+ */
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_invokeC(JNIEnv *env,
+ jclass cls, jlong fn, jlong L) {
+ (void)cls;
+ if (fn == 0) {
+ return -3;
+ }
+ jmp_buf buf;
+ jmp_buf *prev = g_jmp;
+ volatile int r;
+ g_yielding = 0;
+ g_jmp = &buf;
+ if (setjmp(buf) == 0) {
+ int (*f)(void *) = (int (*)(void *))(intptr_t)fn;
+ r = f(LUASHIM_STATE(L));
+ } else {
+ r = (g_yielding || pending_is_yield()) ? -2 : -1;
+ }
+ g_jmp = prev;
+ g_yielding = 0;
+ return (jint)r;
+}
+
+/*
+ * 调一个 lua_KFunction:{@code int (*)(lua_State *L, int status, lua_KContext ctx)}。
+ *
+ * 续体是 resume 之后才跑的,所以它也必须有自己的落点——它里面照样可以
+ * 再 {@code lua_yieldk}、照样可以抛错。返回码与 {@code invokeC} 同义。
+ */
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_invokeK(JNIEnv *env,
+ jclass cls, jlong fn, jlong L, jint status, jlong ctx) {
+ (void)cls;
+ if (fn == 0) {
+ return -3;
+ }
+ jmp_buf buf;
+ jmp_buf *prev = g_jmp;
+ volatile int r;
+ g_yielding = 0;
+ g_jmp = &buf;
+ if (setjmp(buf) == 0) {
+ int (*f)(void *, int, long) = (int (*)(void *, int, long))(intptr_t)fn;
+ r = f(LUASHIM_STATE(L), (int)status, (long)ctx);
+ } else {
+ r = (g_yielding || pending_is_yield()) ? -2 : -1;
+ }
+ g_jmp = prev;
+ g_yielding = 0;
+ return (jint)r;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_alloc(JNIEnv *env,
+ jclass cls, jlong n) {
+ (void)env;
+ (void)cls;
+ return (jlong)(intptr_t)calloc(1, (size_t)(n > 0 ? n : 1));
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_free(JNIEnv *env,
+ jclass cls, jlong p) {
+ (void)env;
+ (void)cls;
+ free((void *)(intptr_t)p);
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_bytesAt(JNIEnv *env,
+ jclass cls, jlong p) {
+ (void)cls;
+ if (p == 0) {
+ return NULL;
+ }
+ size_t n = strlen((const char *)(intptr_t)p);
+ jbyteArray a = (*env)->NewByteArray(env, (jsize)n);
+ if (a != NULL && n > 0) {
+ (*env)->SetByteArrayRegion(env, a, 0, (jsize)n, (const jbyte *)(intptr_t)p);
+ }
+ return a;
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_bytesAtN(JNIEnv *env,
+ jclass cls, jlong p, jint n) {
+ (void)cls;
+ if (p == 0 || n < 0) {
+ return NULL;
+ }
+ jbyteArray a = (*env)->NewByteArray(env, n);
+ if (a != NULL && n > 0) {
+ (*env)->SetByteArrayRegion(env, a, 0, n, (const jbyte *)(intptr_t)p);
+ }
+ return a;
+}
+
+/** 拷进环形缓冲,返回 NUL 结尾的 C 串地址 */
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_strToNative(JNIEnv *env,
+ jclass cls, jbyteArray b) {
+ (void)cls;
+ if (b == NULL) {
+ return 0;
+ }
+ jsize n = (*env)->GetArrayLength(env, b);
+ jbyte *tmp = (*env)->GetByteArrayElements(env, b, NULL);
+ if (tmp == NULL) {
+ return 0;
+ }
+ char *p = ring_put((const char *)tmp, (size_t)n);
+ (*env)->ReleaseByteArrayElements(env, b, tmp, JNI_ABORT);
+ return (jlong)(intptr_t)p;
+}
+
+/**
+ * 往 {@code alloc} 出来的那块里写字节,末尾补一个 NUL。
+ * 给 {@link LuaString#nativePtr} 用:那边要的是「随字符串活到死」的地址,
+ * 不能用环形缓冲(见 ring_put 的注释)。
+ */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_putBytes(JNIEnv *env,
+ jclass cls, jlong p, jbyteArray b) {
+ (void)cls;
+ if (p == 0 || b == NULL) {
+ return;
+ }
+ jsize n = (*env)->GetArrayLength(env, b);
+ jbyte *tmp = (*env)->GetByteArrayElements(env, b, NULL);
+ if (tmp == NULL) {
+ return;
+ }
+ memcpy((void *)(intptr_t)p, tmp, (size_t)n);
+ ((char *)(intptr_t)p)[n] = '\0';
+ (*env)->ReleaseByteArrayElements(env, b, tmp, JNI_ABORT);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_put64(JNIEnv *env,
+ jclass cls, jlong p, jlong v) {
+ (void)env;
+ (void)cls;
+ if (p != 0) {
+ *((jlong *)(intptr_t)p) = v;
+ }
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_get64(JNIEnv *env,
+ jclass cls, jlong p) {
+ (void)env;
+ (void)cls;
+ return p == 0 ? 0 : *((jlong *)(intptr_t)p);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_put32(JNIEnv *env,
+ jclass cls, jlong p, jint v) {
+ (void)env;
+ (void)cls;
+ if (p != 0) {
+ *((jint *)(intptr_t)p) = v;
+ }
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_get32(JNIEnv *env,
+ jclass cls, jlong p) {
+ (void)env;
+ (void)cls;
+ return p == 0 ? 0 : *((jint *)(intptr_t)p);
+}
+
+/** {@code sizeof(lua_Debug)}:Java 侧要给 C 的钩子分配一块,尺寸不能猜 */
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dbgSize(JNIEnv *env,
+ jclass cls) {
+ (void)env;
+ (void)cls;
+ return (jlong)sizeof(lua_Debug);
+}
+
+/*
+ * 句柄换回 {@code lua_State*}。{@code lua_dump} 的 writer 第一个参数是
+ * {@code lua_State*},而 Java 侧手里只有下标——交 0 过去,那些会拿
+ * {@code L} 去调别的 {@code lua_*} 的 writer 就当场崩了。
+ */
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_statePtr(JNIEnv *env,
+ jclass cls, jlong idx) {
+ (void)env;
+ (void)cls;
+ return (jlong)(intptr_t)slot_ptr(idx);
+}
+
+/** 回调任意 C 函数指针:reader / writer / hook / alloc / 续体都走这一条 */
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_callPtr(JNIEnv *env,
+ jclass cls, jlong fn, jlong a0, jlong a1, jlong a2, jlong a3) {
+ (void)env;
+ (void)cls;
+ if (fn == 0) {
+ return 0;
+ }
+ jlong (*f)(jlong, jlong, jlong, jlong) =
+ (jlong (*)(jlong, jlong, jlong, jlong))(intptr_t)fn;
+ return f(a0, a1, a2, a3);
+}
+
+/* ==================== 手写的那批符号 ==================== */
+
+const char lua_ident[] =
+ "$LuaVersion: Lua 5.4.7 Copyright (C) 1994-2024 Lua.org, PUC-Rio $"
+ "$LuaAuthors: R. Ierusalimschy, L. H. de Figueiredo, W. Celes $";
+
+/*
+ * luaO_pushvfstring 的忠实移植。
+ *
+ * 为什么不能拿 vsnprintf 糊过去:lobject.c 里它只认 s c d I f p U % 八个
+ * 动词,其余一律 luaG_runerror("invalid option '%%%c' to 'lua_pushfstring'");
+ * 而且 %d/%I/%f 走的是 addnum2buff(= luaO_tostr,%.14g,整数样的补 .0),
+ * 不是 printf 的 %d/%f。早先这里直接 vsnprintf,三处全错:
+ * lua_pushfstring(L, "%f", 1.5) 我们 1.500000 / 参考 1.5
+ * lua_pushfstring(L, "%x", 42) 我们 2a / 参考报错
+ * lua_pushfstring(L, "%5d", 42) 我们 " 42" / 参考报错
+ * 而 luaL_error 就是 luaL_where + lua_pushvfstring,所以扩展里任何一条
+ * 带 %f 或带精度的 luaL_error 都会露出来。
+ */
+
+/* lua_Integer 是 long long,于是 l_uacInt 也是;LP64 之外的平台要改这里 */
+typedef long long shim_uacInt;
+
+typedef struct {
+ char *b;
+ size_t n;
+ size_t cap;
+ char small[256];
+} FSBuf;
+
+static void fs_add(FSBuf *f, const char *s, size_t l) {
+ if (f->n + l + 1 > f->cap) {
+ size_t cap = f->cap;
+ while (cap < f->n + l + 1) {
+ cap *= 2;
+ }
+ char *nb = (char *) malloc(cap);
+ if (nb == NULL) {
+ return; /* 认了:参考会抛 "not enough memory",这里截断 */
+ }
+ memcpy(nb, f->b, f->n);
+ if (f->b != f->small) {
+ free(f->b);
+ }
+ f->b = nb;
+ f->cap = cap;
+ }
+ memcpy(f->b + f->n, s, l);
+ f->n += l;
+ f->b[f->n] = '\0';
+}
+
+/*
+ * 数字必须经 Lua 自己的 tostring,不能 snprintf:%.14g 之后给整数样的结果
+ * 补 .0(参考 lua_pushfstring(L,"%f",503.0) 给 "503.0"),而 printf 的 %f
+ * 给 "503.000000"。所以这里推到栈上再 lua_tolstring 取回来。
+ */
+static void fs_addnum(lua_State *L, FSBuf *f, int isint,
+ long long iv, double dv) {
+ size_t len = 0;
+ const char *s;
+ luaL_checkstack(L, 1, NULL);
+ if (isint) {
+ lua_pushinteger(L, (lua_Integer) iv);
+ } else {
+ lua_pushnumber(L, (lua_Number) dv);
+ }
+ s = lua_tolstring(L, -1, &len);
+ if (s != NULL) {
+ fs_add(f, s, len);
+ }
+ lua_pop(L, 1);
+}
+
+/** lobject.c: luaO_utf8esc —— %U 把码点编成 UTF-8,倒着写进 buff 尾部 */
+static int shim_utf8esc(char *buff, unsigned long x) {
+ int n = 1;
+ if (x < 0x80) {
+ buff[8 - 1] = (char) x;
+ } else {
+ unsigned int mfb = 0x3f;
+ do {
+ buff[8 - (n++)] = (char) (0x80 | (x & 0x3f));
+ x >>= 6;
+ mfb >>= 1;
+ } while (x > mfb);
+ buff[8 - n] = (char) ((~mfb << 1) & x);
+ }
+ return n;
+}
+
+static const char *pushvfshim(lua_State *L, const char *fmt, va_list ap) {
+ FSBuf f;
+ const char *e;
+ f.b = f.small;
+ f.n = 0;
+ f.cap = sizeof f.small;
+ f.small[0] = '\0';
+ while ((e = strchr(fmt, '%')) != NULL) {
+ fs_add(&f, fmt, (size_t) (e - fmt));
+ switch (*(e + 1)) {
+ case 's': {
+ const char *s = va_arg(ap, char *);
+ if (s == NULL) {
+ s = "(null)";
+ }
+ fs_add(&f, s, strlen(s));
+ break;
+ }
+ case 'c': {
+ char c = (char) (unsigned char) va_arg(ap, int);
+ fs_add(&f, &c, 1);
+ break;
+ }
+ case 'd':
+ fs_addnum(L, &f, 1, (long long) va_arg(ap, int), 0.0);
+ break;
+ case 'I':
+ fs_addnum(L, &f, 1, (long long) va_arg(ap, shim_uacInt), 0.0);
+ break;
+ case 'f':
+ fs_addnum(L, &f, 0, 0, (double) va_arg(ap, double));
+ break;
+ case 'p': {
+ char pb[32];
+ void *p = va_arg(ap, void *);
+ int l = snprintf(pb, sizeof pb, "%p", p);
+ if (l > 0) {
+ fs_add(&f, pb, (size_t) l);
+ }
+ break;
+ }
+ case 'U': {
+ char ub[8];
+ int l = shim_utf8esc(ub, (unsigned long) va_arg(ap, long));
+ fs_add(&f, ub + 8 - l, (size_t) l);
+ break;
+ }
+ case '%':
+ fs_add(&f, "%", 1);
+ break;
+ default: {
+ /*
+ * 参考是 luaG_runerror:从 C 帧抛,不带位置前缀
+ * (isLua(ci) 为假就不 addinfo)。所以这里用 lua_error
+ * 而不是 luaL_error——后者会先拼 luaL_where。
+ */
+ char msg[64];
+ int l = snprintf(msg, sizeof msg,
+ "invalid option '%%%c' to 'lua_pushfstring'", *(e + 1));
+ if (l < 0) {
+ l = 0;
+ }
+ if (f.b != f.small) {
+ free(f.b);
+ }
+ lua_pushlstring(L, msg, (size_t) l);
+ lua_error(L); /* 不返回 */
+ return NULL;
+ }
+ }
+ fmt = e + 2;
+ }
+ fs_add(&f, fmt, strlen(fmt));
+ lua_pushlstring(L, f.b, f.n);
+ {
+ char *r = ring_put(f.b, f.n);
+ if (f.b != f.small) {
+ free(f.b);
+ }
+ return r;
+ }
+}
+
+LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
+ va_list argp) {
+ return pushvfshim(L, fmt, argp);
+}
+
+LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...) {
+ const char *r;
+ va_list ap;
+ va_start(ap, fmt);
+ r = pushvfshim(L, fmt, ap);
+ va_end(ap);
+ return r;
+}
+
+LUA_API int (lua_gc) (lua_State *L, int what, ...) {
+ /* 只有这四个 what 带变参,乱读 va_arg 会拿到垃圾 */
+ jlong e0 = 0, e1 = 0, e2 = 0;
+ va_list ap;
+ va_start(ap, what);
+ switch (what) {
+ case LUA_GCSTEP:
+ case LUA_GCSETPAUSE:
+ case LUA_GCSETSTEPMUL:
+ e0 = va_arg(ap, int);
+ break;
+ case LUA_GCGEN:
+ e0 = va_arg(ap, int);
+ e1 = va_arg(ap, int);
+ break;
+ case LUA_GCINC:
+ e0 = va_arg(ap, int);
+ e1 = va_arg(ap, int);
+ e2 = va_arg(ap, int);
+ break;
+ default:
+ break;
+ }
+ va_end(ap);
+ return (int)luashim_call(ID_lua_gc, LUASHIM_HANDLE(L), (jint)what, e0, e1, e2, 0, 0.0);
+}
+
+/*
+ * lauxlib.c 原文四行,一行都不许省:
+ * luaL_where(L, 1); lua_pushvfstring(L, fmt, ap);
+ * lua_concat(L, 2); return lua_error(L);
+ * 变参只能在 C 侧读;pushvfstring 的动词表是 Lua 自己的(见 pushvfshim);
+ * lua_error 走 luashim_throw 的 longjmp,而那个落点只在本文件的最外层 C 帧。
+ */
+LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ luaL_where(L, 1);
+ lua_pushvfstring(L, fmt, ap);
+ va_end(ap);
+ lua_concat(L, 2);
+ lua_error(L); /* 不返回 */
+ return 0;
+}
+
+/*
+ * luaL_fileresult / luaL_execresult:errno 是每个线程一份的 C 变量,
+ * Java 读不到,只有在 C 侧先取出来再作为参数传进去。lauxlib.c 的第一句就是
+ * `int en = errno;`(注释写着 "calls to Lua API may change this value"),
+ * 顺序照抄。
+ */
+LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname) {
+ int en = errno;
+ return (int) luashim_call(ID_luaL_fileresult, LUASHIM_HANDLE(L),
+ (jint) stat, (intptr_t) fname, (jint) en, 0, 0, 0.0);
+}
+
+LUALIB_API int (luaL_execresult) (lua_State *L, int stat) {
+ static const char exitw[] = "exit";
+ static const char signalw[] = "signal";
+ const char *what = exitw;
+ int en = errno;
+ if (stat != 0 && en != 0) {
+ return luaL_fileresult(L, 0, NULL);
+ }
+ /*
+ * lauxlib.c 的 l_inspectstat。用本平台的 wait 宏,别自己造位运算:
+ * 实测(macOS)execresult(-1) 给 "exit",-1 而不是 "signal",127 ——
+ * 因为 BSD 的 WIFSIGNALED 排除了 _W_STOPPED(0177),而 (x & 0x7f) 恰好
+ * 是 0177。照 Linux 那套位运算写就会错在这一条上。
+ */
+ if (WIFEXITED(stat)) {
+ stat = WEXITSTATUS(stat);
+ } else if (WIFSIGNALED(stat)) {
+ stat = WTERMSIG(stat);
+ what = signalw;
+ }
+ return (int) luashim_call(ID_luaL_execresult, LUASHIM_HANDLE(L),
+ (jint) stat, (jint) en, (intptr_t) what, 0, 0, 0.0);
+}
+
+/* luaL_Reg 数组在 C 侧,Java 读不到:照 lauxlib.c 逐个注册 */
+LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup) {
+ static const char noslots[] = "not enough slots";
+ luashim_call(ID_luaL_checkstack, LUASHIM_HANDLE(L), (jint)nup,
+ (intptr_t)noslots, 0, 0, 0, 0.0);
+ for (; l->name != NULL; l++) {
+ int i;
+ if (l->func == NULL) {
+ /* lauxlib.c 的「占位」分支:压一个 false,不开闭包 */
+ luashim_call(ID_lua_pushboolean, LUASHIM_HANDLE(L), (jint)0,
+ 0, 0, 0, 0, 0.0);
+ } else {
+ for (i = 0; i < nup; i++) {
+ luashim_call(ID_lua_pushvalue, LUASHIM_HANDLE(L), (jint)(-nup), 0, 0, 0, 0, 0.0);
+ }
+ luashim_call(ID_lua_pushcclosure, LUASHIM_HANDLE(L),
+ (intptr_t)(intptr_t)l->func, (jint)nup, 0, 0, 0, 0.0);
+ }
+ luashim_call(ID_lua_setfield, LUASHIM_HANDLE(L), (jint)(-nup - 2),
+ (intptr_t)l->name, 0, 0, 0, 0.0);
+ }
+ luashim_call(ID_lua_settop, LUASHIM_HANDLE(L), (jint)(-nup - 1), 0, 0, 0, 0, 0.0);
+}
+
+/* const char *const lst[]:打包成 "a\0b\0c\0" 连长度一起交给 Java */
+/* 整个照 lauxlib.c:buffinit + addgsub + pushresult 全在 C 侧 */
+LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s,
+ const char *p, const char *r) {
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ luaL_addgsub(&b, s, p, r);
+ luaL_pushresult(&b);
+ return lua_tostring(L, -1);
+}
+
+/* ---- lua_load / luaL_loadfilex:reader 是 C 函数指针,只能在 C 侧喂 ---- */
+/*
+ * 读函数得在 C 侧调:它是 {@code const char *(*)(lua_State*, void*, size_t*)},
+ * 出参 {@code size_t*} 要写 C 内存。若让 Java 去调,不但得凭空造一块
+ * {@code size_t} 的落脚点,还要让 longjmp 跨过一层 JNI 帧——跨不得。
+ * 于是这里把源整段读进一块缓冲再交给 Java 的
+ * {@code luaL_loadbufferx}。
+ *
+ * 「整段读」与 C 版的「边读边编」在这里等价:{@code luaD_protectedparser}
+ * 无论如何都要把流读到 EOF 才返回结果,读函数的调用次序也完全一致。
+ */
+LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt,
+ const char *chunkname, const char *mode) {
+ size_t cap = 1024;
+ size_t n = 0;
+ char *buf;
+ int r;
+ if (reader == NULL) {
+ return LUA_ERRSYNTAX;
+ }
+ buf = (char *)malloc(cap);
+ if (buf == NULL) {
+ return LUA_ERRMEM;
+ }
+ for (;;) {
+ size_t sz = 0;
+ const char *p = reader(L, dt, &sz);
+ if (p == NULL || sz == 0) {
+ break; /* EOF:读函数返回 NULL 或长度 0 都算结束 */
+ }
+ while (n + sz > cap) {
+ char *nb;
+ cap *= 2;
+ nb = (char *)realloc(buf, cap);
+ if (nb == NULL) {
+ free(buf);
+ return LUA_ERRMEM;
+ }
+ buf = nb;
+ }
+ memcpy(buf + n, p, sz);
+ n += sz;
+ }
+ r = (int)luashim_call(ID_luaL_loadbufferx, LUASHIM_HANDLE(L),
+ (intptr_t)buf, (jlong)n, (intptr_t)chunkname, (intptr_t)mode, 0, 0.0);
+ free(buf);
+ return r;
+}
+
+/* 下面四个是 lauxlib.c 里 luaL_loadfilex 的零件,照抄——BOM、'#' 首行、
+ * 二进制重开这三件事都在这里,少一件都会让某个真实文件加载出错 */
+typedef struct LoadF {
+ int n; /* 预读的字符数 */
+ FILE *f; /* 正在读的文件 */
+ char buff[BUFSIZ]; /* 读块缓冲 */
+} LoadF;
+
+static const char *getF (lua_State *L, void *ud, size_t *size) {
+ LoadF *lf = (LoadF *)ud;
+ (void)L;
+ if (lf->n > 0) { /* 有预读的字符? */
+ *size = lf->n; /* 先交出去 */
+ lf->n = 0;
+ }
+ else { /* 从文件读一块 */
+ if (feof(lf->f)) {
+ return NULL; /* 见 lauxlib 的注释:避免多等一次输入 */
+ }
+ *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);
+ }
+ return lf->buff;
+}
+
+static int skipBOM (FILE *f) {
+ int c = getc(f);
+ if (c == 0xEF && getc(f) == 0xBB && getc(f) == 0xBF) {
+ return getc(f);
+ }
+ return c;
+}
+
+static int skipcomment (FILE *f, int *cp) {
+ int c = *cp = skipBOM(f);
+ if (c == '#') { /* 首行是注释(Unix 可执行脚本)? */
+ do {
+ c = getc(f);
+ } while (c != EOF && c != '\n');
+ *cp = getc(f); /* 注释之后那个字符 */
+ return 1;
+ }
+ return 0;
+}
+
+static int errfile (lua_State *L, const char *what, int fnameindex) {
+ int err = errno;
+ const char *filename = lua_tostring(L, fnameindex) + 1;
+ if (err != 0) {
+ lua_pushfstring(L, "cannot %s %s: %s", what, filename, strerror(err));
+ }
+ else {
+ lua_pushfstring(L, "cannot %s %s", what, filename);
+ }
+ lua_remove(L, fnameindex);
+ return LUA_ERRFILE;
+}
+
+LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,
+ const char *mode) {
+ LoadF lf;
+ int status, readstatus;
+ int c;
+ int fnameindex = lua_gettop(L) + 1; /* 文件名在栈上的位置 */
+ if (filename == NULL) {
+ lua_pushliteral(L, "=stdin");
+ lf.f = stdin;
+ }
+ else {
+ lua_pushfstring(L, "@%s", filename);
+ errno = 0;
+ lf.f = fopen(filename, "r");
+ if (lf.f == NULL) {
+ return errfile(L, "open", fnameindex);
+ }
+ }
+ lf.n = 0;
+ if (skipcomment(lf.f, &c)) { /* 读开头那一小段 */
+ lf.buff[lf.n++] = '\n'; /* 补一个换行,好让行号不错位 */
+ }
+ if (c == LUA_SIGNATURE[0]) { /* 二进制文件? */
+ lf.n = 0; /* 可能补进去的换行去掉 */
+ if (filename) { /* 真文件才需要以二进制重开 */
+ errno = 0;
+ lf.f = freopen(filename, "rb", lf.f);
+ if (lf.f == NULL) {
+ return errfile(L, "reopen", fnameindex);
+ }
+ skipcomment(lf.f, &c); /* 重新读开头 */
+ }
+ }
+ if (c != EOF) {
+ lf.buff[lf.n++] = (char)c; /* 第一个有效字符 */
+ }
+ errno = 0;
+ status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode);
+ readstatus = ferror(lf.f);
+ if (filename) {
+ fclose(lf.f); /* 出错也要关 */
+ }
+ if (readstatus) {
+ lua_settop(L, fnameindex); /* 丢掉 lua_load 的结果 */
+ return errfile(L, "read", fnameindex);
+ }
+ lua_remove(L, fnameindex);
+ return status;
+}
+
+LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,
+ const char *const lst[]) {
+ size_t total = 0;
+ int n = 0;
+ int r;
+ char *packed;
+ char *w;
+ int i;
+ for (i = 0; lst[i] != NULL; i++) {
+ total += strlen(lst[i]) + 1;
+ n++;
+ }
+ packed = (char *)malloc(total + 1);
+ if (packed == NULL) {
+ return 0;
+ }
+ w = packed;
+ for (i = 0; i < n; i++) {
+ size_t k = strlen(lst[i]);
+ memcpy(w, lst[i], k + 1);
+ w += k + 1;
+ }
+ *w = '\0';
+ r = (int)luashim_call(ID_luaL_checkoption, LUASHIM_HANDLE(L), (jint)arg,
+ (intptr_t)def, (intptr_t)packed, (jint)n, (jlong)total, 0.0);
+ free(packed);
+ return r;
+}
+
+/* ---- luaL_Buffer:b/size/n 是 C 侧的三个字段,Java 改不了,只能在 C 里长 ---- */
+
+static void buffgrow(luaL_Buffer *B, size_t extra) {
+ size_t want = B->n + extra + 1;
+ size_t ns = B->size < 64 ? 64 : B->size;
+ char *nb;
+ while (ns < want) {
+ ns *= 2;
+ }
+ if (B->b == B->init.b) {
+ nb = (char *)malloc(ns);
+ if (nb == NULL) {
+ return;
+ }
+ memcpy(nb, B->b, B->n);
+ } else {
+ nb = (char *)realloc(B->b, ns);
+ if (nb == NULL) {
+ return;
+ }
+ }
+ B->b = nb;
+ B->size = ns;
+}
+
+static void buffreset(luaL_Buffer *B) {
+ if (B->b != B->init.b) {
+ free(B->b);
+ }
+ B->b = B->init.b;
+ B->size = LUAL_BUFFERSIZE;
+ B->n = 0;
+}
+
+LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B) {
+ B->L = L;
+ B->b = B->init.b;
+ B->size = LUAL_BUFFERSIZE;
+ B->n = 0;
+}
+
+LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz) {
+ luaL_buffinit(L, B);
+ return luaL_prepbuffsize(B, sz);
+}
+
+LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz) {
+ if (B->size - B->n < sz) {
+ buffgrow(B, sz);
+ }
+ return B->b + B->n;
+}
+
+LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l) {
+ char *d = luaL_prepbuffsize(B, l);
+ if (d != NULL) {
+ memcpy(d, s, l);
+ B->n += l;
+ }
+}
+
+LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s) {
+ luaL_addlstring(B, s, strlen(s));
+}
+
+/*
+ * 照 lauxlib.c:lua_tolstring 取栈顶那个值的字节,追加进缓冲,再弹掉。
+ * 整个在 C 侧做完——Java 侧拿不到 luaL_Buffer 的三个字段(b、size、n),
+ * 硬要在 Java 里做就得按固定 offset 去改 C 栈上的结构体,那比这里多一整层
+ * 脆弱的偏移假设。
+ *
+ * 用的是 lua_tolstring 而不是 luaL_tolstring:**不认 __tostring**。给表调它
+ * 会追加空(lua_tolstring 返回 NULL、长度 0),与参考一致——别"顺手"改成认
+ * 元方法,那会把 table 变成 "table: 0x..." 写进结果串。
+ */
+LUALIB_API void (luaL_addvalue) (luaL_Buffer *B) {
+ lua_State *L = B->L;
+ size_t len = 0;
+ const char *s = lua_tolstring(L, -1, &len);
+ if (s != NULL) {
+ luaL_addlstring(B, s, len);
+ }
+ lua_pop(L, 1);
+}
+
+LUALIB_API void (luaL_pushresult) (luaL_Buffer *B) {
+ luashim_call(ID_lua_pushlstring, LUASHIM_HANDLE(B->L),
+ (intptr_t)B->b, (jlong)B->n, 0, 0, 0, 0.0);
+ buffreset(B);
+}
+
+LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz) {
+ B->n += sz;
+ luaL_pushresult(B);
+}
+
+/** 给 Java 用:往 luaL_Buffer 里追加字节 */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_bufAppend(JNIEnv *env,
+ jclass cls, jlong bp, jbyteArray b) {
+ (void)cls;
+ luaL_Buffer *B = (luaL_Buffer *)(intptr_t)bp;
+ jsize n;
+ jbyte *tmp;
+ if (bp == 0 || b == NULL) {
+ return;
+ }
+ n = (*env)->GetArrayLength(env, b);
+ tmp = (*env)->GetByteArrayElements(env, b, NULL);
+ if (tmp == NULL) {
+ return;
+ }
+ luaL_addlstring(B, (const char *)tmp, (size_t)n);
+ (*env)->ReleaseByteArrayElements(env, b, tmp, JNI_ABORT);
+}
+
+/*
+ * 分配器 —— {@code lua_getallocf} / {@code lua_setallocf}。
+ *
+ * 必须在 C 侧就地实现:{@code lua_getallocf} 交出去的是能被直接调用的 C 函数
+ * 指针,扩展拿它就是为了自己管内存({@code lpeg} 的 {@code lpcode.c} 有三处
+ * {@code f(ud, ptr, osize, nsize)}),Java 交不出这种东西。
+ *
+ * 语义照 {@code lauxlib.c} 的 {@code l_alloc}:{@code nsize == 0} 就是释放。
+ *
+ * 这两个是进程级而不是 {@code global_State} 级的(真 Lua 是后者)。差得
+ * 远的场景基本不存在——一个进程里同时跑好几个 Lua 状态、还各自换分配器。
+ */
+static void *luashim_allocf(void *ud, void *ptr, size_t osize, size_t nsize) {
+ (void)ud;
+ (void)osize;
+ if (nsize == 0) {
+ free(ptr);
+ return NULL;
+ }
+ if (ptr == NULL) {
+ return malloc(nsize);
+ }
+ return realloc(ptr, nsize);
+}
+
+static lua_Alloc g_allocf = luashim_allocf;
+static void *g_allocud = NULL;
+
+/*
+ * {@code lua_newstate} 的分配器参数在 C 侧接住:Java 那头只管起状态,而
+ * {@code g->frealloc} / {@code g->ud} 得真的换掉,否则 {@code lua_getallocf}
+ * 交回去的还是默认那个,调用方换分配器的效果就丢了。
+ */
+LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud) {
+ jlong h = luashim_call(ID_lua_newstate, (intptr_t)f, (intptr_t)ud,
+ 0, 0, 0, 0, 0.0);
+ if (h == 0) {
+ return NULL;
+ }
+ g_allocf = (f != NULL) ? f : luashim_allocf;
+ g_allocud = ud;
+ return LUASHIM_STATE(h);
+}
+
+LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud) {
+ (void)L;
+ if (ud != NULL) {
+ *ud = g_allocud;
+ }
+ return g_allocf;
+}
+
+LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud) {
+ (void)L;
+ g_allocf = (f != NULL) ? f : luashim_allocf;
+ g_allocud = ud;
+}
+
+/*
+ * 默认 panic 函数,照 lauxlib.c 那个 {@code panic} 抄。
+ *
+ * 存在的意义是初值:{@code lua_newstate} 里 {@code g->panic = NULL},
+ * 但 {@code luaL_newstate} 紧接着 {@code lua_atpanic(L, &panic)} —— 所以从
+ * Lua 解释器里看,{@code lua_atpanic} 第一次返回的不是 NULL(实测探针
+ * 第一条 {@code old == NULL} 给 false)。
+ */
+static int luashim_panic(lua_State *L) {
+ const char *msg = (lua_type(L, -1) == LUA_TSTRING)
+ ? lua_tostring(L, -1)
+ : "error object is not a string";
+ fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", msg);
+ return 0;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_defaultPanic(JNIEnv *env,
+ jclass cls) {
+ (void)env;
+ (void)cls;
+ return (jlong)(intptr_t)luashim_panic;
+}
+
+/*
+ * yield 与 error 走同一条 longjmp,只是出口标记不同——invokeC 据此返回 -2,
+ * Java 侧再抛 LuaYield。这里不能用生成的 stub:生成版会正常返回,C 函数就继续
+ * 往下跑了,那不是 yield 的语义。
+ */
+LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx,
+ lua_KFunction k) {
+ luashim_call(ID_lua_yieldk, LUASHIM_HANDLE(L), (jint)nresults,
+ (jlong)ctx, (intptr_t)k, 0, 0, 0.0);
+ g_yielding = 1;
+ luashim_throw();
+ return 0;
+}
+
+/*
+ * {@code lua_resume} 的 {@code nres} 是出参({@code int *}),而 Java 那头
+ * 只能回一个 long。所以打包:高 32 位是 status,低 32 位是该从协程栈上搬走的
+ * 结果个数。
+ */
+LUA_API int (lua_resume) (lua_State *L, lua_State *from, int nargs,
+ int *nres) {
+ jlong r = luashim_call(ID_lua_resume, LUASHIM_HANDLE(L),
+ LUASHIM_HANDLE(from), (jint)nargs, 0, 0, 0, 0.0);
+ int status = (int)(r >> 32);
+ if (nres != NULL) {
+ *nres = (int)(r & 0xffffffffL);
+ }
+ return status;
+}
+
+/* ---- lua_Debug:C 侧的 struct,Java 按固定下标给值,C 按 offsetof 落字段 ---- */
+
+#define DBG_N 15
+static const struct {
+ size_t off;
+ size_t size;
+} DBG_FIELD[DBG_N] = {
+ {offsetof(lua_Debug, event), sizeof(int)},
+ {offsetof(lua_Debug, name), sizeof(const char *)},
+ {offsetof(lua_Debug, namewhat), sizeof(const char *)},
+ {offsetof(lua_Debug, what), sizeof(const char *)},
+ {offsetof(lua_Debug, source), sizeof(const char *)},
+ {offsetof(lua_Debug, srclen), sizeof(size_t)},
+ {offsetof(lua_Debug, currentline), sizeof(int)},
+ {offsetof(lua_Debug, linedefined), sizeof(int)},
+ {offsetof(lua_Debug, lastlinedefined), sizeof(int)},
+ {offsetof(lua_Debug, nups), sizeof(unsigned char)},
+ {offsetof(lua_Debug, nparams), sizeof(unsigned char)},
+ {offsetof(lua_Debug, isvararg), sizeof(char)},
+ {offsetof(lua_Debug, istailcall), sizeof(char)},
+ {offsetof(lua_Debug, ftransfer), sizeof(unsigned short)},
+ {offsetof(lua_Debug, ntransfer), sizeof(unsigned short)},
+};
+
+/** 把 Java 给的 DBG_N 个值按各自宽度写进 lua_Debug */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dbgFill(JNIEnv *env,
+ jclass cls, jlong arp, jlongArray vals) {
+ (void)cls;
+ char *ar = (char *)(intptr_t)arp;
+ jlong *v;
+ int i;
+ if (arp == 0 || vals == NULL) {
+ return;
+ }
+ if ((*env)->GetArrayLength(env, vals) < DBG_N) {
+ return;
+ }
+ v = (*env)->GetLongArrayElements(env, vals, NULL);
+ if (v == NULL) {
+ return;
+ }
+ for (i = 0; i < DBG_N; i++) {
+ void *dst = ar + DBG_FIELD[i].off;
+ switch (DBG_FIELD[i].size) {
+ case 1: *((char *)dst) = (char)v[i]; break;
+ case 2: *((short *)dst) = (short)v[i]; break;
+ case 4: *((int *)dst) = (int)v[i]; break;
+ default: *((long *)dst) = (long)v[i]; break;
+ }
+ }
+ (*env)->ReleaseLongArrayElements(env, vals, v, JNI_ABORT);
+}
+
+/** short_src 是定长数组,单独拷 */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_dbgShortSrc(JNIEnv *env,
+ jclass cls, jlong arp, jbyteArray b) {
+ (void)cls;
+ char *ar = (char *)(intptr_t)arp;
+ jsize n;
+ jbyte *tmp;
+ char *dst = ar + offsetof(lua_Debug, short_src);
+ if (arp == 0 || b == NULL) {
+ return;
+ }
+ n = (*env)->GetArrayLength(env, b);
+ tmp = (*env)->GetByteArrayElements(env, b, NULL);
+ if (tmp == NULL) {
+ return;
+ }
+ if (n > LUA_IDSIZE - 1) {
+ n = LUA_IDSIZE - 1;
+ }
+ memcpy(dst, tmp, (size_t)n);
+ dst[n] = '\0';
+ (*env)->ReleaseByteArrayElements(env, b, tmp, JNI_ABORT);
+}
+
+/* ==================== C FILE* 操作 ==================== */
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFopen(JNIEnv *env,
+ jclass cls, jbyteArray path, jbyteArray mode) {
+ (void)cls;
+ jsize pathLen = (*env)->GetArrayLength(env, path);
+ jsize modeLen = (*env)->GetArrayLength(env, mode);
+ /* Java byte arrays 没有 NUL 结尾,必须手动加 */
+ char *pPath = (char *)malloc((size_t)pathLen + 1);
+ char *pMode = (char *)malloc((size_t)modeLen + 1);
+ if (pPath == NULL || pMode == NULL) {
+ free(pPath);
+ free(pMode);
+ return 0;
+ }
+ (*env)->GetByteArrayRegion(env, path, 0, pathLen, (jbyte *)pPath);
+ pPath[pathLen] = '\0';
+ (*env)->GetByteArrayRegion(env, mode, 0, modeLen, (jbyte *)pMode);
+ pMode[modeLen] = '\0';
+ FILE *f = fopen(pPath, pMode);
+ free(pPath);
+ free(pMode);
+ return (jlong)(intptr_t)f;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFclose(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ if (f == 0) return -1;
+ return fclose((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFread(JNIEnv *env,
+ jclass cls, jlong f, jbyteArray buf, jint off, jint len) {
+ (void)cls;
+ jbyte *tmp = (*env)->GetByteArrayElements(env, buf, NULL);
+ size_t n = fread(tmp + off, 1, (size_t)len, (FILE *)(intptr_t)f);
+ (*env)->ReleaseByteArrayElements(env, buf, tmp, 0);
+ return (jint)n;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFwrite(JNIEnv *env,
+ jclass cls, jlong f, jbyteArray buf, jint off, jint len) {
+ (void)cls;
+ jbyte *tmp = (*env)->GetByteArrayElements(env, buf, NULL);
+ size_t n = fwrite(tmp + off, 1, (size_t)len, (FILE *)(intptr_t)f);
+ (*env)->ReleaseByteArrayElements(env, buf, tmp, JNI_ABORT);
+ return (jint)n;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFseek(JNIEnv *env,
+ jclass cls, jlong f, jlong offset, jint whence) {
+ (void)env; (void)cls;
+ return fseeko((FILE *)(intptr_t)f, (off_t)offset, whence);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFtell(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return (jlong)ftello((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFflush(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return fflush((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFerror(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return ferror((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cClearerr(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ clearerr((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFeof(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return feof((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFgets(JNIEnv *env,
+ jclass cls, jlong f, jint maxLen) {
+ (void)cls;
+ char *buf = (char *)malloc((size_t)maxLen + 1);
+ if (buf == NULL) return NULL;
+ char *r = fgets(buf, maxLen + 1, (FILE *)(intptr_t)f);
+ if (r == NULL) { free(buf); return NULL; }
+ jsize n = (jsize)strlen(r);
+ jbyteArray a = (*env)->NewByteArray(env, n);
+ if (a != NULL && n > 0)
+ (*env)->SetByteArrayRegion(env, a, 0, n, (const jbyte *)r);
+ free(buf);
+ return a;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFgetc(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return fgetc((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFputc(JNIEnv *env,
+ jclass cls, jlong f, jint c) {
+ (void)env; (void)cls;
+ return fputc(c, (FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cPopen(JNIEnv *env,
+ jclass cls, jbyteArray cmd, jbyteArray mode) {
+ (void)cls;
+ jsize cmdLen = (*env)->GetArrayLength(env, cmd);
+ jsize modeLen = (*env)->GetArrayLength(env, mode);
+ char *pCmd = (char *)malloc((size_t)cmdLen + 1);
+ char *pMode = (char *)malloc((size_t)modeLen + 1);
+ if (pCmd == NULL || pMode == NULL) {
+ free(pCmd);
+ free(pMode);
+ return 0;
+ }
+ (*env)->GetByteArrayRegion(env, cmd, 0, cmdLen, (jbyte *)pCmd);
+ pCmd[cmdLen] = '\0';
+ (*env)->GetByteArrayRegion(env, mode, 0, modeLen, (jbyte *)pMode);
+ pMode[modeLen] = '\0';
+ fflush(NULL);
+ FILE *f = popen(pCmd, pMode);
+ free(pCmd);
+ free(pMode);
+ return (jlong)(intptr_t)f;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cPclose(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return pclose((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cFileno(JNIEnv *env,
+ jclass cls, jlong f) {
+ (void)env; (void)cls;
+ return fileno((FILE *)(intptr_t)f);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cSizeofLuaLStream(JNIEnv *env,
+ jclass cls) {
+ (void)env; (void)cls;
+ return (jlong)(sizeof(FILE *) + sizeof(void (*)(void)));
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cNewLuaLStream(JNIEnv *env,
+ jclass cls, jlong filePtr, jlong closefPtr) {
+ (void)env; (void)cls;
+ void **p = (void **)calloc(2, sizeof(void *));
+ if (p == NULL) return 0;
+ p[0] = (void *)(intptr_t)filePtr;
+ p[1] = (void *)(intptr_t)closefPtr;
+ return (jlong)(intptr_t)p;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cStreamGetF(JNIEnv *env,
+ jclass cls, jlong streamPtr) {
+ (void)env; (void)cls;
+ if (streamPtr == 0) return 0;
+ void **p = (void **)(intptr_t)streamPtr;
+ return (jlong)(intptr_t)p[0];
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cStdStream(JNIEnv *env,
+ jclass cls, jint which) {
+ (void)env; (void)cls;
+ switch (which) {
+ case 0: return (jlong)(intptr_t)stdin;
+ case 1: return (jlong)(intptr_t)stdout;
+ case 2: return (jlong)(intptr_t)stderr;
+ default: return 0;
+ }
+}
+
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cGetErrno(JNIEnv *env,
+ jclass cls) {
+ (void)env; (void)cls;
+ return (jint)errno;
+}
+
+/*
+ * {@code setlocale(category, locale)} —— 直接调 C 的 setlocale。
+ *
+ * {@code locale} 为 {@code NULL} 时只查询不设置;返回的字符串是 C 运行时的
+ * 内部状态,不能 free。Java 侧要把字节拷走({@code NewStringUTF} 或
+ * {@code GetByteArrayRegion}),不能直接存指针。
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cSetlocale(JNIEnv *env,
+ jclass cls, jint category, jbyteArray locale) {
+ (void)cls;
+ const char *loc = NULL;
+ char *locBuf = NULL;
+ if (locale != NULL) {
+ jsize len = (*env)->GetArrayLength(env, locale);
+ locBuf = (char *)malloc((size_t)len + 1);
+ if (locBuf == NULL) return NULL;
+ (*env)->GetByteArrayRegion(env, locale, 0, len, (jbyte *)locBuf);
+ locBuf[len] = '\0';
+ loc = locBuf;
+ }
+ char *result = setlocale((int)category, loc);
+ free(locBuf);
+ if (result == NULL) return NULL;
+ /*
+ * 把结果拷回 Java byte[]:{@code NewStringUTF} 走 MUTF-8,locale 名字里
+ * 可能有非 ASCII(如 {@code zh_CN.UTF-8}),但实测 C 的 setlocale 返回的都是
+ * ASCII,这里用字节数组更稳妥,Java 侧按 UTF-8 解。
+ */
+ jsize rlen = (jsize)strlen(result);
+ jbyteArray arr = (*env)->NewByteArray(env, rlen);
+ if (arr == NULL) return NULL;
+ (*env)->SetByteArrayRegion(env, arr, 0, rlen, (jbyte *)result);
+ return arr;
+}
+
+/*
+ * {@code waitpid(pid, &status, 0)} —— 拿原始退出状态,区分 exit/signal。
+ *
+ * Java 的 {@code Process.waitFor()} 只给退出码,信号杀死的进程一律按 exit 1 报。
+ * C 的 {@code waitpid} 把状态编码在 {@code status} 里:{@code WIFEXITED(status)}
+ * 判正常退出,{@code WEXITSTATUS(status)} 取退出码;{@code WIFSIGNALED(status)}
+ * 判信号杀死,{@code WTERMSIG(status)} 取信号编号。Lua 的 {@code os.execute}
+ * 走的就是这条路({@code loslib.c: os_execute} → {@code luaL_execresult})。
+ */
+JNIEXPORT jint JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cWaitpid(JNIEnv *env,
+ jclass cls, jlong pid, jlongArray statusOut) {
+ (void)cls;
+ int status = 0;
+ pid_t r = waitpid((pid_t)pid, &status, 0);
+ if (r < 0) return -1;
+ if (statusOut != NULL) {
+ jlong s = (jlong)status;
+ (*env)->SetLongArrayRegion(env, statusOut, 0, 1, &s);
+ }
+ return 0;
+}
+
+/*
+ * {@code WIFEXITED(status)} / {@code WEXITSTATUS(status)} /
+ * {@code WIFSIGNALED(status)} / {@code WTERMSIG(status)} —— 把 waitpid 拿到的
+ * 原始 status 解码成 Java 能直接用的四个字段。
+ *
+ * 宏的定义平台相关(macOS 和 Linux 的位布局不一样),不能直接在 Java 里算,
+ * 所以走 JNI 调 C 的宏。返回值打包成一个 {@code long[4]}:
+ * {@code [wifexit, wexit, wifsig, wtermsig]}。
+ */
+JNIEXPORT void JNICALL
+Java_com_github_esrrhs_javalua_NativeBridge_cDecodeStatus(JNIEnv *env,
+ jclass cls, jint status, jlongArray out) {
+ (void)cls;
+ jlong vals[4];
+ vals[0] = WIFEXITED(status) ? 1 : 0;
+ vals[1] = WEXITSTATUS(status);
+ vals[2] = WIFSIGNALED(status) ? 1 : 0;
+ vals[3] = WTERMSIG(status);
+ (*env)->SetLongArrayRegion(env, out, 0, 4, vals);
+}
+
+/* ==================== 生成的 stub ==================== */
+
+#include "luashim_stubs.inc"
diff --git a/native/luashim_ids.h b/native/luashim_ids.h
new file mode 100644
index 0000000..51e9126
--- /dev/null
+++ b/native/luashim_ids.h
@@ -0,0 +1,162 @@
+/* 自动生成,勿手改:由 native/gen_capi.py 产出 */
+#ifndef LUASHIM_IDS_H
+#define LUASHIM_IDS_H
+/* 与 CApi.java 的 switch 一一对应,两边都由同一个脚本产出 */
+enum {
+ ID_luaL_addgsub = 0,
+ ID_luaL_addlstring = 1,
+ ID_luaL_addstring = 2,
+ ID_luaL_addvalue = 3,
+ ID_luaL_argerror = 4,
+ ID_luaL_buffinit = 5,
+ ID_luaL_buffinitsize = 6,
+ ID_luaL_callmeta = 7,
+ ID_luaL_checkany = 8,
+ ID_luaL_checkinteger = 9,
+ ID_luaL_checklstring = 10,
+ ID_luaL_checknumber = 11,
+ ID_luaL_checkoption = 12,
+ ID_luaL_checkstack = 13,
+ ID_luaL_checktype = 14,
+ ID_luaL_checkudata = 15,
+ ID_luaL_checkversion_ = 16,
+ ID_luaL_error = 17,
+ ID_luaL_execresult = 18,
+ ID_luaL_fileresult = 19,
+ ID_luaL_getmetafield = 20,
+ ID_luaL_getsubtable = 21,
+ ID_luaL_gsub = 22,
+ ID_luaL_len = 23,
+ ID_luaL_loadbufferx = 24,
+ ID_luaL_loadfilex = 25,
+ ID_luaL_loadstring = 26,
+ ID_luaL_newmetatable = 27,
+ ID_luaL_newstate = 28,
+ ID_luaL_optinteger = 29,
+ ID_luaL_optlstring = 30,
+ ID_luaL_optnumber = 31,
+ ID_luaL_prepbuffsize = 32,
+ ID_luaL_pushresult = 33,
+ ID_luaL_pushresultsize = 34,
+ ID_luaL_ref = 35,
+ ID_luaL_requiref = 36,
+ ID_luaL_setfuncs = 37,
+ ID_luaL_setmetatable = 38,
+ ID_luaL_testudata = 39,
+ ID_luaL_tolstring = 40,
+ ID_luaL_traceback = 41,
+ ID_luaL_typeerror = 42,
+ ID_luaL_unref = 43,
+ ID_luaL_where = 44,
+ ID_lua_absindex = 45,
+ ID_lua_arith = 46,
+ ID_lua_atpanic = 47,
+ ID_lua_callk = 48,
+ ID_lua_checkstack = 49,
+ ID_lua_close = 50,
+ ID_lua_closeslot = 51,
+ ID_lua_closethread = 52,
+ ID_lua_compare = 53,
+ ID_lua_concat = 54,
+ ID_lua_copy = 55,
+ ID_lua_createtable = 56,
+ ID_lua_dump = 57,
+ ID_lua_error = 58,
+ ID_lua_gc = 59,
+ ID_lua_getallocf = 60,
+ ID_lua_getfield = 61,
+ ID_lua_getglobal = 62,
+ ID_lua_gethook = 63,
+ ID_lua_gethookcount = 64,
+ ID_lua_gethookmask = 65,
+ ID_lua_geti = 66,
+ ID_lua_getinfo = 67,
+ ID_lua_getiuservalue = 68,
+ ID_lua_getlocal = 69,
+ ID_lua_getmetatable = 70,
+ ID_lua_getstack = 71,
+ ID_lua_gettable = 72,
+ ID_lua_gettop = 73,
+ ID_lua_getupvalue = 74,
+ ID_lua_iscfunction = 75,
+ ID_lua_isinteger = 76,
+ ID_lua_isnumber = 77,
+ ID_lua_isstring = 78,
+ ID_lua_isuserdata = 79,
+ ID_lua_isyieldable = 80,
+ ID_lua_len = 81,
+ ID_lua_load = 82,
+ ID_lua_newstate = 83,
+ ID_lua_newthread = 84,
+ ID_lua_newuserdatauv = 85,
+ ID_lua_next = 86,
+ ID_lua_pcallk = 87,
+ ID_lua_pushboolean = 88,
+ ID_lua_pushcclosure = 89,
+ ID_lua_pushfstring = 90,
+ ID_lua_pushinteger = 91,
+ ID_lua_pushlightuserdata = 92,
+ ID_lua_pushlstring = 93,
+ ID_lua_pushnil = 94,
+ ID_lua_pushnumber = 95,
+ ID_lua_pushstring = 96,
+ ID_lua_pushthread = 97,
+ ID_lua_pushvalue = 98,
+ ID_lua_pushvfstring = 99,
+ ID_lua_rawequal = 100,
+ ID_lua_rawget = 101,
+ ID_lua_rawgeti = 102,
+ ID_lua_rawgetp = 103,
+ ID_lua_rawlen = 104,
+ ID_lua_rawset = 105,
+ ID_lua_rawseti = 106,
+ ID_lua_rawsetp = 107,
+ ID_lua_resetthread = 108,
+ ID_lua_resume = 109,
+ ID_lua_rotate = 110,
+ ID_lua_setallocf = 111,
+ ID_lua_setcstacklimit = 112,
+ ID_lua_setfield = 113,
+ ID_lua_setglobal = 114,
+ ID_lua_sethook = 115,
+ ID_lua_seti = 116,
+ ID_lua_setiuservalue = 117,
+ ID_lua_setlocal = 118,
+ ID_lua_setmetatable = 119,
+ ID_lua_settable = 120,
+ ID_lua_settop = 121,
+ ID_lua_setupvalue = 122,
+ ID_lua_setwarnf = 123,
+ ID_lua_status = 124,
+ ID_lua_stringtonumber = 125,
+ ID_lua_toboolean = 126,
+ ID_lua_tocfunction = 127,
+ ID_lua_toclose = 128,
+ ID_lua_tointegerx = 129,
+ ID_lua_tolstring = 130,
+ ID_lua_tonumberx = 131,
+ ID_lua_topointer = 132,
+ ID_lua_tothread = 133,
+ ID_lua_touserdata = 134,
+ ID_lua_type = 135,
+ ID_lua_typename = 136,
+ ID_lua_upvalueid = 137,
+ ID_lua_upvaluejoin = 138,
+ ID_lua_version = 139,
+ ID_lua_warning = 140,
+ ID_lua_xmove = 141,
+ ID_lua_yieldk = 142,
+ ID_luaL_openlibs = 143,
+ ID_luaopen_base = 144,
+ ID_luaopen_coroutine = 145,
+ ID_luaopen_debug = 146,
+ ID_luaopen_io = 147,
+ ID_luaopen_math = 148,
+ ID_luaopen_os = 149,
+ ID_luaopen_package = 150,
+ ID_luaopen_string = 151,
+ ID_luaopen_table = 152,
+ ID_luaopen_utf8 = 153,
+ LUASHIM_ID_COUNT = 154
+};
+#endif
diff --git a/native/luashim_stubs.inc b/native/luashim_stubs.inc
new file mode 100644
index 0000000..a8adabc
--- /dev/null
+++ b/native/luashim_stubs.inc
@@ -0,0 +1,658 @@
+/* 自动生成,勿手改:由 native/gen_capi.py 产出 */
+/* 手写符号(变参/结构体遍历/数据符号)在 luashim.c 里 */
+
+/* lauxlib.h */
+LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s, const char *p, const char *r) {
+ luashim_call(ID_luaL_addgsub, (intptr_t)b, (intptr_t)s, (intptr_t)p, (intptr_t)r, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg) {
+ return (int)luashim_call(ID_luaL_argerror, LUASHIM_HANDLE(L), (jint)arg, (intptr_t)extramsg, 0, 0, 0, 0);
+ luashim_throw();
+ return 0;
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e) {
+ return (int)luashim_call(ID_luaL_callmeta, LUASHIM_HANDLE(L), (jint)obj, (intptr_t)e, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_checkany) (lua_State *L, int arg) {
+ luashim_call(ID_luaL_checkany, LUASHIM_HANDLE(L), (jint)arg, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg) {
+ return (lua_Integer)luashim_call(ID_luaL_checkinteger, LUASHIM_HANDLE(L), (jint)arg, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API const char * (luaL_checklstring) (lua_State *L, int arg, size_t *l) {
+ return (const char *)(intptr_t)luashim_call(ID_luaL_checklstring, LUASHIM_HANDLE(L), (jint)arg, (intptr_t)l, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg) {
+ return (lua_Number)luashim_callD(ID_luaL_checknumber, LUASHIM_HANDLE(L), (jint)arg, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg) {
+ luashim_call(ID_luaL_checkstack, LUASHIM_HANDLE(L), (jint)sz, (intptr_t)msg, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t) {
+ luashim_call(ID_luaL_checktype, LUASHIM_HANDLE(L), (jint)arg, (jint)t, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void * (luaL_checkudata) (lua_State *L, int ud, const char *tname) {
+ return (void *)(intptr_t)luashim_call(ID_luaL_checkudata, LUASHIM_HANDLE(L), (jint)ud, (intptr_t)tname, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz) {
+ luashim_call(ID_luaL_checkversion_, LUASHIM_HANDLE(L), (jlong)sz, 0, 0, 0, 0, (jdouble)ver);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e) {
+ return (int)luashim_call(ID_luaL_getmetafield, LUASHIM_HANDLE(L), (jint)obj, (intptr_t)e, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname) {
+ return (int)luashim_call(ID_luaL_getsubtable, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)fname, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx) {
+ return (lua_Integer)luashim_call(ID_luaL_len, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode) {
+ return (int)luashim_call(ID_luaL_loadbufferx, LUASHIM_HANDLE(L), (intptr_t)buff, (jlong)sz, (intptr_t)name, (intptr_t)mode, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s) {
+ return (int)luashim_call(ID_luaL_loadstring, LUASHIM_HANDLE(L), (intptr_t)s, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname) {
+ return (int)luashim_call(ID_luaL_newmetatable, LUASHIM_HANDLE(L), (intptr_t)tname, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API lua_State * (luaL_newstate) (void) {
+ return LUASHIM_STATE(luashim_call(ID_luaL_newstate, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* lauxlib.h */
+LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, lua_Integer def) {
+ return (lua_Integer)luashim_call(ID_luaL_optinteger, LUASHIM_HANDLE(L), (jint)arg, (jlong)def, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API const char * (luaL_optlstring) (lua_State *L, int arg, const char *def, size_t *l) {
+ return (const char *)(intptr_t)luashim_call(ID_luaL_optlstring, LUASHIM_HANDLE(L), (jint)arg, (intptr_t)def, (intptr_t)l, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def) {
+ return (lua_Number)luashim_callD(ID_luaL_optnumber, LUASHIM_HANDLE(L), (jint)arg, 0, 0, 0, 0, (jdouble)def);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_ref) (lua_State *L, int t) {
+ return (int)luashim_call(ID_luaL_ref, LUASHIM_HANDLE(L), (jint)t, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, lua_CFunction openf, int glb) {
+ luashim_call(ID_luaL_requiref, LUASHIM_HANDLE(L), (intptr_t)modname, (intptr_t)openf, (jint)glb, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname) {
+ luashim_call(ID_luaL_setmetatable, LUASHIM_HANDLE(L), (intptr_t)tname, 0, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void * (luaL_testudata) (lua_State *L, int ud, const char *tname) {
+ return (void *)(intptr_t)luashim_call(ID_luaL_testudata, LUASHIM_HANDLE(L), (jint)ud, (intptr_t)tname, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API const char * (luaL_tolstring) (lua_State *L, int idx, size_t *len) {
+ return (const char *)(intptr_t)luashim_call(ID_luaL_tolstring, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)len, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, const char *msg, int level) {
+ luashim_call(ID_luaL_traceback, LUASHIM_HANDLE(L), LUASHIM_HANDLE(L1), (intptr_t)msg, (jint)level, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname) {
+ return (int)luashim_call(ID_luaL_typeerror, LUASHIM_HANDLE(L), (jint)arg, (intptr_t)tname, 0, 0, 0, 0);
+ luashim_throw();
+ return 0;
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref) {
+ luashim_call(ID_luaL_unref, LUASHIM_HANDLE(L), (jint)t, (jint)ref, 0, 0, 0, 0);
+}
+
+/* lauxlib.h */
+LUALIB_API void (luaL_where) (lua_State *L, int lvl) {
+ luashim_call(ID_luaL_where, LUASHIM_HANDLE(L), (jint)lvl, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_absindex) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_absindex, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_arith) (lua_State *L, int op) {
+ luashim_call(ID_lua_arith, LUASHIM_HANDLE(L), (jint)op, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf) {
+ return (lua_CFunction)(intptr_t)luashim_call(ID_lua_atpanic, LUASHIM_HANDLE(L), (intptr_t)panicf, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k) {
+ luashim_call(ID_lua_callk, LUASHIM_HANDLE(L), (jint)nargs, (jint)nresults, (jlong)ctx, (intptr_t)k, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_checkstack) (lua_State *L, int n) {
+ return (int)luashim_call(ID_lua_checkstack, LUASHIM_HANDLE(L), (jint)n, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_close) (lua_State *L) {
+ luashim_call(ID_lua_close, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_closeslot) (lua_State *L, int idx) {
+ luashim_call(ID_lua_closeslot, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_closethread) (lua_State *L, lua_State *from) {
+ return (int)luashim_call(ID_lua_closethread, LUASHIM_HANDLE(L), LUASHIM_HANDLE(from), 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op) {
+ return (int)luashim_call(ID_lua_compare, LUASHIM_HANDLE(L), (jint)idx1, (jint)idx2, (jint)op, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_concat) (lua_State *L, int n) {
+ luashim_call(ID_lua_concat, LUASHIM_HANDLE(L), (jint)n, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx) {
+ luashim_call(ID_lua_copy, LUASHIM_HANDLE(L), (jint)fromidx, (jint)toidx, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec) {
+ luashim_call(ID_lua_createtable, LUASHIM_HANDLE(L), (jint)narr, (jint)nrec, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip) {
+ return (int)luashim_call(ID_lua_dump, LUASHIM_HANDLE(L), (intptr_t)writer, (intptr_t)data, (jint)strip, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_error) (lua_State *L) {
+ return (int)luashim_call(ID_lua_error, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+ luashim_throw();
+ return 0;
+}
+
+/* lua.h */
+LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k) {
+ return (int)luashim_call(ID_lua_getfield, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)k, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_getglobal) (lua_State *L, const char *name) {
+ return (int)luashim_call(ID_lua_getglobal, LUASHIM_HANDLE(L), (intptr_t)name, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_Hook (lua_gethook) (lua_State *L) {
+ return (lua_Hook)(intptr_t)luashim_call(ID_lua_gethook, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_gethookcount) (lua_State *L) {
+ return (int)luashim_call(ID_lua_gethookcount, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_gethookmask) (lua_State *L) {
+ return (int)luashim_call(ID_lua_gethookmask, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n) {
+ return (int)luashim_call(ID_lua_geti, LUASHIM_HANDLE(L), (jint)idx, (jlong)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar) {
+ return (int)luashim_call(ID_lua_getinfo, LUASHIM_HANDLE(L), (intptr_t)what, (intptr_t)ar, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n) {
+ return (int)luashim_call(ID_lua_getiuservalue, LUASHIM_HANDLE(L), (jint)idx, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_getlocal) (lua_State *L, const lua_Debug *ar, int n) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_getlocal, LUASHIM_HANDLE(L), (intptr_t)ar, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_getmetatable) (lua_State *L, int objindex) {
+ return (int)luashim_call(ID_lua_getmetatable, LUASHIM_HANDLE(L), (jint)objindex, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar) {
+ return (int)luashim_call(ID_lua_getstack, LUASHIM_HANDLE(L), (jint)level, (intptr_t)ar, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_gettable) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_gettable, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_gettop) (lua_State *L) {
+ return (int)luashim_call(ID_lua_gettop, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_getupvalue) (lua_State *L, int funcindex, int n) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_getupvalue, LUASHIM_HANDLE(L), (jint)funcindex, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_iscfunction) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_iscfunction, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_isinteger) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_isinteger, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_isnumber) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_isnumber, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_isstring) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_isstring, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_isuserdata) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_isuserdata, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_isyieldable) (lua_State *L) {
+ return (int)luashim_call(ID_lua_isyieldable, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_len) (lua_State *L, int idx) {
+ luashim_call(ID_lua_len, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_State * (lua_newthread) (lua_State *L) {
+ return LUASHIM_STATE(luashim_call(ID_lua_newthread, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0));
+}
+
+/* lua.h */
+LUA_API void * (lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue) {
+ return (void *)(intptr_t)luashim_call(ID_lua_newuserdatauv, LUASHIM_HANDLE(L), (jlong)sz, (jint)nuvalue, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_next) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_next, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k) {
+ return (int)luashim_call(ID_lua_pcallk, LUASHIM_HANDLE(L), (jint)nargs, (jint)nresults, (jint)errfunc, (jlong)ctx, (intptr_t)k, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushboolean) (lua_State *L, int b) {
+ luashim_call(ID_lua_pushboolean, LUASHIM_HANDLE(L), (jint)b, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n) {
+ luashim_call(ID_lua_pushcclosure, LUASHIM_HANDLE(L), (intptr_t)fn, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n) {
+ luashim_call(ID_lua_pushinteger, LUASHIM_HANDLE(L), (jlong)n, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p) {
+ luashim_call(ID_lua_pushlightuserdata, LUASHIM_HANDLE(L), (intptr_t)p, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_pushlstring) (lua_State *L, const char *s, size_t len) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_pushlstring, LUASHIM_HANDLE(L), (intptr_t)s, (jlong)len, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushnil) (lua_State *L) {
+ luashim_call(ID_lua_pushnil, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n) {
+ luashim_call(ID_lua_pushnumber, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, (jdouble)n);
+}
+
+/* lua.h */
+LUA_API const char * (lua_pushstring) (lua_State *L, const char *s) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_pushstring, LUASHIM_HANDLE(L), (intptr_t)s, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_pushthread) (lua_State *L) {
+ return (int)luashim_call(ID_lua_pushthread, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_pushvalue) (lua_State *L, int idx) {
+ luashim_call(ID_lua_pushvalue, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2) {
+ return (int)luashim_call(ID_lua_rawequal, LUASHIM_HANDLE(L), (jint)idx1, (jint)idx2, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_rawget) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_rawget, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n) {
+ return (int)luashim_call(ID_lua_rawgeti, LUASHIM_HANDLE(L), (jint)idx, (jlong)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p) {
+ return (int)luashim_call(ID_lua_rawgetp, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)p, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_Unsigned (lua_rawlen) (lua_State *L, int idx) {
+ return (lua_Unsigned)luashim_call(ID_lua_rawlen, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_rawset) (lua_State *L, int idx) {
+ luashim_call(ID_lua_rawset, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n) {
+ luashim_call(ID_lua_rawseti, LUASHIM_HANDLE(L), (jint)idx, (jlong)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p) {
+ luashim_call(ID_lua_rawsetp, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)p, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_resetthread) (lua_State *L) {
+ return (int)luashim_call(ID_lua_resetthread, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_rotate) (lua_State *L, int idx, int n) {
+ luashim_call(ID_lua_rotate, LUASHIM_HANDLE(L), (jint)idx, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_setcstacklimit) (lua_State *L, unsigned int limit) {
+ return (int)luashim_call(ID_lua_setcstacklimit, LUASHIM_HANDLE(L), (jint)limit, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k) {
+ luashim_call(ID_lua_setfield, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)k, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_setglobal) (lua_State *L, const char *name) {
+ luashim_call(ID_lua_setglobal, LUASHIM_HANDLE(L), (intptr_t)name, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count) {
+ luashim_call(ID_lua_sethook, LUASHIM_HANDLE(L), (intptr_t)func, (jint)mask, (jint)count, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n) {
+ luashim_call(ID_lua_seti, LUASHIM_HANDLE(L), (jint)idx, (jlong)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_setiuservalue) (lua_State *L, int idx, int n) {
+ return (int)luashim_call(ID_lua_setiuservalue, LUASHIM_HANDLE(L), (jint)idx, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_setlocal) (lua_State *L, const lua_Debug *ar, int n) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_setlocal, LUASHIM_HANDLE(L), (intptr_t)ar, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_setmetatable) (lua_State *L, int objindex) {
+ return (int)luashim_call(ID_lua_setmetatable, LUASHIM_HANDLE(L), (jint)objindex, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_settable) (lua_State *L, int idx) {
+ luashim_call(ID_lua_settable, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_settop) (lua_State *L, int idx) {
+ luashim_call(ID_lua_settop, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_setupvalue) (lua_State *L, int funcindex, int n) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_setupvalue, LUASHIM_HANDLE(L), (jint)funcindex, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud) {
+ luashim_call(ID_lua_setwarnf, LUASHIM_HANDLE(L), (intptr_t)f, (intptr_t)ud, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_status) (lua_State *L) {
+ return (int)luashim_call(ID_lua_status, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s) {
+ return (size_t)luashim_call(ID_lua_stringtonumber, LUASHIM_HANDLE(L), (intptr_t)s, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_toboolean) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_toboolean, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx) {
+ return (lua_CFunction)(intptr_t)luashim_call(ID_lua_tocfunction, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_toclose) (lua_State *L, int idx) {
+ luashim_call(ID_lua_toclose, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum) {
+ return (lua_Integer)luashim_call(ID_lua_tointegerx, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)isnum, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_tolstring) (lua_State *L, int idx, size_t *len) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_tolstring, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)len, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum) {
+ return (lua_Number)luashim_callD(ID_lua_tonumberx, LUASHIM_HANDLE(L), (jint)idx, (intptr_t)isnum, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const void * (lua_topointer) (lua_State *L, int idx) {
+ return (const void *)(intptr_t)luashim_call(ID_lua_topointer, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_State * (lua_tothread) (lua_State *L, int idx) {
+ return LUASHIM_STATE(luashim_call(ID_lua_tothread, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0));
+}
+
+/* lua.h */
+LUA_API void * (lua_touserdata) (lua_State *L, int idx) {
+ return (void *)(intptr_t)luashim_call(ID_lua_touserdata, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API int (lua_type) (lua_State *L, int idx) {
+ return (int)luashim_call(ID_lua_type, LUASHIM_HANDLE(L), (jint)idx, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API const char * (lua_typename) (lua_State *L, int tp) {
+ return (const char *)(intptr_t)luashim_call(ID_lua_typename, LUASHIM_HANDLE(L), (jint)tp, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void * (lua_upvalueid) (lua_State *L, int fidx, int n) {
+ return (void *)(intptr_t)luashim_call(ID_lua_upvalueid, LUASHIM_HANDLE(L), (jint)fidx, (jint)n, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, int fidx2, int n2) {
+ luashim_call(ID_lua_upvaluejoin, LUASHIM_HANDLE(L), (jint)fidx1, (jint)n1, (jint)fidx2, (jint)n2, 0, 0);
+}
+
+/* lua.h */
+LUA_API lua_Number (lua_version) (lua_State *L) {
+ return (lua_Number)luashim_callD(ID_lua_version, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_warning) (lua_State *L, const char *msg, int tocont) {
+ luashim_call(ID_lua_warning, LUASHIM_HANDLE(L), (intptr_t)msg, (jint)tocont, 0, 0, 0, 0);
+}
+
+/* lua.h */
+LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n) {
+ luashim_call(ID_lua_xmove, LUASHIM_HANDLE(from), LUASHIM_HANDLE(to), (jint)n, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUALIB_API void (luaL_openlibs) (lua_State *L) {
+ luashim_call(ID_luaL_openlibs, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_base) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_base, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_coroutine) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_coroutine, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_debug) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_debug, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_io) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_io, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_math) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_math, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_os) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_os, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_package) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_package, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_string) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_string, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_table) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_table, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
+
+/* lualib.h */
+LUAMOD_API int (luaopen_utf8) (lua_State *L) {
+ return (int)luashim_call(ID_luaopen_utf8, LUASHIM_HANDLE(L), 0, 0, 0, 0, 0, 0);
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fafd530
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,335 @@
+
+
- * fake为上下文环境
- * fake为上下文环境
- * 遍历package下所有类
- * 遍历package下所有类
- * 遍历类下所有函数
- * 如错误处理
- * 解析脚本
- * 解析文本字符串代码
- * 执行指定脚本函数
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static String getcurfile(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_interpreter().get_running_file_name();
- }
- return "nil";
- }
-
- /**
- * 获取当前文件行号
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static int getcurline(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_interpreter().get_running_file_line();
- }
- return 0;
- }
-
- /**
- * 获取当前函数
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static String getcurfunc(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_interpreter().get_running_func_name();
- }
- return "nil";
- }
-
- /**
- * 获取当前调用堆栈
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static String getcurcallstack(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_interpreter().get_running_call_stack();
- }
- return "nil";
- }
-
- /**
- * 获取当前协程信息
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static String getcurroutine(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_routine_info();
- }
- return "nil";
- }
-
- public static int getcurroutineid(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_id();
- }
- return 0;
- }
-
- public static String getcurvaiantbyroutinebyframe(fake f, int rid, int frame, String name, int line)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- warper ret = new warper(new String());
- warper retline = new warper(new Integer(0));
- p.get_routine_by_id(rid).get_interpreter().get_running_vaiant(frame, name, line, ret, retline);
- return (String) ret.d;
- }
- return "";
- }
-
- public static void setcurvaiantbyroutinebyframe(fake f, int rid, int frame, String name, String value, int line)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- p.get_routine_by_id(rid).get_interpreter().set_running_vaiant(frame, name, line, value);
- }
- }
-
- public static String getcurfuncbyroutinebyframe(fake f, int rid, int frame)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- warper stackinfo = new warper(new String());
- warper func = new warper(new String());
- warper file = new warper(new String());
- warper line = new warper(new Integer(0));
- p.get_routine_by_id(rid).get_interpreter().get_running_call_stack_frame_info(frame, stackinfo, func, file,
- line);
- return (String) func.d;
- }
- return "nil";
- }
-
- public static String getcurroutinebyid(fake f, int rid)
- {
- processor p = f.rn.cur_pro();
- if (p != null)
- {
- return p.get_routine_info_by_id(rid);
- }
- return "";
- }
-
- public static int getcurcallstacklength(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_curroutine() != null)
- {
- return p.get_curroutine().get_interpreter().get_running_call_stack_length();
- }
- return 0;
- }
-
- public static String getcurfilebyroutinebyframe(fake f, int rid, int frame)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- warper stackinfo = new warper(new String());
- warper func = new warper(new String());
- warper file = new warper(new String());
- warper line = new warper(new Integer(0));
- p.get_routine_by_id(rid).get_interpreter().get_running_call_stack_frame_info(frame, stackinfo, func, file,
- line);
- return (String) file.d;
- }
- return "nil";
- }
-
- public static int getcurlinebyroutinebyframe(fake f, int rid, int frame)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- warper stackinfo = new warper(new String());
- warper func = new warper(new String());
- warper file = new warper(new String());
- warper line = new warper(new Integer(0));
- p.get_routine_by_id(rid).get_interpreter().get_running_call_stack_frame_info(frame, stackinfo, func, file,
- line);
- return (int) line.d;
- }
- return 0;
- }
-
- public static String getcurcallstackbyroutinebyframe(fake f, int rid, int frame)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- warper stackinfo = new warper(new String());
- warper func = new warper(new String());
- warper file = new warper(new String());
- warper line = new warper(new Integer(0));
- p.get_routine_by_id(rid).get_interpreter().get_running_call_stack_frame_info(frame, stackinfo, func, file,
- line);
- return (String) stackinfo.d;
- }
- return "nil";
- }
-
- public static String getfuncfile(fake f, String func)
- {
- variant funcv = new variant();
- funcv.set_string(func);
- funcunion ff = f.fm.get_func(funcv);
- if (ff != null && ff.m_havefb)
- {
- return ff.m_fb.m_filename;
- }
- return "";
- }
-
- public static int getfuncstartline(fake f, String func)
- {
- variant funcv = new variant();
- funcv.set_string(func);
- funcunion ff = f.fm.get_func(funcv);
- if (ff != null && ff.m_havefb)
- {
- return ff.m_fb.get_binary_lineno(0);
- }
- return 0;
- }
-
- public static int getcurroutinenum(fake f)
- {
- processor p = f.rn.cur_pro();
- if (p != null)
- {
- return p.get_routine_num();
- }
- return 0;
- }
-
- public static int getroutineidbyindex(fake f, int index)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_index(index) != null)
- {
- return p.get_routine_by_index(index).get_id();
- }
- return 0;
- }
-
- public static String getcurroutinebyindex(fake f, int index)
- {
- processor p = f.rn.cur_pro();
- if (p != null)
- {
- return p.get_routine_info_by_index(index);
- }
- return "";
- }
-
- public static int getcurcallstacklengthbyroutine(fake f, int rid)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- return p.get_routine_by_id(rid).get_interpreter().get_running_call_stack_length();
- }
- return 0;
- }
-
- public static int getcurbytecodeposbyroutine(fake f, int rid)
- {
- processor p = f.rn.cur_pro();
- if (p != null && p.get_routine_by_id(rid) != null)
- {
- return p.get_routine_by_id(rid).get_interpreter().get_running_bytecode_pos();
- }
- return -1;
- }
-
- public static String dumpfunc(fake f, String func, int pos)
- {
- return f.bin.dump(func, pos);
- }
-
- public static boolean ishaveroutine(fake f, int rid)
- {
- processor p = f.rn.cur_pro();
- if (p != null)
- {
- return p.get_routine_by_id(rid) != null;
- }
- return false;
- }
-
- public static String getfilecode(fake f, String filename, int line)
- {
- if (filename.isEmpty() || line <= 0)
- {
- return "";
- }
-
- try
- {
- String encoding = "utf-8";
- Reader reader = new InputStreamReader(new FileInputStream(filename), encoding);
- BufferedReader bufferedReader = new BufferedReader(reader);
-
- int i = 0;
- String ret = "";
- while (true)
- {
- String str = bufferedReader.readLine();
- if (str == null)
- {
- break;
- }
-
- i++;
- if (i >= line)
- {
- ret = str;
- break;
- }
- }
- return ret;
- }
- catch (Exception e)
- {
- return e.getMessage();
- }
- }
-
- /**
- * 打开基本的内置函数
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static void openbaselib(fake f)
- {
- f.bif.openbasefunc();
- }
-
- /**
- * 是否有某个函数
- *
- * 注意类的非静态成员函数在绑定的时候会在前面加上类名
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static void openprofile(fake f)
- {
- f.pf.open();
- }
-
- /**
- * 关闭性能监控
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static void closeprofile(fake f)
- {
- f.pf.close();
- }
-
- /**
- * 打开优化
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static void openoptimize(fake f)
- {
- f.opt.open();
- }
-
- /**
- * 关闭优化
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static void closeoptimize(fake f)
- {
- f.opt.close();
- }
-
- /**
- * 打印性能监控数据
- *
- *
- * @param f
- * 上下文环境
- *
- * @return 无
- */
- public static String dumpprofile(fake f)
- {
- return f.pf.dump();
- }
-
- protected static void psclear(fake f)
- {
- f.ps.clear();
- }
-
- protected static void pspush(fake f, Object arg)
- {
- variant v = f.ps.push_and_get();
-
- if (arg == null)
- {
- v.set_pointer(null);
- return;
- }
-
- Class extends Object> c = arg.getClass();
- if (c == Byte.class)
- {
- Byte b = (Byte) arg;
- v.set_real(b);
- }
- else if (c == Short.class)
- {
- Short b = (Short) arg;
- v.set_real(b);
- }
- else if (c == Integer.class)
- {
- Integer b = (Integer) arg;
- v.set_real(b);
- }
- else if (c == Long.class)
- {
- long b = (long) arg;
- v.set_uuid(b);
- }
- else if (c == Float.class)
- {
- Float b = (Float) arg;
- v.set_real(b);
- }
- else if (c == Double.class)
- {
- Double b = (Double) arg;
- v.set_real(b);
- }
- else if (c == Boolean.class)
- {
- Boolean b = (Boolean) arg;
- v.set_real(b ? 1 : 0);
- }
- else if (c == String.class)
- {
- String b = (String) arg;
- v.set_string(b);
- }
- else
- {
- v.set_pointer(arg);
- }
- }
-
- protected static Object pspop(fake f)
- {
- if (f.ps.size() == 0)
- {
- return null;
- }
-
- variant v = f.ps.pop_and_get();
- if (v.m_type == variant_type.NIL)
- {
- return null;
- }
- else if (v.m_type == variant_type.REAL)
- {
- double b = (double) v.m_data;
- return b;
- }
- else if (v.m_type == variant_type.STRING)
- {
- String b = (String) v.m_data;
- return b;
- }
- else if (v.m_type == variant_type.POINTER)
- {
- return v.m_data;
- }
- else if (v.m_type == variant_type.UUID)
- {
- long b = (long) v.m_data;
- return b;
- }
- else
- {
- return null;
- }
- }
-
- protected static Object trans(Object src, Class> c)
- {
- if (src == null)
- {
- return null;
- }
-
- Class> srcc = src.getClass();
-
- if (c == Byte.class || c == Byte.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (byte) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (byte) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (byte) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (byte) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (byte) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (byte) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (byte) 1 : (byte) 0;
- }
- else if (srcc == String.class)
- {
- return Byte.valueOf((String) src);
- }
- else
- {
- return (byte) 0;
- }
- }
- else if (c == Short.class || c == Short.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (short) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (short) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (short) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (short) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (short) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (short) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (short) 1 : (short) 0;
- }
- else if (srcc == String.class)
- {
- return Short.valueOf((String) src);
- }
- else
- {
- return (short) 0;
- }
- }
- else if (c == Integer.class || c == Integer.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (int) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (int) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (int) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (int) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (int) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (int) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (int) 1 : (int) 0;
- }
- else if (srcc == String.class)
- {
- return Integer.valueOf((String) src);
- }
- else
- {
- return (int) 0;
- }
- }
- else if (c == Long.class || c == Long.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (long) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (long) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (long) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (long) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (long) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (long) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (long) 1 : (long) 0;
- }
- else if (srcc == String.class)
- {
- return Long.valueOf((String) src);
- }
- else
- {
- return (long) 0;
- }
- }
- else if (c == Float.class || c == Float.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (float) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (float) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (float) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (float) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (float) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (float) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (float) 1 : (float) 0;
- }
- else if (srcc == String.class)
- {
- return Float.valueOf((String) src);
- }
- else
- {
- return (float) 0;
- }
- }
- else if (c == Double.class || c == Double.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (double) (byte) src;
- }
- else if (srcc == Short.class)
- {
- return (double) (short) src;
- }
- else if (srcc == Integer.class)
- {
- return (double) (int) src;
- }
- else if (srcc == Long.class)
- {
- return (double) (long) src;
- }
- else if (srcc == Float.class)
- {
- return (double) (float) src;
- }
- else if (srcc == Double.class)
- {
- return (double) (double) src;
- }
- else if (srcc == Boolean.class)
- {
- return (boolean) src ? (double) 1 : (double) 0;
- }
- else if (srcc == String.class)
- {
- return Double.valueOf((String) src);
- }
- else
- {
- return (double) 0;
- }
- }
- else if (c == Boolean.class || c == Boolean.TYPE)
- {
- if (srcc == Byte.class)
- {
- return (byte) src != 0;
- }
- else if (srcc == Short.class)
- {
- return (short) src != 0;
- }
- else if (srcc == Integer.class)
- {
- return (int) src != 0;
- }
- else if (srcc == Long.class)
- {
- return (long) src != 0;
- }
- else if (srcc == Float.class)
- {
- return (float) src != 0;
- }
- else if (srcc == Double.class)
- {
- return (double) src != 0;
- }
- else if (srcc == Boolean.class)
- {
- return src;
- }
- else if (srcc == String.class)
- {
- return Integer.valueOf((String) src) != 0;
- }
- else
- {
- return false;
- }
- }
- else if (c == String.class)
- {
- return String.valueOf(src);
- }
- // 这种一般是模板,直接转过去
- else if (c == Object.class)
- {
- return src;
- }
- // 这些就是特定的类型了
- else
- {
- if (srcc == Byte.class)
- {
- return null;
- }
- else if (srcc == Short.class)
- {
- return null;
- }
- else if (srcc == Integer.class)
- {
- return null;
- }
- else if (srcc == Long.class)
- {
- return null;
- }
- else if (srcc == Float.class)
- {
- return null;
- }
- else if (srcc == Double.class)
- {
- return null;
- }
- else if (srcc == Boolean.class)
- {
- return null;
- }
- else if (srcc == String.class)
- {
- return null;
- }
- else
- {
- return src;
- }
- }
- }
-
- private static void rundebugps(fake f, String func)
- {
- variant funcv = new variant();
- funcv.set_string(func);
-
- processor pro = new processor(f);
-
- try
- {
- routine r = pro.start_routine(funcv, new ArrayList
+ * 由 {@code native/gen_capi.py} 初次生成,之后手改:脚本见文件已存在就
+ * 跳过,不会覆盖。分发表 {@link CApi} 每次都重新生成,两边靠方法名对上,
+ * 所以这里的方法签名不能改。
+ *
+ * 下标约定:{@code L} 是句柄(经 {@link NativeBridge#state} 换成
+ * {@link LuaState}),其余整数下标就是 C 的那套——正数从 {@code ci.func+1} 起,
+ * 负数从栈顶倒数,还有 {@code LUA_REGISTRYINDEX} 和 upvalue 两个伪索引。
+ */
+final class Api {
+
+ private Api() {
+ }
+
+ static RuntimeException unimplemented(String what) {
+ return new UnsupportedOperationException("C API 未实现: " + what);
+ }
+
+ /** 句柄换回 {@link LuaState} */
+ static LuaState st(long h) {
+ return NativeBridge.state(h);
+ }
+
+ /** {@code lua_compare} 的 {@code LUA_OPLE}:与 {@link Vm#lessThan} 同构 */
+ private static boolean le(LuaState L, LuaValue a, LuaValue b) {
+ if (a.isInteger() && b.isInteger()) {
+ return a.i <= b.i;
+ }
+ if (a.isNumber() && b.isNumber()) {
+ return !Vm.lessThan(L, b, a);
+ }
+ if (a.isString() && b.isString()) {
+ return Vm.lStrcmp(a.asString(), b.asString()) <= 0;
+ }
+ return LuaTm.callOrderTm(L, a, b, Tm.TM_LE);
+ }
+
+ /** {@code strstr}:在 {@code hay} 里从 {@code from} 起找 {@code needle} */
+ private static int indexOf(byte[] hay, byte[] needle, int from) {
+ if (needle.length == 0) {
+ return from;
+ }
+ for (int i = from; i + needle.length <= hay.length; i++) {
+ int j = 0;
+ while (j < needle.length && hay[i + j] == needle[j]) {
+ j++;
+ }
+ if (j == needle.length) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static boolean sameBytes(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static long strToNative(String s) {
+ return NativeBridge.strToNative(s.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1));
+ }
+
+ /** {@code %p} 的文本:C 的 {@code lua_topointer} 打出来是 {@code 0x...} */
+ private static String ptrText(long p) {
+ return "0x" + Long.toHexString(p);
+ }
+
+ /**
+ * {@code luaL_loadbufferx} / {@code luaL_loadstring} 共用的加载。
+ *
+ * chunk 名是字节串:{@code luaL_loadbufferx} 的 {@code name} 是
+ * {@code const char*},可以是任意字节(还可能是 NULL,那时 C 用 {@code "?"})。
+ * 走 Java String 再 UTF-8 编码回去既会失真,{@code name == 0} 时还会
+ * 直接 NPE——实测 {@code luaL_loadbufferx(L, s, n, NULL, "t")} 在参考实现上
+ * 是合法调用。
+ */
+ private static long loadBytes(long L, byte[] src, byte[] chunkName, String mode) {
+ LuaState S = st(L);
+ /*
+ * 走 {@link BaseLib#loadZio} 而不是自己再写一份:{@code mode} 那两档
+ * 报错、二进制 chunk 的识别、以及「读函数里的错误不抛而是变成返回值」
+ * 三件事都在里面。这里绕开它自己解析,{@code luaL_loadbufferx(s, n,
+ * "=x", "b")} 就会静默成功——而参考实现给的是
+ * {@code nil, "attempt to load a text chunk (mode is 'b')"}。
+ *
+ * {@code mode == null} 是 {@code luaL_loadstring} 与
+ * {@code luaL_loadbufferx} 的默认档:C 的 {@code checkmode} 对
+ * {@code NULL} 一律放行,等价于 {@code "bt"}。
+ */
+ LuaError err = BaseLib.loadZio(S, new Zio(src),
+ (chunkName == null) ? new byte[] {'?'} : chunkName,
+ (mode == null) ? "bt" : mode);
+ if (err == null) {
+ return 0; // LUA_OK,闭包已在栈顶
+ }
+ pushVal(S, (err.value == null) ? LuaValue.ofNil() : err.value);
+ /*
+ * {@code lua.h: LUA_ERRSYNTAX} 是 3,不是 2。{@code lua_load}
+ * 的返回值 C 扩展是拿去和 {@code LUA_OK / LUA_ERRSYNTAX} 比的,
+ * 给 2 会被当成别的失败档。
+ */
+ return 3; // LUA_ERRSYNTAX
+ }
+
+ /** {@code lua_rotate} 用的原地翻转 */
+ private static void reverse(LuaState s, int from, int to) {
+ while (from < to) {
+ LuaValue tmp = new LuaValue(s.stack[from]);
+ s.stack[from].set(s.stack[to]);
+ s.stack[to].set(tmp);
+ from++;
+ to--;
+ }
+ }
+
+ /** {@code luaH_get} 的裸查:不走 {@code __index} */
+ private static LuaValue rawSlot(LuaTable t, LuaValue key) {
+ return t.get(key);
+ }
+
+ /* ==================== 下标与取值 ==================== */
+
+ /** {@code lua.h: LUA_REGISTRYINDEX} = {@code -LUAI_MAXSTACK - 1000} */
+ static final int REGISTRY = -1001000;
+
+ /** {@code lua.h: LUA_RIDX_LAST}(= {@code LUA_RIDX_GLOBALS}) */
+ static final int RIDX_LAST = 2;
+
+ /** {@code lauxlib.c: #define freelist (LUA_RIDX_LAST + 1)} */
+ static final int FREELIST = RIDX_LAST + 1;
+
+ /** {@code lua_upvalueindex(i)} —— i 从 1 起,与 C 一致 */
+ static int upvalueIndex(int i) {
+ return REGISTRY - i;
+ }
+
+ /**
+ * C 的 {@code index2addr}。
+ *
+ * 越界给 {@code null}:C 里那是对着 {@code nilvalue} 取值,而 {@code lua_type}
+ * 报 {@code LUA_TNONE}——两者要分得开,所以这里不能随手折成 nil。
+ */
+ static LuaValue val(LuaState L, int idx) {
+ if (idx == REGISTRY) {
+ return LuaValue.ofTable(L.registry);
+ }
+ if (idx < REGISTRY) {
+ return L.upvalue(REGISTRY - idx - 1);
+ }
+ int p = idx > 0 ? L.ci.func + idx : L.top + idx;
+ return (p >= 0 && p < L.top) ? L.stack[p] : null;
+ }
+
+ static void setVal(LuaState L, int idx, LuaValue v) {
+ if (idx == REGISTRY) {
+ throw new IllegalStateException("registry 不可整体赋值");
+ }
+ if (idx < REGISTRY) {
+ LuaValue f = L.stack[L.ci.func];
+ int i = REGISTRY - idx - 1;
+ if (f.tt == Lua.VCCL) {
+ ((CClosure) f.o).upvalue[i].set(v);
+ } else {
+ ((LClosure) f.o).upvals[i].v.set(v);
+ }
+ return;
+ }
+ int p = idx > 0 ? L.ci.func + idx : L.top + idx;
+ if (p >= 0 && p < L.top) {
+ L.stack[p].set(v);
+ }
+ }
+
+ static int typeOf(LuaState L, int idx) {
+ LuaValue v = val(L, idx);
+ return v == null ? Lua.TNONE : v.baseType();
+ }
+
+ /*
+ * {@code lobject.h: #define ttype(o) (novariant(rawtt(o)))}
+ *
+ * 5.4.7 的 {@code ttype} 是剥掉变体的({@code novariant},不是
+ * {@code withvariant}),和 {@code ttnov} 是同一个数:整数和浮点都答
+ * {@code 3},表里不存在答 {@code 0}({@code LUA_TNIL})。
+ *
+ * 踩过一次:按 5.4.0 的印象以为 {@code ttype} 带变体(浮点 19),于是把
+ * getfield 系写成基础、gettable 系写成带变体。参考实测
+ * ({@code /tmp/ttypeprobe.c})七个函数对 1.5 一律给 3,拆是拆错的。
+ * 要带变体的是 {@code ttypetag},不是 {@code ttype}。
+ */
+ private static int ttype(LuaValue v) {
+ return v.baseType();
+ }
+
+ /** {@code luaL_checkstack} 之外,所有「栈要长」的地方都过这里 */
+ static void grow(LuaState L, int n) {
+ L.ensureSlots(L.top + n);
+ }
+
+ /** 往栈顶压一个值,槽位不够就先长栈 */
+ static void pushVal(LuaState L, LuaValue v) {
+ grow(L, 1);
+ L.stack[L.top++].set(v);
+ }
+
+
+ static void luaL_addgsub(long b, long s, long p, long r) {
+ byte[] sb = NativeBridge.bytesAt(s);
+ byte[] pb = NativeBridge.bytesAt(p);
+ byte[] rb = NativeBridge.bytesAt(r);
+ java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
+ int from = 0;
+ while (true) {
+ int at = indexOf(sb, pb, from);
+ if (at < 0) {
+ break;
+ }
+ out.write(sb, from, at - from);
+ out.write(rb, 0, rb.length);
+ from = at + pb.length;
+ }
+ out.write(sb, from, sb.length - from);
+ NativeBridge.bufAppend(b, out.toByteArray());
+ }
+
+ @Generated
+ static void luaL_addlstring(long B, long s, long l) {
+ throw new IllegalStateException("luaL_addlstring 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ static void luaL_addstring(long B, long s) {
+ throw new IllegalStateException("luaL_addstring 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ static void luaL_addvalue(long B) {
+ throw new IllegalStateException("luaL_addvalue 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static long luaL_argerror(long L, long arg, long extramsg) {
+ /*
+ * 不返回。抛出去的 LuaError 会被 shim 接住转成 longjmp——所以这里要
+ * 抛,不能只是把消息压栈。
+ */
+ String m = extramsg == 0 ? "" : new String(NativeBridge.bytesAt(extramsg));
+ throw st(L).argError((int) arg, m);
+ }
+
+ @Generated
+ static void luaL_buffinit(long L, long B) {
+ throw new IllegalStateException("luaL_buffinit 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ static long luaL_buffinitsize(long L, long B, long sz) {
+ throw new IllegalStateException("luaL_buffinitsize 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static long luaL_callmeta(long L, long obj, long e) {
+ LuaState S = st(L);
+ long k = NativeBridge.strToNative(NativeBridge.bytesAt(e));
+ if (luaL_getmetafield(L, obj, k) == Lua.TNIL) {
+ return 0;
+ }
+ LuaValue f = S.stack[--S.top];
+ LuaValue o = val(S, (int) obj);
+ grow(S, 3);
+ S.stack[S.top++].set(f);
+ S.stack[S.top++].set(o);
+ S.call(S.top - 2, 1);
+ return 1;
+ }
+
+ static void luaL_checkany(long L, long arg) {
+ LuaState S = st(L);
+ if (S.isNoneArg((int) arg)) {
+ throw S.argError((int) arg, "value expected");
+ }
+ }
+
+ static long luaL_checkinteger(long L, long arg) {
+ LuaState S = st(L);
+ LuaValue v = S.arg1((int) arg);
+ if (v.isInteger()) {
+ return v.asInteger();
+ }
+ if (v.isFloat()) {
+ long[] out = new long[1];
+ if (LuaValue.floatToIntExact(v.asFloat(), out)) {
+ return out[0];
+ }
+ throw S.argError((int) arg, "number has no integer representation");
+ }
+ if (S.isNoneArg((int) arg)) {
+ throw S.typeArgError((int) arg, "number");
+ }
+ LuaValue n = v.isString() ? Vm.strToNum(v) : null;
+ if (n != null && n.isInteger()) {
+ return n.asInteger();
+ }
+ throw S.typeArgError((int) arg, "number");
+ }
+
+ static long luaL_checklstring(long L, long arg, long l) {
+ /*
+ * {@code lauxlib.c} 是「先 {@code lua_tolstring}、转不出来才
+ * {@code tag_error}」——所以数字照样会被原地换掉,与
+ * {@link #lua_tolstring} 同形。早先这里是「先判类型、再自己转」,
+ * 于是不做原地转换。
+ */
+ LuaState S = st(L);
+ long p = lua_tolstring(L, arg, l);
+ if (p == 0) {
+ throw S.typeArgError((int) arg, "string");
+ }
+ return p;
+ }
+
+ static double luaL_checknumber(long L, long arg) {
+ LuaState S = st(L);
+ LuaValue v = S.arg1((int) arg);
+ if (v.isNumber()) {
+ return v.isInteger() ? (double) v.asInteger() : v.asFloat();
+ }
+ if (!S.isNoneArg((int) arg) && v.isString()) {
+ LuaValue n = Vm.strToNum(v);
+ if (n != null) {
+ return n.isInteger() ? (double) n.asInteger() : n.asFloat();
+ }
+ }
+ throw S.typeArgError((int) arg, "number");
+ }
+
+ static long luaL_checkoption(long L, long arg, long def, long packed, long n) {
+ LuaState S = st(L);
+ LuaString d = def == 0 ? null : LuaString.of(NativeBridge.bytesAt(def));
+ LuaString s = LuaAux.optString(S, (int) arg, d);
+ long p = packed;
+ for (int i = 0; i < (int) n; i++) {
+ byte[] b = NativeBridge.bytesAt(p);
+ if (sameBytes(b, s.bytes)) {
+ return i;
+ }
+ p += b.length + 1;
+ }
+ throw S.argError((int) arg, "invalid option '" + s.toLatin1String() + "'");
+ }
+
+ static void luaL_checkstack(long L, long sz, long msg) {
+ LuaState S = st(L);
+ try {
+ grow(S, (int) sz);
+ } catch (RuntimeException e) {
+ String m = (msg == 0) ? "" : new String(NativeBridge.bytesAt(msg));
+ throw S.libError("stack overflow" + (m.isEmpty() ? "" : " (" + m + ")"));
+ }
+ }
+
+ static void luaL_checktype(long L, long arg, long t) {
+ LuaState S = st(L);
+ if (S.argBaseType((int) arg) != (int) t) {
+ throw S.typeArgError((int) arg, Lua.typeName((int) t) + "");
+ }
+ }
+
+ static long luaL_checkudata(long L, long ud, long tname) {
+ long p = luaL_testudata(L, ud, tname);
+ if (p == 0) {
+ String tn = tname == 0 ? "?" : new String(NativeBridge.bytesAt(tname));
+ throw st(L).typeArgError((int) ud, tn);
+ }
+ return p;
+ }
+
+ static void luaL_checkversion_(long L, double ver, long sz) {
+ /*
+ * lauxlib.c:sz 是 LUAL_NUMSIZES,等于 sizeof(lua_Integer)*16 + sizeof(lua_Number)。
+ * 真库 mac/linux 上都是 136——扩展编的时候这个值就烧进 .so 了,对不上说明
+ * 两边数值类型不一致,C 版直接报错而不是继续跑。
+ */
+ if (sz != 136) {
+ throw st(L).libError("core and library have incompatible numeric types");
+ }
+ /*
+ * 后面那半句原来是漏的:只比 sz,不比版本。于是任何一个声明
+ * 「我需要 5.3」的老扩展都能静默加载进来,真 Lua 在这里是
+ * {@code luaL_error}(不是返回错误码,是抛)。
+ *
+ * 消息里两个 {@code %f} 是 C99 默认精度的定点格式(6 位小数),
+ * 走 {@code BigDecimal} 而不是 {@code String.format}——后者连舍两次
+ * (见 README 浮点格式化那条)。
+ */
+ if (ver != Lua.VERSION_NUM) {
+ /*
+ * {@code %f} 在 {@code lua_pushvfstring} 里走的不是 printf 那条路:
+ * {@code lobject.c} 的 {@code case 'f'} 是 {@code addnum2buff},
+ * 也就是 {@code tostringbuff} —— {@code %.14g} 之后再给整数样的
+ * 结果补 {@code .0}。所以参考实现打的是 {@code 503.0} 而不是
+ * {@code 503.000000}(一开始照 printf 写,网格当场红了一条)。
+ */
+ String msg = "version mismatch: app. needs "
+ + Vm.numberToString(LuaValue.ofFloat(ver))
+ + ", Lua core provides "
+ + Vm.numberToString(LuaValue.ofFloat(Lua.VERSION_NUM));
+ throw st(L).libError(msg);
+ }
+ }
+
+ /*
+ * {@code luaL_error} 整条搬到 {@code luashim.c} 里去了:
+ *
+ * 早先这里是「C 侧 {@code vsnprintf} 展开 → Java 拼 where + 抛」,
+ * 于是 {@code luaL_error(L, "%f", 1.5)} 给 {@code 1.500000}(参考
+ * {@code 1.5})、{@code "%5d"} 照打不误(参考报
+ * {@code invalid option '%5'})。
+ */
+ @Generated
+ static long luaL_error(long L, long fmt, long a2) {
+ throw new IllegalStateException(
+ "luaL_error 在 luashim.c 里用 luaL_where + lua_pushvfstring 拼出来,不进 Java");
+ }
+
+ /*
+ * {@code luaL_execresult} / {@code luaL_fileresult}:errno 是 C 侧塞进来的。
+ *
+ * {@code lauxlib.c} 里这两个的第一句都是 {@code int en = errno;}
+ * (注释明写 {@code calls to Lua API may change this value})。errno 是
+ * 每个线程一份的 C 变量,Java 读不到——所以 {@code luashim.c} 在 C 侧先取
+ * 出来,多占一个参数槽交给这里(见 {@code gen_capi.py} 的
+ * {@code ARG_OVERRIDE})。
+ *
+ * 早先这两个是「生成出来的普通桩」,errno 一律当 0,后果有两处,而且都
+ * 是我们自己的库永远撞不到的({@code io} / {@code os} 走的是
+ * {@code LuaAux} 另一条路,那边 errno 本来就是从 Java 异常反查的):
+ *
+ * 而且「首次访问」那一支会先把 t[3] 写成 0 再去取
+ * {@code lua_rawlen}:注册表原本 {@code rawlen} 是 2,写完这一格变成 3,
+ * 于是第一个引用是 4。漏掉这次写、或者把链表头放在 0,都会让引用
+ * 号整体错位 —— 实测参考实现连调两次都是 4。
+ */
+ lua_rawgeti(L, ti, FREELIST);
+ long ref;
+ if (S.stack[S.top - 1].isNil()) { // first access
+ ref = 0;
+ S.top--;
+ lua_pushinteger(L, 0);
+ lua_rawseti(L, ti, FREELIST);
+ } else {
+ ref = S.stack[--S.top].asInteger();
+ }
+ if (ref != 0) {
+ lua_rawgeti(L, ti, ref);
+ lua_rawseti(L, ti, FREELIST);
+ } else {
+ ref = lua_rawlen(L, ti) + 1;
+ }
+ lua_rawseti(L, ti, ref);
+ return ref;
+ }
+
+ static void luaL_requiref(long L, long modname, long openf, long glb) {
+ LuaState S = st(L);
+ long k = NativeBridge.strToNative(NativeBridge.bytesAt(modname));
+ luaL_getsubtable(L, REGISTRY, strToNative("_LOADED"));
+ lua_getfield(L, -1, k);
+ if (!S.stack[S.top - 1].toBoolean()) {
+ S.top--;
+ lua_pushcclosure(L, openf, 0);
+ lua_pushstring(L, k);
+ lua_callk(L, 1, 1, 0, 0);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -3, k);
+ }
+ S.stack[S.top - 2].set(S.stack[S.top - 1]);
+ S.top--;
+ if (glb != 0) {
+ lua_pushvalue(L, -1);
+ lua_setglobal(L, k);
+ }
+ }
+
+ @Generated
+ static void luaL_setfuncs(long L, long l, long nup) {
+ throw new IllegalStateException("luaL_setfuncs 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static void luaL_setmetatable(long L, long tname) {
+ LuaState S = st(L);
+ lua_getfield(L, REGISTRY, NativeBridge.strToNative(NativeBridge.bytesAt(tname)));
+ lua_setmetatable(L, -2);
+ }
+
+ static long luaL_testudata(long L, long ud, long tname) {
+ LuaState S = st(L);
+ long p = lua_touserdata(L, ud);
+ if (p == 0) {
+ return 0;
+ }
+ if (lua_getmetatable(L, ud) == 0) {
+ return 0;
+ }
+ lua_getfield(L, REGISTRY, NativeBridge.strToNative(NativeBridge.bytesAt(tname)));
+ boolean eq = LuaValue.rawEqual(S.stack[S.top - 1], S.stack[S.top - 2]);
+ S.top -= 2;
+ return eq ? p : 0;
+ }
+
+ static long luaL_tolstring(long L, long idx, long len) {
+ LuaState S = st(L);
+ int i = (int) (int) lua_absindex(L, idx);
+ if (luaL_callmeta(L, i, strToNative("__tostring")) != 0) {
+ if (!S.stack[S.top - 1].isString()) {
+ throw S.libError("'__tostring' must return a string");
+ }
+ } else {
+ LuaValue v = val(S, i);
+ int tt = typeOf(S, i);
+ if (tt == Lua.TNUMBER || tt == Lua.TSTRING) {
+ pushVal(S, v);
+ } else if (tt == Lua.TBOOLEAN) {
+ pushVal(S, LuaValue.ofString(
+ LuaString.latin1(v.toBoolean() ? "true" : "false")));
+ } else if (tt == Lua.TNIL) {
+ pushVal(S, LuaValue.ofString(LuaString.latin1("nil")));
+ } else {
+ int nt = (int) luaL_getmetafield(L, i, strToNative("__name"));
+ String kind = nt == Lua.TSTRING
+ ? S.stack[S.top - 1].asString().toLatin1String()
+ : Lua.typeName(tt);
+ pushVal(S, LuaValue.ofString(LuaString.latin1(
+ kind + ": " + ptrText(lua_topointer(L, i)))));
+ if (nt != Lua.TNONE) {
+ S.stack[S.top - 2].set(S.stack[S.top - 1]);
+ S.top--;
+ }
+ }
+ }
+ return lua_tolstring(L, -1, len);
+ }
+
+ static void luaL_traceback(long L, long L1, long msg, long level) {
+ /*
+ * 与 {@code debug.traceback} / 未捕获错误的 {@code msghandler}
+ * 同一份 {@link DebugLib#traceback}:三处只要有一处是自己拼的,
+ * 「{@code debug.traceback} 对的、C 扩展里 {@code luaL_traceback} 少一层」
+ * 这种分歧就查不出来。
+ *
+ * {@code msg} 是 {@code const char*},按字节取且不再过
+ * {@code luaL_tolstring}——C 版也是直接 {@code strlen(msg)} 用,
+ * 所以 {@code nil} 就是没有消息,不是字符串 "nil"。
+ */
+ LuaState S = st(L);
+ LuaState T = st(L1);
+ byte[] mb = NativeBridge.bytesAt(msg);
+ LuaString m = (mb == null) ? null : LuaString.of(mb);
+ pushVal(S, LuaValue.ofString(
+ DebugLib.traceback(S, (T == null) ? S : T, m, (int) level)));
+ }
+
+ static long luaL_typeerror(long L, long arg, long tname) {
+ String tn = tname == 0 ? "?" : new String(NativeBridge.bytesAt(tname));
+ throw st(L).typeArgError((int) arg, tn);
+ }
+
+ static void luaL_unref(long L, long t, long ref) {
+ if (ref >= 0) {
+ int ti = (int) lua_absindex(L, t);
+ lua_rawgeti(L, ti, FREELIST);
+ lua_rawseti(L, ti, ref);
+ lua_pushinteger(L, ref);
+ lua_rawseti(L, ti, FREELIST);
+ }
+ }
+
+ /*
+ * {@code lauxlib.c: luaL_where}:压 {@code "
+ * 这一条曾经是空串桩(TODO 上写着「C2 阶段接上」),而它正是
+ * {@code luaL_error} 的第一句——于是所有从 C 扩展里 {@code luaL_error}
+ * 出来的消息都没有位置前缀。参考实测({@code f_where})从 Lua 函数里
+ * 调进来是 {@code target/rt/case.lua:8: },我们给空串。
+ *
+ * 两个容易踩的点:
+ *
+ * 注意 {@code top - 2} 与第一个操作数是同一个槽:两个操作数先取
+ * 引用再算,别在算之前把槽清了(与 {@link #lua_gettable} 同一个坑)。
+ */
+ LuaState S = st(L);
+ int o = (int) op;
+ if (o == Arith.OP_UNM || o == Arith.OP_BNOT) {
+ grow(S, 1);
+ S.stack[S.top].set(S.stack[S.top - 1]);
+ S.top++;
+ }
+ LuaValue p1 = S.stack[S.top - 2];
+ LuaValue p2 = S.stack[S.top - 1];
+ LuaValue res = Arith.rawArith(S, o, p1, p2);
+ if (res == null) {
+ /*
+ * {@code luaO_arith} → {@code luaT_trybinTM}:元方法事件号是
+ * {@code (op - LUA_OPADD) + TM_ADD},而 {@code LUA_OPADD == 0},
+ * 所以就是 {@code op + TM_ADD}——{@code UNM}
+ * (12)落到 {@code TM_UNM}(18)、{@code BNOT}(13)落到
+ * {@code TM_BNOT}(19),与 {@code lobject.h} 的 {@code ORDER TM}
+ * 顺序一致。
+ */
+ LuaTm.tryBinTm(S, p1, p2, S.top - 2, o + Tm.TM_ADD);
+ } else {
+ S.stack[S.top - 2].set(res);
+ }
+ S.top -= 1;
+ }
+
+ /*
+ * lapi.c: lua_atpanic 只是「换个函数指针、交回旧的」。
+ *
+ * 至于什么时候真的调它:C 版是 {@code luaD_throw} 发现
+ * {@code L->errorJmp == NULL}(错误逃出了所有保护区)时调,调完 {@code abort()}。
+ * 本实现的错误是 Java 异常,逃出保护区后一路冒到宿主({@code pcall} 或者
+ * {@code Runner}),那里才是「要不要 panic」的决定点——所以这里只存不调。
+ * 真要接上,宿主在自己的捕获点取 {@link LuaState#panicFn} 即可。
+ */
+ static long lua_atpanic(long L, long panicf) {
+ LuaState S = st(L);
+ long old = S.panic();
+ S.setPanic(panicf);
+ return old;
+ }
+
+ static void lua_callk(long L, long nargs, long nresults, long ctx, long k) {
+ LuaState S = st(L);
+ int funcIdx = S.top - (int) nargs - 1;
+ /*
+ * lapi.c: {@code k != NULL && yieldable(L)} 才把续体记到当前 C 帧上,
+ * 否则走 {@code luaD_callnoyield}——「不可 yield」在 C 里是靠
+ * {@code nyci} 计数的,回调里 yield 会报跨 C 调用边界而不是真的挂起。
+ */
+ if (k != 0 && S.yieldable()) {
+ S.callK(funcIdx, (int) nresults, (int) ctx, new NativeKFunction(k));
+ } else {
+ S.callNoYield(funcIdx, (int) nresults);
+ }
+ }
+
+ static long lua_checkstack(long L, long n) {
+ LuaState S = st(L);
+ try {
+ grow(S, (int) n);
+ return 1;
+ } catch (RuntimeException e) {
+ return 0;
+ }
+ }
+
+ /*
+ * {@code lua_close}。
+ *
+ * 有意偏离,别当 bug 修:C 版会把整个 {@code lua_State} 连同所有对象
+ * 一起释放,本实现的内存归 JVM 管,没有「释放」这个动作可做。这里做的是
+ * 把线程复位到空,于是之后再用它就是 C 里的 use-after-free(未定义)——
+ * 至少不会「关完了还能正常跑」,那才是真正的假象。
+ */
+ static void lua_close(long L) {
+ st(L).reset();
+ }
+
+ @Generated
+ /*
+ * {@code lapi.c: lua_closeslot}:把某个槽上刚标记的 tbc 变量当场关掉,
+ * 然后把它置 nil。
+ *
+ * {@code luaF_close(L, level, CLOSEKTOP, 0)} 的最后一个参数是 {@code 0}——
+ * {@code __close} 里不许 yield({@code lua_toclose} 那条路的
+ * {@code moveresults} 传的是 1,允许)。所以这里 {@code forceYy} 取 false。
+ */
+ static void lua_closeslot(long L, long idx) {
+ LuaState S = st(L);
+ int level = stackIdx(S, (int) idx);
+ S.closeUpvals(level, null, false);
+ S.stack[level].setNil();
+ }
+
+ /** {@code index2stack}:与 {@link #val} 同一套换算,只是要有越界保护 */
+ private static int stackIdx(LuaState L, int idx) {
+ int p = idx > 0 ? L.ci.func + idx : L.top + idx;
+ if (p < 0 || p >= L.top) {
+ throw new IllegalArgumentException("bad stack index " + idx);
+ }
+ return p;
+ }
+
+ /*
+ * lstate.c: {@code lua_closethread} 就是 {@code luaE_resetthread(L, L->status)}。
+ * {@code coroutine.close} 用的是同一条路,这里直接复用。
+ */
+ static long lua_closethread(long L, long from) {
+ LuaState S = st(L);
+ LuaState f = (from == 0) ? null : st(from);
+ return S.closeThread(f);
+ }
+
+ static long lua_compare(long L, long idx1, long idx2, long op) {
+ LuaState S = st(L);
+ LuaValue a = val(S, (int) idx1);
+ LuaValue b = val(S, (int) idx2);
+ if (a == null || b == null) {
+ return 0;
+ }
+ switch ((int) op) {
+ case 0: return LuaValue.rawEqual(a, b) ? 1 : 0;
+ case 1: return Vm.lessThan(S, a, b) ? 1 : 0;
+ case 2: return le(S, a, b) ? 1 : 0;
+ default: return 0;
+ }
+ }
+
+ static void lua_concat(long L, long n) {
+ LuaState S = st(L);
+ Vm.concat(S, (int) n);
+ }
+
+ static void lua_copy(long L, long fromidx, long toidx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) fromidx);
+ setVal(S, (int) toidx, v == null ? LuaValue.ofNil() : v);
+ }
+
+ static void lua_createtable(long L, long narr, long nrec) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofTable(new LuaTable((int) narr, (int) nrec)));
+ }
+
+ static long lua_dump(long L, long writer, long data, long strip) {
+ /*
+ * {@code lapi.c: lua_dump}:只对 Lua 函数做,别的(含 C 函数)
+ * 一律返回 1 表示「不是我该干的」。判据是 {@code isLfunction},
+ * 也就是 {@link Lua#VLIF},不是「能不能调用」。
+ *
+ * 字节交给 writer 走 {@code strToNative} 的环形缓冲:dump 出来的是
+ * 二进制、中间有 {@code \0},但长度是我们自己知道的,C 侧按
+ * {@code sz} 取就不会被截断。
+ *
+ * 只调 writer 一次:C 版 {@code luaU_dump} 是分块的(一个串、
+ * 一段指令各一次)。写得很规矩的 writer 两种都吃;返回值取 writer
+ * 自己的(非 0 即失败)——{@code string.dump} 那条路是另一份实现,
+ * 这里不复用。
+ */
+ LuaState S = st(L);
+ LuaValue v = S.stack[S.top - 1];
+ if (v.tt != Lua.VLIF) {
+ return 1;
+ }
+ byte[] b = Dump.dumpBytes(((LClosure) v.o).p, strip != 0);
+ long p = NativeBridge.strToNative(b);
+ long r = NativeBridge.callPtr(writer,
+ NativeBridge.statePtr(L), p, b.length, data);
+ /*
+ * writer 声明的是 {@code int},而 {@code callPtr} 取的是
+ * {@code jlong}。arm64 上返回 {@code int} 只保证低 32 位有意义,
+ * 高 32 位是未定义的——这里必须截断,否则 {@code return 0} 的 writer
+ * 可能被读成一个巨大的正数。
+ */
+ return (int) r;
+ }
+
+ static long lua_error(long L) {
+ LuaState S = st(L);
+ /*
+ * {@code lapi.c: lua_error} 干两件事:取栈顶那个值当错误对象,然后
+ * {@code luaG_errormsg}。位置是 {@code luaL_error} 自己用
+ * {@code luaL_where} 拼好的,{@code lua_error} 一律不加——所以
+ * 这里 {@code where} 给 {@code ""}(「已定稿」),保保护边界别再补一次。
+ *
+ * 唯一的特判是内存错误:错误对象恰好是驻留短串
+ * {@code "not enough memory"} 时,C 走 {@code luaM_error} 而不是
+ * {@code luaG_errormsg}。可观测——{@code LUA_ERRMEM} 这一档
+ * {@code xpcall} 的处理器接不到(见 {@link LuaState#errorAt})。
+ */
+ LuaValue v = new LuaValue(S.stack[S.top - 1]);
+ LuaError e = new LuaError(v, "");
+ e.errMem = LuaState.isMemErr(v);
+ throw e;
+ }
+
+ static long lua_gc(long L, long what, long a2) {
+ /*
+ * {@code lapi.c: lua_gc} 与 {@code lbaselib.c: luaB_collectgarbage}
+ * 是同一份状态的两张脸——{@link BaseLib#collectgarbage} 里的
+ * 注释全适用于这里(尤其是「{@code count} 一律报 0」那条:报任何具体
+ * 数字都是谎报)。这里照 {@code lua.h} 的 {@code LUA_GC*} 常量逐档映射。
+ *
+ * 只丢了一样东西:{@code LUA_GCGEN} 的 {@code majormul} 与
+ * {@code LUA_GCINC} 的 {@code stepmul}/{@code stepsize}。变参在 C 侧
+ * 是打包进槽位的,而生成器只给变参函数留了三个槽({@code L}、
+ * {@code what}、第一个变参),多出来的进不来。丢的是「调节量」——
+ * 我们本来也不按它调(没有分代收集器可调),返回值 {@code res}
+ * 只取决于切之前的模式,照样算得对。
+ */
+ LuaState S = st(L);
+ LuaState m = S.mainThread;
+ int w = (int) what;
+ switch (w) {
+ case 0: // LUA_GCSTOP
+ m.gcRunning = false;
+ return 0;
+ case 1: // LUA_GCRESTART
+ m.gcRunning = true;
+ return 0;
+ case 2: // LUA_GCCOLLECT
+ BaseLib.doCollect(S);
+ return 0;
+ case 3: // LUA_GCCOUNT —— KB,本实现恒 0
+ case 4: // LUA_GCCOUNTB —— 余数字节,同上
+ return 0;
+ case 5: // LUA_GCSTEP
+ /*
+ * 返回 1 = 「一个收集周期刚跑完」。{@code collectgarbage("step")}
+ * 就是拿这个 1 去推 {@code true} 的,所以这里必须给 1,
+ * 否则 {@code lua_gc} 与 {@code collectgarbage} 两条路自相矛盾。
+ */
+ BaseLib.doCollect(S);
+ return 1;
+ case 6: { // LUA_GCSETPAUSE —— setgcparam 无条件赋值,见 BaseLib#collectgarbage
+ int old = m.gcPause();
+ m.setGcPause((int) a2);
+ return old;
+ }
+ case 7: { // LUA_GCSETSTEPMUL
+ int old = m.gcStepMul();
+ m.setGcStepMul((int) a2);
+ return old;
+ }
+ case 9: // LUA_GCISRUNNING
+ return m.gcRunning ? 1 : 0;
+ case 10: // LUA_GCGEN
+ case 11: { // LUA_GCINC
+ /*
+ * {@code res} 是切换前的模式码({@code LUA_GCGEN} /
+ * {@code LUA_GCINC}),不是 {@code collectgarbage} 那个字符串。
+ * 判据照 {@code isdecGCmodegen}——见 {@link LuaState#gcLastAtomic}。
+ */
+ boolean prevGen = (m.gcKind == LuaState.LUA_GCGEN) || m.gcLastAtomic;
+ m.gcKind = (w == 10) ? LuaState.LUA_GCGEN : LuaState.LUA_GCINC;
+ m.gcLastAtomic = false;
+ return prevGen ? 10 : 11;
+ }
+ default:
+ return -1; // 无效选项
+ }
+ }
+
+ /*
+ * {@code lua_getallocf} / {@code lua_setallocf}:整对在 {@code luashim.c} 里。
+ *
+ * 为什么不能进 Java:{@code lua_getallocf} 交回去的是一个能被直接调用的
+ * C 函数指针,而 {@code lpeg} 这类扩展拿它就是为了自己 malloc/free
+ * ({@code lpcode.c} 的 {@code newcode} / {@code freecode} / {@code realloccode}
+ * 三处都这么干),Java 交不出这种东西。
+ */
+ @Generated
+ static long lua_getallocf(long L, long ud) {
+ throw new IllegalStateException("lua_getallocf 在 luashim.c 里就地实现,不进 Java");
+ }
+
+ static long lua_getfield(long L, long idx, long k) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaString key = LuaString.of(NativeBridge.bytesAt(k));
+ grow(S, 1);
+ S.stack[S.top].setNil();
+ Vm.getTable(S, t, LuaValue.ofString(key), S.top);
+ S.top++;
+ /*
+ * {@code lapi.c: auxgetstr} 的返回值是
+ * {@code ttype(s2v(L->top - 1))} —— 取到的那个值的类型,
+ * 不是被查的那张表的。以前这里返回 {@code t} 的类型,于是
+ * {@code lua_getfield} 在表上恒给 {@code LUA_TTABLE}:扩展里拿返回值
+ * 判「这个字段在不在 / 是不是函数」的判断整个反过来
+ * (实测:新状态上 {@code lua_getglobal(L, "print")} 参考给
+ * {@code LUA_TFUNCTION},本实现给 {@code LUA_TTABLE})。
+ */
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_getglobal(long L, long name) {
+ LuaState S = st(L);
+ LuaString key = LuaString.of(NativeBridge.bytesAt(name));
+ grow(S, 1);
+ S.stack[S.top].setNil();
+ Vm.getTable(S, LuaValue.ofTable(S.globals), LuaValue.ofString(key), S.top);
+ S.top++;
+ // 同 lua_getfield:返回取到的值的类型
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_gethook(long L) {
+ /*
+ * 只认本实现装进去的 C 钩子:脚本侧 {@code debug.sethook} 装的
+ * 是 Java 的 {@link LuaHook},没有函数指针可还,交 0(= NULL)。
+ * C 版当然原样还那个指针,但这里「还 0」与「还一个假地址」之间
+ * 必须选前者——扩展拿到地址多半是去比较或去调。
+ */
+ LuaHook h = st(L).getHook();
+ return (h instanceof NativeHook) ? ((NativeHook) h).fn : 0;
+ }
+
+ static long lua_gethookcount(long L) {
+ /*
+ * 是 {@code basehookcount}(周期),不是还剩几条——
+ * {@code debug.gethook()} 的第三个结果要的也是这个,两边必须同答。
+ */
+ return st(L).getHookCount();
+ }
+
+ static long lua_gethookmask(long L) {
+ return st(L).getHookMask();
+ }
+
+ static long lua_geti(long L, long idx, long n) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ grow(S, 1);
+ S.stack[S.top].setNil();
+ Vm.getTable(S, t, LuaValue.ofInteger(n), S.top);
+ S.top++;
+ // 同 lua_getfield:返回取到的值的类型,不是 {@code t} 的
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_getinfo(long L, long what, long ar) {
+ /*
+ * {@code what} 里带 {@code '>'} 时要先把栈顶那个函数弹掉({@code getInfo}
+ * 内部会这么做),带 {@code 'f'} / {@code 'L'} 时又会往栈上推——
+ * 与 {@code debug.getinfo} 完全同一条路,所以直接复用
+ * {@link DebugLib#getInfo}。
+ */
+ LuaState S = st(L);
+ DebugLib.LuaDebug d = arOf(ar);
+ if (d == null) {
+ return 0; // 没先调 lua_getstack:C 里 ar->i_ci 是野的
+ }
+ byte[] wb = NativeBridge.bytesAt(what);
+ boolean ok = DebugLib.getInfo(S,
+ (wb == null) ? "" : new String(wb, java.nio.charset.StandardCharsets.ISO_8859_1), d);
+ fillDebug(ar, d);
+ return ok ? 1 : 0;
+ }
+
+ static long lua_getiuservalue(long L, long idx, long n) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ LuaUserdata ud = udata(v);
+ /*
+ * 越界(含 {@code nuvalue == 0})时 C 是推一个 nil 并返回
+ * {@code LUA_TNONE},不是什么都不推 —— 栈高一格这点差别,
+ * 扩展里「{@code if (lua_getiuservalue(... ) != LUA_TNONE)}」就反了。
+ */
+ if (ud == null || ud.userValues == null || n <= 0 || n > ud.userValues.length) {
+ pushVal(S, LuaValue.ofNil());
+ return Lua.TNONE;
+ }
+ pushVal(S, ud.userValues[(int) n - 1]);
+ return ud.userValues[(int) n - 1].baseType();
+ }
+
+ /** 取完整 userdata;不是就给 {@code null}(C 的 {@code api_check} 是断言) */
+ private static LuaUserdata udata(LuaValue v) {
+ return (v != null && v.baseType() == Lua.TUSERDATA)
+ ? (LuaUserdata) v.o : null;
+ }
+
+ static long lua_getlocal(long L, long ar, long n) {
+ /*
+ * {@code ar == NULL} 是「查非活动函数」那一支:只看栈顶那个 Lua 函数
+ * 在入口处还活着的参数({@code luaF_getlocalname(p, n, 0)}),
+ * 不压任何值。{@code ldblib.c} 的 {@code getlocal} 就是这么分的。
+ */
+ LuaState S = st(L);
+ DebugLib.LuaDebug d = (ar == 0) ? null : arOf(ar);
+ if (ar != 0 && d == null) {
+ return 0;
+ }
+ String name = DebugLib.getLocal(S, d, (int) n);
+ return (name == null) ? 0 : strToNative(name);
+ }
+
+ static long lua_getmetatable(long L, long objindex) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) objindex);
+ LuaTable mt = (v == null) ? null : LuaTm.metatableOf(S, v);
+ if (mt == null) {
+ return 0;
+ }
+ pushVal(S, LuaValue.ofTable(mt));
+ return 1;
+ }
+
+ /*
+ * {@code lua_Debug} 是 C 栈上的结构体,Java 这边只见得到一个地址。
+ * 而 {@code lua_getstack} 写进去的 {@code i_ci}(我们的
+ * {@link DebugLib.LuaDebug})是个 Java 对象,塞不进那块内存。
+ *
+ * 于是用一张「地址 → Java 侧状态」的表搭桥:{@code lua_getstack} 存,
+ * 紧接着的 {@code lua_getinfo} / {@code lua_getlocal} 取。C 的用法本来
+ * 就是「先 getstack 再 getinfo,同一个 {@code ar}」,一张表够用。
+ *
+ * 表必须有上界:{@code ar} 常常是循环里的局部结构体,地址会被复用,
+ * 不复用的话跑一晚上就攒出几万个条目。{@code 64} 条是「同一个瞬间
+ * 最多同时有几个活的 {@code ar}」的宽裕估计,淘汰的是最久没用到的。
+ */
+ private static final int AR_CACHE_MAX = 64;
+
+ private static final java.util.LinkedHashMap
+ * 顺序与宽度是两套代码之间的契约:C 侧按 {@code offsetof} 落字段,
+ * 这里按下标给值,改任何一边都必须两边一起改。
+ */
+ static void fillDebug(long arPtr, DebugLib.LuaDebug ar) {
+ long[] v = new long[15];
+ v[0] = ar.event;
+ v[1] = strToNative(ar.name == null ? "" : ar.name);
+ v[2] = strToNative(ar.namewhat == null ? "" : ar.namewhat);
+ v[3] = strToNative(ar.what == null ? "" : ar.what);
+ LuaString src = (ar.source == null) ? LuaString.latin1("=?") : ar.source;
+ v[4] = NativeBridge.strToNative(src.bytes);
+ v[5] = src.length();
+ v[6] = ar.currentline;
+ v[7] = ar.linedefined;
+ v[8] = ar.lastlinedefined;
+ v[9] = ar.nups;
+ v[10] = ar.nparams;
+ v[11] = ar.isvararg ? 1 : 0;
+ v[12] = ar.istailcall ? 1 : 0;
+ v[13] = ar.ftransfer;
+ v[14] = ar.ntransfer;
+ NativeBridge.dbgFill(arPtr, v);
+ NativeBridge.dbgShortSrc(arPtr,
+ (ar.shortSrc == null) ? new byte[0] : ar.shortSrc.bytes);
+ }
+
+ static long lua_getstack(long L, long level, long ar) {
+ LuaState S = st(L);
+ DebugLib.LuaDebug d = new DebugLib.LuaDebug();
+ if (!DebugLib.getStack(S, (int) level, d)) {
+ return 0;
+ }
+ arPut(ar, d);
+ return 1;
+ }
+
+ static long lua_gettable(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue key = S.stack[--S.top];
+ /*
+ * 结果就写在「key 原来那一格」上(C 是 {@code L->top - 1} 原地替换),
+ * 所以 key 必须先拷出来:它和目的槽是同一个 {@link LuaValue}
+ * 对象,先 {@code setNil} 或者 {@code getTable} 内部往那一格写东西,
+ * key 就被自己踩掉了 —— 症状是 {@code t[k]} 恒给 nil。
+ */
+ LuaValue k2 = new LuaValue(key);
+ grow(S, 1);
+ Vm.getTable(S, t, k2, S.top);
+ S.top++;
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_gettop(long L) {
+ LuaState S = st(L);
+ return S.top - S.ci.func - 1;
+ }
+
+ static long lua_getupvalue(long L, long funcindex, long n) {
+ /*
+ * 与 {@code debug.getupvalue} 同一条 {@code auxupvalue}:C 闭包给
+ * {@code ""}、Lua 闭包给真正的名字、没名字给 {@code "(no name)"}
+ * (不是 {@code "?"}——那是 {@code ldebug.c: upvalname} 的答法)。
+ */
+ LuaState S = st(L);
+ LuaValue f = val(S, (int) funcindex);
+ LuaValue[] v = new LuaValue[1];
+ String name = (f == null) ? null : DebugLib.auxUpValue(f, (int) n, v, null);
+ if (name == null) {
+ return 0;
+ }
+ pushVal(S, v[0]);
+ return strToNative(name);
+ }
+
+ static long lua_iscfunction(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ return (v.tt == Lua.VLCF || v.tt == Lua.VCCL) ? 1 : 0;
+ }
+
+ static long lua_isinteger(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ return (v != null && v.isInteger()) ? 1 : 0;
+ }
+
+ static long lua_isnumber(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ return (v != null && (v.isNumber() || Vm.strToNum(v) != null)) ? 1 : 0;
+ }
+
+ static long lua_isstring(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ return (v != null && (v.isString() || v.isNumber())) ? 1 : 0;
+ }
+
+ static long lua_isuserdata(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ return (v.baseType() == Lua.TUSERDATA || v.tt == Lua.TLIGHTUSERDATA) ? 1 : 0;
+ }
+
+ static long lua_isyieldable(long L) {
+ /*
+ * {@code ldo.c: yieldable(L)} 就是 {@code L->nNy == 0}:每进一层不可
+ * yield 的 C 帧就 +1。主线程恒为 1({@code lua_newstate} 里的
+ * {@code incnny}),所以「能不能 yield」和「在不在协程里」是同一件事。
+ */
+ return ((L == 0) ? false : st(L).isYieldable()) ? 1 : 0;
+ }
+
+ static void lua_len(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ grow(S, 1);
+ S.stack[S.top].setNil();
+ Vm.objLen(S, S.top, v);
+ S.top++;
+ }
+
+ @Generated
+ static long lua_load(long L, long reader, long dt, long chunkname, long mode) {
+ throw new IllegalStateException("lua_load 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ /*
+ * {@code lua_newstate}:起一个全新的 Lua 状态。
+ *
+ * 与 {@link #luaL_newstate} 的差别是不开库——C 版也是这样,
+ * {@code luaL_openlibs} 得调用方自己喊。
+ *
+ * 分配器那两个参数由 {@code luashim.c} 的手写 stub 接住(它顺手把进程级那
+ * 份 {@code g_allocf} 也换掉,于是 {@code lua_getallocf} 能原样交回去)。
+ */
+ static long lua_newstate(long f, long ud) {
+ return NativeBridge.handle(new LuaState());
+ }
+
+ static long lua_newthread(long L) {
+ LuaState S = st(L);
+ LuaState co = S.newThread();
+ pushVal(S, LuaValue.ofThread(co));
+ return NativeBridge.handle(co);
+ }
+
+ static long lua_newuserdatauv(long L, long sz, long nuvalue) {
+ LuaState S = st(L);
+ NativeMemory m = new NativeMemory(NativeBridge.alloc(sz), sz);
+ /*
+ * 载荷就是 {@code m} 本身(元表、用户值挂在 {@link LuaUserdata} 那层上)。
+ * 这里必须走 {@code ofUserdata(LuaUserdata)} 那条重载:
+ * 之前先 {@code new LuaUserdata(m)} 再交给 {@code ofUserdata(Object)},
+ * 等于套了两层,于是 {@code lua_touserdata} 取到的 {@code data} 是个
+ * {@link LuaUserdata} 而不是 {@link NativeMemory},一律给 0 ——
+ * {@code luaL_checkudata} 因此连自己刚建出来的 userdata 都不认
+ * (报 {@code NatBox expected, got NatBox})。
+ * 今天 {@code ofUserdata(Object)} 对 {@link LuaUserdata} 参数直接转发,
+ * 所以两层写法也能跑;但用户值数组仍然只建在里面那层上,外面那层
+ * {@code nuvalue} 恒为 0,{@code lua_getiuservalue} 永远答
+ * {@code LUA_TNONE}——这是个「能跑但全错」的坑,留着注释别再踩回去。
+ */
+ pushVal(S, LuaValue.ofUserdata(new LuaUserdata(m, (int) nuvalue, (int) sz)));
+ return m.ptr;
+ }
+
+ static long lua_next(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ if (!t.isTable()) {
+ throw S.runError("'__next' not implemented for this type");
+ }
+ LuaValue key = S.stack[S.top - 1];
+ LuaValue[] kv = t.asTable().next(key.isNil() ? null : key);
+ if (kv == null) {
+ S.top -= 1;
+ return 0;
+ }
+ S.stack[S.top - 1].set(kv[0]);
+ grow(S, 1);
+ S.stack[S.top++].set(kv[1]);
+ return 1;
+ }
+
+ static long lua_pcallk(long L, long nargs, long nresults, long errfunc, long ctx, long k) {
+ LuaState S = st(L);
+ int funcIdx = S.top - (int) nargs - 1;
+ int ef = (int) errfunc;
+ LuaValue handler = ef == 0 ? null
+ : (ef == REGISTRY ? null : val(S, ef));
+ if (k != 0 && S.yieldable()) {
+ /*
+ * lapi.c 这个分支不建本地恢复点:错误(和 yield)一路冒到
+ * {@code lua_resume},由 {@code precover} → {@code finishpcallk}
+ * 收尾,收完之后才调续体 —— 续体拿到的 {@code status} 就是那个错误码。
+ * 所以这里不接 {@code LuaError},让它照 C 那样穿出去。
+ */
+ S.pcallK(funcIdx, (int) nresults, handler, (int) ctx,
+ new NativeKFunction(k));
+ return 0;
+ }
+ LuaValue err = S.pcall(funcIdx, (int) nresults, handler);
+ if (err == null) {
+ return 0;
+ }
+ /*
+ * {@code ldo.c: luaD_pcall} 出错后走 {@code luaD_seterrorobj(L, status,
+ * restorestack(old_top))}:错误值被写回函数原来那一格,且
+ * {@code L->top = oldtop + 1} —— 无论 {@code nresults} 是多少,出错时
+ * 栈上都只有这一个值。不写的话那一格还是被调用的函数,C 侧
+ * {@code lua_tostring(L, -1)} 拿到的就是一个函数。
+ */
+ S.top = funcIdx + 1;
+ S.stack[funcIdx].set(err);
+ return LuaState.LUA_ERRRUN;
+ }
+
+ static void lua_pushboolean(long L, long b) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofBoolean(b != 0));
+ }
+
+ static void lua_pushcclosure(long L, long fn, long n) {
+ LuaState S = st(L);
+ int nup = (int) n;
+ grow(S, 1);
+ LuaFunction f = new LuaNativeFunction(fn);
+ if (nup == 0) {
+ S.stack[S.top++].setObject(Lua.VLCF, f);
+ } else {
+ CClosure cc = new CClosure(f, nup);
+ for (int i = 0; i < nup; i++) {
+ cc.upvalue[i].set(S.stack[S.top - nup + i]);
+ }
+ S.top -= nup;
+ S.stack[S.top++].setObject(Lua.VCCL, cc);
+ }
+ }
+
+ @Generated
+ static long lua_pushfstring(long L, long fmt, long a2) {
+ throw new IllegalStateException("lua_pushfstring 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static void lua_pushinteger(long L, long n) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofInteger(n));
+ }
+
+ static void lua_pushlightuserdata(long L, long p) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofLightUserdata(Long.valueOf(p)));
+ }
+
+ static long lua_pushlstring(long L, long s, long len) {
+ LuaState S = st(L);
+ byte[] b = (s == 0 || len <= 0) ? new byte[0] : NativeBridge.bytesAtN(s, (int) len);
+ pushVal(S, LuaValue.ofString(LuaString.of(b)));
+ return NativeBridge.strToNative(b);
+ }
+
+ static void lua_pushnil(long L) {
+ LuaState S = st(L);
+ grow(S, 1);
+ S.stack[S.top++].setNil();
+ }
+
+ static void lua_pushnumber(long L, double n) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofFloat(n));
+ }
+
+ static long lua_pushstring(long L, long s) {
+ LuaState S = st(L);
+ if (s == 0) {
+ grow(S, 1);
+ S.stack[S.top++].setNil();
+ return 0;
+ }
+ byte[] b = NativeBridge.bytesAt(s);
+ pushVal(S, LuaValue.ofString(LuaString.of(b)));
+ return NativeBridge.strToNative(b);
+ }
+
+ static long lua_pushthread(long L) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofThread(S));
+ return S.mainThread == S ? 1 : 0;
+ }
+
+ static void lua_pushvalue(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ pushVal(S, v == null ? LuaValue.ofNil() : v);
+ }
+
+ @Generated
+ static long lua_pushvfstring(long L, long fmt, long argp) {
+ throw new IllegalStateException("lua_pushvfstring 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static long lua_rawequal(long L, long idx1, long idx2) {
+ LuaState S = st(L);
+ LuaValue a = val(S, (int) idx1);
+ LuaValue b = val(S, (int) idx2);
+ return (a != null && b != null && LuaValue.rawEqual(a, b)) ? 1 : 0;
+ }
+
+ static long lua_rawget(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue key = S.stack[--S.top];
+ LuaValue r = t.isTable() ? rawSlot(t.asTable(), key) : LuaValue.ofNil();
+ grow(S, 1);
+ S.stack[S.top++].set(r == null ? LuaValue.ofNil() : r);
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_rawgeti(long L, long idx, long n) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue r = t.isTable() ? rawSlot(t.asTable(), LuaValue.ofInteger(n)) : LuaValue.ofNil();
+ grow(S, 1);
+ S.stack[S.top++].set(r == null ? LuaValue.ofNil() : r);
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_rawgetp(long L, long idx, long p) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ /*
+ * 键是那个指针本身推成的轻量 userdata,不是字符串:C 版
+ * {@code lua_rawgetp} 就是 {@code luaH_get(hvalue(t),
+ * luaP_lightuserdata(L, p))}。注册表里「按 C 地址存东西」的扩展
+ * (给每个 userdata 挂一份宿主状态)全靠这条,之前这里只推了个 nil,
+ * 取回来恒是 nil。
+ */
+ LuaValue key = LuaValue.ofLightUserdata(Long.valueOf(p));
+ LuaValue r = (t != null && t.isTable()) ? rawSlot(t.asTable(), key) : null;
+ pushVal(S, (r == null) ? LuaValue.ofNil() : r);
+ return ttype(S.stack[S.top - 1]);
+ }
+
+ static long lua_rawlen(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ if (v.isString()) {
+ return v.asString().length();
+ }
+ if (v.isTable()) {
+ return v.asTable().length();
+ }
+ if (v.baseType() == Lua.TUSERDATA) {
+ LuaUserdata ud = (LuaUserdata) v.o;
+ /*
+ * C 是 {@code uvalue(o)->len}:与载荷是什么无关,就是建
+ * userdata 时那一段的字节数。早先这里只看「载荷是不是
+ * {@link NativeMemory}」,于是我们自己造的文件句柄一律答 0
+ * (参考答 16,见 {@link LuaUserdata#len})。
+ */
+ return ud.len;
+ }
+ return 0;
+ }
+
+ static void lua_rawset(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ LuaValue key = new LuaValue(S.stack[S.top - 2]);
+ if (t.isTable()) {
+ t.asTable().set(key, val_);
+ }
+ S.top -= 2;
+ }
+
+ static void lua_rawseti(long L, long idx, long n) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ if (t.isTable()) {
+ t.asTable().set(LuaValue.ofInteger(n), val_);
+ }
+ S.top -= 1;
+ }
+
+ static void lua_rawsetp(long L, long idx, long p) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ if (t != null && t.isTable()) {
+ t.asTable().set(LuaValue.ofLightUserdata(Long.valueOf(p)), val_);
+ }
+ S.top -= 1;
+ }
+
+ /** {@code lua_resetthread} 已废弃,等价于 {@code lua_closethread(L, NULL)} */
+ static long lua_resetthread(long L) {
+ return st(L).closeThread(null);
+ }
+
+ /*
+ * {@code lua_resume}。注意 {@code nres} 是 C 侧的出参:
+ * {@code luashim.c} 的手写 stub 在 Java 返回后把它写回 {@code *nres},
+ * 这里拿到的那个值用不上。返回值打包成 {@code status << 32 | nresults},
+ * C 侧再拆开。
+ */
+ static long lua_resume(long L, long from, long narg, long nres) {
+ LuaState S = st(L);
+ LuaState f = (from == 0) ? null : st(from);
+ int[] out = new int[1];
+ int r = S.resume(f, (int) narg, out);
+ return ((long) r << 32) | (out[0] & 0xFFFFFFFFL);
+ }
+
+ static void lua_rotate(long L, long idx, long n) {
+ LuaState S = st(L);
+ int p = (int) idx > 0 ? S.ci.func + (int) idx : S.top + (int) idx;
+ int t = S.top - 1;
+ int m = ((int) n >= 0) ? t - (int) n : p - (int) n - 1;
+ reverse(S, p, m);
+ reverse(S, m + 1, t);
+ reverse(S, p, t);
+ }
+
+ /** 同 {@link #lua_getallocf}:整对在 {@code luashim.c} 里就地实现 */
+ @Generated
+ static void lua_setallocf(long L, long f, long ud) {
+ throw new IllegalStateException("lua_setallocf 在 luashim.c 里就地实现,不进 Java");
+ }
+
+ /*
+ * lstate.c:5.4.7 里这个函数什么都不做,恒返回 {@code LUAI_MAXCCALLS},
+ * 参数连 {@code UNUSED} 都懒得写——别照名字去实现「设上限」。
+ * {@code ldblib.c} 的 {@code debug.setcstacklimit} 就靠它把旧值带回去。
+ */
+ static long lua_setcstacklimit(long L, long limit) {
+ return LuaState.MAXCCALLS;
+ }
+
+ static void lua_setfield(long L, long idx, long k) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ LuaString key = LuaString.of(NativeBridge.bytesAt(k));
+ Vm.setTable(S, t, LuaValue.ofString(key), val_);
+ S.top -= 1;
+ }
+
+ static void lua_setglobal(long L, long name) {
+ LuaState S = st(L);
+ LuaString key = LuaString.of(NativeBridge.bytesAt(name));
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ Vm.setTable(S, LuaValue.ofTable(S.globals), LuaValue.ofString(key), val_);
+ S.top -= 1;
+ }
+
+ static void lua_sethook(long L, long func, long mask, long count) {
+ /*
+ * C 的钩子是 {@code void (*)(lua_State*, lua_Debug*)},装进来得先套一层
+ * {@link LuaHook}。两端的桥是那块 {@code lua_Debug}:Java 侧填好字段
+ * (含 {@code event}),回调时把地址交给 C 函数——它读的正是
+ * {@code ar->event} / {@code ar->currentline} 这些。
+ *
+ * 尺寸按 {@code sizeof(lua_Debug)} 现取,不写死:{@code LUA_IDSIZE}
+ * 与结尾那段私有区都可能随版本变,写死就是越界写。
+ */
+ LuaState S = st(L);
+ int m = (int) mask;
+ S.setHook((func == 0 || m == 0) ? null : new NativeHook(func), m, (int) count);
+ }
+
+ static void lua_seti(long L, long idx, long n) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ Vm.setTable(S, t, LuaValue.ofInteger(n), val_);
+ S.top -= 1;
+ }
+
+ static long lua_setiuservalue(long L, long idx, long n) {
+ LuaState S = st(L);
+ int res = 0;
+ LuaUserdata ud = udata(val(S, (int) idx));
+ if (ud != null && ud.userValues != null && n >= 1 && n <= ud.userValues.length) {
+ ud.userValues[(int) n - 1].set(S.stack[S.top - 1]);
+ res = 1;
+ }
+ S.top -= 1; // 无论成不成都要弹掉那个值
+ return res;
+ }
+
+ static long lua_setlocal(long L, long ar, long n) {
+ LuaState S = st(L);
+ DebugLib.LuaDebug d = (ar == 0) ? null : arOf(ar);
+ if (d == null) {
+ return 0; // C 版 ar 为 NULL 时 luaG_findlocal 会解引用野指针
+ }
+ String name = DebugLib.setLocal(S, d, (int) n);
+ return (name == null) ? 0 : strToNative(name);
+ }
+
+ static long lua_setmetatable(long L, long objindex) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) objindex);
+ LuaValue mt = S.stack[--S.top];
+ LuaTm.setMetatable(S, v, mt.isNil() ? null : mt.asTable());
+ return 1;
+ }
+
+ static void lua_settable(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue t = val(S, (int) idx);
+ LuaValue val_ = new LuaValue(S.stack[S.top - 1]);
+ LuaValue key = new LuaValue(S.stack[S.top - 2]);
+ Vm.setTable(S, t, key, val_);
+ S.top -= 2;
+ }
+
+ static void lua_settop(long L, long idx) {
+ LuaState S = st(L);
+ int base = S.ci.func + 1;
+ int t;
+ if (idx >= 0) {
+ t = base + (int) idx;
+ } else {
+ t = S.top + (int) idx + 1;
+ if (t < base) {
+ t = base; // 负过头:C 里是 api_check,UB
+ }
+ }
+ if (t > S.top) {
+ grow(S, t - S.top);
+ while (S.top < t) {
+ S.stack[S.top++].setNil();
+ }
+ } else {
+ while (S.top > t) {
+ S.stack[--S.top].setNil();
+ }
+ }
+ }
+
+ static long lua_setupvalue(long L, long funcindex, long n) {
+ LuaState S = st(L);
+ LuaValue f = val(S, (int) funcindex);
+ LuaValue[] v = new LuaValue[1];
+ String name = (f == null) ? null : DebugLib.auxUpValue(f, (int) n, v, null);
+ if (name == null) {
+ return 0;
+ }
+ LuaValue src = new LuaValue(S.stack[S.top - 1]);
+ v[0].set(src);
+ S.top -= 1;
+ return strToNative(name);
+ }
+
+ @Generated
+ /*
+ * {@code lapi.c: lua_setwarnf}。三个取值在 {@link LuaState#setWarnf} 里写清楚了;
+ * 这里只强调一点:{@code f == 0} 是「设成 NULL」而不是「恢复默认」——
+ * C 里 {@code luaE_warning} 的判据是 {@code wf != NULL},设成 NULL 之后
+ * 连 {@code warn("@on")} 都开不了开关。
+ */
+ static void lua_setwarnf(long L, long f, long ud) {
+ st(L).setWarnf(f, ud);
+ }
+
+ static long lua_status(long L) {
+ return (L == 0) ? 0 : st(L).getStatus();
+ }
+
+ static long lua_stringtonumber(long L, long s) {
+ /*
+ * {@code lapi.c: lua_stringtonumber} = {@code luaO_str2num} + 压栈,
+ * 返回 {@code strlen(s) + 1}。
+ *
+ * 那个 {@code +1} 不是笔误:{@code l_str2int} / {@code l_str2dloc}
+ * 返回的是结尾那个 {@code '\0'} 的地址,{@code luaO_str2num}
+ * 再写 {@code (e - s) + 1}。三个内部调用点
+ * ({@code luaB_tonumber}、{@code l_strlib}、{@code liolib} 的
+ * {@code readnum})一律比 {@code == len + 1},也就是「整串都得是数字」——
+ * 所以 {@code lua_stringtonumber("42abc")} 是 0({@code tonumber
+ * ("42abc")} 给 nil 就是这么来的),不是「认出 42 返回 2」。
+ *
+ * 本实现 {@link Numeral#str2num} 本来就要求整串吃完(词法分析要的是
+ * 这个语义),正好对上;C 版那句 {@code *s != '\0'} 的判据也一致。
+ */
+ LuaState S = st(L);
+ byte[] b = NativeBridge.bytesAt(s);
+ if (b == null) {
+ return 0;
+ }
+ LuaValue v = Numeral.str2num(new String(b, java.nio.charset.StandardCharsets.ISO_8859_1));
+ if (v == null) {
+ return 0;
+ }
+ pushVal(S, v);
+ return b.length + 1;
+ }
+
+ static long lua_toboolean(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ return (v != null && v.toBoolean()) ? 1 : 0;
+ }
+
+ static long lua_tocfunction(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ if (v.tt == Lua.VLCF) {
+ /*
+ * 只有从 .so 里来的才有真 C 指针;标准库那些是 Java
+ * lambda,没有地址可给。但 C 版对它们同样返回非 NULL(它认的是
+ * 「是不是 C 函数」,不是「有没有地址」),所以这里退回句柄——
+ * 与 {@code lua_topointer} 一致,非 0 即可。直接 cast 会在
+ * {@code print} 这类库函数上抛 ClassCastException。
+ */
+ return (v.o instanceof LuaNativeFunction)
+ ? ((LuaNativeFunction) v.o).fn : NativeBridge.handle(v.o);
+ }
+ if (v.tt == Lua.VCCL) {
+ Object f = ((CClosure) v.o).f;
+ return (f instanceof LuaNativeFunction)
+ ? ((LuaNativeFunction) f).fn : NativeBridge.handle(f);
+ }
+ return 0;
+ }
+
+ @Generated
+ /*
+ * {@code lapi.c: lua_toclose}:把一个栈槽标成 to-be-closed,等这个 C
+ * 函数返回时关掉。
+ *
+ * 「本函数有待关闭变量」在 C 里编码在 {@code ci->nresults} 上:
+ * {@code codeNresults(n) = -n - 3},于是任何 {@code n} 编码后都
+ * {@code < LUA_MULTRET},{@code hastocloseCfunc} 一眼能认出来,
+ * {@code decodeNresults} 又能还原。本实现不另开一个 boolean —— 那会让
+ * {@code moveresults} 里「先关变量还是先报返回钩子」的顺序失去依据。
+ */
+ static void lua_toclose(long L, long idx) {
+ LuaState S = st(L);
+ S.newTbcUpval(stackIdx(S, (int) idx));
+ if (S.ci.nresults >= LuaState.MULTRET) {
+ S.ci.nresults = -S.ci.nresults - 3;
+ }
+ }
+
+ /**
+ * {@code lvm.c: luaV_tointeger(o, &res, F2Ieq)}:先 {@code l_strton} 把
+ * 字符串转成数字,再判「恰好是整数」。
+ *
+ * 少了「转成数字之后再判一遍」这一句,{@code lua_tointegerx(L, i)}
+ * 在 {@code "42.0"} 上给 {@code 0/false} 而参考实现给
+ * {@code 42/true} —— 字符串转出来是浮点 42.0,它确实恰好是整数值,
+ * 只是不在整数子类型里。
+ */
+ private static boolean toIntegerExact(LuaValue v, long[] out) {
+ if (v == null) {
+ return false;
+ }
+ if (v.isInteger()) {
+ out[0] = v.asInteger();
+ return true;
+ }
+ if (v.isFloat()) {
+ return LuaValue.floatToIntExact(v.asFloat(), out);
+ }
+ if (v.isString()) {
+ LuaValue n = Vm.strToNum(v);
+ if (n == null) {
+ return false;
+ }
+ if (n.isInteger()) {
+ out[0] = n.asInteger();
+ return true;
+ }
+ return LuaValue.floatToIntExact(n.asFloat(), out);
+ }
+ return false;
+ }
+
+ static long lua_tointegerx(long L, long idx, long isnum) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ long[] out = new long[1];
+ int ok = toIntegerExact(v, out) ? 1 : 0;
+ if (isnum != 0) {
+ NativeBridge.put32(isnum, ok);
+ }
+ return ok != 0 ? out[0] : 0;
+ }
+
+ static long lua_tolstring(long L, long idx, long len) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null || !(v.isString() || v.isNumber())) {
+ if (len != 0) {
+ NativeBridge.put64(len, 0);
+ }
+ return 0;
+ }
+ LuaString str;
+ if (v.isString()) {
+ str = v.asString();
+ } else {
+ /*
+ * luaO_tostring:原地把栈上那个数字换成字符串。这一步是可观测的——
+ * 之后 lua_type(L, idx) 就是 string 了。
+ */
+ str = LuaString.latin1(Vm.numberToString(v));
+ setVal(S, (int) idx, LuaValue.ofString(str));
+ }
+ if (len != 0) {
+ NativeBridge.put64(len, str.length());
+ }
+ return str.nativePtr();
+ }
+
+ static double lua_tonumberx(long L, long idx, long isnum) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ int ok = 0;
+ double r = 0;
+ if (v != null) {
+ if (v.isNumber()) {
+ r = v.isInteger() ? (double) v.asInteger() : v.asFloat();
+ ok = 1;
+ } else if (v.isString()) {
+ LuaValue n = Vm.strToNum(v);
+ if (n != null) {
+ r = n.isInteger() ? (double) n.asInteger() : n.asFloat();
+ ok = 1;
+ }
+ }
+ }
+ if (isnum != 0) {
+ NativeBridge.put32(isnum, ok);
+ }
+ return r;
+ }
+
+ /**
+ * {@code lapi.c: lua_topointer} 的取值部分,与 {@code luaL_tolstring} 里
+ * {@code "%p"} 打的是同一个值。
+ *
+ * 轻量 userdata 这一支必须取载荷里的指针值,不能退化成对象的
+ * identity hash:C 的 {@code lua_topointer} 对 {@code LUA_TLIGHTUSERDATA}
+ * 返回的正是 {@code pvalue(o)}。{@code cjson.null} 是
+ * {@code lua_pushlightuserdata(L, NULL)} 推出来的,参考实现
+ * {@code print(cjson.null)} 打的是 {@code userdata: 0x0}——拿 identity
+ * hash 顶上会打出一串看着很像、其实毫无意义的十六进制。
+ */
+ static long topointer(LuaValue v) {
+ if (v == null) {
+ return 0;
+ }
+ if (v.baseType() == Lua.TUSERDATA) {
+ Object d = ((LuaUserdata) v.o).data;
+ return d instanceof NativeMemory ? ((NativeMemory) d).ptr : 0;
+ }
+ if (v.tt == Lua.TLIGHTUSERDATA) {
+ return v.o instanceof Long ? ((Long) v.o).longValue() : 0;
+ }
+ switch (v.baseType()) {
+ case Lua.TTABLE:
+ case Lua.TFUNCTION:
+ case Lua.TTHREAD:
+ return NativeBridge.handle(v.o);
+ default:
+ return 0;
+ }
+ }
+
+ static long lua_topointer(long L, long idx) {
+ /*
+ * {@code lapi.c: lua_topointer} 的 switch 没有 default 分支指向对象:
+ * 数字、字符串、布尔、nil 一律落到 {@code return NULL}。
+ * 之前这里拿 {@code NativeBridge.handle} 兜底,于是
+ * {@code lua_topointer(L, 1) != NULL} 在数字上给 true,而参考实现给
+ * false —— 扩展里「这个值有没有地址可当键用」的判断会整个反过来。
+ */
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ return topointer(v);
+ }
+
+ static long lua_tothread(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ return (v != null && v.isThread()) ? NativeBridge.handle(v.asThread()) : 0;
+ }
+
+ static long lua_touserdata(long L, long idx) {
+ LuaState S = st(L);
+ LuaValue v = val(S, (int) idx);
+ if (v == null) {
+ return 0;
+ }
+ if (v.baseType() == Lua.TUSERDATA) {
+ Object d = ((LuaUserdata) v.o).data;
+ return d instanceof NativeMemory ? ((NativeMemory) d).ptr : 0;
+ }
+ if (v.tt == Lua.TLIGHTUSERDATA) {
+ return v.o instanceof Long ? ((Long) v.o).longValue() : 0;
+ }
+ return 0;
+ }
+
+ static long lua_type(long L, long idx) {
+ return typeOf(st(L), (int) idx);
+ }
+
+ static long lua_typename(long L, long tp) {
+ /*
+ * luaT_typenames_[tp],按基础类型取——lua_type 返回的已经是基础
+ * 类型,不用再过一遍 noVariant。LUA_TNONE(-1) 落在 default 上,给
+ * "no value",与参考实现一致。
+ */
+ return strToNative(Lua.typeName((int) tp));
+ }
+
+ static long lua_upvalueid(long L, long fidx, long n) {
+ /*
+ * 返回的是「身份」:Lua 闭包给 {@link UpVal} 对象、C 闭包给那个
+ * {@link LuaValue} 槽——与 {@code debug.upvalueid} 同一个
+ * {@code ownerOut},所以两个闭包共享 upvalue 时两边都相等。
+ * C 版交出去的是 {@code void*},这里只能给一个稳定句柄:
+ * {@code NativeBridge#handle} 的下标,{@code != NULL} 与
+ * 「两个 id 相等」两种判断都还成立。
+ */
+ LuaState S = st(L);
+ LuaValue f = val(S, (int) fidx);
+ Object[] owner = new Object[1];
+ if (f == null || DebugLib.auxUpValue(f, (int) n, new LuaValue[1], owner) == null
+ || owner[0] == null) {
+ return 0;
+ }
+ return NativeBridge.handle(owner[0]);
+ }
+
+ static void lua_upvaluejoin(long L, long fidx1, long n1, long fidx2, long n2) {
+ /*
+ * 只接 Lua 闭包:C 的 {@code getupvalref} 对 C 闭包返回的是
+ * {@code &f->upvalue[n-1]} 的地址,而 {@code *up1 = *up2} 换的是
+ * {@code UpVal*}——C 闭包没有 {@code UpVal},{@code lua_upvalueid}
+ * 虽然给得出地址,join 这一步在 C 里也只有 Lua 闭包走得通
+ * ({@code ldblib.c} 也是先 {@code "Lua function expected"})。
+ */
+ LuaState S = st(L);
+ LuaValue f1 = val(S, (int) fidx1);
+ LuaValue f2 = val(S, (int) fidx2);
+ Object[] o1 = new Object[1];
+ Object[] o2 = new Object[1];
+ if (f1 == null || f2 == null
+ || DebugLib.auxUpValue(f1, (int) n1, new LuaValue[1], o1) == null
+ || DebugLib.auxUpValue(f2, (int) n2, new LuaValue[1], o2) == null) {
+ return; // 越界:C 里是断言
+ }
+ if (f1.tt != Lua.VLIF || f2.tt != Lua.VLIF) {
+ return;
+ }
+ ((LClosure) f1.o).setUpVal((int) n1 - 1, (UpVal) o2[0]);
+ }
+
+ /**
+ * {@code lapi.c: lua_version} —— 恒返回 {@code LUA_VERSION_NUM},
+ * 连 {@code L} 都不看({@code UNUSED(L)})。
+ *
+ * 几乎所有 C 扩展的 {@code luaopen_xxx} 开头都有
+ * {@code luaL_checkversion(L)},那个宏展开就是调它。
+ */
+ static double lua_version(long L) {
+ return Lua.VERSION_NUM;
+ }
+
+ static void lua_warning(long L, long msg, long tocont) {
+ /*
+ * {@code lapi.c: lua_warning} 是 {@code luaE_warning} 的一层薄壳,
+ * 与 {@code warn} 内建({@link BaseLib#warn})
+ * 同一条路——{@code warn("@on")} 那套开关在这里照样生效。
+ * 消息是 {@code const char*},按字节取,别走 Java String。
+ */
+ st(L).warning(LuaString.of(NativeBridge.bytesAt(msg)), tocont != 0);
+ }
+
+ static void lua_xmove(long from, long to, long n) {
+ LuaState.xmove(st(from), st(to), (int) n);
+ }
+
+ /*
+ * {@code lua_yieldk}。
+ *
+ * 这里不抛 {@link LuaYield}:C 侧的 {@code lua_yieldk} stub 在本方法
+ * 返回之后置 {@code g_yielding} 再 {@code longjmp} 出去,
+ * {@code invokeC} 因此返回 {@code -2},{@link LuaNativeFunction} 才抛。
+ * 反过来的话 Java 异常会先被 {@code luashim_call} 捕获、按 error 处理。
+ *
+ * 记账与 {@link LuaState#yieldk} 一致({@code status}、{@code ci.nyield}),
+ * 续体记在 {@code ci} 上——恢复时 {@code doResume} / {@code unroll} 会调它。
+ * {@code k == 0} 就是不续:C 版 {@code luaB_yield} 正是这么用的,恢复时
+ * 直接把 resume 的参数当成那个 C 调用的返回值。
+ */
+ static long lua_yieldk(long L, long nresults, long ctx, long k) {
+ LuaState S = st(L);
+ if (!S.yieldable()) {
+ throw S.runError((S.mainThread == S)
+ ? "attempt to yield from outside a coroutine"
+ : "attempt to yield across a C-call boundary");
+ }
+ S.status = LuaState.LUA_YIELD;
+ S.ci.nyield = (int) nresults;
+ S.ci.k = (k == 0) ? null : new NativeKFunction(k);
+ S.ci.ctx = (int) ctx;
+ return 0;
+ }
+
+ static void luaL_openlibs(long L) {
+ /*
+ * C 版是逐个 {@code luaL_requiref(L, name, luaopen_X, 1)},而
+ * {@code luaL_requiref} 命中 {@code _LOADED} 就跳过——所以
+ * {@code luaL_openlibs} 可以连调两次。直接开十次会在
+ * {@code io} 上炸({@code FILE*} 元表已存在),实测参考实现调两次
+ * 安静无事。
+ */
+ LuaLibs.openAllSkippingLoaded(st(L));
+ }
+
+ /* ================================================================
+ * lualib.h: luaopen_*
+ * ================================================================ */
+
+ /*
+ * 这十个是符号表对撞撞出来的:{@code nm liblua.a} 的导出符号里有它们,
+ * 我们的 dylib 里一个都没有——因为 {@code lualib.h} 用的是
+ * {@code LUAMOD_API} 而不是 {@code LUALIB_API},生成器原本只认后两个,
+ * 于是整整十个缺席。光看「头文件里有没有」看不出来。
+ *
+ * 语义要和 {@code luaL_requiref} 分清:{@code luaopen_X} 不动全局表、
+ * 不动 {@code package.loaded},它只是 {@code luaL_newlib}——
+ * 造一张全新的表,把库函数填进去,压栈返回 1。
+ * 所以 {@code rawequal(n.openlib("table"), table)} 参考给 false:
+ * 每次调都是一张新表,改它不影响真的 {@code table}。
+ * 第一版这里压的是 {@code package.loaded} 里那份,整好反了。
+ */
+ private static long openLib(LuaState S, String name,
+ java.util.function.Consumer
+ * 库表都是平的(键到函数,没有嵌套),浅拷与 C 的 {@code luaL_newlib}
+ * 造出来的那张在观测上一致:同一个 {@code next} 序列、同样改它不影响原表。
+ * 之所以不直接「再跑一遍 open」:{@code IoLib} 会去建 {@code FILE*} 元表,
+ * 建第二遍就炸({@code luaL_openlibs} 连调两次都得靠
+ * {@code openAllSkippingLoaded} 绕开,同一件事)。
+ */
+ private static LuaTable freshCopy(LuaValue src) {
+ LuaTable dst = new LuaTable();
+ if (src == null || !src.isTable()) {
+ return dst;
+ }
+ LuaTable t = src.asTable();
+ LuaValue k = LuaValue.ofNil();
+ while (true) {
+ LuaValue[] kv = t.next(k);
+ if (kv == null) {
+ break;
+ }
+ dst.set(new LuaValue(kv[0]), new LuaValue(kv[1]));
+ k = kv[0];
+ }
+ return dst;
+ }
+
+ static long luaopen_base(long L) {
+ LuaState S = st(L);
+ if (S.globals.get("print") == null) {
+ BaseLib.open(S);
+ }
+ /*
+ * {@code lbaselib.c: luaopen_base} 与其余九个不同:它 {@code luaL_setfuncs}
+ * 到全局表上,压回来的是全局表本身 —— 不是一张新表,
+ * 也不是 {@code package.loaded._G}(那份压根不存在)。
+ */
+ grow(S, 1);
+ S.stack[S.top++].setTable(S.globals);
+ return 1;
+ }
+
+ static long luaopen_coroutine(long L) {
+ return openLib(st(L), "coroutine", CoroutineLib::open);
+ }
+
+ static long luaopen_table(long L) {
+ return openLib(st(L), "table", TableLib::open);
+ }
+
+ static long luaopen_io(long L) {
+ return openLib(st(L), "io", IoLib::open);
+ }
+
+ static long luaopen_os(long L) {
+ return openLib(st(L), "os", OsLib::open);
+ }
+
+ static long luaopen_string(long L) {
+ return openLib(st(L), "string", StringLib::open);
+ }
+
+ static long luaopen_math(long L) {
+ return openLib(st(L), "math", MathLib::open);
+ }
+
+ static long luaopen_utf8(long L) {
+ return openLib(st(L), "utf8", Utf8Lib::open);
+ }
+
+ static long luaopen_debug(long L) {
+ return openLib(st(L), "debug", DebugLib::open);
+ }
+
+ static long luaopen_package(long L) {
+ return openLib(st(L), "package", PackageLib::open);
+ }
+
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/Arith.java b/src/main/java/com/github/esrrhs/javalua/Arith.java
new file mode 100644
index 0000000..4a314ba
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/Arith.java
@@ -0,0 +1,285 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * 常量折叠需要的那部分算术 —— 对应 {@code lobject.c} 的 {@code luaO_rawarith} /
+ * {@code intarith} / {@code numarith},以及 {@code lvm.c} 的 {@code luaV_idiv} /
+ * {@code luaV_mod} / {@code luaV_modf} / {@code luaV_shiftl}。
+ *
+ * 为什么现在就要它:编译期的常量折叠跑的就是这套运算,折叠结果会被直接写进
+ * 常量表。若折叠用的算术与运行期的不是同一套,就会出现
+ * 「同一个表达式,常量写法与变量写法结果不同」——那是最难查的一类 bug。
+ *
+ * 几处刻意的「不像 Java」:
+ *
+ * {@code L} 只为一件事服务:{@code luaV_idiv} / {@code luaV_mod} 在除数为零时
+ * 要抛 {@code luaG_runerror},而后者当场就把 {@code file:line:} 前缀
+ * 拼进消息里({@code luaG_addinfo})。没有 {@code L} 就抛裸消息,
+ * 位置得等到保护边界才补——而补的时候出错的那个帧已经不在了:
+ * {@code coroutine.resume} 把错误当值返回时,参考实现给的是
+ * {@code file:line: attempt to divide by zero},本实现只给后半句。
+ *
+ * {@code L == null} 是编译期常量折叠那一路:{@code validOp} 已经把除零
+ * 排除掉了,那里不会走到这两个分支。
+ *
+ * @return 结果值;操作数类型不合适时返回 {@code null}
+ */
+ static LuaValue rawArith(LuaState L, int op, LuaValue p1, LuaValue p2) {
+ switch (op) {
+ case OP_BAND:
+ case OP_BOR:
+ case OP_BXOR:
+ case OP_SHL:
+ case OP_SHR:
+ case OP_BNOT: { // 位运算只在整数上做
+ Long i1 = toIntegerNs(p1);
+ Long i2 = toIntegerNs(p2);
+ if (i1 == null || i2 == null) {
+ return null;
+ }
+ return LuaValue.ofInteger(intArith(L, op, i1, i2));
+ }
+ case OP_DIV:
+ case OP_POW: { // 只在浮点上做
+ Double n1 = toNumberNs(p1);
+ Double n2 = toNumberNs(p2);
+ if (n1 == null || n2 == null) {
+ return null;
+ }
+ return LuaValue.ofFloat(numArith(op, n1, n2));
+ }
+ default: {
+ if (p1.isInteger() && p2.isInteger()) {
+ return LuaValue.ofInteger(intArith(L, op, p1.i, p2.i));
+ }
+ Double n1 = toNumberNs(p1);
+ Double n2 = toNumberNs(p2);
+ if (n1 == null || n2 == null) {
+ return null;
+ }
+ return LuaValue.ofFloat(numArith(op, n1, n2));
+ }
+ }
+ }
+
+ private static Double toNumberNs(LuaValue v) {
+ if (v.isInteger()) {
+ return (double) v.i;
+ }
+ if (v.isFloat()) {
+ return v.n;
+ }
+ return null;
+ }
+
+ /** {@code luaV_tointegerns}:不做「字符串转数字」,只认真正的数字 */
+ static Long toIntegerNs(LuaValue v) {
+ if (v.isFloat()) {
+ return fltToInteger(v.n, F2I_EQ);
+ }
+ if (v.isInteger()) {
+ return v.i;
+ }
+ return null;
+ }
+
+ /* ---------- 浮点 → 整数 ---------- */
+
+ static final int F2I_EQ = 0;
+ static final int F2I_FLOOR = 1;
+ static final int F2I_CEIL = 2;
+
+ /** {@code luaV_flttointeger} */
+ static Long fltToInteger(double n, int mode) {
+ double f = Math.floor(n);
+ if (n != f) { // 不是整值?
+ if (mode == F2I_EQ) {
+ return null; // 要求必须是整值,失败
+ } else if (mode == F2I_CEIL) {
+ f += 1;
+ }
+ }
+ return numberToInteger(f);
+ }
+
+ /** {@code lua_numbertointeger}:超出 int64 可表示范围时失败 */
+ private static Long numberToInteger(double n) {
+ if (n >= (double) Long.MIN_VALUE && n < -((double) Long.MIN_VALUE)) {
+ return (long) n;
+ }
+ return null;
+ }
+
+ /* ---------- 整数运算 ---------- */
+
+ /** {@code intarith} */
+ private static long intArith(LuaState L, int op, long v1, long v2) {
+ switch (op) {
+ case OP_ADD:
+ return v1 + v2;
+ case OP_SUB:
+ return v1 - v2;
+ case OP_MUL:
+ return v1 * v2;
+ case OP_MOD:
+ return mod(L, v1, v2);
+ case OP_IDIV:
+ return idiv(L, v1, v2);
+ case OP_BAND:
+ return v1 & v2;
+ case OP_BOR:
+ return v1 | v2;
+ case OP_BXOR:
+ return v1 ^ v2;
+ case OP_SHL:
+ return shiftl(v1, v2);
+ case OP_SHR:
+ return shiftl(v1, -v2);
+ case OP_UNM:
+ return -v1;
+ case OP_BNOT:
+ return ~v1;
+ default:
+ throw new IllegalArgumentException("bad int op " + op);
+ }
+ }
+
+ /**
+ * {@code luaV_idiv}:向下取整的整数除法。
+ *
+ * 除零要 {@code luaG_runerror},位置前缀是当场加的(见
+ * {@link #rawArith})。留着那条裸 {@code LuaError} 只在
+ * {@code L == null}(常量折叠)时用得上,而那条路进不来这里。
+ */
+ static long idiv(LuaState L, long m, long n) {
+ if (n == 0) {
+ if (L != null) {
+ throw L.runError("attempt to divide by zero");
+ }
+ throw new LuaError(LuaValue.ofString(LuaString.latin1("attempt to divide by zero")));
+ }
+ if (n == -1) {
+ return -m; // 避开 mininteger // -1 的溢出
+ }
+ long q = m / n;
+ if ((m ^ n) < 0 && m % n != 0) {
+ q -= 1;
+ }
+ return q;
+ }
+
+ /** {@code luaV_mod}:结果的符号跟随除数 */
+ static long mod(LuaState L, long m, long n) {
+ if (n == 0) {
+ /*
+ * 注意是 'n%0' 而不是 'n%%0':C 版这里是 printf 的格式串,
+ * {@code %%} 只是转义,真正打出来的是 {@code n%0}。Java 不做
+ * 格式串转义,照抄 {@code %%} 会多出一个百分号。
+ */
+ if (L != null) {
+ throw L.runError("attempt to perform 'n%0'");
+ }
+ throw new LuaError(LuaValue.ofString(LuaString.latin1("attempt to perform 'n%0'")));
+ }
+ if (n == -1) {
+ return 0; // 避开溢出
+ }
+ long r = m % n;
+ if (r != 0 && (r ^ n) < 0) {
+ r += n;
+ }
+ return r;
+ }
+
+ /**
+ * {@code luaV_shiftl}:移位数超出字长时结果是 0(Java 会按 64 取模,不一样)。
+ *
+ * 注意右移是逻辑右移:C 里的 {@code intop(>>, x, -y)} 先把两个操作数
+ * 转成 {@code lua_Unsigned} 再移,空位补 0。所以 {@code -8 >> 2} 是
+ * {@code 0x3FFFFFFFFFFFFFFE}(4611686018427387902)而不是 {@code -2},
+ * 与 Lua 手册「两个移位运算符都用 0 填充空位」一致。
+ * 参考实现实测过,别照 Java 直觉写成 {@code >>}。
+ */
+ static long shiftl(long x, long y) {
+ if (y < 0) {
+ if (y <= -NBITS) {
+ return 0;
+ }
+ return x >>> (-y);
+ }
+ if (y >= NBITS) {
+ return 0;
+ }
+ return x << y;
+ }
+
+ /* ---------- 浮点运算 ---------- */
+
+ /** {@code numarith} */
+ private static double numArith(int op, double v1, double v2) {
+ switch (op) {
+ case OP_ADD:
+ return v1 + v2;
+ case OP_SUB:
+ return v1 - v2;
+ case OP_MUL:
+ return v1 * v2;
+ case OP_DIV:
+ return v1 / v2;
+ case OP_POW:
+ return (v2 == 2) ? v1 * v1 : Math.pow(v1, v2);
+ case OP_IDIV:
+ return Math.floor(v1 / v2);
+ case OP_UNM:
+ return -v1;
+ case OP_MOD:
+ return modf(v1, v2);
+ default:
+ throw new IllegalArgumentException("bad num op " + op);
+ }
+ }
+
+ /** {@code luaV_modf}:{@code fmod} 之后把结果调到与除数同号 */
+ static double modf(double m, double n) {
+ double r = m % n;
+ if ((r > 0) ? (n < 0) : ((r < 0 && n > 0))) {
+ r += n;
+ }
+ return r;
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/BaseLib.java b/src/main/java/com/github/esrrhs/javalua/BaseLib.java
new file mode 100644
index 0000000..037dfc9
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/BaseLib.java
@@ -0,0 +1,1292 @@
+package com.github.esrrhs.javalua;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 基础库 —— 对应 {@code lbaselib.c},加上 {@code lstrlib.c} 里那组字符串算术元方法。
+ *
+ * 阶段 3 只求「脚本能跑起来并对 stdout 做差分」,所以这里挑的是差分比对真正
+ * 用得上的函数:输出、类型、转换、元表、迭代、错误处理。字符串 / 表 / os /
+ * io 等库随阶段 7 各自成文件。
+ *
+ * 为什么这里要挂字符串的算术元方法。{@code "10" + 1 == 11} 这件事
+ * 不是 VM 做的——VM 的算术宏只认真正的数字。它来自 {@code lstrlib.c}
+ * 给字符串元表装的 {@code __add}:先把两侧 {@code lua_stringtonumber},
+ * 再 {@code lua_arith}。少了这一组,字符串参与算术就会走到「attempt to perform
+ * arithmetic on a string value」——一个看起来合理但与参考实现不同的错误。
+ */
+public final class BaseLib {
+
+ private BaseLib() {
+ }
+
+ /** 把基础库装进全局表 */
+ public static void open(LuaState L) {
+ LuaTable g = L.globals;
+
+ setFn(g, "print", BaseLib::print);
+ setFn(g, "type", BaseLib::type);
+ setFn(g, "tostring", BaseLib::tostring);
+ setFn(g, "tonumber", BaseLib::tonumber);
+ setFn(g, "rawget", BaseLib::rawget);
+ setFn(g, "rawset", BaseLib::rawset);
+ setFn(g, "rawequal", BaseLib::rawequal);
+ setFn(g, "rawlen", BaseLib::rawlen);
+ setFn(g, "next", BaseLib::next);
+ setFn(g, "pairs", BaseLib::pairs);
+ setFn(g, "ipairs", BaseLib::ipairs);
+ setFn(g, "select", BaseLib::select);
+ setFn(g, "error", BaseLib::error);
+ setFn(g, "assert", BaseLib::assertFn);
+ setFn(g, "pcall", BaseLib::pcall);
+ setFn(g, "xpcall", BaseLib::xpcall);
+ setFn(g, "setmetatable", BaseLib::setmetatable);
+ setFn(g, "getmetatable", BaseLib::getmetatable);
+ setFn(g, "collectgarbage", BaseLib::collectgarbage);
+ setFn(g, "load", BaseLib::loadFn);
+ setFn(g, "loadfile", BaseLib::loadfileFn);
+ setFn(g, "dofile", BaseLib::dofile);
+ setFn(g, "warn", BaseLib::warn);
+
+ /*
+ * {@code lbaselib.c: luaopen_base} 最后两件事:设 {@code _G} 与
+ * {@code _VERSION}。{@code _G} 在 {@link LuaState} 的构造里就已经挂上了
+ * (注册表也要用到它),这里只补 {@code _VERSION}。
+ *
+ * 别小看这一个字符串:{@code _VERSION} 是最常见的版本探测手段,
+ * 真实项目里到处是 {@code if _VERSION == "Lua 5.1" then ... end} 这种
+ * 分支,缺了它走的是 else —— 不报错,只是行为悄悄不同,正好是最难查的那类。
+ */
+ g.set("_VERSION", LuaValue.ofString(LuaString.latin1(Lua.LUA_VERSION)));
+
+ openStringMetatable(L);
+ }
+
+ /* ==================== 字符串元表 ==================== */
+
+ /**
+ * {@code lstrlib.c: createmetatable} + 算术元方法。
+ *
+ * 上游还给这个元表设了 {@code __index = string} 库(所以 {@code ("x"):upper()}
+ * 能用),那部分随字符串库一起到阶段 7 再补。
+ */
+ private static void openStringMetatable(LuaState L) {
+ LuaTable mt = new LuaTable();
+ setFn(mt, "__add", l -> strArith(l, Arith.OP_ADD, Tm.TM_ADD, "add"));
+ setFn(mt, "__sub", l -> strArith(l, Arith.OP_SUB, Tm.TM_SUB, "sub"));
+ setFn(mt, "__mul", l -> strArith(l, Arith.OP_MUL, Tm.TM_MUL, "mul"));
+ setFn(mt, "__mod", l -> strArith(l, Arith.OP_MOD, Tm.TM_MOD, "mod"));
+ setFn(mt, "__pow", l -> strArith(l, Arith.OP_POW, Tm.TM_POW, "pow"));
+ setFn(mt, "__div", l -> strArith(l, Arith.OP_DIV, Tm.TM_DIV, "div"));
+ setFn(mt, "__idiv", l -> strArith(l, Arith.OP_IDIV, Tm.TM_IDIV, "idiv"));
+ setFn(mt, "__unm", l -> strArith(l, Arith.OP_UNM, Tm.TM_UNM, "unm"));
+ L.setTypeMetatable(Lua.TSTRING, mt);
+ }
+
+ /**
+ * {@code lstrlib.c: arith}。
+ *
+ * 关键在那句 {@code else trymt(L, mtname)}:转换不成数字不是立刻报错,
+ * 而是再去第二个操作数身上找同名元方法。少了这一步,
+ * {@code '5' + b}({@code b} 是带 {@code __add} 的表)会报
+ * 「attempt to add a 'string' with a 'table'」,而参考实现是正常调用
+ * {@code b} 的 {@code __add}——上游 {@code events.lua} 那一整串
+ * {@code checkcap{"add", '5', b}} 全靠它。
+ */
+ private static int strArith(LuaState L, int op, int event, String name) {
+ LuaValue p1 = L.arg(0);
+ LuaValue p2 = L.arg(1);
+ LuaValue n1 = numOf(p1);
+ LuaValue n2 = numOf(p2);
+ if (n1 != null && n2 != null) {
+ LuaValue res = Arith.rawArith(L, op, n1, n2);
+ if (res != null) {
+ L.push(res);
+ return 1;
+ }
+ }
+ /* lstrlib.c: trymt —— 回到原来的两个参数,去 p2 身上找元方法 */
+ L.setTop(2);
+ /*
+ * 「第二个操作数是字符串」时不找:否则 {@code '5' + 'x'} 会找到
+ * 字符串元表的 {@code __add}——也就是现在正在跑的这个函数——无限递归。
+ * C 版用 {@code lua_type(L, 2) == LUA_TSTRING} 挡住,正是这个原因。
+ */
+ if (!p2.isString()) {
+ LuaValue tm = LuaTm.getTmByObj(L, p2, event);
+ if (tm != null && !tm.isNil()) {
+ int f1 = L.ci.func + 1; // 第一个参数的位置
+ L.push(tm);
+ // lua_insert(L, -3):元方法挪到两个参数下面
+ LuaValue moved = new LuaValue(L.stack[L.top - 1]);
+ for (int i = L.top - 1; i > f1; i--) {
+ L.stack[i].set(L.stack[i - 1]);
+ }
+ L.stack[f1].set(moved);
+ L.call(f1, 1); // C 版是 lua_call,允许 yield
+ return 1;
+ }
+ }
+ throw L.libError("attempt to " + name + " a '" + p1.typeName() + "' with a '"
+ + p2.typeName() + "'");
+ }
+
+ /** {@code lstrlib.c: tonum}:已是数字就原样返回,否则试着把整个字符串解释成数字 */
+ private static LuaValue numOf(LuaValue v) {
+ if (v.isNumber()) {
+ return v;
+ }
+ return Vm.strToNum(v);
+ }
+
+ /* ==================== 各函数 ==================== */
+
+ /** {@code luaB_print}:制表符分隔,末尾换行 */
+ private static int print(LuaState L) {
+ int n = L.argCount();
+ ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ for (int i = 0; i < n; i++) {
+ if (i > 0) {
+ tmp.write('\t');
+ }
+ LuaString s = toLString(L, L.arg(i));
+ tmp.write(s.bytes, 0, s.bytes.length);
+ }
+ tmp.write('\n');
+ byte[] all = tmp.toByteArray();
+ L.out.write(all, 0, all.length);
+ L.out.flush();
+ return 0;
+ }
+
+ /** {@code luaB_type}:{@code luaL_argcheck(t != LUA_TNONE, 1, "value expected")} */
+ private static int type(LuaState L) {
+ LuaAux.argCheck(L, !L.isNoneArg(1), 1, "value expected");
+ L.push(LuaValue.ofString(LuaString.latin1(L.arg(0).typeName())));
+ return 1;
+ }
+
+ /** {@code luaB_tostring} */
+ private static int tostring(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ L.push(LuaValue.ofString(toLString(L, L.arg(0))));
+ return 1;
+ }
+
+ /** {@code luaL_tolstring}:先看 {@code __tostring},再按类型格式化 */
+ static LuaString toLString(LuaState L, LuaValue v) {
+ LuaValue ts = metamethodByName(L, v, "__tostring");
+ if (ts != null) {
+ // 调 __tostring(v)
+ int func = L.top;
+ L.ensureSlots(func + 2);
+ L.stack[func].set(ts);
+ L.stack[func + 1].set(v);
+ L.top = func + 2;
+ /*
+ * luaL_callmeta 走的是 lua_call,也就是 luaD_callnoyield:
+ * __tostring 里 yield 会报「跨 C 调用边界」。实测参考实现
+ * print(setmetatable({}, {__tostring = f})) 里 f 一 yield 就炸,
+ * 换成 L.call 会让它「成功」,反而与参考实现不一致。
+ */
+ L.callNoYield(func, 1);
+ LuaValue res = L.stack[--L.top];
+ /*
+ * 判据是 {@code lua_isstring} 而不是「是不是字符串」:
+ * 数字也算,会被就地转换成字符串。实测
+ * {@code print(setmetatable({}, {__tostring = function() return 42 end}))}
+ * 打印 {@code 42},只有返回表这类真不行的才报
+ * 「'__tostring' must return a string」。
+ */
+ if (res.isString()) {
+ return res.asString();
+ }
+ if (res.isNumber()) {
+ return LuaString.latin1(Vm.numberToString(res));
+ }
+ throw L.libError("'__tostring' must return a string");
+ }
+ switch (v.baseType()) {
+ case Lua.TNUMBER:
+ return LuaString.latin1(Vm.numberToString(v));
+ case Lua.TSTRING:
+ return v.asString();
+ case Lua.TBOOLEAN:
+ return LuaString.latin1(v.toBoolean() ? "true" : "false");
+ case Lua.TNIL:
+ return LuaString.latin1("nil");
+ default: {
+ LuaString kind = LuaString.latin1(v.typeName());
+ LuaValue name = metamethodByName(L, v, "__name");
+ if (name != null && name.isString()) {
+ /*
+ * __name 走的是 C 字符串语义:lauxlib.c: luaL_tolstring 取到 __name
+ * 后用 lua_tostring 转成 const char*,再拿 "%s" 拼进 "名字: 0x地址",
+ * 于是名字在第一个 \0 处被砍断。__name = "A\0B" 只打印出 "A"。
+ */
+ kind = name.asString().toCString();
+ }
+ /*
+ * 地址用 C 的 "%p" 格式:0x 加十六进制、不补前导零
+ * (macOS 打出 0x7ab30002c0,glibc 打出 0x55d3...)。补成 16 位
+ * 只是看着整齐,跟参考实现对不上。
+ *
+ * 取值的活交给 {@link Api#topointer}:轻量 userdata 要打的是它
+ * 载荷里那个 {@code void*},不是 Java 对象的 identity hash。
+ * {@code cjson.null} 是 {@code lua_pushlightuserdata(L, NULL)}
+ * 推出来的,参考实现打 {@code userdata: 0x0}。
+ */
+ String ptr = "0x" + Long.toHexString(Api.topointer(v));
+ return kind.concat(LuaString.latin1(": " + ptr));
+ }
+ }
+ }
+
+ private static LuaValue metamethodByName(LuaState L, LuaValue v, String name) {
+ LuaTable mt = LuaTm.metatableOf(L, v);
+ if (mt == null) {
+ return null;
+ }
+ LuaValue r = mt.get(LuaString.latin1(name));
+ return (r == null || r.isNil()) ? null : r;
+ }
+
+ /**
+ * {@code luaB_tonumber}。
+ *
+ * 无参那句是 {@code luaL_checkany(L, 1)},落点是
+ * {@code luaL_argerror(L, 1, "value expected")},函数名同样由调用者那一帧
+ * 反推:{@code local f = tonumber; f()} 参考实现报 {@code 'f'},不是
+ * {@code 'tonumber'}。以前这里把全名写死,与
+ * {@code coroutine} 那六个函数是同一个错(见 {@link CoroutineLib#getco})。
+ */
+ private static int tonumber(LuaState L) {
+ int n = L.argCount();
+ if (n == 0) {
+ throw L.argError(1, "value expected");
+ }
+ LuaValue v = L.arg(0);
+ if (n == 1 || L.arg(1).isNil()) {
+ if (v.isNumber()) {
+ L.push(v);
+ return 1;
+ }
+ LuaValue num = Vm.strToNum(v);
+ if (num != null) {
+ L.push(num);
+ return 1;
+ }
+ L.pushNil();
+ return 1;
+ }
+ // 带进制
+ /*
+ * 三道校验的顺序照抄 C:{@code luaL_checkinteger(2)} →
+ * {@code luaL_checktype(1, LUA_TSTRING)} → {@code luaL_argcheck(2 <= base <= 36)}。
+ * 范围检查在最后,所以 {@code tonumber(1, 1)} 报的是
+ * {@code bad argument #1 (string expected, got number)} 而不是
+ * {@code bad argument #2 (base out of range)} —— 曾经把范围检查放在
+ * 中间,那两条全反了。
+ */
+ long base = checkInteger(L, 1);
+ LuaAux.checkType(L, 1, Lua.TSTRING);
+ LuaAux.argCheck(L, base >= 2 && base <= 36, 2, "base out of range");
+ Long parsed = parseBase(v.asString().bytes, (int) base);
+ if (parsed == null) {
+ L.pushNil();
+ } else {
+ L.push(LuaValue.ofInteger(parsed));
+ }
+ return 1;
+ }
+
+ /**
+ * {@code lbaselib.c: b_str2int}。
+ *
+ * 必须在字节上做,而且必须回到长度上判「吃完了没有」——这两点
+ * 缺一个就会把 {@code tonumber('1\0', 2)} 算成 1。
+ *
+ * C 版靠的是「{@code s} 走到哪里」与 {@code s + l} 比:数字循环遇到
+ * {@code \0}(不是 {@code isalnum})就停,后面的空格跳过一步也停,返回的
+ * 指针还站在 {@code \0} 上,与 {@code s + l} 不等,于是
+ * {@code luaB_tonumber} 判失败。早先这里用 {@code String.trim()} 去掉首尾
+ * 空白——而 Java 的 {@code trim} 连 {@code \0} 一起去掉——这个差别直接让
+ * 上游 {@code math.lua} 的 {@code assert(not f(tonumber('1\0', 2)))}
+ * 挂掉。
+ *
+ * 另外数字只看 ASCII:C 版用的是 C locale 的 {@code isalnum},不是
+ * {@code lislalnum}(后者把 {@code _} 也算字母),也不是
+ * {@code Character.digit}(后者认全角数字)。
+ *
+ * @return 解析出的整数;不是合法的整数字面量返回 {@code null}
+ */
+ private static Long parseBase(byte[] b, int base) {
+ int len = b.length;
+ int p = 0;
+ while (p < len && LCType.isSpace(b[p] & 0xFF)) {
+ p++; // SPACECHARS
+ }
+ boolean neg = false;
+ if (p < len && b[p] == '-') {
+ p++;
+ neg = true;
+ } else if (p < len && b[p] == '+') {
+ p++;
+ }
+ if (p >= len || !isAsciiAlnum(b[p])) {
+ return null; // 没有数字
+ }
+ long n = 0;
+ do {
+ int d = (b[p] >= '0' && b[p] <= '9')
+ ? b[p] - '0'
+ : ((b[p] >= 'a' && b[p] <= 'z') ? b[p] - 'a' : b[p] - 'A') + 10;
+ if (d >= base) {
+ return null; // 不是这个进制里的数字
+ }
+ n = n * base + d; // 溢出按 Lua 的 int64 环绕处理,与 C 版一致
+ p++;
+ } while (p < len && isAsciiAlnum(b[p]));
+ while (p < len && LCType.isSpace(b[p] & 0xFF)) {
+ p++; // 尾部空白
+ }
+ if (p != len) {
+ return null; // 还有没吃掉的字节,包括内嵌的 \0
+ }
+ return neg ? -n : n;
+ }
+
+ /** C locale 的 {@code isalnum}:只认 ASCII 字母与数字,不含 {@code '_'} */
+ private static boolean isAsciiAlnum(byte c) {
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+ }
+
+ /**
+ * {@code luaB_rawget}。
+ *
+ * C 只做 {@code luaL_checktype(1, TABLE)} 与 {@code luaL_checkany(2)}
+ * 两条校验,没有「键不能是 nil」那条 —— {@code rawget({}, nil)}
+ * 是 {@code true nil}(nil 键走 {@code luaH_get} 落到 dummynode)。报
+ * 「table index is nil」的是 {@code rawset},那条来自
+ * {@code ltable.c: luaH_newkey}。
+ *
+ * 曾经这里既漏了 {@code checkAny(2)},又自作主张加了 nil 键校验。
+ */
+ private static int rawget(LuaState L) {
+ LuaAux.checkType(L, 1, Lua.TTABLE);
+ LuaAux.checkAny(L, 2);
+ LuaValue t = L.arg(0);
+ LuaValue k = L.arg(1);
+ LuaValue v = t.asTable().get(k);
+ L.push((v == null) ? LuaValue.ofNil() : v);
+ return 1;
+ }
+
+ /** {@code luaB_rawset} */
+ private static int rawset(LuaState L) {
+ LuaAux.checkType(L, 1, Lua.TTABLE);
+ LuaAux.checkAny(L, 2);
+ LuaAux.checkAny(L, 3);
+ LuaValue t = L.arg(0);
+ t.asTable().set(L.arg(1), L.arg(2));
+ L.push(t);
+ return 1;
+ }
+
+ /**
+ * {@code luaB_rawequal}。
+ *
+ * {@code luaL_checkany} 要做两遍(第 1、第 2 参数各一遍):
+ * {@code rawequal(1)} 报 {@code bad argument #2 ... (value expected)}。
+ * 曾经一遍也没做,于是 {@code pcall(rawequal, 1)} 直接返回 {@code true true}。
+ */
+ private static int rawequal(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ LuaAux.checkAny(L, 2);
+ L.push(LuaValue.ofBoolean(LuaValue.rawEqual(L.arg(0), L.arg(1))));
+ return 1;
+ }
+
+ /**
+ * {@code luaB_rawlen}:走 {@code luaL_argexpected},条件是「类型是表或字符串」。
+ *
+ * 缺参时 {@code lua_type} 是 {@code LUA_TNONE},既不是表也不是字符串,
+ * 所以报 {@code got no value} —— 用 {@link LuaState#argTypeName} 取类型名,
+ * 它把越界翻译成 {@code no value}。
+ */
+ private static int rawlen(LuaState L) {
+ int t = L.argBaseType(1);
+ LuaAux.argExpected(L, t == Lua.TTABLE || t == Lua.TSTRING, 1, "table or string");
+ LuaValue v = L.arg(0);
+ if (v.isTable()) {
+ L.push(LuaValue.ofInteger(v.asTable().length()));
+ return 1;
+ }
+ L.push(LuaValue.ofInteger(v.asString().length()));
+ return 1;
+ }
+
+ /** {@code luaB_next} */
+ private static int next(LuaState L) {
+ /*
+ * C 是 {@code luaL_checktype(1, TABLE)} 然后 {@code lua_settop(L, 2)}
+ * —— 第二个参数不给就补一个(补出来的是 nil),不是报错。
+ */
+ LuaAux.checkType(L, 1, Lua.TTABLE);
+ LuaValue t = L.arg(0);
+ LuaValue k = L.arg(1);
+ LuaValue[] kv;
+ try {
+ kv = t.asTable().next(k);
+ } catch (IllegalArgumentException e) {
+ /*
+ * 必须是 {@link LuaState#runError} 而不是 {@link LuaState#libError}。
+ *
+ * C 里这句话在 {@code ltable.c: findindex},是
+ * {@code luaG_runerror} 而不是 {@code luaL_error} —— 前者只在
+ * {@code isLua(ci)} 时才加位置,而跑到这儿 {@code ci} 是
+ * {@code luaB_next} 这个 C 帧,于是永远没有位置。
+ * {@code libError} 走的是 {@code luaL_where(L, 1)},会往回跳一层
+ * 拿到调用者的行号,于是凭空多出 {@code case.lua:N:}(约定 #8 那句
+ * 「库函数一律 libError」在这里不成立——这条消息的源头不在库代码里)。
+ *
+ * 实测:{@code next({}, 1)} 参考给 {@code invalid key to 'next'},
+ * 本实现给 {@code case.lua:12: invalid key to 'next'}。
+ */
+ throw L.runError("invalid key to 'next'");
+ }
+ if (kv == null) {
+ L.pushNil();
+ return 1;
+ }
+ L.push(kv[0]);
+ L.push(kv[1]);
+ return 2;
+ }
+
+ /**
+ * {@code lbaselib.c: pairscont}。
+ *
+ * yield 恢复之后被调用,三个返回值(迭代器、状态、初值)那时候已经在栈上,
+ * 这里只要报出「有 3 个结果」。
+ */
+ private static final LuaKFunction PAIRS_CONT = (L, status, ctx) -> 3;
+
+ /**
+ * {@code luaB_pairs}(含 {@code __pairs} 元方法)。
+ *
+ * C 只做 {@code luaL_checkany(L, 1)}——不要求是表。没有
+ * {@code __pairs} 时照样返回 {@code (next, 值, nil)},于是
+ * {@code pairs(5)} 是三个返回值而不是报错;真正炸是在迭代时
+ * {@code next(5)} 报 {@code bad argument #1 to 'next' (table expected, got number)}。
+ * 曾经这里多加了「必须是表」的校验,把 {@code pairs(5)} 变成报错。
+ */
+ private static int pairs(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ LuaValue t = L.arg(0);
+ LuaValue pf = metamethodByName(L, t, "__pairs");
+ if (pf != null) {
+ int func = L.top;
+ L.ensureSlots(func + 2);
+ L.stack[func].set(pf);
+ L.stack[func + 1].set(t);
+ L.top = func + 2;
+ /*
+ * {@code lua_callk(L, 1, 3, 0, pairscont)}:带续体,所以
+ * {@code __pairs} 里 yield 是允许的(上游 nextvar.lua 有这一条)。
+ * 用不带续体的 {@code call} 会在恢复时炸,因为那时 {@code ci.k} 是
+ * {@code null}。
+ */
+ L.callK(func, 3, 0, PAIRS_CONT);
+ return 3;
+ }
+ L.pushFunction(BaseLib::next);
+ L.push(t);
+ L.pushNil();
+ return 3;
+ }
+
+ /** {@code luaB_ipairs}:与 {@code pairs} 一样只 {@code luaL_checkany} */
+ private static int ipairs(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ L.pushFunction(BaseLib::ipairsAux);
+ L.push(L.arg(0));
+ L.push(LuaValue.ofInteger(0));
+ return 3;
+ }
+
+ /**
+ * {@code ipairsaux}。
+ *
+ * 这里有一个容易写反的地方:C 函数的 n 个返回值是「栈顶最后 n 个」,
+ * 不是「参数之后的前 n 个」。所以先压 {@code i}、再压 {@code t[i]},然后
+ * {@code return 1}——取到的是刚压进去的那个值(结束时是 {@code nil}),
+ * 而不是 {@code i}。写成「先压 i 就返回 i」的直觉版本,泛型 for 会看到
+ * 首值非 nil 而永不终止:参考实现里 {@code ipairs} 能停住,靠的
+ * 就是这个「取最后 n 个」的约定,不是靠判 nil 提前返回。
+ */
+ private static int ipairsAux(LuaState L) {
+ long i = checkInteger(L, 1) + 1;
+ LuaValue t = L.arg(0);
+ L.push(LuaValue.ofInteger(i));
+ int vIdx = L.top;
+ L.pushNil();
+ Vm.getTable(L, t, LuaValue.ofInteger(i), vIdx);
+ return L.stack[vIdx].isNil() ? 1 : 2;
+ }
+
+ /**
+ * {@code luaB_select}。
+ *
+ * 两处容易写错,都是实测出来的:
+ *
+ * 必须照 C 版的形状写,尤其那句 {@code lua_settop(L, 1)}——它不是顺手的
+ * 清理,而是决定「栈顶之下那一格是什么」。
+ *
+ * 实测({@code coroutine.wrap} 出来的函数崩掉时):
+ * {@code luaB_auxwrap} 在错误分支里会先 {@code lua_xmove(co, L, 1)} 把错误
+ * 对象搬走,再 {@code lua_closethread} → {@code luaE_resetthread},后者
+ * 走 {@code luaD_seterrorobj(L, status, L->stack + 1)},而
+ * {@code seterrorobj} 的 {@code default} 分支是
+ * {@code setobjs2s(L, oldtop, L->top - 1)}——搬的是「搬走一格之后」的栈顶。
+ * 只有 {@code settop(1)} + {@code where} + {@code pushvalue} + {@code concat}
+ * 这套写法能让那一格正好是带位置的错误对象({@code concat} 的结果把参数格
+ * 覆盖掉,于是「错误对象」在栈上是连着两格的)。少了 {@code settop},那一格
+ * 还是原始的 {@code "boom"},报出来就成了没有 {@code file:line:} 前缀的裸串。
+ */
+ private static int error(LuaState L) {
+ int n = L.argCount();
+ // 注意没有「至少一个参数」的检查:error() 等价于 error(nil),
+ // 参考实现实测 print(pcall(error)) 得到 false nil。
+ long level = (n > 1 && !L.arg(1).isNil()) ? checkInteger(L, 1) : 1;
+ L.setTop(1); // lua_settop(L, 1):多出来的参数全部摘掉
+ LuaValue msg = L.arg(0);
+ if (msg.isString() && level > 0) {
+ L.push(LuaValue.ofString(L.whereAt((int) level))); // luaL_where(字节串,别转 Java String)
+ L.push(msg); // lua_pushvalue(L, 1)
+ Vm.concat(L, 2); // lua_concat(L, 2)
+ }
+ // lua_error(L):抛栈顶那个值。位置已经拼进去了,errorAt 的 level 传 0
+ // 表示「别再加一次」——但 where 字段仍非 null,pcall 也不会补。
+ throw L.errorAt(L.stack[L.top - 1], 0);
+ }
+
+ /**
+ * {@code luaB_assert}。
+ *
+ * 校验的位置照抄 C:{@code luaL_checkany} 在条件为假那一支里,
+ * 不在函数开头。{@code assert()} 走的是「条件假 → 报 value expected」。
+ * 曾经写成「一个参数也没有就报」,措辞对、位置不对 —— 而真正咬人的是
+ * {@code L.arg(0)} 会读到残值:残值为真时 {@code assert()} 直接返回 0 个值。
+ */
+ private static int assertFn(LuaState L) {
+ int n = L.argCount();
+ if (L.arg(0).toBoolean()) {
+ for (int i = 0; i < n; i++) {
+ L.push(L.arg(i));
+ }
+ return n;
+ }
+ LuaAux.checkAny(L, 1);
+ LuaValue msg = (n > 1) ? L.arg(1)
+ : LuaValue.ofString(LuaString.latin1("assertion failed!"));
+ throw L.errorAt(msg, 1);
+ }
+
+ /** {@code luaB_pcall} */
+ private static int pcall(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ return doPcall(L, false);
+ }
+
+ /**
+ * {@code luaB_xpcall}。
+ *
+ * 第一件事是 {@code luaL_checktype(L, 2, LUA_TFUNCTION)} —— 消息处理函数
+ * 必须是函数,而且这条检查在调用被调函数之前就发生。曾经写成
+ * 「只判参数够不够两个」,于是 {@code xpcall(f, 5)} 不报错,而是照常调用
+ * {@code f}:{@code f} 抛错后拿 5 当处理函数,得到
+ * {@code true, false, "error in error handling"};参考实现给的是
+ * {@code false bad argument #2 to 'xpcall' (function expected, got number)}。
+ * 参数不足时同理:报 {@code function expected, got no value},
+ * 而不是含糊的 {@code value expected}。
+ */
+ private static int xpcall(LuaState L) {
+ LuaAux.checkType(L, 2, Lua.TFUNCTION);
+ return doPcall(L, true);
+ }
+
+ /**
+ * pcall / xpcall 的公共部分。
+ *
+ * 布局按 {@code lbaselib.c} 来:先在结果区起点塞 {@code true},再把被调函数
+ * 与它的参数整体上移一格,于是调用结果自然接在 {@code true} 后面,返回时数
+ * {@code L.top - start} 就得到「true + 全部结果」。
+ *
+ * xpcall 的坑在消息处理函数夹在被调函数和参数中间
+ * (原始布局 {@code [f, msgh, args...]})。必须先把它摘出来、把参数左移一格,
+ * 否则它会变成传给 {@code f} 的第一个实参——表现是处理函数收到的
+ * 错误对象是 {@code nil}。参考实现用 {@code lua_rotate(L, 3, 2)} 把
+ * {@code true} 和 {@code f} 转到参数前面,这里直接删掉中间那格,等价且更好读。
+ */
+ private static int doPcall(LuaState L, boolean hasHandler) {
+ int base = L.ci.func;
+ CallInfo myCi = L.ci; // 本函数自己的帧;yield 时要往上面挂续体
+ LuaValue handler = null;
+ if (hasHandler) {
+ handler = new LuaValue(L.stack[base + 2]);
+ // 抹掉 msgh:参数整体左移一格
+ for (int p = base + 2; p < L.top - 1; p++) {
+ L.stack[p].set(L.stack[p + 1]);
+ }
+ L.top--;
+ }
+ int start = base + 1; // 结果区起点,当前放着被调函数
+ L.ensureSlots(L.top + 1);
+ for (int p = L.top; p > start; p--) {
+ L.stack[p].set(L.stack[p - 1]);
+ }
+ L.top++;
+ L.stack[start].setBoolean(true);
+ int f = start + 1;
+ try {
+ LuaValue err = L.pcall(f, LuaState.MULTRET, handler);
+ if (err == null) {
+ return L.top - start;
+ }
+ L.top = start;
+ L.push(LuaValue.ofBoolean(false));
+ L.push(err);
+ return 2;
+ } catch (LuaYield y) {
+ /*
+ * pcall 可以 yield —— 实测参考实现就是这样。但 yield 是异常抛出去的,
+ * Java 栈帧到这里就没了,「收尾」只能交给续体。
+ * 这里做的事等价于 C 版 lua_pcallk 里那段
+ * 「ci->u.c.k = k; ci->callstatus |= CIST_YPCALL; ...」:
+ * 记下恢复点,让 resume 之后的 unroll 能找到它。
+ */
+ myCi.yieldPcall = true;
+ myCi.k = PCALL_CONT;
+ myCi.ctx = start;
+ myCi.funcIdx = f;
+ myCi.errHandler = handler;
+ /*
+ * 两种「被打断」必须分开记,因为 resume 之后 {@code finishPcallK}
+ * 只看 {@code recst}:
+ *
+ * {@code ctx} 是结果区起点(就是 {@code true} 那一格),错误对象由
+ * {@code finishPcallK} 放在 {@code ctx + 1}。
+ */
+ private static final LuaKFunction PCALL_CONT = (L, status, ctx) -> {
+ if (status != LuaState.LUA_OK && status != LuaState.LUA_YIELD) {
+ LuaValue err = new LuaValue(L.stack[ctx + 1]);
+ L.top = ctx;
+ L.push(LuaValue.ofBoolean(false));
+ L.push(err);
+ return 2;
+ }
+ return L.top - ctx;
+ };
+
+ /** {@code luaB_setmetatable} */
+ private static int setmetatable(LuaState L) {
+ LuaAux.checkType(L, 1, Lua.TTABLE);
+ /*
+ * 第二个参数走 {@code luaL_argexpected},措辞是
+ * 「nil or table expected, got 实际类型」——那个 {@code got} 不能省,
+ * 省了就成了裸的「nil or table expected」。
+ * 条件是 {@code lua_type(2)} 属于 {nil, table}:缺参是 TNONE,
+ * 不属于,所以 {@code setmetatable({})} 报错,报的是 {@code got no value}。
+ */
+ int t2 = L.argBaseType(2);
+ LuaAux.argExpected(L, t2 == Lua.TNIL || t2 == Lua.TTABLE, 2, "nil or table");
+ LuaValue t = L.arg(0);
+ LuaValue mt = L.arg(1);
+ if (metamethodByName(L, t, "__metatable") != null) {
+ throw L.libError("cannot change a protected metatable");
+ }
+ t.asTable().setMetatable(mt.isNil() ? null : mt.asTable());
+ // __gc 只在设元表的当下登记一次,之后往元表里塞 __gc 不生效(实测参考实现如此)
+ t.asTable().markFinalizable(L);
+ L.push(t);
+ return 1;
+ }
+
+ /** {@code luaB_getmetatable} */
+ private static int getmetatable(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ LuaValue v = L.arg(0);
+ LuaValue protect = metamethodByName(L, v, "__metatable");
+ if (protect != null) {
+ L.push(protect);
+ return 1;
+ }
+ LuaTable mt = LuaTm.metatableOf(L, v);
+ if (mt == null) {
+ L.pushNil();
+ } else {
+ L.push(LuaValue.ofTable(mt));
+ }
+ return 1;
+ }
+
+ /**
+ * {@code luaB_collectgarbage} 的 {@code opts[]},顺序与 {@code luaB_collectgarbage}
+ * 里那个 {@code switch} 的下标严格对应。
+ */
+ private static final String[] GC_OPTS = {
+ "stop", "restart", "collect", "count", "step", "setpause",
+ "setstepmul", "isrunning", "generational", "incremental"
+ };
+
+ /**
+ * 当前 JVM 堆的已用内存(字节)。
+ *
+ * 用 {@code Runtime.getRuntime().totalMemory() - freeMemory()} 近似。
+ * 这不是精确的 Lua 对象大小,但对 GC 基准测试足够——我们关心的是相对变化,
+ * 不是绝对值。参考 Lua 的 {@code collectgarbage("count")} 报的也是 Lua 自己
+ * 管理的内存,不包括 C 扩展分配的。
+ */
+ private static volatile long currentMemoryUsage = 0;
+
+ /**
+ * 更新内存使用量。在 GC 前后调用,用于 {@code collectgarbage("count")}。
+ */
+ private static void updateMemoryUsage() {
+ Runtime rt = Runtime.getRuntime();
+ currentMemoryUsage = rt.totalMemory() - rt.freeMemory();
+ }
+
+ /**
+ * {@code luaB_collectgarbage}。
+ *
+ * {@code "collect"} 分支做两件事:请求 JVM 做一次完整收集,然后递增
+ * 「收集轮次」——弱表靠轮次决定什么时候清扫失效条目,这一点与 C 版一致
+ * (Lua 是在 GC 周期末尾清弱表,不是键一不可达就立刻消失)。
+ *
+ * {@code "count"} 报 JVM 堆的已用量(KB),不是精确的 Lua 内存。
+ * 对 GC 基准测试足够,上游 {@code gc.lua} 的断言也能过。
+ * 其余选项只保证彼此自洽({@code stop} 之后 {@code isrunning}
+ * 为假、{@code generational} 返回切换前的模式名)。
+ */
+ private static int collectgarbage(LuaState L) {
+ /*
+ * {@code luaL_checkoption}:必须先校验选项。
+ *
+ * 这一句不是为了挑刺——上游 {@code errors.lua} 的
+ * {@code (collectgarbage or print){}} 断言的就是
+ * 「bad argument #1 to 'collectgarbage' (string expected, got table)」。
+ * 少校验,那句里的 {@code pcall} 会成功返回,
+ * {@code checkmessage} 拿到 {@code false} 去 {@code string.find},
+ * 整个文件就死在一条跟它无关的消息上。
+ */
+ int o = LuaAux.checkOption(L, 1, "collect", GC_OPTS);
+ switch (o) {
+ case 3: // "count"
+ // 返回 KB(与参考 Lua 一致),用 double 报小数部分
+ updateMemoryUsage();
+ double kb = currentMemoryUsage / 1024.0;
+ L.push(LuaValue.ofFloat(kb));
+ return 1;
+ case 4: // "step"
+ /*
+ * C 版的 {@code LUA_GCSTEP} 只推进一步,本实现没有步子可迈,
+ * 就照一次完整收集来做。这不是偷懒:弱表与 {@code __gc}
+ * 的清理时机全挂在「收集轮次」上,{@code "step"} 不做任何事的话,
+ * 上游 {@code gengc.lua} 那串「弱表该被清掉」的断言就全红——
+ * 而它们红在一个跟 {@code collectgarbage} 无关的地方。
+ */
+ doCollect(L);
+ L.push(LuaValue.ofBoolean(true));
+ return 1;
+ case 5: // "setpause"
+ case 6: { // "setstepmul"
+ /*
+ * {@code luaL_optinteger(L, 2, 0)} 的默认值 0 也是要写进去的:
+ * {@code lua_gc} 对这两个选项是 {@code setgcparam(p, data)}
+ * 无条件赋值。所以 {@code collectgarbage("setpause")}
+ * (不传第二个参数)报的是上一次的值,同时把 pause 清成 0——
+ * 实测参考实现:
+ *
+ * 先把所有栈的「死区」清掉,再请求收集。顺序不能反:
+ * {@code lgc.c: traversethread} 的 atomic 分支做的正是这件事——
+ * mark 只扫 {@code [stack, top)},扫完把 {@code [top, stack_last)}
+ * 整段置 nil。也就是说 {@code top} 之上的槽在 Lua 里对 GC 不可见。
+ *
+ * 这里必须显式做,因为 {@code stack} 是一条 Java 数组:数组里每个槽
+ * 对 JVM 都是活引用。不清的话,凡是「寄存器落在 {@code top} 之上」的
+ * 对象都收不掉——循环里最后一次迭代建的表就挂在这样的寄存器上
+ * ({@code for i = 1, 5 do t[i] = {i} end} 之后 {@code {5}} 还留在
+ * 寄存器里,而 {@code top} 只到调用参数处),于是弱表清不掉它。
+ */
+ /**
+ * @see #collectgarbage
+ *
+ * 包内可见是因为 {@code C API} 的 {@code lua_gc(L, LUA_GCCOLLECT)}
+ * 走的是同一条路——两条路必须做同一件事,否则「脚本里
+ * {@code collectgarbage("collect")} 与 .so 里 {@code lua_gc}
+ * 效果不同」这种分歧就查不出来了。
+ */
+ static void doCollect(LuaState L) {
+ L.clearDeadStackSlices();
+ /*
+ * 请求 GC、催终结、跑 __gc 三件事的循环都在里面:System.gc() 与 JVM
+ * 的终结线程之间是异步的(见 LuaState#collectAndRunFinalizers)。
+ */
+ L.mainThread.collectAndRunFinalizers(L);
+ LuaTable.gcGeneration++;
+ }
+
+ /* ==================== 加载 ==================== */
+
+ /** {@code LUA_SIGNATURE} 的第一个字节;以它开头的就是二进制 chunk */
+ private static final int SIGNATURE0 = Lua.LUA_SIGNATURE0;
+
+ /**
+ * {@code luaB_load}。
+ *
+ * 二进制 chunk 目前只能挡下来——{@code luaU_undump} 还没搬,硬吃掉会静默
+ * 编出一个错误的 chunk。挡的时候照 C 的 {@code checkmode} 先报模式错,
+ * 模式没错才报「不支持」,这样 mode 的语义先对上。
+ */
+ private static int loadFn(LuaState L) {
+ LuaValue a = L.arg1(1);
+ // checkmode 比的是 const char*:load(src, name, "t\0b") 的模式是 "t"
+ // mode 会原样拼进错误消息(mode is 'x'),Latin-1 往返才不丢字节
+ String mode = LuaAux.optString(L, 3, LuaString.latin1("bt")).toCString().toLatin1String();
+ int env = L.isNoneArg(4) ? 0 : 4;
+ // lua_tolstring:数字也算字符串,load(42) 编的是 "42"
+ LuaString src = (a.isString() || a.isNumber()) ? LuaAux.asString(a) : null;
+ if (src != null) {
+ LuaString chunkname = LuaAux.optString(L, 2, src);
+ return loadAux(L, loadBuffer(L, src.bytes, chunkname.bytes, mode), env);
+ }
+ // 否则第一个参数必须是读函数
+ LuaString chunkname = LuaAux.optString(L, 2, LuaString.latin1("=(load)"));
+ LuaAux.checkType(L, 1, Lua.TFUNCTION);
+ Zio.Source readerSrc = readerSource(L, a);
+ return loadAux(L, loadZio(L, new Zio(readerSrc), chunkname.bytes, mode), env);
+ }
+
+ /**
+ * {@code generic_reader}:把 Lua 侧的读函数包成一条惰性字节流。
+ *
+ * 必须是惰性的。先一股脑读完再解析,遇到永不返回 nil 的读函数就
+ * 会一直累积;而 C 那边是边解析边要,语法错误一冒出来就停了。
+ *
+ * 返回 nil 表示结束;返回非字符串非数字要报
+ * {@code reader function must return a string}(这里用
+ * {@code lua_isstring} 判,所以数字是合法的)。
+ */
+ private static Zio.Source readerSource(LuaState L, LuaValue reader) {
+ return () -> {
+ int f = L.top;
+ L.ensureSlots(f + 2);
+ L.stack[f].set(reader);
+ L.top = f + 1;
+ L.callNoYield(f, 1); /* lbaselib.c: generic_reader 用 lua_call —— 不可 yield */
+ LuaValue r = new LuaValue(L.stack[f]);
+ L.top = f;
+ if (r.isNil()) {
+ return null; // 读函数返回 nil:流结束
+ }
+ if (!(r.isString() || r.isNumber())) {
+ throw L.libError("reader function must return a string");
+ }
+ return LuaAux.asString(r).bytes;
+ };
+ }
+
+ /**
+ * 编译一段源码。
+ *
+ * @return {@code null} 表示成功(闭包已压栈);否则是那条加载期错误
+ */
+ private static LuaError loadBuffer(LuaState L, byte[] src, byte[] chunkname, String mode) {
+ return loadZio(L, new Zio(src), chunkname, mode);
+ }
+
+ /** {@code lbaselib.c: checkmode} —— 结尾是 {@code luaD_throw(LUA_ERRSYNTAX)},直跳 */
+ private static LuaError modeError(String what, String mode) {
+ LuaError e = new LuaError(LuaValue.ofString(LuaString.latin1(
+ "attempt to load a " + what + " chunk (mode is '" + mode + "')")));
+ e.directThrow = true;
+ return e;
+ }
+
+ /**
+ * @see #loadBuffer
+ *
+ * 包内可见是因为 {@code C API} 的 {@code lua_load} /
+ * {@code luaL_loadbufferx} 也要走它——{@code mode} 那两档
+ * ({@code attempt to load a X chunk})只有一份实现才对得上。
+ */
+ static LuaError loadZio(LuaState L, Zio z, byte[] chunkname, String mode) {
+ /*
+ * 整段都必须在 try 里,包括最前面那次窥探。读函数是惰性的,
+ * 第一个字节就要调它一次;而读函数里的错误(返回了非字符串、或自己
+ * error 了)在 C 那边是落在 luaD_protectedparser 的保护区里,
+ * 于是 load 返回 nil + 消息,不抛。窥探写在外面就会让
+ * "reader function must return a string" 变成抛出去的错误。
+ */
+ try {
+ int first = z.getc();
+ if (first == SIGNATURE0) {
+ if (mode.indexOf('b') < 0) {
+ throw modeError("binary", mode);
+ }
+ /*
+ * 二进制分支不回退首字节:C 的 luaU_undump 的注释写得很直白,
+ * "skip 1st char (already read and checked)"——头部的签名首字节
+ * 已经归 f_parser 了,checkHeader 从第二个字节开始比。
+ * 文本分支则要回退(或把首字符交给 parser),否则会吃掉源码第一个字节。
+ */
+ L.push(LuaValue.ofClosure(L.undump(z, chunkname)));
+ return null;
+ }
+ if (mode.indexOf('t') < 0) {
+ throw modeError("text", mode);
+ }
+ /*
+ * 首字节传进去而不是回退。回退看着等价,但对惰性读函数不等价:
+ * 词法器起步时会再 {@code getc()} 一次,而那一次会真的再调一遍读
+ * 函数。首字节是 EOZ 时差别最大——参考实现就此认定流已结束,
+ * 这里却会重新去要下一批。
+ */
+ L.push(LuaValue.ofClosure(L.load(z, chunkname, first)));
+ return null;
+ } catch (LuaError e) {
+ /*
+ * 整段 try 对应 C 的 {@code luaD_protectedparser},而它把
+ * {@code L->errfunc} 原样传进内层的 {@code luaD_pcall}。
+ * 于是「不是 {@code luaD_throw} 直跳的错」在抛出点就被外层
+ * {@code xpcall} 的处理器接住了 —— 实测
+ * {@code xpcall(function() return load(function() error("boom") end) end, H)}
+ * 参考实现给 {@code nil, "H:case.lua:13: boom"};语法错误因为走
+ * {@code luaD_throw} 直跳,给的是裸消息,两种都得对上。
+ */
+ if (!e.directThrow && L.errFunc != null) {
+ return new LuaError(L.applyErrFunc(e.value), e.where);
+ }
+ return e;
+ }
+ }
+
+ /** {@code load_aux}:成功就(可能)设上 {@code _ENV} 返回 1 个值,失败给 nil + 消息 */
+ private static int loadAux(LuaState L, LuaError err, int envidx) {
+ if (err == null) {
+ if (envidx != 0) {
+ L.pushValue(envidx);
+ LuaValue f = L.stack[L.top - 2];
+ if (f.tt == Lua.VLIF) {
+ LClosure cl = (LClosure) f.o;
+ if (cl.upvals.length >= 1 && cl.upvals[0] != null) {
+ cl.upvals[0].value().set(L.stack[L.top - 1]);
+ }
+ }
+ L.top--; // 无论设没设上,env 都要弹掉
+ }
+ return 1;
+ }
+ LuaAux.pushFail(L);
+ L.push(err.value == null ? LuaValue.ofNil() : err.value);
+ return 2;
+ }
+
+ /** {@code luaB_loadfile} */
+ private static int loadfileFn(LuaState L) {
+ LuaString fname = LuaAux.optString(L, 1, null);
+ LuaString modeArg = LuaAux.optString(L, 2, null);
+ // luaL_loadfilex 的 mode 是 NULL 时内部按 "bt" 处理
+ String mode = (modeArg == null) ? "bt" : modeArg.toCString().toLatin1String();
+ int env = L.isNoneArg(3) ? 0 : 3;
+ if (fname == null) {
+ // 无参:从 stdin 读。chunk 名是 "=stdin"
+ byte[] src;
+ try {
+ src = L.in.readAllBytes();
+ } catch (java.io.IOException e) {
+ LuaAux.pushFail(L);
+ L.push(LuaValue.ofString(LuaString.latin1("cannot read stdin")));
+ return 2;
+ }
+ return loadAux(L, loadBuffer(L, src, LuaString.latin1("=stdin").bytes, mode), env);
+ }
+ // luaL_loadfilex 的 filename 是 const char*:第一个 \0 之后的内容既进不了
+ // fopen,也进不了 "cannot open ..." 那条消息
+ String path = fname.toCString().toJavaString();
+ byte[] src;
+ try {
+ // luaL_loadfilex 会先吃掉 BOM 与首行的 #!...;这一步属于加载层,
+ // 用 load 加载字符串时首行写 '#' 照样报 unexpected symbol
+ src = Parser.skipFilePreamble(
+ java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(path)));
+ } catch (Exception e) {
+ /*
+ * 空路径是 C 与 Java 唯一判不一致的一处:{@code fopen("")} 给
+ * {@code ENOENT}(实测 {@code loadfile("")} 报
+ * {@code cannot open : No such file or directory}),而 Java 把空路径
+ * 解析成当前目录、读的时候给 {@code Is a directory}。
+ *
+ * 目录走的是另一条分支,之前这里是错的:{@code loadfile("/tmp")}
+ * 在 C 里 fopen 成功、失败在读,{@code luaL_loadfilex} 的
+ * {@code ferror} 于是为真,报的是
+ * {@code cannot read /tmp: Is a directory} —— 不是
+ * {@code cannot open ...: Input/output error}。实测(参考二进制):
+ *
+ * C 版算的是 {@code lua_gettop(L) - 1}(栈顶减去「帧底 + 1」再减去 chunk
+ * 本身那一格)。这里直接把 chunk 的下标当 {@code ctx} 传进来,省得再推一遍
+ * ——{@code ctx} 在 {@code lua_callk} 里本来就是给续体用的。
+ */
+ private static final LuaKFunction DOFILE_CONT = (L, status, ctx) -> L.top - ctx;
+
+ /**
+ * {@code luaB_dofile}。
+ *
+ * 注意它不吞加载错误:{@code dofile("nope.lua")} 直接把错误抛出去,
+ * 而不是像 {@code loadfile} 那样给 {@code nil, msg}。
+ */
+ private static int dofile(LuaState L) {
+ LuaString fname = LuaAux.optString(L, 1, null);
+ L.setTop(1);
+ int n = loadfileFn(L);
+ if (n == 2) {
+ // 加载失败:栈顶那条消息要当错误抛出去,不是当返回值交出去
+ LuaValue msg = new LuaValue(L.stack[L.top - 1]);
+ L.top -= 2;
+ throw new LuaError(msg);
+ }
+ int f = L.top - 1; // 闭包在栈顶,0 个参数
+ /*
+ * {@code lua_callk(L, 0, LUA_MULTRET, 0, dofilecont)}:在 dofile 出来的
+ * chunk 里 yield 是允许的,所以这里要带续体。续体什么也不做,直接沿用
+ * 恢复时栈上已有的结果个数。
+ */
+ L.callK(f, LuaState.MULTRET, f, DOFILE_CONT);
+ return L.top - f;
+ }
+
+ /**
+ * {@code lbaselib.c: luaB_warn}。
+ *
+ * 两条不显眼的约束,都是逐句照 C 抄出来的,凭记忆推会推反:
+ *
+ * 数字参数是允许的({@code luaL_checkstring} 底下是
+ * {@code lua_tolstring},数字会被转成字符串):{@code warn(1, 2.5, "x")}
+ * 参考实现输出 {@code Lua warning: 12.5x}。
+ */
+ private static int warn(LuaState L) {
+ int n = L.getTop();
+ if (n < 1) {
+ /*
+ * C 版循环之前单独有一句 {@code luaL_checkstring(L, 1)}:一个参数
+ * 都没给时这个检查照样发生,报的是
+ * 「bad argument #1 to 'warn' (string expected, got no value)」。
+ * 只写 {@code for i = 1..n} 的话 n==0 时循环一次都不进,这条报错
+ * 就没了,而 {@code warn()} 会静默成功。
+ */
+ LuaAux.checkString(L, 1);
+ }
+ /*
+ * 存 {@link LuaString} 而不是 {@code String}:{@code lua_tostring} 拿到的是
+ * {@code const char*},截断发生在字节上,而 {@code toJavaString} 会先按
+ * UTF-8 解码——{@code warn("\xff")} 必须原样写出 {@code 0xFF}。
+ */
+ LuaString[] parts = new LuaString[n];
+ for (int i = 1; i <= n; i++) {
+ parts[i - 1] = LuaAux.checkString(L, i);
+ }
+ for (int i = 0; i < n - 1; i++) {
+ L.warning(parts[i], true);
+ }
+ L.warning(parts[n - 1], false);
+ return 0;
+ }
+
+ /* ==================== 小工具 ==================== */
+
+ private static void setFn(LuaTable t, String name, LuaFunction f) {
+ t.set(name, LuaValue.ofFunction(f));
+ }
+
+ /**
+ * {@code luaL_checkinteger} 的 0 下标形态,直接转调 {@link LuaAux#checkInteger}。
+ *
+ * 这里不再自己实现一份。曾经有两份,而这一份少了两样东西,四个调用点
+ * ({@code tonumber} 的进制、{@code select} 的下标、{@code error} 的层号、
+ * {@code ipairs} 的迭代器下标)全都跟着错:
+ *
+ * 本文件由 {@code native/gen_capi.py} 产出。idl 与 C 侧的
+ * {@code luashim_stubs.inc} 同源,改符号集要重跑脚本;重跑会覆盖这里,
+ * 所以实现只能写在本文件里({@code unimplemented} 的那批逐个替换),
+ * 不要另开一个类去写,否则下次生成就丢了。
+ *
+ * 约定:
+ *
+ * 与 Lua 闭包({@link LClosure})的区别只有一个:绑定的是值而不是
+ * upvalue 槽。C 版 {@code CClosure} 里直接放着 {@code TValue upvalue[]},
+ * 因为 C 函数没有栈上的局部变量可供开放 upvalue 指向,绑成值就够了。
+ *
+ * 现存代码里第一个真正需要它的是 {@code coroutine.wrap}:
+ * {@code lcorolib.c} 用 {@code lua_pushcclosure(L, luaB_auxwrap, 1)} 把协程
+ * 对象绑进闭包,之后每次调用 wrap 出来的函数都从 upvalue 里取回那个协程。
+ * 用全局变量或表来存都不行——那样两个 wrap 会互相串。
+ *
+ * 放入 {@link LuaValue} 时用标签 {@link Lua#VCCL}。
+ */
+public final class CClosure {
+
+ public final LuaFunction f;
+
+ /** 绑定上来的值,长度在构造时固定 */
+ public final LuaValue[] upvalue;
+
+ public CClosure(LuaFunction f, int nupvals) {
+ LuaState.chargeGc();
+ this.f = f;
+ this.upvalue = new LuaValue[nupvals];
+ for (int i = 0; i < nupvals; i++) {
+ this.upvalue[i] = LuaValue.ofNil();
+ }
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/CallInfo.java b/src/main/java/com/github/esrrhs/javalua/CallInfo.java
new file mode 100644
index 0000000..91d7303
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/CallInfo.java
@@ -0,0 +1,147 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * 调用帧 —— 对应 {@code lstate.h} 的 {@code CallInfo}。
+ *
+ * C 版用指针({@code func.p} / {@code top.p})在栈上定位,这里换成栈下标。
+ * 等价性依赖于 {@link LuaState} 的不变式:栈扩容时只复制数组、不替换槽对象,
+ * 所以下标与「槽对象引用」两种定位方式在语义上一致。
+ *
+ * C 版把 {@code u.l.savedpc} 与 {@code u.c.*} 放在一个 union 里区分 Lua / C 帧,
+ * 这里摊平成字段,用 {@link #isLua} 区分含义。
+ */
+public final class CallInfo {
+
+ /** 被调用函数在栈上的下标;参数紧跟其后 */
+ int func;
+
+ /** 本帧可用的栈顶(下标),对应 {@code ci->top.p} */
+ int top;
+
+ /** 调用方期望的结果数;{@link LuaState#MULTRET} 表示「全部」 */
+ int nresults;
+
+ /* ---- Lua 帧 ---- */
+
+ /** 下一条待执行指令的 pc(对应 {@code u.l.savedpc}) */
+ int savedpc;
+
+ /** 可变参数里「额外参数」的个数({@code u.l.nextraargs}) */
+ int nextraargs;
+
+ /**
+ * {@code u.l.trap}:本帧是否要在每条指令前停下来查钩子。
+ *
+ * 这是「钩子开销为零」的关键:钩子没开时 {@code trap} 是 0,主循环连
+ * {@code luaG_traceexec} 都不进。它由三处写:
+ * {@code luaG_tracecall}(进函数时置 1)、{@code luaG_traceexec}
+ * (没开行/计数钩子时置 0)、以及 {@code lua_sethook} 的
+ * {@code settraps}——最后这一处才是「执行到一半打开钩子」也能立刻生效的
+ * 原因,因为主循环的 {@code trap} 局部变量只在特定点才从帧里重读。
+ */
+ boolean trap;
+
+ /**
+ * {@code CIST_HOOKED}:本帧正在被钩子跟踪。
+ *
+ * 作用有二:一是 {@code luaD_hook} 期间置位,让钩子内发起的调用不再触发
+ * 新钩子(配合 {@code allowhook});二是让 {@code funcnamefromcall} 把
+ * 回溯里的这一层打印成 {@code in hook '?'}。
+ */
+ boolean hooked;
+
+ /**
+ * {@code CIST_FIN}:本帧正在跑一个 {@code __gc} 终结器。
+ *
+ * 注意这个标记打在发起终结的那一帧上,不是终结器自己的帧——
+ * {@code lgc.c: GCTM} 是在调用之前给 {@code L->ci} 置位、之后再清掉。
+ * 于是终结器里 {@code debug.getinfo(1)} 走
+ * {@code funcnamefromcall(ci->previous)} 时能认出来,给出
+ * {@code namewhat == "metamethod"}、{@code name == "__gc"}。
+ * 上游 {@code db.lua}「testing debug info for finalizers」就断言这两个值,
+ * 而且它用 {@code repeat local a = {} until name} 等终结器跑——认不出来
+ * 就是死循环。
+ */
+ boolean fin;
+
+ /**
+ * {@code CIST_TRAN}:本帧的 {@code ftransfer / ntransfer} 有效。
+ * 只在返回钩子的调用期间为真({@code debug.getinfo(2, 'r')} 读它)。
+ */
+ boolean tran;
+
+ /** {@code u2.transferinfo.ftransfer}:第一个返回值相对 {@link #func} 的偏移 */
+ int ftransfer;
+
+ /** {@code u2.transferinfo.ntransfer}:返回值的个数 */
+ int ntransfer;
+
+ /* ---- 通用 ---- */
+
+ /**
+ * {@code u2.nres}:{@code OP_RETURN} 关闭包时暂存的结果数,
+ * 供 {@code luaV_finishOp} 在 yield 后恢复。本实现不做 yield,
+ * 但字段留着,保持与上游一一对应。
+ */
+ int nres;
+
+ /** 本帧是 Lua 函数(false 表示 C 函数) */
+ boolean isLua = true;
+
+ /* ---- yield 相关(C 版在 {@code u.c.*} 与 {@code u2.*} 里) ---- */
+
+ /**
+ * {@code u2.nyield}:本帧 yield 出去的值的个数。
+ * {@code lua_resume} 靠它知道该从协程栈上搬几个值回调用方。
+ */
+ int nyield;
+
+ /**
+ * {@code u.c.k}:yield 之后的续体。只有「可能被 yield 打断」的 C 帧才设,
+ * 见 {@link LuaKFunction}。
+ */
+ LuaKFunction k;
+
+ /** {@code u.c.ctx}:续体的上下文 */
+ int ctx;
+
+ /**
+ * {@code CIST_YPCALL}:本帧是一个被打断的 {@code pcall} / {@code xpcall},
+ * 可以作为错误恢复点。{@code precover} 全靠这个标记找到「该在哪儿继续」。
+ */
+ boolean yieldPcall;
+
+ /**
+ * {@code CIST_RECST}:{@code pcall} 被 yield 打断后、恢复时拿到的错误状态。
+ * {@code LUA_OK} 表示不是出错而是被 yield 打断的。
+ */
+ int recst;
+
+ /** {@code u2.funcidx}:被 {@code pcall} 保护着调用的那个函数的栈下标 */
+ int funcIdx;
+
+ /** {@code xpcall} 的消息处理函数;没有就是 {@code null} */
+ LuaValue errHandler;
+
+ /** {@code CIST_CLSRET}:C 函数是在关 tbc 变量时被 yield 打断的 */
+ boolean clsret;
+
+ /**
+ * {@code CIST_FRESH}:这一帧是 {@code luaV_execute} 的入口帧。
+ * 返回时若是 fresh 帧就退出解释器,否则回到调用者的帧继续跑——
+ * 这就是 {@code OP_CALL} 不递归而直接 {@code goto startfunc} 的代价:
+ * 得自己记住「该在哪一层停下来」。
+ */
+ boolean fresh;
+
+ /** {@code CIST_TAIL}:是一次尾调用 */
+ boolean tail;
+
+ /** {@code CIST_LEQ}:正在用 {@code __lt} 顶替缺失的 {@code __le} */
+ boolean leq;
+
+ CallInfo previous;
+
+ /** 本帧的名字,仅供错误信息与调试 */
+ String what = "?";
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/Codegen.java b/src/main/java/com/github/esrrhs/javalua/Codegen.java
new file mode 100644
index 0000000..a762bf6
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/Codegen.java
@@ -0,0 +1,1555 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * 代码生成 —— 逐行翻译 {@code lcode.c}(外加 {@code lcode.h} 的 BinOpr / UnOpr)。
+ *
+ * 这里几乎没有「设计」,只有搬运。真正值得记的是几条为什么不能改写的约束:
+ *
+ * 上游是 static,这里给包内可见是为了让「整数常量与浮点常量不合并」这条
+ * 规则能被测试直接锁住——它靠肉眼很难发现。
+ */
+ static int numberK(FuncState fs, double r) {
+ return addK(fs, LuaValue.ofFloat(r));
+ }
+
+ /** {@code boolK} */
+ private static int boolK(FuncState fs, boolean b) {
+ return addK(fs, LuaValue.ofBoolean(b));
+ }
+
+ /** {@code nilK} */
+ private static int nilK(FuncState fs) {
+ return addK(fs, LuaValue.ofNil());
+ }
+
+ /**
+ * {@code addk}:命中缓存就复用下标。
+ *
+ * 缓存是整份源码共享的({@code ls->h}),命中后还要按「下标没越界 + 变体标签
+ * 一致 + 值相等」三道校验——详见 {@link Lexer#lookupConstant}。
+ */
+ private static int addK(FuncState fs, LuaValue v) {
+ Integer idx = fs.ls.lookupConstant(fs, v);
+ if (idx != null) {
+ return idx;
+ }
+ if (fs.f.sizek >= OpCode.MAXARG_AX) {
+ throw fs.ls.syntaxError("too many constants");
+ }
+ return fs.ls.addConstant(fs, v);
+ }
+
+ /* ---------- 立即数 ---------- */
+
+ /** {@code fitsC}:能否塞进 sC 操作数(范围 -127..128) */
+ public static boolean fitsC(long i) {
+ return i >= -OpCode.OFFSET_SC && i <= OpCode.MAXARG_C - OpCode.OFFSET_SC;
+ }
+
+ /** {@code fitsBx}:能否塞进 sBx 操作数(范围 -65535..65536) */
+ public static boolean fitsBx(long i) {
+ return -OpCode.OFFSET_SBX <= i && i <= OpCode.MAXARG_BX - OpCode.OFFSET_SBX;
+ }
+
+ /** {@code luaK_int} */
+ public static void codeInt(FuncState fs, int reg, long i) {
+ if (fitsBx(i)) {
+ codeAsBx(fs, OpCode.OP_LOADI, reg, (int) i);
+ } else {
+ codeK(fs, reg, intK(fs, i));
+ }
+ }
+
+ /** {@code luaK_float} */
+ private static void codeFloat(FuncState fs, int reg, double f) {
+ Long fi = Arith.fltToInteger(f, Arith.F2I_EQ);
+ if (fi != null && fitsBx(fi)) {
+ codeAsBx(fs, OpCode.OP_LOADF, reg, fi.intValue());
+ } else {
+ codeK(fs, reg, numberK(fs, f));
+ }
+ }
+
+ /** {@code luaK_nil}:相邻的 LOADNIL 会合并成一条 */
+ public static void codeNil(FuncState fs, int from, int n) {
+ int l = from + n - 1; // 最后一个要置 nil 的寄存器
+ int previous = previousInstruction(fs);
+ if (previous != -1 && OpCode.getOpCode(previous) == OpCode.OP_LOADNIL) {
+ int pfrom = OpCode.getArgA(previous);
+ int pl = pfrom + OpCode.getArgB(previous);
+ if ((pfrom <= from && from <= pl + 1) || (from <= pfrom && pfrom <= l + 1)) {
+ if (pfrom < from) {
+ from = pfrom;
+ }
+ if (pl > l) {
+ l = pl;
+ }
+ int patched = OpCode.setArgA(previous, from);
+ patched = OpCode.setArgB(patched, l - from);
+ fs.f.setCode(fs.pc - 1, patched);
+ return;
+ }
+ }
+ codeABC(fs, OpCode.OP_LOADNIL, from, n - 1, 0);
+ }
+
+ /**
+ * {@code previousinstruction}:返回上一条指令;若两者之间可能存在跳转目标,
+ * 返回 -1(全 1 的非法指令),以避免跨基本块做优化。
+ */
+ private static int previousInstruction(FuncState fs) {
+ if (fs.pc > fs.lasttarget) {
+ return fs.f.code[fs.pc - 1];
+ }
+ return -1;
+ }
+
+ /* ---------- 跳转 ---------- */
+
+ /** {@code getjump} */
+ private static int getJump(FuncState fs, int pc) {
+ int offset = OpCode.getArgSJ(fs.f.code[pc]);
+ if (offset == NO_JUMP) {
+ return NO_JUMP; // 链表结束
+ }
+ return (pc + 1) + offset; // 相对偏移转绝对位置
+ }
+
+ /** {@code fixjump} */
+ private static void fixJump(FuncState fs, int pc, int dest) {
+ int offset = dest - (pc + 1);
+ if (!(-OpCode.OFFSET_SJ <= offset && offset <= OpCode.MAXARG_SJ - OpCode.OFFSET_SJ)) {
+ fs.ls.syntaxError("control structure too long");
+ }
+ fs.f.setCode(pc, OpCode.setArgSJ(fs.f.code[pc], offset));
+ }
+
+ /** {@code luaK_concat}:把链表 l2 接到 l1 尾部 */
+ public static void concat(FuncState fs, int[] l1, int l2) {
+ if (l2 == NO_JUMP) {
+ return;
+ }
+ if (l1[0] == NO_JUMP) {
+ l1[0] = l2;
+ } else {
+ int list = l1[0];
+ int next;
+ while ((next = getJump(fs, list)) != NO_JUMP) {
+ list = next;
+ }
+ fixJump(fs, list, l2);
+ }
+ }
+
+ /** 把 {@code list} 接到 {@code e} 的 t 链表上(C 里是 {@code &e->t}) */
+ public static void concatT(FuncState fs, ExpDesc e, int list) {
+ int[] box = new int[] { e.t };
+ concat(fs, box, list);
+ e.t = box[0];
+ }
+
+ /** 同上,接到 f 链表 */
+ public static void concatF(FuncState fs, ExpDesc e, int list) {
+ int[] box = new int[] { e.f };
+ concat(fs, box, list);
+ e.f = box[0];
+ }
+
+ /** {@code luaK_jump} */
+ public static int jump(FuncState fs) {
+ return codesJ(fs, OpCode.OP_JMP, NO_JUMP, 0);
+ }
+
+ /** {@code luaK_ret} */
+ public static void ret(FuncState fs, int first, int nret) {
+ int op;
+ switch (nret) {
+ case 0:
+ op = OpCode.OP_RETURN0;
+ break;
+ case 1:
+ op = OpCode.OP_RETURN1;
+ break;
+ default:
+ op = OpCode.OP_RETURN;
+ break;
+ }
+ codeABC(fs, op, first, nret + 1, 0);
+ }
+
+ /** {@code condjump} */
+ private static int condJump(FuncState fs, int op, int a, int b, int c, int k) {
+ codeABCk(fs, op, a, b, c, k);
+ return jump(fs);
+ }
+
+ /** {@code luaK_getlabel} */
+ public static int getLabel(FuncState fs) {
+ fs.lasttarget = fs.pc;
+ return fs.pc;
+ }
+
+ /** {@code getjumpcontrol}:找到真正控制这个跳转的那条(测试指令在前一条) */
+ private static int getJumpControl(FuncState fs, int pc) {
+ if (pc >= 1 && OpCode.testTMode(OpCode.getOpCode(fs.f.code[pc - 1]))) {
+ return pc - 1;
+ }
+ return pc;
+ }
+
+ /**
+ * {@code patchtestreg}:把 TESTSET 的目标寄存器改掉;
+ * 若不需要值就把它降级成 TEST。
+ *
+ * @return 是否成功(那条指令确实是 TESTSET)
+ */
+ private static boolean patchTestReg(FuncState fs, int node, int reg) {
+ int i = getJumpControl(fs, node);
+ int inst = fs.f.code[i];
+ if (OpCode.getOpCode(inst) != OpCode.OP_TESTSET) {
+ return false;
+ }
+ if (reg != OpCode.NO_REG && reg != OpCode.getArgB(inst)) {
+ fs.f.setCode(i, OpCode.setArgA(inst, reg));
+ } else {
+ // 不需要值,或寄存器里已经有值了 —— 降级成纯测试
+ fs.f.setCode(i, OpCode.createABCk(OpCode.OP_TEST, OpCode.getArgB(inst), 0, 0,
+ OpCode.getArgK(inst)));
+ }
+ return true;
+ }
+
+ /** {@code removevalues} */
+ private static void removeValues(FuncState fs, int list) {
+ for (; list != NO_JUMP; list = getJump(fs, list)) {
+ patchTestReg(fs, list, OpCode.NO_REG);
+ }
+ }
+
+ /** {@code patchlistaux} */
+ private static void patchListAux(FuncState fs, int list, int vtarget, int reg, int dtarget) {
+ while (list != NO_JUMP) {
+ int next = getJump(fs, list);
+ if (patchTestReg(fs, list, reg)) {
+ fixJump(fs, list, vtarget);
+ } else {
+ fixJump(fs, list, dtarget);
+ }
+ list = next;
+ }
+ }
+
+ /** {@code luaK_patchlist} */
+ public static void patchList(FuncState fs, int list, int target) {
+ patchListAux(fs, list, target, OpCode.NO_REG, target);
+ }
+
+ /** {@code luaK_patchtohere} */
+ public static void patchToHere(FuncState fs, int list) {
+ int hr = getLabel(fs);
+ patchList(fs, list, hr);
+ }
+
+ /** {@code luaK_jumpto} */
+ public static void jumpTo(FuncState fs, int target) {
+ patchList(fs, jump(fs), target);
+ }
+
+ /* ---------- 表达式落地 ---------- */
+
+ /** {@code luaK_dischargevars}:把「变量」变成「值」(可能仍在寄存器里) */
+ public static void dischargeVars(FuncState fs, ExpDesc e) {
+ switch (e.k) {
+ case ExpDesc.VCONST: {
+ LuaValue v = constVal(fs, e);
+ constToExp(v, e);
+ break;
+ }
+ case ExpDesc.VLOCAL: {
+ int temp = e.varRidx;
+ e.info = temp;
+ e.k = ExpDesc.VNONRELOC;
+ break;
+ }
+ case ExpDesc.VUPVAL: {
+ e.info = codeABC(fs, OpCode.OP_GETUPVAL, 0, e.info, 0);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ case ExpDesc.VINDEXUP: {
+ e.info = codeABC(fs, OpCode.OP_GETTABUP, 0, e.indT, e.indIdx);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ case ExpDesc.VINDEXI: {
+ freeReg(fs, e.indT);
+ e.info = codeABC(fs, OpCode.OP_GETI, 0, e.indT, e.indIdx);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ case ExpDesc.VINDEXSTR: {
+ freeReg(fs, e.indT);
+ e.info = codeABC(fs, OpCode.OP_GETFIELD, 0, e.indT, e.indIdx);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ case ExpDesc.VINDEXED: {
+ freeRegs(fs, e.indT, e.indIdx);
+ e.info = codeABC(fs, OpCode.OP_GETTABLE, 0, e.indT, e.indIdx);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ case ExpDesc.VVARARG:
+ case ExpDesc.VCALL: {
+ setOneRet(fs, e);
+ break;
+ }
+ default:
+ break; // 已经有值了
+ }
+ }
+
+ /** {@code const2val} */
+ private static LuaValue constVal(FuncState fs, ExpDesc e) {
+ return fs.dyd.actvar.get(e.info).k;
+ }
+
+ /** {@code const2exp} */
+ private static void constToExp(LuaValue v, ExpDesc e) {
+ if (v.isInteger()) {
+ e.k = ExpDesc.VKINT;
+ e.ival = v.i;
+ } else if (v.isFloat()) {
+ e.k = ExpDesc.VKFLT;
+ e.nval = v.n;
+ } else if (v.isBoolean()) {
+ e.k = v.asBoolean() ? ExpDesc.VTRUE : ExpDesc.VFALSE;
+ } else if (v.isNil()) {
+ e.k = ExpDesc.VNIL;
+ } else if (v.isString()) {
+ e.k = ExpDesc.VKSTR;
+ e.strval = v.asString();
+ } else {
+ throw new IllegalStateException("bad constant " + v);
+ }
+ }
+
+ /** {@code luaK_exp2const} */
+ public static boolean exp2Const(FuncState fs, ExpDesc e, LuaValue out) {
+ if (e.hasJumps()) {
+ return false;
+ }
+ switch (e.k) {
+ case ExpDesc.VFALSE:
+ out.setBoolean(false);
+ return true;
+ case ExpDesc.VTRUE:
+ out.setBoolean(true);
+ return true;
+ case ExpDesc.VNIL:
+ out.setNil();
+ return true;
+ case ExpDesc.VKSTR:
+ out.setString(e.strval);
+ return true;
+ case ExpDesc.VCONST:
+ out.set(constVal(fs, e));
+ return true;
+ default:
+ if (e.k == ExpDesc.VKINT) {
+ out.setInteger(e.ival);
+ return true;
+ }
+ if (e.k == ExpDesc.VKFLT) {
+ out.setFloat(e.nval);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /** {@code discharge2reg} */
+ private static void discharge2Reg(FuncState fs, ExpDesc e, int reg) {
+ dischargeVars(fs, e);
+ switch (e.k) {
+ case ExpDesc.VNIL:
+ codeNil(fs, reg, 1);
+ break;
+ case ExpDesc.VFALSE:
+ codeABC(fs, OpCode.OP_LOADFALSE, reg, 0, 0);
+ break;
+ case ExpDesc.VTRUE:
+ codeABC(fs, OpCode.OP_LOADTRUE, reg, 0, 0);
+ break;
+ case ExpDesc.VKSTR:
+ e.info = stringK(fs, e.strval);
+ e.k = ExpDesc.VK;
+ // FALLTHROUGH
+ case ExpDesc.VK:
+ codeK(fs, reg, e.info);
+ break;
+ case ExpDesc.VKFLT:
+ codeFloat(fs, reg, e.nval);
+ break;
+ case ExpDesc.VKINT:
+ codeInt(fs, reg, e.ival);
+ break;
+ case ExpDesc.VRELOC: {
+ int pc = e.info;
+ fs.f.setCode(pc, OpCode.setArgA(fs.f.code[pc], reg));
+ break;
+ }
+ case ExpDesc.VNONRELOC:
+ if (reg != e.info) {
+ codeABC(fs, OpCode.OP_MOVE, reg, e.info, 0);
+ }
+ break;
+ default:
+ return; // VJMP:值由跳转链表决定,这里什么都不做
+ }
+ e.info = reg;
+ e.k = ExpDesc.VNONRELOC;
+ }
+
+ private static void discharge2AnyReg(FuncState fs, ExpDesc e) {
+ if (e.k != ExpDesc.VNONRELOC) {
+ reserveRegs(fs, 1);
+ discharge2Reg(fs, e, fs.freereg - 1);
+ }
+ }
+
+ /** {@code code_loadbool} */
+ private static int codeLoadBool(FuncState fs, int a, int op) {
+ getLabel(fs); // 这两条指令可能是跳转目标
+ return codeABC(fs, op, a, 0, 0);
+ }
+
+ /** {@code need_value} */
+ private static boolean needValue(FuncState fs, int list) {
+ for (; list != NO_JUMP; list = getJump(fs, list)) {
+ int i = fs.f.code[getJumpControl(fs, list)];
+ if (OpCode.getOpCode(i) != OpCode.OP_TESTSET) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** {@code exp2reg} */
+ private static void exp2Reg(FuncState fs, ExpDesc e, int reg) {
+ discharge2Reg(fs, e, reg);
+ if (e.k == ExpDesc.VJMP) {
+ concatT(fs, e, e.info);
+ }
+ if (e.hasJumps()) {
+ int pF = NO_JUMP; // 可能的 LOADFALSE
+ int pT = NO_JUMP; // 可能的 LOADTRUE
+ if (needValue(fs, e.t) || needValue(fs, e.f)) {
+ int fj = (e.k == ExpDesc.VJMP) ? NO_JUMP : jump(fs);
+ pF = codeLoadBool(fs, reg, OpCode.OP_LFALSESKIP);
+ pT = codeLoadBool(fs, reg, OpCode.OP_LOADTRUE);
+ patchToHere(fs, fj);
+ }
+ int fin = getLabel(fs);
+ patchListAux(fs, e.f, fin, reg, pF);
+ patchListAux(fs, e.t, fin, reg, pT);
+ }
+ e.f = e.t = NO_JUMP;
+ e.info = reg;
+ e.k = ExpDesc.VNONRELOC;
+ }
+
+ /** {@code luaK_exp2nextreg} */
+ public static void exp2NextReg(FuncState fs, ExpDesc e) {
+ dischargeVars(fs, e);
+ freeExp(fs, e);
+ reserveRegs(fs, 1);
+ exp2Reg(fs, e, fs.freereg - 1);
+ }
+
+ /** {@code luaK_exp2anyreg} */
+ public static int exp2AnyReg(FuncState fs, ExpDesc e) {
+ dischargeVars(fs, e);
+ if (e.k == ExpDesc.VNONRELOC) {
+ if (!e.hasJumps()) {
+ return e.info;
+ }
+ if (e.info >= fs.nvarstack()) {
+ exp2Reg(fs, e, e.info);
+ return e.info;
+ }
+ // 否则它是局部变量:有跳转时不能拿它当结果的落点,走默认分支
+ }
+ exp2NextReg(fs, e);
+ return e.info;
+ }
+
+ /** {@code luaK_exp2anyregup} */
+ public static void exp2AnyRegUp(FuncState fs, ExpDesc e) {
+ if (e.k != ExpDesc.VUPVAL || e.hasJumps()) {
+ exp2AnyReg(fs, e);
+ }
+ }
+
+ /** {@code luaK_exp2val} */
+ public static void exp2Val(FuncState fs, ExpDesc e) {
+ if (e.hasJumps()) {
+ exp2AnyReg(fs, e);
+ } else {
+ dischargeVars(fs, e);
+ }
+ }
+
+ /** {@code luaK_exp2K} */
+ private static boolean exp2K(FuncState fs, ExpDesc e) {
+ if (!e.hasJumps()) {
+ int info;
+ switch (e.k) {
+ case ExpDesc.VTRUE:
+ info = boolK(fs, true);
+ break;
+ case ExpDesc.VFALSE:
+ info = boolK(fs, false);
+ break;
+ case ExpDesc.VNIL:
+ info = nilK(fs);
+ break;
+ case ExpDesc.VKINT:
+ info = intK(fs, e.ival);
+ break;
+ case ExpDesc.VKFLT:
+ info = numberK(fs, e.nval);
+ break;
+ case ExpDesc.VKSTR:
+ info = stringK(fs, e.strval);
+ break;
+ case ExpDesc.VK:
+ info = e.info;
+ break;
+ default:
+ return false; // 不是常量
+ }
+ if (info <= OpCode.MAXARG_B) { // 能塞进 RK 下标?
+ e.k = ExpDesc.VK;
+ e.info = info;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** {@code exp2RK} */
+ private static boolean exp2RK(FuncState fs, ExpDesc e) {
+ if (exp2K(fs, e)) {
+ return true;
+ }
+ exp2AnyReg(fs, e);
+ return false;
+ }
+
+ /** {@code codeABRK} */
+ private static void codeABRK(FuncState fs, int o, int a, int b, ExpDesc ec) {
+ int k = exp2RK(fs, ec) ? 1 : 0;
+ codeABCk(fs, o, a, b, ec.info, k);
+ }
+
+ /* ---------- 多返回值 ---------- */
+
+ /** {@code luaK_setreturns} */
+ public static void setReturns(FuncState fs, ExpDesc e, int nresults) {
+ int pc = e.info;
+ if (e.k == ExpDesc.VCALL) {
+ fs.f.setCode(pc, OpCode.setArgC(fs.f.code[pc], nresults + 1));
+ } else {
+ int inst = fs.f.code[pc];
+ inst = OpCode.setArgC(inst, nresults + 1);
+ inst = OpCode.setArgA(inst, fs.freereg);
+ fs.f.setCode(pc, inst);
+ reserveRegs(fs, 1);
+ }
+ }
+
+ /** {@code luaK_setmultret} */
+ public static void setMultiRet(FuncState fs, ExpDesc e) {
+ setReturns(fs, e, MULTRET);
+ }
+
+ /** {@code luaK_setoneret} */
+ public static void setOneRet(FuncState fs, ExpDesc e) {
+ if (e.k == ExpDesc.VCALL) {
+ e.k = ExpDesc.VNONRELOC;
+ e.info = OpCode.getArgA(fs.f.code[e.info]);
+ } else if (e.k == ExpDesc.VVARARG) {
+ fs.f.setCode(e.info, OpCode.setArgC(fs.f.code[e.info], 2));
+ e.k = ExpDesc.VRELOC;
+ }
+ }
+
+ /* ---------- 变量读写 ---------- */
+
+ /** {@code luaK_storevar} */
+ public static void storeVar(FuncState fs, ExpDesc var, ExpDesc ex) {
+ switch (var.k) {
+ case ExpDesc.VLOCAL:
+ freeExp(fs, ex);
+ exp2Reg(fs, ex, var.varRidx);
+ return;
+ case ExpDesc.VUPVAL: {
+ int e = exp2AnyReg(fs, ex);
+ codeABC(fs, OpCode.OP_SETUPVAL, e, var.info, 0);
+ break;
+ }
+ case ExpDesc.VINDEXUP:
+ codeABRK(fs, OpCode.OP_SETTABUP, var.indT, var.indIdx, ex);
+ break;
+ case ExpDesc.VINDEXI:
+ codeABRK(fs, OpCode.OP_SETI, var.indT, var.indIdx, ex);
+ break;
+ case ExpDesc.VINDEXSTR:
+ codeABRK(fs, OpCode.OP_SETFIELD, var.indT, var.indIdx, ex);
+ break;
+ case ExpDesc.VINDEXED:
+ codeABRK(fs, OpCode.OP_SETTABLE, var.indT, var.indIdx, ex);
+ break;
+ default:
+ throw new IllegalStateException("invalid var kind to store: " + var);
+ }
+ freeExp(fs, ex);
+ }
+
+ /** {@code luaK_self} */
+ public static void self(FuncState fs, ExpDesc e, ExpDesc key) {
+ exp2AnyReg(fs, e);
+ int ereg = e.info;
+ freeExp(fs, e);
+ e.info = fs.freereg;
+ e.k = ExpDesc.VNONRELOC;
+ reserveRegs(fs, 2); // 方法本身与 self
+ codeABRK(fs, OpCode.OP_SELF, e.info, ereg, key);
+ freeExp(fs, key);
+ }
+
+ /** {@code luaK_indexed} */
+ public static void indexed(FuncState fs, ExpDesc t, ExpDesc k) {
+ if (k.k == ExpDesc.VKSTR) {
+ k.info = stringK(fs, k.strval);
+ k.k = ExpDesc.VK;
+ }
+ if (t.k == ExpDesc.VUPVAL && !isKstr(fs, k)) {
+ exp2AnyReg(fs, t); // upvalue 只能被字面短串索引
+ }
+ if (t.k == ExpDesc.VUPVAL) {
+ int temp = t.info;
+ t.indT = temp;
+ t.indIdx = k.info;
+ t.k = ExpDesc.VINDEXUP;
+ } else {
+ t.indT = (t.k == ExpDesc.VLOCAL) ? t.varRidx : t.info;
+ if (isKstr(fs, k)) {
+ t.indIdx = k.info;
+ t.k = ExpDesc.VINDEXSTR;
+ } else if (isCint(k)) {
+ t.indIdx = (int) k.ival;
+ t.k = ExpDesc.VINDEXI;
+ } else {
+ t.indIdx = exp2AnyReg(fs, k);
+ t.k = ExpDesc.VINDEXED;
+ }
+ }
+ }
+
+ /** {@code isKstr}:常量短串(GETFIELD/SETFIELD 要求短串) */
+ private static boolean isKstr(FuncState fs, ExpDesc e) {
+ return e.k == ExpDesc.VK && !e.hasJumps() && e.info <= OpCode.MAXARG_B
+ && fs.f.k[e.info].asString().isShort();
+ }
+
+ /** {@code isKint} */
+ private static boolean isKint(ExpDesc e) {
+ return e.k == ExpDesc.VKINT && !e.hasJumps();
+ }
+
+ /** {@code isCint}:能塞进 C 字段的整数字面量 */
+ private static boolean isCint(ExpDesc e) {
+ return isKint(e) && e.ival >= 0 && e.ival <= OpCode.MAXARG_C;
+ }
+
+ /** {@code isSCint}:能塞进 sC 字段的整数字面量 */
+ private static boolean isSCint(ExpDesc e) {
+ return isKint(e) && fitsC(e.ival);
+ }
+
+ /** {@code isSCnumber} */
+ private static boolean isSCnumber(ExpDesc e, int[] im, boolean[] isfloat) {
+ long i;
+ if (e.k == ExpDesc.VKINT) {
+ i = e.ival;
+ } else if (e.k == ExpDesc.VKFLT) {
+ Long fi = Arith.fltToInteger(e.nval, Arith.F2I_EQ);
+ if (fi == null) {
+ return false;
+ }
+ i = fi;
+ isfloat[0] = true;
+ } else {
+ return false;
+ }
+ if (!e.hasJumps() && fitsC(i)) {
+ im[0] = OpCode.int2sC((int) i);
+ return true;
+ }
+ return false;
+ }
+
+ /* ---------- 条件 ---------- */
+
+ /** {@code negatecondition} */
+ private static void negateCondition(FuncState fs, ExpDesc e) {
+ int pc = getJumpControl(fs, e.info);
+ int inst = fs.f.code[pc];
+ fs.f.setCode(pc, OpCode.setArgK(inst, OpCode.getArgK(inst) ^ 1));
+ }
+
+ /** {@code jumponcond} */
+ private static int jumpOnCond(FuncState fs, ExpDesc e, int cond) {
+ if (e.k == ExpDesc.VRELOC) {
+ int ie = fs.f.code[e.info];
+ if (OpCode.getOpCode(ie) == OpCode.OP_NOT) {
+ removeLastInstruction(fs); // 消掉这条 NOT
+ return condJump(fs, OpCode.OP_TEST, OpCode.getArgB(ie), 0, 0, cond == 0 ? 1 : 0);
+ }
+ }
+ discharge2AnyReg(fs, e);
+ freeExp(fs, e);
+ return condJump(fs, OpCode.OP_TESTSET, OpCode.NO_REG, e.info, 0, cond);
+ }
+
+ /** {@code luaK_goiftrue} */
+ public static void goIfTrue(FuncState fs, ExpDesc e) {
+ int pc;
+ dischargeVars(fs, e);
+ switch (e.k) {
+ case ExpDesc.VJMP:
+ negateCondition(fs, e);
+ pc = e.info;
+ break;
+ case ExpDesc.VK:
+ case ExpDesc.VKFLT:
+ case ExpDesc.VKINT:
+ case ExpDesc.VKSTR:
+ case ExpDesc.VTRUE:
+ pc = NO_JUMP; // 恒为真
+ break;
+ default:
+ pc = jumpOnCond(fs, e, 0); // 为假则跳
+ break;
+ }
+ concatF(fs, e, pc);
+ patchToHere(fs, e.t);
+ e.t = NO_JUMP;
+ }
+
+ /** {@code luaK_goiffalse} */
+ public static void goIfFalse(FuncState fs, ExpDesc e) {
+ int pc;
+ dischargeVars(fs, e);
+ switch (e.k) {
+ case ExpDesc.VJMP:
+ pc = e.info;
+ break;
+ case ExpDesc.VNIL:
+ case ExpDesc.VFALSE:
+ pc = NO_JUMP; // 恒为假
+ break;
+ default:
+ pc = jumpOnCond(fs, e, 1); // 为真则跳
+ break;
+ }
+ concatT(fs, e, pc);
+ patchToHere(fs, e.f);
+ e.f = NO_JUMP;
+ }
+
+ /** {@code codenot} */
+ private static void codeNot(FuncState fs, ExpDesc e) {
+ switch (e.k) {
+ case ExpDesc.VNIL:
+ case ExpDesc.VFALSE:
+ e.k = ExpDesc.VTRUE;
+ break;
+ case ExpDesc.VK:
+ case ExpDesc.VKFLT:
+ case ExpDesc.VKINT:
+ case ExpDesc.VKSTR:
+ case ExpDesc.VTRUE:
+ e.k = ExpDesc.VFALSE;
+ break;
+ case ExpDesc.VJMP:
+ negateCondition(fs, e);
+ break;
+ case ExpDesc.VRELOC:
+ case ExpDesc.VNONRELOC: {
+ discharge2AnyReg(fs, e);
+ freeExp(fs, e);
+ e.info = codeABC(fs, OpCode.OP_NOT, 0, e.info, 0);
+ e.k = ExpDesc.VRELOC;
+ break;
+ }
+ default:
+ throw new IllegalStateException("cannot happen: " + e);
+ }
+ int temp = e.f;
+ e.f = e.t;
+ e.t = temp;
+ removeValues(fs, e.f); // 取反之后值就没用了
+ removeValues(fs, e.t);
+ }
+
+ /* ---------- 运算符 ---------- */
+
+ /** {@code binopr2op}(ORDER OPR - ORDER OP) */
+ private static int binOpr2Op(int opr, int baser, int base) {
+ return (opr - baser) + base;
+ }
+
+ /** {@code unopr2op} */
+ private static int unOpr2Op(int opr) {
+ return (opr - UNOPR_MINUS) + OpCode.OP_UNM;
+ }
+
+ /** {@code binopr2TM}(ORDER OPR - ORDER TM) */
+ private static int binOpr2TM(int opr) {
+ return (opr - OPR_ADD) + Tm.TM_ADD;
+ }
+
+ /** {@code codeunexpval} */
+ private static void codeUnExpVal(FuncState fs, int op, ExpDesc e, int line) {
+ int r = exp2AnyReg(fs, e);
+ freeExp(fs, e);
+ e.info = codeABC(fs, op, 0, r, 0);
+ e.k = ExpDesc.VRELOC;
+ fixLine(fs, line);
+ }
+
+ /** {@code finishbinexpval} */
+ private static void finishBinExpVal(FuncState fs, ExpDesc e1, ExpDesc e2, int op, int v2,
+ int flip, int line, int mmop, int event) {
+ int v1 = exp2AnyReg(fs, e1);
+ int pc = codeABCk(fs, op, 0, v1, v2, 0);
+ freeExps(fs, e1, e2);
+ e1.info = pc;
+ e1.k = ExpDesc.VRELOC;
+ fixLine(fs, line);
+ codeABCk(fs, mmop, v1, v2, event, flip); // 元方法兜底
+ fixLine(fs, line);
+ }
+
+ /** {@code codebinexpval} */
+ private static void codeBinExpVal(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int line) {
+ int op = binOpr2Op(opr, OPR_ADD, OpCode.OP_ADD);
+ int v2 = exp2AnyReg(fs, e2);
+ finishBinExpVal(fs, e1, e2, op, v2, 0, line, OpCode.OP_MMBIN, binOpr2TM(opr));
+ }
+
+ /** {@code codebini} */
+ private static void codeBinI(FuncState fs, int op, ExpDesc e1, ExpDesc e2, int flip, int line,
+ int event) {
+ int v2 = OpCode.int2sC((int) e2.ival);
+ finishBinExpVal(fs, e1, e2, op, v2, flip, line, OpCode.OP_MMBINI, event);
+ }
+
+ /** {@code codebinK} */
+ private static void codeBinK(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int flip,
+ int line) {
+ int event = binOpr2TM(opr);
+ int v2 = e2.info;
+ int op = binOpr2Op(opr, OPR_ADD, OpCode.OP_ADDK);
+ finishBinExpVal(fs, e1, e2, op, v2, flip, line, OpCode.OP_MMBINK, event);
+ }
+
+ /** {@code finishbinexpneg}:{@code a - 5} 编成 {@code a + (-5)} */
+ private static boolean finishBinExpNeg(FuncState fs, ExpDesc e1, ExpDesc e2, int op, int line,
+ int event) {
+ if (!isKint(e2)) {
+ return false;
+ }
+ long i2 = e2.ival;
+ if (!(fitsC(i2) && fitsC(-i2))) {
+ return false;
+ }
+ int v2 = (int) i2;
+ finishBinExpVal(fs, e1, e2, op, OpCode.int2sC(-v2), 0, line, OpCode.OP_MMBINI, event);
+ // 元方法必须拿到原本的第二个操作数
+ int last = fs.pc - 1;
+ fs.f.setCode(last, OpCode.setArgB(fs.f.code[last], OpCode.int2sC(v2)));
+ return true;
+ }
+
+ private static void swapExps(ExpDesc e1, ExpDesc e2) {
+ ExpDesc temp = new ExpDesc(e1);
+ e1.set(e2);
+ e2.set(temp);
+ }
+
+ /** {@code codebinNoK} */
+ private static void codeBinNoK(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int flip,
+ int line) {
+ if (flip != 0) {
+ swapExps(e1, e2);
+ }
+ codeBinExpVal(fs, opr, e1, e2, line);
+ }
+
+ /** {@code codearith} */
+ private static void codeArith(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int flip,
+ int line) {
+ if (isNumeral(e2) && exp2K(fs, e2)) {
+ codeBinK(fs, opr, e1, e2, flip, line);
+ } else {
+ codeBinNoK(fs, opr, e1, e2, flip, line);
+ }
+ }
+
+ /** {@code codecommutative} */
+ private static void codeCommutative(FuncState fs, int op, ExpDesc e1, ExpDesc e2, int line) {
+ int flip = 0;
+ if (isNumeral(e1)) {
+ swapExps(e1, e2);
+ flip = 1;
+ }
+ if (op == OPR_ADD && isSCint(e2)) {
+ codeBinI(fs, OpCode.OP_ADDI, e1, e2, flip, line, Tm.TM_ADD);
+ } else {
+ codeArith(fs, op, e1, e2, flip, line);
+ }
+ }
+
+ /** {@code codebitwise} */
+ private static void codeBitwise(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int line) {
+ int flip = 0;
+ if (e1.k == ExpDesc.VKINT) {
+ swapExps(e1, e2);
+ flip = 1;
+ }
+ if (e2.k == ExpDesc.VKINT && exp2K(fs, e2)) {
+ codeBinK(fs, opr, e1, e2, flip, line);
+ } else {
+ codeBinNoK(fs, opr, e1, e2, flip, line);
+ }
+ }
+
+ /** {@code codeorder} */
+ private static void codeOrder(FuncState fs, int opr, ExpDesc e1, ExpDesc e2) {
+ int r1;
+ int r2;
+ int[] im = new int[1];
+ boolean[] isfloat = new boolean[1];
+ int op;
+ if (isSCnumber(e2, im, isfloat)) {
+ r1 = exp2AnyReg(fs, e1);
+ r2 = im[0];
+ op = binOpr2Op(opr, OPR_LT, OpCode.OP_LTI);
+ } else if (isSCnumber(e1, im, isfloat)) {
+ // (A < B) 变成 (B > A),(A <= B) 变成 (B >= A)
+ r1 = exp2AnyReg(fs, e2);
+ r2 = im[0];
+ op = binOpr2Op(opr, OPR_LT, OpCode.OP_GTI);
+ } else {
+ r1 = exp2AnyReg(fs, e1);
+ r2 = exp2AnyReg(fs, e2);
+ op = binOpr2Op(opr, OPR_LT, OpCode.OP_LT);
+ }
+ freeExps(fs, e1, e2);
+ e1.info = condJump(fs, op, r1, r2, isfloat[0] ? 1 : 0, 1);
+ e1.k = ExpDesc.VJMP;
+ }
+
+ /** {@code codeeq} */
+ private static void codeEq(FuncState fs, int opr, ExpDesc e1, ExpDesc e2) {
+ int r1;
+ int r2;
+ int[] im = new int[1];
+ boolean[] isfloat = new boolean[1];
+ int op;
+ if (e1.k != ExpDesc.VNONRELOC) {
+ swapExps(e1, e2);
+ }
+ r1 = exp2AnyReg(fs, e1); // 第一个操作数必须在寄存器里
+ if (isSCnumber(e2, im, isfloat)) {
+ op = OpCode.OP_EQI;
+ r2 = im[0];
+ } else if (exp2RK(fs, e2)) {
+ op = OpCode.OP_EQK;
+ r2 = e2.info;
+ } else {
+ op = OpCode.OP_EQ;
+ r2 = exp2AnyReg(fs, e2);
+ }
+ freeExps(fs, e1, e2);
+ e1.info = condJump(fs, op, r1, r2, isfloat[0] ? 1 : 0, opr == OPR_EQ ? 1 : 0);
+ e1.k = ExpDesc.VJMP;
+ }
+
+ /** {@code codeconcat} */
+ private static void codeConcat(FuncState fs, ExpDesc e1, ExpDesc e2, int line) {
+ int ie2 = previousInstruction(fs);
+ if (ie2 != -1 && OpCode.getOpCode(ie2) == OpCode.OP_CONCAT) {
+ int n = OpCode.getArgB(ie2); // e2 里已拼接的元素数
+ freeExp(fs, e2);
+ int patched = OpCode.setArgA(ie2, e1.info);
+ patched = OpCode.setArgB(patched, n + 1);
+ fs.f.setCode(fs.pc - 1, patched);
+ } else {
+ codeABC(fs, OpCode.OP_CONCAT, e1.info, 2, 0);
+ freeExp(fs, e2);
+ fixLine(fs, line);
+ }
+ }
+
+ /* ---------- 常量折叠 ---------- */
+
+ /** {@code tonumeral} */
+ private static boolean isNumeral(ExpDesc e) {
+ return !e.hasJumps() && (e.k == ExpDesc.VKINT || e.k == ExpDesc.VKFLT);
+ }
+
+ /** {@code validop}:折叠会不会引发错误(除零、浮点转整数失败) */
+ private static boolean validOp(int op, LuaValue v1, LuaValue v2) {
+ switch (op) {
+ case Arith.OP_BAND:
+ case Arith.OP_BOR:
+ case Arith.OP_BXOR:
+ case Arith.OP_SHL:
+ case Arith.OP_SHR:
+ case Arith.OP_BNOT:
+ return Arith.toIntegerNs(v1) != null && Arith.toIntegerNs(v2) != null;
+ case Arith.OP_DIV:
+ case Arith.OP_IDIV:
+ case Arith.OP_MOD: {
+ Double n = v2.isInteger() ? (double) v2.i : (v2.isFloat() ? v2.n : null);
+ return n != null && n != 0;
+ }
+ default:
+ return true;
+ }
+ }
+
+ /** {@code constfolding} */
+ private static boolean constFolding(FuncState fs, int op, ExpDesc e1, ExpDesc e2) {
+ LuaValue v1 = toNumeralValue(e1);
+ LuaValue v2 = toNumeralValue(e2);
+ if (v1 == null || v2 == null || !validOp(op, v1, v2)) {
+ return false;
+ }
+ /*
+ * 常量折叠没有 LuaState,除零那两个分支也到不了
+ * (validOp 已经把它们挡在外面),所以这里传 null 是安全的。
+ */
+ LuaValue res = Arith.rawArith(null, op, v1, v2);
+ if (res == null) {
+ return false;
+ }
+ if (res.isInteger()) {
+ e1.k = ExpDesc.VKINT;
+ e1.ival = res.i;
+ } else {
+ // NaN 与 0.0 不折叠(避免 -0.0 的问题)
+ double n = res.n;
+ if (Double.isNaN(n) || n == 0) {
+ return false;
+ }
+ e1.k = ExpDesc.VKFLT;
+ e1.nval = n;
+ }
+ return true;
+ }
+
+ private static LuaValue toNumeralValue(ExpDesc e) {
+ if (e.hasJumps()) {
+ return null;
+ }
+ if (e.k == ExpDesc.VKINT) {
+ return LuaValue.ofInteger(e.ival);
+ }
+ if (e.k == ExpDesc.VKFLT) {
+ return LuaValue.ofFloat(e.nval);
+ }
+ return null;
+ }
+
+ /* ---------- prefix / infix / posfix ---------- */
+
+ /** {@code luaK_prefix} */
+ public static void prefix(FuncState fs, int opr, ExpDesc e, int line) {
+ dischargeVars(fs, e);
+ switch (opr) {
+ case UNOPR_MINUS:
+ case UNOPR_BNOT: {
+ ExpDesc ef = ExpDesc.ofInt(0); // 假的第二个操作数
+ if (constFolding(fs, opr + Arith.OP_UNM, e, ef)) {
+ break;
+ }
+ // FALLTHROUGH
+ codeUnExpVal(fs, unOpr2Op(opr), e, line);
+ break;
+ }
+ case UNOPR_LEN:
+ codeUnExpVal(fs, unOpr2Op(opr), e, line);
+ break;
+ case UNOPR_NOT:
+ codeNot(fs, e);
+ break;
+ default:
+ throw new IllegalStateException("bad unopr " + opr);
+ }
+ }
+
+ /** {@code luaK_infix} */
+ public static void infix(FuncState fs, int op, ExpDesc v) {
+ dischargeVars(fs, v);
+ switch (op) {
+ case OPR_AND:
+ goIfTrue(fs, v);
+ break;
+ case OPR_OR:
+ goIfFalse(fs, v);
+ break;
+ case OPR_CONCAT:
+ exp2NextReg(fs, v); // 拼接的操作数必须在栈上相邻
+ break;
+ case OPR_ADD:
+ case OPR_SUB:
+ case OPR_MUL:
+ case OPR_DIV:
+ case OPR_IDIV:
+ case OPR_MOD:
+ case OPR_POW:
+ case OPR_BAND:
+ case OPR_BOR:
+ case OPR_BXOR:
+ case OPR_SHL:
+ case OPR_SHR:
+ if (!isNumeral(v)) {
+ exp2AnyReg(fs, v);
+ }
+ break;
+ case OPR_EQ:
+ case OPR_NE:
+ if (!isNumeral(v)) {
+ exp2RK(fs, v);
+ }
+ break;
+ case OPR_LT:
+ case OPR_LE:
+ case OPR_GT:
+ case OPR_GE: {
+ int[] dummy = new int[1];
+ boolean[] dummy2 = new boolean[1];
+ if (!isSCnumber(v, dummy, dummy2)) {
+ exp2AnyReg(fs, v);
+ }
+ break;
+ }
+ default:
+ throw new IllegalStateException("bad binopr " + op);
+ }
+ }
+
+ /** {@code luaK_posfix} */
+ public static void posfix(FuncState fs, int opr, ExpDesc e1, ExpDesc e2, int line) {
+ dischargeVars(fs, e2);
+ if (foldBinOp(opr) && constFolding(fs, opr + Arith.OP_ADD, e1, e2)) {
+ return; // 折叠掉了
+ }
+ switch (opr) {
+ case OPR_AND:
+ concatF(fs, e2, e1.f);
+ e1.set(e2);
+ break;
+ case OPR_OR:
+ concatT(fs, e2, e1.t);
+ e1.set(e2);
+ break;
+ case OPR_CONCAT:
+ exp2NextReg(fs, e2);
+ codeConcat(fs, e1, e2, line);
+ break;
+ case OPR_ADD:
+ case OPR_MUL:
+ codeCommutative(fs, opr, e1, e2, line);
+ break;
+ case OPR_SUB:
+ if (finishBinExpNeg(fs, e1, e2, OpCode.OP_ADDI, line, Tm.TM_SUB)) {
+ break; // 编成了 (r1 + -I)
+ }
+ // FALLTHROUGH
+ case OPR_DIV:
+ case OPR_IDIV:
+ case OPR_MOD:
+ case OPR_POW:
+ codeArith(fs, opr, e1, e2, 0, line);
+ break;
+ case OPR_BAND:
+ case OPR_BOR:
+ case OPR_BXOR:
+ codeBitwise(fs, opr, e1, e2, line);
+ break;
+ case OPR_SHL:
+ if (isSCint(e1)) {
+ swapExps(e1, e2);
+ codeBinI(fs, OpCode.OP_SHLI, e1, e2, 1, line, Tm.TM_SHL); // I << r2
+ } else if (finishBinExpNeg(fs, e1, e2, OpCode.OP_SHRI, line, Tm.TM_SHL)) {
+ // 编成了 (r1 >> -I)
+ } else {
+ codeBinExpVal(fs, opr, e1, e2, line);
+ }
+ break;
+ case OPR_SHR:
+ if (isSCint(e2)) {
+ codeBinI(fs, OpCode.OP_SHRI, e1, e2, 0, line, Tm.TM_SHR); // r1 >> I
+ } else {
+ codeBinExpVal(fs, opr, e1, e2, line);
+ }
+ break;
+ case OPR_EQ:
+ case OPR_NE:
+ codeEq(fs, opr, e1, e2);
+ break;
+ case OPR_GT:
+ case OPR_GE:
+ // (a > b) == (b < a);(a >= b) == (b <= a)
+ swapExps(e1, e2);
+ opr = (opr - OPR_GT) + OPR_LT;
+ // FALLTHROUGH
+ case OPR_LT:
+ case OPR_LE:
+ codeOrder(fs, opr, e1, e2);
+ break;
+ default:
+ throw new IllegalStateException("bad binopr " + opr);
+ }
+ }
+
+ /* ---------- 表构造 ---------- */
+
+ /** {@code luaK_settablesize}:回填 NEWTABLE 的数组/哈希大小 */
+ public static void setTableSize(FuncState fs, int pc, int ra, int asize, int hsize) {
+ int rb = (hsize != 0) ? ceilLog2(hsize) + 1 : 0;
+ int extra = asize / (OpCode.MAXARG_C + 1); // 数组大小的高位
+ int rc = asize % (OpCode.MAXARG_C + 1); // 低位
+ int k = (extra > 0) ? 1 : 0;
+ fs.f.setCode(pc, OpCode.createABCk(OpCode.OP_NEWTABLE, ra, rb, rc, k));
+ fs.f.setCode(pc + 1, OpCode.createAx(OpCode.OP_EXTRAARG, extra));
+ }
+
+ /**
+ * {@code luaO_ceillog2}:不小于 {@code log2(x)} 的最小整数。
+ *
+ * 上游用「先 {@code x--} 再查一张 256 项表」实现,那张表记的其实是
+ * 「表示 x 需要几位」,所以整件事等价于 {@code bitlength(x - 1)}。
+ * 直接写等价形式而不是照抄移位:照抄的版本我写错过一次
+ * ({@code x = 2} 算出 0 而不是 1,于是 {@code {a = 1, b = 2}} 的
+ * NEWTABLE 少算一格哈希)。
+ */
+ static int ceilLog2(int x) {
+ if (x <= 1) {
+ return 0;
+ }
+ return 32 - Integer.numberOfLeadingZeros(x - 1);
+ }
+
+ /** {@code luaK_setlist} */
+ public static void setList(FuncState fs, int base, int nelems, int tostore) {
+ if (tostore == MULTRET) {
+ tostore = 0;
+ }
+ if (nelems <= OpCode.MAXARG_C) {
+ codeABC(fs, OpCode.OP_SETLIST, base, tostore, nelems);
+ } else {
+ int extra = nelems / (OpCode.MAXARG_C + 1);
+ nelems %= (OpCode.MAXARG_C + 1);
+ codeABCk(fs, OpCode.OP_SETLIST, base, tostore, nelems, 1);
+ codeExtraArg(fs, extra);
+ }
+ fs.freereg = base + 1; // 列表里的值占用的寄存器释放掉
+ }
+
+ /* ---------- 收尾 ---------- */
+
+ /** {@code finaltarget}:跳过「跳到跳转」的链 */
+ private static int finalTarget(int[] code, int i) {
+ for (int count = 0; count < 100; count++) { // 防止死循环
+ int pc = code[i];
+ if (OpCode.getOpCode(pc) != OpCode.OP_JMP) {
+ break;
+ }
+ i += OpCode.getArgSJ(pc) + 1;
+ }
+ return i;
+ }
+
+ /** {@code luaK_finish}:最后一遍扫描,做小优化与回填 */
+ public static void finish(FuncState fs) {
+ Proto p = fs.f;
+ for (int i = 0; i < fs.pc; i++) {
+ int inst = p.code[i];
+ int op = OpCode.getOpCode(inst);
+ switch (op) {
+ case OpCode.OP_RETURN0:
+ case OpCode.OP_RETURN1: {
+ if (!(fs.needclose || p.isVararg)) {
+ break; // 没什么要补的
+ }
+ inst = OpCode.setOpCode(inst, OpCode.OP_RETURN);
+ op = OpCode.OP_RETURN;
+ }
+ // FALLTHROUGH
+ case OpCode.OP_RETURN:
+ case OpCode.OP_TAILCALL: {
+ if (fs.needclose) {
+ inst = OpCode.setArgK(inst, 1);
+ }
+ if (p.isVararg) {
+ inst = OpCode.setArgC(inst, p.numparams + 1);
+ }
+ p.setCode(i, inst);
+ break;
+ }
+ case OpCode.OP_JMP: {
+ int target = finalTarget(p.code, i);
+ fixJump(fs, i, target);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ /** {@code luaK_semerror}:语义错误不带 "near
+ * 没有用线程实现协程。Lua 协程是协作式的:同一时刻只有一个在跑,挂起
+ * 点由 {@code coroutine.yield} 显式给出。C 版靠「每个 {@code lua_State} 一条
+ * 独立栈 + {@code longjmp} 跳出」实现,这里同样靠「每个 {@link LuaState} 一条
+ * 独立栈 + 异常跳出」实现,两者结构一一对应。
+ *
+ * 不用虚拟线程还有一层理由:虚拟线程会让 {@code table.sort} 的比较函数里的
+ * yield 变成合法的,而参考实现在那里报
+ * {@code attempt to yield across a C-call boundary}。目标是「一行不改地跑现有
+ * Lua 项目」,所以宁可和 C 一样保守。
+ */
+public final class CoroutineLib {
+
+ /* lcorolib.c 里的 auxstatus 返回值 */
+ private static final int COS_RUN = 0;
+ private static final int COS_DEAD = 1;
+ private static final int COS_YIELD = 2;
+ private static final int COS_NORM = 3;
+
+ private static final String[] STAT_NAME = {"running", "dead", "suspended", "normal"};
+
+ private CoroutineLib() {
+ }
+
+ public static void open(LuaState L) {
+ LuaTable t = new LuaTable();
+ setFn(t, "create", CoroutineLib::cocreate);
+ setFn(t, "resume", CoroutineLib::coresume);
+ setFn(t, "running", CoroutineLib::corunning);
+ setFn(t, "status", CoroutineLib::costatus);
+ setFn(t, "wrap", CoroutineLib::cowrap);
+ setFn(t, "yield", CoroutineLib::coyield);
+ setFn(t, "isyieldable", CoroutineLib::yieldable);
+ setFn(t, "close", CoroutineLib::coclose);
+ /*
+ * 进 _LOADED 而不只是 _G:参考实现里 require("coroutine") 拿到的
+ * 就是这张表。少登记这一条,脚本里写 require("coroutine") 会直接
+ * 报 module not found。
+ */
+ LuaAux.registerLib(L, "coroutine", t);
+ }
+
+ private static void setFn(LuaTable t, String name, LuaFunction f) {
+ t.set(LuaString.latin1(name), LuaValue.ofFunction(f));
+ }
+
+ /**
+ * {@code lcorolib.c: getco}。
+ *
+ * C 用的是 {@code luaL_argexpected(L, co, 1, "thread")},类型名取自
+ * {@code luaL_typename(L, 1)} —— 缺参是 {@code no value},不是
+ * {@code nil}。曾经直接拿 {@code L.arg(0)} 的 {@code typeName()},
+ * 于是 {@code coroutine.status()} 报 {@code got nil},参考实现报
+ * {@code got no value}。
+ *
+ * 函数名不写死。以前这里拼的是 {@code 'coroutine.X'},与参考实现
+ * 差一格:{@code luaL_argexpected} 最终落到 {@code luaL_argerror},名字由
+ * 调用者那一帧反推({@code ldebug.c: funcnamefromcall}),
+ * 所以 {@code coroutine.close(1)} 报 {@code 'close'},
+ * 取了别名 {@code local f = coroutine.create} 再调报 {@code 'f'}。
+ * 只在「调不回来名字」时才退到 {@code pushglobalfuncname} 的
+ * {@code coroutine.X}。{@link LuaState#argError} 就是这条路,
+ * 其余库都走它,这里没理由单开一份。
+ */
+ private static LuaState getco(LuaState L) {
+ LuaValue v = L.arg(0);
+ if (L.isNoneArg(1) || v.tt != Lua.VTHREAD) {
+ throw L.argError(1, "thread expected, got " + L.argTypeName(1));
+ }
+ return v.asThread();
+ }
+
+ /**
+ * {@code lcorolib.c: auxresume}。
+ *
+ * @return 结果个数;{@code -1} 表示出错(错误对象已压在 {@code L} 上)
+ */
+ private static int auxresume(LuaState L, LuaState co, int narg) {
+ /*
+ * 两处 {@code lua_checkstack} 都不许抛:失败要报的是
+ * {@code too many arguments to resume} / {@code too many results to
+ * resume},不是 {@code stack overflow}。差在这两句上,上游
+ * {@code coroutine.lua} 末尾 {@code table.unpack({}, 1, lim)} 那一组
+ * 用例就过不去——参考实现给的是「结果太多搬不回来」,
+ * 而不是「协程自己栈溢出了」。
+ */
+ if (!co.checkStackOk(narg)) {
+ L.push(LuaValue.ofString(LuaString.latin1("too many arguments to resume")));
+ return -1;
+ }
+ LuaState.xmove(L, co, narg);
+ int[] nres = new int[1];
+ int status = co.resume(L, narg, nres);
+ if (status == LuaState.LUA_OK || status == LuaState.LUA_YIELD) {
+ if (!L.checkStackOk(nres[0] + 1)) {
+ co.pop(nres[0]); // lua_pop(co, nres):结果照样从协程上摘掉
+ L.push(LuaValue.ofString(LuaString.latin1("too many results to resume")));
+ return -1;
+ }
+ LuaState.xmove(co, L, nres[0]);
+ return nres[0];
+ }
+ LuaState.xmove(co, L, 1); // 搬错误对象
+ return -1;
+ }
+
+ /** {@code luaB_coresume} */
+ private static int coresume(LuaState L) {
+ LuaState co = getco(L);
+ int r = auxresume(L, co, L.argCount() - 1);
+ if (r < 0) {
+ L.push(LuaValue.ofBoolean(false));
+ insert(L, -2);
+ return 2;
+ }
+ L.push(LuaValue.ofBoolean(true));
+ insert(L, -(r + 1));
+ return r + 1;
+ }
+
+ /**
+ * 把栈顶元素搬到 {@code idx} 处,上面的元素整体上移一格。
+ *
+ * 对应 {@code lua_insert}。{@code resume} 要把 {@code true/false} 插到
+ * 所有结果前面,就是这个操作。
+ */
+ private static void insert(LuaState L, int idx) {
+ int p = (idx < 0) ? L.top + idx : idx;
+ LuaValue v = new LuaValue(L.stack[L.top - 1]);
+ for (int i = L.top - 1; i > p; i--) {
+ L.stack[i].set(L.stack[i - 1]);
+ }
+ L.stack[p].set(v);
+ }
+
+ /** {@code luaB_auxwrap} */
+ private static int auxwrap(LuaState L) {
+ LuaState co = L.upvalue(0).asThread();
+ int r = auxresume(L, co, L.argCount());
+ if (r < 0) {
+ int stat = co.getStatus();
+ if (stat != LuaState.LUA_OK && stat != LuaState.LUA_YIELD) {
+ /*
+ * {@code lua_closethread(co, L)} + {@code lua_xmove(co, L, 1)}。
+ *
+ * 注意 {@code xmove} 是往 {@code L} 压一个值,不是覆盖:
+ * {@code auxresume} 搬过来的那个还在下面,真正被当作错误对象的
+ * 是这次新压上来的栈顶。
+ *
+ * 而 {@code luaE_resetthread} 要交出的是
+ * {@code luaD_seterrorobj(L, status, L->stack + 1)} 的结果,
+ * 源头是「搬走一格之后的栈顶」——所以 {@code luaB_error} 必须照
+ * C 版写(先 {@code settop(1)} 再 {@code where}+{@code concat}),
+ * 那一格才正好是带位置的错误对象。
+ */
+ LuaValue prev = new LuaValue(L.stack[L.top - 1]); // auxresume 刚搬上来的错误对象
+ if (co.closeThreadValue(L, prev) != null) {
+ LuaState.xmove(co, L, 1);
+ }
+ /*
+ * C 版这里把 stat 重新赋值:{@code stat = lua_closethread(co, L)}。
+ * 也就是说判「是不是内存错误」用的不是协程原来的状态,而是
+ * 关完 {@code
+ * 名字同样交给 {@link LuaState#argError}:实测
+ * {@code coroutine.wrap(1)} 与 {@code coroutine.create(1)} 参考实现报的
+ * 都是短名({@code 'wrap'} / {@code 'create'}),哪怕是
+ * {@code wrap} 内部再去调 {@code create} 那一次也不是全名。以前这里把
+ * 全名写死,两种写法都差一格。
+ */
+ private static void createCo(LuaState L) {
+ LuaValue f = L.arg(0);
+ if (L.isNoneArg(1) || !f.isFunction()) {
+ /*
+ * 同样是 {@code luaL_argexpected}:缺参报 {@code got no value}。
+ */
+ throw L.argError(1, "function expected, got " + L.argTypeName(1));
+ }
+ LuaState nl = L.newThread();
+ L.push(LuaValue.ofThread(nl)); // 这个才是 create 的返回值
+ /*
+ * xmove 搬的是栈顶,所以必须先把函数复制一份压上来,否则搬走的
+ * 是刚压的协程对象——那样 create 会返回上一个栈槽里的东西(多半就是
+ * 那个函数),表现是 type(co) 变成 "function"。
+ */
+ L.push(new LuaValue(L.arg(0)));
+ LuaState.xmove(L, nl, 1); // 把函数搬到新线程的栈上
+ }
+
+ /** {@code luaB_cowrap} */
+ private static int cowrap(LuaState L) {
+ createCo(L); // 栈顶现在是新协程
+ LuaValue co = new LuaValue(L.stack[L.top - 1]);
+ CClosure cl = new CClosure(CoroutineLib::auxwrap, 1);
+ cl.upvalue[0] = co;
+ L.stack[L.top - 1].set(LuaValue.ofCClosure(cl));
+ return 1;
+ }
+
+ /** {@code luaB_yield} */
+ private static int coyield(LuaState L) {
+ L.yieldk(L.argCount());
+ return 0; // 不可达:yieldk 一定抛
+ }
+
+ /** {@code lcorolib.c: auxstatus} */
+ private static int auxStatus(LuaState L, LuaState co) {
+ if (L == co) {
+ return COS_RUN;
+ }
+ switch (co.getStatus()) {
+ case LuaState.LUA_YIELD:
+ return COS_YIELD;
+ case LuaState.LUA_OK:
+ if (co.hasFrames()) {
+ return COS_NORM; // 正在跑(被别人 resume 起来的)
+ }
+ return (co.getTop() == 0) ? COS_DEAD : COS_YIELD; // 初始状态
+ default:
+ return COS_DEAD;
+ }
+ }
+
+ /** {@code luaB_costatus} */
+ private static int costatus(LuaState L) {
+ LuaState co = getco(L);
+ L.push(LuaValue.ofString(LuaString.latin1(STAT_NAME[auxStatus(L, co)])));
+ return 1;
+ }
+
+ /** {@code luaB_yieldable} */
+ private static int yieldable(LuaState L) {
+ LuaState co = (L.argCount() == 0 || L.arg(0).isNone()) ? L : getco(L);
+ L.push(LuaValue.ofBoolean(co.isYieldable()));
+ return 1;
+ }
+
+ /** {@code luaB_corunning} */
+ private static int corunning(LuaState L) {
+ int ismain = L.pushThread();
+ L.push(LuaValue.ofBoolean(ismain != 0));
+ return 2;
+ }
+
+ /** {@code luaB_close} */
+ private static int coclose(LuaState L) {
+ LuaState co = getco(L);
+ int status = auxStatus(L, co);
+ switch (status) {
+ case COS_DEAD:
+ case COS_YIELD: {
+ LuaValue err = co.closeThreadValue(L);
+ if (err == null) {
+ L.push(LuaValue.ofBoolean(true));
+ return 1;
+ }
+ L.push(LuaValue.ofBoolean(false));
+ LuaState.xmove(co, L, 1);
+ return 2;
+ }
+ default:
+ throw L.libError("cannot close a " + STAT_NAME[status] + " coroutine");
+ }
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/DebugLib.java b/src/main/java/com/github/esrrhs/javalua/DebugLib.java
new file mode 100644
index 0000000..9017024
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/DebugLib.java
@@ -0,0 +1,1443 @@
+package com.github.esrrhs.javalua;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * {@code debug} 库 —— 对应 {@code ldblib.c},外加 {@code ldebug.c} 里那部分
+ * 只在调试接口才用到的东西({@code lua_getstack} / {@code lua_getinfo} /
+ * {@code luaG_findlocal})与 {@code lauxlib.c} 的 {@code luaL_traceback}。
+ *
+ * 分成两块搬:
+ *
+ * C 版不需要这个字段——{@code CallInfo} 里存的是栈指针,
+ * {@code luaL_traceback(co, ...)} 拿着协程的 {@code CallInfo} 也能直接
+ * 解引用到协程的栈槽。这里 {@link CallInfo} 存的是下标,必须再记住是
+ * 谁的下标,否则 {@code debug.traceback(co)} 会用主线程的栈去解释
+ * 协程的帧号。
+ */
+ LuaState state;
+ LuaString source;
+ /**
+ * {@code luaO_chunkid} 的结果。
+ *
+ * 这里存的是字节串而不是 Java String:chunk 名可以是任意字节
+ * ({@code load(f, "\xff")} 合法),走 Java String 会被 UTF-8 解码
+ * 替换成 {@code U+FFFD},{@code short_src} 与
+ * {@code debug.traceback} 就都跟着错。
+ */
+ LuaString shortSrc;
+ int linedefined;
+ int lastlinedefined;
+ String what;
+ int currentline = -1;
+ int nups;
+ int nparams;
+ boolean isvararg;
+ boolean istailcall;
+ String name;
+ String namewhat = "";
+ int ftransfer;
+ int ntransfer;
+
+ /**
+ * 给 {@code funcnamefromcall} 传出名字用的「出参」。
+ *
+ * C 版是 {@code const char **name} 直接指向 {@code ar->name};Java 没有
+ * 指向字段的指针,用一个 {@code String[]} 当出参。这是 C 里
+ * 「名字与名字种类分开返回」的惯例——{@code funcnamefromcode} 返回种类
+ * 字符串({@code NULL} 表示没找到),名字本身另走一条通道。
+ */
+ final String[] nameHolder = new String[1];
+ }
+
+ /* ==================== 开库 ==================== */
+
+ public static void open(LuaState L) {
+ LuaTable d = new LuaTable();
+ LuaAux.setFn(d, "getregistry", DebugLib::getRegistry);
+ LuaAux.setFn(d, "getmetatable", DebugLib::getMetatable);
+ LuaAux.setFn(d, "setmetatable", DebugLib::setMetatable);
+ LuaAux.setFn(d, "getuservalue", DebugLib::getUserValue);
+ LuaAux.setFn(d, "setuservalue", DebugLib::setUserValue);
+ LuaAux.setFn(d, "getinfo", DebugLib::getInfoFn);
+ LuaAux.setFn(d, "getlocal", DebugLib::getLocalFn);
+ LuaAux.setFn(d, "setlocal", DebugLib::setLocalFn);
+ LuaAux.setFn(d, "getupvalue", DebugLib::getUpValue);
+ LuaAux.setFn(d, "setupvalue", DebugLib::setUpValue);
+ LuaAux.setFn(d, "upvalueid", DebugLib::upValueId);
+ LuaAux.setFn(d, "upvaluejoin", DebugLib::upValueJoin);
+ LuaAux.setFn(d, "traceback", DebugLib::traceback);
+ LuaAux.setFn(d, "setcstacklimit", DebugLib::setCStackLimit);
+ LuaAux.setFn(d, "debug", DebugLib::debugFn);
+ LuaAux.setFn(d, "sethook", DebugLib::setHook);
+ LuaAux.setFn(d, "gethook", DebugLib::getHook);
+ LuaAux.registerLib(L, "debug", d);
+ }
+
+ /* ==================== ldebug.c:栈帧与信息 ==================== */
+
+ /**
+ * {@code lua_getstack}:回跳 {@code level} 层。
+ *
+ * {@code level < 0} 直接失败;{@code level == 0} 是「主 chunk 外的那一层」,
+ * 也就是 C 的 {@code base_ci},同样失败——所以
+ * {@code debug.getinfo(2)} 在只有一层时给 {@code nil}。
+ */
+ static boolean getStack(LuaState L, int level, LuaDebug ar) {
+ if (level < 0) {
+ return false;
+ }
+ CallInfo ci = L.ci;
+ while (level > 0 && ci != null && ci.previous != null) {
+ ci = ci.previous;
+ level--;
+ }
+ if (level == 0 && ci != null && ci.previous != null) {
+ ar.iCi = ci;
+ ar.state = L;
+ return true;
+ }
+ return false;
+ }
+
+ /** {@code currentpc}:{@code savedpc} 是「下一条」,当前这条要减一 */
+ private static int currentPc(CallInfo ci) {
+ return (ci.savedpc > 0) ? ci.savedpc - 1 : 0;
+ }
+
+ /**
+ * {@code getcurrentline}:{@code ldebug.c} 的 {@code currentline},
+ * 但按 {@link #auxGetInfo} 的要求先判 {@code isLua}——C 版把
+ * {@code lua_assert(isLua(ci))} 放在 {@code currentpc} 里,调用点在外层判。
+ */
+ private static int currentLine(LuaState L, CallInfo ci) {
+ if (!ci.isLua) {
+ return -1;
+ }
+ LuaValue f = L.stack[ci.func];
+ if (f.tt != Lua.VLIF) {
+ return -1;
+ }
+ return ((LClosure) f.o).p.getFuncline(currentPc(ci));
+ }
+
+ /* ==================== ldebug.c:名字 ==================== */
+
+ /** {@code funcinfo}:源名、行号范围、what */
+ private static void funcInfo(LuaDebug ar, Object cl) {
+ if (!(cl instanceof LClosure)) {
+ ar.source = LuaString.latin1("=[C]");
+ ar.linedefined = -1;
+ ar.lastlinedefined = -1;
+ ar.what = "C";
+ } else {
+ Proto p = ((LClosure) cl).p;
+ ar.source = (p.source == null) ? LuaString.latin1("=?") : p.source;
+ ar.linedefined = p.linedefined;
+ ar.lastlinedefined = p.lastlinedefined;
+ ar.what = (p.linedefined == 0) ? "main" : "Lua";
+ }
+ ar.shortSrc = LuaString.of(LuaState.chunkId(ar.source.bytes));
+ }
+
+ /** {@code getfuncname}:被调函数是怎么被上一帧调的 */
+ private static String getFuncName(LuaState L, CallInfo ci, String[] name) {
+ if (ci != null && !ci.tail) {
+ return funcNameFromCall(L, ci.previous, name);
+ }
+ return null;
+ }
+
+ /**
+ * {@code funcnamefromcall}。
+ */
+ private static String funcNameFromCall(LuaState L, CallInfo ci, String[] name) {
+ /*
+ * 顺序照抄上游:{@code CIST_HOOKED} 在 {@code isLua} 之前判。
+ * 这不是风格问题——被钩子跟踪的那一帧常常是 C 帧(比如
+ * {@code pcall} 的调用钩子是在 pcall 自己的 C 帧上打的标记),
+ * 挪到 {@code isLua} 之后就永远走不到,回溯里那一层会错印成
+ * {@code in function <文件:行>} 而不是 {@code in hook '?'}。
+ */
+ if (ci == null) {
+ return null;
+ }
+ if (ci.hooked) {
+ name[0] = "?";
+ return "hook";
+ }
+ if (ci.fin) {
+ name[0] = "__gc";
+ return "metamethod";
+ }
+ // base_ci 就是这条分支的常客:它的 isLua 为 false,等价于 C 的
+ // "ci->callstatus & CIST_C",于是「主 chunk 是被谁调的」永远没有名字
+ if (!ci.isLua) {
+ return null;
+ }
+ LuaValue f = L.stack[ci.func];
+ if (f.tt != Lua.VLIF) {
+ return null;
+ }
+ return LuaState.funcNameFromCode(((LClosure) f.o).p, currentPc(ci), name);
+ }
+
+ /* ==================== ldebug.c:getinfo ==================== */
+
+ /** {@code auxgetinfo} */
+ private static boolean auxGetInfo(LuaState L, String what, LuaDebug ar, Object cl, CallInfo ci) {
+ boolean status = true;
+ for (int i = 0; i < what.length(); i++) {
+ switch (what.charAt(i)) {
+ case 'S':
+ funcInfo(ar, cl);
+ break;
+ case 'l':
+ ar.currentline = (ci != null && ci.isLua) ? currentLine(L, ci) : -1;
+ break;
+ case 'u':
+ ar.nups = nupvaluesOf(cl);
+ if (!(cl instanceof LClosure)) {
+ ar.isvararg = true;
+ ar.nparams = 0;
+ } else {
+ Proto p = ((LClosure) cl).p;
+ ar.isvararg = p.isVararg;
+ ar.nparams = p.numparams;
+ }
+ break;
+ case 't':
+ ar.istailcall = (ci != null) && ci.tail;
+ break;
+ case 'n': {
+ String kind = getFuncName(L, ci, ar.nameHolder);
+ if (kind == null) {
+ ar.namewhat = "";
+ ar.name = null;
+ } else {
+ ar.namewhat = kind;
+ ar.name = ar.nameHolder[0];
+ }
+ break;
+ }
+ case 'r':
+ /*
+ * 传递信息只在返回钩子期间有效:{@code luaD_hook} 进钩子前
+ * 把 {@code CIST_TRAN} 打在这一帧上、出来就清掉。所以
+ * {@code debug.getinfo(2, 'r')} 在钩子里能读到返回值个数,
+ * 在普通代码里一律是 0/0。
+ */
+ if (ci == null || !ci.tran) {
+ ar.ftransfer = 0;
+ ar.ntransfer = 0;
+ } else {
+ ar.ftransfer = ci.ftransfer;
+ ar.ntransfer = ci.ntransfer;
+ }
+ break;
+ case 'L':
+ case 'f':
+ break; // 由 getInfo 处理(它们要往栈上推东西)
+ default:
+ status = false;
+ break;
+ }
+ }
+ return status;
+ }
+
+ /** 取闭包的 upvalue 个数:轻量 C 函数没有闭包,给 0 */
+ private static int nupvaluesOf(Object cl) {
+ if (cl instanceof LClosure) {
+ return ((LClosure) cl).p.sizeupvalues;
+ }
+ if (cl instanceof CClosure) {
+ return ((CClosure) cl).upvalue.length;
+ }
+ return 0;
+ }
+
+ /**
+ * {@code lua_getinfo}。
+ *
+ * {@code what} 以 {@code >} 开头表示「信息取自栈顶那个函数」而不是某个栈层。
+ */
+ static boolean getInfo(LuaState L, String what, LuaDebug ar) {
+ CallInfo ci;
+ LuaValue func;
+ if (!what.isEmpty() && what.charAt(0) == '>') {
+ ci = null;
+ func = new LuaValue(L.stack[L.top - 1]);
+ L.top--; // 弹出函数('f' 分支会把它推回来)
+ what = what.substring(1);
+ } else {
+ ci = ar.iCi;
+ /*
+ * 必须拷出来:这里拿到的是栈上那个活的槽对象,而下面
+ * L.push(func) 若推的是同一个引用,表里就存了一个会随栈变化的别名。
+ * C 版是 setobj2s 的按值复制,等价的是 new LuaValue(...)。
+ */
+ func = new LuaValue(L.stack[ci.func]);
+ }
+ Object cl = null;
+ if (func.tt == Lua.VLIF) {
+ cl = func.o;
+ } else if (func.tt == Lua.VCCL) {
+ cl = func.o;
+ }
+ // VLCF(轻量 C 函数)没有闭包对象,cl 保持 null —— 与 C 的
+ // "ttisclosure(func) ? clvalue(func) : NULL" 一致
+ boolean status = auxGetInfo(L, what, ar, cl, ci);
+ if (what.indexOf('f') >= 0) {
+ L.push(func);
+ }
+ if (what.indexOf('L') >= 0) {
+ L.push(collectValidLines(cl));
+ }
+ return status;
+ }
+
+ /** {@code collectvalidlines}:所有「有指令落在上面」的行 */
+ private static LuaValue collectValidLines(Object cl) {
+ if (!(cl instanceof LClosure)) {
+ return LuaValue.ofNil();
+ }
+ Proto p = ((LClosure) cl).p;
+ LuaTable t = new LuaTable();
+ if (p.sizelineinfo > 0) {
+ int currentline = p.linedefined;
+ int i;
+ if (!p.isVararg) {
+ i = 0;
+ } else {
+ // 变参函数的第一条是 OP_VARARGPREP,不算
+ currentline = nextLine(p, currentline, 0);
+ i = 1;
+ }
+ for (; i < p.sizelineinfo; i++) {
+ currentline = nextLine(p, currentline, i);
+ t.set(currentline, LuaValue.ofBoolean(true));
+ }
+ }
+ return LuaValue.ofTable(t);
+ }
+
+ /** {@code nextline}:{@code ABSLINEINFO} 那一条的行号存在 abslineinfo 里 */
+ private static int nextLine(Proto p, int currentline, int pc) {
+ if (pc < p.lineinfo.length && p.lineinfo[pc] != Proto.ABSLINEINFO) {
+ return currentline + p.lineinfo[pc];
+ }
+ return p.getFuncline(pc);
+ }
+
+ /* ==================== ldebug.c:局部变量 ==================== */
+
+ /** {@code findvararg}:负下标访问变长参数 */
+ private static String findVarArg(LuaState L, CallInfo ci, int n, int[] posOut) {
+ LuaValue f = L.stack[ci.func];
+ if (f.tt == Lua.VLIF && ((LClosure) f.o).p.isVararg) {
+ int nextra = ci.nextraargs;
+ if (n >= -nextra) {
+ posOut[0] = ci.func - nextra - (n + 1);
+ return "(vararg)";
+ }
+ }
+ return null;
+ }
+
+ /** {@code luaG_findlocal} */
+ private static String findLocal(LuaState L, CallInfo ci, int n, int[] posOut) {
+ int base = ci.func + 1;
+ String name = null;
+ if (ci.isLua) {
+ if (n < 0) {
+ return findVarArg(L, ci, n, posOut);
+ }
+ LuaValue f = L.stack[ci.func];
+ if (f.tt == Lua.VLIF) {
+ LuaString s = ((LClosure) f.o).p.getLocalName(n, currentPc(ci));
+ if (s != null) {
+ name = s.toLatin1String();
+ }
+ }
+ }
+ if (name == null) {
+ int limit = (ci == L.ci) ? L.top : nextFrameFunc(L, ci);
+ if (limit - base >= n && n > 0) {
+ name = ci.isLua ? "(temporary)" : "(C temporary)";
+ } else {
+ return null;
+ }
+ }
+ posOut[0] = base + (n - 1);
+ return name;
+ }
+
+ /**
+ * C 的 {@code ci->next->func.p}:本帧里面那一层(被调用者)的起点。
+ *
+ * 本实现只存了 {@code previous} 链,{@code next} 要反着找回来。
+ */
+ private static int nextFrameFunc(LuaState L, CallInfo ci) {
+ for (CallInfo c = L.ci; c != null; c = c.previous) {
+ if (c.previous == ci) {
+ return c.func;
+ }
+ }
+ return L.top;
+ }
+
+ /** {@code lua_getlocal} */
+ static String getLocal(LuaState L, LuaDebug ar, int n) {
+ if (ar == null) {
+ // 查非活动函数:只看入口处还活着的参数
+ LuaValue f = L.stack[L.top - 1];
+ if (f.tt != Lua.VLIF) {
+ return null;
+ }
+ LuaString s = ((LClosure) f.o).p.getLocalName(n, 0);
+ return (s == null) ? null : s.toLatin1String();
+ }
+ int[] pos = new int[1];
+ String name = findLocal(L, ar.iCi, n, pos);
+ if (name != null) {
+ L.push(new LuaValue(L.stack[pos[0]]));
+ }
+ return name;
+ }
+
+ /** {@code lua_setlocal} */
+ static String setLocal(LuaState L, LuaDebug ar, int n) {
+ int[] pos = new int[1];
+ String name = findLocal(L, ar.iCi, n, pos);
+ if (name != null) {
+ L.stack[pos[0]].set(L.stack[L.top - 1]);
+ L.top--;
+ }
+ return name;
+ }
+
+ /* ==================== ldebug.c:upvalue ==================== */
+
+ /** {@code auxupvalue}:返回名字,{@code valOut[0]} 是那个值的槽 */
+ static String auxUpValue(LuaValue f, int n, LuaValue[] valOut, Object[] ownerOut) {
+ if (f.tt == Lua.VCCL) {
+ CClosure c = (CClosure) f.o;
+ if (n <= 0 || n > c.upvalue.length) {
+ return null;
+ }
+ valOut[0] = c.upvalue[n - 1];
+ if (ownerOut != null) {
+ /*
+ * C 的 {@code lua_upvalueid} 给 C 闭包返回的是
+ * {@code &f->upvalue[n-1]}——槽的地址,不是闭包本身。
+ * 这里槽对象是 {@link LuaValue} 且永不被替换(只 {@code set}),
+ * 身份稳定,与 C 的取址等价。注意与 {@code aux_upvalue} 的
+ * {@code *owner = obj2gco(f)} 不同:那是给写屏障用的,我们不做。
+ */
+ ownerOut[0] = c.upvalue[n - 1];
+ }
+ return "";
+ }
+ if (f.tt == Lua.VLIF) {
+ LClosure cl = (LClosure) f.o;
+ Proto p = cl.p;
+ if (n <= 0 || n > p.sizeupvalues) {
+ return null;
+ }
+ valOut[0] = cl.upvals[n - 1].value();
+ if (ownerOut != null) {
+ ownerOut[0] = cl.upvals[n - 1];
+ }
+ /*
+ * 这里不能用 {@link LuaState#upvalName}。C 版有两个不同的函数:
+ * {@code lapi.c: aux_upvalue} 在名字为空时给 {@code "(no name)"},
+ * 而 {@code ldebug.c: upvalname} 给 {@code "?"}。差别只在调试信息被
+ * {@code string.dump(f, true)} 剥掉时才看得见——上游
+ * {@code db.lua} 断言 {@code debug.getupvalue(f, 1)} 拿到
+ * {@code "(no name)"},给 {@code "?"} 就过不去。
+ */
+ LuaString nm = (p.upvalues[n - 1] != null) ? p.upvalues[n - 1].name : null;
+ return (nm == null) ? "(no name)" : nm.toLatin1String();
+ }
+ return null;
+ }
+
+ /* ==================== ldblib.c:各函数 ==================== */
+
+ /** {@code getthread}:第一个参数可能是协程 */
+ private static LuaState getThread(LuaState L, int[] arg) {
+ if (!L.isNoneArg(1) && L.arg1(1).tt == Lua.VTHREAD) {
+ arg[0] = 1;
+ return (LuaState) L.arg1(1).o;
+ }
+ arg[0] = 0;
+ return L;
+ }
+
+ private static int getRegistry(LuaState L) {
+ L.push(LuaValue.ofTable(L.registry));
+ return 1;
+ }
+
+ private static int getMetatable(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ LuaTable mt = LuaTm.metatableOf(L, L.arg1(1));
+ if (mt == null) {
+ L.push(LuaValue.ofNil());
+ } else {
+ L.push(LuaValue.ofTable(mt));
+ }
+ return 1;
+ }
+
+ private static int setMetatable(LuaState L) {
+ /*
+ * 判的是「第 2 个参数的原始类型」,不能先把缺省折算成 nil:
+ * C 版是 {@code int t = lua_type(L, 2)}——参数不存在时 t 是
+ * {@code LUA_TNONE}(-1),一样报错。先折成 nil 再判会让
+ * {@code debug.setmetatable()} 静默通过,而参考实现报
+ * {@code nil or table expected, got no value}。
+ */
+ int t = L.isNoneArg(2) ? Lua.TNONE : L.arg1(2).baseType();
+ LuaAux.argExpected(L, t == Lua.TNIL || t == Lua.TTABLE, 2, "nil or table");
+ L.setTop(2); // 第二个参数不存在时补出一个 nil
+ LuaValue v = L.arg1(2);
+ LuaValue obj = L.arg1(1);
+ if (v.isNil()) {
+ LuaTm.setMetatable(L, obj, null);
+ } else {
+ LuaTm.setMetatable(L, obj, (LuaTable) v.o);
+ }
+ L.setTop(1);
+ return 1;
+ }
+
+ private static int getUserValue(LuaState L) {
+ int n = (int) LuaAux.optInteger(L, 2, 1);
+ if (L.isNoneArg(1) || L.arg1(1).tt != Lua.VUSERDATA) {
+ L.push(LuaValue.ofNil());
+ return 1;
+ }
+ LuaUserdata ud = (LuaUserdata) L.arg1(1).o;
+ if (ud.userValues == null || n <= 0 || n > ud.userValues.length) {
+ L.push(LuaValue.ofNil());
+ return 1;
+ }
+ L.push(ud.userValues[n - 1]);
+ return 1;
+ }
+
+ private static int setUserValue(LuaState L) {
+ /*
+ * 序号取在第 3 个参数上({@code luaL_optinteger(L, 3, 1)}),而且要在
+ * 类型检查之前——所以 {@code debug.setuservalue(1, 2, "x")} 报的是
+ * 「#3 不是数」,不是「#1 不是 userdata」。
+ */
+ int n = (int) LuaAux.optInteger(L, 3, 1);
+ LuaAux.checkType(L, 1, Lua.TUSERDATA);
+ LuaAux.checkAny(L, 2);
+ LuaUserdata ud = (LuaUserdata) L.arg1(1).o;
+ LuaValue val = L.arg1(2);
+ if (ud.userValues != null && n >= 1 && n <= ud.userValues.length) {
+ ud.userValues[n - 1].set(val);
+ L.setTop(1);
+ return 1;
+ }
+ L.push(LuaValue.ofNil());
+ return 1;
+ }
+
+ private static int getInfoFn(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ LuaString optStr = LuaAux.optString(L, arg + 2, LuaString.latin1("flnSrtu"));
+ /*
+ * what 是 const char*:C 的 lua_getinfo 扫到第一个 \0 就收工,后面那几个
+ * 字符连看都不看。不截断的话 debug.getinfo(f, "S\0ln") 会把 'l' 'n'
+ * 也当成 option 解析 —— 而它们在这里是合法的、在别处却可能不合法,
+ * 于是凭空多出一条 "invalid option"。
+ */
+ String options = optStr.toCString().toJavaString();
+ LuaAux.argCheck(L, options.isEmpty() || options.charAt(0) != '>', arg + 2,
+ "invalid option '>'");
+ LuaDebug ar = new LuaDebug();
+ if (!L.isNoneArg(arg + 1) && L.arg1(arg + 1).baseType() == Lua.TFUNCTION) {
+ options = ">" + options;
+ // 把函数放到 L1 的栈顶
+ LuaValue f = new LuaValue(L.arg1(arg + 1));
+ if (L1 == L) {
+ L.push(f);
+ } else {
+ L1.push(f);
+ }
+ } else {
+ int level = (int) LuaAux.checkInteger(L, arg + 1);
+ if (!getStack(L1, level, ar)) {
+ L.push(LuaValue.ofNil()); // 层号越界
+ return 1;
+ }
+ }
+ if (!getInfo(L1, options, ar)) {
+ throw L.argError(arg + 2, "invalid option");
+ }
+ LuaTable t = new LuaTable();
+ L.push(LuaValue.ofTable(t));
+ if (options.indexOf('S') >= 0) {
+ t.set("source", LuaValue.ofString(ar.source));
+ t.set("short_src", LuaValue.ofString(ar.shortSrc));
+ t.set("linedefined", LuaValue.ofInteger(ar.linedefined));
+ t.set("lastlinedefined", LuaValue.ofInteger(ar.lastlinedefined));
+ t.set("what", LuaValue.ofString(LuaString.utf8(ar.what)));
+ }
+ if (options.indexOf('l') >= 0) {
+ t.set("currentline", LuaValue.ofInteger(ar.currentline));
+ }
+ if (options.indexOf('u') >= 0) {
+ t.set("nups", LuaValue.ofInteger(ar.nups));
+ t.set("nparams", LuaValue.ofInteger(ar.nparams));
+ t.set("isvararg", LuaValue.ofBoolean(ar.isvararg));
+ }
+ if (options.indexOf('n') >= 0) {
+ t.set("name", (ar.name == null) ? LuaValue.ofNil()
+ : LuaValue.ofString(LuaString.latin1(ar.name)));
+ t.set("namewhat", LuaValue.ofString(LuaString.latin1(ar.namewhat)));
+ }
+ if (options.indexOf('r') >= 0) {
+ t.set("ftransfer", LuaValue.ofInteger(ar.ftransfer));
+ t.set("ntransfer", LuaValue.ofInteger(ar.ntransfer));
+ }
+ if (options.indexOf('t') >= 0) {
+ t.set("istailcall", LuaValue.ofBoolean(ar.istailcall));
+ }
+ // 'L' 与 'f' 的结果已经压在 L1 栈顶,取下来装进表
+ if (options.indexOf('L') >= 0) {
+ popIntoField(L, L1, t, "activelines");
+ }
+ if (options.indexOf('f') >= 0) {
+ popIntoField(L, L1, t, "func");
+ }
+ return 1;
+ }
+
+ /**
+ * {@code treatstackoption}。
+ *
+ * C 版是 {@code lua_rotate(L, -2, 1)} 再 {@code lua_setfield(L, -2, fname)}:
+ * 结果表是后压的,{@code lua_getinfo} 的产物在它底下,所以要先
+ * 交换再 {@code setfield}。直接 {@code L.top--} 弹掉的是结果表本人,
+ * 于是 {@code debug.getinfo(1).func} 里存的成了那张表自己,
+ * 而函数返回的是表底下的东西——症状是 {@code debug.getinfo(1)} 返回一个
+ * 奇怪的函数而不是表。
+ */
+ private static void popIntoField(LuaState L, LuaState L1, LuaTable t, String fname) {
+ LuaValue v;
+ if (L == L1) {
+ // 栈形如 [..., obj, table]:把 obj 从 table 底下掏出来,再复位栈顶
+ v = new LuaValue(L.stack[L.top - 2]);
+ L.top -= 2;
+ L.push(LuaValue.ofTable(t));
+ } else {
+ v = new LuaValue(L1.stack[--L1.top]);
+ }
+ t.set(fname, v);
+ }
+
+ private static int getLocalFn(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ int nvar = (int) LuaAux.checkInteger(L, arg + 2);
+ if (!L.isNoneArg(arg + 1) && L.arg1(arg + 1).baseType() == Lua.TFUNCTION) {
+ L.push(new LuaValue(L.arg1(arg + 1)));
+ String name = getLocal(L, null, nvar);
+ L.push((name == null) ? LuaValue.ofNil() : LuaValue.ofString(LuaString.utf8(name)));
+ return 1;
+ }
+ int level = (int) LuaAux.checkInteger(L, arg + 1);
+ LuaDebug ar = new LuaDebug();
+ if (!getStack(L1, level, ar)) {
+ throw L.argError(arg + 1, "level out of range");
+ }
+ String name = getLocal(L1, ar, nvar);
+ if (name != null) {
+ LuaValue v;
+ if (L1 == L) {
+ v = new LuaValue(L.stack[--L.top]);
+ } else {
+ v = new LuaValue(L1.stack[--L1.top]);
+ }
+ L.push((name == null) ? LuaValue.ofNil() : LuaValue.ofString(LuaString.utf8(name)));
+ L.push(v);
+ return 2;
+ }
+ L.push(LuaValue.ofNil());
+ return 1;
+ }
+
+ private static int setLocalFn(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ int level = (int) LuaAux.checkInteger(L, arg + 1);
+ int nvar = (int) LuaAux.checkInteger(L, arg + 2);
+ LuaDebug ar = new LuaDebug();
+ if (!getStack(L1, level, ar)) {
+ throw L.argError(arg + 1, "level out of range");
+ }
+ LuaAux.checkAny(L, arg + 3);
+ L.setTop(arg + 3);
+ LuaValue v = new LuaValue(L.stack[L.top - 1]);
+ if (L1 == L) {
+ L.push(v);
+ } else {
+ L1.push(v);
+ }
+ String name = setLocal(L1, ar, nvar);
+ if (name == null) {
+ L1.top--; // 没写进去,把值弹掉
+ }
+ L.push((name == null) ? LuaValue.ofNil() : LuaValue.ofString(LuaString.utf8(name)));
+ return 1;
+ }
+
+ /** {@code auxupvalue} 的两个入口共用的实现 */
+ private static int auxUpValueFn(LuaState L, boolean get) {
+ int n = (int) LuaAux.checkInteger(L, 2);
+ LuaAux.checkType(L, 1, Lua.TFUNCTION);
+ LuaValue f = L.arg1(1);
+ LuaValue[] val = new LuaValue[1];
+ String name = auxUpValue(f, n, val, null);
+ if (name == null) {
+ return 0;
+ }
+ if (get) {
+ L.push(new LuaValue(val[0]));
+ L.push(LuaValue.ofString(LuaString.latin1(name)));
+ // lua_insert(L, -(get+1)):名字挪到值下面,打印出来才是「名字 值」
+ LuaValue nm = new LuaValue(L.stack[L.top - 1]);
+ L.stack[L.top - 1].set(L.stack[L.top - 2]);
+ L.stack[L.top - 2].set(nm);
+ return 2;
+ }
+ LuaValue v = new LuaValue(L.stack[L.top - 1]);
+ L.top--;
+ val[0].set(v);
+ L.push(LuaValue.ofString(LuaString.latin1(name)));
+ return 1;
+ }
+
+ private static int getUpValue(LuaState L) {
+ return auxUpValueFn(L, true);
+ }
+
+ private static int setUpValue(LuaState L) {
+ LuaAux.checkAny(L, 3);
+ return auxUpValueFn(L, false);
+ }
+
+ private static int upValueId(LuaState L) {
+ int n = (int) LuaAux.checkInteger(L, 2);
+ LuaAux.checkType(L, 1, Lua.TFUNCTION);
+ LuaValue f = L.arg1(1);
+ LuaValue[] val = new LuaValue[1];
+ Object[] owner = new Object[1];
+ String name = auxUpValue(f, n, val, owner);
+ if (name == null || owner[0] == null) {
+ L.push(LuaValue.ofNil());
+ return 1;
+ }
+ L.push(LuaValue.ofLightUserdata(owner[0]));
+ return 1;
+ }
+
+ private static int upValueJoin(LuaState L) {
+ int n1 = (int) LuaAux.checkInteger(L, 2);
+ LuaAux.checkType(L, 1, Lua.TFUNCTION);
+ LuaValue f1 = L.arg1(1);
+ Object[] owner1 = new Object[1];
+ if (auxUpValue(f1, n1, new LuaValue[1], owner1) == null) {
+ throw L.argError(2, "invalid upvalue index");
+ }
+ int n2 = (int) LuaAux.checkInteger(L, 4);
+ LuaAux.checkType(L, 3, Lua.TFUNCTION);
+ LuaValue f2 = L.arg1(3);
+ Object[] owner2 = new Object[1];
+ if (auxUpValue(f2, n2, new LuaValue[1], owner2) == null) {
+ throw L.argError(4, "invalid upvalue index");
+ }
+ LuaAux.argCheck(L, f1.tt == Lua.VLIF, 1, "Lua function expected");
+ LuaAux.argCheck(L, f2.tt == Lua.VLIF, 3, "Lua function expected");
+ ((LClosure) f1.o).setUpVal(n1 - 1, ((LClosure) f2.o).upvals[n2 - 1]);
+ return 0;
+ }
+
+ /* ==================== 钩子 ==================== */
+
+ /**
+ * 注册表里那张「线程 → 钩子函数」的表。
+ *
+ * 为什么不直接把函数存在 {@code lua_State} 上:{@code debug.sethook} 允许给
+ * 别的线程设钩子({@code debug.sethook(co, f, "l")}),而每个线程的
+ * 钩子是各自独立的。用线程做键的表正好表达这一点。
+ */
+ private static final LuaString HOOKKEY = LuaString.latin1("_HOOKKEY");
+
+ /** 事件名的顺序必须与 {@code Lua.LUA_HOOK*} 的取值一一对应 */
+ private static final String[] HOOKNAMES =
+ { "call", "return", "line", "count", "tail call" };
+
+ /**
+ * {@code ldblib.c: hookf} —— {@code debug.sethook} 装的那个钩子。
+ *
+ * 它是在「被跟踪的那一帧」上被调用的,{@code ar.iCi} 就指向那一帧;
+ * 这里真正做的是「查出用户注册的那个 Lua 函数并调它」。
+ *
+ * 用 {@code callNoYield} 而不是 {@code call}:C 版这里是
+ * {@code lua_call},而 {@code lua_call} 走 {@code luaD_callnoyield},
+ * 会把「不可 yield」的层数加一。后果是钩子函数里 yield 一律报错——
+ * 实测参考实现:主线程上是
+ * {@code attempt to yield from outside a coroutine},协程里是
+ * {@code attempt to yield across a C-call boundary},两条都让程序/协程死掉。
+ * 换成 {@code call} 就会「成功 yield」,与参考实现相反。
+ */
+ private static final LuaHook HOOKF = DebugLib::hookf;
+
+ private static void hookf(LuaState L, LuaDebug ar) {
+ LuaValue ht = L.registry.get(HOOKKEY);
+ if (ht == null || ht.tt != Lua.TTABLE) {
+ return;
+ }
+ LuaValue fn = ht.asTable().get(LuaValue.ofThread(L));
+ if (fn == null || fn.baseType() != Lua.TFUNCTION) {
+ return;
+ }
+ int f = L.top;
+ L.checkStack(3);
+ L.stack[L.top++].set(fn);
+ L.stack[L.top++].set(LuaValue.ofString(LuaString.utf8(HOOKNAMES[ar.event])));
+ if (ar.currentline >= 0) {
+ L.stack[L.top++].set(LuaValue.ofInteger(ar.currentline));
+ } else {
+ L.stack[L.top++].setNil();
+ }
+ getInfo(L, "lS", ar); // 补齐 currentline 与 source,钩子常用
+ L.callNoYield(f, 0);
+ }
+
+ /**
+ * {@code makemask}:{@code "crl"} 这样的字符串转成掩码。
+ *
+ * C 用的是 {@code strchr(smask, 'c')} 而 {@code smask} 是 {@code const char*},
+ * 所以第一个 {@code \0} 之后的字符看不见:{@code "c\0l"} 只装调用钩子,
+ * {@code "l\0c"} 只装行钩子。实测两者打出来的计数完全相反。
+ */
+ private static int makeMask(LuaString smask, int count) {
+ String s = smask.toCString().toJavaString();
+ int mask = 0;
+ if (s.indexOf('c') >= 0) {
+ mask |= Lua.LUA_MASKCALL;
+ }
+ if (s.indexOf('r') >= 0) {
+ mask |= Lua.LUA_MASKRET;
+ }
+ if (s.indexOf('l') >= 0) {
+ mask |= Lua.LUA_MASKLINE;
+ }
+ if (count > 0) {
+ mask |= Lua.LUA_MASKCOUNT;
+ }
+ return mask;
+ }
+
+ /**
+ * {@code unmakemask}:掩码转回字符串。
+ *
+ * 只输出 {@code c / r / l}——计数是单独一个返回值,不占字符。
+ */
+ private static String unmakeMask(int mask) {
+ StringBuilder b = new StringBuilder();
+ if ((mask & Lua.LUA_MASKCALL) != 0) {
+ b.append('c');
+ }
+ if ((mask & Lua.LUA_MASKRET) != 0) {
+ b.append('r');
+ }
+ if ((mask & Lua.LUA_MASKLINE) != 0) {
+ b.append('l');
+ }
+ return b.toString();
+ }
+
+ /** {@code luaL_getsubtable} 的等价物,拿不到就建一张弱键表 */
+ private static LuaTable hookTable(LuaState L) {
+ LuaValue v = L.registry.get(HOOKKEY);
+ if (v != null && v.tt == Lua.TTABLE) {
+ return (LuaTable) v.o;
+ }
+ LuaTable t = new LuaTable();
+ // 弱键:协程死后不该被钩子表吊住;以自己为元表是上游的写法
+ t.set("__mode", LuaValue.ofString(LuaString.utf8("k")));
+ t.setMetatable(t);
+ L.registry.set(HOOKKEY, LuaValue.ofTable(t));
+ return t;
+ }
+
+ private static int setHook(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ LuaHook func;
+ int mask;
+ int count;
+ if (L.isNoneArg(arg + 1) || L.arg1(arg + 1).isNil()) {
+ L.setTop(arg + 1);
+ func = null;
+ mask = 0;
+ count = 0;
+ } else {
+ /*
+ * 先取掩码再查第 1 个参数的类型:C 版
+ * {@code luaL_checkstring(L, arg+2)} 在前,所以
+ * {@code debug.sethook(1, 2)} 报的是「#2 不是字符串」,
+ * 而不是「#1 不是函数」。
+ */
+ LuaString smask = LuaAux.checkString(L, arg + 2);
+ LuaAux.checkType(L, arg + 1, Lua.TFUNCTION);
+ count = (int) LuaAux.optInteger(L, arg + 3, 0);
+ func = HOOKF;
+ mask = makeMask(smask, count);
+ }
+ LuaTable ht = hookTable(L);
+ ht.set(LuaValue.ofThread(L1), new LuaValue(L.arg1(arg + 1)));
+ L1.setHook(func, mask, count);
+ return 0;
+ }
+
+ private static int getHook(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ int mask = L1.getHookMask();
+ LuaHook hook = L1.getHook();
+ if (hook == null) {
+ L.push(LuaValue.ofNil()); // luaL_pushfail
+ return 1; // 没有钩子时只有一个返回值
+ } else if (hook != HOOKF) {
+ L.push(LuaValue.ofString(LuaString.utf8("external hook")));
+ } else {
+ LuaTable ht = hookTable(L);
+ LuaValue f = ht.get(LuaValue.ofThread(L1)); // rawget 的等价物
+ L.push((f == null) ? LuaValue.ofNil() : new LuaValue(f));
+ }
+ L.push(LuaValue.ofString(LuaString.utf8(unmakeMask(mask))));
+ L.push(LuaValue.ofInteger(L1.getHookCount()));
+ return 3;
+ }
+
+ /* ---------- ldo.c / ldebug.c:钩子的调用点 ---------- */
+
+ /**
+ * {@code ldo.c: luaD_hook}。
+ *
+ * 两处「先扩大再收窄」是本函数的要害:
+ *
+ * 钩子抛错时不还原。C 版在这里 {@code luaD_throw} 一跳到底,还原动作
+ * 全被跳过;{@code allowhook} 与 {@code CIST_HOOKED} 要到
+ * {@code luaD_pcall} 才恢复。这个差别是能观测到的:钩子函数出错时参考实现的
+ * 回溯里那一层印成 {@code in hook '?'},而「出错立刻清标记」会把它印成
+ * {@code in function <文件:行>}。
+ */
+ static void dHook(LuaState L, int event, int line, int ftransfer, int ntransfer) {
+ LuaHook hook = L.hook;
+ if (hook == null || !L.allowHook) {
+ return;
+ }
+ CallInfo ci = L.ci;
+ int savedTop = L.top;
+ int savedCiTop = ci.top;
+ LuaDebug ar = new LuaDebug();
+ ar.event = event;
+ ar.currentline = line;
+ ar.iCi = ci;
+ ar.state = L;
+ boolean tran = ntransfer != 0;
+ if (tran) {
+ ci.ftransfer = ftransfer;
+ ci.ntransfer = ntransfer;
+ }
+ if (ci.isLua && L.top < ci.top) {
+ L.top = ci.top;
+ }
+ L.checkStack(LuaState.MINSTACK);
+ if (ci.top < L.top + LuaState.MINSTACK) {
+ ci.top = L.top + LuaState.MINSTACK;
+ }
+ L.allowHook = false;
+ ci.hooked = true;
+ ci.tran = tran;
+ boolean ok = false;
+ try {
+ hook.hook(L, ar);
+ ok = true;
+ } finally {
+ if (ok) {
+ L.allowHook = true;
+ ci.top = savedCiTop;
+ L.top = savedTop;
+ ci.hooked = false;
+ ci.tran = false;
+ }
+ }
+ }
+
+ /**
+ * {@code ldo.c: luaD_hookcall}:Lua 函数的调用钩子。
+ *
+ * 那句「{@code savedpc} 先加一再加回来」是因为钩子的约定是
+ * 「{@code savedpc} 已经越过当前这条指令」——{@code debug.getinfo(2, 'l')}
+ * 要报的是调用发生的那一行,而此时 {@code savedpc} 还停在
+ * {@code OP_CALL} 上。
+ */
+ static void dHookCall(LuaState L, CallInfo ci) {
+ L.oldpc = 0;
+ if ((L.hookmask & Lua.LUA_MASKCALL) != 0) {
+ int event = ci.tail ? Lua.LUA_HOOKTAILCALL : Lua.LUA_HOOKCALL;
+ Proto p = ((LClosure) L.stack[ci.func].o).p;
+ ci.savedpc++;
+ dHook(L, event, -1, 1, p.numparams);
+ ci.savedpc--;
+ }
+ }
+
+ /**
+ * {@code ldo.c: rethook}:返回钩子,并顺手更新 {@code oldpc}。
+ *
+ * 变参函数要把 {@code ci.func} 先退回「虚拟 func」再算偏移:真实
+ * {@code func} 与虚拟 {@code func} 之间隔着 {@code nextraargs + numparams + 1}
+ * 格,照真实 {@code func} 算出来的 {@code ftransfer} 会是个负数。
+ */
+ static void rethook(LuaState L, CallInfo ci, int nres) {
+ if ((L.hookmask & Lua.LUA_MASKRET) != 0) {
+ int firstres = L.top - nres;
+ int delta = 0;
+ if (ci.isLua) {
+ Proto p = ((LClosure) L.stack[ci.func].o).p;
+ if (p.isVararg) {
+ delta = ci.nextraargs + p.numparams + 1;
+ }
+ }
+ ci.func += delta;
+ dHook(L, Lua.LUA_HOOKRET, -1, firstres - ci.func, nres);
+ ci.func -= delta;
+ }
+ CallInfo prev = ci.previous;
+ if (prev != null && prev.isLua) {
+ L.oldpc = pcRel(prev.savedpc);
+ }
+ }
+
+ /** {@code pcRel(pc, p)}:{@code p->code} 在本实现里恒为下标 0 */
+ private static int pcRel(int pc) {
+ return pc - 1;
+ }
+
+ /** {@code ldebug.c: changedline}:{@code oldpc} 到 {@code newpc} 之间换过行吗 */
+ private static boolean changedLine(Proto p, int oldpc, int newpc) {
+ if (p.sizelineinfo == 0) {
+ return false; // 没有调试信息
+ }
+ if (newpc - oldpc < Proto.MAXIWTHABS / 2) {
+ int delta = 0;
+ int pc = oldpc;
+ while (true) {
+ if (pc + 1 >= p.sizelineinfo) {
+ break;
+ }
+ int lineinfo = p.lineinfo[++pc];
+ if (lineinfo == Proto.ABSLINEINFO) {
+ break; // 算不出差值,落到下面显式比
+ }
+ delta += lineinfo;
+ if (pc == newpc) {
+ return delta != 0;
+ }
+ }
+ }
+ return p.getFuncline(oldpc) != p.getFuncline(newpc);
+ }
+
+ /**
+ * {@code lopcodes.h: isIT(i)}:这条指令的操作数个数由 {@code L->top} 决定吗。
+ *
+ * 是的话 {@code L->top} 此刻是有意义的,不能拿 {@code ci->top} 盖掉;
+ * 不是的话 {@code L->top} 只是「上次用到哪儿」,必须抬到 {@code ci->top}
+ * 才能给钩子腾出干净的栈。
+ */
+ private static boolean isIT(int instr) {
+ switch (OpCode.getOpCode(instr)) {
+ case OpCode.OP_CALL:
+ case OpCode.OP_TAILCALL:
+ case OpCode.OP_RETURN:
+ case OpCode.OP_SETLIST:
+ case OpCode.OP_VARARGPREP:
+ return OpCode.getArgB(instr) == 0;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * {@code ldebug.c: luaG_traceexec}:每条指令前查一次行/计数钩子。
+ *
+ * @param pc 即将执行的那条指令的下标
+ * @return 新的 {@code trap}
+ */
+ static boolean traceExec(LuaState L, CallInfo ci, int pc) {
+ int mask = L.hookmask;
+ Proto p = ((LClosure) L.stack[ci.func].o).p;
+ if ((mask & (Lua.LUA_MASKLINE | Lua.LUA_MASKCOUNT)) == 0) {
+ ci.trap = false; // 不需要再停
+ return false;
+ }
+ pc++; // 参考点永远是「下一条」
+ ci.savedpc = pc;
+ boolean counthook = (mask & Lua.LUA_MASKCOUNT) != 0 && (--L.hookcount == 0);
+ if (counthook) {
+ L.resetHookCount();
+ } else if ((mask & Lua.LUA_MASKLINE) == 0) {
+ return true; // 没有行钩子、计数也没到点,这次没事
+ }
+ if (!isIT(p.code[ci.savedpc - 1])) {
+ L.top = ci.top;
+ }
+ if (counthook) {
+ dHook(L, Lua.LUA_HOOKCOUNT, -1, 0, 0);
+ }
+ if ((mask & Lua.LUA_MASKLINE) != 0) {
+ int oldpc = (L.oldpc < p.sizecode) ? L.oldpc : 0;
+ int npci = pcRel(pc);
+ if (npci <= oldpc || changedLine(p, oldpc, npci)) {
+ dHook(L, Lua.LUA_HOOKLINE, p.getFuncline(npci), 0, 0);
+ }
+ L.oldpc = npci;
+ }
+ return true;
+ }
+
+ /**
+ * {@code ldebug.c: luaG_tracecall}:进一个 Lua 函数时先报调用钩子。
+ *
+ * 变参函数这里返回 {@code false}(把 {@code trap} 关掉),它的调用钩子推迟到
+ * {@code OP_VARARGPREP} 之后——那时参数才归位,报出来的
+ * {@code numparams} 才对得上。{@link Vm} 里 {@code OP_VARARGPREP} 之后的
+ * {@code updatetrap} 会把 {@code trap} 从帧里读回来重新置 1。
+ */
+ static boolean traceCall(LuaState L, CallInfo ci) {
+ Proto p = ((LClosure) L.stack[ci.func].o).p;
+ ci.trap = true;
+ if (ci.savedpc == 0) { // 第一条指令(不是从 yield 恢复的)
+ if (p.isVararg) {
+ return false;
+ }
+ dHookCall(L, ci);
+ }
+ return true;
+ }
+
+ /* ==================== traceback ==================== */
+
+ /**
+ * {@code LEVELS1 / LEVELS2}:回溯太长时只留头尾。
+ *
+ * 上游是 10 和 11——加起来才是 21,且头尾不等长。凭印象写成 12/10
+ * 会让「跳过多少层」那个数字差 1,而且只在深栈上才暴露。
+ */
+ private static final int LEVELS1 = 10;
+ private static final int LEVELS2 = 11;
+
+ private static int lastLevel(LuaState L) {
+ LuaDebug ar = new LuaDebug();
+ int li = 1;
+ int le = 1;
+ while (getStack(L, le, ar)) {
+ li = le;
+ le *= 2;
+ }
+ while (li < le) {
+ int m = (li + le) / 2;
+ if (getStack(L, m, ar)) {
+ li = m + 1;
+ } else {
+ le = m;
+ }
+ }
+ return le - 1;
+ }
+
+ private static int traceback(LuaState L) {
+ int[] argHolder = new int[1];
+ LuaState L1 = getThread(L, argHolder);
+ int arg = argHolder[0];
+ LuaValue m = L.isNoneArg(arg + 1) ? LuaValue.ofNil() : L.arg1(arg + 1);
+ if (!m.isString() && !m.isNumber() && !m.isNil()) {
+ L.push(new LuaValue(m)); // 非字符串原样返回
+ return 1;
+ }
+ // luaL_traceback 用 strlen(msg) 取长度: 之后的内容不进回溯
+ LuaString msg = m.isNil() ? null : LuaAux.asString(m).toCString();
+ int level = (int) LuaAux.optInteger(L, arg + 2, (L == L1) ? 1 : 0);
+ L.push(LuaValue.ofString(traceback(L, L1, msg, level)));
+ return 1;
+ }
+
+ /**
+ * {@code lauxlib.c: luaL_traceback} 的字符串版本。
+ *
+ * 抽出来是因为除了 {@code debug.traceback} 还有第二个调用方:{@code lua.c} 的
+ * {@code msghandler}(未捕获错误的默认处理函数)也调它。两边共一份,
+ * 免得「{@code debug.traceback} 对的、未捕获错误的回溯少一层」这种事发生。
+ *
+ * 注意 {@code L} 与 {@code L1} 的分工:回溯的文本建在 {@code L} 上,
+ * 但帧取自 {@code L1}——{@code debug.traceback(co)} 就是靠这个区别
+ * 拿到协程自己的栈。
+ */
+ static LuaString traceback(LuaState L, LuaState L1, LuaString msg, int level) {
+ /*
+ * 用字节缓冲而不是 StringBuilder:msg 与 short_src 都是 Lua 字节串,
+ * 可以是任意字节。走 Java String 会先按 UTF-8 解码(非法字节变
+ * U+FFFD)再编码回去,内容就变了 —— 实测
+ * debug.traceback("\xff") 一度返回 "\xef\xbf\xbd" 打头的一串。
+ */
+ LuaAux.Buf b = new LuaAux.Buf();
+ if (msg != null) {
+ b.addString(msg);
+ b.addChar('\n');
+ }
+ b.addAscii("stack traceback:");
+ int last = lastLevel(L1);
+ int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1;
+ LuaDebug ar = new LuaDebug();
+ while (getStack(L1, level++, ar)) {
+ if (limit2show-- == 0) {
+ int n = last - level - LEVELS2 + 1;
+ b.addAscii("\n\t...\t(skipping ");
+ b.addAscii(Integer.toString(n));
+ b.addAscii(" levels)");
+ level += n;
+ } else {
+ getInfo(L1, "Slnt", ar);
+ b.addAscii("\n\t");
+ b.addString(ar.shortSrc);
+ if (ar.currentline <= 0) {
+ b.addAscii(": in ");
+ } else {
+ b.addAscii(":");
+ b.addAscii(Integer.toString(ar.currentline));
+ b.addAscii(": in ");
+ }
+ b.addString(pushFuncName(L, ar));
+ if (ar.istailcall) {
+ b.addAscii("\n\t(...tail calls...)");
+ }
+ }
+ }
+ return b.result();
+ }
+
+ /**
+ * {@code lauxlib.c: pushfuncname} 的字符串版本。
+ *
+ * 全局名优先于代码里的名字:{@code error} 在代码里是
+ * {@code global 'error'}({@code GETTABUP _ENV}),但回溯打印的是
+ * {@code function 'error'}——第一支 {@code pushglobalfuncname} 在
+ * {@code _LOADED._G} 里按值相等反查到了这个名字。所以这里必须先查全局,
+ * 只查 {@code ar.namewhat} 会整体差一层「修饰语」。
+ */
+ private static LuaString pushFuncName(LuaState L, LuaDebug ar) {
+ /*
+ * 同样返回字节串:{@code function
+ * 与 {@code LuaState.globalFuncName} 是同一件事,只是取函数的来源不同——
+ * 那边取「当前 C 帧的函数」,这里取 {@code ar.iCi} 那一帧的,而后者可能属于
+ * 另一个线程({@code debug.traceback(co)})。
+ */
+ private static String globalFuncName(LuaState L, LuaDebug ar) {
+ if (ar.iCi == null || ar.state == null) {
+ return null;
+ }
+ LuaValue f = ar.state.stack[ar.iCi.func];
+ String s = LuaState.findFieldName(L.loadedTable(), f, 2);
+ if (s == null) {
+ return null;
+ }
+ // 上游找到的是 "_G.error",把前缀去掉
+ return s.startsWith("_G.") ? s.substring(3) : s;
+ }
+
+ /* ==================== 杂项 ==================== */
+
+ /** {@code lua_setcstacklimit}:本实现没有 C 栈深度限制,一律给上限 */
+ private static final int MAXCCALLS = 200;
+
+ private static int setCStackLimit(LuaState L) {
+ LuaAux.checkInteger(L, 1);
+ L.push(LuaValue.ofInteger(MAXCCALLS));
+ return 1;
+ }
+
+ /** {@code ldblib.c: db_debug} 里 {@code fgets} 的缓冲区大小 */
+ private static final int DB_BUFSIZ = 250;
+
+ /** {@code "lua_debug> "}:注意末尾那个空格,且没有换行 */
+ private static final byte[] DB_PROMPT = "lua_debug> ".getBytes(StandardCharsets.UTF_8);
+
+ /**
+ * {@code db_debug}:交互调试器。
+ *
+ * 三处必须与 C 一字不差,否则逐字节比对立刻红:
+ *
+ * 命令的 chunk 名是 {@code =(debug command)},且每次执行完
+ * {@code lua_settop(L, 0)}——本实现的 {@link LuaState#setTop} 与 C 一样是
+ * 相对当前帧的,所以直接写 0。
+ *
+ * 差分用例里 stdin 是 {@code /dev/null}(见 {@code ReferenceLua#runBytes}),
+ * 所以第一轮就 EOF:表现为「只打一个提示符然后返回」。
+ */
+ private static int debugFn(LuaState L) {
+ byte[] buf = new byte[DB_BUFSIZ];
+ for (;;) {
+ writeErr(L, DB_PROMPT);
+ int n = fgets(L.in, buf);
+ if (n < 0 || isCont(buf, n)) {
+ return 0;
+ }
+ byte[] src = java.util.Arrays.copyOf(buf, n);
+ LuaValue err;
+ try {
+ err = L.runProtected(src, "=(debug command)");
+ } catch (LuaError e) {
+ // luaL_loadbuffer 那一步就失败:消息同样要打出来
+ err = e.value;
+ }
+ if (err != null) {
+ writeErr(L, LuaAux.toLString(L, err).bytes);
+ writeErr(L, new byte[] { '\n' });
+ }
+ L.setTop(0);
+ }
+ }
+
+ /**
+ * C 的 {@code fgets}:最多读 {@code buf.length - 1} 个字节,遇到 {@code '\n'}
+ * 连同换行一起收进来;EOF 时一个字节都没读到才返回 -1。
+ *
+ * @return 读到的字节数,或 -1 表示 EOF
+ */
+ private static int fgets(java.io.InputStream in, byte[] buf) {
+ int n = 0;
+ try {
+ while (n + 1 < buf.length) {
+ int c = in.read();
+ if (c < 0) {
+ break;
+ }
+ buf[n++] = (byte) c;
+ if (c == '\n') {
+ break;
+ }
+ }
+ } catch (java.io.IOException e) {
+ // C 的 fgets 出错也返回 NULL;n == 0 即等价
+ }
+ return (n == 0) ? -1 : n;
+ }
+
+ /** {@code strcmp(buffer, "cont\n") == 0} */
+ private static boolean isCont(byte[] buf, int n) {
+ return n == 5 && buf[0] == 'c' && buf[1] == 'o' && buf[2] == 'n'
+ && buf[3] == 't' && buf[4] == '\n';
+ }
+
+ /** {@code lua_writestringerror}:写字节 + 立刻 flush(C 的 stderr 无缓冲) */
+ private static void writeErr(LuaState L, byte[] b) {
+ L.err.write(b, 0, b.length);
+ L.err.flush();
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/Dump.java b/src/main/java/com/github/esrrhs/javalua/Dump.java
new file mode 100644
index 0000000..c60d0ee
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/Dump.java
@@ -0,0 +1,235 @@
+package com.github.esrrhs.javalua;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * 写预编译 chunk —— 对应 {@code ldump.c},{@link Undump} 的逆操作。
+ *
+ * 对外只有一处入口:{@code string.dump}。{@code luac} 用的是同一份代码,
+ * 但那是命令行工具,不在运行期里,这里不搬。
+ *
+ *
+ * 三个列表都由最外层的解析状态持有,层层嵌套的函数共享同一份:
+ * 局部变量栈要跨函数边界(内层看到外层的局部变量才能识别出 upvalue),
+ * 而 goto/标签列表本来就是整个 chunk 范围内的。
+ */
+public final class DynData {
+
+ /** 变量种类 */
+ public static final int VDKREG = 0;
+ public static final int RDKCONST = 1;
+ public static final int RDKTOCLOSE = 2;
+ public static final int RDKCTC = 3;
+
+ /** 活跃局部变量描述({@code Vardesc}) */
+ public static final class Vardesc {
+ /** 编译期常量的值(仅 kind == RDKCTC 时有意义) */
+ public LuaValue k;
+ /** {@code kind}:VDKREG / RDKCONST / RDKTOCLOSE / RDKCTC */
+ public int kind = VDKREG;
+ /** 变量所在的寄存器 */
+ public int ridx;
+ /** 在 {@code Proto.locvars} 里的下标 */
+ public int pidx;
+ public LuaString name;
+ }
+
+ /** 标签或待处理的 goto({@code Labeldesc}) */
+ public static final class Labeldesc {
+ public LuaString name;
+ /** 代码位置 */
+ public int pc;
+ /** 出现的行 */
+ public int line;
+ /** 该位置上的活跃变量数 */
+ public int nactvar;
+ /** 这个 goto 是否跨越了 upvalue */
+ public boolean close;
+ }
+
+ /**
+ * 局部变量栈。只增长不收缩:{@link #actvarN} 是逻辑长度。
+ *
+ * 这一点必须照抄上游。C 里 {@code removevars} 先把 {@code actvar.n} 减掉,
+ * 然后还要顺着下标读一遍刚「删掉」的那些槽位去补调试信息的
+ * {@code endpc}——靠的就是数组没被真收缩。用 {@code List.remove} 会在那里
+ * 直接越界,或者更糟:读到的是别的变量。
+ */
+ public final List
+ * 这是整个 codegen 的核心数据结构:解析一个表达式时,不急着生成代码,
+ * 而是先记下「这个表达式的值现在在哪儿」。等到不得不落地时(存进变量、当实参、
+ * 参与运算)再决定最省指令的做法——比如 {@code t.x} 可以一直保持
+ * VINDEXSTR 不落地,直到赋值时才发一条 {@code SETFIELD}。
+ *
+ * C 版用一个 union 存不同 kind 的载荷;Java 没有 union,这里把所有字段摊平。
+ * 唯一要小心的是 C 里那些「overlapping」的写法(比如 VLOCAL 的
+ * {@code var.ridx} 与 VINDEXED 的 {@code ind.t} 共用存储),摊平之后反而
+ * 不会出现「读了上一个 kind 留下的位」这类错误。
+ */
+public final class ExpDesc {
+
+ /* expkind —— ORDER 与上游严格一致 */
+ /** 空表达式(最后一个表达式为空列表) */
+ public static final int VVOID = 0;
+ public static final int VNIL = 1;
+ public static final int VTRUE = 2;
+ public static final int VFALSE = 3;
+ /** 常量在 {@code k} 里;info = 下标 */
+ public static final int VK = 4;
+ /** 浮点常量 */
+ public static final int VKFLT = 5;
+ /** 整数常量 */
+ public static final int VKINT = 6;
+ /** 字符串常量(由词法层驻留) */
+ public static final int VKSTR = 7;
+ /** 值已在某个固定寄存器里;info = 寄存器号 */
+ public static final int VNONRELOC = 8;
+ /** 局部变量;varRidx = 寄存器号,varVidx = actvar 下标 */
+ public static final int VLOCAL = 9;
+ /** upvalue;info = upvalues 下标 */
+ public static final int VUPVAL = 10;
+ /** 编译期 {@code
+ * 每进入一个函数体就压一个新的 FuncState,{@link #prev} 串起外层,upvalue 的
+ * 解析就是顺着这条链往上找局部变量。
+ *
+ * 与上游的一处结构性差异:没有 {@code nk} / {@code np} / {@code nabslineinfo}。
+ * C 里这三个是「已用长度」,与数组的「已分配长度」分开;这里数组由 {@link Proto}
+ * 自己增长,两者恒等——留着两套计数只会给「改了一个忘了另一个」留位置。
+ * 于是 {@code nk} 就是 {@code f.sizek},{@code np} 就是 {@code f.sizep}。
+ *
+ * 常量缓存不在这里:上游的 {@code ls->h} 属于词法状态(整份源码共享,
+ * 跨函数),见 {@link Lexer#lookupConstant}。
+ */
+public final class FuncState {
+
+ /** 当前函数的原型 */
+ public final Proto f;
+ /** 外层函数 */
+ public final FuncState prev;
+ /** 词法状态(报错要用它带上位置信息) */
+ public final Lexer ls;
+ /** 跨函数共享的动态结构 */
+ public final DynData dyd;
+
+ /** 下一条指令的写入位置(等价于 sizecode) */
+ public int pc;
+ /** 最后一个跳转目标的位置(防止跨基本块做优化) */
+ public int lasttarget;
+ /** 上一次存进 lineinfo 的行号 */
+ public int previousline;
+ /** 本函数第一个局部变量在 dyd.actvar 里的下标 */
+ public int firstlocal;
+ /** 本函数第一个标签在 dyd.label 里的下标 */
+ public int firstlabel;
+ /** f.locvars 里已用的条目数 */
+ public int ndebugvars;
+ /** 活跃局部变量数(= 寄存器栈上被变量占住的部分) */
+ public int nactvar;
+ /** upvalue 数 */
+ public int nups;
+ /** 第一个空闲寄存器 */
+ public int freereg;
+ /** 距上次写绝对行号以来发出的指令数 */
+ public int iwthabs;
+ /** 返回时需要关闭 upvalue */
+ public boolean needclose;
+ /**
+ * 当前块({@code fs->bl})。
+ *
+ * C 里 {@code BlockCnt} 是栈上对象,靠这个指针串成链;这里只能堆分配,
+ * 但链的用法(进出块时压弹)完全一样。
+ */
+ public Parser.BlockCnt bl;
+
+ public FuncState(Proto f, FuncState prev, Lexer ls, DynData dyd) {
+ this.f = f;
+ this.prev = prev;
+ this.ls = ls;
+ this.dyd = dyd;
+ }
+
+ /**
+ * {@code luaY_nvarstack}:寄存器栈上被局部变量占住的寄存器数。
+ *
+ * 不能简单地返回 {@link #nactvar}:编译期常量({@code
+ * 从上往下找第一个不在常量区的变量,返回它的寄存器 +1。
+ * 跳过 RDKCTC 是必须的:常量的 {@code ridx} 从来没被赋过值。
+ */
+ public static int reglevel(FuncState fs, int nvar) {
+ while (nvar-- > 0) {
+ DynData.Vardesc vd = fs.dyd.actvar.get(fs.firstlocal + nvar);
+ if (vd.kind != DynData.RDKCTC) {
+ return vd.ridx + 1;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/GcSentinel.java b/src/main/java/com/github/esrrhs/javalua/GcSentinel.java
new file mode 100644
index 0000000..d177da6
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/GcSentinel.java
@@ -0,0 +1,68 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * {@code __gc} 的落地手段 —— 一个只被目标表引用的哨兵。
+ *
+ * 为什么要有这个中间对象。要跑 {@code __gc(t)},就得在跑的那一刻还握着
+ * {@code t}。Java 里所有「对象已死」的通知机制({@code WeakReference} 被清、
+ * {@code Cleaner} / {@code PhantomReference} 入队)都发生在对象已经取不到
+ * 之后——引用一清就再也拿不回来。而 C 版没有这个问题:{@code lgc.c: GCTM} 是在
+ * mark 阶段之后、对象还在内存里时直接拿着指针调元方法。
+ *
+ * 唯一能在对象尚可访问时被调用的是 {@link Object#finalize()}——它被调的那一刻
+ * {@code this} 还能用,而且允许复活(把 {@code this} 重新交给别人保存)。
+ * 于是这里绕一圈:
+ *
+ * 放在哨兵上而不是直接给 {@link LuaTable} 写 {@code finalize},是为了不给所有
+ * 表付这份代价:带终结器的对象在 JVM 里至少要两轮 GC 才收得掉,还要进终结
+ * 队列。只有真挂了 {@code __gc} 的表才需要它。
+ *
+ * {@code finalize} 已被标记为 deprecated,这是唯一的出路,别无选择。若将来
+ * {@code --finalization=disabled} 成为默认,这里会静默失效——那也是它该有的反应:
+ * 那时 {@code __gc} 确实没法跑了。
+ */
+final class GcSentinel {
+
+ private final Object owner;
+
+ /** 队列挂在主线程上:哨兵是在 {@code newThread} 之前还是之后创建的无从保证 */
+ private final LuaState main;
+
+ /**
+ * 这个哨兵在 {@link LuaState#finalizables} 上的记录。
+ *
+ * 带着它是为了在 {@code finalize} 里顺手把它交给主线程——弱引用被清之后
+ * 就再也认不出「是哪一条」,而「跑过没有」这份状态只记在记录上。
+ */
+ private final LuaState.GcRef ref;
+
+ GcSentinel(Object owner, LuaState main, LuaState.GcRef ref) {
+ this.owner = owner;
+ this.main = main;
+ this.ref = ref;
+ }
+
+ /**
+ * 表不可达 → 哨兵也不可达 → JVM 调这里。
+ *
+ * 这里唯一的活就是把 {@code owner} 拽回可达集合。它只被 {@link #owner} 强
+ * 引用着,而 {@code owner} 这条边不算「脚本的引用」,所以一旦脚本丢开表,
+ * 两者一起不可达——这正是我们要的判据。
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ protected void finalize() {
+ main.enqueueFinalizer(owner, ref);
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/Generated.java b/src/main/java/com/github/esrrhs/javalua/Generated.java
new file mode 100644
index 0000000..b233f5b
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/Generated.java
@@ -0,0 +1,26 @@
+package com.github.esrrhs.javalua;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标记「不是手写的实现」,JaCoCo 会把它跳出行覆盖统计。
+ *
+ * 用在这里有两处,性质不同但要分开说清楚:
+ *
+ * 之所以不靠「降低门禁值」来消化这批行:门禁一降就再也看不出真下降。而摘
+ * 注解的动作与「这条语义真的落地了」是同一个动作,不会脱节。
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@interface Generated {
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/IoLib.java b/src/main/java/com/github/esrrhs/javalua/IoLib.java
new file mode 100644
index 0000000..5795f06
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/IoLib.java
@@ -0,0 +1,1651 @@
+package com.github.esrrhs.javalua;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * io 库 —— 对应 {@code liolib.c}。
+ *
+ * 文件句柄是带命名元表的 userdata(元表名 {@code FILEHANDLE*}),这是
+ * 本库对内核提出的第一个硬要求:{@code luaL_testudata} 判的是「这个 userdata
+ * 的元表是不是注册表里那一张」,所以 userdata 的元表必须挂在对象上而不是类型上。
+ * 之前的实现恰好只有类型元表,为此在阶段 7 补了 {@link LuaUserdata}。
+ *
+ * 几处与 C 不同但刻意如此的地方(都记进了 README):
+ *
+ * C 扩展 {@code luaL_checkudata(L, idx, "FILE*")} 拿到的是 {@code luaL_Stream*}
+ * (一块 native 内存),而 Java 侧的 I/O 逻辑需要对应的 {@code LuaFile} 对象。
+ * 这个表就是两边的桥梁。
+ *
+ * 条目在 {@link #freeStream} 时移除(文件关闭),残余条目随 JVM 退出回收。
+ */
+ private static final ConcurrentHashMap
+ * 真值是平台相关的:macOS 上参考实现二分出来在 {@code 2^46} 附近断
+ * ({@code 70368740245501} 还能成、{@code 70368740507645} 就不行),
+ * Linux 有过量提交、边界又不一样。所以这个数不进差分用例,
+ * 它只管「明显大到谁都分配不出来」的那一档;唯一确定性的输入是
+ * 负数(C 里转 {@code size_t} 之后必然是天文数字)。
+ */
+ private static final long MAX_READ_ALLOC = 1L << 46;
+
+ /* ==================== 注册 ==================== */
+
+ public static void open(LuaState L) {
+ LuaTable io = new LuaTable();
+ LuaAux.setFn(io, "close", IoLib::ioClose);
+ LuaAux.setFn(io, "flush", IoLib::ioFlush);
+ LuaAux.setFn(io, "input", IoLib::ioInput);
+ LuaAux.setFn(io, "lines", IoLib::ioLines);
+ LuaAux.setFn(io, "open", IoLib::ioOpen);
+ LuaAux.setFn(io, "output", IoLib::ioOutput);
+ LuaAux.setFn(io, "popen", IoLib::ioPopen);
+ LuaAux.setFn(io, "read", IoLib::ioRead);
+ LuaAux.setFn(io, "tmpfile", IoLib::ioTmpfile);
+ LuaAux.setFn(io, "type", IoLib::ioType);
+ LuaAux.setFn(io, "write", IoLib::ioWrite);
+ LuaAux.registerLib(L, "io", io);
+
+ createMeta(L);
+
+ // 三个标准流。注意它们的 closef 是 io_noclose:关不掉,关了要给
+ // (nil, "cannot close standard file")
+ createStdFile(L, io, stdFile(L, true, false, false), IO_INPUT, "stdin");
+ createStdFile(L, io, stdFile(L, false, true, false), IO_OUTPUT, "stdout");
+ createStdFile(L, io, stdFile(L, false, true, true), null, "stderr");
+ }
+
+ /**
+ * {@code createmeta}:建 {@code FILEHANDLE*} 元表,并把方法表挂到
+ * {@code __index} 上。
+ */
+ private static void createMeta(LuaState L) {
+ LuaTable mt = LuaAux.newMetatable(L, FILE_HANDLE);
+ LuaAux.setFn(mt, "__gc", IoLib::fGc);
+ /*
+ * {@code __close} 与 {@code __gc} 必须是同一个值(C 里
+ * {@code {"__gc", f_gc}} 与 {@code {"__close", f_gc}} 挂的是同一个函数
+ * 指针,无 upvalue 时压的是轻 C 函数),所以
+ * {@code getmetatable(io.stdout).__gc == getmetatable(io.stdout).__close}
+ * 在参考实现里是 true。{@code setFn} 每次新建一层包装,两次调用
+ * 就是两个值;这里直接把 {@code __gc} 那个值搬过来。
+ * 与 {@code math.atan/atan2} 是同一件事(见 MathLib 的注册)。
+ */
+ mt.set("__close", mt.get(LuaString.latin1("__gc")));
+ LuaAux.setFn(mt, "__tostring", IoLib::fTostring);
+ LuaTable meth = new LuaTable();
+ LuaAux.setFn(meth, "read", IoLib::fRead);
+ LuaAux.setFn(meth, "write", IoLib::fWrite);
+ LuaAux.setFn(meth, "lines", IoLib::fLines);
+ LuaAux.setFn(meth, "flush", IoLib::fFlush);
+ LuaAux.setFn(meth, "seek", IoLib::fSeek);
+ LuaAux.setFn(meth, "close", IoLib::fClose);
+ LuaAux.setFn(meth, "setvbuf", IoLib::fSetvbuf);
+ mt.set("__index", LuaValue.ofTable(meth));
+ }
+
+ private static void createStdFile(LuaState L, LuaTable io, LuaFile f, String key,
+ String fname) {
+ LuaValue v = newFileValue(L, f, IoLib::ioNoclose);
+ // 标准流也分配 C FILE*,让 C 扩展能拿到真实的 stdin/stdout/stderr
+ if (NativeBridge.AVAILABLE) {
+ int which = f.stdIn ? 0 : (f.stdErr ? 2 : 1);
+ f.filePtr = NativeBridge.cStdStream(which);
+ fillStream(f);
+ }
+ if (key != null) {
+ L.registry.set(key, v);
+ }
+ io.set(fname, v);
+ L.pop(1);
+ }
+
+ /* ==================== 文件句柄 ==================== */
+
+ /**
+ * {@code LStream}:{@code FILE*} 的替身。
+ *
+ * C 里一个 {@code FILE*} 同时能读能写还能定位,Java 没有这种东西,所以按
+ * 「真实文件走 {@link RandomAccessFile}、标准流与管道走 {@code InputStream} /
+ * {@code OutputStream}」分开存,读写时各自分派。
+ *
+ * {@code closef == null} 就是 C 的 {@code isclosed}。
+ */
+ static final class LuaFile {
+
+ /** C 侧的 {@code FILE*};0 表示未打开或已关闭 */
+ long filePtr;
+
+ /**
+ * C 侧的 {@code luaL_Stream*}(16 字节:FILE* + closef)。
+ *
+ * 文件句柄 userdata 的载荷就是这块 native 内存——C 扩展
+ * {@code luaL_checkudata(L, idx, "FILE*")} 拿到的就是它,cast 成
+ * {@code luaL_Stream*} 后直接访问 {@code ->f} 和 {@code ->closef}。
+ */
+ long streamPtr;
+
+ /**
+ * 持有 {@code streamPtr} 对应的 native 内存块,防止 GC 提前释放。
+ *
+ * {@code NativeMemory} 的 Cleaner 会在自身被回收时 {@code free(streamPtr)}。
+ * 只要 {@code LuaFile} 活着(被 Lua 状态引用),{@code mem} 就不可达,
+ * native 内存就不会被回收。
+ */
+ NativeMemory mem;
+
+ RandomAccessFile raf;
+ InputStream in;
+ OutputStream out;
+ java.lang.Process proc;
+
+ /**
+ * {@code io.popen} 的子进程里那些没被接管的流的转发线程。
+ * 关句柄时要先 {@code join} 它们,否则子进程的最后几行输出可能还没
+ * 落到 {@code L.out} / {@code L.err} 上,比对就时好时坏。
+ */
+ Thread[] pumps;
+
+ /**
+ * 标准流要现用现取 {@code LuaState#out} / {@code #in} / {@code #err}。
+ * 测试是在开库之后才把 {@code L.out} 换成内存缓冲区的,若是开库时就把
+ * 流抓在手里,{@code io.write} 的输出就永远落不到那条缓冲上。
+ */
+ LuaState L;
+ boolean stdIn;
+ boolean stdOut;
+ boolean stdErr;
+
+ boolean readable;
+ boolean writable;
+ boolean append;
+
+ /**
+ * 打开的是目录。
+ *
+ * POSIX 的 {@code fopen(dir, "r")} 是成功的({@code open(dir, O_RDONLY)}
+ * 允许),失败发生在第一次 {@code fgetc} / {@code fread} 上,errno 是
+ * {@code EISDIR}(21)。实测(参考二进制):
+ *
+ * C 的 stdio 对文件是块缓冲的:{@code io.output(p):write("x")} 之后
+ * 立刻 {@code io.input(p):read()} 读到的是空,因为那几个字节
+ * 还在 {@code FILE*} 的缓冲里没落盘。不照做的话这类脚本就会在两边
+ * 给出不同结果。标准流则相反——它们是行/无缓冲语义,写完就要看得见,
+ * 而且 {@code L.out} 本来就是个 {@code PrintStream}。
+ */
+ private final byte[] wbuf = new byte[WBUF_CAP];
+ private int wlen;
+
+ private static final int WBUF_CAP = 8192;
+
+ /**
+ * 缓冲模式:0 = 无缓冲(IONBF),1 = 全缓冲(IOFBF),2 = 行缓冲(_IOLBF)。
+ *
+ * 默认是全缓冲(与 C stdio 对文件的默认行为一致)。标准流在创建时设成行缓冲。
+ */
+ int bufMode = 1;
+
+ /** 行缓冲模式下,缓冲区大小(由 setvbuf 的第三个参数指定) */
+ int bufSize = WBUF_CAP;
+
+ /**
+ * 是否走 C FILE* 做 I/O。
+ *
+ * 只有「有 filePtr 且不是标准流且不是目录」的文件走 C 路径。
+ * 标准流走 Java 的 L.out/L.err(测试要捕获输出);目录走 Java 模拟。
+ */
+ boolean isCFile() {
+ return filePtr != 0 && !stdIn && !stdOut && !stdErr && !dir;
+ }
+
+ /** 把写缓冲落盘;{@code raf == null} 时是空操作 */
+ private boolean flushW() {
+ if (raf == null || wlen == 0) {
+ return true;
+ }
+ try {
+ raf.write(wbuf, 0, wlen);
+ wlen = 0;
+ return true;
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ return false;
+ }
+ }
+
+ InputStream inStream() {
+ return stdIn ? L.in : in;
+ }
+
+ OutputStream outStream() {
+ if (stdOut) {
+ return L.out;
+ }
+ if (stdErr) {
+ return L.err;
+ }
+ return out;
+ }
+
+ /** {@code l_getc}:-1 表示 EOF */
+ int getc() {
+ if (pushback >= 0) {
+ int c = pushback;
+ pushback = -1;
+ return c;
+ }
+ if (!readable) {
+ err = true;
+ errno = 9; // EBADF
+ return -1;
+ }
+ if (dir) {
+ err = true;
+ errno = 21; // EISDIR
+ return -1;
+ }
+ if (isCFile()) {
+ int c = NativeBridge.cFgetc(filePtr);
+ if (c < 0 && NativeBridge.cFeof(filePtr) != 0) {
+ return -1;
+ }
+ if (c < 0) {
+ err = true;
+ errno = lastErrno();
+ }
+ return c;
+ }
+ flushW();
+ try {
+ return raf != null ? raf.read() : inStream().read();
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ return -1;
+ }
+ }
+
+ /** {@code ungetc}:{@code c == -1} 时是空操作(C 里 ungetc(EOF) 也是) */
+ void ungetc(int c) {
+ pushback = c;
+ }
+
+ /** {@code fread}:返回读到的字节数,0 表示 EOF 或出错 */
+ int read(byte[] b, int off, int len) {
+ int done = 0;
+ if (pushback >= 0 && len > 0) {
+ b[off] = (byte) pushback;
+ pushback = -1;
+ done = 1;
+ }
+ if (done >= len) {
+ return done;
+ }
+ if (!readable) {
+ err = true;
+ errno = 9; // EBADF
+ return done;
+ }
+ if (dir) {
+ err = true;
+ errno = 21; // EISDIR
+ return done;
+ }
+ if (isCFile()) {
+ int r = NativeBridge.cFread(filePtr, b, off + done, len - done);
+ if (r <= 0 && NativeBridge.cFeof(filePtr) != 0) {
+ return done;
+ }
+ if (r < 0) {
+ err = true;
+ errno = lastErrno();
+ return done;
+ }
+ return done + r;
+ }
+ flushW();
+ try {
+ int r = (raf != null) ? raf.read(b, off + done, len - done)
+ : inStream().read(b, off + done, len - done);
+ return (r < 0) ? done : done + r;
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ return done;
+ }
+ }
+
+ /** {@code fwrite} */
+ void write(byte[] b, int off, int len) {
+ if (len <= 0) {
+ return;
+ }
+ if (!writable) {
+ err = true;
+ errno = 9; // EBADF
+ return;
+ }
+ if (isCFile()) {
+ int w = NativeBridge.cFwrite(filePtr, b, off, len);
+ if (w < len) {
+ err = true;
+ errno = lastErrno();
+ }
+ return;
+ }
+ try {
+ if (raf != null) {
+ // "a" 是追加:不管当前位置在哪,写之前都回到末尾
+ if (append) {
+ flushW();
+ raf.seek(raf.length());
+ }
+ // 无缓冲模式:直接写,不经过缓冲区
+ if (bufMode == 0) {
+ raf.write(b, off, len);
+ return;
+ }
+ if (wlen + len > wbuf.length) {
+ if (!flushW()) {
+ return;
+ }
+ }
+ if (len >= wbuf.length) {
+ raf.write(b, off, len); // 大块直接写,不绕缓冲
+ } else {
+ System.arraycopy(b, off, wbuf, wlen, len);
+ wlen += len;
+ // 行缓冲模式:检查是否有换行符,有就 flush
+ if (bufMode == 2) {
+ for (int i = 0; i < len; i++) {
+ if (b[off + i] == '\n') {
+ flushW();
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ outStream().write(b, off, len);
+ outStream().flush(); // 标准流不缓冲
+ }
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ }
+ }
+
+ /** {@code l_fseek}:成功返回新位置,-1 表示失败(管道会失败) */
+ long seek(long offset, int whence) {
+ if (dir) {
+ long base = (whence == 1) ? dirPos : 0;
+ long pos = base + offset;
+ if (pos < 0) {
+ err = true;
+ errno = 22; // EINVAL
+ return -1;
+ }
+ dirPos = pos;
+ return pos;
+ }
+ if (isCFile()) {
+ pushback = -1;
+ int r = NativeBridge.cFseek(filePtr, offset, whence);
+ if (r != 0) {
+ err = true;
+ errno = lastErrno();
+ return -1;
+ }
+ return NativeBridge.cFtell(filePtr);
+ }
+ if (raf == null) {
+ err = true;
+ errno = 29; // ESPIPE
+ return -1;
+ }
+ flushW();
+ try {
+ long base;
+ switch (whence) {
+ case 0: // SEEK_SET
+ base = 0;
+ break;
+ case 1: // SEEK_CUR
+ base = raf.getFilePointer();
+ break;
+ default: // SEEK_END
+ base = raf.length();
+ break;
+ }
+ long pos = base + offset;
+ if (pos < 0) {
+ err = true;
+ errno = 22; // EINVAL
+ return -1;
+ }
+ pushback = -1;
+ raf.seek(pos);
+ return raf.getFilePointer();
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ return -1;
+ }
+ }
+
+ /** {@code l_ftell} */
+ long tell() {
+ if (dir) {
+ return dirPos;
+ }
+ if (isCFile()) {
+ return NativeBridge.cFtell(filePtr);
+ }
+ if (raf == null) {
+ err = true;
+ errno = 29; // ESPIPE
+ return -1;
+ }
+ try {
+ return raf.getFilePointer() + wlen;
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ return -1;
+ }
+ }
+
+ /** {@code fflush} */
+ void flush() {
+ try {
+ if (isCFile()) {
+ NativeBridge.cFflush(filePtr);
+ } else if (raf != null) {
+ flushW();
+ raf.getFD().sync();
+ } else if (writable) {
+ outStream().flush();
+ }
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ }
+ }
+
+ /** 真正关掉底层资源 */
+ void closeRaw() {
+ try {
+ if (filePtr != 0) {
+ NativeBridge.cFclose(filePtr);
+ filePtr = 0;
+ }
+ /*
+ * 不要从 FILE_MAP 里移除:关掉之后再用要报
+ * "attempt to use a closed file",不是 "FILE* expected, got FILE*"。
+ * 留着条目,fileAt 才能找到这个 LuaFile,方法里才能判 closef==null
+ * 给出正确的关闭错误。FILE_MAP 跟着 LuaFile 的 GC 一起清理
+ * (streamPtr 还在 NativeMemory 里,NativeMemory 跟着 userdata 走)。
+ */
+ if (raf != null) {
+ flushW();
+ raf.close();
+ }
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ err = true;
+ errno = LuaAux.errnoOf(e);
+ }
+ }
+ }
+
+ private static LuaFile stdFile(LuaState L, boolean r, boolean w, boolean e) {
+ LuaFile f = new LuaFile();
+ f.L = L;
+ f.stdIn = r;
+ f.stdOut = w && !e;
+ f.stdErr = e;
+ f.readable = r;
+ f.writable = w;
+ return f;
+ }
+
+ /**
+ * 给 LuaFile 分配 C 侧的 {@code luaL_Stream} 结构和 {@code NativeMemory} 包装。
+ *
+ * 调用后 {@code f.streamPtr} 指向有效的 {@code luaL_Stream},{@code f.mem}
+ * 持有对应的 {@code NativeMemory}(防止 GC 提前释放)。
+ *
+ * 必须在 {@code f.filePtr} 已经设好之后调用。
+ */
+ private static void ensureStream(LuaFile f) {
+ long sp = allocStream(f.filePtr, f);
+ f.streamPtr = sp;
+ f.mem = new NativeMemory(sp, FILE_UDATA_LEN);
+ }
+
+ /**
+ * 把已分配的 {@code luaL_Stream} 写入真实的 FILE*。
+ *
+ * {@code newPreFileValue} 已经分配了 native 内存和注册了 FILE_MAP,
+ * 但 FILE* 当时还是 0。文件打开成功后调这个把 FILE* 写进去。
+ */
+ private static void fillStream(LuaFile f) {
+ if (f.streamPtr != 0 && f.filePtr != 0) {
+ NativeBridge.put64(f.streamPtr, f.filePtr);
+ }
+ }
+
+ /**
+ * 文件句柄 userdata 的长度:参考里 {@code lua_rawlen(io.stdout)} 给
+ * 16,那是 {@code sizeof(LStream)}({@code FILE*} +
+ * {@code lua_CFunction closef})在 LP64 上的值。见 {@link LuaUserdata#len}。
+ */
+ private static final int FILE_UDATA_LEN = 16;
+
+ /**
+ * {@code newprefile} 的建值部分:分配 native 内存(给 C 扩展的 {@code luaL_Stream}
+ * 占位)+ 命名元表。{@code streamPtr} 和 FILE* 稍后由 {@code openInto} 填入。
+ *
+ * 如果打开失败,调用方负责释放这块 native 内存({@code f.mem} 可达时 GC 也会收)。
+ */
+ private static LuaValue newPreFileValue(LuaState L, LuaFile f) {
+ long sp = NativeBridge.AVAILABLE ? NativeBridge.cNewLuaLStream(0, 0) : 0;
+ NativeMemory m = sp != 0 ? new NativeMemory(sp, FILE_UDATA_LEN) : null;
+ f.streamPtr = sp;
+ f.mem = m;
+ if (sp != 0) {
+ FILE_MAP.put(sp, f);
+ }
+ L.push(LuaValue.ofUserdata(new LuaUserdata(m, 0, FILE_UDATA_LEN)));
+ LuaAux.setMetatableByName(L, FILE_HANDLE);
+ return new LuaValue(L.stack[L.top - 1]);
+ }
+
+ /** {@code newprefile} + 指定 {@code closef} */
+ private static LuaValue newFileValue(LuaState L, LuaFile f, LuaFunction closef) {
+ LuaValue v = newPreFileValue(L, f);
+ f.closef = closef;
+ return v;
+ }
+
+ /** {@code newfile}:普通文件,closef 是 {@code io_fclose} */
+ private static LuaValue newFile(LuaState L, LuaFile f) {
+ return newFileValue(L, f, IoLib::ioFclose);
+ }
+
+ /* ==================== 句柄查询 ==================== */
+
+ /**
+ * 从栈上参数取出 LuaFile。
+ *
+ * userdata 的 {@code data} 是 {@code NativeMemory}(给 C 扩展的
+ * {@code luaL_Stream*}),Java 侧通过 FILE_MAP 反查到 {@code LuaFile}。
+ */
+ private static LuaFile fileAt(LuaState L, int arg) {
+ LuaValue v = L.arg1(arg);
+ return fileOfValue(v);
+ }
+
+ /** 从 LuaValue(文件句柄 userdata)反查 LuaFile */
+ private static LuaFile fileOfValue(LuaValue v) {
+ if (v == null || v.baseType() != Lua.TUSERDATA) {
+ return null;
+ }
+ Object d = ((LuaUserdata) v.o).data;
+ if (d instanceof NativeMemory) {
+ long sp = ((NativeMemory) d).ptr;
+ LuaFile f = FILE_MAP.get(sp);
+ if (f != null) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+ /** {@code tolstream}:必须是文件句柄,否则报错 */
+ private static LuaFile toLStream(LuaState L) {
+ LuaFile f = fileAt(L, 1);
+ if (f == null) {
+ throw L.typeArgError(1, FILE_HANDLE);
+ }
+ return f;
+ }
+
+ /** {@code tofile}:在上面再加一条「不能是已关闭的」 */
+ private static LuaFile toFile(LuaState L) {
+ LuaFile p = toLStream(L);
+ if (p.closef == null) {
+ throw L.libError("attempt to use a closed file");
+ }
+ return p;
+ }
+
+ /* ==================== io 库函数 ==================== */
+
+ /**
+ * {@code io.type}。
+ *
+ * 参考实现按元表判:userdata 的元表必须是注册表里 {@code FILEHANDLE*} 那张,
+ * 否则一律答 {@code nil}。{@code debug.setmetatable(f, {...})} 换掉元表之后,
+ * {@code io.type(f)} 就从 {@code "file"} 变成 {@code nil}——即使内部结构还是
+ * 个 {@link LuaFile}。所以这里不能走 {@code fileAt}(那按 streamPtr 查 FILE_MAP,
+ * 永远找得着),必须像参考那样先取元表再比对。
+ */
+ private static int ioType(LuaState L) {
+ LuaAux.checkAny(L, 1);
+ LuaValue v = L.arg1(1);
+ if (v.baseType() != Lua.TUSERDATA) {
+ LuaAux.pushFail(L);
+ return 1;
+ }
+ /*
+ * 取 userdata 自己的元表(不是类型共享的),和 FILE_HANDLE 那张比。
+ * {@code LuaTm.metatableOf} 会把类型元表也算进来,所以要先判是不是 userdata
+ * 自己的 {@code metatable} 字段。
+ */
+ LuaTable mt = ((LuaUserdata) v.o).metatable;
+ if (mt == null) {
+ LuaAux.pushFail(L);
+ return 1;
+ }
+ /*
+ * 比对象:注册表里 {@code FILE_HANDLE} 那张元表是唯一的,{@code debug.setmetatable}
+ * 换上去的新表不是同一张。用 {@code ==} 比引用就够了。
+ */
+ LuaValue fileMtVal = L.registry.get(FILE_HANDLE);
+ if (fileMtVal == null || fileMtVal.baseType() != Lua.TTABLE) {
+ LuaAux.pushFail(L);
+ return 1;
+ }
+ LuaTable fileMt = (LuaTable) fileMtVal.o;
+ if (mt != fileMt) {
+ LuaAux.pushFail(L);
+ return 1;
+ }
+ /*
+ * 元表对上了,再看 {@code closef}:{@code null} 表示已关。
+ * 这里才能安全地 {@code fileOfValue}——元表都对不上就别找了。
+ */
+ LuaFile p = fileOfValue(v);
+ if (p == null || p.closef == null) {
+ L.push(LuaValue.ofString(LuaString.latin1("closed file")));
+ } else {
+ L.push(LuaValue.ofString(LuaString.latin1("file")));
+ }
+ return 1;
+ }
+
+ /**
+ * {@code f_tostring}。
+ *
+ * C 是 {@code "file (%p)"},地址不可能对得上,只能给个同形状的十六进制值。
+ */
+ private static int fTostring(LuaState L) {
+ LuaFile p = toLStream(L);
+ if (p.closef == null) {
+ L.push(LuaValue.ofString(LuaString.latin1("file (closed)")));
+ } else {
+ // "%p" 不补前导零,与 BaseLib.tostring 的地址形状保持一致
+ L.push(LuaValue.ofString(LuaString.latin1(
+ "file (0x" + Integer.toHexString(System.identityHashCode(p)) + ")")));
+ }
+ return 1;
+ }
+
+ /**
+ * {@code aux_close}:调句柄自己的 {@code closef}。
+ *
+ * 先把 {@code closef} 清空再调,顺序照抄 C——这样 {@code __gc} 与
+ * 显式 {@code close} 撞到一起时不会关两遍。
+ */
+ private static int auxClose(LuaState L) {
+ LuaFile p = toLStream(L);
+ LuaFunction cf = p.closef;
+ p.closef = null;
+ return cf.call(L);
+ }
+
+ /** {@code f_close} */
+ private static int fClose(LuaState L) {
+ toFile(L); // 先确认是个开着的流
+ return auxClose(L);
+ }
+
+ /** {@code io_close}:不给参数就关默认输出 */
+ private static int ioClose(LuaState L) {
+ if (L.isNoneArg(1)) {
+ LuaValue v = L.registry.get(IO_OUTPUT);
+ L.push(v == null ? LuaValue.ofNil() : v);
+ }
+ return fClose(L);
+ }
+
+ /** {@code f_gc}:{@code __gc} 与 {@code __close} 共用 */
+ private static int fGc(LuaState L) {
+ LuaFile p = toLStream(L);
+ if (p.closef != null) {
+ auxClose(L);
+ }
+ return 0;
+ }
+
+ /** {@code io_fclose}:普通文件的 closef */
+ private static int ioFclose(LuaState L) {
+ LuaFile p = toLStream(L);
+ p.err = false;
+ p.closeRaw();
+ LuaAux.fileResult(L, !p.err, null, p.errno);
+ return p.err ? 3 : 1;
+ }
+
+ /** {@code io_noclose}:标准文件的 closef,关不掉 */
+ private static int ioNoclose(LuaState L) {
+ LuaFile p = toLStream(L);
+ p.closef = IoLib::ioNoclose; // 保持打开
+ LuaAux.pushFail(L);
+ L.push(LuaValue.ofString(LuaString.latin1("cannot close standard file")));
+ return 2;
+ }
+
+ /** {@code l_checkmode}:只认 {@code [rwa]%+?[b]*} */
+ private static boolean checkMode(String mode) {
+ if (mode.isEmpty()) {
+ return false;
+ }
+ char c = mode.charAt(0);
+ if (c != 'r' && c != 'w' && c != 'a') {
+ return false;
+ }
+ int i = 1;
+ if (i < mode.length() && mode.charAt(i) == '+') {
+ i++;
+ }
+ for (; i < mode.length(); i++) {
+ if (mode.charAt(i) != 'b') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** {@code l_checkmodep}:{@code popen} 只认 {@code "r"} / {@code "w"} */
+ private static boolean checkModeP(String mode) {
+ return (mode.length() == 1) && (mode.charAt(0) == 'r' || mode.charAt(0) == 'w');
+ }
+
+ /** {@code opencheck}:打不开直接报错({@code io.lines} 用它) */
+ private static void openCheck(LuaState L, LuaString fname, String mode) {
+ LuaFile f = new LuaFile();
+ newFileValue(L, f, IoLib::ioFclose);
+ if (!openInto(f, fname.toJavaString(), mode)) {
+ // 文件名是任意字节,消息得按字节拼
+ throw L.libError(LuaString.latin1("cannot open file '").concat(fname)
+ .concat(LuaString.latin1("' (" + LuaAux.strError(f.errno) + ")")));
+ }
+ fillStream(f);
+ }
+
+ /** 按 mode 打开真实文件;失败时把 errno 记进 {@code f} 并返回 false */
+ private static boolean openInto(LuaFile f, String fname, String mode) {
+ char base = mode.charAt(0);
+ boolean plus = mode.indexOf('+') >= 0;
+ f.readable = (base == 'r') || plus;
+ f.writable = (base != 'r') || plus;
+ f.append = (base == 'a');
+ /*
+ * 目录:C 的 fopen 在只读模式上是开得开的(open(dir, O_RDONLY) 允许),
+ * 要写才给 EISDIR。Java 这边 RandomAccessFile 两种模式都开不了,
+ * 所以先判目录再分模式 —— 否则写目录报的是 ENOENT 而不是 EISDIR。
+ *
+ * 目录走 Java 模拟(没有 C FILE*),C 扩展拿到的 FILE* 是 0。
+ */
+ if (new java.io.File(fname).isDirectory()) {
+ if (f.writable) {
+ f.err = true;
+ f.errno = 21; // EISDIR
+ return false;
+ }
+ f.dir = true;
+ return true;
+ }
+ /*
+ * 普通文件走 C 的 {@code fopen}:C 扩展需要拿到真实的 {@code FILE*}。
+ *
+ * {@code "r+"} 不创建文件(C 的 fopen("r+") 走 open(path, O_RDWR),
+ * 没带 O_CREAT),与参考实现一致。
+ */
+ if (!NativeBridge.AVAILABLE) {
+ f.err = true;
+ f.errno = 38; // ENOSYS - Function not implemented
+ return false;
+ }
+ byte[] pathBytes = fname.getBytes(StandardCharsets.UTF_8);
+ byte[] modeBytes = mode.getBytes(StandardCharsets.US_ASCII);
+ f.filePtr = NativeBridge.cFopen(pathBytes, modeBytes);
+ if (f.filePtr == 0) {
+ f.err = true;
+ f.errno = lastErrno();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 取 C 侧的 {@code errno}。{@code cFopen} 失败后调它拿错误码。
+ *
+ * C 的 {@code fopen} 失败时设 {@code errno},JNI 调用本身不碰 {@code errno},
+ * 所以这里读到的就是 {@code fopen} 留下的值。
+ */
+ private static int lastErrno() {
+ return NativeBridge.AVAILABLE ? NativeBridge.cGetErrno() : 38; // ENOSYS
+ }
+
+ /**
+ * {@code io.open}
+ *
+ * 文件名与模式都是 {@code fopen} 的 {@code const char*},在第一个 {@code \0}
+ * 处砍断:{@code io.open("p", "r\0w")} 的模式是 {@code "r"},不是 {@code "r\0w"}
+ * (后者 {@code checkMode} 会判成 invalid mode)。
+ */
+ private static int ioOpen(LuaState L) {
+ LuaString fname = LuaAux.checkString(L, 1).toCString();
+ String filename = fname.toJavaString();
+ String mode = LuaAux.optString(L, 2, LuaString.latin1("r")).toCString().toJavaString();
+ LuaFile f = new LuaFile();
+ newFile(L, f);
+ LuaAux.argCheck(L, checkMode(mode), 2, "invalid mode");
+ f.err = false;
+ if (!openInto(f, filename, mode)) {
+ LuaAux.fileResult(L, false, fname, f.errno);
+ return 3;
+ }
+ fillStream(f);
+ return 1;
+ }
+
+ /** {@code io.popen} */
+ private static int ioPopen(LuaState L) {
+ LuaString fname = LuaAux.checkString(L, 1).toCString();
+ String filename = fname.toJavaString();
+ String mode = LuaAux.optString(L, 2, LuaString.latin1("r")).toCString().toJavaString();
+ LuaFile f = new LuaFile();
+ newPreFileValue(L, f);
+ LuaAux.argCheck(L, checkModeP(mode), 2, "invalid mode");
+ f.err = false;
+ boolean forRead = mode.charAt(0) == 'r';
+ /*
+ * C 的 {@code popen} 只接管一条流:读模式接管子进程的
+ * {@code stdout},写模式接管它的 {@code stdin},其余一律继承
+ * ——它内部就是 {@code pipe} + {@code fork} + 一次 {@code dup2} +
+ * {@code exec},没碰的 fd 原样传下去。
+ *
+ * 实测(参考二进制):
+ *
+ * 目标流用 {@code LuaState#out} / {@code #err} 而不是
+ * {@code Redirect.INHERIT}:INHERIT 走的是 JVM 进程的 fd 1 / fd 2,
+ * 而本实现的「标准输出 / 标准错误」就是 {@code L.out} / {@code L.err}
+ * (差分测试正是靠把它们换成内存缓冲区来比 {@code popen} 的输出的)。
+ * C 那边的落点是 fd 1 / fd 2,两边是同一件事。
+ */
+ try {
+ ProcessBuilder pb = new ProcessBuilder("sh", "-c", filename);
+ /*
+ * 重定向必须在 {@code start()} 之前设好 —— {@code ProcessBuilder}
+ * 是在 {@code start} 那一刻才照这份配置去摆 fd 的,之后再改不影响已
+ * 经起来的子进程。读模式要把 {@code stdin} 设为 INHERIT:子进程的
+ * 输入应当来自 lua 自己的标准输入({@code cat} 这类命令才读得到东西),
+ * 而不是一条没人写也没人关的管道。
+ */
+ if (forRead) {
+ pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ }
+ java.lang.Process p = pb.start();
+ f.proc = p;
+ java.util.List
+ * 搬而不是 {@code Redirect.INHERIT} 的理由见 {@link #ioPopen}:
+ * INHERIT 绕过了 {@code L.out} / {@code L.err}。
+ *
+ * 必须一直抽干:子进程往里写而我们不读的话,管道缓冲满了它就会
+ * 阻塞 —— C 那边目标是继承来的 fd,不会堵。守护线程是为了「脚本压根没
+ * 关这个句柄」时也不拖住 JVM 退出。
+ */
+ private static Thread pump(InputStream src, OutputStream dst) {
+ Thread t = new Thread(() -> {
+ byte[] b = new byte[4096];
+ try {
+ int n;
+ while ((n = src.read(b)) > 0) {
+ dst.write(b, 0, n);
+ dst.flush(); // 行/无缓冲语义:子进程写一行就看得见
+ }
+ } catch (IOException e) {
+ // 子进程一退出管道就自然断在这里,正常结束
+ } finally {
+ try {
+ src.close();
+ } catch (IOException ignored) {
+ // 关不掉就算了
+ }
+ }
+ });
+ t.setName("lua-popen-pump");
+ t.setDaemon(true);
+ t.start();
+ return t;
+ }
+
+ /** {@code pclose} 等转发线程的上限(毫秒),理由见 {@link #ioPclose} */
+ private static final long PUMP_JOIN_MS = 2000;
+
+ /** {@code io_pclose} */
+ private static int ioPclose(LuaState L) {
+ LuaFile p = toLStream(L);
+ if (p.proc == null) {
+ // popen 就没起来过(shell 不存在之类),没有进程可等
+ LuaAux.fileResult(L, false, null, (p.errno != 0) ? p.errno : 2);
+ return 3;
+ }
+ int stat;
+ try {
+ if (p.out != null) {
+ p.out.close();
+ }
+ stat = p.proc.waitFor();
+ if (p.in != null) {
+ p.in.close();
+ }
+ } catch (IOException e) {
+ LuaAux.fileResult(L, false, null, LuaAux.errnoOf(e));
+ return 3;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LuaAux.fileResult(L, false, null, 4); // EINTR
+ return 3;
+ }
+ /*
+ * 等转发线程收尾 —— 子进程已经退出了,它们读到 EOF 就会结束,所以
+ * join 不会久等。不等的话 {@code popen} 的最后几行输出可能还没落到
+ * {@code L.out} / {@code L.err} 上,比对就时好时坏:C 那边是 fd 直接
+ * 写的,{@code pclose} 返回时早就写完了。
+ *
+ * 但 join 必须有上限。C 的 {@code pclose} 只 {@code waitpid}
+ * 那一个子进程,因为没被接管的那两条流是继承来的 fd,孙进程
+ * 拿着也无所谓;而这边是管道 —— {@code io.popen("sleep 60 & echo hi")}
+ * 这种会把写端留给一个活着的孙进程,{@code waitFor} 早返回了,转发线程
+ * 却永远读不到 EOF,无上限的 join 就把 {@code f:close()} 卡死。
+ * 设一个 2 秒的上限:正常情况下子进程一退出、管道里那点数据瞬间就抽
+ * 干了;抽不干的只可能是「还有别人握着写端」,那种情况下等也没用。
+ */
+ if (p.pumps != null) {
+ for (Thread t : p.pumps) {
+ try {
+ t.join(PUMP_JOIN_MS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ p.pumps = null;
+ }
+ LuaAux.execResult(L, stat, 0);
+ return 3;
+ }
+
+ /** {@code io.tmpfile} */
+ private static int ioTmpfile(LuaState L) {
+ LuaFile f = new LuaFile();
+ newFile(L, f);
+ f.err = false;
+ if (!NativeBridge.AVAILABLE) {
+ f.err = true;
+ f.errno = 38; // ENOSYS - Function not implemented
+ } else {
+ try {
+ java.io.File tmp = java.io.File.createTempFile("lua_", ".tmp");
+ tmp.deleteOnExit();
+ String path = tmp.getAbsolutePath();
+ byte[] pathBytes = path.getBytes(StandardCharsets.UTF_8);
+ byte[] modeBytes = "w+b".getBytes(StandardCharsets.US_ASCII);
+ f.filePtr = NativeBridge.cFopen(pathBytes, modeBytes);
+ if (f.filePtr == 0) {
+ f.err = true;
+ f.errno = 2; // ENOENT fallback
+ } else {
+ f.readable = true;
+ f.writable = true;
+ fillStream(f);
+ }
+ } catch (IOException e) {
+ f.err = true;
+ f.errno = LuaAux.errnoOf(e);
+ }
+ }
+ if (f.err) {
+ LuaAux.fileResult(L, false, null, f.errno);
+ return 3;
+ }
+ return 1;
+ }
+
+ /**
+ * {@code getiofile}:取注册表里的默认文件,已关就报错。
+ *
+ * 返回的是值而不是句柄,因为 C 的 {@code getiofile} 是
+ * {@code lua_getfield}——它把文件压到了栈上,而 {@code io.read} /
+ * {@code io.write} 正是靠这个把句柄当成返回值带出去的。
+ */
+ private static LuaValue getIoFile(LuaState L, String findex) {
+ LuaValue v = L.registry.get(findex);
+ LuaFile p = fileOfValue(v);
+ if (p == null || p.closef == null) {
+ throw L.libError("default " + findex.substring("_IO_".length())
+ + " file is closed");
+ }
+ return v;
+ }
+
+ /** {@code g_iofile}:{@code io.input} / {@code io.output} 共用 */
+ private static int gIoFile(LuaState L, String key, String mode) {
+ if (!L.isNoneArg(1) && !L.arg1(1).isNil()) {
+ LuaValue a = L.arg1(1);
+ LuaString s = (a.isString() || a.isNumber()) ? LuaAux.asString(a) : null;
+ if (s != null) {
+ openCheck(L, s.toCString(), mode);
+ } else {
+ toFile(L); // 确认是个开着的句柄
+ L.push(a);
+ }
+ L.registry.set(key, new LuaValue(L.stack[L.top - 1]));
+ }
+ LuaValue v = L.registry.get(key);
+ L.push(v == null ? LuaValue.ofNil() : v);
+ return 1;
+ }
+
+ private static int ioInput(LuaState L) {
+ return gIoFile(L, IO_INPUT, "r");
+ }
+
+ private static int ioOutput(LuaState L) {
+ return gIoFile(L, IO_OUTPUT, "w");
+ }
+
+ /* ==================== lines ==================== */
+
+ /**
+ * {@code aux_lines}。
+ *
+ * C 是「压三个值再 {@code lua_rotate} 再 {@code lua_pushcclosure}」,这里是
+ * 直接按最终布局(文件、读格式个数、是否要关、各读格式)填 {@link CClosure}
+ * 的 upvalue——排布完全一样,省掉那次旋转。
+ */
+ private static void auxLines(LuaState L, boolean toclose) {
+ int n = L.getTop() - 1; // 读格式个数
+ LuaAux.argCheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments");
+ CClosure cl = new CClosure(IoLib::readline, 3 + n);
+ cl.upvalue[0].set(L.arg(0)); // 文件
+ cl.upvalue[1].set(LuaValue.ofInteger(n));
+ cl.upvalue[2].set(LuaValue.ofBoolean(toclose));
+ for (int i = 0; i < n; i++) {
+ cl.upvalue[3 + i].set(L.arg(1 + i));
+ }
+ L.push(LuaValue.ofCClosure(cl));
+ }
+
+ private static int fLines(LuaState L) {
+ toFile(L);
+ auxLines(L, false);
+ return 1;
+ }
+
+ /**
+ * {@code io.lines}。
+ *
+ * 给了文件名时返回的第四个数是「待关闭变量」——泛型 for 会把它当
+ * {@code
+ * C 里 {@code g_read} 写的是 {@code size_t l = (size_t)luaL_checkinteger(L, n)}:
+ * 负数按无符号解释成一个天文数字,随后 {@code luaL_prepbuffsize} 去
+ * 预分配这么大一块,{@code resizebox} 分配失败抛 {@code "not enough memory"}。
+ * 实测 {@code f:read(-1)} 在参考实现上就是 {@code false not enough memory},
+ * 而不是什么整数越界。
+ *
+ * 所以这里不预分配 {@code n} 个字节,改成循环读到「流干」或「够数」为止
+ * ——这也更贴近 {@code fread}:短读时 C 会继续读到 {@code n} 或 EOF。只有大得
+ * 任何系统都分配不出来时才抛内存错。
+ */
+ private static boolean readChars(LuaState L, LuaFile f, long n) {
+ if (n < 0 || n > MAX_READ_ALLOC) {
+ /*
+ * C 那边是 {@code resizebox} 里 {@code lua_pushliteral +
+ * lua_error},而 {@code lua_error} 认出这是内存错误消息,于是走
+ * {@code luaM_error}({@code LUA_ERRMEM})——
+ * {@code xpcall} 的处理函数接不到。
+ */
+ LuaError e = new LuaError(LuaValue.ofString(LuaString.latin1("not enough memory")));
+ e.errMem = true;
+ throw e;
+ }
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buf = new byte[CHUNK];
+ long left = n;
+ while (left > 0) {
+ int nr = f.read(buf, 0, (int) Math.min(left, CHUNK));
+ if (nr <= 0) {
+ break;
+ }
+ bos.write(buf, 0, nr);
+ left -= nr;
+ }
+ byte[] b = bos.toByteArray();
+ L.push(LuaValue.ofString(L.newString(b, 0, b.length)));
+ return b.length > 0;
+ }
+
+ /**
+ * {@code read_number}。
+ *
+ * 先把「一个合法数字前缀」读进缓冲,再交给 {@code lua_stringtonumber} 判定——
+ * 这样 {@code "3x"} 会读成 {@code 3} 并把 {@code x} 留在流里,与 C 一致。
+ */
+ private static boolean readNumber(LuaState L, LuaFile f) {
+ StringBuilder b = new StringBuilder();
+ int count = 0;
+ boolean hex = false;
+ int c;
+ do {
+ c = f.getc();
+ } while (c >= 0 && isSpace(c));
+ if (c == '-' || c == '+') {
+ b.append((char) c);
+ c = f.getc();
+ }
+ if (c == '0') {
+ b.append('0');
+ c = f.getc();
+ if (c == 'x' || c == 'X') {
+ hex = true;
+ b.append((char) c);
+ c = f.getc();
+ } else {
+ count = 1;
+ }
+ }
+ while (c >= 0 && (hex ? isXdigit(c) : isDigit(c)) && b.length() <= L_MAXLENNUM) {
+ b.append((char) c);
+ count++;
+ c = f.getc();
+ }
+ if (c == '.') { // locale 的小数点这里只认 '.',与 C 的默认一致
+ b.append('.');
+ c = f.getc();
+ while (c >= 0 && (hex ? isXdigit(c) : isDigit(c)) && b.length() <= L_MAXLENNUM) {
+ b.append((char) c);
+ count++;
+ c = f.getc();
+ }
+ }
+ if (count > 0 && (c == 'e' || c == 'E' || (hex && (c == 'p' || c == 'P')))) {
+ b.append((char) c);
+ c = f.getc();
+ if (c == '-' || c == '+') {
+ b.append((char) c);
+ c = f.getc();
+ }
+ while (c >= 0 && isDigit(c) && b.length() <= L_MAXLENNUM) {
+ b.append((char) c);
+ c = f.getc();
+ }
+ }
+ f.ungetc(c);
+ LuaValue num = (b.length() > L_MAXLENNUM) ? null : Numeral.str2num(b.toString());
+ if (num != null) {
+ L.push(num);
+ return true;
+ }
+ L.pushNil();
+ return false;
+ }
+
+ /**
+ * C 的 {@code isspace}(C locale):空格、{@code \f \n \r \t \v}。
+ *
+ * 竖制表符写成 {@code 11}:Java 没有 {@code '\v'} 这个转义({@code '\f'} 倒是有)。
+ */
+ private static boolean isSpace(int c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == 11 || c == '\f';
+ }
+
+ private static boolean isDigit(int c) {
+ return c >= '0' && c <= '9';
+ }
+
+ private static boolean isXdigit(int c) {
+ return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+ }
+
+ /**
+ * {@code g_read}。
+ *
+ * @param first 第一个读格式所在的绝对参数下标({@code io.read} 是 1,
+ * {@code f:read} 是 2)
+ */
+ private static int gRead(LuaState L, LuaFile f, int first) {
+ int nargs = L.getTop() - 1;
+ f.err = false;
+ int n;
+ boolean success;
+ if (nargs == 0) {
+ success = readLine(L, f, true);
+ n = first + 1;
+ } else {
+ LuaAux.checkStack(L, nargs + LuaState.MINSTACK, "too many arguments");
+ success = true;
+ n = first;
+ for (int left = nargs; left > 0 && success; left--, n++) {
+ LuaValue a = L.arg1(n);
+ if (a.baseType() == Lua.TNUMBER) {
+ long len = LuaAux.checkInteger(L, n);
+ success = (len == 0) ? testEof(L, f) : readChars(L, f, len);
+ } else {
+ LuaString p = LuaAux.checkString(L, n);
+ int at = (p.length() > 0 && p.byteAt(0) == '*') ? 1 : 0;
+ int fmt = (at < p.length()) ? p.byteAt(at) : 0;
+ switch (fmt) {
+ case 'n':
+ success = readNumber(L, f);
+ break;
+ case 'l':
+ success = readLine(L, f, true);
+ break;
+ case 'L':
+ success = readLine(L, f, false);
+ break;
+ case 'a':
+ readAll(L, f);
+ success = true;
+ break;
+ default:
+ throw L.argError(n, "invalid format");
+ }
+ }
+ }
+ }
+ if (f.err) {
+ LuaAux.fileResult(L, false, null, f.errno);
+ return 3;
+ }
+ if (!success) {
+ L.top--; // 丢掉最后一个结果
+ LuaAux.pushFail(L);
+ }
+ return n - first;
+ }
+
+ private static int ioRead(LuaState L) {
+ LuaValue v = getIoFile(L, IO_INPUT);
+ L.push(v); // 压在所有读格式之后,g_read 会把它顶掉
+ return gRead(L, fileOfValue(v), 1);
+ }
+
+ private static int fRead(LuaState L) {
+ return gRead(L, toFile(L), 2);
+ }
+
+ /**
+ * {@code io_readline}:{@code lines} 的迭代函数。
+ */
+ private static int readline(LuaState L) {
+ LuaFile p = fileOfValue(L.upvalue(0));
+ int n = (int) L.upvalue(1).i;
+ if (p.closef == null) {
+ throw L.libError("file is already closed");
+ }
+ L.setTop(1);
+ LuaAux.checkStack(L, n, "too many arguments");
+ for (int i = 0; i < n; i++) {
+ L.push(L.upvalue(3 + i));
+ }
+ n = gRead(L, p, 2);
+ if (L.stack[L.top - n].toBoolean()) {
+ return n;
+ }
+ if (n > 1) {
+ // 第二个结果是错误信息
+ throw L.libError(L.stack[L.top - n + 1].asString().toLatin1String());
+ }
+ if (L.upvalue(2).toBoolean()) {
+ L.setTop(0);
+ L.push(L.upvalue(0));
+ auxClose(L);
+ }
+ return 0;
+ }
+
+ /* ==================== 写 ==================== */
+
+ /** {@code g_write} */
+ private static int gWrite(LuaState L, LuaFile f, int arg) {
+ int nargs = L.getTop() - arg;
+ boolean status = true;
+ f.err = false;
+ for (; nargs-- > 0; arg++) {
+ LuaValue v = L.arg1(arg);
+ byte[] b;
+ if (v.baseType() == Lua.TNUMBER) {
+ /*
+ * 浮点按 LUA_NUMBER_FMT(%.14g)写,不补 .0——
+ * 那是 luaO_tostring 才做的事。实测 io.write(-0.0) 出 "-0",
+ * 而 print(-0.0) 出 "-0.0"。
+ */
+ String s = v.isInteger() ? Long.toString(v.i) : Vm.formatG(v.n, 14);
+ b = s.getBytes(StandardCharsets.US_ASCII);
+ } else {
+ LuaString s = LuaAux.checkString(L, arg);
+ b = s.bytes;
+ }
+ f.write(b, 0, b.length);
+ status = status && !f.err;
+ }
+ if (status) {
+ return 1; // 句柄已经在栈顶
+ }
+ LuaAux.fileResult(L, false, null, f.errno);
+ return 3;
+ }
+
+ private static int ioWrite(LuaState L) {
+ LuaValue v = getIoFile(L, IO_OUTPUT);
+ L.push(v); // 压在待写的值之后,g_write 返回的 1 个值就是它
+ return gWrite(L, fileOfValue(v), 1);
+ }
+
+ private static int fWrite(LuaState L) {
+ LuaFile f = toFile(L);
+ L.pushValue(1); // 把句柄压到栈顶作为返回值
+ return gWrite(L, f, 2);
+ }
+
+ /* ==================== 定位与缓冲 ==================== */
+
+ /** {@code f_seek} */
+ private static int fSeek(LuaState L) {
+ LuaFile f = toFile(L);
+ int op = LuaAux.checkOption(L, 2, "cur", new String[] { "set", "cur", "end" });
+ long p3 = LuaAux.optInteger(L, 3, 0);
+ f.err = false;
+ long pos = f.seek(p3, op);
+ if (pos < 0) {
+ LuaAux.fileResult(L, false, null, f.errno);
+ return 3;
+ }
+ L.push(LuaValue.ofInteger(f.tell()));
+ return 1;
+ }
+
+ /**
+ * {@code f_setvbuf}。
+ *
+ * 参数校验后真正改缓冲策略:{@code no} 无缓冲、{@code full} 全缓冲、
+ * {@code line} 行缓冲。第三个参数指定缓冲区大小(行缓冲/全缓冲时有效)。
+ */
+ private static int fSetvbuf(LuaState L) {
+ LuaFile f = toFile(L);
+ String[] modes = new String[] { "no", "full", "line" };
+ int modeIdx = LuaAux.checkOption(L, 2, null, modes);
+ String mode = modes[modeIdx];
+ int size = (int) LuaAux.optInteger(L, 3, LUAL_BUFFERSIZE);
+ // 先 flush 现有缓冲,再切换模式
+ f.flushW();
+ switch (mode) {
+ case "no":
+ f.bufMode = 0;
+ break;
+ case "full":
+ f.bufMode = 1;
+ f.bufSize = size;
+ break;
+ case "line":
+ f.bufMode = 2;
+ f.bufSize = size;
+ break;
+ }
+ LuaAux.fileResult(L, true, null, 0);
+ return 1;
+ }
+
+ private static int ioFlush(LuaState L) {
+ LuaFile f = fileOfValue(getIoFile(L, IO_OUTPUT));
+ f.err = false;
+ f.flush();
+ LuaAux.fileResult(L, !f.err, null, f.errno);
+ return f.err ? 3 : 1;
+ }
+
+ private static int fFlush(LuaState L) {
+ LuaFile f = toFile(L);
+ f.err = false;
+ f.flush();
+ LuaAux.fileResult(L, !f.err, null, f.errno);
+ return f.err ? 3 : 1;
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/LCType.java b/src/main/java/com/github/esrrhs/javalua/LCType.java
new file mode 100644
index 0000000..69f22da
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/LCType.java
@@ -0,0 +1,103 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * 字符分类 —— 对应 {@code lctype.c} 的 {@code luai_ctype_} 表。
+ *
+ * 为什么照搬这张表而不是用 {@code Character.isDigit} 之类:Lua 的字符分类是
+ * 与 locale 无关的 ASCII 固定表,且语义与 C 标准库并不完全一致——
+ * 最明显的是 {@code lislalpha} 把 {@code '_'} 当作字母(所以 Lua 标识符可以
+ * 以下划线开头),而 {@code isalpha} 不会。照搬表可以逐字节对齐上游行为。
+ *
+ * 表按 {@code c + 1} 索引,是为了让 EOZ(-1)也有一个条目。
+ */
+final class LCType {
+
+ private LCType() {
+ }
+
+ private static final int ALPHABIT = 0;
+ private static final int DIGITBIT = 1;
+ private static final int PRINTBIT = 2;
+ private static final int SPACEBIT = 3;
+ private static final int XDIGITBIT = 4;
+
+ private static final int MASK_ALPHA = 1 << ALPHABIT;
+ private static final int MASK_DIGIT = 1 << DIGITBIT;
+ private static final int MASK_PRINT = 1 << PRINTBIT;
+ private static final int MASK_SPACE = 1 << SPACEBIT;
+ private static final int MASK_XDIGIT = 1 << XDIGITBIT;
+
+ /**
+ * 逐字节照搬 {@code lctype.c}。NONA 取 0(未定义 LUA_UCID),
+ * 即所有非 ASCII 字节都不是字母——默认配置下 Lua 标识符只能是 ASCII。
+ */
+ private static final byte[] RAW = {
+ (byte) 0x00, /* EOZ */
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* 0. */
+ (byte) 0x00, (byte) 0x08, (byte) 0x08, (byte) 0x08, (byte) 0x08, (byte) 0x08, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* 1. */
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x0c, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, /* 2. */
+ (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04,
+ (byte) 0x16, (byte) 0x16, (byte) 0x16, (byte) 0x16, (byte) 0x16, (byte) 0x16, (byte) 0x16, (byte) 0x16, /* 3. */
+ (byte) 0x16, (byte) 0x16, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04,
+ (byte) 0x04, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x05, /* 4. */
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05,
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, /* 5. */
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x05,
+ (byte) 0x04, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x05, /* 6. */
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05,
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x05, /* 7. */
+ (byte) 0x05, (byte) 0x05, (byte) 0x05, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x04, (byte) 0x00,
+ /* 8. .. f. :NONA == 0,全部非 ASCII 字节都不是字母 */
+ };
+
+ /** 补齐 128..255(NONA == 0,全为 0) */
+ private static byte[] pad(byte[] raw) {
+ byte[] full = new byte[257];
+ System.arraycopy(raw, 0, full, 0, raw.length);
+ return full;
+ }
+
+ private static final byte[] CTYPE = pad(RAW);
+
+ private static int prop(int c) {
+ // c 可能是 EOZ(-1);表按 c+1 索引
+ int idx = c + 1;
+ if (idx < 0 || idx >= CTYPE.length) {
+ return 0;
+ }
+ return CTYPE[idx] & 0xFF;
+ }
+
+ /** {@code lislalpha}:字母或 '_'(注意:包含下划线,与 C 的 isalpha 不同) */
+ static boolean isAlpha(int c) {
+ return (prop(c) & MASK_ALPHA) != 0;
+ }
+
+ /** {@code lislalnum} */
+ static boolean isAlnum(int c) {
+ return (prop(c) & (MASK_ALPHA | MASK_DIGIT)) != 0;
+ }
+
+ static boolean isDigit(int c) {
+ return (prop(c) & MASK_DIGIT) != 0;
+ }
+
+ static boolean isSpace(int c) {
+ return (prop(c) & MASK_SPACE) != 0;
+ }
+
+ static boolean isPrint(int c) {
+ return (prop(c) & MASK_PRINT) != 0;
+ }
+
+ static boolean isXdigit(int c) {
+ return (prop(c) & MASK_XDIGIT) != 0;
+ }
+
+ /** {@code ltolower}:只对大写字母与 '.' 有意义,这正是 Lua 需要的范围 */
+ static int toLower(int c) {
+ return c | ('A' ^ 'a');
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/LClosure.java b/src/main/java/com/github/esrrhs/javalua/LClosure.java
new file mode 100644
index 0000000..105f699
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/LClosure.java
@@ -0,0 +1,56 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * Lua 闭包 —— 对应 {@code lobject.h} 的 {@code LClosure}。
+ *
+ * 闭包 = 原型 + upvalue 数组。同一个 {@link Proto} 可以被任意多个闭包共享
+ * (循环里 {@code local f = function() end} 就会共享),可变的部分只有
+ * {@code upvals},这与 C 版一致。
+ *
+ * 放入 {@link LuaValue} 时用标签 {@link Lua#VLIF}。
+ */
+public final class LClosure {
+
+ public final Proto p;
+
+ /** upvalue 数组,长度等于 {@code p.sizeupvalues}(主 chunk 例外,见下) */
+ public final UpVal[] upvals;
+
+ public LClosure(Proto p) {
+ this(p, p.sizeupvalues);
+ }
+
+ /**
+ * 用显式的 upvalue 个数构造。
+ *
+ * 主 chunk 必须走这条路:{@code luaY_parser} 固定按 1 个 upvalue 建闭包
+ * ({@code _ENV}),之后由 {@code lua_load} 把全局表塞进去,与
+ * {@code p.sizeupvalues} 是否相等无关。
+ */
+ public LClosure(Proto p, int nupvals) {
+ LuaState.chargeGc();
+ this.p = p;
+ this.upvals = new UpVal[Math.max(nupvals, p.sizeupvalues)];
+ }
+
+ /**
+ * 直接替换第 {@code i} 个 upvalue —— {@code lapi.c: lua_upvaluejoin} 的落点。
+ *
+ * C 版是 {@code *up1 = *up2}:把「upvalue 指针」换掉,而不是把值抄过去。
+ * 换完之后两个闭包就共享同一个 {@link UpVal},此后一边写另一边可见——
+ * 这正是 {@code debug.upvaluejoin} 的全部意义。所以这里必须整个替换数组元素,
+ * 不能 {@code upvals[i].set(...)}。
+ */
+ public void setUpVal(int i, UpVal uv) {
+ upvals[i] = uv;
+ }
+
+ /** 把所有 upvalue 初始化为「关闭且值为 nil」({@code lfunc.c: luaF_initupvals}) */
+ public void initUpvals() {
+ for (int i = 0; i < upvals.length; i++) {
+ if (upvals[i] == null) {
+ upvals[i] = UpVal.closed(LuaValue.ofNil());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/Lexer.java b/src/main/java/com/github/esrrhs/javalua/Lexer.java
new file mode 100644
index 0000000..5fad324
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/Lexer.java
@@ -0,0 +1,945 @@
+package com.github.esrrhs.javalua;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 词法分析器 —— 逐行翻译 {@code llex.c} / {@code llex.h}。
+ *
+ * 这是「直接能从 C 源码翻译」这一路线上的第一块真骨头,也是最容易在
+ * 「看起来能跑」之后悄悄错的地方。翻译时守住的几条上游约定,改任何一条都会
+ * 在某个真实脚本上炸掉:
+ *
+ * 没有 {@code luaX_init}:C 版那里把保留字预先塞进字符串表并打上
+ * {@code extra} 标记,这里保留字查一张静态 HashMap 即可。
+ *
+ * 也没有 {@code ls->h} 那张「避免长字符串被回收」的表:C 版它同时兼作
+ * 长字符串去重,但长字符串不驻留、比较一律按内容,去重与否不可观测,
+ * 去重只是省内存。这里只做短字符串驻留({@link StringPool}),语义一致。
+ */
+public final class Lexer {
+
+ /**
+ * 单字符 token 用自身编码表示,其余从这里开始({@code UCHAR_MAX + 1})。
+ */
+ public static final int FIRST_RESERVED = 256 + 1;
+
+ /* ORDER RESERVED —— 顺序必须与 C 的 enum RESERVED 完全一致 */
+ public static final int TK_AND = FIRST_RESERVED + 0;
+ public static final int TK_BREAK = FIRST_RESERVED + 1;
+ public static final int TK_DO = FIRST_RESERVED + 2;
+ public static final int TK_ELSE = FIRST_RESERVED + 3;
+ public static final int TK_ELSEIF = FIRST_RESERVED + 4;
+ public static final int TK_END = FIRST_RESERVED + 5;
+ public static final int TK_FALSE = FIRST_RESERVED + 6;
+ public static final int TK_FOR = FIRST_RESERVED + 7;
+ public static final int TK_FUNCTION = FIRST_RESERVED + 8;
+ public static final int TK_GOTO = FIRST_RESERVED + 9;
+ public static final int TK_IF = FIRST_RESERVED + 10;
+ public static final int TK_IN = FIRST_RESERVED + 11;
+ public static final int TK_LOCAL = FIRST_RESERVED + 12;
+ public static final int TK_NIL = FIRST_RESERVED + 13;
+ public static final int TK_NOT = FIRST_RESERVED + 14;
+ public static final int TK_OR = FIRST_RESERVED + 15;
+ public static final int TK_REPEAT = FIRST_RESERVED + 16;
+ public static final int TK_RETURN = FIRST_RESERVED + 17;
+ public static final int TK_THEN = FIRST_RESERVED + 18;
+ public static final int TK_TRUE = FIRST_RESERVED + 19;
+ public static final int TK_UNTIL = FIRST_RESERVED + 20;
+ public static final int TK_WHILE = FIRST_RESERVED + 21;
+ public static final int TK_IDIV = FIRST_RESERVED + 22;
+ public static final int TK_CONCAT = FIRST_RESERVED + 23;
+ public static final int TK_DOTS = FIRST_RESERVED + 24;
+ public static final int TK_EQ = FIRST_RESERVED + 25;
+ public static final int TK_GE = FIRST_RESERVED + 26;
+ public static final int TK_LE = FIRST_RESERVED + 27;
+ public static final int TK_NE = FIRST_RESERVED + 28;
+ public static final int TK_SHL = FIRST_RESERVED + 29;
+ public static final int TK_SHR = FIRST_RESERVED + 30;
+ public static final int TK_DBCOLON = FIRST_RESERVED + 31;
+ public static final int TK_EOS = FIRST_RESERVED + 32;
+ public static final int TK_FLT = FIRST_RESERVED + 33;
+ public static final int TK_INT = FIRST_RESERVED + 34;
+ public static final int TK_NAME = FIRST_RESERVED + 35;
+ public static final int TK_STRING = FIRST_RESERVED + 36;
+
+ /** 保留字个数({@code NUM_RESERVED}) */
+ public static final int NUM_RESERVED = TK_WHILE - FIRST_RESERVED + 1;
+
+ /** {@code luaX_tokens} */
+ private static final String[] TOKENS = {
+ "and", "break", "do", "else", "elseif",
+ "end", "false", "for", "function", "goto", "if",
+ "in", "local", "nil", "not", "or", "repeat",
+ "return", "then", "true", "until", "while",
+ "//", "..", "...", "==", ">=", "<=", "~=",
+ "<<", ">>", "::", "aState
- */
- private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
-
- private static final String ZZ_ATTRIBUTE_PACKED_0 =
- "\2\0\1\11\2\1\1\11\25\1\1\11\7\1\6\11"+
- "\2\1\1\11\2\1\2\11\1\0\1\1\1\0\31\1"+
- "\1\11\1\0\23\11\1\1\1\11\1\0\57\1\1\0"+
- "\16\1\1\0\16\1";
-
- private static int [] zzUnpackAttribute() {
- int [] result = new int[177];
- int offset = 0;
- offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
- return result;
- }
-
- private static int zzUnpackAttribute(String packed, int offset, int [] result) {
- int i = 0; /* index in packed string */
- int j = offset; /* index in unpacked array */
- int l = packed.length();
- while (i < l) {
- int count = packed.charAt(i++);
- int value = packed.charAt(i++);
- do result[j++] = value; while (--count > 0);
- }
- return j;
- }
-
- /** the input device */
- private java.io.Reader zzReader;
-
- /** the current state of the DFA */
- private int zzState;
-
- /** the current lexical state */
- private int zzLexicalState = YYINITIAL;
-
- /** this buffer contains the current text to be matched and is
- the source of the yytext() string */
- private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
-
- /** the textposition at the last accepting state */
- private int zzMarkedPos;
-
- /** the current text position in the buffer */
- private int zzCurrentPos;
-
- /** startRead marks the beginning of the yytext() string in the buffer */
- private int zzStartRead;
-
- /** endRead marks the last character in the buffer, that has been read
- from input */
- private int zzEndRead;
-
- /** number of newlines encountered up to the start of the matched text */
- private int yyline;
-
- /** the number of characters up to the start of the matched text */
- private int yychar;
-
- /**
- * the number of characters from the last newline up to the start of the
- * matched text
- */
- private int yycolumn;
-
- /**
- * zzAtBOL == true <=> the scanner is currently at the beginning of a line
- */
- private boolean zzAtBOL = true;
-
- /** zzAtEOF == true <=> the scanner is at the EOF */
- private boolean zzAtEOF;
-
- /** denotes if the user-EOF-code has already been executed */
- private boolean zzEOFDone;
-
- /**
- * The number of occupied positions in zzBuffer beyond zzEndRead.
- * When a lead/high surrogate has been read from the input stream
- * into the final zzBuffer position, this will have a value of 1;
- * otherwise, it will have a value of 0.
- */
- private int zzFinalHighSurrogate = 0;
-
- /* user code: */
- fake m_f;
- StringBuilder string = new StringBuilder();
- /* store a reference to the YYParser object */
- private YYParser yyparser;
-
- private ParserVal yylval;
-
- private mybison m_mybison;
-
- public fake get_fake()
- {
- return m_f;
- }
-
- public void set_fake(fake f)
- {
- m_f = f;
- }
-
- public void set_mybison(mybison mb)
- {
- m_mybison = mb;
- }
-
- public mybison get_mybison()
- {
- return m_mybison;
- }
-
- public false, iff there was new input.
- *
- * @exception java.io.IOException if any I/O-Error occurs
- */
- private boolean zzRefill() throws java.io.IOException {
-
- /* first: make room (if you can) */
- if (zzStartRead > 0) {
- zzEndRead += zzFinalHighSurrogate;
- zzFinalHighSurrogate = 0;
- System.arraycopy(zzBuffer, zzStartRead,
- zzBuffer, 0,
- zzEndRead-zzStartRead);
-
- /* translate stored positions */
- zzEndRead-= zzStartRead;
- zzCurrentPos-= zzStartRead;
- zzMarkedPos-= zzStartRead;
- zzStartRead = 0;
- }
-
- /* is the buffer big enough? */
- if (zzCurrentPos >= zzBuffer.length - zzFinalHighSurrogate) {
- /* if not: blow it up */
- char newBuffer[] = new char[zzBuffer.length*2];
- System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
- zzBuffer = newBuffer;
- zzEndRead += zzFinalHighSurrogate;
- zzFinalHighSurrogate = 0;
- }
-
- /* fill the buffer with new input */
- int requested = zzBuffer.length - zzEndRead;
- int numRead = zzReader.read(zzBuffer, zzEndRead, requested);
-
- /* not supposed to occur according to specification of java.io.Reader */
- if (numRead == 0) {
- throw new java.io.IOException("Reader returned 0 characters. See JFlex examples for workaround.");
- }
- if (numRead > 0) {
- zzEndRead += numRead;
- /* If numRead == requested, we might have requested to few chars to
- encode a full Unicode character. We assume that a Reader would
- otherwise never return half characters. */
- if (numRead == requested) {
- if (Character.isHighSurrogate(zzBuffer[zzEndRead - 1])) {
- --zzEndRead;
- zzFinalHighSurrogate = 1;
- }
- }
- /* potentially more input available */
- return false;
- }
-
- /* numRead < 0 ==> end of stream */
- return true;
- }
-
-
- /**
- * Closes the input stream.
- */
- public final void yyclose() throws java.io.IOException {
- zzAtEOF = true; /* indicate end of file */
- zzEndRead = zzStartRead; /* invalidate buffer */
-
- if (zzReader != null)
- zzReader.close();
- }
-
-
- /**
- * Resets the scanner to read from a new input stream.
- * Does not close the old reader.
- *
- * All internal variables are reset, the old input stream
- * cannot be reused (internal buffer is discarded and lost).
- * Lexical state is set to ZZ_INITIAL.
- *
- * Internal scan buffer is resized down to its initial length, if it has grown.
- *
- * @param reader the new input stream
- */
- public final void yyreset(java.io.Reader reader) {
- zzReader = reader;
- zzAtBOL = true;
- zzAtEOF = false;
- zzEOFDone = false;
- zzEndRead = zzStartRead = 0;
- zzCurrentPos = zzMarkedPos = 0;
- zzFinalHighSurrogate = 0;
- yyline = yychar = yycolumn = 0;
- zzLexicalState = YYINITIAL;
- if (zzBuffer.length > ZZ_BUFFERSIZE)
- zzBuffer = new char[ZZ_BUFFERSIZE];
- }
-
-
- /**
- * Returns the current lexical state.
- */
- public final int yystate() {
- return zzLexicalState;
- }
-
-
- /**
- * Enters a new lexical state
- *
- * @param newState the new lexical state
- */
- public final void yybegin(int newState) {
- zzLexicalState = newState;
- }
-
-
- /**
- * Returns the text matched by the current regular expression.
- */
- public final String yytext() {
- return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
- }
-
-
- /**
- * Returns the character at position pos from the
- * matched text.
- *
- * It is equivalent to yytext().charAt(pos), but faster
- *
- * @param pos the position of the character to fetch.
- * A value from 0 to yylength()-1.
- *
- * @return the character at position pos
- */
- public final char yycharat(int pos) {
- return zzBuffer[zzStartRead+pos];
- }
-
-
- /**
- * Returns the length of the matched text region.
- */
- public final int yylength() {
- return zzMarkedPos-zzStartRead;
- }
-
-
- /**
- * Reports an error that occured while scanning.
- *
- * In a wellformed scanner (no or only correct usage of
- * yypushback(int) and a match-all fallback rule) this method
- * will only be called with things that "Can't Possibly Happen".
- * If this method is called, something is seriously wrong
- * (e.g. a JFlex bug producing a faulty scanner etc.).
- *
- * Usual syntax/scanner level error handling should be done
- * in error fallback rules.
- *
- * @param errorCode the code of the errormessage to display
- */
- private void zzScanError(int errorCode) {
- String message;
- try {
- message = ZZ_ERROR_MSG[errorCode];
- }
- catch (ArrayIndexOutOfBoundsException e) {
- message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
- }
-
- throw new Error(message);
- }
-
-
- /**
- * Pushes the specified amount of characters back into the input stream.
- *
- * They will be read again by then next call of the scanning method
- *
- * @param number the number of characters to be read again.
- * This number must not be greater than yylength()!
- */
- public void yypushback(int number) {
- if ( number > yylength() )
- zzScanError(ZZ_PUSHBACK_2BIG);
-
- zzMarkedPos -= number;
- }
-
-
- /**
- * Contains user EOF-code, which will be executed exactly once,
- * when the end of file is reached
- */
- private void zzDoEOF() throws java.io.IOException {
- if (!zzEOFDone) {
- zzEOFDone = true;
- yyclose();
- }
- }
-
-
- /**
- * Resumes scanning until the next regular expression is matched,
- * the end of input is encountered or an I/O-Error occurs.
- *
- * @return the next token
- * @exception java.io.IOException if any I/O-Error occurs
- */
- public int yylex() throws java.io.IOException {
- int zzInput;
- int zzAction;
-
- // cached fields:
- int zzCurrentPosL;
- int zzMarkedPosL;
- int zzEndReadL = zzEndRead;
- char [] zzBufferL = zzBuffer;
- char [] zzCMapL = ZZ_CMAP;
-
- int [] zzTransL = ZZ_TRANS;
- int [] zzRowMapL = ZZ_ROWMAP;
- int [] zzAttrL = ZZ_ATTRIBUTE;
-
- while (true) {
- zzMarkedPosL = zzMarkedPos;
-
- boolean zzR = false;
- int zzCh;
- int zzCharCount;
- for (zzCurrentPosL = zzStartRead ;
- zzCurrentPosL < zzMarkedPosL ;
- zzCurrentPosL += zzCharCount ) {
- zzCh = Character.codePointAt(zzBufferL, zzCurrentPosL, zzMarkedPosL);
- zzCharCount = Character.charCount(zzCh);
- switch (zzCh) {
- case '\u000B':
- case '\u000C':
- case '\u0085':
- case '\u2028':
- case '\u2029':
- yyline++;
- yycolumn = 0;
- zzR = false;
- break;
- case '\r':
- yyline++;
- yycolumn = 0;
- zzR = true;
- break;
- case '\n':
- if (zzR)
- zzR = false;
- else {
- yyline++;
- yycolumn = 0;
- }
- break;
- default:
- zzR = false;
- yycolumn += zzCharCount;
- }
- }
-
- if (zzR) {
- // peek one character ahead if it is \n (if we have counted one line too much)
- boolean zzPeek;
- if (zzMarkedPosL < zzEndReadL)
- zzPeek = zzBufferL[zzMarkedPosL] == '\n';
- else if (zzAtEOF)
- zzPeek = false;
- else {
- boolean eof = zzRefill();
- zzEndReadL = zzEndRead;
- zzMarkedPosL = zzMarkedPos;
- zzBufferL = zzBuffer;
- if (eof)
- zzPeek = false;
- else
- zzPeek = zzBufferL[zzMarkedPosL] == '\n';
- }
- if (zzPeek) yyline--;
- }
- zzAction = -1;
-
- zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
-
- zzState = ZZ_LEXSTATE[zzLexicalState];
-
- // set up zzAction for empty match case:
- int zzAttributes = zzAttrL[zzState];
- if ( (zzAttributes & 1) == 1 ) {
- zzAction = zzState;
- }
-
-
- zzForAction: {
- while (true) {
-
- if (zzCurrentPosL < zzEndReadL) {
- zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL);
- zzCurrentPosL += Character.charCount(zzInput);
- }
- else if (zzAtEOF) {
- zzInput = YYEOF;
- break zzForAction;
- }
- else {
- // store back cached positions
- zzCurrentPos = zzCurrentPosL;
- zzMarkedPos = zzMarkedPosL;
- boolean eof = zzRefill();
- // get translated positions and possibly new buffer
- zzCurrentPosL = zzCurrentPos;
- zzMarkedPosL = zzMarkedPos;
- zzBufferL = zzBuffer;
- zzEndReadL = zzEndRead;
- if (eof) {
- zzInput = YYEOF;
- break zzForAction;
- }
- else {
- zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL, zzEndReadL);
- zzCurrentPosL += Character.charCount(zzInput);
- }
- }
- int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
- if (zzNext == -1) break zzForAction;
- zzState = zzNext;
-
- zzAttributes = zzAttrL[zzState];
- if ( (zzAttributes & 1) == 1 ) {
- zzAction = zzState;
- zzMarkedPosL = zzCurrentPosL;
- if ( (zzAttributes & 8) == 8 ) break zzForAction;
- }
-
- }
- }
-
- // store back cached position
- zzMarkedPos = zzMarkedPosL;
-
- if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
- zzAtEOF = true;
- zzDoEOF();
- switch (zzLexicalState) {
- case YYINITIAL: {
- return YYParser.EOF;
- }
- case 178: break;
- default:
- {
- return YYParser.EOF;
- }
- }
- }
- else {
- switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
- case 1:
- {
- }
- case 78: break;
- case 2:
- { return YYParser.MINUS;
- }
- case 79: break;
- case 3:
- { yybegin(STRING); string.setLength(0);
- }
- case 80: break;
- case 4:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.IDENTIFIER;
- }
- case 81: break;
- case 5:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.NUMBER;
- }
- case 82: break;
- case 6:
- { return YYParser.MORE;
- }
- case 83: break;
- case 7:
- { return YYParser.DIVIDE_MOD;
- }
- case 84: break;
- case 8:
- { return YYParser.ARG_SPLITTER;
- }
- case 85: break;
- case 9:
- { return YYParser.PLUS;
- }
- case 86: break;
- case 10:
- { return YYParser.DIVIDE;
- }
- case 87: break;
- case 11:
- { return YYParser.MULTIPLY;
- }
- case 88: break;
- case 12:
- { return YYParser.COLON;
- }
- case 89: break;
- case 13:
- { return YYParser.ASSIGN;
- }
- case 90: break;
- case 14:
- { return YYParser.LESS;
- }
- case 91: break;
- case 15:
- { return YYParser.OPEN_BRACKET;
- }
- case 92: break;
- case 16:
- { return YYParser.CLOSE_BRACKET;
- }
- case 93: break;
- case 17:
- { return YYParser.OPEN_SQUARE_BRACKET;
- }
- case 94: break;
- case 18:
- { return YYParser.CLOSE_SQUARE_BRACKET;
- }
- case 95: break;
- case 19:
- { return YYParser.OPEN_BIG_BRACKET;
- }
- case 96: break;
- case 20:
- { return YYParser.CLOSE_BIG_BRACKET;
- }
- case 97: break;
- case 21:
- { string.append( yytext() );
- }
- case 98: break;
- case 22:
- { yybegin(YYINITIAL);
- yylval = new ParserVal(string.toString());
- yylval.ival = yyline + 1;
- return YYParser.STRING_DEFINITION;
- }
- case 99: break;
- case 23:
- { /* ignore */
- }
- case 100: break;
- case 24:
- { return YYParser.RIGHT_POINTER;
- }
- case 101: break;
- case 25:
- { return YYParser.MINUS_ASSIGN;
- }
- case 102: break;
- case 26:
- { return YYParser.IF;
- }
- case 103: break;
- case 27:
- { return YYParser.IS;
- }
- case 104: break;
- case 28:
- { return YYParser.OR;
- }
- case 105: break;
- case 29:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.FKUUID;
- }
- case 106: break;
- case 30:
- { return YYParser.STRING_CAT;
- }
- case 107: break;
- case 31:
- { return YYParser.MORE_OR_EQUAL;
- }
- case 108: break;
- case 32:
- { return YYParser.DIVIDE_MOD_ASSIGN;
- }
- case 109: break;
- case 33:
- { return YYParser.INC;
- }
- case 110: break;
- case 34:
- { return YYParser.PLUS_ASSIGN;
- }
- case 111: break;
- case 35:
- { return YYParser.DIVIDE_ASSIGN;
- }
- case 112: break;
- case 36:
- { return YYParser.MULTIPLY_ASSIGN;
- }
- case 113: break;
- case 37:
- { return YYParser.NEW_ASSIGN;
- }
- case 114: break;
- case 38:
- { return YYParser.EQUAL;
- }
- case 115: break;
- case 39:
- { return YYParser.LESS_OR_EQUAL;
- }
- case 116: break;
- case 40:
- { return YYParser.NOT_EQUAL;
- }
- case 117: break;
- case 41:
- { string.append( '\\' );
- }
- case 118: break;
- case 42:
- { string.append( '\"' );
- }
- case 119: break;
- case 43:
- { string.append( '\r' );
- }
- case 120: break;
- case 44:
- { string.append( '\t' );
- }
- case 121: break;
- case 45:
- { string.append( '\n' );
- }
- case 122: break;
- case 46:
- { string.append( '\b' );
- }
- case 123: break;
- case 47:
- { string.append( '\f' );
- }
- case 124: break;
- case 48:
- { string.append( '\'' );
- }
- case 125: break;
- case 49:
- { return YYParser.VAR_BEGIN;
- }
- case 126: break;
- case 50:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.IDENTIFIER_DOT;
- }
- case 127: break;
- case 51:
- { return YYParser.AND;
- }
- case 128: break;
- case 52:
- { return YYParser.END;
- }
- case 129: break;
- case 53:
- { return YYParser.NOT;
- }
- case 130: break;
- case 54:
- { return YYParser.FOR;
- }
- case 131: break;
- case 55:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.FKFLOAT;
- }
- case 132: break;
- case 56:
- { yylval = new ParserVal(yytext());
- yylval.ival = yyline + 1;
- return YYParser.IDENTIFIER_POINTER;
- }
- case 133: break;
- case 57:
- { return YYParser.ELSE;
- }
- case 134: break;
- case 58:
- { return YYParser.FTRUE;
- }
- case 135: break;
- case 59:
- { return YYParser.THEN;
- }
- case 136: break;
- case 60:
- { return YYParser.NULL;
- }
- case 137: break;
- case 61:
- { return YYParser.FAKE;
- }
- case 138: break;
- case 62:
- { return YYParser.FUNC;
- }
- case 139: break;
- case 63:
- { return YYParser.CASE;
- }
- case 140: break;
- case 64:
- { return YYParser.BREAK;
- }
- case 141: break;
- case 65:
- { return YYParser.FFALSE;
- }
- case 142: break;
- case 66:
- { return YYParser.FCONST;
- }
- case 143: break;
- case 67:
- { return YYParser.WHILE;
- }
- case 144: break;
- case 68:
- { return YYParser.SLEEP;
- }
- case 145: break;
- case 69:
- { return YYParser.YIELD;
- }
- case 146: break;
- case 70:
- { return YYParser.RETURN;
- }
- case 147: break;
- case 71:
- { return YYParser.ELSEIF;
- }
- case 148: break;
- case 72:
- { return YYParser.STRUCT;
- }
- case 149: break;
- case 73:
- { return YYParser.SWITCH;
- }
- case 150: break;
- case 74:
- { return YYParser.INCLUDE;
- }
- case 151: break;
- case 75:
- { return YYParser.DEFAULT;
- }
- case 152: break;
- case 76:
- { return YYParser.PACKAGE;
- }
- case 153: break;
- case 77:
- { return YYParser.CONTINUE;
- }
- case 154: break;
- default:
- zzScanError(ZZ_NO_MATCH);
- }
- }
- }
- }
-
-
-}
diff --git a/src/fakescript/bifunc.java b/src/fakescript/bifunc.java
deleted file mode 100644
index f8c42c3..0000000
--- a/src/fakescript/bifunc.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package fakescript;
-
-import java.lang.reflect.Method;
-
-class bifunc
-{
- Method m_m;
-
- public void call(fake f, interpreter inter) throws Exception
- {
- m_m.invoke(null, f, inter);
- }
-}
diff --git a/src/fakescript/binary.java b/src/fakescript/binary.java
deleted file mode 100644
index fbf7c4e..0000000
--- a/src/fakescript/binary.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package fakescript;
-
-import java.util.Map;
-
-class binary
-{
- private fake m_f;
-
- public binary(fake f)
- {
- m_f = f;
- }
-
- public String dump()
- {
- String ret = "";
- for (Map.Entry
- * 所有接口在fake中执行
- *
- * @param config
- * 具体的参数
- * @return fake对象
- */
- public static fake newfake(fkconfig config)
- {
- fake f = new fake();
- if (config != null)
- {
- f.cfg = config;
- }
- return f;
- }
-
- /**
- * 复制fake对象
- *
- * 所有接口在fake中执行
- *
- * @param f
- * fake对象
- * @return fake对象
- */
- public static fake clone(fake f)
- {
- return f.clonef();
- }
-
- /**
- * 绑定java函数
- *
- * 绑定标记fakescript的函数
- *
- * @param f
- * 上下文环境
- *
- * @param packagename
- * 包的名字
- *
- * @return 无
- */
- public static void reg(fake f, String packagename)
- {
- List
- * 绑定所有的函数
- *
- * @param f
- * 上下文环境
- *
- * @param packagename
- * 包的名字
- *
- * @return 无
- */
- public static void regall(fake f, String packagename)
- {
- List
- * 绑定类所有的函数
- *
- * @param f
- * 上下文环境
- *
- * @param packagename
- * 包的名字
- *
- * @return 无
- */
- public static void regclass(fake f, Class> c)
- {
- Class>[] cs = c.getDeclaredClasses();
- for (Class> cc : cs)
- {
- regclass(f, cc);
- }
-
- Method[] ms = c.getMethods();
- for (Method m : ms)
- {
- String name = m.getName();
- if (m.isAnnotationPresent(fakescript.class))
- {
- fakescript fn = (fakescript) m.getAnnotation(fakescript.class);
- if (fn != null && !fn.name().isEmpty())
- {
- name = fn.name();
- }
- }
-
- reg_method(f, name, c, m);
- }
- }
-
- /**
- * 设置回调函数
- *
- * 打印函数
- *
- * @param f
- * 上下文环境
- *
- * @param cb
- * 回调类
- *
- * @return 无
- */
- public static void set_callback(fake f, callback cb)
- {
- f.cb = cb;
- }
-
- /**
- * 解析文件
- *
- * 编译成字节码
- *
- * @param f
- * 上下文环境
- *
- * @param filename
- * 文件名
- *
- * @return 无
- */
- public static boolean parse(fake f, String filename)
- {
- f.pa.clear();
- return f.pa.parse(filename);
- }
-
- /**
- * 解析代码
- *
- * 编译成字节码
- *
- * @param f
- * 上下文环境
- *
- * @param str
- * 文件名
- *
- * @return 无
- */
- public static boolean parsestr(fake f, String str)
- {
- f.pa.clear();
- return f.pa.parsestr(str);
- }
-
- /**
- * 执行脚本
- *
- * 结果通过Object返回,注意内部数值都是用double,所以转换时需要注意下
- *
- * @param f
- * 上下文环境
- *
- * @param func
- * 函数名
- *
- * @param args
- * 参数
- *
- * @return 无
- */
- public static Object run(fake f, String func, Object... args)
- {
- psclear(f);
- for (Object arg : args)
- {
- pspush(f, arg);
- }
- runps(f, func);
- return pspop(f);
- }
-
- public static Object debugrun(fake f, String func, Object... args)
- {
- psclear(f);
- for (Object arg : args)
- {
- pspush(f, arg);
- }
- openstepmod(f);
- rundebugps(f, func);
- closestepmod(f);
- return pspop(f);
- }
-
- public static void openstepmod(fake f)
- {
- f.rn.set_stepmod(true);
- }
-
- public static void closestepmod(fake f)
- {
- f.rn.set_stepmod(false);
- }
-
- /**
- * 获取当前文件
- *
- * 如test.A类的aaa函数,他的实际函数名是test.Aaaa
- *
- * @param f
- * 上下文环境
- *
- * @param name
- * 函数名
- *
- * @return 无
- */
- public static boolean isfunc(fake f, String name)
- {
- variant funcv = new variant();
- funcv.set_string(name);
- return f.fm.get_func(funcv) != null;
- }
-
- /**
- * 打开性能监控
- *
+ * luaL_where(L, 1); lua_pushvfstring(L, fmt, ap); lua_concat(L, 2);
+ * lua_error(L);
+ *
+ * 三件事都得在 C 侧做才对:变参只能在 C 里读;{@code lua_pushvfstring}
+ * 的动词表和 printf 不是一套(见 {@code pushvfshim});
+ * {@code lua_error} 抛出去得走 {@code luashim_throw} 的 longjmp,
+ * 不能让 Java 异常跨过 JNI 帧。
+ *
+ *
+ */
+ static long luaL_execresult(long L, long stat, long en, long what) {
+ /*
+ * {@code l_inspectstat} 已经在 C 侧用本平台的 wait 宏做完了,这里拿到
+ * 的是已经换算过的 {@code stat} 与 {@code what}
+ * ({@code "exit"} / {@code "signal"})。
+ */
+ LuaState S = st(L);
+ LuaString w = LuaString.of(NativeBridge.bytesAt(what));
+ int st = (int) stat;
+ if (st == 0 && w.equals(LuaString.latin1("exit"))) {
+ pushVal(S, LuaValue.ofBoolean(true));
+ } else {
+ LuaAux.pushFail(S);
+ }
+ pushVal(S, LuaValue.ofString(w));
+ pushVal(S, LuaValue.ofInteger(st));
+ return 3;
+ }
+
+ static long luaL_fileresult(long L, long stat, long fname, long en) {
+ LuaState S = st(L);
+ boolean ok = stat != 0;
+ LuaString fn = fname == 0 ? null : LuaString.of(NativeBridge.bytesAt(fname));
+ /*
+ * 返回值个数随 {@code stat} 变:成功只压一个 {@code true}、
+ * 返回 1;失败压 {@code nil, msg, errno}、返回 3。
+ * 早先这里恒返回 3,成功那一支就把栈顶上面两格的陈值当成
+ * 结果交出去了。参考实现实测 {@code luaL_fileresult(L, 1, NULL)}
+ * 只给一个 {@code true}。
+ */
+ LuaAux.fileResult(S, ok, fn, (int) en);
+ return ok ? 1 : 3;
+ }
+
+ static long luaL_getmetafield(long L, long obj, long e) {
+ LuaState S = st(L);
+ if (lua_getmetatable(L, obj) == 0) {
+ return Lua.TNIL;
+ }
+ long k = NativeBridge.strToNative(NativeBridge.bytesAt(e));
+ lua_getfield(L, -1, k);
+ if (S.stack[S.top - 1].isNil()) {
+ S.top -= 2;
+ return Lua.TNIL;
+ }
+ // 去掉元表,只留字段值
+ S.stack[S.top - 2].set(S.stack[S.top - 1]);
+ S.top--;
+ return S.stack[S.top - 1].baseType();
+ }
+
+ static long luaL_getsubtable(long L, long idx, long fname) {
+ LuaState S = st(L);
+ long k = NativeBridge.strToNative(NativeBridge.bytesAt(fname));
+ lua_getfield(L, idx, k);
+ if (S.stack[S.top - 1].isTable()) {
+ return 1;
+ }
+ S.top--;
+ int i = (int) (int) lua_absindex(L, idx);
+ lua_createtable(L, 0, 0);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, i, k);
+ return 0;
+ }
+
+ static long luaL_gsub(long L, long s, long p, long r) {
+ LuaState S = st(L);
+ pushVal(S, LuaValue.ofString(LuaString.of(NativeBridge.bytesAt(s))));
+ luaL_addgsub(S.stack[S.top - 1].hashCode() == 0 ? 0 : 0, s, p, r);
+ return 0;
+ }
+
+ static long luaL_len(long L, long idx) {
+ LuaState S = st(L);
+ lua_len(L, idx);
+ LuaValue v = S.stack[--S.top];
+ if (!v.isInteger()) {
+ throw S.libError("object length is not an integer");
+ }
+ return v.asInteger();
+ }
+
+ static long luaL_loadbufferx(long L, long buff, long sz, long name, long mode) {
+ byte[] src = (buff == 0 || sz <= 0) ? new byte[0]
+ : NativeBridge.bytesAtN(buff, (int) sz);
+ byte[] cn = (name == 0) ? null : NativeBridge.bytesAt(name);
+ byte[] md = (mode == 0) ? null : NativeBridge.bytesAt(mode);
+ return loadBytes(L, src, cn,
+ (md == null) ? null : new String(md, java.nio.charset.StandardCharsets.ISO_8859_1));
+ }
+
+ @Generated
+ static long luaL_loadfilex(long L, long filename, long mode) {
+ throw new IllegalStateException("luaL_loadfilex 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static long luaL_loadstring(long L, long s) {
+ /*
+ * {@code lauxlib.c: luaL_loadstring} 就是
+ * {@code luaL_loadbuffer(L, s, strlen(s), s)} —— 源串自己就是 chunk
+ * 名,所以报错长成 {@code [string "x = "]:1: ...}。传 null 会把
+ * 这段前缀整个丢掉。
+ */
+ byte[] b = NativeBridge.bytesAt(s);
+ /*
+ * chunk 名就是源码本身({@code luaL_loadstring} 里的
+ * {@code luaL_loadbufferx(L, s, strlen(s), s, NULL)}),
+ * mode 传 NULL = 全部放行。
+ */
+ return loadBytes(L, b, b, null);
+ }
+
+ static long luaL_newmetatable(long L, long tname) {
+ LuaState S = st(L);
+ long k = NativeBridge.strToNative(NativeBridge.bytesAt(tname));
+ lua_getfield(L, REGISTRY, k);
+ if (!S.stack[S.top - 1].isNil()) {
+ return 0;
+ }
+ S.top--;
+ lua_createtable(L, 0, 2);
+ lua_pushstring(L, k);
+ lua_setfield(L, -2, strToNative("__name"));
+ lua_pushvalue(L, -1);
+ lua_setfield(L, REGISTRY, k);
+ return 1;
+ }
+
+ static long luaL_newstate() {
+ LuaState s = new LuaState();
+ LuaLibs.openAll(s);
+ return NativeBridge.handle(s);
+ }
+
+ static long luaL_optinteger(long L, long arg, long def) {
+ LuaState S = st(L);
+ if (S.isNoneArg((int) arg) || S.arg1((int) arg).isNil()) {
+ return def;
+ }
+ return luaL_checkinteger(L, arg);
+ }
+
+ static long luaL_optlstring(long L, long arg, long def, long l) {
+ LuaState S = st(L);
+ if (S.isNoneArg((int) arg) || S.arg1((int) arg).isNil()) {
+ /*
+ * {@code def == NULL} 必须原样交回 NULL(0),不能退化成
+ * {@code ""}。差别是可观测的:{@code luaL_optstring(L, n, NULL)}
+ * 最典型的用处就是给 {@code lua_load} / {@code luaL_loadfilex} 传
+ * {@code mode},而 {@code checkmode} 对 {@code NULL} 一律放行、
+ * 对 {@code ""} 却会报
+ * {@code attempt to load a text chunk (mode is '')}。
+ * 长度参数同步写 0({@code lauxlib.c} 里 {@code def} 为 NULL 时
+ * {@code *len} 也是 0)。
+ */
+ if (def == 0) {
+ if (l != 0) {
+ NativeBridge.put64(l, 0);
+ }
+ return 0;
+ }
+ byte[] b = NativeBridge.bytesAt(def);
+ if (l != 0) {
+ NativeBridge.put64(l, b.length);
+ }
+ return NativeBridge.strToNative(b);
+ }
+ return luaL_checklstring(L, arg, l);
+ }
+
+ static double luaL_optnumber(long L, long arg, double def) {
+ LuaState S = st(L);
+ if (S.isNoneArg((int) arg) || S.arg1((int) arg).isNil()) {
+ return def;
+ }
+ return luaL_checknumber(L, arg);
+ }
+
+ @Generated
+ static long luaL_prepbuffsize(long B, long sz) {
+ throw new IllegalStateException("luaL_prepbuffsize 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ static void luaL_pushresult(long B) {
+ throw new IllegalStateException("luaL_pushresult 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ @Generated
+ static void luaL_pushresultsize(long B, long sz) {
+ throw new IllegalStateException("luaL_pushresultsize 在 luashim.c 里用别的 lua_* 拼出来,不进 Java");
+ }
+
+ static long luaL_ref(long L, long t) {
+ LuaState S = st(L);
+ if (S.stack[S.top - 1].isNil()) {
+ S.top--;
+ return -1; // LUA_REFNIL
+ }
+ int ti = (int) lua_absindex(L, t);
+ /*
+ * {@code lauxlib.c: #define freelist (LUA_RIDX_LAST + 1)} —— 空闲链表
+ * 的头存在下标 3,不是 0。
+ *
+ *
+ */
+ static void luaL_where(long L, long lvl) {
+ LuaState S = st(L);
+ LuaString where = LuaString.latin1("");
+ DebugLib.LuaDebug d = new DebugLib.LuaDebug();
+ if (DebugLib.getStack(S, (int) lvl, d)) {
+ DebugLib.getInfo(S, "Sl", d);
+ if (d.currentline > 0) {
+ where = d.shortSrc.concat(
+ LuaString.latin1(":" + d.currentline + ": "));
+ }
+ }
+ pushVal(S, LuaValue.ofString(where));
+ }
+
+ static long lua_absindex(long L, long idx) {
+ LuaState S = st(L);
+ int i = (int) idx;
+ return (i > 0 || i <= REGISTRY) ? i : (S.top - S.ci.func) + i;
+ }
+
+ static void lua_arith(long L, long op) {
+ /*
+ * {@code lapi.c: lua_arith}:一元运算({@code UNM} / {@code BNOT})
+ * 先在栈顶复制一份操作数,之后就与二元同形——结果一律落在
+ * {@code top - 2},再把 {@code top} 收一格。
+ *
+ *
+ */
+final class Arith {
+
+ private Arith() {
+ }
+
+ /* ORDER TM / ORDER OP —— 与 lua.h 的 LUA_OP* 一致 */
+ static final int OP_ADD = 0;
+ static final int OP_SUB = 1;
+ static final int OP_MUL = 2;
+ static final int OP_MOD = 3;
+ static final int OP_POW = 4;
+ static final int OP_DIV = 5;
+ static final int OP_IDIV = 6;
+ static final int OP_BAND = 7;
+ static final int OP_BOR = 8;
+ static final int OP_BXOR = 9;
+ static final int OP_SHL = 10;
+ static final int OP_SHR = 11;
+ static final int OP_UNM = 12;
+ static final int OP_BNOT = 13;
+
+ /** 整数的位数 */
+ private static final int NBITS = 64;
+
+ /**
+ * {@code luaO_rawarith}:不经元方法的原始算术。
+ *
+ *
+ */
+ private static int select(LuaState L) {
+ int n = L.argCount();
+ LuaValue first = L.arg(0);
+ if (first.isString() && first.asString().length() > 0
+ && first.asString().byteAt(0) == '#') {
+ L.push(LuaValue.ofInteger(n - 1));
+ return 1;
+ }
+ long i = checkInteger(L, 0);
+ if (i < 0) {
+ i = n + i;
+ } else if (i > n) {
+ i = n;
+ }
+ LuaAux.argCheck(L, i >= 1, 1, "index out of range");
+ int start = L.ci.func + 1 + (int) i; // 绝对下标:跳过前 i 个参数
+ int count = n - (int) i;
+ for (int j = 0; j < count; j++) {
+ L.push(L.stack[start + j]);
+ }
+ return count;
+ }
+
+ /**
+ * {@code luaB_error}。
+ *
+ *
+ * {@link LuaState#closeErrPending} 非空就说明是后者(错误对象和
+ * 「还有变量没关完」是同一个条件,见那里的注释)。
+ */
+ myCi.recst = (L.closeErrPending != null) ? LuaState.LUA_ERRRUN : LuaState.LUA_OK;
+ throw y;
+ }
+ }
+
+ /**
+ * {@code lbaselib.c: finishpcall} —— pcall 的续体。
+ *
+ * collectgarbage("setpause") --> 200 (并把 pause 设成 0)
+ * collectgarbage("setpause", 100) --> 0
+ * collectgarbage("setpause") --> 100
+ *
+ * 本实现以前写的是 {@code if (v != 0)},三条全错。
+ */
+ LuaState m = L.mainThread;
+ int old = (o == 5) ? m.gcPause() : m.gcStepMul();
+ int v = (int) LuaAux.optInteger(L, 2, 0);
+ if (o == 5) {
+ m.setGcPause(v);
+ } else {
+ m.setGcStepMul(v);
+ }
+ L.push(LuaValue.ofInteger(old));
+ return 1;
+ }
+ case 7: // "isrunning"
+ L.push(LuaValue.ofBoolean(L.mainThread.gcRunning));
+ return 1;
+ case 8: // "generational"
+ case 9: { // "incremental"
+ /*
+ * {@code lbaselib.c: pushmode}:这两个选项返回的是切换前的
+ * 模式,而且是字符串({@code "incremental"} /
+ * {@code "generational"}),不是 {@code LUA_GCINC} 那个整数。
+ * 上游 {@code gc.lua} 开头四行就是照着字符串断言的,返回整数的话
+ * 它连「testing incremental garbage collection」都打不完。
+ */
+ LuaState m = L.mainThread;
+ /*
+ * 判据是 {@code isdecGCmodegen(g)}({@code gckind == KGC_GEN ||
+ * lastatomic != 0})而不是 {@code gckind} 本身 —— 见
+ * {@link LuaState#gcLastAtomic}。两种切换都会把 {@code lastatomic}
+ * 清 0,所以切过一次之后就只看 {@code gckind} 了。
+ */
+ boolean prevGen = (m.gcKind == LuaState.LUA_GCGEN) || m.gcLastAtomic;
+ m.gcKind = (o == 8) ? LuaState.LUA_GCGEN : LuaState.LUA_GCINC;
+ m.gcLastAtomic = false;
+ L.push(LuaValue.ofString(LuaString.latin1(
+ prevGen ? "generational" : "incremental")));
+ return 1;
+ }
+ case 0: // "stop"
+ L.mainThread.gcRunning = false;
+ L.push(LuaValue.ofInteger(0));
+ return 1;
+ case 1: // "restart"
+ L.mainThread.gcRunning = true;
+ L.push(LuaValue.ofInteger(0));
+ return 1;
+ default:
+ break; // "collect"
+ }
+ doCollect(L);
+ // GC 后更新内存使用量,这样后续的 "count" 能拿到准确的值
+ updateMemoryUsage();
+ L.push(LuaValue.ofInteger(0));
+ return 1;
+ }
+
+ /**
+ * 一次「完整收集」在本实现里的等价物:清栈死区 → {@code System.gc()} →
+ * 催终结 → 排空待终结队列 → 递增收集轮次。
+ *
+ * loadfile(dir) -> nil, "cannot read /tmp/fkdir/adir.lua: Is a directory"
+ *
+ */
+ boolean isDir = !path.isEmpty() && new java.io.File(path).isDirectory();
+ int en = path.isEmpty() ? 2
+ : isDir ? 21
+ : LuaAux.errnoOf((e instanceof java.io.IOException)
+ ? (java.io.IOException) e : null);
+ LuaAux.pushFail(L);
+ L.push(LuaValue.ofString(LuaString.latin1(
+ (isDir ? "cannot read " : "cannot open ") + path + ": "
+ + LuaAux.strError(en))));
+ return 2;
+ }
+ /*
+ * luaL_loadfilex 的 chunk 名是 "@" + 文件名。
+ * 加载期错误打印时 luaO_chunkid 会把它剥掉。
+ */
+ return loadAux(L, loadBuffer(L, src, LuaString.utf8("@" + path).bytes, mode), env);
+ }
+
+ /**
+ * {@code lbaselib.c: dofilecont}:yield 恢复后,结果已经在栈上,只要报出个数。
+ *
+ *
+ *
+ *
+ * 这是「同一个 C 函数两份实现」的老毛病,和 {@code Vm.formatG} /
+ * {@code StringLib.generalDigits} 那次是同一类。
+ */
+ static long checkInteger(LuaState L, int i) {
+ return LuaAux.checkInteger(L, i + 1);
+ }
+
+ /** 把 {@link LuaValue} 变成函数值(供外部注册额外库函数) */
+ public static LuaValue functionValue(LuaFunction f) {
+ return LuaValue.ofFunction(f);
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/CApi.java b/src/main/java/com/github/esrrhs/javalua/CApi.java
new file mode 100644
index 0000000..dabfa93
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/CApi.java
@@ -0,0 +1,411 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * C API 的分发表 —— 对应 {@code lapi.c} + {@code lauxlib.c}。
+ *
+ *
+ */
+final class CApi {
+
+ private CApi() {
+ }
+
+ static RuntimeException unimplemented(String what) {
+ return new UnsupportedOperationException("C API 未实现: " + what);
+ }
+
+ /** C 侧 stub 统一走这里进来 */
+ static long dispatch(int id, long a0, long a1, long a2,
+ long a3, long a4, long a5, double d0) {
+ switch (id) {
+ case 0: /* luaL_addgsub */
+ Api.luaL_addgsub(a0, a1, a2, a3);
+ return 0;
+ case 1: /* luaL_addlstring */
+ Api.luaL_addlstring(a0, a1, a2);
+ return 0;
+ case 2: /* luaL_addstring */
+ Api.luaL_addstring(a0, a1);
+ return 0;
+ case 3: /* luaL_addvalue */
+ Api.luaL_addvalue(a0);
+ return 0;
+ case 4: /* luaL_argerror */
+ return Api.luaL_argerror(a0, a1, a2);
+ case 5: /* luaL_buffinit */
+ Api.luaL_buffinit(a0, a1);
+ return 0;
+ case 6: /* luaL_buffinitsize */
+ return Api.luaL_buffinitsize(a0, a1, a2);
+ case 7: /* luaL_callmeta */
+ return Api.luaL_callmeta(a0, a1, a2);
+ case 8: /* luaL_checkany */
+ Api.luaL_checkany(a0, a1);
+ return 0;
+ case 9: /* luaL_checkinteger */
+ return Api.luaL_checkinteger(a0, a1);
+ case 10: /* luaL_checklstring */
+ return Api.luaL_checklstring(a0, a1, a2);
+ case 11: /* luaL_checknumber */
+ throw new IllegalStateException("double 返回走 dispatchD");
+ case 12: /* luaL_checkoption */
+ return Api.luaL_checkoption(a0, a1, a2, a3, a4);
+ case 13: /* luaL_checkstack */
+ Api.luaL_checkstack(a0, a1, a2);
+ return 0;
+ case 14: /* luaL_checktype */
+ Api.luaL_checktype(a0, a1, a2);
+ return 0;
+ case 15: /* luaL_checkudata */
+ return Api.luaL_checkudata(a0, a1, a2);
+ case 16: /* luaL_checkversion_ */
+ Api.luaL_checkversion_(a0, d0, a1);
+ return 0;
+ case 17: /* luaL_error */
+ return Api.luaL_error(a0, a1, a2);
+ case 18: /* luaL_execresult */
+ return Api.luaL_execresult(a0, a1, a2, a3);
+ case 19: /* luaL_fileresult */
+ return Api.luaL_fileresult(a0, a1, a2, a3);
+ case 20: /* luaL_getmetafield */
+ return Api.luaL_getmetafield(a0, a1, a2);
+ case 21: /* luaL_getsubtable */
+ return Api.luaL_getsubtable(a0, a1, a2);
+ case 22: /* luaL_gsub */
+ return Api.luaL_gsub(a0, a1, a2, a3);
+ case 23: /* luaL_len */
+ return Api.luaL_len(a0, a1);
+ case 24: /* luaL_loadbufferx */
+ return Api.luaL_loadbufferx(a0, a1, a2, a3, a4);
+ case 25: /* luaL_loadfilex */
+ return Api.luaL_loadfilex(a0, a1, a2);
+ case 26: /* luaL_loadstring */
+ return Api.luaL_loadstring(a0, a1);
+ case 27: /* luaL_newmetatable */
+ return Api.luaL_newmetatable(a0, a1);
+ case 28: /* luaL_newstate */
+ return Api.luaL_newstate();
+ case 29: /* luaL_optinteger */
+ return Api.luaL_optinteger(a0, a1, a2);
+ case 30: /* luaL_optlstring */
+ return Api.luaL_optlstring(a0, a1, a2, a3);
+ case 31: /* luaL_optnumber */
+ throw new IllegalStateException("double 返回走 dispatchD");
+ case 32: /* luaL_prepbuffsize */
+ return Api.luaL_prepbuffsize(a0, a1);
+ case 33: /* luaL_pushresult */
+ Api.luaL_pushresult(a0);
+ return 0;
+ case 34: /* luaL_pushresultsize */
+ Api.luaL_pushresultsize(a0, a1);
+ return 0;
+ case 35: /* luaL_ref */
+ return Api.luaL_ref(a0, a1);
+ case 36: /* luaL_requiref */
+ Api.luaL_requiref(a0, a1, a2, a3);
+ return 0;
+ case 37: /* luaL_setfuncs */
+ Api.luaL_setfuncs(a0, a1, a2);
+ return 0;
+ case 38: /* luaL_setmetatable */
+ Api.luaL_setmetatable(a0, a1);
+ return 0;
+ case 39: /* luaL_testudata */
+ return Api.luaL_testudata(a0, a1, a2);
+ case 40: /* luaL_tolstring */
+ return Api.luaL_tolstring(a0, a1, a2);
+ case 41: /* luaL_traceback */
+ Api.luaL_traceback(a0, a1, a2, a3);
+ return 0;
+ case 42: /* luaL_typeerror */
+ return Api.luaL_typeerror(a0, a1, a2);
+ case 43: /* luaL_unref */
+ Api.luaL_unref(a0, a1, a2);
+ return 0;
+ case 44: /* luaL_where */
+ Api.luaL_where(a0, a1);
+ return 0;
+ case 45: /* lua_absindex */
+ return Api.lua_absindex(a0, a1);
+ case 46: /* lua_arith */
+ Api.lua_arith(a0, a1);
+ return 0;
+ case 47: /* lua_atpanic */
+ return Api.lua_atpanic(a0, a1);
+ case 48: /* lua_callk */
+ Api.lua_callk(a0, a1, a2, a3, a4);
+ return 0;
+ case 49: /* lua_checkstack */
+ return Api.lua_checkstack(a0, a1);
+ case 50: /* lua_close */
+ Api.lua_close(a0);
+ return 0;
+ case 51: /* lua_closeslot */
+ Api.lua_closeslot(a0, a1);
+ return 0;
+ case 52: /* lua_closethread */
+ return Api.lua_closethread(a0, a1);
+ case 53: /* lua_compare */
+ return Api.lua_compare(a0, a1, a2, a3);
+ case 54: /* lua_concat */
+ Api.lua_concat(a0, a1);
+ return 0;
+ case 55: /* lua_copy */
+ Api.lua_copy(a0, a1, a2);
+ return 0;
+ case 56: /* lua_createtable */
+ Api.lua_createtable(a0, a1, a2);
+ return 0;
+ case 57: /* lua_dump */
+ return Api.lua_dump(a0, a1, a2, a3);
+ case 58: /* lua_error */
+ return Api.lua_error(a0);
+ case 59: /* lua_gc */
+ return Api.lua_gc(a0, a1, a2);
+ case 60: /* lua_getallocf */
+ return Api.lua_getallocf(a0, a1);
+ case 61: /* lua_getfield */
+ return Api.lua_getfield(a0, a1, a2);
+ case 62: /* lua_getglobal */
+ return Api.lua_getglobal(a0, a1);
+ case 63: /* lua_gethook */
+ return Api.lua_gethook(a0);
+ case 64: /* lua_gethookcount */
+ return Api.lua_gethookcount(a0);
+ case 65: /* lua_gethookmask */
+ return Api.lua_gethookmask(a0);
+ case 66: /* lua_geti */
+ return Api.lua_geti(a0, a1, a2);
+ case 67: /* lua_getinfo */
+ return Api.lua_getinfo(a0, a1, a2);
+ case 68: /* lua_getiuservalue */
+ return Api.lua_getiuservalue(a0, a1, a2);
+ case 69: /* lua_getlocal */
+ return Api.lua_getlocal(a0, a1, a2);
+ case 70: /* lua_getmetatable */
+ return Api.lua_getmetatable(a0, a1);
+ case 71: /* lua_getstack */
+ return Api.lua_getstack(a0, a1, a2);
+ case 72: /* lua_gettable */
+ return Api.lua_gettable(a0, a1);
+ case 73: /* lua_gettop */
+ return Api.lua_gettop(a0);
+ case 74: /* lua_getupvalue */
+ return Api.lua_getupvalue(a0, a1, a2);
+ case 75: /* lua_iscfunction */
+ return Api.lua_iscfunction(a0, a1);
+ case 76: /* lua_isinteger */
+ return Api.lua_isinteger(a0, a1);
+ case 77: /* lua_isnumber */
+ return Api.lua_isnumber(a0, a1);
+ case 78: /* lua_isstring */
+ return Api.lua_isstring(a0, a1);
+ case 79: /* lua_isuserdata */
+ return Api.lua_isuserdata(a0, a1);
+ case 80: /* lua_isyieldable */
+ return Api.lua_isyieldable(a0);
+ case 81: /* lua_len */
+ Api.lua_len(a0, a1);
+ return 0;
+ case 82: /* lua_load */
+ return Api.lua_load(a0, a1, a2, a3, a4);
+ case 83: /* lua_newstate */
+ return Api.lua_newstate(a0, a1);
+ case 84: /* lua_newthread */
+ return Api.lua_newthread(a0);
+ case 85: /* lua_newuserdatauv */
+ return Api.lua_newuserdatauv(a0, a1, a2);
+ case 86: /* lua_next */
+ return Api.lua_next(a0, a1);
+ case 87: /* lua_pcallk */
+ return Api.lua_pcallk(a0, a1, a2, a3, a4, a5);
+ case 88: /* lua_pushboolean */
+ Api.lua_pushboolean(a0, a1);
+ return 0;
+ case 89: /* lua_pushcclosure */
+ Api.lua_pushcclosure(a0, a1, a2);
+ return 0;
+ case 90: /* lua_pushfstring */
+ return Api.lua_pushfstring(a0, a1, a2);
+ case 91: /* lua_pushinteger */
+ Api.lua_pushinteger(a0, a1);
+ return 0;
+ case 92: /* lua_pushlightuserdata */
+ Api.lua_pushlightuserdata(a0, a1);
+ return 0;
+ case 93: /* lua_pushlstring */
+ return Api.lua_pushlstring(a0, a1, a2);
+ case 94: /* lua_pushnil */
+ Api.lua_pushnil(a0);
+ return 0;
+ case 95: /* lua_pushnumber */
+ Api.lua_pushnumber(a0, d0);
+ return 0;
+ case 96: /* lua_pushstring */
+ return Api.lua_pushstring(a0, a1);
+ case 97: /* lua_pushthread */
+ return Api.lua_pushthread(a0);
+ case 98: /* lua_pushvalue */
+ Api.lua_pushvalue(a0, a1);
+ return 0;
+ case 99: /* lua_pushvfstring */
+ return Api.lua_pushvfstring(a0, a1, a2);
+ case 100: /* lua_rawequal */
+ return Api.lua_rawequal(a0, a1, a2);
+ case 101: /* lua_rawget */
+ return Api.lua_rawget(a0, a1);
+ case 102: /* lua_rawgeti */
+ return Api.lua_rawgeti(a0, a1, a2);
+ case 103: /* lua_rawgetp */
+ return Api.lua_rawgetp(a0, a1, a2);
+ case 104: /* lua_rawlen */
+ return Api.lua_rawlen(a0, a1);
+ case 105: /* lua_rawset */
+ Api.lua_rawset(a0, a1);
+ return 0;
+ case 106: /* lua_rawseti */
+ Api.lua_rawseti(a0, a1, a2);
+ return 0;
+ case 107: /* lua_rawsetp */
+ Api.lua_rawsetp(a0, a1, a2);
+ return 0;
+ case 108: /* lua_resetthread */
+ return Api.lua_resetthread(a0);
+ case 109: /* lua_resume */
+ return Api.lua_resume(a0, a1, a2, a3);
+ case 110: /* lua_rotate */
+ Api.lua_rotate(a0, a1, a2);
+ return 0;
+ case 111: /* lua_setallocf */
+ Api.lua_setallocf(a0, a1, a2);
+ return 0;
+ case 112: /* lua_setcstacklimit */
+ return Api.lua_setcstacklimit(a0, a1);
+ case 113: /* lua_setfield */
+ Api.lua_setfield(a0, a1, a2);
+ return 0;
+ case 114: /* lua_setglobal */
+ Api.lua_setglobal(a0, a1);
+ return 0;
+ case 115: /* lua_sethook */
+ Api.lua_sethook(a0, a1, a2, a3);
+ return 0;
+ case 116: /* lua_seti */
+ Api.lua_seti(a0, a1, a2);
+ return 0;
+ case 117: /* lua_setiuservalue */
+ return Api.lua_setiuservalue(a0, a1, a2);
+ case 118: /* lua_setlocal */
+ return Api.lua_setlocal(a0, a1, a2);
+ case 119: /* lua_setmetatable */
+ return Api.lua_setmetatable(a0, a1);
+ case 120: /* lua_settable */
+ Api.lua_settable(a0, a1);
+ return 0;
+ case 121: /* lua_settop */
+ Api.lua_settop(a0, a1);
+ return 0;
+ case 122: /* lua_setupvalue */
+ return Api.lua_setupvalue(a0, a1, a2);
+ case 123: /* lua_setwarnf */
+ Api.lua_setwarnf(a0, a1, a2);
+ return 0;
+ case 124: /* lua_status */
+ return Api.lua_status(a0);
+ case 125: /* lua_stringtonumber */
+ return Api.lua_stringtonumber(a0, a1);
+ case 126: /* lua_toboolean */
+ return Api.lua_toboolean(a0, a1);
+ case 127: /* lua_tocfunction */
+ return Api.lua_tocfunction(a0, a1);
+ case 128: /* lua_toclose */
+ Api.lua_toclose(a0, a1);
+ return 0;
+ case 129: /* lua_tointegerx */
+ return Api.lua_tointegerx(a0, a1, a2);
+ case 130: /* lua_tolstring */
+ return Api.lua_tolstring(a0, a1, a2);
+ case 131: /* lua_tonumberx */
+ throw new IllegalStateException("double 返回走 dispatchD");
+ case 132: /* lua_topointer */
+ return Api.lua_topointer(a0, a1);
+ case 133: /* lua_tothread */
+ return Api.lua_tothread(a0, a1);
+ case 134: /* lua_touserdata */
+ return Api.lua_touserdata(a0, a1);
+ case 135: /* lua_type */
+ return Api.lua_type(a0, a1);
+ case 136: /* lua_typename */
+ return Api.lua_typename(a0, a1);
+ case 137: /* lua_upvalueid */
+ return Api.lua_upvalueid(a0, a1, a2);
+ case 138: /* lua_upvaluejoin */
+ Api.lua_upvaluejoin(a0, a1, a2, a3, a4);
+ return 0;
+ case 139: /* lua_version */
+ throw new IllegalStateException("double 返回走 dispatchD");
+ case 140: /* lua_warning */
+ Api.lua_warning(a0, a1, a2);
+ return 0;
+ case 141: /* lua_xmove */
+ Api.lua_xmove(a0, a1, a2);
+ return 0;
+ case 142: /* lua_yieldk */
+ return Api.lua_yieldk(a0, a1, a2, a3);
+ case 143: /* luaL_openlibs */
+ Api.luaL_openlibs(a0);
+ return 0;
+ case 144: /* luaopen_base */
+ return Api.luaopen_base(a0);
+ case 145: /* luaopen_coroutine */
+ return Api.luaopen_coroutine(a0);
+ case 146: /* luaopen_debug */
+ return Api.luaopen_debug(a0);
+ case 147: /* luaopen_io */
+ return Api.luaopen_io(a0);
+ case 148: /* luaopen_math */
+ return Api.luaopen_math(a0);
+ case 149: /* luaopen_os */
+ return Api.luaopen_os(a0);
+ case 150: /* luaopen_package */
+ return Api.luaopen_package(a0);
+ case 151: /* luaopen_string */
+ return Api.luaopen_string(a0);
+ case 152: /* luaopen_table */
+ return Api.luaopen_table(a0);
+ case 153: /* luaopen_utf8 */
+ return Api.luaopen_utf8(a0);
+ default:
+ throw unimplemented("id=" + id);
+ }
+ }
+
+ /** 返回 double 的那些({@code lua_tonumberx} 一类)走这里 */
+ static double dispatchD(int id, long a0, long a1, long a2,
+ long a3, long a4, long a5, double d0) {
+ switch (id) {
+ case 11: /* luaL_checknumber */
+ return Api.luaL_checknumber(a0, a1);
+ case 31: /* luaL_optnumber */
+ return Api.luaL_optnumber(a0, a1, d0);
+ case 131: /* lua_tonumberx */
+ return Api.lua_tonumberx(a0, a1, a2);
+ case 139: /* lua_version */
+ return Api.lua_version(a0);
+ default:
+ throw unimplemented("id=" + id);
+ }
+ }
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/CClosure.java b/src/main/java/com/github/esrrhs/javalua/CClosure.java
new file mode 100644
index 0000000..4eb0f30
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/CClosure.java
@@ -0,0 +1,32 @@
+package com.github.esrrhs.javalua;
+
+/**
+ * C 闭包 —— 对应 {@code lobject.h} 的 {@code CClosure}。
+ *
+ *
+ */
+public final class Codegen {
+
+ private Codegen() {
+ }
+
+ /** {@code NO_JUMP}:跳转链表的结束标记(既是非法地址,也是自环) */
+ public static final int NO_JUMP = -1;
+
+ /** {@code MAXREGS}:一个函数最多 255 个寄存器 */
+ public static final int MAXREGS = 255;
+
+ /** {@code LIMLINEDIFF}:相对行号差值的上限,超了就得存绝对行号 */
+ private static final int LIMLINEDIFF = 0x80;
+
+ /** {@code LUA_MULTRET} */
+ public static final int MULTRET = -1;
+
+ /* ---------- BinOpr / UnOpr(ORDER OPR,与上游严格一致) ---------- */
+
+ public static final int OPR_ADD = 0;
+ public static final int OPR_SUB = 1;
+ public static final int OPR_MUL = 2;
+ public static final int OPR_MOD = 3;
+ public static final int OPR_POW = 4;
+ public static final int OPR_DIV = 5;
+ public static final int OPR_IDIV = 6;
+ public static final int OPR_BAND = 7;
+ public static final int OPR_BOR = 8;
+ public static final int OPR_BXOR = 9;
+ public static final int OPR_SHL = 10;
+ public static final int OPR_SHR = 11;
+ public static final int OPR_CONCAT = 12;
+ public static final int OPR_EQ = 13;
+ public static final int OPR_LT = 14;
+ public static final int OPR_LE = 15;
+ public static final int OPR_NE = 16;
+ public static final int OPR_GT = 17;
+ public static final int OPR_GE = 18;
+ public static final int OPR_AND = 19;
+ public static final int OPR_OR = 20;
+ public static final int OPR_NOBINOPR = 21;
+
+ public static final int UNOPR_MINUS = 0;
+ public static final int UNOPR_BNOT = 1;
+ public static final int UNOPR_NOT = 2;
+ public static final int UNOPR_LEN = 3;
+ public static final int UNOPR_NOUNOPR = 4;
+
+ /** {@code foldbinop}:只有算术与位运算可折叠 */
+ public static boolean foldBinOp(int op) {
+ return op <= OPR_SHR;
+ }
+
+ /* ---------- 指令发射 ---------- */
+
+ /** {@code luaK_code} */
+ public static int code(FuncState fs, int instr) {
+ Proto f = fs.f;
+ f.setCode(fs.pc, instr);
+ fs.pc++;
+ saveLineInfo(fs, f, fs.ls.lastline);
+ return fs.pc - 1;
+ }
+
+ /** {@code luaK_codeABCk} */
+ public static int codeABCk(FuncState fs, int o, int a, int b, int c, int k) {
+ return code(fs, OpCode.createABCk(o, a, b, c, k));
+ }
+
+ /** {@code luaK_codeABC} */
+ public static int codeABC(FuncState fs, int o, int a, int b, int c) {
+ return codeABCk(fs, o, a, b, c, 0);
+ }
+
+ /** {@code luaK_codeABx} */
+ public static int codeABx(FuncState fs, int o, int a, int bx) {
+ return code(fs, OpCode.createABx(o, a, bx));
+ }
+
+ /** {@code codeAsBx} */
+ private static int codeAsBx(FuncState fs, int o, int a, int bx) {
+ return codeABx(fs, o, a, bx + OpCode.OFFSET_SBX);
+ }
+
+ /** {@code codesJ} */
+ private static int codesJ(FuncState fs, int o, int sj, int k) {
+ return code(fs, OpCode.createsJ(o, sj + OpCode.OFFSET_SJ, k));
+ }
+
+ /** {@code codeextraarg} */
+ private static int codeExtraArg(FuncState fs, int a) {
+ return code(fs, OpCode.createAx(OpCode.OP_EXTRAARG, a));
+ }
+
+ /** {@code luaK_codek}:常量下标太大时用 LOADKX + EXTRAARG */
+ private static int codeK(FuncState fs, int reg, int k) {
+ if (k <= OpCode.MAXARG_BX) {
+ return codeABx(fs, OpCode.OP_LOADK, reg, k);
+ }
+ int p = codeABx(fs, OpCode.OP_LOADKX, reg, 0);
+ codeExtraArg(fs, k);
+ return p;
+ }
+
+ /* ---------- 行号 ---------- */
+
+ /** {@code savelineinfo} */
+ private static void saveLineInfo(FuncState fs, Proto f, int line) {
+ int linedif = line - fs.previousline;
+ int pc = fs.pc - 1; // 刚写进去的那条指令
+ if (Math.abs(linedif) >= LIMLINEDIFF || fs.iwthabs++ >= Proto.MAXIWTHABS) {
+ f.addAbsLineInfo(pc, line);
+ linedif = Proto.ABSLINEINFO;
+ fs.iwthabs = 1;
+ }
+ f.setLineInfo(pc, (byte) linedif);
+ fs.previousline = line;
+ }
+
+ /** {@code removelastlineinfo} */
+ private static void removeLastLineInfo(FuncState fs) {
+ Proto f = fs.f;
+ int pc = fs.pc - 1;
+ if (f.lineinfo[pc] != Proto.ABSLINEINFO) {
+ fs.previousline -= f.lineinfo[pc];
+ fs.iwthabs--;
+ } else {
+ f.sizeabslineinfo--;
+ fs.iwthabs = Proto.MAXIWTHABS + 1; // 强制下一条也用绝对行号
+ }
+ }
+
+ /** {@code removelastinstruction} */
+ private static void removeLastInstruction(FuncState fs) {
+ removeLastLineInfo(fs);
+ fs.pc--;
+ }
+
+ /** {@code luaK_fixline}:把刚发的那条指令的行号改写掉 */
+ public static void fixLine(FuncState fs, int line) {
+ removeLastLineInfo(fs);
+ saveLineInfo(fs, fs.f, line);
+ }
+
+ /* ---------- 寄存器栈 ---------- */
+
+ /** {@code luaK_checkstack} */
+ public static void checkStack(FuncState fs, int n) {
+ int newstack = fs.freereg + n;
+ if (newstack > fs.f.maxstacksize) {
+ if (newstack >= MAXREGS) {
+ fs.ls.syntaxError("function or expression needs too many registers");
+ }
+ fs.f.maxstacksize = newstack;
+ }
+ }
+
+ /** {@code luaK_reserveregs} */
+ public static void reserveRegs(FuncState fs, int n) {
+ checkStack(fs, n);
+ fs.freereg += n;
+ }
+
+ /** {@code freereg}:局部变量占住的寄存器不能释放 */
+ private static void freeReg(FuncState fs, int reg) {
+ if (reg >= fs.nvarstack()) {
+ fs.freereg--;
+ }
+ }
+
+ /** {@code freeregs}:按从高到低的顺序释放 */
+ private static void freeRegs(FuncState fs, int r1, int r2) {
+ if (r1 > r2) {
+ freeReg(fs, r1);
+ freeReg(fs, r2);
+ } else {
+ freeReg(fs, r2);
+ freeReg(fs, r1);
+ }
+ }
+
+ private static void freeExp(FuncState fs, ExpDesc e) {
+ if (e.k == ExpDesc.VNONRELOC) {
+ freeReg(fs, e.info);
+ }
+ }
+
+ private static void freeExps(FuncState fs, ExpDesc e1, ExpDesc e2) {
+ int r1 = (e1.k == ExpDesc.VNONRELOC) ? e1.info : -1;
+ int r2 = (e2.k == ExpDesc.VNONRELOC) ? e2.info : -1;
+ freeRegs(fs, r1, r2);
+ }
+
+ /* ---------- 常量 ---------- */
+
+ /** {@code stringK} */
+ private static int stringK(FuncState fs, LuaString s) {
+ return addK(fs, LuaValue.ofString(s));
+ }
+
+ /** {@code luaK_intK} */
+ public static int intK(FuncState fs, long n) {
+ return addK(fs, LuaValue.ofInteger(n));
+ }
+
+ /**
+ * {@code luaK_numberK}。
+ *
+ *
+ *
+ * 与错误信息的差别
+ * {@code LuaState} 里已经有 {@code findSetReg} / {@code basicGetObjName} /
+ * {@code funcNameFromCode}——那是给 {@code luaG_runerror} 拼
+ * 「attempt to call a nil value (global 'x')」用的。看起来是同一套符号执行,
+ * 但取名的起点不同:错误信息问的是「当前这一帧在调什么」,
+ * 而 {@code getinfo('n')} 问的是「{@code ci} 是被上一帧怎么调的」
+ * ({@code ci->previous})。混用会让 {@code debug.getinfo(2).name} 整体错一层。
+ */
+public final class DebugLib {
+
+ private DebugLib() {
+ }
+
+ /** {@code lua_Debug} */
+ public static final class LuaDebug {
+ /** 钩子事件:{@link Lua#LUA_HOOKCALL} 等。只有钩子路径会用到 */
+ int event;
+
+ /** {@code i_ci}:对应的调用帧 */
+ CallInfo iCi;
+
+ /**
+ * {@link #iCi} 所属的线程。
+ *
+ *
+ * 出去时要把这三个都还原:{@code L.top}、{@code ci.top}、
+ * {@code CIST_HOOKED}。
+ *
+ *
+ * {@code strip}
+ * 剥掉调试信息(行号、局部变量名、upvalue 名)后,chunk 仍能正常执行,
+ * 只是报错时没有行号:{@code Proto.getFuncline} 在 {@code sizelineinfo == 0}
+ * 时返回 {@code -1}。剥的时候源名也要一起剥——否则行号没了而源名还在,
+ * 反而暴露了它本该隐藏的东西。
+ */
+public final class Dump {
+
+ private Dump() {
+ }
+
+ /** 写出器:{@code lua_Writer},返回非 0 表示失败 */
+ public interface Writer {
+ int write(byte[] b, int off, int len);
+ }
+
+ private static final byte[] SIGNATURE = { 0x1b, 'L', 'u', 'a' };
+ private static final int LUAC_VERSION = 0x54;
+ private static final int LUAC_FORMAT = 0;
+ private static final byte[] LUAC_DATA = { 0x19, (byte) 0x93, '\r', '\n', 0x1a, '\n' };
+ private static final long LUAC_INT = 0x5678L;
+ private static final double LUAC_NUM = 370.5;
+ private static final int SIZE_INSTRUCTION = 4;
+ private static final int SIZE_INTEGER = 8;
+ private static final int SIZE_NUMBER = 8;
+
+ /** 每个字节存 7 位,64 位最多 10 组 */
+ private static final int DIBS = 10;
+
+ private static final class DumpState {
+ final Writer w;
+ final boolean strip;
+ int status;
+
+ DumpState(Writer w, boolean strip) {
+ this.w = w;
+ this.strip = strip;
+ }
+ }
+
+ private static void dumpBlock(DumpState D, byte[] b, int off, int size) {
+ if (D.status == 0 && size > 0) {
+ D.status = D.w.write(b, off, size);
+ }
+ }
+
+ private static void dumpByte(DumpState D, int y) {
+ dumpBlock(D, new byte[] { (byte) y }, 0, 1);
+ }
+
+ /** 变长无符号数:倒着填,最后一组的最高位置 1(与 {@link Undump} 的读法对称) */
+ private static void dumpSize(DumpState D, long x) {
+ byte[] buff = new byte[DIBS];
+ int n = 0;
+ do {
+ buff[DIBS - (++n)] = (byte) (x & 0x7f);
+ x >>>= 7;
+ } while (x != 0);
+ buff[DIBS - 1] |= (byte) 0x80;
+ dumpBlock(D, buff, DIBS - n, n);
+ }
+
+ private static void dumpInt(DumpState D, int x) {
+ dumpSize(D, x);
+ }
+
+ private static void dumpInteger(DumpState D, long x) {
+ byte[] b = new byte[SIZE_INTEGER];
+ Undump.putInteger(b, 0, x); // 小端
+ dumpBlock(D, b, 0, SIZE_INTEGER);
+ }
+
+ private static void dumpNumber(DumpState D, double x) {
+ dumpInteger(D, Double.doubleToRawLongBits(x));
+ }
+
+ private static void dumpString(DumpState D, LuaString s) {
+ if (s == null) {
+ dumpSize(D, 0);
+ } else {
+ dumpSize(D, s.length() + 1); // 0 留给「没有」
+ dumpBlock(D, s.bytes, 0, s.length());
+ }
+ }
+
+ private static void dumpCode(DumpState D, Proto f) {
+ dumpInt(D, f.sizecode);
+ byte[] b = new byte[f.sizecode * SIZE_INSTRUCTION];
+ for (int i = 0; i < f.sizecode; i++) {
+ int v = f.code[i];
+ for (int k = 0; k < SIZE_INSTRUCTION; k++) { // 小端
+ b[i * SIZE_INSTRUCTION + k] = (byte) ((v >>> (8 * k)) & 0xFF);
+ }
+ }
+ dumpBlock(D, b, 0, b.length);
+ }
+
+ private static void dumpConstants(DumpState D, Proto f) {
+ dumpInt(D, f.sizek);
+ for (int i = 0; i < f.sizek; i++) {
+ LuaValue o = f.k[i];
+ int tt = o.tt;
+ switch (tt) {
+ case Lua.TNIL:
+ dumpByte(D, Lua.VNIL);
+ break;
+ case Lua.TBOOLEAN:
+ dumpByte(D, o.i != 0 ? Lua.VTRUE : Lua.VFALSE);
+ break;
+ case Lua.VNUMINT:
+ dumpByte(D, Lua.VNUMINT);
+ dumpInteger(D, o.i);
+ break;
+ case Lua.VNUMFLT:
+ dumpByte(D, Lua.VNUMFLT);
+ dumpNumber(D, o.n);
+ break;
+ case Lua.VSHRSTR:
+ case Lua.VLNGSTR:
+ dumpByte(D, tt);
+ dumpString(D, (LuaString) o.o);
+ break;
+ default:
+ // 常量表里只可能有这几种——Parser 不会别的东西塞进来
+ throw new IllegalStateException("bad constant type " + tt);
+ }
+ }
+ }
+
+ private static void dumpProtos(DumpState D, Proto f) {
+ dumpInt(D, f.sizep);
+ for (int i = 0; i < f.sizep; i++) {
+ dumpFunction(D, f.p[i], f.source);
+ }
+ }
+
+ private static void dumpUpvalues(DumpState D, Proto f) {
+ dumpInt(D, f.sizeupvalues);
+ for (int i = 0; i < f.sizeupvalues; i++) {
+ dumpByte(D, f.upvalues[i].instack ? 1 : 0);
+ dumpByte(D, f.upvalues[i].idx);
+ dumpByte(D, f.upvalues[i].kind);
+ }
+ }
+
+ private static void dumpDebug(DumpState D, Proto f) {
+ int n = D.strip ? 0 : f.sizelineinfo;
+ dumpInt(D, n);
+ dumpBlock(D, f.lineinfo, 0, n);
+ n = D.strip ? 0 : f.sizeabslineinfo;
+ dumpInt(D, n);
+ for (int i = 0; i < n; i++) {
+ dumpInt(D, f.abslineinfo[i].pc);
+ dumpInt(D, f.abslineinfo[i].line);
+ }
+ n = D.strip ? 0 : f.sizelocvars;
+ dumpInt(D, n);
+ for (int i = 0; i < n; i++) {
+ dumpString(D, f.locvars[i].varname);
+ dumpInt(D, f.locvars[i].startpc);
+ dumpInt(D, f.locvars[i].endpc);
+ }
+ n = D.strip ? 0 : f.sizeupvalues;
+ dumpInt(D, n);
+ for (int i = 0; i < n; i++) {
+ dumpString(D, f.upvalues[i].name);
+ }
+ }
+
+ private static void dumpFunction(DumpState D, Proto f, LuaString psource) {
+ /*
+ * 与父函数同源(或本来就剥调试信息)就不重复写源名。
+ * 比的是引用而不是内容——C 版是 {@code f->source == psource},
+ * 两个指针比。短字符串驻留之后这等价于比内容,长字符串则不驻留,
+ * 于是 C 会为每个内嵌函数各写一遍源名;照内容比会写得比 C 少。
+ */
+ if (D.strip || f.source == psource) {
+ dumpString(D, null);
+ } else {
+ dumpString(D, f.source);
+ }
+ dumpInt(D, f.linedefined);
+ dumpInt(D, f.lastlinedefined);
+ dumpByte(D, f.numparams);
+ dumpByte(D, f.isVararg ? 1 : 0);
+ dumpByte(D, f.maxstacksize);
+ dumpCode(D, f);
+ dumpConstants(D, f);
+ dumpUpvalues(D, f);
+ dumpProtos(D, f);
+ dumpDebug(D, f);
+ }
+
+ private static void dumpHeader(DumpState D) {
+ dumpBlock(D, SIGNATURE, 0, SIGNATURE.length);
+ dumpByte(D, LUAC_VERSION);
+ dumpByte(D, LUAC_FORMAT);
+ dumpBlock(D, LUAC_DATA, 0, LUAC_DATA.length);
+ dumpByte(D, SIZE_INSTRUCTION);
+ dumpByte(D, SIZE_INTEGER);
+ dumpByte(D, SIZE_NUMBER);
+ dumpInteger(D, LUAC_INT);
+ dumpNumber(D, LUAC_NUM);
+ }
+
+ /** {@code luaU_dump} */
+ public static int dump(Proto f, Writer w, boolean strip) {
+ DumpState D = new DumpState(w, strip);
+ dumpHeader(D);
+ dumpByte(D, f.sizeupvalues);
+ dumpFunction(D, f, null);
+ return D.status;
+ }
+
+ /** 把整颗函数树 dump 成字节数组;{@code string.dump} 用这个 */
+ public static byte[] dumpBytes(Proto f, boolean strip) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ dump(f, (b, off, len) -> {
+ out.write(b, off, len);
+ return 0;
+ }, strip);
+ return out.toByteArray();
+ }
+
+}
diff --git a/src/main/java/com/github/esrrhs/javalua/DynData.java b/src/main/java/com/github/esrrhs/javalua/DynData.java
new file mode 100644
index 0000000..cc06b4e
--- /dev/null
+++ b/src/main/java/com/github/esrrhs/javalua/DynData.java
@@ -0,0 +1,71 @@
+package com.github.esrrhs.javalua;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 解析过程中的动态结构 —— 对应 {@code lparser.h} 的 {@code Dyndata}。
+ *
+ *
+ * 第 3、4 步必须分开。{@code finalize} 跑在 JVM 的终结线程上,在那里调
+ * Lua 函数等于在另一个线程上动 {@code LuaState},数据竞争无可避免;而且终结线程
+ * 上抛异常会被 JVM 直接吞掉(连日志都没有)。所以那里只做「入队」这一件事。
+ *
+ *
+ *
+ *
+ */
+public final class IoLib {
+
+ private IoLib() {
+ }
+
+ /**
+ * {@code luaL_Stream*} -> {@code LuaFile} 的反查表。
+ *
+ * io.open(dir, "r") -> file 句柄;f:read("l") -> nil, "Is a directory", 21
+ * io.open(dir, "w") -> nil, "<path>: Is a directory", 21
+ *
+ * Java 的 {@code RandomAccessFile} 在目录上是开都开不了的,所以这里
+ * 单独记一个标记:打开时放行只读,读写一律报 21。
+ */
+ boolean dir;
+
+ /** 目录被当零长文件看待时的虚拟位置 —— 目录 fd 上的 {@code lseek} 是允许的 */
+ long dirPos;
+
+ /** {@code ungetc} 的回退位;{@code -1} 表示空 */
+ int pushback = -1;
+
+ /** 相当于 {@code ferror(f)} */
+ boolean err;
+
+ /** 出错时的 errno */
+ int errno;
+
+ /** {@code closef}:{@code null} 表示已关闭 */
+ LuaFunction closef;
+
+ /**
+ * 写缓冲 —— 真实文件必须有,标准流不能有。
+ *
+ * io.popen("echo out; echo err 1>&2", "r") -- 读到 "out","err" 落在 lua 自己的 stderr
+ * io.popen("cat; echo WERR 1>&2", "w") -- 写进去的 "hello" 被 cat 回显到 stdout
+ *
+ * Java 的 {@code ProcessBuilder} 默认是三条全接成管道,于是这两
+ * 边全被吞掉;更糟的是读模式下子进程的 {@code stdin} 也是管道且没人关
+ * ——{@code io.popen("cat", "r")} 会一直等到天荒地老。
+ *
+ *
+ *