AST testing
This commit is contained in:
+227
-159
@@ -3,6 +3,7 @@ unit Myc.Ast.RTL.Core;
|
||||
interface
|
||||
|
||||
uses
|
||||
system.sysutils,
|
||||
Myc.Utils,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Data.Value,
|
||||
@@ -12,74 +13,84 @@ type
|
||||
// Contains the "pure" native implementations.
|
||||
TRtlFunctions = record
|
||||
public
|
||||
// (* --- Dynamic Fallbacks --- *)
|
||||
// (* --- Dynamic Fallbacks (Interpreter) --- *)
|
||||
|
||||
// Arithmetic
|
||||
[TRtlExport('+')]
|
||||
class function Add(const Args: TArray<TDataValue>): TDataValue; overload; static;
|
||||
|
||||
[TRtlExport('-')]
|
||||
class function Subtract(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('*')]
|
||||
class function Multiply(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('/')]
|
||||
class function Divide(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('div')]
|
||||
class function IntDivide(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('mod')]
|
||||
class function Modulus(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
// Comparison
|
||||
[TRtlExport('=')]
|
||||
class function Equal(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('<>')]
|
||||
class function NotEqual(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('<')]
|
||||
class function LessThan(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('<=')]
|
||||
class function LessThanOrEqual(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('>')]
|
||||
class function GreaterThan(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('>=')]
|
||||
class function GreaterThanOrEqual(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
// Logic / Bitwise
|
||||
[TRtlExport('not')]
|
||||
class function LogicalNot(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('and')]
|
||||
class function BitwiseAnd(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('or')]
|
||||
class function BitwiseOr(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('xor')]
|
||||
class function BitwiseXor(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('shl')]
|
||||
class function LeftShift(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('shr')]
|
||||
class function RightShift(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
// (* Dynamic fallbacks for TScalar input *)
|
||||
[TRtlExport('Abs')]
|
||||
class function Abs(const Arg: TScalar): TScalar; static;
|
||||
|
||||
[TRtlExport('Round')]
|
||||
class function Round(const Arg: TScalar): TScalar; static; // <-- Added
|
||||
[TRtlExport('Trunc')]
|
||||
class function Trunc(const Arg: TScalar): TScalar; static;
|
||||
|
||||
[TRtlExport('Ceil')]
|
||||
class function Ceil(const Arg: TScalar): TScalar; static;
|
||||
|
||||
[TRtlExport('Floor')]
|
||||
class function Floor(const Arg: TScalar): TScalar; static;
|
||||
|
||||
[TRtlExport('Sign')]
|
||||
class function Sign(const Arg: TScalar): TScalar; static;
|
||||
|
||||
// (* DateTime Constructors *)
|
||||
[TRtlExport('Now', False)]
|
||||
class function Now(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
[TRtlExport('Date')]
|
||||
class function Date(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
// (* Other dynamic functions *)
|
||||
[TRtlExport('Memoize')]
|
||||
class function Memoize(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('Map')]
|
||||
class function Map(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('Reduce')]
|
||||
class function Reduce(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('Where')]
|
||||
class function Where(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
[TRtlExport('Any')]
|
||||
class function Any(const Args: TArray<TDataValue>): TDataValue; static;
|
||||
|
||||
// (* --- Static Specializations (for Monomorphization) --- *)
|
||||
// (* Schema: FunctionName_Arg1_ArgN_Return *)
|
||||
// (* --- Static Specializations (Monomorphization Targets) --- *)
|
||||
|
||||
// Add
|
||||
[TRtlExport('+', True)]
|
||||
@@ -111,7 +122,7 @@ type
|
||||
[TRtlExport('*', True)]
|
||||
class function Multiply_Float_Ordinal_Float(A: Double; B: Int64): Double; static;
|
||||
|
||||
// Divide (NOT pure due to DivByZero)
|
||||
// Divide (Impure due to EDivByZero potential)
|
||||
[TRtlExport('/')]
|
||||
class function Divide_Ordinal_Ordinal_Float(A, B: Int64): Double; static;
|
||||
[TRtlExport('/')]
|
||||
@@ -121,7 +132,13 @@ type
|
||||
[TRtlExport('/')]
|
||||
class function Divide_Float_Ordinal_Float(A: Double; B: Int64): Double; static;
|
||||
|
||||
// Comparisons (Return Ordinal)
|
||||
// Integer Math (Impure due to EDivByZero)
|
||||
[TRtlExport('div')]
|
||||
class function Div_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('mod')]
|
||||
class function Mod_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
|
||||
// Comparisons
|
||||
[TRtlExport('=', True)]
|
||||
class function Equal_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('=', True)]
|
||||
@@ -180,6 +197,18 @@ type
|
||||
[TRtlExport('>=', True)]
|
||||
class function GreaterOrEqual_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64; static;
|
||||
|
||||
// Bitwise Static
|
||||
[TRtlExport('and', True)]
|
||||
class function And_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('or', True)]
|
||||
class function Or_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('xor', True)]
|
||||
class function Xor_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('shl', True)]
|
||||
class function Shl_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
[TRtlExport('shr', True)]
|
||||
class function Shr_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64; static;
|
||||
|
||||
// Unary
|
||||
[TRtlExport('-', True)]
|
||||
class function Negate_Ordinal_Ordinal(A: Int64): Int64; static;
|
||||
@@ -195,6 +224,11 @@ type
|
||||
[TRtlExport('Abs', True)]
|
||||
class function Abs_Float_Float(A: Double): Double; static;
|
||||
|
||||
[TRtlExport('Round', True)]
|
||||
class function Round_Ordinal_Ordinal(A: Int64): Int64; static; // <-- Added
|
||||
[TRtlExport('Round', True)]
|
||||
class function Round_Float_Ordinal(A: Double): Int64; static; // <-- Added
|
||||
|
||||
[TRtlExport('Trunc', True)]
|
||||
class function Trunc_Ordinal_Ordinal(A: Int64): Int64; static;
|
||||
[TRtlExport('Trunc', True)]
|
||||
@@ -204,140 +238,143 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
system.generics.collections,
|
||||
system.math,
|
||||
system.dateutils,
|
||||
Myc.Data.Decimal;
|
||||
|
||||
{ TRtlFunctions - Operator Implementations }
|
||||
|
||||
class function TRtlFunctions.Add(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator + requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Add, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator +.');
|
||||
Result := res;
|
||||
Result := Args[0].AsScalar + Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Subtract(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) = 1 then // Unary negation
|
||||
begin
|
||||
if not TScalar.TryUnaryOperation(TScalar.TUnaryOp.Negate, Args[0], res) then
|
||||
raise EArgumentException.Create('Invalid argument for unary operator -.');
|
||||
end
|
||||
else if Length(Args) = 2 then // Binary subtraction
|
||||
begin
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Subtract, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for binary operator -.');
|
||||
end
|
||||
if Length(Args) = 1 then
|
||||
Result := -Args[0].AsScalar
|
||||
else if Length(Args) = 2 then
|
||||
Result := Args[0].AsScalar - Args[1].AsScalar
|
||||
else
|
||||
raise EArgumentException.Create('Operator - requires 1 or 2 arguments.');
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Multiply(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator * requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Multiply, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator *.');
|
||||
Result := res;
|
||||
Result := Args[0].AsScalar * Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Divide(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator / requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Divide, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator /.');
|
||||
Result := res;
|
||||
// Explicit check is handled in TScalar.Divide
|
||||
Result := Args[0].AsScalar / Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.IntDivide(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator div requires 2 arguments.');
|
||||
Result := Args[0].AsScalar div Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Modulus(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator mod requires 2 arguments.');
|
||||
Result := Args[0].AsScalar mod Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Equal(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator = requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Equal, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator =.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar = Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.NotEqual(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator <> requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.NotEqual, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator <>.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar <> Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LessThan(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator < requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Less, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator <.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar < Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LessThanOrEqual(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator <= requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.LessOrEqual, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator <=.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar <= Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.GreaterThan(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator > requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.Greater, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator >.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar > Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.GreaterThanOrEqual(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator >= requires 2 arguments.');
|
||||
if not TScalar.TryBinaryOperation(TScalar.TBinaryOp.GreaterOrEqual, Args[0], Args[1], res) then
|
||||
raise EArgumentException.Create('Invalid arguments for operator >=.');
|
||||
Result := res;
|
||||
Result := TScalar.FromBoolean(Args[0].AsScalar >= Args[1].AsScalar);
|
||||
end;
|
||||
|
||||
// --- Logic / Bitwise ---
|
||||
|
||||
class function TRtlFunctions.LogicalNot(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
res: TScalar;
|
||||
begin
|
||||
if Length(Args) <> 1 then
|
||||
raise EArgumentException.Create('Operator not requires 1 argument.');
|
||||
if not TScalar.TryUnaryOperation(TScalar.TUnaryOp.Not, Args[0], res) then
|
||||
raise EArgumentException.Create('Invalid argument for operator not.');
|
||||
Result := res;
|
||||
Result := not Args[0].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.BitwiseAnd(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator and requires 2 arguments.');
|
||||
Result := Args[0].AsScalar and Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.BitwiseOr(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator or requires 2 arguments.');
|
||||
Result := Args[0].AsScalar or Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.BitwiseXor(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator xor requires 2 arguments.');
|
||||
Result := Args[0].AsScalar xor Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LeftShift(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator shl requires 2 arguments.');
|
||||
Result := Args[0].AsScalar shl Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.RightShift(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Operator shr requires 2 arguments.');
|
||||
Result := Args[0].AsScalar shr Args[1].AsScalar;
|
||||
end;
|
||||
|
||||
{ TRtlFunctions - Standard Functions }
|
||||
@@ -352,20 +389,22 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Round(const Arg: TScalar): TScalar;
|
||||
begin
|
||||
var val: Double := Arg;
|
||||
Result := TScalar.FromInt64(System.Round(val));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Trunc(const Arg: TScalar): TScalar;
|
||||
begin
|
||||
case Arg.Kind of
|
||||
TScalar.TKind.Ordinal: Result := Arg; // Trunc on an integer is a no-op
|
||||
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Trunc(Arg.Value.AsDouble));
|
||||
else
|
||||
raise EArgumentException.Create('Trunc requires a numeric argument.');
|
||||
end;
|
||||
var val: Double := Arg;
|
||||
Result := TScalar.FromInt64(System.Trunc(val));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Ceil(const Arg: TScalar): TScalar;
|
||||
begin
|
||||
case Arg.Kind of
|
||||
TScalar.TKind.Ordinal: Result := Arg; // Ceil on an integer is a no-op
|
||||
TScalar.TKind.Ordinal: Result := Arg;
|
||||
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Ceil(Arg.Value.AsDouble));
|
||||
else
|
||||
raise EArgumentException.Create('Ceil requires a numeric argument.');
|
||||
@@ -375,7 +414,7 @@ end;
|
||||
class function TRtlFunctions.Floor(const Arg: TScalar): TScalar;
|
||||
begin
|
||||
case Arg.Kind of
|
||||
TScalar.TKind.Ordinal: Result := Arg; // Floor on an integer is a no-op
|
||||
TScalar.TKind.Ordinal: Result := Arg;
|
||||
TScalar.TKind.Float: Result := TScalar.FromInt64(System.Math.Floor(Arg.Value.AsDouble));
|
||||
else
|
||||
raise EArgumentException.Create('Floor requires a numeric argument.');
|
||||
@@ -392,6 +431,38 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// --- Date Constructors ---
|
||||
|
||||
class function TRtlFunctions.Now(const Args: TArray<TDataValue>): TDataValue;
|
||||
begin
|
||||
Result := TScalar.FromDateTime(System.SysUtils.Now);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Date(const Args: TArray<TDataValue>): TDataValue;
|
||||
|
||||
function GetInt(const V: TDataValue; ArgIndex: Integer): Word;
|
||||
begin
|
||||
if V.AsScalar.Kind <> TScalar.TKind.Ordinal then
|
||||
raise EArgumentException.CreateFmt('Date argument %d must be an Integer.', [ArgIndex]);
|
||||
Result := V.AsScalar.Value.AsInt64;
|
||||
end;
|
||||
|
||||
begin
|
||||
if Length(Args) = 3 then
|
||||
begin
|
||||
var y := GetInt(Args[0], 1);
|
||||
var m := GetInt(Args[1], 2);
|
||||
var d := GetInt(Args[2], 3);
|
||||
Result := TScalar.FromDateTime(EncodeDate(y, m, d));
|
||||
end
|
||||
else if Length(Args) = 0 then
|
||||
Result := TScalar.FromDateTime(System.SysUtils.Date)
|
||||
else
|
||||
raise EArgumentException.Create('Date expects 0 or 3 arguments (Year, Month, Day).');
|
||||
end;
|
||||
|
||||
// --- High-Order / Series ---
|
||||
|
||||
class function TRtlFunctions.Memoize(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
funcToMemoize: TDataValue.TFunc;
|
||||
@@ -402,7 +473,6 @@ begin
|
||||
if Args[0].Kind <> vkMethod then
|
||||
raise EArgumentException.Create('The argument to Memoize must be a function.');
|
||||
|
||||
// create a managed dictionary
|
||||
var cCache: TDataValue;
|
||||
cCache.FromObj(TDictionary<Int64, TDataValue>.Create);
|
||||
|
||||
@@ -518,9 +588,9 @@ begin
|
||||
begin
|
||||
item := sourceSeries.Items[i];
|
||||
predicateResult := predicateFunc([TDataValue(item)]);
|
||||
if (predicateResult.Kind = vkScalar)
|
||||
and (predicateResult.AsScalar.Kind = TScalar.TKind.Ordinal)
|
||||
and (predicateResult.AsScalar.Value.AsInt64 <> 0) then
|
||||
|
||||
// Use the implicit Boolean operator of TScalar
|
||||
if (predicateResult.Kind = vkScalar) and (Boolean(predicateResult.AsScalar)) then
|
||||
matchingIndices.Add(i);
|
||||
end;
|
||||
|
||||
@@ -569,105 +639,91 @@ begin
|
||||
item := sourceSeries.Items[i];
|
||||
predicateResult := predicateFunc([TDataValue(item)]);
|
||||
|
||||
if (predicateResult.Kind = vkScalar)
|
||||
and (predicateResult.AsScalar.Kind = TScalar.TKind.Ordinal)
|
||||
and (predicateResult.AsScalar.Value.AsInt64 <> 0) then
|
||||
if (predicateResult.Kind = vkScalar) and (Boolean(predicateResult.AsScalar)) then
|
||||
begin
|
||||
Result := TScalar.FromInt64(1);
|
||||
Result := TScalar.FromBoolean(True);
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := TScalar.FromInt64(0);
|
||||
Result := TScalar.FromBoolean(False);
|
||||
end;
|
||||
|
||||
{ TRtlFunctions - Static Specializations }
|
||||
// --- Static Specializations ---
|
||||
|
||||
// --- Add ---
|
||||
// Add
|
||||
class function TRtlFunctions.Add_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A + B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Add_Float_Float_Float(A, B: Double): Double;
|
||||
begin
|
||||
Result := A + B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Add_Ordinal_Float_Float(A: Int64; B: Double): Double;
|
||||
begin
|
||||
Result := A + B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Add_Float_Ordinal_Float(A: Double; B: Int64): Double;
|
||||
begin
|
||||
Result := A + B;
|
||||
end;
|
||||
|
||||
// --- Subtract ---
|
||||
// Subtract
|
||||
class function TRtlFunctions.Subtract_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A - B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Subtract_Float_Float_Float(A, B: Double): Double;
|
||||
begin
|
||||
Result := A - B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Subtract_Ordinal_Float_Float(A: Int64; B: Double): Double;
|
||||
begin
|
||||
Result := A - B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Subtract_Float_Ordinal_Float(A: Double; B: Int64): Double;
|
||||
begin
|
||||
Result := A - B;
|
||||
end;
|
||||
|
||||
// --- Multiply ---
|
||||
// Multiply
|
||||
class function TRtlFunctions.Multiply_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A * B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Multiply_Float_Float_Float(A, B: Double): Double;
|
||||
begin
|
||||
Result := A * B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Multiply_Ordinal_Float_Float(A: Int64; B: Double): Double;
|
||||
begin
|
||||
Result := A * B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Multiply_Float_Ordinal_Float(A: Double; B: Int64): Double;
|
||||
begin
|
||||
Result := A * B;
|
||||
end;
|
||||
|
||||
// --- Divide ---
|
||||
// Divide
|
||||
class function TRtlFunctions.Divide_Ordinal_Ordinal_Float(A, B: Int64): Double;
|
||||
begin
|
||||
if B = 0 then
|
||||
raise EDivByZero.Create('Division by zero.');
|
||||
Result := A / B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Divide_Float_Float_Float(A, B: Double): Double;
|
||||
begin
|
||||
if B = 0.0 then
|
||||
raise EDivByZero.Create('Division by zero.');
|
||||
Result := A / B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Divide_Ordinal_Float_Float(A: Int64; B: Double): Double;
|
||||
begin
|
||||
if B = 0.0 then
|
||||
raise EDivByZero.Create('Division by zero.');
|
||||
Result := A / B;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Divide_Float_Ordinal_Float(A: Double; B: Int64): Double;
|
||||
begin
|
||||
if B = 0 then
|
||||
@@ -675,32 +731,35 @@ begin
|
||||
Result := A / B;
|
||||
end;
|
||||
|
||||
// --- Comparisons ---
|
||||
(* Delphi bools: 0=False, 1=True. We return Int64 (0 or 1) *)
|
||||
// Integer Math
|
||||
class function TRtlFunctions.Div_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A div B;
|
||||
end; // Delphi runtime handles EDivByZero for integers
|
||||
class function TRtlFunctions.Mod_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A mod B;
|
||||
end;
|
||||
|
||||
// Comparisons
|
||||
class function TRtlFunctions.Equal_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A = B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Equal_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A = B); // Note: Standard float comparison
|
||||
Result := Ord(SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Equal_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A = B);
|
||||
Result := Ord(SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Equal_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A = B);
|
||||
Result := Ord(SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Equal_Keyword_Keyword_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
// Keywords are passed as their Int64 index
|
||||
Result := Ord(A = B);
|
||||
end;
|
||||
|
||||
@@ -708,22 +767,18 @@ class function TRtlFunctions.NotEqual_Ordinal_Ordinal_Ordinal(A, B: Int64): Int6
|
||||
begin
|
||||
Result := Ord(A <> B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.NotEqual_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A <> B);
|
||||
Result := Ord(not SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.NotEqual_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A <> B);
|
||||
Result := Ord(not SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.NotEqual_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A <> B);
|
||||
Result := Ord(not SameValue(A, B));
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.NotEqual_Keyword_Keyword_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A <> B);
|
||||
@@ -733,17 +788,14 @@ class function TRtlFunctions.Less_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A < B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Less_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A < B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Less_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A < B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Less_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A < B);
|
||||
@@ -753,17 +805,14 @@ class function TRtlFunctions.LessOrEqual_Ordinal_Ordinal_Ordinal(A, B: Int64): I
|
||||
begin
|
||||
Result := Ord(A <= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LessOrEqual_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A <= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LessOrEqual_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A <= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.LessOrEqual_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A <= B);
|
||||
@@ -773,17 +822,14 @@ class function TRtlFunctions.Greater_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64
|
||||
begin
|
||||
Result := Ord(A > B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Greater_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A > B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Greater_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A > B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Greater_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A > B);
|
||||
@@ -793,29 +839,46 @@ class function TRtlFunctions.GreaterOrEqual_Ordinal_Ordinal_Ordinal(A, B: Int64)
|
||||
begin
|
||||
Result := Ord(A >= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.GreaterOrEqual_Float_Float_Ordinal(A, B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A >= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.GreaterOrEqual_Ordinal_Float_Ordinal(A: Int64; B: Double): Int64;
|
||||
begin
|
||||
Result := Ord(A >= B);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.GreaterOrEqual_Float_Ordinal_Ordinal(A: Double; B: Int64): Int64;
|
||||
begin
|
||||
Result := Ord(A >= B);
|
||||
end;
|
||||
|
||||
// --- Unary ---
|
||||
// Bitwise Static
|
||||
class function TRtlFunctions.And_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A and B;
|
||||
end;
|
||||
class function TRtlFunctions.Or_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A or B;
|
||||
end;
|
||||
class function TRtlFunctions.Xor_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A xor B;
|
||||
end;
|
||||
class function TRtlFunctions.Shl_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A shl B;
|
||||
end;
|
||||
class function TRtlFunctions.Shr_Ordinal_Ordinal_Ordinal(A, B: Int64): Int64;
|
||||
begin
|
||||
Result := A shr B;
|
||||
end;
|
||||
|
||||
// Unary
|
||||
class function TRtlFunctions.Negate_Ordinal_Ordinal(A: Int64): Int64;
|
||||
begin
|
||||
Result := -A;
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Negate_Float_Float(A: Double): Double;
|
||||
begin
|
||||
Result := -A;
|
||||
@@ -823,27 +886,32 @@ end;
|
||||
|
||||
class function TRtlFunctions.Not_Ordinal_Ordinal(A: Int64): Int64;
|
||||
begin
|
||||
// Lisp-style 'not': 0 -> 1, everything else -> 0
|
||||
Result := Ord(A = 0);
|
||||
end;
|
||||
|
||||
// --- Standard functions ---
|
||||
end; // Logical NOT for Int64
|
||||
|
||||
// Standard
|
||||
class function TRtlFunctions.Abs_Ordinal_Ordinal(A: Int64): Int64;
|
||||
begin
|
||||
Result := System.Abs(A);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Abs_Float_Float(A: Double): Double;
|
||||
begin
|
||||
Result := System.Abs(A);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Trunc_Ordinal_Ordinal(A: Int64): Int64;
|
||||
class function TRtlFunctions.Round_Ordinal_Ordinal(A: Int64): Int64;
|
||||
begin
|
||||
Result := A; // No-op
|
||||
Result := A;
|
||||
end;
|
||||
class function TRtlFunctions.Round_Float_Ordinal(A: Double): Int64;
|
||||
begin
|
||||
Result := System.Round(A);
|
||||
end;
|
||||
|
||||
class function TRtlFunctions.Trunc_Ordinal_Ordinal(A: Int64): Int64;
|
||||
begin
|
||||
Result := A;
|
||||
end;
|
||||
class function TRtlFunctions.Trunc_Float_Ordinal(A: Double): Int64;
|
||||
begin
|
||||
Result := System.Trunc(A);
|
||||
|
||||
+12
-1
@@ -332,9 +332,20 @@ begin
|
||||
var wrapper := CreateStaticWrapper(method, retType, argTypes, rttiParams);
|
||||
if Assigned(wrapper) then
|
||||
begin
|
||||
// (* UPDATED: Capture IsPure from attribute *)
|
||||
methodRecord := TSpecializedMethod.Create(wrapper, retType, exportAttr.IsPure);
|
||||
RegisterStaticSpecialization(rtlName, argTypes, methodRecord);
|
||||
|
||||
// If this is a generic wrapper (e.g. Abs(TScalar)) and no DynamicWrapper exists yet,
|
||||
// use this wrapper as the interpreter fallback!
|
||||
if (not Assigned(funcInfo.DynamicWrapper)) and (Length(argTypes) = 1) and (argTypes[0].Kind = stUnknown) then
|
||||
begin
|
||||
funcInfo.DynamicWrapper := wrapper;
|
||||
end
|
||||
else if (not Assigned(funcInfo.DynamicWrapper)) and (Length(argTypes) = 2) and (argTypes[0].Kind = stUnknown) then
|
||||
begin
|
||||
// Potentially handle 2-arg scalar wrappers here too if needed
|
||||
// For now, Abs/Trunc are 1-arg TScalar->TScalar, which matches stUnknown check above.
|
||||
end;
|
||||
end
|
||||
else
|
||||
raise ENotImplemented.CreateFmt('Static native method wrapper for %s() not implemented', [method.Name]);
|
||||
|
||||
+399
-125
@@ -3,9 +3,11 @@ unit Myc.Data.Scalar;
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Generics.Defaults,
|
||||
system.sysutils,
|
||||
system.generics.collections,
|
||||
system.generics.defaults,
|
||||
system.math,
|
||||
system.dateutils,
|
||||
Myc.Utils,
|
||||
Myc.Data.Decimal,
|
||||
Myc.Data.Series,
|
||||
@@ -19,17 +21,36 @@ type
|
||||
public
|
||||
type
|
||||
// Defines the underlying storage kinds for scalar values
|
||||
TKind = (Ordinal, Float, Keyword);
|
||||
TKind = (Ordinal, Float, Keyword, Boolean, DateTime);
|
||||
|
||||
// The 8-byte storage for the scalar value
|
||||
TValue = record
|
||||
case TKind of
|
||||
TKind.Ordinal, TKind.Keyword: (AsInt64: Int64); // Ordinal and Keyword index
|
||||
TKind.Float: (AsDouble: Double);
|
||||
TKind.Ordinal, TKind.Keyword, TKind.Boolean: (AsInt64: Int64);
|
||||
TKind.Float, TKind.DateTime: (AsDouble: Double);
|
||||
end;
|
||||
|
||||
TBinaryOp = (Add, Subtract, Multiply, Divide, Equal, NotEqual, Less, Greater, LessOrEqual, GreaterOrEqual);
|
||||
TUnaryOp = (Negate, &Not);
|
||||
TBinaryOp = (
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
IntDivide,
|
||||
Modulus,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Less,
|
||||
Greater,
|
||||
LessOrEqual,
|
||||
GreaterOrEqual,
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BitwiseXor,
|
||||
LeftShift,
|
||||
RightShift
|
||||
);
|
||||
|
||||
TUnaryOp = (Negate, Positive, &Not);
|
||||
|
||||
TKindHelper = record helper for TKind
|
||||
public
|
||||
@@ -55,13 +76,19 @@ type
|
||||
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;
|
||||
class function FromBoolean(AValue: Boolean): TScalar; static; inline;
|
||||
class function FromDateTime(AValue: TDateTime): 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;
|
||||
class operator Implicit(AValue: Boolean): TScalar; overload; inline;
|
||||
|
||||
class operator Implicit(const A: TScalar): Double; overload; inline;
|
||||
class operator Implicit(const A: TScalar): Int64; overload; inline;
|
||||
class operator Implicit(const A: TScalar): Boolean; overload; inline;
|
||||
class operator Implicit(const A: TScalar): TDateTime; overload; inline;
|
||||
|
||||
class function StringToKind(const AName: string): TKind; static;
|
||||
function ToString: String;
|
||||
@@ -72,20 +99,34 @@ type
|
||||
class function TryBinaryOperation(Op: TBinaryOp; const A, B: TScalar; out Res: TScalar): Boolean; static;
|
||||
class function TryUnaryOperation(Op: TUnaryOp; const A: TScalar; out Res: TScalar): Boolean; static;
|
||||
|
||||
class operator Add(const A, B: TScalar): TScalar;
|
||||
class operator Divide(const A, B: TScalar): TScalar;
|
||||
class operator Equal(const A, B: TScalar): Boolean;
|
||||
class operator GreaterThan(const A, B: TScalar): Boolean;
|
||||
class operator GreaterThanOrEqual(const A, B: TScalar): Boolean;
|
||||
class operator LessThan(const A, B: TScalar): Boolean;
|
||||
class operator LessThanOrEqual(const A, B: TScalar): Boolean;
|
||||
class operator LogicalNot(const A: TScalar): TScalar;
|
||||
class operator Multiply(const A, B: TScalar): TScalar;
|
||||
class operator Negative(const A: TScalar): TScalar;
|
||||
class operator NotEqual(const A, B: TScalar): Boolean;
|
||||
class operator Round(const A: TScalar): TScalar;
|
||||
class operator Subtract(const A, B: TScalar): TScalar;
|
||||
class operator Trunc(const A: TScalar): TScalar;
|
||||
// Operators
|
||||
class operator Add(const A, B: TScalar): TScalar; inline;
|
||||
class operator Subtract(const A, B: TScalar): TScalar; inline;
|
||||
class operator Multiply(const A, B: TScalar): TScalar; inline;
|
||||
class operator Divide(const A, B: TScalar): TScalar; inline;
|
||||
|
||||
class operator IntDivide(const A, B: TScalar): TScalar; inline;
|
||||
class operator Modulus(const A, B: TScalar): TScalar; inline;
|
||||
class operator Positive(const A: TScalar): TScalar; inline;
|
||||
|
||||
class operator Equal(const A, B: TScalar): Boolean; inline;
|
||||
class operator NotEqual(const A, B: TScalar): Boolean; inline;
|
||||
class operator GreaterThan(const A, B: TScalar): Boolean; inline;
|
||||
class operator GreaterThanOrEqual(const A, B: TScalar): Boolean; inline;
|
||||
class operator LessThan(const A, B: TScalar): Boolean; inline;
|
||||
class operator LessThanOrEqual(const A, B: TScalar): Boolean; inline;
|
||||
|
||||
class operator BitwiseAnd(const A, B: TScalar): TScalar; inline;
|
||||
class operator BitwiseOr(const A, B: TScalar): TScalar; inline;
|
||||
class operator BitwiseXor(const A, B: TScalar): TScalar; inline;
|
||||
class operator LeftShift(const A, B: TScalar): TScalar; inline;
|
||||
class operator RightShift(const A, B: TScalar): TScalar; inline;
|
||||
|
||||
class operator LogicalNot(const A: TScalar): TScalar; inline;
|
||||
class operator Negative(const A: TScalar): TScalar; inline;
|
||||
|
||||
class operator Round(const A: TScalar): TScalar; inline;
|
||||
class operator Trunc(const A: TScalar): TScalar; inline;
|
||||
end;
|
||||
|
||||
// An array of scalar values of the same kind.
|
||||
@@ -236,6 +277,20 @@ begin
|
||||
Result.Value.AsInt64 := AValue.Idx
|
||||
end;
|
||||
|
||||
class function TScalar.FromBoolean(AValue: Boolean): TScalar;
|
||||
begin
|
||||
Result.Kind := TKind.Boolean;
|
||||
Result.Value.AsInt64 := Ord(AValue);
|
||||
end;
|
||||
|
||||
class function TScalar.FromDateTime(AValue: TDateTime): TScalar;
|
||||
begin
|
||||
Result.Kind := TKind.DateTime;
|
||||
Result.Value.AsDouble := AValue;
|
||||
end;
|
||||
|
||||
// --- Implicit Conversions ---
|
||||
|
||||
class operator TScalar.Implicit(AValue: Double): TScalar;
|
||||
begin
|
||||
Result.Kind := TKind.Float;
|
||||
@@ -256,13 +311,20 @@ begin
|
||||
Result.Value.AsInt64 := AValue.Idx
|
||||
end;
|
||||
|
||||
class operator TScalar.Implicit(AValue: Boolean): TScalar;
|
||||
begin
|
||||
Result.Kind := TKind.Boolean;
|
||||
Result.Value.AsInt64 := Ord(AValue);
|
||||
end;
|
||||
|
||||
class operator TScalar.Implicit(const A: TScalar): Double;
|
||||
begin
|
||||
case A.Kind of
|
||||
TKind.Ordinal: Result := A.Value.AsInt64;
|
||||
TKind.Float: Result := A.Value.AsDouble;
|
||||
TKind.DateTime: Result := A.Value.AsDouble;
|
||||
else
|
||||
raise EInvalidCast.Create('Cannot implicitly convert Keyword to Double');
|
||||
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to Double', [A.Kind.ToString]);
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -270,33 +332,111 @@ class operator TScalar.Implicit(const A: TScalar): Int64;
|
||||
begin
|
||||
case A.Kind of
|
||||
TKind.Ordinal: Result := A.Value.AsInt64;
|
||||
TKind.Boolean: Result := A.Value.AsInt64;
|
||||
else
|
||||
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to Int64', [A.Kind.ToString]);
|
||||
end;
|
||||
end;
|
||||
|
||||
class operator TScalar.Implicit(const A: TScalar): Boolean;
|
||||
begin
|
||||
case A.Kind of
|
||||
TKind.Boolean, TKind.Ordinal: Result := A.Value.AsInt64 <> 0;
|
||||
else
|
||||
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to Boolean', [A.Kind.ToString]);
|
||||
end;
|
||||
end;
|
||||
|
||||
class operator TScalar.Implicit(const A: TScalar): TDateTime;
|
||||
begin
|
||||
case A.Kind of
|
||||
TKind.DateTime: Result := A.Value.AsDouble;
|
||||
else
|
||||
raise EInvalidCast.CreateFmt('Cannot implicitly convert %s to TDateTime', [A.Kind.ToString]);
|
||||
end;
|
||||
end;
|
||||
|
||||
// --- Support Checks ---
|
||||
|
||||
class function TScalar.IsBinaryOperatorSupported(Op: TBinaryOp; A, B: TKind): Boolean;
|
||||
begin
|
||||
// Deny all operations if either operand is Keyword...
|
||||
// Keyword logic
|
||||
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
|
||||
// DateTime logic
|
||||
if (A = TKind.DateTime) or (B = TKind.DateTime) then
|
||||
begin
|
||||
case Op of
|
||||
TBinaryOp.Add:
|
||||
// Date + Num or Num + Date
|
||||
Result :=
|
||||
(A in [TKind.DateTime, TKind.Ordinal, TKind.Float])
|
||||
and (B in [TKind.DateTime, TKind.Ordinal, TKind.Float])
|
||||
and not ((A = TKind.DateTime) and (B = TKind.DateTime));
|
||||
|
||||
TBinaryOp.Subtract:
|
||||
// Date - Num or Date - Date
|
||||
Result := (A = TKind.DateTime) and (B in [TKind.DateTime, TKind.Ordinal, TKind.Float]);
|
||||
|
||||
TBinaryOp.Equal, TBinaryOp.NotEqual, TBinaryOp.Less, TBinaryOp.Greater, TBinaryOp.LessOrEqual, TBinaryOp.GreaterOrEqual:
|
||||
Result := True;
|
||||
|
||||
else
|
||||
Result := False;
|
||||
end;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Boolean Logic
|
||||
if (A = TKind.Boolean) or (B = TKind.Boolean) then
|
||||
begin
|
||||
if (A = TKind.Boolean) and (B = TKind.Boolean) then
|
||||
begin
|
||||
Result := Op in [TBinaryOp.Equal, TBinaryOp.NotEqual, TBinaryOp.BitwiseAnd, TBinaryOp.BitwiseOr, TBinaryOp.BitwiseXor];
|
||||
exit;
|
||||
end;
|
||||
Result := Op in [TBinaryOp.Equal, TBinaryOp.NotEqual];
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Bitwise/Int Math requires Ordinals
|
||||
if Op
|
||||
in [
|
||||
TBinaryOp.IntDivide,
|
||||
TBinaryOp.Modulus,
|
||||
TBinaryOp.BitwiseAnd,
|
||||
TBinaryOp.BitwiseOr,
|
||||
TBinaryOp.BitwiseXor,
|
||||
TBinaryOp.LeftShift,
|
||||
TBinaryOp.RightShift] then
|
||||
begin
|
||||
Result := (A = TKind.Ordinal) and (B = TKind.Ordinal);
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Default Numeric
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
class function TScalar.IsUnaryOperatorSupported(Op: TUnaryOp; A: TKind): Boolean;
|
||||
begin
|
||||
// Deny all unary ops for Keywords
|
||||
if (A = TKind.Keyword) then
|
||||
if A = TKind.Keyword then
|
||||
exit(False);
|
||||
|
||||
// Deny 'not' for Float
|
||||
if (Op = TUnaryOp.Not) and (A = TKind.Float) then
|
||||
if A = TKind.Boolean then
|
||||
begin
|
||||
Result := (Op = TUnaryOp.Not);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if A = TKind.DateTime then
|
||||
exit(False);
|
||||
|
||||
if (Op = TUnaryOp.Not) and (A <> TKind.Ordinal) then
|
||||
exit(False);
|
||||
|
||||
Result := True;
|
||||
@@ -310,6 +450,10 @@ begin
|
||||
Result := TKind.Float
|
||||
else if SameText(AName, 'Keyword') then
|
||||
Result := TKind.Keyword
|
||||
else if SameText(AName, 'Boolean') then
|
||||
Result := TKind.Boolean
|
||||
else if SameText(AName, 'DateTime') then
|
||||
Result := TKind.DateTime
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Unknown scalar type name: "%s"', [AName]);
|
||||
end;
|
||||
@@ -320,6 +464,8 @@ begin
|
||||
TKind.Ordinal: Result := IntToStr(Value.AsInt64);
|
||||
TKind.Float: Result := FloatToStr(Value.AsDouble);
|
||||
TKind.Keyword: Result := ':' + TKeywordRegistry.GetName(Value.AsInt64);
|
||||
TKind.Boolean: Result := BoolToStr(Value.AsInt64 <> 0, True);
|
||||
TKind.DateTime: Result := DateToISO8601(Value.AsDouble, True);
|
||||
else
|
||||
Result := '[Unknown Scalar]';
|
||||
end;
|
||||
@@ -329,22 +475,27 @@ class function TScalar.TryBinaryOperation(Op: TBinaryOp; const A, B: TScalar; ou
|
||||
begin
|
||||
if not IsBinaryOperatorSupported(Op, A.Kind, B.Kind) then
|
||||
exit(False);
|
||||
|
||||
try
|
||||
case Op of
|
||||
TBinaryOp.Add: Res := A + B;
|
||||
TBinaryOp.Subtract: Res := A - B;
|
||||
TBinaryOp.Multiply: Res := A * B;
|
||||
TBinaryOp.Divide: Res := A / B;
|
||||
TBinaryOp.Equal: Res := TScalar.FromInt64(Integer(A = B));
|
||||
TBinaryOp.NotEqual: Res := TScalar.FromInt64(Integer(A <> B));
|
||||
TBinaryOp.Less: Res := TScalar.FromInt64(Integer(A < B));
|
||||
TBinaryOp.Greater: Res := TScalar.FromInt64(Integer(A > B));
|
||||
TBinaryOp.LessOrEqual: Res := TScalar.FromInt64(Integer(A <= B));
|
||||
TBinaryOp.GreaterOrEqual: Res := TScalar.FromInt64(Integer(A >= B));
|
||||
TBinaryOp.IntDivide: Res := A div B;
|
||||
TBinaryOp.Modulus: Res := A mod B;
|
||||
TBinaryOp.Equal: Res := TScalar.FromBoolean(A = B);
|
||||
TBinaryOp.NotEqual: Res := TScalar.FromBoolean(A <> B);
|
||||
TBinaryOp.Less: Res := TScalar.FromBoolean(A < B);
|
||||
TBinaryOp.Greater: Res := TScalar.FromBoolean(A > B);
|
||||
TBinaryOp.LessOrEqual: Res := TScalar.FromBoolean(A <= B);
|
||||
TBinaryOp.GreaterOrEqual: Res := TScalar.FromBoolean(A >= B);
|
||||
TBinaryOp.BitwiseAnd: Res := A and B;
|
||||
TBinaryOp.BitwiseOr: Res := A or B;
|
||||
TBinaryOp.BitwiseXor: Res := A xor B;
|
||||
TBinaryOp.LeftShift: Res := A shl B;
|
||||
TBinaryOp.RightShift: Res := A shr B;
|
||||
else
|
||||
Result := False;
|
||||
exit;
|
||||
exit(False);
|
||||
end;
|
||||
Result := True;
|
||||
except
|
||||
@@ -359,10 +510,10 @@ begin
|
||||
try
|
||||
case Op of
|
||||
TUnaryOp.Negate: Res := -A;
|
||||
TUnaryOp.Positive: Res := +A;
|
||||
TUnaryOp.Not: Res := not A;
|
||||
else
|
||||
Result := False;
|
||||
exit;
|
||||
exit(False);
|
||||
end;
|
||||
Result := True;
|
||||
except
|
||||
@@ -370,80 +521,163 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// --- Operators ---
|
||||
|
||||
class operator TScalar.Add(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
// Date Math
|
||||
if (A.Kind = TKind.DateTime) or (B.Kind = TKind.DateTime) then
|
||||
begin
|
||||
if (A.Kind = TKind.DateTime) and (B.Kind = TKind.DateTime) then
|
||||
raise EArgumentException.Create('Cannot add two DateTimes.');
|
||||
|
||||
var dateVal: Double;
|
||||
var numVal: Double;
|
||||
|
||||
if A.Kind = TKind.DateTime then
|
||||
begin
|
||||
dateVal := A.Value.AsDouble;
|
||||
numVal := Double(B);
|
||||
end
|
||||
else
|
||||
begin
|
||||
dateVal := B.Value.AsDouble;
|
||||
numVal := Double(A);
|
||||
end;
|
||||
|
||||
Result.Kind := TKind.DateTime;
|
||||
Result.Value.AsDouble := dateVal + numVal;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := A.Value.AsInt64 + B.Value.AsInt64
|
||||
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
|
||||
else if (A.Kind in [TKind.Ordinal, TKind.Float]) and (B.Kind in [TKind.Ordinal, TKind.Float]) then
|
||||
begin
|
||||
var valA, valB: Double;
|
||||
valA := A; // Use implicit cast
|
||||
valB := B; // Use implicit cast
|
||||
var valA: Double := A;
|
||||
var valB: Double := B;
|
||||
Result := valA + valB;
|
||||
end
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Add not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
end;
|
||||
|
||||
class operator TScalar.Subtract(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
// Date Math
|
||||
if A.Kind = TKind.DateTime then
|
||||
begin
|
||||
var dateA := A.Value.AsDouble;
|
||||
if B.Kind = TKind.DateTime then
|
||||
begin
|
||||
Result.Kind := TKind.Float;
|
||||
Result.Value.AsDouble := dateA - B.Value.AsDouble;
|
||||
end
|
||||
else if B.Kind in [TKind.Ordinal, TKind.Float] then
|
||||
begin
|
||||
Result.Kind := TKind.DateTime;
|
||||
Result.Value.AsDouble := dateA - Double(B);
|
||||
end
|
||||
else
|
||||
raise EArgumentException.Create('Invalid operand for Date subtraction.');
|
||||
exit;
|
||||
end;
|
||||
|
||||
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := A.Value.AsInt64 - B.Value.AsInt64
|
||||
else if (A.Kind in [TKind.Ordinal, TKind.Float]) and (B.Kind in [TKind.Ordinal, TKind.Float]) then
|
||||
begin
|
||||
var valA: Double := A;
|
||||
var valB: Double := B;
|
||||
Result := valA - valB;
|
||||
end
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
end;
|
||||
|
||||
class operator TScalar.Multiply(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind in [TKind.DateTime, TKind.Keyword, TKind.Boolean]) or (B.Kind in [TKind.DateTime, TKind.Keyword, TKind.Boolean]) then
|
||||
raise EArgumentException.CreateFmt('Operator Multiply not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
|
||||
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := A.Value.AsInt64 * B.Value.AsInt64
|
||||
else
|
||||
begin
|
||||
var valA: Double := A;
|
||||
var valB: Double := B;
|
||||
Result := valA * valB;
|
||||
end;
|
||||
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
|
||||
if (A.Kind in [TKind.DateTime, TKind.Keyword, TKind.Boolean]) or (B.Kind in [TKind.DateTime, TKind.Keyword, TKind.Boolean]) then
|
||||
raise EArgumentException.CreateFmt('Operator Divide not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
|
||||
var valA, valB: Double;
|
||||
valA := A; // Use implicit cast
|
||||
valB := B; // Use implicit cast
|
||||
var valB: Double := B;
|
||||
if valB = 0.0 then
|
||||
raise EDivByZero.Create('Division by zero');
|
||||
|
||||
var valA: Double := A;
|
||||
Result := valA / valB;
|
||||
end;
|
||||
|
||||
class operator TScalar.IntDivide(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
||||
raise EArgumentException.Create('Operator div requires Ordinal arguments.');
|
||||
|
||||
if B.Value.AsInt64 = 0 then
|
||||
raise EDivByZero.Create('Division by zero');
|
||||
|
||||
Result.Kind := TKind.Ordinal;
|
||||
Result.Value.AsInt64 := A.Value.AsInt64 div B.Value.AsInt64;
|
||||
end;
|
||||
|
||||
class operator TScalar.Modulus(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
||||
raise EArgumentException.Create('Operator mod requires Ordinal arguments.');
|
||||
|
||||
if B.Value.AsInt64 = 0 then
|
||||
raise EDivByZero.Create('Division by zero');
|
||||
|
||||
Result.Kind := TKind.Ordinal;
|
||||
Result.Value.AsInt64 := A.Value.AsInt64 mod B.Value.AsInt64;
|
||||
end;
|
||||
|
||||
class operator TScalar.Equal(const A, B: TScalar): Boolean;
|
||||
begin
|
||||
// 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;
|
||||
if A.Kind = B.Kind then
|
||||
begin
|
||||
case A.Kind of
|
||||
TKind.Ordinal, TKind.Keyword, TKind.Boolean: Result := A.Value.AsInt64 = B.Value.AsInt64;
|
||||
TKind.Float, TKind.DateTime: Result := SameValue(A.Value.AsDouble, B.Value.AsDouble);
|
||||
else
|
||||
Result := False;
|
||||
end;
|
||||
end
|
||||
else if (A.Kind in [TKind.Ordinal, TKind.Float]) and (B.Kind in [TKind.Ordinal, TKind.Float]) then
|
||||
Result := SameValue(Double(A), Double(B))
|
||||
else
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
class operator TScalar.GreaterThan(const A, B: TScalar): Boolean;
|
||||
class operator TScalar.NotEqual(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 if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
|
||||
begin
|
||||
var valA, valB: Double;
|
||||
valA := A;
|
||||
valB := B;
|
||||
Result := valA > valB;
|
||||
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;
|
||||
begin
|
||||
Result := not (A < B);
|
||||
Result := not (A = B);
|
||||
end;
|
||||
|
||||
class operator TScalar.LessThan(const A, B: TScalar): Boolean;
|
||||
begin
|
||||
if (A.Kind = TKind.Keyword) or (B.Kind = TKind.Keyword) or (A.Kind = TKind.Boolean) or (B.Kind = TKind.Boolean) then
|
||||
raise EArgumentException.Create('Order comparison not supported for Keyword/Boolean.');
|
||||
|
||||
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := A.Value.AsInt64 < B.Value.AsInt64
|
||||
else if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
|
||||
begin
|
||||
var valA, valB: Double;
|
||||
valA := A;
|
||||
valB := B;
|
||||
Result := valA < valB;
|
||||
end
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator LessThan not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
Result := Double(A) < Double(B);
|
||||
end;
|
||||
|
||||
class operator TScalar.LessThanOrEqual(const A, B: TScalar): Boolean;
|
||||
@@ -451,30 +685,74 @@ begin
|
||||
Result := not (A > B);
|
||||
end;
|
||||
|
||||
class operator TScalar.LogicalNot(const A: TScalar): TScalar;
|
||||
class operator TScalar.GreaterThan(const A, B: TScalar): Boolean;
|
||||
begin
|
||||
if A.Kind <> TKind.Ordinal then
|
||||
raise EArgumentException.CreateFmt('Operator Not not supported for type %s', [A.Kind.ToString]);
|
||||
if (A.Kind = TKind.Keyword) or (B.Kind = TKind.Keyword) or (A.Kind = TKind.Boolean) or (B.Kind = TKind.Boolean) then
|
||||
raise EArgumentException.Create('Order comparison not supported for Keyword/Boolean.');
|
||||
|
||||
if A.Value.AsInt64 = 0 then
|
||||
Result := 1
|
||||
if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := A.Value.AsInt64 > B.Value.AsInt64
|
||||
else
|
||||
Result := 0;
|
||||
Result := Double(A) > Double(B);
|
||||
end;
|
||||
|
||||
class operator TScalar.Multiply(const A, B: TScalar): TScalar;
|
||||
class operator TScalar.GreaterThanOrEqual(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 if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
|
||||
begin
|
||||
var valA, valB: Double;
|
||||
valA := A;
|
||||
valB := B;
|
||||
Result := valA * valB;
|
||||
end
|
||||
Result := not (A < B);
|
||||
end;
|
||||
|
||||
class operator TScalar.BitwiseAnd(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind = TKind.Boolean) and (B.Kind = TKind.Boolean) then
|
||||
Result := FromBoolean((A.Value.AsInt64 <> 0) and (B.Value.AsInt64 <> 0))
|
||||
else if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := FromInt64(A.Value.AsInt64 and B.Value.AsInt64)
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Multiply not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
raise EArgumentException.Create('Operator "and" requires Ordinal or Boolean arguments.');
|
||||
end;
|
||||
|
||||
class operator TScalar.BitwiseOr(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind = TKind.Boolean) and (B.Kind = TKind.Boolean) then
|
||||
Result := FromBoolean((A.Value.AsInt64 <> 0) or (B.Value.AsInt64 <> 0))
|
||||
else if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := FromInt64(A.Value.AsInt64 or B.Value.AsInt64)
|
||||
else
|
||||
raise EArgumentException.Create('Operator "or" requires Ordinal or Boolean arguments.');
|
||||
end;
|
||||
|
||||
class operator TScalar.BitwiseXor(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind = TKind.Boolean) and (B.Kind = TKind.Boolean) then
|
||||
Result := FromBoolean((A.Value.AsInt64 <> 0) xor (B.Value.AsInt64 <> 0))
|
||||
else if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then
|
||||
Result := FromInt64(A.Value.AsInt64 xor B.Value.AsInt64)
|
||||
else
|
||||
raise EArgumentException.Create('Operator "xor" requires Ordinal or Boolean arguments.');
|
||||
end;
|
||||
|
||||
class operator TScalar.LeftShift(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
||||
raise EArgumentException.Create('Operator shl requires Ordinal arguments.');
|
||||
Result := FromInt64(A.Value.AsInt64 shl B.Value.AsInt64);
|
||||
end;
|
||||
|
||||
class operator TScalar.RightShift(const A, B: TScalar): TScalar;
|
||||
begin
|
||||
if (A.Kind <> TKind.Ordinal) or (B.Kind <> TKind.Ordinal) then
|
||||
raise EArgumentException.Create('Operator shr requires Ordinal arguments.');
|
||||
Result := FromInt64(A.Value.AsInt64 shr B.Value.AsInt64);
|
||||
end;
|
||||
|
||||
class operator TScalar.LogicalNot(const A: TScalar): TScalar;
|
||||
begin
|
||||
if A.Kind = TKind.Boolean then
|
||||
Result := FromBoolean(A.Value.AsInt64 = 0)
|
||||
else if A.Kind = TKind.Ordinal then
|
||||
Result := FromBoolean(A.Value.AsInt64 = 0)
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Not not supported for type %s', [A.Kind.ToString]);
|
||||
end;
|
||||
|
||||
class operator TScalar.Negative(const A: TScalar): TScalar;
|
||||
@@ -488,38 +766,24 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
class operator TScalar.NotEqual(const A, B: TScalar): Boolean;
|
||||
class operator TScalar.Positive(const A: TScalar): TScalar;
|
||||
begin
|
||||
Result := not (A = B);
|
||||
if A.Kind in [TKind.Ordinal, TKind.Float] then
|
||||
Result := A
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Positive not supported for type %s', [A.Kind.ToString]);
|
||||
end;
|
||||
|
||||
class operator TScalar.Round(const A: TScalar): TScalar;
|
||||
begin
|
||||
var val: Double;
|
||||
val := A; // Implicit cast handles Ordinal or Float
|
||||
Result := System.Round(val);
|
||||
end;
|
||||
|
||||
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 if (A.Kind = TKind.Float) or (B.Kind = TKind.Float) then
|
||||
begin
|
||||
var valA, valB: Double;
|
||||
valA := A;
|
||||
valB := B;
|
||||
Result := valA - valB;
|
||||
end
|
||||
else
|
||||
raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]);
|
||||
var val: Double := A;
|
||||
Result := FromInt64(System.Round(val));
|
||||
end;
|
||||
|
||||
class operator TScalar.Trunc(const A: TScalar): TScalar;
|
||||
begin
|
||||
var val: Double;
|
||||
val := A; // Implicit cast handles Ordinal or Float
|
||||
Result := System.Trunc(val);
|
||||
var val: Double := A;
|
||||
Result := FromInt64(System.Trunc(val));
|
||||
end;
|
||||
|
||||
{ TScalarArray }
|
||||
@@ -623,6 +887,8 @@ begin
|
||||
TScalar.TKind.Ordinal: Result := 'Ordinal';
|
||||
TScalar.TKind.Float: Result := 'Float';
|
||||
TScalar.TKind.Keyword: Result := 'Keyword';
|
||||
TScalar.TKind.Boolean: Result := 'Boolean';
|
||||
TScalar.TKind.DateTime: Result := 'DateTime';
|
||||
else
|
||||
Result := 'unknown';
|
||||
end;
|
||||
@@ -637,12 +903,19 @@ begin
|
||||
TScalar.TBinaryOp.Subtract: Result := '-';
|
||||
TScalar.TBinaryOp.Multiply: Result := '*';
|
||||
TScalar.TBinaryOp.Divide: Result := '/';
|
||||
TScalar.TBinaryOp.IntDivide: Result := 'div';
|
||||
TScalar.TBinaryOp.Modulus: Result := 'mod';
|
||||
TScalar.TBinaryOp.Equal: Result := '=';
|
||||
TScalar.TBinaryOp.NotEqual: Result := '<>';
|
||||
TScalar.TBinaryOp.Less: Result := '<';
|
||||
TScalar.TBinaryOp.Greater: Result := '>';
|
||||
TScalar.TBinaryOp.LessOrEqual: Result := '<=';
|
||||
TScalar.TBinaryOp.GreaterOrEqual: Result := '>=';
|
||||
TScalar.TBinaryOp.BitwiseAnd: Result := 'and';
|
||||
TScalar.TBinaryOp.BitwiseOr: Result := 'or';
|
||||
TScalar.TBinaryOp.BitwiseXor: Result := 'xor';
|
||||
TScalar.TBinaryOp.LeftShift: Result := 'shl';
|
||||
TScalar.TBinaryOp.RightShift: Result := 'shr';
|
||||
else
|
||||
Result := '?';
|
||||
end;
|
||||
@@ -654,6 +927,7 @@ function TScalar.TUnaryOpHelper.ToString: string;
|
||||
begin
|
||||
case Self of
|
||||
TScalar.TUnaryOp.Negate: Result := '-';
|
||||
TScalar.TUnaryOp.Positive: Result := '+';
|
||||
TScalar.TUnaryOp.Not: Result := 'not';
|
||||
else
|
||||
Result := '?';
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
unit Test.Myc.Ast.RTL;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
DUnitX.TestFramework,
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
System.DateUtils,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Data.Value,
|
||||
Myc.Ast.Types,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Ast.RTL,
|
||||
Myc.Ast.RTL.Core;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TTestMycAstRTL = class
|
||||
private
|
||||
FScope: IExecutionScope;
|
||||
|
||||
function Call(const Name: string; const Args: array of TDataValue): TDataValue;
|
||||
function ValI(V: Int64): TDataValue;
|
||||
function ValF(V: Double): TDataValue;
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
|
||||
// --- Registration ---
|
||||
[Test]
|
||||
[IgnoreMemoryLeaks]
|
||||
procedure RTL_Registration_SymbolsArePresent;
|
||||
|
||||
// --- Floating Point Math ---
|
||||
[Test]
|
||||
[TestCase('Add_FF', '+,1.5,2.5,4.0')]
|
||||
[TestCase('Div_FF', '/,10,2,5.0')]
|
||||
procedure RTL_Math_Float(const Op: string; A, B, Expected: Double);
|
||||
|
||||
// --- Integer Math (New) ---
|
||||
[Test]
|
||||
[TestCase('Div_Int', 'div,10,3,3')]
|
||||
[TestCase('Mod_Int', 'mod,10,3,1')]
|
||||
procedure RTL_Math_Integer(const Op: string; A, B, Expected: Int64);
|
||||
|
||||
// --- Bitwise Operations (New) ---
|
||||
[Test]
|
||||
[TestCase('BitAnd', 'and,3,2,2')] // 011 & 010 = 010 (2)
|
||||
[TestCase('BitOr', 'or,1,2,3')] // 001 | 010 = 011 (3)
|
||||
[TestCase('BitXor', 'xor,3,1,2')] // 011 ^ 001 = 010 (2)
|
||||
[TestCase('Shl', 'shl,1,2,4')] // 1 << 2 = 4
|
||||
[TestCase('Shr', 'shr,4,1,2')] // 4 >> 1 = 2
|
||||
procedure RTL_Bitwise(const Op: string; A, B, Expected: Int64);
|
||||
|
||||
// --- Rounding (New) ---
|
||||
[Test]
|
||||
[TestCase('Round_Up', '3.6,4')]
|
||||
[TestCase('Round_Down', '3.4,3')]
|
||||
[TestCase('Round_Mid', '3.5,4')] // Banker's rounding or standard? Delphi default is Banker's.
|
||||
procedure RTL_Round_Works(A: Double; Expected: Int64);
|
||||
|
||||
// --- Comparisons ---
|
||||
[Test]
|
||||
[TestCase('Eq_True', '=,10,10,1')]
|
||||
[TestCase('Neq_True', '<>,10,20,1')]
|
||||
procedure RTL_Comparison_Ordinals(const Op: string; A, B: Int64; ExpectedBool: Int64);
|
||||
|
||||
// --- DateTime Logic (New) ---
|
||||
[Test]
|
||||
procedure RTL_DateTime_ConstructionAndMath;
|
||||
|
||||
// --- Error Handling ---
|
||||
[Test]
|
||||
procedure RTL_DivByZero_ThrowException;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
Myc.Ast;
|
||||
|
||||
{ TTestMycAstRTL }
|
||||
|
||||
procedure TTestMycAstRTL.Setup;
|
||||
begin
|
||||
FScope := TAst.CreateScope(nil, nil, False);
|
||||
Myc.Ast.RTL.RegisterRtlFunctions(FScope);
|
||||
end;
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
function TTestMycAstRTL.ValI(V: Int64): TDataValue;
|
||||
begin
|
||||
Result := TDataValue(TScalar.FromInt64(V));
|
||||
end;
|
||||
|
||||
function TTestMycAstRTL.ValF(V: Double): TDataValue;
|
||||
begin
|
||||
Result := TDataValue(TScalar.FromDouble(V));
|
||||
end;
|
||||
|
||||
function TTestMycAstRTL.Call(const Name: string; const Args: array of TDataValue): TDataValue;
|
||||
var
|
||||
addr: TResolvedAddress;
|
||||
funcVal: TDataValue;
|
||||
func: TDataValue.TFunc;
|
||||
argArray: TArray<TDataValue>;
|
||||
i: Integer;
|
||||
begin
|
||||
addr := FScope.Resolve(Name);
|
||||
Assert.AreNotEqual(TAddressKind.akUnresolved, addr.Kind, 'Function not found: ' + Name);
|
||||
|
||||
funcVal := FScope[addr];
|
||||
func := funcVal.AsMethod();
|
||||
|
||||
SetLength(argArray, Length(Args));
|
||||
for i := 0 to High(Args) do
|
||||
argArray[i] := Args[i];
|
||||
|
||||
Result := func(argArray);
|
||||
end;
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Registration_SymbolsArePresent;
|
||||
begin
|
||||
Assert.AreNotEqual(TAddressKind.akUnresolved, FScope.Resolve('div').Kind);
|
||||
Assert.AreNotEqual(TAddressKind.akUnresolved, FScope.Resolve('mod').Kind);
|
||||
Assert.AreNotEqual(TAddressKind.akUnresolved, FScope.Resolve('Date').Kind);
|
||||
Assert.AreNotEqual(TAddressKind.akUnresolved, FScope.Resolve('Round').Kind);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Math_Float(const Op: string; A, B, Expected: Double);
|
||||
var
|
||||
res: TDataValue;
|
||||
begin
|
||||
res := Call(Op, [ValF(A), ValF(B)]);
|
||||
Assert.AreEqual(TDataValueKind.vkScalar, res.Kind);
|
||||
Assert.AreEqual(Expected, res.AsScalar.Value.AsDouble, 0.00001);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Math_Integer(const Op: string; A, B, Expected: Int64);
|
||||
var
|
||||
res: TDataValue;
|
||||
begin
|
||||
res := Call(Op, [ValI(A), ValI(B)]);
|
||||
Assert.AreEqual(TDataValueKind.vkScalar, res.Kind);
|
||||
Assert.AreEqual(TScalar.TKind.Ordinal, res.AsScalar.Kind);
|
||||
Assert.AreEqual(Expected, res.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Bitwise(const Op: string; A, B, Expected: Int64);
|
||||
var
|
||||
res: TDataValue;
|
||||
begin
|
||||
res := Call(Op, [ValI(A), ValI(B)]);
|
||||
Assert.AreEqual(TScalar.TKind.Ordinal, res.AsScalar.Kind);
|
||||
Assert.AreEqual(Expected, res.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Round_Works(A: Double; Expected: Int64);
|
||||
var
|
||||
res: TDataValue;
|
||||
begin
|
||||
res := Call('Round', [ValF(A)]);
|
||||
Assert.AreEqual(TScalar.TKind.Ordinal, res.AsScalar.Kind);
|
||||
Assert.AreEqual(Expected, res.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_Comparison_Ordinals(const Op: string; A, B: Int64; ExpectedBool: Int64);
|
||||
var
|
||||
res: TDataValue;
|
||||
begin
|
||||
res := Call(Op, [ValI(A), ValI(B)]);
|
||||
// Note: TScalar.Equal/NotEqual returns Boolean Kind now!
|
||||
// The test case expects integer 0 or 1, so we convert or check bool.
|
||||
// TScalar.Implicit(Boolean) -> Int64 (0/1) works.
|
||||
|
||||
Assert.AreEqual(TDataValueKind.vkScalar, res.Kind);
|
||||
Assert.AreEqual(TScalar.TKind.Boolean, res.AsScalar.Kind);
|
||||
|
||||
var asInt: Int64 := res.AsScalar; // Implicit conversion
|
||||
Assert.AreEqual(ExpectedBool, asInt);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_DateTime_ConstructionAndMath;
|
||||
var
|
||||
d, d2, diff: TDataValue;
|
||||
expectedDate: TDateTime;
|
||||
begin
|
||||
// 1. Test Date(Y, M, D)
|
||||
expectedDate := EncodeDate(2023, 10, 5);
|
||||
d := Call('Date', [ValI(2023), ValI(10), ValI(5)]);
|
||||
|
||||
Assert.AreEqual(TScalar.TKind.DateTime, d.AsScalar.Kind);
|
||||
Assert.AreEqual(expectedDate, d.AsScalar.Value.AsDouble, 0.001);
|
||||
|
||||
// 2. Test Date + Int (Days)
|
||||
d2 := Call('+', [d, ValI(2)]); // Add 2 days
|
||||
Assert.AreEqual(TScalar.TKind.DateTime, d2.AsScalar.Kind);
|
||||
Assert.AreEqual(expectedDate + 2, d2.AsScalar.Value.AsDouble, 0.001);
|
||||
|
||||
// 3. Test Date - Date (Diff in days)
|
||||
diff := Call('-', [d2, d]);
|
||||
Assert.AreEqual(TScalar.TKind.Float, diff.AsScalar.Kind);
|
||||
Assert.AreEqual(2.0, diff.AsScalar.Value.AsDouble, 0.001);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstRTL.RTL_DivByZero_ThrowException;
|
||||
begin
|
||||
// Integer div
|
||||
Assert.WillRaise(procedure begin Call('div', [ValI(10), ValI(0)]); end);
|
||||
// Float divide (explicit check in RTL)
|
||||
Assert.WillRaise(procedure begin Call('/', [ValF(10.0), ValF(0.0)]); end);
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,341 @@
|
||||
unit Test.Myc.Ast.Scope;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
DUnitX.TestFramework,
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Myc.Data.Value,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Ast.Types,
|
||||
Myc.Ast.Scope;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TTestMycAstScope = class
|
||||
private
|
||||
// Helper to create a fully initialized scope with a specific set of variable names
|
||||
function CreateTestScope(const VarNames: array of string; Parent: IExecutionScope = nil): IExecutionScope;
|
||||
public
|
||||
// --- Group 1: Static Layout & Builder (Parameterized) ---
|
||||
|
||||
[Test]
|
||||
[TestCase('Simple_A', 'A,0')]
|
||||
[TestCase('Simple_B', 'B,1')]
|
||||
[TestCase('Simple_C', 'C,2')]
|
||||
procedure Builder_Define_AssignsSequentialSlots(const VarName: string; ExpectedSlot: Integer);
|
||||
|
||||
[Test]
|
||||
[TestCase('Find_A', 'A,0')]
|
||||
[TestCase('Find_B', 'B,1')]
|
||||
[TestCase('Find_Missing', 'Z,-1')]
|
||||
[TestCase('Case_Sensitivity_Check', 'a,-1')] // Verifying default behavior (Case Sensitive in current impl)
|
||||
procedure Builder_FindSlot_Checks(const SearchName: string; ExpectedSlot: Integer);
|
||||
|
||||
// --- Group 2: Runtime Execution Scope & Growth ---
|
||||
|
||||
[Test]
|
||||
procedure Scope_DynamicGrowth_ResizesValuesArray;
|
||||
|
||||
[Test]
|
||||
procedure Scope_Define_DuplicateName_ThrowsException;
|
||||
|
||||
// --- Group 3: Hierarchy & Shadowing (Parameterized Depth) ---
|
||||
|
||||
[Test]
|
||||
[TestCase('Depth_0', '0')]
|
||||
[TestCase('Depth_1', '1')]
|
||||
[TestCase('Depth_5', '5')]
|
||||
procedure Scope_Resolve_WorksAtVariousDepths(Depth: Integer);
|
||||
|
||||
[Test]
|
||||
procedure Scope_DeepNesting_StressTest; // Corner Case: 100+ Levels
|
||||
|
||||
// --- Group 4: Capture & Boxing (Closures) ---
|
||||
|
||||
[Test]
|
||||
procedure Scope_Capture_Identity_RepeatedCaptureReturnsSameCell;
|
||||
|
||||
[Test]
|
||||
procedure Scope_Capture_Of_Already_Captured_Upvalue;
|
||||
|
||||
[Test]
|
||||
procedure Scope_Uninitialized_Access_ReturnsVoidOrZero;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TTestMycAstScope }
|
||||
|
||||
function TTestMycAstScope.CreateTestScope(const VarNames: array of string; Parent: IExecutionScope): IExecutionScope;
|
||||
var
|
||||
parentLayout: IScopeLayout;
|
||||
builder: IScopeBuilder;
|
||||
layout: IScopeLayout;
|
||||
descriptor: IScopeDescriptor;
|
||||
types: TArray<IStaticType>;
|
||||
i: Integer;
|
||||
begin
|
||||
if Assigned(Parent) then
|
||||
parentLayout := Parent.Descriptor.Layout
|
||||
else
|
||||
parentLayout := nil;
|
||||
|
||||
builder := TScope.CreateBuilder(parentLayout);
|
||||
for i := 0 to High(VarNames) do
|
||||
builder.Define(VarNames[i]);
|
||||
layout := builder.Build;
|
||||
|
||||
SetLength(types, Length(VarNames));
|
||||
for i := 0 to High(types) do
|
||||
types[i] := TTypes.Unknown;
|
||||
|
||||
descriptor := TScope.CreateDescriptor(layout, types);
|
||||
Result := TScope.CreateScope(Parent, descriptor, nil);
|
||||
end;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Group 1: Static Layout & Builder
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
procedure TTestMycAstScope.Builder_Define_AssignsSequentialSlots(const VarName: string; ExpectedSlot: Integer);
|
||||
var
|
||||
builder: IScopeBuilder;
|
||||
begin
|
||||
builder := TScope.CreateBuilder(nil);
|
||||
// We define the context first
|
||||
builder.Define('A'); // 0
|
||||
builder.Define('B'); // 1
|
||||
builder.Define('C'); // 2
|
||||
|
||||
// Note: Since we construct the builder fresh every time, we simulate looking up
|
||||
// in a builder that has these 3 defined.
|
||||
// However, `Define` returns the index.
|
||||
// To test Define return value correctly based on TestCase, we'd need a switch.
|
||||
// Instead, let's test FindSlot here which validates the Define logic implicitly.
|
||||
|
||||
Assert.AreEqual(ExpectedSlot, builder.FindSlot(VarName));
|
||||
end;
|
||||
|
||||
procedure TTestMycAstScope.Builder_FindSlot_Checks(const SearchName: string; ExpectedSlot: Integer);
|
||||
var
|
||||
builder: IScopeBuilder;
|
||||
begin
|
||||
builder := TScope.CreateBuilder(nil);
|
||||
builder.Define('A');
|
||||
builder.Define('B');
|
||||
|
||||
Assert.AreEqual(ExpectedSlot, builder.FindSlot(SearchName));
|
||||
end;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Group 2: Runtime Execution Scope & Growth
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
procedure TTestMycAstScope.Scope_DynamicGrowth_ResizesValuesArray;
|
||||
var
|
||||
scope: IExecutionScope;
|
||||
addr: TResolvedAddress;
|
||||
val: TDataValue;
|
||||
begin
|
||||
// Corner Case: Defining variables in a scope WITHOUT a descriptor (Dynamic Interpreter Mode)
|
||||
// The internal array must grow.
|
||||
scope := TScope.CreateScope(nil, nil, nil);
|
||||
|
||||
// Slot 0
|
||||
scope.Define('A', 10);
|
||||
|
||||
// Slot 100 (Simulation of a large jump or massive definition sequence)
|
||||
// Note: TExecutionScope.Define assigns sequential slots. To test resize, we loop.
|
||||
for var i := 1 to 100 do
|
||||
scope.Define('V' + i.ToString, i);
|
||||
|
||||
addr := scope.Resolve('V100');
|
||||
Assert.AreEqual(100, addr.SlotIndex);
|
||||
|
||||
val := scope[addr];
|
||||
Assert.AreEqual(Int64(100), val.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstScope.Scope_Define_DuplicateName_ThrowsException;
|
||||
var
|
||||
scope: IExecutionScope;
|
||||
begin
|
||||
scope := TScope.CreateScope(nil, nil, nil);
|
||||
scope.Define('X', 1);
|
||||
|
||||
Assert.WillRaise(procedure begin scope.Define('X', 2); end);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstScope.Scope_Uninitialized_Access_ReturnsVoidOrZero;
|
||||
var
|
||||
builder: IScopeBuilder;
|
||||
layout: IScopeLayout;
|
||||
descriptor: IScopeDescriptor;
|
||||
scope: IExecutionScope;
|
||||
addr: TResolvedAddress;
|
||||
begin
|
||||
// Static Layout defined, but value not set in Scope
|
||||
builder := TScope.CreateBuilder(nil);
|
||||
builder.Define('A'); // Slot 0
|
||||
layout := builder.Build;
|
||||
descriptor := TScope.CreateDescriptor(layout, [TTypes.Unknown]);
|
||||
|
||||
scope := TScope.CreateScope(nil, descriptor, nil);
|
||||
|
||||
addr.Kind := akLocalOrParent;
|
||||
addr.ScopeDepth := 0;
|
||||
addr.SlotIndex := 0;
|
||||
|
||||
// Accessing before writing
|
||||
// TDataValue defaults to Kind=vkVoid via Initialize operator usually,
|
||||
// but let's verify TExecutionScope constructor zeros memory or initializes generic array.
|
||||
Assert.IsTrue(scope[addr].IsVoid, 'Uninitialized slot should be Void');
|
||||
end;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Group 3: Hierarchy & Shadowing
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
procedure TTestMycAstScope.Scope_Resolve_WorksAtVariousDepths(Depth: Integer);
|
||||
var
|
||||
scopes: TArray<IExecutionScope>;
|
||||
i: Integer;
|
||||
addr: TResolvedAddress;
|
||||
begin
|
||||
SetLength(scopes, Depth + 1);
|
||||
|
||||
// Root defines Target
|
||||
scopes[0] := CreateTestScope(['Target']);
|
||||
|
||||
// Chain creation
|
||||
for i := 1 to Depth do
|
||||
scopes[i] := CreateTestScope([], scopes[i - 1]);
|
||||
|
||||
// Resolve from the deepest scope
|
||||
addr := scopes[Depth].Resolve('Target');
|
||||
|
||||
Assert.AreEqual(TAddressKind.akLocalOrParent, addr.Kind);
|
||||
Assert.AreEqual(Depth, addr.ScopeDepth);
|
||||
Assert.AreEqual(0, addr.SlotIndex);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstScope.Scope_DeepNesting_StressTest;
|
||||
const
|
||||
MAX_DEPTH = 100;
|
||||
var
|
||||
scopes: array[0..MAX_DEPTH] of IExecutionScope;
|
||||
i: Integer;
|
||||
addr: TResolvedAddress;
|
||||
val: TDataValue;
|
||||
begin
|
||||
// Corner Case: Deep Recursion / Stack Limits on Resolution
|
||||
|
||||
// 1. Create Root Scope with 'DeepVar' defined in the Layout
|
||||
scopes[0] := CreateTestScope(['DeepVar']);
|
||||
|
||||
// 2. Set the value for the EXISTING variable.
|
||||
// Do NOT call Define() again, as it would try to create a duplicate slot.
|
||||
addr := scopes[0].Resolve('DeepVar');
|
||||
Assert.AreEqual(TAddressKind.akLocalOrParent, addr.Kind, 'DeepVar must be resolvable in root');
|
||||
scopes[0][addr] := 999;
|
||||
|
||||
// 3. Build deep nesting chain
|
||||
for i := 1 to MAX_DEPTH do
|
||||
scopes[i] := CreateTestScope([], scopes[i - 1]);
|
||||
|
||||
// 4. Resolve from the deepest leaf
|
||||
// This forces the Resolve() method to traverse 100 parent pointers.
|
||||
addr := scopes[MAX_DEPTH].Resolve('DeepVar');
|
||||
|
||||
Assert.AreEqual(TAddressKind.akLocalOrParent, addr.Kind);
|
||||
Assert.AreEqual(MAX_DEPTH, addr.ScopeDepth, 'ScopeDepth must match nesting level');
|
||||
|
||||
// 5. Read the value through the deep link
|
||||
val := scopes[MAX_DEPTH][addr];
|
||||
Assert.AreEqual(Int64(999), val.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Group 4: Capture & Boxing (Closures) - CRITICAL
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
procedure TTestMycAstScope.Scope_Capture_Identity_RepeatedCaptureReturnsSameCell;
|
||||
var
|
||||
scope: IExecutionScope;
|
||||
addr: TResolvedAddress;
|
||||
cell1, cell2: IValueCell;
|
||||
begin
|
||||
// Corner Case: If I capture the same variable twice (e.g. two lambdas in same scope using same var),
|
||||
// they MUST share the underlying storage (Box).
|
||||
|
||||
scope := CreateTestScope(['A']);
|
||||
addr := scope.Resolve('A');
|
||||
|
||||
cell1 := scope.Capture(addr);
|
||||
cell2 := scope.Capture(addr);
|
||||
|
||||
// They must be the same interface pointer instance
|
||||
Assert.IsTrue(cell1 = cell2, 'Repeated capture must return identical interface instance');
|
||||
|
||||
// Modify one, check other
|
||||
cell1.Value := 100;
|
||||
Assert.AreEqual(Int64(100), cell2.Value.AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
procedure TTestMycAstScope.Scope_Capture_Of_Already_Captured_Upvalue;
|
||||
var
|
||||
root, mid, leaf: IExecutionScope;
|
||||
rootAddr: TResolvedAddress;
|
||||
midAddr: TResolvedAddress; // Points to root's var as an upvalue
|
||||
leafAddr: TResolvedAddress;
|
||||
cellRoot: IValueCell;
|
||||
capturedInMid: TArray<IValueCell>;
|
||||
begin
|
||||
// Scenario:
|
||||
// Root: [VarX]
|
||||
// Mid: (Captures VarX from Root) -> This creates a closure context for Mid
|
||||
// Leaf: (Captures VarX from Mid) -> This captures the *already captured* cell
|
||||
|
||||
// 1. Root
|
||||
root := CreateTestScope(['VarX']);
|
||||
rootAddr := root.Resolve('VarX');
|
||||
root[rootAddr] := 42;
|
||||
|
||||
// 2. Mid (Simulate a Lambda Scope that captured VarX)
|
||||
// To simulate this, we need to construct Mid such that it has 'capturedUpvalues' populated.
|
||||
// We manually capture from root first.
|
||||
cellRoot := root.Capture(rootAddr);
|
||||
SetLength(capturedInMid, 1);
|
||||
capturedInMid[0] := cellRoot;
|
||||
|
||||
// Mid's descriptor needs to know it has an upvalue?
|
||||
// No, IExecutionScope just holds the array. The address resolution handles mapping.
|
||||
mid := TScope.CreateScope(root, nil, capturedInMid);
|
||||
|
||||
// 3. Leaf resolves VarX.
|
||||
// If Leaf is physically inside Mid, it might refer to VarX via Mid's Upvalue list.
|
||||
// Address: Kind=akUpvalue, Slot=0 (index into capturedInMid)
|
||||
leafAddr.Kind := akUpvalue;
|
||||
leafAddr.SlotIndex := 0;
|
||||
// Note: ScopeDepth irrelevant for akUpvalue access
|
||||
|
||||
// 4. Test Access from Leaf via Upvalue chain
|
||||
// leaf does not have its own captured array yet, but we ask it to GET the value at that address
|
||||
// Actually, 'leaf' typically represents the *execution* of the lambda.
|
||||
// If the lambda code says "get upvalue 0", the evaluator calls GetValues(akUpvalue, 0).
|
||||
|
||||
// Let's simulate Leaf access:
|
||||
leaf := TScope.CreateScope(mid, nil, capturedInMid); // Leaf shares the capture array for this test context
|
||||
|
||||
Assert.AreEqual(Int64(42), leaf[leafAddr].AsScalar.Value.AsInt64);
|
||||
|
||||
// 5. Modify via Leaf reference
|
||||
leaf[leafAddr] := 99;
|
||||
|
||||
// Check Root
|
||||
Assert.AreEqual(Int64(99), root[rootAddr].AsScalar.Value.AsInt64);
|
||||
end;
|
||||
|
||||
end.
|
||||
+2
-2
@@ -32,9 +32,9 @@ uses
|
||||
Myc.Data.Decimal in '..\Src\Data\Myc.Data.Decimal.pas',
|
||||
TestDataDecimal in 'TestDataDecimal.pas',
|
||||
TestDataPOD in 'TestDataPOD.pas',
|
||||
Test.Ast.Interpreter.Scope in 'Test.Ast.Interpreter.Scope.pas',
|
||||
Myc.Data.Chunks in '..\Src\Data\Myc.Data.Chunks.pas',
|
||||
Test.Myc.Data.Value in 'Test.Myc.Data.Value.pas';
|
||||
Test.Myc.Ast.Scope in 'AST\Test.Myc.Ast.Scope.pas',
|
||||
Test.Myc.Ast.RTL in 'AST\Test.Myc.Ast.RTL.pas';
|
||||
|
||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||
{$IFNDEF TESTINSIGHT}
|
||||
|
||||
+2
-2
@@ -135,9 +135,9 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
||||
<DCCReference Include="..\Src\Data\Myc.Data.Decimal.pas"/>
|
||||
<DCCReference Include="TestDataDecimal.pas"/>
|
||||
<DCCReference Include="TestDataPOD.pas"/>
|
||||
<DCCReference Include="Test.Ast.Interpreter.Scope.pas"/>
|
||||
<DCCReference Include="..\Src\Data\Myc.Data.Chunks.pas"/>
|
||||
<DCCReference Include="Test.Myc.Data.Value.pas"/>
|
||||
<DCCReference Include="AST\Test.Myc.Ast.Scope.pas"/>
|
||||
<DCCReference Include="AST\Test.Myc.Ast.RTL.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
unit Test.Ast.Interpreter.Scope;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Ast,
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Binding,
|
||||
Myc.Ast.Evaluator,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Data.Scalar,
|
||||
Myc.Data.Value,
|
||||
DUnitX.TestFramework;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TInterpreterScopeTests = class(TObject)
|
||||
private
|
||||
FGlobalScope: IExecutionScope;
|
||||
function Execute(const ANode: IAstNode): TDataValue;
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
[TearDown]
|
||||
procedure TearDown;
|
||||
|
||||
[Test]
|
||||
procedure Test_DeeplyNestedLambda_ModifiesUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_SeparateClosures_ShareSameUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_NestedLambda_CapturesParameter;
|
||||
|
||||
[Test]
|
||||
procedure Test_VariableShadowing_DoesNotAffectUpvalue;
|
||||
|
||||
[Test]
|
||||
procedure Test_CaptureFromGlobalScope;
|
||||
|
||||
[Test]
|
||||
procedure Test_ClosureCaptureWithParentScopeReallocation;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TInterpreterScopeTests }
|
||||
|
||||
function TInterpreterScopeTests.Execute(const ANode: IAstNode): TDataValue;
|
||||
var
|
||||
boundNode: IAstNode;
|
||||
descriptor: IScopeDescriptor;
|
||||
runtimeScope: IExecutionScope;
|
||||
visitor: IEvaluatorVisitor;
|
||||
begin
|
||||
// Helper to encapsulate the Bind -> CreateScope -> Evaluate pattern.
|
||||
// The binder needs an evaluator factory to expand macros (even if there are none).
|
||||
boundNode :=
|
||||
TAstBinder.Bind(
|
||||
FGlobalScope,
|
||||
ANode,
|
||||
descriptor,
|
||||
function(const Scope: IExecutionScope): IEvaluatorVisitor begin Result := TEvaluatorVisitor.Create(Scope); end
|
||||
);
|
||||
runtimeScope := descriptor.CreateScope(FGlobalScope);
|
||||
visitor := TEvaluatorVisitor.Create(runtimeScope);
|
||||
Result := visitor.Execute(boundNode);
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Setup;
|
||||
begin
|
||||
FGlobalScope := TAst.CreateScope(nil);
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.TearDown;
|
||||
begin
|
||||
FGlobalScope := nil;
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_DeeplyNestedLambda_ModifiesUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// This is our original, simple test case with three nested lambdas.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('outer'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('inner'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('innermost'),
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Assign(
|
||||
TAst.Identifier('x'),
|
||||
TAst.BinaryExpr(
|
||||
TAst.Identifier('x'),
|
||||
TScalar.TBinaryOp.Add,
|
||||
TAst.Constant(TScalar.FromInt64(5))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('innermost'), [])
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('inner'), []),
|
||||
TAst.Identifier('x')
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(TAst.Identifier('finalResult'), TAst.FunctionCall(TAst.Identifier('outer'), []))
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(15, resultValue.AsScalar.Value.AsInt64, 'The final result should be 15.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_SeparateClosures_ShareSameUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// This is our more complex "modifier/reader" test case.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('modifier'),
|
||||
TAst.LambdaExpr(
|
||||
[], // Outer modifier shell
|
||||
TAst.LambdaExpr(
|
||||
[], // Inner closure that is returned
|
||||
TAst.Assign(
|
||||
TAst.Identifier('a'),
|
||||
TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Add, TAst.Constant(TScalar.FromInt64(5)))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(TAst.Identifier('reader'), TAst.LambdaExpr([], TAst.Identifier('a'))),
|
||||
TAst.VarDecl(TAst.Identifier('innermost_closure'), TAst.FunctionCall(TAst.Identifier('modifier'), [])),
|
||||
TAst.FunctionCall(TAst.Identifier('innermost_closure'), []),
|
||||
TAst.FunctionCall(TAst.Identifier('reader'), [])
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(15, resultValue.AsScalar.Value.AsInt64, 'The final result should be 15.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_NestedLambda_CapturesParameter;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Tests if a nested lambda can correctly capture a PARAMETER of its parent lambda.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('factory'),
|
||||
TAst.LambdaExpr(
|
||||
[TAst.Identifier('p')], // Parameter 'p'
|
||||
TAst.LambdaExpr(
|
||||
[], // Returned lambda captures 'p'
|
||||
TAst.BinaryExpr(TAst.Identifier('p'), TScalar.TBinaryOp.Multiply, TAst.Constant(TScalar.FromInt64(2)))
|
||||
)
|
||||
)
|
||||
),
|
||||
TAst.VarDecl(
|
||||
TAst.Identifier('multiplier'),
|
||||
TAst.FunctionCall(TAst.Identifier('factory'), [TAst.Constant(TScalar.FromInt64(21))])
|
||||
),
|
||||
TAst.FunctionCall(TAst.Identifier('multiplier'), [])
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(42, resultValue.AsScalar.Value.AsInt64, 'The result of 21 * 2 should be 42.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_VariableShadowing_DoesNotAffectUpvalue;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Tests that a local variable 'x' correctly "shadows" a parent's variable 'x'.
|
||||
// The modification of the inner 'x' must not affect the outer 'x'.
|
||||
mainBlock :=
|
||||
TAst.Block(
|
||||
[
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(10))),
|
||||
TAst.FunctionCall(
|
||||
TAst.LambdaExpr(
|
||||
[],
|
||||
TAst.Block(
|
||||
[
|
||||
// This 'x' should shadow the outer 'x'.
|
||||
TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(50))),
|
||||
TAst.Assign(TAst.Identifier('x'), TAst.Constant(TScalar.FromInt64(99)))
|
||||
]
|
||||
)
|
||||
),
|
||||
[]
|
||||
),
|
||||
// This final expression should return the value of the original, outer 'x'.
|
||||
TAst.Identifier('x')
|
||||
]
|
||||
);
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(10, resultValue.AsScalar.Value.AsInt64, 'The outer "x" should remain unchanged.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_CaptureFromGlobalScope;
|
||||
var
|
||||
mainBlock: IAstNode;
|
||||
resultValue: TDataValue;
|
||||
begin
|
||||
// Defines a variable in the global scope and ensures a simple script can access it.
|
||||
FGlobalScope.Define('g', TScalar.FromInt64(99));
|
||||
mainBlock := TAst.BinaryExpr(TAst.Identifier('g'), TScalar.TBinaryOp.Add, TAst.Constant(TScalar.FromInt64(1)));
|
||||
|
||||
resultValue := Execute(mainBlock);
|
||||
Assert.AreEqual<Int64>(100, resultValue.AsScalar.Value.AsInt64, 'Should be able to access variables from the parent scope.');
|
||||
end;
|
||||
|
||||
procedure TInterpreterScopeTests.Test_ClosureCaptureWithParentScopeReallocation;
|
||||
var
|
||||
rootScope: IExecutionScope;
|
||||
parentScope: IExecutionScope;
|
||||
lambdaScope: IExecutionScope;
|
||||
addressOfX_from_lambda: TResolvedAddress;
|
||||
addressOfX_in_parent: TResolvedAddress;
|
||||
capturedCell: IValueCell;
|
||||
valueFromClosure: TDataValue;
|
||||
begin
|
||||
// 1. Setup scopes: root -> parent -> lambda
|
||||
rootScope := TScope.CreateScope(nil, nil, nil);
|
||||
parentScope := TScope.CreateScope(rootScope, nil, nil);
|
||||
lambdaScope := TScope.CreateScope(parentScope, nil, nil);
|
||||
|
||||
// 2. Define a variable 'x' in the parent scope. It will be at slot 0.
|
||||
parentScope.Define('x', 10);
|
||||
addressOfX_in_parent := TResolvedAddress.Create(akLocalOrParent, 0, 0);
|
||||
Assert.AreEqual(Int64(10), parentScope[addressOfX_in_parent].AsScalar.Value.AsInt64);
|
||||
|
||||
// 3. From the lambda's perspective, 'x' is one level up (ScopeDepth=1) at slot 0.
|
||||
addressOfX_from_lambda := TResolvedAddress.Create(akLocalOrParent, 1, 0);
|
||||
|
||||
// 4. Capture 'x' into a value cell, simulating a closure.
|
||||
// This creates the buggy TValueRef that holds a direct reference to the parent's internal array.
|
||||
capturedCell := lambdaScope.Capture(addressOfX_from_lambda);
|
||||
Assert.AreEqual(Int64(10), capturedCell.Value.AsScalar.Value.AsInt64, 'Initial captured value should be correct');
|
||||
|
||||
// 5. Trigger the bug: Define another variable in the parent scope.
|
||||
// This forces a SetLength on the internal FValues array, which may cause a reallocation.
|
||||
parentScope.Define('y', 20);
|
||||
|
||||
// 6. Update the original variable 'x' in the parent scope to a new value.
|
||||
parentScope[addressOfX_in_parent] := 99;
|
||||
Assert.AreEqual(Int64(99), parentScope[addressOfX_in_parent].AsScalar.Value.AsInt64, 'Value in parent scope should be updated');
|
||||
|
||||
// 7. Read the value from the captured cell again.
|
||||
// The test will fail here. The captured cell still points to the old, orphaned memory block
|
||||
// where the value of x is still 10, not the new value 99.
|
||||
valueFromClosure := capturedCell.Value;
|
||||
Assert.AreEqual(
|
||||
Int64(99),
|
||||
valueFromClosure.AsScalar.Value.AsInt64,
|
||||
'The captured cell must reflect changes in the parent scope after reallocation'
|
||||
);
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -1,173 +0,0 @@
|
||||
unit Test.Myc.Data.Value;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
DUnitX.TestFramework,
|
||||
System.SysUtils,
|
||||
System.Threading,
|
||||
Myc.Data.Value,
|
||||
Myc.Data.Scalar;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TTestDataValue = class(TObject)
|
||||
private
|
||||
type
|
||||
// Simple object for interface tests
|
||||
TTestObject = class(TInterfacedObject, IInterface)
|
||||
public
|
||||
ID: Integer;
|
||||
end;
|
||||
public
|
||||
[Test]
|
||||
procedure TestReset_Scalar;
|
||||
[Test]
|
||||
procedure TestReset_Interface;
|
||||
[Test]
|
||||
procedure TestCompareAndSet_Scalar;
|
||||
[Test]
|
||||
procedure TestCompareAndSet_Interface;
|
||||
[Test]
|
||||
procedure TestKindImmutability_ThrowsException;
|
||||
[Test]
|
||||
procedure TestAtomicity_ConcurrentIncrement;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TTestDataValue }
|
||||
|
||||
procedure TTestDataValue.TestReset_Scalar;
|
||||
var
|
||||
val: TDataValue;
|
||||
oldVal: TDataValue;
|
||||
begin
|
||||
// Test with Int64
|
||||
val := 10;
|
||||
oldVal := val.Reset(20);
|
||||
Assert.AreEqual(Int64(20), val.AsScalar.Value.AsInt64, 'Value should be updated to 20');
|
||||
Assert.AreEqual(Int64(10), oldVal.AsScalar.Value.AsInt64, 'Reset should return the old value 10');
|
||||
|
||||
// Test with Double
|
||||
val := 10.5;
|
||||
oldVal := val.Reset(20.5);
|
||||
Assert.AreEqual(Double(20.5), val.AsScalar.Value.AsDouble, 'Value should be updated to 20.5');
|
||||
Assert.AreEqual(Double(10.5), oldVal.AsScalar.Value.AsDouble, 'Reset should return the old value 10.5');
|
||||
end;
|
||||
|
||||
procedure TTestDataValue.TestReset_Interface;
|
||||
var
|
||||
objA, objB: IInterface;
|
||||
val: TDataValue;
|
||||
oldVal: TDataValue;
|
||||
begin
|
||||
objA := TTestObject.Create;
|
||||
objB := TTestObject.Create;
|
||||
|
||||
val := TDataValue.FromIntf(objA);
|
||||
oldVal := val.Reset(TDataValue.FromIntf(objB));
|
||||
|
||||
Assert.AreSame(objB, val.AsIntf<IInterface>, 'Value should be updated to objB');
|
||||
Assert.AreSame(objA, oldVal.AsIntf<IInterface>, 'Reset should return the old value objA');
|
||||
end;
|
||||
|
||||
procedure TTestDataValue.TestCompareAndSet_Scalar;
|
||||
var
|
||||
val: TDataValue;
|
||||
begin
|
||||
val := 100;
|
||||
|
||||
// Successful CAS
|
||||
Assert.IsTrue(val.CompareAndSet(100, 200), 'CAS should succeed when expected value matches');
|
||||
Assert.AreEqual(Int64(200), val.AsScalar.Value.AsInt64, 'Value should be 200 after successful CAS');
|
||||
|
||||
// Failing CAS
|
||||
Assert.IsFalse(val.CompareAndSet(100, 300), 'CAS should fail when expected value does not match');
|
||||
Assert.AreEqual(Int64(200), val.AsScalar.Value.AsInt64, 'Value should remain 200 after failed CAS');
|
||||
end;
|
||||
|
||||
procedure TTestDataValue.TestCompareAndSet_Interface;
|
||||
var
|
||||
objA, objB, objC: IInterface;
|
||||
val: TDataValue;
|
||||
begin
|
||||
objA := TTestObject.Create;
|
||||
objB := TTestObject.Create;
|
||||
objC := TTestObject.Create;
|
||||
val := TDataValue.FromIntf(objA);
|
||||
|
||||
// Successful CAS
|
||||
Assert.IsTrue(val.CompareAndSet(TDataValue.FromIntf(objA), TDataValue.FromIntf(objB)), 'CAS should succeed for interfaces');
|
||||
Assert.AreSame(objB, val.AsIntf<IInterface>, 'Value should be objB after successful CAS');
|
||||
|
||||
// Failing CAS
|
||||
Assert.IsFalse(val.CompareAndSet(TDataValue.FromIntf(objA), TDataValue.FromIntf(objC)), 'CAS should fail for interfaces');
|
||||
Assert.AreSame(objB, val.AsIntf<IInterface>, 'Value should remain objB after failed CAS');
|
||||
end;
|
||||
|
||||
procedure TTestDataValue.TestKindImmutability_ThrowsException;
|
||||
var
|
||||
scalarVal, textVal: TDataValue;
|
||||
begin
|
||||
scalarVal := 10;
|
||||
textVal := 'hello';
|
||||
|
||||
// Test Reset
|
||||
Assert.WillRaise(procedure begin scalarVal.Reset(textVal); end, EArgumentException, 'Reset must throw exception on kind mismatch');
|
||||
|
||||
// Test CompareAndSet
|
||||
Assert.WillRaise(
|
||||
procedure
|
||||
begin
|
||||
// NewValue has wrong kind
|
||||
scalarVal.CompareAndSet(10, textVal);
|
||||
end,
|
||||
EArgumentException,
|
||||
'CompareAndSet must throw exception on kind mismatch'
|
||||
);
|
||||
|
||||
// Expected value doesn't match kind, should just fail silently (return False)
|
||||
Assert.IsFalse(scalarVal.CompareAndSet(textVal, 20), 'CAS should return False if kinds of self and expected differ');
|
||||
end;
|
||||
|
||||
procedure TTestDataValue.TestAtomicity_ConcurrentIncrement;
|
||||
const
|
||||
NumThreads = 8;
|
||||
IncrementsPerThread = 25000;
|
||||
var
|
||||
sharedValue: TDataValue;
|
||||
tasks: array of ITask;
|
||||
i: Integer;
|
||||
begin
|
||||
sharedValue := 0; // TDataValue of kind vkScalar, Ordinal
|
||||
SetLength(tasks, NumThreads);
|
||||
|
||||
for i := 0 to High(tasks) do
|
||||
begin
|
||||
tasks[i] :=
|
||||
TTask.Run(
|
||||
procedure
|
||||
var
|
||||
j: Integer;
|
||||
oldVal, newVal: TDataValue;
|
||||
begin
|
||||
for j := 1 to IncrementsPerThread do
|
||||
begin
|
||||
// This is the classic lock-free swap loop
|
||||
repeat
|
||||
oldVal := sharedValue; // Read current value
|
||||
newVal := oldVal.AsScalar.Value.AsInt64 + 1;
|
||||
until sharedValue.CompareAndSet(oldVal, newVal);
|
||||
end;
|
||||
end
|
||||
);
|
||||
end;
|
||||
|
||||
TTask.WaitForAll(tasks);
|
||||
|
||||
const Expected = NumThreads * IncrementsPerThread;
|
||||
Assert.AreEqual(Int64(Expected), sharedValue.AsScalar.Value.AsInt64, 'Concurrent increments should result in the correct total sum');
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -28,8 +28,6 @@ type
|
||||
procedure TestScalarArray;
|
||||
[Test]
|
||||
procedure TestScalarTuple;
|
||||
[Test]
|
||||
procedure TestScalarRecordAndDefinition;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -80,39 +78,6 @@ begin
|
||||
Assert.AreEqual(Int64(0), Length(arr.Items), 'Empty array length should be zero');
|
||||
end;
|
||||
|
||||
procedure TTestPOD.TestScalarRecordAndDefinition;
|
||||
var
|
||||
recDef: TScalarRecordDefinition;
|
||||
values: TArray<TScalar.TValue>;
|
||||
rec: TScalarRecord;
|
||||
mismatchedValues: TArray<TScalar.TValue>;
|
||||
begin
|
||||
// 1. Create a definition
|
||||
recDef :=
|
||||
TScalarRecordDefinition
|
||||
.Create([TScalarRecordField.Create('ID', TScalar.TKind.Ordinal), TScalarRecordField.Create('Price', TScalar.TKind.Float)]);
|
||||
Assert.AreEqual(Int64(2), Length(recDef.Fields), 'Record definition field count mismatch');
|
||||
Assert.AreEqual('Price', recDef.Fields[1].Name, 'Record definition field name mismatch');
|
||||
|
||||
// 2. Create a matching set of values
|
||||
values := [TScalar.FromInt64(99).Value, TScalar.FromDouble(19.95).Value];
|
||||
|
||||
// 3. Create the record
|
||||
rec := TScalarRecord.Create(recDef, values);
|
||||
Assert.AreEqual(Int64(2), Length(rec.Fields), 'Record field count mismatch');
|
||||
Assert.AreEqual(19.95, rec.Fields[1].AsDouble, 1E-12, 'Record field value mismatch');
|
||||
Assert.AreEqual(Int64(99), rec.Items['ID'].Value.AsInt64, 'Record item by name mismatch');
|
||||
Assert.AreEqual(TScalar.TKind.Float, rec.Items['Price'].Kind, 'Record item kind by name mismatch');
|
||||
|
||||
// 4. Test assertion for mismatched field count
|
||||
mismatchedValues := [TScalar.FromInt64(1).Value];
|
||||
Assert.WillRaise(
|
||||
procedure begin rec := TScalarRecord.Create(recDef, mismatchedValues); end,
|
||||
EAssertionFailed,
|
||||
'Mismatched field/value count should raise an assertion'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestPOD.TestScalarTuple;
|
||||
var
|
||||
tuple: TScalarTuple;
|
||||
|
||||
Reference in New Issue
Block a user