View Issue Details

IDProjectCategoryView StatusLast Update
0006044JEDI Code LibraryJclExprEvalpublic2024-01-02 21:24
ReporterdavidheffernanAssigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status resolvedResolutionfixed 
Product VersionVersion 2.5 (Subversion repository/Daily zips) 
Target VersionFixed in Version 
Summary0006044: Add a power operator
DescriptionI'd like to see a power operator added to the expression evaluator. My personal choice would be to use ^. There would need to be an extra precedence level introduced. So the operator precedence list would look like this:

// (highest) not bnot(bitwise) +(unary) -(unary) (level 4)
// ^ (level 3)
// * / div mod and band(bitwise) shl shr (level 2)
// +(binary) -(binary) or xor bor(bitwise) bxor(bitwise) (level 1)
// (lowest) < <= > >= cmp = <> (level 0)
Additional InformationImplementing this is pretty simple. The two classes that need work are TExprCompileParser and TExprEvalParser.

Let's look at the latter. The function currently named EvalExprLevel4 would become. And in the implementation it would call EvalExprLevel4 instead of EvalExprLevel3. And a new function named EvalExprLevel3 would have to be added to implement the power operator. These routines would look like this:


function TExprEvalParser.EvalExprLevel3(ASkip: Boolean): TFloat;
begin
  Result := EvalExprLevel4(ASkip);

  while True do
    case Lexer.CurrTok of
      etArrow:
        Result := Power(Result, EvalExprLevel4(True));
    else
      Break;
    end;
end;

function TExprEvalParser.EvalExprLevel4(ASkip: Boolean): TFloat;
begin
  if ASkip then
    Lexer.NextTok;

  case Lexer.CurrTok of
    etPlus:
      Result := EvalExprLevel4(True);
    etMinus:
      Result := -EvalExprLevel4(True);
    etIdentifier: // not, bnot
      if AnsiSameText(Lexer.TokenAsString, 'not') then
      begin
        if EvalExprLevel4(True) <> 0.0 then
          Result := 0.0
        else
          Result := 1.0;
      end
      else
      if AnsiSameText(Lexer.TokenAsString, 'bnot') then
        Result := not Round(EvalExprLevel4(True))
      else
        Result := EvalFactor;
  else
    Result := EvalFactor;
  end;
end;
TagsNo tags attached.
Fixed in GIT commit74e0119b6ff40952dd670d91d2487e502ad01fbe
Fixed in SVN revision
IDE versionAll

Activities

alquimista

2013-03-17 11:39

reporter   ~0020451

thanks, I needed to add a power operator.
This option should be the default..

jfudickar

2013-06-24 21:59

developer   ~0020536

Could you create a patch file and a small sample. Then I will try to commit it.
I'm not familiar with this objects.

2013-06-27 20:27

 

JclExprEvalPowerOperator.patch (6,622 bytes)
diff --git "a/C:\\Users\\heff\\AppData\\Local\\Temp\\TortoiseGit\\JclF486.tmp\\JclExprEval-2f4215d-left.pas" "b/C:\\Users\\heff\\Desktop\\jcl\\jcl\\source\\common\\JclExprEval.pas"
index 9f62a66..bf357e4 100644
--- "a/C:\\Users\\heff\\AppData\\Local\\Temp\\TortoiseGit\\JclF486.tmp\\JclExprEval-2f4215d-left.pas"
+++ "b/C:\\Users\\heff\\Desktop\\jcl\\jcl\\source\\common\\JclExprEval.pas"
@@ -40,7 +40,8 @@
 // all binary operators are associated from left to right
 // all unary operators are associated from right to left
 
-// (highest) not bnot(bitwise) +(unary) -(unary)                      (level 3)
+// (highest) not bnot(bitwise) +(unary) -(unary)                      (level 4)
+//           ^                                                        (level 3)
 //           * / div mod and band(bitwise) shl shr                    (level 2)
 //           +(binary) -(binary) or xor bor(bitwise) bxor(bitwise)    (level 1)
 // (lowest)  < <= > >= cmp = <>                                       (level 0)
@@ -61,9 +62,9 @@ uses
   JclUnitVersioning,
   {$ENDIF UNITVERSIONING}
   {$IFDEF HAS_UNITSCOPE}
-  System.SysUtils, System.Classes,
+  System.SysUtils, System.Classes, System.Math,
   {$ELSE ~HAS_UNITSCOPE}
