Init new repository
This commit is contained in:
285
tools/bindgen.py
Normal file
285
tools/bindgen.py
Normal file
@@ -0,0 +1,285 @@
|
||||
import subprocess
|
||||
import json
|
||||
import code
|
||||
|
||||
def filter_types(str):
|
||||
return str.replace('_Bool', 'bool')
|
||||
|
||||
def parse_struct(decl):
|
||||
if decl.get("inner") == None:
|
||||
return None
|
||||
|
||||
outp = {}
|
||||
outp['kind'] = 'struct'
|
||||
outp['struct_kind'] = decl["tagUsed"]
|
||||
outp['name'] = decl['name']
|
||||
outp['fields'] = []
|
||||
for item_decl in decl['inner']:
|
||||
if item_decl['kind'] == 'FullComment': continue
|
||||
if item_decl['kind'] != 'FieldDecl':
|
||||
print(f"// ERROR: Structs must only contain simple fields ({decl['name']})")
|
||||
print("/*", item_decl, "*/")
|
||||
return None
|
||||
item = {}
|
||||
if 'name' in item_decl:
|
||||
item['name'] = item_decl['name']
|
||||
item['type'] = filter_types(item_decl['type']['qualType'])
|
||||
outp['fields'].append(item)
|
||||
return outp
|
||||
|
||||
def parse_enum(decl):
|
||||
if decl.get("inner") == None:
|
||||
return None
|
||||
outp = {}
|
||||
if 'name' in decl:
|
||||
outp['kind'] = 'enum'
|
||||
outp['name'] = decl['name']
|
||||
needs_value = False
|
||||
else:
|
||||
outp['kind'] = 'consts'
|
||||
needs_value = True
|
||||
outp['items'] = []
|
||||
|
||||
for item_decl in decl['inner']:
|
||||
if item_decl['kind'] == 'EnumConstantDecl':
|
||||
item = {}
|
||||
item['name'] = item_decl['name']
|
||||
if 'inner' in item_decl:
|
||||
const_expr = item_decl['inner'][0]
|
||||
if const_expr['kind'] != 'ConstantExpr':
|
||||
print(f"// ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'")
|
||||
return None
|
||||
if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue':
|
||||
print(f"// ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'")
|
||||
return None
|
||||
if not ((len(const_expr['inner']) == 1) and (const_expr['inner'][0]['kind'] == 'IntegerLiteral')):
|
||||
print(f"// ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})")
|
||||
return None
|
||||
item['value'] = const_expr['inner'][0]['value']
|
||||
if needs_value and 'value' not in item:
|
||||
print(f"// ERROR: anonymous enum items require an explicit value")
|
||||
return None
|
||||
outp['items'].append(item)
|
||||
return outp
|
||||
|
||||
def parse_func(decl):
|
||||
outp = {}
|
||||
outp['kind'] = 'func'
|
||||
outp['name'] = decl['name']
|
||||
outp['type'] = filter_types(decl['type']['qualType'])
|
||||
|
||||
outp['variadic'] = False
|
||||
if decl.get("variadic"):
|
||||
outp['variadic'] = True
|
||||
outp['params'] = []
|
||||
if 'inner' in decl:
|
||||
for param in decl['inner']:
|
||||
if param['kind'] != 'ParmVarDecl':
|
||||
if param['kind'] == 'BuiltinAttr': continue
|
||||
if param['kind'] == 'NoThrowAttr': continue
|
||||
if param['kind'] == 'FullComment': continue
|
||||
if param['kind'] == 'PureAttr': continue
|
||||
if param['kind'] == 'DLLImportAttr': continue
|
||||
if param['kind'] == 'CompoundStmt': continue # function with body
|
||||
if param['kind'] == 'AlwaysInlineAttr': continue
|
||||
if param['kind'] == 'FormatAttr':
|
||||
outp['variadic'] = True
|
||||
continue
|
||||
print(f"// warning: ignoring func {decl['name']} (unsupported parameter type) {param['kind']}")
|
||||
return None
|
||||
if param.get('name') == None: return None
|
||||
outp_param = {}
|
||||
outp_param['name'] = param['name']
|
||||
outp_param['type'] = filter_types(param['type']['qualType'])
|
||||
outp['params'].append(outp_param)
|
||||
return outp
|
||||
|
||||
def parse_typedef(decl):
|
||||
if decl['type']['qualType'].startswith("struct") or\
|
||||
decl['type']['qualType'].startswith("union") or\
|
||||
decl['type']['qualType'].startswith("enum"):
|
||||
return None
|
||||
outp = {};
|
||||
outp['kind'] = 'typedef'
|
||||
outp['name'] = decl['name']
|
||||
outp['type'] = filter_types(decl['type']['qualType'])
|
||||
return outp
|
||||
|
||||
def parse_decl(decl):
|
||||
kind = decl['kind']
|
||||
if kind == 'RecordDecl':
|
||||
return parse_struct(decl)
|
||||
elif kind == 'EnumDecl':
|
||||
return parse_enum(decl)
|
||||
elif kind == 'FunctionDecl':
|
||||
return parse_func(decl)
|
||||
elif kind == "TypedefDecl":
|
||||
return parse_typedef(decl)
|
||||
else:
|
||||
return None
|
||||
|
||||
def bindgen_enum(decl):
|
||||
r += decl["name"] + " :: typedef int;\n"
|
||||
for item in decl["items"]:
|
||||
r += item["name"]
|
||||
if item.get("value"):
|
||||
r += " :: " + item["value"]
|
||||
r += ";\n"
|
||||
return r
|
||||
|
||||
types = {
|
||||
"unsigned int": "uint",
|
||||
"unsigned long": "ulong",
|
||||
"unsigned long long": "ullong",
|
||||
"long long": "llong",
|
||||
"unsigned int": "uint",
|
||||
"unsigned": "uint",
|
||||
"unsigned short": "ushort",
|
||||
"unsigned char": "uchar",
|
||||
"uint64_t": "u64",
|
||||
"uint32_t": "u32",
|
||||
"uint16_t": "u16",
|
||||
"uint8_t": "u8",
|
||||
"int64_t": "i64",
|
||||
"int32_t": "i32",
|
||||
"int16_t": "i16",
|
||||
"int8_t": "i8",
|
||||
"size_t": "usize",
|
||||
}
|
||||
|
||||
def bindgen_type(type):
|
||||
type = type.replace("const ", "")
|
||||
type = type.strip()
|
||||
if types.get(type): return types[type]
|
||||
if type[-1] == '*': return "*" + bindgen_type(type[:-1])
|
||||
if type[-1] == ' ': return bindgen_type(type[:-1])
|
||||
if type[-1] == ']':
|
||||
i = -1
|
||||
while type[i] != '[': i += 1
|
||||
array = type[i:]
|
||||
return array + bindgen_type(type[:i])
|
||||
|
||||
func_ptr_idx = type.find('(*)')
|
||||
if func_ptr_idx != -1:
|
||||
args = type[func_ptr_idx+4:-1]
|
||||
args = args.split(",")
|
||||
new_args = []
|
||||
|
||||
name = 'a'
|
||||
for it in args:
|
||||
new_args.append(name + ": " + bindgen_type(it))
|
||||
name = chr(ord(name[0]) + 1)
|
||||
new_args = ', '.join(new_args)
|
||||
|
||||
ret = type[:func_ptr_idx]
|
||||
return "proc(" + new_args + "): " + bindgen_type(ret)
|
||||
|
||||
func_idx = type.find('(')
|
||||
if func_idx != -1:
|
||||
args = type[func_idx+1:-1]
|
||||
args = args.split(",")
|
||||
new_args = []
|
||||
|
||||
name = 'a'
|
||||
for it in args:
|
||||
new_args.append(name + ": " + bindgen_type(it))
|
||||
name = chr(ord(name[0]) + 1)
|
||||
new_args = ', '.join(new_args)
|
||||
|
||||
ret = type[:func_idx]
|
||||
return "proc(" + new_args + "): " + bindgen_type(ret)
|
||||
|
||||
return type
|
||||
|
||||
def bindgen_struct(decl):
|
||||
r = ""
|
||||
r += decl["name"] + " :: " + decl["struct_kind"] + " {\n"
|
||||
for field in decl["fields"]:
|
||||
r += " " + field["name"] + ": " + bindgen_type(field["type"]) + ";\n"
|
||||
r += "}\n"
|
||||
return r
|
||||
|
||||
def bindgen_func(decl):
|
||||
r = ""
|
||||
vargs = decl['variadic']
|
||||
r += decl["name"] + " :: proc("
|
||||
i = 0
|
||||
for param in decl["params"]:
|
||||
r += param["name"] + ": " + bindgen_type(param["type"])
|
||||
if i != len(decl["params"]) - 1 or vargs:
|
||||
r += ", "
|
||||
i += 1
|
||||
if vargs: r += ".."
|
||||
r += ")"
|
||||
decl_type = decl["type"]
|
||||
return_type = decl_type[:decl_type.index('(')].strip()
|
||||
return_type = bindgen_type(return_type)
|
||||
if return_type != "void":
|
||||
r += ": " + return_type
|
||||
r += ";"
|
||||
return r
|
||||
|
||||
def bindgen_typedef(decl):
|
||||
return decl["name"] + " :: typedef " + bindgen_type(decl["type"]) + ";"
|
||||
|
||||
|
||||
def get_clang_ast(file):
|
||||
cmd = ['clang', '-IC:/Work/', '-Xclang', '-ast-dump=json']
|
||||
cmd.append(file)
|
||||
out = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
|
||||
if out.returncode == 0:
|
||||
tu = json.loads(out.stdout)
|
||||
return tu
|
||||
return None
|
||||
|
||||
def has_prefix(decl, prefixes):
|
||||
if prefixes == None or len(prefixes) == 0: return True
|
||||
|
||||
for it in prefixes:
|
||||
if decl["name"].startswith(it):
|
||||
return True
|
||||
return False
|
||||
|
||||
def bindgen_to_stdout(file, prefixes):
|
||||
tu = get_clang_ast(file)
|
||||
if tu == None: return
|
||||
|
||||
decls = []
|
||||
for decl in tu["inner"]:
|
||||
has_name = decl.get("name")
|
||||
if has_name == None: continue
|
||||
|
||||
hp = has_prefix(decl, prefixes)
|
||||
if hp:
|
||||
out_decl = parse_decl(decl)
|
||||
if out_decl:
|
||||
decls.append(out_decl)
|
||||
|
||||
for decl in decls:
|
||||
try:
|
||||
if decl["kind"] == "func": out = bindgen_func(decl)
|
||||
elif decl["kind"] == "struct": out = bindgen_struct(decl)
|
||||
elif decl["kind"] == "enum": out = bindgen_enum(decl)
|
||||
elif decl["kind"] == "typedef": out = bindgen_typedef(decl)
|
||||
else: sys.exit("invalid decl kind", decl)
|
||||
except IndexError:
|
||||
print(f"// ERROR: generating {decl['name']} {decl['type']}")
|
||||
continue
|
||||
|
||||
print(out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description='generate bindings for a c file using clang')
|
||||
parser.add_argument('filename')
|
||||
parser.add_argument('--prefixes', nargs='+', help = 'generate declarations with these prefixes')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.filename.endswith(".c") or args.filename.endswith(".cpp"):
|
||||
print("warning: for some reason clang doesn't like c and c++ files, the file to generate bindings for need to be a header")
|
||||
|
||||
bindgen_to_stdout(args.filename, args.prefixes)
|
||||
Reference in New Issue
Block a user