translate-c file emission
This commit is contained in:
@@ -151,11 +151,26 @@
|
||||
- array rvalues (e.g. a by-value array return) are materialized into a
|
||||
temporary before slicing, matching the for-loop iterable lowering
|
||||
|
||||
11. c header imports and automatic native brolang bindings (implemented)
|
||||
- `brolang translate-c <header.h> [--target ...] [--c-include-path ...] [--c-define ...]`
|
||||
prints native `.bro` bindings for a C header to stdout (the offline counterpart of the
|
||||
in-memory `native :: import "x.h"`); reuses the libclang `cimport.Result`
|
||||
- emitter lives in `compiler/translatec`; `render_type` mirrors `loader.translate_c_type`
|
||||
one-to-one so emitted source re-parses to identical types (guarded by a round-trip test)
|
||||
- added a native type-alias declaration `Name :: alias T` (parser/lexer/token surface; the
|
||||
`types.define_alias` / `.Alias` machinery already existed) so C typedefs and callback
|
||||
typedefs round-trip
|
||||
- emits functions, complete/opaque structs (collapsing `typedef struct {...} Foo`),
|
||||
typedef aliases, and scalar/aggregate/enum-member constants
|
||||
- C unions, external variables, static-inline functions, and unsupported declarations have
|
||||
no hand-writable spelling and are emitted as `# unsupported in bindings:` comments
|
||||
(functions that reference an un-spellable union therefore keep a dangling reference)
|
||||
|
||||
## A word on multi-unwrap
|
||||
|
||||
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
|
||||
|
||||
```honey
|
||||
```
|
||||
name: ?[]u8 = get_name()
|
||||
age: ?u8 = get_age()
|
||||
if name and age |n, a| {
|
||||
@@ -166,7 +181,7 @@ if name and age |n, a| {
|
||||
|
||||
**With guard clause on multiple values:**
|
||||
|
||||
```honey
|
||||
```
|
||||
if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
|
||||
print("{s}'s got that drip\n", {n})
|
||||
}
|
||||
@@ -174,7 +189,7 @@ if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
|
||||
|
||||
Parentheses around the expression are optional, but can aid readability when combined with guards:
|
||||
|
||||
```honey
|
||||
```
|
||||
# without parentheses
|
||||
if name and hat |n, h : guard| { ... }
|
||||
|
||||
@@ -186,7 +201,7 @@ if (name and hat) |n, h : guard| { ... }
|
||||
|
||||
The `and` in multi-unwrap short-circuits left-to-right:
|
||||
|
||||
```honey
|
||||
```
|
||||
if get_name() and get_hat() |n, h| {
|
||||
# get_hat() is only called if get_name() returned non-none
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
case "c_struct": return .Keyword_C_Struct
|
||||
case "enum": return .Keyword_Enum
|
||||
case "distinct": return .Keyword_Distinct
|
||||
case "alias": return .Keyword_Alias
|
||||
case "import": return .Keyword_Import
|
||||
case "return": return .Keyword_Return
|
||||
case "mut": return .Keyword_Mut
|
||||
|
||||
@@ -1505,6 +1505,19 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
||||
start := advance(parser)
|
||||
child := parse_type(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
||||
if !types.define_alias(&parser.module.type_store, id, child) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if !types.is_valid(child) {
|
||||
source.add(parser.diagnostics, start.span, "alias declarations require a backing type")
|
||||
}
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
||||
start := advance(parser)
|
||||
explicit_backing := false
|
||||
@@ -1736,6 +1749,10 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
parse_distinct(parser, name)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
|
||||
parse_alias(parser, name)
|
||||
return
|
||||
}
|
||||
|
||||
expr := parse_expression(parser)
|
||||
_ = ast.global_id(len(parser.module.globals))
|
||||
|
||||
@@ -54,6 +54,7 @@ Kind :: enum u8 {
|
||||
Keyword_C_Struct,
|
||||
Keyword_Enum,
|
||||
Keyword_Distinct,
|
||||
Keyword_Alias,
|
||||
Keyword_Import,
|
||||
Keyword_Return,
|
||||
Keyword_Mut,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import "./compiler/source"
|
||||
import "./compiler/symbol"
|
||||
import "./compiler/target"
|
||||
import "./compiler/token"
|
||||
import "./compiler/translatec"
|
||||
import "./compiler/types"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
@@ -5769,3 +5770,83 @@ native_enums_compile_and_run_across_packages :: proc(t: ^testing.T) {
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) {
|
||||
result := cimport.init_result(context.allocator)
|
||||
|
||||
// type table: [0]=c_int, [1]=c_ulong, [2]=Record(Pair),
|
||||
// [3]=Function(c_int)->c_int, [4]=Pointer->Function (callback)
|
||||
append(&result.types, cimport.Type{kind = .C_Int, child = cimport.INVALID_TYPE})
|
||||
append(&result.types, cimport.Type{kind = .C_Ulong, child = cimport.INVALID_TYPE})
|
||||
append(&result.types, cimport.Type{kind = .Record, record = 0, child = cimport.INVALID_TYPE})
|
||||
func_params := []cimport.Type_Id{cimport.Type_Id(0)}
|
||||
append(&result.types, cimport.Type{kind = .Function, params = func_params, child = cimport.Type_Id(0)})
|
||||
append(&result.types, cimport.Type{kind = .Pointer, child = cimport.Type_Id(3)})
|
||||
|
||||
// record 0: Pair { left c_int; right c_int }
|
||||
pair_fields: [dynamic]cimport.Field
|
||||
append(&pair_fields, cimport.Field{name = "left", type = cimport.Type_Id(0)})
|
||||
append(&pair_fields, cimport.Field{name = "right", type = cimport.Type_Id(0)})
|
||||
append(&result.records, cimport.Record{name = "Pair", fields = pair_fields, kind = .Struct, complete = true})
|
||||
|
||||
// record 1: union Choice -> commented (no native spelling)
|
||||
choice_fields: [dynamic]cimport.Field
|
||||
append(&choice_fields, cimport.Field{name = "tag", type = cimport.Type_Id(0)})
|
||||
append(&result.records, cimport.Record{name = "Choice", fields = choice_fields, kind = .Union, complete = true})
|
||||
|
||||
// typedef aliases: a scalar and a callback function pointer
|
||||
append(&result.aliases, cimport.Alias{name = "Size", type = cimport.Type_Id(1)})
|
||||
append(&result.aliases, cimport.Alias{name = "Mapper", type = cimport.Type_Id(4)})
|
||||
|
||||
add_params := []cimport.Type_Id{cimport.Type_Id(0), cimport.Type_Id(0)}
|
||||
append(&result.functions, cimport.Function{name = "imported_add", params = add_params, result = cimport.Type_Id(0)})
|
||||
|
||||
append(&result.macros, cimport.Macro_Constant{
|
||||
name = "MAX_LEN",
|
||||
type = cimport.Type_Id(0),
|
||||
value = {kind = .Integer, type = cimport.Type_Id(0), integer = 256},
|
||||
})
|
||||
|
||||
// external variable -> commented (no native spelling)
|
||||
append(&result.variables, cimport.Variable{name = "some_global", type = cimport.Type_Id(0), mutable = true})
|
||||
|
||||
result.available = true
|
||||
|
||||
output := translatec.emit(&result, "test.h")
|
||||
defer delete(output)
|
||||
defer {
|
||||
delete(result.types)
|
||||
delete(result.records)
|
||||
delete(result.aliases)
|
||||
delete(result.functions)
|
||||
delete(result.variables)
|
||||
delete(result.macros)
|
||||
delete(pair_fields)
|
||||
delete(choice_fields)
|
||||
}
|
||||
|
||||
testing.expect(t, strings.contains(output, "Pair :: c_struct {"))
|
||||
testing.expect(t, strings.contains(output, "\tleft c_int"))
|
||||
testing.expect(t, strings.contains(output, "\tright c_int"))
|
||||
testing.expect(t, strings.contains(output, "Size :: alias c_ulong"))
|
||||
testing.expect(t, strings.contains(output, "Mapper :: alias ?*c_func(arg0 c_int) c_int"))
|
||||
testing.expect(t, strings.contains(output, "imported_add :: c_func(arg0 c_int, arg1 c_int) c_int"))
|
||||
testing.expect(t, strings.contains(output, "MAX_LEN c_int :: 256"))
|
||||
testing.expect(t, strings.contains(output, "# unsupported in bindings: C union 'Choice'"))
|
||||
testing.expect(t, strings.contains(output, "# unsupported in bindings: external variable 'some_global'"))
|
||||
|
||||
// Round-trip: the emitted source must lex + parse with zero diagnostics.
|
||||
// This guards render_type against drift from loader.translate_c_type and
|
||||
// exercises the new `alias` declaration syntax.
|
||||
source_file := source.Source{path = "bindings.bro", text = output}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import "./compiler"
|
||||
import "./compiler/cimport"
|
||||
import "./compiler/linker"
|
||||
import "./compiler/target"
|
||||
import "./compiler/translatec"
|
||||
import "core:fmt"
|
||||
import "core:os/os2"
|
||||
|
||||
@@ -85,9 +86,67 @@ print_usage :: proc() {
|
||||
fmt.eprintln(
|
||||
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
)
|
||||
}
|
||||
|
||||
// run_translate_c emits native brolang bindings for a C header to stdout, the
|
||||
// offline counterpart of `native :: import "x.h"`.
|
||||
run_translate_c :: proc(args: []string) -> int {
|
||||
if len(args) < 3 {
|
||||
print_usage()
|
||||
return 2
|
||||
}
|
||||
header := args[2]
|
||||
selected := target.DEFAULT
|
||||
include_paths: [dynamic]string
|
||||
defines: [dynamic]string
|
||||
defer delete(include_paths)
|
||||
defer delete(defines)
|
||||
cursor := 3
|
||||
for cursor < len(args) {
|
||||
option := args[cursor]
|
||||
cursor += 1
|
||||
if cursor >= len(args) {
|
||||
fmt.eprintfln("missing value for %s", option)
|
||||
return 2
|
||||
}
|
||||
value := args[cursor]
|
||||
cursor += 1
|
||||
switch option {
|
||||
case "--c-include-path":
|
||||
append(&include_paths, value)
|
||||
case "--c-define":
|
||||
append(&defines, value)
|
||||
case "--target":
|
||||
parsed, ok := target.parse(value)
|
||||
if !ok {
|
||||
fmt.eprintfln("unknown target '%s'", value)
|
||||
return 2
|
||||
}
|
||||
selected = parsed
|
||||
case:
|
||||
fmt.eprintfln("unknown option '%s'", option)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
c_options := cimport.Options{include_paths=include_paths[:], defines=defines[:]}
|
||||
result := cimport.import_header(c_options, header, selected)
|
||||
defer cimport.destroy_result(&result)
|
||||
if !result.available {
|
||||
message := result.error_message if len(result.error_message) > 0 else "failed to import header"
|
||||
fmt.eprintln(message)
|
||||
return 1
|
||||
}
|
||||
fmt.print(translatec.emit(&result, header))
|
||||
return 0
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
if len(os2.args) >= 2 && os2.args[1] == "translate-c" {
|
||||
os2.exit(run_translate_c(os2.args))
|
||||
}
|
||||
options, valid := parse_cli_args(os2.args)
|
||||
if !valid {
|
||||
print_usage()
|
||||
|
||||
Reference in New Issue
Block a user