packages feed

ghc-wasm-bindgen-0.1.0.0: runtime-hs/GhcWasmBindgen/Runtime.hs

{-# LANGUAGE UndecidableInstances #-}

-- | Imported by the generated Haskell bindings. ADTs cross as JSON encoded with
-- 'options'; EmitJavaScript mirrors these options.
module GhcWasmBindgen.Runtime
  ( Bindgen (..)
  , Generic
  , ToJSON
  , FromJSON
  , ToJSONKey
  , FromJSONKey
  , JSString (..)
  , fromJSString
  , toJSString
  , fromJSText
  , toJSText
  , fromJS
  , toJS
  , Fallback
  , guarded
  ) where

import Control.Exception (SomeException, catch, displayException, evaluate)
import Data.Aeson
import Data.Aeson.Text (encodeToLazyText)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import GHC.Generics (Generic, Rep)
import GHC.Wasm.Prim (JSString (..), fromJSString, toJSString)

-- | The tag key is @$tag@: it cannot be a Haskell field name, so it never
-- clashes with a record field.
options :: Options
options = defaultOptions {sumEncoding = TaggedObject "$tag" "contents", tagSingleConstructors = True}

-- | @deriving via Bindgen T instance ToJSON T@ gives @T@ instances using 'options'.
newtype Bindgen a = Bindgen a

instance (Generic a, GToJSON' Value Zero (Rep a), GToJSON' Encoding Zero (Rep a)) => ToJSON (Bindgen a) where
  toJSON (Bindgen a) = genericToJSON options a
  toEncoding (Bindgen a) = genericToEncoding options a

instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (Bindgen a) where
  parseJSON = fmap Bindgen . genericParseJSON options

toJS :: ToJSON a => a -> JSString
toJS = toJSString . TL.unpack . encodeToLazyText

fromJS :: FromJSON a => JSString -> a
fromJS = either errorWithoutStackTrace id . eitherDecodeStrictText . fromJSText

fromJSText :: JSString -> Text
fromJSText = T.pack . fromJSString

toJSText :: Text -> JSString
toJSText = toJSString . T.unpack

-- | An uncaught exception in a sync export kills the RTS, so every export
-- runs through here. The message goes to a slot that
-- ghc_wasm_bindgen_internal.js checks after each call, and JS gets a
-- placeholder it discards. Results are primitives or JSStrings, so WHNF
-- forces everything.
guarded :: Fallback a => a -> IO a
guarded x = evaluate x `catch` \(e :: SomeException) -> fallback <$ setError (toJSString (displayException e))

foreign import javascript unsafe "__exports.__ghcWasmBindgenError = $1"
  setError :: JSString -> IO ()

class Fallback a where
  fallback :: a

instance Fallback Double where fallback = 0
instance Fallback Int where fallback = 0
instance Fallback Bool where fallback = False
instance Fallback JSString where fallback = toJSString ""