เพรดิเคตความหมายใน ANTLR คืออะไร?
เพรดิเคตความหมายใน ANTLR คืออะไร?
คำตอบ:
สำหรับเพรดิเคตใน ANTLR 4 ให้ชำระเงินQ & A ของstack overflowเหล่านี้:
เพรดิเคตเชิงความหมายเป็นวิธีการบังคับใช้กฎ (ความหมาย) พิเศษกับการดำเนินการทางไวยากรณ์โดยใช้โค้ดธรรมดา
เพรดิเคตเชิงความหมายมี 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
;
จะกลายเป็นเรื่องยุ่งยาก เพรดิเคตเชิงความหมายสามารถช่วยลดความซับซ้อนของกฎประเภทนี้
การตรวจสอบความถูกต้องเพรดิเคตเชิงความหมายไม่ได้เป็นอะไรมากไปกว่าบล็อกโค้ดที่ตามด้วยเครื่องหมายคำถาม:
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");
รั้วรอบขอบชิดความหมายของคำกริยาจะคล้ายกับคำกริยาความหมายการตรวจสอบเพียงรั้วรอบขอบชิด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");
และคุณจะเห็นครั้งสุดท้ายจะแสดงข้อผิดพลาด
ประเภทสุดท้ายของเพรดิเคตคือเพรดิเคตเชิงความหมายที่ไม่น่าเชื่อถือซึ่งดูเหมือนเป็นการตรวจสอบเพรดิเคต ( {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
input.LT(1)
คือgetCurrentToken()
ตอนนี้ :-)
ฉันใช้การอ้างอิงสั้น ๆ กับเพรดิเคตANTLRบน wincent.com เป็นแนวทาง