Що таке "семантичний предикат" в ANTLR?


103

Що таке семантичний предикат у ANTLR?


3
Зауважте, що оскільки я не зміг знайти гідного інтернет-ресурсу для публікації для того, хто хотів би знати, що таке семантичний предикат , я вирішив сам поставити питання тут (на що я також коротко відповім і сам).
Барт Кіерс

1
Дякую за це; Мені завжди подобається, коли люди відповідають на власні запитання, особливо якщо вони задають саме питання, щоб відповісти на це таким чином.
Даніель Н

1
Читати книгу. Розділ 11 Определеного ANTLR 4 Посилання на семантичні предикати. У вас немає книги? Отримайте! Варто кожного долара.
james.garriss

Відповіді:


169

ANTLR 4

Для присудків в ANTLR 4, перевірка цих стек переповнення Q & елементів а:


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

Перевірте його, генеруючи лексери та аналізатори, компілюючи всі .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. Затвердження семантичних предикатів

Перевірки семантичний предикат є не більше ніж блок коду , за яким слід знак питання:

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. Зведені семантичні предикати

Закритий типу семантичного предикат подібний перевіряючому семантичний предикат , тільки закритий варіант видає помилку синтаксису замість 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");

і ви побачите, що останній додасть помилку.


3. Розбіжні семантичні предикати

Остаточний тип присудка - це однозначний семантичний предикат , який трохи схожий на валідизуючий предикат ( {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

12
Людина, якій ви справді повинні розглянути можливість написання керівництва для початківців ANTLR: P
Юрій Генцев

5
@Bart Kiers: Будь ласка, напишіть книгу про ANTLR
santosh singh

2
Для ANTLR v4, input.LT(1)є в getCurrentToken()даний час :-)
Сяо Цзя

Фантастичний ... Це вичерпне пояснення та приклади, які мають бути в документах!
Єзекіїль Віктор

+1. Ця відповідь є набагато кращою, ніж довідник "The Definitive ANTLR 4". Ця відповідь є плямою на концепції з приємними прикладами.
asyncwait

11

Я завжди використовував у своєму керівництві стислі посилання на предикати ANTLR на wincent.com.


6
Так, відмінне посилання! Але, як ви згадуєте, для когось (відносно) нового в ANTLR це може бути дещо складно. Я просто сподіваюсь, що моя відповідь (трохи) дружніша для ANTLR-трави-бункера. :)
Барт Кіерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.