Schema Compiler
Code Generation
How the schema compiler generates serialization code for each target language.
The sia compile command transforms .sia schema files into serialization code. Each target language gets idiomatic output that uses that language's Sia library.
Compilation Pipeline
.sia source → Lexer (tokenize) → Parser (CST) → Visitor (IR) → Generator (target code)
- The lexer tokenizes the source into keywords, identifiers, literals, and operators
- The parser builds a concrete syntax tree (CST) from the token stream
- The visitor walks the CST and produces an intermediate representation (IR): an array of schema and plugin definitions
- The generator takes the IR and produces code for the target language
Target Language Detection
The compiler determines the target language in this priority order:
- Explicit flag:
-e tsor--extension go - Output file extension:
-o schema.tsimplies TypeScript - Auto-detection from project files in the current directory (and up to 2 parent directories):
tsconfig.jsonfound: TypeScriptgo.sumfound: Gopyproject.tomlfound: PythonCMakeLists.txtfound: C++
If nothing is detected, the compiler asks you to specify -e.
Usage
# Compile to TypeScript, write to file
sia compile schema.sia -o schema.ts
# Compile to Go, print to stdout
sia compile schema.sia -e go -s
# Compile to C++ (generates both .hpp and .cpp)
sia compile schema.sia -o schema.cpp
# Auto-detect language from project context
sia compile schema.sia -o schema
Input Schema
The examples below all use this schema:
schema Address {
street? string8 = "Default Street"
city string8
zip int32
}
schema Person {
name string8
age? int32
email? string8(encoding = "ascii")[]
tags string8[]
avatar byteN(length = 32)
photo byte64
address Address
}
Generated Output
The TypeScript generator produces an interface, an encode function, and a decode function for each schema:
import { Sia } from "@timeleap/sia";
export interface Person {
name: string;
age?: number;
email?: string[];
tags: string[];
avatar?: Uint8Array | Buffer;
photo: Uint8Array | Buffer;
address: Address;
}
export function encodePerson(sia: Sia, person: Person): Sia {
sia.addString8(person.name);
sia.addInt32(person.age ?? 0);
sia.addArray8(person.email ?? [], (s: Sia, v) => s.addString8(v));
sia.addArray8(person.tags, (s: Sia, v) => s.addString8(v));
sia.addByteArrayN(person.avatar ?? new Uint8Array(0));
sia.addByteArray64(person.photo);
encodeAddress(sia, person.address);
return sia;
}
export function decodePerson(sia: Sia): Person {
return {
name: sia.readString8(),
age: sia.readInt32(),
email: sia.readArray8((s: Sia) => s.readString8()),
tags: sia.readArray8((s: Sia) => s.readString8()),
avatar: sia.readByteArrayN(32),
photo: sia.readByteArray64(),
address: decodeAddress(sia),
};
}
export interface Address {
street?: string;
city: string;
zip: number;
}
export function encodeAddress(sia: Sia, address: Address): Sia {
sia.addAscii(address.street ?? "");
sia.addString8(address.city);
sia.addInt32(address.zip);
return sia;
}
export function decodeAddress(sia: Sia): Address {
return {
street: sia.readAscii(),
city: sia.readString8(),
zip: sia.readInt32(),
};
}
Key patterns:
- Optional fields use
??with a zero-value fallback - Arrays generate
addArray8/readArray8with inline callbacks - Custom types call the referenced schema's encode/decode functions
- Fields with
encoding = "ascii"useaddAscii/readAscii - Output is formatted with Prettier ::