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
@@ -425,7 +425,7 @@
 endOfLine :: Parser ()
 endOfLine = (word8 10 >> return ()) <|> (string "\r\n" >> return ())
 
---- | Name the parser, in case failure occurs.
+-- | Name the parser, in case failure occurs.
 (<?>) :: Parser a
       -> String                 -- ^ the name to use if parsing fails
       -> Parser a
diff --git a/Data/Attoparsec/Combinator.hs b/Data/Attoparsec/Combinator.hs
--- a/Data/Attoparsec/Combinator.hs
+++ b/Data/Attoparsec/Combinator.hs
@@ -25,6 +25,10 @@
 
 import Control.Applicative (Alternative(..), Applicative(..), empty, liftA2,
                             (<|>), (*>), (<$>))
+#if !MIN_VERSION_base(4,2,0)
+import Control.Applicative (many)
+#endif
+
 #if __GLASGOW_HASKELL__ >= 700
 import Data.Attoparsec.Internal.Types (Parser)
 import qualified Data.Attoparsec.Zepto as Z
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
@@ -202,6 +202,7 @@
     (<|>) = plus
     {-# INLINE (<|>) #-}
 
+#if MIN_VERSION_base(4,2,0)
     many v = many_v
         where many_v = some_v <|> pure []
               some_v = (:) <$> v <*> many_v
@@ -212,6 +213,7 @@
         many_v = some_v <|> pure []
         some_v = (:) <$> v <*> many_v
     {-# INLINE some #-}
+#endif
 
 failDesc :: String -> Parser t a
 failDesc err = Parser (\i0 a0 m0 kf _ks -> kf i0 a0 m0 [] msg)
diff --git a/Data/Attoparsec/Text.hs b/Data/Attoparsec/Text.hs
--- a/Data/Attoparsec/Text.hs
+++ b/Data/Attoparsec/Text.hs
@@ -51,12 +51,19 @@
     , I.satisfyWith
     , I.skip
 
+    -- ** Special character parsers
+    , digit
+    , letter
+    , space
+
     -- ** Character classes
     , I.inClass
     , I.notInClass
 
     -- * Efficient string handling
     , I.string
+    , stringCI
+    , skipSpace
     , I.skipWhile
     , I.scan
     , I.take
@@ -90,9 +97,9 @@
 import Control.Applicative ((<$>), (*>), (<|>))
 import Data.Attoparsec.Combinator
 import Data.Attoparsec.Number (Number(..))
-import Data.Attoparsec.Text.Internal (Parser, Result, parse, takeWhile1)
+import Data.Attoparsec.Text.Internal ((<?>), Parser, Result, parse, takeWhile1)
 import Data.Bits (Bits, (.|.), shiftL)
-import Data.Char (ord)
+import Data.Char (isAlpha, isDigit, isSpace, ord)
 import Data.Int (Int8, Int16, Int32, Int64)
 import Data.Ratio ((%))
 import Data.Text (Text)
@@ -264,7 +271,7 @@
 
 -- | Parse and decode an unsigned decimal number.
 decimal :: Integral a => Parser a
-decimal = T.foldl' step 0 `fmap` takeWhile1 isDigit
+decimal = T.foldl' step 0 `fmap` takeWhile1 isDecimal
   where step a c = a * 10 + fromIntegral (ord c - 48)
 {-# SPECIALISE decimal :: Parser Int #-}
 {-# SPECIALISE decimal :: Parser Int8 #-}
@@ -278,9 +285,9 @@
 {-# SPECIALISE decimal :: Parser Word32 #-}
 {-# SPECIALISE decimal :: Parser Word64 #-}
 
-isDigit :: Char -> Bool
-isDigit c = c >= '0' && c <= '9'
-{-# INLINE isDigit #-}
+isDecimal :: Char -> Bool
+isDecimal c = c >= '0' && c <= '9'
+{-# INLINE isDecimal #-}
 
 -- | Parse a number with an optional leading @\'+\'@ or @\'-\'@ sign
 -- character.
@@ -369,6 +376,31 @@
          then I real
          else D (asDouble real frac fracDenom)
 {-# INLINE number #-}
+
+-- | Parse a single digit, as recognised by 'isDigit'.
+digit :: Parser Char
+digit = I.satisfy isDigit <?> "digit"
+{-# INLINE digit #-}
+
+-- | Parse a letter, as recognised by 'isAlpha'.
+letter :: Parser Char
+letter = I.satisfy isAlpha <?> "letter"
+{-# INLINE letter #-}
+
+-- | Parse a space character, as recognised by 'isSpace'.
+space :: Parser Char
+space = I.satisfy isSpace <?> "space"
+{-# INLINE space #-}
+
+-- | Satisfy a literal string, ignoring case.
+stringCI :: Text -> Parser Text
+stringCI = I.stringTransform T.toCaseFold
+{-# INLINE stringCI #-}
+
+-- | Skip over white space.
+skipSpace :: Parser ()
+skipSpace = I.skipWhile isSpace
+{-# INLINE skipSpace #-}
 
 data T = T !Integer !Int
 
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
@@ -407,7 +407,7 @@
 endOfLine :: Parser ()
 endOfLine = (char '\n' >> return ()) <|> (string "\r\n" >> return ())
 
---- | Name the parser, in case failure occurs.
+-- | Name the parser, in case failure occurs.
 (<?>) :: Parser a
       -> String                 -- ^ the name to use if parsing fails
       -> Parser a
diff --git a/attoparsec.cabal b/attoparsec.cabal
--- a/attoparsec.cabal
+++ b/attoparsec.cabal
@@ -1,5 +1,5 @@
 name:            attoparsec
-version:         0.10.0.2
+version:         0.10.0.3
 license:         BSD3
 license-file:    LICENSE
 category:        Text, Parsing
@@ -69,6 +69,8 @@
   type:           exitcode-stdio-1.0
   hs-source-dirs: tests
   main-is:        QC.hs
+  other-modules:  QC.ByteString
+                  QC.Text
 
   ghc-options:
     -Wall -threaded -rtsopts
diff --git a/tests/QC.hs b/tests/QC.hs
--- a/tests/QC.hs
+++ b/tests/QC.hs
@@ -1,108 +1,12 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
 module Main (main) where
 
-import Control.Applicative ((<$>))
-import Prelude hiding (takeWhile)
+import qualified QC.ByteString as ByteString
+import qualified QC.Text as Text
 import Test.Framework (defaultMain, testGroup)
-import Test.Framework.Providers.QuickCheck2 (testProperty)
-import Test.QuickCheck
-import qualified Data.Attoparsec as P
-import qualified Data.ByteString as B
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Lazy as L
 
-instance Arbitrary S.ByteString where
-    arbitrary   = S.pack <$> arbitrary
-
-instance Arbitrary L.ByteString where
-    arbitrary   = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))
-                  ((L.fromChunks . map (S.pack . nonEmpty)) <$> arbitrary)
-      where nonEmpty (NonEmpty a) = a
-
--- Naming.
-
-{-
-label (NonEmpty s) = case parse (anyWord8 <?> s) B.empty of
-                            (_, Left err) -> s `isInfixOf` err
-                            _             -> False
--}
-
--- Basic byte-level combinators.
-
-maybeP p s = case P.parse p s `P.feed` B.empty of
-               P.Done _ i -> Just i
-               _          -> Nothing
-
-defP p s = P.parse p s `P.feed` B.empty
-
-satisfy w s = maybeP (P.satisfy (<=w)) (B.cons w s) == Just w
-
-word8 w s = maybeP (P.word8 w) (B.cons w s) == Just w
-
-anyWord8 s = maybeP P.anyWord8 s == if B.null s
-                                    then Nothing
-                                    else Just (B.head s)
-
-notWord8 w (NonEmpty s) = maybeP (P.notWord8 w) bs == if v == w
-                                                      then Nothing
-                                                      else Just v
-    where v = B.head bs
-          bs = B.pack s
-
-string s = maybeP (P.string s) s == Just s
-
-skipWhile w s =
-    let t = B.dropWhile (<=w) s
-    in case defP (P.skipWhile (<=w)) s of
-         P.Done t' () -> t == t'
-         _            -> False
-
-takeCount (Positive k) s =
-    case maybeP (P.take k) s of
-      Nothing -> k > B.length s
-      Just s' -> k <= B.length s
-
-takeWhile w s =
-    let (h,t) = B.span (==w) s
-    in case defP (P.takeWhile (==w)) s of
-         P.Done t' h' -> t == t' && h == h'
-         _            -> False
-
-takeWhile1 w s =
-    let s'    = B.cons w s
-        (h,t) = B.span (<=w) s'
-    in case defP (P.takeWhile1 (<=w)) s' of
-         P.Done t' h' -> t == t' && h == h'
-         _            -> False
-
-takeTill w s =
-    let (h,t) = B.break (==w) s
-    in case defP (P.takeTill (==w)) s of
-         P.Done t' h' -> t == t' && h == h'
-         _            -> False
-
-takeWhile1_empty = maybeP (P.takeWhile1 undefined) B.empty == Nothing
-
-endOfInput s = maybeP P.endOfInput s == if B.null s
-                                        then Just ()
-                                        else Nothing
-
 main = defaultMain tests
 
 tests = [
-  testGroup "fnord" [
-    testProperty "satisfy" satisfy,
-    testProperty "word8" word8,
-    testProperty "notWord8" notWord8,
-    testProperty "anyWord8" anyWord8,
-    testProperty "string" string,
-    testProperty "skipWhile" skipWhile,
-    testProperty "takeCount" takeCount,
-    testProperty "takeWhile" takeWhile,
-    testProperty "takeWhile1" takeWhile1,
-    testProperty "takeWhile1_empty" takeWhile1_empty,
-    testProperty "takeTill" takeTill,
-    testProperty "endOfInput" endOfInput
-    ]
+    testGroup "bs" ByteString.tests
+  , testGroup "text" Text.tests
   ]
diff --git a/tests/QC/ByteString.hs b/tests/QC/ByteString.hs
new file mode 100644
--- /dev/null
+++ b/tests/QC/ByteString.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module QC.ByteString (tests) where
+
+import Control.Applicative ((<$>))
+import Prelude hiding (takeWhile)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.QuickCheck
+import qualified Data.Attoparsec.ByteString as P
+import qualified Data.Attoparsec.ByteString.Lazy as PL
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+
+instance Arbitrary B.ByteString where
+    arbitrary   = B.pack <$> arbitrary
+
+instance Arbitrary L.ByteString where
+    arbitrary   = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))
+                  ((L.fromChunks . map (B.pack . nonEmpty)) <$> arbitrary)
+      where nonEmpty (NonEmpty a) = a
+
+-- Naming.
+
+{-
+label (NonEmpty s) = case parse (anyWord8 <?> s) B.empty of
+                            (_, Left err) -> s `isInfixOf` err
+                            _             -> False
+-}
+
+-- Basic byte-level combinators.
+
+maybeP p = PL.maybeResult . PL.parse p
+
+defP p = PL.parse p
+
+satisfy w s = maybeP (P.satisfy (<=w)) (L.cons w s) == Just w
+
+word8 w s = maybeP (P.word8 w) (L.cons w s) == Just w
+
+anyWord8 s
+    | L.null s  = p == Nothing
+    | otherwise = p == Just (L.head s)
+  where p = maybeP P.anyWord8 s
+
+notWord8 w (NonEmpty s) = maybeP (P.notWord8 w) bs == if v == w
+                                                      then Nothing
+                                                      else Just v
+    where v = L.head bs
+          bs = L.pack s
+
+string s t = maybeP (P.string s') (s `L.append` t) == Just s'
+  where s' = toStrict s
+
+toStrict = B.concat . L.toChunks
+
+skipWhile w s =
+    let t = L.dropWhile (<=w) s
+    in case defP (P.skipWhile (<=w)) s of
+         PL.Done t' () -> t == t'
+         _             -> False
+
+takeCount (Positive k) s =
+    case maybeP (P.take k) s of
+      Nothing -> fromIntegral k > L.length s
+      Just s' -> fromIntegral k <= L.length s
+
+takeWhile w s =
+    let (h,t) = L.span (==w) s
+    in case defP (P.takeWhile (==w)) s of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeWhile1 w s =
+    let s'    = L.cons w s
+        (h,t) = L.span (<=w) s'
+    in case defP (P.takeWhile1 (<=w)) s' of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeTill w s =
+    let (h,t) = L.break (==w) s
+    in case defP (P.takeTill (==w)) s of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeWhile1_empty = maybeP (P.takeWhile1 undefined) L.empty == Nothing
+
+endOfInput s = maybeP P.endOfInput s == if L.null s
+                                        then Just ()
+                                        else Nothing
+
+tests = [
+    testProperty "satisfy" satisfy,
+    testProperty "word8" word8,
+    testProperty "notWord8" notWord8,
+    testProperty "anyWord8" anyWord8,
+    testProperty "string" string,
+    testProperty "skipWhile" skipWhile,
+    testProperty "takeCount" takeCount,
+    testProperty "takeWhile" takeWhile,
+    testProperty "takeWhile1" takeWhile1,
+    testProperty "takeWhile1_empty" takeWhile1_empty,
+    testProperty "takeTill" takeTill,
+    testProperty "endOfInput" endOfInput
+  ]
diff --git a/tests/QC/Text.hs b/tests/QC/Text.hs
new file mode 100644
--- /dev/null
+++ b/tests/QC/Text.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module QC.Text (tests) where
+
+import Control.Applicative ((<$>))
+import Prelude hiding (takeWhile)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.QuickCheck
+import qualified Data.Attoparsec.Text as P
+import qualified Data.Attoparsec.Text.Lazy as PL
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as L
+
+instance Arbitrary T.Text where
+    arbitrary   = T.pack <$> arbitrary
+
+instance Arbitrary L.Text where
+    arbitrary   = sized $ \n -> resize (round (sqrt (toEnum n :: Double)))
+                  ((L.fromChunks . map (T.pack . nonEmpty)) <$> arbitrary)
+      where nonEmpty (NonEmpty a) = a
+
+-- Naming.
+
+{-
+label (NonEmpty s) = case parse (anyChar <?> s) T.empty of
+                            (_, Left err) -> s `isInfixOf` err
+                            _             -> False
+-}
+
+-- Basic byte-level combinators.
+
+maybeP p = PL.maybeResult . PL.parse p
+
+defP p = PL.parse p
+
+satisfy w s = maybeP (P.satisfy (<=w)) (L.cons w s) == Just w
+
+char w s = maybeP (P.char w) (L.cons w s) == Just w
+
+anyChar s
+    | L.null s  = p == Nothing
+    | otherwise = p == Just (L.head s)
+  where p = maybeP P.anyChar s
+
+notChar w (NonEmpty s) = maybeP (P.notChar w) bs == if v == w
+                                                      then Nothing
+                                                      else Just v
+    where v = L.head bs
+          bs = L.pack s
+
+string s t = maybeP (P.string s') (s `L.append` t) == Just s'
+  where s' = toStrict s
+
+toStrict = T.concat . L.toChunks
+
+skipWhile w s =
+    let t = L.dropWhile (<=w) s
+    in case defP (P.skipWhile (<=w)) s of
+         PL.Done t' () -> t == t'
+         _             -> False
+
+takeCount (Positive k) s =
+    case maybeP (P.take k) s of
+      Nothing -> fromIntegral k > L.length s
+      Just s' -> fromIntegral k <= L.length s
+
+takeWhile w s =
+    let (h,t) = L.span (==w) s
+    in case defP (P.takeWhile (==w)) s of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeWhile1 w s =
+    let s'    = L.cons w s
+        (h,t) = L.span (<=w) s'
+    in case defP (P.takeWhile1 (<=w)) s' of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeTill w s =
+    let (h,t) = L.break (==w) s
+    in case defP (P.takeTill (==w)) s of
+         PL.Done t' h' -> t == t' && toStrict h == h'
+         _             -> False
+
+takeWhile1_empty = maybeP (P.takeWhile1 undefined) L.empty == Nothing
+
+endOfInput s = maybeP P.endOfInput s == if L.null s
+                                        then Just ()
+                                        else Nothing
+
+tests = [
+    testProperty "satisfy" satisfy,
+    testProperty "char" char,
+    testProperty "notChar" notChar,
+    testProperty "anyChar" anyChar,
+    testProperty "string" string,
+    testProperty "skipWhile" skipWhile,
+    testProperty "takeCount" takeCount,
+    testProperty "takeWhile" takeWhile,
+    testProperty "takeWhile1" takeWhile1,
+    testProperty "takeWhile1_empty" takeWhile1_empty,
+    testProperty "takeTill" takeTill,
+    testProperty "endOfInput" endOfInput
+  ]
