'Vị ngữ ngữ nghĩa' trong ANTLR là gì?


103

Vị ngữ ngữ nghĩa trong ANTLR là gì?


3
Lưu ý rằng vì tôi không thể tìm thấy một nguồn trực tuyến phù hợp để đăng cho những người muốn biết vị ngữ ngữ nghĩa là gì, tôi đã quyết định tự đăng câu hỏi ở đây (tôi cũng sẽ tự trả lời ngay sau đây).
Bart Kiers

1
Cảm ơn vì đã làm điều này; Tôi luôn thích khi mọi người trả lời câu hỏi của chính họ, đặc biệt nếu họ đặt câu hỏi cụ thể để trả lời theo cách này.
Daniel H

1
Đọc sách. Chương 11 của Tham chiếu ANTLR 4 Cuối cùng là về các vị từ ngữ nghĩa. Không có cuốn sách? Hiểu rồi! Đáng giá từng đồng.
james.garriss

Câu trả lời:


169

ANTLR 4

Đối với các vị từ trong ANTLR 4, hãy kiểm tra các câu hỏi & đáp về tràn ngăn xếp sau:


ANTLR 3

Vị từ ngữ nghĩa là một cách để thực thi các quy tắc bổ sung (ngữ nghĩa) dựa trên các hành động ngữ pháp bằng cách sử dụng mã thuần túy.

Có 3 loại vị từ ngữ nghĩa:

  • xác nhận các vị từ ngữ nghĩa;
  • gatedvị ngữ ngữ nghĩa ;
  • phân biệt các vị ngữ ngữ nghĩa.

Ngữ pháp mẫu

Giả sử bạn có một khối văn bản chỉ bao gồm các số được phân tách bằng dấu phẩy, bỏ qua bất kỳ khoảng trắng nào. Bạn muốn phân tích cú pháp đầu vào này để đảm bảo rằng các số có nhiều nhất là 3 chữ số "dài" (nhiều nhất là 999). Ngữ pháp sau ( Numbers.g) sẽ thực hiện một điều như vậy:

grammar Numbers;

// entry point of this parser: it parses an input string consisting of at least 
// one number, optionally followed by zero or more comma's and numbers
parse
  :  number (',' number)* EOF
  ;

// matches a number that is between 1 and 3 digits long
number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

// matches a single digit
Digit
  :  '0'..'9'
  ;

// ignore spaces
WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;

Thử nghiệm

Ngữ pháp có thể được kiểm tra với lớp sau:

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");
        NumbersLexer lexer = new NumbersLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        NumbersParser parser = new NumbersParser(tokens);
        parser.parse();
    }
}

Kiểm tra nó bằng cách tạo lexer và parser, biên dịch tất cả .javacác tệp và chạyMain lớp:

java -cp antlr-3.2.jar org.antlr.Tool Numbers.g
javac -cp antlr-3.2.jar * .java
java -cp .: antlr-3.2.jar Main

Khi làm như vậy, không có gì được in ra bảng điều khiển, điều này cho thấy rằng không có gì sai. Hãy thử thay đổi:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");

thành:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7777   , 89");

và thực hiện kiểm tra lại: bạn sẽ thấy lỗi xuất hiện trên bảng điều khiển ngay sau chuỗi 777.


Dự đoán ngữ nghĩa

Điều này đưa chúng ta đến các vị từ ngữ nghĩa. Giả sử bạn muốn phân tích cú pháp các số có độ dài từ 1 đến 10 chữ số. Một quy tắc như:

number
  :  Digit Digit Digit Digit Digit Digit Digit Digit Digit Digit
  |  Digit Digit Digit Digit Digit Digit Digit Digit Digit
     /* ... */
  |  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

sẽ trở nên cồng kềnh. Các vị từ ngữ nghĩa có thể giúp đơn giản hóa loại quy tắc này.


1. Xác thực các dự đoán ngữ nghĩa

Một vị từ ngữ nghĩa xác thực không gì khác hơn là một khối mã được theo sau bởi một dấu chấm hỏi:

RULE { /* a boolean expression in here */ }?

Để giải quyết vấn đề ở trên bằng cách sử dụng một vị từ ngữ nghĩa xác thực , hãy thay đổi numberquy tắc trong ngữ pháp thành:

number
@init { int N = 0; }
  :  (Digit { N++; } )+ { N <= 10 }?
  ;

