Compare commits
10 Commits
b1ddecfc2e
...
7ce3917c15
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ce3917c15 | |||
| e7f69ffb5a | |||
| cdde49e68b | |||
| 4ebe9c90e9 | |||
| ee41a54e41 | |||
| adf142736c | |||
| e00a4e929a | |||
| b94687c30a | |||
| a98b26446d | |||
| 7cda126924 |
+10
-5
@@ -8,8 +8,8 @@ roadmap and milestone history.
|
||||
### source, declarations, and packages
|
||||
|
||||
- newline-terminated statements and `#` comments
|
||||
- immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
||||
- immutable package globals, function-local mutable locals, and mutable local declarations initialized with `undefined`
|
||||
- immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
|
||||
- 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`
|
||||
- directory packages with merged declarations
|
||||
- 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
|
||||
- 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)`
|
||||
- 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-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
|
||||
@@ -52,8 +52,13 @@ roadmap and milestone history.
|
||||
### functions, C interop, and linking
|
||||
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
@@ -72,14 +77,14 @@ roadmap and milestone history.
|
||||
|
||||
- error-tolerant compilation with diagnostics and runtime traps where recovery is possible
|
||||
- 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
|
||||
- replaceable dynamically loaded libclang C-import backend
|
||||
- C-header import caching by canonical path, target, include paths, and defines
|
||||
|
||||
## PLANNED / DEFERRED
|
||||
|
||||
- comptime polymorphism
|
||||
- aggregate comptime parameters and stable aggregate specialization keys
|
||||
- tuples and native Brolang variadic functions
|
||||
- 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
|
||||
|
||||
@@ -30,6 +30,35 @@ invocation in command-line order:
|
||||
`-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>`.
|
||||
|
||||
```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:
|
||||
|
||||
```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:
|
||||
|
||||
```bro
|
||||
@@ -111,11 +149,12 @@ 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:
|
||||
declares them. Imports beginning with `@` resolve from the project root:
|
||||
|
||||
```bro
|
||||
import "../math"
|
||||
other_math :: import "../math"
|
||||
heap :: import "@std/mem/heap"
|
||||
|
||||
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
|
||||
- `#` 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`
|
||||
- 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
|
||||
@@ -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
|
||||
- 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, and deferred problematic globals
|
||||
- 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
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
- operators: `and`, `or`, `!`
|
||||
- lazy evaluation / short-circuit evaluation
|
||||
- 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)
|
||||
- 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`
|
||||
@@ -403,8 +403,7 @@
|
||||
statements first (a throwaway probe), so a first concrete `yield :blk` that references a
|
||||
block local still resolves the result to `?T`
|
||||
- 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);
|
||||
same-label loop/block shadowing resolves innermost-wins
|
||||
loop) whose concrete yield references a local declared *past* the first yield (annotate)
|
||||
|
||||
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
|
||||
- inspired by zig
|
||||
@@ -627,14 +626,103 @@
|
||||
- typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
|
||||
|
||||
26. import from project "root" (implemented)
|
||||
- imports beginning with `@` resolve from the compiler process cwd / project root
|
||||
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math
|
||||
- imports beginning with `@` resolve from the project root
|
||||
- `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
|
||||
|
||||
@@ -1221,6 +1309,18 @@ data :: read_file(path) catch |e| match e {
|
||||
| `yield :label value` | Provide value from labeled block |
|
||||
| `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
|
||||
|
||||
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
|
||||
|
||||
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
|
||||
Array,
|
||||
None,
|
||||
Undefined,
|
||||
Type,
|
||||
Name,
|
||||
Enum_Literal,
|
||||
Address,
|
||||
@@ -84,6 +85,7 @@ Expr_Kind :: enum u8 {
|
||||
Struct_Literal,
|
||||
Keyed,
|
||||
Cast,
|
||||
Comptime,
|
||||
Negate,
|
||||
Not,
|
||||
Add,
|
||||
@@ -123,6 +125,7 @@ Param :: struct {
|
||||
name: symbol.Id,
|
||||
span: source.Span,
|
||||
type: Type_Syntax,
|
||||
comptime_value: bool,
|
||||
}
|
||||
|
||||
Stmt_Kind :: enum u8 {
|
||||
|
||||
@@ -13,6 +13,32 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
|
||||
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(
|
||||
llvm_path, output_path: string,
|
||||
link_arguments: []linker.Argument,
|
||||
@@ -29,6 +55,7 @@ build_command :: proc(
|
||||
append_owned(&command, target.name(selected), allocator)
|
||||
append_owned(&command, "-Wno-override-module", allocator)
|
||||
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
|
||||
append_macos_sdk_paths(&command, selected, allocator)
|
||||
append_owned(&command, llvm_path, allocator)
|
||||
for path in c_options.include_paths {
|
||||
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+911
-257
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@ compile_package :: proc(
|
||||
link_arguments: []linker.Argument = nil,
|
||||
selected := target.DEFAULT,
|
||||
c_options := cimport.Options{},
|
||||
project_root := "",
|
||||
) -> int {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
@@ -75,6 +76,7 @@ compile_package :: proc(
|
||||
vmem.arena_allocator(&parser_arena),
|
||||
c_options,
|
||||
selected,
|
||||
project_root if len(project_root) > 0 else input_path,
|
||||
)
|
||||
if !loaded {
|
||||
source.print_all(&diagnostics)
|
||||
|
||||
@@ -201,6 +201,9 @@ lex :: proc(
|
||||
case '@':
|
||||
append_token(&stream, source_file, .At, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '$':
|
||||
append_token(&stream, source_file, .Dollar, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '*':
|
||||
start := cursor
|
||||
cursor += 1
|
||||
|
||||
@@ -1234,6 +1234,11 @@ declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: sy
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1411,11 +1416,13 @@ load :: proc(
|
||||
allocator := context.allocator,
|
||||
c_options := cimport.Options{},
|
||||
selected := target.DEFAULT,
|
||||
project_root_path := "",
|
||||
) -> (ast.Module, bool) {
|
||||
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 {
|
||||
project_root = strings.clone(".", allocator)
|
||||
project_root = strings.clone(project_root_source, allocator)
|
||||
}
|
||||
state := State{
|
||||
module=&module,
|
||||
|
||||
+59
-12
@@ -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_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||
.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 false
|
||||
@@ -329,10 +329,11 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
case .Keyword_Bool:
|
||||
advance(parser)
|
||||
return types.BOOL
|
||||
case .Keyword_C_Func:
|
||||
case .Keyword_Func, .Keyword_C_Func:
|
||||
c_abi := tok.kind == .Keyword_C_Func
|
||||
advance(parser)
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
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)
|
||||
for param, index in params {
|
||||
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(params, parser.module.allocator)
|
||||
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 {
|
||||
tok := current(parser)
|
||||
#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,
|
||||
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||
@@ -601,7 +621,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
start := tok
|
||||
target := parse_type_atom(parser)
|
||||
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
|
||||
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) {
|
||||
#partial switch kind {
|
||||
case .Minus, .Ampersand, .Bang, .Keyword_Try:
|
||||
case .Minus, .Ampersand, .Bang, .Dollar, .Keyword_Try:
|
||||
return 20, true
|
||||
}
|
||||
return 0, false
|
||||
@@ -913,6 +940,18 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
left := ast.INVALID_EXPR
|
||||
if right_power, ok := prefix_binding_power(current(parser).kind); ok {
|
||||
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 {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
@@ -922,15 +961,17 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
#partial switch operator.kind {
|
||||
case .Ampersand: prefix_kind = .Address
|
||||
case .Bang: prefix_kind = .Not
|
||||
case .Dollar: prefix_kind = .Comptime
|
||||
case .Keyword_Try: prefix_kind = .Try
|
||||
}
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=prefix_kind,
|
||||
span=span_from(operator.span, operand_expr.span),
|
||||
left=operand,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
kind = prefix_kind,
|
||||
span = span_from(operator.span, operand_expr.span),
|
||||
left = operand,
|
||||
right = ast.INVALID_EXPR,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
left = parse_primary(parser, nesting)
|
||||
}
|
||||
@@ -1577,6 +1618,7 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, comptime_value := allow(parser, .Dollar)
|
||||
names: [dynamic]token.Token
|
||||
names.allocator = parser.module.allocator
|
||||
for {
|
||||
@@ -1596,7 +1638,12 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
||||
}
|
||||
type_syntax := parse_type(parser)
|
||||
for name in names {
|
||||
append(¶ms, ast.Param{name=name.symbol, span=name.span, type=type_syntax})
|
||||
append(¶ms, ast.Param{
|
||||
name=name.symbol,
|
||||
span=name.span,
|
||||
type=type_syntax,
|
||||
comptime_value=comptime_value,
|
||||
})
|
||||
}
|
||||
delete(names)
|
||||
skip_newlines(parser)
|
||||
|
||||
@@ -36,6 +36,7 @@ Kind :: enum u8 {
|
||||
Range_Inclusive,
|
||||
Ellipsis,
|
||||
At,
|
||||
Dollar,
|
||||
Star,
|
||||
Ampersand,
|
||||
Caret,
|
||||
|
||||
+986
-18
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
b :: import "@std/build"
|
||||
|
||||
config :: b.BuildConfig{
|
||||
name = "hello",
|
||||
source = "src",
|
||||
libraries = &[],
|
||||
lib_paths = &[],
|
||||
includes = &[],
|
||||
defines = &[],
|
||||
links = &[],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
main func() i32 {
|
||||
return 0
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
int foreign_add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
foreign_add c_func(a, b i32) i32
|
||||
|
||||
main func() i32 {
|
||||
return foreign_add(20, 22)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"],
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+1221
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -44,11 +44,11 @@ main func() i32 {
|
||||
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
|
||||
if x == 1 {
|
||||
x i32 = 100
|
||||
if x == 100 {
|
||||
inner_x i32 = 100
|
||||
if inner_x == 100 {
|
||||
total = total + 5 # 40
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
bad int = 1
|
||||
bad i32 :: undefined
|
||||
|
||||
read_bad func() int {
|
||||
return bad
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
bad int = 1
|
||||
bad i32 :: undefined
|
||||
|
||||
read_bad func() int {
|
||||
return bad
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
heap :: import "@std/mem/heap"
|
||||
|
||||
#printf c_func(fmt *c_char, ...) c_int
|
||||
|
||||
main func() i32 {
|
||||
memory ?*mut u8 = heap.alloc(4)
|
||||
defer heap.free(memory)
|
||||
@@ -11,11 +9,6 @@ main func() i32 {
|
||||
bytes[1] = 20
|
||||
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) {
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
k u32 = 0
|
||||
while k < 4 : k = k + 1 {
|
||||
k u32 = 100
|
||||
if k == 100 {
|
||||
body_k u32 = 100
|
||||
if body_k == 100 {
|
||||
total = total + 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ import "core:os/os2"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
|
||||
BROLANG_VERSION :: "0.0.0-dev"
|
||||
|
||||
Cli_Options :: struct {
|
||||
input_path: string,
|
||||
output_path: string,
|
||||
project_root: string,
|
||||
link_arguments: []linker.Argument,
|
||||
c_options: cimport.Options,
|
||||
target: target.Target,
|
||||
@@ -71,6 +74,11 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
return {}, false
|
||||
}
|
||||
options.target = selected
|
||||
case "--root":
|
||||
if len(options.project_root) > 0 {
|
||||
return {}, false
|
||||
}
|
||||
options.project_root = value
|
||||
case:
|
||||
return {}, false
|
||||
}
|
||||
@@ -87,17 +95,261 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
|
||||
print_usage :: proc() {
|
||||
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(
|
||||
" 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 {
|
||||
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) {
|
||||
rest := text
|
||||
prefix := ".lib_dir = \""
|
||||
@@ -233,6 +485,36 @@ run_translate_c :: proc(args: []string) -> int {
|
||||
}
|
||||
|
||||
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]) {
|
||||
os2.exit(run_translate_c(os2.args))
|
||||
}
|
||||
@@ -244,7 +526,14 @@ main :: proc() {
|
||||
defer delete(options.link_arguments)
|
||||
defer delete(options.c_options.include_paths)
|
||||
defer delete(options.c_options.defines)
|
||||
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options)
|
||||
status := compiler.compile_package(
|
||||
options.input_path,
|
||||
options.output_path,
|
||||
options.link_arguments,
|
||||
options.target,
|
||||
options.c_options,
|
||||
options.project_root,
|
||||
)
|
||||
if status != 0 {
|
||||
os2.exit(status)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user