brolang
Prototype error-tolerant Brolang compiler written in Odin.
mkdir -p build
odin build . -out:build/brolang
./build/brolang examples/programs/prototype -o build/prototype
./build/prototype
Programs may receive the system I/O capability explicitly. Readers and writers
pair that implementation with a stream; main func() ... remains valid.
io :: import "@std/io"
process :: import "@std/process"
main func(init process.Init) void {
io.print(io.Writer {
impl = init.io,
stream = .stdout,
}, "hello {s} {d}\n", {"bro", 37}) catch |_| {
return
}
}
Bodyless c_func declarations bind exact external symbols and require concrete
types. C primitives use atomic target-dependent names and remain semantically
distinct from exact-width Brolang primitives:
strlen c_func(value *c_char) c_ulong
Additional native inputs and libraries are passed to the final zig cc
invocation in command-line order:
./build/brolang examples/interop/manual -o build/manual \
--c-link examples/interop/manual/native.c
--c-link accepts C sources, object files, archives, and direct library paths.
--c-library-path <dir> becomes -L<dir>, and --c-library <name> becomes
-l<name>. --c-include-path <dir> and --c-define <name[=value]> configure
C preprocessing.
Instead of passing these on the command line, a project can describe its build
in Brolang itself. brolang new <project> creates a project with local std
and ffi copies; brolang init does the same for the current directory without
overwriting existing files. brolang build [root] reads a config constant
from root/build.bro and compiles the program package it names. Without
root, it searches the current directory and parents for the nearest
build.bro. Build outputs are written to root/build/<name>.
b :: import "@std/build"
config :: b.BuildConfig{
name = "manual",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &["examples/build/manual/native.c"],
}
name is a plain executable name, and source is the program package relative
to build.bro. The list fields map to the matching C options (libraries →
-l, lib_paths → -L, includes → -I, defines → C defines, links →
linker inputs) and, like those flags, their paths are relative to the invocation
directory. Lists take the address of an array literal; empty lists are written
&[]. See examples/build/ for runnable projects.
Relative .h imports create synthetic package namespaces backed by libclang:
native :: import "../include/native.h"
main func() void {
_ = native.imported_add(20, 22)
}
Header imports expose supported external functions, typedefs, C scalars, fixed
arrays, complete plain structs and unions, function pointer typedefs, and
pointers to opaque records. C void* imports as nullable anyopaque pointers.
Plain records can be constructed with keyed
literals, accessed by field, and passed or returned by value through fixed C
signatures on aarch64-macos. Unsupported or incomplete records remain
pointer-only. Header imports never add linker inputs; implementations must still
be supplied explicitly with the C-prefixed linking options. Set
BROLANG_LIBCLANG_PATH when libclang is not installed in a standard location.
For offline bindings, brolang --translate-c stdio.h resolves standard C
headers through the Zig libc headers used by the backend.
native :: import "../include/native.h"
pair native.Pair :: native.echo_pair(native.Pair { left = 20, right = 22 })
choice native.Choice :: native.Choice { integer = 42 }
Concrete c_func declarations and definitions can be passed to C function
pointer parameters. Imported C callback typedefs are nullable, so calling one
from Brolang requires an explicit unwrap:
native :: import "../include/native.h"
double c_func(value c_int) c_int {
return value + value
}
call_mapper func(mapper native.Imported_Mapper) c_int {
return mapper?(21)
}
Native Brolang function pointer values use *func(...) R, with fallible
channels written on the result:
call func(callback *func(value i32) i32, value i32) i32 {
return callback(value)
}
Bodyless manual and imported C functions may be variadic:
log_values c_func(tag c_int, ...) c_int
Zero-terminated byte strings can be passed directly to immutable C character
pointers without making u8 and c_char generally interchangeable:
printf c_func(format *c_char, ...) c_int
main func() void {
_ = printf("answer: %d\n", 42)
}
Arguments after ... accept concrete scalars, pointers, and nullable pointers.
Narrow integers are promoted to the target C int or unsigned int, and
f32/c_float are promoted to c_double. Arrays, slices, structs, and other
compound values must be converted to an explicit C-compatible representation
before the call.
Compilation phases are isolated under compiler/:
package loader -> per-file lexer/parser/AST -> checker/HIR -> lower/IR -> opt -> LLVM -> zig cc
Source diagnostics do not block executable generation. When recovery is
possible, errors lower to runtime diagnostic traps; warnings do not trap. Any
source diagnostic makes the compiler return status 1. Infrastructure or
backend failures return status 2.
Top-level function bodies are semantically checked lazily when a concrete
specialization is demanded.
Every immediate .bro or .hon file in the input directory belongs to the root
package. Imports are relative directory paths and are local to the file that
declares them. Imports beginning with @ resolve from the project root:
import "../math"
other_math :: import "../math"
mem :: import "@std/mem"
value :: math.sum(other_math.value, 1)
Top-level declarations are public by default. Prefix a declaration with hide
to keep it local to its source file; leading underscores have no visibility
meaning. Imports are always file-local and cannot be hidden or re-exported:
hide helper func() i32 { return 42 }
hide State :: struct { value i32 }
Current prototype features:
- Newline-terminated, multiline statements;
}may terminate a block's final statement #comments- Immutable
::bindings, typed mutable=locals/globals, and_sinks - Exact-width signed/unsigned integers,
f32,f64,isize,usize, and loose integer-constrainedint - Target-dependent atomic
c_*primitive types,c_func, completec_struct,opaque,anyopaque, and V1ptrcast!(T, ptr) - Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
- String literals as immutable pointers to static zero-terminated byte arrays
- Pointer-preserving
.ptr/.len, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Contextual integer constants and compile-time folding of addition and unary negation trees
- Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
- Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions
- Integer, type, and immutable byte-string comptime parameters may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit or uniquely inferred arguments
- Forced typed comptime expressions (
$sum(1, 2),$Point { x = 1, y = 2 }) and comptime value blocks (${ yield 4 }) - Zig-style comptime type factories returning anonymous native structs (
Box func($T type) type, used asBox(i32)) - Comptime execution for bodyful Brolang functions with mutable locals, loops,
defer/errdefer,match,try/catch, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values - Native function pointer values and types (
*func(...) R,*func(...) R ! E,?*func(...) R) - Typed allocation/reallocation through
std/memand generic dynamic arrays throughstd/arraylist - Bodyless concrete C function declarations with exact external symbol names
- Bodyless manual and imported C variadic declarations with default argument promotions
- Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation
- Float-only
/plus explicitdivtrunc!,divfloor!,divexact!,divceil!,rem!, andmod!scalar intrinsics - Static, eager runtime, mutable runtime, and deferred problematic globals
- Runtime diagnostics followed by
llvm.trap
See LANGUAGE.md for the concise implemented and planned language feature ledger, and TODO.md for the implementation roadmap.
Compiler exit statuses:
0: executable produced without source diagnostics1: executable produced with source diagnostics; errors may embed traps, warnings do not2: executable could not be produced