{
 ***************************************************************************
 *                                                                         *
 *   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 <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    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}
{$else}
  {$ifdef MSWindows}
    {$define Windows}
  {$endif}
{$endif}{$H+}
{$inline on}

interface

uses
  Classes, SysUtils, StrUtils, Laz_AVL_Tree,
  // LazUtils
  LazFileUtils, LazStringUtils, LazUTF8,
  // Codetools
  SourceLog, KeywordFuncLists, FileProcs;

//----------------------------------------------------------------------------
{ These functions are used by the codetools }

// comments
function FindNextNonSpace(const ASource: string; StartPos: integer): integer;
function FindPrevNonSpace(const ASource: string; StartPos: integer): integer;
function FindCommentEnd(const ASource: string; StartPos: integer;
    NestedComments: boolean): integer; overload;
function FindCommentEnd(Src: PChar; NestedComments: boolean): PChar; overload;
function IsCommentEnd(const ASource: string; EndPos: integer): boolean;
function FindNextComment(const ASource: string;
    StartPos: integer; MaxPos: integer = 0): integer;
procedure FindCommentsInRange(const Src: string; StartPos, EndPos: integer;
    out FirstCommentStart, FirstAtomStart, LastCommentEnd, LastAtomEnd: integer;
    NestedComments: boolean = false);
function FindNextCompilerDirective(const ASource: string; StartPos: integer;
    NestedComments: boolean): integer;
function FindNextCompilerDirectiveWithName(const ASource: string;
    StartPos: integer; const DirectiveName: string;
    NestedComments: boolean; out ParamPos: integer): integer;
function FindNextIncludeDirective(const ASource: string;
    StartPos: integer; NestedComments: boolean;
    out FilenameStartPos, FileNameEndPos,
    CommentStartPos, CommentEndPos: integer): integer;
function FindNextIDEDirective(const ASource: string; StartPos: integer;
    NestedComments: boolean; EndPos: integer = 0): integer;
function CleanCodeFromComments(const Src: string;
    NestedComments: boolean; KeepDirectives: boolean = false;
    KeepVerbosityDirectives: boolean = false): string;
function ExtractCommentContent(const ASource: string; CommentStart: integer;
    NestedComments: boolean;
    TrimStart: boolean = false; TrimEnd: boolean = false;
    TrimPasDoc: boolean = false): string;
function FindMainUnitHint(const ASource: string; out Filename: string): boolean;
function InEmptyLine(const ASource: string; StartPos: integer): boolean;
function SkipResourceDirective(const ASource: string; StartPos, EndPos: integer;
    NestedComments: boolean): integer;

// indent
function GetLineIndent(const Source: string; Position: integer): integer;
function GetLineIndentWithTabs(const Source: string; Position: integer;
                               TabWidth: integer): integer;
function GetPosInLine(const Source: string; Position: integer): integer; // 0 based
function GetBlockMinIndent(const Source: string;
    StartPos, EndPos: integer): integer;
function GetIndentStr(Indent: integer; TabWidth: integer = 0): string;
procedure IndentText(const Source: string; Indent, TabWidth: integer;
    out NewSource: string);
function FindFirstNonSpaceCharInLine(const Source: string;
    Position: integer): integer;
function IsFirstNonSpaceCharInLine(const Source: string;
    Position: integer): boolean;
procedure GuessIndentSize(const Source: string;
  var IndentSize: integer; TabWidth: integer = 2; MaxLineCount: integer = 10000);
function ReIndent(const Source: string; OldIndent, OldTabWidth,
  NewIndent, NewTabWidth: integer): string;

// identifiers
procedure GetIdentStartEndAtPosition(const Source:string; Position:integer;
    out IdentStart,IdentEnd:integer);
function GetIdentStartPosition(const Source:string; Position:integer): integer;
function GetIdentLen(Identifier: PChar): integer;
function GetIdentifier(Identifier: PChar; const aSkipAmp: Boolean = True): string;
function FindNextIdentifier(const Source: string; StartPos, MaxPos: integer): integer;
function FindNextIdentifierSkipStrings(const Source: string;
    StartPos, MaxPos: integer): integer;
function IsValidIdentPair(const NamePair: string): boolean;
function IsValidIdentPair(const NamePair: string; out First, Second: string): boolean;
function ExtractPasIdentifier(const Ident: string; AllowDots: Boolean): string;

// line/code ends
function SrcPosToLineCol(const s: string; Position: integer;
    out Line, Col: integer): boolean;
procedure GetLineStartEndAtPosition(const Source: string; Position:integer;
    out LineStart,LineEnd:integer); // LineEnd at first line break character
function GetLineStartPosition(const Source: string; Position:integer): integer;
function GetLineInSrc(const Source: string; Position:integer): string;
function LineEndCount(const Txt: string): integer; inline;
function LineEndCount(const Txt: string; out LengthOfLastLine:integer): integer; inline;
function LineEndCount(const Txt: string; StartPos, EndPos: integer;
                      out LengthOfLastLine:integer): integer;
function EmptyCodeLineCount(const Source: string; StartPos, EndPos: integer;
    NestedComments: boolean): integer;
function PositionsInSameLine(const Source: string;
    Pos1, Pos2: integer): boolean;
function FindLineEndOrCodeInFrontOfPosition(const Source: string;
    Position, MinPosition: integer; NestedComments: boolean;
    StopAtDirectives: boolean = true; SkipSemicolonComma: boolean = true;
    SkipEmptyLines: boolean = false): integer;
function FindLineEndOrCodeAfterPosition(const Source: string;
    Position, MaxPosition: integer; NestedComments: boolean;
    StopAtDirectives: boolean = true; SkipEmptyLines: boolean = false;
    IncludeLineEnd: boolean = false): integer;
function FindFirstLineEndInFrontOfInCode(const Source: string;
    Position, MinPosition: integer; NestedComments: boolean): integer;
function FindFirstLineEndAfterInCode(const Source: string;
    Position, MaxPosition: integer; NestedComments: boolean): integer;
function ChompLineEndsAtEnd(const s: string): string;
function ChompOneLineEndAtEnd(const s: string): string;
function TrimLineEnds(const s: string; TrimStart, TrimEnd: boolean): string;

