lazarus/components/codetools/customcodetool.pas
vincents c1c08bbb99 replaced write by DbgOut
git-svn-id: trunk@6021 -
2004-09-17 20:30:13 +00:00

2474 lines
73 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
TCustomCodeTool is the ancestor class for code tools which parses code
beginning with the Main Source code. It can parse atoms, the smallest code
elements in source code, create new code tree nodes and provides several
useful functions for parsing and changing code.
}
unit CustomCodeTool;
{$ifdef FPC}{$mode objfpc}{$endif}{$H+}
interface
{$I codetools.inc}
{ $DEFINE ShowIgnoreError}
{$DEFINE ShowDirtySrc}
uses
{$IFDEF MEM_CHECK}
MemCheck,
{$ENDIF}
Classes, SysUtils, FileProcs, CodeToolsStrConsts, CodeTree, CodeAtom,
KeywordFuncLists, BasicCodeTools, LinkScanner, CodeCache, AVL_Tree;
const
CodeToolPhaseNone = 0;
CodeToolPhaseScan = 1;
CodeToolPhaseParse = 2;
CodeToolPhaseTool = 3; // or higher
type
TCustomCodeTool = class;
// types for errors
ECodeToolError = class(Exception)
Sender: TCustomCodeTool;
constructor Create(ASender: TCustomCodeTool; const AMessage: string);
end;
ECodeToolErrors = class of ECodeToolError;
ECodeToolFileNotFound = class(ECodeToolError)
Filename: string;
constructor Create(ASender: TCustomCodeTool;
const AMessage, AFilename: string);
end;
{ TDirtySource - class to store a dirty source }
TDirtySource = class
public
CursorPos: TCodeXYPosition;
Src: string;
GapSrc: string;
GapUpperSrc: string;
Code: TCodeBuffer;
Valid: boolean;
CurPos: TAtomPosition;
StartPos: integer;
GapStart: integer;
GapEnd: integer;
LockCount: integer;
Owner: TCustomCodeTool;
procedure BeginUpdate;
procedure EndUpdate;
procedure SetGap(const NewCursorPos: TCodeXYPosition;
NewDirtyStartPos, NewDirtyGapStart, NewDirtyGapEnd: integer);
constructor Create(TheOwner: TCustomCodeTool);
procedure Clear;
procedure SetCursorToIdentStartEndAtPosition;
function GetCursorSrcPos: PChar;
function IsPCharInSrc(p: PChar): boolean;
procedure MoveCursorToPos(APos: integer);
procedure MoveCursorToPos(APos: PChar);
end;
THybridCursorType = (
hcClean,
hcDirty
);
// types for user aborts
TOnParserProgress = function(Tool: TCustomCodeTool): boolean of object;
EParserAbort = class(ECodeToolError)
end;
{ TCustomCodeTool }
TCustomCodeTool = class(TObject)
private
FLastProgressPos: integer;
FLastScannerChangeStep: integer;
FScanner: TLinkScanner;
FOnGetGlobalWriteLockInfo: TOnGetWriteLockInfo;
FOnParserProgress: TOnParserProgress;
FOnSetGlobalWriteLock: TOnSetWriteLock;
protected
FIgnoreErrorAfter: TCodePosition;
KeyWordFuncList: TKeyWordFunctionList;
WordIsKeyWordFuncList: TKeyWordFunctionList;
FForceUpdateNeeded: boolean;
function DefaultKeyWordFunc: boolean;
procedure BuildDefaultKeyWordFunctions; virtual;
procedure SetScanner(NewScanner: TLinkScanner); virtual;
procedure DoDeleteNodes; virtual;
procedure RaiseIdentExpectedButAtomFound;
procedure RaiseBracketOpenExpectedButAtomFound;
procedure RaiseBracketCloseExpectedButAtomFound;
procedure SetIgnoreErrorAfter(const AValue: TCodePosition); virtual;
protected
LastErrorMessage: string;
LastErrorCurPos: TAtomPosition;
LastErrorPhase: integer;
LastErrorValid: boolean;
LastErrorBehindIgnorePosition: boolean;
LastErrorCheckedForIgnored: boolean;
CurrentPhase: integer;
procedure RaiseExceptionInstance(TheException: ECodeToolError); virtual;
procedure RaiseExceptionClass(const AMessage: string;
ExceptionClass: ECodeToolErrors); virtual;
procedure RaiseException(const AMessage: string); virtual;
procedure RaiseExceptionFmt(const AMessage: string;
const args : array of const);
procedure SaveRaiseException(const AMessage: string); virtual;
procedure SaveRaiseExceptionFmt(const AMessage: string;
const args : array of const);
procedure ClearLastError;
procedure RaiseLastError;
procedure DoProgress;
// dirty/dead source
procedure LoadDirtySource(const CursorPos: TCodeXYPosition);
public
Tree: TCodeTree;
// current Values, Position, Node ...
CurPos: TAtomPosition;
Src: string;
UpperSrc: string;
SrcLen: integer;
CurNode: TCodeTreeNode;
LastAtoms: TAtomRing;
NextPos: TAtomPosition;
CheckFilesOnDisk: boolean;
IndentSize: integer;
VisibleEditorLines: integer;
JumpCentered: boolean;
CursorBeyondEOL: boolean;
DirtySrc: TDirtySource;
HybridCursorType: THybridCursorType;
ErrorPosition: TCodeXYPosition;
property Scanner: TLinkScanner read FScanner write SetScanner;
function MainFilename: string;
function FindDeepestNodeAtPos(P: integer;
ExceptionOnNotFound: boolean): TCodeTreeNode;
function FindDeepestNodeAtPos(StartNode: TCodeTreeNode; P: integer;
ExceptionOnNotFound: boolean): TCodeTreeNode;
function CaretToCleanPos(Caret: TCodeXYPosition;
var CleanPos: integer): integer; // 0=valid CleanPos
//-1=CursorPos was skipped, CleanPos between two links
// 1=CursorPos beyond scanned code
//-2=X,Y beyond source
function CleanPosToCodePos(CleanPos: integer;
var CodePos:TCodePosition): boolean; // true=ok, false=invalid CleanPos
function CleanPosToCaret(CleanPos: integer;
var Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
function CleanPosToCaretAndTopLine(CleanPos: integer;
var Caret:TCodeXYPosition; var NewTopLine: integer): boolean; // true=ok, false=invalid CleanPos
procedure GetCleanPosInfo(CodePosInFront, CleanPos: integer;
ResolveComments: boolean; var SameArea: TAtomPosition);
procedure GetLineInfo(ACleanPos: integer;
var ALineStart, ALineEnd, AFirstAtomStart, ALastAtomEnd: integer);
function FindLineEndOrCodeAfterPosition(StartPos: integer): integer;
function FindLineEndOrCodeInFrontOfPosition(StartPos: integer): integer;
function FindLineEndOrCodeInFrontOfPosition(StartPos: integer;
StopAtDirectives: boolean): integer;
function FindFirstLineEndAfterInCode(StartPos: integer): integer;
function UpdateNeeded(OnlyInterfaceNeeded: boolean): boolean;
procedure BeginParsing(DeleteNodes, OnlyInterfaceNeeded: boolean); virtual;
procedure BeginParsingAndGetCleanPos(DeleteNodes,
OnlyInterfaceNeeded: boolean; CursorPos: TCodeXYPosition;
var CleanCursorPos: integer);
function IsDirtySrcValid: boolean;
function StringIsKeyWord(const Word: string): boolean;
// cursor moving
procedure MoveCursorToNodeStart(ANode: TCodeTreeNode);
procedure MoveCursorToCleanPos(ACleanPos: integer);
procedure MoveCursorToCleanPos(ACleanPos: PChar);
procedure MoveCursorToNearestAtom(ACleanPos: integer);
function IsPCharInSrc(ACleanPos: PChar): boolean;
procedure MoveHybridCursorToPos(DirtyPos: PChar);
function GetHybridCursorStart: integer;
// read atoms
procedure ReadNextAtom;
procedure UndoReadNextAtom;
procedure ReadPriorAtom;
// read blocks
function ReadTilBracketClose(ExceptionOnNotFound: boolean): boolean;
function ReadBackTilBracketOpen(ExceptionOnNotFound: boolean): boolean;
procedure ReadTillCommentEnd;
function AtomIs(const AnAtom: shortstring): boolean;
function UpAtomIs(const AnAtom: shortstring): boolean;
function ReadNextAtomIs(const AnAtom: shortstring): boolean;
function ReadNextUpAtomIs(const AnAtom: shortstring): boolean;
function ReadNextAtomIsChar(const c: char): boolean;
function AtomIsChar(const c: char): boolean;
function AtomIsKeyWord: boolean;
function AtomIsNumber: boolean;
function AtomIsRealNumber: boolean;
function AtomIsStringConstant: boolean;
function AtomIsCharConstant: boolean;
function AtomIsIdentifier(ExceptionOnNotFound: boolean): boolean;
function LastAtomIs(BackIndex: integer;
const AnAtom: shortstring): boolean; // 0=current, 1=prior current, ...
function LastUpAtomIs(BackIndex: integer;
const AnAtom: shortstring): boolean; // 0=current, 1=prior current, ...
function GetAtom: string;
function GetUpAtom: string;
function GetAtom(const Atom: TAtomPosition): string;
function GetUpAtom(const Atom: TAtomPosition): string;
function FreeUpAtomIs(const FreeAtomPos: TAtomPosition;
const AnAtom: shortstring): boolean;
function CompareNodeIdentChars(ANode: TCodeTreeNode;
const AnUpperIdent: string): integer;
function CompareSrcIdentifiers(
CleanStartPos1, CleanStartPos2: integer): boolean;
function CompareSrcIdentifier(CleanStartPos: integer;
const Identifier: string): boolean;
function CompareSrcIdentifiers(Identifier1, Identifier2: PChar): boolean;
function CompareSrcIdentifiers(CleanStartPos: integer;
AnIdentifier: PChar): boolean;
function ExtractIdentifier(CleanStartPos: integer): string;
procedure CreateChildNode;
procedure EndChildNode;
function DoAtom: boolean; virtual;
procedure ActivateGlobalWriteLock; virtual;
procedure DeactivateGlobalWriteLock; virtual;
property OnGetGlobalWriteLockInfo: TOnGetWriteLockInfo
read FOnGetGlobalWriteLockInfo write FOnGetGlobalWriteLockInfo;
property OnSetGlobalWriteLock: TOnSetWriteLock
read FOnSetGlobalWriteLock write FOnSetGlobalWriteLock;
property IgnoreErrorAfter: TCodePosition
read FIgnoreErrorAfter write SetIgnoreErrorAfter;
procedure ClearIgnoreErrorAfter;
function IgnoreErrAfterPositionIsInFrontOfLastErrMessage: boolean;
function IgnoreErrorAfterValid: boolean;
function IgnoreErrorAfterCleanedPos: integer;
function LastErrorsInFrontOfCleanedPos(ACleanedPos: integer): boolean;
procedure RaiseLastErrorIfInFrontOfCleanedPos(ACleanedPos: integer);
property OnParserProgress: TOnParserProgress
read FOnParserProgress write FOnParserProgress;
procedure Clear; virtual;
function NodeDescToStr(Desc: integer): string;
function NodeSubDescToStr(Desc, SubDesc: integer): string;
function ConsistencyCheck: integer; virtual; // 0 = ok
procedure WriteDebugTreeReport;
constructor Create;
destructor Destroy; override;
end;
var
RaiseUnhandableExceptions: boolean;
implementation
{ TCustomCodeTool }
constructor TCustomCodeTool.Create;
begin
inherited Create;
Tree:=TCodeTree.Create;
KeyWordFuncList:=TKeyWordFunctionList.Create;
BuildDefaultKeyWordFunctions;
WordIsKeyWordFuncList:=WordIsKeyWord;
LastAtoms:=TAtomRing.Create;
IndentSize:=2;
VisibleEditorLines:=20;
CursorBeyondEOL:=true;
FForceUpdateNeeded:=false;
Clear;
end;
destructor TCustomCodeTool.Destroy;
begin
Clear;
LastAtoms.Free;
Tree.Free;
KeyWordFuncList.Free;
DirtySrc.Free;
DirtySrc:=nil;
inherited Destroy;
end;
procedure TCustomCodeTool.Clear;
begin
if Tree<>nil then DoDeleteNodes;
CurPos:=StartAtomPosition;
LastAtoms.Clear;
NextPos.StartPos:=-1;
ClearLastError;
end;
procedure TCustomCodeTool.RaiseException(const AMessage: string);
begin
RaiseExceptionClass(AMessage,ECodeToolError);
end;
procedure TCustomCodeTool.RaiseExceptionFmt(const AMessage: string;
const args: array of const);
begin
RaiseException(Format(AMessage,args));
end;
procedure TCustomCodeTool.SaveRaiseException(const AMessage: string);
begin
LastErrorMessage:=AMessage;
LastErrorCurPos:=CurPos;
LastErrorPhase:=CurrentPhase;
LastErrorValid:=true;
RaiseException(AMessage);
end;
procedure TCustomCodeTool.SaveRaiseExceptionFmt(const AMessage: string;
const args: array of const);
begin
SaveRaiseException(Format(AMessage,args));
end;
procedure TCustomCodeTool.ClearLastError;
begin
LastErrorPhase:=CodeToolPhaseNone;
LastErrorValid:=false;
LastErrorCheckedForIgnored:=false;
end;
procedure TCustomCodeTool.RaiseLastError;
begin
CurPos:=LastErrorCurPos;
CurNode:=nil;
CurrentPhase:=LastErrorPhase;
SaveRaiseException(LastErrorMessage);
end;
procedure TCustomCodeTool.DoProgress;
begin
// Check every 10.000 chars
if (FLastProgressPos-CurPos.StartPos)<10000 then exit;
FLastProgressPos:=CurPos.StartPos;
if Assigned(OnParserProgress) then begin
if OnParserProgress(Self) then exit;
// abort the parsing process
// mark parsing results as invalid
FForceUpdateNeeded:=true;
// raise the abort exception to stop the parsing
RaiseExceptionClass('Abort',EParserAbort);
end;
end;
procedure TCustomCodeTool.LoadDirtySource(const CursorPos: TCodeXYPosition);
// - create the DirtySrc object
// - load the unparsed source at CursorPos
// - find the gap bounds
var
NewDirtyStartPos: integer;
NewDirtyGapStart: integer;
NewDirtyGapEnd: integer;
CursorInLink: Boolean;
BestLinkIndex: Integer;
BestLink: TSourceLink;
begin
DebugLn('TCustomCodeTool.LoadDirtySource X=',dbgs(CursorPos.X),' Y=',dbgs(CursorPos.Y),
' ',ExtractFilename(CursorPos.Code.Filename));
if DirtySrc=nil then DirtySrc:=TDirtySource.Create(Self);
CursorPos.Code.LineColToPosition(CursorPos.Y,CursorPos.X,NewDirtyStartPos);
if NewDirtyStartPos<1 then
RaiseCatchableException('NewDirtyStartPos<1');
CursorInLink:=false;
BestLinkIndex:=Scanner.LinkIndexNearCursorPos(NewDirtyStartPos,
CursorPos.Code,CursorInLink);
if BestLinkIndex<0 then
RaiseCatchableException('BestLinkIndex<0');
if CursorInLink then
RaiseCatchableException('CursorInLink');
BestLink:=Scanner.Links[BestLinkIndex];
NewDirtyGapStart:=BestLink.SrcPos+Scanner.LinkSize(BestLinkIndex);
if BestLinkIndex<Scanner.LinkCount then
NewDirtyGapEnd:=Scanner.Links[BestLinkIndex+1].SrcPos
else
NewDirtyGapEnd:=CursorPos.Code.SourceLength;
DirtySrc.SetGap(CursorPos,NewDirtyStartPos,NewDirtyGapStart,NewDirtyGapEnd);
end;
procedure TCustomCodeTool.SetScanner(NewScanner: TLinkScanner);
begin
if NewScanner=FScanner then exit;
LastErrorCheckedForIgnored:=false;
Clear;
FScanner:=NewScanner; begin
if Scanner<>nil then
FLastScannerChangeStep:=Scanner.ChangeStep;
Scanner.SetIgnoreErrorAfter(IgnoreErrorAfter.P,IgnoreErrorAfter.Code);
end;
FForceUpdateNeeded:=true;
end;
function TCustomCodeTool.NodeDescToStr(Desc: integer): string;
begin
Result:=NodeDescriptionAsString(TCodeTreeNodeDesc(Desc));
end;
function TCustomCodeTool.NodeSubDescToStr(Desc, SubDesc: integer): string;
begin
if SubDesc<>0 then
Result:=Format(ctsUnknownSubDescriptor,[IntToStr(SubDesc)])
else
Result:='';
case Desc of
ctnProcedure:
begin
if (SubDesc and ctnsForwardDeclaration)>0 then Result:=ctsForward;
end;
ctnProcedureHead, ctnBeginBlock:
begin
if (SubDesc and ctnsNeedJITParsing)>0 then Result:=ctsUnparsed;
end;
ctnClass:
begin
Result:='';
if (SubDesc and ctnsForwardDeclaration)>0 then Result:=ctsForward;
if (SubDesc and ctnsNeedJITParsing)>0 then Result:=Result+ctsUnparsed;
end;
end;
end;
function TCustomCodeTool.AtomIs(const AnAtom: shortstring): boolean;
var AnAtomLen,i : integer;
begin
Result:=false;
if (CurPos.StartPos<=SrcLen) and (CurPos.EndPos<=SrcLen+1)
and (CurPos.StartPos>=1) then begin
AnAtomLen:=length(AnAtom);
if AnAtomLen=CurPos.EndPos-CurPos.StartPos then begin
for i:=1 to AnAtomLen do
if AnAtom[i]<>Src[CurPos.StartPos-1+i] then exit;
Result:=true;
end;
end;
end;
function TCustomCodeTool.UpAtomIs(const AnAtom: shortstring): boolean;
var AnAtomLen,i : integer;
begin
Result:=false;
if (CurPos.EndPos<=SrcLen+1) and (CurPos.StartPos>=1) then begin
AnAtomLen:=length(AnAtom);
if AnAtomLen=CurPos.EndPos-CurPos.StartPos then begin
for i:=1 to AnAtomLen do
if AnAtom[i]<>UpperSrc[CurPos.StartPos-1+i] then exit;
Result:=true;
end;
end;
end;
function TCustomCodeTool.ReadNextAtomIs(const AnAtom: shortstring): boolean;
begin
ReadNextAtom;
Result:=AtomIs(AnAtom);
end;
function TCustomCodeTool.ReadNextAtomIsChar(const c: char): boolean;
begin
ReadNextAtom;
Result:=AtomIsChar(c);
end;
function TCustomCodeTool.ReadNextUpAtomIs(const AnAtom: shortstring): boolean;
begin
ReadNextAtom;
Result:=UpAtomIs(AnAtom);
end;
function TCustomCodeTool.CompareNodeIdentChars(ANode: TCodeTreeNode;
const AnUpperIdent: string): integer;
var AnIdentLen, i, NodeSrcLen, MinLen, p: integer;
begin
if (ANode.StartPos<=SrcLen) and (ANode.EndPos<=SrcLen+1)
and (ANode.StartPos>=1) then begin
AnIdentLen:=length(AnUpperIdent);
NodeSrcLen:=ANode.EndPos-ANode.StartPos;
if AnIdentLen<NodeSrcLen then
MinLen:=AnIdentLen
else
MinLen:=NodeSrcLen;
i:=1;
p:=ANode.StartPos-1+i;
while (i<=MinLen) and (IsIdentChar[Src[p]]) do begin
if AnUpperIdent[i]<>UpperSrc[p] then begin
// identifiers different in one letter
if UpperSrc[p]>AnUpperIdent[i] then
Result:=-1
else
Result:=1;
exit;
end;
inc(i);
inc(p);
end;
if (i>MinLen) and (i>AnIdentLen) then begin
// node is longer than AnUpperIdent
if (i>NodeSrcLen) or (not IsIdentChar[Src[p]]) then
// node identifier is equal to AnUpperIdent
Result:=0
else
// node Identifier is longer than AnUpperIdent
Result:=-1;
end else
// node identifier is shorter than AnUpperIdent
Result:=1
end else
Result:=1;
end;
function TCustomCodeTool.CompareSrcIdentifiers(
CleanStartPos1, CleanStartPos2: integer): boolean;
begin
Result:=(CleanStartPos1>=1) and (CleanStartPos1<=SrcLen)
and (CleanStartPos2>=1) and (CleanStartPos2<=SrcLen);
if not Result then exit;
while (CleanStartPos1<=SrcLen) and (IsIdentChar[Src[CleanStartPos1]]) do begin
if (UpperSrc[CleanStartPos1]<>UpperSrc[CleanStartPos2]) then begin
Result:=false;
exit;
end;
inc(CleanStartPos1);
inc(CleanStartPos2);
end;
Result:=(CleanStartPos2>SrcLen) or (not IsIdentChar[Src[CleanStartPos2]]);
end;
function TCustomCodeTool.AtomIsChar(const c: char): boolean;
begin
Result:=(CurPos.StartPos<=SrcLen)
and (CurPos.EndPos-CurPos.StartPos=1)
and (Src[CurPos.StartPos]=c);
end;
function TCustomCodeTool.AtomIsKeyWord: boolean;
begin
Result:=(CurPos.StartPos<=SrcLen)
and (IsIdentStartChar[UpperSrc[CurPos.StartPos]])
and (WordIsKeyWordFuncList.DoItUpperCase(UpperSrc,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos));
end;
function TCustomCodeTool.AtomIsIdentifier(ExceptionOnNotFound: boolean):boolean;
procedure RaiseIdentExpectedButEOFFound;
begin
SaveRaiseException(ctsIdentExpectedButEOFFound);
end;
begin
if CurPos.StartPos<=SrcLen then begin
if IsIdentStartChar[UpperSrc[CurPos.StartPos]] then begin
if not WordIsKeyWordFuncList.DoItUpperCase(UpperSrc,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos) then
Result:=true
else begin
if ExceptionOnNotFound then
RaiseIdentExpectedButAtomFound
else
Result:=false;
end;
end else begin
if ExceptionOnNotFound then
RaiseIdentExpectedButAtomFound
else
Result:=false;
end;
end else begin
if ExceptionOnNotFound then
RaiseIdentExpectedButEOFFound
else
Result:=false;
end;
end;
function TCustomCodeTool.AtomIsNumber: boolean;
begin
Result:=(CurPos.StartPos<=SrcLen)
and (Src[CurPos.StartPos] in ['0'..'9','%','$']);
end;
function TCustomCodeTool.AtomIsRealNumber: boolean;
var i: integer;
begin
Result:=false;
i:=CurPos.StartPos;
if (i<=SrcLen) and (IsNumberChar[Src[i]]) then begin
while (i<=SrcLen) and (IsNumberChar[Src[i]]) do
inc(i);
if (i<SrcLen) and (Src[i]='.') and (IsNumberChar[Src[i+1]]) then
Result:=true;
end;
end;
function TCustomCodeTool.AtomIsStringConstant: boolean;
begin
Result:=(CurPos.StartPos<=SrcLen)
and (Src[CurPos.StartPos] in ['''','#']);
end;
function TCustomCodeTool.AtomIsCharConstant: boolean;
var i: integer;
begin
Result:=false;
if (CurPos.StartPos<=SrcLen) then begin
case Src[CurPos.StartPos] of
'#':
begin
i:=CurPos.StartPos+1;
if (i<=SrcLen) then begin
if IsNumberChar[Src[i]] then begin
// decimal
while (i<=SrcLen) and (IsNumberChar[Src[i]]) do
inc(i);
end else if Src[i]='$' then begin
// hexadecimal
while (i<=SrcLen) and (IsHexNumberChar[Src[i]]) do
inc(i);
end;
if (i<=SrcLen)
and (not (Src[i] in ['''','#'])) then
Result:=true;
end;
end;
'''':
begin
if (CurPos.StartPos+2<=SrcLen) and (Src[CurPos.StartPos+1]<>'''')
and (Src[CurPos.StartPos+2]='''') then begin
// a single char
if (CurPos.StartPos+2<SrcLen)
and (not (Src[CurPos.StartPos+3] in ['''','#'])) then
Result:=true;
end;
end;
end;
end;
end;
function TCustomCodeTool.LastAtomIs(BackIndex: integer;
const AnAtom: shortstring): boolean;
var ap: TAtomPosition;
AnAtomLen: integer;
i: integer;
begin
Result:=false;
if (BackIndex>=0) and (BackIndex<LastAtoms.Count) then begin
ap:=LastAtoms.GetValueAt(BackIndex);
Result:=false;
if (ap.StartPos<SrcLen) and (ap.EndPos<=SrcLen+1)
and (ap.StartPos>=1) then begin
AnAtomLen:=length(AnAtom);
if AnAtomLen=ap.EndPos-ap.StartPos then begin
for i:=1 to AnAtomLen do
if AnAtom[i]<>Src[ap.StartPos-1+i] then exit;
Result:=true;
end;
end;
end;
end;
function TCustomCodeTool.LastUpAtomIs(BackIndex: integer;
const AnAtom: shortstring): boolean;
var ap: TAtomPosition;
AnAtomLen: integer;
i: integer;
begin
Result:=false;
if (BackIndex>=0) and (BackIndex<LastAtoms.Count) then begin
ap:=LastAtoms.GetValueAt(BackIndex);
Result:=false;
if (ap.StartPos<SrcLen) and (ap.EndPos<=SrcLen+1)
and (ap.StartPos>=1) then begin
AnAtomLen:=length(AnAtom);
if AnAtomLen=ap.EndPos-ap.StartPos then begin
for i:=1 to AnAtomLen do
if AnAtom[i]<>UpperSrc[ap.StartPos-1+i] then exit;
Result:=true;
end;
end;
end;
end;
function TCustomCodeTool.GetAtom: string;
begin
Result:=copy(Src,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos);
end;
function TCustomCodeTool.GetUpAtom: string;
begin
Result:=copy(UpperSrc,CurPos.StartPos,CurPos.EndPos-CurPos.StartPos);
end;
function TCustomCodeTool.GetAtom(const Atom: TAtomPosition): string;
begin
Result:=copy(Src,Atom.StartPos,Atom.EndPos-Atom.StartPos);
end;
function TCustomCodeTool.GetUpAtom(const Atom: TAtomPosition): string;
begin
Result:=copy(UpperSrc,Atom.StartPos,Atom.EndPos-Atom.StartPos);
end;
function TCustomCodeTool.FreeUpAtomIs(const FreeAtomPos: TAtomPosition;
const AnAtom: shortstring): boolean;
var AnAtomLen,i : integer;
begin
Result:=false;
if (FreeAtomPos.StartPos<SrcLen) and (FreeAtomPos.EndPos<=SrcLen+1)
and (FreeAtomPos.StartPos>=1) then begin
AnAtomLen:=length(AnAtom);
if AnAtomLen=FreeAtomPos.EndPos-FreeAtomPos.StartPos then begin
for i:=1 to AnAtomLen do
if AnAtom[i]<>UpperSrc[FreeAtomPos.StartPos-1+i] then exit;
Result:=true;
end;
end;
end;
procedure TCustomCodeTool.ReadNextAtom;
var c1, c2: char;
CommentLvl: integer;
begin
{$IFOPT R+}{$DEFINE RangeChecking}{$ENDIF}
{$R-}
if (CurPos.StartPos<CurPos.EndPos) then
LastAtoms.Add(CurPos);
if NextPos.StartPos<1 then begin
CurPos.StartPos:=CurPos.EndPos;
CurPos.Flag:=cafNone;
if CurPos.StartPos>SrcLen then
exit;
// Skip all spaces and comments
CommentLvl:=0;
while true do begin
case Src[CurPos.StartPos] of
#0:
if CurPos.StartPos>SrcLen then
break
else
inc(CurPos.StartPos);
#1..#32:
inc(CurPos.StartPos);
'{': // pascal comment
begin
CommentLvl:=1;
inc(CurPos.StartPos);
while true do begin
case Src[CurPos.StartPos] of
#0: if CurPos.StartPos>SrcLen then break;
'{': if Scanner.NestedComments then inc(CommentLvl);
'}':
begin
dec(CommentLvl);
if CommentLvl=0 then break;
end;
end;
inc(CurPos.StartPos);
end;
inc(CurPos.StartPos);
end;
'/': // Delphi comment
if (Src[CurPos.StartPos+1]<>'/') then begin
break;
end else begin
inc(CurPos.StartPos,2);
while (not (Src[CurPos.StartPos] in [#10,#13,#0])) do
inc(CurPos.StartPos);
inc(CurPos.StartPos);
if (CurPos.StartPos<=SrcLen) and (Src[CurPos.StartPos] in [#10,#13])
and (Src[CurPos.StartPos-1]<>Src[CurPos.StartPos]) then
inc(CurPos.StartPos);
end;
'(': // old turbo pascal comment
if (Src[CurPos.StartPos+1]<>'*') then begin
break;
end else begin
inc(CurPos.StartPos,3);
while (CurPos.StartPos<=SrcLen)
and ((Src[CurPos.StartPos-1]<>'*') or (Src[CurPos.StartPos]<>')')) do
inc(CurPos.StartPos);
inc(CurPos.StartPos);
end;
else
break;
end;
end;
CurPos.EndPos:=CurPos.StartPos;
// read atom
c1:=UpperSrc[CurPos.EndPos];
case c1 of
#0: ;
'_','A'..'Z':
begin
inc(CurPos.EndPos);
while (IsIdentChar[UpperSrc[CurPos.EndPos]]) do
inc(CurPos.EndPos);
CurPos.Flag:=cafWord;
case c1 of
'B':
if (CurPos.EndPos-CurPos.StartPos=5)
and UpAtomIs('BEGIN')
then
CurPos.Flag:=cafBegin;
'E':
if (CurPos.EndPos-CurPos.StartPos=3)
and (UpperSrc[CurPos.StartPos+1]='N')
and (UpperSrc[CurPos.StartPos+2]='D')
then
CurPos.Flag:=cafEnd;
'R':
if (CurPos.EndPos-CurPos.StartPos=6)
and UpAtomIs('RECORD')
then
CurPos.Flag:=cafRecord;
end;
end;
'''','#':
begin
while (CurPos.EndPos<=SrcLen) do begin
case (Src[CurPos.EndPos]) of
'#':
begin
inc(CurPos.EndPos);
if (CurPos.EndPos<=SrcLen) then begin
if (IsNumberChar[Src[CurPos.EndPos]]) then begin
// decimal
repeat
inc(CurPos.EndPos);
until (CurPos.EndPos>SrcLen)
or (not IsNumberChar[Src[CurPos.EndPos]]);
end else if Src[CurPos.EndPos]='$' then begin
// hexadecimal
repeat
inc(CurPos.EndPos);
until (CurPos.EndPos>SrcLen)
or (not IsHexNumberChar[Src[CurPos.EndPos]]);
end;
end;
end;
'''':
begin
inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen) do begin
case Src[CurPos.EndPos] of
'''':
begin
inc(CurPos.EndPos);
break;
end;
#10,#13:
break;
else
inc(CurPos.EndPos);
end;
end;
end;
else
break;
end;
end;
end;
'0'..'9':
begin
inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen) and (IsNumberChar[Src[CurPos.EndPos]]) do
inc(CurPos.EndPos);
if (CurPos.EndPos<SrcLen)
and (Src[CurPos.EndPos]='.')
and (IsAfterFloatPointChar[Src[CurPos.EndPos+1]])
then begin
// real type number
inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen) and (IsNumberChar[Src[CurPos.EndPos]])
do
inc(CurPos.EndPos);
if (CurPos.EndPos<=SrcLen) and (UpperSrc[CurPos.EndPos]='E') then
begin
// read exponent
inc(CurPos.EndPos);
if (CurPos.EndPos<=SrcLen) and (Src[CurPos.EndPos] in ['-','+'])
then inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen) and (IsNumberChar[Src[CurPos.EndPos]])
do
inc(CurPos.EndPos);
end;
end;
end;
'%':
begin
inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen) and (Src[CurPos.EndPos] in ['0'..'1']) do
inc(CurPos.EndPos);
end;
'$':
begin
inc(CurPos.EndPos);
while (CurPos.EndPos<=SrcLen)
and (IsHexNumberChar[UpperSrc[CurPos.EndPos]]) do
inc(CurPos.EndPos);
end;
';':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafSemicolon;
end;
',':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafComma;
end;
'=':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafEqual;
end;
'(':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafRoundBracketOpen;
end;
')':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafRoundBracketClose;
end;
'[':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafEdgedBracketOpen;
end;
']':
begin
inc(CurPos.EndPos);
CurPos.Flag:=cafEdgedBracketClose;
end;
':':
begin
inc(CurPos.EndPos);
if (Src[CurPos.EndPos]<>'=') then begin
CurPos.Flag:=cafColon;
end else begin
// :=
inc(CurPos.EndPos);
end;
end;
'.':
begin
inc(CurPos.EndPos);
if (Src[CurPos.EndPos]<>'.') then begin
CurPos.Flag:=cafPoint;
end else begin
// ..
inc(CurPos.EndPos);
end;
end;
else
inc(CurPos.EndPos);
c2:=Src[CurPos.EndPos];
// test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, ><
if ((c2='=') and (IsEqualOperatorStartChar[c1]))
or ((c1='<') and (c2='>')) // not equal
or ((c1='>') and (c2='<'))
or ((c1='.') and (c2='.')) // subrange
or ((c1='*') and (c2='*'))
then inc(CurPos.EndPos);
if ((c1='@') and (c2='@')) then begin
// @@ label
repeat
inc(CurPos.EndPos);
until (CurPos.EndPos>SrcLen) or (not IsIdentChar[Src[CurPos.EndPos]]);
end;
end;
end else begin
CurPos:=NextPos;
NextPos.StartPos:=-1;
exit;
end;
{$IFDEF RangeChecking}{$R+}{$ENDIF}
end;
procedure TCustomCodeTool.ReadPriorAtom;
var
CommentLvl, PrePos, OldPrePos: integer;
IsStringConstant: boolean;
procedure ReadStringConstantBackward;
var PrePos: integer;
begin
while (CurPos.StartPos>1) do begin
case UpperSrc[CurPos.StartPos-1] of
'''':
begin
dec(CurPos.StartPos);
repeat
dec(CurPos.StartPos);
until (CurPos.StartPos<1) or (Src[CurPos.StartPos]='''');
end;
'0'..'9','A'..'Z':
begin
// test if char constant
PrePos:=CurPos.StartPos-1;
while (PrePos>1) and (IsHexNumberChar[Src[PrePos]]) do
dec(PrePos);
if (PrePos<1) then break;
if (Src[PrePos]='$') then begin
dec(PrePos);
if (PrePos<1) then break;
end;
if (Src[PrePos]='#') then
CurPos.StartPos:=PrePos
else
break;
end;
else
break;
end;
end;
end;
procedure ReadBackTilCodeLineEnd;
begin
dec(CurPos.StartPos);
if (CurPos.StartPos>=1) and (Src[CurPos.StartPos] in [#10,#13])
and (Src[CurPos.StartPos+1]<>Src[CurPos.StartPos]) then
dec(CurPos.StartPos);
// read backwards till line start
PrePos:=CurPos.StartPos;
while (PrePos>=1) and (not (Src[PrePos] in [#10,#13])) do
dec(PrePos);
// read line forward to find out,
// if line ends in comment or string constant
IsStringConstant:=false;
repeat
inc(PrePos);
case Src[PrePos] of
'/':
if Src[PrePos+1]='/' then begin
// this was a delphi comment -> skip comment
CurPos.StartPos:=PrePos-1;
break;
end;
'{':
begin
// skip pascal comment
CommentLvl:=1;
inc(PrePos);
while (PrePos<=CurPos.StartPos) do begin
case Src[PrePos] of
'{': if Scanner.NestedComments then inc(CommentLvl);
'}':
begin
dec(CommentLvl);
if CommentLvl=0 then break;
end;
end;
inc(PrePos);
end;
end;
'(':
begin
if Src[PrePos+1]='*' then begin
// skip turbo pascal comment
inc(PrePos,2);
while (PrePos<CurPos.StartPos)
and ((Src[PrePos]<>'*') or (Src[PrePos+1]<>')')) do
inc(PrePos);
inc(PrePos);
end;
end;
'''':
begin
// a string constant -> skip it
OldPrePos:=PrePos;
repeat
inc(PrePos);
case Src[PrePos] of
'''':
break;
#10,#13:
begin
// string constant right border is the line end
// -> last atom of line found
IsStringConstant:=true;
break;
end;
end;
until false;
if IsStringConstant then break;
end;
#10,#13:
// no comment and no string constant found
break;
end;
until PrePos>=CurPos.StartPos;
end;
type
TNumberType = (ntDecimal, ntHexadecimal, ntBinary, ntIdentifier,
ntCharConstant, ntFloat, ntFloatWithExponent);
TNumberTypes = set of TNumberType;
const
AllNumberTypes: TNumberTypes = [ntDecimal, ntHexadecimal, ntBinary,
ntIdentifier, ntCharConstant, ntFloat, ntFloatWithExponent];
var c1, c2: char;
ForbiddenNumberTypes: TNumberTypes;
begin
if LastAtoms.Count>0 then begin
UndoReadNextAtom;
exit;
end;
NextPos:=CurPos;
// Skip all spaces and comments
CommentLvl:=0;
dec(CurPos.StartPos);
CurPos.Flag:=cafNone;
IsStringConstant:=false;
OldPrePos:=0;
while CurPos.StartPos>=1 do begin
if IsCommentEndChar[Src[CurPos.StartPos]] then begin
case Src[CurPos.StartPos] of
'}': // pascal comment
begin
CommentLvl:=1;
dec(CurPos.StartPos);
while (CurPos.StartPos>=1) and (CommentLvl>0) do begin
case Src[CurPos.StartPos] of
'}': if Scanner.NestedComments then inc(CommentLvl);
'{': dec(CommentLvl);
end;
dec(CurPos.StartPos);
end;
end;
#10,#13: // possible Delphi comment
ReadBackTilCodeLineEnd;
')': // old turbo pascal comment
if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='*') then begin
dec(CurPos.StartPos,3);
while (CurPos.StartPos>=1)
and ((Src[CurPos.StartPos]<>'(') or (Src[CurPos.StartPos+1]<>'*')) do
dec(CurPos.StartPos);
dec(CurPos.StartPos);
end else
break;
end;
end else if IsSpaceChar[Src[CurPos.StartPos]] then begin
repeat
dec(CurPos.StartPos);
until (CurPos.StartPos<1) or (Src[CurPos.StartPos] in [#10,#13])
or (not (IsSpaceChar[Src[CurPos.StartPos]]));
end else begin
break;
end;
end;
// CurPos.StartPos now points to the last char of the prior atom
CurPos.EndPos:=CurPos.StartPos+1;
if CurPos.StartPos<1 then
exit;
// read atom
if IsStringConstant then begin
CurPos.StartPos:=OldPrePos;
if (CurPos.StartPos>1) and (Src[CurPos.StartPos-1]='''') then begin
ReadStringConstantBackward;
end;
exit;
end;
c2:=UpperSrc[CurPos.StartPos];
case c2 of
'_','A'..'Z':
begin
// identifier or keyword or hexnumber
while (CurPos.StartPos>1) do begin
if (IsIdentChar[UpperSrc[CurPos.StartPos-1]]) then
dec(CurPos.StartPos)
else begin
case Src[CurPos.StartPos-1] of
'@':
// assembler label
if (CurPos.StartPos>2)
and (Src[CurPos.StartPos-2]='@') then
dec(CurPos.StartPos,2);
'$':
// hex number
dec(CurPos.StartPos);
else
WordToAtomFlag.DoItUpperCase(UpperSrc,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos);
CurPos.Flag:=WordToAtomFlag.Flag;
if CurPos.Flag=cafNone then
CurPos.Flag:=cafWord;
end;
break;
end;
end;
end;
'''':
begin
inc(CurPos.StartPos);
ReadStringConstantBackward;
end;
'0'..'9':
begin
// could be a decimal number, an identifier, a hex number,
// a binary number, a char constant, a float, a float with exponent
ForbiddenNumberTypes:=[];
while true do begin
case UpperSrc[CurPos.StartPos] of
'0'..'1':
;
'2'..'9':
ForbiddenNumberTypes:=ForbiddenNumberTypes+[ntBinary];
'A'..'D','F':
ForbiddenNumberTypes:=ForbiddenNumberTypes
+[ntBinary,ntDecimal,ntCharConstant,ntFloat,ntFloatWithExponent];
'E':
ForbiddenNumberTypes:=ForbiddenNumberTypes
+[ntBinary,ntDecimal,ntCharConstant,ntFloat];
'G'..'Z','_':
ForbiddenNumberTypes:=AllNumberTypes-[ntIdentifier];
'.':
begin
// could be the point of a float
if (ntFloat in ForbiddenNumberTypes)
or (CurPos.StartPos<=1) or (Src[CurPos.StartPos-1]='.') then begin
inc(CurPos.StartPos);
break;
end;
dec(CurPos.StartPos);
// this was the part of a float after the point
// -> read decimal in front
ForbiddenNumberTypes:=AllNumberTypes-[ntDecimal];
end;
'+','-':
begin
// could be part of an exponent
if (ntFloatWithExponent in ForbiddenNumberTypes)
or (CurPos.StartPos<=1) or (UpperSrc[CurPos.StartPos-1]<>'E') then
begin
inc(CurPos.StartPos);
break;
end;
dec(CurPos.StartPos);
// this was the exponent of a float -> read the float
ForbiddenNumberTypes:=AllNumberTypes-[ntFloat];
end;
'#': // char constant found
begin
if (ntCharConstant in ForbiddenNumberTypes) then
inc(CurPos.StartPos);
ReadStringConstantBackward;
break;
end;
'$':
begin
// hexadecimal number found
if (ntHexadecimal in ForbiddenNumberTypes) then
inc(CurPos.StartPos);
break;
end;
'%':
begin
// binary number found
if (ntBinary in ForbiddenNumberTypes) then
inc(CurPos.StartPos);
break;
end;
'@':
begin
if (CurPos.StartPos=1) or (Src[CurPos.StartPos-1]<>'@')
or (([ntIdentifier,ntDecimal]*ForbiddenNumberTypes)=[]) then
// atom start found
inc(CurPos.StartPos)
else
// label found
dec(CurPos.StartPos);
break;
end;
else
begin
inc(CurPos.StartPos);
break;
end;
end;
if ForbiddenNumberTypes=AllNumberTypes then begin
inc(CurPos.StartPos);
break;
end;
if CurPos.StartPos<=1 then break;
dec(CurPos.StartPos);
end;
if IsIdentStartChar[Src[CurPos.StartPos]] then begin
// it is an identifier
WordToAtomFlag.DoItUpperCase(UpperSrc,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos);
CurPos.Flag:=WordToAtomFlag.Flag;
if CurPos.Flag=cafNone then
CurPos.Flag:=cafWord;
end;
end;
';': CurPos.Flag:=cafSemicolon;
':': CurPos.Flag:=cafColon;
',': CurPos.Flag:=cafComma;
'(': CurPos.Flag:=cafRoundBracketOpen;
')': CurPos.Flag:=cafRoundBracketClose;
'[': CurPos.Flag:=cafEdgedBracketOpen;
']': CurPos.Flag:=cafEdgedBracketClose;
else
begin
case c2 of
'=': CurPos.Flag:=cafEqual;
'.': CurPos.Flag:=cafPoint;
end;
if CurPos.StartPos>1 then begin
c1:=Src[CurPos.StartPos-1];
// test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, ><
if ((c2='=') and (IsEqualOperatorStartChar[c1]))
or ((c1='<') and (c2='>'))
or ((c1='>') and (c2='<'))
or ((c1='.') and (c2='.'))
or ((c1='*') and (c2='*'))
or ((c1='@') and (c2='@'))
then begin
dec(CurPos.StartPos);
CurPos.Flag:=cafNone;
end;
end;
end;
end;
end;
procedure TCustomCodeTool.UndoReadNextAtom;
procedure RaiseUndoImpossible;
begin
RaiseException('TCustomCodeTool.UndoReadNextAtom impossible');
end;
begin
if LastAtoms.Count>0 then begin
NextPos:=CurPos;
CurPos:=LastAtoms.GetValueAt(0);
LastAtoms.UndoLastAdd;
end else
RaiseUndoImpossible;
end;
function TCustomCodeTool.ReadTilBracketClose(
ExceptionOnNotFound: boolean): boolean;
// reads code brackets (not comment brackets)
// after call cursor is on the closing bracket
var CloseBracket, AntiCloseBracket: TCommonAtomFlag;
Start: TAtomPosition;
procedure RaiseBracketNotFound;
begin
if CloseBracket=cafRoundBracketOpen then
SaveRaiseExceptionFmt(ctsBracketNotFound,['('])
else
SaveRaiseExceptionFmt(ctsBracketNotFound,['[']);
end;
begin
Result:=false;
if (Curpos.Flag=cafRoundBracketOpen) then begin
CloseBracket:=cafRoundBracketClose;
AntiCloseBracket:=cafEdgedBracketClose;
end else if (Curpos.Flag=cafEdgedBracketOpen) then begin
CloseBracket:=cafEdgedBracketClose;
AntiCloseBracket:=cafRoundBracketClose;
end else begin
if ExceptionOnNotFound then
RaiseBracketOpenExpectedButAtomFound;
exit;
end;
Start:=CurPos;
repeat
ReadNextAtom;
if (CurPos.Flag=CloseBracket) then break;
if (CurPos.StartPos>SrcLen)
or (CurPos.Flag in [cafEnd,cafRecord,AntiCloseBracket])
then begin
CurPos:=Start;
if ExceptionOnNotFound then begin
RaiseBracketNotFound;
end;
exit;
end;
if (CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen]) then begin
if not ReadTilBracketClose(ExceptionOnNotFound) then exit;
end;
until false;
Result:=true;
end;
function TCustomCodeTool.ReadBackTilBracketOpen(
ExceptionOnNotFound: boolean): boolean;
// reads code brackets (not comment brackets)
var OpenBracket, AntiOpenBracket: TCommonAtomFlag;
Start: TAtomPosition;
procedure RaiseBracketNotFound;
begin
if OpenBracket=cafRoundBracketOpen then
SaveRaiseExceptionFmt(ctsBracketNotFound,['('])
else
SaveRaiseExceptionFmt(ctsBracketNotFound,['[']);
end;
begin
Result:=false;
if (CurPos.Flag=cafRoundBracketClose) then begin
OpenBracket:=cafRoundBracketOpen;
AntiOpenBracket:=cafEdgedBracketOpen;
end else if (CurPos.Flag=cafEdgedBracketClose) then begin
OpenBracket:=cafEdgedBracketOpen;
AntiOpenBracket:=cafRoundBracketOpen;
end else begin
if ExceptionOnNotFound then
RaiseBracketCloseExpectedButAtomFound;
exit;
end;
Start:=CurPos;
repeat
ReadPriorAtom;
if (CurPos.Flag=OpenBracket) then break;
if (CurPos.StartPos<1)
or (CurPos.Flag in [AntiOpenBracket,cafEND,cafBegin]) then begin
CurPos:=Start;
if ExceptionOnNotFound then
RaiseBracketNotFound;
exit;
end;
if CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose] then begin
if not ReadBackTilBracketOpen(ExceptionOnNotFound) then exit;
end;
until false;
Result:=true;
end;
procedure TCustomCodeTool.ReadTillCommentEnd;
var
CommentLvl: Integer;
begin
{$IFOPT R+}{$DEFINE RangeChecking}{$ENDIF}
{$R-}
case Src[CurPos.StartPos] of
'{': // pascal comment
begin
CommentLvl:=1;
inc(CurPos.StartPos);
while true do begin
case Src[CurPos.StartPos] of
#0: if CurPos.StartPos>SrcLen then break;
'{': if Scanner.NestedComments then inc(CommentLvl);
'}':
begin
dec(CommentLvl);
if CommentLvl=0 then break;
end;
end;
inc(CurPos.StartPos);
end;
inc(CurPos.StartPos);
end;
'/': // Delphi comment
if (Src[CurPos.StartPos+1]='/') then begin
inc(CurPos.StartPos,2);
while (not (Src[CurPos.StartPos] in [#10,#13,#0])) do
inc(CurPos.StartPos);
inc(CurPos.StartPos);
if (CurPos.StartPos<=SrcLen) and (Src[CurPos.StartPos] in [#10,#13])
and (Src[CurPos.StartPos-1]<>Src[CurPos.StartPos]) then
inc(CurPos.StartPos);
end;
'(': // old turbo pascal comment
if (Src[CurPos.StartPos+1]='*') then begin
inc(CurPos.StartPos,3);
while (CurPos.StartPos<=SrcLen)
and ((Src[CurPos.StartPos-1]<>'*') or (Src[CurPos.StartPos]<>')')) do
inc(CurPos.StartPos);
inc(CurPos.StartPos);
end;
end;
{$IFDEF RangeChecking}{$R+}{$ENDIF}
end;
procedure TCustomCodeTool.BeginParsing(DeleteNodes,
OnlyInterfaceNeeded: boolean);
begin
// scan
FLastProgressPos:=0;
CurrentPhase:=CodeToolPhaseScan;
try
Scanner.Scan(OnlyInterfaceNeeded,CheckFilesOnDisk);
// update scanned code
if FLastScannerChangeStep<>Scanner.ChangeStep then begin
// code has changed
ClearLastError;
FLastScannerChangeStep:=Scanner.ChangeStep;
Src:=Scanner.CleanedSrc;
UpperSrc:=UpperCaseStr(Src);
SrcLen:=length(Src);
FForceUpdateNeeded:=true;
if DeleteNodes then DoDeleteNodes;
DirtySrc.Free;
DirtySrc:=nil;
end else begin
if LastErrorPhase=CodeToolPhaseScan then
RaiseLastError;
end;
// init parsing values
CurPos:=StartAtomPosition;
LastAtoms.Clear;
NextPos.StartPos:=-1;
CurNode:=nil;
finally
CurrentPhase:=CodeToolPhaseNone;
end;
end;
procedure TCustomCodeTool.BeginParsingAndGetCleanPos(DeleteNodes,
OnlyInterfaceNeeded: boolean; CursorPos: TCodeXYPosition;
var CleanCursorPos: integer);
var Dummy: integer;
begin
if UpdateNeeded(OnlyInterfaceNeeded) then
BeginParsing(DeleteNodes,OnlyInterfaceNeeded);
// find the CursorPos in cleaned source
Dummy:=CaretToCleanPos(CursorPos, CleanCursorPos);
if (Dummy<>0) and (Dummy<>-1) then
RaiseException(ctsCursorPosOutsideOfCode);
end;
function TCustomCodeTool.IsDirtySrcValid: boolean;
begin
Result:=(DirtySrc<>nil) and (DirtySrc.Code<>nil);
end;
function TCustomCodeTool.IgnoreErrAfterPositionIsInFrontOfLastErrMessage: boolean;
var
IgnoreErrorAfterCleanPos: integer;
begin
//DebugLn('TCustomCodeTool.IgnoreErrAfterPositionIsInFrontOfLastErrMessage ',
// ' LastErrorCheckedForIgnored=',LastErrorCheckedForIgnored,
// ' LastErrorBehindIgnorePosition=',LastErrorBehindIgnorePosition);
if LastErrorCheckedForIgnored then begin
Result:=LastErrorBehindIgnorePosition;
end else begin
if (Scanner<>nil) then begin
IgnoreErrorAfterCleanPos:=Scanner.IgnoreErrorAfterCleanedPos;
//DebugLn(' IgnoreErrorAfterCleanPos=',IgnoreErrorAfterCleanPos,
// ' LastErrorCurPos.EndPos=',LastErrorCurPos.EndPos,
// ' LastErrorPhase>CodeToolPhaseParse=',LastErrorPhase>CodeToolPhaseParse);
if IgnoreErrorAfterCleanPos>0 then begin
// ignore position in scanned code
// -> check if last error behind ignore position
if (not LastErrorValid)
or (IgnoreErrorAfterCleanPos<=LastErrorCurPos.EndPos) then
Result:=true
else
Result:=false;
end else
Result:=false;
end else
Result:=false;
LastErrorBehindIgnorePosition:=Result;
LastErrorCheckedForIgnored:=true;
end;
{$IFDEF ShowIgnoreErrorAfter}
DebugLn('TCustomCodeTool.IgnoreErrAfterPositionIsInFrontOfLastErrMessage ',Result);
{$ENDIF}
end;
function TCustomCodeTool.IgnoreErrorAfterValid: boolean;
begin
Result:=(Scanner<>nil) and (Scanner.IgnoreErrorAfterValid);
{$IFDEF ShowIgnoreErrorAfter}
DebugLn('TCustomCodeTool.IgnoreErrorAfterValid ',Result);
{$ENDIF}
end;
function TCustomCodeTool.IgnoreErrorAfterCleanedPos: integer;
begin
if Scanner<>nil then
Result:=Scanner.IgnoreErrorAfterCleanedPos
else
Result:=-1;
{$IFDEF ShowIgnoreErrorAfter}
DebugLn('TCustomCodeTool.IgnoreErrorAfterCleanedPos ',Result);
{$ENDIF}
end;
function TCustomCodeTool.LastErrorsInFrontOfCleanedPos(ACleanedPos: integer
): boolean;
begin
if (Scanner<>nil) and Scanner.LastErrorsInFrontOfCleanedPos(ACleanedPos)
then
Result:=true
else if (LastErrorValid)
and (LastErrorCurPos.EndPos<=ACleanedPos) then
Result:=true
else
Result:=false;
{$IFDEF ShowIgnoreErrorAfter}
DebugLn('TCustomCodeTool.LastErrorsInFrontOfCleanedPos ACleanedPos=',ACleanedPos,
Result);
{$ENDIF}
end;
procedure TCustomCodeTool.RaiseLastErrorIfInFrontOfCleanedPos(
ACleanedPos: integer);
begin
{$IFDEF ShowIgnoreErrorAfter}
DebugLn('TCustomCodeTool.RaiseLastErrorIfInFrontOfCleanedPos A ACleanedPos=',ACleanedPos,
' ');
{$ENDIF}
if Scanner<>nil then Scanner.RaiseLastErrorIfInFrontOfCleanedPos(ACleanedPos);
//DebugLn('TCustomCodeTool.RaiseLastErrorIfInFrontOfCleanedPos B ',LastErrorPhase<CodeToolPhaseTool,' ',LastErrorCurPos.EndPos);
if LastErrorValid
and (LastErrorCurPos.EndPos<=ACleanedPos) then
RaiseLastError;
//DebugLn('TCustomCodeTool.RaiseLastErrorIfInFrontOfCleanedPos END ');
end;
function TCustomCodeTool.StringIsKeyWord(const Word: string): boolean;
begin
Result:=(Word<>'') and IsIdentStartChar[Word[1]]
and WordIsKeyWordFuncList.DoItUpperCase(Word,1,length(Word));
end;
procedure TCustomCodeTool.MoveCursorToNodeStart(ANode: TCodeTreeNode);
begin
MoveCursorToCleanPos(ANode.StartPos);
CurNode:=ANode;
end;
procedure TCustomCodeTool.MoveCursorToCleanPos(ACleanPos: integer);
begin
CurPos.StartPos:=ACleanPos;
CurPos.EndPos:=ACleanPos;
CurPos.Flag:=cafNone;
LastAtoms.Clear;
NextPos.StartPos:=-1;
CurNode:=nil;
HybridCursorType:=hcClean;
end;
procedure TCustomCodeTool.MoveCursorToCleanPos(ACleanPos: PChar);
procedure RaiseSrcEmpty;
begin
RaiseException('[TCustomCodeTool.MoveCursorToCleanPos - PChar] Src empty');
end;
procedure RaiseNotInSrc;
begin
RaiseException('[TCustomCodeTool.MoveCursorToCleanPos - PChar] '
+'CleanPos not in Src');
end;
var NewPos: integer;
begin
if Src='' then
RaiseSrcEmpty;
NewPos:=Integer(ACleanPos)-Integer(@Src[1])+1;
if (NewPos<1) or (NewPos>SrcLen) then
RaiseNotInSrc;
MoveCursorToCleanPos(NewPos);
end;
procedure TCustomCodeTool.MoveCursorToNearestAtom(ACleanPos: integer);
var
ANode: TCodeTreeNode;
BestPos: Integer;
begin
ANode:=FindDeepestNodeAtPos(ACleanPos,true);
if ANode=nil then
RaiseException('TCustomCodeTool.MoveCursorToNearestAtom internal error');
MoveCursorToNodeStart(ANode);
BestPos:=CurPos.StartPos;
while (CurPos.StartPos<=ACleanPos) and (CurPos.StartPos<=SrcLen) do begin
BestPos:=CurPos.StartPos;
ReadNextAtom;
end;
MoveCursorToCleanPos(BestPos);
end;
function TCustomCodeTool.IsPCharInSrc(ACleanPos: PChar): boolean;
var NewPos: integer;
begin
Result:=false;
if Src='' then exit;
NewPos:=Integer(ACleanPos)-Integer(@Src[1])+1;
if (NewPos<1) or (NewPos>SrcLen) then exit;
Result:=true;
end;
procedure TCustomCodeTool.MoveHybridCursorToPos(DirtyPos: PChar);
begin
if IsDirtySrcValid and (not IsPCharInSrc(DirtyPos)) then begin
DirtySrc.MoveCursorToPos(DirtyPos);
HybridCursorType:=hcDirty;
end else
MoveCursorToCleanPos(DirtyPos);
end;
function TCustomCodeTool.GetHybridCursorStart: integer;
begin
if HybridCursorType=hcDirty then
Result:=DirtySrc.CurPos.StartPos
else
Result:=CurPos.StartPos;
end;
procedure TCustomCodeTool.CreateChildNode;
var NewNode: TCodeTreeNode;
begin
NewNode:=NodeMemManager.NewNode;
Tree.AddNodeAsLastChild(CurNode,NewNode);
CurNode:=NewNode;
CurNode.StartPos:=CurPos.StartPos;
end;
procedure TCustomCodeTool.EndChildNode;
begin
CurNode:=CurNode.Parent;
end;
procedure TCustomCodeTool.BuildDefaultKeyWordFunctions;
begin
KeyWordFuncList.Clear;
KeyWordFuncList.DefaultKeyWordFunction:=
{$ifdef FPC}@{$endif}DefaultKeyWordFunc;
end;
function TCustomCodeTool.DoAtom: boolean;
begin
if (CurPos.StartPos>SrcLen) or (CurPos.EndPos<=CurPos.StartPos) then
Result:=false
else if IsIdentStartChar[Src[CurPos.StartPos]] then
Result:=KeyWordFuncList.DoItUppercase(UpperSrc,CurPos.StartPos,
CurPos.EndPos-CurPos.StartPos)
else
Result:=true;
end;
procedure TCustomCodeTool.SetIgnoreErrorAfter(const AValue: TCodePosition);
begin
if (IgnoreErrorAfter.Code=AValue.Code)
and (IgnoreErrorAfter.P=AValue.P) then exit;
FIgnoreErrorAfter:=AValue;
LastErrorCheckedForIgnored:=false;
{$IFDEF ShowIgnoreErrorAfter}
DbgOut'TCustomCodeTool.SetIgnoreErrorAfter ');
if FIgnoreErrorAfter.Code<>nil then
DbgOutFIgnoreErrorAfter.Code.Filename)
else
DbgOut'nil');
DbgOut' ',FIgnoreErrorAfter.P);
DebugLn('');
{$ENDIF}
if Scanner<>nil then
Scanner.SetIgnoreErrorAfter(IgnoreErrorAfter.P,IgnoreErrorAfter.Code);
end;
procedure TCustomCodeTool.RaiseExceptionInstance(TheException: ECodeToolError);
var CaretXY: TCodeXYPosition;
CursorPos: integer;
Node: TCodeTreeNode;
begin
ErrorPosition.Code:=nil;
CursorPos:=CurPos.StartPos;
// close all open nodes, so that FindDeepestNodeAtPos works in the code
// already parsed
Node:=CurNode;
while (Node<>nil) do begin
if (Node.StartPos>=Node.EndPos) then
Node.EndPos:=CursorPos;
Node:=Node.Parent;
end;
// convert cursor pos to caret pos, which is more human readable
if (CursorPos>SrcLen) and (SrcLen>0) then CursorPos:=SrcLen;
if (CleanPosToCaret(CursorPos,CaretXY))
and (CaretXY.Code<>nil) then begin
ErrorPosition:=CaretXY;
end else if (Scanner<>nil) and (Scanner.MainCode<>nil) then begin
ErrorPosition.Code:=TCodeBuffer(Scanner.MainCode);
ErrorPosition.Y:=-1;
end;
// raise the exception
CurrentPhase:=CodeToolPhaseNone;
if not RaiseUnhandableExceptions then
raise TheException
else
RaiseCatchableException(TheException.Message);
end;
procedure TCustomCodeTool.RaiseExceptionClass(const AMessage: string;
ExceptionClass: ECodeToolErrors);
begin
RaiseExceptionInstance(ExceptionClass.Create(Self,AMessage));
end;
function TCustomCodeTool.DefaultKeyWordFunc: boolean;
begin
Result:=true;
end;
function TCustomCodeTool.ConsistencyCheck: integer;
// 0 = ok
begin
Result:=Tree.ConsistencyCheck;
if Result<>0 then begin
dec(Result,100); exit;
end;
Result:=0;
end;
procedure TCustomCodeTool.WriteDebugTreeReport;
procedure WriteSrcSubString(A,Len: integer);
var i: integer;
begin
DbgOut('"');
for i:=A to A+Len-1 do begin
if (i>0) and (i<SrcLen) and (ord(Src[i])>31) then
DbgOut(Src[i]);
end;
DbgOut('"');
end;
procedure WriteSubTree(RootNode: TCodeTreeNode; Indent: string);
begin
while RootNode<>nil do begin
DbgOut(Indent);
with RootNode do begin
DbgOut(NodeDescToStr(Desc)+'('+NodeSubDescToStr(Desc,SubDesc)+') ');
DbgOut(' Start='+DbgS(StartPos),' ');
WriteSrcSubString(StartPos,5);
DbgOut(' End='+DbgS(EndPos)+' ');
WriteSrcSubString(EndPos-5,5);
{$ifdef fpc}
DbgOut(' Self=',HexStr(Cardinal(RootNode),8));
DbgOut(' P=',HexStr(Cardinal(Parent),8));
DbgOut(' NB=',HexStr(Cardinal(NextBrother),8));
//write(' PB=',HexStr(Cardinal(PriorBrother),8));
//write(' FC=',HexStr(Cardinal(FirstChild),8));
//write(' LC=',HexStr(Cardinal(LastChild),8));
{$endif}
end;
DebugLn('');
WriteSubTree(RootNode.FirstChild,Indent+' ');
RootNode:=RootNode.NextBrother;
end;
end;
begin
DebugLn('[TCustomCodeTool.WriteDebugTreeReport] Consistency=',
dbgs(ConsistencyCheck));
WriteSubTree(Tree.Root,' ');
end;
function TCustomCodeTool.FindDeepestNodeAtPos(P: integer;
ExceptionOnNotFound: boolean): TCodeTreeNode;
begin
Result:=FindDeepestNodeAtPos(Tree.Root,P,ExceptionOnNotFound);
end;
function TCustomCodeTool.FindDeepestNodeAtPos(StartNode: TCodeTreeNode;
P: integer; ExceptionOnNotFound: boolean): TCodeTreeNode;
procedure RaiseNoNodeFoundAtCursor;
begin
SaveRaiseException(ctsNoNodeFoundAtCursor);
end;
var
ChildNode: TCodeTreeNode;
Brother: TCodeTreeNode;
begin
if StartNode<>nil then begin
//DebugLn('SearchInNode ',NodeDescriptionAsString(ANode.Desc),
//',',ANode.StartPos,',',ANode.EndPos,', p=',p,
//' "',copy(Src,ANode.StartPos,4),'" - "',copy(Src,ANode.EndPos-5,4),'"');
if (StartNode.StartPos<=P)
and ((StartNode.EndPos>P) or (StartNode.EndPos<1)) then begin
// StartNode contains P
Result:=StartNode;
// -> search for a child that contains P
Brother:=StartNode;
while (Brother<>nil)
and (Brother.StartPos<=P) do begin
// brother also contains P
if Brother.FirstChild<>nil then begin
ChildNode:=FindDeepestNodeAtPos(Brother.FirstChild,P,false);
if ChildNode<>nil then begin
Result:=ChildNode;
exit;
end;
end;
Brother:=Brother.NextBrother;
end;
end else
// search in next node
Result:=FindDeepestNodeAtPos(StartNode.NextBrother,P,false);
end else
Result:=nil;
if (Result=nil) and ExceptionOnNotFound then begin
MoveCursorToCleanPos(P);
RaiseNoNodeFoundAtCursor;
end;
end;
function TCustomCodeTool.CaretToCleanPos(Caret: TCodeXYPosition;
var CleanPos: integer): integer;
begin
//DebugLn('TCustomCodeTool.CaretToCleanPos A ',Caret.Code.Filename,' ',Caret.Code.SourceLength);
Caret.Code.LineColToPosition(Caret.Y,Caret.X,CleanPos);
//DebugLn('TCustomCodeTool.CaretToCleanPos B ',CleanPos,',',Caret.Y,',',Caret.X);
if (CleanPos>=1) then
Result:=Scanner.CursorToCleanPos(CleanPos,Caret.Code,CleanPos)
else
Result:=-2; // x,y beyond source
//DebugLn('TCustomCodeTool.CaretToCleanPos C CleanPos=',CleanPos,' Result=',Result);
end;
function TCustomCodeTool.CleanPosToCodePos(CleanPos: integer;
var CodePos: TCodePosition): boolean;
var
ACode: pointer;
begin
Result:=Scanner.CleanedPosToCursor(CleanPos,CodePos.p,ACode);
CodePos.Code:=TCodeBuffer(ACode);
end;
function TCustomCodeTool.CleanPosToCaret(CleanPos: integer;
var Caret:TCodeXYPosition): boolean; // true=ok, false=invalid CleanPos
var p: integer;
Code: Pointer;
begin
Result:=Scanner.CleanedPosToCursor(CleanPos,p,Code);
if Result then begin
Caret.Code:=TCodeBuffer(Code);
TCodeBuffer(Code).AbsoluteToLineCol(p,Caret.Y,Caret.X);
Result:=(Caret.Y>=0);
end;
end;
function TCustomCodeTool.CleanPosToCaretAndTopLine(CleanPos: integer;
var Caret:TCodeXYPosition; var NewTopLine: integer): boolean;
// true=ok, false=invalid CleanPos
begin
Result:=CleanPosToCaret(CleanPos,Caret);
if Result then begin
if JumpCentered then begin
NewTopLine:=Caret.Y-(VisibleEditorLines shr 1);
if NewTopLine<1 then NewTopLine:=1;
end else
NewTopLine:=Caret.Y;
end;
end;
procedure TCustomCodeTool.GetCleanPosInfo(CodePosInFront, CleanPos: integer;
ResolveComments: boolean; var SameArea: TAtomPosition);
var
ANode: TCodeTreeNode;
begin
if CodePosInFront<1 then begin
ANode:=FindDeepestNodeAtPos(CleanPos,True);
CodePosInFront:=ANode.StartPos;
end;
MoveCursorToCleanPos(CodePosInFront);
repeat
ReadNextAtom;
//DebugLn('TCustomCodeTool.GetCleanPosInfo A Atom=',GetAtom,' CleanPos=',CleanPos,' CurPos.StartPos=',CurPos.StartPos);
if (CleanPos>=CurPos.StartPos) and (CleanPos<CurPos.EndPos) then begin
// clean pos on token
SameArea:=CurPos;
exit;
end;
if CleanPos<CurPos.StartPos then begin
// clean pos between tokens
SameArea.Flag:=cafNone;
// get range of space behind last atom
if LastAtoms.Count>0 then begin
SameArea.StartPos:=LastAtoms.GetValueAt(0).EndPos;
end else begin
SameArea.StartPos:=CodePosInFront;
end;
SameArea.EndPos:=SameArea.StartPos;
repeat
//DebugLn('TCustomCodeTool.GetCleanPosInfo B CleanPos=',CleanPos,' SameArea.StartPos=',SameArea.StartPos,' SameArea.EndPos=',SameArea.EndPos);
while (SameArea.EndPos<=SrcLen)
and (IsSpaceChar[Src[SameArea.EndPos]]) do
inc(SameArea.EndPos);
if (SameArea.EndPos>CleanPos) or (SameArea.EndPos>SrcLen) then begin
// cursor is in normal space (i.e. not comment)
exit;
end;
// still between the two tokens, but end of space
// -> here starts a comment
SameArea.StartPos:=SameArea.EndPos;
MoveCursorToCleanPos(SameArea.StartPos);
ReadTillCommentEnd;
SameArea.EndPos:=CurPos.StartPos;
if (SameArea.StartPos=SameArea.EndPos) then
RaiseException('TCustomCodeTool.GetCleanPosInfo Internal Error A');
if CleanPos<SameArea.EndPos then begin
// cursor is in comment
if ResolveComments then begin
// take comment as normal code and search again
CodePosInFront:=SameArea.StartPos;
case Src[CodePosInFront] of
'{': inc(CodePosInFront);
'(','/': inc(CodePosInFront,2);
else
RaiseException('TCustomCodeTool.GetCleanPosInfo Internal Error B');
end;
GetCleanPosInfo(CodePosInFront,CleanPos,true,SameArea);
end;
exit;
end;
SameArea.StartPos:=SameArea.EndPos;
until false;
end;
until (CurPos.EndPos>CleanPos) or (CurPos.StartPos>SrcLen);
SameArea:=CurPos;
end;
procedure TCustomCodeTool.GetLineInfo(ACleanPos: integer;
var ALineStart, ALineEnd, AFirstAtomStart, ALastAtomEnd: integer);
begin
if ACleanPos>=1 then begin
if ACleanPos<=SrcLen then begin
// search line start
ALineStart:=ACleanPos-1;
while (ALineStart>=1) and (not (Src[ALineStart] in [#10,#13])) do
dec(ALineStart);
inc(ALineStart);
// search line end
ALineEnd:=ACleanPos;
while (ALineEnd>=1) and (not (Src[ALineEnd] in [#10,#13])) do
inc(ALineEnd);
// search first atom in line
MoveCursorToCleanPos(ALineStart);
ReadNextAtom;
AFirstAtomStart:=CurPos.StartPos;
// search last atom in line
repeat
ALastAtomEnd:=CurPos.EndPos;
ReadNextAtom;
until CurPos.EndPos>ALineEnd;
end else begin
ALineStart:=Srclen+1;
ALineEnd:=Srclen+1;
AFirstAtomStart:=Srclen+1;
ALastAtomEnd:=Srclen+1;
end;
end else begin
ALineStart:=1;
ALineEnd:=1;
AFirstAtomStart:=1;
ALastAtomEnd:=1;
end;
end;
function TCustomCodeTool.FindLineEndOrCodeAfterPosition(StartPos: integer
): integer;
{ Searches a nice position in the cleaned source after StartPos.
It will skip any space or comments (not directives) till next
line end or compiler directive or code or include file end.
}
var
LinkIndex, LinkEnd: integer;
begin
LinkIndex:=Scanner.LinkIndexAtCleanPos(StartPos);
LinkEnd:=Scanner.LinkCleanedEndPos(LinkIndex);
if LinkEnd>StartPos then
Result:=BasicCodeTools.FindLineEndOrCodeAfterPosition(Src,
StartPos,LinkEnd-1,Scanner.NestedComments)
else
Result:=StartPos;
end;
function TCustomCodeTool.FindLineEndOrCodeInFrontOfPosition(StartPos: integer
): integer;
begin
Result:=FindLineEndOrCodeInFrontOfPosition(StartPos,true);
end;
function TCustomCodeTool.FindLineEndOrCodeInFrontOfPosition(StartPos: integer;
StopAtDirectives: boolean): integer;
{ Searches a nice position in the cleaned source in front of StartPos.
It will skip any space or comments (not directives) till next
line end or compiler directive or code or include file end.
}
var
LinkIndex, LinkStart: integer;
begin
LinkIndex:=Scanner.LinkIndexAtCleanPos(StartPos);
LinkStart:=Scanner.Links[LinkIndex].CleanedPos;
Result:=BasicCodeTools.FindLineEndOrCodeInFrontOfPosition(Src,
StartPos,LinkStart,Scanner.NestedComments,StopAtDirectives);
end;
function TCustomCodeTool.FindFirstLineEndAfterInCode(StartPos: integer
): integer;
{ Searches a line end or code break in the cleaned source after StartPos.
It will skip any line ends in comments.
}
var
LinkIndex, LinkEnd: integer;
begin
LinkIndex:=Scanner.LinkIndexAtCleanPos(StartPos);
LinkEnd:=Scanner.LinkCleanedEndPos(LinkIndex);
if LinkEnd>StartPos then
Result:=BasicCodeTools.FindFirstLineEndAfterInCode(Src,
StartPos,LinkEnd-1,Scanner.NestedComments)
else
Result:=StartPos;
end;
procedure TCustomCodeTool.ClearIgnoreErrorAfter;
begin
IgnoreErrorAfter:=CodePosition(0,nil);
end;
function TCustomCodeTool.UpdateNeeded(OnlyInterfaceNeeded: boolean): boolean;
begin
{$IFDEF CTDEBUG}
DebugLn('TCustomCodeTool.UpdateNeeded A ',dbgs(Scanner<>nil),' FForceUpdateNeeded=',dbgs(FForceUpdateNeeded));
{$ENDIF}
if FForceUpdateNeeded then begin
Result:=true;
exit;
end;
Result:=(FLastScannerChangeStep<>Scanner.ChangeStep)
or (Scanner.UpdateNeeded(OnlyInterfaceNeeded, CheckFilesOnDisk));
FForceUpdateNeeded:=Result;
{$IFDEF CTDEBUG}
DebugLn('TCustomCodeTool.UpdateNeeded END Result=',dbgs(Result));
{$ENDIF}
end;
function TCustomCodeTool.CompareSrcIdentifier(CleanStartPos: integer;
const Identifier: string): boolean;
var IdentPos, Len: integer;
begin
Result:=false;
Len:=length(Identifier);
if (CleanStartPos<1) or (CleanStartPos>SrcLen-Len+1) or (Identifier='') then
exit;
IdentPos:=1;
while (IdentPos<=Len) and (IsIdentChar[Src[CleanStartPos]]) do begin
if UpChars[Identifier[IdentPos]]<>UpperSrc[CleanStartPos] then
exit;
inc(IdentPos);
inc(CleanStartPos);
end;
Result:=(IdentPos>Len)
and ((CleanStartPos>Srclen) or (not IsIdentChar[Src[CleanStartPos]]));
end;
function TCustomCodeTool.CompareSrcIdentifiers(Identifier1, Identifier2: PChar
): boolean;
begin
Result:=false;
if (Identifier1=nil) or (Identifier2=nil) then exit;
while IsIdentChar[Identifier1[0]] do begin
if (UpChars[Identifier1[0]]=UpChars[Identifier2[0]]) then begin
inc(Identifier1);
inc(Identifier2);
end else
exit;
end;
Result:=(not IsIdentChar[Identifier2[0]]);
end;
function TCustomCodeTool.CompareSrcIdentifiers(CleanStartPos: integer;
AnIdentifier: PChar): boolean;
begin
Result:=false;
if (AnIdentifier=nil) or (CleanStartPos<1) or (CleanStartPos>SrcLen) then
exit;
while IsIdentChar[AnIdentifier[0]] do begin
if (UpChars[AnIdentifier[0]]=UpperSrc[CleanStartPos]) then begin
inc(AnIdentifier);
inc(CleanStartPos);
if CleanStartPos>SrcLen then break;
end else
exit;
end;
Result:=(CleanStartPos>SrcLen) or (not IsIdentChar[Src[CleanStartPos]]);
end;
function TCustomCodeTool.ExtractIdentifier(CleanStartPos: integer): string;
var len: integer;
begin
if (CleanStartPos>=1) then begin
len:=0;
while (CleanStartPos<=SrcLen)
and (IsIdentChar[Src[CleanStartPos+len]]) do
inc(len);
SetLength(Result,len);
if len>0 then
Move(Src[CleanStartPos],Result[1],len);
end else
Result:='';
end;
procedure TCustomCodeTool.DoDeleteNodes;
begin
Tree.Clear;
end;
procedure TCustomCodeTool.RaiseIdentExpectedButAtomFound;
begin
SaveRaiseExceptionFmt(ctsIdentExpectedButKeyWordFound,[GetAtom])
end;
procedure TCustomCodeTool.RaiseBracketOpenExpectedButAtomFound;
begin
SaveRaiseExceptionFmt(ctsBracketOpenExpectedButAtomFound,[GetAtom]);
end;
procedure TCustomCodeTool.RaiseBracketCloseExpectedButAtomFound;
begin
SaveRaiseExceptionFmt(ctsBracketCloseExpectedButAtomFound,[GetAtom]);
end;
procedure TCustomCodeTool.ActivateGlobalWriteLock;
begin
if Assigned(OnSetGlobalWriteLock) then OnSetGlobalWriteLock(true);
end;
procedure TCustomCodeTool.DeactivateGlobalWriteLock;
begin
if Assigned(OnSetGlobalWriteLock) then OnSetGlobalWriteLock(false);
end;
function TCustomCodeTool.MainFilename: string;
begin
if (Scanner<>nil) and (Scanner.MainCode<>nil) then
Result:=TCodeBuffer(Scanner.MainCode).Filename
else
Result:=ctsUnknownMainFilename;
end;
{ ECodeToolError }
constructor ECodeToolError.Create(ASender: TCustomCodeTool;
const AMessage: string);
begin
inherited Create(AMessage);
Sender:=ASender;
end;
{ ECodeToolFileNotFound }
constructor ECodeToolFileNotFound.Create(ASender: TCustomCodeTool;
const AMessage, AFilename: string);
begin
inherited Create(ASender,AMessage);
Filename:=AFilename;
end;
{ TDirtySource }
procedure TDirtySource.BeginUpdate;
begin
inc(LockCount);
end;
procedure TDirtySource.EndUpdate;
begin
if LockCount<=0 then
RaiseCatchableException('TDirtySource.EndUpdate');
dec(LockCount);
end;
procedure TDirtySource.SetGap(const NewCursorPos: TCodeXYPosition;
NewDirtyStartPos, NewDirtyGapStart, NewDirtyGapEnd: integer);
begin
// check for conflicts
if (LockCount>0) then begin
if (Code<>nil) and (Code<>NewCursorPos.Code) then
RaiseCatchableException('TDirtySource.SetGap Code change');
if (GapStart>0) then
if (NewDirtyStartPos<>StartPos)
or (NewDirtyGapStart<>GapStart)
or (NewDirtyGapEnd<>GapEnd) then
RaiseCatchableException('TDirtySource.SetGap Gap change');
end;
if (NewDirtyGapStart>NewDirtyStartPos)
or (NewDirtyStartPos>NewDirtyGapEnd) then
RaiseCatchableException('TDirtySource.SetGap Gap Bounds');
// set values
CursorPos:=NewCursorPos;
Code:=CursorPos.Code;
StartPos:=NewDirtyStartPos;
GapStart:=NewDirtyGapStart;
GapEnd:=NewDirtyGapEnd;
CurPos.StartPos:=StartPos;
CurPos.EndPos:=StartPos;
CurPos.Flag:=cafNone;
// get source
if Code<>nil then
Src:=Code.Source
else
Src:='';
if (GapStart>0) then begin
GapSrc:=copy(Src,GapStart,GapEnd-GapStart);
GapUpperSrc:=UpperCaseStr(GapSrc);
{$IFDEF ShowDirtySrc}
DebugLn('TDirtySource.SetGap Owner=',ExtractFilename(Owner.MainFilename),
' Code=',ExtractFilename(Code.Filename),
' Gap('+dbgs(GapStart)+','+dbgs(StartPos)+','+dbgs(GapEnd)+')',
'"',StringToPascalConst(copy(GapSrc,1,20)),'"..',
'"',StringToPascalConst(copy(GapSrc,length(GapSrc)-19,20)),'"'
);
{$ENDIF}
end else begin
GapSrc:='';
GapUpperSrc:='';
end;
end;
constructor TDirtySource.Create(TheOwner: TCustomCodeTool);
begin
Owner:=TheOwner;
end;
procedure TDirtySource.Clear;
begin
SetGap(CodeXYPosition(0,0,nil),0,0,0);
end;
procedure TDirtySource.SetCursorToIdentStartEndAtPosition;
begin
GetIdentStartEndAtPosition(GapSrc,CurPos.StartPos,
CurPos.StartPos,CurPos.EndPos);
end;
function TDirtySource.GetCursorSrcPos: PChar;
begin
Result:=@Src[CurPos.StartPos];
end;
function TDirtySource.IsPCharInSrc(p: PChar): boolean;
var NewPos: integer;
begin
Result:=false;
if Src='' then exit;
NewPos:=Integer(p)-Integer(@Src[1])+1;
if (NewPos<1) or (NewPos>length(Src)) then exit;
Result:=true;
end;
procedure TDirtySource.MoveCursorToPos(APos: integer);
begin
CurPos.StartPos:=APos;
CurPos.EndPos:=APos;
CurPos.Flag:=cafNone;
end;
procedure TDirtySource.MoveCursorToPos(APos: PChar);
procedure RaiseSrcEmpty;
begin
RaiseCatchableException('[TDirtySource.MoveCursorToPos - PChar] Src empty');
end;
procedure RaiseNotInSrc;
begin
RaiseCatchableException('[TDirtySource.MoveCursorToPos - PChar] Pos not in Src');
end;
var NewPos: integer;
begin
if Src='' then
RaiseSrcEmpty;
NewPos:=Integer(APos)-Integer(@Src[1])+1;
if (NewPos<1) or (NewPos>length(Src)) then
RaiseNotInSrc;
MoveCursorToPos(NewPos);
end;
initialization
RaiseUnhandableExceptions:=false;
end.