diff --git a/Development/Shake/Core.hs b/Development/Shake/Core.hs
--- a/Development/Shake/Core.hs
+++ b/Development/Shake/Core.hs
@@ -45,7 +45,7 @@
     ,shakeStaunch :: Bool -- ^ Operate in staunch mode, where building continues even after errors (defaults to 'False').
     ,shakeReport :: Maybe FilePath -- ^ Produce an HTML profiling report (defaults to 'Nothing').
     ,shakeLint :: Bool -- ^ Perform basic sanity checks after building (defaults to 'False').
-    ,shakeDeterministic :: Bool -- ^ Build files in a detereminstic order, as far as possbile
+    ,shakeDeterministic :: Bool -- ^ Build files in a deterministic order, as far as possible
     }
     deriving (Show,Eq,Ord,Typeable,Data)
 
@@ -54,7 +54,7 @@
 shakeOptions = ShakeOptions ".shake" 1 1 Normal False Nothing False False
 
 
--- | All forseen exception conditions thrown by Shake, such problems with the rules or errors when executing
+-- | All foreseen exception conditions thrown by Shake, such problems with the rules or errors when executing
 --   rules, will be raised using this exception type.
 data ShakeException = ShakeException
         [String] -- Entries on the stack, starting at the top of the stack.
@@ -170,18 +170,20 @@
 -- | Like 'rule', but lower priority, if no 'rule' exists then 'defaultRule' is checked.
 --   All default rules must be disjoint.
 defaultRule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()
-defaultRule = ruleWith 0
+defaultRule = rulePriority 0
 
 
 -- | Add a rule to build a key, returning an appropriate 'Action'. All rules must be disjoint.
 --   To define lower priority rules use 'defaultRule'.
 rule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()
-rule = ruleWith 1
+rule = rulePriority 1
 
 
--- | Add a rule at a given priority.
-ruleWith :: Rule key value => Int -> (key -> Maybe (Action value)) -> Rules ()
-ruleWith i r = mempty{rules = Map.singleton k (k, v, [(i,ARule r)])}
+-- | Add a rule at a given priority, higher numbers correspond to higher-priority rules.
+--   The function 'defaultRule' is priority 0 and 'rule' is priority 1. All rules of the same
+--   priority must be disjoint.
+rulePriority :: Rule key value => Int -> (key -> Maybe (Action value)) -> Rules ()
+rulePriority i r = mempty{rules = Map.singleton k (k, v, [(i,ARule r)])}
     where k = typeOf $ ruleKey r; v = typeOf $ ruleValue r
 
 
@@ -217,7 +219,7 @@
     deriving (Functor, Monad, MonadIO)
 
 
--- | This function is not actually exported, but Haddock is buggy. Please ignore.
+-- | Internal main function (not exported publicly)
 run :: ShakeOptions -> Rules () -> IO ()
 run opts@ShakeOptions{..} rs = do
     start <- startTime
