diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main :: IO ()
+> main = defaultMainWithHooks simpleUserHooks
diff --git a/quickcheck-regex.cabal b/quickcheck-regex.cabal
new file mode 100644
--- /dev/null
+++ b/quickcheck-regex.cabal
@@ -0,0 +1,17 @@
+Name:                quickcheck-regex
+Version:             0.0.1
+Homepage:            http://github.com/audreyt/quickcheck-regex/
+Category:            QuickCheck, test, regex
+Synopsis:            QuickCheck instances for arbitrary regular expressions and regex-conforming strings
+Description:         QuickCheck instances for arbitrary regular expressions and regex-conforming strings
+License:             PublicDomain
+Author:              Audrey Tang
+Maintainer:          audreyt@audreyt.org
+Copyright:           Audrey Tang 2014 (CC0)
+Cabal-Version:       >= 1.2
+Build-Type:          Simple
+
+Library
+    Build-Depends:   base>=3 && <5, QuickCheck >= 2, regex-genex >= 0.6.0, regex-tdfa, containers
+    Exposed-modules: Test.QuickCheck.Regex
+    Hs-source-dirs:  src
diff --git a/src/Test/QuickCheck/Regex.hs b/src/Test/QuickCheck/Regex.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/QuickCheck/Regex.hs
@@ -0,0 +1,60 @@
+{-|
+
+This module exports a 'matching' function that turns a regular expression
+into a generator for strings matching that regex. For example:
+
+>>> import Test.QuickCheck.Regex (matching)
+>>> import Test.QuickCheck (generate)
+>>> generate (matching "[-a-z0-9._%]+@[-a-z0-9.]+\\.[a-z]{3,18}\\.(asia|eu|today)")
+"9%az4rmek@rar1d8qvo04jkd1.agzy.asia"
+
+-}
+module Test.QuickCheck.Regex (matching) where
+
+import Test.QuickCheck (Gen, oneof, arbitrary, listOf, listOf1, choose, generate)
+import Data.List ((\\))
+import Control.Monad (replicateM)
+import Data.Monoid (mempty)
+import Regex.Genex.Normalize (normalize)
+import Text.Regex.TDFA.Pattern (Pattern(..), PatternSet(..))
+import Text.Regex.TDFA.ReadRegex (parseRegex)
+import qualified Data.Set as Set
+
+matching :: String -> Gen String
+matching regex = case parseRegex regex of
+    Left x -> fail $ show x
+    Right (pattern, _) -> go $ normalize mempty pattern
+    where
+    go :: Pattern -> Gen String
+    go pat = case pat of
+        PEmpty      -> return ""
+        POr ps      -> oneof (map go ps)
+        PConcat ps  -> concat `fmap` mapM go ps
+        PQuest p    -> oneof [return "", go p]
+        PDot{}      -> (:[]) `fmap` arbitrary
+        PPlus p     -> concat `fmap` listOf (go p)
+        PStar _ p   -> concat `fmap` listOf1 (go p)
+        PBound low high p -> do
+            n <- choose (low, maybe 10 id high)
+            concat `fmap` replicateM n (go p)
+        PChar{ getPatternChar = ch } -> return [ch]
+        PEscape{ getPatternChar = ch } -> oneChar $ expandEscape ch
+        PAny{ getPatternSet = PatternSet (Just cset) _ _ _ } -> oneChar $ Set.toList cset
+        PAnyNot{ getPatternSet = PatternSet (Just cset) _ _ _ } -> oneChar $ charExclude (Set.toList cset)
+        _           -> fail $ "Invalid pattern: " ++ show pat
+    oneChar = oneof . map (return . (:[]))
+    charExclude = (['\0'..'\255'] \\)
+    expandEscape ch = case ch of
+        'n' -> "\n"
+        't' -> "\t"
+        'r' -> "\r"
+        'f' -> "\f"
+        'a' -> "\a"
+        'e' -> "\ESC"
+        'd' -> ['0'..'9']
+        'w' -> ['0'..'9'] ++ '_' : ['a'..'z'] ++ ['A'..'Z']
+        's' -> "\9\32"
+        'D' -> charExclude $ ['0'..'9']
+        'W' -> charExclude $ ['0'..'9'] ++ '_' : ['a'..'z'] ++ ['A'..'Z']
+        'S' -> charExclude "\9\32"
+        _   -> [ch]
