1561 lines
51 KiB
Odin
1561 lines
51 KiB
Odin
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..<count {
|
|
param := translate_type(ctx, ctx.api.get_arg_type(value, u32(index)), "", depth+1)
|
|
append(¶ms, param)
|
|
if param == INVALID_TYPE {
|
|
delete(params)
|
|
return INVALID_TYPE
|
|
}
|
|
}
|
|
return add_type(ctx, Type{
|
|
kind=.Function,
|
|
child=result,
|
|
params=params[:],
|
|
variadic=ctx.api.is_function_type_variadic(value) != 0,
|
|
})
|
|
case CXType_Typedef:
|
|
canonical := ctx.api.get_canonical_type(value)
|
|
if canonical.kind != CXType_Invalid && canonical.kind != value.kind {
|
|
return translate_type(ctx, canonical, preferred_record_name, depth+1)
|
|
}
|
|
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_FunctionNoProto,
|
|
CXType_IncompleteArray, CXType_VariableArray, CXType_DependentSizedArray:
|
|
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, 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..<api.get_num_diagnostics(translation_unit) {
|
|
diagnostic := api.get_diagnostic(translation_unit, diagnostic_index)
|
|
severity := api.get_diagnostic_severity(diagnostic)
|
|
if severity >= 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 `<spelling> 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..<count {
|
|
arg_type := ctx.api.get_arg_type(function_type, u32(index))
|
|
spelling := clone_cx_string(ctx.api, ctx.api.get_type_spelling(arg_type), context.temp_allocator)
|
|
if !declarator_safe(spelling) {
|
|
return "", false
|
|
}
|
|
param_spellings[index] = spelling
|
|
}
|
|
|
|
// Namespace the symbol by the canonical header path so two headers that each
|
|
// define a `static inline foo` produce distinct wrappers (the loader dedups
|
|
// trampolines by symbol). The same header is canonical and stable, so it
|
|
// always hashes to the same value and dedups correctly.
|
|
digest := hash.fnv64a(transmute([]byte)ctx.header_path)
|
|
symbol := fmt.aprintf("%s%016x_%s", INLINE_TRAMPOLINE_PREFIX, digest, name, allocator=ctx.allocator)
|
|
builder := strings.builder_make(context.temp_allocator)
|
|
defer strings.builder_destroy(&builder)
|
|
fmt.sbprintf(&builder, "%s %s(", result_spelling, symbol)
|
|
if count == 0 {
|
|
strings.write_string(&builder, "void")
|
|
} else {
|
|
for spelling, index in param_spellings {
|
|
if index > 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..<count {
|
|
if index > 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..<count {
|
|
param := translate_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
|
|
append(¶ms, param)
|
|
if param == INVALID_TYPE && len(reason) == 0 {
|
|
reason = "function parameter type is not supported"
|
|
}
|
|
}
|
|
}
|
|
linkage := ctx.api.get_cursor_linkage(cursor)
|
|
if linkage != CXLinkage_External && len(reason) == 0 {
|
|
// Internal-linkage functions have no external symbol. We can still
|
|
// call a `static inline` function by linking a generated wrapper.
|
|
// `isFunctionInlined` is set from the `inline` keyword without parsing
|
|
// the body, so this works under CXTranslationUnit_SkipFunctionBodies.
|
|
if variadic {
|
|
reason = "variadic static inline C functions are not supported"
|
|
} else if ctx.api.cursor_is_function_inlined(cursor) != 0 {
|
|
if symbol, ok := build_inline_trampoline(ctx, name, function_type); ok {
|
|
link_name = symbol
|
|
} else {
|
|
reason = "static inline C function has an unsupported parameter or result declarator"
|
|
}
|
|
} else {
|
|
reason = "static and non-external C functions are not supported"
|
|
}
|
|
} else if linkage != CXLinkage_External {
|
|
reason = "static and non-external C functions are not supported"
|
|
}
|
|
append(&ctx.result.functions, Function{
|
|
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
|
|
params=params[:],
|
|
result=result_type,
|
|
variadic=variadic,
|
|
link_name=fmt.aprintf("%s", link_name, allocator=ctx.allocator),
|
|
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
|
|
})
|
|
case CXCursor_TypedefDecl:
|
|
value := translate_type(ctx, ctx.api.get_typedef_underlying_type(cursor), name)
|
|
reason := ""
|
|
if value == INVALID_TYPE {
|
|
reason = "typedef underlying type is not supported"
|
|
}
|
|
add_alias(ctx, name, value, reason)
|
|
case CXCursor_StructDecl, CXCursor_UnionDecl:
|
|
if len(name) > 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
|
|
}
|