packages feed

dotparse-0.1.0.0: src/DotParse/Examples/AST.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

-- | Example of Dot graph construction for the <https://hackage.haskell.org/package/chart-svg chart-svg> class heirarchy.
module DotParse.Examples.AST where

import Algebra.Graph.Labelled qualified as L
import Data.ByteString (ByteString)
import Data.ByteString.Char8 qualified as B
import Data.List qualified as List
import Data.List.NonEmpty hiding (filter, head, length, map, zip, zipWith, (!!))
import Data.Map.Strict qualified as Map
import Data.Maybe
import Data.Monoid
import Data.String.Interpolate
import Data.These
import DotParse.Types
import GHC.Generics
import Optics.Core
import Prelude hiding (replicate)

-- $setup
-- >>> import DotParse
-- >>> :set -XOverloadedStrings

-- | A Haskell class and (informal) list of sub-components.
data SubComponents = SubComponents
  { classComponent :: ByteString,
    subComponents :: [ByteString]
  }
  deriving (Eq, Show, Ord, Generic)

-- | Relationship between a class, a sub-component and the class of the sub-component.
data ComponentEdge = ComponentEdge
  { edgeClassComponent :: ByteString,
    edgeSubComponent :: ByteString,
    subComponentClass :: ByteString,
    edgeLabel :: Maybe ByteString
  }
  deriving (Eq, Show, Ord, Generic)

