ideas-1.0: src/Service/BasicServices.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)
--
-----------------------------------------------------------------------------
module Service.BasicServices
( -- * Basic Services
stepsremaining, findbuggyrules, ready, allfirsts, derivation
, onefirst, applicable, allapplications, apply, generate
) where
import Common.Library hiding (derivation, applicable, apply, ready)
import Common.Utils (fst3, safeHead)
import Data.List
import Data.Maybe
import Service.State
import System.Random (StdGen)
import qualified Common.Classes as Apply
generate :: StdGen -> Exercise a -> Difficulty -> State a
generate rng ex dif =
emptyState ex (randomTermWith rng dif ex)
-- TODO: add a location to each step
derivation :: Maybe StrategyConfiguration -> State a -> Either String (Derivation (Rule (Context a), ArgValues) (Context a))
derivation mcfg state =
mapSecond (biMap (\(r, _, as) -> (r, as)) stateContext) $
case (statePrefix state, mcfg) of
(Nothing, _) -> Left "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}
in rec timeout d0 (empyStateContext newExercise (stateContext state))
_ -> rec timeout d0 state
where
d0 = emptyDerivation state
ex = exercise state
timeout = 50 :: Int
rec i acc st =
case onefirst st of
Left _ -> Right acc
Right (r, l, as, next)
| i <= 0 -> Left msg
| otherwise -> rec (i-1) (acc `extend` ((r, l, as), next)) next
where
msg = "Time out after " ++ show timeout ++ " steps. " ++
show (biMap fst3 (prettyPrinterContext ex . stateContext) acc)
-- 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 :: State a -> Either String [(Rule (Context a), Location, ArgValues, State a)]
allfirsts state =
case statePrefix state of
Nothing ->
Left "Prefix is required"
Just p0 ->
let tree = cutOnStep (stop . lastStepInPrefix) (prefixTree p0 (stateContext state))
f (r1, _, _, _) (r2, _, _, _) =
ruleOrdering (exercise state) r1 r2
in Right $ noDuplicates $ sortBy f $ mapMaybe make $ derivations tree
where
stop (Just (RuleStep r)) = isMajorRule r
stop _ = False
make d = do
prefixEnd <- lastStep d
let ca = lastTerm (withoutLast d)
case lastStepInPrefix prefixEnd of
Just (RuleStep r) | isMajorRule r -> return
( r
, location (lastTerm d)
, fromMaybe [] (expectedArguments r ca)
, makeState (exercise state) (Just prefixEnd) (lastTerm d)
)
_ -> Nothing
noDuplicates [] = []
noDuplicates (x:xs) = x : noDuplicates (filter (not . eq x) xs)
eq (r1, l1, a1, s1) (r2, l2, a2, s2) =
r1==r2 && l1==l2 && a1==a2 && exercise s1 == exercise s2
&& similarity (exercise s1) (stateContext s1) (stateContext s2)
onefirst :: State a -> Either String (Rule (Context a), Location, ArgValues, State a)
onefirst state =
case allfirsts state of
Right [] -> Left "No step possible"
Right (hd:_) -> Right hd
Left msg -> Left msg
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 state))
allapplications :: State a -> [(Rule (Context a), Location, State a)]
allapplications state = sortBy cmp (xs ++ ys)
where
ex = exercise state
xs = either (const []) (map (\(r, l, _, s) -> (r, l, s))) (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 ex Nothing new)
| r <- ruleset ex
, (r, location c) `notElem` ps
, new <- applyAll r c
]
cmp (r1, loc1, _) (r2, loc2, _) =
case ruleOrdering ex r1 r2 of
EQ -> loc1 `compare` loc2
this -> this
-- 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 :: Rule (Context a) -> Location -> State a -> Either String (State a)
apply r loc state = maybe applyOff applyOn (statePrefix state)
where
applyOn _ = -- scenario 1: on-strategy
maybe applyOff Right $ safeHead
[ s1 | Right xs <- [allfirsts state], (r1, loc1, _, s1) <- xs, r==r1, loc==loc1 ]
applyOff = -- scenario 2: off-strategy
case Apply.apply r (setLocation loc (stateContext state)) of
Just new -> Right (makeState (exercise state) Nothing new)
Nothing -> Left ("Cannot apply " ++ show r)
ready :: State a -> Bool
ready state = isReady (exercise state) (stateTerm state)
stepsremaining :: State a -> Either String Int
stepsremaining = mapSecond derivationLength . derivation Nothing
findbuggyrules :: State a -> a -> [(Rule (Context a), Location, ArgValues)]
findbuggyrules state a =
[ (r, loc, as)
| r <- filter isBuggyRule (ruleset ex)
, (loc, as) <- recognizeRule ex r (stateContext state) (inContext ex a)
]
where
ex = exercise state