增加注释
This commit is contained in:
parent
4f2ee4a9f0
commit
56301944fa
@ -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());
|
||||||
|
// 判断条件类型是否为 int(SCompiler 约定 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));
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析循环结构(如 for、while)的语义合法性。
|
||||||
|
*
|
||||||
|
* @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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user