java 如何使用ANTLR4创建AST?

mrphzbgm  于 2023-02-28  发布在  Java
关注(0)|答案(4)|浏览(302)

我已经搜索了很多关于这方面的东西,我没有找到任何有用的东西,真正帮助我建立一个AST.我已经知道,ANTLR 4不像ANTLR 3以前做的那样建立AST.大家说:“嘿,使用访客!",但我找不到任何例子或更详细的解释,我怎么能做到这一点...
我有一个语法必须喜欢C,但每一个命令写在葡萄牙语(葡萄牙编程语言)。我可以很容易地生成使用ANTLR 4的解析树。我的问题是:我现在需要做什么来创建AST?
顺便说一句,我正在使用Java和IntelliJ...

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

由于第一次尝试没有像我预期的那样对我起作用,我尝试使用ANTLR 3中的this tutorial,但我不知道如何使用StringTamplate代替ST...
阅读The Definitive ANTLR 4 Reference这本书,我也找不到任何与AST相关的东西。

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

3npbholx

3npbholx1#

好吧,让我们来构建一个简单的数学例子。构建一个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的东西,但它只是一个将委托连接到System.Math类的合适方法的快速方法)。
这里你有了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#的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();
        }
    }
}

现在我们终于可以玩它了

qij5mzcb

qij5mzcb2#

我创建了一个小型的Java项目,它允许您通过在内存中编译ANTLR生成的lexer和parser来立即测试ANTLR语法。您可以通过将字符串传递给parser来解析字符串,parser将自动从中生成AST,然后可以在应用程序中使用该AST。
为了减小AST的大小,您可以使用NodeFilter,您可以向其中添加您希望在构造AST时考虑的非终端的产生式规则名称。
该代码和一些代码示例可以在https://github.com/julianthome/inmemantlr中找到
希望工具有用;-)

fcipmucu

fcipmucu3#

我发现了两种简单的方法,重点是antlr4的TestRig.java文件中可用的功能。
1.通过终端
这是我使用相应的CPP14.g4语法文件java -cp .:antlr-4.9-complete.jar org.antlr.v4.gui.TestRig CPP14 translationunit -tree filename.cpp解析C的示例。如果省略filename.cpp,rig将从stdin读取。"translationunit"是我使用的CPP14.g4语法文件的起始规则名称。
1.经由 java
我使用了www.example.com文件中的部分代码,让我们再次假设我们有一个C
源代码字符串,我们希望从其中生成AST(您也可以直接从文件中读取)。TestRig.java file. Let's suppose again that we have a string of C++ source code from which we want to produce the AST (you can also read directly from a file).

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生成必要的文件(lexer、parser等),然后编译所有 *. java文件。

nfeuvbwi

nfeuvbwi4#

我已经将Terrence Parr的书“Language Implementation Pattern”中基于antlr 3(tpdsl-code/walking/tree-grammar下的source-code)的模式“Tree Grammar”的代码转换为使用访问者和“Homogeneous AST”模式的antlr 4。

语法如下:

矢量数学. g4
// START: header
grammar VecMath;
tokens { VEC }       // define imaginary token for vector literal
// END: header

// START: stat
prog:   stat+ ;              // build list of stat trees
stat:   ID assign='=' expr  #StatAssign // '=' is operator subtree root
    |   print='print' expr  #StatPrint  // 'print' is subtree root
    ;
// END: stat

// START: expr
expr: left=expr op=('*'|'.') right=expr #ExprMult // '*', '.' are roots
    | left=expr op='+' right=expr       #ExprAdd  // '+' is root node
    | '[' expr (',' expr)* ']'    #ExprVec  // VEC is root
    | INT                               #ExprInt
    | ID                                #ExprId
    ;
// END: expr

ID  :   'a'..'z'+ ;
INT :   '0'..'9'+ ;
WS  :   (' '|'\r'|'\n')+ -> skip ;

访客

package walking.v4.vecmath_ast.impl;

import org.antlr.v4.runtime.CommonToken;

import walking.v4.vecmath_ast.antlr.VecMathBaseVisitor;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprAddContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprIdContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprIntContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprMultContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ExprVecContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.ProgContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatAssignContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatContext;
import walking.v4.vecmath_ast.antlr.VecMathParser.StatPrintContext;
import walking.v4.vecmath_ast.antlr.VecMathParser;

public class VecMathBuildASTVisitor extends VecMathBaseVisitor<AST> {
    @Override
    public AST visitProg(ProgContext ctx) {
        AST ast = new AST();
        for (StatContext stmt : ctx.stat()) {
            ast.addChild(visit(stmt));
        }
        return ast;
    }

    @Override
    public AST visitStatAssign(StatAssignContext ctx) {
        AST ast = new AST(ctx.assign);

        ast.addChild(new AST(ctx.ID().getSymbol()));
        ast.addChild(visit(ctx.expr()));
        return ast;
    }

    @Override
    public AST visitStatPrint(StatPrintContext ctx) {
        AST ast = new AST(ctx.print);
        ast.addChild(visit(ctx.expr()));
        return ast;
    }

