diff --git a/benchmarks/benchmark.hs b/benchmarks/benchmark.hs
--- a/benchmarks/benchmark.hs
+++ b/benchmarks/benchmark.hs
@@ -2,67 +2,96 @@
 
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PackageImports #-}
 {-# LANGUAGE StandaloneDeriving #-}
 
 import qualified Control.DeepSeq as DeepSeq
 import qualified Criterion.Main as Criterion
 import qualified Data.Maybe as Maybe
+import qualified "semver" Data.SemVer as SemVer
+import qualified "semver-range" Data.SemVer as SemVerRange
 import qualified Data.Version as Version
 import qualified GHC.Generics as Ghc
 import qualified Salve.Internal as Salve
 import qualified Text.ParserCombinators.ReadP as ReadP
 
-deriving instance Ghc.Generic Salve.Build
-deriving instance Ghc.Generic Salve.Constraint
-deriving instance Ghc.Generic Salve.Operator
-deriving instance Ghc.Generic Salve.PreRelease
-deriving instance Ghc.Generic Salve.Version
-
 deriving instance DeepSeq.NFData Salve.Build
 deriving instance DeepSeq.NFData Salve.Constraint
 deriving instance DeepSeq.NFData Salve.Operator
 deriving instance DeepSeq.NFData Salve.PreRelease
 deriving instance DeepSeq.NFData Salve.Version
+deriving instance DeepSeq.NFData Salve.Wildcard
+deriving instance DeepSeq.NFData SemVerRange.PrereleaseTag
+deriving instance DeepSeq.NFData SemVerRange.PrereleaseTags
+deriving instance DeepSeq.NFData SemVerRange.SemVer
+deriving instance DeepSeq.NFData SemVerRange.SemVerRange
+deriving instance Ghc.Generic Salve.Build
+deriving instance Ghc.Generic Salve.Constraint
+deriving instance Ghc.Generic Salve.Operator
+deriving instance Ghc.Generic Salve.PreRelease
+deriving instance Ghc.Generic Salve.Version
+deriving instance Ghc.Generic Salve.Wildcard
+deriving instance Ghc.Generic SemVerRange.SemVerRange
 
 main :: IO ()
 main = Criterion.defaultMain
-  [ Criterion.bgroup "Data.Version.parseVersion" (map
-    (\ x -> Criterion.bench x (Criterion.nf (runReadP Version.parseVersion) x))
-    [ "0.0.0"
-    , "123.456.789-pre-release-build-metadata"
-    ])
-  , Criterion.bgroup "parseVersion" (map
-    (\ x -> Criterion.bench x (Criterion.nf Salve.parseVersion x))
-    [ "0.0.0"
-    , "123.456.789-pre.release+build.metadata"
-    ])
-  , Criterion.bgroup "parseConstraint" (map
-    (\ x -> Criterion.bench x (Criterion.nf Salve.parseConstraint x))
-    [ ">=0.0.0"
-    , ">1.2.3 || =1.2.3 >=1.2.3 <1.2.3 || 1.2.3 <=1.2.3 ~1.2.3 ^1.2.3 1.2.3 - 1.2.3"
-    ])
-  , Criterion.bgroup "Data.Version.showVersion" (map
-    (\ x ->
-      let y = Maybe.fromJust (runReadP Version.parseVersion x)
-      in Criterion.bench x (Criterion.nf Version.showVersion y))
-    [ "0.0.0"
-    , "123.456.789-pre-release-build-metadata"
-    ])
-  , Criterion.bgroup "renderVersion" (map
-    (\ x ->
-      let y = Salve.unsafeParseVersion x
-      in Criterion.bench x (Criterion.nf Salve.renderVersion y))
-    [ "0.0.0"
-    , "123.456.789-pre.release+build.metadata"
-    ])
-  , Criterion.bgroup "renderConstraint" (map
-    (\ x ->
-      let y = Salve.unsafeParseConstraint x
-      in Criterion.bench x (Criterion.nf Salve.renderConstraint y))
-    [ ">=0.0.0"
-    , ">1.2.3 || =1.2.3 >=1.2.3 <1.2.3 || 1.2.3 <=1.2.3 ~1.2.3 ^1.2.3"
-    ])
+  [ Criterion.bgroup "version"
+    [ Criterion.bgroup "parse"
+      [ Criterion.bgroup "simple"
+        [ Criterion.bench "base" $ Criterion.nf (runReadP Version.parseVersion) "0.0.0"
+        , Criterion.bench "salve" $ Criterion.nf Salve.parseVersion "0.0.0"
+        , Criterion.bench "semver" $ Criterion.nf (fromRight . SemVer.fromText) "0.0.0"
+        , Criterion.bench "semver-range" $ Criterion.nf (fromRight . SemVerRange.parseSemVer) "0.0.0"
+        ]
+      , Criterion.bgroup "complex"
+        [ Criterion.bench "base" $ Criterion.nf (runReadP Version.parseVersion) "1.22.333-pre-build"
+        , Criterion.bench "salve" $ Criterion.nf Salve.parseVersion "1.22.333-pre+build"
+        , Criterion.bench "semver" $ Criterion.nf (fromRight . SemVer.fromText) "1.22.333-pre+build"
+        , Criterion.bench "semver-range" $ Criterion.nf (fromRight . SemVerRange.parseSemVer) "1.22.333-pre+build"
+        ]
+      ]
+    , Criterion.bgroup "render"
+      [ Criterion.bgroup "simple"
+        [ let Just v = runReadP Version.parseVersion "0.0.0" in Criterion.bench "base" $ Criterion.nf Version.showVersion v
+        , let v = Salve.unsafeParseVersion "0.0.0" in Criterion.bench "salve" $ Criterion.nf Salve.renderVersion v
+        , let Right v = SemVer.fromText "0.0.0" in Criterion.bench "semver" $ Criterion.nf SemVer.toText v
+        , let Right v = SemVerRange.parseSemVer "0.0.0" in Criterion.bench "semver-range" $ Criterion.nf SemVerRange.renderSV v
+        ]
+      , Criterion.bgroup "complex"
+        [ let Just v = runReadP Version.parseVersion "1.22.333-pre-build" in Criterion.bench "base" $ Criterion.nf Version.showVersion v
+        , let v = Salve.unsafeParseVersion "1.22.333-pre+build" in Criterion.bench "salve" $ Criterion.nf Salve.renderVersion v
+        , let Right v = SemVer.fromText "1.22.333-pre+build" in Criterion.bench "semver" $ Criterion.nf SemVer.toText v
+        , let Right v = SemVerRange.parseSemVer "1.22.333-pre+build" in Criterion.bench "semver-range" $ Criterion.nf SemVerRange.renderSV v
+        ]
+      ]
+    ]
+  , Criterion.bgroup "constraint"
+    [ Criterion.bgroup "parse"
+      [ Criterion.bgroup "simple"
+        [ Criterion.bench "salve" $ Criterion.nf Salve.parseConstraint "0.0.0"
+        , Criterion.bench "semver-range" $ Criterion.nf (fromRight . SemVerRange.parseSemVerRange) "0.0.0"
+        ]
+      , Criterion.bgroup "complex"
+        [ Criterion.bench "salve" $ Criterion.nf Salve.parseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9"
+        , Criterion.bench "semver-range" $ Criterion.nf (fromRight . SemVerRange.parseSemVerRange) "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9"
+        ]
+      ]
+    , Criterion.bgroup "render"
+      [ Criterion.bgroup "simple"
+        [ let c = Salve.unsafeParseConstraint "0.0.0" in Criterion.bench "salve" $ Criterion.nf Salve.renderConstraint c
+        ]
+      , Criterion.bgroup "complex"
+        [ let c = Salve.unsafeParseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9" in Criterion.bench "salve" $ Criterion.nf Salve.renderConstraint c
+        ]
+      ]
+    ]
   ]
+
+fromRight :: Either a b -> Maybe b
+fromRight e = case e of
+  Left _ -> Nothing
+  Right r -> Just r
 
 runReadP :: ReadP.ReadP a -> String -> Maybe a
 runReadP parser string = Maybe.listToMaybe (do
diff --git a/library/Salve.hs b/library/Salve.hs
--- a/library/Salve.hs
+++ b/library/Salve.hs
@@ -38,16 +38,6 @@
 -- * Constructors
 makeVersion,
 initialVersion,
-constraintLT,
-constraintLE,
-constraintEQ,
-constraintGE,
-constraintGT,
-constraintAnd,
-constraintOr,
-constraintHyphen,
-constraintTilde,
-constraintCaret,
 
 -- * Parsing
 parseVersion,
@@ -152,28 +142,77 @@
 -- >>> parseConstraint "1.2"
 -- Nothing
 --
--- Wildcards (also known as "x-ranges") are not allowed.
+-- Wildcards (also known as "x-ranges") are allowed. The exact character used
+-- for the wildcard is not round-tripped.
 --
--- >>> parseConstraint "1.2.x"
+-- >>> renderConstraint <$> parseConstraint "1.2.x"
+-- Just "1.2.x"
+-- >>> renderConstraint <$> parseConstraint "1.2.X"
+-- Just "1.2.x"
+-- >>> renderConstraint <$> parseConstraint "1.2.*"
+-- Just "1.2.x"
+--
+-- Wildcards can be combined with other constraints.
+--
+-- >>> renderConstraint <$> parseConstraint "1.2.x 2.3.4"
+-- Just "1.2.x 2.3.4"
+-- >>> renderConstraint <$> parseConstraint "1.2.x || 2.3.4"
+-- Just "1.2.x || 2.3.4"
+--
+-- Wildcards are allowed at any position.
+--
+-- >>> renderConstraint <$> parseConstraint "1.2.x"
+-- Just "1.2.x"
+-- >>> renderConstraint <$> parseConstraint "1.x.x"
+-- Just "1.x.x"
+-- >>> renderConstraint <$> parseConstraint "x.x.x"
+-- Just "x.x.x"
+--
+-- Non-wildcards cannot come after wildcards.
+--
+-- >>> parseConstraint "1.x.3"
 -- Nothing
--- >>> parseConstraint "1.2.X"
+-- >>> parseConstraint "x.2.3"
 -- Nothing
--- >>> parseConstraint "1.2.*"
+-- >>> parseConstraint "x.x.3"
 -- Nothing
+-- >>> parseConstraint "x.2.x"
+-- Nothing
 --
--- Extra spaces are not allowed.
+-- Wildcards cannot be used with other operators.
 --
--- >>> parseConstraint " 1.2.3 "
+-- >>> parseConstraint "<1.2.x"
 -- Nothing
--- >>> parseConstraint "> 1.2.3"
+-- >>> parseConstraint "<=1.2.x"
 -- Nothing
--- >>> parseConstraint "1.2.3  -  2.3.4"
+-- >>> parseConstraint "=1.2.x"
 -- Nothing
--- >>> parseConstraint "1.2.3  2.3.4"
+-- >>> parseConstraint ">=1.2.x"
 -- Nothing
--- >>> parseConstraint "1.2.3  ||  2.3.4"
+-- >>> parseConstraint ">1.2.x"
 -- Nothing
+-- >>> parseConstraint "~1.2.x"
+-- Nothing
+-- >>> parseConstraint "^1.2.x"
+-- Nothing
+-- >>> parseConstraint "1.2.x - 2.3.4"
+-- Nothing
+-- >>> parseConstraint "1.2.3 - 2.3.x"
+-- Nothing
 --
+-- Spaces are allowed in most places. Extra spaces are not round-tripped.
+--
+-- >>> renderConstraint <$> parseConstraint " 1.2.3 "
+-- Just "1.2.3"
+-- >>> renderConstraint <$> parseConstraint "> 1.2.3"
+-- Just ">1.2.3"
+-- >>> renderConstraint <$> parseConstraint "1.2.3  -  2.3.4"
+-- Just "1.2.3 - 2.3.4"
+-- >>> renderConstraint <$> parseConstraint "1.2.3  2.3.4"
+-- Just "1.2.3 2.3.4"
+-- >>> renderConstraint <$> parseConstraint "1.2.3  ||  2.3.4"
+-- Just "1.2.3 || 2.3.4"
+--
 -- Parentheses are not allowed. Note that combining two constraints with a
 -- space (and) has higher precedence than combining them with pipes (or). In
 -- other words, @"a b || c"@ parses as @"(a b) || c"@, not @"a (b || c)"@.
@@ -213,7 +252,7 @@
 -- >>> renderConstraint <$> parseConstraint "=1.2.3"
 -- Just "1.2.3"
 --
--- Pre-releases and builds are allowed on any constraints.
+-- Pre-releases and builds are allowed on any constraints except wildcards.
 --
 -- >>> renderConstraint <$> parseConstraint "1.2.3-p+b"
 -- Just "1.2.3-p+b"
@@ -226,16 +265,19 @@
 -- >>> renderConstraint <$> parseConstraint "^1.2.3-p+b"
 -- Just "^1.2.3-p+b"
 --
+-- >>> parseConstraint "1.2.x-p+b"
+-- Nothing
+--
 -- These examples show every type of constraint in a single expression.
 --
--- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 =1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9"
--- Just "<1.2.0 <=1.2.1 1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9"
--- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9"
--- Just "<1.2.0 <=1.2.1 || 1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9"
--- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 =1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9"
--- Just "<1.2.0 || <=1.2.1 1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9"
--- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 || =1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9"
--- Just "<1.2.0 || <=1.2.1 || 1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9"
+-- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 =1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"
+-- Just "<1.2.0 <=1.2.1 1.2.2 >=1.2.3 >1.2.4 1.2.5 1.2.6 - 1.2.7 ~1.2.8 ^1.2.9 1.2.x"
+-- >>> renderConstraint <$> parseConstraint "<1.2.0 <=1.2.1 || =1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"
+-- Just "<1.2.0 <=1.2.1 || 1.2.2 >=1.2.3 || >1.2.4 1.2.5 || 1.2.6 - 1.2.7 ~1.2.8 || ^1.2.9 1.2.x"
+-- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 =1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"
+-- Just "<1.2.0 || <=1.2.1 1.2.2 || >=1.2.3 >1.2.4 || 1.2.5 1.2.6 - 1.2.7 || ~1.2.8 ^1.2.9 || 1.2.x"
+-- >>> renderConstraint <$> parseConstraint "<1.2.0 || <=1.2.1 || =1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"
+-- Just "<1.2.0 || <=1.2.1 || 1.2.2 || >=1.2.3 || >1.2.4 || 1.2.5 || 1.2.6 - 1.2.7 || ~1.2.8 || ^1.2.9 || 1.2.x"
 
 -- ** Satisfying constraints
 -- | Although in general you should use 'satisfies', 'parseVersion', and
@@ -385,6 +427,31 @@
 --     True
 --     >>> "0.0.4" ? "^0.0.3"
 --     False
+--
+-- -   Wildcard:
+--
+--     >>> "1.1.0" ? "1.2.x"
+--     False
+--     >>> "1.2.3" ? "1.2.x"
+--     True
+--     >>> "1.3.0" ? "1.2.x"
+--     False
+--
+--     >>> "0.1.0" ? "1.x.x"
+--     False
+--     >>> "1.0.0" ? "1.x.x"
+--     True
+--     >>> "1.2.3" ? "1.x.x"
+--     True
+--     >>> "2.0.0" ? "1.x.x"
+--     False
+--
+--     >>> "0.0.0" ? "x.x.x"
+--     True
+--     >>> "1.2.3" ? "x.x.x"
+--     True
+--     >>> "2.0.0" ? "x.x.x"
+--     True
 ) where
 
 import Salve.Internal
diff --git a/library/Salve/Internal.hs b/library/Salve/Internal.hs
--- a/library/Salve/Internal.hs
+++ b/library/Salve/Internal.hs
@@ -3,6 +3,14 @@
 -- that thing exported from "Salve".
 module Salve.Internal where
 
+import qualified Control.Monad as Monad
+import qualified Data.Char as Char
+import qualified Data.List as List
+import qualified Data.Maybe as Maybe
+import qualified Data.Ord as Ord
+import qualified Text.ParserCombinators.ReadP as ReadP
+import qualified Text.Read as Read
+
 -- $setup
 -- >>> import Lens.Micro
 -- >>> import Lens.Micro.Extras
@@ -62,9 +70,9 @@
 -- Just False
 instance Ord Version where
   compare x y = mconcat
-    [ comparing versionMajor x y
-    , comparing versionMinor x y
-    , comparing versionPatch x y
+    [ Ord.comparing versionMajor x y
+    , Ord.comparing versionMinor x y
+    , Ord.comparing versionPatch x y
     , case both versionPreReleases (x, y) of
       ([], []) -> EQ
       ([], _) -> GT
@@ -128,6 +136,7 @@
 data Constraint
   = ConstraintOperator Operator Version
   | ConstraintHyphen Version Version
+  | ConstraintWildcard Wildcard
   | ConstraintAnd Constraint Constraint
   | ConstraintOr Constraint Constraint
   deriving (Eq, Show)
@@ -155,99 +164,6 @@
 initialVersion :: Version
 initialVersion = makeVersion 0 0 0 [] []
 
--- | Makes a new constraint that must be less than the version number.
---
--- >>> constraintLT <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "<1.2.3"
--- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintLT :: Version -> Constraint
-constraintLT v = ConstraintOperator OperatorLT v
-
--- | Makes a new constraint that must be less than or euqal to the version
--- number.
---
--- >>> constraintLE <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "<=1.2.3"
--- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintLE :: Version -> Constraint
-constraintLE v = ConstraintOperator OperatorLE v
-
--- | Makes a new constraint that must be equal to the version number.
---
--- >>> constraintEQ <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "=1.2.3"
--- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintEQ :: Version -> Constraint
-constraintEQ v = ConstraintOperator OperatorEQ v
-
--- | Makes a new constraint that must be greater than or equal to the version
--- number.
---
--- >>> constraintGE <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint ">=1.2.3"
--- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintGE :: Version -> Constraint
-constraintGE v = ConstraintOperator OperatorGE v
-
--- | Makes a new constraint that must be greater than the version number.
---
--- >>> constraintGT <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint ">1.2.3"
--- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintGT :: Version -> Constraint
-constraintGT v = ConstraintOperator OperatorGT v
-
--- | Makes a new constraint that must satisfy both constraints.
---
--- >>> constraintAnd <$> (constraintGE <$> parseVersion "1.2.3") <*> (constraintLT <$> parseVersion "2.0.0")
--- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))
--- >>> parseConstraint ">=1.2.3 <2.0.0"
--- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))
-constraintAnd :: Constraint -> Constraint -> Constraint
-constraintAnd l r = ConstraintAnd l r
-
--- | Makes a new constraint that must satisfy either constraint.
---
--- >>> constraintOr <$> (constraintEQ <$> parseVersion "1.2.3") <*> (constraintGT <$> parseVersion "1.2.3")
--- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))
--- >>> parseConstraint "=1.2.3 || >1.2.3"
--- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))
-constraintOr :: Constraint -> Constraint -> Constraint
-constraintOr l r = ConstraintOr l r
-
--- | Makes a new constraint that must be between the versions, inclusive.
---
--- >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"
--- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "1.2.3 - 2.3.4"
--- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))
-constraintHyphen :: Version -> Version -> Constraint
-constraintHyphen v w = ConstraintHyphen v w
-
--- | Makes a new constraint that allows changes to the patch version number.
---
--- >>> constraintTilde <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "~1.2.3"
--- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintTilde :: Version -> Constraint
-constraintTilde v = ConstraintOperator OperatorTilde v
-
--- | Makes a new constraint that allows changes that do not modify the
--- left-most non-zero version number.
---
--- >>> constraintCaret <$> parseVersion "1.2.3"
--- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
--- >>> parseConstraint "^1.2.3"
--- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
-constraintCaret :: Version -> Constraint
-constraintCaret v = ConstraintOperator OperatorCaret v
-
 -- | Attempts to parse a version. This parser follows [SemVer's
 -- BNF](https://github.com/mojombo/semver/blob/eb9aac5/semver.md#backusnaur-form-grammar-for-valid-semver-versions).
 --
@@ -444,9 +360,13 @@
       OperatorGT -> '>' : s
       OperatorTilde -> '~' : s
       OperatorCaret -> '^' : s
-  ConstraintHyphen l r -> join ' ' [renderVersion l, "-", renderVersion r]
-  ConstraintAnd l r -> join ' ' (map renderConstraint [l, r])
-  ConstraintOr l r -> join ' ' [renderConstraint l, "||", renderConstraint r]
+  ConstraintHyphen l r -> unwords [renderVersion l, "-", renderVersion r]
+  ConstraintWildcard w -> case w of
+    WildcardMajor -> "x.x.x"
+    WildcardMinor m -> show m ++ ".x.x"
+    WildcardPatch m n -> List.intercalate "." [show m, show n, "x"]
+  ConstraintAnd l r -> unwords (map renderConstraint [l, r])
+  ConstraintOr l r -> unwords [renderConstraint l, "||", renderConstraint r]
 
 -- | Returns 'True' if the major version number is zero, 'False' otherwise.
 --
@@ -549,6 +469,10 @@
             else makeVersion (versionMajor w + 1) 0 0 [] []
       in (v >= w) && (v < u)
   ConstraintHyphen l r -> (l <= v) && (v <= r)
+  ConstraintWildcard w -> case w of
+    WildcardMajor -> True
+    WildcardMinor m -> (makeVersion m 0 0 [] [] <= v) && (v < makeVersion (m + 1) 0 0 [] [])
+    WildcardPatch m n -> (makeVersion m n 0 [] [] <= v) && (v < makeVersion m (n + 1) 0 [] [])
   ConstraintAnd l r -> satisfies v l && satisfies v r
   ConstraintOr l r -> satisfies v l || satisfies v r
 
@@ -621,266 +545,291 @@
   | OperatorCaret
   deriving (Eq, Show)
 
+data Wildcard
+  = WildcardMajor
+  | WildcardMinor Word
+  | WildcardPatch Word Word
+  deriving (Eq, Show)
+
+-- ** Constructors
+
+-- | Makes a new constraint that must be less than the version number.
+--
+-- >>> constraintLT <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "<1.2.3"
+-- Just (ConstraintOperator OperatorLT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintLT :: Version -> Constraint
+constraintLT v = ConstraintOperator OperatorLT v
+
+-- | Makes a new constraint that must be less than or euqal to the version
+-- number.
+--
+-- >>> constraintLE <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "<=1.2.3"
+-- Just (ConstraintOperator OperatorLE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintLE :: Version -> Constraint
+constraintLE v = ConstraintOperator OperatorLE v
+
+-- | Makes a new constraint that must be equal to the version number.
+--
+-- >>> constraintEQ <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "=1.2.3"
+-- Just (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintEQ :: Version -> Constraint
+constraintEQ v = ConstraintOperator OperatorEQ v
+
+-- | Makes a new constraint that must be greater than or equal to the version
+-- number.
+--
+-- >>> constraintGE <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint ">=1.2.3"
+-- Just (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintGE :: Version -> Constraint
+constraintGE v = ConstraintOperator OperatorGE v
+
+-- | Makes a new constraint that must be greater than the version number.
+--
+-- >>> constraintGT <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint ">1.2.3"
+-- Just (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintGT :: Version -> Constraint
+constraintGT v = ConstraintOperator OperatorGT v
+
+-- | Makes a new constraint that must satisfy both constraints.
+--
+-- >>> constraintAnd <$> (constraintGE <$> parseVersion "1.2.3") <*> (constraintLT <$> parseVersion "2.0.0")
+-- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))
+-- >>> parseConstraint ">=1.2.3 <2.0.0"
+-- Just (ConstraintAnd (ConstraintOperator OperatorGE (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorLT (Version {versionMajor = 2, versionMinor = 0, versionPatch = 0, versionPreReleases = [], versionBuilds = []})))
+constraintAnd :: Constraint -> Constraint -> Constraint
+constraintAnd l r = ConstraintAnd l r
+
+-- | Makes a new constraint that must satisfy either constraint.
+--
+-- >>> constraintOr <$> (constraintEQ <$> parseVersion "1.2.3") <*> (constraintGT <$> parseVersion "1.2.3")
+-- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))
+-- >>> parseConstraint "=1.2.3 || >1.2.3"
+-- Just (ConstraintOr (ConstraintOperator OperatorEQ (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})) (ConstraintOperator OperatorGT (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []})))
+constraintOr :: Constraint -> Constraint -> Constraint
+constraintOr l r = ConstraintOr l r
+
+-- | Makes a new constraint that must be between the versions, inclusive.
+--
+-- >>> constraintHyphen <$> parseVersion "1.2.3" <*> parseVersion "2.3.4"
+-- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "1.2.3 - 2.3.4"
+-- Just (ConstraintHyphen (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}) (Version {versionMajor = 2, versionMinor = 3, versionPatch = 4, versionPreReleases = [], versionBuilds = []}))
+constraintHyphen :: Version -> Version -> Constraint
+constraintHyphen v w = ConstraintHyphen v w
+
+-- | Makes a new constraint that allows changes to the patch version number.
+--
+-- >>> constraintTilde <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "~1.2.3"
+-- Just (ConstraintOperator OperatorTilde (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintTilde :: Version -> Constraint
+constraintTilde v = ConstraintOperator OperatorTilde v
+
+-- | Makes a new constraint that allows changes that do not modify the
+-- left-most non-zero version number.
+--
+-- >>> constraintCaret <$> parseVersion "1.2.3"
+-- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+-- >>> parseConstraint "^1.2.3"
+-- Just (ConstraintOperator OperatorCaret (Version {versionMajor = 1, versionMinor = 2, versionPatch = 3, versionPreReleases = [], versionBuilds = []}))
+constraintCaret :: Version -> Constraint
+constraintCaret v = ConstraintOperator OperatorCaret v
+
 -- ** Parsing
 
-versionP :: Parser Version
+versionP :: ReadP.ReadP Version
 versionP = do
   major <- numberP
-  _ <- charP '.'
+  Monad.void (ReadP.char '.')
   minor <- numberP
-  _ <- charP '.'
+  Monad.void (ReadP.char '.')
   patch <- numberP
   preReleases <- preReleasesP
   builds <- buildsP
   pure (makeVersion major minor patch preReleases builds)
 
-preReleasesP :: Parser [PreRelease]
-preReleasesP = optionP [] (do
-  _ <- charP '-'
-  sepBy1P (charP '.') preReleaseP)
+preReleasesP :: ReadP.ReadP [PreRelease]
+preReleasesP = ReadP.option [] (do
+  Monad.void (ReadP.char '-')
+  ReadP.sepBy1 preReleaseP (ReadP.char '.'))
 
-preReleaseP :: Parser PreRelease
-preReleaseP = choiceP preReleaseStringP preReleaseNumberP
+preReleaseP :: ReadP.ReadP PreRelease
+preReleaseP = preReleaseNumberP ReadP.<++ preReleaseStringP
 
-preReleaseNumberP :: Parser PreRelease
+preReleaseNumberP :: ReadP.ReadP PreRelease
 preReleaseNumberP = do
   n <- numberP
   pure (PreReleaseNumeric n)
 
-preReleaseStringP :: Parser PreRelease
+preReleaseStringP :: ReadP.ReadP PreRelease
 preReleaseStringP = do
-  s <- someP (satisfyP isIdentifier)
-  if all isAsciiDigit s
-    then failP
+  s <- ReadP.munch1 isIdentifier
+  if all Char.isDigit s
+    then ReadP.pfail
     else pure (PreReleaseTextual s)
 
-buildsP :: Parser [Build]
-buildsP = optionP [] (do
-  _ <- charP '+'
-  sepBy1P (charP '.') buildP)
+buildsP :: ReadP.ReadP [Build]
+buildsP = ReadP.option [] (do
+  Monad.void (ReadP.char '+')
+  ReadP.sepBy1 buildP (ReadP.char '.'))
 
-buildP :: Parser Build
+buildP :: ReadP.ReadP Build
 buildP = do
-  b <- someP (satisfyP isIdentifier)
+  b <- ReadP.munch1 isIdentifier
   pure (Build b)
 
-numberP :: Parser Word
-numberP = choiceP zeroP nonZeroP
+numberP :: ReadP.ReadP Word
+numberP = nonZeroP ReadP.<++ zeroP
 
-zeroP :: Parser Word
+zeroP :: ReadP.ReadP Word
 zeroP = do
-  _ <- charP '0'
+  Monad.void (ReadP.char '0')
   pure 0
 
-nonZeroP :: Parser Word
+nonZeroP :: ReadP.ReadP Word
 nonZeroP = do
-  x <- satisfyP isAsciiDigitNonZero
-  ys <- manyP (satisfyP isAsciiDigit)
-  pure (read (x : ys))
+  x <- ReadP.satisfy isAsciiDigitNonZero
+  ys <- ReadP.munch Char.isDigit
+  case Read.readMaybe (x : ys) of
+    Nothing -> ReadP.pfail
+    Just n -> pure n
 
-constraintsP :: Parser Constraint
+constraintsP :: ReadP.ReadP Constraint
 constraintsP = do
-  cs <- sepBy1P orP constraintP
+  spacesP
+  cs <- ReadP.sepBy1 constraintP orP
+  spacesP
   pure (foldr1 constraintOr cs)
 
-constraintP :: Parser Constraint
+constraintP :: ReadP.ReadP Constraint
 constraintP = do
-  cs <- sepBy1P spaceP simpleP
+  cs <- ReadP.sepBy1 simpleP spaces1P
   pure (foldr1 constraintAnd cs)
 
-hyphenatedP :: Parser Constraint
+hyphenatedP :: ReadP.ReadP Constraint
 hyphenatedP = do
   v <- versionP
-  _ <- hyphenP
+  hyphenP
   w <- versionP
   pure (constraintHyphen v w)
 
-simpleP :: Parser Constraint
-simpleP = oneOfP [hyphenatedP, caretP, tildeP, primitiveP]
+simpleP :: ReadP.ReadP Constraint
+simpleP = ReadP.choice [hyphenatedP, wildcardConstraintP, primitiveP]
 
-caretP :: Parser Constraint
-caretP = do
-  _ <- charP '^'
-  v <- versionP
-  pure (constraintCaret v)
+wildcardConstraintP :: ReadP.ReadP Constraint
+wildcardConstraintP = do
+  w <- wildcardP
+  pure (ConstraintWildcard w)
 
-tildeP :: Parser Constraint
-tildeP = do
-  _ <- charP '~'
-  v <- versionP
-  pure (constraintTilde v)
+wildcardP :: ReadP.ReadP Wildcard
+wildcardP = ReadP.choice [wildcardPatchP, wildcardMinorP, wildcardMajorP]
 
-primitiveP :: Parser Constraint
+wildcardPatchP :: ReadP.ReadP Wildcard
+wildcardPatchP = do
+  m <- numberP
+  Monad.void (ReadP.char '.')
+  n <- numberP
+  Monad.void (ReadP.char '.')
+  Monad.void (ReadP.satisfy isWildcard)
+  pure (WildcardPatch m n)
+
+wildcardMinorP :: ReadP.ReadP Wildcard
+wildcardMinorP = do
+  m <- numberP
+  Monad.void (ReadP.char '.')
+  Monad.void (ReadP.satisfy isWildcard)
+  Monad.void (ReadP.char '.')
+  Monad.void (ReadP.satisfy isWildcard)
+  pure (WildcardMinor m)
+
+wildcardMajorP :: ReadP.ReadP Wildcard
+wildcardMajorP = do
+  Monad.void (ReadP.satisfy isWildcard)
+  Monad.void (ReadP.char '.')
+  Monad.void (ReadP.satisfy isWildcard)
+  Monad.void (ReadP.char '.')
+  Monad.void (ReadP.satisfy isWildcard)
+  pure WildcardMajor
+
+primitiveP :: ReadP.ReadP Constraint
 primitiveP = do
   o <- operatorP
+  spacesP
   v <- versionP
   pure (ConstraintOperator o v)
 
-operatorP :: Parser Operator
-operatorP = oneOfP [leP, geP, ltP, gtP, eqP, pure OperatorEQ]
-
-leP :: Parser Operator
-leP = do
-  _ <- stringP "<="
-  pure OperatorLE
-
-geP :: Parser Operator
-geP = do
-  _ <- stringP ">="
-  pure OperatorGE
-
-ltP :: Parser Operator
-ltP = do
-  _ <- charP '<'
-  pure OperatorLT
-
-gtP :: Parser Operator
-gtP = do
-  _ <- charP '>'
-  pure OperatorGT
+operatorP :: ReadP.ReadP Operator
+operatorP = ReadP.choice
+  [ ReadP.string "<=" *> pure OperatorLE
+  , ReadP.string ">=" *> pure OperatorGE
+  , ReadP.char '<' *> pure OperatorLT
+  , ReadP.char '>' *> pure OperatorGT
+  , ReadP.char '=' *> pure OperatorEQ
+  , ReadP.char '^' *> pure OperatorCaret
+  , ReadP.char '~' *> pure OperatorTilde
+  , pure OperatorEQ
+  ]
 
-eqP :: Parser Operator
-eqP = do
-  _ <- charP '='
-  pure OperatorEQ
+hyphenP :: ReadP.ReadP ()
+hyphenP = do
+  spaces1P
+  Monad.void (ReadP.char '-')
+  spaces1P
 
-hyphenP :: Parser String
-hyphenP = stringP " - "
+orP :: ReadP.ReadP ()
+orP = do
+  spaces1P
+  Monad.void (ReadP.string "||")
+  spaces1P
 
-orP :: Parser String
-orP = stringP " || "
+spaces1P :: ReadP.ReadP ()
+spaces1P = Monad.void (ReadP.munch1 (== ' '))
 
-spaceP :: Parser Char
-spaceP = charP ' '
+spacesP :: ReadP.ReadP ()
+spacesP = Monad.void (ReadP.munch (== ' '))
 
 -- *** Helpers
 
-parse :: Parser a -> String -> Maybe a
-parse p s = safeHead (do
-  (x, "") <- runParser p s
-  pure x)
-
-newtype Parser a = Parser { runParser :: String -> [(a, String)] }
-
-instance Functor Parser where
-  fmap f p = Parser (\s -> do
-    (x, t) <- runParser p s
-    pure (f x, t))
-
-instance Applicative Parser where
-  pure x = Parser (\ s -> pure (x, s))
-
-  p <*> q = Parser (\ s -> do
-    (f, t) <- runParser p s
-    (x, u) <- runParser q t
-    pure (f x, u))
-
-instance Monad Parser where
-  fail x = Parser (\ _ -> fail x)
-
-  p >>= f = Parser (\ s -> do
-    (x, t) <- runParser p s
-    runParser (f x) t)
-
-charP :: Char -> Parser Char
-charP c = satisfyP (\ d -> d == c)
-
-choiceP :: Parser a -> Parser a -> Parser a
-choiceP p q = Parser (\ s -> case runParser p s of
-  [] -> runParser q s
-  xs -> xs)
-
-failP :: Parser a
-failP = fail undefined
-
-getP :: Parser Char
-getP = Parser (\ s -> case s of
-  "" -> []
-  c : t -> pure (c, t))
-
-manyP :: Parser a -> Parser [a]
-manyP p = choiceP (someP p) (pure [])
-
-oneOfP :: [Parser a] -> Parser a
-oneOfP ps = foldr choiceP failP ps
-
-optionP :: a -> Parser a -> Parser a
-optionP x p = choiceP p (pure x)
-
-satisfyP :: (Char -> Bool) -> Parser Char
-satisfyP f = do
-  c <- getP
-  if f c then pure c else failP
-
-sepByP :: Parser b -> Parser a -> Parser [a]
-sepByP q p = choiceP (sepBy1P q p) (pure [])
-
-sepBy1P :: Parser b -> Parser a -> Parser [a]
-sepBy1P q p = do
-  x <- p
-  xs <- optionP [] (do
-    _ <- q
-    sepBy1P q p)
-  pure (x : xs)
-
-someP :: Parser a -> Parser [a]
-someP p = do
-  x <- p
-  xs <- manyP p
-  pure (x : xs)
-
-stringP :: String -> Parser String
-stringP s = case s of
-  "" -> pure s
-  c : t -> do
-    _ <- charP c
-    _ <- stringP t
-    pure s
+parse :: ReadP.ReadP a -> String -> Maybe a
+parse p s =
+  let p' = ReadP.readP_to_S p
+  in Maybe.listToMaybe (do
+    (x, "") <- p' s
+    pure x)
 
 -- ** Rendering
 
 renderPreReleases :: [PreRelease] -> String
 renderPreReleases ps = if null ps
   then ""
-  else '-' : join '.' (map renderPreRelease ps)
+  else '-' : List.intercalate "." (map renderPreRelease ps)
 
 renderBuilds :: [Build] -> String
 renderBuilds bs = if null bs
   then ""
-  else '+' : join '.' (map renderBuild bs)
+  else '+' : List.intercalate "." (map renderBuild bs)
 
 -- ** Helpers
 
 both :: (a -> b) -> (a, a) -> (b, b)
 both f (x, y) = (f x, f y)
 
-comparing :: Ord b => (a -> b) -> a -> a -> Ordering
-comparing f x y = compare (f x) (f y)
-
-isAsciiAlpha :: Char -> Bool
-isAsciiAlpha c = (isAsciiAlphaUpper c) || (isAsciiAlphaLower c)
-
-isAsciiAlphaLower :: Char -> Bool
-isAsciiAlphaLower c = ('a' <= c) && (c <= 'z')
-
-isAsciiAlphaUpper :: Char -> Bool
-isAsciiAlphaUpper c = ('A' <= c) && (c <= 'Z')
-
-isAsciiDigit :: Char -> Bool
-isAsciiDigit c = ('0' <= c) && (c <= '9')
-
 isAsciiDigitNonZero :: Char -> Bool
-isAsciiDigitNonZero c = isAsciiDigit c && (c /= '0')
+isAsciiDigitNonZero c = Char.isDigit c && (c /= '0')
 
 isIdentifier :: Char -> Bool
-isIdentifier c = (c == '-') || isAsciiDigit c || isAsciiAlpha c
-
-join :: Char -> [String] -> String
-join c ss = case ss of
-  [] -> ""
-  s : ts -> mconcat (s : map (\ t -> c : t) ts)
+isIdentifier c = (Char.isAscii c && Char.isAlphaNum c) || (c == '-')
 
-safeHead :: [a] -> Maybe a
-safeHead xs = case xs of
-  [] -> Nothing
-  x : _ -> Just x
+isWildcard :: Char -> Bool
+isWildcard c = (c == 'x') || (c == '*') || (c == 'X')
diff --git a/salve.cabal b/salve.cabal
--- a/salve.cabal
+++ b/salve.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           salve
-version:        0.0.3
+version:        0.0.4
 synopsis:       Semantic version numbers and constraints.
 description:    Salve provides semantic version numbers and constraints.
 category:       Distribution
@@ -57,4 +57,6 @@
     , criterion >=1.1.0 && <1.3
     , deepseq >=1.4.1 && <1.5
     , salve
+    , semver >=0.3.3 && <0.4
+    , semver-range >=0.2.2 && <0.3
   default-language: Haskell2010
