diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java index 67e425f..898ee40 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java @@ -2,34 +2,90 @@ package org.jcnc.snow.compiler.semantic.analyzers; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.StatementNode; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.expression.UnsupportedExpressionAnalyzer; import java.util.HashMap; import java.util.Map; /** - * Analyzer 注册表,负责按节点类型分发对应的 Analyzer。 + * Analyzer 注册表:负责维护并按 AST 节点类型分发对应的 StatementAnalyzer 或 ExpressionAnalyzer。 + *

+ * 支持: + *

*/ public class AnalyzerRegistry { + /** 语句节点类型 -> 分析器 映射 */ private final Map, StatementAnalyzer> stmtAnalyzers = new HashMap<>(); + /** 表达式节点类型 -> 分析器 映射 */ private final Map, ExpressionAnalyzer> exprAnalyzers = new HashMap<>(); + /** 默认兜底表达式分析器,处理所有未显式注册的 ExpressionNode 子类型 */ + private final ExpressionAnalyzer defaultUnsupported = + new UnsupportedExpressionAnalyzer<>(); // --- 注册方法 ----------------------------------------------------------- - public void registerStatementAnalyzer(Class cls, StatementAnalyzer analyzer) { + + /** + * 注册一个 StatementAnalyzer,用于处理指定类型的语句节点。 + * + * @param cls 语句节点的 Class 对象 + * @param analyzer 针对该类型节点的分析器实例 + * @param 具体的 StatementNode 子类型 + */ + public void registerStatementAnalyzer( + Class cls, + StatementAnalyzer analyzer + ) { stmtAnalyzers.put(cls, analyzer); } - public void registerExpressionAnalyzer(Class cls, ExpressionAnalyzer analyzer) { + /** + * 注册一个 ExpressionAnalyzer,用于处理指定类型的表达式节点。 + * + * @param cls 表达式节点的 Class 对象 + * @param analyzer 针对该类型节点的分析器实例 + * @param 具体的 ExpressionNode 子类型 + */ + public void registerExpressionAnalyzer( + Class cls, + ExpressionAnalyzer analyzer + ) { exprAnalyzers.put(cls, analyzer); } // --- 获取方法 ----------------------------------------------------------- + + /** + * 根据给定的语句节点实例,返回对应的 StatementAnalyzer。 + *

+ * 若未注册任何分析器,则返回 null。 + * + * @param stmt 要分析的 StatementNode 实例 + * @param 具体的 StatementNode 子类型 + * @return 对应的 StatementAnalyzer,或 null + */ @SuppressWarnings("unchecked") public StatementAnalyzer getStatementAnalyzer(S stmt) { return (StatementAnalyzer) stmtAnalyzers.get(stmt.getClass()); } + /** + * 根据给定的表达式节点实例,返回对应的 ExpressionAnalyzer。 + *

+ * 若未注册任何分析器,则返回 {@link #defaultUnsupported} 兜底。 + * + * @param expr 要分析的 ExpressionNode 实例 + * @param 具体的 ExpressionNode 子类型 + * @return 对应的 ExpressionAnalyzer(可能是默认兜底分析器) + */ @SuppressWarnings("unchecked") public ExpressionAnalyzer getExpressionAnalyzer(E expr) { - return (ExpressionAnalyzer) exprAnalyzers.get(expr.getClass()); + return (ExpressionAnalyzer) + exprAnalyzers.getOrDefault(expr.getClass(), defaultUnsupported); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/ExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/ExpressionAnalyzer.java deleted file mode 100644 index 9b0c7c6..0000000 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/ExpressionAnalyzer.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.jcnc.snow.compiler.semantic.analyzers; - -import org.jcnc.snow.compiler.parser.ast.FunctionNode; -import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; -import org.jcnc.snow.compiler.semantic.core.Context; -import org.jcnc.snow.compiler.semantic.core.ModuleInfo; -import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; -import org.jcnc.snow.compiler.semantic.type.Type; - -/** - * 表达式分析器接口。 - */ -public interface ExpressionAnalyzer { - Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, E expr); -} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/StatementAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/StatementAnalyzer.java deleted file mode 100644 index 624d568..0000000 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/StatementAnalyzer.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.jcnc.snow.compiler.semantic.analyzers; - -import org.jcnc.snow.compiler.parser.ast.FunctionNode; -import org.jcnc.snow.compiler.parser.ast.base.StatementNode; -import org.jcnc.snow.compiler.semantic.core.Context; -import org.jcnc.snow.compiler.semantic.core.ModuleInfo; -import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; - -/** - * 语句分析器接口。 - */ -public interface StatementAnalyzer { - void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, S stmt); -} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/ExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/ExpressionAnalyzer.java new file mode 100644 index 0000000..df49049 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/ExpressionAnalyzer.java @@ -0,0 +1,44 @@ +package org.jcnc.snow.compiler.semantic.analyzers.base; + +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.semantic.core.Context; +import org.jcnc.snow.compiler.semantic.core.ModuleInfo; +import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; +import org.jcnc.snow.compiler.semantic.type.Type; + +/** + * 表达式分析器接口:定义了对 AST 中表达式节点进行语义分析的通用契约。 + *

+ * 各种具体的表达式分析器(如调用、二元运算、标识符、字面量等)需实现此接口, + * 在 {@link #analyze(Context, ModuleInfo, FunctionNode, SymbolTable, ExpressionNode)} + * 方法中完成类型推导、语义检查,并将发现的错误记录到上下文中。 + * + * @param 要分析的具体表达式节点类型,必须是 {@link ExpressionNode} 的子类型 + */ +public interface ExpressionAnalyzer { + + /** + * 对给定的表达式节点进行语义分析,并返回推导出的类型。 + *

+ * 实现者应在分析过程中根据节点语义: + *

    + *
  • 校验子表达式类型并递归调用对应的分析器;
  • + *
  • 检查函数调用、运算符合法性;
  • + *
  • 必要时向 {@link Context#getErrors()} 添加 {@link org.jcnc.snow.compiler.semantic.error.SemanticError};
  • + *
  • 返回最终推导出的 {@link Type},以供上层表达式或语句分析使用。
  • + *
+ * + * @param ctx 全局上下文,提供模块注册表、错误收集、日志输出及分析器注册表等 + * @param mi 当前模块信息,用于跨模块调用和函数签名查找 + * @param fn 当前函数节点,可用于返回类型校验或其他函数级上下文 + * @param locals 当前作用域的符号表,包含已声明的变量及其类型 + * @param expr 待分析的表达式节点 + * @return 表达式的推导类型,用于后续类型兼容性检查和类型传播 + */ + Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + E expr); +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/StatementAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/StatementAnalyzer.java new file mode 100644 index 0000000..7e9d1ca --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/base/StatementAnalyzer.java @@ -0,0 +1,40 @@ +package org.jcnc.snow.compiler.semantic.analyzers.base; + +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.base.StatementNode; +import org.jcnc.snow.compiler.semantic.core.Context; +import org.jcnc.snow.compiler.semantic.core.ModuleInfo; +import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; + +/** + * 语句分析器接口:定义如何对 AST 中的语句节点执行语义检查。 + *

+ * 各具体的语句分析器(如声明、赋值、分支、循环、返回等)需实现此接口, + * 在 {@link #analyze(Context, ModuleInfo, FunctionNode, SymbolTable, StatementNode)} + * 方法中完成: + *

    + *
  • 对自身语义结构进行校验(变量声明、类型匹配、作用域检查等);
  • + *
  • 递归调用其他已注册的语句或表达式分析器;
  • + *
  • 在发现错误时,通过 {@link Context#getErrors()} 记录 {@link org.jcnc.snow.compiler.semantic.error.SemanticError};
  • + *
  • 在上下文需要时记录日志以辅助调试。
  • + *
+ * + * @param 要分析的具体语句节点类型,必须是 {@link StatementNode} 的子类型 + */ +public interface StatementAnalyzer { + + /** + * 对给定的语句节点执行语义分析。 + * + * @param ctx 全局上下文,提供模块注册表、错误收集、日志输出及分析器注册表等 + * @param mi 当前模块信息,用于检查模块导入和函数签名等上下文 + * @param fn 当前函数节点,可用于检查返回类型或函数级别作用域 + * @param locals 当前作用域的符号表,包含已声明的变量及其类型 + * @param stmt 待分析的语句节点实例 + */ + void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + S stmt); +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/BinaryExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/BinaryExpressionAnalyzer.java index f8f3bad..6513427 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/BinaryExpressionAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/BinaryExpressionAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.expression; import org.jcnc.snow.compiler.parser.ast.BinaryExpressionNode; import org.jcnc.snow.compiler.parser.ast.FunctionNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -10,19 +10,57 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 二元表达式分析器:负责对形如 left op right 的表达式进行类型推导和类型检查。 + *

