mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-05 19:32:43 +02:00
334 lines
8.7 KiB
ObjectPascal
334 lines
8.7 KiB
ObjectPascal
{(*}
|
|
(*------------------------------------------------------------------------------
|
|
Delphi Code formatter source code
|
|
|
|
The Original Code is AlignStatements.pas, released April 2000.
|
|
The Initial Developer of the Original Code is Anthony Steele.
|
|
Portions created by Anthony Steele are Copyright (C) 1999-2008 Anthony Steele.
|
|
All Rights Reserved.
|
|
Contributor(s): Anthony Steele.
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version 1.1
|
|
(the "License"). you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at http://www.mozilla.org/NPL/
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing rights and limitations
|
|
under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 2 or later (the "GPL")
|
|
See http://www.gnu.org/licenses/gpl.html
|
|
------------------------------------------------------------------------------*)
|
|
{*)}
|
|
unit AlignBase;
|
|
|
|
|
|
{ AFS 6 Feb 2K
|
|
base class Generalisation of AlignAssign and co.
|
|
This is the base class for all aligners
|
|
}
|
|
|
|
{$I JcfGlobal.inc}
|
|
|
|
interface
|
|
|
|
uses SwitchableVisitor, SourceToken, SourceTokenList;
|
|
|
|
type
|
|
|
|
TAlignBase = class(TSwitchableVisitor)
|
|
private
|
|
fcTokens: TSourceTokenList;
|
|
fcResumeToken: TSourceToken;
|
|
|
|
procedure AlignTheBlock(const pcToken: TSourceToken);
|
|
|
|
function StillSuspended(const pc: TSourceToken): boolean;
|
|
procedure IndentAll(const piIndent: integer);
|
|
|
|
procedure AddToken(const pcToken: TSOurceToken; const pbAligned: boolean);
|
|
|
|
protected
|
|
|
|
{ API for descendant classes }
|
|
function TokenIsAligned(const pt: TSourceToken): boolean; virtual; abstract;
|
|
function TokenEndsStatement(const pt: TSourceToken): boolean; virtual; abstract;
|
|
function IsTokenInContext(const pt: TSourceToken): boolean; virtual;
|
|
function TokenEndsAlignment(const pt: TSourceToken): boolean; virtual;
|
|
|
|
{ override this to let the child class see the tokens as they come
|
|
this is used by the align vars to detect the first non-white space token after the : }
|
|
|
|
procedure OnTokenRead(const pt: TSourceToken); virtual;
|
|
procedure ResetState; virtual;
|
|
|
|
function EnabledVisitSourceToken(const pcNode: TObject): boolean; override;
|
|
public
|
|
constructor Create; override;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{ delphi }SysUtils, Math,
|
|
{ jcl }
|
|
{ jcf } JcfSettings, TokenUtils, SetAlign, ParseTreeNodeType;
|
|
|
|
{ TAlignBase }
|
|
|
|
constructor TAlignBase.Create;
|
|
begin
|
|
inherited;
|
|
fcResumeToken := nil;
|
|
fcTokens := TSourceTokenList.Create;
|
|
end;
|
|
|
|
|
|
destructor TAlignBase.Destroy;
|
|
begin
|
|
FreeAndNil(fcTokens);
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TAlignBase.EnabledVisitSourceToken(const pcNode: TObject): Boolean;
|
|
var
|
|
lcToken: TSourceToken;
|
|
begin
|
|
Result := False;
|
|
lcToken := TSourceToken(pcNode);
|
|
|
|
if StillSuspended(lcToken) then
|
|
exit;
|
|
|
|
if not IsTokenInContext(lcToken) then
|
|
exit;
|
|
|
|
if TokenIsAligned(lcToken) then
|
|
AlignTheBlock(lcToken);
|
|
end;
|
|
|
|
|
|
procedure TAlignBase.AlignTheBlock(const pcToken: TSourceToken);
|
|
var
|
|
liCurrent, liLastKnownAlignedStatement: integer;
|
|
lcCurrent: TSourceToken;
|
|
lbDone, lbFirst, bThisStatementIsAligned, lbThisTokenIsAligned: boolean;
|
|
liMaxIndent, liMinIndent: integer;
|
|
liThisIndent: integer;
|
|
liSettingsMin, liSettingsMax, liSettingsMaxVariance: integer;
|
|
|
|
liUnalignedCount, liMaxUnaligned: integer;
|
|
liAlignedCount: integer;
|
|
begin
|
|
ResetState;
|
|
|
|
Assert(TokenIsAligned(pcToken));
|
|
|
|
lcCurrent := pcToken;
|
|
OnTokenRead(lcCurrent);
|
|
AddToken(lcCurrent, True);
|
|
|
|
liLastKnownAlignedStatement := 0;
|
|
liMaxIndent := lcCurrent.XPosition;
|
|
liMinIndent := liMaxIndent;
|
|
|
|
{ locate end of first statement
|
|
BufferTokens(0) is the first :=
|
|
there must be a semicolon soon after }
|
|
|
|
liCurrent := 0;
|
|
repeat
|
|
Inc(liCurrent);
|
|
lcCurrent := lcCurrent.NextToken;
|
|
|
|
if lcCurrent <> nil then
|
|
begin
|
|
OnTokenRead(pcToken);
|
|
AddToken(lcCurrent, False);
|
|
end;
|
|
until (lcCurrent = nil) or TokenEndsStatement(lcCurrent);
|
|
|
|
{ end the first statement on EOF?! - abort! }
|
|
if (lcCurrent = nil) then
|
|
exit;
|
|
|
|
with FormatSettings do
|
|
begin
|
|
liSettingsMin := Align.MinColumn;
|
|
liSettingsMax := Align.MaxColumn;
|
|
|
|
if pcToken.HasParentNode(nInterfaceSection) then
|
|
liSettingsMaxVariance := Align.MaxVarianceInterface
|
|
else
|
|
liSettingsMaxVariance := Align.MaxVariance;
|
|
|
|
liMaxUnaligned := Align.MaxUnalignedStatements;
|
|
end;
|
|
|
|
{ locate block end - include all consecutive aligned statements }
|
|
lbDone := False;
|
|
lbFirst := True;
|
|
liUnalignedCount := 0;
|
|
liAlignedCount := 1;
|
|
bThisStatementIsAligned := True; // first statement just read will be aligned
|
|
|
|
|
|
{ now look for consecutive similar statements to align }
|
|
while not lbDone do
|
|
begin
|
|
{ EOF?! - abort! }
|
|
if (lcCurrent = nil) then
|
|
begin
|
|
lbDone := True;
|
|
end
|
|
else
|
|
begin
|
|
lbThisTokenIsAligned := TokenIsAligned(lcCurrent);
|
|
if not lbFirst then
|
|
AddToken(lcCurrent, lbThisTokenIsAligned); // first one has been added above
|
|
|
|
lbFirst := False;
|
|
|
|
{ an aligned statement has the aligned token in it -
|
|
e.g. an assign statement has a ':=' in it :) }
|
|
if lbThisTokenIsAligned then
|
|
begin
|
|
bThisStatementIsAligned := True;
|
|
|
|
liThisIndent := lcCurrent.XPosition;
|
|
|
|
if liThisIndent >= liSettingsMin then
|
|
liMinIndent := Min(liThisIndent, liMinIndent);
|
|
|
|
{ store the higest indent in liMaxIndent
|
|
unless it is out of bounds, ie < liSettingsMin or > liSettingsMax }
|
|
liThisIndent := Max(liThisIndent, liSettingsMin);
|
|
if (liThisIndent > liMaxIndent) and (liThisIndent < liSettingsMax) and
|
|
(liThisIndent <= liMinIndent + liSettingsMaxVariance) then
|
|
liMaxIndent := liThisIndent;
|
|
|
|
{ may need to knock down the min if the first one is an outlier }
|
|
if (liThisIndent + liSettingsMaxVariance) < liMaxIndent then
|
|
liMaxIndent := liThisIndent;
|
|
|
|
end;
|
|
|
|
if TokenEndsStatement(lcCurrent) then
|
|
begin
|
|
{ ending a statement - was it an aligned one?
|
|
If not, maybe we should have stopped with the last statement }
|
|
|
|
if bThisStatementIsAligned then
|
|
begin
|
|
liLastKnownAlignedStatement := liCurrent;
|
|
liUnalignedCount := 0;
|
|
bThisStatementIsAligned := False;
|
|
Inc(liAlignedCount);
|
|
end
|
|
else
|
|
begin
|
|
{ look for consecutive unaligned statements to end the aligned block
|
|
depending on the config, this could be just 1 unalaigned statement
|
|
or it could be more
|
|
}
|
|
Inc(liUnalignedCount);
|
|
if liUnalignedCount > liMaxUnaligned then
|
|
lbDone := True;
|
|
end;
|
|
end;
|
|
|
|
if TokenEndsAlignment(lcCurrent) then
|
|
lbDone := True;
|
|
|
|
Inc(liCurrent);
|
|
lcCurrent := lcCurrent.NextToken;
|
|
OnTokenRead(lcCurrent);
|
|
end; { not EOF }
|
|
end; { while loop }
|
|
|
|
{ set iResume equal to the last token aligned }
|
|
fcResumeToken := fcTokens.SourceTokens[fcTokens.Count - 1];
|
|
|
|
{ now we know how far to go and how far to indent, do it? }
|
|
if (liLastKnownAlignedStatement > 0) and (liAlignedCount > 1) then
|
|
IndentAll(liMaxIndent);
|
|
|
|
ResetState;
|
|
end;
|
|
|
|
procedure TAlignBase.IndentAll(const piIndent: integer);
|
|
var
|
|
liLoop: integer;
|
|
lcCurrent, lcNew: TSourceToken;
|
|
begin
|
|
for liLoop := fcTokens.Count - 1 downto 0 do
|
|
begin
|
|
lcCurrent := fcTokens.SourceTokens[liLoop];
|
|
|
|
if (lcCurrent.UserTag > 0) and (lcCurrent.XPosition < piIndent) then
|
|
begin
|
|
{ indent to the specified level - make a new space token }
|
|
lcNew := InsertSpacesBefore(lcCurrent, piIndent - lcCurrent.XPosition);
|
|
fcTokens.Insert(liLoop, lcNew);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
procedure TAlignBase.OnTokenRead(const pt: TSourceToken);
|
|
begin
|
|
// here for override
|
|
end;
|
|
|
|
procedure TAlignBase.ResetState;
|
|
begin
|
|
fcTokens.Clear;
|
|
end;
|
|
|
|
function TAlignBase.IsTokenInContext(const pt: TSourceToken): boolean;
|
|
begin
|
|
// here for override
|
|
Result := True;
|
|
end;
|
|
|
|
function TAlignBase.StillSuspended(const pc: TSourceToken): boolean;
|
|
begin
|
|
if (fcResumeToken = nil) then
|
|
begin
|
|
// we are not suspended, so go.
|
|
Result := False;
|
|
end
|
|
else
|
|
begin
|
|
// have we reached the end of suspension?
|
|
Result := (fcResumeToken <> pc);
|
|
if not Result then
|
|
fcResumeToken := nil;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TAlignBase.AddToken(const pcToken: TSourceToken; const pbAligned: boolean);
|
|
begin
|
|
Assert(pcToken <> nil);
|
|
|
|
if pbAligned then
|
|
pcToken.UserTag := 1
|
|
else
|
|
pcToken.UserTag := 0;
|
|
|
|
fcTokens.Add(pcToken);
|
|
end;
|
|
|
|
function TAlignBase.TokenEndsAlignment(const pt: TSourceToken): boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
end.
|