Files
brolang/LANGUAGE.md
T

12 KiB

language features

This file is the compact current-state ledger. TODO.md remains the detailed roadmap and milestone history.

IMPLEMENTED

source, declarations, and packages

  • newline-terminated statements and # comments
  • immutable :: bindings, typed mutable = locals/globals, and _ sinks
  • immutable package globals, mutable runtime globals, function-local mutable locals, and mutable local declarations initialized with undefined
  • package-level functions, globals, native type declarations, and Name :: alias T
  • directory packages with merged declarations
  • file-local relative imports, import aliases, and qualified member access
  • transparent declaration aliases with Name :: alias package.Member; functions/type factories, named types, and globals retain their original declaration or storage identity
  • native top-level declarations beginning with _ are visible only within their source file; locals, fields, parameters, and C declarations are unaffected
  • relative .h imports as synthetic C header package namespaces
  • root main validation with trap executable recovery for missing or unusable entry points

scalar, aggregate, and pointer types

  • exact-width integers, isize, usize, f32, f64, bool, void, anyopaque, and contextual int, float, and range constraints
  • target-dependent C scalar primitives from c_char through c_longdouble, kept semantically distinct from native scalars
  • contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
  • strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as i32(x) / c_float(x)
  • compile-time min_value(T) and max_value(T) bounds for concrete native and C integer scalar types
  • arrays [N]T, inferred-count arrays [_]T, sentinel arrays [N;S]T, compile-time expression array counts, slices []T / [;S]T, single-item pointers @T, many-item pointers *T, and sentinel many-item pointers [*;S]T
  • pointer mutability via mut, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference ^, and trapping optional unwrap ?
  • pointer-to-array .len, indexing, slicing, .ptr on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
  • ptr_cast(T, ptr) as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
  • UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
  • narrow immutable zero-terminated byte pointer/slice conversion to *c_char / ?*c_char without general u8/c_char interchange
  • optionals with none, orelse, postfix ?, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
  • nominal distinct types with exact backing construction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
  • source-order native structs, opaque nominal records with Name :: opaque, complete c_struct { ... }, keyed record literals, native untagged unions, and native tagged unions union(Enum) / union(enum)
  • void-payload tagged-union variants, anonymous struct payloads, contextual .variant, .variant{payload}, and .variant{field = value} construction
  • native sum composition with A | B for unbacked enums and tagged unions, using program-global u16 variant ids
  • fallible channel types T ! E, where E is a native enum/tagged union or supported sum composition

keyword member names

Reserved keywords are valid native enum members and tagged-union variants when used in an unambiguous member context:

TokenKind :: enum {
    if
    else
    return
}

Token :: union(TokenKind) {
    if i32
    else void
    return i32
}

conditional func() TokenKind { return TokenKind.if }
fallback func() TokenKind { return .else }
token func() Token { return Token{ if = 1 } }

Keyword variants also work with field access and .variant match patterns; .else: remains distinct from the else: catch-all arm. No escaping syntax is required. Keywords remain reserved for ordinary declarations, struct fields, untagged-union fields, and anonymous payload-struct fields. _ is not a keyword member name.

expressions and control flow

  • checked integer + - *, unary -, float-only /, IEEE float arithmetic, comparisons, !, and, and or
  • assignments and compound assignments += -= *= /= with single evaluation of complex lvalues; /= is float-only
  • field access through struct values and pointers, index/slice bounds contextually coerced to usize, and unsigned narrower index support
  • boolean if / else if / else, braceless single-statement branches, and optional parenthesized conditions
  • while loops with optional post-iteration update clauses
  • for loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures |@item|, and optional usize index captures
  • break, continue, labeled break :label, labeled continue :label, and labeled plain blocks
  • bare block scopes and defer, including LIFO flushing on fall-through, return, break, and continue
  • value blocks, value if, value loops, value match, yield, and labeled yield :label value
  • match statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
  • fallible try, fallback catch, and catch |e| { ... } handler blocks
  • direct return match ... and yield match ... value-control-flow operands

division

/ and /= accept only floating-point operands. Integer division must state its rounding and remainder convention with one of these unqualified builtins:

