Vector type

This commit is contained in:
Michael Schimmel
2025-08-27 15:15:47 +02:00
parent 3daf55a355
commit 644b3fa8fd
4 changed files with 634 additions and 4 deletions
+50
View File
@@ -12,11 +12,61 @@ type
function AsTValue: TValue;
end;
function RttiTypeToDataType(rttiType: TRttiType): TDataType;
implementation
uses
System.TypInfo,
System.Math;
// Recursively converts a TRttiType to an IDataType.
function RttiTypeToDataType(rttiType: TRttiType): TDataType;
var
i: Integer;
begin
// Convert an RTTI type representation to its corresponding IDataType.
case rttiType.TypeKind of
tkInteger, tkInt64, tkChar, tkWChar: Result := TDataType.Ordinal;
tkFloat:
if (rttiType.Handle = TypeInfo(TDateTime)) then
Result := TDataType.Timestamp
else
Result := TDataType.Float;
tkString, tkWideString, tkUString: Result := TDataType.Text;
tkEnumeration:
begin
var enumType := rttiType as TRttiEnumerationType;
var names := enumType.GetNames;
var identifiers: TArray<string>;
SetLength(identifiers, Length(names));
for i := 0 to High(names) do
identifiers[i] := names[i];
Result := TDataType.EnumOf(enumType.Name, identifiers);
end;
tkDynArray:
begin
var arrayType := rttiType as TRttiDynamicArrayType;
Result := TDataType.ArrayOf(RttiTypeToDataType(arrayType.ElementType));
end;
tkRecord:
begin
var recordType := rttiType as TRttiRecordType;
var fields := recordType.GetFields;
var recordFields: TArray<TDataRecordField>;
SetLength(recordFields, Length(fields));
for i := 0 to High(fields) do
recordFields[i] := TDataRecordField.Create(fields[i].Name, RttiTypeToDataType(fields[i].FieldType));
Result := TDataType.RecordOf(recordFields);
end;
else
// Raise an exception for unsupported types.
raise ENotSupportedException.CreateFmt(
'Type "%s" (Kind: %s) is not supported for RTTI-based record creation.',
[rttiType.Name, GetEnumName(TypeInfo(TTypeKind), Ord(rttiType.TypeKind))]);
end;
end;
{ TValueHelper }
function TValueHelper.AsTValue: TValue;
+24 -1
View File
@@ -38,17 +38,23 @@ type
class constructor CreateClass;
class destructor DestroyClass;
// Factory method to get a cached or new record type instance.
class function GetInstance(const AFields: TArray<TDataRecordField>): IDataRecordType; overload; static;
class function GetInstance(const AFields: array of TDataRecordField): IDataRecordType; overload; static;
// TODO Create a record ba analyzing the RTTI of T
class function GetInstance<T>: IDataRecordType; overload; static;
function CreateValue(const AItems: array of IDataValue): IDataRecordValue;
end;
implementation
uses
System.SyncObjs;
System.SyncObjs,
System.Rtti,
Myc.Data.Types.RTTI;
type
// Implements the record data value.
@@ -324,6 +330,23 @@ begin
Result := GetInstance(tFields);
end;
class function TImplDataRecordType.GetInstance<T>: IDataRecordType;
var
ctx: TRttiContext;
rttiType: TRttiType;
begin
// Create an RTTI context to introspect the type T.
ctx := TRttiContext.Create;
rttiType := ctx.GetType(TypeInfo(T));
// The generic parameter T must be a record.
if not (rttiType is TRttiRecordType) then
raise EArgumentException.Create('Generic parameter T must be a record type.');
// Delegate the conversion from RTTI type to IDataType to the helper function.
Result := RttiTypeToDataType(rttiType).AsRecord;
end;
constructor TImplDataRecordType.Create(const AFields: array of TDataRecordField);
var
i: Integer;
+403
View File
@@ -0,0 +1,403 @@
unit Myc.Data.Types.Vector;
interface
uses
System.Generics.Collections,
Myc.Data.Types;
type
// Implements the IDataVectorType interface.
TImplDataVectorType = class(TInterfacedObject, IDataType, IDataVectorType)
private
// Registry for singleton type instances.
class var
FVectorTypeRegistry: TDictionary<String, IDataVectorType>;
FElementType: IDataType;
FElementCount: Integer;
FName: string;
function GetName: String;
function GetKind: TDataKind;
function GetElementType: IDataType;
function GetElementCount: Integer;
function CreateValue(const AItems: array of IDataValue): IDataVectorValue;
class constructor Create;
class destructor Destroy;
public
constructor Create(const AElementType: IDataType; const AElementCount: Integer);
// Gets a cached or new instance for the given element type and count.
class function GetInstance(const AElementType: IDataType; const AElementCount: Integer): IDataVectorType; static;
end;
implementation
uses
System.SysUtils;
type
// Implements a generic vector value for N > 4 elements.
TImplDataVectorValue = class(TInterfacedObject, IDataVectorValue)
private
FDataType: IDataVectorType;
FItems: TArray<IDataValue>;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
end;
// Optimized implementation for a vector with 1 element.
TImplDataVectorValue1 = class(TInterfacedObject, IDataValue, IDataVectorValue)
private
FDataType: IDataVectorType;
FItem0: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
end;
// Optimized implementation for a vector with 2 elements.
TImplDataVectorValue2 = class(TInterfacedObject, IDataValue, IDataVectorValue)
private
FDataType: IDataVectorType;
FItem0, FItem1: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
end;
// Optimized implementation for a vector with 3 elements.
TImplDataVectorValue3 = class(TInterfacedObject, IDataValue, IDataVectorValue)
private
FDataType: IDataVectorType;
FItem0, FItem1, FItem2: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
end;
// Optimized implementation for a vector with 4 elements.
TImplDataVectorValue4 = class(TInterfacedObject, IDataValue, IDataVectorValue)
private
FDataType: IDataVectorType;
FItem0, FItem1, FItem2, FItem3: IDataValue;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
end;
{ TImplDataVectorValue }
constructor TImplDataVectorValue.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
var
i: Integer;
begin
inherited Create;
FDataType := ADataType;
SetLength(FItems, Length(AItems));
for i := 0 to High(FItems) do
FItems[i] := AItems[i];
end;
function TImplDataVectorValue.GetAsString: string;
var
i: Integer;
sb: TStringBuilder;
begin
sb := TStringBuilder.Create;
try
sb.Append('(');
for i := 0 to High(FItems) do
begin
if (i > 0) then
sb.Append(', ');
sb.Append(FItems[i].AsString);
end;
sb.Append(')');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataVectorValue.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataVectorValue.GetElementCount: Integer;
begin
Result := Length(FItems);
end;
function TImplDataVectorValue.GetItem(Idx: Integer): IDataValue;
begin
Result := FItems[Idx];
end;
{ TImplDataVectorValue1 }
constructor TImplDataVectorValue1.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
end;
function TImplDataVectorValue1.GetAsString: string;
begin
Result := '(' + FItem0.AsString + ')';
end;
function TImplDataVectorValue1.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataVectorValue1.GetElementCount: Integer;
begin
Result := 1;
end;
function TImplDataVectorValue1.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
{ TImplDataVectorValue2 }
constructor TImplDataVectorValue2.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
end;
function TImplDataVectorValue2.GetAsString: string;
begin
Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ')';
end;
function TImplDataVectorValue2.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataVectorValue2.GetElementCount: Integer;
begin
Result := 2;
end;
function TImplDataVectorValue2.GetItem(Idx: Integer): IDataValue;
begin
case Idx of
0: Result := FItem0;
1: Result := FItem1;
else
raise EArgumentException.Create('Index out of bounds');
end;
end;
{ TImplDataVectorValue3 }
constructor TImplDataVectorValue3.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
FItem2 := AItems[2];
end;
function TImplDataVectorValue3.GetAsString: string;
begin
Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ', ' + FItem2.AsString + ')';
end;
function TImplDataVectorValue3.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataVectorValue3.GetElementCount: Integer;
begin
Result := 3;
end;
function TImplDataVectorValue3.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;
{ TImplDataVectorValue4 }
constructor TImplDataVectorValue4.Create(const ADataType: IDataVectorType; const AItems: array of IDataValue);
begin
inherited Create;
FDataType := ADataType;
FItem0 := AItems[0];
FItem1 := AItems[1];
FItem2 := AItems[2];
FItem3 := AItems[3];
end;
function TImplDataVectorValue4.GetAsString: string;
begin
Result := '(' + FItem0.AsString + ', ' + FItem1.AsString + ', ' + FItem2.AsString + ', ' + FItem3.AsString + ')';
end;
function TImplDataVectorValue4.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataVectorValue4.GetElementCount: Integer;
begin
Result := 4;
end;
function TImplDataVectorValue4.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;
{ TImplDataVectorType }
class constructor TImplDataVectorType.Create;
begin
// Initialize the class-level type registry.
FVectorTypeRegistry := TDictionary<String, IDataVectorType>.Create;
end;
class destructor TImplDataVectorType.Destroy;
begin
// Free the class-level type registry.
FVectorTypeRegistry.Free;
FVectorTypeRegistry := nil;
end;
constructor TImplDataVectorType.Create(const AElementType: IDataType; const AElementCount: Integer);
begin
inherited Create;
if (AElementCount <= 0) then
raise EArgumentException.Create('Element count must be greater than zero.');
FElementType := AElementType;
FElementCount := AElementCount;
FName := Format('Vector<%s>[%d]', [FElementType.Name, FElementCount]);
end;
function TImplDataVectorType.CreateValue(const AItems: array of IDataValue): IDataVectorValue;
var
item: IDataValue;
i: Integer;
begin
// Validate that the number of items matches the vector's element count.
if (Length(AItems) <> FElementCount) then
raise EArgumentException
.CreateFmt('Invalid number of elements for vector. Expected %d, but got %d.', [FElementCount, Length(AItems)]);
// Validate that all items match the element type of this vector type.
i := 0;
for item in AItems do
begin
if (item.DataType <> FElementType) then
raise EArgumentException.CreateFmt(
'Invalid data type for item at index %d. Expected ''%s'', but got ''%s''.',
[i, FElementType.Name, item.DataType.Name]);
inc(i);
end;
// Use optimized implementations for vectors with 1-4 elements.
case FElementCount of
1: Result := TImplDataVectorValue1.Create(Self, AItems);
2: Result := TImplDataVectorValue2.Create(Self, AItems);
3: Result := TImplDataVectorValue3.Create(Self, AItems);
4: Result := TImplDataVectorValue4.Create(Self, AItems);
else
// Use the generic implementation for vectors with more than 4 elements.
Result := TImplDataVectorValue.Create(Self, AItems);
end;
end;
function TImplDataVectorType.GetElementCount: Integer;
begin
Result := FElementCount;
end;
function TImplDataVectorType.GetElementType: IDataType;
begin
Result := FElementType;
end;
function TImplDataVectorType.GetKind: TDataKind;
begin
Result := dkVector;
end;
function TImplDataVectorType.GetName: String;
begin
Result := FName;
end;
class function TImplDataVectorType.GetInstance(const AElementType: IDataType; const AElementCount: Integer): IDataVectorType;
var
key: string;
begin
if (AElementCount <= 0) then
raise EArgumentException.Create('Element count must be greater than zero.');
if not Assigned(AElementType) then
raise EArgumentException.Create('Cannot create a vector type with a nil element type.');
// Generate a unique key for the dictionary. Using a separator that is unlikely in a type name.
key := AElementType.Name + '|' + AElementCount.ToString;
TMonitor.Enter(FVectorTypeRegistry);
try
if not FVectorTypeRegistry.TryGetValue(key, Result) then
begin
Result := TImplDataVectorType.Create(AElementType, AElementCount);
FVectorTypeRegistry.Add(key, Result);
end;
finally
TMonitor.Exit(FVectorTypeRegistry);
end;
end;
end.
+157 -3
View File
@@ -6,7 +6,7 @@ uses
System.SysUtils;
type
TDataKind = (dkVoid, dkOrdinal, dkFloat, dkText, dkTimestamp, dkDecimal, dkRecord, dkTuple, dkArray, dkEnum, dkMethod);
TDataKind = (dkVoid, dkOrdinal, dkFloat, dkText, dkTimestamp, dkDecimal, dkRecord, dkTuple, dkArray, dkVector, dkEnum, dkMethod);
IDataType = interface
{$region 'private'}
@@ -189,6 +189,27 @@ type
property ElementType: IDataType read GetElementType;
end;
// Represents a fixed-size vector of data values.
IDataVectorValue = interface(IDataValue)
{$region 'private'}
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
{$endregion}
property ElementCount: Integer read GetElementCount;
property Items[Idx: Integer]: IDataValue read GetItem; default;
end;
// Represents the type of a fixed-size vector.
IDataVectorType = interface(IDataType)
{$region 'private'}
function GetElementType: IDataType;
function GetElementCount: Integer;
{$endregion}
function CreateValue(const AItems: array of IDataValue): IDataVectorValue;
property ElementType: IDataType read GetElementType;
property ElementCount: Integer read GetElementCount;
end;
TDataType = record
type
TValue = record
@@ -207,6 +228,7 @@ type
function AsRecord: IDataRecordValue; inline;
function AsTuple: IDataTupleValue; inline;
function AsArray: IDataArrayValue; inline;
function AsVector: IDataVectorValue; inline;
function AsEnum: IDataEnumValue; inline;
function AsMethod: IDataMethodValue; inline;
function AsVoid: IDataVoidValue; inline;
@@ -479,6 +501,36 @@ type
property ElementType: IDataType read GetElementType;
end;
TVector = record
type
TValue = record
private
FVectorValue: IDataVectorValue;
function GetDataType: TDataType.TVector;
function GetItems(Idx: Integer): TDataType.TValue; inline;
function GetElementCount: Integer; inline;
public
constructor Create(const AVectorValue: IDataVectorValue);
class operator Implicit(const A: IDataVectorValue): TDataType.TVector.TValue; overload; inline;
class operator Implicit(const A: TDataType.TVector.TValue): IDataVectorValue; overload; inline;
class operator Implicit(const A: TDataType.TVector.TValue): TDataType.TValue; overload; inline;
property ElementCount: Integer read GetElementCount;
property Items[Idx: Integer]: TDataType.TValue read GetItems; default;
property DataType: TDataType.TVector read GetDataType;
end;
private
FVectorType: IDataVectorType;
function GetElementType: IDataType; inline;
function GetElementCount: Integer; inline;
public
constructor Create(const AVectorType: IDataVectorType);
class operator Implicit(const A: IDataVectorType): TDataType.TVector; overload; inline;
class operator Implicit(const A: TDataType.TVector): IDataVectorType; overload; inline;
function CreateValue(const AItems: array of IDataValue): TDataType.TVector.TValue;
property ElementType: IDataType read GetElementType;
property ElementCount: Integer read GetElementCount;
end;
TMethod = record
type
TProc = reference to function(const AValue: TDataType.TValue): TDataType.TValue;
@@ -530,6 +582,7 @@ type
class operator Implicit(const A: TDataType.TRecord): TDataType; overload; inline;
class operator Implicit(const A: TDataType.TTuple): TDataType; overload; inline;
class operator Implicit(const A: TDataType.TArray): TDataType; overload; inline;
class operator Implicit(const A: TDataType.TVector): TDataType; overload; inline;
// Type factories
class function Void: TDataType.TVoid; static; inline;
@@ -541,8 +594,10 @@ type
class function Tuple: TDataType.TTuple; static; inline;
class function MethodOf(const AArgType, AResultType: IDataType): TDataType.TMethod; static;
class function ArrayOf(const AElementType: IDataType): TDataType.TArray; static;
class function VectorOf(const AElementType: IDataType; const AElementCount: Integer): TDataType.TVector; static;
class function RecordOf(const Fields: TArray<TDataRecordField>): TDataType.TRecord; overload; static;
class function RecordOf(const Fields: array of TDataRecordField): TDataType.TRecord; overload; static;
class function RecordOf<T>: TDataType.TRecord; overload; static;
class function EnumOf(const AName: string; const AIdentifiers: array of string): TDataType.TEnum; static;
function AsOrdinal: TDataType.TOrdinal; inline;
@@ -553,6 +608,7 @@ type
function AsRecord: TDataType.TRecord; inline;
function AsTuple: TDataType.TTuple; inline;
function AsArray: TDataType.TArray; inline;
function AsVector: TDataType.TVector; inline;
function AsEnum: TDataType.TEnum; inline;
function AsMethod: TDataType.TMethod; inline;
@@ -572,6 +628,7 @@ uses
Myc.Data.Types.Timestamp,
Myc.Data.Types.Decimal,
Myc.Data.Types.Arrays,
Myc.Data.Types.Vector,
Myc.Data.Types.Records,
Myc.Data.Types.Tuple,
Myc.Data.Types.Enum,
@@ -1264,6 +1321,75 @@ begin
Result := A.FArrayType;
end;
{ TDataType.TVector.TValue }
constructor TDataType.TVector.TValue.Create(const AVectorValue: IDataVectorValue);
begin
FVectorValue := AVectorValue;
end;
function TDataType.TVector.TValue.GetDataType: TDataType.TVector;
begin
Result := IDataVectorType(FVectorValue.DataType);
end;
function TDataType.TVector.TValue.GetElementCount: Integer;
begin
Result := FVectorValue.ElementCount;
end;
function TDataType.TVector.TValue.GetItems(Idx: Integer): TDataType.TValue;
begin
Result.Create(FVectorValue.Items[Idx]);
end;
class operator TDataType.TVector.TValue.Implicit(const A: IDataVectorValue): TDataType.TVector.TValue;
begin
Result.FVectorValue := A;
end;
class operator TDataType.TVector.TValue.Implicit(const A: TDataType.TVector.TValue): IDataVectorValue;
begin
Result := A.FVectorValue;
end;
class operator TDataType.TVector.TValue.Implicit(const A: TDataType.TVector.TValue): TDataType.TValue;
begin
Result.Create(A.FVectorValue);
end;
{ TDataType.TVector }
constructor TDataType.TVector.Create(const AVectorType: IDataVectorType);
begin
FVectorType := AVectorType;
end;
function TDataType.TVector.CreateValue(const AItems: array of IDataValue): TDataType.TVector.TValue;
begin
Result.Create(FVectorType.CreateValue(AItems));
end;
function TDataType.TVector.GetElementCount: Integer;
begin
Result := FVectorType.ElementCount;
end;
function TDataType.TVector.GetElementType: IDataType;
begin
Result := FVectorType.ElementType;
end;
class operator TDataType.TVector.Implicit(const A: IDataVectorType): TDataType.TVector;
begin
Result.FVectorType := A;
end;
class operator TDataType.TVector.Implicit(const A: TDataType.TVector): IDataVectorType;
begin
Result := A.FVectorType;
end;
{ TDataType }
constructor TDataType.Create(const ADataType: IDataType);
@@ -1330,16 +1456,19 @@ end;
class function TDataType.RecordOf(const Fields: TArray<TDataRecordField>): TDataType.TRecord;
begin
// The TImplDataRecordType now manages its own instances.
Result.Create(TImplDataRecordType.GetInstance(Fields));
end;
class function TDataType.RecordOf(const Fields: array of TDataRecordField): TDataType.TRecord;
begin
// The TImplDataRecordType now manages its own instances.
Result.Create(TImplDataRecordType.GetInstance(Fields));
end;
class function TDataType.RecordOf<T>: TDataType.TRecord;
begin
Result.Create(TImplDataRecordType.GetInstance<T>);
end;
class function TDataType.Text: TDataType.TText;
begin
Result.Create(TImplDataTextType.Singleton);
@@ -1355,6 +1484,12 @@ begin
Result.Create(TImplDataTupleType.Singleton);
end;
class function TDataType.VectorOf(const AElementType: IDataType; const AElementCount: Integer): TDataType.TVector;
begin
// The TImplDataVectorType now manages its own instances.
Result.Create(TImplDataVectorType.GetInstance(AElementType, AElementCount));
end;
class function TDataType.Void: TDataType.TVoid;
begin
Result.Create(TImplDataVoidType.Singleton);
@@ -1375,6 +1510,11 @@ begin
Result.FDataType := A.FArrayType;
end;
class operator TDataType.Implicit(const A: TDataType.TVector): TDataType;
begin
Result.FDataType := A.FVectorType;
end;
class operator TDataType.Implicit(const A: TDataType.TDecimal): TDataType;
begin
Result.FDataType := A.FDecimalType;
@@ -1503,6 +1643,13 @@ begin
Result := IDataTupleValue(FDataValue);
end;
function TDataType.TValue.AsVector: IDataVectorValue;
begin
if (FDataValue.DataType.Kind <> dkVector) then
raise EInvalidCast.Create('Vector expected');
Result := IDataVectorValue(FDataValue);
end;
function TDataType.TValue.AsVoid: IDataVoidValue;
begin
if (FDataValue.DataType.Kind <> dkVoid) then
@@ -1598,4 +1745,11 @@ begin
Result.Create(IDataTupleType(FDataType));
end;
function TDataType.AsVector: TDataType.TVector;
begin
if (FDataType.Kind <> dkVector) then
raise EInvalidCast.Create('Vector expected');
Result.Create(IDataVectorType(FDataType));
end;
end.