packages feed

ideas-0.7: src/Service/BasicServices.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Service.BasicServices 
   ( -- * Basic Services
     stepsremaining, findbuggyrules, ready, allfirsts, derivation
   , onefirst, applicable, allapplications, apply, generate, generateWith
   ) where

import Common.Library hiding (derivation, applicable, apply)
import Common.Utils (safeHead)
import Data.List
import Data.Maybe
import System.Random (StdGen, newStdGen)
import Control.Monad
import Service.ExercisePackage
import Service.State
import qualified Common.Classes as Apply
      
-- result must be in the IO monad to access a standard random number generator
generate :: ExercisePackage a -> Int -> IO (State a)
generate pkg level = do 
   stdgen <- newStdGen
   return (generateWith stdgen pkg level)

generateWith :: StdGen -> ExercisePackage a -> Int -> State a
generateWith rng pkg level = 
   emptyState pkg (randomTermWith rng level (exercise pkg))

derivation :: Monad m => Maybe StrategyConfiguration -> State a -> m [(Rule (Context a), Context a)]
derivation mcfg state =
   case (statePrefix state, mcfg) of 
      (Nothing, _) -> fail "Prefix is required"
      -- configuration is only allowed beforehand: hence, the prefix 
      -- should be empty (or else, the configuration is ignored). This
      -- restriction should probably be relaxed later on.
      (Just p, Just cfg) | null (prefixToSteps p) -> 
         let newStrategy = configure cfg (strategy ex)
             newExercise = ex {strategy = newStrategy}
             newPackage  = pkg {exercise = newExercise}
         in rec timeout [] (empyStateContext newPackage (stateContext state))
      _ -> rec timeout [] state
 where
   pkg = exercisePkg state
   ex  = exercise pkg
   timeout = 50 :: Int
 
   rec i acc st = 
      case onefirst st of
         Nothing           -> return (reverse acc)
         Just (r, _, next)
            |  i <= 0      -> fail msg
            | otherwise    -> rec (i-1) ((r, stateContext next) : acc) next
    where
      msg = "Time out after " ++ show timeout ++ " steps. " ++ 
            concatMap f (reverse acc)
      f (r, c) = let s = maybe "???" (prettyPrinter ex) (fromContext c)
                 in "[" ++ show r ++ "]  " ++ s ++ "; "

-- Note that we have to inspect the last step of the prefix afterwards, because
-- the remaining part of the derivation could consist of minor rules only.
allfirsts :: Monad m => State a -> m [(Rule (Context a), Location, State a)]
allfirsts state = 
   case statePrefix state of
      Nothing -> 
         fail "Prefix is required"
      Just p0 ->
         let tree = cutOnStep (stop . lastStepInPrefix) (prefixTree p0 (stateContext state))
             f (r1, _, _) (r2, _, _) = 
                ruleOrdering (exercise (exercisePkg state)) r1 r2
         in return (sortBy f (mapMaybe make (derivations tree)))
 where
   stop (Just (RuleStep r)) = isMajorRule r
   stop _ = False
   
   make d = do
      prefixEnd <- safeHead (reverse (steps d))
      termEnd   <- safeHead (reverse (terms d))
      case lastStepInPrefix prefixEnd of
         Just (RuleStep r) | isMajorRule r -> return
            ( r
            , location termEnd
            , makeState (exercisePkg state) (Just prefixEnd) termEnd
            )
         _ -> Nothing

onefirst :: Monad m => State a -> m (Rule (Context a), Location, State a)
onefirst state = do
   xs <- allfirsts state
   case xs of
      hd:_ -> return hd
      []   -> fail "No step possible"

applicable :: Location -> State a -> [Rule (Context a)]
applicable loc state =
   let p r = not (isBuggyRule r) && Apply.applicable r (setLocation loc (stateContext state))
   in filter p (ruleset (exercise (exercisePkg state)))

allapplications :: State a -> [(Rule (Context a), Location, State a)]
allapplications state = xs ++ ys
 where
   pkg = exercisePkg state
   ex  = exercise pkg
   xs  = concat (allfirsts state)
   ps  = [ (r, loc) | (r, loc, _) <- xs ]
   ys  = maybe [] f (top (stateContext state))
           
   f c = g c ++ concatMap f (allDowns c)
   g c = [ (r, location new, makeState pkg Nothing new)
         | r   <- ruleset ex
         , (r, location c) `notElem` ps
         , new <- applyAll r c
         ]

-- local helper
setLocation :: Location -> Context a -> Context a 
setLocation loc c0 = fromMaybe c0 (navigateTo loc c0)

-- Two possible scenarios: either I have a prefix and I can return a new one (i.e., still following the 
-- strategy), or I return a new term without a prefix. A final scenario is that the rule cannot be applied
-- to the current term at the given location, in which case the request is invalid.
apply :: Monad m => Rule (Context a) -> Location -> State a -> m (State a)
apply r loc state = maybe applyOff applyOn (statePrefix state)
 where
   applyOn _ = -- scenario 1: on-strategy
      maybe applyOff return $ safeHead
      [ s1 | (r1, loc1, s1) <- fromMaybe [] $ allfirsts state, showId r == showId r1, loc==loc1 ]
      
   applyOff  = -- scenario 2: off-strategy
      case Apply.apply r (setLocation loc (stateContext state)) of
         Just new -> return (makeState (exercisePkg state) Nothing new)
         Nothing  -> fail ("Cannot apply " ++ show r)
       
ready :: State a -> Bool
ready state = isReady (exercise (exercisePkg state)) (stateTerm state)

stepsremaining :: Monad m => State a -> m Int
stepsremaining = liftM length . derivation Nothing

findbuggyrules :: State a -> a -> [Rule (Context a)]
findbuggyrules state a =
   let ex      = exercise (exercisePkg state)
       buggies = filter isBuggyRule (ruleset ex)
       p r     = ruleIsRecognized ex r (stateContext state) (inContext ex a)
   in filter p buggies