Updated documentation to reflect changes from the last 2 days: ROADMAP.md: - Mark string interpolation, named/default params, bux fmt/test/new/init, and basic borrow checker as ✅ Done - Update Recommended Order (next: closures) PHASE8_STRATEGY.md: - Update date, achievements, and milestone statuses - Borrow checker is now ✅ working in selfhost - CTFE, LSP, formatter, test runner marked done where applicable LanguageRef.md: - Add defer, switch/case, string interpolation (f"...") - Add &T, &mut T, own T to composite types - Add named/default parameters and operator overloading sections - Expand @[Checked] rules with double-mut-borrow and use-after-move - Update keywords and operators STRATEGY.md: - Update date and version (0.3.0 selfhost) - Mark 8.2 borrow checker, 8.4 CTFE, 8.7 LSP as ✅ BuildAndTest.md: - Add bux init, bux fmt, bux test commands - Expand test sections with golden tests and Std::Test
16 KiB
Bux Language Reference
This document describes the Bux programming language as implemented by the bootstrap compiler.
Table of Contents
- Lexical Structure
- Types
- Variables
- Functions
- Control Flow
- Structs
- Enums
- Pattern Matching
- Methods and Interfaces
- Generics
- Error Handling
- Modules and Imports
- Async/Await
- Operator Overloading
- Operators
Lexical Structure
Comments
// Single-line comment
/*
Multi-line comment
/* Nested comments are supported */
*/
Identifiers
Identifiers start with a letter or underscore, followed by letters, digits, or underscores.
Keywords
func, let, var, const, type, struct, enum, union, interface, extend
module, import, pub, extern, if, else, while, do, loop, for, in
break, continue, return, match, as, is, null, self, super, sizeof
async, await, spawn, defer, switch, case, default, checked
String Literals
"Hello" // String (UTF-8) — escape sequences: \n \t \r \\ \"
c8"Hello" // *char8 (C string)
c16"Hello" // *char16
c32"Hello" // *char32
`raw literal` // Raw multi-line string — no escape processing
`line 1
line 2
line 3` // Newlines preserved as-is
f"Hello, {name}" // Interpolated string — expressions inside {}
Backtick raw strings (`...`) treat all characters literally:
\nis two characters, not a newline- Actual newlines in source are preserved in the string
- No way to escape the backtick character itself (use regular strings if needed)
Interpolated strings (f"..."):
- Expressions inside
{}are evaluated and converted toString - Supported types:
int,uint,float,bool,String - Escaped braces:
\{and\}
Number Literals
42 // int
3.14 // float64
0x2A // hex
0o52 // octal
0b101010 // binary
32i8 // int8 literal
1000u64 // uint64 literal
Types
Primitive Types
| Type | Description |
|---|---|
int8, int16, int32, int64, int |
Signed integers |
uint8, uint16, uint32, uint64, uint |
Unsigned integers |
float32, float64 |
Floating-point |
bool, bool8, bool16, bool32 |
Booleans |
char8, char16, char32 |
Characters |
String |
C-compatible string (const char*) |
Composite Types
*T // Pointer to T
&T // Shared reference (read-only in checked functions)
&mut T // Mutable reference (exclusive borrow)
own T // Owned value (move semantics)
T[] // Slice (unsized)
T[N] // Fixed-size array
(T1, T2, T3) // Tuple
func(T1) -> T2 // Function type
Structs
struct Point {
x: int;
y: int;
}
Enums
enum Color {
Red,
Green,
Blue
}
// Algebraic enum (tagged union)
enum Result {
Ok(int),
Err(String)
}
Unions
union Bits {
asByte: uint8;
asInt: int32;
}
Variables
let x: int = 42; // Immutable
var y: int = 10; // Mutable
y = 20; // OK
const MAX: int = 100; // Compile-time constant
Functions
func Add(a: int, b: int) -> int {
return a + b;
}
// Extern C function
extern func printf(fmt: *char8, ...);
// Generic function
func Min<T>(a: T, b: T) -> T {
if a < b {
return a;
}
return b;
}
// Named and default parameters
func HttpResponse(code: int = 200, body: String = "") -> Response { ... }
let r: Response = HttpResponse(body: "hello"); // code defaults to 200
let s: Response = HttpResponse(404, body: "err"); // positional + named mixed
// Operator overloading (bootstrap only)
func Vec2_operator_add(self: *Vec2, other: Vec2) -> Vec2 { ... }
func Vec2_operator_sub(self: *Vec2, other: Vec2) -> Vec2 { ... }
func Vec2_operator_eq(self: *Vec2, other: Vec2) -> bool { ... }
func Vec2_operator_lt(self: *Vec2, other: Vec2) -> bool { ... }
func MyArray_operator_index_get(self: *MyArray, idx: int) -> int { ... }
func MyArray_operator_index_set(self: *MyArray, idx: int, value: int) { ... }
Control Flow
If / Else
if x > 0 {
PrintLine("positive");
} else if x < 0 {
PrintLine("negative");
} else {
PrintLine("zero");
}
Loops
while i < 10 {
i = i + 1;
}
do {
i = i + 1;
} while i < 10;
loop {
// Infinite loop
break;
}
for i in 0..10 {
// Range 0 to 9 (exclusive)
}
for i in 0..=10 {
// Range 0 to 10 (inclusive)
}
Break / Continue with Labels
outer: loop {
loop {
break outer;
}
}
defer
Runs an expression when the current scope exits (LIFO order).
func ReadFile(path: String) -> String {
let fd: int = Open(path);
defer Close(fd);
defer PrintLine("done");
let data: String = ReadAll(fd);
return data; // both defers run before return
}
switch / case
Desugars to an if-else chain. Supports a default case.
switch statusCode {
case 200: PrintLine("OK");
case 404: PrintLine("Not Found");
case 500: PrintLine("Server Error");
default: PrintLine("Unknown");
}
Structs
struct Rectangle {
width: int;
height: int;
}
func Main() -> int {
let rect: Rectangle = Rectangle { width: 10, height: 5 };
PrintInt(rect.width);
return 0;
}
Enums
Simple Enums
enum Color { Red, Green, Blue }
let c: Color = Color::Red;
if c == Color::Red {
PrintLine("red");
}
Algebraic Enums
enum Result {
Ok(int),
Err(String)
}
func Main() -> int {
let r: Result = Result { tag: Result_Ok };
r.data.Ok_0 = 42;
if r.tag == Result_Ok {
PrintInt(r.data.Ok_0);
}
return 0;
}
Pattern Matching
match opt {
Option::Some(value) => PrintInt(value),
Option::None => PrintLine("none")
}
Supported patterns:
- Wildcard:
_ - Literal:
42,"hello",true - Identifier:
name - Range:
1..9,1..=9 - Enum destructuring:
Shape::Circle(r) - Struct destructuring:
Point { x: 0, y: 0 } - Tuple:
(a, b, c) - Guard:
t if t < 0
Methods and Interfaces
struct Rectangle {
width: int;
height: int;
}
interface Drawable {
func Draw(self: Rectangle);
}
extend Rectangle for Drawable {
func Draw(self: Rectangle) {
PrintLine("Drawing rectangle");
}
}
// Or extend with standalone methods
extend Rectangle {
func Area(self: Rectangle) -> int {
return self.width * self.height;
}
}
Generics
Generic Functions
Generic functions are monomorphized at compile time. Type parameters can be specified explicitly or inferred from arguments:
func Max<T>(a: T, b: T) -> T {
if a > b { return a; }
return b;
}
func Main() -> int {
// Explicit type args
let m1: int = Max<int>(10, 20);
// Type inference — T inferred as int from arguments
let m2: int = Max(10, 20);
return 0;
}
Generic Structs
struct Box<T> {
value: T,
}
// Use extend Type<T> for methods on generic structs
extend Box<T> {
func Get(self: *Box<T>) -> T {
return self.value;
}
func Set(self: *Box<T>, value: T) {
self.value = value;
}
}
func Main() -> int {
let b: Box<int> = Box<int> { value: 42 };
PrintInt(b.Get()); // 42
b.Set(100);
PrintInt(b.Get()); // 100
return 0;
}
Note:
extend Type<T>syntax requires type parameters on the impl block. The compiler propagates them to each method automatically.
Gradual Ownership (Phase 8.2) ✅ Implemented
Bux introduces gradual ownership — opt-in borrow checking. By default, Bux is permissive like C. With @[Checked], the borrow checker enforces memory safety rules.
Syntax
// Default: permissive mode (like C/Nim) — raw pointers, no checks
func QuickSort(arr: *int, len: int) {
for i in 0..len {
arr[i] = arr[i] * 2;
}
}
// Opt-in: @[Checked] enables borrow checking
@[Checked]
func Scale(val: &mut int) {
*val = *val * 2; // OK: &mut T allows mutation
}
@[Checked]
func Read(val: &int) -> int {
return *val; // OK: &T allows reading
}
@[Checked]
func BadWrite(val: &int) {
*val = 42; // ERROR: cannot write through shared reference '&T'
}
Reference types
| Type | Syntax | Description |
|---|---|---|
| Raw pointer | *T |
C-style pointer, no checks |
| Shared ref | &T |
Borrowed reference (read-only in checked functions) |
| Mutable ref | &mut T |
Exclusive mutable borrow (allows mutation) |
| Owned | own T |
Ownership type — values can be moved |
Move Semantics
own T values can be moved. After a move, the original variable is uninitialized and cannot be used until reassigned.
@[Checked]
func Process(data: own String) {
PrintLine(data);
// data is consumed here
}
@[Checked]
func Main() {
let msg: own String = "hello";
Process(msg); // move: msg is now uninitialized
// PrintLine(msg); // ERROR: use after move
msg = "reassigned"; // OK: reinitialization
PrintLine(msg);
}
Moves happen in three contexts:
- Function call argument:
Process(msg)movesmsginto the parameter - Assignment:
b = amovesaintob - Return:
return xmovesxout of the function
Rules in @[Checked] functions
&Tcannot be used to mutate data (compile-time error)&mut Tallows mutation*Tpointers are unrestricted (escape hatch)&mut Tcoerces to&Tand*T- Double mutable borrow: passing
&mut xtwice to the same call is an errorSwap(&mut x, &mut x); // ERROR: double mutable borrow of x - Use after move: using a moved
own Tvalue is an error until reassignedlet msg: own String = "hello"; Process(msg); // move PrintLine(msg); // ERROR: use of moved value msg = "reassigned"; // OK: reinitialization PrintLine(msg);
Compile-Time Function Execution (CTFE) ✅ Implemented
const func functions are evaluated at compile time. Their results can be used in type sizes, array lengths, or other constant contexts.
const func Factorial(n: int) -> int {
if n <= 1 {
return 1;
}
return n * Factorial(n - 1);
}
const TABLE_SIZE = Factorial(10); // 3628800 — computed at compile time
func Main() -> int {
let arr: [TABLE_SIZE]int; // Array size from compile-time value
return 0;
}
Supported in CTFE
- Integer, boolean, and string literals
- Arithmetic (
+,-,*,/,%) - Comparisons and logical operators
if/elsewith constant conditions- Calls to other
const funcfunctions (including recursion)
Limitations
- No
while/forloops (use recursion) - No
mutreferences or heap allocation - No non-const function calls
Error Handling
Result and Option Types
enum Result {
Ok(int),
Err(String)
}
enum Option {
Some(int),
None
}
The ? Operator
The ? operator automatically propagates errors:
func Divide(a: int, b: int) -> Result {
if b == 0 {
return Result_NewErr("division by zero");
}
return Result_NewOk(a / b);
}
func Compute() -> Result {
let x: int = Divide(10, 2)?; // If Err, returns immediately
let y: int = Divide(x, 5)?;
return Result_NewOk(y);
}
? can be used on Result and Option types in any expression context.
Modules and Imports
// Single import
import Std::Io::PrintLine;
// Multiple imports
import Std::Io::{PrintLine, PrintInt};
// Wildcard import
import Std::Io::*;
// Module declaration
module MyModule;
pub func PublicFunc() -> int {
return 42;
}
func PrivateFunc() -> int {
return 0;
}
Concurrency
Bux supports both async/await (stackful coroutines) and pthread-based threads with channels.
Threads and Channels
import Std::Task::{Task_Spawn, Task_Join, TaskHandle};
import Std::Channel::{Channel, Channel_New, Channel_SendInt, Channel_RecvInt, Channel_Close};
func Producer(ch: *Channel<int>) {
Channel_SendInt(ch, 42);
Channel_Close<int>(ch);
}
func Consumer(ch: *Channel<int>) -> int {
let val: int = Channel_RecvInt(ch);
return val;
}
func Main() -> int {
let ch: Channel<int> = Channel_New<int>(1);
let p: *void = spawn Producer(&ch);
let c: *void = spawn Consumer(&ch);
Task_Join(TaskHandle { handle: p });
Task_Join(TaskHandle { handle: c });
return 0;
}
spawn Func()creates a new pthread runningFuncChannel<T>is a buffered channel with mutex/condvarChannel_RecvIntreturns0when the channel is closed and empty
Async/Await
Bux supports stackful coroutines via async/await with a round-robin scheduler.
Declaring Async Functions
async func Compute() -> int {
PrintLine("step 1");
bux_async_yield();
PrintLine("step 2");
return 42;
}
Spawning Tasks
let handle = spawn Compute();
Awaiting Results
let result: int = handle.await as int;
Full Example
import Std::Io::{PrintLine, PrintInt};
async func Compute() -> int {
PrintLine("Compute: start");
bux_async_yield();
PrintLine("Compute: done");
return 42;
}
func Main() -> int {
let h = spawn Compute();
let r: int = h.await as int;
PrintInt(r);
return 0;
}
Runtime Functions
| Function | Description |
|---|---|
bux_async_yield() |
Yield control to the scheduler |
bux_async_spawn(fn) |
Create a new coroutine from a function |
bux_async_await(handle) |
Block until coroutine completes, return result |
bux_async_run() |
Run the scheduler (called implicitly from main) |
bux_async_sleep(ms) |
Sleep for ms milliseconds (non-blocking) |
bux_async_return(value, size) |
Copy return value into task result buffer |
Operator Overloading
Status: ✅ Implemented in bootstrap. Selfhost reserves syntax but has no method-table yet.
Overloadable operators use the naming convention TypeName_operator_<op>:
| Operator | Function Name | Signature Example |
|---|---|---|
+ |
operator_add |
func T_operator_add(self: *T, other: T) -> T |
- |
operator_sub |
func T_operator_sub(self: *T, other: T) -> T |
* |
operator_mul |
func T_operator_mul(self: *T, other: T) -> T |
/ |
operator_div |
func T_operator_div(self: *T, other: T) -> T |
% |
operator_mod |
func T_operator_mod(self: *T, other: T) -> T |
== |
operator_eq |
func T_operator_eq(self: *T, other: T) -> bool |
!= |
operator_ne |
func T_operator_ne(self: *T, other: T) -> bool |
< |
operator_lt |
func T_operator_lt(self: *T, other: T) -> bool |
<= |
operator_le |
func T_operator_le(self: *T, other: T) -> bool |
> |
operator_gt |
func T_operator_gt(self: *T, other: T) -> bool |
>= |
operator_ge |
func T_operator_ge(self: *T, other: T) -> bool |
[] (get) |
operator_index_get |
func T_operator_index_get(self: *T, idx: int) -> U |
[] (set) |
operator_index_set |
func T_operator_index_set(self: *T, idx: int, value: U) |
Notes:
- Short-circuit operators (
&&,||) cannot be overloaded. - Generic method instantiation is supported.
Operators
Arithmetic
+, -, *, /, %, ** (power)
Comparison
==, !=, <, <=, >, >=
Logical
&&, ||, !
Bitwise
&, |, ^, ~, <<, >>
Assignment
=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
Other
as— Cast:expr as Typeis— Type test:expr is Type?— Try / error propagation:expr?&— Address-of:&var*— Dereference:*ptr::— Path separator:Module::Name..— Range (exclusive):0..10..=— Range (inclusive):0..=10sizeof— Size of type:sizeof(Type)