packages feed

tala-frontend-0.1.0: src/Progmasoft/Tala/TemplateValue.hs

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

{- | Exact evaluation for compile-time values embedded in type syntax.

This module intentionally does not reuse runtime numeric coercion. Template
values participate in specialization identity, so host overflow, floating
approximation, and truthy conversions would make artifacts non-portable.
-}
module Progmasoft.Tala.TemplateValue
    ( TemplateValueError (..)
    , evaluateTemplateValue
    , evaluateFixedArraySize
    , renderTemplateValueError
    , templateValueSyntaxSpan
    ) where

import Data.Bits (complement, xor, (.&.), (.|.))
import Progmasoft.Tala.AST
import Progmasoft.Tala.IntegerEvaluation
    ( CompileTimeIntegerError (..)
    , checkCompileTimeInteger
    , evaluateCompileTimePower
    , evaluateCompileTimeShiftLeft
    , evaluateCompileTimeShiftRight
    , multiplyCompileTimeIntegers
    )

-- | Failure while evaluating a template constant or fixed-array extent.
data TemplateValueError
    = -- | Name is not a compile-time constant.
      TemplateValueIsNotConstant QualifiedName
    | -- | Division has a zero divisor.
      TemplateValueDivisionByZero
    | -- | Floor division has a zero divisor.
      TemplateValueFloorDivisionByZero
    | -- | Remainder has a zero divisor.
      TemplateValueRemainderByZero
    | -- | Integer exponent is negative.
      TemplateValueNegativeExponent Integer
    | -- | Exact arithmetic exceeded its resource bound.
      TemplateValueEvaluationLimitExceeded
    | -- | Fixed-array extent is not integral.
      TemplateValueRequiresInteger TemplateValue
    | -- | Fixed-array extent is negative.
      TemplateValueNegativeArraySize Integer
    deriving (Eq, Ord, Read, Show)

data ExactValue
    = ExactInteger Integer
    | ExactBoolean Bool
    | ExactCharacter Integer
    deriving (Eq, Ord, Read, Show)

-- | Evaluate type-syntax constants exactly, preserving their source-level kind.
evaluateTemplateValue :: TemplateValueSyntax -> Either TemplateValueError TemplateValue
evaluateTemplateValue syntax = exactToTemplate <$> evaluateExact syntax

-- | Evaluate a fixed-array extent and reject negative or non-integer values.
evaluateFixedArraySize :: TemplateValueSyntax -> Either TemplateValueError TemplateValue
evaluateFixedArraySize syntax = do
    value <- evaluateTemplateValue syntax
    case value of
        IntegerTemplateValue integer
            | integer < 0 -> Left (TemplateValueNegativeArraySize integer)
            | otherwise -> Right value
        CharacterTemplateValue scalar -> Right (IntegerTemplateValue scalar)
        other -> Left (TemplateValueRequiresInteger other)

evaluateExact :: TemplateValueSyntax -> Either TemplateValueError ExactValue
evaluateExact syntax = case syntax of
    TemplateIntegerSyntax _ value -> ExactInteger <$> checkedTemplateInteger value
    TemplateCharacterSyntax _ value -> ExactCharacter <$> checkedTemplateInteger value
    TemplateBooleanSyntax _ value -> Right (ExactBoolean value)
    TemplateNameSyntax _ name -> Left (TemplateValueIsNotConstant name)
    TemplateUnarySyntax _ operator operand -> do
        value <- evaluateExact operand
        evaluateUnary operator value
    TemplateBinarySyntax _ operator left right -> do
        -- Both operands must be exact values before specialization identity can
        -- be formed; failures are reported here rather than deferred to Core.
        leftValue <- evaluateExact left
        rightValue <- evaluateExact right
        evaluateBinary operator leftValue rightValue

evaluateUnary :: UnaryOperator -> ExactValue -> Either TemplateValueError ExactValue
evaluateUnary operator value = case operator of
    UnaryPlus -> requireInteger value >>= (ExactInteger <$>) . checkedTemplateInteger
    UnaryNegate -> requireInteger value >>= (ExactInteger <$>) . checkedTemplateInteger . negate
    LogicalNot -> ExactBoolean . not <$> requireBooleanContext value
    BitwiseNot -> requireInteger value >>= (ExactInteger <$>) . checkedTemplateInteger . complement

