Final del 02-08-11 - mdereg1

Anuncio
Final del 11/08/08 (Teoría de Lenguajes)
1. Demuestre la equivalencia entre gramáticas regulares a izquierda y a derecha
2. Demuestre el lema de Pumping para lenguajes independientes de contexto
3. Muchos puntos sobre parsers:



Defina gramática de precedencia simple
Algo del automata LR(0)
Dé un ejemplo de una gramática que no sea de precedencia simple pero si LR(0):
ver acá.
LR(0) no Precedencia simple
Vale que es LR(0) por no generar conflictos, y no es de precedencia simple porque se dan
las relaciones b > b (segun la primer produccion) y b = b (segun la segunda).
S -> aSb | bb
No LR(1)
Genera conflicto shift-reduce con las dos últimas producciones, y en ambas el lookahead es
el mismo.
S -> aSb
S -> ab
S -> abb
No LR(k)
Una gramática recursiva a izquierda cualquiera no es LR(k) para ningún k.
S -> Ab | Bc
A -> Aa | λ
B -> Ba | λ
Nota: Eso está mal. Es cierto para LL(k) pero los parsers LR si soportan recursión a
izquierda y derecha.
Además, si ese fuera el caso alcanzaría con la gramática
S -> Ab
A -> Aa | λ
Pero su autómata LR(1) es bastante simple. Es más, creo que es LR(0).
1
LR(2) no LR(1)
Si bien la siguiente gramática no es LR(1), notar que siempre es posible generar una
gramática LR(1) a partir de una LR(k) que capture el mismo lenguaje.
S
A
B
C
D
E
->
->
->
->
->
->
AB
a
CD | aE
ab
bb
bba
LR(0)
Gramática LR(0) común y corriente.
S -> X$
X -> (X)
X -> ()
SLR no LR(0)
Ejemplo de gramática SLR no LR(0), similar a la anterior.
S -> X$
X -> (X)
X -> λ
LR(1) no SLR
Similar a la anterior, pero esta además de matchear igual nro de paréntesis abiertos y
cerrados, matchea un único paréntesis abierto.
S -> X$
X -> Y | (
Y -> (Y)| λ
LR(1) no LALR
Mismo ejemplo que el apunte de definiciones.
S'
S
A
B
->
->
->
->
S
aAd | bBd | aBe | bAe
c
c
Que genera el lenguaje {acd, ace, bcd, bce}. Esta gramática es LR(1). Si construímos el
autómata LR(1), obtenemos el conjunto de items {[A→c.,d],[B→c.,e]} para el prefijo ac y
{[A→c.,e],[B→c.,d]} para bc. Si los unimos:
2
A -> c., d/e
B -> c., d/e
Vemos que introdujimos un conflicto reduce/reduce, dado que tanto [A→c] como [B→c]
pueden ser utilizadas para reducir para los caracteres d y e.

Como se genera el automata del parser LR(0)
4. Sea L={ww} sobre {a,b}, y G la gramática con las siguientes producciones: ... Dé una
DDS para que la gramática G acepte al lenguaje L
Final del XX/12/09 (Teoría de Lenguajes)
Mirá la verdad que nuestro caso fue bastane particular: Jacobo puso 4 ejercicios en el
pizarron -para hacer escritos, aunque cuando son 2 o 3 los que rinden suele tomar oral- y
dijo "tienen que hacer 3 bien para aprobar, y 10 min para elegir si se quedan o no"... nadie
podía hacer ni dos. Había dos demostraciones muy sacadas de la galera que la verdad no
recuerdo bien. Conclusión, antes de que nos fueramos todos sin rendir, acepto tomar
Pumping LLC a cambio de uno de Maq de Turing.
Entonces de lo que me acuerdo es:
Pumping LLC: perfecto al pie de la letra.
Maquina de Turing: demostración (recuerdo que era una de "de cuantas cintas se necesitan
para probar xxx...").
Un típico ejericio que está en todos los finales de parsers LL1. No lo recuerdo ahora pero si
hiciste tres finales de práctica seguro esta en al menos 2.
Final del 17/09/10 (Teoría de Lenguajes)
1. (Escrito) Describir y demostrar la equivalencia entre AFND-L y AFND
2. (Escrito) Demostrar que existe un lenguaje LIC que no es LICD
3. (Escrito) Demostrar que todo LSC es recursivo
4. (Oral) Definir gramatica LR(k) (la definicion "nueva")
Final del XX/11/10 (Teoría de Lenguajes)
1. Transformaciones entre AP por estado final a AP por pila.
3
2. Demostrar que existe un lenguaje que no es LICD y es LIC.
3. Preguntas sobre el APD y por qué si x pertenece a L entonces no puede pertenecer xa a L
en un APD.
4. Pumping para LIC (completo + lema de altura).
5. Preguntas sobre maquina de turing y como se pasa a MTND.
6. Definiciones de Pivote, Prefijo, Item para LR(1) y LR(0).
7. Automata Minimo + el lema auxiliar.
Final del XX/12/10 (Teoría de Lenguajes)
Se dividió en tres partes:
Escrito general:
1. automata reducido es minimo. Para eso habia q enunciar el lema ese de la cant de estados
(el q se demuestra c la f inyectiva) y ese podias demostrarlo o no.
2. Demostrar el teorema que dice que los prefijos viables son un lenguaje regular (En lr). en
realidad solo pedia demostrarlo p 1 lado.
Escrito personal:
3. Pumping para LIC
Oral:
Cosas sobre maquinas de turing (el del probar que hay un algoritmo, que le haces el grafo)
y unas cosas sobre el lenguaje Diagonal.
Final del 01/03/11 (Teoría de Lenguajes)
Fue oral, a uno le tomaron:
1. Indistinguibilidad de orden k: las propiedades y demostrar la de k+1 = k => k+n = k
2. Pumping para LIC y lema de altura
3. Definición de pivote, prefijo y prefijo viable para LR(0)
4
4. De qué tipo es el conjunto generado por todos los prefijos viables (es regular)
5. Cómo se construye el autómata que se usa en la demostración de los prefijos viables.
A otro le tomaron, entre otras cosas:
Definición de Máquina de Turing, y MTND => MTD
Final del XX/05/11 (Teoría de Lenguajes)
1. Demostrar que efectivamente el autómata obtenido con el alg de minimización es el
mínimo (demo del lema y la demo propiamente dicha)
2. Defina prefijo viable
3. Demostrar que el conjunto de prefijos viables es un leng regular (mostrar como se arma
el autómata ND y listo)
4. La demo de que es item valido si pertenece a la función de transición para un caso.
5. Cómo son las producciones de los leng de tipo 0 y tipo 1
6. En los ALA (para reconocer leng de tipo 1) como se determina la distancia entre los
símbolos que delimitan la cinta? (La respuesta es que se determina con la cadena de
entrada)
Otro examen oral:
1. El lema y el teorema relacionados con minimizacion
2. Automatas de pila a Gramaticas libres de contexto y viceversa.
3. Automata para ver los estados de un parser LR(0)
4. Pumping para Libres de contexto
5. Maquina de Turing
6. Clasificacion de Chomsky (toda)
7. Qué reconoce un lenguaje tipo 1.
5
Definiciones y teoremas (Teoría de Lenguajes)
Gramaticas LL(1)
Forma sentencial
Cualquier cadena que puedo derivar a partir del símbolo distinguido.
Primeros
primeros: V*
P(Vt)
primeros(γ) = { t in Vt / γ -*-> α, α = t α'}
Algoritmo para calcular primeros:
Para cada x en V
Si x esta en Vt
primeros(x) := {x}
Si x esta en Vn y x
Y1Y2...Yk esta en P
Si a pertenece a primeros(Yi) con Y1,...,Yi-1 anulables (forall i<=k)
poner a en primeros(x)
Siguientes
siguientes: Vn --> P(Vt)
siguientes(N) = { t in Vt / S -*-> ... Nt ... }
Algoritmo para calcular siguientes:
Agregar $ a siguientes(S)
Repetir hasta que siguientes no cambie
Si A -> α B β
agregar primeros(β) a siguientes(B)
Si A -> α B o A -> α B β con β anulable
agregar los siguientes(A) a los siguientes(B)
Símbolos directrices (SD)
6
Gramática LL(1)
Sea G = <Vn, Vt, P, S>, es LL(1) si, siendo N no terminal y β y β' cadenas de V*, vale:
Esto implica que la gramatica G debe ser no ambigua ni recursiva a izquierda.
Equivale a pedir las siguientes condiciones dado cualquier



:
No puede pasar que tanto α como β deriven en strings que comiencen con un mismo
terminal a.
A lo sumo uno de los dos puede derivar en la cadena vacia.
Si β deriva en la cadena vacia, entonces α no puede derivar en ninguna cadena que
comience con un terminal en siguientes(A).
Analizador Sintáctico Descendente de Pila (A. S. D. P.) con tabla
Inicialización
Agregamos la siguiente producción:
S'
S#
Tenemos una lista w con la cadena a analizar, t es el símbolo apuntado en dicha lista.
Sea M una matriz con tantas columnas como símbolos terminales y tantas filas como
símbolos no terminales tal que:
M(a, s) = s
α, si a pertenece a SD(s
α)
Algoritmo
Repetir
Si tope in Vt
Si tope === t
extraigo tope de la pila
avanzo 1 en w
Si no
error
Si no
Si M(t, tope) === (tope
Y1Y2 ... Yk)
extraigo tope de la pila
apilo YkYk-1 ... Y1
Si no
error
Hasta que tope === # && t === #
7
Observación: No comerse el detalle de que el lado derecho de la producción Y1Y2 ... Yk
se apila al revés de manera que el primer símbolo quede como tope.
Oh! Mi gramática no es LL(1)... Y ahora qué hago?!?!?
Tenés dos posibles maneras de zafarla. Una es factorizar:
A
α β1 | α β2 | ... | α βK | γ1 | ... | γN
Pasa a ser:
A
α A' | γ1 | ... | γN
A'
β1 | β2 | ... | βK
O, si tu gramática es recursiva a izquierda, podés "desrecursivizar":
A
A α1 | ... | A αN | β1 | ... | βK
Pasa a ser:
A
β1 A' | ... | βK A'
A'
α1 A' | ... | αN A' | λ
Gramáticas y parsers LR
Los parsers LR(K) son (al igual que los parsers LL) una familia de parsers para analizar una
clase grande de gramáticas libres de contexto. Se caracterizan por:




Ser analizadores sintácticos ascendentes (analizan la sintaxis de abajo hacia arriba)
Por leer la entrada de izquierda a derecha para encontrar y producir una derivación más a
la derecha de la cadena de entrada (LR de Left to right Rightmost derivation).
La K corresponde a la cantidad de símbolos de entrada de "lookahead" que se tienen en
cuenta para tomar las decisiones de parseo (o sea, es como en los analizadores descentes
predictivos, espía 0, 1 o más caracteres que vienen para decidir que producción usar).
Se usan mucho porque son más potentes que los LL y porque son eficientes, pero tienen la
desventaja de que es difícil programarlos a mano, por eso se suele usar generadores de
parsers (que se encargan de armar las tablas usadas en los algoritmos).
Hay distintos tipos de parsers LR que son más y menos tolerantes a problemas (LR(0),
SLR, LR(1) y LALR). Se habla de gramáticas LR(K) como aquellas gramáticas cuyo
lenguaje puede ser parseado por un parser LR(K) sin problemas. Entonces hay gramáticas
que van a ser LR(1) pero no LR(0), o SLR pero no LR(0), etc. La forma básica del
algoritmo LR es la misma para todos, lo que cambia en cada uno es la construcción de una
tabla que se usa para tomar decisiones durante el mismo.
8
Forma general de los algoritmos LR
Todos los algoritmos LR funcionan con un stack y una tabla de parseo, y las operaciones
básicas que hacen hasta aceptar la cadena o generar un error son shift y reduce
(desplazamiento y reducción). El algoritmo mantienene en el stack símbolos de la
gramática, que tarde o temprano van a formar parte del lado derecho de una producción
usada para generar la cadena de entrada. Lo que hace es ir consumiendo símbolos de
entrada y agregándolos en el stack (a esto se le llama hacer shifts) hasta que decide que
tiene suficiente información e "identifica" en el tope del stack uno o más símbolos que son
la parte derecha de una producción. Entonces hace una reducción, que consiste en
reemplazar todos los símbolos del tope del stack que coinciden con la parte derecha de la
producción por la parte izquierda de la misma. Una vez que hace esto continua haciendo
shift/reduce hasta que redujo toda la entrada al símbolo terminal, momento en el cual el
parser "acepta" la entrada (y en el camino se enocontró la derivación de la cadena, que se
puede reconstruir conociendo todos los reduce que se hicieron).
Lo difícil del algoritmo es cómo saber cuando hacer un shift y cuando hacer un reduce. Lo
primero que uno piensa es que como hacer "pattern matching" entre el stack y los lados
derechos de todas las producciones es fácil, simplemente hago shift hasta tener en el stack
algo que matchee y ahí hago una reducción. El problema es que esta actitud golosa de
suponer que hay que usar la primer producción que "funcione" (habiendo analizado sólo un
prefijo de la cadena de entrada) no siempre anda, ya que por hacer esa reducción podemos
llegar a algo en el stack que no hay manera de seguir reduciendo al símbolo S. Cómo se
hace entonces? Bueno, lo groso de los parsers LR es que tienen como invariante que en el
stack mantienen lo que se conoce como "prefijos viables". Y hay una propiedad que dice
que el conjunto de los prefijos viables de un lenguaje LR es un lenguaje regular, con lo cual
hay un AFND que puede identificar estos prefijos viables.
Entonces, los algoritmos LR lo que hacen es:
1. Construir el AFND de este lenguaje
2. A partir de este calculan una tabla que es lo que usan para decidir si hay que hacer un shift
o un reduce. Al calcular esta tabla pueden surgir conflictos que indican que la gramática no
es del tipo LR que se estaba tratando de usar.
3. Una vez con la tabla terminada y sin conflictos el algoritmo es muy fácil
Los distintos algoritmos LR
Construir el AFND ideal o necesario una una gramática cualquiera no es fácil y puede
llegar a tener miles de estados, ya que cada estado del AFND representa un momento
determinado en el parsing de una cadena de entrada cualquiera, y tiene que tener implícita
la información de todo lo se leyó hasta el momento.
Hay entonces formas de construir AFND más simples y que sirven en muchas situaciones
(aunque no todas).
9




Los más simples son los parsers LR(0), que en el AFND no tienen lookahead.
Los SLR usan el mismo AFND que los LR(0), pero cuando usan el AFND para armar la tabla
que se usa en el algoritmo, se utiliza la función SIGUIENTES para filtrar algunas
reducciones inválidas
Los parsers LR(1) tienen un lookahead de un símbolo y construyen un AFND donde hay
distintos estados que se diferencian por el símbolo de lookahead. Como consecuencia hay
muchísimos más estados que un parser LR(0), y pasan a ser menos eficientes.
Los parsers LALR son una solución de compromiso entre LR(1) y LR(0), ya que lo que hacen
esencialmente es podar el AFND LR(1) agrupando un montón de estados que representan
lo mismo. Consigue así achicar la cantidad de estados drásticamente, pero en
contrapartida puede llegar a crear conflictos de tipo reduce-reduce (que antes no había) al
agrupar estados.
Vale que
. La forma de calcular el AFND en
los cuatro casos es parecida, y se basa en dos funciones: Clausura y Goto
Estas funciones tienen su versión para LR(0) y otra para LR(1). Una vez definidas, cada
parser LR tiene pequeñas diferencias en el algoritmo para armar la tabla de parseo.
Resumiendo, para hacer el parser LR de una gramática necesitamos conocer:


El algoritmo general de parseo LR basado en el stack y la tabla
El algoritmo específico para construir la tabla y el AFND. A su vez, este algoritmo
dependerá de:
o La función clausura
o La función Goto
Los algoritmos en detalles
Para los detalles de estas funciones en cada parser recomiendo altamente leer este ppt:
Algoritmos LR0, SLR, LR1 y LALR Versión Online (Slideshare.net)
Es didáctico, con ejemplos paso a paso y si bien parece largo por tener cientos de slides, la
mayoría son por los ejemplos paso a paso. Recomiendo en serio leerlo si quieren ver como
construir los AFND en detalle.
Gramaticas de Atributos
Una gramatica de atributos es una gramatica en la que se le asignan atributos (variables
tipadas) a los no terminales de cada produccion y se opera a partir de los mismos, con
asignaciones y funciones asociadas a cada produccion. Si bien los terminales no tienen
atributos propiamente dichos, se puede considerar que tienen ciertos valores asignados por
el lexer, y a veces manejarlos como atributos. Por ejemplo, la siguiente es una gramatica no
10
ambigua para operaciones de suma y producto de numeros, como ser 4*(1+2), que obtiene
en el atributo val de la raiz el valor de la expresion.
int L.val, E.val, T.val, F.val
L -> E
{L.val := E.val}
E -> E1 + T
{E.val := E1.val + T.val}
E -> T
{E.val := T.val}
T -> T1 * F
{T.val := T1.val * F.val}
T -> F
{T.val := F.val}
F -> ( E )
{F.val := E.val}
F -> digit
{F.val := value(digit)}
La funcion value() aplicada al terminal digit devuelve el valor del token (notar que al
definir la gramatica, se asume que el lexer ya convirtio los terminales a tokens). Luego, a
partir de dicho valor, al evaluar cada produccion se resuelven los atributos incluidos.
Es conveniente siempre antes de dar las producciones de la gramatica, indicar cuales son
los atributos de cada no terminal, su tipo y si son heredados o sintetizados, para verificar
que no se hayan cometido errores.
Es importante no confundir gramatica de atributos y definicion dirigida por sintaxis (o
DDS). Si bien las dos son iguales en apariencia, la primera no admite funciones con efectos
colaterales (como por ejemplo print) mientras que la segunda si.
Atributos Sintetizados y Heredados
Los atributos pueden ser sintetizados o heredados. Los atributos sintetizados son aquellos
que se asignan en funcion de los atributos de los hijos del no terminal al que pertenecen,
por ejemplo, val en la gramatica anterior. Un atributo sintetizado que aparezca en el lado
izquierdo de una produccion siempre debe ser asignado.
Un atributo heredado es aquel cuyo valor depende de los atributos de su padre o hermanos
en una produccion. Siempre que un heredado aparece en la parte derecha de una produccion
debe ser asignado. Por ejemplo, en producciones como las siguientes, los atributos z e y de
Z y de Y son heredados.
S -> X Y Z
Y -> aZ
{Y.y := X.x ; Z.z = X.x}
{Z.z := Y.y}
Un atributo de un no terminal puede ser solamente sintetizado o heredado. No puede darse
que en algunas producciones se comporte como heredado y en otras como sintetizado.
Los atributos son funcion siempre de otros atributos o constantes. Pueden usarse todo tipo
de funciones: operaciones aritmeticas, operador ternario if-then-else (como el cond ? val1 :
val2 de cpp), etc; siempre y cuando no tengan efectos colaterales.
11
A partir de esta clasificacion de atributos, se pueden clasificar las gramaticas. Una
gramatica S-atribuida es una gramatica que solamente tiene atributos sintetizados; y una
gramatica L-atribuida es una gramatica en la que todos sus atributos heredados dependen de
atributos que se encuentran mas a la izquierda en la produccion. Esto permite que
parseando de arriba hacia abajo y de izquierda a derecha puedan calcularse todos los
atributos en una sola pasada.
Notar que el que una gramatica sea S-atribuida implica que sea L-atribuida, pero no al
reves.
Arbol decorado
Dada una gramatica de atributos y una cadena de entrada, el arbol de analisis sintactico es
el arbol construido por el parser al analizar la cadena. Dicho arbol se dice decorado si
ademas tiene los atributos de cada simbolo y sus valores.
Orden de evaluacion
Para determinar el orden de evaluacion de los atributos se puede construir un grafo de
dependencias. El digrafo tiene en los nodos los atributos de cada simbolo y los arcos
representan dependencias (es decir, si A.a := f(B.b, C.c), entonces A.a depende de B.b y
C.c). Cabe destacar que el grafo se construye para una cadena de entrada en particular, es
decir, no hay un grafo para una gramatica, sino un grafo para una cadena en una gramatica
(pensar en el arbol decorado, donde se dejan solamente los atributos, y los arcos marcan
dependencias).
Una gramatica es circular si existe alguna cadena para la cual su grafo de dependencias
tiene algun ciclo dirigido. Esto implica que no es posible calcular los valores de sus
atributos.
En caso de que el digrafo sea aciclico, entonces se puede determinar un orden de
evaluacion para los atributos siguiendo los arcos. Si la gramatica es ademas L-atribuida, no
es necesario analizar este grafo pues se sabe que un orden de evaluacion valido para
cualquier cadena de entrada es de arriba hacia abajo y de izquierda a derecha.
Condition
Ademas de las asignaciones, en las gramaticas de atributos es posible incluir una clausula
condition. Esta clausula recibe una expresion booleana que depende de los valores de los
atributos de los simbolos de la produccion y permite rechazar una cadena. Por ejemplo, la
siguiente produccion rechaza la cadena si los atributos size de X e Y no coinciden.
S -> X Y
{ Condition: X.size = Y.size }
12
Traduccion Dirigida por Sintaxis
Denominada TDS o esquema de traduccion, es una gramatica en la que se asignan atributos
(variables tipadas) a los simbolos de cada produccion, y se agregan bloques de codigo a las
partes derechas de las producciones, los cuales pueden estar intercalados entre los simbolos,
referenciar a los atributos y tener efectos colaterales.
La diferencia entre una TDS y una gramatica de atributos es, entonces, que la primera tiene
bloques de codigo ubicados en las partes derechas de las producciones, mientras que la
segunda tiene exclusivamente clausulas de asignacion (y condicion) asociadas a cada
produccion.
La diferencia entre una TDS y DDS es que la TDS permite codigo intercalado en las
producciones, y la DDS en rigor solamente permite asignaciones y llamadas a funciones
(que pueden generar efectos colaterales) asociadas a una produccion.
El siguiente ejemplo toma declaraciones de tipos de la forma tipo var, var, var... y los
guarda en una tabla de simbolos tras parsearlos.
D
T
T
L
L
->
->
->
->
->
T {L.tipo := T.tipo} L
int {T.tipo := "int"}
bool {T.tipo := "bool"}
var {add(var.nombre, L.tipo); L2.tipo = L1.tipo} L2
var {add(var.nombre, L.tipo}
No hay restriccion para el codigo a utilizar dentro de los bloques. Puede usarse el
pseudocodigo que se desee a conveniencia, cuanto mas sencillo mejor.
Orden de Ejecucion
El arbol de analisis sintactico se genera de arriba hacia abajo y de izquierda a derecha (al
igual que como se evaluaban las gramaticas L-atribuidas). A medida que se recorren las
producciones, se ejecuta el codigo contienen en el orden correspondiente. Esto impone
ciertas restricciones en el uso de los atributos en los bloques de codigo, pues los mismos
deben estar inicializados siempre antes de ser usados.
Atributos Sintetizados y Heredados
La diferenciacion entre atributos sintetizados y heredados se mantiene en las TDS y debe
respetarse el equivalente a las reglas de asignacion que se manejaban en las gramaticas de
atributos.

Un atributo heredado de un simbolo S en la parte derecha de una produccion debe ser
asignado en un bloque de codigo antes del simbolo S.
13

Un bloque de codigo no puede referirse a un atributo sintetizado de un simbolo que se
encuentre a la derecha del codigo, pues el simbolo aun no fue parseado y sus atributos
sintetizados no fueron inicializados.

Un atributo sintetizado que aparezca en la parte izquierda de una produccion solo puede
asignarse cuando todos los atributos de los que depende han sido asignados
Rechazar una cadena
Puesto que ya no se manejan clausulas de asignacion y de condicion (como en la gramatica
de atributos), para rechazar una cadena basta con llamar a la funcion "error", burbujear un
valor "error" dentro de algun atributo o lanzar una excepcion.
14
15
16
17
18
Descargar