跳到主要内容

编译系统

编译系统是 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>

参数说明:

参数类型说明
langNameKey目标语言名称(如 js、nodejs、rs)
bodyStack编译入口栈,包含辅助函数定义、全局变量、编译触发等

示例:

~ 声明编译到 JavaScript 的入口
@compileEntry js {
let importList = ''

const setImport = alias <String moduleName> -> {
importList = importList .. 'import ' .. moduleName .. ';\n'
}

~ 触发编译并返回结果
return importList .. call @compileStart
}

执行顺序:

  1. 执行入口栈中除最后一项以外的所有语句(设置语句)
  2. 依次执行所有 @compileWith 追加的额外操作
  3. 执行入口栈的最后一项(返回值求值),其结果作为编译输出

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>]

参数说明:

参数类型说明
langNameExpression目标语言名称(如 "js"、"rs")
codeExpression待编译的 Alia 源代码字符串
sourceDirExpression(可选)源文件目录路径,用于编译过程中解析 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>

参数说明:

参数类型说明
nodeNameStringAST 节点类型名称(如 "BinaryOp"、"Literal")
langNameKey目标语言名称
bodyStack编译逻辑,可访问内置变量

内置变量:

变量类型说明
_astObject当前 AST 节点
_tokenArray原始标记流
_codeString源代码字符串
_boundObject子节点的编译结果(键值对)
_ctxObject编译上下文信息
compileFunction递归编译子节点
compileExprFunction编译子表达式

示例:

~ 编译 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 字段
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(含 source, alias, sourceDir, ext, resolvedPath, contentEncoded, contentStr, exports 等)
Programsentences
Stacksentences 或 body

3.2 @compileAlias​

设置语法关键字的编译覆写,可针对某一个特定 alias 提供高效的编译输出。

@compileAlias <Keys aliasDefinition> -> <Key langName> <Stack body>

与 @compileNode "AliasDefinition" 的区别:

  • @compileNode "AliasDefinition" 定义所有 alias 声明的通用编译方法
  • @compileAlias 可为某个特定的 alias 指定底层编译方法

内置变量:

变量类型说明
_astObject当前 AST 节点
_tokenArray原始标记流
_codeString源代码字符串
_boundObject编译后的参数绑定
_ctxObject编译上下文信息
compileFunction递归编译子节点
compileExprFunction编译子表达式
<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>

参数说明:

参数类型说明
classNameKey目标类/对象名。对应 class 声明名或导入模块别名(如 fs、io、Gtk)
langNameKey目标语言名称
payloadObject对象字面量 *[...],每个成员为一个方法的编译覆写或类级标记

payload 组织:

成员键值类型说明
<methodName>alias ... -> { ... }AliasDefinitionExpression,定义该方法在目标语言的编译逻辑。参数列表的辅助关键字与原始方法声明一致,Checker 会对参数类型与返回值进行静态检查
availableBoolean类/方法级可用标记。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 在当前作用域已声明
}
}
字段类型说明
mutableStackBoolean是否在可变栈内
inLoopBoolean是否在循环内
letHasVarBooleanlet/const 声明的变量是否已存在
hasMutVarsBoolean是否有可变栈变量
selfTypeString当前 self 类型('instance' 或 'static')
isExpressionBoolean是否在表达式上下文中
hasVar(name)Function检查变量是否已声明
isAliasName(name)Function检查名称是否为 alias
findAliasDef(aliasId)Function查找 alias 定义

6. 编译配置(@compileConfig)​

@compileConfig 为指定语言设置编译期配置,配置对象中的键值对供编译器内核与后端覆写查询。

@compileConfig <Key langName> <Object configs>

支持的配置键:

