packages feed

fst-0.10.0.0: tests/properties.hs

{-# LANGUAGE ViewPatterns #-}

import Data.List
import Control.Applicative
import Control.Monad
import Data.Maybe

import Test.QuickCheck

import FST.LBFA
import FST.Automaton

import FST.RegTypes
import FST.RRegTypes (idR)
import FST.TransducerTypes
import FST.TransducerInterface (compile)
import FST.RunTransducer

instance Arbitrary a => Arbitrary (Reg a) where
  arbitrary = oneof [return Empty,
                     return Epsilon,
                     return All,
                     liftM2 (:|:) arbitrary arbitrary,
                     liftM2 (:.:) arbitrary arbitrary,
                     liftM2 (:&:) arbitrary arbitrary,
                     liftM Symbol arbitrary,
                     liftM Complement arbitrary,
                     liftM Star arbitrary]

-- | A datatype that only accepts the empty string.
newtype EmptyString a = EmptyString { unEmpty :: Reg a } deriving Show

instance Arbitrary a => Arbitrary (EmptyString a) where
  arbitrary =
    fmap EmptyString $
    oneof [return Epsilon,
           liftM  Star arbitrary,
           liftM2 (:|:) arbitrary (fmap unEmpty arbitrary),
           liftM2 (:|:) (fmap unEmpty arbitrary) arbitrary,
           liftM2 (:&:) (fmap unEmpty arbitrary) (fmap unEmpty arbitrary),
           liftM2 (:.:) (fmap unEmpty arbitrary) (fmap unEmpty arbitrary),
           fmap (Complement . Complement) (fmap unEmpty arbitrary)] 
                     
prop_empty :: Eq a => EmptyString a -> Bool
prop_empty (EmptyString xs) = acceptEps xs

instance Arbitrary a => Arbitrary (Symbol a) where
  arbitrary = frequency [(4, S `fmap` arbitrary)
                        ,(1, return Eps        )] 


newtype Language = Language { unLanguage :: (Reg Char, [String]) } deriving Show

instance Arbitrary Language where
  arbitrary = sized gLanguage 
    
gLanguage :: Int -> Gen Language
gLanguage 0 = Language `fmap` oneof
              -- Return empty language ε
              [return (Epsilon, [""]),
               
              -- Return one of two symbols: a or b
               elements "ab" >>= \ch -> return (Symbol ch, [[ch]])]
gLanguage n = Language `fmap` oneof
               -- Generate A* ≅ {ε, A, A} out of arbitrary A
              [do (reg, lang) <- fmap unLanguage subGen
                  let matches = concat [[""], lang, (++) <$> lang <*> lang]
                  return (Star reg, nub matches),
               
               -- Sequence A B out of arbitrary A and B
               do (reg1, lang1) <- fmap unLanguage subGen
                  (reg2, lang2) <- fmap unLanguage subGen
                  let matches = (++) <$> lang1 <*> lang2
                  return (reg1 :.: reg2, nub matches)]
  where subGen :: Gen Language
        subGen = gLanguage (n `div` 4)

-- If a regular expression generates a (sub)language, then compiling it and 
-- running it on all strings of that languages should always match.
prop_language (Language (reg, inputs)) =
  and [ maybe False (elem input) appliedUp && maybe False (elem input) appliedDown
      | input <- inputs
      , let appliedUp   = applyUp   (compile (idR reg) "ab") input
      , let appliedDown = applyDown (compile (idR reg) "ab") input ] 

-- | Run the test suite
main :: IO ()
main = do
  putStrLn "Running tests"
  quickCheck (prop_empty :: EmptyString String -> Bool)
  quickCheck prop_language