tala-core-0.1.0: src/Progmasoft/Tala/Core/Scalar.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
-- | Canonical scalar facts shared by Core verification and optimization.
module Progmasoft.Tala.Core.Scalar
( coreTypeSpelling
, coreIntegerTypeNames
, coreFloatingTypeNames
, coreNumericTypeNames
, isCoreIntegerType
, isCoreFloatingType
, isCoreNumericType
, coreIntegerBitWidth
, coreIntegerIsSigned
, integerFitsCoreType
, validCoreFloatingSpelling
) where
import Progmasoft.Tala.AST
-- | Extract the leaf spelling from a non-generic built-in type.
coreTypeSpelling :: Type -> String
coreTypeSpelling (NamedType (QualifiedName [Identifier name]) []) = name
coreTypeSpelling _ = ""
-- | Canonical integer type names, including signed and unsigned widths.
coreIntegerTypeNames :: [String]
coreIntegerTypeNames = map fst integerLayouts
-- | Canonical floating-point type names in the language.
coreFloatingTypeNames :: [String]
coreFloatingTypeNames = ["sfloat", "lfloat", "float", "double"]
-- | Integer and floating-point names accepted by Core numeric rules.
coreNumericTypeNames :: [String]
coreNumericTypeNames = coreIntegerTypeNames ++ coreFloatingTypeNames
-- | Test whether a Core type is a built-in integer scalar.
isCoreIntegerType :: Type -> Bool
isCoreIntegerType valueType = coreTypeSpelling valueType `elem` coreIntegerTypeNames
-- | Test whether a Core type is a built-in floating scalar.
isCoreFloatingType :: Type -> Bool
isCoreFloatingType valueType = coreTypeSpelling valueType `elem` coreFloatingTypeNames
-- | Test whether a Core type is an integer or floating scalar.
isCoreNumericType :: Type -> Bool
isCoreNumericType valueType = coreTypeSpelling valueType `elem` coreNumericTypeNames
{- | Return the source-level storage width for a built-in integer type.
Keeping this catalog next to range validation lets optimizer operations
reject unsafe shifts without duplicating the language's integer table.
-}
coreIntegerBitWidth :: Type -> Maybe Int
coreIntegerBitWidth valueType = snd <$> lookup (coreTypeSpelling valueType) integerLayouts
-- | Report whether a scalar integer type uses a sign bit.
coreIntegerIsSigned :: Type -> Maybe Bool
coreIntegerIsSigned valueType = fst <$> lookup (coreTypeSpelling valueType) integerLayouts
integerLayouts :: [(String, (Bool, Int))]
integerLayouts =
[ ("char", (False, 32))
, ("byte", (True, 8))
, ("short", (True, 16))
, ("long", (True, 32))
, ("int", (True, 64))
, ("longint", (True, 128))
, ("ubyte", (False, 8))
, ("ushort", (False, 16))
, ("ulong", (False, 32))
, ("uint", (False, 64))
, ("ulongint", (False, 128))
]
-- | Test whether an exact integer value lies in a Core integer type's range.
integerFitsCoreType :: Type -> Integer -> Bool
integerFitsCoreType valueType value = case lookup (coreTypeSpelling valueType) integerLayouts of
Just (isSigned, width) ->
let magnitude = 2 ^ (width - if isSigned then 1 else 0)
minimumValue = if isSigned then negate magnitude else 0
maximumValue = magnitude - 1
in value >= minimumValue && value <= maximumValue
Nothing -> False
-- | Validate a deterministic ASCII decimal or supported special float spelling.
validCoreFloatingSpelling :: String -> Bool
validCoreFloatingSpelling spelling
| spelling `elem` ["nan", "+nan", "-nan", "inf", "+inf", "-inf"] = True
| otherwise = case dropSign spelling of
[] -> False
unsignedSpelling ->
let (mantissa, exponentPart) = break (`elem` "eE") unsignedSpelling
in validMantissa mantissa && validExponent exponentPart
where
dropSign ('+' : remaining) = remaining
dropSign ('-' : remaining) = remaining
dropSign value = value
validMantissa value = case break (== '.') value of
(whole, []) -> digits whole
(whole, _ : fraction) ->
(not (null whole) || not (null fraction))
&& digitsOrEmpty whole
&& digitsOrEmpty fraction
validExponent [] = True
validExponent (_ : remaining) = digits (dropExponentSign remaining)
dropExponentSign ('+' : remaining) = remaining
dropExponentSign ('-' : remaining) = remaining
dropExponentSign value = value
-- Core artifacts are a stable, language-neutral boundary. Accepting
-- Unicode decimal categories here would make spellings depend on the
-- host implementation of character classification even though source
-- numeric tokens are deliberately ASCII.
asciiDigit character = character >= '0' && character <= '9'
digits value = not (null value) && all asciiDigit value
digitsOrEmpty value = null value || all asciiDigit value