|
Programación en Delphi - Evaluación de expresiones al modo xBase
Copyright © 2001 Alirio A. Gavidia B.. Para ver más de
mis artículos visite Programación Orientada en Delphi
Introducción
Hace un par de años se me encargó resolver un problema común de
"Compatibilidad". Este era el poder evaluar desde Delphi expresiones
xBase (por xBase entendemos a dBase y sus derivados como Clipper y
FoxPro).
Todo este asunto sucede porque los lenguajes xBase basados todos en
alguna forma de interpretación soportan lo que se conoce como "Macros"
(no confundir el almacenamiento de una serie de comandos y acciones
para su repetición posterior). Los programadores, en muchas ocasiones,
solicitan del usuario la definición de filtros para luego aplicarlos
directamente como expresiones (mala práctica si el usuario no sabe
escribir expresiones aritméticas bajo la sintaxis requerida).
Evaluar expresiones dadas como cadenas de caracteres
La aplicación creada en aquella oportunidad solicita expresiones al
usuario como filtros para búsquedas, consultas y reportes, pero cada vez
que lo hace presenta un botón que despliega un formulario de diálogo con
los elementos para construir la expresión. La expresión es tipo xBase
así que si el usuario conoce aplicaciones de ese tipo puede dar su
expresión directamente.
Ser o no ser he allí el dilema
En principio se presentaron dos alternativas:
- Crear un control para evaluar expresiones xBase.
- Usar un control ya existente.
La tercera, enseñarle SQL a los usuarios fue desechada por el principio
de compatibilidad con la aplicación anterior.
Existen algunos componentes interesantes a este respecto, de hecho
encontré que existen dos conjuntos de componentes para ello: Orientados
a aritmética y Orientados a bases de datos. Los primeros son prácticos,
bonitos, baratos (hay varios freeware) pero suelen estar orientados a la
resolución de gráficas y problemas matemáticos. Los segundos consideran
el asunto de los campos, me interesé por los componentes del segundo
tipo. Terminé usando un reemplazo del BDE con soporte para archivos de
Clipper que acepta expresiones xBase como filtros.
Sin embargo, paralelamente desarrollé algunas funciones para evaluar
expresiones (están en mi página personal y son gratuitas, sólo
documentadas en español) como parte de esta artículo presento la clase
Texpression. Por otro lado desarrollé un conjunto de componentes (estos
si son shareware en inglés y con documentación inglés/español) que
permiten evaluar expresiones del tipo:
(CLIENTE->FECNAC>CTOD('01/01/1900')) .AND. CREDITO
cuando la mayoría de los componentes para evaluar fórmulas no pasan de
expresiones constantes como:
3-2/3.14
¿Cómo se evalúan expresiones?
Por lo general hay dos pasos: el análisis (búsqueda) de patrones y como
segundo la combinación de los valores obtenidos en el paso previo para
obtener un resultado.
La búsqueda de patrones (lexicografía) permite reconocer entre una serie
de caracteres el tipo y funcionalidad de ciertos patrones. Por ejemplo
reconocer si una palabra empieza por un dígito indica que posiblemente
es un número constante (o un error), si no es así posiblemente la
palabra es un identificador que define algún valor u operador que altera
un valor.
Descubiertos los patrones estos de almacenan en alguna estructura de
datos (normalmente una pila, lo he visto con dos pilas también) y se
reducen en combinando valores según corresponda a los identificadores.
Al final lo que queda es el resultado de la expresión en la pila.
¿Y las variables?
Las variables y las constantes y en fin cualquier cosa asociada a un
identificador debe ser provisto por el programador a través de un evento
o una lista. Los componentes que presento consideran estas dos vías.
También consideran arreglos y funciones (varias predefinidas,
fundamentalmente similares a las de Clipper).
Por ejemplo:
HalSimpleExpr.Eval('Variable*Pi()')
En este caso hay dos identificadores Variable y Pi.
Como el segundo es seguido por un par de paréntesis es una función. El componente
dispara el evento OnRequestIdentifier (para evaluar Variable)
y OnRequestFunction (para evaluar Pi).
La evaluación de Pi sería manejada así:
procedure TForm1.HalSimpleExpr1RequestFunction(Sender: TObject;
ident: String; var value: Variant; var cancel: Boolean);
Var
col, row : integer;
begin
if CompareText(ident,'Pi')=0 then
value := Pi
else
cancel := true
end;
Nota: Debo señalar que hay un gran ausente entre los parámetros del
evento; este es un arreglo con los argumentos de la función. Para una
próxima versión el argumento de la función evaluada será parte de los
parámetros por lo pronto es sólo un campo público en la clase.
Por cierto verán que si la función no es Pi el control dispara una
excepción (eso define el parámetro cancel). La solución para
evaluar Variable es análoga pero con otro evento. Todo esto
funciona muy bien hasta que tenemos unas 50 funciones.
No podemos usar case...of
No es posible el uso de la sentencia case debido a que
esta funciona para tipos "contables" (enteros, caracteres, boléanos y similares)
y la alternativa de implementar un if tras otro es eficiente para
el primer identificador pero terrible para los últimos. El siguiente paso es
usar una lista, pero ordenada.
Búsqueda binaria
La búsqueda binaria sobre listas ordenadas parece una buena alternativa,
si hablamos de 1000 elementos podremos hallar cualquier valor el 50% de
las veces en unos 10 pasos (el otro 50% en menos). En la mayoría de los
casos esto es muy bueno, sin embargo hay una alternativa digna de
estudio.
Implementación de búsquedas por "hashing"
Mejor que revisar una lista es tener una función que de antemano señale
la posición del identificador en la lista. Por ejemplo una función que
sume el primer carácter, el último menos 97 y la longitud de la cadena,
así si buscamos la palabra "program" aplicaríamos algo como
IndexOf("program") que resultaría en Ord('p') + Ord('m') +
Length('program'). Esto es 112 + 109 + 7 – 97 –> 131. De allí a la
posición 131 de la tabla.
Como se entiende la búsqueda por tablas de "Hash" mejoran los esfuerzos
de búsqueda notablemente. Sin embargo, desperdician memoria. Queda al
programador definir que camino usar, sin embargo, lo componentes que
refiero como shareware presentan listas de este tipo como parte de su
funcionamiento intrínseco.
Donde SQL no ha ido jamás
Resulta que SQL resuelve bien los problemas en cuanto a filtros y
búsquedas. Pero que pasa si quiero hacer algo menos voluminoso como una
pequeña hoja de cálculo definible en sus formulas por el usuario. Por
ello anexo un ejemplo usando
un StrinGrid y la clase TExpression. Creo que
en general hay pequeñas necesidades en las cuales puede resultar conveniente
el uso de un evaluador de formulas más allá de querer imitar las macros xBase.
Notas respecto al ejemplo
El ejemplo es un control TstringGrid donde se hace uso de las
propiedades Cells y Objects. La primera presenta el
resultado de un formula que es almacenado en Objects. Los "strings"
deben ser especificados con comillas simples. Los archivos que salva/carga son de
texto simple. Cada fórmula definida requiere la presión de la tecla 'ok'
para ser almacenada. Hay definido un evento para manejar la
función Cell(c,r) donde c es columna y r fila.
Volviendo al paquete
El uso es simple, un método: eval. Con un parámetro la expresión dada
como un string. Sin embargo, hay diferentes métodos según el tipo a
manejar.
Los componentes consideran distintos tipos de sintaxis: xBase, Basic,
Pascal y Java. Fundamentalmente alterando el uso de operadores lógicos
entre puntos como lo hacen en expresiones xBase.
Tres componentes con distintos alcances: ThalSimpleExpr,
ThalMacroExpr y ThalArithmeticExpr.
El primero carece de funciones y manejo de listas de identificadores, el
segundo implementa unas 75 funciones, el manejo de listas y referencias
a datasets. El tercero no tiene el manejo de "alias" ni datasets, sin
embargo, soporta muchas de las funciones predefinidas.
Los detalles están documentados con el paquete y hay ejemplos incluidos.
Espero lo disfruten.
Pueden bajar el demo shareware directamente de mi página personal en:
http://www.gavidia.org/he-shareware.zip
También lo pueden solicitar a la dirección de correo:
delphi@gavidia.org
Para el ejemplo adjunto a este artículo
http://www.latiumsoftware.com/en/file.php?id=p19
En http://www.gavidia.org/pod hay
material freeware relacionado con el tema.
Lo próximo
Me tomaré el tiempo para dar información respecto a las listas por
"Hashing" que aquí mencioné. Para la próxima entrega ese será el tema.
Copyright © 2001 por Alirio A. Gavidia B. Todos los derechos reservados.
Se permite la publicación de este material por cualquier medio por parte
de cualquiera siempre que este no sea modificado en contenido y se cite
la fuente original.
|