Files
MycLib/Src/Data/Myc.Data.Types.Tuple.pas
2025-08-27 16:05:13 +02:00

539 lines
15 KiB
ObjectPascal

unit Myc.Data.Types.Tuple;
interface
uses
System.Generics.Collections,
System.SysUtils,
System.Generics.Defaults,
System.Hash,
Myc.Data.Types;
type
// Custom comparer for TArray<IDataType> to be used as a dictionary key.
TDataTupleTypeComparer = class(TInterfacedObject, IEqualityComparer<TArray<IDataType>>)
public
function Equals(const Left, Right: TArray<IDataType>): Boolean; reintroduce;
function GetHashCode(const Value: TArray<IDataType>): Integer; reintroduce;
end;
// Implements the tuple data type.
TImplDataTupleType = class(TInterfacedObject, IDataType, IDataTupleType)
strict private
// Class-level registry to cache and reuse tuple type definitions.
class var
FTupleTypeRegistry: TDictionary<TArray<IDataType>, IDataTupleType>;
private
FItemTypes: TArray<IDataType>;
FEmptyValue: IDataTupleValue; // Cached instance for zero-field tuples
function GetName: String;
function GetKind: TDataKind;
function CreateValue(const AItems: array of IDataValue): IDataTupleValue;
function GetItemCount: Integer;
function GetItemType(Idx: Integer): IDataType;
public
constructor Create(const AItemTypes: TArray<IDataType>);
destructor Destroy; override;
class constructor CreateClass;
class destructor DestroyClass;
class function GetInstance(const AItemTypes: array of IDataType): IDataTupleType; static;
end;
implementation
uses
System.SyncObjs,
System.Rtti;
type
// Implements the tuple data value.
TImplDataTupleValue = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
FItems: TArray<IDataValue>;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
end;
// Optimized implementation for a tuple with 0 fields.
TImplDataTupleValue0 = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType);
end;
// Optimized implementation for a tuple with 1 field.
TImplDataTupleValue1 = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
FItem0: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
end;
// Optimized implementation for a tuple with 2 fields.
TImplDataTupleValue2 = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
FItem0, FItem1: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
end;
// Optimized implementation for a tuple with 3 fields.
TImplDataTupleValue3 = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
FItem0, FItem1, FItem2: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
end;
// Optimized implementation for a tuple with 4 fields.
TImplDataTupleValue4 = class(TInterfacedObject, IDataValue, IDataTupleValue)
private
FDataType: IDataTupleType;
FItem0, FItem1, FItem2, FItem3: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
end;
{ TDataTupleTypeComparer }
function TDataTupleTypeComparer.Equals(const Left, Right: TArray<IDataType>): Boolean;
var
i: Integer;
begin
if (Length(Left) <> Length(Right)) then
exit(False);
for i := 0 to High(Left) do
begin
// Compare by interface reference.
// It's safe because all IDataType instances are managed by GetInstance singletons.
if (Left[i] <> Right[i]) then
exit(False);
end;
Result := True;
end;
function TDataTupleTypeComparer.GetHashCode(const Value: TArray<IDataType>): Integer;
var
dataType: IDataType;
begin
Result := 0;
for dataType in Value do
begin
// Use the hash of the interface's memory address.
// This is safe and fast due to the singleton pattern for types.
Result := Integer(PPointer(@dataType)^) xor Result;
end;
end;
{ TImplDataTupleType }
class constructor TImplDataTupleType.CreateClass;
begin
// Initialize the global registry for tuple types.
FTupleTypeRegistry := TDictionary<TArray<IDataType>, IDataTupleType>.Create(TDataTupleTypeComparer.Create);
end;
class destructor TImplDataTupleType.DestroyClass;
begin
FTupleTypeRegistry.Free;
FTupleTypeRegistry := nil;
end;
constructor TImplDataTupleType.Create(const AItemTypes: TArray<IDataType>);
begin
inherited Create;
FItemTypes := AItemTypes;
// Pre-create the singleton value instance if this is a zero-field tuple.
if (Length(FItemTypes) = 0) then
FEmptyValue := TImplDataTupleValue0.Create(Self);
end;
destructor TImplDataTupleType.Destroy;
begin
inherited;
end;
function TImplDataTupleType.CreateValue(const AItems: array of IDataValue): IDataTupleValue;
var
i: Integer;
begin
if (Length(AItems) <> Length(FItemTypes)) then
raise EArgumentException
.CreateFmt('Invalid number of items for this tuple type. Expected %d, but got %d.', [Length(FItemTypes), Length(AItems)]);
for i := 0 to High(AItems) do
if not Assigned(AItems[i]) or (AItems[i].DataType <> FItemTypes[i]) then
raise EArgumentException.CreateFmt(
'Invalid data type for item at index %d. Expected %s, but got %s.',
[i, FItemTypes[i].Name, AItems[i].DataType.Name]);
// Use optimized implementations for tuples with 0, 1, 2, 3, or 4 fields.
case Length(FItemTypes) of
0: Result := FEmptyValue; // Return the pre-cached instance.
1: Result := TImplDataTupleValue1.Create(Self, AItems);
2: Result := TImplDataTupleValue2.Create(Self, AItems);
3: Result := TImplDataTupleValue3.Create(Self, AItems);
4: Result := TImplDataTupleValue4.Create(Self, AItems);
else
// Use the generic implementation for tuples with more than 4 fields.
Result := TImplDataTupleValue.Create(Self, AItems);
end;
end;
class function TImplDataTupleType.GetInstance(const AItemTypes: array of IDataType): IDataTupleType;
var
tItems: TArray<IDataType>;
i: Integer;
res: IDataTupleType;
begin
SetLength(tItems, Length(AItemTypes));
for i := 0 to High(AItemTypes) do
tItems[i] := AItemTypes[i];
TMonitor.Enter(FTupleTypeRegistry);
try
if not FTupleTypeRegistry.TryGetValue(tItems, res) then
begin
res := TImplDataTupleType.Create(tItems);
FTupleTypeRegistry.Add(tItems, res);
end;
finally
TMonitor.Exit(FTupleTypeRegistry);
end;
Result := res;
end;
function TImplDataTupleType.GetItemCount: Integer;
begin
Result := Length(FItemTypes);
end;
function TImplDataTupleType.GetItemType(Idx: Integer): IDataType;
begin
Result := FItemTypes[Idx];
end;
function TImplDataTupleType.GetName: String;
var
i: Integer;
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('Tuple<');
for i := 0 to High(FItemTypes) do
begin
sb.Append(FItemTypes[i].Name);
if (i < High(FItemTypes)) then
sb.Append(', ');
end;
sb.Append('>');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataTupleType.GetKind: TDataKind;
begin
Result := dkTuple;
end;
{ TImplDataTupleValue (generic implementation for N > 4 fields) }
constructor TImplDataTupleValue.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
var
i: Integer;
begin
inherited Create;
FDataType := ADataType;
SetLength(FItems, Length(AItems));
for i := 0 to High(AItems) do
FItems[i] := AItems[i];
end;
function TImplDataTupleValue.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue.GetAsString: string;
var
sb: TStringBuilder;
i: Integer;
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
for i := 0 to High(FItems) do
begin
sb.Append(FItems[i].AsString);
if (i < High(FItems)) then
sb.Append(', ');
end;
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataTupleValue.GetItem(Idx: Integer): IDataValue;
begin
Result := FItems[Idx];
end;
function TImplDataTupleValue.GetItemCount: Integer;
begin
Result := Length(FItems);
end;
{ TImplDataTupleValue0 }
constructor TImplDataTupleValue0.Create(const ADataType: IDataTupleType);
begin
inherited Create;
FDataType := ADataType;
end;
function TImplDataTupleValue0.GetAsString: string;
begin
Result := '()';
end;
function TImplDataTupleValue0.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue0.GetItem(Idx: Integer): IDataValue;
begin
raise EArgumentException.Create('Index out of bounds');
end;
function TImplDataTupleValue0.GetItemCount: Integer;
begin
Result := 0;
end;
{ TImplDataTupleValue1 }
constructor TImplDataTupleValue1.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
end;
function TImplDataTupleValue1.GetAsString: string;
begin
Result := Format('(%s)', [FItem0.AsString]);
end;
function TImplDataTupleValue1.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue1.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
function TImplDataTupleValue1.GetItemCount: Integer;
begin
Result := 1;
end;
{ TImplDataTupleValue2 }
constructor TImplDataTupleValue2.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
end;
function TImplDataTupleValue2.GetAsString: string;
var
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
sb.Append(FItem0.AsString);
sb.Append(', ');
sb.Append(FItem1.AsString);
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataTupleValue2.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue2.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
1: Result := FItem1;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
function TImplDataTupleValue2.GetItemCount: Integer;
begin
Result := 2;
end;
{ TImplDataTupleValue3 }
constructor TImplDataTupleValue3.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
FItem2 := AItems[2];
end;
function TImplDataTupleValue3.GetAsString: string;
var
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
sb.Append(FItem0.AsString);
sb.Append(', ');
sb.Append(FItem1.AsString);
sb.Append(', ');
sb.Append(FItem2.AsString);
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataTupleValue3.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue3.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
1: Result := FItem1;
2: Result := FItem2;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
function TImplDataTupleValue3.GetItemCount: Integer;
begin
Result := 3;
end;
{ TImplDataTupleValue4 }
constructor TImplDataTupleValue4.Create(const ADataType: IDataTupleType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
FItem2 := AItems[2];
FItem3 := AItems[3];
end;
function TImplDataTupleValue4.GetAsString: string;
var
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
sb.Append(FItem0.AsString);
sb.Append(', ');
sb.Append(FItem1.AsString);
sb.Append(', ');
sb.Append(FItem2.AsString);
sb.Append(', ');
sb.Append(FItem3.AsString);
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataTupleValue4.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataTupleValue4.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
1: Result := FItem1;
2: Result := FItem2;
3: Result := FItem3;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
function TImplDataTupleValue4.GetItemCount: Integer;
begin
Result := 4;
end;
end.