antlr4私人学习笔记

俗话说“好记性不如烂笔头”,编程的海洋如此的浩大,养成做笔记的习惯是成功的一步!

此笔记主要是antlr4.13版本的笔记,并且笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。


一、基础知识:
    1、antlr 是语言识别的另一个工具(全称为Another Tool for Language Recognition),通过定制语法结构来解析为AST抽象语法树,
       他的编译器通过java编写的,故需要安装jdk1.6以上,下载antlr-complete.jar包。可以通过java、C++和C#的语法描述来构造语言
       识别器、编译器和解析器。目前最新版本是4.0版本,此笔记都基于此版本编写。
       官方网址:http://www.antlr.org
       官方下载jar包地址:http://www.antlr.org/download/antlr-4.0-complete.jar
       github源码:https://github.com/antlr/grammars-v4/
       语法讲解:https://github.com/antlr/antlr4/blob/master/doc/grammars.md
       知乎简明教程:https://zhuanlan.zhihu.com/p/483679676
       java语法解析g4文件:https://github.com/antlr/grammars-v4/tree/master/java/java
       
       
    2、antlr将生成词法分析器(Lexer)、语法分析器(Parser)和树分析器(tree parser)。
    
    
    
    3、antlr在hive、spark等大数据均有使用,hibernate也使用antlr来将hql语句进行转换为sql语句。
    
    
    4、ANTLR是目前非常流行的语言识别工具,使用Java语言编写,基于LL(*)解析方式,使用自上而下的递归下降分析方法。
     通过输入语法描述文件来自动构造自定义语言的词法分析器、语法分析器和树状分析器等各个模块。ANTLR使用上下无关文法描述语言,
     文法定义使用类似EBNF的方式。所有编程语言的语法,都可以用ANTLR来定义
    

    5、专业术语:
        (1)将字符聚集为单词或者符号(词法符号token)的过程称为词法分析(lexcial analysis)或者词法符号化(tokenizing)。
        (2)把输入文本转换为词法符号的程序称为词法分析器(lexer)。词法分析器可以将相关的词法符号归类,例如定义的INT(整数)。
        (3)将词法符号被消费以识别语句结构,即为语法分析过程。语法分析器会建造一种名为语法分析树(parse tree)或者句法树(syntax tree)的数据结构。
        (4)ALL机制:即全语法分析算法,当出现语法错误时,解析器会尝试找到尽可能多的有效解析,而不是在遇到第一个错误时就停止。
        
       
       
       
二、基础使用:
    1、目前主要使用的版本是antlr v4版本。通过编写语法描述g4文件,然后使用antlr的编译器(插件或者jar包)进行编译g4文件,生成解析相关的java类,
       导入到项目中即可使用。
       
       
    
    2、linux环境:
            方式一:可以建立antlr4.sh使用shell脚本来方便执行antlr-4.0-complete.jar,shell内容如下(注意jar包的目录):
                    java -cp "/usr/local/lib/antlr4-complete.jar:$CLASSPATH" org.antlr.v4.Tool $* g4文件
            
            方式二:配置环境变量,定义别名:
                    alias antlr4='java -jar /usr/local/lib/antlr-4.0-complete.jar'  g4文件
                    
       
       windows环境
            方式一:可以antlr4.bat使用批脚本来方便执行,bat内容如下(注意jar包的目录):
                    java -jar  E:\antlr\demo\work_space\antlr-4.0-complete.jar g4文件
            
            方式二:在dos窗口下输入命令:
                    java -cp C:\libraries\antlr-4.0-complete.jar;%CLASSPATH% org.antlr.v4.Tool %* g4文件
        
        
       注意:jar包路径必须得正确,还有定义的类路径CLASSPATH是区分大小写的,必须与配置的环境变量key保持一致。
        
        

        
    3、可以直接安装idea插件,用来进行生成解析语法的代码。插件为:ANTLR v4,本地已下载了2021年版本的插件antlr-intellij-plugin-v4-1.23-2021。
       通过此插件,可以对g4文件快速生成解析语法的代码,通过可以通过使用 ANTLR Preview工具进行检查语法
       (注意需要输入内容后进行解析才会生成Parse tree预览树)。
    
    
    
    4、java项目里运行antlr时,需要引入依赖包,gav如下(注意需要与antlr编译器版本相同,否则无法支持生成出来的java代码):
         <dependency>
             <groupId>org.antlr</groupId>
             <artifactId>antlr4-runtime</artifactId>
             <version>4.13.1</version>
         </dependency>
    
    
    
    5、TokenStreamRewriter能够修改词法符号流,非常实用在源代码插桩或者重构等场合,最后通过TokenStreamRewriter.getText()方法获取重写后的结果。
       附加:TokenStreamRewriter是不会改变和影响原TokenStream的流内容。
    
    
    6、通常情况下,应对避免将语法和应用程序的逻辑代码纠缠在一起,但内嵌动作仍然是有用的,在如下几种情况会考虑内嵌动作:
       简便:使用少量的动作完成功能,编码创建监听器或者访问器。
       带判断的语法分析过程:需要依赖输入流获取的数据才能正常进行语法分析过程的情况。
       
       附加:所谓动作,就是将目标语言编写的代码防止在语法树里一起解析处理。可以是任意合法的语言语句。
    
    
    
    
    
