• No se han encontrado resultados

Registro de Conectores para el servicio de persistencia 76 Implementación del servicio provisto por diferentes conectores

COMPILADOR 2 PRIMER PASO

Tanto el parser como el analizador lexicográfico son responsables de generar los errores cuando el código fuente de entrada no respete las reglas sintácticas del lenguaje.

Existen dos técnicas ampliamente utilizadas en la construcción de parsers, TOP-DOWN y BOTTOM-UP. La mayoría de los parsers utilizan alguna de estas dos técnicas.

- TOP-DOWN: (de arriba hacia abajo en inglés) estos parsers tienen la estrategia de separar la secuencia de entrada en grupos de caracteres grandes, para luego ir separándolos sucesivamente en los elementos fundamentales o tokens. Es básicamente un juego de prueba y error donde se plantea la hipótesis de que una secuencia de caracteres representan una determinada construcción del lenguaje.

- BOTTOM-UP: (de abajo hacia arriba) estos parsers tratan de ubicar los elementos fundamentales o tokens, y en base a ellos tratan de enmarcarlos en las construcciones más grandes definidas en el lenguaje.

La complejidad de los parsers radica en como tomar la decisión de que construcción es la que corresponde a una secuencia de caracteres determinados.

Por lo general, la construcción tanto del parser como del analizador lexicográfico no es una tarea simple y puede llevar mucho tiempo. Ambas estrategias de procesamiento, se pueden escribir en forma de algoritmo y en base a él construir un programa que realice el proceso sobre un archivo.

Existen herramientas que tienen el objeto de generar el analizador lexicográfico y el parser en base a una definición del lenguaje de forma más o menos automática. Esta definición se realiza en notación EBNF, con algunos agregados propios de la herramienta.

Para la construcción del parser decidí utilizar una herramienta denominada javacc (Java Compiler Compiler) [Javacc00]. Javacc es una herramienta de uso libre bajo licencia BSD (Berkely Software Distribution), publicada en la comunidad abierta: java.net, que es patrocinada por la empresa SUN, creadora del lenguaje Java. Al tener licencia BSD se puede construir y distribuir cualquier tipo de software construido mediante esta herramienta (tanto comercial como libre) sin tener que pagar por ello.

Javacc es una herramienta que permite generar un parser de tipo TOP-DOWN a partir de una definición gramatical. Javacc toma como entrada un archivo fuente (generalmente con extensión jj) y lo transforma en uno o más archivos Java que representan un programa que es capaz de procesar y entender archivos fuente escritos mediante el lenguaje que defina la gramática. Este programa no tiene utilidad práctica, tan solo se limita a reportar si existe un error en el archivo fuente procesado.

Javacc utiliza la notación EBNF junto con agregados que le indican a su pre procesador algunos parámetros que son utilizados para generar las dos rutinas a construir: el parser y el analizador lexicográfico [JavaccTu00].

Si bien teóricamente con la gramática del lenguaje es posible definir un parser de forma sistemática [Garshol00], en la práctica es posible que el tiempo en determinar la validez o no de un archivo o secuencia de caracteres relativamente grande, sea prohibitiva para un parser generado en forma genérica. Un parser genérico utiliza habitualmente la técnica de backtracking [Dasgupta00] para analizar la validez de una secuencia de caracteres. Backtracking se aplica cuando al analizar una secuencia de caracteres se llega a un punto donde se debe tomar una decisión sobre dos o más caminos a tomar. Se toma la decisión de seguir un camino, luego de un tiempo de seguirlo se llega a un punto donde se determina que la secuencia de caracteres es inválida, pero entonces es necesario considerar que la decisión tomada originalmente podría haber sido incorrecta. Se debe volver a dicho punto para considerar la otra alternativa. Básicamente, cada punto de bifurcación debe ser registrado para poder volver a él en caso de que se llegue a un punto que determine un error. Esta técnica requiere de mucho tiempo de procesamiento y memoria, y en muchos casos eso lleva a un programa o sistema que consume recursos inadmisibles.

