如何从解析树到 Java class 文件

How to get from parse tree to Java class file

我正在开发具有以下功能的命令行工具:

  1. 使用扩展的 ANTLR4 Java9 语法解析修改后的 .java 文件。文件中的语法是 Java,对包含目的的方法声明进行了一次修改,如本例所示:public void {marketing} sendEmail() {}
  2. 使用访问者收集并删除所有用途。收集和分析目的是程序的主要功能。
  3. 编译并执行删除用途的 Java 个文件。

我正在寻找实现步骤 3 的最简单和最有效的方法。构建完整的编译器超出了我的项目范围,我更愿意利用 Java 编译器和 运行 javac 如果可能的话。我考虑了以下方法,但 none 似乎是最优的:

非常感谢任何意见。

您可以使用 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 文件进行编译。它可以用于同时执行列表中的两个操作(收集目的并删除它们)。