packages feed

ideas-1.0: src/Service/FeedbackScript/Run.hs

-----------------------------------------------------------------------------
-- Copyright 2011, 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)
--
-- Run a feedbackscript
--
-----------------------------------------------------------------------------
module Service.FeedbackScript.Run
   ( Script
   , Environment(..), newEnvironment
   , feedbackDiagnosis, feedbackHint
   , ruleToString, feedbackIds, attributeIds, conditionIds
   ) where

import Common.Library hiding (ready, Environment)
import Common.Utils (safeHead)
import Control.Monad
import Data.List
import Data.Maybe
import Service.BasicServices
import Service.Diagnose
import Service.FeedbackScript.Syntax
import Service.State

data Environment a = Env
   { oldReady   :: Bool
   , expected   :: Maybe (Rule (Context a))
   , recognized :: Maybe (Rule (Context a))
   , diffPair   :: Maybe (String, String)
   , before     :: Maybe Term
   , after      :: Maybe Term
   , afterText  :: Maybe String
   }

newEnvironment :: State a -> Environment a
newEnvironment st = Env
   { oldReady   = ready st
   , expected   = fmap fst4 next
   , recognized = Nothing
   , diffPair   = Nothing
   , before     = f st
   , after      = liftM fth4 next >>= f
   , afterText  = liftM fth4 next >>= g
   }
 where
   next = either (const Nothing) Just (onefirst st)
   f s  = fmap (`build` stateTerm s) (hasTermView (exercise s))
   g s  = return $ prettyPrinter (exercise s) (stateTerm s)
   fst4 (a, _, _, _) = a
   fth4 (_, _, _, a) = a

toText :: Environment a -> Script -> Text -> Maybe Text
toText env script = eval env script . Right

ruleToString :: Environment a -> Script -> Rule b -> String
ruleToString env script r =
   let f = eval env script . Left . getId
   in maybe (showId r) show (f r)

eval :: Environment a -> Script -> Either Id Text -> Maybe Text
eval env script = either (return . findIdRef) evalText
 where
   evalText :: Text -> Maybe Text
   evalText = liftM mconcat . mapM unref . textItems
    where
      unref (TextRef a)
         | a == expectedId   = fmap (findIdRef . getId) (expected env)
         | a == recognizedId = fmap (findIdRef . getId) (recognized env)
         | a == diffbeforeId = fmap (TextString . fst) (diffPair env)
         | a == diffafterId  = fmap (TextString . snd) (diffPair env)
         | a == beforeId     = fmap TextTerm (before env)
         | a == afterId      = fmap TextTerm (after env)
         | a == afterTextId  = fmap TextString (afterText env)
         | otherwise         = findRef (==a)
      unref t = Just t

   evalBool :: Condition -> Bool
   evalBool (RecognizedIs a) = maybe False (eqId a . getId) (recognized env)
   evalBool (CondNot c)      = not (evalBool c)
   evalBool (CondConst b)    = b
   evalBool (CondRef a)
      | a == oldreadyId    = oldReady env
      | a == hasexpectedId = isJust (expected env)
      | otherwise          = False

   namespaces = nub $ mempty : [ a | NameSpace as <- scriptDecls script, a <- as ]

   -- equality with namespaces
   eqId :: Id -> Id -> Bool
   eqId a b = any (\n -> n#a == b) namespaces

   findIdRef :: Id -> Text
   findIdRef x = fromMaybe (TextString (showId x)) (findRef (`eqId` x))

   findRef :: (Id -> Bool) -> Maybe Text
   findRef p = safeHead $ catMaybes
      [ evalText t
      | (as, c, t) <- allDecls
      , any p as && evalBool c
      ]

   allDecls =
      let f (Simple _ as t)   = [ (as, CondConst True, t) ]
          f (Guarded _ as xs) = [ (as, c, t) | (c, t) <- xs ]
          f _ = []
      in concatMap f (scriptDecls script)

feedbackDiagnosis :: Diagnosis a -> Environment a -> Script -> Text
feedbackDiagnosis diagnosis env =
   fromMaybe (TextString "ERROR") .
   case diagnosis of
      Buggy _ r      -> make "buggy"   env {recognized = Just r}
      NotEquivalent  -> make "noteq"   env
      Expected _ _ r -> make "ok"      env {recognized = Just r}
      Similar _ _    -> make "same"    env
      Detour _ _ _ r -> make "detour"  env {recognized = Just r}
      Correct _ _    -> make "unknown" env

feedbackHint :: Bool -> Environment a -> Script -> Text
feedbackHint b env script =
   fromMaybe (defaultHint env script) $
   make (if b then "hint" else "step") env script

defaultHint :: Environment a -> Script -> Text
defaultHint env script = makeText $
   case expected env of
      Just r  -> ruleToString env script r
      Nothing -> "Sorry, not hint available."

make :: String -> Environment a -> Script -> Maybe Text
make s env script = toText env script (TextRef (newId s))

feedbackIds :: [Id]
feedbackIds = map newId
   ["same", "noteq", "unknown", "ok", "buggy", "detour", "hint", "step"]

attributeIds :: [Id]
attributeIds =
   [expectedId, recognizedId, diffbeforeId, diffafterId, beforeId, afterId, afterTextId]

conditionIds :: [Id]
conditionIds = [oldreadyId, hasexpectedId]

expectedId, recognizedId, diffbeforeId, diffafterId, beforeId, afterId, afterTextId :: Id
expectedId   = newId "expected"
recognizedId = newId "recognized"
diffbeforeId = newId "diffbefore"
diffafterId  = newId "diffafter"
beforeId     = newId "before"
afterId      = newId "after"
afterTextId  = newId "aftertext"

oldreadyId, hasexpectedId :: Id
oldreadyId    = newId "oldready"
hasexpectedId = newId "hasexpected"