You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求ANTLR实现Parse Tree转AST的完整Java开发示例

我完全懂你卡在Parse Tree转AST的那种迷茫——之前我刚接触ANTLR的时候也对着一堆零散示例摸不着头脑,尤其是从语法定义到Java代码落地的全流程,总觉得缺了关键的一环。下面给你一个极简但完整的示例,模拟类似Oracle的SQL子集,一步步带你实现从解析到AST的转换,你可以直接套用到你的项目里。

一、ANTLR语法定义(.g4文件)

先写一个简化版的类Oracle SQL语法,聚焦核心的SELECT逻辑,同时标记需要保留/忽略的节点:

grammar MiniOracleSQL;

// 顶层语法规则
sqlStmt: selectStmt EOF;

// SELECT语句核心结构
selectStmt: SELECT selectItemList FROM tableName (WHERE condition)?;

// 选择项列表
selectItemList: selectItem (',' selectItem)*;
selectItem: columnName | '*';

// 表名、列名
tableName: IDENTIFIER;
columnName: IDENTIFIER;

// WHERE条件
condition: columnName COMPARISON_OPERATOR literal;
literal: NUMBER | STRING;

// 词法规则(关键字、运算符)
SELECT: 'SELECT' | 'select';
FROM: 'FROM' | 'from';
WHERE: 'WHERE' | 'where';

COMPARISON_OPERATOR: '=' | '<' | '>' | '<=' | '>=' | '<>';

