Apa arti "fragmen" di ANTLR?

99

Apa arti fragmen di ANTLR?

Saya telah melihat kedua aturan tersebut:

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

dan

DIGIT : '0'..'9';

Apa bedanya?

Oscar Mederos
sumber

Jawaban:

110

Fragmen agak mirip dengan fungsi sebaris: Fragmen membuat tata bahasa lebih mudah dibaca dan dikelola.

Fragmen tidak akan pernah dihitung sebagai token, ini hanya berfungsi untuk menyederhanakan tata bahasa.

Mempertimbangkan:

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

Dalam contoh ini, mencocokkan NOMOR akan selalu mengembalikan NOMOR ke lexer, terlepas dari apakah itu cocok dengan "1234", "0xab12", atau "0777".

Lihat item 3

persekutuan
sumber
43
Anda benar tentang apa fragmentartinya di ANTLR. Tetapi contoh yang Anda berikan salah: Anda tidak ingin lexer menghasilkan NUMBERtoken yang dapat berupa bilangan heksadesimal, desimal, atau oktal. Itu berarti Anda perlu memeriksa NUMBERtoken dalam produksi (aturan parser). Anda bisa lebih baik membiarkan lexer hasil INT, OCTdan HEXtoken dan membuat aturan produksi: number : INT | OCT | HEX;. Dalam contoh seperti itu, a DIGITbisa menjadi fragmen yang akan digunakan oleh token INTdan HEX.
Bart Kiers
10
Perhatikan bahwa "buruk" mungkin terdengar agak kasar, tetapi saya tidak dapat menemukan kata yang lebih baik untuk itu ... Maaf! :)
Bart Kiers
1
Anda tidak terdengar kasar .. Anda benar dan jujur!
asyncwait
2
Yang penting, fragmen dimaksudkan untuk digunakan hanya dalam aturan lexer lain untuk mendefinisikan token lexer lainnya. Fragmen tidak dimaksudkan untuk digunakan dalam aturan tata bahasa (parser).
djb
1
@BartKiers: bisakah Anda membuat jawaban baru termasuk jawaban Anda yang lebih baik.
David Newcomb
18

Menurut buku referensi Definitive Antlr4:

Aturan yang diawali dengan fragmen hanya bisa dipanggil dari aturan lexer lain; mereka bukanlah token dengan sendirinya.

sebenarnya mereka akan meningkatkan keterbacaan tata bahasa Anda.

lihat contoh ini:

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

STRING adalah lexer yang menggunakan aturan fragmen seperti ESC. Unicode digunakan dalam aturan Esc dan Hex digunakan dalam aturan fragmen Unicode. Aturan ESC dan UNICODE dan HEX tidak dapat digunakan secara eksplisit.

Nastaran Hakimi
sumber
10

Referensi Definitif ANTLR 4 (Halaman 106) :

Aturan yang diawali dengan fragmen hanya bisa dipanggil dari aturan lexer lain; mereka bukanlah token dengan sendirinya.


Konsep abstrak:

Kasus1: (jika saya membutuhkan RULE1, RULE2, entitas RULE3 atau info grup)

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


Case2: (jika saya tidak peduli RULE1, RULE2, RULE3, saya hanya fokus pada 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: (setara dengan Case2, membuatnya lebih mudah dibaca daripada 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 (?)


Perbedaan antara Case1 dan Case2 / 3?

  1. Aturan lexer setara
  2. Masing-masing RULE1 / 2/3 di Case1 adalah grup penangkap, mirip dengan Regex: (X)
  3. Setiap RULE1 / 2/3 di Case3 adalah grup non-capturing, mirip dengan Regex :( ?: X) masukkan deskripsi gambar di sini



Mari kita lihat contoh konkretnya.

Tujuan: mengidentifikasi [ABC]+, [DEF]+, [GHI]+token

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


Kasus1 dan hasil:

Alphabet.g4 (Kasus1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Hasil:

# 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


Kasus2 / 3 dan hasil:

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;

Hasil:

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

Apakah Anda melihat bagian "grup penangkap" dan "grup bukan penangkap" ?




Mari kita lihat contoh konkret2.

Sasaran: mengidentifikasi bilangan oktal / desimal / heksadesimal

input.txt

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


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


Hasil:

# 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

Jika Anda menambahkan modifier 'fragmen' untuk DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, Anda tidak akan dapat menangkap entitas jumlah (karena mereka tidak token lagi). Dan hasilnya adalah:

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

Posting blog ini memiliki contoh yang sangat jelas yang fragmentmembuat perbedaan yang signifikan:

grammar number;  

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

Tata bahasanya akan mengenali '42' tapi bukan '7'. Anda dapat memperbaikinya dengan membuat digit menjadi fragmen (atau memindahkan DIGIT setelah INT).

Vesal
sumber
1
Masalahnya di sini bukanlah ketiadaan kata kunci fragment, tetapi urutan aturan lexer.
BlackBrain
Saya menggunakan kata "perbaiki", tetapi intinya bukan untuk memperbaiki masalah. Saya menambahkan contoh ini di sini karena, bagi saya, ini adalah contoh yang paling membantu dan sederhana tentang apa yang sebenarnya berubah saat menggunakan fragmen kata kunci.
Vesal
2
Saya hanya berpendapat bahwa mendeklarasikan DIGITsebagai fragmen dari INTmemecahkan masalah hanya karena fragmen tidak mendefinisikan token, sehingga membuat INTaturan leksikal pertama. Saya setuju dengan Anda bahwa ini adalah contoh yang berarti tetapi (imo) hanya untuk yang sudah tahu apa arti fragmentkata kunci itu. Saya merasa agak menyesatkan bagi seseorang yang mencoba mencari tahu penggunaan fragmen yang benar untuk pertama kalinya.
BlackBrain
1
Jadi ketika saya mempelajari ini, saya melihat banyak contoh seperti yang di atas, tetapi saya tidak begitu mengerti mengapa seseorang memerlukan kata kunci tambahan untuk ini. Saya tidak mengerti apa yang dimaksud dengan "token dalam dirinya sendiri" dalam praktiknya. Sekarang, saya tidak yakin apa jawaban yang bagus untuk pertanyaan awal. Saya akan menambahkan komentar di atas mengapa saya tidak puas dengan jawaban yang diterima.
Vesal