9.0 KiB
"quick" / "easy" fixes
- for global initialization cycles, report also starting and ending lines
milestones
- interop type foundation (implemented)
- unsigned integers, floats, and target-dependent c scalar types
- atomic
c_*primitive types remain distinct until target-aware lowering c_funcand pointer-onlyc_struct;cremains an ordinary identifier- keep binding mutability (
::/=) separate from element or pointee mutability (mut) - arrays and indexing
[N]T: array withNlogical elements[N;S]T: array withNlogical elements followed by sentinelS
- pointers
@T/@mut T: non-null single-item pointer without arithmetic*T/*mut T: non-null many-item pointer with arithmetic- optional pointers represent nullable pointers (i.e.
?@T/?@mut T,?*T/?*mut T)
- slices and slicing
[]T: pointer and length[;S]T: pointer and length with a sentinel invariant- ordinary slices do not guarantee null termination
- string literals as immutable sentinel slices backed by static arrays (superseded by milestone 3.5)
- character literals
- optionals with trapping unwrap and fallback operations
- native structs with compiler-controlled layout
- pointer-only
c_structsupport with target c layoutSome :: c_struct { ... }: defined c-layout structSome :: c_struct: opaque c-layout struct- passing c structs by value was deferred until milestone 4.1
- restricted c header imports (implemented)
- treat an imported header as a synthetic, file-local package namespace
native :: import "relative/path/to/header.h"
- import functions, typedefs, scalar types, and pointers to opaque records
- keep implementation linking separate from header imports
- cache imports by canonical header path and target/include/define configuration
- diagnose unsupported declarations when referenced
- dynamically load libclang behind a replaceable c importer boundary
- c variadic calls (implemented)
- represent c variadics as a fixed parameter count plus a variadic flag
- apply c default argument promotions at call sites
- emit LLVM c-variadic declarations and calls
- keep native brolang variadics and tuple design separate
3.5. sentinel pointers and c strings (implemented)
- add sentinel many-item pointers:
[*;S]T - represent string literals as immutable pointers to statically stored sentinel arrays:
@[N;0]u8 - arrays expose
.lenbut no.ptr; slices and pointers-to-arrays expose sentinel-preserving.ptr - allow pointer-to-array
.len, indexing, slicing, pointer decay, and slice construction without explicit dereference - preserve or forget sentinel information through compatible pointer and slice coercions without copying arrays
- allow zero-terminated immutable byte pointer views to convert to immutable
*c_charand[*;0]c_char - keep
u8andc_chardistinct to preserve target-dependent scalar c semantics - reject general
u8/c_charinterchange, slice-to-pointer coercion, and conversion to mutable c character pointers
- advanced c interop
- by-value records and unions (implemented)
- complete plain imported structs/unions and manual
c_structvalues - fixed C arrays inside imported records
- keyed struct literals and exactly-one-field union literals
- field reads/writes, storage, and fixed-signature calls/returns
- aarch64-macos small aggregate, homogeneous float aggregate, and indirect ABI lowering
- keep incomplete, bitfield, packed, flexible-array, qualified-field, and otherwise non-plain records pointer-only
- keep C variadic record arguments unsupported
- complete plain imported structs/unions and manual
- function pointers and callbacks (implemented)
- imported C function pointer typedefs lower to nullable pointer types
- manual
?*c_func(...) Tcallback type spelling - concrete
c_funcdeclarations/definitions can be passed as callback values - postfix calls through non-null function pointers, including
callback?(...) - fixed and C-variadic callback ABI emission through LLVM indirect calls
- external variables (implemented)
- imported external C object variables lower to direct LLVM external global references
- top-level
constobject variables are read-only from brolang - mutable external scalars/records can be assigned through qualified package globals
- unsupported variable types remain lazy diagnostics when referenced
- object-like macro constants (implemented)
- scalar integer/float literal macros import as immutable globals
CLITERAL(Type){ ... }/(Type){ ... }record literal macros import as immutable globals- function-like macros and non-literal macro expressions remain unsupported
- static inline functions (implemented)
- control flow
- boolean expressions (implemented)
booltype withtrue/falseliterals- comparison operators:
==,!=,<,<=,>,>=(numeric operands widen;boolsupports only==/!=) - operators:
and,or,! - lazy evaluation / short-circuit evaluation
- if statements (implemented). example:
if condition { ... } else if { ... } else { ... }- conditions must be
bool; block-scoped locals with shadowing across blocks - lowered through new
Label/Br/Cond_BrIR opcodes (alloca-backed locals, no phi nodes)
- conditions must be
- conditional unwrapping for optionals (
?T) (implemented):if val |v| { ... } else { ... }- unwrapvalintovif it is notnone- single immutable binding scoped to the then-block;
vnot visible inelseor after theif |lexes as a newPipetoken; the.Ifreuses ASTname/ HIRlocalto carry the binding (no new statement kind)- new
Optional_Is_Some/Optional_ValueIR opcodes (theUnwrappresence-test + extract, minus the trap)
- single immutable binding scoped to the then-block;
- conditional unwrapping with guard clause:
if val |v : v >= 10| { ... } else { ... }- unwrapvalintovif it is notnone - multi-unwrap (see section below)
- while loops (operates on boolean conditions). examples:
while condition { ... }- iterate while the condition is truewhile condition : i += 1 { ... }- iterate while the condition is true and executei += 1(continue expression) after each iteration
- ranges (see section below)
- for loops (operates on iterable sequences). examples:
for items |item| { ... }- capture just theitemvalue in the array/slice (uses copy semantics, i.e. gets aT)for items |&item| { ... }- capture just theitemvalue in the array/slice (uses (immutable) reference semantics, i.e. gets a@T)for items |&mut item| { ... }- capture just theitemvalue in the array/slice (uses (mutable) reference semantics, i.e. gets a@mut T)for items |item, idx| { ... }- captureitemand its index index in the array/slicefor 0..10 |i| { ... }- iterate over the range0..10(exclusive)for 0..=10 |i| { ... }- iterate over the range0..10(inclusive)for 0..(len) |i| { ... }or equivalentlyfor 0..=(len - 1) |i| { ... }- calculating range bounds, expressions must be parenthesized
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
- compound assignment
A word on multi-unwrap
Unwrap multiple optionals with and. This short-circuits: if the first optional is none, subsequent expressions are not evaluated.
name: ?[]u8 = get_name()
age: ?u8 = get_age()
if name and age |n, a| {
# both n and a are guaranteed non-none here
print("{s} is {d} years old", {n, a})
}
With guard clause on multiple values:
if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
print("{s}'s got that drip\n", {n})
}
Parentheses around the expression are optional, but can aid readability when combined with guards:
# without parentheses
if name and hat |n, h : guard| { ... }
# with parentheses for clarity
if (name and hat) |n, h : guard| { ... }
A word on lazy / short-circuit evaluation
The and in multi-unwrap short-circuits left-to-right:
if get_name() and get_hat() |n, h| {
# get_hat() is only called if get_name() returned non-none
}
This is important for avoiding unnecessary computation or side effects.
A word on ranges
Ranges represent a sequence of values, commonly used in for loops, and is itself a value type:
0..10 # exclusive: 0, 1, 2, ..., 9
0..=10 # inclusive: 0, 1, 2, ..., 10
Parenthesization rule: Each side of .. must be either a simple term (literal or identifier) or a parenthesized expression. This eliminates precedence ambiguity:
0..10 # OK: both sides are literals
0..n # OK: both sides are simple
0..(n + 1) # OK: complex expression is parenthesized
(a + 1)..(b - 1) # OK: both sides parenthesized
# 0..n + 1 # ERROR: must parenthesize complex expressions
This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value.