键类型默认值说明
supportedLibsArray-声明该语言编译时支持的标准库列表,测试系统据此跳过不支持的标准库编译测试
parentString-父语言名称。子语言未覆写的节点/函数/入口沿父语言链继承
autoReturnStyleString'prefix'栈体自动返回风格。'prefix' 在末尾语句前添加 return 前缀;'tail' 直接输出末尾语句作为尾表达式
statementSeparatorString';\n'多条语句编译结果的连接符
fallbackRuntimeStringnull无 @compileEntry 入口时兜底运行时 preamble 文件路径(相对源文件目录解析)
isolateStackScopeBooleanfalse栈体(StackLiteral/*{})编译时使用全新空变量集,局部 let/const 声明不映射为对外层变量的赋值,实现作用域隔离
nativeTypesObject-Alia 静态类型名 → 目标语言原生类型token 的映射(如 Rust 的 *[Number: 'f64', '[Number]': 'Vec'])。内核据此判断绑定/表达式是否可降级为目标语言的原生局部,以及数组类型是否映射为 Vec。内核不硬编码任何语言的原生类型词汇
valueWrapObject-Alia 静态类型名 → 包装模板的映射,模板用 ${code} 占位。Value 模式函数体的尾表达式若降级为原生表达式,内核按此模板将其包装回目标语言的运行时 Value(runtime::num(${code}) 等)
valueUnwrapObject-Alia 静态类型名 → 解包模板的映射,模板用 ${code} 占位。原生签名函数体的尾表达式/返回值为运行时 Value 时,内核按此模板将其解包为声明的原生返回类型(runtime::value_number(&(${code})) 等)
fnReturnWrapBooleanfalse声明表达式 Stack(含语言级转义为 Value 函数的匿名 alias)是否以「返回运行时 Value」模式编译。开启时内核会把尾表达式按 valueWrap 包装后返回
closureBoundaryAliasesArray-除用户自定义 alias 外,还将其 Stack 体参数降级为目标语言闭包的 builtin alias id 列表(如 ['builtin:try-catch', 'builtin:select'])。内核在捕获分析时把它计入闭包边界,避免闭包内对外层变量赋值的编译错误
runAlias-声明 alia run 时管理器解释执行的步骤。参数按名称注入:outDir(编译输出目录)始终提供,其余来自 CLI --key:value 参数(如 alia run --target:'web');未声明为可空(<Type? param>)且未提供的参数会报错
buildAlias-声明 alia build 时管理器解释执行的步骤。参数注入规则同 run
paramsArray-声明该语言消费的命令行参数名,供自省与文档。参数的实际读取与解释全部在覆写端完成(经编译期常量 __aliaParams),编译器内核不持有任何参数名知识
targetCapsObject-编译目标名 → 能力表的映射,能力为布尔开关,由后端自定义。如 *['web': *['noPackageManager': true]] 表示该目标不会安装第三方运行时
targetOutDirObject-编译目标名 → 产物输出目录的映射(如 *['web': './build/nodejs-web'])。未声明的目标沿用默认的 <outDir>/<lang>
targetStepsObject-编译目标名 → 步骤序列的映射(如 *['web': ['run']])。默认 ['build', 'run'];自行接管完整生命周期的目标(如自带安装与打包的打包器)声明为 ['run'] 即可跳过构建步骤

6.0.1 编译期常量与参数声明​

编译器在执行 @compileEntry 前注入以下变量。只有内核自身计算出的事实才是内置常量;调用方传入的参数(如 --target:web 中的 target)不属于内置参数,而是经 __aliaParams 参数袋传入,由后端在 params 中声明消费哪些名字并自行解释。

变量类型含义
__aliaOutDirString产物输出目录,由工具链依据项目结构计算
__aliaParamsObject调用方参数袋,键为 --key:value 的 key;不含 outDir/sourceDir 等内核自有键
__aliaConfigObject当前语言的 @compileConfig 对象本身
__aliaUsedImportsString入口及其传递 .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. 约束与规则​

  1. 普适性原则:编译器内核(JS 代码)不应包含任何针对某种语言特化的修改,所有语言特化逻辑必须在覆写端(Alia 代码)处理
  2. 内置类编译:内置类的编译方法写入运行时基础代码
  3. 接口限制:编译器仅提供 @compileEntry、@compileWith、@compileStart、@compileConfig、@compile、@compileNode、@compileAlias、@compileClass 八个接口,不提供其余任何接口
  4. 入口返回值:@compileEntry 入口栈的最后一项的返回值即为最终编译输出
  5. 编译触发:必须通过 call @compileStart 在入口中触发编译

下一步​