Files
brolang/TODO.md
T

8.7 KiB

"quick" / "easy" fixes

  • for global initialization cycles, report also starting and ending lines

milestones

  1. interop type foundation (implemented)
  • unsigned integers, floats, and target-dependent c scalar types
  • atomic c_* primitive types remain distinct until target-aware lowering
  • c_func and pointer-only c_struct; c remains an ordinary identifier
  • keep binding mutability (:: / =) separate from element or pointee mutability (mut)
  • arrays and indexing
    • [N]T: array with N logical elements
    • [N;S]T: array with N logical elements followed by sentinel S
  • 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_struct support with target c layout
    • Some :: c_struct { ... }: defined c-layout struct
    • Some :: c_struct: opaque c-layout struct
    • passing c structs by value was deferred until milestone 4.1
  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
  1. 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 .len but 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_char and [*;0]c_char
  • keep u8 and c_char distinct to preserve target-dependent scalar c semantics
  • reject general u8/c_char interchange, slice-to-pointer coercion, and conversion to mutable c character pointers
  1. advanced c interop
  • by-value records and unions (implemented)

    • complete plain imported structs/unions and manual c_struct values
    • 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
  • function pointers and callbacks (implemented)

    • imported C function pointer typedefs lower to nullable pointer types
    • manual ?*c_func(...) T callback type spelling
    • concrete c_func declarations/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 const object 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)

    1. control flow
    • boolean expressions (implemented)
      • bool type with true / false literals
      • comparison operators: ==, !=, <, <=, >, >= (numeric operands widen; bool supports 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_Br IR opcodes (alloca-backed locals, no phi nodes)
    • conditional unwrapping for optionals (?T): if val |v| { ... } else { ... } - unwrap val into v if it is not none
    • conditional unwrapping with guard clause: if val |v : v >= 10| { ... } else { ... } - unwrap val into v if it is not none
    • multi-unwrap (see section below)
    • while loops (operates on boolean conditions). examples:
      • while condition { ... } - iterate while the condition is true
      • while condition : i += 1 { ... } - iterate while the condition is true and execute i += 1 (continue expression) after each iteration
    • ranges (see section below)
    • for loops (operates on iterable sequences). examples:
      • for items |item| { ... } - capture just the item value in the array/slice (uses copy semantics, i.e. gets a T)
      • for items |&item| { ... } - capture just the item value in the array/slice (uses (immutable) reference semantics, i.e. gets a @T)
      • for items |&mut item| { ... } - capture just the item value in the array/slice (uses (mutable) reference semantics, i.e. gets a @mut T)
      • for items |item, idx| { ... } - capture item and its index index in the array/slice
      • for 0..10 |i| { ... } - iterate over the range 0..10 (exclusive)
      • for 0..=10 |i| { ... } - iterate over the range 0..10 (inclusive)
      • for 0..(len) |i| { ... } or equivalently for 0..=(len - 1) |i| { ... } - calculating range bounds, expressions must be parenthesized
    • for all conditionals/guards, parentheses are optional but allowed for visual clarity

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.