packages feed

salmon-ops-recipes-0.2.0.0: test/Test/EdgeListReference.hs

{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE ScopedTypeVariables #-}

{- | 'Salmon.Op.Dag.record', 'Salmon.Op.Dag.fromMagma',
'Salmon.Op.Dag.transposeOf', 'Salmon.Op.Dag.mergeDag' and
'Salmon.Actions.Query.outline' as they were before their edge lists stopped
searching and appending on the right (the profiles' finding 3), kept
verbatim as the reference "Test.EdgeListSpec" compares the current ones with.

Each is quadratic in the edges of one node: keep their inputs small. Nothing
outside the tests uses this module.
-}
module Test.EdgeListReference (
    recordAppending,
    fromMagmaAppending,
    transposeOfAppending,
    mergeDagAppending,
    OutlineAppending,
    outlineAppending,
    appendingRefs,
    appendingRoots,
    appendingChildren,
    appendingFirstPaths,
) where

import Control.Comonad.Cofree (Cofree (..))
import Data.Foldable (toList)
import Data.List (foldl')
import qualified Data.List as List
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)

import Salmon.Builtin.Extension (Extension (..), Op)
import Salmon.Op.Actions
import Salmon.Op.Dag (Conflict (..), Dag (..), dagOrder, dependenciesOf, emptyDag, representativeOf)
import Salmon.Op.Graph
import Salmon.Op.OpGraph
import Salmon.Op.Ref

-- | 'Salmon.Op.Dag.record', as it was.
recordAppending ::
    (Act ext -> Act ext -> Bool) ->
    Ref ->
    Act ext ->
    [Ref] ->
    Dag ext ->
    Dag ext
recordAppending same aref act predRefs dag =
    Dag
        { dagNodes = Map.insert aref act (dagNodes dag)
        , dagDependencies = foldl' addDependency deps0 predRefs
        , dagDependants = foldl' addDependant dependants0 predRefs
        , dagOrderRev = order'
        , dagConflicts = conflicts'
        }
  where
    previous = Map.lookup aref (dagNodes dag)

    conflicts'
        | Just old <- previous, not (same old act) =
            Conflict aref act old : dagConflicts dag
        | otherwise = dagConflicts dag

    order'
        | Map.member aref (dagNodes dag) = dagOrderRev dag
        | otherwise = aref : dagOrderRev dag

    deps0 = Map.insertWith (\_ old -> old) aref [] (dagDependencies dag)
    dependants0 = Map.insertWith (\_ old -> old) aref [] (dagDependants dag)

    addDependency m d = Map.insertWith snoc aref [d] (Map.insertWith (\_ old -> old) d [] m)
    addDependant m d = Map.insertWith snoc d [aref] m

    snoc [new] old
        | new `elem` old = old
        | otherwise = old <> [new]
    snoc new old = old <> filter (`notElem` old) new

-- | 'Salmon.Op.Dag.fromMagma', as it was.
fromMagmaAppending :: Map Ref (Act ext) -> Set (Ref, Ref) -> Dag ext
fromMagmaAppending magma edges = foldl' add emptyDag (Map.keys magma)
  where
    add dag r = recordAppending (\_ _ -> True) r (magma Map.! r) (deps r) dag

    deps :: Ref -> [Ref]
    deps r = Map.findWithDefault [] r incoming

    incoming :: Map Ref [Ref]
    incoming =
        Map.fromListWith
            (flip (<>))
            [ (dependant, [dependency])
            | (dependency, dependant) <- Set.toList edges
            , Map.member dependency magma
            , Map.member dependant magma
            ]

-- | 'Salmon.Op.Dag.transposeOf', as it was.
transposeOfAppending :: Map Ref [Ref] -> Map Ref [Ref]
transposeOfAppending deps =
    Map.union
        (Map.fromListWith (flip (<>)) [(d, [r]) | (r, ds) <- Map.toList deps, d <- ds])
        (Map.map (const []) deps)

-- | 'Salmon.Op.Dag.mergeDag', as it was.
mergeDagAppending :: (Act ext -> Act ext -> Bool) -> Dag ext -> Dag ext -> Dag ext
mergeDagAppending same into from = foldl' step into (dagOrder from)
  where
    step dag r =
        case representativeOf from r of
            Nothing -> dag
            Just act -> recordAppending same r act (dependenciesOf from r) dag

-------------------------------------------------------------------------------

-- | 'Salmon.Actions.Query.Outline', as it was, with its fields open.
data OutlineAppending = OutlineAppending
    { oaEntries :: !(Map Ref EntryAppending)
    , oaRootsRev :: ![Ref]
    , oaOrderRev :: ![Ref]
    }

data EntryAppending = EntryAppending
    { eaShorthand :: !Text
    , eaHelp :: !Text
    , eaFirstPath :: ![Text]
    , eaChildrenRev :: ![Ref]
    }

-- | 'Salmon.Actions.Query.outline', as it was.
outlineAppending :: Cofree Graph Op -> OutlineAppending
outlineAppending = go Nothing [] (OutlineAppending Map.empty [] [])
  where
    go :: Maybe Ref -> [Text] -> OutlineAppending -> Cofree Graph Op -> OutlineAppending
    go parent pfx acc (x :< g) =
        case x.node of
            Actionless -> List.foldl' (go parent pfx) acc (toList g)
            Actions act ->
                let ref' = act.extension.ref
                 in case Map.lookup ref' acc.oaEntries of
                        Nothing ->
                            let path = pfx <> [shorthand act]
                                entered =
                                    acc
                                        { oaEntries =
                                            Map.insert ref' (EntryAppending (shorthand act) act.extension.help path []) acc.oaEntries
                                        , oaOrderRev = ref' : acc.oaOrderRev
                                        }
                             in List.foldl' (go (Just ref') path) (link parent ref' entered) (toList g)
                        Just e ->
                            List.foldl' (peek ref' e.eaFirstPath) (link parent ref' acc) (toList g)

    peek :: Ref -> [Text] -> OutlineAppending -> Cofree Graph Op -> OutlineAppending
    peek ref' path acc c@(x :< g) =
        case x.node of
            Actionless -> List.foldl' (peek ref' path) acc (toList g)
            Actions act
                | linked (Just ref') act.extension.ref acc -> acc
                | otherwise -> go (Just ref') path acc c

    linked :: Maybe Ref -> Ref -> OutlineAppending -> Bool
    linked Nothing ref' acc = ref' `elem` acc.oaRootsRev
    linked (Just parent) ref' acc =
        maybe False (elem ref' . (.eaChildrenRev)) (Map.lookup parent acc.oaEntries)

    link :: Maybe Ref -> Ref -> OutlineAppending -> OutlineAppending
    link parent ref' acc
        | linked parent ref' acc = acc
        | otherwise = case parent of
            Nothing -> acc{oaRootsRev = ref' : acc.oaRootsRev}
            Just p ->
                acc{oaEntries = Map.adjust (\e -> e{eaChildrenRev = ref' : e.eaChildrenRev}) p acc.oaEntries}

-- | What 'Salmon.Actions.Query.outlineRefs' reads.
appendingRefs :: OutlineAppending -> Set Ref
appendingRefs = Map.keysSet . (.oaEntries)

-- | What 'Salmon.Actions.Query.outlineRoots' reads.
appendingRoots :: OutlineAppending -> [Ref]
appendingRoots o = reverse o.oaRootsRev

-- | What 'Salmon.Actions.Query.outlineChildren' reads.
appendingChildren :: OutlineAppending -> Ref -> [Ref]
appendingChildren o r = maybe [] (reverse . (.eaChildrenRev)) (Map.lookup r o.oaEntries)

-- | 'Salmon.Actions.Query.outlineFirstPaths', as it was.
appendingFirstPaths :: OutlineAppending -> [([Text], Ref, Text)]
appendingFirstPaths o =
    [ (e.eaFirstPath, r, e.eaHelp)
    | r <- reverse o.oaOrderRev
    , Just e <- [Map.lookup r o.oaEntries]
    ]