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';
và
DIGIT : '0'..'9';
Sự khác biệt là gì?
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';
và
DIGIT : '0'..'9';
Sự khác biệt là gì?
Câu trả lời:
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.
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.
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.
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 (?)
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()
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
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" và "nhóm không chụp" không?
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>)
Bài đăng trên blog này có một ví dụ rất rõ ràng nơi fragment
tạ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).
fragment
, mà là thứ tự của các quy tắc lexer.
DIGIT
như một đoạn mã INT
giả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 INT
quy 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 fragment
từ 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.
fragment
có 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ộtNUMBER
mã 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 traNUMBER
mã 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ảnINT
,OCT
vàHEX
thẻ 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ộtDIGIT
có thể là một phân đoạn sẽ được sử dụng bởi các mã thông báoINT
vàHEX
.