eflint-3.0.0.2: src/Language/EFLINT/Interpreter.hs
{-# LANGUAGE RecordWildCards, LambdaCase #-}
module Language.EFLINT.Interpreter (Config(..), Program(..), interpreter, initialConfig, rest_disabled, rest_enabled, get_transition, context2config, make_initial_state
,OutputWriter, Output(..), getOutput
,errors, violations, ex_triggers, query_ress, missing_inputs
,convert_programs, collapse_programs) where
import Language.EFLINT.Eval
import Language.EFLINT.Spec
import Language.EFLINT.Saturation
import Language.EFLINT.StaticEval (compile_phrase, runStatic)
import Language.EFLINT.State
import Control.Monad (forM, forM_, when)
import Control.Monad.Writer (Writer, tell, runWriter)
import Control.Applicative (empty)
import qualified Data.Map as M
import qualified Data.Set as S
data Program = Program Phrase
| PSeq Program Program
| ProgramSkip
deriving (Eq, Show)
data Config = Config {
cfg_spec :: Spec
, cfg_state :: State
, rest_transitions :: [Transition] -- (label * enabled?) -- replaced
, rest_duties :: [Tagged] -- replaced after ever step
}
deriving (Eq)
data Output = ErrorVal Error
| ExecutedTransition TransInfo
| Violation Violation
| QueryRes QueryRes -- whether query succeed or not
deriving (Eq, Show, Read)
convert_programs :: [Phrase] -> [Program]
convert_programs phrases = map Program phrases
collapse_programs :: [Program] -> Program
collapse_programs [] = ProgramSkip
collapse_programs programs = (foldr1 PSeq programs)
interpreter :: (InputMap, Program) -> Config -> OutputWriter (Maybe Config)
interpreter (inpm, Program p) cfg = case runStatic (compile_phrase p) (ctx_spec ctx) of
Left err -> tell [ErrorVal (CompilationError (unlines err))] >> return Nothing
Right (spec', p') -> fmap context2config <$> sem_phrase p' inpm
(ctx{ctx_spec = spec', ctx_state = rebase_and_sat spec' (ctx_state ctx)})
where ctx = config2context cfg
interpreter (inpm, PSeq p1 p2) cfg = (interpreter (inpm,p1) cfg) >>= interpreter (inpm,p2) . maybe cfg id
interpreter (inpm, ProgramSkip) cfg = return Nothing
initialConfig Nothing = context2config (emptyContext emptySpec)
initialConfig (Just (spec,state)) = context2config $
(emptyContext spec) { ctx_state = state }
context2config :: Context -> Config
context2config ctx = Config {
cfg_spec = ctx_spec ctx
, cfg_state = ctx_state ctx
, rest_transitions = ctx_transitions ctx
, rest_duties = ctx_duties ctx
}
config2context :: Config -> Context
config2context cfg = Context {
ctx_spec = cfg_spec cfg
, ctx_state = cfg_state cfg
, ctx_transitions = []
, ctx_duties = []
}
rest_enabled = map fst . filter snd . map get_transition . rest_transitions
rest_disabled = map fst . filter (not . snd) . map get_transition . rest_transitions
get_transition :: Transition -> (Tagged, Bool)
get_transition transition = (tagged transition, exist transition)
ex_triggers :: [Output] -> [TransInfo]
ex_triggers = concatMap op
where op (ExecutedTransition out) = [out]
op _ = []
violations :: [Output] -> [Violation]
violations = concatMap op
where op (Violation v) = [v]
op _ = []
errors :: [Output] -> [Error]
errors = concatMap op
where op (ErrorVal err) = [err]
op _ = []
query_ress :: [Output] -> [QueryRes]
query_ress = concatMap op
where op (QueryRes b) = [b]
op _ = []
missing_inputs :: [Output] -> [Tagged]
missing_inputs = S.toList . S.fromList . concatMap op
where op (ErrorVal (RuntimeError (MissingInput te))) = [te]
op _ = []
type OutputWriter = Writer [Output]
getOutput :: OutputWriter a -> (a,[Output])
getOutput = runWriter
error_or_process :: M_Subs a -> Spec -> State -> InputMap -> ([a] -> OutputWriter (Maybe Context)) -> OutputWriter (Maybe Context)
error_or_process ma spec state inpm fa = case runSubs ma spec state inpm emptySubs of
Left err -> tell [ErrorVal $ RuntimeError err] >> return Nothing
Right as -> fa as
sem_phrase :: CPhrase -> InputMap -> Context -> OutputWriter (Maybe Context)
sem_phrase p inpm c0 = case p of
CPSkip -> return Nothing
CPOnlyDecls -> no_effect
CQuery t -> error_or_process (eval t) spec state inpm $ \vs -> do
let queryRes | all (== (ResBool True)) vs = QuerySuccess
| otherwise = QueryFailure
tell [QueryRes queryRes] >> no_effect
CCreate vs t -> single_effect (CAll vs t)
CTerminate vs t -> single_effect (TAll vs t)
CObfuscate vs t -> single_effect (OAll vs t)
CDo te -> error_or_process (trigger_or_fail te) spec state inpm consider_transinfos
CTrigger vs t -> let m_subs = do
tes <- foreach vs (whenTagged (eval t) return)
case tes of
(te@(_,d):_) | triggerable spec d -> Right <$> forM tes instantiate_trans
| otherwise -> return (Left d)
_ -> return (Right [])
in error_or_process m_subs spec state inpm consider_transinfos
CPDir dir -> return_context emptyStore [dir]
CSeq p q -> sem_phrase p inpm c0 >>= (sem_phrase q inpm . maybe c0 id)
where spec = ctx_spec c0
state = ctx_state c0
return_context :: Store -> [CDirective] -> OutputWriter (Maybe Context)
return_context ass dirs = do
let duties = [ te | te@(v,d) <- (state_input_holds state' inpm)
, Duty _ <- maybe [] (:[]) (fmap kind (find_decl spec' d)) ]
error_or_process (find_duty_violations duties) spec' state' inpm $ \d_viols ->
error_or_process (find_inv_violations (S.toList $ invariants spec')) spec' state' inpm $ \i_viols -> do
tell (map Violation (concat d_viols ++ concat i_viols))
error_or_process find_transitions spec' state' inpm $ \tss ->
return $ Just $ c0
{ ctx_state = state'
, ctx_spec = spec'
, ctx_transitions = concat tss
, ctx_duties = duties
}
where state' = rebase_and_sat spec (make_assignments ass state)
spec' = process_directives dirs spec
no_effect :: OutputWriter (Maybe Context)
no_effect = return_context emptyStore []
single_effect :: Effect -> OutputWriter (Maybe Context)
single_effect eff =
error_or_process (eval_effect eff) spec state inpm $ \stores ->
return_context (M.unions stores) {- always just one store-} []
trigger_or_fail :: Tagged -> M_Subs (Either DomId [TransInfo])
trigger_or_fail te@(_,d) | triggerable spec d = Right . (:[]) <$> instantiate_trans te
| otherwise = return $ Left d
consider_transinfos [Left d] = tell [ErrorVal (NotTriggerable d)] >> no_effect
consider_transinfos [Right infos] = do
forM_ infos $ \info -> do tell [ExecutedTransition info]
when (trans_is_action info && trans_forced info)
(tell [Violation (TriggerViolation info)])
return_context (store_unions (map trans_assignments infos)) []
consider_transinfos _ = error "ASSERT: consider_transinfos"
find_inv_violations :: [DomId] -> M_Subs [Violation]
find_inv_violations ds = do
spec <- get_spec
forM ds $ \d -> do
let term = Exists [Var d ""] (Present (Ref (Var d "")))
ignoreMissingInput (checkFalse (eval term))
return (InvariantViolation d)
find_duty_violations :: [Tagged] -> M_Subs [Violation]
find_duty_violations tes = do
spec <- get_spec
forM tes $ \te@(_,d) -> do
ignoreMissingInput (eval_violation_condition te (find_violation_cond spec d)) >>= \case
True -> return (DutyViolation te)
False -> empty
find_transitions :: M_Subs [Transition]
find_transitions = do
spec <- get_spec
concat <$> mapM gen_trans (trigger_decls spec)
where gen_trans (d,_) = results $ ignoreMissingInput $ do
tagged <- every_possible_subs (no_decoration d)
exist <- is_in_virtual_state tagged
return Transition{..}
every_possible_subs :: Var -> M_Subs Tagged
every_possible_subs x = do
spec <- get_spec
let d = remove_decoration spec x
(dom, _) <- get_dom d
if enumerable spec dom then generate_instances d
else every_available_subs d
where generate_instances d = do
(dom, dom_filter) <- get_dom d
e <- instantiate_domain d dom
let bindings = case (dom,e) of (Products xs, Product args) -> M.fromList (zip xs args)
_ -> M.singleton (no_decoration d) (e,d)
modify_subs (`subsUnion` bindings) (checkTrue (eval dom_filter))
return (e,d)
every_available_subs d = do
(dom, dom_filter) <- get_dom d
case dom of
Products xs -> do
args <- sequence (map every_possible_subs xs)
modify_subs (`subsUnion` (M.fromList (zip xs args))) (checkTrue (eval dom_filter))
return (Product args, d)
_ -> do
state <- get_state
input <- get_input
nd $ [ te | te@(v,d') <- state_input_holds state input, d' == d ]
make_initial_state :: Spec -> Initialiser -> State
make_initial_state spec inits = do
case runSubs (store_unions <$> mapM eval_effect inits) spec emptyState emptyInput emptySubs of
Left err -> error (print_runtime_error err)
Right res -> case res of
[] -> error "failed to initialise state"
[sto] -> rebase_and_sat spec (make_assignments sto emptyState)
_ -> error "non-deterministic state initialisation"