diff --git a/.run/Demo19.run.xml b/.run/Demo19.run.xml new file mode 100644 index 0000000..0896751 --- /dev/null +++ b/.run/Demo19.run.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.run/Demo20.run.xml b/.run/Demo20.run.xml new file mode 100644 index 0000000..bdf690b --- /dev/null +++ b/.run/Demo20.run.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.run/Demo21.run.xml b/.run/Demo21.run.xml new file mode 100644 index 0000000..2076c8c --- /dev/null +++ b/.run/Demo21.run.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.run/测试.run.xml b/.run/测试.run.xml index 097c514..dc6fb89 100644 --- a/.run/测试.run.xml +++ b/.run/测试.run.xml @@ -9,7 +9,10 @@ + + + diff --git a/playground/Demo/Demo18/Main.snow b/playground/Demo/Demo18/Main.snow index e41610c..107f100 100644 --- a/playground/Demo/Demo18/Main.snow +++ b/playground/Demo/Demo18/Main.snow @@ -31,8 +31,8 @@ module: Main if j % 2 == 0 then continue end if - print(i) - print(j) + os.print(i) + os.print(j) end body end loop end body diff --git a/playground/Demo/Demo19/Main.snow b/playground/Demo/Demo19/Main.snow new file mode 100644 index 0000000..5bcef84 --- /dev/null +++ b/playground/Demo/Demo19/Main.snow @@ -0,0 +1,26 @@ +module: Main + import: os + function: main + return_type: void + body: + declare n: int[][][][] = [ + [ + [ [17, 18], [19, 20] ], + [ [21, 22], [23, 24] ] + ], + [ + [ [25, 26], [27, 28] ], + [ [29, 30], [31, 32] ] + ] + ] + + declare i: int = 0 + declare j: int = 1 + declare k: int = 0 + declare l: int = 0 + + declare x: int = n[i][j][k][l] + os.print(x) + end body + end function +end module diff --git a/playground/Demo/Demo19/OS.snow b/playground/Demo/Demo19/OS.snow new file mode 100644 index 0000000..6026d43 --- /dev/null +++ b/playground/Demo/Demo19/OS.snow @@ -0,0 +1,11 @@ +module: os + import: os + function: print + parameter: + declare i1: int + return_type: void + body: + syscall("PRINT",i1) + end body + end function +end module \ No newline at end of file diff --git a/playground/Demo/Demo20/Main.snow b/playground/Demo/Demo20/Main.snow new file mode 100644 index 0000000..c3ba2e4 --- /dev/null +++ b/playground/Demo/Demo20/Main.snow @@ -0,0 +1,12 @@ +module: Main + import:os + function: main + return_type: void + body: + declare arr: int[] = [1, 2, 3] + arr[0] = 5 + + os.print(arr[0]) + end body + end function +end module diff --git a/playground/Demo/Demo20/OS.snow b/playground/Demo/Demo20/OS.snow new file mode 100644 index 0000000..6026d43 --- /dev/null +++ b/playground/Demo/Demo20/OS.snow @@ -0,0 +1,11 @@ +module: os + import: os + function: print + parameter: + declare i1: int + return_type: void + body: + syscall("PRINT",i1) + end body + end function +end module \ No newline at end of file diff --git a/playground/Demo/Demo21/Main.snow b/playground/Demo/Demo21/Main.snow new file mode 100644 index 0000000..f3b11ce --- /dev/null +++ b/playground/Demo/Demo21/Main.snow @@ -0,0 +1,35 @@ +module: Main + import: os + globals: + declare sum: int = 123 + function: main + parameter: + return_type: int + body: + declare arr: int[][][][][][][][][][][][][][][][][][][][][][] = [[[[[[[[[[[[[[[[[[[[[[1], [2], [3]]]]]]]]]]]]]]]]]]]]]] + loop: + init: + declare i: int = 0 + cond: + i < 3 + step: + i = i + 1 + body: + arr[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][i][0] = arr[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][i][0] + 1 + end body + end loop + loop: + init: + declare i: int = 0 + cond: + i < 3 + step: + i = i + 1 + body: + os.print(arr[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][i][0]) + end body + end loop + return 0 + end body + end function +end module diff --git a/playground/Demo/Demo21/OS.snow b/playground/Demo/Demo21/OS.snow new file mode 100644 index 0000000..6026d43 --- /dev/null +++ b/playground/Demo/Demo21/OS.snow @@ -0,0 +1,11 @@ +module: os + import: os + function: print + parameter: + declare i1: int + return_type: void + body: + syscall("PRINT",i1) + end body + end function +end module \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/compiler/backend/generator/CallGenerator.java b/src/main/java/org/jcnc/snow/compiler/backend/generator/CallGenerator.java index 0f7c9c8..04dfaa1 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/generator/CallGenerator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/generator/CallGenerator.java @@ -10,45 +10,46 @@ import org.jcnc.snow.compiler.ir.value.IRConstant; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import org.jcnc.snow.vm.engine.VMOpCode; -import java.util.*; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * CallGenerator - 将 IR {@code CallInstruction} 生成 VM 指令 - * + * {@code CallGenerator} 负责将 IR 层的 {@link CallInstruction} 生成对应的 VM 层函数调用指令。 *

- * 本类负责将中间表示(IR)中的函数调用指令 {@link CallInstruction} 转换为虚拟机(VM)指令。 - * 支持普通函数调用和特殊的 syscall 指令转换。 + * 支持 syscall、普通函数调用、一维/多维数组下标访问、以及字符串常量池绑定等功能。 *

* - *

- * 能力说明: *

    - *
  • 支持识别 {@code IRConstant} 直接字面量或已绑定到虚拟寄存器的字符串常量,直接降级为 {@code SYSCALL <SUBCMD>} 指令。
  • - *
  • 对普通函数,完成参数加载、调用、返回值保存等指令生成。
  • + *
  • syscall: 支持字符串常量与寄存器到子命令的绑定与解析
  • + *
  • 数组下标访问: 支持所有主流基础类型 “__index_b/s/i/l/f/d/r”
  • + *
  • 普通函数: 支持自动推断返回值类型与槽位类型
  • *
- *

