packages feed

inline-r-0.7.0.0: src/Language/R/QQ.hs

-- |
-- Copyright: (C) 2013 Amgen, Inc.
--

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Language.R.QQ
  ( r
  , rexp
  , rsafe
  ) where

import           Control.Memory.Region
import           Control.Monad.R.Class
import qualified Data.Vector.SEXP as Vector
import qualified Foreign.R as R
import qualified Foreign.R.Type as SingR
import           Foreign.R (SEXP, SomeSEXP(..), SEXPInfo)
import qualified H.Prelude as H
import           Internal.Error
import           Language.R (parseText, string, eval)
import           Language.R.HExp
import           Language.R.Instance
import           Language.R.Literal
import           Language.R.Internal (installIO)

import qualified Data.ByteString.Char8 as BS

import Language.Haskell.TH (Q, runIO)
import Language.Haskell.TH.Lift (deriveLift)
import Language.Haskell.TH.Quote
import qualified Language.Haskell.TH.Syntax as TH
import qualified Language.Haskell.TH.Lib as TH

import Control.Concurrent (MVar, newMVar, withMVar)
import Control.Monad ((>=>), (<=<))
import Data.List (isSuffixOf)
import Data.Complex (Complex)
import Data.Int (Int32)
import Data.Word (Word8)
import System.IO.Unsafe (unsafePerformIO)

-------------------------------------------------------------------------------
-- Compile time Quasi-Quoter                                                 --
-------------------------------------------------------------------------------

-- | An R value, expressed as an R expression, in R's syntax.
r :: QuasiQuoter
r = QuasiQuoter
    { quoteExp  = \txt -> parseEval txt
    , quotePat  = unimplemented "quotePat"
    , quoteType = unimplemented "quoteType"
    , quoteDec  = unimplemented "quoteDec"
    }

-- | Construct an R expression but don't evaluate it.
rexp :: QuasiQuoter
rexp = QuasiQuoter
    { quoteExp  = \txt -> [| io $(parseExp txt) |]
    , quotePat  = unimplemented "quotePat"
    , quoteType = unimplemented "quoteType"
    , quoteDec  = unimplemented "quoteDec"
    }

-- | Quasiquoter for pure R code (no side effects) and that does not depend on
-- the global environment (referential transparency). This means that all
-- symbols must appear qualified with a package namespace (whose bindings are
-- locked by default), the code must not affect R shared state in any way,
-- including the global environment, and must not perform I/O.

-- TODO some of the above invariants can be checked statically. Do so.
rsafe :: QuasiQuoter
rsafe = QuasiQuoter
    { quoteExp  = \txt -> [| unsafePerformIO $ unsafeRToIO . eval =<< $(parseExp txt) |]
    , quotePat  = unimplemented "quotePat"
    , quoteType = unimplemented "quoteType"
    , quoteDec  = unimplemented "quoteDec"
    }

parseEval :: String -> Q TH.Exp
parseEval txt = do
    sexp <- parse txt
    case hexp sexp of
      Expr _ v ->
        let vs = Vector.toList v
        in [| acquireSome <=< io $ $(go vs) |]
  where
    go :: [SomeSEXP s] -> Q TH.Exp
    go []     = error "Impossible happen."
    go [SomeSEXP (returnIO -> a)]    = [| R.withProtected a (unsafeRToIO . eval) |]
    go (SomeSEXP (returnIO -> a) : as) =
        [| R.withProtected a $ unsafeRToIO . eval >=> \(SomeSEXP s) ->
             R.withProtected (return s) (const $(go as))
         |]

returnIO :: a -> IO a
returnIO = return

-- | Serialize quasiquotes using a global lock, because the compiler is allowed
-- in theory to run them in parallel, yet the R runtime is not reentrant.
qqLock :: MVar ()
qqLock = unsafePerformIO $ newMVar ()
{-# NOINLINE qqLock #-}

parse :: String -> Q (R.SEXP V 'R.Expr)
parse txt = runIO $ do
    H.initialize H.defaultConfig
    withMVar qqLock $ \_ -> parseText txt False

parseExp :: String -> Q TH.Exp
parseExp txt = TH.lift . returnIO =<< parse txt

-- XXX Orphan instance defined here due to bad interaction betwen TH and c2hs.
instance TH.Lift (IO (SomeSEXP s)) where
  lift = runIO >=> \s -> R.unSomeSEXP s (TH.lift . returnIO)

deriveLift ''SEXPInfo
deriveLift ''Complex
deriveLift ''R.Logical

instance TH.Lift (IO [SEXP s a]) where
    lift = runIO >=> go
      where
        go []                       = [| return [] |]
        go [returnIO -> xio]        = [| xio >>= return . (:[]) |]
        go ((returnIO -> xio) : xs) =
          [| R.withProtected xio $ $(go xs) . fmap . (:) |]

instance TH.Lift BS.ByteString where
    lift bs = let s = BS.unpack bs in [| BS.pack s |]

#if ! MIN_VERSION_th_orphans(0,11,0)
instance TH.Lift Int32 where
  lift x = let x' = fromIntegral x :: Integer in [| fromInteger x' :: Int32 |]

instance TH.Lift Word8 where
   lift x = let x' = fromIntegral x :: Integer in [| fromInteger x' :: Word8 |]

