classify-frog-0.2.3: src/LabelPattern.hs
{- |
Non-monadic parsers of intervals
where we use a restricted set of operations that preserve the invariants:
* replacing intervals match the outer bounds of the replaced intervals
* produced intervals do not overlap.
-}
module LabelPattern (
Interval(..),
dur,
(&),
Bounds,
fuseBounds,
T,
next,
check,
match,
match2,
fusedMatch2,
maybe,
maybeLabel,
alt,
combine,
expand,
fuse,
fuseWith,
guard,
many1,
mapMaybe,
move,
optional,
followedBy,
notFollowedBy,
atEnd,
precededBy,
terminatedBy,
snocMaybe,
apply,
applyDefault,
Flatten,
flatten1,
flatten2,
flattenFoldable,
flattenPair,
) where
import qualified Sound.Audacity.LabelTrack as LabelTrack
import qualified Control.Monad.Trans.State as MS
import qualified Control.Monad.Trans.Class as MT
import qualified Control.Monad as Monad
import qualified Control.Functor.HT as FuncHT
import Control.Applicative (liftA2, (<$>), (<|>))
import Control.Functor.HT (void)
import qualified Data.NonEmpty.Class as NonEmptyC
import qualified Data.NonEmpty as NonEmpty
import qualified Data.List.HT as ListHT
import qualified Data.Foldable as Fold
import Data.Traversable (Traversable, traverse)
import Data.Foldable (Foldable)
import Data.Tuple.HT (mapPair, mapFst, mapSnd)
import Data.Maybe (isNothing)
import qualified Algebra.Additive as Additive
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (maybe, map)
import qualified Prelude as P
data Interval t a = Interval {intervalBounds :: Bounds t, intervalLabel :: a}
type Bounds t = (t,t)
instance Functor (Interval t) where
fmap f (Interval bnds a) = Interval bnds $ f a
instance Foldable (Interval t) where
foldMap f (Interval _bnds a) = f a
instance Traversable (Interval t) where
traverse f (Interval bnds a) = fmap (Interval bnds) $ f a
pairFromInterval :: Interval t a -> LabelTrack.Interval t a
pairFromInterval (Interval bnds a) = (bnds, a)
dur :: (Additive.C t) => Interval t a -> t
dur = uncurry subtract . intervalBounds
{- |
The two intervals must be adjacent.
This is not checked.
-}
(&) ::
Interval t a ->
Interval t b ->
Interval t (a,b)
Interval bnds0 a & Interval bnds1 b =
Interval (fuseBounds bnds0 bnds1) (a,b)
fuseBounds :: Bounds t -> Bounds t -> Bounds t
fuseBounds bnds0 bnds1 = (fst bnds0, snd bnds1)
newtype
T t a bnds fb = Cons (MS.StateT (LabelTrack.T t a) Maybe (bnds, fb))
instance Functor (T t a bnds) where
fmap = map
map :: (b -> c) -> T t a bnds b -> T t a bnds c
map f (Cons m) = Cons $ fmap (mapSnd f) m
viewL :: LabelTrack.T t a -> Maybe (Interval t a, LabelTrack.T t a)
viewL (LabelTrack.Cons xt) =
fmap (mapPair (uncurry Interval, LabelTrack.Cons)) $ ListHT.viewL xt
next :: T t a (Bounds t) (Interval t a)
next =
Cons $ fmap (\x -> (intervalBounds x, x)) $ MS.StateT viewL
-- like Monoid.<>
infixr 6 `combine`
combine :: T t a bnds0 b -> T t a bnds1 c -> T t a (bnds0,bnds1) (b,c)
combine (Cons f) (Cons g) =
Cons $
liftA2
(\(bnds0,x0) (bnds1,x1) -> ((bnds0,bnds1), (x0,x1)))
f g
fuseCombined :: T t a (Pair (Bounds t)) b -> T t a (Bounds t) b
fuseCombined (Cons f) = Cons $ fmap (mapFst (uncurry fuseBounds)) f
fuseWith ::
(b -> c -> d) ->
T t a (Bounds t) b -> T t a (Bounds t) c -> T t a (Bounds t) d
fuseWith h p q = uncurry h <$> fuse p q
fuse :: T t a (Bounds t) b -> T t a (Bounds t) c -> T t a (Bounds t) (b,c)
fuse p q = fuseCombined $ combine p q
move ::
(Additive.C t) => t -> T t a (Pair (Bounds t)) b -> T t a (Pair (Bounds t)) b
move d (Cons m) =
Cons $ fmap (mapFst (mapPair (mapSnd (d+), mapFst (d+)))) m
guard :: (b -> Bool) -> T t a bnds b -> T t a bnds b
guard p (Cons m) = Cons $ Monad.mfilter (p . snd) m
check :: (a -> Bool) -> T t a (Bounds t) (Interval t a)
check p = guard (p . intervalLabel) next
match :: (Eq a) => a -> T t a (Bounds t) (Interval t a)
match a = check (a==)
type Pair a = (a,a)
match2 :: (Eq a) => Pair a -> T t a (Pair (Bounds t)) (Pair (Interval t a))
match2 (x,y) = combine (match x) (match y)
fusedMatch2 :: (Eq a) => Pair a -> T t a (Bounds t) (Pair (Interval t a))
fusedMatch2 = fuseCombined . match2
infixl 3 `alt`
alt :: T t a f b -> T t a f b -> T t a f b
alt (Cons x) (Cons y) = Cons (x<|>y)
mapMaybe :: (b -> Maybe c) -> T t a bnds b -> T t a bnds c
mapMaybe f (Cons m) = Cons $ MT.lift . FuncHT.mapSnd f =<< m
maybe :: (a -> Maybe b) -> T t a (Bounds t) (Interval t b)
maybe f = mapMaybe (traverse f) next
maybeLabel :: (a -> Maybe b) -> T t a (Bounds t) b
maybeLabel f = mapMaybe (f . intervalLabel) next
optional :: T t a bnds fa -> T t a (Maybe bnds) (Maybe fa)
optional (Cons m) =
Cons $ mapPair (Just, Just) <$> m <|> return (Nothing, Nothing)
{- |
This is dangerous,
because it is not checked whether the outer interval bounds match.
-}
expand ::
(Functor f) =>
T t a (Bounds t) (f (Interval t b)) -> T t a (f (Bounds t)) (f b)
expand (Cons m) = Cons (FuncHT.unzip . fmap pairFromInterval . snd <$> m)
infixr 6 `followedBy`, `notFollowedBy`
followedBy :: T t a bnds0 b -> T t a bnds1 c -> T t a bnds0 b
followedBy (Cons p) (Cons q) =
Cons $ do
x0 <- p
s <- MS.get
void q
MS.put s
return x0
notFollowedBy :: T t a bnds0 b -> T t a bnds1 c -> T t a bnds0 b
notFollowedBy (Cons p) (Cons q) =
Cons $ do
x0 <- p
Monad.guard =<< MS.gets (isNothing . MS.evalStateT q)
return x0
atEnd :: T t a bnds b -> T t a bnds b
atEnd (Cons f) =
Cons $ do
x <- f
Monad.guard . LabelTrack.null =<< MS.get
return x
oneMore ::
T t a (Bounds t) b ->
T t a (Bounds t) (NonEmpty.T [] b) ->
T t a (Bounds t) (NonEmpty.T [] b)
oneMore p q =
alt
(fuseWith NonEmpty.cons p (NonEmpty.flatten <$> q))
(NonEmpty.singleton <$> p)
many1 :: T t a (Pair t) b -> T t a (Pair t) (NonEmpty.T [] b)
many1 p =
let go = oneMore p go
in go
precededBy ::
T t a (Pair t) b -> T t a (Pair t) b -> T t a (Pair t) (NonEmpty.T [] b)
precededBy q p = oneMore q $ many1 p
terminatedBy ::
(b -> c -> c) -> T t a (Pair t) b -> T t a (Pair t) c -> T t a (Pair t) c
terminatedBy f q p =
let go = alt (fuseWith f q go) p
in go
snocMaybe ::
T t a (Bounds t) (NonEmpty.T [] b) ->
T t a (Maybe (Bounds t)) (Maybe b) ->
T t a (Bounds t) (NonEmpty.T [] b)
snocMaybe (Cons p) (Cons q) =
Cons $ do
(bndx, x) <- p
(mbndy, my) <- q
return
(P.maybe bndx (fuseBounds bndx) mbndy,
P.maybe x (NonEmptyC.snoc x) my)
newtype
Flatten bnds fa t a =
Flatten {runFlatten :: bnds -> fa -> [LabelTrack.Interval t a]}
flatten1 :: Flatten (Bounds t) a t a
flatten1 = Flatten $ \bnds a -> [(bnds,a)]
flatten2 :: Flatten (Pair (Bounds t)) (Pair a) t a
flatten2 = Flatten $ \(bnds0,bnds1) (a0,a1) -> [(bnds0,a0), (bnds1,a1)]
flattenFoldable :: (Foldable f) => Flatten (f (Bounds t)) (f a) t a
flattenFoldable =
Flatten $ \bndss as -> zip (Fold.toList bndss) (Fold.toList as)
flattenPair ::
Flatten bnds0 a0 t fa -> Flatten bnds1 a1 t fa ->
Flatten (bnds0, bnds1) (a0, a1) t fa
flattenPair (Flatten flattenFst) (Flatten flattenSnd) =
Flatten $
\(bnds0,bnds1) (a0,a1) -> flattenFst bnds0 a0 ++ flattenSnd bnds1 a1
apply ::
Flatten iv fa t a -> T t a iv fa -> LabelTrack.T t a -> LabelTrack.T t a
apply flatten = applyDefault flatten id
applyDefault ::
Flatten iv fb t b ->
(a -> b) -> T t a iv fb -> LabelTrack.T t a -> LabelTrack.T t b
applyDefault flatten f (Cons p) =
let go xt =
case MS.runStateT p xt of
Just ((bnds,labs),xs) ->
LabelTrack.lift (runFlatten flatten bnds labs ++) $ go xs
Nothing ->
case viewL xt of
Just (x,xs) ->
LabelTrack.lift (pairFromInterval (fmap f x) :) $ go xs
Nothing -> LabelTrack.empty
in go