calligraphy-0.1.2: src/Calligraphy/Phases/NodeFilter.hs
{-# LANGUAGE RecordWildCards #-}
-- | This modules manages the two ways we remove nodes from a graph; collapsing and hiding.
--
-- Collapsing means absorbing a node's descendants into itself, including all edges.
--
-- Hiding means removing a node (and its descendants), moving the edges to the node's parent, if a parent exist.
--
-- There's also the special option --collapse-modules.
-- It's undeniably a little hacky, but for now this is the best home for that functionality.
-- Functionality-wise, it's still essentially just collapsing nodes into one another.
-- The thing that makes it hacky is that it then uses a value node to represent a module.
-- This is not actually a huge deal, because no other module actually cares about the node type, but it's something to watch out for.
-- There's more design discussion on https://github.com/jonascarpay/calligraphy/pull/5
module Calligraphy.Phases.NodeFilter (filterNodes, NodeFilterConfig (..), pNodeFilterConfig) where
import Calligraphy.Util.Types
import Control.Monad.State
import Data.EnumMap (EnumMap)
import qualified Data.EnumMap as EnumMap
import Data.Maybe (catMaybes)
import Data.Tree
import Options.Applicative
data Mode = Show | Collapse | Hide
deriving (Eq, Show)
data NodeFilterConfig = NodeFilterConfig
{ hideLocals :: Bool,
collapseModules :: Bool,
collapseClasses :: Mode,
collapseData :: Mode,
collapseValues :: Mode,
collapseConstructors :: Mode,
hideRecords :: Bool
}
pNodeFilterConfig :: Parser NodeFilterConfig
pNodeFilterConfig =
NodeFilterConfig
<$> switch (long "exports-only" <> long "hide-local-bindings" <> help "Remove all non-exported bindings, merging all edges into its parent, if one exist.")
<*> switch (long "collapse-modules" <> help "Collapse all nodes into a single node per module.")
<*> pMode "classes" "class"
<*> pMode "data" "data"
<*> pMode "values" "value"
<*> pMode "constructors" "constructor"
<*> flag False True (long "hide-records" <> help "Remove all record nodes.")
where
pMode :: String -> String -> Parser Mode
pMode flagName helpName =
flag' Collapse (long ("collapse-" <> flagName) <> help collapseHelp)
<|> flag' Hide (long ("hide-" <> flagName) <> help hideHelp)
<|> pure Show
where
collapseHelp = "Remove all " <> helpName <> " nodes's children, merging the children's edges into itself."
hideHelp = "Remove all " <> helpName <> " nodes and their children."
filterNodes :: NodeFilterConfig -> CallGraph -> CallGraph
filterNodes NodeFilterConfig {..} (CallGraph modules calls types) =
let (modules', reps) =
flip runState mempty $
forM modules $
if collapseModules
then collapseModule
else modForest (fmap catMaybes . traverse (go Nothing))
in CallGraph modules' (rekeyCalls reps calls) (rekeyCalls reps types)
where
collapseModule :: Module -> State (EnumMap Key Key) Module
collapseModule (Module modname path []) = pure $ Module modname path []
collapseModule (Module modname path forest@(Node rep _ : _)) = do
let repKey = declKey rep
forT_ forestT forest $ \decl -> assoc (declKey decl) repKey
pure $ Module modname path [Node (Decl modname repKey mempty True ValueDecl (Loc 1 1)) []]
shouldCollapse :: Decl -> Bool
shouldCollapse decl = case declType decl of
ValueDecl -> collapseValues == Collapse
ClassDecl -> collapseClasses == Collapse
ConDecl -> collapseConstructors == Collapse
DataDecl -> collapseData == Collapse
_ -> False
shouldHide :: Decl -> Bool
shouldHide decl = typ (declType decl) || (hideLocals && not (declExported decl))
where
typ ClassDecl = collapseClasses == Hide
typ DataDecl = collapseData == Hide
typ ValueDecl = collapseValues == Hide
typ ConDecl = collapseConstructors == Hide
typ RecDecl = hideRecords
go :: Maybe Decl -> Tree Decl -> State (EnumMap Key Key) (Maybe (Tree Decl))
go mparent node@(Node decl children)
| shouldHide decl = do
forM_ mparent $ \parent ->
forM_ node $ \child -> assoc (declKey child) (declKey parent)
pure Nothing
| shouldCollapse decl = do
forM_ node $ \child ->
assoc (declKey child) (declKey decl)
pure $ Just $ Node decl []
| otherwise = do
assoc (declKey decl) (declKey decl)
children' <- catMaybes <$> mapM (go (Just decl)) children
pure $ Just $ Node decl children'
assoc :: Key -> Key -> State (EnumMap Key Key) ()
assoc key rep = modify (EnumMap.insert key rep)