Trước tiên, bạn tạo 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 cách sử dụng 4 toán tử toán cơ bản: +, -, * và /. Bạn cũng có thể nhóm các biểu thức bằng dấu ngoặc đơn. Lưu ý rằng ngữ pháp này chỉ là một văn bản rất cơ bản: nó không xử lý các toán tử đơn nhất (trừ: -1 + 9) hoặc số thập phân như 0,99 (không có số đầu), để đặt tên chỉ là hai thiếu sót. Đây chỉ là một ví dụ bạn có thể làm việc trên chính mình.
Đây là nội dung của file 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 Parser bắt đầu với một lá thư chữ thường, và các quy tắc lexer bắt đầu với một chữ cái viết hoa)
Sau tạo ngữ pháp, bạn sẽ muốn tạo một trình phân tích cú pháp và lexer từ nó. Tải xuống ANTLR jar và lưu trữ nó 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 vỏ/command prompt 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, và các tập tin ExpLexer.java, ExpParser.java và Exp.tokens bây giờ sẽ được tạo ra.
Để xem nếu nó tất cả các hoạt động đúng, tạo lớp thử nghiệm 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ẻ tốt, không có gì được in trên bảng đ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 để ngữ pháp để phân tích cú pháp thực sự làm điều gì đó hữu ích . Việc 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ả quy tắc trình phân tích cú pháp trong tệp ngữ pháp phải trả lại giá trị kép nguyên thủy.Bạn có thể làm điều đó bằng cách thêm returns [double value]
sau mỗi quy tắc:
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
('+' multiplyExp
| '-' multiplyExp
)*
;
// ...
mà cần ít lời giải thích: mọi quy tắc được dự kiến sẽ trả về một giá trị gấp đôi. Bây giờ để "tương tác" với giá trị trả về double value
(đó là KHÔNG bên trong một khối mã Java đơn giản {...}
) từ bên trong một khối mã, bạn sẽ cần phải thêm một dấu đô la 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); }
;
// ...
Dưới đây là ngữ pháp nhưng bây giờ với mã Java thêm:
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à kể từ khi quy tắc eval
chúng tôi bây giờ trả về một đôi, thay đổi ANTLRDemo.java của bạn vào đâ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
}
}
Again (lại) tạo ra một lexer tươi và phân tích cú pháp 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à giải thích rất ngắn gọn. Tôi khuyến khích bạn duyệt qua ANTLR wiki 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 mới đăng.
Chúc may mắn!
EDIT:
This post cho thấy làm thế nào để mở rộng ví dụ trên để cho một Map<String, Double>
thể được cung cấp chứa các biến trong biểu thức cung cấp.
Và điều này Q&A chứng tỏ cách tạo trình phân tích cú pháp biểu thức đơn giản và bộ đánh giá bằng cách sử dụng ANTLR4.
Để làm cho mã này hoạt động với phiên bản Antlr hiện tại (tháng 6 năm 2014), tôi cần thực hiện một vài thay đổi. ANTLRStringStream
cần để trở thành ANTLRInputStream
, giá trị trả lại cần thiết để thay đổi từ parser.eval()
thành parser.eval().value
và tôi cần xóa mệnh đề WS
ở cuố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.
Ví dụ chính xác đó được sử dụng làm hướng dẫn trên trang web của Antlr, cuối cùng tôi đã kiểm tra. –
@Cory Petosky: bạn có thể cung cấp liên kết này không? – Eli
Tôi cũng chia sẻ tìm kiếm của bạn. –