Conceptos básicos sobre gramáticas

Anuncio
Procesamiento de Lenguajes (PL)
Curso 2014/2015
Gramáticas
Conceptos básicos sobre gramáticas
Gramáticas y lenguajes
Dado un alfabeto Σ, un lenguaje es un conjunto (finito o infinito) de cadenas de sı́mbolos pertenecientes al
alfabeto. Es posible que la cadena vacı́a, , pertenezca a un lenguaje
Una gramática es una forma compacta de representar un lenguaje
IMPORTANTE: Una gramática genera un único lenguaje, pero un mismo lenguaje puede ser generado por
varias gramáticas
Una gramática es una cuádrupla G = (VN , VT , S, P ), donde VN es el conjunto de sı́mbolos variables o no
terminales, VT es el conjunto de sı́mbolos terminales (todos los terminales deben pertenecer al alfabeto), S es
el sı́mbolo inicial de la gramática (S siempre es un no terminal), y P es el conjunto de producciones o reglas
de la gramática
Producciones y derivaciones
Una producción o regla de una gramática tiene una parte izquierda y una parte derecha. Tanto la parte izquierda
como la parte derecha son una cadena de sı́mbolos terminales y no terminales
Normalmente, solamente se especifica el conjunto de producciones P , y se asume que el sı́mbolo inicial de la
gramática es la parte izquierda de la primera producción.
Ejemplo:
→
→
→
→
→
A
B
B
C
C
a B C
b bas
big C boss
c
G = (VN = {A, B, C }, VT = {a, b, bas, big, boss, c}, S = A, P = {A −→ . . .})
Una derivación es una secuencia de cadenas de sı́mbolos (llamadas formas sentenciales) en la que cada cadena
es resultado de la aplicación de una regla de la gramática a la cadena anterior. Una derivación válida es aquella
en la que la primera cadena de la secuencia es el sı́mbolo inicial, y la última es una cadena de terminales.
Ejemplos:
A
⇒
⇒
⇒
⇒
a
a
a
a
A
⇒
⇒
⇒
a B C
a b bas C
a b bas
B C
B c
big C boss c
big boss c
En los ejemplos, la primera derivación se dice que es una derivación por la derecha, porque siempre se deriva
el no terminal situado más a la derecha, y la segunda es una derivación por la izquierda
IMPORTANTE: El lenguaje generado por una gramática es el conjunto de cadenas de terminales obtenidas
a partir de derivaciones válidas usando las reglas de la gramática
Un árbol de derivación es un árbol en el que se representa una derivación válida de una cadena (pero no se
especifica el orden en que se han aplicado las reglas)
PL, 2013/2014
2
A
a
B
b
C
bas
ε
Caracterı́sticas especiales
Se dice que una gramática es ambigua cuando, para una cadena determinada, existe más de un árbol de
derivación
IMPORTANTE: la única forma de saber si una gramática es ambigua es encontrando una cadena con más
de un árbol de derivación, no hay otra forma
Ejemplo:
E
E
→
→
E opsuma E
num
La cadena “2+3-4” tiene dos árboles de derivación, y la cadena “2+3-4+5” tiene más de dos árboles
(¿cuántos?)
Una gramática se dice que es recursiva por la izquierda si tiene al menos una regla de esta forma:
E
→ E opsuma T
De forma similar, una gramática puede presentar recursividad por la derecha:
E
→ T opsuma E
Una gramática se dice que tiene factores comunes por la izquierda si tiene en al menos dos reglas (con la misma
parte izquierda) sı́mbolos comunes al principio de la parte derecha de la regla:
A
A
...
→
→
B a C
B a d
Jerarquı́a de gramáticas
Según la forma de las producciones, las gramáticas se clasifican en:
Regulares: en la parte izquierda sólo hay un no terminal, y la parte derecha puede haber:
no terminal → terminal
no terminal → terminal no terminal
no terminal → Independientes del contexto (context-free): en la parte izquierda sólo hay un no terminal, en la parte derecha
no hay restricciones
Dependientes del contexto: en la parte izquierda puede haber terminales y no terminales, pero al menos debe
haber un no terminal, y la longitud de la parte derecha debe ser mayor o igual que la de la parte izquierda
No restringidas
PL, 2013/2014
3
¿Qué tipos de gramáticas se usan en los compiladores?
En los compiladores se utilizan solamente gramáticas regulares y gramáticas independientes del contexto (GIC)
Las gramáticas regulares se utilizan para especificar los tokens (en realidad, se utilizan expresiones regulares,
pero son equivalentes)
Las GIC se utilizan para especificar la sintaxis de las construcciones del lenguaje fuente
En los lenguajes de programación hay restricciones semánticas (p.ej. es necesario haber declarado una variable
antes de utilizarla), que hacen que en realidad los lenguajes de programación sean lenguajes sensibles al contexto,
pero no se utilizan gramáticas sensibles al contexto, se utilizan GIC a las que se añaden acciones para la
comprobación de las restricciones semánticas.
Diseño de gramáticas para expresiones en lenguajes de programación
Un buen diseño de la gramática nos permitirá reflejar de forma natural caracterı́sticas semánticas del lenguaje
en el árbol de derivación, y esto permitirá que la traducción sea más sencilla
Es importante por tanto diseñar una buena gramática, pero luego es posible que se tenga que modificar según
el tipo de analizador sintáctico que se desee utilizar
Además, es posible que al diseñar el proceso de traducción sea necesario rediseñar la gramática para facilitar
el diseño del traductor
Diseño de gramáticas para expresiones: asociatividad
La asociatividad indica cómo se agrupan los operandos en un operador cuando aparecen más de dos operandos.
Por ejemplo, “4-3-2” normalmente vale “-1”, porque el operador “-” suele tener asociatividad por la izquierda,
y primero se evalua “4-3” y al resultado se le resta “2”. Sin embargo, si la asociatividad fuera por la derecha,
el resultado serı́a “3” (“4-(3-2)”)
Los paréntesis permiten alterar la asociatividad por defecto de un operador
¿Cómo se puede reflejar la asociatividad en una gramática?
asociatividad izquierda
asociatividad derecha
E
E
→
→
E opsuma T
T opsuma E
Pero... ¿no son todos los operadores asociativos por la izquierda? Sı́, casi todos, pero no todos: “a=b=c=0”
Diseño de gramáticas para expresiones: precedencia
En la mayorı́a de los lenguajes de programación, unos operadores se evaluan antes que otros. Por ejemplo,
“2+3*4” casi siempre vale “14”. Como ocurre con la asociatividad, los paréntesis permiten alterar la precedencia
de los operadores: “(2+3)*4”
¿Cómo se puede reflejar la precedencia en una gramática? con un no terminal diferente para cada nivel de
precedencia:
Expr
Expr
EBool
EBool
ExpRel
E
E
T
T
F
−→
−→
−→
−→
−→
→
→
→
→
→
Expr or EBool
EBool
EBool and ExpRel
ExpRel
E oprel E
E opsuma T
T
T opmul F
F
...
Algunos operadores no permiten usar más de dos operandos, como por ejemplo: “a<b<c” ⇒ “a<b && b<c”
PL, 2013/2014
4
Tabla de asociatividades y precedencias
Es una tabla en la que aparecen los operadores y su asociatividad, ordenados de menor a mayor precedencia:
Operador
@
%
#
A
A
B
B
C
C
D
−→
−→
−→
−→
−→
−→
−→
Asociatividad
izquierda
derecha
izquierda
A @ B
B
C % B
C
C # D
D
...
Operadores unarios
Los operadores unarios son más difı́ciles de reflejar en una gramática, requiere un buen conocimiento del
lenguaje y de las gramáticas
Los ejemplos más conocidos son:
• el operador de negación, “!”, que además permite que se repita el operador: “!!!true”
ExpRel
−→
! ExpRel
• el operador de cambio de signo, “-” o “+”, que no permite repeticiones (por ejemplo, “----3” no es
correcto)
E
−→ opsuma T
El problema del dangling-else
if (a<3)
if (b<4)
c=7;
else
c=8;
¿Con qué “if” se asocia el “else”? La regla que se usa normalmente es asociar el “else” al “if” más cercano:
if (a<3)
if (b<4)
c=7;
else
c=8;
Consejo: es mejor usar bloques entre llaves de forma explı́cita
if (a<3)
{
if (b<4)
{
c=7;
}
else
{
c=8;
}
}
PL, 2013/2014
5
Fragmento de gramática para la instrucción “if”:
Instr
Instr
Instr
Instr
−→
−→
−→
−→
id opasig Expr
...
if ( Expr ) Instr
if ( Expr ) Instr else Instr
Tiene dos caracterı́sticas no deseables:
1. Tiene factores comunes por la izquierda
2. ¡¡¡¡¡Es ambigua!!!!!
. . . y sin embargo, se utiliza en prácticamente todos los compiladores basados en análisis sintáctico ascendente
(los que usan yacc o bison).
Descargar