Using Visitors in AntLR4 in a Simple Integer List Grammar

左心房为你撑大大i 提交于 2020-08-05 05:50:10

问题


I'm a newbie in AntLR. I'm using AntLR4 version.

I wrote the following attribute grammar that recognizes a list of integers and print the sum of the list at the end.

list.g4

grammar list;

@header
{
    import java.util.List;
    import java.util.ArrayList;
}

list
    : BEGL (elems[new ArrayList<Integer>()])? ENDL
        {   
            int sum = 0;
            if($elems.text != null)
                for(Integer i : $elems.listOut)
                    sum += i;
            System.out.println("List Sum: " + sum);
        }
;

elems [List<Integer> listIn] returns [List<Integer> listOut]
    : a=elem (SEP b=elem
            { listIn.add($b.value); }
        )*
            {
                listIn.add($a.value);
                $listOut = $listIn;
            }
;

elem returns [int value]
    : NUM { $value = $NUM.int; }
;

BEGL : '[';
ENDL : ']';
SEP : ',';
NUM : [0-9]+;
WS : (' '|'\t'|'\n')+ -> skip;

A valid input would be:

[1, 2, 3]

For testing my grammar, I'm using TestRig Tool.

Now, I would like to use Visitors to clearily separate the code from the grammar.

I know that I need to use antlr with the -visitor option to generate the Visitor class for my application.

I would like to know how to access the atrributes of a given production in the Visitor methods class and how to "glue" the lexer, parser and visitor code pieces together.


回答1:


Your grammar without actions, and including \r in the WS rule:

grammar list;

list
 : BEGL elems? ENDL
 ;

elems
 : elem ( SEP elem )*
 ;

elem
 : NUM
 ;

BEGL : '[';
ENDL : ']';
SEP  : ',';
NUM  : [0-9]+;
WS   : [ \t\r\n]+ -> skip;

The visitor could then look like this:

public class SumVisitor extends listBaseVisitor<Integer> {

    @Override
    public Integer visitList(@NotNull listParser.ListContext ctx) {
        return ctx.elems() == null ? 0 : this.visitElems(ctx.elems());
    }

    @Override
    public Integer visitElems(@NotNull listParser.ElemsContext ctx) {
        int sum = 0;
        for (listParser.ElemContext elemContext : ctx.elem()) {
            sum += this.visitElem(elemContext);
        }
        return sum;
    }

    @Override
    public Integer visitElem(@NotNull listParser.ElemContext ctx) {
        return Integer.valueOf(ctx.NUM().getText());
    }
}

and can be tested as follows:

listLexer lexer = new listLexer(new ANTLRInputStream("[1, 2, 3]"));
listParser parser = new listParser(new CommonTokenStream(lexer));
Integer sum = new SumVisitor().visit(parser.list());
System.out.println("sum=" + sum);

which will print:

sum=6



回答2:


Making a Visitor is making a class that extends YourGrammarBaseVisitor<T>, in your case, ListBaseVisitor<T>. This class doesn't NEED to have any methods, but can override methods called visitX, where X is a rule name, like visitElem. The generic-type T is the return of the visit calls, but you can use an Object return type to return anything. It would be something like

class MyVisitor extends ListBaseVisitor<Object> {
    @Override
    public Object visitElem(ListParser.ElemRuleContext ctx) {
        return new Integer(Integer.parseInt(ctx.NUM().getText()));
    }
    @Override
    public Object visitElems(ListParser.ElemsRuleContext ctx) {
        ArrayList<Integer> l = new ArrayList<Integer>();
        for (ListParser.ElemRuleContext innerCtx : ctx.elem()) {
            l.Add((Integer)visitElem(innerCtx));
        }
        return l;
    }
    // TODO: visitList method, and suppose it returns an Integer object containing the sum
}

Note that each method will receive its own context type (the visitList method will receive a ListParser.ListRuleContext and so on), and these objects contain information about the rules parsed. For example, in the elem rule a used the NUM() method, and in the elems rule I used the elem() method. Not that EBNF notation for multiple rules (rule* or rule+) makes the method into a collection you can iterate over.

For using your new visitor, you just need to instantiate the Antlr objects like before, and visit the first rule of the grammar with the tree generated by the parser, like this:

AntlrInputStream input = new AntlrInputStream(languageInputComingFromSomewhere);
ListLexer lex = new ListLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lex);
ListParser parser = new ListParser(tokens);

// here we are parsing the tree
ListParser.ListRuleContext parseTree = parser.list();

MyVisitor visitor = new MyVisitor();
// here the visitor will do its work, visiting the tree parsed before
Integer sum = (Integer)visitor.visitList(parseTree);

Pardon any Java mistakes, I'm not a Java programmar



来源:https://stackoverflow.com/questions/27405893/using-visitors-in-antlr4-in-a-simple-integer-list-grammar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!