package cimport import "../target" import "base:runtime" import "core:dynlib" import "core:fmt" import "core:mem" import "core:os/os2" import "core:strings" CXCursor :: struct { kind: i32, xdata: i32, data: [3]rawptr, } CXType :: struct { kind: i32, data: [2]rawptr, } CXString :: struct { data: rawptr, private_flags: u32, } CXIndex :: distinct rawptr CXTranslationUnit :: distinct rawptr CXDiagnostic :: distinct rawptr Cursor_Visitor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 Api :: struct { library: dynlib.Library, create_index: proc "c"(i32, i32) -> CXIndex, dispose_index: proc "c"(CXIndex), parse_translation_unit: proc "c"(CXIndex, cstring, [^]cstring, i32, rawptr, u32, u32, ^CXTranslationUnit) -> i32, dispose_translation_unit: proc "c"(CXTranslationUnit), get_translation_unit_cursor: proc "c"(CXTranslationUnit) -> CXCursor, visit_children: proc "c"(CXCursor, Cursor_Visitor, rawptr) -> u32, get_cursor_kind: proc "c"(CXCursor) -> i32, get_cursor_spelling: proc "c"(CXCursor) -> CXString, get_cursor_usr: proc "c"(CXCursor) -> CXString, get_cursor_linkage: proc "c"(CXCursor) -> i32, get_cursor_type: proc "c"(CXCursor) -> CXType, get_typedef_underlying_type: proc "c"(CXCursor) -> CXType, get_type_declaration: proc "c"(CXType) -> CXCursor, get_canonical_type: proc "c"(CXType) -> CXType, get_pointee_type: proc "c"(CXType) -> CXType, get_result_type: proc "c"(CXType) -> CXType, get_num_arg_types: proc "c"(CXType) -> i32, get_arg_type: proc "c"(CXType, u32) -> CXType, is_const_qualified_type: proc "c"(CXType) -> u32, is_volatile_qualified_type: proc "c"(CXType) -> u32, cursor_is_variadic: proc "c"(CXCursor) -> u32, get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32, get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic, get_diagnostic_severity: proc "c"(CXDiagnostic) -> i32, get_diagnostic_spelling: proc "c"(CXDiagnostic) -> CXString, dispose_diagnostic: proc "c"(CXDiagnostic), get_cstring: proc "c"(CXString) -> cstring, dispose_string: proc "c"(CXString), } CXCursor_StructDecl :: i32(2) CXCursor_UnionDecl :: i32(3) CXCursor_EnumDecl :: i32(5) CXCursor_FunctionDecl :: i32(8) CXCursor_VarDecl :: i32(9) CXCursor_TypedefDecl :: i32(20) CXCursor_MacroDefinition :: i32(501) CXLinkage_External :: i32(4) CXType_Invalid :: i32(0) CXType_Unexposed :: i32(1) CXType_Void :: i32(2) CXType_Char_U :: i32(4) CXType_UChar :: i32(5) CXType_UShort :: i32(8) CXType_UInt :: i32(9) CXType_ULong :: i32(10) CXType_ULongLong :: i32(11) CXType_Char_S :: i32(13) CXType_SChar :: i32(14) CXType_Short :: i32(16) CXType_Int :: i32(17) CXType_Long :: i32(18) CXType_LongLong :: i32(19) CXType_Float :: i32(21) CXType_Double :: i32(22) CXType_LongDouble :: i32(23) CXType_Pointer :: i32(101) CXType_Record :: i32(105) CXType_Enum :: i32(106) CXType_Typedef :: i32(107) CXType_FunctionNoProto :: i32(110) CXType_FunctionProto :: i32(111) CXType_Elaborated :: i32(119) CXType_Attributed :: i32(163) CXChildVisit_Continue :: i32(1) CXTranslationUnit_DetailedPreprocessingRecord :: u32(0x01) CXTranslationUnit_SkipFunctionBodies :: u32(0x40) CXTranslationUnit_KeepGoing :: u32(0x200) CXDiagnostic_Error :: i32(3) load_proc :: proc(api: ^Api, name: string, destination: ^$T) -> bool { address, found := dynlib.symbol_address(api.library, name) if !found { return false } destination^ = transmute(T)address return true } load_api_from :: proc(path: string) -> (Api, bool) { api: Api library, loaded := dynlib.load_library(path) if !loaded { return {}, false } api.library = library ok := load_proc(&api, "clang_createIndex", &api.create_index) && load_proc(&api, "clang_disposeIndex", &api.dispose_index) && load_proc(&api, "clang_parseTranslationUnit2", &api.parse_translation_unit) && load_proc(&api, "clang_disposeTranslationUnit", &api.dispose_translation_unit) && load_proc(&api, "clang_getTranslationUnitCursor", &api.get_translation_unit_cursor) && load_proc(&api, "clang_visitChildren", &api.visit_children) && load_proc(&api, "clang_getCursorKind", &api.get_cursor_kind) && load_proc(&api, "clang_getCursorSpelling", &api.get_cursor_spelling) && load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) && load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) && load_proc(&api, "clang_getCursorType", &api.get_cursor_type) && load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) && load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) && load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) && load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) && load_proc(&api, "clang_getResultType", &api.get_result_type) && load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) && load_proc(&api, "clang_getArgType", &api.get_arg_type) && load_proc(&api, "clang_isConstQualifiedType", &api.is_const_qualified_type) && load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_qualified_type) && load_proc(&api, "clang_Cursor_isVariadic", &api.cursor_is_variadic) && load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) && load_proc(&api, "clang_getDiagnostic", &api.get_diagnostic) && load_proc(&api, "clang_getDiagnosticSeverity", &api.get_diagnostic_severity) && load_proc(&api, "clang_getDiagnosticSpelling", &api.get_diagnostic_spelling) && load_proc(&api, "clang_disposeDiagnostic", &api.dispose_diagnostic) && load_proc(&api, "clang_getCString", &api.get_cstring) && load_proc(&api, "clang_disposeString", &api.dispose_string) if !ok { _ = dynlib.unload_library(api.library) return {}, false } return api, true } load_api :: proc() -> (Api, bool) { if override, found := os2.lookup_env_alloc("BROLANG_LIBCLANG_PATH", context.temp_allocator); found { if api, ok := load_api_from(override); ok { return api, true } } candidates := [?]string{ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib", "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib", "/opt/homebrew/opt/llvm/lib/libclang.dylib", "/opt/homebrew/opt/llvm@21/lib/libclang.dylib", "libclang.dylib", "libclang.so", } for candidate in candidates { if api, ok := load_api_from(candidate); ok { return api, true } } return {}, false } clone_cx_string :: proc(api: ^Api, value: CXString, allocator: mem.Allocator) -> string { defer api.dispose_string(value) text := api.get_cstring(value) if text == nil { return fmt.aprintf("", allocator=allocator) } return fmt.aprintf("%s", string(text), allocator=allocator) } Context :: struct { api: ^Api, result: ^Result, allocator: mem.Allocator, } add_type :: proc(ctx: ^Context, value: Type) -> Type_Id { id := Type_Id(len(ctx.result.types)) append(&ctx.result.types, value) return id } find_record :: proc(ctx: ^Context, identity: string) -> (u32, bool) { for record, index in ctx.result.records { if record.identity == identity { return u32(index), true } } return 0, false } add_record :: proc(ctx: ^Context, declaration: CXCursor, preferred_name: string) -> u32 { identity := clone_cx_string(ctx.api, ctx.api.get_cursor_usr(declaration), ctx.allocator) if len(identity) == 0 { delete(identity, ctx.allocator) identity = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator) } if index, ok := find_record(ctx, identity); ok { delete(identity, ctx.allocator) return index } name := fmt.aprintf("%s", preferred_name, allocator=ctx.allocator) if len(name) == 0 { delete(name, ctx.allocator) name = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator) } index := u32(len(ctx.result.records)) append(&ctx.result.records, Record{name=name, identity=identity}) return index } translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := "", depth := 0) -> Type_Id { if depth > 64 || value.kind == CXType_Invalid || ctx.api.is_volatile_qualified_type(value) != 0 { return INVALID_TYPE } switch value.kind { case CXType_Void: return add_type(ctx, Type{kind=.Void, child=INVALID_TYPE}) case CXType_Char_U: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE}) case CXType_Char_S: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE}) case CXType_SChar: return add_type(ctx, Type{kind=.C_Schar, child=INVALID_TYPE}) case CXType_UChar: return add_type(ctx, Type{kind=.C_Uchar, child=INVALID_TYPE}) case CXType_Short: return add_type(ctx, Type{kind=.C_Short, child=INVALID_TYPE}) case CXType_UShort: return add_type(ctx, Type{kind=.C_Ushort, child=INVALID_TYPE}) case CXType_Int: return add_type(ctx, Type{kind=.C_Int, child=INVALID_TYPE}) case CXType_UInt: return add_type(ctx, Type{kind=.C_Uint, child=INVALID_TYPE}) case CXType_Long: return add_type(ctx, Type{kind=.C_Long, child=INVALID_TYPE}) case CXType_ULong: return add_type(ctx, Type{kind=.C_Ulong, child=INVALID_TYPE}) case CXType_LongLong: return add_type(ctx, Type{kind=.C_Longlong, child=INVALID_TYPE}) case CXType_ULongLong: return add_type(ctx, Type{kind=.C_Ulonglong, child=INVALID_TYPE}) case CXType_Float: return add_type(ctx, Type{kind=.C_Float, child=INVALID_TYPE}) case CXType_Double: return add_type(ctx, Type{kind=.C_Double, child=INVALID_TYPE}) case CXType_LongDouble: return add_type(ctx, Type{kind=.C_Longdouble, child=INVALID_TYPE}) case CXType_Pointer: pointee := ctx.api.get_pointee_type(value) child := translate_type(ctx, pointee, "", depth+1) if child == INVALID_TYPE { return INVALID_TYPE } return add_type(ctx, Type{ kind=.Pointer, child=child, mutable=ctx.api.is_const_qualified_type(pointee) == 0, }) case CXType_Record: declaration := ctx.api.get_type_declaration(value) if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl { return INVALID_TYPE } record := add_record(ctx, declaration, preferred_record_name) return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) case CXType_Typedef: declaration := ctx.api.get_type_declaration(value) name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator) defer delete(name, ctx.allocator) return translate_type(ctx, ctx.api.get_typedef_underlying_type(declaration), name, depth+1) case CXType_Elaborated, CXType_Attributed, CXType_Unexposed: canonical := ctx.api.get_canonical_type(value) if canonical.kind == value.kind { return INVALID_TYPE } return translate_type(ctx, canonical, preferred_record_name, depth+1) case CXType_Enum, CXType_FunctionNoProto, CXType_FunctionProto: return INVALID_TYPE } return INVALID_TYPE } has_named :: proc(items: []Unsupported, name: string) -> bool { for item in items { if item.name == name { return true } } return false } add_unsupported :: proc(ctx: ^Context, name, reason: string) { if len(name) == 0 || strings.has_prefix(name, "__") || has_named(ctx.result.unsupported[:], name) { return } append(&ctx.result.unsupported, Unsupported{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), reason=fmt.aprintf("%s", reason, allocator=ctx.allocator), }) } add_alias :: proc(ctx: ^Context, name: string, value: Type_Id, reason := "") { if len(name) == 0 { return } for alias in ctx.result.aliases { if alias.name == name { return } } append(&ctx.result.aliases, Alias{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), type=value, reason=fmt.aprintf("%s", reason, allocator=ctx.allocator), }) } visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { context = runtime.default_context() ctx := (^Context)(client_data) kind := ctx.api.get_cursor_kind(cursor) name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator) defer delete(name, ctx.allocator) switch kind { case CXCursor_FunctionDecl: if len(name) == 0 { return CXChildVisit_Continue } reason := "" linkage := ctx.api.get_cursor_linkage(cursor) if linkage != CXLinkage_External { reason = "static and non-external C functions are not supported" } variadic := ctx.api.cursor_is_variadic(cursor) != 0 function_type := ctx.api.get_cursor_type(cursor) result_type := translate_type(ctx, ctx.api.get_result_type(function_type)) if result_type == INVALID_TYPE && len(reason) == 0 { reason = "function result type is not supported" } params: [dynamic]Type_Id params.allocator = ctx.allocator count := ctx.api.get_num_arg_types(function_type) if count < 0 { reason = "function declaration has no prototype" } else { for index in 0.. 0 { record := add_record(ctx, cursor, name) value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) add_alias(ctx, name, value) } case CXCursor_UnionDecl: add_unsupported(ctx, name, "C unions are not supported") case CXCursor_EnumDecl: add_unsupported(ctx, name, "C enums are not supported") case CXCursor_VarDecl: add_unsupported(ctx, name, "external C variables are not supported") case CXCursor_MacroDefinition: add_unsupported(ctx, name, "C macros are not supported") case: } return CXChildVisit_Continue } import_with_libclang :: proc(_: rawptr, request: Request, allocator: mem.Allocator) -> Result { result := init_result(allocator) api, loaded := load_api() if !loaded { result.infrastructure = true result.error_message = fmt.aprintf( "could not load libclang; set BROLANG_LIBCLANG_PATH to a compatible library", allocator=allocator, ) return result } defer _ = dynlib.unload_library(api.library) index := api.create_index(1, 0) if index == nil { result.infrastructure = true result.error_message = fmt.aprintf("could not create libclang index", allocator=allocator) return result } defer api.dispose_index(index) arguments: [dynamic]string arguments.allocator = context.temp_allocator append(&arguments, "-x", "c", "-target", target.llvm_triple(request.target)) for path in request.include_paths { append(&arguments, fmt.tprintf("-I%s", path)) } for define in request.defines { append(&arguments, fmt.tprintf("-D%s", define)) } c_arguments := make([]cstring, len(arguments), context.temp_allocator) for argument, argument_index in arguments { c_arguments[argument_index] = strings.clone_to_cstring(argument, context.temp_allocator) } c_path := strings.clone_to_cstring(request.path, context.temp_allocator) translation_unit: CXTranslationUnit error_code := api.parse_translation_unit( index, c_path, raw_data(c_arguments), i32(len(c_arguments)), nil, 0, CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing, &translation_unit, ) if error_code != 0 || translation_unit == nil { result.error_message = fmt.aprintf("libclang could not parse header '%s'", request.path, allocator=allocator) return result } defer api.dispose_translation_unit(translation_unit) for diagnostic_index in 0..= CXDiagnostic_Error && len(result.error_message) == 0 { result.error_message = clone_cx_string(&api, api.get_diagnostic_spelling(diagnostic), allocator) } api.dispose_diagnostic(diagnostic) } if len(result.error_message) > 0 { return result } ctx := Context{api=&api, result=&result, allocator=allocator} root := api.get_translation_unit_cursor(translation_unit) _ = api.visit_children(root, visit_cursor, &ctx) result.available = true return result }