diff --git a/Development/Shake/FilePattern.hs b/Development/Shake/FilePattern.hs
--- a/Development/Shake/FilePattern.hs
+++ b/Development/Shake/FilePattern.hs
@@ -1,11 +1,10 @@
-{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
 
 module Development.Shake.FilePattern(
     FilePattern, (?==),
     compatible, extract, substitute
     ) where
 
-import Data.List
+import System.FilePath(pathSeparators)
 
 
 -- | A type synonym for file patterns, containing @\/\/@ and @*@. For the syntax
@@ -13,11 +12,57 @@
 type FilePattern = String
 
 
+data Lexeme = Star | SlashSlash | Char Char deriving (Show, Eq)
+
+isChar (Char _) = True; isChar _ = False
+
+data Regex = Lit [Char] | Not [Char] | Any
+           | Start | End
+           | Bracket Regex
+           | Or Regex Regex | Concat Regex Regex
+           | Repeat Regex | Empty
+             deriving Show
+
+type SString = (Bool, String) -- fst is True if at the start of the string
+
+
+lexer :: FilePattern -> [Lexeme]
+lexer ('*':xs) = Star : lexer xs
+lexer ('/':'/':xs) = SlashSlash : lexer xs
+lexer (x:xs) = Char x : lexer xs
+lexer [] = []
+
+
+pattern :: [Lexeme] -> Regex
+pattern = Concat Start . foldr Concat End . map f
+    where
+        f Star = Bracket $ Repeat $ Not pathSeparators
+        f SlashSlash = let s = Start `Or` End `Or` Lit pathSeparators in Bracket $ s `Concat` Repeat Any `Concat` s
+        f (Char x) = Lit $ if x == '/' then pathSeparators else [x]
+
+
+-- | Return is (brackets, matched, rest)
+match :: Regex -> SString -> [([String], String, SString)]
+match (Lit l) (_, x:xs) | x `elem` l = [([], [x], (False, xs))]
+match (Not l) (_, x:xs) | x `notElem` l = [([], [x], (False, xs))]
+match Any (_, x:xs) = [([], [x], (False, xs))]
+match Start (True, xs) = [([], [], (True, xs))]
+match End (s, []) = [([], [], (s, []))]
+match (Bracket r) xs = [(a ++ [b], b, c) | (a,b,c) <- match r xs]
+match (Or r1 r2) xs = match r1 xs ++ match r2 xs
+match (Concat r1 r2) xs = [(a1++a2,b1++b2,c2) | (a1,b1,c1) <- match r1 xs, (a2,b2,c2) <- match r2 c1]
+match (Repeat r) xs = match (Empty `Or` Concat r (Repeat r)) xs
+match Empty xs = [([], "", xs)]
+match _ _ = []
+
+
+
+
 -- | Match a 'FilePattern' against a 'FilePath', There are only two special forms:
 --
 -- * @*@ matches an entire path component, excluding any separators.
 --
--- * @\/\/@ matches an arbitrary number of path componenets.
+-- * @\/\/@ matches an arbitrary number of path components.
 --
 --   Some examples that match:
 --
@@ -32,46 +77,29 @@
 -- > "*/*.c" ?== "foo/bar/baz.c"
 --
 (?==) :: FilePattern -> FilePath -> Bool
-(?==) ('/':'/':p) x = any (p ?==) $ x : [i | '/':i <- tails x]
-(?==) ('*':p) x = any (p ?==) $ a ++ take 1 b
-    where (a,b) = break ("/" `isPrefixOf`) $ tails x
-(?==) (p:ps) (x:xs) | p == x = ps ?== xs
-(?==) [] [] = True
-(?==) _ _ = False
+(?==) p x = not $ null $ match (pattern $ lexer p) (True, x)
 
 
 -- | Do they have the same * and // counts in the same order
 compatible :: [FilePattern] -> Bool
 compatible [] = True
 compatible (x:xs) = all ((==) (f x) . f) xs
-    where
-        f ('*':xs) = '*':f xs
-        f ('/':'/':xs) = '/':f xs
-        f (x:xs) = f xs
-        f [] = []
+    where f = filter (not . isChar) . lexer
 
 
 -- | Extract the items that match the wildcards. The pair must match with '?=='.
 extract :: FilePattern -> FilePath -> [String]
-extract p x = head $ f p x ++ [[]]
-    where
-        f ('/':'/':p) x = rest p $ ("",x) : [(pre++"/",i) | (pre,'/':i) <- zip (inits x) (tails x)]
-        f ('*':p) x = rest p $ a ++ take 1 b
-            where (a,b) = break (isPrefixOf "/" . snd) $ zip (inits x) (tails x)
-        f (p:ps) (x:xs) | p == x = f ps xs
-        f [] [] = [[]]
-        f _ _ = []
-
-        rest p xs = [skip:res | (skip,keep) <- xs, res <- f p keep]
+extract p x = ms
+    where (ms,_,_):_ = match (pattern $ lexer p) (True,x)
 
 
 -- | Given the result of 'extract', substitute it back in to a 'compatible' pattern.
 --
 -- > p '?==' x ==> substitute (extract p x) p == x
 substitute :: [String] -> FilePattern -> FilePath
-substitute = f
+substitute ms p = f ms (lexer p)
     where
-        f (a:as) ('/':'/':ps) = a ++ f as ps
-        f (a:as) ('*':ps) = a ++ f as ps
-        f as (p:ps) = p : f as ps
-        f as [] = []
+        f ms (Char p:ps) = p : f ms ps
+        f (m:ms) (_:ps) = m ++ f ms ps
+        f [] [] = []
+        f _ _ = error $ "Substitution failed into pattern " ++ show p ++ " with " ++ show (length ms) ++ " matches, namely " ++ show ms
diff --git a/Examples/Test/Basic.hs b/Examples/Test/Basic.hs
--- a/Examples/Test/Basic.hs
+++ b/Examples/Test/Basic.hs
@@ -25,14 +25,6 @@
 
 
 test build obj = do
-    let f b pat file = assert (b == (pat ?== file)) $ show pat ++ " ?== " ++ show file ++ "\nEXPECTED: " ++ show b
-    f True "//*.c" "foo/bar/baz.c"
-    f True "*.c" "baz.c"
-    f True "//*.c" "baz.c"
-    f True "test.c" "test.c"
-    f False "*.c" "foor/bar.c"
-    f False "*/*.c" "foo/bar/baz.c"
-
     writeFile (obj "A.txt") "AAA"
     writeFile (obj "B.txt") "BBB"
     build ["AB.txt"]
diff --git a/Examples/Test/FilePattern.hs b/Examples/Test/FilePattern.hs
new file mode 100644
--- /dev/null
+++ b/Examples/Test/FilePattern.hs
@@ -0,0 +1,51 @@
+
+module Examples.Test.FilePattern(main) where
+
+import Development.Shake.FilePattern
+import Examples.Util
+
+main = shaken test $ \args obj -> return ()
+
+
+test build obj = do
+    let f b pat file = assert (b == (pat ?== file)) $ show pat ++ " ?== " ++ show file ++ "\nEXPECTED: " ++ show b
+    f True "//*.c" "foo/bar/baz.c"
+    f True "*.c" "baz.c"
+    f True "//*.c" "baz.c"
+    f True "test.c" "test.c"
+    f False "*.c" "foor/bar.c"
+    f False "*/*.c" "foo/bar/baz.c"
+    f False "foo//bar" "foobar"
+    f False "foo//bar" "foobar/bar"
+    f False "foo//bar" "foo/foobar"
+
+    assert (compatible []) "compatible"
+    assert (compatible ["//*a.txt","foo//a*.txt"]) "compatible"
+    assert (not $ compatible ["//*a.txt","foo//a*.*txt"]) "compatible"
+    extract "//*a.txt" "foo/bar/testa.txt" === ["foo/bar/","test"]
+    extract "//*a.txt" "testa.txt" === ["","test"]
+    extract "//*a*.txt" "testada.txt" === ["","test","da"]
+    substitute ["","test","da"] "//*a*.txt" === "testada.txt"
+    substitute  ["foo/bar/","test"] "//*a.txt" === "foo/bar/testa.txt"
+
+
+---------------------------------------------------------------------
+-- LAZY SMALLCHECK PROPERTIES
+
+{-
+newtype Pattern = Pattern FilePattern deriving (Show,Eq)
+newtype Path    = Path    FilePath    deriving (Show,Eq)
+
+-- Since / and * are the only "interesting" elements, just add ab to round out the set
+
+instance Serial Pattern where
+    series = cons Pattern >< f
+        where f = cons [] \/ cons (:) >< const (drawnFrom "/*ab") >< f
+
+instance Serial Path where
+    series = cons Path >< f
+        where f = cons [] \/ cons (:) >< const (drawnFrom "/ab") >< f
+
+testSmallCheck = do
+    smallCheck 10 $ \(Pattern p) (Path x) -> p ?== x ==> substitute (extract p x) p == x
+-}
diff --git a/Examples/Test/Files.hs b/Examples/Test/Files.hs
--- a/Examples/Test/Files.hs
+++ b/Examples/Test/Files.hs
@@ -2,7 +2,6 @@
 module Examples.Test.Files(main) where
 
 import Development.Shake
-import Development.Shake.FilePattern
 import Examples.Util
 import Control.Monad
 import Data.List
@@ -23,15 +22,6 @@
 
 
 test build obj = do
-    assert (compatible []) "compatible"
-    assert (compatible ["//*a.txt","foo//a*.txt"]) "compatible"
-    assert (not $ compatible ["//*a.txt","foo//a*.*txt"]) "compatible"
-    extract "//*a.txt" "foo/bar/testa.txt" === ["foo/bar/","test"]
-    extract "//*a.txt" "testa.txt" === ["","test"]
-    extract "//*a*.txt" "testada.txt" === ["","test","da"]
-    substitute ["","test","da"] "//*a*.txt" === "testada.txt"
-    substitute  ["foo/bar/","test"] "//*a.txt" === "foo/bar/testa.txt"
-
     forM_ [[],["fun"]] $ \args -> do
         sleepFileTime
         let nums = unlines . map show
diff --git a/Main.hs b/Main.hs
--- a/Main.hs
+++ b/Main.hs
@@ -12,6 +12,7 @@
 import qualified Examples.Test.Errors as Errors
 import qualified Examples.Test.Files as Files
 import qualified Examples.Test.FilePath as FilePath
+import qualified Examples.Test.FilePattern as FilePattern
 import qualified Examples.Test.Journal as Journal
 import qualified Examples.Test.Pool as Pool
 import qualified Examples.Test.Random as Random
@@ -23,8 +24,9 @@
 
 mains = ["tar" * Tar.main, "self" * Self.main, "c" * C.main
         ,"basic" * Basic.main, "directory" * Directory.main, "errors" * Errors.main
-        ,"filepath" * FilePath.main, "files" * Files.main, "journal" * Journal.main
-        ,"pool" * Pool.main, "random" * Random.main,"resources" * Resources.main]
+        ,"filepath" * FilePath.main, "filepattern" * FilePattern.main, "files" * Files.main
+        ,"journal" * Journal.main, "pool" * Pool.main, "random" * Random.main
+        ,"resources" * Resources.main]
     where (*) = (,)
 
 
diff --git a/shake.cabal b/shake.cabal
--- a/shake.cabal
+++ b/shake.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.8
 build-type:         Simple
 name:               shake
-version:            0.3.4
+version:            0.3.5
 license:            BSD3
 license-file:       LICENSE
 category:           Development
@@ -137,6 +137,7 @@
         Examples.Test.Directory
         Examples.Test.Errors
         Examples.Test.FilePath
+        Examples.Test.FilePattern
         Examples.Test.Files
         Examples.Test.Journal
         Examples.Test.Pool
