!42 refactor: 重构虚拟机和编译器的调试输出逻辑并且增加debug参数

Merge pull request !42 from Luke/feature/add-debug-mod
This commit is contained in:
Luke 2025-07-23 13:51:38 +00:00 committed by Gitee
commit 4ccff72a5b
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
16 changed files with 274 additions and 145 deletions

View File

@ -4,6 +4,7 @@ module org.jcnc.snow.compiler {
uses CLICommand; uses CLICommand;
requires java.desktop; requires java.desktop;
requires java.logging; requires java.logging;
requires org.graalvm.nativeimage;
exports org.jcnc.snow.compiler.ir.core; exports org.jcnc.snow.compiler.ir.core;
exports org.jcnc.snow.compiler.ir.instruction; exports org.jcnc.snow.compiler.ir.instruction;
} }

View File

@ -60,10 +60,10 @@ public final class CompileCommand implements CLICommand {
List<String> argList = new ArrayList<>(); List<String> argList = new ArrayList<>();
// 保留用户在 cloud 模式下传入的 run 标志 // 保留用户在 cloud 模式下传入的 run / -debug 标志
for (String a : args) { for (String a : args) {
if ("run".equals(a)) { if ("run".equals(a) || "-debug".equals(a)) {
argList.add("run"); argList.add(a);
} }
} }

View File

@ -0,0 +1,20 @@
package org.jcnc.snow.common;
/**
* 程序的运行/调试模式枚举
* <ul>
* <li>RUN运行模式</li>
* <li>DEBUG调试模式</li>
* </ul>
*/
public enum Mode {
/**
* 运行模式
*/
RUN,
/**
* 调试模式
*/
DEBUG
}

View File

@ -0,0 +1,58 @@
package org.jcnc.snow.common;
/**
* 全局编译/运行配置和调试输出工具类
* <p>
* 用于统一设置程序的运行模式并在调试模式下输出日志信息
* </p>
*/
public final class SnowConfig {
/**
* 当前运行模式默认为 {@link Mode#RUN}
*/
public static Mode MODE = Mode.RUN;
/**
* 私有构造方法防止实例化
*/
private SnowConfig() {
}
/**
* 仅在 {@link Mode#DEBUG} 模式下打印一行信息
*
* @param msg 要输出的信息
*/
public static void print(String msg) {
if (MODE == Mode.DEBUG) System.out.println(msg);
}
/**
* 仅在 {@link Mode#DEBUG} 模式下按格式输出信息
*
* @param fmt 格式化字符串
* @param args 格式化参数
*/
public static void print(String fmt, Object... args) {
if (MODE == Mode.DEBUG) System.out.printf(fmt, args);
}
/**
* 判断当前是否为 {@link Mode#DEBUG} 模式
*
* @return 当且仅当当前为调试模式时返回 true
*/
public static boolean isDebug() {
return MODE == Mode.DEBUG;
}
/**
* 判断当前是否为 {@link Mode#RUN} 模式
*
* @return 当且仅当当前为运行模式时返回 true
*/
public static boolean isRun() {
return MODE == Mode.RUN;
}
}

View File

@ -1,5 +1,6 @@
package org.jcnc.snow.compiler.lexer.core; package org.jcnc.snow.compiler.lexer.core;
import org.jcnc.snow.common.SnowConfig;
import org.jcnc.snow.compiler.lexer.base.TokenScanner; import org.jcnc.snow.compiler.lexer.base.TokenScanner;
import org.jcnc.snow.compiler.lexer.scanners.*; import org.jcnc.snow.compiler.lexer.scanners.*;
import org.jcnc.snow.compiler.lexer.token.Token; import org.jcnc.snow.compiler.lexer.token.Token;
@ -10,6 +11,8 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* Snow 语言词法分析器核心实现 * Snow 语言词法分析器核心实现
* <p>采用<b>先扫描 后批量校验 统一报告</b>策略: * <p>采用<b>先扫描 后批量校验 统一报告</b>策略:
@ -53,14 +56,17 @@ public class LexerEngine {
/* 2. 后置整体校验 */ /* 2. 后置整体校验 */
validateTokens(); validateTokens();
/* 3. 打印 token */ /* 3. 打印 token */
TokenPrinter.print(tokens); if (SnowConfig.isDebug()) {
TokenPrinter.print(tokens);
}
/* 4. 统一报告错误 */ /* 4. 统一报告错误 */
report(errors); report(errors);
} }
public static void report(List<LexicalError> errors) { public static void report(List<LexicalError> errors) {
if (errors == null || errors.isEmpty()) { if (errors == null || errors.isEmpty()) {
System.out.println("\n## 词法分析通过,没有发现错误\n"); print("\n## 词法分析通过,没有发现错误\n");
return; return;
} }
System.err.println("\n词法分析发现 " + errors.size() + " 个错误: "); System.err.println("\n词法分析发现 " + errors.size() + " 个错误: ");

View File

@ -4,6 +4,8 @@ import org.jcnc.snow.compiler.semantic.error.SemanticError;
import java.util.List; import java.util.List;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* {@code SemanticAnalysisReporter} 用于在语义分析结束后汇总并打印所有收集到的 * {@code SemanticAnalysisReporter} 用于在语义分析结束后汇总并打印所有收集到的
* {@link SemanticError}为了同时满足完整错误收集按需快速失败两种使用场景 * {@link SemanticError}为了同时满足完整错误收集按需快速失败两种使用场景
@ -28,7 +30,7 @@ public final class SemanticAnalysisReporter {
System.err.println("语义分析发现 " + errors.size() + " 个错误: "); System.err.println("语义分析发现 " + errors.size() + " 个错误: ");
errors.forEach(err -> System.err.println(" " + err)); errors.forEach(err -> System.err.println(" " + err));
} else { } else {
System.out.println("\n## 语义分析通过,没有发现错误\n"); print("\n## 语义分析通过,没有发现错误\n");
} }
} }

