¿Qué es un 'predicado semántico' en ANTLR?


103

¿Qué es un predicado semántico en ANTLR?


3
Tenga en cuenta que, dado que no pude encontrar un recurso en línea decente para publicar para alguien que quisiera saber qué es un predicado semántico , decidí publicar la pregunta aquí yo mismo (que también responderé en breve).
Bart Kiers

1
Gracias por hacer esto; Siempre me gusta cuando la gente responde sus propias preguntas, especialmente si hacen la pregunta específicamente para responderla de esta manera.
Daniel H

1
Leer el libro. El capítulo 11 de The Definitive ANTLR 4 Reference trata sobre predicados semánticos. ¿No tienes el libro? ¡Consíguelo! Vale cada dólar.
james.garriss

Respuestas:


169

ANTLR 4

Para los predicados en ANTLR 4, consulte estas preguntas y respuestas de desbordamiento de pila :


ANTLR 3

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:

  • validar predicados semánticos;
  • predicados semánticos cerrados ;
  • predicados semánticos desambiguantes .

Gramática de ejemplo

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

Pruebas

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 .javaarchivos y ejecutando la Mainclase:

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.


Predicados semánticos

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.


1. Validación de predicados semánticos

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 numberregla 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 numberregla. 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");

2. Predicados semánticos cerrados

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.


3. Desambiguando predicados semánticos

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 Numbertokens (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

12
Hombre, deberías considerar escribir una guía para principiantes de ANTLR: P
Yuri Ghensev

5
@Bart Kiers: escriba un libro sobre ANTLR
santosh singh

2
Para ANTLR v4, input.LT(1)es getCurrentToken()ahora :-)
Xiao Jia

Fantástico ... ¡Este es el tipo de explicación y ejemplos exhaustivos que deberían estar en los documentos!
Ezekiel Victor

+1. Esta respuesta es mucho mejor que el libro de referencia The Definitive ANTLR 4. Esta respuesta es acertada en el concepto con buenos ejemplos.
asyncwait

11

Siempre he utilizado la breve referencia a los predicados ANTLR en wincent.com como guía.


6
¡Sí, un excelente enlace! Pero, como mencionas, puede ser un poco difícil para alguien (relativamente) nuevo en ANTLR. Solo espero que mi respuesta sea (un poco) más amigable para el ANTLR-grass-hopper. :)
Bart Kiers
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.