-- | Class for reading bounded values. module Text.Read.Bounded ( BoundedRead(..), ReadBounded(..), readBoundedInteger, ) where import Data.Int import Data.Word import Text.Read (readMaybe) -- | Information about a bounded read. data BoundedRead a = NoRead -- ^ The read failed. | ExactRead a -- ^ The value was successfully read exactly, and did not have to be clamped to a narrower representation. | ClampedRead a -- ^ The value was successfully read, but had to be clamped to a narrower representation because its value was too wide. deriving (Show, Read, Eq, Ord) fromMaybe :: Maybe a -> BoundedRead a fromMaybe = maybe NoRead ExactRead data Clamped a = Exact a | Clamped a clamp :: (Bounded a, Integral a) => Integer -> Clamped a clamp x = if x < minInteger then Clamped minNum else if x > maxInteger then Clamped maxNum else Exact $ fromInteger x where minNum = minBound maxNum = maxBound minInteger = toInteger minNum maxInteger = toInteger maxNum -- | Reads a clamped value for any integer type with the given class constraints. -- Useful for implementing a 'ReadBounded' instance or avoiding one. readBoundedInteger :: (Bounded a, Read a, Integral a) => String -> BoundedRead a readBoundedInteger str = case readMaybe str of Nothing -> NoRead Just x -> case clamp x of Exact y -> ExactRead y Clamped y -> ClampedRead y -- | Much like the 'Prelude.Read' class, but will return (possibly) clamped values. -- -- Typical instances of this class will clamp against 'Prelude.Bounded.minBound' and 'Prelude.Bounded.maxBound' -- -- This class is designed to avoid inconsistency problems such as the following: -- -- >>> read "321" :: Word8 -- 65 -- >>> read "4321" :: Word8 -- 225 -- >>> read "-4" :: Word8 -- 252 -- -- Using this class, the results are predictable and precise: -- -- >>> readBounded "321" :: BoundedRead Word8 -- ClampedRead 255 -- >>> readBounded "4321" :: BoundedRead Word8 -- ClampedRead 255 -- >>> readBounded "-4" :: BoundedRead Word8 -- ClampedRead 0 -- >>> readBounded "255" :: BoundedRead Word8 -- ExactRead 255 -- >>> readBounded "6" :: BoundedRead Word8 -- ExactRead 6 -- >>> readBounded "xxx" :: BoundedRead Word8 -- NoRead class ReadBounded a where readBounded :: String -> BoundedRead a instance ReadBounded Integer where readBounded = fromMaybe . readMaybe instance ReadBounded Int where readBounded = readBoundedInteger instance ReadBounded Int8 where readBounded = readBoundedInteger instance ReadBounded Int16 where readBounded = readBoundedInteger instance ReadBounded Int32 where readBounded = readBoundedInteger instance ReadBounded Int64 where readBounded = readBoundedInteger instance ReadBounded Word where readBounded = readBoundedInteger instance ReadBounded Word8 where readBounded = readBoundedInteger instance ReadBounded Word16 where readBounded = readBoundedInteger instance ReadBounded Word32 where readBounded = readBoundedInteger instance ReadBounded Word64 where readBounded = readBoundedInteger