Language Specification
The canonical language specification for Braid. This is the definitive reference covering all syntax rules, type rules, semantics, and the execution model.
1. Lexical Grammar
1.1 Character Set
Braid source files are UTF-8 encoded. The lexer recognizes ASCII letters, digits, underscores, and the following special characters: ( ) [ ] . , ; : = + - * / % ! < > | & @ # ' " _. Comments and string literals may contain any UTF-8 character.
1.2 Comments
Three comment styles are supported:
# Hash comment to end of line
// C++ style comment to end of line
/* Block comment spanning
multiple lines */1.3 Identifiers
Identifiers begin with a letter (a-z, A-Z) or underscore (_), followed by zero or more letters, digits, or underscores. Identifiers are case-sensitive. Keywords are reserved and cannot be used as identifiers.
valid: x, _temp, myVar, FooBar123
invalid: 123abc, x-y, x.y, if1.4 Literals
Integer: 42, 0, -17, 1000000
Float: 3.14, 0.5, -2.718, 100.0
String: "hello", 'world', "nested 'quotes'"
Boolean: true, false
Null: nilInteger literals are 64-bit signed integers. Float literals are 64-bit IEEE 754 double precision. String literals support single and double quotes. Boolean literals are true and false. The null value is nil.
1.5 Keywords (Reserved Words)
The following identifiers are reserved as keywords:
fn let if else while return
struct enum match import extern impl
diameter pole observe evolve native model
true false with async await spawn
deviceAdditionally, @autograd, @layer, and @param are decorator tokens used as prefixes.
2. Syntax Grammar
2.1 Program Structure
A Braid program consists of a sequence of declarations and statements at the top level. Declarations include function definitions, struct definitions, enum definitions, diameter definitions, imports, extern FFI declarations, native FFI declarations, model definitions, decorators, and impl blocks. Statements include variable declarations, return statements, if/else, while loops, match expressions, assignments, and expression statements.
2.2 Declarations
Function Declaration
fn name(param1: Type1, param2: Type2, ...) -> ReturnType {
// body
}The fn keyword declares a named function. Parameters are comma-separated name: type pairs. The return type is specified with -> Type and is optional — if omitted, the function returns nil. The function body is a block enclosed in braces. Anonymous functions (closures) omit the name: fn(x: int) -> int { return x * 2 }.
Variable Declaration
let name = value
let name: Type = valueThe let keyword introduces a new binding. The type annotation is optional — the type is inferred from the initializer. Once bound, the variable can be reassigned with = but cannot be redeclared.
Struct Declaration
struct Point {
x: int;
y: int;
}Defines a product type with named fields. Each field has a name and type. Fields are accessed with dot notation: point.x. Struct literals use named field initialization: Point { x: 10, y: 20 }. Maximum 16 fields per struct.
Enum Declaration
enum Color {
Red, Green, Blue
}Defines a sum type with named variants. Variants are accessed with dot notation: Color.Red. Enums support matching via match expressions.
Import Declaration
import std.io
import std.math
import ui.widgetImports a module by dot-separated path. The compiler searches for .br or .bx files matching the path relative to the import search paths.
Extern FFI Declaration
extern fn sqrt(x: float) -> float
extern fn srand(seed: int)Binds to an external C function at link time. The function signature must match the C declaration.
Native FFI Declaration
native fn print_int(val: int)
native fn print_float(val: float)Declares a runtime-linked FFI function implemented in the BraidVM C runtime. Invoked via the OP_INVOKE_FFI bytecode instruction.
Diameter Declaration
diameter name: {
pole pole_a: {
return value_a
}
pole pole_b: {
return value_b
}
}Defines a dialectical reasoning construct with two opposing poles. Each pole is a block that returns a numeric value representing its force. The diameter maintains an internal state that evolves based on the tension between poles.
Model Definition
model name = BaseModel {
param1: value1;
param2: value2;
}Defines an ML model configuration by instantiating a base model with hyperparameters. Each parameter is a key-value pair terminated by a semicolon.
Impl Block
impl TypeName {
fn method(self, ...) -> Type {
// body
}
}Implements methods for a struct type. Methods receive self as the first parameter, which refers to the instance the method is called on.
Decorators
@autograd fn loss_fn(x: float, y: float) -> float { ... }
@layer struct Linear {
@param weight = tensor_init(128, 64);
fn forward(self, x: tensor) -> tensor { ... }
}@autograd marks a function for automatic differentiation. @layer marks a struct as a neural network layer. @param declares a trainable parameter inside a layer.
2.3 Statements
Block
{
statement1;
statement2;
}A block is a sequence of statements enclosed in braces. Blocks create a new scope — variables declared inside a block are not visible outside.
Return Statement
return;
return expression;Exits the current function. If no expression is given, nil is returned.
If/Else Statement
if condition { ... }
if condition { ... } else { ... }
if condition { ... } else if condition { ... } else { ... }The condition must evaluate to bool. The else branch is optional and can chain additional if conditions.
While Loop
while condition {
body;
}Repeatedly executes the body while the condition is true. The condition must evaluate to bool.
Match Expression
match expr {
pattern1 => expression_or_block,
pattern2 => {
body
},
_ => default
}Pattern matching on integers, strings, booleans, enums, and nil. The wildcard _ matches any value. Each case can be a single expression (optionally comma-terminated) or a block.
Assignment
variable = expression
struct.field = expression
array[index] = expressionReassigns a value to an existing mutable binding, struct field, or indexed element.
2.4 Expressions
Operator Precedence (Highest to Lowest)
| Precedence | Operators | Assoc |
|---|---|---|
| 1 | () [] . | Left |
| 2 | - ! | Right |
| 3 | * / % | Left |
| 4 | + - | Left |
| 5 | < <= > >= == != | Left |
| 6 | && | Left |
| 7 | || | Left |
| 8 | = | Right |
Primary Expressions
integer_literal // 42
float_literal // 3.14
string_literal // "hello"
bool_literal // true, false
nil_literal // nil
identifier // x, myVar
struct_literal // Point { x: 10, y: 20 }
array_literal // [1, 2, 3]
tensor_literal // [[1, 2], [3, 4]]
fn_expression // fn(x: int) -> int { return x * 2 }
grouped_expr // (expression)
evolve_expr // evolve(diameter)
observe_expr // observe(diameter)Postfix Operations
expr.identifier // field access, method call
expr(args) // function call
expr[index] // array/tensor index
expr[start:end] // tensor slice
expr[..., dim] // tensor slice with ellipsisTensor Slice Syntax
tensor[i] // single index
tensor[i:j] // range slice [i, j)
tensor[i:j:k] // range slice with step
tensor[..., i:j] // ellipsis for all preceding dims
tensor[i, j] // multi-dimensional indexWith Device Expression
with device("cuda") {
// tensor operations here use GPU
}Device Transfer
let gpu = tensor.to("cuda")
let cpu = tensor.to("cpu")3. Type System
3.1 Primitive Types
| Type | Description | Size |
|---|---|---|
| int | 64-bit signed integer | 8 bytes |
| float | 64-bit IEEE 754 double | 8 bytes |
| bool | Boolean (true/false) | 1 byte |
| string | UTF-8 string (heap-allocated) | Variable |
| nil | Null/void type | 0 bytes |
3.2 Composite Types
- Struct types — Product types with named fields. Each field has a name and a type. Structs are value types (copied on assignment) but fields containing heap objects (strings, arrays, tensors) are reference-counted.
- Enum types — Sum types with named variants. Each variant is a distinct value. Enum comparison uses variant identity.
- Array types — Dynamic arrays of homogeneous elements. Denoted
[Type]in type annotations. Elements are accessed by integer index. - Tensor types — N-dimensional arrays with shape and dtype. Denoted by
[[...]]syntax. Supports 0-d to 8-d tensors. - Function types — Denoted
(Type1, Type2) -> ReturnType. Functions are first-class values (closures). - Diameter types — Dialectical reasoning objects with internal state, two poles, and tension/resonance properties.
3.3 Type Inference
Braid uses Hindley-Milner type inference. Types are inferred from context without requiring explicit annotations, except on function parameters (required) and struct fields (required). Type variables are resolved through unification, and type errors are reported at compile time.
3.4 Type Checking Rules
- Arithmetic operators (
+ - * / %): Both operands must beintor bothfloat. Mixed arithmetic is not allowed (no implicit casting). - Comparison operators (
< <= > >=): Both operands must beintor bothfloat. Return type isbool. - Equality operators (
== !=): Operands must be the same type. Works on all types including structs, enums, and nil. - Logical operators (
&& || !): Both operands must bebool. - Assignment (
=): The assigned value must be assignable to the target type (same type or nil-compatible). - If/While conditions: Must evaluate to
bool. - Return types: The returned expression must match the function's declared return type.
4. Evaluation Semantics
4.1 Expression Evaluation
Expressions are evaluated eagerly left-to-right. Binary operators short-circuit for logical && and ||. Function arguments are evaluated in order before the function call. Struct field initializers are evaluated in declaration order.
4.2 Function Calls
Function calls push a new stack frame. Arguments are passed by value (for primitives) or by reference-counted pointer (for objects). The callee returns a value via return, which is placed on the stack for the caller. Tail calls are optimized by the supercompiler.
4.3 Closure Semantics
Closures capture variables from the enclosing scope by reference (upvalues). The compiler emits OP_CLOSURE with OP_GET_UPVALUE/OP_SET_UPVALUE instructions. Captured variables remain live as long as any closure references them.
4.4 Diameter Semantics
A Diameter maintains an internal state vector S = (value, tension, resonance). The evolve() operation updates value by interpolating between pole_a and pole_b based on current tension. Tension evolves according to the difference between pole outputs — larger differences increase tension. Resonance measures the stability of tension over time. The observe() operation reads the current state properties.
4.5 Tensor Semantics
Tensor literals allocate N-dimensional arrays with type inference from element types. Indexing returns a scalar (zero-dimensional tensor) for individual elements or a sub-tensor for slices. Slicing returns a view (no data copy) when possible. Device transfer (.to("cuda")) moves data to GPU memory. Operations within with device() blocks execute on the specified device.
4.6 Autograd Semantics
Functions decorated with @autograd build a computation graph during the forward pass. Each operation records its inputs and creates a gradient function. The .backward() call traverses the graph in reverse, applying the chain rule to compute gradients. Trainable parameters (@param) accumulate gradients in .grad fields.
5. Memory Model
5.1 Automatic Reference Counting (ARC)
Every heap-allocated object (ObjString, ObjStruct, ObjArray, ObjFunction, ObjClosure, ObjDiameter, ObjTensor) has a ref_count field. When an object is referenced, the count is incremented. When a reference is removed (out of scope, reassignment), the count is decremented. When the count reaches zero, the object is immediately freed.
5.2 Cycle Detection
ARC cannot handle reference cycles. Braid's runtime includes a cycle collector that periodically traverses the object graph, marking reachable objects and collecting unreachable cycles. The marked and is_old fields in the object header support this.
5.3 Generational GC
Optionally, Braid can use a generational GC strategy. Objects are initially allocated as young (is_old = false). Survivors of young collections are promoted to old (is_old = true). Young collections scan only the young generation; full collections scan all objects.
5.4 Value Types vs Reference Types
Primitives (int, float, bool) are value types — they are copied by value. nil is a singleton. All other types are reference-counted heap objects: strings, structs (when they contain heap fields), arrays, functions, closures, diameters, tensors, maps, and bound methods.
6. Module System
6.1 Module Resolution
Modules are resolved by converting dot-separated import paths to filesystem paths. import std.io searches for std/io.br or std/io.bx relative to the current project's import paths. Standard library modules are under std/, UI modules under ui/.
6.2 Compilation Units
Each .br file is a compilation unit. A program may consist of multiple files connected via import declarations. The compiler resolves all imports transitively.
7. Compilation Model
7.1 Pipeline Stages
Source (.br)
|-- [Lexer] --> Token stream
|-- [Parser] --> AST
|-- [Type Checker] --> Typed AST (Hindley-Milner)
|-- [Supercompiler] --> Optimized AST
|-- [C Codegen] --> C source code
| OR
|-- [IR Lowering] --> Braid IR
|-- [IR Verifier] --> Verified IR
|-- [MLIR Backend] (stub)
|-- [LLVM Backend] (stub)
|-- [Bytecode Build] --> .bx bytecode
v
Native executable or bytecode7.2 Bytecode Format
.bx header:
Offset Size Field
0 4 Magic: 'BRDC' (0x42524443)
4 4 Version (uint32)
8 4 Target kind (1=LLVM IR, 2=bytecode)
12 4 Payload size (bytes)
16 n Payload data7.3 Compilation Commands
| Command | Output |
|---|---|
| braidc parse file.br | Syntax validation |
| braidc ast file.br | AST tree |
| braidc ir file.br | SSA IR |
| braidc ccodegen file.br | C source |
| braidc build file.br -o out.bx | .bx bytecode |
| braidc run file.br | Execute |
8. Standard Library (Planned)
| Module | Contents |
|---|---|
| std.io | print, read, file I/O |
| std.math | Math functions, constants |
| std.time | Dates, timers, Duration |
| std.async | Async runtime, spawn, channels |
| ui.widget | Widget primitives (Bond) |
| ui.layout | Layout primitives |
| ui.render | Rendering engine |
| ui.theme | Theming system (Blend) |
9. Concurrency Model
Braid's concurrency model is based on green threads (user-space threads managed by the runtime). The spawn keyword creates a new green thread. Communication is via shared memory with ARC safety. Async/await support for cooperative multitasking is planned via std.async. Channels and message passing are also planned.
10. FFI Model
Braid supports two FFI mechanisms:
- Extern FFI (
extern fn): Compile-time linking to C functions. The C codegen backend emits direct C function calls. Types must match C —intmaps toint64_t,floattodouble,stringtochar*. - Native FFI (
native fn): Runtime linking to functions built into the BraidVM. Invoked viaOP_INVOKE_FFIbytecode instruction. Supports variable argument counts.