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

Documentos relacionados