feat: 实现Snow 语言统一命令行入口(CLI)
This commit is contained in:
parent
49f91e3b4e
commit
a8d9bbe81d
2
pom.xml
2
pom.xml
@ -35,7 +35,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>org.jcnc.snow.compiler.cli.SnowCompiler</mainClass>
|
<mainClass>org.jcnc.snow.compiler.cli.SnowCLI</mainClass>
|
||||||
<addClasspath>true</addClasspath>
|
<addClasspath>true</addClasspath>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
module org.jcnc.snow.compiler {
|
module org.jcnc.snow.compiler {
|
||||||
|
uses org.jcnc.snow.compiler.cli.CLICommand;
|
||||||
requires java.desktop;
|
requires java.desktop;
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
exports org.jcnc.snow.compiler.ir.core;
|
exports org.jcnc.snow.compiler.ir.core;
|
||||||
|
|||||||
44
src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java
Normal file
44
src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package org.jcnc.snow.compiler.cli;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 所有子命令(如 compile、run 等)都必须实现的命令接口。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>实现类应当是无状态(stateless)、线程安全(thread-safe)的。</li>
|
||||||
|
* <li>可通过 {@link java.util.ServiceLoader ServiceLoader} 自动发现,或直接在 {@link SnowCLI} 注册。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
|
||||||
|
*/
|
||||||
|
public interface CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令的名称(如 "compile"、"run")。
|
||||||
|
*
|
||||||
|
* @return 命令名字符串
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令的一行简介(用于 help 列表)。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
String description();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令的专用 usage 信息(可选实现)。
|
||||||
|
* 可覆盖此方法自定义帮助信息,默认无操作。
|
||||||
|
*/
|
||||||
|
default void printUsage() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行命令逻辑。
|
||||||
|
*
|
||||||
|
* @param args 传递给子命令的参数(不含命令名本身)
|
||||||
|
* @return 进程退出码(0 为成功,非0为错误)
|
||||||
|
* @throws Exception 可抛出任意异常,框架会统一捕获和输出
|
||||||
|
*/
|
||||||
|
int execute(String[] args) throws Exception;
|
||||||
|
}
|
||||||
120
src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java
Normal file
120
src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package org.jcnc.snow.compiler.cli;
|
||||||
|
|
||||||
|
import org.jcnc.snow.compiler.cli.commands.CompileCommand;
|
||||||
|
import org.jcnc.snow.compiler.cli.commands.RunCommand;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Snow 语言统一命令行入口(CLI)。
|
||||||
|
* <br>
|
||||||
|
* 负责命令注册、解析与调度。
|
||||||
|
* </p>
|
||||||
|
* <pre>
|
||||||
|
* 示例用法:
|
||||||
|
* $ snow help
|
||||||
|
* $ snow compile main.snow
|
||||||
|
* $ snow run main.vm
|
||||||
|
* </pre>
|
||||||
|
* <ul>
|
||||||
|
* <li>支持核心命令自动注册(compile/run)。</li>
|
||||||
|
* <li>支持通过 ServiceLoader 自动发现并注册第三方命令。</li>
|
||||||
|
* <li>统一异常处理和 usage 帮助输出。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class SnowCLI {
|
||||||
|
|
||||||
|
/** 命令注册表,按插入顺序保存命令名到实现的映射。 */
|
||||||
|
private final Map<String, CLICommand> registry = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造 CLI,自动注册所有可用命令。
|
||||||
|
* <ul>
|
||||||
|
* <li>通过 ServiceLoader 加载所有外部扩展命令。</li>
|
||||||
|
* <li>始终内置注册 compile、run 等核心命令。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public SnowCLI() {
|
||||||
|
// 1. 自动发现 ServiceLoader 扩展命令
|
||||||
|
ServiceLoader.load(CLICommand.class).forEach(this::register);
|
||||||
|
// 2. 注册核心命令,保证 CLI 可用
|
||||||
|
register(new CompileCommand());
|
||||||
|
register(new RunCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册一个命令到 CLI(命令名唯一,若已注册则跳过)。
|
||||||
|
*
|
||||||
|
* @param cmd 待注册命令
|
||||||
|
*/
|
||||||
|
private void register(CLICommand cmd) {
|
||||||
|
registry.putIfAbsent(cmd.name(), cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析命令行参数并执行相应命令。
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
* @return 进程退出码(0=成功, 1=未知命令, -1=命令异常)
|
||||||
|
*/
|
||||||
|
public int run(String[] args) {
|
||||||
|
// 无参数或 help,打印全局用法
|
||||||
|
if (args.length == 0
|
||||||
|
|| Set.of("help", "-h", "--help").contains(args[0])) {
|
||||||
|
printGlobalUsage();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据命令名查找注册表
|
||||||
|
CLICommand cmd = registry.get(args[0]);
|
||||||
|
if (cmd == null) {
|
||||||
|
System.err.printf("Unknown command: %s%n%n", args[0]);
|
||||||
|
printGlobalUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取命令余下参数(不包含命令名)
|
||||||
|
String[] sub = Arrays.copyOfRange(args, 1, args.length);
|
||||||
|
try {
|
||||||
|
return cmd.execute(sub);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.printf("Error executing command '%s': %s%n",
|
||||||
|
cmd.name(), e.getMessage());
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印全局帮助信息(所有已注册命令的 usage)。
|
||||||
|
*/
|
||||||
|
private void printGlobalUsage() {
|
||||||
|
System.out.println("""
|
||||||
|
Snow Programming Language CLI
|
||||||
|
Usage: snow <command> [options]
|
||||||
|
|
||||||
|
Available commands:""");
|
||||||
|
int pad = registry.keySet().stream()
|
||||||
|
.mapToInt(String::length).max().orElse(10) + 2;
|
||||||
|
registry.values().stream()
|
||||||
|
.sorted(Comparator.comparing(CLICommand::name))
|
||||||
|
.forEach(c -> System.out.printf(" %-" + pad + "s%s%n",
|
||||||
|
c.name(), c.description()));
|
||||||
|
|
||||||
|
System.out.println("""
|
||||||
|
|
||||||
|
Use 'snow <command> --help' for command-specific details.
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 程序主入口。
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int code = new SnowCLI().run(args);
|
||||||
|
System.exit(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,143 +0,0 @@
|
|||||||
package org.jcnc.snow.compiler.cli;
|
|
||||||
|
|
||||||
import org.jcnc.snow.compiler.backend.alloc.RegisterAllocator;
|
|
||||||
import org.jcnc.snow.compiler.backend.builder.VMCodeGenerator;
|
|
||||||
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
|
|
||||||
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
|
|
||||||
import org.jcnc.snow.compiler.backend.generator.InstructionGeneratorProvider;
|
|
||||||
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
|
|
||||||
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
|
||||||
import org.jcnc.snow.compiler.ir.core.IRInstruction;
|
|
||||||
import org.jcnc.snow.compiler.ir.core.IRProgram;
|
|
||||||
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
|
||||||
import org.jcnc.snow.compiler.lexer.core.LexerEngine;
|
|
||||||
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
|
||||||
import org.jcnc.snow.compiler.parser.context.ParserContext;
|
|
||||||
import org.jcnc.snow.compiler.parser.core.ParserEngine;
|
|
||||||
import org.jcnc.snow.compiler.parser.function.ASTPrinter;
|
|
||||||
import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner;
|
|
||||||
import org.jcnc.snow.vm.engine.VMMode;
|
|
||||||
import org.jcnc.snow.vm.engine.VirtualMachineEngine;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SnowCompiler CLI —— 多文件 / 单文件 / 目录 模式。
|
|
||||||
*/
|
|
||||||
public class SnowCompiler {
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
if (args.length == 0) {
|
|
||||||
System.err.println("""
|
|
||||||
Usage:
|
|
||||||
snow <file1.snow> [file2.snow …]
|
|
||||||
snow -d <srcDir> (compile all *.snow recursively)
|
|
||||||
""");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- 1. 收集所有待编译源码 ---------- */
|
|
||||||
List<Path> sources = collectSources(args);
|
|
||||||
if (sources.isEmpty()) {
|
|
||||||
System.err.println("No .snow source files found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- 2. 逐个词法+语法分析,合并 AST ---------- */
|
|
||||||
List<Node> allAst = new ArrayList<>();
|
|
||||||
for (Path p : sources) {
|
|
||||||
if (!Files.exists(p)) {
|
|
||||||
System.err.println("File not found: " + p);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String code = Files.readString(p, StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// 保持原有“## 源代码”打印,但标注文件名,兼容旧脚本
|
|
||||||
System.out.println("## 源代码 (" + p.getFileName() + ")");
|
|
||||||
System.out.println(code);
|
|
||||||
|
|
||||||
LexerEngine lexer = new LexerEngine(code, p.toString());
|
|
||||||
ParserContext ctx = new ParserContext(lexer.getAllTokens(), p.toString());
|
|
||||||
allAst.addAll(new ParserEngine(ctx).parse());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- 3. 语义分析 ---------- */
|
|
||||||
SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false);
|
|
||||||
|
|
||||||
/* ---------- 4. AST → IR ---------- */
|
|
||||||
IRProgram program = new IRProgramBuilder().buildProgram(allAst);
|
|
||||||
program = reorderForEntry(program); // 保证入口 main 在首位
|
|
||||||
|
|
||||||
System.out.println("## 编译器输出");
|
|
||||||
System.out.println("### AST");
|
|
||||||
ASTPrinter.printJson(allAst);
|
|
||||||
System.out.println("### IR");
|
|
||||||
System.out.println(program);
|
|
||||||
|
|
||||||
/* ---------- 5. IR → VM 指令 ---------- */
|
|
||||||
VMProgramBuilder builder = new VMProgramBuilder();
|
|
||||||
List<InstructionGenerator<? extends IRInstruction>> generators = InstructionGeneratorProvider.defaultGenerators();
|
|
||||||
|
|
||||||
for (IRFunction fn : program.functions()) {
|
|
||||||
Map<IRVirtualRegister, Integer> slotMap =
|
|
||||||
new RegisterAllocator().allocate(fn);
|
|
||||||
new VMCodeGenerator(slotMap, builder, generators).generate(fn);
|
|
||||||
}
|
|
||||||
List<String> finalCode = builder.build();
|
|
||||||
|
|
||||||
System.out.println("### VM code");
|
|
||||||
finalCode.forEach(System.out::println);
|
|
||||||
|
|
||||||
/* ---------- 6. 运行虚拟机 ---------- */
|
|
||||||
VirtualMachineEngine vm = new VirtualMachineEngine(VMMode.RUN);
|
|
||||||
vm.execute(finalCode);
|
|
||||||
vm.printLocalVariables();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据参数收集待编译文件:
|
|
||||||
* - snow file1 file2 … ← 多文件 / 单文件
|
|
||||||
* - snow -d srcDir ← 目录递归所有 *.snow
|
|
||||||
*/
|
|
||||||
private static List<Path> collectSources(String[] args) throws IOException {
|
|
||||||
if (args.length == 2 && "-d".equals(args[0])) {
|
|
||||||
Path dir = Path.of(args[1]);
|
|
||||||
if (!Files.isDirectory(dir)) {
|
|
||||||
System.err.println("Not a directory: " + dir);
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
try (var stream = Files.walk(dir)) {
|
|
||||||
return stream.filter(p -> p.toString().endsWith(".snow"))
|
|
||||||
.sorted() // 稳定顺序,方便比对输出
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 普通文件参数
|
|
||||||
return Arrays.stream(args).map(Path::of).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 把 main 函数放到 Program.functions()[0],保证 PC=0 即入口;
|
|
||||||
* 如果用户未写 main,则保持原顺序(语义分析会报错)。
|
|
||||||
*/
|
|
||||||
private static IRProgram reorderForEntry(IRProgram in) {
|
|
||||||
List<IRFunction> ordered = new ArrayList<>(in.functions());
|
|
||||||
int idx = -1;
|
|
||||||
for (int i = 0; i < ordered.size(); i++) {
|
|
||||||
if ("main".equals(ordered.get(i).name())) {
|
|
||||||
idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx > 0) Collections.swap(ordered, 0, idx);
|
|
||||||
|
|
||||||
IRProgram out = new IRProgram();
|
|
||||||
ordered.forEach(out::add);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,233 @@
|
|||||||
|
package org.jcnc.snow.compiler.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.compiler.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.compiler.backend.alloc.RegisterAllocator;
|
||||||
|
import org.jcnc.snow.compiler.backend.builder.VMCodeGenerator;
|
||||||
|
import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
|
||||||
|
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
|
||||||
|
import org.jcnc.snow.compiler.backend.generator.InstructionGeneratorProvider;
|
||||||
|
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
|
||||||
|
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
||||||
|
import org.jcnc.snow.compiler.ir.core.IRInstruction;
|
||||||
|
import org.jcnc.snow.compiler.ir.core.IRProgram;
|
||||||
|
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
||||||
|
import org.jcnc.snow.compiler.lexer.core.LexerEngine;
|
||||||
|
import org.jcnc.snow.compiler.parser.ast.base.Node;
|
||||||
|
import org.jcnc.snow.compiler.parser.context.ParserContext;
|
||||||
|
import org.jcnc.snow.compiler.parser.core.ParserEngine;
|
||||||
|
import org.jcnc.snow.compiler.parser.function.ASTPrinter;
|
||||||
|
import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner;
|
||||||
|
import org.jcnc.snow.vm.VMLauncher;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 编译器命令行入口:实现 `snow compile` 命令。
|
||||||
|
* 支持编译一个或多个 .snow 源文件为 VM 字节码文件,或编译后直接运行。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>支持编译单个文件、多个文件、或目录递归批量编译。</li>
|
||||||
|
* <li>支持 compile-only 或 compile-then-run 模式。</li>
|
||||||
|
* <li>完成词法、语法、语义分析,IR 构建,寄存器分配,VM 指令生成。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 用法:<br>
|
||||||
|
* <code>snow compile foo.snow</code><br>
|
||||||
|
* <code>snow compile -d src</code><br>
|
||||||
|
* <code>snow compile run foo.snow</code>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class CompileCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令名称。
|
||||||
|
*
|
||||||
|
* @return 命令名 "compile"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() { return "compile"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令的简要描述。
|
||||||
|
*
|
||||||
|
* @return 命令描述文本
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Compile .snow source files into VM byte-code.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印用法说明。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("""
|
||||||
|
Usage:
|
||||||
|
snow compile <file1.snow> [file2.snow …] (compile only)
|
||||||
|
snow compile -d <srcDir> (recursively compile)
|
||||||
|
snow compile run <file.snow> […] (compile then run)
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 compile 命令。
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
* @return 0 表示成功,非0表示错误
|
||||||
|
* @throws Exception 发生任何编译、文件、运行错误
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
// 0. 检查是否包含 run
|
||||||
|
boolean runAfterCompile = false;
|
||||||
|
int offset = 0;
|
||||||
|
if (args.length > 0 && "run".equals(args[0])) {
|
||||||
|
runAfterCompile = true;
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
if (args.length - offset == 0) {
|
||||||
|
printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String[] srcArgs = Arrays.copyOfRange(args, offset, args.length);
|
||||||
|
|
||||||
|
List<Path> sources = collectSources(srcArgs);
|
||||||
|
if (sources.isEmpty()) {
|
||||||
|
System.err.println("No .snow source files found.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 词法+语法分析,合并AST
|
||||||
|
List<Node> allAst = new ArrayList<>();
|
||||||
|
for (Path p : sources) {
|
||||||
|
if (!Files.exists(p)) {
|
||||||
|
System.err.println("File not found: " + p);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String code = Files.readString(p, StandardCharsets.UTF_8);
|
||||||
|
LexerEngine lexer = new LexerEngine(code, p.toString());
|
||||||
|
ParserContext ctx = new ParserContext(lexer.getAllTokens(), p.toString());
|
||||||
|
allAst.addAll(new ParserEngine(ctx).parse());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 语义分析
|
||||||
|
SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false);
|
||||||
|
|
||||||
|
// 3. AST -> IR
|
||||||
|
IRProgram program = new IRProgramBuilder().buildProgram(allAst);
|
||||||
|
program = reorderForEntry(program); // 保证入口 main 在首位
|
||||||
|
|
||||||
|
System.out.println("## 编译器输出");
|
||||||
|
System.out.println("### AST");
|
||||||
|
ASTPrinter.printJson(allAst);
|
||||||
|
System.out.println("### IR");
|
||||||
|
System.out.println(program);
|
||||||
|
|
||||||
|
// 4. IR -> VM指令
|
||||||
|
VMProgramBuilder builder = new VMProgramBuilder();
|
||||||
|
List<InstructionGenerator<? extends IRInstruction>> generators =
|
||||||
|
InstructionGeneratorProvider.defaultGenerators();
|
||||||
|
|
||||||
|
for (IRFunction fn : program.functions()) {
|
||||||
|
Map<IRVirtualRegister, Integer> slotMap =
|
||||||
|
new RegisterAllocator().allocate(fn);
|
||||||
|
new VMCodeGenerator(slotMap, builder, generators).generate(fn);
|
||||||
|
}
|
||||||
|
List<String> finalCode = builder.build();
|
||||||
|
|
||||||
|
System.out.println("### VM code");
|
||||||
|
finalCode.forEach(System.out::println);
|
||||||
|
|
||||||
|
// 5. 将VM指令写入文件
|
||||||
|
Path outputFile = deriveOutputPath(sources);
|
||||||
|
Files.write(outputFile, finalCode, StandardCharsets.UTF_8);
|
||||||
|
System.out.println("Written to " + outputFile.toAbsolutePath());
|
||||||
|
|
||||||
|
// 6. 若指定run,立即执行
|
||||||
|
if (runAfterCompile) {
|
||||||
|
System.out.println("\n=== Launching VM ===");
|
||||||
|
VMLauncher.main(new String[]{outputFile.toString()});
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集所有待编译的源文件。
|
||||||
|
* <ul>
|
||||||
|
* <li>参数 "-d <srcDir>":递归收集目录下所有 .snow 文件</li>
|
||||||
|
* <li>否则认为是文件列表参数</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
* @return 源文件路径列表,若未找到返回空列表
|
||||||
|
* @throws IOException 文件IO错误
|
||||||
|
*/
|
||||||
|
private static List<Path> collectSources(String[] args) throws IOException {
|
||||||
|
if (args.length == 2 && "-d".equals(args[0])) {
|
||||||
|
Path dir = Path.of(args[1]);
|
||||||
|
if (!Files.isDirectory(dir)) {
|
||||||
|
System.err.println("Not a directory: " + dir);
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
try (var stream = Files.walk(dir)) {
|
||||||
|
return stream.filter(p -> p.toString().endsWith(".snow"))
|
||||||
|
.sorted() // 稳定顺序,方便比对输出
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 普通文件参数
|
||||||
|
return Arrays.stream(args).map(Path::of).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保证 main 函数排在 Program.functions()[0],使 PC=0 即为程序入口。
|
||||||
|
* <ul>
|
||||||
|
* <li>若存在 main,则与第0个函数互换</li>
|
||||||
|
* <li>若不存在 main,则顺序不变(后续语义分析会报错)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param in 原始IR程序对象
|
||||||
|
* @return 处理后的IR程序对象
|
||||||
|
*/
|
||||||
|
private static IRProgram reorderForEntry(IRProgram in) {
|
||||||
|
List<IRFunction> ordered = new ArrayList<>(in.functions());
|
||||||
|
int idx = -1;
|
||||||
|
for (int i = 0; i < ordered.size(); i++) {
|
||||||
|
if ("main".equals(ordered.get(i).name())) {
|
||||||
|
idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx > 0) Collections.swap(ordered, 0, idx);
|
||||||
|
|
||||||
|
IRProgram out = new IRProgram();
|
||||||
|
ordered.forEach(out::add);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推断输出文件名。
|
||||||
|
* <ul>
|
||||||
|
* <li>单文件编译:输出同名 .vm 文件</li>
|
||||||
|
* <li>多文件/目录编译:输出 "program.vm" 到当前目录</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param sources 输入源文件路径列表
|
||||||
|
* @return 输出的 VM 文件路径
|
||||||
|
*/
|
||||||
|
private static Path deriveOutputPath(List<Path> sources) {
|
||||||
|
if (sources.size() == 1) {
|
||||||
|
Path src = sources.getFirst();
|
||||||
|
String name = src.getFileName().toString()
|
||||||
|
.replaceFirst("\\.snow$", "");
|
||||||
|
return src.resolveSibling(name + ".vm");
|
||||||
|
}
|
||||||
|
return Path.of("program.vm");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package org.jcnc.snow.compiler.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.compiler.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.vm.VMLauncher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 命令实现:`snow run`
|
||||||
|
* <br>
|
||||||
|
* 用于运行已编译的 VM 字节码文件(.vm)。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>支持传递额外 VM 参数。</li>
|
||||||
|
* <li>实际执行通过 {@link VMLauncher#main(String[])} 入口完成。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 用法:<br>
|
||||||
|
* <code>snow run program.vm</code>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class RunCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令名。
|
||||||
|
*
|
||||||
|
* @return "run"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() { return "run"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取命令描述。
|
||||||
|
*
|
||||||
|
* @return 命令简介
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Execute compiled VM instructions.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印该命令的用法说明。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("""
|
||||||
|
Usage: snow run <program.vm> [additional VM options]
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 run 命令,运行 VM 指令文件。
|
||||||
|
*
|
||||||
|
* @param args 剩余参数(不含命令名),第一个应为 .vm 文件路径
|
||||||
|
* @return 0 表示成功,1 表示参数错误
|
||||||
|
* @throws Exception 运行 VM 时可能抛出的异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
if (args.length == 0) {
|
||||||
|
printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// 直接复用 VM 启动逻辑
|
||||||
|
VMLauncher.main(args);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user