'semantic predicate' ใน ANTLR คืออะไร?


103

เพรดิเคตความหมายใน ANTLR คืออะไร?


3
โปรดทราบว่าเนื่องจากฉันไม่พบแหล่งข้อมูลออนไลน์ที่เหมาะสมที่จะโพสต์สำหรับคนที่ต้องการทราบว่าเพรดิเคตเชิงความหมายคืออะไรฉันจึงตัดสินใจโพสต์คำถามที่นี่ด้วยตัวเอง (ซึ่งฉันจะตอบตัวเองในไม่ช้าเช่นกัน)
Bart Kiers

1
ขอบคุณที่ทำสิ่งนี้ ฉันชอบเวลาที่มีคนตอบคำถามของตัวเองเสมอโดยเฉพาะอย่างยิ่งถ้าพวกเขาถามคำถามโดยเฉพาะเพื่อตอบคำถามนี้
Daniel H

1
อ่านหนังสือ. Ch 11 ของ The Definitive ANTLR 4 Reference อยู่บน Semantic predicates ไม่มีหนังสือ? รับเลย! คุ้มค่ากับเงินทุกบาท
james.garriss

คำตอบ:


169

ANTLR 4

สำหรับเพรดิเคตใน ANTLR 4 ให้ชำระเงินQ & A ของstack overflowเหล่านี้:


ANTLR 3

เพรดิเคตเชิงความหมายเป็นวิธีการบังคับใช้กฎ (ความหมาย) พิเศษกับการดำเนินการทางไวยากรณ์โดยใช้โค้ดธรรมดา

เพรดิเคตเชิงความหมายมี 3 ประเภท:

  • การตรวจสอบความถูกต้องเพรดิเคตเชิงความหมาย
  • เพรดิเคตความหมายที่มีรั้วรอบขอบชิด ;
  • การทำให้เกิดความสับสนในเพรดิเคตเชิงความหมาย

ตัวอย่างไวยากรณ์

สมมติว่าคุณมีบล็อกข้อความที่ประกอบด้วยตัวเลขเท่านั้นคั่นด้วยเครื่องหมายลูกน้ำโดยไม่สนใจช่องว่างสีขาว คุณต้องการแยกวิเคราะห์อินพุตนี้โดยให้แน่ใจว่าตัวเลขมีความยาวไม่เกิน 3 หลัก (ไม่เกิน 999) ไวยากรณ์ต่อไปนี้ ( Numbers.g) จะทำสิ่งนี้:

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();}
  ;

การทดสอบ

สามารถทดสอบไวยากรณ์ด้วยคลาสต่อไปนี้:

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();
    }
}

ทดสอบโดยการสร้าง lexer และ parser รวบรวม.javaไฟล์ทั้งหมดและรันMainคลาส:

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

เมื่อทำเช่นนั้นจะไม่มีการพิมพ์อะไรลงในคอนโซลซึ่งบ่งชี้ว่าไม่มีอะไรผิดพลาด ลองเปลี่ยน:

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

เข้าสู่:

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

และทำแบบทดสอบอีกครั้ง: 777คุณจะเห็นข้อผิดพลาดที่ปรากฏบนคอนโซลขวาหลังจากสตริง


คำทำนายความหมาย

สิ่งนี้นำเราไปสู่เพรดิเคตเชิงความหมาย สมมติว่าคุณต้องการแยกวิเคราะห์ตัวเลขที่มีความยาวระหว่าง 1 ถึง 10 หลัก กฎเช่น:

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
  ;

จะกลายเป็นเรื่องยุ่งยาก เพรดิเคตเชิงความหมายสามารถช่วยลดความซับซ้อนของกฎประเภทนี้


1. การตรวจสอบความหมายของ Predicates

การตรวจสอบความถูกต้องเพรดิเคตเชิงความหมายไม่ได้เป็นอะไรมากไปกว่าบล็อกโค้ดที่ตามด้วยเครื่องหมายคำถาม:

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

ในการแก้ปัญหาข้างต้นโดยใช้เพรดิเคต เชิงความหมายที่ตรวจสอบความถูกต้องให้เปลี่ยนnumberกฎในไวยากรณ์เป็น:

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

ส่วนต่างๆ{ int N = 0; }และ{ N++; }เป็นคำสั่ง Java ธรรมดาซึ่งส่วนแรกจะเริ่มต้นเมื่อตัวแยกวิเคราะห์ "เข้าสู่" numberกฎ เพรดิเคตที่แท้จริงคือ: { N <= 10 }?ซึ่งทำให้ตัวแยกวิเคราะห์โยน FailedPredicateException เมื่อใดก็ตามที่ตัวเลขมีความยาวมากกว่า 10 หลัก

