-- -- (c) Susumu Katayama -- {-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances, IncoherentInstances #-} -- Used by the Web version. module MagicHaskeller.IOGenerator(module MagicHaskeller.IOGenerator, NearEq((~=))) where #ifdef TFRANDOM import System.Random.TF.Gen import System.Random.TF.Instances #else import System.Random #endif import MagicHaskeller.MyCheck import Data.List(sort, group, sortBy, groupBy, nubBy, intersperse) import Data.Function(on) import Data.Char(isAlphaNum) -- import Data.Ratio import MagicHaskeller.FastRatio import MagicHaskeller.LibTH(everythingF, pgfull, postprocess) import MagicHaskeller.ExpToHtml(pprnn, annotateString, Language(..)) -- annotateFree is replaced with annotateString import Language.Haskell.TH(pprint, Exp) import Data.Typeable import Text.Html(stringToHtmlString) import MagicHaskeller.NearEq(NearEq((~=))) #ifdef TFRANDOM arbitraries :: Arbitrary a => [a] arbitraries = arbs 4 (seedTFGen (12279932681387497184,218462391894233257,1625759680792809304,12756118543158863164)) arbs :: Arbitrary a => Int -> TFGen -> [a] arbs n stdgen = case map (splitn stdgen 8) [0..255] of g0:gs -> map (f n) gs ++ arbs n g0 -- I think 255 seeds is enough, but just in case. where Gen f = arbitrary #else arbitraries :: Arbitrary a => [a] arbitraries = arbs 4 (mkStdGen 1) arbs :: Arbitrary a => Int -> StdGen -> [a] arbs n stdgen = case split stdgen of (g0,g1) -> f n g0 : arbs n g1 where Gen f = arbitrary #endif --showIOPairs :: IOGenerator a => String -> String -> a -> String --showIOPairs crlf funname fun = concat $ map ((crlf ++) . (funname++)) $ generate fun showIOPairsHTML :: String -> [ShownIOPair] -> String showIOPairsHTML = showIOPairsHTML' (const showIOPairHTML) showIOPairsWithFormsHTML :: String -- ^ CGI.myPath, which is usually "/cgi-bin/MagicHaskeller.cgi" -> String -- ^ predicate before escaping single quotes -> String -> [ShownIOPair] -> String showIOPairsWithFormsHTML mypath predicate = let beginForm = "
ShownIOPair -> String) -> String -> [ShownIOPair] -> String showIOPairsHTML' shower funname iopairs = concat $ map (("&& "++) . (funname++) . shower boxSize) {- (nubSortedOn iopairToInputs -} iopairs -- Instead, nubOn is applied to showArbitraries. where boxSize = maximum $ 20 : map length (snd $ unzip iopairs) nubOn f = map snd . nubBy ((==) `on` fst) . map (\x -> (f x, x)) nubSortedOn f = map snd . nubSortedOn' fst . map (\x -> (f x, x)) nubSortedOn' f = map head . groupBy ((==) `on` f) . sortBy (compare `on` f) type ShownIOPair = ([AnnShowS], String) iopairToInputs :: ShownIOPair -> [String] iopairToInputs (funs,_) = map assToString funs assToString :: AnnShowS -> String assToString fun = fun id "" type AnnShowS = (String->String) -- ^ annotater -> String -> String showIOPairHTML :: ShownIOPair -> String showIOPairHTML (args,ret) = foldr (\arg str -> "  " ++ arg (annotateString LHaskell) str) ("   ~=    "++ret++ "  ") args showIOPairWithFormHTML begin boxSize pair@(args,ret) = showIOPairHTML (args, mkForm begin boxSize pair) mkForm :: String -> Int -> ShownIOPair -> String -- predicateとinputsとoutputがあればよい.CGI側では, '(':predicate++") && f "++inputs++" == "++output をpredicateとして実行 mkForm begin boxSize (args,ret) = begin ++ concatMap escapeQuote (showsInputs args "") ++ "'>
" showsInputs args = \s -> foldr (\arg str -> ' ' : arg id str) s args escapeQuote '\'' = "'" escapeQuote c = [c] class IOGenerator a where -- generate :: a -> [String] generateIOPairs :: a -> [ShownIOPair] -- list of pairs of shown arguments and shown return values instance (IOGenerator r) => IOGenerator (Int->r) where -- generate f = [ ' ' : showParen (a<0) (shows a) s | a <- uniqSort $ take 5 arbitraries, s <- generate (f a) ] generateIOPairs = generateIOPairsLitNum integrals instance (IOGenerator r) => IOGenerator (Integer->r) where -- generate f = [ ' ' : showParen (a<0) (shows a) s | a <- uniqSort $ take 5 arbitraries, s <- generate (f a) ] generateIOPairs = generateIOPairsLitNum integrals instance (IOGenerator r) => IOGenerator (Float->r) where -- generate f = [ ' ' : showParen (a<0) (shows a) s | a <- uniqSort $ take 5 arbitraries, s <- generate (f a) ] generateIOPairs = generateIOPairsLitNum arbitraries instance (IOGenerator r) => IOGenerator (Double->r) where -- generate f = [ ' ' : showParen (a<0) (shows a) s | a <- uniqSort $ take 5 arbitraries, s <- generate (f a) ] generateIOPairs = generateIOPairsLitNum arbitraries generateIOPairsLitNum :: (Num a, Ord a, Show a, IOGenerator b) => [a] -> (a -> b) -> [ShownIOPair] -- Can be used for Integer, Double, and Float, but not for Ratio and Complex. generateIOPairsLitNum rs f = [ (const (showParen (a<0) (shows a)) : args, ret) | a <- uniqSort $ take 4 rs, (args,ret) <- generateIOPairs (f a) ] integrals :: Integral i => [i] integrals = concat $ zipWith (\a b -> [a,b]) [0,-1..] [1..] instance (IOGenerator r) => IOGenerator (()->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun False f instance (IOGenerator r) => IOGenerator (Bool->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun False f instance (IOGenerator r) => IOGenerator (Ordering->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun False f instance (IOGenerator r) => IOGenerator (Char->r) where -- generate f = generateFun False f generateIOPairs f = [ (const (stringToHtmlString (show a) ++) : args, ret) | a <- " \nAb."{- ++ take 0 arbitraries -}, (args,ret) <- generateIOPairs (f a) ] instance (IOGenerator r) => IOGenerator (String->r) where -- generate f = generateFun False f generateIOPairs f = [ (const (shows a) : args, ret) | a <- sortBy (compare `on` length) $ uniqSort $ "" : "12345" : "Abc\nd Ef" : take 2 arbitraries, (args, ret) <- generateIOPairs (f a) ] {----------------------------------------------------------------------- Do not use these in order to deal with types like [Int->Int]->Int. -- ただ、コメントアウトすると、[Int]とかでもeverythingFを使うため簡約されていない形で表示されてしまうので,分かりにくくなってしまう.両方のバージョンを用意してエラーになったらもう一方ってのがよさそう。 instance (Arbitrary a, Show a, Ord a, IOGenerator r) => IOGenerator ([a]->r) where -- generate f = generateFun False f generateIOPairs f = [ (shows a : args, ret) | a <- sortBy (compare `on` length) $ uniqSort $ [] : take 4 arbitraries, (args, ret) <- generateIOPairs (f a) ] instance (Arbitrary a, Show a, Ord a, IOGenerator r) => IOGenerator (Maybe a -> r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun True f instance (Arbitrary a, Show a, Ord a, Integral a, Random a, IOGenerator r) => IOGenerator (Ratio a -> r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun True f instance (Arbitrary a, Show a, Ord a, Arbitrary b, Show b, Ord b, IOGenerator r) => IOGenerator ((a,b)->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun False f instance (Arbitrary a, Show a, Ord a, Arbitrary b, Show b, Ord b, Arbitrary c, Show c, Ord c, IOGenerator r) => IOGenerator ((a,b,c)->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun False f instance (Arbitrary a, Show a, Ord a, Arbitrary b, Show b, Ord b, IOGenerator r) => IOGenerator ((Either a b)->r) where -- generate f = generateFun False f generateIOPairs f = generateIOPairsFun True f ---------------------------------------------------------------------} instance (ShowArbitrary a, IOGenerator r) => IOGenerator (a->r) where -- generate f = generateFun True f generateIOPairs = mhGenerateIOPairs mhGenerateIOPairs :: (ShowArbitrary a, IOGenerator b) => (a -> b) -> [ShownIOPair] mhGenerateIOPairs f = [ (astr : args, ret) | (astr, a) <- take 5 $ nubOn (assToString . fst) showArbitraries, (args, ret) <- generateIOPairs (f a) ] -- | 'ShowArbitrary' is a variant of 'Arbitrary' for presenting I/O example pairs. It uses everythingF for generating printable functions. -- Here `good presentation' is more important than `good randomness'. class ShowArbitrary a where showArbitraries :: [(AnnShowS, a)] sas :: (Show a) => (a->Bool) -> [a] -> [(AnnShowS, a)] sas cond xs = [ (const $ showParen (cond x) (shows x), x) | x <- xs ] sasNum :: (Show a, Arbitrary a, Num a, Ord a) => [(AnnShowS, a)] sasNum = sas (<0) arbitraries sasFalse :: (Show a) => [a] -> [(AnnShowS, a)] sasFalse = sas (const False) sasIntegral :: (Show a, Arbitrary a, Integral a, Ord a) => [(AnnShowS, a)] sasIntegral = sas (<0) [0,1] ++ -- interleave [2..] [-1,-2..] drop 2 sasNum -- interleave xs ys = concat $ transpose [xs, ys] instance ShowArbitrary () where showArbitraries = repeat (const ("()"++),()) instance ShowArbitrary Bool where showArbitraries = sasFalse $ [False, True] ++ arbitraries instance ShowArbitrary Int where showArbitraries = sasIntegral instance ShowArbitrary Integer where showArbitraries = sasIntegral instance ShowArbitrary Float where showArbitraries = sasNum instance ShowArbitrary Double where showArbitraries = sasNum instance ShowArbitrary Char where showArbitraries = sasFalse $ " \nAb."++ drop 5 arbitraries instance ShowArbitrary Ordering where showArbitraries = sasFalse $ [LT,EQ,GT] ++ arbitraries instance (Integral i, Random i, Show i) => ShowArbitrary (Ratio i) where showArbitraries = sas (const True) arbitraries instance ShowArbitrary a => ShowArbitrary (Maybe a) where showArbitraries = (const ("Nothing"++), Nothing) : map (mapSA "Just " Just) showArbitraries instance (ShowArbitrary a, ShowArbitrary b) => ShowArbitrary (Either a b) where showArbitraries = zipWith3 (\b l r -> if b then mapSA "Left " Left l else mapSA "Right " Right r) arbitraries showArbitraries showArbitraries -- ほんとはもっとランダムにすべきではある.2本Eitherがある場合,同じarbitraries::[Bool]を共有するので,同じ箇所でLeftやRightになる. mapSA str fun (f,x) = (\annotater -> showParen True ((str++) . f annotater), fun x) instance (ShowArbitrary a, ShowArbitrary b) => ShowArbitrary (a, b) where showArbitraries = zipWith (\(f1,x1) (f2,x2) -> (\annotater -> ('(':) . f1 annotater . (',':) . f2 annotater . (')':), (x1,x2))) (skip 1 showArbitraries) (skip 1 $ drop 1 showArbitraries) instance (ShowArbitrary a, ShowArbitrary b, ShowArbitrary c) => ShowArbitrary (a, b, c) where showArbitraries = zipWith3 (\(f1,x1) (f2,x2) (f3,x3) -> (\annotater -> ('(':) . f1 annotater . (',':) . f2 annotater . (',':) . f3 annotater . (')':), (x1,x2,x3))) (skip 2 showArbitraries) (skip 2 $ drop 1 showArbitraries) (skip 2 $ drop 2 showArbitraries) -- leap frog skip n (x:xs) = x : skip n (drop n xs) instance ShowArbitrary a => ShowArbitrary [a] where showArbitraries = map cvt $ chopBy arbitraries showArbitraries -- ほんとはもっとランダムにすべきではある.2本[a]がある場合,同じarbitraries::[Int]を共有するので,同じ箇所で同じ長さになる. chopBy :: [Int] -> [a] -> [[a]] chopBy _ [] = [] -- everythingFを使ってある点で切る限り,有限の可能性も必ず残る.空リストであることもありえるので,cycleしてもダメ. chopBy is xs = cb is $ cycle xs where cb (i:is) xs | i < 0 = cb is xs | otherwise = case splitAt i xs of (tk,dr) -> tk : cb is dr cvt :: [(AnnShowS,a)] -> (AnnShowS, [a]) cvt ts = case unzip ts of (fs, xs) -> (showsList fs, xs) showsList fs@(f:_) | head (f id "") == '\'' -- The String case is dealt with here. I use overlapping instances only conservatively. The drawback is that "" is still printed as []. = const $ shows (map (\fun -> read $ fun id "") fs :: String) showsList fs = \annotater -> ('[':) . foldr (.) (']':) (intersperse (',':) $ map ($ annotater) fs) instance (Typeable a, Typeable b) => ShowArbitrary (a->b) where showArbitraries = map (\(e,a) -> (\annotater -> (annotater (pprnn (postprocess e)) ++) , a)) $ concat $ take 5 $ everythingF pgfull True generateIOPairsFun :: (Ord a, Show a, Arbitrary a, IOGenerator b) => Bool -> (a->b) -> [ShownIOPair] -- generateFun b f = [ ' ' : showParen b (shows a) s | a <- uniqSort $ take 5 arbitraries, s <- generate (f a) ] generateIOPairsFun b f = [ (const (showParen b (shows a)) : args, ret) | a <- uniqSort $ take 5 arbitraries , (args, ret) <- generateIOPairs (f a) ] instance Show a => IOGenerator a where -- generate x = [" = "++show x] generateIOPairs x = [([], stringToHtmlString $ show x)] uniqSort :: Ord a => [a] -> [a] uniqSort = map head . group . sort