zoukankan      html  css  js  c++  java
  • Antlr4.7学习笔记——小型计算器实现

    如何安装

    由于是在MAC OS 下面,所以跟着官网的教程,直接copy5行代码搞定

    $ cd /usr/local/lib
    $ sudo curl -O http://www.antlr.org/download/antlr-4.7-complete.jar
    $ export CLASSPATH=".:/usr/local/lib/antlr-4.7-complete.jar:$CLASSPATH"
    $ alias antlr4='java -jar /usr/local/lib/antlr-4.7-complete.jar'
    $ alias grun='java org.antlr.v4.gui.TestRig'
    

    但是经历多了,就会发现

    vi ~/.bash_profile
    

    把与环境相关的内容都copy进来,这样的话重启计算机后仍能生效

    [esc]
    :wq
    

    保存退出
    source ~/.bash_profile
    更新环境变量。

    好了,现在就可以进行初步的操作了。

    grun

    这个命令的基本格式为
    grun xxx.g4 __garmmar_begin [参数] [资源文件]+

    其中xxx.g4为语法文件

    __garmmar_begin 为语法开始内容

    首先列出一些比较重要的参数:

    • -tokens 打印出记号流
    • -tree 以LISP风格的文本形式打印出语法分析树
    • -gui 在对话框中可视化地显示出语法分析树。
    • -ps file.ps 在PostScript中生成一个可视化的语法分析树表示,并把它存储在file.ps文件中
    • -encoding _name 指定输入文件编码
    • -trace 在进入/退出规则前打印规则名称和当前记号
    • -diagnostics分析时打开诊断信息。此生成消息仅用于异常情况,如二义性
    • -SLL使用更快但稍弱的分析策略

    -tokens

    -tree

    -gui

    -ps

    这个功能就我发现双击这个ps文件后会出一个pdf文件。

    其他参数由于暂时涉及不到,就暂时没有尝试。

    二义性

    在处理二义性方面的问题时。ANTLR通过选择涉及决定的第一个选项来解决二义性。

    并且在面对下面的问题时,会“智能地选择合理结果”

    BEGIN : 'begin';
    ID : [a-zA-Z]+;
    

    在遇到begin时,会用第一个规则进行匹配,如果遇到了类似beging、abegin这样的都会用第二个规则进行匹配。

    Visitor和Listener

    ANTLR在它的运行库中为两种树遍历机制提供支持。默认下ANTLR生成语法分析树和Listener接口,并在其中定义了回调方法,用于响应被内建的树遍历器的触发。

    在Listener和Visitor机制之间最大的不同是:Listener方法被ANTLR提供的遍历器对象调用; 而Visitor方法必须显式的调用visit方法遍历它们的子节点,在一个节点的子节点上如果忘记调 用visit方法就意味着那些子树没有得到访问。

    在这次学习中,是用Visitor实现了一个计算器。首先上计算器的语法:

    grammar Calc;
    
    prog : stat+;
    
    stat : expr             # printExpr
         | ID '=' expr      # assign
         | 'print(' ID ')'  # print
         ;
    
    expr : <assoc=right> expr '^' expr # power
         | expr op=(MUL|DIV) expr   # MulDiv
         | expr op=(ADD|SUB) expr   # AddSub
         | sign=(ADD|SUB)?NUMBER       # number
         | ID                       # id
         | '(' expr ')'             # parens
         ;
    
    
    ID   : [a-zA-Z]+;
    NUMBER  : [0-9]+('.'([0-9]+)?)?
            | [0-9]+;
    COMMENT : '/*' .*? '*/' -> skip;
    LINE_COMMENT : '//' .*? '
    '? '
    ' -> skip;
    WS   : [ 	
    ]+ -> skip;
    MUL  : '*';
    DIV  : '/';
    ADD  : '+';
    SUB  : '-';
    
    

    我觉得在这个文法中有一些细节值得强调,一个是运算符的优先级,第二个是 #号后面的东西有什么用,最后就是<assoc=right>这个东西。

    运算符优先级类如加减乘除这些基本法则在Antlr中已经自动帮你处理,就参考我上面写的expr,有时候并不意味着你这样写

    ....
    |	expr op=(MUL|DIV) expr
    
    |	expr op=(ADD|SUB) expr
    ....
    

    就可以让乘除先于加减,它与怎样排列无关。

    但是,Antlr会把我们的目标脚本,解析生一棵抽象语法树,越是靠近叶子节点的地方,结合优先级越高,越是靠近根节点的地方,结合优先级越低。

    在就上面的expr来谈,

    ...
    | ID
    | '(' expr ')'
    ...
    

    他们的优先级要高于加减乘除运算

    #号有什么用呢?

    在Visitor模式中,它会给你生成一些visit方法,方便你的编程。

    <assoc=right>有什么用呢?

    在默认情况下,Antlr是默认从左向右结合运算符,然而像指数群这样的运算符则是要从右向左,因此我们必须使用assoc手动指定运算符,这样就能把2^3^4解释成2^(3^4)。

    下面上一下自己写的EvalVisitor类。

    
    import java.text.DecimalFormat;
    import java.util.HashMap;
    import java.util.Map;
    
    public class EvalVisitor extends CalcBaseVisitor<Double> {
        Map<String, Double> memory = new HashMap<String, Double>();
    
        //id = expr
        @Override
        public Double visitAssign(CalcParser.AssignContext ctx){
            String id = ctx.ID().getText();
            Double value = visit(ctx.expr());
            memory.put(id, value);
            return value;
        }
    
        // expr
        @Override
        public Double visitPrintExpr(CalcParser.PrintExprContext ctx) {
            Double value = visit(ctx.expr());
            //保留两位有数字的方法
            DecimalFormat df = new DecimalFormat("#.##");
            String s_value = df.format(value);
            System.out.println(s_value);
            return 0.0;
        }
    
        //print
        @Override
        public Double visitPrint(CalcParser.PrintContext ctx){
            String id = ctx.ID().getText();
            Double value=0.0;
            if(memory.containsKey(id)) value = memory.get(id);
            DecimalFormat df = new DecimalFormat("#.##");
            String s_value = df.format(value);
            System.out.println(s_value);
            return value;
    
        }
    
        //Number
        @Override
        public Double  visitNumber(CalcParser.NumberContext ctx){
            int size = ctx.getChildCount();
            if(size == 2){
                if(ctx.sign.getType() == CalcParser.SUB){
                    return -1 *  Double.valueOf(ctx.getChild(1).getText());
                }else{
                    return Double.valueOf(ctx.getChild(1).getText());
                }
            }else{
                return Double.valueOf(ctx.getChild(0).getText());
            }
        }
    
        //ID
        @Override
        public Double visitId(CalcParser.IdContext ctx){
            String id = ctx.ID().getText();
            if(memory.containsKey(id)) return memory.get(id);
            return 0.0;
        }
    
        //expr op=('*'|'/') expr
        @Override
        public Double visitMulDiv(CalcParser.MulDivContext ctx)  {
            Double left = visit(ctx.expr(0));
            Double right = visit(ctx.expr(1));
    
            if(ctx.op.getType() == CalcParser.MUL){
                return left * right;
            }else{
                if(right == 0 || right == 0.0){
                    System.out.println("Divisor can not be zero");
                    return 0.0;
                }else{
                    return left / right;
                }
            }
        }
    
        //expr op=('+'|'-') expr
        @Override
        public Double visitAddSub(CalcParser.AddSubContext ctx){
            Double left = visit(ctx.expr(0));
            Double right = visit(ctx.expr(1));
            if(ctx.op.getType() == CalcParser.ADD)
                return left + right;
            return left - right;
        }
    
        // '(' expr ')'
        @Override
        public Double visitParens(CalcParser.ParensContext ctx){
            return visit(ctx.expr());
        }
    
        // '^'
        @Override
        public Double visitPower(CalcParser.PowerContext ctx){
            Double base = visit(ctx.expr(0));
            Double exponet = visit(ctx.expr(1));
            return Math.pow(base, exponet);
        }
    }
    
    

    还有Calc.java类,为开始类

    
    import org.antlr.v4.runtime.CharStream;
    import org.antlr.v4.runtime.CharStreams;
    import org.antlr.v4.runtime.CommonTokenStream;
    import org.antlr.v4.runtime.tree.ParseTree;
    
    import java.io.InputStream;
    
    
    public class Calc {
        public static void main(String[] args) throws Exception {
            CharStream input;
            if(args.length == 1) {
                String fileName = String.valueOf(args[0]);
                input = CharStreams.fromFileName(fileName);
            }else if(args.length > 1 || args.length < 0){
                throw  new Exception("the number of arguments is false, Please only give the source file or nothing and then you input your text");
            }else {
                InputStream is = System.in;
                input = CharStreams.fromStream(is);
            }
            CalcLexer lexer = new CalcLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            CalcParser parser = new CalcParser(tokens);
            ParseTree tree = parser.prog();
            EvalVisitor eval = new EvalVisitor();
            eval.visit(tree);
            System.out.println(tree.toStringTree(parser));
        }
    }
    
    

    最后通过下面的命令便可以运行

    antlr4 -no-listener -visitor Calc.g4
    javac *.java
    java Calc 或 java Calc calc.txt
    grun Calc prog -gui calc.txt #可看生成树
    

    具体代码及相关学习书籍在这摸我

    地址: https://github.com/wojiaxiaoyueyue/Antlr4.7-Calculator

  • 相关阅读:
    Volatile的作用---http://www.cnblogs.com/xing901022/p/7840684.html
    基于JDBC持久化的事务管理-https://www.cnblogs.com/xing901022/p/4272420.html
    Class的isAssignableFrom方法--其他博主的博客
    深入并发二 ThreadLocal源码与内存泄漏相关分析 https://www.cnblogs.com/qmlingxin/p/9412061.html
    Beta阶段项目总结
    Alpha阶段项目总结
    Alpha版总结会议——班级派
    第二冲刺阶段——站立会议第十四天6月7日
    第二冲刺阶段——站立会议第十三天6月6日
    第二冲刺阶段——站立会议第十二天6月5日
  • 原文地址:https://www.cnblogs.com/chunzhulovefeiyue/p/7550294.html
Copyright © 2011-2022 走看看