packages feed

Grow-1.1: Language/Grow.hs

{-# LANGUAGE RankNTypes #-}
module Language.Grow where

import Definitive
import IO.Filesystem
import Language.Parser
import IO.Time
import Data.Syntax
import Data.Containers
import System.Process
import System.IO.Unsafe (unsafeInterleaveIO)
import Control.Concurrent (forkIO,newEmptyMVar,putMVar,takeMVar)
import System.Directory (getDirectoryContents)

il = liftIO . unsafeInterleaveIO

type GrowSt = Env Growing
type TimeStamp = Max (TimeVal Seconds)
newtype Growing a = Growing (ReaderT GrowSt (WriterT TimeStamp IO) a)
                  deriving (Functor,Applicative,Unit,Monad,
                            MonadFix,MonadWriter TimeStamp,MonadReader GrowSt,MonadIO)
_Growing :: Iso (Growing a) (Growing b) (ReaderT GrowSt (WriterT TimeStamp IO) a) (ReaderT GrowSt (WriterT TimeStamp IO) b)
_Growing = iso Growing (\(Growing a) -> a)

class (MonadWriter TimeStamp m,MonadReader (Env m) m,MonadIO m) => MonadGrow m
instance MonadGrow Growing

instance Semigroup (SyntaxT f) where
  Text t + Text t' = Text (t+t')
  ValList l + ValList l' = ValList (l+l')
  Dictionary d + Dictionary d' = Dictionary (d+d')
  Function f + Function f' = Function (f'.f)
  a + ValList [] = a
  ValList [] + a = a
  a + b = error $ "Illegal shape for + "+shape a+" "+shape b
instance Monoid (SyntaxT f) where
  zero = nil

initial :: GrowSt
initial = fromList [
  ("+", builtin2 $ liftA2 (+)),
  ("cache", b_cache),
  ("hook", b_hook),
  ("words", b_words),
  ("head", b_head),
  ("tee", b_tee),
  ("in", b_in),
  ("eval",b_eval),
  ("environment", pure (Dictionary initial)),
  ("keys", b_keys),
  ("fold", b_fold),
  ("shape", b_shape),
  ("map", b_map),
  ("seq", b_seq),
  ("lambda", b_lambda),
  ("ls", b_ls)
  ]
  where 

type Builtin = forall m. MonadGrow m => ThunkT m

illegalShape fun args = error $ "Illegal shape for function '"+fun+"' : "+show (shape<$>args)

b_ls, b_lambda, b_seq, b_map,b_shape, b_fold, b_keys, b_eval, b_head, b_words, b_hook, b_cache, b_in, b_tee, b_dollar :: Builtin
b_ls = builtin (mute >=> ls)
  where ls (Text s) = do
          tell $ Max (modTime s^.thunk)
          return $ ValList [pure (Text (s+"/"+n)) | n <- getDirectoryContents s^.thunk]
            
b_lambda = builtin2 (\a b -> a >>= flip lambda b)
  where lambda (Text v) x = pure (Function (\a -> local (insert v a) x)) 
        lambda x _ = illegalShape "lambda" [x]
b_seq = builtin2 (liftA2 _seq)
  where _seq (Text _) a = a
        _seq _ a = a
b_map = builtin2 (liftA2 _map)
  where _map (Function f) (ValList l) = ValList (map f l)
        _map (Function f) (Dictionary d) = Dictionary (map f d)
        _map x y = illegalShape "map" [x,y]
b_eval = builtin (>>= reduce)
b_shape = builtin (map (Text . shape))
b_fold = builtin (>>= _fold)
  where _fold (ValList l) = fold<$>sequence l
        _fold (Dictionary d) = fold<$>sequence d
        _fold x = illegalShape "fold" [x]

b_keys = builtin (map _keys)
  where _keys (Dictionary d) = ValList (pure . Text . fst <$> toList (d^.keyed))
        _keys x = illegalShape "keys" [x]
b_head = builtin (>>= _head)
  where _head (ValList (h:t)) = h
        _head x = illegalShape "head" [x]
    
b_words = builtin (map _words)
  where _words (Text t) = ValList [pure (Text w) | w <- words t]
b_hook = builtin3 (bind3 hook)
  where hook (Text prg) (ValList dsts) (ValList args) = do
          (args,dsts) <- mute ((,)<$>sequence args<*>sequence dsts)
          (tsrc,srcVals) <- intercept (traverse getVal args)
          (tdsts,dstVals) <- unzip <$> traverse (intercept . getVal) dsts
          done <- when (any (liftA2 (||) (<tsrc) (==zero)) tdsts) $ il $ do
            vars <- sequence (newEmptyMVar<$srcVals)
            tids <- for (zip vars srcVals) $ \(v,x) -> do
              forkIO $ x^..thunk >> putMVar v ()
            traverse_ takeMVar vars
            callProcess ("./"+prg) [t | Text t <- dsts + args]
          let dstFile n = tell (Max (modTime n^.thunk) + tsrc) >> seq done . Text <$> il (readString n)
          pure (Dictionary $ fromList [(n,dstFile n) | Text n <- dsts])
b_cache = builtin2 $ \x y -> bind2 cache x (listen y)
  where cache (Text f) (Max t,Text c) = do
          t' <- liftIO (modTime f) 
          if t>t' then
             Text c <$ liftIO (writeString f c)
            else Text<$> il (readString f)
        cache x (_,y) = illegalShape "cache" [x,y]
  
b_in = builtin2 $ \e d -> mute d >>= \(Dictionary d') -> local (d'+) e

b_tee = builtin (>>= tee)
  where tee (Text t) = Text t <$ liftIO (putStrLn t)
        tee x = illegalShape "tee" [x]
b_dollar = builtin (mute >=> getVal)

getVal :: MonadGrow m => SyntaxT m -> ThunkT m
getVal (Text t) = ask >>= \d -> sequence (d^.at t) >>= \case
  Nothing -> do
    stamp <- liftIO (Max<$>modTime t)
    Text (readString t^.thunk) <$ tell stamp
  Just x -> pure x
getVal x = illegalShape "getVal" [x]

grow :: String -> Growing a -> IO (TimeStamp,a)
grow t g = (g^..mapping writerT.readerT._Growing) (insert "arg" (pure (Text t)) initial)