1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     TChangeDeclarationTool enhances TExtractProcTool.
25     TChangeDeclarationTool provides functions to change/move declarations.
26 }
27 unit ChangeDeclarationTool;
28 
29 {$mode objfpc}{$H+}
30 
31 {off $define VerboseAddProcModifier}
32 
33 interface
34 
35 uses
36   Classes, SysUtils, Laz_AVL_Tree, contnrs,
37   // Codetools
38   CodeAtom, CodeCache, FileProcs, CodeTree, ExtractProcTool, FindDeclarationTool,
39   BasicCodeTools, KeywordFuncLists, LinkScanner, SourceChanger;
40 
41 type
42   TChangeParamListAction = (
43     cplaInsertNewParam,  // insert at Index a new parameter. Use DefaultValue in callers.
44     cplaDeleteParam, // delete parameter at Index. In callers too.
45     cplaMoveParam,  // move parameter at OldIndex to Index
46     cplaChangeDefaultValue // if caller use default change to old value, if caller use new value remove it
47     );
48   TChangeParamListActions = set of TChangeParamListAction;
49 
50   { TChangeParamListItem }
51 
52   TChangeParamListItem = class
53   public
54     Action: TChangeParamListAction;
55     Index: integer;
56     OldIndex: integer;
57     ParamModifier: string;
58     ParamName: string;
59     ParamType: string;
60     DefaultValue: string;
61     constructor CreateInsertNewParam(TheIndex: integer;
62                                      aModifier, aName, aType: string);
63     constructor CreateDeleteParam(TheIndex: integer);
64     constructor CreateMoveParam(TheOldIndex, NewIndex: integer);
65     constructor CreateChangeDefaultValue(TheIndex: integer; aValue: string);
66   end;
67 
68   { TChangeDeclarationTool }
69 
70   TChangeDeclarationTool = class(TExtractCodeTool)
71   private
72     procedure CDTParseParamList(ParentNode: TCodeTreeNode; Transactions: TObject);
ApplyParamListTransactionsnull73     function ApplyParamListTransactions(Transactions: TObject;
74       SourceChanger: TSourceChangeCache): boolean;
ChangeParamListDeclarationnull75     function ChangeParamListDeclaration(ParentNode: TCodeTreeNode;
76       Changes: TObjectList; // list of TChangeParamListItem
77       SourceChanger: TSourceChangeCache): boolean;
ChangeParamListDeclarationAtPosnull78     function ChangeParamListDeclarationAtPos(CleanPos: integer;
79       Changes: TObjectList; // list of TChangeParamListItem
80       SourceChanger: TSourceChangeCache): boolean;
81   public
ChangeParamListnull82     function ChangeParamList(Changes: TObjectList; // list of TChangeParamListItem
83       var ProcPos: TCodeXYPosition; // if it is in this unit the proc declaration is changed and this position is cleared
84       TreeOfPCodeXYPosition: TAVLTree; // positions in this unit are processed and removed from the tree
85       SourceChanger: TSourceChangeCache): boolean;
86 
AddProcModifiernull87     function AddProcModifier(const CursorPos: TCodeXYPosition; aModifier: string;
88       SourceChanger: TSourceChangeCache): boolean;
89   end;
90 
91 implementation
92 
93 type
94 
95   { TChgPrmInsertNew }
96 
97   TChgPrmInsertNew = class
98   public
99     Src: string; // if Src='' then use Modifier+Name+Typ+Value
100     Modifier: string;
101     Name: string;
102     Typ: string;
103     DefaultValue: string;
104     CopyFromParamIndex: integer;
105     constructor Create(aSrc, aModifier, aName, aType, aValue: string;
106                        aCopyFrom: integer);
107   end;
108 
109   { TChgPrmModify }
110 
111   TChgPrmModify = class
112   public
113     Node: TCodeTreeNode; // old param node
114     // example: (var buf; {header} a,b:c; d:word=3 {footer}; ...)
115     HeaderCommentPos: integer;
116     Modifier: TAtomPosition; // optional: const, var out, constref
117     Name: TAtomPosition; // name or '...' (MacPas varargs)
118     Typ: TAtomPosition; // optional
119     DefaultValue: TAtomPosition; // optional
120     HasComments: boolean;
121     FooterCommentEndPos: integer;
122     Separator: integer; // the comma or semicolon to the next parameter
123     CommentAfterSeparator: TAtomPosition;
124     FirstInGroup: integer; // index of first parameter i a group, e.g. a,b:c
125     LastInGroup: integer;
126 
127     Delete: boolean;
128     ChangeDefaultValue: boolean;
129     NewDefaultValue: string;
130     InsertBehind: TObjectList;// list of TChgPrmInsertNew
131     constructor Create;
132     destructor Destroy; override;
GetFirstPosnull133     function GetFirstPos: integer;
GetLastPosnull134     function GetLastPos(WithSeparator: boolean): integer;
135   end;
136 
137   { TChangeParamListTransactions }
138 
139   TChangeParamListTransactions = class
140   public
141     Node: TCodeTreeNode; // ctnParameterList
142     OldNodes: array of TChgPrmModify; // one for each old param node
143     InsertFirst: TObjectList;// list of TChgPrmInsertNew
144     Changes: TObjectList;
145     BehindNamePos: integer;
146     BracketOpenPos: integer;
147     BracketClosePos: integer;
148     constructor Create(ParamList: TCodeTreeNode);
149     destructor Destroy; override;
MaxPosnull150     function MaxPos: integer;
151     procedure Insert(Index: integer; Insertion: TChgPrmInsertNew);
152     procedure CreateChanges;
153   end;
154 
155 { TChangeParamTransactionInsert }
156 
157 constructor TChgPrmInsertNew.Create(aSrc, aModifier, aName, aType,
158   aValue: string; aCopyFrom: integer);
159 begin
160   Src:=aSrc;
161   Modifier:=aModifier;
162   Name:=aName;
163   Typ:=aType;
164   DefaultValue:=aValue;
165   CopyFromParamIndex:=aCopyFrom;
166 end;
167 
168 constructor TChgPrmModify.Create;
169 begin
170   InsertBehind:=TObjectList.create(true);
171 end;
172 
173 destructor TChgPrmModify.Destroy;
174 begin
175   InsertBehind.Free;
176   inherited Destroy;
177 end;
178 
TChgPrmModify.GetFirstPosnull179 function TChgPrmModify.GetFirstPos: integer;
180 begin
181   if HeaderCommentPos>0 then
182     Result:=HeaderCommentPos
183   else if Modifier.StartPos>0 then
184     Result:=Modifier.StartPos
185   else
186     Result:=Name.StartPos;
187 end;
188 
GetLastPosnull189 function TChgPrmModify.GetLastPos(WithSeparator: boolean): integer;
190 begin
191   Result:=0;
192   if WithSeparator then begin
193     if CommentAfterSeparator.EndPos>0 then
194       Result:=CommentAfterSeparator.EndPos
195     else if Separator>0 then
196       Result:=Separator;
197     if Result>0 then exit;
198   end;
199   if FooterCommentEndPos>0 then
200     Result:=FooterCommentEndPos
201   else if DefaultValue.EndPos>0 then
202     Result:=DefaultValue.EndPos
203   else if Typ.EndPos>0 then
204     Result:=Typ.EndPos
205   else
206     Result:=Name.EndPos;
207 end;
208 
209 { TChangeParamListInfos }
210 
MaxPosnull211 function TChangeParamListTransactions.MaxPos: integer;
212 begin
213   Result:=length(OldNodes);
214 end;
215 
216 procedure TChangeParamListTransactions.Insert(Index: integer;
217   Insertion: TChgPrmInsertNew);
218 begin
219   if Index=0 then
220     InsertFirst.Add(Insertion)
221   else
222     OldNodes[Index-1].InsertBehind.Add(Insertion);
223 end;
224 
225 procedure TChangeParamListTransactions.CreateChanges;
226 var
227   i, j: Integer;
228 begin
229   FreeAndNil(Changes);
230   Changes:=TObjectList.create(false);
231   for i:=0 to InsertFirst.Count-1 do
232     Changes.Add(InsertFirst[i]);
233   for i:=0 to length(OldNodes)-1 do begin
234     Changes.Add(OldNodes[i]);
235     for j:=0 to OldNodes[i].InsertBehind.Count-1 do
236       Changes.Add(OldNodes[i].InsertBehind[j]);
237   end;
238 end;
239 
240 constructor TChangeParamListTransactions.Create(ParamList: TCodeTreeNode);
241 var
242   ParamNode: TCodeTreeNode;
243   i: Integer;
244 begin
245   InsertFirst:=TObjectList.create(true);
246   Node:=ParamList;
247   if Node<>nil then begin
248     SetLength(OldNodes,Node.ChildCount);
249     ParamNode:=Node.FirstChild;
250     i:=0;
251     while ParamNode<>nil do begin
252       OldNodes[i]:=TChgPrmModify.Create;
253       OldNodes[i].Node:=ParamNode;
254       ParamNode:=ParamNode.NextBrother;
255     end;
256   end;
257 end;
258 
259 destructor TChangeParamListTransactions.Destroy;
260 var
261   i: Integer;
262 begin
263   FreeAndNil(Changes);
264   for i:=0 to length(OldNodes)-1 do
265     FreeAndNil(OldNodes[i]);
266   SetLength(OldNodes,0);
267   FreeAndNil(InsertFirst);
268   inherited Destroy;
269 end;
270 
271 { TChangeParamListItem }
272 
273 constructor TChangeParamListItem.CreateInsertNewParam(TheIndex: integer;
274   aModifier, aName, aType: string);
275 begin
276   Action:=cplaInsertNewParam;
277   Index:=TheIndex;
278   ParamModifier:=aModifier;
279   ParamName:=aName;
280   ParamType:=aType;
281 end;
282 
283 constructor TChangeParamListItem.CreateDeleteParam(TheIndex: integer);
284 begin
285   Action:=cplaDeleteParam;
286   Index:=TheIndex;
287 end;
288 
289 constructor TChangeParamListItem.CreateMoveParam(TheOldIndex, NewIndex: integer);
290 begin
291   Action:=cplaMoveParam;
292   Index:=NewIndex;
293   OldIndex:=TheOldIndex;
294 end;
295 
296 constructor TChangeParamListItem.CreateChangeDefaultValue(TheIndex: integer;
297   aValue: string);
298 begin
299   Action:=cplaChangeDefaultValue;
300   Index:=TheIndex;
301   DefaultValue:=aValue;
302 end;
303 
304 { TChangeDeclarationTool }
305 
306 procedure TChangeDeclarationTool.CDTParseParamList(ParentNode: TCodeTreeNode;
307   Transactions: TObject);
308 var
309   t: TChangeParamListTransactions;
310   ParamIndex: Integer;
311   CurParam: TChgPrmModify;
312   FirstInGroup: integer;
313   i: LongInt;
314   CloseBracket: Char;
315   StartPos: LongInt;
316   EndPos: Integer;
317   p: PChar;
318 
319   procedure ReadPrefixModifier;
320   begin
321     // read parameter prefix modifier
322     if UpAtomIs('VAR') or UpAtomIs('CONST') or UpAtomIs('CONSTREF')
323     or (UpAtomIs('OUT') and (cmsOut in Scanner.CompilerModeSwitches))
324     then begin
325       CurParam.Modifier:=CurPos;
326       ReadNextAtom;
327     end;
328   end;
329 
330 begin
331   t:=Transactions as TChangeParamListTransactions;
332   // parse param list
333   if ParentNode.Desc=ctnProcedureHead then
334     MoveCursorBehindProcName(ParentNode)
335   else if ParentNode.Desc=ctnProperty then
336     MoveCursorBehindPropName(ParentNode)
337   else
338     raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration kind not supported: '+ParentNode.DescAsString);
339   t.BehindNamePos:=LastAtoms.GetPriorAtom.EndPos;
340   // read bracket
341   if CurPos.Flag=cafRoundBracketOpen then
342     CloseBracket:=')'
343   else if CurPos.Flag=cafEdgedBracketOpen then
344     CloseBracket:=']'
345   else
346     exit; // no param list
347 
348   t.BracketOpenPos:=CurPos.StartPos;
349   ParamIndex:=0;
350   ReadNextAtom;
351   repeat
352     CurParam:=t.OldNodes[ParamIndex];
353     FirstInGroup:=-1;
354     if AtomIs('...') then begin
355       // MacPas '...' VarArgs parameter
356       CurParam.Name:=CurPos;
357       ReadNextAtom;
358       // parse end of parameter list
359       if (CurPos.StartPos>SrcLen)
360       or (Src[CurPos.StartPos]<>CloseBracket) then
361         RaiseCharExpectedButAtomFound(20170421201949,CloseBracket);
362       break;
363     end else begin
364       ReadPrefixModifier;
365       // read parameter name(s)
366       repeat
367         AtomIsIdentifierE;
368         CurParam.Name:=CurPos;
369         ReadNextAtom;
370         if CurPos.Flag<>cafComma then
371           break;
372         CurParam.Separator:=CurPos.StartPos;
373         // A group. Example: b,c:char;
374         if FirstInGroup<0 then FirstInGroup:=ParamIndex;
375         inc(ParamIndex);
376         CurParam:=t.OldNodes[ParamIndex];
377         ReadNextAtom;
378       until false;
379       if FirstInGroup>=0 then begin
380         for i:=FirstInGroup to ParamIndex do begin
381           t.OldNodes[i].FirstInGroup:=FirstInGroup;
382           t.OldNodes[i].LastInGroup:=ParamIndex;
383         end;
384       end;
385       // read parameter type
386       if CurPos.Flag=cafColon then begin
387         ReadNextAtom;
388         CurParam.Typ:=CurPos;
389         if not ReadParamType(true,false,[]) then exit;
390         CurParam.Typ.EndPos:=LastAtoms.GetPriorAtom.EndPos;
391         if CurPos.Flag=cafEqual then begin
392           // read default value
393           ReadNextAtom;
394           CurParam.DefaultValue:=CurPos;
395           ReadConstant(true,false,[]);
396           CurParam.DefaultValue.EndPos:=LastAtoms.GetPriorAtom.EndPos;
397         end;
398       end;
399       // close bracket or semicolon
400       if CurPos.Flag in [cafRoundBracketClose,cafEdgedBracketClose] then begin
401         t.BracketClosePos:=CurPos.StartPos;
402         break;
403       end;
404       if CurPos.Flag<>cafSemicolon then
405         RaiseCharExpectedButAtomFound(20170421201951,CloseBracket);
406       CurParam.Separator:=CurPos.StartPos;
407       inc(ParamIndex);
408     end;
409   until false;
410 
411   // check for each parameter if it has comments
412   for i:=0 to t.MaxPos-1 do begin
413     CurParam:=t.OldNodes[i];
414 
415     // check if the param has a comment inside
416     StartPos:=CurParam.GetFirstPos;
417     EndPos:=CurParam.GetLastPos(false);
418     CurParam.HasComments:=FindNextComment(Src,StartPos,EndPos-1)>=EndPos;
419 
420     // check if the param has a comment in front belonging to the param
421     if i=0 then
422       StartPos:=t.BracketOpenPos+1
423     else
424       StartPos:=t.OldNodes[i-1].GetLastPos(true);
425     EndPos:=CurParam.GetFirstPos;
426     while (StartPos<EndPos) and IsSpaceChar[Src[StartPos]] do inc(StartPos);
427     if StartPos<EndPos then begin
428       // there is a comment in front
429       CurParam.HeaderCommentPos:=StartPos;
430       CurParam.HasComments:=true;
431     end;
432 
433     // check if the param has a comment behind, but in front of the next separator
434     StartPos:=CurParam.GetLastPos(false);
435     if CurParam.Separator>0 then
436       EndPos:=CurParam.Separator
437     else
438       EndPos:=t.BracketClosePos;
439     while (StartPos<EndPos) and IsSpaceChar[Src[EndPos-1]] do dec(EndPos);
440     if StartPos<EndPos then begin
441       // there is a comment behind param and in front of the next separator
442       CurParam.FooterCommentEndPos:=EndPos;
443       CurParam.HasComments:=true;
444     end;
445 
446     // check if the param has a comment behind the next separator
447     if CurParam.Separator>0 then begin
448       StartPos:=CurParam.Separator;
449       p:=@Src[StartPos];
450       while p^ in [' ',#9] do inc(p);
451       if (p^='{') or ((p^='(') and (p[1]='*')) or ((p^='/') and (p[1]='/')) then
452       begin
453         // there is a comment after the separator and it belongs to this param
454         StartPos:=p-PChar(Src)+1;
455         CurParam.CommentAfterSeparator.StartPos:=StartPos;
456         EndPos:=FindCommentEnd(Src,StartPos,Scanner.NestedComments);
457         CurParam.CommentAfterSeparator.EndPos:=EndPos;
458       end;
459     end;
460 
461   end;
462 end;
463 
ApplyParamListTransactionsnull464 function TChangeDeclarationTool.ApplyParamListTransactions(
465   Transactions: TObject; SourceChanger: TSourceChangeCache): boolean;
466 var
467   t: TChangeParamListTransactions;
468   InsertCode: String;
469   InsertPos: Integer; // behind the last kept parameter
470   ReplaceStartPos: Integer;
471   ReplaceEndPos: Integer;
472   LastChgPos: integer;
473 
GetLastChgPosnull474   function GetLastChgPos: integer;
475   var
476     i: Integer;
477   begin
478     Result:=-1;
479     for i:=0 to t.Changes.Count-1 do begin
480       if (t.Changes[i] is TChgPrmInsertNew)
481       or ((t.Changes[i] is TChgPrmModify)
482           and (not TChgPrmModify(t.Changes[i]).Delete))
483       then
484         Result:=i
485     end;
486   end;
487 
GetOldGroupIndexnull488   function GetOldGroupIndex(ChgPos: integer): integer;
489   var
490     InsertNew: TChgPrmInsertNew;
491   begin
492     Result:=-1;
493     if (ChgPos<0) or (ChgPos>=t.Changes.Count) then exit;
494     if t.Changes[ChgPos] is TChgPrmModify then begin
495       Result:=TChgPrmModify(t.Changes[ChgPos]).FirstInGroup;
496     end else if t.Changes[ChgPos] is TChgPrmInsertNew then begin
497       InsertNew:=TChgPrmInsertNew(t.Changes[ChgPos]);
498       if InsertNew.CopyFromParamIndex<0 then exit;
499       Result:=t.OldNodes[InsertNew.CopyFromParamIndex].FirstInGroup;
500     end;
501   end;
502 
IsGroupedWithNextnull503   function IsGroupedWithNext(ChgPos: integer): boolean;
504   var
505     Grp1: integer;
506   begin
507     Result:=false;
508     Grp1:=GetOldGroupIndex(ChgPos);
509     if Grp1<0 then exit;
510     Result:=Grp1=GetOldGroupIndex(ChgPos+1);
511   end;
512 
ExtractCodenull513   function ExtractCode(FromPos, ToPos: integer): string;
514   begin
515     Result:=copy(Src,FromPos,ToPos-FromPos);
516   end;
517 
518   procedure InsertParam(Insertion: TChgPrmInsertNew; ChgPos: integer);
519   // Insert a new or moved parameter
520   var
521     SrcParam: TChgPrmModify;
522     StartPos: LongInt;
523     EndPos: LongInt;
524   begin
525     if ReplaceStartPos=0 then begin
526       ReplaceStartPos:=InsertPos;
527       ReplaceEndPos:=ReplaceStartPos;
528     end;
529     if (InsertCode<>'') and (not (InsertCode[length(InsertCode)] in [';','(','[']))
530     then
531       InsertCode:=InsertCode+';';
532     if Insertion.Src<>'' then
533       // add directly
534       InsertCode:=InsertCode+Insertion.Src
535     else if Insertion.CopyFromParamIndex>=0 then begin
536       // copy an existing parameter (the deletion is done by the replace)
537       //  Try to copy comments and groups
538       //  For example:
539       //    var {%H-}a: char = 3; //about a
540       //    var a,b,c: word; //comment
541       SrcParam:=t.OldNodes[Insertion.CopyFromParamIndex];
542 
543       if IsGroupedWithNext(ChgPos-1) then begin
544         // grouped with parameter in front: ..., a...
545         InsertCode:=InsertCode+',';
546         StartPos:=SrcParam.Name.StartPos;
547         if SrcParam.Modifier.StartPos<1 then
548           StartPos:=SrcParam.GetFirstPos;
549         if IsGroupedWithNext(ChgPos) then begin
550           // grouped with parameter in front and behind: ..., a, ...
551           EndPos:=SrcParam.Name.EndPos;
552           if SrcParam.Typ.StartPos<1 then
553             EndPos:=SrcParam.GetLastPos(false);
554           InsertCode:=InsertCode+ExtractCode(StartPos,EndPos);
555         end else begin
556           // last parameter in a group: ..., a: word;
557           if ChgPos=t.Changes.Count-1 then begin
558             // copy without separator
559             InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(false));
560             InsertCode:=InsertCode+ExtractCode(SrcParam.CommentAfterSeparator.StartPos,
561                SrcParam.CommentAfterSeparator.EndPos);
562           end else begin
563             // copy with separator
564             InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(true));
565           end;
566         end;
567       end else begin
568         // not grouped in front
569         StartPos:=SrcParam.GetFirstPos;
570         if IsGroupedWithNext(ChgPos) then begin
571           // first parameter in a group: var a,
572           // ToDo: copy comment behind name
573           InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.Name.EndPos);
574         end else begin
575           // not grouped => copy completely
576           if ChgPos=t.Changes.Count-1 then begin
577             // copy without separator
578             InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(false));
579             InsertCode:=InsertCode+ExtractCode(SrcParam.CommentAfterSeparator.StartPos,
580                SrcParam.CommentAfterSeparator.EndPos);
581           end else begin
582             // copy with separator
583             InsertCode:=InsertCode+ExtractCode(StartPos,SrcParam.GetLastPos(true));
584           end;
585         end;
586       end;
587     end else begin
588       // new parameter (not copied)
589       if Insertion.Modifier<>'' then
590         InsertCode:=InsertCode+Insertion.Modifier+' ';
591       InsertCode:=InsertCode+Insertion.Name;
592       if Insertion.Typ<>'' then
593         InsertCode:=InsertCode+':'+Insertion.Typ;
594       if Insertion.DefaultValue<>'' then
595         InsertCode:=InsertCode+'='+Insertion.DefaultValue;
596     end;
597   end;
598 
599   procedure ChangeParam(aParam: TChgPrmModify; aParamIndex: integer);
600   var
601     Code: String;
602     p: LongInt;
603   begin
604     if aParamIndex=0 then ;
605     if aParam.Delete then begin
606       if ReplaceStartPos<1 then begin
607         // ToDo: delete the last parameter => delete separator from previous parameter
608         // ToDo: delete space in front
609         ReplaceStartPos:=aParam.GetFirstPos;
610       end;
611       // extend the deletion range
612       ReplaceEndPos:=aParam.GetLastPos(true);
613       // ToDo: delete space behind
614     end else begin
615       // keep this parameter at this place
616       if ReplaceStartPos>0 then begin
617         // insert the changes in front
618         ReplaceEndPos:=aParam.GetFirstPos;
619         if not SourceChanger.Replace(gtNone,gtNone,
620           ReplaceStartPos,ReplaceEndPos,InsertCode)
621         then exit;
622         ReplaceStartPos:=0;
623         ReplaceEndPos:=0;
624         InsertCode:='';
625       end;
626       if aParam.ChangeDefaultValue then begin
627         // keep modifier, name and type and change default value
628         if aParam.DefaultValue.StartPos>0 then begin
629           // replace the default value
630           Code:=aParam.NewDefaultValue;
631           if Code<>'' then Code:='='+Code;
632           if not SourceChanger.Replace(gtNone,gtNone,
633             aParam.DefaultValue.StartPos,aParam.DefaultValue.EndPos,Code)
634           then exit;
635         end else if aParam.NewDefaultValue<>'' then begin
636           // insert a default value
637           Code:=':'+aParam.NewDefaultValue;
638           p:=aParam.Typ.EndPos;
639           if not SourceChanger.Replace(gtNone,gtNone,p,p,Code)
640           then exit;
641         end;
642       end;
643     end;
644   end;
645 
646 var
647   i: Integer;
648 begin
649   Result:=false;
650   t:=Transactions as TChangeParamListTransactions;
651   t.CreateChanges;
652 
653   LastChgPos:=GetLastChgPos;
654   if LastChgPos<0 then begin
655     // delete whole param list
656     if (t.BracketOpenPos>0) and (t.BracketClosePos>0) then begin
657       if not SourceChanger.Replace(gtNone,gtNone,t.BracketOpenPos,t.BracketClosePos+1,'')
658       then
659         exit;
660     end;
661     exit(true);
662   end;
663 
664   InsertCode:='';
665   InsertPos:=0;
666   ReplaceStartPos:=0;
667   ReplaceEndPos:=0;
668   if t.BracketOpenPos<1 then begin
669     // start a new param list
670     if t.Node.Desc=ctnProperty then
671       InsertCode:='['
672     else
673       InsertCode:='(';
674     InsertPos:=t.BehindNamePos;
675     ReplaceStartPos:=InsertPos;
676     ReplaceEndPos:=ReplaceStartPos;
677   end else begin
678     // keep brackets
679     InsertPos:=t.BracketOpenPos+1;
680   end;
681 
682   for i:=0 to t.Changes.Count-1 do begin
683     if t.Changes[i] is TChgPrmInsertNew then
684       InsertParam(TChgPrmInsertNew(t.Changes[i]),i)
685     else if t.Changes[i] is TChgPrmModify then
686       ChangeParam(TChgPrmModify(t.Changes[i]),i);
687   end;
688 
689   if t.BracketOpenPos<1 then begin
690     // end a new param list
691     if t.Node.Desc=ctnProperty then
692       InsertCode:=InsertCode+']'
693     else
694       InsertCode:=InsertCode+')';
695   end;
696 
697   if ReplaceStartPos>0 then
698     if not SourceChanger.Replace(gtNone,gtNone,ReplaceStartPos,ReplaceEndPos,InsertCode)
699     then
700       exit;
701   Result:=true;
702 end;
703 
TChangeDeclarationTool.ChangeParamListDeclarationnull704 function TChangeDeclarationTool.ChangeParamListDeclaration(
705   ParentNode: TCodeTreeNode; Changes: TObjectList;
706   SourceChanger: TSourceChangeCache): boolean;
707 var
708   FoundVarArgs: Boolean;
709   FoundDefaultValue: boolean;
710   Transactions: TChangeParamListTransactions;
711 
712   procedure CheckInsert(Insertion: TChgPrmInsertNew);
713   var
714     SrcParam: TChgPrmModify;
715     HasDefaultValue: Boolean;
716   begin
717     // check that '...' (MacPas vararg) is last
718     // check that after a parameter with default value all have default values
719     if FoundVarArgs then
720       raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: ... parameter must be the last');
721     if Insertion.CopyFromParamIndex>=0 then begin
722       SrcParam:=Transactions.OldNodes[Insertion.CopyFromParamIndex];
723       if GetAtom(SrcParam.Name)='...' then
724         FoundVarArgs:=true;
725       if SrcParam.ChangeDefaultValue then
726         HasDefaultValue:=SrcParam.NewDefaultValue<>''
727       else
728         HasDefaultValue:=SrcParam.DefaultValue.StartPos>0;
729     end else begin
730       if (Insertion.Name='...') then
731         FoundVarArgs:=true;
732       HasDefaultValue:=Insertion.DefaultValue<>'';
733     end;
734     if HasDefaultValue then
735       FoundDefaultValue:=true
736     else if FoundDefaultValue then
737       raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: after a parameter with default value all parameters must have default values');
738   end;
739 
740   procedure CheckParam(aParam: TChgPrmModify);
741   var
742     i: Integer;
743     HasDefaultValue: Boolean;
744   begin
745     if not aParam.Delete then begin
746       // check that '...' (MacPas vararg) is last
747       if FoundVarArgs then
748         raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: ... parameter must be the last');
749       if GetAtom(aParam.Name)='...' then
750         FoundVarArgs:=true;
751       if not aParam.Delete then begin
752         if aParam.ChangeDefaultValue then
753           HasDefaultValue:=aParam.NewDefaultValue<>''
754         else
755           HasDefaultValue:=aParam.DefaultValue.StartPos>0;
756         if HasDefaultValue then
757           FoundDefaultValue:=true
758         else if FoundDefaultValue then
759           raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: after a parameter with default value all parameters must have default values');
760       end;
761     end;
762     for i:=0 to aParam.InsertBehind.Count-1 do
763       CheckInsert(TChgPrmInsertNew(aParam.InsertBehind[i]));
764   end;
765 
766 var
767   ParamListNode: TCodeTreeNode;
768   i: Integer;
769   Change: TChangeParamListItem;
770   Transaction: TChgPrmModify;
771 begin
772   Result:=false;
773 
774   // for procs: use ctnProcedureHead as parent
775   if ParentNode.Desc=ctnProcedure then
776     ParentNode:=ParentNode.FirstChild;
777   if (ParentNode.Desc=ctnProcedureHead) and NodeNeedsBuildSubTree(ParentNode) then
778     BuildSubTreeForProcHead(ParentNode);
779 
780   ParamListNode:=ParentNode.FirstChild;
781   if (ParamListNode<>nil) and (ParamListNode.Desc<>ctnParameterList) then
782     ParamListNode:=nil;
783   Transactions:=TChangeParamListTransactions.Create(ParamListNode);
784   try
785     CDTParseParamList(ParentNode,Transactions);
786 
787     for i:=0 to Changes.Count-1 do begin
788       Change:=TChangeParamListItem(Changes[i]);
789       if (Change.Index<0) or (Change.Index>Transactions.MaxPos) then
790         raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' out of bounds');
791       case Change.Action of
792       cplaInsertNewParam:
793         Transactions.Insert(Change.Index,
794           TChgPrmInsertNew.Create('',Change.ParamModifier,
795                      Change.ParamName,Change.ParamType,Change.DefaultValue,-1));
796 
797       cplaDeleteParam:
798         begin
799           Transaction:=Transactions.OldNodes[Change.Index];
800           if Transaction.Delete then
801             raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' already deleted');
802           Transaction.Delete:=true;
803         end;
804 
805       cplaMoveParam:
806         begin
807           if (Change.OldIndex<0) or (Change.OldIndex>Transactions.MaxPos) then
808             raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index out of bounds');
809           if Change.OldIndex<>Change.Index then begin
810             Transaction:=Transactions.OldNodes[Change.OldIndex];
811             if Transaction.Delete then
812               raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.OldIndex)+' already deleted');
813             Transaction.Delete:=true;
814             Transactions.Insert(Change.Index,
815               TChgPrmInsertNew.Create('','','','','',Change.OldIndex));
816           end;
817         end;
818 
819       cplaChangeDefaultValue:
820         begin
821           Transaction:=Transactions.OldNodes[Change.Index];
822           if Transaction.Typ.StartPos<1 then
823             raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: can not change the default value, because index '+dbgs(Change.Index)+' has no type');
824           if Transaction.Delete then
825             raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' already deleted');
826           if Transaction.ChangeDefaultValue then
827             raise EInvalidOperation.Create('TChangeDeclarationTool.ChangeParamListDeclaration: index '+dbgs(Change.Index)+' default value already changed');
828 
829           Transaction.ChangeDefaultValue:=true;
830           Transaction.NewDefaultValue:=Change.DefaultValue;
831         end;
832 
833       end;
834     end;
835 
836     FoundVarArgs:=false;
837     FoundDefaultValue:=false;
838     for i:=0 to Transactions.InsertFirst.Count-1 do
839       CheckInsert(TChgPrmInsertNew(Transactions.InsertFirst[i]));
840     for i:=0 to Transactions.MaxPos-1 do
841       CheckParam(Transactions.OldNodes[i]);
842 
843     // apply
844     Result:=ApplyParamListTransactions(Transactions,SourceChanger);
845   finally
846     Transactions.Free;
847   end;
848 end;
849 
ChangeParamListDeclarationAtPosnull850 function TChangeDeclarationTool.ChangeParamListDeclarationAtPos(CleanPos: integer;
851   Changes: TObjectList; SourceChanger: TSourceChangeCache): boolean;
852 var
853   Node: TCodeTreeNode;
854   ProcNode: TCodeTreeNode;
855   ProcNode2: TCodeTreeNode;
856 begin
857   Result:=false;
858   Node:=FindDeepestNodeAtPos(CleanPos,true);
859   if Node.Desc=ctnProcedureHead then
860     Node:=Node.Parent;
861   if Node.Desc=ctnProcedure then begin
862     // change the parameter list of a procedure
863     ProcNode:=Node;
864     Result:=ChangeParamListDeclaration(ProcNode,Changes,SourceChanger);
865     if not Result then exit;
866     ProcNode2:=FindCorrespondingProcNode(ProcNode);
867     if ProcNode2<>nil then begin
868       Result:=ChangeParamListDeclaration(ProcNode2,Changes,SourceChanger);
869       if not Result then exit;
870     end;
871   end else begin
872     debugln(['TChangeDeclarationTool.ChangeParamListDeclaration unsupported node=',Node.DescAsString]);
873     exit;
874   end;
875   Result:=true;
876 end;
877 
TChangeDeclarationTool.ChangeParamListnull878 function TChangeDeclarationTool.ChangeParamList(Changes: TObjectList;
879   var ProcPos: TCodeXYPosition; TreeOfPCodeXYPosition: TAVLTree;
880   SourceChanger: TSourceChangeCache): boolean;
881 var
882   CleanPos: integer;
883 begin
884   Result:=false;
885   if (Changes=nil) or (Changes.Count=0) then exit(true);
886   if TreeOfPCodeXYPosition=nil then ;
887   BuildTree(lsrEnd);
888   SourceChanger.MainScanner:=Scanner;
889   if (ProcPos.Code<>nil) and (CaretToCleanPos(ProcPos,CleanPos)=0) then begin
890     // declaration is in this unit
891     ProcPos:=CleanCodeXYPosition;
892     if not ChangeParamListDeclarationAtPos(CleanPos,Changes,SourceChanger) then exit;
893   end;
894   Result:=SourceChanger.Apply;
895 end;
896 
AddProcModifiernull897 function TChangeDeclarationTool.AddProcModifier(
898   const CursorPos: TCodeXYPosition; aModifier: string;
899   SourceChanger: TSourceChangeCache): boolean;
900 var
901   CleanPos, EndPos, p, AtomStart, InsertFromPos, InsertToPos: integer;
902   ProcNode, Node: TCodeTreeNode;
903   s, ModifierAtom, InsertTxt: String;
904   Beauty: TBeautifyCodeOptions;
905   NeedLeftSemicolon, NeedRightSemicolon: Boolean;
906   FromGap, ToGap: TGapTyp;
907 begin
908   Result:=false;
909 
910   aModifier:=Trim(aModifier);
911   if aModifier='' then
912     RaiseException(20180513104525,'AddProcModifier invalid modifier "'+aModifier+'"');
913   if aModifier[length(aModifier)]=';' then begin
914     aModifier:=Trim(LeftStr(aModifier,length(aModifier)-1));
915     if aModifier='' then
916       RaiseException(20180513104659,'AddProcModifier invalid modifier "'+aModifier+'"');
917   end;
918 
919   BuildTreeAndGetCleanPos(CursorPos,CleanPos);
920   ProcNode:=FindDeepestNodeAtPos(CleanPos,true);
921   if ProcNode.Desc<>ctnProcedure then begin
922     Node:=ProcNode.GetNodeOfType(ctnProcedureHead);
923     if Node=nil then
924       RaiseExceptionAtCleanPos(20180513100158,'AddProcModifier expects a procedure header, but found '+ProcNode.DescAsString,CleanPos);
925     ProcNode:=Node.Parent;
926     if ProcNode.Desc<>ctnProcedure then
927       RaiseExceptionAtCleanPos(20180513100346,'AddProcModifier expects a procedure, but found '+ProcNode.DescAsString,CleanPos);
928   end;
929   BuildSubTreeForProcHead(ProcNode);
930 
931   // get new modifier type
932   p:=1;
933   s:=ReadNextPascalAtom(aModifier,p,AtomStart,Scanner.NestedComments,true);
934   if s='' then
935     RaiseExceptionAtCleanPos(20180513101346,'AddProcModifier invalid modifier "'+aModifier+'"',CleanPos);
936   ModifierAtom:=shortstring(UpperCaseStr(s));
937 
938   MoveCursorToFirstProcSpecifier(ProcNode);
939   // cursor is now at semicolon or at first modifier
940   EndPos:=ProcNode.FirstChild.EndPos;
941   InsertFromPos:=CurPos.StartPos;
942   InsertToPos:=0;
943   NeedLeftSemicolon:=true;
944   NeedRightSemicolon:=CurPos.Flag<>cafSemicolon;
945   {$IFDEF VerboseAddProcModifier}
946   debugln(['TChangeDeclarationTool.AddProcModifier ModifierAtom="',ModifierAtom,'" FIRST ATOM ',GetAtom]);
947   {$ENDIF}
948   while (CurPos.StartPos<EndPos) do begin
949     {$IFDEF VerboseAddProcModifier}
950     debugln(['TChangeDeclarationTool.AddProcModifier NEXT ATOM ',GetAtom]);
951     {$ENDIF}
952     if CurPos.Flag=cafSemicolon then begin
953       ReadNextAtom;
954     end else begin
955       if UpAtomIs(ModifierAtom) then begin
956         // found
957         InsertFromPos:=CurPos.StartPos;
958         InsertToPos:=InsertFromPos;
959         NeedLeftSemicolon:=false;
960       end else begin
961         InsertFromPos:=CurPos.EndPos;
962         NeedLeftSemicolon:=true;
963       end;
964       if (CurPos.Flag=cafEdgedBracketOpen) then begin
965         ReadTilBracketClose(false);
966         ReadNextAtom;
967       end else if UpAtomIs('MESSAGE') or UpAtomIs('DISPID') or UpAtomIs('ENUMERATOR')
968       or UpAtomIs('DEPRECATED') then begin
969         ReadNextAtom;
970         ReadConstant(true,false,[]);
971       end else if UpAtomIs('IS') then begin
972         ReadNextAtom;
973         if UpAtomIs('NESTED') then
974           ReadNextAtom;
975       end else if UpAtomIs('EXTERNAL') or UpAtomIs('WEAKEXTERNAL')
976       or UpAtomIs('PUBLIC')  then begin
977         ReadNextAtom;
978         if CurPos.Flag<>cafSemicolon then begin
979           if not UpAtomIs('NAME') then
980             ReadConstant(true,false,[]);
981           if UpAtomIs('NAME') or UpAtomIs('INDEX') then begin
982             ReadNextAtom;
983             ReadConstant(true,false,[]);
984           end;
985           if UpAtomIs('DELAYED') then
986             ReadNextAtom;
987         end;
988       end else begin
989         ReadNextAtom;
990       end;
991       if InsertToPos>0 then begin
992         InsertToPos:=CurPos.StartPos;
993         NeedRightSemicolon:=CurPos.Flag<>cafSemicolon;
994         {$IFDEF VerboseAddProcModifier}
995         debugln(['TChangeDeclarationTool.AddProcModifier FOUND "',copy(Src,InsertFromPos,InsertToPos-InsertFromPos),'"']);
996         {$ENDIF}
997         break;
998       end else begin
999         if CurPos.Flag=cafSemicolon then begin
1000           InsertFromPos:=CurPos.EndPos;
1001           NeedLeftSemicolon:=false;
1002         end else begin
1003           InsertFromPos:=CurPos.StartPos;
1004           NeedLeftSemicolon:=true;
1005         end;
1006         NeedRightSemicolon:=true;
1007       end;
1008     end;
1009   end;
1010 
1011   SourceChanger.MainScanner:=Scanner;
1012   Beauty:=SourceChanger.BeautifyCodeOptions;
1013   InsertTxt:=aModifier;
1014   if NeedLeftSemicolon then begin
1015     InsertTxt:=';'+InsertTxt;
1016     FromGap:=gtNone;
1017   end else
1018     FromGap:=gtSpace;
1019   ToGap:=gtNone;
1020   if NeedRightSemicolon then
1021     InsertTxt:=InsertTxt+';';
1022   InsertTxt:=Beauty.BeautifyStatement(InsertTxt,0);
1023 
1024   if InsertToPos>0 then begin
1025     // there is already such a modifier
1026     s:=ExtractCode(InsertFromPos,InsertToPos,[]);
1027     if CompareTextIgnoringSpace(s,InsertTxt,false)=0 then begin
1028       debugln(['TChangeDeclarationTool.AddProcModifier EXISTS ALREADY "',s,'"']);
1029       exit(true);
1030     end;
1031   end;
1032   InsertFromPos:=FindLineEndOrCodeInFrontOfPosition(InsertFromPos,false);
1033   InsertToPos:=FindLineEndOrCodeAfterPosition(InsertToPos);
1034 
1035   if InsertToPos=0 then begin
1036     // append new modifier
1037     {$IFDEF VerboseAddProcModifier}
1038     debugln(['TChangeDeclarationTool.AddProcModifier APPEND "',InsertTxt,'"']);
1039     {$ENDIF}
1040     if not SourceChanger.Replace(FromGap,ToGap,InsertFromPos,InsertFromPos,InsertTxt) then
1041       RaiseExceptionAtCleanPos(20180513105500,'AddProcModifier replace failed',InsertFromPos);
1042   end else begin
1043     // replace old modifier
1044     {$IFDEF VerboseAddProcModifier}
1045     debugln(['TChangeDeclarationTool.AddProcModifier REPLACE "',copy(Src,InsertFromPos,InsertToPos-InsertFromPos),'" with "',InsertTxt,'"']);
1046     {$ENDIF}
1047     if not SourceChanger.Replace(FromGap,ToGap,InsertFromPos,InsertToPos,InsertTxt) then
1048       RaiseExceptionAtCleanPos(20180513105502,'AddProcModifier replace failed',InsertFromPos);
1049   end;
1050 
1051   Result:=SourceChanger.Apply;
1052 end;
1053 
1054 end.
1055 
1056