unit Compiler.StringLists; { Inno Setup Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. Special string list classes used by TSetupCompiler } interface uses Classes; type THashStringItem = record Hash: Longint; Str: String; end; const MaxHashStringItemListSize = MaxInt div 16; type PHashStringItemList = ^THashStringItemList; THashStringItemList = array[0..MaxHashStringItemListSize-1] of THashStringItem; THashStringList = class private FCapacity: Integer; FCount: Integer; FIgnoreDuplicates: Boolean; FList: PHashStringItemList; procedure Grow; public destructor Destroy; override; function Add(const S: String): Integer; function CaseInsensitiveIndexOf(const S: String): Integer; procedure Clear; function Get(Index: Integer): String; property Count: Integer read FCount; property IgnoreDuplicates: Boolean read FIgnoreDuplicates write FIgnoreDuplicates; property Strings[Index: Integer]: String read Get; default; end; PScriptFileLine = ^TScriptFileLine; TScriptFileLine = record LineFilename: String; LineNumber: Integer; LineText: String; end; TScriptFileLines = class private FLines: TList; function Get(Index: Integer): PScriptFileLine; function GetCount: Integer; function GetText: String; public constructor Create; destructor Destroy; override; procedure Add(const LineFilename: String; const LineNumber: Integer; const LineText: String); property Count: Integer read GetCount; property Lines[Index: Integer]: PScriptFileLine read Get; default; property Text: String read GetText; end; implementation uses PathFunc, Compression.Base; { THashStringList } destructor THashStringList.Destroy; begin Clear; inherited; end; function THashStringList.Add(const S: String): Integer; var LS: String; begin if FIgnoreDuplicates and (CaseInsensitiveIndexOf(S) <> -1) then begin Result := -1; Exit; end; Result := FCount; if Result = FCapacity then Grow; LS := PathLowercase(S); Pointer(FList[Result].Str) := nil; { since Grow doesn't zero init } FList[Result].Str := S; FList[Result].Hash := GetCRC32(Pointer(LS)^, Length(LS)*SizeOf(LS[1])); Inc(FCount); end; procedure THashStringList.Clear; begin if FCount > 0 then Finalize(FList[0], FCount); FCount := 0; FCapacity := 0; ReallocMem(FList, 0); end; function THashStringList.Get(Index: Integer): String; begin if (Index < 0) or (Index >= FCount) then raise EStringListError.CreateFmt('THashStringList: Index %d is out of bounds', [Index]); Result := FList[Index].Str; end; procedure THashStringList.Grow; var Delta, NewCapacity: Integer; begin if FCapacity > 64 then Delta := FCapacity div 4 else if FCapacity > 8 then Delta := 16 else Delta := 4; NewCapacity := FCapacity + Delta; if NewCapacity > MaxHashStringItemListSize then raise EStringListError.Create('THashStringList: Exceeded maximum list size'); ReallocMem(FList, NewCapacity * SizeOf(FList[0])); FCapacity := NewCapacity; end; function THashStringList.CaseInsensitiveIndexOf(const S: String): Integer; var LS: String; Hash: Longint; I: Integer; begin LS := PathLowercase(S); Hash := GetCRC32(Pointer(LS)^, Length(LS)*SizeOf(LS[1])); for I := 0 to FCount-1 do if (FList[I].Hash = Hash) and (PathLowercase(FList[I].Str) = LS) then begin Result := I; Exit; end; Result := -1; end; { TScriptFileLines } constructor TScriptFileLines.Create; begin inherited; FLines := TList.Create; end; destructor TScriptFileLines.Destroy; var I: Integer; begin if Assigned(FLines) then begin for I := FLines.Count-1 downto 0 do Dispose(PScriptFileLine(FLines[I])); FLines.Free; end; inherited; end; procedure TScriptFileLines.Add(const LineFilename: String; const LineNumber: Integer; const LineText: String); var L, PrevLine: PScriptFileLine; begin FLines.Expand; New(L); try { Memory usage optimization: If LineFilename is equal to the previous line's LineFilename, then make this line's LineFilename reference the same string (i.e. just increment its refcount). } PrevLine := nil; if (LineFilename <> '') and (FLines.Count > 0) then PrevLine := PScriptFileLine(FLines[FLines.Count-1]); if Assigned(PrevLine) and (PrevLine.LineFilename = LineFilename) then L.LineFilename := PrevLine.LineFilename else L.LineFilename := LineFilename; L.LineNumber := LineNumber; L.LineText := LineText; except Dispose(L); raise; end; FLines.Add(L); end; function TScriptFileLines.Get(Index: Integer): PScriptFileLine; begin Result := PScriptFileLine(FLines[Index]); end; function TScriptFileLines.GetCount: Integer; begin Result := FLines.Count; end; function TScriptFileLines.GetText: String; var I, L, Size, Count: Integer; P: PChar; S, LB: string; begin Count := GetCount; Size := 0; LB := sLineBreak; for I := 0 to Count-1 do Inc(Size, Length(Get(I).LineText) + Length(LB)); Dec(Size, Length(LB)); SetString(Result, nil, Size); P := Pointer(Result); for I := 0 to Count-1 do begin S := Get(I).LineText; L := Length(S); if L <> 0 then begin System.Move(Pointer(S)^, P^, L * SizeOf(Char)); Inc(P, L); end; if I < Count-1 then begin L := Length(LB); if L <> 0 then begin System.Move(Pointer(LB)^, P^, L * SizeOf(Char)); Inc(P, L); end; end; end; end; end.