diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
 Copyright 2011 Edward Kmett
+Copyright 1999-2000 Daan Leijen
 
 All rights reserved.
 
diff --git a/Text/Trifecta.hs b/Text/Trifecta.hs
--- a/Text/Trifecta.hs
+++ b/Text/Trifecta.hs
@@ -1,28 +1,31 @@
 module Text.Trifecta 
   ( module Text.Trifecta.Bytes
+--  , module System.Console.Terminfo.Color
+  , module System.Console.Terminfo.PrettyPrint
+--  , module Text.PrettyPrint.Free
   , module Text.Trifecta.Delta
   , module Text.Trifecta.Diagnostic
   , module Text.Trifecta.Diagnostic.Level
   , module Text.Trifecta.Hunk
-  , module Text.Trifecta.Parser.It
   , module Text.Trifecta.Parser.Char
   , module Text.Trifecta.Parser.Class
+  , module Text.Trifecta.Parser.Combinators
   , module Text.Trifecta.Parser.Err
   , module Text.Trifecta.Parser.Err.State
+  , module Text.Trifecta.Parser.It
   , module Text.Trifecta.Parser.Prim
   , module Text.Trifecta.Parser.Result
   , module Text.Trifecta.Parser.Step
+  , module Text.Trifecta.Parser.Token
+  , module Text.Trifecta.Parser.Token.Class
   , module Text.Trifecta.Path
-  , module Text.Trifecta.Render.Prim
   , module Text.Trifecta.Render.Caret
   , module Text.Trifecta.Render.Fixit
+  , module Text.Trifecta.Render.Prim
   , module Text.Trifecta.Render.Span
   , module Text.Trifecta.Rope
   , module Text.Trifecta.Strand
   , module Text.Trifecta.Util.MaybePair
-  , module Text.PrettyPrint.Free
-  , module System.Console.Terminfo.PrettyPrint
-  , module System.Console.Terminfo.Color
   ) where
 
 import Text.Trifecta.Bytes
@@ -30,23 +33,25 @@
 import Text.Trifecta.Diagnostic
 import Text.Trifecta.Diagnostic.Level
 import Text.Trifecta.Hunk
-import Text.Trifecta.Parser.It
 import Text.Trifecta.Parser.Char
 import Text.Trifecta.Parser.Class
+import Text.Trifecta.Parser.Combinators
 import Text.Trifecta.Parser.Err
 import Text.Trifecta.Parser.Err.State
+import Text.Trifecta.Parser.It
 import Text.Trifecta.Parser.Prim
 import Text.Trifecta.Parser.Result
 import Text.Trifecta.Parser.Step
+import Text.Trifecta.Parser.Token
+import Text.Trifecta.Parser.Token.Class
 import Text.Trifecta.Path
-import Text.Trifecta.Render.Prim
 import Text.Trifecta.Render.Caret
 import Text.Trifecta.Render.Fixit
+import Text.Trifecta.Render.Prim
 import Text.Trifecta.Render.Span
 import Text.Trifecta.Rope
 import Text.Trifecta.Strand
 import Text.Trifecta.Util.MaybePair
-
-import Text.PrettyPrint.Free hiding (column, char, line, string, space)
+-- import Text.PrettyPrint.Free hiding (column, char, line, string, space)
 import System.Console.Terminfo.PrettyPrint
-import System.Console.Terminfo.Color
+-- import System.Console.Terminfo.Color
diff --git a/Text/Trifecta/Parser/Char.hs b/Text/Trifecta/Parser/Char.hs
--- a/Text/Trifecta/Parser/Char.hs
+++ b/Text/Trifecta/Parser/Char.hs
@@ -125,7 +125,7 @@
 -- >              <|> string "mod"
 byteString :: MonadParser m => ByteString -> m ByteString
 byteString bs = do
-   r <- rest
+   r <- restOfLine
    let lr = Strict.length r
        lbs = Strict.length bs
    guard $ lr > 0
