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
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 build [root] (root defaults to the current
directory) reads a config constant from root/build.bro and compiles the
program package it names:
b :: import "@std/build"
config :: b.BuildConfig{
name = "manual",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &["examples/build/manual/native.c"],
}
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. 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, invalid code lowers to runtime diagnostic traps and the compiler
returns 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 file in the input directory belongs to the root
package. Imports are relative directory paths and are local to the file that
declares them:
import "../math"
other_math :: import "../math"
value :: math.sum(other_math.value, 1)
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, and defined or opaquec_struct - 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 and type comptime parameters (
func($N usize) [N]u8,func($T type, value T) T) specialized by comptime argument - Forced typed comptime expressions (
$sum(1, 2),$Point { x = 1, y = 2 }) and comptime value blocks (${ yield 4 }) - Comptime execution for bodyful Brolang functions with mutable locals, loops,
defer,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) - 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
- 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 and embedded traps2: executable could not be produced