From 004bc076b1cc377999632ced994fd9d81c1279ac Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 23 Jun 2025 23:46:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86=E7=9C=9F=E6=AD=A3=E7=9A=84?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=AE=9E=E7=8E=B0=E4=B8=8B=E6=B2=89=E5=88=B0?= =?UTF-8?q?=20pkg=20=E5=B1=82=EF=BC=8C=E9=81=BF=E5=85=8D=20CLI=20=E4=B8=8E?= =?UTF-8?q?=20pkg=20=E7=9A=84=E9=87=8D=E5=A4=8D=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snow/cli/commands/CompileCommand.java | 236 +--------------- .../jcnc/snow/cli/commands/RunCommand.java | 48 +--- .../org/jcnc/snow/pkg/tasks/CompileTask.java | 254 ++++++++++++++++-- .../java/org/jcnc/snow/pkg/tasks/RunTask.java | 39 +++ 4 files changed, 294 insertions(+), 283 deletions(-) create mode 100644 src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java diff --git a/src/main/java/org/jcnc/snow/cli/commands/CompileCommand.java b/src/main/java/org/jcnc/snow/cli/commands/CompileCommand.java index 8d77e8d..6cc11e2 100644 --- a/src/main/java/org/jcnc/snow/cli/commands/CompileCommand.java +++ b/src/main/java/org/jcnc/snow/cli/commands/CompileCommand.java @@ -1,48 +1,20 @@ package org.jcnc.snow.cli.commands; -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.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; -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.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; +import org.jcnc.snow.pkg.dsl.CloudDSLParser; +import org.jcnc.snow.pkg.model.Project; +import org.jcnc.snow.pkg.tasks.CompileTask; +import java.nio.file.Paths; /** - * CLI 命令:将 .snow 源文件编译为 VM 字节码(.water 文件)。 + * CLI 命令:编译当前项目。 *

- * 支持递归目录、多文件编译,可选编译后立即运行。
- * 命令参数支持 run、-o、-d 等。 + * 负责读取项目描述文件并委托给 {@link CompileTask}, *

- * - *
- * 用法示例:
- * $ snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]
- * 
*/ public final class CompileCommand implements CLICommand { - /* --------------------------------------------------------------------- */ - /* CLICommand 接口实现 */ - /* --------------------------------------------------------------------- */ - @Override public String name() { return "compile"; @@ -60,198 +32,14 @@ public final class CompileCommand implements CLICommand { 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"); - } - - /* --------------------------------------------------------------------- */ - /* 核心:执行 compile 子命令 */ - /* --------------------------------------------------------------------- */ + System.out.println(" -d recursively compile all .snow files in directory"); } @Override public int execute(String[] args) throws Exception { - /* ---------------- 解析命令行参数 ---------------- */ - boolean runAfterCompile = false; - 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; - case "-o" -> { - if (i + 1 < args.length) outputName = args[++i]; - else { - System.err.println("Missing argument for -o"); - printUsage(); - return 1; - } - } - case "-d" -> { - if (i + 1 < args.length) dir = Path.of(args[++i]); - else { - System.err.println("Missing argument for -d"); - printUsage(); - return 1; - } - } - 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); - } - } - - if (sources.isEmpty()) { - System.err.println("No .snow source files found."); - return 1; - } - - /* 多文件但未指定 -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<>(); - - System.out.println("## 编译器输出"); - System.out.println("### Snow 源代码"); // ========== 新增:二级标题 ========== - - 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); - - // ------- 打印每个文件的源码 ------- - 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()); - } - - /* ----------------------------------------------------------------- */ - /* 2. 语义分析 */ - /* ----------------------------------------------------------------- */ - SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false); - - /* ----------------------------------------------------------------- */ - /* 3. AST → IR,并把 main 函数调到首位 */ - /* ----------------------------------------------------------------- */ - IRProgram program = new IRProgramBuilder().buildProgram(allAst); - program = reorderForEntry(program); - - /* ---------------- 打印 AST / IR ---------------- */ - System.out.println("### AST"); - ASTPrinter.printJson(allAst); - - System.out.println("### IR"); - System.out.println(program); - - /* ----------------------------------------------------------------- */ - /* 4. IR → VM 指令 */ - /* ----------------------------------------------------------------- */ - VMProgramBuilder builder = new VMProgramBuilder(); - List> generators = - InstructionGeneratorProvider.defaultGenerators(); - - for (IRFunction fn : program.functions()) { - Map slotMap = - new RegisterAllocator().allocate(fn); - new VMCodeGenerator(slotMap, builder, generators).generate(fn); - } - List finalCode = builder.build(); - - System.out.println("### VM code"); - finalCode.forEach(System.out::println); - - /* ----------------------------------------------------------------- */ - /* 5. 写出 .water 文件 */ - /* ----------------------------------------------------------------- */ - Path outputFile = deriveOutputPath(sources, outputName, dir); - Files.write(outputFile, finalCode, StandardCharsets.UTF_8); - System.out.println("Written to " + outputFile.toAbsolutePath()); - - /* ----------------------------------------------------------------- */ - /* 6. 可选:立即运行 VM */ - /* ----------------------------------------------------------------- */ - if (runAfterCompile) { - System.out.println("\n=== Launching VM ==="); - VMLauncher.main(new String[]{outputFile.toString()}); - } - + // 解析云项目描述文件(默认为工作目录下的 cloud.snow) + Project project = CloudDSLParser.parse(Paths.get("project.cloud")); + // 委托给 CompileTask 处理核心编译逻辑 + new CompileTask(project, args).run(); return 0; } - - /* --------------------------------------------------------------------- */ - /* 辅助方法 */ - /* --------------------------------------------------------------------- */ - - /** - * 根据输入情况推断 .water 输出文件名: - *
    - *
  • 若指定 -o,则直接使用
  • - *
  • 目录编译:取目录名
  • - *
  • 单文件编译:取文件名去掉 .snow
  • - *
  • 其他情况兜底为 "program"
  • - *
- */ - 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"); - } - - /** - * 把 main 函数交换到程序函数列表首位,确保 PC=0 即入口。 - */ - private static IRProgram reorderForEntry(IRProgram in) { - List 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; - } -} +} \ No newline at end of file diff --git a/src/main/java/org/jcnc/snow/cli/commands/RunCommand.java b/src/main/java/org/jcnc/snow/cli/commands/RunCommand.java index 09a10d4..c81e6f8 100644 --- a/src/main/java/org/jcnc/snow/cli/commands/RunCommand.java +++ b/src/main/java/org/jcnc/snow/cli/commands/RunCommand.java @@ -1,65 +1,41 @@ + package org.jcnc.snow.cli.commands; import org.jcnc.snow.cli.CLICommand; -import org.jcnc.snow.vm.VMLauncher; +import org.jcnc.snow.pkg.tasks.RunTask; /** * CLI 命令:运行已编译的 VM 字节码文件(.water)。 *

- * 用于执行 VM 程序文件。支持传递额外 VM 参数,实际运行由 {@link VMLauncher#main(String[])} 完成。 + * 仅解析参数并委托给 {@link RunTask}, + * 将 VM 运行逻辑下沉至 pkg 层,保持 CLI 无状态、薄封装。 *

- * - *
- * 用法示例:
- * $ snow run program.water [additional VM options]
- * 
*/ public final class RunCommand implements CLICommand { - /** - * 返回命令名,用于 CLI 调用。 - * - * @return 命令名称字符串("run") - */ @Override public String name() { return "run"; } - /** - * 返回命令简介,用于 CLI 帮助或命令列表展示。 - * - * @return 命令描述字符串 - */ @Override public String description() { - return "Execute compiled VM instructions."; + return "Run the compiled VM bytecode file (.water)"; } - /** - * 打印该命令的用法说明。 - */ - @Override - public void printUsage() { - System.out.println(""" - Usage: snow run [additional VM options] - """); - } - - /** - * 执行 run 命令,运行指定的 VM 字节码文件。 - * - * @param args 剩余参数(不含命令名),第一个应为 .water 文件路径,其后为可选 VM 参数 - * @return 0 表示执行成功,1 表示参数错误 - * @throws Exception VM 启动或执行过程中可能抛出的异常 - */ @Override public int execute(String[] args) throws Exception { if (args.length == 0) { printUsage(); return 1; } - VMLauncher.main(args); + new RunTask(args).run(); return 0; } + + @Override + public void printUsage() { + System.out.println("Usage:"); + System.out.println(" snow run [vm-options]"); + } } diff --git a/src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java b/src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java index fb3fb99..ae39157 100644 --- a/src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java +++ b/src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java @@ -1,43 +1,251 @@ package org.jcnc.snow.pkg.tasks; +import org.jcnc.snow.cli.commands.CompileCommand; import org.jcnc.snow.pkg.model.Project; +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.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.*; + /** - * 编译项目源代码的任务实现。 + * CLI 命令:将 .snow 源文件编译为 VM 字节码(.water 文件)。 *

- * 实现 {@link Task} 接口,用于构建流程中的编译阶段。当前仅为示例,未集成实际编译器。 + * 支持递归目录、多文件编译,可选编译后立即运行。
+ * 命令参数支持 run、-o、-d 等。 *

+ * + *
+ * 用法示例:
+ * $ snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]
+ * 
*/ public final class CompileTask implements Task { - - /** 待编译的项目 */ + /** 项目信息 */ private final Project project; + /** 原始命令行参数 */ + private final String[] args; - /** - * 创建 CompileTask 实例。 - * - * @param project 目标项目 - */ - public CompileTask(Project project) { + public CompileTask(Project project, String[] args) { this.project = project; + this.args = args; + } + + public CompileTask(Project project) { + this(project, new String[0]); } - /** - * 执行编译任务,打印源代码目录和输出目录。 - *

- * 实际编译尚未实现(TODO)。 - *

- * - * @throws Exception 预留,未来集成编译器可能抛出异常 - */ @Override public void run() throws Exception { - // 获取源码目录和输出目录,默认分别为 "src" 和 "build/classes" - Path srcDir = Path.of(project.getProperties().getOrDefault("src_dir", "src")); - Path outDir = Path.of(project.getProperties().getOrDefault("output_dir", "build/classes")); - System.out.println("[compile] sources=" + srcDir + " output=" + outDir); - // TODO: 集成实际的编译器 + // 将任务委托给原始 execute 实现 + execute(this.args); + } + + /* --------------------------------------------------------------------- */ + /* 执行 compile 子命令 */ + /* --------------------------------------------------------------------- */ + public int execute(String[] args) throws Exception { + /* ---------------- 解析命令行参数 ---------------- */ + boolean runAfterCompile = false; + 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; + case "-o" -> { + if (i + 1 < args.length) outputName = args[++i]; + else { + System.err.println("Missing argument for -o"); + new CompileCommand().printUsage(); + return 1; + } + } + case "-d" -> { + if (i + 1 < args.length) dir = Path.of(args[++i]); + else { + System.err.println("Missing argument for -d"); + new CompileCommand().printUsage(); + return 1; + } + } + default -> { + if (arg.endsWith(".snow")) { + sources.add(Path.of(arg)); + } else { + System.err.println("Unknown option or file: " + arg); + new CompileCommand().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); + } + } + + if (sources.isEmpty()) { + System.err.println("No .snow source files found."); + return 1; + } + + /* 多文件但未指定 -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<>(); + + System.out.println("## 编译器输出"); + System.out.println("### Snow 源代码"); // ========== 新增:二级标题 ========== + + 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); + + // ------- 打印每个文件的源码 ------- + 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()); + } + + /* ----------------------------------------------------------------- */ + /* 2. 语义分析 */ + /* ----------------------------------------------------------------- */ + SemanticAnalyzerRunner.runSemanticAnalysis(allAst, false); + + /* ----------------------------------------------------------------- */ + /* 3. AST → IR,并把 main 函数调到首位 */ + /* ----------------------------------------------------------------- */ + IRProgram program = new IRProgramBuilder().buildProgram(allAst); + program = reorderForEntry(program); + + /* ---------------- 打印 AST / IR ---------------- */ + System.out.println("### AST"); + ASTPrinter.printJson(allAst); + + System.out.println("### IR"); + System.out.println(program); + + /* ----------------------------------------------------------------- */ + /* 4. IR → VM 指令 */ + /* ----------------------------------------------------------------- */ + VMProgramBuilder builder = new VMProgramBuilder(); + List> generators = + InstructionGeneratorProvider.defaultGenerators(); + + for (IRFunction fn : program.functions()) { + Map slotMap = + new RegisterAllocator().allocate(fn); + new VMCodeGenerator(slotMap, builder, generators).generate(fn); + } + List finalCode = builder.build(); + + System.out.println("### VM code"); + finalCode.forEach(System.out::println); + + /* ----------------------------------------------------------------- */ + /* 5. 写出 .water 文件 */ + /* ----------------------------------------------------------------- */ + Path outputFile = deriveOutputPath(sources, outputName, dir); + Files.write(outputFile, finalCode, StandardCharsets.UTF_8); + System.out.println("Written to " + outputFile.toAbsolutePath()); + + /* ----------------------------------------------------------------- */ + /* 6. 可选:立即运行 VM */ + /* ----------------------------------------------------------------- */ + if (runAfterCompile) { + System.out.println("\n=== Launching VM ==="); + VMLauncher.main(new String[]{outputFile.toString()}); + } + + return 0; + } + + /* --------------------------------------------------------------------- */ + /* 辅助方法 */ + /* --------------------------------------------------------------------- */ + + /** + * 根据输入情况推断 .water 输出文件名: + *
    + *
  • 若指定 -o,则直接使用
  • + *
  • 目录编译:取目录名
  • + *
  • 单文件编译:取文件名去掉 .snow
  • + *
  • 其他情况兜底为 "program"
  • + *
+ */ + 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"); + } + + /** + * 把 main 函数交换到程序函数列表首位,确保 PC=0 即入口。 + */ + private static IRProgram reorderForEntry(IRProgram in) { + List 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; } } diff --git a/src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java b/src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java new file mode 100644 index 0000000..1eb871a --- /dev/null +++ b/src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java @@ -0,0 +1,39 @@ + +package org.jcnc.snow.pkg.tasks; + +import org.jcnc.snow.vm.VMLauncher; + +/** + * 任务:执行已编译的 VM 字节码文件(.water)。 + *

+ * 作为 CLI、IDE 插件或其他宿主环境启动虚拟机的统一入口, + * 将调用 {@link VMLauncher#main(String[])} 执行。 + *

+ */ +public final class RunTask implements Task { + + /** 传递给 VM 的完整参数列表(第一个应为 .water 文件路径) */ + private final String[] args; + + /** + * 创建运行任务。 + * + * @param args VM 参数数组(第一个为程序路径,其后为可选参数) + */ + public RunTask(String... args) { + this.args = args; + } + + /** + * 执行运行任务,内部委托 {@link VMLauncher#main(String[])}。 + * + * @throws Exception 虚拟机启动或运行期间抛出的异常 + */ + @Override + public void run() throws Exception { + if (args == null || args.length == 0) { + throw new IllegalArgumentException("VM run requires at least the program file path."); + } + VMLauncher.main(args); + } +}