mathlink-1.0.0.0: src/Foreign/MathLink.hs
{-# LANGUAGE MultiParamTypeClasses
, FlexibleContexts
, DeriveDataTypeable
#-}
-- | A Haskell interface to /Mathematica/'s /MathLink/.
module Foreign.MathLink ( -- * Basic usage
-- $usage
-- * Types
Function(..)
-- * Running /MathLink/
, runMathLink
, runMathLinkWithArgs
-- * Data marshaling
, get
, put
-- * Accessing /MathLink/ state
, getLink
, checkAbort
, checkDone
-- * Evaluation
, evaluate
, evaluateAndWait
) where
import Data.Int
import Data.IORef
import Data.Typeable
import Data.Array.Unboxed
import qualified Data.IntMap as IM ( IntMap
, toList
, fromList
, lookup
)
import Control.Monad.Reader
import Control.Monad.Error
import Control.Exception hiding ( evaluate
)
import System.IO ( hPutStrLn
, stderr
)
import System.Environment ( getArgs
)
import Foreign.MathLink.Expressible
import Foreign.MathLink.IO
data MLError = MLError
| MLErrorMsg String
| MLException SomeException
| MLErrors [MLError]
deriving (Typeable)
instance Show MLError where
show MLError = "MLError"
show (MLErrorMsg s) = "MLErrorMsg: " ++ s
show (MLException e) = "MLException: " ++ (show e)
show (MLErrors errs) = "MLErrors: " ++ show errs
instance Error MLError where
noMsg = MLError
strMsg s = MLErrorMsg s
data MLConfig = MLConfig { functionTable :: IM.IntMap Function
}
-- | Encapsulates a description of a function callable from /Mathematica/
data Function =
Function { -- | A string representing the /Mathematica/ pattern
-- whose match should result in a function call to
-- the specified Haskell function. Analogous to the
-- @:Pattern:@ directive in an input to
-- /Mathematica/'s @mprep@ utility.
callPattern :: String
-- | A string representing the /Mathematica/ pattern
-- for the argument that will be marshaled from
-- /Mathematica/ to Haskell. Pattern variables
-- appearing here are bound in the 'callPattern'
-- match. Analogous to the @:Arguments:@ directive
-- in an input to /Mathematica/'s @mprep@ utility.
, argumentPattern :: String
-- | The Haskell function to be invoked.
, function :: IO ()
}
instance Show Function where
show fn = "Function { callPattern = " ++
(show $ callPattern fn) ++
", argumentPattern = " ++
(show $ argumentPattern fn) ++ " }"
type ML a = ErrorT MLError (ReaderT MLConfig IO) a
getConfig :: ML MLConfig
getConfig = ask
-- | Runs /MathLink/, exposing the given list of functions.
runMathLink :: [Function] -> IO ()
runMathLink functions = do
args <- getArgs
runMathLinkWithArgs args functions
-- | Like 'runMathLink', but explicitly requires the command line
-- arguments to be passed to /MathLink/.
runMathLinkWithArgs :: [String]
-> [Function] -> IO ()
runMathLinkWithArgs args functions = do
initializeMathLink $ unwords args
er <- (runReaderT (runErrorT runLoop) config)
finalizeMathLink
case er of
Left err -> do hPutStrLn stderr $ show err
return ()
Right () -> return ()
where config = MLConfig { functionTable =
IM.fromList $ zip [0..] functions
}
runLoop :: ML ()
runLoop = do
installFunctionTable
processPackets
processPackets :: ML ()
processPackets = do
pkt <- answer
case pkt of
ResumePacketCode -> do
liftIO $ refuseToBeAFrontEnd
processPackets
_ -> return ()
answer :: ML PacketCode
answer = do
dn <- liftIO $ checkDone
if dn then do
liftIO clearAbort
return $ mkPacketCode 0
else do
pkt <- liftIO $ getPacket
case pkt of
CallPacketCode -> do
liftIO clearAbort
processCallPacket
liftIO endPacket
liftIO newPacket
answer
_ -> do
liftIO clearAbort
return pkt
printString :: String -> IO ()
printString str = do
evaluate $ ExFunction "Print" [ ExString str ]
return ()
processCallPacket :: ML ()
processCallPacket = do
expr <- liftIO get
case expr of
ExInt n -> do
config <- getConfig
case n `IM.lookup` (functionTable config) of
Just fn -> liftIO $ handle cleanUp (function fn)
_ -> throwError $ MLErrorMsg "Function lookup failed."
_ -> throwError $ MLErrorMsg "Expected int."
where
cleanUp err = do
clearError
printString $ "Error occurred in processing call: " ++ show (err :: SomeException)
put $ ExSymbol "$Failed"
refuseToBeAFrontEnd :: IO ()
refuseToBeAFrontEnd = do
putFunction "EvaluatePacket" 1
putFunction "Module" 2
putFunction "List" 1
putFunction "Set" 2
put meSym
put plSym
putFunction "CompoundExpression" 3
putFunction "Set" 2
put plSym
getLink >>= (liftIO . (\l -> transferExpression l l))
putFunction "Message" 2
putFunction "MessageName" 2
put plSym
put $ ExString "notfe"
put meSym
put meSym
endPacket
waitForPacket (== SuspendPacketCode)
where meSym = ExSymbol "me"
plSym = ExSymbol "$ParentLink"
installFunctionTable :: ML ()
installFunctionTable = do
liftIO activate
functionPairs <- getConfig >>= (return . IM.toList . functionTable)
mapM_ (liftIO . definePattern) functionPairs
liftIO $ put $ ExSymbol "End"
liftIO flush >>= maybeThrow
definePattern :: (Int,Function) -> IO ()
definePattern (ident,func) =
put $ ExFunction "DefineExternal" [ ExString $ callPattern func
, ExString $ argumentPattern func
, ExInt ident
]
-- | Sends the given 'String' to /Mathematica/ for evaluation.
--
-- Does not block
evaluate :: Expression -> IO Bool
evaluate expr = do
abrt <- checkAbort
if abrt then return False else do
put $ ExFunction "EvaluatePacket" [expr]
endPacket
return True
-- | Like 'evaluate', but blocks until the execution is complete.
evaluateAndWait :: Expression -> IO Bool
evaluateAndWait expr = handle
((const $ return False) :: SomeException -> IO Bool) $
do abrt <- checkAbort
if abrt then return False else do
result <- evaluate expr
if result then
waitForPacket (== ReturnPacketCode)
else
return ()
return True
waitForPacket :: (PacketCode -> Bool) -> IO ()
waitForPacket q = do
pkt <- getPacket
newPacket
if q pkt then return () else waitForPacket q
-- lifting utilities
maybeThrow :: Maybe MathLinkError -> ML ()
maybeThrow (Just err) = throwError $ MLException $ toException err
maybeThrow Nothing = return ()
valueOrThrow :: Either MathLinkError b -> ML b
valueOrThrow (Left err) = throwError $ MLException $ toException err
valueOrThrow (Right v) = return v
-- extra documentation
{- $usage
The following is a small Haskell module that exposes a function
callable from /Mathematica/ that gets a pair of 'Int's (as a tuple) and
returns their sum to /Mathematica/:
@
module Main where
import 'Foreign.MathLink'
addTwo :: 'IO' ()
addTwo = do
(i1,i2) <- 'get'
'put' ((i1 + i2) :: 'Int')
main = 'runMathLink' [ 'Function' \{ 'callPattern' = \"AddTwo[i_Integer,j_Integer]\"
, 'argumentPattern' = \"{i,j}\"
, 'function' = addTwo
\}
]
@
A function to be exposed to /Mathematica/ has type @'IO' ()@. In its body
it uses the 'get' function to receive a value from /Mathematica/, performs
the desired computation, and sends the result back to /Mathematica/ via the
'put' function.
The types that can be marshaled to\/from /Mathematica/ are instances of the
'Expressible' class.
See the @examples@ directory of the source distribution for more.
-}