Created
January 19, 2022 11:06
-
-
Save Fr0sT-Brutal/bdf9eb2a177fd5216ad3e085c786c993 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// *************************************************************************** | |
// 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