Mostrando las entradas con la etiqueta lenguage y compiladores. Mostrar todas las entradas
Mostrando las entradas con la etiqueta lenguage y compiladores. Mostrar todas las entradas

12 de enero de 2017

Lenguaje y Compiladores - Parsing Bottom Up #2

Apuntes de la clase del Jueves 12-1-2017, el próximo jueves sera examen de todo lo entendido el año pasado.

Es importante determinar cuando usar SHIFT o REDUCE en la Pila, con el algoritmo de resolución de conflictos resolveremos los conflictos de caso SHIFT/REDUCE y REDUCE/REDUCE.

Recordando:


La acción SHIFT mueve o coloca un nuevo valor terminal en la pila.

____ | XYZ => ____X | YZ

La acción REDUCE puede eliminar cero o mas símbolos en la pila (del lado derecho), o colocar un símbolo no-terminal en la pila (en el lado izquierdo de la producción).

Los conjuntos representados por expresiones regulares son llamados conjuntos regulares.

Una gramática es libre de contexto (GLC) cuando en el lado izquierdo de la gramática solo aparece símbolos terminales.


Nuevos conceptos:


Handle: es una reducción que permite hacer otras reducciones hasta llegar al símbolo inicial.

Prefijos viables: es una parte del handle que se llevara al símbolo inicia.

LWB Es un prefijo viable (Alfa, beta y omega)
LW    Es un prefijo viable (tambien las derivaciones de LWB son prefijos viables)
L       Es un prefijo viable

Item: es una producción con un punto "." en algun lugar del lado derecho de la produccion. Para T -> (E) los items son:

T->. ( E )
T->  (.E )
T->  ( E.)
T->  ( E ).

Nota: Los items que vayan a lambda son LR(0) y su item es

T->L => T -> . (L es Lambda)

Donde el cero de LR representa la cantidad de pasos que ha tomado.

Condiciones:


1. Se debe aplicar REDUCE solo si el resultado permite la eventual transición al símbolo inicial.

2. En un caso SHIFT/REDUCE, el handle debe estar en el tope de la pila y jamas dentro de ella. El detalle es que no existen algoritmos eficientes para detectar los handles.

3. Para cualquier gramática, el conjunto de prefijos viables es un conjunto regular.

Nota: todas las gramáticas para un compilador son SLR.

Reconocer los prefijos viables (ver laminas 9):


Para poderlos reconocer debemos crear un AFN (autómata finito no determinista), lo siguiente son las condiciones para poder construir un AFN. 

  1.  Para un estado S' -> .E (Notese el punto), la transición será S' -> E. y la llamamos transición E.
  2. Para cada item E -> L . XB y producción X -> Y, realizaremos una transición por cada producción de X. Se va a la transición inicial de la producción que trascendió. En ese caso E, y se coloca X.
  3. Un error común es que se dupliquen las mismas producciones, lo correcto es redireccionar a los estados adecuados.
  4. Cuando la transicion sea un simbolo terminal, la transicion llegara hasta ese simbolo. 
    1. T-> . int*T (Transicion por int) T->int . * T (Transicion por *) T->int * . T
Una vez construido el AFN, lo que sigue es pasarlo a un autómata finito determinista (AFD). Esto se hace debido a que el AFN no es eficiente.

AFD y los posibles conflictos (ver laminas 10):

En el AFD, podemos observar que en cada estado hay conjuntos de items. Se llaman colecciones canónicas de items.

En este conjunto de items, generalmente pueden haber conflictos de tipo REDUCE/REDUCE o  REDUCE/SHIFT, el siguiente es un conjunto de razonamientos para identificarlos.

Existe un conflicto REDUCE/REDUCE si en un estado existen dos items con la opcion de aplicar un REDUCE.  Ejemplo:

E -> T
T -> E + T

Existe un conflicto SHIFT/REDUCE si en un estado existe un item para hacer un SHIFT, y otro para REDUCE.

