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
|
### 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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+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_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,15 +961,17 @@ 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{
|
||||||
kind=prefix_kind,
|
kind = prefix_kind,
|
||||||
span=span_from(operator.span, operand_expr.span),
|
span = span_from(operator.span, operand_expr.span),
|
||||||
left=operand,
|
left = operand,
|
||||||
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(¶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)
|
delete(names)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ Kind :: enum u8 {
|
|||||||
Range_Inclusive,
|
Range_Inclusive,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
At,
|
At,
|
||||||
|
Dollar,
|
||||||
Star,
|
Star,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
Caret,
|
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
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
# 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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