{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts #-} import Text.Regex.Applicative import Text.Regex.Applicative.Reference import Control.Applicative import Control.Monad import Data.Traversable import Data.Maybe import Text.Printf import Test.SmallCheck import Test.SmallCheck.Series import Test.Tasty import Test.Tasty.SmallCheck import Test.Tasty.HUnit import StateQueue -- Small alphabets as SmallCheck's series newtype A = A { a :: Char } deriving Show instance Monad m => Serial m A where series = cons0 $ A 'a' newtype AB = AB { ab :: Char } deriving Show instance Monad m => Serial m AB where series = cons0 (AB 'a') \/ cons0 (AB 'b') newtype ABC = ABC { abc :: Char } deriving Show instance Monad m => Serial m ABC where series = cons0 (ABC 'a') \/ cons0 (ABC 'b') \/ cons0 (ABC 'c') re1 = let one = pure 1 <* sym 'a' two = pure 2 <* sym 'a' <* sym 'a' in (,) <$> (one <|> two) <*> (two <|> one) re2 = sequenceA $ [ pure 1 <* sym 'a' <* sym 'a' <|> pure 2 <* sym 'a' , pure 3 <* sym 'b' , pure 4 <* sym 'b' <|> pure 5 <* sym 'a' ] re3 = sequenceA $ [ pure 0 <|> pure 1 , pure 1 <* sym 'a' <* sym 'a' <|> pure 2 <* sym 'a' , pure 3 <* sym 'b' <|> pure 6 , fmap (+1) $ pure 4 <* sym 'b' <|> pure 7 <|> pure 5 <* sym 'a' ] re4 = sym 'a' *> many (sym 'b') <* sym 'a' re5 = (sym 'a' <|> sym 'a' *> sym 'a') *> many (sym 'a') re6 = many (pure 3 <* sym 'a' <* sym 'a' <* sym 'a' <|> pure 1 <* sym 'a') -- Regular expression from the weighted regexp paper. re7 = let many_A_or_B = many (sym 'a' <|> sym 'b') in (,) <$> many ((,,,) <$> many_A_or_B <*> sym 'c' <*> many_A_or_B <*> sym 'c') <*> many_A_or_B re8 = (,) <$> many (sym 'a' <|> sym 'b') <*> many (sym 'b' <|> sym 'c') -- NB: we don't test these against the reference impl, 'cause it will loop! re9 = many (sym 'a' <|> empty) <* sym 'b' re10 = few (sym 'a' <|> empty) <* sym 'b' prop re f s = let fs = map f s in reference re fs == (fs =~ re) prop_withMatched = let re = withMatched $ many (string "a" <|> string "ba") in \str -> case map ab str =~ re of Nothing -> True Just (x, y) -> concat x == y -- Because we have 2 slightly different algorithms for recognition and parsing, -- we test that they agree testRecognitionAgainstParsing re f s = let fs = map f s in isJust (fs =~ re) == isJust (fs =~ (re *> pure ())) tests = testGroup "Tests" [ testGroup "Engine tests" [ t "re1" 10 $ prop re1 a , t "re2" 10 $ prop re2 ab , t "re3" 10 $ prop re3 ab , t "re4" 10 $ prop re4 ab , t "re5" 10 $ prop re5 a , t "re6" 10 $ prop re6 a , t "re7" 7 $ prop re7 abc , t "re8" 7 $ prop re8 abc ] , testGroup "Recognition vs parsing" [ t "re1" 10 $ testRecognitionAgainstParsing re1 a , t "re2" 10 $ testRecognitionAgainstParsing re2 ab , t "re3" 10 $ testRecognitionAgainstParsing re3 ab , t "re4" 10 $ testRecognitionAgainstParsing re4 ab , t "re5" 10 $ testRecognitionAgainstParsing re5 a , t "re6" 10 $ testRecognitionAgainstParsing re6 a , t "re7" 7 $ testRecognitionAgainstParsing re7 abc , t "re8" 7 $ testRecognitionAgainstParsing re8 abc , t "re8" 10 $ testRecognitionAgainstParsing re9 ab , t "re8" 10 $ testRecognitionAgainstParsing re10 ab ] , testProperty "withMatched" prop_withMatched , testGroup "Tests for matching functions" [ testGroup "findFirstPrefix" [ u "t1" (findFirstPrefix ("a" <|> "ab") "abc") (Just ("a","bc")) , u "t2" (findFirstPrefix ("ab" <|> "a") "abc") (Just ("ab","c")) , u "t3" (findFirstPrefix "bc" "abc") Nothing ] , testGroup "findFirstInfix" [ u "t1" (findFirstInfix ("a" <|> "ab") "tabc") (Just ("t", "a","bc")) , u "t2" (findFirstInfix ("ab" <|> "a") "tabc") (Just ("t", "ab","c")) ] , testGroup "findLongestPrefix" [ u "t1" (findLongestPrefix ("a" <|> "ab") "abc") (Just ("ab","c")) , u "t2" (findLongestPrefix ("ab" <|> "a") "abc") (Just ("ab","c")) , u "t3" (findLongestPrefix "bc" "abc") Nothing ] , testGroup "findLongestInfix" [ u "t1" (findLongestInfix ("a" <|> "ab") "tabc") (Just ("t", "ab","c")) , u "t2" (findLongestInfix ("ab" <|> "a") "tabc") (Just ("t", "ab","c")) , u "t3" (findLongestInfix "bc" "tabc") (Just ("ta", "bc","")) ] , testGroup "findShortestPrefix" [ u "t1" (findShortestPrefix ("a" <|> "ab") "abc") (Just ("a","bc")) , u "t2" (findShortestPrefix ("ab" <|> "a") "abc") (Just ("a","bc")) , u "t3" (findShortestPrefix "bc" "abc") Nothing ] , testGroup "findShortestInfix" [ u "t1" (findShortestInfix ("a" <|> "ab") "tabc") (Just ("t", "a","bc")) , u "t2" (findShortestInfix ("ab" <|> "a") "tabc") (Just ("t", "a","bc")) , u "t3" (findShortestInfix "bc" "tabc") (Just ("ta", "bc","")) ] ] , stateQueueTests ] where t name n = localOption (SmallCheckDepth n) . testProperty name u name real ideal = testCase name (assertEqual "" real ideal) main = defaultMain tests