//! 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, } #[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, } /// 本文件所有拒绝路径共用,把具体节点跨度与说明包装为 Semantic 诊断。 fn error(span: Span, message: impl Into) -> 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 { // 交互模式要求恰好一条语句;空输入或批量输入不是本编译入口支持的单位。 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 = 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, ""), AstRoot::Expression(_) => (ScopeKind::Expression, ""), AstRoot::Interactive(_) => (ScopeKind::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::, _>>()?; // 声明预处理后,按根类型分析真正需要求值的内容。 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, 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) -> Result { // 新建函数/方法作用域,局部布局及初始化状态彼此独立。 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, ¶meter.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, layout: &mut Vec, ) -> 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, } 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, ) -> Result { // 表示当前语句序列是否所有可达路径都已返回,供后继不可达检查使用。 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, ) -> 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(()) } }