“Phân mảnh” trong ANTLR có nghĩa là gì?


99

Những gì hiện đoạn trung bình trong ANTLR?

Tôi đã thấy cả hai quy tắc:

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

DIGIT : '0'..'9';

Sự khác biệt là gì?

Câu trả lời:


110

Một phân đoạn hơi giống với một hàm nội tuyến: Nó làm cho ngữ pháp dễ đọc hơn và dễ duy trì hơn.

Một đoạn sẽ không bao giờ được tính là một mã thông báo, nó chỉ dùng để đơn giản hóa ngữ pháp.

Xem xét:

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

Trong ví dụ này, việc so khớp NUMBER sẽ luôn trả lại NUMBER cho lexer, bất kể nó có khớp với "1234", "0xab12" hay "0777" hay không.

Xem mục 3


43
Bạn nói đúng về những gì fragmentcó nghĩa trong ANTLR. Nhưng ví dụ bạn đưa ra là một ví dụ kém: bạn không muốn lexer tạo ra một NUMBERmã thông báo có thể là số hex, số thập phân hoặc số bát phân. Điều đó có nghĩa là bạn cần phải kiểm tra NUMBERmã thông báo trong quá trình sản xuất (quy tắc phân tích cú pháp). Bạn có thể cho tốt hơn lexer sản INT, OCTHEXthẻ và tạo ra một quy tắc sản xuất: number : INT | OCT | HEX;. Trong một ví dụ như vậy, một DIGITcó thể là một phân đoạn sẽ được sử dụng bởi các mã thông báo INTHEX.
Bart Kiers

10
Lưu ý rằng "nghèo nàn" có thể nghe hơi khắc nghiệt, nhưng tôi không thể tìm thấy từ nào tốt hơn cho nó ... Xin lỗi! :)
Bart Kiers

1
Bạn không nghe có vẻ gay gắt .. bạn đã đúng và thẳng thắn!
asyncwait

2
Quan trọng là, các phân đoạn chỉ được sử dụng trong các quy tắc lexer khác để xác định các thẻ lexer khác. Các phân đoạn không được sử dụng trong các quy tắc ngữ pháp (trình phân tích cú pháp).
djb

1
@BartKiers: bạn có thể tạo một câu trả lời mới bao gồm cả câu trả lời hay hơn của bạn.
David Newcomb

18

Theo sách tham khảo Definitive Antlr4:

Các quy tắc có tiền tố là phân mảnh chỉ có thể được gọi từ các quy tắc lexer khác; chúng không phải là token theo đúng nghĩa của chúng.

thực sự chúng sẽ cải thiện khả năng đọc ngữ pháp của bạn.

nhìn vào ví dụ này:

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

STRING là một lexer sử dụng quy tắc phân đoạn như ESC .Unicode được sử dụng trong quy tắc Esc và Hex được sử dụng trong quy tắc phân đoạn Unicode. Các quy tắc ESC và UNICODE và HEX không thể được sử dụng một cách rõ ràng.


10

Tài liệu tham khảo ANTLR 4 cuối cùng (Trang 106) :

Các quy tắc có tiền tố là phân mảnh chỉ có thể được gọi từ các quy tắc lexer khác; chúng không phải là token theo đúng nghĩa của chúng.


Khái niệm trừu tượng:

Trường hợp1: (nếu tôi cần RULE1, RULE2, RULE3 thực thể hoặc thông tin nhóm)

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


Trường hợp 2: (nếu tôi không quan tâm RULE1, RULE2, RULE3, tôi chỉ tập trung vào 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: (tương đương với Case2, làm cho nó dễ đọc hơn 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 (?)


Sự khác biệt giữa Case1 và Case2 / 3?

  1. Các quy tắc lexer là tương đương
  2. Mỗi RULE1 / 2/3 trong Case1 là một nhóm bắt, tương tự như Regex: (X)
  3. Mỗi RULE1 / 2/3 trong Case3 là một nhóm không bắt, tương tự như Regex :( ?: X) nhập mô tả hình ảnh ở đây



Hãy xem một ví dụ cụ thể.

Mục tiêu: xác định [ABC]+, [DEF]+, [GHI]+thẻ

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


Trường hợp1 và kết quả:

Bảng chữ cái.g4 (Trường hợp1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Kết quả:

# 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


Trường hợp 2/3 và kết quả:

Bảng chữ cái.g4 (Trường hợp2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

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

ANYCHAR : . -> skip;

Bảng chữ cái.g4 (Trường hợp 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;

Kết quả:

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

Bạn có thấy phần "nhóm chụp""nhóm không chụp" không?




Hãy xem ví dụ cụ thể2.

Mục tiêu: xác định số bát phân / thập phân / thập lục phân

input.txt

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


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


Kết quả:

# 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

Nếu bạn thêm modifier 'đoạn' để DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER, bạn sẽ không thể nắm bắt các đối tượng số (vì họ không phải mã thông báo nữa). Và kết quả sẽ là:

$ 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

Bài đăng trên blog này có một ví dụ rất rõ ràng nơi fragmenttạo ra sự khác biệt đáng kể:

grammar number;  

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

Ngữ pháp sẽ nhận ra '42' nhưng không nhận ra '7'. Bạn có thể sửa nó bằng cách biến chữ số thành một đoạn (hoặc di chuyển DIGIT sau INT).


1
Vấn đề ở đây không phải là sự vắng mặt của từ khóa fragment, mà là thứ tự của các quy tắc lexer.
Blackrain

Tôi đã sử dụng từ "sửa chữa", nhưng vấn đề không phải là để khắc phục sự cố. Tôi đã thêm ví dụ này vào đây vì đối với tôi, đây là ví dụ đơn giản và hữu ích nhất về những gì thực sự thay đổi khi sử dụng phân đoạn từ khóa.
Vesal ngày

2
Tôi chỉ lập luận rằng việc khai báo DIGITnhư một đoạn mã INTgiải quyết được vấn đề chỉ vì các đoạn mã không xác định mã thông báo, do đó tạo ra INTquy tắc từ vựng đầu tiên. Tôi đồng ý với bạn rằng đây là một ví dụ có ý nghĩa nhưng (imo) chỉ dành cho những người đã biết fragmenttừ khóa nghĩa là gì. Tôi thấy nó hơi sai lầm đối với những người đang cố gắng tìm ra cách sử dụng đúng các mảnh vỡ lần đầu tiên.
Blackrain

1
Vì vậy, khi tôi đang học điều này, tôi đã thấy rất nhiều ví dụ như những ví dụ ở trên, nhưng tôi không hiểu tại sao người ta cần thêm một từ khóa cho điều này. Tôi không hiểu điều này không phải là "token theo đúng nghĩa của chúng" có nghĩa là gì trong thực tế. Bây giờ, tôi thực sự không chắc đâu sẽ là câu trả lời tốt cho câu hỏi ban đầu. Tôi sẽ thêm một nhận xét ở trên tại sao tôi không hài lòng với câu trả lời được chấp nhận.
Vesal ngày
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.