diff --git a/bench/FPStateful.hs b/bench/FPStateful.hs
--- a/bench/FPStateful.hs
+++ b/bench/FPStateful.hs
@@ -6,19 +6,22 @@
 
 import FlatParse.Stateful
 
+ws, open, close, ident, sexp, src :: Parser () () ()
 ws      = many_ $(switch [| case _ of " " -> pure (); "\n" -> pure () |])
 open    = $(char '(') >> ws
 close   = $(char ')') >> ws
 ident   = some_ (satisfyASCII_ isLatinLetter) >> ws
 sexp    = branch open (some_ sexp >> close) ident
 src     = sexp >> eof
-runSexp = runParser src 0 0
+runSexp = runParser src () 0
 
+longw, longws :: Parser () () ()
 longw     = $(string "thisisalongkeyword")
 longws    = some_ (longw >> ws) >> eof
-runLongws = runParser longws 0 0
+runLongws = runParser longws () 0
 
+numeral, comma, numcsv :: Parser () () ()
 numeral   = some_ (satisfyASCII_ isDigit) >> ws
 comma     = $(char ',') >> ws
 numcsv    = numeral >> many_ (comma >> numeral) >> eof
-runNumcsv = runParser numcsv 0 0
+runNumcsv = runParser numcsv () 0
diff --git a/flatparse.cabal b/flatparse.cabal
--- a/flatparse.cabal
+++ b/flatparse.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           flatparse
-version:        0.3.4.0
+version:        0.3.5.0
 synopsis:       High-performance parsing from strict bytestrings
 description:    @Flatparse@ is a high-performance parsing library, focusing on programming languages and
                 human-readable data formats. See the README for more information:
@@ -73,7 +73,7 @@
     , integer-gmp
     , template-haskell
   if flag(dump)
-    ghc-options: -ddump-simpl -ddump-stg -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
+    ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
   if flag(llvm)
     ghc-options: -fllvm
   default-language: Haskell2010
@@ -107,7 +107,7 @@
     , hspec
     , quickcheck-instances
   if flag(dump)
-    ghc-options: -ddump-simpl -ddump-stg -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
+    ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
   if flag(llvm)
     ghc-options: -fllvm
   default-language: Haskell2010
@@ -148,7 +148,7 @@
     , parsec
     , primitive
   if flag(dump)
-    ghc-options: -ddump-simpl -ddump-stg -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
+    ghc-options: -ddump-simpl -ddump-stg-final -ddump-cmm -dsuppress-all -dno-suppress-type-signatures -ddump-to-file
   if flag(llvm)
     ghc-options: -fllvm
   default-language: Haskell2010
diff --git a/src/FlatParse/Basic.hs b/src/FlatParse/Basic.hs
--- a/src/FlatParse/Basic.hs
+++ b/src/FlatParse/Basic.hs
@@ -3,6 +3,9 @@
 {-|
 This module implements a `Parser` supporting custom error types.  If you need efficient indentation
 parsing, use "FlatParse.Stateful" instead.
+
+Many internals are exposed for hacking on and extending. These are generally
+denoted by a @#@ hash suffix.
 -}
 
 module FlatParse.Basic (
@@ -27,7 +30,7 @@
   , try
   , optional
   , optional_
-  , optioned
+  , withOption
   , cut
   , cutting
 
@@ -35,6 +38,7 @@
   , eof
   , takeBs
   , takeRestBs
+  , skip
   , char
   , byte
   , bytes
@@ -72,6 +76,7 @@
   , FlatParse.Internal.isLatinLetter
   , FlatParse.Basic.readInt
   , FlatParse.Basic.readInteger
+  , anyCString
 
   -- ** Explicit-endianness machine integers
   , anyWord16le
@@ -106,9 +111,9 @@
   , setPos
   , endPos
   , spanOf
-  , spanned
+  , withSpan
   , byteStringOf
-  , byteStringed
+  , withByteString
   , inSpan
 
   -- ** Position and span conversions
@@ -131,14 +136,19 @@
 
   -- * Internal functions
   , ensureBytes#
-  , scan8#
-  , scan16#
-  , scan32#
-  , scan64#
-  , scanAny8#
-  , scanBytes#
+
+  -- ** Unboxed arguments
+  , takeBs#
+  , atSkip#
+
+  -- *** Location & address primitives
   , setBack#
+  , withAddr#
+  , takeBsOffAddr#
+  , lookaheadFromAddr#
+  , atAddr#
 
+  -- ** Machine integer continuation parsers
   , withAnyWord8#
   , withAnyWord16#
   , withAnyWord32#
@@ -148,6 +158,15 @@
   , withAnyInt32#
   , withAnyInt64#
 
+  -- ** Unsafe
+  , anyCStringUnsafe
+  , scan8#
+  , scan16#
+  , scan32#
+  , scan64#
+  , scanAny8#
+  , scanBytes#
+
   ) where
 
 import Control.Monad
