Instead of translating the other posting, I am writing here some instructions on usage of the expression parser and hope that this information is useful to somebody out there.
fpExpressionParser belongs to fpc (source in (fpc_dir)/source/packages/fcl-base/src, unit fpcexprpars.pp). I don't know who's the original author.
You apply the parser by creating an instance like that:
var
FParser: TFPExpressionParser;
...
FParser := TFpExpressionParser.Create(self);
This is called from a method of a Form, i.e. "self" points to the form. Since the parser inherits from TComponent there is no need to destroy it explicitly since its owner, the form, will do it this way.
The parser is very flexible, but the default parser is quite dumb. You have to specify which kind of expressions it will accept. In the example that you are citing, I am using the math expressions (I don't have experience with the others):
FParser.BuildIns := [bcMath];
Have a look at the source code to learn more about the other built-in expression kinds (strings, dates, booleans, etc).
Then you have to tell the parser the name and value of the variables in your expression. This is done by
FParser.AddFloatVariable('x', 0.0);
which defines a variable "x" with value 0.0. Of course, you can also add other names, e.g. constants like "e" etc. ("pi" is already built-in).
Then you define a function string (like "x*2 + 4") and assign it to the Expression property:
FParser.Expression := 'x*2+4';
You can obtain the function result by calling "Evaluate". But since fpEpressionParser can handle different data types the function result is not a float but a TFPExpressionResult defined as
type
TFPExpressionResult = record
ResString : String;
Case ResultType : TResultType of
rtBoolean : (ResBoolean : Boolean);
rtInteger : (ResInteger : Int64);
rtFloat : (ResFloat : TExprFloat);
rtDateTime : (ResDateTime : TDatetime);
rtString : ();
end;
Since the result of above funtion is a float, we take the rtFloat member of that data type. If, for other expression, the result were an integer, we could take it as well because it can be converted to a float:
var
y: double;
...
res := FParser.Evaluate;
case res of
rtInteger: y := res.ResInteger;
rtFloat: y := res.ResFloat;
end
If you want to calculate the function for a new x value you must know that the variable values are stored in the property Identifiers. Since the example has only one variable it is accessible for reading and writing of Identifiers[0]:
FParser.Identifiers[0] := new_x_value;
The default parser only knows the math functions declared in the system unit. If you want it to understand also the functions of the math unit or your own functions you can extend it by calling "Identifiers.AddFunction", e.g.
FParser.AddFunction('tan', 'F', 'F', @ExprTan);
In this example, I add the function tan(x) by specifying its name as it will be called in the function expressions (first parameter), the type of the result values (second parameter, "F" = float, or "I" = integer -- see source code) and the type of the input values (third parameter, same logics). If a function accepts several input parameter the type of each one must be specified, e.g. by 'FF' for two floating point values, or 'FI' for a first float and a second integer parameter. The last parameter points to the function which is called whenever "tan" is met in the expression string. Since this function has a particular syntax you have to implement it in your source code, in our case like that:
Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
if IsNumber(x) and ((frac(x - 0.5) / pi) <> 0.0) then Result.resFloat := tan(x)
else Result.resFloat := NaN;
end;
and the TExprParameterArray is just an array of such TFPExpressionResults. The term "Results" is maybe a bit misleading, because this array holds all the *input* parameters as specified by the input types of the AddFunction method.
The parser is very strict on data types, which means that an expression like "tan(0)" cannot be evaluated because "0" is an integer, but the parser expects a float here (that's our definition above); the correct syntax would be "tan(0.0)". To avoid this confusion I wrote the function "ArgToFloat" which converts an integer argument to a float argument if a float is expected:
function ArgToFloat(Arg: TFPExpressionResult): TExprFloat;
// Utility function for the built-in math functions. Accepts also integers
// in place of the floating point arguments. To be called in builtins or
// user-defined callbacks having float results.
begin
if Arg.ResultType = rtInteger then
result := Arg.resInteger
else
result := Arg.resFloat;
end;
When studying the parser I found out that it is very strict when errors occur: It does not have exception handling, it just calls "Abort"... That's no fun if your function is "1/x" and x happens to be entered as 0.0...
To handle these cases, I modified the entire parser in such a way that illegal values are checked before each operation is performed. When an illegal operation is found NaN is returned. The function "IsNumber" checks for NaN and Infinite value and is used to by-pass further calculation:
function IsNumber(AValue: TExprFloat): Boolean;
begin
result := not (IsNaN(AValue) or IsInfinite(AValue) or IsInfinite(-AValue));
end;
I am adding the modified fpexprpars unit here ("ArgToFloat" and "IsNumber" are contained), and I am also enclosing my unit mpMath.pas which prepares most of the math functions and a variety of special functions found in fpc's numlib for usage by the parser. Just copy these units into your project folder, and the new fpexprpars will be used instead of fpc's one.