Ai Guide
AXON AI Programming Guide
Section titled “AXON AI Programming Guide”The complete reference for AI agents writing AXON programs.
After reading this document, you will be able to write any AXON program. This is the only document you need.
Table of Contents
Section titled “Table of Contents”- What is AXON?
- Source Format
- Program Structure
- Type Reference
- Expression Reference
- Operator Reference
- Declaration Reference
- Module System & Imports
- Complete Examples
- Cookbook: Common Patterns
- Compilation & Execution
- Anti-Patterns: What NOT To Do
- Performance Tips
- Memory Layout
- QBE IL Patterns
- Debugging Guide
- Quick Reference Card
- Cross-Platform GUI
1. What is AXON?
Section titled “1. What is AXON?”AXON is a compiled, statically-typed programming language that uses S-expression syntax (fully parenthesized prefix notation). It compiles to native code via QBE or LLVM backends.
Critical rules:
- Every literal must have a type annotation. Write
(i64 42), never just42. - Every expression has a known type. There are no implicit conversions.
- All type conversions are explicit. Use
(cast target_type expr). - Variables are mutable. Introduced by
let, updated byset. - Functions are called with
call. Write(call func_name args...). - The entry point is
main. It must returni32.
2. Source Format
Section titled “2. Source Format”AXON source files use the .axs extension and contain S-expressions.
S-Expression Basics
Section titled “S-Expression Basics”Everything is a parenthesized list in prefix notation:
(operator operand1 operand2)Examples:
(add x y) ;; x + y(mul (i64 3) (i64 7)) ;; 3 * 7(call fib (i64 10)) ;; fib(10)Comments
Section titled “Comments”Line comments start with ;; and extend to end of line:
;; This is a comment(i64 42) ;; inline commentWhitespace
Section titled “Whitespace”Any amount of whitespace (spaces, tabs, newlines) between tokens. Indentation is not significant.
3. Program Structure
Section titled “3. Program Structure”Every AXON program is a module containing declarations:
(module ;; declarations go here )A valid executable module must contain a main function that returns i32:
(module (fn main () i32 (i32 0)))Declaration Order
Section titled “Declaration Order”Declaration order does not matter. Functions can call functions declared later in the file.
4. Type Reference
Section titled “4. Type Reference”4.1 Primitive Types
Section titled “4.1 Primitive Types”| Type | Size | Range / Description |
|---|---|---|
void | 0 | No value |
bool | 1 byte | true or false |
i8 | 1 byte | -128 to 127 |
i16 | 2 bytes | -32,768 to 32,767 |
i32 | 4 bytes | -2,147,483,648 to 2,147,483,647 |
i64 | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
u8 | 1 byte | 0 to 255 |
u16 | 2 bytes | 0 to 65,535 |
u32 | 4 bytes | 0 to 4,294,967,295 |
u64 | 8 bytes | 0 to 18,446,744,073,709,551,615 |
f32 | 4 bytes | 32-bit IEEE 754 floating point |
f64 | 8 bytes | 64-bit IEEE 754 floating point |
4.2 Compound Types
Section titled “4.2 Compound Types”| Type | Syntax | Example |
|---|---|---|
| Pointer | (ptr T) | (ptr i8) — pointer to byte |
| Array | (arr T N) | (arr i32 10) — 10-element i32 array |
| Slice | (slice T) | (slice u8) — byte slice (ptr + len) |
| Struct | Name | Point — user-defined struct type |
4.3 Type Annotations on Literals
Section titled “4.3 Type Annotations on Literals”Every literal value must be wrapped in a type annotation:
(i64 42) ;; integer 42 as i64(i32 -7) ;; integer -7 as i32(u8 255) ;; integer 255 as u8(f64 3.14) ;; float 3.14 as f64(f32 1.0) ;; float 1.0 as f32(bool true) ;; boolean true(bool false) ;; boolean false(str "hello") ;; string literal (type: (ptr i8))You must NEVER write a bare literal. 42 by itself is a variable reference to something named 42, which will fail.
5. Expression Reference
Section titled “5. Expression Reference”5.1 Typed Literals
Section titled “5.1 Typed Literals”(i64 42) ;; 64-bit signed integer(i32 0) ;; 32-bit signed integer(u8 0xFF) ;; 8-bit unsigned integer (hex)(u32 0b10101010) ;; 32-bit unsigned integer (binary)(f64 3.14159) ;; 64-bit float(f32 2.718) ;; 32-bit float(f64 1e-10) ;; scientific notation(bool true) ;; boolean(str "Hello, World!") ;; string (type: (ptr i8))5.2 Variable References
Section titled “5.2 Variable References”Bare identifiers reference variables in scope:
x ;; reference variable xcounter ;; reference variable countermy_value ;; reference variable my_valueA variable must be defined before it can be referenced — either as a function parameter or via a let binding.
5.3 Binary Operations
Section titled “5.3 Binary Operations”(add a b) ;; a + b (integer or float)(sub a b) ;; a - b (integer or float)(mul a b) ;; a * b (integer or float)(div a b) ;; a / b (integer or float)(mod a b) ;; a % b (integer only)
(eq a b) ;; a == b → bool(ne a b) ;; a != b → bool(lt a b) ;; a < b → bool(le a b) ;; a <= b → bool(gt a b) ;; a > b → bool(ge a b) ;; a >= b → bool
(band a b) ;; a & b (bitwise AND, integer only)(bor a b) ;; a | b (bitwise OR, integer only)(bxor a b) ;; a ^ b (bitwise XOR, integer only)(shl a b) ;; a << b (shift left, integer only)(shr a b) ;; a >> b (shift right, integer only)
(land a b) ;; a && b (logical AND, short-circuit, bool only)(lor a b) ;; a || b (logical OR, short-circuit, bool only)Both operands must have the same type. The result type matches the operand type, except for comparison operators which always return bool.
5.4 Unary Operations
Section titled “5.4 Unary Operations”(neg x) ;; -x (arithmetic negation, integer or float)(not x) ;; !x (logical NOT, bool → bool)(bnot x) ;; ~x (bitwise NOT, integer only)5.5 Function Calls
Section titled “5.5 Function Calls”(call function_name arg1 arg2 ...)Examples:
(call fib (i64 35)) ;; one argument(call add (i64 3) (i64 7)) ;; two arguments(call print_i64 x) ;; one variable argument(call print_str (str "hello")) ;; string argument(call main) ;; no argumentsArgument types must match the function’s parameter types exactly.
5.6 If Expressions
Section titled “5.6 If Expressions”;; if-then-else (produces a value)(if condition then_expr else_expr)
;; if-then only (void, no value)(if condition then_expr)The condition must be bool. When both branches are present, they must have the same type.
Examples:
;; Returns the larger of a and b(if (gt a b) a b)
;; Conditional with complex expressions(if (lt n (i64 2)) n (add (call fib (sub n (i64 1))) (call fib (sub n (i64 2)))))
;; Void if (no else branch)(if (gt x (i64 0)) (call print_i64 x))5.7 While Loops
Section titled “5.7 While Loops”(while condition body)The condition must be bool. The body is evaluated repeatedly while the condition is true. While loops have type void.
(while (gt n (i64 0)) (block (call print_i64 n) (set n (sub n (i64 1)))))5.8 Let Bindings (Variable Introduction)
Section titled “5.8 Let Bindings (Variable Introduction)”;; Without type annotation (type inferred from initializer)(let (name init_expr) body_expr)
;; With type annotation(let (name type init_expr) body_expr)let introduces a new mutable variable. The variable is visible in the body expression. The value of the let expression is the value of the body.
;; Introduce x = 42, then compute x + 1(let (x (i64 42)) (add x (i64 1)))
;; With explicit type(let (x i64 (i64 42)) (add x (i64 1)))
;; Nested let bindings(let (a (i64 10)) (let (b (i64 20)) (add a b)))5.9 Set (Mutation)
Section titled “5.9 Set (Mutation)”(set variable_name new_value)Updates a previously-introduced variable. The new value must have the same type.
(set x (i64 5))(set counter (add counter (i64 1)))(set result (mul result n))5.10 Block Expressions
Section titled “5.10 Block Expressions”(block expr1 expr2 ... exprN)Evaluates expressions in sequence. The value of the block is the value of the last expression. All preceding expressions are evaluated for side effects.
(block (call print_i64 (i64 1)) (call print_i64 (i64 2)) (i64 42)) ;; block value is 425.11 Return
Section titled “5.11 Return”(ret expr) ;; return a value(ret) ;; return voidReturns from the enclosing function. The return value must match the function’s return type.
(fn main () i32 (ret (i32 0)))5.12 Type Cast
Section titled “5.12 Type Cast”(cast target_type expr)Converts a value from one type to another. Only certain casts are valid:
(cast i64 (i32 42)) ;; i32 → i64 (sign-extend)(cast i32 (i64 100000)) ;; i64 → i32 (truncate)(cast f64 (i64 42)) ;; i64 → f64 (int-to-float)(cast i64 (f64 3.14)) ;; f64 → i64 (float-to-int, truncates)(cast u64 (i64 -1)) ;; i64 → u64 (reinterpret sign)(cast f64 (f32 1.0)) ;; f32 → f64 (widen)5.13 Array and Struct Operations
Section titled “5.13 Array and Struct Operations”;; Array indexing(idx my_array (i64 3))
;; Struct field access(fld my_point x)
;; Address-of(addr my_var)
;; Dereference pointer(deref my_ptr)
;; Array literal(arr.new i64 (i64 1) (i64 2) (i64 3))
;; Struct literal(new Point (x (f64 1.0)) (y (f64 2.0)))5.14 Defer (Scope-Based Cleanup)
Section titled “5.14 Defer (Scope-Based Cleanup)”(defer expr)Registers expr to run when the enclosing function exits. Use defer to guarantee cleanup regardless of how the function returns.
Basic Usage
Section titled “Basic Usage”;; Acquire a resource, defer its release(fn read_file ((path (ptr i8))) i64 (let (fd (call open_file path)) (block (defer (call close_file fd)) ;; guaranteed to run at function exit (call process_data fd)))) ;; even if process_data fails via tryLIFO Ordering
Section titled “LIFO Ordering”Multiple defers execute in last-in, first-out order (like Go’s defer). The most recently registered defer runs first:
;; Output: 1, 2, 3(fn lifo_demo () void (block (defer (call print_i64 (i64 3))) ;; registered first → runs last (defer (call print_i64 (i64 2))) ;; registered second → runs second (defer (call print_i64 (i64 1)))) ;; registered third → runs firstThis is intuitive for resource cleanup — resources acquired later are released first:
(fn multi_resource () void (let (db (call open_db)) (block (defer (call close_db db)) ;; closes second (let (tx (call begin_transaction db)) (block (defer (call end_transaction tx)) ;; closes first (call do_work tx))))))Defer with Return and Error Propagation
Section titled “Defer with Return and Error Propagation”Deferred expressions run at every function exit point:
(ret ...)— return value is evaluated first, then defers run, then return- Implicit return (end of function body) — defers run after the last expression
(try ...)error propagation — defers run before the error propagates
;; Defer runs on both early return and normal exit(fn safe_compute ((x i64)) i64 (block (defer (call cleanup)) (if (lt x (i64 0)) (ret (i64 -1))) ;; cleanup runs before returning -1 (mul x x))) ;; cleanup also runs on normal exit
;; Defer with try/result — cleanup on error path(fn read_file ((path (ptr i8))) (result i64 i64) (block (let (fd (try (call open_file path))) ;; if open fails, no defer yet (block (defer (call close_file fd)) ;; registered after successful open (try (call read_data fd)))))) ;; if read fails, close_file still runsKey Rules
Section titled “Key Rules”deferhas typevoid.- Defer is per-function scope — a defer inside a
block,if, orwhilestill runs at function exit, not block exit. - The deferred expression is not evaluated when
deferis encountered — it is saved and runs later. - Deferred expressions can reference variables that were in scope at the
defersite.
6. Operator Reference
Section titled “6. Operator Reference”6.1 Binary Operators — Complete Table
Section titled “6.1 Binary Operators — Complete Table”| Operator | Meaning | Operand Types | Result Type | Example |
|---|---|---|---|---|
add | Addition | integer, float | same | (add x y) |
sub | Subtraction | integer, float | same | (sub x (i64 1)) |
mul | Multiplication | integer, float | same | (mul a b) |
div | Division | integer, float | same | (div total count) |
mod | Remainder | integer | same | (mod n (i64 2)) |
eq | Equal | any comparable | bool | (eq x (i64 0)) |
ne | Not equal | any comparable | bool | (ne x y) |
lt | Less than | integer, float | bool | (lt n (i64 2)) |
le | Less or equal | integer, float | bool | (le x max) |
gt | Greater than | integer, float | bool | (gt a b) |
ge | Greater or equal | integer, float | bool | (ge len (i64 0)) |
band | Bitwise AND | integer | same | (band flags mask) |
bor | Bitwise OR | integer | same | (bor a b) |
bxor | Bitwise XOR | integer | same | (bxor x y) |
shl | Shift left | integer | same | (shl x (i64 3)) |
shr | Shift right | integer | same | (shr x (i64 1)) |
land | Logical AND | bool | bool | (land cond1 cond2) |
lor | Logical OR | bool | bool | (lor a b) |
6.2 Unary Operators — Complete Table
Section titled “6.2 Unary Operators — Complete Table”| Operator | Meaning | Operand Type | Result Type | Example |
|---|---|---|---|---|
neg | Negate | integer, float | same | (neg x) |
not | Logical NOT | bool | bool | (not done) |
bnot | Bitwise NOT | integer | same | (bnot mask) |
7. Declaration Reference
Section titled “7. Declaration Reference”7.1 Function Declaration
Section titled “7.1 Function Declaration”(fn name ((param1 type1) (param2 type2) ...) return_type body_expression)Every function has:
- A unique name
- Zero or more typed parameters
- A return type
- A single body expression (use
blockfor multiple statements)
;; No parameters(fn main () i32 (i32 0))
;; One parameter(fn square ((x i64)) i64 (mul x x))
;; Multiple parameters(fn add ((a i64) (b i64)) i64 (add a b))
;; Void return type(fn print_greeting () void (call print_str (str "Hello!")))
;; Complex body with block(fn compute ((n i64)) i64 (block (call print_str (str "Computing...")) (let (result (mul n n)) (block (call print_i64 result) result))))7.2 Extern Declaration
Section titled “7.2 Extern Declaration”Import external C functions:
(extern name ((param1 type1) ...) return_type)For variadic functions, add ... after the last parameter:
;; Import C's puts(extern puts ((s (ptr i8))) i32)
;; Import C's printf (variadic)(extern printf ((fmt (ptr i8)) ...) i32)
;; Import C's exit(extern exit ((code i32)) void)7.3 Struct Declaration
Section titled “7.3 Struct Declaration”(struct name ((field1 type1) (field2 type2) ...))(struct Point ((x f64) (y f64)))(struct Rect ((origin Point) (width f64) (height f64)))(struct Node ((value i64) (next (ptr Node))))7.4 Constant Declaration
Section titled “7.4 Constant Declaration”Define compile-time constants:
(const name type value_expr)Constants are evaluated at compile time and can be referenced anywhere in the module:
;; Numeric constants(const PI f64 (f64 3.14159265358979))(const MAX_SIZE u32 (u32 1024))(const ZERO i64 (i64 0))
;; Usage in functions(fn circle_area ((r f64)) f64 (mul PI (mul r r)))8. Module System & Imports
Section titled “8. Module System & Imports”AXON supports splitting programs across multiple .axs files using imports. There are two import forms: namespaced import and selective import.
8.1 Namespaced Import — (import alias "path")
Section titled “8.1 Namespaced Import — (import alias "path")”Import an entire module under a namespace alias. Access its functions with alias.function_name:
;; main.axs(module (import math "math.axs")
(fn main () i32 (block (call print_i64 (call math.square (i64 5))) ;; prints 25 (call print_i64 (call math.cube (i64 3))) ;; prints 27 (i32 0))));; math.axs — the imported library module(module (pub (fn square ((x i64)) i64 (mul x x)))
(pub (fn cube ((x i64)) i64 (mul x (mul x x)))))Key points:
- The alias (
math) is any valid identifier you choose. - The string (
"math.axs") is the file path, relative to the importing file. - Use dot notation to call:
(call math.square ...).
8.2 Selective Import — (use "path" (names...))
Section titled “8.2 Selective Import — (use "path" (names...))”Import specific functions directly into scope — no qualifier needed:
;; main.axs(module (use "utils.axs" (helper_fn format_value))
(fn main () i32 (block ;; Call directly — no namespace prefix (call print_i64 (call helper_fn (i64 42))) (call print_i64 (call format_value (i64 100))) (i32 0))));; utils.axs(module (pub (fn helper_fn ((x i64)) i64 (add x (i64 1))))
(pub (fn format_value ((x i64)) i64 (mul x (i64 10)))))8.3 Making Functions Public — (pub (fn ...))
Section titled “8.3 Making Functions Public — (pub (fn ...))”By default, all functions are private. Wrap a function declaration in (pub ...) to make it accessible from other modules:
(module ;; PUBLIC — other modules can import and call this (pub (fn add ((a i64) (b i64)) i64 (add a b)))
;; PRIVATE — only callable within this file (fn internal_helper ((x i64)) i64 (mul x x)))If you try to call a private function from another module, you get a compile error:
error: function 'internal_helper' is private in module 'mylib'8.4 Path Resolution
Section titled “8.4 Path Resolution”Import paths are resolved relative to the importing file’s directory:
project/├── main.axs ;; (import math "lib/math.axs")└── lib/ ├── math.axs ;; (import utils "utils.axs") → resolves to lib/utils.axs └── utils.axs8.5 Recursive / Transitive Imports
Section titled “8.5 Recursive / Transitive Imports”The compiler automatically loads transitive dependencies. If a.axs imports b.axs, and b.axs imports c.axs, all three are loaded. Circular imports are detected and handled.
8.6 CLI Multi-File Mode (Flat Namespace)
Section titled “8.6 CLI Multi-File Mode (Flat Namespace)”You can also compile multiple files without import/use by listing them on the command line:
axonc a.axs b.axs -o programIn this mode, all files share a flat namespace — every function is visible to every file. No pub wrapper is needed. This mode is independent of the import system.
8.7 Complete Multi-File Example
Section titled “8.7 Complete Multi-File Example”Here is a full multi-file program:
math.axs:
(module (pub (fn square ((x i64)) i64 (mul x x)))
(pub (fn sum_to ((n i64)) i64 (let (total (i64 0)) (let (i (i64 1)) (block (while (le i n) (block (set total (add total i)) (set i (add i (i64 1))))) total))))))main.axs:
(module (import math "math.axs")
(fn main () i32 (block (call print_i64 (call math.square (i64 7))) ;; prints 49 (call print_i64 (call math.sum_to (i64 100))) ;; prints 5050 (i32 0))))Compile and run:
./build/axonc main.axs -o program./programThe compiler resolves "math.axs" relative to main.axs, loads it, and links everything together.
9. Complete Examples
Section titled “9. Complete Examples”Example 1: Hello World
Section titled “Example 1: Hello World”;; The simplest AXON program.;; Prints "Hello, World!" and exits with code 0.(module (fn main () i32 (block (call print_str (str "Hello, World!")) (i32 0))))Example 2: Basic Arithmetic
Section titled “Example 2: Basic Arithmetic”;; Demonstrates typed literals and arithmetic operators.(module (fn main () i32 (block ;; Addition: 3 + 7 = 10 (call print_i64 (add (i64 3) (i64 7))) ;; Multiplication: 6 * 9 = 54 (call print_i64 (mul (i64 6) (i64 9))) ;; Nested: (2 + 3) * (4 + 5) = 45 (call print_i64 (mul (add (i64 2) (i64 3)) (add (i64 4) (i64 5)))) (i32 0))))Example 3: Simple Function
Section titled “Example 3: Simple Function”;; A function that computes the square of a number.(module (fn square ((x i64)) i64 (mul x x))
(fn main () i32 (block (call print_i64 (call square (i64 7))) ;; prints 49 (call print_i64 (call square (i64 12))) ;; prints 144 (i32 0))))Example 4: Recursive Fibonacci
Section titled “Example 4: Recursive Fibonacci”;; Classic recursive fibonacci.;; fib(0) = 0, fib(1) = 1, fib(n) = fib(n-1) + fib(n-2)(module (fn fib ((n i64)) i64 (if (lt n (i64 2)) n (add (call fib (sub n (i64 1))) (call fib (sub n (i64 2))))))
(fn main () i32 (block (call print_i64 (call fib (i64 10))) ;; prints 55 (call print_i64 (call fib (i64 20))) ;; prints 6765 (call print_i64 (call fib (i64 35))) ;; prints 9227465 (i32 0))))Example 5: Factorial
Section titled “Example 5: Factorial”;; Recursive factorial.;; fact(0) = 1, fact(n) = n * fact(n-1)(module (fn factorial ((n i64)) i64 (if (le n (i64 1)) (i64 1) (mul n (call factorial (sub n (i64 1))))))
(fn main () i32 (block (call print_i64 (call factorial (i64 5))) ;; prints 120 (call print_i64 (call factorial (i64 10))) ;; prints 3628800 (call print_i64 (call factorial (i64 20))) ;; prints 2432902008176640000 (i32 0))))Example 6: Control Flow — If/Else
Section titled “Example 6: Control Flow — If/Else”;; Demonstrates if expressions producing values.(module (fn abs ((x i64)) i64 (if (lt x (i64 0)) (neg x) x))
(fn max ((a i64) (b i64)) i64 (if (gt a b) a b))
(fn sign ((x i64)) i64 (if (lt x (i64 0)) (i64 -1) (if (gt x (i64 0)) (i64 1) (i64 0))))
(fn main () i32 (block (call print_i64 (call abs (i64 -42))) ;; prints 42 (call print_i64 (call abs (i64 17))) ;; prints 17 (call print_i64 (call max (i64 10) (i64 20))) ;; prints 20 (call print_i64 (call sign (i64 -5))) ;; prints -1 (call print_i64 (call sign (i64 0))) ;; prints 0 (call print_i64 (call sign (i64 99))) ;; prints 1 (i32 0))))Example 7: While Loop — Sum of 1..N
Section titled “Example 7: While Loop — Sum of 1..N”;; Iterative sum using while loop and mutable variables.;; sum(n) = 1 + 2 + ... + n = n*(n+1)/2(module (fn sum_to ((n i64)) i64 (let (total (i64 0)) (let (i (i64 1)) (block (while (le i n) (block (set total (add total i)) (set i (add i (i64 1))))) total))))
(fn main () i32 (block (call print_i64 (call sum_to (i64 10))) ;; prints 55 (call print_i64 (call sum_to (i64 100))) ;; prints 5050 (call print_i64 (call sum_to (i64 1000))) ;; prints 500500 (i32 0))))Example 8: Let Bindings and Nested Expressions
Section titled “Example 8: Let Bindings and Nested Expressions”;; Demonstrates variable binding and complex nesting.;; Computes the distance formula: sqrt(dx*dx + dy*dy);; (Approximated as dx*dx + dy*dy since we don't have sqrt yet)(module (fn dist_squared ((x1 i64) (y1 i64) (x2 i64) (y2 i64)) i64 (let (dx (sub x2 x1)) (let (dy (sub y2 y1)) (add (mul dx dx) (mul dy dy)))))
(fn main () i32 (block ;; Distance² from (0,0) to (3,4) = 9 + 16 = 25 (call print_i64 (call dist_squared (i64 0) (i64 0) (i64 3) (i64 4))) ;; Distance² from (1,2) to (4,6) = 9 + 16 = 25 (call print_i64 (call dist_squared (i64 1) (i64 2) (i64 4) (i64 6))) (i32 0))))Example 9: Type Casts
Section titled “Example 9: Type Casts”;; Demonstrates explicit type conversions.(module (fn main () i32 (block ;; Integer widening: i32 → i64 (let (small (i32 42)) (let (big (cast i64 small)) (call print_i64 big))) ;; prints 42
;; Integer to float (let (n (i64 100)) (let (f (cast f64 n)) (call print_f64 f))) ;; prints 100.0
;; Float to integer (truncates) (let (pi (f64 3.14159)) (let (truncated (cast i64 pi)) (call print_i64 truncated))) ;; prints 3
(i32 0))))Example 10: Calling External C Functions
Section titled “Example 10: Calling External C Functions”;; Demonstrates importing and calling C standard library functions.(module ;; Import C's puts function (extern puts ((s (ptr i8))) i32)
;; Import C's exit function (extern exit ((code i32)) void)
(fn main () i32 (block ;; Call puts directly (call puts (str "Direct call to C's puts!"))
;; Conditional exit (let (should_exit (bool false)) (if (eq should_exit (bool true)) (call exit (i32 1))))
(call puts (str "Program completed normally.")) (i32 0))))Example 11: Multiple Functions — FizzBuzz
Section titled “Example 11: Multiple Functions — FizzBuzz”;; FizzBuzz: print Fizz, Buzz, FizzBuzz, or the number for 1..N.;; Since AXON doesn't have string interpolation, we print indicators.(module (fn fizzbuzz ((n i64)) void (let (i (i64 1)) (while (le i n) (block (if (eq (mod i (i64 15)) (i64 0)) (call print_str (str "FizzBuzz")) (if (eq (mod i (i64 3)) (i64 0)) (call print_str (str "Fizz")) (if (eq (mod i (i64 5)) (i64 0)) (call print_str (str "Buzz")) (call print_i64 i)))) (set i (add i (i64 1)))))))
(fn main () i32 (block (call fizzbuzz (i64 30)) (i32 0))))Example 12: Iterative Fibonacci (Efficient)
Section titled “Example 12: Iterative Fibonacci (Efficient)”;; Iterative fibonacci using a while loop — O(n) instead of O(2^n).(module (fn fib_iter ((n i64)) i64 (if (lt n (i64 2)) n (let (a (i64 0)) (let (b (i64 1)) (let (i (i64 2)) (block (while (le i n) (let (tmp (add a b)) (block (set a b) (set b tmp) (set i (add i (i64 1)))))) b))))))
(fn main () i32 (block (call print_i64 (call fib_iter (i64 10))) ;; prints 55 (call print_i64 (call fib_iter (i64 50))) ;; prints 12586269025 (i32 0))))Example 13: Struct Literals and Field Access
Section titled “Example 13: Struct Literals and Field Access”;; Demonstrates struct definition, construction, and field access.(module (struct Point ((x i64) (y i64)))
(fn manhattan_distance ((p Point)) i64 (add (fld p x) (fld p y)))
(fn main () i32 (block (let (p (new Point (x (i64 3)) (y (i64 4)))) (call print_i64 (call manhattan_distance p))) ;; prints 7 (i32 0))))Example 14: Array Literals and Indexing
Section titled “Example 14: Array Literals and Indexing”;; Demonstrates array creation and element access.(module (fn sum_array () i64 (let (nums (arr.new i64 (i64 10) (i64 20) (i64 30))) (add (add (idx nums (i64 0)) (idx nums (i64 1))) (idx nums (i64 2)))))
(fn main () i32 (block (call print_i64 (call sum_array)) ;; prints 60 (i32 0))))Example 15: Constants
Section titled “Example 15: Constants”;; Demonstrates constant declarations.(module (const MAX_VAL i64 (i64 100)) (const SCALE i64 (i64 10))
(fn clamp_and_scale ((x i64)) i64 (if (gt x MAX_VAL) (mul MAX_VAL SCALE) (mul x SCALE)))
(fn main () i32 (block (call print_i64 (call clamp_and_scale (i64 50))) ;; prints 500 (call print_i64 (call clamp_and_scale (i64 200))) ;; prints 1000 (i32 0))))Example 16: Pointer Operations
Section titled “Example 16: Pointer Operations”;; Demonstrates addr (address-of) and deref (dereference).(module (fn swap_via_ptr ((a (ptr i64)) (b (ptr i64))) void (let (tmp (deref a)) (block (set (deref a) (deref b)) (set (deref b) tmp))))
(fn main () i32 (block (let (x (i64 10)) (let (y (i64 20)) (block (call print_i64 x) ;; prints 10 (call print_i64 y) ;; prints 20 (call print_i64 (deref (addr x))) ;; prints 10 (addr then deref) ))) (i32 0))))10. Cookbook: Common Patterns
Section titled “10. Cookbook: Common Patterns”Pattern 1: Accumulator (Sum of 1..N)
Section titled “Pattern 1: Accumulator (Sum of 1..N)”(fn sum_to ((n i64)) i64 (let (total (i64 0)) (let (i (i64 1)) (block (while (le i n) (block (set total (add total i)) (set i (add i (i64 1))))) total))))Pattern: Initialize accumulator → loop with mutation → return accumulator.
Pattern 2: Min/Max of Two Values
Section titled “Pattern 2: Min/Max of Two Values”(fn max ((a i64) (b i64)) i64 (if (gt a b) a b))
(fn min ((a i64) (b i64)) i64 (if (lt a b) a b))Pattern: Use if as a value-producing expression.
Pattern 3: Clamp Value to Range
Section titled “Pattern 3: Clamp Value to Range”(fn clamp ((x i64) (lo i64) (hi i64)) i64 (if (lt x lo) lo (if (gt x hi) hi x)))Pattern 4: GCD (Euclidean Algorithm)
Section titled “Pattern 4: GCD (Euclidean Algorithm)”;; Recursive GCD(fn gcd ((a i64) (b i64)) i64 (if (eq b (i64 0)) a (call gcd b (mod a b))))
;; Iterative GCD(fn gcd_iter ((a i64) (b i64)) i64 (let (x a) (let (y b) (block (while (ne y (i64 0)) (let (tmp (mod x y)) (block (set x y) (set y tmp)))) x))))Pattern 5: Exponentiation by Squaring
Section titled “Pattern 5: Exponentiation by Squaring”(fn power ((base i64) (exp i64)) i64 (let (result (i64 1)) (let (b base) (let (e exp) (block (while (gt e (i64 0)) (block (if (eq (mod e (i64 2)) (i64 1)) (set result (mul result b))) (set b (mul b b)) (set e (div e (i64 2))))) result)))))Pattern 6: Binary Search
Section titled “Pattern 6: Binary Search”;; Binary search: returns 1 if target found in sorted array-like sequence,;; 0 otherwise. This demonstrates the pattern using computed values.(fn binary_search_sum ((target i64) (n i64)) i64 ;; Search for target in the sequence 1, 2, 3, ..., n (let (lo (i64 1)) (let (hi n) (let (found (i64 0)) (block (while (le lo hi) (let (mid (div (add lo hi) (i64 2))) (if (eq mid target) (block (set found (i64 1)) (set lo (add hi (i64 1)))) ;; break: set lo > hi (if (lt mid target) (set lo (add mid (i64 1))) (set hi (sub mid (i64 1))))))) found)))))Pattern 7: String Output
Section titled “Pattern 7: String Output”;; Print multiple labeled values(fn print_labeled ((label (ptr i8)) (value i64)) void (block (call print_str label) (call print_i64 value)))
;; Usage(fn main () i32 (block (call print_labeled (str "Result:") (i64 42)) (call print_labeled (str "Count:") (i64 100)) (i32 0)))Pattern 8: Absolute Value (Generic Integer)
Section titled “Pattern 8: Absolute Value (Generic Integer)”(fn abs ((x i64)) i64 (if (lt x (i64 0)) (neg x) x))Pattern 9: Is Even / Is Odd
Section titled “Pattern 9: Is Even / Is Odd”(fn is_even ((n i64)) bool (eq (mod n (i64 2)) (i64 0)))
(fn is_odd ((n i64)) bool (ne (mod n (i64 2)) (i64 0)))Pattern 10: Countdown Loop
Section titled “Pattern 10: Countdown Loop”(fn countdown ((n i64)) void (let (i n) (while (gt i (i64 0)) (block (call print_i64 i) (set i (sub i (i64 1)))))))Pattern 11: Collatz Conjecture (3n+1)
Section titled “Pattern 11: Collatz Conjecture (3n+1)”(fn collatz_steps ((n i64)) i64 (let (x n) (let (steps (i64 0)) (block (while (ne x (i64 1)) (block (if (eq (mod x (i64 2)) (i64 0)) (set x (div x (i64 2))) (set x (add (mul x (i64 3)) (i64 1)))) (set steps (add steps (i64 1))))) steps))))11. Compilation & Execution
Section titled “11. Compilation & Execution”10.1 Compiler Invocation
Section titled “10.1 Compiler Invocation”# Basic compilation (QBE backend, debug)./build/axonc program.axs -o program./program
# LLVM backend (optimized)./build/axonc program.axs -o program --backend llvm -O2
# Type-check only (no code generation)./build/axonc program.axs --check
# Emit QBE IL to stdout./build/axonc program.axs --emit qbe
# Emit LLVM IR to stdout./build/axonc program.axs --emit llvm
# Dump AST./build/axonc program.axs --emit ast
# Dump AXON IR./build/axonc program.axs --emit ir
# Verbose mode (print pipeline stages)./build/axonc program.axs -o program --verbose10.2 Compiler Options
Section titled “10.2 Compiler Options”| Option | Description |
|---|---|
-o FILE | Output file path |
--backend qbe|llvm | Select backend (default: qbe) |
-O0, -O1, -O2 | Optimization level (default: 0) |
--check | Type-check only, don’t compile |
--emit qbe|llvm|ast|ir | Emit intermediate representation |
--target TRIPLE | Set target triple (default: auto-detect) |
--verbose | Print compilation stages |
10.3 Build System
Section titled “10.3 Build System”# Build the compilermake # debug buildmake release # optimized buildmake clean # remove build artifactsmake test # run test suitemake install # install to /usr/local/bin10.4 Runtime Library
Section titled “10.4 Runtime Library”The AXON runtime (libaxon_rt.a) is automatically linked. It provides:
print_i64(i64)— print integerprint_f64(f64)— print floatprint_str((ptr i8))— print stringprint_bool(bool)— print booleanaxon_alloc(i64)— allocate zero-initialized heap memory (used byalloc)axon_alloc_n(i64, i64)— allocate zero-initialized array (used byalloc.n)axon_free((ptr i8))— free heap memory (used byfree)
The allocation/free functions are called automatically by the compiler.
These functions are always available without extern declarations.
12. Anti-Patterns: What NOT To Do
Section titled “12. Anti-Patterns: What NOT To Do”❌ Bare literals without type annotation
Section titled “❌ Bare literals without type annotation”;; WRONG — 42 is interpreted as a variable name, not a number(add x 42)
;; CORRECT(add x (i64 42))❌ Missing parentheses around operations
Section titled “❌ Missing parentheses around operations”;; WRONG — S-expressions require parentheses around every operationadd x y
;; CORRECT(add x y)❌ Calling functions without call keyword
Section titled “❌ Calling functions without call keyword”;; WRONG — looks like a binary operator named "fib"(fib (i64 35))
;; CORRECT(call fib (i64 35))❌ Implicit type conversions
Section titled “❌ Implicit type conversions”;; WRONG — can't add i32 and i64(add (i32 1) (i64 2))
;; CORRECT — cast to matching types(add (cast i64 (i32 1)) (i64 2))❌ Using if without matching branch types
Section titled “❌ Using if without matching branch types”;; WRONG — branches have different types (i64 vs i32)(if (gt x (i64 0)) (i64 1) (i32 0))
;; CORRECT — both branches must have the same type(if (gt x (i64 0)) (i64 1) (i64 0))❌ Forgetting the module wrapper
Section titled “❌ Forgetting the module wrapper”;; WRONG — no module wrapper(fn main () i32 (i32 0))
;; CORRECT — everything must be inside (module ...)(module (fn main () i32 (i32 0)))❌ Forgetting main function
Section titled “❌ Forgetting main function”;; WRONG — no entry point(module (fn helper ((x i64)) i64 (mul x x)))
;; CORRECT — must have main returning i32(module (fn helper ((x i64)) i64 (mul x x)) (fn main () i32 (block (call print_i64 (call helper (i64 5))) (i32 0))))❌ Using non-bool conditions
Section titled “❌ Using non-bool conditions”;; WRONG — integer is not bool(if (i64 1) (call print_str (str "yes")))
;; CORRECT — use a comparison to produce bool(if (ne x (i64 0)) (call print_str (str "yes")))❌ Wrong literal syntax for booleans
Section titled “❌ Wrong literal syntax for booleans”;; WRONG — bool needs the bool type prefix(if true (i64 1) (i64 0))
;; CORRECT(if (bool true) (i64 1) (i64 0))Wait — actually bare true and false are parsed as the tokens TOK_TRUE/TOK_FALSE and can be used directly as boolean expressions in some positions. However, the safest and most explicit approach is:
;; Always safest to use comparison expressions for conditions(if (eq x (i64 0)) ...)❌ Using set before let
Section titled “❌ Using set before let”;; WRONG — x is not defined(set x (i64 42))
;; CORRECT — introduce with let first(let (x (i64 0)) (block (set x (i64 42)) x))❌ Unbalanced parentheses
Section titled “❌ Unbalanced parentheses”;; WRONG — missing closing paren(fn main () i32 (add (i64 1) (i64 2))
;; CORRECT — every ( has a matching )(fn main () i32 (add (i64 1) (i64 2)))❌ Calling a private function from another module
Section titled “❌ Calling a private function from another module”;; WRONG — internal_helper is not pub in math.axs(import math "math.axs")(call math.internal_helper (i64 5))
;; CORRECT — only call pub functions(call math.square (i64 5))❌ Forgetting pub on library functions
Section titled “❌ Forgetting pub on library functions”;; WRONG — function is private, other modules can't see it(module (fn my_api ((x i64)) i64 (mul x x)))
;; CORRECT — wrap in pub(module (pub (fn my_api ((x i64)) i64 (mul x x))))❌ Using qualified name without import
Section titled “❌ Using qualified name without import”;; WRONG — math is not imported(call math.sqrt (f64 2.0))
;; CORRECT — import first(import math "math.axs")(call math.sqrt (f64 2.0))❌ Deferring inside a loop without understanding accumulation
Section titled “❌ Deferring inside a loop without understanding accumulation”;; WRONG — defers accumulate every iteration, all run at function exit;; This registers N defers, not one per iteration.(fn process_all ((n i64)) void (let (i (i64 0)) (while (lt i n) (block (let (h (call acquire_resource i)) (defer (call release_resource h))) ;; N defers pile up! (set i (add i (i64 1)))))))
;; CORRECT — clean up explicitly in each iteration(fn process_all ((n i64)) void (let (i (i64 0)) (while (lt i n) (block (let (h (call acquire_resource i)) (block (call do_work h) (call release_resource h))) ;; explicit cleanup per iteration (set i (add i (i64 1)))))))
;; ALSO CORRECT — extract loop body to a helper function(fn process_one ((i i64)) void (let (h (call acquire_resource i)) (block (defer (call release_resource h)) ;; defer is per-function, so OK (call do_work h))))
(fn process_all ((n i64)) void (let (i (i64 0)) (while (lt i n) (block (call process_one i) (set i (add i (i64 1)))))))13. Performance Tips — Generating Fast Code
Section titled “13. Performance Tips — Generating Fast Code”13.1 Prefer Integer Operations Over Float
Section titled “13.1 Prefer Integer Operations Over Float”Integer arithmetic maps to single CPU instructions. Float operations may be slower depending on the pipeline:
;; FASTER — integer multiply(mul (i64 100) (i64 3))
;; SLOWER — float multiply (may involve FPU pipeline stalls)(mul (f64 100.0) (f64 3.0))12.2 Avoid Deep Recursion — Use Iterative Loops
Section titled “12.2 Avoid Deep Recursion — Use Iterative Loops”Recursive functions create stack frames. Iterative versions are faster for large inputs:
;; SLOW — O(2^n) recursive Fibonacci, deep stack(fn fib_slow ((n i64)) i64 (if (lt n (i64 2)) n (add (call fib_slow (sub n (i64 1))) (call fib_slow (sub n (i64 2))))))
;; FAST — O(n) iterative Fibonacci, constant stack(fn fib_fast ((n i64)) i64 (if (lt n (i64 2)) n (let (a (i64 0)) (let (b (i64 1)) (let (i (i64 2)) (block (while (le i n) (let (tmp (add a b)) (block (set a b) (set b tmp) (set i (add i (i64 1)))))) b))))))12.3 Use Bitwise Operations for Power-of-Two Math
Section titled “12.3 Use Bitwise Operations for Power-of-Two Math”Note: With
-O1, the compiler automatically convertsmul x, 2^ntoshl x, n. Manual bitwise tricks are still useful for division and modulo, which aren’t auto-optimized yet.
;; SLOW — division(div x (i64 8))
;; FAST — right shift (equivalent for positive integers)(shr x (i64 3))
;; SLOW — modulo(mod x (i64 16))
;; FAST — bitwise AND (equivalent for power-of-two modulus)(band x (i64 15))
;; SLOW — multiplication(mul x (i64 4))
;; FAST — left shift(shl x (i64 2))12.4 Minimize Let Nesting When Possible
Section titled “12.4 Minimize Let Nesting When Possible”Each let creates a scope. Flat blocks are more register-friendly:
;; LESS EFFICIENT — deep nesting(let (a (call compute_a)) (let (b (call compute_b)) (let (c (call compute_c)) (add (add a b) c))))
;; MORE EFFICIENT — direct expression composition(add (add (call compute_a) (call compute_b)) (call compute_c))12.5 Use -O1 for Compiler Optimizations
Section titled “12.5 Use -O1 for Compiler Optimizations”The AXON compiler’s -O1 pass applies several optimization techniques:
# Without optimization:./build/axonc program.axs --emit-qbe -O0
# With optimization:./build/axonc program.axs --emit-qbe -O1Constant Folding — evaluates compile-time constant expressions:
;; (add (i64 3) (i64 7)) → (i64 10) at compile time;; (not true) → false;; (neg (i64 5)) → (i64 -5);; (fadd (f64 1.5) (f64 2.5)) → (f64 4.0);; (flt (f64 1.0) (f64 2.0)) → trueStrength Reduction — replaces expensive operations with cheaper ones:
;; (mul x (i64 8)) → (shl x (i64 3)) — shift instead of multiply;; (mul (i64 16) x) → (shl x (i64 4)) — commutative swap + shift;; (mul x (i64 0)) → (i64 0) — zero annihilatorDead Code Elimination — removes unreachable and unused code:
;; Constant-condition branches are folded to direct jumps:;; (if true a b) → a (else branch is dead, removed);; Unused intermediate values are eliminated after folding.14. Memory Layout — How Types Map to Memory
Section titled “14. Memory Layout — How Types Map to Memory”13.1 Size and Alignment Table
Section titled “13.1 Size and Alignment Table”| Type | Size (bytes) | Alignment | QBE Type | LLVM Type |
|---|---|---|---|---|
bool | 1 | 1 | w (word, zero-extended) | i1 |
i8 / u8 | 1 | 1 | w (word, sign/zero-ext) | i8 |
i16 / u16 | 2 | 2 | w (word, sign/zero-ext) | i16 |
i32 / u32 | 4 | 4 | w (word) | i32 |
i64 / u64 | 8 | 8 | l (long) | i64 |
f32 | 4 | 4 | s (single) | float |
f64 | 8 | 8 | d (double) | double |
(ptr T) | 8 | 8 | l (long) | ptr |
void | 0 | 0 | — | void |
13.2 Function Calling Convention
Section titled “13.2 Function Calling Convention”AXON follows the platform’s C calling convention:
- x86-64 System V ABI (Linux): First 6 integer args in
%rdi,%rsi,%rdx,%rcx,%r8,%r9. First 8 float args in%xmm0–%xmm7. - ARM64 ABI (macOS): First 8 integer args in
x0–x7. First 8 float args ind0–d7. - Return values: Integer/pointer in
rax/x0. Float inxmm0/d0.
13.3 Stack Layout
Section titled “13.3 Stack Layout”┌──────────────────┐ ← Stack grows down│ Return address │├──────────────────┤│ Saved registers │├──────────────────┤│ Local variables │ ← let bindings live here│ (most recent at ││ lowest address) │├──────────────────┤│ Arguments for │ ← when calling other functions│ outgoing calls │└──────────────────┘15. QBE IL Patterns — What AXON Generates
Section titled “15. QBE IL Patterns — What AXON Generates”Understanding the generated QBE IL helps debug compilation issues.
14.1 Simple Function
Section titled “14.1 Simple Function”AXON source:
(fn add_one ((x i64)) i64 (add x (i64 1)))Generated QBE IL:
function l $add_one(l %x) {@start %0 =l add %x, 1 ret %0}14.2 If-Else Expression
Section titled “14.2 If-Else Expression”AXON source:
(fn max ((a i64) (b i64)) i64 (if (gt a b) a b))Generated QBE IL:
function l $max(l %a, l %b) {@start %0 =w csgtl %a, %b jnz %0, @then_0, @else_0@then_0 %1 =l copy %a jmp @merge_0@else_0 %2 =l copy %b jmp @merge_0@merge_0 %3 =l phi @then_0 %1, @else_0 %2 ret %3}14.3 While Loop
Section titled “14.3 While Loop”AXON source:
(let (i (i64 0)) (while (lt i (i64 10)) (set i (add i (i64 1)))))Generated QBE IL:
%i =l copy 0@while_cond_0 %0 =w csltl %i, 10 jnz %0, @while_body_0, @while_end_0@while_body_0 %1 =l add %i, 1 storel %1, %i_addr jmp @while_cond_0@while_end_014.4 QBE Type Suffixes
Section titled “14.4 QBE Type Suffixes”| Suffix | Meaning | Corresponds to |
|---|---|---|
w | Word (32-bit) | i32, u32, bool |
l | Long (64-bit) | i64, u64, pointers |
s | Single (32-bit float) | f32 |
d | Double (64-bit float) | f64 |
14.5 QBE Comparison Instructions
Section titled “14.5 QBE Comparison Instructions”| QBE Instruction | Meaning |
|---|---|
ceqw / ceql | Compare equal (word/long) |
cnew / cnel | Compare not equal |
csltw / csltl | Compare signed less than |
cslew / cslel | Compare signed less or equal |
csgtw / csgtl | Compare signed greater than |
csgew / csgel | Compare signed greater or equal |
cultw / cultl | Compare unsigned less than |
culew / culel | Compare unsigned less or equal |
16. Debugging Guide — Diagnosing Issues
Section titled “16. Debugging Guide — Diagnosing Issues”15.1 Compiler Pipeline
Section titled “15.1 Compiler Pipeline”Source (.axs) → Lexer → Parser → AST → Type Checker → IR → Backend → Output ↓ ↓ ↓ --emit-ast --emit-ir --emit-qbe/llvmEach --emit-* flag lets you inspect the output at that stage.
15.2 Debugging Type Errors
Section titled “15.2 Debugging Type Errors”# Type-check only — fastest way to find type errors./build/axonc program.axs --checkCommon type error messages:
"type mismatch"— binary operands have different types"expected bool"— if/while condition is not boolean"wrong number of arguments"— function call has wrong arg count"undefined variable"— referencing a variable not in scope"undefined function"— calling a function that doesn’t exist
15.3 Debugging Code Generation
Section titled “15.3 Debugging Code Generation”# Inspect the AXON IR (architecture-independent)./build/axonc program.axs --emit-ir
# Inspect QBE IL (before assembling)./build/axonc program.axs --emit-qbe -o output.ssa
# Inspect LLVM IR./build/axonc program.axs --emit-llvm -o output.ll
# Inspect the AST (parsed structure)./build/axonc program.axs --emit-ast15.4 Verbose Mode
Section titled “15.4 Verbose Mode”# Print each compilation stage and timing./build/axonc program.axs -o program --verbose15.5 Common Debugging Workflow
Section titled “15.5 Common Debugging Workflow”- Type error? → Run
--checkfirst - Wrong output? → Compare
--emit-iragainst expected logic - Crash at runtime? → Check
--emit-qbefor malformed IL - Build fails? → Check
--emit-llvmfor LLVM IR validity - Performance issue? → Compare
-O0vs-O1--emit-qbeoutput - Post-mortem crash from production/CI? → Use
tools/addr2line.pyto resolve crash addresses to source locations (requires debug-enabled binary). See addr2line-tool.md for details.
17. Quick Reference Card
Section titled “17. Quick Reference Card”Program Template
Section titled “Program Template”(module ;; extern declarations (optional) (extern puts ((s (ptr i8))) i32)
;; helper functions (fn helper ((param type)) return_type body)
;; entry point (required) (fn main () i32 (block ;; your code here (i32 0)))) ;; exit codeLiterals
Section titled “Literals”| Type | Syntax |
|---|---|
| Integer | (i64 42), (i32 -7), (u8 255) |
| Float | (f64 3.14), (f32 1.0) |
| Bool | (bool true), (bool false) |
| String | (str "hello") |
Operators
Section titled “Operators”| Category | Operators |
|---|---|
| Arithmetic | add, sub, mul, div, mod |
| Comparison | eq, ne, lt, le, gt, ge |
| Bitwise | band, bor, bxor, shl, shr |
| Logical | land, lor, not |
| Unary | neg, not, bnot |
Control Flow
Section titled “Control Flow”| Construct | Syntax |
|---|---|
| If-else | (if cond then else) |
| If (void) | (if cond then) |
| While | (while cond body) |
| Block | (block e1 e2 ... en) |
| Match | (match expr (pattern body) ...) |
| Return | (ret expr) or (ret) |
| Defer | (defer expr) — runs at function exit, LIFO order |
Pattern Matching
Section titled “Pattern Matching”| Construct | Syntax |
|---|---|
| Match on enum | (match color ((Color Red) body1) ((Color Green) body2) ...) |
| Match on integer | (match x ((i32 0) body1) ((i32 1) body2) (_ default_body)) |
| Match on bool | (match flag (true body1) (false body2)) |
| Wildcard arm | (_ body) — matches anything |
| Match as expr | (let (r (match x ...)) ...) |
Key rules:
- All arm bodies must have the same type.
- Patterns must match scrutinee type.
- Enum match requires all variants covered (or
_wildcard). - Bool match requires both
trueandfalsecovered (or_). _is optional for integer types.- Match is an expression — its value is the matched arm’s body.
Variables
Section titled “Variables”| Construct | Syntax |
|---|---|
| Introduce | (let (name init) body) |
| With type | (let (name type init) body) |
| Mutate | (set name value) |
Functions
Section titled “Functions”| Construct | Syntax |
|---|---|
| Define | (fn name ((p1 t1) ...) ret_type body) |
| Public fn | (pub (fn name ((p1 t1) ...) ret_type body)) |
| Call | (call name arg1 arg2 ...) |
| Extern | (extern name ((p1 t1) ...) ret_type) |
| Constant | (const name type value) |
Imports
Section titled “Imports”| Construct | Syntax |
|---|---|
| Namespaced import | (import alias "path.axs") |
| Selective import | (use "path.axs" (fn1 fn2 ...)) |
| Qualified call | (call alias.fn_name args...) |
| Make fn public | (pub (fn ...)) |
| Category | Examples |
|---|---|
| Signed int | i8, i16, i32, i64 |
| Unsigned int | u8, u16, u32, u64 |
| Float | f32, f64 |
| Other | void, bool |
| Pointer | (ptr i8), (ptr (ptr i64)) |
| Array | (arr i32 10) |
| Slice | (slice u8) |
| Function Pointer | (fnptr (i64) i64), (fnptr (i64 i64) bool) |
Structs, Arrays & Pointers
Section titled “Structs, Arrays & Pointers”| Construct | Syntax |
|---|---|
| Struct decl | (struct Name ((f1 t1) ...)) |
| Struct literal | (new Name (f1 v1) (f2 v2) ...) |
| Field access | (fld expr field_name) |
| Array literal | (arr.new T v1 v2 ...) |
| Array index | (idx expr index_expr) |
| Address-of | (addr var_name) |
| Dereference | (deref ptr_expr) |
Heap Allocation
Section titled “Heap Allocation”| Construct | Syntax |
|---|---|
| Single alloc | (alloc T) → (ptr T) (zero-initialized) |
| Array alloc | (alloc.n T count_expr) → (ptr T) (zero-initialized) |
| Free memory | (free ptr_expr) → void |
Key rules:
- Memory is zero-initialized (uses calloc).
- OOM causes a runtime panic.
- You must free what you allocate (no GC).
- Double-free and use-after-free are UB.
Function Pointers
Section titled “Function Pointers”| Construct | Syntax |
|---|---|
| Type annotation | (fnptr (i64 i64) i64) |
| Variable binding | (let (f (fnptr (i64) i64) my_fn) ...) |
| Indirect call | (call f (i64 42)) |
| Higher-order fn | (fn apply ((f (fnptr (i64) i64)) (x i64)) i64 (call f x)) |
| Pass fn as arg | (call apply double (i64 21)) |
Key rules:
- A bare function name in value position is auto-coerced to
(fnptr ...). - Indirect calls use the same
(call ...)syntax as direct calls — the type checker determines which. - Function pointer types are structurally compared (same params + return = same type).
Lambda Expressions (Anonymous Functions)
Section titled “Lambda Expressions (Anonymous Functions)”| Construct | Syntax |
|---|---|
| Simple lambda | (fn ((x i64)) i64 (mul x x)) |
| Multi-param | (fn ((a i64) (b i64)) i64 (add a b)) |
| No params | (fn () i64 (i64 42)) |
| Bind to variable | (let (sq (fn ((x i64)) i64 (mul x x))) (call sq (i64 7))) |
| Pass to HOF | (call apply (fn ((x i64)) i64 (mul x (i64 2))) (i64 5)) |
Key rules:
- Lambda syntax is
(fn ((params...)) ret_type body)— same asfnbut without a name. - Evaluates to a
(fnptr ...)value with matching parameter and return types. - Non-capturing — lambdas cannot reference variables from the enclosing scope.
- The body type must match the declared return type.
- Can be used anywhere a function pointer is expected:
letbindings, function arguments, data structures.
Unit Testing
Section titled “Unit Testing”| Construct | Syntax |
|---|---|
| Test declaration | (test "name" body) |
| Test with block | (test "name" (block expr1 expr2 ...)) |
| Assert (boolean) | (assert cond) or (assert cond "msg") |
| Assert equal | (assert_eq left right) or (assert_eq left right "msg") |
| Assert not equal | (assert_ne left right) or (assert_ne left right "msg") |
| Run tests | axonc test file.axs |
Key rules:
(test ...)is a top-level declaration (same level asfn,struct, etc.).- Test files have NO
mainfunction — the compiler generates a synthetic main when run withaxonc test. - Each test body is a single expression. Use
(block ...)for multiple expressions. assert_eq/assert_nesupport i32, i64, u64, and bool types. Both sides must have the same type.- On failure,
assert_eqshows both values:"assert_eq failed: left=10, right=20". - Tests are isolated: a failing test does NOT abort remaining tests. The runner prints a pass/fail summary.
- Exit code: 0 if all tests pass, 1 if any fail.
Example test file:
(module (test "arithmetic" (block (assert (eq (add (i32 2) (i32 2)) (i32 4))) (assert_eq (add (i64 10) (i64 20)) (i64 30)))) (test "comparisons" (assert_ne (i64 42) (i64 99))))| Construct | Syntax |
|---|---|
| Enum declaration | (enum Color i32 ((Red 0) (Green 1) (Blue 2))) |
| Enum literal | (Color Red), (Color Blue) |
| Equality compare | (eq c (Color Red)), (ne c (Color Green)) |
| Cast to integer | (cast i64 (Color Green)) → 1 |
| Cast from integer | (cast Color (i32 2)) |
| As function param | (fn f ((c Color)) i64 (cast i64 c)) |
| In let binding | (let (c Color (Color Red)) ...) |
Key rules:
- Enum types use nominal typing — different enum names are different types even with same structure.
- Only
eqandneare allowed on enum values. No arithmetic, no ordered comparison. - Cast to/from backing integer type for interop with integer operations.
- Enum values are stored as their backing integer type (same size and alignment).
18. Cross-Platform GUI (Planned)
Section titled “18. Cross-Platform GUI (Planned)”Axon is being designed to support fully compiled, cross-platform GUI applications. This capability is planned for milestones M9-M12.
Architecture
Section titled “Architecture”- Platform Layer (
libaxon_pal): C library for OS windowing, events, input - Rendering Engine (
libaxon_render): Software 2D rasterizer + GPU backends - UI Framework (
axon.ui): Immediate-mode widget API with flexbox layout - Widget Library: 17+ standard widgets, theming, app bundling
Platform Targets
Section titled “Platform Targets”| Platform | Backend | API |
|---|---|---|
| macOS | AppKit via ObjC runtime | objc_msgSend |
| Linux | X11 (Xlib) / Wayland | XCreateWindow |
| Windows | Win32 | CreateWindowEx |
Prerequisites
Section titled “Prerequisites”GUI support requires language features from M2-M5:
- Function pointers (M2) — for event callbacks
- Enums (M2) — for event types
- Heap allocation (M5) — for widget trees
- String type (M5) — for text handling
- Module system (M4) — for
axon.uiimports
See docs/gui-architecture.md for the complete architecture plan.