diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,22 @@
+# 0.7
+
+* COMPILER ASSISTED BREAKING CHANGE. `Parser` is now a type synonym
+  for `ParserT Identity`.
+
+* COMPILER ASSISTED BREAKING CHANGE. `runParser` renamed to `parse`.
+
+* COMPILER ASSISTED BREAKING CHANGE. `Text`, `text` and `text'` are
+  now named `TextLazy`, `textLazy` and `textLazy`. The previous names
+  are now used with strict `Text`.
+
+* Introduced `ParserT`, `parse`, `parseM`, `runParserT`, `parserT`,
+  `ParserState`, `initialParserState`, `pTextLazy`.
+
+* Fixed exhaustivenes checks for `Node` patterns.
+
+* New dependencies: `exceptions`, `mmorph`, `mtl`.
+
+
 # 0.6.2
 
 * `encode` now renders attributes in alphabetical order.
diff --git a/lib/Xmlbf.hs b/lib/Xmlbf.hs
--- a/lib/Xmlbf.hs
+++ b/lib/Xmlbf.hs
@@ -1,7 +1,14 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 -- | XML back and forth!
 --
@@ -15,14 +22,24 @@
 -- package.
 --
 -- @xmlbf@ doesn't do any parsing of raw XML on its own. Instead, one should
