From f4e2cf52f502fd20d8245d2ed730ae64a50da926 Mon Sep 17 00:00:00 2001
From: Luke
Date: Thu, 12 Jun 2025 16:56:34 +0800
Subject: [PATCH] =?UTF-8?q?docs:=20=E5=A2=9E=E5=8A=A0=E6=B3=A8=E9=87=8Adoc?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ir/builder/ExpressionBuilder.java | 10 ++-
.../lexer/scanners/OperatorTokenScanner.java | 72 ++++++++--------
.../parser/ast/UnaryExpressionNode.java | 22 ++++-
.../expression/UnaryOperatorParselet.java | 44 +++++++++-
.../expression/UnaryExpressionAnalyzer.java | 82 ++++++++++++++++---
.../snow/vm/engine/VirtualMachineEngine.java | 3 +-
6 files changed, 179 insertions(+), 54 deletions(-)
diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/ExpressionBuilder.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/ExpressionBuilder.java
index b725ff1..2d2e75e 100644
--- a/src/main/java/org/jcnc/snow/compiler/ir/builder/ExpressionBuilder.java
+++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/ExpressionBuilder.java
@@ -33,8 +33,15 @@ public record ExpressionBuilder(IRContext ctx) {
* 会根据节点的实际类型分别处理:
*
* - 数字字面量:新建常量寄存器
+ * - 布尔字面量:生成值为 0 或 1 的常量寄存器
* - 标识符:查找当前作用域中的寄存器
* - 二元表达式:递归处理子表达式并进行相应运算
+ * - 一元运算符:
+ *
+ * -x(取负,生成 NEG_I32 指令)与
+ * - code>!x(逻辑非,转换为
x == 0 比较指令)
+ *
+ *
* - 函数调用:生成对应的Call指令
* - 其它类型不支持,抛出异常
*
@@ -43,6 +50,7 @@ public record ExpressionBuilder(IRContext ctx) {
* @return 该表达式的计算结果寄存器
* @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型
*/
+
public IRVirtualRegister build(ExpressionNode expr) {
return switch (expr) {
// 数字字面量
@@ -176,7 +184,7 @@ public record ExpressionBuilder(IRContext ctx) {
.toList();
// 获取完整调用目标名称(支持成员/模块调用和普通调用)
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();
case IdentifierNode id -> id.name();
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());
diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/OperatorTokenScanner.java b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/OperatorTokenScanner.java
index 1d52c02..ec2f2bf 100644
--- a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/OperatorTokenScanner.java
+++ b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/OperatorTokenScanner.java
@@ -5,26 +5,29 @@ import org.jcnc.snow.compiler.lexer.token.Token;
import org.jcnc.snow.compiler.lexer.token.TokenType;
/**
- * 运算符扫描器:识别逻辑与比较运算符,包括单字符和双字符组合。
- *
- * 支持的运算符包括:
+ * 运算符扫描器(OperatorTokenScanner)
+ *
+ *
负责在词法分析阶段识别由 = ! < > | & % 等字符
+ * 起始的单字符或双字符运算符,并生成相应 {@link Token}:
+ *
*
- * - 赋值与比较:=、==、!=
- * - 关系运算符:>、>=、<、<=
- * - 逻辑运算符:&&、||
+ * - 赋值 / 比较:{@code =}, {@code ==}, {@code !=}
+ * - 关系运算:{@code >}, {@code >=}, {@code <}, {@code <=}
+ * - 逻辑运算:{@code &&}, {@code ||}
+ * - 取模运算:{@code %}
+ * - 逻辑非:{@code !}
*
- *
- * 不符合上述组合的字符会返回 {@code UNKNOWN} 类型的 Token。
+ *
+ *
如果无法匹配到合法组合,将返回 {@link TokenType#UNKNOWN}。
*/
public class OperatorTokenScanner extends AbstractTokenScanner {
/**
- * 判断是否可以处理当前位置的字符。
- * 运算符扫描器关注的起始字符包括:=、!、<、>、|、&
+ * 判断当前字符是否可能是运算符的起始字符。
*
* @param c 当前字符
- * @param ctx 当前词法上下文
- * @return 如果是潜在的运算符起始字符,则返回 true
+ * @param ctx 词法上下文
+ * @return 若是关注的起始字符则返回 {@code true}
*/
@Override
public boolean canHandle(char c, LexerContext ctx) {
@@ -32,14 +35,12 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
}
/**
- * 扫描并识别运算符 Token。
- * 支持组合运算符判断,如 ==、!=、>= 等,
- * 若无法匹配组合形式则退回单字符形式。
+ * 按最长匹配优先原则扫描并生成运算符 token。
*
- * @param ctx 词法上下文
+ * @param ctx 词法上下文
* @param line 当前行号
- * @param col 当前列号
- * @return 对应的运算符 Token,无法识别的运算符返回 {@code UNKNOWN}
+ * @param col 当前列号
+ * @return 已识别的 {@link Token}
*/
@Override
protected Token scanToken(LexerContext ctx, int line, int col) {
@@ -51,64 +52,71 @@ public class OperatorTokenScanner extends AbstractTokenScanner {
case '=':
if (ctx.match('=')) {
lexeme = "==";
- type = TokenType.DOUBLE_EQUALS;
+ type = TokenType.DOUBLE_EQUALS;
} else {
lexeme = "=";
- type = TokenType.EQUALS;
+ type = TokenType.EQUALS;
}
break;
+
case '!':
if (ctx.match('=')) {
lexeme = "!=";
- type = TokenType.NOT_EQUALS;
+ type = TokenType.NOT_EQUALS;
} else {
lexeme = "!";
- type = TokenType.NOT;
+ type = TokenType.NOT;
}
break;
+
case '>':
if (ctx.match('=')) {
lexeme = ">=";
- type = TokenType.GREATER_EQUAL;
+ type = TokenType.GREATER_EQUAL;
} else {
lexeme = ">";
- type = TokenType.GREATER_THAN;
+ type = TokenType.GREATER_THAN;
}
break;
+
case '<':
if (ctx.match('=')) {
lexeme = "<=";
- type = TokenType.LESS_EQUAL;
+ type = TokenType.LESS_EQUAL;
} else {
lexeme = "<";
- type = TokenType.LESS_THAN;
+ type = TokenType.LESS_THAN;
}
break;
+
case '%':
lexeme = "%";
- type = TokenType.MODULO;
+ type = TokenType.MODULO;
break;
+
case '&':
if (ctx.match('&')) {
lexeme = "&&";
- type = TokenType.AND;
+ type = TokenType.AND;
} else {
lexeme = "&";
- type = TokenType.UNKNOWN;
+ type = TokenType.UNKNOWN;
}
break;
+
case '|':
if (ctx.match('|')) {
lexeme = "||";
- type = TokenType.OR;
+ type = TokenType.OR;
} else {
lexeme = "|";
- type = TokenType.UNKNOWN;
+ type = TokenType.UNKNOWN;
}
break;
+
default:
lexeme = String.valueOf(c);
- type = TokenType.UNKNOWN;
+ type = TokenType.UNKNOWN;
}
return new Token(type, lexeme, line, col);
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/UnaryExpressionNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/UnaryExpressionNode.java
index be60c24..d956fc8 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/ast/UnaryExpressionNode.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/UnaryExpressionNode.java
@@ -3,15 +3,29 @@ package org.jcnc.snow.compiler.parser.ast;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
/**
- * 一元表达式节点,例如 -x 或 !x。
+ * {@code UnaryExpressionNode} —— 前缀一元运算 AST 节点。
*
- * @param operator 运算符字符串 ("-" / "!")
- * @param operand 操作数表达式
+ * 代表两种受支持的一元前缀表达式:
+ *
+ * - 取负:{@code -x}
+ * - 逻辑非:{@code !x}
+ *
+ *
+ * {@link #equals(Object)}、{@link #hashCode()} 等方法。
+ *
+ * @param operator 一元运算符(仅 "-" 或 "!")
+ * @param operand 运算对象 / 右操作数
*/
public record UnaryExpressionNode(String operator,
ExpressionNode operand) implements ExpressionNode {
- @Override public String toString() {
+ /**
+ * 生成调试友好的字符串表示,例如 {@code "-x"} 或 {@code "!flag"}。
+ *
+ * @return 一元表达式的串表示
+ */
+ @Override
+ public String toString() {
return operator + operand;
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/UnaryOperatorParselet.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/UnaryOperatorParselet.java
index 9d7a323..abfa97a 100644
--- a/src/main/java/org/jcnc/snow/compiler/parser/expression/UnaryOperatorParselet.java
+++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/UnaryOperatorParselet.java
@@ -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.expression.base.PrefixParselet;
-/** 前缀一元运算解析器(支持 - 和 !) */
+/**
+ * {@code UnaryOperatorParselet} —— 前缀一元运算符的 Pratt 解析器。
+ *
+ * 当前 parselet 负责解析两种前缀运算:
+ *
+ * - 取负:{@code -x}
+ * - 逻辑非:{@code !x}
+ *
+ *
+ * 解析过程:
+ *
+ *
+ * - 该 parselet 在外层解析器已消费运算符 {@code token} 后被调用。
+ * - 以 {@link Precedence#UNARY} 作为 绑定强度 递归解析右侧子表达式,
+ * 保证任何更高优先级的表达式(括号、字面量等)优先归属右侧。
+ * - 最终生成 {@link UnaryExpressionNode} AST 节点,记录运算符与操作数。
+ *
+ *
+ * 此类仅负责语法结构的构建:
+ *
+ * - 类型正确性在 {@code UnaryExpressionAnalyzer} 中校验;
+ * - IR 生成在 {@code ExpressionBuilder.buildUnary} 中完成。
+ *
+ */
public class UnaryOperatorParselet implements PrefixParselet {
+ /**
+ * 解析前缀一元表达式。
+ *
+ * @param ctx 当前解析上下文
+ * @param token 已被消费的运算符 Token(字面值应为 {@code "-" 或 "!"})
+ * @return 构建出的 {@link UnaryExpressionNode}
+ */
@Override
public ExpressionNode parse(ParserContext ctx, Token token) {
- // 递归解析右侧,使用自身优先级
- ExpressionNode right =
+ /* ------------------------------------------------------------
+ * 1. 以 UNARY 优先级递归解析操作数,避免错误结合顺序。
+ * ------------------------------------------------------------ */
+ ExpressionNode operand =
new PrattExpressionParser().parseExpression(ctx, Precedence.UNARY);
- return new UnaryExpressionNode(token.getLexeme(), right);
+
+ /* ------------------------------------------------------------
+ * 2. 封装成 AST 节点并返回。
+ * ------------------------------------------------------------ */
+ return new UnaryExpressionNode(token.getLexeme(), operand);
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnaryExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnaryExpressionAnalyzer.java
index 37f2ec0..53f6c8f 100644
--- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnaryExpressionAnalyzer.java
+++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnaryExpressionAnalyzer.java
@@ -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.Type;
-/** 一元表达式语义分析 */
+/**
+ * {@code UnaryExpressionAnalyzer} — 一元表达式的语义分析器。
+ *
+ * 目前实现两种一元运算:
+ *
+ * - {@code -x} 取负:仅允许作用于数值类型(int / float 等)。
+ * - {@code !x} 逻辑非:仅允许作用于 {@code boolean} 类型。
+ *
+ *
+ * 分析流程:
+ *
+ * - 递归分析操作数表达式,获取其类型 {@code operandType}。
+ * - 根据运算符检查类型合法性:
+ *
+ * - 若类型不符,记录 {@link SemanticError} 并返回一个占位类型
+ * (取负返回 {@link BuiltinType#INT},逻辑非返回
+ * {@link BuiltinType#BOOLEAN})。
+ * - 若合法,则返回运算后的结果类型
+ * (取负为 {@code operandType},逻辑非为 {@link BuiltinType#BOOLEAN})。
+ *
+ *
+ *
+ *
+ * 若遇到未支持的运算符,将生成错误并返回 {@code int} 作为占位类型。
+ *
+ */
public class UnaryExpressionAnalyzer implements ExpressionAnalyzer {
+ /**
+ * 对一元表达式进行语义分析。
+ *
+ * @param ctx 全局编译上下文,持有错误列表、注册表等
+ * @param mi 当前模块信息
+ * @param fn 所在函数节点(可为 {@code null} 表示顶层)
+ * @param locals 当前作用域符号表
+ * @param expr 要分析的一元表达式节点
+ * @return 表达式的结果类型;若有错误,返回占位类型并在 {@code ctx.getErrors()}
+ * 中记录 {@link SemanticError}
+ */
@Override
- public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn,
- SymbolTable locals, UnaryExpressionNode expr) {
+ public Type analyze(Context ctx,
+ ModuleInfo mi,
+ FunctionNode fn,
+ SymbolTable locals,
+ UnaryExpressionNode expr) {
- // 先分析操作数
+ /* ------------------------------------------------------------------
+ * 1. 先递归分析操作数,确定其类型
+ * ------------------------------------------------------------------ */
Type operandType = ctx.getRegistry()
- .getExpressionAnalyzer(expr.operand())
- .analyze(ctx, mi, fn, locals, expr.operand());
+ .getExpressionAnalyzer(expr.operand())
+ .analyze(ctx, mi, fn, locals, expr.operand());
+ /* ------------------------------------------------------------------
+ * 2. 根据运算符校验类型并给出结果类型
+ * ------------------------------------------------------------------ */
switch (expr.operator()) {
+ /* -------------- 取负运算 -------------- */
case "-" -> {
if (!operandType.isNumeric()) {
- ctx.getErrors().add(new SemanticError(expr,
- "'-' 只能应用于数值类型,当前为 " + operandType));
+ ctx.getErrors().add(new SemanticError(
+ expr,
+ "'-' 只能应用于数值类型,当前为 " + operandType
+ ));
+ // 返回占位类型,避免后续阶段 NPE
return BuiltinType.INT;
}
+ // 合法:结果类型与操作数相同
return operandType;
}
+
+ /* -------------- 逻辑非运算 -------------- */
case "!" -> {
if (operandType != BuiltinType.BOOLEAN) {
- ctx.getErrors().add(new SemanticError(expr,
- "'!' 只能应用于 boolean 类型,当前为 " + operandType));
+ ctx.getErrors().add(new SemanticError(
+ expr,
+ "'!' 只能应用于 boolean 类型,当前为 " + operandType
+ ));
return BuiltinType.BOOLEAN;
}
+ // 合法:结果类型恒为 boolean
return BuiltinType.BOOLEAN;
}
+
+ /* -------------- 未知运算符 -------------- */
default -> {
- ctx.getErrors().add(new SemanticError(expr,
- "未知一元运算符: " + expr.operator()));
+ ctx.getErrors().add(new SemanticError(
+ expr,
+ "未知一元运算符: " + expr.operator()
+ ));
return BuiltinType.INT;
}
}
diff --git a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java
index 14cef48..0f188ab 100644
--- a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java
+++ b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java
@@ -20,7 +20,8 @@ import java.util.List;
* {@link CommandExecutionHandler} — dispatches opcodes
*
*
- * Root-frame contract
+ * Root-frame contract:
+ *
* A root stack frame is pushed once via
* {@link #ensureRootFrame()} before the first instruction executes
* and is never popped. When a {@code RET} executed in the root frame