gpt4 book ai didi

java - 如何使用 ANTLR4 创建 AST?

转载 作者:IT老高 更新时间:2023-10-28 20:27:37 35 4
gpt4 key购买 nike

我一直在搜索这方面的内容,但找不到任何真正帮助我构建 AST 的有用信息。我已经知道 ANTLR4 不像以前的 ANTLR3 那样构建 AST。每个人都说:“嘿,使用访问者!”,但我找不到任何示例或更详细的解释来说明如何做到这一点......

我有一个必须像 C 的语法,但每个命令都是用葡萄牙语(葡萄牙语编程语言)编写的。我可以使用 ANTLR4 轻松生成解析树。我的问题是:我现在需要做什么来创建 AST?

顺便说一句,我正在使用 Java 和 IntelliJ...

EDIT1:我能得到的最接近的是使用这个主题的答案:Is there a simple example of using antlr4 to create an AST from java source code and extract methods, variables and comments?但它只打印访问方法的名称..

由于第一次尝试没有像我预期的那样对我有用,我尝试使用 this tutorial来自 ANTLR3,但我不知道如何使用 StringTamplate 而不是 ST...

看书The Definitive ANTLR 4 Reference我也找不到任何与 AST 相关的内容。

EDIT2:现在我有一个类来创建 DOT 文件,我只需要弄清楚如何正确使用访问者

最佳答案

好的,让我们构建一个简单的数学示例。对于这样的任务来说,构建 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 节点(MathParser.*Context 类)。我们现在必须将这些转换为 AST 节点。

访问者很容易做到这一点,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 节点即可。代码应该是不言自明的(好吧,也许除了 VisitFuncExpr 的东西,但它只是将委托(delegate)连接到 System.Math 类的合适方法的一种快速方法)。

这里有 AST 构建的东西。这就是所有需要的。只需从 CST 中提取相关信息并将其保存在 AST 中即可。

AST 访问者

现在,让我们玩一下 AST。我们必须构建一个 AST 访问者基类来遍历它。让我们做一些类似于 AbstractParseTreeVisitor<T> 的事情。由 ANTLR 提供。

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# 的 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<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();
}
}
}

现在我们终于可以玩了:

enter image description here

关于java - 如何使用 ANTLR4 创建 AST?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29971097/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com