diff --git a/Text/Trifecta/Parser/Class.hs b/Text/Trifecta/Parser/Class.hs
--- a/Text/Trifecta/Parser/Class.hs
+++ b/Text/Trifecta/Parser/Class.hs
@@ -1,15 +1,29 @@
 {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, UndecidableInstances, FlexibleInstances, FlexibleContexts #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Trifecta.Parser.Class
+-- Copyright   :  (c) Edward Kmett 2011
+-- License     :  BSD3
+-- 
+-- Maintainer  :  ekmett@gmail.com
+-- Stability   :  experimental
+-- Portability :  non-portable
+-- 
+-----------------------------------------------------------------------------
 module Text.Trifecta.Parser.Class 
   ( MonadParser(..)
-  , rest
+  , restOfLine
+  , skipping
   , (<?>)
+  , slicedWith
   , sliced
   ) where
 
 import Control.Applicative
 import Control.Monad (MonadPlus(..))
 import Control.Monad.Trans.Class
-import Control.Monad.Trans.State.Lazy
+import Control.Monad.Trans.State.Lazy as Lazy
 import Data.ByteString as Strict
 import Data.Semigroup
 import Data.Set as Set
@@ -17,8 +31,9 @@
 import Text.Trifecta.Rope
 import Text.Trifecta.Parser.It
 
-class ( Alternative m, MonadPlus m) => MonadParser m where
+infix 0 <?>
 
+class ( Alternative m, MonadPlus m) => MonadParser m where
   -- * non-committal actions
   try        :: m a -> m a
   labels     :: m a -> Set String -> m a
@@ -27,22 +42,17 @@
   unexpected :: MonadParser m => String -> m a
   line       :: m ByteString
 
-  -- * actions that commit
-  satisfy    :: (Char -> Bool) -> m Char
+  -- * actions that definitely commit
   release    :: Delta -> m ()
+  satisfy    :: (Char -> Bool) -> m Char
 
   satisfyAscii :: (Char -> Bool) -> m Char
   satisfyAscii f = toEnum . fromEnum <$> satisfy (f . toEnum . fromEnum)
 
-  skipping :: HasDelta d => d -> m d
-  skipping d = do
-    m <- mark
-    d <$ release (m <> delta d)
-
-instance MonadParser m => MonadParser (StateT s m) where
+instance MonadParser m => MonadParser (Lazy.StateT s m) where
   satisfy = lift . satisfy
-  try (StateT m) = StateT $ try . m
-  labels (StateT m) ss = StateT $ \s -> labels (m s) ss
+  try (Lazy.StateT m) = Lazy.StateT $ try . m
+  labels (Lazy.StateT m) ss = Lazy.StateT $ \s -> labels (m s) ss
   line = lift line
   liftIt = lift . liftIt
   mark = lift mark 
@@ -50,17 +60,31 @@
   unexpected = lift . unexpected
   satisfyAscii = lift . satisfyAscii
 
-rest :: MonadParser m => m ByteString
-rest = do
+-- useful when we've just recognized something out of band using access to the current line 
+skipping :: (MonadParser m, HasDelta d) => d -> m d
+skipping d = do
   m <- mark
+  d <$ release (m <> delta d)
+
+-- | grab the remainder of the current line
+restOfLine :: MonadParser m => m ByteString
+restOfLine = do
+  m <- mark
   Strict.drop (columnByte m) <$> line
 
+-- | label a parser with a name
 (<?>) :: MonadParser m => m a -> String -> m a
 p <?> msg = labels p (Set.singleton msg)
 
-sliced :: MonadParser m => (a -> Strict.ByteString -> r) -> m a -> m r
-sliced f pa = do
+-- | run a parser, grabbing all of the text between its start and end points
+slicedWith :: MonadParser m => (a -> Strict.ByteString -> r) -> m a -> m r
+slicedWith f pa = do
   m <- mark
   a <- pa
   r <- mark
   liftIt $ f a <$> sliceIt m r
+
+-- | run a parser, grabbing all of the text between its start and end points and discarding the original result
+sliced :: MonadParser m => m a -> m ByteString
+sliced = slicedWith (\_ bs -> bs)
+  
diff --git a/Text/Trifecta/Parser/Combinators.hs b/Text/Trifecta/Parser/Combinators.hs
new file mode 100644
--- /dev/null
+++ b/Text/Trifecta/Parser/Combinators.hs
@@ -0,0 +1,219 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Trifecta.Parser.Combinators
+-- Copyright   :  (c) Edward Kmett 2011
+-- License     :  BSD3
+-- 
+-- Maintainer  :  ekmett@gmail.com
+-- Stability   :  experimental
+-- Portability :  non-portable
+-- 
+-- Commonly used generic combinators
+-- 
+-----------------------------------------------------------------------------
+
+module Text.Trifecta.Parser.Combinators 
+  ( choice
+  , option
+  , optional -- from Control.Applicative, parsec optionMaybe
+  , skipOptional -- parsec optional
+  , between
+  , skipSome -- parsec skipMany1
+  , skipMany
+  , some -- from Control.Applicative, parsec many1
+  , many -- from Control.Applicative
+  , sepBy
+  , sepBy1
+  , sepEndBy1
+  , sepEndBy
+  , endBy1
+  , endBy
+  , count
+  , chainl
+  , chainr
+  , chainl1
+  , chainr1
+  , eof
+  , manyTill
+  , notFollowedBy
+  , lookAhead
+  ) where
+
+import Data.Traversable
+import Control.Applicative
+import Text.Trifecta.Parser.Class
+
+-- | @choice ps@ tries to apply the parsers in the list @ps@ in order,
+-- until one of them succeeds. Returns the value of the succeeding
+-- parser.
+choice :: Alternative m => [m a] -> m a
+choice = foldr (<|>) empty
+
+-- | @option x p@ tries to apply parser @p@. If @p@ fails without
+-- consuming input, it returns the value @x@, otherwise the value
+-- returned by @p@.
+--
+-- >  priority  = option 0 (do{ d <- digit
+-- >                          ; return (digitToInt d) 
+-- >                          })
+option :: Alternative m => a -> m a -> m a
+option x p = p <|> pure x
+
+-- | @skipOptional p@ tries to apply parser @p@.  It will parse @p@ or nothing.
+-- It only fails if @p@ fails after consuming input. It discards the result
+-- of @p@. (Plays the role of parsec's optional, which conflicts with Applicative's optional)
+skipOptional :: Alternative m => m a -> m ()
+skipOptional p = (() <$ p) <|> pure ()
+
+-- | @between open close p@ parses @open@, followed by @p@ and @close@.
+-- Returns the value returned by @p@.
+--
+-- >  braces  = between (symbol "{") (symbol "}")
+between :: Applicative m => m bra -> m ket -> m a -> m a
+between bra ket p = bra *> p <* ket
+
+-- | @skipSome p@ applies the parser @p@ /one/ or more times, skipping
+-- its result. (aka skipMany1 in parsec)
+skipSome :: Alternative m => m a -> m ()
+skipSome p = p *> skipMany p
+
+-- | @skipMany1 p@ applies the parser @p@ /one/ or more times, skipping
+-- its result. 
+skipMany :: Alternative m => m a -> m ()
+skipMany p = ps where ps = (p *> ps) <|> pure ()
+
+-- | @sepBy p sep@ parses /zero/ or more occurrences of @p@, separated
+-- by @sep@. Returns a list of values returned by @p@.
+--
+-- >  commaSep p  = p `sepBy` (symbol ",")
+sepBy :: Alternative m => m a -> m sep -> m [a]
+sepBy p sep = sepBy1 p sep <|> pure []
+
+-- | @sepBy1 p sep@ parses /one/ or more occurrences of @p@, separated
+-- by @sep@. Returns a list of values returned by @p@. 
+sepBy1 :: Alternative m => m a -> m sep -> m [a]
+sepBy1 p sep = (:) <$> p <*> many (sep *> p)
+
+-- | @sepEndBy1 p sep@ parses /one/ or more occurrences of @p@,
+-- separated and optionally ended by @sep@. Returns a list of values
+-- returned by @p@. 
+sepEndBy1 :: Alternative m => m a -> m sep -> m [a]
+sepEndBy1 p sep = flip id <$> p <*> ((flip (:) <$> (sep *> sepEndBy p sep)) <|> pure pure)
+
+-- | @sepEndBy p sep@ parses /zero/ or more occurrences of @p@,
+-- separated and optionally ended by @sep@, ie. haskell style
+-- statements. Returns a list of values returned by @p@.
+--
+-- >  haskellStatements  = haskellStatement `sepEndBy` semi
+sepEndBy :: Alternative m => m a -> m sep -> m [a]
+sepEndBy p sep = sepEndBy1 p sep <|> pure []
+
+-- | @endBy1 p sep@ parses /one/ or more occurrences of @p@, seperated
+-- and ended by @sep@. Returns a list of values returned by @p@. 
+endBy1 :: Alternative m => m a -> m sep -> m [a]
+endBy1 p sep = some (p <* sep)
+
+-- | @endBy p sep@ parses /zero/ or more occurrences of @p@, seperated
+-- and ended by @sep@. Returns a list of values returned by @p@.
+--
+-- >   cStatements  = cStatement `endBy` semi
+endBy :: Alternative m => m a -> m sep -> m [a]
+endBy p sep = many (p <* sep)
+
+
+-- | @count n p@ parses @n@ occurrences of @p@. If @n@ is smaller or
+-- equal to zero, the parser equals to @return []@. Returns a list of
+-- @n@ values returned by @p@. 
+count :: Applicative m => Int -> m a -> m [a]
+count n p | n <= 0    = pure []
+          | otherwise = sequenceA (replicate n p)
+
+
+-- | @chainr p op x@ parser /zero/ or more occurrences of @p@,
+-- separated by @op@ Returns a value obtained by a /right/ associative
+-- application of all functions returned by @op@ to the values returned
+-- by @p@. If there are no occurrences of @p@, the value @x@ is
+-- returned.
+chainr :: Alternative m => m a -> m (a -> a -> a) -> a -> m a
+chainr p op x = chainr1 p op <|> pure x
+
+-- | @chainl p op x@ parser /zero/ or more occurrences of @p@,
+-- separated by @op@. Returns a value obtained by a /left/ associative
+-- application of all functions returned by @op@ to the values returned
+-- by @p@. If there are zero occurrences of @p@, the value @x@ is
+-- returned.
+chainl :: Alternative m => m a -> m (a -> a -> a) -> a -> m a
+chainl p op x = chainl1 p op <|> pure x
+
+-- | @chainl1 p op x@ parser /one/ or more occurrences of @p@,
+-- separated by @op@ Returns a value obtained by a /left/ associative
+-- application of all functions returned by @op@ to the values returned
+-- by @p@. . This parser can for example be used to eliminate left
+-- recursion which typically occurs in expression grammars.
+--
+-- >  expr    = term   `chainl1` addop
+-- >  term    = factor `chainl1` mulop
+-- >  factor  = parens expr <|> integer
+-- >
+-- >  mulop   =   do{ symbol "*"; return (*)   }
+-- >          <|> do{ symbol "/"; return (div) }
+-- >
+-- >  addop   =   do{ symbol "+"; return (+) }
+-- >          <|> do{ symbol "-"; return (-) }
+chainl1 :: Alternative m => m a -> m (a -> a -> a) -> m a
+chainl1 p op = scan where
+  scan = flip id <$> p <*> rst
+  rst = (\f y g x -> g (f x y)) <$> op <*> p <*> rst <|> pure id
+
+-- | @chainr1 p op x@ parser /one/ or more occurrences of |p|,
+-- separated by @op@ Returns a value obtained by a /right/ associative
+-- application of all functions returned by @op@ to the values returned
+-- by @p@.
+chainr1 :: Alternative m => m a -> m (a -> a -> a) -> m a
+chainr1 p op = scan where
+  scan = flip id <$> p <*> rst
+  rst = (flip <$> op <*> scan) <|> pure id
+
+-- | @manyTill p end@ applies parser @p@ /zero/ or more times until
+-- parser @end@ succeeds. Returns the list of values returned by @p@.
+-- This parser can be used to scan comments:
+--
+-- >  simpleComment   = do{ string "<!--"
+-- >                      ; manyTill anyChar (try (string "-->"))
+-- >                      }
+--
+--    Note the overlapping parsers @anyChar@ and @string \"-->\"@, and
+--    therefore the use of the 'try' combinator.
+manyTill :: Alternative m => m a -> m end -> m [a]
+manyTill p end = go where go = ([] <$ end) <|> ((:) <$> p <*> go)
+
+-- * MonadParsers 
+
+-- | This parser only succeeds at the end of the input. This is not a
+-- primitive parser but it is defined using 'notFollowedBy'.
+--
+-- >  eof  = notFollowedBy anyChar <?> "end of input"
+eof :: MonadParser m => m ()
+eof = notFollowedBy (satisfy (const True)) <?> "end of input"
+
+-- | @notFollowedBy p@ only succeeds when parser @p@ fails. This parser
+-- does not consume any input. This parser can be used to implement the
+-- \'longest match\' rule. For example, when recognizing keywords (for
+-- example @let@), we want to make sure that a keyword is not followed
+-- by a legal identifier character, in which case the keyword is
+-- actually an identifier (for example @lets@). We can program this
+-- behaviour as follows:
+--
+-- >  keywordLet  = try (do{ string "let"
+-- >                       ; notFollowedBy alphaNum
+-- >                       })
+notFollowedBy :: (MonadParser m, Show a) => m a -> m ()
+notFollowedBy p = try ((try p >>= unexpected . show) <|> pure ())
+
+-- | @lookAhead p@ parses @p@ without consuming any input.
+lookAhead :: MonadParser m => m a -> m a
+lookAhead p = try $ do 
+  m <- mark
+  p <* release m
+
+
diff --git a/Text/Trifecta/Parser/Prim.hs b/Text/Trifecta/Parser/Prim.hs
--- a/Text/Trifecta/Parser/Prim.hs
+++ b/Text/Trifecta/Parser/Prim.hs
@@ -1,4 +1,15 @@
 {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleContexts, Rank2Types, FlexibleInstances #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Trifecta.Parser.Prim
+-- Copyright   :  (c) Edward Kmett 2011
+-- License     :  BSD3
+-- 
+-- Maintainer  :  ekmett@gmail.com
+-- Stability   :  experimental
+-- Portability :  non-portable
+-- 
+-----------------------------------------------------------------------------
 module Text.Trifecta.Parser.Prim 
   ( Parser(..)
   , why
@@ -47,6 +58,8 @@
 instance Functor (Parser e) where
   fmap f (Parser m) = Parser $ \ eo ee co -> m (eo . f) ee (co . f)
   {-# INLINE fmap #-}
+  a <$ Parser m = Parser $ \ eo ee co -> m (\_ -> eo a) ee (\_ -> co a)
+  {-# INLINE (<$) #-}
 
 instance Apply (Parser e) where (<.>) = (<*>)
 instance Applicative (Parser e) where
@@ -56,6 +69,14 @@
     m (\f e -> n (\a e' -> eo (f a) (e <> e')) ee (\a e' -> co (f a) (e <> e')) ce) ee
       (\f e -> n (\a e' -> co (f a) (e <> e')) ce (\a e' -> co (f a) (e <> e')) ce) ce
   {-# INLINE (<*>) #-}
+  Parser m <* Parser n = Parser $ \ eo ee co ce -> 
+    m (\a e -> n (\_ e' -> eo a (e <> e')) ee (\_ e' -> co a (e <> e')) ce) ee
+      (\a e -> n (\_ e' -> co a (e <> e')) ce (\_ e' -> co a (e <> e')) ce) ce
+  {-# INLINE (<*) #-}
+  Parser m *> Parser n = Parser $ \ eo ee co ce -> 
+    m (\_ e -> n (\a e' -> eo a (e <> e')) ee (\a e' -> co a (e <> e')) ce) ee
+      (\_ e -> n (\a e' -> co a (e <> e')) ce (\a e' -> co a (e <> e')) ce) ce
+  {-# INLINE (*>) #-}
 
 instance Alt (Parser e) where (<!>) = (<|>)
 instance Plus (Parser e) where zero = empty
@@ -69,9 +90,14 @@
                   co 
                   ce) 
       co ce
