Using a signed Int here is a little silly, since we can never have negative levels. Change-Id: Ibe03be5014226e07dfa6f78d8360301bc1b7c9b1 Reviewed-on: https://cl.tvl.fyi/c/depot/+/3803 Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI
180 lines
5.9 KiB
Haskell
180 lines
5.9 KiB
Haskell
{-# LANGUAGE StandaloneDeriving #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
--------------------------------------------------------------------------------
|
|
module Xanthous.Data.Levels
|
|
( Levels
|
|
, allLevels
|
|
, numLevels
|
|
, nextLevel
|
|
, prevLevel
|
|
, mkLevels1
|
|
, mkLevels
|
|
, oneLevel
|
|
, current
|
|
, ComonadStore(..)
|
|
) where
|
|
--------------------------------------------------------------------------------
|
|
import Xanthous.Prelude hiding ((<.>), Empty, foldMap)
|
|
import Xanthous.Util (between, EqProp, EqEqProp(..))
|
|
import Xanthous.Util.Comonad (current)
|
|
import Xanthous.Orphans ()
|
|
--------------------------------------------------------------------------------
|
|
import Control.Comonad.Store
|
|
import Control.Comonad.Store.Zipper
|
|
import Data.Aeson (ToJSON(..), FromJSON(..))
|
|
import Data.Aeson.Generic.DerivingVia
|
|
import Data.Functor.Apply
|
|
import Data.Foldable (foldMap)
|
|
import Data.List.NonEmpty (NonEmpty)
|
|
import qualified Data.List.NonEmpty as NE
|
|
import Data.Maybe (fromJust)
|
|
import Data.Sequence (Seq((:<|), Empty))
|
|
import Data.Semigroup.Foldable.Class
|
|
import Data.Text (replace)
|
|
import Test.QuickCheck
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Collection of levels plus a pointer to the current level
|
|
--
|
|
-- Navigation is via the 'Comonad' instance. We can get the current level with
|
|
-- 'extract':
|
|
--
|
|
-- extract @Levels :: Levels level -> level
|
|
--
|
|
-- For access to and modification of the level, use
|
|
-- 'Xanthous.Util.Comonad.current'
|
|
newtype Levels a = Levels { levelZipper :: Zipper Seq a }
|
|
deriving stock (Generic)
|
|
deriving (Functor, Comonad, Foldable) via (Zipper Seq)
|
|
|
|
type instance Element (Levels a) = a
|
|
instance MonoFoldable (Levels a)
|
|
instance MonoFunctor (Levels a)
|
|
instance MonoTraversable (Levels a)
|
|
|
|
instance ComonadStore Word Levels where
|
|
pos = toEnum . pos . levelZipper
|
|
peek i = peek (fromEnum i) . levelZipper
|
|
|
|
instance Traversable Levels where
|
|
traverse f (Levels z) = Levels <$> traverse f z
|
|
|
|
instance Foldable1 Levels
|
|
|
|
instance Traversable1 Levels where
|
|
traverse1 f levs@(Levels z) = seek (pos levs) . partialMkLevels <$> go (unzipper z)
|
|
where
|
|
go Empty = error "empty seq, unreachable"
|
|
go (x :<| xs) = (<|) <$> f x <.> go xs
|
|
|
|
-- | Always takes the position of the latter element
|
|
instance Semigroup (Levels a) where
|
|
levs₁ <> levs₂
|
|
= seek (pos levs₂)
|
|
. partialMkLevels
|
|
$ allLevels levs₁ <> allLevels levs₂
|
|
|
|
-- | The number of levels stored in 'Levels'
|
|
--
|
|
-- Equivalent to 'Data.Foldable.length', but likely faster
|
|
numLevels :: Levels a -> Word
|
|
numLevels = toEnum . size . levelZipper
|
|
|
|
-- | Make Levels from a Seq. Throws an error if the seq is not empty
|
|
partialMkLevels :: Seq a -> Levels a
|
|
partialMkLevels = Levels . fromJust . zipper
|
|
|
|
-- | Make Levels from a possibly-empty structure
|
|
mkLevels :: Foldable1 f => f level -> Maybe (Levels level)
|
|
mkLevels = fmap Levels . zipper . foldMap pure
|
|
|
|
-- | Make Levels from a non-empty structure
|
|
mkLevels1 :: Foldable1 f => f level -> Levels level
|
|
mkLevels1 = fromJust . mkLevels
|
|
|
|
oneLevel :: a -> Levels a
|
|
oneLevel = mkLevels1 . Identity
|
|
|
|
-- | Get a sequence of all the levels
|
|
allLevels :: Levels a -> Seq a
|
|
allLevels = unzipper . levelZipper
|
|
|
|
-- | Step to the next level, generating a new level if necessary using the given
|
|
-- applicative action
|
|
nextLevel
|
|
:: Applicative m
|
|
=> m level -- ^ Generate a new level, if necessary
|
|
-> Levels level
|
|
-> m (Levels level)
|
|
nextLevel genLevel levs
|
|
| succ (pos levs) < numLevels levs
|
|
= pure $ seeks succ levs
|
|
| otherwise
|
|
= genLevel <&> \level ->
|
|
seek (pos levs + 1) . partialMkLevels $ allLevels levs |> level
|
|
|
|
-- | Go to the previous level. Returns Nothing if 'pos' is 0
|
|
prevLevel :: Levels level -> Maybe (Levels level)
|
|
prevLevel levs | pos levs == 0 = Nothing
|
|
| otherwise = Just $ seeks pred levs
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | alternate, slower representation of Levels we can Iso into to perform
|
|
-- various operations
|
|
data AltLevels a = AltLevels
|
|
{ _levels :: NonEmpty a
|
|
, _currentLevel :: Word -- ^ invariant: is within the bounds of _levels
|
|
}
|
|
deriving stock (Show, Eq, Generic)
|
|
deriving anyclass (NFData, CoArbitrary, Function)
|
|
deriving (ToJSON, FromJSON)
|
|
via WithOptions '[ FieldLabelModifier '[Drop 1] ]
|
|
(AltLevels a)
|
|
makeLenses ''AltLevels
|
|
|
|
alt :: Iso (Levels a) (Levels b) (AltLevels a) (AltLevels b)
|
|
alt = iso hither yon
|
|
where
|
|
hither levs = AltLevels (NE.fromList . toList $ allLevels levs) (pos levs)
|
|
yon (AltLevels levs curr) = seek curr $ mkLevels1 levs
|
|
|
|
instance Eq a => Eq (Levels a) where
|
|
(==) = (==) `on` view alt
|
|
|
|
deriving via EqEqProp (Levels a) instance Eq a => EqProp (Levels a)
|
|
|
|
instance Show a => Show (Levels a) where
|
|
show = unpack . replace "AltLevels" "Levels" . pack . show . view alt
|
|
|
|
instance NFData a => NFData (Levels a) where
|
|
rnf = rnf . view alt
|
|
|
|
instance ToJSON a => ToJSON (Levels a) where
|
|
toJSON = toJSON . view alt
|
|
|
|
instance FromJSON a => FromJSON (Levels a) where
|
|
parseJSON = fmap (review alt) . parseJSON
|
|
|
|
instance Arbitrary a => Arbitrary (AltLevels a) where
|
|
arbitrary = do
|
|
_levels <- arbitrary
|
|
_currentLevel <- choose (0, pred . toEnum . length $ _levels)
|
|
pure AltLevels {..}
|
|
shrink als = do
|
|
_levels <- shrink $ als ^. levels
|
|
_currentLevel <- filter (between 0 $ pred . toEnum . length $ _levels)
|
|
$ shrink $ als ^. currentLevel
|
|
pure AltLevels {..}
|
|
|
|
|
|
instance Arbitrary a => Arbitrary (Levels a) where
|
|
arbitrary = review alt <$> arbitrary
|
|
shrink = fmap (review alt) . shrink . view alt
|
|
|
|
instance CoArbitrary a => CoArbitrary (Levels a) where
|
|
coarbitrary = coarbitrary . view alt
|
|
|
|
instance Function a => Function (Levels a) where
|
|
function = functionMap (view alt) (review alt)
|