Análisis Sintáctico
3.1 Nociones Generales
Sección titulada «3.1 Nociones Generales»El flujo de la fase de análisis es:
programa fuente→ analizador léxico→ secuencia de componentes léxicos→ analizador sintáctico→ árbol sintáctico→ analizador semánticoLa idea clave es:
- el análisis léxico reconoce tokens;
- el análisis sintáctico comprueba cómo se combinan esos tokens;
- el análisis semántico comprueba si esa estructura tiene sentido en el lenguaje.
3.1.1 Gramáticas Independientes del Contexto
Sección titulada «3.1.1 Gramáticas Independientes del Contexto»Una gramática independiente del contexto se define como una 4-tupla:
donde:
- es el conjunto de símbolos terminales.
- es el conjunto de símbolos no terminales o variables sintácticas.
- es el símbolo inicial.
- es el conjunto de producciones, de la forma:
con y .
Observación: en algunos apuntes también se escribe ; es la misma idea, cambiando la notación.
La máquina teórica asociada al reconocimiento de los lenguajes independientes del contexto es el autómata a pila.
En este tema se usa además el símbolo $ para indicar fin de cadena.
3.1.2 Tipos de Analizadores Sintácticos
Sección titulada «3.1.2 Tipos de Analizadores Sintácticos»Analizadores descendentes
Sección titulada «Analizadores descendentes»Construyen el árbol de arriba hacia abajo:
- parten del símbolo inicial;
- expanden no terminales aplicando producciones;
- intentan generar la cadena de entrada.
Analizadores ascendentes
Sección titulada «Analizadores ascendentes»Construyen el árbol de abajo hacia arriba:
- parten de la cadena de entrada;
- detectan fragmentos que coinciden con partes derechas de producciones;
- los reducen al no terminal correspondiente hasta llegar al símbolo inicial.
Ejemplo
Sección titulada «Ejemplo»Sea la gramática:
La cadena st2 puede analizarse de dos maneras.
Análisis descendente:
id⇒ id digito⇒ id letra digito⇒ letra letra digito⇒ s letra digito⇒ s t digito⇒ s t 2Análisis ascendente:
st2⇐ letra t2⇐ id t2⇐ id letra 2⇐ id 2⇐ id digito⇐ idLa primera derivación genera la cadena desde el símbolo inicial; la segunda reduce la cadena hasta ese símbolo inicial.
3.1.3 Forma de Backus-Naur (BNF) y EBNF
Sección titulada «3.1.3 Forma de Backus-Naur (BNF) y EBNF»La BNF es la notación clásica para describir gramáticas; la EBNF añade mecanismos de abreviación.
Metasímbolos básicos de BNF
Sección titulada «Metasímbolos básicos de BNF»::=define una producción.|indica alternativas.<>identifica no terminales.
Ejemplos:
<condicional_simple> ::= if <condicion> then <sentencia><op_aritmetica_entera> ::= + | - | * | div | mod<programa> ::= program <declaraciones> begin <sentencias> end.Metasímbolos de EBNF
Sección titulada «Metasímbolos de EBNF»[]indica que algo es opcional.{}indica repetición." "se usa para distinguir metasímbolos de terminales literales.
Ejemplos:
<sentencia_if> ::= if <expresion> then <sentencia> [ else <sentencia> ] endif;<id> ::= <letra> { <letra> | <digito> }<regla> ::= <id> "::=" <expresion>La regla:
<id> ::= <letra> { <letra> | <digito> }es equivalente a:
<id> ::= <letra> | <id> <letra> | <id> <digito>3.1.4 BNF Visual
Sección titulada «3.1.4 BNF Visual»La notación BNF visual suele presentarse así:
- Los terminales se escriben sin
<>. - Los no terminales también se escriben sin
<>, pero por contexto se sabe qué son. - Se usa
→en lugar de::=.
Ejemplo:
sentencia_if → if expresion then sentencia [ else sentencia ] endif;3.2 Diseño de una Gramática
Sección titulada «3.2 Diseño de una Gramática»Al diseñar una gramática para un lenguaje de programación hay cuatro ideas centrales:
- Recursividad
- Ambigüedad
- Asociatividad
- Precedencia
3.2.1 Recursividad
Sección titulada «3.2.1 Recursividad»Los lenguajes de programación permiten construir un número ilimitado de programas. Por tanto, no es viable describirlos mediante una casuística finita sin reutilización. La recursividad resuelve este problema: permite generar infinitas cadenas mediante un número finito de reglas.
Decimos que una gramática es recursiva si un no terminal vuelve a aparecer en alguna derivación propia:
Ejemplo:
<sentencia> ::= begin <sentencia> { ; <sentencia> } end<sentencia> ::= <asignacion> | <llamada_funcion> | <sentencia_repetitiva> | <sentencia_condicional>La recursividad permite, por ejemplo, bloques con sentencias anidadas sin imponer una cota artificial.
3.2.2 Ambigüedad
Sección titulada «3.2.2 Ambigüedad»Una gramática es ambigua si existe al menos una cadena con dos o más árboles de derivación distintos.
Esto es un problema porque una misma entrada puede interpretarse estructuralmente de formas distintas, y eso puede llevar a generar código diferente.
Ejemplo clásico con operadores
Sección titulada «Ejemplo clásico con operadores»<expresion> ::= <expresion> + <expresion> | <expresion> * <expresion> | ( <expresion> ) | - <expresion> | idCon esta gramática, la cadena id+id*id admite al menos dos derivaciones:
(1)<expresion>⇒ <expresion> + <expresion>⇒ id + <expresion>⇒ id + <expresion> * <expresion>⇒ id + id * <expresion>⇒ id + id * id(2)<expresion>⇒ <expresion> * <expresion>⇒ <expresion> + <expresion> * <expresion>⇒ id + <expresion> * <expresion>⇒ id + id * <expresion>⇒ id + id * idUna derivación interpreta primero la suma y la otra primero la multiplicación.
Ejemplo del else colgante
Sección titulada «Ejemplo del else colgante»Sea la gramática:
<sentencia> ::= if <expresion> then <sentencia> | if <expresion> then <sentencia> else <sentencia>Para el fragmento:
if (expr1) then if (expr2) then sentencia1; else sentencia2;aparecen dos interpretaciones:
- El
elsese asocia alifinterno. - El
elsese asocia alifexterno.
La interpretación habitual en los lenguajes de programación es la primera: el else se asocia con el if más cercano que todavía no tenga else.
Aclaración importante
Sección titulada «Aclaración importante»La factorización por la izquierda ayuda al análisis predictivo, pero no elimina por sí sola la ambigüedad. De hecho, el propio material propone después un ejercicio donde la gramática ya factorizada sigue siendo ambigua.
Ejercicio 6
Sección titulada «Ejercicio 6»Sea la gramática:
<sentencia> ::= if <expresion> then <sentencia> <else> | <expresion><else> ::= else <sentencia> | ε<expresion> ::= expresionSe pide demostrar que es una gramática ambigua.
3.2.3 Factorización por la izquierda
Sección titulada «3.2.3 Factorización por la izquierda»Cuando dos producciones de un mismo no terminal empiezan igual, un analizador descendente no puede decidir qué regla tomar mirando solo el prefijo común.
Ejemplo:
<sentencia> ::= if <expresion> then <sentencia> | if <expresion> then <sentencia> else <sentencia>La solución es retrasar la decisión extrayendo el prefijo común:
<sentencia> ::= if <expresion> then <sentencia> <else><else> ::= else <sentencia> | εEn general, si tenemos:
podemos transformar en:
Si en las nuevas alternativas vuelve a existir un prefijo común, se repite el proceso.
3.2.4 Asociatividad
Sección titulada «3.2.4 Asociatividad»La asociatividad decide cómo se agrupan operadores del mismo nivel de precedencia.
Asociatividad por la derecha
Sección titulada «Asociatividad por la derecha»Se evalúa de derecha a izquierda:
a = b = c ≡ a = (b = c)Se modela con recursividad por la derecha:
<asignacion> ::= id = <asignacion> | idAsociatividad por la izquierda
Sección titulada «Asociatividad por la izquierda»Se evalúa de izquierda a derecha:
a + b + c ≡ (a + b) + cSe modela con recursividad por la izquierda:
<expresion> ::= <expresion> + id | id3.2.5 Precedencia
Sección titulada «3.2.5 Precedencia»La precedencia fija qué operadores deben agruparse antes que otros.
Cuando en una expresión intervienen varios operadores, si no se impone precedencia, la gramática suele ser ambigua.
Se puede incorporar la precedencia:
- usando un no terminal por nivel de precedencia;
- o separando producciones según el operador.
Un operador tiene menor precedencia cuanto más cerca aparece su regla de la regla inicial.
Ejemplo
Sección titulada «Ejemplo»Queremos:
- suma y resta con precedencia 1;
- multiplicación y división con precedencia 2;
- potenciación con precedencia 3;
- potenciación asociativa por la derecha;
- paréntesis con precedencia máxima.
Una gramática adecuada es:
<expresion> ::= <expresion> + <expr_mult> | <expresion> - <expr_mult> | <expr_mult>
<expr_mult> ::= <expr_mult> * <expr_exp> | <expr_mult> / <expr_exp> | <expr_exp>
<expr_exp> ::= <valor> ^ <expr_exp> | <valor>
<valor> ::= ( <expresion> ) | idLa estructura de la gramática ya impone:
- primero paréntesis;
- después exponentes;
- después multiplicaciones y divisiones;
- después sumas y restas.
Ejercicio 7
Sección titulada «Ejercicio 7»Sea la gramática anterior. Se pide construir el árbol de derivación para:
2 + (2 - 2) / 2 * (2 - 2) / (2 - 2)2 + 2 - 2 / 2 * 2 - 2 / 2 - 2
3.3 Análisis Sintáctico Descendente
Sección titulada «3.3 Análisis Sintáctico Descendente»El análisis sintáctico descendente construye una derivación desde el símbolo inicial hacia la cadena de entrada.
En su forma más simple trabaja mediante:
- Avance: se aplica una producción y el análisis progresa.
- Retroceso: si una elección falla, se vuelve atrás para probar otra.
Para controlar este proceso se usa una pila. El analizador intenta emparejar el símbolo en la cima con el componente léxico actual de la entrada.
3.3.1 Ejemplo básico de ASD con retroceso
Sección titulada «3.3.1 Ejemplo básico de ASD con retroceso»Queremos comprobar si w = cad pertenece a la gramática:
S → cAdA → ab | aLa idea es:
- Partir de
S. - Expandir hasta obtener terminales.
- Comparar contra la entrada.
- Si una expansión falla, retroceder y probar otra.
La cadena cad sí pertenece al lenguaje porque:
S ⇒ cAd ⇒ cadsi se elige la producción A → a.
3.3.2 Implementación recursiva simple
Sección titulada «3.3.2 Implementación recursiva simple»Una forma natural de implementar un analizador descendente es crear una función por no terminal.
Por ejemplo, para la regla:
<expr> ::= <term> + <expr> | <term>una implementación esquemática sería:
bool expresion() { if (!termino()) return false;
if (TOKEN == '+' || TOKEN == '-') { TOKEN = sig_comp_lexico(); if (TOKEN == '$') return false; if (!expresion()) return false; }
return true;}La idea es que cada función reconoce la parte del lenguaje asociada a su no terminal.
3.3.3 Inconvenientes del ASD con retroceso
Sección titulada «3.3.3 Inconvenientes del ASD con retroceso»Los métodos totalmente recursivos con backtracking no suelen ser recomendables porque:
- Son lentos.
- Pueden explorar muchas alternativas antes de concluir.
- Si la entrada no pertenece al lenguaje, es difícil señalar con precisión dónde está el error.
- Si se va generando código durante el análisis, un retroceso obliga a deshacer trabajo ya realizado.
Por eso interesan los analizadores predictivos, que intentan evitar el retroceso.
3.3.4 Recursividad por la izquierda
Sección titulada «3.3.4 Recursividad por la izquierda»La recursividad por la izquierda es peligrosa para el análisis descendente, porque puede provocar un bucle infinito.
Ejemplo:
S → cAdA → Aad | aSi al expandir A siempre aplicamos A → Aad, nunca llegamos a consumir entrada antes de volver a encontrar A.
Eliminación de recursividad inmediata por la izquierda
Sección titulada «Eliminación de recursividad inmediata por la izquierda»Si una gramática contiene:
con las no recursivas por la izquierda, se transforma en:
Ejemplo
Sección titulada «Ejemplo»Aplicado a:
S → cAdA → Aad | atenemos:
y queda:
S → cAdA → aA'A' → adA' | εRecursividad indirecta por la izquierda
Sección titulada «Recursividad indirecta por la izquierda»El procedimiento anterior solo elimina la recursividad inmediata. Si la recursividad aparece a través de otras producciones, primero hay que sustituir.
Ejemplo:
S → Aa | bA → Ac | Sd | εAquí hay recursividad por la izquierda en la secuencia:
S → AaA → SdSustituyendo Sd por su definición:
S → Aa | bA → Ac | Aad | bd | εAhora sí podemos eliminar la recursividad inmediata. Como:
obtenemos:
S → Aa | bA → bdA' | A'A' → cA' | adA' | εEjercicio 8
Sección titulada «Ejercicio 8»Sea la gramática:
<expresion> ::= <expresion> + <termino> | <termino><termino> ::= <termino> <factor> | <factor><factor> ::= <factor> * | <valor><valor> ::= a | bSe pide eliminar la recursividad por la izquierda.
3.3.5 Analizador descendente predictivo
Sección titulada «3.3.5 Analizador descendente predictivo»La idea es evitar retrocesos y predecir qué producción debe aplicarse en cada momento.
Supongamos:
- entrada
c_1 c_2 ... c_i ... c_n; - el componente léxico actual es
c_i; - el no terminal a expandir es
A; - las producciones de
Ason:
Si cada alternativa puede distinguirse por el símbolo terminal con el que empieza, el analizador puede elegir sin retroceso.
Ejemplo:
<sentencia> ::= if <expresion> then <sentencia> | while <expresion> do <sentencia> | begin <sentencia> endCada alternativa comienza con un terminal distinto: if, while, begin.
Gramáticas LL(k)
Sección titulada «Gramáticas LL(k)»Estas ideas llevan a las gramáticas LL(k):
L: lectura de izquierda a derecha.L: derivación más a la izquierda.k: número de componentes léxicos de anticipación.
En este curso interesan las LL(1).
Una gramática LL(1):
- no puede tener recursividad por la izquierda;
- no debe obligar a elegir entre dos alternativas con el mismo símbolo de anticipación.
Importante
Sección titulada «Importante»Eliminar recursividad por la izquierda y factorizar por la izquierda son condiciones necesarias, pero no suficientes para que una gramática sea LL(1). La verificación real se hace con los conjuntos PRIMEROS, SIGUIENTES y la tabla de análisis.
Ejemplo
Sección titulada «Ejemplo»Obtener una gramática LL(1) equivalente a:
A → Aa | bBB → bc | bb | bEliminamos la recursividad por la izquierda en A:
A → bBA'A' → aA' | εFactorizamos B:
B → bB'B' → c | b | εY aun así no basta con afirmar que ya sea LL(1); hay que comprobarlo con la tabla.
3.3.6 Conjuntos de predicción
Sección titulada «3.3.6 Conjuntos de predicción»Los conjuntos de predicción son:
PRIMEROSSIGUIENTES
Sirven para decidir qué producción aplicar en un analizador predictivo.
Conjunto PRIMEROS
Sección titulada «Conjunto PRIMEROS»Sea . PRIMEROS(X) es el conjunto de terminales, incluyendo opcionalmente ε, que pueden aparecer al principio de alguna cadena derivable de X.
Reglas para calcular PRIMEROS(X)
Sección titulada «Reglas para calcular PRIMEROS(X)»- Si
Xes terminala, entonces:
- Si existe
X → ε, entonces:
- Si
Xes no terminal y existe una producción:
se añaden a PRIMEROS(X) los terminales de PRIMEROS(Y_j) siempre que todos los símbolos anteriores puedan derivar a ε.
- Si todos los pueden derivar a
ε, entonces también:
Reglas para calcular PRIMEROS(X_1X_2\cdots X_n)
Sección titulada «Reglas para calcular PRIMEROS(X_1X_2\cdots X_n)»- Añadir
PRIMEROS(X1) - {ε}. - Si
ε ∈ PRIMEROS(X1), añadirPRIMEROS(X2) - {ε}. - Repetir el proceso mientras aparezca
ε. - Si todos pueden derivar a
ε, entoncesεpertenece al conjunto.
Ejemplo
Sección titulada «Ejemplo»Para la gramática:
<expresion> ::= <termino> <expresion'><expresion'> ::= + <termino> <expresion'> | ε<termino> ::= <factor> <termino'><termino'> ::= * <factor> <termino'> | ε<factor> ::= ( <expresion> ) | idse obtiene:
Y para la cadena <termino'><expresion'>id:
Conjunto SIGUIENTES
Sección titulada «Conjunto SIGUIENTES»Sea A un no terminal. SIGUIENTES(A) es el conjunto de terminales que pueden aparecer inmediatamente a la derecha de A en alguna derivación.
Si A puede quedar al final de la cadena derivada, entonces:
Reglas para calcular SIGUIENTES(A)
Sección titulada «Reglas para calcular SIGUIENTES(A)»- Si
Aes el símbolo inicialS, entonces añadir$. - Para cada regla
B → αAβ, añadirPRIMEROS(β) - {ε}aSIGUIENTES(A). - Para cada regla
B → αAβ, siβ ⇒* ε, añadirSIGUIENTES(B)aSIGUIENTES(A). - Para cada regla
B → αA, añadirSIGUIENTES(B)aSIGUIENTES(A).
Ejemplo
Sección titulada «Ejemplo»Para la gramática anterior:
3.3.7 Tabla de análisis sintáctico descendente
Sección titulada «3.3.7 Tabla de análisis sintáctico descendente»Con PRIMEROS y SIGUIENTES se construye una tabla T[A,a]:
- filas: no terminales;
- columnas: terminales y
$; - contenido: la producción que debe aplicarse;
- celdas vacías: error.
Regla de construcción
Sección titulada «Regla de construcción»Para cada producción:
- Para cada terminal
a ∈ PRIMEROS(α), añadirA → αenT[A,a]. - Si
ε ∈ PRIMEROS(α), entonces para cadab ∈ SIGUIENTES(A)añadirA → αenT[A,b].
Una gramática es LL(1) si en cada celda aparece como máximo una producción.
Ejemplo de tabla LL(1)
Sección titulada «Ejemplo de tabla LL(1)»Usando la gramática:
E → T E'E' → + T E' | εT → F T'T' → * F T' | εF → (E) | idla tabla queda:
| No terminal | id | + | * | ( | ) | $ |
|---|---|---|---|---|---|---|
E | E → T E' | E → T E' | ||||
E' | E' → + T E' | E' → ε | E' → ε | |||
T | T → F T' | T → F T' | ||||
T' | T' → ε | T' → * F T' | T' → ε | T' → ε | ||
F | F → id | F → (E) |
Esta tabla no tiene conflictos, así que la gramática es LL(1).
3.3.8 Procedimiento de análisis predictivo
Sección titulada «3.3.8 Procedimiento de análisis predictivo»El analizador predictivo dirigido por tabla usa:
- una pila con terminales y no terminales;
- una tabla LL(1);
- la cadena de entrada terminada en
$.
Algoritmo
Sección titulada «Algoritmo»-
Inicializar la pila con
S$. -
Añadir
$al final de la entrada. -
Sea
Xla cima de la pila yael siguiente símbolo de entrada. -
Actuar según el caso:
- Si
X = a = $, aceptar. - Si
X = a ≠ $, desapilar y avanzar en la entrada. - Si
Xes terminal yX ≠ a, error. - Si
Xes no terminal yT[X,a]está vacía, error. - Si
Xes no terminal yT[X,a] = X → X_1X_2...X_n, desapilarXy apilar la parte derecha.
- Si
Aclaración práctica
Sección titulada «Aclaración práctica»Para que X_1 quede en la cima y sea lo siguiente en analizar, la parte derecha se apila en orden inverso: primero X_n, luego X_{n-1}, …, hasta X_1.
Ejemplo: análisis de id+id
Sección titulada «Ejemplo: análisis de id+id»Con la gramática del ejemplo anterior:
| Pila | Entrada | Acción |
|---|---|---|
$E | id+id$ | Aplicar E → T E' |
$E'T | id+id$ | Aplicar T → F T' |
$E'T'F | id+id$ | Aplicar F → id |
$E'T'id | id+id$ | Avanzar |
$E'T' | +id$ | Aplicar T' → ε |
$E' | +id$ | Aplicar E' → +TE' |
$E'T+ | +id$ | Avanzar |
$E'T | id$ | Aplicar T → F T' |
$E'T'F | id$ | Aplicar F → id |
$E'T'id | id$ | Avanzar |
$E'T' | $ | Aplicar T' → ε |
$E' | $ | Aplicar E' → ε |
$ | $ | Cadena aceptada |
3.3.9 Recapitulación sobre LL(1)
Sección titulada «3.3.9 Recapitulación sobre LL(1)»Las gramáticas LL(1) permiten análisis de complejidad:
No toda gramática es LL(1), pero a veces puede transformarse mediante:
- Eliminación de la recursividad por la izquierda.
- Eliminación de la ambigüedad.
- Factorización por la izquierda.
La eliminación de la ambigüedad no tiene una receta general: suele exigir rediseñar la gramática.
3.3.10 Gestión de errores en ASD
Sección titulada «3.3.10 Gestión de errores en ASD»En un analizador descendente predictivo pueden ocurrir dos tipos de error:
- En la cima de la pila hay un terminal distinto del token actual.
- En la cima hay un no terminal y la celda correspondiente de la tabla está vacía.
El compilador debe informar del error e intentar continuar el análisis.
3.3.11 Conjuntos de sincronización
Sección titulada «3.3.11 Conjuntos de sincronización»La recuperación por conjuntos de sincronización funciona así:
- Se detecta un error.
- El analizador entra en modo de recuperación.
- Va descartando símbolos de entrada hasta encontrar uno perteneciente al conjunto de sincronización.
- En ese momento puede desapilar el no terminal conflictivo y continuar.
La calidad de la recuperación depende de cómo se elijan esos conjuntos.
Estrategias habituales
Sección titulada «Estrategias habituales»- Para un no terminal
A, usarSIGUIENTES(A). - Añadir símbolos que inician construcciones de nivel superior.
- Añadir también
PRIMEROS(A)cuando interese reanudar al comienzo de esa construcción. - Si una producción puede generar
ε, tomarla por omisión. - Si el error es por no emparejar un terminal, se puede suponer que faltaba ese terminal, emitir un mensaje y continuar.
Idea del ejemplo del tema
Sección titulada «Idea del ejemplo del tema»Para la gramática de expresiones, el PDF sugiere rellenar ciertas celdas vacías con sinc usando SIGUIENTES, de forma que el analizador pueda saltar hasta ), $ o el terminal adecuado y continuar.
3.4 Análisis Sintáctico Ascendente
Sección titulada «3.4 Análisis Sintáctico Ascendente»El análisis sintáctico ascendente parte de la cadena de entrada y trata de reconstruir la derivación en sentido inverso.
La estrategia general es:
- Leer la entrada de izquierda a derecha.
- Detectar fragmentos que coincidan con partes derechas de producciones.
- Reducir esos fragmentos al no terminal correspondiente.
- Repetir hasta llegar al símbolo inicial o detectar error.
En otras palabras, el árbol sintáctico se construye de las hojas a la raíz.
3.4.1 Ejemplo básico de reducción
Sección titulada «3.4.1 Ejemplo básico de reducción»Sea la gramática:
S → aABeA → Abc | bB → dLa cadena abbcde puede analizarse como:
abbcde ⇐ aAbcde ⇐ aAde ⇐ aABe ⇐ S3.4.2 Desplazamiento y reducción
Sección titulada «3.4.2 Desplazamiento y reducción»Los analizadores ascendentes suelen trabajar con una pila y dos operaciones básicas:
- Desplazamiento: mover el siguiente símbolo de entrada a la pila.
- Reducción: sustituir en la pila una parte derecha reconocida por su no terminal.
Ejemplo
Sección titulada «Ejemplo»Con la gramática:
E → E + EE → E * EE → (E)E → idpara analizar id+id*id aparecen decisiones del tipo:
- reducir
idaE; - desplazar
+; - más adelante, decidir entre reducir
E+Eo desplazar*.
Esa duda es exactamente la que resolverán las tablas LR.
3.4.3 Gramáticas LR(k)
Sección titulada «3.4.3 Gramáticas LR(k)»En el análisis ascendente por desplazamiento-reducción se usan gramáticas LR(k):
L: lectura de izquierda a derecha.R: construcción de una derivación por la derecha en orden inverso.k: número de símbolos de anticipación.
Para cada gramática LR(k) con k > 1 puede construirse una LR(1) equivalente, por lo que normalmente se trabaja con k = 1.
Las gramáticas LR(1):
- son no ambiguas;
- describen más lenguajes que las gramáticas LL(1);
- incluyen a las LL(1) como caso particular.
Tipos principales:
- SLR(1): Simple LR, construcción más sencilla.
- LR(1): más potentes, pero más costosas.
- LALR(1): compromiso entre las dos anteriores.
En este tema se estudian las SLR(1).
3.4.4 Gramática aumentada
Sección titulada «3.4.4 Gramática aumentada»Para construir un analizador LR se usa la gramática aumentada:
si la gramática original tiene símbolo inicial S, se añade artificialmente:
Cuando el análisis llega a reducir mediante esta producción aumentada, la entrada se acepta.
3.4.5 Elementos LR(0)
Sección titulada «3.4.5 Elementos LR(0)»Un elemento LR(0) es una producción con una marca · en algún punto de su parte derecha.
La marca separa:
- lo ya reconocido;
- lo que aún falta por reconocer.
Ejemplo, para la producción:
A → B - Dlos elementos posibles son:
A → ·B-DA → B·-DA → B-·DA → B-D·Su interpretación es:
A → ·B-D: todavía no se reconoció nada.A → B·-D: ya se reconocióB.A → B-·D: ya se reconocióB-.A → B-D·: ya se reconoció toda la parte derecha; ahora podría reducirse.
Para una producción A → ε, el único elemento es:
A → ·3.4.6 Función clausura(I)
Sección titulada «3.4.6 Función clausura(I)»La función clausura(I) se aplica a un conjunto de elementos I y devuelve un conjunto ampliado que representa todas las situaciones equivalentes en ese punto del análisis.
Regla de construcción
Sección titulada «Regla de construcción»- Todo elemento de
Ipertenece aclausura(I). - Si en
clausura(I)aparece un elemento de la forma:
A → α·Bβy existe una producción:
B → γentonces se añade:
B → ·γSe repite hasta que no puedan añadirse más elementos.
Intuición
Sección titulada «Intuición»Si aparece A → α·Bβ, significa que ya se reconoció una cadena derivable de α y que ahora se espera algo derivable de Bβ. Por tanto, hay que incorporar todas las maneras posibles de empezar a derivar B.
Ejemplo
Sección titulada «Ejemplo»Para la gramática:
E' → E$E → E+T | TT → T*F | FF → (E) | idsi partimos de:
I = { E' → ·E$ }la clausura se construye por pasos:
- Añadimos
E → ·E+TyE → ·T. - Como aparece
·T, añadimosT → ·T*FyT → ·F. - Como aparece
·F, añadimosF → ·(E)yF → ·id.
Así:
I0 = clausura(I) = { E' → ·E$, E → ·E+T, E → ·T, T → ·T*F, T → ·F, F → ·(E), F → ·id}3.4.7 Función Ir_a(I,X)
Sección titulada «3.4.7 Función Ir_a(I,X)»La función Ir_a(I,X) se aplica a:
- un conjunto de elementos
I; - un símbolo gramatical
X.
Devuelve la clausura del conjunto de elementos que resulta de avanzar el punto sobre X.
Formalmente:
si en I aparece:
A → α·Xβentonces en Ir_a(I,X) aparecerá:
A → αX·βy después se aplica clausura.
Intuición
Sección titulada «Intuición»Ir_a(I,X) representa el estado al que se llega cuando, estando en I, se reconoce el símbolo X.
Ejemplo
Sección titulada «Ejemplo»Con I0 anterior:
I1 = Ir_a(I0,E) = { E' → E·$, E → E·+T}I2 = Ir_a(I0,T) = { E → T·, T → T·*F}I3 = Ir_a(I0,F) = { T → F·}I4 = Ir_a(I0,'(') = { F → (·E), E → ·E+T, E → ·T, T → ·T*F, T → ·F, F → ·(E), F → ·id}I5 = Ir_a(I0,id) = { F → id·}3.4.8 Colección canónica LR(0)
Sección titulada «3.4.8 Colección canónica LR(0)»La colección canónica LR(0) es el conjunto de todos los estados posibles del autómata LR, es decir, de todos los conjuntos distintos de elementos que se obtienen aplicando clausura e Ir_a.
Procedimiento
Sección titulada «Procedimiento»- Construir la gramática aumentada.
- Tomar:
I0 = clausura(S' → ·S$)- Calcular
Ir_a(I,X)para todos los símbolos posiblesX. - Repetir el proceso con los nuevos conjuntos hasta que no aparezcan estados nuevos.
Ejemplo completo
Sección titulada «Ejemplo completo»Continuando el ejemplo anterior, además de I0 a I5 se obtienen:
I6 = Ir_a(I1,+) = { E → E+·T, T → ·T*F, T → ·F, F → ·(E), F → ·id}I7 = Ir_a(I2,*) = { T → T*·F, F → ·(E), F → ·id}I8 = Ir_a(I4,E) = { F → (E·), E → E·+T}I9 = Ir_a(I6,T) = { E → E+T·, T → T·*F}I10 = Ir_a(I7,F) = { T → T*F·}I11 = Ir_a(I8,)) = { F → (E)·}Cada I_j se interpreta como un estado del autómata LR.
Tabla de transiciones (Ir_a)
Sección titulada «Tabla de transiciones (Ir_a)»Para el ejemplo:
| Estado | id | + | * | ( | ) | $ | E | T | F |
|---|---|---|---|---|---|---|---|---|---|
0 | 5 | 4 | 1 | 2 | 3 | ||||
1 | 6 | ||||||||
2 | 7 | ||||||||
3 | |||||||||
4 | 5 | 4 | 8 | 2 | 3 | ||||
5 | |||||||||
6 | 5 | 4 | 9 | 3 | |||||
7 | 5 | 4 | 10 | ||||||
8 | 6 | 11 | |||||||
9 | 7 | ||||||||
10 | |||||||||
11 |
3.4.9 Tabla de acciones SLR(1)
Sección titulada «3.4.9 Tabla de acciones SLR(1)»La gran pregunta es: ¿cuándo desplazar y cuándo reducir?
La respuesta se codifica en una tabla de acciones.
Procedimiento de construcción
Sección titulada «Procedimiento de construcción»- Construir la colección canónica LR(0):
I0, I1, ..., In. - Si en
Ijhay un elemento:
A → α·aβcon a terminal e Ir_a(Ij,a)=Ik, entonces:
accion[j,a] = desplazar e ir a k- Si en
Ijhay un elemento:
A → α·entonces para todo s ∈ SIGUIENTES(A):
accion[j,s] = reducir A → α- Si en
Ijestá:
S' → S·$entonces:
accion[j,$] = aceptar- Las celdas vacías indican error.
Ejemplo
Sección titulada «Ejemplo»Con la numeración:
(1) E → E + T(2) E → T(3) T → T * F(4) T → F(5) F → (E)(6) F → idla tabla SLR(1) es:
| Estado | id | + | * | ( | ) | $ | E | T | F |
|---|---|---|---|---|---|---|---|---|---|
0 | d5 | d4 | 1 | 2 | 3 | ||||
1 | d6 | OK | |||||||
2 | r2 | d7 | r2 | r2 | |||||
3 | r4 | r4 | r4 | r4 | |||||
4 | d5 | d4 | 8 | 2 | 3 | ||||
5 | r6 | r6 | r6 | r6 | |||||
6 | d5 | d4 | 9 | 3 | |||||
7 | d5 | d4 | 10 | ||||||
8 | d6 | d11 | |||||||
9 | r1 | d7 | r1 | r1 | |||||
10 | r3 | r3 | r3 | r3 | |||||
11 | r5 | r5 | r5 | r5 |
Aquí:
dnsignifica desplazar e ir al estadon;rnsignifica reducir por la reglan;OKsignifica aceptar.
Interpretación del estado 9
Sección titulada «Interpretación del estado 9»El conjunto:
I9 = { E → E + T·, T → T·*F}mezcla dos ideas:
E → E + T·permite reducir por la regla(1)conSIGUIENTES(E) = {+, ), $}.T → T·*Fobliga a desplazar*e ir al estado7.
3.4.10 Procedimiento de análisis SLR(1)
Sección titulada «3.4.10 Procedimiento de análisis SLR(1)»El analizador usa:
- una pila de estados;
- la tabla
accion; - la tabla
Ir_aogoto; - la entrada.
Algoritmo
Sección titulada «Algoritmo»-
Inicializar la pila con el estado
0. -
Sea
iel estado en la cima yael símbolo actual de entrada. -
Consultar
accion[i,a].- Si es
dn, desplazar la entrada, ir al estadony apilarlo. - Si es
rn, reducir por la reglaA → β. Si|β| = m, desapilarmestados. Si ahorahes la nueva cima, apilarIr_a(h,A). - Si es
aceptar, terminar con éxito. - Si está vacía, error.
- Si es
Ejemplo: análisis de id*id+id$
Sección titulada «Ejemplo: análisis de id*id+id$»| Estados | Símbolos reconocidos | Entrada | Acción |
|---|---|---|---|
0 | id*id+id$ | Desplazar e ir a 5 | |
05 | id | *id+id$ | Reducir con (6) F → id; ir a Ir_a(0,F)=3 |
03 | F | *id+id$ | Reducir con (4) T → F; ir a Ir_a(0,T)=2 |
02 | T | *id+id$ | Desplazar e ir a 7 |
027 | T* | id+id$ | Desplazar e ir a 5 |
0275 | T*id | +id$ | Reducir con (6) F → id; ir a Ir_a(7,F)=10 |
02710 | T*F | +id$ | Reducir con (3) T → T*F; ir a Ir_a(0,T)=2 |
02 | T | +id$ | Reducir con (2) E → T; ir a Ir_a(0,E)=1 |
01 | E | +id$ | Desplazar e ir a 6 |
016 | E+ | id$ | Desplazar e ir a 5 |
0165 | E+id | $ | Reducir con (6) F → id; ir a Ir_a(6,F)=3 |
0163 | E+F | $ | Reducir con (4) T → F; ir a Ir_a(6,T)=9 |
0169 | E+T | $ | Reducir con (1) E → E+T; ir a Ir_a(0,E)=1 |
01 | E | $ | Aceptar |
3.4.11 Conflictos en tablas SLR(1)
Sección titulada «3.4.11 Conflictos en tablas SLR(1)»Una gramática es SLR(1) si en cada celda de la tabla aparece como máximo una acción.
Si esto no ocurre, hay conflicto.
Tipos de conflicto
Sección titulada «Tipos de conflicto»-
Desplazamiento-reducción Aparecen simultáneamente una acción de desplazar y otra de reducir.
-
Reducción-reducción Aparecen simultáneamente dos posibles reducciones.
En la práctica:
- ante un conflicto desplazamiento-reducción, algunos generadores eligen por defecto desplazar;
- ante un conflicto reducción-reducción, lo razonable suele ser rediseñar la gramática.
Ejercicio 10
Sección titulada «Ejercicio 10»Sea la gramática:
<sentencia> ::= = <valorizq> [ <corchete> ]<valorizq> ::= id1 | id2<corchete> ::= <corchete> + <valorizq> | <valorizq>Se pide:
- Obtener la colección canónica de conjuntos de elementos LR(0).
- Generar la tabla SLR(1). ¿Es una gramática SLR(1)?
- Analizar la cadena
=id1[id1+id2].
Solución ejercicio 10
Sección titulada «Solución ejercicio 10»Tomamos la siguiente notación:
S = <sentencia>V = <valorizq>C = <corchete>Gramática aumentada:
S' → S$(1) S → = V [ C ](2) V → id1(3) V → id2(4) C → C + V(5) C → Va) Colección canónica de elementos LR(0)
Sección titulada «a) Colección canónica de elementos LR(0)»I0 = clausura({S' → ·S$}) = { S' → ·S$, S → ·=V[C]}I1 = Ir_a(I0,S) = { S' → S·$}I2 = Ir_a(I0,=) = { S → =·V[C], V → ·id1, V → ·id2}I3 = Ir_a(I2,V) = { S → =V·[C]}I4 = Ir_a(I2,id1) = { V → id1·}I5 = Ir_a(I2,id2) = { V → id2·}I6 = Ir_a(I3,[) = { S → =V[·C], C → ·C+V, C → ·V, V → ·id1, V → ·id2}I7 = Ir_a(I6,C) = { S → =V[C·], C → C·+V}I8 = Ir_a(I6,V) = { C → V·}I9 = Ir_a(I7,]) = { S → =V[C]·}I10 = Ir_a(I7,+) = { C → C+·V, V → ·id1, V → ·id2}I11 = Ir_a(I10,V) = { C → C+V·}Transiciones:
I0 --S--> I1I0 --=--> I2
I2 --V--> I3I2 --id1--> I4I2 --id2--> I5
I3 --[--> I6
I6 --C--> I7I6 --V--> I8I6 --id1--> I4I6 --id2--> I5
I7 --]--> I9I7 --+--> I10
I10 --V--> I11I10 --id1--> I4I10 --id2--> I5Conjuntos SIGUIENTES
Sección titulada «Conjuntos SIGUIENTES»Son necesarios para construir la tabla SLR(1):
SIGUIENTES(S) = {$}SIGUIENTES(C) = {+, ]}SIGUIENTES(V) = {[, +, ]}Justificación breve:
Ses el símbolo inicial.- En
S → =V[C], detrás deVaparece[y detrás deCaparece]. - En
C → C+V, detrás del primerCaparece+yVqueda al final, así que heredaSIGUIENTES(C). - En
C → V,VheredaSIGUIENTES(C).
b) Tabla SLR(1)
Sección titulada «b) Tabla SLR(1)»| Estado | = | id1 | id2 | [ | ] | + | $ | S | V | C |
|---|---|---|---|---|---|---|---|---|---|---|
0 | d2 | 1 | ||||||||
1 | OK | |||||||||
2 | d4 | d5 | 3 | |||||||
3 | d6 | |||||||||
4 | r2 | r2 | r2 | |||||||
5 | r3 | r3 | r3 | |||||||
6 | d4 | d5 | 8 | 7 | ||||||
7 | d9 | d10 | ||||||||
8 | r5 | r5 | ||||||||
9 | r1 | |||||||||
10 | d4 | d5 | 11 | |||||||
11 | r4 | r4 |
Conclusión: sí es una gramática SLR(1), porque no aparece ningún conflicto desplazamiento-reducción ni reducción-reducción.
c) Análisis de la cadena =id1[id1+id2]
Sección titulada «c) Análisis de la cadena =id1[id1+id2]»Entrada:
= id1 [ id1 + id2 ] $Usando la tabla anterior:
| Pila de estados | Entrada | Acción |
|---|---|---|
0 | =id1[id1+id2]$ | d2 |
02 | id1[id1+id2]$ | d4 |
024 | [id1+id2]$ | r2: V → id1, ir a Ir_a(2,V)=3 |
023 | [id1+id2]$ | d6 |
0236 | id1+id2]$ | d4 |
02364 | +id2]$ | r2: V → id1, ir a Ir_a(6,V)=8 |
02368 | +id2]$ | r5: C → V, ir a Ir_a(6,C)=7 |
02367 | +id2]$ | d10 |
0236710 | id2]$ | d5 |
02367105 | ]$ | r3: V → id2, ir a Ir_a(10,V)=11 |
023671011 | ]$ | r4: C → C + V, ir a Ir_a(6,C)=7 |
02367 | ]$ | d9 |
023679 | $ | r1: S → =V[C], ir a Ir_a(0,S)=1 |
01 | $ | OK |
La cadena queda aceptada.
3.4.12 Gestión de errores en analizadores SLR(1)
Sección titulada «3.4.12 Gestión de errores en analizadores SLR(1)»En un analizador SLR(1), hay error cuando desde el estado actual de la cima de la pila no existe ninguna acción definida para el símbolo actual de entrada.
En ese instante:
- la pila representa el contexto a la izquierda del error;
- la parte no consumida de la entrada representa el contexto a la derecha.
La recuperación intenta modificar la pila y/o la entrada hasta reenganchar el análisis.
Métodos mencionados en el tema
Sección titulada «Métodos mencionados en el tema»-
Métodos heurísticos Consisten en programar rutinas específicas para determinadas celdas de error.
-
Método de análisis de transiciones Se baja por la pila hasta encontrar un estado
dtal que existaIr_a(d,A)para algún no terminalA. Después se descartan símbolos de entrada hasta hallar uno que pueda seguir aAsegún la gramática. Entonces se apilaIr_a(d,A)y se continúa.
3.5 Bibliografía
Sección titulada «3.5 Bibliografía»- A. V. Aho, R. Sethi, J. D. Ullman. Compiladores. Principios, técnicas y herramientas. Addison Wesley, 1990.
- M. Alfonseca, M. de la Cruz, A. Ortega, E. Pulido. Compiladores e intérpretes: teoría y práctica. Pearson Educación, 2006.