packages feed

cpsa-4.4.3: src/CPSA/GenRules.hs

-- Generates rules when loading protocols from S-Expressions.

-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.

module CPSA.GenRules where

import qualified Data.List as L
-- import CPSA.Lib.SExpr
import CPSA.Signature (Sig)
import CPSA.Algebra
import CPSA.Channel
import CPSA.Protocol
import CPSA.LoadFormulas

{--
import System.IO.Unsafe

z :: Show a => a -> b -> b
z x y = unsafePerformIO (print x >> return y)

zz :: Show a => a -> a
zz x = z x x

zb :: Show a => a -> Bool -> Bool
zb a False = z a False
zb _ b = b

zn :: Show a => a -> Maybe b -> Maybe b
zn x Nothing = z x Nothing
zn _ y = y

zf :: Show a => a -> Bool -> Bool
zf x False = z x False
zf _ y = y

zt :: Show a => a -> Bool -> Bool
zt x True = z x True
zt _ y = y

zl :: Show a => [a] -> [a]
zl a = z (length a) a

--}

-- Protocol Rules

type Conjunction = [AForm]

conjunctionOfConj :: Conj -> Conjunction
conjunctionOfConj = map snd

-- a Renamer is a function that will replace variables within the
-- AForm that should be regarded as universally bound with the
-- variable that will be chosen for the universal quantifier.

-- The loader will in fact read these variables as occurrences of the
-- role parameters
type Renamer = AForm -> AForm

type Conjunctor = Conjunction -> Renamer -> Conjunction
-- Function which, given Conjunction and a fn to plug in new
-- variables, plugs them in to yield a conjunction

-- The loader ensures that when an existential formula's body is
-- loaded, anything that can be read as a local, existentially bound
-- variable will be.  Since these are generated immediately before the
-- body is read, there can be no occurrences of the role parameters
-- present in the body.  Likewise, there can be no occurrences of the
-- local, existentially bound variables outside this body.

-- Hence a renamer for the antecedent may be safely applied here,
-- without capturing variables that should be locally bound.

type Existor = Conjunctor

-- Used to say:   -- Function which, given new
                                        -- variables to bind
                                        -- existentially, plugs them
                                        -- in to yield a conjunctor

ruleOfClauses :: Sig -> Gen -> String ->
                 [Term] ->  -- VarListSpec ->
                 Conjunction ->
                 [([Term],Conjunction)] -> (Gen,Rule)
