Cosa significa frammento in ANTLR?
Ho visto entrambe le regole:
fragment DIGIT : '0'..'9';
e
DIGIT : '0'..'9';
Qual è la differenza?
Cosa significa frammento in ANTLR?
Ho visto entrambe le regole:
fragment DIGIT : '0'..'9';
e
DIGIT : '0'..'9';
Qual è la differenza?
Risposte:
Un frammento è in qualche modo simile a una funzione inline: rende la grammatica più leggibile e più facile da mantenere.
Un frammento non verrà mai considerato come un segno, serve solo a semplificare una grammatica.
Tener conto di:
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 questo esempio, la corrispondenza di un NUMERO restituirà sempre un NUMERO al lexer, indipendentemente dal fatto che corrisponda a "1234", "0xab12" o "0777".
Secondo il libro di referenze Definitive Antlr4:
Le regole con prefisso fragment possono essere chiamate solo da altre regole di lexer; non sono gettoni di per sé.
in realtà miglioreranno la leggibilità delle tue grammatiche.
guarda questo esempio:
STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;
STRING è un lexer che utilizza una regola di frammento come ESC .Unicode viene utilizzato nella regola Esc e Hex viene utilizzato nella regola di frammento Unicode. Le regole ESC, UNICODE e HEX non possono essere utilizzate esplicitamente.
Riferimento definitivo per ANTLR 4 (pagina 106) :
Le regole con prefisso fragment possono essere chiamate solo da altre regole di lexer; non sono gettoni di per sé.
Caso 1: (se ho bisogno del Regola1, Regola2, Regola3 entità o le informazioni sul gruppo)
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
Caso 2: (se non mi interessa REGOLA1, REGOLA2, REGOLA3, mi concentro solo su REGOLA0)
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: (è equivalente a Case2, rendendolo più leggibile di 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 (?)
Obiettivo: identificare [ABC]+
, [DEF]+
, [GHI]+
gettoni
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 (Case1)
grammar Alphabet;
content : (rule0|ANYCHAR)* EOF;
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Risultato:
# 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 (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;
Risultato:
# 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>)
Hai visto le parti "acquisizione di gruppi" e "non acquisizione di gruppi" ?
Obiettivo: identificare i numeri ottali / decimali / esadecimali
input.txt
0
123
1~9999
001~077
0xFF, 0x01, 0xabc123
Numero.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()
Risultato:
# 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
Se si aggiunge il modificatore 'frammento' a DECIMAL_NUMBER
, OCTAL_NUMBER
, HEXADECIMAL_NUMBER
, non sarà in grado di catturare i soggetti numerici (dal momento che non sono più gettoni). E il risultato sarà:
$ 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>)
Questo post del blog ha un esempio molto chiaro in cui fragment
fa una differenza significativa:
grammar number;
number: INT;
DIGIT : '0'..'9';
INT : DIGIT+;
La grammatica riconoscerà "42" ma non "7". Puoi aggiustarlo trasformando la cifra in un frammento (o spostando DIGIT dopo INT).
fragment
, ma l'ordine delle regole del lexer.
DIGIT
come un frammento di INT
risolve il problema solo perché i frammenti non definiscono i token, creando così INT
la prima regola lessicale. Sono d'accordo con te che questo è un esempio significativo ma (imo) solo per chi sa già cosa fragment
significa la parola chiave. Lo trovo in qualche modo fuorviante per qualcuno che sta cercando di capire l'uso corretto dei frammenti per la prima volta.
fragment
significa in ANTLR. Ma l'esempio che dai è scarso: non vuoi che un lexer produca unNUMBER
token che può essere un numero esadecimale, decimale o ottale. Ciò significherebbe che avresti bisogno di ispezionare ilNUMBER
token in una produzione (regola del parser). Si potrebbe meglio lasciare che la producono lexerINT
,OCT
eHEX
gettoni e creare una regola di produzione:number : INT | OCT | HEX;
. In un esempio del genere, aDIGIT
potrebbe essere un frammento che verrebbe utilizzato dai tokenINT
eHEX
.