Cos'è un predicato semantico in ANTLR?
Cos'è un predicato semantico in ANTLR?
Risposte:
Per predicati in ANTLR 4, checkout questi pila di overflow Q & A:
Un predicato semantico è un modo per applicare regole extra (semantiche) alle azioni grammaticali utilizzando codice semplice.
Esistono 3 tipi di predicati semantici:
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();}
;
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 .java
file ed eseguendo la Main
classe:
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
.
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.
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 number
regola 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 number
regola. 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");
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.
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 Number
token (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
input.LT(1)
è getCurrentToken()
:-)
Ho sempre usato il riferimento conciso ai predicati ANTLR su wincent.com come guida.