{**************************************************************************************************} { } { Delphi language Preprocessor (dpp32) } { } { 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/ } { } { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF } { ANY KIND, either express or implied. See the License for the specific language governing rights } { and limitations under the License. } { } { The Original Code is dpp_Utils.pas } { } { The Initial Developer of the Original Code is Andreas Hausladen } { Portions created by these individuals are Copyright (C) of these individuals. } { } {**************************************************************************************************} unit dpp_Utils; {$define HASHTABLE} interface uses {$ifdef MSWINDOWS} Windows, SysUtils, Classes, RTLConsts; {$endif} {$ifdef LINUX} Libc, SysUtils, Classes, RTLConsts; {$endif} type TBooleanList = class(TObject) private FCount: Integer; FList: array of Boolean; FLast: Boolean; function GetItems(Index: Integer): Boolean; procedure SetItems(Index: Integer; const Value: Boolean); public constructor Create; function Add(Value: Boolean): Integer; procedure Delete(Index: Integer); procedure Clear; procedure DeleteLast; procedure ToggleLast; property Last: Boolean read FLast; property Count: Integer read FCount; property Items[Index: Integer]: Boolean read GetItems write SetItems; end; TRedirectTable = array of record Text: string; Index: Integer; end; procedure MakeStringHash(const Text: string; Index: Integer; var Table: TRedirectTable); function FindStringHash(const Text: string; const Table: TRedirectTable; CaseSensitive: Boolean): Integer; procedure DelStringHash(const Text: string; var Table: TRedirectTable; CaseSensitive: Boolean); overload; procedure DelStringHash(Index: Integer; var Table: TRedirectTable); overload; type TFilenameMapper = class(TStringList) private FHashTable: TRedirectTable; public procedure AddFilename(const Name, Filename: string); function FindFilename(const Name: string; var Filename: string): Boolean; procedure Clear; override; end; // ***************************************************************************** // **************************** File handling ********************************** // ***************************************************************************** function CopyFile(const SourceFileName, DestFileName: String; NativeCopy: Boolean = True): Boolean; // Copy Source to Dest using a native copy function or the built in. function MoveFile(const SourceFilename, DestFilename: String): Boolean; // Moves the file Source to Dest. If renaming is not possible the file is moved // by copy and delete. function FileExistsX(const Filename: string): Boolean; // Like SysUtils.FileExists() but on Windows it is faster. procedure FileToString(const Filename: string; out Content: string); // Reads a file into the string Content. procedure StringToFile(const Filename, Content: string); // Writes a file from the string Content. function GetPreProcessedFilename(const Filename: string; IncludeIndex: Integer = 0): string; // Returns the corresponding preprocessed Filename. // IncludeIndex = 0 -> *.i.* // IncludeIndex > 0 -> *.iX.* where X=IncludeIndex type TExTestMethod = function(const Filename: string): Boolean of object; function TestFilenames(const Paths, Filename: string; ExTestMethod: TExTestMethod = nil): string; // Returns the file name of an existing file by seaching Paths. function FollowRelativePath(BaseDir, Filename: string): string; // Expands the relative file path Filename based on BaseDir. function CompareFileNames(const FileName1, FileName2: string): Integer; // Compares the two file names // ***************************************************************************** // ************************** String handling ********************************** // ***************************************************************************** function CountCharsStop(Ch, StopCh: Char; P: PChar): Integer; function CountChars(Ch: Char; const S: string): Integer; // CountChars() gets the number of char Ch in P. It stops seeking on char StopCh. function PosCharSet(CS: TSysCharSet; const S: string): Integer; // Returns the index of the first char in S which is in CS. function PosChar(Ch: Char; const S: string): Integer; // Returns the index of the first char Ch in S. function StartsText(const StartText, Text: string): Boolean; // Returns TRUE if StartText is the beginning of Text function RemoveQuotes(const Text: string): string; // Removes the embracing quotes ( " and ' ) function IsStrEmpty(const Text: string): Boolean; // Returns true if all chars in Text are in [#1..#32]. function IndexOfStrText(List: TStrings; const StrText: string; CaseSensitive: Boolean): Integer; // Returns the index of StrText in List. function IndexOfFilename(Files: TStrings; const Filename: string): Integer; // Returns the index of the file name in Files (uses CompareFileNames). procedure PathListToStrings(const Paths: string; List: TStrings); // Converts Paths (path;path or path:path) to a string list. implementation { TBooleanList } constructor TBooleanList.Create; begin inherited Create; FLast := True; end; function TBooleanList.Add(Value: Boolean): Integer; begin if FCount >= Length(FList) then SetLength(FList, FCount + 10); // allocate more than 1 saves some memory with SysMemoryManger FList[FCount] := Value; Result := FCount; Inc(FCount); FLast := Value; end; procedure TBooleanList.Clear; begin FCount := 0; SetLength(FList, 0); FLast := True; end; procedure TBooleanList.Delete(Index: Integer); begin Dec(FCount); if FCount mod 10 = 0 then SetLength(FList, FCount); if FCount > 0 then FLast := FList[FCount - 1] else FLast := True; end; procedure TBooleanList.DeleteLast; begin if Count > 0 then Delete(Count - 1) else FLast := True; end; function TBooleanList.GetItems(Index: Integer): Boolean; begin { if Cardinal(Index) >= Cardinal(Count) then TList.Error(@SListIndexError, Index);} Result := FList[Index]; end; procedure TBooleanList.SetItems(Index: Integer; const Value: Boolean); begin { if Cardinal(Index) >= Cardinal(Count) then TList.Error(@SListIndexError, Index);} FList[Index] := Value; if Index = Count - 1 then FLast := Value; end; procedure TBooleanList.ToggleLast; begin if Count > 0 then Items[Count - 1] := not Items[Count - 1]; end; { TFilenameMapper } procedure TFilenameMapper.AddFilename(const Name, Filename: string); begin MakeStringHash(Name, Add(Name) + 1, FHashTable); Add(Filename); end; procedure TFilenameMapper.Clear; begin SetLength(FHashTable, 0); inherited Clear; end; function TFilenameMapper.FindFilename(const Name: string; var Filename: string): Boolean; {$ifdef HASHTABLE} var Index: Integer; begin Index := FindStringHash(Name, FHashTable, {$ifdef MSWINDOWS}False{$endif}{$ifdef LINUX}True{$endif}); if Index > 0 then begin Result := True; Filename := Strings[Index]; end else Result := False; end; {$else} var i: Integer; cnt: Integer; begin cnt := Count; i := 0; while i < cnt do begin if CompareFileNames(Name, Strings[i]) = 0 then begin Result := True; Filename := Strings[i + 1]; Exit; end; Inc(i, 2); end; Result := False; end; {$endif} // ***************************************************************************** // **************************** File handling ********************************** // ***************************************************************************** function CopyFile(const SourceFileName, DestFileName: String; NativeCopy: Boolean = True): Boolean; function BuiltInCopyFile: Boolean; var InFile, OutFile: TFileStream; {$ifdef MSWINDOWS} CreationTime, LastWriteTime, LastAccessTime: TFileTime; {$endif} {$ifdef LINUX} st: TStatBuf; {$endif} begin Result := True; try InFile := TFileStream.Create(SourceFileName, fmOpenRead or fmShareDenyWrite); try OutFile := TFileStream.Create(DestFileName, fmCreate or fmShareExclusive); try try OutFile.CopyFrom(InFile, 0); except Result := False; end; {$ifdef MSWINDOWS} GetFileTime(InFile.Handle, @CreationTime, @LastAccessTime, @LastWriteTime); SetFileTime(OutFile.Handle, @CreationTime, @LastAccessTime, @LastWriteTime); {$endif} finally OutFile.Free; end; {$ifdef LINUX} FileSetDate(DestFileName, FileGetDate(InFile.Handle)); if fstat(InFile.Handle, st) = 0 then Libc.chmod(PChar(DestFileName), st.st_mode); {$endif} finally InFile.Free; end; {$ifdef MSWINDOWS} SetFileAttributes(PChar(DestFileName), GetFileAttributes(PChar(SourceFileName))); {$endif} except Result := False; end; end; begin {$ifdef MSWINDOWS} if NativeCopy then Result := Windows.CopyFile(PChar(SourceFileName), PChar(DestFileName), False) else {$endif} Result := BuiltInCopyFile; end; // ***************************************************************************** function MoveFile(const SourceFilename, DestFilename: String): Boolean; begin Result := False; if (SourceFilename = '') or (DestFilename = '') or (not FileExists(SourceFilename)) then Exit; ForceDirectories(ExtractFilePath(DestFilename)); // create directories if FileExists(DestFilename) then // delete destination file if exist begin {$ifdef MSWINDOWS} SetFileAttributes(PChar(DestFilename), 0); {$endif} {$ifdef LINUX} FileSetReadOnly(DestFilename, False); {$endif} DeleteFile(DestFilename); end; if not RenameFile(SourceFilename, DestFilename) then begin if CopyFile(SourceFilename, DestFilename, True) then begin // delete source file {$ifdef MSWINDOWS} SetFileAttributes(PChar(SourceFilename), 0); {$endif} {$ifdef LINUX} FileSetReadOnly(SourceFilename, False); {$endif LINUX} DeleteFile(SourceFilename); Result := True; end; end else Result := True; end; // ***************************************************************************** function FileExistsX(const Filename: string): Boolean; {$ifdef MSWINDOWS} var Attrib: Cardinal; begin Attrib := GetFileAttributes(PChar(Filename)); Result := (Attrib <> $FFFFFFFF) and (Attrib and FILE_ATTRIBUTE_DIRECTORY = 0); end; {$else} asm JMP FileExists end; {$endif} // ***************************************************************************** procedure FileToString(const Filename: string; out Content: string); var Stream: TStream; Len: Integer; begin Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite); try Len := Stream.Size; SetLength(Content, Len); if Len > 0 then Stream.ReadBuffer(Content[1], Len); finally Stream.Free; end; end; // ***************************************************************************** function GetPreProcessedFilename(const Filename: string; IncludeIndex: Integer = 0): string; var Ext, NewExt: string; begin if IncludeIndex = 0 then NewExt := '.i' else NewExt := Format('.i%d', [IncludeIndex]); Ext := ExtractFileExt(Filename); Result := ChangeFileExt(Filename, NewExt) + Ext; end; // ***************************************************************************** function TestFilenames(const Paths, Filename: string; ExTestMethod: TExTestMethod): string; var List: TStrings; i: Integer; begin List := TStringList.Create; try PathListToStrings(Paths, List); // does never return empty list items for i := 0 to List.Count - 1 do begin Result := List.Strings[i]; if Result[Length(Result)] <> PathDelim then Result := Result + PathDelim + Filename else Result := Result + Filename; if Assigned(ExTestMethod) then if ExTestMethod(Result) then Exit; if FileExistsX(Result) then Exit; end; finally List.Free; end; Result := ''; end; // ***************************************************************************** function FollowRelativePath(BaseDir, Filename: string): string; var ps: Integer; s: string; begin Result := Filename; if Filename = '' then Exit; if Filename[1] = PathDelim then Result := ExtractFileDrive(BaseDir) + Filename else begin BaseDir := ExcludeTrailingPathDelimiter(BaseDir); ps := PosChar(PathDelim, Filename); while ps > 0 do begin s := Copy(Filename, 1, ps - 1); Delete(Filename, 1, ps); if s = '..' then BaseDir := ExtractFileDir(BaseDir) else if s <> '.' then BaseDir := BaseDir + PathDelim + s; ps := PosChar(PathDelim, Filename); end; end; if Pointer(Filename) <> nil then // <= => Length(Filename) > 0 then Result := BaseDir + PathDelim + Filename else Result := BaseDir; end; // ***************************************************************************** procedure StringToFile(const Filename, Content: string); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmCreate); try if Length(Content) > 0 then Stream.WriteBuffer(Content[1], Length(Content)); finally Stream.Free; end; end; // ***************************************************************************** function CompareFileNames(const FileName1, FileName2: string): Integer; assembler asm {$ifdef MSWINDOWS} JMP CompareText {$endif} {$ifdef LINUX} JMP CompareStr {$endif} end; // ***************************************************************************** // ************************** String handling ********************************** // ***************************************************************************** function CountCharsStop(Ch, StopCh: Char; P: PChar): Integer; begin Result := 0; while not (P[0] in [#0, StopCh]) do begin if P[0] = Ch then Inc(Result); Inc(P); end; end; function CountChars(Ch: Char; const S: string): Integer; var i: Integer; begin Result := 0; for i := 1 to Length(S) do if S[i] = Ch then Inc(Result); end; // ***************************************************************************** function PosCharSet(CS: TSysCharSet; const S: string): Integer; begin for Result := 1 to Length(S) do if S[Result] in CS then Exit; Result := 0; end; // ***************************************************************************** function PosChar(Ch: Char; const S: string): Integer; assembler; asm // AL = Ch // EDX = const S PUSH ESI MOV ESI, EDX // ESI = S // String empty ? OR EDX, EDX JZ @@TheEnd @@loop: MOV AH, [ESI] INC ESI // Char found ? CMP AL, AH JZ @@TheEnd // String-End ? OR AH, AH JNZ @@loop MOV ESI, EDX @@TheEnd: MOV EAX, ESI SUB EAX, EDX POP ESI end; // ***************************************************************************** type PStrRec = ^StrRec; StrRec = packed record refCnt: Longint; length: Longint; end; function StartsText(const StartText, Text: string): Boolean; begin if Pointer(Text) = nil then Result := Pointer(StartText) = nil else Result := StrLIComp(Pointer(Text), Pointer(StartText), PStrRec(Integer(StartText) - SizeOf(StrRec)).length) = 0; end; // ***************************************************************************** function RemoveQuotes(const Text: string): string; var Len: Integer; begin Result := Text; Len := Length(Result); if (Len > 0) and (Result[1] = Result[Len]) and (Result[1] in ['''', '"']) then begin Delete(Result, Len, 1); Delete(Result, 1, 1); end; end; // ***************************************************************************** function IsStrEmpty(const Text: string): Boolean; var i: Integer; begin Result := False; for i := 1 to Length(Text) do if Text[i] > ' ' then Exit; Result := True; end; // ***************************************************************************** function IndexOfStrText(List: TStrings; const StrText: string; CaseSensitive: Boolean): Integer; var cmp: function(const S1, S2: string): Integer; begin if List <> nil then begin if CaseSensitive then cmp := CompareStr else cmp := CompareText; for Result := 0 to List.Count - 1 do if cmp(List.Strings[Result], StrText) = 0 then Exit; end; Result := -1; end; // ***************************************************************************** function IndexOfFilename(Files: TStrings; const Filename: string): Integer; begin if Files <> nil then begin for Result := 0 to Files.Count - 1 do if CompareFileNames(Files.Strings[Result], Filename) = 0 then Exit; end; Result := -1; end; // ***************************************************************************** procedure PathListToStrings(const Paths: string; List: TStrings); var s: string; F, P: PChar; begin P := PChar(Paths); while P[0] <> #0 do begin F := P; while not (P[0] in [#0, PathSep]) do Inc(P); if F < P then begin SetString(s, F, P - F); s := RemoveQuotes(s); if Length(s) > 0 then List.Add(s); end; if P[0] <> #0 then Inc(P); end; end; // ***************************************************************************** // ***************************** String Hash *********************************** // ***************************************************************************** function StringHash(const Text: string): Integer; var a, i, ch, Len: Integer; begin Len := Length(Text); a := Len - 6; if a <= 0 then a := 1; Result := 0; for i := Len downto a do begin ch := Byte(Text[i]); if ch >= Byte('a') then Dec(ch, 32); Dec(ch, 48); // no chars below '0' are allowed Inc(Result, ch); end; end; {procedure NextHash(var hash: Integer); begin Inc(hash, 29); end;} procedure MakeStringHash(const Text: string; Index: Integer; var Table: TRedirectTable); var hash, len: Integer; begin hash := StringHash(Text); len := Length(Table); repeat if hash >= len then begin SetLength(Table, hash + 1); Table[hash].Text := Text; Table[hash].Index := Index; Exit; end else if Table[hash].Index = 0 then begin Table[hash].Text := Text; Table[hash].Index := Index; Exit; end; // NextHash(hash); Inc(hash, 29); until False; end; function GetStringHashTableIndex(const Text: string; const Table: TRedirectTable; CaseSensitive: Boolean): Integer; var hash, len: Integer; begin Result := -1; hash := StringHash(Text); len := Length(Table); if CaseSensitive then begin while (hash < len) and (Table[hash].Index <> 0) do begin if Table[hash].Text = Text then begin Result := hash; Exit; end; // NextHash(hash); Inc(hash, 29); end; end else begin while (hash < len) and (Table[hash].Index <> 0) do begin if (SameText(Table[hash].Text, Text)) then begin Result := hash; Exit; end; // NextHash(hash); Inc(hash, 29); end; end; end; function FindStringHash(const Text: string; const Table: TRedirectTable; CaseSensitive: Boolean): Integer; begin Result := GetStringHashTableIndex(Text, Table, CaseSensitive); if Result = -1 then Result := 0 else Result := Table[Result].Index; end; procedure DelStringHash(const Text: string; var Table: TRedirectTable; CaseSensitive: Boolean); var Index, len: Integer; begin Index := GetStringHashTableIndex(Text, Table, CaseSensitive); if Index >= 0 then begin // Table[Index].Text := ''; Table[Index].Index := 0; // shrink table len := Length(Table); while (len > 0) and (Table[len - 1].Index = 0) do Dec(len); SetLength(Table, len); end; end; procedure DelStringHash(Index: Integer; var Table: TRedirectTable); var i, len: Integer; begin for i := 0 to High(Table) do if Table[i].Index = Index then begin // Table[i].Text := ''; Table[i].Index := 0; end; // shrink table len := Length(Table); while (len > 0) and (Table[len - 1].Index = 0) do Dec(len); SetLength(Table, len); end; end.