Recent

Author Topic: About TFPExpressionParser  (Read 22679 times)

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
About TFPExpressionParser
« on: January 21, 2013, 06:55:12 pm »
After a quick search on google and here on "lazarus forum" I found almost no quotes about TFPExpressionParser.... I found a remarkable quote here...

http://www.lazarusforum.de/viewtopic.php?f=18&t=6374  by "wp_xyz"

Code: [Select]

var
FParser: TFPExpressionParser;

...

FParser := TFpExpressionParser.Create(self);
with FParser do begin
   BuiltIns := [bcMath];
   Identifiers.AddFloatVariable('x', 0.0);
   Identifiers.AddFloatVariable('e', exp(1.0));
   Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
// etc.
end;

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;

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;



Hi WP,

Can You put that remarkable post  here in plain English? 

I think TFPExpressionParser need more disclosure and exemples...  I am implementing a wraper component...

Thanks for your valuable help!

Greetings!

Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11455
  • FPC developer.
Re: About TFPExpressionParser
« Reply #1 on: January 21, 2013, 08:54:03 pm »
Or just use symbolic. It is the expressionparser with examples :-)

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
Re: About TFPExpressionParser
« Reply #2 on: January 21, 2013, 11:04:05 pm »
Hi Marcov,

Thanks for the info.  ;D

I'll have a look, I think I found a link to your work... to facilitate you could post a link here?

Greetings!
Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: About TFPExpressionParser
« Reply #3 on: January 22, 2013, 12:10:57 am »
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:
Code: [Select]
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):
Code: [Select]
  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
Code: [Select]
  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:
Code: [Select]
  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
Code: [Select]
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:
Code: [Select]
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]:
Code: [Select]
  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.
Code: [Select]
  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:

Code: [Select]
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:

Code: [Select]
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:
Code: [Select]
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.
« Last Edit: January 22, 2013, 12:16:28 am by wp »

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
Re: About TFPExpressionParser
« Reply #4 on: January 22, 2013, 12:51:59 am »

Hi Wp,

After that excenlente explanation google will now know freepascal/ lazarus has a expression parser!

I will test your work and put my component here.

Thank you so very much!

Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11455
  • FPC developer.

BigChimp

  • Hero Member
  • *****
  • Posts: 5740
  • Add to the wiki - it's free ;)
    • FPCUp, PaperTiger scanning and other open source projects
Re: About TFPExpressionParser
« Reply #6 on: January 22, 2013, 01:05:42 pm »
Thanks, Marco!
Want quicker answers to your questions? Read http://wiki.lazarus.freepascal.org/Lazarus_Faq#What_is_the_correct_way_to_ask_questions_in_the_forum.3F

Open source including papertiger OCR/PDF scanning:
https://bitbucket.org/reiniero

Lazarus trunk+FPC trunk x86, Windows x64 unless otherwise specified

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
Re: About TFPExpressionParser
« Reply #7 on: January 22, 2013, 01:51:08 pm »
Thanks, Marcov

I'm studying your code ... maybe I can make a "bridge" on it too... :D
Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

bigeno

  • Sr. Member
  • ****
  • Posts: 266
Re: About TFPExpressionParser
« Reply #8 on: January 30, 2013, 01:23:17 pm »
Hi, I try to use this parser and I need help.

suppose that I've expression:
Code: [Select]
FParser.Expression := 'v3*2+v5-sin(v1)';where v1,v2,v3,v4,v5,... are variables.
I can't write
Code: [Select]
FParser.Identifiers.AddFloatVariable('v1', value);because I don't know which of v1,v2,v3,... will be in expression.

Is there any parser function to parse for variables values (and names) occured in expression ?

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
Re: About TFPExpressionParser
« Reply #9 on: January 30, 2013, 01:51:55 pm »
1. Carefully read the WP post. ;D

2.Here you create the object and initializes its variables "x" and "e" with zero:

FParser := TFpExpressionParser.Create(self);
with FParser do begin
   BuiltIns := [bcMath];

   Identifiers.AddFloatVariable('x', 0.0);          //index=0
   Identifiers.AddFloatVariable('e', exp(1.0));   //index=1

   Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
// etc.
end;

3. Somewhere in your code you can assign new values to "x" and "e" ​​that you need:

FParser.Identifiers[0] := new_x_value;//new x
FParser.Identifiers[1] := new_e_value;//new e

