translate-c file emission
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user