docs: 增加注释doc

This commit is contained in:
Luke 2025-06-12 16:56:34 +08:00
parent 7679da2657
commit f4e2cf52f5
6 changed files with 179 additions and 54 deletions

View File

@ -33,8 +33,15 @@ public record ExpressionBuilder(IRContext ctx) {
* <p>会根据节点的实际类型分别处理 * <p>会根据节点的实际类型分别处理
* <ul> * <ul>
* <li>数字字面量新建常量寄存器</li> * <li>数字字面量新建常量寄存器</li>
* <li>布尔字面量生成值为 0 1 的常量寄存器</li>
* <li>标识符查找当前作用域中的寄存器</li> * <li>标识符查找当前作用域中的寄存器</li>
* <li>二元表达式递归处理子表达式并进行相应运算</li> * <li>二元表达式递归处理子表达式并进行相应运算</li>
* <li>一元运算符
* <ul>
* <li><code>-x</code>取负生成 <code>NEG_I32</code> 指令</li>
* <li>code>!x</code>逻辑非转换为 <code>x == 0</code> 比较指令</li>
* </ul>
* </li>
* <li>函数调用生成对应的Call指令</li> * <li>函数调用生成对应的Call指令</li>
* <li>其它类型不支持抛出异常</li> * <li>其它类型不支持抛出异常</li>
* </ul> * </ul>
@ -43,6 +50,7 @@ public record ExpressionBuilder(IRContext ctx) {
* @return 该表达式的计算结果寄存器 * @return 该表达式的计算结果寄存器
* @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型 * @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型
*/ */
public IRVirtualRegister build(ExpressionNode expr) { public IRVirtualRegister build(ExpressionNode expr) {
return switch (expr) { return switch (expr) {
// 数字字面量 // 数字字面量
@ -176,7 +184,7 @@ public record ExpressionBuilder(IRContext ctx) {
.toList(); .toList();
// 获取完整调用目标名称支持成员/模块调用和普通调用 // 获取完整调用目标名称支持成员/模块调用和普通调用
String fullName = switch (call.callee()) { String fullName = switch (call.callee()) {
case MemberExpressionNode member when member.object() instanceof IdentifierNode mod -> case MemberExpressionNode member when member.object() instanceof IdentifierNode _ ->
((IdentifierNode)member.object()).name() + "." + member.member(); ((IdentifierNode)member.object()).name() + "." + member.member();
case IdentifierNode id -> id.name(); case IdentifierNode id -> id.name();
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName()); default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());

View File

@ -5,26 +5,29 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType; import org.jcnc.snow.compiler.lexer.token.TokenType;
/** /**
* 运算符扫描器识别逻辑与比较运算符包括单字符和双字符组合 * 运算符扫描器OperatorTokenScanner
* <p> *
* 支持的运算符包括 * <p>负责在词法分析阶段识别由 <b>= ! &lt; &gt; | &amp; %</b> 等字符
* 起始的单字符或双字符运算符并生成相应 {@link Token}</p>
*
* <ul> * <ul>
* <li>赋值与比较===!=</li> * <li>赋值 / 比较{@code =}, {@code ==}, {@code !=}</li>
* <li>关系运算符&gt;&gt;=&lt;&lt;=</li> * <li>关系运算{@code >}, {@code >=}, {@code <}, {@code <=}</li>
* <li>逻辑运算符&&||</li> * <li>逻辑运算{@code &&}, {@code ||}</li>
* <li>取模运算{@code %}</li>
* <li>逻辑非{@code !}</li>
* </ul> * </ul>
* <p> *
* 不符合上述组合的字符会返回 {@code UNKNOWN} 类型的 Token * <p>如果无法匹配到合法组合将返回 {@link TokenType#UNKNOWN}</p>
*/ */
public class OperatorTokenScanner extends AbstractTokenScanner { public class OperatorTokenScanner extends AbstractTokenScanner {
/** /**
* 判断是否可以处理当前位置的字符 * 判断当前字符是否可能是运算符的起始字符
* <p>运算符扫描器关注的起始字符包括=!&lt;&gt;|&amp;</p>
* *
* @param c 当前字符 * @param c 当前字符
* @param ctx 当前词法上下文 * @param ctx 词法上下文
* @return 如果是潜在的运算符起始字符则返回 true * @return 若是关注的起始字符则返回 {@code true}
*/ */
@Override @Override
public boolean canHandle(char c, LexerContext ctx) { public boolean canHandle(char c, LexerContext ctx) {
@ -32,14 +35,12 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
} }
/** /**
* 扫描并识别运算符 Token * 按最长匹配优先原则扫描并生成运算符 token
* <p>支持组合运算符判断 ==!=&gt;=
* 若无法匹配组合形式则退回单字符形式</p>
* *
* @param ctx 词法上下文 * @param ctx 词法上下文
* @param line 当前行号 * @param line 当前行号
* @param col 当前列号 * @param col 当前列号
* @return 对应的运算符 Token无法识别的运算符返回 {@code UNKNOWN} * @return 已识别的 {@link Token}
*/ */
@Override @Override
protected Token scanToken(LexerContext ctx, int line, int col) { protected Token scanToken(LexerContext ctx, int line, int col) {
@ -51,64 +52,71 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
case '=': case '=':
if (ctx.match('=')) { if (ctx.match('=')) {
lexeme = "=="; lexeme = "==";
type = TokenType.DOUBLE_EQUALS; type = TokenType.DOUBLE_EQUALS;
} else { } else {
lexeme = "="; lexeme = "=";
type = TokenType.EQUALS; type = TokenType.EQUALS;
} }
break; break;
case '!': case '!':
if (ctx.match('=')) { if (ctx.match('=')) {
lexeme = "!="; lexeme = "!=";
type = TokenType.NOT_EQUALS; type = TokenType.NOT_EQUALS;
} else { } else {
lexeme = "!"; lexeme = "!";
type = TokenType.NOT; type = TokenType.NOT;
} }
break; break;
case '>': case '>':
if (ctx.match('=')) { if (ctx.match('=')) {
lexeme = ">="; lexeme = ">=";
type = TokenType.GREATER_EQUAL; type = TokenType.GREATER_EQUAL;
} else { } else {
lexeme = ">"; lexeme = ">";
type = TokenType.GREATER_THAN; type = TokenType.GREATER_THAN;
} }
break; break;
case '<': case '<':
if (ctx.match('=')) { if (ctx.match('=')) {
lexeme = "<="; lexeme = "<=";
type = TokenType.LESS_EQUAL; type = TokenType.LESS_EQUAL;
} else { } else {
lexeme = "<"; lexeme = "<";
type = TokenType.LESS_THAN; type = TokenType.LESS_THAN;
} }
break; break;
case '%': case '%':
lexeme = "%"; lexeme = "%";
type = TokenType.MODULO; type = TokenType.MODULO;
break; break;
case '&': case '&':
if (ctx.match('&')) { if (ctx.match('&')) {
lexeme = "&&"; lexeme = "&&";
type = TokenType.AND; type = TokenType.AND;
} else { } else {
lexeme = "&"; lexeme = "&";
type = TokenType.UNKNOWN; type = TokenType.UNKNOWN;
} }
break; break;
case '|': case '|':
if (ctx.match('|')) { if (ctx.match('|')) {
lexeme = "||"; lexeme = "||";
type = TokenType.OR; type = TokenType.OR;
} else { } else {
lexeme = "|"; lexeme = "|";
type = TokenType.UNKNOWN; type = TokenType.UNKNOWN;
} }
break; break;
default: default:
lexeme = String.valueOf(c); lexeme = String.valueOf(c);
type = TokenType.UNKNOWN; type = TokenType.UNKNOWN;
} }
return new Token(type, lexeme, line, col); return new Token(type, lexeme, line, col);

View File

@ -3,15 +3,29 @@ package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/** /**
* 一元表达式节点例如 -x !x * {@code UnaryExpressionNode} 前缀一元运算 AST 节点
* *
* @param operator 运算符字符串 ("-" / "!") * <p>代表两种受支持的一元前缀表达式
* @param operand 操作数表达式 * <ul>
* <li><b>取负</b>{@code -x}</li>
* <li><b>逻辑非</b>{@code !x}</li>
* </ul>
*
* {@link #equals(Object)}{@link #hashCode()} 等方法</p>
*
* @param operator 一元运算符 "-" "!"
* @param operand 运算对象 / 右操作数
*/ */
public record UnaryExpressionNode(String operator, public record UnaryExpressionNode(String operator,
ExpressionNode operand) implements ExpressionNode { ExpressionNode operand) implements ExpressionNode {
@Override public String toString() { /**
* 生成调试友好的字符串表示例如 {@code "-x"} {@code "!flag"}
*
* @return 一元表达式的串表示
*/
@Override
public String toString() {
return operator + operand; return operator + operand;
} }
} }

View File

@ -6,14 +6,50 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.context.ParserContext;
import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet;
/** 前缀一元运算解析器(支持 - 和 ! */ /**
* {@code UnaryOperatorParselet} 前缀一元运算符的 Pratt 解析器
*
* <p>当前 parselet 负责解析两种前缀运算
* <ul>
* <li><b>取负</b>{@code -x}</li>
* <li><b>逻辑非</b>{@code !x}</li>
* </ul>
*
* 解析过程:
*
* <ol>
* <li> parselet 在外层解析器已消费运算符 {@code token} 后被调用</li>
* <li> {@link Precedence#UNARY} 作为 <em>绑定强度</em> 递归解析右侧子表达式
* 保证任何更高优先级的表达式括号字面量等优先归属右侧</li>
* <li>最终生成 {@link UnaryExpressionNode} AST 节点记录运算符与操作数</li>
* </ol>
*
* <p>此类仅负责<strong>语法结构</strong>的构建
* <ul>
* <li>类型正确性在 {@code UnaryExpressionAnalyzer} 中校验</li>
* <li>IR 生成在 {@code ExpressionBuilder.buildUnary} 中完成</li>
* </ul>
*/
public class UnaryOperatorParselet implements PrefixParselet { public class UnaryOperatorParselet implements PrefixParselet {
/**
* 解析前缀一元表达式
*
* @param ctx 当前解析上下文
* @param token 已被消费的运算符 Token字面值应为 {@code "-" "!"}
* @return 构建出的 {@link UnaryExpressionNode}
*/
@Override @Override
public ExpressionNode parse(ParserContext ctx, Token token) { public ExpressionNode parse(ParserContext ctx, Token token) {
// 递归解析右侧使用自身优先级 /* ------------------------------------------------------------
ExpressionNode right = * 1. UNARY 优先级递归解析操作数避免错误结合顺序
* ------------------------------------------------------------ */
ExpressionNode operand =
new PrattExpressionParser().parseExpression(ctx, Precedence.UNARY); new PrattExpressionParser().parseExpression(ctx, Precedence.UNARY);
return new UnaryExpressionNode(token.getLexeme(), right);
/* ------------------------------------------------------------
* 2. 封装成 AST 节点并返回
* ------------------------------------------------------------ */
return new UnaryExpressionNode(token.getLexeme(), operand);
} }
} }

View File

@ -10,38 +10,96 @@ import org.jcnc.snow.compiler.semantic.symbol.SymbolTable;
import org.jcnc.snow.compiler.semantic.type.BuiltinType; 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 UnaryExpressionAnalyzer} 一元表达式的语义分析器
*
* <p>目前实现两种一元运算
* <ul>
* <li>{@code -x} 取负仅允许作用于数值类型int / float </li>
* <li>{@code !x} 逻辑非仅允许作用于 {@code boolean} 类型</li>
* </ul>
*
* <p>分析流程
* <ol>
* <li>递归分析操作数表达式获取其类型 {@code operandType}</li>
* <li>根据运算符检查类型合法性
* <ul>
* <li>若类型不符记录 {@link SemanticError} 并返回一个占位类型
* 取负返回 {@link BuiltinType#INT}逻辑非返回
* {@link BuiltinType#BOOLEAN}</li>
* <li>若合法则返回运算后的结果类型
* 取负为 {@code operandType}逻辑非为 {@link BuiltinType#BOOLEAN}</li>
* </ul>
* </li>
* </ol>
*
* <p>若遇到未支持的运算符将生成错误并返回 {@code int} 作为占位类型</p>
*
*/
public class UnaryExpressionAnalyzer implements ExpressionAnalyzer<UnaryExpressionNode> { public class UnaryExpressionAnalyzer implements ExpressionAnalyzer<UnaryExpressionNode> {
/**
* 对一元表达式进行语义分析
*
* @param ctx 全局编译上下文持有错误列表注册表等
* @param mi 当前模块信息
* @param fn 所在函数节点可为 {@code null} 表示顶层
* @param locals 当前作用域符号表
* @param expr 要分析的一元表达式节点
* @return 表达式的结果类型若有错误返回占位类型并在 {@code ctx.getErrors()}
* 中记录 {@link SemanticError}
*/
@Override @Override
public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, public Type analyze(Context ctx,
SymbolTable locals, UnaryExpressionNode expr) { ModuleInfo mi,
FunctionNode fn,
SymbolTable locals,
UnaryExpressionNode expr) {
// 先分析操作数 /* ------------------------------------------------------------------
* 1. 先递归分析操作数确定其类型
* ------------------------------------------------------------------ */
Type operandType = ctx.getRegistry() Type operandType = ctx.getRegistry()
.getExpressionAnalyzer(expr.operand()) .getExpressionAnalyzer(expr.operand())
.analyze(ctx, mi, fn, locals, expr.operand()); .analyze(ctx, mi, fn, locals, expr.operand());
/* ------------------------------------------------------------------
* 2. 根据运算符校验类型并给出结果类型
* ------------------------------------------------------------------ */
switch (expr.operator()) { switch (expr.operator()) {
/* -------------- 取负运算 -------------- */
case "-" -> { case "-" -> {
if (!operandType.isNumeric()) { if (!operandType.isNumeric()) {
ctx.getErrors().add(new SemanticError(expr, ctx.getErrors().add(new SemanticError(
"'-' 只能应用于数值类型,当前为 " + operandType)); expr,
"'-' 只能应用于数值类型,当前为 " + operandType
));
// 返回占位类型避免后续阶段 NPE
return BuiltinType.INT; return BuiltinType.INT;
} }
// 合法结果类型与操作数相同
return operandType; return operandType;
} }
/* -------------- 逻辑非运算 -------------- */
case "!" -> { case "!" -> {
if (operandType != BuiltinType.BOOLEAN) { if (operandType != BuiltinType.BOOLEAN) {
ctx.getErrors().add(new SemanticError(expr, ctx.getErrors().add(new SemanticError(
"'!' 只能应用于 boolean 类型,当前为 " + operandType)); expr,
"'!' 只能应用于 boolean 类型,当前为 " + operandType
));
return BuiltinType.BOOLEAN; return BuiltinType.BOOLEAN;
} }
// 合法结果类型恒为 boolean
return BuiltinType.BOOLEAN; return BuiltinType.BOOLEAN;
} }
/* -------------- 未知运算符 -------------- */
default -> { default -> {
ctx.getErrors().add(new SemanticError(expr, ctx.getErrors().add(new SemanticError(
"未知一元运算符: " + expr.operator())); expr,
"未知一元运算符: " + expr.operator()
));
return BuiltinType.INT; return BuiltinType.INT;
} }
} }

View File

@ -20,7 +20,8 @@ import java.util.List;
* <li>{@link CommandExecutionHandler} dispatches opcodes</li> * <li>{@link CommandExecutionHandler} dispatches opcodes</li>
* </ul> * </ul>
* *
* <h2>Root-frame contract</h2> * Root-frame contract:
* <p>
* A <strong>root stack frame</strong> is pushed <em>once</em> via * A <strong>root stack frame</strong> is pushed <em>once</em> via
* {@link #ensureRootFrame()} before the first instruction executes * {@link #ensureRootFrame()} before the first instruction executes
* and is never popped. When a {@code RET} executed in the root frame * and is never popped. When a {@code RET} executed in the root frame