¿Qué significa "fragmento" en ANTLR?

99

¿Qué significa fragmento en ANTLR?

He visto ambas reglas:

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

y

DIGIT : '0'..'9';

¿Cuál es la diferencia?

Oscar Mederos
fuente

Respuestas:

110

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".

Ver artículo 3

sirbrialliance
fuente
43
Tienes razón sobre lo que fragmentsignifica en ANTLR. Pero el ejemplo que da es pobre: ​​no quiere que un lexer produzca un NUMBERtoken que puede ser un número hexadecimal, decimal u octal. Eso significaría que necesitaría inspeccionar el NUMBERtoken en una producción (regla del analizador). Usted puede dejar que la producen mejor léxico INT, OCTy HEXlas fichas y crear una regla de producción: number : INT | OCT | HEX;. En tal ejemplo, a DIGITpodría ser un fragmento que sería utilizado por los tokens INTy HEX.
Bart Kiers
10
Tenga en cuenta que "pobre" puede sonar un poco duro, pero no pude encontrar una palabra mejor para eso ... ¡Lo siento! :)
Bart Kiers
1
No estabas sonando duro ... ¡tenías razón!
asyncwait
2
Es importante destacar que los fragmentos están destinados a ser utilizados solo en otras reglas de lexer para definir otros tokens de lexer. Los fragmentos no están destinados a ser utilizados en reglas gramaticales (analizador).
djb
1
@BartKiers: ¿podría crear una nueva respuesta que incluya su mejor respuesta?
David Newcomb
18

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.

Nastaran Hakimi
fuente
10

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.


Conceptos abstractos:

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


¿Diferencias entre Case1 y Case2 / 3?

  1. Las reglas de lexer son equivalentes
  2. Cada uno de RULE1 / 2/3 en Case1 es un grupo de captura, similar a Regex: (X)
  3. Cada uno de RULE1 / 2/3 en Case3 es un grupo que no captura, similar a Regex :( ?: X) ingrese la descripción de la imagen aquí



Veamos un ejemplo concreto.

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


Caso1 y resultados:

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


Caso2 / 3 y resultados:

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" ?




Veamos el ejemplo concreto2.

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>)
蔡宗容
fuente
8

Esta publicación de blog tiene un ejemplo muy claro donde fragmentmarca 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).

Vesal
fuente
1
El problema aquí no es la ausencia de la palabra clave fragment, sino el orden de las reglas del lexer.
BlackBrain
Usé la palabra "arreglar", pero el punto no es arreglar un problema. Agregué este ejemplo aquí porque, para mí, este fue el ejemplo más útil y simple de lo que realmente cambia cuando se usa el fragmento de palabra clave.
Vesal
2
Solo estoy argumentando que declarar DIGITcomo un fragmento de INTresuelve el problema solo porque los fragmentos no definen tokens, por lo que se crea INTla 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 fragmentsignifica la palabra clave. Lo encuentro algo engañoso para alguien que está tratando de averiguar el uso correcto de los fragmentos por primera vez.
BlackBrain
1
Entonces, cuando estaba aprendiendo esto, vi muchos ejemplos como los anteriores, pero no entendí por qué se necesitaría una palabra clave adicional para esto. No entendía qué significaba en la práctica esto de no ser "tokens por derecho propio". Ahora, no estoy seguro de cuál sería una buena respuesta a la pregunta original. Agregaré un comentario arriba por qué no estoy satisfecho con la respuesta aceptada.
Vesal