Chapter 6. Analysis and discussion
6.3. Due process and procedure
6.3.1. Moral evaluations of due process
La implementación del analizador sintáctico sigue el modelo de analizador descendente recursivo predictivo. Para ello se apoya en una gramática LL1 que permita un análisis descendente sin retroceso con la única evaluación del siguiente token en la entrada.
Para ello se implementa una función por cada símbolo no terminal de la gramática. Esta función analizará los tokens a medida que van siendo reconocidos. Para determinar la expansión que debe realizarse utilizará el siguiente token proporcionado por el analizador léxico. Una vez determinada evaluará los símbolos terminales que vayan entrando y procederá a llamar a las funciones “no terminales” que correspondan a la producción realizada.
Un ejemplo de la implementación de la Producción T sería el siguiente:
T I T1 T1 * I T1 T1 / I T1 T1 % I T1
T1 λ
def procesa_T(self, estado):
'''Procesa los términos de una suma o resta.''' expr = self.procesa_I(estado) if not expr: return expr if estado.token[0] == 'oA2': operador = estado.token[1] estado.next() expr1 = self.procesa_T(estado) if not expr1: return expr1
expr = '{0} {1} {2}'.format(expr, operador, expr1) return expr
Observamos que en primer lugar se realiza una llamada a la función “procesa_I” almacenando su resultado en la variable “expr”. Esta llamada se debe al primer símbolo no terminal de la producción: T I T1.
Si no ocurrió ningún error durante la ejecución de “procesa_I” evalúa el tipo de token actualmente apuntado por el analizador léxico. Almacena el operador y se llama a si misma recursivamente mientras no haya errores en la ejecución de “procesa_I” y el siguiente token sea siempre 'oA2', un operador aritmético de segundo nivel “*”, “/” o “%”.
Una vez terminada la cadena de operadores compone la expresión a devolver a la llamada anterior, retrocediendo recursivamente hasta finalizar la cadena y devolver la expresión completa a la función superior que inicio este análisis.
110 CAPÍTULO 3. MODELO DE COMPONENTES ESCALABLE PARA LA MONITORIZACIÓN.
Dado que el código generado en la expresión será posteriormente analizado por el intérprete de Python no se hace una evaluación exhaustiva ni necesariamente ordenada, analizándose únicamente la estructura general de la expresión.
En el siguiente ejemplo podemos observar la implementación de “procesa_C” para la evaluación de una asignación a un “path”: C = E eol B
def procesa_C(self, estado):
'''Procesa una sentencia de asignación a path.''' path = estado.token[1] estado.next() if estado.token[0] == 'asig': estado.next() expr = self.procesa_E(estado) if expr: if estado.token[0] == 'EoL':
traduc = self.traduce_nodo_escritura(path, expr) self.add_refs(traduc, estado.refs)
estado.emite('{0}{1}\n'.format(estado.level(), traduc)) estado.next()
else:
estado.source = error(err_code, err_msg, err_data) else:
estado.source = expr else:
estado.source = error(err_code, err_msg, err_data)
En el código se observa como esta función procesa el path analizado por la función que la llame, “procesa_B”, esto se debe a la necesidad de procesar conjuntamente la expresión y el path en la función “traduce_nodo_escritura” para generar el código de Python.
Una vez almaceno este valor evalúa que el siguiente token es el símbolo terminal de asignación. Una vez verificado avanza el puntero de lectura y llama a “procesa_E” la cual devolverá la expresión traducida de la expresión o un Error.
Si la expresión se evaluó correctamente se llama a la función de traducción de los paths, se añaden las referencias y se procede a emitir el código en Python ya generado. Tras avanzar el puntero de lectura la función finaliza su ejecución.
En caso de error en cualquiera de las llamadas o comprobaciones la función devolverá un Error que será propagado por la función superior hasta alcanzar la función principal del Parser y ser devuelto como resultado de la compilación.
Todo el proceso de compilación se apoyará en la clase Estado. Esta clase incluye el analizador léxico que va extrayendo los tokens del código fuente a medida que se le soliciten. Es también la responsable de mantener las tablas de símbolos de los identificadores declarados y el nivel de anidamiento de las mismas.
CAPÍTULO 3. MODELO DE COMPONENTES ESCALABLE PARA LA MONITORIZACIÓN. 111
Para su implementación se utiliza una pila de diccionarios. Serán las propias funciones de producción las que soliciten descender o ascender en la misma en función de las declaraciones encontradas.
El objeto Status también es el encargado de almacenar el código intermedio Python generado así como su nivel de indentación. El nivel de indentación en Python define los bloques de sentencias de una estructura como se observa en los ejemplos expuestos.
Junto con el código generado almacenará las listas de referencias a los elementos de GEMA accedidos en los paths procesados por el analizador.
Dado que el código generado por el compilador será posteriormente analizado por el intérprete de Python se realizan pocas comprobaciones semánticas. Con respecto a los tipos, dado el tipado dinámico y la incertidumbre a la hora de conocer los tipos de las variables y propiedades accedidas únicamente se comprueba que las asignaciones se realicen sobre identificadores de variables o paths de GEMA.
Las expresiones aritméticas no se evaluarán necesariamente en el correcto orden de evaluación requerido por los operadores. Esto simplifica su evaluación al hacerse directamente en el orden introducido.
Estas expresiones serán traducidas a Python exactamente como se introdujeron en el código GEMA-Script. Será el intérprete Python el encargado de realizar la correcta evaluación de las mismas a la hora de ejecutarlas.
Para la implementación del analizador léxico se ha utilizado la librería de análisis léxico de Python Plex [32]. Esta librería permite una definición de la gramática léxica en la misma estructura presentada anteriormente.
Algunos ejemplos de declaración para algunas de las definiciones serían los siguientes: Dígito: Cualquier valor entre 1 y 9 ambos incluidos.
self.digit = Range("09")
Letra: Cualquier carácter alfabético. self.letra = NoCase(Range("AZ"))
Cadena: Cualquier secuencia de caracteres encabezada por “ o r” y terminada por “. self.cadena = Str('"', 'r"') + Rep(AnyBut('"') + Rep(Str('\\"'))) + Str('"')
Reservada: Cualquiera de “def”, “if”, “for”, “return”, “static”, “while”
self.reservada = NoCase(Str("def", "if", "for", "return", "static", "while"))
Comentario: Cualquier secuencia de caracteres encabezada por “#” y terminada en “\n”, “Fin de línea” o “Fin de fichero”.
112 CAPÍTULO 3. MODELO DE COMPONENTES ESCALABLE PARA LA MONITORIZACIÓN.
Para la construcción del analizador se realiza una asignación de tokens similar a la del diseño, a continuación se muestra una declaración reducida del mismo:
self.lex = Lexicon([ (self.blacklisted, 'black'), (self.binary, 'bin'), (Any('[]'), 'corch'), (Str(';'), 'pyc'), (Str("="), 'asig'), (Str('')|Eof, 'EoF'), (Rep1(Any("\t")), IGNORE), (Any("\n")|Eol, 'EoL'), (AnyChar, 'ERR')])
El código de Plex “IGNORE” indicará al analizador que debe ignorar el token.
Como se puede observar la sintaxis de declaración de gramática apara el léxico proporcionada por Plex es altamente intuitiva y fácil de corresponder con la diseñada. Esto la hace especialmente legible sin necesidad de un alto conocimiento de expresiones regulares y permitirá modificar fácilmente la gramática léxica del lenguaje de ser necesario.
Los tokens generados por el analizador léxico de Plex son procesados por la función “procesa”. Esta función revisa el formato de los tokens generados, reduciendo a minúsculas todas las palabras reservadas y añadiendo la información de posición del token dentro del código. Haciendo coincidir con el formato de token utilizado por el analizador sintáctico.
El formato utilizado consiste en una terna que contendrá la información necesitada por el analizador sintáctico.
(“oA2”, “*”, (28, 8))
Esta terna contendrá en primer lugar el código o tipo del token, en segundo lugar la cadena de texto que lo ha generado y en tercer lugar la información de posición del token en el código. Esta posición se indicará por la fila y columna en la que se encuentra dentro del texto del código GEMA-Script.