mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 19:03:40 +02:00
1056 lines
35 KiB
ObjectPascal
1056 lines
35 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:
|
|
TChangeDeclarationTool enhances TExtractProcTool.
|
|
TChangeDeclarationTool provides functions to change/move declarations.
|
|
}
|
|
unit ChangeDeclarationTool;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{off $define VerboseAddProcModifier}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Laz_AVL_Tree, contnrs,
|
|
// Codetools
|
|
CodeAtom, CodeCache, FileProcs, CodeTree, ExtractProcTool, FindDeclarationTool,
|
|
BasicCodeTools, KeywordFuncLists, LinkScanner, SourceChanger;
|
|
|
|
type
|
|
TChangeParamListAction = (
|
|
cplaInsertNewParam, // insert at Index a new parameter. Use DefaultValue in callers.
|
|
cplaDeleteParam, // delete parameter at Index. In callers too.
|
|
cplaMoveParam, // move parameter at OldIndex to Index
|
|
cplaChangeDefaultValue // if caller use default change to old value, if caller use new value remove it
|
|
);
|
|
TChangeParamListActions = set of TChangeParamListAction;
|
|
|
|
{ TChangeParamListItem }
|
|
|
|
TChangeParamListItem = class
|
|
public
|
|
Action: TChangeParamListAction;
|
|
Index: integer;
|
|
OldIndex: integer;
|
|
ParamModifier: string;
|
|
ParamName: string;
|
|
ParamType: string;
|
|
DefaultValue: string;
|
|
constructor CreateInsertNewParam(TheIndex: integer;
|
|
aModifier, aName, aType: string);
|
|
constructor CreateDeleteParam(TheIndex: integer);
|
|
constructor CreateMoveParam(TheOldIndex, NewIndex: integer);
|
|
constructor CreateChangeDefaultValue(TheIndex: integer; aValue: string);
|
|
end;
|
|
|
|
{ TChangeDeclarationTool }
|
|
|
|
TChangeDeclarationTool = class(TExtractCodeTool)
|
|
private
|
|
procedure CDTParseParamList(ParentNode: TCodeTreeNode; Transactions: TObject);
|
|
function ApplyParamListTransactions(Transactions: TObject;
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
function ChangeParamListDeclaration(ParentNode: TCodeTreeNode;
|
|
Changes: TObjectList; // list of TChangeParamListItem
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
function ChangeParamListDeclarationAtPos(CleanPos: integer;
|
|
Changes: TObjectList; // list of TChangeParamListItem
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
public
|
|
function ChangeParamList(Changes: TObjectList; // list of TChangeParamListItem
|
|
var ProcPos: TCodeXYPosition; // if it is in this unit the proc declaration is changed and this position is cleared
|
|
TreeOfPCodeXYPosition: TAVLTree; // positions in this unit are processed and removed from the tree
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
|
|
function AddProcModifier(const CursorPos: TCodeXYPosition; aModifier: string;
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
end;
|
|
|
|
implementation
|
|
|
|
type
|
|
|
|
{ TChgPrmInsertNew }
|
|
|
|
TChgPrmInsertNew = class
|
|
public
|
|
Src: string; // if Src='' then use Modifier+Name+Typ+Value
|
|
Modifier: string;
|
|
Name: string;
|
|
Typ: string;
|
|
DefaultValue: string;
|
|
CopyFromParamIndex: integer;
|
|
constructor Create(aSrc, aModifier, aName, aType, aValue: string;
|
|
aCopyFrom: integer);
|
|
end;
|
|
|
|
{ TChgPrmModify }
|
|
|
|
TChgPrmModify = class
|
|
public
|
|
Node: TCodeTreeNode; // old param node
|
|
// example: (var buf; {header} a,b:c; d:word=3 {footer}; ...)
|
|
HeaderCommentPos: integer;
|
|
Modifier: TAtomPosition; // optional: const, var out, constref
|
|
Name: TAtomPosition; // name or '...' (MacPas varargs)
|
|
Typ: TAtomPosition; // optional
|
|
DefaultValue: TAtomPosition; // optional
|
|
HasComments: boolean;
|
|
FooterCommentEndPos: integer;
|
|
Separator: integer; // the comma or semicolon to the next parameter
|
|
CommentAfterSeparator: TAtomPosition;
|
|
FirstInGroup: integer; // index of first parameter i a group, e.g. a,b:c
|
|
LastInGroup: integer;
|
|
|
|
Delete: boolean;
|
|
ChangeDefaultValue: boolean;
|
|
NewDefaultValue: string;
|
|
InsertBehind: TObjectList;// list of TChgPrmInsertNew
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function GetFirstPos: integer;
|
|
function GetLastPos(WithSeparator: boolean): integer;
|
|
end;
|
|
|
|
{ TChangeParamListTransactions }
|
|
|
|
TChangeParamListTransactions = class
|
|
public
|
|
Node: TCodeTreeNode; // ctnParameterList
|
|
OldNodes: array of TChgPrmModify; // one for each old param node
|
|
InsertFirst: TObjectList;// list of TChgPrmInsertNew
|
|
Changes: TObjectList;
|
|
BehindNamePos: integer;
|
|
BracketOpenPos: integer;
|
|
BracketClosePos: integer;
|
|
constructor Create(ParamList: TCodeTreeNode);
|
|
destructor Destroy; override;
|
|
function MaxPos: integer;
|
|
procedure Insert(Index: integer; Insertion: TChgPrmInsertNew);
|
|
procedure CreateChanges;
|
|
end;
|
|
|
|
{ TChangeParamTransactionInsert }
|
|
|
|
constructor TChgPrmInsertNew.Create(aSrc, aModifier, aName, aType,
|
|
aValue: string; aCopyFrom: integer);
|
|
begin
|
|
Src:=aSrc;
|
|
Modifier:=aModifier;
|
|
Name:=aName;
|
|
Typ:=aType;
|
|
DefaultValue:=aValue;
|
|
CopyFromParamIndex:=aCopyFrom;
|
|
end;
|
|
|
|
constructor TChgPrmModify.Create;
|
|
begin
|
|
InsertBehind:=TObjectList.create(true);
|
|
end;
|
|
|
|
destructor TChgPrmModify.Destroy;
|
|
begin
|
|
InsertBehind.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TChgPrmModify.GetFirstPos: integer;
|
|
begin
|
|
if HeaderCommentPos>0 then
|
|
Result:=HeaderCommentPos
|
|
else if Modifier.StartPos>0 then
|
|
Result:=Modifier.StartPos
|
|
else
|
|
Result:=Name.StartPos;
|
|
end;
|
|
|
|
function TChgPrmModify.GetLastPos(WithSeparator: boolean): integer;
|
|
begin
|
|
Result:=0;
|
|
if WithSeparator then begin
|
|
if CommentAfterSeparator.EndPos>0 then
|
|
Result:=CommentAfterSeparator.EndPos
|
|
else if Separator>0 then
|
|
Result:=Separator;
|
|
if Result>0 then exit;
|
|
end;
|
|
if FooterCommentEndPos>0 then
|
|
Result:=FooterCommentEndPos
|
|
else if DefaultValue.EndPos>0 then
|
|
Result:=DefaultValue.EndPos
|
|
else if Typ.EndPos>0 then
|
|
Result:=Typ.EndPos
|
|
else
|
|
Result:=Name.EndPos;
|
|
end;
|
|
|
|
{ TChangeParamListInfos }
|
|
|
|
function TChangeParamListTransactions.MaxPos: integer;
|
|
begin
|
|
Result:=length(OldNodes);
|
|
end;
|
|
|
|
procedure TChangeParamListTransactions.Insert(Index: integer;
|
|
Insertion: TChgPrmInsertNew);
|
|
begin
|
|
if Index=0 then
|
|
InsertFirst.Add(Insertion)
|
|
else
|
|
OldNodes[Index-1].InsertBehind.Add(Insertion);
|
|
end;
|
|
|
|
procedure TChangeParamListTransactions.CreateChanges;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
FreeAndNil(Changes);
|
|
Changes:=TObjectList.create(false);
|
|
for i:=0 to InsertFirst.Count-1 do
|
|
Changes.Add(InsertFirst[i]);
|
|
for i:=0 to length(OldNodes)-1 do begin
|
|
Changes.Add(OldNodes[i]);
|
|
for j:=0 to OldNodes[i].InsertBehind.Count-1 do
|
|
Changes.Add(OldNodes[i].InsertBehind[j]);
|
|
end;
|
|
end;
|
|
|
|
constructor TChangeParamListTransactions.Create(ParamList: TCodeTreeNode);
|
|
var
|
|
ParamNode: TCodeTreeNode;
|
|
i: Integer;
|
|
begin
|
|
InsertFirst:=TObjectList.create(true);
|
|
Node:=ParamList;
|
|
if Node<>nil then begin
|
|
SetLength(OldNodes,Node.ChildCount);
|
|
ParamNode:=Node.FirstChild;
|
|
i:=0;
|
|
while ParamNode<>nil do begin
|
|
OldNodes[i]:=TChgPrmModify.Create;
|
|
OldNodes[i].Node:=ParamNode;
|
|
ParamNode:=ParamNode.NextBrother;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TChangeParamListTransactions.Destroy;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FreeAndNil(Changes);
|
|
for i:=0 to length(OldNodes)-1 do
|
|
FreeAndNil(OldNodes[i]);
|
|
SetLength(OldNodes,0);
|
|
FreeAndNil(InsertFirst);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TChangeParamListItem }
|
|
|
|
constructor TChangeParamListItem.CreateInsertNewParam(TheIndex: integer;
|
|
aModifier, aName, aType: string);
|
|
begin
|
|
Action:=cplaInsertNewParam;
|
|
Index:=TheIndex;
|
|
ParamModifier:=aModifier;
|
|
ParamName:=aName;
|
|
ParamType:=aType;
|
|
end;
|
|
|
|
constructor TChangeParamListItem.CreateDeleteParam(TheIndex: integer);
|
|
begin
|
|
Action:=cplaDeleteParam;
|
|
Index:=TheIndex;
|
|
end;
|
|
|
|
constructor TChangeParamListItem.CreateMoveParam(TheOldIndex, NewIndex: integer);
|
|
begin
|
|
Action:=cplaMoveParam;
|
|
Index:=NewIndex;
|
|
OldIndex:=TheOldIndex;
|
|
end;
|
|
|
|
constructor TChangeParamListItem.CreateChangeDefaultValue(TheIndex: integer;
|
|
aValue: string);
|
|
begin
|
|
Action:=cplaChangeDefaultValue;
|
|
Index:=TheIndex;
|
|
DefaultValue:=aValue;
|
|
end;
|
|
|
|
{ TChangeDeclarationTool }
|
|
|
|
procedure TChangeDeclarationTool.CDTParseParamList(ParentNode: TCodeTreeNode;
|
|
Transactions: TObject);
|
|
var
|
|
t: TChangeParamListTransactions;
|
|
ParamIndex: Integer;
|
|
CurParam: TChgPrmModify;
|
|
FirstInGroup: integer;
|
|
i: LongInt;
|
|
CloseBracket: Char;
|
|
StartPos: LongInt;
|
|
EndPos: Integer;
|
|
p: PChar;
|
|
|
|
procedure ReadPrefixModifier;
|
|
begin
|
|
// read parameter prefix modifier
|
|
if UpAtomIs('VAR') or UpAtomIs('CONST') or UpAtomIs('CONSTREF')
|
|
or (UpAtomIs('OUT') and (cmsOut in Scanner.CompilerModeSwitches))
|
|
then begin
|
|
CurParam.Modifier:=CurPos;
|
|
ReadNextAtom;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
t:=Transactions as TChangeParamListTransactions;
|
|
// parse param list
|
|
if ParentNode.Desc=ctnProcedureHead then
|
|
MoveCursorBehindProcName(ParentNode)
|
|
else if ParentNode.Desc=ctnProperty then
|
|
MoveCursorBehindPropName(ParentNode)
|
|
else
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration kind not supported: '+ParentNode.DescAsString);
|
|
t.BehindNamePos:=LastAtoms.GetPriorAtom.EndPos;
|
|
// read bracket
|
|
if CurPos.Flag=cafRoundBracketOpen then
|
|
CloseBracket:=')'
|
|
else if CurPos.Flag=cafEdgedBracketOpen then
|
|
CloseBracket:=']'
|
|
else
|
|
exit; // no param list
|
|
|
|
t.BracketOpenPos:=CurPos.StartPos;
|
|
ParamIndex:=0;
|
|
ReadNextAtom;
|
|
repeat
|
|
CurParam:=t.OldNodes[ParamIndex];
|
|
FirstInGroup:=-1;
|
|
if AtomIs('...') then begin
|
|
// MacPas '...' VarArgs parameter
|
|
CurParam.Name:=CurPos;
|
|
ReadNextAtom;
|
|
// parse end of parameter list
|
|
if (CurPos.StartPos>SrcLen)
|
|
or (Src[CurPos.StartPos]<>CloseBracket) then
|
|
RaiseCharExpectedButAtomFound(20170421201949,CloseBracket);
|
|
break;
|
|
end else begin
|
|
ReadPrefixModifier;
|
|
// read parameter name(s)
|
|
repeat
|
|
AtomIsIdentifierE;
|
|
CurParam.Name:=CurPos;
|
|
ReadNextAtom;
|
|
if CurPos.Flag<>cafComma then
|
|
break;
|
|
CurParam.Separator:=CurPos.StartPos;
|
|
// A group. Example: b,c:char;
|
|
if FirstInGroup<0 then FirstInGroup:=ParamIndex;
|
|
inc(ParamIndex);
|
|
CurParam:=t.OldNodes[ParamIndex];
|
|
ReadNextAtom;
|
|
until false;
|
|
if FirstInGroup>=0 then begin
|
|
for i:=FirstInGroup to ParamIndex do begin
|
|
t.OldNodes[i].FirstInGroup:=FirstInGroup;
|
|
t.OldNodes[i].LastInGroup:=ParamIndex;
|
|
end;
|
|
end;
|
|
// read parameter type
|
|
if CurPos.Flag=cafColon then begin
|
|
ReadNextAtom;
|
|
CurParam.Typ:=CurPos;
|
|
if not ReadParamType(true,false,[]) then exit;
|
|
CurParam.Typ.EndPos:=LastAtoms.GetPriorAtom.EndPos;
|
|
if CurPos.Flag=cafEqual then begin
|
|
// read default value
|
|
ReadNextAtom;
|
|
CurParam.DefaultValue:=CurPos;
|
|
ReadConstant(true,false,[]);
|
|
CurParam.DefaultValue.EndPos:=LastAtoms.GetPriorAtom.EndPos;
|
|
end;
|
|
end;
|
|
// close bracket or semicolon
|
|
if CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose] then begin
|
|
t.BracketClosePos:=CurPos.StartPos;
|
|
break;
|
|
end;
|
|
if CurPos.Flag<>cafSemicolon then
|
|
RaiseCharExpectedButAtomFound(20170421201951,CloseBracket);
|
|
CurParam.Separator:=CurPos.StartPos;
|
|
inc(ParamIndex);
|
|
end;
|
|
until false;
|
|
|
|
// check for each parameter if it has comments
|
|
for i:=0 to t.MaxPos-1 do begin
|
|
CurParam:=t.OldNodes[i];
|
|
|
|
// check if the param has a comment inside
|
|
StartPos:=CurParam.GetFirstPos;
|
|
EndPos:=CurParam.GetLastPos(false);
|
|
CurParam.HasComments:=FindNextComment(Src,StartPos,EndPos-1)>=EndPos;
|
|
|
|
// check if the param has a comment in front belonging to the param
|
|
if i=0 then
|
|
StartPos:=t.BracketOpenPos+1
|
|
else
|
|
StartPos:=t.OldNodes[i-1].GetLastPos(true);
|
|
EndPos:=CurParam.GetFirstPos;
|
|
while (StartPos<EndPos) and IsSpaceChar[Src[StartPos]] do inc(StartPos);
|
|
if StartPos<EndPos then begin
|
|
// there is a comment in front
|
|
CurParam.HeaderCommentPos:=StartPos;
|
|
CurParam.HasComments:=true;
|
|
end;
|
|
|
|
// check if the param has a comment behind, but in front of the next separator
|
|
StartPos:=CurParam.GetLastPos(false);
|
|
if CurParam.Separator>0 then
|
|
EndPos:=CurParam.Separator
|
|
else
|
|
EndPos:=t.BracketClosePos;
|
|
while (StartPos<EndPos) and IsSpaceChar[Src[EndPos-1]] do dec(EndPos);
|
|
if StartPos<EndPos then begin
|
|
// there is a comment behind param and in front of the next separator
|
|
CurParam.FooterCommentEndPos:=EndPos;
|
|
CurParam.HasComments:=true;
|
|
end;
|
|
|
|
// check if the param has a comment behind the next separator
|
|
if CurParam.Separator>0 then begin
|
|
StartPos:=CurParam.Separator;
|
|
p:=@Src[StartPos];
|
|
while p^ in [' ',#9] do inc(p);
|
|
if (p^='{') or ((p^='(') and (p[1]='*')) or ((p^='/') and (p[1]='/')) then
|
|
begin
|
|
// there is a comment after the separator and it belongs to this param
|
|
StartPos:=p-PChar(Src)+1;
|
|
CurParam.CommentAfterSeparator.StartPos:=StartPos;
|
|
EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
|
|
CurParam.CommentAfterSeparator.EndPos:=EndPos;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
function TChangeDeclarationTool.ApplyParamListTransactions(
|
|
Transactions: TObject; SourceChanger: TSourceChangeCache): boolean;
|
|
var
|
|
t: TChangeParamListTransactions;
|
|
InsertCode: String;
|
|
InsertPos: Integer; // behind the last kept parameter
|
|
ReplaceStartPos: Integer;
|
|
ReplaceEndPos: Integer;
|
|
LastChgPos: integer;
|
|
|
|
function GetLastChgPos: integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:=-1;
|
|
for i:=0 to t.Changes.Count-1 do begin
|
|
if (t.Changes[i] is TChgPrmInsertNew)
|
|
or ((t.Changes[i] is TChgPrmModify)
|
|
and (not TChgPrmModify(t.Changes[i]).Delete))
|
|
then
|
|
Result:=i
|
|
end;
|
|
end;
|
|
|
|
function GetOldGroupIndex(ChgPos: integer): integer;
|
|
var
|
|
InsertNew: TChgPrmInsertNew;
|
|
begin
|
|
Result:=-1;
|
|
if (ChgPos<0) or (ChgPos>=t.Changes.Count) then exit;
|
|
if t.Changes[ChgPos] is TChgPrmModify then begin
|
|
Result:=TChgPrmModify(t.Changes[ChgPos]).FirstInGroup;
|
|
end else if t.Changes[ChgPos] is TChgPrmInsertNew then begin
|
|
InsertNew:=TChgPrmInsertNew(t.Changes[ChgPos]);
|
|
if InsertNew.CopyFromParamIndex<0 then exit;
|
|
Result:=t.OldNodes[InsertNew.CopyFromParamIndex].FirstInGroup;
|
|
end;
|
|
end;
|
|
|
|
function IsGroupedWithNext(ChgPos: integer): boolean;
|
|
var
|
|
Grp1: integer;
|
|
begin
|
|
Result:=false;
|
|
Grp1:=GetOldGroupIndex(ChgPos);
|
|
if Grp1<0 then exit;
|
|
Result:=Grp1=GetOldGroupIndex(ChgPos+1);
|
|
end;
|
|
|
|
function ExtractCode(FromPos, ToPos: integer): string;
|
|
begin
|
|
Result:=copy(Src,FromPos,ToPos-FromPos);
|
|
end;
|
|
|
|
procedure InsertParam(Insertion: TChgPrmInsertNew; ChgPos: integer);
|
|
// Insert a new or moved parameter
|
|
var
|
|
SrcParam: TChgPrmModify;
|
|
StartPos: LongInt;
|
|
EndPos: LongInt;
|
|
begin
|
|
if ReplaceStartPos=0 then begin
|
|
ReplaceStartPos:=InsertPos;
|
|
ReplaceEndPos:=ReplaceStartPos;
|
|
end;
|
|
if (InsertCode<>'') and (not (InsertCode[length(InsertCode)] in [';','(','[']))
|
|
then
|
|
InsertCode:=InsertCode+';';
|
|
if Insertion.Src<>'' then
|
|
// add directly
|
|
InsertCode:=InsertCode+Insertion.Src
|
|
else if Insertion.CopyFromParamIndex>=0 then begin
|
|
// copy an existing parameter (the deletion is done by the replace)
|
|
// Try to copy comments and groups
|
|
// For example:
|
|
// var {%H-}a: char = 3; //about a
|
|
// var a,b,c: word; //comment
|
|
SrcParam:=t.OldNodes[Insertion.CopyFromParamIndex];
|
|
|
|
if IsGroupedWithNext(ChgPos-1) then begin
|
|
// grouped with parameter in front: ..., a...
|
|
InsertCode:=InsertCode+',';
|
|
StartPos:=SrcParam.Name.StartPos;
|
|
if SrcParam.Modifier.StartPos<1 then
|
|
StartPos:=SrcParam.GetFirstPos;
|
|
if IsGroupedWithNext(ChgPos) then begin
|
|
// grouped with parameter in front and behind: ..., a, ...
|
|
EndPos:=SrcParam.Name.EndPos;
|
|
if SrcParam.Typ.StartPos<1 then
|
|
EndPos:=SrcParam.GetLastPos(false);
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,EndPos);
|
|
end else begin
|
|
// last parameter in a group: ..., a: word;
|
|
if ChgPos=t.Changes.Count-1 then begin
|
|
// copy without separator
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(false));
|
|
InsertCode:=InsertCode+ExtractCode(SrcParam.CommentAfterSeparator.StartPos,
|
|
SrcParam.CommentAfterSeparator.EndPos);
|
|
end else begin
|
|
// copy with separator
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(true));
|
|
end;
|
|
end;
|
|
end else begin
|
|
// not grouped in front
|
|
StartPos:=SrcParam.GetFirstPos;
|
|
if IsGroupedWithNext(ChgPos) then begin
|
|
// first parameter in a group: var a,
|
|
// ToDo: copy comment behind name
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.Name.EndPos);
|
|
end else begin
|
|
// not grouped => copy completely
|
|
if ChgPos=t.Changes.Count-1 then begin
|
|
// copy without separator
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(false));
|
|
InsertCode:=InsertCode+ExtractCode(SrcParam.CommentAfterSeparator.StartPos,
|
|
SrcParam.CommentAfterSeparator.EndPos);
|
|
end else begin
|
|
// copy with separator
|
|
InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(true));
|
|
end;
|
|
end;
|
|
end;
|
|
end else begin
|
|
// new parameter (not copied)
|
|
if Insertion.Modifier<>'' then
|
|
InsertCode:=InsertCode+Insertion.Modifier+' ';
|
|
InsertCode:=InsertCode+Insertion.Name;
|
|
if Insertion.Typ<>'' then
|
|
InsertCode:=InsertCode+':'+Insertion.Typ;
|
|
if Insertion.DefaultValue<>'' then
|
|
InsertCode:=InsertCode+'='+Insertion.DefaultValue;
|
|
end;
|
|
end;
|
|
|
|
procedure ChangeParam(aParam: TChgPrmModify; aParamIndex: integer);
|
|
var
|
|
Code: String;
|
|
p: LongInt;
|
|
begin
|
|
if aParamIndex=0 then ;
|
|
if aParam.Delete then begin
|
|
if ReplaceStartPos<1 then begin
|
|
// ToDo: delete the last parameter => delete separator from previous parameter
|
|
// ToDo: delete space in front
|
|
ReplaceStartPos:=aParam.GetFirstPos;
|
|
end;
|
|
// extend the deletion range
|
|
ReplaceEndPos:=aParam.GetLastPos(true);
|
|
// ToDo: delete space behind
|
|
end else begin
|
|
// keep this parameter at this place
|
|
if ReplaceStartPos>0 then begin
|
|
// insert the changes in front
|
|
ReplaceEndPos:=aParam.GetFirstPos;
|
|
if not SourceChanger.Replace(gtNone,gtNone,
|
|
ReplaceStartPos,ReplaceEndPos,InsertCode)
|
|
then exit;
|
|
ReplaceStartPos:=0;
|
|
ReplaceEndPos:=0;
|
|
InsertCode:='';
|
|
end;
|
|
if aParam.ChangeDefaultValue then begin
|
|
// keep modifier, name and type and change default value
|
|
if aParam.DefaultValue.StartPos>0 then begin
|
|
// replace the default value
|
|
Code:=aParam.NewDefaultValue;
|
|
if Code<>'' then Code:='='+Code;
|
|
if not SourceChanger.Replace(gtNone,gtNone,
|
|
aParam.DefaultValue.StartPos,aParam.DefaultValue.EndPos,Code)
|
|
then exit;
|
|
end else if aParam.NewDefaultValue<>'' then begin
|
|
// insert a default value
|
|
Code:=':'+aParam.NewDefaultValue;
|
|
p:=aParam.Typ.EndPos;
|
|
if not SourceChanger.Replace(gtNone,gtNone,p,p,Code)
|
|
then exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:=false;
|
|
t:=Transactions as TChangeParamListTransactions;
|
|
t.CreateChanges;
|
|
|
|
LastChgPos:=GetLastChgPos;
|
|
if LastChgPos<0 then begin
|
|
// delete whole param list
|
|
if (t.BracketOpenPos>0) and (t.BracketClosePos>0) then begin
|
|
if not SourceChanger.Replace(gtNone,gtNone,t.BracketOpenPos,t.BracketClosePos+1,'')
|
|
then
|
|
exit;
|
|
end;
|
|
exit(true);
|
|
end;
|
|
|
|
InsertCode:='';
|
|
InsertPos:=0;
|
|
ReplaceStartPos:=0;
|
|
ReplaceEndPos:=0;
|
|
if t.BracketOpenPos<1 then begin
|
|
// start a new param list
|
|
if t.Node.Desc=ctnProperty then
|
|
InsertCode:='['
|
|
else
|
|
InsertCode:='(';
|
|
InsertPos:=t.BehindNamePos;
|
|
ReplaceStartPos:=InsertPos;
|
|
ReplaceEndPos:=ReplaceStartPos;
|
|
end else begin
|
|
// keep brackets
|
|
InsertPos:=t.BracketOpenPos+1;
|
|
end;
|
|
|
|
for i:=0 to t.Changes.Count-1 do begin
|
|
if t.Changes[i] is TChgPrmInsertNew then
|
|
InsertParam(TChgPrmInsertNew(t.Changes[i]),i)
|
|
else if t.Changes[i] is TChgPrmModify then
|
|
ChangeParam(TChgPrmModify(t.Changes[i]),i);
|
|
end;
|
|
|
|
if t.BracketOpenPos<1 then begin
|
|
// end a new param list
|
|
if t.Node.Desc=ctnProperty then
|
|
InsertCode:=InsertCode+']'
|
|
else
|
|
InsertCode:=InsertCode+')';
|
|
end;
|
|
|
|
if ReplaceStartPos>0 then
|
|
if not SourceChanger.Replace(gtNone,gtNone,ReplaceStartPos,ReplaceEndPos,InsertCode)
|
|
then
|
|
exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TChangeDeclarationTool.ChangeParamListDeclaration(
|
|
ParentNode: TCodeTreeNode; Changes: TObjectList;
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
var
|
|
FoundVarArgs: Boolean;
|
|
FoundDefaultValue: boolean;
|
|
Transactions: TChangeParamListTransactions;
|
|
|
|
procedure CheckInsert(Insertion: TChgPrmInsertNew);
|
|
var
|
|
SrcParam: TChgPrmModify;
|
|
HasDefaultValue: Boolean;
|
|
begin
|
|
// check that '...' (MacPas vararg) is last
|
|
// check that after a parameter with default value all have default values
|
|
if FoundVarArgs then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: ... parameter must be the last');
|
|
if Insertion.CopyFromParamIndex>=0 then begin
|
|
SrcParam:=Transactions.OldNodes[Insertion.CopyFromParamIndex];
|
|
if GetAtom(SrcParam.Name)='...' then
|
|
FoundVarArgs:=true;
|
|
if SrcParam.ChangeDefaultValue then
|
|
HasDefaultValue:=SrcParam.NewDefaultValue<>''
|
|
else
|
|
HasDefaultValue:=SrcParam.DefaultValue.StartPos>0;
|
|
end else begin
|
|
if (Insertion.Name='...') then
|
|
FoundVarArgs:=true;
|
|
HasDefaultValue:=Insertion.DefaultValue<>'';
|
|
end;
|
|
if HasDefaultValue then
|
|
FoundDefaultValue:=true
|
|
else if FoundDefaultValue then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: after a parameter with default value all parameters must have default values');
|
|
end;
|
|
|
|
procedure CheckParam(aParam: TChgPrmModify);
|
|
var
|
|
i: Integer;
|
|
HasDefaultValue: Boolean;
|
|
begin
|
|
if not aParam.Delete then begin
|
|
// check that '...' (MacPas vararg) is last
|
|
if FoundVarArgs then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: ... parameter must be the last');
|
|
if GetAtom(aParam.Name)='...' then
|
|
FoundVarArgs:=true;
|
|
if not aParam.Delete then begin
|
|
if aParam.ChangeDefaultValue then
|
|
HasDefaultValue:=aParam.NewDefaultValue<>''
|
|
else
|
|
HasDefaultValue:=aParam.DefaultValue.StartPos>0;
|
|
if HasDefaultValue then
|
|
FoundDefaultValue:=true
|
|
else if FoundDefaultValue then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: after a parameter with default value all parameters must have default values');
|
|
end;
|
|
end;
|
|
for i:=0 to aParam.InsertBehind.Count-1 do
|
|
CheckInsert(TChgPrmInsertNew(aParam.InsertBehind[i]));
|
|
end;
|
|
|
|
var
|
|
ParamListNode: TCodeTreeNode;
|
|
i: Integer;
|
|
Change: TChangeParamListItem;
|
|
Transaction: TChgPrmModify;
|
|
begin
|
|
Result:=false;
|
|
|
|
// for procs: use ctnProcedureHead as parent
|
|
if ParentNode.Desc=ctnProcedure then
|
|
ParentNode:=ParentNode.FirstChild;
|
|
if (ParentNode.Desc=ctnProcedureHead) and NodeNeedsBuildSubTree(ParentNode) then
|
|
BuildSubTreeForProcHead(ParentNode);
|
|
|
|
ParamListNode:=ParentNode.FirstChild;
|
|
if (ParamListNode<>nil) and (ParamListNode.Desc<>ctnParameterList) then
|
|
ParamListNode:=nil;
|
|
Transactions:=TChangeParamListTransactions.Create(ParamListNode);
|
|
try
|
|
CDTParseParamList(ParentNode,Transactions);
|
|
|
|
for i:=0 to Changes.Count-1 do begin
|
|
Change:=TChangeParamListItem(Changes[i]);
|
|
if (Change.Index<0) or (Change.Index>Transactions.MaxPos) then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' out of bounds');
|
|
case Change.Action of
|
|
cplaInsertNewParam:
|
|
Transactions.Insert(Change.Index,
|
|
TChgPrmInsertNew.Create('',Change.ParamModifier,
|
|
Change.ParamName,Change.ParamType,Change.DefaultValue,-1));
|
|
|
|
cplaDeleteParam:
|
|
begin
|
|
Transaction:=Transactions.OldNodes[Change.Index];
|
|
if Transaction.Delete then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' already deleted');
|
|
Transaction.Delete:=true;
|
|
end;
|
|
|
|
cplaMoveParam:
|
|
begin
|
|
if (Change.OldIndex<0) or (Change.OldIndex>Transactions.MaxPos) then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index out of bounds');
|
|
if Change.OldIndex<>Change.Index then begin
|
|
Transaction:=Transactions.OldNodes[Change.OldIndex];
|
|
if Transaction.Delete then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.OldIndex)+' already deleted');
|
|
Transaction.Delete:=true;
|
|
Transactions.Insert(Change.Index,
|
|
TChgPrmInsertNew.Create('','','','','',Change.OldIndex));
|
|
end;
|
|
end;
|
|
|
|
cplaChangeDefaultValue:
|
|
begin
|
|
Transaction:=Transactions.OldNodes[Change.Index];
|
|
if Transaction.Typ.StartPos<1 then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: can not change the default value, because index '+dbgs(Change.Index)+' has no type');
|
|
if Transaction.Delete then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' already deleted');
|
|
if Transaction.ChangeDefaultValue then
|
|
raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' default value already changed');
|
|
|
|
Transaction.ChangeDefaultValue:=true;
|
|
Transaction.NewDefaultValue:=Change.DefaultValue;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
FoundVarArgs:=false;
|
|
FoundDefaultValue:=false;
|
|
for i:=0 to Transactions.InsertFirst.Count-1 do
|
|
CheckInsert(TChgPrmInsertNew(Transactions.InsertFirst[i]));
|
|
for i:=0 to Transactions.MaxPos-1 do
|
|
CheckParam(Transactions.OldNodes[i]);
|
|
|
|
// apply
|
|
Result:=ApplyParamListTransactions(Transactions,SourceChanger);
|
|
finally
|
|
Transactions.Free;
|
|
end;
|
|
end;
|
|
|
|
function TChangeDeclarationTool.ChangeParamListDeclarationAtPos(CleanPos: integer;
|
|
Changes: TObjectList; SourceChanger: TSourceChangeCache): boolean;
|
|
var
|
|
Node: TCodeTreeNode;
|
|
ProcNode: TCodeTreeNode;
|
|
ProcNode2: TCodeTreeNode;
|
|
begin
|
|
Result:=false;
|
|
Node:=FindDeepestNodeAtPos(CleanPos,true);
|
|
if Node.Desc=ctnProcedureHead then
|
|
Node:=Node.Parent;
|
|
if Node.Desc=ctnProcedure then begin
|
|
// change the parameter list of a procedure
|
|
ProcNode:=Node;
|
|
Result:=ChangeParamListDeclaration(ProcNode,Changes,SourceChanger);
|
|
if not Result then exit;
|
|
ProcNode2:=FindCorrespondingProcNode(ProcNode);
|
|
if ProcNode2<>nil then begin
|
|
Result:=ChangeParamListDeclaration(ProcNode2,Changes,SourceChanger);
|
|
if not Result then exit;
|
|
end;
|
|
end else begin
|
|
debugln(['TChangeDeclarationTool.ChangeParamListDeclaration unsupported node=',Node.DescAsString]);
|
|
exit;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TChangeDeclarationTool.ChangeParamList(Changes: TObjectList;
|
|
var ProcPos: TCodeXYPosition; TreeOfPCodeXYPosition: TAVLTree;
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
var
|
|
CleanPos: integer;
|
|
begin
|
|
Result:=false;
|
|
if (Changes=nil) or (Changes.Count=0) then exit(true);
|
|
if TreeOfPCodeXYPosition=nil then ;
|
|
BuildTree(lsrEnd);
|
|
SourceChanger.MainScanner:=Scanner;
|
|
if (ProcPos.Code<>nil) and (CaretToCleanPos(ProcPos,CleanPos)=0) then begin
|
|
// declaration is in this unit
|
|
ProcPos:=CleanCodeXYPosition;
|
|
if not ChangeParamListDeclarationAtPos(CleanPos,Changes,SourceChanger) then exit;
|
|
end;
|
|
Result:=SourceChanger.Apply;
|
|
end;
|
|
|
|
function TChangeDeclarationTool.AddProcModifier(
|
|
const CursorPos: TCodeXYPosition; aModifier: string;
|
|
SourceChanger: TSourceChangeCache): boolean;
|
|
var
|
|
CleanPos, EndPos, p, AtomStart, InsertFromPos, InsertToPos: integer;
|
|
ProcNode, Node: TCodeTreeNode;
|
|
s, ModifierAtom, InsertTxt: String;
|
|
Beauty: TBeautifyCodeOptions;
|
|
NeedLeftSemicolon, NeedRightSemicolon: Boolean;
|
|
FromGap, ToGap: TGapTyp;
|
|
begin
|
|
Result:=false;
|
|
|
|
aModifier:=Trim(aModifier);
|
|
if aModifier='' then
|
|
RaiseException(20180513104525,'AddProcModifier invalid modifier "'+aModifier+'"');
|
|
if aModifier[length(aModifier)]=';' then begin
|
|
aModifier:=Trim(LeftStr(aModifier,length(aModifier)-1));
|
|
if aModifier='' then
|
|
RaiseException(20180513104659,'AddProcModifier invalid modifier "'+aModifier+'"');
|
|
end;
|
|
|
|
BuildTreeAndGetCleanPos(CursorPos,CleanPos);
|
|
ProcNode:=FindDeepestNodeAtPos(CleanPos,true);
|
|
if ProcNode.Desc<>ctnProcedure then begin
|
|
Node:=ProcNode.GetNodeOfType(ctnProcedureHead);
|
|
if Node=nil then
|
|
RaiseExceptionAtCleanPos(20180513100158,'AddProcModifier expects a procedure header, but found '+ProcNode.DescAsString,CleanPos);
|
|
ProcNode:=Node.Parent;
|
|
if ProcNode.Desc<>ctnProcedure then
|
|
RaiseExceptionAtCleanPos(20180513100346,'AddProcModifier expects a procedure, but found '+ProcNode.DescAsString,CleanPos);
|
|
end;
|
|
BuildSubTreeForProcHead(ProcNode);
|
|
|
|
// get new modifier type
|
|
p:=1;
|
|
s:=ReadNextPascalAtom(aModifier,p,AtomStart,Scanner.NestedComments,true);
|
|
if s='' then
|
|
RaiseExceptionAtCleanPos(20180513101346,'AddProcModifier invalid modifier "'+aModifier+'"',CleanPos);
|
|
ModifierAtom:=shortstring(UpperCaseStr(s));
|
|
|
|
MoveCursorToFirstProcSpecifier(ProcNode);
|
|
// cursor is now at semicolon or at first modifier
|
|
EndPos:=ProcNode.FirstChild.EndPos;
|
|
InsertFromPos:=CurPos.StartPos;
|
|
InsertToPos:=0;
|
|
NeedLeftSemicolon:=true;
|
|
NeedRightSemicolon:=CurPos.Flag<>cafSemicolon;
|
|
{$IFDEF VerboseAddProcModifier}
|
|
debugln(['TChangeDeclarationTool.AddProcModifier ModifierAtom="',ModifierAtom,'" FIRST ATOM ',GetAtom]);
|
|
{$ENDIF}
|
|
while (CurPos.StartPos<EndPos) do begin
|
|
{$IFDEF VerboseAddProcModifier}
|
|
debugln(['TChangeDeclarationTool.AddProcModifier NEXT ATOM ',GetAtom]);
|
|
{$ENDIF}
|
|
if CurPos.Flag=cafSemicolon then begin
|
|
ReadNextAtom;
|
|
end else begin
|
|
if UpAtomIs(ModifierAtom) then begin
|
|
// found
|
|
InsertFromPos:=CurPos.StartPos;
|
|
InsertToPos:=InsertFromPos;
|
|
NeedLeftSemicolon:=false;
|
|
end else begin
|
|
InsertFromPos:=CurPos.EndPos;
|
|
NeedLeftSemicolon:=true;
|
|
end;
|
|
if (CurPos.Flag=cafEdgedBracketOpen) then begin
|
|
ReadTilBracketClose(false);
|
|
ReadNextAtom;
|
|
end else if UpAtomIs('MESSAGE') or UpAtomIs('DISPID') or UpAtomIs('ENUMERATOR')
|
|
or UpAtomIs('DEPRECATED') then begin
|
|
ReadNextAtom;
|
|
ReadConstant(true,false,[]);
|
|
end else if UpAtomIs('IS') then begin
|
|
ReadNextAtom;
|
|
if UpAtomIs('NESTED') then
|
|
ReadNextAtom;
|
|
end else if UpAtomIs('EXTERNAL') or UpAtomIs('WEAKEXTERNAL')
|
|
or UpAtomIs('PUBLIC') then begin
|
|
ReadNextAtom;
|
|
if CurPos.Flag<>cafSemicolon then begin
|
|
if not UpAtomIs('NAME') then
|
|
ReadConstant(true,false,[]);
|
|
if UpAtomIs('NAME') or UpAtomIs('INDEX') then begin
|
|
ReadNextAtom;
|
|
ReadConstant(true,false,[]);
|
|
end;
|
|
if UpAtomIs('DELAYED') then
|
|
ReadNextAtom;
|
|
end;
|
|
end else begin
|
|
ReadNextAtom;
|
|
end;
|
|
if InsertToPos>0 then begin
|
|
InsertToPos:=CurPos.StartPos;
|
|
NeedRightSemicolon:=CurPos.Flag<>cafSemicolon;
|
|
{$IFDEF VerboseAddProcModifier}
|
|
debugln(['TChangeDeclarationTool.AddProcModifier FOUND "',copy(Src,InsertFromPos,InsertToPos-InsertFromPos),'"']);
|
|
{$ENDIF}
|
|
break;
|
|
end else begin
|
|
if CurPos.Flag=cafSemicolon then begin
|
|
InsertFromPos:=CurPos.EndPos;
|
|
NeedLeftSemicolon:=false;
|
|
end else begin
|
|
InsertFromPos:=CurPos.StartPos;
|
|
NeedLeftSemicolon:=true;
|
|
end;
|
|
NeedRightSemicolon:=true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
SourceChanger.MainScanner:=Scanner;
|
|
Beauty:=SourceChanger.BeautifyCodeOptions;
|
|
InsertTxt:=aModifier;
|
|
if NeedLeftSemicolon then begin
|
|
InsertTxt:=';'+InsertTxt;
|
|
FromGap:=gtNone;
|
|
end else
|
|
FromGap:=gtSpace;
|
|
ToGap:=gtNone;
|
|
if NeedRightSemicolon then
|
|
InsertTxt:=InsertTxt+';';
|
|
InsertTxt:=Beauty.BeautifyStatement(InsertTxt,0);
|
|
|
|
if InsertToPos>0 then begin
|
|
// there is already such a modifier
|
|
s:=ExtractCode(InsertFromPos,InsertToPos,[]);
|
|
if CompareTextIgnoringSpace(s,InsertTxt,false)=0 then begin
|
|
debugln(['TChangeDeclarationTool.AddProcModifier EXISTS ALREADY "',s,'"']);
|
|
exit(true);
|
|
end;
|
|
end;
|
|
InsertFromPos:=FindLineEndOrCodeInFrontOfPosition(InsertFromPos,false);
|
|
InsertToPos:=FindLineEndOrCodeAfterPosition(InsertToPos);
|
|
|
|
if InsertToPos=0 then begin
|
|
// append new modifier
|
|
{$IFDEF VerboseAddProcModifier}
|
|
debugln(['TChangeDeclarationTool.AddProcModifier APPEND "',InsertTxt,'"']);
|
|
{$ENDIF}
|
|
if not SourceChanger.Replace(FromGap,ToGap,InsertFromPos,InsertFromPos,InsertTxt) then
|
|
RaiseExceptionAtCleanPos(20180513105500,'AddProcModifier replace failed',InsertFromPos);
|
|
end else begin
|
|
// replace old modifier
|
|
{$IFDEF VerboseAddProcModifier}
|
|
debugln(['TChangeDeclarationTool.AddProcModifier REPLACE "',copy(Src,InsertFromPos,InsertToPos-InsertFromPos),'" with "',InsertTxt,'"']);
|
|
{$ENDIF}
|
|
if not SourceChanger.Replace(FromGap,ToGap,InsertFromPos,InsertToPos,InsertTxt) then
|
|
RaiseExceptionAtCleanPos(20180513105502,'AddProcModifier replace failed',InsertFromPos);
|
|
end;
|
|
|
|
Result:=SourceChanger.Apply;
|
|
end;
|
|
|
|
end.
|
|
|