¿Qué es un predicado semántico en ANTLR?
¿Qué es un predicado semántico en ANTLR?
Respuestas:
Para los predicados en ANTLR 4, consulte estas preguntas y respuestas de desbordamiento de pila :
Un predicado semántico es una forma de hacer cumplir reglas adicionales (semánticas) sobre acciones gramaticales utilizando código simple.
Hay 3 tipos de predicados semánticos:
Digamos que tiene un bloque de texto que consta solo de números separados por comas, ignorando los espacios en blanco. Le gustaría analizar esta entrada asegurándose de que los números tengan como máximo 3 dígitos de "longitud" (como máximo 999). La siguiente gramática ( Numbers.g
) haría tal cosa:
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 gramática se puede probar con la siguiente clase:
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();
}
}
Pruébelo generando el lexer y parser, compilando todos los .java
archivos y ejecutando la Main
clase:
java -cp antlr-3.2.jar org.antlr.Tool Numbers.g javac -cp antlr-3.2.jar * .java java -cp.: antlr-3.2.jar Principal
Al hacerlo, no se imprime nada en la consola, lo que indica que nada salió mal. Intente cambiar:
ANTLRStringStream in = new ANTLRStringStream("123, 456, 7 , 89");
dentro:
ANTLRStringStream in = new ANTLRStringStream("123, 456, 7777 , 89");
y vuelva a hacer la prueba: verá un error que aparece en la consola justo después de la cadena 777
.
Esto nos lleva a los predicados semánticos. Supongamos que desea analizar números de entre 1 y 10 dígitos. Una regla como:
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
;
se volvería engorroso. Los predicados semánticos pueden ayudar a simplificar este tipo de regla.
Un predicado semántico de validación no es más que un bloque de código seguido de un signo de interrogación:
RULE { /* a boolean expression in here */ }?
Para resolver el problema anterior usando un
predicado semántico de validación , cambie la number
regla en la gramática a:
number
@init { int N = 0; }
: (Digit { N++; } )+ { N <= 10 }?
;
Las partes { int N = 0; }
y { N++; }
son declaraciones simples de Java de las cuales la primera se inicializa cuando el analizador "ingresa" a la number
regla. El predicado real es:, { N <= 10 }?
que hace que el analizador genere un
FailedPredicateException
siempre que un número tenga más de 10 dígitos.
Pruébelo utilizando lo siguiente ANTLRStringStream
:
// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890");
que no produce ninguna excepción, mientras que lo siguiente hace una excepción:
// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");
Un predicado semántico cerrado es similar a un predicado semántico de validación , solo que la versión cerrada produce un error de sintaxis en lugar de un FailedPredicateException
.
La sintaxis de un predicado semántico cerrado es:
{ /* a boolean expression in here */ }?=> RULE
En su lugar, para resolver el problema anterior utilizando predicados cerrados para hacer coincidir números de hasta 10 dígitos, escribiría:
number
@init { int N = 1; }
: ( { N <= 10 }?=> Digit { N++; } )+
;
Pruébelo de nuevo con ambos:
// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890");
y:
// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");
y verá que el último en arrojará un error.
El tipo final de predicado es un predicado semántico desambiguador , que se parece un poco a un predicado de validación ( {boolean-expression}?
), pero actúa más como un predicado semántico cerrado (no se lanza ninguna excepción cuando la expresión booleana se evalúa como false
). Puede usarlo al comienzo de una regla para verificar alguna propiedad de una regla y dejar que el analizador coincida con dicha regla o no.
Digamos que la gramática de ejemplo crea Number
tokens (una regla de lexer en lugar de una regla de analizador) que coincidirán con números en el rango de 0..999. Ahora, en el analizador, le gustaría hacer una distinción entre números bajos y altos (bajo: 0..500, alto: 501..999). Esto se puede hacer usando un predicado semántico que elimine la ambigüedad en el que inspeccione el siguiente token en la secuencia ( input.LT(1)
) para verificar si es bajo o alto.
Una demostración:
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();}
;
Si ahora analiza la cadena "123, 999, 456, 700, 89, 0"
, verá el siguiente resultado:
low = 123
high = 999
low = 456
high = 700
low = 89
low = 0
input.LT(1)
es getCurrentToken()
ahora :-)
Siempre he utilizado la breve referencia a los predicados ANTLR en wincent.com como guía.