packages feed

calligraphy-0.1.6: src/Calligraphy/Phases/Render/Mermaid.hs

{-# LANGUAGE OverloadedStrings #-}

module Calligraphy.Phases.Render.Mermaid
  ( renderMermaid,
  )
where

import Calligraphy.Phases.Render.Common
import Calligraphy.Prelude hiding (Decl, DeclType, Node)
import Calligraphy.Util.Printer
import Calligraphy.Util.Types
import Control.Monad.State (State, execState, modify)
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Tree (Tree (..))

renderMermaid :: Prints RenderGraph
renderMermaid (RenderGraph roots calls types) = do
  textLn "flowchart TD"
  indent $ do
    case roots of
      Left modules -> mapM_ printModule modules
      Right trees -> mapM_ printTree trees
    forM_ (removeZeroEdges calls) $ \(caller, callee) ->
      strLn $ caller <> " --> " <> callee
    forM_ (removeZeroEdges types) $ \(caller, callee) ->
      strLn $ caller <> " -.-> " <> callee
    strLn "classDef default fill-opacity:0,stroke:#777;"
  where
    printTree :: Prints (Tree RenderNode)
    printTree (Node (RenderNode nodeid typ lbll export) []) = do
      strLn $ nodeid <> nodeShape typ (intercalate "\\n" lbll)
      unless export . strLn $
        "style " <> nodeid <> " stroke-dasharray: 5 5"
    printTree (Node (RenderNode nodeid _typ lbll export) children) = do
      brack ("subgraph " <> nodeid <> "[" <> intercalate "\\n" lbll <> "]") "end" $ do
        unless export . strLn $
          "style " <> nodeid <> " stroke-dasharray: 5 5"
        forM_ children printTree
    -- strLn $ nodeid <> " ~~~ " <> nodeId childNode

    printModule :: Prints RenderModule
    printModule (RenderModule lbl modId decls) =
      brack ("subgraph " <> modId <> " [" <> lbl <> "]") "end" $
        forM_ decls printTree

    -- Because we render hierarchies using subgraphs, there's an odd edge case
    -- when theres an edge between a parent and child; mermaid renders these as
    -- a zero-length edge, i.e. _just_ an arrowhead. So, we remove them.
    removeZeroEdges :: Set (ID, ID) -> Set (ID, ID)
    removeZeroEdges = execState (mapM_ go declRoots)
      where
        declRoots :: NonEmpty (Tree RenderNode)
        declRoots = case roots of
          Left mods -> mods >>= moduleDecls
          Right a -> a
        go :: Tree RenderNode -> State (Set (ID, ID)) ()
        go (Node (RenderNode parentId _ _ _) children) = do
          forM_ children $ \child@(Node (RenderNode childId _ _ _) _) -> do
            modify $ Set.delete (parentId, childId)
            go child

nodeShape :: DeclType -> String -> String
nodeShape DataDecl = wrapBracket "([" "])"
nodeShape ValueDecl = wrapBracket "[" "]"
nodeShape RecDecl = wrapBracket "(" ")"
nodeShape ConDecl = wrapBracket "(" ")"
nodeShape ClassDecl = wrapBracket "[/" "\\]"

wrapBracket :: String -> String -> String -> String
wrapBracket p q inner = p <> inner <> q