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.5.1.1
+version:        0.5.2.0
 synopsis:       High-performance parsing from strict bytestrings
 description:    @Flatparse@ is a high-performance parsing library for strict bytestring input. See the README for more information:
                 <https://github.com/AndrasKovacs/flatparse>.
diff --git a/src/FlatParse/Basic.hs b/src/FlatParse/Basic.hs
--- a/src/FlatParse/Basic.hs
+++ b/src/FlatParse/Basic.hs
@@ -24,6 +24,7 @@
   , runParserIO
   , runParserST
   , embedParserST
+  , embedParser
 
   -- ** Primitive result types
   , type FP.Parser.Res#
@@ -219,7 +220,7 @@
 
 --------------------------------------------------------------------------------
 
--- | Run a parser.
+-- | Run a pure parser.
 runParser :: Parser e a -> B.ByteString -> Result e a
 runParser (ParserT f) b@(B.PS (ForeignPtr _ fp) _ (I# len)) = unsafePerformIO $
   B.unsafeUseAsCString b \(Ptr buf) -> do
@@ -234,7 +235,7 @@
 -- We mark this as noinline to allow power users to safely do unsafe state token coercions.
 -- Details are discussed in https://github.com/AndrasKovacs/flatparse/pull/34#issuecomment-1326999390
 
--- | Run a parser on a 'String', converting it to the corresponding UTF-8 bytes.
+-- | Run a pure parser on a 'String', converting it to the corresponding UTF-8 bytes.
 --
 -- Reminder: @OverloadedStrings@ for 'B.ByteString' does not yield a valid UTF-8
 -- encoding! For non-ASCII 'B.ByteString' literal input, use this wrapper or
@@ -266,10 +267,15 @@
   (# st, a #) -> OK# st a s
 {-# inline liftST #-}
 
--- | Run a `ParserST` inside a pure parser.
-embedParserST :: forall e a. (forall s. ParserST s e a) -> Parser e a
+-- | Run a `ParserST` inside any parser.
+embedParserST :: forall e a s. (forall s. ParserST s e a) -> ParserT s e a
 embedParserST f = unsafeCoerce# (f :: ParserST RealWorld e a)
 {-# inline embedParserST #-}
+
+-- | Run a pure `Parser` inside any parser.
+embedParser :: forall e a s. Parser e a -> ParserT s e a
+embedParser f = unsafeCoerce# f
+{-# inline embedParser #-}
 
 --------------------------------------------------------------------------------
 
diff --git a/src/FlatParse/Basic/Switch.hs b/src/FlatParse/Basic/Switch.hs
--- a/src/FlatParse/Basic/Switch.hs
+++ b/src/FlatParse/Basic/Switch.hs
@@ -137,21 +137,6 @@
     (map (\(x, rhs) -> valD (varP x) (normalB (pure rhs)) []) (Data.Foldable.toList branches))
     (go t)
 
-parseSwitch :: Q Exp -> Q ([(String, Exp)], Maybe Exp)
-parseSwitch exp = exp >>= \case
-  CaseE (UnboundVarE _) []    -> error "switch: empty clause list"
-  CaseE (UnboundVarE _) cases -> do
-    (!cases, !last) <- pure (init cases, last cases)
-    !cases <- forM cases \case
-      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (str, rhs)
-      _ -> error "switch: expected a match clause on a string literal"
-    (!cases, !last) <- case last of
-      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (cases ++ [(str, rhs)], Nothing)
-      Match WildP                (NormalB rhs) [] -> pure (cases, Just rhs)
-      _ -> error "switch: expected a match clause on a string literal or a wildcard"
-    pure (cases, last)
-  _ -> error "switch: expected a \"case _ of\" expression"
-
 genSwitchTrie' :: Maybe Exp -> [(String, Exp)] -> Maybe Exp
               -> (Map (Maybe Int) Exp, Trie' (Rule, Int, Maybe Int))
 genSwitchTrie' postAction cases fallback =
diff --git a/src/FlatParse/Common/Position.hs b/src/FlatParse/Common/Position.hs
--- a/src/FlatParse/Common/Position.hs
+++ b/src/FlatParse/Common/Position.hs
@@ -2,7 +2,7 @@
 
 module FlatParse.Common.Position
   ( Pos(..), endPos, addrToPos#, posToAddr#
-  , Span(..), unsafeSlice
+  , Span(..), unsafeSlice, leftPos, rightPos
   ) where
 
 import qualified Data.ByteString as B
@@ -54,3 +54,11 @@
 endPos :: Pos
 endPos = Pos 0
 {-# inline endPos #-}
+
+leftPos :: Span -> Pos
+leftPos (Span p _) = p
+{-# inline leftPos #-}
+
+rightPos :: Span -> Pos
+rightPos (Span _ p) = p
+{-# inline rightPos #-}
diff --git a/src/FlatParse/Common/Switch.hs b/src/FlatParse/Common/Switch.hs
--- a/src/FlatParse/Common/Switch.hs
+++ b/src/FlatParse/Common/Switch.hs
@@ -1,7 +1,9 @@
 module FlatParse.Common.Switch where
 
+import Control.Monad (forM)
 import Data.Foldable (foldl')
 import Data.Map (Map)
+import Language.Haskell.TH
 
 import qualified Data.Map.Strict as M
 
@@ -87,3 +89,30 @@
 
 compileTrie :: [(Int, String)] -> Trie' (Rule, Int, Maybe Int)
 compileTrie = ensureBytes . fallbacks . pathify . mindepths . listToTrie
+
+parseSwitch :: Q Exp -> Q ([(String, Exp)], Maybe Exp)
+parseSwitch exp = exp >>= \case
+  CaseE (UnboundVarE _) []    -> error "switch: empty clause list"
+  CaseE (UnboundVarE _) cases -> do
+    (!cases, !last) <- pure (init cases, last cases)
+    !cases <- forM cases \case
+      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (str, rhs)
+      _ -> error "switch: expected a match clause on a string literal"
+    (!cases, !last) <- case last of
+      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (cases ++ [(str, rhs)], Nothing)
+      Match WildP                (NormalB rhs) [] -> pure (cases, Just rhs)
+      _ -> error "switch: expected a match clause on a string literal or a wildcard"
+    pure (cases, last)
+  _ -> error "switch: expected a \"case _ of\" expression"
+
+makeRawSwitch :: [(String, Q Exp)] -> Maybe (Q Exp) -> Q Exp
+makeRawSwitch branches deflt = do
+  branches <- forM branches $ \(s, body) -> do
+    body <- body
+    pure $ Match (LitP (StringL s)) (NormalB body) []
+  branches <- case deflt of
+    Nothing    -> pure branches
+    Just deflt -> do
+      deflt <- deflt
+      pure $ branches ++ [Match WildP (NormalB deflt) []]
+  pure $ CaseE (UnboundVarE (mkName "_")) branches
diff --git a/src/FlatParse/Stateful.hs b/src/FlatParse/Stateful.hs
--- a/src/FlatParse/Stateful.hs
+++ b/src/FlatParse/Stateful.hs
@@ -24,6 +24,7 @@
   , runParserIO
   , runParserST
   , embedParserST
+  , embedParser
 
   -- ** Primitive result types
   , type FP.Parser.Res#
@@ -267,10 +268,15 @@
       Fail# rw'  ->  (# rw', Fail #)
 {-# inlinable runParserIO #-}
 
--- | Run a `ParserST` inside a pure parser.
-embedParserST :: forall r e a. (forall s. ParserST s r e a) -> Parser r e a
+-- | Run a `ParserST` inside any parser.
+embedParserST :: forall s r e a. (forall s. ParserST s r e a) -> ParserT s r e a
 embedParserST f = unsafeCoerce# (f :: ParserST RealWorld r e a)
 {-# inline embedParserST #-}
+
+-- | Run a pure `Parser` inside any parser.
+embedParser :: forall s r e a. Parser r e a -> ParserT s r e a
+embedParser f = unsafeCoerce# f
+{-# inline embedParser #-}
 
 --------------------------------------------------------------------------------
 
diff --git a/src/FlatParse/Stateful/Switch.hs b/src/FlatParse/Stateful/Switch.hs
--- a/src/FlatParse/Stateful/Switch.hs
+++ b/src/FlatParse/Stateful/Switch.hs
@@ -136,21 +136,6 @@
     (map (\(x, rhs) -> valD (varP x) (normalB (pure rhs)) []) (Data.Foldable.toList branches))
     (go t)
 
-parseSwitch :: Q Exp -> Q ([(String, Exp)], Maybe Exp)
-parseSwitch exp = exp >>= \case
-  CaseE (UnboundVarE _) []    -> error "switch: empty clause list"
-  CaseE (UnboundVarE _) cases -> do
-    (!cases, !last) <- pure (init cases, last cases)
-    !cases <- forM cases \case
-      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (str, rhs)
-      _ -> error "switch: expected a match clause on a string literal"
-    (!cases, !last) <- case last of
-      Match (LitP (StringL str)) (NormalB rhs) [] -> pure (cases ++ [(str, rhs)], Nothing)
-      Match WildP                (NormalB rhs) [] -> pure (cases, Just rhs)
-      _ -> error "switch: expected a match clause on a string literal or a wildcard"
-    pure (cases, last)
-  _ -> error "switch: expected a \"case _ of\" expression"
-
 genSwitchTrie' :: Maybe Exp -> [(String, Exp)] -> Maybe Exp
               -> (Map (Maybe Int) Exp, Trie' (Rule, Int, Maybe Int))
 genSwitchTrie' postAction cases fallback =
