packages feed

signals-0.2.0.1: src/Signal/Compiler/Cycles.hs

{-# LANGUAGE GADTs               #-}
{-# LANGUAGE Rank2Types          #-}
{-# LANGUAGE TypeOperators       #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Signal.Compiler.Cycles (cycles) where

import Signal.Core
import Signal.Core.Reify

import Control.Monad.State
import Control.Monad.Writer
import Data.Ref
import Data.Ref.Map (Map, Name)
import qualified Data.Ref.Map as M

import Prelude hiding (Left, Right, pred)

import System.Mem.StableName (eqStableName, hashStableName) -- !

--------------------------------------------------------------------------------
-- * 
--------------------------------------------------------------------------------

-- | ...
data Hide f where
  Hide :: f a -> Hide f

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

-- | A node can have three different states during cycle checking
--   * Visited,   no cycles detected in node or children
--   * Visiting,  node is being checked for cycles
--   * Unvisited, node has not yet been checked for cycles
data Status      = Visited | Visiting | Unvisited deriving Eq

data Tagged i a  = Tagged Status Predecessor (Node i a)

-- | A node's predecessor
data Predecessor = Predecessor (Hide Name) | None

-- | Cycle-checking monad
type M i         = WriterT [Hide (Key i)] (State (Map (Tagged i)))

--------------------------------------------------------------------------------
-- **

untag :: Tagged i a -> Node i a
untag (Tagged _ _ n) = n

(=/=) :: Predecessor -> Predecessor -> Bool
(=/=) (Predecessor (Hide n1)) (Predecessor (Hide n2)) = n1 `eqStableName` n2
(=/=) _ _ = False

-- | Sets the status of a tagged node
is :: Name a -> Status -> M i ()
is r s = modify $ flip M.adjust r $ \(Tagged _ p n) -> Tagged s p n

-- | Sets the predecessor of a tagged node
before :: Name a -> Predecessor -> M i ()
before r p = modify $ flip M.adjust r $ \(Tagged s _ n) -> Tagged s p n

-- | Gets the status of a tagged node
status :: Name a -> M i Status
status r = do
  s <- get
  return $ case M.lookup r s of
    Nothing             -> error $ "Sorter.status: lookup failed"
                                ++ "\n\t i: " ++ show (hashStableName r)
    Just (Tagged s _ _) -> s

-- | Gets the predecessor of a tagged node
predecessor :: Name a -> M i Predecessor
predecessor r =
  do s <- get
     return $ case M.lookup r s of
       Nothing             -> error "Sorter.predecessor: lookup failed"
       Just (Tagged _ p _) -> p

node :: Name (S Symbol i a) -> M i (S Key i a)
node r =
  do s <- get
     return $ case M.lookup r s of
       Nothing                    -> error "Sorter.node: lookup failed"
       Just (Tagged _ _ (Node n)) -> n

--------------------------------------------------------------------------------
-- *
--------------------------------------------------------------------------------

-- ! Remove unsafe, It's not really needed.
cycle' :: Key i a -> M i Bool
cycle' key@(Key r) =
  do r `is` Visiting
     n  <- node r
     b  <- case n of
       (Var     _) -> return False
       (Repeat  _) -> return False
       (Map   _ s) -> check s
       (Join  l r) -> (&&) <$> check l <*> check r
       (Left    l) -> check l
       (Right   r) -> check r
       (Delay _ s) -> tell [Hide s] >> return False
       (Mux   s c) -> (&&) <$> check s <*> (and <$> mapM (check . snd) c)
     r `is` Visited
     return b
  where
    check :: Key i a -> M i Bool
    check key@(Key r') =
      do let q = Predecessor (Hide r)
         p <- predecessor r'
         s <- status      r'
         case s of
           Unvisited          -> r' `before` q >> cycle' key
           Visiting | p =/= q -> return False
           _                  -> return True
         
--------------------------------------------------------------------------------

-- | Checks if the given signal contains cycles
cycles :: Key i a -> Nodes i -> Bool
cycles key nodes = flip evalState (init nodes) $ go key
  where
    init :: Nodes i -> Map (Tagged i)
    init = M.hmap $ \node -> Tagged Unvisited None node
    
    go :: Key i a -> State (Map (Tagged i)) Bool
    go node =
      do (b, w) <- runWriterT $ cycle' node
         (bs)   <- mapM add w
         return $  and (b : bs)
      where
        add :: Hide (Key i) -> State (Map (Tagged i)) Bool
        add (Hide key@(Key n)) =
          do s <- get
             case M.lookup n s of
               Nothing -> error "Cycles.cyles.add: lookup failed"
               Just (Tagged _ _ (Node (Delay _ k))) ->
                 go k

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