Files
brolang/LANGUAGE.md
T

13 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; void ! E functions complete successfully on fallthrough, and void-success catch handlers may fall through without yield

native record constraint fields

A direct int, float, or range field in a named native struct or union is a program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed constructors, field assignments, and concrete uses of field reads contribute demands and the field resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer literals remain provisional until inference settles, so a later usize use can resolve an int field to usize; otherwise literal-only int fields use the widest smallest-signed type required, and literal-only float fields use f64.

An undemanded field or incompatible demands are errors. This inference applies only to direct fields of named native records. c_struct fields, nested constraints such as []int, and fields in anonymous generated records still require concrete runtime types.

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; break :label can cross nested scopes to exit a labeled block
  • bare block scopes, defer, and fallible-function errdefer with optional error capture; cleanup is block-scoped and LIFO
  • bare void return, same-line return value, value blocks, value if, value loops, value match, and strictly value-producing yield value / 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