实现函数调用

This commit is contained in:
Luke 2025-05-11 00:13:11 +08:00
parent 174aafb846
commit 07c040b40d
8 changed files with 252 additions and 283 deletions

View File

@ -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 简单的线性扫描寄存器分配器演示级实现
* <p>
* 该类用于为 IRFunction 中出现的所有虚拟寄存器IRVirtualRegister分配槽位编号
* 每个槽位编号对应虚拟机中的局部变量位置或栈槽
* </p>
* <p>
* 算法策略顺序扫描所有 IR 指令每遇到一个新的虚拟寄存器就分配一个递增编号
* 不考虑寄存器生存期重叠重用或优化仅做简单映射
* </p>
* A <em>very</em> simple linear-scan register allocator.
*
* <p>Strategy:</p>
* <ol>
* <li>Assign <strong>formal parameters</strong> to slots
* 0&nbsp;..&nbsp;n-1 (in declaration order).</li>
* <li>Scan the instruction list; every unseen virtual register
* gets the next free slot number.</li>
* </ol>
* <p>No liveness analysis, no spilling demo-grade only.</p>
*/
public final class RegisterAllocator {
/** 虚拟寄存器 → 槽位编号 的映射表 */
private final Map<IRVirtualRegister, Integer> map = new HashMap<>();
private final Map<IRVirtualRegister,Integer> map = new HashMap<>();
/**
* 执行寄存器分配将函数中所有虚拟寄存器映射为连续的整型槽位编号
* Computes a registerslot mapping for the given function.
*
* @param fn 中间表示形式的函数IRFunction
* @return 映射表IRVirtualRegister 槽位编号
* @param fn IRFunction to allocate
* @return mapping table (immutable)
*/
public Map<IRVirtualRegister, Integer> allocate(IRFunction fn) {
int next = 0; // 当前可分配的下一个槽位编号
public Map<IRVirtualRegister,Integer> 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);
}
}

View File