Builtin Result
div_trunc(a, b) quotient rounded toward zero
div_floor(a, b) quotient rounded toward negative infinity
div_exact(a, b) truncated quotient; traps unless it divides exactly
div_ceil(a, b) quotient rounded toward positive infinity
rem(a, b) remainder paired with div_trunc; sign follows a
mod(a, b) modulus paired with div_floor; sign follows b

The operands may be compatible concrete integer or float scalars. Existing literal coercion and numeric widening rules apply, the result has the common operand type, and float quotients are integral-valued floats. These identities hold when representable:

div_trunc(a, b) * b + rem(a, b) == a
div_floor(a, b) * b + mod(a, b) == a

Negative operands distinguish the operations:

div_trunc(-5, 3) == -1
div_floor(-5, 3) == -2
div_ceil(-5, 3) == -1
rem(-5, 3) == -2
mod(-5, 3) == 1
mod(5, -3) == -1

All six builtins diagnose a zero denominator at comptime and trap at runtime, including float zero. Quotient operations also trap for signed min_value(T), -1; rem and mod return zero for that pair. div_exact traps when div_trunc(a, b) * b == a is false in the operand type, so float exactness follows floating-point equality. Other float NaN and infinity behavior follows the underlying IEEE operations. Ordinary float / remains unchecked and therefore preserves IEEE infinity/NaN behavior.

The six spellings are reserved only as direct unqualified calls. A qualified call such as math.div_floor(a, b) resolves to an ordinary package function.

functions, C interop, and linking

  • demand-monomorphized Brolang and C-ABI functions
  • integer comptime value parameters such as make_array func($N usize) [N]u8, specialized by value and omitted from the runtime ABI
  • explicit comptime type parameters such as max func($T type, a, b T) T, specialized by type and omitted from the runtime ABI
  • leading comptime type/integer parameters may be omitted when uniquely recoverable from runtime argument types or the immediate expected result; explicit calls remain valid
  • comptime parameters must form one leading prefix before all runtime parameters
  • 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
  • comptime type factories such as Box func($T type) type { return struct { value T } }; calls like Box(i32) are concrete nominal types and may appear anywhere a type is expected
  • bodyful c_func definitions and bodyless c_func declarations with exact external symbol names
  • concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
  • native function pointer values and types with @func(...) R, fallible @func(...) R ! E, optional ?@func(...) R, and non-variadic native indirect calls
  • Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns
  • imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, C void* as nullable anyopaque pointers, and pointers to opaque records
  • imported external C object variables, including mutable variables and immutable object globals
  • object-like scalar and plain record/union macro constants
  • supported static inline C functions through generated external wrappers
  • C function pointer types, imported nullable callback typedefs, concrete c_func callback values, and postfix calls through non-null function pointers
  • brolang translate-c <header.h> for native .bro bindings from supported C declarations
  • brolang --translate-c stdio.h for offline bindings from Zig-bundled standard C headers
  • ordered linking of additional C sources, objects, archives, library paths, and libraries through compiler CLI options

standard packages

  • root std re-exports ArrayList(T) while its operations remain in std/arraylist
  • std/mem generic slice equality, allocator contract with raw byte operations, typed empty / alloc / realloc / free, overflow checks, zero-sized-type support, and failure-preserving reallocation
  • std/arraylist generic ArrayList(T) with direct items slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
  • std/io explicit Io capabilities, Reader/Writer stream values, one-shot read/write, and allocation-free write_all

compiler behavior

  • error-tolerant compilation with diagnostics and runtime traps where recovery is possible
  • lazy semantic checking of demanded function specializations
  • static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
  • demand-driven LLVM declarations for referenced foreign functions
  • root main may be parameterless or accept the canonical @std/io Io; the injected form is called through a synthesized no-argument C entry point
  • replaceable dynamically loaded libclang C-import backend
  • C-header import caching by canonical path, target, include paths, and defines

PLANNED / DEFERRED

  • aggregate comptime parameters and stable aggregate specialization keys
  • tuples and native Brolang variadic functions
  • exporting Brolang functions to C and broader target-specific C ABI lowering
  • non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
  • arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
  • recursive type factories, type reflection, and type-producing unions/enums
  • broader Zig-style pointer/result casts beyond V1 ptr_cast(T, ptr)
  • sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
  • backed/C enum composition and must-consume fallible linting
  • result-to-argument type-demand propagation through function call boundaries
  • distinct-type backing operators and reverse explicit conversions
  • string concatenation operator