Data Types

This commit is contained in:
Michael Schimmel
2025-08-25 14:48:06 +02:00
parent ce653c83b1
commit c9e28a946d
10 changed files with 374 additions and 3 deletions
+22
View File
@@ -28,6 +28,7 @@ type
FArrayType: IDataArrayType;
FItems: TArray<IDataValue>;
function GetDataType: IDataType;
function GetAsString: string;
function GetElementCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
@@ -57,6 +58,27 @@ begin
Result := FArrayType;
end;
function TImplDataArrayValue.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 TImplDataArrayValue.GetElementCount: Integer;
begin
Result := Length(FItems);
+132
View File
@@ -0,0 +1,132 @@
unit Myc.Data.Types.Enum;
interface
uses
System.SysUtils,
System.Generics.Collections,
Myc.Data.Types;
type
TImplDataEnumValue = class(TInterfacedObject, IDataEnumValue)
private
FDataType: IDataEnumType;
FValue: Integer;
function GetDataType: IDataType;
function GetAsString: string;
function GetValue: Integer;
public
constructor Create(const ADataType: IDataEnumType; const AValue: Integer);
end;
TImplDataEnumType = class(TInterfacedObject, IDataEnumType)
private
FName: string;
FIdentifiers: TArray<string>;
FIdentifierMap: TDictionary<string, Integer>;
function GetName: String;
function GetKind: TDataKind;
function GetIdentifier(Idx: Integer): string;
function GetIdentifierCount: Integer;
function IndexOf(const AIdentifier: string): Integer;
function CreateValue(const AValue: Integer): IDataEnumValue; overload;
function CreateValue(const AIdentifier: string): IDataEnumValue; overload;
public
constructor Create(const AName: string; const AIdentifiers: array of string);
destructor Destroy; override;
end;
implementation
{ TImplDataEnumType }
constructor TImplDataEnumType.Create(const AName: string; const AIdentifiers: array of string);
var
i: Integer;
begin
inherited Create;
FName := AName;
FIdentifierMap := TDictionary<string, Integer>.Create;
SetLength(FIdentifiers, Length(AIdentifiers));
for i := 0 to High(AIdentifiers) do
begin
if FIdentifierMap.ContainsKey(AIdentifiers[i]) then
raise EArgumentException.CreateFmt('Duplicate identifier: %s', [AIdentifiers[i]]);
FIdentifiers[i] := AIdentifiers[i];
FIdentifierMap.Add(AIdentifiers[i], i);
end;
end;
destructor TImplDataEnumType.Destroy;
begin
FIdentifierMap.Free;
inherited;
end;
function TImplDataEnumType.GetName: String;
begin
Result := FName;
end;
function TImplDataEnumType.GetKind: TDataKind;
begin
Result := dkEnum;
end;
function TImplDataEnumType.GetIdentifier(Idx: Integer): string;
begin
Result := FIdentifiers[Idx];
end;
function TImplDataEnumType.GetIdentifierCount: Integer;
begin
Result := Length(FIdentifiers);
end;
function TImplDataEnumType.IndexOf(const AIdentifier: string): Integer;
begin
if not FIdentifierMap.TryGetValue(AIdentifier, Result) then
Result := -1;
end;
function TImplDataEnumType.CreateValue(const AValue: Integer): IDataEnumValue;
begin
if (AValue < 0) or (AValue >= Length(FIdentifiers)) then
raise EArgumentException.Create('Invalid enum value');
Result := TImplDataEnumValue.Create(Self, AValue);
end;
function TImplDataEnumType.CreateValue(const AIdentifier: string): IDataEnumValue;
var
idx: Integer;
begin
idx := IndexOf(AIdentifier);
if idx < 0 then
raise EArgumentException.CreateFmt('Unknown identifier: %s', [AIdentifier]);
Result := CreateValue(idx);
end;
{ TImplDataEnumValue }
constructor TImplDataEnumValue.Create(const ADataType: IDataEnumType; const AValue: Integer);
begin
FDataType := ADataType;
FValue := AValue;
end;
function TImplDataEnumValue.GetDataType: IDataType;
begin
Result := FDataType;
end;
function TImplDataEnumValue.GetAsString: string;
begin
Result := FDataType.Identifiers[FValue];
end;
function TImplDataEnumValue.GetValue: Integer;
begin
Result := FValue;
end;
end.
+6
View File
@@ -16,6 +16,7 @@ type
// IDataValue
function GetDataType: IDataType;
function GetAsString: string;
// IDataFloatValue
function GetValue: Double;
@@ -49,6 +50,11 @@ begin
Result := TImplDataFloatType.Singleton;
end;
function TImplDataFloatValue.GetAsString: string;
begin
Result := FloatToStr(FValue);
end;
function TImplDataFloatValue.GetValue: Double;
begin
Result := FValue;
+6
View File
@@ -16,6 +16,7 @@ type
// IDataValue
function GetDataType: IDataType;
function GetAsString: string;
// IDataOrdinalValue
function GetValue: Int64;
@@ -49,6 +50,11 @@ begin
Result := TImplDataOrdinalType.Singleton;
end;
function TImplDataOrdinalValue.GetAsString: string;
begin
Result := FValue.ToString;
end;
function TImplDataOrdinalValue.GetValue: Int64;
begin
Result := FValue;
+26
View File
@@ -41,6 +41,7 @@ type
FDataType: IDataRecordType;
FItems: TArray<IDataValue>;
function GetDataType: IDataType;
function GetAsString: string;
function GetItem(Idx: Integer): IDataValue;
public
constructor Create(const ADataType: IDataRecordType; const AItems: array of IDataValue);
@@ -97,6 +98,31 @@ begin
Result := FDataType;
end;
function TImplDataRecordValue.GetAsString: string;
var
sb: TStringBuilder;
i: Integer;
recordType: IDataRecordType;
begin
recordType := FDataType;
sb := TStringBuilder.Create;
try
sb.Append('<');
for i := 0 to High(FItems) do
begin
sb.Append(recordType.Fields[i].Name);
sb.Append(': ');
sb.Append(FItems[i].AsString);
if (i < High(FItems)) then
sb.Append(', ');
end;
sb.Append('>');
Result := sb.ToString;
finally
sb.Free;
end;
end;
function TImplDataRecordValue.GetItem(Idx: Integer): IDataValue;
begin
Result := FItems[Idx];
+6
View File
@@ -23,6 +23,7 @@ type
private
FValue: string;
function GetDataType: IDataType;
function GetAsString: string;
function GetValue: string;
public
constructor Create(const AValue: string);
@@ -65,6 +66,11 @@ begin
Result := TImplDataTextType.Singleton;
end;
function TImplDataTextValue.GetAsString: string;
begin
Result := FValue;
end;
function TImplDataTextValue.GetValue: string;
begin
Result := FValue;
+6
View File
@@ -23,6 +23,7 @@ type
private
FValue: TDateTime;
function GetDataType: IDataType;
function GetAsString: string;
function GetValue: TDateTime;
public
constructor Create(const AValue: TDateTime);
@@ -65,6 +66,11 @@ begin
Result := TImplDataTimestampType.Singleton;
end;
function TImplDataTimestampValue.GetAsString: string;
begin
Result := DateTimeToStr(FValue);
end;
function TImplDataTimestampValue.GetValue: TDateTime;
begin
Result := FValue;
+22
View File
@@ -12,6 +12,7 @@ type
private
FItems: TArray<IDataValue>;
function GetDataType: IDataType;
function GetAsString: string;
function GetItemCount: Integer;
function GetItem(Idx: Integer): IDataValue;
public
@@ -51,6 +52,27 @@ begin
Result := TImplDataTupleType.Singleton;
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];
+34 -2
View File
@@ -7,7 +7,7 @@ uses
System.SysUtils;
type
TDataKind = (dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray);
TDataKind = (dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray, dkEnum);
IDataType = interface
function GetName: String;
@@ -18,7 +18,9 @@ type
IDataValue = interface
function GetDataType: IDataType;
function GetAsString: string;
property DataType: IDataType read GetDataType;
property AsString: string read GetAsString;
end;
IDataOrdinalValue = interface(IDataValue)
@@ -57,6 +59,21 @@ type
function CreateValue(const AValue: TDateTime): IDataTimestampValue;
end;
IDataEnumValue = interface(IDataValue)
function GetValue: Integer;
property Value: Integer read GetValue;
end;
IDataEnumType = interface(IDataType)
function GetIdentifier(Idx: Integer): string;
function GetIdentifierCount: Integer;
function IndexOf(const AIdentifier: string): Integer;
function CreateValue(const AValue: Integer): IDataEnumValue; overload;
function CreateValue(const AIdentifier: string): IDataEnumValue; overload;
property IdentifierCount: Integer read GetIdentifierCount;
property Identifiers[Idx: Integer]: string read GetIdentifier; default;
end;
IDataRecordValue = interface(IDataValue)
function GetItem(Idx: Integer): IDataValue;
property Items[Idx: Integer]: IDataValue read GetItem; default;
@@ -127,6 +144,7 @@ type
class function ArrayOfType(const AElementType: IDataType): IDataArrayType; static;
class function RecordOf(const Fields: TArray<TRecordField>): IDataRecordType; overload; static;
class function RecordOf(const Fields: array of TRecordField): IDataRecordType; overload; static;
class function EnumOf(const AName: string; const AIdentifiers: array of string): IDataEnumType; static;
property DataType: IDataType read FDataType;
property Name: String read GetName;
@@ -147,6 +165,7 @@ type
function AsRecord: IDataRecordValue;
function AsTuple: IDataTupleValue;
function AsArray: IDataArrayValue;
function AsEnum: IDataEnumValue;
class operator Implicit(const A: IDataValue): TDataValue; overload;
class operator Implicit(const A: TDataValue): IDataValue; overload;
@@ -173,7 +192,8 @@ uses
Myc.Data.Types.Timestamp,
Myc.Data.Types.Arrays,
Myc.Data.Types.Records,
Myc.Data.Types.Tuple;
Myc.Data.Types.Tuple,
Myc.Data.Types.Enum;
{ TRecordField }
@@ -262,6 +282,11 @@ begin
Result := RecordOf(tFields);
end;
class function TDataType.EnumOf(const AName: string; const AIdentifiers: array of string): IDataEnumType;
begin
Result := TImplDataEnumType.Create(AName, AIdentifiers);
end;
class function TDataType.Text: IDataTextType;
begin
Result := TImplDataTextType.Singleton;
@@ -343,6 +368,13 @@ begin
Result := IDataTupleValue(FDataValue);
end;
function TDataValue.AsEnum: IDataEnumValue;
begin
if FDataValue.DataType.Kind <> dkEnum then
raise EInvalidCast.Create('Enum expected');
Result := IDataEnumValue(FDataValue);
end;
class function TDataValue.FromOrdinal(const AValue: Int64): IDataOrdinalValue;
begin
Result := TImplDataOrdinalType.Singleton.CreateValue(AValue);
+114 -1
View File
@@ -23,6 +23,12 @@ type
[Test]
procedure TestTimestamps;
[Test]
procedure TestEnums;
[Test]
procedure TestAsString;
end;
implementation
@@ -253,7 +259,7 @@ begin
Assert.AreEqual(dkTimestamp, tsType.Kind, 'Type kind should be dkTimestamp');
// --- 2. Test Value Creation and Access ---
now := Now;
now := System.Sysutils.Now;
tsValue1 := TDataValue.FromTimestamp(now);
Assert.IsNotNull(tsValue1, 'TimestampValue should be created');
@@ -277,6 +283,113 @@ begin
);
end;
procedure TMyTestObject.TestEnums;
var
colorType: IDataEnumType;
red, green, blue: IDataEnumValue;
begin
// --- 1. Test Type Creation ---
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
Assert.IsNotNull(colorType, 'EnumType should be created');
Assert.AreEqual('Color', colorType.Name, 'Name should be Color');
Assert.AreEqual(dkEnum, colorType.Kind, 'Kind should be dkEnum');
Assert.AreEqual(3, colorType.IdentifierCount, 'IdentifierCount should be 3');
Assert.AreEqual('Red', colorType.Identifiers[0], 'Identifier at index 0 should be Red');
Assert.AreEqual('Green', colorType.Identifiers[1], 'Identifier at index 1 should be Green');
Assert.AreEqual('Blue', colorType.Identifiers[2], 'Identifier at index 2 should be Blue');
Assert.AreEqual(1, colorType.IndexOf('Green'), 'IndexOf Green should be 1');
Assert.AreEqual(-1, colorType.IndexOf('Yellow'), 'IndexOf Yellow should be -1');
// --- 2. Test Value Creation and Access ---
red := colorType.CreateValue(0);
green := colorType.CreateValue('Green');
blue := TDataValue(colorType.CreateValue(2)).AsEnum;
Assert.IsNotNull(red, 'EnumValue from index should be created');
Assert.AreSame(colorType, red.DataType, 'Red should have the correct data type');
Assert.AreEqual(0, red.Value, 'Value of Red should be 0');
Assert.AreEqual('Red', red.AsString, 'AsText of Red should be Red');
Assert.IsNotNull(green, 'EnumValue from identifier should be created');
Assert.AreSame(colorType, green.DataType, 'Green should have the correct data type');
Assert.AreEqual(1, green.Value, 'Value of Green should be 1');
Assert.AreEqual('Green', green.AsString, 'AsText of Green should be Green');
Assert.AreEqual(2, blue.Value, 'Value of Blue should be 2');
// --- 3. Test Validation and Error Handling ---
// Test creating a type with duplicate identifiers
Assert.WillRaise(
procedure begin TDataType.EnumOf('Fails', ['A', 'B', 'A']); end,
EArgumentException,
'Duplicate identifiers should raise an exception'
);
// Test creating a value with an invalid index
Assert.WillRaise(procedure begin colorType.CreateValue(3); end, EArgumentException, 'Invalid index should raise an exception');
Assert.WillRaise(procedure begin colorType.CreateValue(-1); end, EArgumentException, 'Negative index should raise an exception');
// Test creating a value with an unknown identifier
Assert.WillRaise(
procedure begin colorType.CreateValue('Yellow'); end,
EArgumentException,
'Unknown identifier should raise an exception'
);
end;
procedure TMyTestObject.TestAsString;
var
ordinalValue: IDataOrdinalValue;
floatValue: IDataFloatValue;
textValue: IDataTextValue;
tsValue: IDataTimestampValue;
enumValue: IDataEnumValue;
arrayValue: IDataArrayValue;
recordValue: IDataRecordValue;
tupleValue: IDataTupleValue;
colorType: IDataEnumType;
personType: IDataRecordType;
intArrayType: IDataArrayType;
now: TDateTime;
begin
// Ordinal
ordinalValue := TDataValue.FromOrdinal(123);
Assert.AreEqual('123', ordinalValue.AsString, 'Ordinal AsString incorrect');
// Float
floatValue := TDataValue.FromFloat(45.67);
Assert.AreEqual(FloatToStr(45.67), floatValue.AsString, 'Float AsString incorrect');
// Text
textValue := TDataValue.FromText('Hello');
Assert.AreEqual('Hello', textValue.AsString, 'Text AsString incorrect');
// Timestamp
now := System.Sysutils.Now;
tsValue := TDataValue.FromTimestamp(now);
Assert.AreEqual(DateTimeToStr(now), tsValue.AsString, 'Timestamp AsString incorrect');
// Enum
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
enumValue := colorType.CreateValue('Green');
Assert.AreEqual('Green', enumValue.AsString, 'Enum AsString incorrect');
// Array
intArrayType := TDataType.ArrayOfType(TDataType.Ordinal);
arrayValue := intArrayType.CreateValue([TDataValue.FromOrdinal(1), TDataValue.FromOrdinal(2)]);
Assert.AreEqual('[1, 2]', arrayValue.AsString, 'Array AsString incorrect');
// Record
personType := TDataType.RecordOf([TRecordField.Create('ID', TDataType.Ordinal), TRecordField.Create('Name', TDataType.Text)]);
recordValue := personType.CreateValue([TDataValue.FromOrdinal(1), TDataValue.FromText('Bob')]);
Assert.AreEqual('<ID: 1, Name: Bob>', recordValue.AsString, 'Record AsString incorrect');
// Tuple
tupleValue := TDataValue.FromTuple([TDataValue.FromOrdinal(10), TDataValue.FromText('Tuple')]);
Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect');
end;
initialization
TDUnitX.RegisterTestFixture(TMyTestObject);