instance TH.Lift Double where
   lift x = [| $(return $ TH.LitE $ TH.RationalL $ toRational x) :: Double |]
#endif

instance TH.Lift (IO (Vector.Vector s 'R.Raw Word8)) where
    -- Apparently R considers 'allocVector' to be "defunct" for the CHARSXP
    -- type. So we have to use some bespoke function.
    lift = runIO >=> \v -> do
      let xs :: String
          xs = map (toEnum . fromIntegral) $ Vector.toList v
      [| fmap vector $ string xs |]

instance TH.Lift (IO (Vector.Vector s 'R.Char Word8)) where
    -- Apparently R considers 'allocVector' to be "defunct" for the CHARSXP
    -- type. So we have to use some bespoke function.
    lift = runIO >=> \ v -> do
      let xs :: String
          xs = map (toEnum . fromIntegral) $ Vector.toList v
      [| fmap vector $ string xs |]

instance TH.Lift (IO (Vector.Vector s 'R.Logical R.Logical)) where
    lift = runIO >=> \v -> do
      let xs = Vector.toList v
      [| fmap vector $ mkSEXPVectorIO SingR.SLogical $ map return xs |]

instance TH.Lift (IO (Vector.Vector s 'R.Int Int32)) where
    lift = runIO >=> \v -> do
      let xs = Vector.toList v
      [| fmap vector $ mkSEXPVectorIO SingR.SInt $ map return xs |]

instance TH.Lift (IO (Vector.Vector s 'R.Real Double)) where
    lift = runIO >=> \v -> do
      let xs = Vector.toList v
      [| fmap vector $ mkSEXPVectorIO SingR.SReal $ map return xs |]

instance TH.Lift (IO (Vector.Vector s 'R.Complex (Complex Double))) where
    lift = runIO >=> \v -> do
      let xs = Vector.toList v
      [| fmap vector $ mkSEXPVectorIO SingR.SComplex $ map return xs |]

instance TH.Lift (IO (Vector.Vector s 'R.String (SEXP s 'R.Char))) where
    lift = runIO >=> \v -> do
      let xsio = returnIO $ Vector.toList v
      [| fmap vector . mkProtectedSEXPVectorIO SingR.SString =<< xsio |]

instance TH.Lift (IO (Vector.Vector s 'R.Vector (SomeSEXP s))) where
    lift = runIO >=> \v -> do
      let xsio = returnIO $ map (\(SomeSEXP s) -> R.unsafeCoerce s)
                          $ Vector.toList v :: IO [SEXP s 'R.Any]
      [| fmap vector $ mkProtectedSEXPVectorIO SingR.SVector =<< xsio |]

instance TH.Lift (IO (Vector.Vector s 'R.Expr (SomeSEXP s))) where
    lift = runIO >=> \v -> do
      let xsio = returnIO $ map (\(SomeSEXP s) -> R.unsafeCoerce s)
                          $ Vector.toList v :: IO [SEXP s 'R.Any]
      [| fmap vector . mkProtectedSEXPVectorIO SingR.SExpr =<< xsio |]

-- | Returns 'True' if the variable name is in fact a Haskell value splice.
isSplice :: String -> Bool
isSplice = ("_hs" `isSuffixOf`)

-- | Chop a splice variable in order to obtain the name of the haskell variable
-- to splice.
spliceNameChop :: String -> String
spliceNameChop name = take (length name - 3) name

instance TH.Lift (IO (SEXP s a)) where
    -- Special case some forms, rather than relying on the default code
    -- generated by 'deriveLift'.
    lift = runIO >=> \case
      (hexp -> Symbol pname _ s) | not (hexp s === Nil) -> [| installIO xs |]
        where
          xs :: String
          xs = map (toEnum . fromIntegral) $ Vector.toList $ vector pname
      (hexp -> List s (hexp -> Nil) (hexp -> Nil))
        | R.unsexp s == R.unsexp H.missingArg ->
          [| R.cons H.missingArg H.nilValue |]
      s@(hexp -> Symbol (returnIO -> pnameio) value _)
        | R.unsexp s == R.unsexp value -> [| selfSymbol =<< pnameio |] -- FIXME
      (hexp -> Symbol pname _ (hexp -> Nil))
        | Char (Vector.toString -> name) <- hexp pname
        , isSplice name -> do
          let hvar = TH.varE $ TH.mkName $ spliceNameChop name
          [| H.mkSEXPIO $hvar |]
        | otherwise -> [| installIO xs |]        -- FIXME
       where
        xs :: String
        xs = map (toEnum . fromIntegral) $ Vector.toList $ vector pname
      (hexp -> Lang (hexp -> Symbol pname _ (hexp -> Nil)) (returnIO -> randsio))
        | Char (Vector.toString -> name) <- hexp pname
        , isSplice name -> do
          let nm = spliceNameChop name
          hvar <- fmap (TH.varE . (maybe (TH.mkName nm) id)) (TH.lookupValueName nm)
          [| R.withProtected (installIO ".Call") $ \call ->
             R.withProtected (H.mkSEXPIO $hvar) $ \f -> do
                rands <- randsio
                unhexpIO . Lang call =<< unhexpIO . List f rands =<< unhexpIO Nil
           |]
    -- Override the default for expressions because the default Lift instance
    -- for vectors will allocate a node of VECSXP type, when the node is real an
    -- EXPRSXP.
      (hexp -> Expr n v) ->
        let xsio = returnIO $ map (\(SomeSEXP s) -> R.unsafeCoerce s)
                            $ Vector.toList v :: IO [SEXP s 'R.Any]
         in [| R.withProtected (mkProtectedSEXPVectorIO SingR.SExpr =<< xsio) $
                 unhexpIO . Expr n . vector
             |]
      (returnIO . hexp -> iot) ->
        [| unhexpIO =<< iot |]

instance TH.Lift (IO (HExp s a)) where
  lift = runIO >=> \case
    Nil -> [| return Nil |]
    Symbol (returnIO -> x0io) (returnIO -> x1io) (returnIO -> x2io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
           fmap (Symbol x0 x1) x2io
        |]
    List (returnIO -> x0io) (returnIO -> x1io) (returnIO -> x2io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
           fmap (List x0 x1) x2io
        |]
    Env (returnIO -> x0io) (returnIO -> x1io) (returnIO -> x2io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
           fmap (Env x0 x1) x2io
        |]
    Closure (returnIO -> x0io) (returnIO -> x1io) (returnIO -> x2io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
           fmap (Closure x0 x1) x2io
        |]
    Promise (returnIO -> x0io) (returnIO -> x1io) (returnIO -> x2io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
           fmap (Promise x0 x1) x2io
        |]
    Lang (returnIO -> x0io) (returnIO -> x1io) ->
      [| R.withProtected x0io $ \x0 ->
           fmap (Lang x0) x1io
        |]
    Special                  x0  -> [| return $ Special x0 |]
    Builtin                  x0  -> [| return $ Builtin x0 |]
    Char      (returnIO -> x0io) -> [| fmap Char      x0io |]
    Logical   (returnIO -> x0io) -> [| fmap Logical   x0io |]
    Int       (returnIO -> x0io) -> [| fmap Int       x0io |]
    Real      (returnIO -> x0io) -> [| fmap Real      x0io |]
    Complex   (returnIO -> x0io) -> [| fmap Complex   x0io |]
    String    (returnIO -> x0io) -> [| fmap String    x0io |]
    DotDotDot (returnIO -> x0io) -> [| fmap DotDotDot x0io |]
    Vector x0 (returnIO -> x1io) -> [| fmap (Vector x0) x1io |]
    Expr   x0 (returnIO -> x1io) -> [| fmap (Expr x0) x1io |]
    Bytecode -> [| return Bytecode |]
    ExtPtr _ _ _ -> violation "TH.Lift.lift HExp" "Attempted to lift an ExtPtr."
    WeakRef (returnIO -> x0io) (returnIO -> x1io)
            (returnIO -> x2io) (returnIO -> x3io) ->
      [| R.withProtected x0io $ \x0 ->
         R.withProtected x1io $ \x1 ->
         R.withProtected x2io $ \x2 ->
           fmap (WeakRef x0 x1 x2) x3io
        |]
    Raw (returnIO -> x0io) -> [| fmap Raw x0io |]
    S4  (returnIO -> x0io) -> [| fmap S4  x0io |]

unhexpIO :: HExp s a -> IO (SEXP s a)
unhexpIO = unsafeRToIO . unhexp