ruleOfClauses _ g rn fvs antecedent evarDisjuncts = -- sig is vacuous
    let (g', env, uvars) = renamerAndNewVars fvs g in
    let disjuncts =
            map
            (\(evars,c) -> (evars,
                            (map (instantiateAForm env) c)))
            evarDisjuncts in
    (g',
      (Rule { rlname = rn,
              rlgoal =
                  (Goal
                   { uvars = uvars,
                     antec = map (instantiateAForm env) antecedent,
                     consq = disjuncts,
                     concl = map snd disjuncts}),
              rlcomment = [] }))

applyToSoleEntry :: (a -> b) ->  String -> [a] -> b
applyToSoleEntry f _ [a] = f a
applyToSoleEntry _ s _ = error s

applyToThreeEntries :: (a -> a -> a -> b) -> String -> [a] -> b
applyToThreeEntries f _ [a1,a2,a3] = f a1 a2 a3
applyToThreeEntries _ s _ = error s

applyToStrandVarAndParams :: (a -> [a] -> b) -> [a] -> String -> b
applyToStrandVarAndParams _ [] s = error s
applyToStrandVarAndParams f (a : rest) _ = f a rest

-- foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b

neqRules :: Sig -> Gen -> (Gen, [Rule])
neqRules sig g =
    foldr
     (\sortName (g,rs) ->
          let (g', v) = newVar sig g "x" sortName in
          let (g'', r) =
                  (ruleOfClauses sig g' ("neqRl_" ++ sortName)
                   [v]
                   [(AFact "neq" [v,v])]
                   [])       -- false conclusion
          in
            (g'', r : rs))
     (g,[])
     ["indx", "strd", "mesg"]

transRls :: Sig -> Gen -> Role -> [(Int,Int)] -> (Gen, [Rule])
transRls sig g rl =
    L.foldl
     (\(g, rs) pair ->
          let (g', r) = f g pair in
          (g', r : rs))
     (g, [])
     where
       f g (i,j) =
           let (g', z) = newVar sig g "z" "strd" in
           ruleOfClauses sig g' ("trRl_" ++ (rname rl) ++ "-at-" ++ (show i))
             [z]
             [(Length rl z (indxOfInt (j+1)))]
             [                  --one disjunct
              ([],              -- no existentially bound vars
               [(Trans (z, (indxOfInt i)))] -- one conjunct
               )]

lastRecvInCS :: Role -> Int -> Int -> Int
lastRecvInCS rl start end =
    loop start $ drop (start+1) $ rtrace rl
    -- i is always the index before the index of the first entry still
    -- in c.
    where
      loop i ((In (ChMsg ch _)) : c)
           | i < end && isLocn ch       = loop (i+1) c
           | otherwise                  = i
      loop i _                          = i

csRules :: Sig -> Gen -> Role -> [(Int,Int)] -> (Gen, [Rule])
csRules sig g rl =
    L.foldl
     (\(g, rs) (start,end) ->
          (let (g', csRules) = f g start end in
           (g', csRules ++ rs)))
     (g,[])
     where
       f g start end =
           let lastRecv = lastRecvInCS rl start end in
           let (g',rs) = foldr (\ind (g,soFar) ->
                                    let (g',r) = causeRule g rl start ind in
                                    (g', (r : soFar)))
                         (g,[])
                         [start+1..lastRecv] in
           foldr (\ind (g,soFar) ->
                      let (g',r) = effectRule g rl end ind in
                      (g', (r : soFar)))
             (g',rs)
             [lastRecv+1..end-1]

       causeRule g rl start ind =
           let (g',z) = newVar sig g "z" "strd" in
           let (g'', z1) = newVar sig g' "z1" "strd" in
           let (g''', i) = newVar sig g'' "i" "indx" in

           ruleOfClauses sig g'''
             ("cau-" ++ (rname rl) ++ "-" ++ (show ind))
             [z, z1, i]
             [(Length rl z (indxOfInt (ind+1))),
              (Prec (z1,i) (z, (indxOfInt ind)))]

             [([],              -- either z = z1
               [Equals z z1]),
              ([],              -- or z1's i node comes before start
                                -- of critical section
               [(Prec (z1,i) (z,(indxOfInt start)))])]

       effectRule g rl end ind =
           let (g',z) = newVar sig g "z" "strd" in
           let (g'', z1) = newVar sig g' "z1" "strd" in
           let (g''', i) = newVar sig g'' "i" "indx" in

           ruleOfClauses sig g'''
             ("eff-" ++ (rname rl) ++ "-" ++ (show ind))
             [z, z1, i]
             [(Length rl z (indxOfInt (ind+1))),
              (Prec (z, (indxOfInt ind)) (z1,i))]
             [([],              -- either z = z1
               [Equals z z1]),
              ([],              -- or z is long and z1's i node comes
                                -- after end of critical section
               [(Length rl z (indxOfInt (end+1))),
                (Prec (z, (indxOfInt end)) (z1,i))])]

data FoundAt = FoundAt Int
             | Missing Term

-- Return the smallest height in the trace of rl at which all of the
-- vars are found to have occurred, if found, or a missing var if no
-- such height.
varsUsedHeight :: Role -> [Term] -> FoundAt
varsUsedHeight rl vars =
    loop 0 vars
    where
      occ = flip firstOccurs rl -- return *index*
      loop i [] = FoundAt (1+i) -- convert to height
      loop i (v : rest) =
          case occ v of
            Nothing -> Missing v
            Just j -> loop (max i j) rest

boundVarNamesOfVarListSpec :: VarListSpec -> [String]
boundVarNamesOfVarListSpec [] = []
boundVarNamesOfVarListSpec ((_,names) : rest) =
    L.nub $ names ++ boundVarNamesOfVarListSpec rest

{--
freeVarsInExistential :: ([Term],Existor) -> [Term]
freeVarsInExistential (vars,c) =
    let bvns = map varName vars in
    concatMap
    (\a -> filter (not . (flip elem bvns) . varName)
           (aFreeVars [] a))
    (c [])

freeVarsInDisjunction :: [([Term],Existor)] -> [Term]
freeVarsInDisjunction vcs =
    L.nub
     (foldr (\vc acc -> (freeVarsInExistential vc) ++ acc)
      []
      vcs)
--}

freeVarsInConjLists :: [([Term], Conj)] -> [Term]
freeVarsInConjLists [] = []
freeVarsInConjLists ((vars,conj) : rest) =
    ((foldr (\(_,aForm) soFar -> aFreeVars soFar aForm)
                []
                conj)
     L.\\ vars)
    ++ (freeVarsInConjLists rest)

-- Take the subset of fvars that have a name shared with some member
-- of pvars
freeVarsSubsetByName :: [Term] -> [Term] -> [Term]
freeVarsSubsetByName fvars pvars =
    loop fvars []
    where
      pvarNames = map varName pvars
      loop [] soFar = reverse soFar
      loop (fv : rest) soFar
          | (varName fv) `elem` pvarNames =
              loop rest (fv : soFar)
          | otherwise =
              loop rest soFar

--   freeVarsNamedIn [] pvars = []
--   freeVarsNamedIn (fv : rest) pvars =
--       if (varName fv) `elem` (map varName pvars)
--       then fv : freeVarsNamedIn rest pvars
--       else freeVarsNamedIn rest pvars

conclHeight :: Role -> [([Term], Conj)] -> FoundAt
conclHeight _ [] = FoundAt 1
conclHeight rl (disj : rest) =
    case conclHeight rl rest of
      Missing v -> Missing v
      FoundAt j ->
          case varsUsedHeight rl $ freeVarsInConjLists [disj] of
            Missing v -> Missing v
            FoundAt i -> FoundAt (max i j)

renameApart :: String -> [Term] -> String
renameApart prefix vars =
    if not (prefix `elem` vns) then prefix
    else
        loop 0
    where
      vns = map varName vars
      loop :: Int -> String
      loop i =
          let candidate = prefix ++ "-" ++ (show i) in
          if not (candidate `elem` vns) then candidate
          else
              loop (i+1)

{-

Sig -> Gen -> String -> [Term] ->
Conjunction -> [([Term],Conjunction)] -> (Gen,Rule)

--}

ruleOfDisjAtHeight :: Sig -> Gen -> Role -> String -> [([Term],[AForm])] -> Int -> (Gen, Rule)
ruleOfDisjAtHeight sig g rl rulename disj ht =
    let fvs = fvsConsq disj in
    let rvs = L.filter (\t -> L.elem t (rvars rl)) fvs in
    let (g',z) = newVar sig g "z" "strd" in
    (ruleOfClauses
     sig g' rulename
     (fvs ++ [z])
     ((Length rl z (indxOfInt ht)) :
      (map
       (\v ->
            case firstOccurs v rl of
              Nothing -> errorWithMsg v " not found."
              Just i ->
                  if i < ht
                  then (Param rl v (i+1) z v)
                  else errorWithMsg v
                           (" introduced for " ++ (varName v) ++ " too high in role " ++
                            (rname rl) ++": " ++ (show i) ++
                            " not below " ++ (show ht) ++ "in " ++ rulename ++ "."))
       rvs))
     disj)
    where
      errorWithMsg v tail =
          error ("ruleOfDisjAtHeight:  Parameter " ++
                 (varName v) ++ tail)

genOneAssumeRl :: Sig -> Gen -> Role -> Int -> [([Term], Conj)] -> (Gen, Rule)
genOneAssumeRl sig g rl n disjs =
    case conclHeight rl disjs of
      Missing v -> error ("genOneAssumeRl:  Variable not in role " ++ (rname rl)
                         ++ ": " ++ (show v))
      FoundAt ht ->
          let disjuncts = map (\(vs,cs) -> (vs, map snd cs)) disjs in
          ruleOfDisjAtHeight
          sig g rl ("assume-" ++ (rname rl) ++ "-" ++ (show n))
              disjuncts
              ht

genAssumeRls :: Sig -> Gen -> Role -> [[([Term], Conj)]] -> (Gen, [Rule])
genAssumeRls sig g rl disjs =
    (g',rls)
    where
      (g',rls,_) =
          foldr (\ds (g, rs, n) ->
                       (let (g', new_rule) = genOneAssumeRl sig g rl n ds in
                        (g', new_rule : rs, n+1)))
               (g, [], (0 :: Int))
               disjs

genOneRelyGuarRl :: Sig -> Gen -> Role -> Int -> String -> [([Term], Conj)] -> (Gen, Rule)
genOneRelyGuarRl sig g rl ht kind disjs =
    case conclHeight rl disjs of
      Missing v -> error ("genOneRelyGuarRl:  Variable not in role " ++ (rname rl)
                         ++ ": " ++ (show (varName v)))
      FoundAt fndHt
          | fndHt <= ht ->
              let disjuncts = map (\(vs,cs) -> (vs, map snd cs)) disjs in
              (ruleOfDisjAtHeight
               sig g rl (kind ++ "-" ++ (rname rl) ++ "-" ++ (show ht))
               disjuncts ht)
        | otherwise -> error ("genOneRelyGuarRl:  Variable found above ht " ++ (show ht) ++
                              " in " ++ (rname rl))

genStateRls :: Sig -> Gen -> Role -> [Term] -> (Gen, [Rule])
genStateRls sig g rl ts =
    (g',rls)
    where
      (g',rls,_) =
          foldr (\t (g, rs, n) ->
                       (let (g', new_rule) = f g t n in
                        (g', new_rule : rs, n+1)))
               (g, [], (0 :: Int))
               ts

      -- vSpec t = ("strd", ["z"]) : varListSpecOfVars (varsInTerm t)

      f g t n =
          case varsUsedHeight rl (varsInTerm t) of
            Missing v ->
                error ("genStateRls:  In gen-st of "
                       ++ (rname rl) ++ ": no occurrence of "
                              ++ (show (displayTerm (addToContext emptyContext [t]) v)))
            FoundAt ht ->
                (ruleOfDisjAtHeight
                 sig g rl ("gen-st-" ++ (rname rl) ++ "-" ++ (show n))
                 [ -- One disjunct, no existentially bound variables
                   ([],
                   -- One conjunct:
                    [GenStV t])]
                 ht)

genFactRls :: Sig -> Gen -> Role -> [(String,[Term])] -> (Gen, [Rule])
genFactRls sig g rl predarglists =
        (g',rls)
    where
      (g',rls,_) =
          foldr (\(pred,args) (g, rs, n) ->
                     (let (g', new_rule) = f g pred args n in
                      (g', new_rule : rs, n+1)))
                    (g, [], (0 :: Int))
                    predarglists

      -- vSpec t = ("strd", ["z"]) : varListSpecOfVars (varsInTerm t)

      f g pred args n =
          case varsUsedHeight rl (concatMap varsInTerm args) of
            Missing v ->
                error ("genFactRls:  In fact of "
                       ++ (rname rl) ++ ": no occurrence of "
                              ++ (show $ varName v))
            FoundAt ht ->
                (ruleOfDisjAtHeight
                 sig g rl ("fact-" ++ (rname rl) ++ "-" ++ pred ++ (show n))
                 [ -- One disjunct, no existentially bound variables
                   ([],
                   -- One conjunct:
                    [AFact pred args])]
                 ht)

      {--
    (g',rls)
    where
      (g',rls,_) =
          foldr (\(pred,args) (g, rs, n) ->
                       (let (g', new_rule) = f g pred args n in
                        (g', new_rule : rs, n+1)))
               (g, [], (0 :: Integer))
               predarglists

      vSpec args = ("strd", ["z"]) : varListSpecOfVars (varsInArgs args)

      varsInArgs = concatMap varsInTerm

      f g pred args n =
          case varsUsedHeight rl (varsInArgs args) of
            Missing v -> error ("genFactRls:  Unbound variable " ++ (show v)
                               ++ " in fact of " ++ (rname rl) ++ ": "
                               ++ pred ++ (show args))
            FoundAt ht ->
                ruleOfClauses sig g
                  ("fact-" ++ (rname rl) ++ "-" ++ pred ++ (show n))
                  (vSpec args)
                  (\vars ->
                       applyToStrandVarAndParams
                       (\z pvars ->
                            (Length rl z (indxOfInt ht))
                            : (map
                               (\v ->
                                    case paramOfName (varName v) rl of
                                      Nothing -> error ("genFactRls:  Parameter " ++
                                                        (varName v) ++ " not found.")
                                      Just p ->
                                          case firstOccurs p rl of
                                            Nothing -> error ("genFactRls:  Parameter " ++
                                                              (varName v) ++ " not found.")
                                            Just i -> (Param rl p (i+1) z v))
                              pvars))
                       vars
                       "genFactRls:  vars not strand+params?")
                  [([],
                    (\_ vars ->
                         applyToStrandVarAndParams
                         (\_ pvars ->
                              case envsRoleParams rl g [] pvars of
                                [(_,e)] -> [AFact pred (map (instantiate e) args)]
                                _ -> error "genFactRls:  Non-unary matching not implemented")
                         vars
                         "genFactRls:  vars not strand+params?"))]
--}

theVacuousRule :: Rule
theVacuousRule =
    (Rule { rlname = "vacuity",
            rlgoal = Goal {uvars =  [],
                           antec = [],
                           consq = [([], [])], -- no bvs, no conjuncts
                           concl = [[]]},
            rlcomment = [] })

scissorsRule :: Sig -> Gen -> (Gen, Rule)
scissorsRule sig g =
    case sortedVarsOfNames sig g "strd" ["z0","z1","z2"] of
      (g, [z0,z1,z2]) ->
          case sortedVarsOfNames sig g "indx" ["i0","i1","i2"] of
            (g, [i0,i1,i2]) ->
                (g, (Rule { rlname = "scissorsRule",
                        rlgoal =
                            Goal
                            {uvars = [z0,z1,z2,i0,i1,i2],
                             antec = [ -- useful for debugging:
                                       -- (AFact "no-state-split" []),
                                       (Trans (z0,i0)), (Trans (z1,i1)), (Trans (z2,i2)),
                                       (LeadsTo (z0,i0) (z1,i1)), (LeadsTo (z0,i0) (z2,i2)) ],
                             consq = [([],                -- no bvs
                                       [(Equals z1 z2),   -- two eqns
                                        (Equals i1 i2)])],
                             concl = [[(Equals z1 z2),   -- two eqns
                                       (Equals i1 i2)]]},
                        rlcomment = [] }))
            (g, _) -> (g, theVacuousRule)
      (g, _) -> (g, theVacuousRule)

shearsRule :: Sig -> Gen -> (Gen, Rule)
shearsRule sig g =
    case sortedVarsOfNames sig g "strd" ["z0","z1","z2"] of
      (g, [z0,z1,z2]) ->
          case sortedVarsOfNames sig g "indx" ["i0","i1","i2"] of
            (g, [i0,i1,i2]) ->
                (g, (Rule { rlname = "shearsRule",
                        rlgoal =
                            Goal
                            {uvars = [z0,z1,z2,i0,i1,i2],
                             antec = [ -- useful for debugging:
                                       -- (AFact "no-state-split" []),
                                       (Trans (z0,i0)), (Trans (z1,i1)), (Trans (z2,i2)),
                                       (LeadsTo (z0,i0) (z1,i1)), (SameLocn (z0,i0) (z2,i2)),
                                       (Prec (z0,i0) (z2,i2)) ],
                             consq = [([],                -- no bvs
                                       [(Equals z1 z2),   -- two eqns
                                        (Equals i1 i2)]),
                                     ([],                -- no bvs
                                      -- (z1,i1) precedes (z2,i2)
                                       [(Prec (z1, i1) (z2, i2))])],
                             concl = [[(Equals z1 z2),   -- two eqns
                                        (Equals i1 i2)],
                                      -- (z1,i1) precedes (z2,i2)
                                       [(Prec (z1, i1) (z2, i2))]]},
                        rlcomment = [] }))
            (g, _) -> (g, theVacuousRule)
      (g, _) -> (g, theVacuousRule)

invShearsRule :: Sig -> Gen -> (Gen, Rule)
invShearsRule sig g =
    case sortedVarsOfNames sig g "strd" ["z0","z1","z2"] of
      (g, [z0,z1,z2]) ->
          case sortedVarsOfNames sig g "indx" ["i0","i1","i2"] of
            (g, [i0,i1,i2]) ->
                (g, (Rule { rlname = "invShearsRule",
                        rlgoal =
                            Goal
                            {uvars = [z0,z1,z2,i0,i1,i2],
                             antec = [ -- useful for debugging:
                                       -- (AFact "no-state-split" []),
                                       (Trans (z0,i0)), (Trans (z1,i1)),
                                       (SameLocn (z0,i0) (z1,i1)),
                                       (LeadsTo (z1,i1) (z2,i2)), (Prec (z0,i0) (z2,i2)) ],
                             consq = [([],                -- no bvs
                                       [(Equals z0 z1),   -- two eqns
                                        (Equals i0 i1)]),
                                     ([],                -- no bvs
                                      -- (z0, i0) precedes (z1,i1)
                                       [(Prec (z0, i0) (z1, i1))])],
                             concl = [[(Equals z0 z1),   -- two eqns
                                        (Equals i0 i1)],
                                      -- (z0, i0) precedes (z1,i1)
                                       [(Prec (z0, i0) (z1, i1))]]},
                        rlcomment = [] }))
            (g, _) -> (g, theVacuousRule)
      (g, _) -> (g, theVacuousRule)

uninterruptibleRule :: Sig -> Gen -> (Gen, Rule)
uninterruptibleRule sig g =
    case sortedVarsOfNames sig g "strd" ["z0","z1","z2"] of
      (g, [z0,z1,z2]) ->
          case sortedVarsOfNames sig g "indx" ["i0","i1","i2"] of
            (g, [i0,i1,i2]) ->
                (g,
                 (Rule { rlname = "no-interruption",
                         rlgoal =
                             Goal
                             {uvars = [z0,z1,z2,i0,i1,i2],
                              antec = [ (LeadsTo (z0,i0) (z2,i2)), (Trans (z1,i1)),
                                        (SameLocn (z0,i0) (z1,i1)), (Prec (z0,i0) (z1,i1)),
                                        (Prec (z1,i1) (z2,i2)) ],
                              consq = [], -- implies False
                              concl = []},
                         rlcomment = [] }))
            (g, _) -> (g, theVacuousRule)
      (g, _) -> (g, theVacuousRule)

cakeRule :: Sig -> Gen -> (Gen, Rule)
cakeRule sig g =
    case sortedVarsOfNames sig g "strd" ["z0","z1","z2"] of
      (g, [z0,z1,z2]) ->
          case sortedVarsOfNames sig g "indx" ["i0","i1","i2"] of
            (g, [i0,i1,i2]) ->
                (g,
                 (Rule { rlname = "cakeRule",
                         rlgoal =
                             Goal
                             {uvars = [z0,z1,z2,i0,i1,i2],
                              antec = [ (Trans (z0,i0)), (Trans (z1,i1)),
                                        (LeadsTo (z0,i0) (z1,i1)), (LeadsTo (z0,i0) (z2,i2)),
                                        (Prec (z1,i1) (z2,i2)) ],
                              consq = [], -- implies False
                              concl = []},
                         rlcomment = [] }))
            (g, _) -> (g, theVacuousRule)
      (g, _) -> (g, theVacuousRule)