View File

@ -1,12 +1,14 @@
package org.jcnc.snow.pkg.tasks; package org.jcnc.snow.pkg.tasks;
import org.jcnc.snow.cli.commands.CompileCommand; import org.jcnc.snow.cli.commands.CompileCommand;
import org.jcnc.snow.compiler.backend.utils.OpHelper; import org.jcnc.snow.common.Mode;
import org.jcnc.snow.common.SnowConfig;
import org.jcnc.snow.compiler.backend.alloc.RegisterAllocator; import org.jcnc.snow.compiler.backend.alloc.RegisterAllocator;
import org.jcnc.snow.compiler.backend.builder.VMCodeGenerator; import org.jcnc.snow.compiler.backend.builder.VMCodeGenerator;
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder; import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator; import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.generator.InstructionGeneratorProvider; import org.jcnc.snow.compiler.backend.generator.InstructionGeneratorProvider;
import org.jcnc.snow.compiler.backend.utils.OpHelper;
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder; import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
import org.jcnc.snow.compiler.ir.core.IRFunction; import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction; import org.jcnc.snow.compiler.ir.core.IRInstruction;
@ -26,6 +28,8 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* CLI 任务: 编译 .snow 源文件为 VM 字节码.water 文件 * CLI 任务: 编译 .snow 源文件为 VM 字节码.water 文件
* <p> * <p>
@ -39,16 +43,20 @@ import java.util.*;
* </pre> * </pre>
*/ */
public final class CompileTask implements Task { public final class CompileTask implements Task {
/** 项目信息 */ /**
* 项目信息
*/
private final Project project; private final Project project;
/** 原始命令行参数 */ /**
* 原始命令行参数
*/
private final String[] args; private final String[] args;
/** /**
* 创建一个编译任务 * 创建一个编译任务
* *
* @param project 项目信息对象 * @param project 项目信息对象
* @param args 命令行参数数组 * @param args 命令行参数数组
*/ */
public CompileTask(Project project, String[] args) { public CompileTask(Project project, String[] args) {
this.project = project; this.project = project;
@ -64,6 +72,56 @@ public final class CompileTask implements Task {
this(project, new String[0]); this(project, new String[0]);
} }
/* ---------------------- 调试输出工具 ---------------------- */
/**
* 推断 .water 输出文件名
* <ul>
* <li>如果指定 -o直接使用该名称</li>
* <li>目录编译时取目录名</li>
* <li>单文件编译时取文件名去掉 .snow 后缀</li>
* <li>否则默认 "program"</li>
* </ul>
*
* @param sources 源文件路径列表
* @param outName 输出文件名如有指定否则为 null
* @param dir 源码目录如有指定否则为 null
* @return 推断出的输出文件路径.water 文件
*/
private static Path deriveOutputPath(List<Path> sources, String outName, Path dir) {
String base;
if (outName != null) {
base = outName;
} else if (dir != null) {
base = dir.getFileName().toString();
} else if (sources.size() == 1) {
base = sources.getFirst().getFileName().toString()
.replaceFirst("\\.snow$", "");
} else {
base = "program";
}
return Path.of(base + ".water");
}
/**
* main 函数调整至函数列表首位确保程序入口为 PC=0
*
* @param in 原始 IRProgram
* @return 调整入口后的 IRProgram
*/
private static IRProgram reorderForEntry(IRProgram in) {
List<IRFunction> ordered = new ArrayList<>(in.functions());
for (int i = 0; i < ordered.size(); i++) {
if ("main".equals(ordered.get(i).name())) {
Collections.swap(ordered, 0, i);
break;
}
}
IRProgram out = new IRProgram();
ordered.forEach(out::add);
return out;
}
/** /**
* 执行编译任务该方法会解析参数并调用 {@link #execute(String[])} 进行实际编译流程 * 执行编译任务该方法会解析参数并调用 {@link #execute(String[])} 进行实际编译流程
* *
@ -96,6 +154,7 @@ public final class CompileTask implements Task {
String arg = args[i]; String arg = args[i];
switch (arg) { switch (arg) {
case "run" -> runAfterCompile = true; case "run" -> runAfterCompile = true;
case "-debug" -> SnowConfig.MODE = Mode.DEBUG;
case "-o" -> { case "-o" -> {
if (i + 1 < args.length) outputName = args[++i]; if (i + 1 < args.length) outputName = args[++i];
else { else {
@ -151,8 +210,8 @@ public final class CompileTask implements Task {
// ---------------- 1. 词法/语法分析并打印源代码 ---------------- // ---------------- 1. 词法/语法分析并打印源代码 ----------------
List<Node> allAst = new ArrayList<>(); List<Node> allAst = new ArrayList<>();
System.out.println("## 编译器输出"); print("## 编译器输出");
System.out.println("### Snow 源代码"); print("### Snow 源代码");
for (Path p : sources) { for (Path p : sources) {
if (!Files.exists(p)) { if (!Files.exists(p)) {
@ -163,8 +222,8 @@ public final class CompileTask implements Task {
String code = Files.readString(p, StandardCharsets.UTF_8); String code = Files.readString(p, StandardCharsets.UTF_8);
// 打印源码 // 打印源码
System.out.println("#### " + p.getFileName()); print("#### " + p.getFileName());
System.out.println(code); print(code);
// 词法语法分析 // 词法语法分析
LexerEngine lexer = new LexerEngine(code, p.toString()); LexerEngine lexer = new LexerEngine(code, p.toString());
@ -185,11 +244,11 @@ public final class CompileTask implements Task {
program = reorderForEntry(program); program = reorderForEntry(program);
// 打印 AST IR // 打印 AST IR
System.out.println("### AST"); print("### AST");
ASTPrinter.printJson(allAst); if (SnowConfig.isDebug()) ASTPrinter.printJson(allAst);
System.out.println("### IR"); print("### IR");
System.out.println(program); print(program.toString());
// ---------------- 4. IR VM 指令 ---------------- // ---------------- 4. IR VM 指令 ----------------
VMProgramBuilder builder = new VMProgramBuilder(); VMProgramBuilder builder = new VMProgramBuilder();
@ -203,74 +262,28 @@ public final class CompileTask implements Task {
} }
List<String> finalCode = builder.build(); List<String> finalCode = builder.build();
System.out.println("### VM code"); print("### VM code");
for (int i = 0; i < finalCode.size(); i++) { if (SnowConfig.isDebug()) {
String[] parts = finalCode.get(i).split(" "); for (int i = 0; i < finalCode.size(); i++) {
String name = OpHelper.opcodeName(parts[0]); String[] parts = finalCode.get(i).split(" ");
parts = Arrays.copyOfRange(parts, 1, parts.length); String name = OpHelper.opcodeName(parts[0]);
System.out.printf("%04d: %-10s %s\n", i, name, String.join(" ", parts)); parts = Arrays.copyOfRange(parts, 1, parts.length);
print("%04d: %-10s %s%n", i, name, String.join(" ", parts));
}
} }
// ---------------- 5. 写出 .water 文件 ---------------- // ---------------- 5. 写出 .water 文件 ----------------
Path outputFile = deriveOutputPath(sources, outputName, dir); Path outputFile = deriveOutputPath(sources, outputName, dir);
Files.write(outputFile, finalCode, StandardCharsets.UTF_8); Files.write(outputFile, finalCode, StandardCharsets.UTF_8);
System.out.println("Written to " + outputFile.toAbsolutePath()); print("Written to " + outputFile.toAbsolutePath());
// ---------------- 6. 可选: 立即运行 VM ---------------- // ---------------- 6. 立即运行 VM ----------------
if (runAfterCompile) { if (runAfterCompile) {
System.out.println("\n=== Launching VM ==="); print("\n=== Launching VM ===");
VMLauncher.main(new String[]{outputFile.toString()}); VMLauncher.main(new String[]{outputFile.toString()});
System.out.println("\n=== VM exited ==="); print("\n=== VM exited ===");
} }
return 0; return 0;
} }
/**
* 推断 .water 输出文件名
* <ul>
* <li>如果指定 -o直接使用该名称</li>
* <li>目录编译时取目录名</li>
* <li>单文件编译时取文件名去掉 .snow 后缀</li>
* <li>否则默认 "program"</li>
* </ul>
*
* @param sources 源文件路径列表
* @param outName 输出文件名如有指定否则为 null
* @param dir 源码目录如有指定否则为 null
* @return 推断出的输出文件路径.water 文件
*/
private static Path deriveOutputPath(List<Path> sources, String outName, Path dir) {
String base;
if (outName != null) {
base = outName;
} else if (dir != null) {
base = dir.getFileName().toString();
} else if (sources.size() == 1) {
base = sources.getFirst().getFileName().toString()
.replaceFirst("\\.snow$", "");
} else {
base = "program";
}
return Path.of(base + ".water");
}
/**
* main 函数调整至函数列表首位确保程序入口为 PC=0
*
* @param in 原始 IRProgram
* @return 调整入口后的 IRProgram
*/
private static IRProgram reorderForEntry(IRProgram in) {
List<IRFunction> ordered = new ArrayList<>(in.functions());
for (int i = 0; i < ordered.size(); i++) {
if ("main".equals(ordered.get(i).name())) {
Collections.swap(ordered, 0, i);
break;
}
}
IRProgram out = new IRProgram();
ordered.forEach(out::add);
return out;
}
} }

View File

@ -1,8 +1,8 @@
package org.jcnc.snow.vm; package org.jcnc.snow.vm;
import org.jcnc.snow.common.Mode;
import org.jcnc.snow.vm.execution.CommandLoader; import org.jcnc.snow.vm.execution.CommandLoader;
import org.jcnc.snow.vm.engine.VMCommandExecutor; import org.jcnc.snow.vm.engine.VMCommandExecutor;
import org.jcnc.snow.vm.engine.VMMode;
import org.jcnc.snow.vm.engine.VirtualMachineEngine; import org.jcnc.snow.vm.engine.VirtualMachineEngine;
import org.jcnc.snow.vm.io.FilePathResolver; import org.jcnc.snow.vm.io.FilePathResolver;
import org.jcnc.snow.vm.utils.VMStateLogger; import org.jcnc.snow.vm.utils.VMStateLogger;
@ -45,7 +45,7 @@ public class VMInitializer {
* @param vmMode The mode in which the virtual machine should operate. * @param vmMode The mode in which the virtual machine should operate.
* This can be used to specify different operational modes (e.g., debug mode, normal mode). * This can be used to specify different operational modes (e.g., debug mode, normal mode).
*/ */
public static void initializeAndRunVM(String[] args, VMMode vmMode) { public static void initializeAndRunVM(String[] args, Mode vmMode) {
// Retrieve and validate file path // Retrieve and validate file path
String filePath = FilePathResolver.getFilePath(args); String filePath = FilePathResolver.getFilePath(args);
if (filePath == null) return; if (filePath == null) return;
@ -55,7 +55,7 @@ public class VMInitializer {
if (commands.isEmpty()) return; if (commands.isEmpty()) return;
// Execute the commands using the virtual machine engine // Execute the commands using the virtual machine engine
VirtualMachineEngine virtualMachineEngine = new VirtualMachineEngine(vmMode); VirtualMachineEngine virtualMachineEngine = new VirtualMachineEngine();
VMCommandExecutor.executeInstructions(virtualMachineEngine, commands); VMCommandExecutor.executeInstructions(virtualMachineEngine, commands);
// Print the virtual machine's state // Print the virtual machine's state

View File

@ -1,6 +1,7 @@
package org.jcnc.snow.vm; package org.jcnc.snow.vm;
import org.jcnc.snow.vm.engine.VMMode;
import org.jcnc.snow.common.Mode;
import static org.jcnc.snow.vm.VMInitializer.initializeAndRunVM; import static org.jcnc.snow.vm.VMInitializer.initializeAndRunVM;
@ -12,7 +13,7 @@ import static org.jcnc.snow.vm.VMInitializer.initializeAndRunVM;
* *
* <p>This class provides the entry point to launch the virtual machine. The main method retrieves the file path * <p>This class provides the entry point to launch the virtual machine. The main method retrieves the file path
* of the VM instructions from the command-line arguments, initializes the VM engine, and runs the VM in the * of the VM instructions from the command-line arguments, initializes the VM engine, and runs the VM in the
* specified mode (e.g., {@link VMMode#DEBUG}).</p> * specified mode (e.g., {@link Mode#DEBUG}).</p>
*/ */
public class VMLauncher { public class VMLauncher {
@ -44,6 +45,6 @@ public class VMLauncher {
*/ */
public static void main(String[] args) { public static void main(String[] args) {
// Call the method that initializes and runs the VM in DEBUG mode // Call the method that initializes and runs the VM in DEBUG mode
initializeAndRunVM(args, VMMode.RUN); initializeAndRunVM(args, Mode.RUN);
} }
} }

View File

@ -1,9 +1,11 @@
package org.jcnc.snow.vm.commands.flow.control; package org.jcnc.snow.vm.commands.flow.control;
import org.jcnc.snow.vm.engine.VMMode; import org.jcnc.snow.common.Mode;
import org.jcnc.snow.vm.interfaces.Command; import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*; import org.jcnc.snow.vm.module.*;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* The CallCommand class implements the {@link Command} interface and represents a subroutine/function call * The CallCommand class implements the {@link Command} interface and represents a subroutine/function call
* instruction in the virtual machine. * instruction in the virtual machine.
@ -32,18 +34,18 @@ public class CallCommand implements Command {
* and control transfer to the target function address. * and control transfer to the target function address.
* </p> * </p>
* *
* @param parts The instruction parameters. Must include: * @param parts The instruction parameters. Must include:
* <ul> * <ul>
* <li>{@code parts[0]}: The "CALL" operator.</li> * <li>{@code parts[0]}: The "CALL" operator.</li>
* <li>{@code parts[1]}: The target address of the callee function.</li> * <li>{@code parts[1]}: The target address of the callee function.</li>
* <li>{@code parts[2]}: The number of arguments to pass.</li> * <li>{@code parts[2]}: The number of arguments to pass.</li>
* </ul> * </ul>
* @param currentPC The current program counter, used to record the return address for after the call. * @param currentPC The current program counter, used to record the return address for after the call.
* @param operandStack The operand stack manager. Arguments are popped from this stack. * @param operandStack The operand stack manager. Arguments are popped from this stack.
* @param callerLVS The local variable store of the caller function (not directly modified here). * @param callerLVS The local variable store of the caller function (not directly modified here).
* @param callStack The virtual machine's call stack manager, used to push the new stack frame. * @param callStack The virtual machine's call stack manager, used to push the new stack frame.
* @return The new program counter value, which is the address of the callee function (i.e., jump target). * @return The new program counter value, which is the address of the callee function (i.e., jump target).
* The VM should transfer control to this address after setting up the call frame. * The VM should transfer control to this address after setting up the call frame.
* @throws IllegalArgumentException If the instruction parameters are malformed or missing. * @throws IllegalArgumentException If the instruction parameters are malformed or missing.
* @throws IllegalStateException If the operand stack does not contain enough arguments. * @throws IllegalStateException If the operand stack does not contain enough arguments.
*/ */
@ -61,13 +63,13 @@ public class CallCommand implements Command {
int nArgs; int nArgs;
try { try {
targetAddr = Integer.parseInt(parts[1]); targetAddr = Integer.parseInt(parts[1]);
nArgs = Integer.parseInt(parts[2]); nArgs = Integer.parseInt(parts[2]);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalArgumentException("CALL: malformed operands", e); throw new IllegalArgumentException("CALL: malformed operands", e);
} }
/* build new frame & local table for callee */ /* build new frame & local table for callee */
LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN); LocalVariableStore calleeLVS = new LocalVariableStore();
/* transfer arguments: operand stack top is last arg */ /* transfer arguments: operand stack top is last arg */
for (int slot = nArgs - 1; slot >= 0; slot--) { for (int slot = nArgs - 1; slot >= 0; slot--) {
@ -80,7 +82,7 @@ public class CallCommand implements Command {
new MethodContext("subroutine@" + targetAddr, null)); new MethodContext("subroutine@" + targetAddr, null));
callStack.pushFrame(newFrame); callStack.pushFrame(newFrame);
System.out.println("\nCalling function at address: " + targetAddr); print("\nCalling function at address: " + targetAddr);
return targetAddr; // jump return targetAddr; // jump
} }
} }

View File

@ -3,6 +3,8 @@ package org.jcnc.snow.vm.commands.flow.control;
import org.jcnc.snow.vm.interfaces.Command; import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*; import org.jcnc.snow.vm.module.*;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* Implements the {@code RET} instruction (method return). * Implements the {@code RET} instruction (method return).
* *
@ -44,7 +46,7 @@ public class RetCommand implements Command {
finished.getLocalVariableStore().clearVariables(); finished.getLocalVariableStore().clearVariables();
int returnAddr = finished.getReturnAddress(); int returnAddr = finished.getReturnAddress();
System.out.println("\nReturn " + returnAddr); print("\nReturn " + returnAddr);
return returnAddr; return returnAddr;
} }
} }

View File

@ -1,19 +0,0 @@
package org.jcnc.snow.vm.engine;
/**
* The VMMode enum defines the different operational modes of the virtual machine.
* <p>This class is used to distinguish the behavior of the virtual machine in different states, with each mode corresponding to a different operational logic.</p>
*/
public enum VMMode {
/**
* Run Mode: The virtual machine executes instructions in the normal execution flow.
* <p>In this mode, the virtual machine processes instructions and performs related calculations.</p>
*/
RUN,
/**
* Debug Mode: The virtual machine outputs debug information during execution.
* <p>This mode is used for debugging the virtual machine, allowing developers to view detailed information such as the execution state, local variables, and more.</p>
*/
DEBUG,
}

View File

@ -1,5 +1,6 @@
package org.jcnc.snow.vm.engine; package org.jcnc.snow.vm.engine;
import org.jcnc.snow.common.Mode;
import org.jcnc.snow.vm.execution.CommandExecutionHandler; import org.jcnc.snow.vm.execution.CommandExecutionHandler;
import org.jcnc.snow.vm.module.*; import org.jcnc.snow.vm.module.*;
@ -53,10 +54,10 @@ public class VirtualMachineEngine {
* *
* @param vmMode execution mode (DEBUG / RUN) * @param vmMode execution mode (DEBUG / RUN)
*/ */
public VirtualMachineEngine(VMMode vmMode) { public VirtualMachineEngine() {
this.operandStack = new OperandStack(); this.operandStack = new OperandStack();
this.callStack = new CallStack(); this.callStack = new CallStack();
this.localVariableStore = new LocalVariableStore(vmMode); // shared with root frame this.localVariableStore = new LocalVariableStore(); // shared with root frame
this.commandExecutionHandler = this.commandExecutionHandler =
new CommandExecutionHandler(operandStack, localVariableStore, callStack); new CommandExecutionHandler(operandStack, localVariableStore, callStack);
this.programCounter = 0; this.programCounter = 0;

View File

@ -1,11 +1,12 @@
package org.jcnc.snow.vm.module; package org.jcnc.snow.vm.module;
import org.jcnc.snow.common.SnowConfig;
import org.jcnc.snow.vm.gui.LocalVariableStoreSwing; import org.jcnc.snow.vm.gui.LocalVariableStoreSwing;
import org.jcnc.snow.vm.utils.LoggingUtils; import org.jcnc.snow.vm.utils.LoggingUtils;
import org.jcnc.snow.vm.engine.VMMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import static org.jcnc.snow.vm.utils.VMUtils.isNativeImage;
/** /**
* The {@code LocalVariableStore} represents a simple dynamically-sized * The {@code LocalVariableStore} represents a simple dynamically-sized
@ -18,38 +19,43 @@ import java.util.Objects;
public class LocalVariableStore { public class LocalVariableStore {
private final ArrayList<Object> localVariables; private final ArrayList<Object> localVariables;
private final VMMode vmMode;
/* ---------- construction ---------- */ /* ---------- construction ---------- */
public LocalVariableStore(VMMode vmMode, int initialCapacity) { public LocalVariableStore(int initialCapacity) {
this.localVariables = new ArrayList<>(initialCapacity); this.localVariables = new ArrayList<>(initialCapacity);
this.vmMode = vmMode;
handleMode(); handleMode();
} }
public LocalVariableStore(VMMode vmMode) { public LocalVariableStore() {
this.localVariables = new ArrayList<>(); this.localVariables = new ArrayList<>();
this.vmMode = vmMode;
handleMode(); handleMode();
} }
/* ---------- public API ---------- */ /* ---------- public API ---------- */
/** Sets the value at {@code index}, expanding the list if necessary. */ /**
* Sets the value at {@code index}, expanding the list if necessary.
*/
public void setVariable(int index, Object value) { public void setVariable(int index, Object value) {
ensureCapacity(index + 1); ensureCapacity(index + 1);
localVariables.set(index, value); localVariables.set(index, value);
} }
/* ------------------------------------------------------------ /* ------------------------------------------------------------
* 兼容早期实现: VM 指令译码器可直接调用 store / load * Backward compatibility: VM instruction decoder can directly call
* 而无需关心内部命名差异 * store / load methods without caring about internal naming differences.
* ------------------------------------------------------------ */ * ------------------------------------------------------------ */
public void store(int index, Object value) { setVariable(index, value); } public void store(int index, Object value) {
public Object load(int index) { return getVariable(index); } setVariable(index, value);
}
/** Returns the value at {@code index}. */ public Object load(int index) {
return getVariable(index);
}
/**
* Returns the value at {@code index}.
*/
public Object getVariable(int index) { public Object getVariable(int index) {
/* 修改点 #1 —— 自动扩容以避免 LOAD 越界异常 */ /* 修改点 #1 —— 自动扩容以避免 LOAD 越界异常 */
if (index < 0) if (index < 0)
@ -58,12 +64,16 @@ public class LocalVariableStore {
return localVariables.get(index); // 可能为 null符合 JVM 语义 return localVariables.get(index); // 可能为 null符合 JVM 语义
} }
/** Exposes the backing list (read-only preferred). */ /**
* Exposes the backing list (read-only preferred).
*/
public ArrayList<Object> getLocalVariables() { public ArrayList<Object> getLocalVariables() {
return localVariables; return localVariables;
} }
/** Prints every slot to the logger. */ /**
* Prints every slot to the logger.
*/
public void printLv() { public void printLv() {
if (localVariables.isEmpty()) { if (localVariables.isEmpty()) {
LoggingUtils.logInfo("Local variable table is empty", ""); LoggingUtils.logInfo("Local variable table is empty", "");
@ -76,7 +86,12 @@ public class LocalVariableStore {
} }
} }
/** Clears all variables (used when a stack frame is popped). */
/* ---------- internal helpers ---------- */
/**
* Clears all variables (used when a stack frame is popped).
*/
public void clearVariables() { public void clearVariables() {
localVariables.clear(); localVariables.clear();
} }
@ -96,10 +111,9 @@ public class LocalVariableStore {
} }
} }
/**
/* ---------- internal helpers ---------- */ * Ensures backing list can hold {@code minCapacity} slots.
*/
/** Ensures backing list can hold {@code minCapacity} slots. */
private void ensureCapacity(int minCapacity) { private void ensureCapacity(int minCapacity) {
/* 修改点 #3 —— 使用 while 循环填充 null确保 slot 可随机写入 */ /* 修改点 #3 —— 使用 while 循环填充 null确保 slot 可随机写入 */
while (localVariables.size() < minCapacity) { while (localVariables.size() < minCapacity) {
@ -107,11 +121,19 @@ public class LocalVariableStore {
} }
} }
/** Mode-specific UI hook (unchanged). */ /**
* Mode-specific UI hook for debugging.
* <p>
* If debug mode is enabled and not running inside a GraalVM native-image,
* this method will open the Swing-based variable inspector window.
* In native-image environments (where AWT/Swing is unavailable),
* the window will not be displayed.
*/
private void handleMode() { private void handleMode() {
/* no-op */ if (SnowConfig.isDebug()) {
if (Objects.requireNonNull(vmMode) == VMMode.DEBUG) { if (isNativeImage()) return;
LocalVariableStoreSwing.display(this, "Local Variable Table"); LocalVariableStoreSwing.display(this, "Local Variable Table");
} }
} }
} }

View File

@ -3,6 +3,8 @@ package org.jcnc.snow.vm.utils;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static org.jcnc.snow.common.SnowConfig.print;
/** /**
* The LoggingUtils class provides logging functionality, supporting different log levels for output. * The LoggingUtils class provides logging functionality, supporting different log levels for output.
* This class uses Java's built-in logging system for logging, supporting both console output and log file recording. * This class uses Java's built-in logging system for logging, supporting both console output and log file recording.
@ -51,6 +53,6 @@ public class LoggingUtils {
*/ */
public static void logInfo(String title, String message) { public static void logInfo(String title, String message) {
// Output the informational message to the console // Output the informational message to the console
System.out.println(title + message); print(title + message);
} }
} }

View File

@ -1,5 +1,6 @@
package org.jcnc.snow.vm.utils; package org.jcnc.snow.vm.utils;
import org.graalvm.nativeimage.ImageInfo;
import org.jcnc.snow.vm.engine.VirtualMachineEngine; import org.jcnc.snow.vm.engine.VirtualMachineEngine;
/** /**
@ -39,4 +40,21 @@ public class VMUtils {
vm.printStack(); vm.printStack();
vm.printLocalVariables(); vm.printLocalVariables();
} }
/**
* Detects if the current runtime environment is a GraalVM native-image.
* <p>
* Uses GraalVM's {@code org.graalvm.nativeimage.ImageInfo.inImageCode()} API to determine
* if the application is running as a native executable. If the class is not present
* (for example, in a standard JVM), returns {@code false}.
*
* @return {@code true} if running inside a GraalVM native-image, otherwise {@code false}
*/
public static boolean isNativeImage() {
try {
return ImageInfo.inImageCode();
} catch (Throwable t) {
return false;
}
}
} }