packages feed

mathlink-2.0.1.1: Foreign/MathLink.hs

{-# LANGUAGE GeneralizedNewtypeDeriving
           , ExistentialQuantification
           , MultiParamTypeClasses
           #-}
module Foreign.MathLink 
    ( 
      -- * An example package
      -- $example

      -- * Notes
      -- $notes

      -- * Package declarations
      MLSpec
    , MLDecl(..)

      -- * Running the /MathLink/ loop
    , ML
    , runMLSpec

      -- * /Mathematica/ expressions and data marshaling
    , module Foreign.MathLink.Expression

      -- * Known limitations
      -- $limitations
    ) where

import Prelude hiding (catch)
import Control.Concurrent
import Control.Exception hiding (evaluate)
import Control.Monad
import Control.Monad.Trans

import Foreign.MathLink.Internal
import Foreign.MathLink.Expression

import Data.IntMap (IntMap)
import qualified Data.IntMap as IM

-- | A /Mathematica/ package specification.
type MLSpec = [MLDecl]

-- | A declaration for a /Mathematica/ package.
data MLDecl = forall a . MLPut a  => Eval a
              -- ^ A value to be sent to /Mathematica/ for evaluation.
            | EvalStr String
              -- ^ A /Mathematica/ expression, expressed as a 'String',
              --   to be sent to /Mathematica/ for evaluation.
              --
              --   The 'String' is wrapped with a @ToExpression@ before
              --   being sent.
            | DeclMsg String String String
              -- ^ Define a /Mathematica/ message.
              --
              --   @'DeclMsg' /sym/ /tag/ /defn/@ in Haskell maps to
              --   @/sym/::/tag/ = /defn/@ in /Mathematica/.
            | forall a b . (MLGet a, MLPut b) => 
              DeclFn { -- | A /Mathematica/ pattern, expressed as a
                       --   'String', such that a matching expression
                       --   should signal a call to this Haskell function.
                       callPattern :: String
                       -- | A /Mathematica/ pattern, specifying the 
                       --   expression that is to be marshaled to the 
                       --   Haskell function. 
                       --   
                       --   Pattern variables bound in the 'callPattern' 
                       --   match are in scope for 'argPattern'.
                     , argPattern :: String
                       -- | The function to be called on the Haskell side.
                     , func :: a -> ML b
                     }
              -- ^ Declare a function to be callable from /Mathematica/.
            | Exec (ML ())
              -- ^ Specify an arbitrary action to be executed 
              --   in the process of setting up the package.

globalSpec :: MLSpec
globalSpec = [ DeclMsg "MathLink" "usage" 
                 "Symbol whose only purpose is to provide a place to \
                 \define system-wide messages for the mathlink Haskell \
                 \package."
             , DeclMsg "MathLink" "exn"
                 "Exception caught: `1`"
             , DeclMsg "MathLink" "fnix"
                 "Call packet received with invalid function index: `1`."
             ]

wrapFn :: (MLGet a, MLPut b) => (a -> ML b) -> ML ()
wrapFn fn = do
    lnk <- newLoopbackLink
    transferTo lnk
    newPacket
    clearAbort
    clearInterrupt
    result <- liftIO $ do
      vres <- newEmptyMVar
      idMl <- forkIO $ do runML (do arg <- mlGet
                                    newPacket
                                    fn arg >>= mlPut
                                    endPacket
                                    flush) lnk
                          putMVar vres (Right True)
                       `catch` (\e -> putMVar vres (Left e))
      idIo <- forkIO $ do untilInterrupt
                          putMVar vres (Right False)
                       `catch` (\e -> putMVar vres (Left e))
      res <- takeMVar vres
      killThread idMl
      killThread idIo
      return res
    clearMLError
    case result of
      Right True  -> transferFrom lnk
      Right False -> (mlPut $ Sy "$Aborted")
      Left err    -> do printMessage "MathLink" "exn" 
                          [St $ show (err :: SomeException)]
                        mlPut $ Sy "$Failed"
    endPacket
    flush
  where untilInterrupt = do
          bl <- checkInterrupt
          if bl then return () else do
            threadDelay 100000
            untilInterrupt


-- | Run the /MathLink/ loop.
runMLSpec
    :: MLSpec   -- ^ The package specification
    -> IO ()
runMLSpec spec = do
  result <- try $ runMLMain $ runSpec spec
  case result of
    Right () -> return ()
    Left (MLErr _ 1  _) -> return ()  -- link dead
    Left (MLErr _ 11 _) -> return ()  -- link closed
    Left err -> putStrLn $ "Error occurred: " ++ show err
        
runSpec :: MLSpec -> ML ()
runSpec spec = do
  (_,fns) <- foldM processDecl (0,[]) (globalSpec ++ spec)
  mlPut $ Sy "End"
  flush
  answer $ IM.fromList fns

processDecl :: (Int,[(Int,ML ())]) 
            ->  MLDecl 
            -> ML (Int,[(Int,ML ())])
processDecl pr (Eval v) = do
  send v
  return pr
processDecl pr (EvalStr str) = do
  sendString str
  return pr
processDecl pr (DeclMsg sym tag defn) = do
  defineMessage sym tag defn
  return pr
processDecl pr (Exec action) = do
  action
  return pr
processDecl (n,fns) (DeclFn callPat argPat fn) = do
  send ((Sy "DefineExternal"):@[St callPat, St argPat, I (fromIntegral n)])
  return (n+1,(n, wrapFn fn):fns)
      
answer :: IntMap (ML ()) -> ML ()
answer fnMap = do
  waitForPacket (== (MLPacket PktCall))
  n <- mlGet
  case IM.lookup (fromInteger n) fnMap of
    Nothing -> do newPacket
                  printMessage "MathLink" "fnix" [I n]
                  mlPut $ Sy "$Failed"
                  endPacket
    Just fn -> fn
  answer fnMap

{- $example

@
module Main where

import "Foreign.MathLink"

addFour :: ('Int','Int','Int','Int') -> 'ML' 'Int'
addFour (a,b,c,d) = 'return' '$' a '+' b '+' c '+' d

ackermann :: ('Integer','Integer') -> 'ML' 'Integer'
ackermann (m,n) = 'return' '$' ack m n

ack :: 'Integer' -> 'Integer' -> 'Integer'
ack 0 n = n '+' 1
ack 1 n = n '+' 2
ack 2 n = 2 '*' n '+' 3
ack m 0 = ack (m '-' 1) 1
ack m n = ack (m '-' 1) (ack m (n '-' 1))

decl :: 'MLSpec'
decl = 
  [ 'Eval' $ \"BeginPackage\":\@['St' \"Test\`\"]

\  , 'DeclMsg' \"AddFour\" \"usage\" \"...\"
  , 'DeclMsg' \"Ackermann\" \"usage\" \"...\"
  , 'Eval' $ \"Begin\":\@['St' \"\`Private\`\"]

\  , 'DeclFn' { 
      'callPattern' = \"AddFour[a_Integer,b_Integer,c_Integer,d_Integer]\"
    , 'argPattern' = \"{a,b,c,d}\"
    , 'func' = addFour
    }

\  , 'DeclFn' {
      'callPattern' = \"Ackermann[i_Integer,j_Integer]\"
    , 'argPattern' = \"{i,j}\"
    , 'func' = ackermann
    }

\  , 'Eval' $ \"End\":\@[]
  , 'Eval' $ \"EndPackage\":\@[]
  ]
@
-}

{- $notes

 * The library implementation uses multiple threads so that execution of
   a Haskell function called from /Mathematica/ will respond immediately
   to an abort request.

 * Exceptions thrown during the evaluation of a Haskell function are
   caught and a corresponding message is sent to the /Mathematica/ front
   end.

 * The message loop can be run within @ghci@. Unfortunately, however,
   setting breakpoints causes a segmentation fault.
-}

{- $limitations

   In the current implementation, only one /MathLink/ connection may be 
   made per process.
-}