evaluateBinary :: BinaryOperator -> ExactValue -> ExactValue -> Either TemplateValueError ExactValue
evaluateBinary operator left right = case operator of
    Add -> integerBinary (+)
    Subtract -> integerBinary (-)
    Multiply -> integerBinaryEither multiplyCompileTimeIntegers
    Divide -> do
        divisor <- requireInteger right
        if divisor == 0
            then Left TemplateValueDivisionByZero
            else do
                dividend <- requireInteger left
                ExactInteger <$> checkedTemplateInteger (dividend `quot` divisor)
    FloorDivide -> do
        divisor <- requireInteger right
        if divisor == 0
            then Left TemplateValueFloorDivisionByZero
            else do
                dividend <- requireInteger left
                ExactInteger <$> checkedTemplateInteger (roundedIntegerDivision dividend divisor)
    Remainder -> do
        divisor <- requireInteger right
        if divisor == 0
            then Left TemplateValueRemainderByZero
            else do
                dividend <- requireInteger left
                ExactInteger <$> checkedTemplateInteger (dividend `rem` divisor)
    Power -> do
        exponentValue <- requireInteger right
        if exponentValue < 0
            then Left (TemplateValueNegativeExponent exponentValue)
            else do
                base <- requireInteger left
                ExactInteger <$> mapCompileTimeError exponentValue (evaluateCompileTimePower base exponentValue)
    ShiftLeft -> integerBinaryEither evaluateCompileTimeShiftLeft
    ShiftRight -> integerBinaryEither evaluateCompileTimeShiftRight
    BitwiseAnd -> integerBinary (.&.)
    BitwiseXor -> integerBinary xor
    BitwiseOr -> integerBinary (.|.)
    LessThan -> comparison (<)
    LessEqual -> comparison (<=)
    GreaterThan -> comparison (>)
    GreaterEqual -> comparison (>=)
    Equal -> Right (ExactBoolean (left == right))
    NotEqual -> Right (ExactBoolean (left /= right))
    LogicalAnd -> logical (&&)
    LogicalOr -> logical (||)
    where
        integerBinary operation = do
            lhs <- requireInteger left
            rhs <- requireInteger right
            ExactInteger <$> checkedTemplateInteger (operation lhs rhs)
        integerBinaryEither operation = do
            lhs <- requireInteger left
            rhs <- requireInteger right
            ExactInteger <$> mapCompileTimeError rhs (operation lhs rhs)
        comparison operation = do
            lhs <- requireInteger left
            rhs <- requireInteger right
            Right (ExactBoolean (operation lhs rhs))
        logical operation = do
            lhs <- requireBooleanContext left
            rhs <- requireBooleanContext right
            Right (ExactBoolean (operation lhs rhs))

roundedIntegerDivision :: Integer -> Integer -> Integer
roundedIntegerDivision dividend divisor =
    let (quotient, remainder) = dividend `quotRem` divisor
        adjustment = signum dividend * signum divisor
     in if 2 * abs remainder >= abs divisor then quotient + adjustment else quotient

requireInteger :: ExactValue -> Either TemplateValueError Integer
requireInteger value = case value of
    ExactInteger integer -> Right integer
    ExactCharacter scalar -> Right scalar
    ExactBoolean boolean -> Left (TemplateValueRequiresInteger (BooleanTemplateValue boolean))

requireBooleanContext :: ExactValue -> Either TemplateValueError Bool
requireBooleanContext value = case value of
    ExactBoolean boolean -> Right boolean
    ExactInteger integer -> Right (integer /= 0)
    ExactCharacter scalar -> Right (scalar /= 0)

exactToTemplate :: ExactValue -> TemplateValue
exactToTemplate value = case value of
    ExactInteger integer -> IntegerTemplateValue integer
    ExactBoolean boolean -> BooleanTemplateValue boolean
    ExactCharacter scalar -> CharacterTemplateValue scalar

-- | Return the source span enclosing one template-value syntax expression.
templateValueSyntaxSpan :: TemplateValueSyntax -> SourceSpan
templateValueSyntaxSpan syntax = case syntax of
    TemplateIntegerSyntax spanValue _ -> spanValue
    TemplateCharacterSyntax spanValue _ -> spanValue
    TemplateBooleanSyntax spanValue _ -> spanValue
    TemplateNameSyntax spanValue _ -> spanValue
    TemplateUnarySyntax spanValue _ _ -> spanValue
    TemplateBinarySyntax spanValue _ _ _ -> spanValue

-- | Render a constant-evaluation failure for a source diagnostic.
renderTemplateValueError :: TemplateValueError -> String
renderTemplateValueError issue = case issue of
    TemplateValueIsNotConstant (QualifiedName parts) ->
        "template value " ++ joinQualified (map identifierText parts) ++ " is not a resolved compile-time constant"
    TemplateValueDivisionByZero -> "template value performs division by zero"
    TemplateValueFloorDivisionByZero -> "template value performs floor division by zero"
    TemplateValueRemainderByZero -> "template value performs remainder by zero"
    TemplateValueNegativeExponent value -> "template value uses a negative integer exponent: " ++ show value
    TemplateValueEvaluationLimitExceeded -> "template integer expression exceeds the compile-time evaluation limit"
    TemplateValueRequiresInteger _ -> "fixed System.Array size must evaluate to an integer"
    TemplateValueNegativeArraySize value -> "fixed System.Array size cannot be negative: " ++ show value

joinQualified :: [String] -> String
joinQualified [] = "<empty>"
joinQualified [part] = part
joinQualified (part : parts) = part ++ "." ++ joinQualified parts

checkedTemplateInteger :: Integer -> Either TemplateValueError Integer
checkedTemplateInteger = mapCompileTimeError 0 . checkCompileTimeInteger

mapCompileTimeError :: Integer -> Either CompileTimeIntegerError value -> Either TemplateValueError value
mapCompileTimeError exponentValue result = case result of
    Right value -> Right value
    Left CompileTimeNegativeExponent -> Left (TemplateValueNegativeExponent exponentValue)
    Left CompileTimeIntegerLimitExceeded -> Left TemplateValueEvaluationLimitExceeded