Browse Source

* Implement ExtractIdentifierNames: fix issue 39454

Michaël Van Canneyt 3 years ago
parent
commit
939572ebb6
2 changed files with 161 additions and 6 deletions
  1. 85 6
      packages/fcl-base/src/fpexprpars.pp
  2. 76 0
      packages/fcl-base/tests/testexprpars.pp

+ 85 - 6
packages/fcl-base/src/fpexprpars.pp

@@ -715,7 +715,7 @@ Type
   end;
   end;
 
 
   { TFPExpressionParser }
   { TFPExpressionParser }
-
+  TIdentifierEvent = Procedure (Sender : TObject; Const aIdentifier : String) of object;
   TFPExpressionParser = class(TComponent)
   TFPExpressionParser = class(TComponent)
   private
   private
     FBuiltIns: TBuiltInCategories;
     FBuiltIns: TBuiltInCategories;
@@ -725,6 +725,9 @@ Type
     FIdentifiers : TFPExprIdentifierDefs;
     FIdentifiers : TFPExprIdentifierDefs;
     FHashList : TFPHashObjectlist;
     FHashList : TFPHashObjectlist;
     FDirty : Boolean;
     FDirty : Boolean;
+    FOnExtractIdentifier : TIdentifierEvent;
+    FExtractIdentifiers : TStrings;
+    FUnknownIdentifier : TFPExprIdentifierDef;
     procedure CheckEOF;
     procedure CheckEOF;
     function GetAsBoolean: Boolean;
     function GetAsBoolean: Boolean;
     function GetAsDateTime: TDateTime;
     function GetAsDateTime: TDateTime;
@@ -732,10 +735,12 @@ Type
     function GetAsCurrency: Currency;
     function GetAsCurrency: Currency;
     function GetAsInteger: Int64;
     function GetAsInteger: Int64;
     function GetAsString: String;
     function GetAsString: String;
+    function GetExtractingIdentifiers: Boolean;
     function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
     function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
     procedure CheckNodes(var Left, Right: TFPExprNode);
     procedure CheckNodes(var Left, Right: TFPExprNode);
     procedure SetBuiltIns(const AValue: TBuiltInCategories);
     procedure SetBuiltIns(const AValue: TBuiltInCategories);
     procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
     procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
+    procedure AddIdentifierToStrings(Sender : TObject; Const aIdentifier : String);
   Protected
   Protected
     procedure ParserError(Msg: String);
     procedure ParserError(Msg: String);
     procedure SetExpression(const AValue: String); virtual;
     procedure SetExpression(const AValue: String); virtual;
@@ -758,12 +763,15 @@ Type
     Property Scanner : TFPExpressionScanner Read FScanner;
     Property Scanner : TFPExpressionScanner Read FScanner;
     Property ExprNode : TFPExprNode Read FExprNode;
     Property ExprNode : TFPExprNode Read FExprNode;
     Property Dirty : Boolean Read FDirty;
     Property Dirty : Boolean Read FDirty;
+    Property ExtractingIdentifiers : Boolean Read GetExtractingIdentifiers;
   public
   public
     Constructor Create(AOwner :TComponent); override;
     Constructor Create(AOwner :TComponent); override;
     Destructor Destroy; override;
     Destructor Destroy; override;
     Function IdentifierByName(const AName : ShortString) : TFPExprIdentifierDef; virtual;
     Function IdentifierByName(const AName : ShortString) : TFPExprIdentifierDef; virtual;
     Procedure Clear;
     Procedure Clear;
     Procedure EvaluateExpression(Out Result : TFPExpressionResult);
     Procedure EvaluateExpression(Out Result : TFPExpressionResult);
+    Procedure ExtractIdentifierNames(Const aExpression : String; aList : TStringList); overload;
+    Procedure ExtractIdentifierNames(Const aExpression : String; aCallback : TIdentifierEvent); overload;
     function ExtractNode(var N: TFPExprNode): Boolean;
     function ExtractNode(var N: TFPExprNode): Boolean;
     Function Evaluate : TFPExpressionResult;
     Function Evaluate : TFPExpressionResult;
     Function ResultType : TResultType;
     Function ResultType : TResultType;
