Que signifie «fragment» dans ANTLR?

99

Que signifie fragment dans ANTLR?

J'ai vu les deux règles:

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

et

DIGIT : '0'..'9';

Quelle est la différence?

Oscar Mederos
la source

Réponses:

110

Un fragment s'apparente un peu à une fonction en ligne: il rend la grammaire plus lisible et plus facile à maintenir.

Un fragment ne sera jamais compté comme un jeton, il ne sert qu'à simplifier une grammaire.

Considérer:

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')+;

Dans cet exemple, la correspondance avec un NUMBER renverra toujours un NUMBER au lexer, qu'il corresponde à «1234», «0xab12» ou «0777».

Voir l'élément 3

sirbrialliance
la source
43
Vous avez raison sur ce que fragmentsignifie ANTLR. Mais l'exemple que vous donnez est médiocre: vous ne voulez pas qu'un lexeur produise un NUMBERjeton qui peut être un nombre hexadécimal, décimal ou octal. Cela signifierait que vous auriez besoin d'inspecter le NUMBERjeton dans une production (règle d'analyseur). Vous pouvez mieux laisser le produire lexer INT, OCTet HEXjetons et créer une règle de production: number : INT | OCT | HEX;. Dans un tel exemple, a DIGITpourrait être un fragment qui serait utilisé par les jetons INTet HEX.
Bart Kiers
10
Notez que "pauvre" peut sembler un peu dur, mais je n'ai pas trouvé de meilleur mot pour cela ... Désolé! :)
Bart Kiers
1
Vous n'aviez pas l'air dur ... vous aviez raison et hétéro!
asyncwait
2
Surtout, les fragments sont destinés à être utilisés uniquement dans d'autres règles de lexer pour définir d'autres jetons de lexer. Les fragments ne sont pas destinés à être utilisés dans les règles de grammaire (parseur).
djb
1
@BartKiers: pourriez-vous créer une nouvelle réponse incluant votre meilleure réponse.
David Newcomb
18

Selon le livre de références Definitive Antlr4:

Les règles préfixées par fragment ne peuvent être appelées qu'à partir d'autres règles de lexer; ce ne sont pas des jetons à part entière.

en fait, ils amélioreront la lisibilité de vos grammaires.

regardez cet exemple:

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

STRING est un lexer utilisant une règle de fragment comme ESC. Unicode est utilisé dans la règle Esc et Hex est utilisé dans la règle de fragment Unicode. Les règles ESC et UNICODE et HEX ne peuvent pas être utilisées explicitement.

Nastaran Hakimi
la source
10

La référence définitive ANTLR 4 (Page 106) :

Les règles préfixées par fragment ne peuvent être appelées qu'à partir d'autres règles de lexer; ce ne sont pas des jetons à part entière.


Concepts abstraits:

Cas1: (si j'ai besoin rule1, règle2, Règle3 entités ou informations groupe)

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


Cas 2: (si je m'en fiche RULE1, RULE2, RULE3, je me concentre juste sur 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


Case3: (équivaut à Case2, ce qui le rend plus lisible 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 (?)


Différences entre le cas 1 et le cas 2/3?

  1. Les règles du lexer sont équivalentes
  2. Chacune des RULE1 / 2/3 dans Case1 est un groupe de capture, similaire à Regex: (X)
  3. Chacune des RULE1 / 2/3 dans Case3 est un groupe non capturant, similaire à Regex :( ?: X) entrez la description de l'image ici



Voyons un exemple concret.

Objectif: identifier [ABC]+, [DEF]+, [GHI]+jetons

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


Cas 1 et résultats:

Alphabet.g4 (Cas 1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Résultat:

# 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


Cas 2/3 et résultats:

Alphabet.g4 (Cas 2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;

ANYCHAR : . -> skip;

Alphabet.g4 (Cas 3)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Résultat:

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

Avez-vous vu des parties «groupes de capture » et «groupes sans capture» ?




Voyons l'exemple concret2.

Objectif: identifier les nombres octaux / décimaux / hexadécimaux

input.txt

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


Numéro.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()


Résultat:

# 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 vous ajoutez le modificateur « fragment » à DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, vous ne serez pas en mesure de saisir les entités numériques (car ils ne sont pas plus des jetons). Et le résultat sera:

$ 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>)
蔡宗容
la source
8

Cet article de blog a un exemple très clair où fragmentfait une différence significative:

grammar number;  

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

La grammaire reconnaîtra «42» mais pas «7». Vous pouvez le corriger en faisant de digit un fragment (ou en déplaçant DIGIT après INT).

Vesal
la source
1
Le problème ici n'est pas l'absence du mot-clé fragment, mais l'ordre des règles du lexer.
BlackBrain
J'ai utilisé le mot «réparer», mais le but n'est pas de résoudre un problème. J'ai ajouté cet exemple ici car, pour moi, c'était l'exemple le plus simple et le plus utile de ce qui change réellement lors de l'utilisation du fragment de mot-clé.
Vesal
2
Je soutiens simplement que déclarer en DIGITtant que fragment de INTrésout le problème simplement parce que les fragments ne définissent pas de jetons, créant ainsi INTla première règle lexicale. Je suis d'accord avec vous pour dire que c'est un exemple significatif mais (imo) uniquement pour qui sait déjà ce que fragmentsignifie le mot - clé. Je trouve cela quelque peu trompeur pour quelqu'un qui essaie de comprendre pour la première fois l'utilisation correcte des fragments.
BlackBrain
1
Donc, quand j'ai appris cela, j'ai vu beaucoup d'exemples tels que ceux ci-dessus, mais je ne voyais pas vraiment pourquoi il faudrait un mot-clé supplémentaire pour cela. Je ne comprenais pas ce que cela signifiait en pratique. Maintenant, je ne sais pas vraiment quelle serait la bonne réponse à la question initiale. J'ajouterai un commentaire ci-dessus pourquoi je ne suis pas satisfait de la réponse acceptée.
Vesal