package translatec // translatec renders a parsed C header (cimport.Result) as native brolang (.bro) // source — the offline counterpart to the in-memory `native :: import "x.h"` // path. The type spellings here MUST mirror loader.translate_c_type so the // emitted source re-parses to the same types the in-memory import produces; the // round-trip parser test (compiler_tests.odin) guards that invariant. // // Constructs with no hand-writable brolang spelling — C unions, external // variables, static inline functions, and otherwise unsupported declarations — // are emitted as `# unsupported in bindings:` comments rather than dropped, so // the output is an honest record of the whole header. import "../cimport" import "core:fmt" import "core:mem" import "core:strings" emit :: proc(result: ^cimport.Result, header: string, allocator := context.allocator) -> string { b := strings.builder_make(allocator) fmt.sbprintf(&b, "# generated by brolang translate-c from %s\n\n", header) record_names := record_name_table(result, allocator) defer delete(record_names, allocator) emit_records(&b, result, record_names) emit_aliases(&b, result, record_names) emit_macros(&b, result, record_names) emit_functions(&b, result, record_names) emit_variables(&b, result) emit_unsupported(&b, result) return strings.to_string(b) } // record_name_table maps each record index to the brolang identifier it is // emitted under: its C tag name, or — for an anonymous record named only by a // typedef — that typedef's name (the typedef is then skipped). Truly anonymous // records fall back to the loader's synthetic `__c_record_N`. record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator) -> []string { names := make([]string, len(result.records), allocator) for record, idx in result.records { if len(record.name) > 0 { names[idx] = record.name } else { names[idx] = fmt.aprintf("__c_record_%d", idx, allocator=allocator) } } for alias in result.aliases { if len(alias.reason) > 0 { continue } ti := alias.type if int(ti) < 0 || int(ti) >= len(result.types) { continue } target := result.types[ti] if target.kind != .Record || int(target.record) >= len(result.records) { continue } if len(result.records[target.record].name) == 0 { names[target.record] = alias.name } } return names } // render_type writes the brolang spelling of a C type. Mirror of // loader.translate_c_type — keep the two in lockstep. render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Type_Id, record_names: []string) { if id == cimport.INVALID_TYPE || int(id) < 0 || int(id) >= len(result.types) { strings.write_string(b, "void") return } item := result.types[id] switch item.kind { case .Invalid: strings.write_string(b, "void") case .Void: strings.write_string(b, "void") case .C_Char: strings.write_string(b, "c_char") case .C_Schar: strings.write_string(b, "c_schar") case .C_Uchar: strings.write_string(b, "c_uchar") case .C_Short: strings.write_string(b, "c_short") case .C_Ushort: strings.write_string(b, "c_ushort") case .C_Int: strings.write_string(b, "c_int") case .C_Uint: strings.write_string(b, "c_uint") case .C_Long: strings.write_string(b, "c_long") case .C_Ulong: strings.write_string(b, "c_ulong") case .C_Longlong: strings.write_string(b, "c_longlong") case .C_Ulonglong: strings.write_string(b, "c_ulonglong") case .C_Float: strings.write_string(b, "c_float") case .C_Double: strings.write_string(b, "c_double") case .C_Longdouble: strings.write_string(b, "c_longdouble") case .Pointer: // loader: optional(pointer(child, mutable, many=true)). A Function child // yields the `?*c_func(...) T` callback spelling for free. strings.write_string(b, "?*") if item.mutable { strings.write_string(b, "mut ") } render_type(b, result, item.child, record_names) case .Array: fmt.sbprintf(b, "[%d]", item.count) render_type(b, result, item.child, record_names) case .Function: render_c_func(b, result, item.params, item.child, item.variadic, record_names) case .Record: if int(item.record) >= 0 && int(item.record) < len(record_names) { strings.write_string(b, record_names[item.record]) } else { strings.write_string(b, "void") } } } // render_c_func writes `c_func(arg0 T0, ...) R`. Params are named arg0.. because // the parser requires parameter names; names do not affect type identity. render_c_func :: proc( b: ^strings.Builder, result: ^cimport.Result, params: []cimport.Type_Id, ret: cimport.Type_Id, variadic: bool, record_names: []string, ) { strings.write_string(b, "c_func(") for param, index in params { if index > 0 { strings.write_string(b, ", ") } fmt.sbprintf(b, "arg%d ", index) render_type(b, result, param, record_names) } if variadic { if len(params) > 0 { strings.write_string(b, ", ") } strings.write_string(b, "...") } strings.write_string(b, ") ") render_type(b, result, ret, record_names) } emit_records :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) { wrote := false for record, idx in result.records { name := record_names[idx] if record.kind == .Union { fmt.sbprintf(b, "# unsupported in bindings: C union '%s' has no native spelling\n", name) wrote = true continue } if !record.complete || len(record.reason) > 0 { // opaque / pointer-only struct fmt.sbprintf(b, "%s :: c_struct\n", name) wrote = true continue } fmt.sbprintf(b, "%s :: c_struct {{\n", name) for field in record.fields { fmt.sbprintf(b, "\t%s ", field.name) render_type(b, result, field.type, record_names) strings.write_byte(b, '\n') } strings.write_string(b, "}\n") wrote = true } if wrote { strings.write_byte(b, '\n') } } emit_aliases :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) { wrote := false for alias in result.aliases { if len(alias.reason) > 0 { fmt.sbprintf(b, "# unsupported in bindings: typedef '%s' — %s\n", alias.name, alias.reason) wrote = true continue } // Skip a typedef that merely (re)names a record under the name we already // emitted the record with (anonymous-struct collapse, or `typedef struct // Foo Foo;`). if ti := alias.type; int(ti) >= 0 && int(ti) < len(result.types) { target := result.types[ti] if target.kind == .Record && int(target.record) < len(record_names) && record_names[target.record] == alias.name { continue } } fmt.sbprintf(b, "%s :: alias ", alias.name) render_type(b, result, alias.type, record_names) strings.write_byte(b, '\n') wrote = true } if wrote { strings.write_byte(b, '\n') } } emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) { wrote := false for macro in result.macros { if len(macro.reason) > 0 { fmt.sbprintf(b, "# unsupported in bindings: macro '%s' — %s\n", macro.name, macro.reason) wrote = true continue } if macro.aggregate { emit_aggregate_macro(b, result, macro, record_names) wrote = true continue } strings.write_string(b, macro.name) if macro_scalar_kind(result, macro.type) { strings.write_byte(b, ' ') render_type(b, result, macro.type, record_names) } strings.write_string(b, " :: ") render_macro_value(b, macro.value) strings.write_byte(b, '\n') wrote = true } if wrote { strings.write_byte(b, '\n') } } emit_aggregate_macro :: proc( b: ^strings.Builder, result: ^cimport.Result, macro: cimport.Macro_Constant, record_names: []string, ) { ti := macro.type if int(ti) >= 0 && int(ti) < len(result.types) && result.types[ti].kind == .Record { ridx := int(result.types[ti].record) if ridx < len(result.records) { record := result.records[ridx] if len(record.fields) == len(macro.values) && !record_has_pointer_field(result, record) { fmt.sbprintf(b, "%s :: %s {{", macro.name, record_names[ridx]) for field, index in record.fields { if index > 0 { strings.write_byte(b, ',') } fmt.sbprintf(b, " %s = ", field.name) render_macro_value(b, macro.values[index]) } strings.write_string(b, " }\n") return } } } fmt.sbprintf(b, "# unsupported in bindings: aggregate macro '%s' has no native spelling\n", macro.name) } emit_functions :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) { wrote := false for function in result.functions { if len(function.reason) > 0 { fmt.sbprintf(b, "# unsupported in bindings: function '%s' — %s\n", function.name, function.reason) wrote = true continue } if len(function.link_name) > 0 && function.link_name != function.name { fmt.sbprintf(b, "# unsupported in bindings: function '%s' is a static inline function (needs trampoline)\n", function.name) wrote = true continue } fmt.sbprintf(b, "%s :: ", function.name) render_c_func(b, result, function.params, function.result, function.variadic, record_names) strings.write_byte(b, '\n') wrote = true } if wrote { strings.write_byte(b, '\n') } } emit_variables :: proc(b: ^strings.Builder, result: ^cimport.Result) { for variable in result.variables { fmt.sbprintf(b, "# unsupported in bindings: external variable '%s' has no native spelling\n", variable.name) } } emit_unsupported :: proc(b: ^strings.Builder, result: ^cimport.Result) { for item in result.unsupported { fmt.sbprintf(b, "# unsupported in bindings: %s — %s\n", item.name, item.reason) } } render_macro_value :: proc(b: ^strings.Builder, value: cimport.Macro_Value) { switch value.kind { case .Integer: if value.negative { fmt.sbprintf(b, "-%d", value.integer) } else { fmt.sbprintf(b, "%d", value.integer) } case .Float: number := transmute(f64)value.integer text := fmt.tprintf("%v", number) strings.write_string(b, text) // Ensure it lexes as a float literal, not an integer. if strings.index_byte(text, '.') < 0 && strings.index_byte(text, 'e') < 0 && strings.index_byte(text, 'E') < 0 { strings.write_string(b, ".0") } case .Invalid: strings.write_string(b, "0") } } macro_scalar_kind :: proc(result: ^cimport.Result, id: cimport.Type_Id) -> bool { if int(id) < 0 || int(id) >= len(result.types) { return false } #partial switch result.types[id].kind { case .C_Char, .C_Schar, .C_Uchar, .C_Short, .C_Ushort, .C_Int, .C_Uint, .C_Long, .C_Ulong, .C_Longlong, .C_Ulonglong, .C_Float, .C_Double, .C_Longdouble: return true } return false } record_has_pointer_field :: proc(result: ^cimport.Result, record: cimport.Record) -> bool { for field in record.fields { if int(field.type) >= 0 && int(field.type) < len(result.types) && result.types[field.type].kind == .Pointer { return true } } return false }