packages feed

tala-compiler-0.1.0: test/CompileTimeParityTests.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

module CompileTimeParityTests (compileTimeParityTests) where

import Data.Bits (shiftL)
import Progmasoft.Tala.AST
import Progmasoft.Tala.ConstantEvaluation
import Progmasoft.Tala.TemplateValue

compileTimeParityTests :: [(String, Bool)]
compileTimeParityTests =
    [ ("constant and template evaluators agree across compile-time magnitude boundaries", boundaryBinaryParity)
    , ("constant and template evaluators agree for unary boundary operations", boundaryUnaryParity)
    , ("constant and template evaluators map arithmetic failures consistently", failureParity)
    ]

-- The front-end uses two evaluator entry points for different semantic
-- contexts. This matrix compares their externally visible values and failure
-- classes without requiring either evaluator to call the other.
boundaryBinaryParity :: Bool
boundaryBinaryParity =
    and
        [ normalizeConstant valueResultType (evaluateConstantInteger expression)
            == normalizeTemplate (evaluateTemplateValue template)
        | (operator, _) <- binaryCases
        , left <- boundaryValues
        , right <- boundaryValues
        , let valueResultType = binaryResultType operator
        , let expression = BinaryExpression testSpan operator (integerExpression left) (integerExpression right) valueResultType
        , let template = TemplateBinarySyntax testSpan operator (integerSyntax left) (integerSyntax right)
        ]
        && length binaryCases == 20

boundaryUnaryParity :: Bool
boundaryUnaryParity =
    and
        [ normalizeConstant valueResultType (evaluateConstantInteger expression)
            == normalizeTemplate (evaluateTemplateValue template)
        | (operator, valueResultType) <- unaryCases
        , value <- boundaryValues
        , let expression = UnaryExpression testSpan operator (integerExpression value) valueResultType
        , let template = TemplateUnarySyntax testSpan operator (integerSyntax value)
        ]

failureParity :: Bool
failureParity =
    and
        [ sameBinaryResult Divide 8 0 DivisionByZeroOutcome
        , sameBinaryResult FloorDivide 8 0 FloorDivisionByZeroOutcome
        , sameBinaryResult Remainder 8 0 RemainderByZeroOutcome
        , sameBinaryResult Power 2 (-1) NegativeExponentOutcome
        , sameBinaryResult Add (compileTimeLimit - 1) 1 LimitExceededOutcome
        ]
    where
        sameBinaryResult operator left right expected =
            normalizeConstant (binaryResultType operator) (evaluateConstantInteger (binaryExpression operator left right))
                == expected
                && normalizeTemplate (evaluateTemplateValue (binarySyntax operator left right)) == expected

binaryCases :: [(BinaryOperator, Type)]
binaryCases =
    [ (Add, intType)
    , (Subtract, intType)
    , (Multiply, intType)
    , (Divide, intType)
    , (FloorDivide, intType)
    , (Remainder, intType)
    , (Power, intType)
    , (ShiftLeft, intType)
    , (ShiftRight, intType)
    , (BitwiseAnd, intType)
    , (BitwiseXor, intType)
    , (BitwiseOr, intType)
    , (LessThan, boolType)
    , (LessEqual, boolType)
    , (GreaterThan, boolType)
    , (GreaterEqual, boolType)
    , (Equal, boolType)
    , (NotEqual, boolType)
    , (LogicalAnd, boolType)
    , (LogicalOr, boolType)
    ]

unaryCases :: [(UnaryOperator, Type)]
unaryCases = [(UnaryPlus, intType), (UnaryNegate, intType), (LogicalNot, boolType), (BitwiseNot, intType)]

binaryResultType :: BinaryOperator -> Type
binaryResultType operation = case operation of
    LessThan -> boolType
    LessEqual -> boolType
    GreaterThan -> boolType
    GreaterEqual -> boolType
    Equal -> boolType
    NotEqual -> boolType
    LogicalAnd -> boolType
    LogicalOr -> boolType
    _ -> intType

boundaryValues :: [Integer]
boundaryValues =
    [ negate (compileTimeLimit - 1)
    , negate (compileTimeLimit `quot` 2)
    , -2
    , -1
    , 0
    , 1
    , 2
    , compileTimeLimit `quot` 2
    , compileTimeLimit - 1
    ]

normalizeConstant :: Type -> Either ConstantIntegerError (Maybe Integer) -> NormalizedOutcome
normalizeConstant valueType result = case result of
    Right (Just value)
        | valueType == boolType -> BooleanOutcome (value /= 0)
        | otherwise -> IntegerOutcome value
    Right Nothing -> NonconstantOutcome
    Left ConstantDivisionByZero -> DivisionByZeroOutcome
    Left ConstantFloorDivisionByZero -> FloorDivisionByZeroOutcome
    Left ConstantRemainderByZero -> RemainderByZeroOutcome
    Left ConstantNegativeExponent -> NegativeExponentOutcome
    Left ConstantEvaluationLimitExceeded -> LimitExceededOutcome

normalizeTemplate :: Either TemplateValueError TemplateValue -> NormalizedOutcome
normalizeTemplate result = case result of
    Right (IntegerTemplateValue value) -> IntegerOutcome value
    Right (BooleanTemplateValue value) -> BooleanOutcome value
    Right (CharacterTemplateValue value) -> CharacterOutcome value
    Right _ -> OtherFailureOutcome
    Left TemplateValueDivisionByZero -> DivisionByZeroOutcome
    Left TemplateValueFloorDivisionByZero -> FloorDivisionByZeroOutcome
    Left TemplateValueRemainderByZero -> RemainderByZeroOutcome
    Left (TemplateValueNegativeExponent _) -> NegativeExponentOutcome
    Left TemplateValueEvaluationLimitExceeded -> LimitExceededOutcome
    Left _ -> OtherFailureOutcome

data NormalizedOutcome
    = IntegerOutcome Integer
    | BooleanOutcome Bool
    | CharacterOutcome Integer
    | DivisionByZeroOutcome
    | FloorDivisionByZeroOutcome
    | RemainderByZeroOutcome
    | NegativeExponentOutcome
    | LimitExceededOutcome
    | NonconstantOutcome
    | OtherFailureOutcome
    deriving (Eq, Ord, Read, Show)

integerExpression :: Integer -> Expression name Type
integerExpression value = LiteralExpression testSpan (IntegerLiteral value) intType

binaryExpression :: BinaryOperator -> Integer -> Integer -> Expression name Type
binaryExpression operator left right =
    BinaryExpression testSpan operator (integerExpression left) (integerExpression right) (binaryResultType operator)

integerSyntax :: Integer -> TemplateValueSyntax
integerSyntax = TemplateIntegerSyntax testSpan

binarySyntax :: BinaryOperator -> Integer -> Integer -> TemplateValueSyntax
binarySyntax operator left right = TemplateBinarySyntax testSpan operator (integerSyntax left) (integerSyntax right)

compileTimeLimit :: Integer
compileTimeLimit = 1 `shiftL` 65536

testSpan :: SourceSpan
testSpan = SourceSpan "compile-time-parity.tala" (SourcePosition 1 1) (SourcePosition 1 2)