*/ public class CallGenerator implements InstructionGenerator { /** - * <虚拟寄存器 id, 对应的字符串常量> - *

用于记录虚拟寄存器与其绑定字符串常量的映射。由 {@link LoadConstGenerator} 在编译期间填充。

+ * 字符串常量池:用于绑定虚拟寄存器 id 到字符串值(供 syscall 子命令使用)。 + *
+ * 使用 ConcurrentHashMap 保证并发安全,所有 registerStringConst 的注册和读取都是线程安全的。 */ - private static final Map STRING_CONST_POOL = new HashMap<>(); + private static final Map STRING_CONST_POOL = new ConcurrentHashMap<>(); /** - * 注册字符串常量到虚拟寄存器 - *

供 {@link LoadConstGenerator} 在加载字符串常量时调用。

+ * 注册一个字符串常量,绑定到虚拟寄存器 id。 * * @param regId 虚拟寄存器 id - * @param value 字符串常量值 + * @param value 字符串常量 */ public static void registerStringConst(int regId, String value) { STRING_CONST_POOL.put(regId, value); } /** - * 返回本生成器支持的 IR 指令类型(CallInstruction) + * 返回本生成器支持的 IR 指令类型。 + * + * @return {@link CallInstruction} 类型的 Class 对象 */ @Override public Class supportedClass() { @@ -56,11 +57,11 @@ public class CallGenerator implements InstructionGenerator { } /** - * 生成 VM 指令的主逻辑 + * 生成指定的调用指令,包括 syscall、数组下标、普通函数。 * - * @param ins 当前 IR 指令(函数调用) - * @param out 指令输出构建器 - * @param slotMap IR 虚拟寄存器与物理槽位映射 + * @param ins IR 层的调用指令 + * @param out VM 程序构建器 + * @param slotMap 寄存器到槽位的映射表 * @param currentFn 当前函数名 */ @Override @@ -68,79 +69,357 @@ public class CallGenerator implements InstructionGenerator { VMProgramBuilder out, Map slotMap, String currentFn) { + String fn = ins.getFunctionName(); - /* ========== 特殊处理 syscall 调用 ========== */ - if ("syscall".equals(ins.getFunctionName()) || - ins.getFunctionName().endsWith(".syscall")) { - - List args = ins.getArguments(); - if (args.isEmpty()) { - throw new IllegalStateException("syscall 需要子命令参数"); - } - - // ---------- 0. 解析 syscall 子命令 ---------- - // 子命令支持 IRConstant(直接字面量)或虚拟寄存器(需已绑定字符串) - String subcmd; - IRValue first = args.getFirst(); - - if (first instanceof IRConstant(Object value)) { // 直接字面量 - if (!(value instanceof String s)) - throw new IllegalStateException("syscall 第一个参数必须是字符串常量"); - subcmd = s.toUpperCase(Locale.ROOT); - - } else if (first instanceof IRVirtualRegister vr) { // 虚拟寄存器 - // 从常量池中查找是否已绑定字符串 - subcmd = Optional.ofNullable(STRING_CONST_POOL.get(vr.id())) - .orElseThrow(() -> - new IllegalStateException("未找到 syscall 字符串常量绑定: " + vr)); - subcmd = subcmd.toUpperCase(Locale.ROOT); - - } else { - throw new IllegalStateException("syscall 第一个参数必须是字符串常量"); - } - - // ---------- 1. 压栈其余 syscall 参数(index 1 开始) ---------- - for (int i = 1; i < args.size(); i++) { - IRVirtualRegister vr = (IRVirtualRegister) args.get(i); - int slotId = slotMap.get(vr); - char t = out.getSlotType(slotId); - if (t == '\0') t = 'I'; // 默认整型 - out.emit(OpHelper.opcode(t + "_LOAD") + " " + slotId); - } - - // ---------- 2. 生成 SYSCALL 指令 ---------- - out.emit(VMOpCode.SYSCALL + " " + subcmd); - return; // syscall 无返回值,直接返回 - } - - /* ========== 普通函数调用 ========== */ - - // ---------- 1. 推断返回值类型(非 void 返回时用) ---------- - char retType = 'I'; // 默认为整型 - if (!ins.getArguments().isEmpty()) { - int firstSlot = slotMap.get((IRVirtualRegister) ins.getArguments().getFirst()); - retType = out.getSlotType(firstSlot); - if (retType == '\0') retType = 'I'; - } - - // ---------- 2. 加载全部实参 ---------- - for (var arg : ins.getArguments()) { - int slotId = slotMap.get((IRVirtualRegister) arg); - char t = out.getSlotType(slotId); - if (t == '\0') t = 'I'; // 默认整型 - out.emit(OpHelper.opcode(t + "_LOAD") + " " + slotId); - } - - // ---------- 3. 发出 CALL 指令 ---------- - out.emitCall(ins.getFunctionName(), ins.getArguments().size()); - - // ---------- 3.5 如果为 void 返回直接结束 ---------- - if ("void".equals(GlobalFunctionTable.getReturnType(ins.getFunctionName()))) { + // 特殊处理 syscall 调用 + if ("syscall".equals(fn) || fn.endsWith(".syscall")) { + generateSyscall(ins, out, slotMap, fn); return; } - // ---------- 4. 保存返回值 ---------- - int destSlot = slotMap.get(ins.getDest()); + // 各种一维数组类型(byte/short/int/long/float/double/boolean)读取 + switch (fn) { + case "__index_b" -> { + generateIndexInstruction(ins, out, slotMap, 'B'); + return; + } + case "__index_s" -> { + generateIndexInstruction(ins, out, slotMap, 'S'); + return; + } + case "__index_i" -> { + generateIndexInstruction(ins, out, slotMap, 'I'); + return; + } + case "__index_l" -> { + generateIndexInstruction(ins, out, slotMap, 'L'); + return; + } + case "__index_f" -> { + generateIndexInstruction(ins, out, slotMap, 'F'); + return; + } + case "__index_d" -> { + generateIndexInstruction(ins, out, slotMap, 'D'); + return; + } + case "__index_r" -> { + generateIndexInstruction(ins, out, slotMap, 'R'); + return; + } + + case "__setindex_b" -> { + generateSetIndexInstruction(ins, out, slotMap, 'B'); + return; + } + case "__setindex_s" -> { + generateSetIndexInstruction(ins, out, slotMap, 'S'); + return; + } + case "__setindex_i" -> { + generateSetIndexInstruction(ins, out, slotMap, 'I'); + return; + } + case "__setindex_l" -> { + generateSetIndexInstruction(ins, out, slotMap, 'L'); + return; + } + case "__setindex_f" -> { + generateSetIndexInstruction(ins, out, slotMap, 'F'); + return; + } + case "__setindex_d" -> { + generateSetIndexInstruction(ins, out, slotMap, 'D'); + return; + } + case "__setindex_r" -> { + generateSetIndexInstruction(ins, out, slotMap, 'R'); + return; + } + } + + // 普通函数调用 + generateNormalCall(ins, out, slotMap, fn); + } + + // ========== 私有辅助方法 ========== + + /** + * 生成数组元素赋值指令(arr[idx] = value),无返回值。 + *

+ * 调用栈压栈顺序为:arr (引用类型, R),idx (整型, I),value (元素类型, T)。 + * 执行 SYSCALL ARR_SET 指令以完成数组赋值操作。 + *

+ * + * @param ins 当前的调用指令(包含参数信息) + * @param out VM 指令生成器(用于输出 VM 指令) + * @param slotMap 虚拟寄存器与槽位的映射表 + * @param valType 待写入的 value 的类型标识('B': byte, 'S': short, 'I': int, 'L': long, 'F': float, 'D': double, 其余为引用类型'R') + * @throws IllegalStateException 如果参数数量不为3,则抛出异常 + */ + private void generateSetIndexInstruction(CallInstruction ins, + VMProgramBuilder out, + Map slotMap, + char valType) { + List args = ins.getArguments(); + if (args.size() != 3) { + // 参数数量错误,抛出异常并输出实际参数列表 + throw new IllegalStateException( + "[CallGenerator] __setindex_* 需要三个参数(arr, idx, value),实际: " + args); + } + + // 第一个参数为数组对象,压入引用类型寄存器('R'),如 arr + loadArgument(out, slotMap, args.get(0), 'R', ins.getFunctionName()); + + // 第二个参数为索引值,压入整型寄存器('I'),如 idx + loadArgument(out, slotMap, args.get(1), 'I', ins.getFunctionName()); + + // 第三个参数为待赋值元素,根据元素类型压入相应类型寄存器 + // 支持类型:'B'(byte), 'S'(short), 'I'(int), 'L'(long), 'F'(float), 'D'(double) + // 其他情况(如引用类型),按'R'处理 + switch (valType) { + case 'B' -> loadArgument(out, slotMap, args.get(2), 'B', ins.getFunctionName()); + case 'S' -> loadArgument(out, slotMap, args.get(2), 'S', ins.getFunctionName()); + case 'I' -> loadArgument(out, slotMap, args.get(2), 'I', ins.getFunctionName()); + case 'L' -> loadArgument(out, slotMap, args.get(2), 'L', ins.getFunctionName()); + case 'F' -> loadArgument(out, slotMap, args.get(2), 'F', ins.getFunctionName()); + case 'D' -> loadArgument(out, slotMap, args.get(2), 'D', ins.getFunctionName()); + default -> loadArgument(out, slotMap, args.get(2), 'R', ins.getFunctionName()); + } + + // 输出 VM 指令:SYSCALL ARR_SET,完成数组元素写入操作 + out.emit(VMOpCode.SYSCALL + " " + "ARR_SET"); + } + + + /** + * 解析 syscall 子命令(第一个参数),支持字符串常量与已绑定字符串的虚拟寄存器。 + * + * @param arg 子命令参数(应为字符串常量或寄存器) + * @param fn 当前函数名(仅用于报错信息) + * @return 子命令(大写字符串) + * @throws IllegalStateException 如果参数不是字符串常量或已绑定寄存器 + */ + private String resolveSyscallSubcmd(IRValue arg, String fn) { + switch (arg) { + case IRConstant(String s) -> { + return s.toUpperCase(Locale.ROOT); + } + case IRConstant(Object value) -> throw new IllegalStateException( + "[CallGenerator] syscall 第一个参数必须是字符串常量 (function: %s, value: %s)" + .formatted(fn, value) + ); + case IRVirtualRegister(int id) -> { + String s = STRING_CONST_POOL.get(id); + if (s == null) { + throw new IllegalStateException( + "[CallGenerator] 未找到 syscall 字符串常量绑定 (function: %s, regId: %d)" + .formatted(fn, id) + ); + } + return s.toUpperCase(Locale.ROOT); + } + case null, default -> throw new IllegalStateException( + "[CallGenerator] syscall 第一个参数必须是字符串常量或已绑定字符串的寄存器 (function: %s, arg: %s)" + .formatted(fn, arg) + ); + } + } + + /** + * 加载一个参数到栈,支持指定默认类型。如果未设置类型则采用默认类型。 + * + * @param out VM 程序构建器 + * @param slotMap 寄存器到槽位的映射 + * @param arg 参数值(应为虚拟寄存器) + * @param defaultType 默认类型 + * @param fn 当前函数名(用于错误提示) + * @throws IllegalStateException 如果参数不是虚拟寄存器,或槽位未找到 + */ + private void loadArgument(VMProgramBuilder out, Map slotMap, IRValue arg, char defaultType, String fn) { + if (!(arg instanceof IRVirtualRegister vr)) { + throw new IllegalStateException( + "[CallGenerator] 参数必须为虚拟寄存器 (function: %s, arg: %s)".formatted(fn, arg)); + } + Integer slot = slotMap.get(vr); + if (slot == null) { + throw new IllegalStateException( + "[CallGenerator] 未找到虚拟寄存器的槽位映射 (function: %s, reg: %s)".formatted(fn, vr)); + } + char t = out.getSlotType(slot); + if (t == '\0') t = defaultType; + out.emit(OpHelper.opcode(t + "_LOAD") + " " + slot); + } + + /** + * 生成一维/多维数组的下标访问指令(支持类型分派)。 + *

+ * 本方法用于将 IR 层的数组下标访问表达式(如 arr[idx]),生成对应的 VM 指令序列。 + * 支持 byte/short/int/long/float/double/reference 等所有基础类型的数组元素访问。 + *

+ * + *
    + *
  • 1. 依次加载数组参数(arr)和下标参数(idx)到操作数栈
  • + *
  • 2. 发出 ARR_GET 系统调用指令(SYSCALL ARR_GET),通过 VM 访问对应元素
  • + *
  • 3. 根据元素声明类型,将结果写入目标槽位,支持类型分派(B/S/I/L/F/D/R)
  • + *
  • 4. 若参数或返回寄存器缺失,则抛出异常提示
  • + *
+ * + * @param ins 下标访问对应的 IR 调用指令,函数名通常为 __index_x + * @param out VM 程序构建器,用于发出 VM 指令 + * @param slotMap IR 虚拟寄存器到 VM 槽位的映射表 + * @param retType 元素类型标识('B' byte, 'S' short, 'I' int, 'L' long, 'F' float, 'D' double, 'R' ref/obj) + * @throws IllegalStateException 参数个数不符、缺少目标寄存器、未找到槽位等情况 + */ + private void generateIndexInstruction(CallInstruction ins, VMProgramBuilder out, Map slotMap, char retType) { + String fn = ins.getFunctionName(); + List args = ins.getArguments(); + if (args.size() != 2) { + throw new IllegalStateException( + "[CallGenerator] %s 需要两个参数(arr, idx),实际: %s".formatted(fn, args)); + } + // 加载数组参数(寄存器类型按 R/ref 处理,默认对象槽位) + loadArgument(out, slotMap, args.get(0), 'R', fn); + // 加载下标参数(寄存器类型按 I/int 处理) + loadArgument(out, slotMap, args.get(1), 'I', fn); + + // 发出 ARR_GET 系统调用(元素访问由 VM 完成类型分派) + out.emit(VMOpCode.SYSCALL + " " + "ARR_GET"); + + // 保存返回值到目标寄存器 + IRVirtualRegister dest = ins.getDest(); + if (dest == null) { + throw new IllegalStateException( + "[CallGenerator] %s 需要有目标寄存器用于保存返回值".formatted(fn)); + } + Integer destSlot = slotMap.get(dest); + if (destSlot == null) { + throw new IllegalStateException( + "[CallGenerator] %s 未找到目标寄存器的槽位映射 (dest: %s)".formatted(fn, dest)); + } + + // 按元素类型分派写入 VM 槽位 + switch (retType) { + case 'B' -> { + out.emit(OpHelper.opcode("B_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'B'); + } + case 'S' -> { + out.emit(OpHelper.opcode("S_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'S'); + } + case 'I' -> { + out.emit(OpHelper.opcode("I_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'I'); + } + case 'L' -> { + out.emit(OpHelper.opcode("L_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'L'); + } + case 'F' -> { + out.emit(OpHelper.opcode("F_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'F'); + } + case 'D' -> { + out.emit(OpHelper.opcode("D_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'D'); + } + default -> { + out.emit(OpHelper.opcode("R_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'R'); + } + } + } + + /** + * 生成 syscall 指令分支逻辑。 + *
    + *
  1. 解析 syscall 子命令
  2. + *
  3. 压栈剩余参数
  4. + *
  5. 发出 SYSCALL 指令
  6. + *
  7. 若有返回值则保存至目标槽位
  8. + *
+ * + * @param ins 调用指令 + * @param out VM 程序构建器 + * @param slotMap 寄存器到槽位映射 + * @param fn 当前函数名 + */ + private void generateSyscall(CallInstruction ins, VMProgramBuilder out, Map slotMap, String fn) { + List args = ins.getArguments(); + if (args.isEmpty()) { + throw new IllegalStateException( + "[CallGenerator] syscall 需要子命令参数 (function: %s)".formatted(fn)); + } + + // 0. 解析 syscall 子命令(第一个参数) + String subcmd = resolveSyscallSubcmd(args.getFirst(), fn); + + // 1. 压栈其余 syscall 参数(从 index 1 开始) + for (int i = 1; i < args.size(); i++) { + loadArgument(out, slotMap, args.get(i), 'R', fn); + } + + // 2. 生成 SYSCALL 指令 + out.emit(VMOpCode.SYSCALL + " " + subcmd); + + // 3. 有返回值则保存到目标槽位 + IRVirtualRegister dest = ins.getDest(); + if (dest != null) { + Integer destSlot = slotMap.get(dest); + if (destSlot == null) { + throw new IllegalStateException( + "[CallGenerator] syscall 未找到目标寄存器的槽位映射 (function: %s, dest: %s)" + .formatted(fn, dest)); + } + out.emit(OpHelper.opcode("I_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'I'); + } + } + + /** + * 生成普通函数调用指令: + *
    + *
  1. 推断返回值类型
  2. + *
  3. 压栈所有参数
  4. + *
  5. 生成 CALL 指令
  6. + *
  7. 保存返回值(若非 void)
  8. + *
+ * + * @param ins 调用指令 + * @param out VM 程序构建器 + * @param slotMap 寄存器到槽位映射 + * @param fn 当前函数名 + */ + private void generateNormalCall(CallInstruction ins, VMProgramBuilder out, Map slotMap, String fn) { + // 1. 推断返回值类型(首字母大写,缺省为 'I') + String retTypeName = GlobalFunctionTable.getReturnType(fn); + char retType = (retTypeName != null && !retTypeName.isEmpty()) ? Character.toUpperCase(retTypeName.charAt(0)) : 'I'; + + // 2. 压栈所有参数 + for (IRValue arg : ins.getArguments()) { + loadArgument(out, slotMap, arg, 'R', fn); + } + + // 3. 发出 CALL 指令 + out.emitCall(fn, ins.getArguments().size()); + + // 3.5 void 返回直接结束 + if ("void".equals(retTypeName)) { + return; + } + + // 4. 保存返回值到目标寄存器 + IRVirtualRegister dest = ins.getDest(); + if (dest == null) { + throw new IllegalStateException( + "[CallGenerator] 普通函数调用未找到目标寄存器 (function: %s)".formatted(fn)); + } + Integer destSlot = slotMap.get(dest); + if (destSlot == null) { + throw new IllegalStateException( + "[CallGenerator] 普通函数调用未找到目标寄存器的槽位映射 (function: %s, dest: %s)".formatted(fn, dest)); + } out.emit(OpHelper.opcode(retType + "_STORE") + " " + destSlot); out.setSlotType(destSlot, retType); } diff --git a/src/main/java/org/jcnc/snow/compiler/backend/generator/LoadConstGenerator.java b/src/main/java/org/jcnc/snow/compiler/backend/generator/LoadConstGenerator.java index a24f6d9..84d2e7e 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/generator/LoadConstGenerator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/generator/LoadConstGenerator.java @@ -7,21 +7,93 @@ import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction; import org.jcnc.snow.compiler.ir.value.IRConstant; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** - * LoadConstGenerator - 将 IR {@code LoadConstInstruction} 生成 VM 指令 + * LoadConstGenerator - Generates VM instructions from IR {@code LoadConstInstruction} * *

- * 本类负责将 IR 层的常量加载指令 {@link LoadConstInstruction} 转换为对应的虚拟机指令。 - * 额外支持:如果常量类型为 {@code String},会同步登记到 - * {@link CallGenerator} 的字符串常量池,方便 syscall 降级场景使用。 + * This class is responsible for converting IR-level {@link LoadConstInstruction} into corresponding VM instructions. + * If the constant is a {@code String}, it will also be registered in the + * {@link CallGenerator} string constant pool to support syscall downgrade scenarios. + *

+ * + *

+ * Fix: When the constant is an array (List), type information is preserved in R_PUSH payload: + *

    + *
  • Float is output with f suffix (e.g., 0.1f);
  • + *
  • Long is output with L suffix (e.g., 123L);
  • + *
  • Double/Integer are output in their default format (e.g., 1.0, 42);
  • + *
  • Supports recursive serialization of nested arrays.
  • + *
+ * This prevents float values from being misinterpreted as double on the VM side, + * and avoids Double→Float cast exceptions in later F_STORE operations. *

*/ public class LoadConstGenerator implements InstructionGenerator { /** - * 指定本生成器支持的 IR 指令类型(LoadConstInstruction) + * Formats a constant value as a string for use as a VM payload. + * Lists are recursively serialized, and Float/Long types include suffixes to preserve type information. + * + * @param v The constant value to format. + * @return The formatted string for use in VM code. + */ + private static String formatConst(Object v) { + return formatConst(v, false); + } + + /** + * Internal helper for recursively formatting constant values (including nested arrays) + * with appropriate type suffixes for array payloads. + * + * @param v The constant value to format. + * @param insideArray True if currently formatting inside an array context; affects whether type suffixes are applied. + * @return The formatted string for use in VM code. + */ + private static String formatConst(Object v, boolean insideArray) { + if (v instanceof List list) { + // Recursively process each element in the list + return "[" + list.stream() + .map(x -> formatConst(x, true)) + .collect(Collectors.joining(", ")) + "]"; + } + if (v instanceof String s) { + return s; + } + if (v instanceof Float f) { + // Always keep .0 for integer values + float fv = f; + String s = (fv == (long) fv) ? String.format("%.1f", fv) : f.toString(); + return insideArray ? (s + "f") : s; + } + if (v instanceof Long l) { + return insideArray ? (l + "L") : l.toString(); + } + if (v instanceof Double d) { + double dv = d; + // Always keep .0 for integer values + return (dv == (long) dv) ? String.format("%.1f", dv) : Double.toString(dv); + } + if (v instanceof Short s) { + return insideArray ? (s + "s") : Short.toString(s); + } + if (v instanceof Byte b) { + return insideArray ? (b + "b") : Byte.toString(b); + } + if (v instanceof Boolean b) { + return b ? "1" : "0"; + } + return String.valueOf(v); + } + + + /** + * Specifies the type of IR instruction supported by this generator. + * + * @return The class object representing {@link LoadConstInstruction}. */ @Override public Class supportedClass() { @@ -29,12 +101,16 @@ public class LoadConstGenerator implements InstructionGenerator + * This includes formatting the constant value, emitting the corresponding PUSH and STORE instructions, + * marking the local slot type for later operations, and registering string constants if necessary. + *

* - * @param ins 当前常量加载指令 - * @param out 指令输出构建器 - * @param slotMap 虚拟寄存器与物理槽位映射 - * @param currentFn 当前函数名 + * @param ins The {@link LoadConstInstruction} to generate code for. + * @param out The {@link VMProgramBuilder} used to collect the generated instructions. + * @param slotMap A mapping from {@link IRVirtualRegister} to physical slot indices. + * @param currentFn The name of the current function. */ @Override public void generate(LoadConstInstruction ins, @@ -42,34 +118,35 @@ public class LoadConstGenerator implements InstructionGenerator slotMap, String currentFn) { - /* 1. 获取常量值 */ + // 1. Get the constant value IRConstant constant = (IRConstant) ins.operands().getFirst(); Object value = constant.value(); - /* 2. 生成 PUSH 指令,将常量值入栈 */ - out.emit(OpHelper.pushOpcodeFor(value) + " " + value); + // 2. Generate PUSH instruction (array constants use type-aware formatting) + String payload = formatConst(value); + out.emit(OpHelper.pushOpcodeFor(value) + " " + payload); - /* 3. STORE 到目标槽位 */ + // 3. STORE the result to the destination slot int slot = slotMap.get(ins.dest()); out.emit(OpHelper.storeOpcodeFor(value) + " " + slot); - /* 4. 标记槽位数据类型(用于后续类型推断和 LOAD/STORE 指令选择) */ + // 4. Mark the slot's data type for later inference and instruction selection char prefix = switch (value) { - case Integer _ -> 'I'; // 整型 - case Long _ -> 'L'; // 长整型 - case Short _ -> 'S'; // 短整型 - case Byte _ -> 'B'; // 字节型 - case Double _ -> 'D'; // 双精度 - case Float _ -> 'F'; // 单精度 - case Boolean _ -> 'I'; // 布尔类型用 I 处理 - case String _ -> 'R'; // 字符串常量 - case null, default -> // 其它类型异常 - throw new IllegalStateException("未知的常量类型: " - + (value != null ? value.getClass() : null)); + case Integer _ -> 'I'; // Integer + case Long _ -> 'L'; // Long + case Short _ -> 'S'; // Short + case Byte _ -> 'B'; // Byte + case Double _ -> 'D'; // Double + case Float _ -> 'F'; // Float + case Boolean _ -> 'I'; // Boolean handled as Integer (typically lowered to 1/0) + case String _ -> 'R'; // String constant + case java.util.List _ -> 'R'; // Reference type (arrays, etc.) + case null, default -> throw new IllegalStateException("Unknown constant type: " + + (value != null ? value.getClass() : null)); }; out.setSlotType(slot, prefix); - /* 5. 如果是字符串常量,则登记到 CallGenerator 的常量池,便于 syscall 字符串降级使用 */ + // 5. If the constant is a string, register it for the CallGenerator string pool if (value instanceof String s) { CallGenerator.registerStringConst(ins.dest().id(), s); } diff --git a/src/main/java/org/jcnc/snow/compiler/backend/utils/OpHelper.java b/src/main/java/org/jcnc/snow/compiler/backend/utils/OpHelper.java index 977c85e..2b5f8c8 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/utils/OpHelper.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/utils/OpHelper.java @@ -234,6 +234,7 @@ public final class OpHelper { if (v instanceof Double) return "D"; if (v instanceof Float) return "F"; if (v instanceof String) return "R"; //引用类型 + if (v instanceof java.util.List) return "R"; // 引用类型(数组等) throw new IllegalStateException("Unknown const type: " + v.getClass()); } 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 eab2d75..47bb4a4 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 @@ -1,6 +1,7 @@ package org.jcnc.snow.compiler.ir.builder; import org.jcnc.snow.compiler.ir.core.IROpCode; +import org.jcnc.snow.compiler.ir.core.IRValue; import org.jcnc.snow.compiler.ir.instruction.CallInstruction; import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction; import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction; @@ -11,40 +12,26 @@ import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import org.jcnc.snow.compiler.parser.ast.*; import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; -import java.util.*; +import java.util.ArrayList; +import java.util.List; /** - * ExpressionBuilder - 表达式 → IR 构建器 - * + * {@code ExpressionBuilder} 表达式 → IR 构建器。 *

* 负责将 AST 表达式节点递归转换为 IR 虚拟寄存器操作,并生成对应的 IR 指令序列。 - * 支持字面量、标识符、二元表达式、一元表达式、函数调用等多种类型表达式。 - *

- * - *

- * 主要功能: + * 支持字面量、标识符、二元表达式、一元表达式、函数调用、数组下标等多种类型表达式。 *

    - *
  • 将表达式节点映射为虚拟寄存器
  • - *
  • 为每种表达式类型生成对应 IR 指令
  • - *
  • 支持表达式嵌套的递归构建
  • - *
  • 支持写入指定目标寄存器,避免冗余的 move 指令
  • + *
  • 将表达式节点映射为虚拟寄存器
  • + *
  • 为每种表达式类型生成对应 IR 指令
  • + *
  • 支持表达式嵌套的递归构建
  • + *
  • 支持写入指定目标寄存器,避免冗余的 move 指令
  • + *
  • 支持 IndexExpressionNode 的编译期折叠(arr[2]),并在运行时降级为 __index_i/__index_r
  • *
- *

*/ public record ExpressionBuilder(IRContext ctx) { - /* ───────────────── 顶层入口 ───────────────── */ - /** - * 构建任意 AST 表达式节点,自动为其分配一个新的虚拟寄存器,并返回该寄存器。 - * - *

- * 这是表达式 IR 生成的核心入口。它会根据不同的表达式类型进行分派,递归构建 IR 指令。 - *

- * - * @param expr 任意 AST 表达式节点 - * @return 存储该表达式结果的虚拟寄存器 - * @throws IllegalStateException 遇到不支持的表达式类型或未定义标识符 + * 构建表达式,返回结果寄存器。 */ public IRVirtualRegister build(ExpressionNode expr) { return switch (expr) { @@ -53,22 +40,22 @@ public record ExpressionBuilder(IRContext ctx) { // 字符串字面量,例如 "abc" case StringLiteralNode s -> buildStringLiteral(s.value()); // 布尔字面量,例如 true / false - case BoolLiteralNode b -> buildBoolLiteral(b.getValue()); + case BoolLiteralNode b -> buildBoolLiteral(b.getValue()); // 标识符(变量名),如 a、b case IdentifierNode id -> { // 查找当前作用域中的变量寄存器 IRVirtualRegister reg = ctx.getScope().lookup(id.name()); - if (reg == null) - throw new IllegalStateException("未定义标识符: " + id.name()); + if (reg == null) throw new IllegalStateException("未定义标识符: " + id.name()); yield reg; } // 二元表达式(如 a+b, x==y) case BinaryExpressionNode bin -> buildBinary(bin); // 函数/方法调用表达式 - case CallExpressionNode call -> buildCall(call); + case CallExpressionNode call -> buildCall(call); // 一元表达式(如 -a, !a) - case UnaryExpressionNode un -> buildUnary(un); - + case UnaryExpressionNode un -> buildUnary(un); + case IndexExpressionNode idx -> buildIndex(idx); + case ArrayLiteralNode arr -> buildArrayLiteral(arr); // 默认分支:遇到未知表达式类型则直接抛异常 default -> throw new IllegalStateException( "不支持的表达式类型: " + expr.getClass().getSimpleName()); @@ -78,33 +65,40 @@ public record ExpressionBuilder(IRContext ctx) { /* ───────────────── 写入指定寄存器 ───────────────── */ /** - * 生成表达式,并将其结果直接写入目标寄存器,避免冗余的 move 操作。 - * + * 将表达式节点 {@link ExpressionNode} 的结果写入指定的虚拟寄存器 {@code dest}。 *

- * 某些简单表达式(如字面量、变量名)可以直接写入目标寄存器;复杂表达式则会先 build 到新寄存器,再 move 到目标寄存器。 + * 按表达式类型分派处理,包括: + *

    + *
  • 字面量(数字、字符串、布尔、数组):生成 loadConst 指令直接写入目标寄存器
  • + *
  • 变量标识符:查表获取源寄存器,并 move 到目标寄存器
  • + *
  • 二元表达式、下标、调用表达式:递归生成子表达式结果,并写入目标寄存器
  • + *
  • 其它类型:统一先 build 到临时寄存器,再 move 到目标寄存器
  • + *
*

* - * @param node 要生成的表达式节点 - * @param dest 目标虚拟寄存器(用于存储结果) + * @param node 要求值的表达式节点 + * @param dest 结果目标虚拟寄存器 + * @throws IllegalStateException 若标识符未定义(如变量未声明时引用) */ public void buildInto(ExpressionNode node, IRVirtualRegister dest) { switch (node) { - // 数字字面量:生成 loadConst 指令写入目标寄存器 - case NumberLiteralNode n -> - InstructionFactory.loadConstInto( - ctx, dest, ExpressionUtils.buildNumberConstant(ctx, n.value())); + // 数字字面量:生成 loadConst 指令,将数值常量写入目标寄存器 + case NumberLiteralNode n -> InstructionFactory.loadConstInto( + ctx, dest, ExpressionUtils.buildNumberConstant(ctx, n.value())); - // 字符串字面量:生成 loadConst 指令写入目标寄存器 - case StringLiteralNode s -> - InstructionFactory.loadConstInto( - ctx, dest, new IRConstant(s.value())); + // 字符串字面量:生成 loadConst 指令,将字符串常量写入目标寄存器 + case StringLiteralNode s -> InstructionFactory.loadConstInto( + ctx, dest, new IRConstant(s.value())); - // 布尔字面量:转换为 int 1/0,生成 loadConst - case BoolLiteralNode b -> - InstructionFactory.loadConstInto( - ctx, dest, new IRConstant(b.getValue() ? 1 : 0)); + // 布尔字面量:转换为 int 1/0,生成 loadConst 指令写入目标寄存器 + case BoolLiteralNode b -> InstructionFactory.loadConstInto( + ctx, dest, new IRConstant(b.getValue() ? 1 : 0)); - // 标识符:查表获取原寄存器,然后 move 到目标寄存器 + // 数组字面量:生成数组常量并写入目标寄存器 + case ArrayLiteralNode arr -> InstructionFactory.loadConstInto( + ctx, dest, buildArrayConstant(arr)); + + // 变量标识符:查表获得源寄存器,move 到目标寄存器 case IdentifierNode id -> { IRVirtualRegister src = ctx.getScope().lookup(id.name()); if (src == null) @@ -112,10 +106,22 @@ public record ExpressionBuilder(IRContext ctx) { InstructionFactory.move(ctx, src, dest); } - // 二元表达式:递归生成并写入目标寄存器 + // 二元表达式:递归生成左右子表达式,并将结果写入目标寄存器 case BinaryExpressionNode bin -> buildBinaryInto(bin, dest); - // 其它复杂情况:先 build 到新寄存器,再 move 到目标寄存器 + // 下标表达式:递归生成索引结果,move 到目标寄存器 + case IndexExpressionNode idx -> { + IRVirtualRegister tmp = buildIndex(idx); + InstructionFactory.move(ctx, tmp, dest); + } + + // 调用表达式:递归生成调用结果,move 到目标寄存器 + case CallExpressionNode call -> { + IRVirtualRegister tmp = buildCall(call); + InstructionFactory.move(ctx, tmp, dest); + } + + // 其它类型:统一先 build 到临时寄存器,再 move 到目标寄存器 default -> { IRVirtualRegister tmp = build(node); InstructionFactory.move(ctx, tmp, dest); @@ -123,7 +129,201 @@ public record ExpressionBuilder(IRContext ctx) { } } - /* ───────────────── 具体表达式类型 ───────────────── */ + + /** + * 构建下标访问表达式(IndexExpressionNode)。 + *
    + *
  • 若数组和下标均为编译期常量,则直接进行常量折叠(直接返回目标常量寄存器);
  • + *
  • 否则: + *
      + *
    • 若数组表达式本身是下一个下标的中间值(即多维数组链式下标),则先用 __index_r 获取“引用”;
    • + *
    • 最后一级用 __index_b/s/i/l/f/d/r,按声明类型智能分派。
    • + *
    + *
  • + *
+ * + * @param node 下标访问表达式节点 + * @return 存放结果的虚拟寄存器 + */ + private IRVirtualRegister buildIndex(IndexExpressionNode node) { + // 1. 常量折叠:如果 array 和 index 都是编译期常量,直接取值 + Object arrConst = tryFoldConst(node.array()); + Object idxConst = tryFoldConst(node.index()); + if (arrConst instanceof java.util.List list && idxConst instanceof Number num) { + int i = num.intValue(); + // 越界检查 + if (i < 0 || i >= list.size()) + throw new IllegalStateException("数组下标越界: " + i + " (长度 " + list.size() + ")"); + Object elem = list.get(i); + IRVirtualRegister r = ctx.newRegister(); + // 加载常量元素到新寄存器 + InstructionFactory.loadConstInto(ctx, r, new IRConstant(elem)); + return r; + } + + // 2. 处理多级下标(如 arr[1][2]):中间层用 __index_r 返回“引用” + IRVirtualRegister arrReg = (node.array() instanceof IndexExpressionNode inner) + ? buildIndexRef(inner) // 递归获取引用 + : build(node.array()); // 否则直接生成 array 的值 + + // 3. 生成下标值 + IRVirtualRegister idxReg = build(node.index()); + + // 4. 创建目标寄存器 + IRVirtualRegister dest = ctx.newRegister(); + + // 5. 准备参数 + List argv = new ArrayList<>(); + argv.add(arrReg); + argv.add(idxReg); + + // 6. 选择调用指令 + if (node.array() instanceof IndexExpressionNode) { + // 非最末层,下标取“引用” + ctx.addInstruction(new CallInstruction(dest, "__index_r", argv)); + } else { + // 最末层,下标取实际元素值,按声明类型分派 + String func = "__index_i"; // 默认整型 + if (node.array() instanceof IdentifierNode id) { + String declType = ctx.getScope().lookupType(id.name()); // 如 "double[]"、"int[]" + if (declType != null) { + String base = declType.toLowerCase(); + int p = base.indexOf('['); + if (p > 0) base = base.substring(0, p); // 基本类型 + switch (base) { + case "byte" -> func = "__index_b"; + case "short" -> func = "__index_s"; + case "int" -> func = "__index_i"; + case "long" -> func = "__index_l"; + case "float" -> func = "__index_f"; + case "double" -> func = "__index_d"; + case "boolean" -> func = "__index_i"; // 布尔型用 int 通道返回 1/0 + case "string" -> func = "__index_r"; // 字符串/其它未识别类型均走引用 + default -> func = "__index_r"; + } + } + } + ctx.addInstruction(new CallInstruction(dest, func, argv)); + } + + return dest; + } + + /** + * 构建中间层下标访问表达式(返回引用)。 + *

+ * 用于多维数组的链式下标访问(如 arr[1][2]),保证中间结果是“可被再次下标”的引用。 + *

    + *
  • 若数组和下标均为编译期常量,则直接常量折叠,返回目标常量寄存器;
  • + *
  • 否则,递归处理 array,生成“引用”指令(__index_r)。
  • + *
+ *

+ * + * @param node 下标访问表达式节点 + * @return 存放引用结果的虚拟寄存器 + */ + public IRVirtualRegister buildIndexRef(IndexExpressionNode node) { + // 1. 常量折叠:如果 array 和 index 都是编译期常量,直接取值 + Object arrConst = tryFoldConst(node.array()); + Object idxConst = tryFoldConst(node.index()); + if (arrConst instanceof java.util.List list && idxConst instanceof Number num) { + int i = num.intValue(); + // 越界检查 + if (i < 0 || i >= list.size()) + throw new IllegalStateException("数组下标越界: " + i + " (长度 " + list.size() + ")"); + Object elem = list.get(i); + IRVirtualRegister r = ctx.newRegister(); + // 加载常量元素到新寄存器 + InstructionFactory.loadConstInto(ctx, r, new IRConstant(elem)); + return r; + } + + // 2. 递归生成 array 的“引用”,用于支持链式多级下标 + IRVirtualRegister arrReg = (node.array() instanceof IndexExpressionNode inner) + ? buildIndexRef(inner) // 递归向下返回引用 + : build(node.array()); // 基础数组直接 build + + // 3. 生成下标值 + IRVirtualRegister idxReg = build(node.index()); + + // 4. 创建目标寄存器 + IRVirtualRegister dest = ctx.newRegister(); + + // 5. 组织参数列表 + List argv = new ArrayList<>(); + argv.add(arrReg); + argv.add(idxReg); + + // 6. 生成 __index_r 调用指令(总是返回引用) + ctx.addInstruction(new CallInstruction(dest, "__index_r", argv)); + + return dest; + } + + /** + * 常量折叠工具(支持嵌套数组)。 + *

+ * 尝试将表达式节点 {@code expr} 折叠为常量值,用于编译期计算/优化。 + *

    + *
  • 数字字面量:返回 int 或 double。
  • + *
  • 字符串字面量:返回字符串。
  • + *
  • 布尔字面量:返回 1(true)或 0(false)。
  • + *
  • 数组字面量:递归折叠所有元素为 List,如果有一项不能折叠则返回 null。
  • + *
  • 标识符:尝试从作用域查找编译期常量值。
  • + *
  • 其它类型:无法折叠,返回 null。
  • + *
+ *

+ * + * @param expr 需要折叠的表达式节点 + * @return 编译期常量值(支持 int、double、String、List),否则返回 null + */ + private Object tryFoldConst(ExpressionNode expr) { + if (expr == null) return null; + + // 数字字面量:尝试解析为 int 或 double + if (expr instanceof NumberLiteralNode n) { + String s = n.value(); + try { + if (s.contains(".") || s.contains("e") || s.contains("E")) + return Double.parseDouble(s); // 带小数或科学计数法为 double + return Integer.parseInt(s); // 否则为 int + } catch (NumberFormatException e) { + return null; // 无法解析为数字 + } + } + + // 字符串字面量:直接返回字符串 + if (expr instanceof StringLiteralNode s) return s.value(); + + // 布尔字面量:true 返回 1,false 返回 0 + if (expr instanceof BoolLiteralNode b) return b.getValue() ? 1 : 0; + + // 数组字面量:递归折叠所有元素 + if (expr instanceof ArrayLiteralNode arr) { + List list = new ArrayList<>(); + for (ExpressionNode e : arr.elements()) { + Object v = tryFoldConst(e); + if (v == null) return null; // 有一项无法折叠则整体失败 + list.add(v); + } + return List.copyOf(list); + } + + // 标识符:尝试查找作用域中的常量值 + if (expr instanceof IdentifierNode id) { + Object v = null; + try { + v = ctx.getScope().getConstValue(id.name()); + } catch (Throwable ignored) { + // 查不到常量或异常都视为无法折叠 + } + return v; + } + + // 其它类型:不支持折叠,返回 null + return null; + } + /** * 一元表达式构建 @@ -137,62 +337,70 @@ public record ExpressionBuilder(IRContext ctx) { */ private IRVirtualRegister buildUnary(UnaryExpressionNode un) { // 递归生成操作数的寄存器 - IRVirtualRegister src = build(un.operand()); + IRVirtualRegister src = build(un.operand()); // 分配目标寄存器 IRVirtualRegister dest = ctx.newRegister(); - switch (un.operator()) { - // 算术负号:生成取负指令 + // 算术负号:生成取负指令(例如:-a) case "-" -> ctx.addInstruction( new UnaryOperationInstruction(ExpressionUtils.negOp(un.operand()), dest, src)); - // 逻辑非:等价于 a == 0,生成比较指令 + + // 逻辑非:等价于 a == 0,生成整数等于比较指令(!a) case "!" -> { + // 生成常量0的寄存器 IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0); + // 比较 src 是否等于0,等价于逻辑非 return InstructionFactory.binOp(ctx, IROpCode.CMP_IEQ, src, zero); } - // 其它一元运算符不支持,抛异常 + // 其它一元运算符不支持,抛出异常 default -> throw new IllegalStateException("未知一元运算符: " + un.operator()); } return dest; } + /** - * 构建函数或方法调用表达式。模块内未限定调用会自动补全当前模块名。 + * 构建函数或方法调用表达式。 + *

+ * 支持普通函数调用(foo(a, b))与成员方法调用(obj.method(a, b))。 + *

    + *
  • 首先递归生成所有参数的虚拟寄存器列表。
  • + *
  • 根据 callee 类型区分成员访问或直接标识符调用,并规范化方法名(如加前缀)。
  • + *
  • 为返回值分配新寄存器,生成 Call 指令。
  • + *
+ *

* - * @param call AST 调用表达式节点 - * @return 存储调用结果的虚拟寄存器 + * @param call 函数/方法调用表达式节点 + * @return 存放调用结果的虚拟寄存器 */ private IRVirtualRegister buildCall(CallExpressionNode call) { - // 递归生成所有参数(实参)对应的寄存器 + // 1. 递归生成所有参数的寄存器 List argv = call.arguments().stream().map(this::build).toList(); - // 解析被调用目标名,支持普通函数/成员方法 + // 2. 规范化被调用方法名(区分成员方法与普通函数) String callee = switch (call.callee()) { - // 成员方法调用,例如 obj.foo() - case MemberExpressionNode m when m.object() instanceof IdentifierNode id - -> id.name() + "." + m.member(); - // 普通函数调用,如果未指定模块,自动补全当前模块名 + // 成员方法调用,如 obj.method() + case MemberExpressionNode m when m.object() instanceof IdentifierNode id -> id.name() + "." + m.member(); + // 普通函数调用,或处理命名空间前缀(如当前方法名为 namespace.func) case IdentifierNode id -> { String current = ctx.getFunction().name(); int dot = current.lastIndexOf('.'); - if (dot > 0) { - // 当前处于模块内函数(Module.func),补全为同模块下的全限定名 - yield current.substring(0, dot) + "." + id.name(); - } else { - // 顶层/脚本函数等不含模块前缀,保持原样 - yield id.name(); - } + if (dot > 0) + yield current.substring(0, dot) + "." + id.name(); // 同命名空间内调用 + yield id.name(); // 全局函数调用 } - // 其它情况暂不支持 - default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName()); + // 其它类型不支持 + default -> throw new IllegalStateException( + "不支持的调用目标: " + call.callee().getClass().getSimpleName()); }; - // 为返回值分配新寄存器,生成 Call 指令 + // 3. 分配用于存放返回值的新寄存器,并生成 Call 指令 IRVirtualRegister dest = ctx.newRegister(); ctx.addInstruction(new CallInstruction(dest, callee, new ArrayList<>(argv))); return dest; } + /** * 二元表达式构建,结果存储到新寄存器。 *
@@ -207,44 +415,54 @@ public record ExpressionBuilder(IRContext ctx) { IRVirtualRegister b = build(bin.right()); String op = bin.operator(); - // 比较运算符(==、!=、>、< 等),需要生成条件跳转或布尔值寄存器 + // 判断是否为比较运算符(==、!=、>、<等) if (ComparisonUtils.isComparisonOperator(op)) { + // 生成比较操作,返回布尔值寄存器 return InstructionFactory.binOp( ctx, - // 通过比较工具获得合适的 IR 操作码 + // 根据运算符和操作数类型获得合适的 IR 操作码 ComparisonUtils.cmpOp(ctx.getScope().getVarTypes(), op, bin.left(), bin.right()), a, b); } - // 其它算术/位运算 + // 其它二元运算(算术、位运算等) IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right()); - if (code == null) throw new IllegalStateException("不支持的运算符: " + op); + if (code == null) + throw new IllegalStateException("不支持的运算符: " + op); + // 生成二元操作指令 return InstructionFactory.binOp(ctx, code, a, b); } /** * 二元表达式构建,结果直接写入目标寄存器(用于赋值左值等优化场景)。 * - * @param bin 二元表达式节点 + * @param bin 二元表达式节点 * @param dest 目标虚拟寄存器 */ private void buildBinaryInto(BinaryExpressionNode bin, IRVirtualRegister dest) { + // 递归生成左、右操作数的寄存器 IRVirtualRegister a = build(bin.left()); IRVirtualRegister b = build(bin.right()); String op = bin.operator(); + // 处理比较运算符(==、!=、>、< 等) if (ComparisonUtils.isComparisonOperator(op)) { InstructionFactory.binOpInto( ctx, + // 选择对应类型和符号的比较操作码 ComparisonUtils.cmpOp(ctx.getScope().getVarTypes(), op, bin.left(), bin.right()), a, b, dest); } else { + // 算术或位运算符 IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right()); - if (code == null) throw new IllegalStateException("不支持的运算符: " + op); + if (code == null) + throw new IllegalStateException("不支持的运算符: " + op); + // 生成二元操作指令,写入目标寄存器 InstructionFactory.binOpInto(ctx, code, a, b, dest); } } + /* ───────────────── 字面量辅助方法 ───────────────── */ /** @@ -254,8 +472,11 @@ public record ExpressionBuilder(IRContext ctx) { * @return 存储该字面量的寄存器 */ private IRVirtualRegister buildNumberLiteral(String value) { + // 解析数字常量 IRConstant c = ExpressionUtils.buildNumberConstant(ctx, value); + // 分配新寄存器 IRVirtualRegister r = ctx.newRegister(); + // 生成 LoadConst 指令 ctx.addInstruction(new LoadConstInstruction(r, c)); return r; } @@ -267,8 +488,11 @@ public record ExpressionBuilder(IRContext ctx) { * @return 存储该字符串的寄存器 */ private IRVirtualRegister buildStringLiteral(String value) { + // 构建字符串常量 IRConstant c = new IRConstant(value); + // 分配新寄存器 IRVirtualRegister r = ctx.newRegister(); + // 生成 LoadConst 指令 ctx.addInstruction(new LoadConstInstruction(r, c)); return r; } @@ -280,9 +504,74 @@ public record ExpressionBuilder(IRContext ctx) { * @return 存储 1/0 的寄存器 */ private IRVirtualRegister buildBoolLiteral(boolean v) { + // 转换为 1 或 0 的常量 IRConstant c = new IRConstant(v ? 1 : 0); + // 分配新寄存器 IRVirtualRegister r = ctx.newRegister(); + // 生成 LoadConst 指令 ctx.addInstruction(new LoadConstInstruction(r, c)); return r; } + + /** + * 构建数组字面量表达式(元素均为常量时)。 + * + * @param arr 数组字面量节点 + * @return 存储该数组的寄存器 + */ + private IRVirtualRegister buildArrayLiteral(ArrayLiteralNode arr) { + // 递归生成支持嵌套的数组常量 + IRConstant c = buildArrayConstant(arr); + // 分配新寄存器 + IRVirtualRegister r = ctx.newRegister(); + // 生成 LoadConst 指令 + ctx.addInstruction(new LoadConstInstruction(r, c)); + return r; + } + + /** + * 构建支持嵌套的数组常量表达式。 + *

+ * 遍历并递归处理数组字面量的所有元素: + *

    + *
  • 数字字面量:根据内容生成 int 或 double 常量
  • + *
  • 字符串字面量:直接存储字符串内容
  • + *
  • 布尔字面量:转换为 1(true)或 0(false)存储
  • + *
  • 数组字面量:递归构建,允许多层嵌套,最终生成嵌套的 List
  • + *
+ * 若包含非常量元素,则抛出异常。 + *

+ * + * @param arr 数组字面量节点 + * @return 封装所有常量元素(支持嵌套 List)的 {@link IRConstant} + * @throws IllegalStateException 如果数组中存在非常量元素 + */ + + private IRConstant buildArrayConstant(ArrayLiteralNode arr) { + List list = new ArrayList<>(); + for (ExpressionNode e : arr.elements()) { + switch (e) { + // 数字字面量,解析并加入 + case NumberLiteralNode n -> { + IRConstant num = ExpressionUtils.buildNumberConstant(ctx, n.value()); + list.add(num.value()); + } + // 字符串字面量,直接加入 + case StringLiteralNode s -> list.add(s.value()); + // 布尔字面量,转成 1/0 + case BoolLiteralNode b -> list.add(b.getValue() ? 1 : 0); + // 嵌套数组,递归生成并加入 + case ArrayLiteralNode inner -> { + IRConstant innerConst = buildArrayConstant(inner); + list.add(innerConst.value()); + } + // 其它类型暂不支持 + default -> throw new IllegalStateException( + "暂不支持含非常量元素的数组字面量: " + e.getClass().getSimpleName()); + } + } + // 返回不可变的 List 封装为 IRConstant + return new IRConstant(List.copyOf(list)); + } + } diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/IRBuilderScope.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/IRBuilderScope.java index 209b4fa..774c883 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/builder/IRBuilderScope.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/IRBuilderScope.java @@ -7,109 +7,143 @@ import java.util.HashMap; import java.util.Map; /** - * IRBuilderScope 用于管理单个函数内变量名与虚拟寄存器的映射关系。 - * - *

主要功能包括: + * {@code IRBuilderScope} 用于管理单个函数作用域内的变量名与虚拟寄存器的映射关系。 + *

+ * 主要职责: *

    - *
  • 维护在当前作用域中已声明变量的寄存器分配信息;
  • - *
  • 支持将已有虚拟寄存器与变量名重新绑定;
  • - *
  • 根据变量名查找对应的虚拟寄存器实例或类型。
  • + *
  • 维护当前作用域内所有已声明变量的寄存器和类型信息
  • + *
  • 支持变量与虚拟寄存器的重新绑定与查找
  • + *
  • 支持变量的类型信息记录与查询
  • + *
  • 支持变量的编译期常量值记录与查询(便于常量折叠等优化)
  • *
*/ final class IRBuilderScope { /** - * 存储变量名到对应 IRVirtualRegister 的映射。 - * 变量名为键,虚拟寄存器对象为值,用于查找和更新。 + * 变量名到虚拟寄存器的映射表。 + * 用于跟踪所有声明和分配的变量。 */ private final Map vars = new HashMap<>(); /** - * 存储变量名到对应类型的映射。 - *
- * 变量名为键,变量类型为值,用于变量类型提升。 + * 变量名到类型字符串的映射表。 + * 用于类型分析与推断。 */ private final Map varTypes = new HashMap<>(); - /** - * 当前作用域所绑定的 IRFunction 对象,用于申请新的虚拟寄存器。 + * 变量名到编译期常量值的映射表。 + * 用于常量折叠优化(如 int、string、数组等常量)。 + */ + private final Map varConstValues = new HashMap<>(); + /** + * 当前作用域所绑定的 IRFunction 实例。 + * 用于申请新的虚拟寄存器。 */ private IRFunction fn; /** - * 将指定的 IRFunction 关联到当前作用域,以便后续声明变量时能够 - * 调用该函数的 newRegister() 方法生成新的寄存器。 + * 绑定当前作用域到指定 IRFunction。 * - * @param fn 要绑定到本作用域的 IRFunction 实例 + * @param fn 目标 IRFunction */ void attachFunction(IRFunction fn) { this.fn = fn; } /** - * 在当前作用域中声明一个新变量,并为其分配一个新的虚拟寄存器。 - * 调用绑定的 IRFunction.newRegister() 生成寄存器后保存到映射表中。 + * 声明一个新变量并分配新的虚拟寄存器。 * - * @param name 变量名称,作为映射键使用 - * @param type 变量类型 + * @param name 变量名称 + * @param type 变量类型名 */ void declare(String name, String type) { IRVirtualRegister reg = fn.newRegister(); vars.put(name, reg); varTypes.put(name, type); + varConstValues.remove(name); } /** - * 在当前作用域中声明或导入一个已有的虚拟寄存器,并将其与指定变量名绑定。 - * 该方法可用于将外部或前一作用域的寄存器导入到本作用域。 + * 声明新变量,并绑定到指定的寄存器。 * - * @param name 变量名称,作为映射键使用 - * @param type 变量类型 - * @param reg 要绑定到该名称的 IRVirtualRegister 实例 + * @param name 变量名称 + * @param type 变量类型名 + * @param reg 绑定的虚拟寄存器 */ void declare(String name, String type, IRVirtualRegister reg) { vars.put(name, reg); varTypes.put(name, type); + varConstValues.remove(name); } /** - * 更新已存在变量的虚拟寄存器绑定关系。若变量已声明,则替换其对应的寄存器; - * 若尚未声明,则等同于声明新变量。 + * 更新变量的虚拟寄存器绑定(如变量已存在则覆盖,否则等同于新声明)。 * - * @param name 变量名称,作为映射键使用 - * @param reg 新的 IRVirtualRegister 实例,用于替换旧绑定 + * @param name 变量名称 + * @param reg 新的虚拟寄存器 */ void put(String name, IRVirtualRegister reg) { vars.put(name, reg); } /** - * 根据变量名称在当前作用域中查找对应的虚拟寄存器。 + * 查找变量名对应的虚拟寄存器。 * - * @param name 需要查询的变量名称 - * @return 如果该名称已绑定寄存器,则返回对应的 IRVirtualRegister; - * 如果未声明,则返回 null + * @param name 变量名 + * @return 已绑定的虚拟寄存器,若未声明则返回 null */ IRVirtualRegister lookup(String name) { return vars.get(name); } /** - * 根据变量名称在当前作用域中查找对应的类型。 + * 查找变量名对应的类型名。 * - * @param name 需要查询的变量名称 - * @return 如果该名称已声明,则返回对应的类型 - * 如果未声明,则返回 null + * @param name 变量名 + * @return 已声明类型字符串,若未声明则返回 null */ String lookupType(String name) { return varTypes.get(name); } /** - * 获取 变量->类型的映射 的不可变副本 - * @return 变量->类型的映射 的不可变副本 + * 获取变量名到类型名映射的不可变副本。 + * + * @return 变量名→类型名映射的只读视图 */ Map getVarTypes() { return Map.copyOf(varTypes); } + + // ---------------- 编译期常量相关接口 ---------------- + + /** + * 设置变量的编译期常量值。 + * + * @param name 变量名称 + * @param value 常量值(null 表示清除) + */ + void setConstValue(String name, Object value) { + if (value == null) varConstValues.remove(name); + else varConstValues.put(name, value); + } + + /** + * 获取变量的编译期常量值(如没有则返回 null)。 + * + * @param name 变量名称 + * @return 编译期常量值,或 null + */ + Object getConstValue(String name) { + return varConstValues.get(name); + } + + /** + * 清除变量的编译期常量值绑定。 + * + * @param name 变量名称 + */ + void clearConstValue(String name) { + varConstValues.remove(name); + } } 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 b735213..e95d132 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 @@ -10,13 +10,23 @@ import org.jcnc.snow.compiler.parser.ast.base.NodeContext; import org.jcnc.snow.compiler.parser.ast.base.StatementNode; import java.util.ArrayDeque; -import java.util.Locale; /** - * StatementBuilder —— 将 AST 语句节点 ({@link StatementNode}) 转换为 IR 指令序列的构建器。 + * StatementBuilder —— AST 语句节点 ({@link StatementNode}) 到 IR 指令序列的构建器。 *

- * 负责将各种语句节点(循环、分支、表达式、赋值、声明、返回等)生成对应的 IR 指令,并管理作用域和控制流标签。 + * 负责将各类语句(如循环、分支、表达式、赋值、声明、返回、break、continue 等) + * 转换为对应的 IR 指令,并自动管理作用域、虚拟寄存器分配以及控制流标签(如 break/continue 目标)。 *

+ * + *
    + *
  • 支持多种语句类型的分发与转换。
  • + *
  • 与 {@link ExpressionBuilder} 协作完成表达式相关 IR 生成。
  • + *
  • 负责控制流跳转(分支、循环)的标签分配与维护。
  • + *
  • 在变量赋值和声明时自动常量折叠和登记。
  • + *
+ * + * @author [你的名字] + * @since 1.0 */ public class StatementBuilder { @@ -24,79 +34,126 @@ public class StatementBuilder { * 当前 IR 上下文,包含作用域、指令序列等信息。 */ private final IRContext ctx; + /** * 表达式 IR 构建器,用于将表达式节点转为 IR 指令。 */ private final ExpressionBuilder expr; + /** - * break 目标标签栈(保存每层循环的结束标签) + * break 目标标签栈(保存每层循环的结束标签,用于 break 跳转) */ private final ArrayDeque breakTargets = new ArrayDeque<>(); + /** - * continue 目标标签栈(保存每层循环的 step 起始标签) + * continue 目标标签栈(保存每层循环的 step 起始标签,用于 continue 跳转) */ private final ArrayDeque continueTargets = new ArrayDeque<>(); /** - * 构造方法。 + * 构造方法。初始化 StatementBuilder。 * - * @param ctx IR 编译上下文环境 + * @param ctx IR 编译上下文环境,包含作用域、标签、指令等信息 */ public StatementBuilder(IRContext ctx) { this.ctx = ctx; this.expr = new ExpressionBuilder(ctx); } - 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 整型处理 - }; - } - /** * 将一个 AST 语句节点转为 IR 指令序列。 + *

* 根据节点类型分发到对应的处理方法。 + * 支持:循环、分支、表达式语句、赋值、声明、返回、break、continue。 + * 不支持的语句类型会抛出异常。 + *

* - * @param stmt 待转换的语句节点 + * @param stmt 待转换的语句节点,不能为空 + * @throws IllegalStateException 若遇到不支持的语句类型,或 break/continue 不在循环体中 */ public void build(StatementNode stmt) { if (stmt instanceof LoopNode loop) { - // 循环语句 buildLoop(loop); return; } if (stmt instanceof IfNode ifNode) { - // 分支(if-else)语句 buildIf(ifNode); return; } if (stmt instanceof ExpressionStatementNode(ExpressionNode exp, NodeContext _)) { - // 纯表达式语句,如 foo(); expr.build(exp); return; } if (stmt instanceof AssignmentNode(String var, ExpressionNode rhs, NodeContext _)) { - // 赋值语句,如 a = b + 1; - final String type = ctx.getScope().lookupType(var); - - // 1. 设置声明变量的类型 ctx.setVarType(type); - IRVirtualRegister target = getOrDeclareRegister(var, type); expr.buildInto(rhs, target); - // 2. 清除变量声明 + // 赋值时尝试记录/清除常量 + try { + Object constVal = tryFoldConst(rhs); + if (constVal != null) + ctx.getScope().setConstValue(var, constVal); + else + ctx.getScope().clearConstValue(var); + } catch (Throwable ignored) { + } + ctx.clearVarType(); + return; + } + + // ==== 支持数组下标赋值 arr[idx] = value ==== + if (stmt instanceof IndexAssignmentNode idxAssign) { + IndexExpressionNode target = idxAssign.target(); + + // 1. 目标数组寄存器:多维时用 buildIndexRef 拿引用 + IRVirtualRegister arrReg = (target.array() instanceof IndexExpressionNode inner) + ? expr.buildIndexRef(inner) + : expr.build(target.array()); + + // 2. 下标与右值 + IRVirtualRegister idxReg = expr.build(target.index()); + IRVirtualRegister valReg = expr.build(idxAssign.value()); + + // 3. 选择内置函数名 __setindex_x,根据元素类型分派 + String func = "__setindex_r"; + org.jcnc.snow.compiler.parser.ast.base.ExpressionNode base = target.array(); + while (base instanceof IndexExpressionNode innerIdx) base = innerIdx.array(); + if (base instanceof IdentifierNode id) { + String arrType = ctx.getScope().lookupType(id.name()); + if (arrType != null) { + String elemType = arrType.endsWith("[]") ? arrType.substring(0, arrType.length() - 2) : arrType; + switch (elemType) { + case "byte" -> func = "__setindex_b"; + case "short" -> func = "__setindex_s"; + case "int" -> func = "__setindex_i"; + case "long" -> func = "__setindex_l"; + case "float" -> func = "__setindex_f"; + case "double" -> func = "__setindex_d"; + case "boolean" -> func = "__setindex_i"; + case "string" -> func = "__setindex_r"; + default -> func = "__setindex_r"; + } + } + } + + // 4. 生成 CALL 指令 + java.util.List argv = + java.util.List.of(arrReg, idxReg, valReg); + ctx.addInstruction(new org.jcnc.snow.compiler.ir.instruction.CallInstruction(null, func, argv)); + + // 5. 赋值后清理常量绑定 + try { + if (base instanceof IdentifierNode id2) { + ctx.getScope().clearConstValue(id2.name()); + } + } catch (Throwable ignored) {} return; } + if (stmt instanceof DeclarationNode decl) { // 变量声明语句(如 int a = 1;) if (decl.getInitializer().isPresent()) { @@ -113,16 +170,21 @@ public class StatementBuilder { // 即使初始值是某个已存在变量(如 outer_i),这里是值的拷贝 expr.buildInto(decl.getInitializer().get(), dest); - // 4. 清理类型设置,防止影响后续变量声明 - ctx.clearVarType(); + // 声明赋初值时登记常量 + try { + Object constVal = tryFoldConst(decl.getInitializer().get()); + if (constVal != null) + ctx.getScope().setConstValue(decl.getName(), constVal); + else + ctx.getScope().clearConstValue(decl.getName()); + } catch (Throwable ignored) { + } - // 5. 在作用域内将变量名与新分配的寄存器进行绑定 - // 这样后续对该变量的任何操作都只会影响 dest,不会反向影响初值表达式中的源变量 + ctx.clearVarType(); ctx.getScope().declare(decl.getName(), decl.getType(), dest); } else { - // 仅声明变量,无初值(如 int a;) - // 在作用域内声明并分配新寄存器,但不进行初始化 ctx.getScope().declare(decl.getName(), decl.getType()); + ctx.getScope().clearConstValue(decl.getName()); } return; } @@ -159,10 +221,11 @@ public class StatementBuilder { } /** - * 获取变量名对应的寄存器,不存在则声明一个新的。 + * 获取变量名对应的寄存器,如果尚未声明则新声明一个并返回。 * - * @param name 变量名 - * @return 变量对应的虚拟寄存器 + * @param name 变量名,不能为空 + * @param type 变量类型,不能为空 + * @return 变量对应的虚拟寄存器 {@link IRVirtualRegister} */ private IRVirtualRegister getOrDeclareRegister(String name, String type) { IRVirtualRegister reg = ctx.getScope().lookup(name); @@ -174,19 +237,21 @@ public class StatementBuilder { } /** - * 批量构建一组语句节点,顺序处理每个语句。 + * 批量构建一组语句节点,按顺序依次处理。 * - * @param stmts 语句节点集合 + * @param stmts 语句节点集合,不可为 null */ private void buildStatements(Iterable stmts) { for (StatementNode s : stmts) build(s); } /** - * 构建循环语句(for/while)。 - * 处理流程: 初始语句 → 条件判断 → 循环体 → 更新语句 → 跳回条件。 + * 构建循环语句(for/while),包括初始语句、条件判断、循环体、更新语句、跳回判断等 IR 指令。 + *

+ * 自动维护 break/continue 的目标标签。 + *

* - * @param loop 循环节点 + * @param loop 循环节点,不能为空 */ private void buildLoop(LoopNode loop) { if (loop.init() != null) build(loop.init()); @@ -222,9 +287,11 @@ public class StatementBuilder { /** * 构建分支语句(if/else)。 - * 处理流程: 条件判断 → then 分支 → else 分支(可选)。 + *

+ * 包括:条件判断、then 分支、else 分支(可选)、结束标签等。 + *

* - * @param ifNode if 语句节点 + * @param ifNode if 语句节点,不能为空 */ private void buildIf(IfNode ifNode) { String lblElse = ctx.newLabel(); @@ -245,11 +312,14 @@ public class StatementBuilder { } /** - * 条件跳转指令的生成。 - * 如果是二元比较表达式,直接使用对应比较操作码;否则等价于与 0 比较。 + * 发射条件跳转指令:如果 cond 不成立,则跳转到 falseLabel。 + *

+ * 对于二元比较表达式,会选择恰当的比较指令。 + * 其他类型表达式,等价于 (cond == 0) 时跳转。 + *

* - * @param cond 条件表达式 - * @param falseLabel 条件不成立时跳转到的标签 + * @param cond 条件表达式节点,不可为 null + * @param falseLabel 条件不成立时跳转的标签,不可为 null */ private void emitConditionalJump(ExpressionNode cond, String falseLabel) { if (cond instanceof BinaryExpressionNode( @@ -271,7 +341,79 @@ public class StatementBuilder { } else { IRVirtualRegister condReg = expr.build(cond); IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0); - InstructionFactory.cmpJump(ctx, IROpCode.CMP_IEQ, condReg, zero, falseLabel); + InstructionFactory.cmpJump(ctx, org.jcnc.snow.compiler.ir.core.IROpCode.CMP_IEQ, condReg, zero, falseLabel); } } + + /** + * 递归尝试对表达式做常量折叠(constant folding)。 + *

+ * 该方法会根据表达式类型进行常量求值: + *

    + *
  • 如果是数字字面量,解析为 Integer 或 Double。
  • + *
  • 如果是字符串字面量,直接返回字符串内容。
  • + *
  • 如果是布尔字面量,返回 1(true)或 0(false)。
  • + *
  • 如果是数组字面量,会递归所有元素全部是常量时,返回包含常量元素的 List。
  • + *
  • 如果是标识符节点,尝试从作用域查找是否登记为常量,如果找到则返回。
  • + *
  • 其余类型或无法确定常量时,返回 null。
  • + *
+ * 用于全局/局部常量传播优化与类型推断。 + * + * @param expr 待折叠的表达式节点,允许为 null + * @return 如果可折叠则返回其常量值(如 Integer、Double、String、List),否则返回 null + */ + private Object tryFoldConst(ExpressionNode expr) { + // 1. 空节点直接返回 null + if (expr == null) return null; + + // 2. 数字字面量:尝试解析为 Integer 或 Double + if (expr instanceof NumberLiteralNode n) { + String s = n.value(); // 获取文本内容 + try { + // 判断是否为浮点型(包含 . 或 e/E 科学计数法) + if (s.contains(".") || s.contains("e") || s.contains("E")) { + return Double.parseDouble(s); // 解析为 Double + } + return Integer.parseInt(s); // 否则解析为 Integer + } catch (NumberFormatException e) { + // 解析失败,返回 null + return null; + } + } + + // 3. 字符串字面量:直接返回字符串内容 + if (expr instanceof StringLiteralNode s) { + return s.value(); + } + + // 4. 布尔字面量:true 返回 1,false 返回 0 + if (expr instanceof BoolLiteralNode b) { + return b.getValue() ? 1 : 0; + } + + // 5. 数组字面量:递归所有元素做常量折叠,只有全为常量时才返回 List + if (expr instanceof ArrayLiteralNode arr) { + java.util.List list = new java.util.ArrayList<>(); + for (ExpressionNode e : arr.elements()) { + Object v = tryFoldConst(e); // 递归折叠每个元素 + if (v == null) return null; // 只要有一个不是常量,则整个数组不是常量 + list.add(v); + } + // 所有元素均为常量,返回只读 List + return java.util.List.copyOf(list); + } + + // 6. 标识符:尝试查找该变量在当前作用域是否登记为常量 + if (expr instanceof IdentifierNode id) { + try { + Object v = ctx.getScope().getConstValue(id.name()); + if (v != null) return v; // 查到常量则返回 + } catch (Throwable ignored) { + } + } + + // 7. 其他情况均视为不可折叠,返回 null + return null; + } + } diff --git a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/SymbolTokenScanner.java b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/SymbolTokenScanner.java index 2085fef..3810de8 100644 --- a/src/main/java/org/jcnc/snow/compiler/lexer/scanners/SymbolTokenScanner.java +++ b/src/main/java/org/jcnc/snow/compiler/lexer/scanners/SymbolTokenScanner.java @@ -28,7 +28,7 @@ public class SymbolTokenScanner extends AbstractTokenScanner { */ @Override public boolean canHandle(char c, LexerContext ctx) { - return ":,().+-*/".indexOf(c) >= 0; + return ":,().+-*/[]".indexOf(c) >= 0; } /** @@ -53,6 +53,8 @@ public class SymbolTokenScanner extends AbstractTokenScanner { case '/' -> TokenType.DIVIDE; case '(' -> TokenType.LPAREN; case ')' -> TokenType.RPAREN; + case '[' -> TokenType.LBRACKET; + case ']' -> TokenType.RBRACKET; default -> TokenType.UNKNOWN; }; return new Token(type, String.valueOf(c), line, col); 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 554d48c..07b6561 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 @@ -14,10 +14,10 @@ public enum TokenType { /** 普通标识符,如变量名、函数名等 */ IDENTIFIER, - /** 语言保留关键字(如 if、return、module 等) */ + /** 关键字(declare、if、else、loop、break、continue、return 等) */ KEYWORD, - /** 内置类型名称(如 int、string、bool 等) */ + /** 内置类型名(byte、short、int、long、float、double、string、boolean、void 等) */ TYPE, /* ---------- 字面量 ---------- */ @@ -67,6 +67,12 @@ public enum TokenType { /** 右括号 ')' */ RPAREN, + /** 左中括号 '[' */ + LBRACKET, + + /** 右中括号 ']' */ + RBRACKET, + /** 相等比较符号 '==' */ DOUBLE_EQUALS, diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/ArrayLiteralNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/ArrayLiteralNode.java new file mode 100644 index 0000000..a2a8e7e --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/ArrayLiteralNode.java @@ -0,0 +1,40 @@ +package org.jcnc.snow.compiler.parser.ast; + +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.NodeContext; + +import java.util.List; + +/** + * {@code ArrayLiteralNode} 表示数组字面量表达式节点。 + *

+ * 例如:[1, 2, 3] 或 [[1, 2], [3, 4]] 这样的语法结构,均对应本节点类型。 + *

+ * + *
    + *
  • {@link #elements()} 保存所有元素表达式节点。
  • + *
  • {@link #context()} 表示该节点在源代码中的上下文信息(如位置、父节点等)。
  • + *
+ */ +public record ArrayLiteralNode( + /* + 数组字面量中的所有元素表达式(按顺序)。 + */ + List elements, + + /* + 节点的上下文信息(如源码位置等)。 + */ + NodeContext context +) implements ExpressionNode { + + /** + * 返回字符串形式,如 {@code Array[1, 2, 3]}。 + * + * @return 表示该数组字面量节点的字符串 + */ + @Override + public String toString() { + return "Array" + elements; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexAssignmentNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexAssignmentNode.java new file mode 100644 index 0000000..9dc8333 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexAssignmentNode.java @@ -0,0 +1,42 @@ +package org.jcnc.snow.compiler.parser.ast; + +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.NodeContext; +import org.jcnc.snow.compiler.parser.ast.base.StatementNode; + +/** + * {@code IndexAssignmentNode} 表示数组元素赋值语句节点,例如 {@code arr[i] = value}。 + *

+ * 对于多维数组,{@code target} 可以是嵌套的 {@link IndexExpressionNode}。 + *

+ * + * @param target 要赋值的数组元素(支持多维数组下标) + * @param value 右侧赋值表达式 + * @param context 该节点的源代码上下文信息 + */ +public record IndexAssignmentNode( + /* + 数组元素目标,支持多维数组下标(如 arr[i][j])。 + */ + IndexExpressionNode target, + + /* + 被赋的右侧表达式 + */ + ExpressionNode value, + + /* + 节点的上下文信息(如源码位置等) + */ + NodeContext context +) implements StatementNode { + /** + * 返回赋值语句的字符串表示(如 "arr[i] = value")。 + * + * @return 字符串形式 + */ + @Override + public String toString() { + return target + " = " + value; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexExpressionNode.java b/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexExpressionNode.java new file mode 100644 index 0000000..a33caa9 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/ast/IndexExpressionNode.java @@ -0,0 +1,41 @@ +package org.jcnc.snow.compiler.parser.ast; + +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.NodeContext; + +/** + * {@code IndexExpressionNode} 表示数组/集合的下标访问表达式节点,例如 {@code arr[i]}。 + *

+ * 支持多维数组嵌套(如 arr[i][j])。 + *

+ * + * @param array 被访问的目标表达式(通常是数组、集合或嵌套下标表达式) + * @param index 下标表达式(必须为数值类型) + * @param context 源码上下文信息(如位置) + */ +public record IndexExpressionNode( + /* + 下标访问的目标表达式(如 arr)。 + */ + ExpressionNode array, + + /* + 下标表达式(如 i)。 + */ + ExpressionNode index, + + /* + 源码上下文信息(如行号、文件名等)。 + */ + NodeContext context +) implements ExpressionNode { + + /** + * 返回形如 "arr[i]" 的字符串表示。 + * @return 表达式的字符串形式 + */ + @Override + public String toString() { + return array + "[" + index + "]"; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/ArrayLiteralParselet.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/ArrayLiteralParselet.java new file mode 100644 index 0000000..6a9a483 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/ArrayLiteralParselet.java @@ -0,0 +1,80 @@ +package org.jcnc.snow.compiler.parser.expression; + +import org.jcnc.snow.compiler.lexer.token.Token; +import org.jcnc.snow.compiler.lexer.token.TokenType; +import org.jcnc.snow.compiler.parser.ast.ArrayLiteralNode; +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.NodeContext; +import org.jcnc.snow.compiler.parser.context.ParserContext; +import org.jcnc.snow.compiler.parser.context.TokenStream; +import org.jcnc.snow.compiler.parser.expression.base.PrefixParselet; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@code ArrayLiteralParselet} 用于解析数组字面量表达式。 + *

+ * 支持语法形如 {@code [a, b, c]} 或 {@code [ [1,2], [3,4] ]}, + * 允许元素及逗号前后出现换行符,便于多行书写。 + *

+ *

+ * 语法结构为:
+ *

[ (element (',' element)*)? ]
+ *

+ */ +public class ArrayLiteralParselet implements PrefixParselet { + + /** + * 解析数组字面量表达式。 + * + * @param ctx 解析上下文(包含词法流、源文件信息等) + * @param token 已消费的左中括号(LBRACKET),用于定位节点源信息 + * @return 解析得到的 {@link ArrayLiteralNode} 节点 + */ + @Override + public ExpressionNode parse(ParserContext ctx, Token token) { + // token 为已消费的 LBRACKET,使用其位置生成 NodeContext + int line = token.getLine(); + int col = token.getCol(); + String file = ctx.getSourceName(); + + TokenStream ts = ctx.getTokens(); + skipNewlines(ts); + + List elements = new ArrayList<>(); + + // 空数组: 直接遇到 RBRACKET + if (ts.peek().getType() != TokenType.RBRACKET) { + while (true) { + // 解析一个元素 + ExpressionNode elem = new PrattExpressionParser().parse(ctx); + elements.add(elem); + + skipNewlines(ts); + // 逗号继续,右中括号结束 + if (ts.peek().getType() == TokenType.COMMA) { + ts.next(); + skipNewlines(ts); + continue; + } + break; + } + } + + // 期望并消费右中括号 + ts.expectType(TokenType.RBRACKET); + return new ArrayLiteralNode(elements, new NodeContext(line, col, file)); + } + + /** + * 跳过词法流中连续的换行符,允许数组元素跨多行书写。 + * + * @param ts 词法流 + */ + private static void skipNewlines(TokenStream ts) { + while (ts.peek().getType() == TokenType.NEWLINE) { + ts.next(); + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/parser/expression/IndexParselet.java b/src/main/java/org/jcnc/snow/compiler/parser/expression/IndexParselet.java new file mode 100644 index 0000000..a32e35c --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/parser/expression/IndexParselet.java @@ -0,0 +1,51 @@ +package org.jcnc.snow.compiler.parser.expression; + +import org.jcnc.snow.compiler.lexer.token.TokenType; +import org.jcnc.snow.compiler.parser.ast.IndexExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +import org.jcnc.snow.compiler.parser.ast.base.NodeContext; +import org.jcnc.snow.compiler.parser.context.ParserContext; +import org.jcnc.snow.compiler.parser.expression.base.InfixParselet; + +/** + * {@code IndexParselet} 负责解析数组或集合的下标访问表达式,语法结构为: + *
+ *   primary '[' expr ']'
+ * 
+ * 例如 {@code arr[expr]}。 + */ +public class IndexParselet implements InfixParselet { + + /** + * 解析下标访问表达式。 + * + * @param ctx 解析上下文,包含词法流、错误信息等 + * @param left 已解析的 primary 表达式(如数组名、表达式等) + * @return {@link IndexExpressionNode} 下标访问表达式节点 + */ + @Override + public ExpressionNode parse(ParserContext ctx, ExpressionNode left) { + int line = left.context().line(); + int col = left.context().column(); + String file = left.context().file(); + + // 消耗左中括号 '[' + ctx.getTokens().expectType(TokenType.LBRACKET); + // 解析索引表达式 + ExpressionNode index = new PrattExpressionParser().parse(ctx); + // 消耗右中括号 ']' + ctx.getTokens().expectType(TokenType.RBRACKET); + // 构造下标访问表达式节点 + return new IndexExpressionNode(left, index, new NodeContext(line, col, file)); + } + + /** + * 下标访问优先级与函数调用一致。 + * + * @return {@link Precedence#CALL} 优先级 + */ + @Override + public Precedence precedence() { + return Precedence.CALL; + } +} 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 df2ee36..f18813f 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 @@ -25,45 +25,80 @@ import java.util.Map; */ public class PrattExpressionParser implements ExpressionParser { - /** 前缀解析器注册表(按 Token 类型名索引) */ + /** + * 前缀解析器注册表(按 Token 类型名或词素索引)。 + *

+ * 用于存储所有支持的前缀表达式解析器,例如字面量、变量、分组、数组、一元运算等。 + * 支持通过 TokenType 的名称和特定词素(如 "(", "[")两种方式索引。 + *

+ */ private static final Map prefixes = new HashMap<>(); - /** 中缀解析器注册表(按运算符词素索引) */ + + /** + * 中缀解析器注册表(按运算符词素索引)。 + *

+ * 用于存储所有支持的中缀表达式解析器,如二元运算、函数调用、下标、成员访问等。 + * 仅通过词素索引(如 "+", "-", "(", "[" 等)。 + *

+ */ private static final Map infixes = new HashMap<>(); static { - // 前缀解析器注册 + // -------- 前缀解析器注册 -------- + // 注册数字字面量解析 prefixes.put(TokenType.NUMBER_LITERAL.name(), new NumberLiteralParselet()); + // 注册标识符(变量名)解析 prefixes.put(TokenType.IDENTIFIER.name(), new IdentifierParselet()); - prefixes.put(TokenType.LPAREN.name(), new GroupingParselet()); + // 注册字符串字面量解析 prefixes.put(TokenType.STRING_LITERAL.name(), new StringLiteralParselet()); + // 注册布尔字面量解析 prefixes.put(TokenType.BOOL_LITERAL.name(), new BoolLiteralParselet()); - // 一元前缀运算符 + // 支持括号分组、数组字面量 + prefixes.put(TokenType.LPAREN.name(), new GroupingParselet()); + prefixes.put(TokenType.LBRACKET.name(), new ArrayLiteralParselet()); + // 兼容直接以词素注册(如 '(' '[') + prefixes.put("(", new GroupingParselet()); + prefixes.put("[", new ArrayLiteralParselet()); + + // 一元前缀运算符(负号、逻辑非) prefixes.put(TokenType.MINUS.name(), new UnaryOperatorParselet()); prefixes.put(TokenType.NOT.name(), new UnaryOperatorParselet()); + prefixes.put("-", new UnaryOperatorParselet()); + prefixes.put("!", new UnaryOperatorParselet()); - // 中缀解析器注册 + // -------- 中缀解析器注册 -------- + // 注册常见二元运算符(加减乘除、取模) infixes.put("+", new BinaryOperatorParselet(Precedence.SUM, true)); infixes.put("-", new BinaryOperatorParselet(Precedence.SUM, true)); infixes.put("*", new BinaryOperatorParselet(Precedence.PRODUCT, true)); infixes.put("/", new BinaryOperatorParselet(Precedence.PRODUCT, true)); infixes.put("%", new BinaryOperatorParselet(Precedence.PRODUCT, true)); - infixes.put(">", new BinaryOperatorParselet(Precedence.SUM, true)); - infixes.put("<", new BinaryOperatorParselet(Precedence.SUM, true)); - infixes.put("==", new BinaryOperatorParselet(Precedence.SUM, true)); - infixes.put("!=", new BinaryOperatorParselet(Precedence.SUM, true)); - infixes.put(">=", new BinaryOperatorParselet(Precedence.SUM, true)); - infixes.put("<=", new BinaryOperatorParselet(Precedence.SUM, true)); + // 比较运算符 + infixes.put(">", new BinaryOperatorParselet(Precedence.COMPARISON, true)); + infixes.put("<", new BinaryOperatorParselet(Precedence.COMPARISON, true)); + infixes.put(">=", new BinaryOperatorParselet(Precedence.COMPARISON, true)); + infixes.put("<=", new BinaryOperatorParselet(Precedence.COMPARISON, true)); + // 相等性 + infixes.put("==", new BinaryOperatorParselet(Precedence.EQUALITY, true)); + infixes.put("!=", new BinaryOperatorParselet(Precedence.EQUALITY, true)); + // 逻辑与或 + infixes.put("&&", new BinaryOperatorParselet(Precedence.AND, true)); + infixes.put("||", new BinaryOperatorParselet(Precedence.OR, true)); + // 调用、索引、成员访问 infixes.put("(", new CallParselet()); + infixes.put("[", new IndexParselet()); infixes.put(".", new MemberParselet()); } /** - * 表达式解析统一入口。 - * 以最低优先级启动递归下降,适配任意表达式复杂度。 + * 解析任意表达式的统一入口。 + *

+ * 该方法将以最低优先级启动表达式递归解析,能够自动适配和处理多层嵌套或复杂组合表达式。 + *

* - * @param ctx 当前解析上下文 - * @return 解析后的表达式 AST 节点 + * @param ctx 当前解析上下文对象(持有 token 流等信息) + * @return 解析得到的表达式 AST 节点对象 */ @Override public ExpressionNode parse(ParserContext ctx) { @@ -71,21 +106,32 @@ public class PrattExpressionParser implements ExpressionParser { } /** - * 按指定优先级解析表达式。Pratt 算法主循环。 + * 按指定优先级解析表达式(Pratt 算法核心)。 *

- * 先根据当前 Token 类型查找前缀解析器进行初始解析, - * 然后根据优先级不断递归处理中缀运算符和右侧表达式。 + * 1. 先取当前 token,查找对应的前缀解析器进行初始解析,构建表达式左侧(如字面量、变量等)。 + * 2. 然后循环检测是否有更高优先级的中缀操作符, + * 若有则递归处理右侧表达式并组合为新的表达式节点。 + *

+ *

+ * 未找到对应前缀或中缀解析器时会抛出 {@link UnsupportedFeature} 异常。 *

* * @param ctx 解析上下文 - * @param prec 当前运算符优先级阈值 - * @return 构建完成的表达式节点 - * @throws UnsupportedFeature 若遇到未注册的前缀或中缀解析器 + * @param prec 当前运算符优先级(用于控制递归层级) + * @return 解析构建好的表达式节点 + * @throws UnsupportedFeature 遇到未注册的解析器时抛出 */ ExpressionNode parseExpression(ParserContext ctx, Precedence prec) { + // 取下一个 token 作为本轮前缀表达式起始 Token token = ctx.getTokens().next(); + + // 查找前缀解析器(先按类型名,再按词素) PrefixParselet prefix = prefixes.get(token.getType().name()); if (prefix == null) { + prefix = prefixes.get(token.getLexeme()); + } + if (prefix == null) { + // 未找到前缀解析器则报错 throw new UnsupportedFeature( "没有为该 Token 类型注册前缀解析器: " + token.getType(), token.getLine(), @@ -93,13 +139,17 @@ public class PrattExpressionParser implements ExpressionParser { ); } + // 执行前缀解析,获得左侧表达式 ExpressionNode left = prefix.parse(ctx, token); + // 不断尝试查找优先级更高的中缀运算符,递归处理表达式链 while (!ctx.getTokens().isAtEnd() && prec.ordinal() < nextPrecedence(ctx)) { + // 查看下一个 token 词素,查找中缀解析器 String lex = ctx.getTokens().peek().getLexeme(); InfixParselet infix = infixes.get(lex); if (infix == null) { + // 若未注册中缀解析器,则直接抛异常(常见于语法错误) Token t = ctx.getTokens().peek(); throw new UnsupportedFeature( "没有为该运算符注册中缀解析器: '" + lex + "'", @@ -107,16 +157,21 @@ public class PrattExpressionParser implements ExpressionParser { t.getCol() ); } + // 使用中缀解析器处理表达式组合 left = infix.parse(ctx, left); } + // 返回本层递归已解析的表达式节点 return left; } /** - * 获取下一个中缀解析器的优先级(Pratt 算法核心)。 + * 获取下一个 token 词素对应的中缀运算符优先级(Pratt 算法关键)。 + *

+ * 用于决定当前是否需要递归处理更高优先级的中缀操作。 + *

* * @param ctx 当前解析上下文 - * @return 下一个中缀运算符的优先级序号;若无解析器则为 -1 + * @return 下一个中缀运算符的优先级序号;若无注册解析器则返回 -1 */ private int nextPrecedence(ParserContext ctx) { InfixParselet infix = infixes.get(ctx.getTokens().peek().getLexeme()); 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 61f4dab..e47a5aa 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 @@ -9,26 +9,30 @@ package org.jcnc.snow.compiler.parser.expression; */ public enum Precedence { - /** - * 最低优先级,通常用于整个表达式解析的起始入口。 - */ + /** 最低优先级,通常用于整个表达式解析的起始入口。 */ LOWEST, - /** - * 加法和减法的优先级(例如 +、-)。 - */ + /** 逻辑或(||) */ + OR, + + /** 逻辑与(&&) */ + AND, + + /** 相等/不等(==, !=) */ + EQUALITY, + + /** 大小比较(<, >, <=, >=) */ + COMPARISON, + + /** 加法和减法(+、-) */ SUM, - /** - * 乘法、除法、取模等更高优先级的二元运算符(例如 *、/、%)。 - */ + /** 乘法、除法、取模(*、/、%) */ PRODUCT, /** 一元前缀(-x !x) */ UNARY, - /** - * 函数调用、成员访问等最强绑定(例如 foo()、obj.prop)。 - */ + /** 函数调用、成员访问等最强绑定(foo()、obj.prop) */ CALL } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java b/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java index 5cbcc40..5e0470b 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/statement/DeclarationStatementParser.java @@ -8,39 +8,29 @@ import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.expression.PrattExpressionParser; /** - * {@code DeclarationStatementParser} 类负责解析变量声明语句,是语句级解析器的一部分。 + * {@code DeclarationStatementParser} 负责解析变量声明语句节点。 *

- * 本解析器支持以下两种形式的声明语法: + * 支持以下两种语法结构: *

{@code
  * declare myVar:Integer
  * declare myVar:Integer = 42 + 3
  * }
- * 其中: - *
    - *
  • {@code myVar} 为变量名(必须为标识符类型);
  • - *
  • {@code Integer} 为类型标注(必须为类型标记);
  • - *
  • 可选的初始化表达式由 {@link PrattExpressionParser} 解析;
  • - *
  • 每条声明语句必须以换行符({@code NEWLINE})结束。
  • - *
- * 若语法不满足上述结构,将在解析过程中抛出异常。 + * 解析器能够识别多维数组类型(如 {@code int[]}, {@code string[][]}),并支持可选初始化表达式。 + *

+ * 每个声明语句均要求以换行符结尾,语法不合法时会抛出异常。 + *

*/ public class DeclarationStatementParser implements StatementParser { /** - * 解析一条 {@code declare} 声明语句,并返回对应的抽象语法树节点 {@link DeclarationNode}。 + * 解析一条 {@code declare} 语句,生成对应的抽象语法树节点 {@link DeclarationNode}。 *

- * 解析流程如下: - *

    - *
  1. 匹配关键字 {@code declare};
  2. - *
  3. 读取变量名称(标识符类型);
  4. - *
  5. 读取类型标注(在冒号后,要求为 {@code TYPE} 类型);
  6. - *
  7. 若存在 {@code =},则继续解析其后的表达式作为初始化值;
  8. - *
  9. 最终必须匹配 {@code NEWLINE} 表示语句结束。
  10. - *
- * 若遇到非法语法结构,将触发异常并中断解析过程。 + * 支持类型标注和可选初始化表达式。类型部分自动拼接数组维度(如 int[][])。 + *

* - * @param ctx 当前语法解析上下文,包含词法流、错误信息等。 - * @return 返回一个 {@link DeclarationNode} 节点,表示解析完成的声明语法结构。 + * @param ctx 当前语法解析上下文(包含词法流、错误信息等) + * @return {@link DeclarationNode} 表示声明语句结构 + * @throws RuntimeException 语法不合法时抛出 */ @Override public DeclarationNode parse(ParserContext ctx) { @@ -61,11 +51,19 @@ public class DeclarationStatementParser implements StatementParser { ctx.getTokens().expect(":"); // 获取变量类型(类型标识符) - String type = ctx.getTokens() - .expectType(TokenType.TYPE) - .getLexeme(); + StringBuilder type = new StringBuilder( + ctx.getTokens() + .expectType(TokenType.TYPE) + .getLexeme() + ); - // 可选的初始化表达式,若存在 "=",则解析等号右侧表达式 + // 消费多维数组类型后缀 "[]" + while (ctx.getTokens().match("[")) { + ctx.getTokens().expectType(TokenType.RBRACKET); // 必须配对 + type.append("[]"); // 类型名称拼接 [],如 int[][] 等 + } + + // 可选初始化表达式(=号右侧) ExpressionNode init = null; if (ctx.getTokens().match("=")) { init = new PrattExpressionParser().parse(ctx); @@ -75,6 +73,6 @@ public class DeclarationStatementParser implements StatementParser { ctx.getTokens().expectType(TokenType.NEWLINE); // 返回构建好的声明语法树节点 - return new DeclarationNode(name, type, init, new NodeContext(line, column, file)); + return new DeclarationNode(name, type.toString(), init, new NodeContext(line, column, file)); } } diff --git a/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java b/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java index 7e80851..955baa7 100644 --- a/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java +++ b/src/main/java/org/jcnc/snow/compiler/parser/statement/ExpressionStatementParser.java @@ -51,7 +51,7 @@ public class ExpressionStatementParser implements StatementParser { int column = ts.peek().getCol(); String file = ctx.getSourceName(); - // 赋值语句: IDENTIFIER = expr + // 简单形式: IDENTIFIER = expr if (ts.peek().getType() == TokenType.IDENTIFIER && "=".equals(ts.peek(1).getLexeme())) { String varName = ts.next().getLexeme(); ts.expect("="); @@ -60,9 +60,23 @@ public class ExpressionStatementParser implements StatementParser { return new AssignmentNode(varName, value, new NodeContext(line, column, file)); } + // 尝试解析更通用的左值形式(支持下标): = + ExpressionNode lhs = new PrattExpressionParser().parse(ctx); + if ("=".equals(ts.peek().getLexeme())) { + ts.next(); // consume '=' + ExpressionNode rhs = new PrattExpressionParser().parse(ctx); + ts.expectType(TokenType.NEWLINE); + // 根据左值类型构造具体赋值节点 + if (lhs instanceof org.jcnc.snow.compiler.parser.ast.IdentifierNode id) { + return new AssignmentNode(id.name(), rhs, new NodeContext(line, column, file)); + } else if (lhs instanceof org.jcnc.snow.compiler.parser.ast.IndexExpressionNode idx) { + return new org.jcnc.snow.compiler.parser.ast.IndexAssignmentNode(idx, rhs, new NodeContext(line, column, file)); + } else { + throw new UnexpectedToken("不支持的赋值左值类型: " + lhs.getClass().getSimpleName(), line, column); + } + } // 普通表达式语句 - ExpressionNode expr = new PrattExpressionParser().parse(ctx); ts.expectType(TokenType.NEWLINE); - return new ExpressionStatementNode(expr, new NodeContext(line, column, file)); + return new ExpressionStatementNode(lhs, new NodeContext(line, column, file)); } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java index d61496c..5f0bc30 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/AnalyzerRegistry.java @@ -12,68 +12,64 @@ import java.util.Map; /** * {@code AnalyzerRegistry} 是语义分析器的注册与分发中心。 *

- * 它负责根据 AST 节点的类型,查找并返回相应的 {@link StatementAnalyzer} 或 {@link ExpressionAnalyzer} 实例。 - * 同时支持注册自定义分析器,并在未找到对应表达式分析器时提供默认兜底处理器。 + * 该类维护了 AST 节点类型与相应 {@link StatementAnalyzer}、 + * {@link ExpressionAnalyzer} 实例的映射关系。调用者可以通过节点类型注册自定义的分析器, + * 并在后续通过节点对象高效获取对应分析器,实现语义分析分发。 + *

+ *

+ * 对于表达式分析器的获取({@link #getExpressionAnalyzer(ExpressionNode)}), + * 支持“最近父类匹配”查找机制:若找不到节点的精确类型分析器,则向上递归查找已注册的最近父类类型分析器; + * 若依然未找到,则自动 fallback 到默认的 {@link UnsupportedExpressionAnalyzer},确保分析流程健壮性。 + *

*

- * 主要职责: - *

    - *
  • 支持注册语句和表达式节点类型对应的分析器;
  • - *
  • 在语义分析阶段,根据 AST 节点动态查找对应的分析器;
  • - *
  • 为未注册的表达式类型提供默认处理器 {@link UnsupportedExpressionAnalyzer};
  • - *
  • 不为语句提供默认兜底分析器,未注册类型将返回 {@code null}。
  • - *
*/ public class AnalyzerRegistry { - /** Statement 节点类型 → 对应语义分析器映射表 */ - private final Map, StatementAnalyzer> stmtAnalyzers = new HashMap<>(); - - /** Expression 节点类型 → 对应语义分析器映射表 */ - private final Map, ExpressionAnalyzer> exprAnalyzers = new HashMap<>(); - - /** 默认兜底表达式分析器,用于处理未注册的表达式类型 */ - private final ExpressionAnalyzer defaultUnsupported = - new UnsupportedExpressionAnalyzer<>(); - - // ========================= 注册方法 ========================= + /** + * Statement 节点类型 → 对应语义分析器映射表 + */ + private final Map, StatementAnalyzer> stmtAnalyzers = new HashMap<>(); /** - * 注册一个 {@link StatementAnalyzer} 实例,用于处理指定类型的语句节点。 - * - * @param cls 要注册的语句节点类型(Class 对象) - * @param analyzer 与该类型匹配的分析器实例 - * @param {@link StatementNode} 的具体子类 + * Expression 节点类型 → 对应语义分析器映射表 */ - public void registerStatementAnalyzer( - Class cls, - StatementAnalyzer analyzer - ) { - stmtAnalyzers.put(cls, analyzer); + private final Map, ExpressionAnalyzer> exprAnalyzers = new HashMap<>(); + + /** + * 默认表达式兜底分析器:用于未注册或不能识别的表达式类型 + */ + private final ExpressionAnalyzer defaultUnsupported = new UnsupportedExpressionAnalyzer<>(); + + /** + * 注册 Statement 类型的语义分析器。 + * + * @param clazz AST 语句节点类型(class对象),例如 {@code IfStatementNode.class} + * @param analyzer 针对该节点类型的语义分析器实例 + * @param 语句节点类型参数,必须是 {@link StatementNode} 的子类 + */ + public void registerStatementAnalyzer(Class clazz, StatementAnalyzer analyzer) { + stmtAnalyzers.put(clazz, analyzer); } /** - * 注册一个 {@link ExpressionAnalyzer} 实例,用于处理指定类型的表达式节点。 + * 注册 Expression 类型的语义分析器。 * - * @param cls 要注册的表达式节点类型(Class 对象) - * @param analyzer 与该类型匹配的分析器实例 - * @param {@link ExpressionNode} 的具体子类 + * @param clazz AST 表达式节点类型(class对象),例如 {@code BinaryExprNode.class} + * @param analyzer 针对该节点类型的语义分析器实例 + * @param 表达式节点类型参数,必须是 {@link ExpressionNode} 的子类 */ - public void registerExpressionAnalyzer( - Class cls, - ExpressionAnalyzer analyzer - ) { - exprAnalyzers.put(cls, analyzer); + public void registerExpressionAnalyzer(Class clazz, ExpressionAnalyzer analyzer) { + exprAnalyzers.put(clazz, analyzer); } - // ========================= 获取方法 ========================= - /** - * 根据语句节点的实际类型查找对应的 {@link StatementAnalyzer}。 + * 获取指定语句节点对象的分析器实例。 *

- * 若节点类型未注册,返回 {@code null}。 + * 只支持“精确类型匹配”,即仅当注册过该节点 class 时才返回分析器,否则返回 null。 + *

* - * @param stmt 要分析的语句节点实例 - * @param 语句类型(推断自参数) - * @return 与该节点类型对应的分析器,若未注册则为 {@code null} + * @param stmt 语句节点对象 + * @param 节点类型,需为 {@link StatementNode} 子类 + * @return 匹配的 {@link StatementAnalyzer},若未注册则返回 null */ @SuppressWarnings("unchecked") public StatementAnalyzer getStatementAnalyzer(S stmt) { @@ -81,17 +77,34 @@ public class AnalyzerRegistry { } /** - * 根据表达式节点的实际类型查找对应的 {@link ExpressionAnalyzer}。 + * 获取指定表达式节点对象的分析器实例。 *

- * 若节点类型未注册,返回默认兜底分析器 {@link UnsupportedExpressionAnalyzer}。 + * 首先尝试节点类型的精确匹配;若未注册,向上递归查找其最近的已注册父类类型分析器; + * 若依然未命中,则返回默认的 {@link UnsupportedExpressionAnalyzer},保证分析器始终有返回值。 + *

* - * @param expr 要分析的表达式节点实例 - * @param 表达式类型(推断自参数) - * @return 与该节点类型对应的分析器,或默认兜底分析器 + * @param expr 表达式节点对象 + * @param 节点类型,需为 {@link ExpressionNode} 子类 + * @return 匹配的 {@link ExpressionAnalyzer} 实例;若未注册,则返回兜底分析器 */ @SuppressWarnings("unchecked") public ExpressionAnalyzer getExpressionAnalyzer(E expr) { - return (ExpressionAnalyzer) - exprAnalyzers.getOrDefault(expr.getClass(), defaultUnsupported); + Class cls = expr.getClass(); + // 精确匹配 + ExpressionAnalyzer analyzer = exprAnalyzers.get(cls); + if (analyzer != null) { + return (ExpressionAnalyzer) analyzer; + } + // 向上遍历父类尝试匹配 + Class current = cls.getSuperclass(); + while (current != null && ExpressionNode.class.isAssignableFrom(current)) { + analyzer = exprAnalyzers.get(current); + if (analyzer != null) { + return (ExpressionAnalyzer) analyzer; + } + current = current.getSuperclass(); + } + // fallback + return (ExpressionAnalyzer) defaultUnsupported; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/ArrayLiteralAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/ArrayLiteralAnalyzer.java new file mode 100644 index 0000000..752cc61 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/ArrayLiteralAnalyzer.java @@ -0,0 +1,63 @@ +package org.jcnc.snow.compiler.semantic.analyzers.expression; + +import org.jcnc.snow.compiler.parser.ast.ArrayLiteralNode; +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; +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.ArrayType; +import org.jcnc.snow.compiler.semantic.type.BuiltinType; +import org.jcnc.snow.compiler.semantic.type.Type; + +import java.util.List; + +/** + * {@code ArrayLiteralAnalyzer} 用于分析数组字面量表达式(ArrayLiteralNode)。 + *

+ * 主要负责: + *

    + *
  • 推断数组字面量的元素类型,生成对应的 {@link ArrayType}。
  • + *
  • 检查所有元素类型是否一致,不一致时报错并降级为 {@code int[]}。
  • + *
  • 若数组为空,默认类型为 {@code int[]},并产生相应语义错误。
  • + *
+ *

+ */ +public class ArrayLiteralAnalyzer implements ExpressionAnalyzer { + /** + * 分析数组字面量表达式,推断类型,并检查元素类型一致性。 + * + * @param ctx 全局/当前语义分析上下文 + * @param mi 所属模块信息 + * @param fn 当前分析的函数节点 + * @param locals 当前作用域符号表 + * @param expr 当前数组字面量节点 + * @return 推断出的数组类型,若类型冲突或无法推断,则为 {@code int[]} + */ + @Override + public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, ArrayLiteralNode expr) { + List elems = expr.elements(); + if (elems.isEmpty()) { + ctx.getErrors().add(new SemanticError(expr, "空数组字面量的类型无法推断,已默认为 int[]")); + return new ArrayType(BuiltinType.INT); + } + // 以第一个元素为基准 + Type first = ctx.getRegistry() + .getExpressionAnalyzer(elems.getFirst()) + .analyze(ctx, mi, fn, locals, elems.getFirst()); + + for (int i = 1; i < elems.size(); i++) { + ExpressionNode e = elems.get(i); + Type t = ctx.getRegistry() + .getExpressionAnalyzer(e) + .analyze(ctx, mi, fn, locals, e); + if (!t.equals(first)) { + ctx.getErrors().add(new SemanticError(e, "数组元素类型不一致: 期望 " + first.name() + ",实际 " + t.name())); + return new ArrayType(BuiltinType.INT); + } + } + return new ArrayType(first); + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IndexExpressionAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IndexExpressionAnalyzer.java new file mode 100644 index 0000000..a0a890e --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IndexExpressionAnalyzer.java @@ -0,0 +1,59 @@ +package org.jcnc.snow.compiler.semantic.analyzers.expression; + +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.IndexExpressionNode; +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.ArrayType; +import org.jcnc.snow.compiler.semantic.type.BuiltinType; +import org.jcnc.snow.compiler.semantic.type.Type; + +/** + * {@code IndexExpressionAnalyzer} 负责下标访问表达式(如 {@code array[index]})的类型推断和检查。 + *

+ * 语义规则: + *

    + *
  1. 先分析 array 的类型,必须为 {@link ArrayType}
  2. + *
  3. 再分析 index 的类型,必须为数值类型
  4. + *
  5. 表达式结果类型为 array 的元素类型;若 array 非数组类型,报错并返回 int 类型兜底
  6. + *
+ */ +public class IndexExpressionAnalyzer implements ExpressionAnalyzer { + + /** + * 分析并类型检查下标访问表达式。 + * + * @param ctx 当前语义分析上下文 + * @param mi 当前模块信息 + * @param fn 当前函数节点 + * @param locals 当前作用域符号表 + * @param node 要分析的下标访问表达式节点 + * @return 若 array 合法,则返回元素类型;否则兜底为 int 类型 + */ + @Override + public Type analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, IndexExpressionNode node) { + // 先分析被下标访问的数组表达式 + Type arrType = ctx.getRegistry() + .getExpressionAnalyzer(node.array()) + .analyze(ctx, mi, fn, locals, node.array()); + + if (arrType instanceof ArrayType(Type elementType)) { + // 再分析下标表达式 + Type idxType = ctx.getRegistry() + .getExpressionAnalyzer(node.index()) + .analyze(ctx, mi, fn, locals, node.index()); + + if (!idxType.isNumeric()) { + ctx.getErrors().add(new SemanticError(node, "数组下标必须是数值类型")); + } + // array[index] 的类型是数组元素类型 + return elementType; + } + + ctx.getErrors().add(new SemanticError(node, "仅数组类型支持下标访问")); + return BuiltinType.INT; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IndexAssignmentAnalyzer.java b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IndexAssignmentAnalyzer.java new file mode 100644 index 0000000..4990c79 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IndexAssignmentAnalyzer.java @@ -0,0 +1,72 @@ +package org.jcnc.snow.compiler.semantic.analyzers.statement; + +import org.jcnc.snow.compiler.parser.ast.FunctionNode; +import org.jcnc.snow.compiler.parser.ast.IndexAssignmentNode; +import org.jcnc.snow.compiler.parser.ast.IndexExpressionNode; +import org.jcnc.snow.compiler.semantic.analyzers.base.StatementAnalyzer; +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.ArrayType; +import org.jcnc.snow.compiler.semantic.type.Type; + +/** + * {@code IndexAssignmentAnalyzer} 用于分析和类型检查数组下标赋值语句。 + *

+ * 主要职责: + *

    + *
  • 检查左侧是否为数组类型
  • + *
  • 检查下标是否为数值类型
  • + *
  • 检查右值(被赋值表达式)类型是否与数组元素类型兼容
  • + *
  • 如有类型不符,将语义错误写入 {@link Context#getErrors()}
  • + *
+ */ +public class IndexAssignmentAnalyzer implements StatementAnalyzer { + + /** + * 分析并类型检查数组下标赋值语句,如 {@code arr[i] = v}。 + *
    + *
  • 左侧必须是数组类型
  • + *
  • 下标必须为数值类型
  • + *
  • 右侧赋值类型需与数组元素类型一致
  • + *
  • 任何不合法情况均会产生 {@link SemanticError}
  • + *
+ * + * @param ctx 当前语义分析上下文 + * @param mi 当前模块信息 + * @param fn 所属函数节点 + * @param locals 局部符号表 + * @param node 赋值语句 AST 节点 + */ + @Override + public void analyze(Context ctx, ModuleInfo mi, FunctionNode fn, SymbolTable locals, IndexAssignmentNode node) { + IndexExpressionNode target = node.target(); + + // 分析左侧类型(必须为数组类型) + Type arrT = ctx.getRegistry() + .getExpressionAnalyzer(target.array()) + .analyze(ctx, mi, fn, locals, target.array()); + if (!(arrT instanceof ArrayType(Type elementType))) { + ctx.getErrors().add(new SemanticError(node, "左侧不是数组,无法进行下标赋值")); + return; + } + + // 分析下标类型(必须为数值类型) + Type idxT = ctx.getRegistry() + .getExpressionAnalyzer(target.index()) + .analyze(ctx, mi, fn, locals, target.index()); + if (!idxT.isNumeric()) { + ctx.getErrors().add(new SemanticError(node, "数组下标必须是数值类型")); + } + + // 分析右值类型(必须与元素类型一致) + Type rhsT = ctx.getRegistry() + .getExpressionAnalyzer(node.value()) + .analyze(ctx, mi, fn, locals, node.value()); + if (!rhsT.equals(elementType)) { + ctx.getErrors().add(new SemanticError(node, + "数组元素赋值类型不匹配: 期望 " + elementType.name() + ",实际 " + rhsT.name())); + } + } +} 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 0e5e318..f0b301a 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 @@ -57,6 +57,11 @@ public final class AnalyzerRegistrar { registry.registerExpressionAnalyzer(CallExpressionNode.class, new CallExpressionAnalyzer()); registry.registerExpressionAnalyzer(BinaryExpressionNode.class, new BinaryExpressionAnalyzer()); + registry.registerExpressionAnalyzer(ArrayLiteralNode.class, new ArrayLiteralAnalyzer()); + registry.registerExpressionAnalyzer(IndexExpressionNode.class,new IndexExpressionAnalyzer()); // ★ 关键行 + registry.registerStatementAnalyzer(IndexAssignmentNode.class, new IndexAssignmentAnalyzer()); + + // ---------- 注册一元表达式分析器 ---------- registry.registerExpressionAnalyzer(UnaryExpressionNode.class, new UnaryExpressionAnalyzer()); diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java b/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java index 881f083..6a69b67 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/core/Context.java @@ -2,42 +2,54 @@ package org.jcnc.snow.compiler.semantic.core; import org.jcnc.snow.compiler.semantic.analyzers.AnalyzerRegistry; import org.jcnc.snow.compiler.semantic.error.SemanticError; +import org.jcnc.snow.compiler.semantic.type.ArrayType; import org.jcnc.snow.compiler.semantic.type.Type; import java.util.List; import java.util.Map; /** - * {@code Context} 表示语义分析阶段的共享上下文环境。 + * {@code Context} 表示语义分析阶段的全局上下文环境。 *

- * 它贯穿整个语义分析流程,用于维护并提供以下核心服务: + * 该类贯穿整个语义分析流程,集中管理以下内容: *

    - *
  • 模块信息管理: 包含所有已加载模块(源模块与内置模块);
  • - *
  • 错误收集: 集中存储语义分析期间产生的 {@link SemanticError};
  • - *
  • 日志控制: 支持按需输出详细调试日志;
  • - *
  • 分析器调度: 通过 {@link AnalyzerRegistry} 管理语句/表达式的分析器分发。
  • + *
  • 模块信息管理(所有已加载模块,包括源模块和内置模块);
  • + *
  • 错误收集(集中存储语义分析期间产生的 {@link SemanticError});
  • + *
  • 日志输出控制(可选,支持调试信息);
  • + *
  • 分析器调度(通过 {@link AnalyzerRegistry} 分发对应分析器);
  • *
+ *

+ * 提供便捷的 getter 方法和类型解析工具方法。 + *

*/ public class Context { - /** 模块表: 模块名 → {@link ModuleInfo},用于模块查找与跨模块引用 */ + /** + * 模块表:模块名 → {@link ModuleInfo},用于模块查找与跨模块引用。 + */ private final Map modules; - /** 错误列表: 语义分析过程中收集的所有 {@link SemanticError} */ + /** + * 错误列表:语义分析过程中收集的所有 {@link SemanticError}。 + */ private final List errors; - /** 日志开关: 若为 true,将启用 {@link #log(String)} 输出日志信息 */ + /** + * 日志开关:若为 true,将启用 {@link #log(String)} 输出日志信息。 + */ private final boolean verbose; - /** 语义分析器注册表: 用于按节点类型动态调度分析器 */ + /** + * 语义分析器注册表:用于按节点类型动态调度分析器。 + */ private final AnalyzerRegistry registry; /** * 构造语义分析上下文对象。 * - * @param modules 已注册的模块信息集合 - * @param errors 错误收集器,分析器将所有语义错误写入此列表 - * @param verbose 是否启用调试日志输出 - * @param registry 分析器注册表,提供类型到分析器的映射与调度能力 + * @param modules 已注册的模块信息集合 + * @param errors 错误收集器,分析器将所有语义错误写入此列表 + * @param verbose 是否启用调试日志输出 + * @param registry 分析器注册表,提供类型到分析器的映射与调度能力 */ public Context(Map modules, List errors, @@ -54,13 +66,17 @@ public class Context { /** * 获取所有模块信息映射表。 * - * @return 模块名 → 模块信息 {@link ModuleInfo} 的映射 + * @return 模块名到模块信息({@link ModuleInfo})的映射表 */ public Map getModules() { return modules; } - /** @return 模块信息(快捷方式) */ + /** + * 模块信息 getter(快捷方式)。 + * + * @return 模块名到模块信息({@link ModuleInfo})的映射表 + */ public Map modules() { return modules; } @@ -76,7 +92,11 @@ public class Context { return errors; } - /** @return 错误列表(快捷方式) */ + /** + * 错误列表 getter(快捷方式)。 + * + * @return 错误列表 + */ public List errors() { return errors; } @@ -92,7 +112,11 @@ public class Context { return registry; } - /** @return 注册表(快捷方式) */ + /** + * 注册表 getter(快捷方式)。 + * + * @return {@link AnalyzerRegistry} 实例 + */ public AnalyzerRegistry registry() { return registry; } @@ -113,15 +137,26 @@ public class Context { // ------------------ 工具函数 ------------------ /** - * 将类型名称字符串解析为对应的内置 {@link Type} 实例。 + * 将类型名称字符串解析为对应的类型实例(支持多维数组后缀)。 *

- * 若类型在 {@link BuiltinTypeRegistry#BUILTIN_TYPES} 中存在,则返回对应类型; - * 否则返回 {@code null},调用方可据此决定是否降级处理。 + * 例如,"int" → int 类型,"int[][]" → 二维整型数组类型。 + *

* - * @param name 类型名称(如 "int", "float", "void", "string" 等) - * @return 匹配的 {@link Type},若无匹配项则返回 {@code null} + * @param name 类型名称(支持 "[]" 数组后缀) + * @return 对应的 {@link Type} 实例,若无法识别返回 null */ public Type parseType(String name) { - return BuiltinTypeRegistry.BUILTIN_TYPES.get(name); + int dims = 0; + while (name.endsWith("[]")) { + name = name.substring(0, name.length() - 2); + dims++; + } + Type base = BuiltinTypeRegistry.BUILTIN_TYPES.get(name); + if (base == null) return null; + Type t = base; + for (int i = 0; i < dims; i++) { + t = new ArrayType(t); + } + return t; } } diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/type/ArrayType.java b/src/main/java/org/jcnc/snow/compiler/semantic/type/ArrayType.java new file mode 100644 index 0000000..9b7bd41 --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/semantic/type/ArrayType.java @@ -0,0 +1,73 @@ +package org.jcnc.snow.compiler.semantic.type; + +import java.util.Objects; + +/** + * {@code ArrayType} 表示数组类型,每个数组类型包含其元素类型。 + *

+ * 例如,int[]、string[] 等均可用本类表示。内部通过 {@link #elementType()} 字段保存元素类型。 + *

+ */ +public record ArrayType( + /* + 数组元素的类型。 + */ + Type elementType +) implements Type { + + /** + * 判断当前数组类型能否与另一类型兼容(主要用于类型检查)。 + *

+ * 只有当 {@code other} 也是 ArrayType,且元素类型兼容时返回 true。 + *

+ * + * @param other 需判断的类型 + * @return 类型兼容性结果 + */ + @Override + public boolean isCompatible(Type other) { + if (!(other instanceof ArrayType(Type type))) return false; + return elementType.isCompatible(type); + } + + /** + * 数组类型不是数值类型,直接调用父接口默认实现。 + * + * @return 总为 false + */ + @Override + public boolean isNumeric() { + return Type.super.isNumeric(); + } + + /** + * 判断两个 ArrayType 是否等价(元素类型完全一致即视为等价)。 + * + * @param o 比较对象 + * @return 是否等价 + */ + @Override + public boolean equals(Object o) { + return o instanceof ArrayType(Type type) && Objects.equals(elementType, type); + } + + /** + * 返回数组类型的字符串描述,如 "int[]"。 + * + * @return 类型名称 + */ + @Override + public String toString() { + return name(); + } + + /** + * 获取数组类型的名称描述,如 "int[]"。 + * + * @return 类型名称 + */ + @Override + public String name() { + return elementType.name() + "[]"; + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java b/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java index fc7798f..209e578 100644 --- a/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java +++ b/src/main/java/org/jcnc/snow/compiler/semantic/type/Type.java @@ -47,4 +47,6 @@ public interface Type { int ia = order.indexOf(a), ib = order.indexOf(b); return order.get(Math.max(ia, ib)); } + /** 类型名字符串(如 int、double[]) */ + default String name() { return toString(); } } diff --git a/src/main/java/org/jcnc/snow/vm/commands/ref/control/RPushCommand.java b/src/main/java/org/jcnc/snow/vm/commands/ref/control/RPushCommand.java index 1386018..6c12299 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/ref/control/RPushCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/ref/control/RPushCommand.java @@ -5,59 +5,287 @@ import org.jcnc.snow.vm.module.CallStack; import org.jcnc.snow.vm.module.LocalVariableStore; import org.jcnc.snow.vm.module.OperandStack; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** - * The {@code RPushCommand} class implements the {@link Command} interface and represents the - * reference push instruction ({@code R_PUSH}) in the virtual machine. + * The {@code RPushCommand} class implements the {@link Command} interface + * and represents the "reference push" instruction ({@code R_PUSH}) in the virtual machine. * *

- * This instruction pushes a reference object, such as a String literal, onto the operand stack. + * This instruction pushes a reference-type value onto the operand stack. + * The input is parsed from the textual instruction form, which can represent: + *

    + *
  • String literals
  • + *
  • Array literals (e.g., {@code [1, 2, 3]}), including nested arrays
  • + *
*

* - *

Instruction format: {@code R_PUSH }

- *
    - *
  • {@code }: The reference value (e.g., string) to be pushed onto the stack. - * If the literal contains spaces, all parts after {@code R_PUSH} are joined into a single string.
  • - *
- * - *

Behavior:

- *
    - *
  • Checks that the instruction has at least one parameter after the operator.
  • - *
  • Concatenates all parameters after {@code R_PUSH} into a single string (separated by spaces).
  • - *
  • Pushes the resulting string as a reference object onto the operand stack.
  • - *
  • Increments the program counter to the next instruction.
  • - *
  • Throws an {@code IllegalStateException} if the instruction is missing required parameters.
  • - *
+ *

+ * For array literals, a nested list structure is constructed. In this implementation, + * array literals are pushed as mutable {@link java.util.ArrayList} structures, + * so that subsequent system calls such as {@code ARR_SET} can modify elements in-place. + *

*/ -public final class RPushCommand implements Command { +public class RPushCommand implements Command { /** - * Executes the {@code R_PUSH} instruction, pushing a reference (such as a string literal) - * onto the operand stack. + * Executes the R_PUSH command. * - * @param parts The instruction parameters. {@code parts[0]} is the operator ("R_PUSH"), - * {@code parts[1..]} are the parts of the literal to be concatenated and pushed. - * @param pc The current program counter value, indicating the instruction address being executed. - * @param stack The operand stack manager. The literal will be pushed onto this stack. - * @param lvs The local variable store. (Not used in this instruction.) - * @param cs The call stack manager. (Not used in this instruction.) - * @return The next program counter value ({@code pc + 1}), pointing to the next instruction. - * @throws IllegalStateException if the instruction is missing required parameters. + * @param parts The parts of the instruction, where {@code parts[1..n]} are concatenated as the literal. + * @param pc The current program counter. + * @param stack The operand stack where the result will be pushed. + * @param local The local variable store (unused in this instruction). + * @param callStack The call stack (unused in this instruction). + * @return The new program counter (typically {@code pc+1}). + * @throws IllegalStateException if no literal parameter is provided. */ @Override - public int execute(String[] parts, int pc, - OperandStack stack, - LocalVariableStore lvs, - CallStack cs) { - + public int execute(String[] parts, int pc, OperandStack stack, LocalVariableStore local, CallStack callStack) { if (parts.length < 2) throw new IllegalStateException("R_PUSH missing parameter"); + // Join all arguments into a complete literal string StringBuilder sb = new StringBuilder(); for (int i = 1; i < parts.length; i++) { if (i > 1) sb.append(' '); sb.append(parts[i]); } - stack.push(sb.toString()); + String literal = sb.toString().trim(); + + // Check if this is an array literal + if (literal.startsWith("[") && literal.endsWith("]")) { + Object parsed = parseValue(new Cursor(literal)); + if (!(parsed instanceof List list)) { + // Should not happen in theory; safety fallback + stack.push(parsed); + } else { + // Push a deep-mutable copy so ARR_SET can modify elements in-place + stack.push(deepMutable(list)); + } + } else { + // Regular string, push as-is + stack.push(literal); + } return pc + 1; } + + /** + * A simple string cursor, supporting index increment and character reading, for use by the parser. + */ + static class Cursor { + final String s; + int i; + + /** + * Constructs a new {@code Cursor} for the given string. + * + * @param s The string to parse. + */ + Cursor(String s) { + this.s = s; + this.i = 0; + } + + /** + * Advances the cursor by one character. + */ + void skip() { + i++; + } + + /** + * @return {@code true} if the cursor has reached the end of the string. + */ + boolean end() { + return i >= s.length(); + } + + /** + * Gets the character at the current cursor position. + * + * @return current character + * @throws StringIndexOutOfBoundsException if at end of string + */ + char ch() { + return s.charAt(i); + } + } + + /** + * Parses a value from the input string at the current cursor position. + * This can be an array literal, a quoted string, or a simple atom (number, word). + * + * @param c The cursor for parsing. + * @return The parsed value (could be List, String, Number). + */ + Object parseValue(Cursor c) { + skipWs(c); + if (c.end()) return ""; + char ch = c.ch(); + if (ch == '[') return parseArray(c); + if (ch == '\"') return parseQuoted(c); + return parseAtom(c); + } + + /** + * Skips whitespace characters in the input string. + * + * @param c The cursor to advance. + */ + private static void skipWs(Cursor c) { + while (!c.end()) { + char ch = c.ch(); + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') c.skip(); + else break; + } + } + + /** + * Parses an array literal from the input, including nested arrays. + * + * @param c The cursor (positioned at '[' at entry). + * @return A List representing the parsed array. + */ + private Object parseArray(Cursor c) { + // assumes current char is '[' + c.skip(); // skip '[' + List out = new ArrayList<>(); + skipWs(c); + while (!c.end()) { + if (c.ch() == ']') { + c.skip(); // skip ']' + break; + } + Object v = parseValue(c); + out.add(v); + skipWs(c); + if (!c.end() && c.ch() == ',') { + c.skip(); + skipWs(c); + } + } + return out; + } + + /** + * Parses a quoted string literal, handling escape characters. + * + * @param c The cursor (positioned at '"' at entry). + * @return The parsed string value. + */ + private static String parseQuoted(Cursor c) { + // assumes current char is '"' + c.skip(); // skip opening quote + StringBuilder sb = new StringBuilder(); + while (!c.end()) { + char ch = c.ch(); + c.skip(); + if (ch == '\\') { + if (c.end()) break; + char esc = c.ch(); + c.skip(); + switch (esc) { + case 'n' -> sb.append('\n'); + case 'r' -> sb.append('\r'); + case 't' -> sb.append('\t'); + case '\"' -> sb.append('\"'); + case '\\' -> sb.append('\\'); + default -> sb.append(esc); + } + } else if (ch == '\"') { + break; + } else { + sb.append(ch); + } + } + return sb.toString(); + } + + /** + * Parses an atom (number, hexadecimal, binary, or plain string token). + * + * @param c The cursor. + * @return An Integer, Double, or String, depending on the content. + */ + private static Object parseAtom(Cursor c) { + StringBuilder sb = new StringBuilder(); + while (!c.end()) { + char ch = c.ch(); + if (ch == ',' || ch == ']' || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') break; + sb.append(ch); + c.skip(); + } + String token = sb.toString(); + // try number + try { + if (token.startsWith("0x") || token.startsWith("0X")) { + return Integer.parseInt(token.substring(2), 16); + } + if (token.startsWith("0b") || token.startsWith("0B")) { + return Integer.parseInt(token.substring(2), 2); + } + if (token.contains(".")) { + return Double.parseDouble(token); + } + return Integer.parseInt(token); + } catch (NumberFormatException e) { + // fallback as string + return token; + } + } + + // ---------------------- helpers for immutability/mutability ---------------------- + + /** + * Recursively creates an unmodifiable copy of a list, with all nested lists also unmodifiable. + * + * @param l The list to make unmodifiable. + * @return An unmodifiable deep copy of the list. + */ + List deepUnmodifiable(List l) { + List out = new ArrayList<>(l.size()); + for (Object v : l) out.add(deepUnmodifiableObject(v)); + return Collections.unmodifiableList(out); + } + + /** + * Helper method for {@link #deepUnmodifiable(List)}. Recursively processes each element. + * + * @param v The object to process. + * @return Unmodifiable list if input is a list, otherwise the value itself. + */ + Object deepUnmodifiableObject(Object v) { + if (v instanceof List l) { + return deepUnmodifiable(l); + } + return v; + } + + /** + * Create a deep mutable copy of a nested List structure, preserving element values. + * Nested lists are turned into {@link java.util.ArrayList} so they can be modified by ARR_SET. + * + * @param l The source list. + * @return Deep mutable copy of the list. + */ + private static java.util.List deepMutable(java.util.List l) { + java.util.List out = new java.util.ArrayList<>(l.size()); + for (Object v : l) out.add(deepMutableObject(v)); + return out; + } + + /** + * Helper method for {@link #deepMutable(List)}. Recursively processes each element. + * + * @param v The object to process. + * @return Mutable list if input is a list, otherwise the value itself. + */ + private static Object deepMutableObject(Object v) { + if (v instanceof java.util.List l) { + return deepMutable(l); + } + return v; + } } diff --git a/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java b/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java index 90caf8b..80428fa 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java @@ -46,6 +46,97 @@ import static java.nio.file.StandardOpenOption.*; */ public class SyscallCommand implements Command { + /** + * 根据传入的文件打开标志,构造 NIO {@link OpenOption} 集合。 + *

+ * 本方法负责将底层虚拟机传递的 flags 整数型位域,转换为 Java NIO 标准的文件打开选项集合, + * 以支持文件读、写、创建、截断、追加等多种访问场景。 + * 常用于 SYSCALL 的 OPEN 子命令。 + *

+ * + * @param flags 文件打开模式标志。各标志可组合使用,具体含义请参见虚拟机文档。 + * @return 转换后的 OpenOption 集合,可直接用于 FileChannel.open 等 NIO 方法 + */ + private static Set flagsToOptions(int flags) { + Set opts = new HashSet<>(); + // 如果有写入标志,则添加WRITE,否则默认为READ。 + if ((flags & 0x1) != 0) opts.add(WRITE); + else opts.add(READ); + // 如果包含创建标志,允许创建文件。 + if ((flags & 0x40) != 0) opts.add(CREATE); + // 包含截断标志,打开时清空内容。 + if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING); + // 包含追加标志,文件写入时追加到末尾。 + if ((flags & 0x400) != 0) opts.add(APPEND); + return opts; + } + + /** + * 捕获所有异常并统一处理,操作数栈压入 -1 代表本次系统调用失败。 + *

+ * 本方法是全局错误屏障,任何命令异常都会转换为虚拟机通用的失败信号, + * 保证上层调用逻辑不会被异常打断。实际应用中可拓展错误码机制。 + *

+ * + * @param stack 操作数栈,将失败信号写入此栈 + * @param e 抛出的异常对象,可在调试时输出日志 + */ + private static void pushErr(OperandStack stack, Exception e) { + stack.push(-1); + System.err.println("Syscall exception: " + e); + } + + /** + * 控制台输出通用方法,支持基本类型、字节数组、任意数组、对象等。 + *

+ * 该方法用于 SYSCALL PRINT/PRINTLN,将任意类型对象转为易读字符串输出到标准输出流。 + * 字节数组自动按 UTF-8 解码,其它原生数组按格式化字符串输出。 + *

+ * + * @param obj 待输出的内容,可以为任何类型(如基本类型、byte[]、数组、对象等) + * @param newline 是否自动换行。如果为 true,则在输出后换行;否则直接输出。 + */ + private static void output(Object obj, boolean newline) { + String str; + if (obj == null) { + str = "null"; + } else if (obj instanceof byte[] bytes) { + // 字节数组作为文本输出 + str = new String(bytes); + } else if (obj.getClass().isArray()) { + // 其它数组格式化输出 + str = arrayToString(obj); + } else { + str = obj.toString(); + } + if (newline) System.out.println(str); + else System.out.print(str); + } + + /** + * 将各种原生数组和对象数组转换为可读字符串,便于控制台输出和调试。 + *

+ * 本方法针对 int、long、double、float、short、char、byte、boolean 等所有原生数组类型 + * 以及对象数组都能正确格式化,统一输出格式风格,避免显示为类型 hashCode。 + * 若为不支持的类型,返回通用提示字符串。 + *

+ * + * @param array 任意原生数组或对象数组 + * @return 该数组的可读字符串表示 + */ + private static String arrayToString(Object array) { + if (array instanceof int[] a) return Arrays.toString(a); + if (array instanceof long[] a) return Arrays.toString(a); + if (array instanceof double[] a) return Arrays.toString(a); + if (array instanceof float[] a) return Arrays.toString(a); + if (array instanceof short[] a) return Arrays.toString(a); + if (array instanceof char[] a) return Arrays.toString(a); + if (array instanceof byte[] a) return Arrays.toString(a); + if (array instanceof boolean[] a) return Arrays.toString(a); + if (array instanceof Object[] a) return Arrays.deepToString(a); + return "Unsupported array"; + } + /** * 分发并执行 SYSCALL 子命令,根据子命令类型从操作数栈取出参数、操作底层资源,并将结果压回栈顶。 * @@ -226,6 +317,105 @@ public class SyscallCommand implements Command { sel.close(); } + // 数组元素访问:arr[idx] —— 保留所有类型精度(byte/short/int/long/float/double/boolean/string/ref) + case "ARR_GET" -> { + /* + 执行数组下标访问操作 arr[idx],并将对应元素以真实类型压入操作数栈。 +
    +
  • 支持 List 与任意原生数组类型(int[]、double[] 等);
  • +
  • idx 参数支持 Number/String 类型,自动转 int;
  • +
  • 下标越界将抛出异常,非数组类型将报错;
  • +
  • 返回结果保持类型精度:byte/short/int/long/float/double/boolean/string/object;
  • +
  • boolean 元素以 1/0 压栈,string/引用直接压栈;
  • +
+ + 异常与出错行为: +
    +
  • 索引类型非法、目标非数组/列表,将抛 IllegalArgumentException;
  • +
  • 索引越界,将抛 IndexOutOfBoundsException;
  • +
+ */ + Object idxObj = stack.pop(); + Object arrObj = stack.pop(); + int idx; + if (idxObj instanceof Number n) idx = n.intValue(); + else if (idxObj instanceof String s) idx = Integer.parseInt(s.trim()); + else throw new IllegalArgumentException("ARR_GET: invalid index type " + idxObj); + + Object elem; + if (arrObj instanceof java.util.List list) { + if (idx < 0 || idx >= list.size()) + throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + list.size() + ")"); + elem = list.get(idx); + } else if (arrObj != null && arrObj.getClass().isArray()) { + int len = java.lang.reflect.Array.getLength(arrObj); + if (idx < 0 || idx >= len) + throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + len + ")"); + elem = java.lang.reflect.Array.get(arrObj, idx); + } else { + throw new IllegalArgumentException("ARR_GET: not an array/list: " + arrObj); + } + + // === 按真实类型压栈(byte/short/int/long/float/double/boolean/string/ref)=== + if (elem instanceof Number n) { + if (elem instanceof Double) { + stack.push(n.doubleValue()); + } else if (elem instanceof Float) { + stack.push(n.floatValue()); + } else if (elem instanceof Long) { + stack.push(n.longValue()); + } else if (elem instanceof Integer) { + stack.push(n.intValue()); + } else if (elem instanceof Short) { + stack.push(n.shortValue()); + } else if (elem instanceof Byte) { + stack.push(n.byteValue()); + } else { + stack.push(n.intValue()); // 兜底 + } + } else if (elem instanceof Boolean b) { + stack.push(b ? 1 : 0); + } else { + // string 或其它引用类型,直接返回 + stack.push(elem); + } + } + + case "ARR_SET" -> { + /* + arr[idx] = value + 支持 List 和所有原生数组类型(int[], double[], ...) + 参数顺序:栈顶 value、idx、arr + 不返回值(成功/失败由异常控制) + */ + Object value = stack.pop(); + Object idxObj = stack.pop(); + Object arrObj = stack.pop(); + int idx; + if (idxObj instanceof Number n) idx = n.intValue(); + else if (idxObj instanceof String s) idx = Integer.parseInt(s.trim()); + else throw new IllegalArgumentException("ARR_SET: invalid index type " + idxObj); + + if (arrObj instanceof java.util.List list) { + // 必须是可变 List + @SuppressWarnings("unchecked") + java.util.List mlist = (java.util.List) list; + if (idx < 0 || idx >= mlist.size()) + throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + mlist.size() + ")"); + mlist.set(idx, value); + } else if (arrObj != null && arrObj.getClass().isArray()) { + int len = java.lang.reflect.Array.getLength(arrObj); + if (idx < 0 || idx >= len) + throw new IndexOutOfBoundsException("数组下标越界: " + idx + " (长度 " + len + ")"); + java.lang.reflect.Array.set(arrObj, idx, value); + } else { + throw new IllegalArgumentException("ARR_SET: not an array/list: " + arrObj); + } + // 操作成功,push 0 作为 ok 信号;不需要返回时可省略 + stack.push(0); + } + + // 控制台输出 case "PRINT" -> { Object dataObj = stack.pop(); @@ -246,95 +436,4 @@ public class SyscallCommand implements Command { return pc + 1; } - - /** - * 根据传入的文件打开标志,构造 NIO {@link OpenOption} 集合。 - *

- * 本方法负责将底层虚拟机传递的 flags 整数型位域,转换为 Java NIO 标准的文件打开选项集合, - * 以支持文件读、写、创建、截断、追加等多种访问场景。 - * 常用于 SYSCALL 的 OPEN 子命令。 - *

- * - * @param flags 文件打开模式标志。各标志可组合使用,具体含义请参见虚拟机文档。 - * @return 转换后的 OpenOption 集合,可直接用于 FileChannel.open 等 NIO 方法 - */ - private static Set flagsToOptions(int flags) { - Set opts = new HashSet<>(); - // 如果有写入标志,则添加WRITE,否则默认为READ。 - if ((flags & 0x1) != 0) opts.add(WRITE); - else opts.add(READ); - // 如果包含创建标志,允许创建文件。 - if ((flags & 0x40) != 0) opts.add(CREATE); - // 包含截断标志,打开时清空内容。 - if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING); - // 包含追加标志,文件写入时追加到末尾。 - if ((flags & 0x400) != 0) opts.add(APPEND); - return opts; - } - - /** - * 捕获所有异常并统一处理,操作数栈压入 -1 代表本次系统调用失败。 - *

- * 本方法是全局错误屏障,任何命令异常都会转换为虚拟机通用的失败信号, - * 保证上层调用逻辑不会被异常打断。实际应用中可拓展错误码机制。 - *

- * - * @param stack 操作数栈,将失败信号写入此栈 - * @param e 抛出的异常对象,可在调试时输出日志 - */ - private static void pushErr(OperandStack stack, Exception e) { - stack.push(-1); - System.err.println("Syscall exception: " + e); - } - - /** - * 控制台输出通用方法,支持基本类型、字节数组、任意数组、对象等。 - *

- * 该方法用于 SYSCALL PRINT/PRINTLN,将任意类型对象转为易读字符串输出到标准输出流。 - * 字节数组自动按 UTF-8 解码,其它原生数组按格式化字符串输出。 - *

- * - * @param obj 待输出的内容,可以为任何类型(如基本类型、byte[]、数组、对象等) - * @param newline 是否自动换行。如果为 true,则在输出后换行;否则直接输出。 - */ - private static void output(Object obj, boolean newline) { - String str; - if (obj == null) { - str = "null"; - } else if (obj instanceof byte[] bytes) { - // 字节数组作为文本输出 - str = new String(bytes); - } else if (obj.getClass().isArray()) { - // 其它数组格式化输出 - str = arrayToString(obj); - } else { - str = obj.toString(); - } - if (newline) System.out.println(str); - else System.out.print(str); - } - - /** - * 将各种原生数组和对象数组转换为可读字符串,便于控制台输出和调试。 - *

- * 本方法针对 int、long、double、float、short、char、byte、boolean 等所有原生数组类型 - * 以及对象数组都能正确格式化,统一输出格式风格,避免显示为类型 hashCode。 - * 若为不支持的类型,返回通用提示字符串。 - *

- * - * @param array 任意原生数组或对象数组 - * @return 该数组的可读字符串表示 - */ - private static String arrayToString(Object array) { - if (array instanceof int[] a) return Arrays.toString(a); - if (array instanceof long[] a) return Arrays.toString(a); - if (array instanceof double[] a) return Arrays.toString(a); - if (array instanceof float[] a) return Arrays.toString(a); - if (array instanceof short[] a) return Arrays.toString(a); - if (array instanceof char[] a) return Arrays.toString(a); - if (array instanceof byte[] a) return Arrays.toString(a); - if (array instanceof boolean[] a) return Arrays.toString(a); - if (array instanceof Object[] a) return Arrays.deepToString(a); - return "Unsupported array"; - } } 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 704822b..aa6d227 100644 --- a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java +++ b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java @@ -1,6 +1,5 @@ package org.jcnc.snow.vm.engine; -import org.jcnc.snow.common.Mode; import org.jcnc.snow.vm.execution.CommandExecutionHandler; import org.jcnc.snow.vm.module.*; @@ -51,8 +50,6 @@ public class VirtualMachineEngine { /** * Builds a VM engine with fresh runtime structures. - * - * @param vmMode execution mode (DEBUG / RUN) */ public VirtualMachineEngine() { this.operandStack = new OperandStack();