packages feed

goatee 0.3.1.3 → 0.4.0

raw patch · 33 files changed

+503/−286 lines, 33 filesdep ~basedep ~containersdep ~template-haskellsetup-changedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, containers, template-haskell

API changes (from Hackage documentation)

- Game.Goatee.Lib.Monad: NodeDeleteOk :: NodeDeleteResult
- Game.Goatee.Lib.Monad: data NodeDeleteResult
- Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.NodeDeleteResult
- Game.Goatee.Lib.Monad: instance GHC.Enum.Bounded Game.Goatee.Lib.Monad.NodeDeleteResult
- Game.Goatee.Lib.Monad: instance GHC.Enum.Enum Game.Goatee.Lib.Monad.NodeDeleteResult
- Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.NodeDeleteResult
+ Game.Goatee.Common: whileM'' :: Monad m => m (Either r a) -> (a -> m (Maybe r)) -> m r
+ Game.Goatee.Lib.Board: instance GHC.Classes.Eq Game.Goatee.Lib.Board.GameInfo
+ Game.Goatee.Lib.Monad: DropPositionStackEmpty :: DropPositionError
+ Game.Goatee.Lib.Monad: GoDropPositionError :: DropPositionError -> GoError
+ Game.Goatee.Lib.Monad: GoModifyGameInfoError :: ModifyGameInfoError -> GoError
+ Game.Goatee.Lib.Monad: GoModifyPropertyError :: ModifyPropertyError -> GoError
+ Game.Goatee.Lib.Monad: GoNavigationError :: NavigationError -> GoError
+ Game.Goatee.Lib.Monad: GoNodeDeleteError :: NodeDeleteError -> GoError
+ Game.Goatee.Lib.Monad: GoPopPositionError :: PopPositionError -> GoError
+ Game.Goatee.Lib.Monad: ModifyGameInfoCannotModifyRootInfo :: GameInfo -> GameInfo -> ModifyGameInfoError
+ Game.Goatee.Lib.Monad: ModifyPropertyCannotChangeType :: String -> String -> ModifyPropertyError
+ Game.Goatee.Lib.Monad: NavigationCouldNotMove :: NavigationError
+ Game.Goatee.Lib.Monad: PopPositionCannotRetraceSteps :: PopPositionError
+ Game.Goatee.Lib.Monad: PopPositionStackEmpty :: PopPositionError
+ Game.Goatee.Lib.Monad: data DropPositionError
+ Game.Goatee.Lib.Monad: data GoError
+ Game.Goatee.Lib.Monad: data ModifyGameInfoError
+ Game.Goatee.Lib.Monad: data ModifyPropertyError
+ Game.Goatee.Lib.Monad: data NavigationError
+ Game.Goatee.Lib.Monad: data NodeDeleteError
+ Game.Goatee.Lib.Monad: data PopPositionError
+ Game.Goatee.Lib.Monad: deleteChildAtOrThrow :: (MonadGo m, MonadError GoError m) => Int -> m ()
+ Game.Goatee.Lib.Monad: dropPositionOrThrow :: (MonadGo m, MonadError GoError m) => m ()
+ Game.Goatee.Lib.Monad: goDownOrThrow :: (MonadGo m, MonadError GoError m) => Int -> m ()
+ Game.Goatee.Lib.Monad: goLeftOrThrow :: (MonadGo m, MonadError GoError m) => m ()
+ Game.Goatee.Lib.Monad: goRightOrThrow :: (MonadGo m, MonadError GoError m) => m ()
+ Game.Goatee.Lib.Monad: goUpOrThrow :: (MonadGo m, MonadError GoError m) => m ()
+ Game.Goatee.Lib.Monad: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Game.Goatee.Lib.Monad.GoT m)
+ Game.Goatee.Lib.Monad: instance Control.Monad.Fail.MonadFail m => Control.Monad.Fail.MonadFail (Game.Goatee.Lib.Monad.GoT m)
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.DropPositionError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.GoError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.ModifyGameInfoError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.ModifyPropertyError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.NavigationError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.NodeDeleteError
+ Game.Goatee.Lib.Monad: instance GHC.Classes.Eq Game.Goatee.Lib.Monad.PopPositionError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.DropPositionError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.GoError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.ModifyGameInfoError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.ModifyPropertyError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.NavigationError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.NodeDeleteError
+ Game.Goatee.Lib.Monad: instance GHC.Show.Show Game.Goatee.Lib.Monad.PopPositionError
+ Game.Goatee.Lib.Monad: modifyGameInfoOrThrow :: (MonadGo m, MonadError GoError m) => (GameInfo -> GameInfo) -> m (GameInfo, GameInfo)
+ Game.Goatee.Lib.Monad: modifyPropertyOrThrow :: (MonadGo m, MonadError GoError m, Descriptor d) => d -> (Maybe Property -> Maybe Property) -> m ()
+ Game.Goatee.Lib.Monad: popPositionOrThrow :: (MonadGo m, MonadError GoError m) => m ()
- Game.Goatee.Lib.Monad: AnyEvent :: (Event go h) -> AnyEvent go
+ Game.Goatee.Lib.Monad: AnyEvent :: Event go h -> AnyEvent go
- Game.Goatee.Lib.Monad: NodeDeleteBadIndex :: NodeDeleteResult
+ Game.Goatee.Lib.Monad: NodeDeleteBadIndex :: NodeDeleteError
- Game.Goatee.Lib.Monad: NodeDeleteOnPathStack :: NodeDeleteResult
+ Game.Goatee.Lib.Monad: NodeDeleteOnPathStack :: NodeDeleteError
- Game.Goatee.Lib.Monad: deleteChildAt :: MonadGo go => Int -> go NodeDeleteResult
+ Game.Goatee.Lib.Monad: deleteChildAt :: MonadGo go => Int -> go (Either NodeDeleteError ())
- Game.Goatee.Lib.Monad: dropPosition :: MonadGo go => go ()
+ Game.Goatee.Lib.Monad: dropPosition :: MonadGo go => go (Either DropPositionError ())
- Game.Goatee.Lib.Monad: modifyGameInfo :: MonadGo go => (GameInfo -> GameInfo) -> go GameInfo
+ Game.Goatee.Lib.Monad: modifyGameInfo :: MonadGo go => (GameInfo -> GameInfo) -> go (Either ModifyGameInfoError (GameInfo, GameInfo))
- Game.Goatee.Lib.Monad: modifyProperty :: (MonadGo go, Descriptor d) => d -> (Maybe Property -> Maybe Property) -> go ()
+ Game.Goatee.Lib.Monad: modifyProperty :: (MonadGo go, Descriptor d) => d -> (Maybe Property -> Maybe Property) -> go (Either ModifyPropertyError ())
- Game.Goatee.Lib.Monad: popPosition :: MonadGo go => go ()
+ Game.Goatee.Lib.Monad: popPosition :: MonadGo go => go (Either PopPositionError ())
- Game.Goatee.Lib.Property: B :: (Maybe Coord) -> Property
+ Game.Goatee.Lib.Property: B :: Maybe Coord -> Property
- Game.Goatee.Lib.Property: ValuedPropertyInfo :: String -> PropertyType -> Bool -> Property -> Bool -> PropertyValueType v -> Property -> v -> v -> Property -> ValuedPropertyInfo v
+ Game.Goatee.Lib.Property: ValuedPropertyInfo :: String -> PropertyType -> Bool -> (Property -> Bool) -> PropertyValueType v -> (Property -> v) -> (v -> Property) -> ValuedPropertyInfo v
- Game.Goatee.Lib.Property: W :: (Maybe Coord) -> Property
+ Game.Goatee.Lib.Property: W :: Maybe Coord -> Property

