sunroof-compiler-0.2: Language/Sunroof/JS/MVar.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-missing-methods #-}
-- | 'JSMVar' provides the same functionality and
-- concurrency abstraction in Javascript computations
-- as 'Control.Concurrent.MVar' in Haskell.
module Language.Sunroof.JS.MVar
( JSMVar
, newMVar, newEmptyMVar
, putMVar, takeMVar
) where
import Data.Boolean ( IfB(..), EqB(..) )
import Language.Sunroof.Classes
( Sunroof(..), SunroofArgument(..) )
import Language.Sunroof.Types
import Language.Sunroof.Concurrent ( forkJS )
import Language.Sunroof.Selector ( (!) )
import Language.Sunroof.TH
import Language.Sunroof.JS.Object ( JSObject )
import Language.Sunroof.JS.Array
( JSArray
, newArray, length'
, push, shift )
-- -------------------------------------------------------------
-- JSMVar Type
-- -------------------------------------------------------------
-- | 'JSMVar' abstraction. The type parameter gives
-- the type of values held in a 'JSMVar'.
newtype JSMVar a = JSMVar JSObject
deriveJSTuple
[d| instance (SunroofArgument o) => JSTuple (JSMVar o) where
type Internals (JSMVar o) =
( (JSArray (JSContinuation (JSContinuation o))) -- callbacks of written data
, (JSArray (JSContinuation o)) -- callbacks of waiting readers
)
|]
-- | Reference equality, not value equality.
instance (SunroofArgument o) => EqB (JSMVar o) where
(JSMVar a) ==* (JSMVar b) = a ==* b
-- -------------------------------------------------------------
-- JSMVar Combinators
-- -------------------------------------------------------------
-- | Create a new 'JSMVar' with the given value inside.
-- See 'newEmptyMVar'.
newMVar :: forall a t . (SunroofArgument a) => a -> JS t (JSMVar a)
newMVar a = do
written <- newArray ()
waiting <- newArray ()
-- mvar must be empty, with no one waiting, so just push and continue
f <- continuation $ \ (k :: JSContinuation a) -> goto k a :: JSB ()
written # push (f :: JSContinuation (JSContinuation a))
tuple (written, waiting)
-- | Create a new empty 'JSMVar'.
-- See 'newMVar'.
newEmptyMVar :: (SunroofArgument a) => JS t (JSMVar a)
newEmptyMVar = do
written <- newArray ()
waiting <- newArray ()
tuple (written, waiting)
-- TODO: Not quite right; pauses until someone bites
-- | Put the value into the 'JSMVar'. If there already is a
-- value inside, this will block until it is taken out.
putMVar :: forall a . (SunroofArgument a) => a -> JSMVar a -> JS B ()
putMVar a (match -> (written,waiting)) = do
ifB ((waiting ! length') ==* 0)
(-- no-one is waiting, so check for fullness
ifB ((written ! length') ==* 0)
(-- mvar empty, so just push and continue
do f <- continuation $ \ (k :: JSContinuation a) -> goto k a :: JSB ()
written # push (f :: JSContinuation (JSContinuation a))
)
(-- mvar full, so block
callcc $ \ (k :: JSContinuation ()) -> do
f <- continuation $ \ (kr :: JSContinuation a) -> do
-- we've got a request for the contents
-- so we can continue
forkJS $ (goto k () :: JSB ())
-- and send the boxed value
goto kr a :: JSB ()
written # push (f :: JSContinuation (JSContinuation a))
done
)
)
-- If someone is already waiting, then just pass the value (and continue without pausing)
(do f <- shift waiting
forkJS (goto f a :: JSB ())
return ()
)
-- | Take the value out of the 'JSMVar'. If there is no value
-- inside, this will block until one is available.
takeMVar :: forall a . (Sunroof a, SunroofArgument a) => JSMVar a -> JS B a
takeMVar (match -> (written,waiting)) = do
ifB ((written ! length') ==* 0)
(do -- Add yourself to the 'waiting for writer' Q.
callcc $ \ k -> do waiting # push (k :: JSContinuation a)
done
)
(do f <- shift written
-- Here, we add our continuation into the written Q.
callcc $ \ k -> goto f k
)