#!/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"]) def to_table(typedesc, variable, indent = 0): binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"]) for field in typedesc["fields"]: if field["type"] == "float": binding += ' ' * indent + "lua_pushnumber(L, (double)(%s));\n" % (variable + ".%s" % field["name"]) elif field["type"] == "bool": binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"]) elif field["type"] in api["types"]: binding += to_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4) else: raise BaseException("Unhandled return field type '%s'" % (field["type"])) binding += ' ' * indent + "lua_setfield(L, -2, \"%s\");\n" % field["name"] # binding += ' ' * indent + "lua_pop(L, 1);\n" return binding print('#include "twn_game_api.h"\n') # TODO: reuse implementation from the engine, this also breaks with statically compiled build print('#define STB_DS_IMPLEMENTATION') print('#include ') print('#include ') print('#include ') print('#include \n') bindings, used_converters = [], {} 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 and parameter["type"] != "float": binding += " if (lua_isnoneornil(L, -1))\n" binding += " %s = %s;\n" % (parameter["name"], default(parameter)) binding += " else\n " if parameter["type"] == "float": if "default" in parameter: binding += " int is_%s_num;\n" % parameter["name"] binding += " %s = (float)lua_tonumberx(L, -1, &is_%s_num);\n" % (parameter["name"], parameter["name"]); binding += " if (!is_%s_num) %s = %s;\n" % (parameter["name"], parameter["name"], default(parameter)) else: 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"]: used_converters[basetype] = api["types"][basetype] if parameter["type"].endswith(" *"): binding += " { %s_value = to_%s(L, -1); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]); else: binding += " %s = to_%s(L, -1);\n" % (parameter["name"], basetype.lower()); else: raise BaseException("Unhandled parameter type '%s'" % (parameter["type"])) # binding += " lua_pop(L, %i);\n" % (1 + len(procedure_desc["params"]) if "params" in procedure_desc else 0) if "return" in procedure_desc: if procedure_desc["return"] == "bool": binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"])) elif procedure_desc["return"] == "float": binding += " lua_pushnumber(L, (double)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"])) elif procedure_desc["return"] == "char *": binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"])) elif type(procedure_desc["return"]) is dict or procedure_desc["return"] in api["types"]: # TODO: handle enums type_desc = procedure_desc["return"] if type(procedure_desc["return"]) is dict else api["types"][procedure_desc["return"]] binding += " %s result = %s(%s);\n" % (type_desc["c_type"], procedure, ", ".join(param["name"] for param in procedure_desc["params"])) binding += to_table(type_desc, "result", 4) else: raise BaseException("Unhandled return type '%s'" % (procedure_desc["return"])) binding += " return 1;\n}\n" else: binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"])) binding += " return 0;\n}\n" bindings += [binding] storages, converters, initializers, deinitializers = [], [], [], [] for typename, typedesc in used_converters.items(): if "no_convert" in typedesc and typedesc["no_convert"]: continue converter = "static %s to_%s(lua_State *L, int idx) {\n" % (typename, typename.lower()) converter += " %s %s;\n" % (typename, typename.lower()); if "fields" in typedesc: 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"; # TODO: wild idea: use compile time built hash table elif "enums" in typedesc: storages += ["struct %sHashItem { char *key; %s value; };\nstatic struct %sHashItem *%s_map = NULL;\n" % (typename, typename, typename, typename.lower())] # TODO: use arena for enum in typedesc["enums"]: initializer = " shput(%s_map, \"%s\", %s);" % (typename.lower(), enum, typedesc["enums"][enum]) initializers += [initializer] deinitializers += [" shfree(%s_map);" % typename.lower()] converter += " char *value = lua_tostring(L, -1);\n"; converter += " %s = shget(%s_map, value);\n" % (typename.lower(), typename.lower()) converter += " lua_pop(L, 1);\n"; converter += " return %s;\n}\n" % (typename.lower()) converters += [converter] print('\n'.join(storages)) print("void bindgen_init(void) {\n" + '\n'.join(initializers) + "\n}\n") print('\n'.join(converters)) print('\n'.join(bindings)) loader = "void bindgen_load_%s(lua_State *L) {\n" % api["name"] loader += " bindgen_init();\n" for procedure, procedure_desc in api["procedures"].items(): loader += " lua_pushcfunction(L, binding_%s);\n" % procedure loader += " lua_setglobal(L, \"%s\");\n" % procedure loader += "}\n" print(loader) unloader = "void bindgen_unload_%s(lua_State *L) {\n" % api["name"] unloader += '\n'.join(deinitializers) unloader += "}\n" print(unloader) # exceptions for the base townengine api # TODO: is there a way to generalize it? or rather, is there any need to do so? if api["name"] == "twn": contexter = "void bindgen_build_context(lua_State *L) {\n" contexter += to_table(api["types"]["Context"], "ctx", 4) contexter += "}" print(contexter)