diff --git a/char-qq.cabal b/char-qq.cabal
--- a/char-qq.cabal
+++ b/char-qq.cabal
@@ -1,5 +1,5 @@
 name: char-qq
-version: 0.1
+version: 0.1.1
 category: QuasiQuotes
 synopsis: Quasiquoters for characters and codepoints
 description: A set of quasiquoters providing compile-time conversions between characters and codepoints.
@@ -24,7 +24,10 @@
   exposed-modules:
     CharQq
   other-modules:
+    CharQq.Exp
+    CharQq.Pat
     CharQq.Prelude
+    CharQq.Q
   build-depends:
     base >=4.9 && <5,
     template-haskell >=2.9 && <3
diff --git a/library/CharQq.hs b/library/CharQq.hs
--- a/library/CharQq.hs
+++ b/library/CharQq.hs
@@ -2,13 +2,12 @@
 (
   ord,
   ords,
+  chr,
 )
 where
 
 import CharQq.Prelude hiding (ord, chr)
-import Language.Haskell.TH.Syntax
-import Language.Haskell.TH.Quote
-import qualified CharQq.Prelude as Prelude
+import qualified CharQq.Q as Q
 
 
 -- * Quasi-quoters
@@ -25,8 +24,8 @@
 -}
 ord :: QuasiQuoter
 ord = QuasiQuoter exp pat typ dec where
-  exp = stringOrdExpQ
-  pat = stringOrdPatQ
+  exp = Q.stringCodepointExp
+  pat = Q.stringCodepointPat
   typ = const (fail "Unsupported")
   dec = const (fail "Unsupported")
 
@@ -41,28 +40,23 @@
 -}
 ords :: QuasiQuoter
 ords = QuasiQuoter exp pat typ dec where
-  exp = stringOrdsExpQ
-  pat = stringOrdsPatQ
+  exp = Q.stringCodepointsExp
+  pat = Q.stringCodepointsPat
   typ = const (fail "Unsupported")
   dec = const (fail "Unsupported")
 
--- * Q
--------------------------
-
-stringOrdExpQ :: String -> Q Exp
-stringOrdExpQ = \ case
-  [char] -> return (LitE (IntegerL (fromIntegral (Prelude.ord char))))
-  [] -> fail "Empty quotation"
-  _ -> fail "Overlong quotation"
-
-stringOrdPatQ :: String -> Q Pat
-stringOrdPatQ = \ case
-  [char] -> return (LitP (IntegerL (fromIntegral (Prelude.ord char))))
-  [] -> fail "Empty quotation"
-  _ -> fail "Overlong quotation"
+{-|
+A quasi-quoter which produces a char literal from a codepoint.
+E.g.,
 
-stringOrdsExpQ :: String -> Q Exp
-stringOrdsExpQ = return . ListE . map (LitE . IntegerL . fromIntegral . Prelude.ord)
+>>> [chr|90|]
+'Z'
 
-stringOrdsPatQ :: String -> Q Pat
-stringOrdsPatQ = return . ListP . map (LitP . IntegerL . fromIntegral . Prelude.ord)
+Works in the context of expressions and patterns.
+-}
+chr :: QuasiQuoter
+chr = QuasiQuoter exp pat typ dec where
+  exp = Q.stringCodepointCharExp
+  pat = Q.stringCodepointCharPat
+  typ = const (fail "Unsupported")
+  dec = const (fail "Unsupported")
diff --git a/library/CharQq/Exp.hs b/library/CharQq/Exp.hs
new file mode 100644
--- /dev/null
+++ b/library/CharQq/Exp.hs
@@ -0,0 +1,14 @@
+module CharQq.Exp
+where
+
+import CharQq.Prelude
+
+
+codepoint :: Char -> Exp
+codepoint = LitE . IntegerL . fromIntegral . ord
+
+codepoints :: [Char] -> Exp
+codepoints = ListE . map codepoint
+
+char :: Char -> Exp
+char = LitE . CharL
diff --git a/library/CharQq/Pat.hs b/library/CharQq/Pat.hs
new file mode 100644
--- /dev/null
+++ b/library/CharQq/Pat.hs
@@ -0,0 +1,14 @@
+module CharQq.Pat
+where
+
+import CharQq.Prelude
+
+
+codepoint :: Char -> Pat
+codepoint = LitP . IntegerL . fromIntegral . ord
+
+codepoints :: [Char] -> Pat
+codepoints = ListP . map codepoint
+
+char :: Char -> Pat
+char = LitP . CharL
diff --git a/library/CharQq/Prelude.hs b/library/CharQq/Prelude.hs
--- a/library/CharQq/Prelude.hs
+++ b/library/CharQq/Prelude.hs
@@ -21,7 +21,6 @@
 import Data.Char as Exports
 import Data.Coerce as Exports
 import Data.Complex as Exports
-import Data.Data as Exports
 import Data.Dynamic as Exports
 import Data.Either as Exports
 import Data.Fixed as Exports
@@ -50,7 +49,7 @@
 import Foreign.Ptr as Exports
 import Foreign.StablePtr as Exports
 import Foreign.Storable as Exports hiding (sizeOf, alignment)
-import GHC.Conc as Exports hiding (withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)
+import GHC.Conc as Exports hiding (reportError, withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)
 import GHC.Exts as Exports (lazy, inline, sortWith, groupWith)
 import GHC.Generics as Exports (Generic)
 import GHC.IO.Exception as Exports
@@ -69,3 +68,8 @@
 import Text.Printf as Exports (printf, hPrintf)
 import Text.Read as Exports (Read(..), readMaybe, readEither)
 import Unsafe.Coerce as Exports
+
+-- template-haskell
+-------------------------
+import Language.Haskell.TH.Syntax as Exports
+import Language.Haskell.TH.Quote as Exports
diff --git a/library/CharQq/Q.hs b/library/CharQq/Q.hs
new file mode 100644
--- /dev/null
+++ b/library/CharQq/Q.hs
@@ -0,0 +1,44 @@
+module CharQq.Q
+where
+
+import CharQq.Prelude
+import qualified CharQq.Exp as Exp
+import qualified CharQq.Pat as Pat
+
+
+stringChar :: String -> Q Char
+stringChar = \ case
+  [char] -> return char
+  [] -> fail "Empty quotation"
+  _ -> fail "More than one char in the quotation"
+
+stringCodepointExp :: String -> Q Exp
+stringCodepointExp = fmap Exp.codepoint . stringChar
+
+stringCodepointPat :: String -> Q Pat
+stringCodepointPat = fmap Pat.codepoint . stringChar
+
+stringCodepointsExp :: String -> Q Exp
+stringCodepointsExp = return . Exp.codepoints
+
+stringCodepointsPat :: String -> Q Pat
+stringCodepointsPat = return . Pat.codepoints
+
+codepointChar :: Int -> Q Char
+codepointChar codepoint = if codepoint <= ord maxBound
+  then return (chr codepoint)
+  else fail "Codepoint is out of the supported Unicode range"
+
+stringInt :: String -> Q Int
+stringInt string = case readMaybe string of
+  Just int -> return int
+  Nothing -> fail "String doesn't parse to integer"
+
+stringCodepointChar :: String -> Q Char
+stringCodepointChar = stringInt >=> codepointChar
+
+stringCodepointCharExp :: String -> Q Exp
+stringCodepointCharExp = fmap Exp.char . stringCodepointChar
+
+stringCodepointCharPat :: String -> Q Pat
+stringCodepointCharPat = fmap Pat.char . stringCodepointChar
