Lo primero, lo primero: ANÁLISIS.
¿Que tan variadas y/o complejan podrán, o debieran, ser dichas fórmulas? ¿Se espera demasiada variedad?
¿Cuántos parámetros necesitarían? ¿De donde sacarán los datos? ¿Se tiene pensado contemplar que se indique el campo como parámetro en lugar del valor? Es decir algo como:
OPERAR(ConElCampo) vs OPERAR(5)
¿De donde provienen los datos, de sus propios atributos del registro? ¿Desde otras tablas?
Es posible en cierto modo implementar de mecanismos que no lleven hacia el desarrollo de un lenguaje propiamente. Mi pensamiento, y propuesta, me lleva a esto:
1) Diseñar una Abstracta TOperation que cuente con un método Calculate() virtual y abstracto que devuelva el valor calculado.
2) Luego defino una clase concreta que implemente a su modo el método Calculate() que ésta necesite.
Esto permite que se puedan definir cientos de clases concretas, cada una con su propia implementación del algoritmo adecuado. Así por ejemplo entonces es que puedo tener clases como TLog10, TLog2, TPower...
Ya es cuestión entonces de indicar entonces que tipo aceptar:
A. El valor directamente
B. El nombre del campo
O bien, se podría incluso considerar la posibilidad de que existan métodos Calculate() sobrecargados y cuente con ambas posibilidades, y hasta que puedan devolver sobre diferentes tipos.
En caso de aceptar el nombre del campo se puede prever un diseño basado en algo como esto:
function TConcreteOperationXXX.Calculate(FieldName: string, DataSet: TDataSet): double;
var value: double;
begin
value := DataSet.FieldByName(FieldName).AsDouble;
result := // hacer algo con value...
end;
De este modo la clase sabe encontrar sobre que campo y que dataset buscar el valor con el cual operar.
Este diseño básico puede expandirse. De hecho nada impide que incluso una clase concreta de operación haga uso de otra. Por ejemplo digamos que se tiene pensado hacer una operación del tipo: (Valor1^5) * Log10(Valor2). Suponiendo que ya existieran las clases TPower y TLog10 entonces es posible hacer una clase TPowerLog10 algo como:
type
TPowerLog10 = class
private
FPower: TPower;
FLog10: TLog10;
function GetValue2FromDataSet(FieldByName: string; DataSet: TDataSet): double;
public
function Calculate(FieldByName: string; DataSet: TDataSet): double; override;
constructor Create;
destructor Destroy;
end;
// ...
function TPowerLog10.function Calculate(FieldName: string; DataSet: TDataSet): double;
var Value1, Value2: double;
begin
Value1 := DataSet.FieldByName(FieldName, DataSet);
Value2 := GetValue2FromDataSet('ElCampo2', DataSet);
result := FPower.Calculate(Value1, 5) * FLog10.Calculate(Value2);
end;
Naturalmente hay que hacer un buen análisis... el tema de la cantidad de parámetros es vital.
3) Luego defino una clase TCalculator que actúe de factoría y cree la clase apropiada según la operación leída del campo. Entonces cuenta con un método GetOperation() que le indica la clase de operación y los parámetros.
Si suponemos y definimos que como estándar se "almacene en el DBGrid" un texto con la nomeclatura:
<NameClass>(<ParamList>)
<ParamList> = <OneParam> | <MoreParams>
MoreParams = <Param>, | <MoreParams> | <Param>
Entonces supogamos que almacenamos: ClaseImpuesto(CampoTasa, CampoDescuento). Le pasamos este texto al método GetOperation() de la clase TCalculator y esta descompone al texto en sus partes a fin de detectar la clase a crear, y luego a los parámetros que necesita.
La implementación la dejo a debate. En términos abstractos lo que hará es buscar dentro de una lista de sus clases operaciones concretas la que cumpla con dicho nombre (puede aprovecharse TStrings que cuenta con el método AddObject() y agregar un objeto y asociado a este una "clave" alfanumérica).
Cuando obtiene la clase correspondiente, crea un objeto de la misma. Luego simplemente le envía el mensaje Operate() con los parámetros leídos.
Este diseño no es de lo más sencillo. Como es de esperar conlleva su debido tiempo, y por supuesto análisis.
Es una de las maneras de encararlo. Todo dependerá del análisis y la complejidad. En caso de un escenario más dinámico y complejo (y por sobre todo multiparamétrico) es muy posible que deba aprovecharse de patrones como Composite y Strategy.
Recomiendo una super lectura sobre los patrones aquí expuestos: Factory, Composite y Strategy.
Sea cual fuese la solución a la que llegues, requiere de MUCHO trabajo. De eso estoy seguro y no te salvas. Mantente lo más simple posible, y cuanto más restrictivo seas al momento de definir sobre las posibilidades de operaciones y combinaciones de operaciones y parámetros porque sino tu trabajo será de no acabar.
Las otras posibilidades que pueden barajarse es la de emplear bibliotecas de expresiones regulares... algunas de ellas aceptan cosas al estilo de fórmulas y son capaces de interpretarlas. Pero no esperes que sean tan abiertas y dinámicas como para llegar a entender cosas como:
SI(Campo1 < Campo2, SUMA(Campo1 * Campo3) - INTERES(RANDOM(100) MOD 15), 0)
Que todo tiene su límite.
Saludos,