diff --git a/Src/Data/Myc.Data.Types.Decimal.pas b/Src/Data/Myc.Data.Types.Decimal.pas new file mode 100644 index 0000000..2f6a275 --- /dev/null +++ b/Src/Data/Myc.Data.Types.Decimal.pas @@ -0,0 +1,127 @@ +unit Myc.Data.Types.Decimal; + +interface + +uses + Myc.Data.Types; + +type + // Concrete implementation of the IDataDecimalType interface. + TImplDataDecimalType = class(TInterfacedObject, IDataDecimalType) + private + FScale: Integer; + public + constructor Create(const AScale: Integer); + + // IDataType + function GetName: String; + function GetKind: TDataKind; + + // IDataDecimalType + function GetScale: Integer; + function CreateValue(const AValue: Int64): IDataDecimalValue; + end; + +implementation + +uses + System.SysUtils, + System.Rtti, + System.Math; + +type + TDataDecimalValue = class(TInterfacedObject, IDataDecimalValue) + private + FDataType: IDataDecimalType; + FValue: Int64; + public + constructor Create(const AValue: Int64; const ADataType: IDataDecimalType); + + // IDataValue + function GetDataType: IDataType; + function GetAsString: string; + function AsTValue: TValue; + + // IDataDecimalValue + function GetValue: Int64; + end; + +{ TImplDataDecimalType } + +constructor TImplDataDecimalType.Create(const AScale: Integer); +begin + inherited Create; + FScale := AScale; +end; + +function TImplDataDecimalType.CreateValue(const AValue: Int64): IDataDecimalValue; +begin + Result := TDataDecimalValue.Create(AValue, Self); +end; + +function TImplDataDecimalType.GetKind: TDataKind; +begin + Result := dkDecimal; +end; + +function TImplDataDecimalType.GetName: String; +begin + Result := Format('Decimal(18, %d)', [FScale]); +end; + +function TImplDataDecimalType.GetScale: Integer; +begin + Result := FScale; +end; + +{ TDataDecimalValue } + +constructor TDataDecimalValue.Create(const AValue: Int64; const ADataType: IDataDecimalType); +begin + inherited Create; + FValue := AValue; + FDataType := ADataType; +end; + +function TDataDecimalValue.AsTValue: TValue; +begin + Result := TValue.From(FValue); +end; + +function TDataDecimalValue.GetAsString: string; +var + s: string; + scale: Integer; + insertPos: Integer; +begin + s := IntToStr(abs(FValue)); + scale := FDataType.Scale; + + if scale > 0 then + begin + if Length(s) <= scale then + s := StringOfChar('0', scale - Length(s) + 1) + s; + + insertPos := Length(s) - scale; + Result := Copy(s, 1, insertPos) + '.' + Copy(s, insertPos + 1, MaxInt); + end + else + begin + Result := s; + end; + + if FValue < 0 then + Result := '-' + Result; +end; + +function TDataDecimalValue.GetDataType: IDataType; +begin + Result := FDataType; +end; + +function TDataDecimalValue.GetValue: Int64; +begin + Result := FValue; +end; + +end. diff --git a/Src/Data/Myc.Data.Types.pas b/Src/Data/Myc.Data.Types.pas index c6bbda1..0bdde0e 100644 --- a/Src/Data/Myc.Data.Types.pas +++ b/Src/Data/Myc.Data.Types.pas @@ -8,8 +8,7 @@ uses System.RTTI; type - // Corrected dfVoid to dkVoid and added void type support - TDataKind = (dkVoid, dkOrdinal, dkFloat, dkText, dkTimestamp, dkRecord, dkTuple, dkArray, dkEnum, dkMethod); + TDataKind = (dkVoid, dkOrdinal, dkFloat, dkText, dkTimestamp, dkDecimal, dkRecord, dkTuple, dkArray, dkEnum, dkMethod); IDataType = interface {$region 'private'} @@ -82,6 +81,23 @@ type function CreateValue(const AValue: TDateTime): IDataTimestampValue; end; + // Represents a fixed-point decimal data value. + IDataDecimalValue = interface(IDataValue) + {$region 'private'} + function GetValue: Int64; + {$endregion} + property Value: Int64 read GetValue; + end; + + // Represents the type of a fixed-point decimal data value. + IDataDecimalType = interface(IDataType) + {$region 'private'} + function GetScale: Integer; + {$endregion} + function CreateValue(const AValue: Int64): IDataDecimalValue; + property Scale: Integer read GetScale; + end; + IDataEnumValue = interface(IDataValue) {$region 'private'} function GetValue: Integer; @@ -190,6 +206,7 @@ type function AsFloat: IDataFloatValue; inline; function AsText: IDataTextValue; inline; function AsTimestamp: IDataTimestampValue; inline; + function AsDecimal: IDataDecimalValue; inline; function AsRecord: IDataRecordValue; inline; function AsTuple: IDataTupleValue; inline; function AsArray: IDataArrayValue; inline; @@ -204,6 +221,7 @@ type class operator Implicit(const A: IDataFloatValue): TDataType.TValue; overload; inline; class operator Implicit(const A: IDataTextValue): TDataType.TValue; overload; inline; class operator Implicit(const A: IDataTimestampValue): TDataType.TValue; overload; inline; + class operator Implicit(const A: IDataDecimalValue): TDataType.TValue; overload; inline; class operator Implicit(const A: IDataEnumValue): TDataType.TValue; overload; inline; class operator Implicit(const A: IDataMethodValue): TDataType.TValue; overload; inline; class operator Implicit(const A: IDataRecordValue): TDataType.TValue; overload; inline; @@ -266,6 +284,33 @@ type function CreateValue(const AValue: TDateTime): IDataTimestampValue; inline; end; + TDecimal = record + type + TValue = record + private + // The wrapped interface instance. + FDecimalValue: IDataDecimalValue; + function GetValue: Int64; inline; + public + constructor Create(const ADecimalValue: IDataDecimalValue); + + class operator Implicit(const A: IDataDecimalValue): TDataType.TDecimal.TValue; overload; inline; + class operator Implicit(const A: TDataType.TDecimal.TValue): IDataDecimalValue; overload; inline; + + property Value: Int64 read GetValue; + end; + + private + FDecimalType: IDataDecimalType; + function GetScale: Integer; inline; + public + constructor Create(const ADecimalType: IDataDecimalType); + class operator Implicit(const A: IDataDecimalType): TDataType.TDecimal; overload; inline; + class operator Implicit(const A: TDataType.TDecimal): IDataDecimalType; overload; inline; + function CreateValue(const AValue: Int64): TDataType.TDecimal.TValue; inline; + property Scale: Integer read GetScale; + end; + TEnum = record private FEnumType: IDataEnumType; @@ -343,6 +388,7 @@ type FArrayTypeRegistry: TDictionary; FRecordTypeRegistry: TDictionary, IDataRecordType>; FMethodTypeRegistry: TDictionary, IDataMethodType>; + FDecimalTypes: TArray; class constructor CreateClass; class destructor DestroyClass; public @@ -355,6 +401,7 @@ type class operator Implicit(const A: TDataType.TFloat): TDataType; overload; inline; class operator Implicit(const A: TDataType.TText): TDataType; overload; inline; class operator Implicit(const A: TDataType.TTimestamp): TDataType; overload; inline; + class operator Implicit(const A: TDataType.TDecimal): TDataType; overload; inline; class operator Implicit(const A: TDataType.TEnum): TDataType; overload; inline; class operator Implicit(const A: TDataType.TMethod): TDataType; overload; inline; class operator Implicit(const A: TDataType.TRecord): TDataType; overload; inline; @@ -367,6 +414,7 @@ type class function Float: TDataType.TFloat; static; inline; class function Text: TDataType.TText; static; inline; class function Timestamp: TDataType.TTimestamp; static; inline; + class function DecimalOf(const AScale: Integer): TDataType.TDecimal; static; 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; @@ -374,25 +422,6 @@ type class function RecordOf(const Fields: array of TRecordField): TDataType.TRecord; overload; static; class function EnumOf(const AName: string; const AIdentifiers: array of string): TDataType.TEnum; static; - // Value factories - class function FromOrdinal(const AValue: Int64): IDataOrdinalValue; static; inline; - class function FromFloat(const AValue: Double): IDataFloatValue; static; inline; - class function FromText(const AValue: string): IDataTextValue; static; inline; - class function FromTimestamp(const AValue: TDateTime): IDataTimestampValue; static; inline; - class function FromTuple(const AItems: array of IDataValue): IDataTupleValue; static; - class function FromMethod(const AMethodType: TDataType.TMethod; const AValue: TMethod.TProc): IDataMethodValue; static; inline; - - // Casting - function AsOrdinal: TDataType.TOrdinal; inline; - function AsFloat: TDataType.TFloat; inline; - function AsText: TDataType.TText; inline; - function AsTimestamp: TDataType.TTimestamp; inline; - function AsRecord: TDataType.TRecord; inline; - function AsTuple: TDataType.TTuple; inline; - function AsArray: TDataType.TArray; inline; - function AsEnum: TDataType.TEnum; inline; - function AsMethod: TDataType.TMethod; inline; - property DataType: IDataType read FDataType; property Name: String read GetName; property Kind: TDataKind read GetKind; @@ -408,6 +437,7 @@ uses Myc.Data.Types.Float, Myc.Data.Types.Text, Myc.Data.Types.Timestamp, + Myc.Data.Types.Decimal, Myc.Data.Types.Arrays, Myc.Data.Types.Records, Myc.Data.Types.Tuple, @@ -422,6 +452,28 @@ begin DataType := ADataType; end; +{ TDataType.TDecimal.TValue } + +constructor TDataType.TDecimal.TValue.Create(const ADecimalValue: IDataDecimalValue); +begin + FDecimalValue := ADecimalValue; +end; + +function TDataType.TDecimal.TValue.GetValue: Int64; +begin + Result := FDecimalValue.Value; +end; + +class operator TDataType.TDecimal.TValue.Implicit(const A: IDataDecimalValue): TDataType.TDecimal.TValue; +begin + Result.FDecimalValue := A; +end; + +class operator TDataType.TDecimal.TValue.Implicit(const A: TDataType.TDecimal.TValue): IDataDecimalValue; +begin + Result := A.FDecimalValue; +end; + { TDataType.TVoid } constructor TDataType.TVoid.Create(const AVoidType: IDataVoidType); @@ -532,6 +584,33 @@ begin Result := A.FTimestampType; end; +{ TDataType.TDecimal } + +constructor TDataType.TDecimal.Create(const ADecimalType: IDataDecimalType); +begin + FDecimalType := ADecimalType; +end; + +function TDataType.TDecimal.CreateValue(const AValue: Int64): TDataType.TDecimal.TValue; +begin + Result.Create(FDecimalType.CreateValue(AValue)); +end; + +function TDataType.TDecimal.GetScale: Integer; +begin + Result := FDecimalType.Scale; +end; + +class operator TDataType.TDecimal.Implicit(const A: IDataDecimalType): TDataType.TDecimal; +begin + Result.FDecimalType := A; +end; + +class operator TDataType.TDecimal.Implicit(const A: TDataType.TDecimal): IDataDecimalType; +begin + Result := A.FDecimalType; +end; + { TDataType.TEnum } constructor TDataType.TEnum.Create(const AEnumType: IDataEnumType); @@ -710,9 +789,16 @@ begin end; class constructor TDataType.CreateClass; +var + i: Integer; begin FArrayTypeRegistry := TDictionary.Create; FRecordTypeRegistry := TDictionary, IDataRecordType>.Create(TRecordFieldComparer.Create); + + SetLength(FDecimalTypes, 19); + for i := 0 to 18 do + FDecimalTypes[i] := TImplDataDecimalType.Create(i); + FMethodTypeRegistry := TDictionary, IDataMethodType>.Create( TEqualityComparer>.Construct( @@ -737,6 +823,7 @@ class destructor TDataType.DestroyClass; begin FArrayTypeRegistry.Free; FRecordTypeRegistry.Free; + FDecimalTypes := nil; FMethodTypeRegistry.Free; end; @@ -760,67 +847,11 @@ begin Result.Create(res); end; -function TDataType.AsArray: TDataType.TArray; +class function TDataType.DecimalOf(const AScale: Integer): TDataType.TDecimal; begin - if (FDataType.Kind <> dkArray) then - raise EInvalidCast.Create('Array expected'); - Result.Create(IDataArrayType(FDataType)); -end; - -function TDataType.AsEnum: TDataType.TEnum; -begin - if (FDataType.Kind <> dkEnum) then - raise EInvalidCast.Create('Enum expected'); - Result.Create(IDataEnumType(FDataType)); -end; - -function TDataType.AsFloat: TDataType.TFloat; -begin - if (FDataType.Kind <> dkFloat) then - raise EInvalidCast.Create('Float expected'); - Result.Create(IDataFloatType(FDataType)); -end; - -function TDataType.AsMethod: TDataType.TMethod; -begin - if (FDataType.Kind <> dkMethod) then - raise EInvalidCast.Create('Method expected'); - Result.Create(IDataMethodType(FDataType)); -end; - -function TDataType.AsOrdinal: TDataType.TOrdinal; -begin - if (FDataType.Kind <> dkOrdinal) then - raise EInvalidCast.Create('Ordinal expected'); - Result.Create(IDataOrdinalType(FDataType)); -end; - -function TDataType.AsRecord: TDataType.TRecord; -begin - if (FDataType.Kind <> dkRecord) then - raise EInvalidCast.Create('Record expected'); - Result.Create(IDataRecordType(FDataType)); -end; - -function TDataType.AsText: TDataType.TText; -begin - if (FDataType.Kind <> dkText) then - raise EInvalidCast.Create('Text expected'); - Result.Create(IDataTextType(FDataType)); -end; - -function TDataType.AsTimestamp: TDataType.TTimestamp; -begin - if (FDataType.Kind <> dkTimestamp) then - raise EInvalidCast.Create('Timestamp expected'); - Result.Create(IDataTimestampType(FDataType)); -end; - -function TDataType.AsTuple: TDataType.TTuple; -begin - if (FDataType.Kind <> dkTuple) then - raise EInvalidCast.Create('Tuple expected'); - Result.Create(IDataTupleType(FDataType)); + if (AScale < 0) or (AScale > 18) then + raise EArgumentException.Create('Scale for decimal must be between 0 and 18.'); + Result.Create(FDecimalTypes[AScale]); end; class function TDataType.EnumOf(const AName: string; const AIdentifiers: array of string): TDataType.TEnum; @@ -833,38 +864,6 @@ begin Result.Create(TImplDataFloatType.Singleton); end; -class function TDataType.FromFloat(const AValue: Double): IDataFloatValue; -begin - Result := TDataType.Float.CreateValue(AValue); -end; - -class function TDataType.FromMethod(const AMethodType: TDataType.TMethod; const AValue: TMethod.TProc): IDataMethodValue; -begin - if not Assigned(AMethodType.FMethodType) then // Check underlying interface - raise EArgumentException.Create('AMethodType'); - Result := AMethodType.CreateValue(AValue); -end; - -class function TDataType.FromOrdinal(const AValue: Int64): IDataOrdinalValue; -begin - Result := TDataType.Ordinal.CreateValue(AValue); -end; - -class function TDataType.FromText(const AValue: string): IDataTextValue; -begin - Result := TDataType.Text.CreateValue(AValue); -end; - -class function TDataType.FromTimestamp(const AValue: TDateTime): IDataTimestampValue; -begin - Result := TDataType.Timestamp.CreateValue(AValue); -end; - -class function TDataType.FromTuple(const AItems: array of IDataValue): IDataTupleValue; -begin - Result := TDataType.Tuple.CreateValue(AItems); -end; - function TDataType.GetKind: TDataKind; begin Result := FDataType.Kind; @@ -965,6 +964,11 @@ begin Result.FDataType := A.FArrayType; end; +class operator TDataType.Implicit(const A: TDataType.TDecimal): TDataType; +begin + Result.FDataType := A.FDecimalType; +end; + class operator TDataType.Implicit(const A: TDataType.TEnum): TDataType; begin Result.FDataType := A.FEnumType; @@ -1025,6 +1029,13 @@ begin Result := IDataArrayValue(FDataValue); end; +function TDataType.TValue.AsDecimal: IDataDecimalValue; +begin + if (FDataValue.DataType.Kind <> dkDecimal) then + raise EInvalidCast.Create('Decimal expected'); + Result := IDataDecimalValue(FDataValue); +end; + function TDataType.TValue.AsEnum: IDataEnumValue; begin if (FDataValue.DataType.Kind <> dkEnum) then @@ -1106,6 +1117,11 @@ begin Result.FDataValue := A; end; +class operator TDataType.TValue.Implicit(const A: IDataDecimalValue): TDataType.TValue; +begin + Result.FDataValue := A; +end; + class operator TDataType.TValue.Implicit(const A: IDataEnumValue): TDataType.TValue; begin Result.FDataValue := A; diff --git a/Src/Data/TestDataTypes.pas b/Src/Data/TestDataTypes.pas index a24f3a0..79bc7c5 100644 --- a/Src/Data/TestDataTypes.pas +++ b/Src/Data/TestDataTypes.pas @@ -86,7 +86,7 @@ begin Assert.AreNotSame(IDataRecordType(personType1), IDataRecordType(otherType), 'Different definitions should result in different types'); // --- 3. Test Value Creation and Access --- - personValue := personType1.CreateValue([TDataType.FromOrdinal(123), TDataType.FromFloat(45.67)]); + personValue := personType1.CreateValue([TDataType.Ordinal.CreateValue(123), TDataType.Float.CreateValue(45.67)]); Assert.IsNotNull(personValue, 'RecordValue should be created'); Assert.AreSame(IDataRecordType(personType1), personValue.DataType, 'Value should have the correct data type'); @@ -111,7 +111,7 @@ begin // Test for wrong number of items during value creation Assert.WillRaise( - procedure begin personType1.CreateValue([TDataType.FromOrdinal(99)]); end, + procedure begin personType1.CreateValue([TDataType.Ordinal.CreateValue(99)]); end, EArgumentException, 'Wrong number of items should raise exception' ); @@ -121,7 +121,7 @@ begin procedure begin // Passing Float instead of Integer for the first item - personType1.CreateValue([TDataType.FromFloat(1.0), TDataType.FromFloat(2.0)]); + personType1.CreateValue([TDataType.Float.CreateValue(1.0), TDataType.Float.CreateValue(2.0)]); end, EArgumentException, 'Wrong item type should raise exception' @@ -142,7 +142,8 @@ begin TRecordField.Create('C', TDataType.Float) ] ); - dataValue := dataType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromText('two'), TDataType.FromFloat(3.0)]); + dataValue := + dataType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Text.CreateValue('two'), TDataType.Float.CreateValue(3.0)]); Assert.IsNotNull(dataValue, 'Generic record value should be created'); Assert.AreEqual(3, dataType.FieldCount, 'Generic record should have 3 fields'); @@ -161,11 +162,11 @@ var begin // This test covers optimized implementations for 1 and 2 elements. // --- 1. Setup: Create some values --- - intValue := TDataType.FromOrdinal(123); - floatValue := TDataType.FromFloat(45.67); + intValue := TDataType.Ordinal.CreateValue(123); + floatValue := TDataType.Float.CreateValue(45.67); // --- 2. Test Value Creation and basic properties --- - tuple1 := TDataType.FromTuple([intValue, floatValue]); + tuple1 := TDataType.Tuple.CreateValue([intValue, floatValue]); Assert.IsNotNull(tuple1, 'Tuple value should be created'); Assert.AreEqual(2, tuple1.ItemCount, 'ItemCount should be on the value'); @@ -174,8 +175,8 @@ begin // --- 3. Test Singleton Type Behavior --- // Create more tuples with different structures - tuple2 := TDataType.FromTuple([TDataType.FromOrdinal(99), TDataType.FromFloat(1.1)]); - tuple3 := TDataType.FromTuple([intValue]); + tuple2 := TDataType.Tuple.CreateValue([TDataType.Ordinal.CreateValue(99), TDataType.Float.CreateValue(1.1)]); + tuple3 := TDataType.Tuple.CreateValue([intValue]); // Access the DataType via the underlying interface type1 := tuple1.DataType; @@ -196,14 +197,14 @@ var begin // Test the generic implementation for tuples with > 5 elements. tupleValue := - TDataType.FromTuple( + TDataType.Tuple.CreateValue( [ - TDataType.FromOrdinal(1), - TDataType.FromOrdinal(2), - TDataType.FromOrdinal(3), - TDataType.FromOrdinal(4), - TDataType.FromOrdinal(5), - TDataType.FromOrdinal(6) + TDataType.Ordinal.CreateValue(1), + TDataType.Ordinal.CreateValue(2), + TDataType.Ordinal.CreateValue(3), + TDataType.Ordinal.CreateValue(4), + TDataType.Ordinal.CreateValue(5), + TDataType.Ordinal.CreateValue(6) ] ); @@ -247,8 +248,8 @@ begin Assert.AreEqual('Array', TDataType(floatArrayType).Name, 'Type name should be Array'); // --- 3. Test Value Creation and Access --- - v1 := TDataType.FromOrdinal(10); - v2 := TDataType.FromOrdinal(20); + v1 := TDataType.Ordinal.CreateValue(10); + v2 := TDataType.Ordinal.CreateValue(20); arrayValue := intArrayType1.CreateValue([v1, v2]); Assert.IsNotNull(arrayValue, 'ArrayValue should be created'); @@ -267,7 +268,7 @@ begin procedure begin // Try to add a float value to an Array - intArrayType1.CreateValue([v1, TDataType.FromFloat(3.14)]); + intArrayType1.CreateValue([v1, TDataType.Float.CreateValue(3.14)]); end, EArgumentException, 'Wrong element type should raise an exception' @@ -290,14 +291,15 @@ begin Assert.AreEqual('[]', empty1.AsString, 'Empty array AsString is incorrect'); // Test optimized path for 1 element - single := arrayType.CreateValue([TDataType.FromOrdinal(42)]); + single := arrayType.CreateValue([TDataType.Ordinal.CreateValue(42)]); Assert.IsNotNull(single, 'Single element array should be created'); Assert.AreEqual(1, single.ElementCount, 'Single element array should have 1 element'); Assert.AreEqual(Int64(42), TDataType.TValue(single.Items[0]).AsOrdinal.Value, 'Single element value is incorrect'); Assert.AreEqual('[42]', single.AsString, 'Single element array AsString is incorrect'); // Test generic path for > 1 elements - generic := arrayType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromOrdinal(2), TDataType.FromOrdinal(3)]); + generic := + arrayType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Ordinal.CreateValue(2), TDataType.Ordinal.CreateValue(3)]); Assert.IsNotNull(generic, 'Generic array should be created'); Assert.AreEqual(3, generic.ElementCount, 'Generic array should have 3 elements'); Assert.AreEqual('[1, 2, 3]', generic.AsString, 'Generic array AsString is incorrect'); @@ -317,22 +319,22 @@ begin Assert.AreEqual(dkText, TDataType(textType).Kind, 'Type kind should be dkText'); // --- 2. Test Value Creation and Access --- - textValue1 := TDataType.FromText('Hello World'); + textValue1 := TDataType.Text.CreateValue('Hello World'); Assert.IsNotNull(textValue1, 'TextValue should be created'); Assert.AreSame(IDataType(textType), textValue1.DataType, 'Value should have the correct data type'); Assert.AreEqual('Hello World', textValue1.Value, 'Value should be ''Hello World'''); // Test another text value - textValue2 := TDataType.FromText('Delphi'); + textValue2 := TDataType.Text.CreateValue('Delphi'); Assert.AreEqual('Delphi', textValue2.Value, 'Value should be ''Delphi'''); // --- 3. Test AsText casting --- - dataValue := TDataType.FromText('Test Text'); + dataValue := TDataType.Text.CreateValue('Test Text'); Assert.AreEqual('Test Text', TDataType.TValue(dataValue).AsText.Value, 'AsText should return correct value'); // Test invalid cast - dataValue := TDataType.FromOrdinal(123); + dataValue := TDataType.Ordinal.CreateValue(123); Assert.WillRaise( procedure begin TDataType.TValue(dataValue).AsText; end, EInvalidCast, @@ -345,8 +347,8 @@ var empty1, empty2: IDataTextValue; begin // Test the singleton implementation for the empty string. - empty1 := TDataType.FromText(''); - empty2 := TDataType.FromText(''); + empty1 := TDataType.Text.CreateValue(''); + empty2 := TDataType.Text.CreateValue(''); Assert.IsNotNull(empty1, 'Empty text value should be created'); Assert.AreSame(empty1, empty2, 'Empty text value should be a singleton'); @@ -359,22 +361,22 @@ var zero1, zero2, nan1, nan2, generic: IDataFloatValue; begin // Test singleton for 0.0 - zero1 := TDataType.FromFloat(0.0); - zero2 := TDataType.FromFloat(0.0); + zero1 := TDataType.Float.CreateValue(0.0); + zero2 := TDataType.Float.CreateValue(0.0); Assert.IsNotNull(zero1, 'Zero float should be created'); Assert.AreSame(zero1, zero2, 'Zero float should be a singleton'); Assert.AreEqual(0.0, zero1.Value, 'Value of zero float is incorrect'); // Test singleton for NaN - nan1 := TDataType.FromFloat(NaN); - nan2 := TDataType.FromFloat(NaN); + nan1 := TDataType.Float.CreateValue(NaN); + nan2 := TDataType.Float.CreateValue(NaN); Assert.IsNotNull(nan1, 'NaN float should be created'); Assert.AreSame(nan1, nan2, 'NaN float should be a singleton'); Assert.IsTrue(IsNaN(nan1.Value), 'Value of NaN float should be NaN'); Assert.AreEqual('NaN', nan1.AsString, 'NaN AsString is incorrect'); // Test generic path - generic := TDataType.FromFloat(123.45); + generic := TDataType.Float.CreateValue(123.45); Assert.IsNotNull(generic, 'Generic float should be created'); Assert.AreNotSame(zero1, generic, 'Generic float should not be the zero singleton'); Assert.AreNotSame(nan1, generic, 'Generic float should not be the NaN singleton'); @@ -397,22 +399,22 @@ begin // --- 2. Test Value Creation and Access --- now := System.Sysutils.Now; - tsValue1 := TDataType.FromTimestamp(now); + tsValue1 := TDataType.Timestamp.CreateValue(now); Assert.IsNotNull(tsValue1, 'TimestampValue should be created'); Assert.AreSame(IDataType(tsType), tsValue1.DataType, 'Value should have the correct data type'); Assert.AreEqual(now, tsValue1.Value, 'Value should be the same'); // Test another text value - tsValue2 := TDataType.FromTimestamp(Date); + tsValue2 := TDataType.Timestamp.CreateValue(Date); Assert.AreEqual(Date, tsValue2.Value, 'Value should be the same'); // --- 3. Test AsTimestamp casting --- - dataValue := TDataType.FromTimestamp(now); + dataValue := TDataType.Timestamp.CreateValue(now); Assert.AreEqual(now, TDataType.TValue(dataValue).AsTimestamp.Value, 'AsTimestamp should return correct value'); // Test invalid cast - dataValue := TDataType.FromOrdinal(123); + dataValue := TDataType.Ordinal.CreateValue(123); Assert.WillRaise( procedure begin TDataType.TValue(dataValue).AsTimestamp; end, EInvalidCast, @@ -491,20 +493,20 @@ var now: TDateTime; begin // Ordinal - ordinalValue := TDataType.FromOrdinal(123); + ordinalValue := TDataType.Ordinal.CreateValue(123); Assert.AreEqual('123', ordinalValue.AsString, 'Ordinal AsString incorrect'); // Float - floatValue := TDataType.FromFloat(45.67); + floatValue := TDataType.Float.CreateValue(45.67); Assert.AreEqual(FloatToStr(45.67), floatValue.AsString, 'Float AsString incorrect'); // Text - textValue := TDataType.FromText('Hello'); + textValue := TDataType.Text.CreateValue('Hello'); Assert.AreEqual('Hello', textValue.AsString, 'Text AsString incorrect'); // Timestamp now := System.Sysutils.Now; - tsValue := TDataType.FromTimestamp(now); + tsValue := TDataType.Timestamp.CreateValue(now); Assert.AreEqual(DateTimeToStr(now), tsValue.AsString, 'Timestamp AsString incorrect'); // Enum @@ -514,16 +516,16 @@ begin // Array intArrayType := TDataType.ArrayOf(TDataType.Ordinal); - arrayValue := intArrayType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromOrdinal(2)]); + arrayValue := intArrayType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Ordinal.CreateValue(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([TDataType.FromOrdinal(1), TDataType.FromText('Bob')]); + recordValue := personType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Text.CreateValue('Bob')]); Assert.AreEqual('', recordValue.AsString, 'Record AsString incorrect'); // Tuple - tupleValue := TDataType.FromTuple([TDataType.FromOrdinal(10), TDataType.FromText('Tuple')]); + tupleValue := TDataType.Tuple.CreateValue([TDataType.Ordinal.CreateValue(10), TDataType.Text.CreateValue('Tuple')]); Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect'); end; @@ -537,21 +539,21 @@ var personType: TDataType.TRecord; begin // --- Ordinal --- - dataValue := TDataType.FromOrdinal(123); + dataValue := TDataType.Ordinal.CreateValue(123); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty'); Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64'); Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect'); // --- Float --- - dataValue := TDataType.FromFloat(45.67); + dataValue := TDataType.Float.CreateValue(45.67); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty'); Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat'); Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect'); // --- Text --- - dataValue := TDataType.FromText('Hello'); + dataValue := TDataType.Text.CreateValue('Hello'); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty'); Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString'); @@ -559,7 +561,7 @@ begin // --- Timestamp --- now := System.SysUtils.Now; - dataValue := TDataType.FromTimestamp(now); + dataValue := TDataType.Timestamp.CreateValue(now); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty'); Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect'); @@ -575,7 +577,7 @@ begin // --- Array --- intArrayType := TDataType.ArrayOf(TDataType.Ordinal); - dataValue := intArrayType.CreateValue([TDataType.FromOrdinal(10), TDataType.FromOrdinal(20)]); + dataValue := intArrayType.CreateValue([TDataType.Ordinal.CreateValue(10), TDataType.Ordinal.CreateValue(20)]); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Array TValue should not be empty'); Assert.IsTrue(tv.IsArray, 'Array TValue should be an array'); @@ -598,7 +600,7 @@ begin // --- Record --- personType := TDataType.RecordOf([TRecordField.Create('ID', TDataType.Ordinal), TRecordField.Create('Name', TDataType.Text)]); - dataValue := personType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromText('Bob')]); + dataValue := personType.CreateValue([TDataType.Ordinal.CreateValue(1), TDataType.Text.CreateValue('Bob')]); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Record TValue should not be empty'); Assert.IsTrue(tv.IsArray, 'Record TValue should be represented as an array'); @@ -608,7 +610,7 @@ begin Assert.AreEqual('Bob', tv.GetArrayElement(1).AsType.AsString, 'Record TValue element 1 is incorrect'); // --- Tuple --- - dataValue := TDataType.FromTuple([TDataType.FromOrdinal(99), TDataType.FromText('Red')]); + dataValue := TDataType.Tuple.CreateValue([TDataType.Ordinal.CreateValue(99), TDataType.Text.CreateValue('Red')]); tv := dataValue.AsTValue; Assert.IsFalse(tv.IsEmpty, 'Tuple TValue should not be empty'); Assert.IsTrue(tv.IsArray, 'Tuple TValue should be represented as an array'); @@ -667,15 +669,15 @@ begin begin ordinalValue := AValue.AsOrdinal; val := ordinalValue.Value; - Result := TDataType.FromText(val.ToString); + Result := TDataType.Text.CreateValue(val.ToString); end; - methodValue := TDataType.FromMethod(methodType1, myFunc); + methodValue := methodType1.CreateValue(myFunc); Assert.IsNotNull(methodValue, 'MethodValue should be created'); Assert.AreSame(IDataType(methodType1), methodValue.DataType, 'Value should have the correct data type'); // --- 4. Test Execution (Simulated) --- - inputValue := TDataType.FromOrdinal(42); + inputValue := TDataType.Ordinal.CreateValue(42); // Execute the function retrieved from the value resultValue := methodValue.Value(inputValue); @@ -692,18 +694,11 @@ begin Assert.IsTrue(TypeInfo(TDataMethodProc) = tv.TypeInfo, 'TValue should hold TMethodProc type info'); // --- 6. Test Validation and Error Handling --- - // Test creating a value with a nil type - Assert.WillRaise( - procedure begin TDataType.FromMethod(nil, myFunc); end, - EArgumentException, - 'FromMethod with nil type should raise an exception' - ); - // Test creating a value with a nil function Assert.WillRaise( - procedure begin TDataType.FromMethod(methodType1, nil); end, + procedure begin methodType1.CreateValue(nil); end, EArgumentException, - 'FromMethod with nil function should raise an exception' + 'CreateValue with nil function should raise an exception' ); end; diff --git a/Src/Myc.Trade.Indicators.Common.pas b/Src/Myc.Trade.Indicators.Common.pas index 71999cf..206e700 100644 --- a/Src/Myc.Trade.Indicators.Common.pas +++ b/Src/Myc.Trade.Indicators.Common.pas @@ -367,7 +367,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(SMA(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(SMA(Args.AsFloat.Value)); end ); end @@ -466,7 +466,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(EMA(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(EMA(Args.AsFloat.Value)); end ); end @@ -549,7 +549,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(WMA(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(WMA(Args.AsFloat.Value)); end ); end @@ -644,7 +644,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(HMA(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(HMA(Args.AsFloat.Value)); end ); end @@ -712,7 +712,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(RSI(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(RSI(Args.AsFloat.Value)); end ); end @@ -837,9 +837,9 @@ begin Result := resultType.CreateValue( [ - TDataType.FromFloat(res.MacdLine), - TDataType.FromFloat(res.SignalLine), - TDataType.FromFloat(res.Histogram) + TDataType.Float.CreateValue(res.MacdLine), + TDataType.Float.CreateValue(res.SignalLine), + TDataType.Float.CreateValue(res.Histogram) ] ); end @@ -1011,7 +1011,7 @@ begin var res := Stochastic(ohlcVal); - Result := resultType.CreateValue([TDataType.FromFloat(res.K), TDataType.FromFloat(res.D)]); + Result := resultType.CreateValue([TDataType.Float.CreateValue(res.K), TDataType.Float.CreateValue(res.D)]); end ); end @@ -1143,7 +1143,7 @@ begin indicatorMethodType.CreateValue( function(const Args: TDataType.TValue): TDataType.TValue begin - Result := TDataType.FromFloat(StdDev(Args.AsFloat.Value)); + Result := TDataType.Float.CreateValue(StdDev(Args.AsFloat.Value)); end ); end @@ -1253,9 +1253,9 @@ begin Result := resultType.CreateValue( [ - TDataType.FromFloat(res.UpperBand), - TDataType.FromFloat(res.MiddleBand), - TDataType.FromFloat(res.LowerBand) + TDataType.Float.CreateValue(res.UpperBand), + TDataType.Float.CreateValue(res.MiddleBand), + TDataType.Float.CreateValue(res.LowerBand) ] ); end @@ -1348,7 +1348,7 @@ begin ohlcVal.Low := TDataType.TValue(ohlcRec.Items[2]).AsFloat.Value; ohlcVal.Close := TDataType.TValue(ohlcRec.Items[3]).AsFloat.Value; ohlcVal.Volume := TDataType.TValue(ohlcRec.Items[4]).AsFloat.Value; - Result := TDataType.FromFloat(ATR(ohlcVal)); + Result := TDataType.Float.CreateValue(ATR(ohlcVal)); end ); end @@ -1460,9 +1460,9 @@ begin Result := resultType.CreateValue( [ - TDataType.FromFloat(res.UpperBand), - TDataType.FromFloat(res.MiddleBand), - TDataType.FromFloat(res.LowerBand) + TDataType.Float.CreateValue(res.UpperBand), + TDataType.Float.CreateValue(res.MiddleBand), + TDataType.Float.CreateValue(res.LowerBand) ] ); end @@ -1550,7 +1550,7 @@ begin for i := 0 to valuesArray.ElementCount - 1 do values[i] := TDataType.TValue(valuesArray.Items[i]).AsFloat.Value; - Result := TDataType.FromFloat(Mean(values)); + Result := TDataType.Float.CreateValue(Mean(values)); end ); end diff --git a/Src/Myc.Trade.Indicators_v2.pas b/Src/Myc.Trade.Indicators_v2.pas index 68c1e27..0f7a875 100644 --- a/Src/Myc.Trade.Indicators_v2.pas +++ b/Src/Myc.Trade.Indicators_v2.pas @@ -218,9 +218,9 @@ begin end; case aValue.Kind of - tkInteger, tkInt64, tkEnumeration: Result := TDataType.FromOrdinal(aValue.AsInt64); - tkFloat: Result := TDataType.FromFloat(aValue.AsExtended); - tkString, tkUString: Result := TDataType.FromText(aValue.AsString); + tkInteger, tkInt64, tkEnumeration: Result := TDataType.Ordinal.CreateValue(aValue.AsInt64); + tkFloat: Result := TDataType.Float.CreateValue(aValue.AsExtended); + tkString, tkUString: Result := TDataType.Text.CreateValue(aValue.AsString); tkRecord: begin ctx := TRttiContext.Create; diff --git a/Test/MycTests.dpr b/Test/MycTests.dpr index b6e966b..17b0903 100644 --- a/Test/MycTests.dpr +++ b/Test/MycTests.dpr @@ -32,7 +32,8 @@ uses Myc.Data.Types.Arrays in 'Myc.Data.Types.Arrays.pas', TestDataTypes in '..\Src\Data\TestDataTypes.pas', Myc.Data.Types.Method in '..\Src\Data\Myc.Data.Types.Method.pas', - Myc.Data.Types.Void in '..\Src\Data\Myc.Data.Types.Void.pas'; + Myc.Data.Types.Void in '..\Src\Data\Myc.Data.Types.Void.pas', + Myc.Data.Types.Decimal in '..\Src\Data\Myc.Data.Types.Decimal.pas'; { keep comment here to protect the following conditional from being removed by the IDE when adding a unit } {$IFNDEF TESTINSIGHT} diff --git a/Test/MycTests.dproj b/Test/MycTests.dproj index a642c05..fa420c4 100644 --- a/Test/MycTests.dproj +++ b/Test/MycTests.dproj @@ -134,6 +134,7 @@ $(PreBuildEvent)]]> + Base