Keywords as basic scalar type

This commit is contained in:
Michael Schimmel
2025-10-31 21:15:08 +01:00
parent 8abec8e98f
commit 689dede600
9 changed files with 292 additions and 264 deletions
+129 -132
View File
@@ -6,6 +6,7 @@ uses
System.SysUtils,
System.Generics.Collections,
System.Generics.Defaults,
Myc.Utils,
Myc.Data.Decimal,
Myc.Data.Series,
Myc.Data.Keyword;
@@ -17,11 +18,13 @@ type
TScalar = record
public
type
TKind = (Ordinal, Float);
// Defines the underlying storage kinds for scalar values
TKind = (Ordinal, Float, Keyword);
// The 8-byte storage for the scalar value
TValue = record
case TKind of
TKind.Ordinal: (AsInt64: Int64);
TKind.Ordinal, TKind.Keyword: (AsInt64: Int64); // Ordinal and Keyword index
TKind.Float: (AsDouble: Double);
end;
@@ -51,10 +54,12 @@ type
// Factory methods for core types.
class function FromInt64(AValue: Int64): TScalar; static; inline;
class function FromDouble(AValue: Double): TScalar; static; inline;
class function FromKeyword(const AValue: IKeyword): TScalar; static; inline;
// Implicit casts for core types.
class operator Implicit(AValue: Int64): TScalar; overload; inline;
class operator Implicit(AValue: Double): TScalar; overload; inline;
class operator Implicit(const AValue: IKeyword): TScalar; overload; inline;
class operator Implicit(const A: TScalar): Double; overload;
class operator Implicit(const A: TScalar): Int64; overload;
@@ -159,19 +164,17 @@ type
// A time series of scalar records, optimized for memory and access speed.
TScalarRecordSeries = class(TInterfacedObject, IRecordSeries)
type
TMemberSeries = class(TObject, ISeries)
TMemberSeries = class(TGenericContainedObject<TScalarRecordSeries>, ISeries)
private
FRecordSeries: TScalarRecordSeries;
FKind: TScalar.TKind;
FOffset: Integer;
function GetCount: Int64;
function GetItems(Idx: Integer): TScalar;
function GetRecordSeries: TScalarRecordSeries; inline;
function GetTotalCount: Int64;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
constructor Create(ARecordSeries: TScalarRecordSeries; AElementIdx: Integer);
property RecordSeries: TScalarRecordSeries read GetRecordSeries;
end;
private
FDef: IScalarRecordDefinition;
@@ -219,6 +222,20 @@ begin
Result.Value.AsDouble := AValue;
end;
class function TScalar.FromInt64(AValue: Int64): TScalar;
begin
Result.Kind := TKind.Ordinal;
Result.Value.AsInt64 := AValue;
end;
class function TScalar.FromKeyword(const AValue: IKeyword): TScalar;
begin
Result.Kind := TKind.Keyword;
if not Assigned(AValue) then
raise EArgumentException.Create('Keyword must not be nil.');
Result.Value.AsInt64 := AValue.Idx
end;
class operator TScalar.Implicit(AValue: Double): TScalar;
begin
Result.Kind := TKind.Float;
@@ -231,39 +248,58 @@ begin
Result.Value.AsInt64 := AValue;
end;
class operator TScalar.Implicit(const AValue: IKeyword): TScalar;
begin
Result.Kind := TKind.Keyword;
if not Assigned(AValue) then
raise EArgumentException.Create('Keyword must not be nil.');
Result.Value.AsInt64 := AValue.Idx
end;
class operator TScalar.Implicit(const A: TScalar): Double;
begin
if A.Kind = TKind.Ordinal then
Result := A.Value.AsInt64
case A.Kind of
TKind.Ordinal: Result := A.Value.AsInt64;
TKind.Float: Result := A.Value.AsDouble;
else
Result := A.Value.AsDouble;
raise EInvalidCast.Create('Cannot implicitly convert Keyword to Double');
end;
end;
class operator TScalar.Implicit(const A: TScalar): Int64;
begin
if A.Kind = TKind.Ordinal then
Result := A.Value.AsInt64
case A.Kind of
TKind.Ordinal: Result := A.Value.AsInt64;
else
raise EInvalidCast.Create('Cannot implicitly convert a Float to an Int64');
end;
class function TScalar.FromInt64(AValue: Int64): TScalar;
begin
Result.Kind := TKind.Ordinal;
Result.Value.AsInt64 := AValue;
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to Int64', [A.Kind.ToString]);
end;
end;
class function TScalar.IsBinaryOperatorSupported(Op: TBinaryOp; A, B: TKind): Boolean;
begin
// Deny all operations if either operand is Keyword...
if (A = TKind.Keyword) or (B = TKind.Keyword) then
begin
// ...except for Equality checks
Result := (Op in [TBinaryOp.Equal, TBinaryOp.NotEqual]);
exit;
end;
// Default for Ordinal/Float
Result := True;
end;
class function TScalar.IsUnaryOperatorSupported(Op: TUnaryOp; A: TKind): Boolean;
begin
// Deny all unary ops for Keywords
if (A = TKind.Keyword) then
exit(False);
// Deny 'not' for Float
if (Op = TUnaryOp.Not) and (A = TKind.Float) then
Result := False
else
Result := True;
exit(False);
Result := True;
end;
class function TScalar.StringToKind(const AName: string): TKind;
@@ -272,6 +308,8 @@ begin
Result := TKind.Ordinal
else if SameText(AName, 'Float') then
Result := TKind.Float
else if SameText(AName, 'Keyword') then
Result := TKind.Keyword
else
raise EArgumentException.CreateFmt('Unknown scalar type name: "%s"', [AName]);
end;
@@ -281,6 +319,7 @@ begin
case Kind of
TKind.Ordinal: Result := IntToStr(Value.AsInt64);
TKind.Float: Result := FloatToStr(Value.AsDouble);
TKind.Keyword: Result := TKeywordRegistry.GetName(Value.AsInt64);
else
Result := '[Unknown Scalar]';
end;
@@ -288,6 +327,9 @@ end;
class function TScalar.TryBinaryOperation(Op: TBinaryOp; const A, B: TScalar; out Res: TScalar): Boolean;
begin
if not IsBinaryOperatorSupported(Op, A.Kind, B.Kind) then
exit(False);
try
case Op of
TBinaryOp.Add: Res := A + B;
@@ -313,10 +355,7 @@ end;
class function TScalar.TryUnaryOperation(Op: TUnaryOp; const A: TScalar; out Res: TScalar): Boolean;
begin
if not IsUnaryOperatorSupported(Op, A.Kind) then
begin
Result := False;
exit;
end;
exit(False);
try
case Op of
TUnaryOp.Negate: Res := -A;
@@ -335,51 +374,40 @@ class operator TScalar.Add(const A, B: TScalar): TScalar;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 + B.Value.AsInt64
else
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A; // Use implicit cast
valB := B; // Use implicit cast
Result := valA + valB;
end;
end
else
raise EArgumentException.CreateFmt('Operator Add not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
end;
class operator TScalar.Divide(const A, B: TScalar): TScalar;
begin
// Division *always* promotes to Float, unless types are invalid
if (A.Kind = TKind.Keyword) or (B.Kind = TKind.Keyword) then
raise EArgumentException.CreateFmt('Operator Divide not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A; // Use implicit cast
valB := B; // Use implicit cast
Result := valA / valB;
end;
class operator TScalar.Equal(const A, B: TScalar): Boolean;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 = B.Value.AsInt64
// Must be same kind to be equal (e.g., Ordinal(5) <> Keyword(5))
if A.Kind <> B.Kind then
exit(False);
case A.Kind of
TKind.Ordinal, TKind.Keyword: Result := A.Value.AsInt64 = B.Value.AsInt64;
TKind.Float: Result := A.Value.AsDouble = B.Value.AsDouble;
else
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
Result := valA = valB;
Result := False;
end;
end;
@@ -387,19 +415,15 @@ class operator TScalar.GreaterThan(const A, B: TScalar): Boolean;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 > B.Value.AsInt64
else
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A;
valB := B;
Result := valA > valB;
end;
end
else
raise EArgumentException.CreateFmt('Operator GreaterThan not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
end;
class operator TScalar.GreaterThanOrEqual(const A, B: TScalar): Boolean;
@@ -411,19 +435,15 @@ class operator TScalar.LessThan(const A, B: TScalar): Boolean;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 < B.Value.AsInt64
else
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A;
valB := B;
Result := valA < valB;
end;
end
else
raise EArgumentException.CreateFmt('Operator LessThan not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
end;
class operator TScalar.LessThanOrEqual(const A, B: TScalar): Boolean;
@@ -434,7 +454,7 @@ end;
class operator TScalar.LogicalNot(const A: TScalar): TScalar;
begin
if A.Kind <> TKind.Ordinal then
raise EArgumentException.Create('Operator Not not supported for type Float');
raise EArgumentException.CreateFmt('Operator Not not supported for type %s', [A.Kind.ToString]);
if A.Value.AsInt64 = 0 then
Result := 1
@@ -446,19 +466,15 @@ class operator TScalar.Multiply(const A, B: TScalar): TScalar;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 * B.Value.AsInt64
else
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A;
valB := B;
Result := valA * valB;
end;
end
else
raise EArgumentException.CreateFmt('Operator Multiply not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
end;
class operator TScalar.Negative(const A: TScalar): TScalar;
@@ -467,6 +483,8 @@ begin
case A.Kind of
TKind.Ordinal: Result.Value.AsInt64 := -A.Value.AsInt64;
TKind.Float: Result.Value.AsDouble := -A.Value.AsDouble;
else
raise EArgumentException.CreateFmt('Operator Negative not supported for type %s', [A.Kind.ToString]);
end;
end;
@@ -478,10 +496,7 @@ end;
class operator TScalar.Round(const A: TScalar): TScalar;
begin
var val: Double;
if A.Kind = TKind.Ordinal then
val := A.Value.AsInt64
else
val := A.Value.AsDouble;
val := A; // Implicit cast handles Ordinal or Float
Result := System.Round(val);
end;
@@ -489,28 +504,21 @@ class operator TScalar.Subtract(const A, B: TScalar): TScalar;
begin
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
Result := A.Value.AsInt64 - B.Value.AsInt64
else
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
begin
var valA, valB: Double;
if A.Kind = TKind.Ordinal then
valA := A.Value.AsInt64
else
valA := A.Value.AsDouble;
if B.Kind = TKind.Ordinal then
valB := B.Value.AsInt64
else
valB := B.Value.AsDouble;
valA := A;
valB := B;
Result := valA - valB;
end;
end
else
raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
end;
class operator TScalar.Trunc(const A: TScalar): TScalar;
begin
var val: Double;
if A.Kind = TKind.Ordinal then
val := A.Value.AsInt64
else
val := A.Value.AsDouble;
val := A; // Implicit cast handles Ordinal or Float
Result := System.Trunc(val);
end;
@@ -607,17 +615,20 @@ begin
Result := FTotalCount;
end;
{ TScalar.TKindHelper }
function TScalar.TKindHelper.ToString: string;
begin
case Self of
TScalar.TKind.Ordinal: Result := 'Ordinal';
TScalar.TKind.Float: Result := 'Float';
TScalar.TKind.Keyword: Result := 'Keyword';
else
Result := 'unknown';
end;
end;
{ TScalar_TBinaryOpHelper }
{ TScalar.TBinaryOpHelper }
function TScalar.TBinaryOpHelper.ToString: string;
begin
@@ -637,7 +648,7 @@ begin
end;
end;
{ TScalar_TUnaryOpHelper }
{ TScalar.TUnaryOpHelper }
function TScalar.TUnaryOpHelper.ToString: string;
begin
@@ -653,47 +664,33 @@ end;
constructor TScalarRecordSeries.TMemberSeries.Create(ARecordSeries: TScalarRecordSeries; AElementIdx: Integer);
begin
inherited Create;
FRecordSeries := ARecordSeries;
FKind := FRecordSeries.FDef.Fields[AElementIdx].Value;
inherited Create(ARecordSeries);
FKind := ARecordSeries.FDef.Fields[AElementIdx].Value;
FOffset := sizeof(TScalar.TValue) * AElementIdx;
end;
function TScalarRecordSeries.TMemberSeries.GetCount: Int64;
begin
Result := FRecordSeries.Count;
Result := RecordSeries.Count;
end;
function TScalarRecordSeries.TMemberSeries.GetItems(Idx: Integer): TScalar;
var
P: TChunkArray<TScalar.TValue>.PT;
begin
P := FRecordSeries.GetItemRef(Idx);
P := RecordSeries.GetItemRef(Idx);
inc(PByte(P), FOffset);
Result.Create(FKind, P^);
end;
function TScalarRecordSeries.TMemberSeries.GetRecordSeries: TScalarRecordSeries;
begin
Result := TScalarRecordSeries(Controller);
end;
function TScalarRecordSeries.TMemberSeries.GetTotalCount: Int64;
begin
Result := FRecordSeries.TotalCount;
end;
function TScalarRecordSeries.TMemberSeries.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TScalarRecordSeries.TMemberSeries._AddRef: Integer;
begin
Result := FRecordSeries._AddRef;
end;
function TScalarRecordSeries.TMemberSeries._Release: Integer;
begin
Result := FRecordSeries._Release;
Result := RecordSeries.TotalCount;
end;
{ TScalarSeries }