Was bedeutet "Fragment" in ANTLR?


99

Was bedeutet Fragment in ANTLR?

Ich habe beide Regeln gesehen:

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

und

DIGIT : '0'..'9';

Was ist der Unterschied?

Antworten:


110

Ein Fragment ähnelt in gewisser Weise einer Inline-Funktion: Es macht die Grammatik lesbarer und einfacher zu pflegen.

Ein Fragment wird niemals als Token gezählt, es dient nur zur Vereinfachung einer Grammatik.

Erwägen:

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

In diesem Beispiel gibt das Abgleichen einer NUMMER immer eine NUMMER an den Lexer zurück, unabhängig davon, ob sie mit "1234", "0xab12" oder "0777" übereinstimmt.

Siehe Punkt 3


43
Sie haben Recht damit, was fragmentin ANTLR bedeutet. Das Beispiel, das Sie geben, ist jedoch schlecht: Sie möchten nicht, dass ein Lexer ein NUMBERToken erzeugt, das eine Hex-, Dezimal- oder Oktalzahl sein kann. Das würde bedeuten, dass Sie das NUMBERToken in einer Produktion überprüfen müssen (Parser-Regel). Sie könnten besser die Lexer produzieren lassen INT, OCTund HEXToken und eine Produktionsregel erstellen: number : INT | OCT | HEX;. In einem solchen Beispiel DIGITkönnte a ein Fragment sein, das von den Token INTund verwendet wird HEX.
Bart Kiers

10
Beachten Sie, dass "arm" vielleicht etwas hart klingt, aber ich konnte kein besseres Wort dafür finden ... Entschuldigung! :)
Bart Kiers

1
Sie klangen nicht hart .. Sie hatten Recht und gerade!
Asyncwait

2
Wichtig ist, dass Fragmente nur in anderen Lexer-Regeln verwendet werden sollen, um andere Lexer-Token zu definieren. Fragmente dürfen nicht in Grammatikregeln (Parserregeln) verwendet werden.
DJB

1
@BartKiers: Könnten Sie eine neue Antwort erstellen, einschließlich Ihrer besseren Antwort?
David Newcomb

18

Laut dem Definitive Antlr4-Nachschlagewerk:

Regeln, denen ein Fragment vorangestellt ist, können nur von anderen Lexer-Regeln aufgerufen werden. Sie sind keine eigenständigen Token.

Tatsächlich verbessern sie die Lesbarkeit Ihrer Grammatiken.

Schauen Sie sich dieses Beispiel an:

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

STRING ist ein Lexer, der eine Fragmentregel wie ESC verwendet. Unnicode wird in der Esc-Regel und Hex wird in der Unicode-Fragmentregel verwendet. ESC-, UNICODE- und HEX-Regeln können nicht explizit verwendet werden.


10

Die endgültige ANTLR 4-Referenz (Seite 106):

Regeln, denen ein Fragment vorangestellt ist, können nur von anderen Lexer-Regeln aufgerufen werden. Sie sind keine eigenständigen Token.


Abstrakte Konzepte:

Fall 1: (wenn ich die Entitäten RULE1, RULE2, RULE3 oder Gruppeninformationen benötige )

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


Fall 2: (Wenn mir RULE1, RULE2, RULE3 egal sind, konzentriere ich mich nur auf 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


Fall3: (entspricht Fall2 und ist daher besser lesbar als Fall2)

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


Unterschiede zwischen Fall1 und Fall2 / 3?

  1. Die Lexer-Regeln sind gleichwertig
  2. Jede der REGEL1 / 2/3 in Fall1 ist eine Erfassungsgruppe, ähnlich wie bei Regex: (X)
  3. Jede der REGEL1 / 2/3 in Fall3 ist eine nicht erfassende Gruppe, ähnlich wie bei Regex :(?: X) Geben Sie hier die Bildbeschreibung ein



Sehen wir uns ein konkretes Beispiel an.

Ziel: Identifizierung [ABC]+, [DEF]+, [GHI]+Token

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


Fall 1 und Ergebnisse:

Alphabet.g4 (Fall1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Ergebnis:

# 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


Fall 2/3 und Ergebnisse:

Alphabet.g4 (Fall2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Alphabet.g4 (Fall3)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Ergebnis:

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

Haben Sie Teile "Gruppen erfassen" und "Gruppen nicht erfassen" gesehen ?




Sehen wir uns das konkrete Beispiel2 an.

Ziel: Identifizieren Sie Oktal- / Dezimal- / Hexadezimalzahlen

input.txt

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


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


Ergebnis:

# 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

Wenn Sie den Modifikator ‚Fragment‘ hinzufügen DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, werden Sie nicht in der Lage sein , die Zahl Einheiten zu erfassen (da sie nicht sind Zeichen mehr). Und das Ergebnis wird sein:

$ 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

Dieser Blog-Beitrag enthält ein sehr klares Beispiel, in dem fragmentein wesentlicher Unterschied gemacht wird:

grammar number;  

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

Die Grammatik erkennt '42', aber nicht '7'. Sie können das Problem beheben, indem Sie die Ziffer zu einem Fragment machen (oder DIGIT nach INT verschieben).


1
Das Problem hierbei ist nicht das Fehlen des Schlüsselworts fragment, sondern die Reihenfolge der Lexerregeln.
BlackBrain

Ich habe das Wort "fix" verwendet, aber es geht nicht darum, ein Problem zu beheben. Ich habe dieses Beispiel hier hinzugefügt, weil dies für mich das hilfreichste und einfachste Beispiel dafür war, was sich bei Verwendung des Schlüsselwortfragments tatsächlich ändert .
Vesal

2
Ich argumentiere nur, dass das Deklarieren DIGITals Fragment von INTdas Problem löst, nur weil Fragmente keine Token definieren und somit INTdie erste lexikalische Regel bilden. Ich stimme Ihnen zu, dass dies ein aussagekräftiges Beispiel ist, aber (imo) nur für diejenigen, die bereits wissen, was das fragmentSchlüsselwort bedeutet. Ich finde es etwas irreführend für jemanden, der zum ersten Mal versucht, die richtige Verwendung von Fragmenten herauszufinden.
BlackBrain

1
Als ich das lernte, sah ich viele Beispiele wie die oben genannten, aber ich wusste nicht genau, warum man dafür ein zusätzliches Schlüsselwort benötigen würde. Ich habe nicht verstanden, was es in der Praxis bedeutet, nicht "eigenständige Token" zu sein. Jetzt bin ich mir nicht sicher, was eine gute Antwort auf die ursprüngliche Frage wäre. Ich werde oben einen Kommentar hinzufügen, warum ich mit der akzeptierten Antwort nicht zufrieden bin.
Vesal
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.