mirror of
https://github.com/rgleichman/glance.git
synced 2024-11-22 14:16:42 +03:00
266 lines
9.6 KiB
Haskell
266 lines
9.6 KiB
Haskell
{-# LANGUAGE MultiWayIf #-}
|
|
{-# LANGUAGE PatternSynonyms #-}
|
|
module GraphAlgorithms(
|
|
ParentType(..),
|
|
annotateGraph,
|
|
collapseAnnotatedGraph
|
|
) where
|
|
|
|
import qualified Data.Graph.Inductive as ING
|
|
import Data.List(foldl', find)
|
|
import qualified Data.Set as Set
|
|
import Data.Tuple(swap)
|
|
import GHC.Stack(HasCallStack)
|
|
|
|
import Constants(pattern ResultPortConst, pattern InputPortConst)
|
|
import Types(SyntaxNode(..), IngSyntaxGraph, Edge(..),
|
|
CaseOrMultiIfTag(..), Port(..), NameAndPort(..), SgNamedNode
|
|
, AnnotatedGraph, EmbedInfo(..), EmbedDirection(..), NodeInfo(..)
|
|
, Embedder(..), Named(..), EmbedderSyntaxNode, NodeName)
|
|
import Util(fromMaybeError)
|
|
|
|
{-# ANN module "HLint: ignore Use record patterns" #-}
|
|
|
|
data ParentType = ApplyParent
|
|
| CaseParent
|
|
| MultiIfParent
|
|
| LambdaParent
|
|
| NotAParent
|
|
deriving (Eq, Show)
|
|
|
|
-- Helper functions
|
|
|
|
parentAndChild :: EmbedDirection
|
|
-> (a, a) -- ^ (from, to)
|
|
-> (a, a) -- ^ (parent, child)
|
|
parentAndChild embedDirection
|
|
= case embedDirection of
|
|
EdEmbedTo -> id
|
|
EdEmbedFrom -> swap
|
|
|
|
-- End helper functions
|
|
-- START annotateGraph --
|
|
|
|
-- TODO Use pattern synonyms here
|
|
-- | A syntaxNodeIsEmbeddable if it can be collapsed into another node
|
|
syntaxNodeIsEmbeddable :: ParentType
|
|
-> SyntaxNode
|
|
-> Maybe Port
|
|
-> Maybe Port
|
|
-> Bool
|
|
syntaxNodeIsEmbeddable parentType syntaxNode mParentPort mChildPort
|
|
= case (parentType, syntaxNode) of
|
|
(ApplyParent, ApplyNode _ _) -> parentPortNotResult
|
|
(ApplyParent, LiteralNode _) -> parentPortNotResult
|
|
(ApplyParent, FunctionDefNode _ _)
|
|
-> parentPortNotResult && isResult mChildPort
|
|
|
|
-- The match below works, but can make messy drawings with the current
|
|
-- icon for lambdas.
|
|
-- (LambdaParent, ApplyNode _ _ _) -> parentPortIsInput
|
|
(LambdaParent, LiteralNode _) -> parentPortIsInput
|
|
(LambdaParent, FunctionDefNode _ _)
|
|
-> parentPortIsInput && isResult mChildPort
|
|
|
|
(CaseParent, LiteralNode _) -> parentPortNotResult
|
|
(CaseParent, ApplyNode _ _)
|
|
-> parentPortNotResult && parentPortNotInput
|
|
(CaseParent, PatternApplyNode _ _)
|
|
-> parentPortNotResult && parentPortNotInput
|
|
|
|
(MultiIfParent, LiteralNode _) -> parentPortNotResult
|
|
(MultiIfParent, ApplyNode _ _)
|
|
-> parentPortNotResult && parentPortNotInput
|
|
|
|
_ -> False
|
|
where
|
|
isInput mPort = case mPort of
|
|
Just InputPortConst -> True
|
|
_ -> False
|
|
|
|
isResult mPort = case mPort of
|
|
Nothing -> True
|
|
Just ResultPortConst -> True
|
|
Just _ -> False
|
|
|
|
parentPortIsInput = isInput mParentPort
|
|
|
|
parentPortNotInput = not $ isInput mParentPort
|
|
parentPortNotResult = not $ isResult mParentPort
|
|
|
|
parentTypeForNode :: SyntaxNode -> ParentType
|
|
parentTypeForNode n = case n of
|
|
(ApplyNode _ _) -> ApplyParent
|
|
CaseOrMultiIfNode CaseTag _ -> CaseParent
|
|
CaseOrMultiIfNode MultiIfTag _ -> MultiIfParent
|
|
(FunctionDefNode _ _) -> LambdaParent
|
|
_ -> NotAParent
|
|
|
|
lookupSyntaxNode :: ING.Graph gr =>
|
|
IngSyntaxGraph gr -> ING.Node -> Maybe EmbedderSyntaxNode
|
|
lookupSyntaxNode gr node = naVal <$> ING.lab gr node
|
|
|
|
lookupParentType :: ING.Graph gr => IngSyntaxGraph gr -> ING.Node -> ParentType
|
|
lookupParentType graph node
|
|
= maybe NotAParent parentTypeForNode $ emNode <$> lookupSyntaxNode graph node
|
|
|
|
{-# ANN edgeIsSingular "HLint: ignore Redundant bracket" #-}
|
|
edgeIsSingular :: ING.Graph gr => gr a Edge -> ING.Node -> Edge -> Bool
|
|
edgeIsSingular graph node edge = numEdges <= 1 where
|
|
(childNamePort, _) = edgeConnection edge
|
|
edgeLabels = filter
|
|
(childNamePort ==)
|
|
((fst . edgeConnection . snd) <$> ING.lsuc graph node)
|
|
numEdges = length edgeLabels
|
|
|
|
parentCanEmbedChild :: ING.Graph gr =>
|
|
IngSyntaxGraph gr -> ING.Node -> ING.Node -> Edge -> EmbedDirection -> Bool
|
|
parentCanEmbedChild graph parent child edge embedDirection
|
|
= case lookupSyntaxNode graph child of
|
|
Nothing -> False
|
|
Just childSyntaxNode ->
|
|
edgeIsSingular graph child edge
|
|
&& syntaxNodeIsEmbeddable
|
|
parentType
|
|
(emNode childSyntaxNode)
|
|
parentPort
|
|
childPort
|
|
where
|
|
parentType = lookupParentType graph parent
|
|
(NameAndPort _ fromPort, NameAndPort _ toPort) = edgeConnection edge
|
|
(parentPort, childPort)
|
|
= parentAndChild embedDirection (fromPort, toPort)
|
|
|
|
findEmbedDir :: ING.Graph gr
|
|
=> IngSyntaxGraph gr
|
|
-> ING.Node
|
|
-> ING.Node
|
|
-> Edge
|
|
-> Maybe EmbedDirection
|
|
findEmbedDir gr fromNode toNode e = if
|
|
| parentCanEmbedChild gr fromNode toNode e EdEmbedTo
|
|
-> Just EdEmbedTo
|
|
| parentCanEmbedChild gr toNode fromNode e EdEmbedFrom
|
|
-> Just EdEmbedFrom
|
|
| otherwise -> Nothing
|
|
|
|
|
|
annotateGraph :: ING.DynGraph gr => IngSyntaxGraph gr -> gr SgNamedNode (EmbedInfo Edge)
|
|
annotateGraph gr = ING.gmap edgeMapper gr
|
|
where
|
|
edgeMapper :: ING.Context SgNamedNode Edge
|
|
-> ING.Context SgNamedNode (EmbedInfo Edge)
|
|
edgeMapper (inEdges, node, nodeLabel, outEdges)
|
|
= (getInEmbedInfo node inEdges
|
|
, node
|
|
, nodeLabel
|
|
, getOutEmbedInfo node outEdges)
|
|
getInEmbedInfo toNode
|
|
= fmap (\(e, fromNode)
|
|
-> (EmbedInfo (findEmbedDir gr fromNode toNode e) e, fromNode))
|
|
getOutEmbedInfo fromNode
|
|
= fmap (\(e, toNode)
|
|
-> (EmbedInfo (findEmbedDir gr fromNode toNode e) e, toNode))
|
|
|
|
-- END annotateGraph --
|
|
-- START collapseAnnotatedGraph --
|
|
|
|
findEdgeLabel :: ING.Graph gr => gr a b -> ING.Node -> ING.Node -> Maybe b
|
|
findEdgeLabel graph node1 node2 = fmap fst matchingEdges where
|
|
labelledEdges = ING.lneighbors graph node1
|
|
matchingEdges = find ((== node2) . snd) labelledEdges
|
|
|
|
-- | Replace the a node's label
|
|
changeNodeLabel :: ING.DynGraph gr => ING.Node -> a -> gr a b -> gr a b
|
|
changeNodeLabel node newLabel graph = case ING.match node graph of
|
|
(Just (inEdges, _, _, outEdges), restOfTheGraph)
|
|
-> (inEdges, node, newLabel, outEdges) ING.& restOfTheGraph
|
|
(Nothing, _) -> graph
|
|
|
|
addChildToNodeLabel ::
|
|
(NodeName, Edge) -> EmbedderSyntaxNode -> EmbedderSyntaxNode
|
|
addChildToNodeLabel child (Embedder existingNodes oldSyntaxNode)
|
|
= Embedder (Set.insert child existingNodes) oldSyntaxNode
|
|
|
|
-- | Change the node label of the parent to be nested.
|
|
embedChildSyntaxNode :: ING.DynGraph gr =>
|
|
ING.Node -> ING.Node -> AnnotatedGraph gr -> AnnotatedGraph gr
|
|
embedChildSyntaxNode parentNode childNode oldGraph = newGraph
|
|
where
|
|
mChildAndEdge =
|
|
(,) <$> ING.lab oldGraph childNode
|
|
<*> findEdgeLabel oldGraph parentNode childNode
|
|
newGraph = case ING.lab oldGraph parentNode of
|
|
Nothing -> error "embedChildSyntaxNode: parentNode not found"
|
|
Just (NodeInfo isChild oldNodeLabel) ->
|
|
-- TODO Refactor with the Maybe Monad?
|
|
case mChildAndEdge of
|
|
Nothing -> error "embedChildSyntaxNode: childNode not found."
|
|
Just (NodeInfo _ childNodeLab, EmbedInfo _ edge)
|
|
-> changeNodeLabel
|
|
childNode
|
|
(NodeInfo (Just parentNode) childNodeLab)
|
|
$ changeNodeLabel parentNode newNodeLabel oldGraph
|
|
where
|
|
Named nodeName oldSyntaxNode = oldNodeLabel
|
|
newSyntaxNode = addChildToNodeLabel
|
|
(naName childNodeLab, edge)
|
|
oldSyntaxNode
|
|
newNodeLabel = NodeInfo isChild (Named nodeName newSyntaxNode)
|
|
|
|
collapseEdge :: (HasCallStack, ING.DynGraph gr)
|
|
=> AnnotatedGraph gr
|
|
-> ING.LEdge (EmbedInfo Edge)
|
|
-> AnnotatedGraph gr
|
|
collapseEdge oldGraph lEdge@(fromNode, toNode, EmbedInfo mEmbedDir _)
|
|
= case mEmbedDir of
|
|
Nothing -> oldGraph
|
|
Just embedDir -> ING.delLEdge lEdge childEmbeddedGraph
|
|
where
|
|
(parentNode, childNode) = parentAndChild embedDir (fromNode, toNode)
|
|
childEmbeddedGraph
|
|
= embedChildSyntaxNode parentNode childNode oldGraph
|
|
|
|
mapEdges :: (ING.Graph gr1, ING.Graph gr2)
|
|
=> (ING.LEdge b1 -> ING.LEdge b2)
|
|
-> gr1 a b1
|
|
-> gr2 a b2
|
|
mapEdges f gr = ING.mkGraph nodes mappedEdges
|
|
where
|
|
nodes = ING.labNodes gr
|
|
mappedEdges = f <$> ING.labEdges gr
|
|
|
|
findRootAncestor :: ING.Graph gr
|
|
=> gr (NodeInfo a) b -> ING.Node -> ING.Node
|
|
findRootAncestor gr node =
|
|
let nodeLab = fromMaybeError
|
|
"findRootAncestor: node does not exist"
|
|
(ING.lab gr node)
|
|
in
|
|
case niParent nodeLab of
|
|
Nothing -> node
|
|
Just parentNode -> findRootAncestor gr parentNode
|
|
|
|
-- Note: modifying the edges could probably be eliminated if the algorithms in
|
|
-- Rendering were re-written to us the node's parent.
|
|
-- | For all of the graph edges, this function moves edge to from and to nodes
|
|
-- of the edge to be root (the parents's parent parent etc.) of the edge's
|
|
-- from and to nodes.
|
|
moveEdges :: (ING.Graph gr1, ING.Graph gr2)
|
|
=> gr1 (NodeInfo a) b -> gr2 (NodeInfo a) b
|
|
moveEdges gr = mapEdges moveEdge gr
|
|
where
|
|
moveEdge (fromNode, toNode, label) = (newFrom, newTo, label)
|
|
where
|
|
newFrom = findRootAncestor gr fromNode
|
|
newTo = findRootAncestor gr toNode
|
|
|
|
collapseAnnotatedGraph :: (HasCallStack, ING.DynGraph gr)
|
|
=> gr SgNamedNode (EmbedInfo Edge)
|
|
-> AnnotatedGraph gr
|
|
collapseAnnotatedGraph origGraph = moveEdges newGraph
|
|
where
|
|
defaultNodeInfoGraph = ING.nmap (NodeInfo Nothing) origGraph
|
|
-- TODO Check that there are no embedded edges left.
|
|
newGraph = foldl' collapseEdge defaultNodeInfoGraph (ING.labEdges origGraph)
|