好吧,让我们构建一个简单的数学示例。对于这样的任务来说,构建AST是完全过分的,但这是展示原则的好方法。
我将在C#中执行此操作,但Java版本将非常相似。
语法
首先,让我们写一个非常基本的数学语法来使用:
grammar Math;
compileUnit
    :   expr EOF
    ;
expr
    :   '(' expr ')'                         # parensExpr
    |   op=('+'|'-') expr                    # unaryExpr
    |   left=expr op=('*'|'/') right=expr    # infixExpr
    |   left=expr op=('+'|'-') right=expr    # infixExpr
    |   func=ID '(' expr ')'                 # funcExpr
    |   value=NUM                            # numberExpr
    ;
OP_ADD: '+';
OP_SUB: '-';
OP_MUL: '*';
OP_DIV: '/';
NUM :   [0-9]+ ('.' [0-9]+)? ([eE] [+-]? [0-9]+)?;
ID  :   [a-zA-Z]+;
WS  :   [ \t\r\n] -> channel(HIDDEN);
非常基本的东西,我们有一个处理一切的规则(优先规则等)。expr
AST 节点
然后,让我们定义一些我们将使用的 AST 节点。这些都是完全自定义的,您可以按照自己想要的方式定义它们。
以下是我们将在此示例中使用的节点:
internal abstract class ExpressionNode
{
}
internal abstract class InfixExpressionNode : ExpressionNode
{
    public ExpressionNode Left { get; set; }
    public ExpressionNode Right { get; set; }
}
internal class AdditionNode : InfixExpressionNode
{
}
internal class SubtractionNode : InfixExpressionNode
{
}
internal class MultiplicationNode : InfixExpressionNode
{
}
internal class DivisionNode : InfixExpressionNode
{
}
internal class NegateNode : ExpressionNode
{
    public ExpressionNode InnerNode { get; set; }
}
internal class FunctionNode : ExpressionNode
{
    public Func<double, double> Function { get; set; }
    public ExpressionNode Argument { get; set; }
}
internal class NumberNode : ExpressionNode
{
    public double Value { get; set; }
}
将 CST 转换为 AST
ANTLR为我们生成了CST节点(类)。我们现在必须将它们转换为AST节点。MathParser.*Context
这很容易通过访问者完成,ANTLR为我们提供了一个类,所以让我们使用它。MathBaseVisitor<T>
internal class BuildAstVisitor : MathBaseVisitor<ExpressionNode>
{
    public override ExpressionNode VisitCompileUnit(MathParser.CompileUnitContext context)
    {
        return Visit(context.expr());
    }
    public override ExpressionNode VisitNumberExpr(MathParser.NumberExprContext context)
    {
        return new NumberNode
        {
            Value = double.Parse(context.value.Text, NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent)
        };
    }
    public override ExpressionNode VisitParensExpr(MathParser.ParensExprContext context)
    {
        return Visit(context.expr());
    }
    public override ExpressionNode VisitInfixExpr(MathParser.InfixExprContext context)
    {
        InfixExpressionNode node;
        switch (context.op.Type)
        {
            case MathLexer.OP_ADD:
                node = new AdditionNode();
                break;
            case MathLexer.OP_SUB:
                node = new SubtractionNode();
                break;
            case MathLexer.OP_MUL:
                node = new MultiplicationNode();
                break;
            case MathLexer.OP_DIV:
                node = new DivisionNode();
                break;
            default:
                throw new NotSupportedException();
        }
        node.Left = Visit(context.left);
        node.Right = Visit(context.right);
        return node;
    }
    public override ExpressionNode VisitUnaryExpr(MathParser.UnaryExprContext context)
    {
        switch (context.op.Type)
        {
            case MathLexer.OP_ADD:
                return Visit(context.expr());
            case MathLexer.OP_SUB:
                return new NegateNode
                {
                    InnerNode = Visit(context.expr())
                };
            default:
                throw new NotSupportedException();
        }
    }
    public override ExpressionNode VisitFuncExpr(MathParser.FuncExprContext context)
    {
        var functionName = context.func.Text;
        var func = typeof(Math)
            .GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Where(m => m.ReturnType == typeof(double))
            .Where(m => m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(double) }))
            .FirstOrDefault(m => m.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase));
        if (func == null)
            throw new NotSupportedException(string.Format("Function {0} is not supported", functionName));
        return new FunctionNode
        {
            Function = (Func<double, double>)func.CreateDelegate(typeof(Func<double, double>)),
            Argument = Visit(context.expr())
        };
    }
}
如您所见,只需使用访问者从 CST 节点创建 AST 节点即可。代码应该是不言自明的(好吧,也许除了这些东西,但这只是将委托连接到System.Math类的合适方法的快速方法)。VisitFuncExpr
这里有AST建筑的东西。这就是所有需要的。只需从 CST 中提取相关信息并将其保留在 AST 中即可。
AST访客
现在,让我们玩一下AST。我们必须构建一个 AST 访问者基类来遍历它。让我们做一些类似于ANTLR提供的事情。AbstractParseTreeVisitor<T>
internal abstract class AstVisitor<T>
{
    public abstract T Visit(AdditionNode node);
    public abstract T Visit(SubtractionNode node);
    public abstract T Visit(MultiplicationNode node);
    public abstract T Visit(DivisionNode node);
    public abstract T Visit(NegateNode node);
    public abstract T Visit(FunctionNode node);
    public abstract T Visit(NumberNode node);
    public T Visit(ExpressionNode node)
    {
        return Visit((dynamic)node);
    }
}
在这里,我利用 C# 的关键字在一行代码中执行双重调度。在Java中,您必须使用如下一系列语句自己进行连接:dynamicif
if (node is AdditionNode) {
    return Visit((AdditionNode)node);
} else if (node is SubtractionNode) {
    return Visit((SubtractionNode)node);
} else if ...
但我只是选择了这个例子的捷径。
使用 AST
那么,我们可以用数学表达式树做什么呢?当然,评估它!让我们实现一个表达式计算器:
internal class EvaluateExpressionVisitor : AstVisitor<double>
{
    public override double Visit(AdditionNode node)
    {
        return Visit(node.Left) + Visit(node.Right);
    }
    public override double Visit(SubtractionNode node)
    {
        return Visit(node.Left) - Visit(node.Right);
    }
    public override double Visit(MultiplicationNode node)
    {
        return Visit(node.Left) * Visit(node.Right);
    }
    public override double Visit(DivisionNode node)
    {
        return Visit(node.Left) / Visit(node.Right);
    }
    public override double Visit(NegateNode node)
    {
        return -Visit(node.InnerNode);
    }
    public override double Visit(FunctionNode node)
    {
        return node.Function(Visit(node.Argument));
    }
    public override double Visit(NumberNode node)
    {
        return node.Value;
    }
}
一旦我们有了AST,就非常简单,不是吗?
将它们放在一起
最后但并非最不重要的一点是,我们必须实际编写主程序:
internal class Program
{
    private static void Main()
    {
        while (true)
        {
            Console.Write("> ");
            var exprText = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(exprText))
                break;
            var inputStream = new AntlrInputStream(new StringReader(exprText));
            var lexer = new MathLexer(inputStream);
            var tokenStream = new CommonTokenStream(lexer);
            var parser = new MathParser(tokenStream);
            try
            {
                var cst = parser.compileUnit();
                var ast = new BuildAstVisitor().VisitCompileUnit(cst);
                var value = new EvaluateExpressionVisitor().Visit(ast);
                Console.WriteLine("= {0}", value);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.WriteLine();
        }
    }
}
现在我们终于可以玩它了:
