{- Tock: a compiler for parallel languages Copyright (C) 2008, 2009 University of Kent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module CheckFramework (CheckOptM, CheckOptASTM, forAnyASTTopDown, forAnyASTStructTopDown, substitute, restartForAnyAST, CheckOptASTM', forAnyASTStructBottomUpAccum, askAccum, runChecks, runChecksPass, getFlowGraph, withChild, varsTouchedAfter, getCachedAnalysis, getCachedAnalysis', forAnyFlowNode, getFlowLabel, getFlowMeta, CheckOptFlowM) where import Control.Monad.Reader import Control.Monad.State import Data.Generics (Data) import Data.Graph.Inductive hiding (apply) import Data.List import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set import qualified AST as A import CompState import Data.Generics.Alloy.Route import Errors import FlowAlgorithms import FlowGraph import FlowUtils import Metadata import Pass import Traversal import UsageCheckUtils import Utils -- Each data analysis only works on a connected sub-graph. For forward data flow -- this begins at the root node (the one with no predecessors, and thus is the -- direct or indirect predecessor of all nodes it is connected to), for backwards -- data flow it begins at the terminal node (the one with no successors, and thus -- is the direct or indirect successor of all nodes it is connected to). -- -- Each node has a unique corresponding root (the start of the PROC/FUNCTION) and -- similarly a unique corresponding terminal (the end of the PROC/FUNCTION). This -- should be guaranteed by the building of the flow graph. -- -- Each analysis gives back a map from nodes to some sort of label-value (dependent -- on the analysis). This map is calculated for a given connected sub-graph. -- If the node you are looking for appears in the connected sub-graph (the keys -- of the map), you use that map. Since the analyses are run before unnesting -- takes place, it is possible to descend down the AST into a inner PROC (a different -- sub-graph) and then back up into the outer PROC. -- -- To prevent re-running the analysis several times where there is no need, we -- do the following: -- -- * Modifying any node invalidates the flow-graph. We currently calculate -- the flow-graph for the whole AST at once, but I can't see an easy way to avoid -- that (a more efficient way would be to just calculate the current connected -- sub-graph) -- perhaps we could start from the part of the AST corresponding -- to the root node? TODO should be possible by using the route to the root node -- of the current graph -- -- * Modifying a node (e.g. with substitute or replaceBelow) invalidates all analyses. -- -- I did have an idea that we could invalidate only analyses that contain -- nodes that have a route that is prefixed by that of the current node. So -- for example, if you modify a node with route [1,3,1], we would find all -- nodes with routes that match (1:3:1:_) and invalidate all currently held -- analysis results containing any of those nodes. This would help if for -- example you do a substitute in an inner PROC, we do not have to invalidate -- the analysis for the outer PROC. But this idea DOES NOT WORK because the nodes -- will change when the flow-graph is rebuilt, so we can't let the results get -- out of sync with the flow-graph. Unless in future we decouple the node identifiers -- from our use of them a bit more (but remember not to use routes, as they are -- not unique in the flow graph). data CheckOptData = CheckOptData { ast :: A.AST , parItems :: Maybe (ParItems ()) , nextVarsTouched :: Map.Map Node (Set.Set Var) , flowGraphRootsTerms :: Maybe (FlowGraph CheckOptM UsageLabel, [Node], [Node]) , lastValidMeta :: Meta } data FlowGraphAnalysis res = FlowGraphAnalysis { getFlowGraphAnalysis :: CheckOptData -> Map.Map Node res , addFlowGraphAnalysis :: Map.Map Node res -> CheckOptData -> CheckOptData , doFlowGraphAnalysis :: (FlowGraph CheckOptM UsageLabel, Node) -> CheckOptM (Map.Map Node res) } invalidateAll :: (A.AST -> A.AST) -> CheckOptData -> CheckOptData invalidateAll f d = d { ast = f (ast d), parItems = Nothing, nextVarsTouched = Map.empty, flowGraphRootsTerms = Nothing} newtype CheckOptM a = CheckOptM (StateT CheckOptData PassM a) deriving (Monad, MonadIO) instance Die CheckOptM where dieReport = CheckOptM . lift . dieReport instance MonadState CompState CheckOptM where get = CheckOptM $ lift get put = CheckOptM . lift . put instance CSMR CheckOptM where getCompState = CheckOptM . lift $ getCompState instance Warn CheckOptM where warnReport = CheckOptM . lift . warnReport deCheckOptM :: CheckOptM a -> StateT CheckOptData PassM a deCheckOptM (CheckOptM x) = x newtype CheckOptASTM' acc t a = CheckOptASTM' (ReaderT (acc, Route t A.AST) (RestartT CheckOptM) (Either t a)) type CheckOptASTM = CheckOptASTM' () instance Monad (CheckOptASTM' acc t) where return x = CheckOptASTM' (return (Right x)) (>>=) m f = let (CheckOptASTM' m') = m in CheckOptASTM' $ do x <- m' case x of Left x -> return (Left x) Right x -> let CheckOptASTM' m'' = f x in m'' instance MonadIO (CheckOptASTM' acc t) where liftIO = CheckOptASTM' . liftM Right . liftIO instance MonadState CompState (CheckOptASTM' acc t) where get = CheckOptASTM' . liftM Right . lift . lift $ get put = CheckOptASTM' . liftM Right . lift . lift . put deCheckOptASTM' :: (t -> CheckOptASTM' acc t ()) -> (t, Route t A.AST, acc) -> RestartT CheckOptM (Either t t) deCheckOptASTM' f (x, r, acc) = do x' <- runReaderT (let CheckOptASTM' m = f x in m) (acc, r) case x' of Left replacement -> return (Left replacement) Right _ -> return (Right x) deCheckOptASTM :: (t -> CheckOptASTM t ()) -> (t, Route t A.AST) -> RestartT CheckOptM (Either t t) deCheckOptASTM f (x, r) = deCheckOptASTM' f (x,r,()) -- | The idea is this: in normal operation you use the Right return value. When -- you want to restart the forAnyAST operation from a given point, you use the -- Left constructor. data Monad m => RestartT m a = RestartT { getRestartT :: m (Either () a) } instance Monad m => Monad (RestartT m) where return x = RestartT $ return (Right x) (>>=) m f = let m' = getRestartT m in RestartT $ do x <- m' case x of Left route -> return (Left route) Right x' -> let m'' = getRestartT $ f x' in m'' instance MonadIO m => MonadIO (RestartT m) where liftIO f = RestartT $ (liftIO f) >>* Right instance MonadTrans RestartT where lift = RestartT . liftM Right instance Die m => Die (RestartT m) where dieReport = lift . dieReport instance Die m => Die (ReaderT (Route t outer) m) where dieReport = lift . dieReport instance Die (CheckOptASTM' acc t) where dieReport = liftCheckOptM . dieReport instance Warn (CheckOptASTM' acc t) where warnReport = liftCheckOptM . warnReport instance CSMR (CheckOptASTM' acc t) where getCompState = liftCheckOptM getCompState instance MonadState CompState (CheckOptFlowM t) where get = CheckOptFlowM . lift $ get put = CheckOptFlowM . lift . put askRoute :: CheckOptASTM' acc t (Route t A.AST) askRoute = CheckOptASTM' $ ask >>* snd >>* Right askAccum :: CheckOptASTM' acc t acc askAccum = CheckOptASTM' $ ask >>* fst >>* Right getCheckOptData :: CheckOptM CheckOptData getCheckOptData = CheckOptM get modifyCheckOptData :: (CheckOptData -> CheckOptData) -> CheckOptM () modifyCheckOptData = CheckOptM . modify liftCheckOptM :: CheckOptM a -> CheckOptASTM' acc t a liftCheckOptM = CheckOptASTM' . liftM Right . lift . lift -- Could also include the list of connected nodes in the reader monad: newtype CheckOptFlowM t a = CheckOptFlowM (ReaderT (Node, Map.Map Node t) CheckOptM a) deriving (Monad, MonadIO) instance Die m => Die (ReaderT (Node, Map.Map Node a) m) where dieReport = lift . dieReport instance CSMR (CheckOptFlowM t) where getCompState = CheckOptFlowM $ lift getCompState instance Warn (CheckOptFlowM t) where warnReport = CheckOptFlowM . lift . warnReport forAnyFlowNode :: ((FlowGraph CheckOptM UsageLabel, [Node], [Node]) -> CheckOptM (Map.Map Node t)) -> CheckOptFlowM t () -> CheckOptM () forAnyFlowNode fgraph (CheckOptFlowM f) = do grt@(g,_,_) <- getFlowGraph m <- fgraph grt sequence_ [runReaderT f (n, m) | n <- nodes g] getFlowLabel :: CheckOptFlowM t (UsageLabel, Maybe t) getFlowLabel = CheckOptFlowM $ do (n, m) <- ask (g,_,_) <- lift getFlowGraph l <- checkJust (Nothing, "Label not in flow graph") $ lab g n return (getNodeData l, Map.lookup n m) getFlowMeta :: CheckOptFlowM t Meta getFlowMeta = CheckOptFlowM $ do (n, _) <- ask (g,_,_) <- lift getFlowGraph case lab g n of Nothing -> return emptyMeta Just l -> return $ getNodeMeta l -- | This function currently only supports one type forAnyASTTopDown :: forall a. (AlloyARoute A.AST (a :-@ BaseOpMRoute) BaseOpMRoute ,AlloyARoute a BaseOpMRoute (a :-@ BaseOpMRoute) ) => (a -> CheckOptASTM a ()) -> CheckOptM () forAnyASTTopDown origF = CheckOptM $ do tr <- get >>* ast doTree ops transformMRoute tr where ops = (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM origF) :-@ baseOpMRoute forAnyASTStructTopDown :: (forall a. Data a => (A.Structured a -> CheckOptASTM (A.Structured a) ())) -> CheckOptM () forAnyASTStructTopDown origF = CheckOptM $ do tr <- get >>* ast doTree ops transformMRoute tr where ops = (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.Variant -> CheckOptASTM (A.Structured A.Variant) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.Process -> CheckOptASTM (A.Structured A.Process) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.Option -> CheckOptASTM (A.Structured A.Option) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.ExpressionList -> CheckOptASTM (A.Structured A.ExpressionList) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.Choice -> CheckOptASTM (A.Structured A.Choice) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured A.Alternative -> CheckOptASTM (A.Structured A.Alternative) ())) :-@ (makeTopDownMRoute ops $ keepApplying $ deCheckOptASTM (origF :: A.Structured () -> CheckOptASTM (A.Structured ()) ())) :-@ baseOpMRoute type AccumOps b = b :-@ StructOps type StructOps = A.Structured A.Variant :-@ A.Structured A.Process :-@ A.Structured A.Option :-@ A.Structured A.ExpressionList :-@ A.Structured A.Choice :-@ A.Structured A.Alternative :-@ A.Structured () :-@ BaseOpMRoute type SingleOps b = b :-@ BaseOpMRoute type AccumMap b = Map.Map [Int] b findSub :: [Int] -> AccumMap b -> [b] findSub r m = [v | (k, v) <- Map.toList m, r `isPrefixOf` k] -- TODO this could be made more efficient by picking out a range in the map filterSub :: [Int] -> AccumMap b -> AccumMap b filterSub r = Map.filterWithKey (\k _ -> not $ r `isPrefixOf` k) -- I know the constraints here look horrendous, but it's really just three groups. forAnyASTStructBottomUpAccum :: forall b. (Data b, -- Allow us to descend into the AST with our full set of ops: AlloyARoute A.AST (AccumOps b) BaseOpMRoute, -- Allow us to recurse into each Structured item (and b) with our full set of -- ops: AlloyARoute (A.Structured A.Variant) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured A.Process) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured A.Option) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured A.ExpressionList) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured A.Choice) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured A.Alternative) BaseOpMRoute (AccumOps b), AlloyARoute (A.Structured ()) BaseOpMRoute (AccumOps b), AlloyARoute b BaseOpMRoute (AccumOps b), -- Allow us to descend into each Structured item with just our ops for -- b, when our accumulated stuff becomes invalidated AlloyARoute (A.Structured A.Variant) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured A.Process) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured A.Option) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured A.ExpressionList) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured A.Choice) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured A.Alternative) (SingleOps b) BaseOpMRoute, AlloyARoute (A.Structured ()) (SingleOps b) BaseOpMRoute, -- For b, we will recurse, not descend: AlloyARoute b BaseOpMRoute (SingleOps b) ) => (forall a. Data a => (A.Structured a) -> CheckOptASTM' [b] (A.Structured a) ()) -> CheckOptM () forAnyASTStructBottomUpAccum origF = CheckOptM $ do tr <- get >>* ast doTree ops (\x y z -> flip evalStateT (Map.empty :: AccumMap b) $ transformMRoute x y z) tr where ops :: AccumOps b (StateT (AccumMap b) (RestartT CheckOptM)) A.AST ops = applyAccum (undefined::b) allF keepApplying' :: AlloyARoute t (b :-@ BaseOpMRoute) BaseOpMRoute => ((t, Route t A.AST) -> StateT (AccumMap b) (RestartT CheckOptM) (Either t t)) -> ((t, Route t A.AST) -> StateT (AccumMap b) (RestartT CheckOptM) t) keepApplying' f xr = do x' <- f xr case x' of Right y -> return y Left y -> do -- remove all sub-items from state, -- and then scan the item anew: modify $ filterSub (routeId $ snd xr) transformMRoute (applyAccum (undefined::b) baseOpMRoute) baseOpMRoute (y, snd xr) keepApplying' f (y, snd xr) wrap :: forall a. (Data a, AlloyARoute (A.Structured a) BaseOpMRoute (AccumOps b) , AlloyARoute (A.Structured a) (b :-@ BaseOpMRoute) BaseOpMRoute ) => ((A.Structured a, Route (A.Structured a) A.AST, [b]) -> RestartT CheckOptM (Either (A.Structured a) (A.Structured a))) -> (A.Structured a, Route (A.Structured a) A.AST) -> StateT (AccumMap b) (RestartT CheckOptM) (A.Structured a) wrap f = makeBottomUpMRoute ops $ keepApplying' $ \(x, y) -> get >>= \z -> lift (f (x, y, findSub (routeId y) z)) allF = (wrap $ deCheckOptASTM' (origF :: (A.Structured A.Variant) -> CheckOptASTM' [b] (A.Structured A.Variant) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured A.Process) -> CheckOptASTM' [b] (A.Structured A.Process) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured A.Option) -> CheckOptASTM' [b] (A.Structured A.Option) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured A.ExpressionList) -> CheckOptASTM' [b] (A.Structured A.ExpressionList) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured A.Choice) -> CheckOptASTM' [b] (A.Structured A.Choice) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured A.Alternative) -> CheckOptASTM' [b] (A.Structured A.Alternative) ())) :-@ (wrap $ deCheckOptASTM' (origF :: (A.Structured ()) -> CheckOptASTM' [b] (A.Structured ()) ())) :-@ baseOpMRoute -- | Given a TypeSet, a function to apply to everything of type a, a route -- location to begin at and an AST, transforms the tree. Handles any restarts -- that are requested. doTree :: ops -> (ops -> BaseOpMRoute m outer -> (A.AST, Route A.AST A.AST) -> RestartT CheckOptM A.AST) -> A.AST -> StateT CheckOptData PassM () -- This line applies "apply" to the first thing of the right type in -- the given AST; from there, ops recurses for itself doTree ops trans tr = do x <- deCheckOptM (getRestartT (trans ops baseOpMRoute (tr, identityRoute) >> return ())) case x of Left _ -> do -- Restart tr' <- get >>* ast doTree ops trans tr' Right _ -> return () applyAccum :: forall t ops. AlloyARoute t BaseOpMRoute (t :-@ ops) => t -> ops (StateT (AccumMap t) (RestartT CheckOptM)) A.AST -> (t :-@ ops) (StateT (AccumMap t) (RestartT CheckOptM)) A.AST applyAccum _ ops = ops' where ops' :: (t :-@ ops) (StateT (AccumMap t) (RestartT CheckOptM)) A.AST ops' = accum :-@ ops accum xr = do x' <- transformMRoute baseOpMRoute ops' xr modify $ Map.insert (routeId $ snd xr) x' return x' -- Keep applying the function while there is a Left return (which indicates -- the value was replaced) until there is a Right return keepApplying :: Monad m => ((t, Route t outer) -> m (Either t t)) -> ((t, Route t outer) -> m t) keepApplying f (x, route) = do x' <- f (x, route) case x' of Left y -> keepApplying f (y, route) Right y -> return y -- | For both of these functions I'm going to need to mark all analyses as no longer -- valid, but more difficult will be to maintain the current position (if possible -- -- should be in substitute, but not necessarily in replace) and continue. -- | Substitutes the currently examined item for the given item, and continues -- the traversal from the current point. That is, the new item is transformed -- again too. substitute :: forall a acc. Data a => a -> CheckOptASTM' acc a () substitute x = CheckOptASTM' $ do r <- ask >>* snd lift . lift . CheckOptM $ modify (invalidateAll $ routeSet r x) return (Left x) --replaceBelow :: t -> t -> CheckOptASTM a () --replaceEverywhere :: t -> t -> CheckOptASTM a () -- TODO think about what this means (replace everywhere, or just children?) -- Restarts the current forAnyAST from the top of the tree, but keeps all changes -- made thus far. restartForAnyAST :: CheckOptASTM' acc a a restartForAnyAST = CheckOptASTM' . lift . RestartT $ return $ Left () runChecks :: CheckOptM () -> A.AST -> PassM A.AST runChecks (CheckOptM m) x = execStateT m (CheckOptData {ast = x, parItems = Nothing, nextVarsTouched = Map.empty, flowGraphRootsTerms = Nothing, lastValidMeta = emptyMeta}) >>* ast runChecksPass :: CheckOptM () -> Pass A.AST runChecksPass c = pass "" [] [] (runChecks c) --getParItems :: CheckOptM (ParItems ()) --getParItems = CheckOptM (\d -> Right (d, fromMaybe (generateParItems $ ast d) (parItems d))) -- | Performs the given action for the given child. [0] is the first argument -- of the current node's constructor, [2,1] is the second argument of the constructor -- of the third argument of this constructor. Issuing substitute inside this function -- will yield an error. withChild :: forall acc t a. [Int] -> CheckOptASTM' acc t a -> CheckOptASTM' acc t a withChild ns (CheckOptASTM' m) = askRoute >>= (CheckOptASTM' . lift . inner) where inner :: Route t A.AST -> RestartT CheckOptM (Either t a) inner r = runReaderT m (error "withChild asked for accum", makeRoute (routeId r) (error "withChild attempted a substitution")) -- | Searches forward in the graph from the given node to find all the reachable -- nodes that have no successors, i.e. the terminal nodes findTerminals :: Node -> Gr a b -> [Node] findTerminals n g = nub [x | x <- dfs [n] g, null (suc g x)] varsTouchedAfter :: FlowGraphAnalysis (Set.Set Var) varsTouchedAfter = FlowGraphAnalysis nextVarsTouched (\x d -> d {nextVarsTouched = x `Map.union` nextVarsTouched d}) $ \(g, startNode) -> case findTerminals startNode g of [] -> return Map.empty [termNode] -> let connNodes = rdfs [termNode] g in case flowAlgorithm (funcs g) connNodes (termNode, Set.empty) of Left err -> dieP emptyMeta err Right nodesToVars -> {-(liftIO $ putStrLn $ "Graph:\n" ++ show g ++ "\n\nNodes:\n" ++ show (termNode, connNodes)) >> -}return nodesToVars ts -> dieP (fromMaybe emptyMeta $ fmap getNodeMeta $ lab g startNode) $ "Multiple terminal nodes in flow graph" ++ show [fmap getNodeMeta (lab g n) | n <- ts] where funcs :: FlowGraph CheckOptM UsageLabel -> GraphFuncs Node EdgeLabel (Set.Set Var) funcs g = GF { nodeFunc = iterate g -- Backwards data flow: , nodesToProcess = lsuc g , nodesToReAdd = lpre g , defVal = Set.empty , userErrLabel = ("for node at: " ++) . show . fmap getNodeMeta . lab g } iterate :: FlowGraph CheckOptM UsageLabel -> (Node, EdgeLabel) -> Set.Set Var -> Maybe (Set.Set Var) -> Set.Set Var iterate g node varsForPrevNode maybeVars = case lab g (fst node) of Just ul -> let vs = nodeVars $ getNodeData ul readFromVars = readVars vs writtenToVars = writtenVars vs addTo = fromMaybe Set.empty maybeVars in foldl Set.union addTo [varsForPrevNode, readFromVars, Map.keysSet writtenToVars] Nothing -> error "Node label not found in calculateUsedAgainAfter" getFlowGraph :: CheckOptM (FlowGraph CheckOptM UsageLabel, [Node], [Node]) getFlowGraph = getCache flowGraphRootsTerms (\x d -> d {flowGraphRootsTerms = Just x, nextVarsTouched = Map.empty}) generateFlowGraph -- Makes sure that only the real last node at the end of a PROC/FUNCTION is a terminator -- node, by joining any other nodes without successors to this node. This is a -- bit hacky, but is needed for some of the backwards flow analysis correctFlowGraph :: Node -> (FlowGraph CheckOptM UsageLabel, [Node], [Node]) -> FlowGraph CheckOptM UsageLabel correctFlowGraph curNode (g, roots, terms) = case findTerminals curNode g `intersect` terms of [] -> empty -- Not a PROC/FUNCTION [realTerm] -> foldl (addFakeEdge realTerm) g midTerms where -- The nodes that have no successors but are not the real terminator -- For example, the node after the last condition in an IF, or a STOP node midTerms = findTerminals curNode g \\ terms addFakeEdge :: Node -> FlowGraph CheckOptM UsageLabel -> Node -> FlowGraph CheckOptM UsageLabel addFakeEdge realTerm g n = insEdge (n, realTerm, ESeq Nothing) g getCache :: (CheckOptData -> Maybe a) -> (a -> CheckOptData -> CheckOptData) -> (A.AST -> CheckOptM a) -> CheckOptM a getCache getF setF genF = getCheckOptData >>= \x -> case getF x of Just y -> return y Nothing -> do y <- genF (ast x) modifyCheckOptData (setF y) return y getCachedAnalysis :: Data t => FlowGraphAnalysis res -> CheckOptASTM t (Maybe res) getCachedAnalysis = getCachedAnalysis' (const True) -- Analysis requires the latest flow graph, and uses this to produce a result getCachedAnalysis' :: Data t => (UsageLabel -> Bool) -> FlowGraphAnalysis res -> CheckOptASTM t (Maybe res) getCachedAnalysis' f an = do d <- liftCheckOptM getCheckOptData g'@(g,_,_) <- liftCheckOptM getFlowGraph r <- askRoute -- Find the node that matches our location and the given function: case find (\(_,l) -> f (getNodeData l) && (getNodeRouteId l == routeId r)) (labNodes g) of Nothing -> {- (liftIO $ putStrLn $ "Could not find node for: " ++ show (lastValidMeta d)) >> -} return Nothing Just (n, _) -> case Map.lookup n (getFlowGraphAnalysis an d) of Just y -> return (Just y) Nothing -> liftCheckOptM $ do z <- doFlowGraphAnalysis an (correctFlowGraph n g', n) CheckOptM $ modify $ addFlowGraphAnalysis an z CheckOptM $ get >>* (Map.lookup n . getFlowGraphAnalysis an) generateFlowGraph :: A.AST -> CheckOptM (FlowGraph CheckOptM UsageLabel, [Node], [Node]) generateFlowGraph x = buildFlowGraph labelUsageFunctions x >>= \g -> case g of Left err -> dieP emptyMeta err Right grt -> return grt