module Data.Loc.Pos ( Pos, Line, Column, ToNat (..), -- * Show and Read posShowsPrec, posReadPrec, ) where import Data.Loc.Internal.Prelude import Prelude (Num (..)) import Data.Data (Data) {- | 'Pos' stands for /positive integer/. You can also think of it as /position/, because we use it to represent line and column numbers ('Line' and 'Column'). 'Pos' has instances of several of the standard numeric typeclasses, although many of the operations throw 'Underflow' when non-positive values result. 'Pos' does /not/ have an 'Integral' instance, because there is no sensible way to implement 'quotRem'. -} newtype Pos = Pos Natural deriving (Data, Eq, Ord) instance ToNat Pos where toNat (Pos n) = n instance Show Pos where showsPrec = posShowsPrec instance Read Pos where readPrec = posReadPrec {- | >>> fromInteger 3 :: Pos 3 >>> fromInteger 0 :: Pos *** Exception: arithmetic underflow >>> 2 + 3 :: Pos 5 >>> 3 - 2 :: Pos 1 >>> 3 - 3 :: Pos *** Exception: arithmetic underflow >>> 2 * 3 :: Pos 6 >>> negate 3 :: Pos *** Exception: arithmetic underflow -} instance Num Pos where fromInteger = Pos . checkForUnderflow . fromInteger Pos x + Pos y = Pos (x + y) Pos x - Pos y = Pos (checkForUnderflow (x - y)) Pos x * Pos y = Pos (x * y) abs = id signum _ = Pos 1 negate _ = throw Underflow instance Real Pos where toRational (Pos n) = toRational n {- | >>> toEnum 3 :: Pos 3 >>> toEnum 0 :: Pos *** Exception: arithmetic underflow >>> fromEnum (3 :: Pos) 3 -} instance Enum Pos where toEnum = Pos . checkForUnderflow . toEnum fromEnum (Pos n) = fromEnum n checkForUnderflow :: Natural -> Natural checkForUnderflow n = if n == 0 then throw Underflow else n {- | >>> posShowsPrec minPrec 1 "" "1" >>> posShowsPrec minPrec 42 "" "42" -} posShowsPrec :: Int -> Pos -> ShowS posShowsPrec i (Pos n) = showsPrec i n {- | >>> readPrec_to_S posReadPrec minPrec "1" [(1,"")] >>> readPrec_to_S posReadPrec minPrec "42" [(42,"")] >>> readPrec_to_S posReadPrec minPrec "0" [] >>> readPrec_to_S posReadPrec minPrec "-1" [] -} posReadPrec :: ReadPrec Pos posReadPrec = Pos <$> mfilter (/= 0) readPrec -------------------------------------------------------------------------------- -- ToNat -------------------------------------------------------------------------------- {- | Types that can be converted to 'Natural'. This class mostly exists so that 'toNat' can be used in situations that would normally call for 'toInteger' (which we cannot use because 'Pos' does not have an instance of 'Integral'). -} class ToNat a where toNat :: a -> Natural -------------------------------------------------------------------------------- -- Line -------------------------------------------------------------------------------- newtype Line = Line Pos deriving (Data, Eq, Ord, Num, Real, Enum, ToNat) instance Show Line where showsPrec i (Line pos) = showsPrec i pos instance Read Line where readPrec = Line <$> readPrec -------------------------------------------------------------------------------- -- Column -------------------------------------------------------------------------------- newtype Column = Column Pos deriving (Data, Eq, Ord, Num, Real, Enum, ToNat) instance Show Column where showsPrec i (Column pos) = showsPrec i pos instance Read Column where readPrec = Column <$> readPrec