ClassOverview: per vederci chiaro



Capita spesso di scrivere librerie o programmi molto complessi, soprattutto classi molto complesse. Ad un certo punto ci si può ritrovare a perdere un po' di vista il flusso generale del codice. L'obiettivo di ClasseOverview è quello di mostrare graficamente uno schema generale del progetto. Per il momento, essendo un prototipo, è in grado di analizzare un singolo. pass alla volta. Una volta caricato il sorgente vengono mostrate tutte le procedure,le funcion, i constructor e i destructor. Queste quattro definizioni sono i metodi che vengono gestiti da una lista. E' possibile spostare con il drag and drop del mouse i nomi dei metodi sulla form. Questi posizionamenti vengono salvati nella sottocartella Methods. Facendo clic sul singolo metodo la memo mostrerà quella porzione di codice solitamente compresa tra un begin e un end. I due pulsanti mostrano i metodi in memoria e quando questi metodi chiamano altri metodi.


Puoi scaricare il progetto completo qui.


unit Unit1;
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StrUtils, StdCtrls, Generics.Collections ,Generics.Defaults,
  Inifiles, Math, Vcl.ComCtrls ;

type TRecordClassMethod = record
  Classe : string;    // nome della Classe
  Method : string;    // nome della procedure o function
  pas: string         // unit.pas in cui è dichiarata
end;

type TPasClass = Class
  Classe : string;    // nome della Classe
  pas: string         // unit.pas in cui è dichiarata
end;

type TClassMethod = Class
  posBegin: integer;  // riga del sorgente.pas ( di solito suito sotto si trova 'begin')
  posEnd: integer;    // riga del sorgente.pas dove si trova 'end;' che conclude il begin
  Classe : string;    // nome della Classe
  Method : string;    // nome della procedure o function
  pas: string         // unit.pas in cui è dichiarata
end;

type TmyLabel = Class
  FormLabel: TLabel;
  Classe : string; // nome della Classe
  Method : string;    // nome della procedure o function
  constructor create ( aClass, aMethod: string; X,Y: integer; aCol:TColor );
