Skip to content

Instantly share code, notes, and snippets.

@Fr0sT-Brutal
Created January 19, 2022 11:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fr0sT-Brutal/bdf9eb2a177fd5216ad3e085c786c993 to your computer and use it in GitHub Desktop.
Save Fr0sT-Brutal/bdf9eb2a177fd5216ad3e085c786c993 to your computer and use it in GitHub Desktop.
// ***************************************************************************
// Class to convert enum value <=> enum name
// T is ordinal / enum type. Enums starting from non-0 are supported (but only
// those which have RTTI):
// ```
// TEnFail = (enF1 = 1, ..., enFX) => will fail (no type info for these)
// TEnBase = (enB0, enB1, ..., enBX)
// TEnOK = enB1..enBX => OK
// ```
// ***************************************************************************
TEnum<T> = class
strict private
class var
FPTypInf: PTypeInfo;
FMin, FMax: Integer;
{$IFDEF CAPS_CLASSCONSTROK}
class constructor Create;
{$ELSE}
class procedure Init;
{$ENDIF}
// Internal utils
class procedure CheckRange(Item: Integer); inline;
class function ValFromArray(FoundIdx: Integer; out Res: T): Boolean; inline;
// Getters
class function GetBound(Index: Integer): Integer; static; // static required by compiler
private // for access from other classes
class procedure CheckArrayLength(ArrLen: Integer); inline;
public
// T => Int
class function Int(Item: T): Integer; overload; inline;
// Str => Int
class function Int(const Name: string): Integer; overload; inline;
// Int => T
class function Val(Item: Integer; CheckRange: Boolean = True): T; overload; inline;
// Str => T
class function Val(const Name: string): T; overload; inline;
// T => Str
class function Str(Item: T): string; overload; inline;
// Int => Str
class function Str(Item: Integer): string; overload;
// Search for Item in Values array and return its index as T
class function Find(const Item: string; const Values: array of string): T; overload;
class function Find(const Item: Char; const Values: array of Char): T; overload;
class function Find(const Item: string; const Values: array of string; out Res: T): Boolean; overload;
class function Find(const Item: Char; const Values: array of Char; out Res: T): Boolean; overload;
class property Min: Integer index 1 read GetBound;
class property Max: Integer index 2 read GetBound;
end;
// ***************************************************************************
// Class to convert set of enum values <=> string
// TE is ordinal / enum type and TS is set type: `TSet<TFruit, TFruits>`.
// Enums starting from non-0 are supported (but only those which have RTTI)
// ***************************************************************************
TSet<TE, TS> = class
public
class function ToByteSet(aSet: TS): TByteSet; static;
class function FromByteSet(ByteSet: TByteSet): TS; static;
class function ToStr(aSet: TS; const Delim: string): string; overload; static;
class function ToStr(aSet: TS; const Values: array of string; const Delim: string): string; overload; static;
class function ToStr(aSet: TS; const Values: array of Char; const Delim: string): string; overload; static;
class function FromStr(const List: string; const Delim: string): TS; overload; static;
class function FromStr(const List: string; const Values: array of string; const Delim: string): TS; overload; static;
class function FromStr(const List: string; const Values: array of Char; const Delim: string): TS; overload; static;
end;
{$ENDIF}
const
S_E_NoTypeInfo = 'Тип не имеет информации (локальное перечисление, перечисление с фиксированными значениями и т.п.)';
S_EEnum_NotAnEnum = 'Тип "%s" не является перечислением';
S_EEnum_WrongSize = 'Размер перечисления "%d" не поддерживается';
S_EEnum_NotInRange = 'Значение "%d" не входит в допустимый диапазон "%d..%d" типа "%s"';
S_EEnum_ArrLenMismatch = 'Длина массива знечений "%d" не совпадает с размером перечисления "%d"';
S_EEnum_NoValueForName = 'Нет значения для имени "%s" у типа "%s"';
S_ESet_NoValueForName = 'Нет значения для имени "%s"';
implementation
{$REGION 'TEnum<T>'}
{$IFDEF TYPES_GENERICS}
{
Note to self: if no class c-tors available, any access to internal fields must
be preceeded with conditional Init. Property getters are using it already but
they're slower so for external use only
}
{$IFDEF CAPS_CLASSCONSTROK}
// Perform some checks and save type properties.
// Executed on unit init if the class is used, raises exception if type is invalid.
class constructor TEnum<T>.Create;
{$ELSE}
class procedure TEnum<T>.Init;
{$ENDIF}
begin
FPTypInf := PTypeInfo(TypeInfo(T));
// type info check
if FPTypInf = nil then
raise Err(S_E_NoTypeInfo);
// run-time type check
if FPTypInf.Kind <> tkEnumeration then
raise Err(S_EEnum_NotAnEnum, [FPTypInf.Name]);
// get range
FMin := GetTypeData(FPTypInf).MinValue;
FMax := GetTypeData(FPTypInf).MaxValue;
end;
// Check if Item in enum range
class procedure TEnum<T>.CheckRange(Item: Integer);
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
if (Item < FMin) or (Item > FMax) then
raise Err(S_EEnum_NotInRange, [Item, FMin, FMax, FPTypInf.Name]);
end;
class function TEnum<T>.GetBound(Index: Integer): Integer;
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
case Index of
1: Result := FMin;
2: Result := FMax;
else Result := -1;
end;
end;
// Integer => Enum member, the same as Integer(T)
// CheckRange: controls whether checking if Item belongs Low(T)..High(T) will
// be performed. Useful to return T(-1) as invalid value.
class function TEnum<T>.Val(Item: Integer; CheckRange: Boolean): T;
var p: Pointer;
begin
if CheckRange then
Self.CheckRange(Item);
p := @Result;
case SizeOf(T) of
1: PUInt8(p)^ := UInt8(Item);
2: PUInt16(p)^ := UInt16(Item);
else
raise Err(S_EEnum_WrongSize, [SizeOf(T)]);
end;
end;
// Enum member => Integer, the same as T(Int)
class function TEnum<T>.Int(Item: T): Integer;
var p: Pointer;
begin
p := @Item;
// ! We use -1 as "invalid value" so cast to signed types
case SizeOf(T) of
1: Result := PInt8(p)^ ;
2: Result := PInt16(p)^;
else
raise Err(S_EEnum_WrongSize, [SizeOf(T)]);
end;
end;
// Integer => String
class function TEnum<T>.Str(Item: Integer): string;
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
CheckRange(Item);
Result := GetEnumName(FPTypInf, Item);
end;
// T => String
class function TEnum<T>.Str(Item: T): string;
begin
Result := Self.Str(Self.Int(Item));
end;
// String => Integer
class function TEnum<T>.Int(const Name: string): Integer;
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
Result := GetEnumValue(FPTypInf, Name);
if Result = -1 then
raise Err(S_EEnum_NoValueForName, [Name, FPTypInf.Name]);
end;
// String => T
class function TEnum<T>.Val(const Name: string): T;
begin
Result := Self.Val(Self.Int(Name));
end;
// Internal method to check array length to correspond enum bounds.
class procedure TEnum<T>.CheckArrayLength(ArrLen: Integer);
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
// Check array length is the same as enum's
if ArrLen <> FMax - FMin + 1 then
raise Err(S_EEnum_ArrLenMismatch, [ArrLen, FMax - FMin + 1]);
end;
// Internal method to return T-value from an index that is result of FindStr/FindChar,
// which is -1 if entry isn't found. In this case Res is not modified and result is false.
// Method considers minimal bound of an enumeration.
class function TEnum<T>.ValFromArray(FoundIdx: Integer; out Res: T): Boolean;
begin
{$IFNDEF CAPS_CLASSCONSTROK}
if FPTypInf = nil then
Init;
{$ENDIF}
Result := (FoundIdx <> -1);
if Result then
Res := Self.Val(FoundIdx + FMin);
end;
// Find string representation in array of strings and return flag and T-value
// Similar to Val(Str) but Text and Values could be arbitrary.
// Returns True and T-value in Res if Text is found, returns False and leaves
// Res unchanged otherwise.
// Method considers minimal bound of an enumeration
class function TEnum<T>.Find(const Item: string; const Values: array of string; out Res: T): Boolean;
begin
CheckArrayLength(Length(Values));
Result := ValFromArray(FindStr(Item, Values), Res);
end;
// The same but for Chars
class function TEnum<T>.Find(const Item: Char; const Values: array of Char; out Res: T): Boolean;
begin
CheckArrayLength(Length(Values));
Result := ValFromArray(FindChar(Item, Values), Res);
end;
// Find string representation in array of strings and return T-value.
// Similar to Val(Str) but Text and Values could be arbitrary.
// Returns T(-1) if Text not found
// Method considers minimal bound of an enumeration
class function TEnum<T>.Find(const Item: string; const Values: array of string): T;
begin
if not Find(Item, Values, Result) then
Result := Self.Val(-1, False); // Turn off range check to return -1
end;
// The same but for Chars
class function TEnum<T>.Find(const Item: Char; const Values: array of Char): T;
begin
if not Find(Item, Values, Result) then
Result := Self.Val(-1, False); // Turn off range check to return -1
end;
{$ENDIF}
{$ENDREGION}
{$REGION 'TSet<TE, TS>'}
{$IFDEF TYPES_GENERICS}
// Convert set to set of bytes
class function TSet<TE, TS>.ToByteSet(aSet: TS): TByteSet;
begin
Result := [];
Move(aSet, Result, SizeOf(TS));
end;
// Convert set from set of bytes
class function TSet<TE, TS>.FromByteSet(ByteSet: TByteSet): TS;
begin
Result := Default(TS);
Move(ByteSet, Result, SizeOf(TS));
end;
// Return string containing list of names of items in a set
class function TSet<TE, TS>.ToStr(aSet: TS; const Delim: string): string;
var item: Byte;
begin
Result := '';
for item in TSet<TE, TS>.ToByteSet(aSet) do
AddStr(Result, TEnum<TE>.Str(item), Delim, False);
end;
// Return string containing list of items of given array corresponding to items in a set
class function TSet<TE, TS>.ToStr(aSet: TS; const Values: array of string; const Delim: string): string;
var item, EnumLow: Byte;
begin
TEnum<TE>.CheckArrayLength(Length(Values)); // ensure Values is of the same size as enum
Result := '';
// Handle case of enums not starting from 0: items are >= N but value array starts from 0 always
EnumLow := TEnum<TE>.Min;
for item in TSet<TE, TS>.ToByteSet(aSet) do
AddStr(Result, Values[item - EnumLow], Delim, False);
end;
// Return string containing list of items of given array corresponding to items in a set
class function TSet<TE, TS>.ToStr(aSet: TS; const Values: array of Char; const Delim: string): string;
var item, EnumLow: Byte;
begin
TEnum<TE>.CheckArrayLength(Length(Values)); // ensure Values is of the same size as enum
Result := '';
// Handle case of enums not starting from 0: items are >= N but value array starts from 0 always
EnumLow := TEnum<TE>.Min;
for item in TSet<TE, TS>.ToByteSet(aSet) do
AddStr(Result, Values[item - EnumLow], Delim, False);
end;
// Convert from string containing list of names of items to set
class function TSet<TE, TS>.FromStr(const List: string; const Delim: string): TS;
var
ByteSet: TByteSet;
s: string;
begin
ByteSet := [];
if List <> '' then
for s in Split(List, Delim, True) do
Include(ByteSet, TEnum<TE>.Int(s));
Result := TSet<TE, TS>.FromByteSet(ByteSet);
end;
// Convert from string containing list of items to set
class function TSet<TE, TS>.FromStr(const List: string; const Values: array of string; const Delim: string): TS;
var
ByteSet: TByteSet;
s: string;
begin
ByteSet := [];
if List <> '' then
for s in Split(List, Delim, True) do
Include(ByteSet, TEnum<TE>.Int(TEnum<TE>.Find(s, Values))); // Values length check is included in Find
Result := TSet<TE, TS>.FromByteSet(ByteSet);
end;
// Convert from string containing list of items to set
class function TSet<TE, TS>.FromStr(const List: string; const Values: array of Char; const Delim: string): TS;
var
ByteSet: TByteSet;
s: string;
begin
ByteSet := [];
if List <> '' then
for s in Split(List, Delim, True) do
Include(ByteSet, TEnum<TE>.Int(TEnum<TE>.Find(FirstChar(s), Values))); // Values length check is included in Find
Result := TSet<TE, TS>.FromByteSet(ByteSet);
end;
{$ENDIF}
{$ENDREGION}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment