antlr 生成 java 代码_Java如何使用ANTLR4创建AST?

小编典典

让我们建立一个简单的数学示例。对于这样的任务,构建AST完全是矫kill过正,但这是展示原理的好方法。

我将使用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 Function { get; set; }

public ExpressionNode Argument { get; set; }

}

internal class NumberNode : ExpressionNode

{

public double Value { get; set; }

}

将CST转换为AST

ANTLR为我们(MathParser.*Context类)生成了CST节点。现在,我们必须将这些转换为AST节点。

访问者很容易做到这一点,而ANTLR为我们提供了一个MathBaseVisitor类,因此让我们开始吧。

internal class BuildAstVisitor : MathBaseVisitor

{

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)func.CreateDelegate(typeof(Func)),

Argument = Visit(context.expr())

};

}

}

如你所见,这仅是通过使用访问者从CST节点中创建AST节点的问题。代码应该很容易解释(嗯,也许除了VisitFuncExpr东西之外,但这只是将委托连接到类的合适方法的一种快速方法System.Math)。

在这里,你将拥有AST建筑材料。这就是所需要的。只需从CST中提取相关信息,然后将其保存在AST中即可。

AST访客

现在,让我们玩一下AST。我们必须建立一个AST访问者基类来遍历它。让我们做一些与AbstractParseTreeVisitorANTLR提供的类似的事情。

internal abstract class AstVisitor

{

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#的dynamic关键字在一行代码中执行了两次调度。在Java中,你必须使用以下if语句序列自己进行连接:

if (node is AdditionNode) {

return Visit((AdditionNode)node);

} else if (node is SubtractionNode) {

return Visit((SubtractionNode)node);

} else if ...

但是我只是为这个例子寻求捷径。

与AST合作

那么,我们可以用数学表达式树做什么?评估一下,当然!让我们实现一个表达式评估器:

internal class EvaluateExpressionVisitor : AstVisitor

{

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();

}

}

}

2020-03-07

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值