// 标识符与字面量
IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
NUMBER: [0-9]+ ('.' [0-9]+)?;
STRING: '\'' (~['\r\n] | '\'\'')* '\'';

// 忽略无关的空白字符
WHITESPACE: [ \t\r\n]+ -> skip;

这个语法里我们只保留语义相关的节点,像空格、关键字这类语法符号会被直接忽略或跳过。

二、AST节点的Java类定义

AST只保留业务语义信息,所以我们要为每个核心语法结构定义对应的Java类:

// 所有AST节点的父类,记录位置信息用于错误定位
public abstract class ASTNode {
    private final int line;
    private final int column;

    public ASTNode(int line, int column) {
        this.line = line;
        this.column = column;
    }

    public int getLine() { return line; }
    public int getColumn() { return column; }
    @Override
    public abstract String toString();
}

// SELECT语句节点
public class SelectStmtNode extends ASTNode {
    private final List<SelectItemNode> selectItems;
    private final String tableName;
    private final ConditionNode whereCondition;

    public SelectStmtNode(int line, int column, List<SelectItemNode> selectItems, String tableName, ConditionNode whereCondition) {
        super(line, column);
        this.selectItems = selectItems;
        this.tableName = tableName;
        this.whereCondition = whereCondition;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("SELECT ").append(
                String.join(", ", selectItems.stream().map(ASTNode::toString).toList())
        );
        sb.append(" FROM ").append(tableName);
        if (whereCondition != null) {
            sb.append(" WHERE ").append(whereCondition);
        }
        return sb.toString();
    }
}

// 选择项节点(支持字段或通配符)
public class SelectItemNode extends ASTNode {
    private final String value;
    private final boolean isWildcard;

    public SelectItemNode(int line, int column, String value, boolean isWildcard) {
        super(line, column);
        this.value = value;
        this.isWildcard = isWildcard;
    }

    @Override
    public String toString() {
        return isWildcard ? "*" : value;
    }
}

// WHERE条件节点
public class ConditionNode extends ASTNode {
    private final String columnName;
    private final String operator;
    private final LiteralNode literal;

    public ConditionNode(int line, int column, String columnName, String operator, LiteralNode literal) {
        super(line, column);
        this.columnName = columnName;
        this.operator = operator;
        this.literal = literal;
    }

    @Override
    public String toString() {
        return columnName + " " + operator + " " + literal;
    }
}

// 字面量节点(数字/字符串)
public class LiteralNode extends ASTNode {
    private final Object value;
    private final LiteralType type;

    public enum LiteralType { NUMBER, STRING }

    public LiteralNode(int line, int column, Object value, LiteralType type) {
        super(line, column);
        this.value = value;
        this.type = type;
    }

    @Override
    public String toString() {
        return type == LiteralType.STRING ? "'" + value + "'" : value.toString();
    }
}
三、用ANTLR Visitor转换AST

ANTLR的Visitor模式比Listener更灵活,我们可以精准控制每个Parse Tree节点的转换逻辑:

import org.antlr.v4.runtime.tree.ParseTree;
import java.util.ArrayList;
import java.util.List;

public class MiniOracleSQLVisitorImpl extends MiniOracleSQLBaseVisitor<ASTNode> {

    @Override
    public ASTNode visitSqlStmt(MiniOracleSQLParser.SqlStmtContext ctx) {
        return visit(ctx.selectStmt());
    }

    @Override
    public ASTNode visitSelectStmt(MiniOracleSQLParser.SelectStmtContext ctx) {
        // 转换选择项列表
        List<SelectItemNode> selectItems = new ArrayList<>();
        for (MiniOracleSQLParser.SelectItemContext itemCtx : ctx.selectItemList().selectItem()) {
            selectItems.add((SelectItemNode) visit(itemCtx));
        }

        // 提取表名
        String tableName = ctx.tableName().IDENTIFIER().getText();

        // 转换WHERE条件(可能为空)
        ConditionNode whereCondition = null;
        if (ctx.condition() != null) {
            whereCondition = (ConditionNode) visit(ctx.condition());
        }

        // 创建SELECT语句AST节点
        return new SelectStmtNode(
                ctx.start.getLine(),
                ctx.start.getCharPositionInLine(),
                selectItems,
                tableName,
                whereCondition
        );
    }

    @Override
    public ASTNode visitSelectItem(MiniOracleSQLParser.SelectItemContext ctx) {
        int line = ctx.start.getLine();
        int column = ctx.start.getCharPositionInLine();
        if (ctx.STAR() != null) {
            return new SelectItemNode(line, column, "*", true);
        } else {
            return new SelectItemNode(line, column, ctx.columnName().IDENTIFIER().getText(), false);
        }
    }

    @Override
    public ASTNode visitCondition(MiniOracleSQLParser.ConditionContext ctx) {
        int line = ctx.start.getLine();
        int column = ctx.start.getCharPositionInLine();
        String columnName = ctx.columnName().IDENTIFIER().getText();
        String operator = ctx.COMPARISON_OPERATOR().getText();
        LiteralNode literal = (LiteralNode) visit(ctx.literal());
        return new ConditionNode(line, column, columnName, operator, literal);
    }

    @Override
    public ASTNode visitLiteral(MiniOracleSQLParser.LiteralContext ctx) {
        int line = ctx.start.getLine();
        int column = ctx.start.getCharPositionInLine();
        if (ctx.NUMBER() != null) {
            String numText = ctx.NUMBER().getText();
            Object value = numText.contains(".") ? Double.parseDouble(numText) : Long.parseLong(numText);
            return new LiteralNode(line, column, value, LiteralNode.LiteralType.NUMBER);
        } else {
            // 处理字符串字面量,去掉前后单引号并替换转义的单引号
            String strText = ctx.STRING().getText().substring(1, ctx.STRING().getText().length() - 1);
            strText = strText.replace("''", "'");
            return new LiteralNode(line, column, strText, LiteralNode.LiteralType.STRING);
        }
    }
}

这里每个visit方法对应语法里的规则,我们只提取Parse Tree中对语义有意义的信息,忽略掉关键字、逗号这类语法符号。

四、测试主程序

最后写一个测试类验证整个流程:

import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

public class ASTGeneratorTest {
    public static void main(String[] args) {
        // 测试用的类Oracle SQL语句
        String sql = "SELECT id, name, email FROM users WHERE age > 18 AND status = 'active'";

        // 1. 词法分析
        CharStream input = CharStreams.fromString(sql);
        MiniOracleSQLLexer lexer = new MiniOracleSQLLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        // 2. 语法分析生成Parse Tree
        MiniOracleSQLParser parser = new MiniOracleSQLParser(tokens);
        ParseTree parseTree = parser.sqlStmt();

        // 3. 转换为AST
        MiniOracleSQLVisitorImpl visitor = new MiniOracleSQLVisitorImpl();
        ASTNode ast = visitor.visit(parseTree);

        // 打印AST结果
        System.out.println("生成的AST内容:");
        System.out.println(ast.toString());
    }
}

运行后会输出:

生成的AST内容:
SELECT id, name, email FROM users WHERE age > 18
核心思路总结
  1. 语法定义时,只保留语义相关的结构,语法符号(如关键字、空格)直接忽略;
  2. AST节点只存储业务需要的信息,去掉Parse Tree中的冗余节点;
  3. 使用Visitor模式可以精准控制转换逻辑,比Listener更适合生成AST;
  4. 保留节点的行号列号,方便后续错误定位或代码生成。

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:32:08