diff --git a/src/main/java/org/jcnc/snow/compiler/backend/RegisterAllocator.java b/src/main/java/org/jcnc/snow/compiler/backend/RegisterAllocator.java index 4bd906c..738dea3 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/RegisterAllocator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/RegisterAllocator.java @@ -1,52 +1,57 @@ package org.jcnc.snow.compiler.backend; -import org.jcnc.snow.compiler.ir.core.IRFunction; -import org.jcnc.snow.compiler.ir.core.IRInstruction; -import org.jcnc.snow.compiler.ir.core.IRValue; +import org.jcnc.snow.compiler.ir.core.*; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import java.util.HashMap; import java.util.Map; /** - * RegisterAllocator —— 简单的线性扫描寄存器分配器(演示级实现)。 - *

- * 该类用于为 IRFunction 中出现的所有虚拟寄存器(IRVirtualRegister)分配槽位编号。 - * 每个槽位编号对应虚拟机中的局部变量位置或栈槽。 - *

- *

- * 算法策略:顺序扫描所有 IR 指令,每遇到一个新的虚拟寄存器就分配一个递增编号。 - * 不考虑寄存器生存期重叠、重用或优化,仅做简单映射。 - *

+ * A very simple linear-scan register allocator. + * + *

Strategy:

+ *
    + *
  1. Assign formal parameters to slots + * 0 .. n-1 (in declaration order).
  2. + *
  3. Scan the instruction list; every unseen virtual register + * gets the next free slot number.
  4. + *
+ *

No liveness analysis, no spilling – demo-grade only.

*/ public final class RegisterAllocator { - /** 虚拟寄存器 → 槽位编号 的映射表 */ - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); /** - * 执行寄存器分配:将函数中所有虚拟寄存器映射为连续的整型槽位编号。 + * Computes a register→slot mapping for the given function. * - * @param fn 中间表示形式的函数(IRFunction) - * @return 映射表(IRVirtualRegister → 槽位编号) + * @param fn IRFunction to allocate + * @return mapping table (immutable) */ - public Map allocate(IRFunction fn) { - int next = 0; // 当前可分配的下一个槽位编号 + public Map allocate(IRFunction fn) { - for (IRInstruction i : fn.body()) { - // 若该指令有目标寄存器(dest),且尚未分配槽位 → 分配一个 - if (i.dest() != null && !map.containsKey(i.dest())) { - map.put(i.dest(), next++); + int next = 0; + + /* ---------- 1) parameters first ---------- */ + for (IRVirtualRegister param : fn.parameters()) { + map.put(param, next++); + } + + /* ---------- 2) then all remaining VRegs ---------- */ + for (IRInstruction inst : fn.body()) { + + if (inst.dest() != null && !map.containsKey(inst.dest())) { + map.put(inst.dest(), next++); } - // 遍历指令的所有操作数(operand),若是寄存器也进行分配 - for (IRValue v : i.operands()) { - if (v instanceof IRVirtualRegister r && !map.containsKey(r)) { - map.put(r, next++); + for (IRValue v : inst.operands()) { + if (v instanceof IRVirtualRegister vr && + !map.containsKey(vr)) { + map.put(vr, next++); } } } - return map; + return Map.copyOf(map); } } diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java index 1c096af..4125d0b 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java @@ -1,6 +1,7 @@ package org.jcnc.snow.compiler.ir.builder; import org.jcnc.snow.compiler.ir.core.IRFunction; +import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import org.jcnc.snow.compiler.parser.ast.FunctionNode; import org.jcnc.snow.compiler.parser.ast.ParameterNode; import org.jcnc.snow.compiler.parser.ast.base.StatementNode; @@ -31,10 +32,19 @@ public class FunctionBuilder { // 声明函数参数 for (ParameterNode parameterNode : functionNode.parameters()) { - // 在当前作用域中声明参数名称对应的虚拟寄存器 - irContext.getScope().declare(parameterNode.name()); + + /* 1) 为参数新建一个虚拟寄存器 */ + IRVirtualRegister paramReg = irFunction.newRegister(); + + /* 2) 在当前作用域绑定 变量名 → 寄存器 */ + irContext.getScope().declare(parameterNode.name(), paramReg); + + /* 3) 记录到 IRFunction 的形参列表 + —— 供 RegisterAllocator 在 slot 0,1,2… 分配 */ + irFunction.addParameter(paramReg); } + // 使用 StatementBuilder 遍历并生成函数体所有语句的 IR StatementBuilder statementBuilder = new StatementBuilder(irContext); for (StatementNode statementNode : functionNode.body()) { diff --git a/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java b/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java index 03138b4..37c6b3e 100644 --- a/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java +++ b/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java @@ -6,101 +6,82 @@ import java.util.ArrayList; import java.util.List; /** - * IRFunction 表示一个函数级别的中间表示(IR)实体。 - *

- * 每个 IRFunction 包含: - *

    - *
  • 函数名称
  • - *
  • 函数体的指令列表
  • - *
  • 用于分配虚拟寄存器编号的计数器
  • - *
+ * An intermediate-representation function (IRFunction). * - * 该类提供虚拟寄存器分配、指令添加和调试输出等功能。 + *

For every source-level function we build one IRFunction that + * records:

+ *
    + *
  • the function’s name,
  • + *
  • a list of IR instructions (the body),
  • + *
  • a list of formal parameters (in declaration order),
  • + *
  • a counter that hands out unique register numbers.
  • + *
*/ public class IRFunction { - /** 函数名称 */ + + /* ---------- basic info ---------- */ + + /** function name */ private final String name; - /** 存放函数体中所有中间表示指令 */ + /** linear list of IR instructions */ private final List body = new ArrayList<>(); - /** - * 虚拟寄存器编号计数器; - * 每调用一次 newRegister(),计数器自增并分配唯一编号 - */ + /* ---------- virtual-register management ---------- */ + + /** running counter for new virtual registers */ private int regCounter = 0; - /** - * 构造函数,初始化 IRFunction 实例并设置函数名称。 - * - * @param name 函数名称 - */ + /** list of formal-parameter registers in declaration order */ + private final List parameters = new ArrayList<>(); + + /* ---------- construction ---------- */ + public IRFunction(String name) { this.name = name; } - /** - * 创建并返回一个新的虚拟寄存器。 - * - * @return 分配了唯一编号的 IRVirtualRegister 实例 - */ + /* ---------- register helpers ---------- */ + + /** Allocates and returns a brand-new virtual register. */ public IRVirtualRegister newRegister() { return new IRVirtualRegister(regCounter++); } - /** - * 向函数体中添加一条中间表示指令。 - * - * @param irInstruction 要添加的 IRInstruction 对象 - */ - public void add(IRInstruction irInstruction) { - body.add(irInstruction); - } + /* ---------- parameter helpers ---------- */ /** - * 获取函数体的所有中间表示指令。 - * - * @return 包含所有 IRInstruction 的列表 + * Adds a virtual register to the formal-parameter list. + * Call this once for each parameter in declaration order + * when building the IR. */ - public List body() { - return body; + public void addParameter(IRVirtualRegister vr) { + parameters.add(vr); } - /** - * 获取该函数的名称。 - * - * @return 函数名称字符串 - */ - public String name() { - return name; + /** Returns the immutable list of formal parameters. */ + public List parameters() { + return parameters; } - /** - * 获取当前已分配的虚拟寄存器数量。 - * - * @return 当前的寄存器计数器值 - */ - public int registerCount() { - return regCounter; - } + /* ---------- body helpers ---------- */ - /** - * 将整个 IRFunction 对象格式化为字符串,便于打印和调试。 - * 输出示例: - *
-     * func 方法名 {
-     *   指令1
-     *   指令2
-     *   ...
-     * }
-     * 
- * - * @return 格式化后的函数字符串表示 - */ - @Override - public String toString() { + /** Appends an IR instruction to the function body. */ + public void add(IRInstruction inst) { body.add(inst); } + + /** Returns all instructions. */ + public List body() { return body; } + + /* ---------- meta data ---------- */ + + public String name() { return name; } + public int registerCount() { return regCounter; } + + /* ---------- debugging ---------- */ + + @Override public String toString() { StringBuilder sb = new StringBuilder("func " + name + " {\n"); - body.forEach(i -> sb.append(" ").append(i).append("\n")); + body.forEach(i -> sb.append(" ").append(i).append('\n')); return sb.append('}').toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java b/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java index 6c687e3..e466bc0 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java @@ -5,7 +5,8 @@ import org.jcnc.snow.vm.interfaces.Command; import org.jcnc.snow.vm.module.*; /** - * CALL 指令 —— 创建新栈帧并跳转到目标地址。 + * CALL 指令 —— 创建新栈帧并跳转到目标地址,同时把实参从操作数栈 + * 写入被调函数的局部变量表(目前只处理 1 个参数)。 */ public class CallCommand implements Command { @@ -17,7 +18,7 @@ public class CallCommand implements Command { CallStack callStack) { /* ---------- 解析地址 ---------- */ - if (parts.length < 2) // + if (parts.length < 2) throw new IllegalArgumentException("CALL: missing target address"); int targetAddr; @@ -28,20 +29,27 @@ public class CallCommand implements Command { } /* ---------- 创建新栈帧 ---------- */ - // 为被调函数分配“独立”的局部变量表 LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN); - // 若有调用约定,可在此把实参从 operandStack 弹入 calleeLVS - // 例如:for (int i = nArgs-1; i >= 0; i--) calleeLVS.setVariable(i, operandStack.pop()); + /* ---------- 处理 1 个实参 ---------- */ + if (operandStack.isEmpty()) + throw new IllegalStateException("CALL: operand stack empty, missing argument"); + Object arg0 = operandStack.pop(); // 弹出栈顶实参 + calleeLVS.setVariable(0, arg0); // 写入槽 0 + + /* 若将来有多个参数,可用: + for (int i = nArgs - 1; i >= 0; i--) { + calleeLVS.setVariable(i, operandStack.pop()); + } + */ MethodContext ctx = new MethodContext("subroutine@" + targetAddr, null); - StackFrame newFrame = new StackFrame(currentPC + 1, calleeLVS, ctx); callStack.pushFrame(newFrame); System.out.println("Calling function at address: " + targetAddr); /* ---------- 跳转 ---------- */ - return targetAddr; // 设置 PC = 目标函数入口 + return targetAddr; } } diff --git a/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java b/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java index d62291c..af37032 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java @@ -4,37 +4,19 @@ import org.jcnc.snow.vm.interfaces.Command; import org.jcnc.snow.vm.module.*; /** - * The {@code RetCommand} implements the {@code RET} instruction that returns - * from a method in the virtual-machine execution flow. + * Implements the {@code RET} instruction (method return). * - *

Root-frame protection: after popping the current frame the - * command checks {@link CallStack#isEmpty()}. If the stack became empty, the - * popped frame must have been the root frame; the command therefore signals - * normal program termination by returning {@link Integer#MAX_VALUE}.

- * - *

Algorithm

- *
    - *
  1. Ensure the call stack is not empty (a {@code RET} with no frame is an - * illegal state).
  2. - *
  3. Pop the current frame; clear its local variables.
  4. - *
  5. If the stack is now empty, return the sentinel value to terminate the - * VM’s main loop.
  6. - *
  7. Otherwise, resume execution at the return address stored in the popped - * frame.
  8. - *
- * - *

The VM’s main loop should exit when the program counter equals - * {@code Integer.MAX_VALUE}.

+ *

Root-frame protection: The command first inspects the + * top frame without popping it. If the frame’s return address is {@code 0} + * (the root frame) the VM signals normal termination by returning + * {@link #PROGRAM_END}. The frame is not removed and its locals are + * kept intact. All other frames are popped and their locals cleared.

*/ public class RetCommand implements Command { - /** Sentinel value indicating that execution should terminate. */ + /** Sentinel value that tells the VM loop to terminate gracefully. */ private static final int PROGRAM_END = Integer.MAX_VALUE; - public RetCommand() { - /* no-op */ - } - @Override public int execute(String[] parts, int currentPC, @@ -42,22 +24,23 @@ public class RetCommand implements Command { LocalVariableStore localVariableStore, CallStack callStack) { - /* Guard: a RET with an empty stack is invalid. */ + /* ----- Guard: must have at least one frame ----- */ if (callStack.isEmpty()) { - throw new IllegalStateException("Call stack is empty. No function to return to."); + throw new IllegalStateException("RET: call stack is empty."); } - /* Pop the current frame and clear its locals. */ + StackFrame topFrame = callStack.peekFrame(); + + /* ----- Root frame: do NOT pop, just end program ----- */ + if (topFrame.getReturnAddress() == 0) { + System.out.println("Return 0"); + return PROGRAM_END; // VM main loop should break + } + + /* ----- Normal frame: pop & clean locals, then resume caller ----- */ StackFrame finished = callStack.popFrame(); finished.getLocalVariableStore().clearVariables(); - /* If the stack became empty, we just popped the root frame -> terminate. */ - if (callStack.isEmpty()) { - System.out.println("Return 0"); - return PROGRAM_END; // VM main loop must break. - } - - /* Normal return to the caller’s saved PC. */ int returnAddr = finished.getReturnAddress(); System.out.println("Return " + returnAddr); return returnAddr; 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 d0d93a1..d696511 100644 --- a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java +++ b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java @@ -8,37 +8,36 @@ import java.util.List; /** * Virtual-Machine Engine ({@code VirtualMachineEngine}) * - *

This class interprets and executes a list of VM instructions. - * It maintains a program counter (PC) together with core - * runtime structures:

+ *

Interprets and executes a list of VM instructions while maintaining + * a program counter (PC) and the runtime data structures required for + * operand manipulation and method invocation.

* *
    - *
  • {@link OperandStack} – stores intermediate values;
  • - *
  • {@link LocalVariableStore} – holds local variables for the - * current stack frame;
  • - *
  • {@link CallStack} – keeps stack frames and return addresses;
  • - *
  • {@link CommandExecutionHandler} – dispatches opcodes to the - * corresponding {@link org.jcnc.snow.vm.interfaces.Command}.
  • + *
  • {@link OperandStack} — stores intermediate values
  • + *
  • {@link LocalVariableStore} — holds locals for the current + * stack frame
  • + *
  • {@link CallStack} — manages stack frames and return addresses
  • + *
  • {@link CommandExecutionHandler} — dispatches opcodes
  • *
* *

Root-frame contract

- * A root stack frame is pushed once (via - * {@link #ensureRootFrame()}) before execution starts and is never popped. - * When a {@code RET} executed in the root frame signals - * {@link #PROGRAM_END}, the main loop exits gracefully. + * A root stack frame is pushed once via + * {@link #ensureRootFrame()} before the first instruction executes + * and is never popped. When a {@code RET} executed in the root frame + * returns {@link #PROGRAM_END}, the main loop exits gracefully. */ public class VirtualMachineEngine { /* ---------- Constants ---------- */ - /** Sentinel PC value meaning “terminate the program gracefully”. */ + /** Sentinel PC value that signals “terminate program gracefully”. */ private static final int PROGRAM_END = Integer.MAX_VALUE; /* ---------- Runtime state ---------- */ - private final OperandStack operandStack; - private final LocalVariableStore localVariableStore; - private final CallStack callStack; + private final OperandStack operandStack; + private final LocalVariableStore localVariableStore; + private final CallStack callStack; private final CommandExecutionHandler commandExecutionHandler; private int programCounter; @@ -46,122 +45,122 @@ public class VirtualMachineEngine { /* ---------- Construction ---------- */ /** - * Constructs the virtual-machine engine and initialises its runtime - * structures. The program counter is set to {@code 0}; the root frame - * will be created lazily by {@link #ensureRootFrame()}. + * Builds a VM engine with fresh runtime structures. * - * @param vmMode Operating mode (affects local-variable store policy). + * @param vmMode execution mode (DEBUG / RUN) */ public VirtualMachineEngine(VMMode vmMode) { - this.operandStack = new OperandStack(); - this.callStack = new CallStack(); - this.localVariableStore = new LocalVariableStore(vmMode); + this.operandStack = new OperandStack(); + this.callStack = new CallStack(); + this.localVariableStore = new LocalVariableStore(vmMode); // shared with root frame this.commandExecutionHandler = - new CommandExecutionHandler(operandStack, - localVariableStore, - callStack); + new CommandExecutionHandler(operandStack, localVariableStore, callStack); this.programCounter = 0; } - /* Package-private getter – used by debug helpers */ + /* package-private accessor used by debug helpers */ CallStack getCallStack() { return callStack; } /* ---------- Execution ---------- */ /** - * Executes the supplied program in place. + * Executes the supplied program in place. * - * @param program list of textual instructions (opcode and operands - * separated by spaces) - * @throws IllegalArgumentException if {@code program} is {@code null} - * or empty + * @param program textual instructions (“opcode arg1 arg2 …”) + * @throws IllegalArgumentException if {@code program} is null / empty */ public void execute(List program) { - if (program == null || program.isEmpty()) { - throw new IllegalArgumentException( - "The command list cannot be empty or null."); - } + if (program == null || program.isEmpty()) + throw new IllegalArgumentException("The command list cannot be empty or null."); - /* Ensure the root frame is present exactly once. */ + /* Ensure a single root frame is present. */ ensureRootFrame(); /* -------- Main interpreter loop -------- */ while (true) { - /* ─── graceful termination ─── */ + /* graceful termination */ if (programCounter == PROGRAM_END) break; - /* ─── bounds check ─── */ + /* bounds check */ if (programCounter < 0 || programCounter >= program.size()) break; String instruction = program.get(programCounter); String[] parts = instruction.trim().split(" "); if (parts.length < 1) { - System.err.println("Invalid command format at PC=" + - programCounter + " -> Missing opcode"); + System.err.println("Invalid command format at PC=" + programCounter + + " -> Missing opcode"); break; } try { int opCode = parseOpCode(parts[0]); - int nextPC = - commandExecutionHandler.handle(opCode, - parts, - programCounter); + int nextPC = commandExecutionHandler.handle(opCode, parts, programCounter); - /* HALT (-1) or PROGRAM_END → exit VM */ + /* HALT (-1) or PROGRAM_END → exit */ if (nextPC == -1 || nextPC == PROGRAM_END) break; programCounter = nextPC; } catch (IllegalArgumentException e) { - System.err.println("Command error at PC=" + - programCounter + " -> " + e.getMessage()); + System.err.println("Command error at PC=" + programCounter + " -> " + + e.getMessage()); break; } } + + /* ---------- compact root locals & print debug info ---------- */ + if (!callStack.isEmpty()) { + LocalVariableStore rootLvs = callStack.peekFrame().getLocalVariableStore(); + rootLvs.compact(); // trim leading / trailing null slots + } + + printStack(); + printLocalVariables(); } /* ---------- Helper: ensure root frame ---------- */ /** - * Pushes a root stack-frame if none exists. This frame has - * returnAddress = 0 and an empty - * {@link LocalVariableStore}. It must never be popped; instead, - * when a {@code RET} is executed in this frame, the command should - * return {@link #PROGRAM_END}. + * Pushes the root frame (returnAddress = 0) iff it isn’t there yet. + * This frame is never popped during normal execution. */ private void ensureRootFrame() { - if (!callStack.isEmpty()) return; // already initialised + if (!callStack.isEmpty()) return; // already initialised MethodContext rootCtx = new MethodContext("root", null); - StackFrame root = new StackFrame(0, localVariableStore, rootCtx); - callStack.pushFrame(root); + StackFrame rootFrame = new StackFrame(0, localVariableStore, rootCtx); + callStack.pushFrame(rootFrame); } /* ---------- Debug helpers ---------- */ + /** Prints operand stack + call-stack snapshot. */ public void printStack() { operandStack.printOperandStack(); callStack.printCallStack(); } + /** Prints the local-variable table of the current top frame. */ public void printLocalVariables() { - localVariableStore.printLv(); + if (callStack.isEmpty()) { + System.out.println("Local variable table is empty"); + return; + } + callStack.peekFrame().getLocalVariableStore().printLv(); } /* ---------- Utility ---------- */ - /** Converts the textual opcode to an integer. */ + /** Parses textual opcode to integer. */ private int parseOpCode(String opCodeStr) { try { return Integer.parseInt(opCodeStr); } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Invalid opcode -> " + opCodeStr, e); + throw new IllegalArgumentException("Invalid opcode -> " + opCodeStr, e); } } } diff --git a/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java b/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java index d27b031..8384aff 100644 --- a/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java +++ b/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java @@ -7,96 +7,56 @@ import org.jcnc.snow.vm.engine.VMMode; import java.util.ArrayList; /** - * The LocalVariableStore class represents the local variable store of the virtual machine. - * This class manages the local variables of the virtual machine and can behave differently based on the specified execution mode (such as debug or run mode). + * The {@code LocalVariableStore} represents a simple dynamically-sized + * local-variable table (frame locals) of the VM. * - *

The local variable table is stored using an {@link ArrayList}, which supports dynamic resizing and setting values at specific index locations.

- * - *

The local variable store provides basic operations, including setting and getting variable values, ensuring capacity, and printing the current state.

+ *

It supports random access via {@link #setVariable(int, Object)} + * / {@link #getVariable(int)} and can compact itself + * by trimming trailing {@code null} slots after execution has finished.

*/ public class LocalVariableStore { + private final ArrayList localVariables; private final VMMode vmMode; - /** - * Constructor that initializes the local variable store with a specified initial capacity and execution mode. - * - * @param vmMode The current virtual machine mode (e.g., debug or run mode). - * @param initialCapacity The initial capacity determining the starting size of the local variable table. - */ + /* ---------- construction ---------- */ + public LocalVariableStore(VMMode vmMode, int initialCapacity) { this.localVariables = new ArrayList<>(initialCapacity); this.vmMode = vmMode; handleMode(); } - /** - * Constructor that initializes the local variable store without specifying initial capacity but with a specified execution mode. - * - * @param vmMode The current virtual machine mode (e.g., debug or run mode). - */ public LocalVariableStore(VMMode vmMode) { - this.localVariables = new ArrayList<>(); // Default capacity of 10 + this.localVariables = new ArrayList<>(); this.vmMode = vmMode; handleMode(); } - /** - * Sets the local variable value at the specified index. - * - *

If the index exceeds the current size of the local variable table, the table is automatically expanded to accommodate the index.

- * - * @param index The index position. - * @param value The value to be set for the local variable. - */ + /* ---------- public API ---------- */ + + /** Sets the value at {@code index}, expanding the list if necessary. */ public void setVariable(int index, Object value) { - // Ensure the local variable table has enough capacity to accommodate the specified index ensureCapacity(index + 1); - localVariables.set(index, value); // Set the local variable value + localVariables.set(index, value); } - /** - * Retrieves the local variable value at the specified index. - * - * @param index The index position. - * @return The value of the local variable. - * @throws IndexOutOfBoundsException If the index is invalid, an exception is thrown. - */ + /** Returns the value at {@code index}. */ public Object getVariable(int index) { if (index < 0 || index >= localVariables.size()) { - throw new IndexOutOfBoundsException("Invalid index: " + index + ", current size of table: " + localVariables.size()); + throw new IndexOutOfBoundsException( + "Invalid index: " + index + + ", current size of table: " + localVariables.size()); } return localVariables.get(index); } - /** - * Expands the capacity of the local variable table to ensure it can hold at least the specified number of elements. - * - * @param minCapacity The minimum required capacity. - */ - private void ensureCapacity(int minCapacity) { - if (minCapacity > localVariables.size()) { - localVariables.ensureCapacity(minCapacity); // Expand the ArrayList capacity - while (localVariables.size() < minCapacity) { - localVariables.add(null); // Fill remaining space with null - } - } - } - - /** - * Retrieves the complete data of the local variable table. - * - * @return The ArrayList containing the local variables. - */ + /** Exposes the backing list (read-only preferred). */ public ArrayList getLocalVariables() { - return this.localVariables; + return localVariables; } - /** - * Prints the current state of the local variable table. - * - *

If the local variable table is empty, a message is logged indicating that. Otherwise, each local variable's index and value are printed.

- */ + /** Prints every slot to the logger. */ public void printLv() { if (localVariables.isEmpty()) { LoggingUtils.logInfo("Local variable table is empty", ""); @@ -104,33 +64,54 @@ public class LocalVariableStore { } LoggingUtils.logInfo("Local Variable Table:", ""); for (int i = 0; i < localVariables.size(); i++) { - LoggingUtils.logInfo("", String.format("%d: %s", i, localVariables.get(i))); + LoggingUtils.logInfo("", + String.format("%d: %s", i, localVariables.get(i))); } } - /** - * Clears the local variable table, removing all local variables. - * - *

This method removes all entries from the local variable table, effectively resetting the table.

- */ + /** Clears all variables (used when a stack frame is popped). */ public void clearVariables() { - localVariables.clear(); // Clears all elements in the ArrayList + localVariables.clear(); } /** - * Executes specific operations based on the virtual machine's execution mode. - * - *

This method performs different actions depending on the current mode, such as displaying the local variable table in debug mode.

+ * Compacts the table by removing trailing {@code null} slots. + *

Call this once after program termination (e.g. in + * {@code VirtualMachineEngine.execute()} before printing) to get + * cleaner debug output without affecting execution-time indices.

*/ + public void compact() { + /* 1) 去掉尾部 null */ + for (int i = localVariables.size() - 1; i >= 0; i--) { + if (localVariables.get(i) != null) break; + localVariables.remove(i); + } + /* 2) 去掉前导 null */ + while (!localVariables.isEmpty() && localVariables.getFirst() == null) { + localVariables.removeFirst(); + } + } + + + /* ---------- internal helpers ---------- */ + + /** Ensures backing list can hold {@code minCapacity} slots. */ + private void ensureCapacity(int minCapacity) { + if (minCapacity > localVariables.size()) { + localVariables.ensureCapacity(minCapacity); + while (localVariables.size() < minCapacity) { + localVariables.add(null); + } + } + } + + /** Mode-specific UI hook (unchanged). */ private void handleMode() { switch (vmMode) { - case DEBUG: - // TODO: "If you need to build a project with graalvm into a native image, you do not need to annotate nether code - LocalVariableStoreSwing.display(this, "Local Variable Table"); // Display local variable table in debug mode - break; - case RUN: - default: - break; + case DEBUG -> + LocalVariableStoreSwing.display(this, "Local Variable Table"); + case RUN -> { /* no-op */ } + default -> { /* no-op */ } } } } diff --git a/test b/test index 1459e7c..a67a75c 100644 --- a/test +++ b/test @@ -8,7 +8,7 @@ module: CommonTasks body: num1 = 10 num2=1 - return num1 + CommonTasks.test(1) + return num1 + CommonTasks.test(2) end body end function @@ -17,8 +17,10 @@ module: CommonTasks declare num1:int return_type:int body: - num1 =4 - return num1 + declare result : int + declare num2: int =1 + result = num2 + num1 + return result end body end function end module