- 
   {-# INLINE (<|>) #-}
+instance Semigroup (Parser e a) where
+  (<>) = (<|>) 
 
+instance Monoid (Parser e a) where
+  mappend = (<|>)
+  mempty = empty
+
 instance Bind (Parser e) where (>>-) = (>>=)
 instance Monad (Parser e) where
   return a = Parser $ \ eo _ _ _ -> eo a mempty
@@ -80,6 +106,8 @@
     m (\a e -> unparser (k a) (\b e' -> eo b (e <> e')) (\e' -> ee (e <> e')) co ce) ee 
       (\a e -> unparser (k a) (\b e' -> co b (e <> e')) (\e' -> ce (e <> e')) co ce) ce
   {-# INLINE (>>=) #-}
+  (>>) = (*>) 
+  {-# INLINE (>>) #-}
   fail = throwError . FailErr
   {-# INLINE fail #-}
 
@@ -184,7 +212,7 @@
     expected doc = doc <> text ", expected" <+> fillSep (punctuate (char ',') $ text <$> toList ss)
 
 parseTest :: Show a => Parser TermDoc a -> String -> IO ()
-parseTest p s = case eof (feed st (UTF8.fromString s)) of
+parseTest p s = case starve (feed st (UTF8.fromString s)) of
   Failure xs e -> displayLn $ prettyTerm $ toList (xs |> e)
   Success xs a -> do 
     displayLn $ prettyTerm $ toList xs
diff --git a/Text/Trifecta/Parser/Step.hs b/Text/Trifecta/Parser/Step.hs
--- a/Text/Trifecta/Parser/Step.hs
+++ b/Text/Trifecta/Parser/Step.hs
@@ -2,8 +2,7 @@
 module Text.Trifecta.Parser.Step 
   ( Step(..)
   , feed
-  , eof
-  , eof'
+  , starve
   , stepResult
   ) where
 
@@ -42,15 +41,10 @@
 feed (StepFail r xs e) t = StepFail (snoc r t) xs e
 feed (StepCont r _ k) t = k (snoc r t)
 
-eof :: Step e a -> Result e a
-eof (StepDone _ xs a) = Success xs a
-eof (StepFail _ xs e) = Failure xs e
-eof (StepCont _ z _)  = z
-
-eof' :: Step e a -> Result e (Rope, a)
-eof' (StepDone r xs a) = Success xs (r, a)
-eof' (StepFail _ xs e) = Failure xs e
-eof' (StepCont r z _)  = fmap ((,) r) z
+starve :: Step e a -> Result e a
+starve (StepDone _ xs a) = Success xs a
+starve (StepFail _ xs e) = Failure xs e
+starve (StepCont _ z _)  = z
 
 stepResult :: Rope -> Result e a -> Step e a
 stepResult r (Success xs a) = StepDone r xs a
diff --git a/Text/Trifecta/Parser/Token.hs b/Text/Trifecta/Parser/Token.hs
new file mode 100644
--- /dev/null
+++ b/Text/Trifecta/Parser/Token.hs
@@ -0,0 +1,374 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Trifecta.Parser.Token
+-- Copyright   :  (c) Edward Kmett 2011,
+--                (c) Daan Leijen 1999-2001
+-- License     :  BSD3
+-- 
+-- Maintainer  :  ekmett@gmail.com
+-- Stability   :  provisional
+-- Portability :  non-portable
+-- 
+-----------------------------------------------------------------------------
+module Text.Trifecta.Parser.Token
+  ( identifier
+  , reserved
+  , operator
+  , reservedOp
+  , charLiteral
+  , stringLiteral
+  , natural
+  , integer
+  , double
+  , naturalOrDouble
+  , decimal
+  , hexadecimal
+  , octal
+  , symbol
+  , symbolic
+  , lexeme
+  , parens
+  , braces
+  , angles
+  , brackets
+  , semi
+  , comma
+  , colon
+  , dot
+  , semiSep
+  , semiSep1
+  , commaSep
+  , commaSep1
+  ) where
+
+import Data.Char (digitToInt)
+import Data.ByteString as Strict hiding (map, zip, foldl, foldr)
+import Control.Applicative
+import Control.Monad (when)
+import Text.Trifecta.Parser.Class
+import Text.Trifecta.Parser.Char
+import Text.Trifecta.Parser.Combinators
+import Text.Trifecta.Parser.Token.Class
+
+-- | This lexeme parser parses a legal identifier. Returns the identifier
+-- string. This parser will fail on identifiers that are reserved
+-- words. Legal identifier (start) characters and reserved words are
+-- defined in the 'LanguageDef' that is passed to
+-- 'makeTokenParser'. An @identifier@ is treated as
+-- a single token using 'try'.
+identifier :: MonadTokenParser m => m ByteString
+identifier = lexeme $ try $ do 
+  name <- sliced ident 
+  b <- isReservedName name
+  when b $ unexpected $ "reserved word " ++ show name
+  return name
+  where 
+    ident = identStart *> skipMany identLetter
+        <?> "identifier"
+
+-- | The lexeme parser @reserved name@ parses @symbol 
+-- name@, but it also checks that the @name@ is not a prefix of a
+-- valid identifier. A @reserved@ word is treated as a single token
+-- using 'try'. 
+reserved :: MonadTokenParser m => ByteString -> m ()
+reserved name = lexeme $ try $ do
+  _ <- byteString name
+  notFollowedBy identLetter <?> "end of " ++ show name
+
+-- | This lexeme parser parses a legal operator. Returns the name of the
+-- operator. This parser will fail on any operators that are reserved
+-- operators. Legal operator (start) characters and reserved operators
+-- are defined in the 'LanguageDef' that is passed to
+-- 'makeTokenParser'. An @operator@ is treated as a
+-- single token using 'try'. 
+operator :: MonadTokenParser m => m ByteString
+operator = lexeme $ try $ do
+  name <- sliced (opStart *> skipMany opLetter) <?> "operator"
+  b <- isReservedOpName name
+  when b $ unexpected $ "reserved operator " ++ show name
+  return name
+
+-- |The lexeme parser @reservedOp name@ parses @symbol
+-- name@, but it also checks that the @name@ is not a prefix of a
+-- valid operator. A @reservedOp@ is treated as a single token using
+-- 'try'. 
+reservedOp :: MonadTokenParser m => String -> m ()
+reservedOp name = lexeme $ try $ do
+   _ <- string name 
+   notFollowedBy opLetter <?> "end of " ++ show name
+
+-- | This lexeme parser parses a single literal character. Returns the
+-- literal character value. This parsers deals correctly with escape
+-- sequences. The literal character is parsed according to the grammar
+-- rules defined in the Haskell report (which matches most programming
+-- languages quite closely). 
+charLiteral :: MonadTokenParser m => m Char
+charLiteral = lexeme (between (char '\'') (char '\'' <?> "end of character") characterChar)
+          <?> "character" 
+
+characterChar, charEscape, charLetter :: MonadTokenParser m => m Char
+characterChar = charLetter <|> charEscape
+            <?> "literal character"
+charEscape = char '\\' *> escapeCode
+charLetter = satisfy (\c -> (c /= '\'') && (c /= '\\') && (c > '\026'))
+
+-- | This lexeme parser parses a literal string. Returns the literal
+-- string value. This parsers deals correctly with escape sequences and
+-- gaps. The literal string is parsed according to the grammar rules
+-- defined in the Haskell report (which matches most programming
+-- languages quite closely). 
+
+stringLiteral :: MonadTokenParser m => m String
+stringLiteral = lexeme lit where
+  lit = foldr (maybe id (:)) "" <$> between (char '"') (char '"' <?> "end of string") (many stringChar) 
+    <?> "literal string"
+  stringChar = Just <$> stringLetter 
+           <|> stringEscape 
+       <?> "string character"
+  stringLetter    = satisfy (\c -> (c /= '"') && (c /= '\\') && (c > '\026'))
+
+  stringEscape = char '\\' *> esc where
+    esc = Nothing <$ escapeGap 
+      <|> Nothing <$ escapeEmpty 
+      <|> Just <$> escapeCode
+  escapeEmpty = char '&'
+  escapeGap = do skipSome space
+                 char '\\' <?> "end of string gap"
+
+escapeCode :: MonadTokenParser m => m Char
+escapeCode = (charEsc <|> charNum <|> charAscii <|> charControl) <?> "escape code"
+  where 
+  charControl = (\c -> toEnum (fromEnum c - fromEnum 'A')) <$> (char '^' *> upper)
+  charNum     = toEnum . fromInteger <$> num where
+    num = decimal 
+      <|> (char 'o' *> number 8 octDigit)
+      <|> (char 'x' *> number 16 hexDigit)
+  charEsc = choice $ parseEsc <$> escMap
+  parseEsc (c,code) = code <$ char c
+  escMap = zip ("abfnrtv\\\"\'") ("\a\b\f\n\r\t\v\\\"\'")
+  charAscii = choice $ parseAscii <$> asciiMap
+  parseAscii (asc,code) = try $ code <$ string asc
+  asciiMap = zip (ascii3codes ++ ascii2codes) (ascii3 ++ ascii2)
+  ascii2codes, ascii3codes :: [String]
+  ascii2codes = [ "BS","HT","LF","VT","FF","CR","SO"
+                , "SI","EM","FS","GS","RS","US","SP"]
+  ascii3codes = ["NUL","SOH","STX","ETX","EOT","ENQ","ACK"
+                ,"BEL","DLE","DC1","DC2","DC3","DC4","NAK"
+                ,"SYN","ETB","CAN","SUB","ESC","DEL"]
+  ascii2, ascii3 :: [Char]
+  ascii2 = ['\BS','\HT','\LF','\VT','\FF','\CR','\SO','\SI'
+           ,'\EM','\FS','\GS','\RS','\US','\SP']
+  ascii3 = ['\NUL','\SOH','\STX','\ETX','\EOT','\ENQ','\ACK'
+           ,'\BEL','\DLE','\DC1','\DC2','\DC3','\DC4','\NAK'
+           ,'\SYN','\ETB','\CAN','\SUB','\ESC','\DEL']
+  
+
+-- | This lexeme parser parses a natural number (a positive whole
+-- number). Returns the value of the number. The number can be
+-- specified in 'decimal', 'hexadecimal' or
+-- 'octal'. The number is parsed according to the grammar
+-- rules in the Haskell report. 
+
+natural :: MonadTokenParser m => m Integer
+natural = lexeme nat <?> "natural"
+
+number :: MonadTokenParser m => Integer -> m Char -> m Integer
+number base baseDigit = do
+  digits <- some baseDigit
+  return $! foldl (\x d -> base*x + toInteger (digitToInt d)) 0 digits
+
+-- | This lexeme parser parses an integer (a whole number). This parser
+-- is like 'natural' except that it can be prefixed with
+-- sign (i.e. \'-\' or \'+\'). Returns the value of the number. The
+-- number can be specified in 'decimal', 'hexadecimal'
+-- or 'octal'. The number is parsed according
+-- to the grammar rules in the Haskell report. 
+        
+integer :: MonadTokenParser m => m Integer
+integer = lexeme int <?> "integer"
+
+sign :: MonadTokenParser m => m (Integer -> Integer)
+sign = negate <$ char '-'
+   <|> id <$ char '+'
+   <|> pure id
+
+nat, int, zeroNumber :: MonadTokenParser m => m Integer
+nat = zeroNumber <|> decimal
+int = lexeme sign <*> nat
+zeroNumber = char '0' *> (hexadecimal <|> octal <|> decimal <|> return 0) <?> ""
+
+-- | This lexeme parser parses a floating point value. Returns the value
+-- of the number. The number is parsed according to the grammar rules
+-- defined in the Haskell report. 
+
+double :: MonadTokenParser m => m Double
+double = lexeme floating <?> "double"
+
+floating :: MonadTokenParser m => m Double
+floating = decimal >>= fractExponent
+
+fractExponent :: MonadTokenParser m => Integer -> m Double
+fractExponent n = (\fract expo -> (fromInteger n + fract) * expo) <$> fraction <*> option 1.0 exponent'
+              <|> (fromInteger n *) <$> exponent' where
+  fraction = foldr op 0.0 <$> (char '.' *> (some digit <?> "fraction"))
+  op d f = (f + fromIntegral (digitToInt d))/10.0
+  exponent' = do
+       _ <- oneOf "eE"
+       f <- sign
+       e <- decimal <?> "exponent"
+       return (power (f e))
+    <?> "exponent"
+  power e  
+    | e < 0     = 1.0/power(-e)
+    | otherwise = fromInteger (10^e)
+
+-- | This lexeme parser parses either 'natural' or a 'float'.
+-- Returns the value of the number. This parsers deals with
+-- any overlap in the grammar rules for naturals and floats. The number
+-- is parsed according to the grammar rules defined in the Haskell report. 
+
+naturalOrDouble :: MonadTokenParser m => m (Either Integer Double)
+naturalOrDouble = lexeme natDouble <?> "number"
+
+natDouble , zeroNumFloat, decimalFloat :: MonadTokenParser m => m (Either Integer Double)
+natDouble 
+    = char '0' *> zeroNumFloat
+  <|> decimalFloat
+zeroNumFloat
+    = Left <$> (hexadecimal <|> octal)
+  <|> decimalFloat
+  <|> fractFloat 0
+  <|> return (Left 0)
+decimalFloat = do 
+  n <- decimal
+  option (Left n) (fractFloat n)
+
+fractFloat :: MonadTokenParser m => Integer -> m (Either Integer Double)
+fractFloat n = Right <$> fractExponent n
+
+-- | Parses a positive whole number in the decimal system. Returns the
+-- value of the number. 
+
+decimal :: MonadTokenParser m => m Integer
+decimal = number 10 digit
+
+-- | Parses a positive whole number in the hexadecimal system. The number
+-- should be prefixed with \"0x\" or \"0X\". Returns the value of the
+-- number. 
+
+hexadecimal :: MonadTokenParser m => m Integer
+hexadecimal = oneOf "xX" *> number 16 hexDigit
+
+-- | Parses a positive whole number in the octal system. The number
+-- should be prefixed with \"0o\" or \"0O\". Returns the value of the
+-- number. 
+
+octal :: MonadTokenParser m => m Integer
+octal = oneOf "oO" *> number 8 octDigit
+
+-- | Lexeme parser @symbol s@ parses 'string' @s@ and skips
+-- trailing white space. 
+
+symbol :: MonadTokenParser m => ByteString -> m ByteString
+symbol name = lexeme (byteString name)
+
+-- | Lexeme parser @symbolic s@ parses 'char' @s@ and skips
+-- trailing white space. 
+
+symbolic :: MonadTokenParser m => Char -> m Char
+symbolic name = lexeme (char name)
+
+-- | @lexeme p@ first applies parser @p@ and than the 'whiteSpace'
+-- parser, returning the value of @p@. Every lexical
+-- token (lexeme) is defined using @lexeme@, this way every parse
+-- starts at a point without white space. Parsers that use @lexeme@ are
+-- called /lexeme/ parsers in this document.
+-- 
+-- The only point where the 'whiteSpace' parser should be
+-- called explicitly is the start of the main parser in order to skip
+-- any leading white space.
+--
+-- >    mainParser  = do{ whiteSpace
+-- >                     ; ds <- many (lexeme digit)
+-- >                     ; eof
+-- >                     ; return (sum ds)
+-- >                     }
+
+lexeme :: MonadTokenParser m => m a -> m a
+lexeme p = p <* whiteSpace
+
+-- | Lexeme parser @parens p@ parses @p@ enclosed in parenthesis,
+-- returning the value of @p@.
+
+parens :: MonadTokenParser m => m a -> m a
+parens = between (symbolic '(') (symbolic ')')
+
+
+-- | Lexeme parser @braces p@ parses @p@ enclosed in braces (\'{\' and
+-- \'}\'), returning the value of @p@. 
+
+braces :: MonadTokenParser m => m a -> m a
+braces = between (symbolic '{') (symbolic '}')
+
+-- | Lexeme parser @angles p@ parses @p@ enclosed in angle brackets (\'\<\'
+-- and \'>\'), returning the value of @p@. 
+
+angles :: MonadTokenParser m => m a -> m a
+angles = between (symbolic '<') (symbolic '>')
+
+-- | Lexeme parser @brackets p@ parses @p@ enclosed in brackets (\'[\'
+-- and \']\'), returning the value of @p@. 
+
+brackets :: MonadTokenParser m => m a -> m a
+brackets = between (symbolic '<') (symbolic '>')
+
+-- | Lexeme parser |semi| parses the character \';\' and skips any
+-- trailing white space. Returns the string \";\". 
+
+semi :: MonadTokenParser m => m Char
+semi = symbolic ';'
+
+-- | Lexeme parser @comma@ parses the character \',\' and skips any
+-- trailing white space. Returns the string \",\". 
+
+comma :: MonadTokenParser m => m Char
+comma = symbolic ','
+
+-- | Lexeme parser @colon@ parses the character \':\' and skips any
+-- trailing white space. Returns the string \":\". 
+
+colon :: MonadTokenParser m => m Char
+colon = symbolic ':'
+
+-- | Lexeme parser @dot@ parses the character \'.\' and skips any
+-- trailing white space. Returns the string \".\". 
+
+dot :: MonadTokenParser m => m Char
+dot = symbolic '.'
+
+-- | Lexeme parser @semiSep p@ parses /zero/ or more occurrences of @p@
+-- separated by 'semi'. Returns a list of values returned by
+-- @p@.
+
+semiSep :: MonadTokenParser m => m a -> m [a]
+semiSep p = sepBy p semi
+
+-- | Lexeme parser @semiSep1 p@ parses /one/ or more occurrences of @p@
+-- separated by 'semi'. Returns a list of values returned by @p@. 
+
+semiSep1 :: MonadTokenParser m => m a -> m [a]
+semiSep1 p = sepBy1 p semi
+
+-- | Lexeme parser @commaSep p@ parses /zero/ or more occurrences of
+-- @p@ separated by 'comma'. Returns a list of values returned
+-- by @p@. 
+
+commaSep :: MonadTokenParser m => m a -> m [a]
+commaSep p = sepBy p comma
+
+-- | Lexeme parser @commaSep1 p@ parses /one/ or more occurrences of
+-- @p@ separated by 'comma'. Returns a list of values returned
+-- by @p@. 
+
+commaSep1 :: MonadTokenParser m => m a -> m [a]
+commaSep1 p = sepBy p comma
diff --git a/Text/Trifecta/Parser/Token/Class.hs b/Text/Trifecta/Parser/Token/Class.hs
new file mode 100644
--- /dev/null
+++ b/Text/Trifecta/Parser/Token/Class.hs
@@ -0,0 +1,60 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Text.Trifecta.Parser.Token.Class
+-- Copyright   :  (c) Edward Kmett 2011
+-- License     :  BSD3
+-- 
+-- Maintainer  :  ekmett@gmail.com
+-- Stability   :  experimental
+-- Portability :  non-portable
+-- 
+-----------------------------------------------------------------------------
+module Text.Trifecta.Parser.Token.Class
+  ( MonadTokenParser(..)
+  ) where
+
+import Control.Monad.Trans.Class
+import Data.ByteString
+import Control.Monad.Trans.State.Lazy as Lazy
+import Text.Trifecta.Parser.Class
+
+class MonadParser m => MonadTokenParser m where
+  -- | Parses any white space. White space consists of /zero/ or more
+  -- occurrences of a 'space', a line comment or a block (multi
+  -- line) comment. Block comments may be nested. How comments are
+  -- started and ended is defined by this method.
+  whiteSpace       :: m ()
+
+  -- | This parser should accept any start characters of identifiers. For
+  -- example @letter \<|> char \"_\"@. 
+  identStart       :: m Char
+
+  -- | This parser should accept any legal tail characters of identifiers.
+  -- For example @alphaNum \<|> char \"_\"@. 
+  identLetter      :: m Char
+
+  -- | This parser should accept any start characters of operators. For
+  -- example @oneOf \":!#$%&*+.\/\<=>?\@\\\\^|-~\"@  
+  opStart          :: m Char
+
+  -- | This parser should accept any legal tail characters of operators.
+  -- Note that this parser should even be defined if the language doesn't
+  -- support user-defined operators, or otherwise the 'reservedOp'
+  -- parser won't work correctly.  
+  opLetter         :: m Char
+
+  -- | Check the list of reserved identifiers.  
+  isReservedName   :: ByteString -> m Bool
+
+  -- | Check the list of reserved operators. 
+  isReservedOpName :: ByteString -> m Bool
+
+instance MonadTokenParser m => MonadTokenParser (Lazy.StateT s m) where
+  whiteSpace = lift whiteSpace
+  identStart = lift identStart
+  identLetter = lift identLetter
+  opStart = lift opStart
+  opLetter = lift opLetter
+  isReservedName = lift . isReservedName
+  isReservedOpName = lift . isReservedOpName
+
diff --git a/trifecta.cabal b/trifecta.cabal
--- a/trifecta.cabal
+++ b/trifecta.cabal
@@ -1,6 +1,6 @@
 name:          trifecta
-category:      Text, Parsing
-version:       0.14
+category:      Text, Parsing, Diagnostics, Pretty Printer, Logging
+version:       0.15
 license:       BSD3
 cabal-version: >= 1.6
 license-file:  LICENSE
@@ -9,8 +9,8 @@
 stability:     experimental
 homepage:      http://github.com/ekmett/trifecta/
 copyright:     Copyright (C) 2011 Edward A. Kmett
-synopsis:      Parser combinators with slicing and diagnostic support
-description:   Parser combinators with slicing and diagnostic support
+synopsis:      A modern parser combinator library with convenient diagnostics
+description:   A modern parser combinator library with slicing and Clang-style colored diagnostics
 build-type:    Simple  
 
 source-repository head
@@ -18,32 +18,6 @@
   location: git://github.com/ekmett/trifecta.git
 
 library
-  build-depends: 
-    base               >= 4       && < 5,
-    array              >= 0.3.0.2 && < 0.4, 
-    containers         >= 0.3     && < 0.5,
-    bifunctors         >= 0.1.1.3 && < 0.2,
-    intern             >= 0.5.1.1 && < 0.6,
-    hashable           >= 1.1.2.1 && < 1.2,
-    bytestring         >= 0.9.1   && < 0.10,
-    mtl                >= 2.0.1   && < 2.1,
-    semigroups         >= 0.7.1   && < 0.8, 
-    fingertree         >= 0.0.1   && < 0.1,
-    reducers           >= 0.1.2   && < 0.2,
-    profunctors        >= 0.1.1   && < 0.2,
-    parsec             >= 3.1.1   && < 3.2,
-    utf8-string        >= 0.3.6   && < 0.4,
-    semigroupoids      >= 1.2.4   && < 1.3,
-    parallel           >= 3.1.0.1 && < 3.2,
-    transformers       >= 0.2.2   && < 0.3,
-    comonad            >= 1.1.1   && < 1.2,
-    terminfo           >= 0.3.2   && < 0.4,
-    keys               >= 2.0.1   && < 2.1,
-    wl-pprint-extras   >= 1.4     && < 1.5,
-    wl-pprint-terminfo >= 0.4     && < 0.5
-
-  ghc-options: -Wall
-
   exposed-modules:
     Text.Trifecta
     Text.Trifecta.Bytes
@@ -58,7 +32,10 @@
     Text.Trifecta.Parser.Err.State
     Text.Trifecta.Parser.Prim
     Text.Trifecta.Parser.Result
+    Text.Trifecta.Parser.Combinators
     Text.Trifecta.Parser.Step
+    Text.Trifecta.Parser.Token
+    Text.Trifecta.Parser.Token.Class
     Text.Trifecta.Path
     Text.Trifecta.Render.Prim
     Text.Trifecta.Render.Caret
@@ -70,3 +47,29 @@
 
   other-modules:
     Text.Trifecta.Util
+
+  ghc-options: -Wall
+
+  build-depends: 
+    base               >= 4       && < 5,
+    array              >= 0.3.0.2 && < 0.4, 
+    containers         >= 0.3     && < 0.5,
+    bifunctors         >= 0.1.1.3 && < 0.2,
+    intern             >= 0.5.1.1 && < 0.6,
+    hashable           >= 1.1.2.1 && < 1.2,
+    bytestring         >= 0.9.1   && < 0.10,
+    mtl                >= 2.0.1   && < 2.1,
+    semigroups         >= 0.7.1   && < 0.8, 
+    fingertree         >= 0.0.1   && < 0.1,
+    reducers           >= 0.1.2   && < 0.2,
+    profunctors        >= 0.1.1   && < 0.2,
+    parsec             >= 3.1.1   && < 3.2,
+    utf8-string        >= 0.3.6   && < 0.4,
+    semigroupoids      >= 1.2.4   && < 1.3,
+    parallel           >= 3.1.0.1 && < 3.2,
+    transformers       >= 0.2.2   && < 0.3,
+    comonad            >= 1.1.1   && < 1.2,
+    terminfo           >= 0.3.2   && < 0.4,
+    keys               >= 2.0.1   && < 2.1,
+    wl-pprint-extras   >= 1.4     && < 1.5,
+    wl-pprint-terminfo >= 0.4     && < 0.5
