packages feed

elm-compiler-0.15: tests/Test/Property/Arbitrary.hs

{-# OPTIONS_GHC -W -fno-warn-orphans #-}
module Test.Property.Arbitrary where

import Control.Applicative       ((<$>), (<*>), pure)
import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Gen

import qualified Data.Set as Set
import qualified Parse.Helpers (reserveds)

import qualified AST.Literal as L
import qualified AST.Pattern as P
import qualified AST.Type as T
import qualified AST.Variable as V


instance Arbitrary V.Raw where
  arbitrary =
      V.Raw <$> capVar

  shrink (V.Raw v) =
      V.Raw <$> shrink v


instance Arbitrary L.Literal where
  arbitrary =
      oneof
        [ L.IntNum <$> arbitrary
        , L.FloatNum <$> (arbitrary `suchThat` noE)
        , L.Chr <$> arbitrary
        -- This is too permissive
        , L.Str <$> arbitrary
        -- Booleans aren't actually source syntax
        -- , Boolean  <$> arbitrary
        ]

  shrink literal =
      case literal of
        L.IntNum int ->
            L.IntNum <$> shrink int

        L.FloatNum float ->
            L.FloatNum <$> filter noE (shrink float)

        L.Chr char ->
            L.Chr <$> shrink char

        L.Str string ->
            L.Str <$> shrink string

        L.Boolean bool ->
            L.Boolean <$> shrink bool


noE :: Double -> Bool
noE =
  notElem 'e' . show


genVector :: Int -> (Int -> Gen a) -> Gen [a]
genVector n generator =
  do  len <- choose (0,n)
      let m = n `div` (len + 1)
      vectorOf len $ generator m

instance Arbitrary v => Arbitrary (P.Pattern v) where
  arbitrary =
      sized pattern
    where
      pattern :: (Arbitrary v) => Int -> Gen (P.Pattern v)
      pattern n =
          oneof
          [ pure P.Anything
          , P.Var     <$> lowVar
          , P.Record  <$> (listOf1 lowVar)
          , P.Literal <$> arbitrary
          , P.Alias   <$> lowVar <*> pattern (n-1)
          , P.Data    <$> arbitrary <*> genVector n pattern
          ]

  shrink pattern =
    case pattern of
      P.Anything ->
          []

      P.Var var ->
          P.Var <$> consAndShrink var

      P.Literal literal ->
          P.Literal <$> shrink literal

      P.Alias alias pattern ->
          pattern : (P.Alias <$> consAndShrink alias <*> shrink pattern)

      P.Data name patterns ->
          patterns ++ (P.Data <$> shrink name <*> shrink patterns)

      P.Record fields ->
          P.Record <$> filter (all notNull) (filter notNull (shrink fields))
        where
          notNull = not . null


consAndShrink :: Arbitrary a => [a] -> [[a]]
consAndShrink values =
    case values of
      [] -> error "Should be nonempty"
      x:xs -> (x:) <$> shrink xs


instance Arbitrary v => Arbitrary (T.Type v) where
  arbitrary =
      sized tipe
    where
      tipe :: Arbitrary v => Int -> Gen (T.Type v)
      tipe n =
          let depthTipe = tipe =<< choose (0,n)
              field = (,) <$> lowVar <*> depthTipe
              fields = genVector n (\m -> (,) <$> lowVar <*> tipe m)
              fields1 = (:) <$> field <*> fields
              depthTipes = (:) <$> depthTipe <*> genVector n tipe
          in
              oneof
                [ T.Lambda <$> depthTipe <*> depthTipe
                , T.Var <$> lowVar
                , T.Type <$> arbitrary
                , T.App <$> (T.Type <$> arbitrary) <*> depthTipes
                , T.Record <$> fields <*> pure Nothing
                , T.Record <$> fields1 <*> (Just . T.Var <$> lowVar)
                ]

  shrink tipe =
    case tipe of
      T.Lambda arg result ->
          arg : result : (T.Lambda <$> shrink arg <*> shrink result)

      T.Var _ ->
          []

      T.Aliased name tvars rootType ->
          rootType : (T.Aliased name <$> shrinkList shrinkPair tvars <*> shrink rootType)

      T.Type name ->
          T.Type <$> shrink name

      T.App func args ->
          func : args ++ (T.App <$> shrink func <*> shrink args)

      T.Record fields extension ->
          map snd fields ++ record
        where
          record =
              case extension of
                Nothing ->
                    T.Record <$> shrinkList shrinkPair fields <*> pure Nothing

                Just _ ->
                    do fields' <- filter (not . null) $ shrinkList shrinkPair fields
                       return $ T.Record fields' extension
    where
      shrinkPair (name, tipe) =
          (,) <$> consAndShrink name <*> shrink tipe


lowVar :: Gen String
lowVar =
    notReserved $
        (:) <$> elements ['a'..'z'] <*> listOf varLetter


capVar :: Gen String
capVar =
    notReserved $
        (:) <$> elements ['A'..'Z'] <*> listOf varLetter


varLetter :: Gen Char
varLetter =
    elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['\'', '_']


notReserved :: Gen String -> Gen String
notReserved string =
    string `exceptFor` Parse.Helpers.reserveds


exceptFor :: (Ord a) => Gen a -> [a] -> Gen a
exceptFor generator list =
    generator `suchThat` notInList
  where
    set = Set.fromList list
    notInList x = Set.notMember x set