三、antlr词法语法规则:
    1、四种抽象的计算机语言模式:
        序列:是由许多元素组成的一个序列,比如数组初始化句法中的数值。
        选项:是指在多个可选的短语中的选择,比如编程语言中的不同语句。
        单词约束:一个单词的出现需要对应另一个其它短语的出现,比如左括号和右括号。
        嵌套短语:这是一种自相似的语言结构,例如编程语言中的嵌套算术表达式或嵌套语句块。
            
        
    2、ANTLR除能够自动构建语法分析树外,还能生成基于Listener(监听者模式,通过节点监听,触发处理方法)和Visitor(访问者模式,主动遍历)的树遍历器。
      访问者模式遍历语法树是一种更加灵活的方法,可以避免在文法文件中嵌入繁琐的动作,使解析与应用逻辑代码分离。
      访问器和监听器机制的最大区别:监听器的方法会被ANTLR提供的遍历器对象自动调用,而在访问器的方法中必须显示调用vist()方法来访问子节点
      (如果忘记调用访问器的visit方法后果就是对应的子树将不会被访问)。
     
       
       
    3、Listener监听器:ANTLR为每个语法文件生成一个ParseTreeListener的子类,在该类中,语法中的每条规则都有对应的enter方法和exit方法,当访问到某节点时,
      就会调用相应的enter方法,访问完后就调用相应的exit方法。
      监听器需要额外保存自定义的参数进行传递和处理,可以使用Stack堆对象按顺序存储和取出,也可以使用ANTLR框架的ParseTreeProperty标注方式来存参。
      
      
    4、访问者模式(Visitor Pattern)是一种将操作与对象结构分离的软件设计模式,提供作用于某种对象结构上各元素的操作,可以使我们在不改变元素结构
      的前提下,定义作用于元素的新操作。
      访问器方式的参数传递不需要额外来保存传输,但是仅限于所有的值都具是相同类型。
      
      
      
    5、对于备选分支,ANTLR会优先选择靠前的备选分支,如果找到符合的分支则不会执行剩下的备选分支。
    
    
    6、默认情况下ANTLR的运算符是从左向右进行结合的,在某些情况下(例如指数运算符)是需要从右向左结合,可以通过使用assoc选项来指定结合性,例如<assoc=right> expr '^' expr
    
    
    7、antlr词法分析器解决歧义问题的方法是优先使用位置靠前的词法规则。例如'for'这种隐式定义的词法规则,就需要放置在语法规则之后,显示定义的词法规则之前,
       像ID这样显示定义的词法规则必须定义在所有关键字规则之后,防止都被ID词法误匹配了。
       
       另外antlr会自动将词法规则放置在语法规则之后,即使词法规则写在语法规则前面,也依旧会先解析语法规则。
    

    
    8、词法分析器和语法分析器的界限并不是十分的明显,至于词法分析器是否需要细分各组成部分,这得看语法分析器的需求。例如语法分析器需要处理IP地址中的元素,
       那儿词法分析器就应该把ip地址的各组成部分作为独立的词法符号送入语法分析器。
    
    
    9、关于空格换行[ \t\n\r]+ 这些字符是否需要丢弃,看实际的需求情况,如果不需要取原输入内容,可以直接skip丢弃掉,否则需要放到隐藏通道,监听器和遍历器不会对隐藏通道内容遍历和监听,
       但是可以取出完整的输入内容。
       
       
    10、原来antlr的channel通道的标识定义,是使用 @lexer::members 定义常量来实现,此方法在新版的antlr4.6版本开始废弃掉,改为直接使用数字来标识通道,例如:
        ->channel(0) 默认的常规通道,->channel(1) 隐藏通道, ->channel(2) 数字2及以上都是属于自定义通道。
        
        
        
    
    
    附加:语法说明:
        |           符号是备选分支的分隔符
        ()          符号是指将括号内的字符组合成一个子规则
        #           符号是指定义事件
        grammar     是指定义语法
        lexer grammar 是指定义词法语法规则,用于定义语法模块,作为被导入文件
        import      导入词法语法规则,配合lexer grammar使用
        ~('a')      表示匹配除a字符以外的其他字符,对应正则表达式的^[a]
        ->          表示指定通道或者模式。 ->skip 表示跳过(丢弃)匹配的内容, ->channel(1) 表示送入隐藏通道, -> pushMode(1) 表示进入1模式。
        locals      表示在语法中嵌入自定义的动作(代码)
        $           表示取值变量,配合locals结构或者自定义代码中使用,例如$id.text
        ?           表示可选,和正则表达式相同规则
        *           表示出现0次或多次,和正则表达式相同规则
        +           表示至少一次,和正则表达式相同规则
        ..          表示范围,例如('a'..'z' )等同于正则[a-z]
        fragment    表示这一条规则声明为一个词法片段,而不是一个完整的词法符号,它只能被其他词法规则使用,而不能被语法规则引用。
        .*          贪婪匹配,匹配数量最多的字符。
        .*?         非贪婪匹配,匹配数量最少的字符。
        ''          定义常量字符。
        {}          定义内嵌动作(代码),与locals类似。例如java语言的解析增加内嵌打印{System.out.println("var " + $ID.text);}
        @header     在内嵌动作中的代码,指定一段动作代码。
        @members    在内嵌动作中的代码,注入字段或者方法。
        @parser::   在语法分析器上指定内嵌代码,配合header和members使用。例如@parser::header{...}、@parser::members{...}
        @lexer::    在词法分析器上指定内嵌代码,配合header和members使用。例如@parser::header{...}、@parser::members{...}
        {}?         语义判定。在运行的时候求值,如果为false则备选分支就被关闭。注意如果是语法规则判断是放左侧 {java5}? 'enum' name=id '{' id '}' 词法规则判断是放右侧 'enum' {java5}?
        
        
        附加:语法(文法)规则名以小写字母开头,词法规则名以大写字母开头。
    
    
    
    
    
    
    
