#!/usr/bin/env python3 import sys import os import clang.cindex from clang.cindex import CursorKind, TypeKind, Type def map_type(clang_type: Type): canonical = clang_type.get_canonical() kind = canonical.kind spelling = ( canonical.spelling.replace("const ", "") .replace("volatile ", "") .replace("restrict ", "") .replace("struct ", "") .replace("union ", "") ) if kind == TypeKind.POINTER: pointee = canonical.get_pointee() if pointee.kind == TypeKind.RECORD: decl = pointee.get_declaration() if not decl.is_definition(): return "^void" if pointee.kind == TypeKind.CHAR_S or pointee.kind == TypeKind.CHAR_U: return "cstring" if pointee.kind == TypeKind.FUNCTIONPROTO: arg_types = [] for arg in pointee.get_canonical().argument_types(): arg_types.append(map_type(arg)) mapped_return = map_type(pointee.get_canonical().get_result()) args_str = ", ".join(arg_types) return f"func({args_str}): {mapped_return}" return f"^{map_type(pointee)}" if kind == TypeKind.CONSTANTARRAY: element_type = canonical.get_array_element_type() size = canonical.get_array_size() return f"[{size}]{map_type(element_type)}" if kind == TypeKind.INCOMPLETEARRAY: element_type = canonical.get_array_element_type() return f"[?]{map_type(element_type)}" if kind == TypeKind.FUNCTIONPROTO or kind == TypeKind.FUNCTIONNOPROTO: arg_types = [] for arg in canonical.argument_types(): arg_types.append(map_type(arg)) mapped_return = map_type(canonical.get_result()) args_str = ", ".join(arg_types) return f"func({args_str}): {mapped_return}" if kind == TypeKind.VOID: return "void" if kind == TypeKind.BOOL: return "bool" if kind in [TypeKind.CHAR_S, TypeKind.SCHAR]: return "i8" if kind == TypeKind.CHAR_U or kind == TypeKind.UCHAR: return "u8" if kind == TypeKind.SHORT: return "i16" if kind == TypeKind.USHORT: return "u16" if kind == TypeKind.INT: return "i32" if kind == TypeKind.UINT: return "u32" if kind in [TypeKind.LONG, TypeKind.LONGLONG]: return "i64" if kind in [TypeKind.ULONG, TypeKind.ULONGLONG]: return "u64" if kind == TypeKind.FLOAT: return "f32" if kind == TypeKind.DOUBLE or kind == TypeKind.LONGDOUBLE: return "f64" if kind == TypeKind.RECORD: return spelling raise Exception(f"Unresolved type: {spelling}") if len(sys.argv) != 2: print("Usage: python3 generate.py [path to header]", file=sys.stderr) sys.exit(1) filename = sys.argv[1] index = clang.cindex.Index.create() tu = index.parse(filename, ["-x", "c", "-std=c23", "-I/usr/include"]) if tu.diagnostics: for diag in tu.diagnostics: if diag.severity >= clang.cindex.Diagnostic.Error: print(f"Error: {diag.spelling}", file=sys.stderr) print(f'module "{os.path.basename(filename).split(".")[0]}"') print() seen_structs = [] for cursor in tu.cursor.walk_preorder(): if cursor.location.file and cursor.location.file.name != filename: continue if cursor.kind == CursorKind.FUNCTION_DECL: name = cursor.spelling return_type = map_type(cursor.result_type) params = [] for arg in cursor.get_arguments(): param_name = arg.spelling param_type = map_type(arg.type) params.append(f"{param_name}: {param_type}") params_str = ", ".join(params) print(f'export extern "{name}" func {name}({params_str}): {return_type}') elif cursor.kind == CursorKind.STRUCT_DECL: if cursor.get_usr() in seen_structs: continue seen_structs.append(cursor.get_usr()) if cursor.is_definition(): name = cursor.spelling print(f"export struct {name}") print("{") for field in cursor.get_children(): if field.kind == CursorKind.FIELD_DECL: field_name = field.spelling field_type = map_type(field.type) print(f" {field_name}: {field_type}") else: raise Exception( f"Unsupported child of struct: {field.spelling}: {field.kind}" ) print("}") elif cursor.kind == CursorKind.ENUM_DECL: name = cursor.spelling print(f"export enum {name} : u32") print("{") for field in cursor.get_children(): if field.kind == CursorKind.ENUM_CONSTANT_DECL: field_name = field.spelling field_value = field.enum_value print(f" {field_name} = {field_value}") else: raise Exception( f"Unsupported child of enum: {field.spelling}: {field.kind}" ) print("}")