package main import "./compiler" import "./compiler/cimport" import "./compiler/linker" import "./compiler/target" import "./compiler/translatec" import "core:fmt" import "core:os" import "core:os/os2" import "core:path/filepath" import "core:strings" Cli_Options :: struct { input_path: string, output_path: string, link_arguments: []linker.Argument, c_options: cimport.Options, target: target.Target, } parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) { if len(args) < 4 { return {}, false } options := Cli_Options{input_path=args[1], target=target.DEFAULT} link_arguments: [dynamic]linker.Argument link_arguments.allocator = allocator include_paths: [dynamic]string include_paths.allocator = allocator defines: [dynamic]string defines.allocator = allocator success := false defer { if !success { delete(link_arguments) delete(include_paths) delete(defines) } } output_set := false cursor := 2 for cursor < len(args) { option := args[cursor] cursor += 1 if cursor >= len(args) { return {}, false } value := args[cursor] cursor += 1 switch option { case "-o": if output_set { return {}, false } output_set = true options.output_path = value case "--c-link": append(&link_arguments, linker.Argument{kind=.Input, value=value}) case "--c-library-path": append(&link_arguments, linker.Argument{kind=.Library_Path, value=value}) case "--c-library": append(&link_arguments, linker.Argument{kind=.Library, value=value}) case "--c-include-path": append(&include_paths, value) case "--c-define": append(&defines, value) case "--target": selected, ok := target.parse(value) if !ok { return {}, false } options.target = selected case: return {}, false } } if !output_set || len(options.output_path) == 0 { return {}, false } options.link_arguments = link_arguments[:] options.c_options.include_paths = include_paths[:] options.c_options.defines = defines[:] success = true return options, true } print_usage :: proc() { fmt.eprintln( "usage: brolang -o [--target aarch64-macos] [--c-link | --c-library-path | --c-library | --c-include-path | --c-define ]...", ) fmt.eprintln( " brolang translate-c|--translate-c [--target aarch64-macos] [--c-include-path | --c-define ]...", ) } is_translate_c_command :: proc(arg: string) -> bool { return arg == "translate-c" || arg == "--translate-c" } zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) { rest := text prefix := ".lib_dir = \"" for line in strings.split_lines_iterator(&rest) { trimmed := strings.trim_space(line) if !strings.has_prefix(trimmed, prefix) { continue } value := trimmed[len(prefix):] end := strings.index_byte(value, '"') if end < 0 { return "", false } return strings.clone(value[:end], allocator), true } return "", false } zig_lib_dir :: proc(allocator := context.allocator) -> (string, bool) { state, stdout, stderr, err := os2.process_exec( os2.Process_Desc{command=[]string{"/usr/bin/env", "zig", "env"}}, context.allocator, ) defer delete(stdout) defer delete(stderr) if err != nil || state.exit_code != 0 { return "", false } return zig_lib_dir_from_env_output(string(stdout), allocator) } append_owned_include_path :: proc(paths, owned: ^[dynamic]string, parts: []string) { path, err := filepath.join(parts) if err != nil { return } append(paths, path) append(owned, path) } append_zig_libc_include_paths :: proc(paths, owned: ^[dynamic]string, selected: target.Target) -> bool { lib_dir, ok := zig_lib_dir() if !ok { return false } defer delete(lib_dir) append_owned_include_path(paths, owned, {lib_dir, "include"}) switch selected.kind { case .Aarch64_Macos: append_owned_include_path(paths, owned, {lib_dir, "libc", "include", "any-darwin-any"}) } return true } resolve_translate_c_header :: proc(header: string, include_paths: []string, allocator := context.allocator) -> (string, bool) { if os.exists(header) || filepath.is_abs(header) { return strings.clone(header, allocator), os.exists(header) } for path in include_paths { candidate, err := filepath.join({path, header}, allocator) if err != nil { continue } if os.exists(candidate) { return candidate, true } delete(candidate, allocator) } return strings.clone(header, allocator), false } // run_translate_c emits native brolang bindings for a C header to stdout, the // offline counterpart of `native :: import "x.h"`. run_translate_c :: proc(args: []string) -> int { if len(args) < 3 { print_usage() return 2 } header := args[2] selected := target.DEFAULT include_paths: [dynamic]string owned_include_paths: [dynamic]string defines: [dynamic]string defer delete(include_paths) defer { for path in owned_include_paths { delete(path) } delete(owned_include_paths) } defer delete(defines) cursor := 3 for cursor < len(args) { option := args[cursor] cursor += 1 if cursor >= len(args) { fmt.eprintfln("missing value for %s", option) return 2 } value := args[cursor] cursor += 1 switch option { case "--c-include-path": append(&include_paths, value) case "--c-define": append(&defines, value) case "--target": parsed, ok := target.parse(value) if !ok { fmt.eprintfln("unknown target '%s'", value) return 2 } selected = parsed case: fmt.eprintfln("unknown option '%s'", option) return 2 } } _ = append_zig_libc_include_paths(&include_paths, &owned_include_paths, selected) import_header, _ := resolve_translate_c_header(header, include_paths[:]) defer delete(import_header) c_options := cimport.Options{include_paths=include_paths[:], defines=defines[:]} result := cimport.import_header(c_options, import_header, selected) defer cimport.destroy_result(&result) if !result.available { message := result.error_message if len(result.error_message) > 0 else "failed to import header" fmt.eprintln(message) return 1 } fmt.print(translatec.emit(&result, header)) return 0 } main :: proc() { if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) { os2.exit(run_translate_c(os2.args)) } options, valid := parse_cli_args(os2.args) if !valid { print_usage() os2.exit(2) } defer delete(options.link_arguments) defer delete(options.c_options.include_paths) defer delete(options.c_options.defines) status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options) if status != 0 { os2.exit(status) } }