Proc Cross reference / dependancy examiner / Code Analysis

Developed or developing a new product in PureBasic? Tell the world about it.
Al_the_dutch
User
User
Posts: 70
Joined: Mon Nov 11, 2013 11:07 am
Location: Portugal

Proc Cross reference / dependancy examiner / Code Analysis

Post by Al_the_dutch »

Hello All. I needed a tool for Procedure Cross reference / dependancy examiner / Code Analysis for PureBasic and could not find one here. Thanks to Zebuddi123 in his "Tool Procedure sorter & dep finder 4 x platform porting incs" I found some clues how to make it myself. http://www.purebasic.fr/english/viewtop ... 27&t=60561.

I want to share the result, perhaps it is usefull elsewehere. I hope it is bug-free but no garanties... Additions and suggestions are welcome.

Code: Select all

; ProcCrossReference.pb

; === Procedure Cross reference / dependancy examiner / Code Analysis for PureBasic=== by Al_the_dutch 20160530
; === Who makes a flow chart /html extension from this info? 

; With thanx to Zebuddi123 in his "Tool Procedure sorter & dep finder 4 x platform porting incs"
; http://www.purebasic.fr/english/viewtopic.php?f=27&t=60561

EnableExplicit

Structure Statement
   StatementsFile$   ; The source filename
   StatementsInProc$ ; The statements in the procedure without comments
   StatementsLines.l ; The associated linenumbers
EndStructure

Structure CalledBy
   CalledByStatement$
   CalledByFile$
   CalledByType$
   CalledByProcNameShort$
   CalledByProcNameFull$
   CalledByLine.l   
EndStructure

Structure ProcList
   ProcFile$ ; for handling includes
   ProcName$
   ProcType$ ; Procedure, Macro or FileLevel code
   List ProcStatements.Statement()
EndStructure

Structure Dependancy
   ProcFile$ ; for handling includes
   ProcName$
   Proctype$ ; Procedure, Macro or FileLevel code
   Rank.l   ;     Number To sort
   NumberCalls.l
   NumberNestedCalls.l
   NumberCalledBy.l
   List ChainFromTop$()
   List CalledBys.CalledBy()
   List Calls.CalledBy()
EndStructure

Structure Calls2Rank
   ProcName$
   Rank.l
EndStructure

Structure SaveList
   List ChainFromTop$()
EndStructure

Global NewList Proc.ProcList()
Global NewList Dependancy.Dependancy()
Global NewList StatementsInFiles.Statement()
Global NewList SourceFiles.s()
Global NewList DependancyCalls2Rank.Calls2Rank()
Global NewList UniqueCallChains$()

Procedure.l Maxl(a.l,b.l)   
   If a>b 
      ProcedureReturn a
   Else
      ProcedureReturn b
   EndIf
EndProcedure


Procedure.s GetProcName(Statement.s, Type$) ; Statements like Procedure Abc(a,b,c) or Procedure  Abc (a,b,c) or Procedure.x Abc(a,b,c)
   Protected se.l, sp.l, Key$
   
   If Type$ = "F"
      ProcedureReturn "***File***"
   Else
      If Type$ = "M"
         Key$ = "Macro"
      Else
         Key$ = "Procedure"
      EndIf
      
      Statement = Trim(Statement)
      Statement = Trim(Mid(Statement, Len(Key$) + 1)) ; "Abc(a,b,c)" or "Abc (a,b,c)" or ".x  Abc  (a,b,c)" 
      
      If Left(Statement, 1) = "."      
         sp=FindString(Statement,(" "))       ; //3
         Statement = Trim(Mid(Statement, sp)) ; "Abc(a,b,c)" or "Abc (a,b,c)" or "Abc  (a,b,c)"
      EndIf
         
      se=FindString(Statement,("(")) ; 4 or 5 or 6
      
      Statement = Left(Statement,se-1) ; "Abc" or "Abc " or "Abc  "
      ProcedureReturn Trim(Statement)  ; "Abc"  
   EndIf
   
EndProcedure

Procedure.s GetFirstString(Statement.s)
   Protected sp.l, ep.l
   
   Statement = Trim(Statement)
   
   sp=FindString(Statement, Chr(34)) ; a "                  like Include "This.pbi"
   ep=FindString(Statement, Chr(34), sp+1) ; next "              1       9        18
   
   Statement = Mid(Statement,sp+1,(ep-sp-1))
   ProcedureReturn Trim(Statement)   
   
EndProcedure

Procedure.s WithoutComments(Statement.s)
   
   Protected Pos.l
   
   Pos = FindString(Statement, Chr(59)) 
   
   If Pos > 0
      ProcedureReturn Trim(Left(Statement, Pos - 1))
   Else
      ProcedureReturn Trim(Statement)
   EndIf   
   
EndProcedure

Procedure.b FoundInSourceFiles(File$)
   
   FirstElement(SourceFiles())
   ForEach SourceFiles()
      
      If SourceFiles() = File$
         ProcedureReturn #True
      EndIf
      
   Next SourceFiles()
   
   ProcedureReturn #False
   
EndProcedure


