Files
endorphin-aster-vm/docs/runtime/compiler.md
T
2026-10-20 01:08:51 +08:00

6.4 KiB
Raw Blame History

最小源码编译与执行链路

当前已连接 reader → lexer → PEG parser → AST → 名称/作用域分析 → wordcode → 代码对象 → VM。实现位于独立 compiler;frontend 只负责语法,objects 拥有指令/代码数据与验证,core 解释字节码。用户无需手写指令,也不需要先补全 Python 基础类。

接口与阶段

接口 行为
analyze(&AstRoot, &CompileOptions) 返回 SymbolTable:根与函数作用域、CodeLayout、每次名称读取的位置和 Local/Global 绑定
compile_ast(&AstRoot, &SourceUnit, &CompileOptions) 校验节点与原文范围、分析并生成 CompiledCode;不验证 AST 与原文的语义等价性
compile_source(&SourceUnit, ParseMode, &CompileOptions) 先调用 frontend 解析,再编译;错误携带原始文件/流/URI 与修订来源及一基行列
CompiledCode::load(&mut Vm) 将模板及子模板装载为该 VM 的代码对象,返回一个拥有根的 ObjectRef

解析错误沿用 Lexical/Syntax,名称错误为 Semantic,不支持的执行特性或代码验证错误为 Compilation。编译不修改 VM 或全局字典;词法动作也不执行语言代码。模板提供 instructions/layout/name/filename/first_line/children 只读检查入口。生成时使用已有 EndorCode 验证器检查槽位、操作数和栈平衡;装载再次验证真实常量。

名称与作用域

首版是模块作用域和函数作用域。模块先登记全部函数名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 按声明顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。

函数内先收集全部局部名称,再按语句顺序检查初始化。后面声明的 let 会遮蔽整个函数中的同名全局,但提前读取报错;let x = x 也会报未初始化。参数、局部绑定和同一模块的函数不允许在各自作用域内重复声明;参数或局部可以遮蔽模块函数。let 当前没有赋值/重绑定语法。嵌套函数/闭包分析和块级作用域尚未实现。

CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称,默认空。不认识的名称在编译时报错;已声明的宿主名称仍必须在执行时存在,否则 VM 返回 NameNotFound。编译器不自动注入 print、默认基础类或标准库,也不读取这些名字的值。类型注解只保留语法,不参与名称绑定、运行时求值或类型检查。

指令与代码对象

源结构 生成方式
字符串、整数、十进制常量 放入常量表并 LoadConst;数字经现有 EndorNumber 解析,不转换为 f64
数字字面量前的一个或多个负号 折叠符号为精确常量;不是任意表达式的一元算术
局部读取/let LoadFast/StoreFast;参数和局部名称在 CodeLayout 中
全局读取 LoadGlobal,使用该代码对象的 names 表
函数声明 子代码模板 → LoadConst(code) → MakeFunction(0, 0) → StoreGlobal
普通调用 求值 callee → PushNull → 从左到右求值位置参数 → Call(argc)
成员读取 求值对象 → LoadAttr;沿用 VM 属性协议,无自动 self 绑定
return 求值结果 → ReturnValue;无值 return 或 AST 函数末尾无 return 时生成 nil

源码 parser 仍要求函数体为 let* 加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。

二元算术、非字面量的一元负号和符号值 .name 目前产生明确 Compilation 诊断,不伪造运行结果。导入、类、Agent、条件/循环、关键字参数等仍未实现。函数参数个数和对象是否可调用由 VM 在运行时检查。

装载、执行与所有权

CompiledCode 是纯 Rust 模板,嵌套代码常量在模板内部使用占位槽,装载时替换为当前 VM 的子代码对象;模板不含跨 VM 句柄,可复用到多个 VM。父代码对象成功分配后持有子代码,装载成功或失败都会释放临时子根。返回的根代码对象由调用方 release。

宿主为模块提供 globals 字典,调用 vm.execute(code, globals) 发布函数,然后从字典借用函数句柄并调用 vm.call。函数共享装载后的代码,反复调用不重新编译;execute/call 的对象结果持有根,用后 release_value。函数指向 globals,globals 保存函数,因此宿主需保留命名空间期间的根,并在销毁时清空字典或释放根后显式 collect_cycles。编译器不清空宿主字典。

Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let 或表达式。声明/绑定返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。Lyra 当前没有 REPL、续行或自动会话管理。

代码对象保存来源字符串、函数名和定义起始行,现有 VM traceback 保存该位置及指令偏移;没有每条指令对应的精确源码行/列映射。现有 ExecutionLimits 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。

Lyra 宿主与检查

lyra run [--entry name] <file|-> 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。lyra eval <file|-> 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 VM str;失败仅输出 stderr 并返回非零退出码。文件和 stdin 严格 UTF-8,stdin 到 EOF 后才处理。Lyra 在成功或失败后释放代码/结果并清空自己创建的命名空间。

可执行文件见 示例目录,API/所有权与错误路径见 pipeline.rs,进程入口见 CLI 检查。引擎检查由 Aster CI 覆盖,进程入口检查由独立 Lyra CI 覆盖;本地只做静态检查,不据此宣称实际执行通过,见 验证约定。返回 运行时索引。