Các phần { int N = 0; }{ N++; }là các câu lệnh Java thuần túy mà phần đầu tiên được khởi tạo khi trình phân tích cú pháp "nhập" numberquy tắc. Vị từ thực tế là:, { N <= 10 }?khiến trình phân tích cú pháp ném ra FailedPredicateException bất cứ khi nào một số dài hơn 10 chữ số.

Kiểm tra nó bằng cách sử dụng như sau ANTLRStringStream:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 

mà không tạo ra ngoại lệ, trong khi sau đây không có ngoại lệ:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

2. Gated Semantic Predicates

Một vị từ ngữ nghĩa xác thực tương tự như một vị từ ngữ nghĩa xác thực , chỉ phiên bản gated tạo ra lỗi cú pháp thay vì a FailedPredicateException.

Cú pháp của một vị từ ngữ nghĩa gated là:

{ /* a boolean expression in here */ }?=> RULE

Thay vào đó, để giải quyết vấn đề trên bằng cách sử dụng các vị từ được điều chỉnh để khớp các số có độ dài lên đến 10 chữ số, bạn sẽ viết:

number
@init { int N = 1; }
  :  ( { N <= 10 }?=> Digit { N++; } )+
  ;

Kiểm tra lại với cả hai:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 

và:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

và bạn sẽ thấy lần cuối cùng sẽ xuất hiện một lỗi.


3. Phân biệt các dự đoán ngữ nghĩa

Loại vị từ cuối cùng là một vị từ ngữ nghĩa phân biệt , trông giống như một vị từ xác thực ( {boolean-expression}?), nhưng hoạt động giống như một vị từ ngữ nghĩa định nghĩa (không có ngoại lệ nào được ném ra khi biểu thức boolean đánh giá thành false). Bạn có thể sử dụng nó ở đầu quy tắc để kiểm tra một số thuộc tính của quy tắc và để trình phân tích cú pháp có khớp với quy tắc đã nói hay không.

Giả sử ngữ pháp ví dụ tạo Numbermã thông báo (quy tắc lexer thay vì quy tắc phân tích cú pháp) sẽ khớp với các số trong phạm vi 0..999. Bây giờ trong trình phân tích cú pháp, bạn muốn phân biệt giữa số thấp và số cao (thấp: 0..500, cao: 501..999). Điều này có thể được thực hiện bằng cách sử dụng một vị từ ngữ nghĩa phân biệt nơi bạn kiểm tra mã thông báo tiếp theo trong luồng ( input.LT(1)) để kiểm tra xem nó thấp hay cao.

Bản demo:

grammar Numbers;

parse
  :  atom (',' atom)* EOF
  ;

atom
  :  low  {System.out.println("low  = " + $low.text);}
  |  high {System.out.println("high = " + $high.text);}
  ;

low
  :  {Integer.valueOf(input.LT(1).getText()) <= 500}? Number
  ;

high
  :  Number
  ;

Number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

fragment Digit
  :  '0'..'9'
  ;

WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;

Nếu bây giờ bạn phân tích cú pháp chuỗi "123, 999, 456, 700, 89, 0", bạn sẽ thấy kết quả sau:

low  = 123
high = 999
low  = 456
high = 700
low  = 89
low  = 0

12
Man bạn thực sự nên xem xét văn bản hướng dẫn của một người mới bắt đầu để ANTLR: P
Yuri Ghensev

5
@Bart Kiers: Vui lòng viết một cuốn sách trên ANTLR
santosh singh

2
Đối với ANTLR v4, bây giờ input.LT(1)getCurrentToken():-)
Xiao Jia

Tuyệt vời ... Đây là loại giải thích đầy đủ và các ví dụ nên có trong tài liệu!
Ezekiel Victor

+1. Câu trả lời này tốt hơn nhiều so với cuốn sách Tham khảo The Definitive ANTLR 4. Câu trả lời này nằm ngay trên khái niệm với các ví dụ hay.
asyncwait

11

Tôi luôn sử dụng tham chiếu ngắn gọn đến các vị từ ANTLR trên wincent.com làm hướng dẫn của mình.


6
Vâng, một liên kết tuyệt vời! Tuy nhiên, như bạn đã đề cập, có thể hơi khó đối với một người (tương đối) mới đối với ANTLR. Tôi chỉ hy vọng câu trả lời của tôi (một chút) thân thiện hơn với ANTLR-rầy cỏ. :)
Bart Kiers
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.