From 6e31185519648de264949785f7735f414d16c07c Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 26 Aug 2025 14:29:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=20FunctionChecker=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E4=B8=A4=E9=81=8D=E6=89=AB=E6=8F=8F=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 设计核心:采用“两遍扫描”方案,彻底解决跨模块全局变量/常量类型推断和引用依赖问题 - 第一遍:为所有模块预先构建并注册全局符号表(globals) - 第二遍:在全局符号表全部就绪后,依次分析所有模块的函数体 -功能职责: - 遍历所有模块,先建立 globals,再遍历并检查所有函数体语句 - 为每个函数体构建完整符号表,并注册参数变量 - 分发每条语句到对应 StatementAnalyzer进行类型检查和错误校验 - 自动检查非 void 函数 return 完备性 - 记录所有语义错误,便于前端高亮和诊断 --- .../semantic/core/FunctionChecker.java | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java index f60d854..c57bd07 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/FunctionChecker.java @@ -1,8 +1,8 @@ package org.jcnc.snow.compiler.semantic.core; +import org.jcnc.snow.compiler.parser.ast.DeclarationNode; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.ModuleNode; -import org.jcnc.snow.compiler.parser.ast.DeclarationNode; import org.jcnc.snow.compiler.parser.ast.ReturnNode; import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -11,59 +11,64 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolKind; import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; +import java.util.ArrayList; +import java.util.List; + /** - * {@code FunctionChecker} 是语义分析阶段中用于检查函数体语句合法性的调度器。 + * {@code FunctionChecker} 是 Snow 编译器语义分析阶段用于检查所有函数体合法性的总控调度器。 *

- * 它逐个遍历所有模块中的函数定义,并对函数体中的每一条语句调用对应的语义分析器, - * 执行类型检查、作用域验证、错误记录等任务。 - *

- * 核心职责包括: + * 设计核心:采用“两遍扫描”方案,彻底解决跨模块全局变量/常量类型推断和引用依赖问题: *

+ * 功能职责: + * * - * @param ctx 全局语义分析上下文,提供模块信息、注册表、错误记录等支持 + * @param ctx 全局语义分析上下文,持有模块信息、符号表、错误收集等资源 */ public record FunctionChecker(Context ctx) { /** - * 构造函数体检查器。 - * - * @param ctx 当前语义分析上下文 - */ - public FunctionChecker { - } - - /** - * 执行函数体检查流程。 + * 主入口:对所有模块的所有函数体进行语义检查(两遍扫描实现)。 *

- * 对所有模块中的所有函数依次进行处理: - *

    - *
  1. 查找模块对应的 {@link ModuleInfo};
  2. - *
  3. 创建函数局部符号表 {@link SymbolTable},并注册所有参数变量;
  4. - *
  5. 对函数体中的每一条语句分发到已注册的分析器进行语义分析;
  6. - *
  7. 若某条语句无可用分析器,则记录为 {@link SemanticError}。
  8. - *
+ * 第一遍:为每个模块提前构建全局符号表(包含本模块所有全局变量和常量), + * 并注册到 {@link ModuleInfo},确保跨模块引用时所有全局符号都已可用。 + *
+ * 第二遍:遍历所有模块的所有函数,对每个函数体: + * * * @param mods 所有模块的 AST 根节点集合 */ public void check(Iterable mods) { + List moduleList = new ArrayList<>(); + // ---------- 第1遍:收集所有全局符号表 ---------- for (ModuleNode mod : mods) { - // 获取当前模块对应的语义信息 - ModuleInfo mi = ctx.modules().get(mod.name()); + moduleList.add(mod); - // 先构建全局符号表 + // 获取当前模块的元信息 + ModuleInfo mi = ctx.modules().get(mod.name()); + // 创建本模块全局作用域(无父作用域) SymbolTable globalScope = new SymbolTable(null); - // 根据 isConst() 决定种类 + + // 注册所有全局变量/常量到符号表 for (DeclarationNode g : mod.globals()) { var t = ctx.parseType(g.getType()); SymbolKind k = g.isConst() ? SymbolKind.CONSTANT : SymbolKind.VARIABLE; - - // 错误信息按常量/变量区分 String dupType = g.isConst() ? "常量" : "变量"; + // 检查重复声明 if (!globalScope.define(new Symbol(g.getName(), t, k))) { ctx.errors().add(new SemanticError( g, @@ -71,14 +76,20 @@ public record FunctionChecker(Context ctx) { )); } } + // 注册到模块信息,供跨模块引用 + mi.setGlobals(globalScope); + } + + // ---------- 第2遍:遍历所有函数,分析函数体 ---------- + for (ModuleNode mod : moduleList) { + ModuleInfo mi = ctx.modules().get(mod.name()); + SymbolTable globalScope = mi.getGlobals(); - // 遍历模块中所有函数定义 for (FunctionNode fn : mod.functions()) { - - // 构建函数局部作用域符号表,父作用域为 globalScope + // 构建函数局部作用域,父作用域为 globalScope SymbolTable locals = new SymbolTable(globalScope); - // 将函数参数注册为局部变量 + // 注册函数参数为局部变量 fn.parameters().forEach(p -> locals.define(new Symbol( p.name(), @@ -87,7 +98,7 @@ public record FunctionChecker(Context ctx) { )) ); - // 遍历并分析函数体内的每条语句 + // 分析函数体内每条语句 for (var stmt : fn.body()) { var analyzer = ctx.getRegistry().getStatementAnalyzer(stmt); if (analyzer != null) {