{-# OPTIONS_HADDOCK not-home #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveDataTypeable #-} -- | Intervals describe terminal symbols. Ordinarily you will not -- need to use this module, as "Pinchot" re-exports the things you -- usually need. module Pinchot.Intervals where import qualified Control.Lens as Lens import Control.Monad (join) import Data.Data (Data) import Data.Monoid ((<>)) import Data.Ord (comparing) import Data.Sequence (Seq, ViewL(EmptyL, (:<)), viewl, (<|)) import qualified Data.Sequence as Seq import Language.Haskell.TH import Language.Haskell.TH.Syntax import Text.Show.Pretty (PrettyVal) import qualified Text.Show.Pretty as Pretty import Pinchot.Pretty -- | Groups of terminals. Create an 'Intervals' using 'include', -- 'exclude', 'solo' and 'pariah'. Combine 'Intervals' using -- 'mappend', which will combine both the included and excluded -- terminal symbols from each operand. data Intervals a = Intervals { _included :: Seq (a, a) -- ^ Each pair @(a, b)@ is an inclusive range of terminal symbols, -- in order. For instance, @('a', 'c')@ includes the characters -- @'a'@, @'b'@, and @'c'@. The 'included' sequence contains all -- terminals that are included in the 'Intervals', except for those -- that are 'excluded'. , _excluded :: Seq (a, a) -- ^ Each symbol in 'excluded' is not in the 'Intervals', even if -- the symbol is 'included'. } deriving (Eq, Ord, Show, Data) Lens.makeLenses ''Intervals instance PrettyVal a => PrettyVal (Intervals a) where prettyVal (Intervals inc exc) = Pretty.Rec "Pinchot.Intervals.Intervals" [ ("_included", prettySeq Pretty.prettyVal inc) , ("_excluded", prettySeq Pretty.prettyVal exc) ] instance Functor Intervals where fmap f (Intervals a b) = Intervals (fmap g a) (fmap g b) where g (x, y) = (f x, f y) instance Monoid (Intervals a) where mempty = Intervals mempty mempty (Intervals x1 y1) `mappend` (Intervals x2 y2) = Intervals (x1 <> x2) (y1 <> y2) -- | Include a range of symbols in the 'Intervals'. For instance, to -- include the characters @'a'@, @'b'@, and @'c'@, use @include 'a' -- 'c'@. Example: 'Pinchot.Examples.Postal.rLetter'. include :: a -> a -> Intervals a include l h = Intervals [(l, h)] [] -- | Exclude a range of symbols in the 'Intervals'. Each symbol that -- is 'exclude'd is not included in the 'Intervals', even if it is -- also 'include'd. exclude :: a -> a -> Intervals a exclude l h = Intervals [] [(l, h)] -- | Include a single symbol. Example: -- 'Pinchot.Examples.Postal.rNorth'. solo :: a -> Intervals a solo x = Intervals [(x, x)] [] -- | Exclude a single symbol. pariah :: a -> Intervals a pariah x = Intervals [] [(x, x)] -- | Left endpoint. endLeft :: Ord a => (a, a) -> a endLeft (a, b) = min a b -- | Right endpoint. endRight :: Ord a => (a, a) -> a endRight (a, b) = max a b -- | Is this symbol included in the interval? inInterval :: Ord a => a -> (a, a) -> Bool inInterval x i = x >= endLeft i && x <= endRight i -- | Enumerate all members of an interval. members :: (Ord a, Enum a) => (a, a) -> Seq a members i = Seq.fromList [endLeft i .. endRight i] -- | Sort a sequence of intervals. sortIntervalSeq :: Ord a => Seq (a, a) -> Seq (a, a) sortIntervalSeq = Seq.sortBy (comparing endLeft <> comparing endRight) -- | Arrange an interval so the lower bound is first in the pair. standardizeInterval :: Ord a => (a, a) -> (a, a) standardizeInterval (a, b) = (min a b, max a b) -- | Sorts the intervals using 'sortIntervalSeq' and presents them in a -- regular order using 'flatten'. The function @standardizeIntervalSeq a@ has -- the following properties, where @b@ is the result: -- -- @ -- 'uniqueMembers' a == 'uniqueMembers' b -- -- let go [] = True -- go (_:[]) = True -- go (x:y:xs) -- | 'endRight' x < 'endLeft' y -- && 'endRight' x < pred ('endLeft' x) -- = go (y:xs) -- | otherwise = False -- in go b -- @ -- -- The second property means that adjacent intervals in the list must -- be separated by at least one point on the number line. standardizeIntervalSeq :: (Ord a, Enum a) => Seq (a, a) -> Seq (a, a) standardizeIntervalSeq = flattenIntervalSeq . sortIntervalSeq -- | Presents the intervals in a standard order, as described in -- 'standardizeIntervalSeq'. If the input has already been sorted with -- 'sortIntervalSeq', the same properties for 'standardizeIntervalSeq' hold for -- this function. Otherwise, its properties are undefined. flattenIntervalSeq :: (Ord a, Enum a) => Seq (a, a) -> Seq (a, a) flattenIntervalSeq = fmap standardizeInterval . go Nothing where go mayCurr sq = case (mayCurr, viewl sq) of (Nothing, EmptyL) -> [] (Just i, EmptyL) -> [i] (Nothing, x :< xs) -> go (Just x) xs (Just curr, x :< xs) | endRight curr < endLeft x && endRight curr < pred (endLeft x) -> curr <| go (Just x) xs | otherwise -> go (Just (endLeft curr, max (endRight curr) (endRight x))) xs {- | Removes excluded members from a list of 'Interval'. The following properties hold: @ removeProperties :: (Ord a, Enum a) => Seq (a, a) -> Seq (a, a) -> [Bool] removeProperties inc exc = let r = removeExcludes inc exc allExcluded = concatMap members exc allIncluded = concatMap members inc allResults = concatMap members r in [ -- intervals remain in original order allResults == filter (not . (\`elem\` allExcluded)) allIncluded -- Every resulting member was a member of the original include list , all (\`elem\` allIncluded) allResults -- No resulting member is in the exclude list , all (not . (\`elem\` allExcluded)) allResults -- Every included member that is not in the exclude list is -- in the result , all (\x -> x \`elem\` allExcluded || x \`elem\` allResults) allIncluded ] @ -} removeExcludes :: (Ord a, Enum a) => Seq (a, a) -- ^ Included intervals (not necessarily sorted) -> Seq (a, a) -- ^ Excluded intervals (not necessarily sorted) -> Seq (a, a) removeExcludes inc = foldr remover inc remover :: (Ord a, Enum a) => (a, a) -- ^ Remove this interval -> Seq (a, a) -- ^ From this sequence of intervals -> Seq (a, a) remover ivl = join . fmap squash . fmap (removeInterval ivl) where squash (Nothing, Nothing) = Seq.empty squash (Just x, Nothing) = Seq.singleton x squash (Nothing, Just x) = Seq.singleton x squash (Just x, Just y) = x <| y <| Seq.empty -- | Removes a single interval from a single other interval. Returns -- a sequence of intervals, which always removeInterval :: (Ord a, Enum a) => (a, a) -- ^ Remove this interval -> (a, a) -- ^ From this interval -> (Maybe (a, a), Maybe (a, a)) removeInterval ivl oldIvl = (onLeft, onRight) where onLeft | endLeft ivl > endLeft oldIvl = Just ( endLeft oldIvl , min (pred (endLeft ivl)) (endRight oldIvl)) | otherwise = Nothing onRight | endRight ivl < endRight oldIvl = Just ( max (succ (endRight ivl)) (endLeft oldIvl) , endRight oldIvl) | otherwise = Nothing -- | Runs 'standardizeIntervalSeq' on the 'included' and 'excluded' -- intervals. standardizeIntervals :: (Ord a, Enum a) => Intervals a -> Intervals a standardizeIntervals (Intervals i e) = Intervals (standardizeIntervalSeq i) (standardizeIntervalSeq e) -- | Sorts the intervals using 'standardizeIntervalSeq', and then removes the -- excludes with 'removeExcludes'. splitIntervals :: (Ord a, Enum a) => Intervals a -> Seq (a, a) splitIntervals (Intervals is es) = removeExcludes (standardizeIntervalSeq is) es -- | 'True' if the given element is a member of the 'Intervals'. inIntervals :: (Enum a, Ord a) => Intervals a -> a -> Bool inIntervals ivls a = any (inInterval a) . splitIntervals $ ivls liftSeq :: Lift a => Seq a -> ExpQ liftSeq sq = case viewl sq of EmptyL -> varE 'Seq.empty x :< xs -> uInfixE (lift x) (varE '(<|)) (liftSeq xs) instance Lift a => Lift (Intervals a) where lift (Intervals inc exc) = [| Intervals $sqInc $sqExc |] where sqInc = liftSeq inc sqExc = liftSeq exc