packages feed

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

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

{- | Canonical built-in scalar descriptions shared by type checking and Core.
Widths here are language widths, never host-machine widths.
-}
module Progmasoft.Tala.BuiltinTypes
    ( ScalarFamily (..)
    , ScalarType (..)
    , scalarTypes
    , scalarTypeName
    , scalarTypeWidth
    , scalarTypeFamily
    , scalarTypeSigned
    , scalarWrapperName
    , wrapperNameToScalarType
    , scalarTypeRank
    , widerScalarType
    , scalarTypeToType
    , typeToScalarType
    , isIntegerType
    , isSignedIntegerType
    , isUnsignedIntegerType
    , isFloatingType
    , isNumericType
    , integerMinimum
    , integerMaximum
    , integerFits
    , defaultIntegerScalar
    , defaultFloatingScalar
    ) where

import Progmasoft.Tala.AST

-- | Semantic family used to group built-in scalar types for operator rules.
data ScalarFamily = CharacterFamily | BooleanFamily | SignedIntegerFamily | UnsignedIntegerFamily | FloatingFamily
    deriving (Bounded, Enum, Eq, Ord, Read, Show)

-- | Closed inventory of Tala built-in scalar types.
data ScalarType
    = -- | Unicode scalar-value character type.
      CharacterScalar
    | -- | Boolean type with an 8-bit storage width.
      BooleanScalar
    | -- | 8-bit signed integer.
      ByteScalar
    | -- | 16-bit signed integer.
      ShortScalar
    | -- | 32-bit signed integer.
      LongScalar
    | -- | 64-bit signed integer and default integer type.
      IntScalar
    | -- | 128-bit signed integer.
      LongIntScalar
    | -- | 8-bit unsigned integer.
      UByteScalar
    | -- | 16-bit unsigned integer.
      UShortScalar
    | -- | 32-bit unsigned integer.
      ULongScalar
    | -- | 64-bit unsigned integer.
      UIntScalar
    | -- | 128-bit unsigned integer.
      ULongIntScalar
    | -- | 16-bit floating-point type.
      SFloatScalar
    | -- | 32-bit floating-point type.
      LFloatScalar
    | -- | 64-bit floating-point type and default float.
      FloatScalar
    | -- | 128-bit floating-point type.
      DoubleScalar
    deriving (Bounded, Enum, Eq, Ord, Read, Show)

-- | All built-in scalar types in canonical declaration/rank order.
scalarTypes :: [ScalarType]
scalarTypes = [minBound .. maxBound]

-- | Return the lowercase source spelling of a scalar type.
scalarTypeName :: ScalarType -> String
scalarTypeName scalar = case scalar of
    CharacterScalar -> "char"
    BooleanScalar -> "bool"
    ByteScalar -> "byte"
    ShortScalar -> "short"
    LongScalar -> "long"
    IntScalar -> "int"
    LongIntScalar -> "longint"
    UByteScalar -> "ubyte"
    UShortScalar -> "ushort"
    ULongScalar -> "ulong"
    UIntScalar -> "uint"
    ULongIntScalar -> "ulongint"
    SFloatScalar -> "sfloat"
    LFloatScalar -> "lfloat"
    FloatScalar -> "float"
    DoubleScalar -> "double"

-- | Return the language-defined storage width, independent of host architecture.
scalarTypeWidth :: ScalarType -> Int
scalarTypeWidth scalar = case scalar of
    CharacterScalar -> 32
    BooleanScalar -> 8
    ByteScalar -> 8
    ShortScalar -> 16
    LongScalar -> 32
    IntScalar -> 64
    LongIntScalar -> 128
    UByteScalar -> 8
    UShortScalar -> 16
    ULongScalar -> 32
    UIntScalar -> 64
    ULongIntScalar -> 128
    SFloatScalar -> 16
    LFloatScalar -> 32
    FloatScalar -> 64
    DoubleScalar -> 128

-- | Classify a scalar by its semantic arithmetic family.
scalarTypeFamily :: ScalarType -> ScalarFamily
scalarTypeFamily scalar = case scalar of
    CharacterScalar -> CharacterFamily
    BooleanScalar -> BooleanFamily
    ByteScalar -> SignedIntegerFamily
    ShortScalar -> SignedIntegerFamily
    LongScalar -> SignedIntegerFamily
    IntScalar -> SignedIntegerFamily
    LongIntScalar -> SignedIntegerFamily
    UByteScalar -> UnsignedIntegerFamily
    UShortScalar -> UnsignedIntegerFamily
    ULongScalar -> UnsignedIntegerFamily
    UIntScalar -> UnsignedIntegerFamily
    ULongIntScalar -> UnsignedIntegerFamily
    SFloatScalar -> FloatingFamily
    LFloatScalar -> FloatingFamily
    FloatScalar -> FloatingFamily
    DoubleScalar -> FloatingFamily

-- | Test whether a scalar uses signed integer arithmetic.
scalarTypeSigned :: ScalarType -> Bool
scalarTypeSigned scalar = scalarTypeFamily scalar == SignedIntegerFamily

-- Wrapper names are canonical system types used by contextual Of(value: ...)
-- insertion. Keeping the table beside scalar widths prevents wrapper and
-- primitive catalogs from drifting as the standard library grows.

