{
 ***************************************************************************
 *                                                                         *
 *   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    TPascalReaderTool enhances TPascalParserTool.
    This tool provides a lot of useful functions to read the output of the
    TPascalParserTool.
}
unit PascalReaderTool;

{$ifdef FPC}{$mode objfpc}{$endif}{$H+}

interface

{$I codetools.inc}

uses
  {$IFDEF MEM_CHECK}
  MemCheck,
  {$ENDIF}
  Classes, SysUtils, FileProcs, CodeToolsStrConsts, CodeTree, CodeCache,
  CodeAtom, CustomCodeTool, PascalParserTool, KeywordFuncLists, BasicCodeTools,
  LinkScanner, AVL_Tree, LazFileUtils, LazDbgLog;

type
  TPascalHintModifier = (
    phmDeprecated,
    phmPlatform,
    phmLibrary,
    phmUnimplemented,
    phmExperimental
    );
  TPascalHintModifiers = set of TPascalHintModifier;

  TEPRIRange = (
    epriInCode,
    epriInComment,
    epriInDirective
    );

  //the scope groups of pascal methods.
  //please note that Destructor is principally a method and thus is not listed here -> you cannot define "procedure Destroy;" and "destructor Destroy" in one class
  TPascalMethodGroup = (mgMethod, mgConstructor, mgClassConstructor, mgClassDestructor);

  TPascalMethodHeader = record
    Name: string;
    Group: TPascalMethodGroup;
  end;

  TClassSectionVisibility = (
    csvEverything,//same class same unit
    csvPrivateAndHigher,//same unit different class
    csvProtectedAndHigher,//ancestor class different unit
    csvPublicAndHigher);//other class other unit

  TOnEachPRIdentifier = procedure(Sender: TPascalParserTool;
    IdentifierCleanPos: integer; Range: TEPRIRange;
    Node: TCodeTreeNode; Data: Pointer; var Abort: boolean) of object;

  { TPascalReaderTool }

  TPascalReaderTool = class(TPascalParserTool)
  protected
    CachedSourceName: string;
    procedure RaiseStrConstExpected;
  public
    // comments
    function CleanPosIsInComment(CleanPos, CleanCodePosInFront: integer;
        out CommentStart, CommentEnd: integer;
        OuterCommentBounds: boolean = true): boolean;

    // general extraction
    function ExtractNode(ANode: TCodeTreeNode;
        Attr: TProcHeadAttributes): string;
    function ExtractCode(StartPos, EndPos: integer;
        Attr: TProcHeadAttributes): string;
    function ExtractBrackets(BracketStartPos: integer;
        Attr: TProcHeadAttributes): string;
    function ExtractIdentifierWithPoints(StartPos: integer;
        ExceptionOnError: boolean): string;
    function ExtractIdentCharsFromStringConstant(
        StartPos, MinPos, MaxPos, MaxLen: integer): string;
    function ReadStringConstantValue(StartPos: integer): string;
    function GetNodeIdentifier(Node: TCodeTreeNode): PChar;
    function GetHintModifiers(Node: TCodeTreeNode): TPascalHintModifiers;
    procedure ForEachIdentifierInCleanSrc(StartPos, EndPos: integer;
        SkipComments: boolean; Node: TCodeTreeNode;
        const OnIdentifier: TOnEachPRIdentifier; Data: pointer;
        var Abort: boolean); // range in clean source
    procedure ForEachIdentifierInNode(Node: TCodeTreeNode; SkipComments: boolean;
        const OnIdentifier: TOnEachPRIdentifier; Data: Pointer; var Abort: boolean); // node and child nodes
    procedure ForEachIdentifier(SkipComments: boolean;
        const OnIdentifier: TOnEachPRIdentifier; Data: Pointer); // whole unit/program

    // properties
    function ExtractPropType(PropNode: TCodeTreeNode;
                             InUpperCase, EmptyIfIndexed: boolean): string;
    function MoveCursorToPropType(PropNode: TCodeTreeNode): boolean;
    function MoveCursorToPropName(PropNode: TCodeTreeNode): boolean;
    procedure MoveCursorBehindPropName(PropNode: TCodeTreeNode);
    function ExtractPropName(PropNode: TCodeTreeNode;
                             InUpperCase: boolean): string;
    function ExtractProperty(PropNode: TCodeTreeNode;
                             Attr: TProcHeadAttributes): string;
    function GetPropertyNameIdentifier(PropNode: TCodeTreeNode): PChar;
    function GetPropertyTypeIdentifier(PropNode: TCodeTreeNode): PChar;
    function PositionInPropertyName(PropNode: TCodeTreeNode;
                                    CleanPos: integer): boolean;
    function PropertyIsDefault(PropertyNode: TCodeTreeNode): boolean;
    function PropertyNodeHasParamList(PropNode: TCodeTreeNode): boolean;
    function PropNodeIsTypeLess(PropNode: TCodeTreeNode): boolean;
    function PropertyHasSpecifier(PropNode: TCodeTreeNode;
                 UpperKeyword: string; ExceptionOnNotFound: boolean = true): boolean;

    // procs
    function ExtractProcName(ProcNode: TCodeTreeNode;
        Attr: TProcHeadAttributes): string;
    function ExtractProcHead(ProcNode: TCodeTreeNode;
        Attr: TProcHeadAttributes): string;
    function ExtractProcHeadWithGroup(ProcNode: TCodeTreeNode;
        Attr: TProcHeadAttributes): TPascalMethodHeader;
    function ExtractProcedureHeader(CursorPos: TCodeXYPosition;
      Attributes: TProcHeadAttributes; var ProcHead: string): boolean;
    function ExtractClassNameOfProcNode(ProcNode: TCodeTreeNode;
        AddParentClasses: boolean = true): string;
    function ProcNodeHasSpecifier(ProcNode: TCodeTreeNode;
        ProcSpec: TProcedureSpecifier): boolean;
    function GetProcNameIdentifier(ProcNode: TCodeTreeNode): PChar;
    function FindProcNode(StartNode: TCodeTreeNode; const AProcHead: string;
        AProcSpecType: TPascalMethodGroup;
        Attr: TProcHeadAttributes; Visibility: TClassSectionVisibility = csvEverything): TCodeTreeNode; overload;
    function FindProcNode(StartNode: TCodeTreeNode; const AProcHead: TPascalMethodHeader;
        Attr: TProcHeadAttributes; Visibility: TClassSectionVisibility = csvEverything): TCodeTreeNode; overload;
    function FindCorrespondingProcNode(ProcNode: TCodeTreeNode;
        Attr: TProcHeadAttributes = [phpWithoutClassKeyword,phpWithoutClassName]
        ): TCodeTreeNode;
    function FindCorrespondingProcParamNode(ProcParamNode: TCodeTreeNode;
        Attr: TProcHeadAttributes = [phpInUpperCase,phpWithoutClassName,phpWithVarModifiers]
        ): TCodeTreeNode;
    function FindProcBody(ProcNode: TCodeTreeNode): TCodeTreeNode;
    function ProcBodyIsEmpty(ProcNode: TCodeTreeNode): boolean;
    function ExtractProcedureGroup(ProcNode: TCodeTreeNode): TPascalMethodGroup;
    procedure MoveCursorToFirstProcSpecifier(ProcNode: TCodeTreeNode);
    function MoveCursorToProcSpecifier(ProcNode: TCodeTreeNode;
        ProcSpec: TProcedureSpecifier): boolean;
    procedure MoveCursorToProcName(ProcNode: TCodeTreeNode;
        SkipClassName: boolean);
    procedure MoveCursorBehindProcName(ProcNode: TCodeTreeNode);
    function PositionInProcName(ProcNode: TCodeTreeNode;
                                SkipClassName: boolean; CleanPos: integer): boolean;
    function PositionInFuncResultName(ProcNode: TCodeTreeNode;
                                      CleanPos: integer): boolean;
    function ProcNodeHasParamList(ProcNode: TCodeTreeNode): boolean;
    function ProcNodeHasOfObject(ProcNode: TCodeTreeNode): boolean;
    function GetProcParamList(ProcNode: TCodeTreeNode;
                              Parse: boolean = true): TCodeTreeNode;
    function NodeIsInAMethod(Node: TCodeTreeNode): boolean;
    function NodeIsMethodBody(ProcNode: TCodeTreeNode): boolean;
    function GetMethodOfBody(Node: TCodeTreeNode): TCodeTreeNode;
    function NodeIsFunction(ProcNode: TCodeTreeNode): boolean;
    function NodeIsClassConstructorOrDestructor(ProcNode: TCodeTreeNode): boolean;
    function NodeIsConstructor(ProcNode: TCodeTreeNode): boolean;
    function NodeIsDestructor(ProcNode: TCodeTreeNode): boolean;
    function NodeIsForwardProc(ProcNode: TCodeTreeNode): boolean;
    function NodeIsOperator(ProcNode: TCodeTreeNode): boolean;
    function NodeIsResultIdentifier(Node: TCodeTreeNode): boolean;
    function NodeIsResultType(Node: TCodeTreeNode): boolean;

    // classes
    function ExtractClassName(Node: TCodeTreeNode;
        InUpperCase: boolean; WithParents: boolean = true;
        WithGenericParams: boolean = false): string;
    function ExtractClassPath(Node: TCodeTreeNode): string;
    function ExtractClassInheritance(ClassNode: TCodeTreeNode;
        Attr: TProcHeadAttributes): string;
    function FindClassNode(StartNode: TCodeTreeNode;
        const AClassName: string; // nested: A.B
        IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
    function FindClassNodeBackwards(StartNode: TCodeTreeNode;
        const AClassName: string;
        IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
    function FindNestedClass(RootClassNode: TCodeTreeNode;
         AClassName: PChar; SkipFirst: boolean): TCodeTreeNode;
    function FindClassNode(CursorNode: TCodeTreeNode): TCodeTreeNode;
    function FindClassNodeForMethodBody(ProcNode: TCodeTreeNode;
        IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
    function FindClassOrInterfaceNode(CursorNode: TCodeTreeNode;
        FindClassOfMethod: boolean = false): TCodeTreeNode;
    function FindClassSection(ClassNode: TCodeTreeNode;
        NodeDesc: TCodeTreeNodeDesc): TCodeTreeNode;
    function FindLastClassSection(ClassNode: TCodeTreeNode;
        NodeDesc: TCodeTreeNodeDesc): TCodeTreeNode;
    function GetClassVisibility(Node: TCodeTreeNode): TCodeTreeNodeDesc;
    function FindClassNodeInInterface(const AClassName: string;
        IgnoreForwards, IgnoreNonForwards, ErrorOnNotFound: boolean): TCodeTreeNode;
    function FindClassNodeInUnit(const AClassName: string;
        IgnoreForwards, IgnoreNonForwards, IgnoreImplementation,
        ErrorOnNotFound: boolean): TCodeTreeNode;
    function FindFirstIdentNodeInClass(ClassNode: TCodeTreeNode): TCodeTreeNode;
    function FindLastIdentNodeInClass(ClassNode: TCodeTreeNode): TCodeTreeNode;
    function FindNextIdentNodeInClass(Node: TCodeTreeNode): TCodeTreeNode;
    function FindPriorIdentNodeInClass(Node: TCodeTreeNode): TCodeTreeNode;
    function ClassSectionNodeStartsWithWord(ANode: TCodeTreeNode): boolean;
    function IsClassNode(Node: TCodeTreeNode): boolean; // class, not object
    function FindInheritanceNode(ClassNode: TCodeTreeNode): TCodeTreeNode;
    function FindHelperForNode(HelperNode: TCodeTreeNode): TCodeTreeNode;
    function IdentNodeIsInVisibleClassSection(Node: TCodeTreeNode; Visibility: TClassSectionVisibility): Boolean;

    // records
    function ExtractRecordCaseType(RecordCaseNode: TCodeTreeNode): string;

    // variables, types
    function FindVarNode(StartNode: TCodeTreeNode;
        const UpperVarName: string;
        Visibility: TClassSectionVisibility = csvEverything): TCodeTreeNode;
    function FindTypeNodeOfDefinition(
        DefinitionNode: TCodeTreeNode): TCodeTreeNode;
    function NodeIsPartOfTypeDefinition(ANode: TCodeTreeNode): boolean;
    function ExtractDefinitionNodeType(DefinitionNode: TCodeTreeNode): string;
    function ExtractDefinitionName(DefinitionNode: TCodeTreeNode): string;
    function PositionInDefinitionName(DefinitionNode: TCodeTreeNode;
                                      CleanPos: integer): boolean;
    function MoveCursorToParameterSpecifier(DefinitionNode: TCodeTreeNode
                                            ): boolean;
    function GetFirstGroupVarNode(VarNode: TCodeTreeNode): TCodeTreeNode;
    function NodeIsIdentifierInInterface(Node: TCodeTreeNode): boolean;
    function NodeCanHaveForwardType(TypeNode: TCodeTreeNode): boolean;
    function NodeIsForwardType(TypeNode: TCodeTreeNode): boolean;
    function FindForwardTypeNode(TypeNode: TCodeTreeNode;
                                 SearchFirst: boolean): TCodeTreeNode;
    function FindTypeOfForwardNode(TypeNode: TCodeTreeNode): TCodeTreeNode;
    function FindEndOfWithExpr(WithVarNode: TCodeTreeNode): integer;
    function ExtractWithBlockExpression(WithVarNode: TCodeTreeNode; Attr: TProcHeadAttributes = []): string;
    function FindWithBlockStatement(WithVarNode: TCodeTreeNode): TCodeTreeNode;

    // arrays
    function ExtractArrayRange(ArrayNode: TCodeTreeNode;
        Attr: TProcHeadAttributes): string;

    // module sections
    function ExtractSourceName: string;
    function GetSourceNamePos(out NamePos: TAtomPosition): boolean;
    function GetSourceName(DoBuildTree: boolean = true): string;
    function GetSourceType: TCodeTreeNodeDesc;
    function PositionInSourceName(CleanPos: integer): boolean;

    // uses sections
    procedure MoveCursorToUsesStart(UsesNode: TCodeTreeNode);
    procedure MoveCursorToUsesEnd(UsesNode: TCodeTreeNode);
    function ReadNextUsedUnit(out UnitNameRange, InAtom: TAtomPosition;
          SyntaxExceptions: boolean = true): boolean;
    procedure ReadPriorUsedUnit(out UnitNameRange, InAtom: TAtomPosition);
    function ExtractUsedUnitNameAtCursor(InFilename: PAnsiString = nil): string;
    function ExtractUsedUnitName(UseUnitNode: TCodeTreeNode;
          InFilename: PAnsiString = nil): string;
    function ReadAndCompareUsedUnit(const AnUnitName: string): boolean;

    // comments
    function FindCommentInFront(const StartPos: TCodeXYPosition;
          const CommentText: string; InvokeBuildTree, SearchInParentNode,
          WithCommentBounds, CaseSensitive, IgnoreSpaces,
          CompareOnlyStart: boolean;
          out CommentStart, CommentEnd: TCodeXYPosition): boolean;
    function FindCommentInFront(StartPos: integer;
          const CommentText: string; SearchInParentNode,
          WithCommentBounds, CaseSensitive, IgnoreSpaces,
          CompareOnlyStart: boolean;
          out CommentStart, CommentEnd: integer): boolean;
    function GetPasDocComments(const StartPos: TCodeXYPosition;
                               InvokeBuildTree: boolean;
                               out ListOfPCodeXYPosition: TFPList): boolean;
    function GetPasDocComments(Node: TCodeTreeNode;
                               out ListOfPCodeXYPosition: TFPList): boolean;

    procedure CalcMemSize(Stats: TCTMemStats); override;
  end;

function CompareMethodHeaders(const Method1Name: string; Method1Group: TPascalMethodGroup;
  const Method2Name: string; Method2Group: TPascalMethodGroup): Integer; overload;
function CompareMethodHeaders(const Method1Head: TPascalMethodHeader; const Method2Head: TPascalMethodHeader): Integer; overload;
function SameMethodHeaders(const Method1Name: string; Method1Group: TPascalMethodGroup;
  const Method2Name: string; Method2Group: TPascalMethodGroup): Boolean; overload;
function SameMethodHeaders(const Method1Head: TPascalMethodHeader; const Method2Head: TPascalMethodHeader): Boolean; overload;
function CompareCodeTreeNodeExtMethodHeaders(NodeData1, NodeData2: pointer): integer;

implementation

function CompareMethodHeaders(const Method1Name: string;
  Method1Group: TPascalMethodGroup; const Method2Name: string;
  Method2Group: TPascalMethodGroup): Integer;
begin
  Result := (Ord(Method1Group) - Ord(Method2Group));
  if Result <> 0 then exit;
  Result := CompareTextIgnoringSpace(Method1Name,Method2Name,false);
end;

function CompareMethodHeaders(const Method1Head: TPascalMethodHeader;
  const Method2Head: TPascalMethodHeader): Integer;
begin
  Result := CompareMethodHeaders(Method1Head.Name, Method1Head.Group,
    Method2Head.Name, Method2Head.Group);
end;

function SameMethodHeaders(const Method1Name: string;
  Method1Group: TPascalMethodGroup; const Method2Name: string;
  Method2Group: TPascalMethodGroup): Boolean;
begin
  Result := CompareMethodHeaders(Method1Name, Method1Group,  Method2Name,  Method2Group) = 0;
end;

function SameMethodHeaders(const Method1Head: TPascalMethodHeader;
  const Method2Head: TPascalMethodHeader): Boolean;
begin
  Result := CompareMethodHeaders(Method1Head, Method2Head) = 0;
end;

function CompareCodeTreeNodeExtMethodHeaders(NodeData1, NodeData2: pointer): integer;
var
  NodeExt1: TCodeTreeNodeExtension absolute NodeData1;
  NodeExt2: TCodeTreeNodeExtension absolute NodeData2;
begin
  Result:=CompareMethodHeaders(NodeExt1.Txt,TPascalMethodGroup(NodeExt1.Flags),NodeExt2.Txt,TPascalMethodGroup(NodeExt2.Flags));
end;


{ TPascalReaderTool }

procedure TPascalReaderTool.RaiseStrConstExpected;
begin
  RaiseExceptionFmt(ctsStrExpectedButAtomFound,[ctsStringConstant,GetAtom]);
end;

function TPascalReaderTool.CleanPosIsInComment(CleanPos,
  CleanCodePosInFront: integer; out CommentStart, CommentEnd: integer;
  OuterCommentBounds: boolean): boolean;
var CommentLvl, CurCommentPos: integer;
  CurEnd: Integer;
  CurCommentInnerEnd: Integer;
begin
  Result:=false;
  CommentStart:=0;
  CommentEnd:=0;
  if CleanPos>SrcLen then exit;
  if CleanCodePosInFront>CleanPos then
    RaiseException(
      'TPascalReaderTool.CleanPosIsInComment CleanCodePosInFront>CleanPos');
  MoveCursorToCleanPos(CleanCodePosInFront);
  repeat
    ReadNextAtom;
    if CurPos.StartPos>CleanPos then begin
      //DebugLn(['TPascalReaderTool.CleanPosIsInComment ',GetATom,' StartPos=',CurPos.StartPos,' CleanPos=',CleanPos]);
      // CleanPos between two atoms -> parse space between for comments
      if LastAtoms.Count>0 then
        CommentStart:=LastAtoms.GetValueAt(0).EndPos
      else
        CommentStart:=CleanCodePosInFront;
      CurEnd:=CurPos.StartPos;
      if CurEnd>SrcLen then CurEnd:=SrcLen+1;
      while CommentStart<CurEnd do begin
        if IsCommentStartChar[Src[CommentStart]] then begin
          CurCommentPos:=CommentStart;
          CurCommentInnerEnd:=CurEnd;
          case Src[CommentStart] of
          '{':
            begin
              inc(CurCommentPos);
              if (CurCommentPos<=SrcLen) and (Src[CurCommentPos]=#3) then begin
                // codetools skip comment
                inc(CurCommentPos);
                if not OuterCommentBounds then CommentStart:=CurCommentPos;
                while (CurCommentPos<CurEnd) do begin
                  if (Src[CurCommentPos]=#3)
                  and (CurCommentPos+1<CurEnd) and (Src[CurCommentPos+1]='}')
                  then begin
                    CurCommentInnerEnd:=CurCommentPos;
                    inc(CurCommentPos,2);
                    break;
                  end;
                  inc(CurCommentPos);
                end;
              end else begin
                // pascal comment
                if not OuterCommentBounds then CommentStart:=CurCommentPos;
                CommentLvl:=1;
                while (CurCommentPos<CurEnd) do begin
                  case Src[CurCommentPos] of
                  '{': if Scanner.NestedComments then inc(CommentLvl);
                  '}':
                    begin
                      dec(CommentLvl);
                      if (CommentLvl=0) then begin
                        CurCommentInnerEnd:=CurCommentPos;
                        inc(CurCommentPos);
                        break;
                      end;
                    end;
                  end;
                  inc(CurCommentPos);
                end;
              end;
            end;
          '/':  // Delphi comment
            if (CurCommentPos<SrcLen) and (Src[CurCommentPos+1]='/') then
            begin
              inc(CurCommentPos,2);
              if not OuterCommentBounds then CommentStart:=CurCommentPos;
              while (CurCommentPos<CurEnd)
              and (not (Src[CurCommentPos] in [#10,#13])) do
                inc(CurCommentPos);
              CurCommentInnerEnd:=CurCommentPos;
              inc(CurCommentPos);
              if (CurCommentPos<CurEnd)
              and (Src[CurCommentPos] in [#10,#13])
              and (Src[CurCommentPos-1]<>Src[CurCommentPos]) then
                inc(CurCommentPos);
            end else
              break;
          '(': // Turbo pascal comment
            if (CurCommentPos<SrcLen) and (Src[CurCommentPos+1]='*') then
            begin
              inc(CurCommentPos,2);
              if not OuterCommentBounds then CommentStart:=CurCommentPos;
              while (CurCommentPos<CurEnd) do begin
                if (Src[CurCommentPos]='*') and (CurCommentPos+1<CurEnd)
                and (Src[CurCommentPos+1]=')') then
                begin
                  CurCommentInnerEnd:=CurCommentPos;
                  inc(CurCommentPos,2);
                  break;
                end;
                inc(CurCommentPos);
              end;
            end else
              break;
          end;
          if (CurCommentPos>CommentStart) and (CleanPos<CurCommentPos) then
          begin
            // CleanPos in comment
            if OuterCommentBounds then
              CommentEnd:=CurCommentPos
            else
              CommentEnd:=CurCommentInnerEnd;
            exit(true);
          end;
          CommentStart:=CurCommentPos;
        end else if IsSpaceChar[Src[CommentStart]] then begin
          repeat
            inc(CommentStart);
          until (CommentStart>=CommentEnd)
          or (not (IsSpaceChar[Src[CommentStart]]));
        end else begin
          break;
        end;
      end;
      // CleanPos not in a comment
      exit;
    end else if CurPos.EndPos>CleanPos then begin
      // CleanPos not in a comment
      exit;
    end;
    CleanCodePosInFront:=CurPos.EndPos;
  until CurPos.StartPos>=SrcLen;
end;

function TPascalReaderTool.ExtractPropType(PropNode: TCodeTreeNode;
  InUpperCase, EmptyIfIndexed: boolean): string;
begin
  Result:='';
  if (PropNode=nil)
  or ((PropNode.Desc<>ctnProperty) and (PropNode.Desc<>ctnGlobalProperty)) then
    exit;
  MoveCursorToNodeStart(PropNode);
  ReadNextAtom;
  if (PropNode.Desc=ctnProperty) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    if (not UpAtomIs('PROPERTY')) then exit;
    ReadNextAtom;
  end;
  AtomIsIdentifierE;
  ReadNextAtom;
  if CurPos.Flag=cafEdgedBracketOpen then begin
    if EmptyIfIndexed then exit;
    ReadTilBracketClose(true);
    ReadNextAtom;
  end;
  if CurPos.Flag in [cafSemicolon,cafEND] then exit;
  if not (CurPos.Flag=cafColon) then
    RaiseExceptionFmt(ctsStrExpectedButAtomFound,[':',GetAtom]);
  ReadNextAtom;
  AtomIsIdentifierE;
  if InUpperCase then
    Result:=GetUpAtom
  else
    Result:=GetAtom;
end;

function TPascalReaderTool.ExtractProcName(ProcNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): string;
var
  ProcHeadNode: TCodeTreeNode;
  Part: String;
  HasClassName: Boolean;
begin
  Result:='';
  if [phpWithoutClassName,phpWithoutName]*Attr=
     [phpWithoutClassName,phpWithoutName]
  then
    exit;
  while (ProcNode<>nil) and (ProcNode.Desc<>ctnProcedure) do
    ProcNode:=ProcNode.Parent;
  if ProcNode=nil then exit;
  ProcHeadNode:=ProcNode.FirstChild;
  if (ProcHeadNode=nil) or (ProcHeadNode.StartPos<1) then exit;
  MoveCursorToNodeStart(ProcHeadNode);
  HasClassName:=false;
  repeat
    ReadNextAtom;
    if not AtomIsIdentifier then break;
    if phpInUpperCase in Attr then
      Part:=GetUpAtom
    else
      Part:=GetAtom;
    ReadNextAtom;
    if (CurPos.Flag<>cafPoint) then begin
      // end of method identifier is the proc name
      if phpWithoutName in Attr then break;
      if Result<>'' then Result:=Result+'.';
      Result:=Result+Part;
      break;
    end;
    if not (phpWithoutClassName in Attr) then begin
      // in front of . is class name
      if Result<>'' then Result:=Result+'.';
      Result:=Result+Part;
      HasClassName:=true;
    end;
  until false;
  if (not HasClassName)
  and ([phpWithoutClassName,phpAddClassName]*Attr=[phpAddClassName]) then begin
    Part:=ExtractClassName(ProcNode,false,true);
    if Part<>'' then
      Result:=Part+'.'+Result;
  end;
end;

function TPascalReaderTool.ExtractProcHead(ProcNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): string;
var
  TheClassName, s: string;
  IsClassName, IsProcType: boolean;
  IsProcedure: Boolean;
  IsFunction: Boolean;
  IsOperator: Boolean;
  EndPos: Integer;
const
  SemiColon : char = ';';
begin
  Result:='';
  ExtractProcHeadPos:=phepNone;
  if (ProcNode=nil) or (ProcNode.StartPos<1) then exit;
  if ProcNode.Desc=ctnProcedureHead then begin
    ProcNode:=ProcNode.Parent;
    if ProcNode=nil then exit;
  end;
  if (ProcNode.Desc<>ctnProcedure) and (ProcNode.Desc<>ctnProcedureType) then
    exit;
  IsProcType:=(ProcNode.Desc=ctnProcedureType);

  // build full class name
  TheClassName:='';
  if ([phpAddClassname,phpWithoutClassName]*Attr=[phpAddClassName]) then
    TheClassName:=ExtractClassName(ProcNode,phpInUpperCase in Attr,true);

  // reparse the clean source
  InitExtraction;
  MoveCursorToNodeStart(ProcNode);
  // parse procedure head = start + name + parameterlist + result type ;
  ExtractNextAtom(false,Attr);
  // read procedure start keyword
  if (UpAtomIs('CLASS') or UpAtomIs('STATIC')) then
    ExtractNextAtom((phpWithStart in Attr)
                    and not (phpWithoutClassKeyword in Attr),Attr);
  IsProcedure:=UpAtomIs('PROCEDURE');
  IsFunction:=(not IsProcedure) and UpAtomIs('FUNCTION');
  IsOperator:=(not IsProcedure) and (not IsFunction) and UpAtomIs('OPERATOR');
  if IsProcedure or IsFunction or IsOperator
  or (UpAtomIs('CONSTRUCTOR')) or (UpAtomIs('DESTRUCTOR'))
  then
    ExtractNextAtom(phpWithStart in Attr,Attr)
  else
    exit;
  ExtractProcHeadPos:=phepStart;
  if not IsProcType then begin
    // read name
    if ((not IsOperator)
    or (not WordIsCustomOperator.DoItCaseInsensitive(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)))
    and (not AtomIsIdentifier) then exit;

    if TheClassName<>'' then begin
      s:=TheClassName+'.';
      if phpInUpperCase in Attr then s:=UpperCaseStr(s);
      if ExtractStreamEndIsIdentChar then
        s:=' '+s;
      ExtractMemStream.Write(s[1],length(s));
    end;

    if [phpWithoutClassName,phpWithoutName]*Attr=[] then begin
      // read classname and name
      repeat
        ExtractNextAtom(true,Attr);
        if Scanner.CompilerMode = cmDELPHI then
        begin
          // delphi generics
          if AtomIsChar('<') then
          begin
            while not AtomIsChar('>') and (CurPos.EndPos < SrcLen) do
              ExtractNextAtom(not (phpWithoutGenericParams in Attr),Attr);
            ExtractNextAtom(not (phpWithoutGenericParams in Attr),Attr);
          end;
        end;
        if CurPos.Flag<>cafPoint then break;
        ExtractNextAtom(true,Attr);
        if ((not IsOperator)
        or (not WordIsCustomOperator.DoItCaseInsensitive(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)))
        and (not AtomIsIdentifier) then exit;
      until false;
    end else begin
      // read only part of name
      repeat
        ReadNextAtom;
        if (Scanner.CompilerMode = cmDELPHI) and AtomIsChar('<') then
        begin
          while not AtomIsChar('>') and (CurPos.EndPos < SrcLen) do
            ReadNextAtom;
          ReadNextAtom;
        end;
        IsClassName:=(CurPos.Flag=cafPoint);
        UndoReadNextAtom;
        if IsClassName then begin
          // read class name
          ExtractNextAtom(not (phpWithoutClassName in Attr),Attr);
          // read '.'
          ExtractNextAtom(not (phpWithoutClassName in Attr),Attr);
          if ((not IsOperator)
          or (not WordIsCustomOperator.DoItCaseInsensitive(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos)))
          and (not AtomIsIdentifier) then exit;
        end else begin
          // read name
          ExtractNextAtom(not (phpWithoutName in Attr),Attr);
          break;
        end;
      until false;
    end;
    ExtractProcHeadPos:=phepName;
  end;
  // read parameter list
  if (CurPos.Flag=cafRoundBracketOpen) then
    ReadParamList(false,true,Attr);
  ExtractProcHeadPos:=phepParamList;
  if IsOperator and (CurPos.Flag=cafWord) then begin
    // read operator result name
    ExtractNextAtom([phpWithParameterNames,phpWithResultType]*Attr
                   =[phpWithParameterNames,phpWithResultType],Attr);
  end;
  // read result type
  if (CurPos.Flag=cafColon) then begin
    ExtractNextAtom(phpWithResultType in Attr,Attr);
    if not AtomIsIdentifier then exit;
    ExtractNextAtom(phpWithResultType in Attr,Attr);
    if CurPos.Flag=cafPoint then begin
      ExtractNextAtom(phpWithResultType in Attr,Attr);
      if not AtomIsIdentifier then exit;
      ExtractNextAtom(phpWithResultType in Attr,Attr);
    end;
    ExtractProcHeadPos:=phepResultType;
  end;
  // read 'of object'
  if UpAtomIs('OF') then begin
    if IsProcType then begin
      ExtractNextAtom(phpWithOfObject in Attr,Attr);
      if not UpAtomIs('OBJECT') then exit;
      ExtractNextAtom(phpWithOfObject in Attr,Attr);
    end;
  end;
  // read semicolon
  if CurPos.Flag=cafSemicolon then
    ExtractNextAtom(not (phpWithoutSemicolon in Attr),Attr);
  // read specifiers
  if [phpWithCallingSpecs,phpWithProcModifiers]*Attr<>[] then begin
    if ProcNode.FirstChild<>nil then
      EndPos:=ProcNode.FirstChild.EndPos
    else
      EndPos:=SrcLen+1;
    while (CurPos.StartPos<EndPos) do begin
      if CurPos.Flag=cafSemicolon then begin
        ExtractNextAtom(phpWithProcModifiers in Attr,Attr);
      end else begin
        if IsKeyWordCallingConvention.DoIdentifier(@Src[CurPos.StartPos])
        then begin
          ExtractNextAtom([phpWithCallingSpecs,phpWithProcModifiers]*Attr<>[],
                          Attr);
          if not (phpWithProcModifiers in Attr) then
            ExtractMemStream.Write(SemiColon,1);
        end
        else if (CurPos.Flag=cafEdgedBracketOpen) then begin
          ReadTilBracketClose(false);
          ExtractNextAtom(phpWithProcModifiers in Attr,Attr);
        end else begin
          ExtractNextAtom(phpWithProcModifiers in Attr,Attr);
        end;
      end;
    end;
  end;

  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
  
  // add semicolon
  if ([phpWithoutSemicolon,phpDoNotAddSemicolon]*Attr=[])
  and (Result<>'') and (Result[length(Result)]<>';') then
    Result:=Result+';';
end;

function TPascalReaderTool.ExtractProcHeadWithGroup(ProcNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): TPascalMethodHeader;
begin
  Result.Name := ExtractProcHead(ProcNode, Attr);
  Result.Group := ExtractProcedureGroup(ProcNode);
end;

function TPascalReaderTool.ExtractProcedureHeader(CursorPos: TCodeXYPosition;
  Attributes: TProcHeadAttributes; var ProcHead: string): boolean;
var
  CleanCursorPos: integer;
  ANode: TCodeTreeNode;
begin
  Result:=false;
  ProcHead:='';
  BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
    [btSetIgnoreErrorPos,btCursorPosOutAllowed]);
  ANode:=FindDeepestNodeAtPos(CleanCursorPos,True);
  while (ANode<>nil) and (ANode.Desc<>ctnProcedure) do
    ANode:=ANode.Parent;
  if ANode=nil then exit;
  ProcHead:=ExtractProcHead(ANode,Attributes);
  Result:=true;
end;

function TPascalReaderTool.ExtractClassName(Node: TCodeTreeNode;
  InUpperCase: boolean; WithParents: boolean; WithGenericParams: boolean
  ): string;
var
  ParamsNode: TCodeTreeNode;
  ParamNode: TCodeTreeNode;
  First: Boolean;
begin
  Result:='';
  while Node<>nil do begin
    case Node.Desc of
    ctnTypeDefinition:
      begin
        if Result<>'' then Result:='.'+Result;
        Result:=GetIdentifier(@Src[Node.StartPos])+Result;
        if not WithParents then break;
      end;
    ctnGenericType:
      begin
        if Result<>'' then Result:='.'+Result;
        if (Node.Desc = ctnGenericType) then begin
          // extract generic type param names
          if WithGenericParams then begin
            ParamsNode:=Node.FirstChild.NextBrother;
            First:=true;
            while ParamsNode<>nil do begin
              if ParamsNode.Desc=ctnGenericParams then begin
                Result:='>'+Result;
                ParamNode:=ParamsNode.FirstChild;
                while ParamNode<>nil do begin
                  if ParamNode.Desc=ctnGenericParameter then begin
                    if First then
                      First:=false
                    else
                      Result:=','+Result;
                    Result:=GetIdentifier(@Src[ParamNode.StartPos])+Result;
                  end;
                  ParamNode:=ParamNode.NextBrother;
                end;
                Result:='<'+Result;
              end;
              ParamsNode:=ParamsNode.NextBrother;
            end;
          end;
          Result:=GetIdentifier(@Src[Node.FirstChild.StartPos])+Result;
        end;
        if not WithParents then break;
      end;
    ctnParameterList:
      break;
    end;
    Node:=Node.Parent;
  end;

  if InUpperCase then
    Result:=UpperCaseStr(Result);
end;

function TPascalReaderTool.ExtractClassPath(Node: TCodeTreeNode): string;
var
  InArray: Boolean;
begin
  Result:='';
  InArray:=false;
  while Node<>nil do begin
    case Node.Desc of
    ctnTypeDefinition,ctnGenericType:
      begin
        if Result<>'' then Result:='.'+Result;
        if Node.Desc=ctnTypeDefinition then
          Result:=GetIdentifier(@Src[Node.StartPos])+Result
        else if Node.FirstChild<>nil then
        begin
          if (Scanner.CompilerMode = cmDELPHI) and (Node.Desc = ctnGenericType) then
            Result := Result + ExtractNode(Node.FirstChild.NextBrother, []);
          Result:=GetIdentifier(@Src[Node.FirstChild.StartPos])+Result;
        end;
      end;
    ctnParameterList:
      break;
    ctnRangedArrayType, ctnOpenArrayType:
       begin
         InArray := True;
         Result := '[]' + Result;
       end;
    ctnVarDefinition:
       if InArray then begin
         Result := GetIdentifier(@Src[Node.StartPos]) + Result;
         InArray := False;
       end;
    end;
    Node:=Node.Parent;
  end;
end;

function TPascalReaderTool.ExtractClassInheritance(
  ClassNode: TCodeTreeNode; Attr: TProcHeadAttributes): string;
begin
  Result:='';
  if (ClassNode=nil) or (not (ClassNode.Desc in AllClasses)) then exit;
  MoveCursorToNodeStart(ClassNode);
  ReadNextAtom; // class
  if UpAtomIs('PACKED') then ReadNextAtom;
  if not (UpAtomIs('CLASS') or UpAtomIs('OBJECT') or UpAtomIs('OBJCLASS')
       or (UpAtomIs('INTERFACE')))
  then
    exit;
  ReadNextAtom; // '('
  if CurPos.Flag<>cafRoundBracketOpen then exit;
  ReadNextAtom;
  if not AtomIsIdentifier then exit;
  MoveCursorToCleanPos(CurPos.StartPos);
  ExtractProcHeadPos:=phepNone;
  InitExtraction;
  while (CurPos.StartPos<=SrcLen) do begin
    ExtractNextAtom(true,Attr); // read ancestor/interface
    if not AtomIsIdentifier then break;
    ExtractNextAtom(true,Attr); // read ','
    if not AtomIsChar(',') then break;
  end;
  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
end;

function TPascalReaderTool.ExtractClassNameOfProcNode(ProcNode: TCodeTreeNode;
  AddParentClasses: boolean): string;
var
  Part: String;
begin
  Result:='';
  if (ProcNode<>nil) and (ProcNode.Desc=ctnProcedure) then
    ProcNode:=ProcNode.FirstChild;
  if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedureHead) then exit;
  MoveCursorToNodeStart(ProcNode);
  repeat
    ReadNextAtom;
    if not AtomIsIdentifier then break;
    Part:=GetAtom;
    ReadNextAtom;
    if (Scanner.CompilerMode = cmDELPHI) and AtomIsChar('<') then
    begin { delphi generics }
      Part := Part + GetAtom;
      repeat
        ReadNextAtom;
        Part := Part + GetAtom;
      until (CurPos.StartPos > SrcLen) or AtomIsChar('>');
      ReadNextAtom;
    end;
    if (CurPos.Flag<>cafPoint) then break;
    if Result<>'' then Result:=Result+'.';
    Result:=Result+Part;
  until false;
  if not AddParentClasses then exit;
  Part:=ExtractClassName(ProcNode,false,true);
  if Part='' then exit;
  Result:=Part+'.'+Result;
end;

function TPascalReaderTool.FindProcNode(StartNode: TCodeTreeNode;
  const AProcHead: TPascalMethodHeader; Attr: TProcHeadAttributes;
  Visibility: TClassSectionVisibility): TCodeTreeNode;
// search in all next brothers for a Procedure Node with the Name ProcName
// if there are no further brothers and the parent is a section node
// ( e.g. 'interface', 'implementation', ...) or a class visibility node
// (e.g. 'public', 'private', ...) then the search will continue in the next
// section
var
  InClass: Boolean;
  CurProcHead: TPascalMethodHeader;
begin
  Result:=StartNode;
  InClass:=FindClassOrInterfaceNode(StartNode)<>nil;
  while (Result<>nil) do begin
    if Result.Desc=ctnProcedure then begin
      if (not ((phpIgnoreForwards in Attr)
               and ((Result.SubDesc and ctnsForwardDeclaration)>0)))
      and (not ((phpIgnoreProcsWithBody in Attr)
            and (FindProcBody(Result)<>nil)))
      and (not InClass or IdentNodeIsInVisibleClassSection(Result, Visibility))
      then
      begin
        CurProcHead:=ExtractProcHeadWithGroup(Result,Attr);
        //DebugLn(['TPascalReaderTool.FindProcNode B "',CurProcHead,'" =? "',AProcHead,'" Result=',CompareTextIgnoringSpace(CurProcHead,AProcHead,false)]);
        if (CurProcHead.Name<>'') and
            SameMethodHeaders(AProcHead, CurProcHead)
        then
          exit;
      end;
    end;
    // next node
    if InClass then
      Result:=FindNextIdentNodeInClass(Result)
    else
      Result:=FindNextNodeOnSameLvl(Result);
  end;
end;

function TPascalReaderTool.FindProcNode(StartNode: TCodeTreeNode;
  const AProcHead: string; AProcSpecType: TPascalMethodGroup;
  Attr: TProcHeadAttributes; Visibility: TClassSectionVisibility): TCodeTreeNode;
var
  ProcHead: TPascalMethodHeader;
begin
  ProcHead.Name := AProcHead;
  ProcHead.Group := AProcSpecType;
  Result := FindProcNode(StartNode, ProcHead, Attr, Visibility);
end;

function TPascalReaderTool.FindCorrespondingProcNode(ProcNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): TCodeTreeNode;
var
  ClassNode: TCodeTreeNode;
  StartNode: TCodeTreeNode;
  ProcHead: TPascalMethodHeader;
begin
  Result:=nil;
  // get ctnProcedure
  //debugln('TPascalReaderTool.FindCorrespondingProcNode Start');
  if (ProcNode=nil) then exit;
  if ProcNode.Desc=ctnProcedureHead then begin
    ProcNode:=ProcNode.Parent;
    if (ProcNode=nil) then exit;
  end;
  if ProcNode.Desc<>ctnProcedure then exit;
  
  // check proc kind
  //debugln('TPascalReaderTool.FindCorrespondingProcNode Check kind');
  ClassNode:=FindClassOrInterfaceNode(ProcNode);
  if ClassNode<>nil then begin
    //debugln('TPascalReaderTool.FindCorrespondingProcNode Class');
    // in a class definition -> search method body
    StartNode:=ClassNode.GetTopMostNodeOfType(ctnTypeSection);
  end else if NodeIsMethodBody(ProcNode) then begin
    //debugln('TPascalReaderTool.FindCorrespondingProcNode Method ',ExtractClassNameOfProcNode(ProcNode));
    // in a method body -> search in class
    StartNode:=FindClassNodeInUnit(ExtractClassNameOfProcNode(ProcNode,true),
             true,false,false,true);
    if StartNode=nil then exit;
    if (StartNode<>nil) and (StartNode.Desc in AllClasses)
    then begin
      StartNode:=StartNode.FirstChild;
      while (StartNode<>nil) do begin
        if (StartNode.Desc in AllClassBaseSections)
        and (StartNode.FirstChild<>nil) then begin
          StartNode:=StartNode.FirstChild;
          break;
        end;
        StartNode:=StartNode.NextBrother;
      end;
    end;
  end else begin
    //DebugLn('TPascalReaderTool.FindCorrespondingProcNode Normal');
    // else: search on same lvl
    StartNode:=FindFirstNodeOnSameLvl(ProcNode);
  end;
  if StartNode=nil then exit;

  ProcHead:=ExtractProcHeadWithGroup(ProcNode,Attr);
  //debugln('TPascalReaderTool.FindCorrespondingProcNode StartNode=',StartNode.DescAsString,' ProcHead=',dbgstr(ProcHead),' ',dbgs(Attr),' ',StartNode.DescAsString);
  Result:=FindProcNode(StartNode,ProcHead,Attr);
  if Result=ProcNode then begin
    // found itself -> search further
    StartNode:=FindNextNodeOnSameLvl(Result);
    Result:=FindProcNode(StartNode,ProcHead,Attr);
  end;
  //if Result<>nil then debugln(['TPascalReaderTool.FindCorrespondingProcNode Result=',CleanPosToStr(Result.StartPos),' ',dbgstr(copy(Src,Result.StartPos,50))]);
end;

function TPascalReaderTool.FindCorrespondingProcParamNode(
  ProcParamNode: TCodeTreeNode; Attr: TProcHeadAttributes): TCodeTreeNode;
var
  ProcNode: TCodeTreeNode;
begin
  Result:=nil;
  if ProcParamNode=nil then exit;
  if (ProcParamNode.Desc=ctnVarDefinition)
  and (ProcParamNode.Parent.Desc=ctnParameterList)
  and (ProcParamNode.Parent.Parent.Desc=ctnProcedureHead) then begin
    // this is a parameter name
    ProcNode:=ProcParamNode.GetNodeOfType(ctnProcedure);
    if ProcNode=nil then exit;
    // search alias for parameter
    ProcNode:=FindCorrespondingProcNode(ProcNode,Attr);
    if ProcNode=nil then exit;
    BuildSubTreeForProcHead(ProcNode);
    Result:=ProcNode;
    while (Result<>nil) do begin
      //debugln(['TPascalReaderTool.FindCorrespondingProcParamNode ',dbgstr(copy(Src,Result.StartPos,20))]);
      if Result.Desc
        in [ctnProcedure,ctnProcedureHead,ctnParameterList]
      then
        Result:=Result.FirstChild
      else begin
        if Result.StartPos<1 then break;
        if CompareIdentifiers(@Src[ProcParamNode.StartPos],@Src[Result.StartPos])=0
        then exit;
        Result:=Result.NextBrother;
      end;
    end;
    Result:=nil;
  end;
end;

function TPascalReaderTool.FindProcBody(ProcNode: TCodeTreeNode): TCodeTreeNode;
begin
  Result:=ProcNode;
  if Result=nil then exit;
  if Result.Desc<>ctnProcedure then exit;
  Result:=Result.LastChild;
  while Result<>nil do begin
    if Result.Desc in [ctnBeginBlock,ctnAsmBlock] then
      exit;
    Result:=Result.PriorBrother;
  end;
end;

function TPascalReaderTool.ProcBodyIsEmpty(ProcNode: TCodeTreeNode): boolean;
var
  BodyNode: TCodeTreeNode;
  LastPos: LongInt;
begin
  Result:=false;
  BodyNode:=FindProcBody(ProcNode);
  if (BodyNode=nil) then exit;
  // check if there are nodes in front (e.g. local variables)
  if (BodyNode.PriorBrother<>nil)
  and (BodyNode.PriorBrother.Desc<>ctnProcedureHead) then
    exit;
  // check if there are child nodes
  if BodyNode.FirstChild<>nil then exit;
  // check if bodynode is only 'asm end' or 'begin end'
  // not even a comment should be there, only spaces are allowed
  if ProcNode.FirstChild.Desc<>ctnProcedureHead then exit;
  MoveCursorToCleanPos(ProcNode.FirstChild.EndPos);
  LastPos:=CurPos.EndPos;
  ReadNextAtom;
  if FindNextNonSpace(Src,LastPos)<>CurPos.StartPos then exit;
  if CurPos.Flag=cafSemicolon then begin
    // semicolon is allowed
    LastPos:=CurPos.EndPos;
    ReadNextAtom;
    if FindNextNonSpace(Src,LastPos)<>CurPos.StartPos then exit;
  end;
  if not (UpAtomIs('ASM') or UpAtomIs('BEGIN')) then exit;
  LastPos:=CurPos.EndPos;
  ReadNextAtom;
  if FindNextNonSpace(Src,LastPos)<>CurPos.StartPos then exit;
  // inherited is allowed
  if UpAtomIs('INHERITED') then begin
    ReadNextAtom;
    if CurPos.Flag=cafSemicolon then begin
      // semicolon is allowed
      LastPos:=CurPos.EndPos;
      ReadNextAtom;
      if FindNextNonSpace(Src,LastPos)<>CurPos.StartPos then exit;
    end;
  end;
  if not UpAtomIs('END') then exit;
  Result:=true;
end;

procedure TPascalReaderTool.MoveCursorToFirstProcSpecifier(ProcNode: TCodeTreeNode);
// After the call,
// CurPos will stand on the first proc specifier or on a semicolon
// this can be 'of object'
begin
  //DebugLn(['TPascalReaderTool.MoveCursorToFirstProcSpecifier ',ProcNode.DescAsString,' ',ProcNode.StartPos]);
  if (ProcNode<>nil) and (ProcNode.Desc in [ctnProcedureType,ctnProcedure]) then
    ProcNode:=ProcNode.FirstChild;
  if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedureHead) then begin
    RaiseException('Internal Error in'
      +' TPascalParserTool.MoveCursorFirstProcSpecifier: '
      +' (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure)');
  end;
  if (ProcNode.LastChild<>nil) and (ProcNode.LastChild.Desc=ctnIdentifier) then
  begin
    // jump behind function result type
    MoveCursorToCleanPos(ProcNode.LastChild.EndPos);
    ReadNextAtom;
  end else if (ProcNode.FirstChild<>nil)
    and (ProcNode.FirstChild.Desc=ctnParameterList)
  then begin
    // jump behind parameter list
    MoveCursorToCleanPos(ProcNode.FirstChild.EndPos);
    ReadNextAtom;
  end else begin
    MoveCursorToNodeStart(ProcNode);
    ReadNextAtom;
    if AtomIsCustomOperator(true,false,false) then begin
      // read name
      ReadNextAtom;
      while (CurPos.Flag=cafPoint) do begin
        ReadNextAtom;
        if CurPos.Flag in [cafPoint,cafRoundBracketOpen,cafEdgedBracketOpen,cafColon,cafEnd,cafSemicolon]
        then break;
        ReadNextAtom;
      end;
    end;
    if (CurPos.Flag=cafRoundBracketOpen) then begin
      // read paramlist
      ReadTilBracketClose(false);
      ReadNextAtom;
    end;
  end;
  if (CurPos.Flag=cafColon) then begin
    // read function result type
    ReadNextAtom;
    if AtomIsIdentifier then begin
      ReadNextAtom;
      while CurPos.Flag=cafPoint do begin
        ReadNextAtom;
        if not AtomIsIdentifier then break;
        ReadNextAtom;
      end;
    end;
  end;
  // CurPos now stands on the first proc specifier or on a semicolon or on the syntax error
end;

function TPascalReaderTool.MoveCursorToProcSpecifier(ProcNode: TCodeTreeNode;
  ProcSpec: TProcedureSpecifier): boolean;
begin
  if ProcNode.FirstChild=nil then begin
    exit(false);
  end;
  MoveCursorToFirstProcSpecifier(ProcNode);
  while (CurPos.StartPos<=ProcNode.FirstChild.EndPos) do begin
    if CurPos.Flag=cafSemicolon then begin
      ReadNextAtom;
    end else begin
      if UpAtomIs(ProcedureSpecifierNames[ProcSpec]) then begin
        Result:=true;
        exit;
      end;
      if (CurPos.Flag=cafEdgedBracketOpen) then begin
        ReadTilBracketClose(false);
        ReadNextAtom;
      end else if UpAtomIs('MESSAGE') then begin
        ReadNextAtom;
        ReadConstant(true,false,[]);
      end else if UpAtomIs('EXTERNAL') then begin
        ReadNextAtom;
        if CurPos.Flag<>cafSemicolon then begin
          if not UpAtomIs('NAME') then
            ReadConstant(true,false,[]);
          if UpAtomIs('NAME') or UpAtomIs('INDEX') then begin
            ReadNextAtom;
            ReadConstant(true,false,[]);
          end;
        end;
      end else begin
        ReadNextAtom;
      end;
    end;
  end;
  Result:=false;
end;

procedure TPascalReaderTool.MoveCursorToProcName(ProcNode: TCodeTreeNode;
  SkipClassName: boolean);
begin
  if (ProcNode.Desc=ctnProcedure) and (ProcNode.FirstChild<>nil)
  and (ProcNode.FirstChild.Desc=ctnProcedureHead) then
    ProcNode:=ProcNode.FirstChild;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if (ProcNode.Desc=ctnProcedure) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    ReadNextAtom; // skip proc keyword
  end;
  if not SkipClassName then exit;
  repeat
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then begin
      UndoReadNextAtom;
      break;
    end;
    ReadNextAtom;
  until not AtomIsIdentifier;
end;

procedure TPascalReaderTool.MoveCursorBehindProcName(ProcNode: TCodeTreeNode);
begin
  if (ProcNode.FirstChild<>nil)
  and (ProcNode.FirstChild.Desc=ctnProcedureHead) then
    ProcNode:=ProcNode.FirstChild;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if AtomIsIdentifier then begin
    ReadNextAtom;
    while CurPos.Flag=cafPoint do begin
      ReadNextAtom;
      if not AtomIsIdentifier then exit;
      ReadNextAtom;
    end;
  end else if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen,cafColon]
  then begin
  end else begin
    // operator
    ReadNextAtom;
  end;
end;

function TPascalReaderTool.PositionInProcName(ProcNode: TCodeTreeNode;
  SkipClassName: boolean; CleanPos: integer): boolean;
begin
  if (ProcNode.Desc=ctnProcedure) and (ProcNode.FirstChild<>nil)
  and (ProcNode.FirstChild.Desc=ctnProcedureHead) then
    ProcNode:=ProcNode.FirstChild;
  if (CleanPos<ProcNode.StartPos) or (CleanPos>ProcNode.EndPos) then exit(false);
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if (ProcNode.Desc=ctnProcedure) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    ReadNextAtom; // skip proc keyword
  end;
  if CleanPos<CurPos.StartPos then exit(false);
  while CurPos.Flag=cafWord do begin
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then begin
      UndoReadNextAtom;
      break;
    end;
    ReadNextAtom;
  end;
  // CurPos is now on the proc name
  if CleanPos>CurPos.EndPos then exit(false);
  if SkipClassName and (CleanPos<CurPos.StartPos) then exit(false);
  Result:=true;
end;

function TPascalReaderTool.PositionInFuncResultName(ProcNode: TCodeTreeNode;
  CleanPos: integer): boolean;
// true if position between ) and :
var
  Node: TCodeTreeNode;
begin
  Result:=false;
  if ProcNode=nil then exit;
  if ProcNode.Desc=ctnProcedure then begin
    ProcNode:=ProcNode.FirstChild;
    if ProcNode=nil then exit;
  end;
  if (ProcNode.Desc in [ctnIdentifier,ctnVarDefinition])
  and (ProcNode.Parent<>nil)
  and (ProcNode.Parent.Desc=ctnProcedureHead)
  and (CleanPos>=ProcNode.StartPos) and (CleanPos<=ProcNode.EndPos) then begin
    exit(true);
  end;
  if ProcNode.Desc=ctnProcedureHead then begin
    Node:=ProcNode.FirstChild;
    while (Node<>nil) and (Node.Desc<>ctnIdentifier) do begin
      if (Node.Desc=ctnIdentifier)
      and (CleanPos>=Node.StartPos) and (CleanPos<=Node.EndPos) then
        exit(true);
      Node:=Node.NextBrother;
    end;
  end;
  // read behind parameter list
  if ProcNode.Desc<>ctnProcedureHead then exit;
  if (ProcNode.FirstChild<>nil) and (ProcNode.FirstChild.Desc=ctnParameterList)
  then begin
    if (CleanPos<ProcNode.FirstChild.EndPos) then
      exit;
    MoveCursorToCleanPos(ProcNode.FirstChild.EndPos);
  end else begin
    MoveCursorToNodeStart(ProcNode);
    ReadNextAtom;
    while AtomIsIdentifier do begin
      ReadNextAtom;
      if (CurPos.Flag<>cafPoint) then break;
      ReadNextAtom;
    end;
    if CurPos.Flag=cafRoundBracketOpen then
      if not ReadTilBracketClose(false) then exit;
  end;
  if CurPos.StartPos>CleanPos then exit;
  // read optional result variable (e.g. operator can have them)
  ReadNextAtom;
  if AtomIsIdentifier then ReadNextAtom;
  if CurPos.Flag<>cafColon then exit;
  Result:=CleanPos<=CurPos.StartPos;
end;

function TPascalReaderTool.MoveCursorToPropType(PropNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (PropNode=nil)
  or ((PropNode.Desc<>ctnProperty) and (PropNode.Desc<>ctnGlobalProperty)) then
    exit;
  MoveCursorToNodeStart(PropNode);
  ReadNextAtom;
  if (PropNode.Desc=ctnProperty) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    if (not UpAtomIs('PROPERTY')) then exit;
    ReadNextAtom;
  end;
  if not AtomIsIdentifier then exit;
  ReadNextAtom;
  if CurPos.Flag=cafEdgedBracketOpen then begin
    ReadTilBracketClose(true);
    ReadNextAtom;
  end;
  if CurPos.Flag in [cafSemicolon,cafEND] then exit;
  if CurPos.Flag<>cafColon then exit;
  ReadNextAtom;
  Result:=CurPos.Flag=cafWord;
end;

function TPascalReaderTool.MoveCursorToPropName(PropNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (PropNode=nil)
  or ((PropNode.Desc<>ctnProperty) and (PropNode.Desc<>ctnGlobalProperty)) then
    exit;
  MoveCursorToNodeStart(PropNode);
  ReadNextAtom;
  if (PropNode.Desc=ctnProperty) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    if (not UpAtomIs('PROPERTY')) then exit;
    ReadNextAtom;
  end;
  Result:=CurPos.Flag=cafWord;
end;

procedure TPascalReaderTool.MoveCursorBehindPropName(PropNode: TCodeTreeNode);
begin
  if (PropNode=nil)
  or ((PropNode.Desc<>ctnProperty) and (PropNode.Desc<>ctnGlobalProperty)) then
    exit;
  MoveCursorToNodeStart(PropNode);
  ReadNextAtom;
  if (PropNode.Desc=ctnProperty) then begin
    if UpAtomIs('CLASS') then ReadNextAtom;
    if (not UpAtomIs('PROPERTY')) then exit;
    ReadNextAtom;
  end;
  if not AtomIsIdentifier then exit;
  ReadNextAtom;
end;

function TPascalReaderTool.ProcNodeHasSpecifier(ProcNode: TCodeTreeNode;
  ProcSpec: TProcedureSpecifier): boolean;
begin
  Result:=false;
  if ProcNode=nil then exit;
  if ProcNode.Desc=ctnProcedureHead then
    ProcNode:=ProcNode.Parent;
  {$IFDEF CheckNodeTool}
  if ProcNode.Desc<>ctnProcedure then begin
    DebugLn(['TPascalReaderTool.ProcNodeHasSpecifier Desc=',ProcNode.DescAsString]);
    CTDumpStack;
    RaiseException('[TPascalReaderTool.ProcNodeHasSpecifier] '
      +'internal error: invalid ProcNode');
  end;
  {$ENDIF}
  if (ProcNode.FirstChild=nil)
  or ((ProcNode.SubDesc and ctnsNeedJITParsing)>0) then
    BuildSubTreeForProcHead(ProcNode);

  // ToDo: ppu, dcu

  Result:=MoveCursorToProcSpecifier(ProcNode,ProcSpec);
end;

function TPascalReaderTool.GetProcNameIdentifier(ProcNode: TCodeTreeNode): PChar;
begin

  // ToDo: ppu, dcu

  Result:=nil;
  if ProcNode=nil then exit;
  if ProcNode.Desc=ctnProcedure then begin
    ProcNode:=ProcNode.FirstChild;
    if ProcNode=nil then exit;
  end;
  MoveCursorToNodeStart(ProcNode);
  repeat
    ReadNextAtom;
    if not AtomIsIdentifier then exit(nil);
    Result:=@Src[CurPos.StartPos];
    ReadNextAtom;
  until CurPos.Flag<>cafPoint;
end;

function TPascalReaderTool.ExtractNode(ANode: TCodeTreeNode;
  Attr: TProcHeadAttributes): string;
begin
  Result:='';
  ExtractProcHeadPos:=phepNone;
  if (ANode=nil) or (ANode.StartPos<1) then exit;
  InitExtraction;
  // reparse the clean source
  MoveCursorToNodeStart(ANode);
  while (ANode.EndPos>CurPos.StartPos)
  and (CurPos.StartPos<=SrcLen) do
    ExtractNextAtom(true,Attr);
  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
end;

function TPascalReaderTool.ExtractCode(StartPos, EndPos: integer;
  Attr: TProcHeadAttributes): string;
begin
  Result:='';
  ExtractProcHeadPos:=phepNone;
  if (StartPos<1) or (StartPos>=EndPos) or (StartPos>SrcLen) then exit;
  InitExtraction;
  // reparse the clean source
  MoveCursorToCleanPos(StartPos);
  while (EndPos>CurPos.StartPos)
  and (CurPos.StartPos<=SrcLen) do
    ExtractNextAtom(true,Attr);
  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
end;

function TPascalReaderTool.ExtractBrackets(BracketStartPos: integer;
  Attr: TProcHeadAttributes): string;

  function ExtractTilBracketClose(ExtractBrackets: boolean): boolean;
  var
    CloseBracket: TCommonAtomFlag;
    First: Boolean;
  begin
    Result:=true;
    case CurPos.Flag of
    cafRoundBracketOpen: CloseBracket:=cafRoundBracketClose;
    cafEdgedBracketOpen: CloseBracket:=cafEdgedBracketClose;
    else exit;
    end;
    First:=true;
    repeat
      if First then
        ExtractNextAtom(ExtractBrackets,Attr)
      else
        ExtractNextAtom(true,Attr);
      if CurPos.StartPos>SrcLen then exit;
      if CurPos.Flag=CloseBracket then exit(true);
      if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
        if not ExtractTilBracketClose(true) then exit;
      end;
    until false;
  end;

begin
  Result:='';
  ExtractProcHeadPos:=phepNone;
  if (BracketStartPos<1) or (BracketStartPos>SrcLen) then exit;
  InitExtraction;
  // reparse the clean source
  MoveCursorToCleanPos(BracketStartPos);
  ReadNextAtom;
  if not ExtractTilBracketClose(not (phpWithoutBrackets in Attr)) then exit;
  if not (phpWithoutBrackets in Attr) then
    ExtractNextAtom(true,Attr);
  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
end;

function TPascalReaderTool.ExtractIdentifierWithPoints(StartPos: integer;
  ExceptionOnError: boolean): string;
begin
  Result:='';
  MoveCursorToCleanPos(StartPos);
  ReadNextAtom;
  if not AtomIsIdentifierE(ExceptionOnError) then exit;
  Result:=GetAtom;
  repeat
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then
      exit;
    ReadNextAtom;
    if not AtomIsIdentifierE(ExceptionOnError) then exit;
    Result+='.'+GetAtom;
  until false;
end;

function TPascalReaderTool.ExtractPropName(PropNode: TCodeTreeNode;
  InUpperCase: boolean): string;
begin
  Result:='';
  if not MoveCursorToPropName(PropNode) then exit;
  if InUpperCase then
    Result:=GetUpAtom
  else
    Result:=GetAtom;
end;

function TPascalReaderTool.ExtractProperty(PropNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): string;
begin
  Result:='';
  ExtractProcHeadPos:=phepNone;
  if (PropNode=nil) or (PropNode.StartPos<1)
  or ((PropNode.Desc<>ctnProperty) and (PropNode.Desc<>ctnGlobalProperty)) then
    exit;
  // start extraction
  InitExtraction;
  MoveCursorToNodeStart(PropNode);
  ExtractNextAtom(false,Attr);
  if (PropNode.Desc=ctnProperty) then begin
    if UpAtomIs('CLASS') then
      ExtractNextAtom(phpWithStart in Attr,Attr);
    // parse 'property'
    ExtractNextAtom(phpWithStart in Attr,Attr);
  end;
  ExtractProcHeadPos:=phepStart;
  // parse name
  ExtractNextAtom(not (phpWithoutName in Attr),Attr);
  ExtractProcHeadPos:=phepName;
  // read parameter list
  if (CurPos.Flag=cafEdgedBracketOpen) then
    ReadParamList(false,true,Attr);
  ExtractProcHeadPos:=phepParamList;
  // read result type
  if (CurPos.Flag=cafColon) then begin
    ExtractNextAtom(phpWithResultType in Attr,Attr);
    if not AtomIsIdentifier then exit;
    ExtractNextAtom(phpWithResultType in Attr,Attr);
    if CurPos.Flag=cafPoint then begin
      // unit.type
      ExtractNextAtom(phpWithResultType in Attr,Attr);
      if not AtomIsIdentifier then exit;
      ExtractNextAtom(phpWithResultType in Attr,Attr);
    end;
    ExtractProcHeadPos:=phepResultType;
  end;

  // copy memorystream to Result string
  Result:=GetExtraction(phpInUpperCase in Attr);
end;

function TPascalReaderTool.GetPropertyNameIdentifier(PropNode: TCodeTreeNode): PChar;
begin
  // ToDo: ppu, dcu

  Result:=nil;
  if PropNode=nil then exit;
  if not MoveCursorToPropName(PropNode) then exit;
  Result:=@Src[CurPos.StartPos];
end;

function TPascalReaderTool.GetPropertyTypeIdentifier(PropNode: TCodeTreeNode): PChar;
begin

  // ToDo: ppu, dcu

  Result:=nil;
  if PropNode=nil then exit;
  if not MoveCursorToPropType(PropNode) then exit;
  Result:=@Src[CurPos.StartPos];
end;

function TPascalReaderTool.PositionInPropertyName(PropNode: TCodeTreeNode;
  CleanPos: integer): boolean;
begin
  if PropNode=nil then exit(false);
  MoveCursorToNodeStart(PropNode);
  if (PropNode.Desc=ctnProperty) then begin
    ReadNextAtom; // read 'property'
    if UpAtomIs('CLASS') then ReadNextAtom;
  end;
  ReadNextAtom; // read name
  Result:=(CurPos.Flag=cafWord)
          and (CleanPos>=CurPos.StartPos) and (CleanPos<=CurPos.EndPos);
end;

function TPascalReaderTool.ExtractIdentCharsFromStringConstant(StartPos,
  MinPos, MaxPos, MaxLen: integer): string;
var
  APos: Integer;
  IdentStartPos: Integer;
  IdentStr: String;
  IdentEndPos: LongInt;
begin
  Result:='';
  APos:=StartPos;
  while APos<SrcLen do begin
    if (Src[APos]='#') then begin
      // skip char constant
      inc(APos);
      if IsNumberChar[Src[APos]] then begin
        while (APos<CurPos.EndPos) and IsNumberChar[Src[APos]] do
          inc(APos)
      end else if Src[APos]='$' then begin
        while (APos<CurPos.EndPos) and IsHexNumberChar[Src[APos]] do
          inc(APos);
      end;
    end else if (Src[APos]='''') then begin
      inc(APos);
      repeat
        // read identifier chars
        IdentStartPos:=APos;
        while (APos<SrcLen) and (IsIdentChar[Src[APos]]) do
          inc(APos);
        IdentEndPos:=APos;
        if IdentStartPos<MinPos then IdentStartPos:=MinPos;
        if IdentEndPos>MaxPos then IdentEndPos:=MaxPos;
        if (IdentEndPos>IdentStartPos) then begin
          if IdentEndPos-IdentStartPos+length(Result)>MaxLen then
            IdentEndPos:=IdentStartPos+MaxLen-length(Result);
          IdentStr:=copy(Src,IdentStartPos,IdentEndPos-IdentStartPos);
          if (IdentStr<>'') then begin
            IdentStr[1]:=UpChars[IdentStr[1]];
            Result:=Result+IdentStr;
          end;
        end;
        // skip non identifier chars
        while (APos<SrcLen) and (Src[APos]<>'''')
        and (not IsIdentChar[Src[APos]])
        do
          inc(APos);
      until (APos>=SrcLen) or (Src[APos]='''') or (length(Result)>=MaxLen);
      inc(APos);
    end else
      break;
  end;
end;

function TPascalReaderTool.ReadStringConstantValue(StartPos: integer): string;
// reads a string constant and returns the resulting string
var
  Run: Integer;
  NumberStart: PChar;
  ResultLen: Integer;
  Number: Integer;
  p: PChar;
begin
  Result:='';
  if StartPos>SrcLen then exit;
  // first read and calculate the resulting length, then copy the chars
  for Run:=1 to 2 do begin
    ResultLen:=0;
    p:=@Src[StartPos];
    while true do begin
      case p^ of
      '''':
        begin
          // read string
          inc(p);
          while true do begin
            if p^='''' then begin
              if p[1]='''' then begin
                // a double ' means a single '
                inc(ResultLen);
                if Run=2 then Result[ResultLen]:='''';
                inc(p,2);
              end else begin
                // a single ' means end of string constant
                inc(p);
                break;
              end;
            end else begin
              // normal char
              inc(ResultLen);
              if Run=2 then Result[ResultLen]:=p^;
              inc(p);
            end;
          end;
        end;
      '#':
        begin
          // read char constant
          inc(p);
          NumberStart:=p;
          if IsNumberChar[p^] then begin
            // read decimal number
            while IsNumberChar[p^] do
              inc(p);
            Number:=StrToIntDef(copy(Src,NumberStart-PChar(Src)+1,p-NumberStart),-1);
          end else if p^='$' then begin
            // read hexnumber
            inc(p);
            while IsHexNumberChar[p^] do
              inc(p);
            Number:=HexStrToIntDef(NumberStart,-1);
          end else
            Number:=-1;
          // add special character
          if (Number<0) or (Number>255) then break;
          inc(ResultLen);
          if Run=2 then Result[ResultLen]:=chr(Number);
        end;
      '^':
        begin
          inc(p);
          if p^ in ['A'..'Z'] then begin
            inc(ResultLen);
            if Run=2 then Result[ResultLen]:=chr(ord(p^)-ord('A'));
          end else begin
            break;
          end;
        end;
      else
        break;
      end;
    end;
    if Run=1 then SetLength(Result,ResultLen);
  end;
end;

function TPascalReaderTool.GetNodeIdentifier(Node: TCodeTreeNode): PChar;
begin
  Result:=nil;
  if (Node=nil) or (Node.StartPos>SrcLen) then exit;
  case Node.Desc of
  ctnProcedure,ctnProcedureHead:
    Result:=GetProcNameIdentifier(Node);
  ctnProperty:
     Result:=GetPropertyNameIdentifier(Node);
  ctnTypeDefinition,ctnVarDefinition,ctnConstDefinition,
  ctnEnumIdentifier,ctnIdentifier:
    Result:=@Src[Node.StartPos];
  end;
end;

function TPascalReaderTool.GetHintModifiers(Node: TCodeTreeNode): TPascalHintModifiers;

  function IsHintModifier: boolean;
  begin
    if CurPos.Flag<>cafWord then exit(false);
    Result:=true;
    if UpAtomIs('PLATFORM') then
      Include(GetHintModifiers,phmPlatform)
    else if UpAtomIs('UNIMPLEMENTED') then
      Include(GetHintModifiers,phmUnimplemented)
    else if UpAtomIs('LIBRARY') then
      Include(GetHintModifiers,phmLibrary)
    else if UpAtomIs('EXPERIMENTAL') then
      Include(GetHintModifiers,phmExperimental)
    else if UpAtomIs('DEPRECATED') then
      Include(GetHintModifiers,phmDeprecated)
    else
      Result:=false;
  end;

begin
  Result:=[];
  if Node=nil then exit;
  case Node.Desc of

  ctnProgram,ctnPackage,ctnLibrary,ctnUnit:
    begin
      MoveCursorToNodeStart(Node);
      ReadNextAtom;
      if not (UpAtomIs('PROGRAM') or UpAtomIs('PACKAGE') or UpAtomIs('LIBRARY')
        or UpAtomIs('UNIT')) then exit;
      ReadNextAtom;// name
      while IsHintModifier do ReadNextAtom;
    end;

  ctnProcedure,ctnProcedureType,ctnProcedureHead:
    begin
      if Node.Desc<>ctnProcedureHead then begin
        Node:=Node.FirstChild;
        if Node=nil then exit;
      end;
      MoveCursorToFirstProcSpecifier(Node);
      // ToDo:
    end;

  ctnProperty:
    begin
      Node:=Node.LastChild;
      while Node<>nil do begin
        if Node.Desc=ctnHintModifier then begin
          MoveCursorToNodeStart(Node);
          ReadNextAtom;
          IsHintModifier;
        end;
        Node:=Node.PriorBrother;
      end;
    end;

  ctnVarDefinition,ctnConstant,ctnConstDefinition,
  ctnTypeDefinition,ctnGenericType:
    begin
      Node:=FindTypeNodeOfDefinition(Node);
      if Node=nil then exit;
      while (Node<>nil) do begin
        if Node.Desc=ctnHintModifier then begin
          MoveCursorToNodeStart(Node);
          ReadNextAtom;
          IsHintModifier;
        end;
        Node:=Node.NextBrother;
      end;
    end;

  end;
end;

procedure TPascalReaderTool.ForEachIdentifierInCleanSrc(StartPos,
  EndPos: integer; SkipComments: boolean; Node: TCodeTreeNode;
  const OnIdentifier: TOnEachPRIdentifier; Data: pointer; var Abort: boolean);
var
  CommentLvl: Integer;
  InStrConst: Boolean;
  p: PChar;
  EndP: Pointer;
  Range: TEPRIRange;

  procedure SkipIdentifier; inline;
  begin
    while (p<EndP) and IsIdentChar[p^] do inc(p);
  end;

begin
  //debugln(['TPascalReaderTool.ForEachIdentifierInCleanSrc Node=',Node.DescAsString,' "',dbgstr(Src,StartPos,EndPos-StartPos),'"']);
  if (StartPos<1) then
    StartPos:=1;
  if StartPos>SrcLen then exit;
  if EndPos>SrcLen then EndPos:=SrcLen+1;
  if StartPos>=EndPos then exit;
  Range:=epriInCode;
  p:=@Src[StartPos];
  EndP:=p+EndPos-StartPos;
  while p<EndP do begin
    case p^ of

    '{':
      begin
        inc(p);
        if p^=#3 then begin
          // codetools skip comment {#3 #3}
          inc(p);
          repeat
            if p>=EndP then exit;
            if (p^=#3) and (p[1]='}')
            then begin
              inc(p,2);
              break;
            end;
            inc(p);
          until false;
        end else begin
          // pascal comment {}
          CommentLvl:=1;
          InStrConst:=false;
          if p^='$' then
            Range:=epriInDirective
          else
            Range:=epriInComment;
          repeat
            if p>=EndP then exit;
            case p^ of
            '{': if Scanner.NestedComments then inc(CommentLvl);
            '}':
              begin
                dec(CommentLvl);
                if CommentLvl=0 then break;
              end;
            'a'..'z','A'..'Z','_':
              if not InStrConst then begin
                if not SkipComments then begin
                  OnIdentifier(Self,p-PChar(Src)+1,Range,Node,Data,Abort);
                  SkipIdentifier;
                  if Abort then exit;
                end;
                while (p<EndP) and IsIdentChar[p^] do inc(p);
              end;
            '''':
              InStrConst:=not InStrConst;
            #10,#13:
              InStrConst:=false;
            end;
            inc(p);
          until false;
          inc(p);
          //debugln(StartPos,' ',copy(Src,CommentStart,StartPos-CommentStart));
        end;
      end;

    '/':  // Delphi comment
      if p[1]<>'/' then begin
        inc(p);
      end else begin
        inc(p,2);
        InStrConst:=false;
        repeat
          if p>=EndP then exit;
          case p^ of
          #10,#13:
            break;
          'a'..'z','A'..'Z','_':
            if not InStrConst then begin
              if not SkipComments then begin
                OnIdentifier(Self,p-PChar(Src)+1,Range,Node,Data,Abort);
                SkipIdentifier;
                if Abort then exit;
              end;
              while (p<EndP) and IsIdentChar[p^] do inc(p);
            end;
          '''':
            InStrConst:=not InStrConst;
          end;
          inc(p);
        until false;
        inc(p);
        if (p<EndP) and (p^ in [#10,#13])
        and (p[-1]<>p^) then
          inc(p);
      end;

    '(': // turbo pascal comment
      if (p[1]<>'*') then begin
        inc(p);
      end else begin
        inc(p,3);
        InStrConst:=false;
        repeat
          if p>=EndP then exit;
          case p^ of
          ')':
            if p[-1]='*' then break;
          'a'..'z','A'..'Z','_':
            if not InStrConst then begin
              if not SkipComments then begin
                OnIdentifier(Self,p-PChar(Src)+1,Range,Node,Data,Abort);
                SkipIdentifier;
                if Abort then exit;
              end;
              SkipIdentifier;
            end;
          '''':
            InStrConst:=not InStrConst;
          #10,#13:
            InStrConst:=false;
          end;
          inc(p);
        until false;
        inc(p);
      end;

    'a'..'z','A'..'Z','_':
      begin
        OnIdentifier(Self,p-PChar(Src)+1,epriInCode,Node,Data,Abort);
        SkipIdentifier;
        if Abort then exit;
      end;

    '''':
      begin
        // skip string constant
        inc(p);
        while p<EndP do begin
          if (not (p^ in ['''',#10,#13])) then
            inc(p)
          else begin
            inc(p);
            break;
          end;
        end;
      end;

    else
      inc(p);
    end;
  end;
end;

procedure TPascalReaderTool.ForEachIdentifierInNode(Node: TCodeTreeNode;
  SkipComments: boolean; const OnIdentifier: TOnEachPRIdentifier;
  Data: Pointer; var Abort: boolean);
var
  StartPos: Integer;
  EndPos: Integer;
  Child: TCodeTreeNode;
begin
  //debugln(['TPascalReaderTool.ForEachIdentifierInNode START ',Node.DescAsString]);
  if NodeNeedsBuildSubTree(Node) then
    BuildSubTree(Node);
  if Node.FirstChild<>nil then begin
    EndPos:=Node.StartPos;
    Child:=Node.FirstChild;
    while Child<>nil do begin
      // scan in front of child
      ForEachIdentifierInCleanSrc(EndPos,Child.StartPos,SkipComments,
        Node,OnIdentifier,Data,Abort);
      if Abort then exit;
      // scan child
      ForEachIdentifierInNode(Child,SkipComments,OnIdentifier,Data,Abort);
      if Abort then exit;
      EndPos:=Child.EndPos;
      Child:=Child.NextBrother;
    end;
    // scan behind children
    ForEachIdentifierInCleanSrc(EndPos,Node.EndPos,SkipComments,
      Node,OnIdentifier,Data,Abort);
  end else begin
    // leaf node
    StartPos:=Node.StartPos;
    EndPos:=Node.EndPos;
    // nodes without children can overlap with their NextBrother
    if (Node.NextBrother<>nil)
    and (Node.NextBrother.StartPos<EndPos) then
      EndPos:=Node.NextBrother.StartPos;
    // scan node range
    ForEachIdentifierInCleanSrc(StartPos,EndPos,SkipComments,
      Node,OnIdentifier,Data,Abort);
  end;
end;

procedure TPascalReaderTool.ForEachIdentifier(SkipComments: boolean;
  const OnIdentifier: TOnEachPRIdentifier; Data: Pointer);
var
  Node: TCodeTreeNode;
  Abort: boolean;
begin
  //debugln(['TPascalReaderTool.ForEachIdentifier START']);
  Node:=Tree.Root;
  Abort:=false;
  while Node<>nil do begin
    ForEachIdentifierInNode(Node,SkipComments,OnIdentifier,Data,Abort);
    if Abort then exit;
    Node:=Node.NextBrother;
  end;
end;

function TPascalReaderTool.FindVarNode(StartNode: TCodeTreeNode;
  const UpperVarName: string; Visibility: TClassSectionVisibility
  ): TCodeTreeNode;
var
  InClass: Boolean;
begin
  Result:=StartNode;
  InClass:=FindClassOrInterfaceNode(StartNode)<>nil;
  while Result<>nil do begin
    if (Result.Desc=ctnVarDefinition)
    and (not InClass or IdentNodeIsInVisibleClassSection(Result, Visibility))
    and (CompareNodeIdentChars(Result,UpperVarName)=0) then
      exit;
    if InClass then
      Result:=FindNextIdentNodeInClass(Result)
    else
      Result:=FindNextNodeOnSameLvl(Result);
  end;
end;

function TPascalReaderTool.FindTypeNodeOfDefinition(
  DefinitionNode: TCodeTreeNode): TCodeTreeNode;
// for example: 'var a,b,c: integer;'  only c has a type child
begin
  Result:=DefinitionNode;
  while (Result<>nil)
  and (Result.Desc in AllIdentifierDefinitions) do begin
    if (Result.FirstChild<>nil) then begin
      Result:=Result.FirstChild;
      if Result.Desc=ctnGenericName then begin
        // skip generic name and params
        Result:=Result.NextBrother;
        if Result=nil then exit;
        Result:=Result.NextBrother;
        if Result=nil then exit;
      end;
      if (not (Result.Desc in AllPascalTypes)) then
        Result:=nil;
      exit;
    end;
    if Result.Desc<>ctnVarDefinition then exit(nil);
    Result:=Result.NextBrother;
  end;
end;

function TPascalReaderTool.FindClassNode(StartNode: TCodeTreeNode;
  const AClassName: string; IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
// search for class like types on same level
var
  ANode, CurClassNode: TCodeTreeNode;
  NameNode: TCodeTreeNode;
  p: PChar;
begin
  ANode:=StartNode;
  Result:=nil;
  if AClassName='' then exit;
  p:=PChar(AClassName);
  while (ANode<>nil) do begin
    if ANode.Desc in [ctnTypeDefinition,ctnGenericType] then begin
      //debugln(['TPascalReaderTool.FindClassNode ',GetIdentifier(@Src[ANode.StartPos])]);
      CurClassNode:=FindTypeNodeOfDefinition(ANode);
      if (CurClassNode<>nil)
      and (CurClassNode.Desc in AllClassObjects) then begin
        if (not (IgnoreForwards
                 and ((CurClassNode.SubDesc and ctnsForwardDeclaration)>0)))
        and (not (IgnoreNonForwards
                 and ((CurClassNode.SubDesc and ctnsForwardDeclaration)=0)))
        then begin
          NameNode:=ANode;
          if (ANode.Desc=ctnGenericType) and (ANode.FirstChild<>nil) then
            NameNode:=ANode.FirstChild;
          //debugln(['TPascalReaderTool.FindClassNode class name = "',GetIdentifier(@Src[NameNode.StartPos]),'"']);
          if NameNode.StartPos>SrcLen then exit;
          if CompareIdentifiers(p,@Src[NameNode.StartPos])=0 then begin
            Result:=FindNestedClass(CurClassNode,p,true);
            exit;
          end;
        end;
      end;
    end;
    // next node
    if (ANode.Desc in [ctnTypeSection]+AllCodeSections)
    and (ANode.FirstChild<>nil) then
      ANode:=ANode.FirstChild
    else if ANode.NextBrother<>nil then
      ANode:=ANode.NextBrother
    else begin
      // skip procs, const and var sections
      repeat
        ANode:=ANode.Parent;
        if (ANode=nil) then exit;
        if (not (ANode.Desc in [ctnTypeSection]+AllCodeSections)) then exit;
        if ANode.NextBrother<>nil then begin
          ANode:=ANode.NextBrother;
          break;
        end;
      until false;
    end;
  end;
end;

function TPascalReaderTool.FindClassNodeBackwards(StartNode: TCodeTreeNode;
  const AClassName: string; IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
var
  ANode: TCodeTreeNode;
  CurClassNode: TCodeTreeNode;
  p: PChar;
begin
  ANode:=StartNode;
  p:=PChar(AClassName);
  while ANode<>nil do begin
    if ANode.Desc=ctnTypeDefinition then begin
      CurClassNode:=ANode.FirstChild;
      if (CurClassNode<>nil)
      and (CurClassNode.Desc in AllClassObjects) then begin
        if (not (IgnoreForwards
                 and ((CurClassNode.SubDesc and ctnsForwardDeclaration)>0)))
        and (not (IgnoreNonForwards
                 and ((CurClassNode.SubDesc and ctnsForwardDeclaration)=0)))
        then begin
          if CompareIdentifiers(p,@Src[ANode.StartPos])=0 then begin
            Result:=FindNestedClass(CurClassNode,p,true);
            exit;
          end;
        end;
      end;
    end;
    if ANode.PriorBrother<>nil then begin
      ANode:=ANode.PriorBrother;
      if (ANode.FirstChild<>nil) and (ANode.Desc in AllCodeSections) then
        ANode:=ANode.LastChild;
      if (ANode.FirstChild<>nil) and (ANode.Desc in AllDefinitionSections) then
        ANode:=ANode.LastChild;
    end else begin
      ANode:=ANode.Parent;
    end;
  end;
  Result:=nil;
end;

function TPascalReaderTool.FindNestedClass(RootClassNode: TCodeTreeNode;
  AClassName: PChar; SkipFirst: boolean): TCodeTreeNode;
var
  p: PChar;
  Node: TCodeTreeNode;
  EndNode: TCodeTreeNode;
begin
  Result:=nil;
  if RootClassNode=nil then exit;
  if AClassName=nil then exit;
  p:=AClassName;
  if SkipFirst then begin
    while IsIdentChar[p^] do inc(p);
    if p^='<' then
    begin
      while not (p^ in [#0,'>']) do Inc(p);
      if p^ = '>' then Inc(p);
    end;
    if p^=#0 then exit(RootClassNode);
    if p^<>'.' then exit;
    inc(p);
  end;
  //debugln(['TPascalReaderTool.FindNestedClass p="',p,'"']);
  if not IsIdentStartChar[p^] then exit;
  EndNode:=RootClassNode.NextSkipChilds;
  Node:=RootClassNode.Next;
  while Node<>EndNode do begin
    // debugln(['TPascalReaderTool.FindNestedClass Node=',node.DescAsString]);
    if Node.Desc in [ctnTypeDefinition,ctnGenericType] then begin
      if (Node.LastChild<>nil) and (Node.LastChild.Desc in AllClasses) then begin
        if ((Node.Desc=ctnTypeDefinition)
          and (CompareIdentifierPtrs(p,@Src[Node.StartPos])=0))
        or ((Node.FirstChild.Desc=ctnGenericName)
          and (CompareIdentifierPtrs(p,@Src[Node.FirstChild.StartPos])=0))
        then begin
          // class found
          Node:=Node.LastChild;
          while IsIdentChar[p^] do inc(p);
          if p^=#0 then exit(Node);
          if p^<>'.' then exit;
          Result:=FindNestedClass(Node,p+1,false);
          exit;
        end;
      end;
    end;
    if Node.Desc in AllClassSections then
      Node:=Node.Next
    else
      Node:=Node.NextSkipChilds;
  end;
end;

function TPascalReaderTool.FindClassNode(CursorNode: TCodeTreeNode): TCodeTreeNode;
// find class node of a node in a procedure (declaration or body)
begin
  while CursorNode<>nil do begin
    if CursorNode.Desc in AllClassObjects then begin
      Result:=CursorNode;
      exit;
    end else if NodeIsMethodBody(CursorNode) then begin
      Result:=FindClassNodeForMethodBody(CursorNode,true,false);
      exit;
    end;
    CursorNode:=CursorNode.Parent;
  end;
  Result:=nil;
end;

function TPascalReaderTool.FindClassNodeForMethodBody(ProcNode: TCodeTreeNode;
  IgnoreForwards, IgnoreNonForwards: boolean): TCodeTreeNode;
var
  ProcClassName: String;
begin
  Result:=nil;
  ProcClassName:=ExtractClassNameOfProcNode(ProcNode,true);
  if ProcClassName='' then exit;
  Result:=FindClassNodeBackwards(ProcNode,ProcClassName,IgnoreForwards,
                                 IgnoreNonForwards);
end;

function TPascalReaderTool.FindClassOrInterfaceNode(CursorNode: TCodeTreeNode;
  FindClassOfMethod: boolean): TCodeTreeNode;
begin
  while CursorNode<>nil do begin
    if CursorNode.Desc in AllClasses then begin
      Result:=CursorNode;
      exit;
    end else if FindClassOfMethod and NodeIsMethodBody(CursorNode) then begin
      Result:=FindClassNodeForMethodBody(CursorNode,true,false);
      exit;
    end;
    CursorNode:=CursorNode.Parent;
  end;
  Result:=nil;
end;

function TPascalReaderTool.FindClassSection(ClassNode: TCodeTreeNode;
  NodeDesc: TCodeTreeNodeDesc): TCodeTreeNode;
begin
  Result:=ClassNode.FirstChild;
  while (Result<>nil) and (Result.Desc<>NodeDesc) do
    Result:=Result.NextBrother;
end;

function TPascalReaderTool.FindLastClassSection(ClassNode: TCodeTreeNode;
  NodeDesc: TCodeTreeNodeDesc): TCodeTreeNode;
begin
  Result:=ClassNode.LastChild;
  while (Result<>nil) and (Result.Desc<>NodeDesc) do
    Result:=Result.PriorBrother;
end;

function TPascalReaderTool.GetClassVisibility(Node: TCodeTreeNode
  ): TCodeTreeNodeDesc;
begin
  Result:=ctnNone;
  if Node=nil then exit;
  if Node.Desc=ctnProcedureHead then
    Node:=Node.Parent;
  if not (Node.Desc in AllClassSections) then begin
    Node:=Node.Parent;
    if Node=nil then exit;
  end;
  if Node.Desc in AllClassSubSections then
    Node:=Node.Parent;
  if Node.Desc in AllClassBaseSections then
    Result:=Node.Desc;
end;

function TPascalReaderTool.FindClassNodeInInterface(
  const AClassName: string; IgnoreForwards, IgnoreNonForwards,
  ErrorOnNotFound: boolean): TCodeTreeNode;
  
  procedure RaiseClassNotFound;
  begin
    RaiseExceptionFmt(ctsClassSNotFound, [AClassName]);
  end;
  
begin
  Result:=Tree.Root;
  if Result<>nil then begin
    if Result.Desc=ctnUnit then
      Result:=Result.NextBrother;
    if Result<>nil then begin
      Result:=FindClassNode(Result.FirstChild,AClassName,
                            IgnoreForwards, IgnoreNonForwards);
      if (Result<>nil) and Result.HasParentOfType(ctnImplementation) then
        Result:=nil;
    end;
  end;
  if (Result=nil) and ErrorOnNotFound then
    RaiseClassNotFound;
end;

function TPascalReaderTool.FindClassNodeInUnit(const AClassName: string;
  IgnoreForwards, IgnoreNonForwards, IgnoreImplementation,
  ErrorOnNotFound: boolean): TCodeTreeNode;

  procedure RaiseClassNotFound;
  begin
    RaiseExceptionFmt(ctsClassSNotFound, [AClassName]);
  end;

begin
  Result:=Tree.Root;
  if Result<>nil then begin
    if Result.Desc in [ctnUnit,ctnLibrary,ctnPackage] then begin
      Result:=Result.NextBrother;
    end;
    if Result<>nil then begin
      Result:=FindClassNode(Result.FirstChild,AClassName,
                            IgnoreForwards, IgnoreNonForwards);
      if (Result<>nil) and IgnoreImplementation
      and Result.HasParentOfType(ctnImplementation) then
        Result:=nil;
    end;
  end;
  if (Result=nil) and ErrorOnNotFound then
    RaiseClassNotFound;
end;

function TPascalReaderTool.FindFirstIdentNodeInClass(ClassNode: TCodeTreeNode
  ): TCodeTreeNode;
begin
  if (ClassNode=nil) then exit(nil);
  Result:=FindNextIdentNodeInClass(ClassNode.FirstChild);
end;

function TPascalReaderTool.FindLastIdentNodeInClass(ClassNode: TCodeTreeNode
  ): TCodeTreeNode;
begin
  if (ClassNode=nil) then exit(nil);
  Result:=ClassNode.LastChild;
  if Result=nil then exit;
  while (Result.FirstChild<>nil) and (Result.Desc in AllClassSections) do
    Result:=Result.LastChild;
  if not (Result.Desc in AllClassSections) then
    Result:=FindPriorIdentNodeInClass(Result);
end;

function TPascalReaderTool.FindNextIdentNodeInClass(Node: TCodeTreeNode
  ): TCodeTreeNode;
// Node must be nil or a class section or an identifier node in a class
begin
  Result:=Node;
  if Result=nil then exit;
  repeat
    // descend into class sections, skip empty class sections
    if (Result.FirstChild<>nil) and (Result.Desc in AllClassSections) then
      Result:=Result.FirstChild
    else begin
      while Result.NextBrother=nil do begin
        Result:=Result.Parent;
        if (Result=nil) or (not (Result.Desc in AllClassSections)) then
          exit(nil);
      end;
      Result:=Result.NextBrother
    end;
  until not (Result.Desc in AllClassSections);
end;

function TPascalReaderTool.FindPriorIdentNodeInClass(Node: TCodeTreeNode
  ): TCodeTreeNode;
begin
  Result:=Node;
  if Result=nil then exit;
  repeat
    if Result.PriorBrother<>nil then begin
      Result:=Result.PriorBrother;
      while (Result.LastChild<>nil) and (Result.Desc in AllClassSections) do
        Result:=Result.LastChild;
    end else if Result.Parent.Desc in AllClassSections then
      Result:=Result.Parent
    else
      exit(nil);
  until not (Result.Desc in AllClassSections);
end;

function TPascalReaderTool.ClassSectionNodeStartsWithWord(ANode: TCodeTreeNode
  ): boolean;
begin
  Result:=(ANode<>nil) and (ANode.StartPos<ANode.EndPos)
    and (IsIdentStartChar[Src[ANode.StartPos]]);
end;

function TPascalReaderTool.IsClassNode(Node: TCodeTreeNode): boolean;
begin
  Result:=(Node<>nil) and (Node.Desc=ctnClass);
end;

function TPascalReaderTool.FindInheritanceNode(ClassNode: TCodeTreeNode): TCodeTreeNode;
begin
  Result:=ClassNode.FirstChild;
  while (Result<>nil) and (Result.Desc in [ctnClassSealed,ctnClassAbstract,ctnClassExternal]) do
    Result:=Result.NextBrother;
  if (Result<>nil) and (Result.Desc<>ctnClassInheritance) then
    Result:=nil;
end;

function TPascalReaderTool.ExtractRecordCaseType(RecordCaseNode: TCodeTreeNode): string;
//  case a:b.c of
//  case a:(b,c) of
var
  VarNode: TCodeTreeNode;
begin
  Result:='';
  VarNode:=RecordCaseNode.FirstChild;
  if VarNode=nil then exit;
  if VarNode.FirstChild<>nil then
    Result:=ExtractNode(RecordCaseNode.FirstChild,[]);
end;

function TPascalReaderTool.GetSourceType: TCodeTreeNodeDesc;
begin
  if Tree.Root<>nil then
    Result:=Tree.Root.Desc
  else
    Result:=ctnNone;
end;

function TPascalReaderTool.IdentNodeIsInVisibleClassSection(
  Node: TCodeTreeNode; Visibility: TClassSectionVisibility): Boolean;
begin
  if Visibility = csvEverything then
    Result := True
  else
  if (Node.Parent<>nil) then
    case Visibility of
      //csvAbovePrivate: todo: add strict private and strict protected (should be registered as new sections)
      csvProtectedAndHigher:
        Result := not(Node.Parent.Desc = ctnClassPrivate);//todo: add strict private
      csvPublicAndHigher:
        Result := not(Node.Parent.Desc in [ctnClassPrivate, ctnClassProtected]);//todo: strict private and strict protected
    else
      Result := True
    end
  else
    Result := False;
end;

function TPascalReaderTool.ExtractProcedureGroup(ProcNode: TCodeTreeNode
  ): TPascalMethodGroup;
begin
  Result:=mgMethod;
  if (ProcNode=nil) then exit;
  if ProcNode.Desc=ctnProcedureHead then
    ProcNode:=ProcNode.Parent;
  if ProcNode.Desc<>ctnProcedure then exit;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if UpAtomIs('CLASS') then
  begin
    ReadNextAtom;
    if UpAtomIs('CONSTRUCTOR') then
      Result := mgClassConstructor;
    if UpAtomIs('DESTRUCTOR') then
      Result := mgClassDestructor;
  end else
  if UpAtomIs('CONSTRUCTOR') then
    Result := mgConstructor
end;

function TPascalReaderTool.PositionInSourceName(CleanPos: integer): boolean;
var
  NamePos: TAtomPosition;
begin
  Result:=false;
  if not GetSourceNamePos(NamePos) then exit;
  Result:=(CleanPos>=NamePos.StartPos) and (CleanPos<NamePos.EndPos);
end;

function TPascalReaderTool.ExtractSourceName: string;
begin
  Result:='';
  if Tree.Root<>nil then begin
    MoveCursorToNodeStart(Tree.Root);
    ReadNextAtom; // read source type 'program', 'unit' ...
    if (Tree.Root.Desc<>ctnProgram) or UpAtomIs('PROGRAM') then begin
      ReadNextAtom; // read name
      if AtomIsIdentifier then begin
        Result:=copy(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos);
        ReadNextAtom;
        while CurPos.Flag=cafPoint do begin
          ReadNextAtom;
          if not AtomIsIdentifier then exit;
          Result:=Result+'.'+copy(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos);
          ReadNextAtom;
        end;
        exit;
      end;
    end;
  end;
  if (Tree.Root<>nil) and (Tree.Root.Desc=ctnProgram) then
    // a program without the 'program' header uses the file name as name
    Result:=ExtractFileNameOnly(MainFilename)
  else
    Result:='';
end;

function TPascalReaderTool.GetSourceNamePos(out NamePos: TAtomPosition
  ): boolean;
begin
  Result:=false;
  NamePos.StartPos:=-1;
  if Tree.Root=nil then exit;
  MoveCursorToNodeStart(Tree.Root);
  ReadNextAtom; // read source type 'program', 'unit' ...
  if (Tree.Root.Desc=ctnProgram) and (not UpAtomIs('PROGRAM')) then exit;
  ReadNextAtom; // read name
  if not AtomIsIdentifier then exit;
  NamePos:=CurPos;
  Result:=true;
  ReadNextAtom;
  while CurPos.Flag=cafPoint do begin
    ReadNextAtom;
    if not AtomIsIdentifier then exit;
    NamePos.EndPos:=CurPos.EndPos;
    ReadNextAtom;
  end;
end;

function TPascalReaderTool.GetSourceName(DoBuildTree: boolean): string;
begin
  Result:='';
  if DoBuildTree then
    BuildTree(lsrSourceName);
  CachedSourceName:=ExtractSourceName;
  Result:=CachedSourceName;
end;

function TPascalReaderTool.NodeIsInAMethod(Node: TCodeTreeNode): boolean;
begin
  Result:=false;
  while (Node<>nil) do begin
    if (Node.Desc=ctnProcedure) then begin
      if NodeIsMethodBody(Node) then begin
        Result:=true;
        exit;
      end;
    end;
    Node:=Node.Parent;
  end;
end;

function TPascalReaderTool.NodeIsMethodBody(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (ProcNode<>nil) and (ProcNode.Desc=ctnProcedure)
  and (ProcNode.FirstChild<>nil) then begin

    // ToDo: ppu, dcu

    MoveCursorToNodeStart(ProcNode.FirstChild); // ctnProcedureHead
    ReadNextAtom;
    if not AtomIsIdentifier then exit;
    ReadNextAtom;
    if (CurPos.Flag<>cafPoint) then exit;
    Result:=true;
    exit;
  end;
end;

function TPascalReaderTool.GetMethodOfBody(Node: TCodeTreeNode): TCodeTreeNode;
begin
  Result:=Node;
  while (Result<>nil) and not NodeIsMethodBody(Result) do
    Result:=Result.Parent;
end;

function TPascalReaderTool.NodeIsFunction(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then exit;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if UpAtomIs('CLASS') then ReadNextAtom;
  Result:=UpAtomIs('FUNCTION');
end;

function TPascalReaderTool.NodeIsConstructor(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (ProcNode=nil) then exit;
  if ProcNode.Desc=ctnProcedureHead then
    ProcNode:=ProcNode.Parent;
  if ProcNode.Desc<>ctnProcedure then exit;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if UpAtomIs('CLASS') then ReadNextAtom;
  Result:=UpAtomIs('CONSTRUCTOR');
  if not Result and UpAtomIs('FUNCTION')
  and ([cmsObjectiveC1,cmsObjectiveC2]*Scanner.CompilerModeSwitches<>[]) then
  begin
    ProcNode:=ProcNode.FirstChild;
    if ProcNode=nil then exit;
    if (ProcNode.SubDesc and ctnsNeedJITParsing)>0 then
      BuildSubTreeForProcHead(ProcNode);
    ProcNode:=ProcNode.FirstChild;
    if (ProcNode=nil) then exit;
    if ProcNode.Desc=ctnParameterList then
      ProcNode:=ProcNode.NextBrother;
    if (ProcNode=nil) then exit;
    MoveCursorToNodeStart(ProcNode);
    ReadNextAtom;
    Result:=UpAtomIs('ID');
  end;
end;

function TPascalReaderTool.NodeIsDestructor(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (ProcNode=nil) then exit;
  if ProcNode.Desc=ctnProcedureHead then
    ProcNode:=ProcNode.Parent;
  if ProcNode.Desc<>ctnProcedure then exit;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  Result:=UpAtomIs('DESTRUCTOR');
end;

function TPascalReaderTool.NodeIsForwardProc(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  // check if procedure
  if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then exit;
  // check if in interface
  if (ProcNode.Parent<>nil) and (ProcNode.Parent.Desc=ctnInterface) then
    exit(true);
  // check if has forward
  if (ctnsForwardDeclaration and ProcNode.SubDesc)>0 then exit(true);
end;

function TPascalReaderTool.NodeIsOperator(ProcNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (ProcNode=nil) then exit;
  if ProcNode.Desc=ctnProcedureHead then
    ProcNode:=ProcNode.Parent;
  if ProcNode.Desc<>ctnProcedure then exit;
  MoveCursorToNodeStart(ProcNode);
  ReadNextAtom;
  if UpAtomIs('CLASS') then ReadNextAtom;
  Result:=UpAtomIs('OPERATOR');
end;

function TPascalReaderTool.NodeIsResultIdentifier(Node: TCodeTreeNode): boolean;
begin
  Result:=(Node<>nil)
    and (Node.Desc=ctnVarDefinition)
    and (Node.Parent<>nil)
    and (Node.Parent.Desc=ctnProcedureHead);
end;

function TPascalReaderTool.NodeIsResultType(Node: TCodeTreeNode): boolean;
begin
  Result:=(Node<>nil)
    and (Node.Desc=ctnIdentifier)
    and (Node.Parent<>nil)
    and (Node.Parent.Desc=ctnProcedureHead);
end;

function TPascalReaderTool.NodeIsPartOfTypeDefinition(ANode: TCodeTreeNode
  ): boolean;
begin
  ANode:=ANode.Parent;
  while ANode<>nil do begin
    if ANode.Desc in (AllIdentifierDefinitions+AllPascalTypes) then begin
      Result:=true;
      exit;
    end;
    ANode:=ANode.Parent;
  end;
  Result:=false;
end;

function TPascalReaderTool.ExtractDefinitionNodeType(
  DefinitionNode: TCodeTreeNode): string;
var
  TypeNode: TCodeTreeNode;
begin
  Result:='';
  TypeNode:=FindTypeNodeOfDefinition(DefinitionNode);
  if TypeNode=nil then exit;
  if TypeNode.Desc=ctnIdentifier then
    Result:=GetIdentifier(@Src[TypeNode.StartPos]);
end;

function TPascalReaderTool.ExtractDefinitionName(DefinitionNode: TCodeTreeNode
  ): string;
begin
  if DefinitionNode.Desc=ctnGenericType then begin
    if DefinitionNode.FirstChild<>nil then
      Result:=GetIdentifier(@Src[DefinitionNode.FirstChild.StartPos])
    else
      Result:='';
  end else begin
    Result:=GetIdentifier(@Src[DefinitionNode.StartPos]);
  end;
end;

function TPascalReaderTool.PositionInDefinitionName(
  DefinitionNode: TCodeTreeNode; CleanPos: integer): boolean;
var
  StartPos: LongInt;
begin
  if DefinitionNode.Desc=ctnGenericType then begin
    if DefinitionNode.FirstChild<>nil then
      StartPos:=DefinitionNode.FirstChild.StartPos
    else
      StartPos:=0;
  end else begin
    StartPos:=DefinitionNode.StartPos;
  end;
  Result:=(CleanPos>=StartPos) and (CleanPos<StartPos+GetIdentLen(@Src[StartPos]));
end;

function TPascalReaderTool.MoveCursorToParameterSpecifier(
  DefinitionNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (DefinitionNode=nil) or (DefinitionNode.Desc<>ctnVarDefinition)
  or (DefinitionNode.Parent=nil)
  or (DefinitionNode.Parent.Desc<>ctnParameterList) then exit;
  // find first variable node of this type (e.g. var a,b,c,d: integer)
  DefinitionNode:=GetFirstGroupVarNode(DefinitionNode);
  if DefinitionNode.PriorBrother<>nil then
    MoveCursorToCleanPos(DefinitionNode.PriorBrother.EndPos)
  else
    MoveCursorToCleanPos(DefinitionNode.Parent.StartPos);
  ReadNextAtom;
  while (CurPos.StartPos<DefinitionNode.StartPos) do ReadNextAtom;
  UndoReadNextAtom;
  Result:=CurPos.Flag=cafWord;
end;

function TPascalReaderTool.GetFirstGroupVarNode(VarNode: TCodeTreeNode): TCodeTreeNode;
begin
  Result:=VarNode;
  if (VarNode=nil) or (VarNode.Desc<>ctnVarDefinition) then exit;
  while VarNode<>nil do begin
    VarNode:=VarNode.PriorBrother;
    if (VarNode=nil) or (VarNode.Desc<>ctnVarDefinition)
    or (VarNode.FirstChild<>nil) then exit;
    Result:=VarNode;
  end;
end;

function TPascalReaderTool.FindEndOfWithExpr(WithVarNode: TCodeTreeNode): integer;
begin
  if WithVarNode.Desc<>ctnWithVariable then exit(-1);
  MoveCursorToCleanPos(WithVarNode.StartPos);
  ReadNextAtom;
  if not ReadTilVariableEnd(true,true) then exit(-1);
  UndoReadNextAtom;
  Result:=CurPos.EndPos;
end;

function TPascalReaderTool.ExtractWithBlockExpression(
  WithVarNode: TCodeTreeNode; Attr: TProcHeadAttributes): string;
var
  EndPos: Integer;
begin
  EndPos:=FindEndOfWithExpr(WithVarNode);
  if EndPos<1 then exit('');
  Result:=ExtractCode(WithVarNode.StartPos,EndPos,Attr);
end;

function TPascalReaderTool.FindWithBlockStatement(WithVarNode: TCodeTreeNode
  ): TCodeTreeNode;
begin
  Result:=WithVarNode;
  repeat
    if Result=nil then exit;
    if Result.Desc<>ctnWithVariable then exit(nil);
    if Result.FirstChild<>nil then begin
      Result:=Result.FirstChild;
      if Result.Desc=ctnWithStatement then exit;
      exit(nil);
    end;
  until false;
end;

function TPascalReaderTool.NodeIsIdentifierInInterface(Node: TCodeTreeNode): boolean;
// true if identifier is visible from other units (without prefixing)
begin
  case Node.Desc of
  ctnEnumIdentifier:
    Result:=true;
  ctnVarDefinition:
    Result:=(Node.Parent.Desc=ctnVarSection)
            and (Node.Parent.Parent.Desc=ctnInterface);
  ctnConstDefinition:
    Result:=(Node.Parent.Desc=ctnConstSection)
            and (Node.Parent.Parent.Desc=ctnInterface);
  ctnTypeDefinition,ctnGenericType:
    Result:=(Node.Parent.Desc=ctnTypeSection)
            and (Node.Parent.Parent.Desc=ctnInterface);
  ctnProcedure,ctnProperty:
    Result:=Node.Parent.Desc=ctnInterface;
  ctnProcedureHead:
    Result:=(Node.Parent.Desc=ctnProcedure)
        and (Node.Parent.Parent.Desc=ctnInterface);
  end;
  Result:=false;
end;

function TPascalReaderTool.NodeCanHaveForwardType(TypeNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (TypeNode=nil) or (TypeNode.Desc<>ctnTypeDefinition)
  or (TypeNode.FirstChild=nil) then
    exit;
  if (TypeNode.FirstChild.Desc in AllClasses)
  and (TypeNode.FirstChild.SubDesc and ctnsForwardDeclaration=0) then
    Result:=true;
end;

function TPascalReaderTool.NodeIsClassConstructorOrDestructor(
  ProcNode: TCodeTreeNode): boolean;
begin
  Result := ExtractProcedureGroup(ProcNode) in [mgClassConstructor, mgClassDestructor];
end;

function TPascalReaderTool.NodeIsForwardType(TypeNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (TypeNode=nil) or (TypeNode.Desc<>ctnTypeDefinition)
  or (TypeNode.FirstChild=nil) then
    exit;
  if (TypeNode.FirstChild.Desc in AllClasses)
  and (TypeNode.FirstChild.SubDesc and ctnsForwardDeclaration>0) then
    Result:=true;
end;

function TPascalReaderTool.FindForwardTypeNode(TypeNode: TCodeTreeNode;
  SearchFirst: boolean): TCodeTreeNode;
{ Find the first forward type of TypeNode
}

  function Next: TCodeTreeNode;
  begin
    Result:=FindForwardTypeNode;
    if Result.PriorBrother<>nil then
      // search upwards
      Result:=Result.PriorBrother
    else if Result.Parent.Desc in AllDefinitionSections then begin
      // type section was searched
      // check for other type sections in front
      Result:=Result.Parent;
      repeat
        while (Result.PriorBrother<>nil) do begin
          Result:=Result.PriorBrother;
          if (Result.Desc in AllDefinitionSections)
          and (Result.LastChild<>nil) then begin
            Result:=Result.LastChild;
            exit;
          end;
        end;
        // check if in implementation section
        if (Result.Parent=nil) or (Result.Parent.Desc<>ctnImplementation) then
          exit(nil);
        Result:=Result.Parent;
        // check if there is an interface section
        if (Result.PriorBrother=nil) or (Result.PriorBrother.Desc<>ctnInterface)
        then
          exit(nil);
        // search in interface section
        Result:=Result.PriorBrother;
        Result:=Result.LastChild;
      until Result=nil;
    end else
      exit;
  end;

var
  Node: TCodeTreeNode;
begin
  Result:=nil;
  if not NodeCanHaveForwardType(TypeNode) then exit;
  Node:=TypeNode;
  while Node<>nil do begin
    if Node.Desc in AllIdentifierDefinitions then begin
      if CompareIdentifiers(@Src[TypeNode.StartPos],@Src[Node.StartPos])=0
      then begin
        if (Node.Desc=ctnTypeDefinition) and NodeIsForwardType(Node) then begin
          // a forward
          Result:=Node;
          if not SearchFirst then exit;
        end else begin
          // a redefinition
          exit;
        end;
      end;
    end;
    Node:=Next;
  end;
end;

function TPascalReaderTool.FindHelperForNode(HelperNode: TCodeTreeNode
  ): TCodeTreeNode;
begin
  Result:=HelperNode.FirstChild;
  while (Result<>nil) and (Result.Desc = ctnClassInheritance) do
    Result:=Result.NextBrother;
  if (Result<>nil) and (Result.Desc<>ctnHelperFor) then
    Result:=nil;
end;

function TPascalReaderTool.FindTypeOfForwardNode(TypeNode: TCodeTreeNode
  ): TCodeTreeNode;

  function Next: TCodeTreeNode;
  begin
    Result:=FindTypeOfForwardNode;
    if Result.NextBrother<>nil then
      // search forwards
      Result:=Result.NextBrother
    else if Result.Parent.Desc in AllDefinitionSections then begin
      // type section was searched
      // check for other type sections in front
      Result:=Result.Parent;
      repeat
        while (Result.NextBrother<>nil) do begin
          Result:=Result.NextBrother;
          if (Result.Desc in AllDefinitionSections)
          and (Result.FirstChild<>nil) then begin
            Result:=Result.FirstChild;
            exit;
          end;
        end;
        // check if in interface section
        if (Result.Parent=nil) or (Result.Parent.Desc<>ctnInterface) then
          exit(nil);
        Result:=Result.Parent;
        // check if there is an implementation section
        if (Result.NextBrother=nil) or (Result.NextBrother.Desc<>ctnImplementation)
        then
          exit(nil);
        // search in implementation section
        Result:=Result.NextBrother;
        Result:=Result.FirstChild;
      until Result=nil;
    end else
      exit;
  end;

var
  Node: TCodeTreeNode;
begin
  Result:=nil;
  if not NodeIsForwardType(TypeNode) then exit;
  Node:=TypeNode;
  while Node<>nil do begin
    if Node.Desc in AllIdentifierDefinitions then begin
      if CompareIdentifiers(@Src[TypeNode.StartPos],@Src[Node.StartPos])=0
      then begin
        if (Node.Desc=ctnTypeDefinition) and (not NodeIsForwardType(Node)) then
        begin
          // a type
          Result:=Node;
          exit;
        end else begin
          // a redefinition
          exit;
        end;
      end;
    end;
    Node:=Next;
  end;
end;

function TPascalReaderTool.ExtractArrayRange(ArrayNode: TCodeTreeNode;
  Attr: TProcHeadAttributes): string;
begin
  Result:='';
  if (ArrayNode=nil) or (ArrayNode.Desc<>ctnRangedArrayType) then exit;
  MoveCursorToNodeStart(ArrayNode);
  if not ReadNextUpAtomIs('ARRAY') then exit;
  if not ReadNextAtomIsChar('[') then exit;
  Result:=ExtractBrackets(CurPos.StartPos,Attr);
end;

function TPascalReaderTool.PropertyIsDefault(PropertyNode: TCodeTreeNode): boolean;
begin
  Result:=false;
  if (PropertyNode=nil) or (PropertyNode.Desc<>ctnProperty) then exit;
  MoveCursorToCleanPos(PropertyNode.EndPos);
  ReadPriorAtom;
  if (CurPos.Flag<>cafSemicolon) then exit;
  ReadPriorAtom;
  Result:=UpAtomIs('DEFAULT');
end;

function TPascalReaderTool.PropertyNodeHasParamList(PropNode: TCodeTreeNode
  ): boolean;
begin

  // ToDo: ppu, dcu

  Result:=false;
  if not MoveCursorToPropName(PropNode) then exit;
  ReadNextAtom;
  Result:=(CurPos.Flag=cafEdgedBracketOpen);
end;

function TPascalReaderTool.PropNodeIsTypeLess(PropNode: TCodeTreeNode): boolean;
begin

  // ToDo: ppu, dcu

  Result:=false;
  if PropNode.Desc<>ctnProperty then exit;
  if not MoveCursorToPropName(PropNode) then exit;
  ReadNextAtom; // read colon, skip parameters
  if CurPos.Flag=cafEdgedBracketOpen then begin
    ReadTilBracketClose(true);
    ReadNextAtom;
  end;
  Result:=(CurPos.Flag<>cafColon);
end;

function TPascalReaderTool.PropertyHasSpecifier(PropNode: TCodeTreeNode;
  UpperKeyword: string; ExceptionOnNotFound: boolean): boolean;
// true if cursor is on keyword
begin

  // ToDo: ppu, dcu

  Result:=false;
  if not MoveCursorToPropName(PropNode) then exit;
  if not AtomIsIdentifierE(ExceptionOnNotFound) then exit;
  ReadNextAtom;
  if CurPos.Flag=cafEdgedBracketOpen then begin
    if not ReadTilBracketClose(ExceptionOnNotFound) then exit;
    ReadNextAtom;
  end;
  if CurPos.Flag=cafColon then begin
    // read type
    ReadNextAtom;
    if not AtomIsIdentifierE(ExceptionOnNotFound) then exit;
    ReadNextAtom;
    if CurPos.Flag=cafPoint then begin
      ReadNextAtom;
      if not AtomIsIdentifierE(ExceptionOnNotFound) then exit;
      ReadNextAtom;
    end;
  end;

  UpperKeyword:=UpperCaseStr(UpperKeyword);
  // read specifiers
  while not (CurPos.Flag in [cafSemicolon,cafNone]) do begin
    if WordIsPropertySpecifier.DoIdentifier(@Src[CurPos.StartPos])
    then begin
      if UpAtomIs(UpperKeyword) then exit(true);
    end else if CurPos.Flag=cafEdgedBracketOpen then begin
      if not ReadTilBracketClose(ExceptionOnNotFound) then exit;
      ReadNextAtom;
    end;
    ReadNextAtom;
  end;
  // read modifiers
  while CurPos.Flag=cafSemicolon do begin
    ReadNextAtom;
    if UpAtomIs('DEFAULT') or UpAtomIs('NODEFAULT') or UpAtomIs('DEPRECATED')
    then begin
      if CompareIdentifierPtrs(@Src[CurPos.StartPos],Pointer(UpperKeyword))=0 then exit(true);
    end else if UpAtomIs('ENUMERATOR') then begin
      if CompareIdentifierPtrs(@Src[CurPos.StartPos],Pointer(UpperKeyword))=0 then exit(true);
      ReadNextAtom;
      if not AtomIsIdentifier then exit;
    end else
      exit;
    ReadNextAtom;
  end;
end;

function TPascalReaderTool.ProcNodeHasParamList(ProcNode: TCodeTreeNode): boolean;
begin

  // ToDo: ppu, dcu

  Result:=false;
  if ProcNode=nil then exit;
  if ProcNode.Desc=ctnProcedure then begin
    ProcNode:=ProcNode.FirstChild;
    if ProcNode=nil then exit;
  end;
  if ProcNode.Desc<>ctnProcedureHead then exit;
  if ProcNode.FirstChild<>nil then begin
    Result:=ProcNode.FirstChild.Desc=ctnParameterList;
    exit;
  end;
  MoveCursorBehindProcName(ProcNode);
  Result:=CurPos.Flag=cafRoundBracketOpen;
end;

function TPascalReaderTool.ProcNodeHasOfObject(ProcNode: TCodeTreeNode
  ): boolean;
begin

  // ToDo: ppu, dcu

  Result:=false;
  if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedureType) then exit;
  MoveCursorToFirstProcSpecifier(ProcNode);
  Result:=UpAtomIs('OF') and ReadNextUpAtomIs('OBJECT');
end;

function TPascalReaderTool.GetProcParamList(ProcNode: TCodeTreeNode;
  Parse: boolean): TCodeTreeNode;
begin
  Result:=ProcNode;
  if Result=nil then exit;
  if Result.Desc=ctnProcedure then begin
    Result:=Result.FirstChild;
    if Result=nil then exit;
  end;
  if Result.Desc<>ctnProcedureHead then exit(nil);
  if Parse then
    BuildSubTreeForProcHead(Result);
  Result:=Result.FirstChild;
  if Result=nil then exit;
  if Result.Desc<>ctnParameterList then exit(nil);
end;

procedure TPascalReaderTool.MoveCursorToUsesStart(UsesNode: TCodeTreeNode);
begin
  if (UsesNode=nil)
  or ((UsesNode.Desc<>ctnUsesSection) and (UsesNode.Desc<>ctnContainsSection))
  then
    RaiseException('[TPascalParserTool.MoveCursorToUsesStart] '
      +'internal error: invalid UsesNode');
  // search through the uses section
  MoveCursorToCleanPos(UsesNode.StartPos);
  ReadNextAtom;
  if (not UpAtomIs('USES')) and (not UpAtomIs('CONTAINS')) then
    RaiseExceptionFmt(ctsStrExpectedButAtomFound,['uses',GetAtom]);
  ReadNextAtom;
end;

procedure TPascalReaderTool.MoveCursorToUsesEnd(UsesNode: TCodeTreeNode);
begin
  if (UsesNode=nil)
  or ((UsesNode.Desc<>ctnUsesSection) and (UsesNode.Desc<>ctnContainsSection))
  then
    RaiseException('[TPascalParserTool.MoveCursorToUsesEnd] '
      +'internal error: invalid UsesNode');
  // search backwards through the uses section
  MoveCursorToCleanPos(UsesNode.EndPos);
  ReadPriorAtom; // read ';'
  if not AtomIsChar(';') then
    RaiseExceptionFmt(ctsStrExpectedButAtomFound,[';',GetAtom]);
end;

function TPascalReaderTool.ReadNextUsedUnit(out UnitNameRange,
  InAtom: TAtomPosition; SyntaxExceptions: boolean): boolean;
// after reading CurPos is on atom behind, i.e. comma or semicolon
begin
  Result:=false;
  if not AtomIsIdentifierE(SyntaxExceptions) then exit;
  UnitNameRange:=CurPos;
  repeat
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then break;
    ReadNextAtom;
    if not AtomIsIdentifierE(SyntaxExceptions) then exit;
    UnitNameRange.EndPos:=CurPos.EndPos;
  until false;
  if UpAtomIs('IN') then begin
    ReadNextAtom; // read filename
    if not AtomIsStringConstant then begin
      if not SyntaxExceptions then exit;
      RaiseStrConstExpected;
    end;
    InAtom:=CurPos;
    ReadNextAtom; // read comma or semicolon
  end else begin
    InAtom:=CleanAtomPosition;
  end;
  Result:=true;
end;

procedure TPascalReaderTool.ReadPriorUsedUnit(out UnitNameRange,InAtom: TAtomPosition);
begin
  ReadPriorAtom; // read unitname
  if AtomIsStringConstant then begin
    InAtom:=CurPos;
    ReadPriorAtom; // read 'in'
    if not UpAtomIs('IN') then
      RaiseExceptionFmt(ctsStrExpectedButAtomFound,[ctsKeywordIn,GetAtom]);
    ReadPriorAtom; // read unitname
  end else begin
    InAtom:=CleanAtomPosition;
  end;
  AtomIsIdentifierE;
  UnitNameRange:=CurPos;
  repeat
    ReadPriorAtom;
    if CurPos.Flag<>cafPoint then break;
    ReadPriorAtom;
    AtomIsIdentifierE;
    UnitNameRange.StartPos:=CurPos.StartPos;
  until false;
end;

function TPascalReaderTool.ExtractUsedUnitNameAtCursor(InFilename: PAnsiString): string;
begin
  Result:='';
  if InFilename<>nil then
    InFilename^:='';
  while CurPos.Flag=cafWord do begin
    if Result<>'' then
      Result:=Result+'.';
    Result:=Result+GetAtom;
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then break;
    ReadNextAtom;
  end;
  if UpAtomIs('IN') then begin
    ReadNextAtom;
    if not AtomIsStringConstant then exit;
    if InFilename<>nil then
      InFilename^:=copy(Src,CurPos.StartPos+1,CurPos.EndPos-CurPos.StartPos-2);
    ReadNextAtom;
  end;
end;

function TPascalReaderTool.ExtractUsedUnitName(UseUnitNode: TCodeTreeNode;
  InFilename: PAnsiString): string;
// after reading CurPos is on atom behind, i.e. comma or semicolon
begin
  Result:='';
  if InFilename<>nil then InFilename^:='';
  if (UseUnitNode=nil) or (UseUnitNode.Desc<>ctnUseUnit) then exit;
  MoveCursorToCleanPos(UseUnitNode.StartPos);
  ReadNextAtom;
  Result:=ExtractUsedUnitNameAtCursor(InFilename);
end;

function TPascalReaderTool.ReadAndCompareUsedUnit(const AnUnitName: string): boolean;
// after reading cursor is on atom behind unit name
var
  p: PChar;
begin
  Result:=false;
  if IsDottedIdentifier(AnUnitName) then
    p:=PChar(AnUnitName)
  else
    p:=nil;
  repeat
    if not AtomIsIdentifier then exit;
    if (p<>nil) then begin
      if CompareIdentifiers(p,@Src[CurPos.StartPos])=0 then
        inc(p,CurPos.EndPos-CurPos.StartPos)
      else
        p:=nil;
    end;
    ReadNextAtom;
    if CurPos.Flag<>cafPoint then begin
      // end of unit name
      Result:=(p<>nil) and (p^=#0);
      exit;
    end;
    // dot
    if (p<>nil) then begin
      if p='.' then
        inc(p)
      else
        p:=nil;
    end;
    ReadNextAtom;
  until false;
end;

function TPascalReaderTool.FindCommentInFront(const StartPos: TCodeXYPosition;
  const CommentText: string; InvokeBuildTree, SearchInParentNode,
  WithCommentBounds, CaseSensitive, IgnoreSpaces, CompareOnlyStart: boolean;
  out CommentStart, CommentEnd: TCodeXYPosition): boolean;
var
  CleanCursorPos: integer;
  CommentCleanStart: integer;
  CommentCleanEnd: integer;
begin
  Result:=false;
  if CommentText='' then exit;

  {debugln('TPascalReaderTool.FindCommentInFront A CommentText="',CommentText,'" ',
    ' StartPos=Y='+dbgs(StartPos.Y)+',X='+dbgs(StartPos.X),
    ' InvokeBuildTree='+dbgs(InvokeBuildTree),
    ' SearchInParentNode='+dbgs(SearchInParentNode),
    ' WithCommentBounds='+dbgs(WithCommentBounds),
    ' CaseSensitive='+dbgs(CaseSensitive),
    ' IgnoreSpaces='+dbgs(IgnoreSpaces),
    ' CompareOnlyStart='+dbgs(CompareOnlyStart)); }

  // parse source and find clean positions
  if InvokeBuildTree then
    BuildTreeAndGetCleanPos(StartPos,CleanCursorPos,[])
  else
    if CaretToCleanPos(StartPos,CleanCursorPos)<>0 then
      exit;
  Result:=FindCommentInFront(CleanCursorPos,CommentText,SearchInParentNode,
                  WithCommentBounds,CaseSensitive,IgnoreSpaces,CompareOnlyStart,
                  CommentCleanStart,CommentCleanEnd);
  if not Result then exit;
  Result:=(CommentCleanStart>=1)
          and CleanPosToCaret(CommentCleanStart,CommentStart)
          and CleanPosToCaret(CommentCleanEnd,CommentEnd);
end;

function TPascalReaderTool.FindCommentInFront(StartPos: integer;
  const CommentText: string;
  SearchInParentNode, WithCommentBounds, CaseSensitive,
  IgnoreSpaces, CompareOnlyStart: boolean;
  out CommentStart, CommentEnd: integer): boolean;
// searches a comment in front of StartPos starting with CommentText.
var
  FoundStartPos: integer;
  FoundEndPos: integer;

  procedure CompareComment(CStartPos, CEndPos: integer);
  var
    Found: LongInt;
    CompareStartPos: LongInt;
    CompareEndPos: LongInt;
    CompareLen: Integer;
    CompareCommentLength: Integer;
  begin
    //debugln('CompareComment "',copy(Src,CStartPos,CEndPos-CStartPos),'"');

    CompareStartPos:=CStartPos;
    CompareEndPos:=CEndPos;
    if not WithCommentBounds then begin
      // chomp comment boundaries
      case Src[CompareStartPos] of
      '/','(': inc(CompareStartPos,2);
      '{':
        if (CompareStartPos<SrcLen) and (Src[CompareStartPos+1]=#3) then
          // the codetools skip comment is no real comment
          exit
        else
          inc(CompareStartPos,1);
      end;
      case Src[CompareEndPos-1] of
      '}': dec(CompareEndPos);
      ')': dec(CompareEndPos,2);
      #10,#13:
        begin
          dec(CompareEndPos);
          if (Src[CompareEndPos-1] in [#10,#13])
          and (Src[CompareEndPos-1]<>Src[CompareEndPos]) then
            dec(CompareEndPos);
        end;
      end;
    end;
    if CompareStartPos>CompareEndPos then exit;

    if IgnoreSpaces then begin
      while (CompareStartPos<=CompareEndPos)
      and IsSpaceChar[Src[CompareStartPos]]
      do
        inc(CompareStartPos);
    end;

    CompareCommentLength:=length(CommentText);
    CompareLen:=CompareEndPos-CompareStartPos;
    if CompareOnlyStart and (CompareLen>CompareCommentLength) then
      CompareLen:=CompareCommentLength;

    //debugln('Compare: "',copy(Src,CompareStartPos,CompareEndPos-CompareStartPos),'"',
    //  ' "',CommentText,'"');
    if IgnoreSpaces then begin
      Found:=CompareTextIgnoringSpace(
                          @Src[CompareStartPos],CompareLen,
                          @CommentText[1],length(CommentText),
                          CaseSensitive);
    end else begin
      Found:=CompareText(@Src[CompareStartPos],CompareLen,
                         @CommentText[1],length(CommentText),
                         CaseSensitive);
    end;
    if Found=0 then begin
      FoundStartPos:=CStartPos;
      FoundEndPos:=CEndPos;
    end;
  end;

var
  ANode: TCodeTreeNode;
  p: LongInt;
  CommentStartPos: LongInt;
begin
  Result:=false;
  if StartPos>SrcLen then
    StartPos:=SrcLen+1;
  if CommentText='' then exit;

  {debugln('TPascalReaderTool.FindCommentInFront A CommentText="',CommentText,'" ',
    ' StartPos=Y='+dbgs(StartPos.Y)+',X='+dbgs(StartPos.X),
    ' InvokeBuildTree='+dbgs(InvokeBuildTree),
    ' SearchInParentNode='+dbgs(SearchInParentNode),
    ' WithCommentBounds='+dbgs(WithCommentBounds),
    ' CaseSensitive='+dbgs(CaseSensitive),
    ' IgnoreSpaces='+dbgs(IgnoreSpaces),
    ' CompareOnlyStart='+dbgs(CompareOnlyStart)); }

  // find node
  ANode:=FindDeepestNodeAtPos(StartPos,true);
  if (ANode=nil) then exit;

  { find end of last atom in front of node
    for example:
      uses classes;

      // Comment
      type

    If ANode is the 'type' block, the position after the semicolon is searched
  }

  if SearchInParentNode and (ANode.Parent<>nil) then begin
    // search all siblings in front
    ANode:=ANode.Parent;
    MoveCursorToCleanPos(ANode.Parent.StartPos);
  end else if ANode.PriorBrother<>nil then begin
    // search between prior sibling and this node
    //DebugLn('TPascalReaderTool.FindCommentInFront ANode.Prior=',ANode.Prior.DescAsString);
    MoveCursorToLastNodeAtom(ANode.PriorBrother);
  end else if ANode.Parent<>nil then begin
    // search from start of parent node to this node
    //DebugLn('TPascalReaderTool.FindCommentInFront ANode.Parent=',ANode.Parent.DescAsString);
    MoveCursorToCleanPos(ANode.Parent.StartPos);
  end else begin
    // search in this node
    //DebugLn('TPascalReaderTool.FindCommentInFront Aode=',ANode.DescAsString);
    MoveCursorToCleanPos(ANode.StartPos);
  end;
  p:=CurPos.EndPos;

  //debugln('TPascalReaderTool.FindCommentInFront B Area="',copy(Src,CurPos.StartPos,StartPos-CurPos.StartPos),'"');

  FoundStartPos:=-1;
  repeat
    //debugln('TPascalReaderTool.FindCommentInFront Atom=',GetAtom);
    CommentStartPos:=FindNextComment(Src,p,StartPos);
    if CommentStartPos>=StartPos then break;
    p:=FindCommentEnd(Src,CommentStartPos,Scanner.NestedComments);
    if p>StartPos then break;
    CompareComment(CommentStartPos,p);
  until false;

  Result:=(FoundStartPos>=1);
  CommentStart:=FoundStartPos;
  CommentEnd:=FoundEndPos;
end;

function TPascalReaderTool.GetPasDocComments(const StartPos: TCodeXYPosition;
  InvokeBuildTree: boolean; out ListOfPCodeXYPosition: TFPList): boolean;
var
  CleanCursorPos: integer;
  ANode: TCodeTreeNode;
begin
  ListOfPCodeXYPosition:=nil;
  Result:=false;

  // parse source and find clean positions
  if InvokeBuildTree then
    BuildTreeAndGetCleanPos(StartPos,CleanCursorPos)
  else
    if CaretToCleanPos(StartPos,CleanCursorPos)<>0 then
      exit;

  ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
  Result:=GetPasDocComments(ANode,ListOfPCodeXYPosition);
end;

function TPascalReaderTool.GetPasDocComments(Node: TCodeTreeNode;
  out ListOfPCodeXYPosition: TFPList): boolean;
// Comments are normally in front.
// { Description of TMyClass. }
//  TMyClass = class
//
// Comments can be behind in the same line
// property Color; // description of Color
//
// Comments can be in the following line if started with <
//
// comment starting with $ or % are ignored

  function CommentBelongsToPrior(CommentStart: integer): boolean;
  var
    p: Integer;
  begin
    //DebugLn(['CommentBelongsToPrior Comment=',dbgstr(copy(Src,CommentStart,20))]);
    if (CommentStart<SrcLen) and (Src[CommentStart]='{')
    and (Src[CommentStart+1]='<') then
      Result:=true
    else if (CommentStart+2<=SrcLen) and (Src[CommentStart]='(')
    and (Src[CommentStart+1]='*') and (Src[CommentStart+2]='<') then
      Result:=true
    else if (CommentStart+2<=SrcLen) and (Src[CommentStart]='/')
    and (Src[CommentStart+1]='/') and (Src[CommentStart+2]='<') then
      Result:=true
    else begin
      p:=CommentStart-1;
      while (p>=1) and (Src[p] in [' ',#9]) do dec(p);
      //DebugLn(['CommentBelongsToPrior Code in front: ',dbgstr(copy(Src,p,20))]);
      if (p<1) or (Src[p] in [#10,#13]) then
        Result:=false
      else
        Result:=true; // there is code in the same line in front of the comment
    end;
  end;

  procedure Add(CleanPos: integer);
  var
    CodePos: TCodeXYPosition;
  begin
    if not CleanPosToCaret(CleanPos,CodePos) then exit;
    AddCodePosition(ListOfPCodeXYPosition,CodePos);
  end;

  function Scan(StartPos, EndPos: integer): boolean;
  var
    p: LongInt;
    pp: PChar;
  begin
    // read comments (start in front of node)
    //DebugLn(['TPascalReaderTool.GetPasDocComments Scan Src=',copy(Src,StartPos,EndPos-StartPos)]);
    if EndPos>SrcLen then EndPos:=SrcLen+1;
    p:=FindLineEndOrCodeInFrontOfPosition(StartPos,true);
    while p<EndPos do begin
      p:=FindNextComment(Src,p,EndPos);
      if (p>=EndPos) then break;
      pp:=@Src[p];
      if ((pp^='/') and (pp[1]='/') and (pp[2] in ['$','%']))
      or ((pp^='{') and (pp[1] in ['$','%']))
      or ((pp^='(') and (pp[1]='*') and (pp[2] in ['$','%']))
      then
        break;
      //debugln(['TStandardCodeTool.GetPasDocComments Comment="',copy(Src,p,FindCommentEnd(Src,p,Scanner.NestedComments)-p),'"']);
      if (p<StartPos) then begin
        // comment in front of node
        if not CommentBelongsToPrior(p) then
          Add(p);
      end else if (p<EndPos) then begin
        // comment in the middle or behind
        if CommentBelongsToPrior(p) then
          Add(p);
      end;
      p:=FindCommentEnd(Src,p,Scanner.NestedComments);
    end;
    Result:=true;
  end;

var
  NextNode: TCodeTreeNode;
  EndPos: LongInt;
  TypeNode: TCodeTreeNode;
begin
  ListOfPCodeXYPosition:=nil;
  Result:=false;
  if (Node=nil) then exit;
  if (Node.Desc=ctnProcedureHead)
  and (Node.Parent<>nil) and (Node.Parent.Desc=ctnProcedure) then
    Node:=Node.Parent;

  // add space behind node to scan range
  NextNode:=Node.Next;
  if NextNode<>nil then
    EndPos:=NextNode.StartPos
  else
    EndPos:=Node.EndPos;

  // scan range for comments
  if not Scan(Node.StartPos,EndPos) then exit;

  if Node.Desc in AllIdentifierDefinitions then begin
    // scan behind type
    // for example:   i: integer; // comment
    TypeNode:=FindTypeNodeOfDefinition(Node);
    if TypeNode<>nil then begin
      NextNode:=TypeNode.Next;
      if NextNode<>nil then
        EndPos:=NextNode.StartPos
      else
        EndPos:=Node.EndPos;
      if not Scan(TypeNode.EndPos,EndPos) then exit;
    end;
  end;
  Result:=true;
end;

procedure TPascalReaderTool.CalcMemSize(Stats: TCTMemStats);
begin
  inherited CalcMemSize(Stats);
  Stats.Add('TPascalReaderTool',MemSizeString(CachedSourceName));
end;

end.

