jsaddle-wasm-0.1.2.1: src-wasm/Language/Javascript/JSaddle/Wasm/Internal/TH.hs
module Language.Javascript.JSaddle.Wasm.Internal.TH
( eval,
patchedGhcjsHelpers,
)
where
import Control.Applicative (asum, many)
import Control.Exception (evaluate)
import Data.ByteString.Lazy.Char8 qualified as BLC8
import Language.Haskell.TH qualified as TH
import Language.Haskell.TH.Syntax qualified as TH
import Language.Javascript.JSaddle.Run.Files qualified as JSaddle.Files
import Regex.List qualified as Re
eval :: String -> [TH.Q TH.Type] -> TH.Q TH.Exp
eval jsChunk argTys = do
ffiImportName <- TH.newName . show =<< TH.newName "wasm_ffi_import_eval"
sig <- mkSig argTys
let ffiImport =
TH.ForeignD $
TH.ImportF
TH.JavaScript
TH.Safe
jsChunk
ffiImportName
sig
TH.addTopDecls [ffiImport]
argNames <- traverse (\_ -> TH.newName "x") argTys
let argPats = TH.varP <$> argNames
argExps = TH.varE <$> argNames
-- Safe FFI imports return a thunk that needs to be evaluated to make sure
-- that the FFI call actually completed ('unsafeInterleaveIO'-like). To avoid
-- surprises, use this unconditionally.
TH.lamE argPats [|evaluate =<< $(TH.appsE $ TH.varE ffiImportName : argExps)|]
where
mkSig = \case
[] -> [t|IO ()|]
t : ts -> [t|$t -> $(mkSig ts)|]
-- | The JSaddle GHCJS helpers need to be available in the global scope.
-- Usually, this is done by evaluating them in a global scope; however, we want
-- to avoid JS eval (due to CSP, see
-- https://github.com/tweag/ghc-wasm-miso-examples/issues/33), so we instead use
-- a hack, namely transforming
--
-- > function foo(a) {
-- > return a + 1;
-- > }
--
-- into
--
-- > globalThis["foo"] = function(a) {
-- > return a + 1;
-- > }
--
-- Of course, this only works because of the very particular structure of
-- 'ghcjsHelpers'; but it changes very rarely (didn't change non-trivially in
-- the last 10 years), so this seems acceptable.
patchedGhcjsHelpers :: String
patchedGhcjsHelpers =
Re.replaceAll re $ BLC8.unpack JSaddle.Files.ghcjsHelpers
where
re :: Re.RE Char String
re =
asum
[ f <$> (Re.list "function " *> many (Re.satisfy (/= '('))),
"\n};" <$ Re.list "\n}"
]
where
f name = "globalThis[" <> show name <> "] = function"