{- FOURMOLU_DISABLE -}
-- The MIT License (MIT)

-- Copyright (c) 2016-2024 Objectionary.com

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:

-- The above copyright notice and this permission notice shall be included
-- in all copies or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
{- FOURMOLU_ENABLE -}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Language.EO.Phi.Metrics.Collect where

import Control.Lens ((+=))
import Control.Monad.State (State, execState, runState)
import Data.Foldable (forM_)
import Data.Generics.Labels ()
import Data.Maybe (catMaybes)
import Data.Traversable (forM)
import Language.EO.Phi.Metrics.Data (BindingMetrics (..), BindingsByPathMetrics (..), MetricsCount, ObjectMetrics (..), Path, ProgramMetrics (..))
import Language.EO.Phi.Rules.Common ()
import Language.EO.Phi.Syntax.Abs

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> :set -XOverloadedLists

type HeightSafe = Maybe Int

count :: (a -> Bool) -> [a] -> Int
count :: forall a. (a -> Bool) -> [a] -> Int
count a -> Bool
x = [a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([a] -> Int) -> ([a] -> [a]) -> [a] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
filter a -> Bool
x

getHeight :: [Binding] -> [HeightSafe] -> HeightSafe
getHeight :: [Binding] -> [HeightSafe] -> HeightSafe
getHeight [Binding]
bindings [HeightSafe]
heights
  | Bool
hasDeltaBinding = Int -> HeightSafe
forall a. a -> Maybe a
Just Int
1
  | Bool
otherwise = HeightSafe
heightAttributes
 where
  heightAttributes :: HeightSafe
heightAttributes =
    case [HeightSafe] -> [Int]
forall a. [Maybe a] -> [a]
catMaybes [HeightSafe]
heights of
      [] -> HeightSafe
forall a. Maybe a
Nothing
      [Int]
x -> Int -> HeightSafe
forall a. a -> Maybe a
Just ([Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum [Int]
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)

  isBinding :: Binding -> Bool
isBinding = \case
    DeltaBinding Bytes
_ -> Bool
True
    Binding
_ -> Bool
False

  hasDeltaBinding :: Bool
hasDeltaBinding = (Binding -> Bool) -> [Binding] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Binding -> Bool
isBinding [Binding]
bindings

countDataless :: HeightSafe -> Int
countDataless :: HeightSafe -> Int
countDataless (Just Int
x)
  | Int
x Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2 = Int
1
  | Bool
otherwise = Int
0
countDataless HeightSafe
_ = Int
1

type InspectM = State MetricsCount HeightSafe

class Inspectable a where
  inspect :: a -> InspectM

instance Inspectable Binding where
  inspect :: Binding -> InspectM
  inspect :: Binding -> InspectM
inspect = \case
    AlphaBinding Attribute
_ Object
obj -> Object -> InspectM
forall a. Inspectable a => a -> InspectM
inspect Object
obj
    Binding
_ -> HeightSafe -> InspectM
forall a. a -> StateT MetricsCount Identity a
forall (f :: * -> *) a. Applicative f => a -> f a
pure HeightSafe
forall a. Maybe a
Nothing

instance Inspectable Object where
  inspect :: Object -> InspectM
  inspect :: Object -> InspectM
inspect = \case
    Formation [Binding]
bindings -> do
      #formations += 1
      heights <- forM bindings inspect
      let height = getHeight bindings heights
      #dataless += countDataless height
      pure height
    Application Object
obj [Binding]
bindings -> do
      #applications += 1
      _ <- inspect obj
      forM_ bindings inspect
      pure Nothing
    ObjectDispatch Object
obj Attribute
_ -> do
      #dispatches += 1
      _ <- inspect obj
      pure Nothing
    Object
_ -> HeightSafe -> InspectM
forall a. a -> StateT MetricsCount Identity a
forall (f :: * -> *) a. Applicative f => a -> f a
pure HeightSafe
forall a. Maybe a
Nothing

-- | Get metrics for an object
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ξ, α0 ↦ Φ.org.eolang.bytes( Δ ⤍ 00- ) ⟧"
-- Metrics {dataless = 1, applications = 1, formations = 1, dispatches = 3}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ξ, Δ ⤍ 00- ⟧"
-- Metrics {dataless = 0, applications = 0, formations = 1, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ξ, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ⟧"
-- Metrics {dataless = 0, applications = 0, formations = 2, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ξ, α1 ↦ ⟦ α2 ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧"
-- Metrics {dataless = 1, applications = 0, formations = 3, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ Δ ⤍ 00- ⟧"
-- Metrics {dataless = 0, applications = 0, formations = 1, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ⟦ α0 ↦ ∅ ⟧ ⟧"
-- Metrics {dataless = 2, applications = 0, formations = 2, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ⟦ α0 ↦ ⟦ α0 ↦ ∅ ⟧ ⟧ ⟧"
-- Metrics {dataless = 3, applications = 0, formations = 3, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ⟦ α0 ↦ ⟦ α0 ↦ ⟦ α0 ↦ ∅ ⟧ ⟧ ⟧ ⟧"
-- Metrics {dataless = 4, applications = 0, formations = 4, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ org ↦ ⟦ ⟧ ⟧"
-- Metrics {dataless = 2, applications = 0, formations = 2, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧.e ⟧"
-- Metrics {dataless = 5, applications = 1, formations = 5, dispatches = 5}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ Φ.something(α1 ↦ ⟦ α2 ↦ ⟦ α3 ↦ ⟦ Δ ⤍ 01- ⟧ ⟧ ⟧) ⟧"
-- Metrics {dataless = 2, applications = 1, formations = 4, dispatches = 1}
--
-- >>> getThisObjectMetrics "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c, Δ ⤍ 01- ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦ Δ ⤍ 01- ⟧).d ⟧.e ⟧"
-- Metrics {dataless = 2, applications = 1, formations = 5, dispatches = 5}
--
-- >>> getThisObjectMetrics "⟦ org ↦ ⟦ Δ ⤍ 01-, c ↦ ∅ ⟧(c ↦ ⟦ ⟧) ⟧"
-- Metrics {dataless = 2, applications = 1, formations = 3, dispatches = 0}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ⟦ α0 ↦ ⟦ α0 ↦ ⟦ α0 ↦ ⟦ Δ ⤍ 01- ⟧.a ⟧ ⟧ ⟧ ⟧"
-- Metrics {dataless = 4, applications = 0, formations = 5, dispatches = 1}
--
-- >>> getThisObjectMetrics "⟦ α0 ↦ ⟦ α0 ↦ ⟦ ⟧.a ⟧, α0 ↦ ⟦ Δ ⤍ 01- ⟧.b ⟧"
-- Metrics {dataless = 3, applications = 0, formations = 4, dispatches = 2}
getThisObjectMetrics :: Object -> MetricsCount
getThisObjectMetrics :: Object -> MetricsCount
getThisObjectMetrics Object
obj = InspectM -> MetricsCount -> MetricsCount
forall s a. State s a -> s -> s
execState (Object -> InspectM
forall a. Inspectable a => a -> InspectM
inspect Object
obj) MetricsCount
forall a. Monoid a => a
mempty

-- | Get an object by a path within a given object.
--
-- If no object is accessible by the path, return a prefix of the path that led to a non-formation when the remaining path wasn't empty.
-- >>> flip getObjectByPath ["org", "eolang"] "⟦ org ↦ ⟦ eolang ↦ ⟦ x ↦ ⟦ φ ↦ Φ.org.eolang.bool ( α0 ↦ Φ.org.eolang.bytes (Δ ⤍ 01-) ) ⟧, z ↦ ⟦ y ↦ ⟦ x ↦ ∅, φ ↦ ξ.x ⟧, φ ↦ Φ.org.eolang.bool ( α0 ↦ Φ.org.eolang.bytes (Δ ⤍ 01-) ) ⟧, λ ⤍ Package ⟧, λ ⤍ Package ⟧⟧"
-- Right (Formation [AlphaBinding (Label (LabelId "x")) (Formation [AlphaBinding Phi (Application (ObjectDispatch (ObjectDispatch (ObjectDispatch GlobalObject (Label (LabelId "org"))) (Label (LabelId "eolang"))) (Label (LabelId "bool"))) [AlphaBinding (Alpha (AlphaIndex "\945\&0")) (Application (ObjectDispatch (ObjectDispatch (ObjectDispatch GlobalObject (Label (LabelId "org"))) (Label (LabelId "eolang"))) (Label (LabelId "bytes"))) [DeltaBinding (Bytes "01-")])])]),AlphaBinding (Label (LabelId "z")) (Formation [AlphaBinding (Label (LabelId "y")) (Formation [EmptyBinding (Label (LabelId "x")),AlphaBinding Phi (ObjectDispatch ThisObject (Label (LabelId "x")))]),AlphaBinding Phi (Application (ObjectDispatch (ObjectDispatch (ObjectDispatch GlobalObject (Label (LabelId "org"))) (Label (LabelId "eolang"))) (Label (LabelId "bool"))) [AlphaBinding (Alpha (AlphaIndex "\945\&0")) (Application (ObjectDispatch (ObjectDispatch (ObjectDispatch GlobalObject (Label (LabelId "org"))) (Label (LabelId "eolang"))) (Label (LabelId "bytes"))) [DeltaBinding (Bytes "01-")])])]),LambdaBinding (Function "Package")])
--
-- >>> flip getObjectByPath ["a"] "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧.e ⟧"
-- Right (ObjectDispatch (Formation [AlphaBinding (Label (LabelId "b")) (Formation [EmptyBinding (Label (LabelId "c")),AlphaBinding (Label (LabelId "d")) (Formation [AlphaBinding Phi (ObjectDispatch (ObjectDispatch ThisObject Rho) (Label (LabelId "c")))])]),AlphaBinding (Label (LabelId "e")) (ObjectDispatch (Application (ObjectDispatch ThisObject (Label (LabelId "b"))) [AlphaBinding (Label (LabelId "c")) (Formation [])]) (Label (LabelId "d")))]) (Label (LabelId "e")))
getObjectByPath :: Object -> Path -> Either Path Object
getObjectByPath :: Object -> Path -> Either Path Object
getObjectByPath Object
object Path
path =
  case Path
path of
    [] -> Object -> Either Path Object
forall a b. b -> Either a b
Right Object
object
    (String
p : Path
ps) ->
      case Object
object of
        Formation [Binding]
bindings ->
          case [Object]
objectByPath' of
            [] -> Path -> Either Path Object
forall a b. a -> Either a b
Left Path
path
            (Object
x : [Object]
_) -> Object -> Either Path Object
forall a b. b -> Either a b
Right Object
x
         where
          objectByPath' :: [Object]
objectByPath' =
            do
              Binding
x <- [Binding]
bindings
              Right Object
obj <-
                case Binding
x of
                  AlphaBinding (Alpha (AlphaIndex String
name)) Object
obj | String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
p -> [Object -> Path -> Either Path Object
getObjectByPath Object
obj Path
ps]
                  AlphaBinding (Label (LabelId String
name)) Object
obj | String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
p -> [Object -> Path -> Either Path Object
getObjectByPath Object
obj Path
ps]
                  Binding
_ -> [Path -> Either Path Object
forall a b. a -> Either a b
Left Path
path]
              Object -> [Object]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure Object
obj
        Object
_ -> Path -> Either Path Object
forall a b. a -> Either a b
Left Path
path

-- | Get metrics for bindings of a formation that is accessible by a path within a given object.
--
-- If no formation is accessible by the path, return a prefix of the path that led to a non-formation when the remaining path wasn't empty.
-- >>> flip getBindingsByPathMetrics ["a"] "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧.e ⟧"
-- Left ["a"]
--
-- >>> flip getBindingsByPathMetrics ["a"] "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧ ⟧"
-- Right (BindingsByPathMetrics {path = ["a"], bindingsMetrics = [BindingMetrics {name = "b", metrics = Metrics {dataless = 2, applications = 0, formations = 2, dispatches = 2}},BindingMetrics {name = "e", metrics = Metrics {dataless = 1, applications = 1, formations = 1, dispatches = 2}}]})
getBindingsByPathMetrics :: Object -> Path -> Either Path BindingsByPathMetrics
getBindingsByPathMetrics :: Object -> Path -> Either Path BindingsByPathMetrics
getBindingsByPathMetrics Object
object Path
path =
  case Object -> Path -> Either Path Object
getObjectByPath Object
object Path
path of
    Right (Formation [Binding]
bindings) ->
      let attributes' :: [(HeightSafe, MetricsCount)]
attributes' = (InspectM -> MetricsCount -> (HeightSafe, MetricsCount))
-> MetricsCount -> InspectM -> (HeightSafe, MetricsCount)
forall a b c. (a -> b -> c) -> b -> a -> c
flip InspectM -> MetricsCount -> (HeightSafe, MetricsCount)
forall s a. State s a -> s -> (a, s)
runState MetricsCount
forall a. Monoid a => a
mempty (InspectM -> (HeightSafe, MetricsCount))
-> (Binding -> InspectM) -> Binding -> (HeightSafe, MetricsCount)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Binding -> InspectM
forall a. Inspectable a => a -> InspectM
inspect (Binding -> (HeightSafe, MetricsCount))
-> [Binding] -> [(HeightSafe, MetricsCount)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Binding]
bindings
          ([HeightSafe]
_, [MetricsCount]
objectMetrics) = [(HeightSafe, MetricsCount)] -> ([HeightSafe], [MetricsCount])
forall a b. [(a, b)] -> ([a], [b])
unzip [(HeightSafe, MetricsCount)]
attributes'
          bindingsMetrics :: [BindingMetrics]
bindingsMetrics = do
            (Binding, MetricsCount)
x <- [Binding] -> [MetricsCount] -> [(Binding, MetricsCount)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Binding]
bindings [MetricsCount]
objectMetrics
            case (Binding, MetricsCount)
x of
              (AlphaBinding (Alpha (AlphaIndex String
name)) Object
_, MetricsCount
metrics) -> [BindingMetrics{String
MetricsCount
name :: String
metrics :: MetricsCount
$sel:name:BindingMetrics :: String
$sel:metrics:BindingMetrics :: MetricsCount
..}]
              (AlphaBinding (Label (LabelId String
name)) Object
_, MetricsCount
metrics) -> [BindingMetrics{String
MetricsCount
$sel:name:BindingMetrics :: String
$sel:metrics:BindingMetrics :: MetricsCount
name :: String
metrics :: MetricsCount
..}]
              (Binding, MetricsCount)
_ -> []
       in BindingsByPathMetrics -> Either Path BindingsByPathMetrics
forall a b. b -> Either a b
Right (BindingsByPathMetrics -> Either Path BindingsByPathMetrics)
-> BindingsByPathMetrics -> Either Path BindingsByPathMetrics
forall a b. (a -> b) -> a -> b
$ BindingsByPathMetrics{Path
[BindingMetrics]
path :: Path
bindingsMetrics :: [BindingMetrics]
$sel:path:BindingsByPathMetrics :: Path
$sel:bindingsMetrics:BindingsByPathMetrics :: [BindingMetrics]
..}
    Right Object
_ -> Path -> Either Path BindingsByPathMetrics
forall a b. a -> Either a b
Left Path
path
    Left Path
path' -> Path -> Either Path BindingsByPathMetrics
forall a b. a -> Either a b
Left Path
path'

-- | Get metrics for an object and for bindings of a formation accessible by a given path.
--
-- Combine metrics produced by 'getThisObjectMetrics' and 'getBindingsByPathMetrics'.
--
-- If no formation is accessible by the path, return a prefix of the path that led to a non-formation when the remaining path wasn't empty.
-- >>> flip getObjectMetrics (Just ["a"]) "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧.e ⟧"
-- Left ["a"]
--
-- >>> flip getObjectMetrics (Just ["a"]) "⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧ ⟧"
-- Right (ObjectMetrics {bindingsByPathMetrics = Just (BindingsByPathMetrics {path = ["a"], bindingsMetrics = [BindingMetrics {name = "b", metrics = Metrics {dataless = 2, applications = 0, formations = 2, dispatches = 2}},BindingMetrics {name = "e", metrics = Metrics {dataless = 1, applications = 1, formations = 1, dispatches = 2}}]}), thisObjectMetrics = Metrics {dataless = 5, applications = 1, formations = 5, dispatches = 4}})
getObjectMetrics :: Object -> Maybe Path -> Either Path ObjectMetrics
getObjectMetrics :: Object -> Maybe Path -> Either Path ObjectMetrics
getObjectMetrics Object
object Maybe Path
path = do
  let thisObjectMetrics :: MetricsCount
thisObjectMetrics = Object -> MetricsCount
getThisObjectMetrics Object
object
  Maybe BindingsByPathMetrics
bindingsByPathMetrics <- Maybe Path
-> (Path -> Either Path BindingsByPathMetrics)
-> Either Path (Maybe BindingsByPathMetrics)
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM Maybe Path
path ((Path -> Either Path BindingsByPathMetrics)
 -> Either Path (Maybe BindingsByPathMetrics))
-> (Path -> Either Path BindingsByPathMetrics)
-> Either Path (Maybe BindingsByPathMetrics)
forall a b. (a -> b) -> a -> b
$ \Path
path' -> Object -> Path -> Either Path BindingsByPathMetrics
getBindingsByPathMetrics Object
object Path
path'
  ObjectMetrics -> Either Path ObjectMetrics
forall a. a -> Either Path a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ObjectMetrics{Maybe BindingsByPathMetrics
MetricsCount
thisObjectMetrics :: MetricsCount
bindingsByPathMetrics :: Maybe BindingsByPathMetrics
$sel:bindingsByPathMetrics:ObjectMetrics :: Maybe BindingsByPathMetrics
$sel:thisObjectMetrics:ObjectMetrics :: MetricsCount
..}

-- | Get metrics for a program and for bindings of a formation accessible by a given path.
--
-- Combine metrics produced by 'getThisObjectMetrics' and 'getBindingsByPathMetrics'.
--
-- If no formation is accessible by the path, return a prefix of the path that led to a non-formation when the remaining path wasn't empty.
-- >>> flip getProgramMetrics (Just ["org", "eolang"]) "{⟦ org ↦ ⟦ eolang ↦ ⟦ x ↦ ⟦ φ ↦ Φ.org.eolang.bool ( α0 ↦ Φ.org.eolang.bytes (Δ ⤍ 01-) ) ⟧, z ↦ ⟦ y ↦ ⟦ x ↦ ∅, φ ↦ ξ.x ⟧, φ ↦ Φ.org.eolang.bool ( α0 ↦ Φ.org.eolang.bytes (Δ ⤍ 01-) ) ⟧, λ ⤍ Package ⟧, λ ⤍ Package ⟧⟧ }"
-- Right (ProgramMetrics {bindingsByPathMetrics = Just (BindingsByPathMetrics {path = ["org","eolang"], bindingsMetrics = [BindingMetrics {name = "x", metrics = Metrics {dataless = 1, applications = 2, formations = 1, dispatches = 6}},BindingMetrics {name = "z", metrics = Metrics {dataless = 2, applications = 2, formations = 2, dispatches = 7}}]}), programMetrics = Metrics {dataless = 6, applications = 4, formations = 6, dispatches = 13}})
--
-- >>> flip getProgramMetrics (Just ["a"]) "{⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧.e ⟧}"
-- Left ["a"]
--
-- >>> flip getProgramMetrics (Just ["a"]) "{⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ∅, d ↦ ⟦ φ ↦ ξ.ρ.c ⟧ ⟧, e ↦ ξ.b(c ↦ ⟦⟧).d ⟧ ⟧}"
-- Right (ProgramMetrics {bindingsByPathMetrics = Just (BindingsByPathMetrics {path = ["a"], bindingsMetrics = [BindingMetrics {name = "b", metrics = Metrics {dataless = 2, applications = 0, formations = 2, dispatches = 2}},BindingMetrics {name = "e", metrics = Metrics {dataless = 1, applications = 1, formations = 1, dispatches = 2}}]}), programMetrics = Metrics {dataless = 5, applications = 1, formations = 5, dispatches = 4}})
getProgramMetrics :: Program -> Maybe Path -> Either Path ProgramMetrics
getProgramMetrics :: Program -> Maybe Path -> Either Path ProgramMetrics
getProgramMetrics (Program [Binding]
bindings) Maybe Path
path = do
  ObjectMetrics{Maybe BindingsByPathMetrics
MetricsCount
$sel:bindingsByPathMetrics:ObjectMetrics :: ObjectMetrics -> Maybe BindingsByPathMetrics
$sel:thisObjectMetrics:ObjectMetrics :: ObjectMetrics -> MetricsCount
bindingsByPathMetrics :: Maybe BindingsByPathMetrics
thisObjectMetrics :: MetricsCount
..} <- Object -> Maybe Path -> Either Path ObjectMetrics
getObjectMetrics ([Binding] -> Object
Formation [Binding]
bindings) Maybe Path
path
  ProgramMetrics -> Either Path ProgramMetrics
forall a. a -> Either Path a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ProgramMetrics{$sel:programMetrics:ProgramMetrics :: MetricsCount
programMetrics = MetricsCount
thisObjectMetrics, Maybe BindingsByPathMetrics
bindingsByPathMetrics :: Maybe BindingsByPathMetrics
$sel:bindingsByPathMetrics:ProgramMetrics :: Maybe BindingsByPathMetrics
..}