Have fun!
« Last Edit: January 30, 2013, 02:06:53 pm by jmpessoa »
Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

bigeno

  • Sr. Member
  • ****
  • Posts: 266
Re: About TFPExpressionParser
« Reply #10 on: January 30, 2013, 02:06:57 pm »
Ok, I understand, but what about names of vars ?

I don't know how will look like the expression, as example:
Code: [Select]
FParser.Expression := 'v3*2+v5-sin(v1)';
I know that will be vars v1,v2,....,v1000, but don't know which.

If I get v5 then I know what to substitute for v5.
How to get list of variables (names) occured in expresion ?

I can set names the variables as variable1, variable2, etc. and search for this string in Expression, but it does not look good.  %)

jmpessoa

  • Hero Member
  • *****
  • Posts: 2302
Re: About TFPExpressionParser
« Reply #11 on: January 30, 2013, 02:19:43 pm »
The name of its variables also will be known only at runtime?
is it?  :o
Lamw: Lazarus Android Module Wizard
https://github.com/jmpessoa/lazandroidmodulewizard

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: About TFPExpressionParser
« Reply #12 on: January 30, 2013, 04:25:01 pm »
Quote
The name of its variables also will be known only at runtime?
Exactly, it is ok to define the variable names at runtime.

That should work to set up the parser (the value 0.0 is just a dummy):
Code: [Select]
with FParser do begin
  AddFloatVariable('v3', 0.0);  // the value can be changed by Identifiers[0]
  AddFloatVariable('v5', 0.0);  // the value can be changed by Identifiers[1]
  AddFloatVariable('v1', 0.0);  // the value can be changed by Identifiers[2]
  Expression := 'v3*2+v5-sin(v1)'
end:

In order to scan, for example v1 between 0.1 and 1.0 (in 0.1 steps), for v3 = 1.0 and v5 = -10.0, you can do the following (untested...):

Code: [Select]
const
  v1min = 0.1;
  v1max = 1.0;
  dv1 = 0.1;
var
  v1: Double;
  res: TFPExpressionResult;
begin
  FParser.Identifiers[0] := 1.0;  // this is v3 because it was added first
  FParser.Identifiers[1] := -10.0; // this is v5 because it was added second
  v1 := v1min;
  repeat
    FParser.Identifieres[2] := v2; 
    res := FParser.Evaluate;
    WriteLn(re.ResFloat);  // or do something else with the result
    v1 := v1 + dv1;
  until v1 > v1max;
end;

bigeno

  • Sr. Member
  • ****
  • Posts: 266
Re: About TFPExpressionParser
« Reply #13 on: January 31, 2013, 09:15:19 pm »
@wp,

a lot of limits with AddFunction('tan', 'F', 'F', @ExprTan) when you must to determine number of agruments 'FF...'.
Do you see any reasonable solution when I do not know the number of arguments ? (I know numer of variables but not all of them as a function args. sum(v1,v2)+v3 )

« Last Edit: January 31, 2013, 11:00:37 pm by bigeno »

bigeno

  • Sr. Member
  • ****
  • Posts: 266
Re: About TFPExpressionParser
« Reply #14 on: January 31, 2013, 11:33:19 pm »
OK.

Because the parser only goes forward, it can't predict the number of arguments.
but it can cut agruments if find right bracket (end).

This expression sum(v1,v2)+sum(v1,v2,v3) cause error.

I made patch for fpexprpars2 so I can workaround this problem.
With this modification you can use multi args. function.
Same function name with diff. number of args.
  sum(v1,v2)+sum(v1,v2,v3)

but, you must specify maximum numeber of args. (as ex. count of variables)

 FParser.Identifiers.AddFunction('sum', 'F', 'FFFFFF', @ExprSum);

Code: [Select]
function TFPExpressionParser.Primitive
...
          Args[AI]:=Level1;
          Inc(AI);
          If (TokenType<>ttComma) then
            If (AI<Abs(ACount)) then begin
+              if (TokenType=ttRight) then begin
+                 SetLength(Args,AI);
+                 break;
+              end else
                 ParserError(Format(SErrCommaExpected,[Scanner.Pos,CurrentToken]));
            end;
        Until (AI=ACount) or ((ACount<0) and (TokenType=ttRight));

 

TinyPortal © 2005-2018