@ -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()) {

View File

@ -6,101 +6,82 @@ import java.util.ArrayList;
import java.util.List;
/**
* IRFunction 表示一个函数级别的中间表示IR实体
* <p>
* 每个 IRFunction 包含
* <ul>
* <li>函数名称</li>
* <li>函数体的指令列表</li>
* <li>用于分配虚拟寄存器编号的计数器</li>
* </ul>
* An intermediate-representation function (IRFunction).
*
* 该类提供虚拟寄存器分配指令添加和调试输出等功能
* <p>For every source-level function we build one IRFunction that
* records:</p>
* <ul>
* <li>the functions name,</li>
* <li>a list of IR instructions (the <em>body</em>),</li>
* <li>a list of <strong>formal parameters</strong> (in declaration order),</li>
* <li>a counter that hands out unique register numbers.</li>
* </ul>
*/
public class IRFunction {
/** 函数名称 */
/* ---------- basic info ---------- */
/** function name */
private final String name;
/** 存放函数体中所有中间表示指令 */
/** linear list of IR instructions */
private final List<IRInstruction> 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<IRVirtualRegister> 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 <em>in declaration order</em>
* when building the IR.
*/
public List<IRInstruction> 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<IRVirtualRegister> parameters() {
return parameters;
}
/**
* 获取当前已分配的虚拟寄存器数量
*
* @return 当前的寄存器计数器值
*/
public int registerCount() {
return regCounter;
}
/* ---------- body helpers ---------- */
/**
* 将整个 IRFunction 对象格式化为字符串便于打印和调试
* 输出示例
* <pre>
* func 方法名 {
* 指令1
* 指令2
* ...
* }
* </pre>
*
* @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<IRInstruction> 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();
}
}
}

View File

@ -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) // <op> <addr>
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;
}
}

View File

@ -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).
*
* <p><strong>Root-frame protection:</strong> 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}.</p>
*
* <h2>Algorithm</h2>
* <ol>
* <li>Ensure the call stack is not empty (a {@code RET} with no frame is an
* illegal state).</li>
* <li>Pop the current frame; clear its local variables.</li>
* <li>If the stack is now empty, return the sentinel value to terminate the
* VMs main loop.</li>
* <li>Otherwise, resume execution at the return address stored in the popped
* frame.</li>
* </ol>
*
* <p>The VMs main loop should exit when the program counter equals
* {@code Integer.MAX_VALUE}.</p>
* <p><strong>Root-frame protection:</strong> The command first inspects the
* top frame without popping it. If the frames return address is {@code 0}
* (the root frame) the VM signals normal termination by returning
* {@link #PROGRAM_END}. The frame is <em>not</em> removed and its locals are
* kept intact. All other frames are popped and their locals cleared.</p>
*/
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 callers saved PC. */
int returnAddr = finished.getReturnAddress();
System.out.println("Return " + returnAddr);
return returnAddr;

View File

@ -8,37 +8,36 @@ import java.util.List;
/**
* Virtual-Machine Engine ({@code VirtualMachineEngine})
*
* <p>This class interprets and executes a list of VM instructions.
* It maintains a program counter (PC) together with <em>core
* runtime structures</em>:</p>
* <p>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.</p>
*
* <ul>
* <li>{@link OperandStack} stores intermediate values;</li>
* <li>{@link LocalVariableStore} holds local variables for the
* <em>current</em> stack frame;</li>
* <li>{@link CallStack} keeps stack frames and return addresses;</li>
* <li>{@link CommandExecutionHandler} dispatches opcodes to the
* corresponding {@link org.jcnc.snow.vm.interfaces.Command}.</li>
* <li>{@link OperandStack} stores intermediate values</li>
* <li>{@link LocalVariableStore} holds locals for the <em>current</em>
* stack frame</li>
* <li>{@link CallStack} manages stack frames and return addresses</li>
* <li>{@link CommandExecutionHandler} dispatches opcodes</li>
* </ul>
*
* <h2>Root-frame contract</h2>
* A <strong>root stack frame</strong> is pushed <em>once</em> (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 <strong>root stack frame</strong> is pushed <em>once</em> 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 <i>in place</i>.
* Executes the supplied program <em>in place</em>.
*
* @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<String> 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
* <code>returnAddress&nbsp;=&nbsp;0</code> 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 isnt 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);
}
}
}

View File

@ -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 (<em>frame locals</em>) of the VM.
*
* <p>The local variable table is stored using an {@link ArrayList}, which supports dynamic resizing and setting values at specific index locations.</p>
*
* <p>The local variable store provides basic operations, including setting and getting variable values, ensuring capacity, and printing the current state.</p>
* <p>It supports random access via {@link #setVariable(int, Object)}
* / {@link #getVariable(int)} and can <strong>compact</strong> itself
* by trimming trailing {@code null} slots after execution has finished.</p>
*/
public class LocalVariableStore {
private final ArrayList<Object> 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.
*
* <p>If the index exceeds the current size of the local variable table, the table is automatically expanded to accommodate the index.</p>
*
* @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<Object> getLocalVariables() {
return this.localVariables;
return localVariables;
}
/**
* Prints the current state of the local variable table.
*
* <p>If the local variable table is empty, a message is logged indicating that. Otherwise, each local variable's index and value are printed.</p>
*/
/** 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.
*
* <p>This method removes all entries from the local variable table, effectively resetting the table.</p>
*/
/** 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.
*
* <p>This method performs different actions depending on the current mode, such as displaying the local variable table in debug mode.</p>
* Compacts the table by <b>removing trailing {@code null} slots</b>.
* <p>Call this once after program termination (e.g. in
* {@code VirtualMachineEngine.execute()} before printing) to get
* cleaner debug output without affecting execution-time indices.</p>
*/
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 */ }
}
}
}

8
test
View File

@ -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