mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-12-08 21:48:28 +01:00
487 lines
15 KiB
ObjectPascal
487 lines
15 KiB
ObjectPascal
unit ConvCodeTool;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
// LCL+FCL
|
|
Classes, SysUtils, FileProcs, Forms, Controls, DialogProcs, Dialogs,
|
|
// IDE
|
|
LazarusIDEStrConsts, LazIDEIntf, FormEditor,
|
|
// codetools
|
|
CodeToolManager, StdCodeTools, CodeTree, CodeAtom,
|
|
FindDeclarationTool, PascalReaderTool, PascalParserTool, LFMTrees,
|
|
CodeBeautifier, ExprEval, KeywordFuncLists, BasicCodeTools, LinkScanner,
|
|
CodeCache, SourceChanger, CustomCodeTool, CodeToolsStructs, EventCodeTool,
|
|
// Converter
|
|
ConvertSettings, ReplaceNamesUnit;
|
|
|
|
type
|
|
|
|
// For future, when .dfm form file can be used for both Delphi and Lazarus.
|
|
{ TFormFileAction = (faUseDfm, faRenameToLfm, faUseBothDfmAndLfm); }
|
|
|
|
{ TConvDelphiCodeTool }
|
|
|
|
TConvDelphiCodeTool = class
|
|
private
|
|
fCodeTool: TEventsCodeTool;
|
|
fCode: TCodeBuffer;
|
|
fSrcCache: TSourceChangeCache;
|
|
fAsk: Boolean;
|
|
fHasFormFile: boolean;
|
|
fUseBothDfmAndLfm: boolean;
|
|
fLowerCaseRes: boolean;
|
|
fDfmDirectiveStart: integer;
|
|
fDfmDirectiveEnd: integer;
|
|
fTarget: TConvertTarget;
|
|
// List of units to remove.
|
|
fUnitsToRemove: TStringList;
|
|
// Units to rename. Map of unit name -> real unit name.
|
|
fUnitsToRename: TStringToStringTree;
|
|
// List of units to be commented.
|
|
fUnitsToComment: TStringList;
|
|
function AddDelphiAndLCLSections: boolean;
|
|
function AddModeDelphiDirective: boolean;
|
|
function RenameResourceDirectives: boolean;
|
|
function CommentOutUnits: boolean;
|
|
function HandleCodetoolError: TModalResult;
|
|
public
|
|
constructor Create(Code: TCodeBuffer);
|
|
destructor Destroy; override;
|
|
function Convert: TModalResult;
|
|
function RemoveUnits: boolean;
|
|
function RenameUnits: boolean;
|
|
function UsesSectionsToUnitnames: TStringList;
|
|
function FixMainClassAncestor(AReplaceTypes: TStringToStringTree): boolean;
|
|
public
|
|
property Ask: Boolean read fAsk write fAsk;
|
|
property UseBothDfmAndLfm: boolean read fUseBothDfmAndLfm write fUseBothDfmAndLfm;
|
|
property HasFormFile: boolean read fHasFormFile write fHasFormFile;
|
|
property LowerCaseRes: boolean read fLowerCaseRes write fLowerCaseRes;
|
|
property Target: TConvertTarget read fTarget write fTarget;
|
|
property UnitsToRemove: TStringList read fUnitsToRemove write fUnitsToRemove;
|
|
property UnitsToRename: TStringToStringTree read fUnitsToRename write fUnitsToRename;
|
|
property UnitsToComment: TStringList read fUnitsToComment write fUnitsToComment;
|
|
end;
|
|
|
|
// Global function
|
|
function FixMainClassAncestor(Code: TCodeBuffer; AReplaceTypes: TStringToStringTree): boolean;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
function FixMainClassAncestor(Code: TCodeBuffer;
|
|
AReplaceTypes: TStringToStringTree): boolean;
|
|
var
|
|
ConvTool: TConvDelphiCodeTool;
|
|
begin
|
|
ConvTool:=TConvDelphiCodeTool.Create(Code);
|
|
try Result:=ConvTool.FixMainClassAncestor(AReplaceTypes);
|
|
finally ConvTool.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TConvDelphiCodeTool }
|
|
|
|
constructor TConvDelphiCodeTool.Create(Code: TCodeBuffer);
|
|
begin
|
|
fCode:=Code;
|
|
// Default values for vars.
|
|
fAsk:=true;
|
|
fLowerCaseRes:=false;
|
|
fUseBothDfmAndLfm:=false;
|
|
fTarget:=ctLazarus;
|
|
fUnitsToRemove:=nil; // These are set from outside.
|
|
fUnitsToComment:=nil;
|
|
fUnitsToRename:=nil;
|
|
// Initialize codetools. (Copied from TCodeToolManager.)
|
|
if not CodeToolBoss.InitCurCodeTool(fCode) then exit;
|
|
try
|
|
fCodeTool:=CodeToolBoss.CurCodeTool;
|
|
fSrcCache:=CodeToolBoss.SourceChangeCache;
|
|
fSrcCache.MainScanner:=fCodeTool.Scanner;
|
|
except
|
|
on e: Exception do
|
|
CodeToolBoss.HandleException(e);
|
|
end;
|
|
end;
|
|
|
|
destructor TConvDelphiCodeTool.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.HandleCodetoolError: TModalResult;
|
|
// returns mrOk or mrAbort
|
|
const
|
|
CodetoolsFoundError='The codetools found an error in unit %s:%s%s%s';
|
|
var
|
|
ErrMsg: String;
|
|
begin
|
|
ErrMsg:=CodeToolBoss.ErrorMessage;
|
|
LazarusIDE.DoJumpToCodeToolBossError;
|
|
if fAsk then begin
|
|
Result:=QuestionDlg(lisCCOErrorCaption,
|
|
Format(CodetoolsFoundError, [ExtractFileName(fCode.Filename), #13, ErrMsg, #13]),
|
|
mtWarning, [mrIgnore, lisIgnoreAndContinue, mrAbort], 0);
|
|
if Result=mrIgnore then Result:=mrOK;
|
|
end else begin
|
|
Result:=mrOK;
|
|
end;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.Convert: TModalResult;
|
|
// add {$mode delphi} directive
|
|
// remove {$R *.dfm} or {$R *.xfm} directive
|
|
// Change {$R *.RES} to {$R *.res} if needed
|
|
// TODO: fix delphi ambiguousities like incomplete proc implementation headers
|
|
begin
|
|
Result:=mrCancel;
|
|
try
|
|
fSrcCache.BeginUpdate;
|
|
try
|
|
// these changes can be applied together without rescan
|
|
if not AddModeDelphiDirective then exit;
|
|
if not RenameResourceDirectives then exit;
|
|
if not fSrcCache.Apply then exit;
|
|
finally
|
|
fSrcCache.EndUpdate;
|
|
end;
|
|
if fTarget=ctLazarus then begin
|
|
// One way conversion -> remove, rename and comment out units.
|
|
if not RemoveUnits then exit;
|
|
if not RenameUnits then exit;
|
|
end;
|
|
if fTarget=ctLazarusAndDelphi then begin
|
|
// Support Delphi. Add IFDEF blocks for units.
|
|
if not AddDelphiAndLCLSections then exit;
|
|
end
|
|
else // ctLazarus or ctLazarusWin -> comment units if needed.
|
|
if not CommentOutUnits then exit;
|
|
if not fCodeTool.FixUsedUnitCase(fSrcCache) then exit;
|
|
if not fSrcCache.Apply then exit;
|
|
Result:=mrOK;
|
|
except
|
|
on e: Exception do begin
|
|
CodeToolBoss.HandleException(e);
|
|
Result:=HandleCodetoolError;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.AddDelphiAndLCLSections: boolean;
|
|
// add, remove and rename units for desired target.
|
|
|
|
procedure RemoveUsesUnit(AUnitName: string);
|
|
var
|
|
UsesNode: TCodeTreeNode;
|
|
begin
|
|
fCodeTool.BuildTree(true);
|
|
UsesNode:=fCodeTool.FindMainUsesSection;
|
|
fCodeTool.MoveCursorToUsesStart(UsesNode);
|
|
fCodeTool.RemoveUnitFromUsesSection(UsesNode, UpperCaseStr(AUnitName), fSrcCache);
|
|
end;
|
|
|
|
var
|
|
DelphiOnlyUnits: TStringList; // Delphi specific units.
|
|
LclOnlyUnits: TStringList; // LCL specific units.
|
|
RenameList: TStringList;
|
|
UsesNode: TCodeTreeNode;
|
|
s, nl: string;
|
|
InsPos, i: Integer;
|
|
begin
|
|
Result:=false;
|
|
DelphiOnlyUnits:=TStringList.Create;
|
|
LclOnlyUnits:=TStringList.Create;
|
|
try
|
|
fCodeTool.BuildTree(true);
|
|
fSrcCache.MainScanner:=fCodeTool.Scanner;
|
|
UsesNode:=fCodeTool.FindMainUsesSection;
|
|
if UsesNode<>nil then begin
|
|
fCodeTool.MoveCursorToUsesStart(UsesNode);
|
|
InsPos:=fCodeTool.CurPos.StartPos;
|
|
// Now don't remove or comment but add to Delphi block instead.
|
|
for i:=0 to fUnitsToRemove.Count-1 do begin
|
|
s:=fUnitsToRemove[i];
|
|
RemoveUsesUnit(s);
|
|
DelphiOnlyUnits.Append(s);
|
|
end;
|
|
for i:=0 to fUnitsToComment.Count-1 do begin
|
|
s:=fUnitsToComment[i];
|
|
RemoveUsesUnit(s);
|
|
DelphiOnlyUnits.Append(s);
|
|
end;
|
|
RenameList:=TStringList.Create;
|
|
try
|
|
// Add replacement units to LCL block.
|
|
fUnitsToRename.GetNames(RenameList);
|
|
for i:=0 to RenameList.Count-1 do begin
|
|
s:=RenameList[i];
|
|
RemoveUsesUnit(s);
|
|
DelphiOnlyUnits.Append(s);
|
|
LclOnlyUnits.Append(fUnitsToRename[s]);
|
|
end;
|
|
finally
|
|
RenameList.Free;
|
|
end;
|
|
if (LclOnlyUnits.Count>0) or (DelphiOnlyUnits.Count>0) then begin
|
|
// Add LCL and Delphi sections for output.
|
|
nl:=fSrcCache.BeautifyCodeOptions.LineEnd;
|
|
s:='{$IFNDEF FPC}'+nl+' ';
|
|
for i:=0 to DelphiOnlyUnits.Count-1 do
|
|
s:=s+DelphiOnlyUnits[i]+', ';
|
|
s:=s+nl+'{$ELSE}'+nl+' ';
|
|
for i:=0 to LclOnlyUnits.Count-1 do
|
|
s:=s+LclOnlyUnits[i]+', ';
|
|
s:=s+nl+'{$ENDIF}';
|
|
// Now add the generated lines.
|
|
if not fSrcCache.Replace(gtEmptyLine,gtNewLine,InsPos,InsPos,s) then exit;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
finally
|
|
LclOnlyUnits.Free;
|
|
DelphiOnlyUnits.Free;
|
|
end;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.AddModeDelphiDirective: boolean;
|
|
var
|
|
ModeDirectivePos: integer;
|
|
InsertPos: Integer;
|
|
s, nl: String;
|
|
begin
|
|
Result:=false;
|
|
with fCodeTool do begin
|
|
BuildTree(true);
|
|
if not FindModeDirective(false,ModeDirectivePos) then begin
|
|
// add {$MODE Delphi} behind source type
|
|
if Tree.Root=nil then exit;
|
|
MoveCursorToNodeStart(Tree.Root);
|
|
ReadNextAtom; // 'unit', 'program', ..
|
|
ReadNextAtom; // name
|
|
ReadNextAtom; // semicolon
|
|
InsertPos:=CurPos.EndPos;
|
|
nl:=fSrcCache.BeautifyCodeOptions.LineEnd;
|
|
if fTarget=ctLazarusAndDelphi then
|
|
s:='{$IFDEF FPC}'+nl+' {$MODE Delphi}'+nl+'{$ENDIF}'
|
|
else
|
|
s:='{$MODE Delphi}';
|
|
fSrcCache.Replace(gtEmptyLine,gtEmptyLine,InsertPos,InsertPos,s);
|
|
end;
|
|
// changing mode requires rescan
|
|
BuildTree(false);
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.RenameResourceDirectives: boolean;
|
|
// rename {$R *.dfm} directive to {$R *.lfm}, or lowercase it.
|
|
// lowercase {$R *.RES} to {$R *.res}
|
|
var
|
|
ParamPos: Integer;
|
|
ACleanPos: Integer;
|
|
Key, LowKey, NewKey: String;
|
|
s, nl: string;
|
|
AlreadyIsLfm: Boolean;
|
|
begin
|
|
Result:=false;
|
|
AlreadyIsLfm:=false;
|
|
fDfmDirectiveStart:=-1;
|
|
fDfmDirectiveEnd:=-1;
|
|
ACleanPos:=1;
|
|
// find $R directive
|
|
with fCodeTool do
|
|
repeat
|
|
ACleanPos:=FindNextCompilerDirectiveWithName(Src,ACleanPos,'R',
|
|
fCodeTool.Scanner.NestedComments,ParamPos);
|
|
if (ACleanPos<1) or (ACleanPos>SrcLen) or (ParamPos>SrcLen-6) then break;
|
|
NewKey:='';
|
|
if (Src[ACleanPos]='{') and
|
|
(Src[ParamPos]='*') and (Src[ParamPos+1]='.') and
|
|
(Src[ParamPos+5]='}')
|
|
then begin
|
|
Key:=copy(Src,ParamPos+2,3);
|
|
LowKey:=LowerCase(Key);
|
|
|
|
// Form file resource rename or lowercase:
|
|
if (LowKey='dfm') or (LowKey='xfm') then begin
|
|
// Lowercase existing key. (Future, when the same dfm file can be used)
|
|
// faUseDfm: if Key<>LowKey then NewKey:=LowKey;
|
|
if fUseBothDfmAndLfm then begin
|
|
// Later IFDEF will be added so that Delphi can still use .dfm.
|
|
fDfmDirectiveStart:=ACleanPos;
|
|
fDfmDirectiveEnd:=ParamPos+6;
|
|
end
|
|
else // Change .dfm to .lfm.
|
|
NewKey:='lfm';
|
|
end
|
|
|
|
// If there already is .lfm, prevent adding IFDEF for .dfm / .lfm.
|
|
else if LowKey='lfm' then begin
|
|
AlreadyIsLfm:=true;
|
|
end
|
|
|
|
// lowercase {$R *.RES} to {$R *.res}
|
|
else if (Key='RES') and fLowerCaseRes then
|
|
NewKey:=LowKey;
|
|
|
|
// Now change code.
|
|
if NewKey<>'' then
|
|
if not fSrcCache.Replace(gtNone,gtNone,ParamPos+2,ParamPos+5,NewKey) then exit;
|
|
end;
|
|
ACleanPos:=FindCommentEnd(Src,ACleanPos,fCodeTool.Scanner.NestedComments);
|
|
until false;
|
|
// if there is already .lfm file, don't add IFDEF for .dfm / .lfm.
|
|
if fUseBothDfmAndLfm and (fDfmDirectiveStart<>-1) and not AlreadyIsLfm then
|
|
begin
|
|
// Add IFDEF for .lfm and .dfm allowing Delphi to use .dfm.
|
|
nl:=fSrcCache.BeautifyCodeOptions.LineEnd;
|
|
s:='{$IFNDEF FPC}'+nl+
|
|
' {$R *.dfm}'+nl+
|
|
'{$ELSE}'+nl+
|
|
' {$R *.lfm}'+nl+
|
|
'{$ENDIF}';
|
|
Result:=fSrcCache.Replace(gtNone,gtNone,fDfmDirectiveStart,fDfmDirectiveEnd,s);
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.RemoveUnits: boolean;
|
|
// Remove units
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result:=false;
|
|
if Assigned(fUnitsToRemove) then begin
|
|
for i:=0 to fUnitsToRemove.Count-1 do begin
|
|
fSrcCache:=CodeToolBoss.SourceChangeCache;
|
|
fSrcCache.MainScanner:=fCodeTool.Scanner;
|
|
if not fCodeTool.RemoveUnitFromAllUsesSections(UpperCaseStr(fUnitsToRemove[i]),
|
|
fSrcCache) then
|
|
exit;
|
|
if not fSrcCache.Apply then exit;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.RenameUnits: boolean;
|
|
// Rename units
|
|
begin
|
|
Result:=false;
|
|
if Assigned(fUnitsToRename) then
|
|
if not fCodeTool.ReplaceUsedUnits(fUnitsToRename, fSrcCache) then
|
|
exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.CommentOutUnits: boolean;
|
|
// Comment out missing units
|
|
begin
|
|
Result:=false;
|
|
if Assigned(fUnitsToComment) and (fUnitsToComment.Count>0) then
|
|
if not fCodeTool.CommentUnitsInUsesSections(fUnitsToComment, fSrcCache) then
|
|
exit;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TConvDelphiCodeTool.UsesSectionsToUnitnames: TStringList;
|
|
// Collect all unit names from uses sections to a StringList.
|
|
var
|
|
UsesNode: TCodeTreeNode;
|
|
ImplList: TStrings;
|
|
begin
|
|
fCodeTool.BuildTree(true);
|
|
fSrcCache.MainScanner:=fCodeTool.Scanner;
|
|
UsesNode:=fCodeTool.FindMainUsesSection;
|
|
Result:=TStringList(fCodeTool.UsesSectionToUnitnames(UsesNode));
|
|
UsesNode:=fCodeTool.FindImplementationUsesSection;
|
|
ImplList:=fCodeTool.UsesSectionToUnitnames(UsesNode);
|
|
Result.AddStrings(ImplList);
|
|
ImplList.Free;
|
|
end;
|
|
|
|
|
|
function TConvDelphiCodeTool.FixMainClassAncestor(AReplaceTypes: TStringToStringTree): boolean;
|
|
// Change a type that main form inherits from to a fall-back type,
|
|
// if defined in AReplaceTypes.
|
|
|
|
function FindFirstClassNode: TCodeTreeNode;
|
|
// Search for the first class definition which is the only one for form files.
|
|
var
|
|
ANode, ClassNode: TCodeTreeNode;
|
|
begin
|
|
ANode:=fCodeTool.FindMainUsesSection; // or fCodeTool.FindInterfaceNode;
|
|
if ANode<>nil then
|
|
ANode:=ANode.NextBrother;
|
|
Result:=nil;
|
|
while ANode<>nil do begin
|
|
if ANode.Desc in [ctnTypeDefinition,ctnGenericType] then begin
|
|
ClassNode:=fCodeTool.FindTypeNodeOfDefinition(ANode);
|
|
if (ClassNode<>nil) and (ClassNode.Desc in AllClassObjects) then begin
|
|
if (not ((ClassNode.SubDesc and ctnsForwardDeclaration)>0)) then begin
|
|
Result:=ClassNode;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
ANode:=ANode.Next;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
ANode, InheritanceNode: TCodeTreeNode;
|
|
TypeUpdater: TStringMapUpdater;
|
|
OldType, NewType: String;
|
|
HasChanged: Boolean;
|
|
begin
|
|
Result:=false; // fCodeTool.FindInheritanceNode
|
|
with fCodeTool do begin
|
|
BuildTree(true);
|
|
if (AReplaceTypes=nil) or (AReplaceTypes.Tree.Count=0) then exit(true);
|
|
|
|
// Find the class name that the main class inherits from.
|
|
ANode:=FindFirstClassNode;
|
|
if ANode=nil then exit;
|
|
BuildSubTreeForClass(ANode);
|
|
InheritanceNode:=FindInheritanceNode(ANode);
|
|
if InheritanceNode=nil then exit;
|
|
ANode:=InheritanceNode.FirstChild;
|
|
if ANode=nil then exit;
|
|
if ANode.Desc=ctnIdentifier then begin
|
|
MoveCursorToNodeStart(ANode); // cursor to the identifier
|
|
ReadNextAtom;
|
|
OldType:=GetAtom;
|
|
end;
|
|
|
|
// Change the inheritance type to a fall-back type if needed.
|
|
TypeUpdater:=TStringMapUpdater.Create(AReplaceTypes);
|
|
try
|
|
HasChanged:=false;
|
|
if TypeUpdater.FindReplacement(OldType, NewType) then begin
|
|
// change type
|
|
if not HasChanged then begin
|
|
HasChanged:=true;
|
|
fSrcCache.MainScanner:=Scanner;
|
|
end;
|
|
if not fSrcCache.Replace(gtNone,gtNone,
|
|
CurPos.StartPos,CurPos.EndPos, NewType) then
|
|
exit(false);
|
|
end;
|
|
if HasChanged then
|
|
if not fSrcCache.Apply then exit;
|
|
finally
|
|
TypeUpdater.Free;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
|
|
end.
|
|
|