twnlua: bindgen.py capable of converting share/twn_api.h spec to lua bindings
This commit is contained in:
@ -8,10 +8,19 @@ endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py
|
||||
)
|
||||
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
state.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||
|
||||
lua/src/lapi.c
|
||||
lua/src/lapi.h
|
||||
lua/src/lauxlib.c
|
||||
|
112
apps/twnlua/bindgen.py
Executable file
112
apps/twnlua/bindgen.py
Executable file
@ -0,0 +1,112 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import sys, json
|
||||
|
||||
with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f:
|
||||
api_source = f.read()
|
||||
|
||||
api = json.loads(api_source)
|
||||
|
||||
|
||||
def default(parameter):
|
||||
basetype = parameter["type"].rsplit(' *', 1)[0]
|
||||
if parameter["type"] == "float":
|
||||
return parameter["default"]
|
||||
elif parameter["type"] == "bool":
|
||||
return "true" if parameter["default"] else "false"
|
||||
elif parameter["type"] == "char *":
|
||||
if parameter["default"] == {}:
|
||||
return "NULL"
|
||||
else: return '"' + parameter["default"] + '"'
|
||||
elif basetype in api["types"]:
|
||||
if parameter["type"].endswith("*"):
|
||||
if parameter["default"] == {}:
|
||||
return "NULL"
|
||||
else:
|
||||
return "&(%s){\n%s\n }" % \
|
||||
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
|
||||
else:
|
||||
return "(%s){\n%s\n }" % \
|
||||
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
|
||||
raise BaseException("Unhandled default value of type '%s'" % parameter["type"])
|
||||
|
||||
|
||||
print('#include "twn_game_api.h"')
|
||||
print('#include <lua.h>')
|
||||
print('#include <lualib.h>')
|
||||
print('#include <lauxlib.h>\n')
|
||||
|
||||
|
||||
for typename, typedesc in api["types"].items():
|
||||
converter = "static %s table_to_%s(lua_State *L, int idx) {\n" % (typename, typename.lower())
|
||||
converter += " %s %s;\n" % (typename, typename.lower());
|
||||
|
||||
if not "fields" in typedesc:
|
||||
continue
|
||||
|
||||
for field in typedesc["fields"]:
|
||||
converter += " lua_getfield(L, idx, \"%s\");\n" % (field["name"]);
|
||||
if field["type"] == "float":
|
||||
converter += " %s.%s = (float)lua_tonumber(L, -1);\n" % (typename.lower(), field["name"]);
|
||||
elif field["type"] == "uint8_t":
|
||||
converter += " %s.%s = (uint8_t)lua_tointeger(L, -1);\n" % (typename.lower(), field["name"]);
|
||||
else:
|
||||
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
|
||||
converter += " lua_pop(L, 1);\n";
|
||||
|
||||
converter += " return %s;\n}\n" % (typename.lower())
|
||||
print(converter)
|
||||
|
||||
|
||||
for procedure, procedure_desc in api["procedures"].items():
|
||||
binding = "static int binding_%s(lua_State *L) {\n" % procedure
|
||||
binding += " luaL_checktype(L, 1, LUA_TTABLE);\n"
|
||||
|
||||
if "params" in procedure_desc:
|
||||
for parameter in procedure_desc["params"]:
|
||||
basetype = parameter["type"].rsplit(' *', 1)[0]
|
||||
|
||||
if parameter["type"].endswith("*"):
|
||||
binding += " %s %s_value;\n" % (basetype, parameter["name"])
|
||||
binding += " %s %s;\n" % (parameter["type"], parameter["name"])
|
||||
binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"]
|
||||
|
||||
if "default" in parameter:
|
||||
binding += " if (lua_isnoneornil(L, -1))\n"
|
||||
binding += " %s = %s;\n" % (parameter["name"], default(parameter))
|
||||
binding += " else\n "
|
||||
|
||||
if parameter["type"] == "float":
|
||||
binding += " %s = (float)lua_tonumber(L, -1);\n" % (parameter["name"]);
|
||||
elif parameter["type"] == "bool":
|
||||
binding += " %s = lua_toboolean(L, -1);\n" % (parameter["name"]);
|
||||
elif parameter["type"] == "char *":
|
||||
binding += " %s = lua_tostring(L, -1);\n" % (parameter["name"]);
|
||||
elif basetype in api["types"]:
|
||||
if "enums" in api["types"][basetype]:
|
||||
binding += " %s = lua_tointeger(L, -1);\n" % (parameter["name"]);
|
||||
elif parameter["type"].endswith("*"):
|
||||
binding += " { %s_value = table_to_%s(L, -1); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]);
|
||||
else:
|
||||
binding += " %s = table_to_%s(L, -1);\n" % (parameter["name"], basetype.lower());
|
||||
else:
|
||||
raise BaseException("Unhandled parameter type '%s'" % (parameter["type"]))
|
||||
|
||||
binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
binding += "}\n"
|
||||
print(binding)
|
||||
|
||||
|
||||
loader = "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
|
||||
modules = set(api["procedures"][procedure]["module"] for procedure in api["procedures"])
|
||||
for module in modules:
|
||||
loader += " lua_newtable(L);\n"
|
||||
for procedure, procedure_desc in api["procedures"].items():
|
||||
if procedure_desc["module"] == module:
|
||||
loader += " lua_pushstring(L, \"%s\");\n" % procedure_desc["symbol"]
|
||||
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
|
||||
loader += " lua_settable(L, -3);\n"
|
||||
loader += " lua_setglobal(L, \"%s\");\n" % module
|
||||
|
||||
loader += "}"
|
||||
print(loader)
|
@ -9,17 +9,8 @@ function game_tick()
|
||||
color = { r = 127, g = 0, b = 127, a = 255 },
|
||||
}
|
||||
|
||||
input.action {
|
||||
name = "move_up",
|
||||
control = input.CONTROL_L,
|
||||
}
|
||||
|
||||
if input.action_pressed "move_up" then
|
||||
draw.text { string = "BOO!" }
|
||||
end
|
||||
|
||||
draw.sprite {
|
||||
texture = "/assets/title.png",
|
||||
path = "/assets/title.png",
|
||||
rect = {
|
||||
x = 320 - (320 / 2),
|
||||
y = 180 - (128 / 2),
|
||||
@ -29,7 +20,7 @@ function game_tick()
|
||||
}
|
||||
|
||||
draw.text {
|
||||
string = "IT KEEPS HAPPENING",
|
||||
string = "it never happened",
|
||||
position = offset,
|
||||
font = "/fonts/kenney-pixel.ttf",
|
||||
}
|
||||
|
@ -10,6 +10,10 @@
|
||||
#include <malloc.h>
|
||||
|
||||
|
||||
/* generated by bindgen.py */
|
||||
void bindgen_load_twn(lua_State *L);
|
||||
|
||||
|
||||
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
|
||||
static int physfs_loader(lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
@ -37,201 +41,6 @@ static int physfs_loader(lua_State *L) {
|
||||
}
|
||||
|
||||
|
||||
static Rect table_to_rect(lua_State *L, int idx, const char *name) {
|
||||
/* types are checked here to help prevent unexpected results */
|
||||
Rect rect;
|
||||
int is_num;
|
||||
|
||||
lua_getfield(L, idx, "x");
|
||||
rect.x = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "y");
|
||||
rect.y = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "w");
|
||||
rect.w = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "h");
|
||||
rect.h = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
static Color table_to_color(lua_State *L, int idx) {
|
||||
Color color;
|
||||
|
||||
lua_getfield(L, idx, "r");
|
||||
color.r = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "g");
|
||||
color.g = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "b");
|
||||
color.b = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "a");
|
||||
color.a = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
|
||||
/* sprite(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* path [string]
|
||||
* rect [table]
|
||||
* texture_region [table], optional
|
||||
* color [table], optional
|
||||
* rotation [number], optional
|
||||
* flip_x [boolean], optional
|
||||
* flip_y [boolean], optional
|
||||
* stretch [boolean], optional
|
||||
*/
|
||||
static int b_sprite(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
DrawSpriteArgs args = { 0 };
|
||||
|
||||
lua_getfield(L, 1, "path");
|
||||
const char *field_path = lua_tostring(L, -1);
|
||||
if (field_path == NULL)
|
||||
luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
args.path = field_path;
|
||||
|
||||
lua_getfield(L, 1, "rect");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
args.rect = table_to_rect(L, -1, "data.rect");
|
||||
|
||||
lua_getfield(L, 1, "texture_region");
|
||||
if (lua_istable(L, -1)) {
|
||||
args.texture_region_opt = table_to_rect(L, -1, "data.texture_region");
|
||||
args.texture_region_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "color");
|
||||
if (lua_istable(L, -1)) {
|
||||
args.color_opt = table_to_color(L, -1);
|
||||
args.color_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "rotation");
|
||||
if (lua_isnumber(L, -1)) {
|
||||
args.rotation_opt = (float)lua_tonumber(L, -1);
|
||||
args.rotation_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "flip_x");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.flip_x_opt = lua_toboolean(L, -1);
|
||||
args.flip_x_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "flip_y");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.flip_y_opt = lua_toboolean(L, -1);
|
||||
args.flip_y_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "stretch");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.stretch_opt = lua_toboolean(L, -1);
|
||||
args.stretch_opt_set = true;
|
||||
}
|
||||
|
||||
draw_sprite_args(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* rectangle(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* rect [table]
|
||||
* color [table], optional, defaults to white
|
||||
*/
|
||||
static int b_rectangle(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
lua_getfield(L, 1, "rect");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
Rect rect = table_to_rect(L, -1, "data.rect");
|
||||
|
||||
Color color = { 255, 255, 255, 255 };
|
||||
lua_getfield(L, 1, "color");
|
||||
if (lua_istable(L, -1))
|
||||
color = table_to_color(L, -1);
|
||||
|
||||
draw_rectangle(rect, color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* text(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* string [string]
|
||||
* position [table]
|
||||
* height_px [number], optional, defaults to 22
|
||||
* color [table], optional, defaults to black
|
||||
* font [string]
|
||||
*/
|
||||
static int b_text(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
lua_getfield(L, 1, "string");
|
||||
const char *string = lua_tostring(L, -1);
|
||||
if (string == NULL)
|
||||
luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
|
||||
lua_getfield(L, 1, "position");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
lua_getfield(L, -1, "x");
|
||||
float x = (float)lua_tonumber(L, -1);
|
||||
lua_getfield(L, -2, "y");
|
||||
float y = (float)lua_tonumber(L, -1);
|
||||
|
||||
lua_getfield(L, 1, "height_px");
|
||||
int is_num;
|
||||
int height_px = (int)lua_tointegerx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
height_px = 22;
|
||||
|
||||
lua_getfield(L, 1, "color");
|
||||
Color color = { 0, 0, 0, 255 };
|
||||
if (lua_istable(L, -1))
|
||||
color = table_to_color(L, -1);
|
||||
|
||||
lua_getfield(L, 1, "font");
|
||||
const char *font = lua_tostring(L, -1);
|
||||
if (font == NULL)
|
||||
luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
|
||||
draw_text(string, (Vec2) { x, y }, height_px, color, font);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata)
|
||||
@ -246,14 +55,13 @@ void game_tick(void) {
|
||||
}
|
||||
state->L = luaL_newstate();
|
||||
|
||||
/* fakey version of luaL_openlibs() that excludes file i/o */
|
||||
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
||||
{
|
||||
static const luaL_Reg loaded_libs[] = {
|
||||
{ LUA_GNAME, luaopen_base },
|
||||
{ LUA_LOADLIBNAME, luaopen_package },
|
||||
{ LUA_COLIBNAME, luaopen_coroutine },
|
||||
{ LUA_TABLIBNAME, luaopen_table },
|
||||
{ LUA_OSLIBNAME, luaopen_os },
|
||||
{ LUA_STRLIBNAME, luaopen_string },
|
||||
{ LUA_MATHLIBNAME, luaopen_math },
|
||||
{ LUA_UTF8LIBNAME, luaopen_utf8 },
|
||||
@ -280,9 +88,11 @@ void game_tick(void) {
|
||||
lua_pop(state->L, 2);
|
||||
|
||||
/* binding */
|
||||
lua_register(state->L, "sprite", b_sprite);
|
||||
lua_register(state->L, "rectangle", b_rectangle);
|
||||
lua_register(state->L, "text", b_text);
|
||||
// lua_register(state->L, "sprite", b_sprite);
|
||||
// lua_register(state->L, "rectangle", b_rectangle);
|
||||
// lua_register(state->L, "text", b_text);
|
||||
|
||||
bindgen_load_twn(state->L);
|
||||
|
||||
/* now finally get to running the code */
|
||||
unsigned char *game_buf = NULL;
|
||||
|
Reference in New Issue
Block a user