@@ -317,7 +336,7 @@
   x      -> x
 {-# inline try #-}
 
--- | Convert a parsing failure to a `Maybe`. If possible, use `optioned` instead.
+-- | Convert a parsing failure to a `Maybe`. If possible, use `withOption` instead.
 optional :: Parser e a -> Parser e (Maybe a)
 optional p = (Just <$> p) <|> pure Nothing
 {-# inline optional #-}
@@ -329,12 +348,12 @@
 
 -- | CPS'd version of `optional`. This is usually more efficient, since it gets rid of the
 --   extra `Maybe` allocation.
-optioned :: Parser e a -> (a -> Parser e b) -> Parser e b -> Parser e b
-optioned (Parser f) just (Parser nothing) = Parser \fp eob s -> case f fp eob s of
+withOption :: Parser e a -> (a -> Parser e b) -> Parser e b -> Parser e b
+withOption (Parser f) just (Parser nothing) = Parser \fp eob s -> case f fp eob s of
   OK# a s -> runParser# (just a) fp eob s
   Fail#   -> nothing fp eob s
   Err# e  -> Err# e
-{-# inline optioned #-}
+{-# inline withOption #-}
 
 -- | Convert a parsing failure to an error.
 cut :: Parser e a -> e -> Parser e a
@@ -367,12 +386,7 @@
 --
 -- Throws a runtime error if given a negative integer.
 takeBs :: Int -> Parser e B.ByteString
-takeBs (I# n#) = Parser \fp eob s -> case n# <=# minusAddr# eob s of
-  1# -> -- have to runtime check for negative values, because they cause a hang
-    case n# >=# 0# of
-      1# -> OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) (plusAddr# s n#)
-      _  -> error "FlatParse.Basic.take: negative integer"
-  _  -> Fail#
+takeBs (I# n#) = takeBs# n#
 {-# inline takeBs #-}
 
 -- | Consume the rest of the input. May return the empty bytestring.
@@ -382,6 +396,13 @@
   in  OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) eob
 {-# inline takeRestBs #-}
 
+-- | Skip forward @n@ bytes. Fails if fewer than @n@ bytes are available.
+--
+-- Throws a runtime error if given a negative integer.
+skip :: Int -> Parser e ()
+skip (I# os#) = atSkip# os# (pure ())
+{-# inline skip #-}
+
 -- | Parse a UTF-8 character literal. This is a template function, you can use it as
 --   @$(char \'x\')@, for example, and the splice in this case has type @Parser e ()@.
 char :: Char -> Q Exp
@@ -780,14 +801,14 @@
 
 -- | Bind the result together with the span of the result. CPS'd version of `spanOf`
 --   for better unboxing.
-spanned :: Parser e a -> (a -> Span -> Parser e b) -> Parser e b
-spanned (Parser f) g = Parser \fp eob s -> case f fp eob s of
+withSpan :: Parser e a -> (a -> Span -> Parser e b) -> Parser e b
+withSpan (Parser f) g = Parser \fp eob s -> case f fp eob s of
   OK# a s' -> runParser# (g a (Span (addrToPos# eob s) (addrToPos# eob s'))) fp eob s'
   x        -> unsafeCoerce# x
-{-# inline spanned #-}
+{-# inline withSpan #-}
 
 -- | Return the `B.ByteString` consumed by a parser. Note: it's more efficient to use `spanOf` and
---   `spanned` instead.
+--   `withSpan` instead.
 byteStringOf :: Parser e a -> Parser e B.ByteString
 byteStringOf (Parser f) = Parser \fp eob s -> case f fp eob s of
   OK# a s' -> OK# (B.PS (ForeignPtr s fp) 0 (I# (minusAddr# s' s))) s'
@@ -795,17 +816,17 @@
 {-# inline byteStringOf #-}
 
 -- | CPS'd version of `byteStringOf`. Can be more efficient, because the result is more eagerly unboxed
---   by GHC. It's more efficient to use `spanOf` or `spanned` instead.
-byteStringed :: Parser e a -> (a -> B.ByteString -> Parser e b) -> Parser e b
-byteStringed (Parser f) g = Parser \fp eob s -> case f fp eob s of
+--   by GHC. It's more efficient to use `spanOf` or `withSpan` instead.
+withByteString :: Parser e a -> (a -> B.ByteString -> Parser e b) -> Parser e b
+withByteString (Parser f) g = Parser \fp eob s -> case f fp eob s of
   OK# a s' -> runParser# (g a (B.PS (ForeignPtr s fp) 0 (I# (minusAddr# s' s)))) fp eob s'
   x        -> unsafeCoerce# x
-{-# inline byteStringed #-}
+{-# inline withByteString #-}
 
 -- | Run a parser in a given input span. The input position and the `Int` state is restored after
 --   the parser is finished, so `inSpan` does not consume input and has no side effect.  Warning:
 --   this operation may crash if the given span points outside the current parsing buffer. It's
---   always safe to use `inSpan` if the span comes from a previous `spanned` or `spanOf` call on
+--   always safe to use `inSpan` if the span comes from a previous `withSpan` or `spanOf` call on
 --   the current input.
 inSpan :: Span -> Parser e a -> Parser e a
 inSpan (Span s eob) (Parser f) = Parser \fp eob' s' ->
@@ -1300,3 +1321,115 @@
 anyInt64be :: Parser e Int64
 anyInt64be = withAnyWord64# (\w# -> pure (I64# (word2Int# (byteSwap# w#))))
 {-# inline anyInt64be #-}
+
+--------------------------------------------------------------------------------
+
+-- | Skip forward @n@ bytes and run the given parser. Fails if fewer than @n@
+--   bytes are available.
+--
+-- Throws a runtime error if given a negative integer.
+atSkip# :: Int# -> Parser e a -> Parser e a
+atSkip# os# (Parser p) = Parser \fp eob s -> case os# <=# minusAddr# eob s of
+  1# -> case os# >=# 0# of
+    1# -> p fp eob (plusAddr# s os#)
+    _  -> error "FlatParse.Basic.atSkip#: negative integer"
+  _  -> Fail#
+{-# inline atSkip# #-}
+
+-- | Read the given number of bytes as a 'ByteString'.
+--
+-- Throws a runtime error if given a negative integer.
+takeBs# :: Int# -> Parser e B.ByteString
+takeBs# n# = Parser \fp eob s -> case n# <=# minusAddr# eob s of
+  1# -> -- have to runtime check for negative values, because they cause a hang
+    case n# >=# 0# of
+      1# -> OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) (plusAddr# s n#)
+      _  -> error "FlatParse.Basic.takeBs: negative integer"
+  _  -> Fail#
+{-# inline takeBs# #-}
+
+--------------------------------------------------------------------------------
+
+-- | Run a parser, passing it the current address the parser is at.
+--
+-- Useful for parsing offset-based data tables. For example, you may use this to
+-- save the base address to use together with various 0-indexed offsets.
+withAddr# :: (Addr# -> Parser e a) -> Parser e a
+withAddr# p = Parser \fp eob s -> runParser# (p s) fp eob s
+{-# inline withAddr# #-}
+
+-- | @takeBsOffAddr# addr# offset# len#@ moves to @addr#@, skips @offset#@
+--   bytes, reads @len#@ bytes into a 'ByteString', and restores the original
+--   address.
+--
+-- The 'Addr#' should be from 'withAddr#'.
+--
+-- Useful for parsing offset-based data tables. For example, you may use this
+-- together with 'withAddr#' to jump to an offset in your input and read some
+-- data.
+takeBsOffAddr# :: Addr# -> Int# -> Int# -> Parser e B.ByteString
+takeBsOffAddr# addr# offset# len# =
+    lookaheadFromAddr# addr# $ atSkip# offset# $ takeBs# len#
+{-# inline takeBsOffAddr# #-}
+
+-- | 'lookahead', but specify the address to lookahead from.
+--
+-- The 'Addr#' should be from 'withAddr#'.
+lookaheadFromAddr# :: Addr# -> Parser e a -> Parser e a
+lookaheadFromAddr# s = lookahead . atAddr# s
+{-# inline lookaheadFromAddr# #-}
+
+-- | Run a parser at the given address.
+--
+-- The 'Addr#' should be from 'withAddr#'.
+--
+-- This is a highly internal function -- you likely want 'lookaheadFromAddr#',
+-- which will reset the address after running the parser.
+atAddr# :: Addr# -> Parser e a -> Parser e a
+atAddr# s (Parser p) = Parser \fp eob _ -> p fp eob s
+{-# inline atAddr# #-}
+
+--------------------------------------------------------------------------------
+
+-- | Read a null-terminated bytestring (a C-style string).
+--
+-- Consumes the null terminator.
+anyCString :: Parser e B.ByteString
+anyCString = Parser \fp eob s -> go' fp eob s
+  where
+    go' fp eob s0 = go 0# s0
+      where
+        go n# s = case eqAddr# eob s of
+          1# -> Fail#
+          _  ->
+            let s' = plusAddr# s 1#
+            -- TODO below is a candidate for improving with ExtendedLiterals!
+            in  case eqWord8# (indexWord8OffAddr''# s 0#) (wordToWord8''# 0##) of
+                  1# -> OK# (B.PS (ForeignPtr s0 fp) 0 (I# n#)) s'
+                  _  -> go (n# +# 1#) s'
+{-# inline anyCString #-}
+
+-- | Read a null-terminated bytestring (a C-style string), where the bytestring
+--   is known to be null-terminated somewhere in the input.
+--
+-- Highly unsafe. Unless you have a guarantee that the string will be null
+-- terminated before the input ends, use 'anyCString' instead. Honestly, I'm not
+-- sure if this is a good function to define. But here it is.
+--
+-- Fails on GHC versions older than 9.0, since we make use of the
+-- 'cstringLength#' primop introduced in GHC 9.0, and we aren't very useful
+-- without it.
+--
+-- Consumes the null terminator.
+anyCStringUnsafe :: Parser e B.ByteString
+{-# inline anyCStringUnsafe #-}
+#if MIN_VERSION_base(4,15,0)
+anyCStringUnsafe = Parser \fp eob s ->
+  case eqAddr# eob s of
+    1# -> Fail#
+    _  -> let n#  = cstringLength# s
+              s'# = plusAddr# s (n# +# 1#)
+           in OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) s'#
+#else
+anyCStringUnsafe = error "Flatparse.Basic.anyCStringUnsafe: requires GHC 9.0 / base-4.15, not available on this compiler"
+#endif
diff --git a/src/FlatParse/Examples/BasicLambda/Lexer.hs b/src/FlatParse/Examples/BasicLambda/Lexer.hs
--- a/src/FlatParse/Examples/BasicLambda/Lexer.hs
+++ b/src/FlatParse/Examples/BasicLambda/Lexer.hs
@@ -117,7 +117,7 @@
 -- | Parse a line comment.
 lineComment :: Parser ()
 lineComment =
-  optioned anyWord8
+  withOption anyWord8
     (\case 10 -> ws
            _  -> lineComment)
     (pure ())
diff --git a/src/FlatParse/Examples/BasicLambda/Parser.hs b/src/FlatParse/Examples/BasicLambda/Parser.hs
--- a/src/FlatParse/Examples/BasicLambda/Parser.hs
+++ b/src/FlatParse/Examples/BasicLambda/Parser.hs
@@ -49,7 +49,7 @@
 --   keyword.
 ident :: Parser Name
 ident = token $ byteStringOf $
-  spanned (identStartChar *> many_ identChar) (\_ span -> fails (isKeyword span))
+  withSpan (identStartChar *> many_ identChar) (\_ span -> fails (isKeyword span))
 
 -- | Parse an identifier, throw a precise error on failure.
 ident' :: Parser Name
diff --git a/src/FlatParse/Internal/UnboxedNumerics.hs b/src/FlatParse/Internal/UnboxedNumerics.hs
--- a/src/FlatParse/Internal/UnboxedNumerics.hs
+++ b/src/FlatParse/Internal/UnboxedNumerics.hs
@@ -109,5 +109,7 @@
 extendWord8# w# = w#
 leWord8# :: Word8# -> Word8# -> Int#
 leWord8# w1# w2# = leWord# w1# w2#
+eqWord8# :: Word8# -> Word8# -> Int#
+eqWord8# w1# w2# = eqWord# w1# w2#
 
 #endif
diff --git a/src/FlatParse/Stateful.hs b/src/FlatParse/Stateful.hs
--- a/src/FlatParse/Stateful.hs
+++ b/src/FlatParse/Stateful.hs
@@ -1,8 +1,8 @@
 {-# language UnboxedTuples #-}
 
 {-|
-This module implements a `Parser` supporting an `Int` reader environment, custom error types, and an
-`Int` state.
+This module implements a `Parser` supporting a custom reader environment, custom
+error types and an `Int` state.
 -}
 
 module FlatParse.Stateful (
@@ -33,7 +33,7 @@
   , try
   , optional
   , optional_
-  , optioned
+  , withOption
   , cut
   , cutting
 
@@ -112,9 +112,9 @@
   , setPos
   , endPos
   , spanOf
-  , spanned
+  , withSpan
   , byteStringOf
-  , byteStringed
+  , withByteString
   , inSpan
 
   -- ** Position and span conversions
@@ -201,10 +201,10 @@
 pattern Fail# = (# | (# #) | #)
 {-# complete OK#, Err#, Fail# #-}
 
--- | @Parser e a@ has an error type @e@ and a return type @a@.
-newtype Parser e a = Parser {runParser# :: ForeignPtrContents -> Int# -> Addr# -> Addr# -> Int# -> Res# e a}
+-- | @Parser r e a@ has a reader environment @r@, error type @e@ and a return type @a@.
+newtype Parser r e a = Parser {runParser# :: ForeignPtrContents -> r -> Addr# -> Addr# -> Int# -> Res# e a}
 
-instance Functor (Parser e) where
+instance Functor (Parser r e) where
   fmap f (Parser g) = Parser \fp !r eob s n -> case g fp r eob s n of
     OK# a s n -> let !b = f a in OK# b s n
     x         -> unsafeCoerce# x
@@ -215,7 +215,7 @@
     x         -> unsafeCoerce# x
   {-# inline (<$) #-}
 
-instance Applicative (Parser e) where
+instance Applicative (Parser r e) where
   pure a = Parser \fp !r eob s n -> OK# a s n
   {-# inline pure #-}
   Parser ff <*> Parser fa = Parser \fp !r eob s n -> case ff fp r eob s n of
@@ -235,7 +235,7 @@
     x         -> unsafeCoerce# x
   {-# inline (*>) #-}
 
-instance Monad (Parser e) where
+instance Monad (Parser r e) where
   return = pure
   {-# inline return #-}
   Parser fa >>= f = Parser \fp !r eob s n -> case fa fp r eob s n of
@@ -262,10 +262,9 @@
 
 --------------------------------------------------------------------------------
 
--- | Run a parser. The first `Int` argument is the reader environment, while the second one is the
---   state.
-runParser :: Parser e a -> Int -> Int -> B.ByteString -> Result e a
-runParser (Parser f) (I# r) (I# n) b@(B.PS (ForeignPtr _ fp) _ (I# len)) = unsafeDupablePerformIO do
+-- | Run a parser. The `Int` argument is the initial state.
+runParser :: Parser r e a -> r -> Int -> B.ByteString -> Result e a
+runParser (Parser f) !r (I# n) b@(B.PS (ForeignPtr _ fp) _ (I# len)) = unsafeDupablePerformIO do
   B.unsafeUseAsCString b \(Ptr buf) -> do
     let end = plusAddr# buf len
     case f fp r end buf n of
@@ -281,54 +280,54 @@
 -- | Run a parser on a `String` input. Reminder: @OverloadedStrings@ for `B.ByteString` does not
 --   yield a valid UTF-8 encoding! For non-ASCII `B.ByteString` literal input, use `runParserS` or
 --   `packUTF8` for testing.
-runParserS :: Parser e a -> Int -> Int -> String -> Result e a
+runParserS :: Parser r e a -> r -> Int -> String -> Result e a
 runParserS pa r !n s = runParser pa r n (packUTF8 s)
 
 --------------------------------------------------------------------------------
 
 -- | Query the `Int` state.
-get :: Parser e Int
+get :: Parser r e Int
 get = Parser \fp !r eob s n -> OK# (I# n) s n
 {-# inline get #-}
 
 -- | Write the `Int` state.
-put :: Int -> Parser e ()
+put :: Int -> Parser r e ()
 put (I# n) = Parser \fp !r eob s _ -> OK# () s n
 {-# inline put #-}
 
 -- | Modify the `Int` state.
-modify :: (Int -> Int) -> Parser e ()
+modify :: (Int -> Int) -> Parser r e ()
 modify f = Parser \fp !r eob s n ->
   case f (I# n) of
     I# n -> OK# () s n
 {-# inline modify #-}
 
--- | Query the `Int` environment.
-ask :: Parser e Int
-ask = Parser \fp !r eob s n -> OK# (I# r) s n
+-- | Query the environment.
+ask :: Parser r e r
+ask = Parser \fp !r eob s n -> OK# r s n
 {-# inline ask #-}
 
 -- | Run a parser in a modified environment.
-local :: (Int -> Int) -> Parser e a -> Parser e a
-local f (Parser g) = Parser \fp !r eob s n -> let !(I# r') = f (I# r) in g fp r' eob s n
+local :: (r -> r) -> Parser r e a -> Parser r e a
+local f (Parser g) = Parser \fp !r eob s n -> let !r' = f r in g fp r' eob s n
 {-# inline local #-}
 
 --------------------------------------------------------------------------------
 
 -- | The failing parser. By default, parser choice `(<|>)` arbitrarily backtracks
 --   on parser failure.
-empty :: Parser e a
+empty :: Parser r e a
 empty = Parser \fp !r eob s n -> Fail#
 {-# inline empty #-}
 
 -- | Throw a parsing error. By default, parser choice `(<|>)` can't backtrack
 --   on parser error. Use `try` to convert an error to a recoverable failure.
-err :: e -> Parser e a
+err :: e -> Parser r e a
 err e = Parser \fp !r eob s n -> Err# e
 {-# inline err #-}
 
 -- | Save the parsing state, then run a parser, then restore the state.
-lookahead :: Parser e a -> Parser e a
+lookahead :: Parser r e a -> Parser r e a
 lookahead (Parser f) = Parser \fp !r eob s n ->
   case f fp r eob s n of
     OK# a _ _ -> OK# a s n
@@ -336,7 +335,7 @@
 {-# inline lookahead #-}
 
 -- | Convert a parsing failure to a success.
-fails :: Parser e a -> Parser e ()
+fails :: Parser r e a -> Parser r e ()
 fails (Parser f) = Parser \fp !r eob s n ->
   case f fp r eob s n of
     OK# _ _ _ -> Fail#
@@ -345,33 +344,33 @@
 {-# inline fails #-}
 
 -- | Convert a parsing error into failure.
-try :: Parser e a -> Parser e a
+try :: Parser r e a -> Parser r e a
 try (Parser f) = Parser \fp !r eob s n -> case f fp r eob s n of
   Err# _ -> Fail#
   x      -> x
 {-# inline try #-}
 
--- | Convert a parsing failure to a `Maybe`. If possible, use `optioned` instead.
-optional :: Parser e a -> Parser e (Maybe a)
+-- | Convert a parsing failure to a `Maybe`. If possible, use `withOption` instead.
+optional :: Parser r e a -> Parser r e (Maybe a)
 optional p = (Just <$> p) <|> pure Nothing
 {-# inline optional #-}
 
 -- | Convert a parsing failure to a `()`.
-optional_ :: Parser e a -> Parser e ()
+optional_ :: Parser r e a -> Parser r e ()
 optional_ p = (() <$ p) <|> pure ()
 {-# inline optional_ #-}
 
 -- | CPS'd version of `optional`. This is usually more efficient, since it gets rid of the
 --   extra `Maybe` allocation.
-optioned :: Parser e a -> (a -> Parser e b) -> Parser e b -> Parser e b
-optioned (Parser f) just (Parser nothing) = Parser \fp !r eob s n -> case f fp r eob s n of
+withOption :: Parser r e a -> (a -> Parser r e b) -> Parser r e b -> Parser r e b
+withOption (Parser f) just (Parser nothing) = Parser \fp !r eob s n -> case f fp r eob s n of
   OK# a s n -> runParser# (just a) fp r eob s n
   Fail#     -> nothing fp r eob s n
   Err# e    -> Err# e
-{-# inline optioned #-}
+{-# inline withOption #-}
 
 -- | Convert a parsing failure to an error.
-cut :: Parser e a -> e -> Parser e a
+cut :: Parser r e a -> e -> Parser r e a
 cut (Parser f) e = Parser \fp !r eob s n -> case f fp r eob s n of
   Fail# -> Err# e
   x     -> x
@@ -380,7 +379,7 @@
 -- | Run the parser, if we get a failure, throw the given error, but if we get an error, merge the
 --   inner and the newly given errors using the @e -> e -> e@ function. This can be useful for
 --   implementing parsing errors which may propagate hints or accummulate contextual information.
-cutting :: Parser e a -> e -> (e -> e -> e) -> Parser e a
+cutting :: Parser r e a -> e -> (e -> e -> e) -> Parser r e a
 cutting (Parser f) e merge = Parser \fp !r eob s n -> case f fp r eob s n of
   Fail#   -> Err# e
   Err# e' -> let !e'' = merge e' e in Err# e''
@@ -391,7 +390,7 @@
 
 
 -- | Succeed if the input is empty.
-eof :: Parser e ()
+eof :: Parser r e ()
 eof = Parser \fp !r eob s n -> case eqAddr# eob s of
   1# -> OK# () s n
   _  -> Fail#
@@ -400,7 +399,7 @@
 -- | Read the given number of bytes as a 'ByteString'.
 --
 -- Throws a runtime error if given a negative integer.
-takeBs :: Int -> Parser e B.ByteString
+takeBs :: Int -> Parser r e B.ByteString
 takeBs (I# n#) = Parser \fp !r eob s n -> case n# <=# minusAddr# eob s of
   1# -> -- have to runtime check for negative values, because they cause a hang
     case n# >=# 0# of
@@ -410,31 +409,31 @@
 {-# inline takeBs #-}
 
 -- | Consume the rest of the input. May return the empty bytestring.
-takeRestBs :: Parser e B.ByteString
+takeRestBs :: Parser r e B.ByteString
 takeRestBs = Parser \fp !r eob s n ->
   let n# = minusAddr# eob s
   in  OK# (B.PS (ForeignPtr s fp) 0 (I# n#)) eob n
 {-# inline takeRestBs #-}
 
 -- | Parse a UTF-8 character literal. This is a template function, you can use it as
---   @$(char \'x\')@, for example, and the splice in this case has type @Parser e ()@.
+--   @$(char \'x\')@, for example, and the splice in this case has type @Parser r e ()@.
 char :: Char -> Q Exp
 char c = string [c]
 
 -- | Read a byte.
-byte :: Word8 -> Parser e ()
+byte :: Word8 -> Parser r e ()
 byte w = ensureBytes# 1 >> scan8# w
 {-# inline byte #-}
 
 -- | Read a sequence of bytes. This is a template function, you can use it as @$(bytes [3, 4, 5])@,
---   for example, and the splice has type @Parser e ()@.
+--   for example, and the splice has type @Parser r e ()@.
 bytes :: [Word] -> Q Exp
 bytes bytes = do
   let !len = length bytes
   [| ensureBytes# len >> $(scanBytes# bytes) |]
 
 -- | Parse a UTF-8 string literal. This is a template function, you can use it as @$(string "foo")@,
---   for example, and the splice has type @Parser e ()@.
+--   for example, and the splice has type @Parser r e ()@.
 string :: String -> Q Exp
 string str = bytes (strToBytes str)
 
@@ -477,7 +476,7 @@
 
 {-|
 Switch expression with an optional first argument for performing a post-processing action after
-every successful branch matching. For example, if we have @ws :: Parser e ()@ for a
+every successful branch matching. For example, if we have @ws :: Parser r e ()@ for a
 whitespace parser, we might want to consume whitespace after matching on any of the switch
 cases. For that case, we can define a "lexeme" version of `switch` as follows.
 
@@ -505,14 +504,14 @@
   genTrie $! genSwitchTrie' postAction cases fallback
 
 -- | Parse a UTF-8 `Char` for which a predicate holds.
-satisfy :: (Char -> Bool) -> Parser e Char
+satisfy :: (Char -> Bool) -> Parser r e Char
 satisfy f = Parser \fp !r eob s n -> case runParser# anyChar fp r eob s n of
   OK# c s n | f c -> OK# c s n
   _               -> Fail#
 {-#  inline satisfy #-}
 
 -- | Skip a UTF-8 `Char` for which a predicate holds.
-satisfy_ :: (Char -> Bool) -> Parser e ()
+satisfy_ :: (Char -> Bool) -> Parser r e ()
 satisfy_ f = Parser \fp !r eob s n -> case runParser# anyChar fp r eob s n of
   OK# c s n | f c -> OK# () s n
   _               -> Fail#
@@ -521,7 +520,7 @@
 -- | Parse an ASCII `Char` for which a predicate holds. Assumption: the predicate must only return
 --   `True` for ASCII-range characters. Otherwise this function might read a 128-255 range byte,
 --   thereby breaking UTF-8 decoding.
-satisfyASCII :: (Char -> Bool) -> Parser e Char
+satisfyASCII :: (Char -> Bool) -> Parser r e Char
 satisfyASCII f = Parser \fp !r eob s n -> case eqAddr# eob s of
   1# -> Fail#
   _  -> case derefChar8# s of
@@ -531,7 +530,7 @@
 
 -- | Skip an ASCII `Char` for which a predicate holds.  Assumption: the
 --   predicate must only return `True` for ASCII-range characters.
-satisfyASCII_ :: (Char -> Bool) -> Parser e ()
+satisfyASCII_ :: (Char -> Bool) -> Parser r e ()
 satisfyASCII_ f = () <$ satisfyASCII f
 {-# inline satisfyASCII_ #-}
 
@@ -545,7 +544,7 @@
 --   can do better with @fusedSatisfy isLatinLetter isLetter isLetter isLetter@, since here the
 --   `isLatinLetter` is inlined into the UTF-8 decoding, and it probably handles a great majority of
 --   all cases without accessing the character table.
-fusedSatisfy :: (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> Parser e Char
+fusedSatisfy :: (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> Parser r e Char
 fusedSatisfy f1 f2 f3 f4 = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case derefChar8# buf of
@@ -587,12 +586,12 @@
 {-# inline fusedSatisfy #-}
 
 -- | Skipping variant of `fusedSatisfy`.
-fusedSatisfy_ :: (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> Parser e ()
+fusedSatisfy_ :: (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> (Char -> Bool) -> Parser r e ()
 fusedSatisfy_ f1 f2 f3 f4 = () <$ fusedSatisfy f1 f2 f3 f4
 {-# inline fusedSatisfy_ #-}
 
 -- | Parse any UTF-8-encoded `Char`.
-anyChar :: Parser e Char
+anyChar :: Parser r e Char
 anyChar = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case derefChar8# buf of
@@ -627,7 +626,7 @@
 {-# inline anyChar #-}
 
 -- | Skip any UTF-8-encoded `Char`.
-anyChar_ :: Parser e ()
+anyChar_ :: Parser r e ()
 anyChar_ = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case derefChar8# buf of
@@ -648,7 +647,7 @@
 
 -- | Parse any `Char` in the ASCII range, fail if the next input character is not in the range.
 --   This is more efficient than `anyChar` if we are only working with ASCII.
-anyCharASCII :: Parser e Char
+anyCharASCII :: Parser r e Char
 anyCharASCII = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case derefChar8# buf of
@@ -659,20 +658,20 @@
 
 -- | Skip any `Char` in the ASCII range. More efficient than `anyChar_` if we're working only with
 --   ASCII.
-anyCharASCII_ :: Parser e ()
+anyCharASCII_ :: Parser r e ()
 anyCharASCII_ = () <$ anyCharASCII
 {-# inline anyCharASCII_ #-}
 
 -- | Read an `Int` from the input, as a non-empty digit sequence. The `Int` may
 --   overflow in the result.
-readInt :: Parser e Int
+readInt :: Parser r e Int
 readInt = Parser \fp r eob s n -> case FlatParse.Internal.readInt eob s of
   (# (##) | #)        -> Fail#
   (# | (# i, s' #) #) -> OK# (I# i) s' n
 {-# inline readInt #-}
 
 -- | Read an `Integer` from the input, as a non-empty digit sequence.
-readInteger :: Parser e Integer
+readInteger :: Parser r e Integer
 readInteger = Parser \fp r eob s n -> case FlatParse.Internal.readInteger fp eob s of
   (# (##) | #)        -> Fail#
   (# | (# i, s' #) #) -> OK# i s' n
@@ -684,7 +683,7 @@
 -- | Choose between two parsers. If the first parser fails, try the second one, but if the first one
 --   throws an error, propagate the error.
 infixr 6 <|>
-(<|>) :: Parser e a -> Parser e a -> Parser e a
+(<|>) :: Parser r e a -> Parser r e a -> Parser r e a
 (<|>) (Parser f) (Parser g) = Parser \fp !r eob s n ->
   case f fp r eob s n of
     Fail# -> g fp r eob s n
@@ -694,7 +693,7 @@
 -- | Branch on a parser: if the first argument succeeds, continue with the second, else with the third.
 --   This can produce slightly more efficient code than `(<|>)`. Moreover, `ḃranch` does not
 --   backtrack from the true/false cases.
-branch :: Parser e a -> Parser e b -> Parser e b -> Parser e b
+branch :: Parser r e a -> Parser r e b -> Parser r e b -> Parser r e b
 branch pa pt pf = Parser \fp !r eob s n -> case runParser# pa fp r eob s n of
   OK# _ s n -> runParser# pt fp r eob s n
   Fail#     -> runParser# pf fp r eob s n
@@ -704,7 +703,7 @@
 -- | An analogue of the list `foldl` function: first parse a @b@, then parse zero or more @a@-s,
 --   and combine the results in a left-nested way by the @b -> a -> b@ function. Note: this is not
 --   the usual `chainl` function from the parsec libraries!
-chainl :: (b -> a -> b) -> Parser e b -> Parser e a -> Parser e b
+chainl :: (b -> a -> b) -> Parser r e b -> Parser r e a -> Parser r e b
 chainl f start elem = start >>= go where
   go b = do {!a <- elem; go $! f b a} <|> pure b
 {-# inline chainl #-}
@@ -712,7 +711,7 @@
 -- | An analogue of the list `foldr` function: parse zero or more @a@-s, terminated by a @b@, and
 --   combine the results in a right-nested way using the @a -> b -> b@ function. Note: this is not
 --   the usual `chainr` function from the parsec libraries!
-chainr :: (a -> b -> b) -> Parser e a -> Parser e b -> Parser e b
+chainr :: (a -> b -> b) -> Parser r e a -> Parser r e b -> Parser r e b
 chainr f (Parser elem) (Parser end) = go where
   go = Parser \fp !r eob s n -> case elem fp r eob s n of
     OK# a s n -> case runParser# go fp r eob s n of
@@ -725,7 +724,7 @@
 -- | Run a parser zero or more times, collect the results in a list. Note: for optimal performance,
 --   try to avoid this. Often it is possible to get rid of the intermediate list by using a
 --   combinator or a custom parser.
-many :: Parser e a -> Parser e [a]
+many :: Parser r e a -> Parser r e [a]
 many (Parser f) = go where
   go = Parser \fp !r eob s n -> case f fp r eob s n of
     OK# a s n -> case runParser# go fp r eob s n of
@@ -736,7 +735,7 @@
 {-# inline many #-}
 
 -- | Skip a parser zero or more times.
-many_ :: Parser e a -> Parser e ()
+many_ :: Parser r e a -> Parser r e ()
 many_ (Parser f) = go where
   go = Parser \fp !r eob s n -> case f fp r eob s n of
     OK# a s n -> runParser# go fp r eob s n
@@ -747,18 +746,18 @@
 -- | Run a parser one or more times, collect the results in a list. Note: for optimal performance,
 --   try to avoid this. Often it is possible to get rid of the intermediate list by using a
 --   combinator or a custom parser.
-some :: Parser e a -> Parser e [a]
+some :: Parser r e a -> Parser r e [a]
 some p = (:) <$> p <*> many p
 {-# inline some #-}
 
 -- | Skip a parser one or more times.
-some_ :: Parser e a -> Parser e ()
+some_ :: Parser r e a -> Parser r e ()
 some_ pa = pa >> many_ pa
 {-# inline some_ #-}
 
 -- | Succeed if the first parser succeeds and the second one fails. The parsing
 --   state is restored to the point of the first argument's success.
-notFollowedBy :: Parser e a -> Parser e b -> Parser e a
+notFollowedBy :: Parser r e a -> Parser r e b -> Parser r e a
 notFollowedBy p1 p2 = p1 <* lookahead (fails p2)
 {-# inline notFollowedBy #-}
 
@@ -766,7 +765,7 @@
 --   isolated bytes must be consumed.
 --
 -- Throws a runtime error if given a negative integer.
-isolate :: Int -> Parser e a -> Parser e a
+isolate :: Int -> Parser r e a -> Parser r e a
 isolate (I# n#) p = Parser \fp !r eob s n ->
   let s' = plusAddr# s n#
   in  case n# <=# minusAddr# eob s of
@@ -784,14 +783,14 @@
 --------------------------------------------------------------------------------
 
 -- | Get the current position in the input.
-getPos :: Parser e Pos
+getPos :: Parser r e Pos
 getPos = Parser \fp !r eob s n -> OK# (addrToPos# eob s) s n
 {-# inline getPos #-}
 
 -- | Set the input position. Warning: this can result in crashes if the position points outside the
 --   current buffer. It is always safe to `setPos` values which came from `getPos` with the current
 --   input.
-setPos :: Pos -> Parser e ()
+setPos :: Pos -> Parser r e ()
 setPos s = Parser \fp !r eob _ n -> OK# () (posToAddr# eob s) n
 {-# inline setPos #-}
 
@@ -801,8 +800,8 @@
 {-# inline endPos #-}
 
 
--- | Return the consumed span of a parser. Use `spanned` if possible for better efficiency.
-spanOf :: Parser e a -> Parser e Span
+-- | Return the consumed span of a parser. Use `withSpan` if possible for better efficiency.
+spanOf :: Parser r e a -> Parser r e Span
 spanOf (Parser f) = Parser \fp !r eob s n -> case f fp r eob s n of
   OK# a s' n -> OK# (Span (addrToPos# eob s) (addrToPos# eob s')) s' n
   x          -> unsafeCoerce# x
@@ -810,31 +809,31 @@
 
 -- | Bind the result together with the span of the result. CPS'd version of `spanOf`
 --   for better unboxing.
-spanned :: Parser e a -> (a -> Span -> Parser e b) -> Parser e b
-spanned (Parser f) g = Parser \fp !r eob s n -> case f fp r eob s n of
+withSpan :: Parser r e a -> (a -> Span -> Parser r e b) -> Parser r e b
+withSpan (Parser f) g = Parser \fp !r eob s n -> case f fp r eob s n of
   OK# a s' n -> runParser# (g a (Span (addrToPos# eob s) (addrToPos# eob s'))) fp r eob s' n
   x          -> unsafeCoerce# x
-{-# inline spanned #-}
+{-# inline withSpan #-}
 
 -- | Return the `B.ByteString` consumed by a parser. Note: it's more efficient to use `spanOf` and
---   `spanned` instead.
-byteStringOf :: Parser e a -> Parser e B.ByteString
+--   `withSpan` instead.
+byteStringOf :: Parser r e a -> Parser r e B.ByteString
 byteStringOf (Parser f) = Parser \fp !r eob s n -> case f fp r eob s n of
   OK# a s' n -> OK# (B.PS (ForeignPtr s fp) 0 (I# (minusAddr# s' s))) s' n
   x          -> unsafeCoerce# x
 {-# inline byteStringOf #-}
 
 -- | CPS'd version of `byteStringOf`. Can be more efficient, because the result is more eagerly unboxed
---   by GHC. It's more efficient to use `spanOf` or `spanned` instead.
-byteStringed :: Parser e a -> (a -> B.ByteString -> Parser e b) -> Parser e b
-byteStringed (Parser f) g = Parser \fp !r eob s n -> case f fp r eob s n of
+--   by GHC. It's more efficient to use `spanOf` or `withSpan` instead.
+withByteString :: Parser r e a -> (a -> B.ByteString -> Parser r e b) -> Parser r e b
+withByteString (Parser f) g = Parser \fp !r eob s n -> case f fp r eob s n of
   OK# a s' n -> runParser# (g a (B.PS (ForeignPtr s fp) 0 (I# (minusAddr# s' s)))) fp r eob s' n
   x          -> unsafeCoerce# x
-{-# inline byteStringed #-}
+{-# inline withByteString #-}
 
 -- | Create a `B.ByteString` from a `Span`. The result is invalid is the `Span` points
 --   outside the current buffer, or if the `Span` start is greater than the end position.
-unsafeSpanToByteString :: Span -> Parser e B.ByteString
+unsafeSpanToByteString :: Span -> Parser r e B.ByteString
 unsafeSpanToByteString (Span l r) =
   lookahead (setPos l >> byteStringOf (setPos r))
 {-# inline unsafeSpanToByteString #-}
@@ -843,9 +842,9 @@
 -- | Run a parser in a given input span. The input position and the `Int` state is restored after
 --   the parser is finished, so `inSpan` does not consume input and has no side effect.  Warning:
 --   this operation may crash if the given span points outside the current parsing buffer. It's
---   always safe to use `inSpan` if the span comes from a previous `spanned` or `spanOf` call on
+--   always safe to use `inSpan` if the span comes from a previous `withSpan` or `spanOf` call on
 --   the current input.
-inSpan :: Span -> Parser e a -> Parser e a
+inSpan :: Span -> Parser r e a -> Parser r e a
 inSpan (Span s eob) (Parser f) = Parser \fp !r eob' s' n' ->
   case f fp r (posToAddr# eob' eob) (posToAddr# eob' s) n' of
     OK# a _ _ -> OK# a s' n'
@@ -857,7 +856,7 @@
 
 -- | Parse the rest of the current line as a `String`. Assumes UTF-8 encoding,
 --   throws an error if the encoding is invalid.
-takeLine :: Parser e String
+takeLine :: Parser r e String
 takeLine =
   branch eof (pure "") do
   c <- anyChar
@@ -867,28 +866,28 @@
 
 -- | Parse the rest of the current line as a `String`, but restore the parsing state.
 --   Assumes UTF-8 encoding. This can be used for debugging.
-traceLine :: Parser e String
+traceLine :: Parser r e String
 traceLine = lookahead takeLine
 
 -- | Take the rest of the input as a `String`. Assumes UTF-8 encoding.
-takeRest :: Parser e String
+takeRest :: Parser r e String
 takeRest = ((:) <$> anyChar <*> takeRest) <|> pure []
 
 -- | Get the rest of the input as a `String`, but restore the parsing state. Assumes UTF-8 encoding.
 --   This can be used for debugging.
-traceRest :: Parser e String
+traceRest :: Parser r e String
 traceRest = lookahead traceRest
 
 --------------------------------------------------------------------------------
 
 -- | Convert an UTF-8-coded `B.ByteString` to a `String`.
 unpackUTF8 :: B.ByteString -> String
-unpackUTF8 str = case runParser takeRest 0 0 str of
+unpackUTF8 str = case runParser takeRest () 0 str of
   OK a _ _ -> a
   _        -> error "unpackUTF8: invalid encoding"
 
 -- | Check that the input has at least the given number of bytes.
-ensureBytes# :: Int -> Parser e ()
+ensureBytes# :: Int -> Parser r e ()
 ensureBytes# (I# len) = Parser \fp !r eob s n ->
   case len  <=# minusAddr# eob s of
     1# -> OK# () s n
@@ -897,7 +896,7 @@
 
 -- | Unsafely read a concrete byte from the input. It's not checked that the input has
 --   enough bytes.
-scan8# :: Word8 -> Parser e ()
+scan8# :: Word8 -> Parser r e ()
 scan8# (W8# c) = Parser \fp !r eob s n ->
   case indexWord8OffAddr# s 0# of
     c' -> case eqWord8'# c c' of
@@ -907,7 +906,7 @@
 
 -- | Unsafely read two concrete bytes from the input. It's not checked that the input has
 --   enough bytes.
-scan16# :: Word16 -> Parser e ()
+scan16# :: Word16 -> Parser r e ()
 scan16# (W16# c) = Parser \fp !r eob s n ->
   case indexWord16OffAddr# s 0# of
     c' -> case eqWord16'# c c' of
@@ -917,7 +916,7 @@
 
 -- | Unsafely read four concrete bytes from the input. It's not checked that the input has
 --   enough bytes.
-scan32# :: Word32 -> Parser e ()
+scan32# :: Word32 -> Parser r e ()
 scan32# (W32# c) = Parser \fp !r eob s n ->
   case indexWord32OffAddr# s 0# of
     c' -> case eqWord32'# c c' of
@@ -927,7 +926,7 @@
 
 -- | Unsafely read eight concrete bytes from the input. It's not checked that the input has
 --   enough bytes.
-scan64# :: Word -> Parser e ()
+scan64# :: Word -> Parser r e ()
 scan64# (W# c) = Parser \fp !r eob s n ->
   case indexWord64OffAddr# s 0# of
     c' -> case eqWord# c c' of
@@ -936,11 +935,11 @@
 {-# inline scan64# #-}
 
 -- | Unsafely read and return a byte from the input. It's not checked that the input is non-empty.
-scanAny8# :: Parser e Word8
+scanAny8# :: Parser r e Word8
 scanAny8# = Parser \fp !r eob s n -> OK# (W8# (indexWord8OffAddr# s 0#)) (plusAddr# s 1#) n
 {-# inline scanAny8# #-}
 
-scanPartial64# :: Int -> Word -> Parser e ()
+scanPartial64# :: Int -> Word -> Parser r e ()
 scanPartial64# (I# len) (W# w) = Parser \fp !r eob s n ->
   case indexWordOffAddr# s 0# of
     w' -> case uncheckedIShiftL# (8# -# len) 3# of
@@ -952,12 +951,12 @@
 {-# inline scanPartial64# #-}
 
 -- | Decrease the current input position by the given number of bytes.
-setBack# :: Int -> Parser e ()
+setBack# :: Int -> Parser r e ()
 setBack# (I# i) = Parser \fp !r eob s n ->
   OK# () (plusAddr# s (negateInt# i)) n
 {-# inline setBack# #-}
 
--- | Template function, creates a @Parser e ()@ which unsafely scans a given
+-- | Template function, creates a @Parser r e ()@ which unsafely scans a given
 --   sequence of bytes.
 scanBytes# :: [Word] -> Q Exp
 scanBytes# bytes = do
@@ -1074,56 +1073,56 @@
 
 --------------------------------------------------------------------------------
 
-withAnyWord8# :: (Word8'# -> Parser e a) -> Parser e a
+withAnyWord8# :: (Word8'# -> Parser r e a) -> Parser r e a
 withAnyWord8# p = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case indexWord8OffAddr# buf 0# of
     w# -> runParser# (p w#) fp r eob (plusAddr# buf 1#) n
 {-# inline withAnyWord8# #-}
 
-withAnyWord16# :: (Word16'# -> Parser e a) -> Parser e a
+withAnyWord16# :: (Word16'# -> Parser r e a) -> Parser r e a
 withAnyWord16# p = Parser \fp !r eob buf n -> case 2# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexWord16OffAddr# buf 0# of
     w# -> runParser# (p w#) fp r eob (plusAddr# buf 2#) n
 {-# inline withAnyWord16# #-}
 
-withAnyWord32# :: (Word32'# -> Parser e a) -> Parser e a
+withAnyWord32# :: (Word32'# -> Parser r e a) -> Parser r e a
 withAnyWord32# p = Parser \fp !r eob buf n -> case 4# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexWord32OffAddr# buf 0# of
     w# -> runParser# (p w#) fp r eob (plusAddr# buf 4#) n
 {-# inline withAnyWord32# #-}
 
-withAnyWord64# :: (Word# -> Parser e a) -> Parser e a
+withAnyWord64# :: (Word# -> Parser r e a) -> Parser r e a
 withAnyWord64# p = Parser \fp !r eob buf n -> case 8# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexWordOffAddr# buf 0# of
     w# -> runParser# (p w#) fp r eob (plusAddr# buf 8#) n
 {-# inline withAnyWord64# #-}
 
-withAnyInt8# :: (Int8'# -> Parser e a) -> Parser e a
+withAnyInt8# :: (Int8'# -> Parser r e a) -> Parser r e a
 withAnyInt8# p = Parser \fp !r eob buf n -> case eqAddr# eob buf of
   1# -> Fail#
   _  -> case indexInt8OffAddr# buf 0# of
     i# -> runParser# (p i#) fp r eob (plusAddr# buf 1#) n
 {-# inline withAnyInt8# #-}
 
-withAnyInt16# :: (Int16'# -> Parser e a) -> Parser e a
+withAnyInt16# :: (Int16'# -> Parser r e a) -> Parser r e a
 withAnyInt16# p = Parser \fp !r eob buf n -> case 2# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexInt16OffAddr# buf 0# of
     i# -> runParser# (p i#) fp r eob (plusAddr# buf 2#) n
 {-# inline withAnyInt16# #-}
 
-withAnyInt32# :: (Int32'# -> Parser e a) -> Parser e a
+withAnyInt32# :: (Int32'# -> Parser r e a) -> Parser r e a
 withAnyInt32# p = Parser \fp !r eob buf n -> case 4# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexInt32OffAddr# buf 0# of
     i# -> runParser# (p i#) fp r eob (plusAddr# buf 4#) n
 {-# inline withAnyInt32# #-}
 
-withAnyInt64# :: (Int# -> Parser e a) -> Parser e a
+withAnyInt64# :: (Int# -> Parser r e a) -> Parser r e a
 withAnyInt64# p = Parser \fp !r eob buf n -> case 8# <=# minusAddr# eob buf of
   0# -> Fail#
   _  -> case indexInt64OffAddr# buf 0# of
@@ -1133,142 +1132,142 @@
 --------------------------------------------------------------------------------
 
 -- | Parse any 'Word8' (byte).
-anyWord8 :: Parser e Word8
+anyWord8 :: Parser r e Word8
 anyWord8 = withAnyWord8# (\w# -> pure (W8# w#))
 {-# inline anyWord8 #-}
 
 -- | Skip any 'Word8' (byte).
-anyWord8_ :: Parser e ()
+anyWord8_ :: Parser r e ()
 anyWord8_ = () <$ anyWord8
 {-# inline anyWord8_ #-}
 
 -- | Parse any 'Word16'.
-anyWord16 :: Parser e Word16
+anyWord16 :: Parser r e Word16
 anyWord16 = withAnyWord16# (\w# -> pure (W16# w#))
 {-# inline anyWord16 #-}
 
 -- | Skip any 'Word16'.
-anyWord16_ :: Parser e ()
+anyWord16_ :: Parser r e ()
 anyWord16_ = () <$ anyWord16
 {-# inline anyWord16_ #-}
 
 -- | Parse any 'Word32'.
-anyWord32 :: Parser e Word32
+anyWord32 :: Parser r e Word32
 anyWord32 = withAnyWord32# (\w# -> pure (W32# w#))
 {-# inline anyWord32 #-}
 
 -- | Skip any 'Word32'.
-anyWord32_ :: Parser e ()
+anyWord32_ :: Parser r e ()
 anyWord32_ = () <$ anyWord32
 {-# inline anyWord32_ #-}
 
 -- | Parse any 'Word64'.
-anyWord64 :: Parser e Word64
+anyWord64 :: Parser r e Word64
 anyWord64 = withAnyWord64# (\w# -> pure (W64# w#))
 {-# inline anyWord64 #-}
 
 -- | Skip any 'Word64'.
-anyWord64_ :: Parser e ()
+anyWord64_ :: Parser r e ()
 anyWord64_ = () <$ anyWord64
 {-# inline anyWord64_ #-}
 
 -- | Parse any 'Word'.
-anyWord :: Parser e Word
+anyWord :: Parser r e Word
 anyWord = withAnyWord64# (\w# -> pure (W# w#))
 {-# inline anyWord #-}
 
 -- | Skip any 'Word'.
-anyWord_ :: Parser e ()
+anyWord_ :: Parser r e ()
 anyWord_ = () <$ anyWord
 {-# inline anyWord_ #-}
 
 --------------------------------------------------------------------------------
 
 -- | Parse any 'Int8'.
-anyInt8 :: Parser e Int8
+anyInt8 :: Parser r e Int8
 anyInt8 = withAnyInt8# (\i# -> pure (I8# i#))
 {-# inline anyInt8 #-}
 
 -- | Parse any 'Int16'.
-anyInt16 :: Parser e Int16
+anyInt16 :: Parser r e Int16
 anyInt16 = withAnyInt16# (\i# -> pure (I16# i#))
 {-# inline anyInt16 #-}
 
 -- | Parse any 'Int32'.
-anyInt32 :: Parser e Int32
+anyInt32 :: Parser r e Int32
 anyInt32 = withAnyInt32# (\i# -> pure (I32# i#))
 {-# inline anyInt32 #-}
 
 -- | Parse any 'Int64'.
-anyInt64 :: Parser e Int64
+anyInt64 :: Parser r e Int64
 anyInt64 = withAnyInt64# (\i# -> pure (I64# i#))
 {-# inline anyInt64 #-}
 
 -- | Parse any 'Int'.
-anyInt :: Parser e Int
+anyInt :: Parser r e Int
 anyInt = withAnyInt64# (\i# -> pure (I# i#))
 {-# inline anyInt #-}
 
 --------------------------------------------------------------------------------
 
 -- | Parse any 'Word16' (little-endian).
-anyWord16le :: Parser e Word16
+anyWord16le :: Parser r e Word16
 anyWord16le = anyWord16
 {-# inline anyWord16le #-}
 
 -- | Parse any 'Word16' (big-endian).
-anyWord16be :: Parser e Word16
+anyWord16be :: Parser r e Word16
 anyWord16be = withAnyWord16# (\w# -> pure (W16# (byteSwap16'# w#)))
 {-# inline anyWord16be #-}
 
 -- | Parse any 'Word32' (little-endian).
-anyWord32le :: Parser e Word32
+anyWord32le :: Parser r e Word32
 anyWord32le = anyWord32
 {-# inline anyWord32le #-}
 
 -- | Parse any 'Word32' (big-endian).
-anyWord32be :: Parser e Word32
+anyWord32be :: Parser r e Word32
 anyWord32be = withAnyWord32# (\w# -> pure (W32# (byteSwap32'# w#)))
 {-# inline anyWord32be #-}
 
 -- | Parse any 'Word64' (little-endian).
-anyWord64le :: Parser e Word64
+anyWord64le :: Parser r e Word64
 anyWord64le = anyWord64
 {-# inline anyWord64le #-}
 
 -- | Parse any 'Word64' (big-endian).
-anyWord64be :: Parser e Word64
+anyWord64be :: Parser r e Word64
 anyWord64be = withAnyWord64# (\w# -> pure (W64# (byteSwap# w#)))
 {-# inline anyWord64be #-}
 
 --------------------------------------------------------------------------------
 
 -- | Parse any 'Int16' (little-endian).
-anyInt16le :: Parser e Int16
+anyInt16le :: Parser r e Int16
 anyInt16le = anyInt16
 {-# inline anyInt16le #-}
 
 -- | Parse any 'Int16' (big-endian).
-anyInt16be :: Parser e Int16
+anyInt16be :: Parser r e Int16
 anyInt16be = withAnyWord16# (\w# -> pure (I16# (word16ToInt16# (byteSwap16'# w#))))
 {-# inline anyInt16be #-}
 
 -- | Parse any 'Int32' (little-endian).
-anyInt32le :: Parser e Int32
+anyInt32le :: Parser r e Int32
 anyInt32le = anyInt32
 {-# inline anyInt32le #-}
 
 -- | Parse any 'Int32' (big-endian).
-anyInt32be :: Parser e Int32
+anyInt32be :: Parser r e Int32
 anyInt32be = withAnyWord32# (\w# -> pure (I32# (word32ToInt32# (byteSwap32'# w#))))
 {-# inline anyInt32be #-}
 
 -- | Parse any 'Int64' (little-endian).
-anyInt64le :: Parser e Int64
+anyInt64le :: Parser r e Int64
 anyInt64le = anyInt64
 {-# inline anyInt64le #-}
 
 -- | Parse any 'Int64' (big-endian).
-anyInt64be :: Parser e Int64
+anyInt64be :: Parser r e Int64
 anyInt64be = withAnyWord64# (\w# -> pure (I64# (word2Int# (byteSwap# w#))))
 {-# inline anyInt64be #-}
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -53,8 +53,8 @@
       it "can succeed when argument missing" $ optional empty `shouldParse` ""
       it "propagates errors" $ optional (err "nope") `shouldParseErr` ""
 
-    describe "optioned" $ do
-      let opt p = optioned p (pure . reverse) (pure "bar")
+    describe "withOption" $ do
+      let opt p = withOption p (pure . reverse) (pure "bar")
       it "handles success" $ opt (pure "foo") `shouldParseWith` ("", "oof")
       it "handles failure" $ opt empty `shouldParseWith` ("", "bar")
       it "handles error" $ opt (err "nope") `shouldParseErr` ""
@@ -78,6 +78,11 @@
       it "succeeds at end of file" $ eof `shouldParse` ""
       it "fails with more input" $ eof `shouldParseFail` "more"
 
+    describe "skip" $ do
+      prop "skips to the end of input" $
+        \(bs :: ByteString) ->
+          (skip (B.length bs) >> eof) `shouldParse` bs
+
     describe "char" $ do
       it "succeeds on that char" $ $(char 'a') `shouldParse` "a"
       it "succeeds on multibyte char" $ $(char 'ȩ') `shouldParse` packUTF8 "ȩ"
@@ -396,6 +401,13 @@
       it "fails on negative integers" $ readInteger `shouldParseFail` "-5"
       it "fails on empty input" $ readInteger `shouldParseFail` ""
 
+    describe "anyCString" $ do
+      prop "parses arbitrary null-terminated bytestrings" $
+        \(bs :: ByteString) ->
+          let bs' = B.snoc bs 0x00
+              expected = B.takeWhile (/= 0x00) bs'
+          in  anyCString `shouldParsePartialWith` (bs', expected)
+
     describe "Explicit-endianness machine integers" $ do
       describe "Unsigned" $ do
         prop "parses Word8s" $ do
@@ -605,6 +617,15 @@
 
     describe "unpackUTF8" $ do
       pure ()
+
+  describe "Location & address primitives" $ do
+    it "valid lookaheadFromAddr# usage succeeds" $ do
+      -- use Int#/Int64# directly because Word8# -> Int# is annoying on old GHCs
+      let bs = B.pack [ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                      , 0xFF, 0x31, 0x32, 0x33, 0x00, 0xFF]
+          p = withAddr# $ \addr# -> withAnyInt64# $ \os# ->
+                  lookaheadFromAddr# addr# $ atSkip# os# $ anyCString
+      p `shouldParsePartialWith` (bs, "123")
 
 --------------------------------------------------------------------------------
 
