Difference between revisions of "How To Use TFPExpressionParser"
m (code correction) |
(Add internal link how to extend the parser) |
||
Line 50: | Line 50: | ||
* '''bcConversion''': <code>IntToStr</code>, <code>StrToInt</code>, <code>StrToIntDef</code>, <code>FloatToStr</code>, <code>StrToFloat</code>, <code>StrToFloatDef</code>, <code>BoolToStr</code>, <code>StrToBool</code>, <code>StrToBoolDef</code>, <code>DateToStr</code>, <code>TimeToStr</code>, <code>StrToDate</code>, <code>StrToDateDef</code>, <code>StrToTime</code>, <code>StrToTimeDef</code>, <code>StrToDateTime</code>, <code>StrToDateTimeDef</code> | * '''bcConversion''': <code>IntToStr</code>, <code>StrToInt</code>, <code>StrToIntDef</code>, <code>FloatToStr</code>, <code>StrToFloat</code>, <code>StrToFloatDef</code>, <code>BoolToStr</code>, <code>StrToBool</code>, <code>StrToBoolDef</code>, <code>DateToStr</code>, <code>TimeToStr</code>, <code>StrToDate</code>, <code>StrToDateDef</code>, <code>StrToTime</code>, <code>StrToTimeDef</code>, <code>StrToDateTime</code>, <code>StrToDateTimeDef</code> | ||
− | <code>bcData</code>, <code>bcVaria</code>, and <code>bcUser</code> are not used anywhere within fpexprpars. | + | <code>bcData</code>, <code>bcVaria</code>, and <code>bcUser</code> are not used anywhere within fpexprpars. The [[#Adding user-defined functions|last section]] gives instructions how to extend the parser with more functions. |
{{Note|These symbols are not case-sensitive.}} | {{Note|These symbols are not case-sensitive.}} |
Revision as of 00:26, 14 January 2019
TFPExpressionParser allows to analyze and calculate expressions such as sin(x)*cos(2*x)
for any value of the variable x
. Besides mathematical expressions it can also handle boolean, string formulas, date/time values etc. Even user-provided functions can be linked in.
It belongs to FPC Free Component Library (FCL) and is implemented in the unit fpexprpars.pp
, folder (fpc_source_dir)/packages/fcl-base/src
. Just add fpexprpars
to the uses clauses to get access to its functionality. See the file "fpexprpars.txt" (in (fpc_source_dir)/packages/fcl-base/examples
) for a short documenation.
Creating the parser
You apply the parser by creating an instance like this:
uses
fpexprpars;
var
FParser: TFPExpressionParser;
begin
FParser := TFpExpressionParser.Create(self);
// ... do something (see below)
If this is called from a method of a form, "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. On the other hand, it is also possible to create the parser from anywhere in a program without a form or even class being involved; in this case use nil
as the owner of the parser, but don't forget to .Free
the parser after its usage:
uses
fpexprpars;
var
FParser: TFPExpressionParser;
begin
FParser := TFPExpressionParser.Create(nil);
try
// ... do something (see below)
finally
FParser.Free;
end;
end;
Built-in categories
The parser is designed in a very flexible way, but the default parser is quite dumb. You have to specify which kind of expressions it will accept. This is done by adding the corresponding identifier to the set of built-in categories. They are accessible by the parser's property BuiltIns
:
type
TBuiltInCategory = (bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, bcData, bcVaria, bcUser);
TBuiltInCategories = set of TBuiltInCategory;
Here is a collection of the built-in symbols which can be used by adding categories to the parser's BuiltIns
- it should be clear to anybody who "speaks" Pascal what these symbols mean...
- bcStrings:
Length
,Copy
,Delete
,Pos
,Lowercase
,Uppercase
,StringReplace
,CompareText
- bcDateTime:
Date
,Time
,Now
,DayOfWeek
,ExtractYear
,ExtractMonth
,ExtractDay
,ExtractHour
,ExtractMin
,ExtractSec
,Extractmsec
,EncodeDate
,EncodeTime
,ShortDayName
,ShortMonthName
,LongDayName
,LongMonthName
- bcMath:
cos
,sin
,arctan
,abs
,sqr
,sqrt
,exp
,ln
,log
,frac
,int
,round
,trunc
, - bcBoolean:
shl
,shr
,IFS
,IFF
,IFD
,IFI
(TheIFxxx
symbols have the same effect as fpc'sIfThen
for string (IFS
), floating point (IFF
), date/time (IFD
), or integer (IFI
) variables) - bcConversion:
IntToStr
,StrToInt
,StrToIntDef
,FloatToStr
,StrToFloat
,StrToFloatDef
,BoolToStr
,StrToBool
,StrToBoolDef
,DateToStr
,TimeToStr
,StrToDate
,StrToDateDef
,StrToTime
,StrToTimeDef
,StrToDateTime
,StrToDateTimeDef
bcData
, bcVaria
, and bcUser
are not used anywhere within fpexprpars. The last section gives instructions how to extend the parser with more functions.
In order to use a mathematical expression the option bcMath
has to be added to the parser's Builtins
:
FParser.Builtins := [bcMath]; // or FParser.Builtins := FParser.Builtins + [bcMath];
Expressions
An expression with constants
As a first example we have the parser calculate a very simple expression 1+1
.
The first step is to tell the parser which expression is to be calculated. There is a property Expression
for this purpose:
FParser.Expression := '1+1';
The next step is to calculate the expression: just call Evaluate
or EvaluateExpression
- the former is is a function while the latter one is a procedure which passes the result as a parameter.
var
parserResult: TFPExpressionResult;
begin
....
parserResult := FParser.Evaluate; // or: FParser.EvaluateExpression(parserResult);
What is that mysterious TFPExpressionResult
? Since the parser is very flexible and can deal with numbers, strings, date/times or booleans there must be a more complex data type which returns a calculation result:
type
TResultType = (rtBoolean, rtInteger, rtFloat, tDateTime, rtString);
TFPExpressionResult = record
ResString : String;
Case ResultType : TResultType of
rtBoolean : (ResBoolean : Boolean);
rtInteger : (ResInteger : Int64);
rtFloat : (ResFloat : TExprFloat);
rtDateTime : (ResDateTime : TDatetime);
rtString : ();
end;
The member ResultType
signals which one of the data fields is valid. It is important to understand this since the expression parser is very strict on data types.
In our example, we are adding two integers, therefore the result is an integer as well. If, on the other had, we had used the expression "1.0 + 1"
, the first number would have been a floating point value, and the result would have been a float! Therefore, always have a look at the member ResultType
of the TFPExpressionResult
before picking the result. To simplify the usage of the expression result data type, fpexprpars
exposes a function ArgToFloat
which gets the entire expression result record as a parameter and selects the right component if a floating point result is expected:
var
parserResult: TFPExpressionResult;
resultValue: Double;
...
parserResult := FParser.Evaluate; // or: FParser.EvaluateExpression(parserResult);
resultValue := ArgToFloat(parserResult);
Expression
. An expression with a variable
In this example, we calculate the value of sin(x)*cos(2*x)
for x = 0.5
.
Defining variables
At first we have to define the variables. We have only one, x
. The parser has a method AddFloatVariable
to declare a floating point variable; there are also methods
AddBooleanVariable
AddStringVariable
AddDateTimeVariable
for boolean, string and date/time variables, respectively.
Each one of these methods expects the name of the variable along with its default value. For the sample function sin(x)*cos(2*x)
we just call:
FParser.Identifiers.AddFloatVariable('x', 0.5);
0.5
is entered here as a default value because that is the argument at which we want to calculate the expression (it will be shown below how to modify a variable). From now on, the parser will use this number whenever it finds the variable x
in the expression.
Of course, you can add other names, e.g. constants like e
, etc. (The number pi
is already built-in).
Defining the expression
In the next step, the expression string has to be passed to the parser:
FParser.Expression := 'sin(x)*cos(2*x)';
It is essential to call this after setting up of the variables because the parser needs to know the variables for analyzing the expression.
Calculating the expression
This is done as before with the constant expression - here is a complete procedure which shows the equation and its result in a message box:
var
FParser: TFPExpressionParser;
resultValue: Double;
begin
FParser := TFPExpressionParser.Create(nil);
try
FParser.BuiltIns := [bcMath];
FParser.Identifiers.AddFloatVariable('x', 0.5);
FParser.Expression := 'sin(x)*cos(2*x)';
resultValue := FParser.Evaluate.ResFloat; // or: resultValue := ArgToFloat(FParser.Evaluate);
ShowMessage(FParser.Expression + ' = ' + FloatToStr(resultValue));
finally
FParser.Free;
end;
end;
Changing variables
So far, x
always has the value 0.5 - it behaves like a constant, we could have used the expression "sin(0.5)*cos(2*0.5)"
as well.
To make it behave more like a "variable", we now calculate the test function for the x
values between -10 and 10 at integer steps.
The main question is: How to replace the value assigned to a variable? There are several possibilities - all of them require the internal variable Identifier
(type TFPExprIdentifierDef
) which exposes various ways to access variables and their properties:
- Use the return value of the
AddFloatVariable
function. - Seek an identifier by calling
FindIdentifierByName
with the variable name as a parameter. - Access the identifier from the
Identifiers
collection of the parser by using the known index of the variable: We had addedx
as the only variable, therefore, it must be at index 0.
Once the Identifier
is known, the value of the variable can be changed by accessing the property AsFloat
(or AsDateTime
etc. accordingly):
var
FParser: TFPExpressionParser;
argument: TFPExprIdentifierDef;
s: string;
x: integer;
f: double;
begin
s:='';
FParser := TFPExpressionParser.Create(nil);
try
// Enable the use of mathematical expressions
FParser.BuiltIns := [bcMath];
// Add the function argument
argument := FParser.Identifiers.AddFloatVariable('x', 0.0);
// Define the function, using the argument name x as defined above
FParser.Expression := 'sin(x)*cos(2*x)';
// Calculate some function values
for x := -10 to 10 do
begin
argument.AsFloat := x; // Set the function argument value
f := FParser.Evaluate.ResFloat; // Calculate the function value
s := s + format('x[%d]:[%g]' + LineEnding, [x,f]); // Demo output to display the result
end;
// Show the result
ShowMessage(s);
finally
FParser.Free;
end;
end;
Adding user-defined functions
The default parser only knows the built-in functions mentioned above. One of the strengths of of the expression parser is that it is very easy to extend to include other functions. This can be done by calling the method Identifiers.AddFunction
, e.g.
FParser.Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
In this example, we 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, "I" = integer, "D" = date/time, "S" = string, or "B" = boolean) and the type of the input values (third parameter, same logic). If a function accepts several input parameters 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 found in the expression string. Since this function has a particular syntax we have to implement it in our own source code:
procedure ExprTan(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := tan(x);
end;
The result of the calculation is returned as parameter "Result"
which is a TFPExpressionResult
that we met above. The arguments for the calculation are passed by Args
which is just an array of TFPExpressionResult values - again because parameters can have several data types. The term TFPExpressionResults is maybe a bit misleading here, because this array holds all the *input* parameters as specified by the input types of the AddFunction method.