diff --git a/src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java b/src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java index 4e342af..80b5169 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/CLICommand.java @@ -9,7 +9,6 @@ package org.jcnc.snow.compiler.cli; *
  • 可通过 {@link java.util.ServiceLoader ServiceLoader} 自动发现,或直接在 {@link SnowCLI} 注册。
  • * *

    - */ public interface CLICommand { @@ -31,7 +30,8 @@ public interface CLICommand { * 打印命令的专用 usage 信息(可选实现)。 * 可覆盖此方法自定义帮助信息,默认无操作。 */ - default void printUsage() {} + default void printUsage() { + } /** * 执行命令逻辑。 diff --git a/src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java b/src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java index 1d1dbf4..d45675c 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/SnowCLI.java @@ -91,7 +91,6 @@ public final class SnowCLI { */ private void printGlobalUsage() { System.out.println(""" - Snow Programming Language CLI Usage: snow [options] Available commands:"""); diff --git a/src/main/java/org/jcnc/snow/compiler/cli/commands/CompileCommand.java b/src/main/java/org/jcnc/snow/compiler/cli/commands/CompileCommand.java index cfa7e36..d26fa20 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/commands/CompileCommand.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/commands/CompileCommand.java @@ -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.*; /** - *

    * 编译器命令行入口:实现 `snow compile` 命令。 - * 支持编译一个或多个 .snow 源文件为 VM 字节码文件,或编译后直接运行。 - *

    - * - *

    - * 用法:
    - * snow compile foo.snow
    - * snow compile -d src
    - * snow compile run foo.snow - *

    + * 支持编译一个或多个 .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 [file2.snow …] (compile only) - snow compile -d (recursively compile) - snow compile run […] (compile then run) - """); + System.out.println("Usage:"); + System.out.println(" snow compile [run] [-o ] [-d ] [file1.snow file2.snow …]"); + System.out.println("Options:"); + System.out.println(" run compile then run"); + System.out.println(" -o specify output base name (without .water suffix)"); + System.out.println(" -d recursively compile all .snow files in directory"); + } + + private static Path deriveOutputPath(List 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])) { - runAfterCompile = true; - offset = 1; - } - if (args.length - offset == 0) { - printUsage(); - return 1; - } - String[] srcArgs = Arrays.copyOfRange(args, offset, args.length); + String outputName = null; + Path dir = null; + List sources = new ArrayList<>(); + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + switch (arg) { + case "run": + runAfterCompile = true; + break; + case "-o": + if (i + 1 < args.length) { + outputName = args[++i]; + } else { + System.err.println("Missing argument for -o"); + printUsage(); + return 1; + } + 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 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 "); + return 1; + } + + // 1. 词法+语法分析 List 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> generators = InstructionGeneratorProvider.defaultGenerators(); - for (IRFunction fn : program.functions()) { Map 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 { } /** - * 收集所有待编译的源文件。 - *
      - *
    • 参数 "-d ":递归收集目录下所有 .snow 文件
    • - *
    • 否则认为是文件列表参数
    • - *
    - * - * @param args 命令行参数 - * @return 源文件路径列表,若未找到返回空列表 - * @throws IOException 文件IO错误 - */ - private static List 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,则与第0个函数互换
    • - *
    • 若不存在 main,则顺序不变(后续语义分析会报错)
    • - *
    - * - * @param in 原始IR程序对象 - * @return 处理后的IR程序对象 + * 保证 main 函数为程序入口 */ private static IRProgram reorderForEntry(IRProgram in) { List 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; } - - /** - * 推断输出文件名。 - *
      - *
    • 单文件编译:输出同名 .vm 文件
    • - *
    • 多文件/目录编译:输出 "program.vm" 到当前目录
    • - *
    - * - * @param sources 输入源文件路径列表 - * @return 输出的 VM 文件路径 - */ - private static Path deriveOutputPath(List 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"); - } } diff --git a/src/main/java/org/jcnc/snow/compiler/cli/commands/RunCommand.java b/src/main/java/org/jcnc/snow/compiler/cli/commands/RunCommand.java index fb737d4..92373a5 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/commands/RunCommand.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/commands/RunCommand.java @@ -7,7 +7,7 @@ import org.jcnc.snow.vm.VMLauncher; *

    * 命令实现:`snow run` *
    - * 用于运行已编译的 VM 字节码文件(.vm)。 + * 用于运行已编译的 VM 字节码文件(.water)。 *

    *
      *
    • 支持传递额外 VM 参数。
    • @@ -15,7 +15,7 @@ import org.jcnc.snow.vm.VMLauncher; *
    *

    * 用法:
    - * snow run program.vm + * snow run program.water *

    */ 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 [additional VM options] + Usage: snow run [additional VM options] """); } @@ -61,7 +63,6 @@ public final class RunCommand implements CLICommand { printUsage(); return 1; } - // 直接复用 VM 启动逻辑 VMLauncher.main(args); return 0; }