Que signifie fragment dans ANTLR?
J'ai vu les deux règles:
fragment DIGIT : '0'..'9';
et
DIGIT : '0'..'9';
Quelle est la différence?
Que signifie fragment dans ANTLR?
J'ai vu les deux règles:
fragment DIGIT : '0'..'9';
et
DIGIT : '0'..'9';
Quelle est la différence?
Réponses:
Un fragment s'apparente un peu à une fonction en ligne: il rend la grammaire plus lisible et plus facile à maintenir.
Un fragment ne sera jamais compté comme un jeton, il ne sert qu'à simplifier une grammaire.
Considérer:
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')+;
Dans cet exemple, la correspondance avec un NUMBER renverra toujours un NUMBER au lexer, qu'il corresponde à «1234», «0xab12» ou «0777».
Selon le livre de références Definitive Antlr4:
Les règles préfixées par fragment ne peuvent être appelées qu'à partir d'autres règles de lexer; ce ne sont pas des jetons à part entière.
en fait, ils amélioreront la lisibilité de vos grammaires.
regardez cet exemple:
STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;
STRING est un lexer utilisant une règle de fragment comme ESC. Unicode est utilisé dans la règle Esc et Hex est utilisé dans la règle de fragment Unicode. Les règles ESC et UNICODE et HEX ne peuvent pas être utilisées explicitement.
La référence définitive ANTLR 4 (Page 106) :
Les règles préfixées par fragment ne peuvent être appelées qu'à partir d'autres règles de lexer; ce ne sont pas des jetons à part entière.
Cas1: (si j'ai besoin rule1, règle2, Règle3 entités ou informations groupe)
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
Cas 2: (si je m'en fiche RULE1, RULE2, RULE3, je me concentre juste sur 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: (équivaut à Case2, ce qui le rend plus lisible que 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 (?)
Objectif: identifier [ABC]+
, [DEF]+
, [GHI]+
jetons
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 (Cas 1)
grammar Alphabet;
content : (rule0|ANYCHAR)* EOF;
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Résultat:
# 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 (Cas 2)
grammar Alphabet;
content : (RULE0|ANYCHAR)* EOF;
RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Alphabet.g4 (Cas 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;
Résultat:
# 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>)
Avez-vous vu des parties «groupes de capture » et «groupes sans capture» ?
Objectif: identifier les nombres octaux / décimaux / hexadécimaux
input.txt
0
123
1~9999
001~077
0xFF, 0x01, 0xabc123
Numéro.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()
Résultat:
# 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
Si vous ajoutez le modificateur « fragment » à DECIMAL_NUMBER
, OCTAL_NUMBER
, HEXADECIMAL_NUMBER
, vous ne serez pas en mesure de saisir les entités numériques (car ils ne sont pas plus des jetons). Et le résultat sera:
$ 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>)
Cet article de blog a un exemple très clair où fragment
fait une différence significative:
grammar number;
number: INT;
DIGIT : '0'..'9';
INT : DIGIT+;
La grammaire reconnaîtra «42» mais pas «7». Vous pouvez le corriger en faisant de digit un fragment (ou en déplaçant DIGIT après INT).
fragment
, mais l'ordre des règles du lexer.
DIGIT
tant que fragment de INT
résout le problème simplement parce que les fragments ne définissent pas de jetons, créant ainsi INT
la première règle lexicale. Je suis d'accord avec vous pour dire que c'est un exemple significatif mais (imo) uniquement pour qui sait déjà ce que fragment
signifie le mot - clé. Je trouve cela quelque peu trompeur pour quelqu'un qui essaie de comprendre pour la première fois l'utilisation correcte des fragments.
fragment
signifie ANTLR. Mais l'exemple que vous donnez est médiocre: vous ne voulez pas qu'un lexeur produise unNUMBER
jeton qui peut être un nombre hexadécimal, décimal ou octal. Cela signifierait que vous auriez besoin d'inspecter leNUMBER
jeton dans une production (règle d'analyseur). Vous pouvez mieux laisser le produire lexerINT
,OCT
etHEX
jetons et créer une règle de production:number : INT | OCT | HEX;
. Dans un tel exemple, aDIGIT
pourrait être un fragment qui serait utilisé par les jetonsINT
etHEX
.