ANTLR: Có một ví dụ đơn giản?


230

Tôi muốn bắt đầu với ANTLR, nhưng sau khi dành vài giờ để xem xét các ví dụ tại trang web antlr.org , tôi vẫn không thể hiểu rõ về ngữ pháp đối với quy trình Java.

Có một số ví dụ đơn giản, một cái gì đó giống như một máy tính bốn thao tác được triển khai với ANTLR đi qua định nghĩa của trình phân tích cú pháp và tất cả các cách để mã nguồn Java?


2
Ví dụ chính xác đó được sử dụng như một hướng dẫn trên trang web của Antlr, lần cuối tôi đã kiểm tra.
Cory Petosky

1
@Cory Petosky: bạn có thể cung cấp liên kết không?
Eli

Tôi vừa đăng những phần đầu tiên của video hướng dẫn về ANTLR. Xem javadude.com/articles/antlr3xtut Hy vọng bạn thấy nó hữu ích!
Scott Stanchfield

2
Tôi cũng chia sẻ tìm kiếm của bạn.
Paul Draper

1
Câu trả lời tốt nhất cho ANTLR 4 là mua cuốn sách của Parr "The Reference Definitive ANTLR 4".
james.garriss

Câu trả lời:


447

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.javaExp.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 {}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ì evalquy 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. ANTLRStringStreamcần thiết để trở thành ANTLRInputStream, giá trị được trả về cần thay đổi từ parser.eval()thành parser.eval().valuevà tôi cần xóa WSmệnh đề ở cuối, bởi vì các giá trị thuộc tính như $channelkhông còn được phép xuất hiện trong các hành động lexer.


1
Trường hợp nào parser.eval()xảy ra? Điều đó không rõ ràng TẠI ĐÂY hoặc trên Wiki ANTLR3!

1
@Jarrod, err, xin lỗi, tôi không thực sự hiểu bạn. evallà một quy tắc phân tích cú pháp trả về a double. Vì vậy, có một eval()phương thức bạn có thể gọi trên một thể hiện của một ExpParser, giống như tôi đã trình bày trong ANTLRDemo.main(...). Sau khi tạo một lexer / trình phân tích cú pháp, chỉ cần mở tệp ExpParser.javavà bạn sẽ thấy rằng có một eval()phương thức trả về a double.
Bart Kiers

@Bart Tôi đã nghiên cứu điều này trong một tuần - đây là ví dụ đầu tiên thực sự chi tiết và đầy đủ để làm việc lần đầu tiên và tôi nghĩ rằng tôi hiểu. Tôi đã gần như bỏ cuộc. Cảm ơn!
Vineel

13

Hướng dẫn lớn ANTLR của Gabriele Tomassetti rất hữu ích

Nó có các ví dụ ngữ pháp, ví dụ về khách truy cập bằng các ngôn ngữ khác nhau (Java, JavaScript, C # và Python) và nhiều thứ khác. Rất khuyến khích.

EDIT: các bài viết hữu ích khác của Gabriele Tomassetti trên ANTLR


Hướng dẫn tuyệt vời!
Manish Patel

Antlr hiện có cpp cũng là ngôn ngữ đích. Có bất kỳ hướng dẫn với ví dụ trên cpp?
cây nho

Một anh chàng đã làm một hướng dẫn cho ANTLR trong C ++ tomassetti.me/getting-started-antlr-cpp Tôi đoán bạn sẽ tìm thấy những gì bạn đang tìm kiếm ở đây hoặc trong hướng dẫn lớn
solo

7

Đối với Antlr 4, quy trình tạo mã java như sau: -

java -cp antlr-4.5.3-complete.jar org.antlr.v4.Tool Exp.g

Cập nhật tên jar của bạn trong classpath phù hợp.


2

Tại https://github.com/BITPlan/com.bitplan.antlr, bạn sẽ tìm thấy thư viện java ANTLR với một số lớp trợ giúp hữu ích và một vài ví dụ hoàn chỉnh. Nó đã sẵn sàng để được sử dụng với maven và nếu bạn thích nhật thực và maven.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/Ex/Exp.g4

là một ngôn ngữ Biểu thức đơn giản có thể thực hiện nhân và thêm các thao tác. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java có các bài kiểm tra đơn vị tương ứng cho nó.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 là một trình phân tích cú pháp IRI đã được chia thành ba phần:

  1. ngữ pháp phân tích cú pháp
  2. ngữ pháp lexer
  3. ngữ pháp nhập khẩu

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java có các bài kiểm tra đơn vị cho nó.

Cá nhân tôi thấy đây là phần khó nhất để làm đúng. Xem http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

https://github.com/BITPlan/com.bitplan.antlr/tree/master/src/main/antlr4/com/bitplan/expr

chứa thêm ba ví dụ đã được tạo cho vấn đề hiệu năng của ANTLR4 trong phiên bản trước. Trong khi đó, sự cố này đã được khắc phục dưới dạng thử nghiệm https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java cho thấy.


2

phiên bản 4.7.1 hơi khác một chút: để nhập:

import org.antlr.v4.runtime.*;

cho phân khúc chính - lưu ý CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
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.