{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Functions to parse and edit compiler directives. } unit DirectivesTree; {$ifdef FPC}{$mode objfpc}{$endif}{$H+} { $DEFINE VerboseDisableUnreachableIFDEFs} interface {$I codetools.inc} uses {$IFDEF MEM_CHECK} MemCheck, {$ENDIF} Classes, SysUtils, Laz_AVL_Tree, // Codetools FileProcs, BasicCodeTools, KeywordFuncLists, CodeCache, ExprEval, CodeTree; type TCompilerDirectiveNodeDesc = word; const // descriptors cdnBase = 1000; cdnNone = 0+cdnBase; cdnRoot = 1+cdnBase; cdnDefine = 11+cdnBase; cdnInclude = 12+cdnBase; cdnIf = 21+cdnBase; cdnElseIf = 22+cdnBase; cdnElse = 23+cdnBase; cdnEnd = 24+cdnBase; // sub descriptors cdnsBase = 10000; cdnsNone = 0+cdnsBase; cdnsIfdef = 1+cdnsBase; cdnsIfC = 2+cdnsBase; cdnsIfndef = 3+cdnsBase; cdnsIf = 4+cdnsBase; cdnsIfOpt = 5+cdnsBase; cdnsEndif = 11+cdnsBase; cdnsEndC = 12+cdnsBase; cdnsIfEnd = 13+cdnsBase; cdnsElse = 21+cdnsBase; cdnsElseC = 22+cdnsBase; cdnsElseIf = 23+cdnsBase; cdnsElIfC = 24+cdnsBase; cdnsDefine = 31+cdnsBase; cdnsUndef = 32+cdnsBase; cdnsSetC = 33+cdnsBase; cdnsInclude = 41+cdnsBase; cdnsIncludePath = 42+cdnsBase; cdnsShortSwitch = 51+cdnsBase; cdnsLongSwitch = 52+cdnsBase; cdnsMode = 53+cdnsBase; cdnsThreading = 54+cdnsBase; cdnsOther = 55+cdnsBase; const H2Pas_Function_Prefix = 'H2PAS_FUNCTION_'; type TCompilerDirectivesTree = class; { ECDirectiveParserException } ECDirectiveParserException = class(Exception) public Sender: TCompilerDirectivesTree; Id: int64; constructor Create(ASender: TCompilerDirectivesTree; TheId: int64; const AMessage: string); end; TCompilerMacroStatus = ( cmsUnknown, // never seen cmsDefined, // set to a specific value e.g. by $Define or by $IfDef cmsUndefined, // undefined e.g. by $Undef cmsComplex // value depends on complex expressions. e.g. {$if A or B}. ); TCompilerMacroStats = class public Name: string; Value: string; Status: TCompilerMacroStatus; LastDefineNode: TCodeTreeNode;// define or undef node LastReadNode: TCodeTreeNode;// if node end; { TH2PasFunction } TH2PasFunction = class public Name: string; HeaderStart: integer; HeaderEnd: integer; BeginStart: integer; BeginEnd: integer; IsForward: boolean; IsExternal: boolean; InInterface: boolean; DefNode: TH2PasFunction;// the corresponding node function NeedsBody: boolean; procedure AdjustPositionsAfterInsert(FromPos, ToPos, DiffPos: integer); end; { TCompilerDirectivesTree } TCompilerDirectivesTree = class private FChangeStep: integer; FDefaultDirectiveFuncList: TKeyWordFunctionList; FDisableUnusedDefines: boolean; FNestedComments: boolean; FParseChangeStep: integer; FRemoveDisabledDirectives: boolean; FSimplifyExpressions: boolean; FUndefH2PasFunctions: boolean; FLastErrorMsg: string; fLastErrorPos: integer; fLastErrorXY: TPoint; fLastErrorId: int64; function IfdefDirective: boolean; function IfCDirective: boolean; function IfndefDirective: boolean; function IfDirective: boolean; function IfOptDirective: boolean; function EndifDirective: boolean; function EndCDirective: boolean; function IfEndDirective: boolean; function ElseDirective: boolean; function ElseCDirective: boolean; function ElseIfDirective: boolean; function ElIfCDirective: boolean; function DefineDirective: boolean; procedure SetNestedComments(AValue: boolean); function UndefDirective: boolean; function SetCDirective: boolean; function IncludeDirective: boolean; function IncludePathDirective: boolean; function ShortSwitchDirective: boolean; function ReadNextSwitchDirective: boolean; function LongSwitchDirective: boolean; function ModeDirective: boolean; function ThreadingDirective: boolean; function OtherDirective: boolean; procedure InitKeyWordList; procedure InitParser; procedure CreateChildNode(Desc: TCompilerDirectiveNodeDesc; SubDesc: TCompilerDirectiveNodeDesc = cdnNone); procedure EndChildNode; procedure EndIFNode(const ErrorMsg: string); procedure InternalRemoveNode(Node: TCodeTreeNode); procedure RaiseException(id: int64; const ErrorMsg: string); procedure RaiseLastError; public Code: TCodeBuffer; Src: string; SrcLen: integer; Tree: TCodeTree; CurNode: TCodeTreeNode; SrcPos: Integer; AtomStart: integer; Macros: TAVLTree;// tree of TCompilerMacroStats constructor Create; destructor Destroy; override; procedure Clear; // parsing procedure Parse; procedure Parse(aCode: TCodeBuffer; aNestedComments: boolean); property NestedComments: boolean read FNestedComments write SetNestedComments; property ParseChangeStep: integer read FParseChangeStep; function UpdateNeeded: boolean; procedure MoveCursorToPos(p: integer); procedure ReadNextAtom; function ReadTilBracketClose(CloseBracket: char): boolean; function AtomIs(const s: shortstring): boolean; function UpAtomIs(const s: shortstring): boolean; function AtomIsIdentifier: boolean; function GetAtom: string; // errors property ErrorMsg: string read FLastErrorMsg; property ErrorPos: integer read fLastErrorPos; property ErrorLine: integer read fLastErrorXY.Y; property ErrorColumn: integer read fLastErrorXY.X; property ErrorId: int64 read fLastErrorId; function SrcPosToStr(p: integer; WithFilename: boolean = false): string; // search function FindResourceDirective(const Filename: string = ''; StartPos: integer = 1): TCodeTreeNode; function IsResourceDirective(Node: TCodeTreeNode; const Filename: string = ''): boolean; function FindIncludeDirective(const Filename: string = ''; StartPos: integer = 1): TCodeTreeNode; function IsIncludeDirective(Node: TCodeTreeNode; const Filename: string = ''): boolean; // explore function GetDirectiveName(Node: TCodeTreeNode): string; function GetDirective(Node: TCodeTreeNode): string; function GetIfExpression(Node: TCodeTreeNode; out ExprStart, ExprEnd: integer): boolean; function GetIfExpressionString(Node: TCodeTreeNode): string; function IsIfExpressionSimple(Node: TCodeTreeNode; out NameStart: integer ): boolean; function FindNameInIfExpression(Node: TCodeTreeNode; Identifier: PChar ): integer; function GetDefineNameAndValue(DefineNode: TCodeTreeNode; out NameStart: integer; out HasValue: boolean; out ValueStart: integer ): boolean; function DefineUsesName(DefineNode: TCodeTreeNode; Identifier: PChar): boolean; function NodeIsEmpty(Node: TCodeTreeNode; IgnoreComments: boolean = true): boolean; function FindNodeAtPos(p: integer): TCodeTreeNode; function NodeStartToCodePos(Node: TCodeTreeNode; out CodePos: TCodeXYPosition): boolean; // refactoring procedure ReduceCompilerDirectives(Undefines, Defines: TStrings; var Changed: boolean); procedure GatherH2PasFunctions(out ListOfH2PasFunctions: TFPList; FindDefNodes: boolean); procedure FixMissingH2PasDirectives(var Changed: boolean); procedure CheckAndImproveExpr_Brackets(Node: TCodeTreeNode; var Changed: boolean); procedure CheckAndImproveExpr_IfDefinedMacro(Node: TCodeTreeNode; var Changed: boolean); procedure DisableAllUnusedDefines(var Changed: boolean); procedure MoveIfNotThenDefsUp(var Changed: boolean); procedure DisableUnreachableBlocks(Undefines, Defines: TStrings; var Changed: boolean); procedure DisableNode(Node: TCodeTreeNode; var Changed: boolean; WithContent: boolean); procedure DisableDefineNode(Node: TCodeTreeNode; var Changed: boolean); procedure DisableIfNode(Node: TCodeTreeNode; WithContent: boolean; var Changed: boolean); function InsertDefine(Position: integer; const NewSrc: string; SubDesc: TCompilerDirectiveNodeDesc): TCodeTreeNode; procedure RemoveEmptyNodes(var Changed: boolean); procedure Replace(FromPos, ToPos: integer; const NewSrc: string); procedure IncreaseChangeStep; procedure ResetMacros; procedure ClearMacros; procedure WriteDebugReport; public property SimplifyExpressions: boolean read FSimplifyExpressions write FSimplifyExpressions; property DisableUnusedDefines: boolean read FDisableUnusedDefines write FDisableUnusedDefines; property RemoveDisabledDirectives: boolean read FRemoveDisabledDirectives write FRemoveDisabledDirectives; property UndefH2PasFunctions: boolean read FUndefH2PasFunctions write FUndefH2PasFunctions; property ChangeStep: integer read FChangeStep; end; function CompareCompilerMacroStats(Data1, Data2: Pointer): integer; function ComparePCharWithCompilerMacroStats(Name, MacroStats: Pointer): integer; function CompareH2PasFuncByNameAndPos(Data1, Data2: Pointer): integer; function ComparePCharWithH2PasFuncName(Name, H2PasFunc: Pointer): integer; function CDNodeDescAsString(Desc: TCompilerDirectiveNodeDesc): string; function CDNodeSubDescAsString(Desc: TCompilerDirectiveNodeDesc): string; implementation type TDefineStatus = ( dsUnknown, dsDefined, dsNotDefined ); TDefineValue = class Name: string; Status: TDefineStatus; Value: string; end; {$IFDEF VerboseDisableUnreachableIFDEFs} const DefineStatusNames: array[TDefineStatus] of string = ( 'dsUnknown','dsDefined','dsNotDefined' ); {$ENDIF} function CompareDefineValues(Data1, Data2: Pointer): integer; begin Result:=CompareIdentifierPtrs(Pointer(TDefineValue(Data1).Name), Pointer(TDefineValue(Data2).Name)); end; function ComparePCharWithDefineValue(Name, DefValue: Pointer): integer; begin Result:=CompareIdentifierPtrs(Name, Pointer(TDefineValue(DefValue).Name)); end; function CompareCompilerMacroStats(Data1, Data2: Pointer): integer; begin Result:=CompareIdentifierPtrs(Pointer(TCompilerMacroStats(Data1).Name), Pointer(TCompilerMacroStats(Data2).Name)); end; function ComparePCharWithCompilerMacroStats(Name, MacroStats: Pointer): integer; begin Result:=CompareIdentifierPtrs(Name, Pointer(TCompilerMacroStats(MacroStats).Name)); end; function CompareH2PasFuncByNameAndPos(Data1, Data2: Pointer): integer; var F1: TH2PasFunction; F2: TH2PasFunction; begin F1:=TH2PasFunction(Data1); F2:=TH2PasFunction(Data2); Result:=CompareIdentifierPtrs(Pointer(F1.Name),Pointer(F2.Name)); if Result<>0 then exit; if F1.HeaderStart>F2.HeaderStart then exit(1) else if F1.HeaderStart'' then begin Add(c,{$ifdef FPC}@{$endif}ShortSwitchDirective); Add(CompilerSwitchesNames[c],{$ifdef FPC}@{$endif}LongSwitchDirective); end; end; Add('IFDEF',{$ifdef FPC}@{$endif}IfdefDirective); Add('IFC',{$ifdef FPC}@{$endif}IfCDirective); Add('IFNDEF',{$ifdef FPC}@{$endif}IfndefDirective); Add('IF',{$ifdef FPC}@{$endif}IfDirective); Add('IFOPT',{$ifdef FPC}@{$endif}IfOptDirective); Add('ENDIF',{$ifdef FPC}@{$endif}EndIfDirective); Add('ENDC',{$ifdef FPC}@{$endif}EndCDirective); Add('ELSE',{$ifdef FPC}@{$endif}ElseDirective); Add('ELSEC',{$ifdef FPC}@{$endif}ElseCDirective); Add('ELSEIF',{$ifdef FPC}@{$endif}ElseIfDirective); Add('ELIFC',{$ifdef FPC}@{$endif}ElIfCDirective); Add('IFEND',{$ifdef FPC}@{$endif}IfEndDirective); Add('DEFINE',{$ifdef FPC}@{$endif}DefineDirective); Add('UNDEF',{$ifdef FPC}@{$endif}UndefDirective); Add('SETC',{$ifdef FPC}@{$endif}SetCDirective); Add('INCLUDE',{$ifdef FPC}@{$endif}IncludeDirective); Add('INCLUDEPATH',{$ifdef FPC}@{$endif}IncludePathDirective); Add('MODE',{$ifdef FPC}@{$endif}ModeDirective); Add('THREADING',{$ifdef FPC}@{$endif}ThreadingDirective); DefaultKeyWordFunction:={$ifdef FPC}@{$endif}OtherDirective; end; end; end; procedure TCompilerDirectivesTree.InitParser; begin FParseChangeStep:=Code.ChangeStep; IncreaseChangeStep; InitKeyWordList; Src:=Code.Source; SrcLen:=length(Src); if Tree=nil then Tree:=TCodeTree.Create else Tree.Clear; SrcPos:=1; AtomStart:=1; CurNode:=nil; CreateChildNode(cdnRoot); end; procedure TCompilerDirectivesTree.CreateChildNode( Desc: TCompilerDirectiveNodeDesc; SubDesc: TCompilerDirectiveNodeDesc); var NewNode: TCodeTreeNode; begin NewNode:=TCodeTreeNode.Create; Tree.AddNodeAsLastChild(CurNode,NewNode); NewNode.Desc:=Desc; NewNode.SubDesc:=SubDesc; CurNode:=NewNode; CurNode.StartPos:=AtomStart; //DebugLn([GetIndentStr(CurNode.GetLevel*2),'TCompilerDirectivesTree.CreateChildNode ']); end; procedure TCompilerDirectivesTree.EndChildNode; begin //DebugLn([GetIndentStr(CurNode.GetLevel*2),'TCompilerDirectivesTree.EndChildNode ']); CurNode.EndPos:=AtomStart; CurNode:=CurNode.Parent; end; procedure TCompilerDirectivesTree.EndIFNode(const ErrorMsg: string); begin if (CurNode.Desc<>cdnIf) and (CurNode.Desc<>cdnElse) and (CurNode.Desc<>cdnElseIf) then RaiseException(20170422131836,ErrorMsg); EndChildNode; end; procedure TCompilerDirectivesTree.CheckAndImproveExpr_Brackets( Node: TCodeTreeNode; var Changed: boolean); // improve (MacroName) to MacroName var ExprStart: integer; ExprEnd: integer; NameStart: LongInt; FromPos: LongInt; ToPos: LongInt; begin if not SimplifyExpressions then exit; if (Node.SubDesc<>cdnsIf) and (Node.SubDesc<>cdnElseIf) then exit; if not GetIfExpression(Node,ExprStart,ExprEnd) then exit; // improve (MacroName) to MacroName MoveCursorToPos(ExprStart); repeat ReadNextAtom; if UpAtomIs('DEFINED') then begin // the function defined(): skip keyword and bracket ReadNextAtom; ReadNextAtom; end; if AtomIs('(') then begin FromPos:=AtomStart; ReadNextAtom; if AtomIsIdentifier then begin NameStart:=AtomStart; ReadNextAtom; if AtomIs(')') then begin ToPos:=SrcPos; DebugLn(['TCompilerDirectivesTree.CheckAndImproveExpr_Brackets removing unneeded brackets']); Changed:=true; Replace(FromPos,ToPos,GetIdentifier(@Src[NameStart])); MoveCursorToPos(FromPos); end; end; end; until SrcPos>=ExprEnd; end; procedure TCompilerDirectivesTree.CheckAndImproveExpr_IfDefinedMacro( Node: TCodeTreeNode; var Changed: boolean); // check if {$IF defined(MacroName)} // or {$IF !defined(MacroName)} // or {$IF not defined(MacroName)} // or {$IF not (defined(MacroName))} var ExprStart: integer; ExprEnd: integer; MacroNameStart: LongInt; Negated: Boolean; NewDirective: String; BracketLvl: Integer; begin if not SimplifyExpressions then exit; if (Node.SubDesc<>cdnsIf) then exit; if not GetIfExpression(Node,ExprStart,ExprEnd) then exit; Negated:=false; MoveCursorToPos(ExprStart); ReadNextAtom; if UpAtomIs('NOT') or AtomIs('!') then begin Negated:=true; ReadNextAtom; end; BracketLvl:=0; while AtomIs('(') do begin inc(BracketLvl); ReadNextAtom; end; if not UpAtomIs('DEFINED') then exit; ReadNextAtom; if not AtomIs('(') then exit; inc(BracketLvl); ReadNextAtom; if not AtomIsIdentifier then exit; MacroNameStart:=AtomStart; ReadNextAtom; while AtomIs(')') do begin dec(BracketLvl); ReadNextAtom; end; if BracketLvl>0 then exit; if SrcPos<=ExprEnd then exit; if Negated then NewDirective:='IFNDEF' else NewDirective:='IFDEF'; NewDirective:='{$'+NewDirective+' '+GetIdentifier(@Src[MacroNameStart])+'}'; DebugLn(['TCompilerDirectivesTree.CheckAndImproveExpr_IfDefinedMacro simplifying expression']); Replace(Node.StartPos,FindCommentEnd(Src,Node.StartPos,NestedComments),NewDirective); if Negated then Node.SubDesc:=cdnsIfNdef else Node.SubDesc:=cdnsIfdef; Changed:=true; end; procedure TCompilerDirectivesTree.DisableAllUnusedDefines(var Changed: boolean); var AVLNode: TAVLTreeNode; MacroNode: TCompilerMacroStats; NextAVLNode: TAVLTreeNode; begin if Macros=nil then exit; if not DisableUnusedDefines then exit; AVLNode:=Macros.FindLowest; while AVLNode<>nil do begin NextAVLNode:=Macros.FindSuccessor(AVLNode); MacroNode:=TCompilerMacroStats(AVLNode.Data); if (MacroNode.LastDefineNode<>nil) and (MacroNode.LastReadNode=nil) then begin // this Define/Undef is not used DebugLn(['TCompilerDirectivesTree.DisableAllUnusedDefines']); DisableDefineNode(MacroNode.LastDefineNode,Changed); end; AVLNode:=NextAVLNode; end; end; procedure TCompilerDirectivesTree.MoveIfNotThenDefsUp(var Changed: boolean); (* 1. Search for {$IFNDEF Name} {$DEFINE Name} .. name is not used here .. {$ENDIF} And move the define behind the IF block 2. And check for {$IFDEF Name} .. name is not set here .. {$DEFINE Name} {$ENDIF} And remove the define *) function IdentifierIsReadAfterNode(Identifier: PChar; StartNode: TCodeTreeNode): boolean; var Node: TCodeTreeNode; ParentNode: TCodeTreeNode; begin Node:=StartNode; while Node<>nil do begin case Node.Desc of cdnIf,cdnElseIf: if FindNameInIfExpression(Node,Identifier)>0 then begin exit(true); end; cdnDefine: if DefineUsesName(Node,Identifier) then begin ParentNode:=StartNode; while (ParentNode<>nil) do begin if ParentNode=Node.Parent then exit(false); ParentNode:=ParentNode.Parent; end; end; end; Node:=Node.Next; end; Result:=false; end; var Node: TCodeTreeNode; NextNode: TCodeTreeNode; SubNode: TCodeTreeNode; NameStart: integer; LastDefineNode: TCodeTreeNode; LastIFNode: TCodeTreeNode; NextSubNode: TCodeTreeNode; EndNode: TCodeTreeNode; InsertPos: LongInt; NewSrc: String; LastChildDefineNode: TCodeTreeNode; begin Node:=Tree.Root; while Node<>nil do begin NextNode:=Node.Next; if ((Node.Desc=cdnIf) or (Node.Desc=cdnElseIf)) and IsIfExpressionSimple(Node,NameStart) then begin // an IF with a single test LastIFNode:=nil; LastDefineNode:=nil; LastChildDefineNode:=nil; SubNode:=Node.FirstChild; while (SubNode<>nil) and (SubNode.HasAsParent(Node)) do begin NextSubNode:=SubNode.Next; case SubNode.Desc of cdnIf, cdnElseIf: if FindNameInIfExpression(SubNode,@Src[NameStart])>0 then begin // this sub IF block uses the macro LastIFNode:=SubNode; end; cdnDefine: if ((SubNode.SubDesc=cdnsDefine) or (SubNode.SubDesc=cdnsUndef)) and DefineUsesName(SubNode,@Src[NameStart]) then begin // this sub Define/Undef sets the macro if (LastIFNode=nil) and (LastDefineNode=nil) then begin (* This is {$IF(N)DEF Name} ... Name not used ... {$DEFINE|UNDEF Name} *) if (Node.SubDesc=cdnsIfndef) = (SubNode.SubDesc=cdnsUndef) then begin { this is IFNDEF then UNDEF or IFDEF then DEFINE -> remove define } NextSubNode:=SubNode.NextSkipChilds; DebugLn(['TCompilerDirectivesTree.MoveIfNotThenDefsUp IFDEF + DEFINE => the define is not needed']); if NextNode=SubNode then NextNode:=NextNode.NextSkipChilds; DisableDefineNode(SubNode,Changed); SubNode:=nil; end; end; if SubNode<>nil then begin LastDefineNode:=SubNode; LastIFNode:=nil; if SubNode.Parent=Node then begin // this define is valid for end of the IF block LastChildDefineNode:=SubNode; end else if (LastChildDefineNode<>nil) and (LastChildDefineNode.SubDesc<>SubNode.SubDesc) then begin // this sub define can cancel the higher level define LastChildDefineNode:=nil; end; end; end; end; SubNode:=NextSubNode; end; if (LastChildDefineNode<>nil) then begin (* this is {$IFNDEF Name} ... {$DEFINE Name} ... Name only read ... {$ENDIF} or IFDEF and UNDEF -> move define behind IF block *) EndNode:=Node; while (EndNode<>nil) and (EndNode.Desc<>cdnEnd) do EndNode:=EndNode.NextBrother; if (EndNode<>nil) and IdentifierIsReadAfterNode(@Src[NameStart],EndNode) then begin InsertPos:=FindLineEndOrCodeAfterPosition(Src,EndNode.EndPos,SrcLen, NestedComments); NewSrc:=LineEnding+GetDirective(LastDefineNode); DebugLn(['TCompilerDirectivesTree.MoveIfNotThenDefsUp IFNDEF + DEFINE => add define after block']); InsertDefine(InsertPos,NewSrc,LastDefineNode.SubDesc); if (LastDefineNode=LastChildDefineNode) and (LastIFNode=nil) then begin // the name was not read after it was set -> disable the define // in the block DebugLn(['TCompilerDirectivesTree.MoveIfNotThenDefsUp old DEFINE is not needed anymore']); if NextNode=LastDefineNode then NextNode:=NextNode.NextSkipChilds; DisableDefineNode(LastDefineNode,Changed); end; end; end; end; Node:=NextNode; end; end; procedure TCompilerDirectivesTree.DisableUnreachableBlocks(Undefines, Defines: TStrings; var Changed: boolean); type PDefineChange = ^TDefineChange; TDefineChange = record Name: string; OldStatus: TDefineStatus; Next: PDefineChange; end; var CurDefines: TAVLTree; Stack: array of PDefineChange;// stack of lists of PDefineChange StackPointer: integer; procedure InitStack; begin SetLength(Stack,1); StackPointer:=0; Stack[0]:=nil; end; procedure FreeStack; var i: Integer; Item: PDefineChange; DeleteItem: PDefineChange; begin for i:=0 to StackPointer do begin Item:=Stack[i]; while Item<>nil do begin DeleteItem:=Item; Item:=DeleteItem^.Next; Dispose(DeleteItem); end; end; Setlength(Stack,0); end; procedure AddStackChange(const MacroName: string; OldStatus: TDefineStatus); var Change: PDefineChange; begin {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['AddStackChange ',MacroName,' ',DefineStatusNames[OldStatus]]); {$ENDIF} // check if MacroName was already changed Change:=Stack[StackPointer]; while (Change<>nil) do begin if (CompareIdentifierPtrs(Pointer(MacroName),Pointer(Change^.Name))=0) then begin // old status is already saved exit; end; Change:=Change^.Next; end; {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['AddStackChange ADD ',MacroName,' ',DefineStatusNames[OldStatus]]); {$ENDIF} New(Change); FillChar(Change^,SizeOf(TDefineChange),0); Change^.Name:=MacroName; Change^.OldStatus:=OldStatus; Change^.Next:=Stack[StackPointer]; Stack[StackPointer]:=Change; end; function GetStatus(Identifier: PChar): TDefineStatus; var AVLNode: TAVLTreeNode; begin AVLNode:=CurDefines.FindKey(Identifier,@ComparePCharWithDefineValue); if AVLNode<>nil then Result:=TDefineValue(AVLNode.Data).Status else Result:=dsUnknown; end; procedure SetStatus(Identifier: PChar; NewStatus: TDefineStatus; SaveOnStack, SetGlobal: boolean); var AVLNode: TAVLTreeNode; DefValue: TDefineValue; i: Integer; Change: PDefineChange; begin {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['SetStatus ',GetIdentifier(Identifier),' Old=',DefineStatusNames[GetStatus(Identifier)],' New=',DefineStatusNames[NewStatus],' SaveOnStack=',SaveOnStack,' SetGlobal=',SetGlobal]); {$ENDIF} AVLNode:=CurDefines.FindKey(Identifier,@ComparePCharWithDefineValue); if AVLNode=nil then begin if NewStatus<>dsUnknown then begin DefValue:=TDefineValue.Create; DefValue.Name:=GetIdentifier(Identifier); DefValue.Status:=NewStatus; CurDefines.Add(DefValue); if SaveOnStack then AddStackChange(DefValue.Name,dsUnknown); end else begin // no change end; end else begin DefValue:=TDefineValue(AVLNode.Data); if NewStatus<>dsUnknown then begin if NewStatus<>DefValue.Status then begin if SaveOnStack then AddStackChange(DefValue.Name,DefValue.Status); DefValue.Status:=NewStatus; end; end else begin if SaveOnStack then AddStackChange(DefValue.Name,DefValue.Status); CurDefines.Delete(AVLNode); DefValue.Free; end; end; if SetGlobal then begin for i:=StackPointer downto 0 do begin Change:=Stack[i]; while Change<>nil do begin if CompareIdentifiers(PChar(Change^.Name),Identifier)=0 then begin if (Change^.OldStatus=dsUnknown) or (Change^.OldStatus=NewStatus) then begin // ok end else begin Change^.OldStatus:=dsUnknown; end; end; Change:=Change^.Next; end; end; end; {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['SetStatus ',GetIdentifier(Identifier),' Cur=',DefineStatusNames[GetStatus(Identifier)],' Should=',DefineStatusNames[NewStatus]]); {$ENDIF} end; procedure InitDefines; var i: Integer; CurName: string; Node: TCodeTreeNode; ExprStart: integer; ExprEnd: integer; begin CurDefines:=TAVLTree.Create(@CompareDefineValues); {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['InitDefines ',Defines<>nil,' ',Undefines<>nil]); {$ENDIF} if Undefines<>nil then begin for i:=0 to Undefines.Count-1 do if Undefines[i]<>'' then SetStatus(PChar(Undefines[i]),dsNotDefined,false,false); end; if Defines<>nil then begin for i:=0 to Defines.Count-1 do begin CurName:=Defines[i]; if System.Pos('=',CurName)>0 then CurName:=Defines.Names[i]; if CurName='' then continue; SetStatus(PChar(CurName),dsDefined,false,false); end; end; if UndefH2PasFunctions then begin Node:=Tree.Root; while Node<>nil do begin if ((Node.Desc=cdnIf) or (Node.Desc=cdnElseIf)) then begin if GetIfExpression(Node,ExprStart,ExprEnd) then begin MoveCursorToPos(ExprStart); repeat ReadNextAtom; if AtomStart>=ExprEnd then break; if ComparePrefixIdent(H2Pas_Function_Prefix,@Src[AtomStart]) then SetStatus(@Src[AtomStart],dsNotDefined,false,false); until false; end; end; Node:=Node.Next; end; end; end; procedure FreeDefines; begin if CurDefines=nil then exit; CurDefines.FreeAndClear; FreeAndNil(CurDefines); end; procedure Push; begin inc(StackPointer); if StackPointer=length(Stack) then SetLength(Stack,length(Stack)*2+10); Stack[StackPointer]:=nil; end; procedure Pop; var Change: PDefineChange; begin if StackPointer=0 then RaiseException(20170422131842,'TCompilerDirectivesTree.DisableUnreachableBlocks.Pop without Push'); // undo all changes while Stack[StackPointer]<>nil do begin Change:=Stack[StackPointer]; SetStatus(PChar(Change^.Name),Change^.OldStatus,false,false); Stack[StackPointer]:=Change^.Next; Dispose(Change); end; dec(StackPointer); end; var Node: TCodeTreeNode; NextNode: TCodeTreeNode; NameStart: integer; NewStatus: TDefineStatus; Identifier: PChar; OldStatus: TDefineStatus; HasValue: boolean; ValueStart: integer; ExprNode: TCodeTreeNode; IsIfBlock: Boolean; BlockIsAlwaysReached: Boolean; BlockIsNeverReached: Boolean; BlockIsReachable: Boolean; begin InitDefines; InitStack; try Node:=Tree.Root; while Node<>nil do begin NextNode:=Node.Next; {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks Node=',CDNodeDescAsString(Node.Desc),'=',GetDirective(Node)]); {$ENDIF} case Node.Desc of cdnIf, cdnElse: begin if Node.Desc=cdnIf then begin IsIfBlock:=true; end else begin IsIfBlock:=false; // close prior block Pop; end; // start new block Push; if IsIfBlock then begin ExprNode:=Node; end else begin if Node.PriorBrother.Desc=cdnIf then begin ExprNode:=Node.PriorBrother; end else begin ExprNode:=nil; end; end; {$IFDEF VerboseDisableUnreachableIFDEFs} if (ExprNode<>nil) then DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks Expr=',GetIfExpressionString(ExprNode),' Simple=',IsIfExpressionSimple(ExprNode,NameStart)]) else DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks Expr=nil']); {$ENDIF} if (ExprNode<>nil) and IsIfExpressionSimple(ExprNode,NameStart) then begin // a simple expression Identifier:=@Src[NameStart]; if (Node.SubDesc=cdnsIfndef)=IsIfBlock then NewStatus:=dsNotDefined else NewStatus:=dsDefined; OldStatus:=GetStatus(Identifier); BlockIsReachable:=(OldStatus=dsUnknown) or (OldStatus=NewStatus); BlockIsAlwaysReached:=OldStatus=NewStatus; BlockIsNeverReached:=(OldStatus<>dsUnknown) and (OldStatus<>NewStatus); {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks Identifier=',GetIdentifier(Identifier),' Reachable=',BlockIsReachable,' Always=',BlockIsAlwaysReached,' Never=',BlockIsNeverReached,' NewStatus=',DefineStatusNames[NewStatus]]); {$ENDIF} if BlockIsReachable then SetStatus(Identifier,NewStatus,true,false); if BlockIsAlwaysReached or BlockIsNeverReached then begin // this node can be removed if BlockIsNeverReached or (Node.FirstChild=nil) then begin NextNode:=Node.NextBrother; if (NextNode<>nil) and (NextNode.Desc=cdnEnd) then begin // if the next node is an end node it will be disabled too NextNode:=NextNode.NextSkipChilds; end; end; // we can Pop here, because // this the last block // or this is the first block, then the next block will // become the new first block Pop; if BlockIsAlwaysReached then DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks BlockIsAlwaysReached ',GetDirective(Node)]); if BlockIsNeverReached then DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks BlockIsNeverReached ',GetDirective(Node)]); DisableIfNode(Node,BlockIsNeverReached,Changed); end; end else begin // a complex expression (If, ElseIf, Else) // assume: it is reachable end; end; cdnElseIf: begin // if there is an ElseIf block, then there must be an IF block in front // And the IF block in front must be reachable, // otherwise it would be disabled Pop; // If+ElseIf gives a complex expression // assume: it is reachable Push; end; cdnEnd: begin Pop; end; cdnDefine: if ((Node.SubDesc=cdnsDefine) or (Node.SubDesc=cdnsUndef) or (Node.SubDesc=cdnsSetC)) and GetDefineNameAndValue(Node,NameStart,HasValue,ValueStart) then begin if Node.SubDesc=cdnsDefine then NewStatus:=dsDefined else NewStatus:=dsNotDefined; if GetStatus(@Src[NameStart])=NewStatus then begin // this define is not needed NextNode:=NextNode.NextSkipChilds; DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks DEFINE is already, always set to this value']); DisableDefineNode(Node,Changed); end else begin // set status on all levels SetStatus(@Src[NameStart],NewStatus,true,true); end; end; end; Node:=NextNode; end; finally FreeStack; FreeDefines; end; {$IFDEF VerboseDisableUnreachableIFDEFs} DebugLn(['TCompilerDirectivesTree.DisableUnreachableBlocks END']); {$ENDIF} end; procedure TCompilerDirectivesTree.DisableNode(Node: TCodeTreeNode; var Changed: boolean; WithContent: boolean); begin if Node=nil then exit; case Node.Desc of cdnDefine, cdnInclude: DisableDefineNode(Node,Changed); cdnIf, cdnElseIf, cdnElse: DisableIfNode(Node,WithContent,Changed); end; end; procedure TCompilerDirectivesTree.DisableDefineNode(Node: TCodeTreeNode; var Changed: boolean); var FromPos: LongInt; ToPos: LongInt; NewSrc: String; begin if not DisableUnusedDefines then exit; //DebugLn(['TCompilerDirectivesTree.DisableDefineNode ',GetDirective(Node)]); if RemoveDisabledDirectives then begin // remove directive (including space+empty lines in front and spaces behind) FromPos:=Node.StartPos; while (FromPos>1) and (IsSpaceChar[Src[FromPos-1]]) do dec(FromPos); ToPos:=FindCommentEnd(Src,Node.StartPos,NestedComments); ToPos:=FindLineEndOrCodeAfterPosition(Src,ToPos,SrcLen+1,NestedComments); NewSrc:=''; if (FromPos=1) and (ToPosSrc[ToPos-1]) then inc(ToPos); end; Replace(FromPos,ToPos,NewSrc); end else begin // disable directive -> {off $Define MacroName} Replace(Node.StartPos+1,Node.StartPos+1,'off '); end; Changed:=true; InternalRemoveNode(Node); end; procedure TCompilerDirectivesTree.DisableIfNode(Node: TCodeTreeNode; WithContent: boolean; var Changed: boolean); procedure RaiseImpossible; begin RaiseException(20170422131846,'TCompilerDirectivesTree.DisableIfNode impossible'); end; function GetExpr(ExprNode: TCodeTreeNode; out Negated: boolean): string; var ExprStart: integer; ExprEnd: integer; begin if not GetIfExpression(ExprNode,ExprStart,ExprEnd) then RaiseImpossible; Result:=copy(Src,ExprStart,ExprEnd-ExprStart); Negated:=ExprNode.SubDesc=cdnsIfNdef; if (ExprNode.SubDesc=cdnsIfdef) or (ExprNode.SubDesc=cdnsIfNdef) then Result:='defined('+Result+')'; end; procedure CommentCode(FromPos, ToPos: integer); var p: LongInt; NewSrc: String; begin p:=FromPos; repeat // find code MoveCursorToPos(p); ReadNextAtom; if AtomStart>=ToPos then break; // there is code to comment // = > start comment Replace(AtomStart,AtomStart,'(* '); p:=AtomStart; while (p move child nodes in front of Node (keep source positions) FirstChild:=Node.FirstChild; LastChild:=Node.LastChild; if FirstChild<>nil then begin ChildNode:=FirstChild; while ChildNode<>nil do begin ChildNode.Parent:=Node.Parent; ChildNode:=ChildNode.NextBrother; end; FirstChild.PriorBrother:=Node.PriorBrother; LastChild.NextBrother:=Node; if FirstChild.PriorBrother=nil then begin if Node.Parent<>nil then Node.Parent.FirstChild:=FirstChild; end else begin FirstChild.PriorBrother.NextBrother:=FirstChild; end; Node.PriorBrother:=LastChild; Node.FirstChild:=nil; Node.LastChild:=nil; end; end else begin // free nodes and delete code while Node.FirstChild<>nil do InternalRemoveNode(Node.FirstChild); FromPos:=FindCommentEnd(Src,Node.StartPos,NestedComments); ToPos:=Node.NextBrother.StartPos; if RemoveDisabledDirectives then begin // delete content Replace(FromPos,ToPos,''); end else begin // comment content CommentCode(FromPos,ToPos); end; end; end; var FromPos: LongInt; ToPos: LongInt; Expr: String; ElseNode: TCodeTreeNode; ElseName: String; Expr2: String; NewSrc: String; PrevNode: TCodeTreeNode; NewDesc: TCompilerDirectiveNodeDesc; NewSubDesc: TCompilerDirectiveNodeDesc; Simplified: Boolean; ExprNegated: boolean; Expr2Negated: boolean; p: LongInt; begin if (Node.NextBrother=nil) then RaiseImpossible; if (Node.Desc<>cdnIf) and (Node.Desc<>cdnElseIf) and (Node.Desc<>cdnElse) then RaiseImpossible; DisableContent; Changed:=true; // fix all following elseif and else nodes Expr:=GetExpr(Node,ExprNegated); ElseNode:=Node.NextBrother; while ElseNode<>nil do begin if (ElseNode.Desc=cdnElse) or (ElseNode.Desc=cdnElseIf) then begin PrevNode:=ElseNode.PriorBrother; if (PrevNode.Desc=cdnIf) then begin NewDesc:=cdnIf; if ElseNode.SubDesc=cdnsIfC then NewSubDesc:=cdnsIfC else NewSubDesc:=cdnsIf; // IFDEF, IF -> IF end else begin NewDesc:=cdnElseIf; if (ElseNode.SubDesc=cdnsElseIf) or (ElseNode.SubDesc=cdnsElse) then NewSubDesc:=cdnsElIfC else NewSubDesc:=cdnsElseIf; // Else, ElseIf -> ElseIF end; ElseName:=CDNodeSubDescAsString(NewSubDesc); // convert {$Else} to {$ElseIf not (Expr)} // convert {$ElseIf Expr2} to {$ElseIf (Expr2) and not (Expr)} NewSrc:='('+Expr+')'; if not ExprNegated then NewSrc:='not '+NewSrc; if ElseNode.Desc=cdnElse then NewSrc:='{$'+ElseName+' '+NewSrc+'}' else begin Expr2:=GetExpr(ElseNode,Expr2Negated); NewSrc:='{$'+ElseName+' ('+Expr2+') and '+NewSrc+'}'; end; Replace(ElseNode.StartPos, FindCommentEnd(Src,ElseNode.StartPos,NestedComments),NewSrc); ElseNode.Desc:=NewDesc; ElseNode.SubDesc:=NewSubDesc; Simplified:=false; CheckAndImproveExpr_Brackets(ElseNode,Simplified); CheckAndImproveExpr_IfDefinedMacro(ElseNode,Simplified); end else begin break; end; ElseNode:=ElseNode.NextBrother; end; FromPos:=Node.StartPos; if RemoveDisabledDirectives then begin if (Node.NextBrother.Desc=cdnEnd) and (Node.Desc=cdnIf) then begin // remove the whole IF..END block ToPos:=FindCommentEnd(Src,Node.NextBrother.StartPos,NestedComments); ToPos:=FindLineEndOrCodeAfterPosition(Src,ToPos,SrcLen+1,NestedComments); end else begin // remove a sub block ToPos:=Node.NextBrother.StartPos; end; if WithContent then begin // remove node source with content if (FromPos>1) and (Src[FromPos-1] in [#10,#13]) and (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13]) then begin // the directive has a complete line // remove the line end too inc(ToPos); if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13]) and (Src[ToPos]<>Src[ToPos-1]) then inc(ToPos); if (ToPos<=SrcLen) and (Src[ToPos] in [#10,#13]) then begin // there is an empty line behind the directive // check if there is an empty line in front of the directive p:=FromPos; if (p>1) and (Src[p-1] in [#10,#13]) then begin dec(p); if (p>1) and (Src[p-1] in [#10,#13]) and (Src[p]<>Src[p-1]) then dec(p); if (p>1) and (Src[p-1] in [#10,#13]) then begin // there is an empty line in front of the directive too // => remove one empty line FromPos:=p; end; end; end; end; Replace(FromPos,ToPos,''); end else begin // remove node source keeping content (child node source) Replace(FromPos,FindCommentEnd(Src,FromPos,NestedComments),''); if Node.NextBrother.Desc=cdnEnd then begin ToPos:=FindCommentEnd(Src,Node.NextBrother.StartPos,NestedComments); ToPos:=FindLineEndOrCodeAfterPosition(Src,ToPos,SrcLen+1,NestedComments); Replace(Node.NextBrother.StartPos,ToPos,''); end; end; end else begin // disable directive -> {$off IfDef MacroName} Replace(FromPos+1,FromPos+1,'off '); if Node.NextBrother.Desc=cdnEnd then Replace(Node.NextBrother.StartPos+1,Node.NextBrother.StartPos+1,'off '); end; if Node.NextBrother.Desc=cdnEnd then InternalRemoveNode(Node.NextBrother); InternalRemoveNode(Node); end; procedure TCompilerDirectivesTree.InternalRemoveNode(Node: TCodeTreeNode); var AVLNode: TAVLTreeNode; MacroNode: TCompilerMacroStats; begin // clear references if Macros<>nil then begin AVLNode:=Macros.FindLowest; while AVLNode<>nil do begin MacroNode:=TCompilerMacroStats(AVLNode.Data); if MacroNode.LastDefineNode=Node then MacroNode.LastDefineNode:=nil; if MacroNode.LastReadNode=Node then MacroNode.LastReadNode:=nil; AVLNode:=Macros.FindSuccessor(AVLNode); end; end; // free node Tree.DeleteNode(Node); end; procedure TCompilerDirectivesTree.RaiseException(id: int64; const ErrorMsg: string); begin fLastErrorMsg:=ErrorMsg; fLastErrorPos:=AtomStart; fLastErrorId:=id; if Code<>nil then Code.AbsoluteToLineCol(AtomStart,fLastErrorXY.Y,fLastErrorXY.X) else fLastErrorXY:=Point(0,0); RaiseLastError; end; procedure TCompilerDirectivesTree.RaiseLastError; begin raise ECDirectiveParserException.Create(Self, fLastErrorId, SrcPosToStr(fLastErrorPos)+' Error: '+ErrorMsg); end; procedure TCompilerDirectivesTree.RemoveEmptyNodes(var Changed: boolean); var Node: TCodeTreeNode; NextNode: TCodeTreeNode; procedure CheckNode; begin //DebugLn(['CheckNode ',Node.Desc=cdnIf,' ',(Node.NextBrother<>nil),' ',(Node.FirstChild=nil),' ',GetDirective(Node)]); case Node.Desc of cdnIf,cdnElseIf,cdnElse: if (Node.NextBrother<>nil) and (Node.FirstChild=nil) then begin case Node.NextBrother.Desc of cdnEnd,cdnElseIf,cdnElse: begin //DebugLn(['CheckNode Checking if empty ...']); MoveCursorToPos(Node.StartPos); // skip directive ReadNextAtom; // read the following atom (token or directive) ReadNextAtom; if AtomStart=Node.NextBrother.StartPos then begin // node is empty DebugLn(['TCompilerDirectivesTree.RemoveEmptyNodes node only contains spaces and comments ',GetDirective(Node)]); DisableIfNode(Node,true,Changed); end; end; end; end; end; end; begin //DebugLn(['TCompilerDirectivesTree.RemoveEmptyNodes ']); // check nodes from end to start Node:=Tree.Root; while (Node.NextBrother<>nil) do Node:=Node.NextBrother; while (Node.LastChild<>nil) do Node:=Node.LastChild; while Node<>nil do begin NextNode:=Node.Prior; CheckNode; Node:=NextNode; end; end; function TCompilerDirectivesTree.InsertDefine(Position: integer; const NewSrc: string; SubDesc: TCompilerDirectiveNodeDesc): TCodeTreeNode; var ParentNode: TCodeTreeNode; NextBrotherNode: TCodeTreeNode; begin Replace(Position,Position,NewSrc); ParentNode:=FindNodeAtPos(Position); if ParentNode=nil then ParentNode:=Tree.Root; while (ParentNode<>Tree.Root) and (ParentNode.EndPos=Position) do ParentNode:=ParentNode.Parent; Result:=TCodeTreeNode.Create; Result.Desc:=cdnDefine; Result.SubDesc:=SubDesc; Result.StartPos:=FindNextCompilerDirective(Src,Position,NestedComments); Result.EndPos:=FindCommentEnd(Src,Result.StartPos,NestedComments); NextBrotherNode:=ParentNode.FirstChild; while (NextBrotherNode<>nil) and (NextBrotherNode.StartPos<=Position) do NextBrotherNode:=NextBrotherNode.NextBrother; if NextBrotherNode<>nil then begin Tree.AddNodeInFrontOf(NextBrotherNode,Result); end else begin Tree.AddNodeAsLastChild(ParentNode,Result); if ParentNode.EndPosnil then begin Macros.FreeAndClear; FreeAndNil(Macros); end; end; procedure TCompilerDirectivesTree.Parse; begin Parse(Code,NestedComments); end; {$IFOPT R+}{$DEFINE RangeChecking}{$ENDIF} {$R-} procedure TCompilerDirectivesTree.Parse(aCode: TCodeBuffer; aNestedComments: boolean); procedure RaiseDanglingIFDEF; begin RaiseException(20170422131848,'missing EndIf'); end; var DirectiveName: PChar; Node: TCodeTreeNode; begin if (Code=aCode) and (NestedComments=aNestedComments) and (not UpdateNeeded) then begin if FLastErrorMsg<>'' then RaiseLastError; exit; end; FLastErrorMsg:=''; Code:=aCode; NestedComments:=aNestedComments; InitParser; repeat ReadRawNextPascalAtom(Src,SrcPos,AtomStart,NestedComments); //DebugLn(['TCompilerDirectivesTree.Parse ',NestedComments,' ',copy(Src,AtomStart,SrcPos-AtomStart)]); if SrcPos<=SrcLen then begin if (Src[AtomStart]='{') and (Src[AtomStart+1]='$') then begin // compiler directive DirectiveName:=@Src[AtomStart+2]; //DebugLn(['ParseCompilerDirectives ',GetIdentifier(DirectiveName)]); FDefaultDirectiveFuncList.DoItCaseInsensitive(DirectiveName); end; end else begin break; end; until false; // close nodes Node:=CurNode; while Node<>nil do begin Node.EndPos:=AtomStart; Node:=Node.Parent; end; if CurNode<>Tree.Root then RaiseDanglingIFDEF; end; {$IFDEF RangeChecking}{$R+}{$UNDEF RangeChecking}{$ENDIF} function TCompilerDirectivesTree.UpdateNeeded: boolean; begin Result:=true; if (Code=nil) or (Tree=nil) or (Tree.Root=nil) then exit; if Code.ChangeStep<>ParseChangeStep then exit; Result:=false; end; procedure TCompilerDirectivesTree.ReduceCompilerDirectives( Undefines, Defines: TStrings; var Changed: boolean); (* Check and improve the following cases 1. {$DEFINE Name} and Name is never used afterwards -> disable 2. {$DEFINE Name} ... Name is not used here ... {$DEFINE Name} -> disable first 3. {$IFDEF Name}... only comments and spaces ...{$ENDIF} -> disable the whole block 4. {$IFNDEF Name} ... only comments and spaces ... {$DEFINE Name} ... only comments and spaces ... {$ENDIF} -> disable the IFNDEF and the ENDIF and keep the DEFINE *) function GetMacroNode(p: PChar): TCompilerMacroStats; var AVLNode: TAVLTreeNode; begin AVLNode:=Macros.FindKey(p,@ComparePCharWithCompilerMacroStats); if AVLNode<>nil then Result:=TCompilerMacroStats(AVLNode.Data) else Result:=nil; end; procedure CheckMacroInExpression(Node: TCodeTreeNode; NameStart: integer; Complex: boolean; var {%H-}Changed: boolean); var MacroNode: TCompilerMacroStats; begin MacroNode:=GetMacroNode(@Src[NameStart]); if MacroNode=nil then begin MacroNode:=TCompilerMacroStats.Create; MacroNode.Name:=GetIdentifier(@Src[NameStart]); Macros.Add(MacroNode); end; MacroNode.LastReadNode:=Node; if not Complex then begin end; end; procedure CheckDefine(Node: TCodeTreeNode; var Changed: boolean); var MacroNode: TCompilerMacroStats; NameStart: integer; HasValue: boolean; ValueStart: integer; begin if (Node.SubDesc<>cdnsDefine) and (Node.SubDesc<>cdnsUndef) and (Node.SubDesc<>cdnsSetC) then exit; if not GetDefineNameAndValue(Node,NameStart,HasValue,ValueStart) then exit; MacroNode:=GetMacroNode(@Src[NameStart]); if MacroNode=nil then begin MacroNode:=TCompilerMacroStats.Create; MacroNode.Name:=GetIdentifier(@Src[NameStart]); Macros.Add(MacroNode); end; if (MacroNode.LastReadNode=nil) and (MacroNode.LastDefineNode<>nil) and (MacroNode.LastDefineNode.Parent=Node.Parent) and ((MacroNode.LastDefineNode.SubDesc=cdnsUndef)=(Node.SubDesc=cdnsUndef)) then begin // last define was never used -> disable it DebugLn(['TCompilerDirectivesTree.ReduceCompilerDirectives this define was already set to this value']); DisableDefineNode(MacroNode.LastDefineNode,Changed); end; MacroNode.LastReadNode:=nil; MacroNode.LastDefineNode:=Node; end; var Node: TCodeTreeNode; ExprStart: integer; ExprEnd: integer; Complex: Boolean; AtomCount: Integer; NextNode: TCodeTreeNode; begin try ResetMacros; Node:=Tree.Root; while Node<>nil do begin NextNode:=Node.Next; case Node.Desc of cdnIf,cdnElseIf: if GetIfExpression(Node,ExprStart,ExprEnd) then begin // improve expression CheckAndImproveExpr_Brackets(Node,Changed); CheckAndImproveExpr_IfDefinedMacro(Node,Changed); //DebugLn(['TCompilerDirectivesTree.ReduceCompilerDirectives Expr=',copy(Src,ExprStart,ExprEnd-ExprStart)]); // check if it is a complex expression or just one macro AtomCount:=0; if (Node.SubDesc=cdnsIf) or (Node.SubDesc=cdnsIfC) or (Node.SubDesc=cdnsElseIf) then begin MoveCursorToPos(ExprStart); repeat ReadNextAtom; inc(AtomCount); until AtomStart>=ExprEnd; end; Complex:=AtomCount>1; // mark all macros as read MoveCursorToPos(ExprStart); repeat ReadNextAtom; if AtomIsIdentifier then begin CheckMacroInExpression(Node,AtomStart,Complex,Changed); end; until AtomStart>=ExprEnd; end; cdnDefine: CheckDefine(Node,Changed); end; Node:=NextNode; end; DisableAllUnusedDefines(Changed); MoveIfNotThenDefsUp(Changed); DisableUnreachableBlocks(Undefines,Defines,Changed); RemoveEmptyNodes(Changed); finally ClearMacros; end; end; procedure TCompilerDirectivesTree.GatherH2PasFunctions(out ListOfH2PasFunctions: TFPList; FindDefNodes: boolean); var InInterface: boolean; procedure ReadFunction; var HeaderStart: LongInt; HeaderEnd: LongInt; FuncName: String; IsForward: Boolean; BlockLevel: Integer; CurH2PasFunc: TH2PasFunction; BeginStart: Integer; BeginEnd: Integer; IsExternal: Boolean; begin HeaderStart:=AtomStart; // read name ReadNextAtom; if not AtomIsIdentifier then exit; FuncName:=GetAtom; // read parameter list ReadNextAtom; if AtomIs('(') then begin if not ReadTilBracketClose(')') then exit; ReadNextAtom; end; // read colon if not AtomIs(':') then exit; // read result type ReadNextAtom; if not AtomIsIdentifier then exit; // read semicolon ReadNextAtom; if not AtomIs(';') then exit; HeaderEnd:=SrcPos; // read function modifiers IsForward:=false; IsExternal:=false; repeat ReadNextAtom; if (AtomStart<=SrcLen) and IsKeyWordProcedureSpecifier.DoItCaseInsensitive(@Src[AtomStart]) then begin if UpAtomIs('EXTERNAL') then IsExternal:=true; if UpAtomIs('FORWARD') then IsForward:=true; repeat ReadNextAtom; until (AtomStart>SrcLen) or AtomIs(';'); HeaderEnd:=SrcPos; end else break; until false; // read begin..end block BeginStart:=-1; BeginEnd:=-1; if (not IsForward) and (not InInterface) and (not IsExternal) and UpAtomIs('BEGIN') then begin BeginStart:=AtomStart; BlockLevel:=1; repeat ReadNextAtom; if (AtomStart>SrcLen) then break; if UpAtomIs('END') then begin dec(BlockLevel); if BlockLevel=0 then begin BeginEnd:=SrcPos; ReadNextAtom; if AtomIs(';') then BeginEnd:=SrcPos; break; end; end else if UpAtomIs('BEGIN') or UpAtomIs('ASM') then inc(BlockLevel); until false; end else begin // undo forward read to make sure that current atom is the last of the function MoveCursorToPos(HeaderEnd); end; // found a function //DebugLn(['ReadFunction ',copy(Src,HeaderStart,FuncEnd-HeaderStart)]); CurH2PasFunc:=TH2PasFunction.Create; CurH2PasFunc.Name:=FuncName; CurH2PasFunc.HeaderStart:=HeaderStart; CurH2PasFunc.HeaderEnd:=HeaderEnd; CurH2PasFunc.BeginStart:=BeginStart; CurH2PasFunc.BeginEnd:=BeginEnd; CurH2PasFunc.IsForward:=IsForward; CurH2PasFunc.InInterface:=InInterface; CurH2PasFunc.IsExternal:=IsExternal; if ListOfH2PasFunctions=nil then ListOfH2PasFunctions:=TFPList.Create; ListOfH2PasFunctions.Add(CurH2PasFunc); end; procedure DoFindDefNodes; var i: Integer; CurH2PasFunc: TH2PasFunction; TreeOfForwardFuncs: TAVLTree; TreeOfBodyFuncs: TAVLTree; AVLNode: TAVLTreeNode; BodyAVLNode: TAVLTreeNode; BodyFunc: TH2PasFunction; begin if ListOfH2PasFunctions=nil then exit; // create a tree of the function definitions // and a tree of the function bodies TreeOfForwardFuncs:=TAVLTree.Create(@CompareH2PasFuncByNameAndPos); TreeOfBodyFuncs:=TAVLTree.Create(@CompareH2PasFuncByNameAndPos); for i:=0 to ListOfH2PasFunctions.Count-1 do begin CurH2PasFunc:=TH2PasFunction(ListOfH2PasFunctions[i]); if CurH2PasFunc.NeedsBody then TreeOfForwardFuncs.Add(CurH2PasFunc) else if (CurH2PasFunc.BeginStart>0) then TreeOfBodyFuncs.Add(CurH2PasFunc); end; // search for every definition the corresponding body AVLNode:=TreeOfForwardFuncs.FindLowest; while AVLNode<>nil do begin CurH2PasFunc:=TH2PasFunction(AVLNode.Data); if CurH2PasFunc.DefNode=nil then begin BodyAVLNode:=TreeOfBodyFuncs.FindLeftMostKey(Pointer(CurH2PasFunc.Name), @ComparePCharWithH2PasFuncName); if BodyAVLNode<>nil then begin // there is at least one body with this name repeat BodyFunc:=TH2PasFunction(BodyAVLNode.Data); if BodyFunc.DefNode=nil then begin // this body node with the same name has not yet a definition node // => found the corresponding node BodyFunc.DefNode:=CurH2PasFunc; CurH2PasFunc.DefNode:=BodyFunc; break; end else begin // this body node has already a definition node // search next body node with same name BodyAVLNode:=TreeOfBodyFuncs.FindSuccessor(BodyAVLNode); if (BodyAVLNode=nil) or (ComparePCharWithH2PasFuncName( Pointer(CurH2PasFunc.Name),BodyAVLNode.Data)<>0) then break; end; until false; end; end; AVLNode:=TreeOfBodyFuncs.FindSuccessor(AVLNode); end; // clean up TreeOfForwardFuncs.Free; TreeOfBodyFuncs.Free; end; begin ListOfH2PasFunctions:=nil; InInterface:=false; MoveCursorToPos(1); repeat ReadNextAtom; if SrcPos>SrcLen then break; if UpAtomIs('FUNCTION') then begin ReadFunction; end else if UpAtomIs('INTERFACE') then begin InInterface:=true; end else if UpAtomIs('IMPLEMENTATION') then begin InInterface:=false; end; until false; if FindDefNodes then DoFindDefNodes; end; procedure TCompilerDirectivesTree.FixMissingH2PasDirectives(var Changed: boolean); { Adds the directives around the function bodies, that h2pas forgets to add. } type TBodyBlock = record Definition: TCodeTreeNode; FirstBodyFunc: TH2PasFunction; LastBodyFunc: TH2PasFunction; end; var CurBodyBlock: TBodyBlock; MacroNames: TStrings; // the Objects are the TCodeTreeNode ListOfH2PasFunctions: TFPList; LocalChange: boolean; function IsSameDirective(OldNode: TCodeTreeNode; Position: integer; out NewNode: TCodeTreeNode): boolean; begin NewNode:=FindNodeAtPos(Position); //if OldNode<>nil then DebugLn(['IsSameDirective OldNode=',OldNode.StartPos,' "',copy(Src,OldNode.StartPos,OldNode.EndPos-OldNode.StartPos),'"']); //if NewNode<>nil then DebugLn(['IsSameDirective NewNode=',NewNode.StartPos,' "',copy(Src,NewNode.StartPos,NewNode.EndPos-NewNode.StartPos),'"']); Result:=(NewNode<>nil) and (NewNode=OldNode); end; function HasCodeBetween(FromPos, ToPos: integer): boolean; begin if FromPos<1 then FromPos:=1; if FromPos>ToPos then exit(false); MoveCursorToPos(FromPos); ReadNextAtom; Result:=AtomStart0 then begin for i:=0 to ListOfH2PasFunctions.Count-1 do begin Func:=TH2PasFunction(ListOfH2PasFunctions[i]); Func.AdjustPositionsAfterInsert(FromPos,ToPos,DiffPos); end; end; end; procedure StartBodyBlock(BodyFunc: TH2PasFunction; DefNode: TCodeTreeNode); begin CurBodyBlock.Definition:=DefNode; CurBodyBlock.FirstBodyFunc:=BodyFunc; CurBodyBlock.LastBodyFunc:=BodyFunc; end; procedure EndBodyBlock; var MacroName: String; InsertPos: LongInt; IsNewMacro: boolean; begin if CurBodyBlock.Definition=nil then exit; if CurBodyBlock.Definition<>Tree.Root then begin DebugLn(['TCompilerDirectivesTree.FixMissingH2PasDirectives add missing directives']); // create unique macro name MacroName:=GetMacroNameForNode(CurBodyBlock.Definition,IsNewMacro); if IsNewMacro then begin // insert $DEFINE InsertPos:=FindCommentEnd(Src,CurBodyBlock.Definition.StartPos,NestedComments); LocalReplace(InsertPos,InsertPos,LineEnding+'{$DEFINE '+MacroName+'}'); end; // insert $IFDEF InsertPos:=FindLineEndOrCodeInFrontOfPosition(Src, CurBodyBlock.FirstBodyFunc.HeaderStart,1,NestedComments,true); LocalReplace(InsertPos,InsertPos,LineEnding+'{$IFDEF '+MacroName+'}'); // insert $ENDIF InsertPos:=FindLineEndOrCodeAfterPosition(Src, CurBodyBlock.LastBodyFunc.BeginEnd,1,NestedComments,true); LocalReplace(InsertPos,InsertPos,LineEnding+'{$ENDIF '+MacroName+'}'); end; FillChar(CurBodyBlock,SizeOf(TBodyBlock),0); end; var i: Integer; BodyFunc: TH2PasFunction; LastDefNode: TCodeTreeNode; BodyNode: TCodeTreeNode; begin ListOfH2PasFunctions:=nil; MacroNames:=nil; LocalChange:=false; try GatherH2PasFunctions(ListOfH2PasFunctions,true); DebugLn(['TCompilerDirectivesTree.FixMissingH2PasDirectives ',ListOfH2PasFunctions=nil]); if ListOfH2PasFunctions=nil then exit; FillChar(CurBodyBlock,SizeOf(TBodyBlock),0); LastDefNode:=nil; for i:=0 to ListOfH2PasFunctions.Count-1 do begin BodyFunc:=TH2PasFunction(ListOfH2PasFunctions[i]); //DebugLn(['TCompilerDirectivesTree.FixMissingH2PasDirectives DefNode=',(BodyFunc.DefNode<>nil),' Body="',copy(Src,BodyFunc.HeaderStart,BodyFunc.HeaderEnd-BodyFunc.HeaderStart),'"']); if (BodyFunc.BeginStart<1) or (BodyFunc.DefNode=nil) then continue; BodyNode:=FindNodeAtPos(BodyFunc.HeaderStart); if BodyNode<>Tree.Root then begin // this body has already a directive block continue; end; // this function is a body and has a definition if (CurBodyBlock.LastBodyFunc<>nil) and HasCodeBetween(CurBodyBlock.LastBodyFunc.BeginEnd,BodyFunc.HeaderStart) then begin // there is code between last function body and current function body // end last block EndBodyBlock; end; if not IsSameDirective(LastDefNode, BodyFunc.DefNode.HeaderStart,LastDefNode) then begin // another directive block => end last block EndBodyBlock; end; if (CurBodyBlock.Definition=nil) then begin // a new block StartBodyBlock(BodyFunc, LastDefNode); end else begin // continue current block CurBodyBlock.LastBodyFunc:=BodyFunc; end; end; // end last block EndBodyBlock; finally if ListOfH2PasFunctions<>nil then for i:=0 to ListOfH2PasFunctions.Count-1 do TObject(ListOfH2PasFunctions[i]).Free; ListOfH2PasFunctions.Free; MacroNames.Free; if LocalChange then begin Changed:=true; Parse(Code,NestedComments); end; end; end; function TCompilerDirectivesTree.NodeStartToCodePos(Node: TCodeTreeNode; out CodePos: TCodeXYPosition): boolean; begin CodePos.Code:=nil; CodePos.Y:=0; CodePos.X:=0; if (Node=nil) or (Code=nil) then exit(false); CodePos.Code:=Code; Code.AbsoluteToLineCol(Node.StartPos,CodePos.Y,CodePos.X); Result:=true; end; function TCompilerDirectivesTree.SrcPosToStr(p: integer; WithFilename: boolean): string; var Line: integer; Column: integer; begin if Code=nil then exit('P='+IntToStr(p)); if WithFilename then Result:=Code.Filename else Result:=''; Code.AbsoluteToLineCol(p,Line,Column); Result+='('+IntToStr(Line)+','+IntToStr(Column)+')'; end; function TCompilerDirectivesTree.FindResourceDirective(const Filename: string; StartPos: integer): TCodeTreeNode; begin if Tree=nil then exit(nil); Result:=Tree.Root; while Result<>nil do begin if (Result.StartPos>=StartPos) and IsResourceDirective(Result,Filename) then exit; Result:=Result.Next; end; end; function TCompilerDirectivesTree.IsResourceDirective(Node: TCodeTreeNode; const Filename: string): boolean; // search for {$R filename} // if filename='' then search for any {$R } directive // Beware: do not find {$R+} var p: LongInt; begin Result:=false; if (Node=nil) or (Node.Desc<>cdnDefine) or (Node.SubDesc<>cdnsOther) then exit; p:=Node.StartPos; if (Node.EndPos-p>=5) and (Src[p]='{') and (Src[p+1]='$') and (Src[p+2]='R') and IsSpaceChar[Src[p+3]] then begin if (Filename='') then exit(true); inc(p,4); while (pnil do begin if (Result.StartPos>=StartPos) and IsIncludeDirective(Result,Filename) then exit; Result:=Result.Next; end; end; function TCompilerDirectivesTree.IsIncludeDirective(Node: TCodeTreeNode; const Filename: string): boolean; // search for {$I filename} // if filename='' then search for any {$I } directive // Beware: do not find {$I+} var p: LongInt; FilenameStartPos: integer; FilenameEndPos: integer; CommentStart: integer; CommentEnd: integer; begin Result:=false; //debugln(['TCompilerDirectivesTree.IsIncludeDirective ',CDNodeDescAsString(Node.Desc)]); if (Node=nil) or (Node.Desc<>cdnInclude) then exit; p:=Node.StartPos; if (Node.EndPos-p>=5) and (Src[p]='{') and (Src[p+1]='$') and (Src[p+2]='I') then begin if (Filename='') then exit(true); if FindNextIncludeDirective(Src,p,NestedComments, FilenameStartPos,FilenameEndPos,CommentStart,CommentEnd)=p then begin // do not use CompareFilenamesIgnoreCase if CompareText(Filename, copy(Src,FilenameStartPos,FilenameEndPos-FilenameStartPos))=0 then exit(true); end; end; end; function TCompilerDirectivesTree.GetDirectiveName(Node: TCodeTreeNode): string; begin Result:=GetIdentifier(@Src[Node.StartPos+2]); end; function TCompilerDirectivesTree.GetDirective(Node: TCodeTreeNode): string; begin Result:=copy(Src,Node.StartPos, FindCommentEnd(Src,Node.StartPos,NestedComments)-Node.StartPos); end; function TCompilerDirectivesTree.GetIfExpression(Node: TCodeTreeNode; out ExprStart, ExprEnd: integer): boolean; var p: LongInt; begin Result:=false; ExprStart:=-1; ExprEnd:=-1; p:=Node.StartPos+2; if p>SrcLen then exit; while (p<=SrcLen) and IsIdentChar[Src[p]] do inc(p); if (p>SrcLen) or (not IsSpaceChar[Src[p]]) then exit; inc(p); ExprStart:=p; while (p<=SrcLen) and (Src[p]<>'}') do inc(p); ExprEnd:=p; Result:=true; end; function TCompilerDirectivesTree.GetIfExpressionString(Node: TCodeTreeNode ): string; var ExprStart: integer; ExprEnd: integer; begin if not GetIfExpression(Node,ExprStart,ExprEnd) then Result:='' else Result:=copy(Src,ExprStart,ExprEnd-ExprStart); end; function TCompilerDirectivesTree.IsIfExpressionSimple(Node: TCodeTreeNode; out NameStart: integer): boolean; var p: LongInt; begin Result:=false; NameStart:=-1; // skip {$ p:=Node.StartPos+2; if p>SrcLen then exit; // skip directive name while (p<=SrcLen) and IsIdentChar[Src[p]] do inc(p); // skip space if (p>SrcLen) or (not IsSpaceChar[Src[p]]) then exit; while (p<=SrcLen) and IsSpaceChar[Src[p]] do inc(p); if (p>SrcLen) or (not IsIdentStartChar[Src[p]]) then exit; // the expression starts with word NameStart:=p; if (Node.SubDesc=cdnsIfdef) or (Node.SubDesc=cdnsIfndef) then begin // IFDEF and IFNDEF only test the first word exit(true); end; // skip first word while (p<=SrcLen) and (IsIdentChar[Src[p]]) do inc(p); // skip space while (p<=SrcLen) and IsSpaceChar[Src[p]] do inc(p); if (p>SrcLen) or (Src[p]='}') then begin // the expression only contains one word exit(true); end; Result:=false; end; function TCompilerDirectivesTree.FindNameInIfExpression(Node: TCodeTreeNode; Identifier: PChar): integer; var p: LongInt; begin Result:=-1; // skip {$ p:=Node.StartPos+2; if p>SrcLen then exit; // skip directive name while (p<=SrcLen) and IsIdentChar[Src[p]] do inc(p); // read expression while (p<=SrcLen) do begin if Src[p]='}' then exit; if IsIdentStartChar[Src[p]] then begin if CompareIdentifierPtrs(@Src[p],Identifier)=0 then exit(p); if (Node.SubDesc=cdnsIfdef) or (Node.SubDesc=cdnsIfndef) then begin // IFDEF and IFNDEF have only one word exit; end; while (p<=SrcLen) and (IsIdentChar[Src[p]]) do inc(p); end else begin inc(p); end; end; end; function TCompilerDirectivesTree.GetDefineNameAndValue( DefineNode: TCodeTreeNode; out NameStart: integer; out HasValue: boolean; out ValueStart: integer): boolean; var p: LongInt; begin Result:=false; NameStart:=-1; HasValue:=false; ValueStart:=-1; p:=DefineNode.StartPos+2; if p>SrcLen then exit; // skip keyword while (p<=SrcLen) and (IsIdentChar[Src[p]]) do inc(p); while (p<=SrcLen) and (IsSpaceChar[Src[p]]) do inc(p); // check name if p>SrcLen then exit; NameStart:=p; if not IsIdentStartChar[Src[p]] then exit; Result:=true; // skip name while (p<=SrcLen) and (IsIdentChar[Src[p]]) do inc(p); while (p<=SrcLen) and (IsSpaceChar[Src[p]]) do inc(p); if p>SrcLen then exit; if (Src[p]=':') and (pSrcLen then exit; // skip keyword while (p<=SrcLen) and (IsIdentChar[Src[p]]) do inc(p); while (p<=SrcLen) and (IsSpaceChar[Src[p]]) do inc(p); // check name if p>SrcLen then exit; Result:=CompareIdentifierPtrs(@Src[p],Identifier)=0; end; function TCompilerDirectivesTree.NodeIsEmpty(Node: TCodeTreeNode; IgnoreComments: boolean): boolean; var DirectiveEndPos: LongInt; begin if (Node=nil) then exit(true); if Node.FirstChild<>nil then exit(false); case Node.Desc of cdnNone: exit(true); cdnRoot: exit(false); // root is never empty, can not be deleted cdnDefine: exit(true); cdnIf, cdnElseIf, cdnElse: begin if Node.NextBrother=nil then exit(false); // maybe continued in another file MoveCursorToPos(Node.StartPos); // skip directive ReadNextAtom; DirectiveEndPos:=SrcPos; // read the following atom (token or directive) ReadNextAtom; if AtomStart=Node.NextBrother.StartPos then begin if IgnoreComments then exit(true) else if FindNextNonSpace(Src,DirectiveEndPos)nil do begin if Result.StartPos>p then exit(Result.Parent); if (Result.EndPos>p) or ((Result.EndPos=p) and (Result.NextBrother<>nil) and (Result.NextBrother.StartPos>p)) then begin // p is in range of Result => check children if (Result.FirstChild=nil) or (Result.FirstChild.StartPos>p) then exit; Result:=Result.FirstChild; end else begin // p is behind => next if Result.NextBrother<>nil then Result:=Result.NextBrother else exit(Result.Parent); end; end; end; procedure TCompilerDirectivesTree.MoveCursorToPos(p: integer); begin SrcPos:=p; AtomStart:=p; end; procedure TCompilerDirectivesTree.ReadNextAtom; begin //DebugLn(['TCompilerDirectivesTree.ReadNextAtom START ',AtomStart,'-',SrcPos,' ',Src[SrcPos]]); ReadRawNextPascalAtom(Src,SrcPos,AtomStart,NestedComments); //DebugLn(['TCompilerDirectivesTree.ReadNextAtom END ',AtomStart,'-',SrcPos,' ',copy(Src,AtomStart,SrcPos-AtomStart)]); end; function TCompilerDirectivesTree.ReadTilBracketClose(CloseBracket: char ): boolean; begin Result:=false; repeat ReadNextAtom; if AtomStart>SrcLen then exit; if SrcPos-AtomStart=1 then begin if Src[AtomStart]=CloseBracket then exit(true) else if Src[AtomStart]='(' then ReadTilBracketClose(')') else if Src[AtomStart]='[' then ReadTilBracketClose(']'); end; until false; end; function TCompilerDirectivesTree.AtomIs(const s: shortstring): boolean; var len: Integer; i: Integer; begin len:=length(s); if (len<>SrcPos-AtomStart) then exit(false); if SrcPos>SrcLen then exit(false); for i:=1 to len do if Src[AtomStart+i-1]<>s[i] then exit(false); Result:=true; end; function TCompilerDirectivesTree.UpAtomIs(const s: shortstring): boolean; var len: Integer; i: Integer; begin len:=length(s); if (len<>SrcPos-AtomStart) then exit(false); if SrcPos>SrcLen then exit(false); for i:=1 to len do if UpChars[Src[AtomStart+i-1]]<>s[i] then exit(false); Result:=true; end; function TCompilerDirectivesTree.AtomIsIdentifier: boolean; var p: Integer; begin if (AtomStart>=SrcPos) then exit(false); if (SrcPos>SrcLen) or (SrcPos-AtomStart>255) then exit(false); if not IsIdentStartChar[Src[AtomStart]] then exit(false); p:=AtomStart+1; while (p0 then begin Node:=Tree.Root; while Node<>nil do begin AdjustPositionAfterInsert(Node.StartPos,true,FromPos,ToPos,DiffPos); AdjustPositionAfterInsert(Node.EndPos,false,FromPos,ToPos,DiffPos); Node:=Node.Next; end; end; end; procedure TCompilerDirectivesTree.IncreaseChangeStep; begin if FChangeStep<>$7fffffff then inc(FChangeStep) else FChangeStep:=-$7fffffff; end; procedure TCompilerDirectivesTree.ResetMacros; begin if Macros<>nil then Macros.FreeAndClear else Macros:=TAVLTree.Create(@CompareCompilerMacroStats); end; procedure TCompilerDirectivesTree.ClearMacros; begin if Macros<>nil then begin Macros.FreeAndClear; FreeAndNil(Macros); end; end; procedure TCompilerDirectivesTree.WriteDebugReport; var Node: TCodeTreeNode; begin DebugLn(['TCompilerDirectivesTree.WriteDebugReport ']); if Tree<>nil then begin Node:=Tree.Root; while Node<>nil do begin DebugLn([GetIndentStr(Node.GetLevel*2)+CDNodeDescAsString(Node.Desc),' ',GetDirective(Node)]); Node:=Node.Next; end; end; end; { TH2PasFunction } function TH2PasFunction.NeedsBody: boolean; begin Result:=(IsForward or InInterface) and (not IsExternal) and (BeginStart<0); end; procedure TH2PasFunction.AdjustPositionsAfterInsert(FromPos, ToPos, DiffPos: integer); begin AdjustPositionAfterInsert(HeaderStart,true,FromPos,ToPos,DiffPos); AdjustPositionAfterInsert(HeaderEnd,false,FromPos,ToPos,DiffPos); AdjustPositionAfterInsert(BeginStart,true,FromPos,ToPos,DiffPos); AdjustPositionAfterInsert(BeginEnd,false,FromPos,ToPos,DiffPos); end; { ECDirectiveParserException } constructor ECDirectiveParserException.Create(ASender: TCompilerDirectivesTree; TheId: int64; const AMessage: string); begin Id:=TheId; inherited Create(AMessage); Sender:=ASender; end; end.