[英]How to create AST with ANTLR4?
我一直在搜索很多关于这个的东西,但我找不到任何真正帮助我构建 AST 的有用信息。 我已经知道 ANTLR4 不像以前的 ANTLR3 那样构建 AST。 每个人都说:“嘿,使用访问者!”,但我找不到任何关于如何执行此操作的示例或更详细的说明......
我有一个语法必须像 C,但每个命令都是用葡萄牙语(葡萄牙语编程语言)编写的。 我可以使用 ANTLR4 轻松生成解析树。 我的问题是:我现在需要做什么来创建 AST?
顺便说一句,我正在使用 Java 和 IntelliJ ...
EDIT1:我能得到的最接近的是使用本主题的答案: 是否有一个使用 antlr4 从 Java 源代码创建 AST 并提取方法、变量和注释的简单示例? 但它只打印被访问方法的名称..
由于第一次尝试没有像我预期的那样对我有用,我尝试使用 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 节点。 这些是完全自定义的,您可以按照自己的方式定义它们。
以下是我们将用于此示例的节点:
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; }
}
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
东西,但这只是将委托连接到System.Math
类的合适方法的一种快速方法)。
在这里你有 AST 构建的东西。 这就是所需要的。 只需从 CST 中提取相关信息并将其保存在 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# 的dynamic
关键字在一行代码中执行了双重调度。 在 Java 中,您必须自己使用一系列if
语句进行接线,如下所示:
if (node is AdditionNode) {
return Visit((AdditionNode)node);
} else if (node is SubtractionNode) {
return Visit((SubtractionNode)node);
} else if ...
但我只是选择了这个例子的快捷方式。
那么,我们可以用数学表达式树做什么呢? 当然要评价! 让我们实现一个表达式评估器:
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();
}
}
}
现在我们终于可以玩它了:
我创建了一个小型 Java 项目,它允许您通过编译内存中 ANTLR 生成的词法分析器和解析器来立即测试您的 ANTLR 语法。 您可以通过将字符串传递给解析器来解析字符串,它会自动从中生成一个 AST,然后可以在您的应用程序中使用。
为了减小 AST 的大小,您可以使用一个 NodeFilter,您可以向其中添加在构建 AST 时要考虑的非终端的生产规则名称。
代码和一些代码示例可以在https://github.com/julianthome/inmemantlr找到
希望该工具有用;-)
我找到了两种简单的方法,专注于antlr4 的 TestRig.java文件中可用的功能。
这是我使用相应的 CPP14.g4 语法文件java -cp .:antlr-4.9-complete.jar org.antlr.v4.gui.TestRig CPP14 translationunit -tree filename.cpp
解析 C++ 的示例。 如果省略 filename.cpp,则装备将从 stdin 读取。 “translationunit”是我使用的CPP14.g4语法文件的起始规则名称。
我使用了 TestRig.java 文件中的部分代码。 让我们再次假设我们有一个 C++ 源代码字符串,我们想从中生成 AST(您也可以直接从文件中读取)。
String source_code = "...your cpp source code...";
CodePointCharStream stream_from_string = CharStreams.fromString(source_code);
CPP14Lexer lexer = new CPP14Lexer(new ANTLRInputStream(source_code));
CommonTokenStream tokens = new CommonTokenStream(lexer);
CPP14Parser parser = new CPP14Parser(tokens);
String parserName = "CPP14Parser";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<? extends Parser> parserClass = null;
parserClass = cl.loadClass(parserName).asSubclass(Parser.class);
String startRuleName = "translationunit"; //as specified in my CPP14.g4 file
Method startRule = parserClass.getMethod(startRuleName);
ParserRuleContext tree = (ParserRuleContext)startRule.invoke(parser, (Object[])null);
System.out.println(tree.toStringTree(parser));
我的进口是:
import java.lang.reflect.Method;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Parser;
所有这些都要求您使用命令java -jar yournaltrfile.jar yourgrammar.g4
生成必要的文件(词法分析器、解析器等),然后编译所有 *.java 文件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.