Co oznacza „fragment” w ANTLR?


99

Co oznacza fragment w ANTLR?

Widziałem obie zasady:

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

i

DIGIT : '0'..'9';

Jaka jest różnica?

Odpowiedzi:


110

Fragment jest nieco podobny do funkcji wbudowanej: sprawia, że ​​gramatyka jest bardziej czytelna i łatwiejsza w utrzymaniu.

Fragment nigdy nie będzie liczony jako token, służy jedynie uproszczeniu gramatyki.

Rozważać:

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

W tym przykładzie dopasowanie LICZBY zawsze zwróci lekserowi LICZBĘ, niezależnie od tego, czy pasuje do „1234”, „0xab12” czy „0777”.

Patrz punkt 3


43
Masz rację co do fragmentznaczenia w ANTLR. Ale przykład, który podajesz, jest kiepski: nie chcesz, aby lekser tworzył NUMBERtoken, który może być liczbą szesnastkową, dziesiętną lub ósemkową. Oznaczałoby to, że musiałbyś sprawdzić NUMBERtoken w produkcji (reguła parsera). Można lepiej niech produce lexer INT, OCToraz HEXznaki i utworzyć regułę produkcji: number : INT | OCT | HEX;. W takim przykładzie a DIGITmoże być fragmentem, który będzie używany przez tokeny INTi HEX.
Bart Kiers

10
Zauważ, że „biedny” może brzmieć nieco szorstko, ale nie mogłem znaleźć lepszego określenia ... Przepraszamy! :)
Bart Kiers

1
Nie brzmiałeś ostro… miałeś rację i prosto!
asyncwait

2
Co ważne, fragmenty mają być używane tylko w innych regułach leksera do definiowania innych tokenów leksera. Fragmenty nie są przeznaczone do stosowania w regułach gramatyki (parsera).
djb

1
@BartKiers: czy mógłbyś utworzyć nową odpowiedź, w tym lepszą.
David Newcomb

18

Według książki referencyjnej Definitive Antlr4:

Reguły poprzedzone fragmentem można wywołać tylko z innych reguł leksera; same w sobie nie są żetonami.

w rzeczywistości poprawią czytelność twoich gramatyk.

spójrz na ten przykład:

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

STRING to lekser używający reguły fragmentów, takiej jak ESC. Unicode jest używany w regule Esc, a Hex jest używany w regule fragmentów Unicode. Reguł ESC, UNICODE i HEX nie można używać jawnie.


10

The Definitive ANTLR 4 Reference (Strona 106) :

Reguły poprzedzone fragmentem można wywołać tylko z innych reguł leksera; same w sobie nie są żetonami.


Abstrakcyjne koncepcje:

Przypadek 1: (jeśli muszę RULE1, RULE2, RULE3 podmioty lub informacji o grupie)

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


Przypadek2: (jeśli nie obchodzi mnie RULE1, RULE2, RULE3, skupiam się tylko na 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


Przypadek3: (jest odpowiednikiem przypadku2, dzięki czemu jest bardziej czytelny niż przypadek2)

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


Różnice między przypadkiem 1 i przypadkiem 2/3?

  1. Reguły leksera są równoważne
  2. Każda z RULE1 / 2/3 w przypadku 1 jest grupą przejmującą, podobną do Regex: (X)
  3. Każda z reguł 1/2/3 w przypadku 3 jest grupą nieprzechwytywaną, podobną do Regex :( ?: X) wprowadź opis obrazu tutaj



Zobaczmy konkretny przykład.

Cel: zidentyfikować [ABC]+, [DEF]+, [GHI]+tokeny

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


Przypadek 1 i wyniki:

Alphabet.g4 (przypadek 1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Wynik:

# 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


Przypadek 2/3 i wyniki:

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;

Wynik:

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

Czy widziałeś części „grup przechwytywania” i „grup nieprzechwytywanych” ?




Zobaczmy konkretny przykład 2.

Cel: zidentyfikuj liczby ósemkowe / dziesiętne / szesnastkowe

input.txt

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


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


Wynik:

# 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

Jeśli dodać modyfikator „fragment” do DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, nie będzie w stanie uchwycić żywe numer (ponieważ nie są one już żetony). Rezultatem będzie:

$ 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

Ten post na blogu zawiera bardzo wyraźny przykład, w którym fragmentma znaczenie:

grammar number;  

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

Gramatyka rozpoznaje „42”, ale nie „7”. Możesz to naprawić, tworząc fragment cyfry (lub przesuwając DIGIT po INT).


1
Problemem nie jest tutaj brak słowa kluczowego fragment, ale kolejność reguł leksera.
BlackBrain

Użyłem słowa „napraw”, ale nie chodzi o naprawienie problemu. Dodałem tutaj ten przykład, ponieważ był to dla mnie najbardziej pomocny i prosty przykład tego, co faktycznie zmienia się, gdy używamy fragmentu słowa kluczowego.
Vesal

2
Twierdzę tylko, że zadeklarowanie DIGITjako fragmentu INTrozwiązuje problem tylko dlatego, że fragmenty nie definiują tokenów, tworząc w ten sposób INTpierwszą regułę leksykalną. Zgadzam się z tobą, że jest to znaczący przykład, ale (imo) tylko dla tych, którzy już wiedzą, co fragmentoznacza słowo kluczowe. Uważam to za nieco mylące dla kogoś, kto po raz pierwszy próbuje znaleźć prawidłowe użycie fragmentów.
BlackBrain

1
Więc kiedy się tego uczyłem, widziałem wiele przykładów, takich jak te powyżej, ale nie bardzo rozumiałem, dlaczego potrzebowałoby do tego dodatkowego słowa kluczowego. Nie rozumiałem, co w praktyce oznacza to, że nie jest to „samymi tokenami”. Nie wiem, jaka byłaby dobra odpowiedź na pierwotne pytanie. Powyżej dodam komentarz dlaczego nie jestem zadowolony z zaakceptowanej odpowiedzi.
Vesal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.