35
35
Classes, SysUtils, FileProcs, Forms, Controls, DialogProcs, Dialogs,
38
LazarusIDEStrConsts, LazIDEIntf, FormEditor, IDEMsgIntf,
38
LazarusIDEStrConsts, LazIDEIntf, FormEditor, IDEMsgIntf, IDEExternToolIntf,
40
CodeToolManager, StdCodeTools, CodeTree, CodeAtom, AVL_Tree,
40
CodeToolManager, StdCodeTools, CodeTree, CodeAtom,
41
41
FindDeclarationTool, PascalReaderTool, PascalParserTool, LFMTrees,
42
42
ExprEval, KeywordFuncLists, BasicCodeTools, LinkScanner,
43
43
CodeCache, SourceChanger, CustomCodeTool, CodeToolsStructs, EventCodeTool,
54
54
fCodeTool: TCodeTool;
55
55
fCode: TCodeBuffer;
56
56
fSrcCache: TSourceChangeCache;
57
fAskAboutError: Boolean;
57
fAskAboutError: boolean;
58
58
fSettings: TConvertSettings; // Conversion settings.
59
59
procedure InitCodeTool;
60
function HandleCodetoolError: TModalResult;
62
61
constructor Create(ACode: TCodeBuffer);
63
62
destructor Destroy; override;
64
63
procedure ResetMainScanner;
64
function DummyReplacements: Boolean;
66
66
property CodeTool: TCodeTool read fCodeTool;
67
67
property Code: TCodeBuffer read fCode;
68
68
property SrcCache: TSourceChangeCache read fSrcCache;
69
property AskAboutError: Boolean read fAskAboutError write fAskAboutError;
69
property AskAboutError: boolean read fAskAboutError write fAskAboutError;
70
70
property Settings: TConvertSettings read fSettings write fSettings;
95
95
destructor Destroy; override;
96
96
function Convert: TModalResult;
97
97
function FindApptypeConsole: boolean;
98
function RenameUnitIfNeeded: boolean;
98
99
function RenameResourceDirectives: boolean;
99
100
function FixMainClassAncestor(const AClassName: string;
100
101
AReplaceTypes: TStringToStringTree): boolean;
102
property IsConsoleApp: Boolean read fIsConsoleApp write fIsConsoleApp;
103
property IsConsoleApp: boolean read fIsConsoleApp write fIsConsoleApp;
103
104
property HasFormFile: boolean read fHasFormFile write fHasFormFile;
104
property LowerCaseRes: boolean read fLowerCaseRes write fLowerCaseRes;
105
property ResAction: TResAction read fResAction write fResAction;
105
106
property AddUnitEvent: TAddUnitEvent read fAddUnitEvent write fAddUnitEvent;
131
132
if not CodeToolBoss.InitCurCodeTool(fCode) then exit;
133
fCodeTool:=CodeToolBoss.CurCodeTool;
134
fSrcCache:=CodeToolBoss.SourceChangeCache;
138
CodeToolBoss.HandleException(e);
133
fCodeTool:=CodeToolBoss.CurCodeTool;
134
fSrcCache:=CodeToolBoss.SourceChangeCache;
136
fCodeTool.Scanner.IgnoreMissingIncludeFiles:=True;
142
139
procedure TCodeToolLink.ResetMainScanner;
144
141
fSrcCache.MainScanner:=fCodeTool.Scanner;
147
function TCodeToolLink.HandleCodetoolError: TModalResult;
148
// returns mrOk or mrAbort
150
CodetoolsFoundError='The codetools found an error in unit %s:%s%s%s';
144
function TCodeToolLink.DummyReplacements: Boolean;
145
// If Codetools cannot parse the code, do dummy replacement for all reserved words:
146
// '.'+ReservedWord -> '.&'+ReservedWord, needed for OleVariant.
147
// Most Codetools functions cannot be used because the code is invalid,
148
// but TSourceChangeCache.ReplaceEx works.
152
LastWasPoint: Boolean;
154
ErrMsg:=CodeToolBoss.ErrorMessage;
155
LazarusIDE.DoJumpToCodeToolBossError;
156
if fAskAboutError then begin
157
Result:=QuestionDlg(lisCCOErrorCaption,
158
Format(CodetoolsFoundError, [ExtractFileName(fCode.Filename), #13, ErrMsg, #13]),
159
mtWarning, [mrIgnore, lisIgnoreAndContinue, mrAbort], 0);
160
if Result=mrIgnore then Result:=mrOK;
158
ReadRawNextPascalAtom(Src,p,AStart,false);
159
if p>length(Src) then break;
160
// Reserved words are in WordIsKeyWord list in CodeTools.
161
if LastWasPoint and WordIsKeyWord.DoIdentifier(@Src[AStart]) then
163
// '.'+ReservedWord was found
164
if not fSrcCache.ReplaceEx(gtNone,gtNone,1,1,fCode,AStart,AStart,'&') then
167
LastWasPoint:=Src[AStart]='.';
169
// Apply the changes in buffer
170
if not fSrcCache.Apply then
166
175
{ TConvDelphiCodeTool }
172
181
inherited Create;
173
182
fCTLink:=TCodeToolLink.Create(APascalBuffer);
174
183
fCTLink.AskAboutError:=False;
184
fResAction:=raLowerCase;
176
185
fIsConsoleApp:=False;
177
186
fCTLinkCreated:=True;
178
if fCTLink.CodeTool=nil then exit;
187
if Assigned(fCTLink.CodeTool) then
180
188
fCTLink.CodeTool.BuildTree(lsrInitializationStart);
183
CodeToolBoss.HandleException(e);
187
191
constructor TConvDelphiCodeTool.Create(ACTLink: TCodeToolLink);
189
193
inherited Create;
190
194
fCTLink:=ACTLink;
191
fLowerCaseRes:=False;
192
196
fIsConsoleApp:=False;
193
197
fCTLinkCreated:=False;
206
210
function TConvDelphiCodeTool.Convert: TModalResult;
207
// add {$mode delphi} directive
208
// remove {$R *.dfm} or {$R *.xfm} directive
211
// Add {$mode delphi} directive
212
// Remove {$R *.dfm} or {$R *.xfm} directive
209
213
// Change {$R *.RES} to {$R *.res} if needed
210
214
// TODO: fix delphi ambiguouties like incomplete proc implementation headers
212
216
Result:=mrCancel;
213
217
if fCTLink.CodeTool=nil then exit;
218
fCTLink.SrcCache.BeginUpdate;
215
fCTLink.SrcCache.BeginUpdate;
217
// these changes can be applied together without rescan
218
if not AddModeDelphiDirective then exit;
219
if not RenameResourceDirectives then exit;
220
if fCTLink.Settings.FuncReplaceMode=rsEnabled then
221
if not ReplaceFuncCalls(fIsConsoleApp) then exit;
223
fCTLink.SrcCache.EndUpdate;
227
on e: Exception do begin
228
CodeToolBoss.HandleException(e);
229
Result:=fCTLink.HandleCodetoolError;
220
// these changes can be applied together without rescan
221
if not RenameUnitIfNeeded then exit;
222
if not AddModeDelphiDirective then exit;
223
if not RenameResourceDirectives then exit;
224
if fCTLink.Settings.FuncReplaceMode=rsEnabled then
225
if not ReplaceFuncCalls(fIsConsoleApp) then exit;
227
fCTLink.SrcCache.EndUpdate;
234
232
function TConvDelphiCodeTool.FindApptypeConsole: boolean;
250
function TConvDelphiCodeTool.RenameUnitIfNeeded: boolean;
251
// Change the unit name to match the disk name unless the disk name is all lowercase.
253
NamePos: TAtomPosition;
254
DiskNm, UnitNm: String;
257
//BuildTree(lsrSourceName);
258
with fCTLink do begin
259
DiskNm := ExtractFileNameOnly(Code.Filename);
260
if LowerCase(DiskNm)<>DiskNm then begin // Lowercase name is found always.
261
if not CodeTool.GetSourceNamePos(NamePos) then exit;
262
UnitNm:=copy(CodeTool.Src, NamePos.StartPos, NamePos.EndPos-NamePos.StartPos);
263
if DiskNm<>UnitNm then begin
264
SrcCache.MainScanner:=CodeTool.Scanner;
265
SrcCache.Replace(gtNone, gtNone, NamePos.StartPos, NamePos.EndPos, DiskNm);
266
if not SrcCache.Apply then exit;
267
fSettings.AddLogLine(Format('Fixed unit name from %s to %s.',
252
275
function TConvDelphiCodeTool.AddModeDelphiDirective: boolean;
254
277
ModeDirectivePos: integer;
280
303
function TConvDelphiCodeTool.RenameResourceDirectives: boolean;
281
304
// rename {$R *.dfm} directive to {$R *.lfm}, or lowercase it.
282
// lowercase {$R *.RES} to {$R *.res}
305
// lowercase {$R *.RES} to {$R *.res}, or change it to a comment
284
ParamPos, ACleanPos: Integer;
307
ParamPos, CleanPos: Integer;
285
308
Key, LowKey, NewKey: String;
289
312
if fCTLink.CodeTool=nil then exit;
291
314
// find $R directive
292
315
with fCTLink.CodeTool do begin
293
316
if Scanner=nil then exit;
295
ACleanPos:=FindNextCompilerDirectiveWithName(Src, ACleanPos, 'R',
296
Scanner.NestedComments, ParamPos);
297
if (ACleanPos<1) or (ACleanPos>SrcLen) or (ParamPos>SrcLen-6) then break;
318
CleanPos:=FindNextCompilerDirectiveWithName(Src, CleanPos, 'R',
319
Scanner.NestedComments, ParamPos);
320
if (CleanPos<1) or (CleanPos>SrcLen) or (ParamPos>SrcLen-6) then break;
299
if (Src[ACleanPos]='{') and
322
if (Src[CleanPos]='{') and
300
323
(Src[ParamPos]='*') and (Src[ParamPos+1]='.') and (Src[ParamPos+5]='}')
302
325
Key:=copy(Src,ParamPos+2,3);
313
336
// Add IFDEF for .lfm and .dfm allowing Delphi to use .dfm.
314
s:='{$IFNDEF FPC}'+LineEnding+
337
s:='{$IFnDEF FPC}'+LineEnding+
315
338
' {$R *.dfm}'+LineEnding+
316
339
'{$ELSE}'+LineEnding+
317
340
' {$R *.lfm}'+LineEnding+
319
Result:=fCTLink.SrcCache.Replace(gtNone,gtNone,ACleanPos,ParamPos+6,s);
342
if not fCTLink.SrcCache.Replace(gtNone,gtNone,CleanPos,ParamPos+6,s) then exit;
322
345
else // Change .dfm to .lfm.
325
// lowercase {$R *.RES} to {$R *.res}
326
else if (Key='RES') and fLowerCaseRes then
348
// lowercase {$R *.RES} to {$R *.res}, or change it to a comment
349
else if LowKey='res' then begin
354
raDelete: // Make it a comment by adding a dot (.)
355
if not fCTLink.SrcCache.Replace(gtNone,gtNone,CleanPos,CleanPos+1,'{.') then exit;
328
358
// Change a single resource name.
329
if NewKey<>'' then begin
330
if not fCTLink.SrcCache.Replace(gtNone,gtNone,ParamPos+2,ParamPos+5,NewKey) then
360
if not fCTLink.SrcCache.Replace(gtNone,gtNone,ParamPos+2,ParamPos+5,NewKey) then exit;
334
ACleanPos:=FindCommentEnd(Src, ACleanPos, Scanner.NestedComments);
362
CleanPos:=FindCommentEnd(Src, CleanPos, Scanner.NestedComments);
346
374
OldType, NewType: String;
349
if fCTLink.CodeTool=nil then exit;
377
Assert(Assigned(fCTLink.CodeTool));
350
378
with fCTLink.CodeTool do begin
351
if Scanner=nil then exit;
379
Assert(Assigned(Scanner));
352
380
BuildTree(lsrImplementationStart);
353
381
// Find the class name that the main class inherits from.
354
382
ANode:=FindClassNodeInUnit(AClassName,true,false,false,false);
384
412
ReplacementParams: TObjectList; // Replacement parameters.
386
414
function ParseReplacementParams(const aStr: string): integer;
387
// Parse replacement params. They show which original params are copied where.
415
// Parse replacement params which are defined in configuration.
416
// They show which original params are copied where.
388
417
// Returns the first position where comments can be searched from.
390
419
i, xNum, xStart, xLen: Integer;
393
424
while i<Length(aStr) do begin
395
428
if aStr[i]='$' then begin
397
430
Inc(i); // Skip '$'
399
432
Inc(i); // Get the number after '$'
402
raise EDelphiConverterError.Create('"$" should be followed by a number.');
435
raise EDelphiConverterError.Create('"$" should be followed by a number: '+ aStr);
403
436
xNum:=StrToInt(copy(aStr, xStart+1, xLen-1)); // Leave out '$', convert number.
405
438
raise EDelphiConverterError.Create(
406
'Replacement function parameter number should be >= 1.');
439
'Replacement function parameter number should be >= 1: '+ aStr);
407
440
ReplacementParams.Add(TReplacementParam.Create(xNum, xLen, xStart));
411
raise EDelphiConverterError.Create('")" is missing from replacement function.');
443
if HasBracket and (aStr[i]<>')') then
444
raise EDelphiConverterError.Create('")" is missing from replacement function: '+ aStr);
434
467
function GetComment(const aStr: string; aPossibleStartPos: integer): string;
435
// Extract and return a possible comment.
468
// Extract and return a possible comment from replacement function definition.
437
470
CommChBeg, CommBeg, CommEnd, i: Integer; // Start and end of comment.
472
505
// Replace only if the params match somehow, so eg. a variable is not replaced.
473
506
if (FuncInfo.Params.Count>0) or (ReplacementParams.Count=0) then begin
474
507
NewFunc:=InsertParams2Replacement(FuncInfo);
508
// Separate function body
509
NewFunc:=NewFunc+FuncInfo.InclEmptyBrackets+FuncInfo.InclSemiColon;
510
if fCTLink.fSettings.FuncReplaceComment then
511
NewFunc:=NewFunc+' { *Converted from '+FuncInfo.FuncName+'* }';
475
512
Comment:=GetComment(FuncInfo.ReplFunc, PossibleCommentPos);
476
// Separate function body
477
NewFunc:=Format('%s%s { *Converted from %s* %s }',
478
[NewFunc, FuncInfo.InclSemiColon, FuncInfo.FuncName, Comment]);
513
if Comment<>'' then // Possible comment from the configuration
514
NewFunc:=NewFunc+' { ' +Comment+' }';
479
515
// Old function call with params for IDE message output.
480
516
s:=copy(fCTLink.CodeTool.Src, FuncInfo.StartPos, FuncInfo.EndPos-FuncInfo.StartPos);
481
s:=StringReplace(s, LineEnding, '', [rfReplaceAll]);
517
s:=StringReplace(s, #10, '', [rfReplaceAll]);
518
s:=StringReplace(s, #13, '', [rfReplaceAll]);
482
519
// Now replace it.
483
520
fCTLink.ResetMainScanner;
484
521
if not fCTLink.SrcCache.Replace(gtNone, gtNone,
485
522
FuncInfo.StartPos, FuncInfo.EndPos, NewFunc) then exit;
486
IDEMessagesWindow.AddMsg('Replaced call '+s, '', -1);
487
IDEMessagesWindow.AddMsg(' with '+NewFunc, '', -1);
523
fCTLink.fSettings.AddLogLine('Replaced call '+s+' with '+NewFunc);
488
524
// Add the required unit name to uses section if needed.
489
525
if Assigned(AddUnitEvent) and (FuncInfo.UnitName<>'') then
490
526
AddUnitEvent(FuncInfo.UnitName);
516
552
function TConvDelphiCodeTool.ReplaceFuncCalls(aIsConsoleApp: boolean): boolean;
517
553
// Copied and modified from TFindDeclarationTool.FindReferences.
518
554
// Search for calls to functions / procedures in a list from current unit's
519
// implementation section. Replace found calls with a given replacement.
555
// implementation section code. Replace found calls with a given replacement.
532
568
procedure ReadParams(FuncInfo: TFuncReplacement);
534
ExprStartPos, ExprEndPos, BracketCount: integer;
570
ExprStartPos, ExprEndPos: integer;
571
RoundBrLvl, SquareBrLvl: integer;
572
HasParams, ShouldReadNextAtom: Boolean;
574
FuncInfo.InclEmptyBrackets:='';
536
575
FuncInfo.InclSemiColon:='';
537
576
FuncInfo.StartPos:=xStart;
538
577
with fCTLink.CodeTool do begin
539
578
MoveCursorToCleanPos(xStart);
540
ReadNextAtom; // Read func name.
579
ReadNextAtom; // Read proc name.
541
580
ReadNextAtom; // Read first atom after proc name.
542
if AtomIsChar('(') then begin
581
HasParams:=AtomIsChar('(');
582
if HasParams then begin
543
583
// read parameter list
545
if not AtomIsChar(')') then begin
585
// Don't read twice inside a loop. Atom can be for example '['
586
ShouldReadNextAtom:=False;
587
HasParams:=not AtomIsChar(')');
588
if HasParams then begin
546
589
// read all expressions
548
592
while true do begin
549
593
ExprStartPos:=CurPos.StartPos;
550
594
// read til comma or bracket close
553
if (CurPos.StartPos>SrcLen) or (CurPos.Flag=cafComma) then
555
if CurPos.Flag=cafRoundBracketOpen then
596
if ShouldReadNextAtom then
598
ShouldReadNextAtom:=True;
599
if CurPos.StartPos>SrcLen then
601
if (CurPos.Flag=cafComma) and (RoundBrLvl=0) and (SquareBrLvl=0) then
603
if CurPos.Flag=cafEdgedBracketOpen then
605
else if CurPos.Flag=cafEdgedBracketClose then
607
else if CurPos.Flag=cafRoundBracketOpen then
557
609
else if CurPos.Flag=cafRoundBracketClose then begin
558
if BracketCount=0 then
611
break; // Closing bracket, end of parameters
563
615
ExprEndPos:=CurPos.StartPos;
575
627
raise EDelphiConverterError.Create('Bracket not found');
632
FuncInfo.InclEmptyBrackets:='()';
636
if not HasParams then begin
581
637
FuncInfo.EndPos:=CurPos.StartPos;
582
638
CheckSemiColon(FuncInfo);
608
664
// UTF8 funcs are in LCL which console apps don't have -> don't change.
609
665
and not (aIsConsoleApp and (FuncDefInfo.Category='UTF8Names'))
610
666
// Keep Windows funcs in a Windows application.
611
and (fCTLink.Settings.MultiPlatform or (FuncDefInfo.Category<>'WindowsAPI'))
667
and (fCTLink.Settings.CrossPlatform or (FuncDefInfo.Category<>'WindowsAPI'))
613
669
// Create a new replacement object for params, position and other info.
614
670
FuncCallInfo:=TFuncReplacement.Create(FuncDefInfo);