// brackets
function GetBracketLvl(const Src: string; StartPos, EndPos: 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;
procedure AdjustPositionAfterInsert(var p: integer; IsStart: boolean;
                                    FromPos, ToPos, DiffPos: integer);

// comparison
function CompareText(Txt1: PChar; Len1: integer; Txt2: PChar; Len2: integer;
    CaseSensitive: boolean): integer; overload;
function CompareTextCT(const Txt1, Txt2: string;
    CaseSensitive: boolean = false): integer; overload;
function CompareText(Txt1: PChar; Len1: integer; Txt2: PChar; Len2: integer;
    CaseSensitive, IgnoreSpace: boolean): integer; overload;
function CompareTextIgnoringSpace(const Txt1, Txt2: string;
    CaseSensitive: boolean): integer;
function CompareTextIgnoringSpace(Txt1: PChar; Len1: integer;
    Txt2: PChar; Len2: integer; CaseSensitive: boolean): integer;
function CompareAnsiStringIgnoringSpaceIgnoreCase(Txt1, Txt2: pointer): integer;
function CompareSubStrings(const Find, Txt: string;
    FindStartPos, TxtStartPos, Len: integer; CaseSensitive: boolean): integer;
function CompareIdentifiers(Identifier1, Identifier2: PChar): integer; {$IFDEF HasInline}inline;{$ENDIF}
function CompareIdentifiersCaseSensitive(Identifier1, Identifier2: PChar): integer;
function CompareIdentifierPtrs(Identifier1, Identifier2: Pointer): integer; {$IFDEF HasInline}inline;{$ENDIF}
function ComparePrefixIdent(PrefixIdent, Identifier: PChar): boolean;
function TextBeginsWith(Txt: PChar; TxtLen: integer; StartTxt: PChar;
    StartTxtLen: integer; CaseSensitive: boolean): boolean;
function StrBeginsWith(const s, Prefix: string): boolean;
function IdentifierPos(Search, Identifier: PChar): PtrInt; // search Search in Identifier
function CompareAtom(p1, p2: PChar; NestedComments: boolean): integer;
function CompareStringConstants(p1, p2: PChar): integer; // compare case sensitive
function CompareComments(p1, p2: PChar; NestedComments: boolean): integer; // compare case insensitive
function FindDiff(const s1, s2: string): integer;
function dbgsDiff(Expected, Actual: string): string; overload;

// dotted identifiers
function DottedIdentifierLength(Identifier: PChar): integer;
function GetDottedIdentifier(Identifier: PChar): string;
function IsDottedIdentifier(const Identifier: string): boolean;
function CompareDottedIdentifiers(Identifier1, Identifier2: PChar): integer;
function ChompDottedIdentifier(const Identifier: string): string;

// 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;

// string constants
function SplitStringConstant(const StringConstant: string;
    FirstLineLength, OtherLineLengths, Indent: integer;
    const aLineBreak: string): string;
procedure ImproveStringConstantStart(const ACode: string; var StartPos: integer);
procedure ImproveStringConstantEnd(const ACode: string; var EndPos: integer);
function HexStrToIntDef(p: PChar; Def: integer): integer;

// search
function SearchNextInText(Search: PChar; SearchLen: PtrInt;
    Src: PChar; SrcLen: PtrInt;
    StartPos: PtrInt;// 0 based
    out MatchStart, MatchEnd: PtrInt;// 0 based
    WholeWords: boolean = false; MultiLine: boolean = false): boolean;
procedure HasTxtWord(SearchWord, Txt: PChar; out WholeWord: boolean;
  out Count: SizeInt);

// misc
function SubString(p: PChar; Count: SizeInt): string; overload;

// files
type

  { TUnitFileInfo }

  TUnitFileInfo = class
  private
    FFilename: string;
    FUnitName: string;
    function GetFileUnitNameWithoutNamespace: string;
    function GetIdentifierStartInUnitName: Integer;
  public
    constructor Create(const TheUnitName, TheFilename: string);
    property FileUnitName: string read FUnitName;
    property FileUnitNameWithoutNamespace: string read GetFileUnitNameWithoutNamespace;
    property Filename: string read FFilename;
    property IdentifierStartInUnitName: Integer read GetIdentifierStartInUnitName;
  end;

  { TNameSpaceInfo }

  TNameSpaceInfo = class
  private
    FUnitName: string;
    FNamespace: string;
    FIdentifierStartInUnitName: Integer;
  public
    constructor Create(const TheNamespace, TheUnitName: string; TheIdentifierStartInUnitName: Integer);
    property UnitName: string read FUnitName;
    property Namespace: string read FNamespace;
    property IdentifierStartInUnitName: Integer read FIdentifierStartInUnitName;
  end;

function ExtractFileNamespace(const Filename: string): string;
procedure AddToTreeOfUnitFilesOrNamespaces(
  var TreeOfUnitFiles, TreeOfNameSpaces: TAVLTree;
  const NameSpacePath, Filename: string;
  CaseInsensitive, KeepDoubles: boolean);
function GatherUnitFiles(const BaseDir, SearchPath,
    Extensions, NameSpacePath: string; KeepDoubles, CaseInsensitive: boolean;
    var TreeOfUnitFiles, TreeOfNamespaces: TAVLTree): boolean;
procedure FreeTreeOfUnitFiles(TreeOfUnitFiles: TAVLTree);
procedure AddToTreeOfUnitFiles(var TreeOfUnitFiles: TAVLTree;
  const Filename, Unitname: string;
  KeepDoubles: boolean);
procedure AddToTreeOfNamespaces(var TreeOfNameSpaces: TAVLTree;
  const UnitName, ParentNameSpacePath: string;
  KeepDoubles: boolean);
function CompareUnitFileInfos(Data1, Data2: Pointer): integer;
function CompareNameSpaceInfos(Data1, Data2: Pointer): integer;
function CompareUnitNameAndUnitFileInfo(UnitnamePAnsiString,
                                        UnitFileInfo: Pointer): integer;
function CompareNameSpaceAndNameSpaceInfo(NamespacePAnsiString,
                                        NamespaceInfo: Pointer): integer;

// function filled by CodeToolManager to find inc files
var
  FindIncFileInCfgCache: function(const Name: string; out ExpFilename: string): boolean;

//-----------------------------------------------------------------------------
// functions / procedures

{ These functions are not context sensitive. Especially they ignore compiler
  settings and compiler directives. They exist only for basic usage.
}

// source type
function FindSourceType(const Source: string;
  var SrcNameStart, SrcNameEnd: integer; NestedComments: boolean = false): string;

// identifier
function ReadDottedIdentifier(const Source: string; var Position: integer;
  NestedComments: boolean = false): string;
function ReadDottedIdentifier(var Position: PChar; SrcEnd: PChar;
  NestedComments: boolean = false): string;

// program name
function RenameProgramInSource(Source:TSourceLog;
   const NewProgramName:string):boolean;
function FindProgramNameInSource(const Source:string;
   out ProgramNameStart,ProgramNameEnd:integer):string;

// unit name
function RenameUnitInSource(Source:TSourceLog;const NewUnitName:string):boolean;
function FindUnitNameInSource(const Source:string;
   out UnitNameStart,UnitNameEnd: integer; NestedComments: boolean = false):string;
function FindModuleNameInSource(const Source:string;
   out ModuleType: string; out NameStart,NameEnd: integer;
   NestedComments: boolean = false):string;

// uses sections
function UnitIsUsedInSource(const Source,SrcUnitName: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,SrcUnitName:string;
   UsesStart:integer):boolean;
function RenameUnitInUsesSection(Source:TSourceLog; UsesStart: integer;
   const OldUnitName, NewUnitName, NewInFile:string): boolean;
function AddUnitToUsesSection(Source:TSourceLog;
   const AnUnitName,InFilename:string; UsesStart:integer):boolean;
function RemoveUnitFromUsesSection(Source:TSourceLog;
   const AnUnitName:string; UsesStart:integer):boolean;

// compiler directives
function FindIncludeDirective(const Source,Section:string; Index:integer;
   out IncludeStart,IncludeEnd:integer):boolean;
function ExtractLongParamDirective(const Source: string; CommentStartPos: integer;
   out DirectiveName, FileParam: string): boolean;
function SplitCompilerDirective(const Directive:string;
   out 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;
   out 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;
  const ComponentName, ComponentClassName: string): boolean;
function FindClassAncestorName(const Source, FormClassName: string): string;

// procedure specifiers
function FindFirstProcSpecifier(const ProcText: string;
   NestedComments: boolean = false): integer;
function SearchProcSpecifier(const ProcText, Specifier: string;
   out SpecifierEndPosition: integer;
   NestedComments: boolean = false;
   WithSpaceBehindSemicolon: boolean = true): integer;
function RemoveProcSpecifier(const ProcText, Specifier: string;
   NestedComments: boolean = false): string;

// code search
function SearchCodeInSource(const Source, Find: string; StartPos: integer;
   out EndFoundPosition: integer; CaseSensitive: boolean;
   NestedComments: boolean = false): integer;
function ReadNextPascalAtom(const Source: string;
   var Position: integer; out AtomStart: integer; NestedComments: boolean = false;
   SkipDirectives: boolean = false): string;
procedure ReadRawNextPascalAtom(const Source: string;
   var Position: integer; out AtomStart: integer;
   NestedComments: boolean = false; SkipDirectives: boolean = false);
procedure ReadRawNextPascalAtom(var Position: PChar; out AtomStart: PChar;
   const SrcEnd: PChar = nil; NestedComments: boolean = false;
   SkipDirectives: boolean = false);
procedure ReadPriorPascalAtom(const Source: string;
  var Position: integer; out AtomEnd: integer; NestedComments: boolean = false);
function ReadTilPascalBracketClose(const Source: string;
   var Position: integer; NestedComments: boolean = false): boolean;
function GetAtomLength(p: PChar; NestedComments: boolean): integer;
function GetAtomString(p: PChar; NestedComments: boolean): string;
function FindStartOfAtom(const Source: string; Position: integer): integer;
function FindEndOfAtom(const Source: string; Position: integer): integer;

//-----------------------------------------------------------------------------

const
  MaxLineLength: integer = 80;

//=============================================================================

implementation

function Min(i1, i2: integer): integer; inline;
begin
  if i1<=i2 then Result:=i1 else Result:=i2;
end;

function Max(i1, i2: integer): integer; inline;
begin
  if i1>=i2 then Result:=i1 else Result:=i2;
end;

{ most simple code tools - just methods }

function FindIncludeDirective(const Source,Section:string; Index:integer;
   out 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) or (EndPos<1) then exit;
  // search for include directives
  repeat
    Atom:=ReadNextPascalAtom(Source,Position,AtomStart);
    if LazStartsStr('{$',Atom) or LazStartsStr('(*$', Atom) then begin
      SplitCompilerDirective(Atom,DirectiveName,Filename);
      if (DirectiveName='i') or (DirectiveName='I')
      or (CompareText(DirectiveName,'include')=0) 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 ExtractLongParamDirective(const Source: string; CommentStartPos: integer;
  out DirectiveName, FileParam: string): boolean;
var
  p, StartPos: PChar;
begin
  Result:=false;
  FileParam:='';
  if CommentStartPos>length(Source) then exit;
  p:=@Source[CommentStartPos];
  if (p^<>'{') or (p[1]<>'$') then exit;
  inc(p,2);
  StartPos:=p;
  if not IsIdentStartChar[p^] then exit;
  while IsIdentChar[p^] do inc(p);
  DirectiveName:=copy(Source,StartPos-PChar(Source)+1,p-StartPos);
  Result:=true;
  while p^ in [' ',#9] do inc(p);
  if p^='''' then begin
    // 'param with spaces'
    inc(p);
    StartPos:=p;
    while not (p^ in [#0,#10,#13,'''']) do inc(p);
  end else begin
    // param without spaces
    StartPos:=p;
    while not (p^ in [#0,#9,#10,#13,' ','}']) do inc(p);
  end;
  FileParam:=copy(Source,StartPos-PChar(Source)+1,p-StartPos);
end;

function SplitCompilerDirective(const Directive:string;
   out DirectiveName,Parameters:string):boolean;
var EndPos,DirStart,DirEnd:integer;
begin
  if LazStartsStr('{$',Directive) or LazStartsStr('(*$',Directive) then begin
    if LazStartsStr('{$',Directive) then begin
      DirStart:=3;
      DirEnd:=length(Directive);
    end else begin
      DirStart:=4;
      DirEnd:=length(Directive)-1;
    end;
    EndPos:=DirStart;
    while (EndPos<DirEnd) and (IsIdentChar[Directive[EndPos]]) do
      inc(EndPos);
    DirectiveName:=lowercase(copy(Directive,DirStart,EndPos-DirStart));
    Parameters:=copy(Directive,EndPos+1,DirEnd-EndPos-1);
    Result:=true;
  end else
    Result:=false;
end;

function FindSourceType(const Source: string; var SrcNameStart,
  SrcNameEnd: integer; NestedComments: boolean): string;
var
  u: String;
  p, AtomStart: Integer;
begin
  // read first atom for type
  SrcNameStart:=0;
  SrcNameEnd:=0;
  p:=1;
  Result:=ReadNextPascalAtom(Source,p,AtomStart,NestedComments);
  u:=Uppercase(Result);
  if (u='UNIT') or (u='PROGRAM') or (u='LIBRARY') or (u='PACKAGE') then begin
    // read name
    ReadNextPascalAtom(Source,p,AtomStart,NestedComments);
    if p<=AtomStart then exit;
    if not IsIdentStartChar[Source[AtomStart]] then exit;
    SrcNameStart:=AtomStart;
    SrcNameEnd:=p;
    repeat
      ReadRawNextPascalAtom(Source,p,AtomStart,NestedComments);
      if (AtomStart=p+1) and (Source[AtomStart]='.') then begin
        ReadRawNextPascalAtom(Source,p,AtomStart,NestedComments);
        if p<=AtomStart then exit;
        if not IsIdentStartChar[Source[AtomStart]] then exit;
        SrcNameEnd:=p;
      end else
        break;
    until false;
  end else begin
    Result:='';
  end;
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; out UnitNameStart,
  UnitNameEnd: integer; NestedComments: boolean): string;
var
  ModuleType: string;
begin
  Result:=FindModuleNameInSource(Source,ModuleType,UnitNameStart,UnitNameEnd,NestedComments);
  if CompareText(ModuleType,'UNIT')<>0 then
    Result:='';
end;

function FindModuleNameInSource(const Source: string; out ModuleType: string;
  out NameStart, NameEnd: integer; NestedComments: boolean): string;
var
  u: String;
  p, AtomStart: Integer;
begin
  // read first atom for type
  Result:='';
  NameStart:=0;
  NameEnd:=0;
  p:=1;
  ModuleType:=ReadNextPascalAtom(Source,p,AtomStart,NestedComments);
  u:=UpperCase(ModuleType);
  if (u='UNIT') or (u='PROGRAM') or (u='LIBRARY') or (u='PACKAGE') then begin
    // read name
    ReadNextPascalAtom(Source,p,AtomStart,NestedComments);
    if p<=AtomStart then exit;
    if not IsIdentStartChar[Source[AtomStart]] then exit;
    NameStart:=AtomStart;
    NameEnd:=AtomStart;
    Result:=ReadDottedIdentifier(Source,NameEnd,NestedComments);
  end else
    ModuleType:='';
end;

function ReadDottedIdentifier(const Source: string; var Position: integer;
  NestedComments: boolean): string;
var
  p: PChar;
begin
  if (Position<1) or (Position>length(Source)) then exit('');
  p:=@Source[Position];
  Result:=ReadDottedIdentifier(p,PChar(Source)+length(Source),NestedComments);
  Position:=p-PChar(Source)+1;
end;

function ReadDottedIdentifier(var Position: PChar; SrcEnd: PChar;
  NestedComments: boolean): string;
var
  AtomStart, p: PChar;
begin
  Result:='';
  p:=Position;
  ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
  Position:=AtomStart;
  if (AtomStart>=p) or not IsIdentStartChar[AtomStart^] then exit;
  Result:=GetIdentifier(AtomStart);
  repeat
    ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
    if (AtomStart+1<>p) or (AtomStart^<>'.') then exit;
    ReadRawNextPascalAtom(p,AtomStart,SrcEnd,NestedComments);
    if (AtomStart>=p) or not IsIdentStartChar[AtomStart^] then exit;
    Position:=AtomStart;
    Result:=Result+'.'+GetIdentifier(AtomStart);
  until false;
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;
   out ProgramNameStart,ProgramNameEnd:integer):string;
begin
  ProgramNameStart:=0;
  ProgramNameEnd:=0;
  if uppercasestr(FindSourceType(Source,ProgramNameStart,ProgramNameEnd))=
    'PROGRAM'
  then
    Result:=copy(Source,ProgramNameStart,ProgramNameEnd-ProgramNameStart)
  else
    Result:='';
end;

function UnitIsUsedInSource(const Source,SrcUnitName:string):boolean;
// search in all uses sections
var UsesStart,UsesEnd:integer;
begin
  Result:=false;
  repeat
    UsesStart:=SearchCodeInSource(Source,'uses',1,UsesEnd,false);
    if UsesEnd=0 then ;
    if UsesStart>0 then begin
      if IsUnitUsedInUsesSection(Source,SrcUnitName,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 CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  then begin
    // no uses section in interface -> add one
    Source.Insert(ProgramTermEnd,LineEnding+LineEnding+'uses'+LineEnding+'  ;');
    UsesEnd:=ProgramTermEnd;
    ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
  end;
  if CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  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 CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  then begin
    // no uses section after program term -> add one
    Source.Insert(ProgramTermEnd,LineEnding+LineEnding+'uses'+LineEnding+'  ;');
    UsesEnd:=ProgramTermEnd;
    ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
  end;
  if CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  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 CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  then begin
    // no uses section in interface -> add one
    Source.Insert(InterfaceWordEnd,LineEnding+LineEnding+'uses'+LineEnding+'  ;');
    UsesEnd:=InterfaceWordEnd;
    ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
  end;
  if CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  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 CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  then begin
    // no uses section in interface -> add one
    Source.Insert(InterfaceWordEnd,LineEnding+LineEnding+'uses'+LineEnding+'  ;');
    UsesEnd:=InterfaceWordEnd;
    ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
  end;
  if CompareText(copy(Source.Source,UsesStart,UsesEnd-UsesStart),'uses')<>0
  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 CompareText(Atom,'uses')<>0 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 CompareText(Atom,'uses')<>0 then exit;
  Result:=RemoveUnitFromUsesSection(Source,AUnitName,UsesStart);
end;

function IsUnitUsedInUsesSection(const Source,SrcUnitName:string;
   UsesStart:integer):boolean;
var UsesEnd:integer;
  Atom:string;
begin
  Result:=false;
  if SrcUnitName='' then exit;
  if UsesStart<1 then exit;
  if CompareText(copy(Source,UsesStart,4),'uses')<>0 then exit;
  UsesEnd:=UsesStart+4;
  // parse through all used units and see if it is there
  repeat
    Atom:=ReadNextPascalAtom(Source,UsesEnd,UsesStart);
    if CompareText(Atom,SrcUnitName)=0 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 CompareText(copy(Source.Source,UsesStart,4),'uses')<>0 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 CompareText(Atom,OldUnitName)=0 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,LineEnding+'  ');
  Result:=true;
end;

function AddUnitToUsesSection(Source:TSourceLog;
 const AnUnitName,InFilename:string; UsesStart:integer):boolean;
var UsesEnd:integer;
  LineStart,LineEnd:integer;
  s,Atom,NewUnitTerm:string;
begin
  Result:=false;
  if (AnUnitName='') or (AnUnitName=';') or (UsesStart<1) then exit;
  UsesEnd:=UsesStart+4;
  if CompareText(copy(Source.Source,UsesStart,4),'uses')<>0 then exit;
  // parse through all used units and see if it is already there
  s:=', ';
  repeat
    Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
    if CompareText(Atom,AnUnitName)=0 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:=AnUnitName+' in '''+InFileName+''''
  else
    NewUnitTerm:=AnUnitName;
  Source.Insert(UsesStart,s+NewUnitTerm);
  GetLineStartEndAtPosition(Source.Source,UsesStart,LineStart,LineEnd);
  if (LineEnd-LineStart>MaxLineLength) or (InFileName<>'') then
    Source.Insert(UsesStart,LineEnding+'  ');
  Result:=true;
end;

function RemoveUnitFromUsesSection(Source:TSourceLog; const AnUnitName:string;
   UsesStart:integer):boolean;
var UsesEnd,OldUsesStart,OldUsesEnd:integer;
  Atom:string;
begin
  Result:=false;
  if (UsesStart<1) or (AnUnitName='') or (AnUnitName=',') or (AnUnitName=';') then
    exit;
  // search interface section
  UsesEnd:=UsesStart+4;
  if CompareText(copy(Source.Source,UsesStart,4),'uses')<>0 then exit;
  // parse through all used units and see if it is there
  OldUsesEnd:=-1;
  repeat
    Atom:=ReadNextPascalAtom(Source.Source,UsesEnd,UsesStart);
    if CompareText(Atom,AnUnitName)=0 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 AnUnitName 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(<AClassName>,<AName>);' 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;
  if EndPosition=0 then ;
  Source.Insert(Position,
         'Application.CreateForm('+AClassName+','+AName+');'+LineEnding+'  ');
  Result:=true;
end;

function RemoveCreateFormFromProgram(Source:TSourceLog;
  const AClassName,AName:string):boolean;
// remove 'Application.CreateForm(<AClassName>,<AName>);'
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;
  if EndPosition=0 then ;
end;

function ListAllCreateFormsInProgram(const Source:string):TStrings;
// list format: <formname>:<formclassname>
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;
   out 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('<ResourceName>',"
  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,LineEnding+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: string;
begin
  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 (CompareText(ReadNextPascalAtom(Source,Result,AtomStart),ComponentName)=0)
    and (ReadNextPascalAtom(Source,Result,AtomStart)=':')
    and (CompareText(ReadNextPascalAtom(Source,Result,AtomStart),ComponentClassName)=0)
    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+';'+LineEnding
             +NextSpaces);
      Result:=true;
      exit;
    end;
  until Position>length(Source.Source);
  Result:=false;
end;

function RemoveFormComponentFromSource(Source:TSourceLog;
  FormBodyStartPos: integer;
  const ComponentName, ComponentClassName: string): boolean;
var AtomStart, Position, ComponentStart, LineStart, LineEnd: integer;
  Atom: string;
begin
  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 CompareText(Atom,ComponentName)=0 then begin
      ComponentStart:=AtomStart;
      if (ReadNextPascalAtom(Source.Source,Position,AtomStart)=':')
      and (CompareText(ReadNextPascalAtom(Source.Source,Position,AtomStart),ComponentClassName)=0)
      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 FindClassAncestorName(const Source, FormClassName: string): string;
var
  SrcPos, AtomStart: integer;
begin
  Result:='';
  if SearchCodeInSource(Source,FormClassName+'=class(',1,SrcPos,false)<1 then
    exit;
  Result:=ReadNextPascalAtom(Source,SrcPos,AtomStart);
  if not IsValidIdent(Result) then
    Result:='';
end;

function SearchCodeInSource(const Source, Find: string; StartPos: integer;
  out EndFoundPosition: integer; CaseSensitive: boolean;
  NestedComments: boolean):integer;
// search pascal atoms of <Find> in <Source>
// returns the start pos
// -1 on failure
var
  FindLen: Integer;
  SrcLen: Integer;
  Position: Integer;
  FirstFindPos: Integer;
  FindAtomStart: Integer;
  AtomStart: Integer;
  FindAtomLen: Integer;
  AtomLen: Integer;
  SrcPos: Integer;
  FindPos: Integer;
  SrcAtomStart: Integer;
  FirstFindAtomStart: Integer;
begin
  Result:=-1;
  if (Find='') or (StartPos>length(Source)) then exit;

  FindLen:=length(Find);
  SrcLen:=length(Source);

  Position:=StartPos;
  AtomStart:=StartPos;
  FirstFindPos:=1;
  FirstFindAtomStart:=1;

  // search first atom in find
  ReadRawNextPascalAtom(Find,FirstFindPos,FirstFindAtomStart,NestedComments);
  FindAtomLen:=FirstFindPos-FirstFindAtomStart;
  if FirstFindAtomStart>FindLen then exit;

  repeat
    // read next atom
    ReadRawNextPascalAtom(Source,Position,AtomStart,NestedComments);
    if AtomStart>SrcLen then exit;
    AtomLen:=Position-AtomStart;

    if (AtomLen=FindAtomLen)
    and (CompareText(@Find[FirstFindAtomStart],FindAtomLen,
                     @Source[AtomStart],AtomLen,CaseSensitive)=0)
    then begin
      // compare all atoms
      SrcPos:=Position;
      SrcAtomStart:=SrcPos;
      FindPos:=FirstFindPos;
      FindAtomStart:=FindPos;
      repeat
        // read the next atom from the find
        ReadRawNextPascalAtom(Find,FindPos,FindAtomStart,NestedComments);
        if FindAtomStart>FindLen then begin
          // found !
          EndFoundPosition:=SrcPos;
          Result:=AtomStart;
          exit;
        end;
        // read the next atom from the source
        ReadRawNextPascalAtom(Source,SrcPos,SrcAtomStart,NestedComments);
        // compare
        if (CompareText(@Find[FindAtomStart],FindPos-FindAtomStart,
                        @Source[SrcAtomStart],SrcPos-SrcAtomStart,
                        CaseSensitive)<>0)
        then
          break;
      until false;
    end;
  until false;
end;

function FindCommentEnd(Src: PChar; NestedComments: boolean): PChar;
// returns position after the comment end, e.g. after }
var
  CommentLvl: integer;
begin
  Result:=Src;
  if Result=nil then exit;
  case Result^ of
  '/':
    if Result[1]='/' then begin
      inc(Result,2);
      while not (Result^ in [#0,#10,#13]) do inc(Result);
    end;

  '{':
    begin
      inc(Result);
      if Result^=#3 then begin
        // codetools skip comment
        inc(Result);
        repeat
          case Result^ of
          #0: break;
          #3:
            if Result[1]='}' then begin
              inc(Result,2);
              break;
            end;
          end;
          inc(Result);
        until false;
      end else begin
        // pascal comment {}
        CommentLvl:=1;
        repeat
          case Result^ of
          #0: break;
          '{':
            if NestedComments then
              inc(CommentLvl);

          '}':
            begin
              dec(CommentLvl);
              if CommentLvl=0 then begin
                inc(Result);
                break;
              end;
            end;

          end;
          inc(Result);
        until false;
      end;
    end;

  '(':
    if Result[1]='*' then begin
      inc(Result,2);
      CommentLvl:=1;
      repeat
        if Result^=#0 then break;
        if (Result^='*') and (Result[1]=')') then begin
          inc(Result,2);
          dec(CommentLvl);
          if CommentLvl=0 then
            break;
        end else if (Result^='(') and (Result[1]='*') and NestedComments then begin
          inc(Result,2);
          inc(CommentLvl);
        end else
          inc(Result);
      until false;
    end;

  end;
end;

function IsCommentEnd(const ASource: string; EndPos: integer): boolean;
// return true if EndPos on } or on *) or in a // comment
var
  LineStart: LongInt;
begin
  Result:=false;
  if EndPos<1 then exit;
  if EndPos>length(ASource) then exit;
  if ASource[EndPos]='}' then begin
    // delphi or codetools comment end
    Result:=true;
    exit;
  end;
  if (EndPos>1) and (ASource[EndPos]=')') and (ASource[EndPos-1]='*') then begin
    // TP comment end
    Result:=true;
    exit;
  end;
  // test for Delphi comment //
  // skip line end
  LineStart:=EndPos;
  if ASource[LineStart] in [#10,#13] then begin
    dec(LineStart);
    if (LineStart>=1) and (ASource[LineStart] in [#10,#13])
    and (ASource[LineStart]<>ASource[LineStart+1]) then
      dec(LineStart);
    if LineStart<1 then exit;
  end;
  // find line start
  while (LineStart>1) and (not (ASource[LineStart-1] in [#10,#13])) do
    dec(LineStart);
  // find first non space char in line
  while (LineStart<=EndPos) and (ASource[LineStart] in [' ',#9]) do
    inc(LineStart);
  if (LineStart<EndPos)
  and (ASource[LineStart]='/') and (ASource[LineStart+1]='/') then begin
    // Delphi comment end
    Result:=true;
    exit;
  end;
end;

function FindNextComment(const ASource: string; StartPos: integer;
  MaxPos: integer): integer;
// if not found: Result=MaxPos+1
var
  NotFoundPos: Integer;
begin
  NotFoundPos:=MaxPos;
  if (MaxPos>length(ASource)) or (MaxPos<1) then
    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] in ['''',#10,#13]) then
            break;
          inc(Result);
        end;
      end;

    '/':
      if (Result<MaxPos) and (ASource[Result+1]='/') then
        exit;

    '{':
      exit;

    '(':
      if (Result<MaxPos) and (ASource[Result+1]='*') then
        exit;

    end;
    inc(Result);
  end;
  if Result>MaxPos then
    if NotFoundPos>=1 then
      Result:=NotFoundPos+1
    else
      Result:=MaxPos+1;
end;

procedure FindCommentsInRange(const Src: string; StartPos, EndPos: integer;
  out FirstCommentStart, FirstAtomStart, LastCommentEnd, LastAtomEnd: integer;
  NestedComments: boolean);
var
  p: PChar;
  i: integer;
  AtomStart: integer;
  SrcLen: Integer;
begin
  FirstCommentStart:=0;
  FirstAtomStart:=0;
  LastCommentEnd:=0;
  LastAtomEnd:=0;
  SrcLen:=length(Src);
  if (StartPos<1) then StartPos:=1;
  if StartPos>SrcLen then exit;
  if EndPos>SrcLen then EndPos:=SrcLen+1;
  i:=StartPos;
  while i<EndPos do begin
    p:=@Src[i];
    // skip space
    while IsSpaceChar[p^] do inc(p);
    i:=p-PChar(Src)+1;
    if i>=EndPos then exit;

    if (p^='{') or ((p^='(') and (p[1]='*')) or ((p^='/') and (p[1]='/')) then
    begin
      // a comment
      if FirstCommentStart=0 then
        FirstCommentStart:=i;
      i:=FindCommentEnd(Src,i,NestedComments);
      if LastCommentEnd=0 then
        LastCommentEnd:=i;
    end else begin
      // normal atom
      if FirstAtomStart=0 then
        FirstAtomStart:=i;
      ReadRawNextPascalAtom(Src,i,AtomStart);
      if LastAtomEnd=0 then
        LastAtomEnd:=i;
    end;
  end;
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 (Result<MaxPos) and (ASource[Result+1]='$') then
          exit;
        // skip pascal comment
        Result:=FindCommentEnd(ASource,Result,NestedComments);
      end;

    '(':
      begin
        if (Result<MaxPos) and (ASource[Result+1]='*') then begin
          if (Result+2<=MaxPos) and (ASource[Result+2]='$') then
            exit;
          // skip TP comment
          Result:=FindCommentEnd(ASource,Result,NestedComments);
        end else
          inc(Result);
      end;

    else
      inc(Result);
    end;

  end;
  if Result>MaxPos+1 then Result:=MaxPos+1;
end;

function FindNextCompilerDirectiveWithName(const ASource: string;
  StartPos: integer; const DirectiveName: string;
  NestedComments: boolean; out ParamPos: integer): integer;
var
  Offset: Integer;
  SrcLen: Integer;
begin
  Result:=StartPos;
  ParamPos:=0;
  SrcLen:=length(ASource);
  repeat
    Result:=FindNextCompilerDirective(ASource,Result,NestedComments);
    if (Result<1) or (Result>SrcLen) then break;
    if (ASource[Result]='{') then
      Offset:=2
    else if ASource[Result]='(' then
      Offset:=3
    else
      Offset:=-1;
    if Offset>0 then begin
      if (CompareIdentifiers(PChar(Pointer(DirectiveName)),// pointer type cast avoids #0 check
                             @ASource[Result+Offset])=0)
      then begin
        ParamPos:=FindNextNonSpace(ASource,Result+Offset+length(DirectiveName));
        exit;
      end;
    end;
    Result:=FindCommentEnd(ASource,Result,NestedComments);
  until false;
  Result:=-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 FindPrevNonSpace(const ASource: string; StartPos: integer
    ): integer;
begin
  Result:=StartPos;
  while (Result>=1) and (ASource[Result] in [' ',#9,#10,#13]) do
    dec(Result);
end;

function FindCommentEnd(const ASource: string; StartPos: integer;
  NestedComments: boolean): integer;
// returns position after the comment end, e.g. after }
// failure: returns length(ASource)+1
var
  CommentLvl: integer;
  p: PChar;
begin
  Result:=StartPos;
  if Result<1 then exit;
  if Result>length(ASource) then exit;
  p:=@ASource[Result];
  case p^ of
  '/':
    begin
      if p[1]='/' then begin
        // skip Delphi comment
        while (not (p^ in [#0,#10,#13])) do
          inc(p);
      end;
    end;

  '{':
    begin
      inc(p);
      if p^=#3 then begin
        // Codetools skip comment {#3 #3}
        inc(p);
        repeat
          case p^ of
          #0:
            if p-PChar(ASource)>=length(ASource) then break;
          #3:
            if p[1]='}' then begin
              inc(p,2);
              break;
            end;
          end;
          inc(p);
        until false;
      end else begin
        // Pascal comment {}
        CommentLvl:=1;
        repeat
          case p^ of
          #0:
            if p-PChar(ASource)>=length(ASource) then break;
          '{':
            if NestedComments then
              inc(CommentLvl);
          '}':
            begin
              dec(CommentLvl);
              if CommentLvl=0 then begin
                inc(p);
                break;
              end;
            end;
          end;
          inc(p);
        until false;
      end;
    end;

  '(':
    if (p[1]='*') then begin
      inc(p,2);
      CommentLvl:=1;
      repeat
        if (p^=#0) then begin
          if p-PChar(ASource)>=length(ASource) then break;
          inc(p);
        end else if (p^='(') and (p[1]='*') and NestedComments then begin
          inc(p,2);
          inc(CommentLvl);
        end else if (p^='*') and (p[1]=')') then begin
          inc(p,2);
          dec(CommentLvl);
          if CommentLvl=0 then break;
        end else
          inc(p);
      until false;
    end;

  end;
  Result:=p-PChar(ASource)+1;
end;

procedure GetLineStartEndAtPosition(const Source:string; Position:integer;
   out LineStart,LineEnd:integer);
begin
  if Position<1 then begin
    LineStart:=0;
    LineEnd:=0;
    exit;
  end;
  if Position>length(Source)+1 then begin
    LineStart:=length(Source)+1;
    LineEnd:=LineStart;
    exit;
  end;
  LineStart:=Position;
  while (LineStart>1) and (not (Source[LineStart-1] in [#10,#13])) do
    dec(LineStart);
  LineEnd:=Position;
  while (LineEnd<=length(Source)) and (not (Source[LineEnd] in [#10,#13])) do
    inc(LineEnd);
end;

function LineEndCount(const Txt: string; StartPos, EndPos: integer; out
  LengthOfLastLine: integer): integer;
var
  l: Integer;
  p, LineStart: PChar;
begin
  Result:=0;
  LengthOfLastLine:=0;
  l:=length(Txt);
  if l=0 then exit;
  if StartPos>l then exit;
  if EndPos>l then EndPos:=l+1;
  if StartPos>=EndPos then exit;
  p:=@Txt[StartPos];
  LineStart:=p;
  repeat
    if p^ in [#0,#10,#13] then begin
      if p-PChar(Txt)+1>=EndPos then
        break;
      if p^<>#0 then begin
        inc(Result);
        if (p[1] in [#10,#13]) and (p^<>p[1]) and (p-PChar(Txt)+1<EndPos) then
          inc(p,2)
        else
          inc(p);
        LineStart:=p;
        continue;
      end;
    end;
    inc(p);
  until false;
  LengthOfLastLine:=EndPos-(LineStart-PChar(Txt)+1);
end;

function EmptyCodeLineCount(const Source: string; StartPos, EndPos: integer;
    NestedComments: boolean): integer;
{ search forward for a line end or code
  ignore line ends in comments
  Result is Position of Start of Line End
}
var
  SrcLen: integer;
  SrcPos: Integer;
  CommentEndPos: Integer;
begin
  Result:=0;
  SrcLen:=length(Source);
  if EndPos>SrcLen then EndPos:=SrcLen+1;
  SrcPos:=StartPos;
  while (SrcPos<EndPos) do begin
    case Source[SrcPos] of
    '{','(','/':
      begin
        CommentEndPos:=FindCommentEnd(Source,SrcPos,NestedComments);
        if CommentEndPos>SrcPos then
          SrcPos:=CommentEndPos
        else
          inc(SrcPos); // not a comment start => skip char
      end;
    #10,#13:
      begin
        // skip line end
        inc(SrcPos);
        if (SrcPos<EndPos) and (Source[SrcPos] in [#10,#13])
        and (Source[SrcPos]<>Source[SrcPos-1]) then
          inc(SrcPos);
        // count empty lines
        if (SrcPos<EndPos) and (Source[SrcPos] in [#10,#13]) then
          inc(Result);
      end;
    else
      inc(SrcPos);
    end;
  end;
end;

function PositionsInSameLine(const Source: string;
    Pos1, Pos2: integer): boolean;
var
  StartPos: Integer;
  EndPos: Integer;
begin
  if Pos1<Pos2 then begin
    StartPos:=Pos1;
    EndPos:=Pos2;
  end else begin
    StartPos:=Pos2;
    EndPos:=Pos1;
  end;
  if EndPos>length(Source) then EndPos:=length(Source);
  while StartPos<EndPos do begin
    if Source[StartPos] in [#10,#13] then begin
      Result:=false;
      exit;
    end else
      inc(StartPos);
  end;
  Result:=true;
end;

procedure GetIdentStartEndAtPosition(const Source: string; Position: integer;
  out IdentStart, IdentEnd: integer);
// on success: IdentStart<IdentEnd
begin
  IdentStart:=Position;
  IdentEnd:=Position;
  if (Position<1) or (Position>length(Source)+1) then exit;
  while (IdentStart>1)
  and (IsIdentChar[Source[IdentStart-1]]) do
    dec(IdentStart);
  while (IdentEnd<=length(Source))
  and (IsIdentChar[Source[IdentEnd]]) do
    inc(IdentEnd);
  while (IdentStart<Position)
  and (not IsIdentStartChar[Source[IdentStart]]) do
    inc(IdentStart);
  if (IdentStart>1) and (Source[IdentStart-1]='&') then
    dec(IdentStart);
  if (IdentStart>length(Source)) or not IsIdentStartChar[Source[IdentStart]] then
    IdentEnd:=IdentStart;
end;

function GetIdentStartPosition(const Source: string; Position: integer
  ): integer;
begin
  Result:=Position;
  if (Result<1) or (Result>length(Source)+1) then exit;
  while (Result>1)
  and (IsIdentChar[Source[Result-1]]) do
    dec(Result);
  while (Result<Position)
  and (not IsIdentStartChar[Source[Result]]) do
    inc(Result);
  if (Result>1) and (Source[Result-1]='&') then
    dec(Result);
end;

function GetIdentLen(Identifier: PChar): integer;
begin
  Result:=0;
  if Identifier=nil then exit;
  if not IsIdentStartChar[Identifier^] then exit;
  while (IsIdentChar[Identifier[Result]]) do inc(Result);
end;

function FindFirstProcSpecifier(const ProcText: string; NestedComments: boolean
  ): integer;
// length(ProcText)+1 on failure
var
  AtomStart: integer;
  p: Integer;
begin
  Result:=length(ProcText)+1;
  // read till first semicolon
  p:=1;
  while p<=length(ProcText) do begin
    ReadRawNextPascalAtom(ProcText,p,AtomStart,NestedComments,true);
    if AtomStart>length(ProcText) then exit;
    if ProcText[AtomStart] in ['[','('] then begin
      if not ReadTilPascalBracketClose(ProcText,p,NestedComments) then
        exit;
    end else if ProcText[AtomStart]=';' then begin
      ReadRawNextPascalAtom(ProcText,p,AtomStart,NestedComments,true);
      Result:=AtomStart;
      exit;
    end;
  end;
end;

function SearchProcSpecifier(const ProcText, Specifier: string; out
  SpecifierEndPosition: integer; NestedComments: boolean;
  WithSpaceBehindSemicolon: boolean): integer;
// Result = -1 on failure
// Result = start of Specifier on success
// SpecifierEndPosition on semicolon or >length(ProcText)
// if WithSpaceBehindSemicolon then SpecifierEndPosition is start of next specifier
var
  AtomStart: integer;
begin
  Result:=FindFirstProcSpecifier(ProcText,NestedComments);
  repeat
    if Result>length(ProcText) then exit(-1);
    ReadRawNextPascalAtom(ProcText,Result,AtomStart,NestedComments,true);
    if AtomStart>length(ProcText) then exit(-1);
    if CompareIdentifiers(@ProcText[AtomStart],@Specifier[1])=0 then begin
      Result:=AtomStart;
      break;
    end;
    if ProcText[AtomStart] in ['[','('] then begin
      if not ReadTilPascalBracketClose(ProcText,Result,NestedComments)
      then
        exit(-1);
    end;
  until false;
  SpecifierEndPosition:=Result;
  while (SpecifierEndPosition<=length(ProcText))
  and (ProcText[SpecifierEndPosition]<>';') do begin
    ReadRawNextPascalAtom(ProcText,SpecifierEndPosition,AtomStart,NestedComments,true);
    if AtomStart>length(ProcText) then exit;
    if ProcText[AtomStart] in ['[','('] then begin
      if not ReadTilPascalBracketClose(ProcText,SpecifierEndPosition,NestedComments)
      then
        exit(-1);
    end;
  end;
  if WithSpaceBehindSemicolon and (SpecifierEndPosition<=length(ProcText)) then
  begin
    SpecifierEndPosition:=FindLineEndOrCodeAfterPosition(ProcText,
                                       SpecifierEndPosition+1,0,NestedComments);
  end;
  //DebugLn(['SearchProcSpecifier ',copy(ProcText,Result,SpecifierEndPosition-Result)]);
end;

function RemoveProcSpecifier(const ProcText, Specifier: string;
  NestedComments: boolean): string;
var
  EndPos: integer;
  StartPos: LongInt;
begin
  Result:=ProcText;
  StartPos:=SearchProcSpecifier(Result,Specifier,EndPos,NestedComments);
  if StartPos>=1 then
    Result:=copy(Result,1,StartPos-1)+copy(Result,EndPos,length(Result));
end;

function ReadNextPascalAtom(const Source: string; var Position: integer; out
  AtomStart: integer; NestedComments: boolean; SkipDirectives: boolean): string;
begin
  ReadRawNextPascalAtom(Source,Position,AtomStart,NestedComments,SkipDirectives);
  Result:=copy(Source,AtomStart,Position-AtomStart);
end;

{$IFOPT R+}{$DEFINE RangeChecking}{$ENDIF}
{$R-}
procedure ReadRawNextPascalAtom(const Source: string;
  var Position: integer; out AtomStart: integer; NestedComments: boolean;
  SkipDirectives: boolean);
var
  Len:integer;
  SrcPos, SrcStart, SrcAtomStart: PChar;
begin
  Len:=length(Source);
  if Position>Len then begin
    Position:=Len+1;
    AtomStart:=Position;
    exit;
  end;
  SrcStart:=PChar(Source);
  SrcPos:=@Source[Position];
  ReadRawNextPascalAtom(SrcPos,SrcAtomStart,SrcStart+len,NestedComments,SkipDirectives);
  Position:=SrcPos-SrcStart+1;
  AtomStart:=SrcAtomStart-SrcStart+1;
end;
{$IFDEF RangeChecking}{$R+}{$UNDEF RangeChecking}{$ENDIF}

procedure ReadRawNextPascalAtom(var Position: PChar; out AtomStart: PChar;
  const SrcEnd: PChar; NestedComments: boolean; SkipDirectives: boolean);
var
  c1,c2:char;
  CommentLvl: Integer;
  Src: PChar;
begin
  Src:=Position;
  // read till next atom
  while true do begin
    case Src^ of
    #0:
      if (SrcEnd=nil) or (Src>=SrcEnd) then
        break
      else
        inc(Src);
    #1..#32:  // spaces and special characters
      inc(Src);
    #$EF:
      if (Src[1]=#$BB)
      and (Src[2]=#$BF) then begin
        // skip UTF BOM
        inc(Src,3);
      end else begin
        break;
      end;
    '{':    // comment start or compiler directive
      begin
        if (Src[1]='$') and (not SkipDirectives) then
          // compiler directive
          break
        else if Src[1]=#3 then begin
          // codetools comment => skip
          inc(Src,2);
          repeat
            case Src^ of
            #0:
              if (SrcEnd=nil) or (Src>=SrcEnd) then
                break;
            #3:
              if Src[1]='}' then begin
                inc(Src,2);
                break;
              end;
            end;
            inc(Src);
          until false;
        end else begin
          // Pascal comment => skip
          CommentLvl:=1;
          while true do begin
            inc(Src);
            case Src^ of
            #0:
              if (SrcEnd=nil) or (Src>=SrcEnd) then
                break;
            '{':
              if NestedComments then
                inc(CommentLvl);
            '}':
              begin
                dec(CommentLvl);
                if CommentLvl=0 then begin
                  inc(Src);
                  break;
                end;
              end;
            end;
          end;
        end;
      end;
    '/':  // comment or real division
      if (Src[1]='/') then begin
        // comment start -> read til line end
        inc(Src);
        while not (Src^ in [#0,#10,#13]) do
          inc(Src);
      end else
        break;
    '(':  // comment, bracket or compiler directive
      if (Src[1]='*') then begin
        if (Src[2]='$') and (not SkipDirectives) then
          // compiler directive
          break
        else begin
          // comment start -> read til comment end
          inc(Src,2);
          CommentLvl:=1;
          while true do begin
            case Src^ of
            #0:
              if (SrcEnd=nil) or (Src>=SrcEnd) then
                break
              else
                inc(Src);
            '(':
              if NestedComments and (Src[1]='*') then
                inc(CommentLvl);
            '*':
              if (Src[1]=')') then begin
                dec(CommentLvl);
                if CommentLvl=0 then begin
                  inc(Src,2);
                  break;
                end;
                inc(Position);
              end;
            end;
            inc(Src);
          end;
        end;
      end else
        // round bracket open
        break;
    else
      break;
    end;
  end;
  // read atom
  AtomStart:=Src;
  c1:=Src^;
  case c1 of
  #0:
    ;
  'A'..'Z','a'..'z','_':
    begin
      // identifier
      inc(Src);
      while IsIdentChar[Src^] do
        inc(Src);
    end;
  '0'..'9': // number
    begin
      inc(Src);
      // read numbers
      while (Src^ in ['0'..'9']) do
        inc(Src);
      if (Src^='.')
      and (Src[1]<>'.') then begin
        // real type number
        inc(Src);
        while (Src^ in ['0'..'9']) do
          inc(Src);
      end;
      if (Src^ in ['e','E']) then begin
        // read exponent
        inc(Src);
        if (Src^='-') then inc(Src);
        while (Src^ in ['0'..'9']) do
          inc(Src);
      end;
    end;
  '''','#':  // string constant
    begin
      while true do begin
        case Src^ of
        #0:
          if (SrcEnd=nil) or (Src>=SrcEnd) then
            break
          else
            inc(Src);
        '#':
          begin
            inc(Src);
            while Src^ in ['0'..'9'] do
              inc(Src);
          end;
        '''':
          begin
            inc(Src);
            while not (Src^ in ['''',#0]) do
              inc(Src);
            if Src^='''' then
              inc(Src);
          end;
        else
          break;
        end;
      end;
    end;
  '$':  // hex constant
    begin
      inc(Src);
      while IsHexNumberChar[Src^] do
        inc(Src);
    end;
  '&':  // octal constant or keyword as identifier (e.g. &label)
    begin
      inc(Src);
      if Src^ in ['0'..'7'] then begin
        while Src^ in ['0'..'7'] do
          inc(Src);
      end else begin
        while IsIdentChar[Src^] do
          inc(Src);
      end;
    end;
  '{':  // compiler directive (it can not be a comment, because see above)
    begin
      CommentLvl:=1;
      while true do begin
        inc(Src);
        case Src^ of
        #0:
          if (SrcEnd=nil) or (Src>=SrcEnd) then
            break;
        '{':
          if NestedComments then
            inc(CommentLvl);
        '}':
          begin
            dec(CommentLvl);
            if CommentLvl=0 then begin
              inc(Src);
              break;
            end;
          end;
        end;
      end;
    end;
  '(':  // bracket or compiler directive
    if (Src[1]='*') then begin
      // compiler directive -> read til comment end
      inc(Src,2);
      while (Src^<>#0) and ((Src^<>'*') or (Src[1]<>')')) do
        inc(Src);
      inc(Src,2);
    end else
      // round bracket open
      inc(Src);
  #192..#255:
    begin
      // read UTF8 character
      inc(Src);
      if ((ord(c1) and %11100000) = %11000000) then begin
        // could be 2 byte character
        if (ord(Src[0]) and %11000000) = %10000000 then
          inc(Src);
      end
      else if ((ord(c1) and %11110000) = %11100000) then begin
        // could be 3 byte character
        if ((ord(Src[0]) and %11000000) = %10000000)
        and ((ord(Src[1]) and %11000000) = %10000000) then
          inc(Src,2);
      end
      else if ((ord(c1) and %11111000) = %11110000) then begin
        // could be 4 byte character
        if ((ord(Src[0]) and %11000000) = %10000000)
        and ((ord(Src[1]) and %11000000) = %10000000)
        and ((ord(Src[2]) and %11000000) = %10000000) then
          inc(Src,3);
      end;
    end;
  else
    inc(Src);
    c2:=Src^;
    // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **
    if ((c2='=') and  (IsEqualOperatorStartChar[c1]))
    or ((c1='<') and (c2='>'))
    or ((c1='.') and (c2='.'))
    or ((c1='*') and (c2='*'))
    then
      inc(Src)
    else if ((c1='@') and (c2='@')) then begin
      // @@ label
      repeat
        inc(Src);
      until (not IsIdentChar[Src^]);
    end;
  end;
  Position:=Src;
end;

procedure ReadPriorPascalAtom(const Source: string; var Position: integer; out
  AtomEnd: integer; NestedComments: boolean);
var
  CommentLvl, PrePos, OldPrePos: integer;
  IsStringConstant: boolean;

  procedure ReadStringConstantBackward;
  var PrePos: integer;
  begin
    while (Position>1) do begin
      case Source[Position-1] of
      '''':
        begin
          dec(Position);
          repeat
            dec(Position);
          until (Position<1) or (Source[Position] in [#0,#10,#13,'''']);
        end;
      '0'..'9','A'..'Z','a'..'z':
        begin
          // test if char constant
          PrePos:=Position-1;
          while (PrePos>1) and (IsHexNumberChar[Source[PrePos]]) do
            dec(PrePos);
          if (PrePos<1) then break;
          if (Source[PrePos]='$') then begin
            dec(PrePos);
            if (PrePos<1) then break;
          end;
          if (Source[PrePos]='#') then
            Position:=PrePos
          else
            break;
        end;
      else
        break;
      end;
    end;
  end;

  procedure ReadBackTilCodeLineEnd;
  begin
    dec(Position);
    if (Position>=1) and (Source[Position] in [#10,#13])
    and (Source[Position+1]<>Source[Position]) then
      dec(Position);

    // read backwards till line start
    PrePos:=Position;
    while (PrePos>=1) and (not (Source[PrePos] in [#10,#13])) do
      dec(PrePos);
    // read line forward to find out,
    // if line ends in comment or string constant
    IsStringConstant:=false;
    repeat
      inc(PrePos);
      case Source[PrePos] of

      '/':
        if Source[PrePos+1]='/' then begin
          // this was a delphi comment -> skip comment
          Position:=PrePos-1;
          break;
        end;

      '{':
        begin
          inc(PrePos);
          if (PrePos<=Position) and (Source[PrePos]=#3) then begin
            // skip codetools comment
            inc(PrePos);
            while (PrePos<=Position) do begin
              if (Source[PrePos]=#3) and (PrePos<Position)
              and (Source[PrePos+1]='}') then begin
                inc(PrePos,2);
                break;
              end;
              inc(PrePos);
            end;
          end else begin
            // skip pascal comment
            CommentLvl:=1;
            while (PrePos<=Position) do begin
              case Source[PrePos] of
              '{': if NestedComments then inc(CommentLvl);
              '}':
                begin
                  dec(CommentLvl);
                  if CommentLvl=0 then break;
                end;
              end;
              inc(PrePos);
            end;
          end;
        end;

      '(':
        if Source[PrePos+1]='*' then begin
          // skip turbo pascal comment
          inc(PrePos,2);
          while (PrePos<Position)
          and ((Source[PrePos]<>'*') or (Source[PrePos+1]<>')')) do
            inc(PrePos);
          inc(PrePos);
        end;

      '''':
        begin
          // a string constant -> skip it
          OldPrePos:=PrePos;
          while (PrePos<Position) do begin
            inc(PrePos);
            case Source[PrePos] of
            '''':
              break;

            #0,#10,#13:
              begin
                // string constant right border is the line end
                // -> last atom of line found
                IsStringConstant:=true;
                break;
              end;

            end;
          end;
          if IsStringConstant then break;
        end;

      #10,#13:
        // no comment and no string constant found
        break;

      end;
    until PrePos>=Position;
  end;

type
  TNumberType = (ntDecimal, ntHexadecimal, ntBinary, ntIdentifier,
    ntCharConstant, ntFloat, ntFloatWithExponent);
  TNumberTypes = set of TNumberType;

const
  AllNumberTypes: TNumberTypes = [ntDecimal, ntHexadecimal, ntBinary,
    ntIdentifier, ntCharConstant, ntFloat, ntFloatWithExponent];

var c1, c2: char;
  ForbiddenNumberTypes: TNumberTypes;
begin
  // Skip all spaces and comments
  CommentLvl:=0;
  dec(Position);
  IsStringConstant:=false;
  OldPrePos:=0;
  while Position>=1 do begin
    if IsCommentEndChar[Source[Position]] then begin
      case Source[Position] of

      '}':
        begin
          dec(Position);
          if (Position>=1) and (Source[Position]=#3) then begin
            // codetools skip comment {#3 #3}
            dec(Position);
            while (Position>=1) do begin
              if (Source[Position]=#3) and (Position>1)
              and (Source[Position-1]='}') then begin
                dec(Position,2);
                break;
              end;
              dec(Position);
            end;
          end else begin
            // pascal comment {}
            CommentLvl:=1;
            while (Position>=1) and (CommentLvl>0) do begin
              case Source[Position] of
              '}': if NestedComments then inc(CommentLvl);
              '{': dec(CommentLvl);
              end;
              dec(Position);
            end;
          end;
        end;

      #10,#13: // possible Delphi comment
        ReadBackTilCodeLineEnd;

      ')': // old turbo pascal comment
        if (Position>1) and (Source[Position-1]='*') then begin
          dec(Position,3);
          while (Position>=1)
          and ((Source[Position]<>'(') or (Source[Position+1]<>'*')) do
            dec(Position);
          dec(Position);
        end else
          break;

      end;
    end else if IsSpaceChar[Source[Position]] then begin
      repeat
        dec(Position);
      until (Position<1) or (Source[Position] in [#10,#13])
      or (not (IsSpaceChar[Source[Position]]));
    end else begin
      break;
    end;
  end;
  // Position now points to the last char of the prior atom
  AtomEnd:=Position+1;
  if Position<1 then begin
    Position:=1;
    AtomEnd:=1;
    exit;
  end;
  // read atom
  if IsStringConstant then begin
    Position:=OldPrePos;
    if (Position>1) and (Source[Position-1]='''') then begin
      ReadStringConstantBackward;
    end;
    exit;
  end;
  c2:=Source[Position];
  case c2 of
    '_','A'..'Z','a'..'z':
      begin
        // identifier or keyword or hexnumber
        while (Position>1) do begin
          if (IsIdentChar[Source[Position-1]]) then
            dec(Position)
          else begin
            case UpChars[Source[Position-1]] of
            '@':
              // assembler label
              if (Position>2)
              and (Source[Position-2]='@') then
                dec(Position,2);
            '$':
              // hex number
              dec(Position);
            end;
            break;
          end;
        end;
      end;
    '''':
      begin
        inc(Position);
        ReadStringConstantBackward;
      end;
    '0'..'9':
      begin
        // could be a decimal number, an identifier, a hex number,
        // a binary number, a char constant, a float, a float with exponent
        ForbiddenNumberTypes:=[];
        while true do begin
          case UpChars[Source[Position]] of
          '0'..'1':
            ;
          '2'..'9':
            ForbiddenNumberTypes:=ForbiddenNumberTypes+[ntBinary];
          'A'..'D','F':
            ForbiddenNumberTypes:=ForbiddenNumberTypes
               +[ntBinary,ntDecimal,ntCharConstant,ntFloat,ntFloatWithExponent];
          'E':
            ForbiddenNumberTypes:=ForbiddenNumberTypes
               +[ntBinary,ntDecimal,ntCharConstant,ntFloat];
          'G'..'Z','_':
            ForbiddenNumberTypes:=AllNumberTypes-[ntIdentifier];
          '.':
            begin
              // could be the point of a float
              if (ntFloat in ForbiddenNumberTypes)
              or (Position<=1) or (Source[Position-1]='.') then begin
                inc(Position);
                break;
              end;
              dec(Position);
              // this was the part of a float after the point
              //  -> read decimal in front
              ForbiddenNumberTypes:=AllNumberTypes-[ntDecimal];
            end;
          '+','-':
            begin
              // could be part of an exponent
              if (ntFloatWithExponent in ForbiddenNumberTypes)
              or (Position<=1)
              or (not (Source[Position-1] in ['e','E']))
              then begin
                inc(Position);
                break;
              end;
              dec(Position);
              // this was the exponent of a float -> read the float
              ForbiddenNumberTypes:=AllNumberTypes-[ntFloat];
            end;
          '#': // char constant found
            begin
              if (ntCharConstant in ForbiddenNumberTypes) then
                inc(Position);
              ReadStringConstantBackward;
              break;
            end;
          '$':
            begin
              // hexadecimal number found
              if (ntHexadecimal in ForbiddenNumberTypes) then
                inc(Position);
              break;
            end;
          '%':
            begin
              // binary number found
              if (ntBinary in ForbiddenNumberTypes) then
                inc(Position);
              break;
            end;
          '@':
            begin
              if (Position=1) or (Source[Position-1]<>'@')
              or (([ntIdentifier,ntDecimal]*ForbiddenNumberTypes)=[]) then
                // atom start found
                inc(Position)
              else
                // label found
                dec(Position);
              break;
            end;
          else
            begin
              inc(Position);
              break;
            end;
          end;
          if ForbiddenNumberTypes=AllNumberTypes then begin
            inc(Position);
            break;
          end;
          if Position<=1 then break;
          dec(Position);
        end;
        if IsIdentStartChar[Source[Position]] then begin
          // it is an identifier
        end;
      end;

    ';': ;
    ':': ;
    ',': ;
    '(': ;
    ')': ;
    '[': ;
    ']': ;

    else
      begin
        if Position>1 then begin
          c1:=Source[Position-1];
          // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, ><
          if ((c2='=') and  (IsEqualOperatorStartChar[c1]))
          or ((c1='<') and (c2='>'))
          or ((c1='>') and (c2='<'))
          or ((c1='.') and (c2='.'))
          or ((c1='*') and (c2='*'))
          or ((c1='@') and (c2='@'))
          then begin
            dec(Position);
          end;
        end;
      end;
  end;
end;

function ReadTilPascalBracketClose(const Source: string; var Position: integer;
  NestedComments: boolean): boolean;
// Input: Position points right after the opening bracket
// Output: Position points right after the closing bracket
var
  CloseBracket: Char;
  AtomStart: LongInt;
  Len: Integer;
begin
  Result:=false;
  Len:=length(Source);
  if Position>Len+1 then
    exit;  // no bracket open found
  case Source[Position-1] of
  '{': CloseBracket:='}';
  '(': CloseBracket:=')';
  '[': CloseBracket:=']';
  else
    exit; // no bracket open found
  end;
  AtomStart:=Position;
  while Position<=Len do begin
    ReadRawNextPascalAtom(Source,Position,AtomStart,NestedComments,true);
    //DebugLn(['ReadTilPascalBracketClose ',copy(Source,AtomStart,Position-AtomStart)]);
    if Position>Len then
      exit; // CloseBracket not found
    case Source[AtomStart] of
    '{','(','[':
      begin
        if not ReadTilPascalBracketClose(Source,Position) then exit;
      end;
    '}',')',']':
      if Source[AtomStart]=CloseBracket then begin
        // CloseBracket found
        Result:=true;
        exit;
      end else begin
        exit; // a bracket is closed, that was never opened
      end;
    end;
  end;
end;

function GetAtomLength(p: PChar; NestedComments: boolean): integer;
var
  c1: Char;
  CommentLvl: Integer;
  c2: Char;
  OldP: PChar;
begin
  OldP:=p;
  // read atom
  c1:=p^;
  case c1 of
   'A'..'Z','a'..'z','_':
    begin
      // identifier
      inc(p);
      while (IsIdentChar[p^]) do
        inc(p);
    end;
   '0'..'9': // number
    begin
      inc(p);
      // read numbers
      while (p^ in ['0'..'9']) do
        inc(p);
      if (p^='.')
      and (p[1]<>'.') then begin
        // real type number
        inc(p);
        while (p^ in ['0'..'9']) do
          inc(p);
      end;
      if (p^ in ['e','E']) then begin
        // read exponent
        inc(p);
        if (p^='-') then inc(p);
        while (p^ in ['0'..'9']) do
          inc(p);
      end;
    end;
   '''','#':  // string constant
    begin
      while true do begin
        case p^ of
        '#':
          begin
            inc(p);
            while (p^ in ['0'..'9']) do
              inc(p);
          end;
        '''':
          begin
            inc(p);
            while not (p^ in ['''',#10,#13]) do
              inc(p);
            inc(p);
          end;
        else
          break;
        end;
      end;
    end;
   '$':  // hex constant
    begin
      inc(p);
      while (IsHexNumberChar[p^]) do
        inc(p);
    end;
   '{':  // compiler directive
    begin
      CommentLvl:=1;
      while true do begin
        inc(p);
        case p^ of
        #0:  break;
        '{': if NestedComments then
            inc(CommentLvl);
        '}':
          begin
            dec(CommentLvl);
            if CommentLvl=0 then begin
              inc(p);
              break;
            end;
          end;
        end;
      end;
    end;
   '(':  // bracket or compiler directive
    begin
      inc(p);
      if (p^<>'*') then begin
        // round bracket open
      end else begin
        // comment
        CommentLvl:=1;
        inc(p);
        while true do begin
          case p^ of
          #0: break;
          '*':
            if p[1]=')' then begin
              inc(p,2);
              dec(CommentLvl);
              if CommentLvl=0 then
                break;
            end else
              inc(p);
          '(':
            if (p[1]='*') and NestedComments then begin
              inc(CommentLvl);
              inc(p,2);
            end else
              inc(p);
          else
            inc(p);
          end;
        end;
      end;
    end;
  else
    inc(p);
    c2:=p^;
    // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, .., ><
    if ((c2='=') and  (IsEqualOperatorStartChar[c1]))
    or ((c1='<') and (c2='>'))
    or ((c1='>') and (c2='<'))
    or ((c1='.') and (c2='.'))
    or ((c1='*') and (c2='*'))
    then
      inc(p)
    else if ((c1='@') and (c2='@')) then begin
      // @@ label
      repeat
        inc(p);
      until (not IsIdentChar[p^]);
    end;
  end;
  Result:=P-OldP;
end;

function GetAtomString(p: PChar; NestedComments: boolean): string;
var
  l: LongInt;
begin
  if p=nil then exit('');
  l:=GetAtomLength(p,NestedComments);
  SetLength(Result,l);
  if l>0 then
    System.Move(p^,Result[1],length(Result));
end;

function FindStartOfAtom(const Source: string; Position: integer): integer;

  procedure ReadStringConstantBackward(var p: integer);
  var
    PrePos: integer;
    StartPos: LongInt;
  begin
    StartPos:=p;
    while (p>1) do begin
      case Source[p-1] of
      '''':
        begin
          PrePos:=p;
          dec(PrePos);
          repeat
            dec(PrePos);
            if (PrePos<1) or (Source[PrePos] in [#10,#13]) then begin
              // the StartPos was the start of a string constant
              p:=StartPos-1;
              exit;
            end;
          until (Source[PrePos]='''');
          p:=PrePos;
        end;
      '0'..'9','A'..'Z','a'..'z':
        begin
          // test if char constant
          PrePos:=p-1;
          while (PrePos>1) and (IsHexNumberChar[Source[PrePos]]) do
            dec(PrePos);
          if (PrePos<1) then break;
          if (Source[PrePos]='$') then begin
            dec(PrePos);
            if (PrePos<1) then break;
          end;
          if (Source[PrePos]='#') then
            p:=PrePos
          else
            break;
        end;
      else
        break;
      end;
    end;
  end;

type
  TNumberType = (ntDecimal, ntHexadecimal, ntBinary, ntIdentifier,
    ntCharConstant, ntFloat, ntFloatWithExponent);
  TNumberTypes = set of TNumberType;

const
  AllNumberTypes: TNumberTypes = [ntDecimal, ntHexadecimal, ntBinary,
    ntIdentifier, ntCharConstant, ntFloat, ntFloatWithExponent];
var
  c: Char;
  ForbiddenNumberTypes: TNumberTypes;
  c2: Char;
begin
  Result:=Position;
  if (Result<1) then exit;
  if Result>length(Source) then begin
    Result:=length(Source);
    exit;
  end;

  c:=Source[Result];
  case c of
  '_','A'..'Z','a'..'z':
    begin
      // identifier or keyword or hexnumber
      while (Result>1) do begin
        if (IsIdentChar[Source[Result-1]]) then
          dec(Result)
        else begin
          case UpChars[Source[Result-1]] of
          '@':
            // assembler label
            if (Result>2)
            and (Source[Result-2]='@') then
              dec(Result,2);
          '$':
            // hex number
            dec(Result);
          '&':
            // &keyword
            dec(Result);
          end;
          break;
        end;
      end;
    end;
  '''':
    begin
      // could be start or end
      inc(Result);
      ReadStringConstantBackward(Result);
    end;
  '0'..'9':
    begin
      // could be a decimal number, an identifier, a hex number,
      // a binary number, a char constant, a float, a float with exponent
      ForbiddenNumberTypes:=[];
      while true do begin
        case UpChars[Source[Result]] of
        '0'..'1':
          ;
        '2'..'9':
          ForbiddenNumberTypes:=ForbiddenNumberTypes+[ntBinary];
        'A'..'D','F':
          ForbiddenNumberTypes:=ForbiddenNumberTypes
             +[ntBinary,ntDecimal,ntCharConstant,ntFloat,ntFloatWithExponent];
        'E':
          ForbiddenNumberTypes:=ForbiddenNumberTypes
             +[ntBinary,ntDecimal,ntCharConstant,ntFloat];
        'G'..'Z','_':
          ForbiddenNumberTypes:=AllNumberTypes-[ntIdentifier];
        '.':
          begin
            // could be the point of a float
            if (ntFloat in ForbiddenNumberTypes)
            or (Result<=1) or (Source[Result-1]='.') then begin
              inc(Result);
              break;
            end;
            dec(Result);
            // this was the part of a float after the point
            //  -> read decimal in front
            ForbiddenNumberTypes:=AllNumberTypes-[ntDecimal];
          end;
        '+','-':
          begin
            // could be part of an exponent
            if (ntFloatWithExponent in ForbiddenNumberTypes)
            or (Result<=1)
            or (not (Source[Result-1] in ['e','E']))
            then begin
              inc(Result);
              break;
            end;
            dec(Result);
            // this was the exponent of a float -> read the float
            ForbiddenNumberTypes:=AllNumberTypes-[ntFloat];
          end;
        '#': // char constant found
          begin
            if (ntCharConstant in ForbiddenNumberTypes) then
              inc(Result);
            ReadStringConstantBackward(Result);
            break;
          end;
        '$':
          begin
            // hexadecimal number found
            if (ntHexadecimal in ForbiddenNumberTypes) then
              inc(Result);
            break;
          end;
        '%':
          begin
            // binary number found
            if (ntBinary in ForbiddenNumberTypes) then
              inc(Result);
            break;
          end;
        '@':
          begin
            if (Result=1) or (Source[Result-1]<>'@')
            or (([ntIdentifier,ntDecimal]*ForbiddenNumberTypes)=[]) then
              // atom start found
              inc(Result)
            else
              // label found
              dec(Result);
            break;
          end;
        else
          begin
            inc(Result);
            break;
          end;
        end;
        if ForbiddenNumberTypes=AllNumberTypes then begin
          inc(Result);
          break;
        end;
        if Result<=1 then break;
        dec(Result);
      end;
      if IsIdentStartChar[Source[Result]] then begin
        // it is an identifier
      end;
    end;

  else
    if Result>1 then begin
      c2:=Source[Result-1];
      // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, ><, @@ ..
      if ((c2='=') and  (IsEqualOperatorStartChar[c]))
      or ((c='<') and (c2='>'))
      or ((c='>') and (c2='<'))
      or ((c='.') and (c2='.'))
      or ((c='*') and (c2='*'))
      or ((c='@') and (c2='@'))
      then begin
        dec(Result);
      end;
    end;
  end;
end;

function FindEndOfAtom(const Source: string; Position: integer): integer;
// comments have length 0
var
  p: PChar;
begin
  Result:=FindStartOfAtom(Source,Position);
  if (Result>=1) and (Result<=length(Source)) then begin
    p:=@Source[Result];
    case p^ of
    #0..#31,' ': exit;
    '{': exit;
    '/': if p[1]='/' then exit;
    '(': if p[1]='*' then exit;
    end;
    inc(Result,GetAtomLength(p,false));
  end;
end;

function LineEndCount(const Txt: string;
  out LengthOfLastLine: integer): integer;
begin
  Result:=LineEndCount(Txt,1,length(Txt)+1,LengthOfLastLine);
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-1] in [#10,#13])) do
    dec(Result);
  // search
  while (Result<=length(Source)) and (Source[Result] in [' ',#9]) do inc(Result);
end;

function GetLineIndent(const Source: string; Position: integer): integer;
var LineStart: integer;
begin
  Result:=0;
  LineStart:=Position;
  if LineStart=0 then exit;
  if (LineStart<0) then LineStart:=1;
  if (LineStart>length(Source)+1) then LineStart:=length(Source)+1;
  // search beginning of line
  while (LineStart>1) and not (Source[LineStart-1] in [#10,#13]) do
    dec(LineStart);
  // search code
  Result:=LineStart;
  while (Result<=length(Source)) and (Source[Result]=' ') do inc(Result);
  dec(Result,LineStart);
end;

function FindLineEndOrCodeAfterPosition(const Source: string;
   Position, MaxPosition: integer; NestedComments: boolean;
   StopAtDirectives: boolean; SkipEmptyLines: boolean;
   IncludeLineEnd: boolean): integer;
{ search forward for a line end or code
  ignore line ends in comments
  Result is Position of Start of Line End
  
  if SkipEmptyLines=true, it will skip empty lines at the end
  
  Examples: | is the Position and # is the Result
  
  1. var i: integer;|#
     var j: integer;

     If IncludeLineEnd then
            var i: integer;|
          #  var j: integer;
     
  2. var i: integer;| (*
     *) #var j: integer;
     
  3. SkipEmptyLines=false
     var i: integer;|
     #
     // comment
     var j: integer;

     if IncludeLineEnd then the # will be one line below
     
  4. SkipEmptyLines=true
     var i: integer;|

     #// comment
     var j: integer;
}
var SrcLen: integer;

  procedure DoSkipEmptyLines(var p: integer);
  var
    r: LongInt;
  begin
    r:=p;
    repeat
      while (r<=SrcLen) and (Source[r] in [' ',#9]) do inc(r);
      if (r<=SrcLen) and (Source[r] in [#10,#13]) then begin
        // an empty line => skip
        p:=r;// remember position in front of new line characters
        inc(r);
        if (r<=SrcLen) and (Source[r] in [#10,#13]) and (Source[r]<>Source[r-1])
        then
          inc(r);
      end else begin
        exit;
      end;
    until false;
  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
      '/':
        if (Result<SrcLen) and (Source[Result+1]='/') then
          Result:=FindCommentEnd(Source,Result,NestedComments)
        else
          inc(Result);
      '{':
        if (Result<SrcLen) and (Source[Result+1]='$') and StopAtDirectives then
          exit  // stop at directive {$ }
        else
          Result:=FindCommentEnd(Source,Result,NestedComments);
      '(':
        if (Result<SrcLen) and (Source[Result+1]='*') then begin
          if (Result+1<SrcLen) and (Source[Result+2]='$') and StopAtDirectives then
            exit;  // stop at directive (*$ *)
          Result:=FindCommentEnd(Source,Result,NestedComments);
        end else
          inc(Result); // normal bracket (
      #10,#13:
        begin
          if SkipEmptyLines then DoSkipEmptyLines(Result);
          if IncludeLineEnd and (Result<=SrcLen) and (Source[Result] in [#10,#13])
          then begin
            inc(Result);
            if (Result<=SrcLen) and (Source[Result] in [#10,#13])
            and (Source[Result-1]<>Source[Result]) then
              inc(Result);
          end;
          exit;
        end;
      #9,' ',';':
        inc(Result);
    else
      exit;
    end;
  end;
end;

function IsFirstNonSpaceCharInLine(const Source: string; Position: integer
  ): boolean;
begin
  while (Position>1) and (Source[Position-1] in [' ',#9]) do
    dec(Position);
  Result:=(Position=1) or (Source[Position-1] in [#10,#13]);
end;

procedure GuessIndentSize(const Source: string; var IndentSize: integer;
  TabWidth: integer; MaxLineCount: integer = 10000);
{ check all line indents and return the most common.
  Stop after MaxLineCount lines. Ignore empty lines.
}
const
  MaxIndentSize = 20;
var
  IndentCounts: PSizeInt;
  BestCount: SizeInt;

  procedure AddIndent(CurIndent: integer);
  var
    i: SizeInt;
  begin
    if (CurIndent<1) or (CurIndent>MaxIndentSize) then exit;
    i:=IndentCounts[CurIndent-1]+1;
    IndentCounts[CurIndent-1]:=i;
    if BestCount>=i then
      exit;
    IndentSize:=CurIndent;
    BestCount:=i;
  end;

var
  LineNumber: Integer;
  p: PChar;
  CurLineIndent: Integer;
  LastLineIndent: Integer;
begin
  LineNumber:=0;
  if Source='' then exit;
  if TabWidth<=0 then TabWidth:=8;
  Getmem(IndentCounts,SizeOf(SizeInt)*MaxIndentSize);
  try
    FillByte(IndentCounts[0],SizeOf(SizeInt)*MaxIndentSize,0);
    BestCount:=0;
    p:=PChar(Source);
    LastLineIndent:=0;
    repeat
      inc(LineNumber);
      // read indent
      CurLineIndent:=0;
      repeat
        case p^ of
        ' ': inc(CurLineIndent);
        #9:
          begin
            CurLineIndent+=TabWidth;
            CurLineIndent-=(CurLineIndent mod TabWidth);
          end;
        else break;
        end;
        inc(p);
      until false;
      if not (p^ in [#0,#10,#13]) then begin
        // not an empty line
        AddIndent(CurLineIndent-LastLineIndent);
        LastLineIndent:=CurLineIndent;
      end;
      // skip to next line
      repeat
        case p^ of
        #0:
          if p-PChar(Source)=length(Source) then begin
            // end of soure
            exit;
          end;
        #10,#13:
          begin
            // line break
            repeat
              inc(p)
            until not (p^ in [#10,#13]);
            break;
          end;
        end;
        inc(p);
      until false;
    until LineNumber>MaxLineCount;
  finally
    FreeMem(IndentCounts);
  end;
end;

function ReIndent(const Source: string; OldIndent, OldTabWidth,
  NewIndent, NewTabWidth: integer): string;
{ NewTabWidth = 0 means using spaces
}
var
  Src: PChar;
  SrcIndent: Integer;
  DstIndent: Integer;
  i: Integer;
  Dst: PChar;

  procedure Grow;
  var
    Old: PtrUInt;
  begin
    if (Dst^<>#0) or (Dst-PChar(Result)<>length(Result)) then exit;
    // grow
    Old:=Dst-PChar(Result);
    SetLength(Result,(length(Result)*3) div 2);
    FillByte(Result[Old+1],length(Result)-Old,ord('A'));
    Dst:=PChar(Result)+Old;
  end;

  procedure Add(c: char); inline;
  begin
    //debugln(['Add c="',DbgStr(c),'"']);
    if (Dst^=#0) then
      Grow;
    Dst^:=c;
    inc(Dst);
  end;

begin
  Result:=Source;
  if (Result='') or (OldIndent<=0) or (OldTabWidth<0)
  or (NewIndent<0) or (NewTabWidth<0) then exit;
  UniqueString(Result);
  Src:=PChar(Source);
  Dst:=PChar(Result);
  repeat
    // read indent
    SrcIndent:=0;
    repeat
      case Src^ of
      ' ': inc(SrcIndent);
      #9:
        begin
          SrcIndent:=SrcIndent+OldTabWidth;
          SrcIndent:=SrcIndent-(SrcIndent mod SrcIndent);
        end;
      else break;
      end;
      inc(Src);
    until false;
    // write indent
    DstIndent:=((SrcIndent+OldIndent-1) div OldIndent)*NewIndent;
    //debugln(['ReIndent DstIndent=',DstIndent,' Src=',dbgstr(Src^),' at ',Src-PChar(Source)]);
    if NewTabWidth>0 then begin
      for i:=1 to (DstIndent div NewTabWidth) do
        Add(#9);
      for i:=1 to (DstIndent mod NewTabWidth) do
        Add(' ');
    end else begin
      for i:=1 to DstIndent do
        Add(' ');
    end;
    // copy line
    repeat
      case Src^ of
      #0: if Src-PChar(Source)=length(Source) then break;
      #10,#13: break;
      end;
      Add(Src^);
      inc(Src);
    until false;
    // copy line break
    while Src^ in [#10,#13] do begin
      Add(Src^);
      inc(Src);
    end;
  until (Src^=#0) and (Src-PChar(Source)=length(Source));
  SetLength(Result,Dst-PChar(Result));
end;

function FindLineEndOrCodeInFrontOfPosition(const Source: string;
  Position, MinPosition: integer; NestedComments: boolean;
  StopAtDirectives: boolean; SkipSemicolonComma: boolean;
  SkipEmptyLines: 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)
  comment lines directly in front are skipped too
  if SkipEmptyLines=true then empty lines are skipped too.
  Result is Position of Start of Line End

  examples: Position points at char 'a'

    1:  |
    2: a:=1;

    1:  b:=1; |
    2:  // comment for below
    3:  // comment for below
    4:  a:=1;

    1:  |
    2: (* comment belongs to the following line *)
    3:  a:=1;

    1: end; (* comment belongs to the first line
    2: *)| a:=1;

    1: b:=1; // comment |
    2: a:=1;

    1: b:=1; (*
    2: comment *)   |
    3: a:=1;
}
var SrcStart: integer;

  function IsSpace(c: char): boolean;
  begin
    if SkipSemicolonComma then
      Result:=c in [' ',#9,';',',']
    else
      Result:=c in [' ',#9];
  end;

  function ReadComment(var p: integer; SubComment: boolean): boolean;
  // true if comment was skipped
  // false if not skipped, because comment is compiler directive or simple bracket
  var OldP: integer;
    IsDirective: Boolean;
  begin
    //debugln(['ReadComment ',dbgstr(copy(Source,p-5,5))+'|'+Source[p]+dbgstr(copy(Source,p+1,5))]);
    OldP:=p+1;
    repeat
      IsDirective:=false;
      case Source[p] of
        '}':
          begin
            dec(p);
            if (p>SrcStart) and (Source[p]=#3) then begin
              // codetools skip comment
              dec(p);
              while (p>SrcStart) do begin
                if (Source[p]=#3)
                and (Source[p-1]='{') then begin
                  dec(p);
                  break;
                end;
                dec(p);
              end;
            end else begin
              // Pascal comment {}
              while (p>SrcStart) and (Source[p]<>'{') do begin
                if NestedComments and (Source[p]='}') then
                  ReadComment(p,true)
                else
                  dec(p);
              end;
              IsDirective:=(p>=SrcStart) and (Source[p+1]='$');
              dec(p);
            end;
          end;
        ')':
          begin
            dec(p);
            if (p>SrcStart) and (Source[p]='*') then begin
              // tp comment (* *)
              dec(p);
              while (p>SrcStart)
              and ((Source[p-1]<>'(') or (Source[p]<>'*')) do begin
                if NestedComments and ((Source[p]=')') and (Source[p-1]='*')) then
                  ReadComment(p,true)
                else
                  dec(p);
              end;
              IsDirective:=(p>=SrcStart) and (Source[p+1]='$');
              dec(p,2);
            end else begin
              // normal bracket
              // => position behind code
              p:=OldP;
              exit(false);
            end;
          end;
      else
        exit(true);
      end;
      if SubComment then exit(true); // always skip nested comments
      if IsDirective and StopAtDirectives then begin
        // directive can not be skipped
        p:=OldP;
        exit(false);
      end;
      // it is a normal comment
      // check if it belongs to the code in front
      while (p>=SrcStart) and IsSpace(Source[p]) do
        dec(p);
      if (p<SrcStart) or (Source[p] in [#10,#13]) then begin
        // empty line in front of comment => comment can be skipped
        exit(true);
      end;
      if not (Source[p] in [')','}']) then begin
        // code => comment belongs to code in front
        p:=OldP;
        exit(false);
      end;
      // read next comment
    until false;
  end;

var
  TestPos: integer;
  LineStartPos: LongInt;
  IsEmpty: Boolean;
  LineEndPos: Integer;
  p: LongInt;
begin
  SrcStart:=MinPosition;
  if SrcStart<1 then SrcStart:=1;
  if (Position<=SrcStart) then begin
    Result:=SrcStart;
    exit;
  end;
  // simple heuristic
  // will fail on lines: // }
  Result:=-1;
  p:=Position-1;
  if p>length(Source) then p:=length(Source);
  while (p>=SrcStart) do begin
    case Source[p] of
      #10,#13:
        begin
          // line end found (outside comments)
          if (p>SrcStart) and (Source[p-1] in [#10,#13])
          and (Source[p]<>Source[p-1]) then
            dec(p);
          LineEndPos:=p; // start of line end
          Result:=LineEndPos;
          // test if in a // comment
          LineStartPos:=p;
          IsEmpty:=true;
          while (LineStartPos>SrcStart) do begin
            case Source[LineStartPos-1] of
              #10,#13: break;
              ' ',#9: ;
              ';',',': if not SkipSemicolonComma then IsEmpty:=false;
            else IsEmpty:=false;
            end;
            dec(LineStartPos);
          end;
          if IsEmpty then begin
            // the line is empty => return start of line end
            p:=LineEndPos;
            if SkipEmptyLines then begin
              // skip all empty lines
              LineStartPos:=p;
              while (LineStartPos>SrcStart) do begin
                case Source[LineStartPos-1] of
                  #10,#13:
                    begin
                      // empty line
                      LineEndPos:=LineStartPos-1;
                      if (LineEndPos>SrcStart) and (Source[LineEndPos-1] in [#10,#13])
                      and (Source[LineEndPos]<>Source[LineEndPos-1]) then
                        dec(LineEndPos);
                      p:=LineEndPos;
                    end;
                  ' ',#9: ;
                else
                  // not empty
                  break;
                end;
                dec(LineStartPos);
              end;
            end;
            break;
          end;
          // line is not empty
          TestPos:=LineStartPos;
          while (Source[TestPos] in [' ',#9]) do inc(TestPos);
          if (Source[TestPos]='/') and (Source[TestPos+1]='/') then begin
            // the whole line is a // comment
            // this comment belongs to the code behind
            // => continue on next line
            p:=LineStartPos-1;
            continue;
          end;
          dec(p);
        end;

      '}',')':
        if not ReadComment(p,false) then break;
      ' ',#9:
        dec(p);
      ';',',':
        begin
          if not SkipSemicolonComma then begin
            // code found
            inc(p);
            break;
          end;
          dec(p);
        end;
    else
      // code found
      inc(p);
      break;
    end;
  end;
  if Result<1 then Result:=p;
  if Result<SrcStart then Result:=SrcStart;
end;

function FindFirstLineEndAfterInCode(const Source: string;
  Position, MaxPosition: integer; NestedComments: boolean): integer;
{ search forward for a line end
  ignore line ends in comments
  Result is Position of Start of Line End
}
var
  SrcLen: integer;
  CommentEndPos: Integer;
begin
  SrcLen:=length(Source);
  if SrcLen>MaxPosition then SrcLen:=MaxPosition;
  Result:=Position;
  while (Result<=SrcLen) do begin
    case Source[Result] of
      '{','(','/':
        begin
          CommentEndPos:=FindCommentEnd(Source,Result,NestedComments);
          if CommentEndPos>Result then
            Result:=CommentEndPos
          else
            inc(Result);
        end;
      #10,#13:
        exit;
    else
      inc(Result);
    end;
  end;
end;

function ChompLineEndsAtEnd(const s: string): string;
var
  EndPos: Integer;
begin
  EndPos:=length(s)+1;
  while (EndPos>1) and (s[EndPos-1] in [#10,#13]) do dec(EndPos);
  Result:=s;
  SetLength(Result,EndPos-1);
end;

function ChompOneLineEndAtEnd(const s: string): string;
var
  EndPos: Integer;
begin
  Result:=s;
  EndPos:=length(s)+1;
  if (EndPos>1) and (s[EndPos-1] in [#10,#13]) then begin
    dec(EndPos);
    if (EndPos>1) and (s[EndPos-1] in [#10,#13]) and (s[EndPos]<>s[EndPos-1])
    then
      dec(EndPos);
    SetLength(Result,EndPos-1);
  end;
end;

function TrimLineEnds(const s: string; TrimStart, TrimEnd: boolean): string;
var
  StartPos: Integer;
  EndPos: Integer;
  LineEnd: Integer;
begin
  StartPos:=1;
  if TrimStart then begin
    // trim empty lines at start
    while (StartPos<=length(s))
    and (s[StartPos] in [#10,#13]) do begin
      inc(StartPos);
      if (StartPos<=length(s))
      and (s[StartPos] in [#10,#13])
      and (s[StartPos]<>s[StartPos-1]) then
        inc(StartPos);
    end;
  end;
  EndPos:=length(s)+1;
  if TrimEnd then begin
    // trim empty lines at end
    while (EndPos>StartPos)
    and (s[EndPos-1] in [#10,#13]) do begin
      LineEnd:=EndPos-1;
      if (LineEnd>StartPos) and (s[LineEnd-1] in [#10,#13])
      and (s[LineEnd-1]<>s[LineEnd]) then begin
        dec(LineEnd);
      end;
      if (LineEnd>StartPos) and (s[LineEnd-1] in [#10,#13]) then
        EndPos:=LineEnd
      else
        break;
    end;
  end;
  if EndPos-StartPos<length(s) then
    Result:=copy(s,StartPos,EndPos-StartPos)
  else
    Result:=s;
end;

function SrcPosToLineCol(const s: string; Position: integer;
  out Line, Col: integer): boolean;
// returns false if Postion<1 or >length(s)+1
var
  p: LongInt;
  l: Integer;
begin
  if (Position<1) then begin
    Line:=1;
    Col:=1;
    Result:=false;
    exit;
  end;
  l:=length(s);
  if l>Position then l:=Position;
  Line:=1;
  Col:=1;
  p:=1;
  while (p<l) do begin
    case s[p] of
    #10,#13:
      begin
        inc(p);
        if (p<=length(s)) and (s[p] in [#10,#13]) and (s[p-1]<>s[p]) then
        begin
          if p=Position then exit(true);
          inc(p);
        end;
        // new line
        inc(Line);
        Col:=1;
      end;
    else
      inc(p);
      inc(Col);
    end;
  end;
  Result:=p=Position;
end;

function GetBracketLvl(const Src: string; StartPos, EndPos: integer;
  NestedComments: boolean): integer;
var
  SrcLen: Integer;

  procedure ReadComment;
  var
    CommentEndPos: Integer;
  begin
    CommentEndPos:=FindCommentEnd(Src,StartPos,NestedComments);
    if CommentEndPos>=EndPos then begin
      // EndPos is in a comment
      // -> count bracket lvl in comment
      Result:=0;
      case Src[StartPos] of
      '{': inc(StartPos);
      '(','/': inc(StartPos,2);
      end;
    end else
      // continue after the comment
      StartPos:=CommentEndPos;
  end;

  procedure ReadBrackets(ClosingBracket: Char);
  begin
    while StartPos<EndPos do begin
      case Src[StartPos] of
      '{':
        ReadComment;
        
      '/':
        if (StartPos<SrcLen) and (Src[StartPos]='/') then
          ReadComment
        else
          inc(StartPos);

      '(':
        if (StartPos<SrcLen) and (Src[StartPos]='*') then
          ReadComment
        else begin
          inc(Result);
          inc(StartPos);
          ReadBrackets(')');
        end;

      '[':
        begin
          inc(Result);
          inc(StartPos);
          ReadBrackets(']');
        end;

      ')',']':
        if (Result>0) then begin
          if ClosingBracket=Src[StartPos] then
            dec(Result) // for example: ()
          else
            Result:=0; // for example: [)
          exit;
        end;

      end;
      inc(StartPos);
    end;
  end;

begin
  Result:=0;
  SrcLen:=length(Src);
  if (StartPos<1) then StartPos:=1;
  if (StartPos>SrcLen) or (EndPos<StartPos) then exit;
  if (EndPos>SrcLen) then EndPos:=SrcLen;
  ReadBrackets(#0);
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);
          if (P>=SrcStart) and (Source[P]=#3) then begin
            // codetools comment {#3 #3}
            dec(p);
            while (P>SrcStart) do begin
              if (Source[P]=#3) and (Source[P-1]='{') then begin
                dec(P,2);
                break;
              end;
              dec(P);
            end;
          end else begin
            // Pascal comment {}
            while (P>=SrcStart) and (Source[P]<>'{') do begin
              if NestedComments and (Source[P] in ['}',')']) then
                ReadComment(P)
              else
                dec(P);
            end;
            dec(P);
          end;
        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 (LineEnd<ToPos))
       or ((LineEnd-LineStart-(ToPos-FromPos)+NewLength)>MaxLineLength);
end;

function CompareTextIgnoringSpace(const Txt1, Txt2: string;
  CaseSensitive: boolean): integer;
begin
  Result:=CompareTextIgnoringSpace(
               PChar(Pointer(Txt1)),length(Txt1),// pointer type cast avoids #0 check
               PChar(Pointer(Txt2)),length(Txt2),
               CaseSensitive);
end;

function CompareTextIgnoringSpace(Txt1: PChar; Len1: integer;
  Txt2: PChar; Len2: integer; CaseSensitive: boolean): integer;
{ Txt1  Txt2  Result
   A     A      0
   A     B      1
   A     AB     1
   A;    A      -1
}
var P1, P2: integer;
begin
  P1:=0;
  P2:=0;
  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 (P1>0) and (IsIdentChar[Txt1[P1-1]])
      and (IsIdentChar[Txt1[P1]] xor IsIdentChar[Txt2[P2]]) then begin
        // one identifier is longer than the other
        if IsIdentChar[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(' '));
        if (ord(Txt2[P2])<=ord(' ')) then begin
          // ignore/skip spaces in Txt2
          repeat
            inc(P2);
          until (P2>=Len2) or (ord(Txt2[P2])>ord(' '));
        end;
      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;
  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 CompareAnsiStringIgnoringSpaceIgnoreCase(Txt1, Txt2: pointer): integer;
// Txt1, Txt2 are type casted AnsiString
begin
  Result:=CompareTextIgnoringSpace(Txt1,length(AnsiString(Txt1)),
                                   Txt2,length(AnsiString(Txt2)),false);
end;

function CompareSubStrings(const Find, Txt: string;
  FindStartPos, TxtStartPos, Len: integer; CaseSensitive: boolean): integer;
begin
  Result:=CompareText(@Find[FindStartPos],Min(length(Find)-FindStartPos+1,Len),
                      @Txt[TxtStartPos],Min(length(Txt)-TxtStartPos+1,Len),
                      CaseSensitive);
end;

function FindNextIncludeDirective(const ASource: string; StartPos: integer;
  NestedComments: boolean; out FilenameStartPos, FileNameEndPos,
  CommentStartPos, CommentEndPos: integer): integer;
var
  MaxPos, Offset: Integer;
begin
  Result:=StartPos;
  MaxPos:=length(ASource);
  repeat
    Result:=FindNextCompilerDirective(ASource,Result,NestedComments);
    if (Result<1) or (Result>MaxPos) then exit;
    if (ASource[Result]='{') then
      Offset:=2
    else if ASource[Result]='(' then
      Offset:=3
    else
      Offset:=-1;
    if (Offset>0) then begin
      if ((UpChars[ASource[Result+Offset]]='I')
              and (ASource[Result+Offset+1]=' '))
      or (CompareIdentifiers('include',@ASource[Result+Offset])=0) then begin
        CommentEndPos:=FindCommentEnd(ASource,Result,NestedComments);
        if ASource[Result]='{' then
          dec(CommentEndPos)
        else
          dec(CommentEndPos,2);

        // skip directive name
        FilenameStartPos:=Result+Offset;
        while (FilenameStartPos<=CommentEndPos)
        and (IsIdentChar[ASource[FilenameStartPos]]) do
          inc(FilenameStartPos);
        // skip space after name
        while (FilenameStartPos<=CommentEndPos)
        and (IsSpaceChar[ASource[FilenameStartPos]]) do
          inc(FilenameStartPos);
        // find end of filename
        if (FilenameStartPos<=CommentEndPos) and (ASource[FilenameStartPos]='''')
        then begin
          // quoted filename
          inc(FilenameStartPos);
          FilenameEndPos:=FilenameStartPos;
          while (FilenameEndPos<=CommentEndPos) do begin
            if (ASource[FilenameEndPos]<>'''') then
              inc(FilenameEndPos)
            else
              break;
          end;
          CommentStartPos:=FilenameEndPos+1;
        end else begin
          // normal filename
          FilenameEndPos:=FilenameStartPos;
          while (FilenameEndPos<=CommentEndPos)
          and (not IsSpaceChar[ASource[FilenameEndPos]])
          and (not (ASource[FilenameEndPos] in ['*','}'])) do
            inc(FilenameEndPos);
          CommentStartPos:=FilenameEndPos;
        end;
        // skip space behind filename
        while (CommentStartPos<=CommentEndPos)
        and (IsSpaceChar[ASource[CommentStartPos]]) do
          inc(CommentStartPos);
        // success
        exit;
      end;
    end;
    // try next comment
    Result:=FindCommentEnd(ASource,Result,NestedComments);
  until Result>MaxPos;
end;

function FindNextIDEDirective(const ASource: string; StartPos: integer;
  NestedComments: boolean; EndPos: integer): integer;
var
  MaxPos: integer;
begin
  MaxPos:=length(ASource);
  if (EndPos>0) and (EndPos<=MaxPos) then
    MaxPos:=EndPos-1;
  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 (Result<MaxPos) and (ASource[Result+1]='%') then
          exit;
        // skip pascal comment
        Result:=FindCommentEnd(ASource,Result,NestedComments);
      end;

    '(':
      begin
        if (Result<MaxPos) and (ASource[Result+1]='*') then begin
          // skip TP comment
          Result:=FindCommentEnd(ASource,Result,NestedComments);
        end else
          inc(Result);
      end;

    else
      inc(Result);
    end;

  end;
  Result:=-1;
end;

function CleanCodeFromComments(const Src: string; NestedComments: boolean;
  KeepDirectives: boolean; KeepVerbosityDirectives: boolean): string;
// KeepVerbosityDirectives=true requires KeepDirectives=true
var
  SrcPos: Integer;
  ResultPos: Integer;
  StartPos: Integer;
  l: Integer;
  p: PChar;
begin
  SetLength(Result,length(Src));
  SrcPos:=1;
  ResultPos:=1;
  while SrcPos<=length(Src) do begin
    StartPos:=FindNextComment(Src,SrcPos);
    l:=StartPos-SrcPos;
    if (l>0) then begin
      System.Move(Src[SrcPos],Result[ResultPos],l);
      inc(ResultPos,l);
    end;
    if StartPos>length(Src) then break;
    SrcPos:=FindCommentEnd(Src,StartPos,NestedComments);
    if KeepDirectives then begin
      p:=@Src[StartPos];
      if (p^<>'{') or (p[1]<>'$') then continue;
      if not KeepVerbosityDirectives then begin
        inc(p,2);
        if (CompareIdentifiers(p,'warn')=0)
        or (CompareIdentifiers(p,'hint')=0) then continue;
      end;
      l:=SrcPos-StartPos;
      System.Move(Src[StartPos],Result[ResultPos],l);
      inc(ResultPos,l);
    end;
  end;
  SetLength(Result,ResultPos-1);
end;

function ExtractCommentContent(const ASource: string; CommentStart: integer;
  NestedComments: boolean; TrimStart: boolean; TrimEnd: boolean;
  TrimPasDoc: boolean): string;
var
  CommentEnd: LongInt;
  StartPos: LongInt;
  EndPos: LongInt;
begin
  Result:='';
  if (CommentStart<1) or (CommentStart>length(ASource)) then exit;
  CommentEnd:=FindCommentEnd(ASource,CommentStart,NestedComments);
  StartPos:=CommentStart;
  EndPos:=CommentEnd;
  if (ASource[StartPos]='/') then begin
    inc(StartPos);
    if (StartPos<=length(ASource)) and (ASource[StartPos]='/') then
      inc(StartPos);
    if (EndPos<=length(ASource)) then begin
      while (EndPos>StartPos) and (ASource[EndPos-1] in [#10,#13]) do
        dec(EndPos);
    end;
  end else if (ASource[StartPos]='{') then begin
    inc(StartPos);
    if (StartPos<=length(ASource)) and (ASource[StartPos]=#3) then begin
      // codetools skip comment {#3#3}
      inc(StartPos);
      if (EndPos<=length(ASource)) and (ASource[EndPos-1]='}') then begin
        dec(EndPos);
        if (EndPos<=length(ASource)) and (ASource[EndPos-1]=#3) then
          dec(EndPos);
      end;
    end else begin
      // Pascal comment {}
      if (EndPos<=length(ASource)) and (ASource[EndPos-1]='}') then
        dec(EndPos);
    end;
  end else if (ASource[StartPos]='(') then begin
    inc(StartPos);
    if (StartPos<=length(ASource)) and (ASource[StartPos]='*') then
      inc(StartPos);
    if (EndPos<=length(ASource)) and (ASource[EndPos-1]=')') then begin
      dec(EndPos);
      if (ASource[EndPos-1]='*') then
        dec(EndPos);
    end;
  end;
  if TrimPasDoc then begin
    if (StartPos<EndPos) and (ASource[StartPos]='<') then
      inc(StartPos);
  end;
  if TrimStart then begin
    while (StartPos<EndPos) and (ASource[StartPos] in [' ',#9,#10,#13]) do
      inc(StartPos);
  end;
  if TrimEnd then begin
    while (StartPos<EndPos) and (ASource[Endpos-1] in [' ',#9,#10,#13]) do
      dec(EndPos);
  end;
  Result:=copy(ASource,StartPos,EndPos-StartPos);
end;

function FindMainUnitHint(const ASource: string; out Filename: string
  ): boolean;
const
  IncludeByHintStart = '{%MainUnit ';
var
  MaxPos: Integer;
  StartPos: Integer;
  EndPos: LongInt;
begin
  Result:=false;
  Filename:='';
  MaxPos:=length(ASource);
  StartPos:=length(IncludeByHintStart);
  if not TextBeginsWith(PChar(Pointer(ASource)),// pointer type cast avoids #0 check
          MaxPos,IncludeByHintStart,StartPos,false)
  then
    exit;
  while (StartPos<=MaxPos) and (ASource[StartPos]=' ') do inc(StartPos);
  EndPos:=StartPos;
  while (EndPos<=MaxPos) and (ASource[EndPos]<>'}') do inc(EndPos);
  if (EndPos=StartPos) or (EndPos>MaxPos) then exit;
  Filename:=GetForcedPathDelims(copy(ASource,StartPos,EndPos-StartPos));
  Result:=true;
end;

function InEmptyLine(const ASource: string; StartPos: integer): boolean;
var
  p: LongInt;
  SrcLen: Integer;
begin
  Result:=false;
  SrcLen:=length(ASource);
  if (StartPos<1) or (StartPos>SrcLen) or (not IsSpaceChar[ASource[StartPos]])
  then exit;
  p:=StartPos;
  while (p>1) do begin
    case ASource[p-1] of
    ' ',#9: dec(p);
    #10,#13: break;
    else exit;
    end;
  end;
  p:=StartPos;
  while p<=SrcLen do begin
    case ASource[p] of
    ' ',#9: inc(p);
    #10,#13: break;
    else exit;
    end;
  end;
  Result:=true;
end;

function SkipResourceDirective(const ASource: string;
  StartPos, EndPos: integer; NestedComments: boolean): integer;
var
  MaxPos: integer;

  function IsResourceDirective(DirNamePos: integer): boolean;
  begin
    if UpChars[ASource[DirNamePos]]<>'R' then exit(false);
    if (DirNamePos < MaxPos)
    and (UpChars[ASource[DirNamePos+1]] in [' ',#9]) then exit(true);
    result:=CompareIdentifiers(@ASource[DirNamePos],'RESOURCE')=0;
  end;

var
  i: integer;
begin
  MaxPos:=length(ASource);
  if (EndPos>0) and (EndPos<=MaxPos) then
    MaxPos:=EndPos-1;
  Result:=StartPos;
  i:=StartPos;
  while (i<=MaxPos) do begin
    case ASource[i] of
    '{':
      if (i+1<MaxPos) and (ASource[i+1]='$')
      and IsResourceDirective(i+2) then begin
        Result:=FindCommentEnd(ASource,i,NestedComments);
        exit;
      end
      else exit;

    '(':
      if (i+2<MaxPos) and (ASource[i+1]='*') and (ASource[i+2]='$')
      and IsResourceDirective(i+3) then begin
        Result:=FindCommentEnd(ASource,i,NestedComments);
        exit;
      end
      else exit;

    #9,#10,#13,' ': inc(i);
    else
      exit;
    end;

  end;
end;

function CompareIdentifiers(Identifier1, Identifier2: PChar): integer;
begin
  Result:=KeywordFuncLists.CompareIdentifiers(Identifier1,Identifier2);
end;

function CompareIdentifierPtrs(Identifier1, Identifier2: Pointer): integer;
begin
  Result:=CompareIdentifiers(PChar(Identifier1), PChar(Identifier2));
end;

function CompareIdentifiersCaseSensitive(Identifier1, Identifier2: PChar
  ): integer;
begin
  if (Identifier1<>nil) then begin
    if (Identifier2<>nil) then begin
      while (Identifier1[0]=Identifier2[0]) do begin
        if (IsIdentChar[Identifier1[0]]) then begin
          inc(Identifier1);
          inc(Identifier2);
        end else begin
          Result:=0; // for example  'aaA;' 'aAa;'
          exit;
        end;
      end;
      if (IsIdentChar[Identifier1[0]]) then begin
        if (IsIdentChar[Identifier2[0]]) then begin
          if Identifier1[0]>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 (IsIdentChar[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^]) and (PrefixIdent^>#0) do
      begin
        inc(PrefixIdent);
        inc(Identifier);
      end;
      Result:=not IsIdentChar[PrefixIdent^];
    end else begin
      Result:=false;
    end;
  end else begin
    Result:=true;
  end;
end;

function TextBeginsWith(Txt: PChar; TxtLen: integer; StartTxt: PChar;
    StartTxtLen: integer; CaseSensitive: boolean): boolean;
begin
  if TxtLen<StartTxtLen then exit(false);
  Result:=CompareText(Txt,StartTxtLen,StartTxt,StartTxtLen,CaseSensitive)=0;
end;

function StrBeginsWith(const s, Prefix: string): boolean;
var
  p1: PChar;
  p2: PChar;
  i: Integer;
begin
  Result:=false;
  if length(s)<length(Prefix) then exit;
  if (s='') then exit(true);
  p1:=PChar(s);
  p2:=PChar(Prefix);
  for i:=1 to length(Prefix) do begin
    if p1^<>p2^ then exit;
    inc(p1);
    inc(p2);
  end;
  Result:=true;
end;

function IdentifierPos(Search, Identifier: PChar): PtrInt;
var
  i: Integer;
begin
  if Identifier=nil then exit(-1);
  if (Search=nil) or (Search^=#0) then exit(0);
  Result:=0;
  while (IsIdentChar[Identifier[Result]]) do begin
    if UpChars[Search^]=UpChars[Identifier[Result]] then begin
      i:=1;
      repeat
        if IsIdentChar[Search[i]] then begin
          if (UpChars[Search[i]]=UpChars[Identifier[Result+i]]) then
            inc(i)
          else
            break;
        end else begin
          // whole found
          exit;
        end;
      until false;
    end;
    inc(Result);
  end;
  Result:=-1;
end;

function CompareAtom(p1, p2: PChar; NestedComments: boolean): integer;
var
  Len1: LongInt;
  Len2: LongInt;
  l: LongInt;
  c1: Char;
  c2: Char;
begin
  // quick test for the common case:
  if (p1^<>p2^) then begin
    c1:=UpChars[p1^];
    c2:=UpChars[p2^];
    if c1<c2 then
      exit(1)
    else if c1>c2 then
      exit(-1);
  end;

  case p1^ of
  '''':
    // compare string constants case sensitive
    exit(CompareStringConstants(p1,p2));
  '{':
    exit(CompareComments(p1,p2,NestedComments));
  '(':
    if (p1[1]='*') and (p2[1]='*') then
      exit(CompareComments(p1,p2,NestedComments));
  '/':
    if (p1[1]='/') and (p2[1]='/') then
      exit(CompareComments(p1,p2,NestedComments));
  end;

  // full comparison
  Len1:=GetAtomLength(p1,NestedComments);
  Len2:=GetAtomLength(p2,NestedComments);
  l:=Len1;
  if l>Len2 then l:=Len2;
  while l>0 do begin
    if (p1^<>p2^) then begin
      c1:=UpChars[p1^];
      c2:=UpChars[p2^];
      if c1<c2 then
        exit(1)
      else if c1>c2 then
        exit(-1);
    end;
    inc(p1);
    inc(p2);
    dec(l);
  end;
  if Len1>Len2 then
    Result:=1
  else if Len1<Len2 then
    Result:=-1
  else
    Result:=0;
end;

function CompareStringConstants(p1, p2: PChar): integer;
// 1: 'aa' 'ab' because bigger
// 1: 'aa' 'a'  because longer
begin
  Result := 0;
  if (p1^='''') and (p2^='''') then begin
    inc(p1);
    inc(p2);
    repeat
      if p1^<p2^ then
        exit(1)  // p1 bigger
      else if p1^>p2 then
        exit(-1); // p2 bigger
      inc(p1);
      inc(p2);
      if p1^='''' then begin
        // maybe ''
        inc(p1);
        inc(p2);
        if p1^='''' then begin
          if p2^='''' then begin
            inc(p1);
            inc(p2);
          end else begin
            // p1 is longer (e.g.: 'a''b' 'a')
            exit(1);
          end;
        end else if p2^='''' then begin
          // p2 is longer (e.g. 'a' 'a''b')
          exit(-1);
        end else begin
          // same
          exit(0);
        end;
      end;
      if p1^ in [#0,#10,#13] then begin
        // end of p1 found
        if p2^ in [#0,#10,#13] then begin
          // same
          exit(0);
        end else begin
          // p2 is longer
          exit(-1);
        end;
      end else if p2^ in [#0,#10,#13] then begin
        // p1 is longer
        exit(1);
      end;
    until false;
  end else begin
    if p1^='''' then
      // p1 longer
      exit(1)
    else if p2^='''' then
      // p2 longer
      exit(-1)
    else
      // both empty
      exit(0);
  end;
end;

function CompareComments(p1, p2: PChar; NestedComments: boolean): integer;
var
  CommentLvl: Integer;
  IsCodetoolsComment: Boolean;
begin
  if p1^<>p2^ then begin
    if p1^<p2^ then
      exit(1)
    else
      exit(-1);
  end;
  case p1^ of
  '/':
    begin
      inc(p1);
      inc(p2);
      if p1^<>p2^ then begin
        if p1^<p2^ then
          exit(1)
        else
          exit(-1);
      end;
      if p1^<>'/' then exit(0);
      repeat
        inc(p1);
        inc(p2);
        if p1^ in [#0,#10,#13] then begin
          if p2^ in [#0,#10,#13] then begin
            exit(0);
          end else begin
            // p2 is longer
            exit(-1);
          end;
        end;
        if p1^<>p2^ then begin
          if p2^ in [#0,#10,#13] then begin
            // p1 is longer
            exit(1);
          end;
          if p1^<p2^ then
            exit(1)
          else
            exit(-1);
        end;
      until false;
    end;
  '{':
    begin
      inc(p1);
      inc(p2);
      CommentLvl:=1;
      IsCodetoolsComment:=p1^=#3;
      while true do begin
        if p1^<>p2^ then begin
          if p1^<p2^ then
            exit(1)
          else
            exit(-1);
        end;
        inc(p1);
        inc(p2);
        if IsCodetoolsComment then begin
          case p1^ of
          #0:  exit(0);
          '}': if p1[-1]=#3 then exit(0);
          end;
        end else begin
          case p1^ of
          #0:  exit(0);
          '{': if NestedComments then
              inc(CommentLvl);
          '}':
            begin
              dec(CommentLvl);
              if CommentLvl=0 then
                exit(0);
            end;
          end;
        end;
      end;
    end;
  '(':  // comment
    begin
      inc(p1);
      inc(p2);
      if p1^<>p2^ then begin
        if p1^<p2^ then
          exit(1)
        else
          exit(-1);
      end;
      if p1^<>'*' then exit(0);
      CommentLvl:=1;
      repeat
        inc(p1);
        inc(p2);
        if p1^<>p2^ then begin
          if p1^<p2^ then
            exit(1)
          else
            exit(-1);
        end;
        case p1^ of
        #0: exit(0);
        '*':
          if (p1[1]=')') then begin
            inc(p1);
            inc(p2);
            if p2^=')' then begin
              dec(CommentLvl);
              if CommentLvl=0 then
                exit(0);
            end else
              // p2 longer
              exit(-1);
          end;
        '(':
          if (p1[1]='*') and NestedComments then begin
            inc(CommentLvl);
            inc(p1);
            inc(p2);
            if p1^<>p2^ then begin
              if p1^<p2^ then
                exit(1)
              else
                exit(-1);
            end;
          end;
        end;
      until false;
    end;
  end;
  Result:=0;
end;

function FindDiff(const s1, s2: string): integer;
begin
  Result:=1;
  while (Result<=length(s1)) and (Result<=length(s2)) and (s1[Result]=s2[Result]) do
    inc(Result);
end;

function dbgsDiff(Expected, Actual: string): string;
var
  d: Integer;
begin
  Expected:=dbgstr(Expected);
  Actual:=dbgstr(Actual);
  d:=FindDiff(Expected, Actual);
  Result:='Expected: '+dbgstr(Expected,1,d-1)+'|'+dbgstr(Expected,d,length(Expected))+LineEnding
         +'Actual:   '+dbgstr(Actual,1,d-1)+'|'+dbgstr(Actual,d,length(Actual));
end;

function GetIdentifier(Identifier: PChar; const aSkipAmp: Boolean): string;
var len: integer;
begin
  if (Identifier=nil) then begin
    Result:='';
    exit;
  end;
  if IsIdentStartChar[Identifier^] or ((Identifier^='&') and (IsIdentStartChar[Identifier[1]])) then begin
    len:=0;
    if (Identifier^='&') then
    begin
      if aSkipAmp then
        inc(Identifier)
      else
        inc(len);
    end;
    while (IsIdentChar[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 IsIdentStartChar[Source[Result]]) do
    inc(Result);
end;

function FindNextIdentifierSkipStrings(const Source: string; StartPos,
  MaxPos: integer): integer;
var
  c: Char;
begin
  Result:=StartPos;
  while (Result<=MaxPos) do begin
    c:=Source[Result];
    if IsIdentStartChar[c] then exit;
    if c='''' then begin
      // skip string constant
      inc(Result);
      while (Result<=MaxPos) and (not (Source[Result] in ['''',#10,#13])) do
        inc(Result);
    end;
    inc(Result);
  end;
end;

function IsValidIdentPair(const NamePair: string): boolean;
var
  p: Integer;
begin
  Result:=false;
  p:=1;
  if (p>length(NamePair)) or (not IsIdentStartChar[NamePair[p]]) then exit;
  repeat
    inc(p);
    if p>length(NamePair) then exit;
    if NamePair[p]='.' then break;
    if not IsIdentChar[NamePair[p]] then exit;
  until false;
  inc(p);
  if (p>length(NamePair)) or (not IsIdentStartChar[NamePair[p]]) then exit;
  repeat
    inc(p);
    if p>length(NamePair) then exit(true);
    if not IsIdentChar[NamePair[p]] then exit;
  until false;
end;

function IsValidIdentPair(const NamePair: string; out First, Second: string
  ): boolean;
var
  p: Integer;
  StartPos: LongInt;
begin
  Result:=false;
  First:='';
  Second:='';
  p:=1;
  if (p>length(NamePair)) or (not IsIdentStartChar[NamePair[p]]) then exit;
  StartPos:=p;
  repeat
    inc(p);
    if p>length(NamePair) then exit;
    if NamePair[p]='.' then break;
    if not IsIdentChar[NamePair[p]] then exit;
  until false;
  First:=copy(NamePair,StartPos,p-StartPos);
  inc(p);
  if (p>length(NamePair)) or (not IsIdentStartChar[NamePair[p]]) then exit;
  StartPos:=p;
  repeat
    inc(p);
    if p>length(NamePair) then begin
      Second:=copy(NamePair,StartPos,p-StartPos);
      exit(true);
    end;
    if not IsIdentChar[NamePair[p]] then exit;
  until false;
end;

function ExtractPasIdentifier(const Ident: string; AllowDots: Boolean): string;
var
  p: Integer;
begin
  p:=1;
  Result:=Ident;
  while p<=length(Result) do begin
    if Result[p] in ['a'..'z','A'..'Z','_'] then begin
      inc(p);
      while p<=length(Result) do begin
        case Result[p] of
        'a'..'z','A'..'Z','_','0'..'9': inc(p);
        '.':
          if AllowDots then
            break
          else
            Delete(Result,p,1);
        else
          Delete(Result,p,1);
        end;
      end;
      if p>length(Result) then exit;
      // p is now on the '.'
      inc(p);
    end else
      Delete(Result,p,1);
  end;
  p:=length(Result);
  if (p>0) and (Result[p]='.') then
    Delete(Result,p,1);
end;

function GetLineIndentWithTabs(const Source: string; Position: integer;
  TabWidth: integer): integer;
var p: integer;
begin
  Result:=0;
  p:=Position;
  if p=0 then exit;
  if (p<0) then p:=1;
  if (p>length(Source)+1) then p:=length(Source)+1;
  // search beginning of line
  while (p>1) and not (Source[p-1] in [#10,#13]) do
    dec(p);
  // search code
  Result:=0;
  while (p<=length(Source)) do begin
    case Source[p] of
    ' ': inc(Result);
    #9:
      begin
        Result:=Result+TabWidth;
        Result:=Result-(Result mod TabWidth);
      end;
    else break;
    end;
    inc(p);
  end;
end;

function GetPosInLine(const Source: string; Position: integer): integer;
begin
  Result:=0;
  while (Position>1) and (not (Source[Position-1] in [#10,#13])) do begin
    inc(Result);
    dec(Position);
  end;
end;

function GetBlockMinIndent(const Source: string;
  StartPos, EndPos: integer): integer;
var
  SrcLen: Integer;
  p: Integer;
  CurIndent: Integer;
begin
  SrcLen:=length(Source);
  if EndPos>SrcLen then EndPos:=SrcLen+1;
  Result:=EndPos-StartPos;
  p:=StartPos;
  while p<=EndPos do begin
    // skip line end and empty lines
    while (p<EndPos) and (Source[p] in [#10,#13]) do
      inc(p);
    if (p>=EndPos) then break;
    // count spaces at line start
    CurIndent:=0;
    while (p<EndPos) and (Source[p] in [#9,' ']) do begin
      inc(p);
      inc(CurIndent);
    end;
    if CurIndent<Result then Result:=CurIndent;
    // skip rest of line
    while (p<EndPos) and (not (Source[p] in [#10,#13])) do
      inc(p);
  end;
end;

function GetIndentStr(Indent: integer; TabWidth: integer): string;
var
  TabCnt: Integer;
  SpaceCnt: Integer;
  i: Integer;
begin
  if TabWidth<=0 then begin
    SetLength(Result,Indent);
    if Indent>0 then
      FillChar(Result[1],length(Result),' ');
  end else begin
    TabCnt:=Indent div TabWidth;
    SpaceCnt:=Indent mod TabWidth;
    SetLength(Result,TabCnt+SpaceCnt);
    for i:=1 to TabCnt do
      Result[i]:=#9;
    for i:=TabCnt+1 to TabCnt+SpaceCnt do
      Result[i]:=' ';
  end;
end;

procedure IndentText(const Source: string; Indent, TabWidth: integer;
  out NewSource: string);

  function UnindentTxt(CopyChars: boolean): integer;
  var
    Unindent: Integer;
    SrcPos: Integer;
    SrcLen: Integer;
    NewSrcPos: Integer;
    SkippedSpaces: Integer;
    c: Char;
  begin
    Unindent:=-Indent;
    SrcPos:=1;
    SrcLen:=length(Source);
    NewSrcPos:=1;
    while SrcPos<=SrcLen do begin
      // skip spaces at start of line
      SkippedSpaces:=0;
      while (SrcPos<=SrcLen) and (SkippedSpaces<Unindent) do begin
        c:=Source[SrcPos];
        if c=' ' then begin
          inc(SkippedSpaces);
          inc(SrcPos);
        end else if c=#9 then begin
          inc(SkippedSpaces,TabWidth);
          inc(SrcPos);
        end else
          break;
      end;
      // deleting a tab can unindent too much, so insert some spaces
      while SkippedSpaces>Unindent do begin
        if CopyChars then
          NewSource[NewSrcPos]:=' ';
        inc(NewSrcPos);
        dec(SkippedSpaces);
      end;
      // copy the rest of the line
      while (SrcPos<=SrcLen) do begin
        c:=Source[SrcPos];
        // copy char
        if CopyChars then
          NewSource[NewSrcPos]:=Source[SrcPos];
        inc(NewSrcPos);
        inc(SrcPos);
        if (c in [#10,#13]) then begin
          // line end
          if (SrcPos<=SrcLen) and (Source[SrcPos] in [#10,#13])
          and (Source[SrcPos]<>Source[SrcPos-1]) then begin
            if CopyChars then
              NewSource[NewSrcPos]:=Source[SrcPos];
            inc(NewSrcPos);
            inc(SrcPos);
          end;
          break;
        end;
      end;
    end;
    Result:=NewSrcPos-1;
  end;

var
  LengthOfLastLine: integer;
  LineEndCnt: Integer;
  SrcPos: Integer;
  SrcLen: Integer;
  NewSrcPos: Integer;
  c: Char;
  NewSrcLen: Integer;

  procedure AddIndent;
  var
    i: Integer;
  begin
    for i:=1 to Indent do begin
      NewSource[NewSrcPos]:=' ';
      inc(NewSrcPos);
    end;
  end;

begin
  if (Indent=0) or (Source='') then begin
    NewSource:=Source;
    exit;
  end;
  if Indent>0 then begin
    // indent text
    LineEndCnt:=LineEndCount(Source,LengthOfLastLine);
    if LengthOfLastLine>0 then inc(LineEndCnt);
    SetLength(NewSource,LineEndCnt*Indent+length(Source));
    SrcPos:=1;
    SrcLen:=length(Source);
    NewSrcPos:=1;
    AddIndent;
    while SrcPos<=SrcLen do begin
      c:=Source[SrcPos];
      // copy char
      NewSource[NewSrcPos]:=Source[SrcPos];
      inc(NewSrcPos);
      inc(SrcPos);
      if (c in [#10,#13]) then begin
        // line end
        if (SrcPos<=SrcLen) and (Source[SrcPos] in [#10,#13])
        and (Source[SrcPos]<>Source[SrcPos-1]) then begin
          NewSource[NewSrcPos]:=Source[SrcPos];
          inc(NewSrcPos);
          inc(SrcPos);
        end;
        if (SrcPos<=SrcLen) and (not (Source[SrcPos] in [#10,#13])) then begin
          // next line is not empty -> indent
          AddIndent;
        end;
      end;
    end;
    SetLength(NewSource,NewSrcPos-1);
  end else begin
    // unindent text
    NewSrcLen:=UnindentTxt(false);
    SetLength(NewSource,NewSrcLen);
    UnindentTxt(true);
  end;
end;

function GetLineStartPosition(const Source: string; Position: integer): integer;
begin
  Result:=Position;
  while (Result>1) and (not (Source[Result-1] in [#10,#13])) do
    dec(Result);
end;

function GetLineInSrc(const Source: string; Position: integer): string;
var
  LineStart, LineEnd: integer;
begin
  GetLineStartEndAtPosition(Source,Position,LineStart,LineEnd);
  //debugln(['GetLineInSrc ',Position,' ',LineStart,' ',LineEnd]);
  Result:=copy(Source,LineStart,LineEnd-LineStart);
end;

function LineEndCount(const Txt: string): integer;
var
  LengthOfLastLine: integer;
begin
  Result:=LineEndCount(Txt,LengthOfLastLine);
  if LengthOfLastLine=0 then ;
end;

function DottedIdentifierLength(Identifier: PChar): integer;
var
  p: PChar;
begin
  Result:=0;
  if Identifier=nil then exit;
  p:=Identifier;
  repeat
    if not IsIdentStartChar[p^] then exit;
    repeat
      inc(p);
    until not IsIdentChar[p^];
    if p^<>'.' then break;
    inc(p);
  until false;
  Result:=p-Identifier;
end;

function GetDottedIdentifier(Identifier: PChar): string;
var
  l: Integer;
begin
  l:=DottedIdentifierLength(Identifier);
  SetLength(Result,l);
  if l>0 then
    System.Move(Identifier^,Result[1],l);
end;

function IsDottedIdentifier(const Identifier: string): boolean;
var
  p: PChar;
begin
  Result:=false;
  if Identifier='' then exit;
  p:=PChar(Identifier);
  repeat
    if not IsIdentStartChar[p^] then exit;
    repeat
      inc(p);
    until not IsIdentChar[p^];
    if p^<>'.' then break;
    inc(p);
  until false;
  Result:=(p-PChar(Identifier))=length(Identifier);
end;

function CompareDottedIdentifiers(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 (IsDottedIdentChar[Identifier1[0]]) then begin
          inc(Identifier1);
          inc(Identifier2);
        end else begin
          Result:=0; // for example  'aaA;' 'aAa;'
          exit;
        end;
      end;
      if (IsDottedIdentChar[Identifier1[0]]) then begin
        if (IsDottedIdentChar[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 (IsDottedIdentChar[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 ChompDottedIdentifier(const Identifier: string): string;
var
  p: Integer;
begin
  p:=length(Identifier);
  while (p>0) and (Identifier[p]<>'.') do dec(p);
  Result:=LeftStr(Identifier,p-1);
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
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 (IsIdentChar[c1] and IsIdentChar[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;
// from FromPos to including ToPos
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;
// converts s to a Pascal string literal
// e.g. foo becomes 'foo', bytes 0..31 become #ord

  function Convert(var DestStr: string): integer;
  var
    SrcLen, SrcPos, DestPos: integer;
    c: char;
    i: integer;
    InString: Boolean;
  begin
    SrcLen:=length(s);
    DestPos:=1;
    if DestStr<>'' then DestStr[DestPos]:='''';
    InString:=true;
    for SrcPos:=1 to SrcLen do begin
      inc(DestPos);
      c:=s[SrcPos];
      if c>=' ' then begin
        // normal char
        if not InString then begin
          if DestStr<>'' then DestStr[DestPos]:='''';
          inc(DestPos);
          InString:=true;
        end;
        if DestStr<>'' then
          DestStr[DestPos]:=c;
        if c='''' then begin
          inc(DestPos);
          if DestStr<>'' then DestStr[DestPos]:='''';
        end;
      end else begin
        // special char
        if InString then begin
          if DestStr<>'' then DestStr[DestPos]:='''';
          inc(DestPos);
          InString:=false;
        end;
        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;
    if InString then begin
      inc(DestPos);
      if DestStr<>'' then DestStr[DestPos]:='''';
      InString:=false;
    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;

function SplitStringConstant(const StringConstant: string;
  FirstLineLength, OtherLineLengths, Indent: integer;
  const aLineBreak: string): string;
{ Split long string constants
  If possible it tries to split on word boundaries.

  Examples:
  1.
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ',15,20,6
    becomes:  |'ABCDEFGHIJKLM'|
              |      +'NOPQRSTUVWX'|
              |      +'YZ'|
    Result:
      'ABCDEFGHIJKLM'#13#10      +'NOPQRSTUVWX'#13#10      +'YZ'

  2.
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ',5,20,6


}
const
  // string constant character types:
  stctStart      = 'S'; // ' start char
  stctEnd        = 'E'; // ' end char
  stctWordStart  = 'W'; // word char after non word char
  stctQuotation1 = '1'; // first ' of a double ''
  stctQuotation2 = '2'; // second ' of a double ''
  stctChar       = 'C'; // normal character
  stctMBC        = 'M'; // follow character of multi byte char
  stctHash       = '#'; // hash
  stctHashNumber = '0'; // hash number
  stctLineEnd10  = #10; // hash number is 10
  stctLineEnd13  = #13; // hash number is 13
  stctJunk       = 'j'; // junk

var
  SrcLen: Integer;
  Src: String;
  CurLineMax: Integer;
  ParsedSrc: string;
  ParsedLen: integer;
  SplitPos: integer;
  i: Integer;

  procedure ParseSrc;
  var
    APos: Integer;

    procedure MarkMBC;
    var
      l: LongInt;
    begin
      l:=UTF8CodepointSize(@Src[APos]);
      inc(APos);
      dec(l);
      while (l>0) and (APos<ParsedLen) do begin
        ParsedSrc[APos]:=stctMBC;
        inc(APos);
        dec(l);
      end;
    end;

  var
    NumberStart: Integer;
    Number: Integer;
  begin
    APos:=1;
    ParsedLen:=CurLineMax+1;
    if ParsedLen>SrcLen then ParsedLen:=SrcLen;
    SetLength(ParsedSrc,CurLineMax+1);
    while APos<=ParsedLen do begin
      if Src[APos]='''' then begin
        ParsedSrc[APos]:=stctStart;
        inc(APos);
        while APos<=ParsedLen do begin
          if (Src[APos]='''') then begin
            inc(APos);
            if (APos<=ParsedLen) and (Src[APos]='''') then begin
              // double ''
              ParsedSrc[APos-1]:=stctQuotation1;
              ParsedSrc[APos]:=stctQuotation2;
              inc(APos);
            end else begin
              // end of string
              ParsedSrc[APos-1]:=stctEnd;
              break;
            end;
          end else if Src[APos] in ['A'..'Z','a'..'z',#128..#255] then begin
            // normal word char
            if (APos>1) and (Src[APos-1] in ['A'..'Z','a'..'z',#128..#255]) then
              ParsedSrc[APos]:=stctChar
            else
              ParsedSrc[APos]:=stctWordStart;
            MarkMBC;
          end else begin
            // other char in string constant
            ParsedSrc[APos]:=stctWordStart;
            inc(APos);
          end;
        end;
      end else if Src[APos]='#' then begin
        ParsedSrc[APos]:=stctHash;
        inc(APos);
        NumberStart:=APos;
        if (APos<=ParsedLen) then begin
          // parse character number
          if IsNumberChar[Src[APos]] then begin
            // parse decimal number
            while (APos<=ParsedLen) and IsNumberChar[Src[APos]] do begin
              ParsedSrc[APos]:=stctHashNumber;
              inc(APos);
            end;
          end else if Src[APos]='$' then begin
            // parse hex number
            while (APos<=ParsedLen) and IsHexNumberChar[Src[APos]] do begin
              ParsedSrc[APos]:=stctHashNumber;
              inc(APos);
            end;
          end;
          Number:=StrToIntDef(copy(Src,NumberStart,APos-NumberStart),-1);
          if (Number=10) or (Number=13) then begin
            while NumberStart<APos do begin
              ParsedSrc[NumberStart]:=chr(Number);
              inc(NumberStart);
            end;
          end;
        end;
      end else begin
        // junk
        ParsedSrc[APos]:=stctJunk;
        MarkMBC;
      end;
    end;
  end;

  function SearchCharLeftToRight(c: char): integer;
  begin
    Result:=1;
    while (Result<=ParsedLen) and (ParsedSrc[Result]<>c) do
      inc(Result);
    if Result>ParsedLen then Result:=-1;
  end;

  function SearchDiffCharLeftToRight(StartPos: integer): integer;
  begin
    Result:=StartPos+1;
    while (Result<=ParsedLen) and (ParsedSrc[Result]=ParsedSrc[StartPos]) do
      inc(Result);
  end;

  procedure SplitAtNewLineCharConstant;
  var
    HashPos: Integer;
    NewSplitPos: Integer;
  begin
    if SplitPos>0 then exit;
    // check if there is a aLineBreak character constant
    HashPos:=SearchCharLeftToRight(stctLineEnd10)-1;
    if (HashPos<1) then begin
      HashPos:=SearchCharLeftToRight(stctLineEnd13)-1;
      if HashPos<1 then exit;
    end;
    NewSplitPos:=SearchDiffCharLeftToRight(HashPos+1);
    if NewSplitPos>CurLineMax then exit;
    // check if this is a double new line char const #13#10
    if (NewSplitPos<ParsedLen) and (ParsedSrc[NewSplitPos]=stctHash)
    and (ParsedSrc[NewSplitPos+1] in [stctLineEnd10,stctLineEnd13])
    and (ParsedSrc[NewSplitPos+1]<>ParsedSrc[NewSplitPos-1])
    then begin
      NewSplitPos:=SearchDiffCharLeftToRight(NewSplitPos+1);
      if NewSplitPos>CurLineMax then exit;
    end;
    SplitPos:=NewSplitPos;
  end;

  procedure SplitBetweenConstants;
  var
    APos: Integer;
  begin
    if SplitPos>0 then exit;
    APos:=CurLineMax;
    while (APos>=2) do begin
      if (ParsedSrc[APos] in [stctHash,stctStart]) then begin
        SplitPos:=APos;
        exit;
      end;
      dec(APos);
    end;
  end;

  procedure SplitAtWordBoundary;
  var
    APos: Integer;
  begin
    if SplitPos>0 then exit;
    APos:=CurLineMax-1;
    while (APos>2) and (APos>(CurLineMax shr 1)) do begin
      if (ParsedSrc[APos]=stctWordStart) then begin
        SplitPos:=APos;
        exit;
      end;
      dec(APos);
    end;
  end;

  procedure SplitDefault;
  begin
    if SplitPos>0 then exit;
    SplitPos:=CurLineMax;
    while (SplitPos>1) do begin
      if (ParsedSrc[SplitPos]
      in [stctStart,stctWordStart,stctChar,stctHash,stctJunk])
      then
        break;
      dec(SplitPos);
    end;
  end;

  procedure Split;
  var
    CurIndent: Integer;
  begin
    // move left split side from Src to Result
    //DebugLn('Split: SplitPos=',SplitPos,' ',copy(Src,SplitPos-5,6));
    Result:=Result+copy(Src,1,SplitPos-1);
    Src:=copy(Src,SplitPos,length(Src)-SplitPos+1);
    if ParsedSrc[SplitPos] in [stctWordStart,stctChar] then begin
      // line break in string constant
      // -> add ' to end of last line and to start of next
      Result:=Result+'''';
      Src:=''''+Src;
    end;
    SrcLen:=length(Src);
    // calculate indent size for next line
    CurLineMax:=OtherLineLengths;
    CurIndent:=Indent;
    if CurIndent>(CurLineMax-10) then
      CurIndent:=CurLineMax-10;
    if CurIndent<0 then CurIndent:=0;
    // add indent spaces to Result
    Result:=Result+aLineBreak+GetIndentStr(CurIndent)+'+';
    // calculate next maximum line length
    CurLineMax:=CurLineMax-CurIndent-1;
  end;

begin
  Result:='';
  if FirstLineLength<5 then FirstLineLength:=5;
  if OtherLineLengths<5 then OtherLineLengths:=5;
  Src:=StringConstant;
  SrcLen:=length(Src);
  CurLineMax:=FirstLineLength;
  //DebugLn('SplitStringConstant FirstLineLength=',FirstLineLength,
  //' OtherLineLengths=',OtherLineLengths,' Indent=',Indent,' ');
  i:=0;
  repeat
    //DebugLn(['SrcLen=',SrcLen,' CurMaxLine=',CurLineMax]);
    //DebugLn('Src="',Src,'"');
    //DebugLn('Result="',Result,'"');
    if SrcLen<=CurLineMax then begin
      // line fits
      Result:=Result+Src;
      break;
    end;
    // split line -> search nice split position
    ParseSrc;
    //debugln(['ParsedSrc=',ParsedSrc]);
    SplitPos:=0;
    SplitAtNewLineCharConstant;
    SplitBetweenConstants;
    SplitAtWordBoundary;
    SplitDefault;
    if SplitPos<=1 then begin
      // no split possible
      Result:=Result+Src;
      break;
    end;
    //debugln(['SplitStringConstant SplitPos=',SplitPos]);
    Split;
    inc(i);
    if i>10 then break;
  until false;
  //DebugLn('END Result="',Result,'"');
  //DebugLn('SplitStringConstant END---------------------------------');
end;

procedure ImproveStringConstantStart(const ACode: string; var StartPos: integer);
// if StartPos is on the first character of a string constant it will be moved
// one in front, that means on the start of the string constant.
// Example:  'A' StartPos=2 -> StartPos:=1
var
  AtomStartPos, AtomEndPos: Integer;
  Len: Integer;
  SubTokenStart: LongInt;
begin
  AtomEndPos:=1;
  repeat
    AtomStartPos:=AtomEndPos;
    ReadRawNextPascalAtom(ACode,AtomEndPos,AtomStartPos,true);
    if (AtomEndPos>StartPos) then begin
      // token found
      Len:=length(ACode);
      while (AtomStartPos<=Len) do begin
        case (ACode[AtomStartPos]) of
        '#':
          begin
            SubTokenStart:=AtomStartPos;
            inc(AtomStartPos);
            while (AtomStartPos<=Len)
            and (ACode[AtomStartPos] in ['0'..'9']) do
              inc(AtomStartPos);
            if StartPos<AtomStartPos then begin
              StartPos:=SubTokenStart;
              exit;
            end;
          end;
        '''':
          begin
            inc(AtomStartPos);
            if StartPos=AtomStartPos then begin
              StartPos:=AtomStartPos-1;
              exit;
            end;
            while (AtomStartPos<=Len) do begin
              if (ACode[AtomStartPos]<>'''') then
                inc(AtomStartPos)
              else begin
                if (AtomStartPos<Len) and (ACode[AtomStartPos+1]='''') then
                  inc(AtomStartPos)
                else
                  break;
              end;
            end;
            inc(AtomStartPos);
          end;
        else
          break;
        end;
      end;
    end;
  until AtomEndPos>StartPos;
end;

procedure ImproveStringConstantEnd(const ACode: string; var EndPos: integer);
// if EndPos is on the last character of a string constant it will be moved
// to the end, that means on the end of the string constant.
// Example:  'A' EndPos=3 -> EndPos:=4
var
  AtomStartPos, AtomEndPos: Integer;
  Len: Integer;
begin
  AtomEndPos:=1;
  repeat
    AtomStartPos:=AtomEndPos;
    ReadRawNextPascalAtom(ACode,AtomEndPos,AtomStartPos,true);
    if (AtomEndPos>=EndPos) then begin
      // token found
      Len:=length(ACode);
      while (AtomStartPos<=Len) do begin
        case (ACode[AtomStartPos]) of
        '#':
          begin
            inc(AtomStartPos);
            while (AtomStartPos<=Len)
            and (ACode[AtomStartPos] in ['0'..'9']) do
              inc(AtomStartPos);
            if EndPos<AtomStartPos then begin
              EndPos:=AtomStartPos;
              exit;
            end;
          end;
        '''':
          begin
            inc(AtomStartPos);
            while (AtomStartPos<=Len) do begin
              if (ACode[AtomStartPos]<>'''') then
                inc(AtomStartPos)
              else begin
                if (AtomStartPos<Len) and (ACode[AtomStartPos+1]='''') then
                  inc(AtomStartPos)
                else
                  break;
              end;
            end;
            inc(AtomStartPos);
            if EndPos=AtomStartPos-1 then begin
              EndPos:=AtomStartPos;
              exit;
            end;
          end;
        else
          break;
        end;
      end;
    end;
  until AtomEndPos>=EndPos;
end;

function HexStrToIntDef(p: PChar; Def: integer): integer;
var
  OldP: PChar;
  i: Integer;
begin
  Result:=0;
  OldP:=p;
  while true do begin
    case p^ of
    '0'..'9': i:=ord(p^)-ord('0');
    'A'..'Z': i:=ord(p^)-ord('A')+10;
    'a'..'z': i:=ord(p^)-ord('a')+10;
    else
      exit;
    end;
    if Result>(High(Result) shr 4) then exit(Def);
    Result:=(Result shl 4)+i;
    inc(p);
  end;
  if OldP=p then exit(Def);
end;

function SearchNextInText(Search: PChar; SearchLen: PtrInt; Src: PChar;
  SrcLen: PtrInt; StartPos: PtrInt; out MatchStart, MatchEnd: PtrInt;
  WholeWords: boolean; MultiLine: boolean): boolean;
{ search Search in Src starting at StartPos.
  MatchEnd will be the position of the first character after the found pattern.
  if WholeWords then in front of MatchStart and behind MatchEnd will be
    a non word character.
  if MultiLine then newline characters are the same #13#10 = #10 = #13. }
var
  EndSrc: PChar;
  EndSearch: PChar;
  FirstChar: Char;
  CurPos: PChar;
  CmpSearch: PChar;
  CmpSrc: PChar;
begin
  Result:=false;
  MatchStart:=0;
  MatchEnd:=0;
  if (Search=nil) or (Src=nil) then exit;

  EndSrc:=@Src[SrcLen];
  EndSearch:=@Search[SearchLen];
  FirstChar:=Search^;
  CurPos:=@Src[StartPos];
  while (CurPos<EndSrc) do begin
    if (FirstChar=CurPos^)
    and ((not WholeWords) or (CurPos=Src) or (IsNonWordChar[PChar(CurPos-1)^]))
    then begin
      CmpSearch:=Search;
      CmpSrc:=CurPos;
      while (CmpSearch<EndSearch) and (CmpSrc<EndSrc) do begin
        if CmpSearch^=CmpSrc^ then begin
          inc(CmpSearch);
          inc(CmpSrc);
        end else if MultiLine
        and (CmpSrc^ in [#13,#10]) and (CmpSearch^ in [#13,#10]) then begin
          if (CmpSrc+1<EndSrc) and (CmpSrc[1] in [#13,#10])
          and (CmpSrc^<>CmpSrc[1]) then
            inc(CmpSrc,2)
          else
            inc(CmpSrc);
          if (CmpSearch+1<EndSearch) and (CmpSearch[1] in [#13,#10])
          and (CmpSearch^<>CmpSearch[1]) then
            inc(CmpSearch,2)
          else
            inc(CmpSearch);
        end else begin
          break;
        end;
      end;
      if (CmpSearch=EndSearch)
      and ((not WholeWords) or (CmpSrc=EndSrc) or (IsNonWordChar[CmpSrc^])) then
      begin
        // pattern found
        Result:=true;
        MatchStart:=CurPos-Src;
        MatchEnd:=CmpSrc-Src;
        exit;
      end;
    end;
    inc(CurPos);
  end;
end;

procedure HasTxtWord(SearchWord, Txt: PChar; out WholeWord: boolean; out
  Count: SizeInt);
var
  StartChar: Char;
  CurSearchP: PChar;
  CurTxtP: PChar;
  TxtRun: PChar;
begin
  WholeWord:=false;
  Count:=0;
  if (SearchWord=nil) or (SearchWord^=#0) then exit;
  if (Txt=nil) or (Txt^=#0) then exit;
  TxtRun:=Txt;
  StartChar:=SearchWord^;
  while TxtRun^<>#0 do begin
    if TxtRun^=StartChar then begin
      CurSearchP:=SearchWord+1;
      CurTxtP:=TxtRun+1;
      while (CurTxtP^=CurSearchP^) and (CurTxtP^<>#0) do begin
        inc(CurTxtP);
        inc(CurSearchP);
      end;
      if CurSearchP^=#0 then begin
        // word found
        if ((TxtRun=Txt) or IsNonWordChar[TxtRun[-1]])
        and IsNonWordChar[CurTxtP^] then begin
          // word boundaries
          if not WholeWord then begin
            WholeWord:=true;
            Count:=1;
          end else
            inc(Count);
        end else
          inc(Count);
        TxtRun:=CurTxtP;
        continue;
      end;
    end;
    inc(TxtRun);
  end;
end;

function SubString(p: PChar; Count: SizeInt): string;
var
  l: SizeInt;
begin
  if (p=nil) or (Count=0) then exit('');
  l:=IndexByte(p^,Count,0);
  if l<0 then l:=Count;
  if l=0 then exit('');
  SetLength(Result,l);
  System.Move(p^,Result[1],l);
end;

function ExtractFileNamespace(const Filename: string): string;
begin
  Result:=ExtractFileNameOnly(Filename);
  if Result='' then exit;
  Result:=ChompDottedIdentifier(Result);
end;

procedure AddToTreeOfUnitFilesOrNamespaces(var TreeOfUnitFiles,
  TreeOfNameSpaces: TAVLTree; const NameSpacePath, Filename: string;
  CaseInsensitive, KeepDoubles: boolean);

  procedure FileAndNameSpaceFits(const UnitName: string;
    out FileNameFits, NameSpaceFits: Boolean);
  var
    CompareCaseInsensitive: Boolean;
  begin
    FileNameFits := False;
    NameSpaceFits := False;
    if NameSpacePath = '' then begin
      FileNameFits := true;
      NameSpaceFits := true;
      Exit;
    end;
    if Length(UnitName) < Length(NameSpacePath) then Exit;

    CompareCaseInsensitive:=CaseInsensitive;
    {$IFDEF Windows}
    CompareCaseInsensitive:=true;
    {$ENDIF}

    if CompareText(PChar(UnitName), Length(NameSpacePath), PChar(NameSpacePath), Length(NameSpacePath), not CompareCaseInsensitive) = 0 then
    begin
      FileNameFits := PosEx('.', UnitName, Length(NameSpacePath)+1) = 0;
      NameSpaceFits := not FileNameFits;
    end;
  end;

var
  FileNameFits, NameSpaceFits: Boolean;
  UnitName: string;
begin
  UnitName := ExtractFileNameOnly(Filename);
  if not IsDottedIdentifier(UnitName) then exit;
  FileAndNameSpaceFits(UnitName, FileNameFits, NameSpaceFits);
  if FileNameFits then
    AddToTreeOfUnitFiles(TreeOfUnitFiles,FileName,UnitName,KeepDoubles);
  if NameSpaceFits then
    AddToTreeOfNamespaces(TreeOfNamespaces,UnitName,NameSpacePath,KeepDoubles)
end;

function GatherUnitFiles(const BaseDir, SearchPath, Extensions,
  NameSpacePath: string; KeepDoubles, CaseInsensitive: boolean;
  var TreeOfUnitFiles, TreeOfNamespaces: TAVLTree): boolean;
{ BaseDir: base directory, used when SearchPath is relative
 SearchPath: semicolon separated list of directories
 Extensions: semicolon separated list of extensions (e.g. 'pas;.pp;ppu')
 NameSpacePath: gather files only from this namespace path, empty '' for all
 KeepDoubles: false to return only the first match of each unit
 CaseInsensitive: true to ignore case on comparing extensions
 TreeOfUnitFiles: tree of TUnitFileInfo
 TreeOfNamespaces: tree of TNameSpaceInfo }
var
  SearchedDirectories: TAVLTree; // tree of AnsiString

  function DirectoryAlreadySearched(const ADirectory: string): boolean;
  begin
    Result:=(SearchedDirectories<>nil)
        and (SearchedDirectories.Find(Pointer(ADirectory))<>nil);
  end;

  procedure MarkDirectoryAsSearched(const ADirectory: string);
  var
    s: String;
  begin
    // increase refcount
    //DebugLn('MarkDirectoryAsSearched ',ADirectory);
    s:=ADirectory;  // increase refcount
    if SearchedDirectories=nil then
      SearchedDirectories:=TAVLTree.Create(@CompareAnsiStringFilenames);
    SearchedDirectories.Add(Pointer(s));
    Pointer(s):=nil; // keep refcount
  end;

  procedure FreeSearchedDirectories;
  var
    ANode: TAVLTreeNode;
    s: String;
  begin
    if SearchedDirectories=nil then exit;
    s:='';
    ANode:=SearchedDirectories.FindLowest;
    while ANode<>nil do begin
      Pointer(s):=ANode.Data;
      //DebugLn('FreeSearchedDirectories ',s);
      s:=''; // decrease refcount
      ANode:=SearchedDirectories.FindSuccessor(ANode);
    end;
    if s='' then ;
    SearchedDirectories.Free;
  end;

  function ExtensionFits(const Filename: string): boolean;
  var
    ExtStart: Integer;
    ExtLen: Integer; // length without '.'
    CurExtStart: Integer;
    CurExtEnd: LongInt;
    CompareCaseInsensitive: Boolean;
    p: Integer;
  begin
    CompareCaseInsensitive:=CaseInsensitive;
    {$IFDEF Windows}
    CompareCaseInsensitive:=true;
    {$ENDIF}

    ExtStart:=length(Filename);
    while (ExtStart>=1) and (not (Filename[ExtStart] in [PathDelim,'.'])) do
      dec(ExtStart);
    if (ExtStart>0) and (Filename[ExtStart]='.') then begin
      // filename has an extension
      ExtLen:=length(Filename)-ExtStart;
      inc(ExtStart);
      CurExtStart:=1;
      while (CurExtStart<=length(Extensions)) do begin
        // skip '.'
        if Extensions[CurExtStart]='.' then inc(CurExtStart);
        // read till semicolon
        CurExtEnd:=CurExtStart;
        while (CurExtEnd<=length(Extensions)) and (Extensions[CurExtEnd]<>';')
        do
          inc(CurExtEnd);
        if (CurExtEnd>CurExtStart) and (CurExtEnd-CurExtStart=ExtLen) then begin
          // compare extension
          p:=ExtLen-1;
          while (p>=0) do begin
            if CompareCaseInsensitive then begin
              if UpChars[Filename[ExtStart+p]]
              <>UpChars[Extensions[CurExtStart+p]]
              then
                break;
            end else begin
              if Filename[ExtStart+p]<>Extensions[CurExtStart+p] then
                break;
            end;
            dec(p);
          end;
          if p<0 then begin
            // extension fit
            Result:=true;
            exit;
          end;
        end;
        CurExtStart:=CurExtEnd+1;
      end;
    end;
    Result:=false;
  end;

  function SearchDirectory(const ADirectory: string): boolean;
  var
    FileInfo: TSearchRec;
  begin
    Result:=true;
    //DebugLn('SearchDirectory ADirectory="',ADirectory,'"');
    if DirectoryAlreadySearched(ADirectory) then exit;
    MarkDirectoryAsSearched(ADirectory);
    //DebugLn('SearchDirectory searching ...');

    if not DirPathExists(ADirectory) then exit;
    if FindFirstUTF8(ADirectory+FileMask,faAnyFile,FileInfo)=0 then begin
      repeat
        // check if special file
        if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='')
        then
          continue;
        if ExtensionFits(FileInfo.Name) then begin
          AddToTreeOfUnitFilesOrNamespaces(TreeOfUnitFiles, TreeOfNamespaces,
            NameSpacePath, ADirectory+FileInfo.Name, CaseInsensitive, KeepDoubles);
        end;
      until FindNextUTF8(FileInfo)<>0;
    end;
    FindCloseUTF8(FileInfo);
  end;

var
  PathStartPos: Integer;
  PathEndPos: LongInt;
  CurDir: String;
begin
  Result:=false;
  SearchedDirectories:=nil;
  try
    // search all paths in SearchPath
    PathStartPos:=1;
    while PathStartPos<=length(SearchPath) do begin
      PathEndPos:=PathStartPos;
      while (PathEndPos<=length(SearchPath)) and (SearchPath[PathEndPos]<>';')
      do
        inc(PathEndPos);
      if PathEndPos>PathStartPos then begin
        CurDir:=AppendPathDelim(TrimFilename(
                        copy(SearchPath,PathStartPos,PathEndPos-PathStartPos)));
        if not FilenameIsAbsolute(CurDir) then
          CurDir:=AppendPathDelim(BaseDir)+CurDir;
        if not SearchDirectory(CurDir) then exit;
      end;
      PathStartPos:=PathEndPos;
      while (PathStartPos<=length(SearchPath))
      and (SearchPath[PathStartPos]=';') do
        inc(PathStartPos);
    end;
    Result:=true;
  finally
    FreeSearchedDirectories;
  end;
end;

procedure FreeTreeOfUnitFiles(TreeOfUnitFiles: TAVLTree);
begin
  if TreeOfUnitFiles=nil then exit;
  TreeOfUnitFiles.FreeAndClear;
  TreeOfUnitFiles.Free;
end;

procedure AddToTreeOfUnitFiles(var TreeOfUnitFiles: TAVLTree; const Filename,
  Unitname: string; KeepDoubles: boolean);
var
  NewItem: TUnitFileInfo;
begin
  if (not KeepDoubles) then begin
    if (TreeOfUnitFiles<>nil)
    and (TreeOfUnitFiles.FindKey(Pointer(UnitName),
                                 @CompareUnitNameAndUnitFileInfo)<>nil)
    then begin
      // an unit with the same name was already found and doubles are not
      // wanted
      exit;
    end;
  end;
  // add
  if TreeOfUnitFiles=nil then
    TreeOfUnitFiles:=TAVLTree.Create(@CompareUnitFileInfos);
  NewItem:=TUnitFileInfo.Create(UnitName,Filename);
  TreeOfUnitFiles.Add(NewItem);
end;

procedure AddToTreeOfNamespaces(var TreeOfNameSpaces: TAVLTree; const UnitName,
  ParentNameSpacePath: string; KeepDoubles: boolean);
var
  AnNameSpace: String;
  NewItem: TNameSpaceInfo;
  PointPos: Integer;
begin
  PointPos := PosEx('.', UnitName, Length(ParentNameSpacePath)+1);
  if PointPos = 0 then Exit;
  AnNameSpace:=Copy(UnitName, Length(ParentNameSpacePath)+1, PointPos - Length(ParentNameSpacePath) - 1);
  if AnNameSpace = '' then Exit;
  if (not KeepDoubles) then begin
    if (TreeOfNameSpaces<>nil)
    and (TreeOfNameSpaces.FindKey(Pointer(AnNameSpace),
                                 @CompareNameSpaceAndNameSpaceInfo)<>nil)
    then begin
      // a namespace with the same name was already found and doubles are not
      // wanted
      exit;
    end;
  end;
  // add
  if TreeOfNameSpaces=nil then
    TreeOfNameSpaces:=TAVLTree.Create(@CompareNameSpaceInfos);
  NewItem:=TNameSpaceInfo.Create(AnNameSpace,UnitName,Length(ParentNameSpacePath)+1);
  TreeOfNameSpaces.Add(NewItem);
end;

function CompareUnitFileInfos(Data1, Data2: Pointer): integer;
begin
  Result:=CompareIdentifiers(PChar(TUnitFileInfo(Data1).FileUnitName),
                             PChar(TUnitFileInfo(Data2).FileUnitName));
end;

function CompareNameSpaceInfos(Data1, Data2: Pointer): integer;
begin
  Result:=CompareIdentifiers(PChar(TNameSpaceInfo(Data1).NameSpace),
                             PChar(TNameSpaceInfo(Data2).NameSpace));
end;

function CompareUnitNameAndUnitFileInfo(UnitnamePAnsiString,
  UnitFileInfo: Pointer): integer;
begin
  //do not use CompareIdentifiers - they compare only to the first "."
  Result:=CompareText(PChar(UnitnamePAnsiString),
                      PChar(TUnitFileInfo(UnitFileInfo).FileUnitName));
end;

function CompareNameSpaceAndNameSpaceInfo(NamespacePAnsiString,
  NamespaceInfo: Pointer): integer;
begin
  //do not use CompareIdentifiers - they compare only to the first "."
  Result:=CompareText(PChar(NamespacePAnsiString),
                      PChar(TNameSpaceInfo(NamespaceInfo).NameSpace));
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 AdjustPositionAfterInsert(var p: integer; IsStart: boolean; FromPos,
  ToPos, DiffPos: integer);
begin
  if (ToPos>FromPos) then begin
    // replace
    if p>FromPos then begin
      if p>ToPos then
        inc(p,DiffPos)
      else
        p:=FromPos;
    end;
  end else begin
    // insert
    if IsStart then begin
      if p>=FromPos then inc(p,DiffPos);
    end else begin
      if p>FromPos then inc(p,DiffPos);
    end;
  end;
end;

function CompareText(Txt1: PChar; Len1: integer; Txt2: PChar; Len2: integer;
  CaseSensitive: boolean): integer;
begin
  if CaseSensitive then begin
    while (Len1>0) and (Len2>0) do begin
      if Txt1^=Txt2^ then begin
        inc(Txt1);
        dec(Len1);
        inc(Txt2);
        dec(Len2);
      end else begin
        if Txt1^<Txt2^ then
          Result:=1
        else
          Result:=-1;
        exit;
      end;
    end;
  end else begin
    while (Len1>0) and (Len2>0) do begin
      if UpChars[Txt1^]=UpChars[Txt2^] then begin
        inc(Txt1);
        dec(Len1);
        inc(Txt2);
        dec(Len2);
      end else begin
        if UpChars[Txt1^]<UpChars[Txt2^] then
          Result:=1
        else
          Result:=-1;
        exit;
      end;
    end;
  end;
  if Len1>Len2 then
    Result:=-1
  else if Len1<Len2 then
    Result:=1
  else
    Result:=0;
end;

function CompareTextCT(const Txt1, Txt2: string; CaseSensitive: boolean): integer;
begin
  Result:=CompareText(PChar(Pointer(Txt1)),length(Txt1),
                      PChar(Pointer(Txt2)),length(Txt2),CaseSensitive);
end;

function CompareText(Txt1: PChar; Len1: integer; Txt2: PChar; Len2: integer;
  CaseSensitive, IgnoreSpace: boolean): integer;
begin
  if IgnoreSpace then
    Result:=CompareTextIgnoringSpace(Txt1,Len1,Txt2,Len2,CaseSensitive)
  else
    Result:=CompareText(Txt1,Len1,Txt2,Len2,CaseSensitive);
end;

{ TNameSpaceInfo }

constructor TNameSpaceInfo.Create(const TheNamespace, TheUnitName: string;
  TheIdentifierStartInUnitName: Integer);
begin
  FNamespace:=TheNamespace;
  FUnitName:=TheUnitName;
  FIdentifierStartInUnitName:=TheIdentifierStartInUnitName;
end;

{ TUnitFileInfo }

constructor TUnitFileInfo.Create(const TheUnitName, TheFilename: string);
begin
  FUnitName:=TheUnitName;
  FFilename:=TheFilename;
end;

function TUnitFileInfo.GetFileUnitNameWithoutNamespace: string;
var
  LastPoint: Integer;
begin
  LastPoint := LastDelimiter('.', FUnitName);
  if LastPoint > 0 then
    Result := Copy(FUnitName, LastPoint+1, High(Integer))
  else
    Result := FUnitName;
end;

function TUnitFileInfo.GetIdentifierStartInUnitName: Integer;
var
  LastPoint: Integer;
begin
  LastPoint := LastDelimiter('.', FUnitName);
  if LastPoint > 0 then
    Result := LastPoint+1
  else
    Result := 1;
end;

//=============================================================================

end.

