8. Descripciyn del mydulo (YDO([SU
Transcripción
8. Descripciyn del mydulo (YDO([SU
CAPÍTULO 8. Descripción del módulo EvalExpr 'HVFULSFLyQGHOPyGXOR(YDO([SU El LDD permite asignar a una variable el resultado de evaluar una expresión aritmética. Por ejemplo, n_var1= n_var2 + 5. Sin embargo, dichas expresiones tenían una serie de limitaciones: Las expresiones no podían contener operadores lógicos ni comparaciones. Sólo se admitían los operadores aritméticos +, -. * y /. No se aplicaba ningún tipo de precedencia entre operadores. Así la expresión 1+2*3 se evaluaba como 9. No se podían utilizar paréntesis. La solución más elegante consistiría en modificar la sintaxis de la instrucción de asignación para que pueda recibir como argumentos una serie de números, literales y operadores que conformaran una expresión aritmético-lógica. Esta solución requiere introducir un evaluador de expresiones aritméticas además de modificar el parser del LDD para que las admita en las asignaciones. Debido a los importantes cambios introducidos se tuvo que programar un nuevo evaluador de expresiones partiendo desde cero. Esta tarea se implementó en dos fases: primeramente se elaboró un evaluador de expresiones aritméticas, y posteriormente se integró en el lenguaje LDD. 'HVDUUROORGHOHYDOXDGRUGHH[SUHVLRQHV El evaluador de expresiones aritméticas empezó siendo una práctica de programación en C. Las especificaciones del evaluador fueron: Tener la forma de un ejecutable independiente que admita como parámetro de entrada un fichero de texto con las expresiones a evaluar, cada una de ellas separada de la anterior por un retorno de carro. Cada expresión (cada línea del fichero de entrada) tiene la forma de una asignación. En la parte izquierda de la asignación siempre hay una variable. Mientras que en la parte derecha debe haber una expresión aritmética que puede contener números, variables y los operadores ‘+’, ‘-‘ , ‘*’, ‘/’, ‘(‘ y ‘)’. Las variables de la parte derecha de la expresión pueden haber sido referenciadas con anterioridad o no. En el primer Página CAPÍTULO 8. Descripción del módulo EvalExpr caso tendrán el valor que les hubiera sido asignado previamente, y en el segundo caso se inicializan automáticamente al valor cero. En la evaluación de las expresiones se tendrán en cuenta las precedencias entre operadores habituales en matemáticas, y las impuestas por los paréntesis. Se genera por pantalla el resultado de evaluar cada línea del fichero de entrada. Pongamos como ejemplo que introducimos el siguiente fichero de texto: a= 1 + 2*3 b= a + c d= (1+2)*3 Si ejecutamos el programa con este fichero como entrada obtendríamos la siguiente salida: Evaluando a= 1 + 2*3 Æ 7.0000 Evaluando b= a + c Æ 7.0000 Evaluando d= (1+2)*3 Æ 9.0000 Nótese como en la primera y en la tercera línea varía el orden en que se aplican los operadores debido al uso de paréntesis. En la segunda línea se puede ver cómo la variable a conserva el valor 7, y cómo la variable c que aparece por vez primera se inicializa automáticamente a 0. Estas serían las especificaciones de la práctica desde un punto de vista de caja negra. Con estos requerimientos se podría implementar el evaluador de múltiples formas, pero también había que tener en cuenta su posterior adaptación al sistema SERVIVOX. Por ello se estructuró en dos niveles: una función de parsing, y una función de evaluación. /DIXQFLyQGHSDUVLQJ Su prototipo es el siguiente: int Parsea(int linea,char *expresionConSpc,tipo_list *tipos, arg_list *args) Esta función recibe como entradas la línea en que se ejecuta la aplicación (linea) y la expresión en forma de string tal como se lee del fichero, y genera una lista de tipos (tipos) y Página CAPÍTULO 8. Descripción del módulo EvalExpr argumentos (args). Estos tipos y argumentos son respectivamente de los tipos tipo_list y arg_list, compatibles, por tanto con los tipos que reciben las __funciones. Recordemos primeramente cómo se definían los tipos tipo_list y arg_list: typedef void* arg_list2; typedef struct { arg_list2 arg_list[MAX_NUM_ARG_VAR]; } arg_list; typedef struct { tipo_tipos tipo_list[MAX_NUM_ARG_VAR]; } tipo_list; La función Parsea en una primera fase elimina todos los caracteres blancos, y pasa a analizar el string resultante. En esta segunda fase identifica los caracteres o secuencias de ellos como números, variables u operadores y genera una lista de tipos (tipo_list) y una lista de argumentos (arg_list) adecuados. Podemos distinguir tres tipos de tipo_tipos: Números: cuando el parser identifica un número genera un nuevo nodo de tipo_list de valor T_NUMERO. También genera un nodo de arg_list con el valor del número evaluado como float y transformado a void* mediante un casting. Variables: cuando el parser identifica una secuencia de caracteres como un nombre válido de variable, genera un nuevo nodo de tipo_list de valor T_VARIABLE. También genera un nodo de arg_list, que contiene, previo casting a void*, un handle que identifica a la variable. Este handle en realidad es la posición de la tabla de variables para dicha línea que contiene el valor de dicha variable. Operadores: cuando el parser identica un operador genera un nodo de tipo_list de valor T_SUMAR, T_RESTAR, T_MULT, T_DIVIDE, T_ABRE_PARENTESIS, o T_CIERRA_PARENTESIS. El valor del nodo arg_list generado es irrelevante. En el parsing de la expresión, una vez eliminados los caracteres en blanco hay que distinguir las siguientes fases: Evaluación de la parte izquierda de la expresión: en esta fase sólo se admite un nombre válido de variable seguido por el operador de asignación, ‘=’. Los nodos generados de tipo_list son: T_VARIABLE y T_SALIDA. Evaluación de la parte derecha de la expresión: en esta parte se admite una secuencia de números, variables y operadores que conformen una expresión aritmética válida. Cuando se llega al final de la expresión se genera un nodo T_NULO. Página CAPÍTULO 8. Descripción del módulo EvalExpr /DIXQFLyQGHHYDOXDFLyQ Su prototipo es el siguiente: double Evaluar (int linea, const tipo_list *tipos, const arg_list *args, int inic, int fin, int *eval_error) Esta función evalúa la parte derecha de la expresión que se le pasa en forma de listas de tipos y argumentos. Recibe como entradas el número de la línea (necesario para acceder a la tabla de variables correspondiente), la lista de tipos, la lista de argumentos y las posiciones de la lista en que comienza y termina la expresión. Como salida devuelve el valor evaluado, y en la variable eval_error, si se ha producido o no un error en la evaluación. La evaluación de la expresión se basa en un algoritmo recursivo: cada vez que se llama a la función Evaluar, esta busca en la expresión el primer operador (empezando a contar por la derecha) de menor prioridad fuera de un paréntesis. Posteriormente evalúa (se invoca a sí misma) las subexpresiones situadas a la derecha y a la izquierda del operador, y le aplica el resultado a ambas. Si no encuentra ningún operador fuera de un paréntesis es que o bien la expresión está encerrada entre paréntesis o bien la expresión es trivial (se compone de un solo número o variable). En el primer caso se invoca Evaluar para la subexpresión encerrada entre los paréntesis; en el segundo caso se devuelve directamente el valor del número o variable. ,QWHJUDFLyQ GHO HYDOXDGRU HQ HO VLVWHPD 6(59,92; Una vez programado el evaluador de expresiones el paso lógico siguiente fue integrarlo en el servidor. Sin embargo, antes de dar este paso se decidió ampliar su funcionalidad, permitiendo la evaluación de expresiones lógicas. Para ello se añadieron los operadores AND (‘&&’), OR (‘||’), NOT (‘!’), IGUALDAD (‘==’), DESIGUALDAD (‘!=’) y comparaciones. Los operadores lógicos operan tomando como falso el valor 0 y como verdadero cualquier valor distinto de cero. El valor lógico generado como resultado será 0 para indicar falso y 1 para indicar verdadero. Página CAPÍTULO 8. Descripción del módulo EvalExpr Para la introducción de estos nuevos operadores fue necesario añadir nuevos valores al tipo_tipos. También hubo que definir prioridades para los nuevos operadores. Las precedencias adoptadas fueron las mismas que utiliza el estándar ANSI de C. A continuación mostramos en el cuadro siguiente los tipos asignados a cada operador y su orden de precedencia, del más precedente al menos precedente. Operadores Tipos NOT (‘!’) T_NOT *, / T_MULT, T_DIVIDE +, - T_SUMAR, T_RESTAR >, >= T_MAYOR_QUE, T_MAYOR_IGUAL_QUE, <, <= T_MENOR_QUE, T_MENOR_IGUAL_QUE ==, != T_IGUALDAD, T_DISTINTO && T_AND || T_OR El siguiente paso fue definir una función de alto nivel para el lenguaje LDD que evalúe expresiones: EVALUA (“<expresion>” ; <variable>); Esta función escribe en la variable de salida el resultado de la evaluación de la expresión indicada por el string que se le pasa como entrada. Lógicamente, esta función tiene su correspondiente __funcion, __EVALUA, que simplemente interpreta y llama a las funciones Parsea y Evaluar definidas en el módulo EvalExpr. (OPyGXOR(YDO([SU El módulo EvalExpr se compone de la habitual pareja de archivos “evalexpr.h” y “evalexpr.cpp”. Se ha diseñado de modo que se reutilizara el software programado para la práctica de C; sin embargo han sido necesarias ciertas modificaciones debidas a los diferentes requisitos exigidos por su integración en el SERVIVOX: Página CAPÍTULO 8. Descripción del módulo EvalExpr Incorporación de los nuevos operadores lógicos. Añadir el operador de negación fue lo más complicado, puesto que antes no se contemplaban los operadores monarios. La expresión que se le pasa como entrada no contiene ya una asignación; es equivalente al término derecho de las anteriores expresiones de asignación. Esto simplemente requirió eliminar la evaluación de la parte izquierda de la asignación en la función Parsea. No es necesario mantener una tabla interna con los valores de las variables, puesto que esta tabla ya existe, es la tabla de variables de la aplicación: tabla_variables. En principio, las variables referenciadas son las variables locales o globales de la aplicación. Sin embargo en un futuro se podría querer utilizar este evaluador para generar subtablas a partir de las entradas de una tabla cuyos campos cumplan una determinada condición, a modo de consultas a una base de datos. Por ejemplo si tenemos una tabla de artículos de venta, con sus precios en un campo y sus características en otro, podríamos querer generar una subtabla idéntica a esta pero que contenga sólo los artículos cuyo precio sea inferior a 1000 pesetas (“precio<1000”). Es claro, que “precio” no es una variable del LDD sino un campo de un determinado array. Si queremos aprovechar el mismo evaluador para ambas tareas, debemos independizar el propio evaluador del sistema de almacenamiento de las variables simbólicas que utilice. La solución adoptada ha sido definir la siguiente clase base virtual: class TgestorVar { public: virtual bool GetVarHandle (int *handle, char *nomVar) = 0; virtual float GetVarValue (int handle) = 0; }; Esta clase, como puede verse, define dos métodos: GetVarHandle: Devuelve un handle (un entero) que identifica unívocamente a la variable de nombre nomVar. Este handle es el que utiliza la función Parsea para rellenar el nodo de arg_list correspondiente a la variable. GetVarValue: Devuelve el valor que tenga asignado la variable identificada por el handle que se le pasa como entrada. Es utilizada por Evaluar para evaluar los nodos T_VARIABLE. Las funciones Parsea y Evaluar reciben como argumento de entrada una referencia objeto de tipo TgestorVar que encapsula todo el manejo de las variables simbólicas. Aquí mostramos el prototipo de ambas funciones: int Parsea(const TgestorVar &gestorVar, char *expresionConSpc, tipo_list *tipos, arg_list *args); double Evaluar (const TgestorVar &gestorVar, const tipo_list *tipos, const arg_list *args, int inic, int fin, int *eval_error); Página CAPÍTULO 8. Descripción del módulo EvalExpr El objeto concreto TgestorVar que se le pase dependerá del significado de las variables simbólicas de la expresión: variables de la aplicación o campos de un array. En el presente PFC sólo se ha implementado el caso de las variables de aplicación: class TgestorVarUsuario: public TgestorVar { public: TgestorVarUsuario (int l): linea(l) {} bool GetVarHandle (int *handle, char *nomVar) { *handle= traduce_variables (linea+1, nomVar, 0); return *handle!=-1; } float GetVarValue (int handle) { return atof( punteroaValorVariable (linea, handle) ); } Esta clase derivada se limita a ofrecer la funcionalidad exigida por TgestorVar utilizando las funciones que proporciona la clase variables. La solución adoptada en este proyecto para introducir un evaluador de expresiones es provisional. Lo ideal sería no tener que definir una función de alto nivel nueva como es EVALUA, sino aprovechar la propia instrucción de asignación, como hacen los lenguajes de alto nivel. Es decir, que para hacer EVALUA (“2+3” ; n_a); bastara con escribir n_a= 2+3. Para hacer esto, habría que modificar la función __ASIGNACION y además modificar el parser para que sustituyera a Parsea y proporcionara a __ASIGNACION la lista de tipos y argumentos que necesitaría para invocar a Evaluar. Este es precisamente el motivo de dividir la función de evaluación en el par Parsea-Evaluar, facilitar esta mejora que se deja como línea futura. Página