package cimport import "../target" import "base:runtime" import "core:dynlib" import "core:fmt" import "core:hash" import "core:math" import "core:mem" import "core:os/os2" import "core:strconv" 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, } CXSourceLocation :: struct { ptr_data: [2]rawptr, int_data: u32, } CXSourceRange :: struct { ptr_data: [2]rawptr, begin_int_data: u32, end_int_data: u32, } CXToken :: struct { int_data: [4]u32, ptr_data: rawptr, } CXUnsavedFile :: struct { filename: cstring, contents: cstring, length: uint, } CXIndex :: distinct rawptr CXTranslationUnit :: distinct rawptr CXDiagnostic :: distinct rawptr CXFile :: 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, [^]CXUnsavedFile, 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_extent: proc "c"(CXCursor) -> CXSourceRange, get_cursor_location: proc "c"(CXCursor) -> CXSourceLocation, get_cursor_linkage: proc "c"(CXCursor) -> i32, get_cursor_tls_kind: proc "c"(CXCursor) -> i32, get_cursor_definition: proc "c"(CXCursor) -> CXCursor, is_cursor_definition: proc "c"(CXCursor) -> u32, get_file: proc "c"(CXTranslationUnit, cstring) -> CXFile, get_file_contents: proc "c"(CXTranslationUnit, CXFile, ^uint) -> [^]byte, get_file_location: proc "c"(CXSourceLocation, ^CXFile, ^u32, ^u32, ^u32), get_cursor_type: proc "c"(CXCursor) -> CXType, get_type_spelling: proc "c"(CXType) -> CXString, get_typedef_underlying_type: proc "c"(CXCursor) -> CXType, get_type_declaration: proc "c"(CXType) -> CXCursor, get_enum_decl_integer_type: proc "c"(CXCursor) -> CXType, get_enum_constant_value: proc "c"(CXCursor) -> i64, get_enum_constant_unsigned: proc "c"(CXCursor) -> u64, get_canonical_type: proc "c"(CXType) -> CXType, get_pointee_type: proc "c"(CXType) -> CXType, get_array_element_type: proc "c"(CXType) -> CXType, get_array_size: proc "c"(CXType) -> i64, get_result_type: proc "c"(CXType) -> CXType, get_num_arg_types: proc "c"(CXType) -> i32, get_arg_type: proc "c"(CXType, u32) -> CXType, is_function_type_variadic: proc "c"(CXType) -> u32, is_const_qualified_type: proc "c"(CXType) -> u32, is_volatile_qualified_type: proc "c"(CXType) -> u32, cursor_is_macro_function_like: proc "c"(CXCursor) -> u32, cursor_is_macro_builtin: proc "c"(CXCursor) -> u32, cursor_is_bitfield: proc "c"(CXCursor) -> u32, cursor_is_function_inlined: proc "c"(CXCursor) -> u32, cursor_get_offset_of_field: proc "c"(CXCursor) -> i64, type_get_size_of: proc "c"(CXType) -> i64, type_get_align_of: proc "c"(CXType) -> i64, 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), tokenize: proc "c"(CXTranslationUnit, CXSourceRange, ^[^]CXToken, ^u32), get_token_spelling: proc "c"(CXTranslationUnit, CXToken) -> CXString, dispose_tokens: proc "c"(CXTranslationUnit, [^]CXToken, u32), 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) CXCursor_FieldDecl :: i32(6) CXCursor_EnumConstantDecl :: i32(7) CXLinkage_External :: i32(4) CXTLS_None :: i32(0) 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_ConstantArray :: i32(112) CXType_IncompleteArray :: i32(114) CXType_VariableArray :: i32(115) CXType_DependentSizedArray :: i32(116) 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_getCursorExtent", &api.get_cursor_extent) && load_proc(&api, "clang_getCursorLocation", &api.get_cursor_location) && load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) && load_proc(&api, "clang_getCursorTLSKind", &api.get_cursor_tls_kind) && load_proc(&api, "clang_getCursorDefinition", &api.get_cursor_definition) && load_proc(&api, "clang_isCursorDefinition", &api.is_cursor_definition) && load_proc(&api, "clang_getFile", &api.get_file) && load_proc(&api, "clang_getFileContents", &api.get_file_contents) && load_proc(&api, "clang_getFileLocation", &api.get_file_location) && load_proc(&api, "clang_getCursorType", &api.get_cursor_type) && load_proc(&api, "clang_getTypeSpelling", &api.get_type_spelling) && load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) && load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) && load_proc(&api, "clang_getEnumDeclIntegerType", &api.get_enum_decl_integer_type) && load_proc(&api, "clang_getEnumConstantDeclValue", &api.get_enum_constant_value) && load_proc(&api, "clang_getEnumConstantDeclUnsignedValue", &api.get_enum_constant_unsigned) && load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) && load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) && load_proc(&api, "clang_getArrayElementType", &api.get_array_element_type) && load_proc(&api, "clang_getArraySize", &api.get_array_size) && 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_isFunctionTypeVariadic", &api.is_function_type_variadic) && 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_isMacroFunctionLike", &api.cursor_is_macro_function_like) && load_proc(&api, "clang_Cursor_isMacroBuiltin", &api.cursor_is_macro_builtin) && load_proc(&api, "clang_Cursor_isBitField", &api.cursor_is_bitfield) && load_proc(&api, "clang_Cursor_isFunctionInlined", &api.cursor_is_function_inlined) && load_proc(&api, "clang_Cursor_getOffsetOfField", &api.cursor_get_offset_of_field) && load_proc(&api, "clang_Type_getSizeOf", &api.type_get_size_of) && load_proc(&api, "clang_Type_getAlignOf", &api.type_get_align_of) && 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_tokenize", &api.tokenize) && load_proc(&api, "clang_getTokenSpelling", &api.get_token_spelling) && load_proc(&api, "clang_disposeTokens", &api.dispose_tokens) && 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) } Macro_State :: struct { name: string, defined: bool, } Macro_Table :: struct { items: [dynamic]Macro_State, lookup: map[string]int, allocator: mem.Allocator, } Context :: struct { api: ^Api, translation_unit: CXTranslationUnit, result: ^Result, final_macros: ^Macro_Table, variable_lookup: map[string]int, allocator: mem.Allocator, target: target.Target, header_path: string, } 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, kind=.Union if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl else .Struct, reason=fmt.aprintf("", allocator=ctx.allocator), }) ctx.result.records[index].fields.allocator = ctx.allocator return index } Record_Field_Context :: struct { ctx: ^Context, record: u32, } visit_record_field :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { context = runtime.default_context() field_ctx := (^Record_Field_Context)(client_data) ctx := field_ctx.ctx if ctx.api.get_cursor_kind(cursor) != CXCursor_FieldDecl { return CXChildVisit_Continue } if len(ctx.result.records[field_ctx.record].reason) > 0 { return CXChildVisit_Continue } name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator) if len(name) == 0 { delete(name, ctx.allocator) delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) ctx.result.records[field_ctx.record].reason = fmt.aprintf("anonymous C record fields are not supported", allocator=ctx.allocator) return CXChildVisit_Continue } field_type := ctx.api.get_cursor_type(cursor) if ctx.api.cursor_is_bitfield(cursor) != 0 { delete(name, ctx.allocator) delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) ctx.result.records[field_ctx.record].reason = fmt.aprintf("C bitfields are not supported", allocator=ctx.allocator) return CXChildVisit_Continue } if ctx.api.is_const_qualified_type(field_type) != 0 || ctx.api.is_volatile_qualified_type(field_type) != 0 { delete(name, ctx.allocator) delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) ctx.result.records[field_ctx.record].reason = fmt.aprintf("qualified C record fields are not supported", allocator=ctx.allocator) return CXChildVisit_Continue } if field_type.kind == CXType_IncompleteArray || field_type.kind == CXType_VariableArray || field_type.kind == CXType_DependentSizedArray { delete(name, ctx.allocator) delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) ctx.result.records[field_ctx.record].reason = fmt.aprintf("flexible or variable C array fields are not supported", allocator=ctx.allocator) return CXChildVisit_Continue } translated := translate_type(ctx, field_type) offset_bits := ctx.api.cursor_get_offset_of_field(cursor) if translated == INVALID_TYPE || offset_bits < 0 || offset_bits%8 != 0 { delete(name, ctx.allocator) delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) ctx.result.records[field_ctx.record].reason = fmt.aprintf("C record field type or layout is not supported", allocator=ctx.allocator) return CXChildVisit_Continue } append(&ctx.result.records[field_ctx.record].fields, Field{name=name, type=translated, offset=u64(offset_bits/8)}) return CXChildVisit_Continue } populate_record :: proc(ctx: ^Context, index: u32, declaration: CXCursor) { if ctx.result.records[index].complete || len(ctx.result.records[index].reason) > 0 { return } definition := declaration if ctx.api.is_cursor_definition(definition) == 0 { definition = ctx.api.get_cursor_definition(declaration) } if ctx.api.is_cursor_definition(definition) == 0 { return } record_type := ctx.api.get_cursor_type(definition) size := ctx.api.type_get_size_of(record_type) alignment := ctx.api.type_get_align_of(record_type) if size < 0 || alignment <= 0 { delete(ctx.result.records[index].reason, ctx.allocator) ctx.result.records[index].reason = fmt.aprintf("C record size or alignment is not supported", allocator=ctx.allocator) return } ctx.result.records[index].kind = .Union if ctx.api.get_cursor_kind(definition) == CXCursor_UnionDecl else .Struct ctx.result.records[index].size = u64(size) ctx.result.records[index].alignment = u32(alignment) field_ctx := Record_Field_Context{ctx=ctx, record=index} _ = ctx.api.visit_children(definition, visit_record_field, &field_ctx) ctx.result.records[index].complete = len(ctx.result.records[index].reason) == 0 } 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 } mutable := ctx.api.is_const_qualified_type(pointee) == 0 if pointee.kind == CXType_FunctionProto { mutable = false } return add_type(ctx, Type{ kind=.Pointer, child=child, mutable=mutable, }) case CXType_ConstantArray: count := ctx.api.get_array_size(value) element := ctx.api.get_array_element_type(value) child := translate_type(ctx, element, "", depth+1) if count <= 0 || child == INVALID_TYPE { return INVALID_TYPE } return add_type(ctx, Type{ kind=.Array, child=child, count=u64(count), mutable=ctx.api.is_const_qualified_type(value) == 0 && ctx.api.is_const_qualified_type(element) == 0, }) case CXType_Record: declaration := ctx.api.get_type_declaration(value) record := add_record(ctx, declaration, preferred_record_name) populate_record(ctx, record, declaration) return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) case CXType_Enum: declaration := ctx.api.get_type_declaration(value) backing := ctx.api.get_enum_decl_integer_type(declaration) return translate_type(ctx, backing, preferred_record_name, depth+1) case CXType_FunctionProto: result := translate_type(ctx, ctx.api.get_result_type(value), "", depth+1) if result == INVALID_TYPE { return INVALID_TYPE } count := ctx.api.get_num_arg_types(value) if count < 0 { return INVALID_TYPE } params: [dynamic]Type_Id params.allocator = ctx.allocator for index in 0.. bool { for item in items { if item.name == name { return true } } return false } add_unsupported :: proc(ctx: ^Context, name, reason: string, final_macro := false) { 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), final_macro=final_macro, }) } 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), }) } enum_backing_unsigned :: proc(value: CXType) -> bool { switch value.kind { case CXType_Char_U, CXType_UChar, CXType_UShort, CXType_UInt, CXType_ULong, CXType_ULongLong: return true } return false } Enum_Constant_Context :: struct { ctx: ^Context, backing: Type_Id, unsigned: bool, } visit_enum_constant :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { context = runtime.default_context() enum_ctx := (^Enum_Constant_Context)(client_data) ctx := enum_ctx.ctx if ctx.api.get_cursor_kind(cursor) != CXCursor_EnumConstantDecl { return CXChildVisit_Continue } name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator) defer delete(name, ctx.allocator) if len(name) == 0 { return CXChildVisit_Continue } value := Macro_Value{kind=.Integer, type=enum_ctx.backing} if enum_ctx.unsigned { value.integer = ctx.api.get_enum_constant_unsigned(cursor) } else { signed := ctx.api.get_enum_constant_value(cursor) value.negative = signed < 0 value.integer = u64(-i128(signed)) if signed < 0 else u64(signed) } append(&ctx.result.macros, Macro_Constant{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), type=enum_ctx.backing, value=value, reason=fmt.aprintf("", allocator=ctx.allocator), }) return CXChildVisit_Continue } is_macro_identifier :: proc(value: string) -> bool { if len(value) == 0 { return false } is_start := proc(value: byte) -> bool { return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z' } if !is_start(value[0]) { return false } for byte_value in transmute([]byte)value[1:] { if !is_start(byte_value) && !(byte_value >= '0' && byte_value <= '9') { return false } } return true } init_macro_table :: proc(allocator: mem.Allocator) -> Macro_Table { result: Macro_Table result.items.allocator = allocator result.lookup.allocator = allocator result.allocator = allocator return result } destroy_macro_table :: proc(table: ^Macro_Table) { delete(table.lookup) for item in table.items { delete(item.name, table.allocator) } delete(table.items) } add_macro_candidate :: proc(table: ^Macro_Table, name: string) -> int { if index, ok := table.lookup[name]; ok { return index } cloned := fmt.aprintf("%s", name, allocator=table.allocator) index := len(table.items) append(&table.items, Macro_State{name=cloned}) table.lookup[cloned] = index return index } macro_state_defined :: proc(table: ^Macro_Table, name: string) -> bool { index, ok := table.lookup[name] return ok && index >= 0 && index < len(table.items) && table.items[index].defined } destroy_macro_constant :: proc(item: Macro_Constant, allocator: mem.Allocator) { delete(item.name, allocator) delete(item.type_name, allocator) delete(item.values, allocator) delete(item.reason, allocator) } remove_macro_constant :: proc(ctx: ^Context, name: string) { index := 0 for index < len(ctx.result.macros) { if ctx.result.macros[index].name == name { destroy_macro_constant(ctx.result.macros[index], ctx.allocator) ordered_remove(&ctx.result.macros, index) continue } index += 1 } } remove_unsupported :: proc(ctx: ^Context, name: string) { index := 0 for index < len(ctx.result.unsupported) { if ctx.result.unsupported[index].name == name { delete(ctx.result.unsupported[index].name, ctx.allocator) delete(ctx.result.unsupported[index].reason, ctx.allocator) ordered_remove(&ctx.result.unsupported, index) continue } index += 1 } } clear_macro_import :: proc(ctx: ^Context, name: string) { remove_macro_constant(ctx, name) remove_unsupported(ctx, name) } macro_c_primitive :: proc(kind: Type_Kind) -> (target.C_Primitive, bool) { #partial switch kind { case .C_Int: return .Int, true case .C_Uint: return .Uint, true case .C_Long: return .Long, true case .C_Ulong: return .Ulong, true case .C_Longlong: return .Longlong, true case .C_Ulonglong: return .Ulonglong, true case: } return {}, false } macro_integer_bits_signed :: proc(kind: Type_Kind, selected: target.Target) -> (int, bool, bool) { primitive, ok := macro_c_primitive(kind) if !ok { return 0, false, false } layout := target.c_primitive_layout(selected, primitive) return layout.bits, layout.kind == .Signed_Integer, true } macro_unsigned_max :: proc(bits: int) -> u64 { if bits >= 64 { return max(u64) } return (u64(1) << u32(bits)) - 1 } macro_signed_max :: proc(bits: int) -> u64 { return (u64(1) << u32(bits-1)) - 1 } macro_type_fits :: proc(kind: Type_Kind, magnitude: u64, selected: target.Target) -> bool { bits, signed, ok := macro_integer_bits_signed(kind, selected) if !ok || bits <= 0 || bits > 64 { return false } if signed { return magnitude <= macro_signed_max(bits) } return magnitude <= macro_unsigned_max(bits) } wrap_negative_unsigned_macro :: proc(magnitude: u64, bits: int) -> u64 { if magnitude == 0 { return 0 } return ((max(u64)-magnitude)+1) & macro_unsigned_max(bits) } select_macro_integer :: proc( ctx: ^Context, magnitude: u64, negative: bool, unsigned_suffix: bool, long_count: int, nondecimal: bool, ) -> (Macro_Value, Type_Id, bool) { candidates: []Type_Kind if unsigned_suffix { if long_count >= 2 { candidates = []Type_Kind{.C_Ulonglong} } else if long_count == 1 { candidates = []Type_Kind{.C_Ulong, .C_Ulonglong} } else { candidates = []Type_Kind{.C_Uint, .C_Ulong, .C_Ulonglong} } } else if long_count >= 2 { if nondecimal { candidates = []Type_Kind{.C_Longlong, .C_Ulonglong} } else { candidates = []Type_Kind{.C_Longlong} } } else if long_count == 1 { if nondecimal { candidates = []Type_Kind{.C_Long, .C_Ulong, .C_Longlong, .C_Ulonglong} } else { candidates = []Type_Kind{.C_Long, .C_Longlong} } } else if nondecimal { candidates = []Type_Kind{.C_Int, .C_Uint, .C_Long, .C_Ulong, .C_Longlong, .C_Ulonglong} } else { candidates = []Type_Kind{.C_Int, .C_Long, .C_Longlong} } for kind in candidates { if !macro_type_fits(kind, magnitude, ctx.target) { continue } macro_type := add_type(ctx, Type{kind=kind, child=INVALID_TYPE}) value := Macro_Value{kind=.Integer, type=macro_type, integer=magnitude} bits, signed, _ := macro_integer_bits_signed(kind, ctx.target) if negative { if signed { value.negative = true } else { value.integer = wrap_negative_unsigned_macro(magnitude, bits) } } return value, macro_type, true } return {}, INVALID_TYPE, false } macro_digit_value :: proc(value: byte) -> (u64, bool) { if value >= '0' && value <= '9' { return u64(value-'0'), true } if value >= 'a' && value <= 'f' { return u64(value-'a') + 10, true } if value >= 'A' && value <= 'F' { return u64(value-'A') + 10, true } return 0, false } parse_macro_integer_literal :: proc(text: string) -> ( magnitude: u64, unsigned_suffix: bool, long_count: int, nondecimal: bool, ok: bool, ) { if len(text) == 0 { return } base := u64(10) start := 0 if len(text) > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X') { base = 16 start = 2 nondecimal = true } else if len(text) > 2 && text[0] == '0' && (text[1] == 'b' || text[1] == 'B') { base = 2 start = 2 nondecimal = true } else if len(text) > 0 && text[0] == '0' { base = 8 nondecimal = true } if start >= len(text) { return } index := start for index < len(text) { digit, digit_ok := macro_digit_value(text[index]) if !digit_ok || digit >= base { break } if magnitude > (max(u64)-digit)/base { return } magnitude = magnitude*base + digit index += 1 } if index == start { return } for index < len(text) { byte := text[index] if byte == 'u' || byte == 'U' { if unsigned_suffix { return } unsigned_suffix = true index += 1 continue } if byte == 'l' || byte == 'L' { if long_count != 0 { return } long_count = 1 index += 1 if index < len(text) && (text[index] == 'l' || text[index] == 'L') { long_count = 2 index += 1 } continue } return } ok = true return } strip_float_suffix :: proc(text: string) -> string { if len(text) > 0 { last := text[len(text)-1] if last == 'f' || last == 'F' || last == 'l' || last == 'L' { return text[:len(text)-1] } } return text } macro_float_kind :: proc(text: string) -> Type_Kind { if len(text) == 0 { return .C_Double } switch text[len(text)-1] { case 'f', 'F': return .C_Float case 'l', 'L': return .C_Longdouble case: return .C_Double } return .C_Double } parse_macro_scalar :: proc(ctx: ^Context, texts: []string) -> (Macro_Value, Type_Id, bool) { negative := false literal := "" if len(texts) == 1 { literal = texts[0] } else if len(texts) == 2 && (texts[0] == "-" || texts[0] == "+") { negative = texts[0] == "-" literal = texts[1] } else { return {}, INVALID_TYPE, false } magnitude, unsigned_suffix, long_count, nondecimal, literal_ok := parse_macro_integer_literal(literal) if literal_ok { return select_macro_integer(ctx, magnitude, negative, unsigned_suffix, long_count, nondecimal) } if strings.contains(literal, ".") || strings.contains(literal, "e") || strings.contains(literal, "E") || strings.contains(literal, "p") || strings.contains(literal, "P") { number, ok := strconv.parse_f64(strip_float_suffix(literal)) if !ok { return {}, INVALID_TYPE, false } if negative { number = -number } kind := macro_float_kind(literal) stored := number if kind == .C_Float { stored = f64(f32(number)) } if math.is_nan(stored) || math.is_inf(stored) { return {}, INVALID_TYPE, false } macro_type := add_type(ctx, Type{kind=kind, child=INVALID_TYPE}) return Macro_Value{ kind=.Float, type=macro_type, integer=transmute(u64)stored, }, macro_type, true } return {}, INVALID_TYPE, false } parse_macro_value_list :: proc(ctx: ^Context, texts: []string, values: ^[dynamic]Macro_Value) -> bool { index := 0 for index < len(texts) { if texts[index] == "," { index += 1 continue } start := index for index < len(texts) && texts[index] != "," { index += 1 } if start == index { return false } value, _, ok := parse_macro_scalar(ctx, texts[start:index]) if !ok { return false } append(values, value) } return true } parse_macro_aggregate :: proc(ctx: ^Context, name: string, texts: []string) -> (Macro_Constant, bool) { type_name := "" open_index := -1 if len(texts) >= 6 && texts[0] == "CLITERAL" && texts[1] == "(" && texts[3] == ")" && texts[4] == "{" { type_name = texts[2] open_index = 4 } else if len(texts) >= 5 && texts[0] == "(" && texts[2] == ")" && texts[3] == "{" { type_name = texts[1] open_index = 3 } if open_index < 0 || !is_macro_identifier(type_name) || texts[len(texts)-1] != "}" { return {}, false } values: [dynamic]Macro_Value values.allocator = ctx.allocator if !parse_macro_value_list(ctx, texts[open_index+1:len(texts)-1], &values) { delete(values) return {}, false } return Macro_Constant{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), type_name=fmt.aprintf("%s", type_name, allocator=ctx.allocator), values=values[:], aggregate=true, reason=fmt.aprintf("", allocator=ctx.allocator), }, true } import_macro_constant :: proc(ctx: ^Context, cursor: CXCursor, name: string) -> bool { if len(name) == 0 || strings.has_prefix(name, "__") || !macro_state_defined(ctx.final_macros, name) || ctx.api.cursor_is_macro_builtin(cursor) != 0 { return false } clear_macro_import(ctx, name) if ctx.api.cursor_is_macro_function_like(cursor) != 0 { add_unsupported(ctx, name, "C function-like macros are not supported", true) return true } tokens: [^]CXToken token_count: u32 ctx.api.tokenize(ctx.translation_unit, ctx.api.get_cursor_extent(cursor), &tokens, &token_count) defer { if token_count > 0 { ctx.api.dispose_tokens(ctx.translation_unit, tokens, token_count) } } if token_count <= 1 { add_unsupported(ctx, name, "C macro has no replacement value", true) return true } texts := make([]string, int(token_count), ctx.allocator) defer { for text in texts { delete(text, ctx.allocator) } delete(texts, ctx.allocator) } for index := 0; index < int(token_count); index += 1 { texts[index] = clone_cx_string(ctx.api, ctx.api.get_token_spelling(ctx.translation_unit, tokens[index]), ctx.allocator) } replacement := texts[1:] if value, ok := parse_macro_aggregate(ctx, name, replacement); ok { append(&ctx.result.macros, value) return true } if value, macro_type, ok := parse_macro_scalar(ctx, replacement); ok { append(&ctx.result.macros, Macro_Constant{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), type=macro_type, value=value, reason=fmt.aprintf("", allocator=ctx.allocator), }) return true } add_unsupported(ctx, name, "C macro is not a supported constant", true) return true } cx_file_valid :: proc(file: CXFile) -> bool { return rawptr(file) != nil } cursor_source_file_offset :: proc(api: ^Api, cursor: CXCursor) -> (CXFile, u32) { file: CXFile line, column, offset: u32 api.get_file_location(api.get_cursor_location(cursor), &file, &line, &column, &offset) return file, offset } Macro_Collect_Context :: struct { api: ^Api, candidates: ^Macro_Table, } visit_macro_candidate :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { context = runtime.default_context() ctx := (^Macro_Collect_Context)(client_data) if ctx.api.get_cursor_kind(cursor) != CXCursor_MacroDefinition || ctx.api.cursor_is_macro_builtin(cursor) != 0 { return CXChildVisit_Continue } file, _ := cursor_source_file_offset(ctx.api, cursor) if !cx_file_valid(file) { return CXChildVisit_Continue } name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.candidates.allocator) defer delete(name, ctx.candidates.allocator) if is_macro_identifier(name) && !strings.has_prefix(name, "__") { _ = add_macro_candidate(ctx.candidates, name) } return CXChildVisit_Continue } MACRO_PROBE_PREFIX :: "__BROLANG_FINAL_MACRO_" build_macro_probe_source :: proc( api: ^Api, translation_unit: CXTranslationUnit, c_path: cstring, candidates: ^Macro_Table, allocator: mem.Allocator, ) -> (string, u32, bool) { root_file := api.get_file(translation_unit, c_path) if !cx_file_valid(root_file) { return "", 0, false } size: uint contents := api.get_file_contents(translation_unit, root_file, &size) if contents == nil || size > uint(max(u32)) || size > uint(max(int)) { return "", 0, false } builder := strings.builder_make(allocator) defer strings.builder_destroy(&builder) if strings.write_bytes(&builder, contents[:int(size)]) != int(size) { return "", 0, false } for candidate, index in candidates.items { fmt.sbprintf( &builder, "\n#if defined(%s)\n#define %s%d 1\n#endif\n", candidate.name, MACRO_PROBE_PREFIX, index, ) } return fmt.aprintf("%s", strings.to_string(builder), allocator=allocator), u32(size), true } Macro_Probe_Context :: struct { api: ^Api, candidates: ^Macro_Table, root_file: CXFile, probe_start: u32, } visit_macro_probe :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { context = runtime.default_context() ctx := (^Macro_Probe_Context)(client_data) if ctx.api.get_cursor_kind(cursor) != CXCursor_MacroDefinition { return CXChildVisit_Continue } file, offset := cursor_source_file_offset(ctx.api, cursor) if file != ctx.root_file || offset < ctx.probe_start { return CXChildVisit_Continue } name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), context.temp_allocator) if !strings.has_prefix(name, MACRO_PROBE_PREFIX) { return CXChildVisit_Continue } index, ok := strconv.parse_uint(name[len(MACRO_PROBE_PREFIX):]) if ok && index < uint(len(ctx.candidates.items)) { ctx.candidates.items[index].defined = true } return CXChildVisit_Continue } translation_unit_error :: proc(api: ^Api, translation_unit: CXTranslationUnit, allocator: mem.Allocator) -> string { for diagnostic_index in 0..= CXDiagnostic_Error { message := clone_cx_string(api, api.get_diagnostic_spelling(diagnostic), allocator) api.dispose_diagnostic(diagnostic) return message } api.dispose_diagnostic(diagnostic) } return "" } probe_final_macros :: proc( api: ^Api, index: CXIndex, translation_unit: CXTranslationUnit, c_path: cstring, c_arguments: []cstring, path: string, candidates: ^Macro_Table, allocator: mem.Allocator, ) -> (string, bool) { if len(candidates.items) == 0 { return "", true } probe_source, probe_start, source_ok := build_macro_probe_source( api, translation_unit, c_path, candidates, allocator, ) if !source_ok { return fmt.aprintf("libclang could not read header '%s' for macro probing", path, allocator=allocator), false } defer delete(probe_source, allocator) probe_contents := strings.clone_to_cstring(probe_source, context.temp_allocator) unsaved_file := CXUnsavedFile{ filename=c_path, contents=probe_contents, length=uint(len(probe_source)), } probe_translation_unit: CXTranslationUnit error_code := api.parse_translation_unit( index, c_path, raw_data(c_arguments), i32(len(c_arguments)), &unsaved_file, 1, CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing, &probe_translation_unit, ) if error_code != 0 || probe_translation_unit == nil { return fmt.aprintf("libclang could not probe final macros for header '%s'", path, allocator=allocator), false } defer api.dispose_translation_unit(probe_translation_unit) if message := translation_unit_error(api, probe_translation_unit, allocator); len(message) > 0 { return message, false } probe_ctx := Macro_Probe_Context{ api=api, candidates=candidates, root_file=api.get_file(probe_translation_unit, c_path), probe_start=probe_start, } _ = api.visit_children(api.get_translation_unit_cursor(probe_translation_unit), visit_macro_probe, &probe_ctx) return "", true } c_variable_writable :: proc(api: ^Api, value: CXType, depth := 0) -> bool { if depth > 64 || value.kind == CXType_Invalid || api.is_const_qualified_type(value) != 0 { return false } canonical := api.get_canonical_type(value) if canonical.kind != CXType_Invalid && api.is_const_qualified_type(canonical) != 0 { return false } if value.kind == CXType_ConstantArray { return c_variable_writable(api, api.get_array_element_type(value), depth+1) } if canonical.kind == CXType_ConstantArray { return c_variable_writable(api, api.get_array_element_type(canonical), depth+1) } return true } add_or_upgrade_variable :: proc( ctx: ^Context, name: string, variable_type: Type_Id, mutable: bool, reason: string, ) { if index, found := ctx.variable_lookup[name]; found { previous := &ctx.result.variables[index] if len(previous.reason) > 0 && len(reason) == 0 && variable_type != INVALID_TYPE { previous.type = variable_type previous.mutable = mutable delete(previous.reason, ctx.allocator) previous.reason = fmt.aprintf("", allocator=ctx.allocator) } return } index := len(ctx.result.variables) append(&ctx.result.variables, Variable{ name=fmt.aprintf("%s", name, allocator=ctx.allocator), type=variable_type, mutable=mutable, reason=fmt.aprintf("%s", reason, allocator=ctx.allocator), }) ctx.variable_lookup[ctx.result.variables[index].name] = index } INLINE_TRAMPOLINE_PREFIX :: "__brolang_inline_" // declarator_safe reports whether ` name` is a valid C declarator. // Plain identifiers, pointers, and records satisfy this; function pointers and // arrays that are not hidden behind a typedef embed the name inside their // spelling (e.g. `int (*)(int)`, `int[2]`) and are rejected so we never emit a // malformed wrapper. This is purely a limitation of the simple forwarder, not // the type system: brolang itself can represent these types, so such a // `static inline` is reported as unsupported rather than wrapped. declarator_safe :: proc(spelling: string) -> bool { return len(spelling) > 0 && !strings.contains(spelling, "(") && !strings.contains(spelling, "[") } // build_inline_trampoline synthesizes an external C wrapper that forwards to an // internal-linkage (typically `static inline`) C function, returning the wrapper // symbol and recording its source on the result. It fails when any parameter or // result type needs a complex declarator the simple forwarder cannot express. build_inline_trampoline :: proc( ctx: ^Context, name: string, function_type: CXType, ) -> (string, bool) { result_type := ctx.api.get_result_type(function_type) result_spelling := clone_cx_string(ctx.api, ctx.api.get_type_spelling(result_type), context.temp_allocator) if !declarator_safe(result_spelling) { return "", false } count := ctx.api.get_num_arg_types(function_type) if count < 0 { return "", false } param_spellings := make([]string, int(count), context.temp_allocator) for index in 0.. 0 { strings.write_string(&builder, ", ") } fmt.sbprintf(&builder, "%s a%d", spelling, index) } } strings.write_string(&builder, ") { ") is_void := ctx.api.get_canonical_type(result_type).kind == CXType_Void if !is_void { strings.write_string(&builder, "return ") } fmt.sbprintf(&builder, "%s(", name) for index in 0.. 0 { strings.write_string(&builder, ", ") } fmt.sbprintf(&builder, "a%d", index) } strings.write_string(&builder, "); }\n") append(&ctx.result.trampolines, Trampoline{ symbol=symbol, source=fmt.aprintf("%s", strings.to_string(builder), allocator=ctx.allocator), header=fmt.aprintf("%s", ctx.header_path, allocator=ctx.allocator), }) // `symbol` is owned by the trampoline record above; the caller clones it for // the function's link name. return symbol, true } 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 := "" link_name := "" 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 { 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) populate_record(ctx, record, cursor) value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) add_alias(ctx, name, value) } case CXCursor_EnumDecl: backing_c := ctx.api.get_enum_decl_integer_type(cursor) backing := translate_type(ctx, backing_c) if backing == INVALID_TYPE { add_unsupported(ctx, name, "C enum backing type is not supported") break } add_alias(ctx, name, backing) enum_ctx := Enum_Constant_Context{ ctx=ctx, backing=backing, unsigned=enum_backing_unsigned(backing_c), } _ = ctx.api.visit_children(cursor, visit_enum_constant, &enum_ctx) case CXCursor_VarDecl: if len(name) == 0 { return CXChildVisit_Continue } reason := "" linkage := ctx.api.get_cursor_linkage(cursor) if ctx.api.get_cursor_tls_kind(cursor) != CXTLS_None { reason = "thread-local C variables are not supported" } else if linkage != CXLinkage_External { reason = "static and non-external C variables are not supported" } variable_type := ctx.api.get_cursor_type(cursor) translated := translate_type(ctx, variable_type) if translated == INVALID_TYPE && len(reason) == 0 { reason = "C variable type is not supported" } add_or_upgrade_variable( ctx, name, translated, c_variable_writable(ctx.api, variable_type), reason, ) case CXCursor_MacroDefinition: _ = import_macro_constant(ctx, cursor, name) 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) if message := translation_unit_error(&api, translation_unit, allocator); len(message) > 0 { result.error_message = message return result } root := api.get_translation_unit_cursor(translation_unit) final_macros := init_macro_table(allocator) defer destroy_macro_table(&final_macros) collect_ctx := Macro_Collect_Context{ api=&api, candidates=&final_macros, } _ = api.visit_children(root, visit_macro_candidate, &collect_ctx) if message, ok := probe_final_macros( &api, index, translation_unit, c_path, c_arguments, request.path, &final_macros, allocator, ); !ok { result.error_message = message return result } ctx := Context{ api=&api, translation_unit=translation_unit, result=&result, final_macros=&final_macros, allocator=allocator, target=request.target, header_path=request.path, } ctx.variable_lookup.allocator = allocator defer delete(ctx.variable_lookup) _ = api.visit_children(root, visit_cursor, &ctx) result.available = true return result }