upgrade enum discriminants

This commit is contained in:
2026-07-17 20:32:12 +02:00
parent 866e28adb8
commit cedc63b28b
19 changed files with 3168 additions and 96 deletions
+19
View File
@@ -254,6 +254,19 @@ Global :: struct {
diagnostic: source.Diagnostic_Id,
}
Enum_Value :: struct {
expr: Expr_Id,
span: source.Span,
explicit: bool,
}
Enum_Declaration :: struct {
type: types.Type,
pkg: Package_Id,
file: File_Id,
values: []Enum_Value,
}
Import :: struct {
span: source.Span,
alias: symbol.Id,
@@ -326,6 +339,7 @@ Module :: struct {
statements: [dynamic]Stmt,
functions: [dynamic]Function,
globals: [dynamic]Global,
enum_declarations: [dynamic]Enum_Declaration,
imports: [dynamic]Import,
aliases: [dynamic]Declaration_Alias,
files: [dynamic]File,
@@ -347,6 +361,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.statements.allocator = allocator
module.functions.allocator = allocator
module.globals.allocator = allocator
module.enum_declarations.allocator = allocator
module.imports.allocator = allocator
module.aliases.allocator = allocator
module.files.allocator = allocator
@@ -382,6 +397,9 @@ destroy_module :: proc(module: ^Module) {
for global in module.globals {
delete(global.link_name, module.allocator)
}
for declaration in module.enum_declarations {
delete(declaration.values, module.allocator)
}
for pkg in module.packages {
delete(pkg.path, module.allocator)
}
@@ -400,6 +418,7 @@ destroy_module :: proc(module: ^Module) {
delete(module.statements)
delete(module.functions)
delete(module.globals)
delete(module.enum_declarations)
delete(module.imports)
delete(module.aliases)
delete(module.files)
+26 -2
View File
@@ -502,7 +502,7 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
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)
param := translate_parameter_type(ctx, ctx.api.get_arg_type(value, u32(index)), depth+1)
append(&params, param)
if param == INVALID_TYPE {
delete(params)
@@ -537,6 +537,30 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
return INVALID_TYPE
}
translate_parameter_type :: proc(ctx: ^Context, value: CXType, depth := 0) -> Type_Id {
if depth > 64 {
return INVALID_TYPE
}
array := value
if value.kind != CXType_ConstantArray && value.kind != CXType_IncompleteArray {
canonical := ctx.api.get_canonical_type(value)
if canonical.kind != CXType_ConstantArray && canonical.kind != CXType_IncompleteArray {
return translate_type(ctx, value, "", depth+1)
}
array = canonical
}
element := ctx.api.get_array_element_type(array)
child := translate_type(ctx, element, "", depth+1)
if child == INVALID_TYPE {
return INVALID_TYPE
}
return add_type(ctx, Type{
kind=.Pointer,
child=child,
mutable=ctx.api.is_const_qualified_type(element) == 0,
})
}
has_named :: proc(items: []Unsupported, name: string) -> bool {
for item in items {
if item.name == name {
@@ -1385,7 +1409,7 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
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)))
param := translate_parameter_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
append(&params, param)
if param == INVALID_TYPE && len(reason) == 0 {
reason = "function parameter type is not supported"
+131
View File
@@ -1294,6 +1294,136 @@ find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.I
return ast.INVALID_IMPORT
}
find_visible_enum_global :: proc(
module: ^ast.Module,
pkg: ast.Package_Id,
file: ast.File_Id,
name: symbol.Id,
public_only := false,
) -> ast.Global_Id {
for global, index in module.globals {
if global.pkg == pkg && global.name == name &&
(!global.file_hidden || !public_only && global.file == file) {
return ast.global_id(index)
}
}
return ast.INVALID_GLOBAL
}
eval_enum_global :: proc(
state: ^State,
id: ast.Global_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_GLOBAL || index < 0 || index >= len(state.module.globals) ||
depth > 64 || visiting[index] {
return 0, false
}
global := state.module.globals[index]
if !global.immutable || global.external || global.expr == ast.INVALID_EXPR {
return 0, false
}
if types.is_valid(global.type) && !types.is_concrete_integer(global.type) {
return 0, false
}
visiting[index] = true
defer visiting[index] = false
return eval_enum_constant(state, global.expr, global.pkg, global.file, visiting, depth+1)
}
eval_enum_constant :: proc(
state: ^State,
id: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_EXPR || index < 0 || index >= len(state.module.exprs) || depth > 64 {
return 0, false
}
expr := state.module.exprs[index]
#partial switch expr.kind {
case .Integer:
return i128(expr.integer), true
case .Negate:
value, ok := eval_enum_constant(state, expr.left, pkg, file, visiting, depth+1)
return -value, ok
case .Name:
if symbol.is_valid(expr.qualifier) {
import_id := find_type_import(state.module, file, expr.qualifier)
if import_id == ast.INVALID_IMPORT {
return 0, false
}
state.module.imports[import_id].used = true
import_item := state.module.imports[import_id]
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
int(import_item.target) >= len(state.module.packages) ||
!state.module.packages[import_item.target].available {
return 0, false
}
global := find_visible_enum_global(
state.module,
import_item.target,
ast.INVALID_FILE,
expr.name,
public_only=true,
)
return eval_enum_global(state, global, visiting, depth+1)
}
global := find_visible_enum_global(state.module, pkg, file, expr.name)
return eval_enum_global(state, global, visiting, depth+1)
}
return 0, false
}
resolve_enum_values :: proc(state: ^State) {
visiting := make([]bool, len(state.module.globals), state.allocator)
defer delete(visiting, state.allocator)
for declaration in state.module.enum_declarations {
members := types.enum_members_for(&state.module.type_store, declaration.type)
if len(members) != len(declaration.values) {
continue
}
next_value: i128
previous: i128
has_previous := false
previous_known := true
for &member, index in members {
spec := declaration.values[index]
value := next_value
known := true
if spec.explicit {
if spec.expr == ast.INVALID_EXPR {
value = member.value
} else if resolved, ok := eval_enum_constant(
state, spec.expr, declaration.pkg, declaration.file, visiting, 0,
); ok {
value = resolved
} else {
source.add(state.diagnostics, spec.span, "enum value must be an immutable integer constant")
known = false
}
}
if known {
member.value = value
if has_previous && previous_known && value <= previous {
source.add(state.diagnostics, spec.span, "enum values must be strictly increasing")
}
next_value = value+1
} else {
next_value = member.value+1
}
previous = value
previous_known = known
has_previous = true
}
}
}
alias_declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
return left_file == right_file if left_hidden && right_hidden else true
}
@@ -1783,6 +1913,7 @@ load :: proc(
}
validate_imports(&state)
validate_declaration_aliases(&state)
resolve_enum_values(&state)
diagnose_qualified_type_uses(&state)
canonicalize_types(&module, allocator)
return module, !state.root_failed
+35 -6
View File
@@ -2783,6 +2783,8 @@ parse_enum_body :: proc(
explicit_backing: bool,
backing: ^types.Type,
members: ^[dynamic]types.Enum_Member,
values: ^[dynamic]ast.Enum_Value,
deferred: ^bool,
expected_open: string,
) -> bool {
skip_newlines(parser)
@@ -2793,6 +2795,7 @@ parse_enum_body :: proc(
next_value: i128
previous_value: i128
has_previous := false
order_known := true
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
member, member_ok := parse_member_name(parser)
@@ -2817,18 +2820,20 @@ parse_enum_body :: proc(
source.addf(parser.diagnostics, member.span, "duplicate enum member '%s'", token_text(parser, member))
}
value := next_value
explicit := false
value_expr := ast.INVALID_EXPR
if _, ok := allow(parser, .Equal); ok {
explicit = true
if !explicit_backing {
source.add(parser.diagnostics, member.span, "explicit enum values require a backing type")
}
saved := parser.cursor
negative := false
if _, minus_ok := allow(parser, .Minus); minus_ok {
negative = true
}
literal := current(parser)
if literal.kind != .Integer {
source.add(parser.diagnostics, literal.span, "expected a decimal integer literal for enum value")
} else {
if literal.kind == .Integer {
advance(parser)
magnitude, magnitude_ok := parse_integer_magnitude(token_text(parser, literal))
if !magnitude_ok {
@@ -2839,13 +2844,19 @@ parse_enum_body :: proc(
value = -value
}
}
} else {
parser.cursor = saved
value_expr = parse_expression(parser)
deferred^ = true
order_known = false
}
}
if has_previous && value <= previous_value {
if order_known && has_previous && value <= previous_value {
source.add(parser.diagnostics, member.span, "enum values must be strictly increasing")
}
if !duplicate {
append(members, types.Enum_Member{name=u32(member.symbol), value=value})
append(values, ast.Enum_Value{expr=value_expr, span=member.span, explicit=explicit})
}
previous_value = value
has_previous = true
@@ -2890,7 +2901,11 @@ parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type {
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, false, &backing, &members, "expected '{' after inline enum error type") || !valid {
values: [dynamic]ast.Enum_Value
values.allocator = parser.module.allocator
defer delete(values)
deferred := false
if !parse_enum_body(parser, start.span, false, &backing, &members, &values, &deferred, "expected '{' after inline enum error type") || !valid {
return types.INVALID
}
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
@@ -2910,7 +2925,11 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, explicit_backing, &backing, &members, "expected '{' after enum declaration") {
values: [dynamic]ast.Enum_Value
values.allocator = parser.module.allocator
defer delete(values)
deferred := false
if !parse_enum_body(parser, start.span, explicit_backing, &backing, &members, &values, &deferred, "expected '{' after enum declaration") {
_ = finish_statement(parser)
return
}
@@ -2918,6 +2937,16 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
if explicit_backing && deferred {
stored := make([]ast.Enum_Value, len(values), parser.module.allocator)
copy(stored, values[:])
append(&parser.module.enum_declarations, ast.Enum_Declaration{
type=id,
pkg=parser.pkg,
file=parser.file,
values=stored,
})
}
_ = finish_statement(parser)
}
+449 -51
View File
@@ -17,32 +17,206 @@ import "core:fmt"
import "core:mem"
import "core:strings"
Package_Input :: struct {
result: ^cimport.Result,
header: string,
name: string,
}
Package_Output :: struct {
name: string,
source: string,
}
Declaration_Kind :: enum u8 {
Record,
Alias,
Macro,
Function,
}
Declaration :: struct {
name: string,
canonical: string,
kind: Declaration_Kind,
}
Declaration_Registry :: struct {
lookup: map[string]int,
declarations: [dynamic]Declaration,
error: string,
allocator: mem.Allocator,
}
init_declaration_registry :: proc(allocator: mem.Allocator) -> Declaration_Registry {
registry := Declaration_Registry{allocator=allocator}
registry.lookup.allocator = allocator
registry.declarations.allocator = allocator
return registry
}
destroy_declaration_registry :: proc(registry: ^Declaration_Registry) {
for declaration in registry.declarations {
delete(declaration.name, registry.allocator)
delete(declaration.canonical, registry.allocator)
}
delete(registry.error, registry.allocator)
delete(registry.lookup)
delete(registry.declarations)
}
write_declaration :: proc(
b: ^strings.Builder,
registry: ^Declaration_Registry,
name: string,
kind: Declaration_Kind,
actual: string,
canonical := "",
) -> (emitted, ok: bool) {
if registry == nil {
strings.write_string(b, actual)
return true, true
}
comparison := canonical if len(canonical) > 0 else actual
if index, found := registry.lookup[name]; found {
previous := registry.declarations[index]
if previous.kind == kind && previous.canonical == comparison {
return false, true
}
if len(registry.error) == 0 {
registry.error = fmt.aprintf(
"conflicting generated C declaration '%s'",
name,
allocator=registry.allocator,
)
}
return false, false
}
owned_name := strings.clone(name, registry.allocator)
owned_canonical := strings.clone(comparison, registry.allocator)
registry.lookup[owned_name] = len(registry.declarations)
append(&registry.declarations, Declaration{
name=owned_name,
canonical=owned_canonical,
kind=kind,
})
strings.write_string(b, actual)
return true, true
}
emit :: proc(result: ^cimport.Result, header: string, allocator := context.allocator) -> string {
record_names := record_name_table(result, allocator)
defer destroy_record_name_table(record_names, allocator)
output, ok := emit_with_record_names(result, header, record_names, nil, allocator)
assert(ok)
return output
}
destroy_package_outputs :: proc(outputs: []Package_Output, allocator := context.allocator) {
for output in outputs {
delete(output.name, allocator)
delete(output.source, allocator)
}
delete(outputs, allocator)
}
emit_package :: proc(inputs: []Package_Input, allocator := context.allocator) -> ([]Package_Output, string) {
registry := init_declaration_registry(allocator)
defer destroy_declaration_registry(&registry)
tables := make([][]string, len(inputs), allocator)
defer {
for table in tables {
destroy_record_name_table(table, allocator)
}
delete(tables, allocator)
}
for input, index in inputs {
tables[index] = record_name_table(input.result, allocator, input.name)
}
identities: map[string]string
identities.allocator = allocator
defer delete(identities)
for input, input_index in inputs {
for record, record_index in input.result.records {
if len(record.identity) == 0 {
continue
}
if canonical, found := identities[record.identity]; found {
delete(tables[input_index][record_index], allocator)
tables[input_index][record_index] = strings.clone(canonical, allocator)
} else {
identities[record.identity] = tables[input_index][record_index]
}
}
}
outputs: [dynamic]Package_Output
outputs.allocator = allocator
for input, index in inputs {
source, ok := emit_with_record_names(
input.result,
input.header,
tables[index],
&registry,
allocator,
)
if !ok {
delete(source, allocator)
destroy_package_outputs(outputs[:], allocator)
return nil, strings.clone(registry.error, allocator)
}
append(&outputs, Package_Output{
name=strings.clone(input.name, allocator),
source=source,
})
}
return outputs[:], ""
}
emit_with_record_names :: proc(
result: ^cimport.Result,
header: string,
record_names: []string,
registry: ^Declaration_Registry,
allocator: mem.Allocator,
) -> (string, bool) {
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)
if !emit_records(&b, result, record_names, registry) ||
!emit_aliases(&b, result, record_names, registry) ||
!emit_macros(&b, result, record_names, registry) ||
!emit_functions(&b, result, record_names, registry) {
return strings.to_string(b), false
}
emit_variables(&b, result)
emit_unsupported(&b, result)
return strings.to_string(b)
return strings.to_string(b), true
}
// 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 {
record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator, prefix := "") -> []string {
names := make([]string, len(result.records), allocator)
for record, idx in result.records {
if len(record.name) > 0 {
names[idx] = record.name
if binding_identifier(record.name) {
if record_name_conflicts(result, idx, record.name) {
fragment := safe_name_fragment(prefix, context.temp_allocator)
if len(fragment) > 0 {
names[idx] = fmt.aprintf("__c_%s_%s_record", fragment, record.name, allocator=allocator)
} else {
names[idx] = fmt.aprintf("__c_%s_record", record.name, allocator=allocator)
}
} else {
names[idx] = strings.clone(record.name, allocator)
}
} else if len(prefix) > 0 {
fragment := safe_name_fragment(prefix, context.temp_allocator)
names[idx] = fmt.aprintf("__c_%s_record_%d", fragment, idx, allocator=allocator)
} else {
names[idx] = fmt.aprintf("__c_record_%d", idx, allocator=allocator)
}
@@ -59,13 +233,73 @@ record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator) ->
if target.kind != .Record || int(target.record) >= len(result.records) {
continue
}
if len(result.records[target.record].name) == 0 {
names[target.record] = alias.name
if !binding_identifier(result.records[target.record].name) {
delete(names[target.record], allocator)
names[target.record] = strings.clone(alias.name, allocator)
}
}
return names
}
binding_identifier :: proc(value: string) -> bool {
if len(value) == 0 || lexer.keyword_kind(value) != .Identifier {
return false
}
for byte, index in transmute([]byte)value {
letter := byte == '_' || byte >= 'a' && byte <= 'z' || byte >= 'A' && byte <= 'Z'
if !letter && (index == 0 || byte < '0' || byte > '9') {
return false
}
}
return true
}
record_name_conflicts :: proc(result: ^cimport.Result, record_index: int, name: string) -> bool {
for function in result.functions {
if function.name == name && len(function.reason) == 0 &&
(len(function.link_name) == 0 || function.link_name == function.name) {
return true
}
}
for macro in result.macros {
if macro.name == name && len(macro.reason) == 0 &&
lexer.keyword_kind(macro.name) == .Identifier {
return true
}
}
for alias in result.aliases {
if alias.name != name || len(alias.reason) > 0 || alias.type == cimport.INVALID_TYPE ||
int(alias.type) < 0 || int(alias.type) >= len(result.types) {
continue
}
target := result.types[alias.type]
if target.kind != .Record || int(target.record) != record_index {
return true
}
}
return false
}
destroy_record_name_table :: proc(names: []string, allocator: mem.Allocator) {
for name in names {
delete(name, allocator)
}
delete(names, allocator)
}
safe_name_fragment :: proc(value: string, allocator: mem.Allocator) -> string {
b := strings.builder_make(allocator)
for byte in transmute([]byte)value {
if byte >= 'a' && byte <= 'z' || byte >= 'A' && byte <= 'Z' ||
byte >= '0' && byte <= '9' || byte == '_' {
strings.write_byte(&b, byte)
} else {
strings.write_byte(&b, '_')
}
}
return strings.to_string(b)
}
// 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) {
@@ -150,36 +384,68 @@ render_c_func :: proc(
render_type(b, result, ret, record_names)
}
emit_records :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_records :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for record, idx in result.records {
name := record_names[idx]
representable := record_has_native_spelling(result, u32(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 :: opaque\n", name)
} else if record.complete && len(record.reason) == 0 && !representable {
fmt.sbprintf(b, "# unsupported in bindings: C record '%s' contains an unsupported field type\n", name)
wrote = true
}
if !representable {
// opaque / pointer-only struct
text := fmt.tprintf("%s :: opaque\n", name)
canonical := text
if len(record.identity) > 0 {
canonical = fmt.tprintf("%s\n%s", record.identity, text)
}
emitted, ok := write_declaration(b, registry, name, .Record, text, canonical)
if !ok {
return false
}
wrote = wrote || emitted
continue
}
fmt.sbprintf(b, "%s :: c_struct {{\n", name)
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%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')
fmt.sbprintf(&declaration, "\t%s ", field.name)
render_type(&declaration, result, field.type, record_names)
strings.write_byte(&declaration, '\n')
}
strings.write_string(b, "}\n")
wrote = true
strings.write_string(&declaration, "}\n")
text := strings.to_string(declaration)
canonical := text
if len(record.identity) > 0 {
canonical = fmt.tprintf("%s\n%s", record.identity, text)
}
emitted, ok := write_declaration(b, registry, name, .Record, text, canonical)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_aliases :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_aliases :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for alias in result.aliases {
if len(alias.reason) > 0 {
@@ -187,27 +453,46 @@ emit_aliases :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names:
wrote = true
continue
}
if !type_has_native_spelling(result, alias.type) {
fmt.sbprintf(b, "# unsupported in bindings: typedef '%s' — underlying type has no native spelling\n", alias.name)
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
if target.kind == .Record && int(target.record) < len(record_names) {
record := result.records[target.record]
if record_names[target.record] == alias.name || record.name == 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
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%s :: alias ", alias.name)
render_type(&declaration, result, alias.type, record_names)
strings.write_byte(&declaration, '\n')
text := strings.to_string(declaration)
emitted, ok := write_declaration(b, registry, alias.name, .Alias, text)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_macros :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for macro in result.macros {
if len(macro.reason) > 0 {
@@ -223,23 +508,41 @@ emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names:
continue
}
if macro.aggregate {
emit_aggregate_macro(b, result, macro, record_names)
wrote = true
declaration := strings.builder_make(context.temp_allocator)
binding := emit_aggregate_macro(&declaration, result, macro, record_names)
text := strings.to_string(declaration)
if binding {
emitted, ok := write_declaration(b, registry, macro.name, .Macro, text)
if !ok {
return false
}
wrote = wrote || emitted
} else {
strings.write_string(b, text)
wrote = true
}
continue
}
strings.write_string(b, macro.name)
declaration := strings.builder_make(context.temp_allocator)
strings.write_string(&declaration, macro.name)
if macro_scalar_kind(result, macro.type) {
strings.write_byte(b, ' ')
render_type(b, result, macro.type, record_names)
strings.write_byte(&declaration, ' ')
render_type(&declaration, result, macro.type, record_names)
}
strings.write_string(b, " :: ")
render_macro_value(b, macro.value)
strings.write_byte(b, '\n')
wrote = true
strings.write_string(&declaration, " :: ")
render_macro_value(&declaration, macro.value)
strings.write_byte(&declaration, '\n')
text := strings.to_string(declaration)
emitted, ok := write_declaration(b, registry, macro.name, .Macro, text)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_aggregate_macro :: proc(
@@ -247,11 +550,11 @@ emit_aggregate_macro :: proc(
result: ^cimport.Result,
macro: cimport.Macro_Constant,
record_names: []string,
) {
) -> bool {
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) {
if ridx < len(result.records) && record_has_native_spelling(result, u32(ridx)) {
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])
@@ -263,14 +566,20 @@ emit_aggregate_macro :: proc(
render_macro_value(b, macro.values[index])
}
strings.write_string(b, " }\n")
return
return true
}
}
}
fmt.sbprintf(b, "# unsupported in bindings: aggregate macro '%s' has no native spelling\n", macro.name)
return false
}
emit_functions :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_functions :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for function in result.functions {
if len(function.reason) > 0 {
@@ -278,19 +587,41 @@ emit_functions :: proc(b: ^strings.Builder, result: ^cimport.Result, record_name
wrote = true
continue
}
if !function_has_native_spelling(result, function) {
fmt.sbprintf(b, "# unsupported in bindings: function '%s' — signature has no native spelling\n", function.name)
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
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%s ", function.name)
render_c_func(&declaration, result, function.params, function.param_names, function.result, function.variadic, record_names)
strings.write_byte(&declaration, '\n')
canonical := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&canonical, "%s ", function.name)
render_c_func(&canonical, result, function.params, nil, function.result, function.variadic, record_names)
strings.write_byte(&canonical, '\n')
emitted, ok := write_declaration(
b,
registry,
function.name,
.Function,
strings.to_string(declaration),
strings.to_string(canonical),
)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_variables :: proc(b: ^strings.Builder, result: ^cimport.Result) {
@@ -350,6 +681,73 @@ macro_scalar_kind :: proc(result: ^cimport.Result, id: cimport.Type_Id) -> bool
return false
}
function_has_native_spelling :: proc(result: ^cimport.Result, function: cimport.Function) -> bool {
for param in function.params {
if !type_has_native_spelling(result, param) {
return false
}
}
return type_has_native_spelling(result, function.result, allow_void=true)
}
record_has_native_spelling :: proc(result: ^cimport.Result, index: u32, depth := 0) -> bool {
if depth > 64 || int(index) < 0 || int(index) >= len(result.records) {
return false
}
record := result.records[index]
if record.kind == .Union || !record.complete || len(record.reason) > 0 || len(record.fields) == 0 {
return false
}
for field in record.fields {
if !type_has_native_spelling(result, field.type, depth=depth+1) {
return false
}
}
return true
}
type_has_native_spelling :: proc(
result: ^cimport.Result,
id: cimport.Type_Id,
allow_void := false,
depth := 0,
) -> bool {
if depth > 64 || id == cimport.INVALID_TYPE || int(id) < 0 || int(id) >= len(result.types) {
return false
}
item := result.types[id]
switch item.kind {
case .Void:
return allow_void
case .C_Bool, .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
case .Pointer:
if item.child == cimport.INVALID_TYPE || int(item.child) < 0 || int(item.child) >= len(result.types) {
return false
}
child := result.types[item.child]
if child.kind == .Void || child.kind == .Record {
return true
}
return type_has_native_spelling(result, item.child, allow_void=true, depth=depth+1)
case .Array:
return type_has_native_spelling(result, item.child, depth=depth+1)
case .Function:
for param in item.params {
if !type_has_native_spelling(result, param, depth=depth+1) {
return false
}
}
return type_has_native_spelling(result, item.child, allow_void=true, depth=depth+1)
case .Record:
return record_has_native_spelling(result, item.record, depth+1)
case .Invalid:
return false
}
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) &&