+ * 支持以下运算符: + *

    + *
  • +:如果任一操作数为 {@link BuiltinType#STRING},则结果为字符串拼接,否则如果都是整数则结果为整数
  • + *
  • -, *, /, %:要求两个操作数都是整数,结果为整数
  • + *
  • <, <=, >, >=, ==, !=: + * 要求两个操作数都是整数,结果为整数(表示真假)
  • + *
+ * 对于未知运算符或类型不匹配的情况,会在 {@link Context#getErrors() 错误列表} 中加入对应的 + * {@link SemanticError},并将结果类型降级为 {@link BuiltinType#INT} 以避免后续连锁错误。 + */ public class BinaryExpressionAnalyzer implements ExpressionAnalyzer { + + /** + * 对给定的二元表达式节点进行语义分析,返回推导出的类型。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志开关、注册表等 + * @param mi 当前正在分析的模块信息,用于查找导入、函数签名等 + * @param fn 当前正在分析的函数节点,用于处理返回类型检查等(可用于更复杂场景) + * @param locals 当前作用域的符号表,包含已定义的局部变量及其类型 + * @param bin 待分析的 {@link BinaryExpressionNode} 节点,包含左子表达式、运算符、右子表达式 + * @return 推导出的 {@link Type}: + *
    + *
  • 符合运算规则时返回 {@link BuiltinType#INT} 或 {@link BuiltinType#STRING}
  • + *
  • 类型不匹配或未知运算符时降级返回 {@link BuiltinType#INT}
  • + *
+ */ @Override - public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, BinaryExpressionNode bin) { + public Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + BinaryExpressionNode bin) { ctx.log("检查二元表达式: " + bin.operator()); - var leftA = ctx.getRegistry().getExpressionAnalyzer(bin.left()); - Type left = leftA.analyze(ctx, mi, fn, locals, bin.left()); - var rightA = ctx.getRegistry().getExpressionAnalyzer(bin.right()); - Type right = rightA.analyze(ctx, mi, fn, locals, bin.right()); + + // 递归分析左右两边的子表达式 + var leftAnalyzer = ctx.getRegistry().getExpressionAnalyzer(bin.left()); + Type left = leftAnalyzer.analyze(ctx, mi, fn, locals, bin.left()); + + var rightAnalyzer = ctx.getRegistry().getExpressionAnalyzer(bin.right()); + Type right = rightAnalyzer.analyze(ctx, mi, fn, locals, bin.right()); String op = bin.operator(); Type result; + + // 根据运算符做类型推导 switch (op) { case "+" -> { + // 字符串拼接 或 整数相加 if (left == BuiltinType.STRING || right == BuiltinType.STRING) { result = BuiltinType.STRING; } else if (left == BuiltinType.INT && right == BuiltinType.INT) { @@ -32,6 +70,7 @@ public class BinaryExpressionAnalyzer implements ExpressionAnalyzer { + // 数学运算,要求整数操作数 if (left == BuiltinType.INT && right == BuiltinType.INT) { result = BuiltinType.INT; } else { @@ -39,6 +78,7 @@ public class BinaryExpressionAnalyzer implements ExpressionAnalyzer", ">=", "==", "!=" -> { + // 比较运算,要求整数操作数,返回整数表示真假 if (left == BuiltinType.INT && right == BuiltinType.INT) { result = BuiltinType.INT; } else { @@ -46,19 +86,25 @@ public class BinaryExpressionAnalyzer implements ExpressionAnalyzer { + // 未知运算符 ctx.getErrors().add(new SemanticError(bin, "未知运算符: " + op)); ctx.log("错误: 未知运算符 " + op); return BuiltinType.INT; } } + // 如果推导失败(类型不匹配),记录错误并降级 if (result == null) { - ctx.getErrors().add(new SemanticError(bin, String.format("运算符 '%s' 不支持类型: %s 和 %s", op, left, right))); + ctx.getErrors().add(new SemanticError( + bin, + String.format("运算符 '%s' 不支持类型: %s 和 %s", op, left, right)) + ); ctx.log("错误: 运算符 '" + op + "' 不支持类型: " + left + ", " + right); result = BuiltinType.INT; } else { ctx.log("二元表达式推导类型: " + result); } + return result; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/CallExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/CallExpressionAnalyzer.java index 0cefbf9..cf4c6bd 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/CallExpressionAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/CallExpressionAnalyzer.java @@ -5,7 +5,7 @@ import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.IdentifierNode; import org.jcnc.snow.compiler.parser.ast.MemberExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -17,62 +17,126 @@ import org.jcnc.snow.compiler.semantic.type.Type; import java.util.ArrayList; import java.util.List; +/** + * 函数调用表达式分析器:负责对 callee(arg1, arg2, ...) 形式的调用进行 + * 语义检查和类型推导。 + *

+ * 支持两种调用方式: + *

    + *
  • 模块函数调用:ModuleName.func(...),要求模块已注册且已导入,或与当前模块同名
  • + *
  • 本模块函数调用:func(...)
  • + *
+ *

+ * 分析内容包括: + *

    + *
  1. 解析目标模块与函数名;
  2. + *
  3. 检查函数是否已定义;
  4. + *
  5. 递归分析并收集所有实参的类型;
  6. + *
  7. 检查参数个数与参数类型是否与函数签名匹配;
  8. + *
  9. 返回函数签名中的返回类型;
  10. + *
  11. 在任何错误(未知模块、未导入模块、调用方式不支持、函数未定义、 + * 参数数量或类型不匹配)时,记录对应的 {@link SemanticError}, + * 并降级返回 {@link BuiltinType#INT} 以避免后续连锁错误。
  12. + *
+ */ public class CallExpressionAnalyzer implements ExpressionAnalyzer { + + /** + * 对给定的调用表达式节点进行语义分析,返回推导出的类型。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出、分析器注册表等 + * @param mi 当前正在分析的模块信息,用于查找导入列表和函数签名 + * @param fn 当前正在分析的函数节点,可用于更复杂的上下文校验 + * @param locals 当前作用域的符号表,包含已定义的变量及其类型 + * @param call 待分析的 {@link CallExpressionNode},包含被调用的表达式和实参列表 + * @return 推导出的函数返回类型;若发生任何错误,则降级返回 {@link BuiltinType#INT} + */ @Override - public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, CallExpressionNode call) { + public Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + CallExpressionNode call) { ctx.log("检查函数调用: " + call.callee()); ModuleInfo target = mi; String functionName; ExpressionNode callee = call.callee(); + // 处理模块限定调用:ModuleName.func(...) if (callee instanceof MemberExpressionNode(var object, String member)) { - if (object instanceof IdentifierNode id) { - String moduleName = id.name(); - if (!ctx.getModules().containsKey(moduleName) || - (!mi.getImports().contains(moduleName) && !mi.getName().equals(moduleName))) { - ctx.getErrors().add(new SemanticError(callee, "未知或未导入模块: " + moduleName)); - ctx.log("错误: 未导入模块 " + moduleName); + if (object instanceof IdentifierNode(String name)) { + if (!ctx.getModules().containsKey(name) + || (!mi.getImports().contains(name) && !mi.getName().equals(name))) { + ctx.getErrors().add(new SemanticError(callee, + "未知或未导入模块: " + name)); + ctx.log("错误: 未导入模块 " + name); return BuiltinType.INT; } - target = ctx.getModules().get(moduleName); + target = ctx.getModules().get(name); functionName = member; - ctx.log("调用模块函数: " + moduleName + "." + member); + ctx.log("调用模块函数: " + name + "." + member); } else { - ctx.getErrors().add(new SemanticError(callee, "不支持的调用方式: " + callee)); + ctx.getErrors().add(new SemanticError(callee, + "不支持的调用方式: " + callee)); ctx.log("错误: 不支持的调用方式 " + callee); return BuiltinType.INT; } - } else if (callee instanceof IdentifierNode id) { - functionName = id.name(); + + // 处理本模块函数调用:func(...) + } else if (callee instanceof IdentifierNode(String name)) { + functionName = name; ctx.log("调用当前模块函数: " + functionName); } else { - ctx.getErrors().add(new SemanticError(callee, "不支持的调用方式: " + callee)); + ctx.getErrors().add(new SemanticError(callee, + "不支持的调用方式: " + callee)); ctx.log("错误: 不支持的调用方式 " + callee); return BuiltinType.INT; } + // 查找函数签名 FunctionType ft = target.getFunctions().get(functionName); if (ft == null) { - ctx.getErrors().add(new SemanticError(callee, "函数未定义: " + functionName)); + ctx.getErrors().add(new SemanticError(callee, + "函数未定义: " + functionName)); ctx.log("错误: 函数未定义 " + functionName); return BuiltinType.INT; } + // 分析所有实参并收集类型 List args = new ArrayList<>(); for (var argExpr : call.arguments()) { var argAnalyzer = ctx.getRegistry().getExpressionAnalyzer(argExpr); args.add(argAnalyzer.analyze(ctx, mi, fn, locals, argExpr)); } + // 参数数量检查 if (args.size() != ft.paramTypes().size()) { - ctx.getErrors().add(new SemanticError(call, "参数数量不匹配: 期望 " + ft.paramTypes().size() + " 个, 实际 " + args.size() + " 个")); - ctx.log("错误: 参数数量不匹配: 期望 " + ft.paramTypes().size() + ", 实际 " + args.size()); + ctx.getErrors().add(new SemanticError(call, + "参数数量不匹配: 期望 " + + ft.paramTypes().size() + + " 个, 实际 " + + args.size() + + " 个")); + ctx.log("错误: 参数数量不匹配: 期望 " + + ft.paramTypes().size() + + ", 实际 " + + args.size()); } else { + // 参数类型检查 for (int i = 0; i < args.size(); i++) { - if (!ft.paramTypes().get(i).isCompatible(args.get(i))) { - ctx.getErrors().add(new SemanticError(call, String.format("参数类型不匹配 (位置 %d): 期望 %s, 实际 %s", i, ft.paramTypes().get(i), args.get(i)))); - ctx.log("错误: 参数类型不匹配 (位置 " + i + "): 期望 " + ft.paramTypes().get(i) + ", 实际 " + args.get(i)); + Type expected = ft.paramTypes().get(i); + Type actual = args.get(i); + if (!expected.isCompatible(actual)) { + ctx.getErrors().add(new SemanticError(call, + String.format("参数类型不匹配 (位置 %d): 期望 %s, 实际 %s", + i, expected, actual))); + ctx.log("错误: 参数类型不匹配 (位置 " + + i + + "): 期望 " + + expected + + ", 实际 " + + actual); } } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IdentifierAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IdentifierAnalyzer.java index e0c7f47..08cc087 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IdentifierAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IdentifierAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.expression; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.IdentifierNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -11,15 +11,44 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 标识符表达式分析器:负责对变量或常量等标识符节点进行语义检查, + * 并返回其在当前作用域中的类型。 + *

+ * 如果未在符号表中找到对应的符号,将向错误列表中添加一条 + * {@link SemanticError},并返回 {@link BuiltinType#INT} 作为降级类型, + * 以避免后续分析因缺失类型而连锁报错。 + */ public class IdentifierAnalyzer implements ExpressionAnalyzer { + + /** + * 对给定的标识符节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块信息、错误收集和分析器注册表等 + * @param mi 当前模块信息,用于跨模块引用时的额外检查(此处暂未使用) + * @param fn 当前函数节点,可用于更复杂的上下文校验(此处暂未使用) + * @param locals 当前作用域的符号表,包含已声明的变量及其类型 + * @param id 待分析的 {@link IdentifierNode},包含标识符名称 + * @return 如果符号已声明则返回其 {@link Symbol#type()};否则返回降级类型 {@link BuiltinType#INT} + */ @Override - public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, IdentifierNode id) { + public Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + IdentifierNode id) { + + // 查找当前作用域下的符号 Symbol sym = locals.resolve(id.name()); if (sym == null) { - ctx.getErrors().add(new SemanticError(id, "未声明的标识符: " + id.name())); + // 未声明:记录错误并降级 + ctx.getErrors().add(new SemanticError(id, + "未声明的标识符: " + id.name())); ctx.log("错误: 未声明的标识符 " + id.name()); return BuiltinType.INT; } + + // 已声明:返回符号所持有的类型 return sym.type(); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java index 57e4ffd..148d6fa 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/NumberLiteralAnalyzer.java @@ -1,17 +1,38 @@ package org.jcnc.snow.compiler.semantic.analyzers.expression; -import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.NumberLiteralNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 数字字面量分析器:对 {@link NumberLiteralNode} 节点进行类型推导。 + *

+ * 在本语言中,所有数字字面量均被视为整型,因此直接返回 {@link BuiltinType#INT}。 + * 本分析器不会产生任何语义错误。 + */ public class NumberLiteralAnalyzer implements ExpressionAnalyzer { + + /** + * 对数字字面量节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块信息、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息(此分析器不涉及模块间调用,参数未使用) + * @param fn 当前函数节点(此分析器不依赖函数上下文,参数未使用) + * @param locals 当前作用域的符号表(数字字面量不依赖符号表,参数未使用) + * @param expr 待分析的 {@link NumberLiteralNode},表示一个数字字面量 + * @return 固定返回 {@link BuiltinType#INT} + */ @Override - public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, NumberLiteralNode expr) { + public Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + NumberLiteralNode expr) { return BuiltinType.INT; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/StringLiteralAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/StringLiteralAnalyzer.java index 13cefeb..34d2793 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/StringLiteralAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/StringLiteralAnalyzer.java @@ -1,17 +1,38 @@ package org.jcnc.snow.compiler.semantic.analyzers.expression; -import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.StringLiteralNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 字符串字面量分析器:对 {@link StringLiteralNode} 节点进行类型推导。 + *

+ * 在本语言中,所有字符串字面量均被视为字符串类型,因此直接返回 {@link BuiltinType#STRING}。 + * 本分析器不会产生任何语义错误。 + */ public class StringLiteralAnalyzer implements ExpressionAnalyzer { + + /** + * 对字符串字面量节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等(此分析器不使用上下文) + * @param mi 当前模块信息(此分析器不涉及模块调用,参数未使用) + * @param fn 当前函数节点(此分析器不依赖函数上下文,参数未使用) + * @param locals 当前作用域的符号表(字符串字面量不依赖符号表,参数未使用) + * @param expr 待分析的 {@link StringLiteralNode},表示一个字符串字面量 + * @return 固定返回 {@link BuiltinType#STRING} + */ @Override - public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, StringLiteralNode expr) { + public Type analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + StringLiteralNode expr) { return BuiltinType.STRING; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnsupportedExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnsupportedExpressionAnalyzer.java index 906aedb..4dd427a 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnsupportedExpressionAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnsupportedExpressionAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.expression; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; -import org.jcnc.snow.compiler.semantic.analyzers.ExpressionAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.ExpressionAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -10,18 +10,41 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; -// 之前:implements ExpressionAnalyzer +/** + * 默认兜底表达式分析器:用于处理所有未显式注册的 {@link ExpressionNode} 子类型。 + *

+ * 当遇到不支持或未实现的表达式节点时,本分析器会: + *

    + *
  1. 向 {@link Context#getErrors() 错误列表} 中添加一条 {@link SemanticError},
  2. + *
  3. 在日志中输出对应的错误信息,
  4. + *
  5. 并返回降级类型 {@link BuiltinType#INT} 以避免后续连锁报错。
  6. + *
+ * + * @param 任何 {@link ExpressionNode} 子类型 + */ public class UnsupportedExpressionAnalyzer implements ExpressionAnalyzer { + /** + * 对未知或不支持的表达式节点进行处理。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息(此分析器不使用模块上下文,参数可忽略) + * @param fn 当前函数节点(此分析器不使用函数上下文,参数可忽略) + * @param locals 当前作用域的符号表(此分析器不依赖局部符号,参数可忽略) + * @param expr 待分析的表达式节点,类型为任意 {@link ExpressionNode} 子类型 + * @return 固定返回 {@link BuiltinType#INT},作为错误降级类型 + */ @Override public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, E expr) { - ctx.getErrors().add(new SemanticError(expr, - "不支持的表达式类型: " + expr)); + ctx.getErrors().add(new SemanticError( + expr, + "不支持的表达式类型: " + expr + )); ctx.log("错误: 不支持的表达式类型 " + expr); return BuiltinType.INT; } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java index 2c9f30d..93c6c44 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/AssignmentAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.statement; import org.jcnc.snow.compiler.parser.ast.AssignmentNode; import org.jcnc.snow.compiler.parser.ast.FunctionNode; -import org.jcnc.snow.compiler.semantic.analyzers.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -11,20 +11,58 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolKind; import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 赋值语句分析器:负责对 {@link AssignmentNode} 节点进行语义检查。 + *

+ * 分析步骤: + *

    + *
  1. 检查目标变量是否已在当前作用域中声明且种类为 {@link SymbolKind#VARIABLE};
  2. + *
  3. 递归分析赋值右侧表达式,获取其类型;
  4. + *
  5. 检查右侧表达式类型是否与目标变量类型兼容;
  6. + *
  7. 在任一检查失败时,向 {@link Context#getErrors() 错误列表} 中添加对应的 + * {@link SemanticError} 并记录日志。
  8. + *
+ */ public class AssignmentAnalyzer implements StatementAnalyzer { + + /** + * 对给定的赋值语句节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息,用于处理跨模块符号(此分析器不使用模块上下文) + * @param fn 当前函数节点,可用于更复杂的上下文校验(此分析器不使用函数上下文) + * @param locals 当前作用域的符号表,包含已声明的符号及其类型 + * @param asg 待分析的 {@link AssignmentNode},包含目标变量名和赋值表达式 + */ @Override - public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, AssignmentNode asg) { + public void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + AssignmentNode asg) { ctx.log("赋值检查: " + asg.variable()); + + // 1. 检查变量声明 Symbol sym = locals.resolve(asg.variable()); if (sym == null || sym.kind() != SymbolKind.VARIABLE) { - ctx.getErrors().add(new SemanticError(asg, "未声明的变量: " + asg.variable())); + ctx.getErrors().add(new SemanticError( + asg, + "未声明的变量: " + asg.variable() + )); ctx.log("错误: 未声明的变量 " + asg.variable()); return; } + + // 2. 分析赋值表达式 var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(asg.value()); Type valType = exprAnalyzer.analyze(ctx, mi, fn, locals, asg.value()); + + // 3. 类型兼容性检查 if (!sym.type().isCompatible(valType)) { - ctx.getErrors().add(new SemanticError(asg, "赋值类型不匹配: 期望 " + sym.type() + ", 实际 " + valType)); + ctx.getErrors().add(new SemanticError( + asg, + "赋值类型不匹配: 期望 " + sym.type() + ", 实际 " + valType + )); ctx.log("错误: 赋值类型不匹配 " + asg.variable()); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java index 91c25a0..c0fb86e 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/DeclarationAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.statement; import org.jcnc.snow.compiler.parser.ast.DeclarationNode; import org.jcnc.snow.compiler.parser.ast.FunctionNode; -import org.jcnc.snow.compiler.semantic.analyzers.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -12,29 +12,79 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 变量声明语句分析器:负责对 {@link DeclarationNode} 节点进行语义检查。 + *

+ * 分析流程: + *

    + *
  1. 解析声明类型,如果未知则记录错误并降级为 {@link BuiltinType#INT};
  2. + *
  3. 在当前符号表中注册新变量,若名称已存在则记录重复声明错误;
  4. + *
  5. 如存在初始化表达式,递归分析表达式并检查其类型与声明类型的兼容性;
  6. + *
  7. 兼容性检查失败时记录类型不匹配错误。
  8. + *
+ */ public class DeclarationAnalyzer implements StatementAnalyzer { + + /** + * 对给定的声明节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息(在声明分析中未直接使用,但保留以便扩展) + * @param fn 当前函数节点(在声明分析中未直接使用,但可用于上下文检查) + * @param locals 当前作用域的符号表,用于注册新变量及类型查询 + * @param decl 待分析的 {@link DeclarationNode},包含变量名、声明类型及可选初始化表达式 + */ @Override - public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, DeclarationNode decl) { + public void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + DeclarationNode decl) { + // 1. 类型解析与降级 Type varType = ctx.parseType(decl.getType()); if (varType == null) { - ctx.getErrors().add(new SemanticError(decl, "未知类型: " + decl.getType())); - ctx.log("错误: 参数未知类型 " + decl.getType() + " 在声明 " + decl.getName()); + ctx.getErrors().add(new SemanticError( + decl, + "未知类型: " + decl.getType() + )); + ctx.log("错误: 参数未知类型 " + decl.getType() + + " 在声明 " + decl.getName()); varType = BuiltinType.INT; } - ctx.log("声明变量: " + decl.getName() + " 类型: " + varType); + ctx.log("声明变量: " + decl.getName() + + " 类型: " + varType); - if (!locals.define(new Symbol(decl.getName(), varType, SymbolKind.VARIABLE))) { - ctx.getErrors().add(new SemanticError(decl, "变量重复声明: " + decl.getName())); + // 2. 注册新变量,检查重复声明 + if (!locals.define(new Symbol( + decl.getName(), + varType, + SymbolKind.VARIABLE + ))) { + ctx.getErrors().add(new SemanticError( + decl, + "变量重复声明: " + decl.getName() + )); ctx.log("错误: 变量重复声明 " + decl.getName()); } - Type finalVarType = varType; + // 3. 初始化表达式类型检查 + Type declaredType = varType; decl.getInitializer().ifPresent(init -> { - var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(init); - Type initType = exprAnalyzer.analyze(ctx, mi, fn, locals, init); - if (!finalVarType.isCompatible(initType)) { - ctx.getErrors().add(new SemanticError(decl, "初始化类型不匹配: 期望 " + finalVarType + ", 实际 " + initType)); - ctx.log("错误: 初始化类型不匹配 " + decl.getName()); + var exprAnalyzer = ctx.getRegistry() + .getExpressionAnalyzer(init); + Type initType = exprAnalyzer.analyze( + ctx, mi, fn, locals, init + ); + if (!declaredType.isCompatible(initType)) { + ctx.getErrors().add(new SemanticError( + decl, + "初始化类型不匹配: 期望 " + + declaredType + + ", 实际 " + + initType + )); + ctx.log("错误: 初始化类型不匹配 " + + decl.getName()); } }); } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IfAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IfAnalyzer.java index b7888fa..2d1ad76 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IfAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IfAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.statement; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.IfNode; -import org.jcnc.snow.compiler.semantic.analyzers.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -10,23 +10,60 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * If 语句分析器:负责对 {@link IfNode} 节点进行语义检查。 + *

+ * 分析流程: + *

    + *
  1. 对条件表达式进行类型推导,要求类型为 {@link BuiltinType#INT};
  2. + *
  3. 若条件类型不符,记录错误并降级继续分析;
  4. + *
  5. 分别递归分析 then 分支和 else 分支中的每个子语句;
  6. + *
  7. 不支持的子语句类型将由注册表中未匹配到的分析器处理(或被记录为错误)。
  8. + *
+ */ public class IfAnalyzer implements StatementAnalyzer { + + /** + * 对给定的 if 节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息,用于跨模块调用检查(此分析器未直接使用) + * @param fn 当前函数节点,可用于更复杂的上下文校验(此分析器未直接使用) + * @param locals 当前作用域的符号表,包含已定义变量及其类型 + * @param ifn 待分析的 {@link IfNode},包含条件表达式、then 分支和 else 分支 + */ @Override - public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, IfNode ifn) { + public void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + IfNode ifn) { + // 1. 条件表达式类型检查 ctx.log("检查 if 条件"); var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ifn.condition()); Type cond = exprAnalyzer.analyze(ctx, mi, fn, locals, ifn.condition()); if (cond != BuiltinType.INT) { - ctx.getErrors().add(new SemanticError(ifn, "if 条件必须为 int 类型(表示真假)")); + ctx.getErrors().add(new SemanticError( + ifn, + "if 条件必须为 int 类型(表示真假)" + )); ctx.log("错误: if 条件类型不为 int"); } - ifn.thenBranch().forEach(stmt -> { + + // 2. 递归分析 then 分支 + for (var stmt : ifn.thenBranch()) { var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); - if (stAnalyzer != null) stAnalyzer.analyze(ctx, mi, fn, locals, stmt); - }); - ifn.elseBranch().forEach(stmt -> { + if (stAnalyzer != null) { + stAnalyzer.analyze(ctx, mi, fn, locals, stmt); + } + } + + // 3. 递归分析 else 分支 + for (var stmt : ifn.elseBranch()) { var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); - if (stAnalyzer != null) stAnalyzer.analyze(ctx, mi, fn, locals, stmt); - }); + if (stAnalyzer != null) { + stAnalyzer.analyze(ctx, mi, fn, locals, stmt); + } + } } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/LoopAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/LoopAnalyzer.java index ea2d39e..de67823 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/LoopAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/LoopAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.statement; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.LoopNode; -import org.jcnc.snow.compiler.semantic.analyzers.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -10,25 +10,66 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable; import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * 循环语句分析器:负责对 {@link LoopNode} 节点进行语义检查。 + *

+ * 分析流程: + *

    + *
  1. 分析并执行初始化语句(initializer);
  2. + *
  3. 对循环条件表达式进行类型推导,要求为 {@link BuiltinType#INT};
  4. + *
  5. 分析并执行更新语句(update);
  6. + *
  7. 递归分析循环体(body)中的每个子语句;
  8. + *
  9. 在任何步骤发现类型或语义错误时,都会向 {@link Context#getErrors() 错误列表} 中添加对应的 + * {@link SemanticError} 并在日志中记录。
  10. + *
+ */ public class LoopAnalyzer implements StatementAnalyzer { + + /** + * 对给定的循环节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息,用于跨模块调用检查(此分析器未直接使用) + * @param fn 当前函数节点,可用于更复杂的上下文校验(此分析器未直接使用) + * @param locals 当前作用域的符号表,包含已定义变量及其类型 + * @param ln 待分析的 {@link LoopNode},包含 initializer、condition、update 和 body + */ @Override - public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, LoopNode ln) { + public void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + LoopNode ln) { + // 1. 初始化语句 ctx.log("检查 loop 循环"); var initAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.initializer()); - if (initAnalyzer != null) initAnalyzer.analyze(ctx, mi, fn, locals, ln.initializer()); + if (initAnalyzer != null) { + initAnalyzer.analyze(ctx, mi, fn, locals, ln.initializer()); + } + // 2. 条件表达式类型检查 var condAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ln.condition()); Type cond = condAnalyzer.analyze(ctx, mi, fn, locals, ln.condition()); if (cond != BuiltinType.INT) { - ctx.getErrors().add(new SemanticError(ln, "loop 条件必须为 int 类型(表示真假)")); + ctx.getErrors().add(new SemanticError( + ln, + "loop 条件必须为 int 类型(表示真假)" + )); ctx.log("错误: loop 条件类型不为 int"); } - var updateAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.update()); - if (updateAnalyzer != null) updateAnalyzer.analyze(ctx, mi, fn, locals, ln.update()); - ln.body().forEach(stmt -> { + // 3. 更新语句 + var updateAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.update()); + if (updateAnalyzer != null) { + updateAnalyzer.analyze(ctx, mi, fn, locals, ln.update()); + } + + // 4. 循环体语句 + for (var stmt : ln.body()) { var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); - if (stAnalyzer != null) stAnalyzer.analyze(ctx, mi, fn, locals, stmt); - }); + if (stAnalyzer != null) { + stAnalyzer.analyze(ctx, mi, fn, locals, stmt); + } + } } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/ReturnAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/ReturnAnalyzer.java index afcd94a..570842c 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/ReturnAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/ReturnAnalyzer.java @@ -2,7 +2,7 @@ package org.jcnc.snow.compiler.semantic.analyzers.statement; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.ReturnNode; -import org.jcnc.snow.compiler.semantic.analyzers.StatementAnalyzer; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; import org.jcnc.snow.compiler.semantic.core.Context; import org.jcnc.snow.compiler.semantic.core.ModuleInfo; import org.jcnc.snow.compiler.semantic.error.SemanticError; @@ -11,23 +11,67 @@ import org.jcnc.snow.compiler.semantic.type.BuiltinType; import org.jcnc.snow.compiler.semantic.type.FunctionType; import org.jcnc.snow.compiler.semantic.type.Type; +/** + * Return 语句分析器:负责对 {@link ReturnNode} 节点进行语义检查。 + *

+ * 分析过程包括: + *

    + *
  1. 获取当前函数的预期返回类型;
  2. + *
  3. 如果存在返回表达式,则递归分析其类型并与预期类型进行兼容性检查;
  4. + *
  5. 如果没有返回表达式且预期类型不是 {@link BuiltinType#VOID},则记录缺少返回值的错误;
  6. + *
  7. 遇到类型不兼容时,会向 {@link Context#getErrors() 错误列表} 中添加 {@link SemanticError},
  8. + *
  9. 所有错误同时会记录到日志以便调试。
  10. + *
+ */ public class ReturnAnalyzer implements StatementAnalyzer { + + /** + * 对给定的 return 节点进行语义分析。 + * + * @param ctx 全局上下文,包含模块表、错误收集、日志输出和分析器注册表等 + * @param mi 当前模块信息,用于查找函数签名和返回类型 + * @param fn 当前正在分析的函数节点,包含函数名和参数列表 + * @param locals 当前作用域的符号表(此分析器不使用局部符号表) + * @param ret 待分析的 {@link ReturnNode},可能包含一个返回表达式 + */ @Override - public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, ReturnNode ret) { + public void analyze(Context ctx, + ModuleInfo mi, + FunctionNode fn, + SymbolTable locals, + ReturnNode ret) { ctx.log("检查 return"); - FunctionType expected = ctx.getModules().get(mi.getName()).getFunctions().get(fn.name()); + + // 查找函数的预期返回类型 + FunctionType expected = ctx.getModules() + .get(mi.getName()) + .getFunctions() + .get(fn.name()); + + // 如果有返回表达式,则检查其类型 ret.getExpression().ifPresentOrElse(exp -> { - var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(exp); - Type actual = exprAnalyzer.analyze(ctx, mi, fn, locals, exp); - if (!expected.returnType().isCompatible(actual)) { - ctx.getErrors().add(new SemanticError(ret, "return 类型不匹配: 期望 " + expected.returnType() + ", 实际 " + actual)); - ctx.log("错误: return 类型不匹配"); - } - }, () -> { - if (expected.returnType() != BuiltinType.VOID) { - ctx.getErrors().add(new SemanticError(ret, "非 void 函数必须返回值")); - ctx.log("错误: 非 void 函数缺少返回值"); - } - }); + var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(exp); + Type actual = exprAnalyzer.analyze(ctx, mi, fn, locals, exp); + if (!expected.returnType().isCompatible(actual)) { + ctx.getErrors().add(new SemanticError( + ret, + "return 类型不匹配: 期望 " + + expected.returnType() + + ", 实际 " + + actual + )); + ctx.log("错误: return 类型不匹配"); + } + }, + // 如果没有返回表达式,但函数应有返回值 + () -> { + if (expected.returnType() != BuiltinType.VOID) { + ctx.getErrors().add(new SemanticError( + ret, + "非 void 函数必须返回值" + )); + ctx.log("错误: 非 void 函数缺少返回值"); + } + }); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java index a5f0436..04e389a 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java @@ -1,20 +1,33 @@ package org.jcnc.snow.compiler.semantic.core; -import org.jcnc.snow.compiler.semantic.type.Type; import org.jcnc.snow.compiler.semantic.analyzers.AnalyzerRegistry; import org.jcnc.snow.compiler.semantic.error.SemanticError; +import org.jcnc.snow.compiler.semantic.type.Type; -import java.util.*; +import java.util.List; +import java.util.Map; /** - * 公共上下文对象,贯穿整个语义分析过程。 + * 语义分析公共上下文:在整个分析流程中共享,用于存储模块信息、收集错误、控制日志,以及管理分析器注册表。 */ public class Context { + /** 所有已注册模块名 -> 模块信息 的映射 */ private final Map modules; + /** 语义分析过程中收集的所有错误列表 */ private final List errors; + /** 是否启用详细日志输出 */ private final boolean verbose; + /** 存放并分发各类语句/表达式分析器的注册表 */ private final AnalyzerRegistry registry; + /** + * 构造一个新的 Context 实例。 + * + * @param modules 已初始化的模块信息映射,用于查找模块签名、导入关系等 + * @param errors 用于收集语义分析过程中发现的所有 {@link SemanticError} + * @param verbose 是否启用详细日志;当为 true 时,调用 {@link #log(String)} 会打印日志 + * @param registry 已设置好所有分析器的 {@link AnalyzerRegistry} + */ public Context(Map modules, List errors, boolean verbose, @@ -25,26 +38,56 @@ public class Context { this.registry = registry; } + /** + * 获取所有模块信息的映射。 + * + * @return 模块名到 {@link ModuleInfo} 的 Map + */ public Map getModules() { return modules; } + /** + * 获取语义错误列表。 + * 分析过程中产生的 {@link SemanticError} 会被收集到此列表。 + * + * @return 语义错误列表 + */ public List getErrors() { return errors; } + /** + * 获取分析器注册表。 + * 可通过此注册表按 AST 节点类型分发对应的语句或表达式分析器。 + * + * @return {@link AnalyzerRegistry} 实例 + */ public AnalyzerRegistry getRegistry() { return registry; } + /** + * 打印日志信息,仅在 {@code verbose} 为 true 时输出到标准输出。 + * + * @param msg 要打印的日志内容 + */ public void log(String msg) { if (verbose) { System.out.println("[SemanticAnalyzer] " + msg); } } - /** 根据名称解析为内置类型,未知则返回 null */ + /** + * 将类型名解析为内置类型 {@link Type} 实例。 + *

+ * 若名称在 {@link SemanticAnalyzer#BUILTIN_TYPES} 中存在,则返回对应类型; + * 否则返回 {@code null},调用方可据此决定降级为默认类型并记录错误。 + * + * @param name 类型名称(如 "int", "string", "void") + * @return 对应的 {@link Type},或 {@code null} 表示未知类型 + */ public Type parseType(String name) { - return SemanticAnalyzer.BUILTIN_TYPES.get(name); + return org.jcnc.snow.compiler.semantic.core.SemanticAnalyzer.BUILTIN_TYPES.get(name); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java index d1107a1..650e2d0 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/ModuleInfo.java @@ -8,29 +8,28 @@ import java.util.Map; import java.util.Set; /** - * 保存模块级别的信息。 + * 模块信息:保存单个模块在语义分析阶段的元数据。 *

- * 模块信息包括: + * 包含以下内容: *

    - *
  • 模块名(唯一标识一个模块)。
  • - *
  • 导入的其他模块名称集合。
  • - *
  • 模块中定义的函数签名映射(函数名到 {@link FunctionType} 的对应关系)。
  • + *
  • 模块名称(唯一标识);
  • + *
  • 该模块导入的其他模块名称集合;
  • + *
  • 该模块中定义的函数签名映射,将函数名映射到对应的 {@link FunctionType}。
  • *
- *

- * 主要用于语义分析阶段,帮助管理模块之间的依赖关系和函数声明信息。 + * 在语义分析过程中,用于管理模块依赖关系和查找函数签名。 */ public class ModuleInfo { /** 模块名称 */ private final String name; - /** 导入的模块名称集合 */ + /** 导入的其他模块名称集合 */ private final Set imports = new HashSet<>(); - /** 模块中定义的函数映射表:函数名 -> 函数类型 */ + /** 模块中定义的函数签名映射:函数名 -> 函数类型 */ private final Map functions = new HashMap<>(); /** * 构造一个新的模块信息对象。 * - * @param name 模块名称 + * @param name 模块名称,必须唯一 */ public ModuleInfo(String name) { this.name = name; @@ -39,30 +38,30 @@ public class ModuleInfo { /** * 获取模块名称。 * - * @return 模块名称 + * @return 模块的唯一名称 */ public String getName() { return name; } /** - * 获取导入的模块名称集合。 + * 获取该模块导入的其他模块名称集合。 *

- * 返回的是内部集合的直接引用,可以通过它进行增删操作。 + * 返回的 Set 为内部对象的直接引用,可对其进行添加或移除操作,以维护导入列表。 * - * @return 导入模块名称的集合 + * @return 导入模块名称的可变集合 */ public Set getImports() { return imports; } /** - * 获取模块中定义的函数映射表。 + * 获取模块中定义的函数签名映射表。 *

- * 返回的是函数名到函数类型 {@link FunctionType} 的映射表。 - * 返回的是内部映射的直接引用,可以通过它进行增删操作。 + * 键为函数名,值为对应的 {@link FunctionType}。 + * 返回的 Map 为内部对象的直接引用,可对其进行添加或移除操作,以维护函数签名列表。 * - * @return 函数映射表 + * @return 函数签名映射表 */ public Map getFunctions() { return functions; diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/SemanticAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/SemanticAnalyzer.java index bc32630..b4dcdd0 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/SemanticAnalyzer.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/SemanticAnalyzer.java @@ -1,6 +1,7 @@ package org.jcnc.snow.compiler.semantic.core; import org.jcnc.snow.compiler.parser.ast.*; +import org.jcnc.snow.compiler.parser.ast.base.StatementNode; import org.jcnc.snow.compiler.semantic.analyzers.AnalyzerRegistry; import org.jcnc.snow.compiler.semantic.error.SemanticError; import org.jcnc.snow.compiler.semantic.analyzers.expression.*; @@ -15,100 +16,157 @@ import org.jcnc.snow.compiler.semantic.type.Type; import java.util.*; /** - * 重构后的语义分析器,采用注册表 + 分析器组合的分而治之设计。 + * 重构后的语义分析器,采用“注册表 + 分析器”组合的分而治之设计。 + *

+ * 主要职责: + *

    + *
  • 初始化内置模块与类型映射;
  • + *
  • 注册用户模块及其函数签名与导入关系;
  • + *
  • 使用 StatementAnalyzer 与 ExpressionAnalyzer 进行递归语义检查;
  • + *
  • 收集并返回所有发现的语义错误。
  • + *
*/ public class SemanticAnalyzer { /** - * 内置类型映射表 + * 内置类型映射表:将类型名称映射到对应的 {@link Type} 实例。 */ - public static final Map BUILTIN_TYPES = Map.of("int", BuiltinType.INT, "string", BuiltinType.STRING, "void", BuiltinType.VOID); + public static final Map BUILTIN_TYPES = Map.of( + "int", BuiltinType.INT, + "string", BuiltinType.STRING, + "void", BuiltinType.VOID + ); - // --------------------------------------------------------------------- + /** 所有已注册模块名称 -> {@link ModuleInfo} 的映射 */ private final Map modules = new HashMap<>(); + /** 收集语义分析过程中产生的所有错误 */ private final List errors = new ArrayList<>(); + /** 是否启用详细日志输出 */ private final boolean verbose; + /** 分发语句与表达式分析器的注册表 */ private final AnalyzerRegistry registry = new AnalyzerRegistry(); + /** 全局上下文对象,封装 modules、errors、verbose 与 registry */ private final Context ctx; + /** + * 使用默认(关闭日志)构造语义分析器。 + */ public SemanticAnalyzer() { this(false); } + /** + * 构造语义分析器。 + * + * @param verbose 是否启用详细日志输出 + */ public SemanticAnalyzer(boolean verbose) { this.verbose = verbose; this.ctx = new Context(modules, errors, verbose, registry); registerAnalyzers(); } - // --------------------------------------------------------------------- + /** + * 注册所有语句与表达式分析器到 {@link AnalyzerRegistry}。 + *

+ * 包括:声明、赋值、分支、循环、返回、表达式语句以及 + * 数字、字符串、标识符、调用、二元、兜底表达式分析器。 + */ private void registerAnalyzers() { - // 语句 + // 语句分析器 registry.registerStatementAnalyzer(DeclarationNode.class, new DeclarationAnalyzer()); - registry.registerStatementAnalyzer(AssignmentNode.class, new AssignmentAnalyzer()); - registry.registerStatementAnalyzer(IfNode.class, new IfAnalyzer()); - registry.registerStatementAnalyzer(LoopNode.class, new LoopAnalyzer()); - registry.registerStatementAnalyzer(ReturnNode.class, new ReturnAnalyzer()); - registry.registerStatementAnalyzer(ExpressionStatementNode.class, (c, mi, fn, locals, stmt) -> { - var a = c.getRegistry().getExpressionAnalyzer(stmt.expression()); - a.analyze(c, mi, fn, locals, stmt.expression()); - }); + registry.registerStatementAnalyzer(AssignmentNode.class, new AssignmentAnalyzer()); + registry.registerStatementAnalyzer(IfNode.class, new IfAnalyzer()); + registry.registerStatementAnalyzer(LoopNode.class, new LoopAnalyzer()); + registry.registerStatementAnalyzer(ReturnNode.class, new ReturnAnalyzer()); + registry.registerStatementAnalyzer(ExpressionStatementNode.class, + (c, mi, fn, locals, stmt) -> { + var exprAn = c.getRegistry().getExpressionAnalyzer(stmt.expression()); + exprAn.analyze(c, mi, fn, locals, stmt.expression()); + } + ); - // 表达式 + // 表达式分析器 registry.registerExpressionAnalyzer(NumberLiteralNode.class, new NumberLiteralAnalyzer()); registry.registerExpressionAnalyzer(StringLiteralNode.class, new StringLiteralAnalyzer()); - registry.registerExpressionAnalyzer(IdentifierNode.class, new IdentifierAnalyzer()); - registry.registerExpressionAnalyzer(CallExpressionNode.class, new CallExpressionAnalyzer()); + registry.registerExpressionAnalyzer(IdentifierNode.class, new IdentifierAnalyzer()); + registry.registerExpressionAnalyzer(CallExpressionNode.class,new CallExpressionAnalyzer()); registry.registerExpressionAnalyzer(BinaryExpressionNode.class, new BinaryExpressionAnalyzer()); - // 兜底处理 - // 兜底:所有暂未实现的表达式都用 UnsupportedExpressionAnalyzer 处理 + // 默认兜底 registry.registerExpressionAnalyzer(MemberExpressionNode.class, new UnsupportedExpressionAnalyzer<>()); } - // --------------------------------------------------------------------- + /** + * 对给定的模块 AST 列表执行完整的语义分析。 + * + * @param moduleNodes 所有待分析的 {@link ModuleNode} 列表 + * @return 收集到的所有 {@link SemanticError},如果无错误则返回空列表 + */ public List analyze(List moduleNodes) { ctx.log("开始语义分析"); initBuiltinModule(); ctx.log("内置模块初始化完成"); + registerUserModules(moduleNodes); ctx.log("用户模块注册完成: " + modules.keySet()); + registerSignaturesAndImports(moduleNodes); ctx.log("函数签名与导入检查完成"); + checkAllFunctions(moduleNodes); ctx.log("所有函数检查完成,错误总数: " + errors.size()); + return errors; } - // --------------------------------------------------------------------- + /** + * 初始化内置工具模块及其函数签名。 + *

+ * 包括:to_int、to_string、print。 + */ private void initBuiltinModule() { ModuleInfo builtin = new ModuleInfo("BuiltinUtils"); - builtin.getFunctions().put("to_int", new FunctionType(List.of(BuiltinType.STRING), BuiltinType.INT)); - builtin.getFunctions().put("to_string", new FunctionType(List.of(BuiltinType.INT), BuiltinType.STRING)); - builtin.getFunctions().put("print", new FunctionType(List.of(BuiltinType.STRING), BuiltinType.VOID)); + builtin.getFunctions().put("to_int", new FunctionType(List.of(BuiltinType.STRING), BuiltinType.INT)); + builtin.getFunctions().put("to_string", new FunctionType(List.of(BuiltinType.INT), BuiltinType.STRING)); + builtin.getFunctions().put("print", new FunctionType(List.of(BuiltinType.STRING), BuiltinType.VOID)); modules.put(builtin.getName(), builtin); } + /** + * 注册所有用户模块到模块映射中(只保留名称,用于后续签名与导入检查)。 + * + * @param mods AST 中的模块节点列表 + */ private void registerUserModules(List mods) { - for (var mod : mods) { + for (ModuleNode mod : mods) { modules.put(mod.name(), new ModuleInfo(mod.name())); } } + /** + * 为每个模块注册其导入关系与函数签名。 + *

+ * 对未知模块或类型均会记录 {@link SemanticError} 并进行默认降级处理。 + * + * @param mods AST 中的模块节点列表 + */ private void registerSignaturesAndImports(List mods) { - for (var mod : mods) { + for (ModuleNode mod : mods) { ModuleInfo mi = modules.get(mod.name()); - // 导入 - for (var imp : mod.imports()) { + + // 模块导入检查 + for (ImportNode imp : mod.imports()) { if (!modules.containsKey(imp.moduleName())) { errors.add(new SemanticError(imp, "未知模块: " + imp.moduleName())); - continue; + } else { + mi.getImports().add(imp.moduleName()); } - mi.getImports().add(imp.moduleName()); } - // 函数签名 - for (var fn : mod.functions()) { + + // 函数签名注册 + for (FunctionNode fn : mod.functions()) { List params = new ArrayList<>(); - for (var p : fn.parameters()) { + for (ParameterNode p : fn.parameters()) { Type t = ctx.parseType(p.type()); if (t == null) { errors.add(new SemanticError(p, "未知类型: " + p.type())); @@ -116,23 +174,33 @@ public class SemanticAnalyzer { } params.add(t); } - Type ret = Optional.ofNullable(ctx.parseType(fn.returnType())).orElse(BuiltinType.VOID); - mi.getFunctions().put(fn.name(), new FunctionType(params, ret)); + Type retType = Optional.ofNullable(ctx.parseType(fn.returnType())) + .orElse(BuiltinType.VOID); + mi.getFunctions().put(fn.name(), new FunctionType(params, retType)); } } } + /** + * 对所有模块中的每个函数体执行逐语句分析,收集语义错误。 + * + * @param mods AST 中的模块节点列表 + */ private void checkAllFunctions(List mods) { - for (var mod : mods) { + for (ModuleNode mod : mods) { ModuleInfo mi = modules.get(mod.name()); - for (var fn : mod.functions()) { + for (FunctionNode fn : mod.functions()) { SymbolTable locals = new SymbolTable(null); - // 参数注册 - for (var p : fn.parameters()) { - locals.define(new Symbol(p.name(), ctx.parseType(p.type()), SymbolKind.VARIABLE)); + + // 参数预定义为局部变量 + for (ParameterNode p : fn.parameters()) { + locals.define(new Symbol(p.name(), + ctx.parseType(p.type()), + SymbolKind.VARIABLE)); } - // 函数体 - for (var stmt : fn.body()) { + + // 逐语句分发给对应的 StatementAnalyzer + for (StatementNode stmt : fn.body()) { var analyzer = registry.getStatementAnalyzer(stmt); if (analyzer != null) { analyzer.analyze(ctx, mi, fn, locals, stmt); diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java b/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java index b0aae5f..0931c27 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/error/SemanticError.java @@ -5,21 +5,25 @@ import org.jcnc.snow.compiler.parser.ast.base.Node; /** * 表示语义分析过程中发现的错误。 *

- * 每个语义错误包含两个部分: - * 1. 发生错误的 AST(抽象语法树)节点 {@link Node}; - * 2. 错误的详细描述 {@link String}。 - *

+ * 每个语义错误包含以下内容: + *
    + *
  • {@link Node}:发生错误的 AST 节点,便于定位;
  • + *
  • {@link String message}:错误的详细描述信息。
  • + *
+ *

+ * 使用 Java 16+ record 定义,自动生成构造方法、访问器(getters)、equals、hashCode 和 toString 方法。 * - * 该类以 record 形式定义,自动生成构造方法、访问器方法(getters)、equals、hashCode 以及 toString 方法。 + * @param node 触发语义错误的 AST 节点 + * @param message 错误的详细描述 */ public record SemanticError(Node node, String message) { /** - * 返回语义错误的字符串表示。 - *

- * 格式为:"Semantic error at [节点信息]: [错误信息]"。 - * 方便在日志输出或调试过程中快速定位问题。 - *

+ * 返回语义错误的字符串表示,格式如下: + *
+     * Semantic error at [节点信息]: [错误信息]
+     * 
+ * 便于在日志输出或调试时快速查看定位。 * * @return 格式化后的错误信息字符串 */ diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/Symbol.java b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/Symbol.java index e0374b7..f948938 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/Symbol.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/Symbol.java @@ -3,21 +3,25 @@ package org.jcnc.snow.compiler.semantic.symbol; import org.jcnc.snow.compiler.semantic.type.Type; /** - * 表示符号表(Symbol Table)中的一条符号记录。 + * 符号表中的一条符号记录,表示语言中的命名实体(如变量、函数等)。 *

- * 每条符号记录包含以下三个部分信息: + * 每条符号记录包括三个核心属性: *

    - *
  • name:符号的名称,如变量名、函数名等。
  • - *
  • type:符号的类型信息,通常对应变量类型、函数返回值类型等。
  • - *
  • kind:符号的种类,指明符号是变量、函数、类等 {@link SymbolKind}。
  • + *
  • name:符号名称,例如变量名或函数名;
  • + *
  • type:符号的类型信息,对于变量是变量类型,对于函数是返回类型;
  • + *
  • kind:符号的种类,由 {@link SymbolKind} 枚举区分(变量、函数等)。
  • + *
+ *

+ * 使用 Java 16+ record 定义,自动生成: + *

    + *
  • 构造方法;
  • + *
  • 访问器方法(getters);
  • + *
  • equals、hashCode;
  • + *
  • toString 方法。
  • *
- *

* - * 本类使用 Java 记录类型(record)定义,自动生成构造方法、访问器方法(getters)、equals、hashCode 以及 toString 方法。 - * - * @param name 符号的名称 - * @param type 符号的类型 - * @param kind 符号的种类 + * @param name 符号名称 + * @param type 符号类型 + * @param kind 符号种类 */ -public record Symbol(String name, Type type, SymbolKind kind) { -} +public record Symbol(String name, Type type, SymbolKind kind) { } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java index 0a13ee2..6cff3e7 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolKind.java @@ -1,25 +1,33 @@ package org.jcnc.snow.compiler.semantic.symbol; /** - * 表示符号的种类(Symbol Kind)。 + * 符号种类枚举:用于在符号表中区分不同类型的命名实体。 *

- * 用于在符号表中区分不同类型的符号,例如变量、函数、模块等。 + * 在语义分析过程中,不同种类的符号需要不同的处理策略, + * 例如变量声明、函数调用、模块导入等。 *

* - * 支持的符号种类包括: *
    - *
  • VARIABLE:变量符号,例如局部变量、全局变量等。
  • - *
  • FUNCTION:函数符号,包括普通函数、方法等。
  • - *
  • MODULE:模块符号,表示一个模块或命名空间。
  • + *
  • {@link #VARIABLE} – 变量符号(局部变量、全局变量、成员变量等);
  • + *
  • {@link #FUNCTION} – 函数符号(自由函数、方法、构造函数等);
  • + *
  • {@link #MODULE} – 模块符号,表示一个命名空间或模块;
  • *
*/ public enum SymbolKind { - /** 变量符号,例如局部变量、全局变量等。 */ + /** + * 变量符号,例如在函数或全局作用域中声明的变量。 + */ VARIABLE, - /** 函数符号,表示一个可调用的功能单元。 */ + /** + * 函数符号,表示可调用的函数或方法, + * 用于函数签名注册和调用时的类型匹配。 + */ FUNCTION, - /** 模块符号,表示一个模块或命名空间。 */ + /** + * 模块符号,表示一个模块或命名空间, + * 用于管理模块导入和跨模块引用。 + */ MODULE } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolTable.java b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolTable.java index 0cfd3e0..2162216 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolTable.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/symbol/SymbolTable.java @@ -4,26 +4,26 @@ import java.util.HashMap; import java.util.Map; /** - * 符号表(Symbol Table),用于管理符号(如变量、函数、模块等)及其作用域信息。 + * 符号表(Symbol Table):用于管理命名实体(如变量、函数、模块等)的作用域及其类型信息。 *

- * 支持链式作用域(Nested Scope)管理: + * 本实现支持链式作用域(Nested Scope): *

    - *
  • 每个符号表可以有一个父符号表 {@link #parent},用于实现作用域嵌套。
  • - *
  • 符号表内部使用 {@link Map} 保存当前作用域内定义的符号。
  • + *
  • 每个 {@code SymbolTable} 可拥有一个父作用域,若查找失败可递归向上查找;
  • + *
  • 当前作用域使用内部 {@link Map} 保存定义的符号;
  • + *
  • 提供符号定义与符号解析两种基本操作。
  • *
- *

*/ public class SymbolTable { - /** 父作用域符号表,如果为 null 则表示当前为最外层作用域。 */ + /** 父作用域符号表;若为 null 则表示当前为最外层作用域。 */ private final SymbolTable parent; - /** 当前作用域中定义的符号集合,按名称索引。 */ + /** 当前作用域内定义的符号映射:符号名 -> {@link Symbol}。 */ private final Map symbols = new HashMap<>(); /** - * 创建一个新的符号表,并指定其父作用域。 + * 构造一个新的符号表,并可指定其父作用域以支持嵌套查找。 * - * @param parent 父符号表,若无父作用域则传入 null + * @param parent 父作用域的 {@link SymbolTable};若无父作用域,则传入 {@code null} */ public SymbolTable(SymbolTable parent) { this.parent = parent; @@ -32,12 +32,11 @@ public class SymbolTable { /** * 在当前作用域中定义一个新的符号。 *

- * 如果当前作用域中已存在同名符号,则定义失败并返回 {@code false}; - * 否则,将符号添加到符号表中并返回 {@code true}。 - *

+ * 如果当前作用域已有同名符号,则定义失败并返回 {@code false}; + * 否则将符号添加到当前作用域并返回 {@code true}。 * - * @param symbol 要定义的符号 - * @return 是否定义成功,若名称冲突则返回 {@code false} + * @param symbol 要定义的 {@link Symbol} 对象 + * @return {@code true} 如果添加成功;{@code false} 表示名称冲突 */ public boolean define(Symbol symbol) { if (symbols.containsKey(symbol.name())) { @@ -48,27 +47,21 @@ public class SymbolTable { } /** - * 根据名称查找符号。 - *

- * 查找顺序为: + * 根据名称解析符号,支持嵌套作用域查找: *

    - *
  1. 先在当前作用域中查找。
  2. - *
  3. 如果当前作用域未找到,递归向父作用域查找。
  4. - *
  5. 若最终未找到,返回 {@code null}。
  6. + *
  7. 优先在当前作用域查找;
  8. + *
  9. 若未找到且存在父作用域,则递归在父作用域查找;
  10. + *
  11. 最终未找到则返回 {@code null}。
  12. *
- *

* - * @param name 要查找的符号名称 - * @return 找到的符号对象;如果不存在则返回 {@code null} + * @param name 要解析的符号名称 + * @return 对应的 {@link Symbol},或 {@code null}(表示未声明) */ public Symbol resolve(String name) { Symbol sym = symbols.get(name); if (sym != null) { return sym; } - if (parent != null) { - return parent.resolve(name); - } - return null; + return (parent != null) ? parent.resolve(name) : null; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/type/BuiltinType.java b/src/main/java/org/jcnc/snow/compiler/semantic/type/BuiltinType.java index e832ab3..164e261 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/type/BuiltinType.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/type/BuiltinType.java @@ -1,20 +1,20 @@ package org.jcnc.snow.compiler.semantic.type; /** - * 内置基础类型枚举。 + * 内置基础类型枚举:定义编译器中最基本的三种类型。 *

- * 本枚举定义了编译器中最基本的三种内置类型: + * 枚举值: *

    - *
  • INT:整数类型,同时也可用于表示布尔值(真假值)。
  • - *
  • STRING:字符串类型。
  • - *
  • VOID:无返回值类型,通常用于函数无返回值的情况。
  • + *
  • {@link #INT} – 整数类型,也可用于表示布尔值(真/假);
  • + *
  • {@link #STRING} – 字符串类型;
  • + *
  • {@link #VOID} – 空类型,用于表示无返回值的函数。
  • *
*

- * 实现了 Type 接口,提供了基本的类型兼容性判断和字符串表示方法。 + * 本枚举实现了 {@link Type} 接口,提供了基本的类型兼容性判断和友好的字符串表示方法。 */ public enum BuiltinType implements Type { /** - * 整数类型,也可用于布尔值。 + * 整数类型,也可用于表示布尔值(真假值)。 */ INT, @@ -24,28 +24,27 @@ public enum BuiltinType implements Type { STRING, /** - * 空类型,用于表示无返回值的情况。 + * 空类型,通常用于无返回值的函数。 */ VOID; /** * 判断当前类型是否与另一个类型兼容。 *

- * 兼容的条件是两个类型必须完全相同(引用相等)。 + * 兼容条件:两个类型实例必须相同(同一枚举常量)。 * * @param other 另一个需要检查兼容性的类型 - * @return 如果类型完全相同则返回 true,否则返回 false + * @return 如果类型完全相同则返回 {@code true};否则返回 {@code false} */ @Override public boolean isCompatible(Type other) { - // 只有当两个类型对象是同一个实例时才认为兼容 return this == other; } /** - * 将类型转换为小写字符串形式。 + * 返回小写形式的类型名称,便于输出和日志展示。 *

- * 例如:INT 会转换为 "int"。 + * 例如:{@link #INT} 返回 {@code "int"};{@link #VOID} 返回 {@code "void"}。 * * @return 当前类型的小写字符串表示 */ diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/type/FunctionType.java b/src/main/java/org/jcnc/snow/compiler/semantic/type/FunctionType.java index f527f76..0a54057 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/type/FunctionType.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/type/FunctionType.java @@ -4,20 +4,20 @@ import java.util.List; import java.util.Objects; /** - * 表示函数类型。 + * 表示函数类型。由参数类型列表和返回类型共同确定。 *

- * 一个函数类型由参数类型列表和返回类型共同确定。 - * 例如:一个接受两个 int 参数并返回 string 的函数,其类型描述为 (int, int) -> string。 + * 例如:一个接受两个 int 参数并返回 string 的函数,其类型描述为 (int, int) -> string。 *

- * 本类是一个 Java record,自动生成基本的构造方法、访问器、equals 和 hashCode 方法, - * 并实现了 {@link Type} 接口,支持类型兼容性检查和字符串表示。 + * 该 record 自动生成了构造方法、访问器、equals、hashCode。 + * 并实现了 {@link Type} 接口,可用于类型兼容性检查和字符串表示。 + * + * @param paramTypes 参数类型列表 + * @param returnType 返回类型 */ public record FunctionType(List paramTypes, Type returnType) implements Type { + /** - * 构造函数类型。 - *

- * {@link java.util.Collections#unmodifiableList(List)} 包装成不可变列表, - * 保证函数类型对象本身的安全性和不可变性。 + * 构造函数类型,将参数类型列表包装成不可变列表以保证安全性。 * * @param paramTypes 参数类型列表 * @param returnType 返回类型 @@ -30,30 +30,30 @@ public record FunctionType(List paramTypes, Type returnType) implements Ty /** * 判断当前函数类型是否与另一个类型兼容。 *

- * 兼容条件为: + * 兼容条件: *

    - *
  • 另一类型也是 FunctionType。
  • - *
  • 返回类型兼容。
  • - *
  • 参数类型列表完全相等(顺序和值一致)。
  • + *
  • 另一对象也为 {@code FunctionType};
  • + *
  • 返回类型兼容;
  • + *
  • 参数类型列表完全相等(顺序和值一致)。
  • *
* - * @param other 需要检查兼容性的另一个类型 - * @return 如果兼容返回 true,否则返回 false + * @param other 另一个类型 + * @return 如果兼容则返回 {@code true},否则 {@code false} */ @Override public boolean isCompatible(Type other) { - if (!(other instanceof FunctionType)) return false; - FunctionType o = (FunctionType) other; - return returnType.isCompatible(o.returnType) - && paramTypes.equals(o.paramTypes); + if (!(other instanceof FunctionType(List types, Type type))) return false; + return returnType.isCompatible(type) + && paramTypes.equals(types); } /** * 返回函数类型的字符串表示。 *

- * 格式为:"(参数类型列表) -> 返回类型",例如:(int, string) -> void。 + * 格式为:(param1, param2, ...) -> returnType, + * 例如 (int, string) -> void。 * - * @return 函数类型的字符串表示 + * @return 函数类型的描述字符串 */ @Override public String toString() { @@ -61,32 +61,30 @@ public record FunctionType(List paramTypes, Type returnType) implements Ty } /** - * 判断当前函数类型是否与另一个对象相等。 + * 判断两个函数类型是否相等。 *

- * 相等条件为: + * 相等条件: *

    - *
  • 对象引用相同,或
  • - *
  • 对象是 FunctionType 类型,且参数类型列表和返回类型都相等。
  • + *
  • 引用相同;
  • + *
  • 或都是 {@code FunctionType},且参数列表和返回类型都相同。
  • *
* * @param obj 另一个对象 - * @return 如果相等返回 true,否则返回 false + * @return 相等返回 {@code true},否则 {@code false} */ @Override public boolean equals(Object obj) { if (this == obj) return true; - if (!(obj instanceof FunctionType)) return false; - FunctionType o = (FunctionType) obj; - return returnType.equals(o.returnType) - && paramTypes.equals(o.paramTypes); + if (!(obj instanceof FunctionType(List types, Type type))) return false; + return returnType.equals(type) + && paramTypes.equals(types); } /** - * 返回函数类型的哈希码。 - *

- * 计算规则是基于参数类型列表和返回类型,保证与 {@link #equals(Object)} 方法一致。 + * 计算函数类型的哈希码。基于参数类型列表和返回类型, + * 与 {@link #equals(Object)} 保持一致。 * - * @return 函数类型的哈希值 + * @return 哈希码 */ @Override public int hashCode() { diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java b/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java index 640b5d8..d33c15d 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java @@ -1,11 +1,22 @@ package org.jcnc.snow.compiler.semantic.type; /** - * 类型接口。所有具体类型(内置类型、函数类型等)都应实现此接口。 + * 类型接口:所有类型(包括内置类型、函数类型等)均需实现此接口, + * 用于在语义分析中进行类型兼容性检查和统一表示。 */ public interface Type { /** - * 判断此类型是否可接受赋值或兼容另一个类型。 + * 判断当前类型是否与另一个类型兼容。 + *

+ * 例如: + *

    + *
  • 对于内置类型,只有完全相同的枚举常量才兼容;
  • + *
  • 对于函数类型,需要参数列表和返回类型同时兼容;
  • + *
  • 对于其他复合类型,可按语言规则自行实现。
  • + *
+ * + * @param other 要检查兼容性的另一个类型 + * @return 如果兼容则返回 {@code true},否则返回 {@code false} */ boolean isCompatible(Type other); }