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