CurryCheck.curry 53.5 KB
Newer Older
Michael Hanus 's avatar
Michael Hanus committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
-------------------------------------------------------------------------
--- This is the implementation of the currycheck tool.
--- It performs various checks on Curry programs:
---
--- * Correct usage of set functions, non-strict unification,
---   default rules, DET annotations, contracts
--- * All EasyCheck tests are extracted and checked
--- * For all functions declared as deterministic,
---   determinism properties are generated and checked.
--- * For functions with postconditions (f'post), checks for postconditions
---   are generated (together with possible preconditions)
--- * For functions with specification (f'spec), checks for satisfaction
---   of these specifications are generated
---   (together with possible preconditions).
---
--- @author Michael Hanus, Jan-Patrick Baye
Michael Hanus 's avatar
Michael Hanus committed
17
--- @version October 2018
Michael Hanus 's avatar
Michael Hanus committed
18 19
-------------------------------------------------------------------------

20 21 22 23 24 25 26 27 28
import AnsiCodes
import Char                    ( toUpper )
import Distribution
import FilePath                ( (</>), pathSeparator, takeDirectory )
import GetOpt
import List
import Maybe                   ( fromJust, isJust )
import System                  ( system, exitWith, getArgs, getPID, getEnviron )

Michael Hanus 's avatar
Michael Hanus committed
29 30 31 32
import AbstractCurry.Types
import AbstractCurry.Files
import AbstractCurry.Select
import AbstractCurry.Build
33 34 35
import AbstractCurry.Pretty    ( showCProg )
import AbstractCurry.Transform ( renameCurryModule, updCProg
                               , updQNamesInCProg, updQNamesInCFuncDecl )
Michael Hanus 's avatar
Michael Hanus committed
36 37 38 39
import qualified FlatCurry.Types as FC
import FlatCurry.Files
import qualified FlatCurry.Goodies as FCG

40 41 42
import CC.Config               ( packagePath, packageVersion )
import CC.Options
import CheckDetUsage           ( checkDetUse, containsDetOperations)
Michael Hanus 's avatar
Michael Hanus committed
43
import ContractUsage
44
import DefaultRuleUsage        ( checkDefaultRules, containsDefaultRules )
Michael Hanus 's avatar
Michael Hanus committed
45
import PropertyUsage
46
import SimplifyPostConds       ( simplifyPostConditionsWithTheorems )
Michael Hanus 's avatar
Michael Hanus committed
47
import TheoremUsage
48
import UsageCheck              ( checkBlacklistUse, checkSetUse )
Michael Hanus 's avatar
Michael Hanus committed
49 50 51 52 53 54

-- Banner of this tool:
ccBanner :: String
ccBanner = unlines [bannerLine,bannerText,bannerLine]
 where
   bannerText = "CurryCheck: a tool for testing Curry programs (Version " ++
55
                packageVersion ++ " of 30/10/2018)"
Michael Hanus 's avatar
Michael Hanus committed
56 57 58 59
   bannerLine = take (length bannerText) (repeat '-')

-- Help text
usageText :: String
60
usageText = usageInfo ("Usage: curry-check [options] <module names>\n") options
Michael Hanus 's avatar
Michael Hanus committed
61

62 63 64
--- Maximal arity of check functions and tuples currently supported:
maxArity :: Int
maxArity = 5
Michael Hanus 's avatar
Michael Hanus committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

-------------------------------------------------------------------------
-- The names of suffixes added to specific tests.

defTypeSuffix :: String
defTypeSuffix = "_ON_BASETYPE"

postCondSuffix :: String
postCondSuffix = "SatisfiesPostCondition"

satSpecSuffix :: String
satSpecSuffix = "SatisfiesSpecification"

isDetSuffix :: String
isDetSuffix = "IsDeterministic"

-------------------------------------------------------------------------
-- Internal representation of tests extracted from a Curry module.
83 84 85 86 87 88 89 90
-- A test is
-- * a property test (with a name, type, source line number),
-- * an IO test (with a name and source line number), or
-- * an operation equivalence test (with a name, the names of both operations,
--   and their type, and the source line number).
data Test = PropTest  QName CTypeExpr Int
          | IOTest    QName Int
          | EquivTest QName QName QName CTypeExpr Int
Michael Hanus 's avatar
Michael Hanus committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

-- Is the test an IO test?
isIOTest :: Test -> Bool
isIOTest t = case t of IOTest _ _ -> True
                       _          -> False
-- Is the test a unit test?
isUnitTest :: Test -> Bool
isUnitTest t = case t of PropTest _ texp _ -> null (argTypes texp)
                         _                 -> False

-- Is the test a property test?
isPropTest :: Test -> Bool
isPropTest t = case t of PropTest _ texp _ -> not (null (argTypes texp))
                         _                 -> False

106 107 108 109 110
-- Is the test an equivalence test?
isEquivTest :: Test -> Bool
isEquivTest t = case t of EquivTest _ _ _ _ _ -> True
                          _                   -> False

111 112 113 114 115
-- Returns the names of the operations of an equivalence test.
equivTestOps :: Test -> [QName]
equivTestOps t = case t of EquivTest _ f1 f2 _ _ -> [f1,f2]
                           _                     -> []

Michael Hanus 's avatar
Michael Hanus committed
116
-- The name of a test:
117 118 119 120
testName :: Test -> QName
testName (PropTest  n _     _) = n
testName (IOTest    n       _) = n
testName (EquivTest n _ _ _ _) = n
Michael Hanus 's avatar
Michael Hanus committed
121 122

-- The line number of a test:
123 124 125 126
testLine :: Test -> Int
testLine (PropTest  _ _     n) = n
testLine (IOTest    _       n) = n
testLine (EquivTest _ _ _ _ n) = n
Michael Hanus 's avatar
Michael Hanus committed
127 128 129 130

-- Generates a useful error message for tests (with module and line number)
genTestMsg :: String -> Test -> String
genTestMsg file test =
131 132 133 134 135 136 137 138
  snd (testName test) ++
  " (module " ++ file ++ ", line " ++ show (testLine test) ++ ")"

-- Generates the name of a test in the main test module from the test name.
genTestName :: Test -> String
genTestName test =
  let (modName, fName) = testName test
  in fName ++ "_" ++ modNameToId modName
Michael Hanus 's avatar
Michael Hanus committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

-------------------------------------------------------------------------
-- Representation of the information about a module to be tested:
-- * the original name of the module to be tested
-- * the name of the transformed (public) test module
-- * static errors (e.g., illegal uses of set functions)
-- * test operations
-- * name of generators defined in this module (i.e., starting with "gen"
--   and of appropriate result type)
data TestModule = TestModule
  { orgModuleName  :: String
  , testModuleName :: String
  , staticErrors   :: [String]
  , propTests      :: [Test]
  , generators     :: [QName]
  }

-- A test module with only static errors.
staticErrorTestMod :: String -> [String] -> TestModule
staticErrorTestMod modname staterrs =
 TestModule modname modname staterrs [] []

-- Is this a test module that should be tested?
testThisModule :: TestModule -> Bool
testThisModule tm = null (staticErrors tm) && not (null (propTests tm))

-- Extracts all user data types used as test data generators.
166 167 168
-- Each type has a flag which is `True` if the test data should contain
-- partial values (for checking equivalence of operations).
userTestDataOfModule :: TestModule -> [(QName,Bool)]
Michael Hanus 's avatar
Michael Hanus committed
169 170 171
userTestDataOfModule testmod = concatMap testDataOf (propTests testmod)
 where
  testDataOf (IOTest _ _) = []
172 173 174 175 176 177 178 179 180 181 182 183 184
  testDataOf (PropTest _ texp _) =
    map (\t -> (t,False)) (filter (\ (mn,_) -> mn /= preludeName)
                                  (unionOn tconsOf (argTypes texp)))
  testDataOf (EquivTest _ _ _ texp _) =
    map (\t -> (t,True)) (unionOn tconsOf (argTypes texp))

-- Extracts all result data types used in equivalence properties.
equivPropTypes :: TestModule -> [QName]
equivPropTypes testmod = concatMap equivTypesOf (propTests testmod)
 where
  equivTypesOf (IOTest _ _) = []
  equivTypesOf (PropTest _ _ _) = []
  equivTypesOf (EquivTest _ _ _ texp _) = tconsOf (resultType texp)
Michael Hanus 's avatar
Michael Hanus committed
185 186

-------------------------------------------------------------------------
187 188
-- Transform all tests of a module into operations that perform
-- appropriate calls to EasyCheck:
189 190
genTestFuncs :: Options -> String -> TestModule -> [CFuncDecl]
genTestFuncs opts mainmod tm = map createTest (propTests tm)
Michael Hanus 's avatar
Michael Hanus committed
191 192
 where
  createTest test =
193
    cfunc (mainmod, (genTestName $ testName test)) 0 Public
Michael Hanus 's avatar
Michael Hanus committed
194
          (ioType (maybeType stringType))
195 196 197 198 199 200 201 202 203 204 205 206 207
          (case test of
             PropTest  name t _       -> propBody   name t test
             IOTest    name   _       -> ioTestBody name test
             EquivTest name f1 f2 t _ ->
               -- if test name has suffix "'TERMINATE", the test for terminating
               -- operations is used, otherwise the test for arbitrary
               -- operations (which limits the size of computed results
               -- but might find counter-examples later)
               -- (TODO: automatic selection by using CASS)
               if "'TERMINATE" `isSuffixOf` map toUpper (snd name)
                 then equivBodyTerm f1 f2 t test
                 else equivBodyAny  f1 f2 t test
          )
Michael Hanus 's avatar
Michael Hanus committed
208 209 210 211 212 213 214 215 216 217 218 219 220

  msgOf test = string2ac $ genTestMsg (orgModuleName tm) test

  testmname = testModuleName tm
  
  genTestName (modName, fName) = fName ++ "_" ++ modNameToId modName

  easyCheckFuncName arity =
    if arity>maxArity
    then error $ "Properties with more than " ++ show maxArity ++
                 " parameters are currently not supported!"
    else (easyCheckExecModule,"checkWithValues" ++ show arity)

221 222
  -- Operation equivalence test for terminating operations:
  equivBodyTerm f1 f2 texp test =
223
    let xvars = map (\i -> (i,"x"++show i)) [1 .. arityOfType texp]
224 225
        pvalOfFunc = ctype2pvalOf mainmod "pvalOf" (resultType texp)
    in propOrEquivBody (map (\t -> (t,True)) (argTypes texp)) test
226
         (CLambda (map CPVar xvars)
227
            (applyF (easyCheckModule,"<~>")
228 229
                    [applyE pvalOfFunc [applyF f1 (map CVar xvars)],
                     applyE pvalOfFunc [applyF f2 (map CVar xvars)]]))
230 231 232

  -- Operation equivalence test for arbitrary operations:
  equivBodyAny f1 f2 texp test =
233 234
    let xvars = map (\i -> (i,"x"++show i)) [1 .. arityOfType texp]
        pvar  = (2,"p")
235 236 237 238 239
        pvalOfFunc = ctype2pvalOf mainmod "peval" (resultType texp)
    in propOrEquivBody
         (map (\t -> (t,True)) (argTypes texp) ++
          [(ctype2BotType mainmod  (resultType texp), False)])
         test
240
         (CLambda (map CPVar xvars ++ [CPVar pvar])
241
            (applyF (easyCheckModule,"<~>")
242 243
               [applyE pvalOfFunc [applyF f1 (map CVar xvars), CVar pvar],
                applyE pvalOfFunc [applyF f2 (map CVar xvars), CVar pvar]]))
244 245 246 247 248 249

  propBody qname texp test =
    propOrEquivBody (map (\t -> (t,False)) (argTypes texp))
                    test (CSymbol (testmname,snd qname))

  propOrEquivBody argtypes test propexp =
Michael Hanus 's avatar
Michael Hanus committed
250
    [simpleRule [] $
251 252 253 254
      CLetDecl [CLocalPat (CPVar msgvar) (CSimpleRhs (msgOf test) [])]
               (applyF (easyCheckExecModule, "checkPropWithMsg")
                 [ CVar msgvar
                 , applyF (easyCheckFuncName (length argtypes)) $
Michael Hanus 's avatar
Michael Hanus committed
255
                    [configOpWithMaxFail, CVar msgvar] ++
256
                    (map (\ (t,genpart) ->
Michael Hanus 's avatar
Michael Hanus committed
257 258
                          applyF (easyCheckModule,"valuesOfSearchTree")
                            [if isPAKCS || useUserDefinedGen t || isFloatType t
259
                             then type2genop mainmod tm genpart t
Michael Hanus 's avatar
Michael Hanus committed
260 261 262
                             else applyF (searchTreeModule,"someSearchTree")
                                         [constF (pre "unknown")]])
                         argtypes) ++
263 264
                    [propexp]
                 ])]
Michael Hanus 's avatar
Michael Hanus committed
265
   where
266
    useUserDefinedGen te = case te of
Michael Hanus 's avatar
Michael Hanus committed
267 268 269 270 271 272 273 274 275 276
      CTVar _         -> error "No polymorphic generator!"
      CFuncType _ _   -> error "No generator for functional types!"
      CTCons (_,tc) _ -> isJust
                           (find (\qn -> "gen"++tc == snd qn) (generators tm))

    configOpWithMaxTest =
      let n = optMaxTest opts
       in if n==0 then stdConfigOp
                  else applyF (easyCheckExecModule,"setMaxTest")
                              [cInt n, stdConfigOp]
277

Michael Hanus 's avatar
Michael Hanus committed
278 279 280 281 282
    configOpWithMaxFail =
      let n = optMaxFail opts
       in if n==0 then configOpWithMaxTest
                  else applyF (easyCheckExecModule,"setMaxFail")
                              [cInt n, configOpWithMaxTest]
283

Michael Hanus 's avatar
Michael Hanus committed
284
    msgvar = (0,"msg")
285

Michael Hanus 's avatar
Michael Hanus committed
286
  stdConfigOp = constF (easyCheckConfig opts)
287

Michael Hanus 's avatar
Michael Hanus committed
288 289 290 291 292 293 294 295 296 297 298 299 300
  ioTestBody (_, name) test =
    [simpleRule [] $ applyF (easyCheckExecModule,"checkPropIOWithMsg")
                            [stdConfigOp, msgOf test, CSymbol (testmname,name)]]

-- The configuration option for EasyCheck
easyCheckConfig :: Options -> QName
easyCheckConfig opts =
  (easyCheckExecModule,
   if isQuiet opts     then "quietConfig"   else
   if optVerb opts > 2 then "verboseConfig"
                       else "easyConfig")

-- Translates a type expression into calls to generator operations.
301 302 303 304 305 306 307
-- If the third argument is `True`, calls to partial generators are used.
type2genop :: String -> TestModule -> Bool -> CTypeExpr -> CExpr
type2genop _ _ _ (CTVar _)       = error "No polymorphic generator!"
type2genop _ _ _ (CFuncType _ _) = error "No generator for functional types!"
type2genop mainmod tm genpart (CTCons qt targs) =
  applyF (typename2genopname mainmod (generators tm) genpart qt)
         (map (type2genop mainmod tm genpart) targs)
Michael Hanus 's avatar
Michael Hanus committed
308 309 310 311 312

isFloatType :: CTypeExpr -> Bool
isFloatType texp = case texp of CTCons tc [] -> tc == (preludeName,"Float")
                                _            -> False

313 314 315 316 317 318 319 320 321
-- Translates the name of a type constructor into the name of the
-- generator operation for values of this type.
-- The first argument is the name of the main module.
-- The second argument contains the user-defined generator operations.
-- If the third argument is `True`, generators for partial values are used.
typename2genopname :: String -> [QName] -> Bool -> QName -> QName
typename2genopname mainmod definedgenops genpart (mn,tc)
  | genpart  -- we use our own generator for partial values:
  = (mainmod, "gen_" ++ modNameToId mn ++ "_" ++ transQN tc ++ "_PARTIAL")
Michael Hanus 's avatar
Michael Hanus committed
322 323
  | isJust maybeuserdefined -- take user-defined generator:
  = fromJust maybeuserdefined
324 325
  | mn==preludeName
  = (generatorModule, "gen" ++ transQN tc)
Michael Hanus 's avatar
Michael Hanus committed
326
  | otherwise -- we use our own generator:
327 328
  = (mainmod, "gen_" ++ modNameToId mn ++ "_" ++ transQN tc ++
              if genpart then "_PARTIAL" else "")
Michael Hanus 's avatar
Michael Hanus committed
329 330
 where
  maybeuserdefined = find (\qn -> "gen"++tc == snd qn) definedgenops
331 332 333 334 335 336 337 338 339 340 341 342

-- Transform a qualified (typ) constructor name into a name
-- with alpha-numeric characters.
transQN :: String -> String
transQN tcons | tcons == "[]"     = "List"
              | tcons == ":"      = "Cons"
              | tcons == "()"     = "Unit"
              | tcons == "(,)"    = "Pair"
              | tcons == "(,,)"   = "Triple"
              | tcons == "(,,,)"  = "Tuple4"
              | tcons == "(,,,,)" = "Tuple5"
              | otherwise         = tcons
Michael Hanus 's avatar
Michael Hanus committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

-------------------------------------------------------------------------
-- Turn all functions into public ones.
-- This ensures that all tests can be executed.
makeAllPublic :: CurryProg -> CurryProg
makeAllPublic (CurryProg modname imports typedecls functions opdecls) =
  CurryProg modname stimports typedecls publicFunctions opdecls
 where
  stimports = if generatorModule `elem` imports &&
                 searchTreeModule `notElem` imports
              then searchTreeModule : imports -- just to be safe if module
                                              -- contains generator definitions
              else imports

  publicFunctions = map makePublic $ map ignoreComment functions

  -- since we create a copy of the module, we can ignore unnecessary data
  ignoreComment :: CFuncDecl -> CFuncDecl
  ignoreComment (CmtFunc _ name arity visibility typeExpr rules) =
    CFunc name arity visibility typeExpr rules
  ignoreComment x@(CFunc      _     _          _        _     _) = x

  makePublic :: CFuncDecl -> CFuncDecl
  makePublic (CFunc name arity _      typeExpr rules) =
              CFunc name arity Public typeExpr rules
  makePublic (CmtFunc cmt name arity _      typeExpr rules) =
              CmtFunc cmt name arity Public typeExpr rules

-- classify the tests as either PropTest or IOTest
372 373
classifyTests :: Options -> CurryProg -> [CFuncDecl] -> [Test]
classifyTests opts prog = map makeProperty
Michael Hanus 's avatar
Michael Hanus committed
374
 where
375 376 377 378 379
  makeProperty test =
    if isPropIOType (funcType test)
      then IOTest tname 0
      else maybe (PropTest tname (funcType test) 0)
                 (\ (f1,f2) -> EquivTest tname f1 f2
380
                                         (poly2defaultType opts (funcTypeOf f1))
381 382 383 384 385 386 387
                                         0)
                 (isEquivProperty test)
    where tname = funcName test

  funcTypeOf f = maybe (error $ "Cannot find type of " ++ show f ++ "!")
                       funcType
                       (find (\fd -> funcName fd == f) (functions prog))
Michael Hanus 's avatar
Michael Hanus committed
388 389 390

-- Extracts all tests from a given Curry module and transforms
-- all polymorphic tests into tests on a base type.
391 392
-- The third argument contains the list of function representing
-- proved properties. It is used to simplify post conditions to be tested.
Michael Hanus 's avatar
Michael Hanus committed
393 394
-- The result contains a triple consisting of all actual tests,
-- all ignored tests, and the public version of the original module.
395
transformTests :: Options -> String -> [CFuncDecl] -> CurryProg
Michael Hanus 's avatar
Michael Hanus committed
396
               -> IO ([CFuncDecl],[CFuncDecl],CurryProg)
397
transformTests opts srcdir theofuncs
Michael Hanus 's avatar
Michael Hanus committed
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
               prog@(CurryProg mname imps typeDecls functions opDecls) = do
  simpfuncs <- simplifyPostConditionsWithTheorems (optVerb opts) theofuncs funcs
  let preCondOps  = preCondOperations simpfuncs
      postCondOps = map ((\ (mn,fn) -> (mn, fromPostCondName fn)) . funcName)
                        (funDeclsWith isPostCondName simpfuncs)
      specOps     = map ((\ (mn,fn) -> (mn, fromSpecName fn)) . funcName)
                        (funDeclsWith isSpecName simpfuncs)
      -- generate post condition tests:
      postCondTests = concatMap (genPostCondTest preCondOps postCondOps) funcs
      -- generate specification tests:
      specOpTests   = concatMap (genSpecTest preCondOps specOps) funcs

      (realtests,ignoredtests) = partition fst $
        if not (optProp opts)
        then []
413
        else concatMap (poly2default opts) $
Michael Hanus 's avatar
Michael Hanus committed
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
               -- ignore already proved properties:
               filter (\fd -> funcName fd `notElem` map funcName theofuncs)
                      usertests ++
               (if optSpec opts then postCondTests ++ specOpTests else [])
  return (map snd realtests,
          map snd ignoredtests,
          CurryProg mname
                    (nub (easyCheckModule:imps))
                    typeDecls
                    (simpfuncs ++ map snd (realtests ++ ignoredtests))
                    opDecls)
 where
  (usertests, funcs) = partition isProperty functions


-- Extracts all determinism tests from a given Curry module and
-- transforms deterministic operations back into non-deterministic operations
-- in order to test their determinism property.
-- The result contains a triple consisting of all actual determinism tests,
-- all ignored tests (since they are polymorphic), and the public version
-- of the transformed original module.
transformDetTests :: Options -> [String] -> CurryProg
                  -> ([CFuncDecl],[CFuncDecl],CurryProg)
transformDetTests opts prooffiles
                  (CurryProg mname imports typeDecls functions opDecls) =
  (map snd realtests, map snd ignoredtests,
   CurryProg mname
             (nub (easyCheckModule:imports))
             typeDecls
             (map (revertDetOpTrans detOpNames) functions ++
              map snd (realtests ++ ignoredtests))
             opDecls)
 where
  preCondOps = preCondOperations functions
448

Michael Hanus 's avatar
Michael Hanus committed
449 450 451 452 453 454 455 456 457 458 459
  -- generate determinism tests:
  detOpTests = genDetOpTests prooffiles preCondOps functions

  -- names of deterministic operations:
  detOpNames = map (stripIsDet . funcName) detOpTests

  stripIsDet (mn,fn) = (mn, take (length fn -15) fn)

  (realtests,ignoredtests) = partition fst $
    if not (optProp opts)
    then []
460
    else concatMap (poly2default opts)
Michael Hanus 's avatar
Michael Hanus committed
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
                   (if optDet opts then detOpTests else [])

-- Get all operations with a defined precondition from a list of functions.
preCondOperations :: [CFuncDecl] -> [QName]
preCondOperations fdecls =
  map ((\ (mn,fn) -> (mn,fromPreCondName fn)) . funcName)
      (funDeclsWith isPreCondName fdecls)

-- Filter functions having a name satisfying a given predicate.
funDeclsWith :: (String -> Bool) -> [CFuncDecl] -> [CFuncDecl]
funDeclsWith pred = filter (pred . snd . funcName)

-- Transforms a function type into a property type, i.e.,
-- t1 -> ... -> tn -> t  is transformed into  t1 -> ... -> tn -> Prop
propResultType :: CTypeExpr -> CTypeExpr
propResultType te = case te of
  CFuncType from to -> CFuncType from (propResultType to)
  _                 -> baseType (easyCheckModule,"Prop")

-- Transforms a function declaration into a post condition test if
-- there is a post condition for this function (i.e., a relation named
-- f'post). The post condition test is of the form
-- fSatisfiesPostCondition x1...xn y = always (f'post x1...xn (f x1...xn))
genPostCondTest :: [QName] -> [QName] -> CFuncDecl -> [CFuncDecl]
genPostCondTest prefuns postops (CmtFunc _ qf ar vis texp rules) =
Michael Hanus 's avatar
Michael Hanus committed
486
  genPostCondTest prefuns postops (CFunc qf ar vis texp rules)
Michael Hanus 's avatar
Michael Hanus committed
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
genPostCondTest prefuns postops (CFunc qf@(mn,fn) _ _ texp _) =
 if qf `notElem` postops then [] else
  [CFunc (mn, fn ++ postCondSuffix) ar Public
    (propResultType texp)
    [simpleRule (map CPVar cvars) $
      if qf `elem` prefuns
       then applyF (easyCheckModule,"==>")
                   [applyF (mn,toPreCondName fn) (map CVar cvars), postprop]
       else postprop
    ]]
 where
  ar       = arityOfType texp
  cvars    = map (\i -> (i,"x"++show i)) [1 .. ar]
  rcall    = applyF qf (map CVar cvars)
  postprop = applyF (easyCheckModule,"always")
                    [applyF (mn,toPostCondName fn)
                            (map CVar cvars ++ [rcall])]

-- Transforms a function declaration into a specification test if
-- there is a specification for this function (i.e., an operation named
-- f'spec). The specification test is of the form
-- fSatisfiesSpecification x1...xn =
--   f'pre x1...xn  ==> (f x1...xn <~> f'spec x1...xn)
genSpecTest :: [QName] -> [QName] -> CFuncDecl -> [CFuncDecl]
genSpecTest prefuns specops (CmtFunc _ qf ar vis texp rules) =
  genSpecTest prefuns specops (CFunc qf ar vis texp rules)
genSpecTest prefuns specops (CFunc qf@(mn,fn) _ _ texp _) =
 if qf `notElem` specops then [] else
  [CFunc (mn, fn ++ satSpecSuffix) ar Public
    (propResultType texp)
    [simpleRule (map CPVar cvars) $
       addPreCond (applyF (easyCheckModule,"<~>")
                          [applyF qf (map CVar cvars),
                           applyF (mn,toSpecName fn) (map CVar cvars)])]]
 where
  cvars = map (\i -> (i,"x"++show i)) [1 .. ar]
  ar    = arityOfType texp

  addPreCond exp = if qf `elem` prefuns
                   then applyF (easyCheckModule,"==>")
                          [applyF (mn,toPreCondName fn) (map CVar cvars), exp]
                   else exp

-- Revert the transformation for deterministic operations performed
-- by currypp, i.e., replace rule "f x = selectValue (set f_ORGNDFUN x)"
-- with "f = f_ORGNDFUN".
revertDetOpTrans :: [QName] -> CFuncDecl -> CFuncDecl
revertDetOpTrans  detops (CmtFunc _ qf ar vis texp rules) =
  revertDetOpTrans detops (CFunc qf ar vis texp rules)
revertDetOpTrans detops fdecl@(CFunc qf@(mn,fn) ar vis texp _) =
  if qf `elem` detops
  then CFunc qf ar vis texp [simpleRule [] (constF (mn,fn++"_ORGNDFUN"))]
  else fdecl

-- Look for operations named f_ORGNDFUN and create a determinism property
-- for f.
genDetOpTests :: [String] -> [QName] -> [CFuncDecl] -> [CFuncDecl]
genDetOpTests prooffiles prefuns fdecls =
545
  map (genDetProp prefuns) (filter (isDetOrgOp . funcName) fdecls)
Michael Hanus 's avatar
Michael Hanus committed
546
 where
547 548 549 550 551
  isDetOrgOp (mn,fn) =
    "_ORGNDFUN" `isSuffixOf` fn &&
    not (existsProofFor (mnorg, determinismTheoremFor (take (length fn - 9) fn))
                        prooffiles)
   where mnorg = take (length mn - 10) mn -- remove _PUBLICDET suffix
Michael Hanus 's avatar
Michael Hanus committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574

-- Transforms a declaration of a deterministic operation f_ORGNDFUN
-- into a determinisim property test of the form
-- fIsDeterministic x1...xn = f x1...xn #< 2
genDetProp :: [QName] -> CFuncDecl -> CFuncDecl
genDetProp prefuns (CmtFunc _ qf ar vis texp rules) =
  genDetProp prefuns (CFunc qf ar vis texp rules)
genDetProp prefuns (CFunc (mn,fn) ar _ texp _) =
  CFunc (mn, forg ++ isDetSuffix) ar Public
   (propResultType texp)
   [simpleRule (map CPVar cvars) $
      if (mn,forg) `elem` prefuns
       then applyF (easyCheckModule,"==>")
                   [applyF (mn,toPreCondName forg) (map CVar cvars), rnumcall]
       else rnumcall ]
 where
  forg     = take (length fn - 9) fn
  cvars    = map (\i -> (i,"x"++show i)) [1 .. ar]
  forgcall = applyF (mn,forg) (map CVar cvars)
  rnumcall = applyF (easyCheckModule,"#<") [forgcall, cInt 2]

-- Generates auxiliary (base-type instantiated) test functions for
-- polymorphically typed test function.
575 576 577 578 579 580 581
-- The returned flag indicates whether the test function should actually
-- be passed to the test tool.
-- Thus, IO tests are flagged by `False` if the corresponding option is set.
poly2default :: Options -> CFuncDecl -> [(Bool,CFuncDecl)]
poly2default opts (CmtFunc _ name arity vis ftype rules) =
  poly2default opts (CFunc name arity vis ftype rules)
poly2default opts fdecl@(CFunc (mn,fname) arity vis ftype rs)
Michael Hanus 's avatar
Michael Hanus committed
582 583
  | isPolyType ftype
  = [(False,fdecl)
584
    ,(True, CFunc (mn,fname++defTypeSuffix) arity vis
585
                  (poly2defaultType opts ftype)
586
                  [simpleRule [] (applyF (mn,fname) [])])
Michael Hanus 's avatar
Michael Hanus committed
587
    ]
588 589
  | not (optIOTest opts) && isPropIOType ftype
  = [(False,fdecl)]
Michael Hanus 's avatar
Michael Hanus committed
590 591
  | otherwise
  = [(True,fdecl)]
592

593 594
poly2defaultType :: Options -> CTypeExpr -> CTypeExpr
poly2defaultType opts texp = p2dt texp 
Michael Hanus 's avatar
Michael Hanus committed
595
 where
596
  p2dt (CTVar _)         = baseType (pre (optDefType opts))
Michael Hanus 's avatar
Michael Hanus committed
597
  p2dt (CFuncType t1 t2) = CFuncType (p2dt t1) (p2dt t2)
598
  p2dt (CTCons ct ts)    = CTCons ct (map p2dt ts)
Michael Hanus 's avatar
Michael Hanus committed
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635

-- Transforms a possibly changed test name (like "test_ON_BASETYPE")
-- back to its original name.
orgTestName :: QName -> QName
orgTestName (mn,tname)
  | defTypeSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname defTypeSuffix)
  | isDetSuffix `isSuffixOf` tname
  = orgTestName (mn, take (length tname - 15) tname)
  | postCondSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname postCondSuffix)
  | satSpecSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname satSpecSuffix)
  | otherwise = (mn,tname)

-- This function implements the first phase of CurryCheck: it analyses
-- a module to be checked, i.e., it finds the tests, creates new tests
-- (e.g., for polymorphic properties, deterministic functions, post
-- conditions, specifications)
-- and generates a copy of the module appropriate for the main operation
-- of CurryCheck (e.g., all operations are made public).
-- If there are determinism tests, it also generates a second copy
-- where all deterministic functions are defined as non-deterministic
-- so that these definitions are tested.
analyseModule :: Options -> String -> IO [TestModule]
analyseModule opts modname = do
  putStrIfNormal opts $ withColor opts blue $
                        "Analyzing module '" ++ modname ++ "'...\n"
  catch (readCurryWithParseOptions modname (setQuiet True defaultParams) >>=
         analyseCurryProg opts modname)
        (\_ -> return [staticErrorTestMod modname
                         ["Module '"++modname++"': incorrect source program"]])

-- Analyse a Curry module for static errors:
staticProgAnalysis :: Options -> String -> String -> CurryProg
                   -> IO ([String],[(QName,String)])
staticProgAnalysis opts modname progtxt prog = do
636
  putStrIfDetails opts "Checking source code for static errors...\n"
Michael Hanus 's avatar
Michael Hanus committed
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
  useerrs <- if optSource opts then checkBlacklistUse prog else return []
  seterrs <- if optSource opts then readFlatCurry modname >>= checkSetUse
                               else return []
  let defruleerrs = if optSource opts then checkDefaultRules prog else []
  untypedprog <- readUntypedCurry modname
  let detuseerrs   = if optSource opts then checkDetUse untypedprog else []
      contracterrs = checkContractUse prog
      staticerrs = concat [seterrs,useerrs,defruleerrs,detuseerrs,contracterrs]
      missingCPP =
       if (containsDefaultRules prog || containsDetOperations untypedprog)
          && not (containsPPOptionLine progtxt)
       then ["'" ++ modname ++
           "' uses default rules or det. operations but not the preprocessor!",
           "Hint: insert line: {-# OPTIONS_CYMAKE -F --pgmF=currypp #-}"]
       else []
  return (missingCPP,staticerrs)

-- Analyse a Curry module and generate property tests:
analyseCurryProg :: Options -> String -> CurryProg -> IO [TestModule]
analyseCurryProg opts modname orgprog = do
  -- First we rename all references to Test.Prop into Test.EasyCheck
  let prog = renameProp2EasyCheck orgprog
  (topdir,srcfilename) <- lookupModuleSourceInLoadPath modname >>=
        return .
        maybe (error $ "Source file of module '"++modname++"' not found!") id
  let srcdir = takeDirectory srcfilename
663
  putStrLnIfDebug opts $ "Source file: " ++ srcfilename
Michael Hanus 's avatar
Michael Hanus committed
664
  prooffiles <- if optProof opts then getProofFiles srcdir else return []
665
  unless (null prooffiles) $ putStrIfDetails opts $
Michael Hanus 's avatar
Michael Hanus committed
666 667 668 669 670
    unlines ("Proof files found:" : map ("- " ++) prooffiles)
  progtxt <- readFile srcfilename
  (missingCPP,staticoperrs) <- staticProgAnalysis opts modname progtxt prog
  let words      = map firstWord (lines progtxt)
      staticerrs = missingCPP ++ map (showOpError words) staticoperrs
671
  putStrIfDetails opts "Generating property tests...\n"
672 673 674 675 676 677 678
  theofuncs <- if optProof opts then getTheoremFunctions srcdir prog
                                else return []
  -- compute already proved theorems for public module:
  let pubmodname = modname++"_PUBLIC"
      rnm2pub mn@(mod,n) | mod == modname = (pubmodname,n)
                         | otherwise      = mn
      theopubfuncs = map (updQNamesInCFuncDecl rnm2pub) theofuncs
Michael Hanus 's avatar
Michael Hanus committed
679
  (rawTests,ignoredTests,pubmod) <-
680 681
        transformTests opts srcdir theopubfuncs
          . renameCurryModule pubmodname . makeAllPublic $ prog
Michael Hanus 's avatar
Michael Hanus committed
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
  let (rawDetTests,ignoredDetTests,pubdetmod) =
        transformDetTests opts prooffiles
              . renameCurryModule (modname++"_PUBLICDET")
              . makeAllPublic $ prog
  unless (not (null staticerrs) || null rawTests && null rawDetTests) $
    putStrIfNormal opts $
      "Properties to be tested:\n" ++
      unwords (map (snd . funcName) (rawTests++rawDetTests)) ++ "\n"
  unless (not (null staticerrs) || null ignoredTests && null ignoredDetTests) $
    putStrIfNormal opts $
      "Properties ignored for testing:\n" ++
      unwords (map (snd . funcName) (ignoredTests++ignoredDetTests)) ++ "\n"
  let tm    = TestModule modname
                         (progName pubmod)
                         staticerrs
697 698
                         (addLinesNumbers words
                            (classifyTests opts pubmod rawTests))
Michael Hanus 's avatar
Michael Hanus committed
699 700 701 702
                         (generatorsOfProg pubmod)
      dettm = TestModule modname
                         (progName pubdetmod)
                         []
703 704
                         (addLinesNumbers words
                            (classifyTests opts pubdetmod rawDetTests))
Michael Hanus 's avatar
Michael Hanus committed
705 706 707 708 709 710 711 712 713 714 715 716
                         (generatorsOfProg pubmod)
  when (testThisModule tm) $ writeCurryProgram opts topdir pubmod ""
  when (testThisModule dettm) $ writeCurryProgram opts topdir pubdetmod ""
  return (if testThisModule dettm then [tm,dettm] else [tm])
 where
  showOpError words (qf,err) =
    snd qf ++ " (module " ++ modname ++ ", line " ++
    show (getLineNumber words qf) ++"): " ++ err

  addLinesNumbers words = map (addLineNumber words)

  addLineNumber :: [String] -> Test -> Test
717
  addLineNumber words (PropTest name texp _) =
Michael Hanus 's avatar
Michael Hanus committed
718 719 720
    PropTest   name texp $ getLineNumber words (orgTestName name)
  addLineNumber words (IOTest name _) =
    IOTest name $ getLineNumber words (orgTestName name)
721 722
  addLineNumber words (EquivTest name f1 f2 texp _) =
    EquivTest name f1 f2 texp $ getLineNumber words (orgTestName name)
Michael Hanus 's avatar
Michael Hanus committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737

  getLineNumber :: [String] -> QName -> Int
  getLineNumber words (_, name) = maybe 0 (+1) (elemIndex name words)

-- Extracts all user-defined defined generators defined in a module.
generatorsOfProg :: CurryProg -> [QName]
generatorsOfProg = map funcName . filter isGen . functions
 where
   isGen fdecl = "gen" `isPrefixOf` snd (funcName fdecl) &&
                 isSearchTreeType (resultType (funcType fdecl))

   isSearchTreeType (CTVar _) = False
   isSearchTreeType (CFuncType _ _) = False
   isSearchTreeType (CTCons tc _) = tc == searchTreeTC

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
-------------------------------------------------------------------------
-- Auxiliaries to support equivalence checking of operations.

-- Create data type with explicit bottom constructors.
genBottomType :: String -> FC.TypeDecl -> CTypeDecl
genBottomType _ (FC.TypeSyn _ _ _ _) =
  error "genBottomType: cannot translate type synonyms"
genBottomType mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  CType (mainmod,t2bt tc) Public (map transTVar tvars)
        (CCons (mainmod,"Bot_"++transQN tc) Public [] :
         if isBasicExtType qtc
           then [CCons (mainmod,"Value_"++tc) Public [baseType qtc]]
           else map transConsDecl consdecls)
 where
  transConsDecl (FC.Cons (_,cons) _ _ argtypes) =
    CCons (mainmod,t2bt cons) Public (map transTypeExpr argtypes)

  transTypeExpr (FC.TVar i) = CTVar (transTVar i)
  transTypeExpr (FC.FuncType t1 t2) =
    CFuncType (transTypeExpr t1) (transTypeExpr t2)
  transTypeExpr (FC.TCons (_,tcons) tes) =
    CTCons (mainmod,t2bt tcons) (map transTypeExpr tes)

  transTVar i = (i,'a':show i)

-- Is the type name an external basic prelude type?
isBasicExtType :: QName -> Bool
isBasicExtType (mn,tc) = mn == preludeName && tc `elem` ["Int","Float","Char"]

-- Default value for external basic prelude types.
defaultValueOfBasicExtType :: String -> CLiteral
defaultValueOfBasicExtType qn
  | qn == "Int"   = CIntc   0
  | qn == "Float" = CFloatc 0.0
  | qn == "Char"  = CCharc  'A'
  | otherwise     = error $ "defaultValueOfBasicExtType: unknown type: "++qn
  
ctype2BotType :: String -> CTypeExpr -> CTypeExpr
ctype2BotType _ (CTVar i) = CTVar i
ctype2BotType mainmod (CFuncType t1 t2) =
  CFuncType (ctype2BotType mainmod t1) (ctype2BotType mainmod t2)
ctype2BotType mainmod (CTCons qtc tes) =
  CTCons (mainmod, t2bt (snd qtc)) (map (ctype2BotType mainmod) tes)

-- Translate a type constructor name to its bottom type constructor name
t2bt :: String -> String
t2bt s = "P_" ++ transQN s

-- Create `peval_` operation for a data type with explicit bottom constructors
-- according to the following scheme:
{-
peval_AB :: AB -> P_AB -> P_AB
peval_AB _ Bot_AB = Bot_AB                 -- no evaluation
peval_AB A P_A    = P_A
peval_AB B P_B    = P_B

peval_C :: C -> P_C -> P_C
peval_C _     Bot_C   = Bot_C              -- no evaluation
peval_C (C x) (P_C y) = P_C (peval_AB x y)

f_equiv_g x p = peval_C (f x) p <~> peval_C (g x) p
-}
genPeval :: String -> FC.TypeDecl -> CFuncDecl
genPeval _ (FC.TypeSyn _ _ _ _) =
  error "genPeval: cannot translate type synonyms"
genPeval mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  cmtfunc ("Evaluate a `"++tc++"` value up to a partial approxmiation.")
    (mainmod,"peval_"++transQN tc) 1 Public
    (foldr1 (~>) (map (\ (a,b) -> CTVar a ~> CTVar b ~> CTVar b)
                      (zip polyavars polyrvars) ++
                  [CTCons qtc (map CTVar polyavars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars)]))
    (simpleRule (map CPVar (polyavars ++ [(0,"_")]) ++ [CPComb botSym []])
                (constF botSym) :
     if isBasicExtType qtc
       then [valueRule]
       else map genConsRule consdecls)
 where
  botSym = (mainmod,"Bot_"++transQN tc) -- bottom constructor
  
  -- variables for polymorphic type arguments:
  polyavars = [ (i,"a"++show i) | i <- tvars]
  polyrvars = [ (i,"b"++show i) | i <- tvars]
  
  genConsRule (FC.Cons qc@(_,cons) _ _ argtypes) =
    let args  = [(i,"x"++show i) | i <- [0 .. length argtypes - 1]]
        pargs = [(i,"y"++show i) | i <- [0 .. length argtypes - 1]]
        pcons = (mainmod,t2bt cons)
    in simpleRule (map CPVar polyavars ++
                   [CPComb qc (map CPVar args), CPComb pcons (map CPVar pargs)])
         (applyF pcons
                 (map (\ (e1,e2,te) ->
                        applyE (ftype2pvalOf mainmod "peval" polyavars te)
                               [e1,e2])
                      (zip3 (map CVar args) (map CVar pargs) argtypes)))

  valueRule =
    let xvar    = (0,"x")
        yvar    = (1,"y")
        valcons = (mainmod,"Value_"++tc)
    in guardedRule [CPVar xvar, CPComb valcons [CPVar yvar]]
                   [(constF (pre "True"), --applyF (pre "=:=") [CVar xvar, CVar yvar],
                     applyF valcons [CVar xvar])]
                   []

-- Create `pvalOf` operation for a data type with explicit bottom constructors
-- according to the following scheme:
{-
pvalOf_AB :: AB -> P_AB
pvalOf_AB _ = Bot_AB
pvalOf_AB A = P_A
pvalOf_AB B = P_B

pvalOf_C :: C -> P_C
pvalOf_C _     = Bot_C
pvalOf_C (C x) = P_C (pvalOf_AB x)

f_equiv_g x = pvalOf_C (f x) <~> pvalOf_C (g x)
-}
genPValOf :: String -> FC.TypeDecl -> CFuncDecl
genPValOf _ (FC.TypeSyn _ _ _ _) =
  error "genPValOf: cannot translate type synonyms"
genPValOf mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  cmtfunc ("Map a `"++tc++"` value into all its partial approxmiations.")
    (mainmod,"pvalOf_"++transQN tc) 1 Public
    (foldr1 (~>) (map (\ (a,b) -> CTVar a ~> CTVar b)
                      (zip polyavars polyrvars) ++
                  [CTCons qtc (map CTVar polyavars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars)]))
    (simpleRule (map CPVar (polyavars ++ [(0,"_")]))
                (constF (mainmod,"Bot_"++transQN tc)) :
     if isBasicExtType qtc
       then [valueRule]
       else map genConsRule consdecls)
 where
  -- variables for polymorphic type arguments:
  polyavars = [ (i,"a"++show i) | i <- tvars]
  polyrvars = [ (i,"b"++show i) | i <- tvars]
  
  genConsRule (FC.Cons qc@(_,cons) _ _ argtypes) =
    let args = [(i,"x"++show i) | i <- [0 .. length argtypes - 1]]
    in simpleRule (map CPVar polyavars ++ [CPComb qc (map CPVar args)])
         (applyF (mainmod,t2bt cons)
            (map (\ (e,te) ->
                   applyE (ftype2pvalOf mainmod "pvalOf" polyavars te) [e])
                 (zip (map CVar args) argtypes)))

  valueRule =
    let var = (0,"x")
    in simpleRule [CPVar var] (applyF (mainmod,"Value_"++tc) [CVar var])

-- Translate a FlatCurry type into a corresponding call to `pvalOf`:
ftype2pvalOf :: String -> String -> [(Int,String)] -> FC.TypeExpr -> CExpr
ftype2pvalOf mainmod pvalname polyvars (FC.TCons (_,tc) texps) =
  applyF (mainmod,pvalname++"_"++transQN tc)
         (map (ftype2pvalOf mainmod pvalname polyvars) texps)
ftype2pvalOf _ _ _ (FC.FuncType _ _) =
  error "genPValOf: cannot handle functional types in as constructor args"
ftype2pvalOf _ _ polyvars (FC.TVar i) =
  maybe (error "genPValOf: unbound type variable")
        CVar
        (find ((==i) . fst) polyvars)

-- Translate an AbstractCurry type into a corresponding call to `pvalOf`:
ctype2pvalOf :: String -> String -> CTypeExpr -> CExpr
ctype2pvalOf mainmod pvalname (CTCons (_,tc) texps) =
  applyF (mainmod,pvalname++"_"++transQN tc)
         (map (ctype2pvalOf mainmod pvalname) texps)
ctype2pvalOf _ _ (CFuncType _ _) =
  error "genPValOf: cannot handle functional types in as constructor args"
ctype2pvalOf _ _ (CTVar _) = error "genPValOf: unbound type variable"


-- Translate an AbstractCurry type declaration into a FlatCurry type decl:
ctypedecl2ftypedecl :: CTypeDecl -> FC.TypeDecl
ctypedecl2ftypedecl (CTypeSyn _ _ _ _) =
  error "ctypedecl2ftypedecl: cannot translate type synonyms"
ctypedecl2ftypedecl (CNewType _ _ _ _) =
  error "ctypedecl2ftypedecl: cannot translate newtype"
ctypedecl2ftypedecl (CType qtc _ tvars consdecls) =
  FC.Type qtc FC.Public (map fst tvars) (map transConsDecl consdecls)
 where
  transConsDecl (CCons qc _ argtypes) =
    FC.Cons qc (length argtypes) FC.Public (map transTypeExpr argtypes)
  transConsDecl (CRecord _ _ _) =
    error "ctypedecl2ftypedecl: cannot translate records"

  transTypeExpr (CTVar (i,_)) = FC.TVar i
  transTypeExpr (CFuncType t1 t2) =
    FC.FuncType (transTypeExpr t1) (transTypeExpr t2)
  transTypeExpr (CTCons qtcons tes) = FC.TCons qtcons (map transTypeExpr tes)

Michael Hanus 's avatar
Michael Hanus committed
931 932
-------------------------------------------------------------------------
-- Create the main test module containing all tests of all test modules as
933
-- a Curry program with name `mainmod`.
Michael Hanus 's avatar
Michael Hanus committed
934 935 936 937 938
-- The main test module contains a wrapper operation for each test
-- and a main function to execute these tests.
-- Furthermore, if PAKCS is used, test data generators
-- for user-defined types are automatically generated.
genMainTestModule :: Options -> String -> [TestModule] -> IO ()
939
genMainTestModule opts mainmod modules = do
Michael Hanus 's avatar
Michael Hanus committed
940 941
  let testtypes = nub (concatMap userTestDataOfModule modules)
  testtypedecls <- collectAllTestTypeDecls [] testtypes
942 943 944 945 946 947 948
  equvtypedecls <- collectAllTestTypeDecls []
                     (map (\t->(t,True))
                          (nub (concatMap equivPropTypes modules)))
                     >>= return . map fst
  let bottypes  = map (genBottomType mainmod) equvtypedecls
      pevalfuns = map (genPeval mainmod) equvtypedecls
      pvalfuns  = map (genPValOf mainmod) equvtypedecls
949
      generators   = map (genTestDataGenerator mainmod)
950 951
                         (testtypedecls ++
                          map (\td -> (ctypedecl2ftypedecl td,False)) bottypes)
952
      funcs        = concatMap (genTestFuncs opts mainmod) modules ++
Michael Hanus 's avatar
Michael Hanus committed
953
                               generators
954
      mainFunction = genMainFunction opts mainmod
Michael Hanus 's avatar
Michael Hanus committed
955 956 957
                                     (concatMap propTests modules)
      imports      = nub $ [ easyCheckModule, easyCheckExecModule
                           , searchTreeModule, generatorModule
958
                           , "AnsiCodes","Maybe","System","Profile"] ++
959 960
                           map (fst . fst) testtypes ++
                           map testModuleName modules
Michael Hanus 's avatar
Michael Hanus committed
961 962
  appendix <- readFile (packagePath </> "src" </> "TestAppendix.curry")
  writeCurryProgram opts "."
963 964
                    (CurryProg mainmod imports bottypes
                            (mainFunction : funcs ++ pvalfuns ++ pevalfuns) [])
Michael Hanus 's avatar
Michael Hanus committed
965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
                    appendix

-- Generates the main function which executes all property tests
-- of all test modules.
genMainFunction :: Options -> String -> [Test] -> CFuncDecl
genMainFunction opts testModule tests =
  CFunc (testModule, "main") 0 Public (ioType unitType) [simpleRule [] body]
 where
  body = CDoExpr $
     (if isQuiet opts
        then []
        else [CSExpr (applyF (pre "putStrLn")
                             [string2ac "Executing all tests..."])]) ++
     [ CSPat (cpvar "x1") $ -- run all tests:
          applyF (testModule, "runPropertyTests")
                 [constF (pre (if optColor opts then "True" else "False")),
981
                  constF (pre (if optTime  opts then "True" else "False")),
Michael Hanus 's avatar
Michael Hanus committed
982 983 984 985 986 987 988 989 990 991 992 993
                  easyCheckExprs]
     , CSExpr $ applyF (pre "when")
                  [applyF (pre "/=") [cvar "x1", cInt 0],
                   applyF ("System", "exitWith") [cvar "x1"]]
     ]

  easyCheckExprs = list2ac $ map makeExpr tests

  makeExpr :: Test -> CExpr
  makeExpr (PropTest (mn, name) _ _) =
    constF (testModule, name ++ "_" ++ modNameToId mn)
  makeExpr (IOTest (mn, name) _) =
994 995 996
    constF (testModule, name ++ "_" ++ modNameToId  mn)
  makeExpr (EquivTest (mn, name) _ _ _ _) =
    constF (testModule, name ++ "_" ++ modNameToId mn)
Michael Hanus 's avatar
Michael Hanus committed
997 998 999 1000 1001

-------------------------------------------------------------------------
-- Collect all type declarations for a given list of type
-- constructor names, including the type declarations which are
-- used in these type declarations.
1002 1003
collectAllTestTypeDecls :: [(FC.TypeDecl,Bool)] -> [(QName,Bool)]
                        -> IO [(FC.TypeDecl,Bool)]
Michael Hanus 's avatar
Michael Hanus committed
1004 1005 1006
collectAllTestTypeDecls tdecls testtypenames = do
  newtesttypedecls <- mapIO getTypeDecl testtypenames
  let alltesttypedecls = tdecls ++ newtesttypedecls
1007 1008 1009
      newtcons = filter (\ ((mn,_),genpart) -> genpart || mn /= preludeName)
                        (nub (concatMap allTConsInDecl' newtesttypedecls)
                         \\ map (\(t,p) -> (FCG.typeName t,p)) alltesttypedecls)
Michael Hanus 's avatar
Michael Hanus committed
1010 1011 1012 1013 1014
  if null newtcons then return alltesttypedecls
                   else collectAllTestTypeDecls alltesttypedecls newtcons
 where
  -- gets the type declaration for a given type constructor
  -- (could be improved by caching programs that are already read)
1015 1016
  getTypeDecl :: (QName,Bool) -> IO (FC.TypeDecl,Bool)
  getTypeDecl (qt@(mn,_),genpartial) = do
Michael Hanus 's avatar
Michael Hanus committed
1017 1018 1019
    fprog <- readFlatCurry mn
    maybe (error $ "Definition of type '" ++ FC.showQNameInModule "" qt ++
                   "' not found!")
1020
          (\td -> return (td,genpartial))
Michael Hanus 's avatar
Michael Hanus committed
1021 1022
          (find (\t -> FCG.typeName t == qt) (FCG.progTypes fprog))

1023 1024 1025
  allTConsInDecl' :: (FC.TypeDecl,Bool) -> [(QName,Bool)]
  allTConsInDecl' (td,genpart) = map (\t->(t,genpart)) (allTConsInDecl td)

Michael Hanus 's avatar
Michael Hanus committed
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
  -- compute all type constructors used in a type declaration
  allTConsInDecl :: FC.TypeDecl -> [QName]
  allTConsInDecl = FCG.trType (\_ _ _ -> concatMap allTConsInConsDecl)
                              (\_ _ _ -> allTConsInTypeExpr)
  
  allTConsInConsDecl :: FC.ConsDecl -> [QName]
  allTConsInConsDecl = FCG.trCons (\_ _ _ -> concatMap allTConsInTypeExpr)
  
  allTConsInTypeExpr :: FC.TypeExpr -> [QName]
  allTConsInTypeExpr =
    FCG.trTypeExpr (\_ -> []) (\tc targs -> tc : concat targs) (++)

-- Creates a test data generator for a given type declaration.
1039 1040
-- If the flag of the type declaration is `True`, a generator
-- for partial values is created.
1041 1042
genTestDataGenerator :: String -> (FC.TypeDecl,Bool) -> CFuncDecl
genTestDataGenerator mainmod (tdecl,part) = type2genData tdecl
Michael Hanus 's avatar
Michael Hanus committed
1043 1044 1045 1046 1047 1048
 where
  qt       = FCG.typeName tdecl
  qtString = FC.showQNameInModule "" qt

  type2genData (FC.TypeSyn _ _ _ _) =
    error $ "Cannot create generator for type synonym " ++ qtString
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
  type2genData (FC.Type _ _ tvars cdecls)
    | null cdecls && (fst qt /= preludeName || not part)
    = error $ "Cannot create value generator for type '" ++ qtString ++
              "' without constructors!"
    | otherwise
    = cmtfunc
        ("Generator for " ++ (if part then "partial " else "") ++
         "`" ++ qtString ++ "` values.")
        (typename2genopname mainmod [] part qt) (length tvars) Public
        (foldr (~>) (CTCons searchTreeTC [CTCons qt ctvars])
                    (map (\v -> CTCons searchTreeTC [v]) ctvars))
        [simpleRule (map CPVar cvars)
          (let gencstrs =  foldr1 (\e1 e2 -> applyF choiceGen [e1,e2])
                                  (map cons2gen cdecls)
           in if part
                then applyF choiceGen
                            [ applyF (generatorModule, "genCons0")
                                     [constF (pre "failed")]
                            , if null cdecls
                                then constF (generatorModule,
                                             "gen" ++ transQN (snd qt))
                                else gencstrs ]
                else gencstrs)]
Michael Hanus 's avatar
Michael Hanus committed
1072
   where
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
    cons2gen (FC.Cons qn@(mn,cn) ar _ ctypes)
      | ar>maxArity
      = error $ "Test data constructors with more than " ++ show maxArity ++
                " arguments are currently not supported!"
      | not part && mn == mainmod && "Value_" `isPrefixOf` cn
        -- specific generator for bottom types of external basic types
        -- like Int (actually, do not generate values in order to reduce
        -- search space):
      = applyF (generatorModule, "genCons1")
               [CSymbol qn,
                applyF (searchTreeModule,"Value")
                       [CLit (defaultValueOfBasicExtType (drop 6 cn))]]
      | otherwise
      = applyF (generatorModule, "genCons" ++ show ar)
               ([CSymbol qn] ++ map type2gen ctypes)
Michael Hanus 's avatar
Michael Hanus committed
1088 1089 1090 1091 1092 1093

    type2gen (FC.TVar i) = CVar (i,"a"++show i)
    type2gen (FC.FuncType _ _) =
      error $ "Type '" ++ qtString ++
              "': cannot create value generators for functions!"
    type2gen (FC.TCons qtc argtypes) =
1094 1095
      applyF (typename2genopname mainmod [] part qtc)
             (map type2gen argtypes)
Michael Hanus 's avatar
Michael Hanus committed
1096 1097 1098 1099 1100 1101 1102

    ctvars = map (\i -> CTVar (i,"a"++show i)) tvars
    cvars  = map (\i -> (i,"a"++show i)) tvars

-------------------------------------------------------------------------
-- remove the generated files (except if option "--keep" is set)
cleanup :: Options -> String -> [TestModule] -> IO ()
1103
cleanup opts mainmod modules =
Michael Hanus 's avatar
Michael Hanus committed
1104
  unless (optKeep opts) $ do
1105
    removeCurryModule mainmod
Michael Hanus 's avatar
Michael Hanus committed
1106 1107 1108 1109 1110 1111 1112
    mapIO_ removeCurryModule (map testModuleName modules)
 where
  removeCurryModule modname =
    lookupModuleSourceInLoadPath modname >>=
    maybe done
          (\ (_,srcfilename) -> do
            system $ installDir </> "bin" </> "cleancurry" ++ " " ++ modname
1113
            system $ "/bin/rm -f " ++ srcfilename
Michael Hanus 's avatar
Michael Hanus committed
1114 1115 1116
            done )

-- Show some statistics about number of tests:
1117 1118
showTestStatistics :: Options -> [TestModule] -> String
showTestStatistics opts testmodules =
Michael Hanus 's avatar
Michael Hanus committed
1119 1120 1121
  let numtests  = sumOf (const True) testmodules
      unittests = sumOf isUnitTest   testmodules
      proptests = sumOf isPropTest   testmodules
1122
      equvtests = sumOf isEquivTest  testmodules
Michael Hanus 's avatar
Michael Hanus committed
1123 1124 1125
      iotests   = sumOf isIOTest     testmodules
   in "TOTAL NUMBER OF TESTS: " ++ show numtests ++
      " (UNIT: " ++ show unittests ++ ", PROPERTIES: " ++
1126
      show proptests ++ ", EQUIVALENCE: " ++ show equvtests ++
1127
      (if optIOTest opts then ", IO: " ++ show iotests else "") ++ ")"
Michael Hanus 's avatar
Michael Hanus committed
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
 where
  sumOf p = foldr (+) 0 . map (length . filter p . propTests)

-------------------------------------------------------------------------
main :: IO ()
main = do
  argv <- getArgs
  pid  <- getPID
  let (funopts, args, opterrors) = getOpt Permute options argv
  opts <- processOpts (foldl (flip id) defaultOptions funopts)
  unless (null opterrors)
         (putStr (unlines opterrors) >> putStrLn usageText >> exitWith 1)
1140
  putStrIfNormal opts ccBanner
Michael Hanus 's avatar
Michael Hanus committed
1141
  when (null args || optHelp opts) (putStrLn usageText >> exitWith 1)
1142 1143 1144
  let mods = map stripCurrySuffix args
  mapIO_ checkModuleName mods
  testModules <- mapIO (analyseModule opts) mods
Michael Hanus 's avatar
Michael Hanus committed
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
  let staticerrs       = concatMap staticErrors (concat testModules)
      finaltestmodules = filter testThisModule (concat testModules)
      testmodname = if null (optMainProg opts)
                      then "TEST" ++ show pid
                      else optMainProg opts
  if not (null staticerrs)
   then do showStaticErrors opts staticerrs
           putStrLn $ withColor opts red "Testing aborted!"
           cleanup opts testmodname finaltestmodules
           exitWith 1
   else if null finaltestmodules then exitWith 0 else do
    putStrIfNormal opts $ withColor opts blue $
                          "Generating main test module '"++testmodname++"'..."
1158
    putStrIfDetails opts "\n"
Michael Hanus 's avatar
Michael Hanus committed
1159
    genMainTestModule opts testmodname finaltestmodules
1160
    showGeneratedModule opts "main test" testmodname
Michael Hanus 's avatar
Michael Hanus committed
1161
    putStrIfNormal opts $ withColor opts blue $ "and compiling it...\n"
1162 1163 1164
    ecurrypath <- getEnviron "CURRYPATH"
    let currypath = case ecurrypath of ':':_ -> '.':ecurrypath
                                       _     -> ecurrypath
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174
    let runcmd = unwords $
                   [ installDir </> "bin" </> "curry"
                   , "--noreadline"
                   , ":set -time"
                   , ":set " ++ if optVerb opts > 3 then "v1" else "v0"
                   , ":set parser -Wnone"
                   , if null currypath then "" else ":set path " ++ currypath
                   , ":l "++testmodname,":eval main :q" ]
    putStrLnIfDebug opts $ "Executing command:\n" ++ runcmd
    ret <- system runcmd
Michael Hanus 's avatar
Michael Hanus committed
1175 1176
    cleanup opts testmodname finaltestmodules
    unless (isQuiet opts || ret /= 0) $
1177
      putStrLn $ withColor opts green $ showTestStatistics opts finaltestmodules
Michael Hanus 's avatar
Michael Hanus committed
1178 1179 1180 1181
    exitWith ret
 where
  showStaticErrors opts errs = putStrLn $ withColor opts red $
    unlines (line : "STATIC ERRORS IN PROGRAMS:" : errs) ++ line
1182 1183 1184 1185 1186 1187

  checkModuleName mn =
    when (pathSeparator `elem` mn) $ do
      putStrLn $ "Module names with path prefixes not allowed: " ++ mn
      exitWith 1

Michael Hanus 's avatar
Michael Hanus committed
1188 1189
  line = take 78 (repeat '=')

1190 1191 1192 1193 1194 1195 1196 1197 1198 1199
showGeneratedModule :: Options -> String -> String -> IO ()
showGeneratedModule opts mkind modname = when (optVerb opts > 3) $ do
  putStrLn $ '\n' : line
  putStrLn $ "Generated " ++ mkind ++ " module `" ++ modname ++ ".curry':"
  putStrLn line
  readFile (modname ++ ".curry") >>= putStr
  putStrLn line
 where
  line = take 78 (repeat '=')

Michael Hanus 's avatar
Michael Hanus committed
1200 1201 1202
-------------------------------------------------------------------------
-- Auxiliaries

1203
-- Rename all module references to "Test.Prop" into "Test.EasyCheck"
Michael Hanus 's avatar
Michael Hanus committed
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
renameProp2EasyCheck :: CurryProg -> CurryProg
renameProp2EasyCheck prog =
  updCProg id (map rnmMod) id id id
           (updQNamesInCProg (\ (mod,n) -> (rnmMod mod,n)) prog)
 where
  rnmMod mod | mod == propModule = easyCheckModule
             | otherwise         = mod

-- Extracts the first word of a string
firstWord :: String -> String
firstWord = head . splitOn "\t" . head . splitOn " "

-- Strips a suffix from a string.
stripSuffix :: String -> String -> String
stripSuffix str suf = if suf `isSuffixOf` str
                      then take (length str - length suf) str
                      else str

-- Translate a module name to an identifier, i.e., replace '.' by '_':
modNameToId :: String -> String
modNameToId = intercalate "_" . split (=='.')

-- Computes the arity from a type expression.
arityOfType :: CTypeExpr -> Int
arityOfType = length . argTypes

--- Name of the SearchTree module.
searchTreeModule :: String
searchTreeModule = "SearchTree"

--- Name of SearchTree type constructor.
searchTreeTC :: QName
searchTreeTC = (searchTreeModule,"SearchTree")
1237

Michael Hanus 's avatar
Michael Hanus committed
1238 1239 1240 1241
--- Name of the SearchTreeGenerator module.
generatorModule :: String
generatorModule = "SearchTreeGenerators"

1242 1243 1244
choiceGen :: QName
choiceGen = (generatorModule,"|||")

Michael Hanus 's avatar
Michael Hanus committed
1245 1246 1247 1248
-- Writes a Curry module (together with an appendix) to its file.
writeCurryProgram :: Options -> String -> CurryProg -> String -> IO ()
writeCurryProgram opts srcdir p appendix = do
  let progfile = srcdir </> modNameToPath (progName p) ++ ".curry"
1249
  putStrLnIfDebug opts $ "Writing program: " ++ progfile
Michael Hanus 's avatar
Michael Hanus committed
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
  writeFile progfile
            (showCProg p ++ "\n" ++ appendix ++ "\n")

isPAKCS :: Bool
isPAKCS = curryCompiler == "pakcs"

-- Does a program text contains a OPTIONS_CYMAKE line to call currypp?
containsPPOptionLine :: String -> Bool
containsPPOptionLine = any isOptionLine . lines
 where
   isOptionLine s = "{-# OPTIONS_CYMAKE " `isPrefixOf` s -- -}
                    && "currypp" `isInfixOf` s

1263 1264 1265 1266 1267 1268 1269 1270
tconsOf :: CTypeExpr -> [QName]
tconsOf (CTVar _) = []
tconsOf (CFuncType from to) = union (tconsOf from) (tconsOf to)
tconsOf (CTCons tc argtypes) = union [tc] (unionOn tconsOf argtypes)

unionOn :: (a -> [b]) -> [a] -> [b]
unionOn f = foldr union [] . map f

Michael Hanus 's avatar
Michael Hanus committed
1271
-------------------------------------------------------------------------