package loader import "../ast" import "../cimport" import "../lexer" import "../parser" import "../source" import "../symbol" import "../target" import "../types" import "core:fmt" import "core:math" import "core:mem" import "core:os" import "core:path/filepath" import "core:slice" import "core:strings" State :: struct { module: ^ast.Module, sources: ^source.Store, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, token_allocator: mem.Allocator, allocator: mem.Allocator, c_options: cimport.Options, selected: target.Target, project_root: string, mode: ast.Compile_Mode, record_identities: [dynamic]string, record_types: [dynamic]types.Type, root_failed: bool, } is_identifier :: proc(value: string) -> bool { if len(value) == 0 { return false } is_start := proc(value: byte) -> bool { return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z' } if !is_start(value[0]) { return false } for byte_value in transmute([]byte)value[1:] { if !is_start(byte_value) && !(byte_value >= '0' && byte_value <= '9') { return false } } return true } find_package :: proc(state: ^State, path: string) -> ast.Package_Id { for pkg, id in state.module.packages { if pkg.path == path { return ast.package_id(id) } } return ast.INVALID_PACKAGE } add_placeholder :: proc(state: ^State, path: string) -> ast.Package_Id { if existing := find_package(state, path); existing != ast.INVALID_PACKAGE { return existing } id := ast.package_id(len(state.module.packages)) append(&state.module.packages, ast.Package{ path=strings.clone(path, state.allocator), name=symbol.intern(state.symbols, filepath.base(path)), available=false, }) return id } read_package_files :: proc(state: ^State, path: string) -> ([]os.File_Info, bool) { handle, open_error := os.open(path, os.O_RDONLY) if open_error != nil { return nil, false } defer os.close(handle) entries, read_error := os.read_dir(handle, -1, state.allocator) if read_error != nil { return nil, false } slice.sort_by(entries, proc(a, b: os.File_Info) -> bool { return a.name < b.name }) files: [dynamic]os.File_Info files.allocator = state.allocator for entry in entries { extension := filepath.ext(entry.name) if !entry.is_dir && (extension == ".bro" || extension == ".hon") { append(&files, entry) } else { os.file_info_delete(entry, state.allocator) } } delete(entries, state.allocator) return files[:], true } resolve_import_path :: proc(state: ^State, importing_path, import_path: string) -> (string, bool) { if filepath.is_abs(import_path) { return "", false } base := importing_path path := import_path if strings.has_prefix(import_path, "@") { base = state.project_root path = import_path[1:] } joined, join_error := filepath.join({base, path}, state.allocator) if join_error != nil { return "", false } canonical, ok := filepath.abs(joined, state.allocator) if ok { delete(joined, state.allocator) return canonical, true } return joined, false } header_package_name :: proc(path: string, symbols: ^symbol.Table) -> symbol.Id { base := filepath.base(path) extension := filepath.ext(base) if len(extension) > 0 { base = base[:len(base)-len(extension)] } return symbol.intern(symbols, base) } find_record_identity :: proc(state: ^State, identity: string) -> types.Type { for existing, index in state.record_identities { if existing == identity { return state.record_types[index] } } return types.INVALID } translate_c_type :: proc( state: ^State, result: ^cimport.Result, value: cimport.Type_Id, pkg: ast.Package_Id, record_mapping: []types.Type, type_mapping: []types.Type, ) -> types.Type { if value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) { return types.INVALID } if types.is_valid(type_mapping[value]) { return type_mapping[value] } item := result.types[value] translated := types.INVALID switch item.kind { case .Invalid: translated = types.INVALID case .Void: translated = types.VOID case .C_Bool: translated = types.BOOL case .C_Char: translated = types.C_CHAR case .C_Schar: translated = types.C_SCHAR case .C_Uchar: translated = types.C_UCHAR case .C_Short: translated = types.C_SHORT case .C_Ushort: translated = types.C_USHORT case .C_Int: translated = types.C_INT case .C_Uint: translated = types.C_UINT case .C_Long: translated = types.C_LONG case .C_Ulong: translated = types.C_ULONG case .C_Longlong: translated = types.C_LONGLONG case .C_Ulonglong: translated = types.C_ULONGLONG case .C_Float: translated = types.C_FLOAT case .C_Double: translated = types.C_DOUBLE case .C_Longdouble: translated = types.C_LONGDOUBLE case .Pointer: child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) if child == types.VOID { child = types.ANYOPAQUE } if types.is_valid(child) { pointer := types.pointer(&state.module.type_store, child, item.mutable, true) translated = types.optional(&state.module.type_store, pointer) } case .Array: child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) if types.is_valid(child) { translated = types.array(&state.module.type_store, child, item.count, item.mutable) } case .Function: params := make([]types.Type, len(item.params), state.allocator) for param, index in item.params { params[index] = translate_c_type(state, result, param, pkg, record_mapping, type_mapping) if !types.is_valid(params[index]) { delete(params, state.allocator) type_mapping[value] = types.INVALID return types.INVALID } } result_type := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) if types.is_valid(result_type) { translated = types.function(&state.module.type_store, params, result_type, true, item.variadic) } delete(params, state.allocator) case .Record: if int(item.record) < len(record_mapping) { translated = record_mapping[item.record] } } type_mapping[value] = translated return translated } function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool { if left.result != result || types.is_valid(left.error) || left.variadic != variadic || len(left.params) != len(params) { return false } for param, index in params { if left.params[index].type != param.type { return false } } return true } RECORD_DEPENDENCY_PENDING :: "C record contains an incomplete or unsupported record field" record_layout_reason :: proc( store: ^types.Store, record: cimport.Record, fields: []types.Field, selected: target.Target, ) -> string { if len(fields) == 0 { if record.size > 0 { return "anonymous C record fields are not supported" } return "empty C records are not supported" } if len(fields) != len(record.fields) || record.alignment == 0 { return "C record metadata is incomplete" } size: u64 alignment: u64 = 1 if record.kind == .Union { for field in fields { if !types.is_runtime_value(field.type, store) { return RECORD_DEPENDENCY_PENDING } if field.offset != 0 { return "non-natural C record layouts are not supported" } size = max(size, types.size(field.type, store, selected)) alignment = max(alignment, u64(types.alignment_of(field.type, store, selected))) } } else { offset: u64 for field in fields { if !types.is_runtime_value(field.type, store) { return RECORD_DEPENDENCY_PENDING } field_alignment := u64(types.alignment_of(field.type, store, selected)) offset = (offset+field_alignment-1)/field_alignment*field_alignment if field.offset != offset { if field.offset < offset { return "packed C records are not supported" } return "non-natural C record layouts are not supported" } offset += types.size(field.type, store, selected) alignment = max(alignment, field_alignment) } size = offset } size = (size+alignment-1)/alignment*alignment if u64(record.alignment) > alignment { return "over-aligned C records are not supported" } if u64(record.alignment) < alignment || record.size < size { return "packed C records are not supported" } if record.size != size { return "non-natural C record layouts are not supported" } return "" } c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id, depth := 0) -> string { if depth > 64 || value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) { return "" } item := result.types[value] #partial switch item.kind { case .Pointer: return "" case .Array: return c_record_by_value_reason(result, item.child, depth+1) case .Record: if int(item.record) >= len(result.records) { return "C record metadata is incomplete" } record := result.records[item.record] if len(record.reason) > 0 { return record.reason } if !record.complete { return "incomplete C records are pointer-only" } } return "" } add_import_unsupported :: proc(state: ^State, pkg: ast.Package_Id, name: string, reason: string) { if len(name) == 0 || len(reason) == 0 { return } append(&state.module.unsupported, ast.Unsupported{ pkg=pkg, name=symbol.intern(state.symbols, name), reason=strings.clone(reason, state.allocator), }) } find_trampoline :: proc(result: ^cimport.Result, symbol: string) -> (cimport.Trampoline, bool) { for trampoline in result.trampolines { if trampoline.symbol == symbol { return trampoline, true } } return {}, false } add_c_trampoline :: proc(state: ^State, trampoline: cimport.Trampoline) { if len(trampoline.symbol) == 0 || len(trampoline.source) == 0 { return } for existing in state.module.c_trampolines { if existing.symbol == trampoline.symbol { return } } append(&state.module.c_trampolines, ast.Trampoline{ symbol=strings.clone(trampoline.symbol, state.allocator), source=strings.clone(trampoline.source, state.allocator), header=strings.clone(trampoline.header, state.allocator), }) } add_import_expr :: proc(state: ^State, expr: ast.Expr) -> ast.Expr_Id { id := ast.expr_id(len(state.module.exprs)) append(&state.module.exprs, expr) return id } add_macro_value_expr :: proc(state: ^State, value: cimport.Macro_Value, span: source.Span) -> ast.Expr_Id { #partial switch value.kind { case .Integer: if value.negative { magnitude := value.integer operand := add_import_expr(state, ast.Expr{ kind=.Integer, span=span, integer=magnitude, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) return add_import_expr(state, ast.Expr{ kind=.Negate, span=span, left=operand, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } return add_import_expr(state, ast.Expr{ kind=.Integer, span=span, integer=value.integer, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Float: return add_import_expr(state, ast.Expr{ kind=.Float, span=span, integer=value.integer, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case: } return add_import_expr(state, ast.Expr{ kind=.Invalid, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.add(state.diagnostics, span, "unsupported C macro value"), }) } MAX_MACRO_ZERO_DEPTH :: 64 MAX_MACRO_ZERO_NODES :: 65_536 find_macro_record_name :: proc( state: ^State, pkg: ast.Package_Id, value: types.Type, ) -> (symbol.Id, bool) { resolved := types.resolve_alias(value, &state.module.type_store) for item, index in state.module.type_store.nodes { if item.pkg != u32(pkg) || item.name == 0 { continue } candidate := types.DYNAMIC_START+types.Type(index) if types.resolve_alias(candidate, &state.module.type_store) == resolved { return symbol.Id(item.name), true } } return symbol.INVALID, false } add_macro_zero_expr :: proc( state: ^State, pkg: ast.Package_Id, value_type: types.Type, span: source.Span, depth: int, remaining: ^int, ) -> (ast.Expr_Id, bool) { if depth > MAX_MACRO_ZERO_DEPTH || remaining^ <= 0 { return ast.INVALID_EXPR, false } remaining^ -= 1 store := &state.module.type_store resolved := types.resolve_alias(value_type, store) #partial switch types.kind(resolved, store) { case .Scalar: kind := ast.Expr_Kind.Integer if types.is_float(resolved, state.selected) { kind = .Float } return add_import_expr(state, ast.Expr{ kind=kind, span=span, integer=0, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }), true case .Optional: if !types.is_optional_pointer(resolved, store) { return ast.INVALID_EXPR, false } return add_import_expr(state, ast.Expr{ kind=.None, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }), true case .Array: item, ok := types.node(store, resolved) if !ok || item.count > u64(max(int)) || item.count > u64(remaining^) { return ast.INVALID_EXPR, false } args := make([]ast.Expr_Id, int(item.count), state.allocator) for index in 0.. i128 { magnitude := i128(value.integer) return -magnitude if value.negative else magnitude } convert_macro_integer :: proc( value: i128, destination: types.Type, selected: target.Target, ) -> (Converted_Macro_Value, bool) { bits := types.bits(destination, selected) if bits <= 0 || bits > 64 { return {}, false } if types.is_unsigned(destination, selected) { modulus := i128(1) << u32(bits) wrapped := value % modulus if wrapped < 0 { wrapped += modulus } return Converted_Macro_Value{ kind=.Integer, integer=u64(wrapped), }, true } if !types.is_signed(destination, selected) { return {}, false } limit := i128(1) << u32(bits-1) if value < -limit || value >= limit { return {}, false } if value < 0 { return Converted_Macro_Value{ kind=.Integer, integer=u64(-value), negative=true, }, true } return Converted_Macro_Value{kind=.Integer, integer=u64(value)}, true } macro_float_value :: proc( value: cimport.Macro_Value, source_type: types.Type, selected: target.Target, ) -> (f64, bool) { if value.kind != .Float || !types.is_float(source_type, selected) { return 0, false } number := transmute(f64)value.integer if types.bits(source_type, selected) == 32 { number = f64(f32(number)) } if math.is_nan(number) || math.is_inf(number) { return 0, false } return number, true } convert_macro_float :: proc( number: f64, destination: types.Type, selected: target.Target, ) -> (Converted_Macro_Value, bool) { if !types.is_float(destination, selected) { return {}, false } converted := number if types.bits(destination, selected) == 32 { rounded := f64(f32(number)) if math.is_inf(rounded) { return {}, false } converted = rounded } return Converted_Macro_Value{ kind=.Float, integer=transmute(u64)converted, }, true } convert_macro_field_value :: proc( state: ^State, result: ^cimport.Result, value: cimport.Macro_Value, field_type: types.Type, pkg: ast.Package_Id, record_mapping: []types.Type, type_mapping: []types.Type, ) -> (Converted_Macro_Value, bool) { store := &state.module.type_store destination := types.resolve_alias(field_type, store) source_type := translate_c_type( state, result, value.type, pkg, record_mapping, type_mapping, ) if !types.is_concrete_scalar(source_type) { return {}, false } if types.is_optional_pointer(destination, store) { if types.is_concrete_integer(source_type) && value.kind == .Integer && macro_integer_value(value) == 0 { return Converted_Macro_Value{kind=.Null}, true } return {}, false } if types.is_concrete_integer(source_type) { if value.kind != .Integer { return {}, false } integer := macro_integer_value(value) if types.is_concrete_integer(destination) { return convert_macro_integer(integer, destination, state.selected) } if types.is_float(destination, state.selected) { return convert_macro_float(f64(integer), destination, state.selected) } return {}, false } number, number_ok := macro_float_value(value, source_type, state.selected) if !number_ok { return {}, false } if types.is_float(destination, state.selected) { return convert_macro_float(number, destination, state.selected) } if types.is_concrete_integer(destination) { truncated := math.trunc(number) bits := types.bits(destination, state.selected) if bits <= 0 || bits > 64 { return {}, false } if types.is_signed(destination, state.selected) { limit := f64(i128(1) << u32(bits-1)) if truncated < -limit || truncated >= limit { return {}, false } } else if types.is_unsigned(destination, state.selected) { limit := f64(i128(1) << u32(bits)) if truncated < 0 || truncated >= limit { return {}, false } } else { return {}, false } return convert_macro_integer(i128(truncated), destination, state.selected) } return {}, false } add_converted_macro_value_expr :: proc( state: ^State, value: Converted_Macro_Value, span: source.Span, ) -> (ast.Expr_Id, bool) { #partial switch value.kind { case .Integer: return add_macro_value_expr(state, cimport.Macro_Value{ kind=.Integer, integer=value.integer, negative=value.negative, }, span), true case .Float: return add_macro_value_expr(state, cimport.Macro_Value{ kind=.Float, integer=value.integer, }, span), true case .Null: return add_import_expr(state, ast.Expr{ kind=.None, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }), true case: } return ast.INVALID_EXPR, false } add_macro_aggregate_expr :: proc( state: ^State, result: ^cimport.Result, macro: cimport.Macro_Constant, record_type: types.Type, pkg: ast.Package_Id, record_mapping: []types.Type, type_mapping: []types.Type, span: source.Span, ) -> (ast.Expr_Id, bool) { fields := types.fields_for(&state.module.type_store, record_type) union_record := types.is_union(record_type, &state.module.type_store) initializer_count := 1 if union_record else len(fields) if len(fields) == 0 || len(macro.values) > initializer_count { return ast.INVALID_EXPR, false } converted := make([]Converted_Macro_Value, len(macro.values), state.allocator) defer delete(converted, state.allocator) for value, index in macro.values { converted[index], _ = convert_macro_field_value( state, result, value, fields[index].type, pkg, record_mapping, type_mapping, ) if converted[index].kind == .Invalid { return ast.INVALID_EXPR, false } } args := make([]ast.Expr_Id, initializer_count, state.allocator) remaining := MAX_MACRO_ZERO_NODES for index in 0.. (ast.Global_Id, bool) { for global, index in module.globals { if global.pkg == pkg && global.name == name { return ast.global_id(index), true } } return ast.INVALID_GLOBAL, false } find_function_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool { for function in module.functions { if function.pkg == pkg && function.name == name { return true } } return false } remove_value_declarations_in_package :: proc( state: ^State, pkg: ast.Package_Id, name: symbol.Id, ) { function_index := 0 for function_index < len(state.module.functions) { function := state.module.functions[function_index] if function.pkg != pkg || function.name != name { function_index += 1 continue } delete(function.params, state.allocator) delete(function.body, state.allocator) delete(function.link_name, state.allocator) delete(function.unsupported_reason, state.allocator) ordered_remove(&state.module.functions, function_index) } global_index := 0 for global_index < len(state.module.globals) { global := state.module.globals[global_index] if global.pkg != pkg || global.name != name { global_index += 1 continue } delete(global.link_name, state.allocator) ordered_remove(&state.module.globals, global_index) } unsupported_index := 0 for unsupported_index < len(state.module.unsupported) { item := state.module.unsupported[unsupported_index] if item.pkg != pkg || item.name != name { unsupported_index += 1 continue } delete(item.reason, state.allocator) ordered_remove(&state.module.unsupported, unsupported_index) } } add_external_variable_global :: proc( state: ^State, result: ^cimport.Result, pkg: ast.Package_Id, variable: cimport.Variable, record_mapping: []types.Type, type_mapping: []types.Type, span: source.Span, ) { name := symbol.intern(state.symbols, variable.name) variable_type := translate_c_type(state, result, variable.type, pkg, record_mapping, type_mapping) unsupported_reason := variable.reason if len(unsupported_reason) == 0 { unsupported_reason = c_record_by_value_reason(result, variable.type) } if len(unsupported_reason) == 0 && !types.is_runtime_value(variable_type, &state.module.type_store) { unsupported_reason = "C variable type is not supported" } if len(unsupported_reason) > 0 { add_import_unsupported(state, pkg, variable.name, unsupported_reason) return } if find_function_in_package(state.module, pkg, name) { add_import_unsupported(state, pkg, variable.name, "C variable conflicts with a function declaration") return } if existing, ok := find_global_in_package(state.module, pkg, name); ok { global := state.module.globals[existing] if !global.external || !types.equal(global.type, variable_type) || global.writable != variable.mutable { add_import_unsupported(state, pkg, variable.name, "conflicting C declarations for variable") } return } _ = ast.global_id(len(state.module.globals)) append(&state.module.globals, ast.Global{ span=span, name=name, link_name=strings.clone(variable.name, state.allocator), pkg=pkg, file=ast.INVALID_FILE, type=variable_type, immutable=true, external=true, writable=variable.mutable, expr=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } add_macro_constant_global :: proc( state: ^State, result: ^cimport.Result, pkg: ast.Package_Id, macro: cimport.Macro_Constant, record_mapping: []types.Type, type_mapping: []types.Type, span: source.Span, ) { name := symbol.intern(state.symbols, macro.name) remove_value_declarations_in_package(state, pkg, name) if macro.aggregate { type_name := symbol.intern(state.symbols, macro.type_name) named := types.find_named(&state.module.type_store, u32(pkg), u32(type_name)) record_type := types.resolve_alias(named, &state.module.type_store) if !types.is_record(record_type, &state.module.type_store) || types.is_opaque_struct(record_type, &state.module.type_store) { add_import_unsupported(state, pkg, macro.name, "C macro aggregate type is not supported") return } expr, ok := add_macro_aggregate_expr( state, result, macro, record_type, pkg, record_mapping, type_mapping, span, ) if !ok { add_import_unsupported(state, pkg, macro.name, "C macro aggregate initializer is not representable") return } _ = ast.global_id(len(state.module.globals)) append(&state.module.globals, ast.Global{ span=span, name=name, pkg=pkg, file=ast.INVALID_FILE, type=record_type, immutable=true, expr=expr, diagnostic=source.INVALID_DIAGNOSTIC, }) return } macro_type := translate_c_type(state, result, macro.type, pkg, record_mapping, type_mapping) if !types.is_runtime_value(macro_type, &state.module.type_store) { add_import_unsupported(state, pkg, macro.name, "C macro constant type is not supported") return } expr := add_macro_value_expr(state, macro.value, span) _ = ast.global_id(len(state.module.globals)) append(&state.module.globals, ast.Global{ span=span, name=name, pkg=pkg, file=ast.INVALID_FILE, type=macro_type, immutable=true, expr=expr, diagnostic=source.INVALID_DIAGNOSTIC, }) } load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id { canonical, ok := filepath.abs(path, state.allocator) if !ok { id := add_placeholder(state, path) source.addf(state.diagnostics, import_span, "could not resolve C header '%s'", path) return id } if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE { delete(canonical, state.allocator) return existing } pkg_id := ast.package_id(len(state.module.packages)) append(&state.module.packages, ast.Package{ path=canonical, name=header_package_name(canonical, state.symbols), available=false, kind=.C_Header, }) result := cimport.import_header(state.c_options, canonical, state.selected, state.allocator) defer cimport.destroy_result(&result) if !result.available { message := result.error_message if len(result.error_message) > 0 else "C header import failed" source.addf(state.diagnostics, import_span, "could not import C header '%s': %s", path, message) if result.infrastructure { state.root_failed = true } return pkg_id } state.module.packages[pkg_id].available = true record_mapping := make([]types.Type, len(result.records), state.allocator) defer delete(record_mapping, state.allocator) for record, index in result.records { record_type := find_record_identity(state, record.identity) if !types.is_valid(record_type) { name := record.name if len(name) == 0 { name = fmt.tprintf("__c_record_%d", len(state.record_types)) } record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name))) _ = types.define_record(&state.module.type_store, record_type, nil, false, true, record.kind == .Union) append(&state.record_identities, strings.clone(record.identity, state.allocator)) append(&state.record_types, record_type) } record_mapping[index] = record_type } type_mapping := make([]types.Type, len(result.types), state.allocator) defer delete(type_mapping, state.allocator) for _ in 0.. 0 { continue } record_type := record_mapping[record_index] item, ok := types.node(&state.module.type_store, record_type) if !ok || (item.declared && !item.opaque) { continue } fields := make([]types.Field, len(record.fields), state.allocator) for field, field_index in record.fields { fields[field_index] = types.Field{ name=u32(symbol.intern(state.symbols, field.name)), type=translate_c_type(state, &result, field.type, pkg_id, record_mapping, type_mapping), offset=field.offset, } } layout_reason := record_layout_reason(&state.module.type_store, record, fields, state.selected) if len(layout_reason) == 0 { changed = types.define_record( &state.module.type_store, record_type, fields, true, false, record.kind == .Union, record.size, record.alignment, ) || changed } else if layout_reason != RECORD_DEPENDENCY_PENDING && len(record.reason) == 0 { delete(result.records[record_index].reason, result.allocator) result.records[record_index].reason = fmt.aprintf("%s", layout_reason, allocator=result.allocator) } delete(fields, state.allocator) } if !changed { break } } for alias in result.aliases { name := symbol.intern(state.symbols, alias.name) id := types.named(&state.module.type_store, u32(pkg_id), u32(name)) child := translate_c_type(state, &result, alias.type, pkg_id, record_mapping, type_mapping) _ = types.define_alias(&state.module.type_store, id, child) if len(alias.reason) > 0 { append(&state.module.unsupported, ast.Unsupported{ pkg=pkg_id, name=name, reason=strings.clone(alias.reason, state.allocator), }) } } for function in result.functions { params := make([]ast.Param, len(function.params), state.allocator) for param_type, index in function.params { params[index] = ast.Param{ name=symbol.intern(state.symbols, fmt.tprintf("arg%d", index)), span=import_span, type=translate_c_type(state, &result, param_type, pkg_id, record_mapping, type_mapping), } } function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping) unsupported_reason := function.reason if len(unsupported_reason) == 0 { for param_type in function.params { if reason := c_record_by_value_reason(&result, param_type); len(reason) > 0 { unsupported_reason = reason break } } } if len(unsupported_reason) == 0 { unsupported_reason = c_record_by_value_reason(&result, function.result) } if len(unsupported_reason) == 0 { for param in params { if types.contains_c_struct_by_value(param.type, &state.module.type_store) { unsupported_reason = "C record parameter is incomplete or has an unsupported layout" break } } } if len(unsupported_reason) == 0 && types.contains_c_struct_by_value(function_result, &state.module.type_store) { unsupported_reason = "C record result is incomplete or has an unsupported layout" } name := symbol.intern(state.symbols, function.name) duplicate := false for &existing in state.module.functions { if existing.pkg != pkg_id || existing.name != name { continue } duplicate = true if !function_signatures_equal(existing, params, function_result, function.variadic) && len(existing.unsupported_reason) == 0 { existing.unsupported_reason = fmt.aprintf( "conflicting C declarations for '%s'", function.name, allocator=state.allocator, ) } break } if duplicate { delete(params, state.allocator) continue } append(&state.module.functions, ast.Function{ span=import_span, name=name, pkg=pkg_id, file=ast.INVALID_FILE, c_abi=true, imported=true, has_body=false, variadic=function.variadic, params=params, result=function_result, link_name=strings.clone(function.link_name, state.allocator), unsupported_reason=strings.clone(unsupported_reason, state.allocator), diagnostic=source.INVALID_DIAGNOSTIC, }) // Emit the wrapper only for a `static inline` that survives as supported. // cimport may translate its signature fine yet the by-value record layout // checks above can still reject it; a wrapper for an uncallable function // would just be dead external code. if len(unsupported_reason) == 0 && len(function.link_name) > 0 { if trampoline, ok := find_trampoline(&result, function.link_name); ok { add_c_trampoline(state, trampoline) } } } for variable in result.variables { add_external_variable_global( state, &result, pkg_id, variable, record_mapping, type_mapping, import_span, ) } for macro in result.macros { add_macro_constant_global( state, &result, pkg_id, macro, record_mapping, type_mapping, import_span, ) } for item in result.unsupported { name := symbol.intern(state.symbols, item.name) if item.final_macro { remove_value_declarations_in_package(state, pkg_id, name) } append(&state.module.unsupported, ast.Unsupported{ pkg=pkg_id, name=name, reason=strings.clone(item.reason, state.allocator), }) } return pkg_id } resolve_package_imports :: proc(state: ^State, pkg_id: ast.Package_Id) { if int(pkg_id) >= len(state.module.packages) { return } canonical := state.module.packages[pkg_id].path import_count := len(state.module.imports) for import_id in 0.. ast.Package_Id { canonical, ok := filepath.abs(path, state.allocator) if !ok || !os.is_dir(path) { if is_root { state.root_failed = true if len(canonical) > 0 { delete(canonical, state.allocator) } return ast.INVALID_PACKAGE } placeholder := path if len(canonical) > 0 { placeholder = canonical } id := add_placeholder(state, placeholder) source.addf(state.diagnostics, import_span, "could not import package directory '%s'", path) if len(canonical) > 0 { delete(canonical, state.allocator) } return id } if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE { if include_tests && !state.module.packages[existing].test { state.module.packages[existing].test = true resolve_package_imports(state, existing) } delete(canonical, state.allocator) return existing } pkg_id := ast.package_id(len(state.module.packages)) append(&state.module.packages, ast.Package{ path=canonical, name=symbol.intern(state.symbols, filepath.base(canonical)), available=true, test=state.mode == .Test && is_root || include_tests, }) files, files_ok := read_package_files(state, canonical) if !files_ok { state.root_failed = true return pkg_id } if len(files) == 0 { if is_root { state.root_failed = true } else { source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro or .hon files", canonical) state.module.packages[pkg_id].available = false } os.file_info_slice_delete(files, state.allocator) return pkg_id } for file_info in files { if file_info.size < 0 || !source.fits_source_length(u64(file_info.size)) { source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath) state.root_failed = true continue } bytes, read_ok := os.read_entire_file(file_info.fullpath, state.sources.allocator) if !read_ok { state.root_failed = true continue } if !source.fits_source_length(u64(len(bytes))) { source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath) delete(bytes, state.sources.allocator) state.root_failed = true continue } source_id := source.add_source_owned(state.sources, file_info.fullpath, bytes) file_id := ast.file_id(len(state.module.files)) append(&state.module.files, ast.File{source=source_id, pkg=pkg_id}) stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.symbols, state.token_allocator) parser.parse_into(&stream, &state.sources.items[source_id], state.diagnostics, state.module, pkg_id, file_id) delete(stream.items) } os.file_info_slice_delete(files, state.allocator) resolve_package_imports(state, pkg_id) return pkg_id } declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: ast.File_Id, name: symbol.Id) -> bool { for function in module.functions { if function.pkg == pkg && function.name == name && (!function.file_hidden || function.file == file) { return true } } for global in module.globals { if global.pkg == pkg && global.name == name && (!global.file_hidden || global.file == file) { return true } } type_id := types.find_named(&module.type_store, u32(pkg), u32(name), file=u32(file)) type_item, type_ok := types.node(&module.type_store, type_id) if type_ok && type_item.declared { return true } return false } validate_imports :: proc(state: ^State) { for import_item, import_id in state.module.imports { if import_item.test_only { continue } if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE { state.module.imports[import_id].alias = state.module.packages[import_item.target].name } alias := state.module.imports[import_id].alias alias_text := symbol.resolve(state.symbols, alias) if !is_identifier(alias_text) { state.module.imports[import_id].diagnostic = source.add( state.diagnostics, import_item.span, "import requires an explicit valid identifier alias", ) state.module.imports[import_id].valid = false } if declaration_conflicts(state.module, import_item.pkg, import_item.file, alias) { state.module.imports[import_id].diagnostic = source.addf( state.diagnostics, import_item.span, "import alias '%s' conflicts with a package declaration", alias_text, ) state.module.imports[import_id].valid = false } for previous in state.module.imports[:import_id] { if previous.file == import_item.file && previous.alias == alias { state.module.imports[import_id].diagnostic = source.addf( state.diagnostics, import_item.span, "duplicate import alias '%s' in the same file", alias_text, ) state.module.imports[import_id].valid = false break } } } } find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id { for import_item, index in module.imports { if !import_item.test_only && import_item.file == file && import_item.alias == alias { return ast.import_id(index) } } 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 } alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool { for function in module.functions { if function.pkg == alias.pkg && function.name == alias.name && alias_declarations_conflict(alias.file, alias.file_hidden, function.file, function.file_hidden) { return true } } for global in module.globals { if global.pkg == alias.pkg && global.name == alias.name && alias_declarations_conflict(alias.file, alias.file_hidden, global.file, global.file_hidden) { return true } } for item in module.type_store.nodes { if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) && alias_declarations_conflict(alias.file, alias.file_hidden, ast.File_Id(item.file), item.file_hidden) { return true } } return false } direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Declaration_Alias_Kind, u32, int) { kind := ast.Declaration_Alias_Kind.Invalid target: u32 kinds := 0 for function, index in module.functions { if function.pkg == pkg && function.name == name && !function.generated && !function.file_hidden { kind = .Function target = u32(ast.function_id(index)) kinds += 1 break } } for global, index in module.globals { if global.pkg == pkg && global.name == name && !global.file_hidden { kind = .Global target = u32(ast.global_id(index)) kinds += 1 break } } if value := types.find_named(&module.type_store, u32(pkg), u32(name)); types.is_valid(value) { if item, ok := types.node(&module.type_store, value); ok && item.declared { kind = .Type target = u32(value) kinds += 1 } } return kind, target, kinds } hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool { for function in module.functions { if function.pkg == pkg && function.name == name && function.file_hidden { return true } } for global in module.globals { if global.pkg == pkg && global.name == name && global.file_hidden { return true } } for item in module.type_store.nodes { if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.file_hidden { return true } } for alias in module.aliases { if alias.valid && alias.pkg == pkg && alias.name == name && alias.file_hidden { return true } } return false } find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int { for alias, index in module.aliases { if alias.valid && alias.pkg == pkg && alias.name == name && !alias.file_hidden { return index } } return -1 } resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bool { alias := &state.module.aliases[index] if !alias.valid { return false } if states[index] == 2 { return alias.kind != .Invalid } if states[index] == 1 { alias.diagnostic = source.addf( state.diagnostics, alias.span, "declaration alias cycle involving '%s'", symbol.resolve(state.symbols, alias.name), ) alias.valid = false return false } states[index] = 1 defer states[index] = 2 kind, target, kinds := direct_alias_target(state.module, alias.target_pkg, alias.member) if kinds > 1 { alias.diagnostic = source.addf( state.diagnostics, alias.span, "package member '%s.%s' is ambiguous", symbol.resolve(state.symbols, alias.qualifier), symbol.resolve(state.symbols, alias.member), ) alias.valid = false return false } if kinds == 1 { alias.kind = kind alias.target = target return true } if target_alias := find_public_alias(state.module, alias.target_pkg, alias.member); target_alias >= 0 { if resolve_declaration_alias(state, target_alias, states) { resolved := state.module.aliases[target_alias] alias.kind = resolved.kind alias.target = resolved.target return true } alias.valid = false return false } if hidden_alias_target_exists(state.module, alias.target_pkg, alias.member) { alias.diagnostic = source.addf( state.diagnostics, alias.span, "package member '%s.%s' is file-hidden", symbol.resolve(state.symbols, alias.qualifier), symbol.resolve(state.symbols, alias.member), ) } else { alias.diagnostic = source.addf( state.diagnostics, alias.span, "package '%s' has no member '%s'", symbol.resolve(state.symbols, alias.qualifier), symbol.resolve(state.symbols, alias.member), ) } alias.valid = false return false } validate_declaration_aliases :: proc(state: ^State) { for &alias, index in state.module.aliases { name := symbol.resolve(state.symbols, alias.name) if alias_conflicts_with_declaration(state.module, alias) { alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with a package declaration", name) alias.valid = false continue } for previous in state.module.aliases[:index] { if previous.pkg == alias.pkg && previous.name == alias.name && alias_declarations_conflict(alias.file, alias.file_hidden, previous.file, previous.file_hidden) { alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name) alias.valid = false break } } if !alias.valid { continue } for import_item in state.module.imports { if import_item.file == alias.file && import_item.alias == alias.name { alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with an import", name) alias.valid = false break } } if !alias.valid { continue } import_id := find_type_import(state.module, alias.file, alias.qualifier) if import_id == ast.INVALID_IMPORT { alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown symbol '%s'", symbol.resolve(state.symbols, alias.qualifier)) alias.valid = false continue } state.module.imports[import_id].used = true import_item := state.module.imports[import_id] alias.target_pkg = import_item.target 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 { alias.diagnostic = source.addf(state.diagnostics, alias.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, alias.qualifier)) alias.valid = false } } states := make([]u8, len(state.module.aliases), state.allocator) defer delete(states, state.allocator) for _, index in state.module.aliases { _ = resolve_declaration_alias(state, index, states) } for &alias in state.module.aliases { if !alias.valid || alias.kind != .Type { continue } id := types.named( &state.module.type_store, u32(alias.pkg), u32(alias.name), file=u32(alias.file), file_hidden=alias.file_hidden, ) if !types.define_alias(&state.module.type_store, id, types.Type(alias.target)) { alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name)) alias.valid = false } } } canonical_type :: proc( module: ^ast.Module, value: types.Type, mapping: []types.Type, visiting: []bool, ) -> types.Type { if value < types.DYNAMIC_START { return value } index := int(value-types.DYNAMIC_START) if index < 0 || index >= len(mapping) { return value } if types.is_valid(mapping[index]) { return mapping[index] } if visiting[index] { return value } visiting[index] = true defer visiting[index] = false item := module.type_store.nodes[index] if item.kind == .Named { if item.qualifier != 0 { import_id := find_type_import(module, ast.File_Id(item.file), symbol.Id(item.qualifier)) if import_id != ast.INVALID_IMPORT { module.imports[import_id].used = true import_item := module.imports[import_id] resolved := types.find_named(&module.type_store, u32(import_item.target), item.name) if types.is_valid(resolved) { mapping[index] = canonical_type(module, resolved, mapping, visiting) return mapping[index] } } } mapping[index] = value return value } if item.kind == .Alias { resolved := canonical_type(module, item.child, mapping, visiting) mapping[index] = value if !types.is_valid(resolved) else resolved return mapping[index] } if item.kind == .Distinct { mapping[index] = value module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting) return value } if item.kind == .Enum { mapping[index] = value module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting) return value } if item.kind == .Struct || item.kind == .Union { mapping[index] = value module.type_store.nodes[index].extra = canonical_type(module, item.extra, mapping, visiting) fields := types.fields_for(&module.type_store, value) for &field in fields { field.type = canonical_type(module, field.type, mapping, visiting) } return value } if item.kind == .Fallible { item.child = canonical_type(module, item.child, mapping, visiting) item.extra = canonical_type(module, item.extra, mapping, visiting) resolved := types.fallible(&module.type_store, item.child, item.extra) mapping[index] = resolved return resolved } if item.kind == .Function { params := make([]types.Type, int(item.field_count), context.temp_allocator) for param, param_index in types.params_for(&module.type_store, value) { params[param_index] = canonical_type(module, param.type, mapping, visiting) } result := canonical_type(module, item.child, mapping, visiting) resolved := types.function(&module.type_store, params, result, item.c_abi, item.variadic) mapping[index] = resolved return resolved } if types.is_valid(item.child) { item.child = canonical_type(module, item.child, mapping, visiting) } if types.is_valid(item.extra) { item.extra = canonical_type(module, item.extra, mapping, visiting) } resolved := types.intern(&module.type_store, item) mapping[index] = resolved return resolved } diagnose_qualified_type_uses :: proc(state: ^State) { module := state.module for &type_use in module.type_uses { item, ok := types.node(&module.type_store, type_use.type) if !ok || item.qualifier == 0 || item.kind != .Named { continue } qualifier := symbol.Id(item.qualifier) name := symbol.Id(item.name) import_id := find_type_import(module, type_use.file, qualifier) if import_id == ast.INVALID_IMPORT { type_use.diagnostic = source.addf( state.diagnostics, type_use.span, "unknown symbol '%s'", symbol.resolve(state.symbols, qualifier), ) source.set_primary_label(state.diagnostics, type_use.diagnostic, "unknown symbol") continue } import_item := module.imports[import_id] if !import_item.valid || import_item.target == ast.INVALID_PACKAGE || int(import_item.target) >= len(module.packages) || !module.packages[import_item.target].available { type_use.diagnostic = source.addf( state.diagnostics, type_use.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, qualifier), ) continue } if !types.is_valid(types.find_named(&module.type_store, u32(import_item.target), u32(name))) { type_use.diagnostic = source.addf( state.diagnostics, type_use.span, "package '%s' has no member '%s'", symbol.resolve(state.symbols, qualifier), symbol.resolve(state.symbols, name), ) } } } type_resolution_diagnostic :: proc(module: ^ast.Module, value: types.Type, file: ast.File_Id, depth := 0) -> source.Diagnostic_Id { if depth > 64 { return source.INVALID_DIAGNOSTIC } for type_use in module.type_uses { if type_use.file == file && type_use.type == value && type_use.diagnostic != source.INVALID_DIAGNOSTIC { return type_use.diagnostic } } item, ok := types.node(&module.type_store, value) if !ok { return source.INVALID_DIAGNOSTIC } if diagnostic := type_resolution_diagnostic(module, item.child, file, depth+1); diagnostic != source.INVALID_DIAGNOSTIC { return diagnostic } if diagnostic := type_resolution_diagnostic(module, item.extra, file, depth+1); diagnostic != source.INVALID_DIAGNOSTIC { return diagnostic } if item.kind == .Function { for param in types.params_for(&module.type_store, value) { if diagnostic := type_resolution_diagnostic(module, param.type, file, depth+1); diagnostic != source.INVALID_DIAGNOSTIC { return diagnostic } } } return source.INVALID_DIAGNOSTIC } canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) { original_count := len(module.type_store.nodes) mapping := make([]types.Type, original_count, allocator) visiting := make([]bool, original_count, allocator) defer delete(mapping, allocator) defer delete(visiting, allocator) for &function in module.functions { for param in function.params { if diagnostic := type_resolution_diagnostic(module, param.type, function.file); diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC { function.diagnostic = diagnostic } } signature_results := [2]types.Type{function.result, function.error} for value in signature_results { if diagnostic := type_resolution_diagnostic(module, value, function.file); diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC { function.diagnostic = diagnostic } } for ¶m in function.params { param.type = canonical_type(module, param.type, mapping, visiting) } function.result = canonical_type(module, function.result, mapping, visiting) function.error = canonical_type(module, function.error, mapping, visiting) } for &global in module.globals { if diagnostic := type_resolution_diagnostic(module, global.type, global.file); diagnostic != source.INVALID_DIAGNOSTIC && global.diagnostic == source.INVALID_DIAGNOSTIC { global.diagnostic = diagnostic } global.type = canonical_type(module, global.type, mapping, visiting) } for &statement in module.statements { if diagnostic := type_resolution_diagnostic(module, statement.type, ast.File_Id(statement.span.file)); diagnostic != source.INVALID_DIAGNOSTIC && statement.diagnostic == source.INVALID_DIAGNOSTIC { statement.diagnostic = diagnostic } statement.type = canonical_type(module, statement.type, mapping, visiting) } for &field in module.type_fields { field.type = canonical_type(module, field.type, mapping, visiting) } for index := 0; index < original_count; index += 1 { _ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting) } for &variant in module.type_store.variants { variant.payload = canonical_type(module, variant.payload, mapping, visiting) } } load :: proc( root_path: string, sources: ^source.Store, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, token_allocator := context.allocator, allocator := context.allocator, c_options := cimport.Options{}, selected := target.DEFAULT, project_root_path := "", mode := ast.Compile_Mode.Executable, ) -> (ast.Module, bool) { module := ast.init_module(allocator) project_root_source := project_root_path if len(project_root_path) > 0 else root_path project_root, project_root_ok := filepath.abs(project_root_source, allocator) if !project_root_ok { project_root = strings.clone(project_root_source, allocator) } state := State{ module=&module, sources=sources, diagnostics=diagnostics, symbols=symbols, token_allocator=token_allocator, allocator=allocator, c_options=c_options, selected=selected, project_root=project_root, mode=mode, } state.record_identities.allocator = allocator state.record_types.allocator = allocator defer { delete(state.project_root, allocator) for identity in state.record_identities { delete(identity, allocator) } delete(state.record_identities) delete(state.record_types) } root := load_package(&state, root_path, source.Span{}, true) if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE { state.root_failed = true } if mode == .Test { testing_path, testing_error := filepath.join({project_root, "std", "testing"}, allocator) if testing_error == nil { _ = load_package(&state, testing_path, source.Span{}) delete(testing_path, allocator) } } 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 }