@@ -856,6 +864,7 @@ var
   FileFormatSettings: TFormatSettings;
   FileFormatSettings: TFormatSettings;
 
 
 Resourcestring
 Resourcestring
+  SErrCannotRecursivelyExtractIdentifiers = 'Cannot recursively extract identifiers';
   SBadQuotes        = 'Unterminated string';
   SBadQuotes        = 'Unterminated string';
   SUnknownDelimiter = 'Unknown delimiter character: "%s"';
   SUnknownDelimiter = 'Unknown delimiter character: "%s"';
   SErrUnknownCharacter = 'Unknown character at pos %d: "%s"';
   SErrUnknownCharacter = 'Unknown character at pos %d: "%s"';
@@ -1590,6 +1599,12 @@ begin
   FIdentifiers.Assign(AValue)
   FIdentifiers.Assign(AValue)
 end;
 end;
 
 
+procedure TFPExpressionParser.AddIdentifierToStrings(Sender: TObject; const aIdentifier: String);
+begin
+  If Assigned(FExtractIdentifiers) then
+    FExtractIdentifiers.Add(aIdentifier);
+end;
+
 procedure TFPExpressionParser.EvaluateExpression(Out Result: TFPExpressionResult);
 procedure TFPExpressionParser.EvaluateExpression(Out Result: TFPExpressionResult);
 begin
 begin
   If (FExpression='') then
   If (FExpression='') then
@@ -1599,6 +1614,48 @@ begin
   FExprNode.GetNodeValue(Result);
   FExprNode.GetNodeValue(Result);
 end;
 end;
 
 
+
+Procedure TFPExpressionParser.ExtractIdentifierNames(Const aExpression : String; aCallback : TIdentifierEvent); overload;
+
+
+Var
+  N : TFPExprNode;
+  OldExpr : String;
+
+begin
+  if Assigned(Self.FOnExtractIdentifier) then
+    ParserError(SErrCannotRecursivelyExtractIdentifiers);
+  N:=Nil;
+  FOnExtractIdentifier:=aCallBack;
+  try
+    // for safety
+    FreeAndNil(FUnknownIdentifier);
+    // Save old data
+    OldExpr:=Expression;
+    ExtractNode(N);
+    // Parse
+   Expression:=aExpression;
+  finally
+    FOnExtractIdentifier:=Nil;
+    FreeAndNil(FUnknownIdentifier);
+    FExpression:=OldExpr;
+    FExprNode:=N;
+  end;
+end;
+
+procedure TFPExpressionParser.ExtractIdentifierNames(const aExpression: String; aList: TStringList);
+
+begin
+   if Assigned(FExtractIdentifiers) then
+     ParserError(SErrCannotRecursivelyExtractIdentifiers);
+   FExtractIdentifiers:=aList;
+   try
+     ExtractIdentifierNames(aExpression,@AddIdentifierToStrings);
+   finally
+     FExtractIdentifiers:=Nil;
+   end;
+end;
+
 function TFPExpressionParser.ExtractNode(Var N : TFPExprNode) : Boolean;
 function TFPExpressionParser.ExtractNode(Var N : TFPExprNode) : Boolean;
 begin
 begin
   Result:=Assigned(FExprNode);
   Result:=Assigned(FExprNode);
@@ -1710,6 +1767,11 @@ begin
   Result:=Res.ResString;
   Result:=Res.ResString;
 end;
 end;
 
 
