mirror of https://github.com/mgba-emu/mgba.git
Scripting: Add calling Lua functions
This commit is contained in:
parent
ce97d86906
commit
2c11c4806a
|
@ -9,13 +9,57 @@
|
||||||
static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*);
|
static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*);
|
||||||
|
|
||||||
static void _luaDestroy(struct mScriptEngineContext*);
|
static void _luaDestroy(struct mScriptEngineContext*);
|
||||||
|
static struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext*, const char* name);
|
||||||
static bool _luaLoad(struct mScriptEngineContext*, struct VFile*, const char** error);
|
static bool _luaLoad(struct mScriptEngineContext*, struct VFile*, const char** error);
|
||||||
|
static bool _luaRun(struct mScriptEngineContext*);
|
||||||
|
|
||||||
|
static bool _luaCall(struct mScriptFrame*, void* context);
|
||||||
|
|
||||||
|
struct mScriptEngineContextLua;
|
||||||
|
static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptFrame*);
|
||||||
|
static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptFrame*);
|
||||||
|
static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*);
|
||||||
|
|
||||||
|
static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext);
|
||||||
|
static bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue*);
|
||||||
|
|
||||||
|
static void _luaDeref(struct mScriptValue*);
|
||||||
|
|
||||||
|
#if LUA_VERSION_NUM < 503
|
||||||
|
#define lua_pushinteger lua_pushnumber
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const struct mScriptType mSTLuaFunc = {
|
||||||
|
.base = mSCRIPT_TYPE_FUNCTION,
|
||||||
|
.size = 0,
|
||||||
|
.name = "lua-" LUA_VERSION_ONLY "::function",
|
||||||
|
.details = {
|
||||||
|
.function = {
|
||||||
|
.parameters = {
|
||||||
|
.variable = true
|
||||||
|
},
|
||||||
|
.returnType = {
|
||||||
|
.variable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = _luaDeref,
|
||||||
|
.hash = NULL,
|
||||||
|
.equal = NULL,
|
||||||
|
.cast = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
struct mScriptEngineContextLua {
|
struct mScriptEngineContextLua {
|
||||||
struct mScriptEngineContext d;
|
struct mScriptEngineContext d;
|
||||||
lua_State* lua;
|
lua_State* lua;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mScriptEngineContextLuaRef {
|
||||||
|
struct mScriptEngineContextLua* context;
|
||||||
|
int ref;
|
||||||
|
};
|
||||||
|
|
||||||
static struct mScriptEngineLua {
|
static struct mScriptEngineLua {
|
||||||
struct mScriptEngine2 d;
|
struct mScriptEngine2 d;
|
||||||
} _engineLua = {
|
} _engineLua = {
|
||||||
|
@ -35,7 +79,9 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
|
||||||
luaContext->d = (struct mScriptEngineContext) {
|
luaContext->d = (struct mScriptEngineContext) {
|
||||||
.context = context,
|
.context = context,
|
||||||
.destroy = _luaDestroy,
|
.destroy = _luaDestroy,
|
||||||
|
.getGlobal = _luaGetGlobal,
|
||||||
.load = _luaLoad,
|
.load = _luaLoad,
|
||||||
|
.run = _luaRun
|
||||||
};
|
};
|
||||||
luaContext->lua = luaL_newstate();
|
luaContext->lua = luaL_newstate();
|
||||||
return &luaContext->d;
|
return &luaContext->d;
|
||||||
|
@ -47,6 +93,96 @@ void _luaDestroy(struct mScriptEngineContext* ctx) {
|
||||||
free(luaContext);
|
free(luaContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext* ctx, const char* name) {
|
||||||
|
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
|
||||||
|
lua_getglobal(luaContext->lua, name);
|
||||||
|
return _luaCoerce(luaContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* _luaWrapFunction(struct mScriptEngineContextLua* luaContext) {
|
||||||
|
struct mScriptValue* value = mScriptValueAlloc(&mSTLuaFunc);
|
||||||
|
struct mScriptFunction* fn = calloc(1, sizeof(*fn));
|
||||||
|
struct mScriptEngineContextLuaRef* ref = calloc(1, sizeof(*ref));
|
||||||
|
fn->call = _luaCall;
|
||||||
|
fn->context = ref;
|
||||||
|
ref->context = luaContext;
|
||||||
|
ref->ref = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
|
||||||
|
value->value.opaque = fn;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) {
|
||||||
|
if (lua_isnone(luaContext->lua, -1)) {
|
||||||
|
lua_pop(luaContext->lua, 1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* value = NULL;
|
||||||
|
switch (lua_type(luaContext->lua, -1)) {
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
#if LUA_VERSION_NUM >= 503
|
||||||
|
if (lua_isinteger(luaContext->lua, -1)) {
|
||||||
|
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64);
|
||||||
|
value->value.s64 = lua_tointeger(luaContext->lua, -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64);
|
||||||
|
value->value.f64 = lua_tonumber(luaContext->lua, -1);
|
||||||
|
break;
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
break;
|
||||||
|
case LUA_TSTRING:
|
||||||
|
break;
|
||||||
|
case LUA_TFUNCTION:
|
||||||
|
return _luaWrapFunction(luaContext);
|
||||||
|
}
|
||||||
|
lua_pop(luaContext->lua, 1);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* value) {
|
||||||
|
if (value->type == mSCRIPT_TYPE_MS_WRAPPER) {
|
||||||
|
value = mScriptValueUnwrap(value);
|
||||||
|
}
|
||||||
|
bool ok = true;
|
||||||
|
switch (value->type->base) {
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
if (value->type->size == 4) {
|
||||||
|
lua_pushinteger(luaContext->lua, value->value.s32);
|
||||||
|
} else if (value->type->size == 8) {
|
||||||
|
lua_pushinteger(luaContext->lua, value->value.s64);
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
if (value->type->size == 4) {
|
||||||
|
lua_pushinteger(luaContext->lua, value->value.u32);
|
||||||
|
} else if (value->type->size == 8) {
|
||||||
|
lua_pushinteger(luaContext->lua, value->value.u64);
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_FLOAT:
|
||||||
|
if (value->type->size == 4) {
|
||||||
|
lua_pushnumber(luaContext->lua, value->value.f32);
|
||||||
|
} else if (value->type->size == 8) {
|
||||||
|
lua_pushnumber(luaContext->lua, value->value.f64);
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mScriptValueDeref(value);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
#define LUA_BLOCKSIZE 0x1000
|
#define LUA_BLOCKSIZE 0x1000
|
||||||
struct mScriptEngineLuaReader {
|
struct mScriptEngineLuaReader {
|
||||||
struct VFile* vf;
|
struct VFile* vf;
|
||||||
|
@ -89,3 +225,95 @@ bool _luaLoad(struct mScriptEngineContext* ctx, struct VFile* vf, const char** e
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _luaRun(struct mScriptEngineContext* context) {
|
||||||
|
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) context;
|
||||||
|
return _luaInvoke(luaContext, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) {
|
||||||
|
bool ok = true;
|
||||||
|
if (frame) {
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < mScriptListSize(&frame->arguments); ++i) {
|
||||||
|
if (!_luaWrap(luaContext, mScriptListGetPointer(&frame->arguments, i))) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
lua_pop(luaContext->lua, lua_gettop(luaContext->lua));
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) {
|
||||||
|
int count = lua_gettop(luaContext->lua);
|
||||||
|
bool ok = true;
|
||||||
|
if (frame) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < count; ++i) {
|
||||||
|
struct mScriptValue* value = _luaCoerce(luaContext);
|
||||||
|
if (!value) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lua_pop(luaContext->lua, 1);
|
||||||
|
mScriptValueWrap(value, mScriptListAppend(&frame->returnValues));
|
||||||
|
mScriptValueDeref(value);
|
||||||
|
}
|
||||||
|
if (count > i) {
|
||||||
|
lua_pop(luaContext->lua, count - i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _luaCall(struct mScriptFrame* frame, void* context) {
|
||||||
|
struct mScriptEngineContextLuaRef* ref = context;
|
||||||
|
lua_rawgeti(ref->context->lua, LUA_REGISTRYINDEX, ref->ref);
|
||||||
|
if (!_luaInvoke(ref->context, frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) {
|
||||||
|
int nargs = 0;
|
||||||
|
if (frame) {
|
||||||
|
nargs = mScriptListSize(&frame->arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame && !_luaPushFrame(luaContext, frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = lua_pcall(luaContext->lua, nargs, LUA_MULTRET, 0);
|
||||||
|
|
||||||
|
if (ret == LUA_ERRRUN) {
|
||||||
|
lua_pop(luaContext->lua, 1);
|
||||||
|
}
|
||||||
|
if (ret) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_luaPopFrame(luaContext, frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _luaDeref(struct mScriptValue* value) {
|
||||||
|
struct mScriptEngineContextLuaRef* ref;
|
||||||
|
if (value->type->base == mSCRIPT_TYPE_FUNCTION) {
|
||||||
|
struct mScriptFunction* function = value->value.opaque;
|
||||||
|
ref = function->context;
|
||||||
|
free(function);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
luaL_unref(ref->context->lua, LUA_REGISTRYINDEX, ref->ref);
|
||||||
|
free(ref);
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ M_TEST_DEFINE(loadGood) {
|
||||||
mScriptContextDeinit(&context);
|
mScriptContextDeinit(&context);
|
||||||
}
|
}
|
||||||
|
|
||||||
M_TEST_DEFINE(loadSyntax) {
|
M_TEST_DEFINE(loadBadSyntax) {
|
||||||
struct mScriptContext context;
|
struct mScriptContext context;
|
||||||
mScriptContextInit(&context);
|
mScriptContextInit(&context);
|
||||||
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
|
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
|
||||||
|
@ -59,7 +59,132 @@ M_TEST_DEFINE(loadSyntax) {
|
||||||
mScriptContextDeinit(&context);
|
mScriptContextDeinit(&context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(runNop) {
|
||||||
|
struct mScriptContext context;
|
||||||
|
mScriptContextInit(&context);
|
||||||
|
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
|
||||||
|
|
||||||
|
const char* program = "return";
|
||||||
|
struct VFile* vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
const char* error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
lua->destroy(lua);
|
||||||
|
mScriptContextDeinit(&context);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(getGlobal) {
|
||||||
|
struct mScriptContext context;
|
||||||
|
mScriptContextInit(&context);
|
||||||
|
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
|
||||||
|
|
||||||
|
struct mScriptValue a = mSCRIPT_MAKE_S32(1);
|
||||||
|
struct mScriptValue* val;
|
||||||
|
const char* program;
|
||||||
|
struct VFile* vf;
|
||||||
|
const char* error;
|
||||||
|
|
||||||
|
program = "a = 1";
|
||||||
|
vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
val = lua->getGlobal(lua, "a");
|
||||||
|
assert_non_null(val);
|
||||||
|
assert_true(a.type->equal(&a, val));
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
|
||||||
|
program = "b = 1";
|
||||||
|
vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
val = lua->getGlobal(lua, "a");
|
||||||
|
assert_non_null(val);
|
||||||
|
assert_true(a.type->equal(&a, val));
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
|
||||||
|
val = lua->getGlobal(lua, "b");
|
||||||
|
assert_non_null(val);
|
||||||
|
assert_true(a.type->equal(&a, val));
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
|
||||||
|
a = mSCRIPT_MAKE_S32(2);
|
||||||
|
program = "a = 2";
|
||||||
|
vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
val = lua->getGlobal(lua, "a");
|
||||||
|
assert_non_null(val);
|
||||||
|
assert_true(a.type->equal(&a, val));
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
|
||||||
|
a = mSCRIPT_MAKE_S32(3);
|
||||||
|
program = "b = a + b";
|
||||||
|
vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
val = lua->getGlobal(lua, "b");
|
||||||
|
assert_non_null(val);
|
||||||
|
assert_true(a.type->equal(&a, val));
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
|
||||||
|
lua->destroy(lua);
|
||||||
|
mScriptContextDeinit(&context);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(callLuaFunc) {
|
||||||
|
struct mScriptContext context;
|
||||||
|
mScriptContextInit(&context);
|
||||||
|
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
|
||||||
|
|
||||||
|
struct mScriptValue* fn;
|
||||||
|
const char* program;
|
||||||
|
struct VFile* vf;
|
||||||
|
const char* error;
|
||||||
|
|
||||||
|
program = "function a(b) return b + 1 end";
|
||||||
|
vf = VFileFromConstMemory(program, strlen(program));
|
||||||
|
error = NULL;
|
||||||
|
assert_true(lua->load(lua, vf, &error));
|
||||||
|
assert_null(error);
|
||||||
|
assert_true(lua->run(lua));
|
||||||
|
|
||||||
|
fn = lua->getGlobal(lua, "a");
|
||||||
|
assert_non_null(fn);
|
||||||
|
assert_int_equal(fn->type->base, mSCRIPT_TYPE_FUNCTION);
|
||||||
|
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_true(mScriptInvoke(fn, &frame));
|
||||||
|
int64_t val;
|
||||||
|
assert_true(mScriptPopS64(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 2);
|
||||||
|
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
mScriptValueDeref(fn);
|
||||||
|
|
||||||
|
lua->destroy(lua);
|
||||||
|
mScriptContextDeinit(&context);
|
||||||
|
}
|
||||||
|
|
||||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
|
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
|
||||||
cmocka_unit_test(create),
|
cmocka_unit_test(create),
|
||||||
cmocka_unit_test(loadGood),
|
cmocka_unit_test(loadGood),
|
||||||
cmocka_unit_test(loadSyntax))
|
cmocka_unit_test(loadBadSyntax),
|
||||||
|
cmocka_unit_test(runNop),
|
||||||
|
cmocka_unit_test(getGlobal),
|
||||||
|
cmocka_unit_test(callLuaFunc))
|
||||||
|
|
Loading…
Reference in New Issue