{ *************************************************************************** * * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Functions to beautify code. Goals: - Customizable - fully automatic - Beautification of whole sources. For example a unit, or several sources. - Beautification of parts of sources. For example selections. - Beautification of insertion source. For example beautifying code, that will be inserted in another source. - Working with syntax errors. The beautification will try its best to work, even if the source contains errors. - Does not ignore comments and directives - Contexts: statements, declarations Examples for beautification styles: see scanexamples/indentation.pas } unit CodeBeautifier; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileProcs, KeywordFuncLists, CodeCache, BasicCodeTools; type TBeautifySplit =( bsNone, bsInsertSpace, // insert space before bsNewLine, // break line, no indent bsEmptyLine, // insert empty line, no indent bsNewLineAndIndent, // break line, indent bsEmptyLineAndIndent, // insert empty line, indent bsNewLineUnindent, bsEmptyLineUnindent, bsNoSplit // do not break line here when line too long ); TWordPolicy = ( wpNone, wpLowerCase, wpUpperCase, wpLowerCaseFirstLetterUp ); TFABBlockType = ( bbtNone, // code sections bbtInterface, bbtImplementation, bbtInitialization, bbtFinalization, // identifier sections bbtUsesSection, bbtTypeSection, bbtConstSection, bbtVarSection, bbtResourceStringSection, bbtLabelSection, // statement blocks bbtProcedure, // procedure, constructor, destructor bbtFunction, bbtMainBegin, bbtCommentaryBegin, // begin without any need bbtRepeat, bbtProcedureBegin ); TFABBlockTypes = set of TFABBlockType; const bbtAllIdentifierSections = [bbtTypeSection,bbtConstSection,bbtVarSection, bbtResourceStringSection,bbtLabelSection]; bbtAllCodeSections = [bbtInterface,bbtImplementation,bbtInitialization, bbtFinalization]; bbtAllStatements = [bbtMainBegin,bbtCommentaryBegin,bbtRepeat]; type TOnGetFABExamples = procedure(Sender: TObject; Code: TCodeBuffer; out CodeBuffers: TFPList) of object; TFABIndentation = record Indent: integer; UseTabs: boolean; InsertEmptyLines: integer; end; { TFABPolicies } TFABPolicies = class public Indentations: array[TFABBlockType] of TFABIndentation; IndentationsFound: array[TFABBlockType] of boolean; constructor Create; destructor Destroy; override; end; { TFullyAutomaticBeautifier } TFullyAutomaticBeautifier = class private FOnGetExamples: TOnGetFABExamples; FAtomStarts: PInteger; FAtomCapacity: integer; FAtomCount: integer; procedure ParseSource(const Source: string; NewSrcLen: integer; NewNestedComments: boolean); function IndexOfAtomInFront(CleanPos: integer): integer; function FindContext(CleanPos: integer; out AtomInFront: integer ): TFABBlockType; procedure FindPolicies(Types: TFABBlockTypes; Policies: TFABPolicies); public Src: string; SrcLen: integer; NestedComments: boolean; constructor Create; destructor Destroy; override; procedure Clear; function GetIndent(const Source: string; CleanPos: integer; NewNestedComments: boolean; out Indent: TFABIndentation): boolean; { ToDo: - indent on paste (position + new source) - indent auto generated code (several snippets) - learn from source - learn from nearest lines in source } property OnGetExamples: TOnGetFABExamples read FOnGetExamples write FOnGetExamples; end; const FABBlockTypeNames: array[TFABBlockType] of string = ( 'bbtNone', // code sections 'bbtInterface', 'bbtImplementation', 'bbtInitialization', 'bbtFinalization', // identifier sections 'bbtUsesSection', 'bbtTypeSection', 'bbtConstSection', 'bbtVarSection', 'bbtResourceStringSection', 'bbtLabelSection', // statement blocks 'bbtProcedure', 'bbtFunction', 'bbtMainBegin', 'bbtCommentaryBegin', 'bbtRepeat', 'bbtProcedureBegin' ); implementation type TBlock = record Typ: TFABBlockType; StartPos: integer; end; PBlock = ^TBlock; { TBlockStack } TBlockStack = class public Stack: PBlock; Capacity: integer; Top: integer; TopType: TFABBlockType; constructor Create; destructor Destroy; override; procedure BeginBlock(Typ: TFABBlockType; StartPos: integer); procedure EndBlock; function TopMostIndexOf(Typ: TFABBlockType): integer; function EndTopMostBlock(Typ: TFABBlockType): boolean; end; { TBlockStack } constructor TBlockStack.Create; begin Top:=-1; end; destructor TBlockStack.Destroy; begin ReAllocMem(Stack,0); Capacity:=0; Top:=-1; inherited Destroy; end; procedure TBlockStack.BeginBlock(Typ: TFABBlockType; StartPos: integer); var Block: PBlock; begin inc(Top); if Top>=Capacity then begin if Capacity=0 then Capacity:=16 else Capacity:=Capacity*2; ReAllocMem(Stack,SizeOf(TBlock)*Capacity); end; {$IFDEF ShowCodeBeautifier} DebugLn([GetIndentStr(Top*2),'TBlockStack.BeginBlock ',FABBlockTypeNames[Typ],' ',StartPos]); {$ENDIF} Block:=@Stack[Top]; Block^.Typ:=Typ; Block^.StartPos:=StartPos; TopType:=Typ; end; procedure TBlockStack.EndBlock; begin {$IFDEF ShowCodeBeautifier} DebugLn([GetIndentStr(Top*2),'TBlockStack.EndBlock ',FABBlockTypeNames[TopType]]); {$ENDIF} dec(Top); if Top>=0 then TopType:=Stack[Top].Typ else TopType:=bbtNone; end; function TBlockStack.TopMostIndexOf(Typ: TFABBlockType): integer; begin Result:=Top; while (Result>=0) and (Stack[Result].Typ<>Typ) do dec(Result); end; function TBlockStack.EndTopMostBlock(Typ: TFABBlockType): boolean; // check if there is this type on the stack and if yes, end it var i: LongInt; begin i:=TopMostIndexOf(Typ); if i<0 then exit(false); while Top>=i do EndBlock; end; { TFullyAutomaticBeautifier } procedure TFullyAutomaticBeautifier.ParseSource(const Source: string; NewSrcLen: integer; NewNestedComments: boolean); var Stack: TBlockStack; p: Integer; procedure StartIdentifierSection(Section: TFABBlockType); begin if Stack.TopType in [bbtProcedure,bbtFunction] then begin if (Stack.Top=0) or (Stack.Stack[Stack.Top-1].Typ in [bbtImplementation]) then begin // procedure with begin..end end else begin // procedure without begin..end Stack.EndBlock; end; end; if Stack.TopType in bbtAllIdentifierSections then Stack.EndBlock; if Stack.TopType in bbtAllCodeSections then Stack.BeginBlock(Section,p); end; var AtomStart: integer; MinAtomCapacity: Integer; r: PChar; begin Src:=Source; SrcLen:=NewSrcLen; NestedComments:=NewNestedComments; FAtomCount:=0; MinAtomCapacity:=SrcLen div 4; if MinAtomCapacity<1024 then MinAtomCapacity:=1024; if FAtomCapacitySrcLen then break; FAtomStarts[FAtomCount]:=AtomStart; inc(FAtomCount); if FAtomCount>FAtomCapacity then begin FAtomCapacity:=FAtomCapacity*2; ReAllocMem(FAtomStarts,FAtomCapacity*SizeOf(integer)); end; r:=@Src[p]; case UpChars[r^] of 'B': if CompareIdentifiers('BEGIN',r)=0 then begin while Stack.TopType in (bbtAllIdentifierSections+bbtAllCodeSections) do Stack.EndBlock; case Stack.TopType of bbtNone: Stack.BeginBlock(bbtMainBegin,p); bbtMainBegin: Stack.BeginBlock(bbtCommentaryBegin,p); end; end; 'C': if CompareIdentifiers('CONST',r)=0 then begin StartIdentifierSection(bbtConstSection); end; 'E': if CompareIdentifiers('END',r)=0 then begin case Stack.TopType of bbtMainBegin,bbtCommentaryBegin: Stack.EndBlock; end; StartIdentifierSection(bbtConstSection); end; 'F': case UpChars[r[1]] of 'I': if CompareIdentifiers('FINALIZATION',r)=0 then begin while Stack.TopType in (bbtAllCodeSections+bbtAllIdentifierSections) do Stack.EndBlock; if Stack.TopType=bbtNone then Stack.BeginBlock(bbtInitialization,p); end; 'O': if CompareIdentifiers('FORWARD',r)=0 then begin if Stack.TopType in [bbtProcedure,bbtFunction] then begin Stack.EndBlock; end; end; end; 'I': case UpChars[Src[1]] of 'N': case UpChars[Src[2]] of 'I': if CompareIdentifiers('INITIALIZATION',r)=0 then begin while Stack.TopType in (bbtAllCodeSections+bbtAllIdentifierSections) do Stack.EndBlock; if Stack.TopType=bbtNone then Stack.BeginBlock(bbtInitialization,p); end; 'T': if CompareIdentifiers('INTERFACE',r)=0 then begin if Stack.TopType=bbtNone then Stack.BeginBlock(bbtInterface,p); end; end; 'M': if CompareIdentifiers('IMPLEMENTATION',r)=0 then begin while Stack.TopType in (bbtAllCodeSections+bbtAllIdentifierSections) do Stack.EndBlock; if Stack.TopType=bbtNone then Stack.BeginBlock(bbtImplementation,p); end; end; 'L': if CompareIdentifiers('LABEL',r)=0 then StartIdentifierSection(bbtLabelSection); 'P': if CompareIdentifiers('PROCEDURE',r)=0 then Stack.BeginBlock(bbtProcedure,p); 'R': case UpChars[r[1]] of 'E': case UpChars[r[2]] of 'P': if CompareIdentifiers('REPEAT',r)=0 then begin if Stack.TopType in bbtAllStatements then Stack.BeginBlock(bbtRepeat,p); end; 'S': if CompareIdentifiers('RESOURCESTRING',r)=0 then StartIdentifierSection(bbtResourceStringSection); end; end; 'T': if CompareIdentifiers('TYPE',r)=0 then begin StartIdentifierSection(bbtTypeSection); end; 'U': case UpChars[r[1]] of 'S': if CompareIdentifiers('USES',r)=0 then begin if Stack.TopType in [bbtNone,bbtInterface,bbtImplementation] then Stack.BeginBlock(bbtUsesSection,p); end; 'N': if CompareIdentifiers('UNTIL',r)=0 then begin Stack.EndTopMostBlock(bbtRepeat); end; end; 'V': if CompareIdentifiers('VAR',r)=0 then begin StartIdentifierSection(bbtVarSection); end; ';': case Stack.TopType of bbtUsesSection: Stack.EndBlock; end; end; until false; finally Stack.Free; end; end; function TFullyAutomaticBeautifier.IndexOfAtomInFront(CleanPos: integer ): integer; // returns index in FAtomStarts of atom in front // if CleanPos is start of an atom the atom in front is returned // default: -1 var l: Integer; r: LongInt; m: Integer; p: LongInt; begin l:=0; r:=FAtomCount-1; while l<=r do begin m:=(l+r) shr 1; p:=FAtomStarts[m]; if p>CleanPos then r:=m-1 else if p