Lưu ý : câu trả lời này là dành cho ANTLR3 ! Nếu bạn đang tìm kiếm một ví dụ ANTLR4 , thì Hỏi & Đáp này trình bày cách tạo trình phân tích cú pháp biểu thức đơn giản và trình đánh giá bằng ANTLR4 .
Trước tiên bạn tạo một ngữ pháp. Dưới đây là một ngữ pháp nhỏ mà bạn có thể sử dụng để đánh giá các biểu thức được xây dựng bằng 4 toán tử toán học cơ bản: +, -, * và /. Bạn cũng có thể nhóm biểu thức bằng cách sử dụng dấu ngoặc đơn.
Lưu ý rằng ngữ pháp này chỉ là một ngữ pháp rất cơ bản: nó không xử lý các toán tử đơn nguyên (trừ trong: -1 + 9) hoặc các số thập phân như 0,99 (không có số dẫn đầu), chỉ nêu ra hai thiếu sót. Đây chỉ là một ví dụ bạn có thể tự làm việc.
Đây là nội dung của tệp ngữ pháp Exp.g :
grammar Exp;
/* This will be the entry point of our parser. */
eval
: additionExp
;
/* Addition and subtraction have the lowest precedence. */
additionExp
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
/* Multiplication and division have a higher precedence. */
multiplyExp
: atomExp
( '*' atomExp
| '/' atomExp
)*
;
/* An expression atom is the smallest part of an expression: a number. Or
when we encounter parenthesis, we're making a recursive call back to the
rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
: Number
| '(' additionExp ')'
;
/* A number: can be an integer value, or a decimal value */
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
/* We're going to ignore all white space characters */
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
(Quy tắc phân tích cú pháp bắt đầu bằng chữ in thường và quy tắc từ vựng bắt đầu bằng chữ in hoa)
Sau khi tạo ngữ pháp, bạn sẽ muốn tạo một trình phân tích cú pháp và từ vựng từ nó. Tải xuống bình ANTLR và lưu trữ trong cùng thư mục với tệp ngữ pháp của bạn.
Thực hiện lệnh sau trên dấu nhắc shell / lệnh của bạn:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Nó không nên tạo ra bất kỳ thông báo lỗi nào và các tệp ExpLexer.java , ExpParser.java và Exp.tokens sẽ được tạo ngay bây giờ.
Để xem tất cả có hoạt động đúng không, hãy tạo lớp kiểm tra này:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
parser.eval();
}
}
và biên dịch nó:
// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java
và sau đó chạy nó:
// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo
Nếu mọi việc suôn sẻ, không có gì được in ra bàn điều khiển. Điều này có nghĩa là trình phân tích cú pháp không tìm thấy bất kỳ lỗi nào. Khi bạn thay đổi "12*(5-6)"
vào "12*(5-6"
và sau đó biên dịch lại và chạy nó, có nên được in như sau:
line 0:-1 mismatched input '<EOF>' expecting ')'
Được rồi, bây giờ chúng tôi muốn thêm một chút mã Java vào ngữ pháp để trình phân tích cú pháp thực sự làm một cái gì đó hữu ích. Thêm mã có thể được thực hiện bằng cách đặt {
và }
bên trong ngữ pháp của bạn với một số mã Java đơn giản bên trong nó.
Nhưng trước tiên: tất cả các quy tắc trình phân tích cú pháp trong tệp ngữ pháp sẽ trả về giá trị kép nguyên thủy. Bạn có thể làm điều đó bằng cách thêm vào returns [double value]
sau mỗi quy tắc:
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
// ...
cần giải thích ít: mọi quy tắc dự kiến sẽ trả về giá trị gấp đôi. Bây giờ để "tương tác" với giá trị trả về double value
(KHÔNG nằm trong khối mã Java đơn giản {...}
) từ bên trong khối mã, bạn sẽ cần thêm ký hiệu đô la ở phía trước value
:
grammar Exp;
/* This will be the entry point of our parser. */
eval returns [double value]
: additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
;
// ...
Đây là ngữ pháp nhưng bây giờ với mã Java được thêm vào:
grammar Exp;
eval returns [double value]
: exp=additionExp {$value = $exp.value;}
;
additionExp returns [double value]
: m1=multiplyExp {$value = $m1.value;}
( '+' m2=multiplyExp {$value += $m2.value;}
| '-' m2=multiplyExp {$value -= $m2.value;}
)*
;
multiplyExp returns [double value]
: a1=atomExp {$value = $a1.value;}
( '*' a2=atomExp {$value *= $a2.value;}
| '/' a2=atomExp {$value /= $a2.value;}
)*
;
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| '(' exp=additionExp ')' {$value = $exp.value;}
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
và vì eval
quy tắc của chúng tôi hiện trả lại gấp đôi, hãy thay đổi ANTLRDemo.java của bạn thành điều này:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
System.out.println(parser.eval()); // print the value
}
}
Một lần nữa (lại) tạo ra một từ vựng và trình phân tích cú pháp mới từ ngữ pháp của bạn (1), biên dịch tất cả các lớp (2) và chạy ANTLRDemo (3):
// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java // 2
java -cp .:antlr-3.2.jar ANTLRDemo // 3
// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java // 2
java -cp .;antlr-3.2.jar ANTLRDemo // 3
và bây giờ bạn sẽ thấy kết quả của biểu thức 12*(5-6)
được in trên bảng điều khiển của bạn!
Một lần nữa: đây là một lời giải thích rất ngắn gọn. Tôi khuyến khích bạn duyệt wiki ANTLR và đọc một số hướng dẫn và / hoặc chơi một chút với những gì tôi vừa đăng.
Chúc may mắn!
BIÊN TẬP:
Bài đăng này cho thấy cách mở rộng ví dụ trên để Map<String, Double>
có thể cung cấp một biến chứa các biến trong biểu thức được cung cấp.
Để mã này hoạt động với phiên bản hiện tại của Antlr (tháng 6 năm 2014) tôi cần thực hiện một vài thay đổi. ANTLRStringStream
cần thiết để trở thành ANTLRInputStream
, giá trị được trả về cần thay đổi từ parser.eval()
thành parser.eval().value
và tôi cần xóa WS
mệnh đề ở cuối, bởi vì các giá trị thuộc tính như $channel
không còn được phép xuất hiện trong các hành động lexer.