Files
lib_compiler/tools/bindgen.py
2024-04-13 15:29:53 +02:00

285 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 += 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)