ทดสอบโดยใช้สิ่งต่อไปนี้ANTLRStringStream:

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

ซึ่งไม่มีข้อยกเว้นในขณะที่ข้อยกเว้นต่อไปนี้:

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

2. Gated Semantic Predicates

รั้วรอบขอบชิดความหมายของคำกริยาจะคล้ายกับคำกริยาความหมายการตรวจสอบเพียงรั้วรอบขอบชิดFailedPredicateExceptionรุ่นสร้างข้อผิดพลาดทางไวยากรณ์แทน

ไวยากรณ์ของเพรดิเคตความหมาย gatedคือ:

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

ในการแก้ปัญหาข้างต้นโดยใช้เพรดิเคตgatedเพื่อจับคู่ตัวเลขที่มีความยาวสูงสุด 10 หลักคุณจะต้องเขียน:

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

ทดสอบอีกครั้งกับทั้งสอง:

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

และ:

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

และคุณจะเห็นครั้งสุดท้ายจะแสดงข้อผิดพลาด


3. Disambiguating Semantic Predicates

ประเภทสุดท้ายของเพรดิเคตคือเพรดิเคตเชิงความหมายที่ไม่น่าเชื่อถือซึ่งดูเหมือนเป็นการตรวจสอบเพรดิเคต ( {boolean-expression}?) เล็กน้อย แต่ทำหน้าที่เหมือนเพรดิเคตความหมายแบบ gated มากกว่า (จะไม่มีข้อยกเว้นเกิดขึ้นเมื่อนิพจน์บูลีนประเมินเป็นfalse) คุณสามารถใช้ที่จุดเริ่มต้นของกฎเพื่อตรวจสอบคุณสมบัติบางอย่างของกฎและปล่อยให้ตัวแยกวิเคราะห์ตรงกับกฎดังกล่าวหรือไม่

สมมติว่าไวยากรณ์ตัวอย่างจะสร้างNumberโทเค็น (กฎ lexer แทนที่จะเป็นกฎตัวแยกวิเคราะห์) ซึ่งจะจับคู่ตัวเลขในช่วง 0..999 ตอนนี้ในโปรแกรมแยกวิเคราะห์คุณต้องการสร้างความแตกต่างระหว่างตัวเลขต่ำและสูง (ต่ำ: 0..500 สูง: 501..999) ซึ่งสามารถทำได้โดยใช้เพรดิเคตเชิงความหมายที่ทำให้สับสนซึ่งคุณตรวจสอบโทเค็นถัดไปในสตรีม ( input.LT(1)) เพื่อตรวจสอบว่าโทเค็นต่ำหรือสูง

การสาธิต:

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();}
  ;

หากคุณแยกวิเคราะห์สตริง"123, 999, 456, 700, 89, 0"คุณจะเห็นผลลัพธ์ต่อไปนี้:

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

12
ผู้ชายที่คุณควรพิจารณาเขียนคู่มือสำหรับผู้เริ่มต้นใช้งาน ANTLR: P
Yuri Ghensev

5
@Bart Kiers: กรุณาเขียนหนังสือเกี่ยวกับ ANTLR
santosh singh

2
สำหรับ ANTLR v4, input.LT(1)คือgetCurrentToken()ตอนนี้ :-)
เสี่ยวเจี๋ย

เยี่ยมมาก ... นี่คือคำอธิบายและตัวอย่างที่ละเอียดถี่ถ้วนที่ควรมีในเอกสาร!
Ezekiel Victor

+1. คำตอบนี้ดีกว่าหนังสืออ้างอิง The Definitive ANTLR 4 มาก คำตอบนี้ชี้ให้เห็นถึงแนวคิดพร้อมตัวอย่างที่ดี
asyncwait

11

ฉันใช้การอ้างอิงสั้น ๆ กับเพรดิเคตANTLRบน wincent.com เป็นแนวทาง


6
ใช่ลิงค์ที่ยอดเยี่ยม! แต่อย่างที่คุณพูดถึงอาจเป็นเรื่องยากสำหรับบางคน (ค่อนข้าง) ใหม่กับ ANTLR ฉันแค่หวังว่าคำตอบของฉันจะเป็นมิตรกว่าสำหรับ ANTLR-grass-hopper :)
Bart Kiers
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.