-- | -- Module : Test.LeanCheck.Function.ShowFunction -- Copyright : (c) 2015-2018 Rudy Matela -- License : 3-Clause BSD (see the file LICENSE) -- Maintainer : Rudy Matela -- -- This module is part of LeanCheck, -- a simple enumerative property-based testing library. -- -- This module exports the 'ShowFunction' typeclass, -- its instances and related functions. -- -- Using this module, it is possible to implement -- a Show instance for functions: -- -- > import Test.LeanCheck.ShowFunction -- > instance (Show a, Listable a, ShowFunction b) => Show (a->b) where -- > show = showFunction 8 -- -- This shows functions as a case pattern with up to 8 cases. -- -- The module -- @Test.LeanCheck.Function.Show@ ('Test.LeanCheck.Function.Show') -- exports an instance like the one above. module Test.LeanCheck.Function.ShowFunction ( showFunction , showFunctionLine , Binding , bindings , ShowFunction (..) , tBindingsShow -- * Re-exports , Listable ) where import Test.LeanCheck.Core import Test.LeanCheck.Error (errorToNothing) import Test.LeanCheck.Utils.Types import Data.List import Data.Maybe -- | A functional binding in a showable format. type Binding = ([String], Maybe String) -- | 'ShowFunction' values are those for which -- we can return a list of functional bindings. -- -- As a user, you probably want 'showFunction' and 'showFunctionLine'. -- -- Non functional instances should be defined by: -- -- > instance ShowFunction Ty where tBindings = tBindingsShow class ShowFunction a where tBindings :: a -> [[Binding]] -- | Given a 'ShowFunction' value, return a list of bindings -- for printing. Examples: -- -- > bindings True == [([],True)] -- > bindings (id::Int) == [(["0"],"0"), (["1"],"1"), (["-1"],"-1"), ... -- > bindings (&&) == [ (["False","False"], "False") -- > , (["False","True"], "False") -- > , (["True","False"], "False") -- > , (["True","True"], "True") -- > ] bindings :: ShowFunction a => a -> [Binding] bindings = concat . tBindings -- instances for (algebraic/numeric) data types -- -- | A default implementation of tBindings for already 'Show'-able types. tBindingsShow :: Show a => a -> [[Binding]] tBindingsShow x = [[([],errorToNothing $ show x)]] instance ShowFunction () where tBindings = tBindingsShow instance ShowFunction Bool where tBindings = tBindingsShow instance ShowFunction Int where tBindings = tBindingsShow instance ShowFunction Integer where tBindings = tBindingsShow instance ShowFunction Char where tBindings = tBindingsShow instance ShowFunction Float where tBindings = tBindingsShow instance ShowFunction Double where tBindings = tBindingsShow instance ShowFunction Ordering where tBindings = tBindingsShow instance Show a => ShowFunction [a] where tBindings = tBindingsShow instance Show a => ShowFunction (Maybe a) where tBindings = tBindingsShow instance (Show a, Show b) => ShowFunction (Either a b) where tBindings = tBindingsShow instance (Show a, Show b) => ShowFunction (a,b) where tBindings = tBindingsShow -- instance for functional value type -- instance (Show a, Listable a, ShowFunction b) => ShowFunction (a->b) where tBindings f = concatMapT tBindingsFor tiers where tBindingsFor x = mapFst (show x:) `mapT` tBindings (f x) mapFst f (x,y) = (f x, y) paren :: String -> String paren s = "(" ++ s ++ ")" varnamesFor :: ShowFunction a => a -> [String] varnamesFor = zipWith const varnames . fst . head . bindings where varnames = ["x","y","z","w"] ++ map (++"'") varnames showTuple :: [String] -> String showTuple [x] = x showTuple xs = paren $ intercalate "," xs showNBindingsOf :: ShowFunction a => Int -> Int -> a -> [String] showNBindingsOf m n f = take n bs ++ ["..." | length bs' >= m || length bs > n] where bs' = take m $ bindings f bs = [ showTuple as ++ " -> " ++ r | (as, Just r) <- bs' ] isValue :: ShowFunction a => a -> Bool isValue f = case bindings f of [([],_)] -> True _ -> False showValueOf :: ShowFunction a => a -> String showValueOf x = case snd . head . bindings $ x of Nothing -> "undefined" Just x' -> x' -- | Given a number of patterns to show, shows a 'ShowFunction' value. -- -- > showFunction undefined True == "True" -- > showFunction 3 (id::Int) == "\\x -> case x of\n\ -- > \ 0 -> 0\n\ -- > \ 1 -> 1\n\ -- > \ -1 -> -1\n\ -- > \ ...\n" -- > showFunction 4 (&&) == "\\x y -> case (x,y) of\n\ -- > \ (False,False) -> False\n\ -- > \ (False,True) -> False\n\ -- > \ (True,False) -> False\n\ -- > \ (True,True) -> True\n" -- -- This can be used as an implementation of show for functions: -- -- > instance (Show a, Listable a, ShowFunction b) => Show (a->b) where -- > show = showFunction 8 showFunction :: ShowFunction a => Int -> a -> String showFunction n = showFunctionL False (n*n+1) n -- | Same as showFunction, but has no line breaks. -- -- > showFunction 2 (id::Int) == "\\x -> case x of 0 -> 0; 1 -> 1; ..." showFunctionLine :: ShowFunction a => Int -> a -> String showFunctionLine n = showFunctionL True (n*n+1) n -- | isUndefined checks if a function is totally undefined. -- When it is not possible to check all values, it returns false isUndefined :: ShowFunction a => Int -> a -> Bool isUndefined m f = length bs < m && all (isNothing . snd) bs where bs = take m $ bindings f -- The first boolean parameter tells if we are showing -- the function on a single line showFunctionL :: ShowFunction a => Bool -> Int -> Int -> a -> String showFunctionL singleLine m n f | isValue f = showValueOf f showFunctionL singleLine m n f | otherwise = lambdaPat ++ caseExp where vs = varnamesFor f lambdaPat = "\\" ++ unwords vs ++ " -> " casePat = "case " ++ showTuple vs ++ " of" bs = showNBindingsOf m n f sep | singleLine = " " | otherwise = "\n" cases | singleLine = intercalate "; " bs | otherwise = unlines $ (replicate (length lambdaPat + 2) ' ' ++) `map` bs caseExp = if isUndefined m f then "undefined" else casePat ++ sep ++ cases -- instances for further tuple arities -- instance (Show a, Show b, Show c) => ShowFunction (a,b,c) where tBindings = tBindingsShow instance (Show a, Show b, Show c, Show d) => ShowFunction (a,b,c,d) where tBindings = tBindingsShow instance (Show a, Show b, Show c, Show d, Show e) => ShowFunction (a,b,c,d,e) where tBindings = tBindingsShow instance (Show a, Show b, Show c, Show d, Show e, Show f) => ShowFunction (a,b,c,d,e,f) where tBindings = tBindingsShow instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g) => ShowFunction (a,b,c,d,e,f,g) where tBindings = tBindingsShow instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h) => ShowFunction (a,b,c,d,e,f,g,h) where tBindings = tBindingsShow -- instance for types from Test.LeanCheck.Utils.Types instance ShowFunction Nat where tBindings = tBindingsShow instance ShowFunction Nat1 where tBindings = tBindingsShow instance ShowFunction Nat2 where tBindings = tBindingsShow instance ShowFunction Nat3 where tBindings = tBindingsShow instance ShowFunction Nat4 where tBindings = tBindingsShow instance ShowFunction Nat5 where tBindings = tBindingsShow instance ShowFunction Nat6 where tBindings = tBindingsShow instance ShowFunction Nat7 where tBindings = tBindingsShow instance ShowFunction Int1 where tBindings = tBindingsShow instance ShowFunction Int2 where tBindings = tBindingsShow instance ShowFunction Int3 where tBindings = tBindingsShow instance ShowFunction Int4 where tBindings = tBindingsShow instance ShowFunction Word1 where tBindings = tBindingsShow instance ShowFunction Word2 where tBindings = tBindingsShow instance ShowFunction Word3 where tBindings = tBindingsShow instance ShowFunction Word4 where tBindings = tBindingsShow