diff --git a/.run/Demo6.run.xml b/.run/Demo6.run.xml new file mode 100644 index 0000000..236a33b --- /dev/null +++ b/.run/Demo6.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/Demo7.run.xml b/.run/Demo7.run.xml new file mode 100644 index 0000000..2addca0 --- /dev/null +++ b/.run/Demo7.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/playground/Demo6/Main.snow b/playground/Demo6/Main.snow new file mode 100644 index 0000000..71b29c5 --- /dev/null +++ b/playground/Demo6/Main.snow @@ -0,0 +1,10 @@ +module: Main + function: main + parameter: + return_type: int + body: + declare b1 :int = -1 + return b1 + end body + end function +end module \ No newline at end of file diff --git a/playground/Demo7/Main.snow b/playground/Demo7/Main.snow new file mode 100644 index 0000000..aae7e15 --- /dev/null +++ b/playground/Demo7/Main.snow @@ -0,0 +1,10 @@ +module: Main + function: main + parameter: + return_type: boolean + body: + declare b1 :boolean = true + return !b1 + end body + end function +end module \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/backend/generator/BinaryOpGenerator.java b/src/main/java/org/jcnc/snow/compiler/backend/generator/BinaryOpGenerator.java index de907fb..91a4d91 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/generator/BinaryOpGenerator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/generator/BinaryOpGenerator.java @@ -2,24 +2,47 @@ package org.jcnc.snow.compiler.backend.generator; import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder; import org.jcnc.snow.compiler.backend.core.InstructionGenerator; +import org.jcnc.snow.compiler.backend.util.IROpCodeMapper; import org.jcnc.snow.compiler.backend.util.OpHelper; +import org.jcnc.snow.compiler.ir.core.IRValue; import org.jcnc.snow.compiler.ir.instruction.BinaryOperationInstruction; +import org.jcnc.snow.compiler.ir.value.IRConstant; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** - * 二元运算指令生成器 - * 支持二元运算指令的自动类型提升。 - *

类型提升优先级为:D > F > L > I > S > B

+ * 二元运算指令生成器。 + *

+ * 负责将中间表示的二元运算指令(算术、位运算及比较运算)生成对应的虚拟机指令序列, + * 并自动进行类型提升。 + * 同时实现 "+0 → MOV" 的 Peephole 优化,避免多余的 PUSH/ADD 序列。 + *

+ *

类型提升优先级:D > F > L > I > S > B

