编译系统
编译系统是 Alia 语言的元编译机制,允许用户在 Alia 层面定义如何将 Alia 代码编译为目标语言代码。与传统编译器不同,Alia 的编译系统将编译逻辑本身暴露为可编程的接口——用户通过覆写(override)声明每种语法结构在目标语言中的映射方式,而非由编译器内核硬编码。
1. 编译流程概览
Alia 代码从源文件到目标代码的完整流程:
源代码 (.alia)
↓ Tokenize
标记流
↓ Astify
AST(抽象语法树)
↓ 翻译系统处理
翻译后的 AST
↓ Check(类型检查)
类型检查后的 AST
↓ Compile
目标语言代码
↓ 后端编译
可执行程序
各阶段说明:
| 阶段 | 说明 |
|---|---|
| Tokenize | 将源代码拆分为标记流 |
| Astify | 将标记流转换为 AST |
| 翻译系统 | 根据 @translateAlias / @language 进行关键字替换 |
| Check | 类型检查、未使用变量警告、类型错误检测 |
| Compile | 遍历 AST,根据覆写规则生成目标语言代码 |
| 后端编译 | 将生成的代码交由目标语言的原生编译器处理 |
编译模式与解释模式共享前四个阶段,仅在第五阶段分叉。
2. 编译入口
编译入口是编译流程的起点,负责声明目标语言、设置辅助代码、触发编译并返回最终输出。
2.1 @compileEntry
为指定目标语言声明编译入口。入口栈中的语句在编译时执行,其最后一项的返回值代表最终编译器输出。
@compileEntry <Key langName> <Stack body>
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
langName | Key | 目标语言名称(如 js、nodejs、rs) |
body | Stack | 编译入口栈,包含辅助函数定义、全局变量、编译触发等 |
示例:
~ 声明编译到 JavaScript 的入口
@compileEntry js {
let importList = ''
const setImport = alias <String moduleName> -> {
importList = importList .. 'import ' .. moduleName .. ';\n'
}
~ 触发编译并返回结果
return importList .. call @compileStart
}
执行顺序:
- 执行入口栈中除最后一项以外的所有语句(设置语句)
- 依次执行所有
@compileWith追加的额外操作 - 执行入口栈的最后一项(返回值求值),其结果作为编译输出
2.2 @compileWith
为已有入口追加额外操作,与 @compileEntry 共享同一套语境。多个 @compileWith 按声明顺序依次执行。
@compileWith <Key langName> <Stack body>
执行时 机:位于 @compileEntry 设置语句之后、返回值求值之前,因此其对语境的修改会影响入口的最终返回值。
示例:
~ main.alia
@compileEntry js {
'function main() {\n' .. call @compileStart .. '\n}'
}
~ utils.alia(被 import 后自动追加)
@compileWith js {
setImport 'fs'
}
第三方模组:编译入口 AST 中被 import 的 .alia 模块(含 mods/ 下安装的
模组)所包含的 @compileEntry / @compileWith / @compileNode / @compileAlias /
@compileConfig / @compileClass 会在入口自身指令之前被递归注册(只执行编译指令、不执行模块体,
按解析路径去重防环)。这使得第三方模组可以像标准库一样注入编译后端。
2.3 @compileStart
触发实际的编译过程。编译器遍历用户 AST 并生成目标语言代码,直接返回编译结果。
call @compileStart
说明:
- 必须在
@compileEntry的入口栈中调用 - 返回编译后的目标语言代码字符串
- 编译过程中遇到的覆写规则(
@compileNode/@compileAlias)会被自动应用
2.4 @compile(编译期指令)
编译期表达式,将任意 Alia 代码字符串编译到指定目标语言。与 @compileEntry 不同,@compile 是可在覆写逻辑中使用的通用编译方法,使编译器端无需特化处理任何目标语言。
@compile <Expression langName> <Expression code> [<Expression sourceDir>]
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
langName | Expression | 目标语言名称(如 "js"、"rs") |
code | Expression | 待编译的 Alia 源代码字符串 |
sourceDir | Expression(可选) | 源文件目录路径,用于编译过程中解析 import 依赖 |
返回值:编译后的目标语言代码字符串。
示例:
~ 在 ImportStatement 覆写中使用 @compile
@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 'const ' .. data.alias .. ' = ' .. compiled
}
~ 非 Alia 文件使用其他处理
return 'const ' .. data.alias .. ' = ' .. data.contentEncoded
}
~ 在编译入口中使用 @compile
@compileEntry js {
let compiled = @compile "js" "const x = 1"
~ compiled == "const x = 1"
}
执行机制:
@compile共享当前编译器的aliasRegistry(所有覆写规则)- 共享当前编译器的
@compileNode/@compileAlias覆写注册 - 拥有独立的
vars作用域,避免变量污染外部上下文 langName可指定不同于当前编译目标的语言
3. 覆写系统
覆写系统是编译系统的核心,允许用户为每种 AST 节点和语法关键字定义在目标语言中的编译方式。
3.1 @compileNode
设置 AST 节点的编译覆写,用于描述基础 Alia 语法的编译方法。
@compileNode <String nodeName> -> <Key langName> <Stack body>
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
nodeName | String | AST 节点类型名称(如 "BinaryOp"、"Literal") |
langName | Key | 目标语言名称 |
body | Stack | 编译逻辑,可访问内置变量 |
内置变量:
| 变量 | 类型 | 说明 |
|---|---|---|
_ast | Object | 当前 AST 节点 |
_token | Array | 原始标记流 |
_code | String | 源代码字符串 |
_bound | Object | 子节点的编译结果(键值对) |
_ctx | Object | 编译上下文信息 |
compile | Function | 递归编译子节点 |
compileExpr | Function | 编译子表达式 |
示例:
~ 编译 BinaryOp 节点到 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 字段(按节点类型):
| 节点类型 | _bound 字段 |
|---|---|
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(含 source, alias, sourceDir, ext, resolvedPath, contentEncoded, contentStr, exports 等) |
Program | sentences |
Stack | sentences 或 body |
3.2 @compileAlias
设置语法关键字的编译覆写,可针对某一个特定 alias 提供高效的编译输出。
@compileAlias <Keys aliasDefinition> -> <Key langName> <Stack body>
与 @compileNode "AliasDefinition" 的区别:
@compileNode "AliasDefinition"定义所有alias声明的通用编译方法@compileAlias可为某个特定的 alias 指定底层编译方 法
内置变量:
| 变量 | 类型 | 说明 |
|---|---|---|
_ast | Object | 当前 AST 节点 |
_token | Array | 原始标记流 |
_code | String | 源代码字符串 |
_bound | Object | 编译后的参数绑定 |
_ctx | Object | 编译上下文信息 |
compile | Function | 递归编译子节点 |
compileExpr | Function | 编译子表达式 |
<paramName> | 各参数名 | 编译后的参数值(与 alias 声明的参数名一致) |
示例:
~ 编译关键字 "print" 到 JavaScript
@compileAlias print <Value val> -> js {
'console.log(' .. val .. ')'
}
~ 编译关键字 "add" 到 JavaScript
@compileAlias add <Number a> and <Number b> -> js {
'(' .. a .. ' + ' .. b .. ')'
}
3.3 @compileClass
批量覆写一个类/对象(如模块对象 fs/io 或自定义 class)的所有公开方法,替代分散的多个 @compileAlias。这是标准库与第三方库推荐的编译映射方式。
@compileClass <Key className> -> <Key langName> <Object payload>
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
className | Key | 目标类/对象名。对应 class 声明名或导入模块别名(如 fs、io、Gtk) |
langName | Key | 目标语言名称 |
payload | Object | 对象字面量 *[...],每个成员为一个方法的编译覆写或类级标记 |
payload 组织:
| 成员键 | 值类型 | 说明 |
|---|---|---|
<methodName> | alias ... -> { ... } | AliasDefinitionExpression,定义该方法在目标语言的编译逻辑。参数列表的辅助关键字与原始方法声明一致,Checker 会对参数类型与返回值进行静态检查 |
available | Boolean | 类/方法级可用标记。available: false 时该类或该方法在该语言不可用(编译时回退到通用路径或空输出),默认为 true。方法级 available: false 覆盖类级 |
语义:
payload仅定义对外暴露方法的编译映射,运行时类本身仍需在普通代码中定义(如class AliaFs *[...]或const gtk = *[...]),由解释器执行与类型检查- 编译时
MemberAccess调用obj.method <args>(如fs.write c to p)或裸alias调用(如createWindow with app titled title归属的Gtk类)会按payload中对应alias的pattern进行tryMatchPattern,匹配后以编译后参数(<paramName>)执行方法体,返回值即为该调用的编译结果 payload内每个alias的体与@compileAlias相同,可访问_ast/_token/_code/_bound/_rawBound/_ctx/compile/compileExpr及参数名
类型检查:
Checker 遍历 payload 的每个 alias 方法,校验参数类型的合法性、栈体类型与声明返回类型的一致性,available 必须为 Boolean
示例:
~ 文件系统模块:fs 对象的方法批量映 射到 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
]
~ 仅对外暴露 read/write,其余方法在此语言隐藏
@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
]
~ 裸 alias 归属到 Gtk 类的批量映射(createWindow 等为顶层 alias,归属 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. 栈参数的编译
覆写语句中的 <Stack> 参数在绑定前已递归编译为目标语言代码字符串,直接以参数名引用即可:
@compileAlias if <Boolean condition> <Stack thenStack> -> js {
'if (' .. condition .. ') {\n' .. thenStack .. '\n}'
}
@compileNode "StackLiteral" 覆写通过 _bound.body 读取已编译的栈体:
@compileNode "StackLiteral" -> js {
_bound.body
}
5. 编译上下文
_ctx 对象提供了编译过程中的上下文信息,供覆写逻辑查询:
@compileNode "someNode" -> js {
if _ctx.inLoop {
~ 当前位于循环内部
}
if _ctx.mutableStack {
~ 当前位于可变栈内部
}
if _ctx.hasVar 'myVar' {
~ myVar 在当前作用域已声明
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
mutableStack | Boolean | 是否在可变栈内 |
inLoop | Boolean | 是否在循环内 |
letHasVar | Boolean | let/const 声明的变量是否已存在 |
hasMutVars | Boolean | 是否有可变栈变量 |
selfType | String | 当前 self 类型('instance' 或 'static') |
isExpression | Boolean | 是否在表达式上下文中 |
hasVar(name) | Function | 检查变量是否已声明 |
isAliasName(name) | Function | 检查名称是否为 alias |
findAliasDef(aliasId) | Function | 查找 alias 定义 |
6. 编译配置(@compileConfig)
@compileConfig 为指定语言设置编译期配置,配置对象中的键值对供编译器内核与后端覆写查询。
@compileConfig <Key langName> <Object configs>
支持的配置键:
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
supportedLibs | Array | - | 声明该语言编译时支持的标准库列表,测试系统据此跳过不支持的标准库编译测试 |
parent | String | - | 父语言名称。子语言未覆写的节点/函数/入口沿父语言链继承 |
autoReturnStyle | String | 'prefix' | 栈体自动返回风格。'prefix' 在末尾语句前添加 return 前缀;'tail' 直接输出末尾语句作为尾表达式 |
statementSeparator | String | ';\n' | 多条语句编译结果的连接符 |
fallbackRuntime | String | null | 无 @compileEntry 入口时兜底运行时 preamble 文件路径(相对源文件目录解析) |
isolateStackScope | Boolean | false | 栈体(StackLiteral/*{})编译时使用全新空变量集,局部 let/const 声明不映射为对外层变量的赋值,实现作用域隔离 |
nativeTypes | Object | - | Alia 静态类型名 → 目标语言原生类型token 的映射(如 Rust 的 *[Number: 'f64', '[Number]': 'Vec'])。内核据此判断绑定/表达式是否可降级为目标语言的原生局部,以及数组类型是否映射为 Vec。内核不硬编码任何语言的原生类型词汇 |
valueWrap | Object | - | Alia 静态类型名 → 包装模板的映射,模板用 ${code} 占位。Value 模式函数体的尾表达式若降级为原生表达式,内核按此模板将其包装回目标语言的运行时 Value(runtime::num(${code}) 等) |
valueUnwrap | Object | - | Alia 静态类型名 → 解包模板的映射,模板用 ${code} 占位。原生签名函数体的尾表达式/返回值为运行时 Value 时,内核按此模板将其解包为声明的原生返回类型(runtime::value_number(&(${code})) 等) |
fnReturnWrap | Boolean | false | 声明表达式 Stack(含语言级转义为 Value 函数的匿名 alias)是否以「返回运行时 Value」模式编译。开启时内核会把尾表达式按 valueWrap 包装后返回 |
closureBoundaryAliases | Array | - | 除用户自定义 alias 外,还将其 Stack 体参数降级为目标语言闭包的 builtin alias id 列表(如 ['builtin:try-catch', 'builtin:select'])。内核在捕获分析时把它计入闭包边界,避免闭包内对外层变量赋值的编译错误 |
run | Alias | - | 声明 alia run 时管理器解释执行的步骤。参数按名称注入:outDir(编译输出目录)始终提供,其余来自 CLI --key:value 参数(如 alia run --target:'web');未声明为可空(<Type? param>)且未提供的参数会报错 |
build | Alias | - | 声明 alia build 时管理器解释执行的步骤。参数注入规则同 run |
params | Array | - | 声明该语言消费的命令行参数名,供自省与文档。参数的实际读取与解释全部在覆写端完成(经编译期常量 __aliaParams),编译器内核不持有任何参数名知识 |
targetCaps | Object | - | 编译目标名 → 能力表的映射,能力为布尔开关,由后端自定义。如 *['web': *['noPackageManager': true]] 表示该目标不会安装第三方运行时 |
targetOutDir | Object | - | 编译目标名 → 产物输出目录的映射(如 *['web': './build/nodejs-web'])。未声明的目标沿用默认的 <outDir>/<lang> |
targetSteps | Object | - | 编译目标名 → 步骤序列的映射(如 *['web': ['run']])。默认 ['build', 'run'];自行接管完整生命周期的目标(如自带安装与打包的打包器)声明为 ['run'] 即可跳过构建步骤 |
6.0.1 编译期常量与参数声明
编译器在执行 @compileEntry 前注入以下变量。只有内核自身计算出的事实才是内置常量;调用方传入的参数(如 --target:web 中的 target)不属于内置参数,而是经 __aliaParams 参数袋传入,由后端在 params 中声明消费哪些名字并自行解释。
| 变量 | 类型 | 含义 |
|---|---|---|
__aliaOutDir | String | 产物输出目录,由工具链依据项目结构计算 |
__aliaParams | Object | 调用方参数袋,键为 --key:value 的 key;不含 outDir/sourceDir 等内核自有键 |
__aliaConfig | Object | 当前语言的 @compileConfig 对象本身 |
__aliaUsedImports | String | 入口及其传递 .alia 依赖中全部 import 源,逗号包裹,如 ,fs,gtk-alia, |
因此新增一个 CLI 参数不需要改动编译器内核,只需在后端声明并在覆写端处理:
~ 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'] }
...
}
运行期则由 run/build Alias 的形参按名接收:声明 <String? target> 即可让 alia run --target:web 把该值注入步骤体内解析。
注意:
Object.has只判定自有键。请勿用Object.get之类的可回退到外层语境的读取来做「注册表是否已登记某名字」的判断,否则会被同名外层变量误命中。
6.1 浅合并
同一语言可多次声明 @compileConfig,新声明的键覆盖已有同名键,未声明的键继承基座配置。这允许第三方模组在不重复 parent/supportedLibs 等基座键的前提下仅覆写 run/build/targetSteps 等。
沿父语言链(parent)继承时,更近的语言所声明的同名 alias 与变量优先,父语言的重复声明不会覆盖之。
6.2 run/build 可空参数
@compileConfig 的 run/build Alias 可声明可空参数(如 <String? target>)。当 CLI 未提供对应的 --key:value 参数时,该参数绑定为 null,不会抛出 requires parameter 错误。
@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']
}
]
语言无关的缺失值:编译期缺失值(未匹配覆写的节点、缺失的参数)由内核以空串 '' 输出,不嵌入任何语言特化的占位文本(如 JS 的 undefined)。是否及如何将其替换为特定占位符由覆写端自行决定,内核保持语言无关。
示例:
@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']
}]
执行 alia run 时,管理器解析 main.alia 中的 @compileConfig rs,将 outDir 按参数名注入该 Alias 并在解释器中执行。
7. 支持的目标语言
编译系统当前提供以下后端:
| 目标语言 | 状态 | 说明 |
|---|---|---|
| JavaScript | 已支持 | 完整的编译后端,支持所有语法特性 |
| Nodejs | 已支持 | 基于 JS 后端的 Node 专属后端(含 FFI) |
| Rust | 已支持 | Rust 后端,支持核心语法、类/对象、Stack/async、文件导入与 Args/FileSystem/IO/Process/Web 标准库(supportedLibs);FFI 为 Node 专属 |
添加新语言的支持:
仅需声明 @compileEntry 和相应的 @compileNode / @compileAlias 覆写即可,无需修改编译器内核。编译器内核具有普适性,任何语言特化的逻辑都应在覆写端处理。
- 语言间差异化的运行时 preamble 通过
@compileConfig fallbackRuntime声明,不再由内核硬编码。 - 语句连接符、自动返回风格分别通过
@compileConfig statementSeparator/autoReturnStyle配置。 - 编译期缺失值内核仅输出空串
'',不嵌入任何语言特化占位符;占位符由覆写端决定。 - 衍生方言可声明
parent继承已有后端的全部覆写,仅覆写差异部分。
8. 覆写语句的返回值
覆写语句可以提供返回值,与普通 alias 一样,通过栈的最后一项默认返回或 return <Value> 进行返回。
返回 值代表其编译结果,将传递到引用的其他覆写语句的 _bound 和对应参数名中。
示例:
@compileNode "BinaryOp" -> js {
~ 返回值将作为此节点的编译结果
'(' .. _bound.left .. ' ' .. _bound.op .. ' ' .. _bound.right .. ')'
}
9. 完整示例
9.1 支持一门新语言(仅输出)
@compileEntry mylang {
'main() { ' .. call @compileStart .. ' }'
}
@compileAlias print <Value val> -> mylang {
'print(' .. val .. ')'
}
9.2 JavaScript 后端(简化版)
@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. 栈复用与作用域
栈复用规则:
- 所有编译器块中共享同一套顶层语境(
@compileEntry的语境),即使不在同一个编译器块里 - 仅同一个目标语言的编译器块共享语境
@compileWith与@compileEntry共享同一语境
辅助方法自定义:
- 可在
@compileEntry中定义setImport/setDepend/setRuntime等函数用于处理依赖 - 也可通过
@compileWith在不同文件中追加调用 - 全局变量也应在
@compileEntry中定义
11. 约束与规则
- 普适性原则:编译器内核(JS 代码)不应包含任何针对某种语言特化的修改,所有语言特化逻辑必须在覆写端(Alia 代码)处理
- 内置类编译:内置类的编译方法写入运行时基础代码
- 接口限制:编译器仅提供
@compileEntry、@compileWith、@compileStart、@compileConfig、@compile、@compileNode、@compileAlias、@compileClass八个接口,不提供其余任何接口 - 入口返回值:
@compileEntry入口栈的最后一项的返回值即为最终编译输出 - 编译触发:必须通过
call @compileStart在入口中触发编译