Por esto los parsers generados por javacc no utilizan la técnica de backtracking, lo que hacen es tratar de recaudar la mayor cantidad de información para tomar la decisión correcta, de forma tal de no tener que volver atrás. Javacc lee los N tokens siguientes en la secuencia para determinar si es válido o no el árbol. La cantidad de niveles del árbol a procesar por cada nodo, es determinada por el parámetro LOOKAHEAD del pre procesador. Un valor muy bajo puede determinar que el parser de cómo inválido algo que no lo es, en cambio, uno muy alto puede hacer que el parser consuma mucha memoria y tiempo en analizar y procesar el archivo. Por lo general, se define un LOOKAHEAD global y para ciertas definiciones se pueden aumentar este valor, esto es necesario sobre todo en las definiciones recursivas.

Javacc toma como entrada un archivo de texto que contiene las directivas al pre procesador, la notación EBNF que define el lenguaje y código Java que será ejecutado por cada construcción que el parser detecte en tiempo de ejecución. Al ejecutar javacc sobre el archivo, se generan algunas clases Java que serán utilizadas internamente por el parser y una clase Parser, que es la base para la construcción del compilador.

La clase Parser generada solamente valida la sintaxis del archivo. Una forma práctica de utilizar el Parser es utilizar javacc combinado con otra herramienta denominada jjtree. JJtree es un pre procesador para javacc, que toma como entrada un archivo fuente javacc, y lo adapta de forma tal que el parser generado por javacc genere un árbol con la estructura del archivo fuente procesado. Este pre procesador inserta acciones que construirán el árbol a medida que el documento de entrada con código fuente es procesado.

PSDL.jjt jjtree javacc PSDL.jj Node*,java PSDLParserVisitor.java PSDLParser,java Usa Primer Paso Segundo Paso COMPILADOR 3 - JAVACC

El árbol generado por jjtree estará compuesto por nodos. El tipo de nodo variará en función de la estructura que el parser haya detectado en el archivo fuente. Se le puede indicar a la herramienta que genere una clase para cada tipo de nodo que pueda existir en el árbol, este es el caso de mi compilador. Por lo general, el tipo de nodo se corresponde uno a uno con las definiciones de la gramática del lenguaje. Por ejemplo:

<psdl_state_type_spec> ::= <base_type_spec> | <string_type> | <wide_string_type>

| <abstract_storagetype_ref_type> | <scoped_name>

Es la definición en notación BNF del tipo de atributo de un estado de un objeto almacenado. En la definición del árbol, esto se traduce como un nodo de tipo: psdl_state_type_spec, que puede contener como nodo hijo, algún nodo de los siguientes tipos: base_type_spec, string_type, wide_string_type, abstract_storagetype_ref_type, o scoped_name. Entonces, jjtree generará una clase para cada uno de estos nodos.

A veces no es útil tener en el árbol algunos nodos que no serán utilizados por el compilador, en esos casos se le puede definir al pre-procesador de jjtree que los ignore, por lo que éste no generará una clase para dicho nodo y obviamente no existirá en tiempo de ejecución un nodo que tenga como hijo a un nodo de este tipo.

Volviendo al esquema inicial del compilador en dos fases, se puede pensar que el árbol resultado del procesamiento del parser, puede ser la “Representación Interna” con la que se comunicarán las dos partes del compilador.

ESTRUCTURA DEL ÁRBOL GENERADO

Como ya mencioné anteriormente la estructura del árbol depende fundamentalmente de: - La definición que se le haya otorgado a jjtree, y javacc para que generen el parser. - El archivo fuente procesado

Según mi definición del lenguaje PSDL en notación EBNF, todo árbol generado por el parser tendrá como raíz un nodo de tipo: Nodepsdl_specification. Este nodo contendrá nodos hijos con las construcciones encontradas en el archivo procesado. En el caso de PSDL, las construcciones serán las básicas definidas por el lenguaje más las existentes en IDL.

