Що таке семантичний предикат у ANTLR?
Що таке семантичний предикат у ANTLR?
Відповіді:
Для присудків в ANTLR 4, перевірка цих стек переповнення Q & елементів а:
семантичний предикат є способом забезпечити додаткові (семантичні) правила про дії граматики з допомогою звичайного коду.
Існує 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();
}
}
Перевірте його, генеруючи лексери та аналізатори, компілюючи всі .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
.
Синтаксис значущого предиката, що перебуває у відкритому вигляді, є:
{ /* a boolean expression in here */ }?=> RULE
Для того, щоб замість того, щоб вирішити дану проблему з допомогою стробірованний предикатів , щоб відповідати числам до 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}?
), але діє більше, як семантичний предикат , що перебуває у відкритому форматі (виняток не кидається, коли булевий вираз оцінюється на false
). Ви можете використовувати його на початку правила, щоб перевірити якесь властивість правила і дозволити парсеру відповідати вказаному правилу чи ні.
Скажімо, у прикладі граматики створюються Number
лексеми (правило лексера замість правила парсера), які будуть відповідати числам у діапазоні 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.