----------------------------------------------------------------------------- -- -- Module : Cell -- Copyright : -- License : MIT -- -- Maintainer : agocorona@gmail.com -- Stability : experimental -- Portability : -- -- | -- ----------------------------------------------------------------------------- {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings, CPP, ScopedTypeVariables #-} module GHCJS.HPlay.Cell(Cell(..),boxCell,bcell,(.=),get,mkscell,scell, gcell, calc) where import Transient.Internals import Transient.Move --hiding (JSString) import GHCJS.HPlay.View import Data.Typeable import Unsafe.Coerce import qualified Data.Map as M hiding ((!)) import Control.Monad.State hiding (get) import Control.Monad import Data.Monoid import Data.List import Control.Exception import Data.IORef import System.IO.Unsafe #ifdef ghcjs_HOST_OS import Data.JSString hiding (empty) #else -- type JSString = String #endif data Cell a = Cell { mk :: Maybe a -> Widget a , setter :: a -> IO () , getter :: IO (Maybe a)} --instance Functor Cell where -- fmap f cell = cell{setter= \c x -> c .= f x, getter = \cell -> get cell >>= return . f} -- | creates (but not instantiates) an input box that has a setter and a getter. To instantiate it us his method `mk` bcell :: (Show a, Read a, Typeable a) =>TransIO (Cell a) bcell= genNewId >>= return . boxCell -- | creates (but not instantiates) a input box cell with polimorphic value, identified by a string. -- the cell has a getter and a setter. To instantiate it us his method `mk` boxCell :: (Show a, Read a, Typeable a) => ElemID -> Cell a boxCell id = Cell{ mk= \mv -> getParam (Just id) "text" mv , setter= \x -> do me <- elemById id case me of Just e -> setProp e "value" (toJSString $ show1 x) Nothing -> return () , getter= getID id} getID id = withElem id $ \e -> do ms <- getValue e case ms of Nothing -> return Nothing Just s -> return $ read1 s where read1 s= if typeOf(typeIO getID) /= typestring then case readsPrec 0 s of [(v,_)] -> v `seq` Just v _ -> Nothing else Just $ unsafeCoerce s typeIO :: (ElemID -> IO (Maybe a)) -> a typeIO = undefined typestring :: TypeRep typestring= typeOf (undefined :: String) show1 :: (Show a, Typeable a) => a -> String show1 x= if typeOf x== typestring then unsafeCoerce x else show x instance Attributable (Cell a) where (Cell mk setter getter) ! atr = Cell (\ma -> mk ma ! atr) setter getter -- | Cell assignment using the cell setter (.=) :: MonadIO m => Cell a -> a -> m () (.=) cell x = liftIO $ (setter cell ) x get cell = Transient $ liftIO (getter cell) ---- | a cell value assigned to other cell --(..=) :: Cell a -> Cell a -> Widget () --(..=) cell cell'= get cell' >>= (cell .= ) infixr 0 .= -- , ..= -- experimental: to permit cell arithmetic --instance Num a => Num (Cell a) where -- c + c'= Cell undefined undefined $ -- do r1 <- getter c -- r2 <- getter c' -- return $ liftA2 (+) r1 r2 -- -- c * c'= Cell undefined undefined $ -- do r1 <- getter c -- r2 <- getter c' -- return $ liftA2 (+) r1 r2 -- -- abs c= c{getter= getter c >>= return . fmap abs} -- -- signum c= c{getter= getter c >>= return . fmap signum} -- -- fromInteger i= Cell undefined undefined . return $ Just $ fromInteger i -- * Spradsheet type cells -- Implement a solver that allows circular dependencies . See -- > http://tryplayg.herokuapp.com/try/spreadsheet.hs/edit -- The recursive Cell calculation DSL BELOW ------ -- | within a `mkscell` formula, `gcell` get the the value of another cell using his name. -- -- see http://tryplayg.herokuapp.com/try/spreadsheet.hs/edit gcell :: JSString -> Cloud Double gcell n= loggedc $ do -- onAll $ do -- cutExceptions -- reportBack vars <- getCloudState rvars <|> return M.empty -- liftIO $ readIORef rvars localIO $ print ("gcell", n) case M.lookup n vars of Just exp -> do inc ; exp !> "executing exp" Nothing -> error $ "cell not found: " ++ show n where inc = do Tries tries maxtries <- getCloudState rtries <|> error "no tries" --do -- Exprs exprs <- getCloudState -- return . Tries 0 $ 3 * (M.size $ exprs) localIO $ print tries if tries <= maxtries then localIO $ writeIORef rtries $ Tries (tries+1) maxtries else local $ do -- liftIO $ print "back" back Loop data Loop= Loop deriving (Show,Typeable) instance Exception Loop -- a parameter is a function of all of the rest type Expr a = Cloud a data Tries= Tries Int Int deriving Typeable rtries= unsafePerformIO $ newIORef $ Tries 0 0 --maxtries= 3 * (M.size $ unsafePerformIO $ readIORef rexprs) -- newtype Exprs= Exprs (M.Map JSString (Expr Double)) rexprs :: IORef (M.Map JSString (Expr Double)) rexprs= unsafePerformIO $ newIORef M.empty -- initial expressions -- newtype Vars= Vars (M.Map JSString (Expr Double)) rvars :: IORef (M.Map JSString (Expr Double)) rvars= unsafePerformIO $ newIORef M.empty -- expressions actually used for each cell. -- initially, A mix of reexprs and rmodified -- and also contains the result of calculation -- newtype Modified= Modified (M.Map JSString (Expr Double)) deriving Typeable rmodified :: IORef (M.Map JSString ( Double)) rmodified= unsafePerformIO $ newIORef M.empty -- cells modified by the user or by the loop detection mechanism -- | make a spreadsheet cell. a spreadsheet cell is an input-output box that takes input values from -- the user, has an expression associated and display the result value after executing `calc` -- -- see http://tryplayg.herokuapp.com/try/spreadsheet.hs/edit mkscell :: JSString -> Expr Double -> Cloud (Cell Double) mkscell name expr= do exprs <- onAll $ liftIO (readIORef rexprs) <|> return ( M.empty) -- readIORef rexprs onAll $ liftIO $ writeIORef rexprs $ M.insert name expr exprs return $ scell name expr scell :: JSString -> Expr Double -> Cell Double scell id expr= Cell{ mk= \mv -> Widget $ do r <- norender $ getParam (Just id) "text" mv `fire` OnChange mod <- liftIO (readIORef rmodified) <|> return( M.empty) liftIO $ writeIORef rmodified $ M.insert id r mod return r , setter= \x -> withElem id $ \e -> setProp e "value" (toJSString $ show1 x) , getter= getID id} -- | executes the spreadsheet adjusting the vaules of the cells created with `mkscell` and solving loops -- -- see http://tryplayg.herokuapp.com/try/spreadsheet.hs/edit calc :: Cloud () calc= do mod <- localIO $ readIORef rmodified onAll $ liftIO $ print ("LENGTH MOD", M.size mod) onAll $ liftIO $ print "setCloudState modified" setCloudState rmodified mod exprs <- getCloudState rexprs onAll $ liftIO $ print "setCloudState exprs" setCloudState rexprs exprs onAll $ liftIO $ print "setCloudState rvars" setCloudState rvars M.empty onAll $ return() `onBack` (\(e::Loop) -> runCloud' $ do localIO $ print "REMOVEVAR"; removeVar e; local (forward Loop) ) exprs <- getCloudState rexprs <|> error "no exprs" onAll $ liftIO $ print "setCloudState rtries" setCloudState rtries $ Tries 0 $ 3 * (M.size $ exprs) nvs <- getCloudState rmodified <|> error "no modified" -- liftIO $ readIORef rmodified onAll $ liftIO $ print ("LENGTH NVS", M.size nvs) when (not $ M.null nvs) $ calc1 --values <- calc1 --localIO $ print "NEW CALC" --local $ mapM_ (\(n,v) -> boxCell n .= v) values onAll $ liftIO $ print "setCloudState modified" setCloudState rmodified M.empty where --calc1 :: Expr [(JSString,Double)] calc1= do return () !> "CALC1" cells <- getCloudState rexprs <|> error "no exprs" -- liftIO $ readIORef rexprs nvs <- getCloudState rmodified <|> error "no modified2" -- liftIO $ readIORef rmodified onAll $ liftIO $ print "setCloudState vars" setCloudState rvars $ M.union (M.map return nvs) cells solve --solve :: Expr [(JSString,Double)] solve = do vars <- getCloudState rvars <|> error "no vars" -- liftIO $ readIORef rvars onAll $ liftIO $ print $ ("LENGHT VARS", M.size vars) mapM_ (solve1 vars) $ M.toList vars where solve1 vars (k,f)= do localIO $ print ("solve1",k) x <- f localIO $ print ("setcloudstate var",k,x) local $ boxCell k .= x setCloudState rvars $ M.insert k (return x) vars return () -- (k,x) :: Expr (JSString,Double) setCloudState r v= allNodes $ writeIORef r v getCloudState r= onAll . liftIO $ readIORef r -- removeVar ::SomeException -> IO () -- [(JSString,Double)] removeVar = \(e:: Loop) -> do nvs <- getCloudState rmodified <|> error "no modified 3"-- readIORef rmodified -- mapM (\n -> snd n >>= \v -> localIO $ print (fst n,v)) $ M.toList nvs exprs <- getCloudState rexprs <|> error " no Exprs2" --readIORef rexprs case M.keys exprs \\ M.keys nvs of [] -> error "non solvable circularity in cell dependencies" (name:_) -> do localIO $ print ("removeVar",name) mv <- localIO $ getID name case mv of Nothing -> return () Just v -> do onAll $ liftIO $ print "setCloudState modified" setCloudState rmodified $ M.insert name v nvs return () allNodes :: IO () -> Cloud () allNodes mx= loggedc $ (localIO mx) <> (atRemote $ (localIO $ print "UPDATE" >> mx)) --atBrowser mx= if isBrowserInstance then mx else atRemote mx --atServer mx= if not isBrowserInstance then mx else atRemote mx -- http://blog.sigfpe.com/2006/11/from-l-theorem-to-spreadsheet.html -- loeb :: Functor f => f (t -> a) -> f a -- loeb x = fmap (\a -> a (loeb x)) x -- loeb :: [([a]-> a)] -> [a] -- loeb x= map (\f -> f (loeb x)) x --loeb :: [([a] -> IO a)] -> IO [a] --loeb x= mapM (\f -> loeb x >>= f) x -- fail does not terminate --loeb x= map (\f -> f (loeb x)) x