如何从解析树到 Java class 文件
How to get from parse tree to Java class file
我正在开发具有以下功能的命令行工具:
- 使用扩展的 ANTLR4 Java9 语法解析修改后的 .java 文件。文件中的语法是 Java,对包含目的的方法声明进行了一次修改,如本例所示:
public void {marketing} sendEmail() {}
- 使用访问者收集并删除所有用途。收集和分析目的是程序的主要功能。
- 编译并执行删除用途的 Java 个文件。
我正在寻找实现步骤 3 的最简单和最有效的方法。构建完整的编译器超出了我的项目范围,我更愿意利用 Java 编译器和 运行 javac 如果可能的话。我考虑了以下方法,但 none 似乎是最优的:
- post:Compiling an AST back to source code 中提出的 Prettyprinting(从解析树到源代码)。不过,在大型目录上可能需要做很多工作。
- 使用 ASM 生成字节码,但据我所知,我需要有效的 java 源代码或 class 文件才能正常工作 (https://asm.ow2.io/asm4-guide.pdf)。
- 构建一个Java 编译器插件,用于修改AST 并在编译的解析步骤中删除目的(https://www.baeldung.com/java-build-compiler-plugin)。我不确定在修改 AST 之前编译是否会失败,因为语法无效。
非常感谢任何意见。
您可以使用 TokenStreamRewriter
获取没有目的节点的源代码(或完成许多其他重写任务)。这是一个应用程序示例,我有条件地将顶级 LIMIT
子句添加到 MySQL 查询:
/**
001 * Parses the query to see if there's already a top-level limit clause. If none was found, the query is
002 * rewritten to include a limit clause with the given values.
003 *
004 * @param query The query to check and modify.
005 * @param serverVersion The version of MySQL to use for checking.
006 * @param sqlMode The current SQL mode in the server.
007 * @param offset The limit offset to add.
008 * @param count The row count value to add.
009 *
010 * @returns The rewritten query if the original query is error free and contained no top-level LIMIT clause.
011 * Otherwise the original query is returned.
012 */
013 public checkAndApplyLimits(query: string, serverVersion: number, sqlMode: string, offset: number,
014 count: number): [string, boolean] {
015
016 this.applyServerDetails(serverVersion, sqlMode);
017 const tree = this.startParsing(query, false, MySQLParseUnit.Generic);
018 if (!tree || this.errors.length > 0) {
019 return [query, false];
020 }
021
022 const rewriter = new TokenStreamRewriter(this.tokenStream);
023 const expressions = XPath.findAll(tree, "/query/simpleStatement//queryExpression", this.parser);
024 let changed = false;
025 if (expressions.size > 0) {
026 // There can only be one top-level query expression where we can add a LIMIT clause.
027 const candidate: ParseTree = expressions.values().next().value;
028
029 // Check if the candidate comes from a subquery.
030 let run: ParseTree | undefined = candidate;
031 let invalid = false;
032 while (run) {
033 if (run instanceof SubqueryContext) {
034 invalid = true;
035 break;
036 }
037
038 run = run.parent;
039 }
040
041 if (!invalid) {
042 // Top level query expression here. Check if there's already a LIMIT clause before adding one.
043 const context = candidate as QueryExpressionContext;
044 if (!context.limitClause() && context.stop) {
045 // OK, ready to add an own limit clause.
046 rewriter.insertAfter(context.stop, ` LIMIT ${offset}, ${count}`);
047 changed = true;
048 }
049 }
040 }
051
052 return [rewriter.getText(), changed];
053 }
这段代码在做什么:
- 第017行:解析输入得到解析树。如果你已经这样做了,你当然可以传入解析树,而不是再次解析。
- 第 022 行使用您的令牌流准备一个新的 TokenStreamRewriter 实例。
- 第 023 行使用 ANTLR4 的 XPATH 功能来获取特定上下文类型的所有节点。在这里您可以一次检索所有目的上下文。这也将是您第 2 点的解决方案)。
- 以下几行仅检查是否必须添加新的 LIMIT 子句。对你来说没那么有趣。
- 046行是你操作令牌流的地方。在这种情况下,添加了一些内容,但您也可以 replace or remove 个节点。
- 第 052 行可能包含您最感兴趣的内容:它 returns 输入的原始文本,但应用了所有重写操作。
使用此代码,您可以创建临时 java 文件进行编译。它可以用于同时执行列表中的两个操作(收集目的并删除它们)。
我正在开发具有以下功能的命令行工具:
- 使用扩展的 ANTLR4 Java9 语法解析修改后的 .java 文件。文件中的语法是 Java,对包含目的的方法声明进行了一次修改,如本例所示:
public void {marketing} sendEmail() {}
- 使用访问者收集并删除所有用途。收集和分析目的是程序的主要功能。
- 编译并执行删除用途的 Java 个文件。
我正在寻找实现步骤 3 的最简单和最有效的方法。构建完整的编译器超出了我的项目范围,我更愿意利用 Java 编译器和 运行 javac 如果可能的话。我考虑了以下方法,但 none 似乎是最优的:
- post:Compiling an AST back to source code 中提出的 Prettyprinting(从解析树到源代码)。不过,在大型目录上可能需要做很多工作。
- 使用 ASM 生成字节码,但据我所知,我需要有效的 java 源代码或 class 文件才能正常工作 (https://asm.ow2.io/asm4-guide.pdf)。
- 构建一个Java 编译器插件,用于修改AST 并在编译的解析步骤中删除目的(https://www.baeldung.com/java-build-compiler-plugin)。我不确定在修改 AST 之前编译是否会失败,因为语法无效。
非常感谢任何意见。
您可以使用 TokenStreamRewriter
获取没有目的节点的源代码(或完成许多其他重写任务)。这是一个应用程序示例,我有条件地将顶级 LIMIT
子句添加到 MySQL 查询:
/**
001 * Parses the query to see if there's already a top-level limit clause. If none was found, the query is
002 * rewritten to include a limit clause with the given values.
003 *
004 * @param query The query to check and modify.
005 * @param serverVersion The version of MySQL to use for checking.
006 * @param sqlMode The current SQL mode in the server.
007 * @param offset The limit offset to add.
008 * @param count The row count value to add.
009 *
010 * @returns The rewritten query if the original query is error free and contained no top-level LIMIT clause.
011 * Otherwise the original query is returned.
012 */
013 public checkAndApplyLimits(query: string, serverVersion: number, sqlMode: string, offset: number,
014 count: number): [string, boolean] {
015
016 this.applyServerDetails(serverVersion, sqlMode);
017 const tree = this.startParsing(query, false, MySQLParseUnit.Generic);
018 if (!tree || this.errors.length > 0) {
019 return [query, false];
020 }
021
022 const rewriter = new TokenStreamRewriter(this.tokenStream);
023 const expressions = XPath.findAll(tree, "/query/simpleStatement//queryExpression", this.parser);
024 let changed = false;
025 if (expressions.size > 0) {
026 // There can only be one top-level query expression where we can add a LIMIT clause.
027 const candidate: ParseTree = expressions.values().next().value;
028
029 // Check if the candidate comes from a subquery.
030 let run: ParseTree | undefined = candidate;
031 let invalid = false;
032 while (run) {
033 if (run instanceof SubqueryContext) {
034 invalid = true;
035 break;
036 }
037
038 run = run.parent;
039 }
040
041 if (!invalid) {
042 // Top level query expression here. Check if there's already a LIMIT clause before adding one.
043 const context = candidate as QueryExpressionContext;
044 if (!context.limitClause() && context.stop) {
045 // OK, ready to add an own limit clause.
046 rewriter.insertAfter(context.stop, ` LIMIT ${offset}, ${count}`);
047 changed = true;
048 }
049 }
040 }
051
052 return [rewriter.getText(), changed];
053 }
这段代码在做什么:
- 第017行:解析输入得到解析树。如果你已经这样做了,你当然可以传入解析树,而不是再次解析。
- 第 022 行使用您的令牌流准备一个新的 TokenStreamRewriter 实例。
- 第 023 行使用 ANTLR4 的 XPATH 功能来获取特定上下文类型的所有节点。在这里您可以一次检索所有目的上下文。这也将是您第 2 点的解决方案)。
- 以下几行仅检查是否必须添加新的 LIMIT 子句。对你来说没那么有趣。
- 046行是你操作令牌流的地方。在这种情况下,添加了一些内容,但您也可以 replace or remove 个节点。
- 第 052 行可能包含您最感兴趣的内容:它 returns 输入的原始文本,但应用了所有重写操作。
使用此代码,您可以创建临时 java 文件进行编译。它可以用于同时执行列表中的两个操作(收集目的并删除它们)。