Todos los nodos generados por jjtree heredan de una clase base generada llamada SimpleNode. Esta clase tiene como atributos el nodo padre y los nodos hijos junto con operaciones que permitirán construir el árbol a medida que se procese el archivo de entrada. Además, se le pueden agregar atributos que podrán ser utilizados cuando se procese el árbol para reportar errores, por ejemplo, los tokens que definieron la creación del nodo. Los tokens aportan información importante como el número de línea y columna donde se encuentran en el archivo de entrada.

ERRORES A NIVEL DEL PARSER

La clase Parser se encarga de detectar los errores lexicográficos y sintácticos. Los errores lexicográficos se traducen en excepciones de tipo TokenMgrError, y los sintácticos en excepciones de tipo ParseException. Estas dos clases de excepciones pueden reportar precisamente la ubicación del error detectado en el archivo de entrada, además generan un mensaje de error que describe la causa del mismo, por ejemplo, “se esperaba un token {...} y no X”.

Los errores lexicográficos ocurren cuando el parser encuentra un carácter no esperado en una secuencia. Por ejemplo, dada la siguiente gramática para definir un número:

DEFINICION := NUMERO (+ NUMERO)* , la siguiente secuencia de entrada:

45 – 12

, dará como resultado un error lexicográficos diciendo que se esperaba un carácter “+” después del 12 . Los errores de tipo sintáctico ocurren cuando los caracteres de entrada son válidos, pero sin embargo se encuentran ubicados de forma tal que no corresponden a una construcción válida. Por ejemplo, dada la misma definición anterior de un número, ante una secuencia de entrada de tipo:

45 + + 12

, el parser generará un error sintáctico diciendo que se esperaba un token de tipo NUMERO después del símbolo “+”.

El proceso de parsing puede detectar sólo esta clase de errores descriptos. Existen otros errores que no son derivados de la definición gramatical del lenguaje y que no serán detectados por el parser. Por ejemplo, que un identificador único en el documento se encuentre duplicado. Esta clase de errores tendrán que ser detectados en la segunda fase del proceso de compilación.

G

ENERAR EL CÓDIGO DE

S

ALIDA

Generar el código de salida en lenguaje Java es el último paso del compilador. La salida será el resultado de procesar el árbol generado por el parser. La herramienta que provee jjtree para simplificar este proceso es una opción que se basa en el patrón de diseño Visitor [Gamma01]. Cuanto la opción es activada el procesador generará una interfase Visitor (en inglés visitante), que permitirá recorrer el árbol.

El patrón de diseño Visitor permite que un árbol de nodos sea recorrido o visitado por diferentes objetos sin que los nodos tengan que estar al tanto de ello. Un ejemplo, sería recorrer el árbol para generar una representación del mismo en texto o para validar la estructura y la relación entre los nodos, o generar el código de salida en base al mismo.

Este patrón es utilizado para recorrer estructuras, en este caso un árbol y su beneficio radica en que la estructura o los elementos que la conforman no tienen que estar al tanto de que se hace con ese procesamiento. Por otro lado, quien visita la estructura tampoco requiere conocer como está compuesta. Podrían existir diferentes implementaciones del visitante, sin que los nodos sean afectados por ellos. El compilador representa una implementación del visitante, y a la par podrían existir otras implementaciones, por ejemplo, un optimizador del árbol o para buscar errores en el mismo.

class Visitor

c ompiler:: PSDLCompiler

«i nte rface»

parse r::PSDLParserVisitor parser:: PSDLParse r «i nterface» parser::Node parser:: SimpleNode parser:: Node psdl_ module pa rser:: Nodepsdl_directiv e «rea li ze» -parser -parser -parent -chi l dren «real i ze»

Documento similar