manual cup2
DESCRIPTION
Manual breve sobre CUP2TRANSCRIPT
1
FIUSAC
Organización de lenguajes y compiladores 1
Seccion A
Ing. Mario Bautista
Aux. Wener Aldana
Manual de CUP2
Jose Javier Cardona Polanco
200910523
2
Manual
Introductorio
de CUP2
Por Javier Cardona
3
Índice
Objetivos……………………………………………………………………………………………… 4
Introducción…………………………………………………………………………………………..5
Un Compilador……………………………………………………………………………………….6
Acerca de CUP2…………………………………………………………………………………….9
Acciones semánticas………………………………………………………………………………10
Creando y utilizando un parser…………………………………………………………………12
Declaraciones de símbolos y definiendo reglas gramaticales………………………. 12
Asignación de acciones semánticas a las reglas gramaticales………………………14
Eliminación de ambigüedad……………………………………………………………………..14
Conexión con JFlex………………………………………………………………………………..16
Manejo y recuperación de Errores……………………………………………………………16
Ejemplo……………………………………………………………………...……….………...........17
4
Objetivos
Introducir los aspectos generales de CUP2 y orientar la “instalación”.
Facilitar el uso de CUP2.
Ejemplificar el uso de las reglas gramaticales que maneja CUP2.
Conectar con JFlex para poder estudiar los aspectos básicos de un
compilador.
5
Introducción
El presente manual podrán encontrar una pequeña introducción a los
compiladores y a su vez a los pasos que toma el compilador para analizar un
archivo fuente. Si bien el objetivo de este manual no es profundizar en este
tema se consideró necesario el explorarlo para aquel lector que no tuviera los
conocimientos básicos.
Se tomó en cuenta el hecho de que es de mucho mas ayuda los ejemplos que
la simple explicación en texto plano, por lo que este manual contiene las
explicaciones graficas que se consideraron necesarias.
Al final de este manual podrán encontrar un ejemplo sencillo de una calculadora
sencilla. La estructura final de un archivo CUP2 es muy parecido a CUP (versión
1) por lo que si ustedes tienen conocimientos previos podrán entender este
ejemplo sin mucho análisis.
Si el lector tiene conocimientos previos sobre CUP, es bueno aclarar ahora que
si bien el nombre CUP2 indica que esta es la versión 2 del mismo, esto no es así
ya que cuando se creo CUP2 este fue reinventado completamente.
Sinceramente espero que sea de su ayuda, disfruten el manual.
6
Un compilador
A grandes rasgos, un compilador es un programa que lee un programa escrito
en un lenguaje (lenguaje fuente) y que lo traduce a un programa equivalente en
otro lenguaje (lenguaje objeto), este lenguaje final por lo general es lenguaje de
máquina. Como parte importante de éste proceso de “traducción”, el
compilador informa a su usuario de la presencia de errores en el programa
fuente.
Si nos ponemos a pensar, estamos diciendo que hay una enorme cantidad de
compiladores hoy día. Hay miles de lenguajes fuente y también hay muchos
lenguajes objeto. Esto nos deja con una cantidad de compiladores
increíblemente grande y nos da la idea de la necesidad de nuevos
compiladores. Es por esto que la habilidad para realizar compiladores hoy día
sigue siendo necesaria.
A continuación mostramos un diagrama bastante sencillo de lo que un
compilador regular puede hacer.
Un compilador realiza esta labor de traducción analizando el programa fuente
en diferentes pasos. Para ello realiza este análisis en dos etapas principales. La
primera etapa analiza el programa fuente con un analizador lineal o léxico, un
analizador jerárquico o sintáctico y un analizador semántico.
7
Podemos visualizar de manera mas amplia y especifica los componentes de un
compilador al ver el siguiente esquema que contiene todas las fases de un
compilador.
Estas fases y analizadores se pueden realizar “a mano” programándolos con
técnicas ya establecidas y orientadas a nuestro lenguaje fuente. Pero también
exisen generadores de analizadores léxicos y sintácticos. Uno de los
generadores léxicos mas utilizados, al menos en la USAC, es JFlex que toma las
especificaciones escritas en lenguaje de JFlex y las transforma y las devuelve
como una clase Java que contiene nuestro analizador léxico.
8
Es mas aconsejable utilizar estas herramientas ya que ahorran mucho tiempo y
el manejo de errores puede llegar a ser mucho mas sencillo que si lo
programáramos nosotros mismos.
Un generador de analizadores sintácticos es CUP2, que toma reglas
gramaticales de un lenguaje fuente y las transforma mediante métodos como el
LR(0), LALR(1) y LR(1) para dar como resultado dos clases Java que manejan
nuestras gramáticas según nosotros las hayamos declarado.
Representación de un buen compilador.
9
Acerca de CUP2
Los desarrolladores de CUP2 considera que CUP2 es un sistema para la
generación de analizadores de especificaciones concisas. CUP2 está
completamente escrito en Java y combina las ideas algorítmicas de su
predecesor CUP con las características de Java modernas.
Mientras CUP soporta LALR (1) solamente, CUP2 incluye LR(0), LR (1) y LALR (1)
de la caja y permite fáciles extensiones para otras clases de gramática.
A diferencia de otros generadores de analizadores sintácticos tradicionales
(incluyendo también CUP) CUP2 no crea código fuente parser basado en un
lenguaje de definición de la gramática independiente, sino que utiliza la API de
Java, las llamadas se combina con las estructuras de datos para representar la
información relacionada con la gramática.
Esto significa que no hay un lenguaje especial para definir gramáticas y
acciones semánticas, pero todo se hace en Java puro. Aún más, CUP2 se
exime de generar un analizador como un archivo de código fuente, sino más
bien produce una corriente de código Java serializado, que luego puede ser
cargado por las clases de tiempo de ejecución CUP2.
Definiendo Gramáticas
Si tienes alguna relación con la versión anterior de CUP (si cursaste el curso de
compi1 en la USAC) es probable que te parezca conocida la forma de declarar
las gramáticas en CUP2 ya que utiliza recursos de java como los enums.
Además también utiliza, en mi opinión la mejor manera de declarar gramáticas
tipo 2, que es el BNF (Backus-Naur Form). BNF es un metalenguaje usado para
expresar gramáticas libres de contexto: es decir, una manera formal de
describir lenguajes formales.
10
Como pequeña introducción definiremos algunas reglas de esta notación.
Regla gramatical Representación en BNF No terminal <no_terminal > Terminal (se escribe en negrita)
try
Produce / Símbolo de producción
::=
Simbolo OR (<opcion1> | <opcion2> | <opcion3> )
Producción completa <try>:: try <cuerpo_try> catch <cuerpo_catch>
Rango de el abecedario de minúsculas.
<rango_de_letras_minusculas> ::= [a-z]
Rango del abecedario en mayúsculas y minúsculas.
<rango_de_letras_minusculas> ::= [a-zA-Z]
A continuación les presentamos un pequeño ejemplo de cómo definir un
identificador típico, que tiene como regla iniciar con una letra y puede seguir de
muchos números, letras y guiones bajos.
< 𝑙𝑒𝑡𝑟𝑎 >∷= [𝒂 − 𝒛𝑨 − 𝒛]
< 𝑑𝑖𝑔𝑖𝑡𝑜 >∷= [𝟎 − 𝟗]
< 𝑖𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑐𝑎𝑑𝑜𝑟 >∷=< 𝑙𝑒𝑡𝑟𝑎 > (< 𝑙𝑒𝑡𝑟𝑎 > | < 𝑑𝑖𝑔𝑖𝑡𝑜 > _ ∗
Para una mejor y más extensa explicación buscar en el libro del dragón y en
Wikipedia.
Acciones semánticas.
Para realizar las acciones que tu gramática tiene como objetivo realizar
utilizamos estas acciones semánticas. Que constan en acciones que devuelven
valores en reducciones de la gramática que van produciendo los resultados
para generar una acción final con los valores de nuestro archivo de entrada.
Seguiremos utilizando los ejemplos del Manual de CUP2.
11
Para cada No Terminal tenemos que crear una clase que extienda de la clase
SymbolValue<T>. Para el ejemplo que venimos trabajando declararíamos las
clases de la siguiente manera:
Una vez hecho esto podemos utilizar el metodo prod que habiamos
mencionado antes con un argumento mas y este es el de la acción que
queremos que realice. La estructura general del método quedaría de la
siguiente manera:
12
Creando y Utilizando un parser.
Primero tenemos que crear la tabla de análisis sintáctico y luego somos
capaces de inicializar el analizador con él. Esto se muestra en el siguiente
listado.
Una vez creada nuestra LRParsingTable llamada “tablichi” a ésta le mandamos
un scanner que a su vez le mandara los tokens a la tabla para que sean
analizados y ejecutadas sus acciones, como lo indicábamos en la sección
anterior.
Declaraciones de simbolos y definiendo reglas gramaticales
Como mencionábamos antes CUP2 utiliza enums para alojar a los terminales y a
los no terminales de una gramática. En el manual oficial de CUP2 podemos
encontrar un clásico ejemplo que vemos desde que empezamos compiladores
con el libro del dragón.
< 𝑒 >∷= ( < 𝑒 > + < 𝑡 > ) | < 𝑡 >
< 𝑡 >∷= < 𝑡 > + < 𝑓 > | < 𝑓 >
< 𝑓 >∷= ( < 𝒆 > ) | 𝒏𝒖𝒎𝒆𝒓𝒐
13
El equivalente de estas producciones para CUP2 seria el siguiente:
Para que esta producción tenga sentido para CUP2 tenemos que declara los
terminales y no terminales al igual que lo hacíamos en anteriormente en CUP
(versión 1).
Declaración de no terminales:
Declaración de terminales:
14
Asignación de acciones semánticas a las reglas gramaticales.
Con el fin de calcular cualquier valor o construir cualquier representación de la
entrada del analizador, necesitamos equipar el analizador con acciones
semánticas. Las acciones semánticas en CUP2 son los métodos de Java que
contienen código que se ejecuta cada vez que el parser decide reducir los
símbolos hasta ahora reconocidos de acuerdo con una de sus reglas.
Como habíamos definido antes las acciones semánticas son las que se van a
realizar cono objetivo final por nuestra gramática.
Las acciones se verían de la siguiente manera en un producción cualquiera.
Eliminación de ambigüedad
Si bien CUP2 realiza varias tareas para eliminar la ambigüedad el mismo esto no
quiere decir que no pueda llevar a errores esta ambiguierdad.
Una gramatica tipo 2 es ambigua si existe una cadena que tiene más de una
derivación por la izquierda o más de una derivación por la derecha o si tiene dos
o más árboles de derivación. En caso de que toda cadena tenga un único árbol
de derivación, la gramática no es ambigua.
15
Anteriormente utilizábamos uno de los ejemplos clásicos de el libro del dragon,
que es del de la precedencia de multiplicación sobre la suma.
Ahora utilizaremos otro ejemplo clásico para ejemplificar la ambigüedad de una
gramática al representar gráficamente los arboles permitidos por esta misma
gramática.
< 𝐸 >∷= < 𝐸 > + < 𝐸 >
< 𝐸 >∷= < 𝐸 > ∗ < 𝐸 >
< 𝐸 >∷= 𝑥
< 𝐸 >∷= 𝑦
Tenemos que la anterior gramática nos puede producir estos dos árboles que
como vemos son diferentes. El problema con que sean diferentes es que las
acciones semánticas que mencionábamos antes serian afectadas porque las
reducciones se realizarían sin estandarización.
Para eliminar esta ambigüedad no existe un solo método que lo solucione todo,
lamentablemente. Así que en muchos casos recae en nuestra habilidad como
desarrolladores de compiladores y en nuestra experiencia desarrollando
gramáticas.
Una de las soluciones que, por la experiencia, pueden funcionar es el agregar
producciones y no terminales a nuestra gramática para que sepa por que
camino irse. Ya que el problema principal es que el compilador no va a saber
que producción tomar si es que viene una <E>. Podría tomar cualquiera de las
dos primeras producciones. Podriamos replantear la gramatica de la siguiente
manera.
< 𝐸 >∷ = < 𝐸2 > < 𝐸3 > | 𝑒
< 𝐸2 >∷= 𝑥 | 𝑦
< 𝐸3 >∷= + −) < 𝐸 >
16
Conexión con JFlex
Para conectar con CUP2 con JFlex es muy sencillo. Si ya tienes experiencia con
CUP (versión 1) conectándolo con JFlex se te hara muy fácil ya que es igual con
pequeñas diferencias. Para más información por favor consulta la
documentación de CUP2 y de JFlex.
A continuación una ejemplificación de cómo se vería un .jflex
Manejo de y recuperación de Errores.
Como mencionábamos en el inicio de este manual, el manejo y recuperación de
errores es una de las funciones principales del compilador, ya que esta indicaría
al usuario final en donde están los errores que su programa fuente tiene
indicándole los caracteres de error o el orden esperado de tokens. Para esto se
necesita la posición de los tokens o caracteres que provocan el error dentro del
archivo fuente.
17
Aspecto importante de la construcción de analizadores con CUP2 es el apoyo a
la recuperación de errores sintácticos. En estos casos un analizador preparada
simplemente se detiene con una excepción. Sería muy útil si el analizador
intenta continuar con el análisis en un punto, en el que está seguro de lo que se
entiende o incluso trata de dar consejos, cómo corregir la entrada.
El método mas común de recuperación de errores y el que analizaremos en
esta ocasión es el famoso modo pánico. Que lo que hace es que una vez
encuentra un error en la gramática empieza a ignorar todos los tokes que le
sigan al error hasta que encuentre un token centinela y eso le indique que la
instrucción a terminado y por tanto puede volver a empezar a analizar.
Un ejemplo de la aplicación de este metodoo es el siguiente (tomado del
manual de CUP2).
18
A continuación presento la compilación de los ejemplos que he presentado. De
los dos archivos JFlex y CUP2.
19
A esto se le tiene que sumar una clase principal para correrlo y la clase de
analizador léxico que le envie los tokens. Ya que el principal objetivo de este
manual no es dar ejemplos sino una introducción y guiar al usuario para iniciarse
en CUP2, te puedo recomendar que visites la pagina:
https://www2.in.tum.de/repos/cup2/trunk/
Aquí podrás encontrar mucha mas documentación y ejemplos. Espero que
realmente este manual pueda ser de ayuda y que les haya gustado.
20
Conclusiones
CUP2 trata de una mejor manera el análisis sintáctico y de una manera
mas directa con el lenguaje Java, utilizando directamente las estructuras
para realizar las listas de los terminales y no terminales lo que te da un
acceso mas directo a la construcción de tu parser.
El uso de este tipo de herramientas es de gran ayuda tanto para el
usuario que se dedica a construir compiladores como para nosotros los
estudiantes que pretendemos entender las funciones de un compilador ya
que se puede manipular de una manera muy cercana los componentes
del compilador como lo es el parser.
El manejo de errores es una de las herramientas mas importantes de un
compilador y sin duda de mucha ayuda para el usuario final. Con CUP2 es
bastante sencillo y poderosa la implementación de esta herramienta.
21
Bibliografía
Technische Universitat Munchen. CUP2 User Manual [en línea]. Alemania.
[Consulta: 20 de mayo de 2013]. Disponible en:
http://www2.in.tum.de/~petter/cup2/
Aho, Alfred V.; Ravi Sethi, Jeffrey D. Ullman (2008). «Introducción a la
Compilación». Compiladores: Principios, técnicas y prácticas. México: Addison
Wesley.
Ruben Aldo, Kristan. Ambigüedad en una Gramatica [en línea]. Universal.
[Consulta: 19 de mayo de 2013]. Disponible en:
http://teodelacomp.blogspot.com/2011/03/37-eliminacion-de-la-
ambiguedad.html
Archivo Wiki. Compilador [en línea]. Universal. [Consulta: 18 de mayo de 2013].
Disponible en: http://en.wikipedia.org/wiki/Compiler