Compare commits

..

10 Commits

Author SHA1 Message Date
hl-valdemar 7ce3917c15 toolchain version command 2026-07-06 19:36:55 +02:00
hl-valdemar e7f69ffb5a add new and init to toolchain 2026-07-06 19:36:55 +02:00
hl-valdemar cdde49e68b build system (first pass) 2026-07-05 00:09:39 +02:00
hl-valdemar 4ebe9c90e9 comptime storage and function values 2026-07-03 23:35:00 +02:00
hl-valdemar ee41a54e41 runtime mutable global state 2026-07-03 22:40:21 +02:00
hl-valdemar adf142736c more comptime eval 2026-07-03 18:18:45 +02:00
hl-valdemar e00a4e929a comptime eval 2026-07-03 18:18:45 +02:00
hl-valdemar b94687c30a comptime type params 2026-07-02 22:24:57 +02:00
hl-valdemar a98b26446d array size inference from value 2026-07-02 20:20:31 +02:00
hl-valdemar 7cda126924 comptime value-params 2026-07-02 20:10:02 +02:00
37 changed files with 7797 additions and 361 deletions
+10 -5
View File
@@ -8,8 +8,8 @@ roadmap and milestone history.
### source, declarations, and packages ### source, declarations, and packages
- newline-terminated statements and `#` comments - newline-terminated statements and `#` comments
- immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
- immutable package globals, function-local mutable locals, and mutable local declarations initialized with `undefined` - immutable package globals, mutable runtime globals, function-local mutable locals, and mutable local declarations initialized with `undefined`
- package-level functions, globals, native type declarations, and `Name :: alias T` - package-level functions, globals, native type declarations, and `Name :: alias T`
- directory packages with merged declarations - directory packages with merged declarations
- file-local relative imports, import aliases, and qualified member access - file-local relative imports, import aliases, and qualified member access
@@ -22,7 +22,7 @@ roadmap and milestone history.
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars - target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions - contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)` - strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
- arrays `[N]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T` - arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?` - pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening - pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings - UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
@@ -52,8 +52,13 @@ roadmap and milestone history.
### functions, C interop, and linking ### functions, C interop, and linking
- demand-monomorphized Brolang and C-ABI functions - demand-monomorphized Brolang and C-ABI functions
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ 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
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
- native function pointer values and types with `*func(...) R`, fallible `*func(...) R ! E`, optional `?*func(...) R`, and non-variadic native indirect calls
- Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns - Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns
- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, and pointers to opaque records - imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, and pointers to opaque records
- imported external C object variables, including mutable variables and immutable object globals - imported external C object variables, including mutable variables and immutable object globals
@@ -72,14 +77,14 @@ roadmap and milestone history.
- error-tolerant compilation with diagnostics and runtime traps where recovery is possible - error-tolerant compilation with diagnostics and runtime traps where recovery is possible
- lazy semantic checking of demanded function specializations - lazy semantic checking of demanded function specializations
- static, eager runtime, and deferred problematic globals with cycle diagnostics - static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
- demand-driven LLVM declarations for referenced foreign functions - demand-driven LLVM declarations for referenced foreign functions
- replaceable dynamically loaded libclang C-import backend - replaceable dynamically loaded libclang C-import backend
- C-header import caching by canonical path, target, include paths, and defines - C-header import caching by canonical path, target, include paths, and defines
## PLANNED / DEFERRED ## PLANNED / DEFERRED
- comptime polymorphism - aggregate comptime parameters and stable aggregate specialization keys
- tuples and native Brolang variadic functions - tuples and native Brolang variadic functions
- exporting Brolang functions to C and broader target-specific C ABI lowering - exporting Brolang functions to C and broader target-specific C ABI lowering
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments - non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
+46 -3
View File
@@ -30,6 +30,35 @@ invocation in command-line order:
`-l<name>`. `--c-include-path <dir>` and `--c-define <name[=value]>` configure `-l<name>`. `--c-include-path <dir>` and `--c-define <name[=value]>` configure
C preprocessing. 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>`.
```bro
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: Relative `.h` imports create synthetic package namespaces backed by libclang:
```bro ```bro
@@ -74,6 +103,15 @@ call_mapper func(mapper native.Imported_Mapper) c_int {
} }
``` ```
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: Bodyless manual and imported C functions may be variadic:
```bro ```bro
@@ -111,11 +149,12 @@ specialization is demanded.
Every immediate `.bro` file in the input directory belongs to the root 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 package. Imports are relative directory paths and are local to the file that
declares them: declares them. Imports beginning with `@` resolve from the project root:
```bro ```bro
import "../math" import "../math"
other_math :: import "../math" other_math :: import "../math"
heap :: import "@std/mem/heap"
value :: math.sum(other_math.value, 1) value :: math.sum(other_math.value, 1)
``` ```
@@ -124,7 +163,7 @@ Current prototype features:
- Newline-terminated, multiline statements; `}` may terminate a block's final statement - Newline-terminated, multiline statements; `}` may terminate a block's final statement
- `#` comments - `#` comments
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` - 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` - 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 - Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
@@ -136,11 +175,15 @@ Current prototype features:
- 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 - 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 - Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions - 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 concrete C function declarations with exact external symbol names
- Bodyless manual and imported C variadic declarations with default argument promotions - Bodyless manual and imported C variadic declarations with default argument promotions
- Ordered linking of additional C sources, objects, archives, and libraries - Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation - Checked signed addition and unary negation
- Static, eager runtime, and deferred problematic globals - Static, eager runtime, mutable runtime, and deferred problematic globals
- Runtime diagnostics followed by `llvm.trap` - Runtime diagnostics followed by `llvm.trap`
See [LANGUAGE.md](LANGUAGE.md) for the concise implemented and planned language See [LANGUAGE.md](LANGUAGE.md) for the concise implemented and planned language
+108 -8
View File
@@ -91,7 +91,7 @@
- operators: `and`, `or`, `!` - operators: `and`, `or`, `!`
- lazy evaluation / short-circuit evaluation - lazy evaluation / short-circuit evaluation
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }` - if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
- conditions must be `bool`; block-scoped locals with shadowing across blocks - conditions must be `bool`; block-scoped locals do not escape their blocks
- lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes) - lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes)
- conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none` - conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if` - single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
@@ -403,8 +403,7 @@
statements first (a throwaway probe), so a first concrete `yield :blk` that references a statements first (a throwaway probe), so a first concrete `yield :blk` that references a
block local still resolves the result to `?T` block local still resolves the result to `?T`
- deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or - deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or
loop) whose concrete yield references a local declared *past* the first yield (annotate); loop) whose concrete yield references a local declared *past* the first yield (annotate)
same-label loop/block shadowing resolves innermost-wins
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below) 21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
- inspired by zig - inspired by zig
@@ -627,14 +626,103 @@
- typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred - typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
26. import from project "root" (implemented) 26. import from project "root" (implemented)
- imports beginning with `@` resolve from the compiler process cwd / project root - imports beginning with `@` resolve from the project root
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math - `brolang build [root]` uses `root` as the project root; without `root`, it searches cwd and parents for `build.bro`
- direct `brolang <package-dir> -o <out>` defaults the project root to the package dir; `--root <dir>` overrides it
27. comptime polymorphism (zig inspired) 27. comptime integer value parameters (implemented; v1)
- `$N` marks an integer comptime parameter in a normal `func` signature:
`make_array func($N usize) [N]u8`
- callers pass a compile-time integer expression; the value specializes the function
and is omitted from the runtime ABI
- inside the specialization, `N` is visible as an immutable compile-time integer in
array counts, types, and body expressions
- v1 intentionally supports integer values only; no comptime branch pruning or
user-function execution
28. comptime execution 27.5 comptime type parameters (implemented; v1)
- `$T type` marks an explicit comptime type parameter in a normal `func` signature:
`max func($T type, a, b T) T`
- callers pass the type explicitly as an ordinary comptime argument (`max(i32, a, b)`);
the type argument specializes the function and is omitted from the runtime ABI
- inside the specialization, `T` is visible in parameter, result, local, array, pointer,
slice, and fallible type syntax
- v1 intentionally keeps `type` contextual to comptime parameter declarations; no
inferred type parameters, first-class type values, or comptime execution
29. brolang build system (requires comptime execution) 27.6 comptime-evaluable constants/functions (implemented)
- `$expr` forces comptime evaluation of an expression:
`x :: $32`, `n :: $sum(1, 2)`, and `res :: ${ ... }`
- constant contexts such as array counts and comptime value arguments implicitly
require comptime evaluation; ordinary immutable bindings remain ordinary bindings
- ordinary `func` calls are comptime-evaluable when reached from a comptime context;
do not add a separate `$sum func(...)` declaration form
- v1 evaluator supported integer literals/arithmetic, boolean conditions, immutable
locals, `return`, `if`/`else`, comptime blocks, and direct calls to other evaluable
brolang functions
- broader typed execution is milestone 27.7
27.7 broader Zig-style comptime execution (implemented; v1)
- `compiler/checker/comptime.odin` owns checker-local evaluator state, typed
comptime values, execution, and HIR materialization; `checker.odin` keeps type
checking, inference, specialization, and build orchestration
- typed `$` values cover bools, integers, floats, strings, arrays, structs, tagged
unions, enums, optionals, and fallibles
- supports mutable comptime locals/assignment, `if`, `while`, `for`,
`break`/`continue`, `defer`, `match`, value blocks/`yield`, direct calls to
bodyful Brolang functions, and `try`/`catch`
- successful `$` results materialize back into ordinary HIR expressions so lowering
and LLVM stay unchanged
- evaluation uses a fixed `100_000` step quota
- immutable locals/globals with comptime-known initializers may feed comptime
evaluation; runtime-dependent values remain invalid in comptime contexts
- runtime-only behavior is rejected in comptime: external/bodyless `c_func`,
writable globals, and materializing comptime storage pointers/slices as runtime memory
- v1 keeps integer-only `$N` specialization keys; aggregate comptime parameters
and stable aggregate serialization are deferred
27.8 source-defined mutable runtime globals (implemented)
- allow mutable global declarations in Brolang source for process-global runtime
state, matching the writable-global support already needed for imported C globals
- require source type syntax and an initializer; constraints (`int`/`float`/`range`)
and inferred array counts may resolve through the existing inference fixpoint, but
the final type must be concrete runtime storage
- emit source-defined mutable globals as writable globals, not constants
- allow assignment, address-taking, field/index mutation, and pointer passing under
the same mutability rules as other writable locations
- keep mutable globals invalid in comptime evaluation; `$global_var` and writes from
comptime execution must remain runtime-dependent errors
- define initialization order and cycle behavior by reusing the existing global
initializer dependency/cycle system where possible
- reject user-visible name shadowing across imports, named types, globals, functions,
params, locals, comptime params, captures, and labels; `_` remains reusable
27.9 comptime storage and function values (implemented; practical v1)
- comptime locals, params, and immutable globals can own evaluator storage cells
addressable through places instead of compiler-owned memory
- comptime supports address/deref, mutable pointer and slice mutation, field/index
places, slicing, `.len`, `.ptr`, pointer captures, and pointer-param aliasing
- comptime storage pointers/slices cannot materialize as runtime memory; escaped
dead storage is rejected
- bare concrete non-comptime function names are values; native function pointer
types use `*func(...) R` and fallible `*func(...) R ! E`
- comptime-known native/bodyful `c_func` values can be called; bodyless/imported
callbacks remain runtime-only
- native function pointers are non-variadic v1; C variadic function pointers stay
under `*c_func(...) R`
28. brolang build system — v0 shipped
- `brolang build [root]` reads a declarative `config` constant from
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
program package it names. The config is read from the checked HIR — build.bro
is never lowered — so it is literal-only.
- `brolang new <project>` and `brolang init` create the default project layout
with local `std`, `ffi`, `vendor`, `source`, and `build.bro`
- enabled `&<array literal>` (Zig's `&.{...}`): the literal is promoted to an
anonymous global whose address decays to a slice, so list fields like
`libraries = &["raylib"]` work; empty lists are `&[]`
- deferred: build graph / steps / caching, multiple artifacts, computed paths
(needs string building), struct field defaults to drop `&[]` on empty lists
## A word on multi-unwrap ## A word on multi-unwrap
@@ -1221,6 +1309,18 @@ data :: read_file(path) catch |e| match e {
| `yield :label value` | Provide value from labeled block | | `yield :label value` | Provide value from labeled block |
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type | | `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
## A word on comptime
```
make_array func($N usize) [N]u8 { ... } # implemented: integer comptime value params
max func($T type, a, b T) T { ... } # implemented: comptime type params
x :: $32 # implemented: force comptime expression evaluation
n :: $sum(1, 2) # implemented: ordinary functions can run at comptime
res :: ${ yield 4 } # implemented: comptime value block
p :: $Point { x = 1, y = 2 } # implemented: typed aggregate comptime values
total :: $sum_loop(4) # implemented: mutable locals/loops/defer/match/try/catch
```
## A word on memory allocation ## A word on memory allocation
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY) (NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
+3
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array, Array,
None, None,
Undefined, Undefined,
Type,
Name, Name,
Enum_Literal, Enum_Literal,
Address, Address,
@@ -84,6 +85,7 @@ Expr_Kind :: enum u8 {
Struct_Literal, Struct_Literal,
Keyed, Keyed,
Cast, Cast,
Comptime,
Negate, Negate,
Not, Not,
Add, Add,
@@ -123,6 +125,7 @@ Param :: struct {
name: symbol.Id, name: symbol.Id,
span: source.Span, span: source.Span,
type: Type_Syntax, type: Type_Syntax,
comptime_value: bool,
} }
Stmt_Kind :: enum u8 { Stmt_Kind :: enum u8 {
+27
View File
@@ -13,6 +13,32 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
append(command, strings.clone(value, allocator)) append(command, strings.clone(value, allocator))
} }
macos_sdk_root :: proc(allocator := context.allocator) -> (string, bool) {
candidates := []string{
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
}
for path in candidates {
if os.exists(path) {
return strings.clone(path, allocator), true
}
}
return "", false
}
append_macos_sdk_paths :: proc(command: ^[dynamic]string, selected: target.Target, allocator: mem.Allocator) {
if selected.kind != .Aarch64_Macos {
return
}
sdk, ok := macos_sdk_root(allocator)
if !ok {
return
}
defer delete(sdk, allocator)
append(command, fmt.aprintf("-F%s/System/Library/Frameworks", sdk, allocator=allocator))
append(command, fmt.aprintf("-L%s/usr/lib", sdk, allocator=allocator))
}
build_command :: proc( build_command :: proc(
llvm_path, output_path: string, llvm_path, output_path: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
@@ -29,6 +55,7 @@ build_command :: proc(
append_owned(&command, target.name(selected), allocator) append_owned(&command, target.name(selected), allocator)
append_owned(&command, "-Wno-override-module", allocator) append_owned(&command, "-Wno-override-module", allocator)
append_owned(&command, "-Wno-unused-command-line-argument", allocator) append_owned(&command, "-Wno-unused-command-line-argument", allocator)
append_macos_sdk_paths(&command, selected, allocator)
append_owned(&command, llvm_path, allocator) append_owned(&command, llvm_path, allocator)
for path in c_options.include_paths { for path in c_options.include_paths {
append(&command, fmt.aprintf("-I%s", path, allocator=allocator)) append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
+347
View File
@@ -0,0 +1,347 @@
package compiler
import "./checker"
import "./cimport"
import "./hir"
import "./linker"
import "./loader"
import "./source"
import "./symbol"
import "./target"
import "./types"
import "core:fmt"
import vmem "core:mem/virtual"
import "core:os"
import "core:os/os2"
import "core:path/filepath"
import "core:strings"
// BuildConfig is the native, extracted form of std/build's BuildConfig: all
// strings are cloned into context.allocator so they outlive the build module's
// arena (freed at the end of run_build). Free with destroy_build_config.
BuildConfig :: struct {
output_name: string,
source_dir: string,
link_arguments: []linker.Argument,
c_options: cimport.Options,
}
destroy_build_config :: proc(cfg: ^BuildConfig) {
delete(cfg.output_name)
delete(cfg.source_dir)
for arg in cfg.link_arguments {
delete(arg.value)
}
delete(cfg.link_arguments)
for path in cfg.c_options.include_paths {
delete(path)
}
delete(cfg.c_options.include_paths)
for define in cfg.c_options.defines {
delete(define)
}
delete(cfg.c_options.defines)
}
// run_build implements `brolang build [root]`: it loads and type-checks
// `root/build.bro`, reads its `config` constant, and compiles the program
// package the config names. build.bro is only checked (never lowered/emitted),
// so the config is read straight from the HIR.
run_build :: proc(root: string) -> int {
project_root := root
owns_project_root := false
if len(project_root) == 0 {
found_root, found := find_build_root()
if !found {
fmt.eprintln("brolang build: could not find build.bro in the current directory or any parent")
return 2
}
project_root = found_root
owns_project_root = true
}
defer if owns_project_root {
delete(project_root)
}
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
arena: vmem.Arena
if err := vmem.arena_init_growing(&arena); err != nil {
fmt.eprintln("failed to initialize build arena:", err)
return 2
}
defer vmem.arena_destroy(&arena)
a := vmem.arena_allocator(&arena)
ast_module, loaded := loader.load(project_root, &sources, &diagnostics, &symbols, a, a, cimport.Options{}, target.DEFAULT, project_root)
if !loaded {
source.print_all(&diagnostics)
fmt.eprintln("failed to load build root:", project_root)
return 2
}
// check needs no `main`: it synthesizes a trap main and emits one benign
// "missing main" diagnostic, which is expected for build.bro. Suppress that
// one but surface any real errors in build.bro (and fail on them).
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
if build_bro_has_errors(&diagnostics) {
source.print_all(&diagnostics)
return 2
}
cfg, ok := extract_build_config(&hir_module, &symbols)
if !ok {
return 2
}
defer destroy_build_config(&cfg)
program := filepath.join({project_root, cfg.source_dir})
defer delete(program)
output, output_ok := build_output_path(project_root, cfg.output_name)
if !output_ok {
return 2
}
defer delete(output)
return compile_package(program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root)
}
valid_output_name :: proc(name: string) -> bool {
return len(name) > 0 && name != "." && name != ".." &&
!strings.contains(name, "/") && !strings.contains(name, "\\")
}
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
if !valid_output_name(name) {
fmt.eprintfln("build.bro: config 'name' must be a plain executable name, got '%s'", name)
return "", false
}
build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
if dir_error != nil {
return "", false
}
if os.exists(build_dir) {
if !os.is_dir(build_dir) {
fmt.eprintfln("build path exists and is not a directory: %s", build_dir)
delete(build_dir, allocator)
return "", false
}
} else if err := os2.make_directory_all(build_dir); err != nil {
fmt.eprintfln("failed to create build directory '%s': %v", build_dir, err)
delete(build_dir, allocator)
return "", false
}
output, output_error := filepath.join({build_dir, name}, allocator)
delete(build_dir, allocator)
if output_error != nil {
return "", false
}
return output, true
}
find_build_root :: proc(allocator := context.allocator) -> (string, bool) {
current := os.get_current_directory(allocator)
if len(current) == 0 {
return "", false
}
defer delete(current, allocator)
return find_build_root_from(current, allocator)
}
find_build_root_from :: proc(start: string, allocator := context.allocator) -> (string, bool) {
current, current_ok := filepath.abs(start, allocator)
if !current_ok {
current = strings.clone(start, allocator)
}
for {
build_path, build_error := filepath.join({current, "build.bro"}, allocator)
if build_error != nil {
delete(current, allocator)
return "", false
}
found := os.exists(build_path)
delete(build_path, allocator)
if found {
return current, true
}
if current == "/" {
delete(current, allocator)
return "", false
}
parent := filepath.dir(current, allocator)
if parent == current {
delete(parent, allocator)
delete(current, allocator)
return "", false
}
delete(current, allocator)
current = parent
}
}
// build_bro_has_errors reports whether checking build.bro produced any diagnostic
// other than the benign "missing or unusable main function" (build.bro has no
// main by design; that one is emitted with an empty span).
build_bro_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
for item in diagnostics.items {
if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
continue
}
return true
}
return false
}
// extract_build_config finds the top-level `config` constant and reads its
// BuildConfig{...} fields out of the HIR. All returned strings are cloned into
// context.allocator.
extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildConfig, bool) {
config_id := symbol.intern(symbols, "config")
config_expr := hir.INVALID_EXPR
found := false
for g in m.globals {
if g.name == config_id {
config_expr = g.expr
found = true
break
}
}
if !found {
fmt.eprintln("build.bro: missing top-level 'config' constant")
return {}, false
}
root := unwrap_coercions(m, config_expr)
if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct {
fmt.eprintln("build.bro: 'config' must be a BuildConfig{...} literal")
return {}, false
}
args := m.exprs[root].args
fields := types.fields_for(&m.types, m.exprs[root].type)
cfg: BuildConfig
links: [dynamic]linker.Argument
includes: [dynamic]string
defines: [dynamic]string
for field, i in fields {
if i >= len(args) {
break
}
switch symbol.resolve(symbols, symbol.Id(field.name)) {
case "name":
if s, sok := read_string(m, args[i]); sok {
cfg.output_name = strings.clone(s)
}
case "source":
if s, sok := read_string(m, args[i]); sok {
cfg.source_dir = strings.clone(s)
}
case "libraries":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Library, value = strings.clone(v)})
}
delete(list)
case "lib_paths":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Library_Path, value = strings.clone(v)})
}
delete(list)
case "links":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Input, value = strings.clone(v)})
}
delete(list)
case "includes":
list := read_string_list(m, args[i])
for v in list {
append(&includes, strings.clone(v))
}
delete(list)
case "defines":
list := read_string_list(m, args[i])
for v in list {
append(&defines, strings.clone(v))
}
delete(list)
}
}
cfg.link_arguments = links[:]
cfg.c_options.include_paths = includes[:]
cfg.c_options.defines = defines[:]
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
fmt.eprintln("build.bro: config requires non-empty 'name' and 'source'")
destroy_build_config(&cfg)
return {}, false
}
return cfg, true
}
// unwrap_coercions strips implicit coercion wrappers (each stores its inner
// expr in `.left`) to reach the underlying value expression.
unwrap_coercions :: proc(m: ^hir.Module, id: hir.Expr_Id) -> hir.Expr_Id {
cur := id
for cur != hir.INVALID_EXPR && int(cur) < len(m.exprs) {
#partial switch m.exprs[cur].kind {
case .Retype, .Weaken_Slice, .Weaken_Pointer, .Decay_Array_Pointer, .Slice_Ptr,
.Widen, .Sum_Widen, .Optional_Some, .C_Coerce, .Scalar_Cast:
cur = m.exprs[cur].left
case:
return cur
}
}
return cur
}
read_string :: proc(m: ^hir.Module, id: hir.Expr_Id) -> (string, bool) {
e := unwrap_coercions(m, id)
if e == hir.INVALID_EXPR || m.exprs[e].kind != .String {
return "", false
}
sid := m.exprs[e].integer
if sid < 0 || int(sid) >= len(m.strings) {
return "", false
}
return m.strings[int(sid)], true
}
// read_string_list reads a `&[...]` list field: the value is an address of an
// anonymous global array (see checker `&<array literal>` promotion), whose
// elements are strings. Returned strings alias m.strings; callers clone them.
// The returned slice is owned by the caller (delete it).
read_string_list :: proc(m: ^hir.Module, id: hir.Expr_Id) -> []string {
addr := unwrap_coercions(m, id)
if addr == hir.INVALID_EXPR || m.exprs[addr].kind != .Address {
return nil
}
g := unwrap_coercions(m, m.exprs[addr].left)
if g == hir.INVALID_EXPR || m.exprs[g].kind != .Global {
return nil
}
gid := hir.as_global(m.exprs[g].target)
if gid == hir.INVALID_GLOBAL || int(gid) >= len(m.globals) {
return nil
}
arr := m.globals[gid].expr
if arr == hir.INVALID_EXPR || m.exprs[arr].kind != .Array {
return nil
}
elems := m.exprs[arr].args
out := make([]string, len(elems))
for a, i in elems {
s, ok := read_string(m, a)
if !ok {
delete(out)
return nil
}
out[i] = s
}
return out
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -33,6 +33,7 @@ compile_package :: proc(
link_arguments: []linker.Argument = nil, link_arguments: []linker.Argument = nil,
selected := target.DEFAULT, selected := target.DEFAULT,
c_options := cimport.Options{}, c_options := cimport.Options{},
project_root := "",
) -> int { ) -> int {
sources := source.init_store() sources := source.init_store()
defer source.destroy_store(&sources) defer source.destroy_store(&sources)
@@ -75,6 +76,7 @@ compile_package :: proc(
vmem.arena_allocator(&parser_arena), vmem.arena_allocator(&parser_arena),
c_options, c_options,
selected, selected,
project_root if len(project_root) > 0 else input_path,
) )
if !loaded { if !loaded {
source.print_all(&diagnostics) source.print_all(&diagnostics)
+3
View File
@@ -201,6 +201,9 @@ lex :: proc(
case '@': case '@':
append_token(&stream, source_file, .At, cursor, cursor+1) append_token(&stream, source_file, .At, cursor, cursor+1)
cursor += 1 cursor += 1
case '$':
append_token(&stream, source_file, .Dollar, cursor, cursor+1)
cursor += 1
case '*': case '*':
start := cursor start := cursor
cursor += 1 cursor += 1
+9 -2
View File
@@ -1234,6 +1234,11 @@ declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: sy
return true return true
} }
} }
type_id := types.find_named(&module.type_store, u32(pkg), u32(name))
type_item, type_ok := types.node(&module.type_store, type_id)
if type_ok && type_item.declared {
return true
}
return false return false
} }
@@ -1411,11 +1416,13 @@ load :: proc(
allocator := context.allocator, allocator := context.allocator,
c_options := cimport.Options{}, c_options := cimport.Options{},
selected := target.DEFAULT, selected := target.DEFAULT,
project_root_path := "",
) -> (ast.Module, bool) { ) -> (ast.Module, bool) {
module := ast.init_module(allocator) module := ast.init_module(allocator)
project_root, project_root_ok := filepath.abs(".", 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 { if !project_root_ok {
project_root = strings.clone(".", allocator) project_root = strings.clone(project_root_source, allocator)
} }
state := State{ state := State{
module=&module, module=&module,
+54 -7
View File
@@ -113,7 +113,7 @@ is_type_token :: proc(kind: token.Kind) -> bool {
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint, .Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, .Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
.Keyword_Void, .Keyword_Bool, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket: .Keyword_Void, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket:
return true return true
} }
return false return false
@@ -329,10 +329,11 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
case .Keyword_Bool: case .Keyword_Bool:
advance(parser) advance(parser)
return types.BOOL return types.BOOL
case .Keyword_C_Func: case .Keyword_Func, .Keyword_C_Func:
c_abi := tok.kind == .Keyword_C_Func
advance(parser) advance(parser)
if _, ok := allow(parser, .Left_Paren); !ok { if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(' after c_func type") source.add(parser.diagnostics, current(parser).span, "expected '(' after function type")
return types.INVALID return types.INVALID
} }
params, variadic := parse_params(parser) params, variadic := parse_params(parser)
@@ -340,11 +341,19 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters") source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters")
} }
result := parse_type(parser) result := parse_type(parser)
if _, ok := allow(parser, .Bang); ok {
error_type := parse_error_type(parser)
if c_abi {
source.add(parser.diagnostics, current(parser).span, "c_func pointer types cannot be fallible")
} else {
result = types.fallible(&parser.module.type_store, result, error_type)
}
}
param_types := make([]types.Type, len(params), parser.module.allocator) param_types := make([]types.Type, len(params), parser.module.allocator)
for param, index in params { for param, index in params {
param_types[index] = param.type param_types[index] = param.type
} }
function_type := types.function(&parser.module.type_store, param_types, result, true, variadic) function_type := types.function(&parser.module.type_store, param_types, result, c_abi, variadic)
delete(param_types, parser.module.allocator) delete(param_types, parser.module.allocator)
delete(params, parser.module.allocator) delete(params, parser.module.allocator)
return function_type return function_type
@@ -591,6 +600,17 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
tok := current(parser) tok := current(parser)
#partial switch tok.kind { #partial switch tok.kind {
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Bool:
start := tok
target := parse_type_atom(parser)
return add_expr(parser, ast.Expr{
kind=.Type,
span=start.span,
type=target,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, case .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64, .Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64, .Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
@@ -601,7 +621,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
start := tok start := tok
target := parse_type_atom(parser) target := parse_type_atom(parser)
if _, ok := allow(parser, .Left_Paren); !ok { if _, ok := allow(parser, .Left_Paren); !ok {
return invalid_expr(parser, current(parser).span, "expected '(' after scalar cast type") return add_expr(parser, ast.Expr{
kind=.Type,
span=start.span,
type=target,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} }
parser.delimiter_depth += 1 parser.delimiter_depth += 1
skip_newlines(parser) skip_newlines(parser)
@@ -896,7 +923,7 @@ is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) { prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind { #partial switch kind {
case .Minus, .Ampersand, .Bang, .Keyword_Try: case .Minus, .Ampersand, .Bang, .Dollar, .Keyword_Try:
return 20, true return 20, true
} }
return 0, false return 0, false
@@ -913,6 +940,18 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
left := ast.INVALID_EXPR left := ast.INVALID_EXPR
if right_power, ok := prefix_binding_power(current(parser).kind); ok { if right_power, ok := prefix_binding_power(current(parser).kind); ok {
operator := advance(parser) operator := advance(parser)
if operator.kind == .Dollar && current(parser).kind == .Left_Brace {
body := parse_block(parser)
end := previous(parser)
left = add_expr(parser, ast.Expr{
kind = .Comptime,
span = span_from(operator.span, end.span),
body = body,
left = ast.INVALID_EXPR,
right = ast.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
})
} else {
if parser.delimiter_depth > 0 { if parser.delimiter_depth > 0 {
skip_newlines(parser) skip_newlines(parser)
} }
@@ -922,6 +961,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
#partial switch operator.kind { #partial switch operator.kind {
case .Ampersand: prefix_kind = .Address case .Ampersand: prefix_kind = .Address
case .Bang: prefix_kind = .Not case .Bang: prefix_kind = .Not
case .Dollar: prefix_kind = .Comptime
case .Keyword_Try: prefix_kind = .Try case .Keyword_Try: prefix_kind = .Try
} }
left = add_expr(parser, ast.Expr{ left = add_expr(parser, ast.Expr{
@@ -931,6 +971,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
right = ast.INVALID_EXPR, right = ast.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC, diagnostic = source.INVALID_DIAGNOSTIC,
}) })
}
} else { } else {
left = parse_primary(parser, nesting) left = parse_primary(parser, nesting)
} }
@@ -1577,6 +1618,7 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
} }
continue continue
} }
_, comptime_value := allow(parser, .Dollar)
names: [dynamic]token.Token names: [dynamic]token.Token
names.allocator = parser.module.allocator names.allocator = parser.module.allocator
for { for {
@@ -1596,7 +1638,12 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
} }
type_syntax := parse_type(parser) type_syntax := parse_type(parser)
for name in names { for name in names {
append(&params, ast.Param{name=name.symbol, span=name.span, type=type_syntax}) append(&params, ast.Param{
name=name.symbol,
span=name.span,
type=type_syntax,
comptime_value=comptime_value,
})
} }
delete(names) delete(names)
skip_newlines(parser) skip_newlines(parser)
+1
View File
@@ -36,6 +36,7 @@ Kind :: enum u8 {
Range_Inclusive, Range_Inclusive,
Ellipsis, Ellipsis,
At, At,
Dollar,
Star, Star,
Ampersand, Ampersand,
Caret, Caret,
+986 -18
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
b :: import "@std/build"
config :: b.BuildConfig{
name = "hello",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &[],
}
+3
View File
@@ -0,0 +1,3 @@
main func() i32 {
return 0
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
+11
View File
@@ -0,0 +1,11 @@
b :: import "@std/build"
config :: b.BuildConfig{
name = "manual_build",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &["examples/build/manual/native.c"],
}
+3
View File
@@ -0,0 +1,3 @@
int foreign_add(int a, int b) {
return a + b;
}
+5
View File
@@ -0,0 +1,5 @@
foreign_add c_func(a, b i32) i32
main func() i32 {
return foreign_add(20, 22)
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
+18
View File
@@ -0,0 +1,18 @@
# Illustrative build.bro for a raylib program (not run in CI: needs raylib
# installed).
#
# Generate the raylib bindings once with:
# brolang translate-c /opt/homebrew/Cellar/raylib/*/include/raylib.h > game/raylib.bro
# then:
# brolang build examples/build/raylib
b :: import "@std/build"
config :: b.BuildConfig{
name = "game",
source = "source",
libraries = &["raylib"],
lib_paths = &["/opt/homebrew/lib"],
includes = &["/opt/homebrew/include"],
defines = &[],
links = &["-framework", "Cocoa", "-framework", "IOKit", "-framework", "CoreVideo", "-framework", "OpenGL"],
}
+250
View File
@@ -0,0 +1,250 @@
# Bouncing-shapes sandbox — a tour of brolang on top of raylib.
#
# Click to spawn a shape under the cursor, WASD/arrows to blow them around,
# SPACE to clear. The shape nearest the cursor is highlighted with its stats.
#
# Feature tour: native structs, enums, tagged unions + match (value, statement,
# payload capture, void variants, contextual construction), optionals + unwrap,
# value-loops (`yield :blk`), fallible functions with try/catch, defer, ranged
# and pointer-capturing for-loops, while, break/continue, compound assignment,
# scalar C interop, and multi-line strings.
rl :: import "@vendor/raylib"
# --- screen and physics constants -------------------------------------------
W :: 900
H :: 540
CAP usize :: 64
GRAV :: 0.18 # downward pull per frame
DAMP :: 0.82 # energy kept on a wall bounce
FORCE :: 0.9 # wind impulse from a key press
SPINMAX :: 3.0 # spin magnitude cap
RING_PAD :: 6.0 # highlight ring spacing
# --- shapes -----------------------------------------------------------------
Kind :: enum {
circle
square
triangle
}
Ball :: struct {
x f32
y f32
dx f32
dy f32
radius f32
kind Kind
}
# A frame's worth of player intent, as a tagged union. Each arm carries exactly
# the data that action needs (or `void` when it needs none).
Command :: union(enum) {
spawn rl.Vector2 # spawn a shape at this point
push struct { dx f32, dy f32 } # blow every shape this way
clear void
idle void
}
# Spawning can fail when the backing array is full; the error carries the cap so
# the caller can report it.
SpawnError :: union(enum) {
full struct { cap usize }
}
# value-match used as an expression source: each arm yields a Color.
color_for func(k Kind) rl.Color {
col :: match k {
.circle: rl.Color{ r = 235, g = 90, b = 90, a = 255 }
.square: rl.Color{ r = 90, g = 205, b = 130, a = 255 }
.triangle: rl.Color{ r = 105, g = 160, b = 245, a = 255 }
}
return col
}
# value-match dispatching to a contextual return, used to cycle spawn kind.
next_kind func(k Kind) Kind {
return match k {
.circle: .square
.square: .triangle
.triangle: .circle
}
}
# Read this frame's input into a single Command (contextual union construction:
# `.clear`, `.spawn{...}`, `.push{...}` are built against the return type).
read_command func() Command {
if rl.IsMouseButtonPressed(rl.MOUSE_BUTTON_LEFT) return .spawn{ rl.GetMousePosition() }
if rl.IsKeyPressed(rl.KEY_SPACE) return .clear
fx f32 = 0.0
fy f32 = 0.0
if rl.IsKeyDown(rl.KEY_A) fx -= FORCE
if rl.IsKeyDown(rl.KEY_D) fx += FORCE
if rl.IsKeyDown(rl.KEY_W) fy -= FORCE
if rl.IsKeyDown(rl.KEY_S) fy += FORCE
if rl.IsKeyDown(rl.KEY_LEFT) fx -= FORCE
if rl.IsKeyDown(rl.KEY_RIGHT) fx += FORCE
if rl.IsKeyDown(rl.KEY_UP) fy -= FORCE
if rl.IsKeyDown(rl.KEY_DOWN) fy += FORCE
moved :: fx != 0.0 or fy != 0.0
if (moved) return .push{ dx = fx, dy = fy }
return .idle
}
# Fallible capacity check: returns the slot index to fill, or fails `.full`.
reserve func(used usize) usize ! SpawnError {
if (used >= CAP) return .full{ cap = CAP }
return used
}
# Advance one ball: gravity, integrate, bounce off the four walls with damping.
# `b` is a pointer into the array, so the writes land in place.
step func(b @mut Ball) void {
b.dy += GRAV
b.x += b.dx
b.y += b.dy
if (b.x < b.radius) {
b.x = b.radius
b.dx = -b.dx * DAMP
}
right :: f32(W) - b.radius
if (b.x > right) {
b.x = right
b.dx = -b.dx * DAMP
}
if (b.y < b.radius) {
b.y = b.radius
b.dy = -b.dy * DAMP
}
floor :: f32(H) - b.radius
if (b.y > floor) {
b.y = floor
b.dy = -b.dy * DAMP
}
}
draw_ball func(b @Ball, highlight bool) void {
col :: color_for(b.kind)
center rl.Vector2 = rl.Vector2{ x = b.x, y = b.y }
match b.kind {
.circle: rl.DrawCircleV(center, b.radius, col)
.square: rl.DrawPoly(center, 4, b.radius, 45.0, col)
.triangle: rl.DrawPoly(center, 3, b.radius, 0.0, col)
}
if highlight {
ring rl.Color = rl.Color{ r = 250, g = 245, b = 200, a = 255 }
rl.DrawPoly(center, 24, b.radius + RING_PAD, 0.0, ring)
}
}
main func() i32 {
rl.SetConfigFlags(rl.FLAG_MSAA_4X_HINT)
rl.InitWindow(W, H, "brolang — bouncing shapes")
defer rl.CloseWindow() # runs on every exit path out of main
rl.SetTargetFPS(60)
help ::
`[click] spawn a shape [WASD/arrows] blow wind
`[space] clear
balls [CAP]mut Ball = undefined
count usize = 0 # number of live balls, in slots 0..count
kc Kind = .circle # next kind to spawn
spin f32 = 1.0 # rotates spawn velocity for variety
at_cap bool = false # show the "at capacity" banner
bg :: rl.Color{ r = 24, g = 26, b = 34, a = 255 }
text :: rl.Color{ r = 225, g = 225, b = 230, a = 255 }
warn :: rl.Color{ r = 245, g = 180, b = 90, a = 255 }
while !rl.WindowShouldClose() {
# --- input -> command -----------------------------------------------
cmd :: read_command()
match cmd {
.spawn |at|: {
slot :: reserve(count) catch |e| {
match e {
.full |info|: at_cap = true
}
yield CAP # sentinel: >= CAP means "didn't fit"
}
if (slot < CAP) {
balls[slot] = Ball{
x = f32(at.x), y = f32(at.y),
dx = FORCE * 6.0 * spin,
dy = -FORCE * 5.0,
radius = 18.0,
kind = kc,
}
count += 1
kc = next_kind(kc)
spin = -spin * 1.2
if (spin > SPINMAX or spin < -SPINMAX) spin = 1.0
at_cap = false
}
}
.push |f|: {
for (&balls) |@b, i| {
if (i >= count) break
b.dx += f.dx
b.dy += f.dy
}
}
.clear: {
count = 0
at_cap = false
}
.idle: {}
}
# --- physics --------------------------------------------------------
for (&balls) |@b, i| {
if (i >= count) break
step(b)
}
# --- which shape is under the cursor? (optional via a value-loop) ---
mouse :: rl.GetMousePosition()
sel :: for 0..(count) |i| hover: {
c rl.Vector2 = rl.Vector2{ x = balls[i].x, y = balls[i].y }
if rl.CheckCollisionPointCircle(mouse, c, balls[i].radius) yield :hover i
yield none
}
# --- draw -----------------------------------------------------------
rl.BeginDrawing()
rl.ClearBackground(bg)
for (&balls) |@b, i| {
if (i >= count) break
hot bool = false
if sel |s| {
if (s == i) hot = true # true only for the hovered ball
}
draw_ball(b, hot)
}
rl.DrawText(help, 16, 16, 20, text)
if sel |s| {
label :: match balls[s].kind {
.circle: "circle"
.square: "square"
.triangle: "triangle"
}
rl.DrawText(label, 16, H - 36, 20, text)
}
if (at_cap) rl.DrawText("at capacity", W - 170, 16, 20, warn)
rl.DrawFPS(W - 90, H - 28)
rl.EndDrawing()
}
return 0
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
sum func(a, b int) int {
return a + b
}
max func(a, b int) int {
if a > b {
return a
}
return b
}
nested func(value int) int {
two :: 2
return sum(value, two)
}
make_array func($N usize) [N]u8 {
data [N]u8 = undefined
return data
}
forced :: $sum(1, 2)
main func() i32 {
value i32 :: $sum(20, 22)
choice i32 :: $max(9, 3)
blocked i32 :: ${
local :: 5
yield sum(local, 6)
}
bytes [_]u8 :: make_array($nested(2))
if forced != 3 {
return 1
}
if value != 42 {
return 2
}
if choice != 9 {
return 3
}
if blocked != 11 {
return 4
}
if bytes.len != 4 {
return 5
}
return 0
}
@@ -0,0 +1,46 @@
Point :: struct {
x i32
}
max func($T type, a, b T) T {
if a > b {
return a
}
return b
}
id func($T type, value T) T {
return value
}
buffer func($T type, $N usize, value T) [N]T {
data [N]T = undefined
_ = value
return data
}
main func() i32 {
a i32 :: 42
b i32 :: 27
if max(i32, a, b) != 42 {
return 1
}
small_a u8 :: 3
small_b u8 :: 9
if max(u8, small_a, small_b) != 9 {
return 2
}
p Point :: Point { x = 11 }
q Point :: id(Point, p)
if q.x != 11 {
return 3
}
bytes [_]u8 :: buffer(u8, 4, small_a)
if bytes.len != 4 {
return 4
}
return 0
}
+209
View File
@@ -0,0 +1,209 @@
State :: enum {
idle
ready
failed
}
Point :: struct {
x i32
y i32
}
Box :: union(enum) {
point Point
empty void
}
Error :: enum {
bad
}
make_point func() Point {
return Point { x = 3, y = 4 }
}
sum_loop func(limit i32) i32 {
total i32 = 0
i i32 = 0
while i < limit {
i += 1
if i == 2 {
continue
}
total += i
}
return total
}
sum_for func() i32 {
total i32 = 0
values [_]i32 = [1, 2, 3]
for values |value, index| {
total += value + index
}
return total
}
defer_value func() i32 {
value i32 = 1
{
defer value += 10
value += 1
}
return value
}
describe func(box Box) i32 {
return match box {
.point |p|: p.x + p.y
.empty: 0
}
}
maybe func(flag bool) ?i32 {
if flag {
return 9
}
return none
}
may_fail func(flag bool) i32 ! Error {
if flag {
return .bad
}
return 7
}
increment func(value i32) i32 {
return value + 1
}
call_native func(callback *func(value i32) i32, value i32) i32 {
return callback(value)
}
call_fallible func(callback *func(flag bool) i32 ! Error, flag bool) i32 ! Error {
return try callback(flag)
}
use_try func() i32 ! Error {
value :: try may_fail(false)
return value + 1
}
recover func() i32 {
return may_fail(true) catch |e| {
match e {
.bad: yield 5
}
}
}
bump_ptr func(value @mut i32) void {
value^ += 1
}
alias_add func(left @mut i32, right @mut i32) void {
left^ += 2
right^ += 3
}
storage_mutation func() i32 {
values [3]mut i32 = [1, 2, 3]
values[0] += 1
bump_ptr(&values[1])
view []mut i32 = values[..]
for view |@item| {
item^ += 1
}
pointer *mut i32 = view.ptr
pointer[2] += 1
alias_add(&values[0], &view[0])
box Box = Box { point = Point { x = 2, y = 3 } }
match box {
.point |@p|: p.x += values[1]
.empty: values[0] = values[0]
}
if view.len != 3 {
return 0
}
return values[0] + view[1] + pointer[2] + box.point.x
}
GLOBAL :: $sum_loop(4)
main func() i32 {
point Point :: $make_point()
numbers [_]i32 :: $[4, 5, 6]
box Box :: $Box { point = Point { x = 8, y = 1 } }
state State :: $State.ready
name :: $"bro"
value i32 :: $sum_for()
deferred i32 :: $defer_value()
optional i32 :: $maybe(true)?
tried i32 :: $use_try() catch 0
recovered i32 :: $recover()
storage i32 :: $storage_mutation()
called i32 :: $call_native(increment, 11)
fallible_ok i32 :: $call_fallible(may_fail, false) catch 0
fallible_err i32 :: $call_fallible(may_fail, true) catch |e| {
result i32 :: match e {
.bad: 13
}
yield result
}
if point.x + point.y != 7 {
return 1
}
if numbers.len != 3 or numbers[2] != 6 {
return 2
}
if describe(box) != 9 {
return 3
}
if state != State.ready {
return 4
}
if name.len != 3 {
return 5
}
if value != 9 {
return 6
}
if deferred != 12 {
return 7
}
if optional != 9 {
return 8
}
if tried != 8 {
return 9
}
if recovered != 5 {
return 10
}
if GLOBAL != 8 {
return 11
}
if storage != 23 {
return 12
}
if called != 12 {
return 13
}
if call_native(increment, 20) != 21 {
return 14
}
if fallible_ok != 7 {
return 15
}
if fallible_err != 13 {
return 16
}
return 0
}
@@ -0,0 +1,32 @@
make_array func($N usize) [N]u8 {
data [N]u8 = undefined
return data
}
value func($N usize) usize {
return N
}
main func() i32 {
four [_]u8 :: make_array(4)
if four.len != 4 {
return 1
}
if value(4) != 4 {
return 2
}
eight [_]u8 :: make_array(8)
if eight.len != 8 {
return 3
}
if value(8) != 8 {
return 4
}
again [_]u8 :: [1, 2, 3, 4]
if again.len != 4 {
return 5
}
return 0
}
+3 -3
View File
@@ -44,11 +44,11 @@ main func() i32 {
total = total + 5 # 35 total = total + 5 # 35
} }
# block scoping: inner x shadows outer x, outer is unchanged after the block # block scoping: inner bindings do not escape the block
x i32 = 1 x i32 = 1
if x == 1 { if x == 1 {
x i32 = 100 inner_x i32 = 100
if x == 100 { if inner_x == 100 {
total = total + 5 # 40 total = total + 5 # 40
} }
} }
@@ -1,4 +1,4 @@
bad int = 1 bad i32 :: undefined
read_bad func() int { read_bad func() int {
return bad return bad
@@ -1,4 +1,4 @@
bad int = 1 bad i32 :: undefined
read_bad func() int { read_bad func() int {
return bad return bad
-7
View File
@@ -1,7 +1,5 @@
heap :: import "@std/mem/heap" heap :: import "@std/mem/heap"
#printf c_func(fmt *c_char, ...) c_int
main func() i32 { main func() i32 {
memory ?*mut u8 = heap.alloc(4) memory ?*mut u8 = heap.alloc(4)
defer heap.free(memory) defer heap.free(memory)
@@ -11,11 +9,6 @@ main func() i32 {
bytes[1] = 20 bytes[1] = 20
bytes[2] = bytes[0] + bytes[1] bytes[2] = bytes[0] + bytes[1]
_ = printf("bytes[0]: %d\n", bytes[0])
_ = printf("bytes[1]: %d\n", bytes[1])
_ = printf("bytes[2]: %d\n", bytes[2])
_ = printf("bytes[3]: %d\n", bytes[3])
if (bytes[2] != 30) { if (bytes[2] != 30) {
return 2 return 2
} }
+30
View File
@@ -0,0 +1,30 @@
Point :: struct {
x i32
}
counter int = 0
ratio float = 1
span range = 0..2
point Point = Point { x = 1 }
values [_]mut i32 = [10, 20]
bump func(value @mut i32) void {
value^ += 1
}
main func() i32 {
counter = 10
counter += 5
bump(&counter)
point.x += counter
values[1] = point.x
total i32 = counter + point.x + values[1]
for span |i| {
total += i
}
if ratio == 1.0 {
total += 1
}
return total
}
+3 -3
View File
@@ -31,12 +31,12 @@ main func() i32 {
} }
} }
# The body-local k shadows only inside the body. The update still targets # Body-local storage stays scoped to the body. The update still targets
# the mutable k declared before the loop. # the mutable k declared before the loop.
k u32 = 0 k u32 = 0
while k < 4 : k = k + 1 { while k < 4 : k = k + 1 {
k u32 = 100 body_k u32 = 100
if k == 100 { if body_k == 100 {
total = total + 2 total = total + 2
} }
} }
+291 -2
View File
@@ -11,9 +11,12 @@ import "core:os/os2"
import "core:path/filepath" import "core:path/filepath"
import "core:strings" import "core:strings"
BROLANG_VERSION :: "0.0.0-dev"
Cli_Options :: struct { Cli_Options :: struct {
input_path: string, input_path: string,
output_path: string, output_path: string,
project_root: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
c_options: cimport.Options, c_options: cimport.Options,
target: target.Target, target: target.Target,
@@ -71,6 +74,11 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
return {}, false return {}, false
} }
options.target = selected options.target = selected
case "--root":
if len(options.project_root) > 0 {
return {}, false
}
options.project_root = value
case: case:
return {}, false return {}, false
} }
@@ -87,17 +95,261 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
print_usage :: proc() { print_usage :: proc() {
fmt.eprintln( fmt.eprintln(
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...", "usage: brolang <package-directory> -o <executable> [--root <project-root>] [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
) )
fmt.eprintln( fmt.eprintln(
" brolang translate-c|--translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...", " brolang translate-c|--translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
) )
fmt.eprintln(
" brolang build [root] (reads root/build.bro; writes root/build/name)",
)
fmt.eprintln(
" brolang new <project-path>",
)
fmt.eprintln(
" brolang init",
)
fmt.eprintln(
" brolang version",
)
} }
is_translate_c_command :: proc(arg: string) -> bool { is_translate_c_command :: proc(arg: string) -> bool {
return arg == "translate-c" || arg == "--translate-c" return arg == "translate-c" || arg == "--translate-c"
} }
is_build_command :: proc(arg: string) -> bool {
return arg == "build"
}
is_new_command :: proc(arg: string) -> bool {
return arg == "new"
}
is_init_command :: proc(arg: string) -> bool {
return arg == "init"
}
is_version_command :: proc(arg: string) -> bool {
return arg == "version"
}
template_root_valid :: proc(root: string) -> bool {
std_build, std_error := filepath.join({root, "std", "build", "build.bro"})
if std_error != nil {
return false
}
defer delete(std_build)
ffi_stdio, ffi_error := filepath.join({root, "ffi", "c", "stdio.bro"})
if ffi_error != nil {
return false
}
defer delete(ffi_stdio)
return os.exists(std_build) && os.exists(ffi_stdio)
}
find_template_root :: proc(allocator := context.allocator) -> (string, bool) {
if exe, exe_error := os2.get_executable_path(allocator); exe_error == nil {
defer delete(exe, allocator)
exe_dir := filepath.dir(exe, allocator)
defer delete(exe_dir, allocator)
if template_root_valid(exe_dir) {
return strings.clone(exe_dir, allocator), true
}
parent := filepath.dir(exe_dir, allocator)
defer delete(parent, allocator)
if template_root_valid(parent) {
return strings.clone(parent, allocator), true
}
}
cwd := os.get_current_directory(allocator)
defer delete(cwd, allocator)
if template_root_valid(cwd) {
return strings.clone(cwd, allocator), true
}
return "", false
}
ensure_directory :: proc(path: string) -> bool {
if os.exists(path) {
if os.is_dir(path) {
return true
}
fmt.eprintfln("path exists and is not a directory: %s", path)
return false
}
if err := os2.make_directory_all(path); err != nil {
fmt.eprintfln("failed to create directory '%s': %v", path, err)
return false
}
return true
}
ensure_child_directory :: proc(root, name: string) -> bool {
path, err := filepath.join({root, name})
if err != nil {
return false
}
defer delete(path)
return ensure_directory(path)
}
write_file_if_missing :: proc(path, text: string) -> bool {
if os.exists(path) {
if os.is_dir(path) {
fmt.eprintfln("path exists and is not a file: %s", path)
return false
}
return true
}
if !os.write_entire_file(path, transmute([]byte)text) {
fmt.eprintfln("failed to write file '%s'", path)
return false
}
return true
}
write_escaped_brolang_string :: proc(builder: ^strings.Builder, value: string) {
for b in transmute([]byte)value {
if b == '\\' || b == '"' {
strings.write_byte(builder, '\\')
}
strings.write_byte(builder, b)
}
}
default_build_bro :: proc(project_name: string, allocator := context.allocator) -> string {
name := project_name
if len(name) == 0 || name == "." || name == "/" {
name = "app"
}
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "b :: import \"@std/build\"\n\nconfig :: b.BuildConfig{\n\tname = \"")
write_escaped_brolang_string(&builder, name)
strings.write_string(&builder, "\",\n\tsource = \"source\",\n\tlibraries = &[],\n\tlib_paths = &[],\n\tincludes = &[],\n\tdefines = &[],\n\tlinks = &[],\n}\n")
return strings.clone(strings.to_string(builder), allocator)
}
ensure_default_sources :: proc(root: string) -> bool {
source_dir, source_error := filepath.join({root, "source"})
if source_error != nil {
return false
}
defer delete(source_dir)
if !ensure_directory(source_dir) {
return false
}
main_path, main_error := filepath.join({source_dir, "main.bro"})
if main_error != nil {
return false
}
defer delete(main_path)
return write_file_if_missing(main_path, "main func() i32 {\n\treturn 0\n}\n")
}
copy_tree_if_missing :: proc(root, template_root, name: string) -> bool {
dst, dst_error := filepath.join({root, name})
if dst_error != nil {
return false
}
defer delete(dst)
if os.exists(dst) {
if os.is_dir(dst) {
return true
}
fmt.eprintfln("path exists and is not a directory: %s", dst)
return false
}
src, src_error := filepath.join({template_root, name})
if src_error != nil {
return false
}
defer delete(src)
if err := os2.copy_directory_all(dst, src); err != nil {
fmt.eprintfln("failed to copy '%s' to '%s': %v", src, dst, err)
return false
}
return true
}
init_project :: proc(root: string) -> bool {
if !ensure_directory(root) {
return false
}
if !ensure_default_sources(root) || !ensure_child_directory(root, "vendor") {
return false
}
needs_template := true
std_path, std_error := filepath.join({root, "std"})
ffi_path, ffi_error := filepath.join({root, "ffi"})
if std_error == nil && ffi_error == nil {
needs_template = !os.exists(std_path) || !os.exists(ffi_path)
}
if std_error == nil {
delete(std_path)
}
if ffi_error == nil {
delete(ffi_path)
}
template_root := ""
if needs_template {
ok: bool
template_root, ok = find_template_root()
if !ok {
fmt.eprintln("could not find bundled std/ffi sources")
return false
}
defer delete(template_root)
if !copy_tree_if_missing(root, template_root, "std") ||
!copy_tree_if_missing(root, template_root, "ffi") {
return false
}
}
build_path, build_error := filepath.join({root, "build.bro"})
if build_error != nil {
return false
}
defer delete(build_path)
project_name := filepath.base(root)
build_text := default_build_bro(project_name)
defer delete(build_text)
return write_file_if_missing(build_path, build_text)
}
new_project :: proc(path: string) -> bool {
if os.exists(path) {
fmt.eprintfln("project path already exists: %s", path)
return false
}
template_root, ok := find_template_root()
if !ok {
fmt.eprintln("could not find bundled std/ffi sources")
return false
}
defer delete(template_root)
if !ensure_directory(path) {
return false
}
if !init_project(path) {
return false
}
return true
}
run_init_project :: proc() -> int {
cwd := os.get_current_directory()
defer delete(cwd)
return 0 if init_project(cwd) else 2
}
run_new_project :: proc(path: string) -> int {
return 0 if new_project(path) else 2
}
zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) { zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) {
rest := text rest := text
prefix := ".lib_dir = \"" prefix := ".lib_dir = \""
@@ -233,6 +485,36 @@ run_translate_c :: proc(args: []string) -> int {
} }
main :: proc() { main :: proc() {
if len(os2.args) >= 2 && is_version_command(os2.args[1]) {
if len(os2.args) != 2 {
print_usage()
os2.exit(2)
}
fmt.printfln("brolang %s", BROLANG_VERSION)
os2.exit(0)
}
if len(os2.args) >= 2 && is_build_command(os2.args[1]) {
if len(os2.args) > 3 {
print_usage()
os2.exit(2)
}
root := os2.args[2] if len(os2.args) == 3 else ""
os2.exit(compiler.run_build(root))
}
if len(os2.args) >= 2 && is_new_command(os2.args[1]) {
if len(os2.args) != 3 {
print_usage()
os2.exit(2)
}
os2.exit(run_new_project(os2.args[2]))
}
if len(os2.args) >= 2 && is_init_command(os2.args[1]) {
if len(os2.args) != 2 {
print_usage()
os2.exit(2)
}
os2.exit(run_init_project())
}
if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) { if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) {
os2.exit(run_translate_c(os2.args)) os2.exit(run_translate_c(os2.args))
} }
@@ -244,7 +526,14 @@ main :: proc() {
defer delete(options.link_arguments) defer delete(options.link_arguments)
defer delete(options.c_options.include_paths) defer delete(options.c_options.include_paths)
defer delete(options.c_options.defines) defer delete(options.c_options.defines)
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options) status := compiler.compile_package(
options.input_path,
options.output_path,
options.link_arguments,
options.target,
options.c_options,
options.project_root,
)
if status != 0 { if status != 0 {
os2.exit(status) os2.exit(status)
} }
+17
View File
@@ -0,0 +1,17 @@
# Build configuration surface for `brolang build` (v0).
#
# A project's `build.bro` imports this module and declares a top-level constant
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
# build.bro, reads the config, and writes root/build/name.
#
# Declarative and literal-only: one executable per build. List fields take an
# address-of an array literal (`&["raylib"]`); empty lists are written `&[]`.
BuildConfig :: struct {
name []u8 # output executable name under root/build
source []u8 # program package directory, relative to build.bro
libraries [][]u8 # library names to link (-l)
lib_paths [][]u8 # library search directories (-L)
includes [][]u8 # C include directories (-I)
defines [][]u8 # C preprocessor defines (name or name=value)
links [][]u8 # extra linker inputs (object/source files, -framework pairs)
}