-- | algebraic graph vertices
graphVs :: (Monoid a) => [SubComponents] -> L.Graph a (ByteString, ByteString)
graphVs cs =
  L.vertices $
    ((\x -> (x, x)) . view #classComponent <$> cs)
      <> (mconcat $ subs <$> cs)

-- | Convert sub-components to a list of class, subcomponent bytestring tuples.
subs :: SubComponents -> [(ByteString, ByteString)]
subs c = (view #classComponent c,) <$> view #subComponents c

-- | algebraic graph edges
graphEs :: [ComponentEdge] -> L.Graph (Maybe ByteString) (ByteString, ByteString)
graphEs es =
  L.edges ((\c -> (view #edgeLabel c, (view #edgeClassComponent c, view #edgeSubComponent c), (view #subComponentClass c, view #subComponentClass c))) <$> es)

-- | algebraic graph
graphAST :: [SubComponents] -> [ComponentEdge] -> L.Graph (Maybe ByteString) (ByteString, ByteString)
graphAST cs es =
  graphVs cs <> graphEs es

-- | Create a list of 'SubComponents' from a list of 'ComponentEdge's
fromCEs :: [ComponentEdge] -> [SubComponents]
fromCEs es = fmap (uncurry SubComponents) $ Map.toList $ Map.fromListWith (<>) ((\e -> (view #edgeClassComponent e, [view #edgeSubComponent e])) <$> es)

-- | Convert an algebraic Graph into dot record nodes
recordNodes :: L.Graph (Maybe ByteString) (ByteString, ByteString) -> [Statement]
recordNodes g = (\(s, cs) -> StatementNode $ NodeStatement (IDQuoted s) Nothing (Map.fromList ([(ID "label", IDQuoted (ls (s, cs)))] <> maybe [] (\url -> [(ID "URL", IDQuoted url)]) (toURL s)))) <$> supers
  where
    ls (s, cs) = B.intercalate "|" $ ("<x" <> s <> "> " <> s) : fmap (\y -> " <x" <> y <> "> " <> y) cs
    supers = (\(s, cs) -> (s, filter (/= s) cs)) <$> (Map.toList $ Map.fromListWith (++) ((\(s, c) -> (s, [c])) <$> L.vertexList g))

-- | Convert an algebraic Graph into dot edges
recordEdges :: Directed -> L.Graph (Maybe ByteString) (ByteString, ByteString) -> [Statement]
recordEdges d g =
  ( \(l, (s0, c0), (s1, c1)) ->
      StatementEdge $
        EdgeStatement
          (fromDirected d)
          (EdgeID (IDQuoted s0) (Just (Port (This (IDQuoted ("x" <> c0))))))
          (fromList [EdgeID (IDQuoted c1) (Just (Port (This (IDQuoted ("x" <> s1)))))])
          (Map.fromList [(ID "label", IDQuoted (fromMaybe "x" l))])
  )
    <$> L.edgeList g

-- | create Statements from a (edge labelled) algebraic graph
--
-- https://graphviz.org/Gallery/directed/datastruct.html
toStatementsRecord :: Directed -> L.Graph (Maybe ByteString) (ByteString, ByteString) -> [Statement]
toStatementsRecord d g =
  recordEdges d g <> recordNodes g

-- | Convert a node ID to a label for chart-svg charts
-- Doing this directly in dot doesn't quite work because the engines get the width of the link wrong.
toURL :: ByteString -> Maybe ByteString
toURL name = fmap (\i' -> [i|https://hackage.haskell.org/package/#{view #itemPackage i'}/docs/#{view #itemModule i'}.html\#t:#{view #item i'}|]) item
  where
    item = List.find ((== name) . view #item) itemModules

-- | AST 'Graph'
--
-- > gAST = dotAST allSC componentEdges
-- > C.writeFile "other/ast.dot" $ dotPrint defaultDotConfig gAST
-- > bsSvg <- processDotWith Directed ["-Tsvg"] (dotPrint defaultDotConfig gAST)
-- > C.writeFile "other/ast.svg" bsSvg
dotAST :: [SubComponents] -> [ComponentEdge] -> Graph
dotAST sc ce =
  defaultGraph
    & set (attL GraphType (ID "size")) (Just $ IDQuoted "5")
    & addStatements (toStatementsRecord Directed (graphAST sc ce))
    & attL NodeType (ID "shape")
    .~ Just (ID "record")
    & gattL (ID "rankdir")
    .~ Just (IDQuoted "LR")

-- | Link values
data ItemModule = ItemModule {item :: ByteString, itemModule :: ByteString, itemPackage :: ByteString} deriving (Eq, Show, Generic)

-- | List of link values
itemModules :: [ItemModule]
itemModules =
  [ ItemModule "ChartOptions" "Chart-Markup" "chart-svg",
    ItemModule "MarkupOptions" "Chart-Markup" "chart-svg",
    ItemModule "CssOptions" "Chart-Markup" "chart-svg",
    ItemModule "ChartTree" "Chart-Primitive" "chart-svg",
    ItemModule "Chart" "Chart-Primitive" "chart-svg",
    ItemModule "HudOptions" "Chart-Hud" "chart-svg",
    ItemModule "RenderStyle" "Chart-Markup" "chart-svg",
    ItemModule "ChartAspect" "Chart-Primitive" "chart-svg",
    ItemModule "ShapeRendering" "Chart-Markup" "chart-svg",
    ItemModule "PreferColorScheme" "Chart-Primitive" "chart-svg",
    ItemModule "Tree" "Data-Tree" "containers",
    ItemModule "Priority" "Chart-Hud" "chart-svg",
    ItemModule "TitleOptions" "Chart-Hud" "chart-svg",
    ItemModule "AxisOptions" "Chart-Hud" "chart-svg",
    ItemModule "LegendOptions" "Chart-Hud" "chart-svg",
    ItemModule "FrameOptions" "Chart-Hud" "chart-svg",
    ItemModule "Adjustments" "Chart-Hud" "chart-svg",
    ItemModule "Ticks" "Chart-Hud" "chart-svg",
    ItemModule "Tick" "Chart-Hud" "chart-svg",
    ItemModule "Place" "Chart-Hud" "chart-svg",
    ItemModule "TickStyle" "Chart-Hud" "chart-svg",
    ItemModule "AxisBar" "Chart-Hud" "chart-svg",
    ItemModule "HudChartSection" "Chart-Hud" "chart-svg",
    ItemModule "TickExtend" "Chart-Hud" "chart-svg",
    ItemModule "FormatN" "Data-FormatN" "formatn",
    ItemModule "FStyle" "Data-FormatN" "formatn",
    ItemModule "Colour" "Data-Colour" "chart-svg",
    ItemModule "Style" "Chart-Style" "chart-svg",
    ItemModule "EscapeText" "Chart-Style" "chart-svg",
    ItemModule "GlyphShape" "Chart-Style" "chart-svg",
    ItemModule "Anchor" "Chart-Style" "chart-svg",
    ItemModule "LineCap" "Chart-Style" "chart-svg",
    ItemModule "LineJoin" "Chart-Style" "chart-svg",
    ItemModule "ScaleP" "Chart-Style" "chart-svg",
    ItemModule "ChartData" "Chart-Primitive" "chart-svg",
    ItemModule "PathData" "Data-Path" "chart-svg",
    ItemModule "ArcInfo" "Data-Path" "chart-svg",
    ItemModule "Rect" "NumHask-Space-Rect" "numhask-space",
    ItemModule "Point" "NumHask-Space-Point" "numhask-space"
  ]

-- | list of chart-svg component edges
componentEdges :: [ComponentEdge]
componentEdges =
  [ ComponentEdge "ChartOptions" "markupOptions" "MarkupOptions" (Just ""),
    ComponentEdge "ChartOptions" "hudOptions" "HudOptions" (Just ""),
    ComponentEdge "ChartOptions" "chartTree" "ChartTree" (Just ""),
    ComponentEdge "MarkupOptions" "chartAspect" "ChartAspect" (Just ""),
    ComponentEdge "MarkupOptions" "cssOptions" "CssOptions" (Just ""),
    ComponentEdge "MarkupOptions" "renderStyle" "RenderStyle" (Just ""),
    ComponentEdge "CssOptions" "shapeRendering" "ShapeRendering" (Just ""),
    ComponentEdge "CssOptions" "preferColorScheme" "PreferColorScheme" (Just ""),
    ComponentEdge "HudOptions" "axes" "AxisOptions" (Just "each % #item"),
    ComponentEdge "HudOptions" "frames" "FrameOptions" (Just "each % #item"),
    ComponentEdge "HudOptions" "legends" "LegendOptions" (Just "each % #item"),
    ComponentEdge "HudOptions" "titles" "TitleOptions" (Just "each % #item"),
    ComponentEdge "HudOptions" "axes" "Priority" (Just "each % #priority"),
    ComponentEdge "HudOptions" "frames" "Priority" (Just "each % #priority"),
    ComponentEdge "HudOptions" "legends" "Priority" (Just "each % #priority"),
    ComponentEdge "HudOptions" "titles" "Priority" (Just "each % #priority"),
    ComponentEdge "AxisOptions" "axisBar" "AxisBar" (Just "_Just"),
    ComponentEdge "AxisOptions" "adjustments" "Adjustments" (Just "_Just"),
    ComponentEdge "AxisOptions" "ticks" "Ticks" (Just ""),
    ComponentEdge "AxisOptions" "place" "Place" (Just ""),
    ComponentEdge "AxisBar" "style" "Style" (Just ""),
    ComponentEdge "AxisBar" "anchorTo" "HudChartSection" (Just ""),
    ComponentEdge "Tick" "formatN'" "FormatN" (Just "_Just"),
    ComponentEdge "FormatN" "fstyle" "FStyle" (Just ""),
    ComponentEdge "Tick" "tickExtend'" "TickExtend" (Just "_Just"),
    ComponentEdge "Ticks" "tick" "Tick" (Just "_Just"),
    ComponentEdge "Ticks" "glyphTick" "TickStyle" (Just "_Just"),
    ComponentEdge "Ticks" "textTick" "TickStyle" (Just "_Just"),
    ComponentEdge "Ticks" "lineTick" "TickStyle" (Just "_Just"),
    ComponentEdge "TickStyle" "style" "Style" (Just ""),
    ComponentEdge "TickStyle" "anchorTo" "HudChartSection" (Just ""),
    ComponentEdge "FrameOptions" "frame" "Style" (Just "#frame % _Just"),
    ComponentEdge "FrameOptions" "anchorTo" "HudChartSection" (Just ""),
    ComponentEdge "TitleOptions" "style" "Style" (Just ""),
    ComponentEdge "TitleOptions" "place" "Place" (Just ""),
    ComponentEdge "TitleOptions" "anchor" "Anchor" (Just ""),
    ComponentEdge "ChartTree" "tree" "Tree" (Just ""),
    ComponentEdge "ChartTree" "charts'" "Chart" (Just "each"),
    ComponentEdge "Chart" "chartStyle" "Style" (Just ""),
    ComponentEdge "Chart" "chartData" "ChartData" (Just ""),
    ComponentEdge "ChartData" "rectData'" "Rect" (Just "_Just % each"),
    ComponentEdge "ChartData" "lineData'" "Point" (Just "_Just % each % each"),
    ComponentEdge "ChartData" "glyphData'" "Point" (Just "_Just % each"),
    ComponentEdge "ChartData" "textData'" "(Text,Point)" (Just "_Just % each"),
    ComponentEdge "(Text,Point)" "_2" "Point" (Just ""),
    ComponentEdge "ChartData" "pathData'" "PathData" (Just "_Just % each"),
    ComponentEdge "ChartData" "blankData'" "Rect" (Just "_Just % each"),
    ComponentEdge "LegendOptions" "textStyle" "Style" (Just ""),
    ComponentEdge "LegendOptions" "frame" "Style" (Just "_Just"),
    ComponentEdge "LegendOptions" "place" "Place" (Just ""),
    ComponentEdge "LegendOptions" "scaleP" "ScaleP" (Just ""),
    ComponentEdge "LegendOptions" "legendCharts" "Chart" (Just "each % _2 % each"),
    ComponentEdge "PathData" "ArcP" "ArcInfo" (Just "(ArcP arcinfo _)"),
    ComponentEdge "Style" "color" "Colour" (Just ""),
    ComponentEdge "Style" "borderColor" "Colour" (Just ""),
    ComponentEdge "Style" "scaleP" "ScaleP" (Just ""),
    ComponentEdge "Style" "anchor" "Anchor" (Just ""),
    ComponentEdge "Style" "translate" "Point" (Just "_Just"),
    ComponentEdge "Style" "escapeText" "EscapeText" (Just ""),
    ComponentEdge "Style" "frame" "Style" (Just "_Just"),
    ComponentEdge "Style" "lineCap" "LineCap" (Just "_Just"),
    ComponentEdge "Style" "lineJoin" "LineJoin" (Just "_Just"),
    ComponentEdge "Style" "glyphShape" "GlyphShape" (Just "")
  ]

-- | list of chart-svg subcomponents
allSC :: [SubComponents]
allSC =
  [ SubComponents
      { classComponent = "AxisBar",
        subComponents =
          [ "style",
            "size",
            "buffer",
            "overhang",
            "anchorTo"
          ]
      },
    SubComponents
      { classComponent = "AxisOptions",
        subComponents =
          [ "axisBar",
            "adjustments",
            "ticks",
            "place"
          ]
      },
    SubComponents
      { classComponent = "Chart",
        subComponents =
          [ "chartStyle",
            "chartData"
          ]
      },
    SubComponents
      { classComponent = "ChartData",
        subComponents =
          [ "rectData'",
            "lineData'",
            "glyphData'",
            "textData'",
            "pathData'",
            "blankData'"
          ]
      },
    SubComponents
      { classComponent = "ChartOptions",
        subComponents =
          [ "markupOptions",
            "hudOptions",
            "chartTree"
          ]
      },
    SubComponents
      { classComponent = "ChartTree",
        subComponents =
          [ "tree",
            "charts'"
          ]
      },
    SubComponents
      { classComponent = "FrameOptions",
        subComponents =
          [ "frame",
            "anchorTo",
            "buffer"
          ]
      },
    SubComponents
      { classComponent = "HudOptions",
        subComponents =
          [ "axes",
            "frames",
            "legends",
            "titles"
          ]
      },
    SubComponents
      { classComponent = "MarkupOptions",
        subComponents =
          [ "markupHeight",
            "chartAspect",
            "cssOptions",
            "renderStyle"
          ]
      },
    SubComponents
      { classComponent = "(Text,Point)",
        subComponents =
          [ "_1",
            "_2"
          ]
      },
    SubComponents
      { classComponent = "TickStyle",
        subComponents =
          [ "style",
            "anchorTo",
            "buffer"
          ]
      },
    SubComponents
      { classComponent = "Ticks",
        subComponents =
          [ "tick",
            "glyphTick",
            "textTick",
            "lineTick"
          ]
      },
    SubComponents
      { classComponent = "TitleOptions",
        subComponents =
          [ "text",
            "style",
            "place",
            "anchor",
            "buffer"
          ]
      },
    SubComponents
      { classComponent = "RenderStyle",
        subComponents =
          [ "Compact",
            "Indented"
          ]
      },
    SubComponents
      { classComponent = "CssOptions",
        subComponents =
          [ "shapeRendering",
            "preferColorScheme",
            "fontFamilies",
            "cssExtra"
          ]
      },
    SubComponents
      { classComponent = "ChartAspect",
        subComponents =
          [ "FixedAspect",
            "CanvasAspect",
            "ChartAspect",
            "UnscaledAspect"
          ]
      },
    SubComponents
      { classComponent = "HudChartSection",
        subComponents =
          [ "CanvasSection",
            "CanvasStyleSection",
            "HudSection",
            "HudStyleSection"
          ]
      },
    SubComponents
      { classComponent = "Adjustments",
        subComponents =
          [ "maxXRatio",
            "maxYRatio",
            "angledRatio",
            "allowDiagonal"
          ]
      },
    SubComponents
      { classComponent = "Tick",
        subComponents =
          [ "TickNone",
            "TickLabels",
            "TickRound",
            "TickExact",
            "TickPlaced",
            "numTicks'",
            "formatN'",
            "tickExtend'"
          ]
      },
    SubComponents
      { classComponent = "TickExtend",
        subComponents =
          [ "TickExtend",
            "NoTickExtend"
          ]
      },
    SubComponents
      { classComponent = "FStyle",
        subComponents =
          [ "FSDecimal",
            "FSExponent",
            "FSComma",
            "FSFixed Int",
            "FSPercent",
            "FSDollar",
            "FSPrec",
            "FSCommaPrec",
            "FSNone"
          ]
      },
    SubComponents
      { classComponent = "FormatN",
        subComponents =
          [ "fstyle",
            "sigFigs",
            "maxDistinguishIterations",
            "addLPad",
            "cutRightZeros"
          ]
      },
    SubComponents
      { classComponent = "ShapeRendering",
        subComponents =
          [ "UseGeometricPrecision",
            "UseCssCrisp",
            "NoShapeRendering"
          ]
      },
    SubComponents
      { classComponent = "PreferColorScheme",
        subComponents =
          [ "PreferHud",
            "PreferDark",
            "PreferLight",
            "PreferNormal"
          ]
      },
    SubComponents
      { classComponent = "Place",
        subComponents =
          [ "PlaceLeft",
            "PlaceRight",
            "PlaceTop",
            "PlaceBottom",
            "PlaceAbsolute"
          ]
      },
    SubComponents
      { classComponent = "LegendOptions",
        subComponents =
          [ "legendSize",
            "buffer",
            "vgap",
            "hgap",
            "textStyle",
            "innerPad",
            "outerPad",
            "frame",
            "place",
            "scaleChartsBy",
            "scaleP",
            "legendCharts"
          ]
      },
    SubComponents
      { classComponent = "Anchor",
        subComponents =
          [ "AnchorMiddle",
            "AnchorStart",
            "AnchorEnd"
          ]
      },
    SubComponents
      { classComponent = "Point",
        subComponents =
          [ "_x",
            "_y"
          ]
      },
    SubComponents
      { classComponent = "PathData",
        subComponents =
          [ "StartP",
            "LineP",
            "CubicP",
            "QuadP",
            "ArcP"
          ]
      },
    SubComponents
      { classComponent = "ArcInfo",
        subComponents =
          [ "radii",
            "phi",
            "large",
            "clockwise"
          ]
      },
    SubComponents
      { classComponent = "Style",
        subComponents =
          [ "size",
            "borderSize",
            "color",
            "borderColor",
            "scaleP",
            "anchor",
            "rotation",
            "translate",
            "escapeText",
            "frame",
            "lineCap",
            "lineJoin",
            "dasharray",
            "dashoffset",
            "hsize",
            "vsize",
            "vshift",
            "glyphShape"
          ]
      },
    SubComponents
      { classComponent = "ScaleP",
        subComponents =
          [ "NoScaleP",
            "ScalePX",
            "ScalePY",
            "ScalePMinDim",
            "ScalePArea"
          ]
      },
    SubComponents
      { classComponent = "Colour",
        subComponents =
          [ "opac'",
            "lightness'",
            "chroma'",
            "hue'"
          ]
      },
    SubComponents
      { classComponent = "EscapeText",
        subComponents =
          [ "EscapeText",
            "NoEscapeText"
          ]
      },
    SubComponents
      { classComponent = "LineCap",
        subComponents =
          [ "LineCapButt",
            "LineCapRound",
            "LineCapSquare"
          ]
      },
    SubComponents
      { classComponent = "LineJoin",
        subComponents =
          [ "LineJoinMiter",
            "LineJoinBevel",
            "LineJoinRound"
          ]
      },
    SubComponents
      { classComponent = "GlyphShape",
        subComponents =
          [ "CircleGlyph",
            "SquareGlyph",
            "EllipseGlyph",
            "RectSharpGlyph",
            "RectRoundedGlyph",
            "TriangleGlyph",
            "VLineGlyph",
            "HLineGlyph",
            "PathGlyph"
          ]
      }
  ]