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