*/ public class BinaryOpGenerator implements InstructionGenerator { - /* ---------- 类型优先级工具 ---------- */ + /* -------------------- 常量与工具 -------------------- */ /** - * 返回类型前缀的优先级数值。数值越大,类型“越宽”。 - * D: 6, F: 5, L: 4, I: 3, S: 2, B: 1 + * 用于生成唯一标签的计数器 + */ + private static final AtomicInteger COUNTER = new AtomicInteger(0); + + /** + * 生成一个新的唯一标签。 + * + * @param fn 当前函数名,用于标签前缀 + * @param tag 标签用途标识 + * @return 形如 fn$tag$序号 的唯一标签 + */ + private static String fresh(String fn, String tag) { + return fn + "$" + tag + "$" + COUNTER.getAndIncrement(); + } + + /** + * 类型优先级:D > F > L > I > S > B */ private static int rank(char p) { return switch (p) { @@ -34,30 +57,42 @@ public class BinaryOpGenerator implements InstructionGenerator= rank(b) ? a : b; } /** - * 类型前缀转字符串,方便拼接。 + * 单字符转字符串 */ private static String str(char p) { return String.valueOf(p); } - /* ---------- 类型转换指令工具 ---------- */ + /** + * 判断常量值是否等于 0。 + * 仅支持 Java 原生数值类型。 + */ + private static boolean isZero(Object v) { + if (v == null) return false; + return switch (v) { + case Integer i -> i == 0; + case Long l -> l == 0L; + case Short s -> s == (short) 0; + case Byte b -> b == (byte) 0; + case Float f -> f == 0.0f; + case Double d -> d == 0.0; + default -> false; + }; + } /** - * 根据源类型和目标类型前缀,返回相应的类型转换指令助记符。 - * - * @param from 源类型前缀 - * @param to 目标类型前缀 - * @return 转换指令字符串,如 "I2L" 或 "F2D",若无需转换则返回null + * 获取从类型 {@code from} 到 {@code to} 的转换指令名。 + * 相同类型或无显式转换需求返回 {@code null}。 */ private static String convert(char from, char to) { - if (from == to) return null; // 类型一致,无需转换 + if (from == to) return null; return switch ("" + from + to) { case "IL" -> "I2L"; case "ID" -> "I2D"; @@ -73,61 +108,104 @@ public class BinaryOpGenerator implements InstructionGenerator "D2F"; case "SI" -> "S2I"; case "BI" -> "B2I"; - default -> null; // 其它组合暂未用到 + default -> null; }; } - /** - * 返回本生成器支持的指令类型,即 BinaryOperationInstruction。 - */ + /* -------------------- 接口实现 -------------------- */ + @Override public Class supportedClass() { return BinaryOperationInstruction.class; } - /** - * 生成二元运算的虚拟机指令,实现自动类型提升和必要的类型转换。 - * - * @param ins 当前二元运算IR指令 - * @param out 虚拟机程序构建器 - * @param slotMap IR虚拟寄存器到实际槽位编号的映射 - * @param currentFn 当前函数名 - */ @Override public void generate(BinaryOperationInstruction ins, VMProgramBuilder out, Map slotMap, String currentFn) { - /* ------- 1. 获取左右操作数的槽位编号和类型 ------- */ - int lSlot = slotMap.get((IRVirtualRegister) ins.operands().get(0)); // 左操作数槽位 - int rSlot = slotMap.get((IRVirtualRegister) ins.operands().get(1)); // 右操作数槽位 - int dSlot = slotMap.get(ins.dest()); // 目标槽位 - char lType = out.getSlotType(lSlot); // 左操作数类型前缀 - char rType = out.getSlotType(rSlot); // 右操作数类型前缀 + /* ---------- 0. +0 → MOV Peephole 优化 ---------- */ + String irName = ins.op().name(); + if (irName.startsWith("ADD_")) { + IRValue lhs = ins.operands().getFirst(); + IRValue rhs = ins.operands().get(1); - // 类型提升,确定本次二元运算的目标类型(优先级较高的那一个) - char tType = promote(lType, rType); - String tPref = str(tType); // 用于拼接指令字符串 + boolean lhsZero = lhs instanceof IRConstant && isZero(((IRConstant) lhs).value()); + boolean rhsZero = rhs instanceof IRConstant && isZero(((IRConstant) rhs).value()); - /* ------- 2. 加载左操作数,并自动进行类型转换(如有必要) ------- */ - out.emit(OpHelper.opcode(str(lType) + "_LOAD") + " " + lSlot); // LOAD指令 - String cvt = convert(lType, tType); // 如需类型提升 - if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令 + // 仅当一侧为常量 0 时可替换为 MOV + if (lhsZero ^ rhsZero) { + IRVirtualRegister srcVr = null; + if ((lhsZero ? rhs : lhs) instanceof IRVirtualRegister) { + srcVr = (IRVirtualRegister) (lhsZero ? rhs : lhs); + } + int srcSlot = slotMap.get(srcVr); + int destSlot = slotMap.get(ins.dest()); - /* ------- 3. 加载右操作数,并自动进行类型转换(如有必要) ------- */ - out.emit(OpHelper.opcode(str(rType) + "_LOAD") + " " + rSlot); // LOAD指令 - cvt = convert(rType, tType); // 如需类型提升 - if (cvt != null) out.emit(OpHelper.opcode(cvt)); // 插入类型转换指令 + // 源与目标槽位不同才需要发 MOV + if (srcSlot != destSlot) { + out.emit(OpHelper.opcode("MOV") + " " + srcSlot + " " + destSlot); + } + // 复制槽位类型信息 + out.setSlotType(destSlot, out.getSlotType(srcSlot)); + return; // 优化路径结束 + } + } - /* ------- 4. 生成具体的二元运算指令 ------- */ - // 获取IR指令中的操作名(如ADD、SUB、MUL等,去掉结尾的"_"后缀) - String opName = ins.op().name().split("_")[0]; - // 例如生成 "I_ADD", "D_MUL" 等虚拟机指令 - out.emit(OpHelper.opcode(tPref + "_" + opName)); + /* ---------- 1. 槽位与类型 ---------- */ + int lSlot = slotMap.get((IRVirtualRegister) ins.operands().get(0)); + int rSlot = slotMap.get((IRVirtualRegister) ins.operands().get(1)); + int dSlot = slotMap.get(ins.dest()); - /* ------- 5. 结果存入目标槽位,并更新槽位类型 ------- */ - out.emit(OpHelper.opcode(tPref + "_STORE") + " " + dSlot); - out.setSlotType(dSlot, tType); // 记录运算结果的类型前缀,便于后续指令正确处理 + char lType = out.getSlotType(lSlot); // 未登记默认 'I' + char rType = out.getSlotType(rSlot); + + char tType = promote(lType, rType); // 类型提升结果 + String tPre = str(tType); + + /* ---------- 2. 加载并做类型转换 ---------- */ + out.emit(OpHelper.opcode(str(lType) + "_LOAD") + " " + lSlot); + String cvt = convert(lType, tType); + if (cvt != null) out.emit(OpHelper.opcode(cvt)); + + out.emit(OpHelper.opcode(str(rType) + "_LOAD") + " " + rSlot); + cvt = convert(rType, tType); + if (cvt != null) out.emit(OpHelper.opcode(cvt)); + + /* ---------- 3. 区分算术 / 比较 ---------- */ + boolean isCmp = irName.startsWith("CMP_"); + + /* === 3-A. 普通算术 / 位运算 === */ + if (!isCmp) { + String opCore = irName.split("_")[0]; // ADD / SUB / MUL … + out.emit(OpHelper.opcode(tPre + "_" + opCore)); + out.emit(OpHelper.opcode(tPre + "_STORE") + " " + dSlot); + out.setSlotType(dSlot, tType); + return; + } + + /* === 3-B. CMP_* —— 生成布尔结果 === */ + String branchOp = OpHelper.opcode(IROpCodeMapper.toVMOp(ins.op())); // IC_E / IC_NE … + String lblTrue = fresh(currentFn, "true"); + String lblEnd = fresh(currentFn, "end"); + + // ① 条件跳转;成立 → lblTrue + out.emitBranch(branchOp, lblTrue); + + // ② 不成立:压 0 + out.emit(OpHelper.opcode("I_PUSH") + " 0"); + out.emitBranch(OpHelper.opcode("JUMP"), lblEnd); + + // ③ 成立分支:压 1 + out.emit(lblTrue + ":"); + out.emit(OpHelper.opcode("I_PUSH") + " 1"); + + // ④ 结束标签 + out.emit(lblEnd + ":"); + + // ⑤ 写入目标槽位 + out.emit(OpHelper.opcode("I_STORE") + " " + dSlot); + out.setSlotType(dSlot, 'I'); // 布尔 ➜ int } -} +} \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/backend/generator/UnaryOpGenerator.java b/src/main/java/org/jcnc/snow/compiler/backend/generator/UnaryOpGenerator.java index f1c5db2..46d1f53 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/generator/UnaryOpGenerator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/generator/UnaryOpGenerator.java @@ -41,13 +41,29 @@ public class UnaryOpGenerator implements InstructionGenerator slotMap, String currentFn) { - // 获取操作数所在槽号 - int slotId = slotMap.get((IRVirtualRegister) ins.operands().getFirst()); - // 加载操作数到虚拟机栈顶 - out.emit(OpHelper.opcode("I_LOAD") + " " + slotId); - // 生成对应的一元运算操作码(如取负等) - out.emit(OpHelper.opcode(IROpCodeMapper.toVMOp(ins.op()))); - // 将结果存储到目标寄存器槽 - out.emit(OpHelper.opcode("I_STORE") + " " + slotMap.get(ins.dest())); + + /* -------- 1. 源槽位与类型 -------- */ + int srcSlot = slotMap.get((IRVirtualRegister) ins.operands().getFirst()); + char prefix = out.getSlotType(srcSlot); // 未登记则返回默认 'I' + + String loadOp = prefix + "_LOAD"; + String storeOp = prefix + "_STORE"; + + /* -------- 2. 指令序列 -------- */ + // 2-A. 加载操作数 + out.emit(OpHelper.opcode(loadOp) + + " " + srcSlot); + + // 2-B. 执行具体一元运算(NEG、NOT…) + out.emit(OpHelper.opcode( + IROpCodeMapper.toVMOp(ins.op()))); + + // 2-C. 存结果到目标槽 + int destSlot = slotMap.get(ins.dest()); + out.emit(OpHelper.opcode(storeOp) + + " " + destSlot); + + /* -------- 3. 更新目标槽类型 -------- */ + out.setSlotType(destSlot, prefix); } } 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 a658d9d..11288ba 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 @@ -3,6 +3,7 @@ package org.jcnc.snow.compiler.ir.builder; import org.jcnc.snow.compiler.ir.core.IROpCode; import org.jcnc.snow.compiler.ir.instruction.CallInstruction; import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction; +import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction; import org.jcnc.snow.compiler.ir.value.IRConstant; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import org.jcnc.snow.compiler.ir.utils.ExpressionUtils; @@ -32,8 +33,15 @@ public record ExpressionBuilder(IRContext ctx) { *

会根据节点的实际类型分别处理: *

    *
  • 数字字面量:新建常量寄存器
  • + *
  • 布尔字面量:生成值为 0 或 1 的常量寄存器
  • *
  • 标识符:查找当前作用域中的寄存器
  • *
  • 二元表达式:递归处理子表达式并进行相应运算
  • + *
  • 一元运算符: + *
      + *
    • -x(取负,生成 NEG_I32 指令)与
    • + *
    • code>!x(逻辑非,转换为 x == 0 比较指令)
    • + *
    + *
  • *
  • 函数调用:生成对应的Call指令
  • *
  • 其它类型不支持,抛出异常
  • *
@@ -42,6 +50,7 @@ public record ExpressionBuilder(IRContext ctx) { * @return 该表达式的计算结果寄存器 * @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型 */ + public IRVirtualRegister build(ExpressionNode expr) { return switch (expr) { // 数字字面量 @@ -59,11 +68,33 @@ public record ExpressionBuilder(IRContext ctx) { case BinaryExpressionNode bin -> buildBinary(bin); // 函数调用 case CallExpressionNode call -> buildCall(call); + case UnaryExpressionNode u -> buildUnary(u); default -> throw new IllegalStateException( "不支持的表达式类型: " + expr.getClass().getSimpleName()); }; } + /** 处理一元表达式 */ + private IRVirtualRegister buildUnary(UnaryExpressionNode un) { + String op = un.operator(); + IRVirtualRegister val = build(un.operand()); + + // -x → NEG_*(根据类型自动选择位宽) + if (op.equals("-")) { + IRVirtualRegister dest = ctx.newRegister(); + IROpCode code = ExpressionUtils.negOp(un.operand()); + ctx.addInstruction(new UnaryOperationInstruction(code, dest, val)); + return dest; + } + + // !x → (x == 0) + if (op.equals("!")) { + IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0); + return InstructionFactory.binOp(ctx, IROpCode.CMP_EQ, val, zero); + } + + throw new IllegalStateException("未知一元运算符: " + op); + } /** * 直接将表达式计算结果写入指定的目标寄存器(dest)。 @@ -154,7 +185,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/ir/builder/IRContext.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/IRContext.java index 7b7232d..4c5078d 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/builder/IRContext.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/IRContext.java @@ -19,7 +19,7 @@ import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; */ public class IRContext { - /* ➡ 新增:生成唯一标签用 */ + /* 生成唯一标签用 */ private int labelCounter = 0; /** * 当前正在构建的 IRFunction 对象,所有指令将添加至此 diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java index b0a9993..9b7a467 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/InstructionFactory.java @@ -78,7 +78,7 @@ public class InstructionFactory { } /** - * 简易 Move 指令(src → dest)。若寄存器相同也安全。 + * Move 指令(src → dest)。若寄存器相同也安全。 *

* 实现方式:dest = src + 0(即加上常量 0)。 *

@@ -88,7 +88,11 @@ public class InstructionFactory { * @param dest 目标寄存器 */ public static void move(IRContext ctx, IRVirtualRegister src, IRVirtualRegister dest) { - /* 采用 “dest = src + 0” 的最简实现 */ + // 自赋值无需任何操作,避免生成多余的常量 0 寄存器 + if (src == dest) { + return; + } + // 回退实现:dest = src + 0 IRVirtualRegister zero = loadConst(ctx, 0); ctx.addInstruction(new BinaryOperationInstruction(IROpCode.ADD_I32, dest, src, zero)); } diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/StatementBuilder.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/StatementBuilder.java index 18f750b..8a95ca5 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/builder/StatementBuilder.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/StatementBuilder.java @@ -8,6 +8,8 @@ import org.jcnc.snow.compiler.parser.ast.*; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import org.jcnc.snow.compiler.parser.ast.base.StatementNode; +import java.util.Locale; + /** * StatementBuilder —— 将 AST 语句节点 ({@link StatementNode}) 转换为 IR 指令序列的构建器。 *

@@ -177,4 +179,16 @@ public class StatementBuilder { InstructionFactory.cmpJump(ctx, IROpCode.CMP_EQ, condReg, zero, falseLabel); } } + + private static char typeSuffixFromType(String type) { + if (type == null) return '\0'; + return switch (type.toLowerCase(Locale.ROOT)) { + case "byte" -> 'b'; + case "short" -> 's'; + case "long" -> 'l'; + case "float" -> 'f'; + case "double" -> 'd'; + default -> '\0'; // 其余默认按 32-bit 整型处理 + }; + } } diff --git a/src/main/java/org/jcnc/snow/compiler/ir/utils/ExpressionUtils.java b/src/main/java/org/jcnc/snow/compiler/ir/utils/ExpressionUtils.java index cc1e426..91155df 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/utils/ExpressionUtils.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/utils/ExpressionUtils.java @@ -78,6 +78,30 @@ public class ExpressionUtils { }; } + /** + * 根据表达式节点推断一元取负(-)运算应使用的操作码。 + * + *

优先级与 {@link #resolveOpCode} 使用的类型提升规则保持一致:

+ *
    + *
  • 字面量或标识符带显式后缀时,直接以后缀决定位宽;
  • + *
  • 未显式指定时,默认使用 32 位整型 {@link IROpCode#NEG_I32}。
  • + *
+ * + * @param operand 一元取负运算的操作数 + * @return 匹配的 {@link IROpCode} + */ + public static IROpCode negOp(ExpressionNode operand) { + char t = typeChar(operand); + return switch (t) { + case 'b' -> IROpCode.NEG_B8; + case 's' -> IROpCode.NEG_S16; + case 'l' -> IROpCode.NEG_L64; + case 'f' -> IROpCode.NEG_F32; + case 'd' -> IROpCode.NEG_D64; + default -> IROpCode.NEG_I32; + }; + } + /* =================== 类型推断与操作符匹配 =================== */ /** 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 6b8dde9..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.UNKNOWN; + 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/lexer/token/TokenFactory.java b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java index 98155f6..e6463f4 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenFactory.java @@ -19,9 +19,6 @@ import java.util.Set; *
  • 对不合法的词素自动标记为 UNKNOWN 类型。
  • * *

    - * - * @author 你的名字 - * @version 1.0 */ public class TokenFactory { diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenType.java b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenType.java index 4ff4394..554d48c 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenType.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/token/TokenType.java @@ -10,6 +10,7 @@ package org.jcnc.snow.compiler.lexer.token; */ public enum TokenType { + /* ---------- 基础 ---------- */ /** 普通标识符,如变量名、函数名等 */ IDENTIFIER, @@ -19,14 +20,17 @@ public enum TokenType { /** 内置类型名称(如 int、string、bool 等) */ TYPE, + /* ---------- 字面量 ---------- */ /** 布尔字面量 (true / false) */ BOOL_LITERAL, + /** 字符串字面量(如 "hello") */ STRING_LITERAL, /** 数字字面量(整数或浮点数) */ NUMBER_LITERAL, + /* ---------- 分隔符 ---------- */ /** 冒号 ':' */ COLON, @@ -36,6 +40,7 @@ public enum TokenType { /** 点号 '.' */ DOT, + /* ---------- 运算符 ---------- */ /** 赋值符号 '=' */ EQUALS, @@ -53,6 +58,9 @@ public enum TokenType { /** 减号 '-' */ MINUS, + /** 取反 '!' */ + NOT, + /** 左括号 '(' */ LPAREN, diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/BoolLiteralNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/BoolLiteralNode.java index a82b98c..15c7088 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/ast/BoolLiteralNode.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/BoolLiteralNode.java @@ -5,15 +5,13 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; /** * 表示布尔字面量(boolean literal)的抽象语法树(AST)节点。 *

    - * 本类实现了 {@link ExpressionNode} 接口,用于在编译器前端构建语法分析过程中, + * 该纪录类实现 {@link ExpressionNode} 接口,用于在编译器前端构建语法分析过程中, * 表达布尔类型的字面量常量(如 "true" 或 "false")。 *

    + * + * @param value 字面量的布尔值 */ -public class BoolLiteralNode implements ExpressionNode { - /** - * 字面量的布尔值。 - */ - private final boolean value; +public record BoolLiteralNode(boolean value) implements ExpressionNode { /** * 使用布尔字面量字符串构造一个 {@code BoolLiteralNode} 实例。 @@ -25,7 +23,7 @@ public class BoolLiteralNode implements ExpressionNode { * @param lexeme 布尔字面量的字符串表示 */ public BoolLiteralNode(String lexeme) { - this.value = Boolean.parseBoolean(lexeme); + this(Boolean.parseBoolean(lexeme)); } /** 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 new file mode 100644 index 0000000..d956fc8 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/UnaryExpressionNode.java @@ -0,0 +1,31 @@ +package org.jcnc.snow.compiler.parser.ast; + +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; + +/** + * {@code UnaryExpressionNode} —— 前缀一元运算 AST 节点。 + * + *

    代表两种受支持的一元前缀表达式: + *

      + *
    • 取负:{@code -x}
    • + *
    • 逻辑非:{@code !x}
    • + *
    + * + * {@link #equals(Object)}、{@link #hashCode()} 等方法。

    + * + * @param operator 一元运算符(仅 "-" 或 "!") + * @param operand 运算对象 / 右操作数 + */ +public record UnaryExpressionNode(String operator, + ExpressionNode operand) implements ExpressionNode { + + /** + * 生成调试友好的字符串表示,例如 {@code "-x"} 或 {@code "!flag"}。 + * + * @return 一元表达式的串表示 + */ + @Override + public String toString() { + return operator + operand; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java index 1a1d124..4f79274 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/PrattExpressionParser.java @@ -45,6 +45,10 @@ public class PrattExpressionParser implements ExpressionParser { prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet()); prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet()); + // 注册一元前缀运算 + prefixes.put(TokenType.MINUS.name(), new UnaryOperatorParselet()); + prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet()); + // 注册中缀解析器 infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true)); infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true)); diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/Precedence.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/Precedence.java index 5eb7efe..61f4dab 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/expression/Precedence.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/Precedence.java @@ -24,6 +24,9 @@ public enum Precedence { */ PRODUCT, + /** 一元前缀(-x !x) */ + UNARY, + /** * 函数调用、成员访问等最强绑定(例如 foo()、obj.prop)。 */ 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 new file mode 100644 index 0000000..abfa97a --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/UnaryOperatorParselet.java @@ -0,0 +1,55 @@ +package org.jcnc.snow.compiler.parser.expression; + +import org.jcnc.snow.compiler.lexer.token.Token; +import org.jcnc.snow.compiler.parser.ast.UnaryExpressionNode; +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}
    • + *
    + * + * 解析过程: + * + *
      + *
    1. 该 parselet 在外层解析器已消费运算符 {@code token} 后被调用。
    2. + *
    3. 以 {@link Precedence#UNARY} 作为 绑定强度 递归解析右侧子表达式, + * 保证任何更高优先级的表达式(括号、字面量等)优先归属右侧。
    4. + *
    5. 最终生成 {@link UnaryExpressionNode} AST 节点,记录运算符与操作数。
    6. + *
    + * + *

    此类仅负责语法结构的构建: + *

      + *
    • 类型正确性在 {@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) { + /* ------------------------------------------------------------ + * 1. 以 UNARY 优先级递归解析操作数,避免错误结合顺序。 + * ------------------------------------------------------------ */ + ExpressionNode operand = + new PrattExpressionParser().parseExpression(ctx, Precedence.UNARY); + + /* ------------------------------------------------------------ + * 2. 封装成 AST 节点并返回。 + * ------------------------------------------------------------ */ + return new UnaryExpressionNode(token.getLexeme(), operand); + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/function/FunctionParser.java b/src/main/java/org/jcnc/snow/compiler/parser/function/FunctionParser.java index 372387c..af2251e 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/function/FunctionParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/function/FunctionParser.java @@ -140,7 +140,6 @@ public class FunctionParser implements TopLevelParser { private void parseFunctionFooter(TokenStream ts) { ts.expect("end"); ts.expect("function"); - ts.expectType(TokenType.NEWLINE); } /** diff --git a/src/main/java/org/jcnc/snow/compiler/parser/utils/ASTJsonSerializer.java b/src/main/java/org/jcnc/snow/compiler/parser/utils/ASTJsonSerializer.java index 87dc160..aec4b50 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/utils/ASTJsonSerializer.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/utils/ASTJsonSerializer.java @@ -13,7 +13,7 @@ import java.util.*; * 并可借助 {@code JSONParser.toJson(Object)} 方法将其序列化为 JSON 字符串,用于调试、 * 可视化或跨语言数据传输。 *

    - * 支持的节点类型包括: + * 支持的节点类型包括(新增对 {@code BoolLiteralNode}、{@code UnaryExpressionNode} 的完整支持): *

      *
    • {@link ModuleNode}
    • *
    • {@link FunctionNode}
    • @@ -23,7 +23,9 @@ import java.util.*; *
    • {@link LoopNode}
    • *
    • {@link ReturnNode}
    • *
    • {@link ExpressionStatementNode}
    • - *
    • 各类 {@link ExpressionNode} 子类型,如 {@code BinaryExpressionNode}, {@code IdentifierNode} 等
    • + *
    • {@link BoolLiteralNode}
    • + *
    • {@link UnaryExpressionNode}
    • + *
    • 以及各类 {@link ExpressionNode} 子类型,如 {@code BinaryExpressionNode}, {@code IdentifierNode} 等
    • *
    */ public class ASTJsonSerializer { @@ -174,19 +176,32 @@ public class ASTJsonSerializer { */ private static Object exprToMap(ExpressionNode expr) { return switch (expr) { + // 二元表达式 case BinaryExpressionNode(ExpressionNode left, String operator, ExpressionNode right) -> exprMap("BinaryExpression", "left", exprToMap(left), "operator", operator, "right", exprToMap(right) ); + // 一元表达式 + case UnaryExpressionNode(String operator, ExpressionNode operand) -> exprMap("UnaryExpression", + "operator", operator, + "operand", exprToMap(operand) + ); + // 布尔字面量 + case BoolLiteralNode(boolean value) -> exprMap("BoolLiteral", "value", value); + // 标识符 case IdentifierNode(String name) -> exprMap("Identifier", "name", name); + // 数字字面量 case NumberLiteralNode(String value) -> exprMap("NumberLiteral", "value", value); + // 字符串字面量 case StringLiteralNode(String value) -> exprMap("StringLiteral", "value", value); - case CallExpressionNode(ExpressionNode callee, List arguments, int line, int column, String file) -> { + // 调用表达式 + case CallExpressionNode(ExpressionNode callee, List arguments, _, _, _) -> { List args = new ArrayList<>(arguments.size()); for (ExpressionNode arg : arguments) args.add(exprToMap(arg)); yield exprMap("CallExpression", "callee", exprToMap(callee), "arguments", args); } + // 成员访问表达式 case MemberExpressionNode(ExpressionNode object, String member) -> exprMap("MemberExpression", "object", exprToMap(object), "member", member @@ -195,4 +210,4 @@ public class ASTJsonSerializer { default -> Map.of("type", expr.getClass().getSimpleName()); }; } -} \ No newline at end of file +} 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 new file mode 100644 index 0000000..53f6c8f --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/UnaryExpressionAnalyzer.java @@ -0,0 +1,107 @@ +package org.jcnc.snow.compiler.semantic.analyzers.expression; + +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.UnaryExpressionNode; +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; +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} 类型。
    • + *
    + * + *

    分析流程: + *

      + *
    1. 递归分析操作数表达式,获取其类型 {@code operandType}。
    2. + *
    3. 根据运算符检查类型合法性: + *
        + *
      • 若类型不符,记录 {@link SemanticError} 并返回一个占位类型 + * (取负返回 {@link BuiltinType#INT},逻辑非返回 + * {@link BuiltinType#BOOLEAN})。
      • + *
      • 若合法,则返回运算后的结果类型 + * (取负为 {@code operandType},逻辑非为 {@link BuiltinType#BOOLEAN})。
      • + *
      + *
    4. + *
    + * + *

    若遇到未支持的运算符,将生成错误并返回 {@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) { + + /* ------------------------------------------------------------------ + * 1. 先递归分析操作数,确定其类型 + * ------------------------------------------------------------------ */ + Type operandType = ctx.getRegistry() + .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 + )); + // 返回占位类型,避免后续阶段 NPE + return BuiltinType.INT; + } + // 合法:结果类型与操作数相同 + return operandType; + } + + /* -------------- 逻辑非运算 -------------- */ + case "!" -> { + if (operandType != BuiltinType.BOOLEAN) { + ctx.getErrors().add(new SemanticError( + expr, + "'!' 只能应用于 boolean 类型,当前为 " + operandType + )); + return BuiltinType.BOOLEAN; + } + // 合法:结果类型恒为 boolean + return BuiltinType.BOOLEAN; + } + + /* -------------- 未知运算符 -------------- */ + default -> { + ctx.getErrors().add(new SemanticError( + expr, + "未知一元运算符: " + expr.operator() + )); + return BuiltinType.INT; + } + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java index 865bb5e..c8de9e0 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/AnalyzerRegistrar.java @@ -55,7 +55,10 @@ public final class AnalyzerRegistrar { registry.registerExpressionAnalyzer(CallExpressionNode.class, new CallExpressionAnalyzer()); registry.registerExpressionAnalyzer(BinaryExpressionNode.class, new BinaryExpressionAnalyzer()); - // 对尚未实现的表达式类型使用兜底处理器(如 MemberExpression) + // ---------- 注册一元表达式分析器 ---------- + registry.registerExpressionAnalyzer(UnaryExpressionNode.class,new UnaryExpressionAnalyzer()); + + // 对尚未实现的表达式类型使用兜底处理器 registry.registerExpressionAnalyzer(MemberExpressionNode.class, new UnsupportedExpressionAnalyzer<>()); } 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