end;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    aBox1: TComboBox;
    Button1: TButton;
    Button2: TButton;
    ProgressBar1: TProgressBar;
    ProgressBar2: TProgressBar;
    Button3: TButton;
    OpenDialog1: TOpenDialog;
    Label1: TLabel;
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure LabelClick(Sender: TObject);
    procedure LabelDblClick(Sender: TObject);
    procedure LabelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure LabelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormPaint(Sender: TObject);

    procedure TsCopyToLower;
    function FindNextEnd ( StartRow: integer ) : integer;
    function GetMethod ( S: string): TClassMethod;
    procedure FindReferences ( S: string);
    procedure FindEveryCall ( Index, b,e: integer ; apasFile:string );

    function FindValidMethod ( MainClassMethod, aClassMethod:TclassMethod; row: integer): TRecordClassMethod;
    function GetClassFromVariable ( aMethod,aRowString: string; StartMethod, StartAfterMethod,Row: integer): string;

    procedure aBox1Select(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure aBox1KeyPress(Sender: TObject; var Key: Char);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
    procedure SaveIni;

    procedure ExtractClassMethods ( aPasClass: TPasClass );
    function MyLabelExists ( aClass, aMethod: string): boolean;
    function MethodExist ( aClass, aMethod: string): boolean;
    function ClassesExist ( aClass: string): boolean;
    procedure AddClassMethod ( aPasClass, aClassName , aMethodName: string ; i: integer);
    function JustClassName ( S: string; Index: integer ) : string; overload;  // JustClassName + '.' + JustMethodName;
    function JustMethodName ( S: string; Index, endm: integer ) : string; overload; // JustClassName + '.' + JustMethodName;
    function ValidLeftMethod ( aRowString: string; StartMethod, StartAfterMethod, row: integer ): TRecordClassMethod;
    function ValidRightMethod ( aRowString: string; StartMethod, StartAfterMethod: integer ): TRecordClassMethod;
    function ValidRightVariable ( aRowString: string; StartVar, StartAfterVar, row: integer ): string;
    function ValidLeftVariable ( aRowString: string; StartVar, StartAfterVar, row: integer ): string;
    Function GetClass  (aVariable,aRowString: string; StartVar: integer ): string;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  DIR_APP: string;
  DIR_METHODS :string;
  px,py:integer;
  tsLink: TStringList;
  tsPasLower : Tstringlist;                // tutto il sorgente .pas attuale in formato LowerCase
  tsPasOriginal : Tstringlist;             // tutto il sorgente .pas attuale

  lstLabels: TobjectList<TmyLabel>;   // lista delle Tlabel
  lstClassMethods: TobjectList<TClassMethod>;   // lista di tutte le classi e metodi globale
  lstPasClasses: TobjectList<TPasClass>;   // lista di tutte le classi globale
implementation
function GetAppVersionStr: string;
var
  Exe: string;
  Size, Handle: DWORD;
  Buffer: TBytes;
  FixedPtr: PVSFixedFileInfo;
begin
  Exe := ParamStr(0);
  Size := GetFileVersionInfoSize(PChar(Exe), Handle);
  if Size = 0 then
    RaiseLastOSError;
  SetLength(Buffer, Size);
  if not GetFileVersionInfo(PChar(Exe), Handle, Size, Buffer) then
    RaiseLastOSError;
  if not VerQueryValue(Buffer, '\', Pointer(FixedPtr), Size) then
    RaiseLastOSError;
  Result := Format('%d.%d.%d.%d',
    [LongRec(FixedPtr.dwFileVersionMS).Hi,  //major
     LongRec(FixedPtr.dwFileVersionMS).Lo,  //minor
     LongRec(FixedPtr.dwFileVersionLS).Hi,  //release
     LongRec(FixedPtr.dwFileVersionLS).Lo]) //build
end;
{$R *.dfm}
constructor TmyLabel.create ( aClass, aMethod: string; X,Y: integer; aCol:TColor );
begin
  classe:= aClass;
  Method:= aMethod;
  FormLabel:= TLabel.Create(Form1);
  FormLabel.Parent := Form1;
  FormLabel.Name:= lowercase(aClass + aMethod);
  if aCol <> clBlue then
    FormLabel.Caption := '-->' + aClass+'.' + aMethod
  else
    FormLabel.Caption := aMethod;

  FormLabel.Width:=100;
  FormLabel.height:=20;
  FormLabel.Left := X;
  FormLabel.Top := Y;
  FormLabel.Font.Color := aCol;
  FormLabel.autosize:=true;
  FormLabel.OnMouseDown:= form1.LabelMouseDown;
  FormLabel.OnMouseMove := form1.LabelMouseMove;
  FormLabel.OnClick := form1.LabelClick;
  FormLabel.OnDblClick := form1.LabelDblClick;
  FormLabel.Visible:=true;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption:= 'ClassOverview ' + GetAppVersionStr;
  DIR_APP:=   extractfilepath(application.exename) ;
  DIR_METHODS := extractfilepath(application.exename) + 'methods\';
  if not DirectoryExists(DIR_METHODS) then CreateDir (DIR_METHODS);


  TsPasOriginal:= TStringList.Create ;
  TsPasLower:= TStringList.Create ;

  lstClassMethods:= TobjectList<TClassMethod>.create (true);
  lstPasClasses:= TobjectList<TPasClass>.create (true);

  lstLabels:=  TobjectList<TMyLabel>.create (true);

  tsLink:= TStringList.Create ;
  tsLink.Duplicates := dupIgnore;
  tsLink.Sorted := true;


end;
function Tform1.GetMethod ( s: string ): TClassMethod;
var
i: integer;
begin
   result:=nil;
   for I := 0 to lstClassMethods.count -1 do begin
    if lowercase(aBox1.Text)  = lowercase(lstClassMethods[i].Classe) then begin    // solo la classe selezionata dalla combobox
      if S = lowercase(lstClassMethods[i].Method) then begin
       Result := lstClassMethods[i];
       exit;
      end;
    end;
   end;
end;

procedure TForm1.LabelClick(Sender: TObject);
var
aClassMethod: TClassMethod;
c: integer;
begin
   memo1.Lines.Clear;
   //advMemo1.lines.Clear ;
   aClassMethod := GetMethod (TLabel(sender).Caption);
   if aClassMethod <> nil then begin
      for c:= aClassMethod.posBegin -1 to aClassMethod.posEnd do begin
        memo1.Lines.Add(tsPasLower[c]);
       // advMemo1.lines.Add(tsPas[c]);
      end;
   end;

end;
procedure TForm1.LabelDblClick(Sender: TObject);
var
  I: Integer;
begin
    SaveIni;
   for I := 0 to aBox1.Items.Count -1  do begin
    if lowercase(aBox1.Items[i]) = (JustClassName (TLabel(sender).Caption,4) )  then begin // 4 per le frecce -->
      aBox1.ItemIndex := i;
      aBox1Select(aBox1);
      invalidate;
      exit;
    end;

   end;

end;

procedure TForm1.LabelMouseDown(Sender: TObject;  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
i: integer;
hide: string;
begin
    px:=x;
    py:=y;
    if Button = mbRight then begin
      // dalla TLabel risalgo a tsLink per settare le LineTo !HIDE, ma TLabel rimane visibile;
      tsLink.Sorted := false;
      if Leftstr(tLabel(Sender).Caption,3) = '-->' then begin  // qui Label di un'altra classe
        // Cerco nella tsLink dove il valore Classe.metodo=Classe.metodo è uguale a Classe+punto+metodo
        for I := 0 to tsLink.Count -1 do begin
          Hide:= LeftStr(tsLink.Names [i],5 );
          if Hide <> '!HIDE' then begin
            if ((tsLink.ValueFromIndex [i] ) = JustClassName (tLabel(Sender).Caption,4) +
                                              '.' +
                                              JustMethodName(tLabel(Sender).Caption ,4, Length(tLabel(Sender).Caption)+1))
            or (tsLink.Names [i] = lowercase(JustClassName (tLabel(Sender).Caption,4) +
                                              '.' +
                                              JustMethodName(tLabel(Sender).Caption ,4, Length(tLabel(Sender).Caption)+1)))
            then begin
                // Inserisco !HIDE
                tslink[i]:= '!HIDE' + tsLink.Names [i] + '=' + tsLink.ValueFromIndex [i];// <-- nella Form.paint evita di tirare le linee
            end;
          end
          else if LeftStr(tsLink.Names [i],5 ) = '!HIDE' then begin
                // Elimino !HIDE
                tslink[i]:=  RightStr(tsLink.Names [i],length(tsLink.Names [i])-5 ) + '=' + tsLink.ValueFromIndex [i];// <-- nella Form.paint evita di tirare le linee
          end;
        end
      end
      else begin // qui Label di questa Classe // NO frecce -->
        // Creco nella tsLink dove il valore Classe.metodo=Classe.metodo è uguale a Classe+punto+metodo
        for I := 0 to tsLink.Count -1 do begin
          Hide:= LeftStr(tsLink.Names [i],5 );
          if Hide <> '!HIDE' then begin
              if (  tsLink.ValueFromIndex [i] = lowercase(aBox1.Items [aBox1.ItemIndex ] + '.' + tLabel(Sender).Caption ))
              or (  tsLink.Names [i] = lowercase(aBox1.Items [aBox1.ItemIndex ]) +  '.' + tLabel(Sender).Caption )
              then begin
                  // Inserisco !HIDE
                  tslink[i]:= '!HIDE' + tsLink.Names [i] + '=' + tsLink.ValueFromIndex [i];// <-- nella Form.paint evita di tirare le linee
              end;
          end
          else if LeftStr(tsLink.Names [i],5 ) = '!HIDE' then begin
                // Elimino !HIDE
                tslink[i]:=  RightStr(tsLink.Names [i],length(tsLink.Names [i])-5 ) + '=' + tsLink.ValueFromIndex [i];// <-- nella Form.paint evita di tirare le linee
          end;
        end;
      end;
      tsLink.Sorted := true;
      invalidate;
  end;
end;

procedure TForm1.LabelMouseMove(Sender: TObject;  Shift: TShiftState; X, Y: Integer);
begin
  if ssLeft in Shift then
  with Sender as TControl do
  begin
    SetBounds(Left+X-PX,Top+Y-PY,Width,Height);
  if sender is TLabel then
  begin
    form1.Repaint;
  end;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SaveIni;
  lstClassmethods.free;
  lstPasClasses.free;
  tsLink.free;

end;
procedure Tform1.SaveIni;
var
i: integer;
b:TLabel;
ini: Tinifile;
begin
  ini:= TIniFile.Create(DIR_METHODS +  aBox1.Text + '.ini') ;
  // salvo le TLabel solo della classe selezionata dalla combobox e le relative call esterne -->
  for i := 0 to lstClassMethods.Count-1 do begin
    if lowercase(aBox1.Text)  = lowercase(lstClassMethods[i].Classe) then begin
      B := FindComponent (lowercase(lstClassMethods[i].classe) + lowercase(lstClassMethods[i].Method)) as TLabel;
      ini.WriteInteger(lstClassMethods[i].Classe + lstClassMethods[i].Method , 'x',b.left);
      ini.WriteInteger(lstClassMethods[i].Classe + lstClassMethods[i].Method , 'y',b.top);
    end
    else begin
      B := FindComponent ( lowercase(lstClassMethods[i].classe) + lowercase(lstClassMethods[i].Method)) as TLabel;
      if B <> nil then begin
        if leftStr(B.Caption,3) ='-->' then begin
          ini.WriteInteger(lstClassMethods[i].Classe + lstClassMethods[i].Method , 'x',b.left);
          ini.WriteInteger(lstClassMethods[i].Classe + lstClassMethods[i].Method , 'y',b.top);
        end;
      end;
    end;
  end;

  ini.Free;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
i: integer;
b: TLabel;
S: string;
begin
//  exit;
  Canvas.Pen.Style := psSolid;
  Canvas.Pen.Color := clBlack;
  Canvas.Pen.Width :=1;

  for i := 0 to tsLink.Count-1 do begin
    if leftStr(tsLink.Names [i],5 ) <> '!HIDE' then

    S:= StringReplace (tsLink.Names [i],'.','',[rfReplaceAll]);
    b:=FindComponent (S) as TLabel;
    if b= nil then continue; // le !HIDE non esistono come Tlabel, la caption della Tlabel rimane invariata

    Canvas.MoveTo(b.Left,b.top);
    Canvas.Rectangle (b.Left,b.Top,b.Left-5,b.Top-5);
    S:= StringReplace (tsLink.ValueFromIndex [i],'.','',[rfReplaceAll]);

    // Possono comunque esistere delle Tlabel aasenti generate da inherited alla classe astratta. Non conoscendo la classe
    // da cui deriva skippo l'errore.
    b:=FindComponent (S) as TLabel;;
    if b= nil then continue;


    Canvas.LineTo(b.Left,b.top);
    Canvas.Rectangle (b.Left,b.Top,b.Left-2,b.Top-2);

         // Tlabel che iniziano con i la freccia --> appartenenti ad un'altra classe
         // Possiamo vedere solo una classe alla volta
    end;
end;



procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  memo1.Lines.Clear ;
  for I := 0 to tsLink.Count -1 do begin
    memo1.Lines.Add(tsLink[i]);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
i:integer;
begin
  memo1.Lines.Clear ;
  for I := 0 to lstClassMethods.Count -1 do begin
    memo1.Lines.Add(lstClassMethods[i].Classe + '.' + lstClassMethods[i].Method ) ;
  end;

end;
procedure TForm1.Button3Click(Sender: TObject);
var
Classmarker, i,y: integer;
aPasClass: TPasClass;
label NextClass;
begin
if OpenDialog1.Execute then begin
    if FileExists(OpenDialog1.FileName) then
      TsPasOriginal.LoadFromFile(OpenDialog1.filename);

      TsCopyToLower;


      for I := 0 to tsPasLower.Count -1 do begin

        classMarker := pos ( 'class', tsPasLower[i],1 );
        if ClassMarker <> 0 then begin
         // showMessage(tsPasLower.Strings[i]);
          for y := Classmarker -1 downto 0 do begin
            if tsPasLower[i][y] = '=' then begin
              if Not ClassesExist (trim ( leftstr (tsPasLower[i],y-1))) then begin  // check duplicati
                aPasClass := TPasClass.Create;
                aPasClass.pas := OpenDialog1.filename;
                aPasClass.Classe := trim ( leftstr (tsPasLower[i],y-1));
                lstPasClasses.Add(aPasClass);
              end;
            end
            else if (tsPasLower[i][y] <> '=') and (tsPasLower[i][y] <> ' ')  then goto NextClass; // sono un bad programmer...

          end;
        end;
NextClass:
      end;


      aBox1.Clear ;

      for I := 0 to lstPasClasses.Count -1 do begin
        aBox1.AddItem (lstPasClasses[i].Classe ,nil );
        ExtractClassMethods ( lstPasClasses[i] );
      end;
      ListBox1.AddItem( extractfilename(aPasClass.pas),nil);
end
else
      raise Exception.Create('File does not exist.');

end;

procedure TForm1.ExtractClassMethods ( aPasClass: TPasClass);
var
  c,endm,i: integer;
  tmpS: string;
begin

  for i := 0 to tsPasLower.count -1 do begin    // tutto il sorgente .pas
    tmpS := StringReplace ( tsPasLower[i] ,' ', '', [rfReplaceAll]);
    c:= pos ( lowercase ( aPasClass.Classe ) + '.' , tmpS, 1);  // es.= 'TSomeClass.'
    if c <> 0 then begin

      // Qui sotto posso trovare TSomeClass.SomeMethod (  oppure TSomeClass.SomeMethod e la riga inizia con function o procedure ecc...
      // Se trovo TSomeClass.SomeMethod verifico che sia function, procedure, constructor, destructor
      // quindi se elimino tutti gli spazi troverò ad esempio procedureTSomeClass.SomeMethod( oppure procedureTSomeClass.SomeMethod;
      // oppure functionTSomeClass.SomeMethod: oppure functionTSomeClass.SomeMethod(
      // non mi servono i parametri ma solo il nome del metodo
      if (leftstr (  trim ( tsPasLower[i] ),8 ) = 'function')  then begin
      // cerco da c in poi il carattere  ( che determina la fine della function. A questo punto non devo cercare i :
         endm:= pos (  '(', tmpS , c + length(aPasClass.Classe  + '.') ); // prima cerco la parentesi (
         if endm <> 0 then
              AddClassMethod ( aPasClass.pas, JustClassName  (tmpS, c), JustMethodName (tmpS, c, endm), i  )
         else begin // non ho trovato parentesi, è una function senza params, allora cerco i :
           endm:= pos (  ':', tmpS , c + length(aPasClass.Classe  + '.') );
         // cerco da almeno a partire dal . che divide class.method in poi il carattere  : che determina la fine della function
         if endm <> 0 then
              AddClassMethod ( aPasClass.pas, JustClassName  (tmpS, c), JustMethodName (tmpS, c, endm), i  )
         end;
      end
      else if (leftstr (  trim ( tsPasLower[i]) ,9 ) = 'procedure') or
            (leftstr (  trim ( tsPasLower[i] ),11 ) = 'constructor') or
            (leftstr (  trim ( tsPasLower[i]),10 ) = 'destructor') then begin
      // cerco da c in poi il carattere  ( che determina la fine della procedure. A questo punto non devo cercare ;
//         if leftstr (  trim ( lowercase(tsPas[i]) ),11 ) = 'constructor' then asm int 3 end;
         endm:= pos (  '(', tmpS , c + length(aPasClass.Classe  + '.') ); // prima cerco la parentesi (
         if endm <> 0 then
              AddClassMethod ( aPasClass.pas, JustClassName  (tmpS, c), JustMethodName (tmpS, c, endm), i  )
         else begin // non ho trovato parentesi, è una procedure senza params, allora cerco il punto e virgola ;
           endm:= pos (  ';', tmpS , c + length(aPasClass.Classe  + '.') );
         // cerco da almeno a partire dal . che divide class.method in poi il carattere  : che determina la fine della function
         if endm <> 0 then
              AddClassMethod ( aPasClass.pas, JustClassName  (tmpS, c), JustMethodName (tmpS, c, endm), i  )
         end;

         end;


      end;
    end;
end;


(* Trova il numero di riga che contiene 'end;' . In realtà trova il prossimo method *)
 { TODO : non sono ammesse nested routines }
 { TODO : bug sulle property nel getvariables}
function Tform1.FindNextEnd ( StartRow: integer ) : integer;
var
i: integer;
begin
  Result:= StartRow;
  for I := StartRow+1 to tsPasLower.Count -1 do begin
    if  (leftstr (  trim ( tsPasLower[i] ),8  ) = 'function' ) or
        (leftstr (  trim ( tsPasLower[i] ),9  ) = 'procedure') or
        (leftstr (  trim ( tsPasLower[i] ),11 ) = 'constructor') or
        (leftstr (  trim ( tsPasLower[i] ),10 ) = 'destructor') or
        (leftstr (  trim ( tsPasLower[i] ),10 ) = 'end.')
    then begin
      result := i-1;
      exit;
    end;

  end;
end;


(* estrae il nome deella function o procedure da una stringa più complessa *)
function Tform1.JustMethodName ( S: string; Index,endm: integer ) : string;
var
c: integer;
begin
  // Index indica la posizione di inizio Classe , la tipica T
  // esempio di S: 'functionTMyClass.SomeMethod'
  c:= pos ( '.', S, Index);
  Result := MidStr ( S, c +1 , endm -c-1 );
end;

(* estrae il nome deella classe da una stringa più complessa *)
function Tform1.JustClassName ( S: string; Index: integer  ) : string;
var
c: integer;
begin
  // Index indica la posizione di inizio Classe , la tipica T
  // esempio di S: 'functionTMyClass.SomeMethod'
  c:= pos ( '.', S, Index);
  Result := MidStr ( S,  Index , c-Index );
end;

procedure Tform1.FindReferences ( S: string );
var
i,c: integer;
begin
  tsLink.Clear; // una Tstringlist con index e value

  ProgressBar1.Position :=0;

  for i := 0 to lstClassMethods.Count -1 do begin
    if lowercase(S) = lowercase(lstClassMethods[i].Classe) then begin    // solo la classe selezionata dalla combobox
      // mi posiziono alla riga di begin e cerco fino alla riga di 'end;' call ad altri metodi
      ProgressBar2.Position := 0;
      for c := lstClassMethods[i].posBegin to lstClassMethods[i].posEnd do begin
        FindEveryCall (  i, lstClassMethods[i].posBegin, lstClassMethods[i].posEnd,lstClassMethods[i].pas  );
        ProgressBar2.Position := trunc( (100 * c) / (lstClassMethods[i].posEnd-lstClassMethods[i].posBegin));
      //  memo1.Lines.Add(lstClassMethods[i].Classe + '.'+lstClassMethods[i].Method );
      end;
    end;
        ProgressBar1.Position := trunc( (100 * i) / lstClassMethods.Count);
        memo1.Lines.Add(lstClassMethods[i].Classe + '.'+lstClassMethods[i].Method );
  end;

  ProgressBar2.Position := 0;
  ProgressBar1.Position :=0;
end;
procedure TForm1.aBox1KeyPress(Sender: TObject; var Key: Char);
begin
key:=#0;
end;

procedure TForm1.aBox1Select(Sender: TObject);
var
  B: TLabel;
  i,X,Y: integer;
  ini:TIniFile;
  aLabel: tMyLabel;

begin
  // In memoria ho tutte le classi.metodo in lstMethods
  // ora devo ciclare per la classe selezionata ogni Method, e trovare tra posBegin e posEnd altri Method chiamati
  // anche appartenenti ad altri classi della stesso sorgente .pas

  // delete manuale di tutte le Tlabel nella form
  for i := 0 to lstClassMethods.Count -1 do begin
    B := FindComponent (lstClassMethods[i].classe + lstClassMethods[i].Method) as TLabel;
    if B <> nil then B.Destroy ;
  end;
  // ora che ho distrutto le TLabel distruggo anche i puntatori della lista
  lstLabels.Clear ;
  ini:= TIniFile.Create(DIR_METHODS + aBox1.Text + '.ini') ;

  for i := 0 to lstClassMethods.Count -1 do begin
    if lowercase(aBox1.Items [aBox1.ItemIndex ]) = lowercase(lstClassMethods[i].Classe) then begin  // solo la classe selezionata dalla combobox
      X :=   ini.ReadInteger( lstClassMethods[i].Classe + lstClassMethods[i].Method  , 'x', randomrange (20,800));
      Y :=   ini.ReadInteger( lstClassMethods[i].Classe + lstClassMethods[i].Method  , 'y', randomrange (20,600));

      if not MyLabelExists( lstClassMethods[i].Classe , lstClassMethods[i].Method ) then begin
        aLabel:= TmyLabel.Create ( lstClassMethods[i].Classe , lstClassMethods[i].Method,  X, Y ,clblue); // creo a video la TLabel
        lstLabels.Add(aLabel);
      end;
    end;
  end;

  ini.Free;

  FindReferences ( aBox1.Items [aBox1.ItemIndex ] ) ; // riempe tsLink, lista definitiva delle caption a video
  Form1.Paint ;

end;
procedure Tform1.FindEveryCall ( Index, b,e: integer; aPasFile:string );
var
XX,YY,i,y: integer;
aLabel:TmyLabel;
ini: Tinifile;
aValidClassMethod: TRecordClassMethod;
begin
  ini:= TIniFile.Create(DIR_METHODS +  aBox1.Text + '.ini') ;
  tsPasOriginal.LoadFromFile(aPasFile);

  TsCopyToLower;
  // lstSource = lstMethods è globale ma la passo comunque per modifiche future
  for i := b to e do begin
    if (Trim (LeftStr (TsPasLower[i],2)) = '//') or (trim(TsPasLower[i])='') then continue;

    for y := 0 to lstClassMethods.Count -1 do begin

      aValidClassMethod:= FindValidMethod ( lstClassMethods[Index], lstClassMethods[y], i);
      if (aValidClassMethod.classe <> '') and (aValidClassMethod.Method <> '') then begin // classe e method trovati
        tsLink.Add(lowercase(lstClassMethods[Index].Classe + '.' + lstClassMethods[Index].Method + '=' +
                            // lstClassMethods[y].Classe +'.' + lstClassMethods[y].Method)  );  //<---- tsLink
                            aValidClassMethod.Classe +'.' + aValidClassMethod.Method)  );  //<---- tsLink

         if lstClassMethods[Index].Classe <>  lstClassMethods[y].Classe then begin
          if not MyLabelExists( lstClassMethods[y].Classe , lstClassMethods[y].Method ) then begin
            // se non esiste la myLabel la creo. Sono labels di altra classe
            XX :=   ini.ReadInteger( lstClassMethods[y].Classe + lstClassMethods[y].Method  , 'x', randomrange (20,800));
            YY :=   ini.ReadInteger( lstClassMethods[y].Classe + lstClassMethods[y].Method  , 'y', randomrange (20,600));
            aLabel:= TmyLabel.Create ( lstClassMethods[y].Classe , lstClassMethods[y].Method,  XX, YY ,clred); // creo a video la TLabel
            lstLabels.Add(aLabel);
          end;
        end;


      end



    end;
  end;
ini.Free;
end;

(* Ritorna true se esiste la MyLabel e quindi Tlabel con Name = classe + method *)
function Tform1.MyLabelExists ( aClass, aMethod: string): boolean;
var
i: integer;
begin
  result:= false;
  for I := 0 to lstLabels.count -1 do begin
    if (lstLabels[i].Classe = aClass ) and (lstLabels[i].Method  = aMethod  )then begin
      result := true;
      exit;
    end;

  end;

end;
(* Ritorna true se classe + method sono già nella lista per evitare duplicati di overload*)
function TForm1.MethodExist ( aClass, aMethod: string): boolean;
var
i: integer;
begin
  result:= false;
  for I := 0 to lstClassMethods.count -1 do begin
    if (lstClassMethods[i].Classe = aClass ) and (lstClassMethods[i].Method  = aMethod  )then begin
      result := true;
      exit;
    end;

  end;
end;
(* Ritorna true se classe è già nella lista per evitare duplicati*)
function TForm1.ClassesExist ( aClass: string): boolean;
var
i: integer;
begin
  result:= false;
  for I := 0 to lstPasClasses.count -1 do begin
    if lowercase(lstPasClasses[i].Classe) = lowercase(aClass)  then begin
      result := true;
      exit;
    end;

  end;
end;
procedure Tform1.AddClassMethod ( aPasClass, aClassName , aMethodName: string ; i: integer);
var
aClassMethod: TClassMethod;
begin
  if Not MethodExist ( aClassName, aMethodName ) then begin
    aClassMethod:= TClassMethod.Create ;
    aClassMethod.Classe := aClassName;
    aClassMethod.Method := aMethodName;
    aClassMethod.posBegin := i+1;              // di solito begin è la riga subito sotto. Da migliorare!
    aClassMethod.posEnd := FindNextEnd (i);
    aClassMethod.pas := aPasClass;
    lstClassMethods.Add(aClassMethod);
  end;
end;
function Tform1.ValidLeftMethod ( aRowString: string; StartMethod, StartAfterMethod, row: integer ): TRecordClassMethod;
const
  PrefixAllowed = '(;+-*/.,@{='; // attenzione particolare a @
var
  aPossibleMethod: string;
  i: integer;
begin
  // qui ho trovato per esempio MyGame.CreateMapCoord ( a, b ); e sia Create, Map e Coord possono essere il nostro metodo
  // quindi devo distinguere metodi come Create Map CreateMapCoord  SetCoords enucleando il nome del metodo
  // il metodo può essere .map .map(  map
  // ValidLeftMethod a differenza di ValidRightMethod torna anche la classe (se presente) verificando eventuali inherited
  aPossibleMethod := MidStr (aRowString  , StartMethod, StartAfterMethod - StartMethod);
  if StartMethod = 1 then exit;

  // qui minimo StartMethod è 2
  for i := StartMethod-1 downto 1 do begin
    if pos ( aRowString [i], PrefixAllowed, 1 ) > 0 then begin
      Result.Method := aPossibleMethod;
      { il punto determina la ricerca di un typecast o di una variabile }
      if aRowString [i]='.' then Result.classe:= GetClassFromVariable (aPossibleMethod,aRowString,StartMethod, StartAfterMethod,row);
      exit;
    end
    else if pos ( aRowString [i], ' ', 1 ) > 0 then begin   { TODO : potrebbe essere Myclass. myMethod }
      Result.Method := aPossibleMethod;
      exit;
    end
    else begin // qualsiasi altro carattere
      Result.classe :='';
      Result.Method :='';
      exit;
    end;

  end;
  { TODO : gestione inherited }
end;
function Tform1.ValidRightMethod ( aRowString: string; StartMethod, StartAfterMethod: integer ): TRecordClassMethod;
const
  SuffixAllowed = '(;+-*/),{:';
var
  i: integer;
begin
  // qui ho trovato per esempio MyGame.CreateMapCoord ( a, b ); e sia Create, Map e Coord possono essere il nostro metodo
  // quindi devo distinguere metodi come Create Map CreateMapCoord  SetCoords enucleando il nome del metodo
  Result.Method := MidStr (aRowString  , StartMethod, StartAfterMethod - StartMethod);
  if StartAfterMethod = length (aRowString) then exit;

  for i := StartAfterMethod to length (aRowString) do begin
    if pos ( aRowString [i], SuffixAllowed, 1 ) > 0 then begin
  //    Result.Method := aPossibleMethod;
      exit;
    end
    else begin // qualsiasi altro carattere, anche lo spazio
      Result.classe :='';
      Result.Method :='';
      exit;
    end;
  end;
end;

function Tform1.FindValidMethod ( MainClassMethod, aClassMethod:TclassMethod; row: integer): TRecordClassMethod;
var
StartMethod, StartAfterMethod: integer;
begin
 // if (MainClassMethod.Method ='refreshsurface' ) and  (aClassMethod.Method ='rendersprites')
//     and  (pos ( 'rendersprites' , tsPasLower[Row] , 1 ) <> 0)
//  then asm int 3 end;

  result.classe:=''; result.Method :='';
  StartMethod:= pos ( lowercase(aClassMethod.Method) , tsPasLower[Row] , 1 ); // se trovo quel metodo tra begin e end
  if StartMethod = 0 then
    exit;

  StartAfterMethod := StartMethod + length (aClassMethod.Method );

  result.Classe := MainClassMethod.Classe; // di default
  Result:= ValidRightMethod ( tsPasLower[Row] , StartMethod, StartAfterMethod );
  if Result.Method = '' then exit;

  Result:= ValidLeftMethod ( tsPasLower[Row] , StartMethod, StartAfterMethod , row);

end;

function Tform1.GetClassFromVariable ( aMethod,aRowString: string; StartMethod, StartAfterMethod, Row: integer): string;
const
Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]<>';
var
i,R,c,b,e: integer;
aPossibleVariable: string;
begin
  // qui aMtehod è sicuramente un Method di un'altra classe prechè preceduto da un punto.
  // adesso cerco a ritroso la variabile se esiste. Può essere locale o globale. Oppure è
  // direttamente il Typecast della classe.
  //es.1 TmyClass(a).aMethod
  //es.2 MyVariable.aMethod
  // es.3 a:= TMyClass.create
  Result:='';
  for i := StartMethod-2 downto 1 do begin  // -2 perchè -1 c'è per forza il punto
    if pos ( aRowString [i], Alphabet, 1 ) > 0 then begin
      // prendo caratteri fino a quando non trovo uno spazio
      // a questo punto ho il nome della variabile
      Result:= aRowString [i] + result;
    end
    else if (pos ( aRowString [i], ' ', 1 ) > 0)
     or (pos ( aRowString [i], '=', 1 ) > 0) then begin
      // es=   myvariable   .myvariable  :=Myvariable := Myvariable
      Break;
    end
    else if (pos ( aRowString [i], '.', 1 ) > 0) then begin
        { TODO : potrebbe essere un array o una TlistObject ... la classe è dopo OF }
        Result:='';
    end;

  end;

  // Prima vedo se il nome della variabile è la classe stessa.
  for I := 0 to lstPasClasses.Count -1 do begin
    if lowercase(lstPasClasses[i].Classe) = trim(Result) then
      exit;
  end;

  // se non era la classe stessa cerco na variabile



  // cerco in tutto il codice a ritroso nelle righe la dichiarazione della variabile
  // es=  myvariable: TsomeClass
  // Poi cerco se la classe esiste. Se esiste la aggiungo. In questo modo evito
  // cose come canvas.fill che non dovrebbe essere mostrata, cioè classi note a tutti
  for R := Row-1 Downto 0 do begin
    c:= pos( Result, tsPasLower[R], 1) ;
    if c > 0 then begin
      // devo trovare, al netto di tutto, Myvariable:TSomeClass
        aPossibleVariable:= ValidRightVariable  ( tsPasLower[R], c, c + Length( Result), R );
        if aPossibleVariable = '' then Continue;
        aPossibleVariable:= ValidLeftVariable( tsPasLower[R], c , c + Length( Result),R);
        // adesso ho la variabile, trovo la classe che dovrebbe essere di fianco dopo i due punti
        // es Myabriable:Tsome;
        if aPossibleVariable = '' then Continue;
        result:= GetClass  (aPossibleVariable,tsPasLower[R], c );
        if Result <> '' then break;

    end;
   end;

   // Potrebbe essere TobjectList<TMyClass>
    b:= pos( 'tobjectlist', Result, 1) ;
    if b > 0 then begin
      Result:= StringReplace (Result,' ','',[rfReplaceAll]);
      b:=  pos( '<', Result, 1) ;
      if b > 0 then e:=  pos( '>',Result, b) ;
      if e > 0 then Result:= midStr (Result, b+1, e - b -1);

    end;

end;
Function Tform1.GetClass  (aVariable,aRowString: string; StartVar: integer ): string;
var
  i,x:integer;
begin
  Result:='';
  for I := StartVar + Length(aVariable) to length(aRowString) do begin
    if aRowString [i] = ' ' then continue;
    if aRowString[i] = ':' then begin
      for x:= i+1 to length(aRowString) do begin  // cerco direttamente il punto e virgola
        if aRowString[x] = ';' then begin
          Result:= MidStr ( aRowString, i+1, x-i-1 );
          exit;
        end;
      end;
    end;
  end;
end;

procedure Tform1.TsCopyToLower;
var
i: integer;
begin
  TsPasLower.Clear ;
  for I := 0 to TsPasOriginal.Count -1 do begin
    TsPasLower.Add(lowercase(tsPasoriginal[i]));
  end;
end;
function Tform1.ValidLeftVariable ( aRowString: string; StartVar, StartAfterVar, row: integer ): string;
const
  PrefixAllowed = ', ';
var
  i: integer;
begin
//  if Pos ('<fs>tiratheater.bitmap', arowstring,1) > 0 then asm int 3 end;

  Result := MidStr (aRowString  , StartVar, StartAfterVar - StartVar);
  if StartVar = 1 then exit;

  // qui minimo StartVar è 2
  for i := StartVar -1 downto 1 do begin
    if pos ( aRowString [i], PrefixAllowed, 1 ) > 0 then begin // è permesso solo lo spazio e una virgola
      exit;
    end
    else begin // qualsiasi altro carattere invalida la variabile
      Result :='';
    end;

  end;
end;
function Tform1.ValidRightVariable ( aRowString: string; StartVar, StartAfterVar, row: integer ): string;
const
  SuffixAllowed = ',:';
var
  i: integer;
begin
  // di solito Myvariable:TSome;
  Result := MidStr (aRowString  , StartVar, StartAfterVar - StartVar );
  if StartVar = length (aRowString) then exit;

  for i := StartAfterVar to length (aRowString) do begin
    if aRowString [i] = ' ' then continue;
    if pos ( aRowString [i], SuffixAllowed, 1 ) > 0 then begin
      // se dopo i due punti c'è un uguale non è una dichiarazione  es= myvariable:=  altrimenti è la dichiarazione
      if aRowString [i+1] ='=' then begin
        Result:='';
        exit;
      end
      else
      exit;
    end
    else begin // qualsiasi altro carattere
      Result :='';
      exit;
    end;
  end;
end;
end.

Commenti

Post più popolari