在一个简单的整数列表语法中使用AntLR4中的访问者模式

3

我是AntLR的新手。我正在使用AntLR4版本。

我编写了以下属性语法,可以识别整数列表并在末尾打印列表的总和。

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;

一个有效的输入应该是:

[1, 2, 3]

为了测试我的语法,我正在使用TestRig工具。

现在,我想使用访问者来清晰地分离代码和语法。

我知道我需要使用antlr并带上-visitor选项来为我的应用程序生成Visitor类。

我想知道如何在Visitor方法类中访问给定产生式的属性以及如何将词法分析器、解析器和访问者代码片段“粘合”在一起。

2个回答

4

如果你的语法没有动作,而且在WS规则中包含了\r

grammar list;

list
 : BEGL elems? ENDL
 ;

elems
 : elem ( SEP elem )*
 ;

elem
 : NUM
 ;

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

访问者可能看起来像这样:
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());
    }
}

并可以按如下方式进行测试:
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);

将会打印:

sum=6

1

"Making a Visitor"是指创建一个继承YourGrammarBaseVisitor<T>的类, 在你的情况下,应该继承ListBaseVisitor<T>。 这个类不需要有任何方法,但可以重写名为visitX的方法,其中X是规则名称,例如visitElem。泛型Tvisit调用的返回值,但也可以使用Object返回类型来返回任何内容。 如下所示:

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
}

请注意,每个方法都将接收其自己的上下文类型(例如,visitList 方法将接收一个 ListParser.ListRuleContext 等),这些对象包含有关解析规则的信息。例如,在 elem 规则中,我使用了 NUM() 方法,在 elems 规则中,我使用了 elem() 方法。请注意,对于多个规则的 EBNF 符号(rule*rule+),该方法会将其转换为可迭代的集合。
要使用您的新访问者,您只需要像以前一样实例化 Antlr 对象,并使用由解析器生成的树访问语法的第一个规则,就像这样:
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);

请原谅任何Java错误,我不是Java程序员


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接