Este artículo presenta el componente TExpression para evaluación de expresiones en tiempo de corrida

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

LMD-Tools 6.1 - 300+ componentes para varias tareas de desarrollo!

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:

  1. Crear un control para evaluar expresiones xBase.
  2. 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.

Boletín Pascal. Newsletter gratuito para programadores Delphi (y Kylix) con artículos, noticias, trucos, componentes y enlaces a nuevo contenido Delphi en la red.