-  SysUtils, Classes,
+  SysUtils, Classes, Math,
   {$ENDIF ~HAS_UNITSCOPE}
   JclBase, JclSysUtils, JclStrHashMap, JclResources;
 
@@ -441,6 +442,7 @@ type
     function Subtract(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
     function Multiply(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
     function Divide(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
+    function Power(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
     function IntegerDivide(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
     function Modulo(ALeft, ARight: TExprNode): TExprNode; virtual; abstract;
     function Negate(AValue: TExprNode): TExprNode; virtual; abstract;
@@ -486,6 +488,7 @@ type
     function CompileExprLevel1(ASkip: Boolean): TExprNode; virtual;
     function CompileExprLevel2(ASkip: Boolean): TExprNode; virtual;
     function CompileExprLevel3(ASkip: Boolean): TExprNode; virtual;
+    function CompileExprLevel4(ASkip: Boolean): TExprNode; virtual;
     function CompileFactor: TExprNode; virtual;
     function CompileIdentFactor: TExprNode; virtual;
   public
@@ -505,6 +508,7 @@ type
     function EvalExprLevel1(ASkip: Boolean): TFloat; virtual;
     function EvalExprLevel2(ASkip: Boolean): TFloat; virtual;
     function EvalExprLevel3(ASkip: Boolean): TFloat; virtual;
+    function EvalExprLevel4(ASkip: Boolean): TFloat; virtual;
     function EvalFactor: TFloat; virtual;
     function EvalIdentFactor: TFloat; virtual;
   public
@@ -607,6 +611,7 @@ type
     function Subtract(ALeft, ARight: TExprNode): TExprNode; override;
     function Multiply(ALeft, ARight: TExprNode): TExprNode; override;
     function Divide(ALeft, ARight: TExprNode): TExprNode; override;
+    function Power(ALeft, ARight: TExprNode): TExprNode; override;
     function IntegerDivide(ALeft, ARight: TExprNode): TExprNode; override;
     function Modulo(ALeft, ARight: TExprNode): TExprNode; override;
     function Negate(AValue: TExprNode): TExprNode; override;
@@ -1216,20 +1221,33 @@ end;
 
 function TExprCompileParser.CompileExprLevel3(ASkip: Boolean): TExprNode;
 begin
+  Result := CompileExprLevel4(ASkip);
+
+  while True do
+    case Lexer.CurrTok of
+      etArrow:
+        Result := NodeFactory.Power(Result, CompileExprLevel4(True));
+    else
+      Break;
+    end;
+end;
+
+function TExprCompileParser.CompileExprLevel4(ASkip: Boolean): TExprNode;
+begin
   if ASkip then
     Lexer.NextTok;
 
   case Lexer.CurrTok of
     etPlus:
-      Result := CompileExprLevel3(True);
+      Result := CompileExprLevel4(True);
     etMinus:
-      Result := NodeFactory.Negate(CompileExprLevel3(True));
+      Result := NodeFactory.Negate(CompileExprLevel4(True));
     etIdentifier: // not, bnot
       if AnsiSameText(Lexer.TokenAsString, 'not') then
-        Result := NodeFactory.LogicalNot(CompileExprLevel3(True))
+        Result := NodeFactory.LogicalNot(CompileExprLevel4(True))
       else
       if AnsiSameText(Lexer.TokenAsString, 'bnot') then
-        Result := NodeFactory.BitwiseNot(CompileExprLevel3(True))
+        Result := NodeFactory.BitwiseNot(CompileExprLevel4(True))
       else
         Result := CompileFactor;
   else
@@ -1451,25 +1469,38 @@ end;
 
 function TExprEvalParser.EvalExprLevel3(ASkip: Boolean): TFloat;
 begin
+  Result := EvalExprLevel4(ASkip);
+
+  while True do
+    case Lexer.CurrTok of
+      etArrow:
+        Result := Power(Result, EvalExprLevel4(True));
+    else
+      Break;
+    end;
+end;
+
+function TExprEvalParser.EvalExprLevel4(ASkip: Boolean): TFloat;
+begin
   if ASkip then
     Lexer.NextTok;
 
   case Lexer.CurrTok of
     etPlus:
-      Result := EvalExprLevel3(True);
+      Result := EvalExprLevel4(True);
     etMinus:
-      Result := -EvalExprLevel3(True);
+      Result := -EvalExprLevel4(True);
     etIdentifier: // not, bnot
       if AnsiSameText(Lexer.TokenAsString, 'not') then
       begin
-        if EvalExprLevel3(True) <> 0.0 then
+        if EvalExprLevel4(True) <> 0.0 then
           Result := 0.0
         else
           Result := 1.0;
       end
       else
       if AnsiSameText(Lexer.TokenAsString, 'bnot') then
-        Result := not Round(EvalExprLevel3(True))
+        Result := not Round(EvalExprLevel4(True))
       else
         Result := EvalFactor;
   else
@@ -1903,6 +1934,11 @@ type
     procedure Execute; override;
   end;
 
+  TExprPowerVmOp = class(TExprBinaryVmOp)
+  public
+    procedure Execute; override;
+  end;
+
   TExprCompareVmOp = class(TExprBinaryVmOp)
   public
     procedure Execute; override;
@@ -2236,6 +2272,13 @@ begin
   FOutput := FLeft^ / FRight^;
 end;
 
+//=== { TExprPowerVmOp } =====================================================
+
+procedure TExprPowerVmOp.Execute;
+begin
+  FOutput := Power(FLeft^, FRight^);
+end;
+
 //=== { TExprCompareVmOp } ===================================================
 
 procedure TExprCompareVmOp.Execute;
@@ -3127,6 +3170,11 @@ begin
   Result := AddNode(TExprBinaryVmNode.Create(TExprDivideVmOp, [ALeft, ARight]));
 end;
 
+function TExprVirtMachNodeFactory.Power(ALeft, ARight: TExprNode): TExprNode;
+begin
+  Result := AddNode(TExprBinaryVmNode.Create(TExprPowerVmOp, [ALeft, ARight]));
+end;
+
 function TExprVirtMachNodeFactory.IntegerDivide(ALeft, ARight: TExprNode): TExprNode;
 begin
   Result := AddNode(TExprBinaryVmNode.Create(TExprIntegerDivideVmOp, [ALeft, ARight]));

davidheffernan

2013-06-27 20:29

reporter   ~0020547

I have just added a patch file. And here's a very simple demo sample that exercises the operator precedence rules.

program Mantis0006044demo;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Math,
  JclExprEval;

type
  TTestExpressionProc = reference to procedure(const Expression: string; const Expected: Double);

procedure DoTest(const TestExpression: TTestExpressionProc);
begin
  TestExpression('1.3^2.5*4.8', Power(1.3,2.5)*4.8);
  TestExpression('1.3^2.5*4.8', Power(1.3,2.5)*4.8);
  TestExpression('2.0*1.3^2.5+4.8', 2.0*Power(1.3,2.5)+4.8);
  TestExpression('2.0*1.3^-2.5+-4.8', 2.0*Power(1.3,-2.5)-4.8);
end;

procedure Main;
var
  Evaluator: TEvaluator;
  CompiledEvaluator: TCompiledEvaluator;
  TestExpression: TTestExpressionProc;
begin
  Evaluator := TEvaluator.Create;
  try
    TestExpression := procedure(const Expression: string; const Expected: Double)
      begin
        Writeln(Format('Expected=%.6f, Evaluator=%.6f', [Expected, Evaluator.Evaluate(Expression)]));
      end;
    Writeln('TEvaluator:');
    DoTest(TestExpression);
    Writeln;
  finally
    Evaluator.Free;
  end;

  CompiledEvaluator := TCompiledEvaluator.Create;
  try
    TestExpression := procedure(const Expression: string; const Expected: Double)
      begin
        CompiledEvaluator.Compile(Expression);
        Writeln(Format('Expected=%.6f, Evaluator=%.6f', [Expected, CompiledEvaluator.Evaluate]));
      end;
    Writeln('TCompiledEvaluator:');
    DoTest(TestExpression);
  finally
    Evaluator.Free;
  end;
end;

begin
  try
    Main;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Issue History

Date Modified Username Field Change
2012-12-03 20:37 davidheffernan New Issue
2012-12-03 20:37 davidheffernan IDE version => All
2013-03-17 11:39 alquimista Note Added: 0020451
2013-06-24 21:59 jfudickar Note Added: 0020536
2013-06-24 22:01 jfudickar Status new => feedback
2013-06-27 20:27 davidheffernan File Added: JclExprEvalPowerOperator.patch
2013-06-27 20:29 davidheffernan Note Added: 0020547
2024-01-02 21:24 AHUser Status feedback => resolved
2024-01-02 21:24 AHUser Resolution open => fixed
2024-01-02 21:24 AHUser Fixed in GIT commit => 74e0119b6ff40952dd670d91d2487e502ad01fbe