Che cos'è un "predicato semantico" in ANTLR?


103

Cos'è un predicato semantico in ANTLR?


3
Nota che dal momento che non sono riuscito a trovare una risorsa online decente da pubblicare per qualcuno che volesse sapere cosa sia un predicato semantico , ho deciso di postare la domanda qui (a cui risponderò a breve anch'io).
Bart Kiers

1
Grazie per averlo fatto; Mi piace sempre quando le persone rispondono alle proprie domande, soprattutto se fanno la domanda specificatamente per rispondere in questo modo.
Daniel H

1
Leggi il libro. Il capitolo 11 di The Definitive ANTLR 4 Reference è sui predicati semantici. Non hai il libro? Prendilo! Vale ogni dollaro.
james.garriss

Risposte:


169

ANTLR 4

Per predicati in ANTLR 4, checkout questi pila di overflow Q & A:


ANTLR 3

Un predicato semantico è un modo per applicare regole extra (semantiche) alle azioni grammaticali utilizzando codice semplice.

Esistono 3 tipi di predicati semantici:

  • convalida dei predicati semantici;
  • predicati semantici gated ;
  • predicati semantici disambiguanti .

Grammatica di esempio

Supponiamo che tu abbia un blocco di testo composto da soli numeri separati da virgole, ignorando eventuali spazi bianchi. Si desidera analizzare questo input assicurandosi che i numeri siano al massimo 3 cifre "lunghe" (al massimo 999). La seguente grammatica ( Numbers.g) farebbe una cosa del genere:

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

analisi

La grammatica può essere verificata con la seguente classe:

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

Provalo generando il lexer e il parser, compilando tutti i .javafile ed eseguendo la Mainclasse:

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

In tal caso, non viene stampato nulla sulla console, il che indica che nulla è andato storto. Prova a cambiare:

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

in:

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

e ripeti il ​​test: vedrai apparire un errore sulla console subito dopo la stringa 777.


Predicati semantici

Questo ci porta ai predicati semantici. Supponiamo che tu voglia analizzare numeri di lunghezza compresa tra 1 e 10 cifre. Una regola come:

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
  ;

diventerebbe ingombrante. I predicati semantici possono aiutare a semplificare questo tipo di regola.


1. Convalida dei predicati semantici

Un predicato semantico di convalida non è altro che un blocco di codice seguito da un punto interrogativo:

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

Per risolvere il problema precedente utilizzando un predicato semantico di convalida , modifica la numberregola nella grammatica in:

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

Le parti { int N = 0; }e { N++; }sono semplici istruzioni Java di cui la prima viene inizializzata quando il parser "entra" nella numberregola. Il predicato effettivo è:, { N <= 10 }?che fa sì che il parser lanci un FailedPredicateException ogni volta che un numero è lungo più di 10 cifre.

Provalo utilizzando quanto segue ANTLRStringStream:

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

che non produce eccezioni, mentre quanto segue fa un'eccezione:

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

2. Predicati semantici gated

Un predicato semantico gated è simile a un predicato semantico di convalida , solo la versione gated produce un errore di sintassi invece di un FailedPredicateException.

La sintassi di un predicato semantico gated è:

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

Per risolvere invece il problema di cui sopra utilizzando predicati gated per abbinare numeri lunghi fino a 10 cifre, dovresti scrivere:

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

Provalo di nuovo con entrambi:

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

e:

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

e vedrai che l'ultimo acceso genererà un errore.


3. Disambiguare i predicati semantici

L'ultimo tipo di predicato è un predicato semantico disambiguante , che assomiglia un po 'a un predicato convalidante ( {boolean-expression}?), ma agisce più come un predicato semantico gated (nessuna eccezione viene generata quando l'espressione booleana restituisce false). Puoi usarlo all'inizio di una regola per controllare alcune proprietà di una regola e lasciare che il parser corrisponda a detta regola o meno.

Supponiamo che la grammatica di esempio crei Numbertoken (una regola lexer invece di una regola parser) che corrisponderanno a numeri nell'intervallo 0..999. Ora nel parser, vorresti fare una distinzione tra numeri bassi e alti (basso: 0..500, alto: 501..999). Questo potrebbe essere fatto usando un predicato semantico disambiguante in cui ispezioni il token successivo nello stream ( input.LT(1)) per verificare se è basso o alto.

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

Se ora analizzi la stringa "123, 999, 456, 700, 89, 0", vedrai il seguente output:

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

12
Amico, dovresti davvero considerare di scrivere una guida per principianti su ANTLR: P
Yuri Ghensev

5
@Bart Kiers: Per favore scrivi un libro su ANTLR
santosh singh

2
Per ANTLR v4, ora input.LT(1)è getCurrentToken():-)
Xiao Jia

Fantastico ... Questo è il tipo di spiegazione esaustiva e di esempi che dovrebbero essere nei documenti!
Ezekiel Victor il

+1. Questa risposta è di gran lunga migliore del libro The Definitive ANTLR 4 Reference. Questa risposta è perfetta sul concetto con bei esempi.
asyncwait

11

Ho sempre usato il riferimento conciso ai predicati ANTLR su wincent.com come guida.


6
Sì, un ottimo collegamento! Ma, come hai detto, potrebbe essere un po 'difficile per qualcuno (relativamente) nuovo di ANTLR. Spero solo che la mia risposta sia (un po ') più amichevole per ANTLR-grass-hopper. :)
Bart Kiers
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.