{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Basic pascal code functions. Many of the functions have counterparts in the code tools, which are faster, more flexible and aware of compiler settings and directives. } unit BasicCodeTools; {$ifdef FPC}{$mode objfpc}{$endif}{$H+} interface uses Classes, SysUtils, CodeToolsStrConsts, SourceLog, KeywordFuncLists; //----------------------------------------------------------------------------- // functions / procedures // source type function FindSourceType(const Source: string; var SrcNameStart, SrcNameEnd: integer): string; // program name function RenameProgramInSource(Source:TSourceLog; const NewProgramName:string):boolean; function FindProgramNameInSource(const Source:string; var ProgramNameStart,ProgramNameEnd:integer):string; // unit name function RenameUnitInSource(Source:TSourceLog;const NewUnitName:string):boolean; function FindUnitNameInSource(const Source:string; var UnitNameStart,UnitNameEnd:integer):string; // uses sections function UnitIsUsedInSource(const Source,UnitName:string):boolean; function RenameUnitInProgramUsesSection(Source:TSourceLog; const OldUnitName, NewUnitName, NewInFile:string): boolean; function AddToProgramUsesSection(Source:TSourceLog; const AUnitName,InFileName:string):boolean; function RemoveFromProgramUsesSection(Source:TSourceLog; const AUnitName:string):boolean; function RenameUnitInInterfaceUsesSection(Source:TSourceLog; const OldUnitName, NewUnitName, NewInFile:string): boolean; function AddToInterfaceUsesSection(Source:TSourceLog; const AUnitName,InFileName:string):boolean; function RemoveFromInterfaceUsesSection(Source:TSourceLog; const AUnitName:string):boolean; // single uses section function IsUnitUsedInUsesSection(const Source,UnitName:string; UsesStart:integer):boolean; function RenameUnitInUsesSection(Source:TSourceLog; UsesStart: integer; const OldUnitName, NewUnitName, NewInFile:string): boolean; function AddUnitToUsesSection(Source:TSourceLog; const UnitName,InFilename:string; UsesStart:integer):boolean; function RemoveUnitFromUsesSection(Source:TSourceLog; const UnitName:string; UsesStart:integer):boolean; // compiler directives function FindIncludeDirective(const Source,Section:string; Index:integer; var IncludeStart,IncludeEnd:integer):boolean; function SplitCompilerDirective(const Directive:string; var DirectiveName,Parameters:string):boolean; // createform function AddCreateFormToProgram(Source:TSourceLog; const AClassName,AName:string):boolean; function RemoveCreateFormFromProgram(Source:TSourceLog; const AClassName,AName:string):boolean; function CreateFormExistsInProgram(const Source, AClassName,AName:string):boolean; function ListAllCreateFormsInProgram(const Source:string):TStrings; // resource code function FindResourceInCode(const Source, AddCode:string; var Position,EndPosition:integer):boolean; function AddResourceCode(Source:TSourceLog; const AddCode:string):boolean; // form components function FindFormClassDefinitionInSource(const Source, FormClassName:string; var FormClassNameStartPos, FormBodyStartPos: integer):boolean; function FindFormComponentInSource(const Source: string; FormBodyStartPos: integer; const ComponentName, ComponentClassName: string): integer; function AddFormComponentToSource(Source:TSourceLog; FormBodyStartPos: integer; const ComponentName, ComponentClassName: string): boolean; function RemoveFormComponentFromSource(Source:TSourceLog; FormBodyStartPos: integer; ComponentName, ComponentClassName: string): boolean; // code search function SearchCodeInSource(const Source,Find:string; StartPos:integer; var EndFoundPosition:integer; CaseSensitive:boolean):integer; function ReadNextPascalAtom(const Source:string; var Position,AtomStart:integer):string; function ReadRawNextPascalAtom(const Source:string; var Position,AtomStart:integer):string; //---------------------------------------------------------------------------- // comments function FindNextNonSpace(const ASource: string; StartPos: integer ): integer; function FindCommentEnd(const ASource: string; StartPos: integer; NestedComments: boolean): integer; function FindNextCompilerDirective(const ASource: string; StartPos: integer; NestedComments: boolean): integer; function CleanCodeFromComments(const DirtyCode: string; NestedComments: boolean): string; // line ranges and indent procedure GetLineStartEndAtPosition(const Source:string; Position:integer; var LineStart,LineEnd:integer); function GetLineIndent(const Source: string; Position: integer): integer; function GetIndentStr(Indent: integer): string; function LineEndCount(const Txt: string; var LengthOfLastLine:integer): integer; // identifiers procedure GetIdentStartEndAtPosition(const Source:string; Position:integer; var IdentStart,IdentEnd:integer); function GetIdentLen(Identifier: PChar): integer; function GetIdentifier(Identifier: PChar): string; function FindNextIdentifier(const Source: string; StartPos, MaxPos: integer ): integer; // line/code ends function FindFirstNonSpaceCharInLine(const Source: string; Position: integer): integer; function FindLineEndOrCodeInFrontOfPosition(const Source: string; Position, MinPosition: integer; NestedComments: boolean; StopAtDirectives: boolean): integer; function FindLineEndOrCodeAfterPosition(const Source: string; Position, MaxPosition: integer; NestedComments: boolean): integer; function FindFirstLineEndInFrontOfInCode(const Source: string; Position, MinPosition: integer; NestedComments: boolean): integer; function FindFirstLineEndAfterInCode(const Source: string; Position, MaxPosition: integer; NestedComments: boolean): integer; // replacements function ReplacementNeedsLineEnd(const Source: string; FromPos, ToPos, NewLength, MaxLineLength: integer): boolean; function CountNeededLineEndsToAddForward(const Src: string; StartPos, MinLineEnds: integer): integer; function CountNeededLineEndsToAddBackward(const Src: string; StartPos, MinLineEnds: integer): integer; // comparison function CompareTextIgnoringSpace(const Txt1, Txt2: string; CaseSensitive: boolean): integer; function CompareSubStrings(const Find, Txt: string; FindStartPos, TxtStartPos, Len: integer; CaseSensitive: boolean): integer; function CompareIdentifiers(Identifier1, Identifier2: PChar): integer; function ComparePrefixIdent(PrefixIdent, Identifier: PChar): boolean; // space and special chars function TrimCodeSpace(const ACode: string): string; function CodeIsOnlySpace(const ACode: string; FromPos, ToPos: integer): boolean; function StringToPascalConst(const s: string): string; // other useful stuff procedure RaiseCatchableException(const Msg: string); //----------------------------------------------------------------------------- const MaxLineLength: integer = 80; const // ToDo: find the constant in the fpc units. EndOfLine:shortstring={$IFDEF win32}#13+{$ENDIF}#10; //============================================================================= implementation var IsIDChar, // ['a'..'z','A'..'Z','0'..'9','_'] IsIDStartChar, // ['a'..'z','A'..'Z','_'] IsSpaceChar : array[char] of boolean; { most simple code tools - just methods } function FindIncludeDirective(const Source,Section:string; Index:integer; var IncludeStart,IncludeEnd:integer):boolean; var Atom,DirectiveName:string; Position,EndPos,AtomStart:integer; Filename:string; begin Result:=false; // find section Position:=SearchCodeInSource(Source,Section,1,EndPos,false); if Position<1 then exit; // search for include directives repeat Atom:=ReadNextPascalAtom(Source,Position,AtomStart); if (copy(Atom,1,2)='{$') or (copy(Atom,1,3)='(*$') then begin SplitCompilerDirective(Atom,DirectiveName,Filename); DirectiveName:=lowercase(DirectiveName); if (DirectiveName='i') or (DirectiveName='include') then begin // include directive dec(Index); if Index=0 then begin IncludeStart:=AtomStart; IncludeEnd:=Position; Result:=true; exit; end; end; end; until Atom=''; end; function SplitCompilerDirective(const Directive:string; var DirectiveName,Parameters:string):boolean; var EndPos,DirStart,DirEnd:integer; begin if (copy(Directive,1,2)='{$') or (copy(Directive,1,3)='(*$') then begin if copy(Directive,1,2)='{$' then begin DirStart:=3; DirEnd:=length(Directive); end else begin DirStart:=4; DirEnd:=length(Directive)-1; end; EndPos:=DirStart; while (EndPos'' then ReadNextPascalAtom(Source,SrcNameEnd,SrcNameStart); end; function RenameUnitInSource(Source:TSourceLog;const NewUnitName:string):boolean; var UnitNameStart,UnitNameEnd:integer; begin UnitNameStart:=0; UnitNameEnd:=0; Result:=(FindUnitNameInSource(Source.Source,UnitNameStart,UnitNameEnd)<>''); if Result then Source.Replace(UnitNameStart,UnitNameEnd-UnitNameStart,NewUnitName); end; function FindUnitNameInSource(const Source:string; var UnitNameStart,UnitNameEnd:integer):string; begin if uppercasestr(FindSourceType(Source,UnitNameStart,UnitNameEnd))='UNIT' then Result:=copy(Source,UnitNameStart,UnitNameEnd-UnitNameStart) else Result:=''; end; function RenameProgramInSource(Source: TSourceLog; const NewProgramName:string):boolean; var ProgramNameStart,ProgramNameEnd:integer; begin Result:=(FindProgramNameInSource(Source.Source, ProgramNameStart,ProgramNameEnd)<>''); if Result then Source.Replace(ProgramNameStart, ProgramNameEnd-ProgramNameStart,NewProgramName) end; function FindProgramNameInSource(const Source:string; var ProgramNameStart,ProgramNameEnd:integer):string; begin if uppercasestr(FindSourceType(Source,ProgramNameStart,ProgramNameEnd))= 'PROGRAM' then Result:=copy(Source,ProgramNameStart,ProgramNameEnd-ProgramNameStart) else Result:=''; end; function UnitIsUsedInSource(const Source,UnitName:string):boolean; // search in all uses sections var UsesStart,UsesEnd:integer; begin Result:=false; repeat UsesStart:=SearchCodeInSource(Source,'uses',1,UsesEnd,false); if UsesStart>0 then begin if IsUnitUsedInUsesSection(Source,UnitName,UsesStart) then begin Result:=true; exit; end; end; until UsesStart<1; end; function RenameUnitInProgramUsesSection(Source:TSourceLog; const OldUnitName, NewUnitName, NewInFile:string): boolean; var ProgramTermStart,ProgramTermEnd, UsesStart,UsesEnd:integer; begin Result:=false; // search Program section ProgramTermStart:=SearchCodeInSource(Source.Source,'program',1,ProgramTermEnd ,false); if ProgramTermStart<1 then exit; // search programname ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart); // search semicolon after programname if not (ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart)=';') then exit; UsesEnd:=ProgramTermEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then begin // no uses section in interface -> add one Source.Insert(ProgramTermEnd,EndOfLine+EndOfLine+'uses'+EndOfLine+' ;'); UsesEnd:=ProgramTermEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); end; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then exit; Result:=RenameUnitInUsesSection(Source,UsesStart,OldUnitName ,NewUnitName,NewInFile); end; function AddToProgramUsesSection(Source:TSourceLog; const AUnitName,InFileName:string):boolean; var ProgramTermStart,ProgramTermEnd, UsesStart,UsesEnd:integer; begin Result:=false; if (AUnitName='') or (AUnitName=';') then exit; // search program ProgramTermStart:=SearchCodeInSource(Source.Source,'program',1,ProgramTermEnd ,false); if ProgramTermStart<1 then exit; // search programname ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart); // search semicolon after programname if not (ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart)=';') then exit; // search uses section UsesEnd:=ProgramTermEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then begin // no uses section after program term -> add one Source.Insert(ProgramTermEnd,EndOfline+EndOfline+'uses'+EndOfline+' ;'); UsesEnd:=ProgramTermEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); end; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then exit; Result:=AddUnitToUsesSection(Source,AUnitName,InFileName,UsesStart); end; function RenameUnitInInterfaceUsesSection(Source:TSourceLog; const OldUnitName, NewUnitName, NewInFile:string): boolean; var InterfaceStart,InterfaceWordEnd, UsesStart,UsesEnd:integer; begin Result:=false; // search interface section InterfaceStart:=SearchCodeInSource(Source.Source,'interface',1 ,InterfaceWordEnd,false); if InterfaceStart<1 then exit; UsesEnd:=InterfaceWordEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then begin // no uses section in interface -> add one Source.Insert(InterfaceWordEnd,EndOfLine+EndOfLine+'uses'+EndOfLine+' ;'); UsesEnd:=InterfaceWordEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); end; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then exit; Result:=RenameUnitInUsesSection(Source,UsesStart,OldUnitName ,NewUnitName,NewInFile); end; function AddToInterfaceUsesSection(Source:TSourceLog; const AUnitName,InFileName:string):boolean; var InterfaceStart,InterfaceWordEnd, UsesStart,UsesEnd:integer; begin Result:=false; if AUnitName='' then exit; // search interface section InterfaceStart:=SearchCodeInSource(Source.Source,'interface',1 ,InterfaceWordEnd,false); if InterfaceStart<1 then exit; UsesEnd:=InterfaceWordEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then begin // no uses section in interface -> add one Source.Insert(InterfaceWordEnd,EndOfLine+EndOfLine+'uses'+EndOfLine+' ;'); UsesEnd:=InterfaceWordEnd; ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); end; if not (lowercase(copy(Source.Source,UsesStart,UsesEnd-UsesStart))='uses') then exit; Result:=AddUnitToUsesSection(Source,AUnitName,InFileName,UsesStart); end; function RemoveFromProgramUsesSection(Source:TSourceLog; const AUnitName:string):boolean; var ProgramTermStart,ProgramTermEnd, UsesStart,UsesEnd:integer; Atom:string; begin Result:=false; if AUnitName='' then exit; // search program ProgramTermStart:=SearchCodeInSource(Source.Source,'program',1 ,ProgramTermEnd,false); if ProgramtermStart<1 then exit; // search programname ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart); // search semicolon after programname if not (ReadNextPascalAtom(Source.Source,ProgramTermEnd,ProgramTermStart)=';') then exit; UsesEnd:=ProgramTermEnd; Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(Atom)='uses') then exit; Result:=RemoveUnitFromUsesSection(Source,AUnitName,UsesStart); end; function RemoveFromInterfaceUsesSection(Source:TSourceLog; const AUnitName:string):boolean; var InterfaceStart,InterfaceWordEnd, UsesStart,UsesEnd:integer; Atom:string; begin Result:=false; if AUnitName='' then exit; // search interface section InterfaceStart:=SearchCodeInSource(Source.Source,'interface',1 ,InterfaceWordEnd,false); if InterfaceStart<1 then exit; UsesEnd:=InterfaceWordEnd; Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if UsesEnd>length(Source.Source) then exit; if not (lowercase(Atom)='uses') then exit; Result:=RemoveUnitFromUsesSection(Source,AUnitName,UsesStart); end; function IsUnitUsedInUsesSection(const Source,UnitName:string; UsesStart:integer):boolean; var UsesEnd:integer; Atom:string; begin Result:=false; if UnitName='' then exit; if UsesStart<1 then exit; if not (lowercase(copy(Source,UsesStart,4))='uses') then exit; UsesEnd:=UsesStart+4; // parse through all used units and see if it is there repeat Atom:=ReadNextPascalAtom(Source,UsesEnd,UsesStart); if (lowercase(Atom)=lowercase(UnitName)) then begin // unit found Result:=true; exit; end; // read til next comma or semicolon repeat Atom:=ReadNextPascalAtom(Source,UsesEnd,UsesStart); until (Atom=',') or (Atom=';') or (Atom=''); until Atom<>','; // unit not used Result:=true; end; function RenameUnitInUsesSection(Source:TSourceLog; UsesStart: integer; const OldUnitName, NewUnitName, NewInFile:string): boolean; var UsesEnd:integer; LineStart,LineEnd,OldUsesStart:integer; s,Atom,NewUnitTerm:string; begin Result:=false; if (OldUnitName='') then begin Result:=AddUnitToUsesSection(Source,NewUnitName,NewInFile,UsesStart); exit; end; if (NewUnitName='') or (NewUnitName=';') or (OldUnitName=';') or (UsesStart<1) then exit; UsesEnd:=UsesStart+4; if not (lowercase(copy(Source.Source,UsesStart,4))='uses') then exit; // parse through all used units and see if it is already there if NewInFile<>'' then NewUnitTerm:=NewUnitName+' in '''+NewInFile+'''' else NewUnitTerm:=NewUnitName; s:=', '; repeat Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if (lowercase(Atom)=lowercase(OldUnitName)) then begin // unit already used OldUsesStart:=UsesStart; // find comma or semicolon repeat Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); until (Atom=',') or (Atom=';') or (Atom=''); Source.Replace(OldUsesStart,UsesStart-OldUsesStart,NewUnitTerm); Result:=true; exit; end else if (Atom=';') then begin s:=' '; break; end; // read til next comma or semicolon while (Atom<>',') and (Atom<>';') and (Atom<>'') do Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); until Atom<>','; // unit not used yet -> add it Source.Insert(UsesStart,s+NewUnitTerm); GetLineStartEndAtPosition(Source.Source,UsesStart,LineStart,LineEnd); if (LineEnd-LineStart>MaxLineLength) or (NewInFile<>'') then Source.Insert(UsesStart,EndOfLine+' '); Result:=true; end; function AddUnitToUsesSection(Source:TSourceLog; const UnitName,InFilename:string; UsesStart:integer):boolean; var UsesEnd:integer; LineStart,LineEnd:integer; s,Atom,NewUnitTerm:string; begin Result:=false; if (UnitName='') or (UnitName=';') or (UsesStart<1) then exit; UsesEnd:=UsesStart+4; if not (lowercase(copy(Source.Source,UsesStart,4))='uses') then exit; // parse through all used units and see if it is already there s:=', '; repeat Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if (lowercase(Atom)=lowercase(UnitName)) then begin // unit found Result:=true; exit; end else if (Atom=';') then begin s:=' '; break; end; // read til next comma or semicolon while (Atom<>',') and (Atom<>';') and (Atom<>'') do Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); until Atom<>','; // unit not used yet -> add it if InFilename<>'' then NewUnitTerm:=UnitName+' in '''+InFileName+'''' else NewUnitTerm:=UnitName; Source.Insert(UsesStart,s+NewUnitTerm); GetLineStartEndAtPosition(Source.Source,UsesStart,LineStart,LineEnd); if (LineEnd-LineStart>MaxLineLength) or (InFileName<>'') then Source.Insert(UsesStart,EndOfLine+' '); Result:=true; end; function RemoveUnitFromUsesSection(Source:TSourceLog; const UnitName:string; UsesStart:integer):boolean; var UsesEnd,OldUsesStart,OldUsesEnd:integer; Atom:string; begin Result:=false; if (UsesStart<1) or (UnitName='') or (UnitName=',') or (UnitName=';') then exit; // search interface section UsesEnd:=UsesStart+4; if not (lowercase(copy(Source.Source,UsesStart,4))='uses') then exit; // parse through all used units and see if it is there OldUsesEnd:=-1; repeat Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); if (lowercase(Atom)=lowercase(UnitName)) then begin // unit found OldUsesStart:=UsesStart; // find comma or semicolon repeat Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); until (Atom=',') or (Atom=';') or (Atom=''); if OldUsesEnd<1 then // first used unit Source.Delete(OldUsesStart,UsesStart-OldUsesStart) else // not first used unit (remove comma in front of unitname too) Source.Delete(OldUsesEnd,UsesStart-OldUsesEnd); Result:=true; exit; end else OldUsesEnd:=UsesEnd; // read til next comma or semicolon while (Atom<>',') and (Atom<>';') and (Atom<>'') do Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart); until Atom<>','; // unit not used end; function AddCreateFormToProgram(Source:TSourceLog; const AClassName,AName:string):boolean; // insert 'Application.CreateForm(,);' // in front of 'Application.Run;' var Position, EndPosition: integer; begin Result:=false; Position:=SearchCodeInSource(Source.Source,'application.run',1 ,EndPosition,false); if Position<1 then exit; Source.Insert(Position, 'Application.CreateForm('+AClassName+','+AName+');'+EndOfLine+' '); Result:=true; end; function RemoveCreateFormFromProgram(Source:TSourceLog; const AClassName,AName:string):boolean; // remove 'Application.CreateForm(,);' var Position,EndPosition,AtomStart:integer; begin Result:=false; Position:=SearchCodeInSource(Source.Source, 'application.createform('+AClassName+','+AName+')',1,EndPosition,false); if Position<1 then exit; if ReadNextPascalAtom(Source.Source,EndPosition,AtomStart)=';' then ReadNextPascalAtom(Source.Source,EndPosition,AtomStart); EndPosition:=AtomStart; Source.Delete(Position,EndPosition-Position); Result:=true; end; function CreateFormExistsInProgram(const Source, AClassName,AName:string):boolean; var Position,EndPosition:integer; begin Position:=SearchCodeInSource(Source, 'application.createform('+AClassName+','+AName+')',1,EndPosition,false); Result:=Position>0; end; function ListAllCreateFormsInProgram(const Source:string):TStrings; // list format: : var Position,EndPosition:integer; s:string; begin Result:=TStringList.Create; Position:=1; repeat Position:=SearchCodeInSource(Source, 'application.createform(',Position,EndPosition,false); if Position>0 then begin s:=ReadNextPascalAtom(Source,EndPosition,Position); ReadNextPascalAtom(Source,EndPosition,Position); s:=ReadNextPascalAtom(Source,EndPosition,Position)+':'+s; Result.Add(s); end; until Position<1; end; function FindResourceInCode(const Source, AddCode:string; var Position,EndPosition:integer):boolean; var Find,Atom:string; FindPosition,FindAtomStart,SemicolonPos:integer; begin Result:=false; if AddCode='' then begin Result:=true; exit; end; if Source='' then exit; // search "LazarusResources.Add(''," FindPosition:=1; repeat Atom:=ReadNextPascalAtom(AddCode,FindPosition,FindAtomStart); until (Atom='') or (Atom=','); if Atom='' then exit; // search the resource start in code Find:=copy(AddCode,1,FindPosition-1); Position:=SearchCodeInSource(Source,Find,1,EndPosition,false); if Position<1 then exit; // search resource end in code SemicolonPos:=SearchCodeInSource(Source,');',EndPosition,EndPosition,false); if SemicolonPos<1 then exit; Result:=true; end; function AddResourceCode(Source:TSourceLog; const AddCode:string):boolean; var StartPos,EndPos:integer; begin if FindResourceInCode(Source.Source,AddCode,StartPos,EndPos) then begin // resource exists already -> replace it Source.Replace(StartPos,EndPos-StartPos,AddCode); end else begin // add resource Source.Insert(length(Source.Source)+1,EndOfLine+AddCode); end; Result:=true; end; function FindFormClassDefinitionInSource(const Source, FormClassName:string; var FormClassNameStartPos, FormBodyStartPos: integer):boolean; var AtomEnd,AtomStart: integer; begin Result:=false; if FormClassName='' then exit; repeat FormClassNameStartPos:=SearchCodeInSource(Source, FormClassName+'=class(TForm)',1,FormBodyStartPos,false); if FormClassNameStartPos<1 then exit; AtomEnd:=FormBodyStartPos; until ReadNextPascalAtom(Source,AtomEnd,AtomStart)<>';'; Result:=true; end; function FindFormComponentInSource(const Source: string; FormBodyStartPos: integer; const ComponentName, ComponentClassName: string): integer; var AtomStart, OldPos: integer; Atom,LowComponentName,LowComponentClassName: string; begin LowComponentName:=lowercase(ComponentName); LowComponentClassName:=lowercase(ComponentClassName); Result:=FormBodyStartPos; repeat Atom:=lowercase(ReadNextPascalAtom(Source,Result,AtomStart)); if (Atom='public') or (Atom='private') or (Atom='end') or (Atom='protected') or (Atom='') then begin Result:=-1; exit; end; OldPos:=Result; if (lowercase(ReadNextPascalAtom(Source,Result,AtomStart))=LowComponentName) and (ReadNextPascalAtom(Source,Result,AtomStart)=':') and (lowercase(ReadNextPascalAtom(Source,Result,AtomStart))= LowComponentClassName) and (ReadNextPascalAtom(Source,Result,AtomStart)=';') then begin Result:=OldPos; exit; end; until Result>length(Source); Result:=-1; end; function AddFormComponentToSource(Source:TSourceLog; FormBodyStartPos: integer; const ComponentName, ComponentClassName: string): boolean; var Position, AtomStart: integer; Atom: string; PriorSpaces, NextSpaces: string; begin Result:=false; if FindFormComponentInSource(Source.Source,FormBodyStartPos ,ComponentName,ComponentClassName)>0 then begin Result:=true; exit; end; Position:=FormBodyStartPos; repeat // find a good position to insert the component // in front of next section and in front of procedures/functions Atom:=lowercase(ReadNextPascalAtom(Source.SOurce,Position,AtomStart)); if (Atom='procedure') or (Atom='function') or (Atom='end') or (Atom='class') or (Atom='constructor') or (Atom='destructor') or (Atom='public') or (Atom='private') or (Atom='protected') or (Atom='published') or (Atom='class') or (Atom='property') then begin // insert component definition in source if (Atom='public') or (Atom='private') or (Atom='protected') or (Atom='published') then begin PriorSpaces:=' '; NextSpaces:=' '; end else begin PriorSpaces:=''; NextSpaces:=' '; end; Source.Insert(AtomStart, PriorSpaces+ComponentName+': '+ComponentClassName+';'+EndOfLine +NextSpaces); Result:=true; exit; end; until Position>length(Source.Source); Result:=false; end; function RemoveFormComponentFromSource(Source:TSourceLog; FormBodyStartPos: integer; ComponentName, ComponentClassName: string): boolean; var AtomStart, Position, ComponentStart, LineStart, LineEnd: integer; Atom: string; begin ComponentName:=lowercase(ComponentName); ComponentClassName:=lowercase(ComponentClassName); Position:=FormBodyStartPos; repeat Atom:=lowercase(ReadNextPascalAtom(Source.Source,Position,AtomStart)); if (Atom='public') or (Atom='private') or (Atom='end') or (Atom='protected') or (Atom='') then begin Result:=false; exit; end; if (Atom=ComponentName) then begin ComponentStart:=AtomStart; if (ReadNextPascalAtom(Source.Source,Position,AtomStart)=':') and (lowercase(ReadNextPascalAtom(Source.Source,Position,AtomStart))= ComponentClassName) then begin GetLineStartEndAtPosition(Source.Source,ComponentStart,LineStart,LineEnd); if (LineEnd<=length(Source.Source)) and (Source.Source[LineEnd] in [#10,#13]) then begin inc(LineEnd); if (LineEnd<=length(Source.Source)) and (Source.Source[LineEnd] in [#10,#13]) and (Source.Source[LineEnd]<>Source.Source[LineEnd-1]) then inc(LineEnd); end; Source.Delete(LineStart,LineEnd-LineStart); Result:=true; exit; end; end; until Atom=''; Result:=true; end; function ReadNextPascalAtomEx(const Source : string;var Position,EndPosition : integer;CaseSensitive : boolean; var Atom : string):boolean; begin Atom := ReadNextPascalAtom(Source,Position,EndPosition); if not(CaseSensitive) then Atom := lowerCase(Atom); Result := (Position > length(Source)); end; // search pascal atoms of Find in Source function SearchCodeInSource(const Source,Find:string; StartPos:integer; var EndFoundPosition:integer; CaseSensitive:boolean):integer; var FindAtomStart : integer; FindPos : integer; Position : integer; AtomStart : integer; FirstSrcAtomStart : integer; CompareSrcPosition: integer; FindAtom : string ; SrcAtom : string; HasFound : boolean; FirstFindAtom : string; FirstFindPos : integer; begin Result:=-1; if (Find='') or (StartPos>length(Source)) then exit; Position:=StartPos; FirstFindPos:=1; {search first atom in find} if ReadNextPascalAtomEx(Find,FirstFindPos,FindAtomStart,CaseSensitive,FirstFindAtom) then exit; repeat if ReadNextPascalAtomEx(Source,Position,AtomStart,CaseSensitive,SrcAtom) then break; if SrcAtom=FirstFindAtom then begin {first atom found} FirstSrcAtomStart := AtomStart; CompareSrcPosition := Position; FindPos := FirstFindPos; {read next source and find atoms and compare} repeat if ReadNextPascalAtomEx(Find,FindPos,FindAtomStart,CaseSensitive,FindAtom) then break; if ReadNextPascalAtomEx(Source,CompareSrcPosition,AtomStart,CaseSensitive,SrcAtom) then break; HasFound := SrcAtom = FindAtom; if HasFound then begin Result := FirstSrcAtomStart; EndFoundPosition := CompareSrcPosition; exit; end; until not(HasFound); end; until false; end; function FindNextCompilerDirective(const ASource: string; StartPos: integer; NestedComments: boolean): integer; var MaxPos: integer; begin MaxPos:=length(ASource); Result:=StartPos; while (Result<=MaxPos) do begin case ASource[Result] of '''': begin inc(Result); while (Result<=MaxPos) do begin if (ASource[Result]<>'''') then inc(Result) else begin inc(Result); break; end; end; end; '/': begin inc(Result); if (Result<=MaxPos) and (ASource[Result]='/') then begin // skip Delphi comment while (Result<=MaxPos) and (not (ASource[Result] in [#10,#13])) do inc(Result); end; end; '{': begin if (ResultMaxPos+1 then Result:=MaxPos+1; end; function FindNextNonSpace(const ASource: string; StartPos: integer ): integer; var SrcLen: integer; begin SrcLen:=length(ASource); Result:=StartPos; while (Result<=SrcLen) and (ASource[Result] in [' ',#9,#10,#13]) do inc(Result); end; function FindCommentEnd(const ASource: string; StartPos: integer; NestedComments: boolean): integer; var MaxPos, CommentLvl: integer; begin MaxPos:=length(ASource); Result:=StartPos; if Result>MaxPos then exit; case ASource[Result] of '/': begin if (Result0) and (not (Source[LineStart] in [#10,#13])) do dec(LineStart); inc(LineStart); LineEnd:=Position; while (LineEnd<=length(Source)) and (not (Source[LineEnd] in [#10,#13])) do inc(LineEnd); end; procedure GetIdentStartEndAtPosition(const Source: string; Position: integer; var IdentStart, IdentEnd: integer); begin IdentStart:=Position; IdentEnd:=Position; if (Position<1) or (Position>length(Source)) then exit; while (IdentStart>1) and (IsIdChar[Source[IdentStart-1]]) do dec(IdentStart); while (IdentEnd<=length(Source)) and (IsIdChar[Source[IdentEnd]]) do inc(IdentEnd); end; function GetIdentLen(Identifier: PChar): integer; begin Result:=0; if Identifier=nil then exit; while (IsIDChar[Identifier[Result]]) do inc(Result); end; function ReadNextPascalAtom(const Source:string; var Position,AtomStart:integer):string; var DirectiveName:string; DirStart,DirEnd,EndPos:integer; begin repeat Result:=ReadRawNextPascalAtom(Source,Position,AtomStart); if (copy(Result,1,2)='{$') or (copy(Result,1,3)='(*$') then begin if copy(Result,1,2)='{$' then begin DirStart:=3; DirEnd:=length(Result); end else begin DirStart:=4; DirEnd:=length(Result)-1; end; EndPos:=DirStart; while (EndPos'}') do inc(Position); inc(Position); end; end; '/': // comment or real division if (Position read til line end inc(Position); while (Position<=Len) and (not (Source[Position] in [#10,#13])) do inc(Position); end else break; '(': // comment, bracket or compiler directive if (Position read til comment end inc(Position,2); while (Position'*') or (Source[Position]<>')')) do inc(Position); inc(Position,2); end; end else // round bracket open break; else break; end; end; // read atom AtomStart:=Position; if Position<=Len then begin c1:=Source[Position]; if IsIDStartChar[c1] then begin // identifier inc(Position); while (Position<=Len) and (IsIDChar[Source[Position]]) do inc(Position); end else begin case c1 of '0'..'9': // number begin inc(Position); // read numbers while (Position<=Len) and (Source[Position] in ['0'..'9']) do inc(Position); if (Position'.') then begin // real type number inc(Position); while (Position<=Len) and (Source[Position] in ['0'..'9']) do inc(Position); if (Position<=Len) and (Source[Position] in ['e','E']) then begin // read exponent inc(Position); if (Position<=Len) and (Source[Position]='-') then inc(Position); while (Position<=Len) and (Source[Position] in ['0'..'9']) do inc(Position); end; end; end; '''','#': // string constant begin while (Position<=Len) do begin case (Source[Position]) of '#': begin inc(Position); while (Position<=Len) and (Source[Position] in ['0'..'9']) do inc(Position); end; '''': begin inc(Position); while (Position<=Len) and (Source[Position]<>'''') do inc(Position); inc(Position); end; else break; end; end; end; '$': // hex constant begin inc(Position); while (Position<=Len) and (Source[Position] in ['0'..'9','A'..'F','a'..'f']) do inc(Position); end; '{': // compiler directive begin inc(Position); while (Position<=Len) and (Source[Position]<>'}') do inc(Position); inc(Position); end; '(': // bracket or compiler directive if (Position read til comment end inc(Position,2); while (Position'*') or (Source[Position]<>')')) do inc(Position); inc(Position,2); end else // round bracket open inc(Position); else inc(Position); if Position<=Len then begin c2:=Source[Position]; // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, ** if ((c2='=') and (c1 in [':','+','-','/','*','<','>'])) or ((c1='<') and (c2='>')) or ((c1='.') and (c2='.')) or ((c1='*') and (c2='*')) then inc(Position); end; end; end; end; Result:=copy(Source,AtomStart,Position-AtomStart); end; function LineEndCount(const Txt: string; var LengthOfLastLine: integer): integer; var i, LastLineEndPos: integer; begin i:=1; LastLineEndPos:=0; Result:=0; while iTxt[i]) then inc(i); LastLineEndPos:=i; end else inc(i); end; LengthOfLastLine:=length(Txt)-LastLineEndPos; end; function FindFirstNonSpaceCharInLine(const Source: string; Position: integer): integer; begin Result:=Position; if (Result<0) then Result:=1; if (Result>length(Source)) then Result:=length(Source); if Result=0 then exit; // search beginning of line while (Result>1) and (not (Source[Result] in [#10,#13])) do dec(Result); // search while (Resultlength(Source)+1) then LineStart:=length(Source)+1; // search beginning of line repeat dec(LineStart); until (LineStart<1) or (Source[LineStart] in [#10,#13]); inc(LineStart); // search code Result:=LineStart; while (Result'}') do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P); end; '(': begin inc(P); if (P<=SrcLen) and (Source[P]='*') then begin inc(P); while (P<=SrcLen-1) and ((Source[P]<>'*') or (Source[P-1]<>')')) do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P,2); end; end; '/': begin inc(P); if (P<=SrcLen) and (Source[P]='/') then begin inc(P); while (P<=SrcLen) and (not (Source[P] in [#10,#13])) do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; end; end; end; end; begin SrcLen:=length(Source); if SrcLen>MaxPosition then SrcLen:=MaxPosition; Result:=Position; if Result=0 then exit; while (Result<=SrcLen) do begin case Source[Result] of '{','(','/': ReadComment(Result); #10,#13: exit; #9,' ',';': inc(Result); else exit; end; end; end; function FindLineEndOrCodeInFrontOfPosition(const Source: string; Position, MinPosition: integer; NestedComments: boolean; StopAtDirectives: boolean): integer; { search backward for a line end or code ignore line ends in comments or at the end of comment lines (comment lines are lines without code and at least one comment) Result is Position of Start of Line End examples: Position points at char 'a' 1: | 2: a:=1; 1: b:=1; | 2: // comment 3: // comment 4: a:=1; 1: | 2: /* */ 3: a:=1; 1: end;| /* 2: */ a:=1; 1: b:=1; // comment | 2: a:=1; 1: b:=1; /* 2: comment */ | 3: a:=1; } var SrcStart: integer; function ReadComment(var P: integer): boolean; // false if compiler directive var OldP: integer; begin OldP:=P; case Source[P] of '}': begin dec(P); while (P>=SrcStart) and (Source[P]<>'{') do begin if NestedComments and (Source[P] in ['}',')']) then ReadComment(P) else dec(P); end; Result:=not (StopAtDirectives and (P>=SrcStart) and (Source[P+1]='$')); dec(P); end; ')': begin dec(P); if (P>=SrcStart) and (Source[P]='*') then begin dec(P); while (P>SrcStart) and ((Source[P-1]<>'(') or (Source[P]<>'*')) do begin if NestedComments and (Source[P] in ['}',')']) then ReadComment(P) else dec(P); end; Result:=not (StopAtDirectives and (P>=SrcStart) and (Source[P+1]='$')); dec(P,2); end else Result:=true; end; else Result:=true; end; if not Result then P:=OldP+1; end; var TestPos: integer; OnlySpace: boolean; begin SrcStart:=MinPosition; if SrcStart<1 then SrcStart:=1; if Position<=SrcStart then begin Result:=SrcStart; exit; end; Result:=Position-1; if Result>length(Source) then Result:=length(Source); while (Result>=SrcStart) do begin case Source[Result] of '}',')': if not ReadComment(Result) then exit; #10,#13: begin // line end in code found if (Result>SrcStart) and (Source[Result-1] in [#10,#13]) and (Source[Result]<>Source[Result-1]) then dec(Result); // test if it is a comment line (a line without code and at least one // comment) TestPos:=Result-1; OnlySpace:=true; while (TestPos>SrcStart) do begin if (Source[TestPos]='/') and (Source[TestPos-1]='/') then begin // this is a comment line end -> search further dec(TestPos); break; end else if Source[TestPos] in [#10,#13] then begin // no comment, the line end ist really there :) exit; end else if OnlySpace and ((Source[TestPos]='}') or ((Source[TestPos]=')') and (Source[TestPos-1]='*'))) then begin // this is a comment line end -> search further break; end else begin if (Source[Result]>' ') then OnlySpace:=false; dec(TestPos); end; end; Result:=TestPos; end; ' ',';',',': dec(Result); else // code found inc(Result); exit; end; end; if Result'}') do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P); end; '(': begin inc(P); if (P<=SrcLen) and (Source[P]='*') then begin inc(P); while (P<=SrcLen-1) and ((Source[P]<>'*') or (Source[P-1]<>')')) do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P,2); end; end; '/': begin inc(P); if (P<=SrcLen) and (Source[P]='/') then begin inc(P); while (P<=SrcLen) and (not (Source[P] in [#10,#13])) do begin if NestedComments and (Source[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; end; end; end; end; begin SrcLen:=length(Source); if SrcLen>MaxPosition then SrcLen:=MaxPosition; Result:=Position; while (Result<=SrcLen) do begin case Source[Result] of '{','(','/': ReadComment(Result); #10,#13: exit; else inc(Result); end; end; end; function FindFirstLineEndInFrontOfInCode(const Source: string; Position, MinPosition: integer; NestedComments: boolean): integer; { search backward for a line end ignore line ends in comments Result will be at the Start of the Line End } var SrcStart: integer; procedure ReadComment(var P: integer); begin case Source[P] of '}': begin dec(P); while (P>=SrcStart) and (Source[P]<>'{') do begin if NestedComments and (Source[P] in ['}',')']) then ReadComment(P) else dec(P); end; dec(P); end; ')': begin dec(P); if (P>=SrcStart) and (Source[P]='*') then begin dec(P); while (P>SrcStart) and ((Source[P-1]<>'(') or (Source[P]<>'*')) do begin if NestedComments and (Source[P] in ['}',')']) then ReadComment(P) else dec(P); end; dec(P,2); end; end; end; end; var TestPos: integer; begin Result:=Position; SrcStart:=MinPosition; if SrcStart<1 then SrcStart:=1; while (Result>=SrcStart) do begin case Source[Result] of '}',')': ReadComment(Result); #10,#13: begin // test if it is a '//' comment if (Result>SrcStart) and (Source[Result-1] in [#10,#13]) and (Source[Result]<>Source[Result-1]) then dec(Result); TestPos:=Result-1; while (TestPos>SrcStart) do begin if (Source[TestPos]='/') and (Source[TestPos-1]='/') then begin // this is a comment line end -> search further break; end else if Source[TestPos] in [#10,#13] then begin // no comment, the line end ist really there :) exit; end else dec(TestPos); end; Result:=TestPos; end; else dec(Result); end; end; end; function ReplacementNeedsLineEnd(const Source: string; FromPos, ToPos, NewLength, MaxLineLength: integer): boolean; // test if old text contains a line end // or if new line is too long var LineStart, LineEnd: integer; begin GetLineStartEndAtPosition(Source,FromPos,LineStart,LineEnd); Result:=((LineEnd>=FromPos) and (LineEndMaxLineLength); end; function CompareTextIgnoringSpace(const Txt1, Txt2: string; CaseSensitive: boolean): integer; { Txt1 Txt2 Result A A 0 A B 1 A AB 1 A; A -1 } var P1, P2, Len1, Len2: integer; InIdentifier: boolean; begin P1:=1; P2:=1; Len1:=length(Txt1); Len2:=length(Txt2); InIdentifier:=false; while (P1<=Len1) and (P2<=Len2) do begin if (CaseSensitive and (Txt1[P1]=Txt2[P2])) or ((not CaseSensitive) and (UpChars[Txt1[P1]]=UpChars[Txt2[P2]])) then begin inc(P1); inc(P2); end else begin // different chars found if InIdentifier and (IsIDChar[Txt1[P1]] xor IsIDChar[Txt2[P2]]) then begin // one identifier is longer than the other if IsIDChar[Txt1[P1]] then // identifier in Txt1 is longer than in Txt2 Result:=-1 else // identifier in Txt2 is longer than in Txt1 Result:=+1; exit; end else if (ord(Txt1[P1])<=ord(' ')) then begin // ignore/skip spaces in Txt1 repeat inc(P1); until (P1>Len1) or (ord(Txt1[P1])>ord(' ')); end else if (ord(Txt2[P2])<=ord(' ')) then begin // ignore/skip spaces in Txt2 repeat inc(P2); until (P2>Len2) or (ord(Txt2[P2])>ord(' ')); end else begin // Txt1<>Txt2 if (CaseSensitive and (Txt1[P1]>Txt2[P2])) or ((not CaseSensitive) and (UpChars[Txt1[P1]]>UpChars[Txt2[P2]])) then Result:=-1 else Result:=+1; exit; end; end; InIdentifier:=IsIDChar[Txt1[P1]]; end; // one text was totally read -> check the rest of the other one // skip spaces while (P1<=Len1) and (ord(Txt1[P1])<=ord(' ')) do inc(P1); while (P2<=Len2) and (ord(Txt2[P2])<=ord(' ')) do inc(P2); if (P1>Len1) then begin // rest of P1 was only space if (P2>Len2) then // rest of P2 was only space Result:=0 else // there is some text at the end of P2 Result:=1; end else begin // there is some text at the end of P1 Result:=-1 end; end; function CompareSubStrings(const Find, Txt: string; FindStartPos, TxtStartPos, Len: integer; CaseSensitive: boolean): integer; var FindLen, TxtLen: integer; begin FindLen:=length(Find); TxtLen:=length(Txt); if CaseSensitive then begin while (FindStartPos<=FindLen) and (TxtStartPos<=TxtLen) and (Len>0) do begin if Find[FindStartPos]=Txt[TxtStartPos] then begin inc(FindStartPos); inc(TxtStartPos); dec(Len); end else begin if Find[FindStartPos]>Txt[TxtStartPos] then Result:=1 else Result:=-1; exit; end; end; end else begin while (FindStartPos<=FindLen) and (TxtStartPos<=TxtLen) and (Len>0) do begin if UpChars[Find[FindStartPos]]=UpChars[Txt[TxtStartPos]] then begin inc(FindStartPos); inc(TxtStartPos); dec(Len); end else begin if UpChars[Find[FindStartPos]]>UpChars[Txt[TxtStartPos]] then Result:=1 else Result:=-1; exit; end; end; end; if Len=0 then Result:=0 else if FindStartPos>FindLen then Result:=1 else Result:=-1; end; function CleanCodeFromComments(const DirtyCode: string; NestedComments: boolean): string; var DirtyPos, CleanPos, DirtyLen: integer; c: char; procedure ReadComment(var P: integer); begin case DirtyCode[P] of '{': begin inc(P); while (P<=DirtyLen) and (DirtyCode[P]<>'}') do begin if NestedComments and (DirtyCode[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P); end; '(': begin inc(P); if (P<=DirtyLen) and (DirtyCode[P]='*') then begin inc(P); while (P<=DirtyLen-1) and ((DirtyCode[P]<>'*') or (DirtyCode[P-1]<>')')) do begin if NestedComments and (DirtyCode[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; inc(P,2); end; end; '/': begin inc(P); if (P<=DirtyLen) and (DirtyCode[P]='/') then begin inc(P); while (P<=DirtyLen) and (not (DirtyCode[P] in [#10,#13])) do begin if NestedComments and (DirtyCode[P] in ['{','(','/']) then ReadComment(P) else inc(P); end; end; end; end; end; begin DirtyLen:=length(DirtyCode); SetLength(Result,DirtyLen); DirtyPos:=1; CleanPos:=1; while (DirtyPos<=DirtyLen) do begin c:=DirtyCode[DirtyPos]; if not (c in ['/','{','(']) then begin Result[CleanPos]:=c; inc(DirtyPos); inc(CleanPos); end else begin ReadComment(DirtyPos); end; end; SetLength(Result,CleanPos-1); end; function CompareIdentifiers(Identifier1, Identifier2: PChar): integer; begin if (Identifier1<>nil) then begin if (Identifier2<>nil) then begin while (UpChars[Identifier1[0]]=UpChars[Identifier2[0]]) do begin if (IsIDChar[Identifier1[0]]) then begin inc(Identifier1); inc(Identifier2); end else begin Result:=0; // for example 'aaA;' 'aAa;' exit; end; end; if (IsIDChar[Identifier1[0]]) then begin if (IsIDChar[Identifier2[0]]) then begin if UpChars[Identifier1[0]]>UpChars[Identifier2[0]] then Result:=-1 // for example 'aab' 'aaa' else Result:=1; // for example 'aaa' 'aab' end else begin Result:=-1; // for example 'aaa' 'aa;' end; end else begin if (IsIDChar[Identifier2[0]]) then Result:=1 // for example 'aa;' 'aaa' else Result:=0; // for example 'aa;' 'aa,' end; end else begin Result:=-1; // for example 'aaa' nil end; end else begin if (Identifier2<>nil) then begin Result:=1; // for example nil 'bbb' end else begin Result:=0; // for example nil nil end; end; end; function ComparePrefixIdent(PrefixIdent, Identifier: PChar): boolean; begin if PrefixIdent<>nil then begin if Identifier<>nil then begin while UpChars[PrefixIdent^]=UpChars[Identifier^] do begin inc(PrefixIdent); inc(Identifier); end; Result:=not IsIDChar[PrefixIdent^]; end else begin Result:=false; end; end else begin Result:=true; end; end; function GetIdentifier(Identifier: PChar): string; var len: integer; begin if Identifier<>nil then begin len:=0; while (IsIdChar[Identifier[len]]) do inc(len); SetLength(Result,len); if len>0 then Move(Identifier[0],Result[1],len); end else Result:=''; end; function FindNextIdentifier(const Source: string; StartPos, MaxPos: integer ): integer; begin Result:=StartPos; while (Result<=MaxPos) and (not IsIDStartChar[Source[Result]]) do inc(Result); end; function GetIndentStr(Indent: integer): string; begin SetLength(Result,Indent); if Indent>0 then FillChar(Result[1],length(Result),' '); end; function TrimCodeSpace(const ACode: string): string; // turn all lineends and special chars to space // space is combined to one char // space which is not needed is removed. // space is only needed between two words or between 2-char operators const NonSpaceSymbols = [',',';','(',')','[',']']; var CodePos, ResultPos, CodeLen, SpaceEndPos: integer; c1, c2: char; begin CodeLen:=length(ACode); SetLength(Result,CodeLen); CodePos:=1; ResultPos:=1; while CodePos<=CodeLen do begin if ACode[CodePos]>#32 then begin Result[ResultPos]:=ACode[CodePos]; inc(ResultPos); inc(CodePos); end else begin SpaceEndPos:=CodePos; while (SpaceEndPos<=CodeLen) and (ACode[SpaceEndPos]<=#32) do inc(SpaceEndPos); if (CodePos>1) and (SpaceEndPos<=CodeLen) then begin c1:=ACode[CodePos-1]; c2:=ACode[SpaceEndPos]; if (IsIdChar[c1] and IsIdChar[c2]) // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, >< or ((c2='=') and (c1 in [':','+','-','/','*','>','<'])) or ((c1='<') and (c2='>')) or ((c1='>') and (c2='<')) or ((c1='.') and (c2='.')) or ((c1='*') and (c2='*')) or ((c1='@') and (c2='@')) then begin // keep one space Result[ResultPos]:=' '; inc(ResultPos); end; end; // skip space CodePos:=SpaceEndPos; end; end; SetLength(Result,ResultPos-1); end; function CodeIsOnlySpace(const ACode: string; FromPos, ToPos: integer): boolean; var SrcLen: integer; CodePos: integer; begin Result:=true; SrcLen:=length(ACode); if ToPos>SrcLen then ToPos:=SrcLen; CodePos:=FromPos; while (CodePos<=ToPos) do begin if ACode[CodePos] in [' ',#9,#10,#13] then inc(CodePos) else begin Result:=false; exit; end; end; end; function StringToPascalConst(const s: string): string; function Convert(var DestStr: string): integer; var SrcLen, SrcPos, DestPos: integer; c: char; i: integer; begin SrcLen:=length(s); DestPos:=0; for SrcPos:=1 to SrcLen do begin inc(DestPos); c:=s[SrcPos]; if c>=' ' then begin if DestStr<>'' then DestStr[DestPos]:=c; end else begin if DestStr<>'' then DestStr[DestPos]:='#'; inc(DestPos); i:=ord(c); if i>=100 then begin if DestStr<>'' then DestStr[DestPos]:=chr((i div 100)+ord('0')); inc(DestPos); end; if i>=10 then begin if DestStr<>'' then DestStr[DestPos]:=chr(((i div 10) mod 10)+ord('0')); inc(DestPos); end; if DestStr<>'' then DestStr[DestPos]:=chr((i mod 10)+ord('0')); end; end; Result:=DestPos; end; var NewLen: integer; begin Result:=''; NewLen:=Convert(Result); if NewLen=length(s) then begin Result:=s; exit; end; SetLength(Result,NewLen); Convert(Result); end; procedure RaiseCatchableException(const Msg: string); begin { Raises an exception. gdb does not catch fpc Exception objects, therefore this procedure raises a standard AV which is catched by gdb. } writeln('ERROR in CodeTools: ',Msg); // creates an exception, that gdb catches: writeln('Creating gdb catchable error:'); if (length(Msg) div (length(Msg) div 10000))=0 then ; end; function CountNeededLineEndsToAddForward(const Src: string; StartPos, MinLineEnds: integer): integer; var c:char; SrcLen: integer; begin Result:=MinLineEnds; if (StartPos<1) or (Result=0) then exit; SrcLen:=length(Src); while (StartPos<=SrcLen) do begin c:=Src[StartPos]; if c in [#10,#13] then begin dec(Result); if Result=0 then break; inc(StartPos); if (StartPos<=SrcLen) and (Src[StartPos] in [#10,#13]) and (Src[StartPos]<>c) then inc(StartPos); end else if IsSpaceChar[c] then inc(StartPos) else break; end; end; function CountNeededLineEndsToAddBackward( const Src: string; StartPos, MinLineEnds: integer): integer; var c:char; SrcLen: integer; begin Result:=MinLineEnds; SrcLen:=length(Src); if (StartPos>SrcLen) or (Result=0) then exit; while (StartPos>=1) do begin c:=Src[StartPos]; if c in [#10,#13] then begin dec(Result); if Result=0 then break; dec(StartPos); if (StartPos>=1) and (Src[StartPos] in [#10,#13]) and (Src[StartPos]<>c) then dec(StartPos); end else if IsSpaceChar[c] then dec(StartPos) else break; end; end; //============================================================================= procedure BasicCodeToolInit; var c: char; begin for c:=#0 to #255 do begin IsIDChar[c]:=(c in ['a'..'z','A'..'Z','0'..'9','_']); IsIDStartChar[c]:=(c in ['a'..'z','A'..'Z','_']); IsSpaceChar[c]:=c in [#0..#32]; end; end; initialization BasicCodeToolInit; end.