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; 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.