lazarus/components/codetools/finddeclarationtool.pas
mattias fa47319eb9 codetools: debug FindFileAtCursor
git-svn-id: branches/fixes_1_8@56730 -
2017-12-14 18:03:55 +00:00

13904 lines
483 KiB
ObjectPascal

{
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
TFindDeclarationTool enhances the TPascalReaderTool with the ability
to find the source position or code tree node of a declaration.
ToDo:
- high type expression evaluation
(i.e. at the moment: integer+integer=longint
wanted: integer+integer=integer)
- make @Proc context sensitive (started, but not complete)
- operator overloading
- ppu, dcu files
- many things, search for 'ToDo'
}
unit FindDeclarationTool;
{$ifdef FPC}{$mode objfpc}{$endif}{$H+}
interface
{$I codetools.inc}
// activate for debugging:
// mem check
{ $DEFINE MEM_CHECK}
// verbosity
{ $DEFINE CTDEBUG}
{ $DEFINE ShowTriedFiles}
{ $DEFINE ShowTriedContexts}
{ $DEFINE ShowTriedBaseContexts}
{ $DEFINE ShowTriedParentContexts}
{ $DEFINE ShowTriedIdentifiers}
{ $DEFINE ShowTriedUnits}
{ $DEFINE ShowExprEval}
{ $DEFINE ShowForInEval}
{ $DEFINE ShowFoundIdentifier}
{ $DEFINE ShowNodeCache}
{ $DEFINE ShowBaseTypeCache}
{ $DEFINE ShowCacheDependencies}
{ $DEFINE ShowCollect}
{ $DEFINE ShowProcSearch}
{ $DEFINE VerboseFindDeclarationFail}
{ $DEFINE DebugAddToolDependency}
{ $DEFINE VerboseCPS}
{ $DEFINE VerboseFindDeclarationAndOverload}
{ $DEFINE VerboseFindFileAtCursor}
{$IFDEF CTDEBUG}{$DEFINE DebugPrefix}{$ENDIF}
{$IFDEF ShowTriedIdentifiers}{$DEFINE DebugPrefix}{$ENDIF}
{$IFDEF ShowTriedContexts}{$DEFINE DebugPrefix}{$ENDIF}
{$IFDEF ShowExprEval}{$DEFINE ShowForInEval}{$ENDIF}
uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
Classes, SysUtils, Laz_AVL_Tree,
// LazUtils
LazFileUtils, LazUtilities,
// Codetools
CodeToolsStrConsts, CodeTree, CodeAtom, CustomCodeTool,
SourceLog, KeywordFuncLists, BasicCodeTools, LinkScanner, CodeCache,
DirectoryCacher, PascalParserTool, PascalReaderTool, FileProcs,
DefineTemplates, FindDeclarationCache;
type
TFindDeclarationTool = class;
//----------------------------------------------------------------------------
// variable atoms
TVariableAtomType = (
vatNone, // undefined
vatSpace, // empty or space
vatIdentifier, // an identifier
vatPreDefIdentifier, // an identifier with special meaning to the compiler
vatPoint, // .
vatAS, // AS keyword
vatINHERITED, // INHERITED keyword
vatUp, // ^
vatRoundBracketOpen, // (
vatRoundBracketClose,// )
vatEdgedBracketOpen, // [
vatEdgedBracketClose,// ]
vatAddrOp, // @
vatKeyword, // other keywords
vatNumber, // decimal, & octal, % binary, $ hex
vatStringConstant // '' or #
);
const
// for nicer debugging output
VariableAtomTypeNames: array[TVariableAtomType] of string =
('<None>',
'Space',
'Ident',
'PreDefIdent',
'Point',
'AS',
'INHERITED',
'Up^ ',
'Bracket(',
'Bracket)',
'Bracket[',
'Bracket]',
'AddrOperator@ ',
'Keyword',
'Number',
'StringConstant'
);
type
//----------------------------------------------------------------------------
// searchpath delimiter is semicolon
TOnGetSearchPath = function(Sender: TObject): string of object;
TOnGetSrcPathForCompiledUnit =
function(Sender: TObject; const Filename: string): string of object;
//----------------------------------------------------------------------------
TOnGetMethodName = function(const AMethod: TMethod;
CheckOwner: TObject): string of object;
//----------------------------------------------------------------------------
// flags/states for searching
TFindDeclarationFlag = (
fdfSearchInAncestors, // if context is a class, search also in
// ancestors/interfaces
fdfSearchInParentNodes, // if identifier not found in current context,
// proceed in prior nodes on same lvl and parents
fdfIgnoreCurContextNode,// skip context and proceed in prior/parent context
fdfIgnoreUsedUnits, // stay in current source
fdfSearchForward, // instead of searching in prior nodes, search in
// next nodes (successors)
fdfExceptionOnNotFound, // raise exception if identifier not found
// predefined identifiers will not raise
fdfExceptionOnPredefinedIdent,// raise an exception even if the identifier
// is a predefined identifier
fdfIgnoreClassVisibility,//find inaccessible private+protected fields
fdfIgnoreMissingParams, // found proc fits, even if parameters are missing
fdfOnlyCompatibleProc, // incompatible procs are ignored
fdfIgnoreOverloadedProcs,// ignore param lists and take the first proc found
fdfFindVariable, // do not search for the base type of a variable,
// instead return the variable declaration
fdfFunctionResult, // if function is found, return result type
fdfEnumIdentifier, // do not resolve enum to its enum type
fdfFindChildren, // search the class of a 'class of', the interface of a unit
fdfSkipClassForward, // when a class forward was found search the class
fdfCollect, // return every reachable identifier
fdfTopLvlResolving, // set, when searching for an identifier of the
// top lvl variable. Calling DoOnIdentifierFound.
fdfDoNotCache, // result will not be cached
fdfExtractOperand, // operand will be extracted
fdfPropertyResolving, // used with fdfExtractOperand to resolve properties to getters
fdfSearchInHelpers, // search in class/record/type helpers too
fdfSearchInHelpersInTheEnd, // search in helpers after current class (used with inherited call in helper)
fdfTypeType, // do not resolve TMyString = type string;
fdfIgnoreOperatorError // return expression type even if an operator error was found
);
TFindDeclarationFlags = set of TFindDeclarationFlag;
const
// masks to pass flags to sub searches
fdfGlobals = [fdfExceptionOnNotFound, fdfTopLvlResolving,
fdfExtractOperand, fdfPropertyResolving];
fdfGlobalsSameIdent = fdfGlobals+[fdfExceptionOnPredefinedIdent,
fdfIgnoreMissingParams, fdfIgnoreUsedUnits, fdfDoNotCache,
fdfOnlyCompatibleProc, fdfSearchInAncestors, fdfCollect, fdfSearchInHelpers];
// initial flags for searches
fdfDefaultForExpressions = [fdfSearchInParentNodes, fdfSearchInAncestors, fdfSearchInHelpers,
fdfExceptionOnNotFound,fdfIgnoreCurContextNode];
type
// flags/states for result
TFoundDeclarationFlag = (
fodDoNotCache
);
TFoundDeclarationFlags = set of TFoundDeclarationFlag;
//----------------------------------------------------------------------------
type
TFindDeclarationParams = class;
TFindContext = record
Node: TCodeTreeNode;
Tool: TFindDeclarationTool;
end;
PFindContext = ^TFindContext;
const
CleanFindContext: TFindContext = (Node:nil; Tool:nil);
type
//----------------------------------------------------------------------------
{ TExpressionTypeDesc describes predefined types
The Freepascal compiler can automatically convert them
}
TExpressionTypeDesc = (
xtNone, // undefined
xtContext, // a node
xtChar, // char
xtWideChar, // widechar
xtReal, // real
xtSingle, // single
xtDouble, // double
xtExtended, // extended
xtCExtended, // cextended
xtCurrency, // currency
xtComp, // comp
xtInt64, // int64
xtCardinal, // cardinal
xtQWord, // qword
xtBoolean, // boolean
xtByteBool, // bytebool
xtWordBool, // wordbool
xtLongBool, // longbool
xtQWordBool, // qwordbool
xtString, // string
xtAnsiString, // ansistring
xtShortString, // shortstring
xtWideString, // widestring
xtUnicodeString,// unicodestring
xtPChar, // pchar
xtPointer, // pointer
xtFile, // file
xtText, // text
xtConstOrdInteger,// enum, number, integer
xtConstString, // string, string constant, char constant
xtConstReal, // real number
xtConstSet, // [] set
xtConstBoolean,// true, false
xtLongint, // longint
xtLongWord, // longword
xtWord, // word
xtSmallInt, // smallint
xtShortInt, // shortint
xtByte, // byte
xtNativeInt, // depends on compiler and platform
xtNativeUInt, // depends on compiler and platform
xtCompilerFunc,// SUCC, PREC, LOW, HIGH, ORD, LENGTH, COPY (1.1), ...
xtVariant, // variant
xtJSValue, // jsvalue only in Pas2JS, similar to variant
xtNil // nil = pointer, class, procedure, method, ...
);
// Do not define: TExpressionTypeDescs = set of TExpressionTypeDesc;
// There are too many enums, so the set would be big and slow
var
ExpressionTypeDescNames: array[TExpressionTypeDesc] of string = (
'None',
'Context',
'Char',
'WideChar',
'Real',
'Single',
'Double',
'Extended',
'CExtended',
'Currency',
'Comp',
'Int64',
'Cardinal',
'QWord',
'Boolean',
'ByteBool',
'WordBool',
'LongBool',
'QWordBool',
'String',
'AnsiString',
'ShortString',
'WideString',
'UnicodeString',
'PChar',
'Pointer',
'File',
'TextFile',
'ConstOrdInt',
'ConstString',
'ConstReal',
'ConstSet',
'ConstBoolean',
'LongInt',
'LongWord',
'Word',
'SmallInt',
'ShortInt',
'Byte',
'NativeInt',
'NativeUInt',
'CompilerFunc',
'Variant',
'JSValue',
'Nil'
);
const
xtAllTypes = [Low(TExpressionTypeDesc)..High(TExpressionTypeDesc)]-[xtNone];
xtAllPredefinedTypes = xtAllTypes-[xtContext];
xtAllConstTypes = [xtConstOrdInteger,xtConstBoolean,xtConstReal,
xtConstString,xtConstSet,xtCompilerFunc,xtNil];
xtAllIdentTypes = xtAllTypes - xtAllConstTypes;
xtAllIdentPredefinedTypes = xtAllIdentTypes - [xtContext];
xtAllIntegerTypes = [xtInt64, xtQWord, xtConstOrdInteger, xtLongint,
xtLongWord, xtWord, xtCardinal, xtSmallInt, xtShortInt,
xtByte,xtNativeInt,xtNativeUInt];
xtAllBooleanTypes = [xtBoolean, xtByteBool, xtWordBool, xtLongBool,xtQWordBool];
xtAllRealTypes = [xtReal, xtConstReal, xtSingle, xtDouble,
xtExtended, xtCExtended, xtCurrency, xtComp];
xtAllStringTypes = [xtConstString, xtShortString, xtString, xtAnsiString];
xtAllWideStringTypes = [xtConstString, xtWideString, xtUnicodeString];
xtAllPointerTypes = [xtPointer, xtNil];
xtAllTypeHelperTypes = xtAllPredefinedTypes-[xtCompilerFunc,xtVariant,xtJSValue,xtNil];
xtAllStringCompatibleTypes = xtAllStringTypes+[xtChar,xtJSValue];
xtAllWideStringCompatibleTypes = xtAllWideStringTypes+[xtWideChar,xtChar];
xtAllIntegerConvertibles = xtAllIntegerTypes;
xtAllRealConvertibles = xtAllRealTypes+xtAllIntegerTypes;
xtAllStringConvertibles = xtAllStringCompatibleTypes+[xtPChar];
xtAllWideStringConvertibles = xtAllWideStringCompatibleTypes+[xtPChar];
xtAllBooleanConvertibles = xtAllBooleanTypes+[xtConstBoolean];
xtAllPointerConvertibles = xtAllPointerTypes+[xtPChar];
xtAllPas2JSExtraTypes = [xtJSValue,xtNativeInt,xtNativeUInt];
type
{ TExpressionType is used for compatibility check
A compatibility check is done by comparing two TExpressionType
if Desc = xtConstSet, SubDesc contains the type of the set
if Context.Node<>nil, it contains the corresponding codetree node
if Desc = xtPointer then SubDesc contains the type e.g. xtChar
}
TExpressionType = record
Desc: TExpressionTypeDesc;
SubDesc: TExpressionTypeDesc;
Context: TFindContext;
end;
PExpressionType = ^TExpressionType;
const
CleanExpressionType : TExpressionType =
(Desc:xtNone; SubDesc:xtNone; Context:(Node:nil; Tool:nil));
type
//----------------------------------------------------------------------------
// TTypeCompatibility is the result of a compatibility check
TTypeCompatibility = (
tcExact, // exactly same type, can be used for var parameters
tcCompatible, // type can be auto converted, can not be used for var parameters
tcIncompatible // type is incompatible
);
TTypeCompatibilityList = ^TTypeCompatibility;
const
TypeCompatibilityNames: array[TTypeCompatibility] of string = (
'Exact',
'Compatible', // convertable, but not allowed for var params
'Incompatible'
);
type
//----------------------------------------------------------------------------
// TExprTypeList is used for compatibility checks of whole parameter lists
TExprTypeList = class
private
FCapacity: integer;
procedure SetCapacity(const AValue: integer);
protected
procedure Grow;
public
Count: integer;
Items: PExpressionType;
AliasTypes: PFindContext;
procedure Add(const ExprType: TExpressionType);
procedure Add(const ExprType: TExpressionType; const AliasType: TFindContext);
procedure AddFirst(const ExprType: TExpressionType);
property Capacity: integer read FCapacity write SetCapacity;
destructor Destroy; override;
function AsString: string;
function CalcMemSize: PtrUInt;
end;
type
{ TOperand }
TOperand = record
Expr: TExpressionType;
AliasType: TFindContext;
end;
//----------------------------------------------------------------------------
// TTypeAliasOrderList is used for comparing type aliases in binary operators
TTypeAliasItem = class
public
AliasName: string;
Position: Integer;
end;
TTypeAliasOrderList = class
private
FTree: TAVLTree;
public
constructor Create(const AliasNames: array of string);
destructor Destroy; override;
procedure Add(const AliasName: string);
procedure Add(const AliasNames: array of string);
procedure Insert(const AliasName: string; const Pos: Integer);
procedure InsertBefore(const AliasName, BeforeAlias: string);
procedure InsertAfter(const AliasName, AfterAlias: string);
procedure Delete(const Pos: Integer);
procedure Delete(const AliasName: string);
function IndexOf(const AliasName: string): Integer;
function Compare(const AliasName1, AliasName2: string): Integer;
function Compare(const Operand1, Operand2: TOperand;
Tool: TFindDeclarationTool; CleanPos: Integer): TOperand;
end;
function CompareTypeAliasItems(Item1, Item2: Pointer): Integer;
function CompareTypeAliasItemString(AliasName, Item: Pointer): Integer;
type
//----------------------------------------------------------------------------
// TFoundProc is used for comparing overloaded procs
PFoundProc = ^TFoundProc;
TFoundProc = record
// the expression input list, which should fit into the searched proc
ExprInputList: TExprTypeList;
// the best proc found till now
Context: TFindContext;
// if the proc was already compared (CacheValid=true), then some of the
// compatibility check results are cached.
CacheValid: boolean;
ProcCompatibility: TTypeCompatibility;
ParamCompatibilityList: TTypeCompatibilityList;
// each TFindDeclarationParams has a list of PFoundProc
Owner: TObject;
Next, Prior: PFoundProc;
end;
//---------------------------------------------------------------------------
type
TIdentifierFoundResult = (ifrProceedSearch, ifrAbortSearch, ifrSuccess);
const
IdentifierFoundResultNames: array[TIdentifierFoundResult] of string =
('ProceedSearch', 'AbortSearch', 'Success');
type
TOnIdentifierFound = function(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult of object;
TOnFindUsedUnit = function(SrcTool: TFindDeclarationTool;
const TheUnitName, TheUnitInFilename: string): TCodeBuffer of object;
TOnGetCodeToolForBuffer = function(Sender: TObject;
Code: TCodeBuffer; GoToMainCode: boolean): TFindDeclarationTool of object;
TOnGetDirectoryCache = function(const ADirectory: string
): TCTDirectoryCache of object;
TFDHelpersListKind = (
fdhlkDelphiHelper,
fdhlkObjCCategory
);
{ TFDHelpersListItem }
TFDHelpersListItem = class(TObject)
ForExprType: TExpressionType;
HelperContext: TFindContext; // Node.Desc (ctnClassHelper, ctnRecordHelper, ctnTypeHelper) or (ctnObjCCategory)
function CalcMemSize: PtrUInt;
end;
{ TFDHelpersListRec }
TFDHelpersListRec = record
ForExprType: TExpressionType;
HelperContext: TFindContext;
end;
{ TFDHelpersList }
TFDHelpersList = class
private
FKind: TFDHelpersListKind;
FTree: TAVLTree; { tree of TFDHelpersListItem sorted for CompareHelpersList.
Nodes with same key (ForExprType) are chronologically ordered from left to right. }
procedure AddChronologically(Item: TFDHelpersListItem);
public
function AddFromHelperNode(HelperNode: TCodeTreeNode;
Tool: TFindDeclarationTool; Replace: Boolean): TFDHelpersListItem;
procedure AddFromList(const ExtList: TFDHelpersList);
function IterateFromClassNode(ClassNode: TCodeTreeNode;
Tool: TFindDeclarationTool; out HelperContext: TFindContext; out Iterator: TAVLTreeNode): boolean; // returns newest (rightmost)
function GetNext(out HelperContext: TFindContext; var Iterator: TAVLTreeNode): boolean;
function FindFromExprType(const ExprType: TExpressionType): TFindContext; // returns newest (rightmost)
procedure DeleteHelperNode(HelperNode: TCodeTreeNode; Tool: TFindDeclarationTool);
constructor Create(aKind: TFDHelpersListKind);
destructor Destroy; override;
procedure Clear;
function Count: Integer;
function CalcMemSize: PtrUInt;
procedure WriteDebugReport;
property Kind: TFDHelpersListKind read FKind;
property Tree: TAVLTree read FTree;
end;
{ TGenericParams }
TGenericParams = record
ParamValuesTool: TFindDeclarationTool;
SpecializeParamsNode: TCodeTreeNode;
end;
TFindDeclarationInput = record
Flags: TFindDeclarationFlags;
Identifier: PChar;
ContextNode: TCodeTreeNode;
OnIdentifierFound: TOnIdentifierFound;
IdentifierTool: TFindDeclarationTool;
FoundProc: PFoundProc;
end;
{ TFindDeclarationParams
This contains the parameters for find declaration, the result, the hooks
and the memory management for dynamic search data.
It can be re-used. That means, the search parameters can be saved, changed
and restored (load).
The static parameters are stored on the stack, while the dynamic data
(e.g. FoundProc) is stored in a private list (FirstFoundProc).
For speed reasons the find declaration does not use try..finally and that's
why some saved data is not explicitely freed. Therefore the Load method
frees all dynamic data, that was later saved too.
That's why the following code is forbidden:
Save(Data1);
Save(Data2);
Load(Data1); // this will free Data2
Load(Data2);
When searching a procedure, the parameter list must be compared.
The parameter list of the currently best fitting procedure is stored in
FoundProc.
}
TFindDeclarationParams = class(TObject)
private
FFoundProcStackFirst: PFoundProc;//list of all saved PFoundProc
FFoundProcStackLast: PFoundProc;
FExtractedOperand: string;
FHelpers: array[TFDHelpersListKind] of TFDHelpersList;
FFreeHelpers: array[TFDHelpersListKind] of Boolean;
FNeedHelpers: Boolean;
procedure ClearFoundProc;
procedure FreeFoundProc(aFoundProc: PFoundProc; FreeNext: boolean);
procedure RemoveFoundProcFromList(aFoundProc: PFoundProc);
private
procedure SetFoundProc(const ProcContext: TFindContext);
procedure ChangeFoundProc(const ProcContext: TFindContext;
ProcCompatibility: TTypeCompatibility;
ParamCompatibilityList: TTypeCompatibilityList);
private
procedure SetGenericParamValues(SpecializeParamsTool: TFindDeclarationTool;
SpecializeNode: TCodeTreeNode);
function FindGenericParamType: Boolean;
procedure AddOperandPart(aPart: string);
property ExtractedOperand: string read FExtractedOperand;
function IsFoundProcFinal: boolean;
procedure PrettifyResult;
procedure ConvertResultCleanPosToCaretPos;
procedure ClearResult(CopyCacheFlags: boolean);
procedure ClearInput;
public
// input parameters:
Flags: TFindDeclarationFlags;
Identifier: PChar;
StartTool: TFindDeclarationTool;
StartNode: TCodeTreeNode;
Parent: TFindDeclarationParams;
ContextNode: TCodeTreeNode;
OnIdentifierFound: TOnIdentifierFound;
IdentifierTool: TFindDeclarationTool;
FoundProc: PFoundProc;
Data: Pointer;
// global params
OnTopLvlIdentifierFound: TOnIdentifierFound;
GenParams: TGenericParams;
// results:
NewNode: TCodeTreeNode;
NewCleanPos: integer;
NewCodeTool: TFindDeclarationTool;
NewPos: TCodeXYPosition;
NewTopLine: integer;
NewFlags: TFoundDeclarationFlags;
constructor Create(ParentParams: TFindDeclarationParams = nil);
constructor Create(Tool: TFindDeclarationTool; AContextNode: TCodeTreeNode);
destructor Destroy; override;
procedure Clear;
procedure Save(out Input: TFindDeclarationInput);
procedure Load(Input: TFindDeclarationInput; FreeInput: boolean);
procedure SetResult(const AFindContext: TFindContext);
procedure SetResult(ANewCodeTool: TFindDeclarationTool;
ANewNode: TCodeTreeNode);
procedure SetResult(ANewCodeTool: TFindDeclarationTool;
ANewNode: TCodeTreeNode; ANewCleanPos: integer);
procedure SetResult(NodeCacheEntry: PCodeTreeNodeCacheEntry);
procedure SetIdentifier(NewIdentifierTool: TFindDeclarationTool;
NewIdentifier: PChar; NewOnIdentifierFound: TOnIdentifierFound);
procedure WriteDebugReport;
function GetHelpers(HelperKind: TFDHelpersListKind; CreateIfNotExists: boolean = false): TFDHelpersList;
end;
//----------------------------------------------------------------------------
// TFindDeclarationTool is source based and can therefore search for more
// than declarations:
type
TFindSmartFlag = (
fsfIncludeDirective, // search for include file
fsfFindMainDeclaration, // stop if already on a declaration
fsfSearchSourceName, // if searching for a unit name, return the source name node
fsfSkipClassForward // when a forward class was found, jump further to the class
);
TFindSmartFlags = set of TFindSmartFlag;
const
DefaultFindSmartFlags = [fsfIncludeDirective];
DefaultFindSmartHintFlags = DefaultFindSmartFlags+[fsfFindMainDeclaration];
type
TFindSrcStartType = (
fsstIdentifier
);
TFindDeclarationListFlag = (
fdlfWithoutEmptyProperties, // omit properties without type and attributes
fdlfWithoutForwards, // omit foward classes and procedures
fdlfIfStartIsDefinitionStop,// omit overloads when start is a definition
fdlfOneOverloadPerUnit // ignore other overloads of an identifier within the same unit
);
TFindDeclarationListFlags = set of TFindDeclarationListFlag;
TFindOperatorEnumerator = (
foeProcNode, // proc node of operator
foeResultClassNode, // classnode of result type of operator
foeEnumeratorCurrentNode, // function or property with modifier 'enumerator Current'
foeEnumeratorCurrentExprType // expression type of 'enumerator Current'
);
TFindFileAtCursorFlag = (
ffatNone,
ffatUsedUnit,
ffatIncludeFile,
ffatDisabledIncludeFile,
ffatResource,
ffatDisabledResource,
ffatLiteral,
ffatComment,
ffatUnit // unit by name
);
TFindFileAtCursorFlags = set of TFindFileAtCursorFlag;
const
DefaultFindFileAtCursorAllowed = [Low(TFindFileAtCursorFlag)..high(TFindFileAtCursorFlag)];
type
//----------------------------------------------------------------------------
ECodeToolUnitNotFound = class(ECodeToolFileNotFound)
end;
//----------------------------------------------------------------------------
TFindIdentifierInUsesSection_FindMissingFPCUnit = class;
//----------------------------------------------------------------------------
{ TFindDeclarationTool }
TFindDeclarationTool = class(TPascalReaderTool)
private
FAdjustTopLineDueToComment: boolean;
FDirectoryCache: TCTDirectoryCache;
FFindMissingFPCUnits: TFindIdentifierInUsesSection_FindMissingFPCUnit;
FInterfaceIdentifierCache: TInterfaceIdentifierCache;
FInterfaceHelperCache: array[TFDHelpersListKind] of TFDHelpersList;
FOnFindUsedUnit: TOnFindUsedUnit;
FOnGetCodeToolForBuffer: TOnGetCodeToolForBuffer;
FOnGetDirectoryCache: TOnGetDirectoryCache;
FOnGetMethodName: TOnGetMethodname;
FOnGetSrcPathForCompiledUnit: TOnGetSrcPathForCompiledUnit;
FOnGetUnitSourceSearchPath: TOnGetSearchPath;
FFirstNodeCache: TCodeTreeNodeCache;
FOnRescanFPCDirectoryCache: TNotifyEvent;
FRootNodeCache: TCodeTreeNodeCache;
FFirstBaseTypeCache: TBaseTypeCache;
FDependentCodeTools: TAVLTree;// the codetools, that depend on this codetool
FDependsOnCodeTools: TAVLTree;// the codetools, that this codetool depends on
FClearingDependentNodeCaches: boolean;
FCheckingNodeCacheDependencies: boolean;
FSourcesChangeStep, FFilesChangeStep: int64;
FInitValuesChangeStep: integer;
{$IFDEF DebugPrefix}
DebugPrefix: string;
procedure IncPrefix;
procedure DecPrefix;
{$ENDIF}
function FindDeclarationInUsesSection(UsesNode: TCodeTreeNode;
CleanPos: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindUnitFileInUsesSection(UsesNode: TCodeTreeNode;
const AFilename: string): TCodeTreeNode;
function FindUnitFileInAllUsesSections(const AFilename: string;
CheckMain: boolean = true; CheckImplementation: boolean = true): TCodeTreeNode;
function FindEnumInContext(Params: TFindDeclarationParams): boolean;
// sub methods for FindIdentifierInContext
function DoOnIdentifierFound(Params: TFindDeclarationParams;
FoundNode: TCodeTreeNode): TIdentifierFoundResult;
function FindIdentifierInProcContext(ProcContextNode: TCodeTreeNode;
Params: TFindDeclarationParams): TIdentifierFoundResult;
function FindIdentifierInClassOfMethod(ProcContextNode: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
function FindIdentifierInWithVarContext(WithVarNode: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
function FindIdentifierInAncestors(ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams; var IdentFoundResult: TIdentifierFoundResult): boolean;
function FindIdentifierInAncestors(ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
function FindIdentifierInUsesSection(UsesNode: TCodeTreeNode;
Params: TFindDeclarationParams; FindMissingFPCUnits: Boolean): boolean;
function FindIdentifierInHiddenUsedUnits(
Params: TFindDeclarationParams): boolean;
function FindIdentifierInUsedUnit(const AnUnitName: string;
Params: TFindDeclarationParams; ErrorPos: integer): boolean;
function FindIdentifierInTypeOfConstant(VarConstNode: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
protected
WordIsPredefinedIdentifier: TKeyWordFunctionList;
procedure RaiseUsesExpected(id: int64);
procedure RaiseStrConstExpected(id: int64);
protected
// node caches
procedure DoDeleteNodes(StartNode: TCodeTreeNode); override;
function CheckDependsOnNodeCaches(CheckedTools: TAVLTree = nil): boolean;
procedure ClearNodeCaches;
procedure ClearDependentNodeCaches;
procedure ClearDependsOnToolRelationships;
procedure AddToolDependency(DependOnTool: TFindDeclarationTool);
function CreateNewNodeCache(Node: TCodeTreeNode): TCodeTreeNodeCache;
function CreateNewBaseTypeCache(Tool: TFindDeclarationTool;
Node: TCodeTreeNode): TBaseTypeCache;
procedure CreateBaseTypeCaches(NodeStack: PCodeTreeNodeStack;
const Result: TFindContext);
function GetNodeCache(Node: TCodeTreeNode;
CreateIfNotExists: boolean): TCodeTreeNodeCache;
procedure AddResultToNodeCaches(
StartNode, EndNode: TCodeTreeNode; SearchedForward: boolean;
Params: TFindDeclarationParams; SearchRangeFlags: TNodeCacheEntryFlags);
protected
// expressions, operands, variables
function GetCurrentAtomType: TVariableAtomType;
function FindEndOfTerm(StartPos: integer;
ExceptionIfNoVariableStart, WithAsOperator: boolean): integer; // read one operand
function FindStartOfTerm(EndPos: integer; InType: boolean): integer;
function NodeTermInType(Node: TCodeTreeNode): boolean;
function FindExpressionTypeOfTerm(StartPos, EndPos: integer;
Params: TFindDeclarationParams; WithAsOperator: boolean;
AliasType: PFindContext = nil): TExpressionType;
function FindEndOfExpression(StartPos: integer): integer; // read all operands and operators
function ReadOperandTypeAtCursor(Params: TFindDeclarationParams;
MaxEndPos: integer = -1; AliasType: PFindContext = nil): TExpressionType;
function FindExpressionTypeOfPredefinedIdentifier(StartPos: integer;
Params: TFindDeclarationParams; AliasType: PFindContext = nil): TExpressionType;
function FindExpressionTypeOfConstSet(Node: TCodeTreeNode): TExpressionType;
function GetDefaultStringType: TExpressionTypeDesc;
function CalculateBinaryOperator(LeftOperand, RightOperand: TOperand;
BinaryOperator: TAtomPosition;
Params: TFindDeclarationParams): TOperand;
function GetParameterNode(Node: TCodeTreeNode): TCodeTreeNode;
function GetExpressionTypeOfTypeIdentifier(
Params: TFindDeclarationParams): TExpressionType;
function FindTermTypeAsString(TermPos: TAtomPosition;
Params: TFindDeclarationParams; out ExprType: TExpressionType): string;
function FindForInTypeAsString(TermPos: TAtomPosition;
CursorNode: TCodeTreeNode; Params: TFindDeclarationParams;
out ExprType: TExpressionType): string;
function FindEnumeratorOfClass(ClassNode: TCodeTreeNode;
ExceptionOnNotFound: boolean; out ExprType: TExpressionType;
AliasType: PFindContext = nil; ParentParams: TFindDeclarationParams = nil): boolean;
function FindOperatorEnumerator(Node: TCodeTreeNode;
ExprType: TExpressionType; Need: TFindOperatorEnumerator;
out ResultExprType: TExpressionType): boolean;
function FindEnumerationTypeOfSetType(SetTypeNode: TCodeTreeNode;
out Context: TFindContext): boolean;
function FindElementTypeOfArrayType(ArrayNode: TCodeTreeNode;
out ExprType: TExpressionType; AliasType: PFindContext): boolean;
function CheckOperatorEnumerator(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
function CheckModifierEnumeratorCurrent({%H-}Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
function IsTermEdgedBracket(TermPos: TAtomPosition;
out EdgedBracketsStartPos: integer): boolean;
function IsTermNamedPointer(TermPos: TAtomPosition;
out ExprType: TExpressionType): boolean;
function FindSetOfEnumerationType(EnumNode: TCodeTreeNode): TCodeTreeNode;
function FindPointerOfIdentifier(TypeNode: TCodeTreeNode): TCodeTreeNode;
function FindExprTypeAsString(const ExprType: TExpressionType;
TermCleanPos: integer;
AliasType: PFindContext = nil): string;
protected
function CheckSrcIdentifier(Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
function FindDeclarationOfIdentAtParam(
Params: TFindDeclarationParams; out ExprType: TExpressionType): boolean;
function FindDeclarationOfIdentAtParam(
Params: TFindDeclarationParams): boolean;
function IdentifierIsDefined(const IdentAtom: TAtomPosition;
ContextNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
function FindContextNodeAtCursor(
Params: TFindDeclarationParams): TFindContext;
function FindClassOfMethod(ProcNode: TCodeTreeNode;
FindClassContext, ExceptionOnNotFound: boolean): TCodeTreeNode;
function FindClassMember(aClassNode: TCodeTreeNode; Identifier: PChar): TCodeTreeNode;
function FindForwardIdentifier(Params: TFindDeclarationParams;
out IsForward: boolean): boolean;
function FindNonForwardClass(ForwardNode: TCodeTreeNode): TCodeTreeNode;
function FindNonForwardClass(Params: TFindDeclarationParams): boolean;
function FindIdentifierInInterface(AskingTool: TFindDeclarationTool;
Params: TFindDeclarationParams): boolean;
function CompareNodeIdentifier(Node: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
function GetInterfaceNode: TCodeTreeNode;
function CompatibilityList1IsBetter(List1, List2: TTypeCompatibilityList;
ListCount: integer): boolean;
function IsParamExprListCompatibleToNodeList(
FirstTargetParameterNode: TCodeTreeNode;
SourceExprParamList: TExprTypeList; IgnoreMissingParameters: boolean;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList): TTypeCompatibility;
function IsParamNodeListCompatibleToParamNodeList(FirstTargetParameterNode,
FirstSourceParameterNode: TCodeTreeNode;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList): TTypeCompatibility;
function CreateParamExprListFromStatement(StartPos: integer;
Params: TFindDeclarationParams; GetAlias: boolean = false): TExprTypeList;
function ContextIsDescendOf(
const DescendContext, AncestorContext: TFindContext;
Params: TFindDeclarationParams): boolean;
function IsCompatible(TargetNode: TCodeTreeNode;
const ExpressionType: TExpressionType;
Params: TFindDeclarationParams): TTypeCompatibility;
function IsCompatible(TargetType, ExpressionType: TExpressionType;
Params: TFindDeclarationParams): TTypeCompatibility;
function IsBaseCompatible(const TargetType, ExpressionType: TExpressionType;
Params: TFindDeclarationParams): TTypeCompatibility;
function CheckParameterSyntax(StartPos, CleanCursorPos: integer;
out ParameterAtom, ProcNameAtom: TAtomPosition;
out ParameterIndex: integer): boolean;
procedure OnFindUsedUnitIdentifier(Sender: TPascalParserTool;
IdentifierCleanPos: integer; Range: TEPRIRange;
Node: TCodeTreeNode; Data: Pointer; var {%H-}Abort: boolean);
public
constructor Create;
destructor Destroy; override;
procedure ConsistencyCheck; override;
procedure CalcMemSize(Stats: TCTMemStats); override;
procedure BeginParsing(Range: TLinkScannerRange); override;
procedure ValidateToolDependencies; override;
function BuildInterfaceIdentifierCache(ExceptionOnNotUnit: boolean): boolean;
function FindDeclaration(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindMainDeclaration(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindDeclarationOfIdentifier(const CursorPos: TCodeXYPosition;
Identifier: PChar;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindDeclaration(const CursorPos: TCodeXYPosition;
SearchSmartFlags: TFindSmartFlags;
out NewTool: TFindDeclarationTool; out NewNode: TCodeTreeNode;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindDeclaration(const CursorPos: TCodeXYPosition;
SearchSmartFlags: TFindSmartFlags;
out NewExprType: TExpressionType;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindDeclarationInInterface(const Identifier: string;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindDeclarationWithMainUsesSection(const Identifier: string;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
function FindClassMember(aClassNode: TCodeTreeNode;
const Identifier: String; SearchInAncestors: boolean): TFindContext;
function FindDeclarationOfPropertyPath(const PropertyPath: string;
out NewContext: TFindContext; IgnoreTypeLess: boolean = false): boolean;
function FindDeclarationOfPropertyPath(const PropertyPath: string;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IgnoreTypeLess: boolean = false): boolean;
function FindDeclarationNodeInInterface(const Identifier: string;
BuildTheTree: Boolean): TCodeTreeNode;// search for type, const, var, proc, prop
function FindDeclarationNodeInImplementation(Identifier: string;
BuildTheTree: Boolean): TCodeTreeNode;// search for type, const, var, proc, prop
function FindSubDeclaration(Identifier: string; ParentNode: TCodeTreeNode
): TCodeTreeNode; // search for type, const, var, proc, prop
function FindInitializationSection: TCodeTreeNode; deprecated 'Use FindInitializationNode instead.';
function FindMainUsesSection(UseContainsSection: boolean = false): TCodeTreeNode; deprecated 'Use FindMainUsesNode instead.';
function FindImplementationUsesSection: TCodeTreeNode; deprecated 'Use FindImplementationUsesNode instead.';
function FindNameInUsesSection(UsesNode: TCodeTreeNode; const AUnitName: string): TCodeTreeNode;
function FindUnitInUsesSection(UsesNode: TCodeTreeNode; const AnUnitName: string;
out NamePos, InPos: TAtomPosition): boolean;
function FindUnitInAllUsesSections(const AnUnitName: string;
out NamePos, InPos: TAtomPosition): boolean;
function GetUnitNameForUsesSection(TargetTool: TFindDeclarationTool): string;
function GetUnitForUsesSection(TargetTool: TFindDeclarationTool): string; deprecated 'use GetUnitNameForUsesSection instead';
function IsHiddenUsedUnit(TheUnitName: PChar): boolean;
function FindCodeToolForUsedUnit(const AnUnitName, AnUnitInFilename: string;
ExceptionOnNotFound: boolean): TFindDeclarationTool;
function FindUnitSource(const AnUnitName,
AnUnitInFilename: string; ExceptionOnNotFound: boolean;
ErrorPos: integer = 0): TCodeBuffer;
function FindUnitCaseInsensitive(var AnUnitName,
AnUnitInFilename: string): string;
procedure GatherUnitAndSrcPath(var UnitPath, CompleteSrcPath: string);
function SearchUnitInUnitLinks(const TheUnitName: string): string; deprecated;
function SearchUnitInUnitSet(const TheUnitName: string): string;
function GetNameSpaces: string;
function IsIncludeDirectiveAtPos(CleanPos, CleanCodePosInFront: integer;
out IncludeCode: TCodeBuffer): boolean;
function FindFileAtCursor(const CursorPos: TCodeXYPosition;
out Found: TFindFileAtCursorFlag; out FoundFilename: string;
SearchFor: TFindFileAtCursorFlags = DefaultFindFileAtCursorAllowed;
StartPos: PCodeXYPosition = nil): boolean;
function FindSmartHint(const CursorPos: TCodeXYPosition;
Flags: TFindSmartFlags = DefaultFindSmartHintFlags): string;
function GetSmartHint(Node: TCodeTreeNode; XYPos: TCodeXYPosition;
WithPosition: boolean; WithDefinition: boolean = True): string;
function BaseTypeOfNodeHasSubIdents(ANode: TCodeTreeNode): boolean;
function FindBaseTypeOfNode(Params: TFindDeclarationParams;
Node: TCodeTreeNode; AliasType: PFindContext = nil;
NodeStack: PCodeTreeNodeStack = nil): TFindContext;
function ConvertNodeToExpressionType(Node: TCodeTreeNode;
Params: TFindDeclarationParams; AliasType: PFindContext = nil): TExpressionType;
function FindExpressionResultType(Params: TFindDeclarationParams;
StartPos, EndPos: integer; AliasType: PFindContext = nil): TExpressionType;
function FindDeclarationAndOverload(const CursorPos: TCodeXYPosition;
out ListOfPCodeXYPosition: TFPList;
Flags: TFindDeclarationListFlags): boolean;
function FindIdentifierContextsAtStatement(CleanPos: integer;
out IsSubIdentifier: boolean; out ListOfPFindContext: TFPList): boolean;
// ancestors
function FindClassAndAncestors(ClassNode: TCodeTreeNode;
var ListOfPFindContext: TFPList; ExceptionOnNotFound: boolean
): boolean; // without interfaces, recursive
function FindContextClassAndAncestorsAndExtendedClassOfHelper(const CursorPos: TCodeXYPosition;
var ListOfPFindContext: TFPList): boolean; // without interfaces
function FindAncestorOfClass(ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams; FindClassContext: boolean): boolean; // returns false for TObject, IInterface, IUnknown
function FindDefaultAncestorOfClass(ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams; FindClassContext: boolean): boolean; // returns false for TObject, IInterface, IUnknown
function FindAncestorOfClassInheritance(IdentifierNode: TCodeTreeNode;
ResultParams: TFindDeclarationParams; FindClassContext: boolean): boolean;
function FindAncestorsOfClass(ClassNode: TCodeTreeNode;
var ListOfPFindContext: TFPList;
Params: TFindDeclarationParams; FindClassContext: boolean;
ExceptionOnNotFound: boolean = true): boolean; // with interfaces, not recursive
function FindExtendedExprOfHelper(HelperNode: TCodeTreeNode): TExpressionType;
function FindReferences(const CursorPos: TCodeXYPosition;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean;
function FindUnitReferences(UnitCode: TCodeBuffer;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean; // searches unitname of UnitCode
procedure FindUsedUnitReferences(const CursorPos: TCodeXYPosition;
SkipComments: boolean; out UsedUnitFilename: string;
out ListOfPCodeXYPosition: TFPList); // searches all references of unit in uses clause
procedure FindUsedUnitReferences(TargetTool: TFindDeclarationTool;
SkipComments: boolean;
out ListOfPCodeXYPosition: TFPList); // searches all references of TargetTool
function CleanPosIsDeclarationIdentifier(CleanPos: integer;
Node: TCodeTreeNode): boolean;
procedure FindHelpersInContext(Params: TFindDeclarationParams);
procedure FindHelpersInUsesSection(UsesNode: TCodeTreeNode;
Params: TFindDeclarationParams);
procedure FindHelpersInInterface(AskingTool: TFindDeclarationTool;
Params: TFindDeclarationParams);
function FindIdentifierInContext(Params: TFindDeclarationParams;
var IdentFoundResult: TIdentifierFoundResult): boolean;
function FindIdentifierInContext(Params: TFindDeclarationParams): boolean;
function FindIdentifierInBasicTypeHelpers(ExprType: TExpressionTypeDesc;
Params: TFindDeclarationParams): Boolean;
function FindNthParameterNode(Node: TCodeTreeNode;
ParameterIndex: integer): TCodeTreeNode;
function GetFirstParameterNode(Node: TCodeTreeNode): TCodeTreeNode;
function IsParamNodeListCompatibleToExprList(
TargetExprParamList: TExprTypeList;
FirstSourceParameterNode: TCodeTreeNode;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList = nil): TTypeCompatibility;
function CreateParamExprListFromProcNode(ProcNode: TCodeTreeNode;
Params: TFindDeclarationParams): TExprTypeList;
function JumpToNode(ANode: TCodeTreeNode;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IgnoreJumpCentered: boolean): boolean;
function JumpToCleanPos(NewCleanPos, NewTopLineCleanPos,
NewBottomLineCleanPos: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IgnoreJumpCentered: boolean): boolean;
function NodeIsForwardDeclaration(Node: TCodeTreeNode): boolean;
function GetExpandedOperand(const CursorPos: TCodeXYPosition;
out Operand: string; ResolveProperty: Boolean): Boolean;
property InterfaceIdentifierCache: TInterfaceIdentifierCache
read FInterfaceIdentifierCache;
property OnGetUnitSourceSearchPath: TOnGetSearchPath
read FOnGetUnitSourceSearchPath write FOnGetUnitSourceSearchPath;
property OnFindUsedUnit: TOnFindUsedUnit
read FOnFindUsedUnit write FOnFindUsedUnit;
property OnGetCodeToolForBuffer: TOnGetCodeToolForBuffer
read FOnGetCodeToolForBuffer write FOnGetCodeToolForBuffer;
property OnGetDirectoryCache: TOnGetDirectoryCache read FOnGetDirectoryCache
write FOnGetDirectoryCache;
property OnGetSrcPathForCompiledUnit: TOnGetSrcPathForCompiledUnit
read FOnGetSrcPathForCompiledUnit write fOnGetSrcPathForCompiledUnit;
property OnGetMethodName: TOnGetMethodname read FOnGetMethodName
write FOnGetMethodName;
property AdjustTopLineDueToComment: boolean
read FAdjustTopLineDueToComment write FAdjustTopLineDueToComment;
property DirectoryCache: TCTDirectoryCache read FDirectoryCache write FDirectoryCache;
property OnRescanFPCDirectoryCache: TNotifyEvent read FOnRescanFPCDirectoryCache write FOnRescanFPCDirectoryCache;
end;
TFindIdentifierInUsesSection_FindMissingFPCUnit = class
private
FUnitName: string;
FFound: Boolean;
FResults: TStringList;
procedure Iterate(const AFilename: string);
public
constructor Create;
destructor Destroy; override;
function Find(const AUnitName: string; const ADirectoryCache: TCTDirectoryCache): Boolean;
function IsInResults(const AUnitName: string): Boolean;
end;
function ExprTypeToString(const ExprType: TExpressionType): string;
function CreateExpressionType(const Desc, SubDesc: TExpressionTypeDesc;
const Context: TFindContext): TExpressionType;
function FindContextToString(const FindContext: TFindContext; RelativeFilename: boolean = true): string; overload;
function FindContextToString(const FindContext: PFindContext; RelativeFilename: boolean = true): string; overload;
function CreateFindContext(NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode): TFindContext;
function CreateFindContext(Params: TFindDeclarationParams): TFindContext;
function CreateFindContext(BaseTypeCache: TBaseTypeCache): TFindContext;
function FindContextAreEqual(const Context1, Context2: TFindContext): boolean;
function CompareFindContexts(const Context1, Context2: PFindContext): integer;
procedure AddFindContext(var ListOfPFindContext: TFPList;
const NewContext: TFindContext);
function IndexOfFindContext(var ListOfPFindContext: TFPList;
const AContext: PFindContext): integer;
procedure FreeListOfPFindContext(var ListOfPFindContext: TFPList);
function ListOfPFindContextToStr(const ListOfPFindContext: TFPList): string;
function dbgsFC(const Context: TFindContext): string;
function PredefinedIdentToExprTypeDesc(Identifier: PChar; Compiler: TPascalCompiler): TExpressionTypeDesc;
function dbgs(const Flags: TFindDeclarationFlags): string; overload;
function dbgs(const Flags: TFoundDeclarationFlags): string; overload;
function dbgs(const vat: TVariableAtomType): string; overload;
function dbgs(const Kind: TFDHelpersListKind): string; overload;
function BooleanTypesOrderList: TTypeAliasOrderList;
function IntegerTypesOrderList: TTypeAliasOrderList;
function RealTypesOrderList: TTypeAliasOrderList;
function StringTypesOrderList: TTypeAliasOrderList;
implementation
var
FBooleanTypesOrderList: TTypeAliasOrderList;
FIntegerTypesOrderList: TTypeAliasOrderList;
FRealTypesOrderList: TTypeAliasOrderList;
FStringTypesOrderList: TTypeAliasOrderList;
type
{ TFindUsedUnitReferences }
TFindUsedUnitReferences = class
public
TargetTool: TFindDeclarationTool;
TargetUnitName: string;
ListOfPCodeXYPosition: TFPList;
Params: TFindDeclarationParams;
constructor Create(Tool: TFindDeclarationTool; AContextNode: TCodeTreeNode);
destructor Destroy; override;
end;
function dbgs(const Flags: TFindDeclarationFlags): string;
var
Flag: TFindDeclarationFlag;
s: string;
begin
Result:='';
for Flag:=Low(TFindDeclarationFlag) to High(TFindDeclarationFlag) do begin
if Flag in Flags then begin
if Result<>'' then
Result:=Result+', ';
WriteStr(s, Flag);
Result:=Result+s;
end;
end;
end;
function dbgs(const Flags: TFoundDeclarationFlags): string;
var
Flag: TFoundDeclarationFlag;
s: string;
begin
Result:='';
for Flag:=Low(TFoundDeclarationFlag) to High(TFoundDeclarationFlag) do begin
if Flag in Flags then begin
if Result<>'' then
Result:=Result+', ';
WriteStr(s, Flag);
Result:=Result+s;
end;
end;
end;
function dbgs(const vat: TVariableAtomType): string;
begin
Result:=VariableAtomTypeNames[vat];
end;
function dbgs(const Kind: TFDHelpersListKind): string;
begin
WriteStr(Result, Kind);
end;
function BooleanTypesOrderList: TTypeAliasOrderList;
begin
if FBooleanTypesOrderList=nil then
FBooleanTypesOrderList:=TTypeAliasOrderList.Create([
'LongBool', 'WordBool', 'Boolean', 'ByteBool']);
Result := FBooleanTypesOrderList;
end;
function IntegerTypesOrderList: TTypeAliasOrderList;
begin
if FIntegerTypesOrderList=nil then
FIntegerTypesOrderList:=TTypeAliasOrderList.Create([
'Int64', 'QWord',
'NativeInt', 'IntPtr', 'SizeInt', 'NativeUInt', 'UIntPtr',
'Int32', 'Integer', 'LongInt', 'UInt32', 'Cardinal', 'LongWord',
'Int16', 'SmallInt', 'UInt16', 'Word',
'Int8', 'ShortInt', 'UInt8', 'Byte']);
Result := FIntegerTypesOrderList;
end;
function RealTypesOrderList: TTypeAliasOrderList;
begin
if FRealTypesOrderList=nil then
FRealTypesOrderList:=TTypeAliasOrderList.Create([
'Extended', 'Real', 'Double', 'Single']);
Result := FRealTypesOrderList;
end;
function StringTypesOrderList: TTypeAliasOrderList;
begin
if FStringTypesOrderList=nil then
FStringTypesOrderList:=TTypeAliasOrderList.Create([
'string', 'AnsiString', 'WideString', 'ShortString', 'Char', 'WideChar', 'AnsiChar']);
Result := FStringTypesOrderList;
end;
function ListOfPFindContextToStr(const ListOfPFindContext: TFPList): string;
var
Context: TFindContext;
i: Integer;
begin
if ListOfPFindContext=nil then
Result:='nil'
else begin
Result:='';
for i:=0 to ListOfPFindContext.Count-1 do begin
Context:=PFindContext(ListOfPFindContext[i])^;
Result:=Result+' '+DbgsFC(Context)+LineEnding;
end;
end;
end;
function dbgsFC(const Context: TFindContext): string;
var
CursorPos: TCodeXYPosition;
begin
if Context.Tool=nil then
Result:='nil'
else begin
Result:=Context.Tool.MainFilename;
if Context.Node=nil then
Result:=Result+'()'
else begin
Context.Tool.CleanPosToCaret(Context.Node.StartPos,CursorPos);
Result:=Result+'(y='+dbgs(CursorPos.Y)+',x='+dbgs(CursorPos.X)+')';
end;
end;
end;
function PredefinedIdentToExprTypeDesc(Identifier: PChar;
Compiler: TPascalCompiler): TExpressionTypeDesc;
begin
// predefined identifiers
if CompareIdentifiers(Identifier,'NIL')=0 then
Result:=xtNil
else if CompareIdentifiers(Identifier,'POINTER')=0 then
Result:=xtPointer
else if (CompareIdentifiers(Identifier,'TRUE')=0)
or (CompareIdentifiers(Identifier,'FALSE')=0) then
Result:=xtConstBoolean
else if CompareIdentifiers(Identifier,'STRING')=0 then
Result:=xtString
else if CompareIdentifiers(Identifier,'SHORTSTRING')=0 then
Result:=xtShortString
else if CompareIdentifiers(Identifier,'ANSISTRING')=0 then
Result:=xtAnsiString
else if CompareIdentifiers(Identifier,'WIDESTRING')=0 then
Result:=xtWideString
else if CompareIdentifiers(Identifier,'UNICODESTRING')=0 then
Result:=xtUnicodeString
else if CompareIdentifiers(Identifier,'INT64')=0 then
Result:=xtInt64
else if CompareIdentifiers(Identifier,'CARDINAL')=0 then
Result:=xtCardinal
else if CompareIdentifiers(Identifier,'QWORD')=0 then
Result:=xtQWord
else if CompareIdentifiers(Identifier,'BOOLEAN')=0 then
Result:=xtBoolean
else if CompareIdentifiers(Identifier,'BYTEBOOL')=0 then
Result:=xtByteBool
else if CompareIdentifiers(Identifier,'WORDBOOL')=0 then
Result:=xtWordBool
else if CompareIdentifiers(Identifier,'LONGBOOL')=0 then
Result:=xtLongBool
else if CompareIdentifiers(Identifier,'QWORDBOOL')=0 then
Result:=xtQWordBool
else if CompareIdentifiers(Identifier,'CHAR')=0 then
Result:=xtChar
else if CompareIdentifiers(Identifier,'WIDECHAR')=0 then
Result:=xtWideChar
else if CompareIdentifiers(Identifier,'REAL')=0 then
Result:=xtReal
else if CompareIdentifiers(Identifier,'SINGLE')=0 then
Result:=xtSingle
else if CompareIdentifiers(Identifier,'DOUBLE')=0 then
Result:=xtDouble
else if CompareIdentifiers(Identifier,'EXTENDED')=0 then
Result:=xtExtended
else if CompareIdentifiers(Identifier,'CEXTENDED')=0 then
Result:=xtCExtended
else if CompareIdentifiers(Identifier,'COMP')=0 then
Result:=xtComp
else if CompareIdentifiers(Identifier,'FILE')=0 then
Result:=xtFile
else if CompareIdentifiers(Identifier,'TEXT')=0 then
Result:=xtText
else if CompareIdentifiers(Identifier,'SIZEOF')=0 then
Result:=xtConstOrdInteger
else if CompareIdentifiers(Identifier,'ORD')=0 then
Result:=xtConstOrdInteger
else if CompareIdentifiers(Identifier,'ASSIGNED')=0 then
Result:=xtConstBoolean
else if CompareIdentifiers(Identifier,'VARIANT')=0 then
Result:=xtVariant
else if CompareIdentifiers(Identifier,'CURRENCY')=0 then
Result:=xtCurrency
else if CompareIdentifiers(Identifier,'LONGINT')=0 then
Result:=xtLongInt
else if CompareIdentifiers(Identifier,'LONGWORD')=0 then
Result:=xtLongWord
else if CompareIdentifiers(Identifier,'WORD')=0 then
Result:=xtWord
else if CompareIdentifiers(Identifier,'LONGWORD')=0 then
Result:=xtCardinal
else if CompareIdentifiers(Identifier,'SMALLINT')=0 then
Result:=xtSmallInt
else if CompareIdentifiers(Identifier,'SHORTINT')=0 then
Result:=xtShortInt
else if CompareIdentifiers(Identifier,'BYTE')=0 then
Result:=xtByte
else if CompareIdentifiers(Identifier,'PCHAR')=0 then
Result:=xtPChar
else if IsWordBuiltInFunc.DoItCaseInsensitive(Identifier) then
Result:=xtCompilerFunc
else begin
// compiler specific
if (Compiler=pcPas2js) then begin
if CompareIdentifiers(Identifier,'JSVALUE')=0 then
exit(xtJSValue);
if CompareIdentifiers(Identifier,'NATIVEINT')=0 then
exit(xtNativeInt);
if CompareIdentifiers(Identifier,'NATIVEUINT')=0 then
exit(xtNativeUInt);
end;
if (Compiler=pcDelphi) then begin
if CompareIdentifiers(Identifier,'NATIVEINT')=0 then
exit(xtNativeInt);
if CompareIdentifiers(Identifier,'NATIVEUINT')=0 then
exit(xtNativeUInt);
end;
Result:=xtNone;
end;
end;
function CompareTypeAliasItems(Item1, Item2: Pointer): Integer;
var
xItem1: TTypeAliasItem absolute Item1;
xItem2: TTypeAliasItem absolute Item2;
begin
Result := CompareIdentifiers(PChar(xItem1.AliasName), PChar(xItem2.AliasName));
end;
function CompareTypeAliasItemString(AliasName, Item: Pointer): Integer;
var
xAliasName: PChar absolute AliasName;
xItem: TTypeAliasItem absolute Item;
begin
Result := CompareIdentifiers(xAliasName, PChar(xItem.AliasName));
end;
function ExprTypeToString(const ExprType: TExpressionType): string;
begin
Result:='Desc='+ExpressionTypeDescNames[ExprType.Desc]
+' SubDesc='+ExpressionTypeDescNames[ExprType.SubDesc]
+' '+FindContextToString(ExprType.Context);
end;
function CreateExpressionType(const Desc, SubDesc: TExpressionTypeDesc;
const Context: TFindContext): TExpressionType;
begin
Result.Desc:=Desc;
Result.SubDesc:=SubDesc;
Result.Context:=Context;
end;
{ TFindContext }
function FindContextToString(const FindContext: TFindContext;
RelativeFilename: boolean): string;
var
IdentNode: TCodeTreeNode;
begin
Result:='';
if FindContext.Node<>nil then begin
Result:=Result+'Node="'+FindContext.Node.DescAsString+'"';
IdentNode:=FindContext.Node;
while IdentNode<>nil do begin
if IdentNode.Desc in AllSimpleIdentifierDefinitions
+[ctnIdentifier,ctnEnumIdentifier,ctnLabel]
then begin
Result:=Result+' Ident="'+
FindContext.Tool.ExtractIdentifier(IdentNode.StartPos)+'"';
break;
end else if IdentNode.Desc=ctnGenericType then begin
if IdentNode.FirstChild<>nil then
Result:=Result+' Generic="'+
FindContext.Tool.ExtractIdentifier(IdentNode.FirstChild.StartPos)+'"'
else
Result:=Result+' Generic=?';
end else if IdentNode.Desc in [ctnProperty,ctnGlobalProperty] then begin
Result:=Result+' PropName="'+
FindContext.Tool.ExtractPropName(IdentNode,false)+'"';
break;
end else if IdentNode.Desc=ctnProcedure then begin
Result:=Result+' Proc="'+FindContext.Tool.ExtractProcName(IdentNode,[])+'"';
break;
end;
IdentNode:=IdentNode.Parent;
end;
if RelativeFilename then
Result:=Result+' at "'+FindContext.Tool.CleanPosToStr(FindContext.Node.StartPos,true)+'"'
else
Result:=Result+' at "'+FindContext.Tool.CleanPosToRelativeStr(FindContext.Node.StartPos,'')+'"'
end else
Result:='nil';
end;
function FindContextToString(const FindContext: PFindContext;
RelativeFilename: boolean): string;
begin
if FindContext=nil then
Result:='-'
else
Result:=FindContextToString(FindContext^,RelativeFilename);
end;
function CreateFindContext(NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode): TFindContext;
begin
Result.Node:=NewNode;
Result.Tool:=NewTool;
end;
function CreateFindContext(Params: TFindDeclarationParams): TFindContext;
begin
Result.Node:=Params.NewNode;
Result.Tool:=TFindDeclarationTool(Params.NewCodeTool);
end;
function CreateFindContext(BaseTypeCache: TBaseTypeCache): TFindContext;
begin
Result.Node:=BaseTypeCache.BaseNode;
Result.Tool:=TFindDeclarationTool(BaseTypeCache.BaseTool);
end;
function FindContextAreEqual(const Context1, Context2: TFindContext): boolean;
begin
Result:=(Context1.Tool=Context2.Tool) and (Context1.Node=Context2.Node);
end;
function CompareFindContexts(const Context1, Context2: PFindContext): integer;
begin
if Pointer(Context1^.Tool)>Pointer(Context2^.Tool) then
Result:=1
else if Pointer(Context1^.Tool)<Pointer(Context2^.Tool) then
Result:=-1
else if Pointer(Context1^.Node)>Pointer(Context2^.Node) then
Result:=1
else if Pointer(Context1^.Node)<Pointer(Context2^.Node) then
Result:=-1
else
Result:=0;
end;
procedure AddFindContext(var ListOfPFindContext: TFPList;
const NewContext: TFindContext);
var
AddContext: PFindContext;
begin
if ListOfPFindContext=nil then ListOfPFindContext:=TFPList.Create;
New(AddContext);
AddContext^:=NewContext;
ListOfPFindContext.Add(AddContext);
end;
function IndexOfFindContext(var ListOfPFindContext: TFPList;
const AContext: PFindContext): integer;
begin
if ListOfPFindContext=nil then
Result:=-1
else begin
Result:=ListOfPFindContext.Count-1;
while (Result>=0)
and (CompareFindContexts(AContext,
PFindContext(ListOfPFindContext[Result]))<>0)
do
dec(Result);
end;
end;
procedure FreeListOfPFindContext(var ListOfPFindContext: TFPList);
var
CurContext: PFindContext;
i: Integer;
begin
if ListOfPFindContext=nil then exit;
for i:=0 to ListOfPFindContext.Count-1 do begin
CurContext:=PFindContext(ListOfPFindContext[i]);
Dispose(CurContext);
end;
ListOfPFindContext.Free;
ListOfPFindContext:=nil;
end;
{ TFindIdentifierInUsesSection_FindMissingFPCUnit }
constructor TFindIdentifierInUsesSection_FindMissingFPCUnit.Create;
begin
inherited;
FResults := TStringList.Create;
FResults.CaseSensitive := True;
FResults.Duplicates := dupIgnore;
FResults.Sorted := True;
end;
destructor TFindIdentifierInUsesSection_FindMissingFPCUnit.Destroy;
begin
FResults.Free;
inherited Destroy;
end;
function TFindIdentifierInUsesSection_FindMissingFPCUnit.Find(
const AUnitName: string; const ADirectoryCache: TCTDirectoryCache): Boolean;
var
IRes: Integer;
begin
IRes := FResults.IndexOf(AUnitName);
if IRes>=0 then
Exit(Boolean(PtrInt(FResults.Objects[IRes])));
FUnitName := AUnitName;
ADirectoryCache.IterateFPCUnitsInSet(@Iterate);
Result := FFound;
FResults.AddObject(AUnitName, TObject(PtrInt(Result)));
end;
function TFindIdentifierInUsesSection_FindMissingFPCUnit.IsInResults(
const AUnitName: string): Boolean;
begin
Result := FResults.IndexOf(AUnitName)>=0;
end;
procedure TFindIdentifierInUsesSection_FindMissingFPCUnit.Iterate(
const AFilename: string);
begin
FFound := FFound or SameFileName(FUnitName, ExtractFileNameOnly(AFilename));
end;
{ TTypeAliasOrderList }
constructor TTypeAliasOrderList.Create(const AliasNames: array of string);
begin
inherited Create;
FTree := TAVLTree.Create(@CompareTypeAliasItems);
Add(AliasNames);
end;
procedure TTypeAliasOrderList.Add(const AliasNames: array of string);
var
AliasName: string;
begin
for AliasName in AliasNames do
Add(AliasName);
end;
procedure TTypeAliasOrderList.Add(const AliasName: string);
var
NewItem: TTypeAliasItem;
begin
if IndexOf(AliasName) > -1 then Exit;
NewItem := TTypeAliasItem.Create;
NewItem.AliasName := AliasName;
NewItem.Position := FTree.Count;
FTree.Add(NewItem);
end;
function TTypeAliasOrderList.Compare(const AliasName1, AliasName2: string
): Integer;
var
xAliasIndex1, xAliasIndex2: Integer;
begin
xAliasIndex1 := IndexOf(AliasName1);
xAliasIndex2 := IndexOf(AliasName2);
if (xAliasIndex1=-1) and (xAliasIndex2=-1) then
Exit(0)
else if (xAliasIndex2=-1) then
Exit(-1)
else if (xAliasIndex1=-1) then
Exit(1)
else
Result := xAliasIndex1-xAliasIndex2;
end;
function TTypeAliasOrderList.Compare(const Operand1,
Operand2: TOperand; Tool: TFindDeclarationTool; CleanPos: Integer
): TOperand;
var
xCompRes: Integer;
begin
// first check if one of the operands is a constant -> if yes, automatically
// return the other
// (x := f + 1; should return always type of f)
if (Operand1.Expr.Desc in xtAllConstTypes) and not (Operand2.Expr.Desc in xtAllConstTypes) then
Exit(Operand2)
else
if (Operand2.Expr.Desc in xtAllConstTypes) and not (Operand1.Expr.Desc in xtAllConstTypes) then
Exit(Operand1);
// then compare base types
xCompRes := Compare(
Tool.FindExprTypeAsString(Operand1.Expr, CleanPos, nil),
Tool.FindExprTypeAsString(Operand2.Expr, CleanPos, nil));
// if base types are same, compare aliases
if xCompRes = 0 then
xCompRes := Compare(
Tool.FindExprTypeAsString(Operand1.Expr, CleanPos, @Operand1.AliasType),
Tool.FindExprTypeAsString(Operand2.Expr, CleanPos, @Operand2.AliasType));
if xCompRes > 0 then
Result := Operand2
else
Result := Operand1;
end;
procedure TTypeAliasOrderList.Delete(const Pos: Integer);
var
xAVItem, xDelItem: TAVLTreeNode;
xItem: TTypeAliasItem;
begin
xDelItem := nil;
for xAVItem in FTree do
begin
xItem := TTypeAliasItem(xAVItem.Data);
if xItem.Position = Pos then
xDelItem := xAVItem
else if xItem.Position > Pos then
Dec(xItem.Position);
end;
if xDelItem<>nil then
FTree.FreeAndDelete(xDelItem);
end;
procedure TTypeAliasOrderList.Delete(const AliasName: string);
var
xIndex: Integer;
begin
xIndex := IndexOf(AliasName);
if xIndex<0 then Exit;
Delete(xIndex);
end;
destructor TTypeAliasOrderList.Destroy;
begin
FTree.FreeAndClear;
FTree.Free;
inherited Destroy;
end;
function TTypeAliasOrderList.IndexOf(const AliasName: string): Integer;
var
xAVNode: TAVLTreeNode;
begin
xAVNode := FTree.FindKey(PChar(AliasName), @CompareTypeAliasItemString);
if xAVNode<>nil then
Result := TTypeAliasItem(xAVNode.Data).Position
else
Result := -1;
end;
procedure TTypeAliasOrderList.Insert(const AliasName: string; const Pos: Integer
);
var
xAVItem: TAVLTreeNode;
xItem, NewItem: TTypeAliasItem;
begin
for xAVItem in FTree do
begin
xItem := TTypeAliasItem(xAVItem.Data);
if xItem.Position >= Pos then
Inc(xItem.Position);
end;
NewItem := TTypeAliasItem.Create;
NewItem.AliasName := AliasName;
NewItem.Position := Pos;
FTree.Add(NewItem);
end;
procedure TTypeAliasOrderList.InsertAfter(const AliasName, AfterAlias: string);
var
xIndex: Integer;
begin
if IndexOf(AliasName) = -1 then
begin
xIndex := IndexOf(AfterAlias);
if xIndex >= 0 then
Insert(AliasName, xIndex+1)
else
Add(AliasName);
end;
end;
procedure TTypeAliasOrderList.InsertBefore(const AliasName, BeforeAlias: string
);
var
xIndex: Integer;
begin
if IndexOf(AliasName) = -1 then
begin
xIndex := IndexOf(BeforeAlias);
if xIndex >= 0 then
Insert(AliasName, xIndex)
else
Add(AliasName);
end;
end;
{ TFDHelpersListItem }
function TFDHelpersListItem.CalcMemSize: PtrUInt;
begin
Result := InstanceSize;
end;
{ TFDHelpersList }
function CompareHelpersList(Item1, Item2: Pointer): Integer;
var
I1: TFDHelpersListItem absolute Item1;
I2: TFDHelpersListItem absolute Item2;
begin
Result := ord(I1.ForExprType.Desc)-ord(I2.ForExprType.Desc);
if Result<>0 then exit;
Result := ComparePointers(I1.ForExprType.Context.Node, I2.ForExprType.Context.Node);
end;
function CompareHelpersListExprType(Item1, Item2: Pointer): Integer;
var
I1: PExpressionType absolute Item1;
I2: TFDHelpersListItem absolute Item2;
begin
Result := ord(I1^.Desc)-ord(I2.ForExprType.Desc);
if Result<>0 then exit;
Result := ComparePointers(I1^.Context.Node, I2.ForExprType.Context.Node);
end;
procedure TFDHelpersList.AddFromList(const ExtList: TFDHelpersList);
function CopyNode(ANode: TAVLTreeNode): TFDHelpersListItem;
var
FromNode: TFDHelpersListItem;
begin
FromNode := TFDHelpersListItem(ANode.Data);
if Kind=fdhlkDelphiHelper then
if FTree.FindKey(FromNode, @CompareHelpersList) <> nil then
Exit(nil); //FPC & Delphi don't support duplicate class helpers!
Result := TFDHelpersListItem.Create;
Result.HelperContext := FromNode.HelperContext;
Result.ForExprType := FromNode.ForExprType;
AddChronologically(Result);
end;
var
Node: TAVLTreeNode;
begin
for Node in ExtList.FTree do
CopyNode(Node);
end;
function TFDHelpersList.CalcMemSize: PtrUInt;
var
Node: TAVLTreeNode;
begin
Result:=PtrUInt(InstanceSize)+PtrUInt(FTree.InstanceSize);
for Node in FTree do
Inc(Result, TFDHelpersListItem(Node.Data).CalcMemSize);
end;
procedure TFDHelpersList.WriteDebugReport;
var
Node: TAVLTreeNode;
Item: TFDHelpersListItem;
begin
debugln(['TFDHelpersList.WriteDebugReport ',dbgs(Kind),' Count=',FTree.Count]);
Node:=FTree.FindLowest;
while Node<>nil do begin
Item:=TFDHelpersListItem(Node.Data);
debugln([' ForExprType=[',ExprTypeToString(Item.ForExprType),']',
' Helper=[',FindContextToString(Item.HelperContext),']']);
Node:=FTree.FindSuccessor(Node);
end;
end;
procedure TFDHelpersList.AddChronologically(Item: TFDHelpersListItem);
begin
with Item.ForExprType.Context do begin
// Note: ObjCCategory allows multiple helpers for a class (here: ForExprType)
// => there can be multiple items with the same key in the tree which
// must be chronologically sorted
// -> append the new item rightmost by slightly increasing the key
Node:=TCodeTreeNode(Pointer(Node)-SizeOf(Pointer));
FTree.Add(Item);
Node:=TCodeTreeNode(Pointer(Node)+SizeOf(Pointer));
end;
end;
function TFDHelpersList.AddFromHelperNode(HelperNode: TCodeTreeNode;
Tool: TFindDeclarationTool; Replace: Boolean): TFDHelpersListItem;
var
OldKey: TAVLTreeNode;
ExprType: TExpressionType;
begin
//debugln(['TFDHelpersList.AddFromHelperNode Start ',Tool.CleanPosToStr(HelperNode.StartPos,true),' ',Tool.ExtractCode(HelperNode.StartPos,HelperNode.StartPos+20,[])]);
ExprType:=Tool.FindExtendedExprOfHelper(HelperNode);
//debugln(['TFDHelpersList.AddFromHelperNode ExprType=',ExprTypeToString(ExprType)]);
if ExprType.Desc in xtAllIdentTypes then
begin
if Kind=fdhlkDelphiHelper then begin
// class/type/record helpers only allow one helper per class
OldKey := FTree.FindKey(@ExprType, @CompareHelpersListExprType);
if OldKey <> nil then
begin
Result:=TFDHelpersListItem(OldKey.Data);
if Replace then begin
// keep AVLNode, it may be in use by the iterator of SearchInHelpers
Result.HelperContext.Node := HelperNode;
Result.HelperContext.Tool := Tool;
end;
exit;
end;
end;
Result := TFDHelpersListItem.Create;
Result.ForExprType := ExprType;
Result.HelperContext.Node := HelperNode;
Result.HelperContext.Tool := Tool;
AddChronologically(Result);
end else
Result := nil;
end;
procedure TFDHelpersList.Clear;
begin
FTree.FreeAndClear;
end;
function TFDHelpersList.Count: Integer;
begin
Result := FTree.Count;
end;
constructor TFDHelpersList.Create(aKind: TFDHelpersListKind);
begin
inherited Create;
FKind:=aKind;
FTree:=TAVLTree.Create(@CompareHelpersList);
end;
procedure TFDHelpersList.DeleteHelperNode(HelperNode: TCodeTreeNode;
Tool: TFindDeclarationTool);
var
OldKey: TAVLTreeNode;
ExprType: TExpressionType;
begin
ExprType:=Tool.FindExtendedExprOfHelper(HelperNode);
if ExprType.Desc in xtAllIdentTypes then
begin
OldKey := FTree.FindKey(@ExprType, @CompareHelpersListExprType);
if OldKey <> nil then
FTree.FreeAndDelete(OldKey);
end;
end;
destructor TFDHelpersList.Destroy;
begin
Clear;
FTree.Free;
inherited Destroy;
end;
function TFDHelpersList.IterateFromClassNode(ClassNode: TCodeTreeNode;
Tool: TFindDeclarationTool; out HelperContext: TFindContext; out
Iterator: TAVLTreeNode): boolean;
var
ExprType: TExpressionType;
begin
ExprType.Desc:=xtContext;
ExprType.Context.Node:=ClassNode;
ExprType.Context.Tool:=Tool;
Iterator := FTree.FindRightMostKey(@ExprType, @CompareHelpersListExprType);
if Iterator=nil then exit(false);
HelperContext:=TFDHelpersListItem(Iterator.Data).HelperContext;
Result:=true;
end;
function TFDHelpersList.GetNext(out HelperContext: TFindContext;
var Iterator: TAVLTreeNode): boolean;
var
NextNode: TAVLTreeNode;
begin
NextNode:=FTree.FindPrecessor(Iterator);
if (NextNode=nil) or (CompareHelpersList(NextNode.Data,Iterator.Data)<>0) then
exit(false);
// found an older compatible helper
Iterator:=NextNode;
HelperContext:=TFDHelpersListItem(Iterator.Data).HelperContext;
Result:=true;
end;
function TFDHelpersList.FindFromExprType(const ExprType: TExpressionType
): TFindContext;
var
Node: TAVLTreeNode;
begin
Node := FTree.FindRightMostKey(@ExprType, @CompareHelpersListExprType);
if Node<>nil then
Result := TFDHelpersListItem(Node.Data).HelperContext
else
Result := CleanFindContext;
end;
constructor TFindUsedUnitReferences.Create(Tool: TFindDeclarationTool; AContextNode: TCodeTreeNode);
begin
inherited Create;
Params:=TFindDeclarationParams.Create(Tool, AContextNode);
end;
destructor TFindUsedUnitReferences.Destroy;
begin
FreeAndNil(Params);
inherited Destroy;
end;
{ TFindDeclarationTool }
function TFindDeclarationTool.FindDeclaration(const CursorPos: TCodeXYPosition;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var
NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode;
begin
Result:=FindDeclaration(CursorPos,DefaultFindSmartFlags,NewTool,NewNode,
NewPos,NewTopLine);
end;
function TFindDeclarationTool.FindMainDeclaration(
const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition;
out NewTopLine: integer): boolean;
var
NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode;
begin
Result:=FindDeclaration(CursorPos,[fsfFindMainDeclaration],NewTool,NewNode,
NewPos,NewTopLine);
end;
function TFindDeclarationTool.FindDeclarationOfIdentifier(
const CursorPos: TCodeXYPosition; Identifier: PChar;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var
CleanCursorPos: integer;
CursorNode: TCodeTreeNode;
Params: TFindDeclarationParams;
begin
Result:=false;
ActivateGlobalWriteLock;
Params:=nil;
try
// build code tree
{$IFDEF CTDEBUG}
DebugLn('TFindDeclarationTool.FindDeclarationOfIdentifier A CursorPos=X',dbgs(CursorPos.X),',Y',dbgs(CursorPos.Y));
{$ENDIF}
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
{$IFDEF CTDEBUG}
DebugLn('TFindDeclarationTool.FindDeclarationOfIdentifier B CleanCursorPos=',dbgs(CleanCursorPos));
{$ENDIF}
// find CodeTreeNode at cursor
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
// search
Params:=TFindDeclarationParams.Create(Self, CursorNode);
Params.SetIdentifier(Self,Identifier,nil);
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound,
fdfExceptionOnPredefinedIdent,
fdfTopLvlResolving,fdfSearchInAncestors,fdfSearchInHelpers,
fdfIgnoreCurContextNode];
FindIdentifierInContext(Params);
// convert result to nice source position
Params.PrettifyResult;
Params.ConvertResultCleanPosToCaretPos;
NewPos:=Params.NewPos;
NewTopLine:=Params.NewTopLine;
Result:=true;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.FindDeclaration(const CursorPos: TCodeXYPosition;
SearchSmartFlags: TFindSmartFlags; out NewExprType: TExpressionType; out
NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var
CleanCursorPos: integer;
CursorNode, ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams;
DirectSearch, SkipChecks, SearchForward: boolean;
function CheckIfNodeIsForwardDefinedClass(ANode: TCodeTreeNode;
ATool: TFindDeclarationTool): Boolean;
var
TypeNode: TCodeTreeNode;
begin
Result := False;
if not (ANode.Desc in [ctnTypeDefinition,ctnGenericType]) then exit;
TypeNode:=ATool.FindTypeNodeOfDefinition(ANode);
if (TypeNode<>nil)
and (TypeNode.Desc in AllClasses)
and ((TypeNode.SubDesc and ctnsForwardDeclaration)>0)
then
Result := True;
end;
procedure CheckIfCursorOnAForwardDefinedClass;
begin
if SkipChecks then exit;
if CheckIfNodeIsForwardDefinedClass(CursorNode, Self) then
begin
DirectSearch:=true;
SearchForward:=true;
SkipChecks:=true;
end;
end;
procedure CheckIfCursorInClassNode;
begin
if SkipChecks then exit;
ClassNode:=CursorNode;
while (ClassNode<>nil)
and (not (ClassNode.Desc in AllClasses))
do
ClassNode:=ClassNode.Parent;
if ClassNode=nil then exit;
// cursor is in class/object/class interface definition
if (ClassNode.SubDesc and ctnsForwardDeclaration)>0 then exit;
// parse class and build CodeTreeNodes for all properties/methods
CursorNode:=FindDeepestNodeAtPos(ClassNode,CleanCursorPos,true);
if CursorNode.GetNodeOfType(ctnClassInheritance)=nil then exit;
// identifier is an ancestor/interface identifier
CursorNode:=ClassNode.Parent;
DirectSearch:=true;
SkipChecks:=true;
end;
procedure CheckIfCursorInProcNode;
var IsMethod: boolean;
begin
if SkipChecks then exit;
if CursorNode.Desc=ctnProcedureHead then
CursorNode:=CursorNode.Parent;
if CursorNode.Desc<>ctnProcedure then exit;
BuildSubTreeForProcHead(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CursorNode,CleanCursorPos,true);
// check if cursor on proc name
if (CursorNode.Desc=ctnProcedureHead)
and (CleanCursorPos>=CursorNode.StartPos) then begin
MoveCursorToNodeStart(CursorNode);
ReadNextAtom;
IsMethod:=false;
if AtomIsIdentifier then begin
ReadNextAtom;
if AtomIsChar('.') then begin
ReadNextAtom;
ReadNextAtom;
IsMethod:=true;
end;
end;
if (CurPos.StartPos>CleanCursorPos) and (not IsMethod) then begin
// cursor on proc name
// -> ignore proc name and search overloaded identifier
DirectSearch:=true;
SkipChecks:=true;
end;
end;
if CursorNode.Desc=ctnProcedureHead then
CursorNode:=CursorNode.Parent;
end;
procedure CheckIfCursorInPropertyNode;
begin
if SkipChecks then exit;
if not (CursorNode.Desc in [ctnProperty,ctnGlobalProperty]) then exit;
MoveCursorToNodeStart(CursorNode);
if (CursorNode.Desc=ctnProperty) then begin
ReadNextAtom; // read 'property'
if UpAtomIs('CLASS') then ReadNextAtom;
end;
ReadNextAtom; // read property name
if CleanCursorPos<CurPos.EndPos then begin
DirectSearch:=true;
SkipChecks:=true;
end;
end;
function FindSourceName(ACode: TCodeBuffer): boolean;
var
NamePos: TAtomPosition;
begin
Result:=false;
NewExprType :=CleanExpressionType;
if Assigned(FOnGetCodeToolForBuffer) then
NewExprType.Context.Tool:=FOnGetCodeToolForBuffer(Self,ACode,false);
if NewExprType.Context.Tool=nil then exit;
NewExprType.Context.Tool.BuildTree(lsrSourceName);
if not NewExprType.Context.Tool.GetSourceNamePos(NamePos) then exit;
NewExprType.Context.Node:=NewExprType.Context.Tool.Tree.Root;
if not NewExprType.Context.Tool.JumpToCleanPos(NamePos.StartPos,NamePos.StartPos,
NamePos.StartPos,NewPos,NewTopLine,false)
then exit;
Result:=true;
NewExprType.Desc:=xtContext;
end;
{$IFDEF VerboseFindDeclarationFail}
procedure WriteFailReport;
var
CodePos: integer;
LinkIndex: Integer;
Link: TSourceLink;
i: Integer;
SrcCodes: TAVLTree;
SrcNode: TAVLTreeNode;
begin
debugln(['TFindDeclarationTool.FindDeclaration failed',
' CursorPos=X=',CursorPos.X,',Y=',CursorPos.Y,
',File=',CursorPos.Code.Filename,
',LineCount=',CursorPos.Code.LineCount]);
if CursorPos.Y<=CursorPos.Code.LineCount then
debugln([' Line="',dbgstr(CursorPos.Code.GetLine(CursorPos.Y-1),1,CursorPos.X-1),'|',dbgstr(CursorPos.Code.GetLine(CursorPos.Y-1),CursorPos.X,1000),'"']);
if CleanCursorPos>0 then begin
debugln([ ' CleanCursorPos=',CleanCursorPos,' CleanCode="',dbgstr(Src,CleanCursorPos-40,40),'|',dbgstr(Src,CleanCursorPos,30),'"']);
end;
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,CodePos);
LinkIndex:=Scanner.LinkIndexAtCursorPos(CodePos,CursorPos.Code);
dbgout([' CodePos=',CodePos,' LinkIndex=',LinkIndex]);
if LinkIndex>=0 then begin
Link:=Scanner.Links[LinkIndex];
dbgout([',CleanedPos=',Link.CleanedPos,',Size=',Scanner.LinkSize(LinkIndex),',SrcPos=',Link.SrcPos,',Kind=',dbgs(Link.Kind),',CodeSame=',Link.Code=Pointer(CursorPos.Code)]);
end else begin
dbgout([' LinkCount=',Scanner.LinkCount]);
i:=0;
while (i<Scanner.LinkCount-1) do begin
Link:=Scanner.Links[i];
if Link.Code=Pointer(CursorPos.Code) then begin
if LinkIndex<0 then
dbgout([', First Link of Code: ID=',i,',CleanedPos=',Link.CleanedPos,',Size=',Scanner.LinkSize(i),',SrcPos=',Link.SrcPos,',Kind=',dbgs(Link.Kind)]);
LinkIndex:=i;
end;
inc(i);
end;
if LinkIndex>=0 then begin
Link:=Scanner.Links[LinkIndex];
dbgout([', Last Link of Code: ID=',LinkIndex,',CleanedPos=',Link.CleanedPos,',Size=',Scanner.LinkSize(i),',SrcPos=',Link.SrcPos,',Kind=',dbgs(Link.Kind)]);
end else begin
SrcCodes:=Scanner.CreateTreeOfSourceCodes;
try
for SrcNode in SrcCodes do begin
dbgout(',LinkFile="',TCodeBuffer(SrcNode.Data).Filename,'"');
end;
finally
SrcCodes.Free;
end;
end;
end;
debugln;
end;
{$ENDIF}
var
IdentStartPos: Integer;
function TrySkipClassForward: Boolean;
var
ForwardXY, NewSkipPos: TCodeXYPosition;
NewSkipExprType: TExpressionType;
NewSkipTopLine, NewSkipCleanPos: integer;
begin
// if we skip forward class definitions and we found one -> proceed search!
Result :=
(fsfSkipClassForward in SearchSmartFlags)
and CheckIfNodeIsForwardDefinedClass(Params.NewNode, Params.NewCodeTool)
and Params.NewCodeTool.CleanPosToCaret(Params.NewNode.StartPos, ForwardXY)
and Params.NewCodeTool.FindDeclaration(ForwardXY, SearchSmartFlags-[fsfSkipClassForward],
NewSkipExprType, NewSkipPos, NewSkipTopLine);
if Result
and (NewSkipExprType.Desc=xtContext)
and (NewSkipExprType.Context.Tool=Self)
and (NewSkipExprType.Context.Tool.CaretToCleanPos(NewSkipPos, NewSkipCleanPos)=0)
and (IdentStartPos = GetIdentStartPosition(Src,NewSkipCleanPos))
then begin
// the old startpos and the skipclass startpos are the same -> we want to
// jump to the forward declaration because we jump from the actual one
Result := False;
end;
if Result then
begin
NewExprType := NewSkipExprType;
NewPos := NewSkipPos;
NewTopLine := NewSkipTopLine;
end;
end;
var
CleanPosInFront: integer;
CursorAtIdentifier: boolean;
IdentifierStart: PChar;
LineRange: TLineRange;
begin
Result:=false;
NewExprType:=CleanExpressionType;
NewPos.X:=-1;
NewPos.Y:=-1;
SkipChecks:=false;
// check cursor in source
if (CursorPos.Y<1) or (CursorPos.Y>CursorPos.Code.LineCount)
or (CursorPos.X<1) then begin
{$IFDEF VerboseFindDeclarationFail}
debugln(['TFindDeclarationTool.FindDeclaration invalid CursorPos=X=',CursorPos.X,' Y=',CursorPos.Y,' File=',CursorPos.Code.Filename,' LineCount=',CursorPos.Code.LineCount]);
{$ENDIF}
exit;
end;
CursorPos.Code.GetLineRange(CursorPos.Y-1,LineRange);
if LineRange.EndPos-LineRange.StartPos+1<CursorPos.X then begin
// beyond end of line
{$IFDEF VerboseFindDeclarationFail}
debugln(['TFindDeclarationTool.FindDeclaration beyond end of line: CursorPos=X=',CursorPos.X,' Y=',CursorPos.Y,' File=',CursorPos.Code.Filename,' LineLen=',LineRange.EndPos-LineRange.StartPos]);
{$ENDIF}
exit;
end;
CleanCursorPos:=0;
ActivateGlobalWriteLock;
try
// build code tree
{$IFDEF CTDEBUG}
DebugLn('TFindDeclarationTool.FindDeclaration A CursorPos=X',dbgs(CursorPos.X),',Y',dbgs(CursorPos.Y),' ',CursorPos.Code.Filename);
debugln(['TFindDeclarationTool.FindDeclaration B ',dbgtext(copy(CursorPos.Code.GetLine(CursorPos.Y),1,CursorPos.X-1)),'|',dbgtext(copy(CursorPos.Code.GetLine(CursorPos.Y),CursorPos.X,120))]);
{$ENDIF}
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos,btCursorPosOutAllowed]);
{$IFDEF CTDEBUG}
debugLn('TFindDeclarationTool.FindDeclaration B CleanCursorPos=',dbgs(CleanCursorPos));
debugln(['TFindDeclarationTool.FindDeclaration C ',dbgtext(copy(Src,CleanCursorPos-30,30)),'|',dbgtext(copy(Src,CleanCursorPos,30))]);
{$ENDIF}
// find CodeTreeNode at cursor
if (Tree.Root<>nil) and (Tree.Root.StartPos<=CleanCursorPos) then begin
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true);
if (fsfFindMainDeclaration in SearchSmartFlags)
and CleanPosIsDeclarationIdentifier(CleanCursorPos,CursorNode)
then begin
//DebugLn(['TFindDeclarationTool.FindDeclaration CleanPosIsDeclarationIdentifier']);
NewExprType.Desc:=xtContext;
NewExprType.Context.Tool:=Self;
NewExprType.Context.Node:=CursorNode;
CleanCursorPos:=GetIdentStartPosition(Src,CleanCursorPos);
if CursorNode.Desc=ctnVarDefinition then begin
// if this is a parameter, try to find the corresponding declaration
NewExprType.Context.Node:=FindCorrespondingProcParamNode(NewExprType.Context.Node);
if (NewExprType.Context.Node<>nil) and (NewExprType.Context.Node.StartPos<CursorNode.StartPos) then
CleanCursorPos:=NewExprType.Context.Node.StartPos
else
NewExprType.Context.Node:=CursorNode;
end;
if (CursorNode.Desc=ctnProcedureHead)
and (NodeIsMethodBody(CursorNode.Parent)) then begin
// if this is a procedure body, try to find the corresponding declaration
NewExprType.Context.Node:=FindCorrespondingProcNode(CursorNode.Parent);
if (NewExprType.Context.Node<>nil) and (NewExprType.Context.Node.Desc=ctnProcedure) then
NewExprType.Context.Node:=NewExprType.Context.Node.FirstChild;
if (NewExprType.Context.Node<>nil) and (NewExprType.Context.Node.StartPos<CursorNode.StartPos) then begin
CleanCursorPos:=NewExprType.Context.Node.StartPos;
end
else
NewExprType.Context.Node:=CursorNode;
end;
Result:=JumpToCleanPos(CleanCursorPos,CleanCursorPos,CleanCursorPos,
NewPos,NewTopLine,false);
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration cursor at declaration, but JumpToCleanPos failed']);
end;
{$ENDIF}
exit;
end;
CleanPosInFront:=CursorNode.StartPos;
end else begin
CleanPosInFront:=1;
CursorNode:=nil;
end;
if IsIncludeDirectiveAtPos(CleanCursorPos,CleanPosInFront,NewPos.Code)
then begin
// include directive
//DebugLn(['TFindDeclarationTool.FindDeclaration IsIncludeDirectiveAtPos']);
NewPos.X:=1;
NewPos.Y:=1;
NewTopLine:=1;
NewExprType.Desc:=xtContext;
NewExprType.Context.Node:=nil;
NewExprType.Context.Tool:=Self;
Result:=(fsfIncludeDirective in SearchSmartFlags);
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration cursor at include directive and fsfIncludeDirective not set']);
end;
{$ENDIF}
exit;
end;
if CursorNode=nil then begin
// raise exception
RaiseCursorOutsideCode(CursorPos);
end;
{$IFDEF CTDEBUG}
DebugLn('TFindDeclarationTool.FindDeclaration D CursorNode=',NodeDescriptionAsString(CursorNode.Desc),' HasChildren=',dbgs(CursorNode.FirstChild<>nil));
{$ENDIF}
if (CursorNode.Desc = ctnUseUnitNamespace) then begin
NewExprType.Desc:=xtContext;
NewExprType.Context.Node:=CursorNode;
NewExprType.Context.Tool:=Self;
CleanPosToCaret(CursorNode.StartPos, NewPos);
NewTopLine := NewPos.Y;
Result := True;
Exit;
end else
if (CursorNode.Desc in [ctnUsesSection,ctnUseUnitClearName]) then begin
// in uses section
//DebugLn(['TFindDeclarationTool.FindDeclaration IsUsesSection']);
Result:=FindDeclarationInUsesSection(CursorNode,CleanCursorPos,
NewPos,NewTopLine);
NewExprType:=CleanExpressionType;
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration cursor in uses and FindDeclarationInUsesSection failed']);
end;
{$ENDIF}
if Result and (fsfSearchSourceName in SearchSmartFlags) then begin
Result:=FindSourceName(NewPos.Code);
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration cursor in uses and FindSourceName failed']);
end;
{$ENDIF}
end;
exit;
end;
DirectSearch:=false;
SearchForward:=false;
CheckIfCursorOnAForwardDefinedClass;
CheckIfCursorInClassNode;
CheckIfCursorInProcNode;
CheckIfCursorInPropertyNode;
// set cursor on identifier
MoveCursorToCleanPos(CleanCursorPos);
GetIdentStartEndAtPosition(Src,CleanCursorPos,
CurPos.StartPos,CurPos.EndPos);
IdentStartPos:=CurPos.StartPos;
CursorAtIdentifier:=CurPos.StartPos<CurPos.EndPos;
if CursorAtIdentifier then
IdentifierStart:=@Src[CurPos.StartPos]
else
IdentifierStart:=PChar(Src);
if CursorAtIdentifier then begin
// find declaration of identifier
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
Params.SetIdentifier(Self,IdentifierStart,@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound,
fdfExceptionOnPredefinedIdent,
fdfTopLvlResolving,fdfSearchInAncestors,fdfSearchInHelpers];
if fsfSkipClassForward in SearchSmartFlags then
Include(Params.Flags,fdfSkipClassForward);
if not DirectSearch then begin
Result:=FindDeclarationOfIdentAtParam(Params, NewExprType);
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration FindDeclarationOfIdentAtParam failed']);
end;
{$ENDIF}
if Result and TrySkipClassForward then
Exit(True);
end else begin
Include(Params.Flags,fdfIgnoreCurContextNode);
if SearchForward then
Include(Params.Flags,fdfSearchForward);
//debugln(['TFindDeclarationTool.FindDeclaration Flags=',dbgs(Params.Flags),' FindIdentifierInContext ...']);
Result:=FindIdentifierInContext(Params);
if Result then
begin
if TrySkipClassForward then
Exit(True);
NewExprType.Desc:=xtContext;
NewExprType.Context.Node:=Params.NewNode;
NewExprType.Context.Tool:=Params.NewCodeTool;
end;
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration FindIdentifierInContext failed']);
end;
{$ENDIF}
end;
if Result then begin
Params.PrettifyResult;
Params.ConvertResultCleanPosToCaretPos;
NewPos:=Params.NewPos;
NewTopLine:=Params.NewTopLine;
if (NewExprType.Desc=xtContext) and
((NewPos.Code=nil) or (NewExprType.Context.Node=nil))
then begin
if Params.IdentifierTool.IsPCharInSrc(Params.Identifier) then
Params.IdentifierTool.MoveCursorToCleanPos(Params.Identifier)
else
MoveCursorToCleanPos(CleanCursorPos);
Params.IdentifierTool.RaiseExceptionFmt(20170421200024,ctsIdentifierNotFound,
[GetIdentifier(Params.Identifier)]);
end;
end;
finally
Params.Free;
end;
end else begin
// find declaration of non identifier, e.g. numeric label
{$IFDEF VerboseFindDeclarationFail}
if not Result then begin
debugln(['TFindDeclarationTool.FindDeclaration cursor at non identifier']);
end;
{$ENDIF}
end;
finally
ClearIgnoreErrorAfter;
DeactivateGlobalWriteLock;
{$IFDEF VerboseFindDeclarationFail}
WriteFailReport;
{$ENDIF}
end;
end;
function TFindDeclarationTool.FindDeclaration(const CursorPos: TCodeXYPosition;
SearchSmartFlags: TFindSmartFlags; out NewTool: TFindDeclarationTool; out
NewNode: TCodeTreeNode; out NewPos: TCodeXYPosition; out NewTopLine: integer
): boolean;
var
ExprType: TExpressionType;
begin
Result := FindDeclaration(CursorPos, SearchSmartFlags, ExprType, NewPos, NewTopLine) and
(NewPos.X >= 0) and (NewPos.Y >= 0);
if Result then begin
NewTool := ExprType.Context.Tool;
NewNode := ExprType.Context.Node;
end else begin
NewTool := nil;
NewNode := nil;
end;
end;
function TFindDeclarationTool.FindDeclarationInInterface(
const Identifier: string; out NewPos: TCodeXYPosition; out NewTopLine: integer
): boolean;
var
Node: TCodeTreeNode;
begin
Result:=false;
if Identifier='' then exit;
Node:=FindDeclarationNodeInInterface(Identifier,true);
if Node<>nil then
Result:=JumpToNode(Node,NewPos,NewTopLine,false);
end;
function TFindDeclarationTool.FindDeclarationWithMainUsesSection(
const Identifier: string; out NewPos: TCodeXYPosition; out NewTopLine: integer
): boolean;
var
UsesNode: TCodeTreeNode;
Params: TFindDeclarationParams;
begin
Result:=false;
if Identifier='' then exit;
BuildTree(lsrMainUsesSectionEnd);
UsesNode:=FindMainUsesNode;
if UsesNode=nil then exit;
Params:=TFindDeclarationParams.Create(Self, FindLastNode);
ActivateGlobalWriteLock;
try
Params.Flags:=[fdfExceptionOnNotFound];
Params.SetIdentifier(Self,PChar(Pointer(Identifier)),nil);
if FindIdentifierInUsesSection(UsesNode,Params,True) then begin
if Params.NewNode=nil then exit;
Result:=Params.NewCodeTool.JumpToNode(Params.NewNode,NewPos,
NewTopLine,false);
end;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.FindDeclarationOfPropertyPath(
const PropertyPath: string; out NewContext: TFindContext;
IgnoreTypeLess: boolean): boolean;
// example: PropertyPath='TForm1.Font.Color'
var
StartPos: Integer;
function GetNextIdentifier: string;
var
EndPos: LongInt;
begin
EndPos:=StartPos;
while (EndPos<=length(PropertyPath)) and (IsIdentChar[PropertyPath[EndPos]])
do inc(EndPos);
if (EndPos<=length(PropertyPath)) and (PropertyPath[EndPos]<>'.') then
Result:=''
else begin
Result:=copy(PropertyPath,StartPos,EndPos-StartPos);
StartPos:=EndPos+1;
end;
end;
var
Params: TFindDeclarationParams;
Identifier: String;
IsLastProperty: Boolean;
Context: TFindContext;
IsTypeLess: Boolean;
Node: TCodeTreeNode;
begin
Result:=false;
NewContext:=CleanFindContext;
//DebugLn('TFindDeclarationTool.FindDeclarationOfPropertyPath ',MainFilename,' PropertyPath="',PropertyPath,'"');
if PropertyPath='' then exit;
ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Self, FindLastNode);
try
BuildTree(lsrInitializationStart);
//DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath ',Src]);
// first search the class/variable in the interface
StartPos:=1;
Identifier:=GetNextIdentifier;
if Identifier='' then exit;
Context.Tool:=Self;
Context.Node:=FindDeclarationNodeInInterface(Identifier,true);
if Context.Node=nil then begin
DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath Identifier not found in interface ',Identifier]);
exit;
end;
Context:=FindBaseTypeOfNode(Params,Context.Node);
if Context.Node=nil then begin
DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath context not found']);
exit;
end;
// then search the properties
repeat
Identifier:=GetNextIdentifier;
IsLastProperty:=StartPos>length(PropertyPath);
//DebugLn('TFindDeclarationTool.FindDeclarationOfPropertyPath Context=',Context.Node.DescAsString,' Identifier=',Identifier);
if Identifier='' then begin
NewContext:=Context;
exit(true);
end;
if Context.Node.Desc=ctnSetType then begin
// set
if not IsLastProperty then exit;
Node:=Context.Node.FirstChild;
if (Node=nil) or (Node.Desc<>ctnIdentifier) then exit;
// search enum type
Params.Flags:=[fdfExceptionOnNotFound,fdfSearchInParentNodes,fdfFindChildren];
Params.SetIdentifier(Self,@Context.Tool.Src[Node.StartPos],nil);
Params.ContextNode:=Node;
if not Context.Tool.FindIdentifierInContext(Params) then exit;
Context.Tool:=Params.NewCodeTool;
Context.Node:=Params.NewNode;
// search enum base type
Context:=Context.Tool.FindBaseTypeOfNode(Params,Context.Node);
//debugln(['TFindDeclarationTool.FindDeclarationOfPropertyPath enum base type ',FindContextToString(Context)]);
if (Context.Node=nil) or (Context.Node.Desc<>ctnEnumerationType) then
exit;
// search enum
Node:=Context.Node.FirstChild;
while Node<>nil do begin
if CompareIdentifiers(PChar(Pointer(Identifier)),@Context.Tool.Src[Node.StartPos])=0
then begin
//debugln(['TFindDeclarationTool.FindDeclarationOfPropertyPath identifier=',Identifier]);
NewContext.Tool:=Context.Tool;
NewContext.Node:=Node;
//debugln(['TFindDeclarationTool.FindDeclarationOfPropertyPath FOUND ',FindContextToString(NewContext)]);
exit(true);
end;
Node:=Node.NextBrother;
end;
exit;
end;
if (not (Context.Node.Desc in AllClasses)) then begin
debugln(['TFindDeclarationTool.FindDeclarationOfPropertyPath failed Context=',Context.Node.DescAsString]);
exit;
end;
//DebugLn('TFindDeclarationTool.FindDeclarationOfPropertyPath Identifier="',identifier,'"');
Params.Flags:=[fdfExceptionOnNotFound,fdfSearchInAncestors,fdfSearchInHelpers];
Params.SetIdentifier(Self,PChar(Pointer(Identifier)),nil);
Params.ContextNode:=Context.Node;
if IsLastProperty then
Params.Flags:=Params.Flags+[fdfFindVariable]
else
Params.Flags:=Params.Flags-[fdfFindVariable]+[fdfFunctionResult,fdfFindChildren];
if not Context.Tool.FindIdentifierInContext(Params) then exit;
Context.Tool:=Params.NewCodeTool;
Context.Node:=Params.NewNode;
if Context.Node=nil then exit;
if IsLastProperty then begin
if IgnoreTypeLess then begin
repeat
IsTypeLess:=false;
if (Context.Node.Desc=ctnProperty)
and Context.Tool.PropNodeIsTypeLess(Context.Node) then
IsTypeLess:=true;
if not IsTypeLess then break;
//DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath has no type, searching next ...']);
Params.SetIdentifier(Self,PChar(Pointer(Identifier)),nil);
Params.ContextNode:=Context.Tool.FindClassOrInterfaceNode(Context.Node);
if Params.ContextNode=nil then
Params.ContextNode:=Context.Node;
Params.Flags:=[fdfExceptionOnNotFound,fdfSearchInAncestors,fdfSearchInHelpers,
fdfFindVariable,fdfIgnoreCurContextNode];
//DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath ',Context.Tool.MainFilename,' ',Params.ContextNode.DescAsString,' ',Context.Tool.CleanPosToStr(Params.ContextNode.StartPos)]);
if not Context.Tool.FindIdentifierInContext(Params) then exit;
Context.Tool:=Params.NewCodeTool;
Context.Node:=Params.NewNode;
if Context.Node=nil then exit;
until false;
end;
//DebugLn(['TFindDeclarationTool.FindDeclarationOfPropertyPath FOUND']);
NewContext:=Context;
Result:=true;
exit;
end else begin
Context:=Context.Tool.FindBaseTypeOfNode(Params,Context.Node);
if Context.Node=nil then exit;
end;
until false;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.FindDeclarationOfPropertyPath(
const PropertyPath: string;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IgnoreTypeLess: boolean): boolean;
var
Context: TFindContext;
begin
Result:=FindDeclarationOfPropertyPath(PropertyPath,Context,IgnoreTypeLess);
if not Result then exit;
Result:=Context.Tool.JumpToNode(Context.Node,NewPos,NewTopLine,false);
end;
function TFindDeclarationTool.FindDeclarationNodeInInterface(
const Identifier: string; BuildTheTree: Boolean): TCodeTreeNode;
var
CacheEntry: PInterfaceIdentCacheEntry;
begin
Result:=nil;
if Identifier='' then exit;
if BuildTheTree and (not BuildInterfaceIdentifierCache(true)) then
exit;
CacheEntry:=FInterfaceIdentifierCache.FindIdentifier(PChar(Identifier));
if CacheEntry=nil then exit;
Result:=CacheEntry^.Node;
end;
function TFindDeclarationTool.FindDeclarationNodeInImplementation(
Identifier: string; BuildTheTree: Boolean): TCodeTreeNode;
begin
Result:=nil;
if Identifier='' then exit;
if BuildTheTree then
BuildTree(lsrInitializationStart);
Result:=FindSubDeclaration(Identifier,FindImplementationNode);
end;
function TFindDeclarationTool.FindSubDeclaration(Identifier: string;
ParentNode: TCodeTreeNode): TCodeTreeNode;
var
LastNode: TCodeTreeNode;
begin
Result:=nil;
if ParentNode=nil then exit;
if Identifier='' then exit;
Identifier:=UpperCaseStr(Identifier);
LastNode:=ParentNode.NextSkipChilds;
Result:=ParentNode.Next;
while Result<>LastNode do begin
// ToDo: check enums
if Result.Desc in AllIdentifierDefinitions then begin
if CompareNodeIdentChars(Result,Identifier)=0 then
exit;
Result:=Result.NextSkipChilds;
end else if Result.Desc=ctnProcedure then begin
if CompareIdentifiers(PChar(ExtractProcName(Result,[])),PChar(Pointer(Identifier)))=0 then
exit;
Result:=Result.NextSkipChilds;
end else
Result:=Result.Next;
end;
Result:=nil;
end;
function TFindDeclarationTool.FindMainUsesSection(UseContainsSection: boolean
): TCodeTreeNode;
begin
Result := FindMainUsesNode(UseContainsSection);
end;
function TFindDeclarationTool.FindImplementationUsesSection: TCodeTreeNode;
begin
Result := FindImplementationUsesNode;
end;
function TFindDeclarationTool.FindNameInUsesSection(UsesNode: TCodeTreeNode;
const AUnitName: string): TCodeTreeNode;
var
CurUnitName: string;
begin
Result:=UsesNode.FirstChild;
while (Result<>nil) do begin
CurUnitName:=ExtractUsedUnitName(Result);
if CompareDottedIdentifiers(PChar(CurUnitName),PChar(AUnitName))=0 then exit;
Result:=Result.NextBrother;
end;
end;
function TFindDeclarationTool.FindUnitInUsesSection(UsesNode: TCodeTreeNode;
const AnUnitName: string; out NamePos, InPos: TAtomPosition): boolean;
var
CurUnitName: String;
StartPos: Integer;
begin
Result:=false;
NamePos:=CleanAtomPosition;
InPos:=CleanAtomPosition;
if (UsesNode=nil) or (not IsDottedIdentifier(AnUnitName))
or (UsesNode.Desc<>ctnUsesSection) then begin
DebugLn(['TFindDeclarationTool.FindUnitInUsesSection invalid AnUnitName']);
exit;
end;
MoveCursorToNodeStart(UsesNode);
ReadNextAtom; // read 'uses'
repeat
ReadNextAtom; // read name
if CurPos.Flag=cafSemicolon then break;
if (CurPos.StartPos>SrcLen) then break;
StartPos:=CurPos.StartPos;
CurUnitName:=ExtractUsedUnitNameAtCursor;
if CompareDottedIdentifiers(PChar(CurUnitName),PChar(AnUnitName))=0 then
begin
MoveCursorToCleanPos(StartPos);
ReadNextAtom;
ReadNextUsedUnit(NamePos,InPos);
Result:=true;
exit;
end;
if CurPos.Flag=cafSemicolon then break;
if CurPos.Flag<>cafComma then break;
until (CurPos.StartPos>SrcLen);
end;
function TFindDeclarationTool.FindUnitInAllUsesSections(
const AnUnitName: string; out NamePos, InPos: TAtomPosition): boolean;
procedure RaiseInvalidUnitName;
begin
raise Exception.Create('invalid unit name '+AnUnitName);
end;
function FindInSection(UsesNode: TCodeTreeNode): boolean;
begin
Result:=(UsesNode<>nil)
and FindUnitInUsesSection(UsesNode,AnUnitName,NamePos,InPos);
end;
begin
Result:=false;
NamePos.StartPos:=-1;
InPos.StartPos:=-1;
if not IsDottedIdentifier(AnUnitName) then
RaiseInvalidUnitName;
BuildTree(lsrImplementationUsesSectionEnd);
if FindInSection(FindMainUsesNode) then exit;
if FindInSection(FindImplementationUsesNode) then exit;
end;
function TFindDeclarationTool.GetUnitNameForUsesSection(
TargetTool: TFindDeclarationTool): string;
// if unit is already used return ''
// else return nice name
var
UsesNode: TCodeTreeNode;
Alternative: String;
begin
Result:='';
if (TargetTool=nil) or (TargetTool.MainFilename='') or (TargetTool=Self) then
exit;
Result:=ExtractFileNameOnly(TargetTool.MainFilename);
if Result='' then exit;
// check if system unit
if IsHiddenUsedUnit(PChar(Result)) then begin
Result:='';
exit;
end;
// check if already there
UsesNode:=FindMainUsesNode;
if (UsesNode<>nil) and (FindNameInUsesSection(UsesNode,Result)<>nil)
then begin
Result:='';
exit;
end;
UsesNode:=FindImplementationUsesNode;
if (UsesNode<>nil) and (FindNameInUsesSection(UsesNode,Result)<>nil)
then begin
Result:='';
exit;
end;
// beautify
if Result=lowercase(Result) then begin
Alternative:=TargetTool.GetSourceName(false);
if Alternative<>'' then
Result:=Alternative;
end;
end;
function TFindDeclarationTool.GetUnitForUsesSection(
TargetTool: TFindDeclarationTool): string;
begin
Result:=GetUnitNameForUsesSection(TargetTool);
end;
function TFindDeclarationTool.IsHiddenUsedUnit(TheUnitName: PChar): boolean;
var
HiddenUnits: String;
p: PChar;
begin
if TheUnitName=nil then exit(false);
HiddenUnits:=Scanner.GetHiddenUsedUnits;
if HiddenUnits<>'' then begin
p:=PChar(HiddenUnits);
while p^<>#0 do begin
if CompareDottedIdentifiers(TheUnitName,p)=0 then
exit(true);
while not (p^ in [',',#0]) do inc(p);
while p^=',' do inc(p);
end;
end;
Result:=false;
end;
function TFindDeclarationTool.FindInitializationSection: TCodeTreeNode;
begin
Result:=FindInitializationNode;
end;
function TFindDeclarationTool.FindDeclarationInUsesSection(
UsesNode: TCodeTreeNode; CleanPos: integer;
out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean;
var AUnitName, UnitInFilename: string;
UnitNamePos, UnitInFilePos: TAtomPosition;
begin
Result:=false;
{$IFDEF ShowTriedContexts}
DebugLn('TFindDeclarationTool.FindDeclarationInUsesSection A');
{$ENDIF}
{$IFDEF CheckNodeTool}CheckNodeTool(UsesNode);{$ENDIF}
// reparse uses section, ignore errors after CleanPos
MoveCursorToNodeStart(UsesNode);
if (UsesNode.Desc=ctnUsesSection) then begin
ReadNextAtom;
if not UpAtomIs('USES') then
RaiseUsesExpected(20170421200506);
end else
if (UsesNode.Desc = ctnUseUnitClearName) then
MoveCursorToNodeStart(UsesNode.Parent);
repeat
ReadNextAtom; // read name
if CurPos.StartPos>CleanPos then break;
if CurPos.Flag=cafSemicolon then break;
ReadNextUsedUnit(UnitNamePos,UnitInFilePos);
if CleanPos<=CurPos.StartPos then begin
// cursor is on an used unit -> try to locate it
MoveCursorToCleanPos(UnitNamePos.StartPos);
ReadNextAtom;
AUnitName:=ExtractUsedUnitNameAtCursor(@UnitInFilename);
NewPos.Code:=FindUnitSource(AUnitName,UnitInFilename,true,UnitNamePos.StartPos);
NewPos.X:=1;
NewPos.Y:=1;
NewTopLine:=1;
Result:=true;
exit;
end;
if CurPos.Flag=cafSemicolon then break;
if CurPos.Flag<>cafComma then
RaiseExceptionFmt(20170421200032,ctsStrExpectedButAtomFound,[';',GetAtom])
until (CurPos.StartPos>SrcLen);
{$IFDEF ShowTriedContexts}
DebugLn('TFindDeclarationTool.FindDeclarationInUsesSection END cursor not on AUnitName');
{$ENDIF}
end;
function TFindDeclarationTool.FindUnitFileInUsesSection(
UsesNode: TCodeTreeNode; const AFilename: string): TCodeTreeNode;
var
TargetLoUnitName: string;
TargetLoShortFilename: string;
function CheckUseNode(Node: TCodeTreeNode): boolean;
var
Code: TCodeBuffer;
UnitInFilename: string;
AUnitName: string;
begin
Result:=false;
MoveCursorToNodeStart(Node);
ReadNextAtom;
AUnitName:=ExtractUsedUnitNameAtCursor(@UnitInFilename);
if AUnitName='' then exit;
// quick check: compare unitname
if UnitInFilename<>'' then begin
if lowercase(ExtractFilename(UnitInFilename))<>TargetLoShortFilename then
exit;
end else if LowerCase(AUnitName)<>TargetLoUnitName then
exit;
// search in search paths
Code:=FindUnitSource(AUnitName,UnitInFilename,false,Node.StartPos);
Result:=(Code<>nil) and (CompareFilenames(Code.Filename,AFilename)=0);
end;
begin
Result:=nil;
if (UsesNode=nil) or (UsesNode.Desc<>ctnUsesSection) then exit;
TargetLoUnitName:=LowerCase(ExtractFileNameOnly(AFilename));
TargetLoShortFilename:=LowerCase(ExtractFileName(AFilename));
if TargetLoShortFilename='' then exit;
Result:=UsesNode.LastChild;
while Result<>nil do begin
if CheckUseNode(Result) then exit;
Result:=Result.PriorBrother;
end;
end;
function TFindDeclarationTool.FindUnitFileInAllUsesSections(
const AFilename: string; CheckMain: boolean; CheckImplementation: boolean
): TCodeTreeNode;
begin
Result:=nil;
//debugln(['TFindDeclarationTool.FindUnitFileInAllUsesSections Self=',ExtractFilename(MainFilename),' Search=',ExtractFilename(AFilename)]);
if AFilename='' then exit;
if CheckMain then begin
Result:=FindUnitFileInUsesSection(FindMainUsesNode,AFilename);
//debugln(['TFindDeclarationTool.FindUnitFileInAllUsesSections Self=',ExtractFilename(MainFilename),' Search=',ExtractFilename(AFilename),' used in main uses=',Result<>nil]);
if Result<>nil then exit;
end;
if CheckImplementation then
Result:=FindUnitFileInUsesSection(FindImplementationUsesNode,AFilename);
end;
function TFindDeclarationTool.FindUnitSource(const AnUnitName,
AnUnitInFilename: string; ExceptionOnNotFound: boolean; ErrorPos: integer
): TCodeBuffer;
var
CompiledFilename: string;
AFilename: String;
NewUnitName: String;
NewInFilename: String;
NewCompiledUnitname: String;
ErrMsg: string;
begin
{$IF defined(ShowTriedFiles) or defined(ShowTriedUnits)}
DebugLn('TFindDeclarationTool.FindUnitSource Self="',MainFilename,'" AnUnitName="',AnUnitName,'" AnUnitInFilename="',AnUnitInFilename,'"');
{$ENDIF}
Result:=nil;
if (AnUnitName='') or (Scanner=nil) then
RaiseException(20171214184503,'TFindDeclarationTool.FindUnitSource Invalid Data');
if (Scanner.MainCode=nil) then
RaiseException(20171214184512,'TFindDeclarationTool.FindUnitSource Invalid Data');
if (not (TObject(Scanner.MainCode) is TCodeBuffer)) then
RaiseException(20171214184519,'TFindDeclarationTool.FindUnitSource Invalid Data');
if (Scanner.OnLoadSource=nil) then
RaiseException(20171214184527,'TFindDeclarationTool.FindUnitSource Invalid Data');
NewUnitName:=AnUnitName;
NewInFilename:=AnUnitInFilename;
AFilename:=DirectoryCache.FindUnitSourceInCompletePath(
NewUnitName,NewInFilename,false,false,AddedNameSpace);
Result:=TCodeBuffer(Scanner.OnLoadSource(Self,AFilename,true));
if (Result=nil) and Assigned(OnFindUsedUnit) then begin
// no unit found
Result:=OnFindUsedUnit(Self,AnUnitName,AnUnitInFilename);
end;
if Result=nil then begin
// search .ppu
NewCompiledUnitname:=AnUnitName+'.ppu';
CompiledFilename:=DirectoryCache.FindCompiledUnitInCompletePath(
NewCompiledUnitname,false);
//debugln(['TFindDeclarationTool.FindUnitSource UnitName=',NewUnitName,' ',NewCompiledUnitname,' CompiledFilename=',CompiledFilename]);
end else begin
CompiledFilename:='';
end;
if (Result=nil) and ExceptionOnNotFound then begin
ErrMsg:='';
if ErrorPos>0 then
MoveCursorToCleanPos(ErrorPos)
else if ErrorPos=0 then begin
CurPos.StartPos:=-1;
end else begin
CurPos.StartPos:=-1;
ErrMsg:=Format(ctsNeededByMode, [CompilerModeNames[Scanner.CompilerMode]]);
end;
if CompiledFilename<>'' then begin
// there is a compiled unit, only the source was not found
RaiseExceptionInstance(
ECodeToolUnitNotFound.Create(Self,20170421200052,
Format(ctsSourceNotFoundUnit+ErrMsg, [CompiledFilename]),
AnUnitName));
end else begin
// nothing found
RaiseExceptionInstance(
ECodeToolUnitNotFound.Create(Self,20170421200056,
Format(ctsUnitNotFound+ErrMsg,[AnUnitName]),
AnUnitInFilename));
end;
end;
end;
function TFindDeclarationTool.FindUnitCaseInsensitive(var AnUnitName,
AnUnitInFilename: string): string;
begin
Result:=DirectoryCache.FindUnitSourceInCompletePath(
AnUnitName,AnUnitInFilename,true,false,AddedNameSpace);
end;
procedure TFindDeclarationTool.GatherUnitAndSrcPath(var UnitPath,
CompleteSrcPath: string);
begin
UnitPath:='';
CompleteSrcPath:='';
UnitPath:=DirectoryCache.Strings[ctdcsUnitPath];
CompleteSrcPath:=DirectoryCache.Strings[ctdcsCompleteSrcPath];
//DebugLn('TFindDeclarationTool.GatherUnitAndSrcPath UnitPath="',UnitPath,'" CompleteSrcPath="',CompleteSrcPath,'"');
end;
function TFindDeclarationTool.SearchUnitInUnitLinks(const TheUnitName: string
): string;
begin
Result:=DirectoryCache.FindUnitLink(TheUnitName);
end;
function TFindDeclarationTool.SearchUnitInUnitSet(const TheUnitName: string
): string;
begin
Result:=DirectoryCache.FindUnitInUnitSet(TheUnitName);
end;
function TFindDeclarationTool.GetNameSpaces: string;
begin
Result:=DirectoryCache.Strings[ctdcsNamespaces];
if AddedNameSpace<>'' then begin
if Result<>'' then Result:=';'+Result;
Result:=AddedNameSpace+Result;
end;
end;
function TFindDeclarationTool.FindSmartHint(const CursorPos: TCodeXYPosition;
Flags: TFindSmartFlags): string;
var
NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode;
NewPos: TCodeXYPosition;
NewTopLine: integer;
begin
Result:='';
if not FindDeclaration(CursorPos,Flags,NewTool,NewNode,NewPos,NewTopLine) then
begin
// identifier not found
exit;
end;
Result:=NewTool.GetSmartHint(NewNode,NewPos,true);
end;
function TFindDeclarationTool.GetSmartHint(Node: TCodeTreeNode;
XYPos: TCodeXYPosition; WithPosition: boolean; WithDefinition: boolean
): string;
function ReadIdentifierWithDots: String;
begin
Result := '';
repeat
ReadNextAtom;
Result := Result + GetAtom;
ReadNextAtom;
if CurPos.Flag = cafPoint then
Result := Result + '.'
else
break;
until false;
end;
function MoveToLastIdentifierThroughDots(ExtTool: TFindDeclarationTool): Boolean;
var
LastPos: TAtomPosition;
begin
LastPos := ExtTool.CurPos;
ExtTool.ReadNextAtom;
if ExtTool.CurPos.Flag = cafWord then
ExtTool.ReadNextAtom;
while ExtTool.CurPos.Flag = cafPoint do
begin
ExtTool.ReadNextAtom;
LastPos := ExtTool.CurPos;
ExtTool.ReadNextAtom;
end;
ExtTool.CurPos := LastPos;
Result := True;
end;
function ProceedWithSmartHint(ExtTool: TFindDeclarationTool): string;
var
CTExprType: TExpressionType;
CTXYPos: TCodeXYPosition;
CTTopLine: integer;
CTCursorPos: TCodeXYPosition;
begin
MoveToLastIdentifierThroughDots(ExtTool);
if ExtTool.CleanPosToCaret(ExtTool.CurPos.StartPos,CTCursorPos)
and ExtTool.FindDeclaration(CTCursorPos,
DefaultFindSmartHintFlags+[fsfSearchSourceName],CTExprType,CTXYPos,CTTopLine)
and not((CTExprType.Desc=xtContext) and (CTExprType.Context.Node=nil) and (CTExprType.Context.Tool=nil))
and not((CTExprType.Context.Tool=Self) and (CTXYPos.X=XYPos.X) and (CTXYPos.Y=XYPos.Y)) // prevent endless loop
then
Result := CTExprType.Context.Tool.GetSmartHint(CTExprType.Context.Node, CTXYPos, False, False)
else
Result := '';
end;
var
IdentNode, TypeNode, ANode: TCodeTreeNode;
ClassStr, NodeStr, SetStr: String;
Params: TFindDeclarationParams;
Tool: TFindDeclarationTool;
HelperForNode: TCodeTreeNode;
SubNode: TCodeTreeNode;
begin
Result:='';
{ Examples:
var i: integer
/home/.../codetools/finddeclarationtools.pas(1224,7)
}
// identifier category and identifier
if Node<>nil then begin
// class visibility
if Node.Parent<>nil then begin
ANode:=Node.Parent;
while ANode<>nil do begin
case ANode.Desc of
ctnClassPrivate:
Result+='private ';
ctnClassProtected:
Result+='protected ';
ctnClassPublic:
Result+='public ';
ctnClassPublished:
Result+='published ';
ctnClassClassVar:
Result+='class ';
else
break;
end;
ANode:=ANode.Parent;
end;
end;
if Node.Desc = ctnGenericName then
Node := Node.Parent;
case Node.Desc of
ctnIdentifier:
if Assigned(Node.Parent) and (Node.Parent.Desc = ctnProcedureHead) then
// function result
Result := 'var Result: ' + ExtractNode(Node, []);
ctnVarDefinition, ctnTypeDefinition, ctnConstDefinition,
ctnEnumIdentifier, ctnLabel, ctnGenericType:
begin
case Node.Desc of
ctnVarDefinition: Result+='var ';
ctnTypeDefinition: Result+='type ';
ctnConstDefinition: Result+='const ';
ctnEnumIdentifier: Result+='enum ';
ctnLabel: Result+='label ';
ctnGenericType: Result+='generic type ';
end;
// add class name
ClassStr := ExtractClassPath(Node.Parent);
if ClassStr <> '' then Result += ClassStr + '.';
Result:=Result+ExtractDefinitionName(Node);
TypeNode:=FindTypeNodeOfDefinition(Node);
if not WithDefinition then Result := '';
if TypeNode<>nil then begin
case Node.Desc of
ctnTypeDefinition, ctnGenericType:
Result+=' = ';
ctnConstDefinition:
if TypeNode.Desc = ctnConstant then
Result += ' = '
else
Result += ': ';
ctnEnumIdentifier,ctnLabel: ;
else
Result += ': ';
end;
case TypeNode.Desc of
ctnSetType:
begin
Result += ExtractNode(TypeNode, [phpCommentsToSpace]);
MoveCursorToNodeStart(TypeNode);
ReadNextAtom;
if ReadNextUpAtomIs('OF') then
begin
if (Length(Result) > 0) and (Result[Length(Result)] = ';') then//delete last ";" from set
Delete(Result, Length(Result), 1);
ReadNextAtom;
SetStr := ProceedWithSmartHint(Self);
if (Length(SetStr) > 2) and (SetStr[2] = '=') then
SetStr := Copy(SetStr, 4, High(Integer));
if (SetStr <> '') then
Result += ' = ['+SetStr+']';
end;
end;
ctnIdentifier, ctnSpecialize, ctnSpecializeType,
ctnPointerType, ctnRangeType, ctnFileType, ctnClassOfType:
begin
Result += ExtractNode(TypeNode, [phpCommentsToSpace]);
MoveCursorToNodeStart(TypeNode);
Result += ProceedWithSmartHint(Self);
end;
ctnClass, ctnClassInterface, ctnDispinterface,
ctnClassHelper, ctnTypeHelper, ctnRecordHelper,
ctnObject, ctnRangedArrayType, ctnOpenArrayType,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass:
begin
MoveCursorToNodeStart(TypeNode);
case TypeNode.Desc of
ctnClass: Result:=Result+'class';
ctnClassHelper: Result:=Result+'class helper';
ctnRecordHelper: Result:=Result+'record helper';
ctnTypeHelper: Result:=Result+'type helper';
ctnObject: Result:=Result+'object';
ctnObjCClass: Result:=Result+'objcclass';
ctnObjCCategory: Result:=Result+'objccategory';
ctnCPPClass: Result:=Result+'cppclass';
ctnClassInterface: Result:=Result+'interface';
ctnObjCProtocol: Result:=Result+'objcprotocol';
ctnDispinterface: Result:=Result+'dispinterface';
ctnRangedArrayType, ctnOpenArrayType: Result:=Result+'array';
end;
try
BuildSubTree(TypeNode);
except
on ECodeToolError do ;
end;
SubNode:=FindInheritanceNode(TypeNode);
if SubNode<>nil then
Result:=Result+ExtractNode(SubNode,[]);
if TypeNode.Desc in [ctnClassHelper, ctnRecordHelper, ctnTypeHelper] then
HelperForNode := FindHelperForNode(TypeNode)
else
HelperForNode := nil;
if HelperForNode<>nil then
Result:=Result+' '+ExtractNode(HelperForNode,[]);
end;
ctnRecordType:
Result:=Result+'record';
ctnTypeType:
begin
Result:=Result+'type';
if TypeNode.FirstChild <> nil then
Result:=Result+' '+ExtractNode(TypeNode.FirstChild,[]);
end;
ctnConstant:
begin
NodeStr:=ExtractNode(TypeNode,[phpCommentsToSpace]);
Result+=copy(NodeStr,1,50);
end;
ctnEnumerationType:
begin
if Assigned(Node.FirstChild) then
begin
NodeStr:=ExtractCode(Node.FirstChild.StartPos,Node.FirstChild.EndPos,[phpCommentsToSpace]);
if Length(NodeStr) > 50 then
NodeStr:=Copy(NodeStr, 1, 50) + ' ...';
Result += NodeStr;
end else
Result += 'enum';
end;
end;
end else begin
case Node.Desc of
ctnConstDefinition:
begin
DebugLn('TFindDeclarationTool.GetSmartHint const without subnode "',ExtractNode(Node,[]),'"');
NodeStr:=ExtractCode(Node.StartPos
+GetIdentLen(@Src[Node.StartPos]),
Node.EndPos,[phpCommentsToSpace]);
Result+=copy(NodeStr,1,50);
end;
end;
end;
end;
ctnProcedure,ctnProcedureHead:
begin
// ToDo: ppu, dcu files
Result+=ExtractProcHead(Node,
[phpAddClassName,phpWithStart,phpWithVarModifiers,phpWithParameterNames,
phpWithDefaultValues,phpWithResultType,phpWithOfObject,phpCommentsToSpace]);
end;
ctnProperty,ctnGlobalProperty:
begin
IdentNode:=Node;
// ToDo: ppu, dcu files
Result+='property ';
MoveCursorToNodeStart(IdentNode);
ReadNextAtom;
if Node.Desc = ctnProperty then begin
// e.g. property Caption: string;
// skip keyword
ReadNextAtom;
// add class name
ClassStr := ExtractClassName(Node, False, True);
if ClassStr <> '' then Result += ClassStr + '.';
end else begin
// global property starts with identifier
end;
// add name
Result+=GetAtom;
Tool:=Self;
while (Node.Desc=ctnProperty)
and not Tool.MoveCursorToPropType(Node) do begin
// property without type
// -> search ancestor property
if not Tool.MoveCursorToPropName(Node) then break;
Params:=TFindDeclarationParams.Create(Tool, Node);
try
Params.SetIdentifier(Tool,@Tool.Src[Tool.CurPos.StartPos],nil);
Params.Flags:=[fdfSearchInAncestors,fdfSearchInHelpers];
if not FindIdentifierInAncestors(Node.Parent.Parent,Params) then break;
Tool:=Params.NewCodeTool;
Node:=Params.NewNode;
finally
Params.Free;
end;
end;
if (Node<>nil) then begin
if (Node.Desc in [ctnProperty,ctnGlobalProperty]) then begin
Result += Tool.ExtractProperty(Node,
[phpWithoutName,phpWithParameterNames,phpWithResultType]);
end;
if Tool.MoveCursorToPropType(Node) then
Result += ProceedWithSmartHint(Tool);
end;
end;
ctnProgram,ctnUnit,ctnPackage,ctnLibrary:
begin
IdentNode:=Node;
// ToDo: ppu, dcu files
MoveCursorToNodeStart(IdentNode);
ReadNextAtom;
if (IdentNode.Desc=ctnProgram) and not UpAtomIs('PROGRAM') then begin
// program without source name
Result:='program '+ExtractFileNameOnly(MainFilename)+' ';
end else begin
Result+=GetAtom+' '; // keyword
Result := Result + ReadIdentifierWithDots + ' ';
end;
end;
ctnUseUnitNamespace:
begin
// hint for unit namespace in "uses" section
Result += 'namespace ';
MoveCursorToNodeStart(Node);
ReadNextAtom;
Result := Result + GetAtom;
end;
ctnUseUnitClearName:
begin
// hint for unit in "uses" section
Result += 'unit ';
MoveCursorToNodeStart(Node.Parent);
Result := Result + ReadIdentifierWithDots;
end
else
DebugLn('ToDo: TFindDeclarationTool.GetSmartHint ',Node.DescAsString);
end;
end;
if WithPosition then begin
// filename
if Result<>'' then Result:=Result+LineEnding;
if XYPos.Code=nil then
CleanPosToCaret(Node.StartPos,XYPos);
Result+=XYPos.Code.Filename;
// file position
if XYPos.Y>=1 then begin
Result+='('+IntToStr(XYPos.Y);
if XYPos.X>=1 then begin
Result+=','+IntToStr(XYPos.X);
end;
Result+=')';
end;
end;
end;
function TFindDeclarationTool.BaseTypeOfNodeHasSubIdents(ANode: TCodeTreeNode
): boolean;
var
FindContext: TFindContext;
Params: TFindDeclarationParams;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ANode);{$ENDIF}
Result:=false;
if (ANode=nil) then exit;
ActivateGlobalWriteLock;
Params:=TFindDeclarationParams.Create(Self, ANode);
try
Params.Flags:=Params.Flags+[fdfFunctionResult,fdfFindChildren];
FindContext:=FindBaseTypeOfNode(Params,ANode);
if (FindContext.Node<>nil)
and ((FindContext.Node.Desc in ([ctnEnumerationType]+AllClasses)))
and (FindContext.Node.FirstChild<>nil)
then
Result:=true;
finally
Params.Free;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.IsIncludeDirectiveAtPos(CleanPos,
CleanCodePosInFront: integer; out IncludeCode: TCodeBuffer): boolean;
var LinkIndex, CommentStart, CommentEnd: integer;
SrcLink: TSourceLink;
begin
Result:=false;
IncludeCode:=nil;
if (Scanner=nil) then exit;
LinkIndex:=Scanner.LinkIndexAtCleanPos(CleanPos);
if (LinkIndex<0) or (LinkIndex>=Scanner.LinkCount-1) then exit;
SrcLink:=Scanner.Links[LinkIndex+1];
if (SrcLink.Code=nil) or (SrcLink.Code=Scanner.Links[LinkIndex].Code) then
exit;
//DebugLn(['TFindDeclarationTool.IsIncludeDirectiveAtPos CleanPos=',CleanPos,' CleanCodePosInFront=',CleanCodePosInFront,' ',copy(Src,CleanCodePosInFront,10)]);
if CleanPosIsInComment(CleanPos,CleanCodePosInFront,CommentStart,CommentEnd)
and (CommentEnd=SrcLink.CleanedPos) then begin
//DebugLn(['TFindDeclarationTool.IsIncludeDirectiveAtPos CommentStart=',CommentStart,' CommentEnd=',CommentEnd,' ',copy(Src,CommentStart,CommentEnd-CommentStart)]);
IncludeCode:=TCodeBuffer(SrcLink.Code);
Result:=true;
exit;
end;
end;
function TFindDeclarationTool.FindFileAtCursor(
const CursorPos: TCodeXYPosition; out Found: TFindFileAtCursorFlag; out
FoundFilename: string; SearchFor: TFindFileAtCursorFlags;
StartPos: PCodeXYPosition): boolean;
var
CleanPos: integer;
function CheckComment(CommentStart, CommentEnd: integer; Enabled: boolean): boolean;
var
DirectiveName, Param: string;
NewCode: TCodeBuffer;
MissingIncludeFile: TMissingIncludeFile;
NewCodePtr: Pointer;
begin
Result:=false;
// cursor in comment in parsed code
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor.CheckComment']);
{$ENDIF}
if CommentStart=CommentEnd then exit;
if ExtractLongParamDirective(Src,CommentStart,DirectiveName,Param) then begin
DirectiveName:=lowercase(DirectiveName);
if ((Enabled and (ffatIncludeFile in SearchFor))
or (not Enabled and (ffatDisabledIncludeFile in SearchFor)))
and (DirectiveName='i') or (DirectiveName='include')
then begin
// include directive
if (Param<>'') and (Param[1]<>'%') then begin
// include file directive
Result:=true;
if Enabled then
Found:=ffatIncludeFile
else
Found:=ffatDisabledIncludeFile;
if Enabled and IsIncludeDirectiveAtPos(CleanPos,CommentStart,NewCode) then
begin
FoundFilename:=NewCode.Filename;
end else begin
FoundFilename:=ResolveDots(GetForcedPathDelims(Param));
// search include file
MissingIncludeFile:=nil;
if Scanner.SearchIncludeFile(FoundFilename,NewCodePtr,
MissingIncludeFile)
then
FoundFilename:=TCodeBuffer(NewCodePtr).Filename;
end;
exit;
end;
end else if ((Enabled and (ffatResource in SearchFor))
or (not Enabled and (ffatDisabledResource in SearchFor)))
and ((DirectiveName='r') or (DirectiveName='resource'))
then begin
// resource directive
Result:=true;
if Enabled then
Found:=ffatResource
else
Found:=ffatDisabledResource;
FoundFilename:=ResolveDots(GetForcedPathDelims(Param));
if (FoundFilename<>'') and (copy(FoundFilename,1,2)='*.') then begin
Delete(FoundFilename,1,1);
FoundFilename:=ChangeFileExt(MainFilename,FoundFilename);
end else if not FilenameIsAbsolute(FoundFilename) then begin
FoundFilename:=ResolveDots(ExtractFilePath(MainFilename)+FoundFilename);
end;
exit;
end;
end;
end;
function CheckPlainComments(Source: string; CurAbsPos: integer): boolean;
var
Filename: String;
p, EndPos, FileStartPos, FileEndPos, MinPos, MaxPos: Integer;
begin
// check if cursor in a comment (ignoring directives)
Result:=false;
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,CurAbsPos);
Source:=CursorPos.Code.Source;
if (CurAbsPos<1) or (CurAbsPos>length(Source)) then exit;
p:=1;
repeat
p:=FindNextComment(Source,p);
if p>CurAbsPos then break;
EndPos:=FindCommentEnd(Source,p,Scanner.NestedComments);
if EndPos>CurAbsPos then begin
// cursor in comment
MinPos:=p+1;
MaxPos:=EndPos-1;
if Source[p]<>'{' then begin
inc(MinPos);
dec(MaxPos);
end;
FileStartPos:=CurAbsPos;
while (FileStartPos>MinPos) and not (Source[FileStartPos-1] in [#0..#32]) do
dec(FileStartPos);
FileEndPos:=CurAbsPos;
while (FileEndPos<MaxPos) and not (Source[FileEndPos] in [#0..#32]) do
inc(FileEndPos);
Filename:=TrimFilename(copy(Source,FileStartPos,FileEndPos-FileStartPos));
if not FilenameIsAbsolute(Filename) then
Filename:=ResolveDots(ExtractFilePath(MainFilename)+Filename);
if Scanner.OnLoadSource(Scanner,Filename,false)<>nil then begin
Found:=ffatComment;
FoundFilename:=Filename;
exit(true);
end;
exit;
end;
p:=EndPos;
until false;
end;
function CheckUnitByWordAtCursor(Source: string; CurAbsPos: integer): boolean;
// e.g. 'Sy|sUtils.CompareText'
var
AnUnitName: String;
Code: TCodeBuffer;
p: Integer;
begin
Result:=false;
p:=FindStartOfAtom(Source,CurAbsPos);
if p<1 then exit;
AnUnitName:=GetIdentifier(@Source[p]);
Code:=FindUnitSource(AnUnitName,'',false);
if Code=nil then exit;
Found:=ffatUnit;
FoundFilename:=Code.Filename;
Result:=true;
end;
var
CommentStart, CommentEnd, Col, StartCol, CurAbsPos: integer;
Node: TCodeTreeNode;
aUnitName, UnitInFilename, Line, Literal, aSource: string;
NewCode: TCodeBuffer;
p, StartP: PChar;
begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor START']);
{$ENDIF}
Result:=false;
Found:=ffatNone;
FoundFilename:='';
if StartPos<>nil then
StartPos^:=CleanCodeXYPosition;
if CursorPos.Code.LineColIsOutside(CursorPos.Y,CursorPos.X) then begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor LineColIsOutside ',dbgs(CursorPos)]);
{$ENDIF}
exit;
end;
if CursorPos.Code.LineColIsSpace(CursorPos.Y,CursorPos.X) then begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor LineColIsSpace ',dbgs(CursorPos)]);
{$ENDIF}
exit;
end;
if (CursorPos.Y<1) or (CursorPos.Y>CursorPos.Code.LineCount) then begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor outside Line ',dbgs(CursorPos)]);
{$ENDIF}
exit;
end;
if [ffatUsedUnit,ffatIncludeFile,ffatDisabledIncludeFile]*SearchFor<>[]
then begin
try
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor search in nodes']);
{$ENDIF}
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanPos,
[btSetIgnoreErrorPos,btCursorPosOutAllowed]);
Node:=FindDeepestNodeAtPos(CleanPos,false);
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor has node: ',Node<>nil]);
{$ENDIF}
if Node<>nil then begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor in node "',Node.DescAsString,'"']);
{$ENDIF}
// cursor in parsed code
if CleanPosIsInComment(CleanPos,Node.StartPos,CommentStart,CommentEnd,true)
then begin
//debugln(['TFindDeclarationTool.FindFileAtCursor Comment="',copy(Src,CommentStart,CommentEnd-CommentStart),'"']);
if (CommentEnd-CommentStart>4)
and (Src[CommentStart]='{') and (Src[CommentStart+1]=#3) then begin
// cursor in disabled code
if CleanPosIsInComment(CleanPos,CommentStart+2,CommentStart,CommentEnd,true)
then begin
// cursor in disabled comment
if CheckComment(CommentStart,CommentEnd,false) then
exit(true);
end;
end else begin
// cursor in enabled comment
if CheckComment(CommentStart,CommentEnd,true) then
exit(true);
end;
end else begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor in parsed code, not in comment Node=',Node.DescAsString]);
{$ENDIF}
if Node.Desc in [ctnUseUnitClearName,ctnUseUnitNamespace] then begin
Node:=Node.Parent;
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor node="',Node.DescAsString,'"']);
{$ENDIF}
end;
if Node.Desc=ctnUseUnit then begin
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor in use unit CleanPos=',CleanPos,' Node=',Node.StartPos,'-',Node.EndPos]);
{$ENDIF}
if (CleanPos>=Node.StartPos) and (CleanPos<Node.EndPos) then begin
// cursor on used unit
Found:=ffatUsedUnit;
if StartPos<>nil then
CleanPosToCaret(Node.StartPos,StartPos^);
MoveCursorToNodeStart(Node);
ReadNextAtom;
aUnitName:=ExtractUsedUnitNameAtCursor(@UnitInFilename);
NewCode:=FindUnitSource(aUnitName,UnitInFilename,false);
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor cursor on used unit "',aUnitName,'" in "',UnitInFilename,'" Found=',NewCode<>nil]);
{$ENDIF}
if NewCode<>nil then begin
FoundFilename:=NewCode.Filename;
Result:=true;
end else begin
FoundFilename:=UnitInFilename;
Result:=false;
end;
exit;
end;
end;
end;
end;
except
on ELinkScannerError do ;
on ECodeToolError do ;
end;
end;
// fallback: ignore parsed code and read the line at cursor directly
if (CursorPos.Y<1) or (CursorPos.Y>CursorPos.Code.LineCount) then exit;
Line:=CursorPos.Code.GetLine(CursorPos.Y-1,false);
{$IFDEF VerboseFindFileAtCursor}
debugln(['TFindDeclarationTool.FindFileAtCursor Line="',copy(Line,1,CursorPos.X-1),'|',copy(Line,CursorPos.X,200),'"']);
{$ENDIF}
if CursorPos.X>length(Line) then exit;
if ffatLiteral in SearchFor then begin
// check literal
p:=PChar(Line);
repeat
case p^ of
#0:
break;
'''':
begin
StartCol:=p-PChar(Line)+1;
inc(p);
StartP:=p;
repeat
case p^ of
#0,'''': break;
else inc(p);
end;
until false;
Col:=p-PChar(Line)+1;
//writeln('TFindDeclarationTool.FindFileAtCursor Col=',Col,' CursorCol=',CursorPos.X,' Literal=',copy(Line,StartCol+1,p-StartP));
if (p>StartP) and (CursorPos.X>=StartCol) and (CursorPos.X<=Col) then begin
Literal:=copy(Line,StartCol+1,p-StartP);
if not FilenameIsAbsolute(Literal) then
Literal:=TrimFilename(ExtractFilePath(Scanner.MainFilename)+Literal);
Found:=ffatLiteral;
FoundFilename:=Literal;
exit(true);
end;
if p^=#0 then break;
// p is now on the ending '
end;
end;
inc(p);
inc(Col);
until false;
end;
// search without node tree with basic tools
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,CurAbsPos);
aSource:=CursorPos.Code.Source;
if (CurAbsPos<1) or (CurAbsPos>length(aSource)) then exit;
if ffatComment in SearchFor then begin
// ignore syntax and only read comments
if CheckPlainComments(aSource,CurAbsPos) then exit(true);
end;
if ffatUnit in SearchFor then begin
if CheckUnitByWordAtCursor(aSource,CurAbsPos) then exit(true);
end;
end;
function TFindDeclarationTool.FindDeclarationOfIdentAtParam(
Params: TFindDeclarationParams): boolean;
var
ExprType: TExpressionType;
begin
Result := FindDeclarationOfIdentAtParam(Params, ExprType) and (Params.NewNode<>nil);
end;
function TFindDeclarationTool.FindDeclarationOfIdentAtParam(
Params: TFindDeclarationParams; out ExprType: TExpressionType): boolean;
{ searches an identifier in clean code, parses code in front and after the
identifier
Params:
Identifier in clean source
ContextNode // = DeepestNode at Cursor
Result:
true, if found
Examples:
A^.B().C[].Identifier
inherited Identifier(p1,p2)
'Hello'.identifier
}
var
StartPos, EndPos: integer;
SkipForward: boolean;
begin
{$IFDEF CTDEBUG}
DebugLn('[TFindDeclarationTool.FindDeclarationOfIdentAtParam] Identifier=',
'"',GetIdentifier(Params.Identifier),'"',
' ContextNode=',NodeDescriptionAsString(Params.ContextNode.Desc),
' "',dbgstr(copy(Src,Params.ContextNode.StartPos,20)),'"');
{$ENDIF}
Result:=false;
// search in cleaned source
MoveCursorToCleanPos(Params.Identifier);
StartPos:=FindStartOfTerm(CurPos.StartPos,NodeTermInType(Params.ContextNode));
MoveCursorToCleanPos(Params.Identifier);
ReadNextAtom;
EndPos:=CurPos.EndPos;
ReadNextAtom;
if CurPos.Flag=cafRoundBracketOpen then begin
ReadTilBracketClose(true);
EndPos:=CurPos.EndPos;
end;
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.FindDeclarationOfIdentAtParam Term=',dbgstr(Src,StartPos,EndPos-StartPos)]);
{$ENDIF}
SkipForward:=fdfSkipClassForward in Params.Flags;
Include(Params.Flags,fdfFindVariable);
ExprType:=FindExpressionTypeOfTerm(StartPos,EndPos,Params,false);
if (ExprType.Desc=xtContext) then
Params.SetResult(ExprType.Context)
else
Params.SetResult(CleanFindContext);
if SkipForward and (Params.NewNode<>nil) then
Params.NewCodeTool.FindNonForwardClass(Params);
{$IFDEF ShowExprEval}
DbgOut('[TFindDeclarationTool.FindDeclarationOfIdentAtParam] Ident=',
'"',GetIdentifier(Params.Identifier),'" ');
if Params.NewNode<>nil then
DebugLn('Node=',Params.NewNode.DescAsString,' ',Params.NewCodeTool.MainFilename)
else
DebugLn('NOT FOUND');
{$ENDIF}
Result:=ExprType.Desc<>xtNone;
end;
function TFindDeclarationTool.IdentifierIsDefined(const IdentAtom: TAtomPosition;
ContextNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
var
Identifier: PChar;
Node: TCodeTreeNode;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ContextNode);{$ENDIF}
// find declaration of identifier
Identifier:=@Src[IdentAtom.StartPos];
//DebugLn(['TFindDeclarationTool.IdentifierIsDefined BEGIN Params IdentAtom.StartPos=',IdentAtom.StartPos,'=',GetIdentifier(Identifier),', ContextNode.StartPos=',ContextNode.StartPos,'=',ContextNode.DescAsString,' "',ExtractNode(ContextNode,[]),'"']);
if (CompareIdentifiers(Identifier,'Self')=0) then begin
Node:=ContextNode;
while (Node<>nil) do begin
if NodeIsMethodBody(Node) then
exit(true);
Node:=Node.Parent;
end;
end;
if (cmsResult in FLastCompilerModeSwitches)
and (CompareIdentifiers(Identifier,'Result')=0) then begin
Node:=ContextNode;
while (Node<>nil) do begin
if NodeIsFunction(Node) then
exit(true);
Node:=Node.Parent;
end;
end;
Params.ContextNode:=ContextNode;
Params.SetIdentifier(Self,Identifier,nil);
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFindVariable,fdfIgnoreCurContextNode];
Result:=FindIdentifierInContext(Params);
//DebugLn(['TFindDeclarationTool.IdentifierIsDefined END Result=',Result]);
end;
function TFindDeclarationTool.FindIdentifierInContext(
Params: TFindDeclarationParams; var IdentFoundResult: TIdentifierFoundResult
): boolean;
{ searches an identifier in context node
It does not care about code in front of the identifier like 'a.Identifier'.
Params:
Identifier
ContextNode // = DeepestNode at Cursor
Result:
true, if NewPos+NewTopLine valid
}
var
LastContextNode, StartContextNode, FirstSearchedNode, LastSearchedNode,
ContextNode: TCodeTreeNode;
IsForward: boolean;
IdentifierFoundResult: TIdentifierFoundResult;
LastNodeCache: TCodeTreeNodeCache;
LastCacheEntry: PCodeTreeNodeCacheEntry;
SearchRangeFlags: TNodeCacheEntryFlags;
NodeCacheEntryFlags: TNodeCacheEntryFlags;
Flags: TFindDeclarationFlags;
OldFlags: TFindDeclarationFlags;
SearchInHelpersInTheEnd: Boolean;
procedure InitNodesAndCacheAccess;
procedure RaiseInternalError;
begin
RaiseException(20170421200059,'[TFindDeclarationTool.FindIdentifierInContext] '
+' internal error: Params.ContextNode=nil');
end;
begin
ContextNode:=Params.ContextNode;
if ContextNode=nil then RaiseInternalError;
{$IFDEF CheckNodeTool}
CheckNodeTool(ContextNode);
{$ENDIF}
StartContextNode:=ContextNode;
FirstSearchedNode:=nil;
LastSearchedNode:=nil;
SearchRangeFlags:=[];
Flags:=Params.Flags;
if fdfSearchInParentNodes in Flags then
Include(SearchRangeFlags,ncefSearchedInParents);
if fdfSearchInAncestors in Flags then
Include(SearchRangeFlags,ncefSearchedInAncestors);
LastNodeCache:=nil;
LastCacheEntry:=nil;
NodeCacheEntryFlags:=[];
if fdfSearchInParentNodes in Flags then
Include(NodeCacheEntryFlags,ncefSearchedInParents);
if fdfSearchInAncestors in Flags then
Include(NodeCacheEntryFlags,ncefSearchedInAncestors);
end;
function FindInNodeCache: boolean;
var
NodeCache: TCodeTreeNodeCache;
begin
Result:=false;
// the node cache is identifier based
if ([fdfCollect,fdfExtractOperand]*Flags<>[]) then exit;
NodeCache:=GetNodeCache(ContextNode,false);
if (NodeCache<>LastNodeCache) then begin
// NodeCache changed -> search nearest cache entry for the identifier
LastNodeCache:=NodeCache;
if NodeCache<>nil then begin
LastCacheEntry:=NodeCache.FindNearest(Params.Identifier,
ContextNode.StartPos,ContextNode.EndPos,
not (fdfSearchForward in Flags));
end else
LastCacheEntry:=nil;
end;
if (LastCacheEntry<>nil)
and (LastCacheEntry^.CleanStartPos<=ContextNode.StartPos)
and (LastCacheEntry^.CleanEndPos>=ContextNode.EndPos)
and ((NodeCacheEntryFlags-LastCacheEntry^.Flags)=[])
then begin
// cached result found
Params.SetResult(LastCacheEntry);
{$IFDEF ShowNodeCache}
DbgOut(':::: TFindDeclarationTool.FindIdentifierInContext.FindInNodeCache');
DebugLn(' Ident=',GetIdentifier(Params.Identifier),
' Wanted=[',NodeCacheEntryFlagsAsString(NodeCacheEntryFlags),']',
' Cache=[',NodeCacheEntryFlagsAsString(LastCacheEntry^.Flags),']'
);
DebugLn(' ContextNode=',ContextNode.DescAsString,
' StartPos=',DbgS(ContextNode.StartPos),
' EndPos=',DbgS(ContextNode.EndPos),
' Self=',MainFilename);
DebugLn(' LastCacheEntry(Pos=',DbgS(LastCacheEntry^.CleanStartPos),
'-',DbgS(LastCacheEntry^.CleanEndPos),')');
if (Params.NewNode<>nil) then
DebugLn(' NewTool=',Params.NewCodeTool.MainFilename,
' NewNode=',Params.NewNode.DescAsString)
else
DebugLn(' cache says: identifier does NOT exist');
if CompareSrcIdentifiers(Params.Identifier,'TDefineAction') then begin
NodeCache.WriteDebugReport('NANUNANA: ');
end;
{$ENDIF}
Result:=true;
end;
end;
procedure CacheResult(Found: boolean; EndNode: TCodeTreeNode);
begin
if not Found then exit;
FindIdentifierInContext:=true;
{$IFDEF ShowCollect}
if fdfCollect in Flags then
raise Exception.Create('fdfCollect must never return true');
{$ENDIF}
{$IFDEF ShowFoundIdentifier}
debugln(['CacheResult FOUND ',GetIdentifier(Params.Identifier)]);
Params.WriteDebugReport;
{$ENDIF}
if (FirstSearchedNode=nil) then exit;
if ([fdfDoNotCache,fdfCollect,fdfExtractOperand]*Flags<>[]) then exit;
if ([fodDoNotCache]*Params.NewFlags<>[]) then exit;
if (Params.OnIdentifierFound<>@CheckSrcIdentifier) then exit;
if (Params.FoundProc<>nil) then exit; // do not cache proc searches
// cache result
if (Params.NewNode<>nil) and (Params.NewNode.Desc=ctnProcedure) then begin
DebugLn('NOTE: TFindDeclarationTool.FindIdentifierInContext.CacheResult Node is proc');
// ToDo:
// The search range is from start to end of search.
// This does not work for overloaded procs.
// -> do not cache
exit;
end;
AddResultToNodeCaches(FirstSearchedNode,EndNode,
fdfSearchForward in Flags,Params,SearchRangeFlags);
end;
function CheckResult(NewResult, CallOnIdentifierFound: boolean): boolean;
// returns: true to stop search
// false if search should continue
procedure RaiseNotFound;
var
Identifier: string;
begin
Identifier:=GetIdentifier(Params.Identifier);
if (Identifier='') and (Params.Identifier<>nil)
and (Params.Identifier[0]<>#0) then begin
Identifier:=Params.Identifier[0];
if Identifier='[' then begin
Params.IdentifierTool.RaiseException(20170421200103,ctsDefaultPropertyNotFound);
end;
end;
Params.IdentifierTool.RaiseExceptionFmt(20170421200105,ctsIdentifierNotFound,
[Identifier]);
end;
begin
Result:=true;
FindIdentifierInContext:=NewResult and (not (fdfCollect in Flags));
{$IFDEF ShowCollect}
if fdfCollect in Flags then begin
DebugLn('[TFindDeclarationTool.FindIdentifierInContext.CheckResult] COLLECT CheckResult Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' File="',ExtractFilename(MainFilename)+'"',
' Flags=[',dbgs(Flags)+']',
' NewResult=',DbgS(NewResult),
' CallOnIdentifierFound=',DbgS(CallOnIdentifierFound));
end;
{$ENDIF}
if NewResult then begin
// identifier found
{$IFDEF ShowFoundIdentifier}
debugln(['CheckResult FOUND ',GetIdentifier(Params.Identifier)]);
Params.WriteDebugReport;
{$ENDIF}
if fdfExtractOperand in Flags then
case Params.NewNode.Desc of
ctnVarDefinition, ctnConstDefinition:
with Params do
AddOperandPart(GetIdentifier(@NewCodeTool.Src[NewNode.StartPos]));
ctnProperty,ctnGlobalProperty:
begin
if fdfPropertyResolving in Flags then begin
if not PropNodeIsTypeLess(Params.NewNode)
and ReadTilGetterOfProperty(Params.NewNode) then begin
// continue searching of getter
Params.Identifier := @Src[CurPos.StartPos];
end;
ContextNode := Params.NewNode;
Exit(False);
end else
Params.AddOperandPart(GetIdentifier(Params.Identifier));
end;
ctnProcedure:
begin
Params.AddOperandPart(ExtractProcName(Params.NewNode,[]));
// ToDo: add default parameters
end;
end;
if CallOnIdentifierFound then begin
{debugln(['[TFindDeclarationTool.FindIdentifierInContext.CheckResult] CallOnIdentifierFound Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' StartContext="',StartContextNode.DescAsString,'" "',copy(Src,StartContextNode.StartPos,20),'"',
' File="',ExtractFilename(MainFilename)+'"',
' Flags=[',dbgs(Flags),']'
]);}
IdentFoundResult:=Params.NewCodeTool.DoOnIdentifierFound(Params,
Params.NewNode);
{$IFDEF ShowProcSearch}
DebugLn(['[TFindDeclarationTool.FindIdentifierInContext.CheckResult] DoOnIdentifierFound=',IdentifierFoundResultNames[IdentFoundResult]]);
{$ENDIF}
if (IdentFoundResult=ifrSuccess) then
CacheResult(true,ContextNode);
Result:=IdentFoundResult<>ifrProceedSearch;
if IdentFoundResult<>ifrAbortSearch then exit;
end else begin
if fdfCollect in Flags then
Result:=false;
CacheResult(true,ContextNode);
exit;
end;
end;
if Params.FoundProc<>nil then begin
// there was a proc,
// either the search for the overloaded proc was unsuccessful
// or the searched proc was found in a recursive sub search
// -> return the found proc
if Params.FoundProc^.CacheValid
and (Params.FoundProc^.ProcCompatibility=tcExact) then begin
// stop the search
Result:=true;
end;
FindIdentifierInContext:=true;
{$IFDEF ShowCollect}
if fdfCollect in Flags then
raise Exception.Create('fdfCollect must never return true');
{$ENDIF}
Params.SetResult(Params.FoundProc^.Context.Tool,
Params.FoundProc^.Context.Node);
{$IF defined(ShowProcSearch) or defined(ShowFoundIdentifier)}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] PROC Search ended with only one proc (normal when searching every used unit):');
Params.WriteDebugReport;
{$ENDIF}
exit;
end;
// identifier was not found
if not (fdfExceptionOnNotFound in Flags) then exit;
if (Params.Identifier<>nil)
and not (fdfExceptionOnPredefinedIdent in Flags)
and WordIsPredefinedIdentifier.DoItCaseInsensitive(Params.Identifier)
then begin
Params.SetResult(nil,nil);
exit;
end;
// identifier was not found and exception is wanted
// -> raise exception
if Params.IdentifierTool.IsPCharInSrc(Params.Identifier) then
Params.IdentifierTool.MoveCursorToCleanPos(Params.Identifier);
RaiseNotFound;
end;
procedure MoveContextNodeToChildren;
begin
if (ContextNode.LastChild<>nil) then begin
if not (fdfSearchForward in Flags) then begin
RaiseLastErrorIfInFrontOfCleanedPos(ContextNode.EndPos);
ContextNode:=ContextNode.LastChild;
end else
ContextNode:=ContextNode.FirstChild;
end;
end;
function SearchInGenericParams(GenParamsNode: TCodeTreeNode): boolean;
var
Node: TCodeTreeNode;
begin
Result:=false;
if (GenParamsNode=nil) or (GenParamsNode.Desc<>ctnGenericParams) then exit;
Node:=GenParamsNode.FirstChild;
while Node<>nil do begin
if (fdfCollect in Flags)
or CompareSrcIdentifiers(Node.StartPos,Params.Identifier)
then begin
{$IFDEF ShowTriedIdentifiers}
DebugLn(' SearchInGenericParams Identifier found="',GetIdentifier(@Src[Node.StartPos]),'" at '+CleanPosToStr(Node.StartPos));
{$ENDIF}
// identifier found
Params.SetResult(Self,Node);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then
exit;
end;
Node:=Node.NextBrother;
end;
end;
function SearchInTypeVarConstGlobPropDefinition: boolean;
// returns: true if ok to exit
// false if search should continue
var
NameNode: TCodeTreeNode;
begin
Result:=false;
NameNode:=ContextNode;
if ContextNode.Desc=ctnGenericType then begin
NameNode:=ContextNode.FirstChild;
if NameNode=nil then exit;
end;
if (fdfCollect in Flags)
or CompareSrcIdentifiers(NameNode.StartPos,Params.Identifier)
then begin
{$IFDEF ShowTriedIdentifiers}
DebugLn(' Definition Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
// identifier found
Params.SetResult(Self,ContextNode);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then begin
if (fdfSkipClassForward in Flags)
and (ContextNode.FirstChild<>nil)
and (ContextNode.FirstChild.Desc in AllClasses)
and ((ctnsForwardDeclaration and ContextNode.FirstChild.SubDesc)<>0)
then begin
FindNonForwardClass(Params);
end;
exit;
end;
end;
// search for enums
Params.ContextNode:=ContextNode;
if FindEnumInContext(Params) then begin
Result:=CheckResult(true,false);
end;
end;
function SearchInGenericType: boolean;
// returns: true if ok to exit
// false if search should continue
var
NameNode: TCodeTreeNode;
begin
Result:=false;
NameNode:=ContextNode.FirstChild;
if NameNode=nil then exit;
// try type name
if (fdfCollect in Flags)
or CompareSrcIdentifiers(NameNode.StartPos,Params.Identifier)
then begin
{$IFDEF ShowTriedIdentifiers}
DebugLn(' Definition Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
// identifier found
Params.SetResult(Self,ContextNode);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then begin
if (fdfSkipClassForward in Flags)
and (ContextNode.LastChild.Desc in AllClasses)
and ((ctnsForwardDeclaration and ContextNode.LastChild.SubDesc)<>0)
then begin
FindNonForwardClass(Params);
end;
exit;
end;
end;
// search for enums
Params.ContextNode:=ContextNode;
if FindEnumInContext(Params) then begin
Result:=CheckResult(true,false);
end;
end;
function SearchInTypeOfVarConst: boolean;
// returns: true if ok to exit
// false if search should continue
begin
Result:=false;
//debugln(['SearchInTypeOfVarConst ',ContextNode.Parent.DescAsString]);
if (ContextNode.Parent.Desc in [ctnConstDefinition,ctnVarDefinition])
and (Src[ContextNode.StartPos]='(') then
begin
if FindIdentifierInTypeOfConstant(ContextNode.Parent,Params) then begin
Result:=CheckResult(true,false);
end;
end;
end;
function SearchInEnumLabelDefinition: boolean;
// returns: true if ok to exit
// false if search should continue
begin
Result:=false;
if (fdfCollect in Flags)
or CompareSrcIdentifiers(ContextNode.StartPos,Params.Identifier)
then begin
{$IFDEF ShowTriedIdentifiers}
DebugLn(' Enum/Label Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
// identifier found
Params.SetResult(Self,ContextNode);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then begin
exit;
end;
end;
end;
function SearchInOnBlockDefinition: boolean;
begin
Result:=false;
if ContextNode.FirstChild=nil then exit;
//debugln('SearchInOnBlockDefinition B ',GetIdentifier(@Src[ContextNode.StartPos]));
if (fdfCollect in Flags)
or CompareSrcIdentifiers(ContextNode.FirstChild.StartPos,Params.Identifier)
then begin
{$IFDEF ShowTriedIdentifiers}
DebugLn(' ON Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
// identifier found
Params.SetResult(Self,ContextNode.FirstChild);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then
exit;
end;
end;
function SearchInSourceName: boolean;
// returns: true if ok to exit
// false if search should continue
var
SrcNode: TCodeTreeNode;
begin
Result:=false;
SrcNode:=Tree.Root;
MoveCursorToNodeStart(SrcNode);
ReadNextAtom; // read keyword
if (SrcNode.Desc=ctnProgram) and (not UpAtomIs('PROGRAM')) then exit;
ReadNextAtom; // read name
if (fdfCollect in Flags)
or CompareSrcIdentifiers(CurPos.StartPos,Params.Identifier) then
begin
// identifier found
{$IFDEF ShowTriedIdentifiers}
if not (fdfCollect in Flags) then
DebugLn(' Source Name Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
Params.SetResult(Self,SrcNode,CurPos.StartPos);
Result:=CheckResult(true,true);
if not (fdfCollect in Flags) then
exit;
end;
end;
function SearchDefault: boolean;
begin
Result:=false;
if SearchInSourceName then
exit(true);
if (not (fdfIgnoreUsedUnits in Flags))
and FindIdentifierInHiddenUsedUnits(Params) then begin
Result:=CheckResult(true,false);
end;
end;
function SearchInProperty: boolean;
// search in ctnProperty, not ctnGlobalProperty
// returns: true if ok to exit
// false if search should continue
begin
Result:=false;
if (fdfCollect in Flags)
or (Params.Identifier[0]<>'[') then begin
MoveCursorToNodeStart(ContextNode);
ReadNextAtom; // read keyword 'property'
if UpAtomIs('CLASS') then ReadNextAtom;
ReadNextAtom; // read name
if (fdfCollect in Flags)
or CompareSrcIdentifiers(CurPos.StartPos,Params.Identifier) then begin
// identifier found
{$IFDEF ShowTriedIdentifiers}
DebugLn(' Property Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
Params.SetResult(Self,ContextNode,CurPos.StartPos);
Result:=CheckResult(true,true);
end;
end else begin
// the default property is searched
if PropertyIsDefault(ContextNode) then begin
Params.SetResult(Self,ContextNode);
Result:=CheckResult(true,true);
end;
end;
end;
function LeavingContextIsPermitted: boolean;
begin
Result:=true;
if (not ContextNode.HasAsParent(StartContextNode)) then begin
// searching in a prior node, will leave the start context
if (not (fdfSearchInParentNodes in Flags)) then begin
// searching in any parent context is not permitted
if not ((fdfSearchInAncestors in Flags)
and (ContextNode.Desc in AllClasses)) then begin
// even searching in ancestors contexts is not permitted
// -> there is no prior context accessible any more
// -> identifier not found
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] no prior node accessible ',
' ContextNode=',ContextNode.DescAsString,
' "',StringToPascalConst(copy(Src,ContextNode.StartPos,15)),'"'
);
{$ENDIF}
ContextNode:=nil;
Result:=false;
end;
end;
end;
end;
function SearchInHelpers: Boolean;
var
HelperContext: TFindContext;
Helpers: TFDHelpersList;
HelperKind: TFDHelpersListKind;
HelperIterator: TAVLTreeNode;
begin
Result := False;
SearchInHelpersInTheEnd := False;
if StartContextNode.Desc=ctnObjCClass then
HelperKind:=fdhlkObjCCategory
else
HelperKind:=fdhlkDelphiHelper;
Helpers:=Params.GetHelpers(HelperKind);
if Helpers=nil then exit;
if not Helpers.IterateFromClassNode(StartContextNode,Self,
HelperContext,HelperIterator) then exit;
//debugln(['SearchInHelpers START at least one helper found, iterating...']);
//Helpers.WriteDebugReport;
repeat
//debugln(['SearchInHelpers searching in Helper=',FindContextToString(HelperContext),'...']);
OldFlags := Params.Flags;
try
Params.Flags:=Params.Flags
-[fdfExceptionOnNotFound,fdfIgnoreCurContextNode,fdfSearchInHelpers]
+[fdfIgnoreUsedUnits];
Params.ContextNode := HelperContext.Node;
if HelperContext.Tool.FindIdentifierInContext(Params, IdentFoundResult) then
begin
if (IdentFoundResult = ifrAbortSearch)
or ((IdentFoundResult = ifrSuccess) and CheckResult(true,False))
then
Result := True;
end;
finally
Params.Flags := OldFlags;
end;
until (HelperKind=fdhlkDelphiHelper) or (not Helpers.GetNext(HelperContext,HelperIterator));
//debugln(['SearchInHelpers END']);
end;
function SearchInNamespaces(SourceNamespaceNode: TCodeTreeNode): Boolean;
// SourceNamespaceNode.Desc = ctnUseUnitNamespace
// search all use-unit nodes with the same namespace prefix as SourceNamespaceNode
var
UnitNode, ThisNamespaceNode, TargetNamespaceNode, UsesNode: TCodeTreeNode;
Level, CurLevel: Integer;
InFilename, AnUnitName, FoundNames, FoundName: String;
NewCodeTool: TFindDeclarationTool;
begin
Result := False;
if SourceNamespaceNode.Desc<>ctnUseUnitNamespace then
RaiseException(20170426102058,'');
//debugln(['SearchInNamespaces ',ExtractNode(SourceNamespaceNode.Parent,[]),' ',fdfCollect in Flags]);
if not (fdfCollect in Flags) then begin
// search a specific identifier within a use-unit name
if (SourceNamespaceNode.NextBrother<>nil)
and (
(Params.Identifier=nil) or
CompareSrcIdentifiers(SourceNamespaceNode.NextBrother.StartPos,Params.Identifier))
then begin
Params.SetResult(Self,SourceNamespaceNode.NextBrother);
Result:=CheckResult(true,true);
end;
exit;
end;
// collect all uses-units with same namespace
UsesNode:=SourceNamespaceNode.Parent.Parent;
Level:=1;
while SourceNamespaceNode.PriorBrother<>nil do begin
inc(Level);
SourceNamespaceNode:=SourceNamespaceNode.PriorBrother;
end;
FoundNames:='';
UnitNode := UsesNode.LastChild;
while UnitNode<>nil do
begin
ThisNamespaceNode := SourceNamespaceNode;
TargetNamespaceNode := UnitNode.FirstChild;
CurLevel:=0;
while (ThisNamespaceNode<>nil) and (TargetNamespaceNode<>nil) do
begin
if CompareIdentifierPtrs(
@Src[ThisNamespaceNode.StartPos],
@Src[TargetNamespaceNode.StartPos]) <> 0
then Break;
inc(CurLevel);
if CurLevel=Level then break;
ThisNamespaceNode := ThisNamespaceNode.NextBrother;
TargetNamespaceNode := TargetNamespaceNode.NextBrother;
end;
if CurLevel=Level then
begin
// namespace paths match
//debugln(['SearchInNamespaces Match ',ExtractNode(TargetNamespaceNode.Parent,[])]);
if (TargetNamespaceNode.NextBrother<>nil) then begin
// prefix matches
FoundName:='('+GetIdentifier(@Src[TargetNamespaceNode.NextBrother.StartPos])+')';
if (Pos(FoundName,FoundNames)<1)
and ((Params.Identifier=nil)
or CompareSrcIdentifiers(TargetNamespaceNode.NextBrother.StartPos,Params.Identifier))
then begin
FoundNames:=FoundNames+FoundName;
Params.SetResult(Self,TargetNamespaceNode.NextBrother);
Result:=CheckResult(true,true);
end;
end else begin
// whole unit name matches -> list all interface identifiers
AnUnitName:=ExtractUsedUnitName(UnitNode,@InFilename);
NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,true);
NewCodeTool.FindIdentifierInInterface(Params.IdentifierTool,Params);
end;
end;
if UnitNode.PriorBrother<>nil then
UnitNode := UnitNode.PriorBrother
else if UnitNode.Parent.Desc=ctnImplementation then begin
UnitNode:=FindMainUsesNode;
if UnitNode=nil then break;
UnitNode:=UnitNode.LastChild;
end else
break;
end;
end;
function SearchNextNode: boolean;
const
AbortNoCacheResult = false;
Proceed = true;
begin
repeat
// search for prior node
{$IFDEF ShowTriedIdentifiers}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext.SearchNextNode] Searching prior node of ',ContextNode.DescAsString,' ',dbgstr(copy(Src,ContextNode.StartPos,ContextNode.EndPos-ContextNode.StartPos)));
{$ENDIF}
LastSearchedNode:=ContextNode;
if (ContextNode.Desc in AllClasses) then begin
// after searching in a class definition ...
if (ContextNode.PriorBrother<>nil) and (ContextNode.PriorBrother.Desc=ctnGenericParams)
then begin
// before searching in the ancestors, search in the generic parameters
if SearchInGenericParams(ContextNode.PriorBrother) then begin
FindIdentifierInContext:=true;
{$IFDEF ShowCollect}
if fdfCollect in Flags then
raise Exception.Create('fdfCollect must never return true');
{$ENDIF}
exit(AbortNoCacheResult);
end;
end;
//allow ctnRecordType and ctnTypeTypeBeforeHelper: they can have helpers!
if (fdfSearchInAncestors in Flags) then begin
// after searching in a class definition, search in its ancestors
// ToDo: check for cycles in ancestors
OldFlags := Params.Flags;
Params.Flags:=Params.Flags-[fdfExceptionOnNotFound,fdfSearchInHelpersInTheEnd];
// leaving current class -> check if search in helpers in the end
if SearchInHelpersInTheEnd then
begin
Result := SearchInHelpers;
Params.Flags := OldFlags;
if Result then
begin
FindIdentifierInContext:=true;
Exit(AbortNoCacheResult);
end;
end;
Exclude(Params.Flags,fdfExceptionOnNotFound);
Result:=FindIdentifierInAncestors(ContextNode,Params,IdentFoundResult);
Params.Flags := OldFlags;
if Result then begin
FindIdentifierInContext:=true;
{$IFDEF ShowCollect}
if fdfCollect in Flags then
raise Exception.Create('fdfCollect must never return true');
{$ENDIF}
exit(AbortNoCacheResult);
end;
end;
// if this was a nested class, the identifier can be in the ancestors
// of the enclosing class
Flags:=Flags+[fdfSearchInAncestors];
end else if ContextNode.Desc=ctnClassInheritance then begin
if (StartContextNode=ContextNode)
or StartContextNode.HasAsParent(ContextNode) then
// searching an ancestor => don't search within ancestors
Exclude(Flags,fdfSearchInAncestors);
end;
if (ContextNode=StartContextNode)
and (not (fdfSearchInParentNodes in Flags)) then begin
// startcontext completed => not searching in parents or ancestors
ContextNode:=nil;
exit(Proceed);
end;
if ((not (fdfSearchForward in Flags))
and (ContextNode.PriorBrother<>nil))
or ((fdfSearchForward in Flags)
and (ContextNode.NextBrother<>nil)
and (ContextNode.NextBrother.Desc<>ctnImplementation)) then
begin
// search next in prior/next brother
if not (fdfSearchForward in Flags) then
ContextNode:=ContextNode.PriorBrother
else begin
RaiseLastErrorIfInFrontOfCleanedPos(ContextNode.NextBrother.EndPos);
ContextNode:=ContextNode.NextBrother;
end;
{$IFDEF ShowTriedIdentifiers}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] Searching in Brother ContextNode=',ContextNode.DescAsString);
{$ENDIF}
// it is not always allowed to search in every node on the same lvl:
// -> test if class visibility valid
if ContextNode.Desc in AllClassSections then
break
else if ContextNode.Desc=ctnWithVariable then begin
{ check if StartContextNode is covered by the ContextNode
a WithVariable ranges from the start of its expression
to the end of the with statement
for example:
will be skipped:
with ContextNode do ;
with B do StartContextNode;
will be searched:
with ContextNode, StartContextNode do ;
}
{$IFDEF ShowExprEval}
DebugLn('SearchNextNode WithVar StartContextNode.StartPos=',dbgs(StartContextNode.StartPos),
' ContextNode=',dbgs(ContextNode.StartPos),'-',dbgs(ContextNode.EndPos),
' WithStart=',StringToPascalConst(
copy(copy(Src,ContextNode.StartPos,ContextNode.EndPos-ContextNode.StartPos),1,50)));
{$ENDIF}
if (StartContextNode.StartPos>=ContextNode.StartPos)
and (StartContextNode.StartPos<ContextNode.EndPos) then begin
{$IFDEF ShowExprEval}
debugln(['SearchNextNode WithVar covers startcontext']);
{$ENDIF}
// for example: with ContextNode, StartContextNode do ;
break;
end else begin
// this with statement does not cover the startcontext. For instance:
// with ContextNode do ;
// with B do StartContextNode;
// -> skip it
end;
end else if ContextNode.Desc=ctnOnBlock then begin
// the ctnOnIdentifier is only valid within the ctnOnStatement
// => skip
end else begin
break;
end;
end else if (ContextNode.Parent<>nil)
and ((fdfSearchInParentNodes in Flags)
or (ContextNode.HasAsParent(StartContextNode))) then
begin
// search next in parent
{$IFDEF ShowTriedParentContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] Searching in Parent ',
' old ContextNode=',ContextNode.DescAsString,
' new ContextNode=',ContextNode.Parent.DescAsString
);
{$ENDIF}
ContextNode:=ContextNode.Parent;
case ContextNode.Desc of
ctnTypeSection, ctnVarSection, ctnConstSection, ctnResStrSection,
ctnLabelSection, ctnPropertySection,
ctnInterface, ctnImplementation, ctnProgram, ctnLibrary,
ctnClassPublished,ctnClassPublic,ctnClassProtected,ctnClassPrivate,
ctnClassClassVar,
ctnRecordVariant,
ctnProcedureHead, ctnParameterList,
ctnClassInheritance,ctnHelperFor:
// these codetreenodes build a parent-child-relationship, but
// for pascal it is only a range, hence after searching in the
// children of the last node, search must continue in the children
// of the prior node
;
ctnClass, ctnClassInterface, ctnDispinterface, ctnObject,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass,
ctnRecordType, ctnRecordCase,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnEnumerationType:
// do not search again in this node, go on ...
;
ctnVarDefinition, ctnConstDefinition:
if (ContextNode.Parent<>nil)
and (ContextNode.Parent.Desc=ctnParameterList) then begin
// pascal allows declarations like: 'var a: a;' in parameters
// -> skip variable and search in next context node
;
end else begin
break;
end;
ctnProcedure:
begin
Result:=FindIdentifierInClassOfMethod(ContextNode,Params);
if Result then begin
FindIdentifierInContext:=true;
{$IFDEF ShowCollect}
if fdfCollect in Flags then
raise Exception.Create('fdfCollect must never return true');
{$ENDIF}
exit(AbortNoCacheResult);
end;
end;
else
break;
end;
end else begin
ContextNode:=nil;
break;
end;
until false;
Result:=Proceed;
end;
begin
Result:=false;
InitNodesAndCacheAccess;
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] Start Ident=',
'"'+GetIdentifier(Params.Identifier)+'"',
' Context="'+ContextNode.DescAsString+'" "'+StringToPascalConst(copy(Src,ContextNode.StartPos,20)),'"',
' at '+CleanPosToStr(ContextNode.StartPos,true),
' Flags=['+dbgs(Flags)+']'
);
{$ELSE}
{$IFDEF ShowCollect}
if fdfCollect in Flags then begin
DebugLn(['[TFindDeclarationTool.FindIdentifierInContext] COLLECT Start Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' Context="',ContextNode.DescAsString,'" "',copy(Src,ContextNode.StartPos,20),'"',
' at '+CleanPosToStr(ContextNode.StartPos,true),
' Flags=[',dbgs(Flags),']'
]);
end;
{$ENDIF}
{$ENDIF}
if (ContextNode.Desc=ctnInterface)
and (fdfIgnoreUsedUnits in Flags) then begin
{$IFDEF ShowTriedContexts}
DebugLn(['TFindDeclarationTool.FindIdentifierInContext searching in interface of ',MainFilename]);
{$ENDIF}
Result:=FindIdentifierInInterface(Params.IdentifierTool,Params);
CheckResult(Result,false);
exit;
end;
if (ContextNode.Desc=ctnUseUnitNamespace) then
begin
//search in namespaces
//debugln(['TFindDeclarationTool.FindIdentifierInContext NameSpace ',GetIdentifier(Params.Identifier),' ',ExtractNode(ContextNode,[])]);
if SearchInNamespaces(ContextNode) then exit;
Exit;
end;
// find class helper functions
SearchInHelpersInTheEnd := False;
if (fdfSearchInHelpers in Flags)
and (ContextNode.Desc in [ctnClass,ctnRecordType,ctnTypeType,ctnObjCClass,ctnEnumerationType,ctnRangedArrayType,ctnOpenArrayType])
and (ContextNode.Parent<>nil) and (ContextNode.Parent.Desc = ctnTypeDefinition)
then begin
if (fdfSearchInHelpersInTheEnd in Flags) then
SearchInHelpersInTheEnd := True
else begin
if SearchInHelpers then Exit;
end;
end;
//try
// search in the Tree of this tool
repeat
{$IFDEF ShowTriedIdentifiers}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] Loop Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' Context="',ContextNode.DescAsString,'" "',copy(Src,ContextNode.StartPos,20),'"',
' Flags=[',dbgs(Flags),']'
);
{$ELSE}
{$IFDEF ShowCollect}
if fdfCollect in Flags then begin
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] COLLECT Loop Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' Context="',ContextNode.DescAsString,'" "',copy(Src,ContextNode.StartPos,20),'"',
' Flags=[',dbgs(Flags),']'
);
end;
{$ENDIF}
{$ENDIF}
// search identifier in current context
LastContextNode:=ContextNode;
if not (fdfIgnoreCurContextNode in Flags) then begin
// search in cache
if FindInNodeCache then begin
if CheckResult(Params.NewNode<>nil,Params.NewNode<>nil) then
exit;
end;
if FirstSearchedNode=nil then FirstSearchedNode:=ContextNode;
LastSearchedNode:=ContextNode;
case ContextNode.Desc of
ctnTypeSection, ctnVarSection, ctnConstSection, ctnResStrSection,
ctnLabelSection, ctnPropertySection,
ctnInterface, ctnImplementation,
ctnProgram, ctnLibrary,
ctnClassPublic, ctnClassPrivate, ctnClassProtected, ctnClassPublished,
ctnClassClassVar,
ctnClass, ctnClassInterface, ctnDispinterface, ctnObject,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass,
ctnRecordType, ctnRecordVariant,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnEnumerationType,
ctnParameterList:
// these nodes build a parent-child relationship. But in pascal
// they just define a range and not a context.
// -> search in all children
MoveContextNodeToChildren;
ctnTypeDefinition, ctnVarDefinition, ctnConstDefinition,
ctnGlobalProperty:
if SearchInTypeVarConstGlobPropDefinition then exit;
ctnGenericType:
if SearchInGenericType then exit;
// ctnGenericParams: skip here, it was searched before searching the ancestors
ctnIdentifier:
if (ContextNode.Parent.Desc in [ctnConstDefinition,ctnVarDefinition])
and (ContextNode=ContextNode.Parent.LastChild)
and SearchInTypeOfVarConst then exit;
ctnEnumIdentifier,ctnLabel:
if SearchInEnumLabelDefinition then exit;
ctnProcedure:
begin
IdentifierFoundResult:=
FindIdentifierInProcContext(ContextNode,Params);
if IdentifierFoundResult in [ifrAbortSearch,ifrSuccess] then begin
if CheckResult(IdentifierFoundResult=ifrSuccess,true) then begin
{$IFDEF ShowProcSearch}
DebugLn(['TFindDeclarationTool.FindIdentifierInContext ctnProcedure FOUND, stopping']);
{$ENDIF}
exit;
end;
{$IFDEF ShowProcSearch}
DebugLn(['TFindDeclarationTool.FindIdentifierInContext ctnProcedure FOUND, continue']);
{$ENDIF}
end;
end;
ctnProcedureHead:
begin
BuildSubTreeForProcHead(ContextNode);
if ContextNode.FirstChild<>nil then
ContextNode:=ContextNode.FirstChild; // the ctnParameterList
end;
ctnProperty:
if SearchInProperty then exit;
ctnUsesSection:
begin
if FindIdentifierInUsesSection(ContextNode,Params,True)
and CheckResult(true,false) then
exit;
end;
ctnWithVariable:
begin
if FindIdentifierInWithVarContext(ContextNode,Params)
and CheckResult(true,false) then
exit;
end;
ctnOnBlock:
if SearchInOnBlockDefinition then exit;
ctnPointerType,ctnClassOfType:
begin
// pointer and class-of can be forward definitions
// -> search in both directions
Params.ContextNode:=ContextNode.Parent;
if CheckResult(FindForwardIdentifier(Params,IsForward),false) then
exit;
end;
ctnRecordCase:
begin
// search in variable and variants
MoveContextNodeToChildren;
end;
end;
end else begin
Exclude(Params.Flags,fdfIgnoreCurContextNode);
Exclude(Flags,fdfIgnoreCurContextNode);
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInContext] IgnoreCurContext ');
{$ENDIF}
end;
if LastContextNode=ContextNode then begin
// no special context switch => search next node
if not LeavingContextIsPermitted then break;
if not SearchNextNode then exit;
end;
until ContextNode=nil;
if SearchInHelpersInTheEnd then
begin
if SearchInHelpers then Exit;
end;
if LastSearchedNode=Tree.Root then begin
if SearchDefault then exit;
end;
{except
// unexpected exception
on E: Exception do begin
DebugLn('*** Unexpected Exception during find declaration: ',
E.ClassName,': ',E.Message);
DebugLn(' MainFilename=',MainFilename);
raise;
end;
end;}
// if we are here, the identifier was not found and there was no error
if (FirstSearchedNode<>nil) and (Params.FoundProc=nil)
and ([fdfCollect,fdfExtractOperand]*Flags=[]) then begin
// add result to cache
Params.NewNode:=nil;
Params.NewCodeTool:=nil;
AddResultToNodeCaches(FirstSearchedNode,LastSearchedNode,
fdfSearchForward in Flags,Params,SearchRangeFlags);
end;
CheckResult(false,false);
end;
function TFindDeclarationTool.FindEnumInContext(
Params: TFindDeclarationParams): boolean;
{ search all subnodes for ctnEnumIdentifier
Params:
Identifier
ContextNode // = DeepestNode at Cursor
Result:
true, if enum found
}
var OldContextNode, CurContextNode: TCodeTreeNode;
CollectResult: TIdentifierFoundResult;
SearchEnumIdentifiers: Boolean;
begin
Result:=false;
CurContextNode:=Params.ContextNode;
if CurContextNode=nil then exit;
if CurContextNode.Desc=ctnEnumerationType then
SearchEnumIdentifiers := not (Scanner.GetDirectiveValueAt(sdScopedEnums, CurContextNode.StartPos) = '1')
else
SearchEnumIdentifiers := False;
CurContextNode:=CurContextNode.FirstChild;
while CurContextNode<>nil do begin
if SearchEnumIdentifiers and (CurContextNode.Desc=ctnEnumIdentifier) then begin
if (fdfCollect in Params.Flags) then begin
//debugln('TFindDeclarationTool.FindEnumInContext ',GetIdentifier(@Src[CurContextNode.StartPos]));
CollectResult:=DoOnIdentifierFound(Params,CurContextNode);
if CollectResult=ifrAbortSearch then begin
Result:=false;
exit;
end else if CollectResult=ifrSuccess then begin
Result:=true;
Params.SetResult(Self,CurContextNode);
exit;
end;
end else if CompareSrcIdentifiers(CurContextNode.StartPos,Params.Identifier)
then begin
// identifier found
Result:=true;
Params.SetResult(Self,CurContextNode);
exit;
end;
end;
if CurContextNode.FirstChild<>nil then begin
OldContextNode:=Params.ContextNode;
Params.ContextNode:=CurContextNode;
Result:=FindEnumInContext(Params);
Params.ContextNode:=OldContextNode;
if Result then exit;
end;
CurContextNode:=CurContextNode.NextBrother;
end;
end;
function TFindDeclarationTool.FindContextNodeAtCursor(
Params: TFindDeclarationParams): TFindContext;
{ searches for the context node at a specific cursor pos
Params.Context should contain the deepest node at cursor
if there is no special context, then result is equal to Params.Context }
var
EndPos: integer;
ExprType: TExpressionType;
OldFlags: TFindDeclarationFlags;
begin
EndPos:=CurPos.StartPos;
OldFlags:=Params.Flags;
Params.Flags:=Params.Flags-[fdfFindVariable];
ExprType:=FindExpressionTypeOfTerm(-1,EndPos,Params,false);
Params.Flags:=OldFlags;
if (ExprType.Desc=xtContext) then
Result:=ExprType.Context
else begin
Result:=CleanFindContext;
if fdfExceptionOnNotFound in Params.Flags then begin
MoveCursorToCleanPos(EndPos);
RaiseException(20170421200111,ctsNoContextNodeFoundAtCursor);
end;
end;
end;
function TFindDeclarationTool.FindBaseTypeOfNode(
Params: TFindDeclarationParams; Node: TCodeTreeNode; AliasType: PFindContext;
NodeStack: PCodeTreeNodeStack): TFindContext;
var
MyNodeStack: TCodeTreeNodeStack;
procedure RaiseForwardClassNameLess;
begin
RaiseException(20170421200114,'[TFindDeclarationTool.FindBaseTypeOfNode] '
+'forward class node without name');
end;
procedure RaiseCircleDefs;
begin
Params.NewCodeTool.RaiseException(20170421200117,ctsCircleInDefinitions
+' ('+ctsIdentifier+'='+GetIdentifier(Params.Identifier)+')');
end;
procedure RaiseInternalError;
begin
Params.IdentifierTool.RaiseException(20170421200121,
'[TFindDeclarationTool.FindBaseTypeOfNode]'
+' internal error: not IsPCharInSrc(Params.Identifier) '
+' Params.IdentifierTool.='
+TCodeBuffer(Params.IdentifierTool.Scanner.MainCode).Filename
+' Ident="'+GetIdentifier(Params.Identifier)+'"');
end;
procedure RaiseBaseTypeOfNotFound;
begin
RaiseExceptionFmt(20170421200124,ctsBaseTypeOfNotFound,[GetIdentifier(Params.Identifier)]);
end;
procedure RaiseClassOfWithoutIdentifier;
begin
RaiseExceptionFmt(20170421200133,ctsBaseTypeOfNotFound+' ("class of")',
[GetIdentifier(Params.Identifier)]);
end;
procedure RaiseForwardNotResolved(ClassIdentNode: TCodeTreeNode);
begin
RaiseExceptionFmt(20170421200136,ctsForwardClassDefinitionNotResolved,
[copy(Src,ClassIdentNode.StartPos,
ClassIdentNode.EndPos-ClassIdentNode.StartPos)]);
end;
procedure RaiseClassOfNotResolved(ClassIdentNode: TCodeTreeNode);
begin
MoveCursorToNodeStart(ClassIdentNode);
RaiseExceptionFmt(20170421200141,ctsClassOfDefinitionNotResolved,
[copy(Src,ClassIdentNode.StartPos,
ClassIdentNode.EndPos-ClassIdentNode.StartPos)]);
end;
procedure SearchIdentifier(StartNode: TCodeTreeNode; CleanPos: integer;
out IsPredefined: boolean; var Context: TFindContext);
var
TypeFound: Boolean;
TestContext: TFindContext;
IdentStart: LongInt;
SubParams: TFindDeclarationParams;
ExprType: TExpressionType;
begin
IsPredefined:=false;
SubParams:=TFindDeclarationParams.Create(Params);
try
SubParams.GenParams := Params.GenParams;
IdentStart:=CleanPos;
{$IFDEF ShowTriedBaseContexts}
debugln(['TFindDeclarationTool.FindBaseTypeOfNode.SearchIdentifier Identifier=',GetIdentifier(@Src[IdentStart])]);
{$ENDIF}
SubParams.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound]
+(fdfGlobals*SubParams.Flags);
SubParams.ContextNode:=StartNode.Parent;
if (SubParams.ContextNode.Desc in (AllIdentifierDefinitions))
then begin
// pascal allows things like 'var a: a;' -> skip var definition
Include(SubParams.Flags,fdfIgnoreCurContextNode);
end;
if SubParams.ContextNode.Desc=ctnParameterList then
// skip search in parameter list
SubParams.ContextNode:=SubParams.ContextNode.Parent;
if SubParams.ContextNode.Desc=ctnProcedureHead then
// skip search in proc parameters
SubParams.ContextNode:=SubParams.ContextNode.Parent;
MoveCursorToCleanPos(CleanPos);
ReadNextAtom;
ReadNextAtom;
if (CurPos.Flag=cafPoint) or AtomIsChar('<') then begin
// this is an expression, e.g. A.B or A<B>
Include(SubParams.Flags,fdfFindVariable);
ExprType:=FindExpressionTypeOfTerm(CleanPos,-1,SubParams,false);
if ExprType.Desc=xtContext then begin
if not (ExprType.Context.Node.Desc in [ctnTypeDefinition,ctnGenericType,ctnGenericParameter]) then
begin
// not a type
{$IFDEF ShowTriedBaseContexts}
debugln(['TFindDeclarationTool.FindBaseTypeOfNode.SearchIdentifier expression: type expected but found ',ExprTypeToString(ExprType)]);
{$ENDIF}
MoveCursorToCleanPos(IdentStart);
ReadNextAtom;
RaiseExceptionFmt(20170421200144,ctsStrExpectedButAtomFound,
[ctsTypeIdentifier,GetAtom]);
end;
Context:=ExprType.Context;
end else begin
IsPredefined:=true;
end;
exit;
end;
SubParams.SetIdentifier(Self,@Src[IdentStart],nil);
TypeFound:=FindIdentifierInContext(SubParams);
if TypeFound and (SubParams.NewNode.Desc in [ctnUnit,ctnLibrary,ctnPackage])
then begin
// identifier is a unit
// => type expected
MoveCursorToCleanPos(IdentStart);
ReadNextAtom; // read AUnitName
SaveRaiseCharExpectedButAtomFound(20170421200146,'.');
end;
if TypeFound and (SubParams.NewNode.Desc=ctnGenericParameter) then begin
TypeFound:=SubParams.FindGenericParamType;
end;
if TypeFound then begin
// only types allowed here
TestContext.Tool:=SubParams.NewCodeTool;
TestContext.Node:=SubParams.NewNode;
if not (TestContext.Node.Desc in [ctnTypeDefinition,ctnGenericType,ctnGenericParameter]) then
begin
// not a type
{$IFDEF ShowTriedBaseContexts}
debugln(['TFindDeclarationTool.FindBaseTypeOfNode.SearchIdentifier expected type but found ',TestContext.Node.DescAsString]);
{$ENDIF}
MoveCursorToCleanPos(IdentStart);
ReadNextAtom;
RaiseExceptionFmt(20170421200149,ctsStrExpectedButAtomFound,
[ctsTypeIdentifier,GetAtom]);
end;
Context:=TestContext;
{$IFDEF ShowTriedBaseContexts}
debugln(['TFindDeclarationTool.FindBaseTypeOfNode.SearchIdentifier found ',GetIdentifier(@Src[IdentStart]),' Node=',Context.Node.DescAsString,' ',Context.Tool.CleanPosToStr(Context.Node.StartPos,true)]);
{$ENDIF}
end else begin
// predefined identifier
IsPredefined:=true;
end;
finally
SubParams.Free;
end;
end;
procedure CheckResult(var Context: TFindContext);
var
ResultNode: TCodeTreeNode;
OldFlags: TFindDeclarationFlags;
AliasContext: TFindContext;
Cache: TBaseTypeCache;
begin
if (NodeStack<>nil) and (NodeStack<>@MyNodeStack) then exit; // will be handled by caller
if (Context.Node<>nil) and (Context.Node.Desc in [ctnProcedure,ctnProcedureHead])
and (fdfFunctionResult in Params.Flags) then begin
// Note: do not resolve a constructor here
// because TMyClass.Create should return TMyClass
// and not TObject, where the Create is defined
// a proc -> if this is a function then return the Context type
//debugln(['TFindDeclarationTool.FindBaseTypeOfNode checking function Context: ',Context.Tool.ExtractNode(Context.Node,[])]);
Context.Tool.BuildSubTreeForProcHead(Context.Node,ResultNode);
if (ResultNode<>nil) then begin
// a function or an overloaded operator
// search further for the base type of the function Context type
OldFlags:=Params.Flags;
Exclude(Params.Flags,fdfFunctionResult);
//debugln(['TFindDeclarationTool.FindBaseTypeOfNode searching for function Context type: ',Context.Tool.ExtractNode(DummyNode,[])]);
Context:=Context.Tool.FindBaseTypeOfNode(Params,ResultNode,AliasType);
AliasType:=nil; // aliasing has been done
Params.Flags:=OldFlags;
exit;
end;
end;
if (Context.Node=nil) and (fdfExceptionOnNotFound in Params.Flags) then begin
if (Context.Tool<>nil) and (Params.Identifier<>nil) then begin
// ToDo ppu, dcu
if (not Params.IdentifierTool.IsPCharInSrc(Params.Identifier)) then
RaiseInternalError;
Params.IdentifierTool.MoveCursorToCleanPos(Params.Identifier);
end;
RaiseBaseTypeOfNotFound;
end;
if AliasType<>nil then begin
// follow the base type chain to the first type
// for example: var d: TDateTime; use TDateTime, instead of Double.
AliasContext.Node:=Node;
AliasContext.Tool:=Self;
while AliasContext.Node<>nil do begin
if AliasContext.Node.Desc in [ctnTypeDefinition,ctnGenericType] then begin
{$IF defined(ShowExprEval) or defined(ShowTriedBaseContexts)}
debugln(['TFindDeclarationTool.FindBaseTypeOfNode.CheckResult using alias ',AliasContext.Tool.ExtractDefinitionName(AliasContext.Node),' instead of base type ',Context.Node.DescAsString]);
{$ENDIF}
AliasType^:=AliasContext;
exit;
end;
if AliasContext.Node.Cache is TBaseTypeCache then begin
Cache:=TBaseTypeCache(AliasContext.Node.Cache);
if AliasContext.Node=Cache.NextNode then break;
AliasContext.Node:=Cache.NextNode;
AliasContext.Tool:=TFindDeclarationTool(Cache.NextTool);
end else
break;
end;
end;
end;
var
OldInput: TFindDeclarationInput;
ClassIdentNode: TCodeTreeNode;
TestContext: TFindContext;
OldPos: integer;
SpecializeNode: TCodeTreeNode;
NameNode: TCodeTreeNode;
IsPredefined: boolean;
OldStartFlags: TFindDeclarationFlags;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
//debugln(['TFindDeclarationTool.FindBaseTypeOfNode Flags=[',dbgs(Params.Flags),'] CacheValid=',Node.Cache is TBaseTypeCache]);
if (Node<>nil) and (Node.Cache is TBaseTypeCache) then begin
// base type already cached
Result:=CreateFindContext(TBaseTypeCache(Node.Cache));
CheckResult(Result);
exit;
end;
Result.Node:=Node;
Result.Tool:=Self;
OldStartFlags:=Params.Flags;
Exclude(Params.Flags,fdfTopLvlResolving);
if NodeStack=nil then begin
NodeStack:=@MyNodeStack;
InitializeNodeStack(NodeStack);
end;
try
while (Result.Node<>nil) do begin
if (Result.Node.Cache is TBaseTypeCache) then begin
// base type already cached
if NodeStack^.StackPtr>=0 then
AddNodeToStack(NodeStack,Result.Tool,Result.Node);
Result:=CreateFindContext(TBaseTypeCache(Result.Node.Cache));
break;
end;
{$IFDEF ShowTriedBaseContexts}
DebugLn('[TFindDeclarationTool.FindBaseTypeOfNode] LOOP Result=',Result.Node.DescAsString,' ',Result.Tool.CleanPosToStr(Result.Node.StartPos,true),' Flags=[',dbgs(Params.Flags),']');
{$ENDIF}
if NodeExistsInStack(NodeStack,Result.Node) then begin
// cycle detected
Result.Tool.MoveCursorToNodeStart(Result.Node);
Result.Tool.RaiseException(20170421200151,ctsCircleInDefinitions);
end;
{$IFDEF CheckNodeTool}Result.Tool.CheckNodeTool(Result.Node);{$ENDIF}
if Result.Tool<>Self then begin
{$IFDEF ShowTriedBaseContexts}
DebugLn(['[TFindDeclarationTool.FindBaseTypeOfNode] continuing in ',Result.Tool.MainFilename]);
{$ENDIF}
Result:=Result.Tool.FindBaseTypeOfNode(Params,Result.Node,AliasType,NodeStack);
break;
end;
AddNodeToStack(NodeStack,Result.Tool,Result.Node);
if (Result.Node.Desc in (AllSimpleIdentifierDefinitions+[ctnGenericType]))
then begin
// instead of variable/const/type definition, return the type
TestContext.Node:=FindTypeNodeOfDefinition(Result.Node);
if TestContext.Node=nil then
// some constants and variants do not have a type
break;
Result.Node:=TestContext.Node;
end else
if (Result.Node.Desc in AllClasses)
and ((Result.Node.SubDesc and ctnsForwardDeclaration)>0) then
begin
// this is a forward defined class
// -> search the real class
{$IFDEF ShowTriedBaseContexts}
DebugLn('[TFindDeclarationTool.FindBaseTypeOfNode] Class is forward');
{$ENDIF}
// ToDo: check for cycles in ancestor chain
ClassIdentNode:=Result.Node.Parent;
if (ClassIdentNode=nil)
or (not (ClassIdentNode.Desc in [ctnTypeDefinition,ctnGenericType]))
then begin
MoveCursorToCleanPos(Result.Node.StartPos);
RaiseForwardClassNameLess;
end;
Params.Save(OldInput);
Params.SetIdentifier(Self,@Src[ClassIdentNode.StartPos],
@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,fdfSearchForward,
fdfIgnoreUsedUnits,fdfExceptionOnNotFound,
fdfIgnoreCurContextNode]
+(fdfGlobals*Params.Flags);
Params.ContextNode:=ClassIdentNode;
FindIdentifierInContext(Params);
if (not (Params.NewNode.Desc in [ctnTypeDefinition,ctnGenericType]))
or (Params.NewCodeTool<>Self) then begin
MoveCursorToCleanPos(Result.Node.StartPos);
RaiseForwardNotResolved(ClassIdentNode);
end;
Result.Tool:=Params.NewCodeTool;
Result.Node:=Params.NewNode;
Params.Load(OldInput,true);
end else
if (Result.Node.Desc=ctnClassOfType) and (fdfFindChildren in Params.Flags)
then begin
// this is a 'class of' type
// -> search the real class
{$IFDEF ShowTriedBaseContexts}
DebugLn('[TFindDeclarationTool.FindBaseTypeOfNode] "Class Of"');
{$ENDIF}
// ToDo: check for cycles in ancestor chain
ClassIdentNode:=Result.Node.FirstChild;
if (ClassIdentNode=nil) or (not (ClassIdentNode.Desc=ctnIdentifier))
then begin
MoveCursorToCleanPos(Result.Node.StartPos);
RaiseClassOfWithoutIdentifier;
end;
Params.Save(OldInput);
// first search backwards
Params.SetIdentifier(Self,@Src[ClassIdentNode.StartPos],
@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,
fdfIgnoreCurContextNode]
+(fdfGlobals*Params.Flags)-[fdfExceptionOnNotFound];
Params.ContextNode:=Result.Node.Parent;
if not FindIdentifierInContext(Params) then begin
// then search forwards
Params.Load(OldInput,false);
Params.SetIdentifier(Self,@Src[ClassIdentNode.StartPos],
@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound,
fdfIgnoreCurContextNode,fdfSearchForward]
+(fdfGlobals*Params.Flags);
Params.ContextNode:=Result.Node.Parent;
FindIdentifierInContext(Params);
end;
if not (Params.NewNode.Desc in [ctnTypeDefinition,ctnGenericType]) then
begin
MoveCursorToCleanPos(Result.Node.StartPos);
RaiseClassOfNotResolved(ClassIdentNode);
end;
Result.Tool:=Params.NewCodeTool;
Result.Node:=Params.NewNode;
Params.Load(OldInput,true);
end else
if (Result.Node.Desc=ctnOnIdentifier) and (Result.Node.PriorBrother=nil)
then begin
// this is the ON variable node, the type comes right behind
Result.Node:=Result.Node.NextBrother;
end else if Result.Node.Desc=ctnSrcName then begin
break;
end else if (Result.Node.Desc=ctnIdentifier)
and (Result.Node.Parent.Desc=ctnSrcName) then begin
if (Result.Node.NextBrother=nil) then
Result.Node:=Result.Node.Parent;
break;
end else
if (Result.Node.Desc in [ctnIdentifier,ctnOnIdentifier])
then begin
// this type is just an alias for another type
// -> search the basic type
if Result.Node.Parent=nil then
break;
SearchIdentifier(Result.Node,Result.Node.StartPos,IsPredefined,Result);
if IsPredefined then break;
end else
if (Result.Node.Desc=ctnProperty)
or (Result.Node.Desc=ctnGlobalProperty) then begin
// this is a property -> search the type definition of the property
if MoveCursorToPropType(Result.Node) then begin
// property has a type
SearchIdentifier(Result.Node,CurPos.StartPos,IsPredefined,Result);
if IsPredefined then break;
end else if (Result.Node.Desc=ctnProperty) then begin
// property has no type
// -> search ancestor property
Params.Save(OldInput);
if not MoveCursorToPropName(Result.Node) then break;
OldPos:=CurPos.StartPos;
Params.SetIdentifier(Self,@Src[CurPos.StartPos],nil);
Params.Flags:=[fdfExceptionOnNotFound,fdfSearchInAncestors,fdfSearchInHelpers]
+(fdfGlobalsSameIdent*Params.Flags);
FindIdentifierInAncestors(Result.Node.Parent.Parent,Params);
TestContext.Tool:=Params.NewCodeTool;
TestContext.Node:=Params.NewNode;
Params.Load(OldInput,true);
if Params.NewNode.Desc<>ctnProperty then begin
// ancestor is not a property
MoveCursorToCleanPos(OldPos);
RaiseException(20170421200153,ctsAncestorIsNotProperty);
end;
Result:=TestContext;
end else
break;
end else
if Result.Node.Desc=ctnProcedure then begin
Result.Node:=Result.Node.FirstChild;
break;
end else
if Result.Node.Desc=ctnProcedureHead then begin
break;
end else
if (Result.Node.Desc=ctnTypeType) then begin
if fdfTypeType in Params.Flags then
break; // the type node is wanted, not its real type
// a TypeType is for example 'MyInt = type integer;'
// the context is not the 'type' keyword, but the identifier after it.
Result.Node:=Result.Node.FirstChild;
end else
if (Result.Node.Desc=ctnEnumIdentifier) then begin
// an enum identifier
if fdfEnumIdentifier in Params.Flags then
break; // the enum is wanted, not its type
// an enum identifier, the base type is the enumeration
Result.Node:=Result.Node.Parent;
end else
if (Result.Node.Desc=ctnSpecialize) then begin
// go to the type name of the specialisation
SpecializeNode:=Result.Node;
NameNode:=SpecializeNode.FirstChild;
Result.Node:=NameNode;
if Result.Node=nil then break;
Params.SetGenericParamValues(Self, SpecializeNode);
SearchIdentifier(SpecializeNode,NameNode.StartPos,IsPredefined,Result);
if (Result.Node=nil) or (Result.Node.Desc<>ctnGenericType) then begin
// not a generic
MoveCursorToNodeStart(NameNode);
ReadNextAtom;
RaiseExceptionFmt(20170421200156,ctsStrExpectedButAtomFound,
[ctsGenericIdentifier,GetAtom]);
end;
end else
break;
end;
Params.Flags:=OldStartFlags;
finally
if NodeStack=@MyNodeStack then begin
// cache the result in all nodes
// do not cache the result of generic type
if not Assigned(Params.GenParams.ParamValuesTool) then
CreateBaseTypeCaches(NodeStack,Result);
// free node stack
FinalizeNodeStack(NodeStack);
end;
end;
CheckResult(Result);
{$IFDEF ShowFoundIdentifier}
Debugln(['[TFindDeclarationTool.FindBaseTypeOfNode] END Node=',Node.DescAsString,' Result=',Result.Node.DescAsString]);
{$ENDIF}
end;
function TFindDeclarationTool.FindIdentifierInBasicTypeHelpers(
ExprType: TExpressionTypeDesc; Params: TFindDeclarationParams): Boolean;
var
OldFlags: TFindDeclarationFlags;
FullExprType: TExpressionType;
CHContext: TFindContext;
Helpers: TFDHelpersList;
begin
Helpers:=Params.GetHelpers(fdhlkDelphiHelper);
if Helpers=nil then exit(false);
FullExprType := CleanExpressionType;
FullExprType.Desc := ExprType;
case FullExprType.Desc of
xtConstString: FullExprType.Desc:=GetDefaultStringType;
xtConstOrdInteger: FullExprType.Desc:=xtLongint;
xtConstBoolean: FullExprType.Desc:=xtBoolean;
xtConstReal: FullExprType.Desc:=xtDouble;
end;
//debugln(['TFindDeclarationTool.FindIdentifierInBasicTypeHelpers ',ExprTypeToString(FullExprType)]);
// find class helper functions
CHContext := Helpers.FindFromExprType(FullExprType);
if Assigned(CHContext.Node) and Assigned(CHContext.Tool) then
begin
OldFlags := Params.Flags;
try
Exclude(Params.Flags, fdfExceptionOnNotFound);
Exclude(Params.Flags, fdfIgnoreCurContextNode);
Include(Params.Flags, fdfIgnoreUsedUnits);
Params.ContextNode := CHContext.Node;
Result := CHContext.Tool.FindIdentifierInContext(Params);
finally
Params.Flags := OldFlags;
end;
end else
Result := False;
end;
function TFindDeclarationTool.FindDeclarationAndOverload(
const CursorPos: TCodeXYPosition; out ListOfPCodeXYPosition: TFPList;
Flags: TFindDeclarationListFlags): boolean;
var
CurCursorPos: TCodeXYPosition;
NewTool: TFindDeclarationTool;
NewNode: TCodeTreeNode;
NewPos: TCodeXYPosition;
NewTopLine: integer;
CurTool: TFindDeclarationTool;
OldPositions: TFPList;
NodeList: TFPList;
CleanPos: integer;
AtDefinition: Boolean;
procedure AddPos;
begin
AddCodePosition(OldPositions,NewPos);
if (NodeList.IndexOf(NewNode)>=0) then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['AddPos skip, because Node already in NodList']);
{$ENDIF}
exit;
end;
if (fdlfOneOverloadPerUnit in Flags)
and (NodeList.Count>0)
and (TCodeTreeNode(NodeList[NodeList.Count-1]).GetRoot=NewTool.Tree.Root)
then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['AddPos skip, because in same unit']);
{$ENDIF}
exit;
end;
NodeList.Add(NewNode);
if (fdlfWithoutEmptyProperties in Flags)
and (NewNode.Desc=ctnProperty)
and (NewTool.PropNodeIsTypeLess(NewNode)) then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['AddPos skip, because property has no type']);
{$ENDIF}
exit;
end;
if (fdlfWithoutForwards in Flags) then begin
if (NewNode.Desc in [ctnTypeDefinition,ctnGenericType])
and NewTool.NodeIsForwardDeclaration(NewNode) then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['AddPos skip, because type is forward']);
{$ENDIF}
exit;
end;
if (NewNode.Desc=ctnProcedure)
and ((NewNode.SubDesc and ctnsForwardDeclaration)>0)
and (not NewNode.HasParentOfType(ctnInterface)) then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['AddPos skip, because proc is forward']);
{$ENDIF}
exit;
end;
end;
AddCodePosition(ListOfPCodeXYPosition,NewPos);
end;
function StartPositionAtDefinition: boolean;
begin
if (NewNode.Desc in AllIdentifierDefinitions)
and (PositionInDefinitionName(NewNode,CleanPos)) then
Result:=true
else if (NewNode.Desc in [ctnProcedure,ctnProcedureHead])
and (PositionInProcName(NewNode,false,CleanPos)) then
Result:=true
else if (NewNode.Desc in [ctnProperty,ctnGlobalProperty])
and (PositionInPropertyName(NewNode,CleanPos)) then
Result:=true
else if (NewNode.Desc in AllSourceTypes)
and (PositionInSourceName(CleanPos)) then
Result:=true
else
Result:=false;
end;
function StartPositionAtFunctionResult: boolean;
var
Node: TCodeTreeNode;
begin
Result:=false;
if (NewNode.Desc in [ctnProcedureHead,ctnIdentifier])
and PositionInFuncResultName(NewNode,CleanPos) then begin
Node:=NewNode;
if Node.Desc=ctnProcedureHead then begin
Node:=Node.FirstChild;
if Node=nil then exit;
if Node.Desc=ctnParameterList then Node:=Node.NextBrother;
if Node=nil then exit;
end;
if Node.Desc in [ctnVarDefinition,ctnIdentifier] then begin
// return the function result type or the operator variable name
NewNode:=Node;
Result:=true;
end;
end;
end;
begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['TFindDeclarationTool.FindDeclarationAndOverload START']);
{$ENDIF}
Result:=true;
ListOfPCodeXYPosition:=nil;
NewTool:=nil;
NewNode:=nil;
OldPositions:=nil;
NodeList:=nil;
ActivateGlobalWriteLock;
try
BuildTreeAndGetCleanPos(trTillCursorSection,lsrEnd,CursorPos,CleanPos,[]);
NodeList:=TFPList.Create;
NewTool:=Self;
NewNode:=BuildSubTreeAndFindDeepestNodeAtPos(CleanPos,true);
NewPos:=CursorPos;
AtDefinition:=StartPositionAtDefinition;
if AtDefinition then begin
AddPos;
if fdlfIfStartIsDefinitionStop in Flags then begin
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['TFindDeclarationTool.FindDeclarationAndOverload AtDefiniton and fdlfIfStartIsDefinitionStop in Flags']);
{$ENDIF}
exit;
end;
end;
if StartPositionAtFunctionResult then begin
AddPos;
// the function result has no overloads => stop search
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['TFindDeclarationTool.FindDeclarationAndOverload function result has no overloads']);
{$ENDIF}
exit;
end;
if NewNode.Desc in AllSourceTypes then begin
// the unit name has no overloads => stop search
{$IFDEF VerboseFindDeclarationAndOverload}
debugln(['TFindDeclarationTool.FindDeclarationAndOverload unit name has no overload']);
{$ENDIF}
exit;
end;
CurCursorPos:=CursorPos;
CurTool:=Self;
try
while CurTool.FindDeclaration(CurCursorPos,DefaultFindSmartFlags
+[fsfSearchSourceName],
NewTool,NewNode,NewPos,NewTopLine) do
begin
if IndexOfCodePosition(OldPositions,@NewPos)>=0 then break;
AddPos;
CurCursorPos:=NewPos;
CurTool:=NewTool;
{$IFDEF VerboseFindDeclarationAndOverload}
debugln('TFindDeclarationTool.FindDeclarationAndOverload Self="',MainFilename,'" ');
if CurCursorPos.Code<>nil then
debugln(' CurCursorPos=',CurCursorPos.Code.Filename,' ',dbgs(CurCursorPos.X),',',dbgs(CurCursorPos.Y));
if CurTool<>nil then
debugln(' CurTool=',CurTool.MainFilename);
{$ENDIF}
if (CurTool=nil) then exit;
end;
except
// ignore normal errors
on E: ECodeToolError do ;
on E: ELinkScannerError do ;
end;
finally
FreeListOfPCodeXYPosition(OldPositions);
NodeList.Free;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.FindIdentifierContextsAtStatement(CleanPos: integer;
out IsSubIdentifier: boolean; out ListOfPFindContext: TFPList): boolean;
var
Params: TFindDeclarationParams;
CursorNode: TCodeTreeNode;
Node: TCodeTreeNode;
Context: TFindContext;
WithNode: TCodeTreeNode;
ExprType: TExpressionType;
begin
Result:=false;
IsSubIdentifier:=false;
ListOfPFindContext:=nil;
CursorNode:=FindDeepestNodeAtPos(CleanPos,true);
if not (CursorNode.Desc in AllPascalStatements) then begin
debugln(['TFindDeclarationTool.FindIdentifierContextsAtStatement CursorNode.Desc=',CursorNode.DescAsString]);
exit;
end;
// check expression in front
MoveCursorToCleanPos(CleanPos);
ReadPriorAtom;
if CurPos.Flag=cafPoint then begin
// sub identifier
// for example A.Identifier
IsSubIdentifier:=true;
// search the context of A and add it to the ListOfPFindContext
Params:=TFindDeclarationParams.Create(Self, CursorNode);
try
Params.ContextNode:=CursorNode;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFunctionResult];
ExprType:=FindExpressionTypeOfTerm(-1,CleanPos,Params,false);
finally
Params.Free;
end;
if ExprType.Desc=xtContext then
AddFindContext(ListOfPFindContext,ExprType.Context);
end else begin
// not a sub identifier
BuildSubTree(CursorNode);
CursorNode:=FindDeepestNodeAtPos(CursorNode,CleanPos,true);
Node:=CursorNode;
while Node<>nil do begin
case Node.Desc of
ctnWithStatement:
begin
// add all With contexts
WithNode:=Node.Parent;
while WithNode<>nil do begin
if WithNode.Desc<>ctnWithVariable then break;
Params:=TFindDeclarationParams.Create(Self, WithNode);
try
Params.ContextNode:=WithNode;
Params.Flags:=[fdfExceptionOnNotFound,fdfSearchInAncestors,fdfSearchInHelpers,
fdfSearchInParentNodes,fdfFunctionResult,fdfIgnoreCurContextNode,
fdfFindChildren];
ExprType:=FindExpressionResultType(Params,WithNode.StartPos,-1);
if ExprType.Desc=xtContext then
AddFindContext(ListOfPFindContext,ExprType.Context);
finally
Params.Free;
end;
WithNode:=WithNode.PriorBrother;
end;
end;
ctnProcedure:
begin
// add procedure context
Context.Node:=Node;
Context.Tool:=Self;
AddFindContext(ListOfPFindContext,Context);
if NodeIsMethodBody(Node) then begin
// add class context
Context.Node:=FindClassNodeForMethodBody(Node,true,false);
if Context.Node<>nil then begin
Context.Tool:=Self;
AddFindContext(ListOfPFindContext,Context);
end;
end;
end;
ctnImplementation:
begin
Context.Node:=Node;
Context.Tool:=Self;
AddFindContext(ListOfPFindContext,Context);
end;
end;
Node:=Node.Parent;
end;
end;
Result:=true;
end;
function TFindDeclarationTool.FindIdentifierInAncestors(
ClassNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
var
IdentFoundResult: TIdentifierFoundResult;
begin
Result := FindIdentifierInAncestors(ClassNode, Params, IdentFoundResult{%H-});
end;
function TFindDeclarationTool.FindClassAndAncestors(ClassNode: TCodeTreeNode;
var ListOfPFindContext: TFPList; ExceptionOnNotFound: boolean): boolean;
var
Params: TFindDeclarationParams;
function Search: boolean;
var
CurTool: TFindDeclarationTool;
FoundContext: TFindContext;
begin
CurTool:=Self;
while CurTool.FindAncestorOfClass(ClassNode,Params,true) do begin
if (Params.NewCodeTool=nil) then break;
FoundContext.Tool:=Params.NewCodeTool;
FoundContext.Node:=Params.NewNode;
if IndexOfFindContext(ListOfPFindContext,@FoundContext)>=0 then break;
AddFindContext(ListOfPFindContext,FoundContext);
//debugln('TFindDeclarationTool.FindClassAndAncestors FoundContext=',DbgsFC(FoundContext));
CurTool:=Params.NewCodeTool;
ClassNode:=Params.NewNode;
if (ClassNode=nil)
or (not (ClassNode.Desc in AllClasses)) then
break;
end;
Result:=true;
end;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ClassNode);{$ENDIF}
Result:=false;
if (ClassNode=nil) or (not (ClassNode.Desc in AllClasses))
or (ClassNode.Parent=nil)
or (not (ClassNode.Parent.Desc in [ctnTypeDefinition,ctnGenericType])) then
exit;
AddFindContext(ListOfPFindContext,CreateFindContext(Self,ClassNode));
Params:=TFindDeclarationParams.Create;
ActivateGlobalWriteLock;
try
if ExceptionOnNotFound then
Result:=Search
else begin
try
Result:=Search;
except
// catch syntax errors
on E: ECodeToolError do ;
on E: ELinkScannerError do ;
end;
end;
finally
DeactivateGlobalWriteLock;
Params.Free;
end;
end;
procedure TFindDeclarationTool.FindHelpersInContext(
Params: TFindDeclarationParams);
var
Node: TCodeTreeNode;
begin
Node:=Params.StartNode;
Params.FNeedHelpers:=false;
while Node<>nil do
begin
case Node.Desc of
ctnClassHelper, ctnRecordHelper, ctnTypeHelper:
if (Node.Parent.Desc = ctnTypeDefinition) then
Params.GetHelpers(fdhlkDelphiHelper,true).AddFromHelperNode(Node, Self,
False{ keep last found Helper }
);
ctnObjCCategory:
if (Node.Parent.Desc = ctnTypeDefinition) then
Params.GetHelpers(fdhlkObjCCategory,true).AddFromHelperNode(Node, Self, False);
ctnUsesSection:
FindHelpersInUsesSection(Node, Params);
end;
Node := Node.Prior;
end;
end;
procedure TFindDeclarationTool.FindHelpersInInterface(
AskingTool: TFindDeclarationTool; Params: TFindDeclarationParams);
var
HelperKind: TFDHelpersListKind;
Cache: TFDHelpersList;
begin
// build tree for pascal source
if not BuildInterfaceIdentifierCache(true) then exit;
if (AskingTool<>Self) and (AskingTool<>nil) then
begin
AskingTool.AddToolDependency(Self);
for HelperKind in TFDHelpersListKind do begin
Cache:=FInterfaceHelperCache[HelperKind];
if (Cache<>nil) and (Cache.Count>0) then
Params.GetHelpers(HelperKind,true).AddFromList(FInterfaceHelperCache[HelperKind]);
end;
end;
end;
procedure TFindDeclarationTool.FindHelpersInUsesSection(
UsesNode: TCodeTreeNode; Params: TFindDeclarationParams);
var
NewCodeTool: TFindDeclarationTool;
Node: TCodeTreeNode;
AnUnitName: string;
InFilename: string;
begin
// search in units
//debugln(['TFindDeclarationTool.FindHelpersInUsesSection START ',CleanPosToStr(UsesNode.StartPos,true),' Main=',MainFilename]);
Node:=UsesNode.LastChild;
while Node<>nil do begin
AnUnitName:=ExtractUsedUnitName(Node,@InFilename);
if AnUnitName<>'' then begin
//debugln(['TFindDeclarationTool.FindHelpersInUsesSection ',CleanPosToStr(Node.StartPos),' AnUnitName="',AnUnitName,'" in "',InFilename,'"']);
NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,false);
if NewCodeTool<>nil then begin
// search the identifier in the interface of the used unit
NewCodeTool.FindHelpersInInterface(Self,Params);
end;
end;
Node:=Node.PriorBrother;
end;
end;
function TFindDeclarationTool.FindContextClassAndAncestorsAndExtendedClassOfHelper
(const CursorPos: TCodeXYPosition; var ListOfPFindContext: TFPList): boolean;
// returns a list of nodes of AllClasses (ctnClass, ...)
var
CleanCursorPos: integer;
ANode: TCodeTreeNode;
ClassNode: TCodeTreeNode;
ExtendedClassExpr: TExpressionType;
begin
Result:=false;
ListOfPFindContext:=nil;
ActivateGlobalWriteLock;
try
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos]);
// find class node
ANode:=FindDeepestNodeAtPos(CleanCursorPos,true);
if (ANode.GetNodeOfType(ctnClassInheritance)<>nil) then
exit;
ClassNode:=FindClassNode(ANode);
if (ClassNode=nil) or (ClassNode.Parent=nil)
or (not (ClassNode.Parent.Desc in [ctnTypeDefinition,ctnGenericType])) then
exit;
//debugln('TFindDeclarationTool.FindContextClassAndAncestors A ClassName=',ExtractClassName(ClassNode,false));
// add class and ancestors type definition to ListOfPCodeXYPosition
if not FindClassAndAncestors(ClassNode,ListOfPFindContext,true)
then exit;
//find extended class node
ExtendedClassExpr := FindExtendedExprOfHelper(ClassNode);
if ((ExtendedClassExpr.Desc=xtContext) and (ExtendedClassExpr.Context.Tool<>nil) and
(ExtendedClassExpr.Context.Node<>nil) and (ExtendedClassExpr.Context.Node.Desc=ctnClass)) then
begin
if not ExtendedClassExpr.Context.Tool.FindClassAndAncestors(ExtendedClassExpr.Context.Node,ListOfPFindContext,true)
then exit;
end;
//debugln('TFindDeclarationTool.FindContextClassAndAncestors List: ',ListOfPFindContextToStr(ListOfPFindContext));
finally
DeactivateGlobalWriteLock;
end;
Result:=true;
end;
function TFindDeclarationTool.FindDefaultAncestorOfClass(
ClassNode: TCodeTreeNode; Params: TFindDeclarationParams;
FindClassContext: boolean): boolean;
var
OldInput: TFindDeclarationInput;
AncestorNode, ClassIdentNode: TCodeTreeNode;
AncestorContext: TFindContext;
BaseClassName: PChar;
procedure RaiseBaseClassNotFound;
begin
MoveCursorToNodeStart(ClassNode);
if BaseClassName='TObject' then
RaiseException(20170421200159,ctsDefaultClassAncestorTObjectNotFound)
else if BaseClassName='IInterface' then
RaiseException(20170421200202,ctsDefaultInterfaceAncestorIInterfaceNotFound)
else if BaseClassName='IDispatch' then
RaiseException(20170421200205,ctsDefaultDispinterfaceAncestorIDispatchNotFound)
else if BaseClassName='JLObject' then
RaiseException(20170421200207,ctsDefaultJavaClassAncestorJLObjectNotFound)
else
RaiseExceptionFmt(20170421200210,ctsDefaultAncestorNotFound, [BaseClassName]);
end;
begin
//debugln(['TFindDeclarationTool.FindAncestorOfClass ',CleanPosToStr(ClassNode.StartPos,true)]);
{$IFDEF CheckNodeTool}CheckNodeTool(ClassNode);{$ENDIF}
if (ClassNode=nil) or (not (ClassNode.Desc in AllClasses))
then
RaiseException(20170421200213,'[TFindDeclarationTool.FindDefaultAncestorOfClass] '
+' invalid classnode');
Result:=false;
// ToDo: ppu, dcu
// no ancestor class specified
ClassIdentNode:=ClassNode.Parent;
// check class name
if (ClassIdentNode<>nil)
and (not (ClassIdentNode.Desc in [ctnTypeDefinition,ctnGenericType])) then
begin
debugln(['TFindDeclarationTool.FindDefaultAncestorOfClass not a type']);
exit;
end;
BaseClassName:=nil;
case ClassNode.Desc of
ctnClass:
if Scanner.Values.IsDefined('CPUJVM') then
BaseClassName:='JLObject'
else if (Scanner.PascalCompiler=pcPas2js)
and (FindClassExternalNode(ClassNode)<>nil) then
exit // external root class has no ancestor
else
BaseClassName:='TObject';
ctnDispinterface:
// default interface is IDispatch
BaseClassName:='IDispatch';
ctnClassInterface:
begin
if Scanner.Values.IsDefined('CPUJVM') then
exit; // JVM has no default interface
// Delphi has as default interface IInterface
// FPC has as default interface IUnknown and an alias IInterface = IUnknown
if CompareSrcIdentifiers(ClassIdentNode.StartPos,'IUnknown') then exit;
BaseClassName:='IInterface';
end
else
exit; // has no default ancestor (e.g. record)
end;
if CompareSrcIdentifiers(ClassIdentNode.StartPos,BaseClassName) then
exit; // this is already the base class
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindAncestorOfClass] ',
' search default ancestor class '+BaseClassName);
{$ENDIF}
// search default ancestor
Params.Save(OldInput);
Params.Flags:=[fdfSearchInParentNodes,fdfIgnoreCurContextNode,
fdfExceptionOnNotFound]
+(fdfGlobals*Params.Flags)
-[fdfTopLvlResolving];
Params.SetIdentifier(Self,BaseClassName,nil);
Params.ContextNode:=ClassNode;
if not FindIdentifierInContext(Params) then
RaiseBaseClassNotFound;
// check result
if not (Params.NewNode.Desc in [ctnTypeDefinition,ctnGenericType]) then
RaiseBaseClassNotFound;
// search ancestor class context
if FindClassContext then begin
AncestorNode:=Params.NewNode;
Params.Flags:=Params.Flags+[fdfFindChildren];
AncestorContext:=Params.NewCodeTool.FindBaseTypeOfNode(Params,
AncestorNode);
Params.SetResult(AncestorContext);
// check result
if Params.NewNode.Desc<>ClassNode.Desc then
RaiseBaseClassNotFound;
end;
Result:=true;
Params.Load(OldInput,true);
end;
{-------------------------------------------------------------------------------
function TFindDeclarationTool.FindReferences(const CursorPos: TCodeXYPosition;
SkipComments: boolean; var ListOfPCodeXYPosition: TFPList): boolean;
Search for all identifiers in current unit, referring to the declaration
at CursorPos.
-------------------------------------------------------------------------------}
function TFindDeclarationTool.FindReferences(const CursorPos: TCodeXYPosition;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean;
var
DeclarationFound: boolean;
Identifier: string;
CleanDeclCursorPos: integer;
DeclarationTool: TFindDeclarationTool;
DeclarationNode: TCodeTreeNode;
AliasDeclarationNode: TCodeTreeNode; // if exists: always in front of DeclarationNode
Params: TFindDeclarationParams;
PosTree: TAVLTree; // tree of PChar positions in Src
ReferencePos: TCodeXYPosition;
MinPos, MaxPos: Integer;
CursorNode: TCodeTreeNode;
UnitStartFound, Found: Boolean;
StartPos: integer; // keep this here, it is modified at several places
procedure AddReference(ACleanPos: integer);
var
p: PChar;
begin
if PosTree=nil then
PosTree:=TAVLTree.Create;
p:=@Src[ACleanPos];
//debugln('TFindDeclarationTool.FindReferences.AddReference ',CleanPosToStr(ACleanPos),' ',dbgs(PosTree.Find(p)=nil),' Code=',dbgstr(copy(Src,ACleanPos-8,8)+'|'+dbgstr(copy(Src,ACleanPos,5))));
if PosTree.Find(p)=nil then
PosTree.Add(p);
end;
procedure AddNodeReference(Node: TCodeTreeNode);
var
p: LongInt;
begin
p:=Node.StartPos;
if Node.Desc in [ctnProcedure,ctnProcedureHead] then begin
MoveCursorToProcName(Node,true);
p:=CurPos.StartPos;
end else if Node.Desc in [ctnProperty,ctnGlobalProperty] then begin
MoveCursorToPropName(Node);
p:=CurPos.StartPos;
end;
AddReference(p);
end;
procedure UseProcHead(var Node: TCodeTreeNode);
begin
if Node=nil then exit;
if (Node.Desc=ctnProcedure)
and (Node.FirstChild<>nil)
and (Node.FirstChild.Desc=ctnProcedureHead) then
Node:=Node.FirstChild;
end;
procedure ReadIdentifier(IsComment: boolean);
var
IdentStartPos: Integer;
IdentEndPos: integer;
begin
if (not IsComment) then
UnitStartFound:=true;
IdentStartPos:=StartPos;
IdentEndPos:=IdentStartPos;
while (IdentEndPos<=MaxPos) and (IsIdentChar[Src[IdentEndPos]]) do
inc(IdentEndPos);
StartPos:=IdentEndPos;
//debugln(['ReadIdentifier ',CleanPosToStr(IdentStartPos,true),' ',copy(Src,IdentStartPos,IdentEndPos-IdentStartPos),' ',CompareIdentifiers(PChar(Pointer(Identifier)),@Src[IdentStartPos])]);
if IdentEndPos-IdentStartPos<>length(Identifier) then exit;
if CompareIdentifiers(PChar(Pointer(Identifier)),@Src[IdentStartPos])<>0 then exit;
if IsComment and (SkipComments or (not UnitStartFound)) then exit;
{debugln(['Identifier with same name found at: ',
IdentStartPos,'=',CleanPosToStr(StartPos),' ',GetIdentifier(@Src[IdentStartPos]),
' CleanDeclCursorPos=',CleanDeclCursorPos,
' MaxPos=',MaxPos,
' IsComment=',IsComment,
' SkipComments=',SkipComments,
' UnitStartFound=',UnitStartFound
]);}
CursorNode:=BuildSubTreeAndFindDeepestNodeAtPos(IdentStartPos,true);
//debugln(' CursorNode=',CursorNode.DescAsString,' Forward=',dbgs(CursorNode.SubDesc and ctnsForwardDeclaration));
if (DeclarationTool=Self)
and ((IdentStartPos=CleanDeclCursorPos) or (CursorNode=AliasDeclarationNode))
then begin
// declaration itself found
//debugln(['ReadIdentifier declaration itself found, adding ...']);
AddReference(IdentStartPos)
end
else if CleanPosIsDeclarationIdentifier(IdentStartPos,CursorNode) then
// this identifier is another declaration with the same name
else begin
// find declaration
if Params=nil then
Params:=TFindDeclarationParams.Create(Self, CursorNode)
else
Params.Clear;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfIgnoreCurContextNode];
Params.ContextNode:=CursorNode;
//debugln(copy(Src,Params.ContextNode.StartPos,200));
Params.SetIdentifier(Self,@Src[IdentStartPos],@CheckSrcIdentifier);
// search identifier in comment -> if not found, this is no bug
// => silently ignore
try
Found:=FindDeclarationOfIdentAtParam(Params);
except
on E: ECodeToolError do begin
if E.Sender<>Self then begin
// there is an error in another unit, which prevents searching
// stop further searching in this unit
raise;
end;
// continue
end;
on E: Exception do
raise;
end;
//debugln(' Found=',dbgs(Found));
if Found and (Params.NewNode<>nil) then begin
UseProcHead(Params.NewNode);
//debugln('Context=',Params.NewNode.DescAsString,' ',dbgs(Params.NewNode.StartPos),' ',dbgs(DeclarationNode.StartPos));
if (Params.NewNode=DeclarationNode)
or (Params.NewNode=AliasDeclarationNode) then begin
//debugln(['ReadIdentifier reference found, adding ...']);
AddReference(IdentStartPos);
end;
end;
end;
end;
procedure SearchIdentifiers;
var
CommentLvl: Integer;
InStrConst: Boolean;
begin
StartPos:=MinPos;
UnitStartFound:=false;
while StartPos<=MaxPos do begin
case Src[StartPos] of
'{':
begin
inc(StartPos);
if (StartPos<=MaxPos) and (Src[StartPos]=#3) then begin
// codetools skip comment {#3 #3}
inc(StartPos);
while (StartPos<=MaxPos) do begin
if (Src[StartPos]=#3) and (StartPos<MaxPos) and (Src[StartPos+1]='}')
then begin
inc(StartPos,2);
break;
end;
inc(StartPos);
end;
end else begin
// pascal comment {}
CommentLvl:=1;
InStrConst:=false;
while StartPos<=MaxPos do begin
case Src[StartPos] 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
ReadIdentifier(true);
dec(StartPos);
end;
'''':
InStrConst:=not InStrConst;
#10,#13:
InStrConst:=false;
end;
inc(StartPos);
end;
inc(StartPos);
//debugln(StartPos,' ',copy(Src,CommentStart,StartPos-CommentStart));
end;
end;
'/': // Delphi comment
if (Src[StartPos+1]<>'/') then begin
inc(StartPos);
end else begin
inc(StartPos,2);
InStrConst:=false;
while (StartPos<=MaxPos) do begin
case Src[StartPos] of
#10,#13:
break;
'a'..'z','A'..'Z','_':
if not InStrConst then begin
ReadIdentifier(true);
dec(StartPos);
end;
'''':
InStrConst:=not InStrConst;
end;
inc(StartPos);
end;
inc(StartPos);
if (StartPos<=MaxPos) and (Src[StartPos] in [#10,#13])
and (Src[StartPos-1]<>Src[StartPos]) then
inc(StartPos);
end;
'(': // turbo pascal comment
if (Src[StartPos+1]<>'*') then begin
inc(StartPos);
end else begin
inc(StartPos,3);
InStrConst:=false;
while (StartPos<=MaxPos) do begin
case Src[StartPos] of
')':
if Src[StartPos-1]='*' then break;
'a'..'z','A'..'Z','_':
if not InStrConst then begin
ReadIdentifier(true);
dec(StartPos);
end;
'''':
InStrConst:=not InStrConst;
#10,#13:
InStrConst:=false;
end;
inc(StartPos);
end;
inc(StartPos);
end;
'a'..'z','A'..'Z','_':
ReadIdentifier(false);
'''':
begin
// skip string constant
inc(StartPos);
while (StartPos<=MaxPos) do begin
if (not (Src[StartPos] in ['''',#10,#13])) then
inc(StartPos)
else begin
inc(StartPos);
break;
end;
end;
end;
else
inc(StartPos);
end;
end;
end;
function GetDeclarationTool: boolean;
begin
Result:=false;
DeclarationTool:=nil;
if Assigned(FOnGetCodeToolForBuffer) then
DeclarationTool:=FOnGetCodeToolForBuffer(Self,CursorPos.Code,true)
else if CursorPos.Code=TObject(Scanner.MainCode) then
DeclarationTool:=Self;
if DeclarationTool=nil then begin
debugln('WARNING: TFindDeclarationTool.FindReferences DeclarationTool=nil');
exit;
end;
Result:=true;
end;
function FindDeclarationNode: boolean;
const
ProcAttr = [phpInUpperCase,phpAddClassName,phpWithVarModifiers];
var
Node: TCodeTreeNode;
CommentStart: integer;
CommentEnd: integer;
p: LongInt;
begin
if DeclarationFound then exit(true);
Result:=false;
// find the main declaration node and identifier
DeclarationTool.BuildTreeAndGetCleanPos(CursorPos,CleanDeclCursorPos);
DeclarationNode:=DeclarationTool.BuildSubTreeAndFindDeepestNodeAtPos(
CleanDeclCursorPos,true);
Identifier:=DeclarationTool.ExtractIdentifier(CleanDeclCursorPos);
if Identifier='' then begin
//debugln('FindDeclarationNode Identifier="',Identifier,'"');
exit;
end;
UseProcHead(DeclarationNode);
if DeclarationTool=Self then begin
//debugln(['FindDeclarationNode adding DeclarationNode ...']);
AddNodeReference(DeclarationNode);
end;
// find alias declaration node
//debugln('FindDeclarationNode DeclarationNode=',DeclarationNode.DescAsString);
AliasDeclarationNode:=nil;
case DeclarationNode.Desc of
ctnProcedure,ctnProcedureHead:
begin
Node:=DeclarationNode;
if DeclarationNode.Desc=ctnProcedureHead then
Node:=Node.Parent;
AliasDeclarationNode:=DeclarationTool.FindCorrespondingProcNode(
Node,ProcAttr);
end;
ctnVarDefinition:
if DeclarationNode.HasParentOfType(ctnProcedureHead) then begin
AliasDeclarationNode:=FindCorrespondingProcParamNode(DeclarationNode,ProcAttr);
end;
ctnTypeDefinition:
if NodeIsForwardType(DeclarationNode) then
AliasDeclarationNode:=DeclarationTool.FindTypeOfForwardNode(DeclarationNode)
else
AliasDeclarationNode:=DeclarationTool.FindForwardTypeNode(DeclarationNode,true);
end;
if AliasDeclarationNode=DeclarationNode then
AliasDeclarationNode:=nil;
if AliasDeclarationNode<>nil then begin
UseProcHead(AliasDeclarationNode);
if DeclarationTool=Self then begin
//debugln(['FindDeclarationNode adding alias node ...']);
AddNodeReference(AliasDeclarationNode);
end;
if AliasDeclarationNode.StartPos>DeclarationNode.StartPos then begin
Node:=AliasDeclarationNode;
AliasDeclarationNode:=DeclarationNode;
DeclarationNode:=Node;
end;
//debugln('FindDeclarationNode AliasDeclarationNode=',AliasDeclarationNode.DescAsString,' ',DeclarationTool.CleanPosToStr(AliasDeclarationNode.StartPos,DeclarationTool<>Self));
end;
// search comment in front of declaration
//debugln(['FindDeclarationNode search comment in front: ',DeclarationTool=Self,' SkipComments=',SkipComments,' Identifier=',Identifier]);
if (DeclarationTool=Self)
and (not SkipComments)
and FindCommentInFront(DeclarationNode.StartPos,Identifier,
true,false,false,true,true,CommentStart,CommentEnd)
then begin
//debugln(['FindDeclarationNode Comment="',dbgstr(copy(Src,CommentStart,CommentEnd)),'"']);
p:=CommentStart;
if (Src[p]='{') then begin
inc(p);
while (p<=SrcLen) and IsSpaceChar[Src[p]] do inc(p);
if (p<=SrcLen) and (CompareIdentifiers(@Src[p],PChar(Identifier))=0)
then begin
//debugln(['FindDeclarationNode comment in front']);
AddReference(p);
end;
end;
end;
DeclarationFound:=true;
Result:=true;
end;
procedure LimitScope(UseNode: TCodeTreeNode);
var
Node: TCodeTreeNode;
StartNode: TCodeTreeNode;
begin
MinPos:=Tree.FindFirstPosition;
MaxPos:=Tree.FindLastPosition;
if MaxPos>SrcLen then MaxPos:=SrcLen;
if DeclarationTool<>Self then begin
MinPos:=UseNode.Parent.EndPos;
exit;
end;
StartNode:=DeclarationNode;
if (AliasDeclarationNode<>nil) then
StartNode:=AliasDeclarationNode;
Node:=StartNode;
while Node<>nil do begin
case Node.Desc of
ctnImplementation:
// only search in implementation
if MinPos<Node.StartPos then MinPos:=Node.StartPos;
ctnTypeDefinition:
begin
// Note: types can be used before declaration
end;
ctnVarDefinition,ctnConstDefinition,ctnEnumIdentifier,ctnLabel:
begin
// only search behind variable
if MinPos<Node.StartPos then MinPos:=Node.StartPos;
end;
ctnProcedureHead:
MinPos:=Node.StartPos;
ctnProcedure:
begin
if (FindProcBody(Node)<>nil) and (StartNode<>Node.FirstChild) then
begin
// DeclarationNode is a local identifier
// limit scope to procedure
//debugln(['LimitScope ProcNode=',CleanPosToStr(Node.StartPos),'..',CleanPosToStr(Node.EndPos)]);
if MinPos<Node.FirstChild.EndPos then
MinPos:=Node.FirstChild.EndPos;
if MaxPos>Node.EndPos then
MaxPos:=Node.EndPos;
end;
end;
ctnOnBlock:
begin
// a declaration in an on block is only accessible in the on block
if MinPos<Node.StartPos then
MinPos:=Node.StartPos;
if MaxPos>Node.EndPos then
MaxPos:=Node.EndPos;
end;
end;
//debugln(['scope limited to node: ',Node.DescAsString,' ',CleanPosToStr(MinPos),'..',CleanPosToStr(MaxPos),': ',dbgstr(copy(Src,MinPos,20)),'..',dbgstr(copy(Src,MaxPos-20,20))]);
Node:=Node.Parent;
end;
//debugln(['LimitScope ',CleanPosToStr(MinPos),'..',CleanPosToStr(MaxPos),': ',dbgstr(copy(Src,MinPos,20)),'..',dbgstr(copy(Src,MaxPos-20,20))]);
end;
var
UseNode: TCodeTreeNode;
AVLNode: TAVLTreeNode;
begin
Result:=false;
//debugln('FindReferences ',MainFilename,' CursorPos=',CursorPos.Code.Filename,' x=',dbgs(CursorPos.X),' y=',dbgs(CursorPos.Y),' SkipComments=',dbgs(SkipComments));
ListOfPCodeXYPosition:=nil;
Params:=nil;
PosTree:=nil;
DeclarationFound:=false;
ActivateGlobalWriteLock;
try
// get the tool of the declaration
if not GetDeclarationTool then exit;
// check if this unit uses the declaration unit
UseNode:=nil;
if Self<>DeclarationTool then begin
BuildTree(lsrImplementationUsesSectionEnd);
UseNode:=FindUnitFileInAllUsesSections(DeclarationTool.MainFilename);
if UseNode=nil then
exit(true); // the declaration unit is not used
end;
// find declaration nodes and identifier
BuildTree(lsrEnd);
if not FindDeclarationNode then exit;
// search identifiers
LimitScope(UseNode);
//debugln('FindReferences MinPos=',CleanPosToStr(MinPos),' MaxPos=',CleanPosToStr(MaxPos));
SearchIdentifiers;
// create the reference list
if PosTree<>nil then begin
AVLNode:=PosTree.FindHighest;
while AVLNode<>nil do begin
StartPos:=PChar(AVLNode.Data)-PChar(Pointer(Src))+1;
// Note: if an include file is included twice a code position could be duplicated
if CleanPosToCaret(StartPos,ReferencePos) then
AddCodePosition(ListOfPCodeXYPosition,ReferencePos);
AVLNode:=PosTree.FindPrecessor(AVLNode);
end;
end;
finally
Params.Free;
PosTree.Free;
DeactivateGlobalWriteLock;
end;
Result:=true;
end;
function TFindDeclarationTool.FindUnitReferences(UnitCode: TCodeBuffer;
SkipComments: boolean; out ListOfPCodeXYPosition: TFPList): boolean;
var
AUnitName, UpperUnitName: String;
function CheckUsesSection(UsesNode: TCodeTreeNode; out Found: boolean): boolean;
var
ReferencePos: TCodeXYPosition;
begin
Result:=true;
Found:=false;
if UsesNode=nil then exit;
//DebugLn(['CheckUsesSection ']);
MoveCursorToNodeStart(UsesNode);
if (UsesNode.Desc=ctnUsesSection) then begin
ReadNextAtom;
if not UpAtomIs('USES') then
RaiseUsesExpected(20170421200509);
end;
repeat
ReadNextAtom; // read name
if CurPos.StartPos>SrcLen then break;
if AtomIsChar(';') then break;
AtomIsIdentifierE;
//DebugLn(['CheckUsesSection ',GetAtom,' ',AUnitName]);
if UpAtomIs(UpperUnitName) then begin // compare case insensitive
if CleanPosToCaret(CurPos.StartPos,ReferencePos) then begin
//DebugLn(['CheckUsesSection found in uses section: ',Dbgs(ReferencePos)]);
Found:=true;
AddCodePosition(ListOfPCodeXYPosition,ReferencePos);
end;
end;
ReadNextAtom;
if UpAtomIs('IN') then begin
ReadNextAtom;
if not AtomIsStringConstant then RaiseStrConstExpected(20170421200522);
ReadNextAtom;
end;
if AtomIsChar(';') then break;
if not AtomIsChar(',') then
RaiseExceptionFmt(20170421200217,ctsStrExpectedButAtomFound,[';',GetAtom])
until (CurPos.StartPos>SrcLen);
end;
function CheckSource(StartPos: integer): boolean;
var
ReferencePos: TCodeXYPosition;
begin
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
if not SkipComments then
; // ToDo
if UpAtomIs(UpperUnitName)
and not LastAtomIs(0,'.') then begin
if CleanPosToCaret(CurPos.StartPos,ReferencePos) then begin
//DebugLn(['CheckSource found: ',Dbgs(ReferencePos)]);
AddCodePosition(ListOfPCodeXYPosition,ReferencePos);
end;
end;
until CurPos.StartPos>SrcLen;
Result:=true;
end;
var
InterfaceUsesNode: TCodeTreeNode;
ImplementationUsesNode: TCodeTreeNode;
Found: boolean;
StartPos: Integer;
begin
Result:=false;
//debugln('FindUnitReferences UnitCode=',UnitCode.Filename,' SkipComments=',dbgs(SkipComments),' ',MainFilename);
AUnitName:=ExtractFileNameOnly(UnitCode.Filename);
UpperUnitName:=UpperCaseStr(AUnitName);
ListOfPCodeXYPosition:=nil;
ActivateGlobalWriteLock;
try
BuildTree(lsrEnd);
InterfaceUsesNode:=FindMainUsesNode;
if not CheckUsesSection(InterfaceUsesNode,Found) then exit;
StartPos:=-1;
if Found then begin
StartPos:=InterfaceUsesNode.EndPos;
end else begin
ImplementationUsesNode:=FindImplementationUsesNode;
if not CheckUsesSection(ImplementationUsesNode,Found) then exit;
if Found then
StartPos:=ImplementationUsesNode.EndPos;
end;
// find unit reference in source
if StartPos>0 then begin
if not CheckSource(StartPos) then exit;
end;
finally
DeactivateGlobalWriteLock;
end;
Result:=true;
end;
procedure TFindDeclarationTool.FindUsedUnitReferences(
const CursorPos: TCodeXYPosition; SkipComments: boolean; out
UsedUnitFilename: string; out ListOfPCodeXYPosition: TFPList);
var
CleanPos: integer;
Node: TCodeTreeNode;
UnitInFilename: string;
AnUnitName: String;
TargetCode: TCodeBuffer;
TargetTool: TFindDeclarationTool;
begin
//debugln(['TFindDeclarationTool.FindUsedUnitReferences ',dbgs(CursorPos)]);
UsedUnitFilename:='';
ListOfPCodeXYPosition:=nil;
BuildTreeAndGetCleanPos(CursorPos,CleanPos);
Node:=FindDeepestNodeAtPos(CleanPos,true);
if Node.Desc in [ctnUseUnitNamespace,ctnUseUnitClearName] then
Node:=Node.Parent;
if Node.Desc<>ctnUseUnit then
RaiseException(20170421200221,'This function needs the cursor at a unit in a uses clause');
// cursor is on an used unit -> try to locate it
MoveCursorToCleanPos(Node.StartPos);
ReadNextAtom;
AnUnitName:=ExtractUsedUnitNameAtCursor(@UnitInFilename);
//debugln(['TFindDeclarationTool.FindUsedUnitReferences Used Unit=',AnUnitName,' in "',UnitInFilename,'"']);
TargetCode:=FindUnitSource(AnUnitName,UnitInFilename,true,Node.StartPos);
UsedUnitFilename:=TargetCode.Filename;
//debugln(['TFindDeclarationTool.FindUsedUnitReferences TargetCode=',TargetCode.Filename]);
TargetTool:=FOnGetCodeToolForBuffer(Self,TargetCode,false);
FindUsedUnitReferences(TargetTool,SkipComments,ListOfPCodeXYPosition);
end;
procedure TFindDeclarationTool.FindUsedUnitReferences(
TargetTool: TFindDeclarationTool; SkipComments: boolean; out
ListOfPCodeXYPosition: TFPList);
var
refs: TFindUsedUnitReferences;
begin
ListOfPCodeXYPosition:=TFPList.Create;
if TargetTool=nil then
RaiseException(20170421200226,'TargetTool=nil');
TargetTool.BuildInterfaceIdentifierCache(true);
refs:=TFindUsedUnitReferences.Create(Self, FindLastNode);
try
refs.TargetTool:=TargetTool;
refs.TargetUnitName:=TargetTool.GetSourceName(false);
refs.ListOfPCodeXYPosition:=ListOfPCodeXYPosition;
ForEachIdentifier(SkipComments,@OnFindUsedUnitIdentifier,refs);
finally
refs.Free;
end;
end;
{-------------------------------------------------------------------------------
function TFindDeclarationTool.CleanPosIsDeclarationIdentifier(CleanPos: integer;
Node: TCodeTreeNode): boolean;
Node should be the deepest node at CleanPos, and all sub trees built.
See BuildSubTree
-------------------------------------------------------------------------------}
function TFindDeclarationTool.CleanPosIsDeclarationIdentifier(CleanPos: integer;
Node: TCodeTreeNode): boolean;
function InNodeIdentifier(NodeIdentStartPos: Integer): boolean;
var
IdentStartPos, IdentEndPos: integer;
begin
GetIdentStartEndAtPosition(Src,CleanPos,IdentStartPos,IdentEndPos);
Result:=(IdentEndPos>IdentStartPos) and (IdentStartPos=NodeIdentStartPos);
end;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=false;
if Node=nil then exit;
case Node.Desc of
ctnTypeDefinition,ctnVarDefinition,ctnConstDefinition,ctnEnumIdentifier,ctnLabel:
begin
if NodeIsForwardDeclaration(Node) then exit;
Result:=InNodeIdentifier(Node.StartPos);
end;
ctnGenericType:
begin
if (Node.FirstChild=nil) or NodeIsForwardDeclaration(Node) then exit;
Result:=InNodeIdentifier(Node.FirstChild.StartPos);
end;
ctnProcedure:
begin
if (Node.FirstChild<>nil)
and ((Node.FirstChild.SubDesc and ctnsForwardDeclaration)>0) then
RaiseException(20170421200230,'TFindDeclarationTool.CleanPosIsDeclarationIdentifier Node not expanded');
MoveCursorToProcName(Node,true);
Result:=InNodeIdentifier(CurPos.StartPos);
end;
ctnProcedureHead:
begin
MoveCursorToProcName(Node,true);
Result:=InNodeIdentifier(CurPos.StartPos);
end;
ctnProperty, ctnGlobalProperty:
begin
if not MoveCursorToPropName(Node) then exit;
Result:=InNodeIdentifier(CurPos.StartPos);
end;
ctnUnit:
Result:=PositionInSourceName(CleanPos);
end;
end;
function TFindDeclarationTool.JumpToNode(ANode: TCodeTreeNode;
out NewPos: TCodeXYPosition; out NewTopLine: integer;
IgnoreJumpCentered: boolean): boolean;
var
JumpPos: LongInt;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ANode);{$ENDIF}
Result:=false;
if (ANode=nil) or (ANode.StartPos<1) then exit;
JumpPos:=ANode.StartPos;
if ANode.Desc in [ctnProperty,ctnGlobalProperty] then begin
MoveCursorToPropName(ANode);
JumpPos:=CurPos.StartPos;
end;
Result:=JumpToCleanPos(JumpPos,JumpPos,ANode.EndPos,
NewPos,NewTopLine,IgnoreJumpCentered);
end;
function TFindDeclarationTool.JumpToCleanPos(NewCleanPos, NewTopLineCleanPos,
NewBottomLineCleanPos: integer; out NewPos: TCodeXYPosition;
out NewTopLine: integer; IgnoreJumpCentered: boolean): boolean;
var
CenteredTopLine: integer;
NewTopLinePos: TCodeXYPosition;
NewBottomLinePos: TCodeXYPosition;
begin
Result:=false;
// convert clean position to line, column and code
if not CleanPosToCaret(NewCleanPos,NewPos) then exit;
NewTopLine:=NewPos.Y;
if AdjustTopLineDueToComment then begin
// if there is a comment in front of the top position, it probably belongs
// to the destination code
// -> adjust the topline position, so that the comment is visible
NewTopLineCleanPos:=FindLineEndOrCodeInFrontOfPosition(NewTopLineCleanPos,
false);
if (NewTopLineCleanPos>=1) and (Src[NewTopLineCleanPos] in [#13,#10])
then begin
inc(NewTopLineCleanPos);
if (Src[NewTopLineCleanPos] in [#10,#13])
and (Src[NewTopLineCleanPos]<>Src[NewTopLineCleanPos-1]) then
inc(NewTopLineCleanPos);
end;
end;
// convert clean top line position to line, column and code
if not CleanPosToCaret(NewTopLineCleanPos,NewTopLinePos) then exit;
// convert clean bottom line position to line, column and code
NewBottomLinePos:=NewPos;
if (NewBottomLineCleanPos>NewCleanPos)
and (not CleanPosToCaret(NewBottomLineCleanPos,NewBottomLinePos)) then exit;
if NewTopLinePos.Code=NewPos.Code then begin
// top line position is in the same code as the destination position
NewTopLine:=NewTopLinePos.Y;
CenteredTopLine:=NewPos.Y-VisibleEditorLines div 2;
if JumpCentered and (not IgnoreJumpCentered) then begin
// center the destination position in the source editor
if CenteredTopLine<NewTopLine then
NewTopLine:=CenteredTopLine;
end;
// NewTopLine not above first line of code
if NewTopLine<1 then NewTopLine:=1;
// make NewTopLine visible
if NewTopLine<=NewPos.Y-VisibleEditorLines then begin
// NewTopLine is not visible
// center or align to bottom
if (NewBottomLineCleanPos>NewCleanPos)
and (NewBottomLinePos.Y<NewPos.Y+(VisibleEditorLines div 2))
then begin
// align to bottom
NewTopLine:=NewBottomLinePos.Y-VisibleEditorLines+1;
end else begin
// center
NewTopLine:=CenteredTopLine;
end;
if NewTopLine<1 then NewTopLine:=1;
end;
end else
NewTopLine:=1;
Result:=true;
end;
function TFindDeclarationTool.NodeIsForwardDeclaration(Node: TCodeTreeNode
): boolean;
var
TypeNode: TCodeTreeNode;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=false;
if (Node=nil) or (not (Node.Desc in [ctnTypeDefinition,ctnGenericType])) then
exit;
TypeNode:=FindTypeNodeOfDefinition(Node);
if TypeNode=nil then exit;
if TypeNode.Desc in AllClasses then begin
if (TypeNode.SubDesc and ctnsForwardDeclaration)>0 then begin
Result:=true;
exit;
end;
end;
end;
function TFindDeclarationTool.GetExpandedOperand(const CursorPos: TCodeXYPosition;
out Operand: string; ResolveProperty: Boolean): Boolean;
var
CursorNode: TCodeTreeNode;
CleanCursorPos: integer;
Params: TFindDeclarationParams;
Identifier: PChar;
LineRange: TLineRange;
begin
Result := False;
Operand := '';
if (CursorPos.Y<1) or (CursorPos.Y>CursorPos.Code.LineCount)
or (CursorPos.X<1) then Exit;
CursorPos.Code.GetLineRange(CursorPos.Y-1,LineRange);
if LineRange.EndPos-LineRange.StartPos+1<CursorPos.X then Exit;
ActivateGlobalWriteLock;
try
// build code tree
BuildTreeAndGetCleanPos(trTillCursor,lsrEnd,CursorPos,CleanCursorPos,
[btSetIgnoreErrorPos,btCursorPosOutAllowed]);
// find CodeTreeNode at cursor
if (Tree.Root<>nil) and (Tree.Root.StartPos<=CleanCursorPos) then
CursorNode := BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos, True)
else
CursorNode := nil;
if CursorNode = nil then begin
// raise exception
CursorNode := FindDeepestNodeAtPos(CleanCursorPos, True);
end;
if CursorNode.Desc = ctnBeginBlock then begin
BuildSubTreeForBeginBlock(CursorNode);
CursorNode := FindDeepestNodeAtPos(CursorNode, CleanCursorPos, True);
end;
// set cursor on identifier
MoveCursorToCleanPos(CleanCursorPos);
GetIdentStartEndAtPosition(Src,CleanCursorPos,
CurPos.StartPos,CurPos.EndPos);
if CurPos.StartPos >= CurPos.EndPos then Exit;
Identifier := @Src[CurPos.StartPos];
// find declaration of identifier
Params := TFindDeclarationParams.Create;
try
Params.ContextNode := CursorNode;
Params.SetIdentifier(Self, Identifier, nil);
Params.Flags := [fdfSearchInParentNodes, fdfTopLvlResolving,
fdfSearchInAncestors, fdfSkipClassForward,
fdfExtractOperand];
if ResolveProperty then
Include(Params.Flags, fdfPropertyResolving);
if FindDeclarationOfIdentAtParam(Params) then
begin
Operand := Params.ExtractedOperand;
Result := Operand <> '';
end;
finally
Params.Free;
end;
finally
ClearIgnoreErrorAfter;
DeactivateGlobalWriteLock;
end;
end;
function TFindDeclarationTool.FindIdentifierInProcContext(
ProcContextNode: TCodeTreeNode;
Params: TFindDeclarationParams): TIdentifierFoundResult;
{ this function is internally used by FindIdentifierInContext
}
var
NameAtom: TAtomPosition;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ProcContextNode);{$ENDIF}
Result:=ifrProceedSearch;
// if proc is a method body, search in class
// -> find class name
if ProcContextNode.FirstChild=nil then
exit(ifrProceedSearch);
MoveCursorToNodeStart(ProcContextNode.FirstChild);
ReadNextAtom; // read name
if not AtomIsIdentifier then exit; // ignore operator procs
NameAtom:=CurPos;
ReadNextAtom;
if AtomIsChar('.') then begin
// proc is a method body (not a declaration).
// -> proceed the search normally ...
end else begin
// proc is a proc declaration
if ((fdfCollect in Params.Flags)
or CompareSrcIdentifiers(NameAtom.StartPos,Params.Identifier)) then begin
// proc identifier found
// the parameters will be checked by the caller
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInProcContext] Proc-Identifier found="',GetIdentifier(@Src[NameAtom.StartPos]),'"');
{$ENDIF}
Params.SetResult(Self,ProcContextNode,NameAtom.StartPos);
Result:=ifrSuccess;
end else begin
// proceed the search normally ...
end;
end;
end;
function TFindDeclarationTool.FindIdentifierInClassOfMethod(
ProcContextNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
{ this function is internally used by FindIdentifierInContext
}
var
ClassNameAtom: TAtomPosition;
OldFlags: TFindDeclarationFlags;
IdentFoundResult: TIdentifierFoundResult;
CurClassNode: TCodeTreeNode;
ForExprType: TExpressionType;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ProcContextNode);{$ENDIF}
Result:=false;
// if proc is a method, search in class
// -> find class name
MoveCursorToNodeStart(ProcContextNode);
ReadNextAtom; // read keyword
if UpAtomIs('CLASS') then
ReadNextAtom;
ReadNextAtom; // read classname
ClassNameAtom:=CurPos;
ReadNextAtom;
if AtomIsChar('.') then begin
// proc is a method
if CompareSrcIdentifiers(ClassNameAtom.StartPos,Params.Identifier) then
begin
// the class itself is searched
// -> proceed the search normally ...
end else begin
// search the identifier in the class first
// search the class in the same unit
CurClassNode:=FindClassOfMethod(ProcContextNode,true,true);
repeat
// search identifier in class
OldFlags := Params.Flags;
Params.Flags:=[fdfSearchInAncestors]
+(fdfGlobalsSameIdent*Params.Flags)
-[fdfExceptionOnNotFound];
Params.ContextNode:=CurClassNode;
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInClassOfMethod] searching identifier in class of method Identifier=',GetIdentifier(Params.Identifier));
{$ENDIF}
if (fdfSearchInHelpers in Params.Flags)
and (CurClassNode.Desc in [ctnClassHelper,ctnRecordHelper])
and (Params.GetHelpers(fdhlkDelphiHelper)<>nil)
then begin
// override current helper for the type and search in that type
ForExprType := Params.GetHelpers(fdhlkDelphiHelper).AddFromHelperNode(CurClassNode, Self, True).ForExprType;
if (ForExprType.Desc = xtContext) and (ForExprType.Context.Node<>nil)
then begin
Params.ContextNode:=ForExprType.Context.Node;
Result:=ForExprType.Context.Tool.FindIdentifierInContext(Params);
end;
end else
Result:=FindIdentifierInContext(Params);
Params.Flags := OldFlags;
if Result and Params.IsFoundProcFinal then exit;
// in a nested class, continue search in enclosing class
repeat
CurClassNode:=CurClassNode.Parent;
until (CurClassNode=nil) or (CurClassNode.Desc in AllClassObjects);
until CurClassNode=nil;
end;
end else begin
// proc is not a method
if (fdfCollect in Params.Flags)
or CompareSrcIdentifiers(ClassNameAtom.StartPos,Params.Identifier) then
begin
// proc identifier found
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInClassOfMethod] Proc Identifier found="',GetIdentifier(Params.Identifier),'"');
{$ENDIF}
Params.SetResult(Self,ProcContextNode,ClassNameAtom.StartPos);
IdentFoundResult:=Params.NewCodeTool.DoOnIdentifierFound(Params,
Params.NewNode);
Result:=IdentFoundResult=ifrSuccess;
exit;
end else begin
// proceed the search normally ...
end;
end;
end;
function TFindDeclarationTool.FindIdentifierInContext(
Params: TFindDeclarationParams): boolean;
var
IdentFoundResult: TIdentifierFoundResult;
begin
Result := FindIdentifierInContext(Params, IdentFoundResult{%H-});
end;
function TFindDeclarationTool.FindClassOfMethod(ProcNode: TCodeTreeNode;
FindClassContext, ExceptionOnNotFound: boolean): TCodeTreeNode;
var
ClassNameAtom: TAtomPosition;
Node: TCodeTreeNode;
TypeNode: TCodeTreeNode;
NextNameAtom: TAtomPosition;
CurClassName: PChar;
CurClassNode: TCodeTreeNode;
procedure RaiseClassNotFound;
begin
MoveCursorToAtomPos(ClassNameAtom);
RaiseExceptionFmt(20170421200233,'Class %s not found',[GetAtom]);
end;
procedure RaiseNotAClass;
begin
MoveCursorToAtomPos(ClassNameAtom);
RaiseExceptionFmt(20170421200237,'Class expected, but %s found',[GetAtom]);
end;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ProcNode);{$ENDIF}
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindClassOfMethod] A ');
{$ENDIF}
Result:=nil;
if ProcNode.Desc=ctnProcedureHead then
ProcNode:=ProcNode.Parent;
if (ProcNode.Parent<>nil) and (ProcNode.Parent.Desc in AllClassSections) then begin
CurClassNode:=ProcNode.Parent.Parent;
if FindClassContext then begin
// return the class node
exit(CurClassNode);
end else begin
// return the type identifier node
exit(CurClassNode.Parent);
end;
end;
MoveCursorToNodeStart(ProcNode);
ReadNextAtom; // read keyword
if UpAtomIs('CLASS') then ReadNextAtom;
ReadNextAtom; // read classname
ClassNameAtom:=CurPos;
if CurPos.Flag<>cafWord then begin
if not ExceptionOnNotFound then exit;
RaiseNotAClass;
end;
CurClassName:=@Src[ClassNameAtom.StartPos];
ReadNextAtom;
if CurPos.Flag<>cafPoint then begin
// not a method
if not ExceptionOnNotFound then exit;
RaiseNotAClass;
end;
ReadNextAtom;
NextNameAtom:=CurPos;
//debugln(['TFindDeclarationTool.FindClassOfMethod ClassName="',GetAtom(ClassNameAtom),'"']);
// proc is a method
// -> search the class
Node:=ProcNode;
repeat
if Node.Desc=ctnTypeSection then begin
TypeNode:=Node.LastChild;
while TypeNode<>nil do begin
{$IFDEF ShowTriedIdentifiers}
debugln(['TFindDeclarationTool.FindClassOfMethod ',TypeNode.DescAsString,' ',dbgstr(ExtractNode(TypeNode,[]),1,40)]);
{$ENDIF}
if ((TypeNode.Desc=ctnTypeDefinition)
and (CompareIdentifierPtrs(CurClassName,@Src[TypeNode.StartPos])=0))
or ((TypeNode.Desc=ctnGenericType)
and (TypeNode.FirstChild<>nil)
and (CompareIdentifierPtrs(CurClassName,@Src[TypeNode.FirstChild.StartPos])=0))
then begin
repeat
// type with same name found
//debugln(['TFindDeclarationTool.FindClassOfMethod type found ',ExtractDefinitionName(TypeNode)]);
CurClassNode:=FindTypeNodeOfDefinition(TypeNode);
if (CurClassNode=nil) then begin
if not ExceptionOnNotFound then exit;
RaiseClassNotFound;
end;
if (not (CurClassNode.Desc in AllClassObjects))
or ((ctnsForwardDeclaration and Node.SubDesc)<>0)
then begin
if not ExceptionOnNotFound then exit;
RaiseNotAClass;
end;
//debugln(['TFindDeclarationTool.FindClassOfMethod class found, NextNameAtom=',GetAtom(NextNameAtom)]);
// class found
if NextNameAtom.Flag=cafWord then begin
MoveCursorToAtomPos(NextNameAtom);
ReadNextAtom;
if CurPos.Flag<>cafPoint then begin
if FindClassContext then begin
// return the class node
exit(CurClassNode);
end else begin
// return the type identifier node
exit(TypeNode);
end;
end;
ReadNextAtom;
ClassNameAtom:=NextNameAtom;
NextNameAtom:=CurPos;
CurClassName:=@Src[ClassNameAtom.StartPos];
end else begin
// operator or missing sub identifier
if FindClassContext then begin
// return the class node
exit(CurClassNode);
end else begin
// return the type identifier node
exit(TypeNode);
end;
end;
// search sub class
//debugln(['TFindDeclarationTool.FindClassOfMethod searching sub class "',GetIdentifier(CurClassName),'"']);
Node:=FindClassMember(CurClassNode,CurClassName);
if Node=nil then begin
if not ExceptionOnNotFound then exit;
RaiseClassNotFound;
end;
if not (Node.Desc in [ctnTypeDefinition,ctnGenericType]) then begin
if not ExceptionOnNotFound then exit;
RaiseNotAClass;
end;
TypeNode:=Node;
until false;
end;
TypeNode:=TypeNode.PriorBrother;
end;
end;
// next
if Node.PriorBrother<>nil then
Node:=Node.PriorBrother
else begin
Node:=Node.Parent;
if (Node=nil) or (Node.Desc<>ctnImplementation) then break;
Node:=Node.PriorBrother;
if (Node=nil) or (Node.Desc<>ctnInterface) then break;
Node:=Node.LastChild;
if Node=nil then break;
end;
until false;
if ExceptionOnNotFound then
RaiseClassNotFound;
end;
function TFindDeclarationTool.FindClassMember(aClassNode: TCodeTreeNode;
Identifier: PChar): TCodeTreeNode;
var
Node: TCodeTreeNode;
CurIdentifier: PChar;
begin
Result:=nil;
if GetIdentLen(Identifier)=0 then exit;
if aClassNode=nil then exit;
Node:=aClassNode.LastChild;
while Node<>nil do begin
if (Node.Desc in AllClassSections)
and (Node.FirstChild<>nil) then begin
Node:=Node.LastChild;
continue;
end
else if Node.Desc in AllSimpleIdentifierDefinitions then begin
if CompareIdentifierPtrs(@Src[Node.StartPos],Identifier)=0 then
exit(Node);
end else if Node.Desc=ctnProperty then begin
CurIdentifier:=GetPropertyNameIdentifier(Node);
if CompareIdentifierPtrs(CurIdentifier,Identifier)=0 then
exit(Node);
end else if Node.Desc=ctnProcedure then begin
CurIdentifier:=GetProcNameIdentifier(Node);
if CompareIdentifierPtrs(CurIdentifier,Identifier)=0 then
exit(Node);
end;
// next
if Node.PriorBrother<>nil then
Node:=Node.PriorBrother
else begin
repeat
Node:=Node.Parent;
if Node=aClassNode then exit;
until Node.PriorBrother<>nil;
Node:=Node.PriorBrother;
end;
end;
end;
function TFindDeclarationTool.FindClassMember(aClassNode: TCodeTreeNode;
const Identifier: String; SearchInAncestors: boolean): TFindContext;
var
Params: TFindDeclarationParams;
begin
Result.Tool:=Self;
Result.Node:=FindClassMember(aClassNode,PChar(Identifier));
if Result.Node<>nil then exit;
if not SearchInAncestors then begin
Result:=CleanFindContext;
exit;
end;
Params:=TFindDeclarationParams.Create;
try
while Result.Tool.FindAncestorOfClass(aClassNode,Params,True) do begin
Result.Tool:=Params.NewCodeTool;
aClassNode:=Params.NewNode;
Result.Node:=Result.Tool.FindClassMember(aClassNode,PChar(Identifier));
if Result.Node<>nil then exit;
end;
Result:=CleanFindContext;
finally
Params.Free;
end;
end;
function TFindDeclarationTool.FindAncestorOfClass(ClassNode: TCodeTreeNode;
Params: TFindDeclarationParams; FindClassContext: boolean): boolean;
var
InheritanceNode: TCodeTreeNode;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ClassNode);{$ENDIF}
if (ClassNode=nil) or (not (ClassNode.Desc in AllClasses)) then
RaiseException(20170421200240,'[TFindDeclarationTool.FindAncestorOfClass] invalid classnode');
Result:=false;
// ToDo: ppu, dcu
InheritanceNode:=FindInheritanceNode(ClassNode);
if (InheritanceNode<>nil)
and (InheritanceNode.FirstChild<>nil) then begin
Result:=FindAncestorOfClassInheritance(InheritanceNode.FirstChild,
Params,FindClassContext);
end else begin
Result:=FindDefaultAncestorOfClass(ClassNode,Params,FindClassContext);
end;
end;
function TFindDeclarationTool.FindAncestorOfClassInheritance(
IdentifierNode: TCodeTreeNode; ResultParams: TFindDeclarationParams;
FindClassContext: boolean): boolean;
var
InheritanceNode: TCodeTreeNode;
ClassNode: TCodeTreeNode;
AncestorContext: TFindContext;
AncestorStartPos: LongInt;
ExprType: TExpressionType;
Params: TFindDeclarationParams;
procedure RaiseExpected(const Expected: string);
begin
MoveCursorToCleanPos(AncestorStartPos);
ReadNextAtom;
RaiseExceptionFmt(20170421200243,ctsStrExpectedButAtomFound,[Expected,ExtractNode(IdentifierNode,[])]);
end;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(IdentifierNode);{$ENDIF}
if (IdentifierNode=nil)
or (not (IdentifierNode.Desc in [ctnIdentifier,ctnSpecialize]))
or (IdentifierNode.Parent=nil)
or (IdentifierNode.Parent.Desc<>ctnClassInheritance)
then
RaiseException(20170421200245,'[TFindDeclarationTool.FindAncestorOfClass] '
+' not an inheritance node');
Result:=false;
InheritanceNode:=IdentifierNode.Parent;
ClassNode:=InheritanceNode.Parent;
if IdentifierNode.Desc=ctnSpecialize then begin
if (IdentifierNode.FirstChild=nil) then begin
MoveCursorToCleanPos(IdentifierNode.StartPos);
ReadNextAtom;
if UpAtomIs('SPECIALIZE') then
ReadNextAtom;
RaiseStringExpectedButAtomFound(20170421200248,'class type');
end;
MoveCursorToCleanPos(IdentifierNode.FirstChild.StartPos);
end else
MoveCursorToCleanPos(IdentifierNode.StartPos);
ReadNextAtom;
AtomIsIdentifierE;
AncestorStartPos:=CurPos.StartPos;
ReadNextAtom;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions;
Params.ContextNode:=IdentifierNode;
if CurPos.Flag=cafPoint then begin
// complex identifier
{$IFDEF ShowTriedContexts}
DebugLn(['[TFindDeclarationTool.FindAncestorOfClass] ',
' search complex ancestor class = "',ExtractNode(IdentifierNode,[]),'" for class "',ExtractClassName(ClassNode,false),'"']);
{$ENDIF}
if not FindClassContext then
Params.Flags:=Params.Flags+[fdfFindVariable];
ExprType:=FindExpressionTypeOfTerm(IdentifierNode.StartPos,IdentifierNode.EndPos,Params,false);
if ExprType.Desc<>xtContext then
RaiseExpected('type');
AncestorContext:=ExprType.Context
end else begin
// simple identifier
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindAncestorOfClass] ',
' search ancestor class="',GetIdentifier(@Src[AncestorStartPos]),'" for class "',ExtractClassName(ClassNode,false),'"');
{$ENDIF}
Params.SetIdentifier(Self,@Src[AncestorStartPos],nil);
if not FindIdentifierInContext(Params) then
exit;
AncestorContext.Tool:=Params.NewCodeTool;
AncestorContext.Node:=Params.NewNode;
end;
finally
Params.Free;
end;
if FindClassContext then begin
// search ancestor class context
if (AncestorContext.Node.Desc in [ctnTypeDefinition,ctnGenericType]) then
begin
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions+[fdfFindChildren];
AncestorContext:=AncestorContext.Tool.FindBaseTypeOfNode(Params,AncestorContext.Node);
finally
Params.Free;
end;
end;
// check result
if not (AncestorContext.Node.Desc in AllClasses) then
RaiseExpected('class');
if AncestorContext.Node=ClassNode then begin
MoveCursorToCleanPos(AncestorStartPos);
ReadNextAtom;
RaiseException(20170421200252,'cycle detected');
end;
end else begin
// check if class identifier
if (not (AncestorContext.Node.Desc in [ctnTypeDefinition,ctnGenericType])) then
RaiseExpected('type');
end;
ResultParams.SetResult(AncestorContext);
Result:=true;
end;
function TFindDeclarationTool.FindAncestorsOfClass(ClassNode: TCodeTreeNode;
var ListOfPFindContext: TFPList;
Params: TFindDeclarationParams; FindClassContext: boolean;
ExceptionOnNotFound: boolean): boolean;
var
Node: TCodeTreeNode;
Context: TFindContext;
InheritanceNode: TCodeTreeNode;
begin
Result:=false;
InheritanceNode:=FindInheritanceNode(ClassNode);
if (InheritanceNode=nil) then
exit(true);
Node:=InheritanceNode.FirstChild;
if Node=nil then begin
try
if not FindAncestorOfClass(ClassNode,Params,FindClassContext) then begin
exit(true); // this is TObject or IInterface, IUnknown
end else begin
Context:=CreateFindContext(Params);
end;
AddFindContext(ListOfPFindContext,Context);
Result:=Context.Node<>nil;
except
if ExceptionOnNotFound then raise;
end;
end else begin
while Node<>nil do begin
try
if FindAncestorOfClassInheritance(Node,Params,FindClassContext) then
begin
Context:=CreateFindContext(Params);
AddFindContext(ListOfPFindContext,Context);
end;
except
if ExceptionOnNotFound then raise;
end;
Node:=Node.NextBrother;
end;
end;
Result:=true;
end;
function TFindDeclarationTool.FindForwardIdentifier(
Params: TFindDeclarationParams; out IsForward: boolean): boolean;
{ first search the identifier in the normal way via FindIdentifierInContext
then search the other direction }
var
OldInput: TFindDeclarationInput;
begin
Params.Save(OldInput);
Exclude(Params.Flags,fdfExceptionOnNotFound);
Result:=FindIdentifierInContext(Params);
if not Result then begin
Params.Load(OldInput,false);
Params.Flags:=Params.Flags+[fdfSearchForward,fdfIgnoreCurContextNode];
Result:=FindIdentifierInContext(Params);
IsForward:=true;
end else begin
IsForward:=false;
end;
Params.Load(OldInput,true);
end;
function TFindDeclarationTool.FindNonForwardClass(ForwardNode: TCodeTreeNode
): TCodeTreeNode;
var
Node: TCodeTreeNode;
Identifier: PChar;
begin
Result:=nil;
Node:=ForwardNode;
if Node.Desc=ctnGenericType then begin
Node:=Node.FirstChild;
if Node=nil then exit;
end else if Node.Desc<>ctnTypeDefinition then
exit;
Node:=Node.FirstChild;
if Node=nil then
Exit;
Identifier:=@Src[Node.StartPos];
if (Node=nil)
or (not (Node.Desc in AllClasses))
or ((ctnsForwardDeclaration and Node.SubDesc)=0) then
exit;
Node:=ForwardNode;
repeat
//DebugLn(['TFindDeclarationTool.FindNonForwardClass Node=',dbgstr(copy(Src,Node.StartPos,20))]);
if Node.NextBrother<>nil then
Node:=Node.NextBrother
else if (Node.Parent=nil)
or (not (Node.Parent.Desc in AllDefinitionSections)) then
break
else begin
Node:=Node.Parent.NextBrother;
while (Node<>nil)
and ((Node.FirstChild=nil) or (not (Node.Desc in AllDefinitionSections)))
do
Node:=Node.NextBrother;
if Node=nil then break;
Node:=Node.FirstChild;
end;
if CompareSrcIdentifiers(Node.StartPos,Identifier) then begin
Result:=Node;
exit;
end;
until false;
end;
function TFindDeclarationTool.FindNonForwardClass(Params: TFindDeclarationParams
): boolean;
var
Node: TCodeTreeNode;
begin
Node:=FindNonForwardClass(Params.NewNode);
if Node<>nil then begin
Params.SetResult(Self,Node,Node.StartPos);
Result:=true;
end else begin
Result:=false;
end;
end;
function TFindDeclarationTool.FindIdentifierInWithVarContext(
WithVarNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
{ this function is internally used by FindIdentifierInContext }
var
WithVarExpr: TExpressionType;
OldInput: TFindDeclarationInput;
OldExtractedOperand, NewExtractedOperand: string;
begin
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.FindIdentifierInWithVarContext] Ident=',
'"',GetIdentifier(Params.Identifier),'"',
' WithStart=',StringToPascalConst(copy(Src,WithVarNode.StartPos,15))
);
{$ENDIF}
{$IFDEF CheckNodeTool}CheckNodeTool(WithVarNode);{$ENDIF}
Result:=false;
// find the base type of the with variable
// move cursor to end of with-variable
Params.Save(OldInput);
Params.ContextNode:=WithVarNode;
Params.Flags:=Params.Flags*fdfGlobals
+[fdfExceptionOnNotFound,fdfFunctionResult,fdfFindChildren];
OldExtractedOperand:=Params.ExtractedOperand;
WithVarExpr:=FindExpressionTypeOfTerm(WithVarNode.StartPos,-1,Params,true);
if fdfExtractOperand in Params.Flags then
NewExtractedOperand:=Params.ExtractedOperand+'.'
else
NewExtractedOperand:='';
if (WithVarExpr.Desc<>xtContext)
or (WithVarExpr.Context.Node=nil)
or (WithVarExpr.Context.Node=OldInput.ContextNode)
or (not (WithVarExpr.Context.Node.Desc in (AllClasses+[ctnEnumerationType])))
then begin
MoveCursorToCleanPos(WithVarNode.StartPos);
RaiseException(20170421200254,ctsExprTypeMustBeClassOrRecord);
end;
// search identifier in 'with' context
// Note: do not search in parent nodes (e.g. with ListBox1 do Items)
Params.Load(OldInput,false);
Params.Flags:=Params.Flags-[fdfExceptionOnNotFound,fdfSearchInParentNodes];
Params.ContextNode:=WithVarExpr.Context.Node;
Result:=WithVarExpr.Context.Tool.FindIdentifierInContext(Params);
Params.Load(OldInput,true);
if fdfExtractOperand in Params.Flags then
if Result then
Params.FExtractedOperand:=NewExtractedOperand
else
Params.FExtractedOperand:=OldExtractedOperand;
end;
function TFindDeclarationTool.FindIdentifierInAncestors(
ClassNode: TCodeTreeNode; Params: TFindDeclarationParams;
var IdentFoundResult: TIdentifierFoundResult): boolean;
{ this function is internally used by FindIdentifierInContext
and FindBaseTypeOfNode
}
function Search(AncestorTool: TFindDeclarationTool;
AncestorClassNode: TCodeTreeNode): boolean;
var
OldFlags: TFindDeclarationFlags;
begin
OldFlags := Params.Flags;
Params.ContextNode:=AncestorClassNode;
Params.Flags:=Params.Flags
-[fdfIgnoreCurContextNode,fdfSearchInParentNodes]
+[fdfSearchInAncestors];
Result:=AncestorTool.FindIdentifierInContext(Params,IdentFoundResult);
Params.Flags := OldFlags;
end;
var
InheritanceNode: TCodeTreeNode;
Node: TCodeTreeNode;
SearchDefaultAncestor: Boolean;
begin
Result:=false;
if not (fdfSearchInAncestors in Params.Flags) then exit;
SearchDefaultAncestor:=true;
InheritanceNode:=FindInheritanceNode(ClassNode);
if (InheritanceNode<>nil) then begin
Node:=InheritanceNode.FirstChild;
while Node<>nil do begin
if not FindAncestorOfClassInheritance(Node,Params,true) then exit;
SearchDefaultAncestor:=false;
if Search(Params.NewCodeTool,Params.NewNode) then exit(true);
Node:=Node.NextBrother;
end;
end;
//debugln(['TFindDeclarationTool.FindIdentifierInAncestors SearchDefaultAncestor=',SearchDefaultAncestor,' ',CleanPosToStr(ClassNode.StartPos,true)]);
if SearchDefaultAncestor then begin
if not FindDefaultAncestorOfClass(ClassNode,Params,true) then exit;
//debugln(['TFindDeclarationTool.FindIdentifierInAncestors search in default ancestor ',FindContextToString(CreateFindContext(Params.NewCodeTool,Params.NewNode))]);
Result:=Search(Params.NewCodeTool,Params.NewNode);
end;
end;
{$IFDEF DebugPrefix}
procedure TFindDeclarationTool.DecPrefix;
begin
DebugPrefix:=copy(DebugPrefix,1,length(DebugPrefix)-2);
end;
procedure TFindDeclarationTool.IncPrefix;
begin
DebugPrefix:=DebugPrefix+' ';
end;
{$ENDIF}
function TFindDeclarationTool.FindExpressionResultType(
Params: TFindDeclarationParams; StartPos, EndPos: integer;
AliasType: PFindContext): TExpressionType;
{
- operators
- mixing ansistring and shortstring gives ansistring
- Pointer +,- Pointer gives Pointer
- Sets:
[enum1] gives set of enumeration type
set *,-,+ set gives set of same type
set <>,=,<,> set gives boolean
- precedence rules table:
1. brackets
2. not @ sign
3. * / div mod and shl shr as
4. + - or xor
5. < <> > <= >= in is
- nil is compatible to pointers and classes
- operator overloading?
- internal types. e.g. string[], ansistring[], shortstring[], pchar[] to char
- the type of a subrange is the type of the first constant/enum/number/char
- predefined types:
ordinal:
int64, cardinal, QWord, boolean, bytebool, wordbool, qwordbool, longbool, char
real:
real, single, double, extended, cextended, comp, currency
- predefined functions:
function pred(ordinal type): ordinal constant of same type;
function succ(ordinal type): ordinal constant of same type;
function ord(ordinal type): ordinal type;
val?
function low(array): type of leftmost index type in the array;
function high(array): type of leftmost index type in the array;
procedure dec(ordinal var);
procedure dec(ordinal var; ordinal type);
procedure dec(pointer var);
procedure dec(pointer var; ordinal type);
procedure inc(ordinal var);
procedure inc(ordinal var; ordinal type);
procedure inc(pointer var);
procedure inc(pointer var; ordinal type);
procedure write(...);
procedure writeln(...);
function SizeOf(type): ordinal constant;
typeinfo?
uniquestring?
procedure include(set type,enum identifier);
procedure exclude(set type,enum identifier);
function objcselector(string): sel;
}
type
TOperandAndOperator = record
Operand: TOperand;
theOperator: TAtomPosition;
OperatorLvl: integer;
end;
POperandAndOperator = ^TOperandAndOperator;
TExprStack = array[0..4] of TOperandAndOperator;
var
CurExprType: TExpressionType;
CurAliasType: PFindContext;
AliasTypeStorage: TFindContext;
ExprStack: TExprStack;
StackPtr: integer;
procedure ExecuteStack(Complete: boolean);
{ Executes the operand+operator stack
Examples:
Position Operand Operator
0 AWord *
1 AByte +
Because * has higher predence than + the stack is executed:
AWord*AByte gives an integer. New stack
Position Operand Operator
0 Integer +
}
var
NewOperand: TOperand;
LastPos: TAtomPosition;
begin
if StackPtr<=0 then begin
// only one element -> nothing to do
exit;
end;
LastPos:=CurPos;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.FindExpressionResultType.ExecuteStack] ',
' StackPtr=',dbgs(StackPtr),
' Lvl=',dbgs(ExprStack[StackPtr].OperatorLvl),
' Complete=',dbgs(Complete));
{$ENDIF}
while (StackPtr>0)
and (Complete
or (ExprStack[StackPtr-1].OperatorLvl<=ExprStack[StackPtr].OperatorLvl)) do
begin
// next operand has a higher or equal precedence
// (lower is computed before higher)
// -> calculate last two operands
NewOperand:=CalculateBinaryOperator(ExprStack[StackPtr-1].Operand,
ExprStack[StackPtr].Operand,ExprStack[StackPtr-1].theOperator,
Params);
// put result on stack
ExprStack[StackPtr-1]:=ExprStack[StackPtr];
dec(StackPtr);
ExprStack[StackPtr].Operand:=NewOperand;
end;
MoveCursorToAtomPos(LastPos);
end;
procedure RaiseBinaryOperatorNotFound;
begin
RaiseExceptionFmt(20170421200256,ctsStrExpectedButAtomFound,[ctsBinaryOperator,GetAtom]);
end;
procedure RaiseInternalError;
begin
RaiseException(20170421200300,'[TFindDeclarationTool.FindExpressionResultType]'
+' internal error: unknown precedence lvl for operator '+GetAtom);
end;
procedure RaiseInternalErrorStack;
begin
RaiseException(20170421200303,'[TFindDeclarationTool.FindExpressionResultType]'
+' internal error: stackptr too big ');
end;
var
OldFlags: TFindDeclarationFlags;
StackEntry: POperandAndOperator;
IsEnd, IsBinOpError: Boolean;
begin
{$IFDEF ShowExprEval}
DebugLn(['[TFindDeclarationTool.FindExpressionResultType] Start',
' Pos=',StartPos,'-',EndPos,
'="',dbgstr(Src,StartPos,EndPos-StartPos),'" Context=',Params.ContextNode.DescAsString,' Alias=',AliasType<>nil]);
{$ENDIF}
Result:=CleanExpressionType;
if (AliasType<>nil) and (AliasType^.Node=nil) then begin
AliasTypeStorage:=CleanFindContext;
CurAliasType:=@AliasTypeStorage;
end else
CurAliasType:=nil;
OldFlags:=Params.Flags;
Exclude(Params.Flags,fdfFindVariable);
// read the expression from left to right and calculate the type
StackPtr:=-1;
MoveCursorToCleanPos(StartPos);
repeat
// read operand
CurExprType:=ReadOperandTypeAtCursor(Params,EndPos,CurAliasType);
{$IFDEF ShowExprEval}
DebugLn(['[TFindDeclarationTool.FindExpressionResultType] Operand: ',
ExprTypeToString(CurExprType),' Alias=',FindContextToString(CurAliasType)]);
{$ENDIF}
// put operand on stack
inc(StackPtr);
if StackPtr>High(ExprStack) then
RaiseInternalErrorStack;
StackEntry:=@ExprStack[StackPtr];
StackEntry^.Operand.Expr:=CurExprType;
if CurAliasType<>nil then
StackEntry^.Operand.AliasType:=CurAliasType^
else
StackEntry^.Operand.AliasType:=CleanFindContext;
StackEntry^.theOperator.StartPos:=-1;
StackEntry^.OperatorLvl:=5;
// read operator
ReadNextAtom;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.FindExpressionResultType] Operator: ',
GetAtom,' CurPos.EndPos=',dbgs(CurPos.EndPos),' EndPos=',dbgs(EndPos));
{$ENDIF}
IsEnd := (CurPos.EndPos>EndPos) or (CurExprType.Desc=xtNone);
if not IsEnd then
IsBinOpError := not WordIsBinaryOperator.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
else
IsBinOpError := False;
// check if expression is completely parsed
if IsEnd or (IsBinOpError and (fdfIgnoreOperatorError in Params.Flags)) then
begin
// -> execute complete stack
ExecuteStack(true);
Result:=ExprStack[StackPtr].Operand.Expr;
if CurAliasType<>nil then
AliasType^:=ExprStack[StackPtr].Operand.AliasType;
Params.Flags:=OldFlags;
exit;
end;
if IsBinOpError then
RaiseBinaryOperatorNotFound;
// put operator on stack
ExprStack[StackPtr].theOperator:=CurPos;
// find operator precendence level
if WordIsLvl1Operator.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
then
ExprStack[StackPtr].OperatorLvl:=1
else if WordIsLvl2Operator.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
then
ExprStack[StackPtr].OperatorLvl:=2
else if WordIsLvl3Operator.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
then
ExprStack[StackPtr].OperatorLvl:=3
else if WordIsLvl4Operator.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
then
ExprStack[StackPtr].OperatorLvl:=4
else
RaiseInternalError;
// execute stack if possible
ExecuteStack(false);
// move cursor to next atom (= next operand start)
ReadNextAtom;
until false;
end;
function TFindDeclarationTool.FindIdentifierInUsesSection(
UsesNode: TCodeTreeNode; Params: TFindDeclarationParams;
FindMissingFPCUnits: Boolean): boolean;
{ this function is internally used by FindIdentifierInContext
search backwards through the uses section
compare first all unit names, then load the units and search there
}
var
NewCodeTool: TFindDeclarationTool;
OldFlags: TFindDeclarationFlags;
Node: TCodeTreeNode;
CollectResult: TIdentifierFoundResult;
MissingUnit: TCodeTreeNode;
procedure RaiseUnitNotFound;
var
AnUnitName: String;
InFilename: String;
begin
AnUnitName:=ExtractUsedUnitName(MissingUnit,@InFilename);
RaiseExceptionInstance(
ECodeToolUnitNotFound.Create(Self,20170421200312,
Format(ctsUnitNotFound,[AnUnitName]),InFilename));
end;
var
AnUnitName: string;
InFilename: string;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(UsesNode);{$ENDIF}
{$IFDEF ShowTriedParentContexts}
DebugLn(['TFindDeclarationTool.FindIdentifierInUsesSection ',MainFilename,' fdfIgnoreUsedUnits=',fdfIgnoreUsedUnits in Params.Flags]);
{$ENDIF}
Result:=false;
// first search the identifier in the uses section (not in the interfaces of the units)
if (Params.IdentifierTool=Self) then begin
Node:=UsesNode.LastChild;
while Node<>nil do begin
if (fdfCollect in Params.Flags) then begin
CollectResult:=DoOnIdentifierFound(Params,Node.FirstChild);
if CollectResult=ifrAbortSearch then begin
Result:=false;
exit;
end else if CollectResult=ifrSuccess then begin
Result:=true;
Params.SetResult(Self,Node.FirstChild);
exit;
end;
end else if CompareSrcIdentifiers(Node.StartPos,Params.Identifier) then begin
// the searched identifier was a uses AUnitName, point to the identifier in
// the uses section
// if the unit name has a namespace defined point to the namespace
Params.SetResult(Self,Node.FirstChild);
Result:=true;
exit;
end;
Node:=Node.PriorBrother;
end;
end;
if not (fdfIgnoreUsedUnits in Params.Flags) then begin
MissingUnit:=nil;
// search in units
Node:=UsesNode.LastChild;
while Node<>nil do begin
AnUnitName:=ExtractUsedUnitName(Node,@InFilename);
if AnUnitName<>'' then begin
NewCodeTool:=FindCodeToolForUsedUnit(AnUnitName,InFilename,false);
if NewCodeTool<>nil then begin
// search the identifier in the interface of the used unit
OldFlags:=Params.Flags;
Params.Flags:=[fdfIgnoreUsedUnits]+(fdfGlobalsSameIdent*Params.Flags)
-[fdfExceptionOnNotFound];
Result:=NewCodeTool.FindIdentifierInInterface(Self,Params);
Params.Flags:=OldFlags;
if Result and Params.IsFoundProcFinal then exit;
end else if MissingUnit=nil then begin
MissingUnit:=Node;
end;
{$IFDEF ShowTriedParentContexts}
DebugLn(['TFindDeclarationTool.FindIdentifierInUsesSection ',AnUnitName,' Result=',Result]);
{$ENDIF}
end;
Node:=Node.PriorBrother;
end;
if (not Result) and (MissingUnit<>nil) then begin
// identifier not found and there is a missing unit
if FindMissingFPCUnits and Assigned(FOnRescanFPCDirectoryCache) then
begin
AnUnitName := LowerCase(AnUnitName);
if FFindMissingFPCUnits=nil then
FFindMissingFPCUnits := TFindIdentifierInUsesSection_FindMissingFPCUnit.Create;
if not FFindMissingFPCUnits.IsInResults(AnUnitName) // don't rescan twice
and FFindMissingFPCUnits.Find(AnUnitName, DirectoryCache) then
begin
FOnRescanFPCDirectoryCache(Self);
Result := FindIdentifierInUsesSection(UsesNode, Params, False);
end else
RaiseUnitNotFound;
end else
RaiseUnitNotFound;
end;
end;
end;
function TFindDeclarationTool.FindCodeToolForUsedUnit(const AnUnitName,
AnUnitInFilename: string; ExceptionOnNotFound: boolean): TFindDeclarationTool;
var
NewCode: TCodeBuffer;
begin
Result:=nil;
NewCode:=FindUnitSource(AnUnitName,AnUnitInFilename,ExceptionOnNotFound);
if (NewCode=nil) then begin
// no source found
if ExceptionOnNotFound then
RaiseException(20170421200315,'unit '+AnUnitName+' not found');
end else begin
// source found -> get codetool for it
{$IF defined(ShowTriedFiles) or defined(ShowTriedUnits)}
DebugLn('[TFindDeclarationTool.FindCodeToolForUsedUnit] ',
' This source is=',TCodeBuffer(Scanner.MainCode).Filename,
' NewCode=',NewCode.Filename);
{$ENDIF}
if Assigned(FOnGetCodeToolForBuffer) then
Result:=FOnGetCodeToolForBuffer(Self,NewCode,false)
else if NewCode=TCodeBuffer(Scanner.MainCode) then
Result:=Self;
end;
end;
function TFindDeclarationTool.FindIdentifierInInterface(
AskingTool: TFindDeclarationTool; Params: TFindDeclarationParams): boolean;
function CheckEntry(Entry: PInterfaceIdentCacheEntry): TIdentifierFoundResult;
begin
while Entry<>nil do begin
Params.SetResult(Self,Entry^.Node,Entry^.CleanPos);
Result:=DoOnIdentifierFound(Params,Params.NewNode);
if Result in [ifrSuccess,ifrAbortSearch] then
exit;
// proceed
Entry:=Entry^.Overloaded;
end;
Result:=ifrProceedSearch;
end;
var
CacheEntry: PInterfaceIdentCacheEntry;
AVLNode: TAVLTreeNode;
begin
Result:=false;
// build code tree
{$IFDEF ShowTriedContexts}
DebugLn({$IFDEF DebugPrefix}DebugPrefix,{$ENDIF}
'TFindDeclarationTool.FindIdentifierInInterface',
' Ident="',GetIdentifier(Params.Identifier),'"',
' IgnoreUsedUnits=',dbgs(fdfIgnoreUsedUnits in Params.Flags),
' Self=',TCodeBuffer(Scanner.MainCode).Filename
);
{$ENDIF}
// ToDo: build codetree for ppu, dcu files
// build tree for pascal source
if not BuildInterfaceIdentifierCache(true) then exit(false);
if (AskingTool<>Self) and (AskingTool<>nil) then
AskingTool.AddToolDependency(Self);
// search identifier in cache
if fdfCollect in Params.Flags then begin
AVLNode:=FInterfaceIdentifierCache.Items.FindLowest;
while AVLNode<>nil do begin
CacheEntry:=PInterfaceIdentCacheEntry(AVLNode.Data);
//DebugLn(['TFindDeclarationTool.FindIdentifierInInterface ',CacheEntry^.Identifier]);
case CheckEntry(CacheEntry) of
ifrSuccess: exit(true);
ifrAbortSearch: exit(false);
end;
AVLNode:=FInterfaceIdentifierCache.Items.FindSuccessor(AVLNode);
end;
end else begin
CacheEntry:=FInterfaceIdentifierCache.FindIdentifier(Params.Identifier);
if CacheEntry=nil then
exit(false);
case CheckEntry(CacheEntry) of
ifrSuccess: exit(true);
ifrAbortSearch: exit(false);
end;
end;
// proceed search
Result:=false;
end;
function TFindDeclarationTool.BuildInterfaceIdentifierCache(
ExceptionOnNotUnit: boolean): boolean;
procedure ScanForEnums(ParentNode: TCodeTreeNode);
var
Node: TCodeTreeNode;
begin
Node:=ParentNode.FirstChild;
if (Node=nil) or (Scanner.GetDirectiveValueAt(sdScopedEnums, Node.StartPos) = '1') then
Exit;
while Node<>nil do begin
if Node.Desc=ctnEnumIdentifier then
FInterfaceIdentifierCache.Add(@Src[Node.StartPos],Node,Node.StartPos);
if Node.FirstChild<>nil then
Node:=Node.FirstChild
else begin
while Node.NextBrother=nil do begin
Node:=Node.Parent;
if Node=ParentNode then exit;
end;
Node:=Node.NextBrother;
end;
end;
end;
procedure ScanChildren(ParentNode: TCodeTreeNode); forward;
procedure ScanNode(Node: TCodeTreeNode);
var
FirstChild: TCodeTreeNode;
begin
case Node.Desc of
ctnTypeSection,ctnConstSection,ctnVarSection,ctnResStrSection,ctnPropertySection:
ScanChildren(Node);
ctnVarDefinition,ctnConstDefinition,ctnTypeDefinition,ctnGlobalProperty:
begin
FInterfaceIdentifierCache.Add(@Src[Node.StartPos],Node,Node.StartPos);
ScanForEnums(Node);
FirstChild:=Node.FirstChild;
if (Node.Desc = ctnTypeDefinition) and (FirstChild<>nil) then begin
case FirstChild.Desc of
ctnClassHelper, ctnRecordHelper, ctnTypeHelper:
FInterfaceHelperCache[fdhlkDelphiHelper].AddFromHelperNode(FirstChild, Self,
True{ use last found helper}
);
ctnObjCCategory:
FInterfaceHelperCache[fdhlkObjCCategory].AddFromHelperNode(FirstChild, Self, false);
end;
end;
end;
ctnGenericType:
if Node.FirstChild<>nil then begin
FInterfaceIdentifierCache.Add(@Src[Node.FirstChild.StartPos],Node,Node.StartPos);
ScanForEnums(Node);
end;
ctnProperty:
begin
MoveCursorToPropName(Node);
FInterfaceIdentifierCache.Add(@Src[CurPos.StartPos],Node,Node.StartPos);
end;
ctnProcedure:
if (Node.FirstChild<>nil) and (not NodeIsOperator(Node)) then
FInterfaceIdentifierCache.Add(@Src[Node.FirstChild.StartPos],Node,
Node.FirstChild.StartPos);
end;
end;
procedure ScanChildren(ParentNode: TCodeTreeNode);
var
Node: TCodeTreeNode;
begin
Node:=ParentNode.FirstChild;
while Node<>nil do begin
ScanNode(Node);
Node:=Node.NextBrother;
end;
end;
var
InterfaceNode: TCodeTreeNode;
Node: TCodeTreeNode;
HelperKind: TFDHelpersListKind;
begin
// build tree for pascal source
//debugln(['TFindDeclarationTool.BuildInterfaceIdentifierCache BEFORE ',MainFilename]);
BuildTree(lsrImplementationStart);
//debugln(['TFindDeclarationTool.BuildInterfaceIdentifierCache AFTER ',MainFilename]);
if Tree.Root=nil then exit(false);
// search interface section
InterfaceNode:=FindInterfaceNode;
if InterfaceNode=nil then begin
// check source type
if ExceptionOnNotUnit then begin
MoveCursorToNodeStart(Tree.Root);
ReadNextAtom; // read keyword for source type, e.g. 'unit'
if not UpAtomIs('UNIT') then
RaiseException(20170421200317,ctsSourceIsNotUnit);
RaiseException(20170421200319,ctsInterfaceSectionNotFound);
end;
end;
// create tree
if (FInterfaceIdentifierCache<>nil) and FInterfaceIdentifierCache.Complete then
exit(true);
if FInterfaceIdentifierCache=nil then
FInterfaceIdentifierCache:=TInterfaceIdentifierCache.Create(Self)
else
FInterfaceIdentifierCache.Clear;
FInterfaceIdentifierCache.Complete:=true;
for HelperKind in TFDHelpersListKind do
if FInterfaceHelperCache[HelperKind]=nil then
FInterfaceHelperCache[HelperKind]:=TFDHelpersList.Create(HelperKind)
else
FInterfaceHelperCache[HelperKind].Clear;
// add unit node
MoveCursorToNodeStart(Tree.Root);
ReadNextAtom; // keyword unit
ReadNextAtom;
FInterfaceIdentifierCache.Add(@Src[CurPos.StartPos],Tree.Root,CurPos.StartPos);
// create nodes
if InterfaceNode<>nil then
// scan interface
ScanChildren(InterfaceNode)
else begin
// scan program
Node:=Tree.Root;
while Node<>nil do begin
ScanNode(Node);
Node:=Node.NextBrother;
end;
end;
//DebugLn(['TFindDeclarationTool.BuildInterfaceIdentifierCache ',MainFilename,' ',FInterfaceIdentifierCache.Items.Count,' ',GlobalIdentifierTree.Count]);
Result:=true;
end;
function TFindDeclarationTool.CompareNodeIdentifier(Node: TCodeTreeNode;
Params: TFindDeclarationParams): boolean;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=false;
if Node=nil then exit;
if Node.Desc in AllSourceTypes then begin
MoveCursorToNodeStart(Node);
ReadNextAtom;
if (Node.Desc=ctnProgram) and not UpAtomIs('PROGRAM') then exit;
ReadNextAtom;
Result:=CompareSrcIdentifiers(CurPos.StartPos,Params.Identifier);
end else if (Node.Desc in AllSimpleIdentifierDefinitions)
or (Node.Desc in [ctnIdentifier,ctnGenericName]) then begin
Result:=CompareSrcIdentifiers(Node.StartPos,Params.Identifier);
end else if Node.Desc=ctnGenericType then begin
if Node.FirstChild<>nil then
Result:=CompareSrcIdentifiers(Node.FirstChild.StartPos,Params.Identifier);
end;
end;
function TFindDeclarationTool.GetInterfaceNode: TCodeTreeNode;
begin
Result:=Tree.Root;
if Result=nil then begin
CurPos.StartPos:=-1;
RaiseException(20170421200323,'[TFindDeclarationTool.GetInterfaceNode] no code tree found');
end;
if not (Tree.Root.Desc in AllUsableSourceTypes) then begin
CurPos.StartPos:=-1;
RaiseException(20170421200325,ctsUsedUnitIsNotAPascalUnit);
end;
Result:=FindInterfaceNode;
if Result=nil then begin
CurPos.StartPos:=-1;
RaiseException(20170421200327,ctsInterfaceSectionNotFound);
end;
end;
function TFindDeclarationTool.FindIdentifierInUsedUnit(
const AnUnitName: string; Params: TFindDeclarationParams; ErrorPos: integer
): boolean;
{ Note: this function is internally used by FindIdentifierInHiddenUsedUnits
for hidden used units, like the system unit or the objpas unit
}
var
NewCode: TCodeBuffer;
NewCodeTool: TFindDeclarationTool;
OldFlags: TFindDeclarationFlags;
begin
Result:=false;
// open the unit and search the identifier in the interface
NewCode:=FindUnitSource(AnUnitName,'',true,ErrorPos);
if NewCode=TCodeBuffer(Scanner.MainCode) then begin
// Searching again in hidden unit
DebugLn('WARNING: Searching again in hidden unit: "',NewCode.Filename,'" identifier=',GetIdentifier(Params.Identifier));
NewCodeTool:=Self;
CurPos.StartPos:=ErrorPos;
RaiseExceptionFmt(20170421200330,ctsIllegalCircleInUsedUnits,[AnUnitName]);
end else begin
// source found -> get codetool for it
{$IF defined(ShowTriedContexts) or defined(ShowTriedUnits)}
DebugLn('[TFindDeclarationTool.FindIdentifierInUsedUnit] ',
' This source is=',TCodeBuffer(Scanner.MainCode).Filename,
' NewCode=',NewCode.Filename,' IgnoreUsedUnits=',dbgs(fdfIgnoreUsedUnits in Params.Flags));
{$ENDIF}
NewCodeTool:=nil;
if not Assigned(FOnGetCodeToolForBuffer) then begin
CurPos.StartPos:=ErrorPos;
RaiseExceptionFmt(20170421200333,
'Unable to create codetool for "%s", need OnGetCodeToolForBuffer',
[NewCode.Filename]);
end;
NewCodeTool:=FOnGetCodeToolForBuffer(Self,NewCode,false);
if NewCodeTool=nil then begin
CurPos.StartPos:=ErrorPos;
RaiseExceptionFmt(20170421200346,'Unable to create codetool for "%s"',[NewCode.Filename]);
end;
// search the identifier in the interface of the used unit
OldFlags:=Params.Flags;
Params.Flags:=[fdfIgnoreUsedUnits]+(fdfGlobalsSameIdent*Params.Flags)
-[fdfExceptionOnNotFound];
Result:=NewCodeTool.FindIdentifierInInterface(Self,Params);
Params.Flags:=OldFlags;
end;
end;
function TFindDeclarationTool.FindIdentifierInTypeOfConstant(
VarConstNode: TCodeTreeNode; Params: TFindDeclarationParams): boolean;
{ const a: atype = context;
for example: const p: TPoint = (x:0; y:0);
}
var
TypeNode: TCodeTreeNode;
ExprType: TExpressionType;
TypeParams: TFindDeclarationParams;
OldInput: TFindDeclarationInput;
begin
Result:=false;
//debugln(['TFindDeclarationTool.FindIdentifierInTypeOfConstant ',VarConstNode.DescAsString]);
TypeNode:=VarConstNode.FirstChild;
if TypeNode=nil then exit;
if TypeNode.Desc=ctnIdentifier then begin
// resolve type
//debugln(['TFindDeclarationTool.FindIdentifierInTypeOfConstant ']);
TypeParams:=TFindDeclarationParams.Create(Params);
try
TypeParams.ContextNode:=TypeNode;
TypeParams.SetIdentifier(Self,nil,nil);
TypeParams.Flags:=fdfDefaultForExpressions;
ExprType:=FindExpressionTypeOfTerm(TypeNode.StartPos,-1,TypeParams,false);
//debugln(['TFindDeclarationTool.FindIdentifierInTypeOfConstant ExprType=',ExprTypeToString(ExprType)]);
finally
TypeParams.Free;
end;
if ExprType.Desc=xtContext then begin
if ExprType.Context.Node.Parent=nil then exit;
if not (ExprType.Context.Node.Parent.Desc in [ctnTypeDefinition,ctnGenericType])
then
exit;
// search identifier in type
Params.Save(OldInput);
Params.ContextNode:=ExprType.Context.Node;
Params.Flags:=Params.Flags-[fdfIgnoreCurContextNode,fdfSearchInParentNodes];
Result:=ExprType.Context.Tool.FindIdentifierInContext(Params);
Params.Load(OldInput,true);
end;
end;
end;
procedure TFindDeclarationTool.RaiseUsesExpected(id: int64);
begin
RaiseExceptionFmt(id,ctsStrExpectedButAtomFound,['"uses"',GetAtom]);
end;
procedure TFindDeclarationTool.RaiseStrConstExpected(id: int64);
begin
RaiseExceptionFmt(id,ctsStrExpectedButAtomFound,[ctsStringConstant,GetAtom]);
end;
procedure TFindDeclarationTool.BeginParsing(Range: TLinkScannerRange);
begin
// scan code and init parser
inherited BeginParsing(Range);
// now the scanner knows, which compiler mode is needed
// -> setup compiler dependent tables
case Scanner.PascalCompiler of
pcDelphi: WordIsPredefinedIdentifier:=WordIsPredefinedDelphiIdentifier;
pcPas2js: WordIsPredefinedIdentifier:=WordIsPredefinedPas2jsIdentifier;
else
WordIsPredefinedIdentifier:=WordIsPredefinedFPCIdentifier;
end;
end;
function TFindDeclarationTool.FindIdentifierInHiddenUsedUnits(
Params: TFindDeclarationParams): boolean;
var
HiddenUnits: String;
p: Integer;
AnUnitName: String;
begin
Result:=false;
{$IFDEF ShowTriedContexts}
DebugLn('[TFindDeclarationTool.FindIdentifierInHiddenUsedUnits] ',
'"',GetIdentifier(Params.Identifier),'" IgnoreUsedUnits=',dbgs(fdfIgnoreUsedUnits in Params.Flags));
{$ENDIF}
if (Tree.Root<>nil) and (not (fdfIgnoreUsedUnits in Params.Flags)) then begin
HiddenUnits:=Scanner.GetHiddenUsedUnits;
{$IFDEF ShowTriedContexts}
debugln(['TFindDeclarationTool.FindIdentifierInHiddenUsedUnits Identifier=',GetIdentifier(Params.Identifier),' ',Scanner.MainFilename,' SourceName=',Scanner.SourceName,' HiddenUnits=',HiddenUnits]);
{$ENDIF}
p:=length(HiddenUnits);
while p>=1 do begin
while (p>1) and (HiddenUnits[p-1]<>',') do dec(p);
AnUnitName:=GetDottedIdentifier(@HiddenUnits[p]);
if AnUnitName<>'' then begin
// try hidden used unit
Result:=FindIdentifierInUsedUnit(AnUnitName,Params,0);
if Result and Params.IsFoundProcFinal then exit;
end;
dec(p);
end;
end;
end;
function TFindDeclarationTool.FindEndOfTerm(
StartPos: integer; ExceptionIfNoVariableStart, WithAsOperator: boolean
): integer;
{ ExceptionIfNoVariableStart: if false allow starting in the middle of a term
a variable can have the form:
A
A.B()^.C()[]^^.D
(A).B
inherited A
A as B
}
procedure RaiseIdentNotFound;
begin
RaiseExceptionFmt(20170421200525,ctsIdentExpectedButAtomFound,[GetAtom]);
end;
var
FirstIdentifier: boolean;
procedure StartVar;
begin
ReadNextAtom;
if UpAtomIs('INHERITED') then
ReadNextAtom;
if UpAtomIs('ARRAY') then
begin
ReadNextAtom;
if UpAtomIs('OF') then
ReadNextAtom;
end;
FirstIdentifier:=true;
if not (CurPos.Flag in AllCommonAtomWords) then exit;
AtomIsIdentifierE;
FirstIdentifier:=false;
ReadNextAtom;
end;
begin
MoveCursorToCleanPos(StartPos);
StartVar;
repeat
case CurPos.Flag of
cafRoundBracketOpen:
begin
ReadTilBracketClose(true);
FirstIdentifier:=false;
end;
cafPoint:
begin
if FirstIdentifier and ExceptionIfNoVariableStart then
RaiseIdentNotFound;
ReadNextAtom;
AtomIsIdentifierE;
end;
cafEdgedBracketOpen:
begin
if FirstIdentifier and ExceptionIfNoVariableStart then
RaiseIdentNotFound;
ReadTilBracketClose(true);
end;
else
if AtomIsChar('^') then begin
if FirstIdentifier and ExceptionIfNoVariableStart then
RaiseIdentNotFound;
end else if UpAtomIs('AS') then begin
if not WithAsOperator then
break;
StartVar;
UndoReadNextAtom;
end else
break;
end;
ReadNextAtom;
until false;
if LastAtoms.Count>0 then
UndoReadNextAtom
else
MoveCursorToCleanPos(StartPos);
Result:=CurPos.EndPos;
end;
function TFindDeclarationTool.FindStartOfTerm(EndPos: integer; InType: boolean
): integer;
{ a variable can be combinations of
1. A.B
2. A().B
3. inherited A
4. A[].
5. A[].B
6. A^.
7. (A).
8. (A as B)
9. (@A)
10. A()[]
11. nothing (e.g. cursor behind semicolon, keyword or closing bracket)
12. 'A'.B (constant.B, type helpers)
}
procedure RaiseIdentNotFound;
begin
RaiseExceptionFmt(20170421200528,ctsIdentExpectedButAtomFound,[GetAtom]);
end;
var CurAtom, NextAtom: TAtomPosition;
NextAtomType, CurAtomType: TVariableAtomType;
StartPos: LongInt;
CurIsValue, NextIsValue: Boolean;
begin
StartPos:=FindStartOfAtom(Src,EndPos);
MoveCursorToCleanPos(StartPos);
NextAtom:=CurPos;
if not IsSpaceChar[Src[StartPos]] then
ReadNextAtom;
NextAtomType:=GetCurrentAtomType;
NextIsValue:=NextAtomType in [vatIdentifier,vatPreDefIdentifier,vatNumber,vatStringConstant];
repeat
ReadPriorAtom;
CurAtom:=CurPos;
CurAtomType:=GetCurrentAtomType;
if CurAtomType=vatNone then begin
Result:=NextAtom.StartPos;
exit;
end;
//DebugLn(['TFindDeclarationTool.FindStartOfTerm ',GetAtom,' Cur=',VariableAtomTypeNames[CurAtomType],' Next=',VariableAtomTypeNames[NextAtomType]]);
if CurAtomType in [vatRoundBracketClose,vatEdgedBracketClose] then begin
if NextAtomType in [vatRoundBracketOpen,vatRoundBracketClose,
vatEdgedBracketOpen,vatEdgedBracketClose,vatPoint,vatUp,
vatAS,vatNone,vatSpace]
then begin
ReadBackTilBracketOpen(true);
CurAtom.StartPos:=CurPos.StartPos;
end else begin
Result:=NextAtom.StartPos;
exit;
end;
end;
// check if CurAtom belongs to variable
if CurAtomType=vatINHERITED then begin
Result:=CurAtom.StartPos;
exit;
end;
if (CurAtomType in [vatAS,vatKeyword]) then begin
Result:=NextAtom.StartPos;
exit;
end;
if (CurAtomType=vatUp) and InType then begin
Result:=NextAtom.StartPos;
exit;
end;
CurIsValue:=CurAtomType in [vatIdentifier,vatPreDefIdentifier,vatNumber,vatStringConstant];
if (not (CurAtomType in [vatIdentifier,vatPreDefIdentifier,vatNumber,vatStringConstant,
vatPoint,vatUp,vatEdgedBracketClose,vatRoundBracketClose]))
or (CurIsValue and NextIsValue)
then begin
// boundary found between current and next
if NextAtom.StartPos>=EndPos then begin
// no token belongs to a variable (e.g. ; ;)
Result:=EndPos;
end else begin
// the next atom is the start of the variable
if (not (NextAtomType in [vatSpace,vatIdentifier,vatPreDefIdentifier,
vatRoundBracketClose,vatEdgedBracketClose,vatAddrOp])) then
begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIdentNotFound;
end;
Result:=NextAtom.StartPos;
end;
exit;
end;
NextAtom:=CurAtom;
NextAtomType:=CurAtomType;
NextIsValue:=CurIsValue;
until false;
end;
function TFindDeclarationTool.NodeTermInType(Node: TCodeTreeNode): boolean;
begin
if Node=nil then exit(false);
Result:=not (Node.Desc in AllPascalStatements);
end;
function TFindDeclarationTool.FindExpressionTypeOfTerm(StartPos,
EndPos: integer; Params: TFindDeclarationParams; WithAsOperator: boolean;
AliasType: PFindContext): TExpressionType;
{ examples
1. A.B
2. A().B
3. inherited A
4. A[]
5. A[].B
6. A^.
7. (A).
8. (A as B)
9. (@A)
10. A as B
}
type
TIsIdentEndOfVar = (iieovYes, iieovNo, iieovUnknown);
var
CurAtomType: TVariableAtomType;
NextAtomType: TVariableAtomType; // next, if CurAtomType is brackets then after the brackets
PrevAtomType: TVariableAtomType; // previous, start of brackets
CurAtom, NextAtom: TAtomPosition;
CurAtomBracketEndPos: integer;
StartNode: TCodeTreeNode;
OldInput: TFindDeclarationInput;
StartFlags: TFindDeclarationFlags;
IsIdentEndOfVar: TIsIdentEndOfVar;
FlagCanBeForwardDefined, FlagCanBeForwardDefinedValid: boolean;
ExprType: TExpressionType;
procedure RaiseIdentExpected;
begin
RaiseExceptionFmt(20170421200530,ctsStrExpectedButAtomFound,[ctsIdentifier,GetAtom]);
end;
procedure RaiseIdentNotFound;
begin
RaiseExceptionFmt(20170421200532,ctsIdentifierNotFound,[GetAtom]);
end;
procedure RaiseIllegalQualifierFound;
begin
RaiseExceptionFmt(20170421200535,ctsIllegalQualifier,[GetAtom]);
end;
procedure RaisePointNotFound;
begin
RaiseExceptionFmt(20170421200537,ctsStrExpectedButAtomFound,['.',GetAtom]);
end;
procedure RaiseClassDeclarationNotFound(Tool: TFindDeclarationTool);
begin
Tool.RaiseExceptionFmt(20170421200539,ctsClassSNotFound, [Tool.GetAtom]);
end;
function InitAtomQueue: boolean;
procedure RaiseInternalError;
begin
RaiseException(20170421200543,'internal codetool error: FindExpressionTypeOfVariable '
+' StartPos='+IntToStr(StartPos)+' EndPos='+IntToStr(EndPos));
end;
begin
Result:=false;
if StartPos<1 then
StartPos:=FindStartOfTerm(EndPos,NodeTermInType(Params.ContextNode))
else if EndPos<1 then
EndPos:=FindEndOfTerm(StartPos,true,WithAsOperator);
//DebugLn(['InitAtomQueue StartPos=',StartPos,'=',dbgstr(copy(Src,StartPos,10)),' EndPos=',dbgstr(copy(Src,EndPos,10))]);
if (StartPos<1) then
RaiseInternalError;
if StartPos>SrcLen then exit;
if StartPos=EndPos then begin
// e.g. cursor behind semicolon, keyword or closing bracket
exit;
end;
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm InitAtomQueue StartPos=',StartPos,' EndPos=',EndPos,' Expr="',copy(Src,StartPos,EndPos-StartPos),'"']);
{$ENDIF}
PrevAtomType:=vatNone;
MoveCursorToCleanPos(StartPos);
ReadNextAtom;
if CurPos.StartPos>SrcLen then exit;
CurAtom:=CurPos;
CurAtomType:=GetCurrentAtomType;
if CurAtomType in [vatRoundBracketOpen,vatEdgedBracketOpen] then
ReadTilBracketClose(true);
CurAtomBracketEndPos:=CurPos.EndPos;
ReadNextAtom;
NextAtom:=CurPos;
if NextAtom.EndPos<=EndPos then
NextAtomType:=GetCurrentAtomType
else
NextAtomType:=vatSpace;
MoveCursorToCleanPos(CurAtom.StartPos);
IsIdentEndOfVar:=iieovUnknown;
FlagCanBeForwardDefinedValid:=false;
Result:=true;
end;
procedure ReadNextExpressionAtom;
begin
PrevAtomType:=CurAtomType;
CurAtom:=NextAtom;
CurAtomType:=NextAtomType;
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
if CurAtomType in [vatRoundBracketOpen,vatEdgedBracketOpen] then
ReadTilBracketClose(true);
CurAtomBracketEndPos:=CurPos.EndPos;
ReadNextAtom;
NextAtom:=CurPos;
if NextAtom.EndPos<=EndPos then
NextAtomType:=GetCurrentAtomType
else
NextAtomType:=vatSpace;
MoveCursorToCleanPos(CurAtom.StartPos);
IsIdentEndOfVar:=iieovUnknown;
end;
function IsIdentifierEndOfVariable: boolean;
var BehindFuncAtomType: TVariableAtomType;
begin
if IsIdentEndOfVar=iieovUnknown then begin
if CurAtom.StartPos>=EndPos then begin
IsIdentEndOfVar:=iieovYes;
end else if CurAtom.Flag=cafWord then begin
MoveCursorToCleanPos(CurAtom.EndPos);
ReadNextAtom;
if AtomIsChar('(') then begin
ReadTilBracketClose(true);
ReadNextAtom;
end;
if CurPos.StartPos<EndPos then begin
BehindFuncAtomType:=GetCurrentAtomType;
if (BehindFuncAtomType in [vatPoint,vatUP,
vatEdgedBracketOpen,vatRoundBracketOpen])
then
IsIdentEndOfVar:=iieovNo
else
IsIdentEndOfVar:=iieovYes;
end else begin
IsIdentEndOfVar:=iieovYes;
end;
end else begin
IsIdentEndOfVar:=iieovNo
end;
end;
Result:=(IsIdentEndOfVar=iieovYes);
end;
function CanBeForwardDefined: boolean;
var
Node: TCodeTreeNode;
begin
if not FlagCanBeForwardDefinedValid then begin
FlagCanBeForwardDefinedValid:=true;
FlagCanBeForwardDefined:=false;
Node:=StartNode;
while Node<>nil do begin
if Node.Desc in [ctnTypeDefinition,ctnGenericType] then begin
FlagCanBeForwardDefined:=true;
break;
end else if not (Node.Desc in AllPascalTypes) then
break;
Node:=Node.Parent;
end;
end;
Result:=FlagCanBeForwardDefined;
end;
procedure ResolveTypeLessProperty;
begin
if ExprType.Desc<>xtContext then exit;
with ExprType.Context do begin
if not (Node.Desc in [ctnProperty,ctnGlobalProperty]) then exit;
if Tool.PropNodeIsTypeLess(Node)
and Tool.MoveCursorToPropName(Node) then begin
// typeless property => search in ancestors: it can be property with parameters
Params.Save(OldInput);
Params.SetIdentifier(Tool,@Tool.Src[Tool.CurPos.StartPos],nil);
Params.Flags:=[fdfSearchInAncestors,fdfSearchInHelpers];
if Tool.FindIdentifierInAncestors(Node.Parent.Parent,Params) then begin
Tool:=Params.NewCodeTool;
Node:=Params.NewNode;
end;
Params.Load(OldInput,true);
end;
end;
end;
procedure ResolveBaseTypeOfIdentifier;
{ normally not the identifier is searched, but its type
but there is one exception:
if the identifier is a function and it is the end of the variable then
the decision is based on the fdfFunctionResult flag.
}
var
ProcNode, FuncResultNode: TCodeTreeNode;
AtEnd: Boolean;
CurAliasType: PFindContext;
Context: TFindContext;
begin
//DebugLn(['ResolveBaseTypeOfIdentifier ',ExprType.Context.Node<>nil]);
if ExprType.Desc=xtContext then
Context:=ExprType.Context
else
Context:=CreateFindContext(Self,StartNode);
if (Context.Node=nil) then exit;
AtEnd:=IsIdentifierEndOfVariable;
// check if at the end of the variable
if AtEnd and (fdfFindVariable in StartFlags) then begin
// the variable is wanted, not its type
exit;
end;
if (not AtEnd)
and (Context.Node.Desc in [ctnProperty,ctnGlobalProperty])
then begin
ResolveTypeLessProperty;
end;
CurAliasType:=nil;
if AtEnd then CurAliasType:=AliasType;
// find base type
Params.Flags:=Params.Flags+[fdfEnumIdentifier]-[fdfFunctionResult,fdfFindChildren];
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveBaseTypeOfIdentifier BEFORE ExprType=',ExprTypeToString(ExprType),' Alias=',CurAliasType<>nil]);
{$ENDIF}
ExprType:=Context.Tool.ConvertNodeToExpressionType(
Context.Node,Params,CurAliasType);
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveBaseTypeOfIdentifier AFTER ExprType=',ExprTypeToString(ExprType),' Alias=',FindContextToString(CurAliasType)]);
{$ENDIF}
if (ExprType.Desc=xtContext)
and (ExprType.Context.Node.Desc in [ctnProcedure,ctnProcedureHead]) then
begin
// check if this is a function
ProcNode:=ExprType.Context.Node;
if ProcNode.Desc=ctnProcedureHead then
ProcNode:=ProcNode.Parent;
ExprType.Context.Tool.BuildSubTreeForProcHead(ProcNode.FirstChild,
FuncResultNode);
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveBaseTypeOfIdentifier IsFunction=',FuncResultNode<>nil,' IsIdentifierEndOfVariable=',IsIdentifierEndOfVariable,' fdfFunctionResult in StartFlags=',fdfFunctionResult in StartFlags]);
{$ENDIF}
if (FuncResultNode<>nil) then begin
// it is function
// -> use the result type instead of the function
if AtEnd then begin
// this function identifier is the end of the variable
if not (fdfFunctionResult in StartFlags) then
exit;
end;
Include(Params.Flags,fdfFunctionResult);
ExprType:=ExprType.Context.Tool.ConvertNodeToExpressionType(
ProcNode,Params,CurAliasType);
end;
end;
end;
function ResolveUseUnit(StartUseUnitNode: TCodeTreeNode): TCodeTreeNode;
// IsStart=true, NextAtomType=vatPoint,
// StartUseUnitNameNode.Desc=ctnUseUnit
// -> Find the longest namespaced used unit (ctnUseUnitNamespace,ctnUseUnitClearName)
// or the source name (ctnIdentifier), that fits the start of the
// current identifier a.b.c...
//
function GetPrevUseUnit(UseUnitNode: TCodeTreeNode): TCodeTreeNode;
begin
if UseUnitNode.PriorBrother<>nil then
Result:=UseUnitNode.PriorBrother
else begin
if UseUnitNode.Parent.Parent.Desc=ctnImplementation then begin
Result:=FindMainUsesNode;
if Result<>nil then
Result:=Result.FirstChild;
end else
Result:=nil;
end;
end;
var
UseUnitNode, Node, BestNode: TCodeTreeNode;
HasNamespace: Boolean;
Count, Level, BestLevel: Integer;
p: PChar;
DottedIdentifier: String;
begin
Result:=StartUseUnitNode.FirstChild;
//debugln(['ResolveUsenit START ',NextAtomType,' ',StartUseUnitNode.DescAsString,' "',GetIdentifier(@Src[CurAtom.StartPos]),'"']);
// find all candidates
Count:=0;
HasNamespace:=false;
UseUnitNode:=StartUseUnitNode;
repeat
if (UseUnitNode.FirstChild<>nil)
and CompareSrcIdentifiers(CurAtom.StartPos,UseUnitNode.StartPos) then begin
// found candidate
inc(Count);
//debugln(['ResolveUsenit candidate found']);
if UseUnitNode.FirstChild.Desc=ctnUseUnitNamespace then begin
HasNamespace:=true;
end;
end;
UseUnitNode:=GetPrevUseUnit(UseUnitNode);
until UseUnitNode=nil;
//debugln(['ResolveUsenit CandidateCount=',Count,' HasNamespace=',HasNamespace]);
if not HasNamespace then exit;
// multiple uses start with this identifier -> collect candidates
//debugln(['ResolveUsenit collect candidates ...']);
// read a.b.c...
DottedIdentifier:=GetIdentifier(@Src[CurAtom.StartPos]);
MoveCursorToCleanPos(NextAtom.EndPos);
Level:=1;
repeat
ReadNextAtom;
if not AtomIsIdentifier then break;
inc(Level);
DottedIdentifier:=DottedIdentifier+'.'+GetAtom;
ReadNextAtom;
until CurPos.Flag<>cafPoint;
//debugln(['ResolveUsenit DottedIdentifier="',DottedIdentifier,'"']);
// find longest dotted unit name in uses and source name
UseUnitNode:=StartUseUnitNode;
BestNode:=nil;
BestLevel:=0;
repeat
Node:=UseUnitNode.FirstChild; // ctnUseUnitNamespace or ctnUseUnitClearName
UseUnitNode:=GetPrevUseUnit(UseUnitNode);
if (Node<>nil)
and CompareSrcIdentifiers(CurAtom.StartPos,Node.StartPos) then begin
// found candidate
//debugln(['ResolveUseUnit Candidate=',ExtractNode(Node,[])]);
Level:=1;
p:=PChar(DottedIdentifier);
repeat
inc(p,GetIdentLen(p));
if p^='.' then inc(p);
//writeln('ResolveUseUnit p=',p,' NextBrother=',Node.NextBrother<>nil);
if Node.NextBrother=nil then begin
// fits
if Level>BestLevel then begin
BestNode:=Node.Parent;
BestLevel:=Level;
end;
break;
end else if p^=#0 then begin
// unitname too long
break;
end else begin
Node:=Node.NextBrother;
//writeln('ResolveUseUnit p=',p,' node=',GetIdentifier(@Src[Node.StartPos]));
if not CompareSrcIdentifiers(Node.StartPos,p) then
break;
inc(Level);
end;
until false;
end;
until UseUnitNode=nil;
//debugln(['ResolveUseUnit collected candidates Best=',ExtractNode(BestNode,[])]);
//debugln(['ResolveUseUnit Src=',Tree.Root.DescAsString,' Name=',GetSourceName(false),' DottedIdentifier="',DottedIdentifier,'"']);
// check source name
if (Tree.Root.Desc in AllSourceTypes)
and (Tree.Root.FirstChild<>nil)
and (Tree.Root.FirstChild.Desc=ctnSrcName)
and CompareSrcIdentifiers(Tree.Root.FirstChild.StartPos,PChar(DottedIdentifier))
then begin
// found candidate
Level:=1;
Node:=Tree.Root.FirstChild.FirstChild;
//debugln(['ResolveUseUnit Candidate SrcName']);
p:=PChar(DottedIdentifier);
repeat
//debugln('ResolveUseUnit SrcName p=',p,' Node=',ExtractNode(Node,[]));
if (Node.FirstChild=nil) or (Node.NextBrother.Desc<>ctnIdentifier) then begin
// fits
//debugln(['ResolveUseUnit FITS Level=',Level,' Best=',BestLevel]);
if Level>BestLevel then begin
// source name fits best
Result:=Tree.Root.FirstChild.FirstChild;
// move cursor forward
while (Result.NextBrother<>nil)
and (NextAtom.EndPos<EndPos) do begin
if (Result.NextBrother=nil) then
exit(Tree.Root);
ReadNextExpressionAtom; // read point
ReadNextExpressionAtom; // read namespace/unitname
//debugln(['ResolveUseUnit Next ',GetAtom(CurAtom)]);
Result:=Result.NextBrother;
end;
//debugln(['ResolveUseUnit SrcName fits better']);
exit;
end;
break;
end else if p^=#0 then begin
// source name too long
break;
end else begin
Node:=Node.NextBrother;
inc(p,GetIdentLen(p));
if p^='.' then inc(p);
//debugln('ResolveUseUnit SrcName NEXT p=',p,' Node=',ExtractNode(Node,[]));
if not CompareSrcIdentifiers(Node.StartPos,p) then
break;
inc(Level);
end;
until false;
end;
Result:=BestNode;
if Result=nil then exit;
// Result is now a ctnUseUnit
Result:=Result.FirstChild;
// move cursor forward
while (Result.NextBrother<>nil) and (NextAtom.EndPos<EndPos) do begin
ReadNextExpressionAtom; // read point
ReadNextExpressionAtom; // read namespace/unitname
//debugln(['ResolveUseUnit Next ',GetAtom(CurAtom)]);
Result:=Result.NextBrother;
end;
end;
procedure ResolveIdentifier;
var
ProcNode: TCodeTreeNode;
IdentFound: boolean;
OldFlags: TFindDeclarationFlags;
ResultNode: TCodeTreeNode;
IsStart: Boolean;
Context: TFindContext;
IsEnd: Boolean;
SearchForwardToo: Boolean;
begin
// for example 'AnObject[3]'
{$IFDEF ShowExprEval}
debugln(['ResolveIdentifier "',GetAtom(CurAtom),'"']);
{$ENDIF}
// check special identifiers 'Result' and 'Self'
IdentFound:=false;
IsStart:=ExprType.Desc=xtNone;
IsEnd:=IsIdentifierEndOfVariable;
if IsStart then begin
// start context
if (StartNode.Desc in AllPascalStatements) then begin
if CompareSrcIdentifiers(CurAtom.StartPos,'SELF') then begin
// SELF in a method is the object itself
// -> check if in a method or nested proc of a method
if fdfExtractOperand in Params.Flags then
Params.AddOperandPart('Self');
ProcNode:=StartNode;
while (ProcNode<>nil) do begin
if (ProcNode.Desc=ctnProcedure) and NodeIsMethodBody(ProcNode) then
begin
ResultNode:=FindClassOfMethod(ProcNode,True,
fdfExceptionOnNotFound in Params.Flags);
if (ResultNode<>nil) and
(ResultNode.Desc in [ctnClassHelper,ctnRecordHelper,ctnTypeHelper])
then//Self is helper -> return extended type
begin
ExprType := FindExtendedExprOfHelper(ResultNode);
ResultNode := ExprType.Context.Node;
end else
begin//Self is class/record
if (ResultNode<>nil) and (ResultNode.Parent<>nil) then
begin
ExprType.Desc:=xtContext;
ExprType.Context.Tool:=Self;
end else
ExprType := CleanExpressionType;
end;
if IsEnd and (ResultNode<>nil) then
ResultNode := ResultNode.Parent;
ExprType.Context.Node:=ResultNode;
IdentFound:=ExprType.Desc<>xtNone;
break;
end;
ProcNode:=ProcNode.Parent;
end;
end else if CompareSrcIdentifiers(CurAtom.StartPos,'RESULT')
and (cmsResult in Scanner.CompilerModeSwitches) then begin
// RESULT has a special meaning in a function
// -> check if in a function
if fdfExtractOperand in Params.Flags then
Params.AddOperandPart('Result');
ProcNode:=StartNode;
while (ProcNode<>nil) do begin
if (ProcNode.Desc=ctnProcedure)
and (NodeIsFunction(ProcNode) or NodeIsOperator(ProcNode)) then
break;
ProcNode:=ProcNode.Parent;
end;
if (ProcNode<>nil) then begin
if IsEnd and (fdfFindVariable in StartFlags) then begin
BuildSubTreeForProcHead(ProcNode);
ResultNode:=ProcNode.FirstChild.FirstChild;
while (ResultNode<>nil) do begin
if ResultNode.Desc in [ctnVarDefinition,ctnIdentifier] then begin
// procedure: none
// operator: ctnVarDefinition,ctnIdentifier
// function: ctnIdentifier
ExprType.Desc:=xtContext;
ExprType.Context.Node:=ResultNode;
ExprType.Context.Tool:=Self;
exit;
end;
ResultNode:=ResultNode.NextBrother;
end;
end else begin
OldFlags:=Params.Flags;
Params.Flags:=Params.Flags+[fdfFunctionResult,fdfFindChildren];
ExprType.Context:=FindBaseTypeOfNode(Params,ProcNode);
ExprType.Desc:=xtContext;
Params.Flags:=OldFlags;
exit;
end;
end;
end;
end;
end;
// find identifier
if not IdentFound then begin
if not (ExprType.Desc in [xtContext,xtNone]) then
begin
// find special sub identifier
if (ExprType.Desc in xtAllTypeHelperTypes) then
begin
// found predefined basic type (e.g. string) without a context!
// -> search in type helpers
Params.Save(OldInput);
// build new param flags for sub identifiers
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,fdfSearchInHelpers]
+(fdfGlobals*Params.Flags);
Params.SetIdentifier(Self,@Src[CurAtom.StartPos],nil);
{$IFDEF ShowExprEval}
debugln(['ResolveIdentifier searching "',GetAtom(CurAtom),'" in helper of predefined type "',ExprTypeToString(ExprType),'"']);
{$ENDIF}
if FindIdentifierInBasicTypeHelpers(ExprType.Desc, Params) then
begin
ExprType.Desc:=xtContext;
ExprType.SubDesc:=xtNone;
ExprType.Context.Tool := Params.NewCodeTool;
ExprType.Context.Node := Params.NewNode;
{$IFDEF ShowExprEval}
debugln(['ResolveIdentifier "',GetAtom(CurAtom),'" Found In Helper: "',ExprTypeToString(ExprType),'"']);
{$ENDIF}
end else begin
{$IFDEF ShowExprEval}
debugln(['ResolveIdentifier "',GetAtom(CurAtom),'" NOT Found In Helper']);
{$ENDIF}
end;
Params.Load(OldInput,true);
end;
if ExprType.Desc in xtAllPredefinedTypes then begin
ExprType:=FindExpressionTypeOfPredefinedIdentifier(CurAtom.StartPos,
Params);
{$IFDEF CheckNodeTool}
if ExprType.Desc=xtContext then
ExprType.Context.Tool.CheckNodeTool(ExprType.Context.Node);
{$ENDIF}
{$IFDEF ShowExprEval}
debugln(['ResolveIdentifier Predefined "',GetAtom(CurAtom),'" : ',ExprType.Desc in xtAllTypeHelperTypes]);
{$ENDIF}
end;
end else
begin
// find identifier
if ExprType.Desc=xtContext then
Context:=ExprType.Context
else
Context:=CreateFindContext(Self,StartNode);
Params.Save(OldInput);
// build new param flags for sub identifiers
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,fdfSearchInHelpers]
+(fdfGlobals*Params.Flags);
Params.ContextNode:=Context.Node;
SearchForwardToo:=false;
if Context.Node=StartNode then begin
// there is no special context -> search in parent contexts too
Params.Flags:=Params.Flags+[fdfSearchInParentNodes,fdfIgnoreCurContextNode];
// check if searching forward too
if CanBeForwardDefined then begin
SearchForwardToo:=true;
Params.Flags:=Params.Flags-[fdfExceptionOnNotFound];
end;
end else begin
// only search in special context
Params.Flags:=Params.Flags+[fdfIgnoreUsedUnits];
if Assigned(Context.Node) and (Context.Node.Desc=ctnImplementation) then
Params.Flags:=Params.Flags+[fdfSearchInParentNodes];
if Context.Node.Desc=ctnObjCClass then
Exclude(Params.Flags,fdfExceptionOnNotFound); // ObjCClass has predefined identifiers like 'alloc'
end;
// check identifier for overloaded procs
if (IsEnd and (fdfIgnoreOverloadedProcs in StartFlags))
then
Include(Params.Flags,fdfIgnoreOverloadedProcs);
Params.SetIdentifier(Self,@Src[CurAtom.StartPos],@CheckSrcIdentifier);
// search ...
{$IFDEF ShowExprEval}
Debugln([' FindExpressionTypeOfTerm ResolveIdentifier "',GetAtom(CurAtom),'" backward ',BoolToStr(IsStart,'Main','Sub'),'Ident="',GetIdentifier(Params.Identifier),'" ContextNode="',Params.ContextNode.DescAsString,'" "',dbgstr(Context.Tool.Src,Params.ContextNode.StartPos,15),'" ',dbgs(Params.Flags)]);
{$ENDIF}
ExprType.Desc:=xtNone;
// first search backwards
if Context.Tool.FindIdentifierInContext(Params) then begin
ExprType.Desc:=xtContext;
end else if SearchForwardToo then begin
// then search forwards
Params.Load(OldInput,false);
Params.SetIdentifier(Self,@Src[CurAtom.StartPos],@CheckSrcIdentifier);
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound,
fdfIgnoreCurContextNode,fdfSearchForward]
+(fdfGlobals*Params.Flags);
Params.ContextNode:=Context.Node;
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveIdentifier "',GetAtom(CurAtom),'" forward SubIdent="',GetIdentifier(Params.Identifier),'" ContextNode="',Params.ContextNode.DescAsString,'" "',dbgstr(Context.Tool.Src,Params.ContextNode.StartPos,15),'" ',dbgs(Params.Flags)]);
{$ENDIF}
if FindIdentifierInContext(Params) then begin
ExprType.Desc:=xtContext;
end;
end;
if ExprType.Desc=xtContext then begin
// identifier found
if Params.NewCodeTool.NodeIsConstructor(Params.NewNode) then begin
// identifier is a constructor
if (Context.Node.Desc in AllClassObjects) then begin
if (not IsEnd) or (not (fdfFindVariable in StartFlags)) then begin
// examples:
// TMyClass.Create.
// :=TMyClass.Create;
// use this class (the constructor can be defined in the ancestor)
ExprType.Context:=Context;
Params.Load(OldInput,true);
exit;
end;
end;
end;
if IsStart and (NextAtomType=vatPoint)
and (Params.NewCodeTool=Self)
and (Params.NewNode.Desc in [ctnUseUnitClearName,ctnUseUnitNamespace])
then begin
// first identifier is a used unit -> find longest fitting unitname
//debugln(['ResolveIdentifier UseUnit FindLongest... ',Params.NewNode.DescAsString,' ',ExtractNode(Params.NewNode,[])]);
Params.NewNode:=ResolveUseUnit(Params.NewNode.Parent);
//debugln(['ResolveIdentifier UseUnit FoundLongest: ',Params.NewNode.DescAsString,' ',ExtractNode(Params.NewNode,[])]);
end;
ExprType.Context:=CreateFindContext(Params);
Params.Load(OldInput,true);
end else begin
// predefined identifier
if (Context.Node.Desc=ctnObjCClass)
and CompareSrcIdentifiers('alloc',@Src[CurAtom.StartPos])
then begin
// 'alloc' returns the class itself
ExprType.Context:=Context;
Params.Load(OldInput,true);
exit;
end;
Params.Load(OldInput,true);
if IsEnd then
ExprType:=FindExpressionTypeOfPredefinedIdentifier(CurAtom.StartPos,
Params,AliasType)
else
ExprType:=FindExpressionTypeOfPredefinedIdentifier(CurAtom.StartPos,
Params);
{$IFDEF CheckNodeTool}
if ExprType.Desc=xtContext then
ExprType.Context.Tool.CheckNodeTool(ExprType.Context.Node);
{$ENDIF}
end;
end;
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveIdentifier END Ident="',dbgstr(Src,StartPos,CurAtom.EndPos-StartPos),'" Expr=',ExprTypeToString(ExprType)]);
{$ENDIF}
end;
end;
procedure ResolveConstant;
var
IsStart: Boolean;
begin
IsStart:=ExprType.Desc=xtNone;
if not IsStart then
RaiseExceptionFmt(20170421200546,ctsOperatorExpectedButAtomFound,[GetAtom]);
if AtomIsStringConstant then begin
// string or char constant
if AtomIsCharConstant then
ExprType.Desc:=xtChar
else
ExprType.Desc:=xtConstString;
MoveCursorToCleanPos(CurPos.StartPos);
end
else if AtomIsNumber then begin
// ordinal or real constant
if AtomIsRealNumber then
ExprType.Desc:=xtConstReal
else
ExprType.Desc:=xtConstOrdInteger;
MoveCursorToCleanPos(CurPos.EndPos);
end else
RaiseExceptionFmt(20170421200548,ctsOperatorExpectedButAtomFound,[GetAtom]);
end;
procedure ResolveUseUnit;
var
AnUnitName: string;
InFilename: string;
aTool: TFindDeclarationTool;
NewCodeTool: TFindDeclarationTool;
NewNode: TCodeTreeNode;
begin
aTool:=ExprType.Context.Tool;
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveUseUnit used unit -> interface node ',dbgstr(ExprType.Context.Tool.ExtractNode(ExprType.Context.Node,[]))]);
{$ENDIF}
AnUnitName:=aTool.ExtractUsedUnitName(ExprType.Context.Node.Parent,@InFilename);
NewCodeTool:=aTool.FindCodeToolForUsedUnit(AnUnitName,InFilename,true);
NewCodeTool.BuildInterfaceIdentifierCache(true);
NewNode:=NewCodeTool.FindInterfaceNode;
ExprType.Context.Tool:=NewCodeTool;
ExprType.Context.Node:=NewNode;
end;
procedure ResolveChildren;
var
NewNode: TCodeTreeNode;
begin
if (ExprType.Context.Node=nil) then exit;
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveChildren']);
{$ENDIF}
ResolveBaseTypeOfIdentifier;
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveChildren ExprType=',ExprTypeToString(ExprType)]);
{$ENDIF}
NewNode:=ExprType.Context.Node;
if (NewNode=nil) then exit;
if (NewNode.Desc in AllUsableSourceTypes)
or (NewNode.Desc=ctnSrcName)
or ((NewNode.Desc=ctnIdentifier) and (NewNode.Parent.Desc=ctnSrcName)
and (NewNode.NextBrother=nil))
then begin
if ExprType.Context.Tool=Self then begin
// unit name of this unit => implementation
// Note: allowed for programs too
NewNode:=Tree.Root;
if NewNode.Desc=ctnUnit then begin
NewNode:=FindImplementationNode;
if NewNode=nil then
NewNode:=FindInterfaceNode;
end;
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveChildren this unit -> ',NewNode.DescAsString]);
{$ENDIF}
ExprType.Context.Node:=NewNode;
end else begin
// unit name of another unit => interface
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveChildren unit -> interface node']);
{$ENDIF}
ExprType.Context.Node:=ExprType.Context.Tool.GetInterfaceNode;
end;
end
else if (ExprType.Context.Node.Desc=ctnUseUnitClearName) then begin
// uses unit name => interface of used unit
ResolveUseUnit;
end
else if (ExprType.Context.Node.Desc=ctnClassOfType) then begin
// 'class of' => jump to the class
ExprType.Context:=ExprType.Context.Tool.FindBaseTypeOfNode(Params,ExprType.Context.Node.FirstChild);
end
else if (ExprType.Desc=xtContext)
and (ExprType.Context.Node.Desc=ctnPointerType)
and (ExprType.Context.Node<>StartNode)
and (cmsAutoderef in Scanner.CompilerModeSwitches) then begin
// Delphi knows . as shortcut for ^.
// -> check for pointer type
// left side of expression has defined a special context
// => this '.' is a dereference
ExprType.Context:=ExprType.Context.Tool.FindBaseTypeOfNode(Params,ExprType.Context.Node.FirstChild);
end;
end;
procedure ResolvePoint;
begin
// for example 'A.B'
if fdfExtractOperand in Params.Flags then
Params.AddOperandPart('.');
if (not (NextAtomType in [vatSpace,vatIdentifier,vatPreDefIdentifier])) then
begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIdentExpected;
end;
ResolveChildren;
if ExprType.Desc in xtAllTypeHelperTypes then begin
// Lazarus supports record helpers for basic types (string) as well (with TYPEHELPERS modeswitch!).
end else if (ExprType.Context.Node=nil) then begin
MoveCursorToCleanPos(CurAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end else if ExprType.Context.Node.Desc in AllPointContexts then begin
// ok, allowed
end else begin
// not allowed
MoveCursorToCleanPos(CurAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
end;
procedure ResolveAs;
begin
// for example 'A as B'
if (not (NextAtomType in [vatSpace,vatIdentifier,vatPreDefIdentifier])) then
begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIdentExpected;
end;
// 'as' is a type cast, so the left side is irrelevant
// -> context is default context
ExprType.Desc:=xtContext;
ExprType.Context.Tool:=Self;
ExprType.Context.Node:=StartNode;
end;
procedure ResolveUp;
begin
// for example:
// 1. 'PInt = ^integer' pointer type
// 2. a^ dereferencing
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveUp']);
{$ENDIF}
if fdfExtractOperand in Params.Flags then
Params.AddOperandPart('^');
if (not (NextAtomType in [vatSpace,vatPoint,vatUp,vatAS,vatEdgedBracketOpen]))
or ((ExprType.Context.Node=nil) and (ExprType.Desc<>xtPointer))
then begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
ResolveBaseTypeOfIdentifier;
if (ExprType.Desc=xtPointer) then begin
// the compiler type 'Pointer'
exit;
end;
if (ExprType.Context.Node<>StartNode) then begin
// left side of expression has defined a special context
// => this '^' is a dereference
if (not
(NextAtomType in [vatSpace,vatPoint,vatAS,vatUP,vatEdgedBracketOpen]))
then begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaisePointNotFound;
end;
if (ExprType.Context.Node=nil)
or (ExprType.Context.Node.Desc<>ctnPointerType) then begin
MoveCursorToCleanPos(CurAtom.StartPos);
RaiseExceptionFmt(20170421200550,ctsIllegalQualifier,['^']);
end;
ExprType.Desc:=xtContext;
ExprType.Context.Node:=ExprType.Context.Node.FirstChild;
end else if NodeHasParentOfType(ExprType.Context.Node,ctnPointerType) then
begin
// this is a pointer type definition
// -> the default context is ok
end;
end;
procedure ResolveEdgedBracketOpen;
{ for example: a[]
this could be:
1. ranged array e.g. array[1..2] of
2. dynamic array e.g. array of integer
3. variant array e.g. array of const
4. indexed pointer e.g. PInteger[1]
5. default property e.g. Items[Index: integer]
6. indexed property e.g. Items[Index: integer]
7. string character e.g. string[3]
}
procedure RaiseTypeIdentNotFound;
begin
ExprType.Context.Tool.RaiseExceptionFmt(20170421200553,ctsStrExpectedButAtomFound,
[ctsTypeIdentifier,ExprType.Context.Tool.GetAtom]);
end;
procedure RaiseIdentInCurContextNotFound;
begin
ExprType.Context.Tool.RaiseExceptionFmt(20170421200557,ctsStrExpectedButAtomFound,
[ctsIdentifier,GetAtom]);
end;
begin
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveEdgedBracketOpen ',ExprTypeToString(ExprType)]);
{$ENDIF}
if fdfExtractOperand in Params.Flags then begin
// simple copying, todo: expand argument
Params.AddOperandPart(ExtractBrackets(CurPos.StartPos,[]));
end;
if (not (NextAtomType in [vatSpace,vatPoint,vatAs,vatUp,vatRoundBracketClose,
vatRoundBracketOpen,vatEdgedBracketClose,vatEdgedBracketOpen]))
then begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
if (ExprType.Desc=xtContext)
and (ExprType.Context.Node.Desc=ctnProperty) then begin
// [] behind a property
// -> Check if this property has parameters
ResolveTypeLessProperty;
if (ExprType.Desc=xtContext)
and (ExprType.Context.Node.Desc=ctnProperty)
and ExprType.Context.Tool.PropertyNodeHasParamList(ExprType.Context.Node)
then begin
// use the property type
ResolveChildren;
exit;
end;
end;
ResolveBaseTypeOfIdentifier;
if ExprType.Desc in xtAllStringTypes then begin
ExprType.Desc:=xtChar;
ExprType.Context.Node:=nil;
exit;
end;
if ExprType.Desc in xtAllWideStringTypes then begin
ExprType.Desc:=xtWideChar;
ExprType.Context.Node:=nil;
exit;
end;
if ExprType.Context.Node=nil then begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
if ExprType.Context.Node.Desc in [ctnRangedArrayType,ctnOpenArrayType] then
begin
MoveCursorToCleanPos(CurAtom.StartPos);
ReadNextAtom; // "["
ReadNextAtom;
repeat
case CurPos.Flag of
cafRoundBracketClose: SaveRaiseBracketCloseExpectedButAtomFound(20170425090717);
cafRoundBracketOpen,
cafEdgedBracketOpen: ReadTilBracketClose(true);
cafComma:
with ExprType, Context do begin
Context:=Tool.FindBaseTypeOfNode(Params,Node.LastChild);
if not (Node.Desc in [ctnRangedArrayType,ctnOpenArrayType]) then
RaiseIllegalQualifierFound;
end;
end;
ReadNextAtom;
until CurPos.Flag=cafEdgedBracketClose;
end;
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ResolveEdgedBracketOpen ExprType=',ExprTypeToString(ExprType)]);
{$ENDIF}
case ExprType.Context.Node.Desc of
ctnOpenArrayType,ctnRangedArrayType:
begin
// the array type is the last child node
//debugln('ResolveEdgedBracketOpen Open/RangedArray LastChild=',ExprType.Context.Node.LastChild.DescAsString);
if ExprType.Context.Node.LastChild.Desc=ctnOfConstType then begin
// 'array of const'; the array type is 'TVarRec'
// => search 'TVarRec'
Params.Save(OldInput);
Params.Flags:=[fdfSearchInParentNodes,fdfIgnoreCurContextNode,
fdfExceptionOnNotFound,fdfFindChildren];
// special identifier for TVarRec
Params.SetIdentifier(Self,'tvarrec',nil);
Params.ContextNode:=ExprType.Context.Node;
ExprType.Context.Tool.FindIdentifierInContext(Params);
ExprType.Context:=CreateFindContext(Params);
Params.Load(OldInput,true);
end else begin
ExprType.Context.Node:=ExprType.Context.Node.LastChild;
end;
end;
ctnPointerType:
// the pointer type is the only child node
ExprType.Context.Node:=ExprType.Context.Node.FirstChild;
ctnClass, ctnClassInterface, ctnDispinterface, ctnObject, ctnRecordType,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass:
begin
// search default property of the class / interface
Params.Save(OldInput);
Params.Flags:=[fdfSearchInAncestors,fdfExceptionOnNotFound,fdfSearchInHelpers]
+fdfGlobals*Params.Flags;
// special identifier '[' for default property
Params.SetIdentifier(Self,@Src[CurAtom.StartPos],nil);
Params.ContextNode:=ExprType.Context.Node;
ExprType.Context.Tool.FindIdentifierInContext(Params);
ExprType.Context:=CreateFindContext(Params);
Params.Load(OldInput,true);
end;
ctnProperty, ctnGlobalProperty:
begin
if not ExprType.Context.Tool.PropertyNodeHasParamList(ExprType.Context.Node) then
RaiseIdentInCurContextNotFound;
end;
ctnIdentifier:
begin
MoveCursorToNodeStart(ExprType.Context.Node);
ReadNextAtom;
if UpAtomIs('STRING') or UpAtomIs('ANSISTRING')
or UpAtomIs('SHORTSTRING') then begin
ExprType.Desc:=xtChar;
ExprType.Context.Node:=nil;
exit;
end else if UpAtomIs('WIDESTRING') or UpAtomIs('UNICODESTRING') then begin
ExprType.Desc:=xtWideChar;
ExprType.Context.Node:=nil;
exit;
end else begin
MoveCursorToCleanPos(CurAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
end;
else
MoveCursorToCleanPos(CurAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
end;
procedure ResolveRoundBracketOpen;
begin
{ for example:
(a+b) expression bracket: the type is the result type of the
expression.
a() typecast or function
}
if not (NextAtomType in [vatSpace,vatPoint,vatAs,vatUp,vatRoundBracketClose,
vatRoundBracketOpen,vatEdgedBracketClose,vatEdgedBracketOpen]) then
begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIllegalQualifierFound;
end;
if PrevAtomType<>vatNone then begin
// typecast or function
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveRoundBracketOpen skip typecast/paramlist="',dbgstr(Src,CurAtom.StartPos,CurAtomBracketEndPos-CurAtom.StartPos),'"']);
{$ENDIF}
if fdfExtractOperand in Params.Flags then begin
if (ExprType.Context.Node<>nil)
and (ExprType.Context.Node.Desc=ctnTypeDefinition) then begin
// typecast
with ExprType.Context do
Params.AddOperandPart(GetIdentifier(@Tool.Src[Node.StartPos]));
Params.AddOperandPart('(');
// assumption: one term in brakets
FindExpressionTypeOfTerm(CurAtom.StartPos+1,CurAtomBracketEndPos-1,
Params,false);
Params.AddOperandPart(')');
end;
end;
end else begin
// expression
{$IFDEF ShowExprEval}
debugln([' FindExpressionTypeOfTerm ResolveRoundBracketOpen subexpr="',dbgstr(Src,CurAtom.StartPos,CurAtomBracketEndPos-CurAtom.StartPos),'"']);
{$ENDIF}
ExprType:=FindExpressionResultType(Params,CurAtom.StartPos+1,
CurAtomBracketEndPos-1, AliasType);
end;
end;
procedure ResolveINHERITED;
// for example: inherited A; inherited;
// inherited skips the class and begins to search in the ancestor class
var
ProcNode: TCodeTreeNode;
ClassNodeOfMethod: TCodeTreeNode;
HasIdentifier: Boolean;
Context: TFindContext;
var
DefProcNode: TCodeTreeNode;
HelperForExpr: TExpressionType;
SearchInHelpersInTheEnd: Boolean;
begin
if ExprType.Desc=xtNone then
Context:=CreateFindContext(Self,StartNode)
else
Context:=ExprType.Context;
if (Context.Node<>StartNode) or (Context.Node=nil) then begin
MoveCursorToCleanPos(CurAtom.StartPos);
RaiseIllegalQualifierFound;
end;
ProcNode:=GetMethodOfBody(Context.Node);
if ProcNode=nil then begin
MoveCursorToCleanPos(CurAtom.StartPos);
RaiseException(20170421200601,ctsInheritedKeywordOnlyAllowedInMethods);
end;
HasIdentifier:=NextAtom.EndPos<=EndPos;
if HasIdentifier then begin
if (not (NextAtomType in [vatIdentifier,vatPreDefIdentifier])) then
begin
MoveCursorToCleanPos(NextAtom.StartPos);
ReadNextAtom;
RaiseIdentExpected;
end;
ReadNextExpressionAtom;
end;
{$IFDEF ShowExprEval}
DebugLn(' FindExpressionTypeOfTerm ResolveINHERITED CurAtomType=',
VariableAtomTypeNames[CurAtomType],
' CurAtom="',copy(Src,CurAtom.StartPos,CurAtom.EndPos-CurAtom.StartPos),'"');
{$ENDIF}
// find class of method
ClassNodeOfMethod:=FindClassOfMethod(ProcNode,true,true);
// find class ancestor
OldInput.Flags:=Params.Flags;
Params.Flags:=[fdfSearchInParentNodes,fdfExceptionOnNotFound]
+fdfGlobals*Params.Flags;
FindAncestorOfClass(ClassNodeOfMethod,Params,true);
Params.Flags:=OldInput.Flags;
ExprType.Desc:=xtContext;
ExprType.Context:=CreateFindContext(Params);
SearchInHelpersInTheEnd := False;
if ClassNodeOfMethod.Desc in [ctnClassHelper,ctnRecordHelper] then
begin
// helpers have different order in "inherited" call.
// -> first search in extended class and then in helper (applies only to inherited call)
if (ExprType.Context.Node<>nil) then//inherited helper found -> use it!
Params.GetHelpers(fdhlkDelphiHelper,true)
.AddFromHelperNode(ExprType.Context.Node, ExprType.Context.Tool, True)
else//inherited helper not found -> delete current
Params.GetHelpers(fdhlkDelphiHelper,true)
.DeleteHelperNode(ClassNodeOfMethod, Self);
HelperForExpr := FindExtendedExprOfHelper(ClassNodeOfMethod);
if HelperForExpr.Desc = xtContext then
begin
ExprType.Context := HelperForExpr.Context;
SearchInHelpersInTheEnd := True;
end;
end;
if (not HasIdentifier) then begin
// the keyword 'inherited' is the last atom
if StartFlags*[fdfFindChildren,fdfFindVariable]=[fdfFindVariable] then begin
// for example: inherited; search the method, not the context
DefProcNode:=FindCorrespondingProcNode(ProcNode);
if DefProcNode=nil then begin
MoveCursorToProcName(ProcNode,true);
RaiseExceptionFmt(20170421200604,ctsMethodSignatureSNotFoundInClass, [GetAtom]);
end;
MoveCursorToProcName(DefProcNode,true);
end else begin
// for example: inherited |
// return the ancestor class context
exit;
end;
end else
MoveCursorToCleanPos(CurAtom.StartPos);
// search identifier only in class ancestor
if SearchInHelpersInTheEnd then
Params.Flags := Params.Flags + [fdfSearchInHelpersInTheEnd];
Params.Save(OldInput);
Params.SetIdentifier(Self,@Src[CurPos.StartPos],@CheckSrcIdentifier);
Params.ContextNode:=ExprType.Context.Node;
Params.Flags:=Params.Flags-[fdfSearchInParentNodes]
+[fdfExceptionOnNotFound,fdfSearchInAncestors];
ExprType.Context.Tool.FindIdentifierInContext(Params);
ExprType.Context:=CreateFindContext(Params);
Params.Load(OldInput,true);
Params.Flags := Params.Flags - [fdfSearchInHelpersInTheEnd];
end;
begin
Result:=CleanExpressionType;
StartFlags:=Params.Flags;
StartNode:=Params.ContextNode;
{$IFDEF ShowExprEval}
DebugLn(['[TFindDeclarationTool.FindExpressionTypeOfTerm] START',
' Flags=[',dbgs(Params.Flags),']',
' StartContext=',StartNode.DescAsString,'=',dbgstr(Src,StartNode.StartPos,15),
' Alias=',AliasType<>nil]
);
{$ENDIF}
{$IFDEF CheckNodeTool}
CheckNodeTool(StartNode);
{$ENDIF}
if not InitAtomQueue then exit;
ExprType:=CleanExpressionType;
repeat
{$IFDEF ShowExprEval}
DebugLn([' FindExpressionTypeOfTerm ATOM CurAtomType=',
VariableAtomTypeNames[CurAtomType],' CurAtom="',GetAtom(CurAtom),'"',
' ExprType=',ExprTypeToString(ExprType)]);
{$ENDIF}
case CurAtomType of
vatIdentifier, vatPreDefIdentifier: ResolveIdentifier;
vatStringConstant,vatNumber: ResolveConstant;
vatPoint: ResolvePoint;
vatAS: ResolveAs;
vatUP: ResolveUp;
vatEdgedBracketOpen: ResolveEdgedBracketOpen;
vatRoundBracketOpen: ResolveRoundBracketOpen;
vatINHERITED: ResolveINHERITED;
end;
ReadNextExpressionAtom;
until CurAtom.EndPos>EndPos;
if fdfFunctionResult in StartFlags then
ResolveChildren;
Result:=ExprType;
if (Result.Desc=xtContext) and (not (fdfFindVariable in StartFlags)) then
Result:=Result.Context.Tool.ConvertNodeToExpressionType(
Result.Context.Node,Params);
{$IFDEF ShowExprEval}
DebugLn(' FindExpressionTypeOfTerm Result=',ExprTypeToString(Result));
{$ENDIF}
end;
function TFindDeclarationTool.FindEndOfExpression(StartPos: integer): integer;
var
First: Integer;
begin
MoveCursorToCleanPos(StartPos);
Result:=CurPos.StartPos;
First:=0;
repeat
ReadNextAtom;
if First=0 then begin
First:=CurPos.StartPos;
if UpAtomIs('INHERITED') then begin
Result:=CurPos.EndPos;
ReadNextAtom;
end;
end;
// read till statement end
if (CurPos.StartPos>SrcLen)
or (CurPos.Flag in [cafSemicolon,cafComma,cafEnd,
cafRoundBracketClose,cafEdgedBracketClose])
or (AtomIsKeyWord
and not IsKeyWordInConstAllowed.DoItCaseInsensitive(Src,
CurPos.StartPos,CurPos.EndPos-CurPos.StartPos))
then begin
break;
end
else if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
ReadTilBracketClose(true);
end;
Result:=CurPos.EndPos;
until false;
end;
function TFindDeclarationTool.ConvertNodeToExpressionType(Node: TCodeTreeNode;
Params: TFindDeclarationParams; AliasType: PFindContext): TExpressionType;
procedure ConvertIdentifierAtCursor(Tool: TFindDeclarationTool);
begin
if WordIsPredefinedIdentifier.DoItCaseInsensitive(Tool.Src,Tool.CurPos.StartPos,
Tool.CurPos.EndPos-Tool.CurPos.StartPos) then
begin
// predefined identifiers
ConvertNodeToExpressionType:=Tool.FindExpressionTypeOfPredefinedIdentifier(
Tool.CurPos.StartPos,Params);
end;
end;
var
BaseContext: TFindContext;
OldInput: TFindDeclarationInput;
Tool: TFindDeclarationTool;
CurAliasType: PFindContext;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
{$IFDEF ShowExprEval}
DebugLn(['[TFindDeclarationTool.ConvertNodeToExpressionType] A',
' Node=',Node.DescAsString,' "',dbgstr(copy(ExtractNode(Node,[]),1,30)),'" Flags=[',dbgs(Params.Flags),'] Alias=',AliasType<>nil]);
{$ENDIF}
BaseContext:=FindBaseTypeOfNode(Params,Node,AliasType);
Node:=BaseContext.Node;
Tool:=BaseContext.Tool;
Result:=CleanExpressionType;
Result.Desc:=xtContext;
Result.Context:=BaseContext;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ConvertNodeToExpressionType] B',
' Expr=',ExprTypeToString(Result),' Alias=',FindContextToString(AliasType));
{$ENDIF}
if (AliasType<>nil) and (AliasType^.Node=nil) then
CurAliasType:=AliasType
else
CurAliasType:=nil;
case Node.Desc of
ctnRangeType:
begin
// range type -> convert to special expression type
// for example: type c = 1..3;
// ToDo: ppu, dcu files
Tool.MoveCursorToNodeStart(Node);
// ToDo: check for cycles
Params.Save(OldInput);
Params.ContextNode:=Node;
Result:=Tool.ReadOperandTypeAtCursor(Params,-1,CurAliasType);
Params.Load(OldInput,true);
Result.Context:=CreateFindContext(Tool,Node);
end;
ctnConstDefinition:
begin
// const -> convert to special expression type
// for example: const a: integer = 3;
// ToDo: ppu, dcu files
Tool.MoveCursorToNodeStart(Node);
Tool.ReadNextAtom;
if not Tool.AtomIsIdentifier then exit;
Tool.ReadNextAtom;
if not (CurPos.Flag in [cafEqual,cafColon]) then exit;
Tool.ReadNextAtom;
// ToDo: check for cycles
Params.Save(OldInput);
Params.ContextNode:=Node;
Result:=Tool.ReadOperandTypeAtCursor(Params,-1,CurAliasType);
Params.Load(OldInput,true);
Result.Context:=CreateFindContext(Tool,Node);
end;
ctnIdentifier:
begin
// ToDo: ppu, dcu files
Tool.MoveCursorToNodeStart(Node);
Tool.ReadNextAtom;
ConvertIdentifierAtCursor(Tool);
end;
ctnProperty,ctnGlobalProperty:
begin
// ToDo: ppu, dcu files
if Tool.MoveCursorToPropType(Node) then
ConvertIdentifierAtCursor(Tool);
end;
ctnConstant:
begin
// for example: const a = 3;
// ToDo: ppu, dcu files
Tool.MoveCursorToNodeStart(Node);
Params.Save(OldInput);
Params.ContextNode:=Node;
Result:=Tool.ReadOperandTypeAtCursor(Params,-1,CurAliasType);
Params.Load(OldInput,true);
Result.Context:=CreateFindContext(Tool,Node);
end;
end;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ConvertNodeToExpressionType] END',
' Expr=',ExprTypeToString(Result),' Alias=',FindContextToString(AliasType));
{$ENDIF}
end;
function TFindDeclarationTool.ReadOperandTypeAtCursor(
Params: TFindDeclarationParams; MaxEndPos: integer; AliasType: PFindContext
): TExpressionType;
{ internally used by FindExpressionResultType
after reading, the cursor will be on the next atom
}
var EndPos, SubStartPos: integer;
procedure ReadEdgedBracketOperand;
procedure RaiseConstExpected;
begin
RaiseExceptionFmt(20170421200607,ctsStrExpectedButAtomFound,[ctsConstant,GetAtom]);
end;
begin
// 'set' constant
SubStartPos:=CurPos.StartPos;
ReadNextAtom;
if not AtomIsChar(']') then begin
Result:=ReadOperandTypeAtCursor(Params);
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ReadOperandTypeAtCursor] Set of ',
ExpressionTypeDescNames[Result.Desc]);
if Result.Desc=xtContext then
DebugLn(' Result.Context.Node=',Result.Context.Node.DescAsString);
{$ENDIF}
end else begin
// empty set '[]'
Result.Desc:=xtNone;
end;
Result.SubDesc:=Result.Desc;
Result.Desc:=xtConstSet;
MoveCursorToCleanPos(SubStartPos);
ReadNextAtom;
ReadTilBracketClose(true);
MoveCursorToCleanPos(CurPos.EndPos);
end;
procedure RaiseIdentExpected;
begin
RaiseExceptionFmt(20170421200609,ctsStrExpectedButAtomFound,[ctsIdentifier,GetAtom]);
end;
var
OldFlags: TFindDeclarationFlags;
begin
Result:=CleanExpressionType;
if AliasType<>nil then
AliasType^:=CleanFindContext;
if CurPos.StartPos=CurPos.EndPos then ReadNextAtom;
// read unary operators which have no effect on the type: +, -, not
while AtomIsChar('+') or AtomIsChar('-') or UpAtomIs('NOT') do
ReadNextAtom;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ReadOperandTypeAtCursor] A Atom=',GetAtom);
debugln(['TFindDeclarationTool.ReadOperandTypeAtCursor StartContext=',Params.ContextNode.DescAsString,'="',dbgstr(Src,Params.ContextNode.StartPos,15),'"']);
{$ENDIF}
if (AtomIsIdentifier)
or (CurPos.Flag=cafRoundBracketOpen)
or UpAtomIs('INHERITED')
or UpAtomIs('ARRAY')
then begin
// read variable
SubStartPos:=CurPos.StartPos;
EndPos:=FindEndOfTerm(SubStartPos,false,true);
if EndPos>MaxEndPos then
EndPos:=MaxEndPos;
OldFlags:=Params.Flags;
Params.Flags:=(Params.Flags*fdfGlobals)+[fdfFunctionResult];
Result:=FindExpressionTypeOfTerm(SubStartPos,EndPos,Params,true,AliasType);
Params.Flags:=OldFlags;
MoveCursorToCleanPos(EndPos);
end
else if UpAtomIs('NIL') then begin
Result.Desc:=xtNil;
ReadNextAtom;
end
else if AtomIsChar('[') then begin
ReadEdgedBracketOperand;
end
else if AtomIsStringConstant then begin
// string or char constant
if AtomIsCharConstant then
Result.Desc:=xtChar
else
Result.Desc:=xtConstString;
MoveCursorToCleanPos(CurPos.StartPos);
ReadAsStringConstant;
end
else if AtomIsNumber then begin
// ordinal or real constant
if AtomIsRealNumber then
Result.Desc:=xtConstReal
else
Result.Desc:=xtConstOrdInteger;
MoveCursorToCleanPos(CurPos.EndPos);
end
else if AtomIsChar('@') then begin
// a simple pointer or a PChar or an event
ReadNextAtom;
if CurPos.Flag=cafWord then begin
SubStartPos:=CurPos.StartPos;
EndPos:=FindEndOfTerm(SubStartPos,false,true);
if EndPos>MaxEndPos then
EndPos:=MaxEndPos;
OldFlags:=Params.Flags;
Params.Flags:=(Params.Flags*fdfGlobals)-[fdfFunctionResult];
Result:=FindExpressionTypeOfTerm(SubStartPos,EndPos,Params,true,AliasType);
Params.Flags:=OldFlags;
MoveCursorToCleanPos(EndPos);
end else begin
MoveCursorToCleanPos(CurPos.StartPos);
Result:=ReadOperandTypeAtCursor(Params);
end;
if (Result.Desc=xtContext)
or ((Result.Context.Node<>nil) and (Result.Context.Node.Desc=ctnProcedure))
then begin
Result.SubDesc:=Result.Desc;
Result.Desc:=xtPointer;
end else if (Result.Desc=xtChar) then begin
Result.SubDesc:=xtNone;
Result.Desc:=xtPChar
end else begin
Result.SubDesc:=xtNone;
Result.Context:=CleanFindContext;
Result.Desc:=xtPointer;
end;
end
else
RaiseIdentExpected;
{$IFDEF ShowExprEval}
DbgOut('[TFindDeclarationTool.ReadOperandTypeAtCursor] END ',
ExpressionTypeDescNames[Result.Desc]);
if Result.Context.Node<>nil then
DbgOut(' Context.Node=',Result.Context.Node.DescAsString)
else
DbgOut(' Context.Node=nil');
if AliasType<>nil then
DbgOut(' Alias=',FindContextToString(AliasType));
DebugLn('');
{$ENDIF}
end;
function TFindDeclarationTool.FindExpressionTypeOfPredefinedIdentifier(
StartPos: integer; Params: TFindDeclarationParams; AliasType: PFindContext
): TExpressionType;
var
IdentPos: PChar;
ParamList: TExprTypeList;
ParamNode: TCodeTreeNode;
SubParams: TFindDeclarationParams;
NewTool: TFindDeclarationTool;
begin
Result:=CleanExpressionType;
IdentPos:=@Src[StartPos];
Result.Desc:=PredefinedIdentToExprTypeDesc(IdentPos,Scanner.PascalCompiler);
{$IFDEF ShowExprEval}
debugln('TFindDeclarationTool.FindExpressionTypeOfPredefinedIdentifier "',GetIdentifier(IdentPos),'" ',
ExpressionTypeDescNames[Result.Desc]);
if Result.desc=xtNone then begin
//CTDumpStack;
//IsWordBuiltInFunc.WriteDebugListing;
end;
{$ENDIF}
ParamList:=nil;
try
case Result.Desc of
xtCompilerFunc:
begin
if not (Params.ContextNode.Desc in (AllPascalStatements+[ctnConstant])) then begin
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.FindExpressionTypeOfPredefinedIdentifier Skipping non expr parent ContextNode=',Params.ContextNode.DescAsString]);
{$ENDIF}
exit;
end;
MoveCursorToCleanPos(StartPos);
ReadNextAtom;
ReadNextAtom;
if not AtomIsChar('(') then
exit;
ParamList:=CreateParamExprListFromStatement(CurPos.StartPos,Params,true);
if (CompareIdentifiers(IdentPos,'PRED')=0)
or (CompareIdentifiers(IdentPos,'SUCC')=0)
or (CompareIdentifiers(IdentPos,'DEFAULT')=0)
then begin
// the DEFAULT, PRED and SUCC of a expression has the same type as the expression
if ParamList.Count<>1 then exit;
Result:=ParamList.Items[0];
if AliasType<>nil then
AliasType^:=ParamList.AliasTypes[0];
//debugln(['TFindDeclarationTool.FindExpressionTypeOfPredefinedIdentifier ',ExprTypeToString(Result)]);
end
else if (CompareIdentifiers(IdentPos,'LOW')=0)
or (CompareIdentifiers(IdentPos,'HIGH')=0) then
begin
{$IFDEF ShowExprEval}
debugln('TFindDeclarationTool.FindExpressionTypeOfPredefinedIdentifier Ident=',GetIdentifier(IdentPos));
{$ENDIF}
{ examples:
Low(ordinal type) is the ordinal type
Low(array) has type of the array items
Low(set) has type of the enums
}
if ParamList.Count<>1 then exit;
Result:=ParamList.Items[0];
if Result.Desc<>xtContext then exit;
ParamNode:=Result.Context.Node;
case ParamNode.Desc of
ctnEnumerationType:
// Low(enum) has the type of the enum
if (ParamNode.Parent<>nil)
and (ParamNode.Parent.Desc=ctnTypeDefinition) then
Result.Context.Node:=ParamNode.Parent;
ctnOpenArrayType:
// array without explicit range -> open array
// Low(Open array) is ordinal integer
begin
Result.Desc:=xtConstOrdInteger;
Result.Context:=CleanFindContext;
end;
ctnRangedArrayType:
begin
// array with explicit range
// Low(array[SubRange]) has the type of the subrange
Result.Context.Tool.MoveCursorToNodeStart(ParamNode.FirstChild);
SubParams:=TFindDeclarationParams.Create(Params);
try
SubParams.Flags:=fdfDefaultForExpressions;
SubParams.ContextNode:=ParamNode;
Result:=Result.Context.Tool.ReadOperandTypeAtCursor(SubParams);
finally
SubParams.Free;
end;
end;
else
DebugLn('NOTE: unimplemented Low(type) type=',ParamNode.DescAsString);
end;
end
else if (CompareIdentifiers(IdentPos,'LENGTH')=0) then
begin
if ParamList.Count<>1 then exit;
Result.Desc:=xtConstOrdInteger;
end
else if (CompareIdentifiers(IdentPos,'COPY')=0) then
begin
if (ParamList.Count<1) or (ParamList.Count>3) or (Scanner.Values.IsDefined('VER1_0')) then
exit;
Result:=ParamList.Items[0]; // Copy sets the result based on the first
// parameter (can be any kind of string or array)
end
else if (CompareIdentifiers(IdentPos,'GET_FRAME')=0) then
begin
if ParamList.Count<>1 then exit;
Result.Desc:=xtPointer;
end
else if (CompareIdentifiers(IdentPos,'OBJCSELECTOR')=0) then
begin
// return type is System.SEL
NewTool:=FindCodeToolForUsedUnit('system','',true);
if NewTool=nil then exit;
SubParams:=TFindDeclarationParams.Create(Params);
try
SubParams.Identifier:='SEL'#0;
if (not NewTool.FindIdentifierInInterface(Self,SubParams))
or (SubParams.NewNode=nil) then exit;
Result.Desc:=xtContext;
Result.Context.Node:=SubParams.NewNode;
Result.Context.Tool:=SubParams.NewCodeTool;
finally
SubParams.Free;
end;
end;
end;
xtString:
Result.Desc:=GetDefaultStringType;
end;
finally
ParamList.Free;
end;
end;
function TFindDeclarationTool.FindExpressionTypeOfConstSet(Node: TCodeTreeNode
): TExpressionType;
var
AliasType: TFindContext;
Params: TFindDeclarationParams;
begin
Result:=CleanExpressionType;
if Node=nil then
RaiseException(20170421212058,'TFindDeclarationTool.FindExpressionTypeOfConstSet Node=nil');
{$IFDEF CheckNodeTool}
CheckNodeTool(Node);
{$ENDIF}
MoveCursorToNodeStart(Node);
ReadNextAtom;
if CurPos.Flag<>cafEdgedBracketOpen then
RaiseStringExpectedButAtomFound(20170421212227,'[');
ReadNextAtom;
Params:=TFindDeclarationParams.Create(Self,Node);
try
Params.Flags:=fdfDefaultForExpressions+[fdfFunctionResult];
AliasType:=CleanFindContext;
Result:=FindExpressionTypeOfTerm(CurPos.StartPos,-1,Params,false,@AliasType);
//debugln(['TFindDeclarationTool.FindExpressionTypeOfConstSet ',ExprTypeToString(Result)]);
finally
Params.Free;
end;
end;
function TFindDeclarationTool.GetDefaultStringType: TExpressionTypeDesc;
begin
if cmsDefault_unicodestring in Scanner.CompilerModeSwitches then
Result:=xtUnicodeString
else if (Scanner.PascalCompiler=pcDelphi)
or ((Scanner.CompilerMode=cmDELPHI)
or (Scanner.Values['LONGSTRINGS']='1')) then
Result:=xtAnsiString
else
Result:=xtString;
end;
function TFindDeclarationTool.CalculateBinaryOperator(LeftOperand,
RightOperand: TOperand; BinaryOperator: TAtomPosition;
Params: TFindDeclarationParams): TOperand;
begin
Result.Expr:=CleanExpressionType;
Result.AliasType:=CleanFindContext;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.CalculateBinaryOperator] A',
' LeftOperand=',ExprTypeToString(LeftOperand.Expr),
' Operator=',GetAtom(BinaryOperator),
' RightOperand=',ExprTypeToString(RightOperand.Expr)
);
{$ENDIF}
// convert Left and RightOperand contexts to expressiontype
if LeftOperand.Expr.Desc=xtContext then begin
LeftOperand.Expr:=LeftOperand.Expr.Context.Tool.ConvertNodeToExpressionType(
LeftOperand.Expr.Context.Node,Params);
end;
if RightOperand.Expr.Desc=xtContext then begin
RightOperand.Expr:=RightOperand.Expr.Context.Tool.ConvertNodeToExpressionType(
RightOperand.Expr.Context.Node,Params);
end;
// ToDo: search for an overloaded operator
if WordIsBooleanOperator.DoItCaseInsensitive(Src,BinaryOperator.StartPos,
BinaryOperator.EndPos-BinaryOperator.StartPos)
then begin
// Boolean operators
// < > <= >= <> in is
Result.Expr.Desc:=xtBoolean;
end
else if (BinaryOperator.EndPos-BinaryOperator.StartPos=1)
and (Src[BinaryOperator.StartPos]='/') then begin
// real division /
Result:=RealTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos);
if not(Result.Expr.Desc in xtAllRealTypes) then
begin
Result.Expr.Desc:=xtConstReal;
Result.AliasType:=CleanFindContext;
end;
end
else if WordIsOrdNumberOperator.DoItCaseInsensitive(Src,BinaryOperator.StartPos,
BinaryOperator.EndPos-BinaryOperator.StartPos)
then begin
// ordinal number operator
// or xor and mod div shl shr
if LeftOperand.Expr.Desc in xtAllBooleanTypes then
Result:=BooleanTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos)
else
Result:=IntegerTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos);
end
else if WordIsNumberOperator.DoItCaseInsensitive(Src,BinaryOperator.StartPos,
BinaryOperator.EndPos-BinaryOperator.StartPos)
then begin
// number operator (or string concatenating or set cut)
// + - *
if (Src[BinaryOperator.StartPos]='+')
and (LeftOperand.Expr.Desc in xtAllStringCompatibleTypes)
then begin
// string/char '+'
if (RightOperand.Expr.Desc in xtAllStringCompatibleTypes)
then
begin
Result:=StringTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos);
if not(Result.Expr.Desc in xtAllStringTypes) then
begin
Result.Expr.Desc:=xtConstString;
Result.AliasType:=CleanFindContext;
end;
end else begin
MoveCursorToCleanPos(BinaryOperator.EndPos);
ReadNextAtom;
RaiseExceptionFmt(20170421200612,ctsIncompatibleTypesGotExpected,
['char',ExpressionTypeDescNames[RightOperand.Expr.Desc]]);
end;
end else if (Src[BinaryOperator.StartPos] in ['+','-','*'])
and (LeftOperand.Expr.Desc=xtContext)
and (LeftOperand.Expr.Context.Node<>nil)
and (LeftOperand.Expr.Context.Node.Desc=ctnSetType)
then begin
Result:=LeftOperand;
end else begin
if (LeftOperand.Expr.Desc in xtAllRealTypes)
or (RightOperand.Expr.Desc in xtAllRealTypes) then
Result:=RealTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos)
else if (LeftOperand.Expr.Desc=xtPointer)
or (RightOperand.Expr.Desc=xtPointer)
or ((LeftOperand.Expr.Desc=xtContext)
and (LeftOperand.Expr.Context.Node.Desc=ctnPointerType))
or ((RightOperand.Expr.Desc=xtContext)
and (RightOperand.Expr.Context.Node.Desc=ctnPointerType))
then
Result.Expr.Desc:=xtPointer
else
Result:=IntegerTypesOrderList.Compare(LeftOperand, RightOperand, Self, BinaryOperator.EndPos);
end;
end else begin
// ???
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.CalculateBinaryOperator unknown operator: ',GetAtom(BinaryOperator)]);
{$ENDIF}
Result:=RightOperand;
end;
end;
function TFindDeclarationTool.IsParamExprListCompatibleToNodeList(
FirstTargetParameterNode: TCodeTreeNode;
SourceExprParamList: TExprTypeList; IgnoreMissingParameters: boolean;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList): TTypeCompatibility;
// tests if SourceExprParamList fits into the TargetFirstParameterNode
var
ParamNode: TCodeTreeNode;
i, MinParamCnt, MaxParamCnt: integer;
ParamCompatibility: TTypeCompatibility;
CompatibilityListCount: LongInt;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(FirstTargetParameterNode);{$ENDIF}
// quick check: parameter count
ParamNode:=FirstTargetParameterNode;
MinParamCnt:=0;
while (ParamNode<>nil)
and ((ParamNode.SubDesc and ctnsHasDefaultValue)=0) do begin
ParamNode:=ParamNode.NextBrother;
inc(MinParamCnt);
end;
MaxParamCnt:=MinParamCnt;
while (ParamNode<>nil) do begin
ParamNode:=ParamNode.NextBrother;
inc(MaxParamCnt);
end;
{$IF defined(ShowExprEval) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.IsParamExprListCompatibleToNodeList] ',
' ExprParamList.Count=',dbgs(SourceExprParamList.Count),
' MinParamCnt=',dbgs(MinParamCnt),' MaxParamCnt=',dbgs(MaxParamCnt)
);
try
{$ENDIF}
Result:=tcExact;
if (SourceExprParamlist.Count>MaxParamCnt)
or ((not IgnoreMissingParameters) and (SourceExprParamList.Count<MinParamCnt))
then begin
Result:=tcIncompatible;
exit;
end;
// check each parameter for compatibility
ParamNode:=FirstTargetParameterNode;
i:=0;
CompatibilityListCount:=SourceExprParamList.Count;
while (ParamNode<>nil) and (i<CompatibilityListCount) do begin
ParamCompatibility:=IsCompatible(ParamNode,SourceExprParamList.Items[i],
Params);
{$IF defined(ShowExprEval) or defined(ShowProcSearch)}
DebugLn(['[TFindDeclarationTool.IsParamExprListCompatibleToNodeList] SourceParam=',ExprTypeToString(SourceExprParamList.Items[i]),' TargetParam=',ExtractNode(ParamNode,[]),' ',TypeCompatibilityNames[ParamCompatibility]]);
{$ENDIF}
if CompatibilityList<>nil then
CompatibilityList[i]:=ParamCompatibility;
if (ParamCompatibility=tcIncompatible)
or ((ParamCompatibility=tcCompatible)
and MoveCursorToParameterSpecifier(ParamNode)
and (UpAtomIs('VAR') or UpAtomIs('CONSTREF')
or (UpAtomIs('OUT') and (cmsOut in Scanner.CompilerModeSwitches))))
then begin
Result:=tcIncompatible;
exit;
end;
if ParamCompatibility=tcCompatible then
Result:=tcCompatible;
ParamNode:=ParamNode.NextBrother;
inc(i);
end;
if (i<SourceExprParamList.Count) then begin
// there are more expressions, then the param list has variables
Result:=tcIncompatible;
end else if (ParamNode<>nil) then begin
// there are not enough expressions for the param list
// -> check if missing variables have default variables
if (ParamNode.SubDesc and ctnsHasDefaultValue)>0 then begin
// the rest params have default values
if CompatibilityList<>nil then begin
while (ParamNode<>nil) and (i<CompatibilityListCount) do begin
CompatibilityList[i]:=tcExact;
ParamNode:=ParamNode.NextBrother;
inc(i);
end;
end;
end else if not IgnoreMissingParameters then begin
// not enough expression for param list
// -> incompatible
Result:=tcIncompatible;
end;
end;
{$IF defined(ShowExprEval) or defined(ShowProcSearch)}
finally
DebugLn('[TFindDeclarationTool.IsParamExprListCompatibleToNodeList] END ',
' Result=',TypeCompatibilityNames[Result],' ! ONLY VALID if no error !'
);
end;
{$ENDIF}
end;
function TFindDeclarationTool.IsParamNodeListCompatibleToExprList(
TargetExprParamList: TExprTypeList; FirstSourceParameterNode: TCodeTreeNode;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList): TTypeCompatibility;
// tests if FirstSourceParameterNode fits (i.e. can be assigned) into
// the TargetExprParamList
var
ParamNode: TCodeTreeNode;
i, MinParamCnt, MaxParamCnt: integer;
ParamCompatibility: TTypeCompatibility;
SourceExprType: TExpressionType;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(FirstSourceParameterNode);{$ENDIF}
// quick check: parameter count
MinParamCnt:=0;
ParamNode:=FirstSourceParameterNode;
while (ParamNode<>nil) do begin
ParamNode:=ParamNode.NextBrother;
inc(MinParamCnt);
end;
MaxParamCnt:=MinParamCnt;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsParamNodeListCompatibleToExprList] ',
' ExprParamList.Count=',dbgs(TargetExprParamList.Count),' ',
' MinParamCnt=',dbgs(MinParamCnt),' MaxParamCnt=',dbgs(MaxParamCnt)
);
try
{$ENDIF}
Result:=tcExact;
if (TargetExprParamList.Count<>MaxParamCnt) then begin
Result:=tcIncompatible;
exit;
end;
// check each parameter for compatibility
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsParamNodeListCompatibleToExprList] ',
' ExprParamList=[',TargetExprParamList.AsString,']');
{$ENDIF}
ParamNode:=FirstSourceParameterNode;
i:=0;
while (ParamNode<>nil) and (i<TargetExprParamList.Count) do begin
SourceExprType:=ConvertNodeToExpressionType(ParamNode,Params);
ParamCompatibility:=IsCompatible(TargetExprParamList.Items[i],
SourceExprType,Params);
{$IFDEF ShowExprEval}
DebugLn(['[TFindDeclarationTool.IsParamNodeListCompatibleToExprList] B ',i,' Source=[',ExprTypeToString(SourceExprType),'] Target=[',ExprTypeToString(TargetExprParamList.Items[i]),'] Result=',TypeCompatibilityNames[ParamCompatibility]]);
{$ENDIF}
if CompatibilityList<>nil then
CompatibilityList[i]:=ParamCompatibility;
if ParamCompatibility=tcIncompatible then begin
Result:=tcIncompatible;
exit;
end else if ParamCompatibility=tcCompatible then begin
Result:=tcCompatible;
end;
ParamNode:=ParamNode.NextBrother;
inc(i);
end;
if (ParamNode<>nil) or (i<TargetExprParamList.Count) then
RaiseException(20170421200618,'Internal Error: one param list has changed');
{$IFDEF ShowExprEval}
finally
DebugLn('[TFindDeclarationTool.IsParamNodeListCompatibleToExprList] END ',
' Result=',TypeCompatibilityNames[Result],' ! ONLY VALID if no error !'
);
end;
{$ENDIF}
end;
function TFindDeclarationTool.IsParamNodeListCompatibleToParamNodeList(
FirstTargetParameterNode, FirstSourceParameterNode: TCodeTreeNode;
Params: TFindDeclarationParams;
CompatibilityList: TTypeCompatibilityList): TTypeCompatibility;
var
CurParamNode1, CurParamNode2: TCodeTreeNode;
ParamCompatibility: TTypeCompatibility;
SourceExprType, TargetExprType: TExpressionType;
OldFlags: TFindDeclarationFlags;
i: integer;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(FirstTargetParameterNode);{$ENDIF}
// quick check: parameter count
CurParamNode1:=FirstTargetParameterNode;
CurParamNode2:=FirstSourceParameterNode;
while (CurParamNode1<>nil) and (CurParamNode2<>nil) do begin
CurParamNode1:=CurParamNode1.NextBrother;
CurParamNode2:=CurParamNode2.NextBrother;
end;
if (CurParamNode1<>nil) or (CurParamNode2<>nil) then begin
Result:=tcIncompatible;
exit;
end;
// check each parameter
OldFlags:=Params.Flags;
Params.Flags:=Params.Flags-[fdfFindVariable]+[fdfIgnoreOverloadedProcs];
CurParamNode1:=FirstTargetParameterNode;
CurParamNode2:=FirstSourceParameterNode;
Result:=tcExact;
i:=0;
while (CurParamNode1<>nil) and (CurParamNode2<>nil) do begin
TargetExprType:=ConvertNodeToExpressionType(CurParamNode1,Params);
SourceExprType:=ConvertNodeToExpressionType(CurParamNode2,Params);
ParamCompatibility:=IsBaseCompatible(TargetExprType,SourceExprType,Params);
if CompatibilityList<>nil then
CompatibilityList[i]:=ParamCompatibility;
if ParamCompatibility=tcIncompatible then begin
Result:=tcIncompatible;
exit;
end else if ParamCompatibility=tcCompatible then begin
Result:=tcCompatible;
end;
CurParamNode1:=CurParamNode1.NextBrother;
CurParamNode2:=CurParamNode2.NextBrother;
inc(i);
end;
Params.Flags:=OldFlags;
end;
function TFindDeclarationTool.GetParameterNode(Node: TCodeTreeNode
): TCodeTreeNode;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=Node;
if Result=nil then exit;
if Result.Desc=ctnReferenceTo then begin
Result:=Result.FirstChild;
if Result=nil then exit;
end;
if (Result.Desc in [ctnProperty,ctnGlobalProperty]) then
Result:=Result.FirstChild
else if Result.Desc in [ctnProcedure,ctnProcedureHead,ctnProcedureType] then begin
BuildSubTreeForProcHead(Result);
if Result.Desc in [ctnProcedure,ctnProcedureType] then
Result:=Result.FirstChild;
if Result.Desc=ctnProcedureHead then
Result:=Result.FirstChild;
end;
end;
function TFindDeclarationTool.GetFirstParameterNode(Node: TCodeTreeNode
): TCodeTreeNode;
begin
Result:=GetParameterNode(Node);
if Result<>nil then Result:=Result.FirstChild;
end;
function TFindDeclarationTool.CheckSrcIdentifier(
Params: TFindDeclarationParams;
const FoundContext: TFindContext): TIdentifierFoundResult;
// this is a TOnIdentifierFound function
// if identifier found is a proc then it searches for the best overloaded proc
function CallHasEmptyParamsAndFoundProcFits: boolean;
var
FirstParameterNode: TCodeTreeNode;
TargetTool: TFindDeclarationTool;
begin
Result:=false;
FirstParameterNode:=FoundContext.Tool.GetFirstParameterNode(
FoundContext.Node);
if (FirstParameterNode<>nil)
and ((FirstParameterNode.SubDesc and ctnsHasDefaultValue)=0) then begin
// found proc needs at least one parameter
exit;
end;
// FoundContext is a proc with no or only default params
TargetTool:=Params.IdentifierTool;
TargetTool.MoveCursorToCleanPos(Params.Identifier);
TargetTool.ReadNextAtom; // read identifier
TargetTool.ReadNextAtom; // read bracket
if TargetTool.CurPos.Flag<>cafRoundBracketOpen then exit;
TargetTool.ReadNextAtom; // read bracket close
if TargetTool.CurPos.Flag<>cafRoundBracketClose then exit;
Result:=true;
end;
var
FirstParameterNode, StartContextNode: TCodeTreeNode;
ParamCompatibility: TTypeCompatibility;
OldInput: TFindDeclarationInput;
CurCompatibilityList: TTypeCompatibilityList;
CompListSize: integer;
NewExprInputList: TExprTypeList;
begin
// the search has found an identifier with the right name
{$IFDEF ShowFoundIdentifier}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' FoundContext=',FoundContext.Node.DescAsString,
' Flags=[',dbgs(Params.Flags),']'
);
{$ENDIF}
if FoundContext.Node.Desc=ctnProcedure then begin
// the found node is a proc
// 1. the current identifier cache is blind for parameter lists
// => proc identifiers can not be identified by the name alone
// -> do not cache
// 2. Even if there is only one proc. With different search flags,
// different routes will be searched and then there can be another proc.
// The only solution is to store the param expression list and all flags
// in the cache. This is a ToDo
Include(Params.Flags,fdfDoNotCache);
Include(Params.NewFlags,fodDoNotCache);
if (fdfIgnoreOverloadedProcs in Params.Flags) then begin
// do not check for overloaded procs -> ident found
Result:=ifrSuccess;
exit;
end;
// Procs can be overloaded, that means there can be several procs with the
// same name, but with different param lists.
// The search must go on, and the most compatible proc is returned.
if not Params.IdentifierTool.IsPCharInSrc(Params.Identifier) then begin
// Params.Identifier is not in the source of the start tool
// => impossible to check param list, because the context is unknown
// -> identifier found
{$IFDEF ShowProcSearch}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' NO SOURCE to check params'
);
{$ENDIF}
Result:=ifrSuccess;
exit;
end;
if FoundContext.Tool.NodeIsClassConstructorOrDestructor(FoundContext.Node) then
begin
Result:=ifrProceedSearch;
Exit;
end;
if (not (fdfCollect in Params.Flags))
and CallHasEmptyParamsAndFoundProcFits then begin
// call has brackets without params (e.g. writeln() )
// and found proc fits exactly
// => stop search
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
debugln(['TFindDeclarationTool.CheckSrcIdentifier call is () and found proc fits exactly',
' Ident=',GetIdentifier(Params.Identifier),
' ',FoundContext.Tool.CleanPosToStr(FoundContext.Node.StartPos)
]);
{$ENDIF}
Params.SetResult(FoundContext);
Result:=ifrSuccess;
exit;
end;
Result:=ifrProceedSearch;
if (Params.FoundProc=nil) then begin
// this is the first proc found
// -> save it and proceed the search to find all overloadeded procs
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' ',FoundContext.Tool.CleanPosToStr(FoundContext.Node.StartPos),
' FIRST PROC searching for overloads ...'
);
{$ENDIF}
Params.SetFoundProc(FoundContext);
exit;
end;
// -> check which one is more compatible
// create the input expression list
// (the expressions in the brackets are parsed and converted to types)
if Params.FoundProc^.ExprInputList=nil then begin
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Creating Input Expression List ...'
);
{$ENDIF}
if Params.IdentifierTool.IsPCharInSrc(Params.Identifier) then begin
Params.IdentifierTool.MoveCursorToCleanPos(Params.Identifier);
StartContextNode:=Params.IdentifierTool.FindDeepestNodeAtPos(
Params.IdentifierTool.CurPos.StartPos,true);
if (StartContextNode<>nil) then begin
if (StartContextNode.Desc in AllPascalStatements) then begin
{$IFDEF ShowProcSearch}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Creating Input Expression List for statement ...'
);
{$ENDIF}
Params.Save(OldInput);
Params.IdentifierTool.MoveCursorToCleanPos(Params.Identifier);
Params.Flags:=fdfDefaultForExpressions+Params.Flags*fdfGlobals;
Params.ContextNode:=StartContextNode;
Params.OnIdentifierFound:=@Params.IdentifierTool.CheckSrcIdentifier;
Params.IdentifierTool.ReadNextAtom;
NewExprInputList:=
Params.IdentifierTool.CreateParamExprListFromStatement(
Params.IdentifierTool.CurPos.EndPos,Params);
{$IFDEF ShowProcSearch}
debugln(['TFindDeclarationTool.CheckSrcIdentifier Params: ',NewExprInputList.AsString]);
{$ENDIF}
Params.Load(OldInput,true);
FreeAndNil(Params.FoundProc^.ExprInputList);
Params.FoundProc^.ExprInputList:=NewExprInputList;
end
else if (StartContextNode.Desc in [ctnProcedureHead,ctnProcedure])
then begin
{$IFDEF ShowProcSearch}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Creating Input Expression List for proc node ...'
);
{$ENDIF}
NewExprInputList:=
Params.IdentifierTool.CreateParamExprListFromProcNode(
StartContextNode,Params);
{$IFDEF ShowProcSearch}
debugln(['TFindDeclarationTool.CheckSrcIdentifier Params: ',NewExprInputList.AsString]);
{$ENDIF}
FreeAndNil(Params.FoundProc^.ExprInputList);
Params.FoundProc^.ExprInputList:=NewExprInputList;
end;
end;
end;
if Params.FoundProc^.ExprInputList=nil then begin
// create expression list without params
Params.FoundProc^.ExprInputList:=TExprTypeList.Create;
end;
end;
// create compatibility lists for params
// (each parameter is checked for compatibility)
CompListSize:=SizeOf(TTypeCompatibility)
*Params.FoundProc^.ExprInputList.Count;
if (CompListSize>0)
and (Params.FoundProc^.ParamCompatibilityList=nil) then begin
GetMem(Params.FoundProc^.ParamCompatibilityList,CompListSize);
//DebugLn(['TFindDeclarationTool.CheckSrcIdentifier FoundProc=',dbgs(Params.FoundProc),' New ParamCompatibilityList=',dbgs(Params.FoundProc^.ParamCompatibilityList),' CompListSize=',CompListSize]);
end else begin
//DebugLn(['TFindDeclarationTool.CheckSrcIdentifier FoundProc=',dbgs(Params.FoundProc),' Old ParamCompatibilityList=',dbgs(Params.FoundProc^.ParamCompatibilityList),' CompListSize=',CompListSize]);
end;
// check the first found proc for compatibility
// (compare the expression list with the proc param list)
if not Params.FoundProc^.CacheValid then begin
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Check the first found proc for compatibility ...'
);
{$ENDIF}
FirstParameterNode:=Params.FoundProc^.Context.Tool.GetFirstParameterNode(
Params.FoundProc^.Context.Node);
ParamCompatibility:=
Params.FoundProc^.Context.Tool.IsParamExprListCompatibleToNodeList(
FirstParameterNode,
Params.FoundProc^.ExprInputList,
fdfIgnoreMissingParams in Params.Flags,
Params,Params.FoundProc^.ParamCompatibilityList);
Params.FoundProc^.ProcCompatibility:=ParamCompatibility;
Params.FoundProc^.CacheValid:=true;
if ParamCompatibility=tcExact then begin
Params.SetResult(Params.FoundProc^.Context.Tool,
Params.FoundProc^.Context.Node.FirstChild);
end;
end;
if Params.FoundProc^.ProcCompatibility=tcExact then begin
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' First Proc ParamCompatibility=',TypeCompatibilityNames[Params.FoundProc^.ProcCompatibility]
);
{$ENDIF}
// the first proc fits exactly -> stop the search
Result:=ifrSuccess;
exit;
end;
// check the current proc for compatibility
// (compare the expression list with the proc param list)
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Check the current found proc for compatibility ...'
);
{$ENDIF}
if CompListSize>0 then begin
GetMem(CurCompatibilityList,CompListSize);
//DebugLn(['TFindDeclarationTool.CheckSrcIdentifier create temp CurCompatibilityList=',dbgs(CurCompatibilityList),' CompListSize=',CompListSize]);
end else begin
CurCompatibilityList:=nil;
end;
try
FirstParameterNode:=
FoundContext.Tool.GetFirstParameterNode(FoundContext.Node);
ParamCompatibility:=
FoundContext.Tool.IsParamExprListCompatibleToNodeList(
FirstParameterNode,
Params.FoundProc^.ExprInputList,
fdfIgnoreMissingParams in Params.Flags,
Params,CurCompatibilityList);
{$IF defined(ShowFoundIdentifier) or defined(ShowProcSearch)}
DebugLn('[TFindDeclarationTool.CheckSrcIdentifier]',
' Ident=',GetIdentifier(Params.Identifier),
' Current Proc ParamCompatibility=',TypeCompatibilityNames[ParamCompatibility]
);
{$ENDIF}
if ParamCompatibility=tcExact then begin
// the current proc fits exactly -> stop the search
Params.ChangeFoundProc(FoundContext,ParamCompatibility,
CurCompatibilityList);
CurCompatibilityList:=nil; // set to nil, so that it will not be freed
Params.SetResult(FoundContext.Tool,FoundContext.Node.FirstChild);
Result:=ifrSuccess;
end else if ParamCompatibility=tcCompatible then begin
// the proc fits not exactly, but is compatible
if (Params.FoundProc^.ProcCompatibility=tcInCompatible)
or CompatibilityList1IsBetter(CurCompatibilityList,
Params.FoundProc^.ParamCompatibilityList,
Params.FoundProc^.ExprInputList.Count) then
begin
// the new proc fits better
Params.ChangeFoundProc(FoundContext,ParamCompatibility,CurCompatibilityList);
CurCompatibilityList:=nil; // set to nil, so that it will not be freed
end;
end;
finally
// end overloaded proc search
if CurCompatibilityList<>nil then begin
//DebugLn(['TFindDeclarationTool.CheckSrcIdentifier free CurCompatibilityList=',dbgs(CurCompatibilityList)]);
FreeMem(CurCompatibilityList);
end;
end;
end else
if (FoundContext.Node.Desc=ctnVarDefinition) then begin
if not (fdfIgnoreClassVisibility in Params.Flags)
and (FoundContext.Tool<>Params.IdentifierTool)
and (GetClassVisibility(FoundContext.Node)=ctnClassPrivate) then
Result:=ifrProceedSearch
else
Result:=ifrSuccess;
end else begin
Result:=ifrSuccess;
end;
end;
function TFindDeclarationTool.DoOnIdentifierFound(
Params: TFindDeclarationParams;
FoundNode: TCodeTreeNode): TIdentifierFoundResult;
// this internal function is called, whenever an identifier is found
var IsTopLvlIdent: boolean;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(FoundNode);{$ENDIF}
IsTopLvlIdent:=(fdfTopLvlResolving in Params.Flags);
if Assigned(Params.OnIdentifierFound) then
Result:=Params.OnIdentifierFound(Params,CreateFindContext(Self,FoundNode))
else
Result:=ifrSuccess;
if (Result=ifrSuccess) and IsTopLvlIdent
and Assigned(Params.OnTopLvlIdentifierFound) then
Params.OnTopLvlIdentifierFound(Params,CreateFindContext(Self,FoundNode));
end;
function TFindDeclarationTool.IsCompatible(TargetNode: TCodeTreeNode;
const ExpressionType: TExpressionType;
Params: TFindDeclarationParams): TTypeCompatibility;
var TargetContext: TFindContext;
OldInput: TFindDeclarationInput;
NodeExprType: TExpressionType;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(TargetNode);{$ENDIF}
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsCompatible] A Node=',TargetNode.DescAsString,
' ExpressionType=',ExpressionTypeDescNames[ExpressionType.Desc]);
{$ENDIF}
Result:=tcIncompatible;
// find base type of node
OldInput.Flags:=Params.Flags;
Include(Params.Flags,fdfExceptionOnNotFound);
TargetContext:=FindBaseTypeOfNode(Params,TargetNode);
Params.Flags:=OldInput.Flags;
// compare node base type and ExpressionType
if (ExpressionType.Context.Node<>nil)
and (ExpressionType.Context.Node=TargetContext.Node) then begin
// same base type
Result:=tcExact;
end
else if (TargetContext.Node.Desc=ctnGenericParameter)
or ((ExpressionType.Desc=xtContext)
and (ExpressionType.Context.Node.Desc=ctnGenericParameter))
then begin
// generic type is always preferred
Result:=tcExact;
end
else if (TargetContext.Node.Desc=ctnSetType) then begin
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsCompatible] TargetContext.Node.Desc=ctnSetType',
' "',copy(TargetContext.Tool.Src,TargetContext.Node.Parent.StartPos,20),'"');
{$ENDIF}
if (ExpressionType.Desc=xtConstSet) then begin
// both are sets, compare type of sets
if (ExpressionType.SubDesc<>xtNone) then begin
// ToDo: check if enums of expression fits into enums of target
// ToDo: ppu, dcu
Result:=tcCompatible;
end else
// the empty set is compatible to all kinds of sets
Result:=tcExact;
end else begin
end;
end else begin
NodeExprType:=CleanExpressionType;
NodeExprType.Desc:=xtContext;
NodeExprType.Context:=CreateFindContext(Self,TargetNode);
Result:=IsCompatible(NodeExprType,ExpressionType,Params);
end;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsCompatible] END',
' BaseNode=',TargetContext.Node.DescAsString,
' ExpressionType=',ExpressionTypeDescNames[ExpressionType.Desc],
' Result=',TypeCompatibilityNames[Result]
);
{$ENDIF}
end;
function TFindDeclarationTool.IsCompatible(TargetType,
ExpressionType: TExpressionType; Params: TFindDeclarationParams
): TTypeCompatibility;
begin
if TargetType.Desc=xtContext then begin
if TargetType.Context.Node.Desc=ctnGenericParameter then
exit(tcExact);
TargetType:=TargetType.Context.Tool.ConvertNodeToExpressionType(
TargetType.Context.Node,Params);
end;
if ExpressionType.Desc=xtContext then begin
if ExpressionType.Context.Node.Desc=ctnGenericParameter then
exit(tcExact);
ExpressionType:=ExpressionType.Context.Tool.ConvertNodeToExpressionType(
ExpressionType.Context.Node,Params);
end;
Result:=IsBaseCompatible(TargetType,ExpressionType,Params);
end;
function TFindDeclarationTool.GetCurrentAtomType: TVariableAtomType;
var
Node: TCodeTreeNode;
c: Char;
begin
//debugln(['TFindDeclarationTool.GetCurrentAtomType ',CurPos.StartPos,' ',CurPos.EndPos,' ',SrcLen,' ',GetAtom]);
if (CurPos.StartPos=CurPos.EndPos) then
exit(vatSpace)
else if (CurPos.StartPos<1) or (CurPos.StartPos>SrcLen) then
exit(vatNone);
c:=Src[CurPos.StartPos];
if IsIdentStartChar[c] then begin
if WordIsPredefinedIdentifier.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
exit(vatPreDefIdentifier)
else if UpAtomIs('INHERITED') then
exit(vatINHERITED)
else if UpAtomIs('AS') then
exit(vatAS)
else if WordIsKeyWord.DoItCaseInsensitive(Src,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
exit(vatKeyWord)
else if UpAtomIs('PROPERTY') then begin
Node:=FindDeepestNodeAtPos(CurPos.StartPos,false);
if (Node<>nil) and (Node.Desc in [ctnProperty,ctnPropertySection]) then
exit(vatKeyword)
else
exit(vatIdentifier);
end else
exit(vatIdentifier);
end else if (CurPos.StartPos=CurPos.EndPos-1) then begin
case c of
'.': exit(vatPoint);
'^': exit(vatUp);
'(': exit(vatRoundBracketOpen);
')': exit(vatRoundBracketClose);
'[': exit(vatEdgedBracketOpen);
']': exit(vatEdgedBracketClose);
'@': exit(vatAddrOp);
else exit(vatNone);
end;
end
else begin
case c of
'''','#': exit(vatStringConstant);
'&':
begin
if (CurPos.StartPos+1=CurPos.EndPos) then exit(vatNone);
c:=Src[CurPos.StartPos+1];
if IsIdentStartChar[c] then begin
// &keyword
exit(vatIdentifier);
end else if IsNumberChar[c] then
exit(vatNumber) // octal
else exit(vatNone);
end;
else exit(vatNone);
end;
end;
end;
function TFindDeclarationTool.CreateParamExprListFromStatement(
StartPos: integer; Params: TFindDeclarationParams; GetAlias: boolean
): TExprTypeList;
var ExprType: TExpressionType;
BracketClose: char;
ExprStartPos, ExprEndPos: integer;
CurIgnoreErrorAfterPos: Integer;
OldFlags: TFindDeclarationFlags;
ok: Boolean;
AliasType: TFindContext;
procedure RaiseBracketNotFound;
begin
RaiseExceptionFmt(20170421200621,ctsStrExpectedButAtomFound,[BracketClose,GetAtom]);
end;
begin
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.CreateParamExprListFromStatement] ',
'"',copy(Src,StartPos,40),'" Context=',Params.ContextNode.DescAsString);
{$ENDIF}
Result:=TExprTypeList.Create;
ok:=false;
try
MoveCursorToCleanPos(StartPos);
ReadNextAtom; // reads first atom after proc name
if AtomIsChar('(') then
BracketClose:=')'
else if AtomIsChar('[') then
BracketClose:=']'
else
BracketClose:=#0;
if IgnoreErrorAfterValid then
CurIgnoreErrorAfterPos:=IgnoreErrorAfterCleanedPos
else
CurIgnoreErrorAfterPos:=-1;
OldFlags:=Params.Flags;
if BracketClose<>#0 then begin
// read parameter list
ReadNextAtom;
if not AtomIsChar(BracketClose) then begin
// read all expressions
while true do begin
ExprStartPos:=CurPos.StartPos;
// read til comma or bracket close
repeat
if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
ReadTilBracketClose(true);
end;
ReadNextAtom;
if (CurPos.StartPos>SrcLen)
or (CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose,cafComma])
then
break;
until false;
ExprEndPos:=CurPos.StartPos;
// find expression type
if (CurIgnoreErrorAfterPos>=ExprStartPos) then
Params.Flags:=Params.Flags-[fdfExceptionOnNotFound];
//DebugLn('TFindDeclarationTool.CreateParamExprListFromStatement CurIgnoreErrorAfterPos=',dbgs(CurIgnoreErrorAfterPos),' ExprStartPos=',dbgs(ExprStartPos));
if GetAlias then begin
AliasType:=CleanFindContext;
ExprType:=FindExpressionResultType(Params,ExprStartPos,ExprEndPos,@AliasType);
Result.Add(ExprType,AliasType);
end else begin
ExprType:=FindExpressionResultType(Params,ExprStartPos,ExprEndPos);
Result.Add(ExprType);
end;
MoveCursorToCleanPos(ExprEndPos);
ReadNextAtom;
if AtomIsChar(BracketClose) then break;
if not AtomIsChar(',') then
RaiseBracketNotFound;
ReadNextAtom;
end;
end;
end;
Params.Flags:=OldFlags;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.CreateParamExprListFromStatement] END ',
'ParamCount=',dbgs(Result.Count),' "',copy(Src,StartPos,40),'"');
DebugLn(' ExprList=[',Result.AsString,']');
{$ENDIF}
Ok:=true;
finally
if not Ok then Result.Free;
end;
end;
function TFindDeclarationTool.CreateParamExprListFromProcNode(
ProcNode: TCodeTreeNode; Params: TFindDeclarationParams): TExprTypeList;
var
ExprType: TExpressionType;
ParamNode: TCodeTreeNode;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(ProcNode);{$ENDIF}
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.CreateParamExprListFromProcNode] ',
'"',copy(Src,ProcNode.StartPos,40),'" Context=',ProcNode.DescAsString);
{$ENDIF}
Result:=TExprTypeList.Create;
ParamNode:=GetFirstParameterNode(ProcNode);
while ParamNode<>nil do begin
// find expression type
ExprType:=ConvertNodeToExpressionType(ParamNode,Params);
// add expression type to list
Result.Add(ExprType);
ParamNode:=ParamNode.NextBrother;
end;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.CreateParamExprListFromProcNode] END ',
'ParamCount=',dbgs(Result.Count),' "',copy(Src,ProcNode.StartPos,40),'"');
DebugLn(' ExprList=[',Result.AsString,']');
{$ENDIF}
end;
function TFindDeclarationTool.CompatibilityList1IsBetter( List1,
List2: TTypeCompatibilityList; ListCount: integer): boolean;
// List1 and List2 should only contain tcCompatible and tcExact values
var i: integer;
begin
// search first difference, start at end
i:=ListCount-1;
while (i>=0) and (List1[i]=List2[i]) do dec(i);
// List1 is better, if first difference is better for List1
Result:=(i>=0) and (List1[i]=tcExact);
{$IFDEF ShowFoundIdentifier}
DebugLn('[TFindDeclarationTool.CompatibilityList1IsBetter] END i=',dbgs(i));
{$ENDIF}
end;
function TFindDeclarationTool.ContextIsDescendOf(const DescendContext,
AncestorContext: TFindContext; Params: TFindDeclarationParams): boolean;
procedure RaiseInternalError;
begin
RaiseException(20170421200624,'[TFindDeclarationTool.ContextIsDescendOf] '
+' internal error: DescendContext.Desc<>ctnClass');
end;
var CurContext: TFindContext;
OldInput: TFindDeclarationInput;
begin
if not (DescendContext.Node.Desc in AllClasses) then
RaiseInternalError;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ContextIsDescendOf] ',
' DescendContext="',copy(DescendContext.Tool.Src,DescendContext.Node.Parent.StartPos,15),'"');
{$ENDIF}
CurContext:=DescendContext;
Params.Save(OldInput);
repeat
Result:=CurContext.Tool.FindAncestorOfClass(CurContext.Node,Params,true);
if Result then begin
CurContext:=CreateFindContext(Params);
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.ContextIsDescendOf] B ',
' CurContext="',copy(CurContext.Tool.Src,CurContext.Node.Parent.StartPos,15),'"');
{$ENDIF}
Result:=FindContextAreEqual(CurContext,AncestorContext);
if Result then exit;
end else
break;
until false;
Result:=false;
end;
function TFindDeclarationTool.IsBaseCompatible(const TargetType,
ExpressionType: TExpressionType; Params: TFindDeclarationParams
): TTypeCompatibility;
// test if ExpressionType can be assigned to TargetType
// both expression types must be base types
var TargetNode, ExprNode: TCodeTreeNode;
begin
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsBaseCompatible] START ',
' TargetType=',ExprTypeToString(TargetType),
' ExpressionType=',ExprTypeToString(ExpressionType));
{$ENDIF}
Result:=tcIncompatible;
if (TargetType.Desc=xtContext)
and (TargetType.Context.Node.Desc=ctnGenericParameter) then
exit(tcExact);
if (ExpressionType.Desc=xtContext)
and (ExpressionType.Context.Node.Desc=ctnGenericParameter) then
exit(tcExact);
if (TargetType.Desc=ExpressionType.Desc) then begin
case TargetType.Desc of
xtNone: ;
xtContext:
begin
TargetNode:=TargetType.Context.Node;
ExprNode:=ExpressionType.Context.Node;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsBaseCompatible] C ',
' TargetContext="',copy(TargetType.Context.Tool.Src,TargetType.Context.Node.StartPos,20),'"',
' ExpressionContext="',copy(ExpressionType.Context.Tool.Src,ExpressionType.Context.Node.StartPos,20),'"'
);
{$ENDIF}
if TargetNode=ExprNode then
Result:=tcExact
else
if ExprNode.Desc=TargetNode.Desc then begin
// same context type
case ExprNode.Desc of
ctnClass, ctnClassInterface, ctnDispinterface, ctnObject, ctnRecordType,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass:
// check, if ExpressionType.Context descends from TargetContext
if ContextIsDescendOf(ExpressionType.Context,
TargetType.Context,Params)
then
Result:=tcExact;
ctnRangedArrayType,ctnOpenArrayType:
// ToDo: check range and type of arrayfields
begin
Result:=tcCompatible;
end;
end;
end else begin
// different context type
end;
end;
else
Result:=tcExact;
end;
end else if ((TargetType.Desc=xtPointer)
and (ExpressionType.Desc=xtContext)
and (ExpressionType.Context.Node.Desc in AllClasses))
then begin
// assigning a class to a pointer
Result:=tcExact;
end else begin
// check, if ExpressionType can be auto converted into TargetType
if ((TargetType.Desc in xtAllRealTypes)
and (ExpressionType.Desc in xtAllRealConvertibles))
or ((TargetType.Desc in xtAllStringTypes)
and (ExpressionType.Desc in xtAllStringConvertibles))
or ((TargetType.Desc in xtAllWideStringTypes)
and (ExpressionType.Desc in xtAllWideStringCompatibleTypes))
or ((TargetType.Desc in xtAllIntegerTypes)
and (ExpressionType.Desc in xtAllIntegerConvertibles))
or ((TargetType.Desc in xtAllBooleanTypes)
and (ExpressionType.Desc in xtAllBooleanConvertibles))
or ((TargetType.Desc in xtAllPointerTypes)
and (ExpressionType.Desc in xtAllPointerConvertibles))
or (TargetType.Desc=xtJSValue)
then
Result:=tcCompatible
else if (TargetType.Desc=xtContext) then begin
TargetNode:=TargetType.Context.Node;
if ((TargetNode.Desc in (AllClasses+[ctnProcedure]))
and (ExpressionType.Desc=xtNil))
or ((TargetNode.Desc in [ctnOpenArrayType,ctnRangedArrayType])
and (TargetNode.LastChild<>nil)
and (TargetNode.LastChild.Desc=ctnOfConstType)
and (ExpressionType.Desc=xtConstSet))
then
Result:=tcCompatible
end
else if (ExpressionType.Desc=xtContext) then begin
ExprNode:=ExpressionType.Context.Node;
if (TargetType.Desc=xtFile) and (ExprNode.Desc=ctnFileType)
then
Result:=tcCompatible
end;
end;
{$IFDEF ShowExprEval}
DebugLn('[TFindDeclarationTool.IsBaseCompatible] END ',
' TargetType=',ExpressionTypeDescNames[TargetType.Desc],
' ExpressionType=',ExpressionTypeDescNames[ExpressionType.Desc],
' Result=',TypeCompatibilityNames[Result]
);
{$ENDIF}
end;
function TFindDeclarationTool.CheckParameterSyntax(StartPos,
CleanCursorPos: integer; out ParameterAtom, ProcNameAtom: TAtomPosition; out
ParameterIndex: integer): boolean;
// check for Identifier(expr,expr,...,expr,VarName
// or Identifier[expr,expr,...,expr,VarName
// ParameterIndex is 0 based
{off $DEFINE VerboseCPS}
procedure RaiseBracketNotOpened;
begin
if CurPos.Flag=cafRoundBracketClose then
RaiseExceptionFmt(20170421200628,ctsBracketNotFound,['('])
else
RaiseExceptionFmt(20170421200630,ctsBracketNotFound,['[']);
end;
function CheckIdentifierAndParameterList: boolean; forward;
function CheckBrackets: boolean;
{ check simple brackets (no identifier in front of brackets)
}
var
BracketAtom: TAtomPosition;
begin
BracketAtom:=CurPos;
{$IFDEF VerboseCPS}DebugLn('CheckBrackets "',GetAtom,'" BracketAtom=',dbgs(BracketAtom));{$ENDIF}
repeat
ReadNextAtom;
if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then begin
if (LastAtoms.GetValueAt(0).Flag=cafWord) then begin
{$IFDEF VerboseCPS}DebugLn('CheckBrackets check word+bracket open');{$ENDIF}
UndoReadNextAtom;
if CheckIdentifierAndParameterList() then exit(true);
end else begin
{$IFDEF VerboseCPS}DebugLn('CheckBrackets check bracket open');{$ENDIF}
if CheckBrackets() then exit(true);
end;
end else if CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose]
then begin
if (BracketAtom.Flag=cafRoundBracketOpen)
=(CurPos.Flag=cafRoundBracketClose)
then begin
// closing bracket found, but the variable was not in them
{$IFDEF VerboseCPS}DebugLn('CheckBrackets bracket closed');{$ENDIF}
exit(false);
end else begin
// invalid closing bracket found
RaiseBracketNotOpened;
end;
end;
until (CurPos.EndPos>CleanCursorPos);
Result:=false;
end;
function CheckIdentifierAndParameterList: boolean;
{ when called: CursorPos is at an identifier followed by a ( or [
}
var
BracketAtom: TAtomPosition;
CurProcNameAtom: TAtomPosition;
CurParameterIndex: Integer;
ParameterStart: integer;
begin
Result:=false;
if CurPos.Flag<>cafWord then exit;
CurProcNameAtom:=CurPos;
CurParameterIndex:=0;
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList START "',GetAtom,'" ',dbgs(CurProcNameAtom));{$ENDIF}
ReadNextAtom;
if not (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen]) then exit;
BracketAtom:=CurPos;
ParameterStart:=CurPos.EndPos;
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList Bracket="',GetAtom,'"');{$ENDIF}
repeat
ReadNextAtom;
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList Atom="',GetAtom,'"');{$ENDIF}
if (CurPos.EndPos>CleanCursorPos)
or ((CurPos.EndPos=CleanCursorPos)
and ((CurPos.Flag=cafWord) or AtomIsChar('@')))
then begin
// parameter found => search parameter expression bounds e.g. ', parameter ,'
// important: this function should work, even if the code
// behind CleanCursorPos has syntax errors
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList Parameter found, search range ...');{$ENDIF}
ProcNameAtom:=CurProcNameAtom;
ParameterIndex:=CurParameterIndex;
ParameterAtom.StartPos:=ParameterStart;
ParameterAtom.EndPos:=ParameterStart;
MoveCursorToCleanPos(ParameterStart);
repeat
ReadNextAtom;
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList parameter atom "',GetAtom,'"');{$ENDIF}
if (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen]) then
begin
// atom belongs to the parameter expression
if ParameterAtom.StartPos=ParameterAtom.EndPos then
ParameterAtom.StartPos:=CurPos.StartPos;
ReadTilBracketClose(false);
ParameterAtom.EndPos:=CurPos.EndPos;
end
else
if (CurPos.StartPos>SrcLen)
or (CurPos.Flag in [cafComma,cafSemicolon,cafEnd,
cafRoundBracketClose,cafEdgedBracketClose])
or ((CurPos.Flag=cafWord)
and (LastAtoms.GetValueAt(0).Flag=cafWord)
and (not LastUpAtomIs(0,'INHERITED'))) then
begin
// end of parameter expression found
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList end of parameter found "',GetAtom,'" Parameter="',dbgstr(Src,ParameterAtom.StartPos,ParameterAtom.EndPos-ParameterAtom.StartPos),'"');{$ENDIF}
exit(true);
end else begin
// atom belongs to the parameter expression
if ParameterAtom.StartPos=ParameterAtom.EndPos then
ParameterAtom.StartPos:=CurPos.StartPos;
ParameterAtom.EndPos:=CurPos.EndPos;
end;
until false;
end;
if (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen]) then begin
if (LastAtoms.GetValueAt(0).Flag=cafWord) then begin
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList check word+bracket open');{$ENDIF}
UndoReadNextAtom;
if CheckIdentifierAndParameterList() then exit(true);
end else begin
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList check bracket open');{$ENDIF}
if CheckBrackets then exit(true);
end;
end
else if CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose] then
begin
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList check bracket close');{$ENDIF}
if (BracketAtom.Flag=cafRoundBracketOpen)
=(CurPos.Flag=cafRoundBracketClose)
then begin
// parameter list ended in front of Variable => continue search
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList parameter list ended in front of cursor');{$ENDIF}
if CurPos.Flag=cafEdgedBracketClose then begin
ReadNextAtom;
if CurPos.Flag=cafEdgedBracketOpen then begin
// [][] is equal to [,]
ParameterStart:=CurPos.EndPos;
inc(CurParameterIndex);
continue;
end else
UndoReadNextAtom;
end;
exit;
end else begin
// invalid closing bracket found
RaiseBracketNotOpened;
end;
end;
// finally after checking the expression: count commas
if CurPos.Flag=cafComma then begin
ParameterStart:=CurPos.EndPos;
inc(CurParameterIndex);
end;
{$IFDEF VerboseCPS}DebugLn('CheckIdentifierAndParameterList After parsing atom. atom="',GetAtom,'"');{$ENDIF}
until (CurPos.EndPos>CleanCursorPos);
end;
var
CommentStart: integer;
CommentEnd: integer;
CleanPosInFront: Integer;
begin
Result:=false;
ParameterAtom:=CleanAtomPosition;
ProcNameAtom:=CleanAtomPosition;
ParameterIndex:=0;
//DebugLn('TFindDeclarationTool.CheckParameterSyntax START');
if StartPos<1 then exit;
// read code in front to find ProcName and check the syntax
MoveCursorToCleanPos(StartPos);
repeat
ReadNextAtom;
{$IFDEF VerboseCPS}
DebugLn('TFindDeclarationTool.CheckParameterSyntax ',GetAtom,' at ',CleanPosToStr(CurPos.StartPos),' ',dbgs(CurPos.EndPos),'<',dbgs(CleanCursorPos));
{$ENDIF}
if CurPos.EndPos>CleanCursorPos then begin
if LastAtoms.Count=0 then exit;
CleanPosInFront:=LastAtoms.GetValueAt(0).EndPos;
//debugln(['TFindDeclarationTool.CheckParameterSyntax Cur="',GetAtom,'" Last="',GetAtom(LastAtoms.GetValueAt(0)),'"']);
if not CleanPosIsInComment(CleanCursorPos,CleanPosInFront,
CommentStart,CommentEnd,false) then exit;
// cursor in a comment
// => parse within the comment
MoveCursorToCleanPos(CommentStart);
end else if (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen])
and (LastAtoms.GetValueAt(0).Flag=cafWord) then begin
UndoReadNextAtom;
if CheckIdentifierAndParameterList then exit(true);
if CurPos.EndPos>CleanCursorPos then exit;
end;
until false;
end;
procedure TFindDeclarationTool.OnFindUsedUnitIdentifier(
Sender: TPascalParserTool; IdentifierCleanPos: integer; Range: TEPRIRange;
Node: TCodeTreeNode; Data: Pointer; var Abort: boolean);
var
Identifier: PChar;
CacheEntry: PInterfaceIdentCacheEntry;
refs: TFindUsedUnitReferences;
Found: Boolean;
ReferencePos: TCodeXYPosition;
begin
if Range=epriInDirective then exit;
if not (Node.Desc in (AllPascalTypes+AllPascalStatements)) then exit;
Identifier:=@Src[IdentifierCleanPos];
refs:=TFindUsedUnitReferences(Data);
CacheEntry:=refs.TargetTool.FInterfaceIdentifierCache.FindIdentifier(Identifier);
//debugln(['TFindUsedUnitReferences.OnIdentifier Identifier=',GetIdentifier(Identifier),' Found=',CacheEntry<>nil]);
if (CacheEntry=nil)
and (CompareIdentifiers(Identifier,PChar(refs.TargetUnitName))<>0) then
exit;
Sender.MoveCursorToCleanPos(IdentifierCleanPos);
Sender.ReadPriorAtom;
if (Sender.CurPos.Flag=cafPoint) or (Sender.UpAtomIs('inherited')) then exit;
//debugln(['TFindUsedUnitReferences.OnIdentifier Identifier=',GetIdentifier(Identifier),' at begin of term']);
// find declaration
refs.Params.Clear;
refs.Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,
fdfIgnoreCurContextNode];
refs.Params.ContextNode:=Node;
//debugln(copy(Src,Params.ContextNode.StartPos,200));
refs.Params.SetIdentifier(Self,Identifier,@CheckSrcIdentifier);
if Range=epriInCode then begin
// search identifier in code
Found:=FindDeclarationOfIdentAtParam(refs.Params);
end else begin
// search identifier in comment -> if not found, this is no problem
// => silently ignore
try
Found:=FindDeclarationOfIdentAtParam(refs.Params);
except
on E: ECodeToolError do begin
// continue
end;
on E: Exception do
raise;
end;
end;
//debugln(['TFindUsedUnitReferences.OnIdentifier Identifier=',GetIdentifier(Identifier),' found=',Found]);
if not Found then exit;
if CleanPosToCaret(IdentifierCleanPos,ReferencePos) then
AddCodePosition(refs.ListOfPCodeXYPosition,ReferencePos);
end;
function TFindDeclarationTool.FindNthParameterNode(Node: TCodeTreeNode;
ParameterIndex: integer): TCodeTreeNode;
var
ProcNode, FunctionNode: TCodeTreeNode;
ProcHeadNode: TCodeTreeNode;
ParameterNode: TCodeTreeNode;
i: Integer;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=nil;
if Node=nil then exit;
if Node.Desc=ctnReferenceTo then begin
Node:=Node.FirstChild;
if Node=nil then exit;
end;
if Node.Desc in [ctnProcedure,ctnProcedureType] then begin
ProcNode:=Node;
//DebugLn(' FindNthParameterNode ProcNode="',copy(Params.NewCodeTool.Src,ProcNode.StartPos,ProcNode.EndPos-ProcNode.StartPos),'"');
FunctionNode:=nil;
BuildSubTreeForProcHead(ProcNode,FunctionNode);
// find procedure head
ProcHeadNode:=ProcNode.FirstChild;
if (ProcHeadNode=nil) or (ProcHeadNode.Desc<>ctnProcedureHead) then begin
DebugLn(' FindNthParameterNode Procedure has no parameter list');
exit;
end;
// find parameter list
ParameterNode:=ProcHeadNode.FirstChild;
if (ParameterNode=nil) or (ParameterNode.Desc<>ctnParameterList)
then begin
DebugLn(' FindNthParameterNode Procedure has no parameter list');
exit;
end;
// find parameter
ParameterNode:=ParameterNode.FirstChild;
i:=0;
while (i<ParameterIndex) and (ParameterNode<>nil) do begin
//DebugLn(' FindNthParameterNode ',ParameterNode.DescAsString);
ParameterNode:=ParameterNode.NextBrother;
inc(i);
end;
Result:=ParameterNode;
end;
end;
constructor TFindDeclarationTool.Create;
begin
inherited Create;
FSourcesChangeStep:=CTInvalidChangeStamp64;
FFilesChangeStep:=CTInvalidChangeStamp64;
FInitValuesChangeStep:=CTInvalidChangeStamp;
end;
procedure TFindDeclarationTool.DoDeleteNodes(StartNode: TCodeTreeNode);
var
HelperKind: TFDHelpersListKind;
begin
ClearNodeCaches;
if FInterfaceIdentifierCache<>nil then begin
FInterfaceIdentifierCache.Clear;
FInterfaceIdentifierCache.Complete:=false;
end;
for HelperKind in TFDHelpersListKind do
if FInterfaceHelperCache[HelperKind]<>nil then
FInterfaceHelperCache[HelperKind].Clear;
inherited DoDeleteNodes(StartNode);
end;
function TFindDeclarationTool.CheckDependsOnNodeCaches(
CheckedTools: TAVLTree = nil): boolean;
var
ANode: TAVLTreeNode;
ATool: TFindDeclarationTool;
FreeCheckedTools: Boolean;
SourcesChangeStep, FilesChangeStep: int64;
InitValuesChangeStep: integer;
begin
Result:=false;
//debugln(['TFindDeclarationTool.CheckDependsOnNodeCaches ',MainFilename,' FDependsOnCodeTools=',FDependsOnCodeTools]);
if (FDependsOnCodeTools=nil) or FCheckingNodeCacheDependencies then exit;
if Scanner=nil then exit;
if Assigned(Scanner.OnGetGlobalChangeSteps) then begin
// check if any sources or values have changed
Scanner.OnGetGlobalChangeSteps(SourcesChangeStep,FilesChangeStep,
InitValuesChangeStep);
if (SourcesChangeStep=FSourcesChangeStep)
and (FilesChangeStep=FFilesChangeStep)
and (InitValuesChangeStep=FInitValuesChangeStep) then
// all sources and values are the same
exit;
FSourcesChangeStep:=SourcesChangeStep;
FFilesChangeStep:=FilesChangeStep;
FInitValuesChangeStep:=InitValuesChangeStep;
end;
if (CheckedTools<>nil) and (CheckedTools.Find(Self)<>nil) then exit;
{$IFDEF ShowCacheDependencies}
DebugLn(['[TFindDeclarationTool.CheckDependsOnNodeCaches] START DependsOn=',FDependsOnCodeTools.Count,' ',MainFilename]);
{$ENDIF}
FCheckingNodeCacheDependencies:=true;
FreeCheckedTools:=false;
if CheckedTools=nil then begin
FreeCheckedTools:=true;
CheckedTools:=TAVLTree.Create;
end;
try
CheckedTools.Add(Self);
ANode:=FDependsOnCodeTools.FindLowest;
while ANode<>nil do begin
ATool:=TFindDeclarationTool(ANode.Data);
Result:=ATool.UpdateNeeded(lsrImplementationStart)
or ATool.CheckDependsOnNodeCaches(CheckedTools);
if Result then exit;
ANode:=FDependsOnCodeTools.FindSuccessor(ANode);
end;
Result:=false;
finally
{$IFDEF ShowCacheDependencies}
DebugLn('[TFindDeclarationTool.CheckDependsOnNodeCaches] Result=',DbgS(Result),' ',MainFilename);
{$ENDIF}
FCheckingNodeCacheDependencies:=false;
if FreeCheckedTools then FreeAndNil(CheckedTools);
if Result then ClearNodeCaches;
end;
end;
destructor TFindDeclarationTool.Destroy;
var
HelperKind: TFDHelpersListKind;
begin
FreeAndNil(FInterfaceIdentifierCache);
for HelperKind in TFDHelpersListKind do
FreeAndNil(FInterfaceHelperCache[HelperKind]);
FreeAndNil(FDependsOnCodeTools);
FreeAndNil(FDependentCodeTools);
if FDirectoryCache<>nil then begin
FDirectoryCache.Release;
FDirectoryCache:=nil;
end;
FFindMissingFPCUnits.Free;
inherited Destroy;
end;
procedure TFindDeclarationTool.ClearNodeCaches;
var
NodeCache: TCodeTreeNodeCache;
BaseTypeCache: TBaseTypeCache;
begin
// check if there is something in cache to delete
if (FFirstNodeCache=nil) and (FFirstBaseTypeCache=nil)
and (FRootNodeCache=nil)
and ((FDependentCodeTools=nil) or (FDependentCodeTools.Count=0))
and ((FDependsOnCodeTools=nil) or (FDependsOnCodeTools.Count=0)) then
exit;
{$IFDEF ShowCacheDependencies}
DebugLn('[TFindDeclarationTool.ClearNodeCaches] Force=',
DbgS(Force),' ',MainFilename);
{$ENDIF}
// clear node caches
while FFirstNodeCache<>nil do begin
NodeCache:=FFirstNodeCache;
FFirstNodeCache:=NodeCache.Next;
NodeCacheMemManager.DisposeNodeCache(NodeCache);
end;
while FFirstBaseTypeCache<>nil do begin
BaseTypeCache:=FFirstBaseTypeCache;
FFirstBaseTypeCache:=BaseTypeCache.NextCache;
BaseTypeCacheMemManager.DisposeBaseTypeCache(BaseTypeCache);
end;
if FRootNodeCache<>nil then begin
NodeCacheMemManager.DisposeNodeCache(FRootNodeCache);
FRootNodeCache:=nil;
end;
// clear dependent codetools
ClearDependentNodeCaches;
ClearDependsOnToolRelationships;
end;
procedure TFindDeclarationTool.ClearDependentNodeCaches;
var
ANode: TAVLTreeNode;
DependentTool: TFindDeclarationTool;
begin
if (FDependentCodeTools=nil) or (FDependentCodeTools.Count=0)
or FClearingDependentNodeCaches then exit;
FClearingDependentNodeCaches:=true;
{$IFDEF ShowCacheDependencies}
DebugLn('[TFindDeclarationTool.ClearDependentNodeCaches] ',MainFilename);
{$ENDIF}
try
ANode:=FDependentCodeTools.FindLowest;
while ANode<>nil do begin
DependentTool:=TFindDeclarationTool(ANode.Data);
DependentTool.ClearNodeCaches;
ANode:=FDependentCodeTools.FindSuccessor(ANode);
end;
FDependentCodeTools.Clear;
finally
FClearingDependentNodeCaches:=false;
end;
end;
procedure TFindDeclarationTool.ClearDependsOnToolRelationships;
var
ANode: TAVLTreeNode;
DependOnTool: TFindDeclarationTool;
begin
if (FDependsOnCodeTools=nil) or (FDependsOnCodeTools.Count=0) then exit;
{$IFDEF ShowCacheDependencies}
DebugLn('[TFindDeclarationTool.ClearDependsOnToolRelationships] ',MainFilename);
{$ENDIF}
ANode:=FDependsOnCodeTools.FindLowest;
while ANode<>nil do begin
DependOnTool:=TFindDeclarationTool(ANode.Data);
if not DependOnTool.FClearingDependentNodeCaches then
DependOnTool.FDependentCodeTools.Remove(Self);
ANode:=FDependsOnCodeTools.FindSuccessor(ANode);
end;
FDependsOnCodeTools.Clear;
end;
procedure TFindDeclarationTool.AddToolDependency(
DependOnTool: TFindDeclarationTool);
// build a relationship: this tool depends on DependOnTool
{$IFDEF DebugAddToolDependency}
var
AVLNode: TAVLTreeNode;
Tool: TFindDeclarationTool;
{$ENDIF}
begin
{$IFDEF ShowCacheDependencies}
DebugLn('[TFindDeclarationTool.AddToolDependency] "',MainFilename,'" depends on "',DependOnTool.MainFilename,'"');
{$ENDIF}
if DependOnTool.FDependentCodeTools=nil then
DependOnTool.FDependentCodeTools:=TAVLTree.Create;
if DependOnTool.FDependentCodeTools.Find(Self)=nil then
DependOnTool.FDependentCodeTools.Add(Self);
if FDependsOnCodeTools=nil then
FDependsOnCodeTools:=TAVLTree.Create;
if FDependsOnCodeTools.Find(DependOnTool)=nil then begin
{$IFDEF DebugAddToolDependency}
AVLNode:=FDependsOnCodeTools.FindLowest;
while AVLNode<>nil do begin
Tool:=TFindDeclarationTool(AVLNode.Data);
if CompareFilenames(ExtractFilename(Tool.MainFilename),ExtractFilename(DependOnTool.MainFilename))=0 then begin
DebugLn(['TFindDeclarationTool.AddToolDependency inconsistency: ',Tool.MainFilename,' ',DependOnTool.MainFilename]);
end;
AVLNode:=FDependsOnCodeTools.FindSuccessor(AVLNode);
end;
{$ENDIF}
FDependsOnCodeTools.Add(DependOnTool);
end;
end;
procedure TFindDeclarationTool.ConsistencyCheck;
var ANodeCache: TCodeTreeNodeCache;
begin
inherited ConsistencyCheck;
if FInterfaceIdentifierCache<>nil then
FInterfaceIdentifierCache.ConsistencyCheck;
ANodeCache:=FFirstNodeCache;
while ANodeCache<>nil do begin
ANodeCache.ConsistencyCheck;
ANodeCache:=ANodeCache.Next;
end;
if FDependentCodeTools<>nil then
FDependentCodeTools.ConsistencyCheck;
if FDependsOnCodeTools<>nil then
FDependsOnCodeTools.ConsistencyCheck;
end;
procedure TFindDeclarationTool.CalcMemSize(Stats: TCTMemStats);
var
NodeCache: TCodeTreeNodeCache;
TypeCache: TBaseTypeCache;
m: PtrUInt;
HelperKind: TFDHelpersListKind;
begin
inherited CalcMemSize(Stats);
if FInterfaceIdentifierCache<>nil then
Stats.Add('TFindDeclarationTool.FInterfaceIdentifierCache',
FInterfaceIdentifierCache.CalcMemSize);
for HelperKind in TFDHelpersListKind do
if FInterfaceHelperCache[HelperKind]<>nil then
Stats.Add('TFindDeclarationTool.FInterfaceHelperCache[]',
FInterfaceHelperCache[HelperKind].CalcMemSize);
if FFirstNodeCache<>nil then begin
m:=0;
NodeCache:=FFirstNodeCache;
while NodeCache<>nil do begin
inc(m,NodeCache.CalcMemSize);
NodeCache:=NodeCache.Next;
end;
Stats.Add('TFindDeclarationTool.NodeCache',m);
end;
if FFirstBaseTypeCache<>nil then begin
m:=0;
TypeCache:=FFirstBaseTypeCache;
while TypeCache<>nil do begin
inc(m,TypeCache.CalcMemSize);
TypeCache:=TypeCache.NextCache;
end;
Stats.Add('TFindDeclarationTool.TypeCache',m);
end;
if FDependentCodeTools<>nil then
Stats.Add('TFindDeclarationTool.FDependentCodeTools',
FDependentCodeTools.Count*SizeOf(TAVLTreeNode));
if FDependsOnCodeTools<>nil then
Stats.Add('TFindDeclarationTool.FDependsOnCodeTools',
FDependsOnCodeTools.Count*SizeOf(TAVLTreeNode));
end;
procedure TFindDeclarationTool.ValidateToolDependencies;
begin
//debugln(['TFindDeclarationTool.ValidateToolDependencies ',MainFilename]);
inherited ValidateToolDependencies;
CheckDependsOnNodeCaches;
end;
function TFindDeclarationTool.GetNodeCache(Node: TCodeTreeNode;
CreateIfNotExists: boolean): TCodeTreeNodeCache;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
while (Node<>nil) and (not (Node.Desc in AllNodeCacheDescs)) do
Node:=Node.Parent;
if Node<>nil then begin
if (Node.Cache=nil) and CreateIfNotExists then
CreateNewNodeCache(Node);
if (Node.Cache is TCodeTreeNodeCache) then
Result:=TCodeTreeNodeCache(Node.Cache)
else
Result:=nil;
end else begin
if (FRootNodeCache=nil) and CreateIfNotExists then
FRootNodeCache:=CreateNewNodeCache(nil);
Result:=FRootNodeCache;
end;
end;
procedure TFindDeclarationTool.AddResultToNodeCaches(
StartNode, EndNode: TCodeTreeNode; SearchedForward: boolean;
Params: TFindDeclarationParams; SearchRangeFlags: TNodeCacheEntryFlags);
var Node: TCodeTreeNode;
CurNodeCache, LastNodeCache: TCodeTreeNodeCache;
CleanStartPos, CleanEndPos: integer;
NewNode: TCodeTreeNode;
NewTool: TPascalParserTool;
NewCleanPos: integer;
{$IFDEF ShowNodeCache}
BeVerbose: boolean;
NodeOwner: TObject;
function WriteSrcPos(t: TPascalParserTool; p: integer): string;
begin
Result:=StringToPascalConst(copy(t.Src,p-10,10)+'|'+copy(t.Src,p,15)+'"');
end;
function NodeOwnerAsString(ANodeOwner: TObject): string;
begin
if ANodeOwner=nil then
Result:='nil'
else if ANodeOwner is TPascalParserTool then
Result:=ExtractFileName(TPascalParserTool(ANodeOwner).MainFilename)
else
Result:='?'+ANodeOwner.ClassName+'?';
end;
{$ENDIF}
begin
{$IFDEF CheckNodeTool}CheckNodeTool(StartNode);{$ENDIF}
if StartNode=nil then exit;
if EndNode=nil then EndNode:=StartNode;
if Params.NewNode<>nil then begin
// identifier found
NewNode:=Params.NewNode;
NewTool:=Params.NewCodeTool;
NewCleanPos:=Params.NewCleanPos;
end else begin
// identifier not found
NewNode:=nil;
NewTool:=nil;
NewCleanPos:=-1;
end;
// calculate search range
if EndNode<>nil then begin
if SearchedForward then begin
CleanStartPos:=StartNode.StartPos;
CleanEndPos:=EndNode.EndPos;
end else begin
CleanStartPos:=EndNode.StartPos;
CleanEndPos:=StartNode.EndPos;
end;
end else begin
// searched till start or end of source
if not SearchedForward then begin
CleanStartPos:=1;
CleanEndPos:=StartNode.StartPos;
end else begin
CleanStartPos:=StartNode.StartPos;
CleanEndPos:=SrcLen+1;
end;
end;
{$IFDEF ShowNodeCache}
beVerbose:=true; //CompareSrcIdentifiers(Params.Identifier,'InitDecompressor');
if beVerbose then begin
DebugLn('(((((((((((((((((((((((((((==================');
DbgOut('TFindDeclarationTool.AddResultToNodeCaches ',
' Ident=',GetIdentifier(Params.Identifier));
DbgOut(' SearchedForward=',DbgS(SearchedForward));
DbgOut(' Flags=[');
if ncefSearchedInParents in SearchRangeFlags then DbgOut('Parents');
if ncefSearchedInAncestors in SearchRangeFlags then DbgOut(',Ancestors');
DebugLn(']');
DbgOut(' StartNode=',StartNode.DescAsString,
'('+DbgS(StartNode.StartPos),'-',DbgS(StartNode.EndPos)+')=',
WriteSrcPos(Self,StartNode.StartPos));
NodeOwner:=FindOwnerOfCodeTreeNode(StartNode);
if NodeOwner<>Self then DbgOut(' StartNodeOwner=',NodeOwnerAsString(NodeOwner));
DebugLn('');
if EndNode<>nil then
DbgOut(' EndNode=',EndNode.DescAsString,
'('+DbgS(EndNode.StartPos),'-',DbgS(EndNode.EndPos)+')=',
WriteSrcPos(Self,EndNode.StartPos))
else
DbgOut(' EndNode=nil');
NodeOwner:=FindOwnerOfCodeTreeNode(EndNode);
if NodeOwner<>Self then DbgOut(' EndNodeOwner=',NodeOwnerAsString(NodeOwner));
DebugLn('');
DebugLn(' Self=',ExtractFileName(MainFilename));
if NewNode<>nil then begin
DebugLn(' NewNode=',NewNode.DescAsString,
'(',DbgS(NewNode.StartPos),'-',DbgS(NewNode.EndPos),')=',
WriteSrcPos(NewTool,NewNode.StartPos),
' NewTool=',ExtractFileName(NewTool.MainFilename));
end else begin
DebugLn(' NOT FOUND');
//RaiseCatchableException('');
end;
DebugLn(' CleanStartPos=',DbgS(CleanStartPos),' ',WriteSrcPos(Self,CleanStartPos));
DebugLn(' CleanEndPos=',DbgS(CleanEndPos),' ',WriteSrcPos(Self,CleanEndPos));
end;
{$ENDIF}
LastNodeCache:=nil;
// start with parent of deepest node and end parent of highest
Node:=StartNode;
repeat
if (Node.Desc in AllNodeCacheDescs) then begin
if (Node.Cache=nil) then
CreateNewNodeCache(Node);
if (Node.Cache is TCodeTreeNodeCache) then begin
CurNodeCache:=TCodeTreeNodeCache(Node.Cache);
if LastNodeCache<>CurNodeCache then begin
{$IFDEF ShowNodeCache}
if BeVerbose then begin
CurNodeCache.WriteDebugReport(' BEFORE NODECACHE REPORT: ');
end;
{$ENDIF}
CurNodeCache.Add(Params.Identifier,
Self,CleanStartPos,CleanEndPos,
NewNode,NewTool,NewCleanPos,SearchRangeFlags);
{$IFDEF ShowNodeCache}
if BeVerbose then begin
CurNodeCache.WriteDebugReport(' AFTER NODECACHE REPORT: ');
end;
{$ENDIF}
LastNodeCache:=CurNodeCache;
end;
end;
end;
Node:=Node.Parent;
until (Node=nil) or (EndNode=Node) or EndNode.HasAsParent(Node);
{$IFDEF ShowNodeCache}
if BeVerbose then begin
DebugLn('=========================))))))))))))))))))))))))))))))))');
end;
{$ENDIF}
end;
function TFindDeclarationTool.CreateNewNodeCache(
Node: TCodeTreeNode): TCodeTreeNodeCache;
begin
{$IFDEF CheckNodeTool}CheckNodeTool(Node);{$ENDIF}
Result:=NodeCacheMemManager.NewNodeCache(Node);
Result.Next:=FFirstNodeCache;
FFirstNodeCache:=Result;
end;
function TFindDeclarationTool.CreateNewBaseTypeCache(
Tool: TFindDeclarationTool; Node: TCodeTreeNode): TBaseTypeCache;
begin
{$IFDEF CheckNodeTool}Tool.CheckNodeTool(Node);{$ENDIF}
Result:=BaseTypeCacheMemManager.NewBaseTypeCache(Node);
Result.NextCache:=Tool.FFirstBaseTypeCache;
Tool.FFirstBaseTypeCache:=Result;
end;
procedure TFindDeclarationTool.CreateBaseTypeCaches(
NodeStack: PCodeTreeNodeStack; const Result: TFindContext);
var i: integer;
Entry: PCodeTreeNodeStackEntry;
BaseTypeCache: TBaseTypeCache;
NextEntry: PCodeTreeNodeStackEntry;
begin
{$IFDEF ShowBaseTypeCache}
DbgOut('[TFindDeclarationTool.CreateBaseTypeCaches] ',
' StackPtr=',DbgS(NodeStack^.StackPtr));
DebugLn(' Self=',MainFilename);
if Result.Node<>nil then
DbgOut(' Result='+Result.Node.DescAsString,
' Start='+DbgS(Result.Node.StartPos),
' End='+DbgS(Result.Node.EndPos),
' "'+copy(Result.Tool.Src,Result.Node.StartPos,15)+'" ',Result.Tool.MainFilename)
else
DbgOut(' Result=nil');
DebugLn('');
{$ENDIF}
for i:=0 to NodeStack^.StackPtr do begin
Entry:=GetNodeStackEntry(NodeStack,i);
if Entry^.Node.Cache=nil then begin
{$IFDEF ShowBaseTypeCache}
DebugLn(' i=',DbgS(i),' Node=',Entry^.Node.DescAsString,' "',copy(Entry^.Tool.Src,Entry^.Node.StartPos,15),'"');
{$ENDIF}
BaseTypeCache:=
CreateNewBaseTypeCache(TFindDeclarationTool(Entry^.Tool),Entry^.Node);
if BaseTypeCache<>nil then begin
BaseTypeCache.BaseNode:=Result.Node;
BaseTypeCache.BaseTool:=Result.Tool;
if i<NodeStack^.StackPtr then begin
NextEntry:=GetNodeStackEntry(NodeStack,i+1);
BaseTypeCache.NextNode:=NextEntry^.Node;
BaseTypeCache.NextTool:=NextEntry^.Tool;
end else begin
BaseTypeCache.NextNode:=Result.Node;
BaseTypeCache.NextTool:=Result.Tool;
end;
end;
end;
end;
end;
function TFindDeclarationTool.GetExpressionTypeOfTypeIdentifier(
Params: TFindDeclarationParams): TExpressionType;
var
OldFlags: TFindDeclarationFlags;
begin
OldFlags:=Params.Flags;
if FindIdentifierInContext(Params) then begin
Params.Flags:=OldFlags;
Result:=Params.NewCodeTool.ConvertNodeToExpressionType(Params.NewNode,Params);
end else begin
// predefined identifier
Params.Flags:=OldFlags;
Result:=CleanExpressionType;
Result.Desc:=PredefinedIdentToExprTypeDesc(Params.Identifier,Scanner.PascalCompiler);
end;
end;
function TFindDeclarationTool.FindTermTypeAsString(TermPos: TAtomPosition;
Params: TFindDeclarationParams;
out ExprType: TExpressionType): string;
var
EdgedBracketsStartPos: integer;
SetNode: TCodeTreeNode;
SetTool: TFindDeclarationTool;
AliasType: TFindContext;
begin
//debugln(['TFindDeclarationTool.FindTermTypeAsString START']);
{$IFDEF CheckNodeTool}CheckNodeTool(Params.ContextNode);{$ENDIF}
Result:='';
AliasType:=CleanFindContext;
if IsTermEdgedBracket(TermPos,EdgedBracketsStartPos) then begin
// check for constant sets: [enum]
MoveCursorToCleanPos(EdgedBracketsStartPos);
ReadNextAtom;
ReadNextAtom;
if CurPos.Flag=cafWord then begin
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.FindTermTypeAsString "[name" : check for enumeration type ...']);
debugln(['TFindDeclarationTool.FindTermTypeAsString StartContext=',Params.ContextNode.DescAsString,'=',dbgstr(Src,Params.ContextNode.StartPos,15),'"']);
{$ENDIF}
ExprType:=FindExpressionResultType(Params,EdgedBracketsStartPos+1,-1);
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.FindTermTypeAsString "[name" : ',ExprTypeToString(ExprType)]);
{$ENDIF}
if (ExprType.Desc=xtContext)
and (ExprType.Context.Node.Desc in [ctnEnumerationType,ctnEnumIdentifier])
then begin
SetTool:=ExprType.Context.Tool;
SetNode:=SetTool.FindSetOfEnumerationType(ExprType.Context.Node);
if SetNode<>nil then begin
ExprType:=CleanExpressionType;
ExprType.Desc:=xtContext;
ExprType.SubDesc:=xtNone;
ExprType.Context.Tool:=SetTool;
ExprType.Context.Node:=SetNode;
Result:=SetTool.ExtractDefinitionName(SetNode);
exit;
end;
end;
end;
end;
// check if TermPos is @Name and a pointer (= ^Name) can be found
if IsTermNamedPointer(TermPos,ExprType) then begin
// pointer type
end else begin
ExprType:=CleanExpressionType;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFunctionResult,fdfIgnoreOperatorError];
ExprType:=FindExpressionResultType(Params,TermPos.StartPos,TermPos.EndPos,
@AliasType);
end;
if AliasType.Node<>nil then begin
ExprType:=CleanExpressionType;
ExprType.Desc:=xtContext;
ExprType.Context:=AliasType;
end;
Result:=FindExprTypeAsString(ExprType,TermPos.StartPos);
end;
function TFindDeclarationTool.FindForInTypeAsString(TermPos: TAtomPosition;
CursorNode: TCodeTreeNode; Params: TFindDeclarationParams; out
ExprType: TExpressionType): string;
procedure RaiseTermHasNoIterator(id: int64; TermExprType: TExpressionType);
begin
if TermPos.StartPos<1 then
TermPos.StartPos:=1;
MoveCursorToCleanPos(TermPos.StartPos);
debugln(['TFindDeclarationTool.FindForInTypeAsString TermExprType=',ExprTypeToString(TermExprType)]);
RaiseException(id,'Can not find an enumerator for '''+TrimCodeSpace(GetAtom(TermPos))+'''');
end;
procedure ResolveExpr(SubExprType: TExpressionType);
var
AliasType: TFindContext;
Node: TCodeTreeNode;
begin
{$IFDEF ShowForInEval}
debugln([' ResolveExpr ',ExprTypeToString(SubExprType)]);
{$ENDIF}
// use default enumerators
case SubExprType.Desc of
xtContext:
begin
case SubExprType.Context.Node.Desc of
ctnClass, ctnRecordType, ctnClassHelper, ctnRecordHelper, ctnTypeHelper:
begin
AliasType:=CleanFindContext;
if not SubExprType.Context.Tool.FindEnumeratorOfClass(
SubExprType.Context.Node,true,ExprType,@AliasType, Params)
then
RaiseTermHasNoIterator(20170421211210,SubExprType);
Result:=FindExprTypeAsString(ExprType,TermPos.StartPos,@AliasType);
end;
ctnEnumerationType:
begin
Node:=SubExprType.Context.Node.Parent;
if Node.Desc=ctnTypeDefinition then
Result:=SubExprType.Context.Tool.ExtractIdentifier(Node.StartPos);
end;
ctnSetType:
if SubExprType.Context.Tool.FindEnumerationTypeOfSetType(
SubExprType.Context.Node,ExprType.Context)
then begin
ExprType.Desc:=xtContext;
Result:=FindExprTypeAsString(ExprType,TermPos.StartPos);
end;
ctnRangedArrayType,ctnOpenArrayType:
begin
AliasType:=CleanFindContext;
if SubExprType.Context.Tool.FindElementTypeOfArrayType(
SubExprType.Context.Node,ExprType,@AliasType)
then begin
Result:=FindExprTypeAsString(ExprType,TermPos.StartPos,@AliasType);
end;
end;
else
RaiseTermHasNoIterator(20170421211213,SubExprType);
end;
end;
xtChar,
xtSmallInt,
xtShortInt,
xtByte,
xtWord,
xtBoolean,
xtByteBool,
xtWordBool,
xtLongBool,
xtQWordBool,
xtNativeInt,
xtNativeUInt:
Result:=ExpressionTypeDescNames[SubExprType.Desc];
xtNone,
xtWideChar,
xtReal,
xtSingle,
xtDouble,
xtExtended,
xtCExtended,
xtCurrency,
xtComp,
xtInt64,
xtCardinal,
xtQWord,
xtPointer,
xtFile,
xtText,
xtConstOrdInteger,
xtConstReal,
xtConstBoolean,
xtLongint,
xtLongWord,
xtCompilerFunc,
xtVariant,
xtJSValue,
xtNil:
RaiseTermHasNoIterator(20170421211217,SubExprType);
xtString,
xtAnsiString,
xtShortString,
xtPChar,
xtConstString:
begin
ExprType.Desc:=xtChar;
Result:=ExpressionTypeDescNames[ExprType.Desc];
end;
xtWideString,
xtUnicodeString:
begin
ExprType.Desc:=xtWideChar;
Result:=ExpressionTypeDescNames[ExprType.Desc];
end;
xtConstSet:
begin
if SubExprType.Context.Node=nil then
RaiseTermHasNoIterator(20170421211222,SubExprType);
SubExprType:=SubExprType.Context.Tool.FindExpressionTypeOfConstSet(SubExprType.Context.Node);
{$IFDEF ShowForInEval}
debugln([' ResolveExpr ConstSet Element: ',ExprTypeToString(SubExprType)]);
{$ENDIF}
if SubExprType.Desc=xtConstSet then
RaiseTermHasNoIterator(20170421211222,SubExprType);
ResolveExpr(SubExprType);
end;
else
DebugLn('TFindDeclarationTool.FindForInTypeAsString.ResolveExpr TermExprType=',
ExprTypeToString(SubExprType));
RaiseTermHasNoIterator(20170421211225,SubExprType);
end;
end;
var
TermExprType: TExpressionType;
OperatorExprType: TExpressionType;
begin
Result:='';
ExprType:=CleanExpressionType;
TermExprType:=CleanExpressionType;
Params.ContextNode:=CursorNode;
Params.Flags:=[fdfSearchInParentNodes,fdfSearchInAncestors,fdfSearchInHelpers,
fdfTopLvlResolving,fdfFunctionResult];
TermExprType:=FindExpressionResultType(Params,TermPos.StartPos,TermPos.EndPos);
{$IFDEF ShowForInEval}
DebugLn('TFindDeclarationTool.FindForInTypeAsString TermExprType=',
ExprTypeToString(TermExprType));
{$ENDIF}
// search operator enumerator
if FindOperatorEnumerator(CursorNode,TermExprType,foeEnumeratorCurrentExprType,
OperatorExprType)
then begin
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindForInTypeAsString Operator=',ExprTypeToString(OperatorExprType)]);
{$ENDIF}
ExprType:=OperatorExprType;
Result:=FindExprTypeAsString(ExprType,TermPos.StartPos);
exit;
end;
// convert to string
ResolveExpr(TermExprType);
{$IFDEF ShowExprEval}
DebugLn('TFindDeclarationTool.FindForInTypeAsString Result=',Result);
{$ENDIF}
end;
function TFindDeclarationTool.FindEnumeratorOfClass(ClassNode: TCodeTreeNode;
ExceptionOnNotFound: boolean; out ExprType: TExpressionType;
AliasType: PFindContext; ParentParams: TFindDeclarationParams): boolean;
var
Params: TFindDeclarationParams;
ProcTool: TFindDeclarationTool;
ProcNode: TCodeTreeNode;
EnumeratorContext: TFindContext;
PropTool: TFindDeclarationTool;
PropNode: TCodeTreeNode;
CurrentContext: TFindContext;
begin
Result:=false;
if AliasType<>nil then
AliasType^:=CleanFindContext;
ExprType:=CleanExpressionType;
Params:=TFindDeclarationParams.Create(ParentParams);
try
// search function 'GetEnumerator'
Params.ContextNode:=ClassNode;
Params.Flags:=[fdfSearchInAncestors,fdfSearchInHelpers];
Params.SetIdentifier(Self,'GetEnumerator',nil);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass searching GetEnumerator for ',ExtractClassName(ClassNode,false),' ...']);
{$ENDIF}
if not FindIdentifierInContext(Params) then begin
if ExceptionOnNotFound then begin
MoveCursorToCleanPos(ClassNode.StartPos);
RaiseException(20170421200638,ctsFunctionGetEnumeratorNotFoundInThisClass);
end else begin
{$IFDEF ShowForInEval}
debugln(['TFindDeclarationTool.FindEnumeratorOfClass GetEnumerator not found for ',ExtractClassName(ClassNode,false)]);
{$ENDIF}
exit;
end;
end;
ProcTool:=Params.NewCodeTool;
ProcNode:=Params.NewNode;
//DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass Proc']);
if (ProcNode=nil) or (ProcNode.Desc<>ctnProcedure) then begin
if ExceptionOnNotFound then begin
MoveCursorToCleanPos(ClassNode.StartPos);
RaiseException(20170421200640,ctsFunctionGetEnumeratorNotFoundInThisClass2);
end else begin
{$IFDEF ShowForInEval}
debugln(['TFindDeclarationTool.FindEnumeratorOfClass GetEnumerator is not a proc, class=',ExtractClassName(ClassNode,false)]);
{$ENDIF}
exit;
end;
end;
// search function type
Params.Clear;
Include(Params.Flags,fdfFunctionResult);
EnumeratorContext:=ProcTool.FindBaseTypeOfNode(Params,ProcNode);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass EnumeratorContext=',FindContextToString(EnumeratorContext)]);
{$ENDIF}
if (EnumeratorContext.Node=nil) or not(EnumeratorContext.Node.Desc in [ctnClass,ctnClassInterface,ctnRecordType])
then begin
if ExceptionOnNotFound then begin
ProcTool.MoveCursorToCleanPos(ProcNode.StartPos);
ProcTool.RaiseException(20170421200642,ctsResultTypeOfFunctionGetEnumeratorNotFound);
end else
exit;
end;
// search 'Current' in enumerator class
Params.Clear;
Params.ContextNode:=EnumeratorContext.Node;
Params.Flags:=[fdfSearchInAncestors];
if ExceptionOnNotFound then
Include(Params.Flags,fdfExceptionOnNotFound);
Params.SetIdentifier(EnumeratorContext.Tool,'Current',nil);
//DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass search current ...']);
if not EnumeratorContext.Tool.FindIdentifierInContext(Params) then begin
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass missing "current" in ',EnumeratorContext.Tool.ExtractClassName(EnumeratorContext.Node,false)]);
{$ENDIF}
exit;
end;
// check if "current" is a property
PropTool:=Params.NewCodeTool;
PropNode:=Params.NewNode;
//DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass PropNode=',PropNode.DescAsString]);
if (PropNode=nil) or (PropNode.Desc<>ctnProperty) then begin
if ExceptionOnNotFound then begin
EnumeratorContext.Tool.MoveCursorToCleanPos(EnumeratorContext.Node.StartPos);
RaiseException(20170421200644,ctsPropertyCurrentNotFound);
end else begin
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass "current" is not a property']);
{$ENDIF}
exit;
end;
end;
// search type of Current
Params.Clear;
if ExceptionOnNotFound then
Include(Params.Flags,fdfExceptionOnNotFound);
//DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass searching property type ...']);
CurrentContext:=PropTool.FindBaseTypeOfNode(Params,PropNode,AliasType);
ExprType:=CurrentContext.Tool.ConvertNodeToExpressionType(
CurrentContext.Node,Params,AliasType);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindEnumeratorOfClass exprtype of CURRENT: ExprType=',ExprTypeToString(ExprType),' Alias=',FindContextToString(AliasType)]);
{$ENDIF}
Result:=ExprType.Desc<>xtNone;
finally
Params.Free;
end;
end;
function TFindDeclarationTool.FindOperatorEnumerator(Node: TCodeTreeNode;
ExprType: TExpressionType; Need: TFindOperatorEnumerator; out
ResultExprType: TExpressionType): boolean;
// find a compatible operator overload for 'enumerator' with a parameter
// compatible to ExprType
// for example:
// operator enumerator (AList: TMyList): TMyListEnumerator;
var
Params: TFindDeclarationParams;
OperatorTool: TFindDeclarationTool;
OperatorNode: TCodeTreeNode;
ClassContext: TFindContext;
EnumeratorCurrentTool: TFindDeclarationTool;
EnumeratorCurrentNode: TCodeTreeNode;
begin
Result:=false;
ResultExprType:=CleanExpressionType;
Params:=TFindDeclarationParams.Create;
try
// search compatible operator enumerator
Params.ContextNode:=Node;
Params.Flags:=[fdfSearchInParentNodes];
Params.Data:=@ExprType;
Params.SetIdentifier(Self,'Enumerator',@CheckOperatorEnumerator);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator searching operator enumerator ...']);
{$ENDIF}
if not FindIdentifierInContext(Params) then begin
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator operator enumerator not found']);
{$ENDIF}
exit;
end;
// operator found
// now check if it is valid
OperatorTool:=Params.NewCodeTool;
OperatorNode:=Params.NewNode;
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator Operator="',OperatorTool.ExtractNode(OperatorNode,[]),'"']);
{$ENDIF}
if Need=foeProcNode then begin
ResultExprType.Desc:=xtContext;
ResultExprType.Context.Tool:=OperatorTool;
ResultExprType.Context.Node:=OperatorNode;
exit(true);
end;
// search class node
Params.Clear;
Params.Flags:=[fdfFunctionResult];
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator searching operator result object ...']);
{$ENDIF}
ClassContext:=OperatorTool.FindBaseTypeOfNode(Params,OperatorNode);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator ClassContext=',FindContextToString(ClassContext)]);
{$ENDIF}
case ClassContext.Node.Desc of
ctnClass,ctnObject,ctnRecordType,ctnClassInterface: ;
else
OperatorTool.MoveCursorToNodeStart(OperatorNode);
OperatorTool.RaiseException(20170421200650,'operator enumerator result type is not object');
end;
if Need=foeResultClassNode then begin
ResultExprType.Desc:=xtContext;
ResultExprType.Context:=ClassContext;
exit(true);
end;
// search property with modifier enumerator Current
Params.Clear;
Params.ContextNode:=ClassContext.Node;
Params.Flags:=[fdfSearchInAncestors,fdfCollect];
Params.SetIdentifier(Self,'',@CheckModifierEnumeratorCurrent);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator searching enumerator current ...']);
{$ENDIF}
if not ClassContext.Tool.FindIdentifierInContext(Params) then begin
ClassContext.Tool.MoveCursorToNodeStart(ClassContext.Node);
ClassContext.Tool.RaiseException(20170421200654,'enumerator ''current'' not found');
end;
EnumeratorCurrentTool:=Params.NewCodeTool;
EnumeratorCurrentNode:=Params.NewNode;
if Need=foeEnumeratorCurrentNode then begin
ResultExprType.Desc:=xtContext;
ResultExprType.Context.Tool:=EnumeratorCurrentTool;
ResultExprType.Context.Node:=EnumeratorCurrentNode;
exit(true);
end;
// search expression type of 'enumerator current'
Params.Clear;
Params.Flags:=[fdfFunctionResult];
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator searching enumerator current result ...']);
{$ENDIF}
ResultExprType:=EnumeratorCurrentTool.ConvertNodeToExpressionType(
EnumeratorCurrentNode,Params);
{$IFDEF ShowForInEval}
DebugLn(['TFindDeclarationTool.FindOperatorEnumerator enumerator current result=',ExprTypeToString(ResultExprType)]);
{$ENDIF}
Result:=true;
finally
Params.Free;
end;
end;
function TFindDeclarationTool.FindEnumerationTypeOfSetType(
SetTypeNode: TCodeTreeNode; out Context: TFindContext): boolean;
var
Params: TFindDeclarationParams;
p: LongInt;
begin
Result:=false;
if (SetTypeNode=nil) or (SetTypeNode.Desc<>ctnSetType) then exit;
MoveCursorToNodeStart(SetTypeNode);
ReadNextAtom; // set
if not UpAtomIs('SET') then exit;
ReadNextAtom; // of
if not UpAtomIs('OF') then exit;
ReadNextAtom;
if not IsIdentStartChar[Src[CurPos.StartPos]] then
// set of ()
exit;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions;
Params.ContextNode:=SetTypeNode;
p:=CurPos.StartPos;
Params.SetIdentifier(Self,@Src[p],nil);
if not FindIdentifierInContext(Params) then exit;
if (Params.NewNode=nil)
or (Params.NewNode.Desc<>ctnTypeDefinition)
or (Params.NewNode.FirstChild=nil)
or (Params.NewNode.FirstChild.Desc<>ctnEnumerationType) then begin
MoveCursorToCleanPos(p);
ReadNextAtom;
RaiseStringExpectedButAtomFound(20170421200656,ctsEnumerationType);
end;
Context.Tool:=Params.NewCodeTool;
Context.Node:=Params.NewNode;
Result:=true;
finally
Params.Free;
end;
end;
function TFindDeclarationTool.FindElementTypeOfArrayType(
ArrayNode: TCodeTreeNode; out ExprType: TExpressionType;
AliasType: PFindContext): boolean;
var
Params: TFindDeclarationParams;
p: LongInt;
begin
Result:=false;
ExprType:=CleanExpressionType;
AliasType^:=CleanFindContext;
if (ArrayNode=nil) then exit;
if (ArrayNode.Desc<>ctnOpenArrayType) and (ArrayNode.Desc<>ctnRangedArrayType)
then exit;
MoveCursorToNodeStart(ArrayNode);
ReadNextAtom; // array
if not UpAtomIs('ARRAY') then exit;
ReadNextAtom; // of
if CurPos.Flag=cafEdgedBracketOpen then begin
ReadTilBracketClose(true);
ReadNextAtom;
end;
if not UpAtomIs('OF') then exit;
ReadNextAtom;
if not AtomIsIdentifier then exit;
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions;
Params.ContextNode:=ArrayNode;
p:=CurPos.StartPos;
Params.SetIdentifier(Self,@Src[p],nil);
ExprType:=FindExpressionResultType(Params,p,-1,AliasType);
Result:=true;
finally
Params.Free;
end;
end;
function TFindDeclarationTool.CheckOperatorEnumerator(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
var
Node: TCodeTreeNode;
ExprType: TExpressionType;
Params2: TFindDeclarationParams;
begin
Result:=ifrProceedSearch;
{$IFDEF ShowExprEval}
DebugLn(['TFindDeclarationTool.CheckOperatorEnumerator ',FindContextToString(FoundContext)]);
{$ENDIF}
if not FoundContext.Tool.NodeIsOperator(FoundContext.Node) then exit;
FoundContext.Tool.BuildSubTreeForProcHead(FoundContext.Node);
Node:=FoundContext.Node.FirstChild;
if (Node=nil) or (Node.Desc<>ctnProcedureHead) then exit;
Node:=Node.FirstChild;
if (Node=nil) or (Node.Desc<>ctnParameterList) then exit;
Node:=Node.FirstChild;
if (Node=nil) then exit;
if Node.NextBrother<>nil then exit;
ExprType:=PExpressionType(Params.Data)^;
Params2:=TFindDeclarationParams.Create;
try
if IsCompatible(Node,ExprType,Params2)=tcIncompatible then exit;
finally
Params2.Free;
end;
{$IFDEF ShowExprEval}
DebugLn(['TFindDeclarationTool.CheckOperatorEnumerator FOUND ',FoundContext.Tool.ExtractNode(FoundContext.Node,[])]);
{$ENDIF}
Result:=ifrSuccess;
end;
function TFindDeclarationTool.CheckModifierEnumeratorCurrent(
Params: TFindDeclarationParams; const FoundContext: TFindContext
): TIdentifierFoundResult;
begin
Result:=ifrProceedSearch;
//DebugLn(['TFindDeclarationTool.CheckModifierEnumeratorCurrent ',FindContextToString(FoundContext)]);
case FoundContext.Node.Desc of
ctnProperty:
begin
if FoundContext.Tool.PropertyHasSpecifier(FoundContext.Node,'Enumerator',false)
then begin
FoundContext.Tool.ReadNextAtom;
if FoundContext.Tool.UpAtomIs('CURRENT') then
Result:=ifrSuccess;
end;
end;
end;
end;
function TFindDeclarationTool.IsTermEdgedBracket(TermPos: TAtomPosition; out
EdgedBracketsStartPos: integer): boolean;
{ allowed:
- at least one edged brackets
- identifiers
- functions
- operators: + and -
[a,b]+[c]-D()*inherited E
not allowed:
[]<>[]
}
var
Lvl: Integer;
EndPos: LongInt;
begin
Result:=false;
EdgedBracketsStartPos:=0;
EndPos:=TermPos.EndPos;
if EndPos>SrcLen then
EndPos:=SrcLen;
MoveCursorToCleanPos(TermPos.StartPos);
Lvl:=0;
repeat
ReadNextAtom;
if (CurPos.StartPos>=EndPos) then
break;
case CurPos.Flag of
cafRoundBracketOpen: ReadTilBracketClose(false);
cafEdgedBracketOpen:
begin
inc(Lvl);
if (Lvl=1) and (EdgedBracketsStartPos<1) then begin
if (LastAtoms.Count=0)
or LastAtomIs(-1,'+') or LastAtomIs(-1,'-') or LastAtomIs(-1,'*')
then
EdgedBracketsStartPos:=CurPos.StartPos;
end;
end;
cafEdgedBracketClose:
dec(Lvl);
cafWord:
;
cafComma:
if Lvl<1 then
break
else if Lvl>1 then
exit;
else
if AtomIsChar('+') or AtomIsChar('-') then begin
// allowed
end else begin
// not allowed
exit;
end;
end;
until false;
Result:=EdgedBracketsStartPos>0;
end;
function TFindDeclarationTool.IsTermNamedPointer(TermPos: TAtomPosition; out
ExprType: TExpressionType): boolean;
// check if TermPos is @Name and a pointer (= ^Name) can be found
var
SubExprType: TExpressionType;
Node: TCodeTreeNode;
PointerTool: TFindDeclarationTool;
Params: TFindDeclarationParams;
PointerNode: TCodeTreeNode;
begin
//debugln(['TFindDeclarationTool.IsTermNamedPointer ',CleanPosToStr(TermPos.StartPos,true),' Term={',copy(Src,TermPos.StartPos,TermPos.EndPos-TermPos.StartPos),'}']);
Result:=false;
MoveCursorToCleanPos(TermPos.StartPos);
ReadNextAtom;
if not AtomIsChar('@') then exit;
// a pointer
ExprType:=CleanExpressionType;
ExprType.Desc:=xtPointer;
Result:=true;
// try to find a name
ReadNextAtom;
if CurPos.StartPos>SrcLen then exit;
Node := FindDeepestNodeAtPos(CurPos.StartPos,true);
Params:=TFindDeclarationParams.Create(Self, Node);
try
SubExprType:=FindExpressionResultType(Params,CurPos.StartPos,-1);
finally
Params.Free;
end;
//debugln(['TFindDeclarationTool.IsTermNamedPointer SubExprType=',ExprTypeToString(SubExprType)]);
if SubExprType.Desc in xtAllPredefinedTypes then begin
ExprType.SubDesc:=SubExprType.Desc;
exit(true);
end else if (SubExprType.Desc=xtContext) then begin
Node:=SubExprType.Context.Node;
if (not (Node.Desc in AllIdentifierDefinitions))
and (Node.Parent<>nil) and (Node.Parent.Desc in AllIdentifierDefinitions) then
Node:=Node.Parent;
if (Node.Desc in AllIdentifierDefinitions) then begin
PointerTool:=SubExprType.Context.Tool;
PointerNode:=PointerTool.FindPointerOfIdentifier(Node);
if PointerNode<>nil then begin
ExprType:=CleanExpressionType;
ExprType.Desc:=xtContext;
ExprType.SubDesc:=xtNone;
ExprType.Context.Tool:=PointerTool;
ExprType.Context.Node:=PointerNode;
exit(true);
end;
end;
end;
end;
function TFindDeclarationTool.FindSetOfEnumerationType(EnumNode: TCodeTreeNode
): TCodeTreeNode;
// search in the same type section for a 'set of ' node
var
p: PChar;
function IsSetOfEnum(Node: TCodeTreeNode): boolean;
begin
Result:=false;
if (Node.Desc<>ctnTypeDefinition)
or (Node.FirstChild=nil)
or (Node.FirstChild.Desc<>ctnSetType) then exit;
MoveCursorToNodeStart(Node.FirstChild);
ReadNextAtom; // read set
if not UpAtomIs('SET') then exit;
ReadNextAtom; // read of
if not UpAtomIs('OF') then exit;
ReadNextAtom; // read of
if CurPos.Flag<>cafWord then exit;
Result:=CompareSrcIdentifiers(CurPos.StartPos,p);
end;
begin
{$IFDEF ShowExprEval}
debugln(['TFindDeclarationTool.FindSetOfEnumerationType ',EnumNode.DescAsString]);
{$ENDIF}
if EnumNode.Desc=ctnEnumIdentifier then EnumNode:=EnumNode.Parent;
if EnumNode.Desc=ctnEnumerationType then EnumNode:=EnumNode.Parent;
p:=@Src[EnumNode.StartPos];
Result:=EnumNode.Parent.FirstChild;
while Result<>nil do begin
if IsSetOfEnum(Result) then exit;
Result:=Result.NextBrother;
end;
end;
function TFindDeclarationTool.FindPointerOfIdentifier(
TypeNode: TCodeTreeNode): TCodeTreeNode;
// search in the same type section for a '^identifier' node
var
p: PChar;
function IsPointerOf(Node: TCodeTreeNode): boolean;
begin
Result:=false;
if (Node.Desc<>ctnTypeDefinition)
or (Node.FirstChild=nil)
or (Node.FirstChild.Desc<>ctnPointerType) then exit;
MoveCursorToNodeStart(Node.FirstChild);
ReadNextAtom; // read ^
if not AtomIsChar('^') then exit;
ReadNextAtom; // read identifier
if not AtomIsIdentifier then exit;
Result:=CompareSrcIdentifiers(CurPos.StartPos,p);
end;
begin
if TypeNode.Desc<>ctnTypeDefinition then exit(nil);
p:=@Src[TypeNode.StartPos];
Result:=TypeNode.Parent.FirstChild;
while Result<>nil do begin
if IsPointerOf(Result) then exit;
Result:=Result.NextBrother;
end;
end;
function TFindDeclarationTool.FindExprTypeAsString(
const ExprType: TExpressionType; TermCleanPos: integer;
AliasType: PFindContext): string;
procedure RaiseTermNotSimple(id: int64);
begin
if TermCleanPos<1 then
TermCleanPos:=1;
MoveCursorToCleanPos(TermCleanPos);
RaiseException(id,ctsTermNotSimple);
end;
var
FindContext: TFindContext;
ANode: TCodeTreeNode;
begin
{$IFDEF ShowExprEval}
DebugLn('TFindDeclarationTool.FindExprTypeAsString ExprType=',
ExprTypeToString(ExprType),' Alias=',FindContextToString(AliasType));
{$ENDIF}
Result:='';
if (AliasType<>nil) and (AliasType^.Node<>nil) then begin
case AliasType^.Node.Desc of
ctnTypeDefinition:
Result:=GetIdentifier(@AliasType^.Tool.Src[AliasType^.Node.StartPos]);
end;
if Result<>'' then exit;
end;
case ExprType.Desc of
xtNone:
RaiseTermNotSimple(20170421204649);
xtContext:
begin
FindContext:=ExprType.Context;
// ToDo: PPU, DCU
if FindContext.Node.Parent.Desc=ctnTypeDefinition then
FindContext.Node:=FindContext.Node.Parent;
case FindContext.Node.Desc of
ctnTypeDefinition:
Result:=GetIdentifier(
@FindContext.Tool.Src[FindContext.Node.StartPos]);
ctnVarDefinition,ctnConstDefinition:
begin
ANode:=FindContext.Tool.FindTypeNodeOfDefinition(FindContext.Node);
if (ANode=nil) or (ANode.Desc<>ctnIdentifier) then
RaiseTermNotSimple(20170421204653);
Result:=GetIdentifier(@FindContext.Tool.Src[ANode.StartPos]);
end;
ctnClass, ctnClassInterface, ctnDispinterface, ctnObject, ctnRecordType,
ctnClassHelper, ctnRecordHelper, ctnTypeHelper,
ctnObjCClass, ctnObjCCategory, ctnObjCProtocol, ctnCPPClass:
if (FindContext.Node.Parent<>nil)
and (FindContext.Node.Parent.Desc in [ctnTypeDefinition,ctnGenericType])
then
Result:=GetIdentifier(
@FindContext.Tool.Src[FindContext.Node.Parent.StartPos]);
ctnEnumIdentifier:
if (FindContext.Node.Parent<>nil)
and (FindContext.Node.Parent.Desc=ctnEnumerationType)
and (FindContext.Node.Parent.Parent<>nil)
and (FindContext.Node.Parent.Parent.Desc=ctnTypeDefinition)
then
Result:=GetIdentifier(
@FindContext.Tool.Src[FindContext.Node.Parent.Parent.StartPos]);
ctnEnumerationType:
if (FindContext.Node.Parent<>nil)
and (FindContext.Node.Parent.Desc=ctnTypeDefinition)
then
Result:=GetIdentifier(
@FindContext.Tool.Src[FindContext.Node.Parent.StartPos]);
ctnProperty,ctnGlobalProperty:
begin
FindContext.Tool.MoveCursorToPropType(FindContext.Node);
Result:=FindContext.Tool.GetAtom;
end;
ctnIdentifier:
begin
Result:=GetIdentifier(
@FindContext.Tool.Src[FindContext.Node.StartPos]);
end;
ctnProcedureHead:
begin
ANode:=GetProcResultNode(FindContext.Node);
if ANode<>nil then
Result:=FindContext.Tool.ExtractNode(ANode,[]);
end;
end;
if Result='' then begin
DebugLn('TFindDeclarationTool.FindExprTypeAsString ContextNode=',
FindContext.Node.DescAsString,' ',dbgsFC(FindContext));
RaiseTermNotSimple(20170421204655);
end;
end;
xtChar,
xtWideChar,
xtReal,
xtSingle,
xtDouble,
xtExtended,
xtCExtended,
xtCurrency,
xtComp,
xtInt64,
xtCardinal,
xtQWord,
xtPChar:
Result:=ExpressionTypeDescNames[ExprType.Desc];
xtPointer:
begin
case ExprType.SubDesc of
xtChar,
xtWideChar,
xtReal,
xtSingle,
xtDouble,
xtExtended,
xtCExtended,
xtCurrency,
xtComp,
xtInt64,
xtCardinal,
xtQWord,
xtBoolean,
xtByteBool,
xtWordBool,
xtLongBool,
xtQWordBool,
xtString,
xtAnsiString,
xtShortString,
xtWideString,
xtUnicodeString,
xtLongint,
xtLongWord,
xtWord,
xtSmallInt,
xtShortInt,
xtByte,
xtNativeInt,
xtNativeUInt:
Result:='P'+ExpressionTypeDescNames[ExprType.SubDesc];
else
Result:=ExpressionTypeDescNames[xtPointer];
end;
end;
xtFile,
xtText,
xtLongint,
xtLongWord,
xtSmallInt,
xtWord,
xtShortInt,
xtByte,
xtNativeInt,
xtNativeUInt:
Result:=ExpressionTypeDescNames[ExprType.Desc];
xtBoolean,
xtByteBool,
xtWordBool,
xtLongBool,
xtQWordBool:
Result:=ExpressionTypeDescNames[xtBoolean];
xtString,
xtAnsiString,
xtShortString:
Result:=ExpressionTypeDescNames[xtString];
xtWideString:
Result:=ExpressionTypeDescNames[ExprType.Desc];
xtConstOrdInteger:
Result:='Integer';
xtConstString:
Result:=ExpressionTypeDescNames[xtString];
xtConstReal:
Result:=ExpressionTypeDescNames[xtExtended];
xtConstSet:
begin
// eventually try to find the 'set of ' type
RaiseTermNotSimple(20170421204658);
end;
xtConstBoolean:
Result:=ExpressionTypeDescNames[xtBoolean];
xtJSValue:
Result:=ExpressionTypeDescNames[ExprType.Desc];
xtNil:
RaiseTermNotSimple(20170421204702);
else
DebugLn('TCodeCompletionCodeTool.FindExprTypeAsString ExprType=',
ExprTypeToString(ExprType),' Alias=',FindContextToString(AliasType));
RaiseTermNotSimple(20170421204705);
end;
end;
function TFindDeclarationTool.FindExtendedExprOfHelper(HelperNode: TCodeTreeNode
): TExpressionType;
// returns the expression type of the extended class/type of a "helper for"
var
ForNode: TCodeTreeNode;
Params: TFindDeclarationParams;
begin
case HelperNode.Desc of
ctnClassHelper,ctnRecordHelper,ctnTypeHelper:
ForNode:=FindHelperForNode(HelperNode);
ctnObjCCategory:
ForNode:=FindInheritanceNode(HelperNode);
else
exit(CleanExpressionType);
end;
if Assigned(ForNode) and Assigned(ForNode.FirstChild) then
begin
Params:=TFindDeclarationParams.Create;
try
Params.Flags:=fdfDefaultForExpressions-[fdfSearchInHelpers,fdfSearchInAncestors]+[fdfTypeType];
Params.ContextNode:=ForNode;
Result:=FindExpressionTypeOfTerm(ForNode.FirstChild.StartPos,ForNode.FirstChild.EndPos,Params,false);
finally
Params.Free;
end;
end else
Result := CleanExpressionType;
end;
{ TFindDeclarationParams }
procedure TFindDeclarationParams.ClearFoundProc;
begin
if FoundProc=nil then exit;
//DebugLn(['TFindDeclarationParams.ClearFoundProc ',dbgs(FoundProc),' Saved=',FoundProc^.Owner<>nil]);
if FoundProc^.Owner=nil then
// the FoundProc is not saved
FreeFoundProc(FoundProc,true)
else if FoundProc^.Next<>nil then
// the FoundProc is saved (release the later FoundProcs,
// which are not needed any more)
FreeFoundProc(FoundProc^.Next,true)
else begin
// the FoundProc is owned, that means someo other function is reponsible for freeing it
end;
FoundProc:=nil;
end;
procedure TFindDeclarationParams.FreeFoundProc(aFoundProc: PFoundProc; FreeNext: boolean);
var
Next: PFoundProc;
begin
//DebugLn(['TFindDeclarationParams.FreeFoundProc ',dbgs(aFoundProc)]);
while aFoundProc<>nil do begin
if (aFoundProc^.Owner<>Self)
and ((FFoundProcStackFirst=aFoundProc)
or (aFoundProc^.Prior<>nil) or (aFoundProc^.Next<>nil))
then
raise Exception.Create('FoundProc is in list, but not owned');
if FreeNext then
Next:=aFoundProc^.Next
else
Next:=nil;
RemoveFoundProcFromList(aFoundProc);
with aFoundProc^ do begin
//DebugLn(['TFindDeclarationParams.FreeFoundProc ExprInputList=',dbgs(ExprInputList)]);
if ExprInputList<>nil then
FreeAndNil(ExprInputList);
//DebugLn(['TFindDeclarationParams.FreeFoundProc ParamCompatibilityList=',dbgs(ParamCompatibilityList)]);
if ParamCompatibilityList<>nil then begin
FreeMem(ParamCompatibilityList);
ParamCompatibilityList:=nil;
end;
CacheValid:=false;
end;
//DebugLn(['TFindDeclarationParams.FreeFoundProc Dispose ',dbgs(aFoundProc)]);
Dispose(aFoundProc);
aFoundProc:=Next;
end;
end;
procedure TFindDeclarationParams.RemoveFoundProcFromList(aFoundProc: PFoundProc);
begin
//DebugLn(['TFindDeclarationParams.RemoveFoundProcFromList ',dbgs(aFoundProc)]);
if aFoundProc^.Owner<>Self then exit;
if FFoundProcStackFirst=aFoundProc then
FFoundProcStackFirst:=aFoundProc^.Next;
if FFoundProcStackLast=aFoundProc then
FFoundProcStackLast:=aFoundProc^.Next;
with aFoundProc^ do begin
if Next<>nil then
Next^.Prior:=Prior;
if Prior<>nil then
Prior^.Next:=Next;
Prior:=nil;
Next:=nil;
Owner:=nil;
end;
end;
constructor TFindDeclarationParams.Create(ParentParams: TFindDeclarationParams);
begin
inherited Create;
Clear;
Parent:=ParentParams;
end;
constructor TFindDeclarationParams.Create(Tool: TFindDeclarationTool;
AContextNode: TCodeTreeNode);
begin
Create(nil);//helper list will be created
StartTool := Tool;
StartNode := AContextNode;
ContextNode := AContextNode;
{$IFDEF CheckNodeTool}
if (StartNode<>nil) and (StartNode.GetRoot<>StartTool.Tree.Root) then begin
debugln(['TFindDeclarationParams.Create Inconsistency']);
CTDumpStack;
raise Exception.Create('TFindDeclarationParams.Create StartNode does not belong to StartTool');
end;
{$ENDIF}
if (StartTool<>nil) and (StartNode<>nil) then
FNeedHelpers:=true;
end;
destructor TFindDeclarationParams.Destroy;
var
HelperKind: TFDHelpersListKind;
begin
Clear;
FreeFoundProc(FFoundProcStackFirst,true);
for HelperKind in TFDHelpersListKind do
if FFreeHelpers[HelperKind] then
FreeAndNil(FHelpers[HelperKind]);
inherited Destroy;
end;
procedure TFindDeclarationParams.Clear;
begin
ClearInput;
ClearFoundProc;
ClearResult(false);
OnTopLvlIdentifierFound:=nil;
end;
procedure TFindDeclarationParams.Save(out Input: TFindDeclarationInput);
begin
Input.Flags:=Flags;
Input.Identifier:=Identifier;
Input.ContextNode:=ContextNode;
Input.OnIdentifierFound:=OnIdentifierFound;
Input.IdentifierTool:=IdentifierTool;
Input.FoundProc:=FoundProc;
if (FoundProc<>nil) and (FoundProc^.Owner=nil) then begin
// add to list of saved FoundProcs
//DebugLn(['TFindDeclarationParams.Save ',dbgs(FoundProc)]);
FoundProc^.Prior:=FFoundProcStackLast;
if FFoundProcStackLast<>nil then
FFoundProcStackLast^.Next:=FoundProc;
FFoundProcStackLast:=FoundProc;
if FFoundProcStackFirst=nil then
FFoundProcStackFirst:=FoundProc;
FoundProc^.Owner:=Self;
end;
end;
procedure TFindDeclarationParams.Load(Input: TFindDeclarationInput;
FreeInput: boolean);
// set FreeInput to true, if the Input is not needed anymore and the dynamic
// data can be freed.
begin
Flags:=Input.Flags;
Identifier:=Input.Identifier;
ContextNode:=Input.ContextNode;
OnIdentifierFound:=Input.OnIdentifierFound;
IdentifierTool:=Input.IdentifierTool;
if FoundProc<>Input.FoundProc then begin
// clear current FoundProc
if FoundProc<>nil then
ClearFoundProc;
// use saved FoundProc
FoundProc:=Input.FoundProc;
// free all FoundProcs, that were saved later
if (FoundProc<>nil) then begin
FreeFoundProc(FoundProc^.Next,true);
if FreeInput then begin
Input.FoundProc:=nil;
RemoveFoundProcFromList(FoundProc);
end;
end;
end;
end;
procedure TFindDeclarationParams.ClearResult(CopyCacheFlags: boolean);
begin
NewPos.Code:=nil;
NewPos.X:=-1;
NewPos.Y:=-1;
NewTopLine:=-1;
NewNode:=nil;
NewCleanPos:=-1;
NewCodeTool:=nil;
NewFlags:=[];
if CopyCacheFlags and (fdfDoNotCache in Flags) then
Include(NewFlags,fodDoNotCache);
end;
procedure TFindDeclarationParams.SetResult(const AFindContext: TFindContext);
begin
ClearResult(true);
NewCodeTool:=AFindContext.Tool;
NewNode:=AFindContext.Node;
end;
procedure TFindDeclarationParams.SetResult(ANewCodeTool: TFindDeclarationTool;
ANewNode: TCodeTreeNode);
begin
ClearResult(true);
NewCodeTool:=ANewCodeTool;
NewNode:=ANewNode;
{$IFDEF CheckNodeTool}if NewCodeTool<>nil then NewCodeTool.CheckNodeTool(NewNode);{$ENDIF}
end;
procedure TFindDeclarationParams.SetResult(ANewCodeTool: TFindDeclarationTool;
ANewNode: TCodeTreeNode; ANewCleanPos: integer);
begin
ClearResult(true);
NewCodeTool:=ANewCodeTool;
NewNode:=ANewNode;
NewCleanPos:=ANewCleanPos;
{$IFDEF CheckNodeTool}if NewCodeTool<>nil then NewCodeTool.CheckNodeTool(NewNode);{$ENDIF}
end;
procedure TFindDeclarationParams.ConvertResultCleanPosToCaretPos;
begin
NewPos.Code:=nil;
if NewCodeTool<>nil then begin
if (NewCleanPos>=1) then
NewCodeTool.CleanPosToCaretAndTopLine(NewCleanPos,NewPos,NewTopLine)
else if (NewNode<>nil) then
NewCodeTool.CleanPosToCaretAndTopLine(NewNode.StartPos,NewPos,NewTopLine);
end;
end;
procedure TFindDeclarationParams.ClearInput;
begin
Flags:=[];
Identifier:=nil;
ContextNode:=nil;
OnIdentifierFound:=nil;
IdentifierTool:=nil;
end;
procedure TFindDeclarationParams.WriteDebugReport;
begin
DebugLn('TFindDeclarationParams.WriteDebugReport Self=',DbgS(Self));
// input parameters:
DebugLn(' Flags=',dbgs(Flags));
DebugLn(' Identifier=',GetIdentifier(Identifier));
if ContextNode<>nil then
DebugLn(' ContextNode=',ContextNode.DescAsString)
else
DebugLn(' ContextNode=nil');
if OnIdentifierFound<>nil then
DebugLn(' OnIdentifierFound=',TFindDeclarationTool(TMethod(OnIdentifierFound).Data).MainFilename);
if IdentifierTool<>nil then
DebugLn(' IdentifierTool=',IdentifierTool.MainFilename)
else
DebugLn(' IdentifierTool=nil');
if FoundProc<>nil then begin
if FoundProc^.Context.Node<>nil then
DebugLn(' FoundProc=',FoundProc^.Context.Tool.CleanPosToStr(FoundProc^.Context.Node.StartPos,true))
else
DebugLn(' FoundProc<>nil');
end;
// global params
if OnTopLvlIdentifierFound<>nil then
DebugLn(' OnTopLvlIdentifierFound=',TFindDeclarationTool(TMethod(OnTopLvlIdentifierFound).Code).MainFilename);
// results:
if NewNode<>nil then
DebugLn(' NewNode=',NewNode.DescAsString)
else
DebugLn(' NewNode=nil');
DebugLn(' NewCleanPos=',dbgs(NewCleanPos));
if NewCodeTool<>nil then begin
DebugLn(' NewCodeTool=',NewCodeTool.MainFilename,' at ',NewCodeTool.CleanPosToStr(NewCleanPos,false))
end else begin
DebugLn([' NewCodeTool=nil NewCleanPos=',NewCleanPos]);
end;
if NewPos.Code<>nil then
DebugLn([' NewPos=',NewPos.Code.Filename,' x=',NewPos.X,' y=',NewPos.Y,' topline=',NewTopLine])
else
DebugLn(' NewPos=nil');
DebugLn(' NewFlags=',dbgs(NewFlags));
DebugLn('');
end;
function TFindDeclarationParams.GetHelpers(HelperKind: TFDHelpersListKind;
CreateIfNotExists: boolean): TFDHelpersList;
begin
if Parent<>nil then
exit(Parent.GetHelpers(HelperKind,CreateIfNotExists));
if FNeedHelpers then
StartTool.FindHelpersInContext(Self); // beware: this calls GetHelpers
Result:=FHelpers[HelperKind];
if (Result=nil) and CreateIfNotExists then begin
Result:=TFDHelpersList.Create(HelperKind);
FHelpers[HelperKind]:=Result;
FFreeHelpers[HelperKind]:=true;
//if HelperKind=fdhlkDelphiHelper then
// debugln(['TFindDeclarationParams.GetHelpers Self=',dbgs(Pointer(Self)),' Helper=',dbgs(Pointer(FHelpers[HelperKind]))]);
end;
end;
procedure TFindDeclarationParams.SetIdentifier(
NewIdentifierTool: TFindDeclarationTool; NewIdentifier: PChar;
NewOnIdentifierFound: TOnIdentifierFound);
begin
Identifier:=NewIdentifier;
IdentifierTool:=NewIdentifierTool;
OnIdentifierFound:=NewOnIdentifierFound;
ClearFoundProc;
end;
procedure TFindDeclarationParams.SetFoundProc(
const ProcContext: TFindContext);
begin
//DebugLn(['TFindDeclarationParams.SetFirstFoundProc Old=',dbgs(FoundProc)]);
if FoundProc<>nil then
ClearFoundProc;
New(FoundProc);
//DebugLn(['TFindDeclarationParams.SetFirstFoundProc New=',dbgs(FoundProc)]);
FillChar(FoundProc^,SizeOf(TFoundProc),0);
FoundProc^.Context:=ProcContext;
end;
procedure TFindDeclarationParams.SetGenericParamValues(
SpecializeParamsTool: TFindDeclarationTool;
SpecializeNode: TCodeTreeNode);
begin
GenParams.ParamValuesTool := SpecializeParamsTool;
GenParams.SpecializeParamsNode := SpecializeNode.FirstChild.NextBrother;
end;
function TFindDeclarationParams.FindGenericParamType: Boolean;
var
i, n: integer;
GenParamType: TCodeTreeNode;
begin
// NewCodeTool, NewNode=GenericParamType
if not Assigned(NewCodeTool) or not Assigned(NewNode)
or not Assigned(GenParams.ParamValuesTool)
or not Assigned(GenParams.SpecializeParamsNode) then exit(false);
n:=0;
GenParamType:=NewNode;
while GenParamType<>nil do begin
GenParamType:=GenParamType.PriorBrother;
inc(n);
end;
with GenParams.ParamValuesTool do begin
MoveCursorToNodeStart(GenParams.SpecializeParamsNode);
ReadNextAtom;
// maybe all this syntax check is redundant
if not AtomIsChar('<') then
RaiseExceptionFmt(20170421200701,ctsStrExpectedButAtomFound,['<']);
ReadNextAtom;
if CurPos.Flag<>cafWord then
RaiseExceptionFmt(20170421200703,ctsIdentExpectedButAtomFound,[GetAtom]);
for i:=2 to n do begin
ReadNextAtom;
if AtomIsChar('>') then
RaiseException(20170421200705,ctsNotEnoughGenParams);
if not AtomIsChar(',') then
RaiseExceptionFmt(20170421200707,ctsStrExpectedButAtomFound,['>']);
ReadNextAtom;
if CurPos.Flag<>cafWord then
RaiseExceptionFmt(20170421200710,ctsIdentExpectedButAtomFound,[GetAtom]);
end;
Identifier:=@Src[CurPos.StartPos];
IdentifierTool:=GenParams.ParamValuesTool;
ContextNode:=GenParams.SpecializeParamsNode;
Result:=FindIdentifierInContext(Self);
end;
end;
procedure TFindDeclarationParams.AddOperandPart(aPart: string);
begin
FExtractedOperand := FExtractedOperand + aPart;
end;
procedure TFindDeclarationParams.ChangeFoundProc(
const ProcContext: TFindContext;
ProcCompatibility: TTypeCompatibility;
ParamCompatibilityList: TTypeCompatibilityList);
begin
FoundProc^.Context:=ProcContext;
FoundProc^.ProcCompatibility:=ProcCompatibility;
if (FoundProc^.ParamCompatibilityList<>ParamCompatibilityList) then begin
//DebugLn(['TFindDeclarationParams.ChangeFoundProc Old ParamCompatibilityList=',dbgs(FoundProc^.ParamCompatibilityList)]);
if (FoundProc^.ParamCompatibilityList<>nil) then
FreeMem(FoundProc^.ParamCompatibilityList);
FoundProc^.ParamCompatibilityList:=ParamCompatibilityList;
//DebugLn(['TFindDeclarationParams.ChangeFoundProc New ParamCompatibilityList=',dbgs(FoundProc^.ParamCompatibilityList)]);
end;
end;
function TFindDeclarationParams.IsFoundProcFinal: boolean;
begin
Result:=(FoundProc=nil)
or (FoundProc^.CacheValid and (FoundProc^.ProcCompatibility=tcExact));
end;
procedure TFindDeclarationParams.PrettifyResult;
begin
// adjust result for nicer position
if (NewNode<>nil) then begin
{$IFDEF CheckNodeTool}
if NewCodeTool<>nil then
NewCodeTool.CheckNodeTool(NewNode);
{$ENDIF}
case NewNode.Desc of
ctnProcedure:
if (NewNode.FirstChild<>nil)
and (NewNode.FirstChild.Desc=ctnProcedureHead) then begin
// Instead of jumping to the procedure keyword,
// jump to the procedure name
NewNode:=NewNode.FirstChild;
NewCleanPos:=NewNode.StartPos;
end;
ctnGenericType:
if (NewNode.FirstChild<>nil) then begin
// Instead of jumping to the generic keyword,
// jump to the name
NewNode:=NewNode.FirstChild;
NewCleanPos:=NewNode.StartPos;
end;
ctnProperty:
// jump to the name of the property
if NewCodeTool.MoveCursorToPropName(NewNode) then
NewCleanPos:=NewCodeTool.CurPos.StartPos;
end;
end;
end;
procedure TFindDeclarationParams.SetResult(
NodeCacheEntry: PCodeTreeNodeCacheEntry);
begin
ClearResult(true);
NewCodeTool:=TFindDeclarationTool(NodeCacheEntry^.NewTool);
NewNode:=NodeCacheEntry^.NewNode;
NewCleanPos:=NodeCacheEntry^.NewCleanPos;
end;
{ TExprTypeList }
destructor TExprTypeList.Destroy;
begin
if Items<>nil then begin
FreeMem(Items);
Freemem(AliasTypes);
end;
end;
function TExprTypeList.AsString: string;
var i: integer;
begin
Result:='';
for i:=0 to Count-1 do begin
Result:=Result+'{'+IntToStr(i)+'/'+IntToStr(Count)+':'+ExprTypeToString(Items[i])+'}'+LineEnding;
end;
end;
function TExprTypeList.CalcMemSize: PtrUInt;
begin
Result:=PtrUInt(InstanceSize)
+PtrUInt(FCapacity)*SizeOf(TExpressionType);
end;
procedure TExprTypeList.SetCapacity(const AValue: integer);
var NewSize, NewAliasSize: integer;
begin
if FCapacity=AValue then exit;
FCapacity:=AValue;
NewSize:=FCapacity*SizeOf(TExpressionType);
NewAliasSize:=FCapacity*SizeOf(TFindContext);
if Items=nil then begin
GetMem(Items,NewSize);
GetMem(AliasTypes,NewAliasSize);
end
else begin
ReAllocMem(Items,NewSize);
ReAllocMem(AliasTypes,NewAliasSize);
end;
if Count>Capacity then Count:=Capacity;
end;
procedure TExprTypeList.Grow;
begin
Capacity:=Capacity*2+4;
end;
procedure TExprTypeList.Add(const ExprType: TExpressionType);
begin
inc(Count);
if Count>Capacity then Grow;
Items[Count-1]:=ExprType;
AliasTypes[Count-1]:=CleanFindContext;
end;
procedure TExprTypeList.Add(const ExprType: TExpressionType;
const AliasType: TFindContext);
begin
inc(Count);
if Count>Capacity then Grow;
Items[Count-1]:=ExprType;
AliasTypes[Count-1]:=AliasType;
end;
procedure TExprTypeList.AddFirst(const ExprType: TExpressionType);
begin
inc(Count);
if Count>Capacity then Grow;
if Count>1 then
Move(Items[0],Items[1],SizeOf(TExpressionType)*(Count-1));
Items[0]:=ExprType;
end;
finalization
FreeAndNil(FBooleanTypesOrderList);
FreeAndNil(FIntegerTypesOrderList);
FreeAndNil(FRealTypesOrderList);
FreeAndNil(FStringTypesOrderList);
end.