1 {-# LANGUAGE NoRebindableSyntax #-}
2 {-# OPTIONS_GHC -Wno-orphans #-}
4 module Literate.Accounting.Chart where
6 import Control.Applicative (Applicative (..))
7 import Control.DeepSeq (NFData (..))
8 import Control.Monad (Monad (..))
9 import Control.Monad.Trans.Writer qualified as MT
11 import Data.Eq (Eq (..))
12 import Data.Foldable (Foldable (..), all)
13 import Data.Function (const, flip, ($), (.))
14 import Data.Functor (Functor (..), (<$>))
15 import Data.List qualified as List
16 import Data.List.NonEmpty (NonEmpty (..))
17 import Data.List.NonEmpty qualified as NonEmpty
18 import Data.Map.Strict (Map)
19 import Data.Map.Strict qualified as Map
20 import Data.Maybe (Maybe (..), isNothing)
21 import Data.Monoid (Monoid (..))
22 import Data.Ord (Ord (..))
23 import Data.Semigroup (Semigroup (..))
24 import Data.String (String)
25 import Data.Traversable (Traversable (..))
26 import Data.Tuple (fst, snd)
27 import GHC.Exts qualified as GHC
28 import Text.Show (Show (..))
29 import Prelude (error)
32 import Literate.Accounting.Quantity
33 import Literate.Prelude hiding (lookup)
36 newtype Chart k a = Chart {unChart :: Map.Map k (a, Chart k a)}
37 deriving newtype (Eq, NFData)
38 instance (Show k, Show a) => Show (Chart k a) where
39 show = showChartAsTree
41 showChartAsTree :: Show k => Show a => Chart k a -> String
42 showChartAsTree = List.unlines . drawMap
44 -- drawNode :: (k, (a, Chart k a)) -> [String]
45 drawNode (k, (a, ts0)) =
48 (List.lines (showsPrec 11 k ""))
49 (List.lines (" " <> showsPrec 11 a "") <> List.repeat "")
51 drawMap = go . Map.toList . unChart
54 go [t] = shift "` " " " (drawNode t)
55 go (t : ts) = shift "+ " "| " (drawNode t) <> go ts
56 shift ind0 ind = List.zipWith (<>) (ind0 : List.repeat ind)
58 table :: [[String]] -> String
59 table cells = List.unlines (List.foldr (\cell acc -> "|" <> cell <> acc) "|" <$> rows)
62 maxWidths :: [Int] -- for each row
64 (maxCols, maxWidths, rows) = List.foldr go (0, List.repeat 0, []) cells
66 go :: [String] -> (Int, [Int], [[String]]) -> (Int, [Int], [[String]])
67 go row (accMaxCols, accMaxWidths, accRows) =
68 ( max accMaxCols (List.length row)
69 , List.zipWith max accMaxWidths (List.map List.length row <> List.repeat 0)
70 , List.take maxCols (List.zipWith alignLeft row maxWidths) : accRows
72 alignRight cell maxWidth = List.replicate (maxWidth - List.length cell) ' ' <> cell
73 alignLeft cell maxWidth = List.take maxWidth $ cell <> List.repeat ' '
75 {- >>> error $ table [["toto", "titi"], ["123", "4", "567890"], ["", "", "0"]]
81 instance Functor (Chart k) where
82 fmap f = Chart . fmap (\(a, ch) -> (f a, fmap f ch)) . unChart
83 instance Foldable (Chart k) where
84 foldMap f = foldMap (\(a, ch) -> f a <> foldMap f ch) . unChart
85 instance Traversable (Chart k) where
88 . traverse (\(a, ch) -> (,) <$> f a <*> traverse f ch)
90 instance (Semigroup a, Ord k) => Semigroup (Chart k a) where
94 (\new old -> (fst old <> fst new, snd old <> snd new))
97 instance (Semigroup a, Ord k) => Monoid (Chart k a) where
98 mempty = Chart Map.empty
99 instance (Ord k, Addable a) => Addable (Chart k a) where
103 (\(ym, ya) (xm, xa) -> (xm + ym, xa + ya))
106 instance (Ord k, Substractable a) => Substractable (Chart k a) where
110 (\(ym, ya) (xm, xa) -> (xm - ym, xa - ya))
114 -- ** Type 'ChartPath'
115 type ChartPath = NonEmpty.NonEmpty
117 -- | @('insert' merge path value chart)@
118 -- returns the given @chart@ with a mapping from @path@ to @value@,
119 -- using @merge value oldValue@ in case @path@ already map to an @oldValue@.
120 insert :: Ord k => a -> (a -> a -> a) -> ChartPath k -> a -> Chart k a -> Chart k a
121 insert init merge p a ch = go ch p
127 (\_new (old, c) -> (merge a old, c))
134 (\_new (old, c) -> (old, go c (k1 :| ks)))
136 (init, go empty (k1 :| ks))
139 -- | Return the value (if any) associated with the given 'Path'.
140 lookup :: Ord k => ChartPath k -> Chart k a -> Maybe a
141 lookup (k :| ks) (Chart m) = do
142 (a, ms) <- Map.lookup k m
145 (k' : ks') -> lookup (k' :| ks') ms
147 -- | @('filter' predicate chart)@ returns the given @chart@
148 -- with only the paths whose mappings satisfied the given @predicate@.
149 filter :: Ord k => (a -> Bool) -> Chart k a -> Chart k (Maybe a)
155 in let fm = filter f m
156 in if not fx && all isNothing fm
158 else Just (if fx then Just x else Nothing, fm)
162 -- | The empty 'Chart'.
164 empty = Chart Map.empty
166 -- | @('singleton' ancestorsValue path leafValue)@
167 -- returns a 'Chart' mapping @path@ to @leafValue@
168 -- and mapping all its ancestors paths to @ancestorsValue@.
169 singleton :: Ord k => a -> ChartPath k -> a -> Chart k a
170 singleton init ks a = insert init const ks a empty
172 -- | Return a 'Map' associating each 'ChartPath' in the given 'Chart',
173 -- with its value mapped by the given function.
174 flatten :: Ord k => (x -> y) -> Chart k x -> Map (ChartPath k) y
175 flatten = flattenWithPath . const
177 flattenWithPath :: Ord k => ([k] -> x -> y) -> Chart k x -> Map (ChartPath k) y
178 flattenWithPath = go []
183 (NonEmpty.reverse . flip (:|) p)
185 ( \k (a, _children) ->
186 f (List.reverse (k : p)) a
191 (\k (_a, children) -> (go (k : p) f children :))
195 mapByDepthFirst :: Ord k => (Chart k b -> a -> b) -> Chart k a -> Chart k b
199 (\(a, ch) -> let m = mapByDepthFirst f ch in (f m a, m))
202 foldrPath :: Ord k => (ChartPath k -> a -> acc -> acc) -> ChartPath k -> Chart k a -> acc -> acc
203 foldrPath f = go [] . NonEmpty.toList
206 go p (k : ks) (Chart m) acc =
207 case Map.lookup k m of
208 Just (a, ch) -> f (NonEmpty.reverse (k :| p)) a $ go (k : p) ks ch acc
211 foldrWithPath :: Ord k => (ChartPath k -> a -> acc -> acc) -> acc -> Chart k a -> acc
212 foldrWithPath f = go []
218 (NonEmpty.reverse (k :| p))