【问题标题】:What does "fragment" mean in ANTLR?ANTLR 中的“片段”是什么意思?
【发布时间】:2011-06-27 00:03:17
【问题描述】:

fragment在ANTLR中是什么意思?

这两条规则我都见过:

fragment DIGIT : '0'..'9';

DIGIT : '0'..'9';

有什么区别?

【问题讨论】:

    标签: antlr


    【解决方案1】:

    片段有点类似于内联函数:它使语法更具可读性和更易于维护。

    片段永远不会被算作一个标记,它只是用来简化语法。

    考虑:

    NUMBER: DIGITS | OCTAL_DIGITS | HEX_DIGITS;
    fragment DIGITS: '1'..'9' '0'..'9'*;
    fragment OCTAL_DIGITS: '0' '0'..'7'+;
    fragment HEX_DIGITS: '0x' ('0'..'9' | 'a'..'f' | 'A'..'F')+;
    

    在本例中,匹配一个 NUMBER 将始终向词法分析器返回一个 NUMBER,无论它匹配的是“1234”、“0xab12”还是“0777”。

    See item 3

    【讨论】:

    • 您对fragment 在 ANTLR 中的含义是正确的。但是您给出的示例很糟糕:您不希望词法分析器生成可以是十六进制、十进制或八进制数的NUMBER 标记。这意味着您需要检查生产中的 NUMBER 令牌(解析器规则)。您最好让词法分析器产生INTOCTHEX 标记并创建生产规则:number : INT | OCT | HEX;。在这样的示例中,DIGIT 可能是一个片段,将被标记 INTHEX 使用。
    • 请注意,“可怜”可能听起来有点刺耳,但我找不到更好的词来形容它……抱歉! :)
    • 你听起来并不刺耳..你是对的和直的!
    • 重要的是,片段仅用于其他词法分析器规则以定义其他词法分析器标记。片段不适用于语法(解析器)规则。
    • @BartKiers:您能否创建一个新答案,包括您更好的答案。
    【解决方案2】:

    根据 Definitive Antlr4 参考书:

    以fragment为前缀的规则只能从其他词法规则中调用;它们本身不是代币。

    实际上它们会提高语法的可读性。

    看看这个例子:

    STRING : '"' (ESC | ~["\\])* '"' ;
    fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
    fragment UNICODE : 'u' HEX HEX HEX HEX ;
    fragment HEX : [0-9a-fA-F] ;
    

    STRING 是一个使用 ESC 等片段规则的词法分析器。Unicode 用于 Esc 规则,Hex 用于 Unicode 片段规则。 ESC 和 UNICODE 和 HEX 规则不能显式使用。

    【讨论】:

      【解决方案3】:

      权威的ANTLR 4参考(第106页):

      以片段为前缀的规则可以 只能从其他词法分析器规则中调用;它们本身不是代币。


      抽象概念:

      案例 1:(如果我需要 RULE1、RULE2、RULE3 实体或组信息)

      rule0 : RULE1 | RULE2 | RULE3 ;
      RULE1 : [A-C]+ ;
      RULE2 : [DEF]+ ;
      RULE3 : ('G'|'H'|'I')+ ;
      


      案例2:(如果我不关心RULE1、RULE2、RULE3,我只关注RULE0)

      RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;
      // RULE0 is a terminal node. 
      // You can't name it 'rule0', or you will get syntax errors:
      // 'A-C' came as a complete surprise to me while matching alternative
      // 'DEF' came as a complete surprise to me while matching alternative
      


      Case3:(相当于Case2,比Case2更易读)

      RULE0 : RULE1 | RULE2 | RULE3 ;
      fragment RULE1 : [A-C]+ ;
      fragment RULE2 : [DEF]+ ;
      fragment RULE3 : ('G'|'H'|'I')+ ;
      // You can't name it 'rule0', or you will get warnings:
      // warning(125): implicit definition of token RULE1 in parser
      // warning(125): implicit definition of token RULE2 in parser
      // warning(125): implicit definition of token RULE3 in parser
      // and failed to capture rule0 content (?)
      


      Case1 和 Case2/3 的区别?

      1. 词法分析器规则是等效的
      2. Case1 中的每个 RULE1/2/3 都是一个捕获组,类似于 Regex:(X)
      3. Case3 中的每个 RULE1/2/3 都是非捕获组,类似于 Regex:(?:X)



      让我们看一个具体的例子。

      目标:识别[ABC]+[DEF]+[GHI]+令牌

      input.txt

      ABBCCCDDDDEEEEE ABCDE
      FFGGHHIIJJKK FGHIJK
      ABCDEFGHIJKL
      


      Main.py

      import sys
      from antlr4 import *
      from AlphabetLexer import AlphabetLexer
      from AlphabetParser import AlphabetParser
      from AlphabetListener import AlphabetListener
      
      class MyListener(AlphabetListener):
          # Exit a parse tree produced by AlphabetParser#content.
          def exitContent(self, ctx:AlphabetParser.ContentContext):
              pass
      
          # (For Case1 Only) enable it when testing Case1
          # Exit a parse tree produced by AlphabetParser#rule0.
          def exitRule0(self, ctx:AlphabetParser.Rule0Context):
              print(ctx.getText())
      # end-of-class
      
      def main():
          file_name = sys.argv[1]
          input = FileStream(file_name)
          lexer = AlphabetLexer(input)
          stream = CommonTokenStream(lexer)
          parser = AlphabetParser(stream)
          tree = parser.content()
          print(tree.toStringTree(recog=parser))
      
          listener = MyListener()
          walker = ParseTreeWalker()
          walker.walk(listener, tree)
      # end-of-def
      
      main()
      


      案例1及结果:

      Alphabet.g4 (Case1)

      grammar Alphabet;
      
      content : (rule0|ANYCHAR)* EOF;
      
      rule0 : RULE1 | RULE2 | RULE3 ;
      RULE1 : [A-C]+ ;
      RULE2 : [DEF]+ ;
      RULE3 : ('G'|'H'|'I')+ ;
      
      ANYCHAR : . -> skip;
      

      结果:

      # Input data (for reference)
      # ABBCCCDDDDEEEEE ABCDE
      # FFGGHHIIJJKK FGHIJK
      # ABCDEFGHIJKL
      
      $ python3 Main.py input.txt 
      (content (rule0 ABBCCC) (rule0 DDDDEEEEE) (rule0 ABC) (rule0 DE) (rule0 FF) (rule0 GGHHII) (rule0 F) (rule0 GHI) (rule0 ABC) (rule0 DEF) (rule0 GHI) <EOF>)
      ABBCCC
      DDDDEEEEE
      ABC
      DE
      FF
      GGHHII
      F
      GHI
      ABC
      DEF
      GHI
      


      案例 2/3 和结果:

      Alphabet.g4 (Case2)

      grammar Alphabet;
      
      content : (RULE0|ANYCHAR)* EOF;
      
      RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;
      
      ANYCHAR : . -> skip;
      

      Alphabet.g4 (Case3)

      grammar Alphabet;
      
      content : (RULE0|ANYCHAR)* EOF;
      
      RULE0 : RULE1 | RULE2 | RULE3 ;
      fragment RULE1 : [A-C]+ ;
      fragment RULE2 : [DEF]+ ;
      fragment RULE3 : ('G'|'H'|'I')+ ;
      
      ANYCHAR : . -> skip;
      

      结果:

      # Input data (for reference)
      # ABBCCCDDDDEEEEE ABCDE
      # FFGGHHIIJJKK FGHIJK
      # ABCDEFGHIJKL
      
      $ python3 Main.py input.txt 
      (content ABBCCC DDDDEEEEE ABC DE FF GGHHII F GHI ABC DEF GHI <EOF>)
      

      您是否看到“捕获组”“非捕获组”部分?




      让我们看看具体的例子2。

      目标:识别八进制/十进制/十六进制数

      input.txt

      0
      123
       1~9999
       001~077
      0xFF, 0x01, 0xabc123
      


      Number.g4

      grammar Number;
      
      content
          : (number|ANY_CHAR)* EOF
          ;
      
      number
          : DECIMAL_NUMBER
          | OCTAL_NUMBER
          | HEXADECIMAL_NUMBER
          ;
      
      DECIMAL_NUMBER
          : [1-9][0-9]*
          | '0'
          ;
      
      OCTAL_NUMBER
          : '0' '0'..'9'+
          ;
      
      HEXADECIMAL_NUMBER
          : '0x'[0-9A-Fa-f]+
          ;
      
      ANY_CHAR
          : .
          ;
      


      Main.py

      import sys
      from antlr4 import *
      from NumberLexer import NumberLexer
      from NumberParser import NumberParser
      from NumberListener import NumberListener
      
      class Listener(NumberListener):
          # Exit a parse tree produced by NumberParser#Number.
          def exitNumber(self, ctx:NumberParser.NumberContext):
              print('%8s, dec: %-8s, oct: %-8s, hex: %-8s' % (ctx.getText(),
                  ctx.DECIMAL_NUMBER(), ctx.OCTAL_NUMBER(), ctx.HEXADECIMAL_NUMBER()))
          # end-of-def
      # end-of-class
      
      def main():
          input = FileStream(sys.argv[1])
          lexer = NumberLexer(input)
          stream = CommonTokenStream(lexer)
          parser = NumberParser(stream)
          tree = parser.content()
          print(tree.toStringTree(recog=parser))
      
          listener = Listener()
          walker = ParseTreeWalker()
          walker.walk(listener, tree)
      # end-of-def
      
      main()
      


      结果:

      # Input data (for reference)
      # 0
      # 123
      #  1~9999
      #  001~077
      # 0xFF, 0x01, 0xabc123
      
      $ python3 Main.py input.txt 
      (content (number 0) \n (number 123) \n   (number 1) ~ (number 9999) \n   (number 001) ~ (number 077) \n (number 0xFF) ,   (number 0x01) ,   (number 0xabc123) \n <EOF>)
             0, dec: 0       , oct: None    , hex: None    
           123, dec: 123     , oct: None    , hex: None    
             1, dec: 1       , oct: None    , hex: None    
          9999, dec: 9999    , oct: None    , hex: None    
           001, dec: None    , oct: 001     , hex: None    
           077, dec: None    , oct: 077     , hex: None    
          0xFF, dec: None    , oct: None    , hex: 0xFF    
          0x01, dec: None    , oct: None    , hex: 0x01    
      0xabc123, dec: None    , oct: None    , hex: 0xabc123
      

      如果将修饰符“片段”添加到DECIMAL_NUMBEROCTAL_NUMBERHEXADECIMAL_NUMBER,您将无法捕获数字实体(因为它们不再是令牌)。结果将是:

      $ python3 Main.py input.txt 
      (content 0 \n 1 2 3 \n   1 ~ 9 9 9 9 \n   0 0 1 ~ 0 7 7 \n 0 x F F ,   0 x 0 1 ,   0 x a b c 1 2 3 \n <EOF>)
      

      【讨论】:

        【解决方案4】:

        这个blog post 有一个非常明显的例子,fragment 有很大的不同:

        grammar number;  
        
        number: INT;  
        DIGIT : '0'..'9';  
        INT   :  DIGIT+;
        

        语法将识别“42”但不能识别“7”。您可以通过将数字设为片段(或在 INT 后移动 DIGIT)来修复它。

        【讨论】:

        • 这里的问题不是没有关键字fragment,而是词法规则的顺序。
        • 我用了“修复”这个词,但重点不是解决问题。我在这里添加了这个示例,因为对我来说,这是使用关键字片段时实际更改的最有用和最简单的示例。
        • 我只是认为将DIGIT 声明为INT 的片段可以解决问题,因为片段没有定义标记,因此使INT 成为第一个词法规则。我同意你的观点,这是一个有意义的例子,但 (imo) 仅适用于已经知道 fragment 关键字含义的人。对于第一次尝试弄清楚片段的正确用法的人来说,我发现它有些误导。
        • 所以当我学习这个的时候,我看到了很多例子,比如上面的例子,但我不太明白为什么需要一个额外的关键字。我不明白这在实践中不是“本身的代币”意味着什么。现在,我实际上不确定什么是原始问题的好答案。我会在上面添加一条评论,为什么我对接受的答案不满意。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-21
        相关资源
        最近更新 更多