hoop-0.3.0.0: src/Language/MSH/StateEnv.hs
{-# LANGUAGE FlexibleContexts #-}
module Language.MSH.StateEnv where
--------------------------------------------------------------------------------
import Control.Monad.Except
import Data.Graph
import Data.List (intersperse)
import qualified Data.Map as M
import Language.Haskell.TH.Syntax
import Language.MSH.StateDecl
import Language.MSH.Pretty
--------------------------------------------------------------------------------
-- | Represents different errors that can arise during the construction of
-- a class graph.
data StateGraphError
= ClassNotFound String
| CyclicInheritance [String]
instance Show StateGraphError where
show (ClassNotFound cls) = "`" ++ cls ++ "' is not in scope."
show (CyclicInheritance cs) =
"The following state classes form a cyclic inheritance hierarchy: " ++
concat (intersperse ", " cs)
--------------------------------------------------------------------------------
type StateEnv = M.Map String StateDecl
ppStateEnv :: StateEnv -> String
ppStateEnv env = render $ vcat $ map (\(k,d) -> pp d) $ M.toList env
--------------------------------------------------------------------------------
-- | `preProcessMethods' @ds builds a method table from a list of decls that
-- make up an object class.
{-preProcessMethods :: [Dec] -> MethodTable
preProcessMethods ds = go emptyMethodTable ds
where
go tbl [] = tbl
go tbl (d@(SigD name ty) : ds) = go (addMethodSig name d tbl) ds
go tbl (d@(FunD name cs) : ds) = go (addMethodDef name d tbl) ds
go tbl (d@(ValD (VarP name) body wh) : ds) = go (addMethodDef name d tbl) ds
go tbl (d : ds) = go tbl ds-}
-- | `inherit` @decl@ extracts the method table from @decl@ (the parent of
-- another state class) and marks all method table entries as inherited.
inherit :: StateDecl -> MethodTable
inherit StateDecl{..} = go stateMethods
where
go (MkMethodTable methods) =
MkMethodTable (M.mapWithKey transform methods)
-- if @n@ originated in the parent, we inherit it (possibly abstract)
transform n (GenesisMethod a (Just d) mdef) = InheritedMethod a d mdef
-- if @n@ was overriden in the parent, we inherit it (never abstract)
transform n (OverridenMethod dec def) = InheritedMethod False dec (Just def)
-- otherwise @n@ was inherited by the parent and we continue to inherit it
transform n d = d
-- | `buildMethodTable` @decl@ builds a method table for @decl@.
buildMethodTable :: StateDecl -> StateDecl
buildMethodTable s@StateDecl{..} =
s { stateMethods = checkMethodTable s (go initialTable stateBody) }
where
initialTable :: MethodTable
initialTable = case stateParent of
Just p -> inherit p
Nothing -> emptyMethodTable
go :: MethodTable -> [Dec] -> MethodTable
go tbl [] = tbl
-- is this a function signature?
go tbl (d@(SigD name ty) : ds)
| declByParent name s = error $
"Method typing for inherited method `" ++ nameBase name ++ "`"
| otherwise = go (addMethodSig name d tbl) ds
-- is this a function/value definition (annoyingly: two different
-- constructors)
go tbl (d@(FunD name cs) : ds)
| declByParent name s = go (addOverride name d tbl) ds
| otherwise = go (addGenesisDef name d tbl) ds
go tbl (d@(ValD (VarP name) body wh) : ds)
| declByParent name s = go (addOverride name d tbl) ds
| otherwise = go (addGenesisDef name d tbl) ds
-- otherwise it is some type of Haskell definition we don't care about;
-- just ignore it
go tbl (d : ds) = go tbl ds
--------------------------------------------------------------------------------
-- | `checkMethodTable` @decl tbl@ performs sanity checks on @tbl@ for @decl@:
-- this function fails if @tbl@ contains abstract methods and @decl@ is not
-- marked as abstract
checkMethodTable :: StateDecl -> MethodTable -> MethodTable
checkMethodTable s tbl =
MkMethodTable $ M.mapWithKey check (methods tbl)
where check k v | abstractEntry v && not (isAbstractClass s) =
error $ "Abstract member `" ++ k ++
"` in non-abstract class `" ++
stateName s ++ "`"
| otherwise = v
--------------------------------------------------------------------------------
-- | `buildStateGraph` @env@ constructs a strongly-connected graph from @env@.
buildStateGraph :: StateEnv -> Except StateGraphError StateEnv
buildStateGraph = {- throwError . ClassNotFound . show . toGraph -}
go M.empty . stronglyConnCompR . toGraph
where
-- nothing more to add
go env [] = return env
-- we found a cyclic dependency group, raise an error
go env (CyclicSCC cs : ds) =
throwError $ CyclicInheritance [c | (_,c,_) <- cs]
-- we found an acyclic node without dependencies, so
-- we just construct the method table for it
go env (AcyclicSCC (dec,n,[]) : ds) =
go (M.insert n (buildMethodTable dec) env) ds
-- we found an acyclic node with dependencies, try to resolve
-- the parent or throw an error if it cannot be found
go env (AcyclicSCC (dec,n,[p]) : ds) = case M.lookup p env of
Nothing -> throwError (ClassNotFound p)
(Just pd) -> go (M.insert n (buildMethodTable dec') env) ds
where dec' = dec { stateParent = Just pd }
-- | `toGraph` @env@ turns @env@ into a suitable graph for the SCC algorithm.
toGraph :: StateEnv -> [(StateDecl, String, [String])]
toGraph = map (\(k,v) -> (v, k, dep v)) . M.toList
where
-- a state class either has zero dependencies if it is a base class,
-- or exactly one dependency if it inherits from another class
dep StateDecl{ stateParentN = Nothing } = []
dep StateDecl{ stateParentN = Just p } = [p]
--------------------------------------------------------------------------------