-- | Resolve the canonical @System.*@ wrapper name for contextual conversions.
scalarWrapperName :: ScalarType -> QualifiedName
scalarWrapperName scalar = QualifiedName [Identifier "System", Identifier (wrapperLeaf scalar)]
    where
        wrapperLeaf value = case value of
            CharacterScalar -> "Character"
            BooleanScalar -> "Boolean"
            ByteScalar -> "Byte"
            ShortScalar -> "Short"
            LongScalar -> "Long"
            IntScalar -> "Integer"
            LongIntScalar -> "LongInteger"
            UByteScalar -> "UByte"
            UShortScalar -> "UShort"
            ULongScalar -> "ULong"
            UIntScalar -> "UInteger"
            ULongIntScalar -> "ULongInteger"
            SFloatScalar -> "SFloat"
            LFloatScalar -> "LFloat"
            FloatScalar -> "Float"
            DoubleScalar -> "Double"

-- | Resolve a canonical wrapper qualified name to its scalar type.
wrapperNameToScalarType :: QualifiedName -> Maybe ScalarType
wrapperNameToScalarType name = lookup name [(scalarWrapperName scalar, scalar) | scalar <- scalarTypes]

-- Rank is meaningful only inside one family. It is not an implicit-conversion
-- rule; it exists for range analysis and explicit conversion diagnostics.

{- | Return widening rank within the scalar's own arithmetic family.
The rank is metadata for range analysis, not an implicit-conversion rule.
-}
scalarTypeRank :: ScalarType -> Int
scalarTypeRank scalar = case scalar of
    CharacterScalar -> 0
    BooleanScalar -> 0
    ByteScalar -> 0
    ShortScalar -> 1
    LongScalar -> 2
    IntScalar -> 3
    LongIntScalar -> 4
    UByteScalar -> 0
    UShortScalar -> 1
    ULongScalar -> 2
    UIntScalar -> 3
    ULongIntScalar -> 4
    SFloatScalar -> 0
    LFloatScalar -> 1
    FloatScalar -> 2
    DoubleScalar -> 3

{- | Select the wider scalar when both operands belong to the same family.
@Nothing@ means the families differ and no widening relation is defined.
-}
widerScalarType :: ScalarType -> ScalarType -> Maybe ScalarType
widerScalarType left right
    | scalarTypeFamily left /= scalarTypeFamily right = Nothing
    | scalarTypeRank left >= scalarTypeRank right = Just left
    | otherwise = Just right

-- | Convert a scalar descriptor to its canonical AST named type.
scalarTypeToType :: ScalarType -> Type
scalarTypeToType = named . scalarTypeName
    where
        named value = NamedType (QualifiedName [Identifier value]) []

-- | Recognize a non-generic built-in scalar AST type.
typeToScalarType :: Type -> Maybe ScalarType
typeToScalarType (NamedType (QualifiedName [Identifier name]) []) = lookup name table
    where
        table = [(scalarTypeName scalar, scalar) | scalar <- scalarTypes]
typeToScalarType _ = Nothing

-- | Test whether an AST type denotes a signed or unsigned integer scalar.
isIntegerType :: Type -> Bool
isIntegerType valueType = maybe False integerScalar (typeToScalarType valueType)
    where
        integerScalar scalar = scalarTypeFamily scalar `elem` [SignedIntegerFamily, UnsignedIntegerFamily]

-- | Test whether an AST type denotes a signed integer scalar.
isSignedIntegerType :: Type -> Bool
isSignedIntegerType valueType = maybe False ((== SignedIntegerFamily) . scalarTypeFamily) (typeToScalarType valueType)

-- | Test whether an AST type denotes an unsigned integer scalar.
isUnsignedIntegerType :: Type -> Bool
isUnsignedIntegerType valueType = maybe False ((== UnsignedIntegerFamily) . scalarTypeFamily) (typeToScalarType valueType)

-- | Test whether an AST type denotes a floating-point scalar.
isFloatingType :: Type -> Bool
isFloatingType valueType = maybe False ((== FloatingFamily) . scalarTypeFamily) (typeToScalarType valueType)

-- | Test whether an AST type denotes an integer or floating-point scalar.
isNumericType :: Type -> Bool
isNumericType valueType = isIntegerType valueType || isFloatingType valueType

{- | Return the inclusive minimum mathematical value for an integer scalar.
Character, Boolean, and floating-point types have no integer range.
-}
integerMinimum :: ScalarType -> Maybe Integer
integerMinimum scalar = case scalarTypeFamily scalar of
    SignedIntegerFamily -> Just (negate (2 ^ (scalarTypeWidth scalar - 1)))
    UnsignedIntegerFamily -> Just 0
    _ -> Nothing

-- | Return the inclusive maximum mathematical value for an integer scalar.
integerMaximum :: ScalarType -> Maybe Integer
integerMaximum scalar = case scalarTypeFamily scalar of
    SignedIntegerFamily -> Just (2 ^ (scalarTypeWidth scalar - 1) - 1)
    UnsignedIntegerFamily -> Just (2 ^ scalarTypeWidth scalar - 1)
    _ -> Nothing

-- | Test whether an arbitrary-precision integer fits a scalar's exact range.
integerFits :: ScalarType -> Integer -> Bool
integerFits scalar value = case (integerMinimum scalar, integerMaximum scalar) of
    (Just minimumValue, Just maximumValue) -> value >= minimumValue && value <= maximumValue
    _ -> False

-- | Default contextual type for an otherwise-unconstrained integer literal.
defaultIntegerScalar :: ScalarType
defaultIntegerScalar = IntScalar

-- | Default contextual type for an otherwise-unconstrained float literal.
defaultFloatingScalar :: ScalarType
defaultFloatingScalar = FloatScalar