purekell-0.1.0.0: test/Purekell/Arbitrary.hs
module Purekell.Arbitrary
( genIdent
, genUpperIdent
, noRecordAccess
, noTuple
, noTuplePat
, noConsExpr
, noConsPat
, noQualClash
) where
import Data.Text (Text)
import qualified Data.Text as T
import Test.QuickCheck
import Purekell.AST
import Purekell.Instance (MethodEquation (..))
-- | Generate a valid Haskell/PureScript lowercase identifier
genIdent :: Gen Text
genIdent = do
c <- elements ['a'..'z']
cs <- listOf (elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['_'])
let ident = T.pack (c : cs)
if ident `elem` keywords then genIdent else pure ident
where
keywords :: [Text]
keywords = [ "case", "of", "let", "in", "where", "do", "if", "then"
, "else", "data", "type", "class", "instance", "module"
, "import", "forall", "infixl", "infixr", "infix" ]
-- | Generate a valid uppercase identifier (for constructors)
genUpperIdent :: Gen Text
genUpperIdent = do
c <- elements ['A'..'Z']
cs <- listOf (elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['_'])
pure (T.pack (c : cs))
-- | Generate a qualifier (1-2 uppercase components)
genQualifier :: Gen [Name]
genQualifier = do
n <- choose (1, 2)
vectorOf n (Name <$> genUpperIdent)
-- | Generate a valid operator name
genOperator :: Gen Name
genOperator = Name <$> elements
["==", "/=", "<", ">", "<=", ">=", "<>", "&&", "||", "+", "*"
, "div", "mod", "elem", "seq"]
instance Arbitrary Type where
arbitrary = sized go
where
go 0 = oneof
[ TyCon . Name <$> genUpperIdent
, TyVar <$> arbitrary
, TyQCon <$> genQualifier <*> (Name <$> genUpperIdent)
]
go n = oneof
[ go 0
, TyApp <$> go (n `div` 2) <*> go (n `div` 2)
, TyFun <$> go (n `div` 2) <*> go (n `div` 2)
]
shrink (TyApp f x) = [f, x]
shrink (TyFun a b) = [a, b]
shrink (TyQCon [_] n) = [TyCon n]
shrink (TyQCon qs n) = [TyQCon (drop 1 qs) n]
shrink _ = []
instance Arbitrary Name where
arbitrary = Name <$> genIdent
instance Arbitrary Lit where
arbitrary = oneof
[ IntLit . getNonNegative <$> arbitrary
, FloatLit <$> (getNonNegative <$> arbitrary) `suchThat` (\d -> not (isNaN d) && not (isInfinite d))
, StringLit <$> genSafeString
, CharLit <$> elements (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'])
]
where
genSafeString = T.pack <$> listOf (elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ [' '])
instance Arbitrary Guard where
arbitrary = sized $ \n -> Guard <$> resize (n `div` 2) arbitrary
instance Arbitrary CaseAlt where
arbitrary = sized $ \n -> do
pat <- resize (n `div` 3) arbitrary
numGuards <- choose (0, 1)
guards <- vectorOf numGuards (resize (n `div` 3) arbitrary)
body <- resize (n `div` 3) arbitrary
pure (CaseAlt pat guards body)
shrink (CaseAlt p gs e) =
[CaseAlt p [] e | not (null gs)]
++ [CaseAlt p gs e' | e' <- shrink e]
instance Arbitrary Binding where
arbitrary = sized $ \n -> Binding <$> resize (n `div` 2) arbitrary <*> resize (n `div` 2) arbitrary
shrink (Binding p e) = [Binding p e' | e' <- shrink e]
instance Arbitrary Stmt where
arbitrary = sized $ \n -> oneof
[ StmtBind <$> resize (n `div` 2) arbitrary <*> resize (n `div` 2) arbitrary
, StmtExpr <$> resize (n `div` 2) arbitrary
, do numBinds <- choose (1, 2)
StmtLet <$> vectorOf numBinds (resize (n `div` 3) arbitrary)
]
shrink (StmtBind _ e) = [StmtExpr e]
shrink (StmtExpr e) = [StmtExpr e' | e' <- shrink e]
shrink (StmtLet bs) = [StmtLet bs' | bs' <- shrinkList shrink bs, not (null bs')]
instance Arbitrary Expr where
arbitrary = sized go
where
go 0 = oneof
[ Literal <$> arbitrary
, Var <$> arbitrary
, Con . Name <$> genUpperIdent
, QVar <$> genQualifier <*> arbitrary
, QCon <$> genQualifier <*> (Name <$> genUpperIdent)
]
go n = frequency
[ (3, go 0)
, (2, App <$> go half <*> go half)
, (2, InfixApp <$> go half <*> genOperator <*> go half)
, (1, do numPats <- choose (1, 2)
pats <- vectorOf numPats (resize half arbitrary)
body <- go half
pure (Lam pats body))
, (1, If <$> go third <*> go third <*> go third)
, (1, do scrut <- go half
numAlts <- choose (1, 2)
alts <- vectorOf numAlts (resize half arbitrary)
pure (Case scrut alts))
, (1, do numBinds <- choose (1, 2)
bindings <- vectorOf numBinds (resize half arbitrary)
body <- go half
pure (Let bindings body))
, (1, do numStmts <- choose (1, 3)
stmts <- vectorOf numStmts (resize half arbitrary)
pure (Do stmts))
, (1, Neg <$> go half)
, (1, RecordAccess <$> go half <*> (Name <$> genIdent))
, (1, do numElems <- choose (2, 3)
Tuple <$> vectorOf numElems (go (n `div` (numElems + 1))))
, (1, do numElems <- choose (0, 3)
ListLit <$> vectorOf numElems (go (n `div` (numElems + 1))))
, (1, LeftSection <$> go half <*> genOperator)
, (1, RightSection <$> genOperator <*> go half)
, (1, do numBinds <- choose (1, 2)
bindings <- vectorOf numBinds (resize half arbitrary)
body <- go half
pure (Where body bindings))
, (1, Ann <$> go half <*> resize half arbitrary)
, (1, do numFields <- choose (1, 2)
fields <- vectorOf numFields ((,) <$> (Name <$> genIdent) <*> go (n `div` (numFields + 1)))
RecordUpdate <$> go half <*> pure fields)
, (1, QVar <$> genQualifier <*> arbitrary)
, (1, QCon <$> genQualifier <*> (Name <$> genUpperIdent))
]
where
half = n `div` 2
third = n `div` 3
shrink (App f x) = [f, x] ++ [App f' x | f' <- shrink f] ++ [App f x' | x' <- shrink x]
shrink (InfixApp l _ r) = [l, r]
shrink (Lam _ body) = [body]
shrink (If c t e) = [c, t, e]
shrink (Case scrut alts) = scrut : [body | CaseAlt _ _ body <- alts]
shrink (Let _ body) = [body]
shrink (Do stmts) = [e | StmtExpr e <- stmts]
shrink (Neg e) = e : [Neg e' | e' <- shrink e]
shrink (RecordAccess rec _) = [rec]
shrink (Tuple es) = es
shrink (ListLit es) = es ++ [ListLit es' | es' <- shrinkList shrink es]
shrink (LeftSection e _) = [e]
shrink (RightSection _ e) = [e]
shrink (Where e bs) = [e] ++ [Where e bs' | bs' <- shrinkList shrink bs, not (null bs')]
shrink (Ann e _) = [e]
shrink (RecordUpdate e _) = [e]
shrink (QVar [_] n) = [Var n]
shrink (QVar qs n) = [QVar (drop 1 qs) n]
shrink (QCon [_] n) = [Con n]
shrink (QCon qs n) = [QCon (drop 1 qs) n]
shrink _ = []
instance Arbitrary Pat where
arbitrary = sized go
where
go 0 = oneof
[ VarPat <$> arbitrary
, LitPat <$> arbitrary
, pure WildPat
, ConPat . Name <$> genUpperIdent <*> pure []
]
go n = oneof
[ go 0
, do con <- Name <$> genUpperIdent
numArgs <- choose (1, 3)
args <- vectorOf numArgs (go (n `div` (numArgs + 1)))
pure (ConPat con args)
, do numElems <- choose (2, 3)
TuplePat <$> vectorOf numElems (go (n `div` (numElems + 1)))
, do numElems <- choose (0, 3)
ListPat <$> vectorOf numElems (go (n `div` (numElems + 1)))
, ConsPat <$> go (n `div` 2) <*> go (n `div` 2)
, AsPat <$> arbitrary <*> go (n `div` 2)
, NegLitPat <$> oneof
[ IntLit . getNonNegative <$> arbitrary
, FloatLit <$> (getNonNegative <$> arbitrary) `suchThat` (\d -> not (isNaN d) && not (isInfinite d))
]
, do con <- Name <$> genUpperIdent
numFields <- choose (1, 2)
fields <- vectorOf numFields ((,) <$> (Name <$> genIdent) <*> go (n `div` (numFields + 1)))
pure (RecordPat con fields)
]
shrink (ConPat n args) = [ConPat n (take i args) | i <- [0 .. length args - 1]]
++ [ConPat n args' | args' <- shrinkList shrink args]
shrink (TuplePat ps) = ps ++ [TuplePat ps' | ps' <- shrinkList shrink ps, length ps' >= 2]
shrink (ListPat ps) = ps ++ [ListPat ps' | ps' <- shrinkList shrink ps]
shrink (ConsPat l r) = [l, r] ++ [ConsPat l' r | l' <- shrink l] ++ [ConsPat l r' | r' <- shrink r]
shrink (AsPat _ p) = [p]
shrink (NegLitPat l) = [LitPat l]
shrink (RecordPat n [(_, p)]) = [ConPat n [p]]
shrink (RecordPat n fields) = [RecordPat n (take i fields) | i <- [1 .. length fields - 1]]
shrink _ = []
-- | Check recursively that an expression tree contains no RecordAccess.
-- RecordAccess only roundtrips in PureScript (dot syntax), not Haskell
-- (prints as function application) or Instance (no dot access parser).
noRecordAccess :: Expr -> Bool
noRecordAccess (Neg e) = noRecordAccess e
noRecordAccess (RecordAccess _ _) = False
noRecordAccess (App f x) = noRecordAccess f && noRecordAccess x
noRecordAccess (InfixApp l _ r) = noRecordAccess l && noRecordAccess r
noRecordAccess (Lam _ e) = noRecordAccess e
noRecordAccess (If c t e) = noRecordAccess c && noRecordAccess t && noRecordAccess e
noRecordAccess (Case scrut alts) = noRecordAccess scrut && all altOk alts
where altOk (CaseAlt _ gs e) = all guardOk gs && noRecordAccess e
guardOk (Guard e) = noRecordAccess e
noRecordAccess (Let bs e) = all bindOk bs && noRecordAccess e
where bindOk (Binding _ e') = noRecordAccess e'
noRecordAccess (Do stmts) = all stmtOk stmts
where stmtOk (StmtBind _ e) = noRecordAccess e
stmtOk (StmtExpr e) = noRecordAccess e
stmtOk (StmtLet bs) = all (\(Binding _ e) -> noRecordAccess e) bs
noRecordAccess (Tuple es) = all noRecordAccess es
noRecordAccess (ListLit es) = all noRecordAccess es
noRecordAccess (LeftSection e _) = noRecordAccess e
noRecordAccess (RightSection _ e) = noRecordAccess e
noRecordAccess (Where e bs) = noRecordAccess e && all bindOk bs
where bindOk (Binding p e') = noRecordAccessPat p && noRecordAccess e'
noRecordAccess (Ann e _) = noRecordAccess e
noRecordAccess (RecordUpdate e fields) = noRecordAccess e && all (\(_, v) -> noRecordAccess v) fields
noRecordAccess _ = True
noRecordAccessPat :: Pat -> Bool
noRecordAccessPat (ConPat _ args) = all noRecordAccessPat args
noRecordAccessPat (TuplePat ps) = all noRecordAccessPat ps
noRecordAccessPat (ListPat ps) = all noRecordAccessPat ps
noRecordAccessPat (ConsPat l r) = noRecordAccessPat l && noRecordAccessPat r
noRecordAccessPat (AsPat _ p) = noRecordAccessPat p
noRecordAccessPat (RecordPat _ fields) = all (noRecordAccessPat . snd) fields
noRecordAccessPat _ = True
-- | Check recursively that an expression tree contains no Tuple.
-- Tuple doesn't roundtrip in PureScript (prints as App (Con "Tuple") ...).
noTuple :: Expr -> Bool
noTuple (Tuple _) = False
noTuple (Neg e) = noTuple e
noTuple (RecordAccess e _) = noTuple e
noTuple (App f x) = noTuple f && noTuple x
noTuple (InfixApp l _ r) = noTuple l && noTuple r
noTuple (Lam ps e) = all noTuplePat ps && noTuple e
noTuple (If c t e) = noTuple c && noTuple t && noTuple e
noTuple (Case scrut alts) = noTuple scrut && all altOk alts
where altOk (CaseAlt p gs e) = noTuplePat p && all guardOk gs && noTuple e
guardOk (Guard e) = noTuple e
noTuple (Let bs e) = all bindOk bs && noTuple e
where bindOk (Binding p e') = noTuplePat p && noTuple e'
noTuple (Do stmts) = all stmtOk stmts
where stmtOk (StmtBind p e) = noTuplePat p && noTuple e
stmtOk (StmtExpr e) = noTuple e
stmtOk (StmtLet bs) = all (\(Binding p e) -> noTuplePat p && noTuple e) bs
noTuple (ListLit es) = all noTuple es
noTuple (LeftSection e _) = noTuple e
noTuple (RightSection _ e) = noTuple e
noTuple (Where e bs) = noTuple e && all bindOk bs
where bindOk (Binding p e') = noTuplePat p && noTuple e'
noTuple (Ann e _) = noTuple e
noTuple (RecordUpdate e fields) = noTuple e && all (\(_, v) -> noTuple v) fields
noTuple _ = True
-- | Check that a pattern contains no TuplePat (and no Tuple in nested expressions).
noTuplePat :: Pat -> Bool
noTuplePat (TuplePat _) = False
noTuplePat (ConPat _ args) = all noTuplePat args
noTuplePat (ListPat ps) = all noTuplePat ps
noTuplePat (ConsPat l r) = noTuplePat l && noTuplePat r
noTuplePat (AsPat _ p) = noTuplePat p
noTuplePat (RecordPat _ fields) = all (noTuplePat . snd) fields
noTuplePat (NegLitPat _) = True
noTuplePat _ = True
-- | Check recursively that an expression tree contains no ConsPat.
-- ConsPat doesn't roundtrip in PureScript (prints as Cons → parses as ConPat).
noConsExpr :: Expr -> Bool
noConsExpr (Neg e) = noConsExpr e
noConsExpr (RecordAccess e _) = noConsExpr e
noConsExpr (App f x) = noConsExpr f && noConsExpr x
noConsExpr (InfixApp l _ r) = noConsExpr l && noConsExpr r
noConsExpr (Lam ps e) = all noConsPat ps && noConsExpr e
noConsExpr (If c t e) = noConsExpr c && noConsExpr t && noConsExpr e
noConsExpr (Case scrut alts) = noConsExpr scrut && all altOk alts
where altOk (CaseAlt p gs e) = noConsPat p && all guardOk gs && noConsExpr e
guardOk (Guard e) = noConsExpr e
noConsExpr (Let bs e) = all bindOk bs && noConsExpr e
where bindOk (Binding p e') = noConsPat p && noConsExpr e'
noConsExpr (Do stmts) = all stmtOk stmts
where stmtOk (StmtBind p e) = noConsPat p && noConsExpr e
stmtOk (StmtExpr e) = noConsExpr e
stmtOk (StmtLet bs) = all (\(Binding p e) -> noConsPat p && noConsExpr e) bs
noConsExpr (Tuple es) = all noConsExpr es
noConsExpr (ListLit es) = all noConsExpr es
noConsExpr (LeftSection e _) = noConsExpr e
noConsExpr (RightSection _ e) = noConsExpr e
noConsExpr (Where e bs) = noConsExpr e && all bindOk bs
where bindOk (Binding p e') = noConsPat p && noConsExpr e'
noConsExpr (Ann e _) = noConsExpr e
noConsExpr (RecordUpdate e fields) = noConsExpr e && all (\(_, v) -> noConsExpr v) fields
noConsExpr _ = True
-- | Check that a pattern contains no ConsPat.
noConsPat :: Pat -> Bool
noConsPat (ConsPat _ _) = False
noConsPat (ConPat _ args) = all noConsPat args
noConsPat (TuplePat ps) = all noConsPat ps
noConsPat (ListPat ps) = all noConsPat ps
noConsPat (AsPat _ p) = noConsPat p
noConsPat (RecordPat _ fields) = all (noConsPat . snd) fields
noConsPat (NegLitPat _) = True
noConsPat _ = True
-- | Check recursively that no RecordAccess has a Con or QCon as its base.
-- RecordAccess (Con "Foo") "bar" prints as Foo.bar which re-parses as QVar.
noQualClash :: Expr -> Bool
noQualClash (RecordAccess (Con _) _) = False
noQualClash (RecordAccess (QCon _ _) _) = False
noQualClash (Neg e) = noQualClash e
noQualClash (RecordAccess e _) = noQualClash e
noQualClash (App f x) = noQualClash f && noQualClash x
noQualClash (InfixApp l _ r) = noQualClash l && noQualClash r
noQualClash (Lam _ e) = noQualClash e
noQualClash (If c t e) = noQualClash c && noQualClash t && noQualClash e
noQualClash (Case scrut alts) = noQualClash scrut && all altOk alts
where altOk (CaseAlt _ gs e) = all guardOk gs && noQualClash e
guardOk (Guard e) = noQualClash e
noQualClash (Let bs e) = all bindOk bs && noQualClash e
where bindOk (Binding _ e') = noQualClash e'
noQualClash (Do stmts) = all stmtOk stmts
where stmtOk (StmtBind _ e) = noQualClash e
stmtOk (StmtExpr e) = noQualClash e
stmtOk (StmtLet bs) = all (\(Binding _ e) -> noQualClash e) bs
noQualClash (Tuple es) = all noQualClash es
noQualClash (ListLit es) = all noQualClash es
noQualClash (LeftSection e _) = noQualClash e
noQualClash (RightSection _ e) = noQualClash e
noQualClash (Where e bs) = noQualClash e && all bindOk bs
where bindOk (Binding _ e') = noQualClash e'
noQualClash (Ann e _) = noQualClash e
noQualClash (RecordUpdate e fields) = noQualClash e && all (\(_, v) -> noQualClash v) fields
noQualClash _ = True
instance Arbitrary MethodEquation where
arbitrary = sized $ \n -> do
name <- Name <$> genIdent
numPats <- choose (0, 3)
pats <- vectorOf numPats (resize (n `div` 3) arbitrary)
numGuards <- choose (0, 1)
guards <- vectorOf numGuards (resize (n `div` 3) arbitrary)
body <- resize (n `div` 2) arbitrary
pure (MethodEquation name pats guards body)
shrink (MethodEquation name pats guards body) =
[MethodEquation name pats [] body | not (null guards)]
++ [MethodEquation name [] guards body | not (null pats)]
++ [MethodEquation name pats guards body' | body' <- shrink body]