{-# LANGUAGE OverloadedStrings,KindSignatures, GADTs, ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
module Network.JavaScript
(
send
, sendA
, sendE
, JavaScript(..)
, command
, procedure
, constructor
, Packet
, RemoteMonad
, Command()
, Procedure()
, RemoteValue
, delete
, localize
, remote
, var
, value
, call
, number
, string
, JavaScriptException(..)
, event
, addListener
, listen
, readEventChan
, start
, Engine
, Application
) where
import Control.Applicative(liftA2)
import Control.Exception(Exception, throwIO)
import Data.Monoid ((<>))
import qualified Data.Text.Lazy as LT
import Data.Text.Lazy (Text)
import Network.Wai (Application)
import Control.Monad.Trans.State.Strict
import Data.Aeson ( Value(..), FromJSON(..), ToJSON(..), encode, Result(..), fromJSON)
import Data.Text.Lazy.Encoding(decodeUtf8)
import Network.JavaScript.Internal
import Network.JavaScript.Services
command :: Command f => JavaScript -> f ()
command = internalCommand
constructor :: forall a f . Command f => JavaScript -> f (RemoteValue a)
constructor = internalConstructor
procedure :: forall a f . (Procedure f, FromJSON a) => JavaScript -> f a
procedure = internalProcedure
send :: Engine -> RemoteMonad a -> IO a
send e p = do
r <- sendE e p
case r of
Right a -> return a
Left err -> throwIO $ JavaScriptException err
data JavaScriptException = JavaScriptException Value
deriving (Show,Eq)
instance Exception JavaScriptException
sendE :: Engine -> RemoteMonad a -> IO (Either Value a)
sendE e (RemoteMonad rm) = go rm
where
go m = do
w <- walkStmtM e m
case w of
ResultPacket af _ -> sendStmtA e af
IntermPacket af k -> do
r <- sendStmtA e af
case r of
Right a -> go (k a)
Left msg -> return $ Left msg
data PingPong a where
ResultPacket :: AF Stmt a -> Maybe a -> PingPong a
IntermPacket :: AF Stmt a -> (a -> M Primitive b) -> PingPong b
walkStmtM :: Engine -> M Primitive a -> IO (PingPong a)
walkStmtM _ (PureM a) = pure $ ResultPacket (pure a) (pure a)
walkStmtM Engine{..} (PrimM p) = do
s <- prepareStmt genNonce p
let af = PrimAF s
return $ ResultPacket af (evalStmtA af [])
walkStmtM e (ApM g h) = do
w1 <- walkStmtM e g
case w1 of
ResultPacket g_af g_r -> do
w2 <- walkStmtM e h
case w2 of
ResultPacket h_af h_r -> return $ ResultPacket (g_af <*> h_af) (liftA2 ($) g_r h_r)
IntermPacket h_af k -> return $
IntermPacket (liftA2 (,) g_af h_af)
(\ (r1,r2) -> pure r1 <*> k r2)
IntermPacket g_af k -> return $ IntermPacket g_af (\ r -> k r <*> h)
walkStmtM e (BindM m k) = do
w1 <- walkStmtM e m
case w1 of
ResultPacket m_af (Just a) -> do
w2 <- walkStmtM e (k a)
case w2 of
ResultPacket h_af h_r ->
return $ ResultPacket (m_af *> h_af) h_r
IntermPacket h_af k' -> return $
IntermPacket (m_af *> h_af) k'
ResultPacket m_af Nothing ->
return $ IntermPacket m_af k
IntermPacket m_af k0 -> return $ IntermPacket m_af (\ r -> k0 r >>= k)
sendA :: Engine -> Packet a -> IO a
sendA e p = do
r <- sendAE e p
case r of
Right a -> return a
Left err -> throwIO $ JavaScriptException err
sendAE :: Engine -> Packet a -> IO (Either Value a)
sendAE e@Engine{..} (Packet af) = prepareStmtA genNonce af >>= sendStmtA e
data Stmt a where
CommandStmt :: JavaScript -> Stmt ()
ProcedureStmt :: FromJSON a => Int -> JavaScript -> Stmt a
ConstructorStmt :: RemoteValue a -> JavaScript -> Stmt (RemoteValue a)
deriving instance Show (Stmt a)
prepareStmtA :: Monad f => f Int -> AF Primitive a -> f (AF Stmt a)
prepareStmtA _ (PureAF a) = pure (pure a)
prepareStmtA ug (PrimAF p) = PrimAF <$> prepareStmt ug p
prepareStmtA ug (ApAF g h) = ApAF <$> prepareStmtA ug g <*> prepareStmtA ug h
prepareStmt :: Monad f => f Int -> Primitive a -> f (Stmt a)
prepareStmt _ (Command stmt) = pure $ CommandStmt stmt
prepareStmt ug (Procedure stmt) = ug >>= \ i -> pure $ ProcedureStmt i stmt
prepareStmt ug (Constructor stmt) = ug >>= \ i -> pure $ ConstructorStmt (RemoteValue i) stmt
showStmtA :: AF Stmt a -> JavaScript
showStmtA stmts = JavaScript
$ LT.concat [ js | JavaScript js <- concatAF (return . showStmt) stmts ]
showStmt :: Stmt a -> JavaScript
showStmt (CommandStmt cmd) = cmd <> ";"
showStmt (ProcedureStmt n cmd) = "var " <> procVar n <> "=" <> cmd <> ";"
showStmt (ConstructorStmt rv cmd) = var rv <> "=" <> cmd <> ";"
evalStmtA :: AF Stmt a -> [Value] -> Maybe a
evalStmtA af st = evalStateT (evalAF evalStmt af) st
evalStmt :: Stmt a -> StateT [Value] Maybe a
evalStmt (CommandStmt _) = pure ()
evalStmt (ProcedureStmt _ _) = do
vs <- get
case vs of
(v:vs') -> put vs' >> case fromJSON v of
Error _ -> fail "can not parse result"
Success r -> return r
_ -> fail "not enough values"
evalStmt (ConstructorStmt c _) = pure c
sendStmtA :: Engine -> AF Stmt a -> IO (Either Value a)
sendStmtA _ (PureAF a) = return (pure a)
sendStmtA e af
| null assignments = do
sendJavaScript e $ showStmtA af
return $ case evalStmtA af [] of
Nothing -> error "internal failure"
Just r -> Right r
| otherwise = do
nonce <- genNonce e
sendJavaScript e $ catchMe nonce $ showStmtA af
theReply <- replyBox e nonce
case theReply of
Right replies -> return $ case evalStmtA af replies of
Nothing -> error "internal failure"
Just r -> Right r
Left err -> return $ Left err
where
catchMe :: Int -> JavaScript -> JavaScript
catchMe nonce txt =
"try{" <> txt
<> "}catch(err){jsb.error(" <> JavaScript (LT.pack (show nonce))
<> ",err);};" <>
reply nonce <> ";"
assignments :: [Int]
assignments = concatAF findAssign af
findAssign :: Stmt a -> Maybe Int
findAssign (ProcedureStmt i _) = Just i
findAssign _ = Nothing
reply :: Int -> JavaScript
reply n = JavaScript $
"jsb.reply(" <> LT.intercalate ","
[ LT.pack (show n)
, "[" <> LT.intercalate "," [ x | JavaScript x <- map procVar assignments] <> "]"
] <> ")"
procVar :: Int -> JavaScript
procVar n = JavaScript $ "v" <> LT.pack (show n)
delete :: Command f => RemoteValue a -> f ()
delete rv = command $ "delete " <> var rv
localize :: Procedure f => RemoteValue a -> f Value
localize = procedure . var
remote :: Command f => Value -> f (RemoteValue a)
remote = constructor . value
value :: ToJSON v => v -> JavaScript
value = JavaScript . decodeUtf8 . encode
number :: Double -> JavaScript
number = value
string :: Text -> JavaScript
string = value
call :: JavaScript -> [JavaScript] -> JavaScript
call fn args = fn <> "(" <> JavaScript (LT.intercalate "," [ js | JavaScript js <- args ]) <> ")"
event :: ToJSON v => v -> JavaScript
event v = call "jsb.event" [value v]