ANTLR中的“碎片”是什么意思?


Answers:


110

片段在某种程度上类似于内联函数:它使语法更具可读性且易于维护。

片段永远不会被视为标记,它仅用于简化语法。

考虑:

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”。

见项目3


43
您对fragmentANTLR中的含义是正确的。但是您给出的示例很糟糕:您不希望词法分析器生成NUMBER可以为十六进制,十进制或八进制数字的令牌。这意味着您需要检查NUMBER生产中的令牌(解析器规则)。您最好让词法分析器产生INTOCTHEX标记并创建一条产生规则:number : INT | OCT | HEX;。在这样的示例中,a DIGIT可以是令牌INT和所使用的片段HEX
巴特·基尔斯

10
请注意,“可怜”听起来可能有些刺耳,但我找不到更好的词……对不起!:)
Bart Kiers

1
您听起来并不刺耳..您是正确而直率的!
asyncwait 2014年

2
重要的是,片段只能在其他词法分析器规则中使用以定义其他词法分析器标记。片段不打算在语法(解析器)规则中使用。
2014年

1
@BartKiers:您能否创建一个新的答案,包括更好的答案。
David Newcomb

18

根据权威的Antlr4参考书:

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

实际上,它们将提高您语法的可读性。

看这个例子:

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

STRING是使用像ESC这样的片段规则的词法分析器。Esc规则中使用Unicode,Unicode片段规则中使用Hex。ESC,UNICODE和HEX规则无法明确使用。


10

权威的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


主程序

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(案例1)

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


Case2 / 3和结果:

Alphabet.g4(案例2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;

ANYCHAR : . -> skip;

Alphabet.g4(案例3)

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>)

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




让我们看具体的例子。

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

input.txt

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


g.4

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
    : .
    ;


主程序

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>)

8

这篇博客文章有一个非常明显的例子,fragment可以带来很大的不同:

grammar number;  

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

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


1
这里的问题不是缺少关键字fragment,而是词法分析器规则的顺序。
BlackBrain

我使用了“修复”一词,但重点并不是要解决问题。我在此处添加此示例的原因是,对我而言,这是使用关键字片段时实际变化的最有用和最简单的示例。
Vesal '18

2
我只是在争辩说,声明DIGIT为of的片段INT可以解决问题,因为片段没有定义标记,因此成为INT第一个词汇规则。我同意您的看法,这是一个有意义的示例,但(imo)仅适用于已经知道fragment关键字含义的人。我发现这对于第一次尝试正确使用碎片的人有些误导。
BlackBrain

1
因此,当我学习此内容时,我看到了许多诸如上述示例的示例,但是我并没有完全理解为什么为此需要一个额外的关键字。在实践中,我不明白这不是“凭自己的权利”。现在,我实际上不确定最初的问题会是什么好答案。我将在为什么我不满意所接受答案的上方添加一条评论。
Vesal '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.