O que “fragmento” significa em ANTLR?


99

O que significa fragmento em ANTLR?

Eu vi as duas regras:

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

e

DIGIT : '0'..'9';

Qual é a diferença?

Respostas:


110

Um fragmento é algo semelhante a uma função embutida: torna a gramática mais legível e mais fácil de manter.

Um fragmento nunca será contado como um token, ele serve apenas para simplificar uma 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')+;

Neste exemplo, combinar um NUMBER sempre retornará um NUMBER para o lexer, independentemente de corresponder a "1234", "0xab12" ou "0777".

Ver item 3


43
Você está certo sobre o que fragmentsignifica no ANTLR. Mas o exemplo que você dá é pobre: ​​você não quer que um lexer produza umNUMBER token que pode ser um número hexadecimal, decimal ou octal. Isso significa que você precisa inspecionar o NUMBERtoken em uma produção (regra do analisador). Você poderia melhor deixar o lexer produzir INT, OCTe HEXos tokens e criar uma regra de produção: number : INT | OCT | HEX;. Nesse exemplo, a DIGITpoderia ser um fragmento que seria usado pelos tokens INTe HEX.
— Bart Kiers

10
Observe que "pobre" pode soar um pouco duro, mas não consegui encontrar uma palavra melhor para isso ... Desculpe! :)
— Bart Kiers

1
Você não estava soando rude .. você estava certo e direto!
— asyncwait de

2
É importante ressaltar que os fragmentos devem ser usados ​​apenas em outras regras lexer para definir outros tokens lexer. Os fragmentos não devem ser usados ​​em regras gramaticais (analisador).
— djb de

1
@BartKiers: você poderia criar uma nova resposta incluindo sua melhor resposta.
— David Newcomb

18

De acordo com o livro de referências Definitive Antlr4:

Regras prefixadas com fragmento podem ser chamadas apenas de outras regras lexer; eles não são símbolos por si próprios.

na verdade, eles vão melhorar a legibilidade de suas gramáticas.

veja este exemplo:

STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;

STRING é um lexer que usa a regra de fragmento como ESC .Unicode é usado na regra Esc e Hex é usado na regra de fragmento Unicode. As regras ESC, UNICODE e HEX não podem ser usadas explicitamente.


10

A Referência ANTLR 4 Definitiva (Página 106):

Regras prefixadas com fragmento podem ser chamadas apenas de outras regras lexer; eles não são símbolos por si próprios.


Conceitos abstratos:

Case1: (se preciso o RULE1, RULE2, Regra3 entidades ou grupo info)

rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;


Caso 2: (se eu não me importo com RULE1, RULE2, RULE3, eu apenas me concentro em 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


Caso 3: (é equivalente ao Caso 2, tornando-o mais legível do que o Caso 2)

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


Diferenças entre Case1 e Case2 / 3?

  1. As regras lexer são equivalentes
  2. Cada uma das RULE1 / 2/3 no Caso1 é um grupo de captura, semelhante ao Regex: (X)
  3. Cada uma das RULE1 / 2/3 no Caso 3 é um grupo sem captura, semelhante ao Regex :( ?: X) insira a descrição da imagem aqui



Vamos ver um exemplo 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()


Caso 1 e resultados:

Alphabet.g4 (Case1)

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


Caso 2/3 e resultados:

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;

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

Você viu partes de "grupos de captura " e "grupos de não captura" ?




Vamos ver o exemplo concreto2.

Objetivo: identificar números octais / decimais / hexadecimais

input.txt

0
123
 1~9999
 001~077
0xFF, 0x01, 0xabc123


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

Se você adicionar o modificador 'fragmento' para DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, você não será capaz de capturar as entidades numéricas (uma vez que eles não são fichas mais). E o 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>)

8

Esta postagem do blog tem um exemplo muito claro em que fragmentfaz uma diferença significativa:

grammar number;  

number: INT;  
DIGIT : '0'..'9';  
INT   :  DIGIT+;

A gramática reconhecerá '42', mas não '7'. Você pode consertá-lo criando um fragmento de dígito (ou movendo DIGIT após INT).


1
O problema aqui não é a ausência da palavra-chave fragment, mas a ordem das regras do lexer.
— BlackBrain

Usei a palavra "consertar", mas a questão não é consertar um problema. Eu adicionei este exemplo aqui porque, para mim, este foi o exemplo mais útil e simples do que realmente muda ao usar o fragmento de palavra-chave.
— Vesal

2
Estou apenas argumentando que declarar DIGITcomo um fragmento de INTresolve o problema simplesmente porque os fragmentos não definem tokens, constituindo assim INTa primeira regra lexical. Concordo com você que este é um exemplo significativo, mas (imo) apenas para quem já sabe o que a fragmentpalavra - chave significa. Acho um tanto enganoso para alguém que está tentando descobrir o uso correto de fragmentos pela primeira vez.
— BlackBrain

1
Então, quando estava aprendendo isso, vi muitos exemplos como os acima, mas não entendi por que seria necessária uma palavra-chave extra para isso. Eu não entendia o que isso significava na prática não ser "tokens em seu próprio direito". Bem, não tenho certeza de qual seria uma boa resposta para a pergunta original. Vou adicionar um comentário acima porque não estou satisfeito com a resposta aceita.
— Vesal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.