haskell-formatter-2.0.0: src/library/Language/Haskell/Formatter/Toolkit/Splitter.hs
{-|
Description : Splitting lists on sublists
-}
module Language.Haskell.Formatter.Toolkit.Splitter (separate) where
import qualified Control.Applicative as Applicative
import qualified Data.List as List
import qualified Data.Monoid as Monoid
import qualified Language.Haskell.Formatter.Toolkit.ListTool as ListTool
import qualified Language.Haskell.Formatter.Toolkit.Visit as Visit
{-| Strategy to split a list on sublists. -}
data Splitter a = Splitter{delimiterPolicy :: DelimiterPolicy,
delimiterQueue :: [[a]]}
deriving (Eq, Ord, Show)
{-| What to do with the delimiters? -}
data DelimiterPolicy = Drop
| Separate
| MergeLeft
| MergeRight
deriving (Eq, Ord, Show)
{-| @separate d l@ splits @l@ on the delimiters @d@, which are matched in the
given order. The delimiters are not kept.
>>> separate ["pineapple", "pine"] "0pineapple1"
["0","1"]
>>> separate ["pine", "pineapple"] "0pineapple1"
["0","apple1"] -}
separate :: Eq a => [[a]] -> [a] -> [[a]]
separate = split . createSplitter Drop
where createSplitter rawDelimiterPolicy rawDelimiterQueue
= Splitter{delimiterPolicy = rawDelimiterPolicy,
delimiterQueue = rawDelimiterQueue}
{-| @split s l@ splits @l@ according to the strategy @s@. -}
split :: Eq a => Splitter a -> [a] -> [[a]]
split splitter list
= case delimiterPolicy splitter of
Drop -> ListTool.takeEvery period parts
Separate -> parts
MergeLeft -> ListTool.concatenateRuns period parts
MergeRight -> ListTool.concatenateShiftedRuns period shift parts
where shift = 1
where period = 2
parts = rawSplit (delimiterQueue splitter) list
{-| @rawSplit s l@ splits @l@ on the sublists @s@, keeping the separators.
prop> odd . length $ separate ["apple", "pine"] l -}
rawSplit :: Eq a => [[a]] -> [a] -> [[a]]
rawSplit delimiters = move [] []
where move parts left [] = Monoid.mappend parts [left]
move parts left right@(first : rest)
= case stripFirstPrefix delimiters right of
Nothing -> move parts (Monoid.mappend left [first]) rest
Just (delimiter, suffix) -> move
(Monoid.mappend parts
[left, delimiter])
[]
suffix
{-| @stripFirstPrefix p l@ returns the first element of @p@ which is a prefix of
@l@ and the rest of @l@. It returns 'Nothing' if there is no such element.
>>> stripFirstPrefix ["\LF", "\CR\LF", "\CR"] "\CR\LFpine"
Just ("\r\n","pine")
>>> stripFirstPrefix ["apple"] "pineapple"
Nothing -}
stripFirstPrefix :: Eq a => [[a]] -> [a] -> Maybe ([a], [a])
stripFirstPrefix prefixes list = Visit.findJust strip prefixes
where strip prefix = (,) prefix Applicative.<$> List.stripPrefix prefix list