+function TFPExpressionParser.GetExtractingIdentifiers: Boolean;
+begin
+  Result:=Assigned(FOnExtractIdentifier);
+end;
+
 {
 {
   Checks types of todo and match. If ToDO can be converted to it matches
   Checks types of todo and match. If ToDO can be converted to it matches
   the type of match, then a node is inserted.
   the type of match, then a node is inserted.
@@ -1847,7 +1909,8 @@ begin
       GetToken;
       GetToken;
       CheckEOF;
       CheckEOF;
       Right:=Level4;
       Right:=Level4;
-      CheckNodes(Result,Right);
+      if Not ExtractingIdentifiers then
+        CheckNodes(Result,Right);
       Case tt of
       Case tt of
         ttPlus  : Result:=TFPAddOperation.Create(Result,Right);
         ttPlus  : Result:=TFPAddOperation.Create(Result,Right);
         ttMinus : Result:=TFPSubtractOperation.Create(Result,Right);
         ttMinus : Result:=TFPSubtractOperation.Create(Result,Right);
@@ -1877,7 +1940,8 @@ begin
       tt:=TokenType;
       tt:=TokenType;
       GetToken;
       GetToken;
       Right:=Level5;
       Right:=Level5;
-      CheckNodes(Result,Right);
+      if Not ExtractingIdentifiers then
+        CheckNodes(Result,Right);
       Case tt of
       Case tt of
         ttMul : Result:=TFPMultiplyOperation.Create(Result,Right);
         ttMul : Result:=TFPMultiplyOperation.Create(Result,Right);
         ttDiv : Result:=TFPDivideOperation.Create(Result,Right);
         ttDiv : Result:=TFPDivideOperation.Create(Result,Right);
@@ -1960,6 +2024,7 @@ Var
   ID : TFPExprIdentifierDef;
   ID : TFPExprIdentifierDef;
   Args : TExprArgumentArray;
   Args : TExprArgumentArray;
   AI : Integer;
   AI : Integer;
+  S : String;
 
 
 begin
 begin
 {$ifdef debugexpr}  Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
 {$ifdef debugexpr}  Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
@@ -1989,9 +2054,22 @@ begin
     IFC:=TokenType=ttCase;
     IFC:=TokenType=ttCase;
     if Not (IFF or IFC) then
     if Not (IFF or IFC) then
       begin
       begin
-      ID:=self.IdentifierByName(CurrentToken);
+      S:=CurrentToken;
+      ID:=self.IdentifierByName(S);
       If (ID=Nil) then
       If (ID=Nil) then
-        ParserError(Format(SErrUnknownIdentifier,[CurrentToken]))
+        begin
+        if Assigned(FOnExtractIdentifier) then
+          begin
+          if not Assigned(FUnknownIdentifier) then
+            FUnknownIdentifier:=TFPExprIdentifierDef.Create(Nil);
+          ID:=FUnknownIdentifier;
+          // Call only once in case of stringlist.
+          If Not (Assigned(FExtractIdentifiers) and (FExtractIdentifiers.IndexOf(S)<>-1)) then
+            FOnExtractIdentifier(Self,S);
+          end
+        else
+          ParserError(Format(SErrUnknownIdentifier,[S]))
+        end
       end;
       end;
     // Determine number of arguments
     // Determine number of arguments
     if Iff then
     if Iff then
@@ -2070,7 +2148,8 @@ begin
     FExprNode:=Level1;
     FExprNode:=Level1;
     If (TokenType<>ttEOF) then
     If (TokenType<>ttEOF) then
       ParserError(Format(SErrUnterminatedExpression,[Scanner.Pos,CurrentToken]));
       ParserError(Format(SErrUnterminatedExpression,[Scanner.Pos,CurrentToken]));
-    FExprNode.Check;
+    if not ExtractingIdentifiers then
+      FExprNode.Check;
     end
     end
   else
   else
     FExprNode:=Nil;
     FExprNode:=Nil;

+ 76 - 0
packages/fcl-base/tests/testexprpars.pp

@@ -730,6 +730,8 @@ type
     FEventName: String;
     FEventName: String;
     FBoolValue : Boolean;
     FBoolValue : Boolean;
     FTest33 : TFPExprIdentifierDef;
     FTest33 : TFPExprIdentifierDef;
+    FIdentifiers : TStrings;
+    procedure AddIdentifier(Sender: TObject; const aIdentifier: String);
     procedure DoGetBooleanVar(var Res: TFPExpressionResult; ConstRef AName: ShortString);
     procedure DoGetBooleanVar(var Res: TFPExpressionResult; ConstRef AName: ShortString);
     procedure DoGetBooleanVarWrong(var Res: TFPExpressionResult; ConstRef AName: ShortString);
     procedure DoGetBooleanVarWrong(var Res: TFPExpressionResult; ConstRef AName: ShortString);
     procedure TestAccess(Skip: TResultType);
     procedure TestAccess(Skip: TResultType);
@@ -780,6 +782,10 @@ type
     procedure TestVariable34;
     procedure TestVariable34;
     procedure TestVariable35;
     procedure TestVariable35;
     procedure TestVariable36;
     procedure TestVariable36;
+    Procedure TestGetIdentifierNames;
+    Procedure TestGetIdentifierNamesCallback;
+    Procedure TestGetIdentifierNamesDouble;
+    Procedure TestGetIdentifierNamesDoubleCallback;
   end;
   end;
 
 
   { TTestParserFunctions }
   { TTestParserFunctions }
@@ -4686,6 +4692,70 @@ begin
   AssertCurrencyResult(1.23);
   AssertCurrencyResult(1.23);
 end;
 end;
 
 
+procedure TTestParserVariables.TestGetIdentifierNames;
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.Sorted:=true;
+    FP.ExtractIdentifierNames('a+b',L);
+    AssertEquals('Element count',2,L.Count);
+    AssertEquals('First element','a',L[0]);
+    AssertEquals('second element','b',L[1]);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestParserVariables.TestGetIdentifierNamesCallback;
+
+begin
+  FIdentifiers:=TStringList.Create;
+  try
+    TStringList(FIdentifiers).Sorted:=true;
+    FP.ExtractIdentifierNames('a+b',@AddIdentifier);
+    AssertEquals('Element count',2,FIdentifiers.Count);
+    AssertEquals('First element','a',FIdentifiers[0]);
+    AssertEquals('second element','b',FIdentifiers[1]);
+  Finally
+    FreeAndNil(FIdentifiers);
+  end;
+end;
+
+procedure TTestParserVariables.TestGetIdentifierNamesDouble;
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.Sorted:=true;
+    FP.ExtractIdentifierNames('a+(b*a)',L);
+    AssertEquals('Element count',2,L.Count);
+    AssertEquals('First element','a',L[0]);
+    AssertEquals('second element','b',L[1]);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestParserVariables.TestGetIdentifierNamesDoubleCallback;
+begin
+  FIdentifiers:=TStringList.Create;
+  try
+    FP.ExtractIdentifierNames('a+(b*a)',@AddIdentifier);
+    AssertEquals('Element count',3,FIdentifiers.Count);
+    AssertEquals('First element','a',FIdentifiers[0]);
+    AssertEquals('second element','b',FIdentifiers[1]);
+    AssertEquals('third element','a',FIdentifiers[2]);
+  Finally
+    FreeAndNil(FIdentifiers);
+  end;
+end;
+
 procedure TTestParserVariables.TestVariable12;
 procedure TTestParserVariables.TestVariable12;
 
 
 Var
 Var
@@ -4927,6 +4997,12 @@ begin
   Res.ResBoolean:=FBoolValue;
   Res.ResBoolean:=FBoolValue;
 end;
 end;
 
 
+procedure TTestParserVariables.AddIdentifier(Sender: TObject; const aIdentifier: String);
+begin
+  AssertNotNull('Have identifier list',FIdentifiers);
+  FIdentifiers.Add(aIdentifier);
+end;
+
 procedure TTestParserVariables.TestVariable31;
 procedure TTestParserVariables.TestVariable31;
 
 
 Var
 Var