四、使用笔记:
    1、java代码里使用自定义的Visitor模式的主要代码:
        // 新建一个词法分析器,处理输入的CharStream。数据读取也可以改为用fromStream从流中读取
        CalculatorLexer lexer = new CalculatorLexer(CharStreams.fromString("6/(2+1)+4/2"));
        // 新建一个词法符号的缓冲区,用于存储词法分析器生成的词法符号
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        // 新建一个语法分析器,处理词法符号缓冲区中的内容
        CalculatorParser parser = new CalculatorParser(tokens);
        // 针对expr规则,开发语法分析
        CalculatorParser.ExprContext tree = parser.expr();

        //ArithmeticEvalVisitor是自定义的访问者,继承CalculatorBaseVisitor类
        ArithmeticEvalVisitor eval = new ArithmeticEvalVisitor();
        //获取解析处理后的结果
        Integer result = eval.visit(tree);
        System.out.println(result);
    
    
    
    2、 java代码里使用自定义的Listener模式的主要代码:
        CalculatorLexer lexer = new CalculatorLexer(CharStreams.fromString(expr));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        CalculatorParser parser = new CalculatorParser(tokens);
        ParseTree tree = parser.expr();
        // 新建一个通用的能够触发回调函数的语法分析树遍历器
        ParseTreeWalker walker = new ParseTreeWalker();
        //ArithmeticEvalListener是自定义的监听者,继承CalculatorBaseListener类
        ArithmeticEvalListener listener = new ArithmeticEvalListener();
        // 遍历语法分析过程中生成的语法分析树,触发回调。
        walker.walk(listener, tree);
        Integer result = listener.getResult();
        // 获取解析处理后的结果
        System.out.println(result);
            
            
            
    3、java代码打印解析出来的语法树:  
        HelloLexer lexer = new HelloLexer(CharStreams.fromString("hello sss world"));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        HelloParser parser = new HelloParser(tokens);
        ParseTree tree = parser.s();
        System.out.println(tree.toStringTree(parser));
     
    
    
    4、定制简单的加减乘除法整数计算器的antlr语法:
        grammar Calculator;

        /**
            起始规则 语法分析器起点,定义expr语法。
            注意加减法和乘除法的顺序,会影响到解析顺序和运行结果。
            并且需要注意#并不是注释,而是给这个终端节点命名。
            op是表示对于括号内的多种运算符合都支持,并且统一用op来定义,可以在代码中获取op代表的具体运算符类型。
        */
        expr:    expr op=('*'|'/') expr  # MulDiv
            |    expr op=('+'|'-') expr  # AddSub
            |    number                  # num
            |    '(' expr ')'            # parens
            ;

        number     : INT | decimal ; //匹配整数和小数

        decimal    : INT DOT INT;

        INT     :[0-9]+;
        DOT     :'.';

        MUL     : '*' ;
        DIV     : '/' ;
        ADD     : '+' ;
        SUB     : '-' ;
        WS      : [ \t\r\n]+ -> skip ;    //跳过空格、制表符、回车符和换行符
        
     
    
    5、经过并发测试,确认以下监听器的代码是线程安全的,可以作为单例
        JSON2XMLLexer lexer = new JSON2XMLLexer(CharStreams.fromString(input));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JSON2XMLParser parser = new JSON2XMLParser(tokens);
        JSON2XMLParser.JsonContext tree = parser.json();
        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(《自定义的监听器》, tree);
        
        注意:自定义的监听器是否线程安全,是需要根据具体的实现方式来看,如果存在共享变量则不是线程安全。
     
     
     
    6、默认情况下,当解析规则出现问题报错时,antlr会将错误打印到控制台。这是因为默认加入了ConsoleErrorListener监听器。可以自定义监听器,继承BaseErrorListener
       类覆盖syntaxError(),然后在语法分析器上增加错误监听器,参考代码如下:
        HelloParser parser = new HelloParser(tokens);
        //追加自定义的错误监听
        parser.addErrorListener(new HelloErrorListener());
    
    
    
    7、默认情况下antlr会进行错误恢复处理,最大限度的让语法解析正常执行,使用的是DefaultErrorStrategy策略。可以自定义异常策略,继续DefaultErrorStrategy类进行覆盖
      ,然后再语法分析器上设置错误策略,参考代码如下:
        HelloParser parser = new HelloParser(tokens);
        //设置默认的异常策略
        parser.setErrorHandler(new HelloErrorStrategy());
    
    
    
    8、为方便调试和查看匹配情况,除了可以使用工具进行生成语法树来看语法匹配情况,还可以打印tokens的字符来看匹配的情况,参考代码如下:
        ShiftVarCommentsLexer lexer = new ShiftVarCommentsLexer(CharStreams.fromString("//测试 \n int a= 8;"));
        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
        System.out.println(tokenStream.getTokens());
        
        输出结果参考:
            [[@0,0:5='//测试 \n',<14>,channel=2,1:0], [@1,6:6=' ',<13>,channel=1,2:0], [@2,7:9='int',<4>,2:1], [@3,10:10=' ',<13>,channel=1,2:4], [@4,11:11='a',<11>,2:5], [@5,12:12='=',<1>,2:6], [@6,13:13=' ',<13>,channel=1,2:7], [@7,14:14='8',<12>,2:8], [@8,15:15=';',<3>,2:9]
   
        结果解析:
            [[@0,0:5='//测试 \n',<14>,channel=2,1:0]  具体说明=》 [[@词法匹配的位置,匹配文本在整个内容的开始位置:匹配文本在整个内容的结束位置='匹配的文本内容',<词法符号类型>,匹配通道=通道标识,匹配文本所在行数:匹配文本开始位置]
            
            注意:序号是从0开始算起;匹配通道如果是默认通道0,则不会打印channel通道字样;匹配文本开始位置是根据每行单独计算,每次换行后都是从0开始算起;匹配文本在整个内容的位置是不受换行影响,一直往上累加。
   
        
        附加:
            如果想看简版的匹配词法和语法情况,可以输出树字符串:         
                ShiftVarCommentsParser parser = new ShiftVarCommentsParser(tokenStream);
                ShiftVarCommentsParser.StatContext tree = parser.stat();
                System.out.println(tree.toStringTree(parser));
            
                输出结果为:(stat (var (type int) a = (val 8) ;))
        
        
        
        
        
        
    

相关推荐

  1. antlr4私人学习笔记

    2024-07-18 23:36:03       19 阅读
  2. 基于Antlr4实现自定义语法规则

    2024-07-18 23:36:03       53 阅读
  3. [NCNN学习笔记]-4

    2024-07-18 23:36:03       39 阅读
  4. MySQL学习笔记4 DQL

    2024-07-18 23:36:03       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 23:36:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 23:36:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 23:36:03       58 阅读
  4. Python语言-面向对象

    2024-07-18 23:36:03       69 阅读

热门阅读

  1. python的with语句

    2024-07-18 23:36:03       23 阅读
  2. Context使用

    2024-07-18 23:36:03       21 阅读
  3. 2024年5月份架构师考试案例真题完整版

    2024-07-18 23:36:03       21 阅读
  4. C语言 default 踩坑

    2024-07-18 23:36:03       22 阅读
  5. 使用Python批量压缩图片

    2024-07-18 23:36:03       24 阅读
  6. 快速log10函数 fast_log10

    2024-07-18 23:36:03       21 阅读
  7. Linux 日志管理与系统调优补充

    2024-07-18 23:36:03       24 阅读
  8. Qt Creator 项目Console 项目踩坑日记

    2024-07-18 23:36:03       22 阅读
  9. 信息系统项目管理师(高项)—学习笔记三

    2024-07-18 23:36:03       20 阅读
  10. linux修改文件夹下所有文件的权限(常用)

    2024-07-18 23:36:03       19 阅读
  11. c++类的继承详解

    2024-07-18 23:36:03       20 阅读
  12. 目标检测算法

    2024-07-18 23:36:03       23 阅读