Nomyx-Core-0.7.3: src/Nomyx/Core/Engine/EventEval.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
-- | Evaluation of the events
module Nomyx.Core.Engine.EventEval where
import Prelude hiding ((.), log)
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Data.List
import Data.Typeable
import Data.Lens
import Data.Maybe
import Data.Todo
import Data.Either
import Data.Function (on)
import Control.Category hiding (id)
import Control.Applicative
import Control.Monad.Error.Class (MonadError(..))
import Language.Nomyx.Expression
import Nomyx.Core.Engine.Types hiding (_vRuleNumber)
import Nomyx.Core.Engine.EvalUtils
import Nomyx.Core.Engine.Utils
import Safe
-- * Event triggers
-- trigger an event
triggerEvent :: (Typeable e, Show e) => Signal e -> e -> Evaluate ()
triggerEvent s dat = do
evs <- access (eGame >>> events)
triggerEvent' (SignalData s dat) Nothing evs
-- trigger some specific signal
triggerEvent' :: SignalData -> Maybe SignalAddress -> [EventInfo] -> Evaluate ()
triggerEvent' sd msa evs = do
let evs' = sortBy (compare `on` _ruleNumber) evs
eids <- mapM (liftEval . (getUpdatedEventInfo sd msa)) evs' -- get all the EventInfos updated with the field
(eGame >>> events) %= union (map fst eids) -- store them
void $ mapM triggerIfComplete eids -- trigger the handlers for completed events
-- if the event is complete, trigger its handler
triggerIfComplete :: (EventInfo, Maybe SomeData) -> Evaluate ()
triggerIfComplete (EventInfo en rn _ h SActive _, Just (SomeData val)) = case (cast val) of
Just a -> do
evalNomex <- gets evalNomexFunc
void $ withRN rn $ (evalNomex $ h (en, a)) `catchError` (errorHandler en)
Nothing -> error "Bad trigger data type"
triggerIfComplete _ = return ()
-- get update the EventInfo updated with the signal data.
-- get the event result if all signals are completed
getUpdatedEventInfo :: SignalData -> Maybe SignalAddress -> EventInfo -> EvaluateNE (EventInfo, Maybe SomeData)
getUpdatedEventInfo sd@(SignalData signal _) addr ei@(EventInfo _ _ ev _ _ envi) = do
trs <- getEventResult ev envi
case trs of
Todo rs -> case find (\(sa, ss) -> (ss == SomeSignal signal) && maybe True (==sa) addr) rs of -- check if our signal match one of the remaining signals
Just (sa, _) -> do
let envi' = (SignalOccurence sd sa) : envi
er <- getEventResult ev envi' -- add our event to the environment and get the result
return $ case er of
Todo _ -> (env ^= envi' $ ei, Nothing) -- some other signals are left to complete: add ours in the environment
Done a -> (env ^= [] $ ei, Just $ SomeData a) -- event complete: return the final data result
Nothing -> return (ei, Nothing) -- our signal does not belong to this event.
Done a -> return (env ^= [] $ ei, Just $ SomeData a)
--get the signals left to be completed in an event
getRemainingSignals' :: EventInfo -> EvaluateNE [(SignalAddress, SomeSignal)]
getRemainingSignals' (EventInfo _ _ e _ _ env) = do
tr <- getEventResult e env
return $ case tr of
Done _ -> []
Todo a -> a
-- compute the result of an event given an environment.
-- in the case the event cannot be computed because some signals results are pending, return that list instead.
getEventResult :: Event a -> [SignalOccurence] -> EvaluateNE (Todo (SignalAddress, SomeSignal) a)
getEventResult e frs = getEventResult' e frs []
-- compute the result of an event given an environment. The third argument is used to know where we are in the event tree.
getEventResult' :: Event a -> [SignalOccurence] -> SignalAddress -> EvaluateNE (Todo (SignalAddress, SomeSignal) a)
getEventResult' (PureEvent a) _ _ = return $ Done a
getEventResult' EmptyEvent _ _ = return $ Todo []
getEventResult' (SumEvent a b) ers fa = liftM2 (<|>) (getEventResult' a ers (fa ++ [SumL])) (getEventResult' b ers (fa ++ [SumR]))
getEventResult' (AppEvent f b) ers fa = liftM2 (<*>) (getEventResult' f ers (fa ++ [AppL])) (getEventResult' b ers (fa ++ [AppR]))
getEventResult' (LiftEvent a) _ _ = do
evalNomexNE <- asks evalNomexNEFunc
r <- evalNomexNE a
return $ Done r
getEventResult' (BindEvent a f) ers fa = do
er <- getEventResult' a ers (fa ++ [BindL])
case er of
Done a' -> getEventResult' (f a') ers (fa ++ [BindR])
Todo bs -> return $ Todo bs
getEventResult' (SignalEvent a) ers fa = return $ case lookupSignal a fa ers of
Just r -> Done r
Nothing -> Todo [(fa, SomeSignal a)]
getEventResult' (ShortcutEvents es f) ers fa = do
(ers :: [Todo (SignalAddress, SomeSignal) a]) <- mapM (\e -> getEventResult' e ers (fa ++ [Shortcut])) es -- get the result for each event in the list
return $ case f (toMaybe <$> ers) of -- apply f to the event results that we already have
True -> Done $ toMaybe <$> ers -- if the result is true, we are done. Return the list of maybe results
False -> Todo $ join $ lefts $ toEither <$> ers -- otherwise, return the list of remaining fields to complete from each event
-- * Input triggers
-- trigger the input form with the input data
triggerInput :: FormField -> InputData -> SignalAddress -> EventNumber -> Evaluate ()
triggerInput ff id sa en = do
evs <- access (eGame >>> events)
let mei = find ((== en) . getL eventNumber) evs
when (isJust mei) $ triggerInputSignal id sa ff (fromJust mei)
-- trigger the input signal with the input data
triggerInputSignal :: InputData -> SignalAddress -> FormField -> EventInfo -> Evaluate ()
triggerInputSignal id sa ff ei@(EventInfo _ _ _ _ SActive _) = do
i <- liftEval $ findField ff sa ei
case i of
Just sf -> triggerInputSignal' id sf sa ei
Nothing -> logAll $ "Input not found, InputData=" ++ (show id) ++ " SignalAddress=" ++ (show sa) ++ " FormField=" ++ (show ff)
triggerInputSignal _ _ _ _ = return ()
-- trigger the input signal with the input data
triggerInputSignal' :: InputData -> SomeSignal -> SignalAddress -> EventInfo -> Evaluate ()
triggerInputSignal' (TextData s) (SomeSignal e@(Input _ _ (Text))) sa ei = triggerEvent' (SignalData e s) (Just sa) [ei]
triggerInputSignal' (TextAreaData s) (SomeSignal e@(Input _ _ (TextArea))) sa ei = triggerEvent' (SignalData e s) (Just sa) [ei]
triggerInputSignal' (ButtonData) (SomeSignal e@(Input _ _ (Button))) sa ei = triggerEvent' (SignalData e ()) (Just sa) [ei]
triggerInputSignal' (RadioData i) (SomeSignal e@(Input _ _ (Radio cs))) sa ei = triggerEvent' (SignalData e (fst $ cs!!i)) (Just sa) [ei]
triggerInputSignal' (CheckboxData is) (SomeSignal e@(Input _ _ (Checkbox cs))) sa ei = triggerEvent' (SignalData e (fst <$> cs `sel` is)) (Just sa) [ei]
triggerInputSignal' _ _ _ _ = return ()
-- | Get the form field at a certain address
findField :: FormField -> SignalAddress -> EventInfo -> EvaluateNE (Maybe SomeSignal)
findField ff addr (EventInfo _ _ e _ _ env) = findField' addr e env ff
findField' :: SignalAddress -> Event e -> [SignalOccurence] -> FormField -> EvaluateNE (Maybe SomeSignal)
findField' [] (SignalEvent f) _ ff = return $ do
ff' <- getFormField (SomeSignal f)
guard (ff' == ff)
return $ SomeSignal f
findField' (SumL:as) (SumEvent e1 _) env ff = findField' as e1 (filterPath SumL env) ff
findField' (SumR:as) (SumEvent _ e2) env ff = findField' as e2 (filterPath SumR env) ff
findField' (AppL:as) (AppEvent e1 _) env ff = findField' as e1 (filterPath AppL env) ff
findField' (AppR:as) (AppEvent _ e2) env ff = findField' as e2 (filterPath AppR env) ff
findField' (BindL:as) (BindEvent e1 _) env ff = findField' as e1 (filterPath BindL env) ff
findField' (BindR:as) (BindEvent e1 f) env ff = do
ter <- getEventResult e1 (filterPath BindL env)
case ter of
Done e2 -> findField' as (f e2) (filterPath BindR env) ff
Todo _ -> return $ Nothing
findField' (Shortcut:as) (ShortcutEvents es _) env ff = do
msfs <- mapM (\e-> findField' as e env ff) es
return $ headMay $ catMaybes msfs -- returning the first field that matches
findField' fa _ _ _ = error $ "findField: wrong field address: " ++ (show fa)
-- | removes one element of signal address for all signal occurences
filterPath :: SignalAddressElem -> [SignalOccurence] -> [SignalOccurence]
filterPath fa env = mapMaybe f env where
f (SignalOccurence sd (fa':fas)) | fa == fa' = Just $ SignalOccurence sd fas
f fr = Just fr
getFormField :: SomeSignal -> Maybe FormField
getFormField (SomeSignal (Input pn s (Radio choices))) = Just $ RadioField pn s (zip [0..] (snd <$> choices))
getFormField (SomeSignal (Input pn s Text)) = Just $ TextField pn s
getFormField (SomeSignal (Input pn s TextArea)) = Just $ TextAreaField pn s
getFormField (SomeSignal (Input pn s Button)) = Just $ ButtonField pn s
getFormField (SomeSignal (Input pn s (Checkbox choices))) = Just $ CheckboxField pn s (zip [0..] (snd <$> choices))
getFormField _ = Nothing