feat: 修改为编译为water文件
This commit is contained in:
parent
a8d9bbe81d
commit
9bb624f024
@ -9,7 +9,6 @@ package org.jcnc.snow.compiler.cli;
|
||||
* <li>可通过 {@link java.util.ServiceLoader ServiceLoader} 自动发现,或直接在 {@link SnowCLI} 注册。</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
|
||||
*/
|
||||
public interface CLICommand {
|
||||
|
||||
@ -31,7 +30,8 @@ public interface CLICommand {
|
||||
* 打印命令的专用 usage 信息(可选实现)。
|
||||
* 可覆盖此方法自定义帮助信息,默认无操作。
|
||||
*/
|
||||
default void printUsage() {}
|
||||
default void printUsage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令逻辑。
|
||||
|
||||
@ -91,7 +91,6 @@ public final class SnowCLI {
|
||||
*/
|
||||
private void printGlobalUsage() {
|
||||
System.out.println("""
|
||||
Snow Programming Language CLI
|
||||
Usage: snow <command> [options]
|
||||
|
||||
Available commands:""");
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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.cli.CLICommand;
|
||||
import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder;
|
||||
import org.jcnc.snow.compiler.ir.core.IRFunction;
|
||||
import org.jcnc.snow.compiler.ir.core.IRInstruction;
|
||||
@ -19,91 +19,119 @@ 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>
|
||||
* 支持编译一个或多个 .snow 源文件为 VM 字节码文件,选项 -o 指定输出名称(不含后缀),
|
||||
* -d 递归目录编译(输出名自动取目录名),输出后缀统一为 .water。
|
||||
*/
|
||||
public final class CompileCommand implements CLICommand {
|
||||
|
||||
/**
|
||||
* 获取命令名称。
|
||||
*
|
||||
* @return 命令名 "compile"
|
||||
*/
|
||||
@Override
|
||||
public String name() { return "compile"; }
|
||||
public String name() {
|
||||
return "compile";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令的简要描述。
|
||||
*
|
||||
* @return 命令描述文本
|
||||
*/
|
||||
@Override
|
||||
public String description() {
|
||||
return "Compile .snow source files into VM byte-code.";
|
||||
return "Compile .snow source files into VM byte-code (.water).";
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印用法说明。
|
||||
*/
|
||||
@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)
|
||||
""");
|
||||
System.out.println("Usage:");
|
||||
System.out.println(" snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]");
|
||||
System.out.println("Options:");
|
||||
System.out.println(" run compile then run");
|
||||
System.out.println(" -o <name> specify output base name (without .water suffix)");
|
||||
System.out.println(" -d <srcDir> recursively compile all .snow files in directory");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 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])) {
|
||||
String outputName = null;
|
||||
Path dir = null;
|
||||
List<Path> sources = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
switch (arg) {
|
||||
case "run":
|
||||
runAfterCompile = true;
|
||||
offset = 1;
|
||||
}
|
||||
if (args.length - offset == 0) {
|
||||
break;
|
||||
case "-o":
|
||||
if (i + 1 < args.length) {
|
||||
outputName = args[++i];
|
||||
} else {
|
||||
System.err.println("Missing argument for -o");
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
String[] srcArgs = Arrays.copyOfRange(args, offset, args.length);
|
||||
break;
|
||||
case "-d":
|
||||
if (i + 1 < args.length) {
|
||||
dir = Path.of(args[++i]);
|
||||
} else {
|
||||
System.err.println("Missing argument for -d");
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (arg.endsWith(".snow")) {
|
||||
sources.add(Path.of(arg));
|
||||
} else {
|
||||
System.err.println("Unknown option or file: " + arg);
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 目录编译时收集所有 .snow
|
||||
if (dir != null) {
|
||||
if (!Files.isDirectory(dir)) {
|
||||
System.err.println("Not a directory: " + dir);
|
||||
return 1;
|
||||
}
|
||||
try (var stream = Files.walk(dir)) {
|
||||
stream.filter(p -> p.toString().endsWith(".snow"))
|
||||
.sorted()
|
||||
.forEach(sources::add);
|
||||
}
|
||||
}
|
||||
|
||||
List<Path> sources = collectSources(srcArgs);
|
||||
if (sources.isEmpty()) {
|
||||
System.err.println("No .snow source files found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1. 词法+语法分析,合并AST
|
||||
// 多文件非目录编译时必须指定 -o
|
||||
if (sources.size() > 1 && outputName == null && dir == null) {
|
||||
System.err.println("Please specify output name using -o <name>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1. 词法+语法分析
|
||||
List<Node> allAst = new ArrayList<>();
|
||||
for (Path p : sources) {
|
||||
if (!Files.exists(p)) {
|
||||
@ -119,9 +147,9 @@ public final class CompileCommand implements CLICommand {
|
||||
// 2. 语义分析
|
||||
SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false);
|
||||
|
||||
// 3. AST -> IR
|
||||
// 3. AST -> IR 并重排序入口
|
||||
IRProgram program = new IRProgramBuilder().buildProgram(allAst);
|
||||
program = reorderForEntry(program); // 保证入口 main 在首位
|
||||
program = reorderForEntry(program);
|
||||
|
||||
System.out.println("## 编译器输出");
|
||||
System.out.println("### AST");
|
||||
@ -129,11 +157,10 @@ public final class CompileCommand implements CLICommand {
|
||||
System.out.println("### IR");
|
||||
System.out.println(program);
|
||||
|
||||
// 4. IR -> VM指令
|
||||
// 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);
|
||||
@ -144,12 +171,12 @@ public final class CompileCommand implements CLICommand {
|
||||
System.out.println("### VM code");
|
||||
finalCode.forEach(System.out::println);
|
||||
|
||||
// 5. 将VM指令写入文件
|
||||
Path outputFile = deriveOutputPath(sources);
|
||||
// 5. 写出 .water 文件
|
||||
Path outputFile = deriveOutputPath(sources, outputName, dir);
|
||||
Files.write(outputFile, finalCode, StandardCharsets.UTF_8);
|
||||
System.out.println("Written to " + outputFile.toAbsolutePath());
|
||||
|
||||
// 6. 若指定run,立即执行
|
||||
// 6. 执行
|
||||
if (runAfterCompile) {
|
||||
System.out.println("\n=== Launching VM ===");
|
||||
VMLauncher.main(new String[]{outputFile.toString()});
|
||||
@ -158,76 +185,18 @@ public final class CompileCommand implements CLICommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集所有待编译的源文件。
|
||||
* <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程序对象
|
||||
* 保证 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;
|
||||
Collections.swap(ordered, 0, 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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import org.jcnc.snow.vm.VMLauncher;
|
||||
* <p>
|
||||
* 命令实现:`snow run`
|
||||
* <br>
|
||||
* 用于运行已编译的 VM 字节码文件(.vm)。
|
||||
* 用于运行已编译的 VM 字节码文件(.water)。
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>支持传递额外 VM 参数。</li>
|
||||
@ -15,7 +15,7 @@ import org.jcnc.snow.vm.VMLauncher;
|
||||
* </ul>
|
||||
* <p>
|
||||
* 用法:<br>
|
||||
* <code>snow run program.vm</code>
|
||||
* <code>snow run program.water</code>
|
||||
* </p>
|
||||
*/
|
||||
public final class RunCommand implements CLICommand {
|
||||
@ -26,7 +26,9 @@ public final class RunCommand implements CLICommand {
|
||||
* @return "run"
|
||||
*/
|
||||
@Override
|
||||
public String name() { return "run"; }
|
||||
public String name() {
|
||||
return "run";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令描述。
|
||||
@ -44,7 +46,7 @@ public final class RunCommand implements CLICommand {
|
||||
@Override
|
||||
public void printUsage() {
|
||||
System.out.println("""
|
||||
Usage: snow run <program.vm> [additional VM options]
|
||||
Usage: snow run <program.water> [additional VM options]
|
||||
""");
|
||||
}
|
||||
|
||||
@ -61,7 +63,6 @@ public final class RunCommand implements CLICommand {
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
// 直接复用 VM 启动逻辑
|
||||
VMLauncher.main(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user