193 lines
7.4 KiB
Markdown
193 lines
7.4 KiB
Markdown
# brolang
|
|
|
|
Prototype error-tolerant Brolang compiler written in Odin.
|
|
|
|
```sh
|
|
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:
|
|
|
|
```bro
|
|
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:
|
|
|
|
```sh
|
|
./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:
|
|
|
|
```bro
|
|
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:
|
|
|
|
```bro
|
|
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.
|
|
|
|
```bro
|
|
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:
|
|
|
|
```bro
|
|
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:
|
|
|
|
```bro
|
|
call func(callback *func(value i32) i32, value i32) i32 {
|
|
return callback(value)
|
|
}
|
|
```
|
|
|
|
Bodyless manual and imported C functions may be variadic:
|
|
|
|
```bro
|
|
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:
|
|
|
|
```bro
|
|
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/`:
|
|
|
|
```text
|
|
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:
|
|
|
|
```bro
|
|
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-constrained `int`
|
|
- Target-dependent atomic `c_*` primitive types, `c_func`, and defined or opaque `c_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](LANGUAGE.md) for the concise implemented and planned language
|
|
feature ledger, and [TODO.md](TODO.md) for the implementation roadmap.
|
|
|
|
Compiler exit statuses:
|
|
|
|
- `0`: executable produced without source diagnostics
|
|
- `1`: executable produced with source diagnostics and embedded traps
|
|
- `2`: executable could not be produced
|