Files
endorphin-aster-vm/crates/compiler/src/analysis.rs
T

562 lines
28 KiB
Rust

//! compile_ast 调用的名称与确定初始化分析;只构造 Scope/CodeLayout,不分配 VM 对象。
//! 先登记所有顶层声明,再分析函数体;codegen 根据名称、跨度和绑定索引选择 LoadFast/LoadGlobal。
use std::collections::BTreeSet;
use endor_core::CodeLayout;
use frontend::ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, Function, Spanned, StmtKind};
use frontend::{Diagnostic, DiagnosticKind, Span};
use crate::{CompileOptions, diagnostic};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 根节点或函数的编译上下文;生成器据此区分局部槽写入与全局环境写入。
pub enum ScopeKind {
// 模块声明入口,执行后通常返回 Nil。
Module,
// 单个表达式入口,直接返回其求值结果。
Expression,
// 单条交互输入,可声明、赋值或返回表达式结果。
Interactive,
// 函数或类方法体,拥有参数、局部槽和 return 语义。
Function,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// expression 解析的读取位置;codegen 使用此索引编码对应的加载指令。
pub enum NameBinding {
// CodeLayout.locals 中的位置,参数也占局部槽。
Local(usize),
// CodeLayout.names 中的位置,运行时先查 globals 再查捕获的 builtins。
Global(usize),
}
#[derive(Debug, Clone, PartialEq, Eq)]
/// 一次名称表达式的解析结果;名字加源码跨度定位具体使用点,避免同名出现互相混淆。
pub struct NameUse {
// 本次读取的名称文本,生成器与 span 联合匹配,取得该表达式的绑定。
pub name: String,
// 原源码中的半开字节范围,用于诊断及生成阶段定位。
pub span: Span,
// 名称读取已解析到局部槽或全局名字索引。
pub binding: NameBinding,
}
#[derive(Debug, Clone)]
/// 一个代码单元的布局与读取记录,由 analyze/function_scope 构造,Emitter 借用。
pub struct Scope {
// 决定赋值存局部还是全局,以及是否允许 return。
pub kind: ScopeKind,
// 根的合成诊断名或源码函数名;生成器同时用 span 区分不同类的同名方法。
pub name: String,
// 原源码中的半开字节范围,用于诊断及生成阶段定位。
pub span: Span,
// VM 代码验证及帧分配需要的参数、局部名和名字表。
pub layout: CodeLayout,
// 每次名称表达式的绑定,按分析访问顺序追加,供生成器按跨度检索。
pub references: Vec<NameUse>,
}
#[derive(Debug, Clone)]
/// analyze 返回、compile_ast 交给 codegen 的分析结果;根布局和每个函数体布局分开保存。
/// 字段公开以供宿主/测试检查,但改动此表不会更新原 AST,也没有公开入口把任意改表直接编译。
pub struct SymbolTable {
// 根代码单元,即模块、表达式或交互输入的作用域。
pub root: Scope,
/// Function and class-method scopes in declaration order. Nested declarations
/// are rejected until source closure semantics are implemented.
// 顶层函数和类方法按声明顺序展平;生成器以名称和跨度查找对应体。
pub functions: Vec<Scope>,
}
/// 本文件所有拒绝路径共用,把具体节点跨度与说明包装为 Semantic 诊断。
fn error(span: Span, message: impl Into<String>) -> Diagnostic {
diagnostic(DiagnosticKind::Semantic, span, message)
}
/// analyze 登记顶层声明时调用,统一取得函数或类的名字节点(含诊断位置)。
fn declaration_name(declaration: &Decl) -> &frontend::ast::Name {
// 按声明种类取相同用途的名字,不把类方法当作顶层全局声明。
match &declaration.kind {
DeclKind::Function(function) => &function.name,
DeclKind::Class(class) => &class.name,
}
}
/// Resolve a closed source unit plus explicitly declared host globals.
/// Function locals are collected before reading expressions; a later let
/// shadows the global for the entire function, but cannot be read early.
/// compile_ast 和直接分析测试调用;options 仅声明额外全局名,返回根及各函数的绑定布局。
/// 函数体先收集所有局部名,因此后面的 let 也会遮蔽前面的全局读取,但未初始化读取会报错。
/// 直接调用只做本阶段的名称/控制流分析,不执行 AstDocument 跨度验证、类型解析或字节码验证;
/// 需要完整流水线的宿主使用 compile_ast/compile_source,不能把此处成功视为代码已可执行。
pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable, Diagnostic> {
// 交互模式要求恰好一条语句;空输入或批量输入不是本编译入口支持的单位。
if let AstRoot::Interactive(input) = root
&& input.statements.len() != 1
{
return Err(error(
input.span,
"interactive input must contain exactly one statement",
));
}
// 宿主名称先去重,后续加入源码声明;这里只登记文本,不校验其是否符合词法名称规则或提供 VM 值。
let mut globals: BTreeSet<String> = options.globals.iter().cloned().collect();
// 在分析任何代码前拒绝空宿主名,避免不可解析的名称混入绑定集合。
if globals.contains("") {
return Err(error(root.span(), "host global names must not be empty"));
}
// 提取根级函数/类声明,表达式根没有声明;交互输入只取声明语句。
let declarations: Vec<&Decl> = match root {
AstRoot::Module(program) => program.declarations.iter().collect(),
AstRoot::Interactive(input) => input
.statements
.iter()
// 交互输入仅声明语句进入预登记列表,其余语句稍后按执行语义分析。
.filter_map(|statement| match &statement.kind {
StmtKind::Declaration(declaration) => Some(declaration),
_ => None,
})
.collect(),
AstRoot::Expression(_) => Vec::new(),
};
// 展平顶层函数及类方法,保留原声明顺序供统一分析。
let functions: Vec<&Function> = declarations
.iter()
// 类本身不生成函数体作用域,其 methods 逐个参与分析。
.flat_map(|declaration| match &declaration.kind {
DeclKind::Function(function) => vec![function],
DeclKind::Class(class) => class.methods.iter().collect(),
})
.collect();
// 按根类型选作用域种类及用于代码诊断的合成名字。
let (kind, name) = match root {
AstRoot::Module(_) => (ScopeKind::Module, "<module>"),
AstRoot::Expression(_) => (ScopeKind::Expression, "<expression>"),
AstRoot::Interactive(_) => (ScopeKind::Interactive, "<interactive>"),
};
// 根作用域记录声明、属性等名字表及根表达式读取,生成器随后借用它。
let mut root_scope = Builder::new(kind, name, root.span());
// 单独检查本源码顶层重名;宿主同名声明不在这个集合中。
let mut declared = BTreeSet::new();
// 遍历根级声明;首轮注册名称,次轮检查类结构,保证函数体分析时名字已齐全。
for declaration in &declarations {
// 取得声明的名字和精确跨度,重名错误定位到名字本身。
let name = declaration_name(declaration);
declare(&mut declared, &name.text, name.span)?;
globals.insert(name.text.clone());
root_scope.intern(&name.text);
}
// 遍历根级声明;首轮注册名称,次轮检查类结构,保证函数体分析时名字已齐全。
for declaration in &declarations {
// 类声明额外检查基类名和成员结构,普通函数跳过这一段。
if let DeclKind::Class(class) = &declaration.kind {
// 未指定基类时默认 Object;这里只确认名称合法,实际 Type 在执行时解析。
let base_name = class
.base
.as_ref()
.map_or("Object", |base| base.text.as_str());
// 基类必须是宿主或源码已声明的名称,不隐式引入未知全局。
if !globals.contains(base_name) {
return Err(error(
class.span,
format!("class base is not supplied: {base_name}"),
));
}
root_scope.intern(base_name);
// 字段与方法共用类内去重集合,不能跨两类成员重复名称。
let mut members = BTreeSet::new();
// 字段名参与类成员去重;类型名留给后续代码签名/运行时类型协议处理。
for field in &class.fields {
declare(&mut members, &field.name.text, field.span)?;
}
// 方法名也参与去重,且必须显式接收第一个 self 参数。
for method in &class.methods {
declare(&mut members, &method.name.text, method.span)?;
// 检查第一个参数确实存在且名为 self,不自动插入接收者。
if method
.parameters
.first()
.is_none_or(|parameter| parameter.name.text != "self")
{
return Err(error(
method.span,
"class method requires an explicit first self parameter",
));
}
}
}
}
// Analyze all bodies after registering all functions, enabling forward
// references and recursion without instantiating function objects here.
// 所有顶层名注册完才解析函数体,因此允许函数前向引用及递归;任一错误中止整体。
let scopes = functions
.iter()
.map(|function| function_scope(function, &globals))
.collect::<Result<Vec<_>, _>>()?;
// 声明预处理后,按根类型分析真正需要求值的内容。
match root {
// 模块根只含已处理的声明,没有额外顶层语句需要分析。
AstRoot::Module(_) => {}
AstRoot::Expression(expression) => root_scope.expression(expression, &globals)?,
// 交互根按唯一语句的种类决定名称及赋值规则。
AstRoot::Interactive(input) => {
// analyze 开头已保证只有一条,仍按列表结构遍历 AST。
for statement in &input.statements {
// 区分绑定、赋值、成员更新与控制流,不能把 return 当成交互结果。
match &statement.kind {
// 新绑定先检查重名并分析右值,完成后才把名称加入可用全局集合。
StmtKind::Binding(binding) => {
declare(&mut declared, &binding.name.text, binding.name.span)?;
root_scope.expression(&binding.value, &globals)?;
globals.insert(binding.name.text.clone());
root_scope.intern(&binding.name.text);
}
// 交互赋值先解析右值,再登记写入目标;目标无需预先存在。
StmtKind::Assignment(assignment) => {
root_scope.expression(&assignment.value, &globals)?;
globals.insert(assignment.name.text.clone());
root_scope.intern(&assignment.name.text);
}
// 成员写入分析接收对象与右值,并把属性名加入名字表。
StmtKind::MemberAssignment(assignment) => {
root_scope.expression(&assignment.object, &globals)?;
root_scope.expression(&assignment.value, &globals)?;
root_scope.intern(&assignment.name.text);
}
// 声明已经预登记且函数体已统一分析,不能重复分析。
StmtKind::Declaration(_) => {}
// 交互表达式只需验证读取,不创建新绑定。
StmtKind::Expression(expression) => {
root_scope.expression(expression, &globals)?
}
// 复合语句先收集块内名称到根名字表,再复用语句控制流分析。
StmtKind::If(_) | StmtKind::While(_) => {
// 借一个节点组成切片,复用预收集和控制流分析,不克隆 AST。
let statements = std::slice::from_ref(statement);
collect_locals(
statements,
&mut declared,
&mut root_scope.scope.layout.names,
)?;
globals.extend(root_scope.scope.layout.names.iter().cloned());
root_scope.statements(statements, &globals)?;
}
// 根交互语句没有函数帧,拒绝 return。
StmtKind::Return(_) => {
return Err(error(statement.span, "return outside a function"));
}
}
}
}
}
Ok(SymbolTable {
root: root_scope.scope,
functions: scopes,
})
}
/// 顶层、参数、局部及类成员共用的去重检查;仅向传入集合登记,不分配布局索引。
fn declare(names: &mut BTreeSet<String>, name: &str, span: Span) -> Result<(), Diagnostic> {
// 空名或重复插入失败立即报错;成功 insert 同时完成名称登记。
if name.is_empty() || !names.insert(name.to_owned()) {
return Err(error(
span,
format!("duplicate or empty declaration: {name}"),
));
}
Ok(())
}
/// analyze 为每个顶层函数/类方法调用;参数先占局部槽,再收集全体局部名并按控制流检查初始化。
fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Scope, Diagnostic> {
// 新建函数/方法作用域,局部布局及初始化状态彼此独立。
let mut builder = Builder::new(ScopeKind::Function, &function.name.text, function.span);
// 跟踪参数和局部名是否重复,与当前路径是否初始化分开记录。
let mut locals = BTreeSet::new();
// 参数在函数进入时已有值,按声明顺序同时登记参数表和前部局部槽。
for parameter in &function.parameters {
declare(&mut locals, &parameter.name.text, parameter.name.span)?;
builder
.scope
.layout
.parameters
.push(parameter.name.text.clone());
builder
.scope
.layout
.locals
.push(parameter.name.text.clone());
builder.initialized.insert(parameter.name.text.clone());
}
collect_locals(
&function.body.statements,
&mut locals,
&mut builder.scope.layout.locals,
)?;
builder.statements(&function.body.statements, globals)?;
Ok(builder.scope)
}
/// function_scope 或交互复合语句调用;递归预分配函数级名称,不在此推断执行后是否已初始化。
/// let 重复声明报错,普通赋值首次出现才追加槽,if/while 块共享同一组名称。
fn collect_locals(
statements: &[frontend::ast::Stmt],
locals: &mut BTreeSet<String>,
layout: &mut Vec<String>,
) -> Result<(), Diagnostic> {
// 依源码顺序遍历当前语句序列,递归进入块但不创建块级局部作用域。
for statement in statements {
// 预收集阶段仅关心能引入名称的语句或嵌套声明拒绝规则。
match &statement.kind {
// let 必须是首次声明,即使出现在不同分支也不能重复同一函数级名。
StmtKind::Binding(b) => {
declare(locals, &b.name.text, b.name.span)?;
layout.push(b.name.text.clone());
}
// 赋值允许写已有名;仅第一次出现时追加一个布局槽。
StmtKind::Assignment(a) => {
// 首次出现的赋值目标才分配槽;后续普通赋值复用原槽。
if locals.insert(a.name.text.clone()) {
layout.push(a.name.text.clone());
}
}
// 两侧的名称都预分配;这里不决定分支之后是否能读取它们。
StmtKind::If(b) => {
collect_locals(&b.body.statements, locals, layout)?;
// 预收集 else 中的局部名,与 then 和外围共享同一函数级布局。
if let Some(other) = &b.otherwise {
collect_locals(&other.statements, locals, layout)?;
}
}
// 循环体也预分配函数级名字,但执行零次的情况由后续初始化分析处理。
StmtKind::While(b) => collect_locals(&b.body.statements, locals, layout)?,
// 源码闭包/嵌套类尚未支持,不生成看似合法但缺捕获语义的布局。
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
"nested declarations are not compiled yet",
));
}
// 不引入名字的表达式、返回和成员写入留给后续语句分析。
_ => {}
}
}
Ok(())
}
/// 分析单个代码单元的可变状态;Scope 是输出,initialized 是当前可达路径上确定初始化的名称。
struct Builder {
// 正在构造的作用域,最终移交 SymbolTable。
scope: Scope,
// 仅控制流中保证赋值的局部名;分支取交集,循环出口恢复进入前集合。
initialized: BTreeSet<String>,
}
impl Builder {
/// analyze/function_scope 创建空分析器;源码身份和布局初始值在这里统一设置。
fn new(kind: ScopeKind, name: &str, span: Span) -> Self {
Self {
scope: Scope {
kind,
name: name.into(),
span,
layout: CodeLayout::default(),
references: Vec::new(),
},
initialized: BTreeSet::new(),
}
}
/// 分析名称/属性/声明时调用;复用或追加名字表项,保证生成器查索引时必能找到。
fn intern(&mut self, name: &str) -> usize {
// 按文本查找已登记名字;命中时保留稳定索引。
if let Some(index) = self
.scope
.layout
.names
.iter()
.position(|candidate| candidate == name)
{
index
} else {
// 没有现有名字索引才追加,保留此前已记录的索引。
self.scope.layout.names.push(name.to_owned());
self.scope.layout.names.len() - 1
}
}
/// Function-scoped locals; branches merge definite initialization. A loop
/// may execute zero times, so its writes do not initialize later reads.
/// 函数体与复合语句递归调用;返回本语句序列是否所有可达路径都已 return。
fn statements(
&mut self,
statements: &[frontend::ast::Stmt],
globals: &BTreeSet<String>,
) -> Result<bool, Diagnostic> {
// 表示当前语句序列是否所有可达路径都已返回,供后继不可达检查使用。
let mut returned = false;
// 按源码顺序推进当前可达路径的初始化状态,检查每条后继语句。
for statement in statements {
// 前面的 return 或双侧必返分支已终止所有路径,后继语句报不可达。
if returned {
return Err(error(
statement.span,
"statement after return is unreachable",
));
}
// 按控制流检查实际读取与初始化,不重新分配预收集过的局部槽。
match &statement.kind {
// 先分析右值,之后才把本局部标记已初始化,防止 let x = x 读取自身未绑定槽。
StmtKind::Binding(b) => {
self.expression(&b.value, globals)?;
self.initialized.insert(b.name.text.clone());
// 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。
if self.scope.kind != ScopeKind::Function {
self.intern(&b.name.text);
}
}
// 普通赋值同样先检查右值,再令目标在当前路径上已初始化。
StmtKind::Assignment(a) => {
self.expression(&a.value, globals)?;
self.initialized.insert(a.name.text.clone());
// 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。
if self.scope.kind != ScopeKind::Function {
self.intern(&a.name.text);
}
}
// 检查对象和右值读取,并为后续 StoreAttr 登记属性名字。
StmtKind::MemberAssignment(a) => {
self.expression(&a.object, globals)?;
self.expression(&a.value, globals)?;
self.intern(&a.name.text);
}
StmtKind::Expression(e) => self.expression(e, globals)?,
// 只有函数可返回;可选返回表达式先接受同样的名称检查。
StmtKind::Return(r) => {
// 只有函数有 return 语义,根控制流块也不能绕过此限制。
if self.scope.kind != ScopeKind::Function {
return Err(error(statement.span, "return outside a function"));
}
// 无返回表达式由生成器补 Nil;有表达式时先检查其中所有名称读取。
if let Some(e) = &r.value {
self.expression(e, globals)?;
}
returned = true;
}
// 先分析条件,再以相同入口初始化集合分别分析两侧。
StmtKind::If(b) => {
self.expression(&b.condition, globals)?;
// 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。
let before = self.initialized.clone();
// 分析 then 分支并取得其是否必定返回的标志。
let yes_return = self.statements(&b.body.statements, globals)?;
// 保存 then 出口状态,恢复入口后再单独分析 else。
let yes = self.initialized.clone();
self.initialized = before;
// 有 else 时分析其状态;无 else 视为存在不返回的空路径。
let no_return = if let Some(other) = &b.otherwise {
self.statements(&other.statements, globals)?
} else {
false
};
// then 已返回则只保留 else 的继续路径;否则按下方规则合并。
self.initialized = if yes_return {
self.initialized.clone()
} else if no_return {
// 只有 else 返回时保留 then 的继续路径。
yes
} else {
// 两边都能继续时,只保留双方都保证初始化的名称。
yes.intersection(&self.initialized).cloned().collect()
};
// 只有两侧都必返时整个 if 才让后继语句不可达。
returned = yes_return && no_return;
}
// 检查条件及循环体,但循环可能零次执行,出口恢复入口初始化状态。
StmtKind::While(b) => {
self.expression(&b.condition, globals)?;
// 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。
let before = self.initialized.clone();
self.statements(&b.body.statements, globals)?;
self.initialized = before;
}
// 函数体/复合语句不支持嵌套声明,不能在这里隐式生成闭包作用域。
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
"nested declarations are not compiled yet",
));
}
}
}
Ok(returned)
}
/// analyze/statements 及自身递归调用;检查名称已声明/已初始化,记录读取绑定并登记属性名字。
fn expression(
&mut self,
expression: &Expr,
globals: &BTreeSet<String>,
) -> Result<(), Diagnostic> {
// 递归覆盖名称读取和子表达式;字面量不需要绑定。
match &expression.kind {
// 局部名优先于全局,即便这个局部尚未初始化也不回退到同名全局。
ExprKind::Name(name) => {
// 先查完整局部布局,未命中才考虑合法全局名,不按声明位置临时改变遮蔽规则。
let binding = if let Some(index) = self
.scope
.layout
.locals
.iter()
.position(|local| local == name)
{
// 槽已预分配不代表当前路径已经赋值,未确定初始化必须拒绝读取。
if !self.initialized.contains(name) {
return Err(error(
expression.span,
format!("local variable read before initialization: {name}"),
));
}
NameBinding::Local(index)
} else if globals.contains(name) {
// 没有同名局部时,宿主或源码登记过的全局名才能读取。
NameBinding::Global(self.intern(name))
} else {
return Err(error(expression.span, format!("undefined name: {name}")));
};
// 为此名字及源码位置保存绑定,codegen 据此选择 LoadFast 或 LoadGlobal。
self.scope.references.push(NameUse {
name: name.clone(),
span: expression.span,
binding,
});
}
// 一元及 await 都只分析操作数;是否允许实际等待由执行器处理。
ExprKind::Unary { operand, .. } | ExprKind::Await(operand) => {
self.expression(operand, globals)?
}
// 按左右顺序分析所有读取,类型及运算协议由运行时处理。
ExprKind::Binary { left, right, .. } => {
self.expression(left, globals)?;
self.expression(right, globals)?;
}
// 对象表达式接受名称检查,属性本身仅登记为名字表项。
ExprKind::Member { object, name } => {
self.expression(object, globals)?;
self.intern(&name.text);
}
// 调用目标和每个实参都可能读取名称,逐个递归检查。
ExprKind::Call { callee, arguments } => {
self.expression(callee, globals)?;
// 按源码顺序检查参数,错误带各自表达式的位置。
for argument in arguments {
self.expression(argument, globals)?;
}
}
// 字面量无变量读取;Symbol 是否可生成运行时值由 codegen 再拒绝。
ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Symbol(_) => {}
}
Ok(())
}
}