Files

Setup.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
goatee.cabal view
@@ -1,16 +1,16 @@ name: goatee-version: 0.3.1.3+version: 0.4.0 synopsis: A monadic take on a 2,500-year-old board game - library. category: Game license: AGPL-3 license-file: LICENSE-copyright: Copyright 2014-2018 Bryan Gardiner+copyright: Copyright 2014-2021 Bryan Gardiner author: Bryan Gardiner <bog@khumba.net> maintainer: Bryan Gardiner <bog@khumba.net> homepage: http://khumba.net/projects/goatee bug-reports: https://savannah.nongnu.org/projects/goatee/ tested-with: GHC-cabal-version: >=1.8+cabal-version: 2.0 build-type: Simple data-files: LICENSE description:@@ -28,11 +28,11 @@  library     build-depends:-        base >= 4 && < 5,-        containers >= 0.4 && < 0.6,+        base >= 4.12 && < 5,+        containers >= 0.4 && < 0.7,         mtl >= 2.1 && < 2.3,         parsec >= 3.1 && < 3.2,-        template-haskell >= 2.7 && < 2.14+        template-haskell >= 2.7 && < 2.17     exposed-modules:         Game.Goatee.App         Game.Goatee.Common@@ -46,11 +46,12 @@         Game.Goatee.Lib.Renderer.Tree         Game.Goatee.Lib.Tree         Game.Goatee.Lib.Types-    extensions:+    default-extensions:         ExistentialQuantification         FlexibleContexts         FlexibleInstances         FunctionalDependencies+        LambdaCase         MultiParamTypeClasses         UndecidableInstances     ghc-options: -W -fwarn-incomplete-patterns -fwarn-unused-do-bind@@ -61,11 +62,14 @@         Game.Goatee.Lib.Property.Renderer         Game.Goatee.Lib.Property.Value         Paths_goatee+    autogen-modules:+        Paths_goatee+    default-language: Haskell2010  test-suite test-goatee     build-depends:-        base >= 4 && < 5,-        containers >= 0.4 && < 0.6,+        base >= 4.12 && < 5,+        containers >= 0.4 && < 0.7,         goatee,         HUnit >= 1.2 && < 1.7,         mtl >= 2.1 && < 2.3,@@ -88,4 +92,8 @@         Game.Goatee.Lib.TreeTest         Game.Goatee.Lib.TypesTest         Game.Goatee.Test.Common+        Paths_goatee+    autogen-modules:+        Paths_goatee     type: exitcode-stdio-1.0+    default-language: Haskell2010
src/Game/Goatee/App.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -29,7 +29,7 @@  -- | A user-presentable copyright message. applicationCopyright :: String-applicationCopyright = "Copyright 2014-2018 Bryan Gardiner"+applicationCopyright = "Copyright 2014-2021 Bryan Gardiner"  -- | The home page for Goatee on the web. applicationWebsite :: String
src/Game/Goatee/Common.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -32,6 +32,7 @@   forIndexM_,   whileM,   whileM',+  whileM'',   doWhileM,   ) where @@ -130,6 +131,22 @@                        case x of                          Nothing -> return ()                          Just y -> body y >> whileM' test body++-- | A while loop that supports returning a value, and also exiting early from+-- the body.+--+-- @whileM'' test body@ repeatedly evaluates @test@.  If the test produces a+-- @Right a@, then @body a@ is evaluated, and the loop will either repeat (if+-- @body@ returns @Nothing@) or exit (if @body@ returns a @Just r@).  If @test@+-- produces a @Left r@ at any point, then the loop immediately exits with that+-- @r@ value.+whileM'' :: Monad m => m (Either r a) -> (a -> m (Maybe r)) -> m r+whileM'' test body =+  test >>= \case+    Left r -> return r+    Right a -> body a >>= \case+      Just r -> return r+      Nothing -> whileM'' test body  -- | @doWhileM init body@ repeatedly calls @body@ with @init@.  As long as -- @body@ returns a @Right@ value, it is re-executed with the returned value.
src/Game/Goatee/Common/Bigfloat.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Board.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -82,7 +82,7 @@    , gameInfoAnnotatorName :: Maybe SimpleText   , gameInfoEntererName :: Maybe SimpleText-  } deriving (Show)+  } deriving (Eq, Show)  -- | Builds a 'GameInfo' with the given 'RootInfo' and no extra data. emptyGameInfo :: RootInfo -> GameInfo
src/Game/Goatee/Lib/Monad.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -26,7 +26,24 @@   evalGoT, evalGo,   execGoT, execGo,   Step (..),-  NodeDeleteResult (..),+  -- * Errors+  GoError (..),+  NavigationError (..),+  PopPositionError (..),+  DropPositionError (..),+  ModifyPropertyError (..),+  ModifyGameInfoError (..),+  NodeDeleteError (..),+  -- * Throwing monadic actions+  goUpOrThrow,+  goDownOrThrow,+  goLeftOrThrow,+  goRightOrThrow,+  popPositionOrThrow,+  dropPositionOrThrow,+  modifyPropertyOrThrow,+  modifyGameInfoOrThrow,+  deleteChildAtOrThrow,   -- * Event handling   Event, AnyEvent (..), eventName, fire, eventHandlerFromAction,   -- * Events@@ -38,13 +55,14 @@   variationModeChangedEvent, VariationModeChangedHandler,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), Applicative ((<*>), pure))-#endif #if !MIN_VERSION_containers(0,5,0) import Control.Arrow (second) #endif import Control.Monad ((<=<), ap, forM, forM_, liftM, msum, unless, when)+import Control.Monad.Except (MonadError, catchError, throwError)+#if !MIN_VERSION_base(4,13,0)+import Control.Monad.Fail (MonadFail, fail)+#endif import Control.Monad.Identity (Identity, runIdentity) import qualified Control.Monad.State as State import Control.Monad.State (MonadState, StateT, get, put)@@ -62,6 +80,9 @@ import qualified Game.Goatee.Lib.Tree as Tree import Game.Goatee.Lib.Tree hiding (addChild, addChildAt, deleteChildAt) import Game.Goatee.Lib.Types+#if !MIN_VERSION_base(4,13,0)+import Prelude hiding (fail)+#endif  -- | The internal state of a Go monad transformer.  @go@ is the type of -- Go monad or transformer (instance of 'GoMonad').@@ -188,12 +209,14 @@   pushPosition :: go ()    -- | Returns to the last position pushed onto the internal position stack via-  -- 'pushPosition'.  This action must be balanced by a 'pushPosition'.-  popPosition :: go ()+  -- 'pushPosition', if there is one.  Returns a code indicating the result of+  -- the action.+  popPosition :: go (Either PopPositionError ())    -- | Drops the last position pushed onto the internal stack by 'pushPosition'-  -- off of the stack.  This action must be balanced by a 'pushPosition'.-  dropPosition :: go ()+  -- off of the stack, if there is one.  Returns a code indicating the result of+  -- the action.+  dropPosition :: go (Either DropPositionError ())    -- | Returns the set of properties on the current node.   getProperties :: go [Property]@@ -216,7 +239,14 @@   -- the same name, if one exists.  Fires 'propertiesModifiedEvent' if the   -- property has changed.   putProperty :: Property -> go ()-  putProperty property = modifyProperty property $ const $ Just property+  putProperty property = do+    result <- modifyProperty property $ const $ Just property+    case result of+      Right () -> return ()+      Left (ModifyPropertyCannotChangeType old new) ->+        error $ "MonadGo.putProperty: " +++        "Internal error, should not have attempted to change property type " +++        "(old '" ++ old ++ "', new '" ++ new ++ "')."    -- | Deletes a property from the current node, if it's set, and fires   -- 'propertiesModifiedEvent'.@@ -230,14 +260,30 @@   -- >    deleteProperty $ PL White   -- >    getPropertyValue propertyPL   deleteProperty :: Descriptor d => d -> go ()-  deleteProperty descriptor = modifyProperty descriptor $ const Nothing+  deleteProperty descriptor = do+    result <- modifyProperty descriptor $ const Nothing+    case result of+      Right () -> return ()+      Left (ModifyPropertyCannotChangeType old new) ->+        error $ "MonadGo.deleteProperty: " +++        "Internal error, should not have attempted to change property type " +++        "(old '" ++ old ++ "', new '" ++ new ++ "')."    -- | Calls the given function to modify the state of the given property   -- (descriptor) on the current node.  'Nothing' represents the property not   -- existing on the node, and a 'Just' marks the property's presence.  Fires   -- 'propertiesModifiedEvent' if the property changed.  This function does not   -- do any validation to check that the resulting tree state is valid.-  modifyProperty :: Descriptor d => d -> (Maybe Property -> Maybe Property) -> go ()+  --+  -- The given function is not allowed to change the property into a different+  -- property.  Instead, the old property should be removed and the new property+  -- should be inserted separately.  If the function does this,+  -- 'ModifyPropertyCannotChangeType' is returned and no modification takes+  -- place.+  modifyProperty :: Descriptor d+                 => d+                 -> (Maybe Property -> Maybe Property)+                 -> go (Either ModifyPropertyError ())    -- | Calls the given function to modify the state of the given valued property   -- (descriptor) on the current node.  'Nothing' represents the property not@@ -246,8 +292,13 @@   -- changed.  This function does not do any validation to check that the   -- resulting tree state is valid.   modifyPropertyValue :: ValuedDescriptor v d => d -> (Maybe v -> Maybe v) -> go ()-  modifyPropertyValue descriptor fn = modifyProperty descriptor $ \old ->-    propertyBuilder descriptor <$> fn (propertyValue descriptor <$> old)+  modifyPropertyValue descriptor fn = modifyProperty descriptor modify >>= \case+    Right () -> return ()+    Left (ModifyPropertyCannotChangeType old new) ->+      error $ "MonadGo.modifyPropertyValue: Internal error, attempted to change " +++      "property type (old '" ++ old ++ "', new '" ++ new ++ "')."+    where modify old =+            propertyBuilder descriptor <$> fn (propertyValue descriptor <$> old)    -- | Mutates the string-valued property attached to the current node according   -- to the given function.  The input string will be empty if the current node@@ -299,8 +350,13 @@   -- | Mutates the game info for the current path, returning the new info.  If   -- the current node or one of its ancestors has game info properties, then   -- that node is modified.  Otherwise, properties are inserted on the root-  -- node.-  modifyGameInfo :: (GameInfo -> GameInfo) -> go GameInfo+  -- node.  The return value on success is @(oldGameInfo, newGameInfo)@.+  --+  -- The given function is not allowed to modify the 'RootInfo' within the+  -- 'GameInfo'.  If this happens, an error code is returned and no+  -- modifications are made.+  modifyGameInfo :: (GameInfo -> GameInfo)+                 -> go (Either ModifyGameInfoError (GameInfo, GameInfo))    -- | Sets the game's 'VariationMode' via the 'ST' property on the root node,   -- then fires a 'variationModeChangedEvent' if the variation mode has changed.@@ -375,7 +431,7 @@                                 buildCoordList coords]                              }           ok <- goDown =<< subtract 1 . length . cursorChildren <$> getCursor-          unless ok $ fail "GoT.modifyAssignedStones: Failed to move to new child."+          unless ok $ error "GoT.modifyAssignedStones: Failed to move to new child."       else do         -- Get a map from getAllAssignedStones: Map Coord (Maybe Color)         allAssignedStones <- getAllAssignedStones@@ -438,14 +494,14 @@    -- | Adds a child node to the current node at the given index, shifting all   -- existing children at and after the index to the right.  The index must be-  -- in the range @[0, numberOfChildren]@.  Fires a 'childAddedEvent' after the-  -- child is added.+  -- in the range @[0, numberOfChildren]@; if it is not, it will be capped to+  -- this range.  Fires a 'childAddedEvent' after the child is added.   addChildAt :: Int -> Node -> go ()    -- | Tries to remove the child node at the given index below the current node.   -- Returns a status code indicating whether the deletion succeeded, or why   -- not.-  deleteChildAt :: Int -> go NodeDeleteResult+  deleteChildAt :: Int -> go (Either NodeDeleteError ())    -- | Registers a new event handler for a given event type.   on :: Event go h -> h -> go ()@@ -456,16 +512,127 @@   on0 :: Event go h -> go () -> go ()   on0 event handler = on event $ eventHandlerFromAction event handler --- | The result of deleting a node.-data NodeDeleteResult =-  NodeDeleteOk-  -- ^ The node was deleted successfully.-  | NodeDeleteBadIndex-    -- ^ The node couldn't be deleted, because an invalid index was given.+-- | All of the types of errors that 'MonadGo' functions can return.+data GoError =+  GoNavigationError NavigationError+  | GoPopPositionError PopPositionError+  | GoDropPositionError DropPositionError+  | GoModifyPropertyError ModifyPropertyError+  | GoModifyGameInfoError ModifyGameInfoError+  | GoNodeDeleteError NodeDeleteError+  deriving (Eq, Show)++-- | Errors from attempting to navigate.  Thrown by 'goUpOrThrow',+-- 'goDownOrThrow', 'goLeftOrThrow', 'goRightOrThrow'.+data NavigationError =+  NavigationCouldNotMove+  -- ^ Could not make the requested motion.+  deriving (Eq, Show)++-- | Errors from 'popPosition'.+data PopPositionError =+  PopPositionStackEmpty+  -- ^ There is no previous position to return to.  No action was taken.+  | PopPositionCannotRetraceSteps+    -- ^ The previous position could not be returned to, because the game tree+    -- has been modified.  The current position in the game tree is where motion+    -- reached when it could go no further.  This is probably not useful, and+    -- computation should be abandoned.+  deriving (Eq, Show)++-- | Errors from 'dropPosition'.+data DropPositionError =+  DropPositionStackEmpty+  -- ^ There is no previous position to drop.  No action was taken.+  deriving (Eq, Show)++-- | Errors from 'modifyProperty'.+data ModifyPropertyError =+  ModifyPropertyCannotChangeType String String+  -- ^ The function attempted to change the property into another property;+  -- this is not allowed.  No change was made.  The two strings are renderings+  -- of the old and new property, respectively.+  deriving (Eq, Show)++-- | Errors from 'modifyGameInfo'.+data ModifyGameInfoError =+  ModifyGameInfoCannotModifyRootInfo GameInfo GameInfo+  -- ^ The function illegally attempted to modify 'RootInfo' properties within+  -- the 'GameInfo'.  The old and attempted new records are returned,+  -- respectfully.  No changes were committed to the game info.+  deriving (Eq, Show)++-- | Errors from calling 'deleteChildAt'.+data NodeDeleteError =+  NodeDeleteBadIndex+  -- ^ The node couldn't be deleted, because an invalid index was given.   | NodeDeleteOnPathStack     -- ^ The node couldn't be deleted, because it is on the path stack.-  deriving (Bounded, Enum, Eq, Show)+  deriving (Eq, Show) +-- | Like 'goUp', but throws 'NavigationCouldNotMove' if at the root of the+-- tree.+goUpOrThrow :: (MonadGo m, MonadError GoError m) => m ()+goUpOrThrow = goUp >>= \case+  True -> return ()+  False -> throwError $ GoNavigationError NavigationCouldNotMove++-- | Like 'goDown', but throws 'NavigationCouldNotMove' if the requested child+-- does not exist.+goDownOrThrow :: (MonadGo m, MonadError GoError m) => Int -> m ()+goDownOrThrow index = goDown index >>= \case+  True -> return ()+  False -> throwError $ GoNavigationError NavigationCouldNotMove++-- | Like 'goLeft', but throws 'NavigationCouldNotMove' if there is no left+-- sibling to move to.+goLeftOrThrow :: (MonadGo m, MonadError GoError m) => m ()+goLeftOrThrow = goLeft >>= \case+  True -> return ()+  False -> throwError $ GoNavigationError NavigationCouldNotMove++-- | Like 'goRight', but throws 'NavigationCouldNotMove' if there is no right+-- sibling to move to.+goRightOrThrow :: (MonadGo m, MonadError GoError m) => m ()+goRightOrThrow = goRight >>= \case+  True -> return ()+  False -> throwError $ GoNavigationError NavigationCouldNotMove++-- | Same as 'popPosition', but throws errors rather than returning them.+popPositionOrThrow :: (MonadGo m, MonadError GoError m) => m ()+popPositionOrThrow =+  popPosition >>=+  either (throwError . GoPopPositionError) return++-- | Same as 'dropPosition', but throws errors rather than returning them.+dropPositionOrThrow :: (MonadGo m, MonadError GoError m) => m ()+dropPositionOrThrow =+  dropPosition >>=+  either (throwError . GoDropPositionError) return++-- | Same as 'modifyProperty', but throws errors rather than returning them.+modifyPropertyOrThrow :: (MonadGo m, MonadError GoError m, Descriptor d)+                      => d+                      -> (Maybe Property -> Maybe Property)+                      -> m ()+modifyPropertyOrThrow descriptor fn =+  modifyProperty descriptor fn >>=+  either (throwError . GoModifyPropertyError) return++-- | Same as 'modifyGameInfo', but throws errors rather than returning them.+modifyGameInfoOrThrow :: (MonadGo m, MonadError GoError m)+                      => (GameInfo -> GameInfo)+                      -> m (GameInfo, GameInfo)+modifyGameInfoOrThrow fn =+  modifyGameInfo fn >>=+  either (throwError . GoModifyGameInfoError) return++-- | Same as 'deleteChildAt', but throws errors rather than returning them.+deleteChildAtOrThrow :: (MonadGo m, MonadError GoError m) => Int -> m ()+deleteChildAtOrThrow index =+  deleteChildAt index >>=+  either (throwError . GoNodeDeleteError) return+ -- | The standard monad transformer for 'MonadGo'. newtype GoT m a = GoT { goState :: StateT (GoState (GoT m)) m a } @@ -482,8 +649,10 @@ instance Monad m => Monad (GoT m) where   return x = GoT $ return x   m >>= f = GoT $ goState . f =<< goState m-  fail = lift . fail +instance MonadFail m => MonadFail (GoT m) where+  fail = GoT . fail+ instance MonadTrans GoT where   lift = GoT . lift @@ -500,6 +669,14 @@   listen = GoT . listen . goState   pass = GoT . pass . goState +instance MonadError e m => MonadError e (GoT m) where+  throwError = lift . throwError+  catchError action handler =+    -- action :: GoT m a+    -- handler :: e -> GoT m a+    -- Need to call catchError :: StateT (GoState (GoT m)) m a -> (e -> StateT ...) -> StateT ...+    GoT $ catchError (goState action) (goState . handler)+ -- | Executes a Go monad transformer on a cursor, returning in the underlying -- monad a tuple that contains the resulting value and the final cursor. runGoT :: Monad m => GoT m a -> Cursor -> m (a, Cursor)@@ -559,18 +736,22 @@     case (cursorParent cursor, cursorChildIndex cursor) of       (Nothing, _) -> return False       (Just _, 0) -> return False-      (Just _, n) -> do True <- goUp-                        True <- goDown $ n - 1-                        return True+      (Just _, n) -> goUp >>= \case+        True -> goDown (n - 1) >>= \case+          True -> return True+          False -> error "GoT.goLeft: Internal error, could not go down."+        False -> error "GoT.goLeft: Internal error, could not go up."    goRight = do     cursor <- getCursor     case (cursorParent cursor, cursorChildIndex cursor) of       (Nothing, _) -> return False       (Just parent, n) | n == cursorChildCount parent - 1 -> return False-      (Just _, n) -> do True <- goUp-                        True <- goDown $ n + 1-                        return True+      (Just _, n) -> goUp >>= \case+        True -> goDown (n + 1) >>= \case+          True -> return True+          False -> error "GoT.goRight: Internal error, could not go down."+        False -> error "GoT.goRight: Internal error, could not go up."    goToRoot = whileM goUp $ return () @@ -578,31 +759,52 @@     where findGameInfoNode = do             cursor <- getCursor             if hasGameInfo cursor-              then dropPosition >> return True+              then dropPosition >>= \case+                     Right () -> return True+                     Left DropPositionStackEmpty -> errorDropStackEmpty               else if isNothing $ cursorParent cursor-                   then do if goToRootIfNotFound then dropPosition else popPosition-                           return False+                   then if goToRootIfNotFound+                        then dropPosition >>= \case+                               Right () -> return False+                               Left DropPositionStackEmpty -> errorDropStackEmpty+                        else popPosition >>= \case+                               Right () -> return False+                               Left PopPositionStackEmpty -> errorPopStackEmpty+                               Left PopPositionCannotRetraceSteps -> errorPopCannotRetrace                    else goUp >> findGameInfoNode           hasGameInfo cursor = internalIsGameInfoNode $ cursorNode cursor+          errorDropStackEmpty =+            error "GoT.goToGameInfoNode: Internal error, DropPositionStackEmpty."+          errorPopStackEmpty =+            error "GoT.goToGameInfoNode: Internal error, PopPositionStackEmpty."+          errorPopCannotRetrace =+            error "GoT.goToGameInfoNode: Internal error, PopPositionCannotRetraceSteps."    pushPosition = modifyState $ \state ->     state { statePathStack = []:statePathStack state }    popPosition = do-    getPathStack >>= \stack -> when (null stack) $-      fail "popPosition: No position to pop from the stack."+    getPathStack >>= \case+      [] -> return $ Left PopPositionStackEmpty+      _ -> do+        -- Drop each step in the top list of the path stack one at a time, until the+        -- top list is empty.+        maybeRetraceErrorResult <- whileM''+          (do ~(path:_) <- getPathStack  -- TODO Don't use irrefutable pattern.+              return $ if null path then Left Nothing else Right $ head path)+          (\step -> do+            ok <- takeStepM step $ \((_:steps):paths) -> steps:paths+            return $ if ok then Nothing else Just $ Just PopPositionCannotRetraceSteps) -    -- Drop each step in the top list of the path stack one at a time, until the-    -- top list is empty.-    whileM' (do path:_ <- getPathStack-                return $ if null path then Nothing else Just $ head path) $ \step -> do-      ok <- takeStepM step $ \((_:steps):paths) -> steps:paths-      unless ok $ fail "popPosition: Failed to retrace steps."+        case maybeRetraceErrorResult of+          Just err -> return $ Left err+          Nothing -> do+            -- Finally, drop the empty top of the path stack.+            modifyState $ \state -> case statePathStack state of+              []:rest -> state { statePathStack = rest }+              _ -> error "popPosition: Internal failure, top of path stack is not empty." -    -- Finally, drop the empty top of the path stack.-    modifyState $ \state -> case statePathStack state of-      []:rest -> state { statePathStack = rest }-      _ -> error "popPosition: Internal failure, top of path stack is not empty."+            return $ Right ()    dropPosition = do     state <- getState@@ -611,9 +813,11 @@     -- may still be needed to return to the second-on-stack position by a     -- following popPosition.     case statePathStack state of-      x:y:xs -> putState $ state { statePathStack = (x ++ y):xs }-      [_] -> putState $ state { statePathStack = [] }-      [] -> fail "dropPosition: No position to drop from the stack."+      x:y:xs -> do putState $ state { statePathStack = (x ++ y):xs }+                   return $ Right ()+      [_] -> do putState $ state { statePathStack = [] }+                return $ Right ()+      [] -> return $ Left DropPositionStackEmpty    modifyProperties fn = do     oldCursor <- getCursor@@ -646,15 +850,16 @@     let node = cursorNode cursor         old = findProperty descriptor node         new = fn old-    when (maybe False (not . propertyPredicate descriptor) new) $-      fail $ "modifyProperty: May not change property type: " ++-      show old ++ " -> " ++ show new ++ "."-    case (old, new) of-      (Just _, Nothing) -> modifyProperties $ remove descriptor-      (Nothing, Just value') -> modifyProperties $ add value'-      (Just value, Just value') | value /= value' ->-        modifyProperties $ add value' . remove descriptor-      _ -> return ()+    if (maybe False (not . propertyPredicate descriptor) new)+      then return $ Left $ ModifyPropertyCannotChangeType (show old) (show new)+      else do+        case (old, new) of+          (Just _, Nothing) -> modifyProperties $ remove descriptor+          (Nothing, Just value') -> modifyProperties $ add value'+          (Just value, Just value') | value /= value' -> do+            modifyProperties $ add value' . remove descriptor+          _ -> return ()+        return $ Right ()     where remove descriptor = filter (not . propertyPredicate descriptor)           add value = (value:) @@ -662,14 +867,20 @@     cursor <- getCursor     let info = boardGameInfo $ cursorBoard cursor         info' = fn info-    when (gameInfoRootInfo info /= gameInfoRootInfo info') $-      fail "Illegal modification of root info in modifyGameInfo."-    pushPosition-    _ <- goToGameInfoNode True-    modifyProperties $ \props ->-      gameInfoToProperties info' ++ filter ((GameInfoProperty /=) . propertyType) props-    popPosition-    return info'+    if gameInfoRootInfo info /= gameInfoRootInfo info'+      then return $ Left $ ModifyGameInfoCannotModifyRootInfo info info'+      else do+        pushPosition+        _ <- goToGameInfoNode True+        modifyProperties $ \props ->+          gameInfoToProperties info' ++ filter ((GameInfoProperty /=) . propertyType) props+        popPosition >>= \case+          Right () -> return ()+          Left PopPositionStackEmpty ->+            error "GoT.modifyGameInfo: Internal error, PopPositionStackEmpty."+          Left PopPositionCannotRetraceSteps ->+            error "GoT.modifyGameInfo: Internal error, PopPositionCannotRetraceSteps."+        return $ Right (info, info')    modifyVariationMode fn = do     pushPosition@@ -689,41 +900,48 @@          else if new == defaultVariationMode               then Nothing               else Just new-    popPosition+    result <- popPosition+    case result of+      Right () -> return ()+      Left PopPositionStackEmpty ->+        error "GoT.modifyVariationMode: Internal error, got PopPositionStackEmpty."+      Left PopPositionCannotRetraceSteps ->+        error "GoT.modifyVariationMode: Internal error, got PopPositionCannotRetraceSteps."    addChildAt index node = do     cursor <- getCursor     let childCount = cursorChildCount cursor-    when (index < 0 || index > childCount) $ fail $-      "Monad.addChildAt: Index " ++ show index ++ " is not in [0, " ++ show childCount ++ "]."-    let cursor' = cursorModifyNode (Tree.addChildAt index node) cursor+        indexCapped = if index < 0 then 0+                      else if index > childCount then childCount+                      else index+    let cursor' = cursorModifyNode (Tree.addChildAt indexCapped node) cursor     modifyState $ \state ->       state { stateCursor = cursor'             , statePathStack = foldPathStack                                (\step -> case step of-                                   GoUp n -> GoUp $ if n < index then n else n + 1+                                   GoUp n -> GoUp $ if n < indexCapped then n else n + 1                                    down@(GoDown _) -> down)                                (\step -> case step of                                    up@(GoUp _) -> up-                                   GoDown n -> GoDown $ if n < index then n else n + 1)+                                   GoDown n -> GoDown $ if n < indexCapped then n else n + 1)                                id                                cursor'                                (statePathStack state)             }-    fire childAddedEvent ($ index)+    fire childAddedEvent ($ indexCapped)    deleteChildAt index = do     childCount <- cursorChildCount <$> getCursor     if index < 0 || index >= childCount-      then return NodeDeleteBadIndex+      then return $ Left NodeDeleteBadIndex       else do goDown index >>=-                \ok -> unless ok $ fail "GoT.deleteChildAt: Internal error, index isn't valid."+                \ok -> unless ok $ error "GoT.deleteChildAt: Internal error, index isn't valid."               childCursor <- getCursor               deletingNodeOnPath <- doesPathStackEnterCurrentNode <$>                                     pure childCursor <*> getPathStack-              goUp >>= \ok -> unless ok $ fail "GoT.deleteChildAt: Internal error, can't go up."+              goUp >>= \ok -> unless ok $ error "GoT.deleteChildAt: Internal error, can't go up."               if deletingNodeOnPath-                then return NodeDeleteOnPathStack+                then return $ Left NodeDeleteOnPathStack                 else do cursor <- getCursor                         let cursor' = cursorModifyNode (Tree.deleteChildAt index) cursor                         modifyState $ \state ->@@ -741,7 +959,7 @@                                   (statePathStack state)                                 }                         fire childDeletedEvent ($ childCursor)-                        return NodeDeleteOk+                        return $ Right ()    on event handler = modifyState $ addHandler event handler 
src/Game/Goatee/Lib/Parser.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -26,9 +26,6 @@   ) where  import Control.Arrow ((+++))-#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<*), (*>))-#endif import Data.Maybe (fromMaybe) import Game.Goatee.Common import Game.Goatee.Lib.Board
src/Game/Goatee/Lib/Property.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Property/Base.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -35,9 +35,6 @@   defProperty, defValuedProperty,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$))-#endif import Game.Goatee.Lib.Property.Value (PropertyValueType(..), nonePvt) import Game.Goatee.Lib.Renderer import Game.Goatee.Lib.Types
src/Game/Goatee/Lib/Property/Info.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Property/Parser.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -45,16 +45,9 @@   text,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$), (<$>), (<*), (<*>), (*>))-#endif import Control.Monad (when) import Data.Char (isUpper, ord) import Data.Maybe (catMaybes)-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid (Monoid, mappend, mconcat, mempty)-#endif-import Data.Semigroup as Sem ((<>), Semigroup) import qualified Game.Goatee.Common.Bigfloat as BF import Game.Goatee.Lib.Types import Text.ParserCombinators.Parsec (@@ -74,7 +67,7 @@ -- between two @CoordList@s. newtype CoordListMonoid = CoordListMonoid { runCoordListMonoid :: CoordList } -instance Sem.Semigroup CoordListMonoid where+instance Semigroup CoordListMonoid where   (<>) (CoordListMonoid x) (CoordListMonoid y) =     CoordListMonoid $ coords' (coordListSingles x ++ coordListSingles y)                               (coordListRects x ++ coordListRects y)
src/Game/Goatee/Lib/Property/Renderer.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Property/Value.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Renderer.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Renderer/Tree.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
src/Game/Goatee/Lib/Tree.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -27,9 +27,6 @@   validateNode,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif import Control.Monad (forM_, unless, when) import Control.Monad.Writer (Writer, execWriter, tell) import Data.Function (on)
src/Game/Goatee/Lib/Types.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -47,9 +47,6 @@   Ruleset (..), RulesetType (..), fromRuleset, toRuleset,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>))-#endif import Data.Char (isSpace) import Data.Function (on) import Data.List (delete, groupBy, partition, sort)
tests/Game/Goatee/Common/BigfloatTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/CommonTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/BoardTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/MonadTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -19,18 +19,12 @@  module Game.Goatee.Lib.MonadTest (tests) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif import Control.Arrow ((&&&), second) import Control.Monad (forM_, liftM, replicateM_, void)-import Control.Monad.Writer (Writer, execWriter, runWriter, tell)+import Control.Monad.Writer (Writer, execWriter, execWriterT, runWriter, tell) import Data.List (sortBy, unfoldr) import qualified Data.Map as Map import Data.Maybe (fromJust, maybeToList)-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid (Monoid)-#endif import Data.Ord (comparing) import Game.Goatee.Common import Game.Goatee.Lib.Board@@ -45,13 +39,6 @@  {-# ANN module "HLint: ignore Reduce duplication" #-} -type LoggedGoM = GoT (Writer [String])--runLoggedGo :: LoggedGoM a -> Cursor -> (a, Cursor, [String])-runLoggedGo go cursor =-  let ((value, cursor'), log) = runWriter $ runGoT go cursor-  in (value, cursor', log)- tests = "Game.Goatee.Lib.Monad" ~: TestList   [ monadTests   , navigationTests@@ -170,7 +157,7 @@                 "False",                 "[B (Just (4,4))]"] -  , "invokes handlers when navigating" ~:+  , "invokes handlers when navigating" ~: do     let cursor = rootCursor $ node1 [B Nothing] $ node [W Nothing]         action = do on navigationEvent $ \step -> case step of                       GoUp index -> tell ["Up " ++ show index]@@ -178,11 +165,10 @@                     on navigationEvent $ \step -> case step of                       GoDown index -> tell ["Down " ++ show index]                       _ -> return ()-                    True <- goDown 0-                    True <- goUp-                    return ()-        (_, _, log) = runLoggedGo action cursor-    in log @?= ["Down 0", "Up 0"]+                    goDownOrThrow 0+                    goUpOrThrow+        result = execWriterT $ runGoT action cursor+    result @?= Right ["Down 0", "Up 0"]    , "navigates to the root of a tree, invoking handlers" ~: do     let cursor = child 0 $ child 0 $ rootCursor $@@ -191,7 +177,8 @@                  node [B $ Just (2,2)]         action = do on navigationEvent $ \step -> tell [show step]                     goToRoot-        (_, cursor', log) = runLoggedGo action cursor+        (cursor', log) = runWriter $ execGoT action cursor+    --Right (_, cursor', log) <- runLoggedGoT action cursor     cursorProperties cursor' @?= [B $ Just (0,0)]     log @?= ["GoUp 0", "GoUp 0"] @@ -237,12 +224,12 @@ positionStackTests = "position stack" ~: TestList   [ "should push, pop, and drop with no navigation" ~: do     let cursor = rootCursor $ node []-        actions = [pushPosition >> popPosition,-                   pushPosition >> pushPosition >> popPosition >> popPosition,-                   pushPosition >> dropPosition,-                   pushPosition >> pushPosition >> dropPosition >> popPosition,-                   pushPosition >> pushPosition >> popPosition >> dropPosition]-    forM_ actions $ \action -> cursorProperties (execGo action cursor) @?= []+        actions = [pushPosition >> popPositionOrThrow,+                   pushPosition >> pushPosition >> popPositionOrThrow >> popPositionOrThrow,+                   pushPosition >> dropPositionOrThrow,+                   pushPosition >> pushPosition >> dropPositionOrThrow >> popPositionOrThrow,+                   pushPosition >> pushPosition >> popPositionOrThrow >> dropPositionOrThrow]+    forM_ actions $ \action -> cursorProperties <$> execGoT action cursor @?= Right []    , "should backtrack up and down the tree" ~: do     let cursor = child 0 $ child 1 $ rootCursor $@@ -250,44 +237,44 @@                        [node1 [W $ Just (1,1)] $ node [B $ Just (2,2)],                         node1 [W Nothing] $ node [B Nothing]]         action = pushPosition >> goUp >> goUp >> goDown 0 >> goDown 0-    cursorProperties (execGo (action >> popPosition) cursor) @?= [B Nothing]-    cursorProperties (execGo (action >> dropPosition) cursor) @?= [B $ Just (2,2)]+    (cursorProperties <$> execGoT (action >> popPositionOrThrow) cursor) @?= Right [B Nothing]+    (cursorProperties <$> execGoT (action >> dropPositionOrThrow) cursor) @?= Right [B $ Just (2,2)]    , "should pop multiple stacks" ~: do     let cursor = child 0 $ child 0 commonCursor         action = do navigate-                    log >> popPosition-                    log >> popPosition+                    log >> popPositionOrThrow+                    log >> popPositionOrThrow                     log-    execWriter (runGoT action cursor) @?=-      ["B (2,2)", "B (3,3)", "B (5,5)", "B (3,3)", "B (2,2)"]+    execWriterT (runGoT action cursor) @?=+      Right ["B (2,2)", "B (3,3)", "B (5,5)", "B (3,3)", "B (2,2)"]    , "should drop then pop" ~: do     let cursor = child 0 $ child 0 commonCursor         action = do navigate-                    log >> dropPosition-                    log >> popPosition+                    log >> dropPositionOrThrow+                    log >> popPositionOrThrow                     log-    execWriter (runGoT action cursor) @?=-      ["B (2,2)", "B (3,3)", "B (5,5)", "B (5,5)", "B (2,2)"]+    execWriterT (runGoT action cursor) @?=+      Right ["B (2,2)", "B (3,3)", "B (5,5)", "B (5,5)", "B (2,2)"]    , "should drop twice" ~: do     let cursor = child 0 $ child 0 commonCursor         action = do navigate-                    log >> dropPosition-                    log >> dropPosition+                    log >> dropPositionOrThrow+                    log >> dropPositionOrThrow                     log-    execWriter (runGoT action cursor) @?=-      ["B (2,2)", "B (3,3)", "B (5,5)", "B (5,5)", "B (5,5)"]+    execWriterT (runGoT action cursor) @?=+      Right ["B (2,2)", "B (3,3)", "B (5,5)", "B (5,5)", "B (5,5)"]    , "should fire navigation handlers while popping" ~: do     let cursor = rootCursor $ node1 [B Nothing] $ node [W Nothing]         action = do pushPosition-                    True <- goDown 0-                    True <- goUp+                    goDownOrThrow 0+                    goUpOrThrow                     on navigationEvent $ \step -> tell [step]-                    popPosition-    execWriter (runGoT action cursor) @?= [GoDown 0, GoUp 0]+                    popPositionOrThrow+    execWriterT (runGoT action cursor) @?= Right [GoDown 0, GoUp 0]   ]   where commonCursor = rootCursor $                        node' [B $ Just (0,0)]@@ -300,14 +287,13 @@           [W (Just x)] -> tell ["W " ++ show x]           xs -> error $ "Unexpected properties: " ++ show xs         navigate = do log >> pushPosition-                      True <- goUp-                      True <- goDown 1+                      goUpOrThrow+                      goDownOrThrow 1                       log >> pushPosition-                      True <- goUp-                      True <- goUp-                      True <- goDown 1-                      True <- goDown 0-                      return ()+                      goUpOrThrow+                      goUpOrThrow+                      goDownOrThrow 1+                      goDownOrThrow 0  propertiesTests = "properties" ~: TestList   [ "getProperties" ~: TestList@@ -942,28 +928,28 @@     [ "basic case just not needing updating" ~:       let cursor = child 0 $ rootCursor $ node' [B Nothing] [node [W Nothing]]           action = do pushPosition-                      True <- goUp+                      goUpOrThrow                       addChildAt 1 $ node [W $ Just (0,0)]-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W Nothing]+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W Nothing])      , "basic case just needing updating" ~:       let cursor = child 0 $ rootCursor $ node' [B Nothing] [node [W Nothing]]           action = do pushPosition-                      True <- goUp+                      goUpOrThrow                       addChildAt 0 $ node [W $ Just (0,0)]-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W Nothing]+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W Nothing])      , "basic case definitely needing updating" ~:       let cursor = rootCursor $ node' [B Nothing] [node [W $ Just (0,0)],                                                    node [W $ Just (1,1)]]-          action = do True <- goDown 1+          action = do goDownOrThrow 1                       pushPosition-                      True <- goUp+                      goUpOrThrow                       addChildAt 0 $ node [W Nothing]-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W $ Just (1,1)]+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W $ Just (1,1)])      , "multiple paths to update" ~:       let at y x = B $ Just (y,x)@@ -971,102 +957,114 @@           level1Node i = node' [at 1 i] $ map level2Node [0..2]           level2Node i = node' [at 2 i] $ map level3Node [0..3]           level3Node i = node [at 3 i]-          action = do True <- and <$> sequence [goDown 1, goDown 2, goDown 3]+          action = do goDownOrThrow 1+                      goDownOrThrow 2+                      goDownOrThrow 3                       pushPosition-                      replicateM_ 3 goUp+                      replicateM_ 3 goUpOrThrow                       pushPosition-                      True <- and <$> sequence [goDown 1, goDown 2, goDown 2, goUp, goDown 1]+                      goDownOrThrow 1+                      goDownOrThrow 2+                      goDownOrThrow 2+                      goUpOrThrow+                      goDownOrThrow 1                       addChildAt 0 $ node1 [] $ node []-                      True <- and <$> sequence [goDown 0, goDown 0]+                      goDownOrThrow 0+                      goDownOrThrow 0                       goToRoot-                      popPosition-                      popPosition-      in cursorNode (execGo action $ rootCursor level0Node) @?= node [B $ Just (3,3)]+                      popPositionOrThrow+                      popPositionOrThrow+      in fmap cursorNode (execGoT action $ rootCursor level0Node) @?=+         Right (node [B $ Just (3,3)])      , "updates paths with GoUp correctly" ~:       let cursor = rootCursor $ node1 [B $ Just (0,0)] $ node [W $ Just (1,1)]           action = do pushPosition-                      True <- goDown 0-                      True <- goUp+                      goDownOrThrow 0+                      goUpOrThrow                       addChildAt 0 $ node [B $ Just (2,2)]                       on navigationEvent $ \step -> tell [step]-                      popPosition-          log = execWriter (runGoT action cursor)-      in log @?= [GoDown 1, GoUp 1]+                      popPositionOrThrow+          log = execWriterT (runGoT action cursor)+      in log @?= Right ([GoDown 1, GoUp 1])     ]   ]  deleteChildAtTests = "deleteChildAt" ~: TestList   [ "ignores invalid indices" ~: do     second cursorNode (runGo (deleteChildAt 0) $ rootCursor $ node []) @?=-      (NodeDeleteBadIndex, node [])+      (Left NodeDeleteBadIndex, node [])     let base = node' [] [node [MN 0]]-    second cursorNode (runGo (deleteChildAt (-2)) $ rootCursor base) @?= (NodeDeleteBadIndex, base)-    second cursorNode (runGo (deleteChildAt (-1)) $ rootCursor base) @?= (NodeDeleteBadIndex, base)-    second cursorNode (runGo (deleteChildAt 1) $ rootCursor base) @?= (NodeDeleteBadIndex, base)-    second cursorNode (runGo (deleteChildAt 2) $ rootCursor base) @?= (NodeDeleteBadIndex, base)+    second cursorNode (runGo (deleteChildAt (-2)) $ rootCursor base) @?=+      (Left NodeDeleteBadIndex, base)+    second cursorNode (runGo (deleteChildAt (-1)) $ rootCursor base) @?=+      (Left NodeDeleteBadIndex, base)+    second cursorNode (runGo (deleteChildAt 1) $ rootCursor base) @?=+      (Left NodeDeleteBadIndex, base)+    second cursorNode (runGo (deleteChildAt 2) $ rootCursor base) @?=+      (Left NodeDeleteBadIndex, base)    , "deletes an only child" ~:     let cursor = rootCursor $ node1 [MN 0] $ node [MN 1]-        action = deleteChildAt 0-    in second cursorNode (runGo action cursor) @?= (NodeDeleteOk, node [MN 0])+        action = deleteChildAtOrThrow 0+    in fmap cursorNode (execGoT action cursor) @?= Right (node [MN 0])    , "deletes a first child" ~:     let cursor = rootCursor $ node' [MN 0] [node [MN 1], node [MN 2]]-        action = deleteChildAt 0-    in second cursorNode (runGo action cursor) @?= (NodeDeleteOk, node1 [MN 0] $ node [MN 2])+        action = deleteChildAtOrThrow 0+    in fmap cursorNode (execGoT action cursor) @?= Right (node1 [MN 0] $ node [MN 2])    , "deletes middle children" ~: do     let base = node' [MN 0] [node [MN 1], node [MN 2], node [MN 3], node [MN 4]]         cursor = rootCursor base-    second cursorNode (runGo (deleteChildAt 1) cursor) @?=-      (NodeDeleteOk, base { nodeChildren = listDeleteAt 1 $ nodeChildren base })-    second cursorNode (runGo (deleteChildAt 2) cursor) @?=-      (NodeDeleteOk, base { nodeChildren = listDeleteAt 2 $ nodeChildren base })+    fmap cursorNode (execGoT (deleteChildAtOrThrow 1) cursor) @?=+      Right (base { nodeChildren = listDeleteAt 1 $ nodeChildren base })+    fmap cursorNode (execGoT (deleteChildAtOrThrow 2) cursor) @?=+      Right (base { nodeChildren = listDeleteAt 2 $ nodeChildren base })    , "deletes a final child" ~: do     let base = node' [MN 0] [node [MN 1], node [MN 2], node [MN 3], node [MN 4]]         cursor = rootCursor base-    second cursorNode (runGo (deleteChildAt 1) cursor) @?=-      (NodeDeleteOk, base { nodeChildren = listDeleteAt 1 $ nodeChildren base })-    second cursorNode (runGo (deleteChildAt 2) cursor) @?=-      (NodeDeleteOk, base { nodeChildren = listDeleteAt 2 $ nodeChildren base })+    fmap cursorNode (execGoT (deleteChildAtOrThrow 1) cursor) @?=+      Right (base { nodeChildren = listDeleteAt 1 $ nodeChildren base })+    fmap cursorNode (execGoT (deleteChildAtOrThrow 2) cursor) @?=+      Right (base { nodeChildren = listDeleteAt 2 $ nodeChildren base })    , "fires childDeletedEvent after deleting a child" ~:     let cursor = rootCursor $ node' [MN 0] [node [MN 1], node [MN 2]]         action = do on childDeletedEvent $ tell . (:[]) . (cursorChildIndex &&& cursorNode)-                    deleteChildAt 1-    in execWriter (runGoT action cursor) @?= [(1, cursorNode $ child 1 cursor)]+                    deleteChildAtOrThrow 1+    in execWriterT (runGoT action cursor) @?= Right [(1, cursorNode $ child 1 cursor)]    , "path stack correctness" ~: TestList     [ "basic case just not needing updating" ~:       let cursor = rootCursor $ node' [B Nothing] [node [W Nothing], node [W $ Just (0,0)]]-          action = do True <- goDown 0+          action = do goDownOrThrow 0                       pushPosition-                      True <- goUp-                      NodeDeleteOk <- deleteChildAt 1-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W Nothing]+                      goUpOrThrow+                      deleteChildAtOrThrow 1+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W Nothing])      , "basic case just needing updating" ~:       let cursor = rootCursor $ node' [B Nothing] [node [W Nothing], node [W $ Just (0,0)]]-          action = do True <- goDown 1+          action = do goDownOrThrow 1                       pushPosition-                      True <- goUp-                      NodeDeleteOk <- deleteChildAt 0-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W $ Just (0,0)]+                      goUpOrThrow+                      deleteChildAtOrThrow 0+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W $ Just (0,0)])      , "basic case definitely needing updating" ~:       let cursor = rootCursor $ node' [B Nothing] [node [W $ Just (0,0)],                                                    node [W $ Just (1,1)],                                                    node [W $ Just (2,2)]]-          action = do True <- goDown 2+          action = do goDownOrThrow 2                       pushPosition-                      True <- goUp-                      NodeDeleteOk <- deleteChildAt 0-                      popPosition-      in cursorNode (execGo action cursor) @?= node [W $ Just (2,2)]+                      goUpOrThrow+                      deleteChildAtOrThrow 0+                      popPositionOrThrow+      in fmap cursorNode (execGoT action cursor) @?= Right (node [W $ Just (2,2)])      , "multiple paths to update" ~:       let at y x = B $ Just (y,x)@@ -1074,37 +1072,40 @@           level1Node i = node' [at 1 i] $ map level2Node [0..2]           level2Node i = node' [at 2 i] $ map level3Node [0..3]           level3Node i = node [at 3 i]-          action = do True <- and <$> sequence [goDown 1, goDown 2, goDown 3]+          action = do goDownOrThrow 1+                      goDownOrThrow 2+                      goDownOrThrow 3                       pushPosition-                      True <- goUp-                      NodeDeleteOk <- deleteChildAt 1+                      goUpOrThrow+                      deleteChildAtOrThrow 1                       pushPosition                       goToRoot-                      True <- goDown 0-                      True <- goDown 2+                      goDownOrThrow 0+                      goDownOrThrow 2                       pushPosition-                      True <- goUp-                      NodeDeleteOk <- deleteChildAt 0+                      goUpOrThrow+                      deleteChildAtOrThrow 0                       goToRoot-                      True <- goDown 1-                      True <- goDown 2-                      NodeDeleteOk <- deleteChildAt 1+                      goDownOrThrow 1+                      goDownOrThrow 2+                      deleteChildAtOrThrow 1                       replicateM_ 3 popPosition-      in cursorNode (execGo action $ rootCursor level0Node) @?= node [B $ Just (3,3)]+      in fmap cursorNode (execGoT action $ rootCursor level0Node) @?=+         Right (node [B $ Just (3,3)])      , "returns an error if a node to delete is on the path stack" ~:       let base = node' [B $ Just (0,0)] [node [W $ Just (1,1)],                                          node [W $ Just (2,2)]]-          action = do True <- goDown 1+          action = do goDownOrThrow 1                       pushPosition-                      True <- goUp+                      goUpOrThrow                       pushPosition-                      True <- goDown 0+                      goDownOrThrow 0                       pushPosition-                      True <- goUp-                      deleteChildAt 1-      in second cursorNode (runGo action $ rootCursor base) @?=-         (NodeDeleteOnPathStack, base)+                      goUpOrThrow+                      deleteChildAt 1  -- Note, nonthrowing.+      in fmap (second cursorNode) (runGoT action $ rootCursor base) @?=+         Right (Left NodeDeleteOnPathStack, base)     ]   ] @@ -1114,30 +1115,28 @@                  node1 [B $ Just (0,0)] $                  node [W $ Just (0,0), GN $ toSimpleText "Foo"]         action = do on gameInfoChangedEvent onInfo-                    True <- goDown 0-                    return ()-    in execWriter (runGoT action cursor) @?= [(Nothing, Just $ toSimpleText "Foo")]+                    goDownOrThrow 0+    in execWriterT (runGoT action cursor) @?= Right [(Nothing, Just $ toSimpleText "Foo")]    , "fires when navigating up" ~:     let cursor = child 0 $ rootCursor $                  node1 [B $ Just (0,0)] $                  node [W $ Just (0,0), GN $ toSimpleText "Foo"]         action = do on gameInfoChangedEvent onInfo-                    True <- goUp-                    return ()-    in execWriter (runGoT action cursor) @?= [(Just $ toSimpleText "Foo", Nothing)]+                    goUpOrThrow+    in execWriterT (runGoT action cursor) @?= Right [(Just $ toSimpleText "Foo", Nothing)]    , "fires from within popPosition" ~:     let cursor = rootCursor $                  node1 [B $ Just (0,0)] $                  node [W $ Just (0,0), GN $ toSimpleText "Foo"]         action = do pushPosition-                    True <- goDown 0-                    True <- goUp+                    goDownOrThrow 0+                    goUpOrThrow                     on gameInfoChangedEvent onInfo-                    popPosition-    in execWriter (runGoT action cursor) @?=-       [(Nothing, Just $ toSimpleText "Foo"), (Just $ toSimpleText "Foo", Nothing)]+                    popPositionOrThrow+    in execWriterT (runGoT action cursor) @?=+       Right [(Nothing, Just $ toSimpleText "Foo"), (Just $ toSimpleText "Foo", Nothing)]    , "fires when modifying properties" ~:     let cursor = rootCursor $ node []
tests/Game/Goatee/Lib/ParserTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/ParserTestUtils.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -24,9 +24,6 @@   assertNoParse,   ) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<*))-#endif import Game.Goatee.Lib.Parser import Game.Goatee.Lib.Tree import Test.HUnit (assertFailure)
tests/Game/Goatee/Lib/Property/ParserTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by@@ -19,9 +19,6 @@  module Game.Goatee.Lib.Property.ParserTest (tests) where -#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif import Control.Monad (forM_) import Data.Maybe (catMaybes) import Game.Goatee.Common
tests/Game/Goatee/Lib/PropertyTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/RoundTripTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/TestInstances.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/TestUtils.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/TreeTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Lib/TypesTest.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Game/Goatee/Test/Common.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by
tests/Test.hs view
@@ -1,6 +1,6 @@ -- This file is part of Goatee. ----- Copyright 2014-2018 Bryan Gardiner+-- Copyright 2014-2021 Bryan Gardiner -- -- Goatee is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by