packages feed

g2-0.2.0.0: src/G2/Language/CallGraph.hs

module G2.Language.CallGraph ( CallGraph
                             , getCallGraph
                             , functions
                             , calls
                             , calledBy
                             , nameLevels
                             , reachable ) where

import qualified G2.Language.ExprEnv as E
import G2.Language.Naming
import G2.Language.Syntax

import Data.Graph hiding (reachable)
import qualified Data.Graph as G
import Data.Maybe

data CallGraph = CallGraph { graph :: Graph
                           , nfv :: Vertex -> ((), Name, [Name])
                           , vert :: Name -> Maybe Vertex}

getCallGraph :: E.ExprEnv -> CallGraph
getCallGraph eenv =
    let
        funcs = E.keys eenv
    
        (g, nfv', vert') = graphFromEdges
            . map (\(n, e) -> 
                        let
                            clls = filter (\n' -> n' `elem` funcs) . map idName $ varIds e
                        in
                        ((), n, clls)) $ E.toList eenv
    in
    CallGraph g nfv' vert'

functions :: CallGraph -> [Name]
functions cg = map (\(_, n, _) -> n) . map (nfv cg) . vertices $ graph cg

callsList :: CallGraph -> [(Name, Name)]
callsList cg = map (\(v1, v2) -> (mid $ nfv cg v1, mid $ nfv cg v2)) . edges $ graph cg
    where
        mid (_, m, _) = m

nodeName :: CallGraph -> Vertex -> Name
nodeName g v = (\(_, n, _) -> n) $ nfv g v

-- | Functions directly called by the named function
calls :: Name -> CallGraph -> Maybe [Name]
calls n g = fmap (\(_, _, ns) -> ns) . fmap (nfv g) $ vert g n

calledBy :: Name -> CallGraph -> [Name]
calledBy n g = map fst
             . filter ((==) n . snd)
             . map (\(v1, v2) -> (nodeName g v1, nodeName g v2)) $ edges (graph g)

-- Functions directly and indirectly called by the named function
reachable :: Name -> CallGraph -> [Name]
reachable n g =
    map ((\(_, x, _) -> x) . nfv g) . maybe [] (G.reachable $ graph g) $ vert g n

-- | Returns:
-- (1) a list of list of names, where the first list contains functions
-- that are not called by any functions (except themselves), and the nth list, n > 2,
-- includes functions called by functions in the (n - 1)th list.
nameLevels :: CallGraph -> [[Name]]
nameLevels cg =
    let
        fs = functions cg
        eds = callsList cg

        callers = fs
        called_by_others = mapMaybe (\(n1, n2) -> if n1 /= n2 then Just n2 else Nothing) eds

        only_caller = filter (`notElem` called_by_others) callers
    in
    only_caller:nameLevels' only_caller (removeEdgesTo only_caller eds)

nameLevels' :: [Name] -> [(Name, Name)] -> [[Name]]
nameLevels' [] _ = []
nameLevels' callers eds =
    let
        called = map snd $ filter (\(n1, _) -> n1 `elem` callers) eds
    in
    called:nameLevels' called (removeEdgesTo called eds)

removeEdgesTo :: [Name] -> [(Name, Name)] -> [(Name, Name)]
removeEdgesTo ns = filter (\(_, n2) -> n2 `notElem` ns)