Binary Format
AXON Binary AST Format Specification
Section titled βAXON Binary AST Format SpecificationβVersion 0.1.0 β Phase 2 (Draft)
Table of Contents
Section titled βTable of Contentsβ- Overview
- Rationale
- File Structure
- Header
- String Table Section
- Type Table Section
- Function Table Section
- Node Encoding
- Variable Encoding (De Bruijn Indices)
- Integer Encoding (LEB128)
- Example: Fibonacci in Binary
- Round-Trip Guarantee
- Tooling
1. Overview
Section titled β1. OverviewβThe AXON Binary AST format (.axb) is a compact binary encoding of the abstract syntax tree. It is designed as the primary output format for AI models generating AXON code. Instead of emitting text that must be lexed and parsed, the AI emits a byte stream that the compiler loads directly into its internal representation.
File extension: .axb
MIME type: application/x-axon-binary
Byte order: Little-endian
2. Rationale
Section titled β2. RationaleβWhy binary format is better for AI
Section titled βWhy binary format is better for AIβ-
No parsing errors. Text formats introduce failure modes: unbalanced parentheses, typos in keywords, encoding issues. Binary format eliminates the entire class of syntax errors β if the structural invariants hold, the AST is valid.
-
Smaller output. AI token budgets are expensive. A binary AST is 3-5Γ smaller than the equivalent S-expression text, meaning the AI can express more complex programs within the same token limit.
-
Faster compilation. The compiler skips lexing and parsing entirely β the binary is memory-mapped directly into the AST representation. This makes the compile-code-test loop faster for AI-in-the-loop development.
-
Structural constraints. The binary format encodes the AST structure directly. An AI model generating binary can be constrained at the token level to only produce well-formed trees (e.g., via a constrained decoding grammar over the opcode vocabulary).
-
De Bruijn indices. Variables are referenced by structural position (how many binders away), not by name. This eliminates name shadowing bugs and variable capture β the AI never needs to invent unique names.
3. File Structure
Section titled β3. File StructureβAn .axb file consists of a fixed-size header followed by four sections:
βββββββββββββββββββββββββββββββββ Header (32 bytes) βββββββββββββββββββββββββββββββββ€β String Table Section βββββββββββββββββββββββββββββββββ€β Type Table Section βββββββββββββββββββββββββββββββββ€β Function Table Section βββββββββββββββββββββββββββββββββ€β AST Nodes Section βββββββββββββββββββββββββββββββββ4. Header
Section titled β4. HeaderβThe header is exactly 32 bytes:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | magic | Magic bytes: AXN\0 (0x41 0x58 0x4E 0x00) |
| 4 | 2 | version_major | Format version major (currently 0) |
| 6 | 2 | version_minor | Format version minor (currently 1) |
| 8 | 4 | flags | Bitfield flags (see below) |
| 12 | 4 | str_table_offset | Byte offset to string table section |
| 16 | 4 | type_table_offset | Byte offset to type table section |
| 20 | 4 | func_table_offset | Byte offset to function table section |
| 24 | 4 | node_offset | Byte offset to AST nodes section |
| 28 | 4 | checksum | CRC-32 of all sections (header excluded) |
| Bit | Name | Description |
|---|---|---|
| 0 | F_DEBUG | Contains debug info (source locations) |
| 1 | F_OPTIMIZED | Pre-optimized AST (constant folding applied) |
| 2-31 | β | Reserved (must be 0) |
5. String Table Section
Section titled β5. String Table SectionβThe string table stores all string literals and identifiers. Each string is referenced by a 0-based index (StrId). Index 0 is reserved for βno stringβ.
ββββββββββββββββββββββββββββββββ count: u32 β Number of strings (including slot 0)βββββββββββββββββββββββββββββββ€β offsets: u32[count] β Byte offset of each string from start of dataβββββββββββββββββββββββββββββββ€β data: u8[] β Concatenated null-terminated stringsβββββββββββββββββββββββββββββββ- String 0 is always the empty string
""(1 byte:0x00). - Strings are null-terminated in the data section.
offsets[i]gives the byte offset from the start ofdatato stringi.
Example
Section titled βExampleβStrings: ["", "main", "fib", "n"]
count: 04 00 00 00offsets: 00 00 00 00 01 00 00 00 06 00 00 00 0A 00 00 00data: 00 6D 61 69 6E 00 66 69 62 00 6E 00 "" "main\0" "fib\0" "n\0"6. Type Table Section
Section titled β6. Type Table SectionβThe type table stores all types used in the module. Types are referenced by 0-based index (TypeId). Index 0 is reserved for TYPE_INVALID.
ββββββββββββββββββββββββββββββββ count: u32 β Number of typesβββββββββββββββββββββββββββββββ€β types: TypeEntry[count] β Type entriesβββββββββββββββββββββββββββββββType Entry Encoding
Section titled βType Entry EncodingβEach type entry begins with a 1-byte kind tag, followed by kind-specific data:
| Kind | Tag | Extra Data |
|---|---|---|
TY_VOID | 0x00 | β |
TY_BOOL | 0x01 | β |
TY_NEVER | 0x02 | β |
TY_I8 | 0x03 | β |
TY_I16 | 0x04 | β |
TY_I32 | 0x05 | β |
TY_I64 | 0x06 | β |
TY_U8 | 0x07 | β |
TY_U16 | 0x08 | β |
TY_U32 | 0x09 | β |
TY_U64 | 0x0A | β |
TY_F32 | 0x0B | β |
TY_F64 | 0x0C | β |
TY_PTR | 0x0D | pointee: uleb128 (TypeId) |
TY_ARRAY | 0x0E | elem: uleb128 (TypeId), count: uleb128 |
TY_SLICE | 0x0F | elem: uleb128 (TypeId) |
TY_STRUCT | 0x10 | name: uleb128 (StrId), field_count: uleb128, then for each field: name: uleb128 (StrId), type: uleb128 (TypeId) |
TY_FUNC | 0x11 | ret: uleb128 (TypeId), param_count: uleb128, is_variadic: u8, then for each param: type: uleb128 (TypeId) |
7. Function Table Section
Section titled β7. Function Table SectionβThe function table provides metadata for each function in the module. The actual function bodies are encoded in the AST nodes section.
ββββββββββββββββββββββββββββββββ count: u32 β Number of functionsβββββββββββββββββββββββββββββββ€β entries: FuncEntry[count] β Function entriesβββββββββββββββββββββββββββββββFunction Entry
Section titled βFunction Entryβ| Field | Encoding | Description |
|---|---|---|
name | uleb128 (StrId) | Function name |
func_type | uleb128 (TypeId) | Function type from type table |
param_count | uleb128 | Number of parameters |
params | (uleb128, uleb128)[param_count] | For each param: (name StrId, type TypeId) |
is_extern | u8 | 1 if extern (no body), 0 otherwise |
body_offset | u32 | Byte offset to body node in AST nodes section (0 if extern) |
attrs | u32 | Attribute bitmask (ATTR_PURE, ATTR_INLINE, etc.) |
8. Node Encoding
Section titled β8. Node EncodingβAST nodes are encoded as a depth-first pre-order traversal of the tree. Each node starts with a 1-byte opcode, followed by opcode-specific data.
Node Opcodes
Section titled βNode Opcodesβ| Opcode | Tag | Operands |
|---|---|---|
ND_INT_LIT | 0x01 | type: uleb128, value: sleb128 |
ND_FLOAT_LIT | 0x02 | type: uleb128, value: f64 (8 bytes, IEEE 754) |
ND_BOOL_LIT | 0x03 | value: u8 (0 or 1) |
ND_STR_LIT | 0x04 | str_id: uleb128 |
ND_VAR | 0x05 | debruijn_index: uleb128 |
ND_BINOP | 0x06 | op: u8, then lhs: node, rhs: node |
ND_UNOP | 0x07 | op: u8, then operand: node |
ND_CALL | 0x08 | callee: uleb128 (StrId), nargs: uleb128, then args: node[nargs] |
ND_IF | 0x09 | has_else: u8, then cond: node, then: node, optionally else: node |
ND_WHILE | 0x0A | cond: node, body: node |
ND_LET | 0x0B | has_type: u8, optionally type: uleb128, then init: node, body: node |
ND_SET | 0x0C | debruijn_index: uleb128, value: node |
ND_BLOCK | 0x0D | count: uleb128, then stmts: node[count] |
ND_RET | 0x0E | has_value: u8, optionally value: node |
ND_CAST | 0x0F | target_type: uleb128, then expr: node |
ND_INDEX | 0x10 | array: node, index: node |
ND_FIELD | 0x11 | field_name: uleb128 (StrId), then object: node |
ND_DEREF | 0x12 | ptr: node |
ND_ADDR | 0x13 | debruijn_index: uleb128 |
ND_ARRAY_LIT | 0x14 | elem_type: uleb128, count: uleb128, then elems: node[count] |
ND_STRUCT_LIT | 0x15 | struct_name: uleb128, field_count: uleb128, then for each: name: uleb128, value: node |
Binary Operator Tags
Section titled βBinary Operator Tagsβ| Op | Tag |
|---|---|
ADD | 0x00 |
SUB | 0x01 |
MUL | 0x02 |
DIV | 0x03 |
MOD | 0x04 |
EQ | 0x05 |
NE | 0x06 |
LT | 0x07 |
LE | 0x08 |
GT | 0x09 |
GE | 0x0A |
BAND | 0x0B |
BOR | 0x0C |
BXOR | 0x0D |
SHL | 0x0E |
SHR | 0x0F |
LAND | 0x10 |
LOR | 0x11 |
Unary Operator Tags
Section titled βUnary Operator Tagsβ| Op | Tag |
|---|---|
NEG | 0x00 |
NOT | 0x01 |
BNOT | 0x02 |
9. Variable Encoding (De Bruijn Indices)
Section titled β9. Variable Encoding (De Bruijn Indices)βIn the binary format, variables are not referenced by name. Instead, they use De Bruijn indices β a variable reference is a non-negative integer that counts how many binders (lambda abstractions / let bindings) outward the binding site is.
Example
Section titled βExampleβIn the text format:
(fn add ((a i64) (b i64)) i64 (add a b))Here, a is the parameter at index 0, b at index 1. In the binary format, a is referenced as De Bruijn index 1 (1 binder away) and b as De Bruijn index 0 (0 binders away β the closest one).
More precisely, De Bruijn indices count from the innermost binding outward:
(fn f ((x i64)) i64 ;; x is at depth 0 (let (y (i64 1)) ;; y is at depth 0, x is now at depth 1 (add y x))) ;; y = debruijn(0), x = debruijn(1)Function Parameters
Section titled βFunction ParametersβFunction parameters are numbered from right to left (last param = index 0):
(fn f ((a i64) (b i64) (c i64)) i64 ;; c = debruijn(0), b = debruijn(1), a = debruijn(2) (add a c)) ;; a = debruijn(2), c = debruijn(0)Benefits for AI
Section titled βBenefits for AIβ- No name invention. The AI never needs to generate unique variable names.
- No shadowing bugs. De Bruijn indices are structurally unambiguous.
- Smaller output. A single integer replaces a multi-character identifier.
- Easier verification. Index validity is a simple bound check.
10. Integer Encoding (LEB128)
Section titled β10. Integer Encoding (LEB128)βAll variable-length integers in the binary format use LEB128 (Little-Endian Base 128) encoding:
Unsigned LEB128 (uleb128)
Section titled βUnsigned LEB128 (uleb128)βEach byte encodes 7 bits of data. The high bit (bit 7) is set if more bytes follow.
Value: 624485Binary: 100110 0001000 1100101Encoding: 0xE5 0x8E 0x26
Byte 0: 1_1100101 (0xE5) β more bytes followByte 1: 1_0001000 (0x8E) β more bytes followByte 2: 0_0100110 (0x26) β last byteSigned LEB128 (sleb128)
Section titled βSigned LEB128 (sleb128)βSame as unsigned, but the sign bit is the highest bit of the last byte.
Value: -123456Encoding: 0xC0 0xBB 0x78Common Values
Section titled βCommon Valuesβ| Value | uleb128 |
|---|---|
| 0 | 0x00 |
| 1 | 0x01 |
| 127 | 0x7F |
| 128 | 0x80 0x01 |
| 255 | 0xFF 0x01 |
| 300 | 0xAC 0x02 |
11. Example: Fibonacci in Binary
Section titled β11. Example: Fibonacci in BinaryβThe following AXON program:
(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)))))))Hex Dump
Section titled βHex Dumpβ;; === Header (32 bytes) ===41 58 4E 00 ;; magic: "AXN\0"00 00 ;; version_major: 001 00 ;; version_minor: 100 00 00 00 ;; flags: none20 00 00 00 ;; str_table_offset: 323F 00 00 00 ;; type_table_offset: 634C 00 00 00 ;; func_table_offset: 7662 00 00 00 ;; node_offset: 98A7 B3 C1 D2 ;; checksum: CRC-32
;; === String Table ===03 00 00 00 ;; count: 3 (including slot 0)00 00 00 00 ;; offsets[0]: 0 β ""01 00 00 00 ;; offsets[1]: 1 β "fib"05 00 00 00 ;; offsets[2]: 5 β "n"00 ;; data[0]: "" (null)66 69 62 00 ;; data[1]: "fib\0"6E 00 ;; data[2]: "n\0"
;; === Type Table ===03 00 00 00 ;; count: 300 ;; types[0]: TY_VOID (placeholder for TYPE_INVALID)06 ;; types[1]: TY_I6401 ;; types[2]: TY_BOOL;; (plus function type entry β elided for brevity)
;; === Function Table ===01 00 00 00 ;; count: 101 ;; name: StrId 1 ("fib")03 ;; func_type: TypeId 3 (i64 β i64)01 ;; param_count: 102 01 ;; param[0]: name=StrId 2 ("n"), type=TypeId 1 (i64)00 ;; is_extern: false00 00 00 00 ;; body_offset: 0 (start of nodes section)00 00 00 00 ;; attrs: ATTR_NONE
;; === AST Nodes (body of fib) ===;; (if (lt n (i64 2)) n (add (call fib (sub n (i64 1))) (call fib (sub n (i64 2)))))
09 ;; ND_IF01 ;; has_else: true 06 ;; ND_BINOP 07 ;; op: LT 05 ;; ND_VAR 00 ;; debruijn_index: 0 (n) 01 ;; ND_INT_LIT 01 ;; type: TypeId 1 (i64) 02 ;; value: sleb128(2) 05 ;; ND_VAR (then branch: n) 00 ;; debruijn_index: 0 06 ;; ND_BINOP (else branch: add) 00 ;; op: ADD 08 ;; ND_CALL 01 ;; callee: StrId 1 ("fib") 01 ;; nargs: 1 06 ;; ND_BINOP 01 ;; op: SUB 05 ;; ND_VAR 00 ;; debruijn_index: 0 (n) 01 ;; ND_INT_LIT 01 ;; type: TypeId 1 (i64) 01 ;; value: sleb128(1) 08 ;; ND_CALL 01 ;; callee: StrId 1 ("fib") 01 ;; nargs: 1 06 ;; ND_BINOP 01 ;; op: SUB 05 ;; ND_VAR 00 ;; debruijn_index: 0 (n) 01 ;; ND_INT_LIT 01 ;; type: TypeId 1 (i64) 02 ;; value: sleb128(2)Total size: ~98 bytes for the nodes section, compared to ~170 characters of S-expression text.
12. Round-Trip Guarantee
Section titled β12. Round-Trip GuaranteeβThe binary format provides a semantic round-trip guarantee:
decode(encode(ast)) β‘ astSpecifically:
- Structural equivalence: The decoded AST has the same tree structure as the original.
- Type preservation: All type annotations are preserved exactly.
- Value preservation: All literal values are preserved exactly (including floating-point bit patterns).
- Variable binding: De Bruijn indices are resolved back to the same binding sites.
Note: The round-trip does not preserve:
- Variable names (De Bruijn indices replace names)
- Source locations (unless
F_DEBUGflag is set) - Comments (not representable in binary)
- Whitespace or formatting
Verification
Section titled βVerificationβThe compiler includes a verification mode:
# Encode text to binaryaxonc encode input.axs -o output.axb
# Decode binary back to textaxonc decode output.axb -o roundtrip.axs
# Verify structural equivalence (ignoring names)axonc verify input.axs output.axb13. Tooling
Section titled β13. ToolingβPlanned Tools
Section titled βPlanned Toolsβ| Tool | Description |
|---|---|
axonc encode | Compile .axs text to .axb binary |
axonc decode | Decompile .axb binary to .axs text |
axonc dump | Hex dump with annotations |
axonc verify | Verify round-trip equivalence |
axonc inspect | Pretty-print type table, string table, function table |
Library API
Section titled βLibrary APIβFor AI agent integration, a C library API will be provided:
// Write binary ASTAxbWriter *axb_writer_new(Arena *a);void axb_write_header(AxbWriter *w, uint32_t flags);void axb_write_string(AxbWriter *w, const char *s, size_t len);uint32_t axb_write_type(AxbWriter *w, Type *t);void axb_write_node(AxbWriter *w, uint8_t opcode, ...);void axb_finish(AxbWriter *w, FILE *out);
// Read binary ASTAxbReader *axb_reader_new(const uint8_t *data, size_t len, Arena *a);bool axb_read_header(AxbReader *r, AxbHeader *hdr);Node *axb_read_module(AxbReader *r, TypeTable *types, StrTable *strings);