增加注释

This commit is contained in:
Luke 2025-05-16 16:58:43 +08:00
parent 4f2ee4a9f0
commit 56301944fa
4 changed files with 102 additions and 65 deletions

View File

@ -11,10 +11,31 @@ import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.Type; import org.jcnc.snow.compiler.semantic.type.Type;
/** /**
* 改进版 {@code IfAnalyzer} then/else 分支提供独立块级作用域 * {@code IfAnalyzer} 用于分析 if 语句的语义正确性
* <p>
* 主要职责如下
* <ul>
* <li>条件表达式类型检查确认 if 的条件表达式类型为 int用于真假判断否则记录语义错误</li>
* <li>块级作用域分别为 then 分支和 else 分支创建独立的符号表SymbolTable
* 支持分支内变量的块级作用域防止分支内声明的变量污染外部或互相干扰允许分支内变量同名遮蔽</li>
* <li>分支递归分析 then else 分支的每条语句递归调用对应的语义分析器进行语义检查</li>
* <li>错误记录若遇到条件类型不符不支持的语句类型或分支内部其他语义问题均通过 {@link SemanticError} 记录详细错误信息并附带代码位置信息</li>
* <li>健壮性不会因一处错误立即终止而是尽量分析全部分支收集所有能发现的错误一次性输出</li>
* </ul>
* <p>
* 该分析器提升了语言的健壮性与可维护性是支持 SCompiler 块级作用域及全局错误收集能力的关键一环
*/ */
public class IfAnalyzer implements StatementAnalyzer<IfNode> { public class IfAnalyzer implements StatementAnalyzer<IfNode> {
/**
* 分析 if 语句的语义合法性包括条件表达式类型分支作用域及分支语句检查
*
* @param ctx 语义分析上下文记录全局符号表和错误
* @param mi 当前模块信息
* @param fn 当前所在函数
* @param locals 当前作用域符号表
* @param ifn if 语句 AST 节点
*/
@Override @Override
public void analyze(Context ctx, public void analyze(Context ctx,
ModuleInfo mi, ModuleInfo mi,
@ -22,30 +43,41 @@ public class IfAnalyzer implements StatementAnalyzer<IfNode> {
SymbolTable locals, SymbolTable locals,
IfNode ifn) { IfNode ifn) {
// 1. 条件类型检查 // 1. 检查 if 条件表达式类型
// 获取对应条件表达式的表达式分析器
var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ifn.condition()); var exprAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ifn.condition());
// 对条件表达式执行类型分析
Type condType = exprAnalyzer.analyze(ctx, mi, fn, locals, ifn.condition()); Type condType = exprAnalyzer.analyze(ctx, mi, fn, locals, ifn.condition());
// 判断条件类型是否为 intSCompiler 约定 int 表示真假否则报错
if (condType != BuiltinType.INT) { if (condType != BuiltinType.INT) {
ctx.getErrors().add(new SemanticError(ifn, "if 条件必须为 int 类型(表示真假)")); ctx.getErrors().add(new SemanticError(ifn, "if 条件必须为 int 类型(表示真假)"));
} }
// 2. then 分支 // 2. 分析 then 分支
// 创建 then 分支的块级作用域以当前 locals 为父作用域
SymbolTable thenScope = new SymbolTable(locals); SymbolTable thenScope = new SymbolTable(locals);
// 遍历 then 分支下的每一条语句
for (var stmt : ifn.thenBranch()) { for (var stmt : ifn.thenBranch()) {
// 获取对应语句类型的分析器
var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
if (stAnalyzer != null) { if (stAnalyzer != null) {
// 对当前语句执行语义分析作用域为 thenScope
stAnalyzer.analyze(ctx, mi, fn, thenScope, stmt); stAnalyzer.analyze(ctx, mi, fn, thenScope, stmt);
} else { } else {
// 若找不到对应的分析器记录错误
ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt)); ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt));
} }
} }
// 3. else 分支可选 // 3. 分析 else 分支可选
if (!ifn.elseBranch().isEmpty()) { if (!ifn.elseBranch().isEmpty()) {
// 创建 else 分支的块级作用域同样以 locals 为父作用域
SymbolTable elseScope = new SymbolTable(locals); SymbolTable elseScope = new SymbolTable(locals);
// 遍历 else 分支下的每一条语句
for (var stmt : ifn.elseBranch()) { for (var stmt : ifn.elseBranch()) {
var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
if (stAnalyzer != null) { if (stAnalyzer != null) {
// 对当前语句执行语义分析作用域为 elseScope
stAnalyzer.analyze(ctx, mi, fn, elseScope, stmt); stAnalyzer.analyze(ctx, mi, fn, elseScope, stmt);
} else { } else {
ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt)); ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt));

View File

@ -11,10 +11,28 @@ import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.Type; import org.jcnc.snow.compiler.semantic.type.Type;
/** /**
* 改进版 {@code LoopAnalyzer}为循环结构提供块级作用域支持 * {@code LoopAnalyzer} 用于分析 for/while 等循环结构的语义正确性
* <p>
* 主要职责如下
* <ul>
* <li>为整个循环体包括初始化条件更新循环体本身创建独立的块级符号表作用域保证循环内变量与外部隔离</li>
* <li>依次分析初始化语句条件表达式更新语句和循环体各语句并递归检查嵌套的语法结构</li>
* <li>检查条件表达式的类型必须为 int布尔条件否则记录语义错误</li>
* <li>支持所有错误的收集而不中断流程便于一次性输出全部问题</li>
* </ul>
* 该分析器实现了 SCompiler 语言的块级作用域循环与类型健壮性是健全语义分析的基础部分
*/ */
public class LoopAnalyzer implements StatementAnalyzer<LoopNode> { public class LoopAnalyzer implements StatementAnalyzer<LoopNode> {
/**
* 分析循环结构 forwhile的语义合法性
*
* @param ctx 语义分析上下文错误收集等
* @param mi 当前模块信息
* @param fn 当前所在函数
* @param locals 外部传入的符号表本地作用域
* @param ln 当前循环节点
*/
@Override @Override
public void analyze(Context ctx, public void analyze(Context ctx,
ModuleInfo mi, ModuleInfo mi,
@ -22,34 +40,38 @@ public class LoopAnalyzer implements StatementAnalyzer<LoopNode> {
SymbolTable locals, SymbolTable locals,
LoopNode ln) { LoopNode ln) {
// 1. 整个循环的块级作用域 // 1. 创建整个循环结构的块级作用域
// 新建 loopScope以支持循环内部变量声明与外部隔离
SymbolTable loopScope = new SymbolTable(locals); SymbolTable loopScope = new SymbolTable(locals);
// 2. 初始化语句 // 2. 分析初始化语句 for(i=0)使用 loopScope 作为作用域
var initAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.initializer()); var initAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.initializer());
if (initAnalyzer != null) { if (initAnalyzer != null) {
initAnalyzer.analyze(ctx, mi, fn, loopScope, ln.initializer()); initAnalyzer.analyze(ctx, mi, fn, loopScope, ln.initializer());
} }
// 3. 条件表达式 // 3. 分析条件表达式 for(...; cond; ...) while(cond)
var condAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ln.condition()); var condAnalyzer = ctx.getRegistry().getExpressionAnalyzer(ln.condition());
Type condType = condAnalyzer.analyze(ctx, mi, fn, loopScope, ln.condition()); Type condType = condAnalyzer.analyze(ctx, mi, fn, loopScope, ln.condition());
// 条件类型必须为 int bool否则记录错误
if (condType != BuiltinType.INT) { if (condType != BuiltinType.INT) {
ctx.getErrors().add(new SemanticError(ln, "loop 条件必须为 int 类型(表示真假)")); ctx.getErrors().add(new SemanticError(ln, "loop 条件必须为 int 类型(表示真假)"));
} }
// 4. 更新语句 // 4. 分析更新语句 for(...; ...; update)
var updateAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.update()); var updateAnalyzer = ctx.getRegistry().getStatementAnalyzer(ln.update());
if (updateAnalyzer != null) { if (updateAnalyzer != null) {
updateAnalyzer.analyze(ctx, mi, fn, loopScope, ln.update()); updateAnalyzer.analyze(ctx, mi, fn, loopScope, ln.update());
} }
// 5. 循环体 // 5. 分析循环体内的每一条语句
for (var stmt : ln.body()) { for (var stmt : ln.body()) {
var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt); var stAnalyzer = ctx.getRegistry().getStatementAnalyzer(stmt);
if (stAnalyzer != null) { if (stAnalyzer != null) {
// 递归分析循环体语句作用域同样为 loopScope
stAnalyzer.analyze(ctx, mi, fn, loopScope, stmt); stAnalyzer.analyze(ctx, mi, fn, loopScope, stmt);
} else { } else {
// 不支持的语句类型记录错误
ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt)); ctx.getErrors().add(new SemanticError(stmt, "不支持的语句类型: " + stmt));
} }
} }

