Skip to main content

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:

StageDescription
TokenizeSplits the source code into a token stream
AstifyConverts the token stream into an AST
Translation systemPerforms keyword substitution according to @translateAlias / @language
CheckType checking, unused variable warnings, type error detection
CompileTraverses the AST and generates target language code according to override rules
Backend compilationHands 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:

ParameterTypeDescription
langNameKeyTarget language name (e.g. js, nodejs, rs)
bodyStackThe 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:

  1. Execute all statements in the entry stack except the last (the setup statements)
  2. Execute in order all additional operations appended by @compileWith
  3. 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:

ParameterTypeDescription
langNameExpressionTarget language name (e.g. "js", "rs")
codeExpressionThe Alia source code string to compile
sourceDirExpression (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:

  • @compile shares the current compiler's aliasRegistry (all override rules)
  • Shares the current compiler's @compileNode/@compileAlias override registrations
  • Has an independent vars scope to avoid polluting the outer context with variables
  • langName may 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:

ParameterTypeDescription
nodeNameStringAST node type name (e.g. "BinaryOp", "Literal")
langNameKeyTarget language name
bodyStackCompilation logic, with access to built-in variables

Built-in variables:

VariableTypeDescription
_astObjectThe current AST node
_tokenArrayThe original token stream
_codeStringThe source code string
_boundObjectCompilation results of child nodes (key-value pairs)
_ctxObjectCompilation context information
compileFunctionRecursively compiles child nodes
compileExprFunctionCompiles 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
BinaryOpleft, op, right
UnaryOpop, operand
Literalvalue, isNull
Variablename, inScope
Callcallee, args, calleeType, callKind
MemberAccessobject, property
IndexAccessobject, index, accessKind, isAliasCall
ArrayLiteralelements
ObjectLiteralpropStrings, hasStatic
StackLiteralbody, isMutable
ConditionalExpressiontest, consequent, alternate
AliasDefinitionisAnonymous, aliasName, paramNamesString, body
AliasDefinitionExpressionparamNamesString, body
ImportStatementimportData (containing source, alias, sourceDir, ext, resolvedPath, contentEncoded, contentStr, exports, etc.)
Programsentences
Stacksentences 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 all alias declarations
  • @compileAlias can specify a low-level compilation method for one particular alias

Built-in variables:

VariableTypeDescription
_astObjectThe current AST node
_tokenArrayThe original token stream
_codeStringThe source code string
_boundObjectCompiled parameter bindings
_ctxObjectCompilation context information
compileFunctionRecursively compiles child nodes
compileExprFunctionCompiles sub-expressions
<paramName>Each parameter nameThe 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:

ParameterTypeDescription
classNameKeyTarget class/object name. Corresponds to a class declaration name or an imported module alias (e.g. fs, io, Gtk)
langNameKeyTarget language name
payloadObjectAn object literal *[...] whose members are per-method compilation overrides or class-level flags

payload organization:

Member KeyValue TypeDescription
<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
availableBooleanA 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 payload only 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 *[...] or const gtk = *[...]) and is executed and type-checked by the interpreter
  • At compile time, a MemberAccess call obj.method <args> (e.g. fs.write c to p) or a bare alias call (e.g. createWindow with app titled title, attributed to the Gtk class) runs tryMatchPattern against the pattern of the corresponding alias in the payload; on a match, the method body executes with the compiled parameters (<paramName>), and its return value is the compilation result of that call
  • Each alias body inside the payload behaves like @compileAlias and can access _ast/_token/_code/_bound/_rawBound/_ctx/compile/compileExpr and 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
}
}
FieldTypeDescription
mutableStackBooleanWhether inside a mutable stack
inLoopBooleanWhether inside a loop
letHasVarBooleanWhether a variable declared by let/const already exists
hasMutVarsBooleanWhether there are mutable stack variables
selfTypeStringThe current self type ('instance' or 'static')
isExpressionBooleanWhether in an expression context
hasVar(name)FunctionChecks whether a variable is declared
isAliasName(name)FunctionChecks whether a name is an alias
findAliasDef(aliasId)FunctionFinds 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:

KeyTypeDefaultDescription
supportedLibsArray-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
parentString-Parent language name. Nodes/functions/entries not overridden by the child language are inherited along the parent language chain
autoReturnStyleString'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
statementSeparatorString';\n'Separator joining the compiled results of multiple statements
fallbackRuntimeStringnullFallback runtime preamble file path when there is no @compileEntry (resolved relative to the source file directory)
isolateStackScopeBooleanfalseWhen 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
nativeTypesObject-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
valueWrapObject-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.)
valueUnwrapObject-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.)
fnReturnWrapBooleanfalseWhether 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
closureBoundaryAliasesArray-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
runAlias-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
buildAlias-The steps the manager interprets when alia build is declared. Parameter injection rules are the same as run
paramsArray-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
targetCapsObject-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
targetOutDirObject-Compilation target name → output directory, e.g. *['web': './build/nodejs-web']. Targets without an entry keep the default <outDir>/<lang>
targetStepsObject-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.

VariableTypeMeaning
__aliaOutDirStringOutput directory, computed by the toolchain from the project layout
__aliaParamsObjectCaller argument bag keyed by --key:value; excludes kernel-owned keys such as outDir/sourceDir
__aliaConfigObjectThe language's own @compileConfig object
__aliaUsedImportsStringAll 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.has only tests own keys. Do not use a read that can fall back to the enclosing scope (such as Object.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 LanguageStatusDescription
JavaScriptSupportedA complete compilation backend supporting all syntax features
NodejsSupportedA Node-specific backend built on the JS backend (including FFI)
RustSupportedThe 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 fallbackRuntime instead of being hard-coded in the kernel.
  • The statement separator and automatic return style are configured via @compileConfig statementSeparator / autoReturnStyle respectively.
  • 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 parent to 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 @compileEntry context), even across different compiler blocks
  • Only compiler blocks for the same target language share a context
  • @compileWith shares the same context as @compileEntry

Helper method customization:

  • You can define functions such as setImport / setDepend / setRuntime inside @compileEntry to handle dependencies
  • You can also append calls in different files via @compileWith
  • Global variables should also be defined inside @compileEntry

11. Constraints and Rules​

  1. 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)
  2. Built-in class compilation: compilation methods for built-in classes are written into the runtime base code
  3. Interface restriction: the compiler provides exactly eight interfaces — @compileEntry, @compileWith, @compileStart, @compileConfig, @compile, @compileNode, @compileAlias, @compileClass — and no others
  4. Entry return value: the return value of the last item in the @compileEntry entry stack is the final compilation output
  5. Compilation trigger: compilation must be triggered inside the entry via call @compileStart

Next Steps​