350 lines
11 KiB
Odin
350 lines
11 KiB
Odin
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 "../lexer"
|
|
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:
|
|
// Function-pointer types carry no parameter names, so every slot renders `_`.
|
|
render_c_func(b, result, item.params, nil, 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(name T0, ...) R`, using the real C parameter name
|
|
// when `names` provides one and `_` (the sink) otherwise. Names do not affect type
|
|
// identity; the parser requires a name slot, so unnamed params use `_`.
|
|
render_c_func :: proc(
|
|
b: ^strings.Builder,
|
|
result: ^cimport.Result,
|
|
params: []cimport.Type_Id,
|
|
names: []string,
|
|
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, ", ")
|
|
}
|
|
name := names[index] if index < len(names) else ""
|
|
fmt.sbprintf(b, "%s ", safe_param_name(name))
|
|
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.param_names, 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")
|
|
}
|
|
}
|
|
|
|
// safe_param_name returns the C parameter name when it is a usable brolang
|
|
// identifier, else `_` (the sink): empty names (unnamed C params) and names that
|
|
// collide with a brolang keyword (e.g. legal C `int f(int and)`) both become `_`.
|
|
safe_param_name :: proc(name: string) -> string {
|
|
if len(name) > 0 && lexer.keyword_kind(name) == .Identifier {
|
|
return name
|
|
}
|
|
return "_"
|
|
}
|
|
|
|
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
|
|
}
|