diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+1.1.1.0 [2024-03-14]
+--------------------
+* Mini.Transformers.ParserT:
+    * Add MonadFail instance
+    * Add end-of-file parser
+    * Add chain combinators
+
 1.1.0.0 [2024-03-11]
 --------------------
 * Conventionalise module naming: package.section.title
diff --git a/Mini/Transformers/ParserT.hs b/Mini/Transformers/ParserT.hs
--- a/Mini/Transformers/ParserT.hs
+++ b/Mini/Transformers/ParserT.hs
@@ -19,12 +19,17 @@
   string,
   oneOf,
   noneOf,
+  eof,
 
   -- * Combinators
   sepBy,
   sepBy1,
   endBy,
   endBy1,
+  chainl,
+  chainl1,
+  chainr,
+  chainr1,
   between,
   option,
 ) where
@@ -100,17 +105,22 @@
 instance (Monad m, Monoid a) => Monoid (ParserT s m a) where
   mempty = pure mempty
 
+instance (Monad m) => MonadFail (ParserT s m) where
+  fail msg = ParserT . const . pure $ Left [FailError msg]
+
 -- | Abstract representation of a parse error for symbols /s/
 data ParseError s
   = EndOfInput
   | Unexpected s
   | EmptyError
+  | FailError String
 
 instance (Show s) => Show (ParseError s) where
   show = \case
     EndOfInput -> "unexpected EOF"
     Unexpected s -> "unexpected " <> show s
     EmptyError -> "empty"
+    FailError msg -> msg
 
 {-
  - Parsers
@@ -118,14 +128,13 @@
 
 -- | Parse symbols satisfying a predicate
 sat :: (Applicative m) => (s -> Bool) -> ParserT s m s
-sat p =
-  ParserT $ \case
-    [] -> pure $ Left [EndOfInput]
-    (s : ss) ->
-      bool
-        (pure $ Left [Unexpected s])
-        (pure $ Right (s, ss))
-        $ p s
+sat p = ParserT $ \case
+  [] -> pure $ Left [EndOfInput]
+  (s : ss) ->
+    bool
+      (pure $ Left [Unexpected s])
+      (pure $ Right (s, ss))
+      $ p s
 
 -- | Parse any symbol
 item :: (Applicative m) => ParserT s m s
@@ -147,6 +156,12 @@
 noneOf :: (Applicative m, Foldable t, Eq s) => t s -> ParserT s m s
 noneOf = sat . flip notElem
 
+-- | Parse successfully only at end of input
+eof :: (Applicative m) => ParserT s m ()
+eof = ParserT $ \case
+  [] -> pure $ Right ((), [])
+  (s : _) -> pure $ Left [Unexpected s]
+
 {-
  - Combinators
  -}
@@ -166,6 +181,45 @@
 -- | Parse one or more @p@ separated and ended by @q@ via @p \`endBy1\` q@
 endBy1 :: (Monad m, Eq s) => ParserT s m a -> ParserT s m b -> ParserT s m [a]
 endBy1 p sep = sepBy1 p sep <* sep
+
+-- | Parse zero or more @p@ left-chained with @op@ atop @a@ via @chainl p op a@
+chainl
+  :: (Monad m, Eq s)
+  => ParserT s m a
+  -> ParserT s m (a -> a -> a)
+  -> a
+  -> ParserT s m a
+chainl p op a = option a $ chainl1 p op
+
+-- | Parse one or more @p@ left-chained with @op@ via @chainl1 p op@
+chainl1
+  :: (Monad m, Eq s)
+  => ParserT s m a
+  -> ParserT s m (a -> a -> a)
+  -> ParserT s m a
+chainl1 p op = p >>= go
+ where
+  go a = option a $ op <*> pure a <*> p >>= go
+
+-- | Parse zero or more @p@ right-chained with @op@ atop @a@ via @chainr p op a@
+chainr
+  :: (Monad m, Eq s)
+  => ParserT s m a
+  -> ParserT s m (a -> a -> a)
+  -> a
+  -> ParserT s m a
+chainr p op a = option a $ chainr1 p op
+
+-- | Parse one or more @p@ right-chained with @op@ via @chainr1 p op@
+chainr1
+  :: (Monad m, Eq s)
+  => ParserT s m a
+  -> ParserT s m (a -> a -> a)
+  -> ParserT s m a
+chainr1 p op = go
+ where
+  go = p >>= rest
+  rest a = option a $ op <*> pure a <*> go >>= rest
 
 -- | Parse @p@ enclosed by @a@ and @b@ via @between a b p@
 between
diff --git a/mini.cabal b/mini.cabal
--- a/mini.cabal
+++ b/mini.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               mini
-version:            1.1.0.0
+version:            1.1.1.0
 license:            MIT
 license-file:       LICENSE
 copyright:          (c) 2023-2024 Victor Wallsten
