mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-25 03:19:36 +02:00
2455 lines
76 KiB
ObjectPascal
2455 lines
76 KiB
ObjectPascal
{
|
|
***************************************************************************
|
|
* *
|
|
* This source is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This code is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* A copy of the GNU General Public License is available on the World *
|
|
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
|
|
* *
|
|
***************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
}
|
|
(*
|
|
Abstract:
|
|
Functions to beautify code.
|
|
Goals:
|
|
- fully automatic (instead of fixed rules mimic the indentation of example code(s))
|
|
- when target source is within example source use the nearest match.
|
|
- Customizable (e.g. use boolean: check the global example code, boolean)
|
|
- Beautification of whole sources. For example a unit, or several
|
|
sources.
|
|
- Beautification of parts of sources. For example selections.
|
|
- Beautification of insertion source (Paste). For example beautifying
|
|
code, that will be inserted in another source.
|
|
- Find a nice indendation for empty line (e.g. after pressing return)
|
|
- Inside comments: use indentation of last non empty line
|
|
- Working with syntax errors. The beautification will try its best to
|
|
work, even if the source contains errors.
|
|
- Comments are special statements.
|
|
|
|
Line break:
|
|
1. indent to the smallest indent
|
|
For example, when there is always an indent after 'try':
|
|
try|
|
|
|
|
|
For example when sometimes no indent is after 'then':
|
|
if expr then|
|
|
|
|
|
2. unindent when block was closed
|
|
For example after closing blocks with a semicolon:
|
|
if expr then
|
|
if expr then
|
|
doit;|
|
|
|
|
|
Closing the corresponding block, not all blocks:
|
|
if expr then
|
|
if expr then begin|
|
|
|end
|
|
3. optional 'UseLineStart': when next token in line closes block:
|
|
repeat|
|
|
|until
|
|
When 'until' is not current line, ignore it:
|
|
repeat|
|
|
|
|
|
until
|
|
4. When unsure, tell that and use identation of last non empty line
|
|
5. Nested blocks do not need to be indented monoton:
|
|
if expr
|
|
or expr then
|
|
statement;
|
|
begin
|
|
//comment
|
|
statement;
|
|
//comment
|
|
statement;
|
|
{$IFDEF expr}
|
|
statement;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
Examples for beautification styles: see examples/scanexamples/indentation.pas
|
|
|
|
ToDo:
|
|
* LineBreak:
|
|
- Optional: indent last line after pressing return key:
|
|
if true then
|
|
exit;|
|
|
|
|
|
* long lines
|
|
DoSomething(Param1,
|
|
Param2);
|
|
*)
|
|
unit CodeBeautifier;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
{ $DEFINE ShowCodeBeautifier}
|
|
{ $DEFINE ShowCodeBeautifierParser}
|
|
{ $DEFINE ShowCodeBeautifierLearn}
|
|
{ $DEFINE VerboseIndenter}
|
|
|
|
{$IFDEF ShowCodeBeautifierParser}
|
|
{$DEFINE ShowCodeBeautifierLearn}
|
|
{$ENDIF}
|
|
{$IF defined(VerboseIndenter) or defined(ShowCodeBeautifierLearn)}
|
|
{$DEFINE StoreLearnedPositions}
|
|
{$ENDIF}
|
|
|
|
uses
|
|
Classes, SysUtils, AVL_Tree,
|
|
// Codetools
|
|
FileProcs, KeywordFuncLists, CodeCache, BasicCodeTools,
|
|
// LazUtils
|
|
LazUtilities;
|
|
|
|
type
|
|
TWordPolicy = (
|
|
wpNone,
|
|
wpLowerCase,
|
|
wpUpperCase,
|
|
wpLowerCaseFirstLetterUp
|
|
);
|
|
|
|
TFABBlockType = (
|
|
bbtNone, // all else (comments, enums, continued lines, ...)
|
|
// code sections
|
|
bbtInterface,
|
|
bbtImplementation,
|
|
bbtInitialization,
|
|
bbtFinalization,
|
|
// identifier sections
|
|
bbtUsesSection,
|
|
bbtTypeSection,
|
|
bbtConstSection,
|
|
bbtVarSection,
|
|
bbtResourceStringSection,
|
|
bbtLabelSection,
|
|
bbtDefinition, // child of bbtTypeSection,bbtConstSection,bbtVarSection,bbtResourceStringSection,bbtLabelSection
|
|
// type blocks
|
|
bbtRecord,
|
|
bbtClass, // class, object, objcclass, objccategory
|
|
bbtClassInterface, // interface, dispinterface, objcprotocol
|
|
bbtClassSection, // public, private, protected, published
|
|
bbtTypeRoundBracket,
|
|
bbtTypeEdgedBracket,
|
|
// statement blocks
|
|
bbtProcedure, // procedure, constructor, destructor
|
|
bbtFunction, // function, operator
|
|
bbtProcedureHead, // child of bbtProcedure or bbtFunction
|
|
bbtProcedureParamList, // child of bbtProcedureHead
|
|
bbtProcedureModifiers, // child of bbtProcedureHead
|
|
bbtProcedureBegin, // child of bbtProcedure or bbtFunction
|
|
bbtMainBegin,
|
|
bbtFreeBegin, // a normal begin
|
|
bbtRepeat,
|
|
bbtWhile,
|
|
bbtWhileDo, // child of bbtWhile
|
|
bbtFor,
|
|
bbtForDo, // child of bbtFor
|
|
bbtWith,
|
|
bbtWithDo, // child of bbtWith
|
|
bbtCase,
|
|
bbtCaseOf, // child of bbtCase
|
|
bbtCaseLabel, // child of bbtCaseOf
|
|
bbtCaseColon, // child of bbtCaseLabel
|
|
bbtCaseElse, // child of bbtCase
|
|
bbtTry,
|
|
bbtFinally, // sibling of bbtTry
|
|
bbtExcept, // sibling of bbtTry
|
|
bbtIf,
|
|
bbtIfThen, // child of bbtIf
|
|
bbtIfElse, // child of bbtIf
|
|
bbtIfBegin, // child of bbtIfThen or bbtIfElse
|
|
bbtStatement,
|
|
bbtStatementRoundBracket,
|
|
bbtStatementEdgedBracket,
|
|
bbtProperty // global or class property
|
|
);
|
|
TFABBlockTypes = set of TFABBlockType;
|
|
|
|
const
|
|
bbtAllIdentifierSections = [bbtTypeSection,bbtConstSection,bbtVarSection,
|
|
bbtResourceStringSection,bbtLabelSection,bbtClassSection];
|
|
bbtAllProcedures = [bbtProcedure,bbtFunction];
|
|
bbtAllCodeSections = [bbtInterface,bbtImplementation,bbtInitialization,
|
|
bbtFinalization];
|
|
bbtAllStatementParents = [bbtMainBegin,bbtFreeBegin,bbtProcedureBegin,
|
|
bbtRepeat,bbtWhileDo,bbtForDo,bbtWithDo,
|
|
bbtCaseColon,bbtCaseElse,
|
|
bbtTry,bbtFinally,bbtExcept,
|
|
bbtIfThen,bbtIfElse,bbtIfBegin];
|
|
bbtAllStatements = bbtAllStatementParents+[
|
|
bbtStatement,bbtStatementRoundBracket,bbtStatementEdgedBracket];
|
|
bbtAllBrackets = [bbtTypeRoundBracket,bbtTypeEdgedBracket,
|
|
bbtStatementRoundBracket,bbtStatementEdgedBracket];
|
|
bbtAllAutoEnd = [bbtStatement,bbtIf,bbtIfThen,bbtIfElse,bbtWhile,bbtWhileDo,
|
|
bbtFor,bbtForDo,bbtWith,bbtWithDo,bbtCaseLabel,bbtCaseColon];
|
|
bbtAllAlignToSibling = [bbtNone]+bbtAllStatements;
|
|
|
|
type
|
|
TOnGetFABExamples = procedure(Sender: TObject; Code: TCodeBuffer;
|
|
Step: integer; // starting at 0
|
|
var CodeBuffers: TFPList; // stopping when CodeBuffers=nil
|
|
var ExpandedFilenames: TStrings // and ExpandedFilenames=nil
|
|
) of object;
|
|
TOnGetFABNestedComments = procedure(Sender: TObject; Code: TCodeBuffer;
|
|
out NestedComments: boolean) of object;
|
|
TOnLoadCTFile = procedure(Sender: TObject; const ExpandedFilename: string;
|
|
out Code: TCodeBuffer; var Abort: boolean) of object;
|
|
|
|
TFABIndentationPolicy = record
|
|
Indent: integer;
|
|
IndentValid: boolean;
|
|
end;
|
|
|
|
TFABFoundIndentationPolicy = packed record
|
|
Typ, SubTyp: TFABBlockType;
|
|
Indent: integer;
|
|
{$IFDEF StoreLearnedPositions}
|
|
SrcPos: integer;
|
|
{$ENDIF}
|
|
end;
|
|
PFABFoundIndentationPolicy = ^TFABFoundIndentationPolicy;
|
|
|
|
{ TFABPolicies }
|
|
|
|
TFABPolicies = class
|
|
private
|
|
function FindIndentation(Typ, SubType: TFABBlockType;
|
|
out InsertPos: integer): boolean;
|
|
public
|
|
IndentationCount, IndentationCapacity: integer;
|
|
Indentations: PFABFoundIndentationPolicy; // sorted ascending
|
|
Code: TCodeBuffer;
|
|
CodeChangeStep: integer;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure AddIndent(Typ, SubType: TFABBlockType; {%H-}SrcPos, Indent: integer);
|
|
function GetSmallestIndent(Typ: TFABBlockType): integer;// -1 if none found
|
|
function GetIndent(Typ, SubType: TFABBlockType;
|
|
UseNoneIfNotFound,
|
|
UseSmallestIfNotFound: boolean): integer;// -1 if none found
|
|
function CodePosToStr(p: integer): string;
|
|
procedure ConsistencyCheck;
|
|
procedure WriteDebugReport;
|
|
end;
|
|
|
|
type
|
|
TBlock = record
|
|
Typ: TFABBlockType;
|
|
StartPos: integer;
|
|
Indent: integer;
|
|
Trailing: boolean; // true = StartPos is not first atom in line
|
|
InnerStartPos: integer;
|
|
InnerIdent: integer; // valid if >=0
|
|
end;
|
|
PBlock = ^TBlock;
|
|
|
|
const
|
|
CleanBlock: TBlock = (
|
|
Typ: bbtNone;
|
|
StartPos: -1;
|
|
Indent: -1;
|
|
Trailing: false;
|
|
InnerStartPos: -1;
|
|
InnerIdent: -1
|
|
);
|
|
|
|
type
|
|
TFABPositionIndent = record
|
|
CleanPos: integer;
|
|
Indent: TFABIndentationPolicy;
|
|
Block: TBlock;
|
|
SubType: TFABBlockType;
|
|
SubTypeValid: boolean;
|
|
end;
|
|
PFABPositionIndent = ^TFABPositionIndent;
|
|
|
|
{ TFABPositionIndents }
|
|
|
|
TFABPositionIndents = class
|
|
private
|
|
FCount: integer;
|
|
procedure SetCount(const AValue: integer);
|
|
public
|
|
Items: PFABPositionIndent;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
property Count: integer read FCount write SetCount;
|
|
end;
|
|
|
|
{ TFABBlockStack }
|
|
|
|
TFABBlockStack = class
|
|
public
|
|
Stack: PBlock;
|
|
Capacity: integer;
|
|
Top: integer; // -1 = empty, 0 = 1 item
|
|
TopType: TFABBlockType;
|
|
LastBlockClosed: TBlock;
|
|
LastBlockClosedAt: integer;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure BeginBlock(Typ: TFABBlockType; StartPos: integer;
|
|
Trailing: boolean; Indent: integer);
|
|
procedure EndBlock(EndPos: integer);
|
|
function TopMostIndexOf(Typ: TFABBlockType): integer;
|
|
function EndTopMostBlock(Typ: TFABBlockType; EndPos: integer): boolean;
|
|
{$IFDEF ShowCodeBeautifier}
|
|
Src: string;
|
|
function PosToStr(p: integer): string;
|
|
{$ENDIF}
|
|
procedure WriteDebugReport(Prefix: string);
|
|
end;
|
|
|
|
{ TFullyAutomaticBeautifier }
|
|
|
|
TFullyAutomaticBeautifier = class
|
|
private
|
|
FOnGetExamples: TOnGetFABExamples;
|
|
FCodePolicies: TAVLTree;// tree of TFABPolicies sorted for Code
|
|
FOnGetNestedComments: TOnGetFABNestedComments;
|
|
FOnLoadFile: TOnLoadCTFile;
|
|
FUseDefaultIndentForTypes: TFABBlockTypes;
|
|
procedure ParseSource(const Src: string; StartPos, EndPos: integer;
|
|
NestedComments: boolean;
|
|
Stack: TFABBlockStack; Policies: TFABPolicies;
|
|
out LastAtomStart, LastAtomEnd: integer; // set if LastAtomStart<EndPos<LastAtomEnd
|
|
LearnFromFirstLine: boolean = true
|
|
);
|
|
procedure ParseSource(const Src: string; StartPos, EndPos: integer;
|
|
NestedComments: boolean;
|
|
Stack: TFABBlockStack; Policies: TFABPolicies;
|
|
LearnFromFirstLine: boolean = true);
|
|
function FindPolicyInExamples(StartCode: TCodeBuffer;
|
|
Typ, SubTyp: TFABBlockType;
|
|
UseNoneIfNotFound,
|
|
UseSmallestIfNotFound: boolean): TFABPolicies;
|
|
function GetNestedCommentsForCode(Code: TCodeBuffer): boolean;
|
|
function AdjustByNextAtom(const Source: string;
|
|
CleanPos: integer; NestedComments: boolean;
|
|
Stack: TFABBlockStack;
|
|
out TopType: TFABBlockType;
|
|
out TopTypeValid: boolean): integer;
|
|
procedure WriteDebugReport(Msg: string; Stack: TFABBlockStack);
|
|
public
|
|
DefaultTabWidth: integer;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function GetIndent(const Source: string; CleanPos: integer;
|
|
NewNestedComments: boolean; UseLineStart: boolean;
|
|
out Indent: TFABIndentationPolicy;
|
|
ContextLearn: boolean = true; // true = learn policies from Source
|
|
const InsertText: string = ''
|
|
): boolean;
|
|
function GetIndents(const Source: string; Positions: TFABPositionIndents;
|
|
NewNestedComments: boolean; UseLineStart: boolean;
|
|
ContextLearn: boolean = true // true = learn policies from Source
|
|
): boolean;
|
|
procedure GetDefaultSrcIndent(const Source: string; CleanPos: integer;
|
|
{%H-}NewNestedComments: boolean;
|
|
out Indent: TFABIndentationPolicy);
|
|
procedure GetDefaultIndentPolicy(Typ, SubTyp: TFABBlockType;
|
|
out Indent: TFABIndentationPolicy);
|
|
property OnGetExamples: TOnGetFABExamples read FOnGetExamples
|
|
write FOnGetExamples;
|
|
property OnGetNestedComments: TOnGetFABNestedComments
|
|
read FOnGetNestedComments write FOnGetNestedComments;
|
|
property OnLoadFile: TOnLoadCTFile read FOnLoadFile write FOnLoadFile;
|
|
property UseDefaultIndentForTypes: TFABBlockTypes
|
|
read FUseDefaultIndentForTypes write FUseDefaultIndentForTypes;
|
|
end;
|
|
|
|
function EnumToStr(BlockType: TFABBlockType): string;
|
|
function CompareFABPoliciesWithCode(Data1, Data2: Pointer): integer;
|
|
function CompareCodeWithFABPolicy(Key, Data: Pointer): integer;
|
|
|
|
implementation
|
|
|
|
function EnumToStr(BlockType: TFABBlockType): string;
|
|
begin
|
|
WriteStr(Result, BlockType);
|
|
end;
|
|
|
|
function CompareFABPoliciesWithCode(Data1, Data2: Pointer): integer;
|
|
var
|
|
Policies1: TFABPolicies absolute Data1;
|
|
Policies2: TFABPolicies absolute Data2;
|
|
begin
|
|
Result:=ComparePointers(Policies1.Code,Policies2.Code);
|
|
end;
|
|
|
|
function CompareCodeWithFABPolicy(Key, Data: Pointer): integer;
|
|
var
|
|
Policies: TFABPolicies absolute Data;
|
|
begin
|
|
Result:=ComparePointers(Key,Policies.Code);
|
|
end;
|
|
|
|
{ TFABBlockStack }
|
|
|
|
constructor TFABBlockStack.Create;
|
|
begin
|
|
Top:=-1;
|
|
end;
|
|
|
|
destructor TFABBlockStack.Destroy;
|
|
begin
|
|
ReAllocMem(Stack,0);
|
|
Capacity:=0;
|
|
Top:=-1;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFABBlockStack.BeginBlock(Typ: TFABBlockType; StartPos: integer;
|
|
Trailing: boolean; Indent: 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),'TFABBlockStack.BeginBlock ',EnumToStr(Typ),' ',StartPos,' at ',PosToStr(StartPos)]);
|
|
{$ENDIF}
|
|
Block:=@Stack[Top];
|
|
Block^.Typ:=Typ;
|
|
Block^.StartPos:=StartPos;
|
|
Block^.Indent:=Indent;
|
|
Block^.Trailing:=Trailing;
|
|
Block^.InnerIdent:=-1;
|
|
Block^.InnerStartPos:=-1;
|
|
TopType:=Typ;
|
|
LastBlockClosed.Typ:=bbtNone;
|
|
LastBlockClosed.StartPos:=0;
|
|
LastBlockClosedAt:=0;
|
|
end;
|
|
|
|
procedure TFABBlockStack.EndBlock(EndPos: integer);
|
|
begin
|
|
{$IFDEF ShowCodeBeautifier}
|
|
DebugLn([GetIndentStr(Top*2),'TFABBlockStack.EndBlock ',EnumToStr(TopType)]);
|
|
{$ENDIF}
|
|
if Top<0 then
|
|
exit;
|
|
if Top>=0 then begin
|
|
LastBlockClosed:=Stack[Top];
|
|
LastBlockClosedAt:=EndPos;
|
|
end;
|
|
dec(Top);
|
|
if Top>=0 then
|
|
TopType:=Stack[Top].Typ
|
|
else
|
|
TopType:=bbtNone;
|
|
end;
|
|
|
|
function TFABBlockStack.TopMostIndexOf(Typ: TFABBlockType): integer;
|
|
begin
|
|
Result:=Top;
|
|
while (Result>=0) and (Stack[Result].Typ<>Typ) do dec(Result);
|
|
end;
|
|
|
|
function TFABBlockStack.EndTopMostBlock(Typ: TFABBlockType;
|
|
EndPos: integer): 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);
|
|
Result:=true;
|
|
while Top>=i do EndBlock(EndPos);
|
|
end;
|
|
|
|
procedure TFABBlockStack.WriteDebugReport(Prefix: string);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Top do begin
|
|
debugln([Prefix+GetIndentStr(i*2),EnumToStr(Stack[i].Typ),
|
|
' StartPos=',Stack[i].StartPos,
|
|
' Indent=',Stack[i].Indent,
|
|
' Trailing=',Stack[i].Trailing,
|
|
' Indent=',Stack[i].Indent,
|
|
' InnerStartPos=',Stack[i].InnerStartPos,
|
|
' InnerIdent=',Stack[i].InnerIdent,
|
|
'']);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF ShowCodeBeautifier}
|
|
function TFABBlockStack.PosToStr(p: integer): string;
|
|
var
|
|
X: integer;
|
|
Y: LongInt;
|
|
begin
|
|
Result:='';
|
|
if Src='' then exit;
|
|
Y:=LineEndCount(Src,1,p,X)+1;
|
|
Result:='y='+dbgs(Y)+',x='+dbgs(X+1);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{ TFullyAutomaticBeautifier }
|
|
|
|
procedure TFullyAutomaticBeautifier.ParseSource(const Src: string;
|
|
StartPos, EndPos: integer; NestedComments: boolean; Stack: TFABBlockStack;
|
|
Policies: TFABPolicies; out LastAtomStart, LastAtomEnd: integer;
|
|
LearnFromFirstLine: boolean);
|
|
var
|
|
p: Integer;
|
|
AtomStart: integer;
|
|
AtomStartedBlock, AtomEndedBlock: boolean;
|
|
FirstAtomOnNewLine: Boolean;
|
|
InFirstLine: boolean;
|
|
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
function PosToStr(p: integer): string;
|
|
var
|
|
X: integer;
|
|
Y: LongInt;
|
|
begin
|
|
Y:=LineEndCount(Src,1,p,X)+1;
|
|
Result:='Line='+dbgs(Y)+' Col='+dbgs(X+1);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure UpdateBlockInnerIndent;
|
|
var
|
|
Block: PBlock;
|
|
BlockStartPos: LongInt;
|
|
i: LongInt;
|
|
begin
|
|
i:=Stack.Top;
|
|
Block:=@Stack.Stack[i];
|
|
if Block^.InnerIdent<0 then begin
|
|
while (i>0) and Stack.Stack[i].Trailing do dec(i);
|
|
BlockStartPos:=Stack.Stack[i].StartPos;
|
|
if not PositionsInSameLine(Src,BlockStartPos,Block^.InnerStartPos) then
|
|
Block^.InnerIdent:=
|
|
GetLineIndentWithTabs(Src,Block^.InnerStartPos,DefaultTabWidth)
|
|
-GetLineIndentWithTabs(Src,BlockStartPos,DefaultTabWidth);
|
|
end;
|
|
end;
|
|
|
|
procedure BeginBlock(Typ: TFABBlockType);
|
|
var
|
|
Block: PBlock;
|
|
Indent: Integer;
|
|
i: LongInt;
|
|
BaseBlock: PBlock;
|
|
begin
|
|
AtomStartedBlock:=true;
|
|
i:=Stack.Top;
|
|
Indent:=-1;
|
|
if (Policies<>nil) and (i>=0) and FirstAtomOnNewLine then begin
|
|
Block:=@Stack.Stack[i];
|
|
{ For example:
|
|
if expr
|
|
or expr then
|
|
Code;
|
|
Learn that ifThen/Statement is indented by two
|
|
The indentation is taken from the IF, because the THEN is trailing.
|
|
}
|
|
while (i>0) and Stack.Stack[i].Trailing do dec(i);
|
|
BaseBlock:=@Stack.Stack[i];
|
|
if BaseBlock^.Indent<0 then
|
|
BaseBlock^.Indent:=GetLineIndentWithTabs(Src,BaseBlock^.StartPos,DefaultTabWidth);
|
|
Indent:=GetLineIndentWithTabs(Src,AtomStart,DefaultTabWidth);
|
|
if BaseBlock^.Indent<=Indent then begin
|
|
if (not InFirstLine) or LearnFromFirstLine then
|
|
Policies.AddIndent(Block^.Typ,Typ,AtomStart,Indent-BaseBlock^.Indent);
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
DebugLn([GetIndentStr(Stack.Top*2),'nested indentation learned ',EnumToStr(Block^.Typ),'/',EnumToStr(Typ),': ',GetAtomString(@Src[AtomStart],NestedComments),' at ',PosToStr(AtomStart),' Indent=',Indent,'-',BaseBlock^.Indent,'=',Indent-BaseBlock^.Indent]);
|
|
debugln([GetIndentStr(Stack.Top*2),' Src=',dbgstr(copy(Src,AtomStart-10,10)),'|',copy(Src,AtomStart,p-AtomStart),' BaseBlock=',EnumToStr(BaseBlock^.Typ)]);
|
|
if Typ=bbtCaseLabel then
|
|
Stack.WriteDebugReport(GetIndentStr(Stack.Top*2));
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
//if not FirstAtomOnNewLine then DebugLn([GetIndentStr(Stack.Top*2),'TRAILING BeginBlock ',EnumToStr(Typ),' ',GetAtomString(@Src[AtomStart],NestedComments),' at ',PosToStr(AtomStart)]);
|
|
Stack.BeginBlock(Typ,AtomStart,not FirstAtomOnNewLine,Indent);
|
|
{$IFDEF ShowCodeBeautifierParser}
|
|
DebugLn([GetIndentStr(Stack.Top*2),'BeginBlock ',EnumToStr(Typ),' ',GetAtomString(@Src[AtomStart],NestedComments),' at ',PosToStr(AtomStart)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure EndBlock;
|
|
begin
|
|
{$IFDEF ShowCodeBeautifierParser}
|
|
DebugLn([GetIndentStr(Stack.Top*2),'EndBlock ',EnumToStr(Stack.TopType),' ',GetAtomString(@Src[AtomStart],NestedComments),' at ',PosToStr(AtomStart)]);
|
|
{$ENDIF}
|
|
AtomEndedBlock:=true;
|
|
Stack.EndBlock(p);
|
|
end;
|
|
|
|
procedure EndTopMostBlock(Typ: TFABBlockType);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Stack.TopMostIndexOf(Typ);
|
|
if i<0 then exit;
|
|
while Stack.Top>=i do EndBlock;
|
|
end;
|
|
|
|
procedure BeginClass;
|
|
begin
|
|
BeginBlock(bbtClass);
|
|
// the first section is created automatically
|
|
BeginBlock(bbtClassSection);
|
|
end;
|
|
|
|
procedure EndStatements;
|
|
begin
|
|
while Stack.TopType in bbtAllStatements do EndBlock;
|
|
end;
|
|
|
|
function IsProcedureImplementation: boolean;
|
|
// check if current bbtProcedure/bbtFunction expects a begin..end
|
|
begin
|
|
Result:=(Stack.Top=0)
|
|
or (Stack.Stack[Stack.Top-1].Typ in (bbtAllProcedures+[bbtImplementation]));
|
|
end;
|
|
|
|
procedure EndIdentifierSectionAndProc;
|
|
begin
|
|
EndStatements; // fix dangling statements
|
|
if Stack.TopType=bbtProcedureModifiers then
|
|
EndBlock;
|
|
if Stack.TopType=bbtProcedureHead then
|
|
EndBlock;
|
|
if Stack.TopType in bbtAllProcedures then begin
|
|
if IsProcedureImplementation then begin
|
|
// procedure with begin..end
|
|
end else begin
|
|
// procedure without begin..end
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
if Stack.TopType=bbtDefinition then
|
|
EndBlock;
|
|
if Stack.TopType in bbtAllIdentifierSections then
|
|
EndBlock;
|
|
end;
|
|
|
|
procedure StartIdentifierSection(Section: TFABBlockType);
|
|
begin
|
|
EndIdentifierSectionAndProc;
|
|
if Stack.TopType in (bbtAllCodeSections+bbtAllProcedures+[bbtNone]) then
|
|
BeginBlock(Section);
|
|
end;
|
|
|
|
procedure StartProcedure(Typ: TFABBlockType);
|
|
begin
|
|
if not (Stack.TopType in [bbtDefinition,bbtClassSection]) then
|
|
EndIdentifierSectionAndProc;
|
|
if Stack.TopType in (bbtAllCodeSections+bbtAllProcedures+[bbtNone,bbtDefinition,bbtClassSection])
|
|
then begin
|
|
BeginBlock(Typ);
|
|
BeginBlock(bbtProcedureHead);
|
|
end;
|
|
end;
|
|
|
|
procedure StartProperty;
|
|
begin
|
|
if Stack.TopType in [bbtNone, bbtClassSection] then
|
|
BeginBlock(bbtProperty);
|
|
end;
|
|
|
|
procedure StartClassSection;
|
|
begin
|
|
if (LastAtomStart>0) and (CompareIdentifiers('STRICT',@Src[LastAtomStart])=0)
|
|
then begin
|
|
exit;
|
|
end;
|
|
if Stack.TopType=bbtClassSection then
|
|
EndBlock;
|
|
if Stack.TopType=bbtClass then
|
|
BeginBlock(bbtClassSection);
|
|
end;
|
|
|
|
procedure EndProcedureHead;
|
|
begin
|
|
if Stack.TopType=bbtProcedureModifiers then
|
|
EndBlock;
|
|
if Stack.TopType=bbtProcedureHead then
|
|
EndBlock;
|
|
if (Stack.TopType in bbtAllProcedures) and (not IsProcedureImplementation)
|
|
then begin
|
|
EndBlock;
|
|
if Stack.TopType=bbtDefinition then
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
|
|
function CheckProcedureModifiers: boolean;
|
|
var
|
|
NextAtomStart: LongInt;
|
|
NextAtomEnd: LongInt;
|
|
i: LongInt;
|
|
ParentTyp: TFABBlockType;
|
|
begin
|
|
Result:=false;
|
|
i:=Stack.Top;
|
|
if Stack.TopType=bbtProcedureModifiers then
|
|
dec(i);
|
|
if (i<0) then exit;
|
|
if Stack.Stack[i].Typ<>bbtProcedureHead then exit;
|
|
dec(i);
|
|
if i<0 then exit;
|
|
if not (Stack.Stack[i].Typ in bbtAllProcedures) then exit;
|
|
dec(i);
|
|
if i<0 then exit;
|
|
if Stack.Stack[i].Typ=bbtDefinition then begin
|
|
dec(i);
|
|
if i<0 then exit;
|
|
end;
|
|
// cursor is on the semicolon, peek next atom
|
|
NextAtomStart:=AtomStart;
|
|
NextAtomEnd:=p;
|
|
ReadRawNextPascalAtom(Src,NextAtomEnd,NextAtomStart,NestedComments);
|
|
if NextAtomStart>length(Src) then exit;
|
|
ParentTyp:=Stack.Stack[i].Typ;
|
|
case ParentTyp of
|
|
bbtClassSection:
|
|
if not IsKeyWordMethodSpecifier.DoItCaseInsensitive(@Src[NextAtomStart])
|
|
then exit;
|
|
bbtProcedure,bbtFunction,bbtImplementation,bbtInterface:
|
|
if not IsKeyWordProcedureSpecifier.DoItCaseInsensitive(@Src[NextAtomStart])
|
|
then exit;
|
|
bbtTypeSection:
|
|
if not IsKeyWordProcedureTypeSpecifier.DoItCaseInsensitive(@Src[NextAtomStart])
|
|
then exit;
|
|
else
|
|
exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
var
|
|
r: PChar;
|
|
Block: PBlock;
|
|
CommentStartPos: LongInt;
|
|
CommentEndPos: LongInt;
|
|
begin
|
|
p:=StartPos;
|
|
if EndPos>length(Src) then EndPos:=length(Src)+1;
|
|
AtomStart:=p;
|
|
InFirstLine:=true;
|
|
repeat
|
|
LastAtomStart:=AtomStart;
|
|
LastAtomEnd:=p;
|
|
AtomStartedBlock:=false;
|
|
AtomEndedBlock:=false;
|
|
ReadRawNextPascalAtom(Src,p,AtomStart,NestedComments);
|
|
if InFirstLine and (not PositionsInSameLine(Src,LastAtomEnd,AtomStart)) then
|
|
InFirstLine:=false;
|
|
//DebugLn(['TFullyAutomaticBeautifier.ParseSource Atom=',copy(Src,AtomStart,p-AtomStart)]);
|
|
if p>EndPos then begin
|
|
if (AtomStart<EndPos) then begin
|
|
LastAtomStart:=AtomStart;
|
|
LastAtomEnd:=p;
|
|
end else begin
|
|
// EndPos between two atom: in space or comment
|
|
CommentStartPos:=FindNextNonSpace(Src,LastAtomEnd);
|
|
LastAtomStart:=0;
|
|
LastAtomEnd:=0;
|
|
if CommentStartPos<EndPos then begin
|
|
CommentEndPos:=FindCommentEnd(Src,CommentStartPos,NestedComments);
|
|
if CommentEndPos>EndPos then begin
|
|
// EndPos is in comment => return bounds of comment
|
|
LastAtomStart:=CommentStartPos;
|
|
LastAtomEnd:=CommentEndPos;
|
|
end;
|
|
end;
|
|
end;
|
|
break;
|
|
end else if AtomStart=EndPos then
|
|
break;
|
|
|
|
// check if found first inner atom of current block
|
|
FirstAtomOnNewLine:=IsFirstNonSpaceCharInLine(Src,AtomStart);
|
|
if FirstAtomOnNewLine and (Stack.Top>=0) then begin
|
|
Block:=@Stack.Stack[Stack.Top];
|
|
if (Block^.InnerStartPos<0) then
|
|
Block^.InnerStartPos:=AtomStart;
|
|
end;
|
|
|
|
r:=@Src[AtomStart];
|
|
|
|
case UpChars[r^] of
|
|
'B':
|
|
if CompareIdentifiers('BEGIN',r)=0 then begin
|
|
while Stack.TopType
|
|
in (bbtAllIdentifierSections+bbtAllCodeSections+bbtAllBrackets
|
|
+[bbtDefinition,bbtProcedureModifiers,bbtProcedureHead,bbtStatement])
|
|
do
|
|
EndBlock;
|
|
case Stack.TopType of
|
|
bbtNone:
|
|
BeginBlock(bbtMainBegin);
|
|
bbtProcedure,bbtFunction:
|
|
BeginBlock(bbtProcedureBegin);
|
|
bbtMainBegin,bbtProcedureBegin,bbtStatement:
|
|
BeginBlock(bbtFreeBegin);
|
|
bbtIfThen,bbtIfElse:
|
|
BeginBlock(bbtIfBegin);
|
|
else
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtFreeBegin);
|
|
end;
|
|
end;
|
|
'C':
|
|
case UpChars[r[1]] of
|
|
'A': // CA
|
|
if CompareIdentifiers('CASE',r)=0 then begin
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtCase);
|
|
end;
|
|
'L': // CL
|
|
if CompareIdentifiers('CLASS',r)=0 then begin
|
|
if Stack.TopType=bbtDefinition then
|
|
BeginClass;
|
|
end;
|
|
'O': // CO
|
|
if CompareIdentifiers('CONST',r)=0 then
|
|
StartIdentifierSection(bbtConstSection)
|
|
else if CompareIdentifiers('CONSTRUCTOR',r)=0 then
|
|
StartProcedure(bbtProcedure);
|
|
'P': // CP
|
|
if CompareIdentifiers('CPPCLASS',r)=0 then begin
|
|
if Stack.TopType=bbtDefinition then
|
|
BeginClass;
|
|
end;
|
|
end;
|
|
'D':
|
|
case UpChars[r[1]] of
|
|
'O':
|
|
if CompareIdentifiers('DO',r)=0 then begin
|
|
case Stack.TopType of
|
|
bbtWhile: BeginBlock(bbtWhileDo);
|
|
bbtFor: BeginBlock(bbtForDo);
|
|
bbtWith: BeginBlock(bbtWithDo);
|
|
end;
|
|
end;
|
|
'E':
|
|
if CompareIdentifiers('DESTRUCTOR',r)=0 then
|
|
StartProcedure(bbtProcedure);
|
|
'I':
|
|
if CompareIdentifiers('DISPINTERFACE',r)=0 then begin
|
|
if Stack.TopType=bbtDefinition then begin
|
|
BeginBlock(bbtClassInterface);
|
|
end;
|
|
end;
|
|
end;
|
|
'E':
|
|
case UpChars[r[1]] of
|
|
'L': // EL
|
|
if CompareIdentifiers('ELSE',r)=0 then begin
|
|
// common syntax error: open brackets in IF expression => ignore
|
|
while Stack.TopType in bbtAllBrackets do
|
|
EndBlock;
|
|
|
|
if Stack.TopType=bbtStatement then
|
|
EndBlock;
|
|
while Stack.TopType in [bbtFor,bbtForDo] do EndBlock;
|
|
case Stack.TopType of
|
|
bbtIfThen:
|
|
begin
|
|
EndBlock;
|
|
BeginBlock(bbtIfElse);
|
|
end;
|
|
bbtCaseOf,bbtCaseLabel,bbtCaseColon:
|
|
begin
|
|
if Stack.TopType=bbtCaseColon then
|
|
EndBlock;
|
|
if Stack.TopType=bbtCaseLabel then
|
|
EndBlock;
|
|
EndBlock; // close bbtCaseOf
|
|
BeginBlock(bbtCaseElse);
|
|
end;
|
|
end;
|
|
end;
|
|
'N': // EN
|
|
if CompareIdentifiers('END',r)=0 then begin
|
|
// common syntax error: open brackets in statements => ignore
|
|
while Stack.TopType in bbtAllBrackets do
|
|
EndBlock;
|
|
// statements can be closed by end without semicolon
|
|
while Stack.TopType in bbtAllAutoEnd do
|
|
EndBlock;
|
|
|
|
if Stack.TopType=bbtProcedureModifiers then
|
|
EndBlock;
|
|
if Stack.TopType=bbtProcedureHead then
|
|
EndBlock;
|
|
if Stack.TopType in bbtAllProcedures then
|
|
EndBlock;
|
|
if Stack.TopType=bbtClassSection then
|
|
EndBlock;
|
|
|
|
case Stack.TopType of
|
|
bbtMainBegin,bbtFreeBegin,
|
|
bbtRecord,bbtClass,bbtClassInterface,bbtTry,bbtFinally,bbtExcept,
|
|
bbtCase,bbtIfBegin:
|
|
EndBlock;
|
|
bbtCaseLabel,bbtCaseColon:
|
|
begin
|
|
if Stack.TopType=bbtCaseColon then
|
|
EndBlock;
|
|
EndBlock; // close bbtCaseLabel
|
|
EndBlock; // close bbtCaseOf
|
|
EndBlock; // close bbtCase
|
|
end;
|
|
bbtCaseElse,bbtCaseOf:
|
|
begin
|
|
EndBlock;
|
|
EndBlock; // close bbtCase
|
|
end;
|
|
bbtProcedureBegin:
|
|
begin
|
|
EndBlock;
|
|
if Stack.TopType in bbtAllProcedures then
|
|
EndBlock;
|
|
end;
|
|
bbtInterface,bbtImplementation,bbtInitialization,bbtFinalization:
|
|
EndBlock;
|
|
end;
|
|
|
|
while Stack.TopType in bbtAllAutoEnd do
|
|
EndBlock;
|
|
end;
|
|
'X': // EX
|
|
if CompareIdentifiers('EXCEPT',r)=0 then begin
|
|
if Stack.TopType=bbtTry then begin
|
|
EndBlock;
|
|
BeginBlock(bbtExcept);
|
|
end;
|
|
end;
|
|
end;
|
|
'F':
|
|
case UpChars[r[1]] of
|
|
'I': // FI
|
|
if CompareIdentifiers('FINALIZATION',r)=0 then begin
|
|
while Stack.Top>=0 do
|
|
EndBlock;
|
|
if Stack.TopType=bbtNone then
|
|
BeginBlock(bbtFinalization);
|
|
end else if CompareIdentifiers('FINALLY',r)=0 then begin
|
|
if Stack.TopType=bbtTry then begin
|
|
EndBlock;
|
|
BeginBlock(bbtFinally);
|
|
end;
|
|
end;
|
|
'O': // FO
|
|
if CompareIdentifiers('FOR',r)=0 then begin
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtFor)
|
|
end else if CompareIdentifiers('FORWARD',r)=0 then begin
|
|
if Stack.TopType=bbtProcedureModifiers then
|
|
EndBlock;
|
|
if Stack.TopType=bbtProcedureHead then
|
|
EndBlock;
|
|
if Stack.TopType in bbtAllProcedures then
|
|
EndBlock;
|
|
end;
|
|
'U': // FU
|
|
if CompareIdentifiers('FUNCTION',r)=0 then
|
|
StartProcedure(bbtFunction);
|
|
end;
|
|
'I':
|
|
case UpChars[r[1]] of
|
|
'F': // IF
|
|
if p-AtomStart=2 then begin
|
|
// 'IF'
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtIf);
|
|
end;
|
|
'N': // IN
|
|
case UpChars[r[2]] of
|
|
'I': // INI
|
|
if CompareIdentifiers('INITIALIZATION',r)=0 then begin
|
|
while Stack.Top>=0 do
|
|
EndBlock;
|
|
if Stack.TopType=bbtNone then
|
|
BeginBlock(bbtInitialization);
|
|
end;
|
|
'T': // INT
|
|
if CompareIdentifiers('INTERFACE',r)=0 then begin
|
|
case Stack.TopType of
|
|
bbtNone:
|
|
BeginBlock(bbtInterface);
|
|
bbtDefinition:
|
|
BeginBlock(bbtClassInterface);
|
|
end;
|
|
end;
|
|
end;
|
|
'M': // IM
|
|
if CompareIdentifiers('IMPLEMENTATION',r)=0 then begin
|
|
while Stack.Top>=0 do
|
|
EndBlock;
|
|
if Stack.TopType=bbtNone then
|
|
BeginBlock(bbtImplementation);
|
|
end;
|
|
end;
|
|
'L':
|
|
if CompareIdentifiers('LABEL',r)=0 then
|
|
StartIdentifierSection(bbtLabelSection);
|
|
'O':
|
|
case UpChars[r[1]] of
|
|
'B':
|
|
case UpChars[r[2]] of
|
|
'J':
|
|
case UpChars[r[3]] of
|
|
'C':
|
|
case UpChars[r[4]] of
|
|
'C':
|
|
if (CompareIdentifiers('ObjCCategory',r)=0)
|
|
or (CompareIdentifiers('ObjCClass',r)=0) then begin
|
|
if Stack.TopType=bbtDefinition then
|
|
BeginClass;
|
|
end;
|
|
'P':
|
|
if CompareIdentifiers('ObjCProtocol',r)=0 then begin
|
|
if Stack.TopType=bbtDefinition then
|
|
BeginBlock(bbtClassInterface);
|
|
end;
|
|
end;
|
|
'E':
|
|
if CompareIdentifiers('OBJECT',r)=0 then begin
|
|
if Stack.TopType=bbtDefinition then
|
|
BeginClass;
|
|
end;
|
|
end;
|
|
end;
|
|
'F': // OF
|
|
if CompareIdentifiers('OF',r)=0 then begin
|
|
case Stack.TopType of
|
|
bbtCase:
|
|
BeginBlock(bbtCaseOf);
|
|
bbtClass,bbtClassInterface:
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'P': // OP
|
|
if CompareIdentifiers('OPERATOR',r)=0 then
|
|
StartProcedure(bbtFunction);
|
|
end;
|
|
'P':
|
|
case UpChars[r[1]] of
|
|
'R': // PR
|
|
case UpChars[r[2]] of
|
|
'I': // PRI
|
|
if (CompareIdentifiers('PRIVATE',r)=0) then
|
|
StartClassSection;
|
|
'O': // PRO
|
|
case UpChars[r[3]] of
|
|
'P': // PROP
|
|
if (CompareIdentifiers('PROPERTY',r)=0) then
|
|
StartProperty;
|
|
'T': // PROT
|
|
if (CompareIdentifiers('PROTECTED',r)=0) then
|
|
StartClassSection;
|
|
'C': // PROC
|
|
if CompareIdentifiers('PROCEDURE',r)=0 then
|
|
StartProcedure(bbtProcedure);
|
|
end;
|
|
end;
|
|
'U': // PU
|
|
if (CompareIdentifiers('PUBLIC',r)=0)
|
|
or (CompareIdentifiers('PUBLISHED',r)=0) then
|
|
StartClassSection;
|
|
end;
|
|
'R':
|
|
case UpChars[r[1]] of
|
|
'E': // RE
|
|
case UpChars[r[2]] of
|
|
'C': // REC
|
|
if CompareIdentifiers('RECORD',r)=0 then
|
|
BeginBlock(bbtRecord);
|
|
'P': // REP
|
|
if CompareIdentifiers('REPEAT',r)=0 then
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtRepeat);
|
|
'S': // RES
|
|
if CompareIdentifiers('RESOURCESTRING',r)=0 then
|
|
StartIdentifierSection(bbtResourceStringSection);
|
|
end;
|
|
end;
|
|
'S':
|
|
if (CompareIdentifiers('STRICT',r)=0) then
|
|
StartClassSection;
|
|
'T':
|
|
case UpChars[r[1]] of
|
|
'H': // TH
|
|
if CompareIdentifiers('THEN',r)=0 then begin
|
|
// common syntax error: open brackets in if expression => ignore
|
|
while Stack.TopType in bbtAllBrackets do
|
|
EndBlock;
|
|
if Stack.TopType=bbtIf then
|
|
BeginBlock(bbtIfThen);
|
|
end;
|
|
'R': // TR
|
|
if CompareIdentifiers('TRY',r)=0 then begin
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtTry);
|
|
end;
|
|
'Y': // TY
|
|
if CompareIdentifiers('TYPE',r)=0 then begin
|
|
if Stack.TopType<>bbtDefinition then
|
|
StartIdentifierSection(bbtTypeSection);
|
|
end;
|
|
end;
|
|
'U':
|
|
case UpChars[r[1]] of
|
|
'S': // US
|
|
if CompareIdentifiers('USES',r)=0 then begin
|
|
if Stack.TopType in [bbtNone,bbtInterface,bbtImplementation] then
|
|
BeginBlock(bbtUsesSection);
|
|
end;
|
|
'N': // UN
|
|
if CompareIdentifiers('UNTIL',r)=0 then begin
|
|
EndTopMostBlock(bbtRepeat);
|
|
end;
|
|
end;
|
|
'V':
|
|
if CompareIdentifiers('VAR',r)=0 then begin
|
|
StartIdentifierSection(bbtVarSection);
|
|
end;
|
|
'W':
|
|
case UpChars[r[1]] of
|
|
'H': // WH
|
|
if CompareIdentifiers('WHILE',r)=0 then begin
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtWhile)
|
|
end;
|
|
'I': // WI
|
|
if CompareIdentifiers('WITH',r)=0 then begin
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtWith)
|
|
end;
|
|
end;
|
|
';':
|
|
begin
|
|
// common syntax error: unclosed bracket => ignore it
|
|
while Stack.TopType in [bbtStatementRoundBracket,bbtStatementEdgedBracket] do
|
|
EndBlock;
|
|
case Stack.TopType of
|
|
bbtUsesSection,bbtDefinition,bbtProperty:
|
|
EndBlock;
|
|
bbtIfThen,bbtIfElse,bbtStatement,bbtFor,bbtForDo,bbtCaseColon,bbtCaseLabel:
|
|
begin
|
|
while Stack.TopType in bbtAllAutoEnd do
|
|
EndBlock;
|
|
end;
|
|
bbtProcedureHead:
|
|
if CheckProcedureModifiers then
|
|
BeginBlock(bbtProcedureModifiers)
|
|
else
|
|
EndProcedureHead;
|
|
bbtProcedureModifiers:
|
|
if not CheckProcedureModifiers then
|
|
EndProcedureHead;
|
|
bbtClassSection,bbtClass:
|
|
begin
|
|
if Stack.TopType=bbtClassSection then
|
|
EndBlock;
|
|
EndBlock;
|
|
if Stack.TopType=bbtDefinition then
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
end;
|
|
':':
|
|
if p-AtomStart=1 then begin
|
|
// colon
|
|
case Stack.TopType of
|
|
bbtCaseLabel:
|
|
BeginBlock(bbtCaseColon);
|
|
bbtIf:
|
|
EndBlock;
|
|
bbtIfThen,bbtIfElse:
|
|
begin
|
|
EndBlock;
|
|
if Stack.TopType=bbtIf then
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
end;
|
|
'(':
|
|
if p-AtomStart=1 then begin
|
|
// round bracket open
|
|
case Stack.TopType of
|
|
bbtProcedureHead:
|
|
BeginBlock(bbtProcedureParamList);
|
|
else
|
|
if Stack.TopType in bbtAllStatements then begin
|
|
// ignore brackets in statements, there are no consistent rules
|
|
// to indent them
|
|
// Note: keep in mind: bbtCaseLabel
|
|
end else
|
|
BeginBlock(bbtTypeRoundBracket);
|
|
end;
|
|
end;
|
|
')':
|
|
if p-AtomStart=1 then begin
|
|
// round bracket close
|
|
EndTopMostBlock(bbtStatementEdgedBracket);
|
|
case Stack.TopType of
|
|
bbtProcedureParamList,bbtTypeRoundBracket,bbtStatementRoundBracket:
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'[':
|
|
if p-AtomStart=1 then begin
|
|
// edge bracket open
|
|
if Stack.TopType in bbtAllStatements then
|
|
BeginBlock(bbtStatementEdgedBracket)
|
|
else
|
|
BeginBlock(bbtTypeEdgedBracket);
|
|
end;
|
|
']':
|
|
if p-AtomStart=1 then begin
|
|
// edge bracket close
|
|
EndTopMostBlock(bbtStatementRoundBracket);
|
|
case Stack.TopType of
|
|
bbtTypeEdgedBracket,bbtStatementEdgedBracket:
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
end;
|
|
// check blocks that start without keyword/symbol
|
|
if (not AtomStartedBlock) and (not AtomEndedBlock)
|
|
and (r^<>';') then begin
|
|
if (Stack.TopType in bbtAllIdentifierSections)
|
|
and (IsIdentStartChar[Src[AtomStart]]) then begin
|
|
// new definition
|
|
BeginBlock(bbtDefinition);
|
|
end else if (Stack.TopType=bbtCaseOf) then begin
|
|
// new case label
|
|
BeginBlock(bbtCaseLabel);
|
|
end else if (Stack.TopType in bbtAllStatementParents) then begin
|
|
// new statement
|
|
BeginBlock(bbtStatement);
|
|
end;
|
|
end;
|
|
|
|
if FirstAtomOnNewLine and (Stack.Top>=0)
|
|
and (not AtomStartedBlock) and (not AtomEndedBlock)
|
|
and ((not InFirstLine) or LearnFromFirstLine)
|
|
and (Policies<>nil)
|
|
then begin
|
|
Block:=@Stack.Stack[Stack.Top];
|
|
if Block^.InnerIdent<0 then begin
|
|
UpdateBlockInnerIndent;
|
|
if (Block^.InnerIdent>=0) then begin
|
|
Policies.AddIndent(Block^.Typ,bbtNone,AtomStart,Block^.InnerIdent);
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
DebugLn([GetIndentStr(Stack.Top*2),'Indentation learned for bbtNone: ',EnumToStr(Block^.Typ),' Indent=',Block^.InnerIdent,' at ',PosToStr(p)]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
until false;
|
|
end;
|
|
|
|
procedure TFullyAutomaticBeautifier.ParseSource(const Src: string; StartPos,
|
|
EndPos: integer; NestedComments: boolean; Stack: TFABBlockStack;
|
|
Policies: TFABPolicies; LearnFromFirstLine: boolean);
|
|
var
|
|
LastAtomStart, LastAtomEnd: integer;
|
|
begin
|
|
ParseSource(Src,StartPos,EndPos,NestedComments,Stack,Policies,
|
|
LastAtomStart,LastAtomEnd,LearnFromFirstLine);
|
|
end;
|
|
|
|
function TFullyAutomaticBeautifier.FindPolicyInExamples(StartCode: TCodeBuffer;
|
|
Typ, SubTyp: TFABBlockType; UseNoneIfNotFound, UseSmallestIfNotFound: boolean
|
|
): TFABPolicies;
|
|
|
|
function CheckCode(Code: TCodeBuffer; out Policies: TFABPolicies): boolean;
|
|
// result=false : abort
|
|
var
|
|
AVLNode: TAVLTreeNode;
|
|
Stack: TFABBlockStack;
|
|
begin
|
|
Policies:=nil;
|
|
if Code=nil then exit(true);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.FindPolicyInExamples ',Code.Filename]);
|
|
{$ENDIF}
|
|
// search Policies for code
|
|
AVLNode:=FCodePolicies.FindKey(Code,@CompareCodeWithFABPolicy);
|
|
if AVLNode=nil then begin
|
|
Policies:=TFABPolicies.Create;
|
|
Policies.Code:=Code;
|
|
FCodePolicies.Add(Policies);
|
|
end else
|
|
Policies:=TFABPolicies(AVLNode.Data);
|
|
if Policies.CodeChangeStep<>Code.ChangeStep then begin
|
|
// parse code
|
|
Policies.Clear;
|
|
Policies.CodeChangeStep:=Code.ChangeStep;
|
|
Stack:=TFABBlockStack.Create;
|
|
try
|
|
ParseSource(Code.Source,1,length(Code.Source)+1,
|
|
GetNestedCommentsForCode(Code),Stack,Policies);
|
|
finally
|
|
Stack.Free;
|
|
end;
|
|
end;
|
|
// search policy
|
|
if Policies.GetIndent(Typ,SubTyp,UseNoneIfNotFound,UseSmallestIfNotFound)>=0
|
|
then begin
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.FindPolicyInExamples found in ',
|
|
Code.Filename,' ',EnumToStr(Typ),'/',EnumToStr(SubTyp)]);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
Policies:=nil;
|
|
Result:=true;
|
|
end;
|
|
|
|
var
|
|
CodeBuffers: TFPList;
|
|
i: Integer;
|
|
Code: TCodeBuffer;
|
|
Step: Integer;
|
|
Filenames: TStrings;
|
|
Abort: boolean;
|
|
begin
|
|
Result:=nil;
|
|
if not Assigned(OnGetExamples) then exit;
|
|
Step:=0;
|
|
repeat
|
|
// get examples for current step
|
|
CodeBuffers:=nil;
|
|
Filenames:=nil;
|
|
try
|
|
OnGetExamples(Self,StartCode,Step,CodeBuffers,Filenames);
|
|
if (CodeBuffers=nil) and (Filenames=nil) then exit;
|
|
// search policy in every example
|
|
if CodeBuffers<>nil then
|
|
for i:=0 to CodeBuffers.Count-1 do begin
|
|
Code:=TCodeBuffer(CodeBuffers[i]);
|
|
if not CheckCode(Code,Result) then exit;
|
|
if Result<>nil then exit;
|
|
end;
|
|
if (Filenames<>nil) and Assigned(OnLoadFile) then
|
|
for i:=0 to Filenames.Count-1 do begin
|
|
Abort:=false;
|
|
Code:=nil;
|
|
OnLoadFile(Self,Filenames[i],Code,Abort);
|
|
if Abort then exit;
|
|
if Code=nil then continue;
|
|
if not CheckCode(Code,Result) then exit;
|
|
if Result<>nil then exit;
|
|
end;
|
|
finally
|
|
CodeBuffers.Free;
|
|
Filenames.Free;
|
|
end;
|
|
// next step
|
|
inc(Step);
|
|
until false;
|
|
end;
|
|
|
|
function TFullyAutomaticBeautifier.GetNestedCommentsForCode(Code: TCodeBuffer
|
|
): boolean;
|
|
begin
|
|
Result:=true;
|
|
if Assigned(OnGetNestedComments) then
|
|
OnGetNestedComments(Self,Code,Result);
|
|
end;
|
|
|
|
function TFullyAutomaticBeautifier.AdjustByNextAtom(
|
|
const Source: string; CleanPos: integer; NestedComments: boolean;
|
|
Stack: TFABBlockStack; out TopType: TFABBlockType; out TopTypeValid: boolean
|
|
): integer;
|
|
{ For example:
|
|
if expr then
|
|
begin
|
|
|DoSomething;
|
|
|
|
if expr then
|
|
begin
|
|
|end;
|
|
}
|
|
|
|
function StackTopType: TFABBlockType;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i:=AdjustByNextAtom;
|
|
if (i>=0) and (i<=Stack.Top) then
|
|
Result:=Stack.Stack[i].Typ
|
|
else
|
|
Result:=bbtNone;
|
|
end;
|
|
|
|
procedure EndBlock(aCount: integer = 1);
|
|
begin
|
|
dec(AdjustByNextAtom,aCount);
|
|
TopTypeValid:=false;
|
|
end;
|
|
|
|
procedure BeginBlock(Typ: TFABBlockType);
|
|
begin
|
|
TopType:=Typ;
|
|
TopTypeValid:=true;
|
|
end;
|
|
|
|
procedure EndIdentifierSectionAndProc;
|
|
begin
|
|
if StackTopType=bbtDefinition then
|
|
EndBlock;
|
|
if StackTopType in bbtAllIdentifierSections then
|
|
EndBlock;
|
|
end;
|
|
|
|
procedure StartProcedure;
|
|
begin
|
|
if StackTopType=bbtDefinition then
|
|
EndBlock;
|
|
if StackTopType in (bbtAllIdentifierSections-[bbtClassSection]) then
|
|
EndBlock;
|
|
BeginBlock(bbtProcedure);
|
|
end;
|
|
|
|
function IsMethodDeclaration: boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i:=AdjustByNextAtom;
|
|
Result:=(StackTopType in bbtAllProcedures)
|
|
and (i>0)
|
|
and (Stack.Stack[i-1].Typ=bbtClassSection);
|
|
end;
|
|
|
|
procedure EndClassSection;
|
|
begin
|
|
if StackTopType=bbtClassSection then
|
|
EndBlock
|
|
else if IsMethodDeclaration then
|
|
EndBlock(2);
|
|
end;
|
|
|
|
procedure EndBigSection;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i:=AdjustByNextAtom;
|
|
if i>=0 then
|
|
EndBlock(i+1);
|
|
end;
|
|
|
|
procedure EndTopMostBlock(BlockTyp: TFABBlockType);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Stack.TopMostIndexOf(BlockTyp);
|
|
if i>=0 then
|
|
AdjustByNextAtom:=i-1;
|
|
end;
|
|
|
|
var
|
|
AtomStart: integer;
|
|
r: PChar;
|
|
p: LongInt;
|
|
begin
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.AdjustByNextAtom START']);
|
|
{$ENDIF}
|
|
Result:=Stack.Top;
|
|
TopType:=bbtNone;
|
|
TopTypeValid:=false;
|
|
if Result<0 then exit;
|
|
if (CleanPos<1) or (CleanPos>length(Source))
|
|
or (Source[CleanPos] in [#0..#31,' ']) then
|
|
exit;
|
|
p:=CleanPos;
|
|
ReadRawNextPascalAtom(Source,p,AtomStart,NestedComments);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.AdjustByNextAtom ',AtomStart<>CleanPos,' CleanPos=',dbgstr(copy(Source,CleanPos,10)),' AtomStart=',dbgstr(copy(Source,AtomStart,10))]);
|
|
{$ENDIF}
|
|
if AtomStart<>CleanPos then exit;
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.AdjustByNextAtom Atom=',copy(Source,AtomStart,p-AtomStart)]);
|
|
{$ENDIF}
|
|
TopTypeValid:=true;
|
|
r:=@Source[AtomStart];
|
|
case UpChars[r^] of
|
|
'B':
|
|
if CompareIdentifiers('BEGIN',r)=0 then begin
|
|
if StackTopType=bbtDefinition then
|
|
EndBlock;
|
|
if StackTopType in bbtAllIdentifierSections then
|
|
EndBlock;
|
|
case StackTopType of
|
|
bbtIfThen:
|
|
BeginBlock(bbtIfBegin);
|
|
bbtProcedure:
|
|
BeginBlock(bbtProcedureBegin);
|
|
end;
|
|
end;
|
|
'C':
|
|
if CompareIdentifiers('CONST',r)=0 then begin
|
|
EndIdentifierSectionAndProc;
|
|
if StackTopType=bbtProcedure then
|
|
BeginBlock(bbtLabelSection);
|
|
end;
|
|
'E':
|
|
case UpChars[r[1]] of
|
|
'L': // EL
|
|
if CompareIdentifiers('ELSE',r)=0 then begin
|
|
// common syntax error: open brackets in statements => ignore
|
|
while StackTopType in bbtAllBrackets do
|
|
EndBlock;
|
|
while StackTopType in [bbtFor,bbtForDo,bbtStatement] do
|
|
EndBlock;
|
|
case StackTopType of
|
|
bbtCaseOf,bbtCaseLabel,bbtCaseColon:
|
|
begin
|
|
if StackTopType=bbtCaseColon then
|
|
EndBlock;
|
|
if StackTopType=bbtCaseLabel then
|
|
EndBlock;
|
|
EndBlock; // close bbtCaseOf
|
|
BeginBlock(bbtCaseElse);
|
|
end;
|
|
bbtIfThen:
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'N': // EN
|
|
if CompareIdentifiers('END',r)=0 then begin
|
|
// common syntax error: open brackets in statements => ignore
|
|
while StackTopType in bbtAllBrackets do
|
|
EndBlock;
|
|
// statements can be closed by end without semicolon
|
|
while StackTopType in bbtAllAutoEnd do
|
|
EndBlock;
|
|
if IsMethodDeclaration then
|
|
EndBlock;
|
|
if StackTopType=bbtClassSection then
|
|
EndBlock;
|
|
|
|
case StackTopType of
|
|
bbtMainBegin,bbtFreeBegin,
|
|
bbtRecord,bbtClass,bbtClassInterface,bbtTry,bbtFinally,bbtExcept,
|
|
bbtCase,bbtIfBegin:
|
|
EndBlock;
|
|
bbtCaseOf,bbtCaseLabel,bbtCaseColon:
|
|
begin
|
|
if StackTopType=bbtCaseColon then
|
|
EndBlock;
|
|
if StackTopType=bbtCaseLabel then
|
|
EndBlock;
|
|
EndBlock; // close bbtCaseOf
|
|
EndBlock; // close bbtCase
|
|
end;
|
|
bbtCaseElse:
|
|
begin
|
|
EndBlock;
|
|
EndBlock; // close bbtCase
|
|
end;
|
|
bbtProcedureBegin:
|
|
EndBlock;
|
|
bbtInterface,bbtImplementation,bbtInitialization,bbtFinalization:
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'X': // EX
|
|
if CompareIdentifiers('EXCEPT',r)=0 then begin
|
|
if StackTopType=bbtTry then
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'F':
|
|
case UpChars[r[1]] of
|
|
'I': // FI
|
|
if CompareIdentifiers('FINALIZATION',r)=0 then begin
|
|
EndBigSection;
|
|
end else if CompareIdentifiers('FINALLY',r)=0 then begin
|
|
if StackTopType=bbtTry then
|
|
EndBlock;
|
|
end;
|
|
end;
|
|
'I':
|
|
case UpChars[r[1]] of
|
|
'F': // IF
|
|
if p-AtomStart=2 then begin
|
|
BeginBlock(bbtIf);
|
|
end;
|
|
'N': // IN
|
|
case UpChars[r[2]] of
|
|
'I': // INI
|
|
if CompareIdentifiers('INITIALIZATION',r)=0 then
|
|
EndBigSection;
|
|
end;
|
|
'M': // IM
|
|
if CompareIdentifiers('IMPLEMENTATION',r)=0 then begin
|
|
EndBigSection;
|
|
end;
|
|
end;
|
|
'L':
|
|
if CompareIdentifiers('LABEL',r)=0 then begin
|
|
EndIdentifierSectionAndProc;
|
|
if StackTopType=bbtProcedure then
|
|
BeginBlock(bbtLabelSection);
|
|
end;
|
|
'P':
|
|
case UpChars[r[1]] of
|
|
'R': // PR
|
|
case UpChars[r[2]] of
|
|
'I': // PRI
|
|
if CompareIdentifiers('PRIVATE',r)=0 then
|
|
EndClassSection;
|
|
'O': // PRO
|
|
case UpChars[r[3]] of
|
|
'C': // PROC
|
|
if CompareIdentifiers('PROCEDURE',r)=0 then
|
|
StartProcedure;
|
|
'T': // PROT
|
|
if CompareIdentifiers('PROTECTED',r)=0 then
|
|
EndClassSection;
|
|
end;
|
|
end;
|
|
'U': // PU
|
|
if (CompareIdentifiers('PUBLIC',r)=0)
|
|
or (CompareIdentifiers('PUBLISHED',r)=0) then
|
|
EndClassSection;
|
|
end;
|
|
'R':
|
|
case UpChars[r[1]] of
|
|
'E': // RE
|
|
case UpChars[r[2]] of
|
|
'S': // RES
|
|
if CompareIdentifiers('RESOURCESTRING',r)=0 then
|
|
EndIdentifierSectionAndProc;
|
|
end;
|
|
end;
|
|
'S':
|
|
if (CompareIdentifiers('STRICT',r)=0) then
|
|
EndClassSection;
|
|
'T':
|
|
case UpChars[r[1]] of
|
|
'Y': // TY
|
|
if CompareIdentifiers('TYPE',r)=0 then begin
|
|
EndIdentifierSectionAndProc;
|
|
if StackTopType=bbtProcedure then
|
|
BeginBlock(bbtTypeSection);
|
|
end;
|
|
end;
|
|
'U':
|
|
case UpChars[r[1]] of
|
|
'N': // UN
|
|
if CompareIdentifiers('UNTIL',r)=0 then begin
|
|
EndTopMostBlock(bbtRepeat);
|
|
end;
|
|
end;
|
|
'V':
|
|
if CompareIdentifiers('VAR',r)=0 then begin
|
|
EndIdentifierSectionAndProc;
|
|
if StackTopType=bbtProcedure then
|
|
BeginBlock(bbtVarSection);
|
|
end;
|
|
end;
|
|
{$IFDEF VerboseIndenter}
|
|
if (Stack.Top<>Result) then
|
|
DebugLn(['TFullyAutomaticBeautifier.AdjustByNextAtom block close: Stack.Top=',Stack.Top,' Result=',Result]);
|
|
if TopTypeValid then
|
|
DebugLn(['TFullyAutomaticBeautifier.AdjustByNextAtom block open: TopType=',EnumToStr(TopType)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TFullyAutomaticBeautifier.WriteDebugReport(Msg: string;
|
|
Stack: TFABBlockStack);
|
|
var
|
|
i: Integer;
|
|
Block: PBlock;
|
|
begin
|
|
DebugLn(['TFullyAutomaticBeautifier.WriteDebugReport ',Msg]);
|
|
if Stack<>nil then begin
|
|
for i:=0 to Stack.Top do begin
|
|
Block:=@Stack.Stack[i];
|
|
DebugLn([GetIndentStr(i*2),' : Typ=',EnumToStr(Block^.Typ),' StartPos=',Block^.StartPos,' InnerIdent=',Block^.InnerIdent,' InnerStartPos=',Block^.InnerStartPos]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TFullyAutomaticBeautifier.Create;
|
|
begin
|
|
FCodePolicies:=TAVLTree.Create(@CompareFABPoliciesWithCode);
|
|
DefaultTabWidth:=4;
|
|
UseDefaultIndentForTypes:=[bbtStatement,bbtStatementRoundBracket,
|
|
bbtStatementEdgedBracket,bbtTypeRoundBracket,bbtTypeEdgedBracket];
|
|
end;
|
|
|
|
destructor TFullyAutomaticBeautifier.Destroy;
|
|
begin
|
|
Clear;
|
|
FreeAndNil(FCodePolicies);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFullyAutomaticBeautifier.Clear;
|
|
begin
|
|
FCodePolicies.FreeAndClear;
|
|
end;
|
|
|
|
function TFullyAutomaticBeautifier.GetIndent(const Source: string;
|
|
CleanPos: integer; NewNestedComments: boolean;
|
|
UseLineStart: boolean; out Indent: TFABIndentationPolicy;
|
|
ContextLearn: boolean; const InsertText: string): boolean;
|
|
var
|
|
Block: TBlock;
|
|
SubType: TFABBlockType;
|
|
SubTypeValid: Boolean;
|
|
|
|
function CheckPolicies(Policies: TFABPolicies; var Found: boolean;
|
|
UseSmallestIfNotFound: boolean): boolean;
|
|
// returns true to stop searching
|
|
var
|
|
BlockIndent: LongInt;
|
|
begin
|
|
Result:=false;
|
|
Found:=false;
|
|
if (Policies=nil) then exit;
|
|
if SubTypeValid then
|
|
BlockIndent:=Policies.GetIndent(Block.Typ,SubType,true,UseSmallestIfNotFound)
|
|
else
|
|
BlockIndent:=Policies.GetSmallestIndent(Block.Typ);
|
|
if (BlockIndent<0) then exit;
|
|
// policy found
|
|
{$IFDEF VerboseIndenter}
|
|
if SubTypeValid then
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent policy found: Block.Typ=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' BlockIndent=',BlockIndent])
|
|
else
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent policy found: Block.Typ=',EnumToStr(Block.Typ),' BlockIndent=',BlockIndent]);
|
|
//Policies.WriteDebugReport;
|
|
{$ENDIF}
|
|
Indent.Indent:=GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)
|
|
+BlockIndent;
|
|
Indent.IndentValid:=true;
|
|
Result:=true;
|
|
Found:=true;
|
|
end;
|
|
|
|
var
|
|
Stack: TFABBlockStack;
|
|
Policies: TFABPolicies;
|
|
LastAtomStart, LastAtomEnd: integer;
|
|
StackIndex: LongInt;
|
|
PrevLineAtomEndPos: LongInt;
|
|
InsertTextStartPos: Integer;
|
|
ExamplePolicies: TFABPolicies;
|
|
begin
|
|
Result:=false;
|
|
FillByte(Indent,SizeOf(Indent),0);
|
|
|
|
CleanPos:=FindStartOfAtom(Source,CleanPos);
|
|
//DebugLn(['TFullyAutomaticBeautifier.GetIndent ']);
|
|
if CleanPos<1 then exit;
|
|
|
|
if UseLineStart and (InsertText='') then begin
|
|
while (CleanPos<=length(Source)) and (Source[CleanPos] in [' ',#9]) do
|
|
inc(CleanPos);
|
|
end;
|
|
|
|
Block:=CleanBlock;
|
|
Policies:=nil;
|
|
Stack:=TFABBlockStack.Create;
|
|
try
|
|
if ContextLearn then
|
|
Policies:=TFABPolicies.Create;
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
if Policies=nil then
|
|
Policies:=TFABPolicies.Create;
|
|
Policies.Code:=TCodeBuffer.Create;
|
|
Policies.Code.Source:=Source;
|
|
{$ENDIF}
|
|
// parse source in front
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent "',dbgstr(copy(Source,CleanPos-10,10)),'|',dbgstr(copy(Source,CleanPos,10)),'"']);
|
|
{$ENDIF}
|
|
ParseSource(Source,1,CleanPos,NewNestedComments,Stack,Policies,
|
|
LastAtomStart,LastAtomEnd);
|
|
{$IFDEF VerboseIndenter}
|
|
WriteDebugReport('After parsing code in front:',Stack);
|
|
{$ENDIF}
|
|
if (LastAtomStart>0) and (CleanPos>LastAtomStart) then begin
|
|
// in comment or atom
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed code in front: position in middle of atom, e.g. comment']);
|
|
{$ENDIF}
|
|
GetDefaultSrcIndent(Source,CleanPos,NewNestedComments,Indent);
|
|
exit(Indent.IndentValid);
|
|
end;
|
|
if LastAtomStart>0 then CleanPos:=LastAtomStart;
|
|
|
|
StackIndex:=Stack.Top;
|
|
SubType:=bbtNone;
|
|
SubTypeValid:=false;
|
|
if UseLineStart then begin
|
|
if InsertText='' then begin
|
|
StackIndex:=AdjustByNextAtom(Source,CleanPos,
|
|
NewNestedComments,Stack,SubType,SubTypeValid);
|
|
end else begin
|
|
InsertTextStartPos:=1;
|
|
while (InsertTextStartPos<=length(InsertText))
|
|
and (InsertText[InsertTextStartPos] in [' ',#9]) do
|
|
inc(InsertTextStartPos);
|
|
StackIndex:=AdjustByNextAtom(InsertText,InsertTextStartPos,
|
|
NewNestedComments,Stack,SubType,SubTypeValid);
|
|
end;
|
|
end;
|
|
if (StackIndex<0) then begin
|
|
// no context
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed code in front: no context']);
|
|
{$ENDIF}
|
|
Indent.Indent:=0;
|
|
Indent.IndentValid:=true;
|
|
exit(Indent.IndentValid);
|
|
end;
|
|
|
|
if (Stack.Stack[StackIndex].Typ in UseDefaultIndentForTypes) then begin
|
|
// use default indent
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent use default for this type: ',EnumToStr(Stack.Stack[StackIndex].Typ)]);
|
|
{$ENDIF}
|
|
GetDefaultSrcIndent(Source,CleanPos,NewNestedComments,Indent);
|
|
exit(Indent.IndentValid);
|
|
end;
|
|
|
|
if (StackIndex<Stack.Top) and (not SubTypeValid) then begin
|
|
// block(s) closed by next token
|
|
// use indent of block start
|
|
Block:=Stack.Stack[StackIndex+1];
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent next token close block: ',EnumToStr(Stack.TopType),' Block=',dbgstr(copy(Source,Block.StartPos,20))]);
|
|
{$ENDIF}
|
|
Indent.Indent:=GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth);
|
|
Indent.IndentValid:=true;
|
|
exit(true);
|
|
end;
|
|
Block:=Stack.Stack[StackIndex];
|
|
|
|
// search last non empty line start
|
|
PrevLineAtomEndPos:=CleanPos;
|
|
while (PrevLineAtomEndPos>0)
|
|
and (not (Source[PrevLineAtomEndPos] in [#10,#13])) do
|
|
dec(PrevLineAtomEndPos);
|
|
if (PrevLineAtomEndPos>0) then
|
|
PrevLineAtomEndPos:=FindPrevNonSpace(Source,PrevLineAtomEndPos);
|
|
|
|
//debugln(['TFullyAutomaticBeautifier.GetIndent BEFORE check for last sibling ',Stack.LastBlockClosed.StartPos,' ',PositionsInSameLine(Source,Stack.LastBlockClosed.StartPos,Block.StartPos),' SubTypeValid=',SubTypeValid]);
|
|
if (Stack.LastBlockClosed.StartPos>0)
|
|
and (not PositionsInSameLine(Source,Stack.LastBlockClosed.StartPos,Block.StartPos))
|
|
and ((not SubTypeValid) or (SubType in bbtAllAlignToSibling))
|
|
then begin
|
|
//debugln(['TFullyAutomaticBeautifier.GetIndent BEFORE2 check for last sibling ',Stack.LastBlockClosedAt,' ',PositionsInSameLine(Source,Stack.LastBlockClosedAt,PrevLineAtomEndPos)]);
|
|
// a child block was closed that was started in another line than current block
|
|
// and this subtype aligns as its siblings
|
|
if (Stack.LastBlockClosedAt>0)
|
|
and PositionsInSameLine(Source,Stack.LastBlockClosedAt,PrevLineAtomEndPos)
|
|
then begin
|
|
// between block end and CleanPos are only empty lines
|
|
// => indent like the last child block one
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent line after end of last sibling block, copy indent']);
|
|
{$ENDIF}
|
|
Indent.Indent:=GetLineIndentWithTabs(Source,
|
|
Stack.LastBlockClosed.StartPos,DefaultTabWidth);
|
|
end else begin
|
|
// between block end and CleanPos are non empty lines
|
|
// => indent like the last non empty line
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent unstructural code found, indent as last line: LastBlockClosedAt=',dbgstr(copy(Source,Stack.LastBlockClosedAt,10)),' PrevAtom=',dbgstr(copy(Source,PrevLineAtomEndPos,10))]);
|
|
{$ENDIF}
|
|
Indent.Indent:=GetLineIndentWithTabs(Source,
|
|
PrevLineAtomEndPos,DefaultTabWidth);
|
|
end;
|
|
Indent.IndentValid:=true;
|
|
exit(true);
|
|
end;
|
|
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed code in front: context=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' indent=',GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if Policies<>nil then begin
|
|
// check source in front for good match
|
|
if CheckPolicies(Policies,Result,false) then exit;
|
|
|
|
// parse source behind
|
|
ParseSource(Source,CleanPos,length(Source)+1,NewNestedComments,Stack,
|
|
Policies,false);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed source behind']);
|
|
{$ENDIF}
|
|
// check source for good match
|
|
if CheckPolicies(Policies,Result,false) then exit;
|
|
end;
|
|
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent Valid=',Indent.IndentValid,' Indent=',Indent.Indent]);
|
|
{$ENDIF}
|
|
|
|
// parse examples for good match
|
|
ExamplePolicies:=FindPolicyInExamples(nil,Block.Typ,SubType,true,false);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent searched examples for exact match: context=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' contextindent=',GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if CheckPolicies(ExamplePolicies,Result,false) then exit;
|
|
|
|
if Policies<>nil then begin
|
|
// check current source for any match
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent check current source for any match: context=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' contextindent=',GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if CheckPolicies(Policies,Result,true) then exit;
|
|
end;
|
|
|
|
// parse examples for any match
|
|
ExamplePolicies:=FindPolicyInExamples(nil,Block.Typ,SubType,true,true);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent searching examples for any match: context=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' contextindent=',GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if CheckPolicies(ExamplePolicies,Result,true) then exit;
|
|
|
|
finally
|
|
Stack.Free;
|
|
if Policies<>nil then
|
|
FreeAndNil(Policies.Code);
|
|
Policies.Free;
|
|
end;
|
|
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent no example found : context=',EnumToStr(Block.Typ),'/',EnumToStr(SubType),' contextindent=',GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if SubTypeValid then
|
|
GetDefaultIndentPolicy(Block.Typ,SubType,Indent)
|
|
else
|
|
GetDefaultIndentPolicy(Block.Typ,bbtNone,Indent);
|
|
if Indent.IndentValid then begin
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent using default ',Indent.Indent]);
|
|
{$ENDIF}
|
|
inc(Indent.Indent,GetLineIndentWithTabs(Source,Block.StartPos,DefaultTabWidth));
|
|
Result:=true;
|
|
end;
|
|
end;
|
|
|
|
function TFullyAutomaticBeautifier.GetIndents(const Source: string;
|
|
Positions: TFABPositionIndents; NewNestedComments: boolean;
|
|
UseLineStart: boolean; ContextLearn: boolean): boolean;
|
|
var
|
|
Needed: LongInt;
|
|
|
|
function CheckPolicies(Policies: TFABPolicies;
|
|
Item: PFABPositionIndent): boolean;
|
|
// returns true to stop searching
|
|
var
|
|
BlockIndent: LongInt;
|
|
begin
|
|
Result:=false;
|
|
if (Policies=nil) then exit;
|
|
if Item^.SubTypeValid then
|
|
BlockIndent:=Policies.GetIndent(Item^.Block.Typ,Item^.SubType,true,true)
|
|
else
|
|
BlockIndent:=Policies.GetSmallestIndent(Item^.Block.Typ);
|
|
if (BlockIndent<0) then exit;
|
|
// policy found
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent policy found: Block.Typ=',EnumToStr(Item^.Block.Typ),' BlockIndent=',BlockIndent]);
|
|
{$ENDIF}
|
|
Item^.Indent.Indent:=GetLineIndentWithTabs(Source,Item^.Block.StartPos,DefaultTabWidth)
|
|
+BlockIndent;
|
|
Item^.Indent.IndentValid:=true;
|
|
dec(Needed);
|
|
Result:=Needed=0;
|
|
end;
|
|
|
|
var
|
|
Item: PFABPositionIndent;
|
|
ItemIndex: Integer;
|
|
LastAtomStart, LastAtomEnd: integer;
|
|
Stack: TFABBlockStack;
|
|
StackIndex: LongInt;
|
|
Policies: TFABPolicies;
|
|
begin
|
|
Result:=false;
|
|
if (Positions=nil) or (Positions.Count=0) then exit;
|
|
Needed:=Positions.Count;
|
|
for ItemIndex:=0 to Positions.Count-1 do begin
|
|
Item:=@Positions.Items[ItemIndex];
|
|
Item^.CleanPos:=FindStartOfAtom(Source,Item^.CleanPos);
|
|
if Item^.CleanPos<1 then exit;
|
|
FillByte(Item^.Indent,SizeOf(Item^.Indent),0);
|
|
if (ItemIndex>0)
|
|
and (Item^.CleanPos<=Positions.Items[ItemIndex-1].CleanPos) then
|
|
exit;
|
|
Item^.Block:=CleanBlock;
|
|
Item^.SubType:=bbtNone;
|
|
Item^.SubTypeValid:=false;
|
|
end;
|
|
|
|
if UseLineStart then begin
|
|
Item:=@Positions.Items[0];
|
|
while (Item^.CleanPos<=length(Source))
|
|
and (Source[Item^.CleanPos] in [' ',#9]) do
|
|
inc(Item^.CleanPos);
|
|
end;
|
|
|
|
Policies:=nil;
|
|
Stack:=TFABBlockStack.Create;
|
|
try
|
|
if ContextLearn then
|
|
Policies:=TFABPolicies.Create;
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
Policies.Code:=TCodeBuffer.Create;
|
|
Policies.Code.Source:=Source;
|
|
{$ENDIF}
|
|
for ItemIndex:=0 to Positions.Count-1 do begin
|
|
Item:=@Positions.Items[ItemIndex];
|
|
if ItemIndex=0 then begin
|
|
// parse source in front
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent Index=',ItemIndex,' "',dbgstr(copy(Source,Item^.CleanPos-10,10)),'|',dbgstr(copy(Source,Item^.CleanPos,10)),'"']);
|
|
{$ENDIF}
|
|
ParseSource(Source,1,Item^.CleanPos,NewNestedComments,Stack,Policies,
|
|
LastAtomStart,LastAtomEnd);
|
|
end else begin
|
|
// parse to next position
|
|
ParseSource(Source,Positions.Items[ItemIndex-1].CleanPos,
|
|
Item^.CleanPos,NewNestedComments,Stack,nil,
|
|
LastAtomStart,LastAtomEnd);
|
|
end;
|
|
{$IFDEF VerboseIndenter}
|
|
WriteDebugReport('After parsing code: ',Stack);
|
|
{$ENDIF}
|
|
if (LastAtomStart>0) and (Item^.CleanPos>LastAtomStart) then begin
|
|
// in comment or atom
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent Index=',ItemIndex,' parsed code in front: position in middle of atom, e.g. comment']);
|
|
{$ENDIF}
|
|
GetDefaultSrcIndent(Source,Item^.CleanPos,NewNestedComments,Item^.Indent);
|
|
if Item^.Indent.IndentValid then begin
|
|
dec(Needed);
|
|
if Needed=0 then exit;
|
|
end;
|
|
end;
|
|
if not Item^.Indent.IndentValid then begin
|
|
if LastAtomStart>0 then Item^.CleanPos:=LastAtomStart;
|
|
|
|
Item^.SubType:=bbtNone;
|
|
Item^.SubTypeValid:=false;
|
|
if UseLineStart then
|
|
StackIndex:=AdjustByNextAtom(Source,Item^.CleanPos,
|
|
NewNestedComments,Stack,Item^.SubType,Item^.SubTypeValid);
|
|
if (StackIndex<0) then begin
|
|
// no context
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent Index=',ItemIndex,' parsed code in front: no context']);
|
|
{$ENDIF}
|
|
GetDefaultSrcIndent(Source,Item^.CleanPos,NewNestedComments,Item^.Indent);
|
|
if Item^.Indent.IndentValid then begin
|
|
dec(Needed);
|
|
if Needed=0 then exit(true);
|
|
end;
|
|
end;
|
|
end;
|
|
StackIndex:=Stack.Top;
|
|
if not Item^.Indent.IndentValid then begin
|
|
if StackIndex=0 then begin
|
|
dec(Needed);
|
|
if Needed=0 then exit(true);
|
|
end else begin
|
|
Item^.Block:=Stack.Stack[StackIndex];
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed code in front: context=',EnumToStr(Item^.Block.Typ),'/',EnumToStr(Item^.SubType),' indent=',GetLineIndentWithTabs(Source,Item^.Block.StartPos,DefaultTabWidth)]);
|
|
{$ENDIF}
|
|
if CheckPolicies(Policies,Item) then exit(true);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if Policies<>nil then begin
|
|
// parse source behind
|
|
ParseSource(Source,Item^.CleanPos,length(Source)+1,NewNestedComments,
|
|
Stack,Policies,false);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed source behind']);
|
|
{$ENDIF}
|
|
for ItemIndex:=0 to Positions.Count-1 do begin
|
|
Item:=@Positions.Items[ItemIndex];
|
|
if (not Item^.Indent.IndentValid) and (Item^.Block.Typ<>bbtNone) then
|
|
if CheckPolicies(Policies,Item) then exit(true);
|
|
end;
|
|
end;
|
|
finally
|
|
Stack.Free;
|
|
if Policies<>nil then
|
|
FreeAndNil(Policies.Code);
|
|
Policies.Free;
|
|
end;
|
|
|
|
// parse examples
|
|
for ItemIndex:=0 to Positions.Count-1 do begin
|
|
Item:=@Positions.Items[ItemIndex];
|
|
if (not Item^.Indent.IndentValid) and (Item^.Block.Typ<>bbtNone) then begin
|
|
Policies:=FindPolicyInExamples(nil,Item^.Block.Typ,Item^.SubType,true,true);
|
|
{$IFDEF VerboseIndenter}
|
|
DebugLn(['TFullyAutomaticBeautifier.GetIndent parsed examples']);
|
|
{$ENDIF}
|
|
if (Policies<>nil) and CheckPolicies(Policies,Item) then
|
|
exit(true);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFullyAutomaticBeautifier.GetDefaultSrcIndent(const Source: string;
|
|
CleanPos: integer; NewNestedComments: boolean; out
|
|
Indent: TFABIndentationPolicy);
|
|
// return indent of last non empty line
|
|
begin
|
|
Indent.Indent:=0;
|
|
Indent.IndentValid:=false;
|
|
// go to start of line
|
|
while (CleanPos>1) and (not (Source[CleanPos-1] in [#10,#13])) do
|
|
dec(CleanPos);
|
|
while CleanPos>1 do begin
|
|
// skip line end
|
|
dec(CleanPos);
|
|
if (CleanPos>1) and (Source[CleanPos-1] in [#10,#13])
|
|
and (Source[CleanPos]<>Source[CleanPos-1]) then
|
|
dec(CleanPos);
|
|
// read line
|
|
while (CleanPos>1) do begin
|
|
case Source[CleanPos-1] of
|
|
' ',#9: dec(CleanPos);
|
|
#10,#13:
|
|
begin
|
|
// empty line
|
|
break;
|
|
end;
|
|
else
|
|
dec(CleanPos);
|
|
Indent.Indent:=GetLineIndentWithTabs(Source,CleanPos,DefaultTabWidth);
|
|
Indent.IndentValid:=true;
|
|
exit;
|
|
end;
|
|
dec(CleanPos);
|
|
end;
|
|
end;
|
|
// only empty lines in front
|
|
end;
|
|
|
|
procedure TFullyAutomaticBeautifier.GetDefaultIndentPolicy(Typ,
|
|
SubTyp: TFABBlockType; out Indent: TFABIndentationPolicy);
|
|
begin
|
|
Indent.IndentValid:=false;
|
|
Indent.Indent:=0;
|
|
case Typ of
|
|
bbtInterface,
|
|
bbtImplementation,
|
|
bbtInitialization,
|
|
bbtFinalization,
|
|
bbtClass,
|
|
bbtClassInterface,
|
|
bbtProcedure,
|
|
bbtFunction,
|
|
bbtCaseOf,
|
|
bbtCaseLabel,
|
|
bbtIf:
|
|
begin
|
|
Indent.Indent:=0;
|
|
Indent.IndentValid:=true;
|
|
end;
|
|
bbtUsesSection,
|
|
bbtTypeSection,
|
|
bbtConstSection,
|
|
bbtVarSection,
|
|
bbtResourceStringSection,
|
|
bbtLabelSection,
|
|
bbtDefinition,
|
|
bbtRecord,
|
|
bbtClassSection,
|
|
bbtMainBegin,
|
|
bbtFreeBegin,
|
|
bbtRepeat,
|
|
bbtForDo,
|
|
bbtProcedureBegin,
|
|
bbtCase,
|
|
bbtCaseColon,
|
|
bbtCaseElse,
|
|
bbtTry,
|
|
bbtFinally,
|
|
bbtExcept,
|
|
bbtIfBegin:
|
|
begin
|
|
Indent.Indent:=2;
|
|
Indent.IndentValid:=true;
|
|
end;
|
|
bbtIfThen,
|
|
bbtIfElse:
|
|
if SubTyp=bbtIfBegin then begin
|
|
Indent.Indent:=0;
|
|
Indent.IndentValid:=true;
|
|
end else begin
|
|
Indent.Indent:=2;
|
|
Indent.IndentValid:=true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TFABPolicies }
|
|
|
|
function TFABPolicies.FindIndentation(Typ, SubType: TFABBlockType;
|
|
out InsertPos: integer): boolean;
|
|
// binary search
|
|
var
|
|
l: Integer;
|
|
r: Integer;
|
|
m: Integer;
|
|
Ind: PFABFoundIndentationPolicy;
|
|
begin
|
|
l:=0;
|
|
r:=IndentationCount-1;
|
|
while l<=r do begin
|
|
m:=(l+r) div 2;
|
|
Ind:=@Indentations[m];
|
|
if (Typ<Ind^.Typ) then
|
|
r:=m-1
|
|
else if (Typ>Ind^.Typ) then
|
|
l:=m+1
|
|
else if SubType<Ind^.SubTyp then
|
|
r:=m-1
|
|
else if SubType>Ind^.SubTyp then
|
|
l:=m+1
|
|
else begin
|
|
InsertPos:=m;
|
|
exit(true);
|
|
end;
|
|
end;
|
|
Result:=false;
|
|
if IndentationCount=0 then
|
|
InsertPos:=0
|
|
else if r<m then
|
|
InsertPos:=m
|
|
else
|
|
InsertPos:=m+1;
|
|
end;
|
|
|
|
constructor TFABPolicies.Create;
|
|
begin
|
|
|
|
end;
|
|
|
|
destructor TFABPolicies.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFABPolicies.Clear;
|
|
begin
|
|
IndentationCount:=0;
|
|
ReAllocMem(Indentations,0);
|
|
IndentationCapacity:=0;
|
|
end;
|
|
|
|
procedure TFABPolicies.AddIndent(Typ, SubType: TFABBlockType;
|
|
SrcPos, Indent: integer);
|
|
var
|
|
i: Integer;
|
|
Ind: PFABFoundIndentationPolicy;
|
|
begin
|
|
if not FindIndentation(Typ,SubType,i) then begin
|
|
inc(IndentationCount);
|
|
if IndentationCount>IndentationCapacity then begin
|
|
IndentationCapacity:=IndentationCapacity*2+12;
|
|
ReAllocMem(Indentations,SizeOf(TFABFoundIndentationPolicy)*IndentationCapacity);
|
|
end;
|
|
if i<IndentationCount-1 then
|
|
System.Move(Indentations[i],Indentations[i+1],
|
|
SizeOf(TFABFoundIndentationPolicy)*(IndentationCount-i-1));
|
|
Ind:=@Indentations[i];
|
|
Ind^.Typ:=Typ;
|
|
Ind^.SubTyp:=SubType;
|
|
Ind^.Indent:=Indent;
|
|
{$IFDEF StoreLearnedPositions}
|
|
Ind^.SrcPos:=SrcPos;
|
|
{$ENDIF}
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
DebugLn(['TFABPolicies.AddIndent New SubTyp ',EnumToStr(Typ),'-',EnumToStr(SubType),': indent=',Indent,' ',CodePosToStr(SrcPos)]);
|
|
ConsistencyCheck;
|
|
{$ENDIF}
|
|
end else begin
|
|
Ind:=@Indentations[i];
|
|
if Ind^.Indent<>Indent then begin
|
|
Ind^.Indent:=Indent;
|
|
{$IFDEF ShowCodeBeautifierLearn}
|
|
DebugLn(['TFABPolicies.AddIndent Changed SubTyp ',EnumToStr(Typ),'-',EnumToStr(SubType),': indent=',Indent,' ',CodePosToStr(SrcPos)]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TFABPolicies.GetSmallestIndent(Typ: TFABBlockType): integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:=High(integer);
|
|
for i:=0 to IndentationCount-1 do begin
|
|
if (Indentations[i].Typ<>Typ) or (Indentations[i].Indent<0) then continue;
|
|
{$IFDEF VerboseIndenter}
|
|
debugln(['TFABPolicies.GetSmallestIndent ',EnumToStr(Indentations[i].Typ),'/',EnumToStr(Indentations[i].SubTyp),' Indent=',Indentations[i].Indent
|
|
{$IFDEF StoreLearnedPositions}
|
|
,' SrcPos=',CodePosToStr(Indentations[i].SrcPos)
|
|
{$ENDIF}
|
|
]);
|
|
{$ENDIF}
|
|
if Indentations[i].Indent<Result then
|
|
Result:=Indentations[i].Indent;
|
|
end;
|
|
if Result=High(integer) then
|
|
Result:=-1;
|
|
end;
|
|
|
|
function TFABPolicies.GetIndent(Typ, SubType: TFABBlockType;
|
|
UseNoneIfNotFound, UseSmallestIfNotFound: boolean): integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
if FindIndentation(Typ,SubType,i) then begin
|
|
Result:=Indentations[i].Indent;
|
|
{$IFDEF VerboseIndenter}
|
|
debugln(['TFABPolicies.GetIndent ',EnumToStr(Typ),'/',EnumToStr(SubType),' learned at ',CodePosToStr(Indentations[i].SrcPos),' Result=',Result]);
|
|
{$ENDIF}
|
|
end else if UseNoneIfNotFound and FindIndentation(Typ,bbtNone,i) then begin
|
|
Result:=Indentations[i].Indent;
|
|
{$IFDEF VerboseIndenter}
|
|
debugln(['TFABPolicies.GetIndent ',EnumToStr(Typ),'/',EnumToStr(bbtNone),' learned at ',CodePosToStr(Indentations[i].SrcPos),' Result=',Result]);
|
|
{$ENDIF}
|
|
end else if UseSmallestIfNotFound then
|
|
Result:=GetSmallestIndent(Typ)
|
|
else
|
|
Result:=-1;
|
|
end;
|
|
|
|
function TFABPolicies.CodePosToStr(p: integer): string;
|
|
var
|
|
Line: integer;
|
|
Col: integer;
|
|
begin
|
|
if Code<>nil then begin
|
|
Code.AbsoluteToLineCol(p,Line,Col);
|
|
Result:='('+IntToStr(Line)+','+IntToStr(Col)+')';
|
|
end else begin
|
|
Result:='(p='+IntToStr(p)+')';
|
|
end;
|
|
end;
|
|
|
|
procedure TFABPolicies.ConsistencyCheck;
|
|
|
|
procedure Error;
|
|
begin
|
|
WriteDebugReport;
|
|
RaiseCatchableException('');
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
Ind1: PFABFoundIndentationPolicy;
|
|
Ind2: PFABFoundIndentationPolicy;
|
|
InsertPos: integer;
|
|
begin
|
|
for i:=0 to IndentationCount-1 do begin
|
|
Ind1:=@Indentations[i];
|
|
if Ind1^.Indent=High(Ind1^.Indent) then
|
|
Error;
|
|
if Ind1^.Indent<0 then
|
|
Error;
|
|
if i<IndentationCount-1 then begin
|
|
// check for duplicates and sorted
|
|
Ind2:=@Indentations[i+1];
|
|
if Ind1^.Typ>Ind2^.Typ then
|
|
Error;
|
|
if Ind1^.Typ=Ind2^.Typ then begin
|
|
if Ind1^.SubTyp>=Ind2^.SubTyp then
|
|
Error;
|
|
end;
|
|
end;
|
|
if not FindIndentation(Ind1^.Typ,Ind1^.SubTyp,InsertPos) then
|
|
Error;
|
|
if InsertPos<>i then
|
|
Error;
|
|
end;
|
|
end;
|
|
|
|
procedure TFABPolicies.WriteDebugReport;
|
|
var
|
|
i: Integer;
|
|
Ind: PFABFoundIndentationPolicy;
|
|
begin
|
|
debugln(['TFABPolicies.WriteDebugReport ']);
|
|
for i:=0 to IndentationCount-1 do begin
|
|
Ind:=@Indentations[i];
|
|
debugln([' ',i,'/',IndentationCount,' ',EnumToStr(Ind^.Typ),'=',ord(Ind^.Typ),' ',EnumToStr(Ind^.SubTyp),'=',ord(Ind^.SubTyp),' Indent=',Ind^.Indent]);
|
|
end;
|
|
end;
|
|
|
|
{ TFABPositionIndents }
|
|
|
|
procedure TFABPositionIndents.SetCount(const AValue: integer);
|
|
begin
|
|
if FCount=AValue then exit;
|
|
ReAllocMem(Items,SizeOf(TFABPositionIndent)*AValue);
|
|
if AValue>FCount then
|
|
FillByte(Items[FCount],SizeOf(TFABPositionIndent)*(AValue-FCount),0);
|
|
FCount:=AValue;
|
|
end;
|
|
|
|
constructor TFABPositionIndents.Create;
|
|
begin
|
|
|
|
end;
|
|
|
|
destructor TFABPositionIndents.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFABPositionIndents.Clear;
|
|
begin
|
|
Count:=0;
|
|
end;
|
|
|
|
end.
|
|
|