module Graphics.UI.Threepenny.Internal.FFI (
ffi,
FFI(..), ToJS(..),
JSFunction,
toCode, marshalResult,
) where
import Data.Functor
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import Text.JSON.Generic
import Graphics.UI.Threepenny.Internal.Types
newtype JSCode = JSCode { unJSCode :: String }
deriving (Eq, Ord, Show, Data, Typeable)
class ToJS a where
render :: a -> JSCode
instance ToJS String where render = JSCode . show
instance ToJS Int where render = JSCode . show
instance ToJS Bool where render b = JSCode $ if b then "false" else "true"
instance ToJS JSValue where render x = JSCode $ showJSValue x ""
instance ToJS ByteString where render = JSCode . show
instance ToJS ElementId where
render (ElementId x) = apply "elidToElement(%1)" [render x]
instance ToJS Element where render = render . unprotectedGetElementId
data JSFunction a = JSFunction
{ code :: JSCode
, marshal :: Window -> JSValue -> Result a
}
toCode :: JSFunction a -> String
toCode = unJSCode . code
marshalResult
:: JSFunction a
-> Window
-> JSValue
-> Result a
marshalResult = marshal
instance Functor JSFunction where
fmap f = fmapWindow (const f)
fmapWindow :: (Window -> a -> b) -> JSFunction a -> JSFunction b
fmapWindow f (JSFunction c m) = JSFunction c (\w v -> f w <$> m w v)
fromJSCode :: JSCode -> JSFunction ()
fromJSCode c = JSFunction { code = c, marshal = \_ _ -> Ok () }
class FFI a where
fancy :: ([JSCode] -> JSCode) -> a
instance (ToJS a, FFI b) => FFI (a -> b) where
fancy f a = fancy $ f . (render a:)
instance FFI (JSFunction ()) where fancy f = fromJSCode $ f []
instance FFI (JSFunction String) where fancy = mkResult "%1.toString()"
instance FFI (JSFunction JSValue) where fancy = mkResult "%1"
instance FFI (JSFunction [ElementId]) where fancy = mkResult "elementsToElids(%1)"
mkResult :: JSON a => String -> ([JSCode] -> JSCode) -> JSFunction a
mkResult client f = JSFunction
{ code = apply client [f []]
, marshal = \w -> readJSON
}
ffi :: FFI a => String -> a
ffi macro = fancy (apply macro)
testFFI :: String -> Int -> JSFunction String
testFFI = ffi "$(%1).prop('checked',%2)"
apply :: String -> [JSCode] -> JSCode
apply code args = JSCode $ go code
where
argument i = unJSCode (args !! i)
go [] = []
go ('%':c:cs) = argument index ++ go cs
where index = fromEnum c fromEnum '1'
go (c:cs) = c : go cs