Capítulo 4 Modelo para la Construcción Dinámica de Workflows
4.3 Gramáticas de contexto libre: BNF
En Lingüística e Informática, una gramática de contexto libre es una gramática formal en la que cada regla de producción es de la forma:
V → w
dónde V es un símbolo no terminal y w es una cadena de terminales y/o no terminales. El término contexto libre se refiere al hecho de que el no terminal V puede siempre ser sustituido por w sin tener en cuenta el contexto en el que ocurra. Un lenguaje formal es de contexto libre si hay una gramática de contexto libre que lo genera.
Las gramáticas de contexto libre permiten describir la mayoría de los lenguajes de programación, de hecho, la sintaxis de la mayoría de lenguajes de programación está definida mediante gramáticas de contexto libre. Por otro lado, estas gramáticas son suficientemente simples como para permitir el diseño de eficientes algoritmos de análisis sintáctico que, para una cadena de caracteres dada determinen cómo puede ser generada desde la gramática.
La gramática describe de forma natural la estructura jerárquica de muchas construcciones de lenguajes de programación, por ejemplo, una proposición en el lenguaje C if-else tiene la siguiente forma:
if (expresión) proposición else proposición
puede expresarse por la siguiente producción
donde la palabra clave if y los paréntesis, se llaman componentes léxicos y la flecha se lee como “puede tener la forma”. Las variables expresión y proposición representan conjuntos de componentes léxicos y se llaman no terminales.
Definición 13 (Gramáticas de contexto libre). Una gramática de contexto libre es una cuádrupla de terminales, no terminales, un símbolo inicial y producciones:
G = (T, N, I, P) (5.1)
dónde:
T = Terminales, son los símbolos básicos con que se forman las cadenas. “Componente léxico” es un sinónimo de terminal cuando se trata de gramáticas para lenguajes de programación.
N = No terminales, son variables sintácticas que denotan conjuntos de cadenas. Los no terminales definen conjuntos de cadenas que ayudan a definir el lenguaje generado por la gramática, también imponen una estructura jerárquica sobre el lenguaje que es útil para el análisis sintáctico.
I = Inicial, en una gramática, un no Terminal es considerado como el símbolo inicial y el conjunto de cadenas que representa es el lenguaje definido por la gramática.
P = Producciones, las producciones de una gramática especifican cómo se pueden combinar los terminales y no terminales para formar cadenas. Cada producción consta de un no terminal, seguido por una flecha (o el símbolo :==) seguida por una cadena de no terminales y terminales.
Se sigue la regla convencional de especificar las gramáticas dando una lista de sus producciones, donde las producciones del símbolo inicial se listan primero. Se asume que los dígitos, los signos como “<” y las palabras clave de los lenguajes como while son terminales.
Una gramática de contexto libre da una especificación sintáctica precisa y fácil de entender de un lenguaje de programación, esto implica, que cualquier notación representada por una gramática se puede generar sin ambigüedades sintácticas y puede ser verificada que sea correcta de forma automática.
La notación más frecuentemente utilizada para expresar gramáticas de contexto libre es BNF. El Backus-Naur form (BNF) (también conocido como Backus-Naur formalism, Backus normal form o Pānini-Backus Form) es una metasintaxis usada para expresar gramáticas de contexto libre, una manera formal de describir lenguajes formales. BNF se utiliza extensamente como notación para las gramáticas de los lenguajes de programación de la computadora, de los sistemas de comando y de los protocolos de comunicación, así como una notación para representar partes de las gramáticas de lenguaje natural. La mayoría de los libros de textos para la teoría y/o la semántica del lenguaje de programación documentan el lenguaje de programación en BNF. Algunas variantes, tales como la augmented Backus-Naur form (ABNF), tienen su propia documentación. La notación se llama Backus por el nombre del autor John Backus y a sugerencia de Donald Knuth, el nombre de Naur se añadió debido a que Peter Naur simplificó la notación minimizando el conjunto de caracteres usados. La Backus-Naur Form o las gramáticas de BNF tiene semejanzas significativas a las reglas de la gramática de Pānini. BNF fue creado como parte para crear las reglas de ALGOL 60.
Una especificación de BNF es un sistema de reglas de la derivación, escrito como
<símbolo> ::= <expresión>
donde <símbolo> es un no terminal, y la expresión consiste en secuencias de símbolos y/o secuencias separadas por la barra vertical, '|', indicando una opción, el conjunto de secuencias es una posible substitución para el símbolo de la izquierda. Los símbolos que nunca aparecen en un lado izquierdo son terminales. BNF permite que se inserten acciones semánticas en el lado derecho de las producciones y la posición en que se ejecutan se da entre llaves. Las acciones semánticas son una secuencia de proposiciones que permiten acceder cualquier componente de una producción.
A partir de la gramática se puede construir automáticamente un analizador sintáctico que determine si un programa o una cadena tienen una sintaxis correcta, es decir, cumple con las reglas de escritura y no contiene ambigüedades. Los métodos empleados generalmente se clasifican como descendentes y ascendentes. Como sus nombres indican, los analizadores sintácticos descendentes construyen árboles de análisis sintáctico desde la raíz hasta las hojas, mientras que los analizadores sintácticos ascendentes comienzan en las hojas y avanzan hacia la raíz. En ambos casos, se examina la entrada al analizador sintáctico de izquierda a derecha, un símbolo a la vez. La figura 4.2 muestra el ejemplo para la generación de un árbol de análisis sintáctico a partir de una gramática:
SEQ ::== cAd A ::== ab | a
y una cadena de entrada p = cad. Primero se crea un árbol de análisis sintáctico para un solo nodo etiquetado por SEQ. Después se utiliza la primera producción de SEQ para expandir el árbol y obtener el de la figura 4.2a). Se empareja la hoja más a la izquierda con el primer símbolo de p. Después se expande A para emparejar el segundo símbolo de
p, si la primera alternativa de A concuerda, se continua con el tercer símbolo de p, y no se toma en cuenta b ya que no empareja con ninguna cadena de p, figura 4.2b). Por último se empareja la hoja d con el tercer símbolo de p, resultando el árbol mostrado en la figura 4.2c).
Figura 4.2 Generación del árbol sintáctico para p = cad.
SEQ A d SEQ d A c b a SEQ d A c c a (a) (b) (c)
En BNF el análisis sintáctico descendente se denomina derivación y da una descripción precisa de la construcción descendente de un árbol de análisis sintáctico. La idea central es que se considera una producción como una regla de reescritura, donde el no terminal de la izquierda es sustituido por la cadena del lado derecho de la producción, por ejemplo, dada la siguiente gramática:
WFK ::== SEQ SEQ ::== P T P
T ::== T P T
La derivación consiste en construir el siguiente árbol que puede representar la estructura de la red de Petri mostrada en la figura 4.1
SEQ :== P T P :== P T P T P :== pI t1 p1 t2 pE
Dónde:
t1 representa la actividad Registrar
p2 representa el estado En trámite
t2 representa la actividad Pagar
Al análisis sintáctico ascendente se le denomina reducción y para construirse, en cada paso se sustituye una subcadena determinada que concuerde con el lado derecho de una producción por el símbolo del lado izquierdo de dicha producción y si en cada paso se elige correctamente la subcadena se traza una derivación por la derecha en sentido inverso, si se llega a la secuencia inicial se considera que la cadena es correcta sintácticamente, por ejemplo, dada la gramática del ejemplo anterior, la red resultante del ejemplo se puede reducir:
pI t1 p1 t2 pE ⇓ P T P T P ⇓ P T P ⇓ SEQ
Los procesos de derivación y de reducción se utilizarán en la sección 4.4.2 para representar y construir redes WF utilizando BNF.