# 第9章 CLI 与调试开关的正确打开方式
# 一、前言
高效的排障从“看得见的中间态”开始。本章统一 CLI 参数的使用顺序与组合,形成自底向上的验证路径。
# 二、目标
- 熟练使用
--dump-tokens/--dump-parse-tree/--dump-ast/--emit-class
- 能读懂 CLI 的参数解析代码与主流程代码
- 按推荐顺序定位问题来源
# 三、设计
术语说明:
- dump:打印中间产物
- emit-class:导出 .class 供
javap -v
反汇编
核心流程图:
架构交互图:
# 四、实现
目录树(关注项):
src
└── main
└── java
└── com
└── lxg
└── tools
└── Main.java
命令:
java -jar target/my-language-0.1.0-SNAPSHOT.jar examples/arithmetic.lxg --dump-tokens
java -jar target/my-language-0.1.0-SNAPSHOT.jar examples/arithmetic.lxg --dump-parse-tree | cat
java -jar target/my-language-0.1.0-SNAPSHOT.jar examples/arithmetic.lxg --dump-ast
java -jar target/my-language-0.1.0-SNAPSHOT.jar examples/arithmetic.lxg --emit-class=out/Program.class
javap -v out/Program.class | sed -n '1,200p'
代码对照:参数解析
for (String arg : args) {
if (arg.startsWith("--emit-class=")) {
emitClassPath = arg.substring("--emit-class=".length());
} else if ("--dump-tokens".equals(arg)) {
dumpTokens = true;
} else if ("--dump-parse-tree".equals(arg)) {
dumpParseTree = true;
} else if ("--dump-ast".equals(arg)) {
dumpAst = true;
} else if (!arg.startsWith("--")) {
sourcePath = arg;
}
}
代码对照:完整流水线(runSource 节选)
public static void runSource(String source, String emitClassPath, boolean dumpTokens, boolean dumpParseTree, boolean dumpAst) {
try {
// 1) Lexer/Parser
LxgLexer lexer = new LxgLexer(CharStreams.fromString(source));
CommonTokenStream tokens = new CommonTokenStream(lexer);
if (dumpTokens) {
tokens.fill();
for (Token t : tokens.getTokens()) {
System.out.println(t.getText() + " -> " + LxgLexer.VOCABULARY.getDisplayName(t.getType()));
}
}
LxgParser parser = new LxgParser(tokens);
LxgParser.ProgContext prog = parser.prog();
if (dumpParseTree) {
System.out.println(prog.toStringTree(parser));
}
// 2) AST
AstBuilder builder = new AstBuilder();
CompilationUnit unit = builder.build(prog);
if (dumpAst) {
System.out.println(AstPrinter.print(unit));
}
// 3) Semantic check
Diagnostics diags = new TypeChecker().check(unit);
if (diags.hasErrors()) {
diags.printAll(System.err);
return; // 中止
}
// 4) Codegen
ClassGenerator gen = new ClassGenerator();
byte[] cls = gen.generate(unit);
// 5) 可选:写出 .class
if (emitClassPath != null && !emitClassPath.isEmpty()) {
Path out = Paths.get(emitClassPath);
Files.createDirectories(out.getParent() == null ? Paths.get(".") : out.getParent());
Files.write(out, cls);
System.out.println("Emitted class to: " + out.toAbsolutePath());
}
// 6) 运行
new LxgShell().run(cls);
} catch (Exception e) {
e.printStackTrace(System.err);
throw new RuntimeException("Failed to run source", e);
}
}
# 五、测试
- 端到端:
LxgEndToEndTest
- 手动:对同一源码逐层 dump 并记录差异,验证链路正确性
# 六、总结
- 推荐顺序:tokens → parse-tree → ast → emit-class;自底向上缩小范围
- CLI 是“可观测性入口”,与生成、反汇编配合更高效