ANTLR'de "parça" ne anlama geliyor?


99

ANTLR'de fragman ne anlama geliyor?

Her iki kuralı da gördüm:

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

ve

DIGIT : '0'..'9';

Fark ne?

Yanıtlar:


112

Bir parça, bir şekilde satır içi bir işleve benzer: Dilbilgisini daha okunaklı ve bakımı daha kolay hale getirir.

Bir parça asla bir simge olarak sayılmaz, yalnızca bir dilbilgisini basitleştirmeye yarar.

Düşünmek:

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

Bu örnekte, bir NUMARA ile eşleşmek, "1234", "0xab12" veya "0777" ile eşleşip eşleşmediğine bakılmaksızın her zaman sözcü için bir SAYI döndürür.

3. maddeye bakın


43
fragmentANTLR'de ne anlama geldiği konusunda haklısınız . Ancak verdiğiniz örnek zayıf bir örnektir: bir lexer'in NUMBERonaltılık, ondalık veya sekizlik sayı olabilen bir simge üretmesini istemezsiniz . Bu, NUMBERbelirteci bir üretimde (ayrıştırıcı kuralı) incelemeniz gerektiği anlamına gelir . Sen en iyisi lexer üretim izin verebilir INT, OCTve HEXjeton ve bir üretim kuralı oluşturun: number : INT | OCT | HEX;. Böyle bir örnekte, a DIGIT, simgeleri tarafından kullanılacak bir parça olabilir INTve HEX.
Bart Kiers

10
"Fakir" kelimesinin kulağa biraz sert gelebileceğini unutmayın, ancak bunun için daha iyi bir kelime bulamadım ... Üzgünüm! :)
Bart Kiers

1
Kulağa sert gelmiyordun .. haklı ve dürüsttün!
asyncwait

2
Önemlisi, fragmanların yalnızca diğer lexer kurallarında diğer lexer belirteçlerini tanımlamak için kullanılması amaçlanmıştır. Parçaların dilbilgisi (ayrıştırıcı) kurallarında kullanılması amaçlanmamıştır.
djb

1
@BartKiers: Daha iyi cevabınızı içeren yeni bir cevap oluşturabilir misiniz?
David Newcomb

18

Definitive Antlr4 referans kitabına göre:

Parça ile ön eklenmiş kurallar yalnızca diğer lexer kurallarından çağrılabilir; onlar kendi başlarına birer simge değildir.

aslında gramerlerinizin okunabilirliğini artıracaklar.

şu örneğe bakın:

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

STRING, ESC gibi parça kuralını kullanan bir sözlükçedir. Esc kuralında Unicode, Unicode parça kuralında ise Hex kullanılır. ESC ve UNICODE ve HEX kuralları açıkça kullanılamaz.


10

Kesin ANTLR 4 Referansı (Sayfa 106) :

Parça ile ön eklenmiş kurallar yalnızca diğer lexer kurallarından çağrılabilir; onlar kendi başlarına birer simge değildir.


Soyut Kavramlar:

Durum1: (RULE1, RULE2, RULE3 varlıklarına veya grup bilgilerine ihtiyacım olursa )

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


Örnek 2 : (KURAL1, KURAL2, KURAL3 umursamıyorsam, sadece KURAL0'a odaklanırım)

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: ( Case2'ye eşdeğerdir, Case2'den daha okunabilir kılar )

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


Durum1 ve Durum2 / 3 arasındaki farklar?

  1. Lexer kuralları eşdeğerdir
  2. Durum1'deki KURAL1 / 2 / 3'ün her biri, Regex'e benzer bir yakalama grubudur: (X)
  3. Durum3'teki KURAL1 / 2 / 3'ün her biri, Regex'e benzer şekilde yakalamayan bir gruptur :( ?: X) görüntü açıklamasını buraya girin



Somut bir örnek görelim.

Hedef: tanımlamak [ABC]+, [DEF]+, [GHI]+jetonlar

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


Örnek 1 ve sonuçlar:

Alphabet.g4 (Örnek 1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Sonuç:

# 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


Örnek 2/3 ve sonuçlar:

Alphabet.g4 (Durum2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Alphabet.g4 (Durum3)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Sonuç:

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

"Grupları yakalama" ve "yakalamayan gruplar" bölümlerini gördünüz mü ?




Somut örnek 2'ye bakalım.

Hedef: sekizlik / ondalık / onaltılık sayıları tanımlama

input.txt

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


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


Sonuç:

# 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

Size değiştirici 'parçasını' eklerseniz DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER(bunlar artık belirteçleri olmadıklarından), sayı varlıkları yakalamak mümkün olmayacaktır. Ve sonuç şöyle olacak:

$ 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

Bu blog gönderisinde , fragmentönemli bir fark yaratan çok net bir örnek var :

grammar number;  

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

Dilbilgisi "42" yi tanır ancak "7" yi tanımaz. Basamağı bir parça yaparak (veya INT'den sonra DIGIT'i hareket ettirerek) düzeltebilirsiniz.


1
Buradaki sorun anahtar kelimenin olmaması değil fragment, lexer kurallarının sırasıdır.
Blackrain

"Düzelt" kelimesini kullandım, ancak önemli olan bir sorunu çözmek değil. Bu örneği buraya ekledim çünkü benim için anahtar kelime parçasını kullanırken gerçekte neyin değiştiğine dair en yararlı ve basit örnek buydu .
Vesal

2
Sadece parçaların belirteçleri tanımlamaması ve böylece ilk sözcük kuralını oluşturması nedeniyle DIGITbir parçası olarak bildirmenin INTsorunu çözdüğünü tartışıyorum INT. Bunun anlamlı bir örnek olduğu konusunda size katılıyorum, ancak (imo) yalnızca fragmentanahtar kelimenin ne anlama geldiğini zaten bilenler için . Parçaların doğru kullanımını ilk kez anlamaya çalışan biri için bunu biraz yanıltıcı buluyorum.
Blackrain

1
Bunu öğrenirken yukarıdakiler gibi birçok örnek gördüm, ancak bunun için neden fazladan bir anahtar kelimeye ihtiyaç duyulduğunu tam olarak anlamadım. Pratikte bunun "kendi başlarına belirteç" olmamasının ne anlama geldiğini anlamadım. Şimdi, asıl soruya neyin iyi bir cevap olacağından emin değilim. Kabul edilen cevaptan neden memnun olmadığımı yukarıya bir yorum ekleyeceğim.
Vesal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.