Si se tiene un conflicto SHIFT/REDUCE en una producción T, se hará un FOLLOW (los siguientes de T)a la producción del estado y comparamos con el siguiente caracter al lado de la barra, si existe hacemos un movimiento REDUCE, de lo contrario SHIFT.

Ejemplo:

S' -> E (Esta producción no se toma en cuenta)
E -> T + E | T
T -> int * T | int | ( E )

Follow(T) = {"+", ")", $}

Como * no pertenece a follow de T, realizaremos un SHIFT, moviendolo al estado 11. Como no hay item al final en alguno del estado 11, no hay conflictos.

Notas: 
- En el libro del dragon se usa otra forma de construcción.
- El algoritmo para resolver este problema usa SLR(0), no se puede usar LR(0).

PASO A PASO:


  1. Tenemos la gramatica
  2. Expandir gramatica (para obtener el estado inicial de AFN)
  3. Calcular los items de la gramatica.
  4. Construir el AFN con transiciones lambda.
  5. Transformar el AFN-L a AFD llamado R.
  6. Ver los posibles conflictos del AFD y calcular los follows de los no-terminales presentes en el conflicto.
  7. Aplicar el algoritmo para R recibiendo como entrada la pila R(stack)

Ultimos detalles


  1. Para la proxima clase se explicara la tabla de simbolos.
  2. Ll(1) - Haskell (transforma la gramatica)
  3. SLR  - CUP
  4. El examen del jueves va hasta Ll(1)

Links





10 de enero de 2017

Lenguaje y Compiladores - Parsing Bottom Up #1



Anteriormente, vimos la derivación descendente, a continuación aprenderemos la reducción que consiste en ir desde la cadena de entrada hasta el símbolo inicial del árbol sintáctico.

Parsing Bottom-Up:


Es una estrategia general. Siendo determinista es decir, sin backtracking. Es mas eficiente que el método predictivo debido a que no utiliza backtracking, es usado por la mayoría de generadores de analizadores sintácticos conocidos como Java, C++, etc.

Usa la gramática en su versión mas natural por lo que no es necesaria la factorizacion a diferencia del top down.

Establece reglas de prioridad y asociatividad de los operadores. Podemos asociarlo con una operación matemática.
5x8x3
Para nosotros no es difícil determinar que el orden de los factores no alterara el producto, sin embargo, el computador no tiene esa capacidad, por lo que debemos establecer reglas de prioridad y asociatividad indicando en que dirección se hará las operaciones. Esto se hace para evitar la ambigüedad en la gramática.

Recordando, una gramática es ambigua cuando genera dos arboles sintácticos para una misma entrada.

La estrategia:


La idea es construir el árbol sintáctico de abajo hacia arriba, desde las hojas (tokens de entrada) hasta la raíz (símbolo no terminal de inicio). Esto se hace con el método de reducción, distinto al top down que usa producción.

Algo a notar es que la reducción se debe hacer por la derecha.

Reducción por desplazamiento:

Si hacemos las derivaciones por la derecha, debe haber una secuencia de símbolos terminales por la derecha, y por la izquierda símbolos tanto terminales como no terminales.

Si lo tuviéramos que visualizar, seria como una barra que atraviesa la cadena de entrada. Podemos indicar también el área a la derecha de la barra como área no examinada.

|int * int + int -> int * T | + int

Para la reduccion por desplazamiento, existen dos movimientos.

Movimientos reduce: reemplaza la parte derecha de una produccion por la parte izquierda de un paso de reduccion.

Movimientos shift: basicamente rueda la barra a la derecha. Generalmente se hace al no encontrar produccion, se hace hasta no haber mas entrada sin analizar.

Si se puede hacer hacer shift/reduce en un paso, esto se puede resolver con precedencia.

Si es un caso en el que se puede hacer reduce/reduce este sera mas complicado, para el proximo articulo se indicara la forma de resolver el problema.

Recurso usado:


- Lamina: Parsing Bottom-Up 

Terminos a investigar:


- Prefijo viable
- Item: implementa un algoritmo con los automatas finitos deterministas.