View File

@ -9,46 +9,41 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* {@code SemanticAnalyzerRunner} 是语义分析的统一入口 * {@code SemanticAnalyzerRunner} 是语义分析阶段的统一入口与调度器
* <p> * <p>
* 负责从原始 AST 中提取模块节点调用语义分析主流程 * 功能职责
* 并在出现语义错误时统一报告并终止编译流程
* <p>
* 使用方式
* <pre>{@code
* SemanticAnalyzerRunner.runSemanticAnalysis(ast, true);
* }</pre>
* <p>
* 功能概述
* <ul> * <ul>
* <li>筛选出所有 {@link ModuleNode} 节点模块级入口</li> * <li>从原始 AST 列表中过滤并收集所有 {@link ModuleNode} 节点作为模块分析的起点</li>
* <li>调用 {@link SemanticAnalyzer} 执行完整语义分析流程</li> * <li>调用 {@link SemanticAnalyzer} 对所有模块节点执行完整语义分析流程</li>
* <li>将收集到的 {@link SemanticError} 列表交由报告器处理</li> * <li>汇总并报告所有 {@link SemanticError}如有语义错误自动中止编译流程防止后续崩溃</li>
* <li>若存在语义错误调用 {@link SemanticAnalysisReporter#reportAndExitIfNecessary(List)} 自动中止流程</li>
* </ul> * </ul>
* <p>
* 推荐使用方式
* <pre>
* SemanticAnalyzerRunner.runSemanticAnalysis(ast, true);
* </pre>
* <p>
* 该类是实现 SCompiler 所有错误一次性输出且错误即终止 语义分析约束的关键
*/ */
public class SemanticAnalyzerRunner { public class SemanticAnalyzerRunner {
/** /**
* 对输入的语法树执行语义分析 * 对输入的语法树执行语义分析并自动报告
* <p>
* 语法树应为编译前阶段如解析器产出的 AST 列表
* 本方法会自动筛选其中的 {@link ModuleNode} 节点并调用语义分析器执行完整分析
* *
* @param ast 根节点列表应包含一个或多个 {@link ModuleNode} * @param ast 根节点列表应包含一个或多个 {@link ModuleNode}
* @param verbose 是否启用详细日志将控制内部 {@link Context#log(String)} 的行为 * @param verbose 是否启用详细日志将控制内部 {@link Context#log(String)} 的行为
*/ */
public static void runSemanticAnalysis(List<Node> ast, boolean verbose) { public static void runSemanticAnalysis(List<Node> ast, boolean verbose) {
// 1. 提取模块节点 // 1. AST 列表中过滤所有模块节点 ModuleNode
List<ModuleNode> modules = ast.stream() List<ModuleNode> modules = ast.stream()
.filter(ModuleNode.class::isInstance) .filter(ModuleNode.class::isInstance) // 保留类型为 ModuleNode 的节点
.map(ModuleNode.class::cast) .map(ModuleNode.class::cast) // 转换为 ModuleNode
.collect(Collectors.toList()); .collect(Collectors.toList()); // 收集为 List<ModuleNode>
// 2. 执行语义分析 // 2. 调用语义分析器对所有模块进行全流程语义分析返回错误列表
List<SemanticError> errors = new SemanticAnalyzer(verbose).analyze(modules); List<SemanticError> errors = new SemanticAnalyzer(verbose).analyze(modules);
// 3. 报告并在必要时终止 // 3. 统一报告全部语义错误如有错误则自动终止编译System.exit
SemanticAnalysisReporter.reportAndExitIfNecessary(errors); SemanticAnalysisReporter.reportAndExitIfNecessary(errors);
} }
} }

View File

@ -1,44 +1,32 @@
package org.jcnc.snow.compiler.semantic.error; package org.jcnc.snow.compiler.semantic.error;
import org.jcnc.snow.compiler.parser.ast.base.Node; import org.jcnc.snow.compiler.parser.ast.base.Node;
/** /**
* {@code SemanticError} 表示语义分析过程中发现的错误信息 * 表示一次语义错误<br/>
* <p>
* 语义错误是编译器无法接受的程序逻辑问题例如
* <ul> * <ul>
* <li>使用了未声明的变量</li> * <li>记录对应 {@link Node} 及出错信息</li>
* <li>类型不兼容的赋值</li> * <li>重写 {@link #toString()} <code> X, Y: message</code> 格式输出</li>
* <li>函数返回类型与实际返回值不一致</li> * <li>避免默认的 <code>Node@hash</code> 形式</li>
* <li>调用了不存在的函数或模块</li>
* </ul> * </ul>
* <p>
* 访问器方法{@code node()} / {@code message()}相等性判断等功能
*
* <p>主要字段说明
* <ul>
* <li>{@code node}发生语义错误的 AST 节点可用于定位源代码位置</li>
* <li>{@code message}具体的错误描述适合用于报错提示日志输出IDE 集成等</li>
* </ul>
*
* @param node 发生错误的抽象语法树节点 {@link Node}
* @param message 错误描述信息
*/ */
public record SemanticError(Node node, String message) { public record SemanticError(Node node, String message) {
/**
* 返回格式化后的语义错误信息字符串
* <p>
* 输出格式
* <pre>
* Semantic error at [节点]: [错误信息]
* </pre>
* 适用于命令行编译器输出调试日志或错误收集器展示
*
* @return 格式化的错误信息字符串
*/
@Override @Override
public String toString() { public String toString() {
return "Semantic error at " + node + ": " + message; // Node 假定提供 line() / column() 方法如无则返回 -1
int line = -1;
int col = -1;
if (node != null) {
try {
line = (int) node.getClass().getMethod("line").invoke(node);
col = (int) node.getClass().getMethod("column").invoke(node);
} catch (ReflectiveOperationException ignored) {
// Node 未提供 line/column 方法则保持 -1
}
}
String pos = (line >= 0 && col >= 0) ? ("" + line + ", 列 " + col) : "未知位置";
return pos + ": " + message;
} }
} }