¿Qué significa fragmento en ANTLR?
He visto ambas reglas:
fragment DIGIT : '0'..'9';
y
DIGIT : '0'..'9';
¿Cuál es la diferencia?
¿Qué significa fragmento en ANTLR?
He visto ambas reglas:
fragment DIGIT : '0'..'9';
y
DIGIT : '0'..'9';
¿Cuál es la diferencia?
Respuestas:
Un fragmento es algo parecido a una función en línea: hace que la gramática sea más legible y más fácil de mantener.
Un fragmento nunca se contará como un token, solo sirve para simplificar una gramática.
Considerar:
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')+;
En este ejemplo, hacer coincidir un NÚMERO siempre devolverá un NÚMERO al lexer, independientemente de si coincidió con "1234", "0xab12" o "0777".
Según el libro de referencias Definitive Antlr4:
Las reglas con el prefijo fragment sólo se pueden llamar desde otras reglas de lexer; no son tokens por derecho propio.
de hecho, mejorarán la legibilidad de sus gramáticas.
mira este ejemplo:
STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;
STRING es un lexer que usa una regla de fragmento como ESC. Unicode se usa en la regla Esc y Hex se usa en la regla de fragmento Unicode. Las reglas ESC, UNICODE y HEX no se pueden usar explícitamente.
La referencia definitiva de ANTLR 4 (página 106):
Las reglas con el prefijo fragment sólo se pueden llamar desde otras reglas de lexer; no son tokens por derecho propio.
Caso 1: (si necesito el RULE1, RULE2, RULE3 entidades o información de grupo)
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
Caso2: (si no me importa REGLA1, REGLA2, REGLA3, solo me concentro en REGLA0)
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: (es equivalente a Case2, lo que lo hace más legible 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 (?)
Objetivo: identificar [ABC]+
, [DEF]+
, [GHI]+
fichas
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 (Caso1)
grammar Alphabet;
content : (rule0|ANYCHAR)* EOF;
rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Resultado:
# 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 (Caso2)
grammar Alphabet;
content : (RULE0|ANYCHAR)* EOF;
RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Alphabet.g4 (Caso3)
grammar Alphabet;
content : (RULE0|ANYCHAR)* EOF;
RULE0 : RULE1 | RULE2 | RULE3 ;
fragment RULE1 : [A-C]+ ;
fragment RULE2 : [DEF]+ ;
fragment RULE3 : ('G'|'H'|'I')+ ;
ANYCHAR : . -> skip;
Resultado:
# 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>)
¿ Viste las partes "grupos de captura " y "grupos que no capturan" ?
Objetivo: identificar números octales / decimales / hexadecimales
input.txt
0
123
1~9999
001~077
0xFF, 0x01, 0xabc123
Número.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()
Resultado:
# 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 se agrega el modificador 'fragmento' a DECIMAL_NUMBER
, OCTAL_NUMBER
, HEXADECIMAL_NUMBER
, usted no será capaz de capturar las entidades numéricas (ya que no son fichas más). Y el resultado será:
$ 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>)
Esta publicación de blog tiene un ejemplo muy claro donde fragment
marca una diferencia significativa:
grammar number;
number: INT;
DIGIT : '0'..'9';
INT : DIGIT+;
La gramática reconocerá '42' pero no '7'. Puede arreglarlo haciendo de dígito un fragmento (o moviendo DIGIT después de INT).
fragment
, sino el orden de las reglas del lexer.
DIGIT
como un fragmento de INT
resuelve el problema solo porque los fragmentos no definen tokens, por lo que se crea INT
la primera regla léxica. Estoy de acuerdo con usted en que este es un ejemplo significativo, pero (en mi opinión) solo para quienes ya saben lo que fragment
significa la palabra clave. Lo encuentro algo engañoso para alguien que está tratando de averiguar el uso correcto de los fragmentos por primera vez.
fragment
significa en ANTLR. Pero el ejemplo que da es pobre: no quiere que un lexer produzca unNUMBER
token que puede ser un número hexadecimal, decimal u octal. Eso significaría que necesitaría inspeccionar elNUMBER
token en una producción (regla del analizador). Usted puede dejar que la producen mejor léxicoINT
,OCT
yHEX
las fichas y crear una regla de producción:number : INT | OCT | HEX;
. En tal ejemplo, aDIGIT
podría ser un fragmento que sería utilizado por los tokensINT
yHEX
.