Cosa significa "frammento" in ANTLR?


99

Cosa significa frammento in ANTLR?

Ho visto entrambe le regole:

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

e

DIGIT : '0'..'9';

Qual è la differenza?

Risposte:


110

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

Vedi punto 3


43
Hai ragione su cosa fragmentsignifica in ANTLR. Ma l'esempio che dai è scarso: non vuoi che un lexer produca un NUMBERtoken che può essere un numero esadecimale, decimale o ottale. Ciò significherebbe che avresti bisogno di ispezionare il NUMBERtoken in una produzione (regola del parser). Si potrebbe meglio lasciare che la producono lexer INT, OCTe HEXgettoni e creare una regola di produzione: number : INT | OCT | HEX;. In un esempio del genere, a DIGITpotrebbe essere un frammento che verrebbe utilizzato dai token INTe HEX.
Bart Kiers

10
Nota che "povero" potrebbe suonare un po 'duro, ma non sono riuscito a trovare una parola migliore per questo ... Scusa! :)
Bart Kiers

1
Non stavi suonando duro .. avevi ragione e dritto!
asyncwait

2
È importante sottolineare che i frammenti devono essere utilizzati solo in altre regole lexer per definire altri token lexer. I frammenti non sono pensati per essere utilizzati nelle regole grammaticali (parser).
djb

1
@BartKiers: potresti creare una nuova risposta che includa la tua risposta migliore.
David Newcomb

18

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.


10

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


Concetti astratti:

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


Differenze tra Case1 e Case2 / 3?

  1. Le regole del lexer sono equivalenti
  2. Ciascuna delle REGOLA1 / 2/3 nel caso 1 è un gruppo di acquisizione, simile a Regex: (X)
  3. Ciascuna delle REGOLE1 / 2/3 in Case3 è un gruppo non di acquisizione, simile a Regex :( ?: X) inserisci qui la descrizione dell'immagine



Vediamo un esempio concreto.

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


Case1 e risultati:

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


Caso 2/3 e risultati:

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




Vediamo l'esempio concreto 2.

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

8

Questo post del blog ha un esempio molto chiaro in cui fragmentfa 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).


1
Il problema qui non è l'assenza della parola chiave fragment, ma l'ordine delle regole del lexer.
BlackBrain

Ho usato la parola "aggiustare", ma il punto non è risolvere un problema. Ho aggiunto questo esempio qui perché, per me, questo è stato l'esempio più utile e semplice di ciò che effettivamente cambia quando si utilizza il frammento di parola chiave.
Vesal

2
Sto solo sostenendo che dichiarare DIGITcome un frammento di INTrisolve il problema solo perché i frammenti non definiscono i token, creando così INTla prima regola lessicale. Sono d'accordo con te che questo è un esempio significativo ma (imo) solo per chi sa già cosa fragmentsignifica 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.
BlackBrain

1
Quindi, quando stavo imparando questo, ho visto molti esempi come quelli sopra, ma non ho capito perché fosse necessaria una parola chiave in più per questo. Non capivo cosa significasse in pratica questo non essere "gettoni a sé stanti". Ora, in realtà non sono sicuro di quale sarebbe una buona risposta alla domanda originale. Aggiungerò un commento sopra perché non sono soddisfatto della risposta accettata.
Vesal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.