    @Override
    public AST visitExprMult(ExprMultContext ctx) {
        AST ast = new AST(ctx.op);
        ast.addChild(visit(ctx.left));
        ast.addChild(visit(ctx.right));
        return ast;
    }

    @Override
    public AST visitExprAdd(ExprAddContext ctx) {
        AST ast = new AST(ctx.op);
        ast.addChild(visit(ctx.left));
        ast.addChild(visit(ctx.right));
        return ast;
    }

    @Override
    public AST visitExprVec(ExprVecContext ctx) {
        AST ast = new AST(new CommonToken(VecMathParser.VEC, "VEC"));
        for (ExprContext expr : ctx.expr()) {
            ast.addChild(visit(expr));
        }
        return ast;
    }

    @Override
    public AST visitExprId(ExprIdContext ctx) {
        AST ast = new AST(ctx.ID().getSymbol());
        return ast;
    }

    @Override
    public AST visitExprInt(ExprIntContext ctx) {
        AST ast = new AST(ctx.INT().getSymbol());
        return ast;
    }
}

AST

与tpdsl-code/IR/Homo中的原始版本相比,基本上只是调整了令牌

package  walking.v4.vecmath_ast.impl;

import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.Token;

import walking.v4.vecmath_ast.antlr.VecMathParser;

import java.util.ArrayList;
import java.util.List;

// Homogenous AST node type 
public class AST {
   Token token;             // from which token do we create this node?
   List<AST> children;      // normalized list of children 

   public AST() { ; } // for making nil-rooted nodes

   public AST(Token token) { this.token = token; }

   /** Create node from token type; used mainly for imaginary tokens */
   public AST(int tokenType) { this.token = new CommonToken(tokenType); }

   /** external visitors execute the same action for all nodes
    * with same node type while walking
    */
    public int getNodeType() { return token.getType(); }

    public void addChild(AST t) {
        if (children == null) children = new ArrayList<>();
        children.add(t);
    }

    public List<AST> getChildren() { return children; }
    
    /** to represent flat lists. A list is a subtree w/o a root, which we simulate
     * with a nil root node. A nil node is a node with token == null.
     */
    public boolean isNil() { return token == null; }

    /** Compute string for single node */
    public String toString() { 
        String typeName = VecMathParser.VOCABULARY.getSymbolicName(getNodeType());
        typeName = typeName == null ? token.getText() : typeName;
        return token != null ? "<" +typeName +", '" + token.getText() +"'>": "nil"; 
    }

    /** Compute string for a whole tree */
    public String toStringTree() {
        if (children == null || children.size() == 0) return this.toString();

        StringBuffer buf = new StringBuffer();
        if (!isNil()) {
           buf.append('(');
           buf.append(this.toString());
           buf.append(' ');
        }
        for (int i = 0; i < children.size(); i++) {
            AST t = (AST) children.get(i); // normalized (unnamed) children
            if (i>0) buf.append(' ');
            buf.append(t.toStringTree());
        }
        if (!isNil()) buf.append(')');
        return buf.toString();
    }
}

测试类

package walking.v4.vecmath_ast;

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

import walking.v4.vecmath_ast.antlr.VecMathLexer;
import walking.v4.vecmath_ast.antlr.VecMathParser;

import walking.v4.vecmath_ast.impl.AST;
import walking.v4.vecmath_ast.impl.VecMathBuildASTVisitor;

public class Test {
    public static void main(String[] args) throws Exception {
        CharStream input = CharStreams.fromFileName(args[0]);
        VecMathLexer lexer = new VecMathLexer(input);

        CommonTokenStream tokens = new CommonTokenStream(lexer);
        VecMathParser parser = new VecMathParser(tokens);

        ParseTree tree = parser.prog();

        for (AST ast : new VecMathBuildASTVisitor().visit(tree).getChildren()) {
            System.out.println(ast.toStringTree());
        }
    } 
}

测试输入

x = 3 + 4
y = 3 + 4 + 5
a = 3 * 4
a = 3 * 4 * 5
c = 3 * 4 + 5
print x * [2, 3, 4]
print x * [2+5, 3, 4]

产量:

(<=, '='> <ID, 'x'> (<+, '+'> <INT, '3'> <INT, '4'>))
(<=, '='> <ID, 'y'> (<+, '+'> (<+, '+'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<=, '='> <ID, 'a'> (<*, '*'> <INT, '3'> <INT, '4'>))
(<=, '='> <ID, 'a'> (<*, '*'> (<*, '*'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<=, '='> <ID, 'c'> (<+, '+'> (<*, '*'> <INT, '3'> <INT, '4'>) <INT, '5'>))
(<print, 'print'> (<*, '*'> <ID, 'x'> (<VEC, 'VEC'> <INT, '2'> <INT, '3'> <INT, '4'>)))
(<print, 'print'> (<*, '*'> <ID, 'x'> (<VEC, 'VEC'> (<+, '+'> <INT, '2'> <INT, '5'>) <INT, '3'> <INT, '4'>)))

相关问题