286 lines
9.0 KiB
Python
286 lines
9.0 KiB
Python
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 = ""
|
|
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) |