{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: A graph of declaration overloads. Create via CodeToolBoss.GatherOverloads(Code,X,Y,Graph). Add units via Graph.ScanToolForIdentifier. } unit FindOverloads; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Laz_AVL_Tree, // Codetools FileProcs, BasicCodeTools, CodeTree, CodeGraph, CodeCache, FindDeclarationTool, FindDeclarationCache, StdCodeTools; type TOverloadsGraphEdge = class; { TOverloadsGraphNode } TOverloadsGraphNode = class(TCodeGraphNode) public Identifier: string; Tool: TFindDeclarationTool; ShortestPathLength: integer; ShortestPathEdge: TOverloadsGraphEdge; function AsDebugString: string; end; TOverloadsGraphEdgeType = ( ogetParentChild, ogetAncestorInherited, ogetAliasOld ); TOverloadsGraphEdgeTypes = set of TOverloadsGraphEdgeType; { TOverloadsGraphEdge } TOverloadsGraphEdge = class(TCodeGraphEdge) public Typ: TOverloadsGraphEdgeType; function AsDebugString: string; function Cost: integer; end; { TDeclarationOverloadsGraph } TDeclarationOverloadsGraph = class private FGraph: TCodeGraph; FIdentifier: string; FOnGetCodeToolForBuffer: TOnGetCodeToolForBuffer; FShortestNodes: TAVLTree; FStartCode: TCodeBuffer; FStartCodeNode: TCodeTreeNode; FStartTool: TFindDeclarationTool; FStartX: integer; FStartY: integer; function AddContext(Tool: TFindDeclarationTool; CodeNode: TCodeTreeNode): TOverloadsGraphNode; function AddEdge(Typ: TOverloadsGraphEdgeType; FromNode, ToNode: TCodeTreeNode): TOverloadsGraphEdge; public constructor Create; destructor Destroy; override; procedure Clear; function Init(Code: TCodeBuffer; X,Y: integer): Boolean; procedure ScanToolForIdentifier(Tool: TStandardCodeTool; OnlyInterface: boolean); procedure ComputeShortestPaths; public property Graph: TCodeGraph read FGraph; property Identifier: string read FIdentifier; property ShortestNodes: TAVLTree read FShortestNodes;// nodes sorted for ShortestPathLength (after ComputeShortestPaths) property StartCode: TCodeBuffer read FStartCode; property StartX: integer read FStartX; property StartY: integer read FStartY; property StartTool: TFindDeclarationTool read FStartTool; property StartCodeNode: TCodeTreeNode read FStartCodeNode; property OnGetCodeToolForBuffer: TOnGetCodeToolForBuffer read FOnGetCodeToolForBuffer write FOnGetCodeToolForBuffer; end; const OverloadsGraphEdgeTypeNames: array[TOverloadsGraphEdgeType] of string = ( 'Parent-Child', 'Ancestor-Inherited', 'Alias-Old' ); function CompareOverloadsNodesByPathLen(Node1, Node2: TOverloadsGraphNode): integer; implementation function CompareOverloadsNodesByPathLen(Node1, Node2: TOverloadsGraphNode ): integer; begin if Node1.ShortestPathLength>Node2.ShortestPathLength then Result:=1 else if Node1.ShortestPathLengthnil then exit; // add new node //DebugLn(['TDeclarationOverloadsGraph.AddContext ',Tool.MainFilename,' ',CodeNode.DescAsString,' "',dbgstr(copy(Tool.Src,CodeNode.StartPos,20)),'"']); Result:=TOverloadsGraphNode(Graph.GetGraphNode(CodeNode,true)); Result.Tool:=Tool; if CodeNode.Desc in AllIdentifierDefinitions then Result.Identifier:=Tool.ExtractDefinitionName(CodeNode) else begin case CodeNode.Desc of ctnEnumIdentifier: Result.Identifier:=GetIdentifier(@Tool.Src[CodeNode.StartPos]); end; end; // add parent nodes to graph ParentCodeNode:=CodeNode.Parent; while ParentCodeNode<>nil do begin //DebugLn(['TDeclarationOverloadsGraph.AddContext ',ParentCodeNode.DescAsString]); if ParentCodeNode.Desc in AllSourceTypes+AllClasses then begin //DebugLn(['TDeclarationOverloadsGraph.AddContext ADD parent']); ParentGraphNode:=AddContext(Tool,ParentCodeNode); AddEdge(ogetParentChild,ParentGraphNode.Node,Result.Node); break; end; if ParentCodeNode.Parent<>nil then ParentCodeNode:=ParentCodeNode.Parent else ParentCodeNode:=ParentCodeNode.PriorBrother; end; // add ancestors, interfaces if (CodeNode.Desc=ctnTypeDefinition) and (CodeNode.FirstChild<>nil) and (CodeNode.FirstChild.Desc in AllClasses) then begin //DebugLn(['TDeclarationOverloadsGraph.AddContext a class or interface']); // a class or class interface ClassNode:=CodeNode.FirstChild; Tool.BuildSubTree(ClassNode); if (ClassNode.FirstChild<>nil) and (ClassNode.FirstChild.Desc=ctnClassInheritance) then begin //DebugLn(['TDeclarationOverloadsGraph.AddContext has ancestor(s)']); Params:=TFindDeclarationParams.Create; ListOfPFindContext:=nil; try Tool.FindAncestorsOfClass(ClassNode,ListOfPFindContext,Params,false); //DebugLn(['TDeclarationOverloadsGraph.AddContext ancestors found: ',ListOfPFindContext<>nil]); if ListOfPFindContext<>nil then begin for i:=0 to ListOfPFindContext.Count-1 do begin //DebugLn(['TDeclarationOverloadsGraph.AddContext ancestor #',i]); ContextPtr:=PFindContext(ListOfPFindContext[i]); AncestorGraphNode:=AddContext(ContextPtr^.Tool,ContextPtr^.Node); AddEdge(ogetAncestorInherited,AncestorGraphNode.Node,Result.Node); end; end; finally FreeListOfPFindContext(ListOfPFindContext); Params.Free; end; end; end; // ToDo: add alias if (CodeNode.Desc=ctnTypeDefinition) and (CodeNode.FirstChild<>nil) and (CodeNode.FirstChild.Desc=ctnIdentifier) then begin //DebugLn(['TDeclarationOverloadsGraph.AddContext alias']); Params:=TFindDeclarationParams.Create; try try Context:=Tool.FindBaseTypeOfNode(Params,CodeNode); if Context.Node<>nil then begin while (Context.Node<>nil) and (not (Context.Node.Desc in AllIdentifierDefinitions)) do Context.Node:=Context.Node.Parent; if Context.Node<>nil then begin AliasGraphNode:=AddContext(Context.Tool,Context.Node); AddEdge(ogetAliasOld,AliasGraphNode.Node,Result.Node); end; end; except end; finally Params.Free; end; end; end; function TDeclarationOverloadsGraph.AddEdge(Typ: TOverloadsGraphEdgeType; FromNode, ToNode: TCodeTreeNode): TOverloadsGraphEdge; begin Result:=TOverloadsGraphEdge(Graph.GetEdge(FromNode,ToNode,false)); if (Result<>nil) then begin if Result.Typ<>Typ then RaiseCatchableException('TDeclarationOverloadsGraph.AddEdge Typ conflict'); exit; end; // create new edge Result:=TOverloadsGraphEdge(Graph.GetEdge(FromNode,ToNode,true)); Result.Typ:=Typ; //DebugLn(['TDeclarationOverloadsGraph.AddEdge ',Result.AsDebugString]); end; constructor TDeclarationOverloadsGraph.Create; begin FGraph:=TCodeGraph.Create(TOverloadsGraphNode,TOverloadsGraphEdge); end; destructor TDeclarationOverloadsGraph.Destroy; begin Clear; FreeAndNil(FGraph); inherited Destroy; end; procedure TDeclarationOverloadsGraph.Clear; begin FreeAndNil(FShortestNodes); Graph.Clear; FStartCodeNode:=nil; FStartCode:=nil; FStartX:=0; FStartY:=0; FIdentifier:=''; end; function TDeclarationOverloadsGraph.Init(Code: TCodeBuffer; X, Y: integer ): Boolean; var CleanPos: integer; begin Result:=false; FStartCode:=Code; FStartX:=X; FStartY:=Y; fStartTool:=OnGetCodeToolForBuffer(Self,Code,true); if fStartTool.CaretToCleanPos(CodeXYPosition(X,Y,Code),CleanPos)<>0 then begin DebugLn(['TDeclarationOverloadsGraph.Init Tool.CaretToCleanPos failed']); exit(false); end; fStartCodeNode:=fStartTool.FindDeepestNodeAtPos(CleanPos,true); DebugLn(['TDeclarationOverloadsGraph.Init Add start context ',FStartTool.MainFilename,' ',FStartCodeNode.DescAsString,' ',dbgstr(copy(FStartTool.Src,FStartCodeNode.StartPos,20))]); AddContext(fStartTool,StartCodeNode); fIdentifier:=''; if fStartCodeNode.Desc in AllIdentifierDefinitions+[ctnEnumIdentifier] then fIdentifier:=GetIdentifier(@fStartTool.Src[fStartCodeNode.StartPos]); Result:=true; end; procedure TDeclarationOverloadsGraph.ScanToolForIdentifier( Tool: TStandardCodeTool; OnlyInterface: boolean); var Entry: PInterfaceIdentCacheEntry; Node: TCodeTreeNode; begin if Identifier='' then exit; if OnlyInterface then begin // use interface cache try Tool.BuildInterfaceIdentifierCache(false); except end; if Tool.InterfaceIdentifierCache<>nil then begin Entry:=Tool.InterfaceIdentifierCache.FindIdentifier(PChar(Identifier)); while Entry<>nil do begin if CompareIdentifiers(Entry^.Identifier,PChar(Identifier))=0 then AddContext(Tool,Entry^.Node); Entry:=Entry^.NextEntry; end; end; end else begin // scan whole unit/program try Tool.Explore(false); except end; if Tool.Tree=nil then exit; Node:=Tool.Tree.Root; while Node<>nil do begin case Node.Desc of ctnTypeDefinition,ctnVarDefinition,ctnConstDefinition,ctnGenericType, ctnEnumIdentifier: if CompareIdentifiers(@Tool.Src[Node.StartPos],PChar(Identifier))=0 then AddContext(Tool,Node); ctnProcedure: begin Tool.MoveCursorToProcName(Node,true); if CompareIdentifiers(@Tool.Src[Tool.CurPos.StartPos],PChar(Identifier))=0 then AddContext(Tool,Node); end; ctnProperty: begin Tool.MoveCursorToPropName(Node); if CompareIdentifiers(@Tool.Src[Tool.CurPos.StartPos],PChar(Identifier))=0 then AddContext(Tool,Node); end; end; Node:=Node.Next; end; end; end; procedure TDeclarationOverloadsGraph.ComputeShortestPaths; var AVLNode: TAVLTreeNode; GraphNode: TOverloadsGraphNode; StartGraphNode: TOverloadsGraphNode; WorkNodes: TAVLTree; Edge: TOverloadsGraphEdge; GraphNode2: TOverloadsGraphNode; begin (* Dijkstra-Algorithm: for v in V do l(v):=inf l(u) := 0 W:=V while W not empty do v := { v in W | l(v) minimal } W:=W-{v} for x in Adj(v), x in W do if l(v)+w(v,x)nil do begin GraphNode:=TOverloadsGraphNode(AVLNode.Data); GraphNode.ShortestPathEdge:=nil; if GraphNode.Node=StartCodeNode then GraphNode.ShortestPathLength:=0 else GraphNode.ShortestPathLength:=high(integer) div 2; WorkNodes.Add(GraphNode); AVLNode:=Graph.Nodes.FindSuccessor(AVLNode); end; // for each remaining node that has currently the shortest path ... while WorkNodes.Count>0 do begin GraphNode:=TOverloadsGraphNode(WorkNodes.FindLowest.Data); // this node's ShortestPathLength is final WorkNodes.Remove(GraphNode); // update adjacent nodes AVLNode:=GraphNode.OutTree.FindLowest; while AVLNode<>nil do begin Edge:=TOverloadsGraphEdge(AVLNode.Data); GraphNode2:=TOverloadsGraphNode(Edge.ToNode); if GraphNode.ShortestPathLength+Edge.Costnil do begin Edge:=TOverloadsGraphEdge(AVLNode.Data); GraphNode2:=TOverloadsGraphNode(Edge.FromNode); if GraphNode.ShortestPathLength+Edge.Costnil do begin GraphNode:=TOverloadsGraphNode(AVLNode.Data); FShortestNodes.Add(GraphNode); AVLNode:=Graph.Nodes.FindSuccessor(AVLNode); end; end; { TOverloadsGraphNode } function TOverloadsGraphNode.AsDebugString: string; begin Result:=Identifier; if Node<>nil then Result:=Result+' Desc="'+Node.DescAsString+'"'; if (Tool<>nil) and (Node<>nil) and (Identifier='') then begin Result:=Result+' "'+dbgstr(copy(Tool.Src,Node.StartPos,20))+'"'; end; end; { TOverloadsGraphEdge } function TOverloadsGraphEdge.AsDebugString: string; begin Result:='Typ='+OverloadsGraphEdgeTypeNames[Typ] +' '+(FromNode as TOverloadsGraphNode).AsDebugString +'->' +(ToNode as TOverloadsGraphNode).AsDebugString; end; function TOverloadsGraphEdge.Cost: integer; begin case Typ of ogetParentChild: Result:=10; ogetAncestorInherited: Result:=1; ogetAliasOld: Result:=1; else Result:=100; end; end; end.