Procedure AllStatements2List(File$)
   
   Protected l.i, a$, LineNr.l, NrFile.i, File2$, bNoDebugger.b, bData.b
   
   Debug "Phase  1. AllStatements2List " + File$
   
   NrFile = ReadFile(#PB_Any, File$)
   
   If IsFile(NrFile)
      LineNr = 0
      
      AddElement(SourceFiles())
      SourceFiles() = File$
      
      While Not Eof(NrFile)
         a$=ReadString(NrFile,ReadStringFormat(NrFile))
         a$=Trim(WithoutComments(a$))
         LineNr+1
         
         If a$ = "DisableDebugger"
            bNoDebugger = #True
         ElseIf a$ = "EnableDebugger"
            bNoDebugger = #False
         EndIf
         
         If a$ = "DataSection"
            bData = #True
         ElseIf a$ = "EndDataSection"
            bData = #False
         EndIf
         
         If a$>"" And Left(a$,1)<>Chr(59) And Not bNoDebugger And Not bData And Left(a$, 7) <> "Declare"
            AddElement(StatementsInFiles())
            
            StatementsInFiles()\StatementsFile$ = File$
            StatementsInFiles()\StatementsInProc$ = a$
            StatementsInFiles()\StatementsLines = LineNr
            
            If Left(a$,12)  = "XIncludeFile" Or Left(a$,11)  = "IncludeFile"
               
               File2$ = GetFirstString(a$) ; the include file
               ; Scan SourceFiles() if not already done               
               If Not FoundInSourceFiles(File2$)
                  
                  Debug "Waiting for your answer, should I add " + File2$ + "? (move debug window if necassary)"
                  If MessageRequester("Include file to analyze?", "Add " + File2$ + "?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
                     ;@@2DO: IncludePath support               
                     AllStatements2List(File2$) ; nested call to add includefile(s)
                  EndIf
                  
               EndIf
               
            EndIf
            
         EndIf
      Wend
      CloseFile(NrFile)  
   EndIf
   
;    Debug "" + ListSize(StatementsInFiles()) + " StatementsInFiles."
   
EndProcedure

Procedure.s TypeRoutine(Statement$)
   
   If Left(Statement$,10)="Procedure " Or
      Left(Statement$,10)="Procedure." Or 
      Left(StatementsInFiles()\StatementsInProc$,6)="Macro "
      ProcedureReturn Left(Statement$,1)
   Else
      ProcedureReturn "F" ; filelevel OR next statement within a procedure or Macro...
   EndIf
   
EndProcedure

Procedure.b FoundProcElement(ProcFile$, ProcName$, ProcType$)
   
   FirstElement(Proc())
   ForEach Proc()
      
      If Proc()\ProcFile$ = ProcFile$ And Proc()\ProcName$ = ProcName$ And Proc()\ProcType$ = ProcType$
         ProcedureReturn #True
      EndIf
      
   Next Proc()
   
   ProcedureReturn #False
   
EndProcedure

Procedure CreateOrFindProcElement(ProcFile$, ProcName$, ProcType$)
   
   If ProcType$ = "F" And FoundProcElement(ProcFile$, "***File***", ProcType$)
   ElseIf FoundProcElement(ProcFile$, ProcName$, ProcType$)
      
   Else
         
      AddElement(proc())
      
      Proc()\ProcFile$ = ProcFile$
      Proc()\ProcName$=ProcName$
      If ProcType$ = "F"
         Proc()\ProcName$ = "***File***"
      EndIf                  
      Proc()\ProcType$ = ProcType$
      ;Debug "TESTING Proc added: " + Proc()\ProcName$
   EndIf
   
EndProcedure   

Procedure StatemensInFiles2Procedures()
   
   Protected Type$, OldType$ ; for Procedures, Macros or Filelevel code
   Protected EndType$, Statement$, bInProcedure.b
   
   Debug "Phase  2. StatementsInFiles2Procedures()"
   
   With Proc()      
      If ListSize(StatementsInFiles())>2

         ForEach StatementsInFiles()
            
            Statement$ = Trim(StatementsInFiles()\StatementsInProc$)
                        
            ; not empty, no comment or constant assigns
            If Statement$ > "" And Left(Statement$,1) <> Chr(59) And Left(Statement$,1) <> Chr(35)
               
               OldType$ = Type$
               Type$ = TypeRoutine(Statement$)
               
               If Type$ = "P"
                  EndType$ = "EndProcedure"
                  bInProcedure = #True
               ElseIf Type$ = "M"
                  EndType$ = "EndMacro"
                  bInProcedure = #True
               ElseIf Type$ = "F" And Not bInProcedure
                  EndType$ = "<EOF>"
               Else
                  ; within a procedure                  
                  Type$ = OldType$
                  If Statement$ = EndType$ ; EXIT procedure or macro
                     bInProcedure = #False
                  EndIf
               EndIf
               
               If OldType$ <> Type$
                  CreateOrFindProcElement(StatementsInFiles()\StatementsFile$, Statement$, Type$)
               EndIf
               AddElement(\ProcStatements())                        
               \ProcStatements()\StatementsInProc$ =Statement$
               \ProcStatements()\StatementsLines = StatementsInFiles()\StatementsLines
               
               If Not bInProcedure ; Two Procedure or macros behind each other need a reset of Type$
                  Type$ = ""
               EndIf
                              
            EndIf
         Next StatementsInFiles()
      EndIf ; listsize
      
   EndWith
   
;    Debug "TESTING Proc-listing ===================================================="
;    ForEach Proc()
;       Debug " ProcFile$, ProcName$, ProcType$ " + Proc()\ProcFile$ +"\"+ Proc()\ProcName$ +"\"+ Proc()\ProcType$
;       ForEach Proc()\ProcStatements()
;          Debug Proc()\ProcStatements()\StatementsFile$ + "\\line:\\" +
;                Proc()\ProcStatements()\StatementsLines + "\\stat:\\" + 
;                Proc()\ProcStatements()\StatementsInProc$
;       Next Proc()\ProcStatements()      
;    Next Proc()
;    End ; enditall
;    Debug "END TESTING Proc-listing ===================================================="

EndProcedure

Procedure.s NoStrings(Statement$)
   
   Protected i.l, L$, Return$, InString.b
   
   For i = 1 To Len(Statement$)      
      L$ = Mid(Statement$, i,1)
      If L$ = Chr(34) ; dwz "
         
         If InString
            InString = #False
         Else
            InString = #True
         EndIf
         
      EndIf
      
      If Not InString And L$ <> Chr(34) 
         Return$ + L$
      EndIf
      
   Next i
   
   ProcedureReturn Return$
   
EndProcedure

Procedure Statement2RealWords(Statement$, List Words.s()) ; not words within strings ; statement already trimmed.
   
   Protected Word$, i.l, L$
   
   ; A Statement like 'Debug("MyProcedureABC " + MyProcedureABC("ABC DEF") + Shit())' should give the 3 Words
   ; Debug
   ; MyProcedureABC (2e)
   ; Shit

;    Debug "Statement was " + Statement$
   Statement$ = NoStrings(Statement$) ; ==> Debug+MyProcedureABC+Shit
;    Debug "Statement became " + Statement$
   
   For i = 1 To Len(Statement$)
      L$ = Mid(Statement$, i, 1)
      If L$ = " " Or L$ = "+" Or L$ = "-" Or L$ = "*" Or L$ = "/" Or L$ = "(" Or L$ = ","; Word seprators
         Word$ = Trim(Word$)
         If Len(Word$) > 0
            AddElement(Words())
            Words() = Word$
            Word$ = ""
         EndIf
         
      ElseIf L$ <> ")"
         Word$ + L$
      EndIf
      
   Next i
   

EndProcedure

Procedure.b CreateOrFindDependancyListElement(ProcName$, Make.b = #True, Rank = 0)
   
   FirstElement(Dependancy())
    
   ForEach Dependancy()
      If UCase(Dependancy()\ProcName$) = UCase(ProcName$) Or (Rank > 0 And Dependancy()\Rank = Rank)
         ProcedureReturn #False
      EndIf      
   Next Dependancy()
   
   If Make
      AddElement(Dependancy())
      ProcedureReturn #True ; Created
   Else
      Debug "*****************************************CreateOrFindDependancyListElement did not find " + ProcName$
   EndIf
   
EndProcedure

Procedure Dependencies()
   
   Protected NrLine.l, CallingProc$, CalledByPos.l, CallingPos.l
   Protected T1$, T2$ ; debug
   
   Debug "Phase  3. Dependencies for " + ListSize(Proc()) + " procedures/macros."
   
   NewList ProcCopy.ProcList()
   CopyList(Proc(), ProcCopy())
   
   FirstElement(ProcCopy())
   
   ForEach ProcCopy()
      
      CallingProc$ = GetProcName(ProcCopy()\ProcName$, ProcCopy()\ProcType$)
      If Trim(CallingProc$) = "" Or FindString(CallingProc$, ",") > 0
         Debug "1. WRONG! " + CallingProc$
      EndIf
      If CreateOrFindDependancyListElement(CallingProc$)
         Dependancy()\ProcName$ = CallingProc$
         ;Debug "I. TESTING Dependancy #" + ListSize(Dependancy()) + " created With name " + Dependancy()\ProcName$ + " from " + ProcCopy()\ProcFile$
      EndIf
      Dependancy()\ProcFile$ = ProcCopy()\ProcFile$
      Dependancy()\Proctype$ = ProcCopy()\Proctype$      
      CalledByPos = ListIndex(Dependancy())
      
      FirstElement(Proc())            
      ForEach Proc()    ; loop through the code lines in the procs to search for calls
         
         NrLine = 0

         ForEach Proc()\ProcStatements()
            NrLine+1
            If NrLine = 1
               CallingProc$ = GetProcName(Proc()\ProcStatements()\StatementsInProc$, Proc()\ProcType$)
               If Trim(CallingProc$) = "" Or FindString(CallingProc$, ",") > 0
                  Debug "2. WRONG! " + CallingProc$
               EndIf
               
               If CreateOrFindDependancyListElement(CallingProc$)
                  Dependancy()\ProcName$ = CallingProc$
                  ;Debug "II. TESTING Dependancy #" + ListSize(Dependancy()) + " created With name " + Dependancy()\ProcName$ + " from " + Proc()\ProcFile$
               EndIf
               CallingPos = ListIndex(Dependancy())
            EndIf
            
            SelectElement(Dependancy(), CalledByPos)
            T1$ = Proc()\ProcStatements()\StatementsInProc$
            T2$ = Dependancy()\ProcName$
            
            If NrLine > 1 And FindString(UCase(T1$), UCase(T2$))  ; not its own but nested calls will be added
               ; Whole word has to be exact the same and not within strings
               ; like A_MyRoutine = 1 should not be added as a dependancy of MyRoutine
               ; like MyRoutineABC = 1 should not be added as a dependancy of MyRoutine
               ; like Debug( "MyRoutine") should not be added as a dependancy of MyRoutine
               NewList Words.s() ; reset
               
               Statement2RealWords(Proc()\ProcStatements()\StatementsInProc$, Words()) ; not words within strings etc
               
               ForEach Words()
                  If UCase(Words()) = UCase(Dependancy()\ProcName$) Or UCase(Words()) = "@" + UCase(Dependancy()\ProcName$)
                     
                     AddElement(Dependancy()\CalledBys())                     
                     
                     Dependancy()\CalledBys()\CalledByFile$ = Proc()\ProcFile$
                     Dependancy()\CalledBys()\CalledByProcNameFull$ = Proc()\ProcName$
                     Dependancy()\CalledBys()\CalledByProcNameShort$ = GetProcName(Proc()\ProcName$, Proc()\ProcType$)
                     Dependancy()\CalledBys()\CalledByType$ = Proc()\Proctype$
                     
                     Dependancy()\CalledBys()\CalledByLine = Proc()\ProcStatements()\StatementsLines
                     Dependancy()\CalledBys()\CalledByStatement$ = Proc()\ProcStatements()\StatementsInProc$
                     
                     SelectElement(Dependancy(), CallingPos)
                     AddElement(Dependancy()\Calls())                     
                     
                     Dependancy()\Calls()\CalledByFile$ = ProcCopy()\ProcFile$
                     Dependancy()\Calls()\CalledByProcNameFull$ = ProcCopy()\ProcName$
                     Dependancy()\Calls()\CalledByProcNameShort$ = GetProcName(ProcCopy()\ProcName$, ProcCopy()\ProcType$)
                     Dependancy()\Calls()\CalledByType$ = ProcCopy()\ProcType$
                     
                     ;Proc() and not ProcCopy()!
                     Dependancy()\Calls()\CalledByLine = Proc()\ProcStatements()\StatementsLines
                     Dependancy()\Calls()\CalledByStatement$ = Proc()\ProcStatements()\StatementsInProc$
                     
                  EndIf                  
               Next Words()
            
            EndIf
         Next Proc()\ProcStatements()
      Next Proc()
      
;       If Mod(ListIndex(ProcCopy()), 10) = 0
;          Debug "Progress in Dependancies " + ListIndex(ProcCopy())+ "/" +ListSize(ProcCopy())
;       EndIf
      
   Next ProcCopy()
   
EndProcedure

Procedure UpdateDependancyCalls2Rank(ProcName$, Rank.l)
;   Only Non existing CalledByProcNameShort$ With Rank+1. 
;   Update existing CalledByProcNameShort$ With lower Rank
   
   Protected bFound.b
   
   FirstElement(DependancyCalls2Rank())
   ForEach DependancyCalls2Rank()
      If DependancyCalls2Rank()\ProcName$ = ProcName$ ; already existing item
         If bFound                                    ; already earlier found
            DeleteElement(DependancyCalls2Rank())
            Debug DependancyCalls2Rank()\ProcName$ + " Redundant Item of DependancyCalls2Rank Deleted."
         Else            
            bFound = #True
            DependancyCalls2Rank()\Rank = Maxl(DependancyCalls2Rank()\Rank, Rank)
;             Debug DependancyCalls2Rank()\ProcName$ + " Item of DependancyCalls2Rank Updated to Rank " + 
;                   DependancyCalls2Rank()\Rank
         EndIf
         
      EndIf      
   Next DependancyCalls2Rank()
   
   If Not bFound
      AddElement(DependancyCalls2Rank())
      DependancyCalls2Rank()\ProcName$ = ProcName$
      DependancyCalls2Rank()\Rank = Rank
;       Debug "2. DependancyCalls2Rank added " + DependancyCalls2Rank()\ProcName$ + " with Rank " + DependancyCalls2Rank()\Rank
   EndIf
   
EndProcedure        

Procedure.l RankFirstCalledDependancys(Rank.l)
   
   Protected Number.l, Position1.l, Position2.l
   
   Position1 = ListIndex(Dependancy())
   
   ForEach Dependancy()\Calls()
      Position2 = ListIndex(Dependancy()\Calls())
      
      CreateOrFindDependancyListElement(Dependancy()\Calls()\CalledByProcNameShort$, #False) ; only find
      Dependancy()\Rank = Maxl(Rank, Dependancy()\Rank)
      Debug Dependancy()\ProcName$ + " Dependancy()\Rank := " + Dependancy()\Rank
      Number+1
      
      If Dependancy()\NumberCalls > 0
         FirstElement(Dependancy()\Calls())
         ForEach Dependancy()\Calls()
            UpdateDependancyCalls2Rank(Dependancy()\Calls()\CalledByProcNameShort$, Rank+1)            
;             Debug "1. DependancyCalls2Rank added " + DependancyCalls2Rank()\ProcName$ + " with Rank " + DependancyCalls2Rank()\Rank
         Next Dependancy()\Calls()   
      EndIf
      
      SelectElement(Dependancy(), Position1)
      SelectElement(Dependancy()\Calls(), Position2) ; to be sure
      
   Next Dependancy()\Calls()
   
   ProcedureReturn Number
            
EndProcedure

Procedure RankProcedures()
   
   Protected Rank.l, Position.l
   
   Debug "Phase  4. RankProcedures"
   
   FirstElement(Dependancy())
   ForEach Dependancy()
      Dependancy()\NumberCalledBy = ListSize(Dependancy()\CalledBys())      
      Dependancy()\NumberCalls = ListSize(Dependancy()\Calls())
   Next Dependancy()
   
   FirstElement(Dependancy())
   ForEach Dependancy()
      If Dependancy()\Proctype$ = "F" And Dependancy()\NumberCalls > 0
         Rank = 0
         Dependancy()\Rank = Rank                
         RankFirstCalledDependancys(Rank+1)         
      EndIf         
   Next Dependancy()
   
   While ListSize(DependancyCalls2Rank()) > 0
      
      Debug "Ranking start loop with elements: " + ListSize(DependancyCalls2Rank())
      
      FirstElement(DependancyCalls2Rank())
      ForEach DependancyCalls2Rank()
               
         Rank = DependancyCalls2Rank()\Rank
   ;       Debug DependancyCalls2Rank()\ProcName$ + " Rank " + Rank + " DependancyCalls2Rank size" + 
   ;             ListSize(DependancyCalls2Rank()) + " Index: " + ListIndex(DependancyCalls2Rank())
         
         CreateOrFindDependancyListElement(DependancyCalls2Rank()\ProcName$, #False)
         Dependancy()\Rank = Maxl(Rank, Dependancy()\Rank)
;          Debug Dependancy()\ProcName$ + " Dependancy()\Rank ==> " + Dependancy()\Rank
         
         FirstElement(Dependancy()\Calls())
         ForEach Dependancy()\Calls()    
            
            If UCase(Dependancy()\Calls()\CalledByProcNameShort$) = UCase(DependancyCalls2Rank()\ProcName$)
               ; Nested Call no ranking (eternal loop)
               Debug "No Ranking for Nested Call in " + DependancyCalls2Rank()\ProcName$
            Else
               Position = ListIndex(DependancyCalls2Rank())
               UpdateDependancyCalls2Rank(Dependancy()\Calls()\CalledByProcNameShort$, Rank+1)
               SelectElement(DependancyCalls2Rank(), Position)
            EndIf
            
         Next Dependancy()\Calls()
         
         DeleteElement(DependancyCalls2Rank())
         
      Next DependancyCalls2Rank()
   Wend
   
EndProcedure

Procedure FillDependenciesChainFromTop(Chain$) ; This proces is dependant of the right ranking (Dependancy is sorted on rank)
   
   Static RankDebug.l   
   Protected Pos1.l, Pos2.l, Pos3.l
   
   NewList CallChain$() ; of only 1 dependancy(), the last element Chain$, in 1e call the main procedure   
   Static NewList CallChainNextLevel.SaveList() ; of only 1 dependancy() further in the chain
   
   If Dependancy()\NumberCalls > 0
      Pos1 = ListIndex(Dependancy())
      ForEach Dependancy()\Calls() ; of Chain$ 
         Pos2 = ListIndex(Dependancy()\Calls())
         If Dependancy()\Calls()\CalledByProcNameShort$ <> Dependancy()\ProcName$ ; non nested calls           
            
            AddElement(CallChain$())
            CallChain$() = Chain$ + ";" + Dependancy()\Calls()\CalledByProcNameShort$
            CreateOrFindDependancyListElement(Dependancy()\Calls()\CalledByProcNameShort$)
            If Dependancy()\NumberCalls - Dependancy()\NumberNestedCalls = 0 And Right(CallChain$(),1) <> "."
               CallChain$() + "."               
            EndIf
            
            AddElement(CallChainNextLevel())
            Pos3 = ListIndex(CallChainNextLevel())
            
;             Debug "FillDependenciesChainFromTop before nested call with # " + ListSize(CallChain$()) + " CallChain: " +
;                    CallChain$() + " Position "+ Pos3
            
            CopyList(CallChain$(), CallChainNextLevel()\ChainFromTop$()) ; CallChainNextLevel$ is static so can be given to the nested call
            FillDependenciesChainFromTop(CallChain$()) ; Nested call!!
            SelectElement(CallChainNextLevel(), Pos3)
            CopyList(CallChainNextLevel()\ChainFromTop$(), CallChain$()) ; Take it over
;             DeleteElement(CallChainNextLevel())
;             Debug "FillDependenciesChainFromTop after nested call with # " + ListSize(CallChain$()) + " CallChain: " +
;                    CallChain$() + " Position "+ Pos3
            
         Else
            Debug "Nested call not in (endless) chain: " + Dependancy()\ProcName$
            Dependancy()\NumberNestedCalls+1
            If Dependancy()\NumberCalls - Dependancy()\NumberNestedCalls = 0 
               If ListSize(CallChain$()) > 0
                  If Right(CallChain$(),1) <> "."
                     CallChain$() + "."
                  EndIf
               Else
                   If Right(Chain$,1) <> "."
                      Chain$ + "."
                   EndIf
               EndIf              
            EndIf ; Last element in chain?        
         EndIf ; nested call?    
         SelectElement(Dependancy(), Pos1)
         SelectElement(Dependancy()\Calls(), Pos2)
      Next Dependancy()\Calls()
   EndIf
   
   If ListSize(CallChain$()) > 0 ; Ready; save list to our Dependancy
      ;Testing
;       Debug Dependancy()\ProcName$ + "'s ChainFromTop$ is being filled with"
;       ForEach CallChain$()
;          Debug "- " + CallChain$()   
;       Next CallChain$()
               
      CopyList(CallChain$(), Dependancy()\ChainFromTop$())
      ClearList(CallChain$())
   Else
      ;Testing
;       Debug Dependancy()\ProcName$ + "'s ChainFromTop$ is being filled with "
      If Right(Chain$,1) <> "."
         Chain$ + "."
      EndIf
      
;       Debug "* " + Chain$
      AddElement(Dependancy()\ChainFromTop$())
      Dependancy()\ChainFromTop$() = Chain$
   EndIf
   
;    If Dependancy()\Rank > RankDebug
;       Debug "Progress in FillDependenciesChainFromTop  " + Dependancy()\Rank
;       RankDebug = Dependancy()\Rank
;    EndIf
   
EndProcedure

; Procedure.s SeekCompleteChain()
;    ; Dependancy()\ChainFromTop$() is not complete; with multiple calls and different chains this might be buggy
;    
;    Protected LastProc$, Chain$, ChainOld$
;    
;    Chain$ = Dependancy()\ChainFromTop$()
;    Debug "SeekCompleteChain$ for " + Chain$
;    
;    While Right(Chain$,1) <> "." And ChainOld$ <> Chain$; Found none
;       ChainOld$ = Chain$
;       LastProc$ = ReverseString(StringField(ReverseString(Chain$), 1, ";"))
;       CreateOrFindDependancyListElement(LastProc$, #False)
;       
;       ForEach Dependancy()\ChainFromTop$()
;          If Left(Dependancy()\ChainFromTop$(), Len(Chain$)) = Chain$
;             Chain$ = Dependancy()\ChainFromTop$()            
;             If Right(Chain$,1) = "." ; Found one
;                Debug "SeekCompleteChain$ found " + Chain$
;                Break 2
;             Else
;                Debug "SeekCompleteChain$ found non complete " + Chain$
;             EndIf         
;          EndIf
;       Next Dependancy()\ChainFromTop$()   
;    Wend
;    
;    If Right(Chain$,1) <> "."
;       Chain$ + "@"
;    EndIf
;    
;  
;    ProcedureReturn Chain$ 
;    
; EndProcedure

; Procedure AddChainInfo()
;    
;    ; The chains that do not end with a "." are not complete. This procedure adds the rest of the chains
;    
;    Protected Pos1.l, Pos2.l, Chain$
;    
;    ForEach Dependancy()
;       Pos1 = ListIndex(Dependancy())
;       ForEach Dependancy()\ChainFromTop$()
;          If Right(Dependancy()\ChainFromTop$(),1) <> "."            
;             Pos2 = ListIndex(Dependancy()\ChainFromTop$())
;             ;Seek remaining info
;             Chain$ = SeekCompleteChain()
;             ;return to original position
;             SelectElement(Dependancy(), Pos1)
;             SelectElement(Dependancy()\ChainFromTop$(), Pos2)
;             Dependancy()\ChainFromTop$() = Chain$
;          EndIf         
;       Next Dependancy()\ChainFromTop$()
;    Next Dependancy()
;       
;    
; EndProcedure

Procedure BuildUniqueCallChains()
   
   Protected Chain$
   
   ForEach Dependancy()
      ForEach Dependancy()\ChainFromTop$()
         If Right(Dependancy()\ChainFromTop$(),1) = "."
            AddElement(UniqueCallChains$())
            UniqueCallChains$() = Dependancy()\ChainFromTop$()
         EndIf         
      Next Dependancy()\ChainFromTop$() 
   Next Dependancy()
   
   SortList(UniqueCallChains$(), #PB_Sort_Ascending)
   
   ForEach UniqueCallChains$()
      If UniqueCallChains$() = Chain$
         DeleteElement(UniqueCallChains$())
      EndIf
      Chain$ = UniqueCallChains$()
   Next UniqueCallChains$()
   
EndProcedure

Procedure ShowInfo(NrDep.l)
   
   SelectElement(Dependancy(), NrDep-1)
   
   If OpenWindow(1, 0, 0, 800, 400, "Info of procedure " + 
          Dependancy()\ProcName$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
      ListViewGadget(0, 10, 10, 780, 380)
      
      AddGadgetItem (0, -1, "Called By " + Dependancy()\NumberCalledBy)
      NrDep = 0
      ForEach Dependancy()\CalledBys()
         NrDep+1
         AddGadgetItem (0, -1, "CB-" + Str(NrDep) + Dependancy()\CalledBys()\CalledByFile$ + ":" +
                        Dependancy()\CalledBys()\CalledByProcNameShort$) ; define listview content
      Next Dependancy()\CalledBys()
      
      AddGadgetItem (0, -1, "Calling " + Dependancy()\NumberCalls)
      NrDep = 0
      ForEach Dependancy()\Calls()
         NrDep+1
         AddGadgetItem (0, -1, "CA-" + Str(NrDep) + Dependancy()\Calls()\CalledByFile$ + ":" +
                        Dependancy()\Calls()\CalledByProcNameShort$) ; define listview content
      Next Dependancy()\Calls()
      
      AddGadgetItem (0, -1, "Calling trees")
      ForEach UniqueCallChains$()
         If FindString(UniqueCallChains$(),  ";"+ Dependancy()\ProcName$ + ";") > 0 Or
            FindString(UniqueCallChains$(),  ";"+ Dependancy()\ProcName$ + ".") > 0 Or
            FindString(UniqueCallChains$(),  Dependancy()\ProcName$ + ";") = 1
            AddGadgetItem (0, -1, UniqueCallChains$())  
         EndIf   
      Next UniqueCallChains$()      
      
      Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
      
      CloseWindow(1)
   EndIf
      
EndProcedure

Procedure Main()
   
   Protected f$, NrDep.l, Text$, MainProc$
   Protected Event.l, Quit.l

   f$=OpenFileRequester("Select A .pb Source File","*.pb","PureBasic (*.pb)|*.pb",1)
   If f$

      AllStatements2List(f$)        ; process file(s) to a linked list with only code lines
      StatemensInFiles2Procedures()  ; process lines into procedures
      Dependencies()                ; examine dependencies (called by)
      RankProcedures()              ; rank the procedures
      
      ;SortStructuredList(Proc(), #PB_Sort_Ascending, OffsetOf(ProcList\ProcName$) ,TypeOf(ProcList\ProcName$))
      SortStructuredList(Dependancy(), #PB_Sort_Ascending, OffsetOf(Dependancy\Rank) ,TypeOf(Dependancy\Rank))
      
      CreateOrFindDependancyListElement("", #False, 1)
      If Dependancy()\Rank = 1
         MainProc$ = Dependancy()\ProcName$
         Debug "Main procedure found: " + MainProc$
         Debug "Phase 5: FillDependenciesChainFromTop called with " + MainProc$
         FillDependenciesChainFromTop(MainProc$)
         ; buggy, lets make FillDependenciesChainFromTop better so we don't need AddChainInfo()
         Debug "Phase 6: Building unique call-chains."
         BuildUniqueCallChains()
      Else
         Debug "No main procedure (rank 1) was found so no chain-building"
      EndIf
      
      Debug "========================================================================="
      Debug "TESTING List of called Dependencies with full info ======================"
      Debug "========================================================================="
      ForEach Dependancy()
         If Dependancy()\NumberCalledBy > 0 Or Dependancy()\Proctype$ = "F" Or Dependancy()\Rank = 0 
            If Dependancy()\Rank = 0 And Dependancy()\Proctype$ <> "F"
               Debug Dependancy()\ProcName$ + " : does not seem to be used"
            Else
               
               Text$ = Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " is called by " +
                       Dependancy()\NumberCalledBy + " and is calling " + Dependancy()\NumberCalls + 
                       "-" + Dependancy()\NumberNestedCalls +" :"
               Debug RSet("", Len(Text$), "=")
               Debug Text$
               Debug RSet("", Len(Text$), "=")
               
               NrDep = 0
               ForEach Dependancy()\CalledBys()
                  NrDep+1
                  Debug "CB- " + NrDep + ". " + Dependancy()\CalledBys()\CalledByFile$ +" //type:// "+
                                    Dependancy()\CalledBys()\CalledByType$ + " //proc:// "+
                                    Dependancy()\CalledBys()\CalledByProcNameShort$ + " //full:// "+ 
                                    Dependancy()\CalledBys()\CalledByProcNameFull$ + " //line:// "+ 
                                    Dependancy()\CalledBys()\CalledByLine + " //stat:// "+ 
                                    Dependancy()\CalledBys()\CalledByStatement$
               Next Dependancy()\CalledBys()
               
               NrDep = 0
               ForEach Dependancy()\Calls()
                  NrDep+1
                  Debug "CA- " + NrDep + ". " + Dependancy()\Calls()\CalledByFile$ +" //type:// "+
                                    Dependancy()\Calls()\CalledByType$ + " //proc:// "+ 
                                    Dependancy()\Calls()\CalledByProcNameShort$ + " //full:// "+ 
                                    Dependancy()\Calls()\CalledByProcNameFull$ + " //line:// "+ 
                                    Dependancy()\Calls()\CalledByLine + " //stat:// "+ 
                                    Dependancy()\Calls()\CalledByStatement$
               
               Next Dependancy()\Calls()  
            EndIf    
         EndIf
      Next Dependancy()
      Debug "==========================================================================="
;       Debug "TESTING List of Dependencies with basic calling info ======================"
;       Debug "==========================================================================="
;       
;       ForEach Dependancy() 
;          If Dependancy()\NumberCalls > 0
;             Debug Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " is calling"
;          Else
;             Debug Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " is the end of a line"
;          EndIf
;          
;          NrDep = 0
;          ForEach Dependancy()\Calls()
;             NrDep+1
;             Debug "- " + NrDep + ". " + Dependancy()\Calls()\CalledByFile$ +" //type:// "+
;                               Dependancy()\Calls()\CalledByType$ + " //proc:// "+ 
;                               Dependancy()\Calls()\CalledByProcNameShort$ + " //stat:// "+ 
;                               Dependancy()\Calls()\CalledByStatement$
;          Next Dependancy()\Calls()  
;       Next Dependancy()
      
      Debug "==========================================================================="
      Debug "TESTING List of called Dependencies without calling info =================="
      Debug "==========================================================================="
      
      ForEach Dependancy() 
         If Dependancy()\NumberCalledBy > 0 Or Dependancy()\Proctype$ = "F"  Or Dependancy()\Rank > 0
            If Dependancy()\Rank = 0 And Dependancy()\Proctype$ <> "F"
               Debug Dependancy()\ProcName$ + " : does not seem to be used"
            Else
               Debug Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " is calling " + Dependancy()\NumberCalls +
                     " - " + Dependancy()\NumberNestedCalls +" :"
            EndIf
            
         EndIf         
      Next Dependancy()
      
;       Debug "==========================================================================="
;       Debug "TESTING List of calling chaines"
;       Debug "==========================================================================="
;       
;       ForEach Dependancy()
;          If Dependancy()\Rank > 0
;             Text$ = Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " has calling chains: "
;             Debug RSet("", Len(Text$), "=")
;             Debug Text$
;             Debug RSet("", Len(Text$), "=")
;                
;             ForEach Dependancy()\ChainFromTop$()
;                Debug Dependancy()\ChainFromTop$()
;             Next Dependancy()\ChainFromTop$()
;          EndIf         
;       Next Dependancy()
      
      Debug "==========================================================================="
      Debug "TESTING List of unique calling chaines (" + ListSize(UniqueCallChains$()) + ")"
      Debug "==========================================================================="
      
      NrDep = 0
      ForEach UniqueCallChains$()
         NrDep+1
         Debug Str(NrDep) + ". " + UniqueCallChains$()
      Next UniqueCallChains$()
      
      Debug ""
      Debug "Finished."
      Debug "Saving debug info to " + f$ + "-CR.txt"
      
      SaveDebugOutput(f$ + "-CR.txt")
      
      SortStructuredList(Dependancy(), #PB_Sort_Ascending, OffsetOf(Dependancy\ProcName$) ,TypeOf(Dependancy\ProcName$))
      
      If OpenWindow(0, 200, 200, 400, 400, "Select procedure")
         If CreateMenu(0, WindowID(0))    ; menu creation starts....
            MenuTitle("Procedure")
            NrDep = 0
            ForEach Dependancy()
               NrDep+1
               MenuItem(NrDep, Dependancy()\ProcName$)
            Next Dependancy()            
            
            Repeat 
;                Event = WindowEvent()
;                Delay(5) ; for other programs, don't eat all CPU time
               Event = WaitWindowEvent()
               Select Event
         
                   Case #PB_Event_Menu
             
                      NrDep = EventMenu()
                      ShowInfo(NrDep)
                      
                   Case #PB_Event_CloseWindow
                      Quit = 1            
                EndSelect ; Event

             Until Quit > 0
     
          EndIf ; CreateMenu
      EndIf ; OpenWindow

   EndIf ;File
   
EndProcedure

Main()

End
Al_the_dutch
User
User
Posts: 70
Joined: Mon Nov 11, 2013 11:07 am
Location: Portugal

Re: Proc Cross reference / dependancy examiner / Code Analys

Post by Al_the_dutch »

Here is an example of a pb file and the output.

Code: Select all

; Test_ProcCrosReference.pb

EnableExplicit

Declare.b HeIsNotHere()

Procedure.s BCalledByAandMain(a.i)   
   Debug "BCalledByAandMain"   
   ProcedureReturn "ACallingB called BCalledByAandMain(" + a + ")"      
EndProcedure
Procedure ACallingB()
   
   Debug "ACallingB"
   ; comment
   Debug BCalledByAandMain(1)
   Debug BCalledByAandMain(2)   
EndProcedure
Macro _ACallingB()
   ACallingB()
EndMacro
Procedure NothingWillBeDone(bActie.b)
   If bActie
      NothingWillBeDone(#False) ; Nested Call test
   EndIf   
EndProcedure

Procedure Main()   
   Debug "Main"   
   _ACallingB()
   BCalledByAandMain(3)
   NothingWillBeDone(#True)
EndProcedure

Main()

DataSection
MyClass:
    Data.i @Main()   
EndDataSection

End
  • =========================================================================
    TESTING List of called Dependencies with full info ======================
    =========================================================================
    ================================================
    0.***File*** is called by 0 and is calling 1-0 :
    ================================================
    CA- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// Main //full:// Procedure Main() //line:// 34 //stat:// Main()
    ==========================================
    1.Main is called by 1 and is calling 3-0 :
    ==========================================
    CB- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// F //proc:// ***File*** //full:// ***File*** //line:// 34 //stat:// Main()
    CA- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// BCalledByAandMain //full:// Procedure.s BCalledByAandMain(a.i) //line:// 30 //stat:// BCalledByAandMain(3)
    CA- 2. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// M //proc:// _ACallingB //full:// Macro _ACallingB() //line:// 29 //stat:// _ACallingB()
    CA- 3. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// NothingWillBeDone //full:// Procedure NothingWillBeDone(bActie.b) //line:// 31 //stat:// NothingWillBeDone(#True)
    ================================================
    2._ACallingB is called by 1 and is calling 1-0 :
    ================================================
    CB- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// Main //full:// Procedure Main() //line:// 29 //stat:// _ACallingB()
    CA- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// ACallingB //full:// Procedure ACallingB() //line:// 19 //stat:// ACallingB()
    =======================================================
    2.NothingWillBeDone is called by 2 and is calling 1-1 :
    =======================================================
    CB- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// NothingWillBeDone //full:// Procedure NothingWillBeDone(bActie.b) //line:// 23 //stat:// NothingWillBeDone(#False)
    CB- 2. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// Main //full:// Procedure Main() //line:// 31 //stat:// NothingWillBeDone(#True)
    CA- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// NothingWillBeDone //full:// Procedure NothingWillBeDone(bActie.b) //line:// 23 //stat:// NothingWillBeDone(#False)
    ===============================================
    3.ACallingB is called by 1 and is calling 2-0 :
    ===============================================
    CB- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// M //proc:// _ACallingB //full:// Macro _ACallingB() //line:// 19 //stat:// ACallingB()
    CA- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// BCalledByAandMain //full:// Procedure.s BCalledByAandMain(a.i) //line:// 15 //stat:// Debug BCalledByAandMain(1)
    CA- 2. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// BCalledByAandMain //full:// Procedure.s BCalledByAandMain(a.i) //line:// 16 //stat:// Debug BCalledByAandMain(2)
    =======================================================
    4.BCalledByAandMain is called by 3 and is calling 0-0 :
    =======================================================
    CB- 1. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// ACallingB //full:// Procedure ACallingB() //line:// 15 //stat:// Debug BCalledByAandMain(1)
    CB- 2. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// ACallingB //full:// Procedure ACallingB() //line:// 16 //stat:// Debug BCalledByAandMain(2)
    CB- 3. D:\PureBasic\CrossRef\Test_ProcCrosReference.pb //type:// P //proc:// Main //full:// Procedure Main() //line:// 30 //stat:// BCalledByAandMain(3)
    ===========================================================================
    ===========================================================================
    TESTING List of called Dependencies without calling info ==================
    ===========================================================================
    0.***File*** is calling 1 - 0 :
    1.Main is calling 3 - 0 :
    2._ACallingB is calling 1 - 0 :
    2.NothingWillBeDone is calling 1 - 1 :
    3.ACallingB is calling 2 - 0 :
    4.BCalledByAandMain is calling 0 - 0 :
    ===========================================================================
    TESTING List of unique calling chaines (3)
    ===========================================================================
    1. Main;BCalledByAandMain.
    2. Main;NothingWillBeDone.
    3. Main;_ACallingB;ACallingB;BCalledByAandMain.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Proc Cross reference / dependancy examiner / Code Analys

Post by Kwai chang caine »

Works great here
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
Al_the_dutch
User
User
Posts: 70
Joined: Mon Nov 11, 2013 11:07 am
Location: Portugal

Re: Proc Cross reference / dependancy examiner / Code Analys

Post by Al_the_dutch »

YW and thanks. BTW, a new version is coming one of these days.
Found some minor bugs as I studied the results of a large project. Added some things too.
Al_the_dutch
User
User
Posts: 70
Joined: Mon Nov 11, 2013 11:07 am
Location: Portugal

Re: Proc Cross reference / dependancy examiner / Code Analys

Post by Al_the_dutch »

Here is an update. Fixed some bugs (major one with includes) and added support for what I call "Alias name" which I use in conjunction with Threaded vars, a workaround to get semi-local-threaded vars. Let me explain.

Threaded vars are global and in my procedures I don't want to share these vars between them, I want them to be local as much as possible. So I give each procedure an Alias and for threaded vars I use the prefix <Alias>_. The Alias names I use contain a capital and a number with 3 or 4 digits and I put them after a semicolon. F.e.:

Code: Select all

Threaded A46_Number.d
...
Procedure DoIt(Number.d) ;A46 ; DoIt gets Alias A46
   A46_Number = Number
I added a check on the condition that only threaded vars with corresponding Alias prefix are being used within a procedure.

The new code:

Code: Select all

; ProcCrossReference.pb

; === Procedure Cross reference / dependancy examiner / Code Analysis for PureBasic=== by Al_the_dutch 20160602
; === Who makes a flow chart /html extension from this info? 

; With thanx to Zebuddi123 in his "Tool Procedure sorter & dep finder 4 x platform porting incs"
; http://www.purebasic.fr/english/viewtopic.php?f=27&t=60561

EnableExplicit

Structure Statement
   StatementsFile$   ; The source filename
   StatementsInProc$ ; The statements in the procedure without comments
   StatementsLines.l ; The associated linenumbers
   StatementsProcAlias$ ; fe T03
EndStructure

Structure CalledBy
   CalledByStatement$
   CalledByFile$
   CalledByType$
   CalledByProcNameShort$
   CalledByProcNameFull$
   CalledByLine.l   
EndStructure

Structure ProcList
   ProcFile$ ; for handling includes
   ProcName$
   ProcType$ ; Procedure, Macro or FileLevel code
   ProcAlias$ ; fe T03
   List ProcStatements.Statement()
EndStructure

Structure Dependancy
   ProcFile$ ; for handling includes
   ProcName$
   ProcType$ ; Procedure, Macro or FileLevel code
   ProcAlias$ ; fe T03
   Rank.l   ;     Number To sort
   NumberCalls.l
   NumberNestedCalls.l
   NumberCalledBy.l
   List ChainFromTop$()
   List CalledBys.CalledBy()
   List Calls.CalledBy()
EndStructure

Structure Calls2Rank
   ProcName$
   Rank.l
EndStructure

Structure SaveList
   List ChainFromTop$()
EndStructure

Global NewList Proc.ProcList()
Global NewList Dependancy.Dependancy()
Global NewList StatementsInFiles.Statement()
Global NewList SourceFiles.s()
Global NewList DependancyCalls2Rank.Calls2Rank()
Global NewList UniqueCallChains$()

Global gStartms.l

Procedure.l Maxl(a.l,b.l)   
   If a>b 
      ProcedureReturn a
   Else
      ProcedureReturn b
   EndIf
EndProcedure

Procedure.s sElapsedTimeMs()
   ProcedureReturn "Ms: " + Str(ElapsedMilliseconds() - gStartms)
EndProcedure


Procedure.s TypeRoutine(Statement$)
   
   If Left(Statement$,10) = "Procedure " Or
      Left(Statement$,10) = "Procedure." Or 
      Left(Statement$,6) = "Macro "
      ProcedureReturn Left(Statement$,1)
   Else
      ProcedureReturn "F" ; filelevel OR next statement within a procedure or Macro...
   EndIf
   
EndProcedure

Procedure.s GetProcName(Statement.s, Type$) ; Statements like Procedure Abc(a,b,c) or Procedure  Abc (a,b,c) or Procedure.x Abc(a,b,c)
   Protected se.l, sp.l, Key$
   
   If Type$ = "F"
      ProcedureReturn "***File***"
   Else
      If Type$ = "M"
         Key$ = "Macro"
      Else
         Key$ = "Procedure"
      EndIf
      
      Statement = Trim(Statement)
      Statement = Trim(Mid(Statement, Len(Key$) + 1)) ; "Abc(a,b,c)" or "Abc (a,b,c)" or ".x  Abc  (a,b,c)" 
      
      If Left(Statement, 1) = "."      
         sp=FindString(Statement,(" "))       ; //3
         Statement = Trim(Mid(Statement, sp)) ; "Abc(a,b,c)" or "Abc (a,b,c)" or "Abc  (a,b,c)"
      EndIf
         
      se=FindString(Statement,("(")) ; 4 or 5 or 6
      
      Statement = Left(Statement,se-1) ; "Abc" or "Abc " or "Abc  "
      ProcedureReturn Trim(Statement)  ; "Abc"  
   EndIf
   
EndProcedure

Procedure.s GetFirstString(Statement.s)
   Protected sp.l, ep.l
   
   Statement = Trim(Statement)
   
   sp=FindString(Statement, Chr(34)) ; a "                  like Include "This.pbi"
   ep=FindString(Statement, Chr(34), sp+1) ; next "              1       9        18
   
   Statement = Mid(Statement,sp+1,(ep-sp-1))
   ProcedureReturn Trim(Statement)   
   
EndProcedure

Procedure.s WithoutComments(Statement.s)
   
   Protected Pos.l
   
   Pos = FindString(Statement, Chr(59)) 
   
   If Pos > 0
      ProcedureReturn Trim(Left(Statement, Pos - 1))
   Else
      ProcedureReturn Trim(Statement)
   EndIf   
   
EndProcedure

Procedure.b FoundInSourceFiles(File$)
   
   FirstElement(SourceFiles())
   ForEach SourceFiles()
      
      If SourceFiles() = File$
         ProcedureReturn #True
      EndIf
      
   Next SourceFiles()
   
   ProcedureReturn #False
   
EndProcedure

Procedure.s NoStrings(Statement$)
   
   Protected i.l, L$, Return$, InString.b
   
   For i = 1 To Len(Statement$)      
      L$ = Mid(Statement$, i,1)
      If L$ = Chr(34) ; dwz "
         
         If InString
            InString = #False
         Else
            InString = #True
         EndIf
         
      EndIf
      
      If Not InString And L$ <> Chr(34) 
         Return$ + L$
      EndIf
      
   Next i
   
   ProcedureReturn Return$
   
EndProcedure

Procedure Statement2RealWords(Statement$, List Words.s()) ; not words within strings ; statement already trimmed.
   
   Protected Word$, i.l, L$
   
   ; A Statement like 'Debug("MyProcedureABC " + MyProcedureABC("ABC DEF") + Shit())' should give the 3 Words
   ; Debug
   ; MyProcedureABC (2e)
   ; Shit

;    Debug "Statement was " + Statement$
   Statement$ = NoStrings(Statement$) ; ==> Debug+MyProcedureABC+Shit
;    Debug "Statement became " + Statement$
   
   For i = 1 To Len(Statement$)
      L$ = Mid(Statement$, i, 1)
      If L$ = " " Or L$ = "+" Or L$ = "-" Or L$ = "*" Or L$ = "/" Or L$ = "(" Or L$ = "," Or L$ = ";"; Word seprators
         Word$ = Trim(Word$)
         If Len(Word$) > 0
            AddElement(Words())
            Words() = Word$
            Word$ = ""
         EndIf
         
      ElseIf L$ <> ")"
         Word$ + L$
      EndIf
      
   Next i
   Word$ = Trim(Word$)
   If Len(Word$) > 0
      AddElement(Words())
      Words() = Word$
      Word$ = ""
   EndIf
   
EndProcedure

Procedure.s GetAliasProc(b$, a$, bContinueNextLines.b) ; b is including comments, a$ without
   
   Protected nPos.l, Test$
   
   If Left(a$, 10) = "Procedure " Or Left(a$,10)="Procedure." Or Left(a$, 6) = "Macro " Or bContinueNextLines
      ;ok we need to look in b for an alias (if any) in the form Ann, 3 or 4 long, a uppercase letter, a number of 2 or 3 digits
      nPos = FindString(b$, ";")
      If nPos > 1
         b$ = Mid(b$, nPos+1)
         NewList Words.s() ; reset
         Statement2RealWords(b$, Words()) ; not words within strings etc
         ForEach Words()
            If Len(Words()) = 3 Or Len(Words()) = 4
               Test$ = Left(Words(),1)
               If Asc(Test$) >= 65 And Asc(Test$) <= 90 ; A-Z
                  Test$ = Mid(Words(),2)               ; should be a number
                  If Val(Test$) > 0
                     ;Debug "ProcAlias detected: " + Words() + " in " + b$
                     ProcedureReturn Words()   
                  EndIf                  
               EndIf                  
            EndIf
         Next Words()
         ProcedureReturn ""
      Else
         ProcedureReturn ""
      EndIf
      
   Else
      ProcedureReturn ""
   EndIf
      
EndProcedure

Procedure AllStatements2List(File$)
   
   Protected l.i, a$, b$, LineNr.l, NrFile.i, File2$, bNoDebugger.b, bData.b, bContinue.b = #False
   
   Debug sElapsedTimeMs() + " Phase 1. AllStatements2List " + File$
   
   NrFile = ReadFile(#PB_Any, File$)
   
   If IsFile(NrFile)
      LineNr = 0
      
      AddElement(SourceFiles())
      SourceFiles() = File$
      
      While Not Eof(NrFile)
         b$ = ReadString(NrFile,ReadStringFormat(NrFile))
         a$ = Trim(WithoutComments(b$))
         b$ = GetAliasProc(b$, a$, bContinue)
         
         ; Procedure or Macro but no alias found, continue on next line
         If TypeRoutine(a$) <> "F" And b$ = "" And (Right(a$,1) = "," Or Right(a$,1) = ")")
            bContinue = #True
         ElseIf bContinue And (Right(a$,1) = "," Or Right(a$,1) = ")")
            bContinue = #True
         ElseIf b$ <> ""
            bContinue = #False
         ElseIf Not (Right(a$,1) = "," Or Right(a$,1) = ")")
            bContinue = #False
         EndIf
         
         LineNr+1
         
         If a$ = "DisableDebugger"
            bNoDebugger = #True
         ElseIf a$ = "EnableDebugger"
            bNoDebugger = #False
         EndIf
         
         If a$ = "DataSection"
            bData = #True
         ElseIf a$ = "EndDataSection"
            bData = #False
         EndIf
         
         If b$ > "" Or (a$>"" And Left(a$,1)<>Chr(59) And Not bNoDebugger And Not bData And Left(a$, 7) <> "Declare")
            AddElement(StatementsInFiles())
            
            StatementsInFiles()\StatementsFile$ = File$
            StatementsInFiles()\StatementsInProc$ = a$
            StatementsInFiles()\StatementsLines = LineNr
            StatementsInFiles()\StatementsProcAlias$ = b$
            
            If Left(a$,12)  = "XIncludeFile" Or Left(a$,11)  = "IncludeFile"
               
               File2$ = GetFirstString(a$) ; the include file
               If FindString(GetPathPart(File2$), ":") = 0 ; no (complete) path
                  File2$ = GetPathPart(File$) + File2$ ; give it one
               EndIf
                                 
               ; Scan SourceFiles() if not already done               
               If Not FoundInSourceFiles(File2$)
                  
                  Debug "Waiting for your answer, should I add " + File2$ + "? (move debug window if necassary)"
                  If MessageRequester("Include file to analyze?", "Add " + File2$ + "?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
                     ;@@2DO: IncludePath support               
                     AllStatements2List(File2$) ; nested call to add includefile(s)
                  EndIf
                  
               EndIf
               
            EndIf
            
         EndIf
      Wend
      CloseFile(NrFile)  
   Else
      Debug "File "+ File$ + " could not be added. IncludePath (not yet supported) problem?"
   EndIf
   
;    Debug "" + ListSize(StatementsInFiles()) + " StatementsInFiles."
   
EndProcedure

Procedure.b FoundProcElement(ProcFile$, ProcName$, ProcType$)
   
   FirstElement(Proc())
   ForEach Proc()
      
      If Proc()\ProcFile$ = ProcFile$ And Proc()\ProcName$ = ProcName$ And Proc()\ProcType$ = ProcType$
         ProcedureReturn #True
      EndIf
      
   Next Proc()
   
   ProcedureReturn #False
   
EndProcedure

Procedure CreateOrFindProcElement(ProcFile$, ProcName$, ProcType$, ProcAlias$)
   
   If ProcType$ = "F" And FoundProcElement(ProcFile$, "***File***", ProcType$)
   ElseIf FoundProcElement(ProcFile$, ProcName$, ProcType$)
      
   Else
         
      AddElement(Proc())
      
      Proc()\ProcFile$ = ProcFile$
      Proc()\ProcName$=ProcName$
      If ProcType$ = "F"
         Proc()\ProcName$ = "***File***"
      EndIf                  
      Proc()\ProcType$ = ProcType$
      Proc()\ProcAlias$ = ProcAlias$
      ;Debug "TESTING Proc added: " + Proc()\ProcName$
   EndIf
   
EndProcedure   

Procedure StatemensInFiles2Procedures()
   
   Protected Type$, OldType$ ; for Procedures, Macros or Filelevel code
   Protected EndType$, Statement$, bInProcedure.b
   
   Debug sElapsedTimeMs() + " Phase 2. StatementsInFiles2Procedures()"
   
   With Proc()      
      If ListSize(StatementsInFiles())>2

         ForEach StatementsInFiles()
            
            Statement$ = Trim(StatementsInFiles()\StatementsInProc$)
                        
            ; not empty, no comment or constant assigns
            If StatementsInFiles()\StatementsProcAlias$ <> "" Or (Statement$ > "" And 
                      Left(Statement$,1) <> Chr(59) And Left(Statement$,1) <> Chr(35))
               
               OldType$ = Type$
               Type$ = TypeRoutine(Statement$)
               
               If Type$ = "P"
                  EndType$ = "EndProcedure"
                  bInProcedure = #True
               ElseIf Type$ = "M"
                  EndType$ = "EndMacro"
                  bInProcedure = #True
               ElseIf Type$ = "F" And Not bInProcedure
                  EndType$ = "<EOF>"
               Else
                  ; within a procedure                  
                  Type$ = OldType$
                  If Statement$ = EndType$ ; EXIT procedure or macro
                     bInProcedure = #False
                  EndIf
               EndIf
               
               If OldType$ <> Type$
                  CreateOrFindProcElement(StatementsInFiles()\StatementsFile$, Statement$, Type$,
                                          StatementsInFiles()\StatementsProcAlias$)
               EndIf
               
               If StatementsInFiles()\StatementsProcAlias$ <> ""
                  \ProcAlias$ = StatementsInFiles()\StatementsProcAlias$
               EndIf
               
               AddElement(\ProcStatements())                        
               \ProcStatements()\StatementsInProc$ =Statement$
               \ProcStatements()\StatementsLines = StatementsInFiles()\StatementsLines
               \ProcStatements()\StatementsProcAlias$ = StatementsInFiles()\StatementsProcAlias$
               
               If Not bInProcedure ; Two Procedure or macros behind each other need a reset of Type$
                  Type$ = ""
               EndIf
                              
            EndIf
         Next StatementsInFiles()
      EndIf ; listsize
      
   EndWith ; Proc()

EndProcedure

Procedure.b CreateOrFindDependancyListElement(ProcName$, Make.b = #True, Rank = 0)
   
   FirstElement(Dependancy())
    
   ForEach Dependancy()
      If UCase(Dependancy()\ProcName$) = UCase(ProcName$) Or (Rank > 0 And Dependancy()\Rank = Rank)
         ProcedureReturn #False
      EndIf      
   Next Dependancy()
   
   If Make
      AddElement(Dependancy())
      ProcedureReturn #True ; Created
   Else
      Debug "*****************************************CreateOrFindDependancyListElement did not find " + ProcName$
   EndIf
   
EndProcedure

Procedure Dependencies()
   
   Protected NrLine.l, CallingProc$, CalledByPos.l, CallingPos.l, nPos.l, Test$
   Protected T1$, T2$, T3$ ; debug
   
   Debug sElapsedTimeMs() + " Phase 3. Dependencies for " + ListSize(Proc()) + " procedures/macros."
   If ListSize(Proc()) > 50
      Debug "This may take some time!"
   EndIf
      
   NewList ProcCopy.ProcList()
   CopyList(Proc(), ProcCopy())
   
   FirstElement(ProcCopy())
   
   ForEach ProcCopy()
      
      CallingProc$ = GetProcName(ProcCopy()\ProcName$, ProcCopy()\ProcType$)
      If Trim(CallingProc$) = "" Or FindString(CallingProc$, ",") > 0
         Debug "1. WRONG! " + CallingProc$
      EndIf
      If CreateOrFindDependancyListElement(CallingProc$)
         Dependancy()\ProcName$ = CallingProc$
         ;Debug "I. TESTING Dependancy #" + ListSize(Dependancy()) + " created With name " + Dependancy()\ProcName$ + " from " + ProcCopy()\ProcFile$
      EndIf
      Dependancy()\ProcFile$ = ProcCopy()\ProcFile$
      Dependancy()\ProcType$ = ProcCopy()\ProcType$  
      Dependancy()\ProcAlias$ = ProcCopy()\ProcAlias$
      
      CalledByPos = ListIndex(Dependancy())
      
      FirstElement(Proc())            
      ForEach Proc()    ; loop through the code lines in the procs to search for calls
         
         NrLine = 0

         ForEach Proc()\ProcStatements()
            NrLine+1
            If NrLine = 1
               CallingProc$ = GetProcName(Proc()\ProcStatements()\StatementsInProc$, Proc()\ProcType$)
               If Trim(CallingProc$) = "" Or FindString(CallingProc$, ",") > 0
                  Debug "2. WRONG! " + CallingProc$
               EndIf
               
               If CreateOrFindDependancyListElement(CallingProc$)
                  Dependancy()\ProcName$ = CallingProc$
                  ;Debug "II. TESTING Dependancy #" + ListSize(Dependancy()) + " created With name " + Dependancy()\ProcName$ + " from " + Proc()\ProcFile$
               EndIf
               CallingPos = ListIndex(Dependancy())
            EndIf
            
            SelectElement(Dependancy(), CalledByPos)
            T1$ = Proc()\ProcStatements()\StatementsInProc$
            T2$ = Dependancy()\ProcName$
            T3$ = Proc()\ProcAlias$
            
            If NrLine > 1 ; for check this one out  And FindString(UCase(T1$), UCase(T2$))  ; not its own but nested calls will be added
               ; Whole word has to be exact the same and not within strings
               ; like A_MyRoutine = 1 should not be added as a dependancy of MyRoutine
               ; like MyRoutineABC = 1 should not be added as a dependancy of MyRoutine
               ; like Debug( "MyRoutine") should not be added as a dependancy of MyRoutine
               NewList Words.s() ; reset
               
               Statement2RealWords(Proc()\ProcStatements()\StatementsInProc$, Words()) ; not words within strings etc
               
               ForEach Words()
                  If UCase(Words()) = UCase(Dependancy()\ProcName$) Or UCase(Words()) = "@" + UCase(Dependancy()\ProcName$)
                     
                     AddElement(Dependancy()\CalledBys())                     
                     
                     Dependancy()\CalledBys()\CalledByFile$ = Proc()\ProcFile$
                     Dependancy()\CalledBys()\CalledByProcNameFull$ = Proc()\ProcName$
                     Dependancy()\CalledBys()\CalledByProcNameShort$ = GetProcName(Proc()\ProcName$, Proc()\ProcType$)
                     Dependancy()\CalledBys()\CalledByType$ = Proc()\ProcType$
                     
                     Dependancy()\CalledBys()\CalledByLine = Proc()\ProcStatements()\StatementsLines
                     Dependancy()\CalledBys()\CalledByStatement$ = Proc()\ProcStatements()\StatementsInProc$
                     
                     SelectElement(Dependancy(), CallingPos)
                     AddElement(Dependancy()\Calls())                     
                     
                     Dependancy()\Calls()\CalledByFile$ = ProcCopy()\ProcFile$
                     Dependancy()\Calls()\CalledByProcNameFull$ = ProcCopy()\ProcName$
                     Dependancy()\Calls()\CalledByProcNameShort$ = GetProcName(ProcCopy()\ProcName$, ProcCopy()\ProcType$)
                     Dependancy()\Calls()\CalledByType$ = ProcCopy()\ProcType$
                     
                     ;Proc() and not ProcCopy()!
                     Dependancy()\Calls()\CalledByLine = Proc()\ProcStatements()\StatementsLines
                     Dependancy()\Calls()\CalledByStatement$ = Proc()\ProcStatements()\StatementsInProc$
                     
                  EndIf
                  
                  If ListIndex(ProcCopy()) = 1 And Left(T1$, 9) <> "Threaded "; only first time check
                     nPos = FindString(Words(), "_")
                     If nPos = 4 Or nPos = 5 ; check on possible mistakes with alias/threaded variables
                        Test$ = Left(Words(), nPos-1)
                        If Test$ <> T3$
                           ; check if Test$ is a valid alias 
                           If Asc(Test$) >= 65 And Asc(Test$) <= 90 ; A-Z
                              Test$ = Mid(Test$,2)               ; should be a number
                              If Val(Test$) > 0                                 
                                 Debug "$$$ WARNING $$$ ALIAS = " + T3$ + " variable "+ Words() + 
                                       " in Line "+ Proc()\ProcStatements()\StatementsLines + " " +
                                       Proc()\ProcStatements()\StatementsInProc$
                              EndIf                  
                           EndIf      
                        EndIf ;Test$                        
                     EndIf ; nPos
                  EndIf ; listindex
               Next Words()
            
            EndIf
         Next Proc()\ProcStatements()
      Next Proc()
      
      If Mod(ListIndex(ProcCopy()), 10) = 0
         Debug sElapsedTimeMs() + " Progress in Dependancies " + ListIndex(ProcCopy())+ "/" +ListSize(ProcCopy())
      EndIf
      
   Next ProcCopy()
   
EndProcedure

Procedure UpdateDependancyCalls2Rank(ProcName$, Rank.l)
;   Only Non existing CalledByProcNameShort$ With Rank+1. 
;   Update existing CalledByProcNameShort$ With lower Rank
   
   Protected bFound.b
   
   FirstElement(DependancyCalls2Rank())
   ForEach DependancyCalls2Rank()
      If DependancyCalls2Rank()\ProcName$ = ProcName$ ; already existing item
         If bFound                                    ; already earlier found
            DeleteElement(DependancyCalls2Rank())
            Debug DependancyCalls2Rank()\ProcName$ + " Redundant Item of DependancyCalls2Rank Deleted."
         Else            
            bFound = #True
            DependancyCalls2Rank()\Rank = Maxl(DependancyCalls2Rank()\Rank, Rank)
         EndIf
         
      EndIf      
   Next DependancyCalls2Rank()
   
   If Not bFound
      AddElement(DependancyCalls2Rank())
      DependancyCalls2Rank()\ProcName$ = ProcName$
      DependancyCalls2Rank()\Rank = Rank
;       Debug "2. DependancyCalls2Rank added " + DependancyCalls2Rank()\ProcName$ + " with Rank " + DependancyCalls2Rank()\Rank
   EndIf
   
EndProcedure        

Procedure.l RankFirstCalledDependancys(Rank.l)
   
   Protected Number.l, Position1.l, Position2.l
   
   Position1 = ListIndex(Dependancy())
   
   ForEach Dependancy()\Calls()
      Position2 = ListIndex(Dependancy()\Calls())
      
      CreateOrFindDependancyListElement(Dependancy()\Calls()\CalledByProcNameShort$, #False) ; only find
      Dependancy()\Rank = Maxl(Rank, Dependancy()\Rank)
      Debug Dependancy()\ProcName$ + " Dependancy()\Rank := " + Dependancy()\Rank
      Number+1
      
      If Dependancy()\NumberCalls > 0
         FirstElement(Dependancy()\Calls())
         ForEach Dependancy()\Calls()
            UpdateDependancyCalls2Rank(Dependancy()\Calls()\CalledByProcNameShort$, Rank+1)            
;             Debug "1. DependancyCalls2Rank added " + DependancyCalls2Rank()\ProcName$ + " with Rank " + DependancyCalls2Rank()\Rank
         Next Dependancy()\Calls()   
      EndIf
      
      SelectElement(Dependancy(), Position1)
      SelectElement(Dependancy()\Calls(), Position2) ; to be sure
      
   Next Dependancy()\Calls()
   
   ProcedureReturn Number
            
EndProcedure

Procedure RankProcedures()
   
   Protected Rank.l, Position.l
   
   Debug sElapsedTimeMs() + " Phase 4. RankProcedures"
   
   FirstElement(Dependancy())
   ForEach Dependancy()
      Dependancy()\NumberCalledBy = ListSize(Dependancy()\CalledBys())      
      Dependancy()\NumberCalls = ListSize(Dependancy()\Calls())
   Next Dependancy()
   
   FirstElement(Dependancy())
   ForEach Dependancy()
      If Dependancy()\ProcType$ = "F" And Dependancy()\NumberCalls > 0
         Rank = 0
         Dependancy()\Rank = Rank                
         RankFirstCalledDependancys(Rank+1)         
      EndIf         
   Next Dependancy()
   
   While ListSize(DependancyCalls2Rank()) > 0
      
      Debug "Ranking start loop with elements: " + ListSize(DependancyCalls2Rank())
      
      FirstElement(DependancyCalls2Rank())
      ForEach DependancyCalls2Rank()
               
         Rank = DependancyCalls2Rank()\Rank
         
         CreateOrFindDependancyListElement(DependancyCalls2Rank()\ProcName$, #False)
         Dependancy()\Rank = Maxl(Rank, Dependancy()\Rank)
;          Debug Dependancy()\ProcName$ + " Dependancy()\Rank ==> " + Dependancy()\Rank
         
         FirstElement(Dependancy()\Calls())
         ForEach Dependancy()\Calls()    
            
            If UCase(Dependancy()\Calls()\CalledByProcNameShort$) = UCase(DependancyCalls2Rank()\ProcName$)
               ; Nested Call no ranking (eternal loop)
               Debug "No Ranking for Nested Call in " + DependancyCalls2Rank()\ProcName$
            Else
               Position = ListIndex(DependancyCalls2Rank())
               UpdateDependancyCalls2Rank(Dependancy()\Calls()\CalledByProcNameShort$, Rank+1)
               SelectElement(DependancyCalls2Rank(), Position)
            EndIf
            
         Next Dependancy()\Calls()
         
         DeleteElement(DependancyCalls2Rank())
         
      Next DependancyCalls2Rank()
   Wend
   
EndProcedure

Procedure FillDependenciesChainFromTop(Chain$) ; This proces is dependant of the right ranking (Dependancy is sorted on rank)
   
   Static RankDebug.l   
   Protected Pos1.l, Pos2.l, Pos3.l
   
   NewList CallChain$() ; of only 1 dependancy(), the last element Chain$, in 1e call the main procedure   
   Static NewList CallChainNextLevel.SaveList() ; of only 1 dependancy() further in the chain
   
   If Dependancy()\NumberCalls > 0
      Pos1 = ListIndex(Dependancy())
      ForEach Dependancy()\Calls() ; of Chain$ 
         Pos2 = ListIndex(Dependancy()\Calls())
         If Dependancy()\Calls()\CalledByProcNameShort$ <> Dependancy()\ProcName$ ; non nested calls           
            
            AddElement(CallChain$())
            CallChain$() = Chain$ + ";" + Dependancy()\Calls()\CalledByProcNameShort$
            CreateOrFindDependancyListElement(Dependancy()\Calls()\CalledByProcNameShort$)
            If Dependancy()\NumberCalls - Dependancy()\NumberNestedCalls = 0 And Right(CallChain$(),1) <> "."
               CallChain$() + "."               
            EndIf
            
            AddElement(CallChainNextLevel())
            Pos3 = ListIndex(CallChainNextLevel())
            
            CopyList(CallChain$(), CallChainNextLevel()\ChainFromTop$()) ; CallChainNextLevel$ is static so can be given to the nested call
            FillDependenciesChainFromTop(CallChain$()) ; Nested call!!
            SelectElement(CallChainNextLevel(), Pos3)
            CopyList(CallChainNextLevel()\ChainFromTop$(), CallChain$()) ; Take it over
            
         Else
            Debug "Nested call not in (endless) chain: " + Dependancy()\ProcName$
            Dependancy()\NumberNestedCalls+1
            If Dependancy()\NumberCalls - Dependancy()\NumberNestedCalls = 0 
               If ListSize(CallChain$()) > 0
                  If Right(CallChain$(),1) <> "."
                     CallChain$() + "."
                  EndIf
               Else
                   If Right(Chain$,1) <> "."
                      Chain$ + "."
                   EndIf
               EndIf              
            EndIf ; Last element in chain?        
         EndIf ; nested call?    
         SelectElement(Dependancy(), Pos1)
         SelectElement(Dependancy()\Calls(), Pos2)
      Next Dependancy()\Calls()
   EndIf
   
   If ListSize(CallChain$()) > 0 ; Ready; save list to our Dependancy
              
      CopyList(CallChain$(), Dependancy()\ChainFromTop$())
      ClearList(CallChain$())
   Else
      ;Testing
;       Debug Dependancy()\ProcName$ + "'s ChainFromTop$ is being filled with "
      If Right(Chain$,1) <> "."
         Chain$ + "."
      EndIf
      
;       Debug "* " + Chain$
      AddElement(Dependancy()\ChainFromTop$())
      Dependancy()\ChainFromTop$() = Chain$
   EndIf
   
EndProcedure

Procedure BuildUniqueCallChains()
   
   Protected Chain$
   
   ForEach Dependancy()
      ForEach Dependancy()\ChainFromTop$()
         If Right(Dependancy()\ChainFromTop$(),1) = "."
            AddElement(UniqueCallChains$())
            UniqueCallChains$() = Dependancy()\ChainFromTop$()
         EndIf         
      Next Dependancy()\ChainFromTop$() 
   Next Dependancy()
   
   SortList(UniqueCallChains$(), #PB_Sort_Ascending)
   
   ForEach UniqueCallChains$()
      If UniqueCallChains$() = Chain$
         DeleteElement(UniqueCallChains$())
      EndIf
      Chain$ = UniqueCallChains$()
   Next UniqueCallChains$()
   
EndProcedure

Procedure ShowInfo(NrDep.l)
   
   SelectElement(Dependancy(), NrDep-1)
   
   If OpenWindow(1, 0, 0, 800, 400, "Info of procedure " + 
          Dependancy()\ProcName$+ "("+ Dependancy()\ProcAlias$ + ")", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
      ListViewGadget(0, 10, 10, 780, 380)
      
      AddGadgetItem (0, -1, "Called By " + Dependancy()\NumberCalledBy)
      NrDep = 0
      ForEach Dependancy()\CalledBys()
         NrDep+1
         AddGadgetItem (0, -1, "CB-" + Str(NrDep) + Dependancy()\CalledBys()\CalledByFile$ + ":" +
                        Dependancy()\CalledBys()\CalledByProcNameShort$) ; define listview content
      Next Dependancy()\CalledBys()
      
      AddGadgetItem (0, -1, "Calling " + Dependancy()\NumberCalls)
      NrDep = 0
      ForEach Dependancy()\Calls()
         NrDep+1
         AddGadgetItem (0, -1, "CA-" + Str(NrDep) + Dependancy()\Calls()\CalledByFile$ + ":" +
                        Dependancy()\Calls()\CalledByProcNameShort$) ; define listview content
      Next Dependancy()\Calls()
      
      AddGadgetItem (0, -1, "Calling trees")
      ForEach UniqueCallChains$()
         If FindString(UniqueCallChains$(),  ";"+ Dependancy()\ProcName$ + ";") > 0 Or
            FindString(UniqueCallChains$(),  ";"+ Dependancy()\ProcName$ + ".") > 0 Or
            FindString(UniqueCallChains$(),  Dependancy()\ProcName$ + ";") = 1
            AddGadgetItem (0, -1, UniqueCallChains$())  
         EndIf   
      Next UniqueCallChains$()      
      
      Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
      
      CloseWindow(1)
   EndIf
      
EndProcedure

Procedure Main()
   
   Protected f$, NrDep.l, Text$, MainProc$
   Protected Event.l, Quit.l

   f$=OpenFileRequester("Select A .pb Source File","*.pb","PureBasic (*.pb)|*.pb",1)
   If f$
      gStartms = ElapsedMilliseconds()
      AllStatements2List(f$)        ; process file(s) to a linked list with only code lines
      StatemensInFiles2Procedures() ; process lines into procedures
      Dependencies()                ; examine dependencies (called by)
      RankProcedures()              ; rank the procedures
      
      SortStructuredList(Dependancy(), #PB_Sort_Ascending, OffsetOf(Dependancy\Rank) ,TypeOf(Dependancy\Rank))
      
      CreateOrFindDependancyListElement("", #False, 1)
      If Dependancy()\Rank = 1
         MainProc$ = Dependancy()\ProcName$
         Debug "Main procedure found: " + MainProc$
         Debug sElapsedTimeMs() + " Phase 5: FillDependenciesChainFromTop called with " + MainProc$
         FillDependenciesChainFromTop(MainProc$)
         ; buggy, lets make FillDependenciesChainFromTop better so we don't need AddChainInfo()
         Debug sElapsedTimeMs() + " Phase 6: Building unique call-chains."
         BuildUniqueCallChains()
      Else
         Debug "No main procedure (rank 1) was found so no chain-building"
      EndIf
      
      Debug sElapsedTimeMs() + " Phase 7: Output."
      
      Debug "========================================================================="
      Debug "                              EXPLANATION"
      Debug "========================================================================="
      Debug "Type = File(F), Plain Procedure(P) or Macro(M). If a procedure has an alias it is noted (right of its name)"
      Debug "... is calling a-b, the b stands for the number it is calling itself in a nested call"
      Debug "CB = Called By, CA = Calling"
      Debug "========================================================================="
      Debug "            TESTING List of called Dependencies with full info"
      Debug "========================================================================="
      
      ForEach Dependancy()
         If Dependancy()\NumberCalledBy > 0 Or Dependancy()\ProcType$ = "F" Or Dependancy()\Rank = 0 
            If Dependancy()\Rank = 0 And Dependancy()\ProcType$ <> "F"
               Debug Dependancy()\ProcName$ + " : does not seem to be used"
            Else
               
               If Dependancy()\ProcAlias$ > ""
                  Text$ = Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + "(" + Dependancy()\ProcAlias$ + 
                          ") type " + Dependancy()\ProcType$ + " is called by " +
                          Dependancy()\NumberCalledBy + " and is calling " + Dependancy()\NumberCalls + 
                          "-" + Dependancy()\NumberNestedCalls +" :"
                     
               Else
                  Text$ = Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + 
                          " type " + Dependancy()\ProcType$ + " is called by " +
                          Dependancy()\NumberCalledBy + " and is calling " + Dependancy()\NumberCalls + 
                          "-" + Dependancy()\NumberNestedCalls +" :"
               EndIf
               
               Debug RSet("", Len(Text$), "=")
               Debug Text$
               Debug RSet("", Len(Text$), "=")
               
               NrDep = 0
               ForEach Dependancy()\CalledBys()
                  NrDep+1
                  Debug "CB- " + NrDep + ". " + Dependancy()\CalledBys()\CalledByFile$ +" //type:// "+
                                    Dependancy()\CalledBys()\CalledByType$ + " //proc:// "+
                                    Dependancy()\CalledBys()\CalledByProcNameShort$ + " //full:// "+ 
                                    Dependancy()\CalledBys()\CalledByProcNameFull$ + " //line:// "+ 
                                    Dependancy()\CalledBys()\CalledByLine + " //stat:// "+ 
                                    Dependancy()\CalledBys()\CalledByStatement$
               Next Dependancy()\CalledBys()
               
               NrDep = 0
               ForEach Dependancy()\Calls()
                  NrDep+1
                  Debug "CA- " + NrDep + ". " + Dependancy()\Calls()\CalledByFile$ +" //type:// "+
                                    Dependancy()\Calls()\CalledByType$ + " //proc:// "+ 
                                    Dependancy()\Calls()\CalledByProcNameShort$ + " //full:// "+ 
                                    Dependancy()\Calls()\CalledByProcNameFull$ + " //line:// "+ 
                                    Dependancy()\Calls()\CalledByLine + " //stat:// "+ 
                                    Dependancy()\Calls()\CalledByStatement$
               
               Next Dependancy()\Calls()  
            EndIf    
         EndIf
      Next Dependancy()
      Debug "==========================================================================="
      Debug "==========================================================================="
      Debug "          TESTING List of called Dependencies without calling info"
      Debug "==========================================================================="
      
      ForEach Dependancy() 
         If Dependancy()\NumberCalledBy > 0 Or Dependancy()\ProcType$ = "F"  Or Dependancy()\Rank > 0
            If Dependancy()\Rank = 0 And Dependancy()\ProcType$ <> "F"
               Debug Dependancy()\ProcName$ + " : does not seem to be used"
            Else
               Debug Str(Dependancy()\Rank) + "." + Dependancy()\ProcName$ + " is calling " + Dependancy()\NumberCalls +
                     " - " + Dependancy()\NumberNestedCalls +" :"
            EndIf
            
         EndIf         
      Next Dependancy()
      
      Debug "==========================================================================="
      Debug "TESTING List of unique calling chaines (" + ListSize(UniqueCallChains$()) + ")"
      Debug "==========================================================================="
      
      NrDep = 0
      ForEach UniqueCallChains$()
         NrDep+1
         Debug Str(NrDep) + ". " + UniqueCallChains$()
      Next UniqueCallChains$()
      
      Debug ""
      Debug sElapsedTimeMs() + " Finished."
      Debug "Saving debug info to " + f$ + "-CR.txt"
      
      SaveDebugOutput(f$ + "-CR.txt")
      
      SortStructuredList(Dependancy(), #PB_Sort_Ascending, OffsetOf(Dependancy\ProcName$) ,TypeOf(Dependancy\ProcName$))
      
      If OpenWindow(0, 200, 200, 400, 400, "Select procedure")
         If CreateMenu(0, WindowID(0))    ; menu creation starts....
            MenuTitle("Procedure")
            NrDep = 0
            ForEach Dependancy()
               NrDep+1
               MenuItem(NrDep, Dependancy()\ProcName$ + "("+ Dependancy()\ProcAlias$ + ")")
            Next Dependancy()            
            
            Repeat 
               Event = WaitWindowEvent()
               Select Event
         
                   Case #PB_Event_Menu
             
                      NrDep = EventMenu()
                      ShowInfo(NrDep)
                      
                   Case #PB_Event_CloseWindow
                      Quit = 1            
                EndSelect ; Event

             Until Quit > 0
     
          EndIf ; CreateMenu
      EndIf ; OpenWindow

   EndIf ;File
   
EndProcedure

Main()

End
The Code for the test-program:

Code: Select all

; Test_ProcCrosReference.pb

EnableExplicit

Declare.b HeIsNotHere()

Threaded T02_I.l, T04_I.l

Procedure.s BCalledByAandMain(a.i)   ;T01
   Debug "BCalledByAandMain"   
   ProcedureReturn "ACallingB called BCalledByAandMain(" + a + ")"      
EndProcedure ;T01
Procedure ACallingB() ;T02
   
   Debug "ACallingB"
   ; comment
   T02_I = 1
   Debug BCalledByAandMain(T02_I)   ; ok, no warning
   Debug BCalledByAandMain(T04_I)   ; Threaded variable from other routine used by mistake; generate warning
EndProcedure
Macro _ACallingB() ;T03
   ACallingB()
EndMacro ;T03
Procedure NothingWillBeDone(bActie.b,
                            NewLine.l = 0) ;20160601 T04
   If bActie
      T04_I = 3    ; ok, no warning
      NothingWillBeDone(#False) ; Nested Call test
   EndIf   
EndProcedure ;T04

Procedure Main() ;;20160601 T05  
   Debug "Main"   
   _ACallingB()
   T04_I = 3               ; Threaded variable from other routine used by mistake; generate warning
   BCalledByAandMain(T04_I); Threaded variable from other routine used by mistake; generate warning
   NothingWillBeDone(#True)
EndProcedure ;T05

Main()

DataSection
MyClass:
    Data.i @Main()   
EndDataSection

End
Post Reply