unwitch (empty) → 1.0.0
raw patch · 57 files changed
+4712/−0 lines, 57 filesdep +basedep +bytestringdep +ghc-bignum
Dependencies added: base, bytestring, ghc-bignum, hspec, hspec-core, text, unwitch
Files
- Changelog.md +11/−0
- LICENSE +21/−0
- Readme.md +99/−0
- src/Unwitch.hs +25/−0
- src/Unwitch/Constant.hs +15/−0
- src/Unwitch/Convert/ByteString.hs +55/−0
- src/Unwitch/Convert/Char.hs +61/−0
- src/Unwitch/Convert/Complex.hs +18/−0
- src/Unwitch/Convert/Double.hs +110/−0
- src/Unwitch/Convert/Fixed.hs +40/−0
- src/Unwitch/Convert/Float.hs +113/−0
- src/Unwitch/Convert/Int.hs +177/−0
- src/Unwitch/Convert/Int16.hs +127/−0
- src/Unwitch/Convert/Int32.hs +164/−0
- src/Unwitch/Convert/Int64.hs +213/−0
- src/Unwitch/Convert/Int8.hs +112/−0
- src/Unwitch/Convert/Integer.hs +238/−0
- src/Unwitch/Convert/LazyByteString.hs +48/−0
- src/Unwitch/Convert/LazyText.hs +50/−0
- src/Unwitch/Convert/Natural.hs +196/−0
- src/Unwitch/Convert/Ratio.hs +30/−0
- src/Unwitch/Convert/ShortByteString.hs +20/−0
- src/Unwitch/Convert/Text.hs +52/−0
- src/Unwitch/Convert/Word.hs +158/−0
- src/Unwitch/Convert/Word16.hs +98/−0
- src/Unwitch/Convert/Word32.hs +147/−0
- src/Unwitch/Convert/Word64.hs +178/−0
- src/Unwitch/Convert/Word8.hs +75/−0
- src/Unwitch/Errors.hs +8/−0
- src/Unwitch/TryEvaluate.hs +11/−0
- test/Spec.hs +36/−0
- test/Test/Convert/ByteStringSpec.hs +72/−0
- test/Test/Convert/CharSpec.hs +41/−0
- test/Test/Convert/ComplexSpec.hs +24/−0
- test/Test/Convert/DoubleSpec.hs +87/−0
- test/Test/Convert/FixedSpec.hs +38/−0
- test/Test/Convert/FloatSpec.hs +91/−0
- test/Test/Convert/Int16Spec.hs +39/−0
- test/Test/Convert/Int32Spec.hs +43/−0
- test/Test/Convert/Int64Spec.hs +44/−0
- test/Test/Convert/Int8Spec.hs +38/−0
- test/Test/Convert/IntSpec.hs +73/−0
- test/Test/Convert/IntegerSpec.hs +46/−0
- test/Test/Convert/LazyByteStringSpec.hs +56/−0
- test/Test/Convert/LazyTextSpec.hs +60/−0
- test/Test/Convert/NaturalSpec.hs +69/−0
- test/Test/Convert/PropertySpec.hs +495/−0
- test/Test/Convert/RatioSpec.hs +35/−0
- test/Test/Convert/ShortByteStringSpec.hs +22/−0
- test/Test/Convert/TextSpec.hs +68/−0
- test/Test/Convert/UnboxedSpec.hs +154/−0
- test/Test/Convert/Word16Spec.hs +49/−0
- test/Test/Convert/Word32Spec.hs +54/−0
- test/Test/Convert/Word64Spec.hs +53/−0
- test/Test/Convert/Word8Spec.hs +38/−0
- test/Test/Convert/WordSpec.hs +66/−0
- unwitch.cabal +151/−0
+ Changelog.md view
@@ -0,0 +1,11 @@+# Change log for unwitch project++## Version 0.1.0 ++Implement some of Double.+Write description++## Version 0.0.0 ++import [template](https://github.com/jappeace/haskell-template-project).+
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2021 Jappie Klooster++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Readme.md view
@@ -0,0 +1,99 @@+[](https://jappieklooster.nl/tag/haskell.html)+[](https://www.twitch.tv/jappiejappie)+[](https://discord.gg/Hp4agqy)++> Three hundred and twenty years have passed since the coven sank in the dark. ++Removes the magic from witch.+This provides safe conversions like witch does.+But it [doesn't](https://jappie.me/death-to-type-classes.html) use [type classes](https://www.haskellforall.com/2012/05/scrap-your-type-classes.html)+or exceptions.+This has a couple of advantages:++1. No need to use type application for function selection.+2. Functions get names that describe what they do.+ This allows ctags to work as well.+3. No trouble with orphans.+4. Custom errors instead of the prelude based ones allow client+ code to recover with typesafety even on partial conversions.+ +## Usage++The idea is to select some module *from* which type you want to convert.+So if you have a double you do:++```haskell+import qualified Unwitch.Convert.Double as Double++```+Next you simply use the functions within the module+to convert to something you want, for example:++```haskell+spec = do+ Double.toFloat 5.6 `shouldBe` 5.6+```++Although dubious since float holds less information+then double,+this can never fail in the current implementation.+But there are situation where conversion can fail,+such as when converting to an integer:+```haskell+spec = do+ Double.toInteger 5.0 `shouldBe` Right 5+ Double.toInteger 5.6 `shouldBe` Left $ RationalConversion $ DenomNotOne (5 % 6)+```++In these situation you'll `Right` for success, or `Left`+for an error.+The library will attempt to give you as much information+as possible,+in this case the denominator wasn't 1 so the rational+conversion failed.++## Comparison ++### Witch++Type applications are unnecessary because there is no polymorphism.+Since there are no polymorphic types we don't need to distinct+between conversions that fail and conversions that don't,+This is encoded in types.+Furthermore on failure, if the client can figure out how to go on with a+Rational, they can.+In witch this information gets squashed, we happily provide it.+It's not an exception like witch implied.+++## Hacking+### Tools+Enter the nix shell.+```+nix-shell+```+You can checkout the makefile to see what's available:+```+cat makefile+```++### Running+```+make run+```++### Fast filewatch which runs tests+```+make ghcid+```++## Design decisions+++ Modules names indicate where you're converting *from*.++ Errors should be packed with information to allow recovery++ Only restricted to primitives in base.+ The original witch had support for Map, and Text.+ I feel this is out of scope.++https://serokell.io/blog/introduction-to-template-haskell+
+ src/Unwitch.hs view
@@ -0,0 +1,25 @@+-- just used to load ghci+module Unwitch () where++import Unwitch.Errors ()+import Unwitch.Constant()+import Unwitch.Convert.ByteString ()+import Unwitch.Convert.Double ()+import Unwitch.Convert.Float ()+import Unwitch.Convert.Int ()+import Unwitch.Convert.Int8 ()+import Unwitch.Convert.Int16 ()+import Unwitch.Convert.Int32 ()+import Unwitch.Convert.Int64 ()+import Unwitch.Convert.Integer ()+import Unwitch.Convert.LazyByteString ()+import Unwitch.Convert.LazyText ()+import Unwitch.Convert.Natural ()+import Unwitch.Convert.Ratio ()+import Unwitch.Convert.ShortByteString ()+import Unwitch.Convert.Text ()+import Unwitch.Convert.Word ()+import Unwitch.Convert.Word8 ()+import Unwitch.Convert.Word16 ()+import Unwitch.Convert.Word32 ()+import Unwitch.Convert.Word64 ()
+ src/Unwitch/Constant.hs view
@@ -0,0 +1,15 @@+module Unwitch.Constant+ ( maxIntegralRepDouble+ , maxIntegralRepFloat+ )+where++-- | The maximum integral value that can be unambiguously represented as a+-- 'Double'. Equal to 9,007,199,254,740,991 (2^53 - 1).+maxIntegralRepDouble :: Num a => a+maxIntegralRepDouble = 9007199254740991++-- | The maximum integral value that can be unambiguously represented as a+-- 'Float'. Equal to 16,777,215 (2^24 - 1).+maxIntegralRepFloat :: Num a => a+maxIntegralRepFloat = 16777215
+ src/Unwitch/Convert/ByteString.hs view
@@ -0,0 +1,55 @@+module Unwitch.Convert.ByteString+ ( toLazyByteString+ , toShortByteString+ , toWord8s+ , fromWord8s+ , toTextLatin1+ , toTextUtf8+ , toTextUtf16LE+ , toTextUtf16BE+ , toTextUtf32LE+ , toTextUtf32BE+ )+where++import Data.ByteString (ByteString)+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as LBS+import Data.ByteString.Short qualified as SBS+import Data.ByteString.Short (ShortByteString)+import Data.Text (Text)+import Data.Text.Encoding qualified as TE+import Data.Text.Encoding.Error (UnicodeException)+import Data.Word (Word8)+import Unwitch.TryEvaluate (tryEvaluate)++toLazyByteString :: ByteString -> LBS.ByteString+toLazyByteString = LBS.fromStrict++toShortByteString :: ByteString -> ShortByteString+toShortByteString = SBS.toShort++toWord8s :: ByteString -> [Word8]+toWord8s = BS.unpack++fromWord8s :: [Word8] -> ByteString+fromWord8s = BS.pack++toTextLatin1 :: ByteString -> Text+toTextLatin1 = TE.decodeLatin1++toTextUtf8 :: ByteString -> Either UnicodeException Text+toTextUtf8 = TE.decodeUtf8'++toTextUtf16LE :: ByteString -> Either UnicodeException Text+toTextUtf16LE = tryEvaluate . TE.decodeUtf16LE++toTextUtf16BE :: ByteString -> Either UnicodeException Text+toTextUtf16BE = tryEvaluate . TE.decodeUtf16BE++toTextUtf32LE :: ByteString -> Either UnicodeException Text+toTextUtf32LE = tryEvaluate . TE.decodeUtf32LE++toTextUtf32BE :: ByteString -> Either UnicodeException Text+toTextUtf32BE = tryEvaluate . TE.decodeUtf32BE+
+ src/Unwitch/Convert/Char.hs view
@@ -0,0 +1,61 @@+module Unwitch.Convert.Char+ ( toInt+ , toWord+ , fromInt+ , fromWord+ , fromInt#+ , fromWord#+ )+where++import Data.Char (ord, chr)+import GHC.Exts (Int(..), Word(..), Char(..), chr#,+ word2Int#, (>=#), (<=#), leWord#, gtWord#)++-- | Converts a Char to its Unicode codepoint as Int. Infallible.+toInt :: Char -> Int+toInt = ord++-- | Converts a Char to its Unicode codepoint as Word. Infallible.+toWord :: Char -> Word+toWord = fromIntegral . ord++-- | Converts an Int to a Char if it is a valid Unicode codepoint.+-- Valid range: 0..0xD7FF and 0xE000..0x10FFFF (excludes surrogates).+fromInt :: Int -> Maybe Char+fromInt i = if isValidCodepoint (fromIntegral i)+ then Just $ chr i+ else Nothing++-- | Unboxed variant of 'fromInt'. Checks valid Unicode codepoint range.+fromInt# :: Int -> (# Char | (# #) #)+fromInt# (I# i#) = case i# >=# 0# of+ 1# -> case i# <=# 0xD7FF# of+ 1# -> (# C# (chr# i#) | #)+ _ -> case i# >=# 0xE000# of+ 1# -> case i# <=# 0x10FFFF# of+ 1# -> (# C# (chr# i#) | #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)++-- | Converts a Word to a Char if it is a valid Unicode codepoint.+-- Valid range: 0..0xD7FF and 0xE000..0x10FFFF (excludes surrogates).+fromWord :: Word -> Maybe Char+fromWord w = if isValidCodepoint (fromIntegral w)+ then Just $ chr (fromIntegral w)+ else Nothing++-- | Unboxed variant of 'fromWord'. Checks valid Unicode codepoint range.+fromWord# :: Word -> (# Char | (# #) #)+fromWord# (W# w#) = case leWord# w# 0xD7FF## of+ 1# -> (# C# (chr# (word2Int# w#)) | #)+ _ -> case gtWord# w# 0xDFFF## of+ 1# -> case leWord# w# 0x10FFFF## of+ 1# -> (# C# (chr# (word2Int# w#)) | #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)++isValidCodepoint :: Integer -> Bool+isValidCodepoint cp =+ (cp >= 0 && cp <= 0xD7FF) || (cp >= 0xE000 && cp <= 0x10FFFF)
+ src/Unwitch/Convert/Complex.hs view
@@ -0,0 +1,18 @@+module Unwitch.Convert.Complex+ ( fromReal+ , toReal+ )+where++import Data.Complex (Complex((:+)), imagPart, realPart)++-- | Wraps a real number as a complex number with zero imaginary part.+fromReal :: (Num a) => a -> Complex a+fromReal x = x :+ 0++-- | Extracts the real part if the imaginary part is zero.+toReal :: (Eq a, Num a) => Complex a -> Maybe a+toReal c = if imagPart c == 0+ then Just $ realPart c+ else Nothing+
+ src/Unwitch/Convert/Double.hs view
@@ -0,0 +1,110 @@+module Unwitch.Convert.Double+ ( toFloat+ , toRational+ , toInteger+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , ViaIntegerErrors(..)+ , IntegerErrors(..)+ , RationalErrors(..)+ )+where++import Data.Bifunctor(first)+import Unwitch.Constant+import qualified GHC.Float as F+import Unwitch.Convert.Ratio(unwrapIfDenominatorOne)+import qualified Prelude+import Unwitch.Errors+import Prelude hiding (toRational, toInteger)+import qualified Unwitch.Convert.Integer as Integer+import Data.Word+import Data.Int+import Numeric.Natural (Natural)++-- loses precision?!+toFloat :: Double -> Float+toFloat = F.double2Float++data IntegerErrors = IntegerFlow Integer Overflows+ | RationalConversion RationalErrors+ | DenomNotOne Rational+ deriving (Show, Eq)++data ViaIntegerErrors = MkInteger IntegerErrors+ | BitConversionFailed Integer+ deriving (Show, Eq)++toInt8 :: Double -> Either ViaIntegerErrors Int8+toInt8 = toViaInteger Integer.toInt8++toInt16 :: Double -> Either ViaIntegerErrors Int16+toInt16 = toViaInteger Integer.toInt16++toInt32 :: Double -> Either ViaIntegerErrors Int32+toInt32 = toViaInteger Integer.toInt32++toInt64 :: Double -> Either ViaIntegerErrors Int64+toInt64 = toViaInteger Integer.toInt64++toInt :: Double -> Either ViaIntegerErrors Int+toInt = toViaInteger Integer.toInt++toWord8 :: Double -> Either ViaIntegerErrors Word8+toWord8 = toViaInteger Integer.toWord8++toWord16 :: Double -> Either ViaIntegerErrors Word16+toWord16 = toViaInteger Integer.toWord16++toWord32 :: Double -> Either ViaIntegerErrors Word32+toWord32 = toViaInteger Integer.toWord32++toWord64 :: Double -> Either ViaIntegerErrors Word64+toWord64 = toViaInteger Integer.toWord64++toWord :: Double -> Either ViaIntegerErrors Word+toWord = toViaInteger Integer.toWord++toNatural :: Double -> Either ViaIntegerErrors Natural+toNatural double = do+ integer <- first MkInteger $ toInteger double+ case Integer.toNatural integer of+ Left err -> Left $ MkInteger $ IntegerFlow integer err+ Right n -> Right n++toViaInteger :: (Integer -> Maybe a) -> Double -> Either ViaIntegerErrors a+toViaInteger fun x = do+ integer <- first MkInteger $ toInteger x+ maybe (Left $ BitConversionFailed integer) Right $ fun integer++toInteger :: Double -> Either IntegerErrors Integer+toInteger double = do+ rational <- first RationalConversion $ toRational double+ integer <- maybe (Left $ DenomNotOne rational) Right $ unwrapIfDenominatorOne rational+ if+ | integer < -maxIntegralRepDouble -> Left $ IntegerFlow integer Underflow+ | integer > maxIntegralRepDouble -> Left $ IntegerFlow integer Overflow+ | otherwise -> Right integer+++data RationalErrors = IsNan+ | IsInf Overflows+ deriving (Show, Eq)++toRational :: Double -> Either RationalErrors Rational+toRational double = if+ | isNaN double -> Left IsNan+ | isInfinite double -> if+ | double > 0 -> Left $ IsInf Overflow+ | otherwise -> Left $ IsInf Underflow+ | True -> Right $ Prelude.toRational double+
+ src/Unwitch/Convert/Fixed.hs view
@@ -0,0 +1,40 @@+module Unwitch.Convert.Fixed+ ( fromInteger+ , toInteger+ , toRational+ , toFixed+ )+where++import Data.Fixed (Fixed, HasResolution)+import Data.Ratio (denominator, numerator)+import qualified Prelude+import Prelude hiding (fromInteger, toInteger, toRational)++-- | Converts an Integer to a Fixed value. Infallible.+fromInteger :: (HasResolution a) => Integer -> Fixed a+fromInteger = Prelude.fromInteger++-- | Converts a Fixed value to Integer, succeeding only if there is+-- no fractional part (i.e. the value is a whole number).+toInteger :: (HasResolution a) => Fixed a -> Maybe Integer+toInteger fixed =+ let r = Prelude.toRational fixed+ in if denominator r == 1+ then Just $ numerator r+ else Nothing++-- | Converts a Fixed value to Rational. Infallible, exact.+toRational :: (HasResolution a) => Fixed a -> Rational+toRational = Prelude.toRational++-- | Converts between Fixed types with potentially different resolutions.+-- Succeeds only if the value can be exactly represented in the target resolution.+toFixed :: (HasResolution a, HasResolution b) => Fixed a -> Maybe (Fixed b)+toFixed source =+ let r = Prelude.toRational source+ target = Prelude.fromRational r+ in if Prelude.toRational target == r+ then Just target+ else Nothing+
+ src/Unwitch/Convert/Float.hs view
@@ -0,0 +1,113 @@+module Unwitch.Convert.Float+ ( toDouble+ , toRational+ , toInteger+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , ViaIntegerErrors(..)+ , IntegerErrors(..)+ , RationalErrors(..)+ )+where++import Data.Bifunctor(first)+import Unwitch.Constant+import qualified GHC.Float as F+import Unwitch.Convert.Ratio(unwrapIfDenominatorOne)+import qualified Prelude+import Unwitch.Errors+import Prelude hiding (toRational, toInteger)+import qualified Unwitch.Convert.Integer as Integer+import Data.Word+import Data.Int+import Numeric.Natural (Natural)++toDouble :: Float -> Double+toDouble = F.float2Double++data IntegerErrors = IntegerFlow Integer Overflows+ | RationalConversion RationalErrors+ | DenomNotOne Rational+ deriving (Show, Eq)++data ViaIntegerErrors = MkInteger IntegerErrors+ | BitConversionFailed Integer+ deriving (Show, Eq)++toInt8 :: Float -> Either ViaIntegerErrors Int8+toInt8 = toViaInteger Integer.toInt8+++toInt16 :: Float -> Either ViaIntegerErrors Int16+toInt16 = toViaInteger Integer.toInt16+++toInt32 :: Float -> Either ViaIntegerErrors Int32+toInt32 = toViaInteger Integer.toInt32+++toInt64 :: Float -> Either ViaIntegerErrors Int64+toInt64 = toViaInteger Integer.toInt64+++toInt :: Float -> Either ViaIntegerErrors Int+toInt = toViaInteger Integer.toInt+++toWord8 :: Float -> Either ViaIntegerErrors Word8+toWord8 = toViaInteger Integer.toWord8+++toWord16 :: Float -> Either ViaIntegerErrors Word16+toWord16 = toViaInteger Integer.toWord16++toWord32 :: Float -> Either ViaIntegerErrors Word32+toWord32 = toViaInteger Integer.toWord32++toWord64 :: Float -> Either ViaIntegerErrors Word64+toWord64 = toViaInteger Integer.toWord64++toWord :: Float -> Either ViaIntegerErrors Word+toWord = toViaInteger Integer.toWord++toNatural :: Float -> Either ViaIntegerErrors Natural+toNatural float = do+ integer <- first MkInteger $ toInteger float+ case Integer.toNatural integer of+ Left err -> Left $ MkInteger $ IntegerFlow integer err+ Right n -> Right n++toViaInteger :: (Integer -> Maybe a) -> Float -> Either ViaIntegerErrors a+toViaInteger fun x = do+ integer <- first MkInteger $ toInteger x+ maybe (Left $ BitConversionFailed integer) Right $ fun integer++toInteger :: Float -> Either IntegerErrors Integer+toInteger float = do+ rational <- first RationalConversion $ toRational float+ integer <- maybe (Left $ DenomNotOne rational) Right $ unwrapIfDenominatorOne rational+ if+ | integer < -maxIntegralRepFloat -> Left $ IntegerFlow integer Underflow+ | integer > maxIntegralRepFloat -> Left $ IntegerFlow integer Overflow+ | otherwise -> Right integer++data RationalErrors = IsNan+ | IsInf Overflows+ deriving (Show, Eq)++toRational :: Float -> Either RationalErrors Rational+toRational float = if+ | isNaN float -> Left IsNan+ | isInfinite float -> if+ | float > 0 -> Left $ IsInf Overflow+ | otherwise -> Left $ IsInf Underflow+ | True -> Right $ Prelude.toRational float
+ src/Unwitch/Convert/Int.hs view
@@ -0,0 +1,177 @@+module Unwitch.Convert.Int+ ( toInt8+ , toInt8#+ , toInt16+ , toInt16#+ , toInt32+ , toInt32#+ , toInt64+ , toInteger+ , toWord8+ , toWord8#+ , toWord16+ , toWord16#+ , toWord32+ , toWord32#+ , toWord64+ , toWord64#+ , toWord+ , toWord#+ , toNatural+ , toNatural#+ , toFloat+ , toFloat#+ , toDouble+ , toDouble#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ intToInt8#, int8ToInt#, intToInt16#, int16ToInt#,+ intToInt32#, int32ToInt#,+ int2Word#, wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ wordToWord64#,+ int2Float#, int2Double#,+ (==#), (>=#), (<#), (>#),+ word2Int#)+import GHC.Int (Int8(..), Int16(..), Int32(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (Natural(NS))++toInt8 :: Int -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Int -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Int -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Int -> Int64+toInt64 = fromIntegral++toInteger :: Int -> Integer+toInteger = fromIntegral++toWord8 :: Int -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Int -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Int -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Int -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Int -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Int -> Either Overflows Natural+toNatural x = if+ | x < 0 -> Left Underflow+ | otherwise -> Right $ fromIntegral x++toFloat :: Int -> Either Overflows Float+toFloat x = if+ | x < -maxIntegralRepFloat -> Left Underflow+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Int -> Either Overflows Double+toDouble x = if+ | fromIntegral x < (-maxIntegralRepDouble :: Integer) -> Left Underflow+ | fromIntegral x > (maxIntegralRepDouble :: Integer) -> Left Overflow+ | otherwise -> Right $ fromIntegral x++-- | Signed narrowing, roundtrip at Int#+toInt8# :: Int -> (# Int8 | (# #) #)+toInt8# (I# x#) =+ let n# = intToInt8# x#+ in case int8ToInt# n# ==# x# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Signed narrowing, roundtrip at Int#+toInt16# :: Int -> (# Int16 | (# #) #)+toInt16# (I# x#) =+ let n# = intToInt16# x#+ in case int16ToInt# n# ==# x# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)++-- | Signed narrowing, roundtrip at Int#+toInt32# :: Int -> (# Int32 | (# #) #)+toInt32# (I# x#) =+ let n# = intToInt32# x#+ in case int32ToInt# n# ==# x# of+ 1# -> (# I32# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord8# :: Int -> (# Word8 | (# #) #)+toWord8# (I# x#) =+ let n# = wordToWord8# (int2Word# x#)+ in case word2Int# (word8ToWord# n#) ==# x# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord16# :: Int -> (# Word16 | (# #) #)+toWord16# (I# x#) =+ let n# = wordToWord16# (int2Word# x#)+ in case word2Int# (word16ToWord# n#) ==# x# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord32# :: Int -> (# Word32 | (# #) #)+toWord32# (I# x#) =+ let n# = wordToWord32# (int2Word# x#)+ in case word2Int# (word32ToWord# n#) ==# x# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord64# :: Int -> (# Word64 | (# #) #)+toWord64# (I# x#) = case x# >=# 0# of+ 1# -> (# W64# (wordToWord64# (int2Word# x#)) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord# :: Int -> (# Word | (# #) #)+toWord# (I# x#) = case x# >=# 0# of+ 1# -> (# W# (int2Word# x#) | #)+ _ -> (# | (# #) #)++-- | Check non-negative, construct NS directly+toNatural# :: Int -> (# Overflows | Natural #)+toNatural# (I# i#) = case i# >=# 0# of+ 1# -> (# | NS (int2Word# i#) #)+ _ -> (# Underflow | #)++-- | Bounds-checked float conversion+toFloat# :: Int -> (# Overflows | Float #)+toFloat# (I# i#) = case i# <# -16777215# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 16777215# of+ 1# -> (# Overflow | #)+ _ -> (# | F# (int2Float# i#) #)++-- | Bounds-checked double conversion+toDouble# :: Int -> (# Overflows | Double #)+toDouble# (I# i#) = case i# <# -9007199254740991# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 9007199254740991# of+ 1# -> (# Overflow | #)+ _ -> (# | D# (int2Double# i#) #)
+ src/Unwitch/Convert/Int16.hs view
@@ -0,0 +1,127 @@+module Unwitch.Convert.Int16+ ( toInt8+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toFloat+ , toDouble+ , toInt8#+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord64#+ , toWord#+ , toNatural#+ )+where++import Unwitch.Errors+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Word(..), int16ToInt#, intToInt8#, int8ToInt#,+ int2Word#, word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, wordToWord32#, wordToWord64#,+ (==#), (>=#))+import GHC.Int (Int8(..), Int16(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (Natural(NS))++toInt8 :: Int16 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt32 :: Int16 -> Int32+toInt32 = fromIntegral++toInt64 :: Int16 -> Int64+toInt64 = fromIntegral++toInt :: Int16 -> Int+toInt = fromIntegral++toInteger :: Int16 -> Integer+toInteger = fromIntegral++toWord8 :: Int16 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Int16 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Int16 -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Int16 -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Int16 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Int16 -> Either Overflows Natural+toNatural x = if+ | x < 0 -> Left Underflow+ | otherwise -> Right $ fromIntegral x++toFloat :: Int16 -> Float+toFloat = fromIntegral++toDouble :: Int16 -> Double+toDouble = fromIntegral++-- | Signed narrowing, roundtrip at Int#+toInt8# :: Int16 -> (# Int8 | (# #) #)+toInt8# (I16# x16#) =+ let i# = int16ToInt# x16#+ n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord8# :: Int16 -> (# Word8 | (# #) #)+toWord8# (I16# x16#) =+ let i# = int16ToInt# x16#+ n# = wordToWord8# (int2Word# i#)+ in case word2Int# (word8ToWord# n#) ==# i# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord16# :: Int16 -> (# Word16 | (# #) #)+toWord16# (I16# x16#) = case int16ToInt# x16# >=# 0# of+ 1# -> (# W16# (wordToWord16# (int2Word# (int16ToInt# x16#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord32# :: Int16 -> (# Word32 | (# #) #)+toWord32# (I16# x16#) = case int16ToInt# x16# >=# 0# of+ 1# -> (# W32# (wordToWord32# (int2Word# (int16ToInt# x16#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord64# :: Int16 -> (# Word64 | (# #) #)+toWord64# (I16# x16#) = case int16ToInt# x16# >=# 0# of+ 1# -> (# W64# (wordToWord64# (int2Word# (int16ToInt# x16#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord# :: Int16 -> (# Word | (# #) #)+toWord# (I16# x16#) = case int16ToInt# x16# >=# 0# of+ 1# -> (# W# (int2Word# (int16ToInt# x16#)) | #)+ _ -> (# | (# #) #)++-- | Check non-negative, construct NS directly+toNatural# :: Int16 -> (# Overflows | Natural #)+toNatural# (I16# x16#) = case int16ToInt# x16# >=# 0# of+ 1# -> (# | NS (int2Word# (int16ToInt# x16#)) #)+ _ -> (# Underflow | #)
+ src/Unwitch/Convert/Int32.hs view
@@ -0,0 +1,164 @@+module Unwitch.Convert.Int32+ ( toInt8+ , toInt16+ , toInt64+ , toInt+ , toInteger+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toFloat+ , toDouble+ , toInt8#+ , toInt16#+ , toInt#+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord64#+ , toWord#+ , toNatural#+ , toFloat#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..),+ int32ToInt#, intToInt8#, int8ToInt#,+ intToInt16#, int16ToInt#,+ int2Word#, word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, wordToWord64#,+ int2Float#,+ (==#), (>=#), (<#), (>#))+import GHC.Int (Int8(..), Int16(..), Int32(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (Natural(NS))++toInt8 :: Int32 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Int32 -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt64 :: Int32 -> Int64+toInt64 = fromIntegral++toInt :: Int32 -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Int32 -> Integer+toInteger = fromIntegral++toWord8 :: Int32 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Int32 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Int32 -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Int32 -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Int32 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Int32 -> Either Overflows Natural+toNatural x = if+ | x < 0 -> Left Underflow+ | otherwise -> Right $ fromIntegral x++toFloat :: Int32 -> Either Overflows Float+toFloat x = if+ | x < -maxIntegralRepFloat -> Left Underflow+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Int32 -> Double+toDouble = fromIntegral++-- | Signed narrowing, roundtrip at Int#+toInt8# :: Int32 -> (# Int8 | (# #) #)+toInt8# (I32# x32#) =+ let i# = int32ToInt# x32#+ n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Signed narrowing, roundtrip at Int#+toInt16# :: Int32 -> (# Int16 | (# #) #)+toInt16# (I32# x32#) =+ let i# = int32ToInt# x32#+ n# = intToInt16# i#+ in case int16ToInt# n# ==# i# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)++-- | Int32 always fits in Int (Int is at least 32 bits)+toInt# :: Int32 -> (# Int | (# #) #)+toInt# (I32# x32#) = (# I# (int32ToInt# x32#) | #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord8# :: Int32 -> (# Word8 | (# #) #)+toWord8# (I32# x32#) =+ let i# = int32ToInt# x32#+ n# = wordToWord8# (int2Word# i#)+ in case word2Int# (word8ToWord# n#) ==# i# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow, roundtrip via Word# back to Int#+toWord16# :: Int32 -> (# Word16 | (# #) #)+toWord16# (I32# x32#) =+ let i# = int32ToInt# x32#+ n# = wordToWord16# (int2Word# i#)+ in case word2Int# (word16ToWord# n#) ==# i# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord32# :: Int32 -> (# Word32 | (# #) #)+toWord32# (I32# x32#) = case int32ToInt# x32# >=# 0# of+ 1# -> (# W32# (wordToWord32# (int2Word# (int32ToInt# x32#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord64# :: Int32 -> (# Word64 | (# #) #)+toWord64# (I32# x32#) = case int32ToInt# x32# >=# 0# of+ 1# -> (# W64# (wordToWord64# (int2Word# (int32ToInt# x32#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord# :: Int32 -> (# Word | (# #) #)+toWord# (I32# x32#) = case int32ToInt# x32# >=# 0# of+ 1# -> (# W# (int2Word# (int32ToInt# x32#)) | #)+ _ -> (# | (# #) #)++-- | Check non-negative, construct NS directly+toNatural# :: Int32 -> (# Overflows | Natural #)+toNatural# (I32# x32#) = case int32ToInt# x32# >=# 0# of+ 1# -> (# | NS (int2Word# (int32ToInt# x32#)) #)+ _ -> (# Underflow | #)++-- | Bounds-checked float conversion+toFloat# :: Int32 -> (# Overflows | Float #)+toFloat# (I32# x32#) =+ let i# = int32ToInt# x32#+ in case i# <# -16777215# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 16777215# of+ 1# -> (# Overflow | #)+ _ -> (# | F# (int2Float# i#) #)
+ src/Unwitch/Convert/Int64.hs view
@@ -0,0 +1,213 @@+module Unwitch.Convert.Int64+ ( toInt8+ , toInt16+ , toInt32+ , toInt+ , toInteger+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toFloat+ , toDouble+ , toInt8#+ , toInt16#+ , toInt32#+ , toInt#+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord64#+ , toWord#+ , toNatural#+ , toFloat#+ , toDouble#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ int64ToInt#, intToInt64#,+ intToInt8#, int8ToInt#,+ intToInt16#, int16ToInt#,+ intToInt32#, int32ToInt#,+ int2Word#, word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ wordToWord64#,+ int2Float#, int2Double#,+ (<#), (>#),+ eqInt64#, geInt64#)+import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (Natural(NS))++toInt8 :: Int64 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Int64 -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Int64 -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt :: Int64 -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Int64 -> Integer+toInteger = fromIntegral++toWord8 :: Int64 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Int64 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Int64 -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Int64 -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Int64 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Int64 -> Either Overflows Natural+toNatural x = if+ | x < 0 -> Left Underflow+ | otherwise -> Right $ fromIntegral x++toFloat :: Int64 -> Either Overflows Float+toFloat x = if+ | x < -maxIntegralRepFloat -> Left Underflow+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Int64 -> Either Overflows Double+toDouble x = if+ | x < -maxIntegralRepDouble -> Left Underflow+ | x > maxIntegralRepDouble -> Left Overflow+ | otherwise -> Right $ fromIntegral x++-- | Narrow through Int#, compare at Int64#+toInt8# :: Int64 -> (# Int8 | (# #) #)+toInt8# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = intToInt8# i#+ in case eqInt64# (intToInt64# (int8ToInt# n#)) x64# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Signed narrowing via Int64# comparison+toInt16# :: Int64 -> (# Int16 | (# #) #)+toInt16# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = intToInt16# i#+ in case eqInt64# (intToInt64# (int16ToInt# n#)) x64# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)++-- | Signed narrowing via Int64# comparison+toInt32# :: Int64 -> (# Int32 | (# #) #)+toInt32# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = intToInt32# i#+ in case eqInt64# (intToInt64# (int32ToInt# n#)) x64# of+ 1# -> (# I32# n# | #)+ _ -> (# | (# #) #)++-- | Roundtrip check at Int64# level for platform safety+toInt# :: Int64 -> (# Int | (# #) #)+toInt# (I64# x64#) =+ let i# = int64ToInt# x64#+ in case eqInt64# (intToInt64# i#) x64# of+ 1# -> (# I# i# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow via Int64# comparison+toWord8# :: Int64 -> (# Word8 | (# #) #)+toWord8# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = wordToWord8# (int2Word# i#)+ in case eqInt64# (intToInt64# (word2Int# (word8ToWord# n#))) x64# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow via Int64# comparison+toWord16# :: Int64 -> (# Word16 | (# #) #)+toWord16# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = wordToWord16# (int2Word# i#)+ in case eqInt64# (intToInt64# (word2Int# (word16ToWord# n#))) x64# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned narrow via Int64# comparison+toWord32# :: Int64 -> (# Word32 | (# #) #)+toWord32# (I64# x64#) =+ let i# = int64ToInt# x64#+ n# = wordToWord32# (int2Word# i#)+ in case eqInt64# (intToInt64# (word2Int# (word32ToWord# n#))) x64# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative at Int64# level+toWord64# :: Int64 -> (# Word64 | (# #) #)+toWord64# (I64# x64#) = case geInt64# x64# (intToInt64# 0#) of+ 1# -> (# W64# (wordToWord64# (int2Word# (int64ToInt# x64#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative at Int64# then roundtrip+toWord# :: Int64 -> (# Word | (# #) #)+toWord# (I64# x64#) = case geInt64# x64# (intToInt64# 0#) of+ 1# -> let i# = int64ToInt# x64#+ in case eqInt64# (intToInt64# i#) x64# of+ 1# -> (# W# (int2Word# i#) | #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)++-- | Check non-negative at Int64# level, construct NS+toNatural# :: Int64 -> (# Overflows | Natural #)+toNatural# (I64# x64#) = case geInt64# x64# (intToInt64# 0#) of+ 1# -> let i# = int64ToInt# x64#+ in case eqInt64# (intToInt64# i#) x64# of+ 1# -> (# | NS (int2Word# i#) #)+ _ -> (# Overflow | #)+ _ -> (# Underflow | #)++-- | Bounds-checked float conversion via Int#+toFloat# :: Int64 -> (# Overflows | Float #)+toFloat# (I64# x64#) =+ let i# = int64ToInt# x64#+ in case eqInt64# (intToInt64# i#) x64# of+ 1# -> case i# <# -16777215# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 16777215# of+ 1# -> (# Overflow | #)+ _ -> (# | F# (int2Float# i#) #)+ _ -> case geInt64# x64# (intToInt64# 0#) of+ 1# -> (# Overflow | #)+ _ -> (# Underflow | #)++-- | Bounds-checked double conversion via Int#+toDouble# :: Int64 -> (# Overflows | Double #)+toDouble# (I64# x64#) =+ let i# = int64ToInt# x64#+ in case eqInt64# (intToInt64# i#) x64# of+ 1# -> case i# <# -9007199254740991# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 9007199254740991# of+ 1# -> (# Overflow | #)+ _ -> (# | D# (int2Double# i#) #)+ _ -> case geInt64# x64# (intToInt64# 0#) of+ 1# -> (# Overflow | #)+ _ -> (# Underflow | #)
+ src/Unwitch/Convert/Int8.hs view
@@ -0,0 +1,112 @@+module Unwitch.Convert.Int8+ ( toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toFloat+ , toDouble+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord64#+ , toWord#+ , toNatural#+ )+where++import Unwitch.Errors+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Word(..), int8ToInt#, int2Word#,+ wordToWord8#, wordToWord16#, wordToWord32#,+ wordToWord64#, (>=#))+import GHC.Int (Int8(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (Natural(NS))++toInt16 :: Int8 -> Int16+toInt16 = fromIntegral++toInt32 :: Int8 -> Int32+toInt32 = fromIntegral++toInt64 :: Int8 -> Int64+toInt64 = fromIntegral++toInt :: Int8 -> Int+toInt = fromIntegral++toInteger :: Int8 -> Integer+toInteger = fromIntegral++toWord8 :: Int8 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Int8 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Int8 -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Int8 -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Int8 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Int8 -> Either Overflows Natural+toNatural x = if+ | x < 0 -> Left Underflow+ | otherwise -> Right $ fromIntegral x++toFloat :: Int8 -> Float+toFloat = fromIntegral++toDouble :: Int8 -> Double+toDouble = fromIntegral++-- | Signed->unsigned, check non-negative+toWord8# :: Int8 -> (# Word8 | (# #) #)+toWord8# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# W8# (wordToWord8# (int2Word# (int8ToInt# x8#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord16# :: Int8 -> (# Word16 | (# #) #)+toWord16# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# W16# (wordToWord16# (int2Word# (int8ToInt# x8#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord32# :: Int8 -> (# Word32 | (# #) #)+toWord32# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# W32# (wordToWord32# (int2Word# (int8ToInt# x8#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord64# :: Int8 -> (# Word64 | (# #) #)+toWord64# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# W64# (wordToWord64# (int2Word# (int8ToInt# x8#))) | #)+ _ -> (# | (# #) #)++-- | Signed->unsigned, check non-negative+toWord# :: Int8 -> (# Word | (# #) #)+toWord# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# W# (int2Word# (int8ToInt# x8#)) | #)+ _ -> (# | (# #) #)++-- | Check non-negative, construct NS directly+toNatural# :: Int8 -> (# Overflows | Natural #)+toNatural# (I8# x8#) = case int8ToInt# x8# >=# 0# of+ 1# -> (# | NS (int2Word# (int8ToInt# x8#)) #)+ _ -> (# Underflow | #)
+ src/Unwitch/Convert/Integer.hs view
@@ -0,0 +1,238 @@+module Unwitch.Convert.Integer+ ( toDouble+ , toDouble#+ , toFloat+ , toFloat#+ , toNatural+ , toNatural#+ , toInt8+ , toInt8#+ , toInt16+ , toInt16#+ , toInt32+ , toInt32#+ , toInt64+ , toInt64#+ , toInt+ , toInt#+ , toWord8+ , toWord8#+ , toWord16+ , toWord16#+ , toWord32+ , toWord32#+ , toWord64+ , toWord64#+ , toWord+ , toWord#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ intToInt8#, int8ToInt#,+ intToInt16#, int16ToInt#,+ intToInt32#, int32ToInt#,+ intToInt64#,+ int2Word#, word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ wordToWord64#,+ int2Float#, int2Double#,+ (==#), (>=#), (<#), (>#))+import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Integer (Integer(..), integerToWord#,+ integerFromWord#, integerEq#)+import GHC.Num.Natural (Natural(NS, NB))+++toDouble :: Integer -> Either Overflows Double+toDouble integer = if+ | integer < -maxIntegralRepDouble -> Left Underflow+ | integer > maxIntegralRepDouble -> Left Overflow+ | otherwise -> Right $ Prelude.fromIntegral integer++toFloat :: Integer -> Either Overflows Float+toFloat integer = if+ | integer < -maxIntegralRepFloat -> Left Underflow+ | integer > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ Prelude.fromIntegral integer++toNatural :: Integer -> Either Overflows Natural+toNatural integer = if+ | integer < 0 -> Left Underflow+ | otherwise -> Right $ Prelude.fromIntegral integer++toInt8 :: Integer -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Integer -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Integer -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Integer -> Maybe Int64+toInt64 = Bits.toIntegralSized++toInt :: Integer -> Maybe Int+toInt = Bits.toIntegralSized++toWord8 :: Integer -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Integer -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Integer -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Integer -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Integer -> Maybe Word+toWord = Bits.toIntegralSized++-- | Bounds-checked double conversion via IS/IP/IN+toDouble# :: Integer -> (# Overflows | Double #)+toDouble# x = case x of+ IS i# -> case i# <# -9007199254740991# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 9007199254740991# of+ 1# -> (# Overflow | #)+ _ -> (# | D# (int2Double# i#) #)+ IP _ -> (# Overflow | #)+ IN _ -> (# Underflow | #)++-- | Bounds-checked float conversion via IS/IP/IN+toFloat# :: Integer -> (# Overflows | Float #)+toFloat# x = case x of+ IS i# -> case i# <# -16777215# of+ 1# -> (# Underflow | #)+ _ -> case i# ># 16777215# of+ 1# -> (# Overflow | #)+ _ -> (# | F# (int2Float# i#) #)+ IP _ -> (# Overflow | #)+ IN _ -> (# Underflow | #)++-- | Integer->Natural via IS/IP/IN+toNatural# :: Integer -> (# Overflows | Natural #)+toNatural# x = case x of+ IS i# -> case i# >=# 0# of+ 1# -> (# | NS (int2Word# i#) #)+ _ -> (# Underflow | #)+ IP ba# -> (# | NB ba# #)+ IN _ -> (# Underflow | #)++-- | Integer->Int8 via IS/IP/IN, narrow and roundtrip at Int#+toInt8# :: Integer -> (# Int8 | (# #) #)+toInt8# x = case x of+ IS i# ->+ let n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Int16 via IS/IP/IN+toInt16# :: Integer -> (# Int16 | (# #) #)+toInt16# x = case x of+ IS i# ->+ let n# = intToInt16# i#+ in case int16ToInt# n# ==# i# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Int32 via IS/IP/IN+toInt32# :: Integer -> (# Int32 | (# #) #)+toInt32# x = case x of+ IS i# ->+ let n# = intToInt32# i#+ in case int32ToInt# n# ==# i# of+ 1# -> (# I32# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Int64 via IS/IP/IN+toInt64# :: Integer -> (# Int64 | (# #) #)+toInt64# x = case x of+ IS i# -> (# I64# (intToInt64# i#) | #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Int via IS/IP/IN+toInt# :: Integer -> (# Int | (# #) #)+toInt# x = case x of+ IS i# -> (# I# i# | #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Word8, IS case uses signed->unsigned narrow+toWord8# :: Integer -> (# Word8 | (# #) #)+toWord8# x = case x of+ IS i# ->+ let n# = wordToWord8# (int2Word# i#)+ in case word2Int# (word8ToWord# n#) ==# i# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Word16+toWord16# :: Integer -> (# Word16 | (# #) #)+toWord16# x = case x of+ IS i# ->+ let n# = wordToWord16# (int2Word# i#)+ in case word2Int# (word16ToWord# n#) ==# i# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Word32+toWord32# :: Integer -> (# Word32 | (# #) #)+toWord32# x = case x of+ IS i# ->+ let n# = wordToWord32# (int2Word# i#)+ in case word2Int# (word32ToWord# n#) ==# i# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)+ IP _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Word64: IS checks non-negative; IP uses integerToWord# roundtrip+toWord64# :: Integer -> (# Word64 | (# #) #)+toWord64# x = case x of+ IS i# -> case i# >=# 0# of+ 1# -> (# W64# (wordToWord64# (int2Word# i#)) | #)+ _ -> (# | (# #) #)+ IP _ ->+ let w# = integerToWord# x+ in case integerEq# (integerFromWord# w#) x of+ 1# -> (# W64# (wordToWord64# w#) | #)+ _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)++-- | Integer->Word: IS checks non-negative; IP uses integerToWord# roundtrip+toWord# :: Integer -> (# Word | (# #) #)+toWord# x = case x of+ IS i# -> case i# >=# 0# of+ 1# -> (# W# (int2Word# i#) | #)+ _ -> (# | (# #) #)+ IP _ ->+ let w# = integerToWord# x+ in case integerEq# (integerFromWord# w#) x of+ 1# -> (# W# w# | #)+ _ -> (# | (# #) #)+ IN _ -> (# | (# #) #)
+ src/Unwitch/Convert/LazyByteString.hs view
@@ -0,0 +1,48 @@+module Unwitch.Convert.LazyByteString+ ( toByteString+ , toWord8s+ , fromWord8s+ , toLazyTextLatin1+ , toLazyTextUtf8+ , toLazyTextUtf16LE+ , toLazyTextUtf16BE+ , toLazyTextUtf32LE+ , toLazyTextUtf32BE+ )+where++import Data.ByteString (ByteString)+import Data.ByteString.Lazy qualified as LBS+import Data.Text.Lazy qualified as LT+import Data.Text.Lazy.Encoding qualified as LTE+import Data.Text.Encoding.Error (UnicodeException)+import Data.Word (Word8)+import Unwitch.TryEvaluate (tryEvaluate)++toByteString :: LBS.ByteString -> ByteString+toByteString = LBS.toStrict++toWord8s :: LBS.ByteString -> [Word8]+toWord8s = LBS.unpack++fromWord8s :: [Word8] -> LBS.ByteString+fromWord8s = LBS.pack++toLazyTextLatin1 :: LBS.ByteString -> LT.Text+toLazyTextLatin1 = LTE.decodeLatin1++toLazyTextUtf8 :: LBS.ByteString -> Either UnicodeException LT.Text+toLazyTextUtf8 = LTE.decodeUtf8'++toLazyTextUtf16LE :: LBS.ByteString -> Either UnicodeException LT.Text+toLazyTextUtf16LE = tryEvaluate . LTE.decodeUtf16LE++toLazyTextUtf16BE :: LBS.ByteString -> Either UnicodeException LT.Text+toLazyTextUtf16BE = tryEvaluate . LTE.decodeUtf16BE++toLazyTextUtf32LE :: LBS.ByteString -> Either UnicodeException LT.Text+toLazyTextUtf32LE = tryEvaluate . LTE.decodeUtf32LE++toLazyTextUtf32BE :: LBS.ByteString -> Either UnicodeException LT.Text+toLazyTextUtf32BE = tryEvaluate . LTE.decodeUtf32BE+
+ src/Unwitch/Convert/LazyText.hs view
@@ -0,0 +1,50 @@+module Unwitch.Convert.LazyText+ ( toText+ , toString+ , fromString+ , toLazyByteStringUtf8+ , toLazyByteStringUtf16LE+ , toLazyByteStringUtf16BE+ , toLazyByteStringUtf32LE+ , toLazyByteStringUtf32BE+ , toLazyByteStringLatin1+ )+where++import Data.ByteString.Lazy qualified as LBS+import Data.ByteString.Lazy.Char8 qualified as LBSC8+import Data.Text (Text)+import Data.Text.Lazy qualified as LT+import Data.Text.Lazy.Encoding qualified as LTE++toText :: LT.Text -> Text+toText = LT.toStrict++toString :: LT.Text -> String+toString = LT.unpack++fromString :: String -> LT.Text+fromString = LT.pack++toLazyByteStringUtf8 :: LT.Text -> LBS.ByteString+toLazyByteStringUtf8 = LTE.encodeUtf8++toLazyByteStringUtf16LE :: LT.Text -> LBS.ByteString+toLazyByteStringUtf16LE = LTE.encodeUtf16LE++toLazyByteStringUtf16BE :: LT.Text -> LBS.ByteString+toLazyByteStringUtf16BE = LTE.encodeUtf16BE++toLazyByteStringUtf32LE :: LT.Text -> LBS.ByteString+toLazyByteStringUtf32LE = LTE.encodeUtf32LE++toLazyByteStringUtf32BE :: LT.Text -> LBS.ByteString+toLazyByteStringUtf32BE = LTE.encodeUtf32BE++toLazyByteStringLatin1 :: LT.Text -> Maybe LBS.ByteString+toLazyByteStringLatin1 t = if LT.all isLatin1 t+ then Just $ LBSC8.pack (LT.unpack t)+ else Nothing++isLatin1 :: Char -> Bool+isLatin1 c = c <= '\xFF'
+ src/Unwitch/Convert/Natural.hs view
@@ -0,0 +1,196 @@+module Unwitch.Convert.Natural+ ( toWord8+ , toWord16+ , toWord32+ , toWord64+ , toWord+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord64#+ , toWord#+ , toInt8#+ , toInt16#+ , toInt32#+ , toInt64#+ , toInt#+ , toFloat#+ , toDouble#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ wordToWord64#,+ intToInt8#,+ intToInt16#,+ intToInt32#,+ intToInt64#,+ int2Float#, int2Double#,+ eqWord#, leWord#, (>=#))+import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))+import GHC.Num.Natural (naturalToWordMaybe#)++toWord8 :: Natural -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Natural -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Natural -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Natural -> Maybe Word64+toWord64 = Bits.toIntegralSized++toWord :: Natural -> Maybe Word+toWord = Bits.toIntegralSized++toInt8 :: Natural -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Natural -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Natural -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Natural -> Maybe Int64+toInt64 = Bits.toIntegralSized++toInt :: Natural -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Natural -> Integer+toInteger = fromIntegral++toFloat :: Natural -> Either Overflows Float+toFloat x = if+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Natural -> Either Overflows Double+toDouble x = if+ | x > maxIntegralRepDouble -> Left Overflow+ | otherwise -> Right $ fromIntegral x++-- | Via naturalToWordMaybe#, then narrow and roundtrip at Word#+toWord8# :: Natural -> (# Word8 | (# #) #)+toWord8# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) ->+ let n# = wordToWord8# w#+ in case word8ToWord# n# `eqWord#` w# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, then narrow+toWord16# :: Natural -> (# Word16 | (# #) #)+toWord16# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) ->+ let n# = wordToWord16# w#+ in case word16ToWord# n# `eqWord#` w# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, then narrow+toWord32# :: Natural -> (# Word32 | (# #) #)+toWord32# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) ->+ let n# = wordToWord32# w#+ in case word32ToWord# n# `eqWord#` w# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, then widen to Word64+toWord64# :: Natural -> (# Word64 | (# #) #)+toWord64# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) -> (# W64# (wordToWord64# w#) | #)++-- | Via naturalToWordMaybe#+toWord# :: Natural -> (# Word | (# #) #)+toWord# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) -> (# W# w# | #)++-- | Via naturalToWordMaybe#, check upper bound for Int8+toInt8# :: Natural -> (# Int8 | (# #) #)+toInt8# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) -> case leWord# w# 127## of+ 1# -> (# I8# (intToInt8# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, check upper bound for Int16+toInt16# :: Natural -> (# Int16 | (# #) #)+toInt16# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) -> case leWord# w# 32767## of+ 1# -> (# I16# (intToInt16# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, check upper bound for Int32+toInt32# :: Natural -> (# Int32 | (# #) #)+toInt32# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) -> case leWord# w# 2147483647## of+ 1# -> (# I32# (intToInt32# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, check fits in non-negative Int64+toInt64# :: Natural -> (# Int64 | (# #) #)+toInt64# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) ->+ let i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> (# I64# (intToInt64# i#) | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, check fits in non-negative Int+toInt# :: Natural -> (# Int | (# #) #)+toInt# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# | (# #) #)+ (# | w# #) ->+ let i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> (# I# i# | #)+ _ -> (# | (# #) #)++-- | Via naturalToWordMaybe#, bounds-checked float+toFloat# :: Natural -> (# Overflows | Float #)+toFloat# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# Overflow | #)+ (# | w# #) -> case leWord# w# 16777215## of+ 1# -> (# | F# (int2Float# (word2Int# w#)) #)+ _ -> (# Overflow | #)++-- | Via naturalToWordMaybe#, bounds-checked double+toDouble# :: Natural -> (# Overflows | Double #)+toDouble# nat = case naturalToWordMaybe# nat of+ (# (# #) | #) -> (# Overflow | #)+ (# | w# #) -> case leWord# w# 9007199254740991## of+ 1# -> (# | D# (int2Double# (word2Int# w#)) #)+ _ -> (# Overflow | #)
+ src/Unwitch/Convert/Ratio.hs view
@@ -0,0 +1,30 @@+module Unwitch.Convert.Ratio+ ( unwrapIfDenominatorOne+ , fromIntegralToRatio+ , toFloat+ , toDouble+ )+where++import Data.Ratio(Ratio, (%))+import qualified Data.Ratio as Ratio++-- | Converts if denominator == 1+unwrapIfDenominatorOne :: (Eq a, Num a) => Ratio a -> Maybe a+unwrapIfDenominatorOne s = if Ratio.denominator s == 1 then+ Just $ Ratio.numerator s+ else Nothing++-- | Wraps an integral value as a Ratio with denominator 1.+fromIntegralToRatio :: (Integral a) => a -> Ratio a+fromIntegralToRatio x = x % 1++-- | Converts a Rational to Float. This is lossy for rationals+-- that cannot be exactly represented as Float.+toFloat :: Rational -> Float+toFloat = fromRational++-- | Converts a Rational to Double. This is lossy for rationals+-- that cannot be exactly represented as Double.+toDouble :: Rational -> Double+toDouble = fromRational
+ src/Unwitch/Convert/ShortByteString.hs view
@@ -0,0 +1,20 @@+module Unwitch.Convert.ShortByteString+ ( toByteString+ , toWord8s+ , fromWord8s+ )+where++import Data.ByteString (ByteString)+import Data.ByteString.Short (ShortByteString)+import Data.ByteString.Short qualified as SBS+import Data.Word (Word8)++toByteString :: ShortByteString -> ByteString+toByteString = SBS.fromShort++toWord8s :: ShortByteString -> [Word8]+toWord8s = SBS.unpack++fromWord8s :: [Word8] -> ShortByteString+fromWord8s = SBS.pack
+ src/Unwitch/Convert/Text.hs view
@@ -0,0 +1,52 @@+module Unwitch.Convert.Text+ ( toLazyText+ , toString+ , fromString+ , toByteStringUtf8+ , toByteStringUtf16LE+ , toByteStringUtf16BE+ , toByteStringUtf32LE+ , toByteStringUtf32BE+ , toByteStringLatin1+ )+where++import Data.ByteString (ByteString)+import Data.ByteString.Char8 qualified as BSC8+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Data.Text.Lazy qualified as LT++toLazyText :: Text -> LT.Text+toLazyText = LT.fromStrict++toString :: Text -> String+toString = T.unpack++fromString :: String -> Text+fromString = T.pack++toByteStringUtf8 :: Text -> ByteString+toByteStringUtf8 = TE.encodeUtf8++toByteStringUtf16LE :: Text -> ByteString+toByteStringUtf16LE = TE.encodeUtf16LE++toByteStringUtf16BE :: Text -> ByteString+toByteStringUtf16BE = TE.encodeUtf16BE++toByteStringUtf32LE :: Text -> ByteString+toByteStringUtf32LE = TE.encodeUtf32LE++toByteStringUtf32BE :: Text -> ByteString+toByteStringUtf32BE = TE.encodeUtf32BE++toByteStringLatin1 :: Text -> Maybe ByteString+toByteStringLatin1 t = if T.all isLatin1 t+ then Just $ BSC8.pack (T.unpack t)+ else Nothing++isLatin1 :: Char -> Bool+isLatin1 c = c <= '\xFF'+
+ src/Unwitch/Convert/Word.hs view
@@ -0,0 +1,158 @@+module Unwitch.Convert.Word+ ( toWord8+ , toWord16+ , toWord32+ , toWord64+ , toNatural+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toWord8#+ , toWord16#+ , toWord32#+ , toInt8#+ , toInt16#+ , toInt32#+ , toInt64#+ , toInt#+ , toFloat#+ , toDouble#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ word2Int#,+ intToInt8#, intToInt16#, intToInt32#,+ intToInt64#,+ int2Float#, int2Double#,+ eqWord#, leWord#, (>=#))+import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))+import GHC.Word (Word8(..), Word16(..), Word32(..))++toWord8 :: Word -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Word -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Word -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord64 :: Word -> Word64+toWord64 = fromIntegral++toNatural :: Word -> Natural+toNatural = fromIntegral++toInt8 :: Word -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Word -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Word -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Word -> Maybe Int64+toInt64 = Bits.toIntegralSized++toInt :: Word -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Word -> Integer+toInteger = fromIntegral++toFloat :: Word -> Either Overflows Float+toFloat x = if+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Word -> Either Overflows Double+toDouble x = if+ | fromIntegral x > (maxIntegralRepDouble :: Integer) -> Left Overflow+ | otherwise -> Right $ fromIntegral x++-- | Unsigned narrowing, roundtrip at Word#+toWord8# :: Word -> (# Word8 | (# #) #)+toWord8# (W# w#) =+ let n# = wordToWord8# w#+ in case word8ToWord# n# `eqWord#` w# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned narrowing, roundtrip at Word#+toWord16# :: Word -> (# Word16 | (# #) #)+toWord16# (W# w#) =+ let n# = wordToWord16# w#+ in case word16ToWord# n# `eqWord#` w# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned narrowing, roundtrip at Word#+toWord32# :: Word -> (# Word32 | (# #) #)+toWord32# (W# w#) =+ let n# = wordToWord32# w#+ in case word32ToWord# n# `eqWord#` w# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)++-- | Check upper bound for signed target+toInt8# :: Word -> (# Int8 | (# #) #)+toInt8# (W# w#) = case leWord# w# 127## of+ 1# -> (# I8# (intToInt8# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Check upper bound for signed target+toInt16# :: Word -> (# Int16 | (# #) #)+toInt16# (W# w#) = case leWord# w# 32767## of+ 1# -> (# I16# (intToInt16# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Check upper bound for signed target+toInt32# :: Word -> (# Int32 | (# #) #)+toInt32# (W# w#) = case leWord# w# 2147483647## of+ 1# -> (# I32# (intToInt32# (word2Int# w#)) | #)+ _ -> (# | (# #) #)++-- | Check high bit not set for Int64+toInt64# :: Word -> (# Int64 | (# #) #)+toInt64# (W# w#) =+ let i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> (# I64# (intToInt64# i#) | #)+ _ -> (# | (# #) #)++-- | Check high bit not set for Int+toInt# :: Word -> (# Int | (# #) #)+toInt# (W# w#) =+ let i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> (# I# i# | #)+ _ -> (# | (# #) #)++-- | Bounds-checked float conversion+toFloat# :: Word -> (# Overflows | Float #)+toFloat# (W# w#) = case leWord# w# 16777215## of+ 1# -> (# | F# (int2Float# (word2Int# w#)) #)+ _ -> (# Overflow | #)++-- | Bounds-checked double conversion+toDouble# :: Word -> (# Overflows | Double #)+toDouble# (W# w#) = case leWord# w# 9007199254740991## of+ 1# -> (# | D# (int2Double# (word2Int# w#)) #)+ _ -> (# Overflow | #)
+ src/Unwitch/Convert/Word16.hs view
@@ -0,0 +1,98 @@+module Unwitch.Convert.Word16+ ( toWord8+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toWord8#+ , toInt8#+ , toInt16#+ )+where++import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (word16ToWord#, word2Int#,+ wordToWord8#, word8ToWord#,+ intToInt8#, int8ToInt#,+ intToInt16#, int16ToInt#,+ eqWord#, (==#))+import GHC.Int (Int8(..), Int16(..))+import GHC.Word (Word8(..), Word16(..))++toWord8 :: Word16 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord32 :: Word16 -> Word32+toWord32 = fromIntegral++toWord64 :: Word16 -> Word64+toWord64 = fromIntegral++toWord :: Word16 -> Word+toWord = fromIntegral++toNatural :: Word16 -> Natural+toNatural = fromIntegral++toInt8 :: Word16 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Word16 -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Word16 -> Int32+toInt32 = fromIntegral++toInt64 :: Word16 -> Int64+toInt64 = fromIntegral++toInt :: Word16 -> Int+toInt = fromIntegral++toInteger :: Word16 -> Integer+toInteger = fromIntegral++toFloat :: Word16 -> Float+toFloat = fromIntegral++toDouble :: Word16 -> Double+toDouble = fromIntegral++-- | Unsigned narrowing, roundtrip at Word#+toWord8# :: Word16 -> (# Word8 | (# #) #)+toWord8# (W16# w16#) =+ let w# = word16ToWord# w16#+ n# = wordToWord8# w#+ in case word8ToWord# n# `eqWord#` w# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned->signed, source fits in Int#, roundtrip at Int#+toInt8# :: Word16 -> (# Int8 | (# #) #)+toInt8# (W16# w16#) =+ let i# = word2Int# (word16ToWord# w16#)+ n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned->signed, source fits in Int#, roundtrip at Int#+toInt16# :: Word16 -> (# Int16 | (# #) #)+toInt16# (W16# w16#) =+ let i# = word2Int# (word16ToWord# w16#)+ n# = intToInt16# i#+ in case int16ToInt# n# ==# i# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)
+ src/Unwitch/Convert/Word32.hs view
@@ -0,0 +1,147 @@+module Unwitch.Convert.Word32+ ( toWord8+ , toWord16+ , toWord64+ , toWord+ , toNatural+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toWord8#+ , toWord16#+ , toWord#+ , toInt8#+ , toInt16#+ , toInt32#+ , toInt#+ , toFloat#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..),+ word32ToWord#, word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ intToInt8#, int8ToInt#,+ intToInt16#, int16ToInt#,+ intToInt32#, int32ToInt#,+ int2Float#,+ eqWord#, leWord#, (==#), (>=#))+import GHC.Int (Int8(..), Int16(..), Int32(..))+import GHC.Word (Word8(..), Word16(..), Word32(..))++toWord8 :: Word32 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Word32 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord64 :: Word32 -> Word64+toWord64 = fromIntegral++toWord :: Word32 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Word32 -> Natural+toNatural = fromIntegral++toInt8 :: Word32 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Word32 -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Word32 -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Word32 -> Int64+toInt64 = fromIntegral++toInt :: Word32 -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Word32 -> Integer+toInteger = fromIntegral++toFloat :: Word32 -> Either Overflows Float+toFloat x = if+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Word32 -> Double+toDouble = fromIntegral++-- | Unsigned narrowing, roundtrip at Word#+toWord8# :: Word32 -> (# Word8 | (# #) #)+toWord8# (W32# w32#) =+ let w# = word32ToWord# w32#+ n# = wordToWord8# w#+ in case word8ToWord# n# `eqWord#` w# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned narrowing, roundtrip at Word#+toWord16# :: Word32 -> (# Word16 | (# #) #)+toWord16# (W32# w32#) =+ let w# = word32ToWord# w32#+ n# = wordToWord16# w#+ in case word16ToWord# n# `eqWord#` w# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Word32 always fits in Word (Word is at least 32 bits)+toWord# :: Word32 -> (# Word | (# #) #)+toWord# (W32# w32#) = (# W# (word32ToWord# w32#) | #)++-- | Unsigned->signed, source fits in Int#, roundtrip at Int#+toInt8# :: Word32 -> (# Int8 | (# #) #)+toInt8# (W32# w32#) =+ let i# = word2Int# (word32ToWord# w32#)+ n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned->signed, source fits in Int#, roundtrip at Int#+toInt16# :: Word32 -> (# Int16 | (# #) #)+toInt16# (W32# w32#) =+ let i# = word2Int# (word32ToWord# w32#)+ n# = intToInt16# i#+ in case int16ToInt# n# ==# i# of+ 1# -> (# I16# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned->signed, roundtrip at Int#+toInt32# :: Word32 -> (# Int32 | (# #) #)+toInt32# (W32# w32#) =+ let i# = word2Int# (word32ToWord# w32#)+ n# = intToInt32# i#+ in case int32ToInt# n# ==# i# of+ 1# -> (# I32# n# | #)+ _ -> (# | (# #) #)++-- | Word32 fits in non-negative Int on all platforms, check via sign bit+toInt# :: Word32 -> (# Int | (# #) #)+toInt# (W32# w32#) =+ let i# = word2Int# (word32ToWord# w32#)+ in case i# >=# 0# of+ 1# -> (# I# i# | #)+ _ -> (# | (# #) #)++-- | Bounds-checked float conversion+toFloat# :: Word32 -> (# Overflows | Float #)+toFloat# (W32# w32#) = case leWord# (word32ToWord# w32#) 16777215## of+ 1# -> (# | F# (int2Float# (word2Int# (word32ToWord# w32#))) #)+ _ -> (# Overflow | #)
+ src/Unwitch/Convert/Word64.hs view
@@ -0,0 +1,178 @@+module Unwitch.Convert.Word64+ ( toWord8+ , toWord16+ , toWord32+ , toWord+ , toNatural+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toWord8#+ , toWord16#+ , toWord32#+ , toWord#+ , toInt8#+ , toInt16#+ , toInt32#+ , toInt64#+ , toInt#+ , toFloat#+ , toDouble#+ )+where++import Unwitch.Errors+import Unwitch.Constant+import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (Int(..), Word(..), Float(..), Double(..),+ word64ToWord#, wordToWord64#,+ word2Int#,+ wordToWord8#, word8ToWord#,+ wordToWord16#, word16ToWord#,+ wordToWord32#, word32ToWord#,+ intToInt8#, intToInt16#, intToInt32#,+ intToInt64#,+ int2Float#, int2Double#,+ eqWord64#, leWord64#,+ (>=#))+import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))+import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))++toWord8 :: Word64 -> Maybe Word8+toWord8 = Bits.toIntegralSized++toWord16 :: Word64 -> Maybe Word16+toWord16 = Bits.toIntegralSized++toWord32 :: Word64 -> Maybe Word32+toWord32 = Bits.toIntegralSized++toWord :: Word64 -> Maybe Word+toWord = Bits.toIntegralSized++toNatural :: Word64 -> Natural+toNatural = fromIntegral++toInt8 :: Word64 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Word64 -> Maybe Int16+toInt16 = Bits.toIntegralSized++toInt32 :: Word64 -> Maybe Int32+toInt32 = Bits.toIntegralSized++toInt64 :: Word64 -> Maybe Int64+toInt64 = Bits.toIntegralSized++toInt :: Word64 -> Maybe Int+toInt = Bits.toIntegralSized++toInteger :: Word64 -> Integer+toInteger = fromIntegral++toFloat :: Word64 -> Either Overflows Float+toFloat x = if+ | x > maxIntegralRepFloat -> Left Overflow+ | otherwise -> Right $ fromIntegral x++toDouble :: Word64 -> Either Overflows Double+toDouble x = if+ | x > maxIntegralRepDouble -> Left Overflow+ | otherwise -> Right $ fromIntegral x++-- | Unsigned narrowing via Word64# comparison+toWord8# :: Word64 -> (# Word8 | (# #) #)+toWord8# (W64# w64#) =+ let w# = word64ToWord# w64#+ n# = wordToWord8# w#+ in case eqWord64# (wordToWord64# (word8ToWord# n#)) w64# of+ 1# -> (# W8# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned narrowing via Word64# comparison+toWord16# :: Word64 -> (# Word16 | (# #) #)+toWord16# (W64# w64#) =+ let w# = word64ToWord# w64#+ n# = wordToWord16# w#+ in case eqWord64# (wordToWord64# (word16ToWord# n#)) w64# of+ 1# -> (# W16# n# | #)+ _ -> (# | (# #) #)++-- | Unsigned narrowing via Word64# comparison+toWord32# :: Word64 -> (# Word32 | (# #) #)+toWord32# (W64# w64#) =+ let w# = word64ToWord# w64#+ n# = wordToWord32# w#+ in case eqWord64# (wordToWord64# (word32ToWord# n#)) w64# of+ 1# -> (# W32# n# | #)+ _ -> (# | (# #) #)++-- | Roundtrip check at Word64# level+toWord# :: Word64 -> (# Word | (# #) #)+toWord# (W64# w64#) =+ let w# = word64ToWord# w64#+ in case eqWord64# (wordToWord64# w#) w64# of+ 1# -> (# W# w# | #)+ _ -> (# | (# #) #)++-- | Check upper bound at Word64# level+toInt8# :: Word64 -> (# Int8 | (# #) #)+toInt8# (W64# w64#) = case leWord64# w64# (wordToWord64# 127##) of+ 1# -> (# I8# (intToInt8# (word2Int# (word64ToWord# w64#))) | #)+ _ -> (# | (# #) #)++-- | Check upper bound for Int16+toInt16# :: Word64 -> (# Int16 | (# #) #)+toInt16# (W64# w64#) = case leWord64# w64# (wordToWord64# 32767##) of+ 1# -> (# I16# (intToInt16# (word2Int# (word64ToWord# w64#))) | #)+ _ -> (# | (# #) #)++-- | Check upper bound for Int32+toInt32# :: Word64 -> (# Int32 | (# #) #)+toInt32# (W64# w64#) = case leWord64# w64# (wordToWord64# 2147483647##) of+ 1# -> (# I32# (intToInt32# (word2Int# (word64ToWord# w64#))) | #)+ _ -> (# | (# #) #)++-- | Check high bit not set for Int64+toInt64# :: Word64 -> (# Int64 | (# #) #)+toInt64# (W64# w64#) =+ let w# = word64ToWord# w64#+ i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> case eqWord64# (wordToWord64# w#) w64# of+ 1# -> (# I64# (intToInt64# i#) | #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)++-- | Check fits in non-negative Int range+toInt# :: Word64 -> (# Int | (# #) #)+toInt# (W64# w64#) =+ let w# = word64ToWord# w64#+ i# = word2Int# w#+ in case i# >=# 0# of+ 1# -> case eqWord64# (wordToWord64# w#) w64# of+ 1# -> (# I# i# | #)+ _ -> (# | (# #) #)+ _ -> (# | (# #) #)++-- | Bounds-checked float conversion at Word64# level+toFloat# :: Word64 -> (# Overflows | Float #)+toFloat# (W64# w64#) = case leWord64# w64# (wordToWord64# 16777215##) of+ 1# -> (# | F# (int2Float# (word2Int# (word64ToWord# w64#))) #)+ _ -> (# Overflow | #)++-- | Bounds-checked double conversion at Word64# level+toDouble# :: Word64 -> (# Overflows | Double #)+toDouble# (W64# w64#) = case leWord64# w64# (wordToWord64# 9007199254740991##) of+ 1# -> (# | D# (int2Double# (word2Int# (word64ToWord# w64#))) #)+ _ -> (# Overflow | #)
+ src/Unwitch/Convert/Word8.hs view
@@ -0,0 +1,75 @@+module Unwitch.Convert.Word8+ ( toWord16+ , toWord32+ , toWord64+ , toWord+ , toNatural+ , toInt8+ , toInt16+ , toInt32+ , toInt64+ , toInt+ , toInteger+ , toFloat+ , toDouble+ , toInt8#+ )+where++import qualified Data.Bits as Bits+import Data.Word+import Data.Int+import Numeric.Natural (Natural)+import Prelude hiding (toInteger)+import GHC.Exts (word8ToWord#, word2Int#, intToInt8#, int8ToInt#,+ (==#))+import GHC.Int (Int8(..))+import GHC.Word (Word8(..))++toWord16 :: Word8 -> Word16+toWord16 = fromIntegral++toWord32 :: Word8 -> Word32+toWord32 = fromIntegral++toWord64 :: Word8 -> Word64+toWord64 = fromIntegral++toWord :: Word8 -> Word+toWord = fromIntegral++toNatural :: Word8 -> Natural+toNatural = fromIntegral++toInt8 :: Word8 -> Maybe Int8+toInt8 = Bits.toIntegralSized++toInt16 :: Word8 -> Int16+toInt16 = fromIntegral++toInt32 :: Word8 -> Int32+toInt32 = fromIntegral++toInt64 :: Word8 -> Int64+toInt64 = fromIntegral++toInt :: Word8 -> Int+toInt = fromIntegral++toInteger :: Word8 -> Integer+toInteger = fromIntegral++toFloat :: Word8 -> Float+toFloat = fromIntegral++toDouble :: Word8 -> Double+toDouble = fromIntegral++-- | Unsigned->signed, source fits in Int#, roundtrip at Int#+toInt8# :: Word8 -> (# Int8 | (# #) #)+toInt8# (W8# w8#) =+ let i# = word2Int# (word8ToWord# w8#)+ n# = intToInt8# i#+ in case int8ToInt# n# ==# i# of+ 1# -> (# I8# n# | #)+ _ -> (# | (# #) #)
+ src/Unwitch/Errors.hs view
@@ -0,0 +1,8 @@+module Unwitch.Errors+ ( Overflows(..)+ )+where++data Overflows = Overflow+ | Underflow+ deriving (Show, Eq)
+ src/Unwitch/TryEvaluate.hs view
@@ -0,0 +1,11 @@+module Unwitch.TryEvaluate+ ( tryEvaluate+ )+where++import Control.Exception (Exception, evaluate, try)+import System.IO.Unsafe (unsafePerformIO)++tryEvaluate :: Exception e => a -> Either e a+tryEvaluate = unsafePerformIO . try . evaluate+{-# NOINLINE tryEvaluate #-}
+ test/Spec.hs view
@@ -0,0 +1,36 @@+module Main where++import Test.Hspec+import qualified Test.Convert.Int8Spec+import qualified Test.Convert.Int16Spec+import qualified Test.Convert.Int32Spec+import qualified Test.Convert.Int64Spec+import qualified Test.Convert.IntSpec+import qualified Test.Convert.Word8Spec+import qualified Test.Convert.Word16Spec+import qualified Test.Convert.Word32Spec+import qualified Test.Convert.Word64Spec+import qualified Test.Convert.WordSpec+import qualified Test.Convert.NaturalSpec+import qualified Test.Convert.IntegerSpec+import qualified Test.Convert.FloatSpec+import qualified Test.Convert.DoubleSpec+import qualified Test.Convert.PropertySpec++main :: IO ()+main = hspec $ do+ Test.Convert.Int8Spec.spec+ Test.Convert.Int16Spec.spec+ Test.Convert.Int32Spec.spec+ Test.Convert.Int64Spec.spec+ Test.Convert.IntSpec.spec+ Test.Convert.Word8Spec.spec+ Test.Convert.Word16Spec.spec+ Test.Convert.Word32Spec.spec+ Test.Convert.Word64Spec.spec+ Test.Convert.WordSpec.spec+ Test.Convert.NaturalSpec.spec+ Test.Convert.IntegerSpec.spec+ Test.Convert.FloatSpec.spec+ Test.Convert.DoubleSpec.spec+ Test.Convert.PropertySpec.spec
+ test/Test/Convert/ByteStringSpec.hs view
@@ -0,0 +1,72 @@+module Test.Convert.ByteStringSpec (spec) where++import Test.Hspec+import Data.ByteString qualified as BS+import Data.Word (Word8)+import qualified Unwitch.Convert.ByteString as ByteString+import qualified Unwitch.Convert.LazyByteString as LazyByteString+import qualified Unwitch.Convert.ShortByteString as ShortByteString++spec :: Spec+spec = describe "Unwitch.Convert.ByteString" $ do++ describe "toLazyByteString / toByteString round-trip" $+ it "round-trips" $+ let bs = BS.pack [104, 101, 108, 108, 111]+ in LazyByteString.toByteString (ByteString.toLazyByteString bs) `shouldBe` bs++ describe "toShortByteString round-trip" $+ it "round-trips with ShortByteString.toByteString" $+ let bs = BS.pack [1, 2, 3]+ in ShortByteString.toByteString (ByteString.toShortByteString bs) `shouldBe` bs++ describe "toWord8s / fromWord8s" $ do+ it "round-trips" $+ let ws = [72, 105] :: [Word8]+ in ByteString.toWord8s (ByteString.fromWord8s ws) `shouldBe` ws+ it "empty list" $+ ByteString.toWord8s (ByteString.fromWord8s []) `shouldBe` ([] :: [Word8])++ describe "toTextLatin1" $+ it "decodes latin1 bytes" $+ let bs = BS.pack [0xE9, 0xE8] -- e-acute, e-grave+ in ByteString.toTextLatin1 bs `shouldBe` "\x00E9\x00E8"++ describe "toTextUtf8" $ do+ it "decodes valid UTF-8" $+ ByteString.toTextUtf8 "hello" `shouldBe` Right "hello"+ it "fails on invalid UTF-8" $+ let bs = BS.pack [0xFF, 0xFE]+ in case ByteString.toTextUtf8 bs of+ Left _ -> pure ()+ Right _ -> expectationFailure "expected Left"++ describe "toTextUtf16LE" $ do+ it "decodes valid UTF-16LE" $+ let bs = BS.pack [0x41, 0x00] -- 'A' in UTF-16LE+ in ByteString.toTextUtf16LE bs `shouldBe` Right "A"+ it "fails on truncated input" $+ let bs = BS.pack [0x41] -- odd byte count+ in case ByteString.toTextUtf16LE bs of+ Left _ -> pure ()+ Right _ -> expectationFailure "expected Left"++ describe "toTextUtf16BE" $+ it "decodes valid UTF-16BE" $+ let bs = BS.pack [0x00, 0x42] -- 'B' in UTF-16BE+ in ByteString.toTextUtf16BE bs `shouldBe` Right "B"++ describe "toTextUtf32LE" $+ it "decodes valid UTF-32LE" $+ let bs = BS.pack [0x43, 0x00, 0x00, 0x00] -- 'C' in UTF-32LE+ in ByteString.toTextUtf32LE bs `shouldBe` Right "C"++ describe "toTextUtf32BE" $ do+ it "decodes valid UTF-32BE" $+ let bs = BS.pack [0x00, 0x00, 0x00, 0x44] -- 'D' in UTF-32BE+ in ByteString.toTextUtf32BE bs `shouldBe` Right "D"+ it "fails on invalid input" $+ let bs = BS.pack [0x01] -- not 4-byte aligned+ in case ByteString.toTextUtf32BE bs of+ Left _ -> pure ()+ Right _ -> expectationFailure "expected Left"
+ test/Test/Convert/CharSpec.hs view
@@ -0,0 +1,41 @@+module Test.Convert.CharSpec (spec) where++import Test.Hspec+import qualified Unwitch.Convert.Char as Char++spec :: Spec+spec = describe "Unwitch.Convert.Char" $ do++ describe "toInt" $ do+ it "converts 'A' to 65" $+ Char.toInt 'A' `shouldBe` 65+ it "converts '0' to 48" $+ Char.toInt '0' `shouldBe` 48++ describe "toWord" $ do+ it "converts 'A' to 65" $+ Char.toWord 'A' `shouldBe` (65 :: Word)+ it "converts null to 0" $+ Char.toWord '\0' `shouldBe` (0 :: Word)++ describe "fromInt" $ do+ it "succeeds for valid codepoint" $+ Char.fromInt 65 `shouldBe` Just 'A'+ it "fails for negative" $+ Char.fromInt (-1) `shouldBe` Nothing+ it "fails for value > 0x10FFFF" $+ Char.fromInt 0x110000 `shouldBe` Nothing+ it "fails for surrogate" $+ Char.fromInt 0xD800 `shouldBe` Nothing++ describe "fromWord" $ do+ it "succeeds for valid codepoint" $+ Char.fromWord 65 `shouldBe` Just 'A'+ it "fails for value > 0x10FFFF" $+ Char.fromWord 0x110000 `shouldBe` Nothing+ it "fails for surrogate" $+ Char.fromWord 0xD800 `shouldBe` Nothing++ describe "round-trip" $+ it "fromInt . toInt == Just" $+ Char.fromInt (Char.toInt 'Z') `shouldBe` Just 'Z'
+ test/Test/Convert/ComplexSpec.hs view
@@ -0,0 +1,24 @@+module Test.Convert.ComplexSpec (spec) where++import Test.Hspec+import Data.Complex (Complex((:+)))+import qualified Unwitch.Convert.Complex as Complex++spec :: Spec+spec = describe "Unwitch.Convert.Complex" $ do++ describe "fromReal" $ do+ it "wraps with zero imaginary part" $+ Complex.fromReal (3.0 :: Double) `shouldBe` (3.0 :+ 0.0)+ it "wraps zero" $+ Complex.fromReal (0 :: Int) `shouldBe` (0 :+ 0)++ describe "toReal" $ do+ it "succeeds when imaginary is 0" $+ Complex.toReal (5.0 :+ 0.0 :: Complex Double) `shouldBe` Just 5.0+ it "fails when imaginary is nonzero" $+ Complex.toReal (5.0 :+ 1.0 :: Complex Double) `shouldBe` Nothing++ describe "round-trip" $+ it "toReal . fromReal == Just" $+ Complex.toReal (Complex.fromReal (42.0 :: Double)) `shouldBe` Just 42.0
+ test/Test/Convert/DoubleSpec.hs view
@@ -0,0 +1,87 @@+module Test.Convert.DoubleSpec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import Unwitch.Errors+import qualified Unwitch.Convert.Double as Double++spec :: Spec+spec = describe "Unwitch.Convert.Double" $ do++ describe "toFloat" $+ it "converts 0" $+ Double.toFloat 0.0 `shouldBe` 0.0++ describe "toRational" $ do+ it "converts finite value" $+ Double.toRational 1.5 `shouldBe` Right (3 / 2)+ it "rejects NaN" $+ Double.toRational (0 / 0 :: Double) `shouldSatisfy` isLeft+ it "rejects positive Infinity" $+ Double.toRational (1 / 0 :: Double) `shouldBe` Left (Double.IsInf Overflow)+ it "rejects negative Infinity" $+ Double.toRational ((-1) / 0 :: Double) `shouldBe` Left (Double.IsInf Underflow)++ describe "toInteger" $ do+ it "converts whole number" $+ Double.toInteger 42.0 `shouldBe` Right 42+ it "rejects fractional" $+ Double.toInteger 1.5 `shouldSatisfy` isLeft++ describe "toInt8" $ do+ it "converts in-range" $+ Double.toInt8 42.0 `shouldBe` Right (42 :: Int8)+ it "rejects out-of-range" $+ Double.toInt8 200.0 `shouldSatisfy` isLeft++ describe "toInt16" $+ it "converts in-range" $+ Double.toInt16 1000.0 `shouldBe` Right (1000 :: Int16)++ describe "toInt32" $+ it "converts in-range" $+ Double.toInt32 100000.0 `shouldBe` Right (100000 :: Int32)++ describe "toInt64" $+ it "converts in-range" $+ Double.toInt64 100000.0 `shouldBe` Right (100000 :: Int64)++ describe "toInt" $+ it "converts in-range" $+ Double.toInt 42.0 `shouldBe` Right (42 :: Int)++ describe "toWord8" $ do+ it "converts in-range" $+ Double.toWord8 200.0 `shouldBe` Right (200 :: Word8)+ it "rejects negative" $+ Double.toWord8 (-1.0) `shouldSatisfy` isLeft++ describe "toWord16" $+ it "converts in-range" $+ Double.toWord16 1000.0 `shouldBe` Right (1000 :: Word16)++ describe "toWord32" $+ it "converts in-range" $+ Double.toWord32 100000.0 `shouldBe` Right (100000 :: Word32)++ describe "toWord64" $+ it "converts in-range" $+ Double.toWord64 100000.0 `shouldBe` Right (100000 :: Word64)++ describe "toWord" $+ it "converts in-range" $+ Double.toWord 42.0 `shouldBe` Right (42 :: Word)++ describe "toNatural" $ do+ it "converts positive whole number" $+ Double.toNatural 42.0 `shouldBe` Right (42 :: Natural)+ it "rejects negative" $+ Double.toNatural (-1.0) `shouldSatisfy` isLeft+ it "rejects fractional" $+ Double.toNatural 1.5 `shouldSatisfy` isLeft++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft (Right _) = False
+ test/Test/Convert/FixedSpec.hs view
@@ -0,0 +1,38 @@+module Test.Convert.FixedSpec (spec) where++import Test.Hspec+import Data.Fixed (Fixed, E2, E6)+import Data.Ratio (numerator)+import qualified Unwitch.Convert.Fixed as Fixed++spec :: Spec+spec = describe "Unwitch.Convert.Fixed" $ do++ describe "fromInteger" $ do+ it "converts integer to Fixed E2" $+ Fixed.fromInteger 42 `shouldBe` (42.0 :: Fixed E2)+ it "converts zero" $+ Fixed.fromInteger 0 `shouldBe` (0.0 :: Fixed E2)++ describe "toInteger" $ do+ it "succeeds for whole Fixed values" $+ Fixed.toInteger (42.0 :: Fixed E2) `shouldBe` Just 42+ it "fails for fractional Fixed values" $+ Fixed.toInteger (1.50 :: Fixed E2) `shouldBe` Nothing++ describe "toRational" $ do+ it "converts Fixed to exact Rational" $+ Fixed.toRational (1.50 :: Fixed E2) `shouldBe` (3 / 2)+ it "round-trips whole values through toInteger" $+ let f = 10.0 :: Fixed E2+ in Fixed.toInteger f `shouldBe` Just (numerator (Fixed.toRational f))++ describe "toFixed" $ do+ it "succeeds for compatible resolutions" $+ Fixed.toFixed (1.50 :: Fixed E2) `shouldBe` Just (1.500000 :: Fixed E6)+ it "fails for incompatible resolutions" $+ -- 0.01 as E2 is representable, but converting to E6 should also work+ -- However 1/3 cannot be represented in either, so let's test a value+ -- that exists in E6 but not in E2:+ -- 1.123 in E6 -> 1.12 in E2 loses precision+ Fixed.toFixed (1.123000 :: Fixed E6) `shouldBe` (Nothing :: Maybe (Fixed E2))
+ test/Test/Convert/FloatSpec.hs view
@@ -0,0 +1,91 @@+module Test.Convert.FloatSpec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import Unwitch.Errors+import qualified Unwitch.Convert.Float as Float++spec :: Spec+spec = describe "Unwitch.Convert.Float" $ do++ describe "toDouble (infallible)" $ do+ it "converts 0" $+ Float.toDouble 0.0 `shouldBe` 0.0+ it "converts 1.5" $+ Float.toDouble 1.5 `shouldBe` 1.5++ describe "toRational" $ do+ it "converts finite value" $+ Float.toRational 1.5 `shouldBe` Right (3 / 2)+ it "rejects NaN" $+ Float.toRational (0 / 0 :: Float) `shouldSatisfy` isLeft+ it "rejects positive Infinity" $+ Float.toRational (1 / 0 :: Float) `shouldBe` Left (Float.IsInf Overflow)+ it "rejects negative Infinity" $+ Float.toRational ((-1) / 0 :: Float) `shouldBe` Left (Float.IsInf Underflow)++ describe "toInteger" $ do+ it "converts whole number" $+ Float.toInteger 42.0 `shouldBe` Right 42+ it "rejects fractional" $+ Float.toInteger 1.5 `shouldSatisfy` isLeft+ it "rejects NaN" $+ Float.toInteger (0 / 0 :: Float) `shouldSatisfy` isLeft++ describe "toInt8" $ do+ it "converts in-range" $+ Float.toInt8 42.0 `shouldBe` Right (42 :: Int8)+ it "rejects out-of-range" $+ Float.toInt8 200.0 `shouldSatisfy` isLeft+ it "rejects fractional" $+ Float.toInt8 1.5 `shouldSatisfy` isLeft++ describe "toInt16" $+ it "converts in-range" $+ Float.toInt16 1000.0 `shouldBe` Right (1000 :: Int16)++ describe "toInt32" $+ it "converts in-range" $+ Float.toInt32 100000.0 `shouldBe` Right (100000 :: Int32)++ describe "toInt64" $+ it "converts in-range" $+ Float.toInt64 100000.0 `shouldBe` Right (100000 :: Int64)++ describe "toInt" $+ it "converts in-range" $+ Float.toInt 42.0 `shouldBe` Right (42 :: Int)++ describe "toWord8" $ do+ it "converts in-range" $+ Float.toWord8 200.0 `shouldBe` Right (200 :: Word8)+ it "rejects negative" $+ Float.toWord8 (-1.0) `shouldSatisfy` isLeft++ describe "toWord16" $+ it "converts in-range" $+ Float.toWord16 1000.0 `shouldBe` Right (1000 :: Word16)++ describe "toWord32" $+ it "converts in-range" $+ Float.toWord32 100000.0 `shouldBe` Right (100000 :: Word32)++ describe "toWord64" $+ it "converts in-range" $+ Float.toWord64 100000.0 `shouldBe` Right (100000 :: Word64)++ describe "toWord" $+ it "converts in-range" $+ Float.toWord 42.0 `shouldBe` Right (42 :: Word)++ describe "toNatural" $ do+ it "converts positive whole number" $+ Float.toNatural 42.0 `shouldBe` Right (42 :: Natural)+ it "rejects negative" $+ Float.toNatural (-1.0) `shouldSatisfy` isLeft++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft (Right _) = False
+ test/Test/Convert/Int16Spec.hs view
@@ -0,0 +1,39 @@+module Test.Convert.Int16Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Int16 as Int16++-- Property tests cover: toInt8 narrowing, toInt32/64 round-trips,+-- toWord16 (same-width), toNatural iff negative, toFloat/toDouble.+-- Kept: toInt (no property), cross-width signed-to-unsigned.++spec :: Spec+spec = describe "Unwitch.Convert.Int16" $ do++ describe "toInt (infallible)" $+ it "widens maxBound" $+ Int16.toInt maxBound `shouldBe` 32767++ describe "toWord8 (fallible)" $ do+ it "rejects negative" $+ Int16.toWord8 (-1 :: Int16) `shouldBe` Nothing+ it "rejects too large" $+ Int16.toWord8 (256 :: Int16) `shouldBe` Nothing+ it "converts in range" $+ Int16.toWord8 (255 :: Int16) `shouldBe` Just (255 :: Word8)++ describe "toWord32 (fallible)" $ do+ it "rejects negative" $+ Int16.toWord32 (-1 :: Int16) `shouldBe` Nothing+ it "converts maxBound" $+ Int16.toWord32 (32767 :: Int16) `shouldBe` Just (32767 :: Word32)++ describe "toWord64 (fallible)" $+ it "rejects negative" $+ Int16.toWord64 (-1 :: Int16) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "rejects negative" $+ Int16.toWord (-1 :: Int16) `shouldBe` Nothing
+ test/Test/Convert/Int32Spec.hs view
@@ -0,0 +1,43 @@+module Test.Convert.Int32Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Int32 as Int32++-- Property tests cover: toInt16 narrowing, toInt64 round-trip,+-- toWord32 (same-width), toNatural iff negative,+-- toFloat range check, toDouble preserve value.+-- Kept: toInt8 (no property), toInt (no property),+-- cross-width signed-to-unsigned.++spec :: Spec+spec = describe "Unwitch.Convert.Int32" $ do++ describe "toInt8 (fallible)" $ do+ it "narrows in-range" $+ Int32.toInt8 (100 :: Int32) `shouldBe` Just (100 :: Int8)+ it "rejects out-of-range" $+ Int32.toInt8 (200 :: Int32) `shouldBe` Nothing++ describe "toInt (fallible via toIntegralSized)" $+ it "converts 0" $+ Int32.toInt 0 `shouldBe` Just 0++ describe "toWord8 (fallible)" $ do+ it "rejects negative" $+ Int32.toWord8 (-1 :: Int32) `shouldBe` Nothing+ it "converts in-range" $+ Int32.toWord8 (255 :: Int32) `shouldBe` Just (255 :: Word8)++ describe "toWord16 (fallible)" $+ it "rejects too large" $+ Int32.toWord16 (70000 :: Int32) `shouldBe` Nothing++ describe "toWord64 (fallible)" $+ it "rejects negative" $+ Int32.toWord64 (-1 :: Int32) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "rejects negative" $+ Int32.toWord (-1 :: Int32) `shouldBe` Nothing
+ test/Test/Convert/Int64Spec.hs view
@@ -0,0 +1,44 @@+module Test.Convert.Int64Spec (spec) where++import Test.Hspec+import Data.Int+import qualified Unwitch.Convert.Int64 as Int64++-- Property tests cover: toInt32 narrowing, toInteger widening,+-- toWord64 (same-width), toNatural iff negative,+-- toFloat range check, toDouble range check.+-- Kept: toInt8/toInt16 (no property), toInt (no property),+-- cross-width signed-to-unsigned.++spec :: Spec+spec = describe "Unwitch.Convert.Int64" $ do++ describe "toInt8 (fallible)" $ do+ it "narrows in-range" $+ Int64.toInt8 (42 :: Int64) `shouldBe` Just (42 :: Int8)+ it "rejects out-of-range" $+ Int64.toInt8 (200 :: Int64) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects out-of-range" $+ Int64.toInt16 (40000 :: Int64) `shouldBe` Nothing++ describe "toInt (fallible)" $+ it "converts 0" $+ Int64.toInt 0 `shouldBe` Just 0++ describe "toWord8 (fallible)" $+ it "rejects negative" $+ Int64.toWord8 (-1 :: Int64) `shouldBe` Nothing++ describe "toWord16 (fallible)" $+ it "rejects negative" $+ Int64.toWord16 (-1 :: Int64) `shouldBe` Nothing++ describe "toWord32 (fallible)" $+ it "rejects negative" $+ Int64.toWord32 (-1 :: Int64) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "rejects negative" $+ Int64.toWord (-1 :: Int64) `shouldBe` Nothing
+ test/Test/Convert/Int8Spec.hs view
@@ -0,0 +1,38 @@+module Test.Convert.Int8Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Int8 as Int8++-- Only cross-width signed-to-unsigned conversions that no property test covers.+-- All other Int8 conversions are fully covered by PropertySpec:+-- round-trips, toNatural iff negative, toFloat/toDouble preserve value,+-- path independence, signed-to-unsigned (same-width Int8->Word8).++spec :: Spec+spec = describe "Unwitch.Convert.Int8" $ do++ describe "toWord16 (fallible)" $ do+ it "rejects negative values" $+ Int8.toWord16 (-1 :: Int8) `shouldBe` Nothing+ it "converts maxBound" $+ Int8.toWord16 (127 :: Int8) `shouldBe` Just (127 :: Word16)++ describe "toWord32 (fallible)" $ do+ it "rejects negative values" $+ Int8.toWord32 (-1 :: Int8) `shouldBe` Nothing+ it "converts maxBound" $+ Int8.toWord32 (127 :: Int8) `shouldBe` Just (127 :: Word32)++ describe "toWord64 (fallible)" $ do+ it "rejects negative values" $+ Int8.toWord64 (-1 :: Int8) `shouldBe` Nothing+ it "converts maxBound" $+ Int8.toWord64 (127 :: Int8) `shouldBe` Just (127 :: Word64)++ describe "toWord (fallible)" $ do+ it "rejects negative values" $+ Int8.toWord (-1 :: Int8) `shouldBe` Nothing+ it "converts maxBound" $+ Int8.toWord (127 :: Int8) `shouldBe` Just (127 :: Word)
+ test/Test/Convert/IntSpec.hs view
@@ -0,0 +1,73 @@+module Test.Convert.IntSpec (spec) where++import Test.Hspec+import Data.Int+import Numeric.Natural (Natural)+import Unwitch.Errors+import qualified Unwitch.Convert.Int as Int++-- Property tests only cover Int.toNatural (iff negative).+-- All other Int conversions have no property coverage.++spec :: Spec+spec = describe "Unwitch.Convert.Int" $ do++ describe "toInt8 (fallible)" $ do+ it "narrows in-range" $+ Int.toInt8 (42 :: Int) `shouldBe` Just (42 :: Int8)+ it "rejects out-of-range" $+ Int.toInt8 (200 :: Int) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects out-of-range" $+ Int.toInt16 (40000 :: Int) `shouldBe` Nothing++ describe "toInt32 (fallible)" $+ it "converts in-range" $+ Int.toInt32 (1000 :: Int) `shouldBe` Just (1000 :: Int32)++ describe "toInt64 (infallible)" $+ it "widens maxBound" $+ Int.toInt64 maxBound `shouldBe` fromIntegral (maxBound :: Int)++ describe "toInteger (infallible)" $+ it "converts maxBound" $+ Int.toInteger maxBound `shouldBe` fromIntegral (maxBound :: Int)++ describe "toWord8 (fallible)" $+ it "rejects negative" $+ Int.toWord8 (-1 :: Int) `shouldBe` Nothing++ describe "toWord16 (fallible)" $+ it "rejects negative" $+ Int.toWord16 (-1 :: Int) `shouldBe` Nothing++ describe "toWord32 (fallible)" $+ it "rejects negative" $+ Int.toWord32 (-1 :: Int) `shouldBe` Nothing++ describe "toWord64 (fallible)" $+ it "rejects negative" $+ Int.toWord64 (-1 :: Int) `shouldBe` Nothing++ describe "toWord (fallible)" $ do+ it "rejects negative" $+ Int.toWord (-1 :: Int) `shouldBe` Nothing+ it "converts 0" $+ Int.toWord 0 `shouldBe` Just (0 :: Word)++ describe "toNatural" $ do+ it "rejects negative with Underflow" $+ Int.toNatural (-1 :: Int) `shouldBe` Left Underflow+ it "converts 0" $+ Int.toNatural 0 `shouldBe` Right (0 :: Natural)++ describe "toFloat (range-checked)" $ do+ it "converts at boundary" $+ Int.toFloat (16777215 :: Int) `shouldBe` Right 16777215.0+ it "rejects above boundary" $+ Int.toFloat (16777216 :: Int) `shouldBe` Left Overflow++ describe "toDouble (range-checked)" $+ it "converts 0" $+ Int.toDouble 0 `shouldBe` Right (0.0 :: Double)
+ test/Test/Convert/IntegerSpec.hs view
@@ -0,0 +1,46 @@+module Test.Convert.IntegerSpec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Integer as Integer++-- Property tests cover: toDouble/toFloat/toNatural range checks,+-- toInt8/toWord8 narrowing (success implies fromIntegral).+-- Kept: toInt16, toInt32, toInt64, toInt, toWord16, toWord32,+-- toWord64, toWord (no direct property coverage).++spec :: Spec+spec = describe "Unwitch.Convert.Integer" $ do++ describe "toInt16 (fallible)" $+ it "rejects too large" $+ Integer.toInt16 32768 `shouldBe` Nothing++ describe "toInt32 (fallible)" $+ it "rejects too large" $+ Integer.toInt32 2147483648 `shouldBe` Nothing++ describe "toInt64 (fallible)" $+ it "converts in-range" $+ Integer.toInt64 42 `shouldBe` Just (42 :: Int64)++ describe "toInt (fallible)" $+ it "converts 0" $+ Integer.toInt 0 `shouldBe` Just (0 :: Int)++ describe "toWord16 (fallible)" $+ it "rejects too large" $+ Integer.toWord16 65536 `shouldBe` Nothing++ describe "toWord32 (fallible)" $+ it "rejects too large" $+ Integer.toWord32 4294967296 `shouldBe` Nothing++ describe "toWord64 (fallible)" $+ it "converts in-range" $+ Integer.toWord64 42 `shouldBe` Just (42 :: Word64)++ describe "toWord (fallible)" $+ it "converts 0" $+ Integer.toWord 0 `shouldBe` Just (0 :: Word)
+ test/Test/Convert/LazyByteStringSpec.hs view
@@ -0,0 +1,56 @@+module Test.Convert.LazyByteStringSpec (spec) where++import Test.Hspec+import Data.ByteString.Lazy qualified as LBS+import Data.Word (Word8)+import qualified Unwitch.Convert.LazyByteString as LazyByteString+import qualified Unwitch.Convert.ByteString as ByteString++spec :: Spec+spec = describe "Unwitch.Convert.LazyByteString" $ do++ describe "toByteString / toLazyByteString round-trip" $+ it "round-trips" $+ let lbs = LBS.pack [10, 20, 30]+ in ByteString.toLazyByteString (LazyByteString.toByteString lbs) `shouldBe` lbs++ describe "toWord8s / fromWord8s" $ do+ it "round-trips" $+ let ws = [1, 2, 3, 4] :: [Word8]+ in LazyByteString.toWord8s (LazyByteString.fromWord8s ws) `shouldBe` ws+ it "empty list" $+ LazyByteString.toWord8s (LazyByteString.fromWord8s []) `shouldBe` ([] :: [Word8])++ describe "toLazyTextLatin1" $+ it "decodes latin1 bytes" $+ let lbs = LBS.pack [0xFC] -- u-umlaut+ in LazyByteString.toLazyTextLatin1 lbs `shouldBe` "\x00FC"++ describe "toLazyTextUtf8" $ do+ it "decodes valid UTF-8" $+ LazyByteString.toLazyTextUtf8 "hello" `shouldBe` Right "hello"+ it "fails on invalid UTF-8" $+ let lbs = LBS.pack [0xFF, 0xFE]+ in case LazyByteString.toLazyTextUtf8 lbs of+ Left _ -> pure ()+ Right _ -> expectationFailure "expected Left"++ describe "toLazyTextUtf16LE" $+ it "decodes valid UTF-16LE" $+ let lbs = LBS.pack [0x41, 0x00]+ in LazyByteString.toLazyTextUtf16LE lbs `shouldBe` Right "A"++ describe "toLazyTextUtf16BE" $+ it "decodes valid UTF-16BE" $+ let lbs = LBS.pack [0x00, 0x42]+ in LazyByteString.toLazyTextUtf16BE lbs `shouldBe` Right "B"++ describe "toLazyTextUtf32LE" $+ it "decodes valid UTF-32LE" $+ let lbs = LBS.pack [0x43, 0x00, 0x00, 0x00]+ in LazyByteString.toLazyTextUtf32LE lbs `shouldBe` Right "C"++ describe "toLazyTextUtf32BE" $+ it "decodes valid UTF-32BE" $+ let lbs = LBS.pack [0x00, 0x00, 0x00, 0x44]+ in LazyByteString.toLazyTextUtf32BE lbs `shouldBe` Right "D"
+ test/Test/Convert/LazyTextSpec.hs view
@@ -0,0 +1,60 @@+module Test.Convert.LazyTextSpec (spec) where++import Test.Hspec+import Data.Text.Lazy qualified as LT+import qualified Unwitch.Convert.LazyText as LazyText+import qualified Unwitch.Convert.Text as Text+import qualified Unwitch.Convert.LazyByteString as LazyByteString++spec :: Spec+spec = describe "Unwitch.Convert.LazyText" $ do++ describe "toText / toLazyText round-trip" $+ it "round-trips" $+ let lt = LT.pack "hello world"+ in Text.toLazyText (LazyText.toText lt) `shouldBe` lt++ describe "toString / fromString" $ do+ it "round-trips" $+ let s = "test string"+ in LazyText.toString (LazyText.fromString s) `shouldBe` s+ it "handles unicode" $+ let s = "\x00E9\x00FC\x00F1"+ in LazyText.toString (LazyText.fromString s) `shouldBe` s++ describe "toLazyByteStringUtf8" $ do+ it "encodes ASCII" $+ LazyText.toLazyByteStringUtf8 "hello" `shouldBe` "hello"+ it "round-trips with LazyByteString.toLazyTextUtf8" $+ let lt = LT.pack "caf\x00E9"+ in LazyByteString.toLazyTextUtf8 (LazyText.toLazyByteStringUtf8 lt) `shouldBe` Right lt++ describe "toLazyByteStringUtf16LE" $+ it "produces valid output" $+ let lt = LT.pack "A"+ in LazyByteString.toLazyTextUtf16LE (LazyText.toLazyByteStringUtf16LE lt) `shouldBe` Right lt++ describe "toLazyByteStringUtf16BE" $+ it "produces valid output" $+ let lt = LT.pack "B"+ in LazyByteString.toLazyTextUtf16BE (LazyText.toLazyByteStringUtf16BE lt) `shouldBe` Right lt++ describe "toLazyByteStringUtf32LE" $+ it "produces valid output" $+ let lt = LT.pack "C"+ in LazyByteString.toLazyTextUtf32LE (LazyText.toLazyByteStringUtf32LE lt) `shouldBe` Right lt++ describe "toLazyByteStringUtf32BE" $+ it "produces valid output" $+ let lt = LT.pack "D"+ in LazyByteString.toLazyTextUtf32BE (LazyText.toLazyByteStringUtf32BE lt) `shouldBe` Right lt++ describe "toLazyByteStringLatin1" $ do+ it "succeeds for Latin1 range chars" $+ let lt = LT.pack "\x00E9\x00FC"+ in case LazyText.toLazyByteStringLatin1 lt of+ Just lbs -> LazyByteString.toLazyTextLatin1 lbs `shouldBe` lt+ Nothing -> expectationFailure "expected Just"+ it "fails for chars above 0xFF" $+ let lt = LT.pack "\x0100"+ in LazyText.toLazyByteStringLatin1 lt `shouldBe` Nothing
+ test/Test/Convert/NaturalSpec.hs view
@@ -0,0 +1,69 @@+module Test.Convert.NaturalSpec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import Unwitch.Errors+import qualified Unwitch.Convert.Natural as Natural++spec :: Spec+spec = describe "Unwitch.Convert.Natural" $ do++ describe "toWord8 (fallible)" $ do+ it "rejects too large" $+ Natural.toWord8 (256 :: Natural) `shouldBe` Nothing+ it "converts in-range" $+ Natural.toWord8 (255 :: Natural) `shouldBe` Just (255 :: Word8)++ describe "toWord16 (fallible)" $+ it "rejects too large" $+ Natural.toWord16 (65536 :: Natural) `shouldBe` Nothing++ describe "toWord32 (fallible)" $+ it "rejects too large" $+ Natural.toWord32 (4294967296 :: Natural) `shouldBe` Nothing++ describe "toWord64 (fallible)" $+ it "rejects too large" $+ Natural.toWord64 (18446744073709551616 :: Natural) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "converts 0" $+ Natural.toWord 0 `shouldBe` Just (0 :: Word)++ describe "toInt8 (fallible)" $+ it "rejects too large" $+ Natural.toInt8 (128 :: Natural) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects too large" $+ Natural.toInt16 (32768 :: Natural) `shouldBe` Nothing++ describe "toInt32 (fallible)" $+ it "rejects too large" $+ Natural.toInt32 (2147483648 :: Natural) `shouldBe` Nothing++ describe "toInt64 (fallible)" $+ it "converts in-range" $+ Natural.toInt64 (100 :: Natural) `shouldBe` Just (100 :: Int64)++ describe "toInt (fallible)" $+ it "converts 0" $+ Natural.toInt 0 `shouldBe` Just (0 :: Int)++ describe "toInteger (infallible)" $+ it "converts large value" $+ Natural.toInteger (999999999999 :: Natural) `shouldBe` 999999999999++ describe "toFloat (range-checked)" $ do+ it "converts in-range" $+ Natural.toFloat (16777215 :: Natural) `shouldBe` Right 16777215.0+ it "rejects too large" $+ Natural.toFloat (16777216 :: Natural) `shouldBe` Left Overflow++ describe "toDouble (range-checked)" $ do+ it "converts in-range" $+ Natural.toDouble (9007199254740991 :: Natural) `shouldBe` Right 9007199254740991.0+ it "rejects too large" $+ Natural.toDouble (9007199254740992 :: Natural) `shouldBe` Left Overflow
+ test/Test/Convert/PropertySpec.hs view
@@ -0,0 +1,495 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Convert.PropertySpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck (prop)+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Int8 as Int8+import qualified Unwitch.Convert.Int16 as Int16+import qualified Unwitch.Convert.Int32 as Int32+import qualified Unwitch.Convert.Int64 as Int64+import qualified Unwitch.Convert.Int as Int+import qualified Unwitch.Convert.Word8 as Word8+import qualified Unwitch.Convert.Word16 as Word16+import qualified Unwitch.Convert.Word32 as Word32+import qualified Unwitch.Convert.Word64 as Word64+import qualified Unwitch.Convert.Natural as Natural+import qualified Unwitch.Convert.Integer as Integer+import qualified Unwitch.Convert.Word as Word+import qualified Unwitch.Convert.Char as Char+import qualified Unwitch.Convert.Float as Float+import qualified Unwitch.Convert.Double as Double+import Numeric.Natural (Natural)++spec :: Spec+spec = describe "Property tests" $ do++ describe "Round-trip: widen then narrow recovers original" $ do+ prop "Int8 -> Int16 -> Int8" $ \(x :: Int8) ->+ Int16.toInt8 (Int8.toInt16 x) `shouldBe` Just x++ prop "Int8 -> Int32 -> Int8" $ \(x :: Int8) ->+ Int32.toInt8 (Int8.toInt32 x) `shouldBe` Just x++ prop "Int8 -> Int64 -> Int8" $ \(x :: Int8) ->+ Int64.toInt8 (Int8.toInt64 x) `shouldBe` Just x++ prop "Int8 -> Integer -> Int8" $ \(x :: Int8) ->+ Integer.toInt8 (Int8.toInteger x) `shouldBe` Just x++ prop "Int16 -> Int32 -> Int16" $ \(x :: Int16) ->+ Int32.toInt16 (Int16.toInt32 x) `shouldBe` Just x++ prop "Int16 -> Int64 -> Int16" $ \(x :: Int16) ->+ Int64.toInt16 (Int16.toInt64 x) `shouldBe` Just x++ prop "Int32 -> Int64 -> Int32" $ \(x :: Int32) ->+ Int64.toInt32 (Int32.toInt64 x) `shouldBe` Just x++ prop "Word8 -> Word16 -> Word8" $ \(x :: Word8) ->+ Word16.toWord8 (Word8.toWord16 x) `shouldBe` Just x++ prop "Word8 -> Word32 -> Word8" $ \(x :: Word8) ->+ Word32.toWord8 (Word8.toWord32 x) `shouldBe` Just x++ prop "Word8 -> Word64 -> Word8" $ \(x :: Word8) ->+ Word64.toWord8 (Word8.toWord64 x) `shouldBe` Just x++ prop "Word16 -> Word32 -> Word16" $ \(x :: Word16) ->+ Word32.toWord16 (Word16.toWord32 x) `shouldBe` Just x++ prop "Word16 -> Word64 -> Word16" $ \(x :: Word16) ->+ Word64.toWord16 (Word16.toWord64 x) `shouldBe` Just x++ prop "Word32 -> Word64 -> Word32" $ \(x :: Word32) ->+ Word64.toWord32 (Word32.toWord64 x) `shouldBe` Just x++ prop "Word8 -> Int16 -> Word8" $ \(x :: Word8) ->+ Int16.toWord8 (Word8.toInt16 x) `shouldBe` Just x++ prop "Word8 -> Int32 -> Word8" $ \(x :: Word8) ->+ Int32.toWord8 (Word8.toInt32 x) `shouldBe` Just x++ -- Int via Int+ prop "Int8 -> Int -> Int8" $ \(x :: Int8) ->+ Int.toInt8 (Int8.toInt x) `shouldBe` Just x++ prop "Int16 -> Int -> Int16" $ \(x :: Int16) ->+ Int.toInt16 (Int16.toInt x) `shouldBe` Just x++ -- Int via Integer+ prop "Int16 -> Integer -> Int16" $ \(x :: Int16) ->+ Integer.toInt16 (Int16.toInteger x) `shouldBe` Just x++ prop "Int32 -> Integer -> Int32" $ \(x :: Int32) ->+ Integer.toInt32 (Int32.toInteger x) `shouldBe` Just x++ prop "Int64 -> Integer -> Int64" $ \(x :: Int64) ->+ Integer.toInt64 (Int64.toInteger x) `shouldBe` Just x++ prop "Int -> Int64 -> Int" $ \(x :: Int) ->+ Int64.toInt (Int.toInt64 x) `shouldBe` Just x++ prop "Int -> Integer -> Int" $ \(x :: Int) ->+ Integer.toInt (Int.toInteger x) `shouldBe` Just x++ -- Word via Word+ prop "Word8 -> Word -> Word8" $ \(x :: Word8) ->+ Word.toWord8 (Word8.toWord x) `shouldBe` Just x++ prop "Word16 -> Word -> Word16" $ \(x :: Word16) ->+ Word.toWord16 (Word16.toWord x) `shouldBe` Just x++ -- Word via Natural+ prop "Word8 -> Natural -> Word8" $ \(x :: Word8) ->+ Natural.toWord8 (Word8.toNatural x) `shouldBe` Just x++ prop "Word16 -> Natural -> Word16" $ \(x :: Word16) ->+ Natural.toWord16 (Word16.toNatural x) `shouldBe` Just x++ prop "Word32 -> Natural -> Word32" $ \(x :: Word32) ->+ Natural.toWord32 (Word32.toNatural x) `shouldBe` Just x++ prop "Word64 -> Natural -> Word64" $ \(x :: Word64) ->+ Natural.toWord64 (Word64.toNatural x) `shouldBe` Just x++ prop "Word -> Natural -> Word" $ \(x :: Word) ->+ Natural.toWord (Word.toNatural x) `shouldBe` Just x++ -- Word via Integer+ prop "Word64 -> Integer -> Word64" $ \(x :: Word64) ->+ Integer.toWord64 (Word64.toInteger x) `shouldBe` Just x++ prop "Word -> Word64 -> Word" $ \(x :: Word) ->+ Word64.toWord (Word.toWord64 x) `shouldBe` Just x++ prop "Word -> Integer -> Word" $ \(x :: Word) ->+ Integer.toWord (Word.toInteger x) `shouldBe` Just x++ -- Cross-sign: Word via signed+ prop "Word8 -> Int64 -> Word8" $ \(x :: Word8) ->+ Int64.toWord8 (Word8.toInt64 x) `shouldBe` Just x++ prop "Word8 -> Int -> Word8" $ \(x :: Word8) ->+ Int.toWord8 (Word8.toInt x) `shouldBe` Just x++ prop "Word8 -> Integer -> Word8" $ \(x :: Word8) ->+ Integer.toWord8 (Word8.toInteger x) `shouldBe` Just x++ prop "Word16 -> Int32 -> Word16" $ \(x :: Word16) ->+ Int32.toWord16 (Word16.toInt32 x) `shouldBe` Just x++ prop "Word16 -> Int64 -> Word16" $ \(x :: Word16) ->+ Int64.toWord16 (Word16.toInt64 x) `shouldBe` Just x++ prop "Word16 -> Int -> Word16" $ \(x :: Word16) ->+ Int.toWord16 (Word16.toInt x) `shouldBe` Just x++ prop "Word16 -> Integer -> Word16" $ \(x :: Word16) ->+ Integer.toWord16 (Word16.toInteger x) `shouldBe` Just x++ prop "Word32 -> Int64 -> Word32" $ \(x :: Word32) ->+ Int64.toWord32 (Word32.toInt64 x) `shouldBe` Just x++ prop "Word32 -> Integer -> Word32" $ \(x :: Word32) ->+ Integer.toWord32 (Word32.toInteger x) `shouldBe` Just x++ -- Natural via Integer (Either pattern, generated via Word64)+ prop "Natural -> Integer -> Natural" $ \(w :: Word64) ->+ let x = fromIntegral w :: Natural+ in Integer.toNatural (Natural.toInteger x) `shouldBe` Right x++ -- Char+ prop "Char -> Int -> Char" $ \(x :: Char) ->+ Char.fromInt (Char.toInt x) `shouldBe` Just x++ prop "Char -> Word -> Char" $ \(x :: Char) ->+ Char.fromWord (Char.toWord x) `shouldBe` Just x++ -- Via Float (Either ViaIntegerErrors pattern)+ prop "Int8 -> Float -> Int8" $ \(x :: Int8) ->+ Float.toInt8 (Int8.toFloat x) `shouldBe` Right x++ prop "Int16 -> Float -> Int16" $ \(x :: Int16) ->+ Float.toInt16 (Int16.toFloat x) `shouldBe` Right x++ prop "Word8 -> Float -> Word8" $ \(x :: Word8) ->+ Float.toWord8 (Word8.toFloat x) `shouldBe` Right x++ prop "Word16 -> Float -> Word16" $ \(x :: Word16) ->+ Float.toWord16 (Word16.toFloat x) `shouldBe` Right x++ -- Via Double (Either ViaIntegerErrors pattern)+ prop "Int8 -> Double -> Int8" $ \(x :: Int8) ->+ Double.toInt8 (Int8.toDouble x) `shouldBe` Right x++ prop "Int16 -> Double -> Int16" $ \(x :: Int16) ->+ Double.toInt16 (Int16.toDouble x) `shouldBe` Right x++ prop "Int32 -> Double -> Int32" $ \(x :: Int32) ->+ Double.toInt32 (Int32.toDouble x) `shouldBe` Right x++ prop "Word8 -> Double -> Word8" $ \(x :: Word8) ->+ Double.toWord8 (Word8.toDouble x) `shouldBe` Right x++ prop "Word16 -> Double -> Word16" $ \(x :: Word16) ->+ Double.toWord16 (Word16.toDouble x) `shouldBe` Right x++ prop "Word32 -> Double -> Word32" $ \(x :: Word32) ->+ Double.toWord32 (Word32.toDouble x) `shouldBe` Right x++ describe "Fallible narrowing success agrees with fromIntegral" $ do+ prop "Int16 -> Int8: success implies fromIntegral match" $ \(x :: Int16) ->+ case Int16.toInt8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Int32 -> Int16: success implies fromIntegral match" $ \(x :: Int32) ->+ case Int32.toInt16 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Int64 -> Int32: success implies fromIntegral match" $ \(x :: Int64) ->+ case Int64.toInt32 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word16 -> Word8: success implies fromIntegral match" $ \(x :: Word16) ->+ case Word16.toWord8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word32 -> Word16: success implies fromIntegral match" $ \(x :: Word32) ->+ case Word32.toWord16 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word64 -> Word32: success implies fromIntegral match" $ \(x :: Word64) ->+ case Word64.toWord32 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Integer -> Int8: success implies fromIntegral match" $ \(x :: Integer) ->+ case Integer.toInt8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Integer -> Word8: success implies fromIntegral match" $ \(x :: Integer) ->+ case Integer.toWord8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Int32 -> Int8: success implies fromIntegral match" $ \(x :: Int32) ->+ case Int32.toInt8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Int64 -> Int16: success implies fromIntegral match" $ \(x :: Int64) ->+ case Int64.toInt16 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Int64 -> Int8: success implies fromIntegral match" $ \(x :: Int64) ->+ case Int64.toInt8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word32 -> Word8: success implies fromIntegral match" $ \(x :: Word32) ->+ case Word32.toWord8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word64 -> Word16: success implies fromIntegral match" $ \(x :: Word64) ->+ case Word64.toWord16 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ prop "Word64 -> Word8: success implies fromIntegral match" $ \(x :: Word64) ->+ case Word64.toWord8 x of+ Just y -> fromIntegral y `shouldBe` x+ Nothing -> pure ()++ describe "toNatural rejects iff negative" $ do+ prop "Int8 -> Natural: Left iff x < 0" $ \(x :: Int8) ->+ isLeft (Int8.toNatural x) `shouldBe` (x < 0)++ prop "Int16 -> Natural: Left iff x < 0" $ \(x :: Int16) ->+ isLeft (Int16.toNatural x) `shouldBe` (x < 0)++ prop "Int32 -> Natural: Left iff x < 0" $ \(x :: Int32) ->+ isLeft (Int32.toNatural x) `shouldBe` (x < 0)++ prop "Int64 -> Natural: Left iff x < 0" $ \(x :: Int64) ->+ isLeft (Int64.toNatural x) `shouldBe` (x < 0)++ prop "Int -> Natural: Left iff x < 0" $ \(x :: Int) ->+ isLeft (Int.toNatural x) `shouldBe` (x < 0)++ prop "Integer -> Natural: Left iff x < 0" $ \(x :: Integer) ->+ isLeft (Integer.toNatural x) `shouldBe` (x < 0)++ describe "toNatural success preserves value" $ do+ prop "Int64 -> Natural -> Integer round-trips via Integer" $ \(x :: Int64) ->+ case Int64.toNatural x of+ Right n -> Natural.toInteger n `shouldBe` fromIntegral x+ Left _ -> pure ()++ prop "Word64 -> Natural -> Integer preserves value" $ \(x :: Word64) ->+ Natural.toInteger (Word64.toNatural x) `shouldBe` fromIntegral x++ describe "Float range check is exact at boundary" $ do+ prop "Int32 -> Float: succeeds iff abs value <= maxRepFloat" $ \(x :: Int32) ->+ let xi = fromIntegral x :: Integer+ in isRight (Int32.toFloat x) `shouldBe`+ (xi >= -maxRepFloat && xi <= maxRepFloat)++ prop "Word32 -> Float: succeeds iff value <= maxRepFloat" $ \(x :: Word32) ->+ isRight (Word32.toFloat x) `shouldBe`+ (fromIntegral x <= (maxRepFloat :: Integer))++ prop "Int64 -> Float: succeeds iff abs value <= maxRepFloat" $ \(x :: Int64) ->+ let xi = fromIntegral x :: Integer+ in isRight (Int64.toFloat x) `shouldBe`+ (xi >= -maxRepFloat && xi <= maxRepFloat)++ prop "Word64 -> Float: succeeds iff value <= maxRepFloat" $ \(x :: Word64) ->+ isRight (Word64.toFloat x) `shouldBe`+ (fromIntegral x <= (maxRepFloat :: Integer))++ prop "Integer -> Float: succeeds iff abs value <= maxRepFloat" $ \(x :: Integer) ->+ isRight (Integer.toFloat x) `shouldBe`+ (x >= -maxRepFloat && x <= maxRepFloat)++ describe "Double range check is exact at boundary" $ do+ prop "Int64 -> Double: succeeds iff abs value <= maxRepDouble" $ \(x :: Int64) ->+ let xi = fromIntegral x :: Integer+ in isRight (Int64.toDouble x) `shouldBe`+ (xi >= -maxRepDouble && xi <= maxRepDouble)++ prop "Word64 -> Double: succeeds iff value <= maxRepDouble" $ \(x :: Word64) ->+ isRight (Word64.toDouble x) `shouldBe`+ (fromIntegral x <= (maxRepDouble :: Integer))++ prop "Integer -> Double: succeeds iff abs value <= maxRepDouble" $ \(x :: Integer) ->+ isRight (Integer.toDouble x) `shouldBe`+ (x >= -maxRepDouble && x <= maxRepDouble)++ describe "Path independence: different widening paths agree" $ do+ prop "Int8: toInt32 == toInt16 >> Int16.toInt32" $ \(x :: Int8) ->+ Int8.toInt32 x `shouldBe` Int16.toInt32 (Int8.toInt16 x)++ prop "Int8: toInt64 == toInt32 >> Int32.toInt64" $ \(x :: Int8) ->+ Int8.toInt64 x `shouldBe` Int32.toInt64 (Int8.toInt32 x)++ prop "Int8: toInteger == toInt64 >> Int64.toInteger" $ \(x :: Int8) ->+ Int8.toInteger x `shouldBe` Int64.toInteger (Int8.toInt64 x)++ prop "Word8: toWord32 == toWord16 >> Word16.toWord32" $ \(x :: Word8) ->+ Word8.toWord32 x `shouldBe` Word16.toWord32 (Word8.toWord16 x)++ prop "Word8: toWord64 == toWord32 >> Word32.toWord64" $ \(x :: Word8) ->+ Word8.toWord64 x `shouldBe` Word32.toWord64 (Word8.toWord32 x)++ prop "Word8: toInteger == toWord64 >> Word64.toInteger" $ \(x :: Word8) ->+ Word8.toInteger x `shouldBe` Word64.toInteger (Word8.toWord64 x)++ describe "Infallible conversions preserve numeric value (via Integer)" $ do+ prop "Int8.toFloat preserves value" $ \(x :: Int8) ->+ Int8.toFloat x `shouldBe` fromIntegral x++ prop "Int8.toDouble preserves value" $ \(x :: Int8) ->+ Int8.toDouble x `shouldBe` fromIntegral x++ prop "Int16.toFloat preserves value" $ \(x :: Int16) ->+ Int16.toFloat x `shouldBe` fromIntegral x++ prop "Int16.toDouble preserves value" $ \(x :: Int16) ->+ Int16.toDouble x `shouldBe` fromIntegral x++ prop "Word8.toFloat preserves value" $ \(x :: Word8) ->+ Word8.toFloat x `shouldBe` fromIntegral x++ prop "Word8.toDouble preserves value" $ \(x :: Word8) ->+ Word8.toDouble x `shouldBe` fromIntegral x++ prop "Word16.toFloat preserves value" $ \(x :: Word16) ->+ Word16.toFloat x `shouldBe` fromIntegral x++ prop "Word16.toDouble preserves value" $ \(x :: Word16) ->+ Word16.toDouble x `shouldBe` fromIntegral x++ prop "Int32.toDouble preserves value" $ \(x :: Int32) ->+ Int32.toDouble x `shouldBe` fromIntegral x++ prop "Word32.toDouble preserves value" $ \(x :: Word32) ->+ Word32.toDouble x `shouldBe` fromIntegral x++ describe "Signed-to-unsigned: Nothing iff negative or too large" $ do+ prop "Int8 -> Word8: Nothing iff x < 0" $ \(x :: Int8) ->+ (Int8.toWord8 x == Nothing) `shouldBe` (x < 0)++ prop "Int16 -> Word16: Nothing iff x < 0" $ \(x :: Int16) ->+ (Int16.toWord16 x == Nothing) `shouldBe` (x < 0)++ prop "Int32 -> Word32: Nothing iff x < 0" $ \(x :: Int32) ->+ (Int32.toWord32 x == Nothing) `shouldBe` (x < 0)++ prop "Int64 -> Word64: Nothing iff x < 0" $ \(x :: Int64) ->+ (Int64.toWord64 x == Nothing) `shouldBe` (x < 0)++ describe "Cross-sign narrowing: exact failure condition" $ do+ prop "Int16 -> Word8: Nothing iff x < 0 || x > 255" $ \(x :: Int16) ->+ (Int16.toWord8 x == Nothing) `shouldBe` (x < 0 || x > 255)++ prop "Int32 -> Word16: Nothing iff x < 0 || x > 65535" $ \(x :: Int32) ->+ (Int32.toWord16 x == Nothing) `shouldBe` (x < 0 || x > 65535)++ prop "Int64 -> Word32: Nothing iff x < 0 || x > 4294967295" $ \(x :: Int64) ->+ (Int64.toWord32 x == Nothing) `shouldBe` (x < 0 || x > 4294967295)++ prop "Word16 -> Int8: Nothing iff x > 127" $ \(x :: Word16) ->+ (Word16.toInt8 x == Nothing) `shouldBe` (x > 127)++ prop "Word32 -> Int16: Nothing iff x > 32767" $ \(x :: Word32) ->+ (Word32.toInt16 x == Nothing) `shouldBe` (x > 32767)++ prop "Word64 -> Int32: Nothing iff x > 2147483647" $ \(x :: Word64) ->+ (Word64.toInt32 x == Nothing) `shouldBe` (x > 2147483647)++ describe "Float/Double to integer: exact success condition" $ do+ prop "Float -> Integer: succeeds iff finite and whole" $ \(x :: Float) ->+ isRight (Float.toInteger x) `shouldBe` isWholeFloat x++ prop "Float -> Int8: succeeds iff finite, whole, in [-128,127]" $ \(x :: Float) ->+ let i = truncate x :: Integer+ in isRight (Float.toInt8 x) `shouldBe`+ (isWholeFloat x && i >= -128 && i <= 127)++ prop "Float -> Word8: succeeds iff finite, whole, in [0,255]" $ \(x :: Float) ->+ let i = truncate x :: Integer+ in isRight (Float.toWord8 x) `shouldBe`+ (isWholeFloat x && i >= 0 && i <= 255)++ prop "Double -> Integer: succeeds iff finite and whole" $ \(x :: Double) ->+ isRight (Double.toInteger x) `shouldBe` isWholeDouble x++ prop "Double -> Int8: succeeds iff finite, whole, in [-128,127]" $ \(x :: Double) ->+ let i = truncate x :: Integer+ in isRight (Double.toInt8 x) `shouldBe`+ (isWholeDouble x && i >= -128 && i <= 127)++ prop "Double -> Word8: succeeds iff finite, whole, in [0,255]" $ \(x :: Double) ->+ let i = truncate x :: Integer+ in isRight (Double.toWord8 x) `shouldBe`+ (isWholeDouble x && i >= 0 && i <= 255)++ describe "Integer to Float/Double round-trip via toInteger" $ do+ prop "Int32 -> Float -> Integer: success round-trips" $ \(x :: Int32) ->+ case Int32.toFloat x of+ Right y -> Float.toInteger y `shouldBe` Right (fromIntegral x)+ Left _ -> pure ()++ prop "Int64 -> Double -> Integer: success round-trips" $ \(x :: Int64) ->+ case Int64.toDouble x of+ Right y -> Double.toInteger y `shouldBe` Right (fromIntegral x)+ Left _ -> pure ()++ prop "Word32 -> Float -> Integer: success round-trips" $ \(x :: Word32) ->+ case Word32.toFloat x of+ Right y -> Float.toInteger y `shouldBe` Right (fromIntegral x)+ Left _ -> pure ()++ prop "Word64 -> Double -> Integer: success round-trips" $ \(x :: Word64) ->+ case Word64.toDouble x of+ Right y -> Double.toInteger y `shouldBe` Right (fromIntegral x)+ Left _ -> pure ()++ describe "Float to Double round-trip" $ do+ prop "Float -> Double -> Float preserves value" $ \(x :: Float) ->+ let y = Double.toFloat (Float.toDouble x)+ in if isNaN x+ then y `shouldSatisfy` isNaN+ else y `shouldBe` x++maxRepFloat :: Num a => a+maxRepFloat = 16777215++maxRepDouble :: Num a => a+maxRepDouble = 9007199254740991++isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft (Right _) = False++isRight :: Either a b -> Bool+isRight (Right _) = True+isRight (Left _) = False++isWholeFloat :: Float -> Bool+isWholeFloat x = not (isNaN x) && not (isInfinite x)+ && x == fromIntegral (truncate x :: Integer)++isWholeDouble :: Double -> Bool+isWholeDouble x = not (isNaN x) && not (isInfinite x)+ && x == fromIntegral (truncate x :: Integer)
+ test/Test/Convert/RatioSpec.hs view
@@ -0,0 +1,35 @@+module Test.Convert.RatioSpec (spec) where++import Test.Hspec+import Data.Ratio ((%))+import qualified Unwitch.Convert.Ratio as Ratio++spec :: Spec+spec = describe "Unwitch.Convert.Ratio" $ do++ describe "fromIntegralToRatio" $ do+ it "wraps integer as ratio with denominator 1" $+ Ratio.fromIntegralToRatio (5 :: Integer) `shouldBe` (5 % 1)+ it "wraps zero" $+ Ratio.fromIntegralToRatio (0 :: Integer) `shouldBe` (0 % 1)+ it "wraps negative" $+ Ratio.fromIntegralToRatio ((-3) :: Integer) `shouldBe` ((-3) % 1)++ describe "unwrapIfDenominatorOne" $ do+ it "round-trips with fromIntegralToRatio" $+ Ratio.unwrapIfDenominatorOne (Ratio.fromIntegralToRatio (7 :: Integer))+ `shouldBe` Just 7+ it "rejects non-unit denominator" $+ Ratio.unwrapIfDenominatorOne (3 % 2 :: Rational) `shouldBe` Nothing++ describe "toFloat" $ do+ it "converts simple rational" $+ Ratio.toFloat (3 % 2) `shouldBe` (1.5 :: Float)+ it "converts integer rational" $+ Ratio.toFloat (4 % 1) `shouldBe` (4.0 :: Float)++ describe "toDouble" $ do+ it "converts simple rational" $+ Ratio.toDouble (3 % 2) `shouldBe` (1.5 :: Double)+ it "converts integer rational" $+ Ratio.toDouble (4 % 1) `shouldBe` (4.0 :: Double)
+ test/Test/Convert/ShortByteStringSpec.hs view
@@ -0,0 +1,22 @@+module Test.Convert.ShortByteStringSpec (spec) where++import Test.Hspec+import Data.ByteString.Short qualified as SBS+import Data.Word (Word8)+import qualified Unwitch.Convert.ShortByteString as ShortByteString+import qualified Unwitch.Convert.ByteString as ByteString++spec :: Spec+spec = describe "Unwitch.Convert.ShortByteString" $ do++ describe "toByteString round-trip" $+ it "round-trips with ByteString.toShortByteString" $+ let sbs = SBS.pack [5, 10, 15]+ in ByteString.toShortByteString (ShortByteString.toByteString sbs) `shouldBe` sbs++ describe "toWord8s / fromWord8s" $ do+ it "round-trips" $+ let ws = [100, 200, 255] :: [Word8]+ in ShortByteString.toWord8s (ShortByteString.fromWord8s ws) `shouldBe` ws+ it "empty list" $+ ShortByteString.toWord8s (ShortByteString.fromWord8s []) `shouldBe` ([] :: [Word8])
+ test/Test/Convert/TextSpec.hs view
@@ -0,0 +1,68 @@+module Test.Convert.TextSpec (spec) where++import Test.Hspec+import Data.Text qualified as T+import qualified Unwitch.Convert.Text as Text+import qualified Unwitch.Convert.LazyText as LazyText+import qualified Unwitch.Convert.ByteString as ByteString++spec :: Spec+spec = describe "Unwitch.Convert.Text" $ do++ describe "toLazyText / toText round-trip" $+ it "round-trips" $+ let t = T.pack "hello world"+ in LazyText.toText (Text.toLazyText t) `shouldBe` t++ describe "toString / fromString" $ do+ it "round-trips" $+ let s = "test string"+ in Text.toString (Text.fromString s) `shouldBe` s+ it "handles unicode" $+ let s = "\x00E9\x00FC\x00F1"+ in Text.toString (Text.fromString s) `shouldBe` s++ describe "toByteStringUtf8" $ do+ it "encodes ASCII" $+ Text.toByteStringUtf8 "hello" `shouldBe` "hello"+ it "round-trips with ByteString.toTextUtf8" $+ let t = T.pack "caf\x00E9"+ in ByteString.toTextUtf8 (Text.toByteStringUtf8 t) `shouldBe` Right t++ describe "toByteStringUtf16LE" $+ it "produces valid UTF-16LE output" $+ let t = T.pack "A"+ bs = Text.toByteStringUtf16LE t+ in ByteString.toTextUtf16LE bs `shouldBe` Right t++ describe "toByteStringUtf16BE" $+ it "produces valid UTF-16BE output" $+ let t = T.pack "B"+ bs = Text.toByteStringUtf16BE t+ in ByteString.toTextUtf16BE bs `shouldBe` Right t++ describe "toByteStringUtf32LE" $+ it "produces valid UTF-32LE output" $+ let t = T.pack "C"+ bs = Text.toByteStringUtf32LE t+ in ByteString.toTextUtf32LE bs `shouldBe` Right t++ describe "toByteStringUtf32BE" $+ it "produces valid UTF-32BE output" $+ let t = T.pack "D"+ bs = Text.toByteStringUtf32BE t+ in ByteString.toTextUtf32BE bs `shouldBe` Right t++ describe "toByteStringLatin1" $ do+ it "succeeds for ASCII text" $+ Text.toByteStringLatin1 "hello" `shouldSatisfy` \case+ Just _ -> True+ Nothing -> False+ it "succeeds for Latin1 range chars" $+ let t = T.pack "\x00E9\x00FC"+ in case Text.toByteStringLatin1 t of+ Just bs -> ByteString.toTextLatin1 bs `shouldBe` t+ Nothing -> expectationFailure "expected Just"+ it "fails for chars above 0xFF" $+ let t = T.pack "\x0100" -- Latin Extended-A+ in Text.toByteStringLatin1 t `shouldBe` Nothing
+ test/Test/Convert/UnboxedSpec.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Test.Convert.UnboxedSpec+ ( spec+ )+where++import Test.Hspec+import Test.Hspec.QuickCheck (prop)++import qualified Unwitch.Convert.Integer as Integer+import qualified Unwitch.Convert.Int as Int+import qualified Unwitch.Convert.Int8 as Int8+import qualified Unwitch.Convert.Int16 as Int16+import qualified Unwitch.Convert.Int32 as Int32+import qualified Unwitch.Convert.Int64 as Int64+import qualified Unwitch.Convert.Word as Word+import qualified Unwitch.Convert.Word8 as Word8+import qualified Unwitch.Convert.Word16 as Word16+import qualified Unwitch.Convert.Word32 as Word32+import qualified Unwitch.Convert.Word64 as Word64+import qualified Unwitch.Convert.Natural as Natural+import qualified Unwitch.Convert.Char as Char+import Data.Int+import Data.Word+import Numeric.Natural (Natural)++spec :: Spec+spec = describe "Unboxed sum variants" $ do++ describe "Integer" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Integer) ->+ case Integer.toInt8# x of+ (# y | #) -> Integer.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Integer.toInt8 x `shouldBe` Nothing++ prop "toDouble# agrees with toDouble" $ \(x :: Integer) ->+ case Integer.toDouble# x of+ (# e | #) -> Integer.toDouble x `shouldBe` Left e+ (# | y #) -> Integer.toDouble x `shouldBe` Right y++ describe "Int" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Int) ->+ case Int.toInt8# x of+ (# y | #) -> Int.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Int.toInt8 x `shouldBe` Nothing++ prop "toNatural# agrees with toNatural" $ \(x :: Int) ->+ case Int.toNatural# x of+ (# e | #) -> Int.toNatural x `shouldBe` Left e+ (# | y #) -> Int.toNatural x `shouldBe` Right y++ describe "Int8" $ do+ prop "toWord8# agrees with toWord8" $ \(x :: Int8) ->+ case Int8.toWord8# x of+ (# y | #) -> Int8.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Int8.toWord8 x `shouldBe` Nothing++ prop "toNatural# agrees with toNatural" $ \(x :: Int8) ->+ case Int8.toNatural# x of+ (# e | #) -> Int8.toNatural x `shouldBe` Left e+ (# | y #) -> Int8.toNatural x `shouldBe` Right y++ describe "Int16" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Int16) ->+ case Int16.toInt8# x of+ (# y | #) -> Int16.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Int16.toInt8 x `shouldBe` Nothing++ describe "Int32" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Int32) ->+ case Int32.toInt8# x of+ (# y | #) -> Int32.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Int32.toInt8 x `shouldBe` Nothing++ prop "toFloat# agrees with toFloat" $ \(x :: Int32) ->+ case Int32.toFloat# x of+ (# e | #) -> Int32.toFloat x `shouldBe` Left e+ (# | y #) -> Int32.toFloat x `shouldBe` Right y++ describe "Int64" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Int64) ->+ case Int64.toInt8# x of+ (# y | #) -> Int64.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Int64.toInt8 x `shouldBe` Nothing++ prop "toDouble# agrees with toDouble" $ \(x :: Int64) ->+ case Int64.toDouble# x of+ (# e | #) -> Int64.toDouble x `shouldBe` Left e+ (# | y #) -> Int64.toDouble x `shouldBe` Right y++ describe "Word" $ do+ prop "toWord8# agrees with toWord8" $ \(x :: Word) ->+ case Word.toWord8# x of+ (# y | #) -> Word.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Word.toWord8 x `shouldBe` Nothing++ prop "toFloat# agrees with toFloat" $ \(x :: Word) ->+ case Word.toFloat# x of+ (# e | #) -> Word.toFloat x `shouldBe` Left e+ (# | y #) -> Word.toFloat x `shouldBe` Right y++ describe "Word8" $ do+ prop "toInt8# agrees with toInt8" $ \(x :: Word8) ->+ case Word8.toInt8# x of+ (# y | #) -> Word8.toInt8 x `shouldBe` Just y+ (# | (# #) #) -> Word8.toInt8 x `shouldBe` Nothing++ describe "Word16" $ do+ prop "toWord8# agrees with toWord8" $ \(x :: Word16) ->+ case Word16.toWord8# x of+ (# y | #) -> Word16.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Word16.toWord8 x `shouldBe` Nothing++ describe "Word32" $ do+ prop "toWord8# agrees with toWord8" $ \(x :: Word32) ->+ case Word32.toWord8# x of+ (# y | #) -> Word32.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Word32.toWord8 x `shouldBe` Nothing++ prop "toFloat# agrees with toFloat" $ \(x :: Word32) ->+ case Word32.toFloat# x of+ (# e | #) -> Word32.toFloat x `shouldBe` Left e+ (# | y #) -> Word32.toFloat x `shouldBe` Right y++ describe "Word64" $ do+ prop "toWord8# agrees with toWord8" $ \(x :: Word64) ->+ case Word64.toWord8# x of+ (# y | #) -> Word64.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Word64.toWord8 x `shouldBe` Nothing++ prop "toFloat# agrees with toFloat" $ \(x :: Word64) ->+ case Word64.toFloat# x of+ (# e | #) -> Word64.toFloat x `shouldBe` Left e+ (# | y #) -> Word64.toFloat x `shouldBe` Right y++ describe "Natural" $ do+ prop "toWord8# agrees with toWord8" $ \(w :: Word) ->+ let x = fromIntegral w :: Natural+ in case Natural.toWord8# x of+ (# y | #) -> Natural.toWord8 x `shouldBe` Just y+ (# | (# #) #) -> Natural.toWord8 x `shouldBe` Nothing++ prop "toFloat# agrees with toFloat" $ \(w :: Word) ->+ let x = fromIntegral w :: Natural+ in case Natural.toFloat# x of+ (# e | #) -> Natural.toFloat x `shouldBe` Left e+ (# | y #) -> Natural.toFloat x `shouldBe` Right y++ describe "Char" $ do+ prop "fromInt# agrees with fromInt" $ \(x :: Int) ->+ case Char.fromInt# x of+ (# y | #) -> Char.fromInt x `shouldBe` Just y+ (# | (# #) #) -> Char.fromInt x `shouldBe` Nothing+
+ test/Test/Convert/Word16Spec.hs view
@@ -0,0 +1,49 @@+module Test.Convert.Word16Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import qualified Unwitch.Convert.Word16 as Word16++-- Property tests cover: Word16->Word32/64 round-trips,+-- Word16->Word8 narrowing, toFloat/toDouble preserve value.+-- Kept: toWord, toNatural, toInt8, toInt16, toInt32, toInt64, toInt,+-- toInteger (all have no direct property coverage).++spec :: Spec+spec = describe "Unwitch.Convert.Word16" $ do++ describe "toWord (infallible)" $+ it "widens maxBound" $+ Word16.toWord maxBound `shouldBe` 65535++ describe "toNatural (infallible)" $+ it "converts maxBound" $+ Word16.toNatural maxBound `shouldBe` (65535 :: Natural)++ describe "toInt8 (fallible)" $+ it "rejects too large" $+ Word16.toInt8 (128 :: Word16) `shouldBe` Nothing++ describe "toInt16 (fallible)" $ do+ it "rejects too large" $+ Word16.toInt16 (32768 :: Word16) `shouldBe` Nothing+ it "converts in-range" $+ Word16.toInt16 (32767 :: Word16) `shouldBe` Just (32767 :: Int16)++ describe "toInt32 (infallible)" $+ it "widens maxBound" $+ Word16.toInt32 maxBound `shouldBe` 65535++ describe "toInt64 (infallible)" $+ it "widens maxBound" $+ Word16.toInt64 maxBound `shouldBe` 65535++ describe "toInt (infallible)" $+ it "widens maxBound" $+ Word16.toInt maxBound `shouldBe` 65535++ describe "toInteger (infallible)" $+ it "widens maxBound" $+ Word16.toInteger maxBound `shouldBe` 65535
+ test/Test/Convert/Word32Spec.hs view
@@ -0,0 +1,54 @@+module Test.Convert.Word32Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import qualified Unwitch.Convert.Word32 as Word32++-- Property tests cover: Word32->Word64 round-trip,+-- Word32->Word16 narrowing, toFloat range check,+-- toDouble preserve value.+-- Kept: toWord8 (no property), toWord, toNatural, toInt8, toInt16,+-- toInt32, toInt64, toInt, toInteger.++spec :: Spec+spec = describe "Unwitch.Convert.Word32" $ do++ describe "toWord8 (fallible)" $+ it "rejects too large" $+ Word32.toWord8 (256 :: Word32) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "converts 0" $+ Word32.toWord 0 `shouldBe` Just (0 :: Word)++ describe "toNatural (infallible)" $+ it "converts maxBound" $+ Word32.toNatural maxBound `shouldBe` (4294967295 :: Natural)++ describe "toInt8 (fallible)" $+ it "rejects too large" $+ Word32.toInt8 (200 :: Word32) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects too large" $+ Word32.toInt16 (40000 :: Word32) `shouldBe` Nothing++ describe "toInt32 (fallible)" $ do+ it "rejects too large" $+ Word32.toInt32 (2147483648 :: Word32) `shouldBe` Nothing+ it "converts at boundary" $+ Word32.toInt32 (2147483647 :: Word32) `shouldBe` Just (2147483647 :: Int32)++ describe "toInt64 (infallible)" $+ it "widens maxBound" $+ Word32.toInt64 maxBound `shouldBe` 4294967295++ describe "toInt (fallible)" $+ it "converts 0" $+ Word32.toInt 0 `shouldBe` Just (0 :: Int)++ describe "toInteger (infallible)" $+ it "converts maxBound" $+ Word32.toInteger maxBound `shouldBe` 4294967295
+ test/Test/Convert/Word64Spec.hs view
@@ -0,0 +1,53 @@+module Test.Convert.Word64Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import qualified Unwitch.Convert.Word64 as Word64++-- Property tests cover: Word64->Word32 narrowing,+-- Word64->Natural->Integer round-trip,+-- toFloat range check, toDouble range check.+-- Kept: toWord8, toWord16, toWord, toInt8, toInt16, toInt32,+-- toInt64, toInt, toInteger (all have no direct property coverage).++spec :: Spec+spec = describe "Unwitch.Convert.Word64" $ do++ describe "toWord8 (fallible)" $+ it "rejects too large" $+ Word64.toWord8 (256 :: Word64) `shouldBe` Nothing++ describe "toWord16 (fallible)" $+ it "rejects too large" $+ Word64.toWord16 (65536 :: Word64) `shouldBe` Nothing++ describe "toWord (fallible)" $+ it "converts 0" $+ Word64.toWord 0 `shouldBe` Just (0 :: Word)++ describe "toInt8 (fallible)" $+ it "rejects too large" $+ Word64.toInt8 (200 :: Word64) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects too large" $+ Word64.toInt16 (40000 :: Word64) `shouldBe` Nothing++ describe "toInt32 (fallible)" $+ it "rejects too large" $+ Word64.toInt32 (3000000000 :: Word64) `shouldBe` Nothing++ describe "toInt64 (fallible)" $ do+ it "rejects too large" $+ Word64.toInt64 (maxBound :: Word64) `shouldBe` Nothing+ it "converts at boundary" $+ Word64.toInt64 (9223372036854775807 :: Word64) `shouldBe` Just (9223372036854775807 :: Int64)++ describe "toInt (fallible)" $+ it "converts 0" $+ Word64.toInt 0 `shouldBe` Just (0 :: Int)++ describe "toInteger (infallible)" $+ it "converts maxBound" $+ Word64.toInteger maxBound `shouldBe` 18446744073709551615
+ test/Test/Convert/Word8Spec.hs view
@@ -0,0 +1,38 @@+module Test.Convert.Word8Spec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Numeric.Natural (Natural)+import qualified Unwitch.Convert.Word8 as Word8++-- Property tests cover: Word8->Word16/32/64 round-trips,+-- Word8->Int16/32 round-trips, Word8->Integer path independence,+-- toFloat/toDouble preserve value.+-- Kept: toWord (no property), toNatural (no property),+-- toInt8 (no property), toInt64 (no property), toInt (no property).++spec :: Spec+spec = describe "Unwitch.Convert.Word8" $ do++ describe "toWord (infallible)" $+ it "widens maxBound" $+ Word8.toWord maxBound `shouldBe` 255++ describe "toNatural (infallible)" $+ it "converts maxBound" $+ Word8.toNatural maxBound `shouldBe` (255 :: Natural)++ describe "toInt8 (fallible)" $ do+ it "rejects too large" $+ Word8.toInt8 (128 :: Word8) `shouldBe` Nothing+ it "converts in-range" $+ Word8.toInt8 (127 :: Word8) `shouldBe` Just (127 :: Int8)++ describe "toInt64 (infallible)" $+ it "widens maxBound" $+ Word8.toInt64 maxBound `shouldBe` 255++ describe "toInt (infallible)" $+ it "widens maxBound" $+ Word8.toInt maxBound `shouldBe` 255
+ test/Test/Convert/WordSpec.hs view
@@ -0,0 +1,66 @@+module Test.Convert.WordSpec (spec) where++import Test.Hspec+import Data.Int+import Data.Word+import Unwitch.Errors+import qualified Unwitch.Convert.Word as Word++spec :: Spec+spec = describe "Unwitch.Convert.Word" $ do++ describe "toWord8 (fallible)" $+ it "rejects too large" $+ Word.toWord8 (256 :: Word) `shouldBe` Nothing++ describe "toWord16 (fallible)" $+ it "rejects too large" $+ Word.toWord16 (65536 :: Word) `shouldBe` Nothing++ describe "toWord32 (fallible)" $+ it "converts in-range" $+ Word.toWord32 (100 :: Word) `shouldBe` Just (100 :: Word32)++ describe "toWord64 (infallible)" $+ it "widens maxBound" $+ Word.toWord64 maxBound `shouldBe` fromIntegral (maxBound :: Word)++ describe "toNatural (infallible)" $+ it "converts maxBound" $+ Word.toNatural maxBound `shouldBe` fromIntegral (maxBound :: Word)++ describe "toInt8 (fallible)" $+ it "rejects too large" $+ Word.toInt8 (200 :: Word) `shouldBe` Nothing++ describe "toInt16 (fallible)" $+ it "rejects too large" $+ Word.toInt16 (40000 :: Word) `shouldBe` Nothing++ describe "toInt32 (fallible)" $+ it "rejects too large" $+ Word.toInt32 (3000000000 :: Word) `shouldBe` Nothing++ describe "toInt64 (fallible)" $+ it "converts in-range" $+ Word.toInt64 (100 :: Word) `shouldBe` Just (100 :: Int64)++ describe "toInt (fallible)" $ do+ it "rejects maxBound" $+ Word.toInt (maxBound :: Word) `shouldBe` Nothing+ it "converts 0" $+ Word.toInt 0 `shouldBe` Just (0 :: Int)++ describe "toInteger (infallible)" $+ it "converts maxBound" $+ Word.toInteger maxBound `shouldBe` fromIntegral (maxBound :: Word)++ describe "toFloat (range-checked)" $ do+ it "converts in-range" $+ Word.toFloat (16777215 :: Word) `shouldBe` Right 16777215.0+ it "rejects too large" $+ Word.toFloat (16777216 :: Word) `shouldBe` Left Overflow++ describe "toDouble (range-checked)" $+ it "converts 0" $+ Word.toDouble 0 `shouldBe` Right (0.0 :: Double)
+ unwitch.cabal view
@@ -0,0 +1,151 @@+cabal-version: 3.0++name: unwitch+version: 1.0.0+homepage: https://github.com/jappeace/haskell-unwitch-project#readme+synopsis: converts between primitives+description: + Removes the magic from witch.+ This provides safe conversions like witch does.+ But it [doesn't](https://jappie.me/death-to-type-classes.html) use [type classes](https://www.haskellforall.com/2012/05/scrap-your-type-classes.html)+ or exceptions.+ This has a couple of advantages:++ 1. No need to use type application for function selection.+ 2. Functions get names that describe what they do.+ This allows ctags to work as well.+ 3. No trouble with orphans.+ 4. Custom errors instead of the prelude based ones allow client+ code to recover with typesafety even on partial conversions.+bug-reports: https://github.com/jappeace/haskell-unwitch-project/issues+author: Jappie Klooster+maintainer: jappieklooster@hotmail.com+copyright: 2020 Jappie Klooster+license: MIT+category: Data+license-file: LICENSE+build-type: Simple+extra-source-files:+ Readme.md+ LICENSE+extra-doc-files:+ Changelog.md++source-repository head+ type: git+ location: https://github.com/jappeace/haskell-unwitch-project++common common-options+ default-extensions:+ EmptyCase+ FlexibleContexts+ FlexibleInstances+ InstanceSigs+ MultiParamTypeClasses+ LambdaCase+ MultiWayIf+ NamedFieldPuns+ TupleSections+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveLift+ DeriveTraversable+ DerivingStrategies+ GeneralizedNewtypeDeriving+ StandaloneDeriving+ OverloadedStrings+ TypeApplications+ NumericUnderscores+ ImportQualifiedPost+ UnboxedSums+ UnboxedTuples+ MagicHash++ ghc-options:+ -Wall -Wincomplete-uni-patterns+ -Wincomplete-record-updates -Widentities -Wredundant-constraints+ -Wcpp-undef -fwarn-tabs -Wpartial-fields+ -fdefer-diagnostics -Wunused-packages+ -fenable-th-splice-warnings+ -fno-omit-yields++ build-depends:+ base >=4.18.0.0 && <4.23,+ ghc-bignum < 2,+ bytestring >= 0.10 && < 0.13,+ text >= 1.2 && < 2.2++ default-language: Haskell2010++library+ import: common-options+ exposed-modules:+ Unwitch.Convert.ByteString+ Unwitch.Convert.Char+ Unwitch.Convert.Complex+ Unwitch.Convert.Double+ Unwitch.Convert.Fixed+ Unwitch.Convert.Float+ Unwitch.Convert.Int+ Unwitch.Convert.Int8+ Unwitch.Convert.Int16+ Unwitch.Convert.Int32+ Unwitch.Convert.Int64+ Unwitch.Convert.Integer+ Unwitch.Convert.LazyByteString+ Unwitch.Convert.LazyText+ Unwitch.Convert.Natural+ Unwitch.Convert.Ratio+ Unwitch.Convert.ShortByteString+ Unwitch.Convert.Text+ Unwitch.Convert.Word+ Unwitch.Convert.Word8+ Unwitch.Convert.Word16+ Unwitch.Convert.Word32+ Unwitch.Convert.Word64+ Unwitch.Errors+ other-modules:+ Unwitch+ Unwitch.Constant+ Unwitch.TryEvaluate+ hs-source-dirs:+ src++test-suite unit+ import: common-options+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ ghc-options: -threaded -rtsopts "-with-rtsopts=-N" -Wno-unused-packages+ other-modules:+ Test.Convert.ByteStringSpec+ Test.Convert.Int8Spec+ Test.Convert.Int16Spec+ Test.Convert.Int32Spec+ Test.Convert.Int64Spec+ Test.Convert.IntSpec+ Test.Convert.LazyByteStringSpec+ Test.Convert.LazyTextSpec+ Test.Convert.Word8Spec+ Test.Convert.Word16Spec+ Test.Convert.Word32Spec+ Test.Convert.Word64Spec+ Test.Convert.WordSpec+ Test.Convert.NaturalSpec+ Test.Convert.IntegerSpec+ Test.Convert.FloatSpec+ Test.Convert.DoubleSpec+ Test.Convert.PropertySpec+ Test.Convert.RatioSpec+ Test.Convert.FixedSpec+ Test.Convert.ComplexSpec+ Test.Convert.CharSpec+ Test.Convert.ShortByteStringSpec+ Test.Convert.TextSpec+ Test.Convert.UnboxedSpec+ hs-source-dirs:+ test+ build-depends:+ hspec,+ hspec-core,+ unwitch