diff --git a/Data/Attoparsec/ByteString/Internal.hs b/Data/Attoparsec/ByteString/Internal.hs
--- a/Data/Attoparsec/ByteString/Internal.hs
+++ b/Data/Attoparsec/ByteString/Internal.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE BangPatterns, OverloadedStrings, RecordWildCards #-}
+{-# LANGUAGE BangPatterns, GADTs, OverloadedStrings, RecordWildCards #-}
 -- |
 -- Module      :  Data.Attoparsec.ByteString.Internal
 -- Copyright   :  Bryan O'Sullivan 2007-2014
@@ -70,6 +70,7 @@
 import Data.Attoparsec.ByteString.Buffer (Buffer, buffer)
 import Data.Attoparsec.ByteString.FastSet (charClass, memberWord8)
 import Data.Attoparsec.Combinator ((<?>))
+import Data.Attoparsec.Internal
 import Data.Attoparsec.Internal.Fhthagn (inlinePerformIO)
 import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
 import Data.ByteString (ByteString)
@@ -467,51 +468,6 @@
 -- Non-recursive so the bounds check can be inlined:
 {-# INLINE ensure #-}
 
--- | Ask for input.  If we receive any, pass it to a success
--- continuation, otherwise to a failure continuation.
-prompt :: Buffer -> Pos -> More
-       -> (Buffer -> Pos -> More -> IResult ByteString r)
-       -> (Buffer -> Pos -> More -> IResult ByteString r)
-       -> IResult ByteString r
-prompt t pos _more lose succ = Partial $ \s ->
-  if B.null s
-  then lose t pos Complete
-  else succ (Buf.pappend t s) pos Incomplete
-
--- | Immediately demand more input via a 'Partial' continuation
--- result.
-demandInput :: Parser ()
-demandInput = T.Parser $ \t pos more lose succ ->
-  case more of
-    Complete -> lose t pos more [] "not enough input"
-    _ -> let lose' t' pos' more' = lose t' pos' more' [] "not enough input"
-             succ' t' pos' more' = succ t' pos' more' ()
-         in prompt t pos more lose' succ'
-
--- | This parser always succeeds.  It returns 'True' if any input is
--- available either immediately or on demand, and 'False' if the end
--- of all input has been reached.
-wantInput :: Parser Bool
-wantInput = T.Parser $ \t pos more _lose succ ->
-  case () of
-    _ | lengthAtLeast pos 1 t -> succ t pos more True
-      | more == Complete -> succ t pos more False
-      | otherwise       -> let lose' t' pos' more' = succ t' pos' more' False
-                               succ' t' pos' more' = succ t' pos' more' True
-                           in prompt t pos more lose' succ'
-{-# INLINE wantInput #-}
-
--- | Match only if all input has been consumed.
-endOfInput :: Parser ()
-endOfInput = T.Parser $ \t pos more lose succ ->
-  case () of
-    _| lengthAtLeast pos 1 t -> lose t pos more [] "endOfInput"
-     | more == Complete -> succ t pos more ()
-     | otherwise ->
-       let lose' t' pos' more' _ctx _msg = succ t' pos' more' ()
-           succ' t' pos' more' _a = lose t' pos' more' [] "endOfInput"
-       in  runParser demandInput t pos more lose' succ'
-
 -- | Return both the result of a parse and the portion of the input
 -- that was consumed while it was being parsed.
 match :: Parser a -> Parser (ByteString, a)
@@ -519,12 +475,6 @@
   let succ' t' pos' more' a =
         succ t' pos' more' (substring pos (pos'-pos) t', a)
   in runParser p t pos more lose succ'
-
--- | Return an indication of whether the end of input has been
--- reached.
-atEnd :: Parser Bool
-atEnd = not <$> wantInput
-{-# INLINE atEnd #-}
 
 lengthAtLeast :: Pos -> Int -> Buffer -> Bool
 lengthAtLeast (Pos pos) n bs = Buf.length bs >= pos + n
diff --git a/Data/Attoparsec/Combinator.hs b/Data/Attoparsec/Combinator.hs
--- a/Data/Attoparsec/Combinator.hs
+++ b/Data/Attoparsec/Combinator.hs
@@ -30,12 +30,16 @@
     , skipMany1
     , eitherP
     , feed
+    , satisfyElem
+    , endOfInput
+    , atEnd
     ) where
 
 import Control.Applicative (Alternative(..), Applicative(..), empty, liftA2,
                             many, (<|>), (*>), (<$>))
 import Control.Monad (MonadPlus(..))
 import Data.Attoparsec.Internal.Types (Parser(..), IResult(..))
+import Data.Attoparsec.Internal (endOfInput, atEnd, satisfyElem)
 import Data.ByteString (ByteString)
 import Data.Monoid (Monoid(mappend))
 import Data.Text (Text)
diff --git a/Data/Attoparsec/Internal.hs b/Data/Attoparsec/Internal.hs
--- a/Data/Attoparsec/Internal.hs
+++ b/Data/Attoparsec/Internal.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}
 -- |
 -- Module      :  Data.Attoparsec.Internal
 -- Copyright   :  Bryan O'Sullivan 2007-2014
@@ -12,10 +12,20 @@
 -- library.
 
 module Data.Attoparsec.Internal
-    (
-      compareResults
+    ( compareResults
+    , prompt
+    , demandInput
+    , wantInput
+    , endOfInput
+    , atEnd
+    , satisfyElem
     ) where
 
+import Control.Applicative ((<$>))
+#if __GLASGOW_HASKELL__ >= 700
+import Data.ByteString (ByteString)
+import Data.Text (Text)
+#endif
 import Data.Attoparsec.Internal.Types
 import Prelude hiding (succ)
 
@@ -31,3 +41,115 @@
     Just (t0 == t1 && r0 == r1)
 compareResults (Partial _) (Partial _) = Nothing
 compareResults _ _ = Just False
+
+-- | Ask for input.  If we receive any, pass it to a success
+-- continuation, otherwise to a failure continuation.
+prompt :: Chunk t
+       => State t -> Pos -> More
+       -> (State t -> Pos -> More -> IResult t r)
+       -> (State t -> Pos -> More -> IResult t r)
+       -> IResult t r
+prompt t pos _more lose succ = Partial $ \s ->
+  if nullChunk s
+  then lose t pos Complete
+  else succ (pappendChunk t s) pos Incomplete
+#if __GLASGOW_HASKELL__ >= 700
+{-# SPECIALIZE prompt :: State ByteString -> Pos -> More
+                      -> (State ByteString -> Pos -> More
+                          -> IResult ByteString r)
+                      -> (State ByteString -> Pos -> More
+                          -> IResult ByteString r)
+                      -> IResult ByteString r #-}
+{-# SPECIALIZE prompt :: State Text -> Pos -> More
+                      -> (State Text -> Pos -> More -> IResult Text r)
+                      -> (State Text -> Pos -> More -> IResult Text r)
+                      -> IResult Text r #-}
+#endif
+
+-- | Immediately demand more input via a 'Partial' continuation
+-- result.
+demandInput :: Chunk t => Parser t ()
+demandInput = Parser $ \t pos more lose succ ->
+  case more of
+    Complete -> lose t pos more [] "not enough input"
+    _ -> let lose' t' pos' more' = lose t' pos' more' [] "not enough input"
+             succ' t' pos' more' = succ t' pos' more' ()
+         in prompt t pos more lose' succ'
+#if __GLASGOW_HASKELL__ >= 700
+{-# SPECIALIZE demandInput :: Parser ByteString () #-}
+{-# SPECIALIZE demandInput :: Parser Text () #-}
+#endif
+
+-- | This parser always succeeds.  It returns 'True' if any input is
+-- available either immediately or on demand, and 'False' if the end
+-- of all input has been reached.
+wantInput :: forall t . Chunk t => Parser t Bool
+wantInput = Parser $ \t pos more _lose succ ->
+  case () of
+    _ | pos < atBufferEnd (undefined :: t) t -> succ t pos more True
+      | more == Complete -> succ t pos more False
+      | otherwise       -> let lose' t' pos' more' = succ t' pos' more' False
+                               succ' t' pos' more' = succ t' pos' more' True
+                           in prompt t pos more lose' succ'
+{-# INLINE wantInput #-}
+
+-- | Match only if all input has been consumed.
+endOfInput :: forall t . Chunk t => Parser t ()
+endOfInput = Parser $ \t pos more lose succ ->
+  case () of
+    _| pos < atBufferEnd (undefined :: t) t -> lose t pos more [] "endOfInput"
+     | more == Complete -> succ t pos more ()
+     | otherwise ->
+       let lose' t' pos' more' _ctx _msg = succ t' pos' more' ()
+           succ' t' pos' more' _a = lose t' pos' more' [] "endOfInput"
+       in  runParser demandInput t pos more lose' succ'
+#if __GLASGOW_HASKELL__ >= 700
+{-# SPECIALIZE endOfInput :: Parser ByteString () #-}
+{-# SPECIALIZE endOfInput :: Parser Text () #-}
+#endif
+
+-- | Return an indication of whether the end of input has been
+-- reached.
+atEnd :: Chunk t => Parser t Bool
+atEnd = not <$> wantInput
+{-# INLINE atEnd #-}
+
+satisfySuspended :: forall t r . Chunk t
+                 => (ChunkElem t -> Bool)
+                 -> State t -> Pos -> More
+                 -> Failure t (State t) r
+                 -> Success t (State t) (ChunkElem t) r
+                 -> IResult t r
+satisfySuspended p t pos more lose succ =
+    runParser (demandInput >> go) t pos more lose succ
+  where go = Parser $ \t' pos' more' lose' succ' ->
+          case bufferElemAt (undefined :: t) pos' t' of
+            Just (e, l) | p e -> succ' t' (pos' + Pos l) more' e
+                        | otherwise -> lose' t' pos' more' [] "satisfyElem"
+            Nothing -> runParser (demandInput >> go) t' pos' more' lose' succ'
+#if __GLASGOW_HASKELL__ >= 700
+{-# SPECIALIZE satisfySuspended :: (ChunkElem ByteString -> Bool)
+                                -> State ByteString -> Pos -> More
+                                -> Failure ByteString (State ByteString) r
+                                -> Success ByteString (State ByteString)
+                                           (ChunkElem ByteString) r
+                                -> IResult ByteString r #-}
+{-# SPECIALIZE satisfySuspended :: (ChunkElem Text -> Bool)
+                                -> State Text -> Pos -> More
+                                -> Failure Text (State Text) r
+                                -> Success Text (State Text)
+                                           (ChunkElem Text) r
+                                -> IResult Text r #-}
+#endif
+
+-- | The parser @satisfyElem p@ succeeds for any chunk element for which the
+-- predicate @p@ returns 'True'. Returns the element that is
+-- actually parsed.
+satisfyElem :: forall t . Chunk t
+            => (ChunkElem t -> Bool) -> Parser t (ChunkElem t)
+satisfyElem p = Parser $ \t pos more lose succ ->
+    case bufferElemAt (undefined :: t) pos t of
+      Just (e, l) | p e -> succ t (pos + Pos l) more e
+                  | otherwise -> lose t pos more [] "satisfyElem"
+      Nothing -> satisfySuspended p t pos more lose succ
+{-# INLINE satisfyElem #-}
diff --git a/Data/Attoparsec/Internal/Types.hs b/Data/Attoparsec/Internal/Types.hs
--- a/Data/Attoparsec/Internal/Types.hs
+++ b/Data/Attoparsec/Internal/Types.hs
@@ -15,21 +15,27 @@
 module Data.Attoparsec.Internal.Types
     (
       Parser(..)
-    , Input(..)
+    , State
     , Failure
     , Success
     , Pos(..)
     , IResult(..)
     , More(..)
     , (<>)
+    , Chunk(..)
     ) where
 
 import Control.Applicative (Alternative(..), Applicative(..), (<$>))
 import Control.DeepSeq (NFData(rnf))
 import Control.Monad (MonadPlus(..))
+import Data.Word (Word8)
 import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import Data.ByteString.Internal (w2c)
 import Data.Monoid (Monoid(..))
 import Data.Text (Text)
+import qualified Data.Text as Text
+import Data.Text.Unsafe (Iter(..))
 import Prelude hiding (getChar, succ)
 import qualified Data.Attoparsec.ByteString.Buffer as B
 import qualified Data.Attoparsec.Text.Buffer as T
@@ -96,21 +102,16 @@
 --
 -- * 'Alternative', which follows 'MonadPlus'.
 newtype Parser i a = Parser {
-      runParser :: forall r. Input i =>
+      runParser :: forall r.
                    State i -> Pos -> More
                 -> Failure i (State i)   r
                 -> Success i (State i) a r
                 -> IResult i r
     }
 
-class Input i where
-    type State i :: *
-
-instance Input ByteString where
-    type State ByteString = B.Buffer
-
-instance Input Text where
-    type State Text = T.Buffer
+type family State i
+type instance State ByteString = B.Buffer
+type instance State Text = T.Buffer
 
 type Failure i t   r = t -> Pos -> More -> [String] -> String
                        -> IResult i r
@@ -202,3 +203,48 @@
 (<>) :: (Monoid m) => m -> m -> m
 (<>) = mappend
 {-# INLINE (<>) #-}
+
+-- | A common interface for input chunks.
+class Monoid c => Chunk c where
+  type ChunkElem c
+  -- | Test if the chunk is empty.
+  nullChunk :: c -> Bool
+  -- | Append chunk to a buffer.
+  pappendChunk :: State c -> c -> State c
+  -- | Position at the end of a buffer. The first argument is ignored.
+  atBufferEnd :: c -> State c -> Pos
+  -- | Return the buffer element at the given position along with its length.
+  bufferElemAt :: c -> Pos -> State c -> Maybe (ChunkElem c, Int)
+  -- | Map an element to the corresponding character.
+  --   The first argument is ignored.
+  chunkElemToChar :: c -> ChunkElem c -> Char
+
+instance Chunk ByteString where
+  type ChunkElem ByteString = Word8
+  nullChunk = BS.null
+  {-# INLINE nullChunk #-}
+  pappendChunk = B.pappend
+  {-# INLINE pappendChunk #-}
+  atBufferEnd _ = Pos . B.length
+  {-# INLINE atBufferEnd #-}
+  bufferElemAt _ (Pos i) buf
+    | i < B.length buf = Just (B.unsafeIndex buf i, 1)
+    | otherwise = Nothing
+  {-# INLINE bufferElemAt #-}
+  chunkElemToChar _ = w2c
+  {-# INLINE chunkElemToChar #-}
+
+instance Chunk Text where
+  type ChunkElem Text = Char
+  nullChunk = Text.null
+  {-# INLINE nullChunk #-}
+  pappendChunk = T.pappend
+  {-# INLINE pappendChunk #-}
+  atBufferEnd _ = Pos . T.length
+  {-# INLINE atBufferEnd #-}
+  bufferElemAt _ (Pos i) buf
+    | i < T.length buf = let Iter c l = T.iter buf i in Just (c, l)
+    | otherwise = Nothing
+  {-# INLINE bufferElemAt #-}
+  chunkElemToChar _ = id
+  {-# INLINE chunkElemToChar #-}
diff --git a/Data/Attoparsec/Text/Internal.hs b/Data/Attoparsec/Text/Internal.hs
--- a/Data/Attoparsec/Text/Internal.hs
+++ b/Data/Attoparsec/Text/Internal.hs
@@ -68,6 +68,7 @@
 import Control.Applicative ((<|>), (<$>))
 import Control.Monad (when)
 import Data.Attoparsec.Combinator ((<?>))
+import Data.Attoparsec.Internal
 import Data.Attoparsec.Internal.Types hiding (Parser, Failure, Success)
 import qualified Data.Attoparsec.Text.Buffer as Buf
 import Data.Attoparsec.Text.Buffer (Buffer, buffer)
@@ -478,50 +479,6 @@
 -- Non-recursive so the bounds check can be inlined:
 {-# INLINE ensure #-}
 
--- | Ask for input.  If we receive any, pass it to a success
--- continuation, otherwise to a failure continuation.
-prompt :: Buffer -> Pos -> More
-       -> (Buffer -> Pos -> More -> IResult Text r)
-       -> (Buffer -> Pos -> More -> IResult Text r)
-       -> IResult Text r
-prompt t pos _more lose succ = Partial $ \s ->
-  if T.null s
-  then lose t pos Complete
-  else succ (Buf.pappend t s) pos Incomplete
-
--- | Immediately demand more input via a 'Partial' continuation
--- result.
-demandInput :: Parser ()
-demandInput = T.Parser $ \t pos more lose succ ->
-  case more of
-    Complete -> lose t pos more [] "not enough input"
-    _ -> let lose' t' pos' more' = lose t' pos' more' [] "not enough input"
-             succ' t' pos' more' = succ t' pos' more' ()
-         in prompt t pos more lose' succ'
-
--- | This parser always succeeds.  It returns 'True' if any input is
--- available either immediately or on demand, and 'False' if the end
--- of all input has been reached.
-wantInput :: Parser Bool
-wantInput = T.Parser $ \t pos more _lose succ ->
-  case () of
-    _ | pos < lengthOf t -> succ t pos more True
-      | more == Complete -> succ t pos more False
-      | otherwise       -> let lose' t' pos' more' = succ t' pos' more' False
-                               succ' t' pos' more' = succ t' pos' more' True
-                           in prompt t pos more lose' succ'
-
--- | Match only if all input has been consumed.
-endOfInput :: Parser ()
-endOfInput = T.Parser $ \t pos more lose succ ->
-  case () of
-    _| pos < lengthOf t -> lose t pos more [] "endOfInput"
-     | more == Complete -> succ t pos more ()
-     | otherwise ->
-       let lose' t' pos' more' _ctx _msg = succ t' pos' more' ()
-           succ' t' pos' more' _a = lose t' pos' more' [] "endOfInput"
-       in  runParser demandInput t pos more lose' succ'
-
 -- | Return both the result of a parse and the portion of the input
 -- that was consumed while it was being parsed.
 match :: Parser a -> Parser (Text, a)
@@ -529,12 +486,6 @@
   let succ' t' pos' more' a = succ t' pos' more'
                               (substring pos (pos'-pos) t', a)
   in runParser p t pos more lose succ'
-
--- | Return an indication of whether the end of input has been
--- reached.
-atEnd :: Parser Bool
-atEnd = not <$> wantInput
-{-# INLINE atEnd #-}
 
 lengthAtLeast :: Pos -> Int -> Buffer -> Maybe Pos
 lengthAtLeast pos n t = go 0 (fromPos pos)
diff --git a/Data/Attoparsec/Types.hs b/Data/Attoparsec/Types.hs
--- a/Data/Attoparsec/Types.hs
+++ b/Data/Attoparsec/Types.hs
@@ -14,6 +14,7 @@
     (
       Parser
     , IResult(..)
+    , Chunk(chunkElemToChar)
     ) where
 
-import Data.Attoparsec.Internal.Types (Parser(..), IResult(..))
+import Data.Attoparsec.Internal.Types (Parser(..), IResult(..), Chunk(..))
diff --git a/attoparsec.cabal b/attoparsec.cabal
--- a/attoparsec.cabal
+++ b/attoparsec.cabal
@@ -1,5 +1,5 @@
 name:            attoparsec
-version:         0.12.0.0
+version:         0.12.1.0
 license:         BSD3
 license-file:    LICENSE
 category:        Text, Parsing
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,9 @@
+0.12.1.0
+
+* Now compatible with GHC 7.9
+
+* Reintroduced the Chunk class, used by the parsers package
+
 0.12.0.0
 
 * A new internal representation makes almost all real-world parsers
