Was bedeutet Fragment in ANTLR?
Ich habe beide Regeln gesehen:
fragment DIGIT : '0'..'9';
und
DIGIT : '0'..'9';
Was ist der Unterschied?
Was bedeutet Fragment in ANTLR?
Ich habe beide Regeln gesehen:
fragment DIGIT : '0'..'9';
und
DIGIT : '0'..'9';
Was ist der Unterschied?
Antworten:
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.
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.
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.
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 (?)
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()
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
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 ?
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>)
Dieser Blog-Beitrag enthält ein sehr klares Beispiel, in dem fragment
ein 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).
fragment
, sondern die Reihenfolge der Lexerregeln.
DIGIT
als Fragment von INT
das Problem löst, nur weil Fragmente keine Token definieren und somit INT
die 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 fragment
Schlüsselwort bedeutet. Ich finde es etwas irreführend für jemanden, der zum ersten Mal versucht, die richtige Verwendung von Fragmenten herauszufinden.
fragment
in ANTLR bedeutet. Das Beispiel, das Sie geben, ist jedoch schlecht: Sie möchten nicht, dass ein Lexer einNUMBER
Token erzeugt, das eine Hex-, Dezimal- oder Oktalzahl sein kann. Das würde bedeuten, dass Sie dasNUMBER
Token in einer Produktion überprüfen müssen (Parser-Regel). Sie könnten besser die Lexer produzieren lassenINT
,OCT
undHEX
Token und eine Produktionsregel erstellen:number : INT | OCT | HEX;
. In einem solchen BeispielDIGIT
könnte a ein Fragment sein, das von den TokenINT
und verwendet wirdHEX
.