Что означает «фрагмент» в ANTLR?

99

Что означает фрагмент в ANTLR?

Я видел оба правила:

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

и

DIGIT : '0'..'9';

В чем разница?

Оскар Медерос
источник

Ответы:

112

Фрагмент чем-то похож на встроенную функцию: он делает грамматику более читаемой и простой в обслуживании.

Фрагмент никогда не будет считаться токеном, он служит только для упрощения грамматики.

Рассматривать:

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')+;

В этом примере сопоставление ЧИСЛА всегда будет возвращать НОМЕР в лексер, независимо от того, соответствует ли он «1234», «0xab12» или «0777».

См. Пункт 3

сирбриальянс
источник
43
Вы правы насчет того, что fragmentозначает в ANTLR. Но приведенный вами пример плохой: вы не хотите, чтобы лексер создавал NUMBERтокен, который может быть шестнадцатеричным, десятичным или восьмеричным числом. Это означает, что вам нужно проверить NUMBERтокен в производственной среде (правило парсера). Вы могли бы лучше пусть лексер производит INT, OCTи HEXмаркер и создать правило производства: number : INT | OCT | HEX;. В таком примере a DIGITможет быть фрагментом, который будет использоваться токенами INTи HEX.
Барт Кирс
10
Заметьте, что «бедный» может звучать немного резковато, но я не мог найти для него лучшего слова ... Извините! :)
Барт Кирс
1
Вы не были резкими ... Вы были правы и прямо!
asyncwait
2
Важно отметить, что фрагменты предназначены для использования только в других правилах лексера для определения других токенов лексера. Фрагменты не предназначены для использования в правилах грамматики (парсера).
djb
1
@BartKiers: не могли бы вы создать новый ответ, включая ваш лучший ответ.
Дэвид Ньюкомб
18

Согласно справочнику Definitive Antlr4:

Правила с префиксом фрагмент могут быть вызваны только из других правил лексера; они не являются токенами сами по себе.

на самом деле они улучшат читаемость вашей грамматики.

посмотрите на этот пример:

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

STRING - это лексический анализатор, использующий правило фрагмента, например ESC. Юникод используется в правиле Esc, а шестнадцатеричный - в правиле фрагмента Юникода. Правила ESC, UNICODE и HEX не могут использоваться явно.

Настаран Хакими
источник
10

Окончательный справочник по ANTLR 4 (стр. 106) :

Правила с префиксом фрагмент могут быть вызваны только из других правил лексера; они не являются токенами сами по себе.


Абстрактные концепции:

Вариант 1: (если мне нужна rule1 Правилу2, Правилу3 лица или о группе)

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


Случай 2: (если мне все равно ПРАВИЛО1, ПРАВИЛО2, ПРАВИЛО3, я просто сосредоточусь на ПРАВИЛЕ0)

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. Каждое из правил RULE1 / 2/3 в Case1 является группой захвата, аналогичной Regex: (X)
  3. Каждое из RULE1 / 2/3 в Case3 является группой без захвата, аналогично 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


Число.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_NUMBER, OCTAL_NUMBER, HEXADECIMAL_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).

Весал
источник
1
Проблема здесь не в отсутствии ключевого слова fragment, а в порядке правил лексера.
BlackBrain 05
Я использовал слово «исправить», но дело не в том, чтобы исправить проблему. Я добавил этот пример сюда, потому что для меня это был самый полезный и простой пример того, что на самом деле меняется при использовании фрагмента ключевого слова.
Vesal
2
Я просто утверждаю, что объявление DIGITкак фрагмент INTрешает проблему только потому, что фрагменты не определяют токены, тем самым создавая INTпервое лексическое правило. Я согласен с вами, что это значимый пример, но (имо) только для тех, кто уже знает, что fragmentозначает ключевое слово. Я считаю, что это несколько вводит в заблуждение тех, кто впервые пытается понять, как правильно использовать фрагменты.
BlackBrain
1
Поэтому, когда я изучал это, я видел множество примеров, подобных приведенным выше, но я не совсем понимал, зачем для этого нужно дополнительное ключевое слово. Я не понимал, что на практике означает отсутствие «токенов сами по себе». Я не совсем уверен, что было бы хорошим ответом на исходный вопрос. Я добавлю комментарий выше, почему меня не устраивает принятый ответ.
Весаль