packages feed

stgi-1: test/Testsuite/Test/Orphans/Language.hs

{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE QuasiQuotes           #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Test.Orphans.Language () where



import           Control.Applicative
import qualified Data.List.NonEmpty  as NonEmpty
import qualified Data.Map            as M
import           Data.Monoid         hiding (Alt)
import           Data.Ratio
import           Data.Text           (Text)
import qualified Data.Text           as T

import Stg.Language
import Stg.Parser.QuasiQuoter

import Test.SmallCheck.Series
import Test.Tasty.QuickCheck
import Test.Util



--------------------------------------------------------------------------------
-- Helpers

shrinkBut1stLetter :: Text -> [Text]
shrinkBut1stLetter text = case T.uncons text of
    Nothing -> []
    Just (x, xs) ->
        let shrunkenRest = map T.pack . shrink . T.unpack
        in map (T.singleton x <>) (shrunkenRest xs)

reservedKeywords :: [Var]
reservedKeywords = ["let", "in", "case", "of", "default"]


--------------------------------------------------------------------------------
-- QuickCheck

instance Arbitrary Program where
    arbitrary = arbitrary1 Program
    shrink = genericShrink

instance Arbitrary Binds where
    arbitrary = do
        xs <- listOf1 (scaled (2%3) arbitrary)
        pure (Binds (M.fromList xs))
    shrink (Binds b) =
          [binds| bind = \ -> Unit |]
        : [binds| bind1 = \ -> Unit1; bind2 = \ -> Unit2 |]
        : (map (Binds . M.fromList) . shrinkBut1st . M.toList) b
      where
        -- Bindings have to be non-empty, we ensure at least one element is in
        -- the shrunken result.
        shrinkBut1st [] = []
        shrinkBut1st (x:xs) = liftA2 (:) (shrink x) (shrink xs)

instance Arbitrary LambdaForm where
    arbitrary = do
        free <- arbitrary
        updateFlag <- arbitrary
        bound <- case updateFlag of
            Update -> pure []
            NoUpdate -> arbitrary
        body <- oneof
            ([ arbitrary3 Let
            , arbitrary2 Case
            -- Lambdas cannot have primitive type, so we exclude AppP and Lit
            , arbitrary2 AppF ]
            <>
            -- Standard constructors are never updatable, so we exclude those
            [arbitrary2 AppC | updateFlag == NoUpdate] )
        pure (LambdaForm free updateFlag bound body)
    shrink lf =
          [lambdaForm| \ -> x |]
        : [lambdaForm| \ => x |]
        : [lambdaForm| \x -> x |]
        : [lambdaForm| \(y) x -> x |]
        : [lambdaForm| \(y z) x w -> Con x y z w |]
        : filter isValid (genericShrink lf)
      where
        isValid (LambdaForm _ Update (_:_) AppF{}) = False
        isValid (LambdaForm _ Update (_:_) AppC{}) = False
        isValid (LambdaForm _ _      _     AppP{}) = False
        isValid (LambdaForm _ _      _     Lit{} ) = False
        isValid _ = True

instance Arbitrary UpdateFlag where
    arbitrary = allEnums
    shrink = genericShrink

instance Arbitrary Rec where
    arbitrary = allEnums
    shrink = genericShrink

instance Arbitrary Expr where
    arbitrary = oneof
        [ arbitrary3 Let
        , arbitrary2 Case
        , arbitrary2 AppF
        , arbitrary2 AppC
        , arbitrary3 AppP
        , arbitrary1 Lit ]
    shrink = genericShrink

instance Arbitrary Alts where
    arbitrary = arbitrary2 Alts

instance Arbitrary NonDefaultAlts where
    arbitrary = oneof
        [ pure NoNonDefaultAlts
        , fmap (AlgebraicAlts . NonEmpty.fromList)
            (listOf1 (scaled (2%3) (arbitrary3 AlgebraicAlt)))
        , fmap (PrimitiveAlts . NonEmpty.fromList)
            (listOf1 (scaled (2%3) (arbitrary2 PrimitiveAlt))) ]

instance Arbitrary DefaultAlt where
    arbitrary = oneof [arbitrary1 DefaultNotBound, arbitrary2 DefaultBound]
    shrink = genericShrink

instance Arbitrary Literal where
    arbitrary = resize 128 (arbitrary1 Literal)
    shrink = genericShrink

instance Arbitrary PrimOp where
    arbitrary = allEnums
    shrink = genericShrink

word, lower, upper :: Gen Text
word = T.pack <$> listOf (elements (['a'..'z'] <> ['A'..'Z'] <> ['0'..'9'] <> "\'_"))
lower = T.singleton <$> elements ['a'..'z']
upper = T.singleton <$> elements ['A'..'Z']

instance Arbitrary Var where
    arbitrary = do
        var <- (\head' tail' -> Var (head' <> tail')) <$> lower <*> word
        if var `elem` reservedKeywords
            then arbitrary
            else pure var
    shrink (Var var) = map Var (shrinkBut1stLetter var)

instance Arbitrary Atom where
    arbitrary = oneof [arbitrary1 AtomVar, arbitrary1 AtomLit]
    shrink = genericShrink

instance Arbitrary Constr where
    arbitrary = (\head' tail' hash -> Constr (head' <> tail' <> hash))
                <$> upper
                <*> word
                <*> frequency [(3, pure ""), (1, pure "#")]
    shrink (Constr constr) = map Constr (shrinkBut1stLetter constr)


--------------------------------------------------------------------------------
-- SmallCheck

instance Monad m => Serial m Literal
instance Monad m => Serial m PrimOp
instance Monad m => Serial m Rec
instance Monad m => Serial m UpdateFlag