tala-compiler-0.1.0: test/ScalarWireTests.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
module ScalarWireTests (scalarWireTests) where
import Data.Word (Word8)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep
import Progmasoft.Tala.Core.CorePrep.Verifier
import Progmasoft.Tala.Core.CorePrep.Wire
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Core.Wire
import Progmasoft.Tala.Diagnostic
scalarWireTests :: [(String, Bool)]
scalarWireTests =
versionTests
++ coreRoundTripTests
++ corePrepRoundTripTests
++ coreVerifierTests
++ corePrepVerifierTests
++ malformedWireTests
versionTests :: [(String, Bool)]
versionTests =
[ ("Core wire current version is 10", currentCoreWireVersion == CoreWireVersion 10)
, ("CorePrep wire current version is 8", currentWireVersion == WireVersion 8)
, ("Core numeric payload default is bounded", maximumCoreNumericBytes defaultCoreWireLimits == 4096)
, ("CorePrep numeric payload default is bounded", maximumNumericBytes defaultWireLimits == 4096)
]
scalarTypes :: [(String, Type)]
scalarTypes =
[ ("char", named "char")
, ("byte", named "byte")
, ("short", named "short")
, ("long", named "long")
, ("int", intType)
, ("longint", named "longint")
, ("ubyte", named "ubyte")
, ("ushort", named "ushort")
, ("ulong", named "ulong")
, ("uint", named "uint")
, ("ulongint", named "ulongint")
, ("sfloat", named "sfloat")
, ("lfloat", named "lfloat")
, ("float", named "float")
, ("double", named "double")
]
integerTypes :: [(String, Type, Integer, Integer)]
integerTypes =
[ ("char", named "char", 0, 2 ^ (32 :: Int) - 1)
, ("byte", named "byte", negate (2 ^ (7 :: Int)), 2 ^ (7 :: Int) - 1)
, ("short", named "short", negate (2 ^ (15 :: Int)), 2 ^ (15 :: Int) - 1)
, ("long", named "long", negate (2 ^ (31 :: Int)), 2 ^ (31 :: Int) - 1)
, ("int", intType, negate (2 ^ (63 :: Int)), 2 ^ (63 :: Int) - 1)
, ("longint", named "longint", negate (2 ^ (127 :: Int)), 2 ^ (127 :: Int) - 1)
, ("ubyte", named "ubyte", 0, 2 ^ (8 :: Int) - 1)
, ("ushort", named "ushort", 0, 2 ^ (16 :: Int) - 1)
, ("ulong", named "ulong", 0, 2 ^ (32 :: Int) - 1)
, ("uint", named "uint", 0, 2 ^ (64 :: Int) - 1)
, ("ulongint", named "ulongint", 0, 2 ^ (128 :: Int) - 1)
]
floatingTypes :: [(String, Type)]
floatingTypes =
[ ("sfloat", named "sfloat")
, ("lfloat", named "lfloat")
, ("float", named "float")
, ("double", named "double")
]
named :: String -> Type
named spelling = NamedType (QualifiedName [Identifier spelling]) []
resolved :: Int -> String -> ResolvedName
resolved identifier spelling = ResolvedName (SymbolId identifier) (Identifier spelling)
literalFor :: Type -> CoreLiteral
literalFor valueType
| valueType == unitType = CoreUnit
| valueType == boolType = CoreBoolean True
| valueType == stringType = CoreString "Tala λ 😀"
| valueType `elem` map snd floatingTypes = CoreFloating "-12345.625e-2"
| otherwise = CoreInteger 42
coreModuleFor :: Type -> CoreLiteral -> CoreModule
coreModuleFor valueType literal =
CoreModule
(QualifiedName [Identifier "Scalar", Identifier "Core"])
[CoreFunction (resolved 1 "Value") [] valueType [CoreReturn (CoreLiteral literal valueType)]]
corePrepModuleFor :: Type -> CoreLiteral -> CorePrepModule
corePrepModuleFor valueType literal =
CorePrepModule
(QualifiedName [Identifier "Scalar", Identifier "CorePrep"])
[ CorePrepFunction
(resolved 1 "Value")
""
[]
valueType
1
[CorePrepBlock 1 [] (CorePrepReturn (CorePrepLiteral literal valueType))]
]
[]
coreRoundTrip :: Type -> Bool
coreRoundTrip valueType =
let source = coreModuleFor valueType (literalFor valueType)
in (encodeCore defaultCoreWireLimits source >>= decodeCore defaultCoreWireLimits) == Right source
corePrepRoundTrip :: Type -> Bool
corePrepRoundTrip valueType =
let source = corePrepModuleFor valueType (literalFor valueType)
in (encodeCorePrep source >>= decodeCorePrep) == Right source
coreRoundTripTests :: [(String, Bool)]
coreRoundTripTests =
[ ("Core wire round-trips unit", coreRoundTrip unitType)
, ("Core wire round-trips bool", coreRoundTrip boolType)
, ("Core wire round-trips String", coreRoundTrip stringType)
]
++ [("Core wire round-trips " ++ name, coreRoundTrip valueType) | (name, valueType) <- scalarTypes]
++ concatMap coreIntegerBoundaryRoundTrips integerTypes
++ [("Core wire preserves " ++ name ++ " spelling", coreFloatingRoundTrip valueType) | (name, valueType) <- floatingTypes]
corePrepRoundTripTests :: [(String, Bool)]
corePrepRoundTripTests =
[ ("CorePrep wire round-trips unit", corePrepRoundTrip unitType)
, ("CorePrep wire round-trips bool", corePrepRoundTrip boolType)
, ("CorePrep wire round-trips String", corePrepRoundTrip stringType)
]
++ [("CorePrep wire round-trips " ++ name, corePrepRoundTrip valueType) | (name, valueType) <- scalarTypes]
++ concatMap corePrepIntegerBoundaryRoundTrips integerTypes
++ [ ("CorePrep wire preserves " ++ name ++ " spelling", corePrepFloatingRoundTrip valueType)
| (name, valueType) <- floatingTypes
]
coreIntegerBoundaryRoundTrips :: (String, Type, Integer, Integer) -> [(String, Bool)]
coreIntegerBoundaryRoundTrips (name, valueType, minimumValue, maximumValue) =
[ ("Core wire round-trips " ++ name ++ " minimum", roundTrip minimumValue)
, ("Core wire round-trips " ++ name ++ " maximum", roundTrip maximumValue)
]
where
roundTrip value =
let source = coreModuleFor valueType (CoreInteger value)
in (encodeCore defaultCoreWireLimits source >>= decodeCore defaultCoreWireLimits) == Right source
corePrepIntegerBoundaryRoundTrips :: (String, Type, Integer, Integer) -> [(String, Bool)]
corePrepIntegerBoundaryRoundTrips (name, valueType, minimumValue, maximumValue) =
[ ("CorePrep wire round-trips " ++ name ++ " minimum", roundTrip minimumValue)
, ("CorePrep wire round-trips " ++ name ++ " maximum", roundTrip maximumValue)
]
where
roundTrip value =
let source = corePrepModuleFor valueType (CoreInteger value)
in (encodeCorePrep source >>= decodeCorePrep) == Right source
coreFloatingRoundTrip :: Type -> Bool
coreFloatingRoundTrip valueType =
all
( \spelling ->
let source = coreModuleFor valueType (CoreFloating spelling)
in (encodeCore defaultCoreWireLimits source >>= decodeCore defaultCoreWireLimits) == Right source
)
["0", "1.0", ".5", "5.", "-1.25", "6.022e23", "1e-9"]
corePrepFloatingRoundTrip :: Type -> Bool
corePrepFloatingRoundTrip valueType =
all
( \spelling ->
let source = corePrepModuleFor valueType (CoreFloating spelling)
in (encodeCorePrep source >>= decodeCorePrep) == Right source
)
["0", "1.0", ".5", "5.", "-1.25", "6.022e23", "1e-9"]
hasDiagnostic :: String -> Either [Diagnostic] value -> Bool
hasDiagnostic code result = case result of
Left issues -> any ((== code) . diagnosticCode) issues
Right _ -> False
coreVerifierTests :: [(String, Bool)]
coreVerifierTests =
concatMap verifyIntegerBoundaries integerTypes
++ [ ("Core verifier accepts every floating scalar", all validFloating floatingTypes)
, ("Core verifier rejects floating payload on int", invalidCoreLiteral intType (CoreFloating "1.0"))
, ("Core verifier rejects integer payload on float", invalidCoreLiteral (named "float") (CoreInteger 1))
, ("Core verifier rejects invalid floating exponent", invalidCoreLiteral (named "double") (CoreFloating "1e"))
, ("Core verifier rejects String payload on bool", invalidCoreLiteral boolType (CoreString "wrong"))
]
where
validFloating (_, valueType) =
verifyCore (coreModuleFor valueType (CoreFloating "1.25e2")) == Right (coreModuleFor valueType (CoreFloating "1.25e2"))
corePrepVerifierTests :: [(String, Bool)]
corePrepVerifierTests =
concatMap verifyCorePrepIntegerBoundaries integerTypes
++ [ ("CorePrep verifier accepts every floating scalar", all validFloating floatingTypes)
, ("CorePrep verifier rejects floating payload on int", invalidCorePrepLiteral intType (CoreFloating "1.0"))
, ("CorePrep verifier rejects integer payload on float", invalidCorePrepLiteral (named "float") (CoreInteger 1))
, ("CorePrep verifier rejects invalid floating exponent", invalidCorePrepLiteral (named "double") (CoreFloating "1e"))
, ("CorePrep verifier rejects String payload on bool", invalidCorePrepLiteral boolType (CoreString "wrong"))
,
( "CorePrep verifier accepts discarded integer rounded division"
, validDiscardedFloorDivide (named "long") (CoreInteger 7) (CoreInteger 2)
)
,
( "CorePrep verifier accepts discarded floating rounded division"
, validDiscardedFloorDivide (named "float") (CoreFloating "7.8") (CoreFloating "2.0")
)
, ("CorePrep verifier rejects mismatched discarded rounded division operands", rejectsMismatchedDiscardedFloorDivide)
]
where
validFloating (_, valueType) =
verifyCorePrep (corePrepModuleFor valueType (CoreFloating "1.25e2"))
== Right (corePrepModuleFor valueType (CoreFloating "1.25e2"))
verifyIntegerBoundaries :: (String, Type, Integer, Integer) -> [(String, Bool)]
verifyIntegerBoundaries (name, valueType, minimumValue, maximumValue) =
[ ("Core verifier accepts " ++ name ++ " minimum", valid minimumValue)
, ("Core verifier accepts " ++ name ++ " maximum", valid maximumValue)
, ("Core verifier rejects " ++ name ++ " below minimum", invalid (minimumValue - 1))
, ("Core verifier rejects " ++ name ++ " above maximum", invalid (maximumValue + 1))
]
where
valid value = verifyCore (coreModuleFor valueType (CoreInteger value)) == Right (coreModuleFor valueType (CoreInteger value))
invalid value = invalidCoreLiteral valueType (CoreInteger value)
verifyCorePrepIntegerBoundaries :: (String, Type, Integer, Integer) -> [(String, Bool)]
verifyCorePrepIntegerBoundaries (name, valueType, minimumValue, maximumValue) =
[ ("CorePrep verifier accepts " ++ name ++ " minimum", valid minimumValue)
, ("CorePrep verifier accepts " ++ name ++ " maximum", valid maximumValue)
, ("CorePrep verifier rejects " ++ name ++ " below minimum", invalid (minimumValue - 1))
, ("CorePrep verifier rejects " ++ name ++ " above maximum", invalid (maximumValue + 1))
]
where
valid value =
verifyCorePrep (corePrepModuleFor valueType (CoreInteger value))
== Right (corePrepModuleFor valueType (CoreInteger value))
invalid value = invalidCorePrepLiteral valueType (CoreInteger value)
invalidCoreLiteral :: Type -> CoreLiteral -> Bool
invalidCoreLiteral valueType literal = hasDiagnostic "TAC1029" (verifyCore (coreModuleFor valueType literal))
invalidCorePrepLiteral :: Type -> CoreLiteral -> Bool
invalidCorePrepLiteral valueType literal = hasDiagnostic "TAC0023" (verifyCorePrep (corePrepModuleFor valueType literal))
validDiscardedFloorDivide :: Type -> CoreLiteral -> CoreLiteral -> Bool
validDiscardedFloorDivide valueType left right =
let moduleValue = discardedFloorDivideModule valueType left valueType right
in verifyCorePrep moduleValue == Right moduleValue
rejectsMismatchedDiscardedFloorDivide :: Bool
rejectsMismatchedDiscardedFloorDivide =
hasDiagnostic
"TAC0019"
(verifyCorePrep (discardedFloorDivideModule intType (CoreInteger 7) (named "long") (CoreInteger 2)))
discardedFloorDivideModule :: Type -> CoreLiteral -> Type -> CoreLiteral -> CorePrepModule
discardedFloorDivideModule leftType left rightType right =
CorePrepModule
(QualifiedName [Identifier "Scalar", Identifier "DiscardedFloorDivide"])
[ CorePrepFunction
(resolved 1 "Run")
""
[]
unitType
1
[ CorePrepBlock
1
[ CorePrepEvaluate
( CorePrepPrimitive
CoreFloorDivide
[ CorePrepLiteral left leftType
, CorePrepLiteral right rightType
]
)
]
(CorePrepReturn (CorePrepLiteral CoreUnit unitType))
]
]
[]
malformedWireTests :: [(String, Bool)]
malformedWireTests =
[ ("Core wire rejects v2 input", rejectsCoreVersion 2)
, ("Core wire rejects the previous v9 schema", rejectsCoreVersion 9)
, ("Core wire rejects future input", rejectsCoreVersion 11)
, ("CorePrep wire rejects v2 input", rejectsCorePrepVersion 2)
, ("CorePrep wire rejects the previous v7 schema", rejectsCorePrepVersion 7)
, ("CorePrep wire rejects future input", rejectsCorePrepVersion 9)
, ("Core wire enforces numeric byte limit", coreNumericLimit)
, ("CorePrep wire enforces numeric byte limit", corePrepNumericLimit)
]
rejectsCoreVersion :: Word8 -> Bool
rejectsCoreVersion version = case encodeCore defaultCoreWireLimits (coreModuleFor unitType CoreUnit) of
Left _ -> False
Right (_ : _ : _ : _ : _ : remaining) -> case decodeCore defaultCoreWireLimits ([0x54, 0x41, 0x43, 0x52, version] ++ remaining) of
Left issue -> coreWireErrorKind issue == CoreUnsupportedVersion
Right _ -> False
Right _ -> False
rejectsCorePrepVersion :: Word8 -> Bool
rejectsCorePrepVersion version = case encodeCorePrep (corePrepModuleFor unitType CoreUnit) of
Left _ -> False
Right (_ : _ : _ : _ : _ : remaining) -> case decodeCorePrep ([0x54, 0x41, 0x43, 0x50, version] ++ remaining) of
Left issue -> wireErrorKind issue == UnsupportedVersion
Right _ -> False
Right _ -> False
coreNumericLimit :: Bool
coreNumericLimit =
let limits = defaultCoreWireLimits {maximumCoreNumericBytes = 1}
source = coreModuleFor (named "long") (CoreInteger 256)
in case encodeCore limits source of
Left issue -> coreWireErrorKind issue == CoreLimitExceeded
Right _ -> False
corePrepNumericLimit :: Bool
corePrepNumericLimit =
let limits = defaultWireLimits {maximumNumericBytes = 1}
source = corePrepModuleFor (named "long") (CoreInteger 256)
in case encodeCorePrepWith limits source of
Left issue -> wireErrorKind issue == LimitExceeded
Right _ -> False