Compare commits

...

3 Commits

Author SHA1 Message Date
6d36cc7669 implement transpiling ImDrawList 2023-11-19 01:04:02 +09:00
a4a419c73b update ImGui to v1.9 2023-11-19 01:03:46 +09:00
c69e652716 tidy imgui4lua.py 2023-11-18 23:32:47 +09:00
2 changed files with 156 additions and 34 deletions

View File

@ -2,7 +2,9 @@ import json
import subprocess
import sys
# ---- GENERATOR DEFINITIONS
# ---- GENERATOR ALGORITHM
# generate Lua to define enum constants
def gen_enum(item):
name = item.get("name", "")
if name not in kEnumWhitelist:
@ -20,71 +22,86 @@ def gen_enum(item):
print(f"}} // {name}")
print()
def gen_func(name, ovlds):
# generate Lua API calls to define an imgui function which switches overloads and calls
def gen_func(name, ovlds, indent="", ctxptr=None):
print(f"{indent}lua_pushcfunction(L, [](auto L) {{")
print(f"{indent} (void) L;")
gen_func_def(name, ovlds, indent=indent+" ", ctxptr=ctxptr)
print(f"{indent}}});")
print(f"{indent}lua_setfield(L, -2, \"{name}\");")
print()
# generate Lua API calls to push a function which calls a appropriate overload
def gen_func_def(name, ovlds, indent="", ctxptr=None):
ovld_root = (0, {}, None)
for ovld in ovlds:
ovld_node = ovld_root
idx = 0
for arg in ovld.get("inner", []):
if "ParmVarDecl" != arg.get("kind"): continue
tname = get_type_from_argument(arg)
if tname not in kLuaTypeChecker:
break
ovld_node[1][tname] = (idx, {}, None)
if tname not in ovld_node[1]:
ovld_node[1][tname] = (ovld_node[0]+1, {}, None)
ovld_node = ovld_node[1][tname]
idx += 1
ovld_node[1]["$"] = (idx, {}, ovld)
ovld_node[1]["$"] = (ovld_node[0], {}, ovld)
def _output(ovld_node, depth=1):
indent = " "*depth
def _output(ovld_node, depth=0):
ind = indent + " "*depth
if 0 == len(ovld_node[1]):
gen_single_func(ovld_node[2], indent=indent)
gen_func_call(name, ovld_node[2], indent=ind, ctxptr=ctxptr)
elif 1 == len(ovld_node[1]):
_output(*ovld_node[1].values(), depth=depth)
else:
for tname in ovld_node[1]:
checker = kLuaTypeChecker[tname].replace("#", str(ovld_node[0]+1))
print(f"{indent}if ({checker}) {{")
print(f"{ind}if ({checker}) {{")
_output(ovld_node[1][tname], depth=depth+1)
print(f"{indent}}}")
print(f"{indent}return luaL_error(L, \"unexpected type in param#{ovld_node[0]}\");")
print("lua_pushcfunction(L, [](auto L) {")
print(" (void) L;")
_output(ovld_root)
print("});")
print(f"lua_setfield(L, -2, \"{name}\");")
print()
print(f"{ind}}}")
print(f"{ind}return luaL_error(L, \"unexpected type in param#{ovld_node[0]}\");")
def gen_single_func(item, indent=""):
name = item["name"]
_output(ovld_root)
# generate Lua to call the function declared by the AST
def gen_func_call(name, item, indent="", ctxptr=None):
pops = []
params = []
pushes = []
pcount = 0
if ctxptr is not None:
pops.append(
f"const {ctxptr}* p0 = reinterpret_cast<{ctxptr}*>(lua_topointer(L, 1))")
pcount = 1
for arg in item.get("inner", []):
if "ParmVarDecl" != arg.get("kind"): continue
pop, param, push = gen_argument(pcount, arg)
pop, param, push = gen_func_argument(pcount, arg)
pcount += 1
pops .extend(pop)
params.extend(param)
push .extend(push)
pushes.extend(push)
pushes = gen_return(item)
pushes = gen_func_return(item)
use_ret = 0 < len(pushes)
# text output
nl = "\n"+indent
cm = ", "
prefix = "ImGui::" if ctxptr is None else "p0->"
if 0 < len(pops):
print(f"{indent}{nl.join(pops)}")
if use_ret:
print(f"{indent}const auto r = ImGui::{name}({cm.join(params)});")
print(f"{indent}const auto r = {prefix}{name}({cm.join(params)});")
else:
print(f"{indent}ImGui::{name}({cm.join(params)});")
print(f"{indent}{prefix}{name}({cm.join(params)});")
if 0 < len(pushes):
print(f"{indent}{nl.join(pushes)}")
print(f"{indent}return {len(pushes)};")
def gen_return(item):
# generate Lua to push return values of the function
# returns lines of the source code
def gen_func_return(item):
ftype = item["type"]["qualType"]
type = ftype[0:ftype.find("(")-1]
@ -96,14 +113,19 @@ def gen_return(item):
return ["lua_pushnumber(L, static_cast<lua_Number>(r));"]
if "ImVec2" == type:
return [
"lua_pushnumber(L, static_cast<lua_Number>(r.x));",
"lua_pushnumber(L, static_cast<lua_Number>(r.y));",
"lua_pushnumber(L, static_cast<lua_Number>(r.x));",
"lua_pushnumber(L, static_cast<lua_Number>(r.y));",
]
print(f"unknown return type: {type}", file=sys.stderr)
return []
def gen_argument(pc, item):
# generate Lua to push one of parameters of the function
# returns a tuple with the followings:
# - string list of sentences to define variable for storing the parameter
# - string list of expressions to pass to the actual C++ function
# - string list of sentences to load the parameter
def gen_func_argument(pc, item):
type = item["type"].get("desugaredQualType", item["type"]["qualType"])
n = pc+1
pn = f"p{pc}"
@ -114,6 +136,9 @@ def gen_argument(pc, item):
if "bool" == type:
return ([f"const bool {pn} = lua_toboolean(L, {n});"], [pn], [])
if "float" == type:
return ([f"const float {pn} = static_cast<float>(lua_tonumber(L, {n}));"], [pn], [])
if "const char *" == type:
return ([f"const char* {pn} = luaL_checkstring(L, {n});"], [pn], [])
@ -129,21 +154,38 @@ def gen_argument(pc, item):
print(f"unknown argument type: {type}", file=sys.stderr)
return ([], [], [])
def gen_struct(name, funcs):
print(f"static const auto Push{name} = [](auto L, {name}* ctxptr) {{")
print(f" *reinterpret_cast<{name}**>(lua_newuserdata(sizeof({name}*))) = ctxptr;")
print(f" if (luaL_newmetatable(L, \"imgui4lua::{name}\")) {{")
for fname in funcs:
print(f" lua_createtable(L, 0, 0);")
print()
gen_func(fname, funcs[fname], indent=" ", ctxptr=name)
print(f" lua_setfield(L, -2, \"__index\")")
print(f" }}")
print(f" lua_setmetatable(L, -2);")
print(f"}};")
# ---- GENERATOR UTILITIES
# get a variable type from the AST of an argument
def get_type_from_argument(item):
return item["type"].get("desugaredQualType", item["type"]["qualType"])
# ---- WALKER DEFINITIONS
class Walker:
class RootWalker:
def __init__(self):
self._funcs = {}
self._enums = {}
self._structs = {}
def emit(self):
for x in self._enums: gen_enum(self._enums[x])
for x in self._funcs: gen_func(x, self._funcs[x])
for x in self._structs: self._structs[x].emit()
#for x in self._enums: gen_enum(self._enums[x])
#for x in self._funcs: gen_func(x, self._funcs[x])
def walk(self, item):
kind = item.get("kind")
@ -151,6 +193,9 @@ class Walker:
if "EnumDecl" == kind:
if name in kEnumWhitelist:
self._enums[name] = item
elif "CXXRecordDecl" == kind:
if name in kStructWhitelist and "inner" in item:
self._structs[name] = StructWalker(kStructWhitelist[name], item)
else:
w = self.walk
if "NamespaceDecl" == kind:
@ -170,6 +215,29 @@ class Walker:
else:
self.walk(item)
class StructWalker:
def __init__(self, whitelist, item):
self._funcWhitelist = whitelist
self._name = item.get("name")
self._funcs = {}
self._walk(item)
def _walk(self, item):
kind = item.get("kind")
name = item.get("name")
if "CXXMethodDecl" == kind:
if name in self._funcWhitelist:
if name not in self._funcs:
self._funcs[name] = [item]
else:
self._funcs[name].append(item)
else:
for child in item.get("inner", []):
self._walk(child)
def emit(self):
gen_struct(self._name, self._funcs)
# ---- DATA DEFINITIONS
kFuncWhitelist = [
@ -190,7 +258,61 @@ kFuncWhitelist = [
kEnumWhitelist = [
"ImGuiWindowFlags_",
]
kStructWhitelist = {
"ImDrawList": [
"PushClipRect",
"PushClipRectFullScreen",
"PopClipRect",
"GetClipRectMin",
"GetClipRectMax",
"PushClipRect",
"PushClipRectFullScreen",
"PopClipRect",
#"PushTextureID",
"PopTextureID",
"GetClipRectMin",
"GetClipRectMax",
"AddLine",
"AddRect",
"AddRectFilled",
"AddRectFilledMultiColor",
"AddQuad",
"AddQuadFilled",
"AddTriangle",
"AddTriangleFilled",
"AddCircle",
"AddCircleFilled",
"AddNgon",
"AddNgonFilled",
"AddEllipse",
"AddEllipseFilled",
"AddText",
#"AddPolyline",
#"AddConvexPolyFilled",
"AddBezierCubic",
"AddBezierQuadratic",
#"AddImage",
#"AddImageQuad",
#"AddImageRounded",
"PathClear",
"PathLineTo",
"PathLineToMergeDuplicate",
"PathFillConvex",
"PathStroke",
"PathArcTo",
"PathArcToFast",
"PathEllipticalArcTo",
"PathBezierCubicCurveTo",
"PathBezierQuadraticCurveTo",
"PathRect",
"AddDrawCmd",
"ChannelsSplit",
"ChannelsMerge",
"ChannelsSetCurrent",
],
}
kLuaTypeChecker = {
"ImDrawList": "lua_isudata(L, #, \"imgui4lua::@\")",
"int": "LUA_TNUMBER == lua_type(L, #)",
"const char *": "LUA_TSTRING == lua_type(L, #)",
"$": "lua_isnone(L, #)",
@ -200,7 +322,7 @@ kLuaTypeChecker = {
# ---- ENTRYPOINT
proc = subprocess.run(["clang++", "-x", "c++", "-std=c++2b", "-Xclang", "-ast-dump=json", "-fsyntax-only", sys.argv[1]], capture_output=True)
if 0 == proc.returncode:
walker = Walker()
walker = RootWalker()
walker.walk(json.loads(proc.stdout))
walker.emit()
else:

View File

@ -15,7 +15,7 @@ FetchContent_MakeAvailable(googletest)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.89.9
GIT_TAG v1.90
)
FetchContent_Populate(imgui)
include(imgui.cmake)