--- use @xmlbf@ together with libraries like
+-- use
+-- [xmlbf](https://hackage.haskell.org/package/xmlbf)
+-- together with libraries like
 -- [xmlbf-xeno](https://hackage.haskell.org/package/xmlbf-xeno) or
 -- [xmlbf-xmlhtml](https://hackage.haskell.org/package/xmlbf-xmlhtml) for
 -- this.
 module Xmlbf {--}
  ( -- * Parsing
-   runParser
+   parse
+ , parseM
+   -- ** Low-level
  , Parser
+ , ParserT
+ , parserT
+ , runParserT
+ , ParserState
+ , initialParserState
+
    -- * Parsers
  , pElement
  , pAnyElement
@@ -31,6 +48,7 @@
  , pAttrs
  , pChildren
  , pText
+ , pTextLazy
  , pEndOfInput
 
     -- * Rendering
@@ -40,14 +58,21 @@
  , Node
  , node
 
+   -- ** Element
  , pattern Element
  , element
  , element'
 
+   -- ** Text (strict)
  , pattern Text
  , text
  , text'
 
+   -- ** Text (lazy)
+ , pattern TextLazy
+ , textLazy
+ , textLazy'
+
    -- * Fixpoints
  , dfpos
  , dfposM
@@ -62,19 +87,26 @@
 
 import Control.Applicative (Alternative(empty, (<|>)), liftA2)
 import Control.DeepSeq (NFData(rnf))
-import Control.Monad (MonadPlus(mplus, mzero), join, when)
+import Control.Monad (MonadPlus(mplus, mzero), join, when, ap)
+import qualified Control.Monad.Catch as Ex
+import Control.Monad.Error.Class (MonadError(catchError, throwError))
 import qualified Control.Monad.Fail
 import Control.Monad.Fix (MonadFix(mfix))
+import Control.Monad.IO.Class (MonadIO(liftIO))
+import Control.Monad.Morph (MFunctor(hoist))
+import Control.Monad.Reader.Class (MonadReader(local, ask))
+import Control.Monad.State.Class (MonadState(state))
+import Control.Monad.Trans (MonadTrans(lift))
 import Control.Monad.Zip (MonadZip(mzipWith))
 import Control.Selective (Selective(select))
 import qualified Data.ByteString.Builder as BB
 import qualified Data.ByteString.Builder.Prim as BBP
 import qualified Data.Char as Char
 import Data.Foldable (for_, toList)
-import Data.Function (fix)
 import Data.Functor.Identity (Identity(Identity), runIdentity)
-import qualified Data.HashMap.Strict as HM
 import qualified Data.Map.Strict as Map
+import qualified Data.HashMap.Strict as HM
+import Data.Kind (Type)
 import Data.Sequence (Seq)
 import qualified Data.Sequence as Seq
 import qualified Data.Text as T
@@ -88,49 +120,87 @@
 
 -- | Either a text or an element node in an XML fragment body.
 --
--- Construct with 'text' or 'element'. Destruct with 'Text' or 'Element'.
+-- Construct with 'text', 'textLazy' or 'element'.
+--
+-- Destruct with 'Text', 'TextLazy' or 'Element'.
 data Node
-  = Element' !T.Text !(HM.HashMap T.Text T.Text) ![Node]
-  | Text' !TL.Text
+  = Node_Element !T.Text !(HM.HashMap T.Text T.Text) ![Node]
+  | Node_Text !TL.Text
   deriving (Eq)
 
 instance NFData Node where
   rnf = \case
-    Element' n as cs -> rnf n `seq` rnf as `seq` rnf cs `seq` ()
-    Text' t -> rnf t `seq` ()
+    Node_Element n as cs -> rnf n `seq` rnf as `seq` rnf cs `seq` ()
+    Node_Text t -> rnf t `seq` ()
   {-# INLINABLE rnf #-}
 
 instance Show Node where
   showsPrec n = \x -> showParen (n > 10) $ case x of
-    Text' t -> showString "Text " . showsPrec 0 t
-    Element' t as cs ->
+    Node_Text t -> showString "Text " . showsPrec 0 t
+    Node_Element t as cs ->
       showString "Element " .
       showsPrec 0 t . showChar ' ' .
       showsPrec 0 (HM.toList as) . showChar ' ' .
       showsPrec 0 cs
 
 -- | Destruct an element 'Node'.
-pattern Element :: T.Text -> HM.HashMap T.Text T.Text -> [Node] -> Node
-pattern Element t as cs <- Element' t as cs
-{-# COMPLETE Element #-} -- TODO this leads to silly pattern matching warnings
+--
+-- @
+-- case n :: Node of
+--   'Element' t as cs -> ...
+--   _ -> ...
+-- @
+pattern Element
+  :: T.Text  -- ^ Element name as strict 'T.Text'.
+  -> HM.HashMap T.Text T.Text
+  -- ^ Pairs of attribute names and possibly 'T.empty' values, as strict
+  -- 'T.Text'.
+  -> [Node]  -- ^ Element children.
+  -> Node
+pattern Element t as cs <- Node_Element t as cs
 
--- | Destruct a text 'Node'.
-pattern Text :: TL.Text -> Node
-pattern Text t <- Text' t
-{-# COMPLETE Text #-} -- TODO this leads to silly pattern matching warnings
+-- | Destruct a text 'Node' into a lazy 'TL.Text'.
+--
+-- @
+-- case n :: Node of
+--   'TextLazy' tl -> ...
+--   _ -> ...
+-- @
+pattern TextLazy
+  :: TL.Text  -- ^ Lazy 'TL.Text'.
+  -> Node
+pattern TextLazy tl <- Node_Text tl
 
+-- | Destruct a text 'Node' into a strict 'T.Text'.
+--
+-- @
+-- case n :: Node of
+--   'Text' t -> ...
+--   _ -> ...
+-- @
+pattern Text
+  :: T.Text  -- ^ Strict 'T.Text'.
+  -> Node
+pattern Text t <- Node_Text (TL.toStrict -> t)
+
+{-# COMPLETE Text, Element :: Node #-}
+{-# COMPLETE TextLazy, Element :: Node #-}
+{-# COMPLETE Node_Text, Element :: Node #-}
+{-# COMPLETE Text, Node_Element :: Node #-}
+{-# COMPLETE TextLazy, Node_Element :: Node #-}
+
 -- | Case analysis for a 'Node'.
 node
   :: (T.Text -> HM.HashMap T.Text T.Text -> [Node] -> a)
   -- ^ Transform an 'Element' node.
   -> (TL.Text -> a)
-  -- ^ Transform a 'Text' node.
+  -- ^ Transform a 'TextLazy' node.
   -> Node
   -> a
 {-# INLINE node #-}
 node fe ft = \case
-  Text' t -> ft t
-  Element' t as cs -> fe t as cs
+  Node_Text t -> ft t
+  Node_Element t as cs -> fe t as cs
 
 -- | Normalizes 'Node's by concatenating consecutive 'Text' nodes.
 normalize :: [Node] -> [Node]
@@ -138,14 +208,14 @@
 normalize = \case
    -- Note that @'Text' ""@ is forbidden by construction, actually. But we do
    -- take care of it in case the 'Node' was constructed unsafely somehow.
-   Text' "" : ns -> normalize ns
-   Text' a : Text' b : ns -> normalize (text (a <> b) <> ns)
-   Text' a : ns -> Text' a : normalize ns
-   Element' t as cs : ns -> Element' t as (normalize cs) : normalize ns
+   Node_Text "" : ns -> normalize ns
+   Node_Text a : Node_Text b : ns -> normalize (textLazy (a <> b) <> ns)
+   Node_Text a : ns -> Node_Text a : normalize ns
+   Node_Element t as cs : ns -> Node_Element t as (normalize cs) : normalize ns
    [] -> []
 
--- | Construct a XML fragment body containing a single 'Text' 'Node', if
--- possible.
+-- | Construct a XML fragment body containing a single text 'Node', if given
+-- 'T.Text' not empty.
 --
 -- This function will return empty list if it is not possible to construct the
 -- 'Text' with the given input. To learn more about /why/ it was not possible to
@@ -156,25 +226,38 @@
 -- times more convenient to use. For example, when you know statically the input
 -- is valid.
 text
-  :: TL.Text  -- ^ Lazy 'TL.Text'.
+  :: T.Text  -- ^ Strict 'T.Text'.
   -> [Node]
 {-# INLINE text #-}
-text t = case text' t of
-  Right x -> [x]
-  Left _  -> []
+text = textLazy . TL.fromStrict
 
--- | Construct a 'Text' 'Node', if possible.
+-- | Construct a text 'Node', if given 'T.Text' not empty.
 --
 -- Returns 'Left' if the 'Text' 'Node' can't be created, with an explanation
 -- of why.
 text'
-  :: TL.Text  -- ^ Lazy 'TL.Text'.
+  :: T.Text  -- ^ Strict 'T.Text'.
   -> Either String Node
 {-# INLINE text' #-}
-text' = \case
+text' = textLazy' . TL.fromStrict
+
+-- | A version of 'text' working with lazy 'TL.Text'.
+textLazy
+  :: TL.Text  -- ^ Lazy 'TL.Text'.
+  -> [Node]
+{-# INLINE textLazy #-}
+textLazy = either (\_ -> []) (\x -> [x]) . textLazy'
+
+-- | A version of 'text'' working with lazy 'TL.Text'.
+textLazy'
+  :: TL.Text  -- ^ Lazy 'TL.Text'.
+  -> Either String Node
+{-# INLINE textLazy' #-}
+textLazy' = \case
   "" -> Left "Empty text"
-  t  -> Right (Text' t)
+  t  -> Right (Node_Text t)
 
+
 -- | Construct a XML fragment body containing a single 'Element' 'Node', if
 -- possible.
 --
@@ -186,8 +269,10 @@
 -- to acknowledge a failing situation in case it happens. However, 'element' is
 -- at times more convenient to use, whenever you know the input is valid.
 element
-  :: T.Text                   -- ^ Element' name as a strict 'T.Text'.
-  -> HM.HashMap T.Text T.Text -- ^ Attributes as strict 'T.Text' pairs.
+  :: T.Text                   -- ^ Element name as a strict 'T.Text'.
+  -> HM.HashMap T.Text T.Text
+  -- ^ Pairs of attribute names and possibly 'T.empty' values, as strict
+  -- 'T.Text'.
   -> [Node]                   -- ^ Children.
   -> [Node]
 {-# INLINE element #-}
@@ -200,21 +285,21 @@
 -- Returns 'Left' if the 'Element' 'Node' can't be created, with an explanation
 -- of why.
 element'
-  :: T.Text                   -- ^ Element' name as a strict 'T.Text'.
-  -> HM.HashMap T.Text T.Text -- ^ Attributes as strict 'T.Text' pairs.
+  :: T.Text                   -- ^ Element name as a strict 'T.Text'.
+  -> HM.HashMap T.Text T.Text
+  -- ^ Pairs of attribute names and possibly 'T.empty' values, as strict
+  -- 'T.Text'.
   -> [Node]                   -- ^ Children.
   -> Either String Node
 element' t0 hm0 ns0 = do
   when (t0 /= T.strip t0)
      (Left ("Element name has surrounding whitespace: " ++ show t0))
-  when (T.null t0)
-     (Left ("Element name is blank: " ++ show t0))
+  when (T.null t0) (Left "Element name is blank")
   for_ (HM.keys hm0) $ \k -> do
      when (k /= T.strip k)
         (Left ("Attribute name has surrounding whitespace: " ++ show k))
-     when (T.null k)
-        (Left ("Attribute name is blank: " ++ show k))
-  Right (Element' t0 hm0 (normalize ns0))
+     when (T.null k) (Left "Attribute name is blank")
+  Right (Node_Element t0 hm0 (normalize ns0))
 
 --------------------------------------------------------------------------------
 --------------------------------------------------------------------------------
@@ -226,54 +311,116 @@
   -- If a 'ToXml' instance for @a@ exists, then:
   --
   -- @
-  -- 'runParser' 'fromXml' ('toXml' a) == pure ('Right' a)
+  -- 'parse' 'fromXml' ('toXml' a)  ==  'Right' a
   -- @
-  fromXml :: Parser a
+  fromXml :: Monad m => ParserT m a
 
 -- | Internal parser state.
-data S
+data ParserState
   = STop ![Node]
     -- ^ Parsing the top-level nodes.
   | SReg !T.Text !(HM.HashMap T.Text T.Text) ![Node]
     -- ^ Parsing a particular root element.
+  deriving (Eq)
 
--- | Construct an initial parser state to use with 'unParser' from zero or
+-- | Construct an initial 'ParserState' to use with 'runParserT' from zero or
 -- more top-level 'Node's.
-initialS :: [Node] -> S
-initialS = STop . normalize
-{-# INLINE initialS #-}
+initialParserState :: [Node] -> ParserState
+initialParserState = STop . normalize
+{-# INLINE initialParserState #-}
 
 -- | XML parser for a value of type @a@.
 --
--- You can build a 'Parser' using 'pElement', 'pAnyElement', 'pName',
+-- This parser runs on top of some 'Monad' @m@,
+-- making 'ParserT' a suitable monad transformer.
+--
+-- You can build a 'ParserT' using 'pElement', 'pAnyElement', 'pName',
 -- 'pAttr', 'pAttrs', 'pChildren', 'pText', 'pEndOfInput', any of the
--- 'Applicative', 'Alternative', 'Monad' or related combinators.
+-- 'Applicative', 'Alternative' or 'Monad' combinators, or you can
+-- use 'parserT' directly.
 --
--- Run a 'Parser' using 'runParser'.
-newtype Parser a = Parser { unParser :: S -> Either String (S, a) }
+-- Run a 'ParserT' using 'parse', 'parseM' or 'runParserT'
+newtype ParserT (m :: Type -> Type) (a :: Type)
+  = ParserT (ParserState -> m (ParserState, Either String a))
 
--- | Run a 'Parser' on an XML fragment body.
+-- | @'Parser' a@ is a type synonym for @'Parser' 'Identity' a@.
+type Parser = ParserT Identity :: Type -> Type
+
+-- | 'parserT' is the most general way or building a 'ParserT'.
 --
+-- Notice that 'ParserState''s internals are not exported, so you won't be
+-- able to do much with it other than pass it around.
+--
+-- @
+-- 'runParserT' . 'parserT'  ==  'id'
+-- @
+parserT
+  :: (ParserState -> m (ParserState, Either String a))
+  -- ^ Given a parser's internal state, obtain an @a@ if possible, otherwise
+  -- return a 'String' describing the parsing failure. A new state with
+  -- leftovers is returned.
+  -> ParserT m a
+parserT = ParserT
+{-# INLINE parserT #-}
+
+-- | 'runParserT' is the most general way or running a 'ParserT'.
+--
+-- As a simpler alternative to 'runParserT', consider using 'parseM', or even 'parse'
+-- if you don't need transformer functionality.
+--
+-- Notice that 'ParserState''s internals are not exported, so you won't be
+-- able to do much with it other than pass it around.
+--
+-- @
+-- 'runParserT' . 'parserT'  ==  'id'
+-- @
+runParserT
+  :: ParserT m a
+  -- ^ Parser to run.
+  -> ParserState
+  -- ^ Initial parser state. You can obtain this from
+  -- 'initialParserState' or from a previous execution of 'runParserT'.
+  -> m (ParserState, Either String a)
+  -- ^ Returns the leftover parser state, as well as an @a@ in case parsing was
+  -- successful, or a 'String' with an error message otherwise.
+runParserT (ParserT f) = f
+{-# INLINE runParserT #-}
+
+-- | Run a 'ParserT' on an XML fragment body.
+--
 -- Notice that this function doesn't enforce that all input is consumed. If you
--- want that behavior, then please use 'pEndOfInput' in the given 'Parser'.
-runParser
+-- want that behavior, then please use 'pEndOfInput' in the given 'ParserT'.
+parseM
+  :: Applicative m
+  => ParserT m a
+  -- ^ Parser to run.
+  -> [Node]
+  -- ^ XML fragment body to parse. That is, top-level XML 'Node's.
+  -> m (Either String a)
+  -- ^ If parsing fails, a 'String' with an error message is returned.
+  -- Otherwise, the parser output @a@ is returned.
+parseM p = fmap snd . runParserT p . initialParserState
+{-# INLINE parseM #-}
+
+-- | Pure version of 'parseM'.
+parse
   :: Parser a
   -- ^ Parser to run.
   -> [Node]
   -- ^ XML fragment body to parse. That is, top-level XML 'Node's.
   -> Either String a
   -- ^ If parsing fails, a 'String' with an error message is returned.
-  -- Otherwise, we the parser output @a@ is returned.
-runParser p = fmap snd . unParser p . initialS
-{-# INLINE runParser #-}
+  -- Otherwise, the parser output @a@ is returned.
+parse p = runIdentity . parseM p
+{-# INLINE parse #-}
 
 #if MIN_VERSION_base(4,9,0)
-instance Semigroup a => Semigroup (Parser a) where
+instance (Monad m, Semigroup a) => Semigroup (ParserT m a) where
   (<>) = liftA2 (<>)
   {-# INLINE (<>) #-}
 #endif
 
-instance Monoid a => Monoid (Parser a) where
+instance (Monad m, Monoid a) => Monoid (ParserT m a) where
   mempty = pure mempty
   {-# INLINE mempty #-}
 #if MIN_VERSION_base(4,9,0)
@@ -283,83 +430,173 @@
 #endif
   {-# INLINE mappend #-}
 
-instance Functor Parser where
-  fmap f = \pa -> Parser (\s -> fmap (fmap f) (unParser pa s))
+instance Functor m => Functor (ParserT m) where
+  fmap f = \pa -> ParserT (\s -> fmap (fmap (fmap f)) (runParserT pa s))
   {-# INLINE fmap #-}
 
-instance Applicative Parser where
-  pure = \a -> Parser (\s -> Right (s, a))
+-- | The 'Monad' superclass is necessary because 'ParserT' shortcircuits like
+-- 'Control.Monad.Trans.Except.ExceptT'.
+instance Monad m => Applicative (ParserT m) where
+  pure = \a -> ParserT (\s -> pure (s, Right a))
   {-# INLINE pure #-}
-  pf <*> pa = Parser (\s0 -> do
-    (s1, f) <- unParser pf s0
-    (s2, a) <- unParser pa s1
-    Right (s2, f a))
-  {-# INLINABLE (<*>) #-}
+  (<*>) = ap
+  {-# INLINE (<*>) #-}
 
 -- | @ma '<|>' mb@ backtracks the internal parser state before running @mb@.
-instance Alternative Parser where
+instance Monad m => Alternative (ParserT m) where
   empty = pFail "empty"
   {-# INLINE empty #-}
-  pa <|> pb = Parser (\s0 ->
-    case unParser pa s0 of
-      Right (s1, a) -> Right (s1, a)
-      Left _ -> unParser pb s0)
+  pa <|> pb = ParserT (\s0 -> do
+    (s1, ea) <- runParserT pa s0
+    case ea of
+      Right a -> pure (s1, Right a)
+      Left _ -> runParserT pb s0)
   {-# INLINABLE (<|>) #-}
 
-instance Selective Parser where
-  select pe pf = Parser (\s0 -> do
-    (s1, ea) <- unParser pe s0
-    case ea of
-      Right b -> Right (s1, b)
-      Left a -> do
-        (s2, f) <- unParser pf s1
-        Right (s2, f a))
+instance Monad m => Selective (ParserT m) where
+  select pe pf = ParserT (\s0 -> do
+    (s1, eeab) <- runParserT pe s0
+    case eeab of
+      Right (Right b) -> pure (s1, Right b)
+      Right (Left a) -> runParserT (pf <*> pure a) s1
+      Left msg -> pure (s1, Left msg))
   {-# INLINABLE select #-}
 
-instance Monad Parser where
+instance Monad m => Monad (ParserT m) where
   return = pure
   {-# INLINE return #-}
-  pa >>= kpb = Parser (\s0 -> do
-    (s1, a) <- unParser pa s0
-    unParser (kpb a) s1)
+  pa >>= kpb = ParserT (\s0 -> do
+    (s1, ea) <- runParserT pa s0
+    case ea of
+      Right a -> runParserT (kpb a) s1
+      Left msg -> pure (s1, Left msg))
   {-# INLINABLE (>>=) #-}
-#if !MIN_VERSION_base(4,13,0)
+#if !MIN_VERSION_base(4,9,0)
   fail = pFail
   {-# INLINE fail #-}
 #endif
 
 #if MIN_VERSION_base(4,9,0)
-instance Control.Monad.Fail.MonadFail Parser where
+instance Monad m => Control.Monad.Fail.MonadFail (ParserT m) where
   fail = pFail
   {-# INLINE fail #-}
 #endif
 
--- | A 'Parser' that always fails with the given error message.
-pFail :: String -> Parser a
-pFail = \msg -> Parser (\_ -> Left msg)
+-- | A 'ParserT' that always fails with the given error message.
+pFail :: Applicative m => String -> ParserT m a
+pFail = \msg -> ParserT (\s -> pure (s, Left msg))
 {-# INLINE pFail #-}
 
 -- | @'mplus' ma mb@ backtracks the internal parser state before running @mb@.
-instance MonadPlus Parser where
+instance Monad m => MonadPlus (ParserT m) where
   mzero = empty
   {-# INLINE mzero #-}
   mplus = (<|>)
   {-# INLINE mplus #-}
 
-instance MonadFix Parser where
+instance MonadFix m => MonadFix (ParserT m) where
   mfix f =
-    let die = \msg -> error ("mfix (Parser): " <> msg)
-    in Parser (\s0 -> fix (flip unParser s0 . f . either die snd))
-  {-# INLINABLE mfix #-}
+    let die = \msg -> error ("mfix (ParserT): " <> msg)
+    in ParserT (\s0 ->
+         mfix (\ ~(_s1, ea) -> runParserT (f (either die id ea)) s0))
 
-instance MonadZip Parser where
-  mzipWith = liftA2
-  {-# INLINE mzipWith #-}
+instance MonadZip m => MonadZip (ParserT m) where
+  mzipWith f pa pb = ParserT (\s0 -> do
+    (s1, ea) <- runParserT pa s0
+    case ea of
+      Right a0 ->
+        mzipWith (\a1 (s2, eb) -> (s2, fmap (f a1) eb))
+                 (pure a0) (runParserT pb s1)
+      Left msg -> pure (s1, Left msg))
+  {-# INLINABLE mzipWith #-}
 
+instance MonadTrans ParserT where
+  lift = \ma -> ParserT (\s -> ma >>= \a -> pure (s, Right a))
+  {-# INLINE lift #-}
+
+instance MFunctor ParserT where
+  hoist nat = \p -> ParserT (\s -> nat (runParserT p s))
+  {-# INLINE hoist #-}
+
+instance MonadIO m => MonadIO (ParserT m) where
+  liftIO = lift . liftIO
+  {-# INLINE liftIO #-}
+
+instance MonadReader r m => MonadReader r (ParserT m) where
+  ask = lift ask
+  {-# INLINE ask #-}
+  local f = \p -> ParserT (\s -> local f (runParserT p s))
+  {-# INLINE local #-}
+
+instance MonadState s m => MonadState s (ParserT m) where
+  state = lift . state
+  {-# INLINE state #-}
+
+instance MonadError e m => MonadError e (ParserT m) where
+  throwError = lift . throwError
+  {-# INLINABLE throwError #-}
+  catchError ma h = ParserT (\s ->
+    catchError (runParserT ma s)
+               (\e -> runParserT (h e) s))
+  {-# INLINABLE catchError #-}
+
+instance Ex.MonadThrow m => Ex.MonadThrow (ParserT m) where
+  throwM = lift . Ex.throwM
+  {-# INLINABLE throwM #-}
+
+instance Ex.MonadCatch m => Ex.MonadCatch (ParserT m) where
+  catch ma h = ParserT (\s ->
+    Ex.catch (runParserT ma s)
+             (\e -> runParserT (h e) s))
+  {-# INLINABLE catch #-}
+
+instance Ex.MonadMask m => Ex.MonadMask (ParserT m) where
+  mask f = ParserT (\s ->
+    Ex.mask (\u ->
+      runParserT (f (\p -> ParserT (u . runParserT p))) s))
+  {-# INLINABLE mask #-}
+  uninterruptibleMask f = ParserT (\s ->
+    Ex.uninterruptibleMask (\u ->
+      runParserT (f (\p -> ParserT (u . runParserT p))) s))
+  {-# INLINABLE uninterruptibleMask #-}
+  generalBracket acq rel use = ParserT (\s0 -> do
+    ((_sb,eb), (sc,ec)) <- Ex.generalBracket
+      (runParserT acq s0)
+      (\(s1, ea) ec -> case ea of
+          Right a -> case ec of
+            Ex.ExitCaseSuccess (s2, Right b) ->
+              runParserT (rel a (Ex.ExitCaseSuccess b)) s2
+            Ex.ExitCaseSuccess (s2, Left msg) ->
+              -- Result of using mzero or similar on release
+              pure (s2, Left msg)
+            Ex.ExitCaseException e ->
+              runParserT (rel a (Ex.ExitCaseException e)) s1
+            Ex.ExitCaseAbort ->
+              runParserT (rel a Ex.ExitCaseAbort) s1
+          Left msg ->
+            -- acq failed, nothing to release
+            pure (s1, Left msg))
+      (\(s1, ea) -> case ea of
+          Right a -> runParserT (use a) s1
+          Left msg ->
+            -- acq failed, nothing to use
+            pure (s1, Left msg))
+    -- We run ec first because its error message, if any, has priority
+    pure (sc, flip (,) <$> ec <*> eb))
+
+{- TODO: Should we export this?
+-- | This version uses the current state on entering the continuation. See
+-- 'liftCallCC''.  It does not satisfy the uniformity property (see
+-- 'Control.Monad.Signatures.CallCC').
+instance MonadCont m => MonadCont (ParserT m) where
+  callCC f = ParserT (\s0 ->
+    callCC (\c -> runParserT (f (\a -> ParserT (\s1 -> c (s1, Right a)))) s0))
+-}
+
 --------------------------------------------------------------------------------
 -- Some parsers
 
--- | @'pElement' "foo" p@ runs a 'Parser @p@ inside a 'Element' node named
+-- | @'pElement' "foo" p@ runs a 'ParserT' @p@ inside a 'Element' node named
 -- @"foo"@. This parser __fails__ if such element does not exist at the current
 -- position.
 --
@@ -368,32 +605,33 @@
 --
 -- __Consumes the matched element__ from the parser state.
 pElement
-  :: T.Text    -- ^ Element name as strict 'T.Text'.
-  -> Parser a  -- ^ 'Parser' to run /inside/ the matched 'Element'.
-  -> Parser a
-pElement t0 p0 = Parser $ \case
-  SReg t1 as0 (Element' t as cs : cs0) | t == t0 ->
-    case unParser p0 (SReg t as cs) of
-      Right (_, a) -> Right (SReg t1 as0 cs0, a)
-      Left msg -> Left msg
-  STop (Element' t as cs : cs0) | t == t0 ->
-    case unParser p0 (SReg t as cs) of
-      Right (_, a) -> Right (STop cs0, a)
-      Left msg -> Left msg
+  :: Monad m
+  => T.Text       -- ^ Element name as strict 'T.Text'.
+  -> ParserT m a  -- ^ 'ParserT' to run /inside/ the matched 'Element'.
+  -> ParserT m a
+pElement t0 p0 = ParserT $ \case
+  SReg t1 as0 (Node_Element t as cs : cs0) | t == t0 ->
+    runParserT p0 (SReg t as cs) >>= \case
+      (_, Right a) -> pure (SReg t1 as0 cs0, Right a)
+      (s1, Left msg) -> pure (s1, Left msg)
+  STop (Node_Element t as cs : cs0) | t == t0 ->
+    runParserT p0 (SReg t as cs) >>= \case
+      (_, Right a) -> pure (STop cs0, Right a)
+      (s1, Left msg) -> pure (s1, Left msg)
   -- skip leading whitespace
-  SReg t as (Text' x : cs) | TL.all Char.isSpace x ->
-    unParser (pElement t0 p0) (SReg t as cs)
-  STop (Text' x : cs) | TL.all Char.isSpace x ->
-    unParser (pElement t0 p0) (STop cs)
-  _ -> Left ("Missing element " <> show t0)
+  SReg t as (Node_Text x : cs) | TL.all Char.isSpace x ->
+    runParserT (pElement t0 p0) (SReg t as cs)
+  STop (Node_Text x : cs) | TL.all Char.isSpace x ->
+    runParserT (pElement t0 p0) (STop cs)
+  s0 -> pure (s0, Left ("Missing element " <> show t0))
 {-# INLINABLE pElement #-}
 
--- | @'pAnyElement' p@ runs a 'Parser' @p@ inside the 'Element' node at the
+-- | @'pAnyElement' p@ runs a 'ParserT' @p@ inside the 'Element' node at the
 -- current position, if any. Otherwise, if no such element exists, this parser
 -- __fails__.
 --
 -- You can recover the name of the matched element using 'pName' inside the
--- given 'Parser'. However, if you already know beforehand the name of the
+-- given 'ParserT'. However, if you already know beforehand the name of the
 -- element that you want to match, it's better to use 'pElement' rather than
 -- 'pAnyElement'.
 --
@@ -402,23 +640,24 @@
 --
 -- __Consumes the matched element__ from the parser state.
 pAnyElement
-  :: Parser a  -- ^ 'Parser' to run /inside/ any matched 'Element'.
-  -> Parser a
-pAnyElement p0 = Parser $ \case
-  SReg t0 as0 (Element' t as cs : cs0) ->
-    case unParser p0 (SReg t as cs) of
-      Right (_, a) -> Right (SReg t0 as0 cs0, a)
-      Left msg -> Left msg
-  STop (Element' t as cs : cs0) ->
-    case unParser p0 (SReg t as cs) of
-      Right (_, a) -> Right (STop cs0, a)
-      Left msg -> Left msg
+  :: Monad m
+  => ParserT m a  -- ^ 'ParserT' to run /inside/ any matched 'Element'.
+  -> ParserT m a
+pAnyElement p0 = ParserT $ \case
+  SReg t0 as0 (Node_Element t as cs : cs0) ->
+    runParserT p0 (SReg t as cs) >>= \case
+      (_, Right a) -> pure (SReg t0 as0 cs0, Right a)
+      (s1, Left msg) -> pure (s1, Left msg)
+  STop (Node_Element t as cs : cs0) ->
+    runParserT p0 (SReg t as cs) >>= \case
+      (_, Right a) -> pure (STop cs0, Right a)
+      (s1, Left msg) -> pure (s1, Left msg)
   -- skip leading whitespace
-  SReg t as (Text' x : cs) | TL.all Char.isSpace x ->
-    unParser (pAnyElement p0) (SReg t as cs)
-  STop (Text' x : cs) | TL.all Char.isSpace x ->
-    unParser (pAnyElement p0) (STop cs)
-  _ -> Left "Missing element"
+  SReg t as (Node_Text x : cs) | TL.all Char.isSpace x ->
+    runParserT (pAnyElement p0) (SReg t as cs)
+  STop (Node_Text x : cs) | TL.all Char.isSpace x ->
+    runParserT (pAnyElement p0) (STop cs)
+  s0 -> pure (s0, Left "Missing element")
 {-# INLINABLE pAnyElement #-}
 
 -- | Returns the name of the currently selected 'Element'.
@@ -427,48 +666,52 @@
 -- 'pElement', 'pAnyElement').
 --
 -- Doesn't modify the parser state.
-pName :: Parser T.Text -- ^ Element name as strict 'T.Text'.
-pName = Parser (\case
-  s@(SReg t _ _) -> Right (s, t)
-  _ -> Left "Before selecting an name, you must select an element")
+pName
+  :: Applicative m
+  => ParserT m T.Text -- ^ Element name as strict 'T.Text'.
+pName = ParserT (\s -> case s of
+  SReg t _ _ -> pure (s, Right t)
+  _ -> pure (s, Left "Before selecting an name, you must select an element"))
 {-# INLINABLE pName #-}
 
--- | Return the value of the requested attribute, if defined. Returns an
--- 'T.empty' 'T.Text' in case the attribute is defined but no value was given to
--- it.
+-- | Return the value of the requested attribute, if defined, as strict
+-- 'T.Text'. Returns an 'T.empty' strict 'T.Text' in case the attribute is
+-- defined but no value was given to it.
 --
 -- This parser __fails__ if there's no currently selected 'Element' (see
 -- 'pElement', 'pAnyElement').
 --
 -- __Consumes the matched attribute__ from the parser state.
 pAttr
-  :: T.Text
+  :: Applicative m
+  => T.Text
   -- ^ Attribute name as strict 'T.Text'.
-  -> Parser T.Text
+  -> ParserT m T.Text
   -- ^ Attribute value as strict 'T.Text', possibly 'T.empty'.
-pAttr n = Parser (\case
+pAttr n = ParserT (\s -> case s of
   SReg t as cs -> case HM.lookup n as of
-    Just x -> Right (SReg t (HM.delete n as) cs, x)
-    Nothing -> Left ("Missing attribute " <> show n)
-  _ -> Left "Before selecting an attribute, you must select an element")
+    Just x -> pure (SReg t (HM.delete n as) cs, Right x)
+    Nothing -> pure (s, Left ("Missing attribute " <> show n))
+  _ -> pure (s, Left "Before selecting an attribute, you must select an element"))
 {-# INLINABLE pAttr #-}
 
 -- | Returns all of the available element attributes.
 --
--- Returns 'T.empty' 'T.Text' as values in case an attribute is defined but no
--- value was given to it.
+-- Returns 'T.empty' strict 'T.Text' as values in case an attribute is defined
+-- but no value was given to it.
 --
 -- This parser __fails__ if there's no currently selected 'Element' (see
 -- 'pElement', 'pAnyElement').
 --
 -- __Consumes all the attributes__ for this element from the parser state.
 pAttrs
-  :: Parser (HM.HashMap T.Text T.Text)
+  :: Applicative m
+  => ParserT m (HM.HashMap T.Text T.Text)
   -- ^ Pairs of attribute names and possibly 'T.empty' values, as strict
   -- 'T.Text'.
-pAttrs = Parser (\case
-  SReg t as cs -> Right (SReg t mempty cs, as)
-  _ -> Left "Before selecting an attribute, you must select an element")
+pAttrs = ParserT (\s -> case s of
+  SReg t as cs -> pure (SReg t mempty cs, Right as)
+  _ -> pure (s, Left "Before selecting an attribute, you must select an element"))
 {-# INLINABLE pAttrs #-}
 
 -- | Returns all of the immediate children of the current element.
@@ -479,56 +722,64 @@
 --
 -- __Consumes all the returned nodes__ from the parser state.
 pChildren
-  :: Parser [Node]
-  -- ^ 'Node's in their original order.
-pChildren = Parser (\case
-  STop cs -> Right (STop mempty, cs)
-  SReg t as cs -> Right (SReg t as mempty, cs))
+  :: Applicative m
+  => ParserT m [Node] -- ^ 'Node's in their original order.
+pChildren = ParserT (\case
+  STop cs -> pure (STop mempty, Right cs)
+  SReg t as cs -> pure (SReg t as mempty, Right cs))
 {-# INLINABLE pChildren #-}
 
--- | Returns the contents of a 'Text' node.
+-- | Returns the contents of a text node as a strict 'TL.Text'.
 --
 -- Surrounidng whitespace is not removed, as it is considered to be part of the
 -- text node.
 --
--- If there is no text node at the current position, then this parser __fails__.
--- This implies that 'pText' /never/ returns an empty 'TL.Text', since there is
--- no such thing as a text node without text.
+-- If there is no text node at the current position, then this parser
+-- __fails__.  This implies that 'pText' /never/ returns an empty strict
+-- 'T.Text', since there is no such thing as a text node without text.
 --
 -- Please note that consecutive text nodes are always concatenated and returned
 -- together.
 --
 -- @
--- 'runParser' 'pText' ('text' \"Ha\" <> 'text' \"sk\" <> 'text' \"ell\")
---     == 'Right' ('text' \"Haskell\")
+-- 'parse' 'pText' ('text' \"Ha\" <> 'text' \"sk\" <> 'text' \"ell\")
+--     == 'Right' ('text' "Haskell")
 -- @
 --
 -- __Consumes the text__ from the parser state. This implies that if you
 -- perform two consecutive 'pText' calls, the second will always fail.
 --
 -- @
--- 'runParser' ('pText' >> 'pText') ('text' \"Ha\" <> 'text' \"sk\" <> 'text' \"ell\")
---     == 'Left' \"Missing text node\"
+-- 'parse' ('pText' >> 'pText') ('text' \"Ha\" <> 'text' \"sk\" <> 'text' \"ell\")
+--     == 'Left' "Missing text node"
 -- @
 pText
-  :: Parser TL.Text
+  :: Applicative m
+  => ParserT m T.Text
+  -- ^ Content of the text node as a strict 'T.Text'.
+pText = fmap TL.toStrict pTextLazy
+
+-- | Like 'pText', but returns a lazy 'TL.Text'.
+pTextLazy
+  :: Applicative m
+  => ParserT m TL.Text
   -- ^ Content of the text node as a lazy 'TL.Text'.
-pText = Parser (\case
+pTextLazy = ParserT (\case
   -- Note: this works only because we asume 'normalize' has been used.
-  STop (Text x : ns) -> Right (STop ns, x)
-  SReg t as (Text x : cs) -> Right (SReg t as cs, x)
-  _ -> Left "Missing text node")
+  STop (Node_Text x : ns) -> pure (STop ns, Right x)
+  SReg t as (Node_Text x : cs) -> pure (SReg t as cs, Right x)
+  s0 -> pure (s0, Left "Missing text node"))
 {-# INLINABLE pText #-}
 
 -- | Succeeds if all of the elements, attributes and text nodes have
 -- been consumed.
-pEndOfInput :: Parser ()
-pEndOfInput = Parser (\s -> case isEof s of
-  True -> Right (s, ())
-  False -> Left "Not end of input yet")
+pEndOfInput :: Applicative m => ParserT m ()
+pEndOfInput = ParserT (\s -> case isEof s of
+  True -> pure (s, Right ())
+  False -> pure (s, Left "Not end of input yet"))
 {-# INLINABLE pEndOfInput #-}
 
-isEof :: S -> Bool
+isEof :: ParserState -> Bool
 isEof = \case
   SReg _ as cs -> HM.null as && null cs
   STop ns -> null ns
@@ -543,7 +794,7 @@
   -- If a 'FromXml' instance for @a@ exists, then:
   --
   -- @
-  -- 'runParser' 'fromXml' ('toXml' a) == 'Right' a
+  -- 'parse' 'fromXml' ('toXml' a)  ==  'Right' a
   -- @
   toXml :: a -> [Node]
 
@@ -562,20 +813,19 @@
   where
     encodeNode :: Node -> BB.Builder
     encodeNode = \case
-      Text x -> encodeXmlUtf8Lazy x
-      Element t as cs ->
+      Node_Text x -> encodeXmlUtf8Lazy x
+      Node_Element t as cs ->
          -- This ugly code is so that we make sure we always bind concatenation
          -- to the right with as little effort as possible, using (<>).
          "<" <> encodeUtf8 t
-             <> encodeAttrs (">" <> encode cs <> "</" <> encodeUtf8 t <> ">")
-                            (mapFromHashMap as)
+             <> encodeAttrs (">" <> encode cs <> "</" <> encodeUtf8 t <> ">") as
     {-# INLINE encodeNode #-}
-    encodeAttrs :: BB.Builder -> Map.Map T.Text T.Text -> BB.Builder
-    encodeAttrs = Map.foldrWithKey
+    encodeAttrs :: BB.Builder -> HM.HashMap T.Text T.Text -> BB.Builder
+    encodeAttrs b as = Map.foldrWithKey'
       (\k v o -> " " <> encodeUtf8 k <> "=\"" <> encodeXmlUtf8 v <> "\"" <> o)
+      b (mapFromHashMap as)
     {-# INLINE encodeAttrs #-}
 
-
 --------------------------------------------------------------------------------
 --------------------------------------------------------------------------------
 -- Node fixpoint
@@ -649,7 +899,7 @@
 -- | Zipper into a 'Node' tree.
 data Cursor = Cursor
   { _cursorCurrent :: !Node
-    -- ^ Retrieves the current node of a 'Cursor'.
+    -- ^ Retrieves the current node of a @Cursor@.
   , _cursorLefts :: !(Seq Node)
     -- ^ Nodes to the left (ordered right to left).
   , _cursorRights :: !(Seq Node)
@@ -664,10 +914,10 @@
 traverseChildren :: Monad m => (Node -> m [Node]) -> Cursor -> m Cursor
 {-# INLINABLE traverseChildren #-}
 traverseChildren f c0 = case _cursorCurrent c0 of
-  Text _ -> pure c0
-  Element t as cs -> do
+  Node_Text _ -> pure c0
+  Node_Element t as cs -> do
      n1s <- fmap (normalize . join) (traverse f cs)
-     pure (c0 {_cursorCurrent = Element' t as n1s})
+     pure (c0 {_cursorCurrent = Node_Element t as n1s})
 
 -- | The cursor if left in the rightmost sibling.
 traverseRightSiblings :: Monad m => (Node -> m [Node]) -> Cursor -> m Cursor
@@ -678,7 +928,7 @@
       n2s <- fmap normalize (f n1)
       traverseRightSiblings f (cursorInsertManyRight n2s c1)
 
--- | Builds a 'Cursor' for navigating a tree. That is, a forest with a single
+-- | Builds a @Cursor@ for navigating a tree. That is, a forest with a single
 -- root 'Node'.
 cursorFromNode :: Node -> Cursor
 {-# INLINE cursorFromNode #-}
@@ -747,4 +997,3 @@
 mapFromHashMap = HM.foldrWithKey' Map.insert Map.empty
 {-# INLINE mapFromHashMap #-}
 
---}
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -3,15 +3,17 @@
 {-# LANGUAGE OverloadedLists #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
+
 module Main where
 
 import Control.Applicative (liftA2, (<|>))
 import Control.Monad (mplus)
+import Control.Monad.Trans.Class
 import qualified Control.Monad.Trans.State as S
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Builder as BB
 import qualified Data.ByteString.Lazy as BL
-import Data.Either (isLeft)
 import qualified Data.Text as T
 import qualified Test.Tasty as Tasty
 import qualified Test.Tasty.Runners as Tasty
@@ -58,22 +60,28 @@
 tt_element :: Tasty.TestTree
 tt_element = Tasty.testGroup "element"
   [ HU.testCase "empty name" $ do
-      HU.assert (isLeft (X.element' "" [] []))
+      X.element' "" [] []
+        @?= Left "Element name is blank"
 
   , HU.testCase "name with leading whitespace" $ do
-      HU.assert (isLeft (X.element' " x" [] []))
+      X.element' " x" [] []
+        @?= Left "Element name has surrounding whitespace: \" x\""
 
   , HU.testCase "name with trailing whitespace" $ do
-      HU.assert (isLeft (X.element' "x " [] []))
+      X.element' "x " [] []
+        @?= Left "Element name has surrounding whitespace: \"x \""
 
   , HU.testCase "empty attribute" $ do
-      HU.assert (isLeft (X.element' "x" [("","a")] []))
+      X.element' "x" [("","a")] []
+        @?= Left "Attribute name is blank"
 
   , HU.testCase "attribute with leading whitespace" $ do
-      HU.assert (isLeft (X.element' "x" [(" x","a")] []))
+      X.element' "x" [(" x","a")] []
+        @?= Left "Attribute name has surrounding whitespace: \" x\""
 
   , HU.testCase "attribute with trailing whitespace" $ do
-      HU.assert (isLeft (X.element' "x" [("x ","a")] []))
+      X.element' "x" [("x ","a")] []
+        @?= Left "Attribute name has surrounding whitespace: \"x \""
   ]
 
 tt_encoding :: Tasty.TestTree
@@ -120,83 +128,83 @@
 tt_parsing :: Tasty.TestTree
 tt_parsing = Tasty.testGroup "Parsing"
   [ HU.testCase "endOfInput" $ do
-      Right () @=? X.runParser X.pEndOfInput []
+      Right () @=? X.parse X.pEndOfInput []
 
   , HU.testCase "endOfInput: Not end of input yet" $ do
-      Left "Not end of input yet" @=? X.runParser X.pEndOfInput (X.text "&")
+      Left "Not end of input yet" @=? X.parse X.pEndOfInput (X.text "&")
 
   , HU.testCase "text': empty" $ do
-      Left "Missing text node" @=? X.runParser X.pText []
+      Left "Missing text node" @=? X.parse X.pText []
 
   , HU.testCase "text': blank" $ do
-      Left "Missing text node" @=? X.runParser X.pText (X.text "")
+      Left "Missing text node" @=? X.parse X.pText (X.text "")
 
   , HU.testCase "text': space" $ do
       Right " \t\n" @=?
-         X.runParser X.pText (X.text " " <> X.text "\t" <> X.text "\n")
+         X.parse X.pText (X.text " " <> X.text "\t" <> X.text "\n")
 
   , HU.testCase "text': missing" $ do
-      Left "Missing text node" @=? X.runParser X.pText (X.element "a" [] [])
+      Left "Missing text node" @=? X.parse X.pText (X.element "a" [] [])
 
   , HU.testCase "text'" $ do
-      Right "&" @=? X.runParser X.pText (X.text "&")
+      Right "&" @=? X.parse X.pText (X.text "&")
 
   , HU.testCase "text': concat" $ do
-      Right "&<" @=? X.runParser X.pText
+      Right "&<" @=? X.parse X.pText
          (X.text "&" <> X.text "" <> X.text "<")
 
   , HU.testCase "text': twice" $ do
-      Left "Missing text node" @=? X.runParser (X.pText >> X.pText)
+      Left "Missing text node" @=? X.parse (X.pText >> X.pText)
          (X.text "&" <> X.text "" <> X.text "<")
 
   , HU.testCase "any element: empty" $ do
-      Left "Missing element" @=? X.runParser (X.pAnyElement (pure ())) []
+      Left "Missing element" @=? X.parse (X.pAnyElement (pure ())) []
 
   , HU.testCase "any element: text" $ do
       Left "Missing element"
-        @=? X.runParser (X.pAnyElement (pure ())) (X.text "a")
+        @=? X.parse (X.pAnyElement (pure ())) (X.text "a")
 
   , HU.testCase "any element: pure" $ do
-      Right () @=?  X.runParser (X.pAnyElement (pure ())) (X.element "x" [] [])
+      Right () @=?  X.parse (X.pAnyElement (pure ())) (X.element "x" [] [])
 
   , HU.testCase "any element: name" $ do
       Right "x"
-        @=? X.runParser (X.pAnyElement X.pName) (X.element "x" [] [])
+        @=? X.parse (X.pAnyElement X.pName) (X.element "x" [] [])
 
   , HU.testCase "element: empty" $ do
-      Left "Missing element \"x\"" @=? X.runParser (X.pElement "x" (pure ())) []
+      Left "Missing element \"x\"" @=? X.parse (X.pElement "x" (pure ())) []
 
   , HU.testCase "element: Missing element" $ do
       Left "Missing element \"x\""
-         @=? X.runParser (X.pElement "x" (pure ())) (X.element "y" [] [])
+         @=? X.parse (X.pElement "x" (pure ())) (X.element "y" [] [])
 
   , HU.testCase "element: pure" $ do
       Right ()
-         @=? X.runParser (X.pElement "x" (pure ())) (X.element "x" [] [])
+         @=? X.parse (X.pElement "x" (pure ())) (X.element "x" [] [])
 
   , HU.testCase "element: name" $ do
       Right "x"
-         @=? X.runParser (X.pElement "x" X.pName) (X.element "x" [] [])
+         @=? X.parse (X.pElement "x" X.pName) (X.element "x" [] [])
 
   , HU.testCase "element: leading whitespace" $ do
       Right ()
-         @=? X.runParser (X.pElement "x" (pure ()))
+         @=? X.parse (X.pElement "x" (pure ()))
                          (X.text " \n \t" <> X.element "x" [] [])
 
   , HU.testCase "element: text'" $ do
       Right "ab"
-        @=? X.runParser (X.pElement "x" X.pText)
+        @=? X.parse (X.pElement "x" X.pText)
                 (X.element "x" [] (X.text "a" <> X.text "b"))
 
   , HU.testCase "element: nested" $ do
       Right ([("a","b")], "z")
-        @=? X.runParser
+        @=? X.parse
                 (X.pElement "x" (X.pElement "y" (liftA2 (,) X.pAttrs X.pText)))
                 (X.element "x" [] (X.element "y" [("a","b")] (X.text "z")))
 
   , HU.testCase "element: nested with leading whitespace" $ do
       Right ([("a","b")], "z")
-        @=? X.runParser
+        @=? X.parse
                 (X.pElement "x" (X.pElement "y" (liftA2 (,) X.pAttrs X.pText)))
                 (X.text " " <>
                  X.element "x" [] (X.text " " <>
@@ -204,104 +212,130 @@
 
   , HU.testCase "element: twice" $ do
       Left "Missing element \"x\""
-         @=? X.runParser (X.pElement "x" (pure ()) >> X.pElement "x" (pure ()))
+         @=? X.parse (X.pElement "x" (pure ()) >> X.pElement "x" (pure ()))
                 (X.element "x" [] [])
 
   , HU.testCase "attr" $ do
       Right "a"
-         @=? X.runParser (X.pElement "x" (X.pAttr "y"))
+         @=? X.parse (X.pElement "x" (X.pAttr "y"))
                 (X.element "x" [("y","a"), ("z","b")] [])
 
   , HU.testCase "attr: Missing" $ do
       Left "Missing attribute \"y\""
-         @=? X.runParser (X.pElement "x" (X.pAttr "y")) (X.element "x" [] [])
+         @=? X.parse (X.pElement "x" (X.pAttr "y")) (X.element "x" [] [])
 
   , HU.testCase "attrs: empty" $ do
       Right []
-         @=? X.runParser (X.pElement "x" X.pAttrs) (X.element "x" [] [])
+         @=? X.parse (X.pElement "x" X.pAttrs) (X.element "x" [] [])
 
   , HU.testCase "attrs" $ do
       Right [("y","a"), ("z","b")]
-         @=? X.runParser (X.pElement "x" X.pAttrs)
+         @=? X.parse (X.pElement "x" X.pAttrs)
                 (X.element "x" [("z","b"), ("y","a")] [])
 
   , HU.testCase "attrs: twice" $ do
       Right []
-         @=? X.runParser (X.pElement "x" (X.pAttrs >> X.pAttrs))
+         @=? X.parse (X.pElement "x" (X.pAttrs >> X.pAttrs))
                 (X.element "x" [("z","b"), ("y","a")] [])
 
   , HU.testCase "fail: empty" $ do
       (Left "x" :: Either String ())
-        @=? X.runParser (fail "x") []
+        @=? X.parse (fail "x") []
 
   , HU.testCase "fail" $ do
       (Left "x" :: Either String ())
-        @=? X.runParser (fail "x") (X.text "y")
+        @=? X.parse (fail "x") (X.text "y")
 
   , HU.testCase "children: empty" $ do
       Right []
-         @=? X.runParser (X.pElement "x" X.pChildren) (X.element "x" [] [])
+         @=? X.parse (X.pElement "x" X.pChildren) (X.element "x" [] [])
 
   , HU.testCase "children: top empty" $ do
-      Right [] @=? X.runParser X.pChildren []
+      Right [] @=? X.parse X.pChildren []
 
   , HU.testCase "children: top 1 node" $ do
       Right (X.element "x" [] [])
-         @=? X.runParser X.pChildren (X.element "x" [] [])
+         @=? X.parse X.pChildren (X.element "x" [] [])
 
   , HU.testCase "children: top 1 node twice" $ do
       Right []
-         @=? X.runParser (X.pChildren >> X.pChildren) (X.element "x" [] [])
+         @=? X.parse (X.pChildren >> X.pChildren) (X.element "x" [] [])
 
   , HU.testCase "children: top 2 nodes" $ do
       Right (X.element "x" [] [] <> X.text "ab" <> X.element "y" [] [])
-         @=? X.runParser X.pChildren
+         @=? X.parse X.pChildren
                 (X.element "x" [] [] <> X.text "a" <> X.text "b" <>
                  X.element "y" [] [])
 
   , HU.testCase "children: 1 node" $ do
       Right (X.text "foo")
-         @=? X.runParser (X.pElement "x" X.pChildren)
+         @=? X.parse (X.pElement "x" X.pChildren)
                 (X.element "x" [] (X.text "foo"))
 
   , HU.testCase "children: 1 node twice" $ do
       Right []
-         @=? X.runParser (X.pElement "x" (X.pChildren >> X.pChildren))
+         @=? X.parse (X.pElement "x" (X.pChildren >> X.pChildren))
                 (X.element "x" [] (X.text "foo"))
 
   , HU.testCase "children: 2 successive text' nodes" $ do
       Right (X.text "foobar")
-         @=? X.runParser (X.pElement "x" X.pChildren)
+         @=? X.parse (X.pElement "x" X.pChildren)
                 (X.element "x" [] (X.text "foo" <> X.text "bar"))
 
   , HU.testCase "children: 2 text' nodes twice" $ do
       Right []
-         @=? X.runParser (X.pElement "x" (X.pChildren >> X.pChildren))
+         @=? X.parse (X.pElement "x" (X.pChildren >> X.pChildren))
                 (X.element "x" [] (X.text "foo" <> X.text "bar"))
 
   , HU.testCase "children: 3 nodes" $ do
       let ns0 = X.text "foo" <> X.element "a" [] [] <> X.text "bar"
       Right ns0
-         @=? X.runParser (X.pElement "x" X.pChildren)
-                         (X.element "x" [] ns0)
+         @=? X.parse (X.pElement "x" X.pChildren)
+                     (X.element "x" [] ns0)
 
   , HU.testCase "children: 3 nodes twice" $ do
       Right []
-         @=? X.runParser (X.pElement "x" (X.pChildren >> X.pChildren))
+         @=? X.parse (X.pElement "x" (X.pChildren >> X.pChildren))
                 (X.element "x" []
                    (X.text "foo" <> X.element "a" [] [] <> X.text "bar"))
 
+  , QC.testProperty "parserT (runParserT pAnyElement) == pAnyElement" $ do
+      QC.forAllShrink QC.arbitrary QC.shrink $ \t ->
+         let p0 = X.pAnyElement ((,,) <$> X.pName <*> X.pAttrs <*> X.pChildren)
+             p1 = X.parserT (X.runParserT p0)
+             ns = X.element t [] []
+          in X.parse p0 ns === X.parse p1 ns
+
+  , QC.testProperty "parserT (runParserT pText) == pText" $ do
+      QC.forAllShrink QC.arbitrary QC.shrink $ \t ->
+         let p0 = X.pText
+             p1 = X.parserT (X.runParserT p0)
+             ns = X.text t
+          in X.parse p0 ns === X.parse p1 ns
+
+  , HU.testCase "ParserT StateT" $ do
+      (Right "Haskell", "b")
+         @=? S.runState (X.parseM (do s0 <- lift S.get
+                                      a <- X.pElement s0 X.pText
+                                      lift $ S.put (T.map succ s0)
+                                      s1 <- lift S.get
+                                      b <- X.pElement s1 X.pText
+                                      pure (a <> b))
+                                  (X.element "a" [] (X.text "Has") <>
+                                   X.element "b" [] (X.text "kell")))
+                        "a"
   ]
 
+
 tt_backtracking :: Tasty.TestTree
 tt_backtracking = Tasty.testGroup "Backtracking"
   [ HU.testCase "Alternative" $
-      Right "y" @=? X.runParser
+      Right "y" @=? X.parse
         -- The second pText fails because the state is empty after the first
         ((X.pText >> X.pText >> pure "a") <|> X.pText)
         (X.text "y")
   , HU.testCase "MonadPlus" $
-      Right "y" @=? X.runParser
+      Right "y" @=? X.parse
         -- The second pText fails because the state is empty after the first
         (mplus (X.pText >> X.pText >> pure "a") X.pText)
         (X.text "y")
@@ -323,7 +357,8 @@
 
 fixvisit :: (X.Node -> S.State T.Text [X.Node])
          -> (X.Node -> S.State T.Text [X.Node])
-fixvisit _ n@(X.Element t _ _) = do
+fixvisit _ n = do
+   let X.Element t _ _ = n
    S.modify (\ts -> mappend ts t)
    pure [n]
 
diff --git a/xmlbf.cabal b/xmlbf.cabal
--- a/xmlbf.cabal
+++ b/xmlbf.cabal
@@ -1,5 +1,5 @@
 name: xmlbf
-version: 0.6.2
+version: 0.7
 synopsis: XML back and forth! Parser, renderer, ToXml, FromXml, fixpoints.
 description: XML back and forth! Parser, renderer, ToXml, FromXml, fixpoints.
 homepage: https://gitlab.com/k0001/xmlbf
@@ -29,6 +29,9 @@
     bytestring,
     containers,
     deepseq,
+    exceptions,
+    mmorph,
+    mtl,
     selective,
     text,
     transformers,
