upgrade enum discriminants
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(¶ms, 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(¶ms, param)
|
||||
if param == INVALID_TYPE && len(reason) == 0 {
|
||||
reason = "function parameter type is not supported"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(®istry.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(®istry)
|
||||
|
||||
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],
|
||||
®istry,
|
||||
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) &&
|
||||
|
||||
Reference in New Issue
Block a user