!16 feat: 支持生成项目脚手架
Merge pull request !16 from Luke/feat/pkg-project-scaffolding-generate
This commit is contained in:
commit
c7a953995a
@ -20,7 +20,7 @@ https://gitee.com/jcnc-org/snow/blob/main/doc/Git-Management/Git-Management.md
|
||||
感谢你的配合!🙏
|
||||
-->
|
||||
|
||||
# 描述 (Description)
|
||||
## 描述 (Description)
|
||||
|
||||
请简要描述本次变更的目的和内容。
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
|
||||
module org.jcnc.snow.compiler {
|
||||
uses org.jcnc.snow.cli.CLICommand;
|
||||
uses CLICommand;
|
||||
requires java.desktop;
|
||||
requires java.logging;
|
||||
exports org.jcnc.snow.compiler.ir.core;
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
package org.jcnc.snow.cli;
|
||||
|
||||
import org.jcnc.snow.cli.commands.CompileCommand;
|
||||
import org.jcnc.snow.cli.commands.RunCommand;
|
||||
import org.jcnc.snow.cli.commands.VersionCommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.cli.commands.*;
|
||||
import org.jcnc.snow.cli.utils.CLIUtils;
|
||||
import org.jcnc.snow.cli.utils.VersionUtils;
|
||||
|
||||
@ -30,14 +29,16 @@ public class SnowCLI {
|
||||
* 值为返回相应 {@link CLICommand} 实例的 Supplier。
|
||||
*/
|
||||
private static final Map<String, Supplier<CLICommand>> COMMANDS = Map.of(
|
||||
"generate", GenerateCommand::new,
|
||||
"compile", CompileCommand::new,
|
||||
"run", RunCommand::new,
|
||||
"version", VersionCommand::new,
|
||||
"init", org.jcnc.snow.cli.commands.InitCommand::new,
|
||||
"build", org.jcnc.snow.cli.commands.BuildCommand::new,
|
||||
"install", org.jcnc.snow.cli.commands.InstallCommand::new,
|
||||
"publish", org.jcnc.snow.cli.commands.PublishCommand::new,
|
||||
"clean", org.jcnc.snow.cli.commands.CleanCommand::new
|
||||
"init", InitCommand::new,
|
||||
"build", BuildCommand::new,
|
||||
"install", InstallCommand::new,
|
||||
"publish", PublishCommand::new,
|
||||
"clean", CleanCommand::new
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
package org.jcnc.snow.cli;
|
||||
package org.jcnc.snow.cli.api;
|
||||
|
||||
import org.jcnc.snow.cli.SnowCLI;
|
||||
|
||||
/**
|
||||
* 所有 CLI 子命令(如 compile、run 等)都必须实现的命令接口。
|
||||
@ -1,6 +1,6 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||
import org.jcnc.snow.pkg.tasks.CleanTask;
|
||||
|
||||
@ -1,48 +1,35 @@
|
||||
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 org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||
import org.jcnc.snow.pkg.model.Project;
|
||||
import org.jcnc.snow.pkg.tasks.CompileTask;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CLI 命令:将 .snow 源文件编译为 VM 字节码(.water 文件)。
|
||||
* <p>
|
||||
* 支持递归目录、多文件编译,可选编译后立即运行。<br>
|
||||
* 命令参数支持 run、-o、-d 等。
|
||||
* </p>
|
||||
* CLI 命令:编译当前项目。
|
||||
*
|
||||
* <pre>
|
||||
* 用法示例:
|
||||
* $ snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]
|
||||
* </pre>
|
||||
* <p>工作模式说明:</p>
|
||||
* <ul>
|
||||
* <li><strong>Cloud 模式</strong>
|
||||
* - 项目根目录存在 {@code project.cloud} 时触发;
|
||||
* - 解析 build 区块,自动推导源码目录与输出文件名;
|
||||
* - 用法:{@code snow compile [run]}</li>
|
||||
* <li><strong>Local 模式</strong>
|
||||
* - 未检测到 {@code project.cloud} 时回退;
|
||||
* - 保持向后兼容:{@code snow compile [run] [-o <name>] [-d <srcDir>] [file.snow …]}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>两种模式均将最终参数交由 {@link CompileTask} 处理。</p>
|
||||
*/
|
||||
public final class CompileCommand implements CLICommand {
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* CLICommand 接口实现 */
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "compile";
|
||||
@ -56,202 +43,50 @@ public final class CompileCommand implements CLICommand {
|
||||
@Override
|
||||
public void printUsage() {
|
||||
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");
|
||||
System.out.println(" snow compile [run] (cloud mode, use project.cloud)");
|
||||
System.out.println(" snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow …] (GOPATH mode)");
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* 核心:执行 compile 子命令 */
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
@Override
|
||||
public int execute(String[] args) throws Exception {
|
||||
/* ---------------- 解析命令行参数 ---------------- */
|
||||
boolean runAfterCompile = false;
|
||||
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;
|
||||
case "-o" -> {
|
||||
if (i + 1 < args.length) outputName = args[++i];
|
||||
Path dslFile = Paths.get("project.cloud");
|
||||
Project project;
|
||||
String[] compileArgs;
|
||||
|
||||
/* ---------- 1. Cloud 模式 ---------- */
|
||||
if (Files.exists(dslFile)) {
|
||||
project = CloudDSLParser.parse(dslFile);
|
||||
|
||||
List<String> argList = new ArrayList<>();
|
||||
|
||||
// 保留用户在 cloud 模式下传入的 “run” 标志
|
||||
for (String a : args) {
|
||||
if ("run".equals(a)) {
|
||||
argList.add("run");
|
||||
}
|
||||
}
|
||||
|
||||
/* 源码目录:build.srcDir -> 默认 src */
|
||||
String srcDir = project.getBuild().get("srcDir", "src");
|
||||
argList.add("-d");
|
||||
argList.add(srcDir);
|
||||
|
||||
/* 输出名称:build.output -> fallback to artifact */
|
||||
String output = project.getBuild().get("output", project.getArtifact());
|
||||
argList.add("-o");
|
||||
argList.add(output);
|
||||
|
||||
compileArgs = argList.toArray(new String[0]);
|
||||
}
|
||||
/* ---------- 2. Local 模式 ---------- */
|
||||
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 <name>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/* 1. 词法 + 语法分析;同时打印源代码 */
|
||||
/* ----------------------------------------------------------------- */
|
||||
List<Node> 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<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. 写出 .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()});
|
||||
project = Project.fromFlatMap(Collections.emptyMap()); // 占位项目,保持接口统一
|
||||
compileArgs = args; // 透传原始 CLI 参数
|
||||
}
|
||||
|
||||
// 委托给 CompileTask 完成实际编译/运行
|
||||
new CompileTask(project, compileArgs).run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* 辅助方法 */
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 根据输入情况推断 .water 输出文件名:
|
||||
* <ul>
|
||||
* <li>若指定 -o,则直接使用</li>
|
||||
* <li>目录编译:取目录名</li>
|
||||
* <li>单文件编译:取文件名去掉 .snow</li>
|
||||
* <li>其他情况兜底为 "program"</li>
|
||||
* </ul>
|
||||
*/
|
||||
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 即入口。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||
import org.jcnc.snow.pkg.model.Project;
|
||||
import org.jcnc.snow.pkg.tasks.GenerateTask;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* CLI 命令:根据 project.cloud 生成项目目录结构。
|
||||
* <p>
|
||||
* 负责解析云项目描述文件,并通过 {@link GenerateTask}
|
||||
* 在 INIT 生命周期阶段内生成基础目录结构。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* 用法示例:
|
||||
* $ snow generate
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 注意事项:
|
||||
* - 若当前目录不存在 project.cloud,则提示用户先执行 `snow init`。
|
||||
* - 执行成功后,会输出已创建的目录/文件。
|
||||
* </p>
|
||||
*/
|
||||
public final class GenerateCommand implements CLICommand {
|
||||
|
||||
/**
|
||||
* 返回命令名称,用于 CLI 调用。
|
||||
*
|
||||
* @return 命令名称,如 "generate"
|
||||
*/
|
||||
@Override
|
||||
public String name() {
|
||||
return "generate";
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||
*
|
||||
* @return 命令描述字符串
|
||||
*/
|
||||
@Override
|
||||
public String description() {
|
||||
return "Generate project directory structure based on project.cloud.";
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印命令用法信息。
|
||||
*/
|
||||
@Override
|
||||
public void printUsage() {
|
||||
System.out.println("Usage: snow generate");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行生成任务。
|
||||
*
|
||||
* @param args CLI 传入的参数数组(此命令不接受参数)
|
||||
* @return 执行结果码(0 表示成功,1 表示 project.cloud 缺失)
|
||||
* @throws Exception 执行过程中出现错误时抛出
|
||||
*/
|
||||
@Override
|
||||
public int execute(String[] args) throws Exception {
|
||||
Path dsl = Paths.get("project.cloud");
|
||||
if (Files.notExists(dsl)) {
|
||||
System.err.println("project.cloud not found. Please run `snow init` first.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 1. 解析 DSL */
|
||||
Project project = CloudDSLParser.parse(dsl);
|
||||
|
||||
/* 2. 执行生成任务 —— 复用 Lifecycle INIT 阶段 */
|
||||
LifecycleManager lm = new LifecycleManager();
|
||||
lm.register(LifecyclePhase.INIT, new GenerateTask(project));
|
||||
lm.executeAll();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,16 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.cli.utils.ProjectCloudExample;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* CLI 命令:创建新的项目骨架。
|
||||
* CLI 命令:初始化项目配置文件。
|
||||
* <p>
|
||||
* 用于快速初始化标准目录结构和 DSL 配置文件(project.cloud)。
|
||||
* 用于快速生成 DSL 配置文件(project.cloud)。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
@ -36,7 +37,7 @@ public final class InitCommand implements CLICommand {
|
||||
*/
|
||||
@Override
|
||||
public String description() {
|
||||
return "Initialize a new project skeleton with directory structure and project.cloud file.";
|
||||
return "Initialize a new project with project.cloud file.";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,11 +45,11 @@ public final class InitCommand implements CLICommand {
|
||||
*/
|
||||
@Override
|
||||
public void printUsage() {
|
||||
System.out.println("Usage: snow init [--lang <lang>]");
|
||||
System.out.println("Usage: snow init");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行项目初始化流程,创建 src 目录和 DSL 配置文件。
|
||||
* 执行项目初始化流程,仅创建 DSL 配置文件。
|
||||
*
|
||||
* @param args CLI 传入的参数数组
|
||||
* @return 执行结果码(0 表示成功)
|
||||
@ -56,19 +57,11 @@ public final class InitCommand implements CLICommand {
|
||||
*/
|
||||
@Override
|
||||
public int execute(String[] args) throws Exception {
|
||||
// 生成 skeleton `.cloud` 文件和 src 目录
|
||||
// 仅生成 `.cloud` 文件
|
||||
Path dir = Paths.get(".").toAbsolutePath();
|
||||
Files.createDirectories(dir.resolve("src"));
|
||||
Path dsl = dir.resolve("project.cloud");
|
||||
if (Files.notExists(dsl)) {
|
||||
Files.writeString(dsl, """
|
||||
# Generated by snow init
|
||||
project {
|
||||
group = "com.example"
|
||||
artifact = "demo-app"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
}
|
||||
""");
|
||||
Files.writeString(dsl, ProjectCloudExample.getProjectCloud());
|
||||
System.out.println("[init] created " + dsl);
|
||||
} else {
|
||||
System.out.println("[init] project.cloud already exists");
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||
import org.jcnc.snow.pkg.model.Project;
|
||||
import org.jcnc.snow.pkg.resolver.DependencyResolver;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||
|
||||
@ -1,25 +1,26 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.vm.VMLauncher;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
import org.jcnc.snow.pkg.tasks.RunTask;
|
||||
|
||||
/**
|
||||
* CLI 命令:运行已编译的 VM 字节码文件(.water)。
|
||||
* <p>
|
||||
* 用于执行 VM 程序文件。支持传递额外 VM 参数,实际运行由 {@link VMLauncher#main(String[])} 完成。
|
||||
* 仅解析参数并委托给 {@link RunTask},
|
||||
* 将 VM 运行逻辑下沉至 pkg 层,保持 CLI 无状态、薄封装。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* 用法示例:
|
||||
* $ snow run program.water [additional VM options]
|
||||
* $ snow run main.water
|
||||
* </pre>
|
||||
*/
|
||||
public final class RunCommand implements CLICommand {
|
||||
|
||||
/**
|
||||
* 返回命令名,用于 CLI 调用。
|
||||
* 返回命令名称,用于 CLI 调用。
|
||||
*
|
||||
* @return 命令名称字符串("run")
|
||||
* @return 命令名称,如 "run"
|
||||
*/
|
||||
@Override
|
||||
public String name() {
|
||||
@ -33,25 +34,15 @@ public final class RunCommand implements CLICommand {
|
||||
*/
|
||||
@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 <program.water> [additional VM options]
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 run 命令,运行指定的 VM 字节码文件。
|
||||
* 执行运行任务。
|
||||
*
|
||||
* @param args 剩余参数(不含命令名),第一个应为 .water 文件路径,其后为可选 VM 参数
|
||||
* @return 0 表示执行成功,1 表示参数错误
|
||||
* @throws Exception VM 启动或执行过程中可能抛出的异常
|
||||
* @param args CLI 传入的参数数组
|
||||
* @return 执行结果码(0 表示成功,非 0 表示失败)
|
||||
* @throws Exception 执行过程中出现错误时抛出
|
||||
*/
|
||||
@Override
|
||||
public int execute(String[] args) throws Exception {
|
||||
@ -59,7 +50,17 @@ public final class RunCommand implements CLICommand {
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
VMLauncher.main(args);
|
||||
// 委托给 RunTask 执行字节码运行逻辑
|
||||
new RunTask(args).run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印命令用法信息。
|
||||
*/
|
||||
@Override
|
||||
public void printUsage() {
|
||||
System.out.println("Usage:");
|
||||
System.out.println(" snow run <program.water>");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package org.jcnc.snow.cli.commands;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.SnowCLI;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
|
||||
/**
|
||||
* CLI 子命令:输出当前 Snow 工具的版本号。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package org.jcnc.snow.cli.utils;
|
||||
|
||||
import org.jcnc.snow.cli.CLICommand;
|
||||
import org.jcnc.snow.cli.api.CLICommand;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
package org.jcnc.snow.cli.utils;
|
||||
|
||||
public class ProjectCloudExample {
|
||||
/**
|
||||
* 工具类构造方法,禁止实例化。
|
||||
*/
|
||||
private ProjectCloudExample() {
|
||||
// 工具类不允许实例化
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 main.snow 示例模块的内容字符串。
|
||||
*
|
||||
* @return main.snow 示例模块的完整代码
|
||||
*/
|
||||
public static String getProjectCloud() {
|
||||
return """
|
||||
# Generated by snow init
|
||||
project {
|
||||
group = "com.example"
|
||||
artifact = "demo-app"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
}
|
||||
|
||||
build {
|
||||
srcDir = "src"
|
||||
output = "build/demo-app"
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
64
src/main/java/org/jcnc/snow/pkg/doc/README.md
Normal file
64
src/main/java/org/jcnc/snow/pkg/doc/README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Snow Build - 包管理模块
|
||||
|
||||
> Snow 构建工具中的 **pkg** 子系统 —— 负责解析 `.cloud` 配置、解析依赖、 orchestrate 构建生命周期并执行各阶段任务
|
||||
|
||||
## 项目简介
|
||||
|
||||
**包管理模块(pkg)** 是 Snow 构建工具的关键组成部分,承担“从配置到产物” 的整条流水线:
|
||||
|
||||
1. **DSL 解析**:读取并解析 `.cloud` 配置文件,生成统一的项目/依赖/构建配置模型;
|
||||
2. **生命周期编排**:按 *INIT → RESOLVE\_DEPENDENCIES → COMPILE → PACKAGE → PUBLISH → CLEAN* 的顺序驱动构建流程;
|
||||
3. **依赖解析与缓存**:按需下载缺失依赖并存入本地缓存,离线优先;
|
||||
4. **任务执行**:在各生命周期阶段调用对应 `Task` 实现(清理、编译、打包、发布等);
|
||||
5. **配置模板展开**:支持在配置中使用 `@{key}` 形式的占位符,并在构建前统一替换。
|
||||
|
||||
整个模块强调 **可扩展性** 与 **内聚职责**:DSL → Model、Lifecycle → Task、Resolver → Repository 各自解耦,可独立演进。
|
||||
|
||||
## 核心功能
|
||||
|
||||
| 功能 | 关键类 | 说明 |
|
||||
|-----------------|----------------------------------------------------------|-------------------------------------------------|
|
||||
| **CloudDSL 解析** | `CloudDSLParser` | 支持区块语法、嵌套 build 展平、注释过滤,解析为扁平 `Map` |
|
||||
| **模型对象** | `Project / Dependency / Repository / BuildConfiguration` | 只读数据类,提供静态工厂方法 *fromFlatMap()* |
|
||||
| **生命周期管理** | `LifecycleManager`, `LifecyclePhase` | 注册并顺序执行阶段任务,阶段可扩展 |
|
||||
| **任务体系** | `Task` + `*Task` 实现 | Clean / Compile / Package / Publish 四大内置任务,易于新增 |
|
||||
| **依赖解析器** | `DependencyResolver` | 支持本地缓存、HTTP 下载、URI 解析、断点续传(基于 NIO) |
|
||||
| **模板变量替换** | `BuildConfiguration` | 在加载阶段把 `@{key}` 替换为外部属性值 |
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── dsl/
|
||||
│ └── CloudDSLParser.java // .cloud DSL 解析
|
||||
├── lifecycle/
|
||||
│ ├── LifecycleManager.java // 阶段编排
|
||||
│ └── LifecyclePhase.java // 阶段枚举
|
||||
├── model/
|
||||
│ ├── Project.java // 项目信息
|
||||
│ ├── BuildConfiguration.java // 构建配置(支持变量)
|
||||
│ ├── Dependency.java // 依赖描述
|
||||
│ └── Repository.java // 仓库描述
|
||||
├── resolver/
|
||||
│ └── DependencyResolver.java // 依赖解析与缓存
|
||||
└── tasks/
|
||||
├── Task.java // 任务接口
|
||||
├── CleanTask.java // 清理
|
||||
├── CompileTask.java // 编译
|
||||
├── PackageTask.java // 打包
|
||||
└── PublishTask.java // 发布
|
||||
```
|
||||
|
||||
## 典型流程概览
|
||||
|
||||
1. 解析配置
|
||||
|
||||
2. 注册任务并执行
|
||||
|
||||
3. 解析依赖(在 RESOLVE\_DEPENDENCIES 阶段内部)
|
||||
|
||||
## 开发环境
|
||||
|
||||
* JDK 24 或更高版本
|
||||
* Maven 构建管理
|
||||
* 推荐 IDE:IntelliJ IDEA
|
||||
@ -10,61 +10,61 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* CloudDSLParser — .cloud 配置文件解析器
|
||||
* <p>
|
||||
* 作用:
|
||||
* - 读取 Snow 构建工具自定义的 .cloud 文件
|
||||
* - 将内容转换为内存中的 {@link Project} 模型
|
||||
* <p>
|
||||
* 解析规则(:
|
||||
* <p>
|
||||
* 1. 顶级区块(project、properties、repositories、dependencies、build)
|
||||
* 以 “sectionName {” 开始,以 “}” 结束。
|
||||
* <p>
|
||||
* 2. 区块内部识别 “key = value” 形式的赋值。
|
||||
* <p>
|
||||
* 3. build 区块允许嵌套;内部键通过 “.” 展平,
|
||||
* 例如 compile.enabled = true。
|
||||
* CloudDSL 配置文件解析器。
|
||||
* <p>
|
||||
* 负责将自定义的 .cloud 构建配置文件解析为 {@link Project} 模型。
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>顶级区块(如 project、properties、repositories、dependencies、build)以 <code>sectionName {</code> 开始,以 <code>}</code> 结束</li>
|
||||
* <li>区块内部只识别 <code>key = value</code> 赋值,行尾可有 <code># 注释</code></li>
|
||||
* <li>build 区块支持嵌套,内部键通过 <code>.</code> 展平,例如 <code>compile.enabled = true</code></li>
|
||||
* <li><strong>新增:</strong>对 <code>"value"</code> 或 <code>'value'</code> 形式的字面量自动去引号,调用方得到的均是不含引号的裸字符串</li>
|
||||
* </ul>
|
||||
*
|
||||
* <pre>
|
||||
* 示例 .cloud 文件片段:
|
||||
* project {
|
||||
* group = com.example
|
||||
* artifact = "demo-app"
|
||||
* version = 1.0.0
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public final class CloudDSLParser {
|
||||
|
||||
/* ---------- 正则表达式 ---------- */
|
||||
|
||||
/**
|
||||
* 匹配 “sectionName {” 形式的行
|
||||
*/
|
||||
/** 匹配 sectionName { 的行 */
|
||||
private static final Pattern SECTION_HEADER = Pattern.compile("^(\\w+)\\s*\\{\\s*$");
|
||||
|
||||
/**
|
||||
* 匹配 “key = value” 行。
|
||||
* value 允许空格,并忽略行尾 “# …” 注释。
|
||||
* 匹配 key = value 行,忽略行尾注释。
|
||||
* 使用非贪婪匹配 <code>.*?</code>,确保 <code>value</code> 内部允许出现空格或 =。
|
||||
*/
|
||||
private static final Pattern KEY_VALUE = Pattern.compile("^(\\w+)\\s*=\\s*([^#]+?)\\s*(?:#.*)?$");
|
||||
private static final Pattern KEY_VALUE = Pattern.compile("^(\\w+)\\s*=\\s*(.*?)\\s*(?:#.*)?$");
|
||||
|
||||
/**
|
||||
* 匹配仅包含 “}” 的行(可有前后空白)
|
||||
*/
|
||||
/** 匹配仅为 } 的行 */
|
||||
private static final Pattern BLOCK_END = Pattern.compile("^}\\s*$");
|
||||
|
||||
/**
|
||||
* 私有构造,禁止实例化
|
||||
*/
|
||||
private CloudDSLParser() {
|
||||
}
|
||||
/** 工具类禁止实例化 */
|
||||
private CloudDSLParser() {}
|
||||
|
||||
/**
|
||||
* 解析指定 .cloud 文件并生成 {@link Project} 对象。
|
||||
* 解析指定 .cloud 文件为 {@link Project} 对象。
|
||||
* <ul>
|
||||
* <li>遇到语法错误(括号不配对、无法识别的行)时抛出异常</li>
|
||||
* <li>支持嵌套区块和注释</li>
|
||||
* <li>对字面量自动去除成对单/双引号</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return 解析后的 Project
|
||||
* @param path .cloud 文件路径
|
||||
* @return 解析得到的 Project 实例
|
||||
* @throws IOException 文件读取失败
|
||||
* @throws IllegalStateException 语法错误(如括号不匹配、未知语句)
|
||||
* @throws IllegalStateException 文件内容格式非法或语法错误
|
||||
*/
|
||||
public static Project parse(Path path) throws IOException {
|
||||
|
||||
Deque<String> sectionStack = new ArrayDeque<>(); // 记录当前区块层级
|
||||
Map<String, String> flatMap = new LinkedHashMap<>(); // 扁平化 key → value
|
||||
Deque<String> sectionStack = new ArrayDeque<>(); // 当前区块栈
|
||||
Map<String, String> flatMap = new LinkedHashMap<>(); // 扁平化后的 key → value
|
||||
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
|
||||
@ -73,19 +73,19 @@ public final class CloudDSLParser {
|
||||
lineNo++;
|
||||
String line = raw.trim();
|
||||
|
||||
/* 1. 跳过空行和注释 */
|
||||
// 跳过空行和注释
|
||||
if (line.isEmpty() || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 2. 区块起始:sectionName { */
|
||||
// 区块起始
|
||||
Matcher sec = SECTION_HEADER.matcher(line);
|
||||
if (sec.matches()) {
|
||||
sectionStack.push(sec.group(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 3. 区块结束:} */
|
||||
// 区块结束
|
||||
if (BLOCK_END.matcher(line).matches()) {
|
||||
if (sectionStack.isEmpty()) {
|
||||
throw new IllegalStateException("第 " + lineNo + " 行出现未配对的 '}'");
|
||||
@ -94,13 +94,14 @@ public final class CloudDSLParser {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 4. 键值对:key = value */
|
||||
// 键值对
|
||||
Matcher kv = KEY_VALUE.matcher(line);
|
||||
if (kv.matches()) {
|
||||
String key = kv.group(1).trim();
|
||||
String value = kv.group(2).trim();
|
||||
value = unquote(value); // 去除首尾成对引号
|
||||
|
||||
/* 4.1 计算前缀(栈倒序,即从外到内) */
|
||||
// 计算区块前缀
|
||||
String prefix = String.join(".", (Iterable<String>) sectionStack::descendingIterator);
|
||||
if (!prefix.isEmpty()) {
|
||||
key = prefix + "." + key;
|
||||
@ -110,16 +111,29 @@ public final class CloudDSLParser {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 5. 无法识别的行 */
|
||||
// 无法识别的行
|
||||
throw new IllegalStateException("无法解析第 " + lineNo + " 行: " + raw);
|
||||
}
|
||||
|
||||
/* 6. 检查括号是否全部闭合 */
|
||||
// 检查区块是否全部闭合
|
||||
if (!sectionStack.isEmpty()) {
|
||||
throw new IllegalStateException("文件结束但区块未闭合:" + sectionStack);
|
||||
}
|
||||
|
||||
/* 7. 构造 Project 模型 */
|
||||
// 构建 Project 模型
|
||||
return Project.fromFlatMap(flatMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果字符串首尾包裹成对单引号或双引号,则去掉引号后返回;否则直接返回原字符串。
|
||||
*/
|
||||
private static String unquote(String s) {
|
||||
if (s == null || s.length() < 2) return s;
|
||||
char first = s.charAt(0);
|
||||
char last = s.charAt(s.length() - 1);
|
||||
if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) {
|
||||
return s.substring(1, s.length() - 1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,9 +6,10 @@ import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 管理不同生命周期阶段与其对应任务的工具类。
|
||||
* 生命周期任务管理器。<br>
|
||||
* 用于管理不同生命周期阶段与其对应 {@link Task},并支持顺序执行所有已注册任务。
|
||||
* <p>
|
||||
* 可为每个 {@link LifecyclePhase} 注册对应的 {@link Task},并按阶段顺序执行所有任务。
|
||||
* 可为每个 {@link LifecyclePhase} 注册对应的 {@link Task},并在构建/部署流程中自动执行。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
@ -20,9 +21,7 @@ import java.util.Map;
|
||||
*/
|
||||
public final class LifecycleManager {
|
||||
|
||||
/**
|
||||
* 存储生命周期阶段与对应任务的映射关系。
|
||||
*/
|
||||
/** 生命周期阶段与对应任务的映射关系 */
|
||||
private final Map<LifecyclePhase, Task> tasks = new EnumMap<>(LifecyclePhase.class);
|
||||
|
||||
/**
|
||||
@ -45,11 +44,13 @@ public final class LifecycleManager {
|
||||
|
||||
/**
|
||||
* 按 {@link LifecyclePhase} 声明顺序依次执行所有已注册任务。
|
||||
* <p>
|
||||
* 未注册任务的阶段将被跳过。任务执行前会打印阶段名。
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>未注册任务的阶段会被自动跳过</li>
|
||||
* <li>每个任务执行前会输出当前阶段名</li>
|
||||
* <li>执行中遇到异常将立即抛出并终止后续执行</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws Exception 若某个任务执行时抛出异常,将直接抛出并终止后续任务执行
|
||||
* @throws Exception 若某个任务执行时抛出异常,将直接抛出
|
||||
*/
|
||||
public void executeAll() throws Exception {
|
||||
for (LifecyclePhase phase : LifecyclePhase.values()) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.jcnc.snow.pkg.lifecycle;
|
||||
|
||||
/**
|
||||
* 定义了典型软件包生命周期的各个阶段。
|
||||
* 定义典型软件包生命周期的各个阶段枚举。
|
||||
* <p>
|
||||
* 用于区分构建、依赖、发布等不同阶段的任务调度与管理。
|
||||
* </p>
|
||||
*/
|
||||
public enum LifecyclePhase {
|
||||
/** 初始化阶段 */
|
||||
|
||||
@ -6,16 +6,16 @@ import java.util.HashMap;
|
||||
/**
|
||||
* 构建配置对象,封装构建过程中的所有选项。
|
||||
* <p>
|
||||
* 支持基于模板变量(形如{@code @{key}})的选项值替换。
|
||||
* 支持模板变量(形如 <code>@{key}</code>)的值自动替换。
|
||||
* </p>
|
||||
*/
|
||||
public final class BuildConfiguration {
|
||||
|
||||
/** 存储配置项的键值对 */
|
||||
/** 存储所有配置项 */
|
||||
private final Map<String, String> options;
|
||||
|
||||
/**
|
||||
* 私有构造函数,用于初始化配置项。
|
||||
* 私有构造函数,仅供工厂方法调用。
|
||||
*
|
||||
* @param options 配置项键值对
|
||||
*/
|
||||
@ -24,14 +24,15 @@ public final class BuildConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于原始配置项和变量属性创建配置对象。
|
||||
* <p>
|
||||
* 会将原始配置中的所有值中的{@code @{key}}模板,替换为属性props中对应的值。
|
||||
* </p>
|
||||
* 基于原始配置项和属性集创建配置对象。
|
||||
* <ul>
|
||||
* <li>会将所有值中的 <code>@{key}</code> 模板变量,替换为 props 中对应的值</li>
|
||||
* <li>属性未匹配到时保留原模板</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param flat 原始的配置项,值中可包含模板变量(如@{name})
|
||||
* @param props 用于替换模板变量的属性集
|
||||
* @return 构建完成的配置对象
|
||||
* @param flat 原始配置项,值中可包含模板变量
|
||||
* @param props 变量替换用的属性集
|
||||
* @return 处理后生成的配置对象
|
||||
*/
|
||||
public static BuildConfiguration fromFlatMap(Map<String, String> flat, Map<String, String> props) {
|
||||
Map<String, String> resolved = new HashMap<>();
|
||||
@ -46,11 +47,11 @@ public final class BuildConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定key对应的配置值。
|
||||
* 获取指定配置项的值。
|
||||
*
|
||||
* @param key 配置项名称
|
||||
* @param def 默认值(若未找到key则返回此值)
|
||||
* @return 配置项对应值,若不存在则返回默认值
|
||||
* @param def 默认值(未找到时返回)
|
||||
* @return 配置项值,若不存在则返回默认值
|
||||
*/
|
||||
public String get(String key, String def) {
|
||||
return options.getOrDefault(key, def);
|
||||
|
||||
@ -5,18 +5,23 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 表示一个 Maven 风格的 group:artifact:version 依赖坐标。
|
||||
* group:artifact:version 依赖坐标对象。
|
||||
* <p>
|
||||
* 支持通过占位符和属性映射进行动态变量替换,可用于构建工具或依赖管理场景。
|
||||
* 支持占位符和属性映射进行动态变量替换,适用于 Snow 语言包管理和源码依赖场景。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* 示例:
|
||||
* 示例用法:
|
||||
* Dependency dep = Dependency.fromString(
|
||||
* "core", "com.example:core:@{version}",
|
||||
* Map.of("version", "1.2.3")
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
* @param id 依赖唯一标识
|
||||
* @param group 组织/分组名
|
||||
* @param artifact 构件名
|
||||
* @param version 版本号
|
||||
*/
|
||||
public record Dependency(
|
||||
String id,
|
||||
@ -25,25 +30,23 @@ public record Dependency(
|
||||
String version
|
||||
) {
|
||||
|
||||
/**
|
||||
* 用于匹配 group:artifact:version 格式的正则表达式。
|
||||
*/
|
||||
/** 匹配 group:artifact:version 格式的正则表达式。 */
|
||||
private static final Pattern GAV = Pattern.compile("([^:]+):([^:]+):(.+)");
|
||||
|
||||
/**
|
||||
* 根据字符串坐标和属性映射创建依赖对象。
|
||||
* <p>
|
||||
* 坐标中的占位符如 {@code @{key}} 会用 props 中对应的值替换。
|
||||
* 坐标中的占位符(如 <code>@{key}</code>)会用 props 中对应的值替换。
|
||||
* </p>
|
||||
*
|
||||
* @param id 依赖唯一标识
|
||||
* @param coordinate 依赖坐标字符串,格式为 group:artifact:version,支持变量占位符
|
||||
* @param props 占位符替换的属性映射
|
||||
* @param coordinate 坐标字符串,格式为 group:artifact:version,支持占位符
|
||||
* @param props 占位符替换属性映射
|
||||
* @return 解析后的 Dependency 实例
|
||||
* @throws IllegalArgumentException 如果坐标格式非法
|
||||
* @throws IllegalArgumentException 坐标格式非法时抛出
|
||||
*/
|
||||
public static Dependency fromString(String id, String coordinate, Map<String, String> props) {
|
||||
// 替换 @{prop} 占位符
|
||||
// 替换占位符
|
||||
String resolved = coordinate;
|
||||
for (Map.Entry<String, String> p : props.entrySet()) {
|
||||
resolved = resolved.replace("@{" + p.getKey() + "}", p.getValue());
|
||||
@ -57,23 +60,23 @@ public record Dependency(
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成依赖对应的标准仓库 jar 路径。
|
||||
* 生成依赖对应的源码文件路径。
|
||||
* <p>
|
||||
* 路径格式通常为:groupId/artifactId/version/artifactId-version.jar<br>
|
||||
* 例如:com/example/core/1.2.3/core-1.2.3.jar
|
||||
* 路径格式:groupId/artifactId/version/artifactId.snow
|
||||
* 例如:com/example/core/1.2.3/core.snow
|
||||
* </p>
|
||||
*
|
||||
* @return 仓库 jar 文件的相对路径
|
||||
* @return 仓库源码文件的相对路径
|
||||
*/
|
||||
public String toPath() {
|
||||
String groupPath = group.replace('.', '/');
|
||||
return groupPath + "/" + artifact + "/" + version + "/" + artifact + "-" + version + ".jar";
|
||||
return groupPath + "/" + artifact + "/" + version + "/" + artifact + ".snow";
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回该依赖的 group:artifact:version 字符串表示。
|
||||
*
|
||||
* @return Maven 坐标字符串
|
||||
* @return 坐标字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@ -8,8 +8,8 @@ import java.util.Map;
|
||||
/**
|
||||
* 表示一个软件包/模块的项目信息,包括元数据、属性、仓库、依赖和构建配置等。
|
||||
* <p>
|
||||
* 本类为不可变对象,仅提供getter,无setter。
|
||||
* 支持通过 {@link #fromFlatMap(Map)} 静态工厂方法从扁平Map快速创建。
|
||||
* 本类为不可变对象,仅提供 getter 方法,无 setter。<br>
|
||||
* 支持通过 {@link #fromFlatMap(Map)} 静态工厂方法,从扁平 Map 快速创建实例。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
@ -43,7 +43,6 @@ public final class Project {
|
||||
/** 构建配置 */
|
||||
private final BuildConfiguration build;
|
||||
|
||||
|
||||
/**
|
||||
* 构造函数(私有),请使用 {@link #fromFlatMap(Map)} 创建实例。
|
||||
*/
|
||||
@ -74,7 +73,7 @@ public final class Project {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过扁平Map创建 Project 实例。约定key格式如下:
|
||||
* 通过扁平 Map 创建 Project 实例。key 格式约定如下:
|
||||
* <ul>
|
||||
* <li>project.* —— 项目元数据</li>
|
||||
* <li>properties.* —— 额外属性</li>
|
||||
@ -87,8 +86,7 @@ public final class Project {
|
||||
* @return Project 实例
|
||||
*/
|
||||
public static Project fromFlatMap(Map<String, String> map) {
|
||||
|
||||
// 1. simple project metadata
|
||||
// 1. 基本元数据
|
||||
String group = map.getOrDefault("project.group", "unknown");
|
||||
String artifact = map.getOrDefault("project.artifact", "unknown");
|
||||
String name = map.getOrDefault("project.name", artifact);
|
||||
@ -123,14 +121,13 @@ public final class Project {
|
||||
}
|
||||
});
|
||||
|
||||
// 5. build.* simply hand the subtree map
|
||||
// 5. build.*
|
||||
Map<String, String> buildMap = new LinkedHashMap<>();
|
||||
map.forEach((k, v) -> {
|
||||
if (k.startsWith("build.")) {
|
||||
buildMap.put(k.substring("build.".length()), v);
|
||||
}
|
||||
});
|
||||
|
||||
BuildConfiguration buildCfg = BuildConfiguration.fromFlatMap(buildMap, props);
|
||||
|
||||
return new Project(group, artifact, name, version, description, license, homepage, props, repos, deps, buildCfg);
|
||||
@ -161,7 +158,7 @@ public final class Project {
|
||||
return description;
|
||||
}
|
||||
|
||||
/** @return 许可证 */
|
||||
/** @return 许可证标识 */
|
||||
public String getLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
@ -3,16 +3,16 @@ package org.jcnc.snow.pkg.model;
|
||||
/**
|
||||
* 表示一个远程仓库的基本信息,通常用于依赖解析和发布。
|
||||
* <p>
|
||||
* 每个仓库由唯一的ID和对应的URL确定。
|
||||
* 每个仓库由唯一的 ID 和对应的 URL 标识。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* 示例:
|
||||
* 示例用法:
|
||||
* Repository repo = new Repository("central", "https://");
|
||||
* </pre>
|
||||
*
|
||||
* @param id 仓库唯一标识
|
||||
* @param url 仓库地址(一般为HTTP(S)链接)
|
||||
* @param url 仓库地址(通常为 HTTP(S) 链接)
|
||||
*/
|
||||
public record Repository(String id, String url) {
|
||||
}
|
||||
|
||||
@ -6,17 +6,27 @@ import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* 清理构建输出目录(如 build 和 dist)的任务实现。
|
||||
* 用于清理构建输出目录(如 {@code build} 和 {@code dist})的任务实现类。
|
||||
* <p>
|
||||
* 实现 {@link Task} 接口,通常用于构建流程中的清理阶段。
|
||||
* 实现 {@link Task} 接口,常用于自动化构建流程的清理阶段,负责递归删除指定的构建产物目录。
|
||||
* </p>
|
||||
* <p>
|
||||
* 本类为无状态实现,线程安全。
|
||||
* </p>
|
||||
*
|
||||
* <p><b>示例用法:</b></p>
|
||||
* <pre>{@code
|
||||
* Task clean = new CleanTask();
|
||||
* clean.run();
|
||||
* }</pre>
|
||||
*/
|
||||
public final class CleanTask implements Task {
|
||||
|
||||
/**
|
||||
* 执行清理任务,删除 "build" 和 "dist" 目录及其所有内容。
|
||||
* 执行清理任务,递归删除当前目录下的 {@code build} 和 {@code dist} 目录及其所有内容。
|
||||
* 如果目标目录不存在,则跳过不处理。
|
||||
*
|
||||
* @throws IOException 删除目录过程中出现 IO 错误时抛出
|
||||
* @throws IOException 删除目录或文件过程中发生 IO 错误时抛出
|
||||
*/
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
@ -27,10 +37,15 @@ public final class CleanTask implements Task {
|
||||
|
||||
/**
|
||||
* 递归删除指定目录及其所有子文件和子目录。
|
||||
* 使用 try-with-resources 自动关闭文件流,避免资源泄漏。
|
||||
* <p>
|
||||
* 若目录不存在,则直接返回。
|
||||
* </p>
|
||||
* <p>
|
||||
* 内部使用 try-with-resources 保证文件流自动关闭,避免资源泄漏。
|
||||
* </p>
|
||||
*
|
||||
* @param dir 需要删除的目录路径
|
||||
* @throws IOException 删除过程中出现 IO 错误时抛出
|
||||
* @throws IOException 删除目录或文件过程中发生 IO 错误时抛出
|
||||
*/
|
||||
private void deleteDir(Path dir) throws IOException {
|
||||
if (Files.notExists(dir)) return;
|
||||
|
||||
@ -1,43 +1,264 @@
|
||||
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 文件)。
|
||||
* <p>
|
||||
* 实现 {@link Task} 接口,用于构建流程中的编译阶段。当前仅为示例,未集成实际编译器。
|
||||
* 支持单文件、多文件和目录递归编译,并可在编译后立即运行虚拟机。<br>
|
||||
* 命令行参数支持 run、-o、-d 及直接指定源文件。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* 用法示例:
|
||||
* $ snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow ...]
|
||||
* </pre>
|
||||
*/
|
||||
public final class CompileTask implements Task {
|
||||
|
||||
/** 待编译的项目 */
|
||||
/** 项目信息 */
|
||||
private final Project project;
|
||||
/** 原始命令行参数 */
|
||||
private final String[] args;
|
||||
|
||||
/**
|
||||
* 创建 CompileTask 实例。
|
||||
* 创建一个编译任务。
|
||||
*
|
||||
* @param project 目标项目
|
||||
* @param project 项目信息对象
|
||||
* @param args 命令行参数数组
|
||||
*/
|
||||
public CompileTask(Project project) {
|
||||
public CompileTask(Project project, String[] args) {
|
||||
this.project = project;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行编译任务,打印源代码目录和输出目录。
|
||||
* <p>
|
||||
* 实际编译尚未实现(TODO)。
|
||||
* </p>
|
||||
* 创建一个不带参数的编译任务。
|
||||
*
|
||||
* @throws Exception 预留,未来集成编译器可能抛出异常
|
||||
* @param project 项目信息对象
|
||||
*/
|
||||
public CompileTask(Project project) {
|
||||
this(project, new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行编译任务。该方法会解析参数并调用 {@link #execute(String[])} 进行实际编译流程。
|
||||
*
|
||||
* @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(this.args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译 .snow 源文件为 VM 字节码,并可选择立即运行。
|
||||
* <ul>
|
||||
* <li>支持参数 run(编译后运行)、-o(输出文件名)、-d(递归目录)、直接指定多个源文件。</li>
|
||||
* <li>输出源代码、AST、IR、最终 VM code,并写出 .water 文件。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param args 命令行参数数组
|
||||
* @return 0 表示成功,非 0 表示失败
|
||||
* @throws Exception 编译或写入过程中出现异常时抛出
|
||||
*/
|
||||
public int execute(String[] args) throws Exception {
|
||||
// ---------------- 解析命令行参数 ----------------
|
||||
boolean runAfterCompile = false;
|
||||
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;
|
||||
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 <name>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------- 1. 词法/语法分析,并打印源代码 ----------------
|
||||
List<Node> 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<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. 写出 .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 输出文件名。
|
||||
* <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;
|
||||
}
|
||||
}
|
||||
|
||||
95
src/main/java/org/jcnc/snow/pkg/tasks/GenerateTask.java
Normal file
95
src/main/java/org/jcnc/snow/pkg/tasks/GenerateTask.java
Normal file
@ -0,0 +1,95 @@
|
||||
package org.jcnc.snow.pkg.tasks;
|
||||
|
||||
import org.jcnc.snow.pkg.model.Project;
|
||||
import org.jcnc.snow.pkg.utils.SnowExample;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目脚手架生成任务。<br>
|
||||
* 根据 {@link Project} 元数据自动创建标准项目目录结构,并生成示例入口文件
|
||||
* <code>main.snow</code>。
|
||||
*
|
||||
* <p>
|
||||
* 生成内容包括:
|
||||
* <ul>
|
||||
* <li><code>src/</code> —— 源码根目录</li>
|
||||
* <li><code>src/{group package}/</code> —— 按 <code>project.group</code> 创建的包路径
|
||||
* (如 <code>com.example</code> → <code>src/com/example/</code>)</li>
|
||||
* <li><code>test/</code> —— 测试源码目录</li>
|
||||
* <li><code>build/</code> —— 编译输出目录</li>
|
||||
* <li><code>dist/</code> —— 打包输出目录</li>
|
||||
* <li><code>src/{group package}/main.snow</code> —— 示例入口文件</li>
|
||||
* </ul>
|
||||
* 如目录或入口文件已存在,则自动跳过,不会覆盖。
|
||||
* </p>
|
||||
*/
|
||||
public final class GenerateTask implements Task {
|
||||
|
||||
/** 项目信息元数据 */
|
||||
private final Project project;
|
||||
|
||||
/**
|
||||
* 创建项目生成任务。
|
||||
*
|
||||
* @param project 项目信息元数据对象
|
||||
*/
|
||||
public GenerateTask(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行脚手架生成流程,创建标准目录和入口示例文件。
|
||||
* <ul>
|
||||
* <li>若相关目录不存在则创建</li>
|
||||
* <li>若设置了 <code>project.group</code>,则在 <code>src/</code> 下新建对应包路径</li>
|
||||
* <li>示例入口文件 <code>main.snow</code> 写入包路径目录</li>
|
||||
* <li>生成过程输出进度信息</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws IOException 创建目录或写入文件时发生 IO 错误时抛出
|
||||
*/
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
Path root = Paths.get(".").toAbsolutePath();
|
||||
|
||||
/* ---------- 1. 构造待创建目录列表 ---------- */
|
||||
List<Path> dirs = new ArrayList<>(List.of(
|
||||
root.resolve("src"),
|
||||
root.resolve("test"),
|
||||
root.resolve("build"),
|
||||
root.resolve("dist")
|
||||
));
|
||||
|
||||
/* ---------- 2. 处理 group:追加包目录 ---------- */
|
||||
String group = project != null ? project.getGroup() : null;
|
||||
Path srcDir = root.resolve("src");
|
||||
Path packageDir = srcDir; // 默认直接在 src 下
|
||||
if (group != null && !group.isBlank()) {
|
||||
packageDir = srcDir.resolve(group.replace('.', '/'));
|
||||
dirs.add(packageDir);
|
||||
}
|
||||
|
||||
/* ---------- 3. 创建目录 ---------- */
|
||||
for (Path dir : dirs) {
|
||||
if (Files.notExists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
System.out.println("[generate] created directory " + root.relativize(dir));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 4. 写入示例入口文件 main.snow ---------- */
|
||||
Path mainSnow = packageDir.resolve("main.snow");
|
||||
if (Files.notExists(mainSnow)) {
|
||||
Files.writeString(mainSnow, SnowExample.getMainModule());
|
||||
System.out.println("[generate] created " + root.relativize(mainSnow));
|
||||
}
|
||||
|
||||
System.out.println("[generate] project scaffold is ready.");
|
||||
}
|
||||
}
|
||||
@ -9,29 +9,38 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 项目打包任务,将编译输出目录(如 build/classes)打包为 .ice 文件。
|
||||
* 项目打包任务,将编译输出目录(如 <code>build/classes</code>)打包为 .ice 文件。
|
||||
* <p>
|
||||
* 实现 {@link Task} 接口,通常用于构建流程中的打包阶段。
|
||||
* 实现 {@link Task} 接口,通常用于构建流程的打包阶段。<br>
|
||||
* 只会打包 build/classes 目录下所有文件,不含其他目录。
|
||||
* </p>
|
||||
* <p>
|
||||
* 输出文件位于 dist 目录,命名为 artifact-version.ice。
|
||||
* </p>
|
||||
*/
|
||||
public final class PackageTask implements Task {
|
||||
|
||||
/** 目标项目 */
|
||||
/** 目标项目元数据 */
|
||||
private final Project project;
|
||||
|
||||
/**
|
||||
* 创建 PackageTask 实例。
|
||||
* 创建打包任务。
|
||||
*
|
||||
* @param project 目标项目
|
||||
* @param project 目标项目元数据
|
||||
*/
|
||||
public PackageTask(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行打包任务,将编译输出目录压缩为 artifact-version.ice 文件。
|
||||
* 执行打包任务,将 build/classes 目录下所有文件压缩为 dist/artifact-version.ice。
|
||||
* <ul>
|
||||
* <li>若输出目录 dist 不存在会自动创建</li>
|
||||
* <li>只打包 build/classes 下所有普通文件(保持相对目录结构)</li>
|
||||
* <li>如无 build/classes 则不会生成包</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws Exception 打包过程中出现 IO 或其他异常时抛出
|
||||
* @throws Exception 打包过程中发生 IO 或其他异常时抛出
|
||||
*/
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
@ -43,15 +52,15 @@ public final class PackageTask implements Task {
|
||||
Path packageFile = distDir.resolve(fileName);
|
||||
|
||||
try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(packageFile))) {
|
||||
// 仅将编译输出目录打包
|
||||
// 仅将 build/classes 目录打包
|
||||
Path classesDir = Path.of("build/classes");
|
||||
if (Files.exists(classesDir)) {
|
||||
// 使用 try-with-resources 正确关闭 Stream<Path>
|
||||
// 遍历所有文件并写入 zip
|
||||
try (Stream<Path> stream = Files.walk(classesDir)) {
|
||||
stream.filter(Files::isRegularFile)
|
||||
.forEach(p -> {
|
||||
try {
|
||||
// 将文件以相对路径加入压缩包
|
||||
// 以 classesDir 为根的相对路径存入 zip
|
||||
zos.putNextEntry(new ZipEntry(classesDir.relativize(p).toString()));
|
||||
Files.copy(p, zos);
|
||||
zos.closeEntry();
|
||||
|
||||
@ -5,25 +5,26 @@ import org.jcnc.snow.pkg.model.Project;
|
||||
/**
|
||||
* 发布项目构件到远程仓库的任务实现。
|
||||
* <p>
|
||||
* 实现 {@link Task} 接口,通常用于构建流程中的发布阶段。目前仅为演示,尚未实现实际上传。
|
||||
* 实现 {@link Task} 接口,通常用于构建流程中的发布阶段。
|
||||
* 当前仅输出发布提示,尚未实现实际上传功能。
|
||||
* </p>
|
||||
*/
|
||||
public final class PublishTask implements Task {
|
||||
|
||||
/** 目标项目 */
|
||||
/** 目标项目元数据 */
|
||||
private final Project project;
|
||||
|
||||
/**
|
||||
* 创建 PublishTask 实例。
|
||||
* 创建发布任务。
|
||||
*
|
||||
* @param project 目标项目
|
||||
* @param project 目标项目元数据
|
||||
*/
|
||||
public PublishTask(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行发布任务。当前仅打印发布提示,未实现实际上传逻辑。
|
||||
* 执行发布任务。目前仅打印发布提示信息,未实现实际上传逻辑。
|
||||
*
|
||||
* @throws Exception 预留,未来实现上传逻辑时可能抛出异常
|
||||
*/
|
||||
|
||||
44
src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java
Normal file
44
src/main/java/org/jcnc/snow/pkg/tasks/RunTask.java
Normal file
@ -0,0 +1,44 @@
|
||||
package org.jcnc.snow.pkg.tasks;
|
||||
|
||||
import org.jcnc.snow.vm.VMLauncher;
|
||||
|
||||
/**
|
||||
* 任务:执行已编译的 VM 字节码文件(.water)。
|
||||
* <p>
|
||||
* 作为 CLI、IDE 插件或其他宿主环境启动虚拟机的统一入口,<br>
|
||||
* 通过调用 {@link VMLauncher#main(String[])} 启动 VM 并执行指定程序。
|
||||
* </p>
|
||||
*/
|
||||
public final class RunTask implements Task {
|
||||
|
||||
/**
|
||||
* 传递给虚拟机的完整参数列表(第一个应为 .water 文件路径)
|
||||
*/
|
||||
private final String[] args;
|
||||
|
||||
/**
|
||||
* 创建运行任务。
|
||||
*
|
||||
* @param args VM 参数数组(第一个为 .water 程序路径,其后为可选参数)
|
||||
*/
|
||||
public RunTask(String... args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行运行任务。内部委托 {@link VMLauncher#main(String[])} 启动 VM。
|
||||
* <ul>
|
||||
* <li>如果参数为空则抛出 {@link IllegalArgumentException}</li>
|
||||
* <li>异常由虚拟机本身抛出,直接透出</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
package org.jcnc.snow.pkg.tasks;
|
||||
|
||||
/**
|
||||
* 构建任务的通用接口,所有具体任务都应实现该接口。
|
||||
* 构建任务的通用接口,所有具体任务(如编译、打包、清理等)都应实现该接口。
|
||||
* <p>
|
||||
* 用于统一生命周期内的任务行为,例如编译、打包、清理等。
|
||||
* 用于统一不同阶段任务的生命周期与执行行为。
|
||||
* </p>
|
||||
*/
|
||||
public interface Task {
|
||||
@ -11,7 +11,7 @@ public interface Task {
|
||||
/**
|
||||
* 执行具体任务的入口方法。
|
||||
*
|
||||
* @throws Exception 任务执行过程中出现的异常
|
||||
* @throws Exception 任务执行过程中出现的任意异常
|
||||
*/
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
58
src/main/java/org/jcnc/snow/pkg/utils/SnowExample.java
Normal file
58
src/main/java/org/jcnc/snow/pkg/utils/SnowExample.java
Normal file
@ -0,0 +1,58 @@
|
||||
package org.jcnc.snow.pkg.utils;
|
||||
|
||||
/**
|
||||
* 示例模块模板工具类,提供 main.snow 的标准示例代码字符串。
|
||||
* <p>
|
||||
* 用于项目脚手架生成或帮助用户快速上手 .snow 语言。
|
||||
* </p>
|
||||
*/
|
||||
public final class SnowExample {
|
||||
|
||||
/**
|
||||
* 工具类构造方法,禁止实例化。
|
||||
*/
|
||||
private SnowExample() {
|
||||
// 工具类不允许实例化
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 main.snow 示例模块的内容字符串。
|
||||
*
|
||||
* @return main.snow 示例模块的完整代码
|
||||
*/
|
||||
public static String getMainModule() {
|
||||
return """
|
||||
module: Math
|
||||
function: main
|
||||
parameter:
|
||||
return_type: int
|
||||
body:
|
||||
Math.factorial(6)
|
||||
return 0
|
||||
end body
|
||||
end function
|
||||
|
||||
function: factorial
|
||||
parameter:
|
||||
declare n:int
|
||||
return_type: int
|
||||
body:
|
||||
declare num1:int = 1
|
||||
loop:
|
||||
initializer:
|
||||
declare counter:int = 1
|
||||
condition:
|
||||
counter <= n
|
||||
update:
|
||||
counter = counter + 1
|
||||
body:
|
||||
num1 = num1 * counter
|
||||
end body
|
||||
end loop
|
||||
return num1
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
""";
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user