From 82f4ba1a6e5d361f6a4d4e2aa7b6f0e2054bbca1 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 1 Aug 2025 18:34:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=8F=8A=E6=95=B0=E7=BB=84=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ArrayLiteralNode 表示数组字面量表达式节点 - 实现 ArrayLiteralAnalyzer 进行数组字面量的语义分析 - 添加 ArrayType 表示数组类型,并支持多维数组 - 修改 Context 类,增加对数组类型的支持 - 更新 DeclarationStatementParser,支持多维数组类型的声明 - 在 CallGenerator 中添加对特殊函数 __index_i 的处理,用于数组索引操作 --- .../backend/generator/CallGenerator.java | 102 +++++---- .../backend/generator/LoadConstGenerator.java | 3 +- .../snow/compiler/backend/utils/OpHelper.java | 1 + .../ir/builder/ExpressionBuilder.java | 171 +++++++++++++--- .../compiler/ir/builder/IRBuilderScope.java | 112 ++++++---- .../compiler/ir/builder/StatementBuilder.java | 193 +++++++++++++----- .../lexer/scanners/SymbolTokenScanner.java | 4 +- .../snow/compiler/lexer/token/TokenType.java | 10 +- .../compiler/parser/ast/ArrayLiteralNode.java | 40 ++++ .../parser/ast/IndexAssignmentNode.java | 42 ++++ .../parser/ast/IndexExpressionNode.java | 41 ++++ .../expression/ArrayLiteralParselet.java | 80 ++++++++ .../parser/expression/IndexParselet.java | 51 +++++ .../expression/PrattExpressionParser.java | 103 +++++++--- .../parser/expression/Precedence.java | 28 +-- .../statement/DeclarationStatementParser.java | 52 +++-- .../statement/ExpressionStatementParser.java | 20 +- .../semantic/analyzers/AnalyzerRegistry.java | 119 ++++++----- .../expression/ArrayLiteralAnalyzer.java | 63 ++++++ .../expression/IndexExpressionAnalyzer.java | 59 ++++++ .../statement/IndexAssignmentAnalyzer.java | 72 +++++++ .../semantic/core/AnalyzerRegistrar.java | 5 + .../snow/compiler/semantic/core/Context.java | 83 +++++--- .../compiler/semantic/type/ArrayType.java | 73 +++++++ .../snow/compiler/semantic/type/Type.java | 2 + .../vm/commands/ref/control/RPushCommand.java | 106 +++++++--- .../system/control/SyscallCommand.java | 33 +++ 27 files changed, 1339 insertions(+), 329 deletions(-) create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/ast/ArrayLiteralNode.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/ast/IndexAssignmentNode.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/ast/IndexExpressionNode.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/expression/ArrayLiteralParselet.java create mode 100644 src/main/java/org/jcnc/snow/compiler/parser/expression/IndexParselet.java create mode 100644 src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/ArrayLiteralAnalyzer.java create mode 100644 src/main/java/org/jcnc/snow/compiler/semantic/analyzers/expression/IndexExpressionAnalyzer.java create mode 100644 src/main/java/org/jcnc/snow/compiler/semantic/analyzers/statement/IndexAssignmentAnalyzer.java create mode 100644 src/main/java/org/jcnc/snow/compiler/semantic/type/ArrayType.java 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..15957a1 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 @@ -13,42 +13,39 @@ import org.jcnc.snow.vm.engine.VMOpCode; import java.util.*; /** - * CallGenerator - 将 IR {@code CallInstruction} 生成 VM 指令 - * + * {@code CallGenerator} 负责将 IR 层的 {@link CallInstruction} 生成对应的 VM 层函数调用指令。 *

- * 本类负责将中间表示(IR)中的函数调用指令 {@link CallInstruction} 转换为虚拟机(VM)指令。 - * 支持普通函数调用和特殊的 syscall 指令转换。 - *

- * - *

- * 能力说明: + * 支持: *

+ *

+ * 对于 syscall 子命令,支持常量字符串和字符串寄存器两种来源,并支持寄存器-字符串常量池注册机制。 *

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

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

+ * 字符串常量池:用于绑定虚拟寄存器 id 到字符串值(供 syscall 子命令使用)。 */ private static final Map STRING_CONST_POOL = new HashMap<>(); /** - * 注册字符串常量到虚拟寄存器 - *

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

+ * 注册一个字符串常量,绑定到虚拟寄存器 id。 * - * @param regId 虚拟寄存器 id - * @param value 字符串常量值 + * @param regId 虚拟寄存器 id + * @param value 字符串常量 */ public static void registerStringConst(int regId, String value) { STRING_CONST_POOL.put(regId, value); } /** - * 返回本生成器支持的 IR 指令类型(CallInstruction) + * 返回当前指令生成器支持的 IR 指令类型(即 {@link CallInstruction})。 + * + * @return {@code CallInstruction.class} */ @Override public Class supportedClass() { @@ -56,12 +53,12 @@ public class CallGenerator implements InstructionGenerator { } /** - * 生成 VM 指令的主逻辑 + * 生成 VM 指令序列,实现函数调用/特殊 syscall/数组索引等 IR 指令的转换。 * - * @param ins 当前 IR 指令(函数调用) - * @param out 指令输出构建器 - * @param slotMap IR 虚拟寄存器与物理槽位映射 - * @param currentFn 当前函数名 + * @param ins 当前函数调用 IR 指令 + * @param out VM 指令输出构建器 + * @param slotMap IR 虚拟寄存器 → VM 槽位映射表 + * @param currentFn 当前函数名 */ @Override public void generate(CallInstruction ins, @@ -79,7 +76,7 @@ public class CallGenerator implements InstructionGenerator { } // ---------- 0. 解析 syscall 子命令 ---------- - // 子命令支持 IRConstant(直接字面量)或虚拟寄存器(需已绑定字符串) + // 支持 IRConstant 字面量或虚拟寄存器(需已绑定字符串) String subcmd; IRValue first = args.getFirst(); @@ -88,13 +85,11 @@ public class CallGenerator implements InstructionGenerator { 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 if (first instanceof IRVirtualRegister vr) { // 来自寄存器的字符串 + String s = STRING_CONST_POOL.get(vr.id()); + if (s == null) + throw new IllegalStateException("未找到 syscall 字符串常量绑定: " + vr); + subcmd = s.toUpperCase(Locale.ROOT); } else { throw new IllegalStateException("syscall 第一个参数必须是字符串常量"); } @@ -113,21 +108,54 @@ public class CallGenerator implements InstructionGenerator { return; // syscall 无返回值,直接返回 } + /* ========== 特殊处理内部索引函数:__index_i(arr, idx) ========== */ + if ("__index_i".equals(ins.getFunctionName())) { + // 加载参数(arr 为引用类型,idx 为整型) + if (ins.getArguments().size() != 2) { + throw new IllegalStateException("__index_i 需要两个参数(arr, idx)"); + } + IRVirtualRegister arr = (IRVirtualRegister) ins.getArguments().get(0); + IRVirtualRegister idx = (IRVirtualRegister) ins.getArguments().get(1); + + int arrSlot = slotMap.get(arr); + int idxSlot = slotMap.get(idx); + + char arrT = out.getSlotType(arrSlot); + if (arrT == '\0') arrT = 'R'; // 默认为引用类型 + out.emit(OpHelper.opcode(arrT + "_LOAD") + " " + arrSlot); + + char idxT = out.getSlotType(idxSlot); + if (idxT == '\0') idxT = 'I'; // 默认为整型 + out.emit(OpHelper.opcode(idxT + "_LOAD") + " " + idxSlot); + + // 调用 SYSCALL ARR_GET,让 VM 取出数组元素并压回栈顶 + out.emit(VMOpCode.SYSCALL + " " + "ARR_GET"); + + // 取回返回值并保存(当前仅支持 int 元素) + int destSlot = slotMap.get(ins.getDest()); + out.emit(OpHelper.opcode("I_STORE") + " " + destSlot); + out.setSlotType(destSlot, 'I'); + return; + } + /* ========== 普通函数调用 ========== */ // ---------- 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'; + // 简化:根据第一个参数类型推断返回类型,或者通过全局表拿到返回类型 + String ret = GlobalFunctionTable.getReturnType(ins.getFunctionName()); + if (ret != null) { + retType = Character.toUpperCase(ret.charAt(0)); + } } - // ---------- 2. 加载全部实参 ---------- - for (var arg : ins.getArguments()) { - int slotId = slotMap.get((IRVirtualRegister) arg); + // ---------- 2. 压栈所有参数 ---------- + for (IRValue arg : ins.getArguments()) { + IRVirtualRegister vr = (IRVirtualRegister) arg; + int slotId = slotMap.get(vr); char t = out.getSlotType(slotId); - if (t == '\0') t = 'I'; // 默认整型 + if (t == '\0') t = 'I'; out.emit(OpHelper.opcode(t + "_LOAD") + " " + slotId); } 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..28348a3 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 @@ -63,7 +63,8 @@ public class LoadConstGenerator implements InstructionGenerator 'F'; // 单精度 case Boolean _ -> 'I'; // 布尔类型用 I 处理 case String _ -> 'R'; // 字符串常量 - case null, default -> // 其它类型异常 + case java.util.List _ -> 'R'; // 引用类型(如数组等) + case null, default -> throw new IllegalStateException("未知的常量类型: " + (value != null ? value.getClass() : null)); }; 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..4a5d525 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; @@ -14,37 +15,26 @@ import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode; import java.util.*; /** - * ExpressionBuilder - 表达式 → IR 构建器 - * + * {@code ExpressionBuilder} 表达式 → IR 构建器。 *

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

- * - *

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

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

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

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

- * - * @param expr 任意 AST 表达式节点 - * @return 存储该表达式结果的虚拟寄存器 - * @throws IllegalStateException 遇到不支持的表达式类型或未定义标识符 + * @param expr 要生成 IR 的表达式节点 + * @return 存储表达式值的虚拟寄存器 + * @throws IllegalStateException 不支持的表达式类型或未定义标识符 */ public IRVirtualRegister build(ExpressionNode expr) { return switch (expr) { @@ -78,14 +68,20 @@ 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) { @@ -103,8 +99,8 @@ public record ExpressionBuilder(IRContext ctx) { case BoolLiteralNode b -> InstructionFactory.loadConstInto( ctx, dest, new IRConstant(b.getValue() ? 1 : 0)); - - // 标识符:查表获取原寄存器,然后 move 到目标寄存器 + case ArrayLiteralNode arr -> + InstructionFactory.loadConstInto(ctx, dest, buildArrayConstant(arr)); case IdentifierNode id -> { IRVirtualRegister src = ctx.getScope().lookup(id.name()); if (src == null) @@ -114,8 +110,10 @@ public record ExpressionBuilder(IRContext ctx) { // 二元表达式:递归生成并写入目标寄存器 case BinaryExpressionNode bin -> buildBinaryInto(bin, dest); - - // 其它复杂情况:先 build 到新寄存器,再 move 到目标寄存器 + case IndexExpressionNode idx -> { + IRVirtualRegister tmp = buildIndex(idx); + InstructionFactory.move(ctx, tmp, dest); + } default -> { IRVirtualRegister tmp = build(node); InstructionFactory.move(ctx, tmp, dest); @@ -123,7 +121,79 @@ public record ExpressionBuilder(IRContext ctx) { } } - /* ───────────────── 具体表达式类型 ───────────────── */ + /** + * 下标访问表达式处理。支持编译期常量折叠(数组和下标均为常量时直接求值), + * 否则生成运行时调用 __index_i(由 VM 降级为 ARR_GET)。 + * + * @param node 下标访问表达式 + * @return 存储结果的虚拟寄存器 + */ + private IRVirtualRegister buildIndex(IndexExpressionNode node) { + 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; + } + IRVirtualRegister arrReg = build(node.array()); + IRVirtualRegister idxReg = build(node.index()); + IRVirtualRegister dest = ctx.newRegister(); + List argv = new ArrayList<>(); + argv.add(arrReg); + argv.add(idxReg); + ctx.addInstruction(new CallInstruction(dest, "__index_i", argv)); + return dest; + } + + /** + * 尝试将表达式折叠为编译期常量(支持嵌套)。 + * 支持数字、字符串、布尔、数组、常量标识符。 + * + * @param expr 要折叠的表达式节点 + * @return 常量对象(如数字、字符串、List),否则返回 null + */ + private Object tryFoldConst(ExpressionNode expr) { + if (expr == null) return null; + if (expr instanceof NumberLiteralNode n) { + String s = n.value(); + try { + if (s.contains(".") || s.contains("e") || s.contains("E")) { + return Double.parseDouble(s); + } + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return null; + } + } + if (expr instanceof StringLiteralNode s) { + return s.value(); + } + if (expr instanceof BoolLiteralNode b) { + return b.getValue() ? 1 : 0; + } + 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); + } + return java.util.List.copyOf(list); + } + if (expr instanceof IdentifierNode id) { + Object v = null; + try { + v = ctx.getScope().getConstValue(id.name()); + } catch (Throwable ignored) {} + return v; + } + return null; + } /** * 一元表达式构建 @@ -285,4 +355,41 @@ public record ExpressionBuilder(IRContext ctx) { ctx.addInstruction(new LoadConstInstruction(r, c)); return r; } + + /** + * 构建数组字面量表达式(元素均为常量时)。 + * + * @param arr 数组字面量节点 + * @return 存储该数组的寄存器 + */ + private IRVirtualRegister buildArrayLiteral(ArrayLiteralNode arr) { + IRConstant c = buildArrayConstant(arr); + IRVirtualRegister r = ctx.newRegister(); + ctx.addInstruction(new LoadConstInstruction(r, c)); + return r; + } + + /** + * 构建数组常量(所有元素均为数字、字符串或布尔常量)。 + * + * @param arr 数组字面量节点 + * @return 数组 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()); + case BoolLiteralNode b -> list.add(b.getValue() ? 1 : 0); + default -> throw new IllegalStateException( + "暂不支持含非常量元素的数组字面量: " + e.getClass().getSimpleName()); + } + } + 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..6fd26a7 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,77 +34,73 @@ 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. 清除变量声明 - ctx.clearVarType(); + // 赋值时尝试记录/清除常量 + try { + Object constVal = tryFoldConst(rhs); + if (constVal != null) + ctx.getScope().setConstValue(var, constVal); + else + ctx.getScope().clearConstValue(var); + } catch (Throwable ignored) { + } + ctx.clearVarType(); return; } if (stmt instanceof DeclarationNode decl) { @@ -113,16 +119,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 +170,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 +186,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 +236,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 +261,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 +290,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..3223860 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 @@ -6,42 +6,82 @@ import org.jcnc.snow.vm.module.LocalVariableStore; import org.jcnc.snow.vm.module.OperandStack; /** - * 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 value—typically a string literal or an array literal—onto the operand stack. *

* - *

Instruction format: {@code R_PUSH }

+ *

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.
  • + *
  • {@code }: The reference value (e.g., string, boolean, integer, floating-point, or array literal) + * to be pushed onto the stack. If the literal contains spaces, all arguments after {@code R_PUSH} are joined with spaces.
  • *
* - *

Behavior:

+ *

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.
  • + *
  • Ensures that at least one parameter is provided after the operator; otherwise, throws {@code IllegalStateException}.
  • + *
  • Concatenates all parameters after {@code R_PUSH} into a single string, separated by spaces.
  • + *
  • If the resulting string is an array literal (i.e., surrounded by square brackets), splits and parses its elements using {@link #getObject(String)} for primitive type support.
  • + *
  • Otherwise, pushes the literal as a string reference onto the operand stack.
  • + *
  • Increments the program counter to advance to the next instruction.
  • *
+ * + *

+ * Supported element types in array literals include integers, floating-point numbers, booleans, + * and quoted strings (surrounded by double quotes). + *

*/ public final class RPushCommand implements Command { /** - * Executes the {@code R_PUSH} instruction, pushing a reference (such as a string literal) - * onto the operand stack. + * Parses a string element into its corresponding Java object. + *
    + *
  • If enclosed in double quotes, treats as a string literal (quotes are removed).
  • + *
  • If "true" or "false" (case-insensitive), returns 1 or 0 respectively (as integer representation).
  • + *
  • If numeric (integer or floating-point), parses accordingly.
  • + *
  • Otherwise, returns the string as-is.
  • + *
* - * @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 e The string to parse. + * @return The parsed object (String, Integer, Double, or Integer for boolean). + */ + private static Object getObject(String e) { + String x = e.trim(); + Object v; + if (x.startsWith("\"") && x.endsWith("\"") && x.length() >= 2) { + // String literal (remove surrounding double quotes) + v = x.substring(1, x.length() - 1); + } else if ("true".equalsIgnoreCase(x) || "false".equalsIgnoreCase(x)) { + // Boolean value: true → 1, false → 0 + v = Boolean.parseBoolean(x) ? 1 : 0; + } else { + try { + // Attempt to parse as floating-point or integer number + if (x.contains(".") || x.contains("e") || x.contains("E")) { + v = Double.parseDouble(x); + } else { + v = Integer.parseInt(x); + } + } catch (NumberFormatException ex) { + // Fallback: treat as plain string + v = x; + } + } + return v; + } + + /** + * Executes the {@code R_PUSH} instruction: pushes a reference value onto the operand stack. + * + * @param parts The instruction split into its components. + * @param pc The current program counter. + * @param stack The operand stack. + * @param lvs The local variable store (unused). + * @param cs The call stack (unused). + * @return The next program counter value. + * @throws IllegalStateException If no parameter is supplied after {@code R_PUSH}. */ @Override public int execute(String[] parts, int pc, @@ -52,12 +92,32 @@ public final class RPushCommand implements Command { if (parts.length < 2) throw new IllegalStateException("R_PUSH missing parameter"); + // Join all arguments after R_PUSH into a single string, separated by spaces. 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(); + + // If the literal is an array (e.g., [1, 2, "foo"]), parse elements and push as an unmodifiable list. + if (literal.startsWith("[") && literal.endsWith("]")) { + String inside = literal.substring(1, literal.length() - 1).trim(); + java.util.List list = new java.util.ArrayList<>(); + if (!inside.isEmpty()) { + // Split by comma, support element parsing (numbers, booleans, quoted strings) + String[] elems = inside.split(","); + for (String e : elems) { + Object v = getObject(e); + list.add(v); + } + } + // Push as an unmodifiable list to prevent further modifications. + stack.push(java.util.Collections.unmodifiableList(list)); + } else { + // Otherwise, push the string literal as-is. + stack.push(literal); + } return pc + 1; } } 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..e3fb9e7 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 @@ -226,6 +226,39 @@ public class SyscallCommand implements Command { sel.close(); } + // 数组元素访问:arr[idx] -> 返回元素(当前实现返回 int 或字符串/引用原样) + case "ARR_GET" -> { + 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); + } + + if (elem instanceof Number n) { + stack.push(n.intValue()); + } else if (elem instanceof Boolean b) { + stack.push(b ? 1 : 0); + } else { + // 对于字符串或其它引用类型,直接返回引用,由上层决定如何存储 + stack.push(elem); + } + } + // 控制台输出 case "PRINT" -> { Object dataObj = stack.pop();