Compilation System
The compilation system is Alia's meta-compilation mechanism, allowing users to define at the Alia level how Alia code is compiled into target language code. Unlike traditional compilers, Alia's compilation system exposes the compilation logic itself as a programmable interface — users declare how each syntax structure maps to the target language through overrides, rather than having the compiler kernel hard-code it.
1. Compilation Pipeline Overview
The complete pipeline of Alia code from source file to target code:
Source code (.alia)
↓ Tokenize
Token stream
↓ Astify
AST (Abstract Syntax Tree)
↓ Translation system processing
Translated AST
↓ Check (type checking)
Type-checked AST
↓ Compile
Target language code
↓ Backend compilation
Executable program
Stage descriptions:
| Stage | Description |
|---|---|
| Tokenize | Splits the source code into a token stream |
| Astify | Converts the token stream into an AST |
| Translation system | Performs keyword substitution according to @translateAlias / @language |
| Check | Type checking, unused variable warnings, type error detection |
| Compile | Traverses the AST and generates target language code according to override rules |
| Backend compilation | Hands the generated code to the target language's native compiler |
Compilation mode and interpretation mode share the first four stages and only diverge at the fifth.
2. Compilation Entry
The compilation entry is the starting point of the compilation pipeline: it declares the target language, sets up helper code, triggers compilation, and returns the final output.
2.1 @compileEntry
Declares the compilation entry for a given target language. Statements in the entry stack execute at compile time, and the return value of its last item represents the final compiler output.
@compileEntry <Key langName> <Stack body>
Parameters:
| Parameter | Type | Description |
|---|---|---|
langName | Key | Target language name (e.g. js, nodejs, rs) |
body | Stack | The compilation entry stack, containing helper function definitions, global variables, compilation triggers, etc. |
Example:
~ Declare the entry for compiling to JavaScript
@compileEntry js {
let importList = ''
const setImport = alias <String moduleName> -> {
importList = importList .. 'import ' .. moduleName .. ';\n'
}
~ Trigger compilation and return the result
return importList .. call @compileStart
}
Execution order:
- Execute all statements in the entry stack except the last (the setup statements)
- Execute in order all additional operations appended by
@compileWith - Evaluate the last item of the entry stack (the return value), whose result becomes the compilation output
2.2 @compileWith
Appends additional operations to an existing entry, sharing the same context as @compileEntry. Multiple @compileWith declarations execute in declaration order.
@compileWith <Key langName> <Stack body>
Execution timing: after the @compileEntry setup statements and before the return value is evaluated, so its modifications to the context affect the entry's final return value.
Example:
~ main.alia
@compileEntry js {
'function main() {\n' .. call @compileStart .. '\n}'
}
~ utils.alia (appended automatically once imported)
@compileWith js {
setImport 'fs'
}
Third-party modules: for .alia modules imported in the compilation entry's AST (including modules installed under mods/), any @compileEntry / @compileWith / @compileNode / @compileAlias /
@compileConfig / @compileClass they contain are recursively registered before the entry's own directives (only the compilation directives execute, not the module bodies;
de-duplication by resolved path prevents cycles). This lets third-party modules inject compilation backends just like the standard library.
2.3 @compileStart
Triggers the actual compilation process. The compiler traverses the user AST and generates target language code, returning the compilation result directly.
call @compileStart
Notes:
- Must be called inside the entry stack of
@compileEntry - Returns the compiled target language code string
- Override rules (
@compileNode/@compileAlias) encountered during compilation are applied automatically
2.4 @compile (Compile-Time Directive)
A compile-time expression that compiles an arbitrary Alia code string to a specified target language. Unlike @compileEntry, @compile is a general compilation method usable inside override logic, freeing the compiler side from specializing for any target language.
@compile <Expression langName> <Expression code> [<Expression sourceDir>]
Parameters:
| Parameter | Type | Description |
|---|---|---|
langName | Expression | Target language name (e.g. "js", "rs") |
code | Expression | The Alia source code string to compile |
sourceDir | Expression (optional) | Source directory path, used to resolve import dependencies during compilation |
Return value: the compiled target language code string.
Example:
~ Using @compile inside an ImportStatement override
@compileNode "ImportStatement" -> js {
let data = _bound.importData
if data.ext == '.alia' {
let content = readFile data.resolvedPath
let compiled = @compile "js" content (dirname data.resolvedPath)
~ Return the wrapped compilation result
return 'const ' .. data.alias .. ' = ' .. compiled
}
~ Non-Alia files are handled differently
return 'const ' .. data.alias .. ' = ' .. data.contentEncoded
}
~ Using @compile inside the compilation entry
@compileEntry js {
let compiled = @compile "js" "const x = 1"
~ compiled == "const x = 1"
}
Execution mechanism:
@compileshares the current compiler'saliasRegistry(all override rules)- Shares the current compiler's
@compileNode/@compileAliasoverride registrations - Has an independent
varsscope to avoid polluting the outer context with variables langNamemay specify a language different from the current compilation target
3. The Override System
The override system is the heart of the compilation system, letting users define how each AST node and syntax keyword compiles in the target language.
3.1 @compileNode
Sets a compilation override for an AST node type, describing how base Alia syntax is compiled.
@compileNode <String nodeName> -> <Key langName> <Stack body>
Parameters:
| Parameter | Type | Description |
|---|---|---|
nodeName | String | AST node type name (e.g. "BinaryOp", "Literal") |
langName | Key | Target language name |
body | Stack | Compilation logic, with access to built-in variables |
Built-in variables:
| Variable | Type | Description |
|---|---|---|
_ast | Object | The current AST node |
_token | Array | The original token stream |
_code | String | The source code string |
_bound | Object | Compilation results of child nodes (key-value pairs) |
_ctx | Object | Compilation context information |
compile | Function | Recursively compiles child nodes |
compileExpr | Function | Compiles sub-expressions |
Example:
~ Compile the BinaryOp node to JavaScript
@compileNode "BinaryOp" -> js {
switch _bound.op {
case '+' { return _bound.left .. ' + ' .. _bound.right }
case '-' { return _bound.left .. ' - ' .. _bound.right }
case '*' { return _bound.left .. ' * ' .. _bound.right }
case '/' { return _bound.left .. ' / ' .. _bound.right }
case '..' { return _bound.left .. ' + ' .. _bound.right }
case '==' { return _bound.left .. ' === ' .. _bound.right }
case '!=' { return _bound.left .. ' !== ' .. _bound.right }
case '>' { return _bound.left .. ' > ' .. _bound.right }
case '<' { return _bound.left .. ' < ' .. _bound.right }
case '>=' { return _bound.left .. ' >= ' .. _bound.right }
case '<=' { return _bound.left .. ' <= ' .. _bound.right }
case '&'{ return _bound.left .. ' && ' .. _bound.right }
case '|' { return _bound.left .. ' || ' .. _bound.right }
}
}
_bound fields (by node type):
| Node Type | _bound Fields |
|---|---|
BinaryOp | left, op, right |
UnaryOp | op, operand |
Literal | value, isNull |
Variable | name, inScope |
Call | callee, args, calleeType, callKind |
MemberAccess | object, property |
IndexAccess | object, index, accessKind, isAliasCall |
ArrayLiteral | elements |
ObjectLiteral | propStrings, hasStatic |
StackLiteral | body, isMutable |
ConditionalExpression | test, consequent, alternate |
AliasDefinition | isAnonymous, aliasName, paramNamesString, body |
AliasDefinitionExpression | paramNamesString, body |
ImportStatement | importData (containing source, alias, sourceDir, ext, resolvedPath, contentEncoded, contentStr, exports, etc.) |
Program | sentences |
Stack | sentences or body |
3.2 @compileAlias
Sets a compilation override for a syntax keyword, providing an efficient compilation output for one specific alias.
@compileAlias <Keys aliasDefinition> -> <Key langName> <Stack body>
Difference from @compileNode "AliasDefinition":
@compileNode "AliasDefinition"defines the general compilation method for allaliasdeclarations@compileAliascan specify a low-level compilation method for one particular alias
Built-in variables:
| Variable | Type | Description |
|---|---|---|
_ast | Object | The current AST node |
_token | Array | The original token stream |
_code | String | The source code string |
_bound | Object | Compiled parameter bindings |
_ctx | Object | Compilation context information |
compile | Function | Recursively compiles child nodes |
compileExpr | Function | Compiles sub-expressions |
<paramName> | Each parameter name | The compiled parameter value (matching the parameter names in the alias declaration) |
Example:
~ Compile the "print" keyword to JavaScript
@compileAlias print <Value val> -> js {
'console.log(' .. val .. ')'
}
~ Compile the "add" keyword to JavaScript
@compileAlias add <Number a> and <Number b> -> js {
'(' .. a .. ' + ' .. b .. ')'
}
3.3 @compileClass
Overrides all public methods of a class/object (such as the module objects fs/io or a custom class) in one batch, replacing scattered @compileAlias declarations. This is the recommended compilation mapping approach for the standard library and third-party libraries.
@compileClass <Key className> -> <Key langName> <Object payload>
Parameters:
| Parameter | Type | Description |
|---|---|---|
className | Key | Target class/object name. Corresponds to a class declaration name or an imported module alias (e.g. fs, io, Gtk) |
langName | Key | Target language name |
payload | Object | An object literal *[...] whose members are per-method compilation overrides or class-level flags |
payload organization:
| Member Key | Value Type | Description |
|---|---|---|
<methodName> | alias ... -> { ... } | An AliasDefinitionExpression defining the method's compilation logic in the target language. The auxiliary keywords in the parameter list match the original method declaration; the Checker statically verifies parameter types and return values |
available | Boolean | A class/method-level availability flag. When available: false, the class or method is unavailable in that language (falling back to the generic path or empty output at compile time); defaults to true. A method-level available: false overrides the class-level flag |
Semantics:
- The
payloadonly defines the compilation mapping of publicly exposed methods; at runtime the class itself still needs to be defined in ordinary code (e.g.class AliaFs *[...]orconst gtk = *[...]) and is executed and type-checked by the interpreter - At compile time, a
MemberAccesscallobj.method <args>(e.g.fs.write c to p) or a barealiascall (e.g.createWindow with app titled title, attributed to theGtkclass) runstryMatchPatternagainst thepatternof the correspondingaliasin thepayload; on a match, the method body executes with the compiled parameters (<paramName>), and its return value is the compilation result of that call - Each
aliasbody inside thepayloadbehaves like@compileAliasand can access_ast/_token/_code/_bound/_rawBound/_ctx/compile/compileExprand the parameter names
Type checking:
The Checker iterates over every alias method in the payload, validating the legality of parameter types and the consistency between the stack body's type and the declared return type; available must be a Boolean
Example:
~ File system module: batch-mapping the fs object's methods to js
@compileClass fs -> js *[
read: alias <String path> -> String {
'__aliaFs.read(' .. path .. ')'
},
write: alias <String content> to <String path> -> Null {
'__aliaFs.write(' .. content .. ', ' .. path .. ')'
},
stat: alias <String path> -> Object {
'__aliaFs.stat(' .. path .. ')'
},
available: true
]
~ Expose only read/write; hide the remaining methods in this language
@compileClass fs -> js *[
read: alias <String path> -> String { '__aliaFs.read(' .. path .. ')' },
write: alias <String content> to <String path> -> Null { '__aliaFs.write(' .. content .. ', ' .. path .. ')' },
copy: false,
remove: false,
available: true
]
~ Batch mapping for bare aliases attributed to the Gtk class (createWindow etc. are top-level aliases attributed to Gtk)
@compileClass Gtk -> rs *[
createWindow: alias with <Value app> titled <String title> -> Value {
'{ let __win = runtime::call_value(&runtime::get_v(&runtime::__alia_gtk(), "windowNew"), vec![' .. app .. ']); __win }'
},
setWindowTitle: alias of <Value win> to <String title> -> Null {
'runtime::call_value(&runtime::get_v(&runtime::__alia_gtk(), "windowSetTitle"), vec![' .. win .. ', ' .. title .. '])'
},
available: true
]
4. Compiling Stack Parameters
<Stack> parameters in override statements are already recursively compiled into target language code strings before binding; simply reference them by parameter name:
@compileAlias if <Boolean condition> <Stack thenStack> -> js {
'if (' .. condition .. ') {\n' .. thenStack .. '\n}'
}
The @compileNode "StackLiteral" override reads the compiled stack body through _bound.body:
@compileNode "StackLiteral" -> js {
_bound.body
}
5. Compilation Context
The _ctx object provides context information during compilation for override logic to query:
@compileNode "someNode" -> js {
if _ctx.inLoop {
~ currently inside a loop
}
if _ctx.mutableStack {
~ currently inside a mutable stack
}
if _ctx.hasVar 'myVar' {
~ myVar is declared in the current scope
}
}
| Field | Type | Description |
|---|---|---|
mutableStack | Boolean | Whether inside a mutable stack |
inLoop | Boolean | Whether inside a loop |
letHasVar | Boolean | Whether a variable declared by let/const already exists |
hasMutVars | Boolean | Whether there are mutable stack variables |
selfType | String | The current self type ('instance' or 'static') |
isExpression | Boolean | Whether in an expression context |
hasVar(name) | Function | Checks whether a variable is declared |
isAliasName(name) | Function | Checks whether a name is an alias |
findAliasDef(aliasId) | Function | Finds an alias definition |
6. Compilation Configuration (@compileConfig)
@compileConfig sets compile-time configuration for a specified language; the key-value pairs in the configuration object are queried by the compiler kernel and backend overrides.
@compileConfig <Key langName> <Object configs>
Supported configuration keys:
| Key | Type | Default | Description |
|---|---|---|---|
supportedLibs | Array | - | Declares the list of standard libraries supported when compiling for this language; the test system uses it to skip compilation tests for unsupported standard libraries |
parent | String | - | Parent language name. Nodes/functions/entries not overridden by the child language are inherited along the parent language chain |
autoReturnStyle | String | 'prefix' | Automatic return style for stack bodies. 'prefix' prepends a return prefix before the final statement; 'tail' emits the final statement directly as a tail expression |
statementSeparator | String | ';\n' | Separator joining the compiled results of multiple statements |
fallbackRuntime | String | null | Fallback runtime preamble file path when there is no @compileEntry (resolved relative to the source file directory) |
isolateStackScope | Boolean | false | When compiling stack bodies (StackLiteral/*{}), use a brand-new empty variable set so that local let/const declarations do not map to assignments on outer variables, isolating the scope |
nativeTypes | Object | - | Mapping from Alia static type names to target-language native type tokens (e.g. for Rust, *[Number: 'f64', '[Number]': 'Vec']). The kernel uses it to decide whether a binding/expression can be lowered to a native local of the target language and whether array types map to Vec. The kernel hard-codes no language's native type vocabulary |
valueWrap | Object | - | Mapping from Alia static type names to wrapping templates, with ${code} as the placeholder. When a tail expression in a Value-mode function body is lowered to a native expression, the kernel wraps it back into the target language's runtime Value according to this template (runtime::num(${code}), etc.) |
valueUnwrap | Object | - | Mapping from Alia static type names to unwrapping templates, with ${code} as the placeholder. When a native-signature function body's tail expression/return value is a runtime Value, the kernel unwraps it into the declared native return type according to this template (runtime::value_number(&(${code})), etc.) |
fnReturnWrap | Boolean | false | Whether declaration-expression Stacks (including anonymous aliases escaped at the language level as Value functions) compile in "return a runtime Value" mode. When enabled, the kernel wraps the tail expression via valueWrap before returning |
closureBoundaryAliases | Array | - | Besides user-defined aliases, the list of builtin alias ids whose Stack body parameters are lowered into target language closures (e.g. ['builtin:try-catch', 'builtin:select']). The kernel counts them as closure boundaries during capture analysis, avoiding compilation errors from assigning to outer variables inside closures |
run | Alias | - | The steps the manager interprets when alia run is declared. Parameters are injected by name: outDir (the compilation output directory) is always provided, the rest come from CLI --key:value arguments (e.g. alia run --target:'web'); parameters not declared nullable (<Type? param>) and not provided raise an error |
build | Alias | - | The steps the manager interprets when alia build is declared. Parameter injection rules are the same as run |
params | Array | - | Names of the CLI arguments this language consumes, for introspection and documentation. Reading and interpreting those arguments happens entirely on the override side (via the __aliaParams compile-time constant); the compiler kernel holds no knowledge of any argument name |
targetCaps | Object | - | Compilation target name → capability table, where capabilities are backend-defined boolean switches. E.g. *['web': *['noPackageManager': true]] means the target installs no third-party runtimes |
targetOutDir | Object | - | Compilation target name → output directory, e.g. *['web': './build/nodejs-web']. Targets without an entry keep the default <outDir>/<lang> |
targetSteps | Object | - | Compilation target name → step sequence, e.g. *['web': ['run']]. Defaults to ['build', 'run']; a target that owns its whole lifecycle (such as a bundler that installs and packages on its own) declares ['run'] to skip the build step |
6.0.1 Compile-Time Constants and Argument Declaration
The compiler injects the following variables before running @compileEntry. Only facts the kernel computes itself are built-in constants; arguments supplied by the caller (such as target in --target:web) are not built-in parameters — they arrive through the __aliaParams bag, and the backend declares in params which names it consumes and interprets them itself.
| Variable | Type | Meaning |
|---|---|---|
__aliaOutDir | String | Output directory, computed by the toolchain from the project layout |
__aliaParams | Object | Caller argument bag keyed by --key:value; excludes kernel-owned keys such as outDir/sourceDir |
__aliaConfig | Object | The language's own @compileConfig object |
__aliaUsedImports | String | All import sources in the entry and its transitive .alia dependencies, comma-wrapped, e.g. ,fs,gtk-alia, |
Adding a CLI argument therefore requires no compiler-kernel change — only a backend declaration plus override-side handling:
~ base/Core/src/backends/nodejs.alia
@compileConfig nodejs *[..., params: ['filename', 'target'], ...]
@compileEntry nodejs {
let cliParams = __aliaParams
let buildTarget = ''
if Object.has cliParams keyed 'target' { buildTarget = cliParams['target'] }
...
}
At run time the value is received through the run/build Alias parameters by name: declaring <String? target> is what makes alia run --target:web inject it into the step body for interpretation.
Note:
Object.hasonly tests own keys. Do not use a read that can fall back to the enclosing scope (such asObject.get) to decide "is this name registered in the registry", or a same-named outer variable will be matched by mistake.
6.1 Shallow Merge
@compileConfig can be declared multiple times for the same language: newly declared keys override existing keys of the same name, while undeclared keys inherit the base configuration. This lets third-party modules override only run/build/targetSteps without repeating base keys such as parent/supportedLibs.
When inheriting along a parent chain, a closer language's alias and variable declarations win; repeated declarations in a parent language never override them.
6.2 Nullable run/build Parameters
The run/build Aliases of @compileConfig can declare nullable parameters (e.g. <String? target>). When the CLI does not provide the corresponding --key:value argument, the parameter binds to null and no requires parameter error is thrown.
@compileConfig nodejs *[
run: alias <String outDir> <String? target> -> {
import 'process' as process
let entry = 'main.js'
if Ok target { entry = target }
process.spawn 'node' with [entry] with *[cwd: outDir, stdio: 'inherit']
}
]
Language-agnostic missing values: compile-time missing values (nodes without a matching override, missing parameters) are emitted by the kernel as the empty string '', without embedding any language-specific placeholder text (such as JS's undefined). Whether and how to replace them with a specific placeholder is decided by the override side; the kernel stays language-agnostic.
Example:
@compileConfig js *[supportedLibs: ['FileSystem']]
@compileConfig rs *[run: alias <String outDir> -> {
import 'process' as process
process.spawn 'cargo' with ['run', '--quiet'] with *[cwd: outDir, stdio: 'inherit']
}]
When alia run executes, the manager parses @compileConfig rs in main.alia, injects outDir into that Alias by parameter name, and runs it in the interpreter.
7. Supported Target Languages
The compilation system currently provides the following backends:
| Target Language | Status | Description |
|---|---|---|
| JavaScript | Supported | A complete compilation backend supporting all syntax features |
| Nodejs | Supported | A Node-specific backend built on the JS backend (including FFI) |
| Rust | Supported | The Rust backend, supporting core syntax, classes/objects, Stack/async, file imports, and the Args/FileSystem/IO/Process/Web standard libraries (supportedLibs); FFI is Node-specific |
Adding support for a new language:
You only need to declare @compileEntry plus the corresponding @compileNode / @compileAlias overrides — no changes to the compiler kernel are required. The compiler kernel is universal; all language-specific logic belongs on the override side.
- Language-specific runtime preambles are declared through
@compileConfig fallbackRuntimeinstead of being hard-coded in the kernel. - The statement separator and automatic return style are configured via
@compileConfig statementSeparator/autoReturnStylerespectively. - For compile-time missing values the kernel only outputs the empty string
''without embedding any language-specific placeholder; the placeholder is decided by the override side. - Derived dialects can declare
parentto inherit all overrides of an existing backend and override only the differences.
8. Return Values of Override Statements
Override statements can provide return values: like an ordinary alias, they return by default via the last item of the stack or explicitly via return <Value>.
The return value represents its compilation result and is passed into the _bound of other override statements that reference it, as well as into the corresponding parameter names.
Example:
@compileNode "BinaryOp" -> js {
~ The return value becomes this node's compilation result
'(' .. _bound.left .. ' ' .. _bound.op .. ' ' .. _bound.right .. ')'
}
9. Complete Examples
9.1 Supporting a New Language (Output Only)
@compileEntry mylang {
'main() { ' .. call @compileStart .. ' }'
}
@compileAlias print <Value val> -> mylang {
'print(' .. val .. ')'
}
9.2 JavaScript Backend (Simplified)
@compileEntry js {
'use strict;\n' .. call @compileStart
}
@compileNode "BinaryOp" -> js {
'(' .. _bound.left .. ' ' .. _bound.op .. ' ' .. _bound.right .. ')'
}
@compileNode "Literal" -> js {
if _bound.isNull {
'null'
} else {
call _bound.value.toCode
}
}
@compileNode "Variable" -> js {
_bound.name
}
@compileAlias let <Variable name> = <Value val> -> js {
'let ' .. name .. ' = ' .. val
}
@compileAlias const <Variable name> = <Value val> -> js {
'const ' .. name .. ' = ' .. val
}
@compileAlias print <Value val> -> js {
'console.log(' .. val .. ')'
}
@compileAlias if <Boolean condition> <Stack thenStack> -> js {
if _bound.elseStack {
'if (' .. condition .. ') {\n' .. thenStack .. '\n} else {\n' .. _bound.elseStack .. '\n}'
} else {
'if (' .. condition .. ') {\n' .. thenStack .. '\n}'
}
}
@compileAlias while <Boolean cond> <Stack body> -> js {
'while (' .. cond .. ') {\n' .. body .. '\n}'
}
@compileAlias for <Stack init> <Boolean cond> <Stack update> <Stack body> -> js {
'for (' .. init .. '; ' .. cond .. '; ' .. update .. ') {\n' .. body .. '\n}'
}
10. Stack Reuse and Scoping
Stack reuse rules:
- All compiler blocks share the same top-level context (the
@compileEntrycontext), even across different compiler blocks - Only compiler blocks for the same target language share a context
@compileWithshares the same context as@compileEntry
Helper method customization:
- You can define functions such as
setImport/setDepend/setRuntimeinside@compileEntryto handle dependencies - You can also append calls in different files via
@compileWith - Global variables should also be defined inside
@compileEntry
11. Constraints and Rules
- Universality principle: the compiler kernel (JS code) must not contain any language-specific modifications; all language-specific logic must be handled on the override side (Alia code)
- Built-in class compilation: compilation methods for built-in classes are written into the runtime base code
- Interface restriction: the compiler provides exactly eight interfaces —
@compileEntry,@compileWith,@compileStart,@compileConfig,@compile,@compileNode,@compileAlias,@compileClass— and no others - Entry return value: the return value of the last item in the
@compileEntryentry stack is the final compilation output - Compilation trigger: compilation must be triggered inside the entry via
call @compileStart