ANTLR4-实践

最近在研究Yang模型转换,自然而然就看到了ANTLR4与Xtend的相关技术。简单学习下基本用法,以此作为记录。

对于ANTLR4是一款优秀的开源语法解析器。不仅如此,可以为开发者自己定义的语法提供识别解析计算的功能。从一个简单的小例子(仅作测试)开始:


如定义一个语法:

数字 操作A操作B操作C操作D 数据

具象一点

10 +* 2

计算结果是  (10+2)*2=24

 <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4-runtime</artifactId>
            <version>4.5.3</version>
        </dependency>


利用ANTLR开发的一般步骤:

1.定义文法

grammar Jia;

expres : expres (PLUS|SUB|MUL|DIV)* NUMBER| NUMBER;
NUMBER : [0]|[1-9]+[0-9]*;
PLUS : 'p';
SUB : 's' ;
MUL : 'm' ;
DIV : 'd' ;
WS : [ \t\n\r]+ -> skip ;

具体规则含义参考百度,

() : 产生式组合
?  : 产生式出现0或1次
*  : 0或多次
+  : 1或多次
.   : 任意一个字符
~  : 不出现后面的字符
..  : 字符范围

从上文的语法定义中,已经能够识别出如1 p 2 或者 1 md 2 之类的表达式

intelligent 的ANTLR插件提供了对语言的语法进行校验功能,所见即所得,在文法复杂时,则非常实用。

合法输入:



非法输入:



2.根据g4文件生成相应的antlr辅助代码

右击文法文件,输入生成文件所在目录(需要确保该目录在classpath下)


注意ANTLR生成语法树有两种遍历方法,分别为listener与visitor,如上默认选择的是listener.

右击生成

ps.

listener与vistor两个语法树遍历机制的区别:

listener:在遍历语法树时,自动回调Listener中回调方法。实现简单,全自动,只需要实现listener中的各个接口方法。即可完成语义表达。并采用深度优先遍历


expres-->expres->expres->10->p->s->m->29->p->2


vistior:则提供了可以控制遍历过程的一种方式,通过显示调用visit方法,可以访问子树节点。


3.自定义listener执行语义分析及表达式计算

package com.sunquan.demo.antlr.listener;

import com.sunquan.jia.antlr.gen.JiaBaseListener;
import com.sunquan.jia.antlr.gen.JiaParser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Created by on 2017/5/10.
 */
public class JiaListener extends JiaBaseListener {
    private Stack<Integer> numStack = new Stack<>();
    private Queue<String> symbolQueue = new ConcurrentLinkedQueue<>();

    @Override
    public void enterExpres(JiaParser.ExpresContext ctx) {
        super.enterExpres(ctx);
    }

    @Override
    public void enterEveryRule(ParserRuleContext ctx) {
        super.enterEveryRule(ctx);
    }

    @Override
    public void visitTerminal(TerminalNode node) {
        super.visitTerminal(node);
        String text = node.getText();
        if (isCalSymbol(text))
            symbolQueue.add(text);
        else
            cal(text);
    }

    private boolean isCalSymbol(String text) {
        return text.equals("p") || text.equals("s") || text.equals("m") || text.equals("d");
    }

    private void cal(String text) {
        if (text != null && !text.equals(" ")) {
            if (symbolQueue.isEmpty()) {
                numStack.push(Integer.parseInt(text));
            } else {
                while (!symbolQueue.isEmpty()) {
                    String symbol = symbolQueue.poll();
                    switch (symbol) {
                        case "p":
                            numStack.push(numStack.pop() + Integer.parseInt(text));
                            break;
                        case "s":
                            numStack.push(numStack.pop() - Integer.parseInt(text));
                            break;
                        case "m":
                            numStack.push(numStack.pop() * Integer.parseInt(text));
                            break;
                        case "l":
                            numStack.push(numStack.pop() / Integer.parseInt(text));
                            break;
                        default:
                            throw new RuntimeException("not support symbol");
                    }
                }
            }
        }
    }

    @Override
    public void visitErrorNode(ErrorNode node) {
        super.visitErrorNode(node);
    }

    public int result() {
        return numStack.pop();
    }
}

4测试结果

    @Test
    public void test3() {
//        PLUS : 'p';
//        SUB : 's' ;
//        MUL : 'm' ;
//        DIV : 'd' ;
        String hello = "1 psw 2";
        ANTLRInputStream inputStream = new ANTLRInputStream(hello);
        JiaLexer lexer = new JiaLexer(inputStream);
        CommonTokenStream tokenStream = new CommonTokenStream(lexer);

        JiaParser parser = new JiaParser(tokenStream);
        ParseTreeWalker walker = new ParseTreeWalker();
        JiaListener jiaListener = new JiaListener();
        walker.walk(jiaListener, parser.expres());
        System.out.println(jiaListener.result());
    }


ps. 为方便visitor与listener的比较,针对visitor的遍历计算如下:


package com.sunquan.demo.antlr.listener;

import com.sunquan.jiav.antlr.gen.JiavBaseVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Created by on 2017/5/10.
 */
public class JiavVisitor extends JiavBaseVisitor<Integer> {
    private Stack<Integer> numStack = new Stack<>();
    private Queue<String> symbolQueue = new ConcurrentLinkedQueue<>();

    @Override
    public Integer visitTerminal(TerminalNode node) {
        String text = node.getText();
        if (isCalSymbol(text))
            symbolQueue.add(text);
        else
            cal(text);
        return numStack.peek();
    }

    private boolean isCalSymbol(String text) {
        return text.equals("p") || text.equals("s") || text.equals("m") || text.equals("d");
    }

    private void cal(String text) {
        if (text != null && !text.equals(" ")) {
            if (symbolQueue.isEmpty()) {
                numStack.push(Integer.parseInt(text));
            } else {
                while (!symbolQueue.isEmpty()) {
                    String symbol = symbolQueue.poll();
                    switch (symbol) {
                        case "p":
                            numStack.push(numStack.pop() + Integer.parseInt(text));
                            break;
                        case "s":
                            numStack.push(numStack.pop() - Integer.parseInt(text));
                            break;
                        case "m":
                            numStack.push(numStack.pop() * Integer.parseInt(text));
                            break;
                        case "l":
                            numStack.push(numStack.pop() / Integer.parseInt(text));
                            break;
                        default:
                            throw new RuntimeException("not support symbol");
                    }
                }
            }
        }
    }
}


    @Test
    public void test4() {
//        PLUS : 'p';
//        SUB : 's' ;
//        MUL : 'm' ;
//        DIV : 'd' ;
        String hello = "1 pm 3";
        ANTLRInputStream inputStream = new ANTLRInputStream(hello);
        JiavLexer lexer = new JiavLexer(inputStream);
        CommonTokenStream tokenStream = new CommonTokenStream(lexer);

        JiavParser parser = new JiavParser(tokenStream);
        JiavVisitor visitor = new JiavVisitor();
        Integer result = visitor.visit(parser.expres());
        System.out.println(result);
    }


万事开头难,入了门就好多了。









展开阅读全文

没有更多推荐了,返回首页