refactor: 重构函数语义检查器并增强错误提示

- 重构 FunctionChecker 类,优化函数体语义检查流程- 增加全局符号表构建和局部变量声明的详细注释
- 改进错误提示信息,提高代码可读性和可维护性
This commit is contained in:
Luke 2025-08-29 17:40:45 +08:00
parent b56824d935
commit cdfcbf0d6f

View File

@ -17,35 +17,55 @@ import java.util.ArrayList;
import java.util.List;
/**
* 对所有模块的函数体进行两遍扫描的语义检查
* 1) 先为每个模块建立全局符号表globals注册模块级变量/常量
* 2) 再在全局表就绪后依次分析各函数体
* {@code FunctionChecker} 负责对所有模块的函数体进行两遍扫描式的语义检查
* <p>
* 检查流程为
* <ol>
* <li>第一遍为每个模块构建全局符号表注册所有模块级变量与常量声明并检查重复/未知类型</li>
* <li>第二遍在所有全局符号表准备好后依次分析各模块下所有函数的参数和函数体语句</li>
* </ol>
* 检查要点
* <ul>
* <li>类型未知或变量/常量重复声明时均收集为语义错误</li>
* <li>所有函数参数注册为局部变量</li>
* <li>所有函数体语句分派到对应 StatementAnalyzer 实例做分析</li>
* <li> void 返回类型的函数必须有至少一条 return</li>
* </ul>
*/
public record FunctionChecker(Context ctx) {
/**
* 对传入的所有模块做函数体的两遍扫描式语义检查
*
* @param mods 所有待分析的模块 AST 节点集合
*/
public void check(Iterable<ModuleNode> mods) {
List<ModuleNode> moduleList = new ArrayList<>();
// ---------- 第一遍构建并注册各模块全局符号表 ----------
for (ModuleNode mod : mods) {
ctx.setCurrentModule(mod.name());
ctx.setCurrentModule(mod.name()); // 标记当前模块
moduleList.add(mod);
ModuleInfo mi = ctx.modules().get(mod.name());
SymbolTable globalScope = new SymbolTable(null);
SymbolTable globalScope = new SymbolTable(null); // 模块级全局作用域
// 处理所有全局变量/常量声明
for (DeclarationNode g : mod.globals()) {
Type t = ctx.parseType(g.getType());
Type t = ctx.parseType(g.getType()); // 解析声明类型
if (t == null) {
// 类型未知记录错误兜底为 int 类型避免后续 NullPointer
ctx.errors().add(new SemanticError(g, "未知类型: " + g.getType()));
t = BuiltinType.INT; // 兜底避免后续 NPE
t = BuiltinType.INT;
}
SymbolKind kind = g.isConst() ? SymbolKind.CONSTANT : SymbolKind.VARIABLE;
String dupType = g.isConst() ? "常量" : "变量";
// 注册符号表防止重名
if (!globalScope.define(new Symbol(g.getName(), t, kind))) {
ctx.errors().add(new SemanticError(g, dupType + "重复声明: " + g.getName()));
}
}
// 将全局符号表挂载到模块信息对象
mi.setGlobals(globalScope);
}
@ -53,13 +73,13 @@ public record FunctionChecker(Context ctx) {
for (ModuleNode mod : moduleList) {
ctx.setCurrentModule(mod.name());
ModuleInfo mi = ctx.modules().get(mod.name());
SymbolTable globalScope = mi.getGlobals();
SymbolTable globalScope = mi.getGlobals(); // 全局作用域
for (FunctionNode fn : mod.functions()) {
// 构建函数局部作用域父作用域为全局
// 构建函数的局部作用域父作用域为模块全局
SymbolTable locals = new SymbolTable(globalScope);
// 注册函数参数为局部变量
// 注册所有函数参数到局部作用域类型未知时兜底为 int
fn.parameters().forEach(p -> {
Type t = ctx.parseType(p.type());
if (t == null) {
@ -69,19 +89,20 @@ public record FunctionChecker(Context ctx) {
locals.define(new Symbol(p.name(), t, SymbolKind.VARIABLE));
});
// 分析函数体语句 关键修复实例而不是 Class
// 分析函数体所有语句
for (StatementNode stmt : fn.body()) {
@SuppressWarnings("unchecked")
StatementAnalyzer<StatementNode> analyzer =
(StatementAnalyzer<StatementNode>) ctx.getRegistry().getStatementAnalyzer(stmt);
ctx.getRegistry().getStatementAnalyzer(stmt);
if (analyzer != null) {
// 传递语义分析器实例避免类型擦除/反射调用
analyzer.analyze(ctx, mi, fn, locals, stmt);
} else {
// 语句类型未支持收集错误
ctx.errors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt));
}
}
// void 函数必须至少包含一条 return
// void 函数要求必须至少一条 return 语句
Type ret = ctx.parseType(fn.returnType());
if (ret != null && ret != BuiltinType.VOID) {
boolean hasReturn = fn.body().stream().anyMatch(s -> s instanceof ReturnNode);