{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: VclClxCvt.pas, released on 2004-05-19. The Initial Developer of the Original Code is Andreas Hausladen (Andreas dott Hausladen att gmx dott de) Portions created by Andreas Hausladen are Copyright (C) 2004 Andreas Hausladen. All Rights Reserved. Contributor(s): - You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Known Issues: -----------------------------------------------------------------------------} // $Id: VclClxCvt.pas 11089 2006-12-16 22:32:51Z ahuser $ unit VclClxCvt; {$I jvcl.inc} interface uses SysUtils, Classes, Contnrs, dpp_PascalParser, VclClxCvtUtils; type TParseContext = record InImplementation, InInterfaceSection: Boolean; LastToken, CurToken: TTokenInfo; end; { TVCLConverter This class converts one file from VCL to CLX. If a unit name is found outside a VCL/VisualCLX block the unitname is translated by a TranslateUnit() call if it is not in the IgnoreUnits list. The TranslateUnit() method's default behaviour is to rename the unit by the UnitReplaceList[]. The class searches for full qualified identifier (Unitname.unitmember), too. After the file is parsed it is stored with a filename returned by ChangeFileName() to the OutDirectory. } TVCLConverter = class(TObject) private FStatistics: TConverterStatistics; FIniDirectory: string; FKeepLines: Boolean; FReduceConditions: Boolean; FOutDirectory: string; FUnixLineBreak: Boolean; FForceOverwrite: Boolean; FIncludeFiles: TStringList; FUsesUnits: TStringList; { contains the units in the "uses" clause (sorted). This is used to find contructs like "type TMyType = MyUnit.TMyType;" or "JvJVCLUtils.func" } FLockedUsesUnits: TStringList; // if a UsesUnit's name is reused for a function, const, variable, ... (unsorted) FConditionStack: TConditionStack; // only valid while in Parse() FDefines: TStringList; // only valid while in Parse() (sorted) FUnitReplaceList: TUnitReplaceList; FIgnoreUnits: TStringList; FRemoveConditions: TStringList; FConvertProtected: TStringList; // list of Conditions where no unit names should be translated FFilename: string; FUnixPathDelim: Boolean; // currently parsed file procedure SetOutDirectory(const Value: string); procedure WriteFile(Lines: TStrings; const Filename: string; AllowBeforeSave: Boolean); procedure CheckDfmLine(var Line: string; const Control: string; Controls: TStrings); procedure CheckOption(Token: PTokenInfo); { Parses the compiler directives and allows the replacement of include file names. } procedure CheckCondition(Parser: TPascalParser; EndifToken: PTokenInfo); { Removes if necessary the condition blocks. } procedure CheckUses(Token: PTokenInfo; var Context: TParseContext); { Parses the uses-clause and allows the replacement of unit names. asn: the following is not required anymore. Required types are added to QWindows. If it finds a "Windows" in a non-IFDEF'ed area the "Types" unit will be added before the replaced "Windows" If no "Windows" unit is found the "Types" unit will be inserted before the replaced "Graphics" unit. "Types" will not be added when it already is in the uses list. } procedure CheckFileHead(Token: PTokenInfo; var Context: TParseContext); { Replaces the "unit", "program", ... name and adds the unit name to the UsedUnits list. } procedure CheckFunction(Token: PTokenInfo; var Context: TParseContext); { Parses procedure/function. } procedure CheckFunctionVarDecls(Token: PTokenInfo; var Context: TParseContext); function CaseParseContext(Token: PTokenInfo; var Context: TParseContext): Boolean; function GetLineBreak: string; function CheckFullQualifiedUnitIdentifier(Token: PTokenInfo; var Context: TParseContext): Boolean; function GetNextToken(Parser: TPascalParser; var Token: PTokenInfo; var Context: TParseContext): Boolean; protected procedure InitUnitReplaceList; virtual; { InitUnitReplaceList is called in the constructor after all sub objects are created. The function can load the unit replace list, ... } procedure TranslateUnit(var AName: string); virtual; { The parser calls TranslateInc() when ever a unit name is found in the source code. The method can change the unit name. The returned name should match the ChangeFileName() returned name for the unit itself. } procedure TranslateInc(var AName: string); virtual; { The parser calls TranslateInc() when it reaches an $I, $INCLUDE compiler directive. The method can change the included file name. } procedure TranslateResource(var AName: string); virtual; { TranslateResource is call when a {$R is found that is not encapsulated by a $IFDEF MSWINDOWS/LINUX $ENDIF. The AName contains all after the compiler directive name. } function ChangeFileName(const Name: string): string; virtual; { ChangeFileName() is called when the parser requires a CLX filename for the source code file. It is called twice. The first time when the "unit", "program", "library" and "packages" statements are parsed and the second time when the file is actually stored. } procedure BeforeSave(const Filename: string; Lines: TStrings); virtual; { BeforeSave() is called before the file is stored. Here you can modify the file lines. Time-dependend lines are not allowed. Filename is the CLX filename (changed by ChangeFileName). } procedure ChangeDfmLine(var Line: string; const Control: string; Controls: TStrings); virtual; procedure Parse(Parser: TPascalParser); virtual; function IsUnitIgnored(const AName: string): Boolean; virtual; function IsUsesUnit(const AName: string): Boolean; procedure ReplaceUnitName(Token: PTokenInfo); function IsProtectedByConditions: Boolean; virtual; public constructor Create(const AIniDirectory: string); destructor Destroy; override; procedure ParseDfmFile(const Filename: string); procedure ParsePasFile(const Filename: string); property Statistics: TConverterStatistics read FStatistics; property IniDirectory: string read FIniDirectory; property OutDirectory: string read FOutDirectory write SetOutDirectory; { Directory where the generated file should be stored. } property ReduceConditions: Boolean read FReduceConditions write FReduceConditions default True; { Removes VCL,COMPILER5,COMPILER6,BCB5,BCB6,BCB condition content and VisualCLX conditions. If False If False the ($I jvcl.inc) is replaced by ($I qjvcl.inc) } property KeepLines: Boolean read FKeepLines write FKeepLines default True; { In combination with ReduceConditions this will keep empty lines for the removed lines. } property UnitReplaceList: TUnitReplaceList read FUnitReplaceList; { Unit -> QUnit names e.g. Controls=QControls} property IgnoreUnits: TStringList read FIgnoreUnits; { These unit names are not touched. } property RemoveConditions: TStringList read FRemoveConditions; { All condition names that are in the RemoveConditions list will be swept out of the source code if ReduceConditions is True. A leading '!' char means the "NOT"-part should be removed. } property ConvertProtected: TStringList read FConvertProtected; { A list of Conditions where the unit names shouldn't be translated. } property UnixLineBreak: Boolean read FUnixLineBreak write FUnixLineBreak default False; { If UnixLineBreak is True the written files have #10 as line break else it uses #13#10. } property UnixPathDelim: Boolean read FUnixPathDelim write FUnixPathDelim default False; { Set UnixPathDelim to True if you want the converter to change every '\' in the "uses unitname in 'filename.pas'" filename to '/'. } property ForceOverwrite: Boolean read FForceOverwrite write FForceOverwrite default False; { If ForceOverwrite is True even unchanged files will be rewritten. } property Filename: string read FFilename; // currently parsed file end; implementation uses Utils, StrUtils; { TVCLConverter } constructor TVCLConverter.Create(const AIniDirectory: string); begin inherited Create; FStatistics := TConverterStatistics.Create; FIniDirectory := ExcludeTrailingPathDelimiter(AIniDirectory); FKeepLines := True; FReduceConditions := True; FIncludeFiles := TStringList.Create; FIncludeFiles.Sorted := True; FIncludeFiles.Duplicates := dupIgnore; FUsesUnits := TStringList.Create; FUsesUnits.Sorted := True; FUsesUnits.Duplicates := dupIgnore; // Must not be case sensetive under Linux ! FLockedUsesUnits := TStringList.Create; // Must not be case sensetive under Linux ! FIgnoreUnits := TStringList.Create; FIgnoreUnits.Sorted := True; FIgnoreUnits.Duplicates := dupIgnore; // Must not be case sensetive under Linux ! FRemoveConditions := TStringList.Create; FRemoveConditions.Sorted := True; FRemoveConditions.Duplicates := dupIgnore; FUnitReplaceList := TUnitReplaceList.Create; FConvertProtected := TStringList.Create; InitUnitReplaceList; end; destructor TVCLConverter.Destroy; begin FIncludeFiles.Free; FUsesUnits.Free; FLockedUsesUnits.Free; FIgnoreUnits.Free; FRemoveConditions.Free; FUnitReplaceList.Free; FConvertProtected.Free; FStatistics.Free; inherited Destroy; end; procedure TVCLConverter.SetOutDirectory(const Value: string); begin FOutDirectory := ExcludeTrailingPathDelimiter(Value); end; function TVCLConverter.GetLineBreak: string; begin if UnixLineBreak then Result := #10 else Result := #13#10; end; procedure TVCLConverter.InitUnitReplaceList; var Lines: TStrings; i: Integer; Filename: string; begin Filename := IniDirectory + PathDelim + 'convertvcl.ini'; if FileExists(Filename) then FUnitReplaceList.AddFromIni(Filename); Lines := TStringList.Create; try Filename := IniDirectory + PathDelim + 'convertprotected.ini'; if FileExists(Filename) then begin Lines.LoadFromFile(Filename); for i := 0 to Lines.Count - 1 do if not IsEmptyStr(Lines[i]) then FConvertProtected.Add(Lines[i]); end; Filename := IniDirectory + PathDelim + 'ignorevcl.ini'; if FileExists(Filename) then begin Lines.LoadFromFile(Filename); for i := 0 to Lines.Count - 1 do if not IsEmptyStr(Lines[i]) then FIgnoreUnits.Add(Lines[i]); end; Filename := IniDirectory + PathDelim + 'nointextreplace.ini'; if FileExists(Filename) then begin Lines.LoadFromFile(Filename); for i := 0 to Lines.Count - 1 do if not IsEmptyStr(Lines[i]) then FLockedUsesUnits.Add(Lines[i]); end; finally Lines.Free; end; end; procedure TVCLConverter.TranslateUnit(var AName: string); begin AName := UnitReplaceList.Find(AName); end; procedure TVCLConverter.TranslateInc(var AName: string); begin // do nothing by default end; procedure TVCLConverter.TranslateResource(var AName: string); begin if SameText(AName, '*.DFM') then AName := '*.xfm'; end; procedure TVCLConverter.ParsePasFile(const Filename: string); var Parser: TPascalParser; Lines: TStrings; FFilepath: string; begin FFilename := Filename; FUsesUnits.Clear; FIncludeFiles.Clear; Lines := TStringList.Create; try Lines.LoadFromFile(Filename); Parser := TPascalParser.Create(Filename, Lines.Text); try Lines.Clear; // reduce memory usage Parse(Parser); Statistics.IncParsedFiles; {statistic} Lines.Text := Parser.Text; FFilepath := FOutDirectory + PathDelim + ChangeFileName(ExtractFileName(Filename)); WriteFile(Lines, FFilePath,True); finally Parser.Free; end; finally Lines.Free; end; end; procedure TVCLConverter.WriteFile(Lines: TStrings; const Filename: string; AllowBeforeSave: Boolean); var i: Integer; sb: IStringBuilder; lb, S, OldFileContent: string; begin if AllowBeforeSave then BeforeSave(Filename, Lines); lb := GetLineBreak; sb := StringBuilder(''); for i := 0 to Lines.Count - 1 do begin sb.Append(Lines[i]); sb.Append(lb); end; sb.GetValue(S); sb := nil; if not ForceOverwrite and FileExists(Filename) then begin ReadFileToString(Filename, OldFileContent); if OldFileContent = S then Exit; // file content is the same end; Statistics.IncWrittenFiles; {statistic} WriteFileFromString(Filename, S); end; function TVCLConverter.ChangeFileName(const Name: string): string; begin if SameText(ExtractFileExt(Name), '.dfm') then Result := 'Q' + ChangeFileExt(Name, '.xfm') else Result := 'Q' + Name; end; function TVCLConverter.IsUnitIgnored(const AName: string): Boolean; var Index, i: Integer; begin Result := FIgnoreUnits.Find(AName, Index); if not Result then begin for i := FIncludeFiles.Count - 1 downto 0 do begin Result := FIgnoreUnits.Find(FIncludeFiles[i] + '::' + AName, Index); if Result then Break; end; end; end; function TVCLConverter.IsUsesUnit(const AName: string): Boolean; var Index: Integer; begin Result := FUsesUnits.Find(AName, Index); if Result then Result := FLockedUsesUnits.IndexOf(AName) < 0; end; procedure TVCLConverter.ReplaceUnitName(Token: PTokenInfo); var UnitName: string; begin UnitName := Token.Value; TranslateUnit(UnitName); if UnitName <> Token.Value then begin FStatistics.IncUnitReplacements; Token.Parser.ReplaceParseNext(Token, Token, UnitName); end; end; function TVCLConverter.CheckFullQualifiedUnitIdentifier(Token: PTokenInfo; var Context: TParseContext): Boolean; var ParserIndex: Integer; Parser: TPascalParser; Tk: TTokenInfo; begin Result := False; with Context do begin if (((Context.LastToken.Kind = tkSymbol) and (Context.LastToken.Value <> '.')) or (Context.LastToken.Kind <> tkSymbol)) and not IsProtectedByConditions and IsUsesUnit(Token.Value) then begin // "UnitName.xxx" but not ".Unitname.xxx" Tk := Token^; Parser := Token.Parser; ParserIndex := Parser.Index; if GetNextToken(Parser, Token, Context) and (Token.Kind = tkSymbol) and (Token.Value = '.') then begin if not IsUnitIgnored(Tk.Value) then ReplaceUnitName(@Tk); Result := True; end else Parser.Index := ParserIndex; end; end; end; function TVCLConverter.CaseParseContext(Token: PTokenInfo; var Context: TParseContext): Boolean; var S: string; begin Result := False; with Context do begin case Token.Kind of tkIdent: begin S := Token.Value; if InImplementation and (SameText(S, 'procedure') or SameText(S, 'function') or SameText(S, 'constructor') or SameText(S, 'destructor')) then begin CheckFunction(Token, Context); Result := True; end else Result := CheckFullQualifiedUnitIdentifier(Token, Context); end; end; end; end; procedure TVCLConverter.Parse(Parser: TPascalParser); var Token: PTokenInfo; Context: TParseContext; S: string; begin FConditionStack := nil; FDefines := nil; try FConditionStack := TConditionStack.Create; FDefines := TStringList.Create; FDefines.Sorted := True; FDefines.Duplicates := dupIgnore; with Context do begin FillChar(Context, SizeOf(Context), 0); InImplementation := False; InInterfaceSection := False; while GetNextToken(Parser, Token, Context) do begin case Token.Kind of tkIdent: begin if not CaseParseContext(Token, Context) then begin S := Token.Value; if SameText(S, 'uses') then CheckUses(Token, Context) else if (not InInterfaceSection) and (not InImplementation) and SameText(S, 'interface') then InInterfaceSection := True else if (not InImplementation) and (SameText(S, 'unit') or SameText(S, 'program') or SameText(S, 'package') or SameText(S, 'library')) then begin CheckFileHead(Token, Context); end else if SameText(S, 'implementation') then begin InImplementation := True; InInterfaceSection := False; end; end; end else CaseParseContext(Token, Context); end; end; end; finally FreeAndNil(FConditionStack); end; end; procedure TVCLConverter.CheckOption(Token: PTokenInfo); // handles the compiler directives var Condition, S: string; IncFilename, OrgIncFilename: string; ResourceName, OrgResource: string; Index: Integer; begin S := RemoveCommentChars(Token.Value); if AnsiStartsText('$I ', S) or AnsiStartsText('$INCLUDE ', S) then begin if AnsiStartsText('$I ', S) then IncFilename := TrimCopy(S, 4, MaxInt) else IncFilename := TrimCopy(S, 9, MaxInt); FIncludeFiles.Add(IncFilename); OrgIncFilename := IncFilename; TranslateInc(IncFilename); if IncFilename <> OrgIncFilename then begin S := StringReplace(Token.Value, OrgIncFilename, IncFilename, []); Token.Parser.ReplaceParseNext(Token, Token, S); end; end else begin if AnsiStartsText('$DEFINE ', S) then FDefines.Add(TrimCopy(S, 9, MaxInt)) else if AnsiStartsText('$UNDEF ', S) then begin if FDefines.Find(TrimCopy(S, 8, MaxInt), Index) then FDefines.Delete(Index); end else if AnsiStartsText('$IFDEF ', S) then begin Condition := TrimCopy(S, 8, MaxInt); FConditionStack.Enter(Condition, Token.StartIndex, Token.EndIndex, Token.StartLine); end else if AnsiStartsText('$IFNDEF ', S) then begin Condition := TrimCopy(S, 9, MaxInt); FConditionStack.EnterNot(Condition, Token.StartIndex, Token.EndIndex, Token.StartLine); end else if AnsiStartsText('$ELSE', S) then // $ELSEIF ??? begin FConditionStack.GoElse(Token.StartIndex, Token.EndIndex, Token.StartLine); end else if AnsiStartsText('$ENDIF', S) then begin CheckCondition(Token.Parser, Token); // accesses FConditionStack.Current FConditionStack.Leave; end else if AnsiStartsText('$R ', S) or AnsiStartsText('$RESOURCE ', S) then begin if ((FConditionStack.IsIn('LINUX') = 0) and (FConditionStack.IsIn('MSWINDOWS') = 0)) or (SameText(S, '$R *.DFM')) then begin if SameText(S, '$R *.DFM') then begin if IsProtectedByConditions then Exit; // forced by condition block end; if AnsiStartsText('$R ', S) then ResourceName := TrimCopy(S, 4, MaxInt) else ResourceName := TrimCopy(S, 11, MaxInt); OrgResource := ResourceName; TranslateResource(ResourceName); if ResourceName <> OrgResource then begin S := StringReplace(Token.Value, OrgResource, ResourceName, []); Token.Parser.ReplaceParseNext(Token, Token, S); end; end; end; end; end; procedure TVCLConverter.CheckCondition(Parser: TPascalParser; EndifToken: PTokenInfo); var Cond: TConditionStackItem; function LineClean(Index: Integer): Integer; // after LineClean the tokens are invalidt var StartIndex: Integer; begin Result := 0; StartIndex := Index; while Index > 0 do begin case Parser.Text[Index] of #0..#9: ; #10: // we read backward begin if Parser.Text[Index - 1] = #13 then Dec(Index); Break; end; #11, #12: ; #13: Break; #14..#32: ; else Exit; end; Dec(Index); end; Result := StartIndex - Index; Parser.Delete(Index, Result); Parser.Index := Index; end; procedure Remove(RemoveContent: Boolean); var S: string; ParserIndex: Integer; begin ParserIndex := Parser.Index; if not Cond.HasElse then begin if not RemoveContent then begin // remove $ENDIF before $IFDEF Dec(ParserIndex, EndifToken.EndIndex - EndifToken.StartIndex + 1); Parser.Replace(EndifToken, EndifToken, ''); if not KeepLines then Dec(ParserIndex, LineClean(EndifToken.StartIndex - 1)); Dec(ParserIndex, Cond.OpenEndIndex - Cond.OpenStartIndex + 1); Parser.ReplaceParseNext(Cond.OpenStartIndex, Cond.OpenEndIndex - Cond.OpenStartIndex + 1, ''); end else begin if KeepLines then S := RepeatStr(GetLineBreak, EndifToken.EndLine - Cond.OpenLine) else S := ''; Dec(ParserIndex, EndifToken.EndIndex - Cond.OpenStartIndex + 1); Parser.ReplaceParseNext(Cond.OpenStartIndex, EndifToken.EndIndex - Cond.OpenStartIndex + 1, S); Inc(ParserIndex, Length(S)); end; end else begin if not RemoveContent then begin // remove $ENDIF before $IFDEF if KeepLines then S := RepeatStr(GetLineBreak, EndifToken.EndLine - Cond.ElseLine) else S := ''; Dec(ParserIndex, EndifToken.EndIndex - Cond.ElseStartIndex + 1); Parser.ReplaceParseNext(Cond.ElseStartIndex, EndifToken.EndIndex - Cond.ElseStartIndex + 1, S); Inc(ParserIndex, Length(S)); if not KeepLines then Dec(ParserIndex, LineClean(Cond.ElseStartIndex - 1)); Dec(ParserIndex, Cond.OpenEndIndex - Cond.OpenStartIndex + 1); Parser.ReplaceParseNext(Cond.OpenStartIndex, Cond.OpenEndIndex - Cond.OpenStartIndex + 1, ''); end else begin // remove $ENDIF before $IFDEF if KeepLines then S := RepeatStr(GetLineBreak, Cond.ElseLine - Cond.OpenLine) else S := ''; Dec(ParserIndex, EndifToken.EndIndex - EndifToken.StartIndex + 1); Parser.Replace(EndifToken, EndifToken, ''); if not KeepLines then Dec(ParserIndex, LineClean(EndifToken.StartIndex - 1)); Dec(ParserIndex, Cond.ElseEndIndex - Cond.OpenStartIndex + 1); Parser.ReplaceParseNext(Cond.OpenStartIndex, Cond.ElseEndIndex - Cond.OpenStartIndex + 1, S); Inc(ParserIndex, Length(S)); end; end; if not KeepLines then Dec(ParserIndex, LineClean(Parser.Index - 1)); // restore next token start index Parser.Index := ParserIndex; end; var Index: Integer; begin if not ReduceConditions then Exit; // do nothing here Cond := FConditionStack.Current; if Cond = nil then begin FStatistics.AddError('No IFDEF/IFNDEF open.'); Exit; end; if FRemoveConditions.Find(Cond.Condition, Index) then // "Condition" Remove(not Cond.Negative) else if FRemoveConditions.Find('!' + Cond.Condition, Index) then // "!Condition" Remove(Cond.Negative); end; procedure TVCLConverter.CheckUses(Token: PTokenInfo; var Context: TParseContext); var Parser: TPascalParser; StartConditionStackCount: Integer; // InsertTypesUnitStartIndex: Integer; i: Integer; Changed: Boolean; begin // InsertTypesUnitStartIndex := -1; StartConditionStackCount := FConditionStack.OpenCount; Parser := Token.Parser; while GetNextToken(Parser, Token, Context) do begin case Token.Kind of tkSymbol: if (Token.Value = ';') and (StartConditionStackCount <= FConditionStack.OpenCount) then Break; // finished tkIdent: begin if SameText(Token.Value, 'in') and UnixPathDelim then // uses unitname in 'filename.pas'; begin if GetNextToken(Parser, Token, Context) then begin { Replace '\' by '/' in the filename } if Token.Kind = tkString then begin Changed := False; for i := 1 to Length(Token.Value) do if Token.Value[i] = '\' then begin Token.Value[i] := '/'; Changed := True; end; if Changed then Parser.ReplaceParseNext(Token, Token, Token.Value); end; end; Continue; end; if SameText(Token.Value, 'type') or SameText(Token.Value, 'const') or SameText(Token.Value, 'resourcestring') or SameText(Token.Value, 'var') or SameText(Token.Value, 'function') or SameText(Token.Value, 'procedure') or SameText(Token.Value, 'implementation') or SameText(Token.Value, 'begin') then begin FStatistics.AddError('Wrong condition blocks in ' + Token.Parser.Filename); Parser.Index := Token.StartIndex; // reparse this token Break; // there is something wrong with the Condition-Blocks. end; FUsesUnits.Add(Token.Value); if not IsProtectedByConditions then begin // replace unit names, because we are outside a VCL/VisualCLX condition if not IsUnitIgnored(Token.Value) then begin { asn: not required anymore. if SameText(Token.Value, 'Windows') then InsertTypesUnitStartIndex := Token.StartIndex; if (InsertTypesUnitStartIndex = -1) and SameText(Token.Value, 'Graphics') then InsertTypesUnitStartIndex := Token.StartIndex; } ReplaceUnitName(Token); end; end; end; end; end; { if (InsertTypesUnitStartIndex > 0) and Context.InInterfaceSection and (FUsesUnits.IndexOf('Types') = -1) then begin Parser.Insert(InsertTypesUnitStartIndex, 'Types, '); Parser.IndexNoClear := Parser.Index + 7; end; } end; procedure TVCLConverter.CheckFileHead(Token: PTokenInfo; var Context: TParseContext); var Parser: TPascalParser; NewFilename, Filename, Ext: string; begin if SameText(Token.Value, 'unit') then Ext := '.pas' else if SameText(Token.Value, 'package') then Ext := '.dpk' else Ext := '.dpr'; Filename := ''; Parser := Token.Parser; while GetNextToken(Parser, Token, Context) do begin if Token.Kind = tkIdent then begin // unit/program/library/package name if Filename = '' then // only the first identifier is the unit name, others are syntax errors begin FUsesUnits.Add(Token.Value); Filename := Token.Value + Ext; NewFilename := ChangeFileName(Filename); if NewFilename <> Filename then begin Filename := ChangeFileExt(ExtractFileName(NewFilename), ''); Parser.ReplaceParseNext(Token, Token, Filename); end; end; end else if (Token.Kind = tkSymbol) and (Token.Value = ';') then Break; // finished end; end; procedure TVCLConverter.CheckFunction(Token: PTokenInfo; var Context: TParseContext); var Parser: TPascalParser; LockedUnitStartCount: Integer; BeginBlockCount: Integer; InParams: Boolean; LastTokenValue: string; begin Parser := Token.Parser; LockedUnitStartCount := FLockedUsesUnits.Count; try // procedure/function header InParams := False; while GetNextToken(Parser, Token, Context) do begin if not InParams then begin if Token.Kind = tkSymbol then begin if Token.Value = ';' then InParams := True; // no parameters if Token.Value = '(' then InParams := True; end; end else begin case Token.Kind of tkIdent: begin if (LastTokenValue <> ':') and IsUsesUnit(Token.Value) then FLockedUsesUnits.Add(Token.Value) // this unit name is redeclared as parameter else begin CaseParseContext(Token, Context); if SameText(Token.Value, 'external') or SameText(Token.Value, 'forward') then Exit; // this is only a procedure head if SameText(Token.Value, 'begin') or SameText(Token.Value, 'var') or SameText(Token.Value, 'const') or SameText(Token.Value, 'type') or SameText(Token.Value, 'resourcestring') then Break; if SameText(Token.Value, 'end') then begin FStatistics.AddError('"end" found but "begin", "var", "const", "type" or "resourcestring" expected.'); Exit; // something very strange happend end; end; end; else CaseParseContext(Token, Context); end; end; LastTokenValue := Token.Value; end; if Token = nil then Exit; if not SameText(Token.Value, 'begin') then CheckFunctionVarDecls(Token, Context); BeginBlockCount := 1; while GetNextToken(Parser, Token, Context) do begin if Token.Kind = tkIdent then begin if SameText(Token.Value, 'begin') then Inc(BeginBlockCount) else if SameText(Token.Value, 'end') then begin Dec(BeginBlockCount); if BeginBlockCount = 0 then Break; // function end end; end; CaseParseContext(Token, Context); end; finally // we leave the function so remove the locked local "unit name" variables while FLockedUsesUnits.Count > LockedUnitStartCount do FLockedUsesUnits.Delete(FLockedUsesUnits.Count - 1); end; end; procedure TVCLConverter.CheckFunctionVarDecls(Token: PTokenInfo; var Context: TParseContext); var Parser: TPascalParser; LastTokenValue: string; begin Parser := Token.Parser; while GetNextToken(Parser, Token, Context) do begin case Token.Kind of tkIdent: begin if (LastTokenValue <> ':') and IsUsesUnit(Token.Value) then FLockedUsesUnits.Add(Token.Value) // this unit name is redeclared as variable/const/resstring else begin if not CaseParseContext(Token, Context) then // meight find records, ... begin if SameText(Token.Value, 'begin') then Break; if SameText(Token.Value, 'end') then begin FStatistics.AddError('"end" found but "begin", "var", "const", "type" or "resourcestring" expected.'); Exit; // something very strange happend end; end; end; end; else CaseParseContext(Token, Context); end; LastTokenValue := Token.Value; end; end; procedure TVCLConverter.BeforeSave(const Filename: string; Lines: TStrings); begin // do nothing end; { Calls CheckDfmLine with parameter Control= * ClassName e.g. 'TListView' * ClassName.Property for Collections e.g: 'TListView.Columns' * ClassName.Property:item for Collection items: e.g. 'TListView.Columns:item' } procedure TVCLConverter.ParseDfmFile(const Filename: string); var Lines: TStrings; i, ps: Integer; S, TrimS: string; Controls: TStringList; begin Controls := TStringList.Create; Lines := TStringList.Create; try Lines.LoadFromFile(Filename); if Lines.Count > 0 then begin if (Lines[0] <> '') and (Lines[0][1] < #32) or (Lines[0][2] < #32) then begin FStatistics.AddError(ExtractFileName(Filename) + ' is binary. Converting to text.'); ConvertBinDfmToText(Filename); Lines.LoadFromFile(Filename); end; i := 0; while i < Lines.Count do begin S := Lines[i]; TrimS := Trim(S); if TrimS <> '' then begin if TrimS = 'DesignSize = (' then begin Lines.Delete(i); Lines.Delete(i); Lines.Delete(i); Continue; end else begin if AnsiStartsText('object ', TrimS) then begin ps := Pos(':', TrimS); if ps > 0 then Controls.Add(Trim(Copy(TrimS, ps + 1, MaxInt))); end else if SameText(TrimS, 'end') and (Controls.Count > 0) then Controls.Delete(Controls.Count - 1) else if Controls.Count > 0 then begin if AnsiEndsText('= <', TrimS) then begin // collection Controls.Add(Controls[Controls.Count - 1] + '.' + Trim(Copy(TrimS, 1, Pos('=', TrimS) - 1))); end else if SameText(TrimS, 'end>') then begin Controls.Delete(Controls.Count - 1); Controls.Delete(Controls.Count - 1); end else if SameText(TrimS, 'item') then Controls.Add(Controls[Controls.Count - 1] + ':item') else CheckDfmLine(S, Controls[Controls.Count - 1], Controls); Lines[i] := S; end; end; end; Inc(i); end; WriteFile(Lines, FOutDirectory + PathDelim + ChangeFileName(ExtractFileName(Filename)), False); end else FStatistics.AddError(ExtractFileName(Filename) + ' is empty.'); finally Lines.Free; Controls.Free; end; end; procedure TVCLConverter.CheckDfmLine(var Line: string; const Control: string; Controls: TStrings); var S, OrgS: string; begin Line := TrimRight(Line); if Line <> '' then begin S := TrimLeft(Line); OrgS := S; ChangeDfmLine(S, Control, Controls); if S <> OrgS then Line := StringReplace(Line, OrgS, S, []); end; end; procedure TVCLConverter.ChangeDfmLine(var Line: string; const Control: string; Controls: TStrings); begin if (Controls.Count = 1) and AnsiStartsText('BorderStyle = ', Line) then Line := StringReplace(Line, ' bs', ' fbs', [rfIgnoreCase]); if AnsiStartsText('Ctl3D = ', Line) or AnsiStartsText('ParentCtl3D = ', Line) then Line := ''; if AnsiStartsText('IsControl = True', Line) or AnsiStartsText('PageSize = 0', Line) or AnsiStartsText('DefaultMonitor = ', Line) or AnsiStartsText('RightClickSelect = True', Line) then Line := ''; if (Control = 'TProgressBar') and AnsiStartsText('TabOrder = ', Line) then Line := ''; if (Control = 'TComboBox') and (AnsiStartsText('AutoDropDown = ', Line) or AnsiStartsText('AutoCloseUp = ', Line)) then Line := ''; if (Control = 'TListView') then begin if AnsiStartsText('SmallImages = ', Line) then Line := StringReplace(Line, 'SmallImages = ', 'Images = ', [rfIgnoreCase]) else if AnsiStartsText('OnCompare = ', Line) then Line := '' else if AnsiStartsText('SortType = ', Line) then begin if Pos('= stNone', Line) = 0 then Line := 'Sorted = True' else Line := '' end; end; end; function TVCLConverter.IsProtectedByConditions: Boolean; var i: Integer; begin Result := True; for i := 0 to ConvertProtected.Count - 1 do if FConditionStack.IsIn(ConvertProtected[i]) <> 0 then Exit; Result := False; end; function TVCLConverter.GetNextToken(Parser: TPascalParser; var Token: PTokenInfo; var Context: TParseContext): Boolean; begin Context.LastToken := Context.CurToken; while Parser.GetToken(Token) and (Token.Kind = tkComment) do begin if Token.ExKind = tekOption then CheckOption(Token); end; Result := Token <> nil; if Result then Context.CurToken := Token^; end; end.