packages feed

pyfi-0.4.0.2: Python.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveDataTypeable #-}

module Python(
    module Str,
    defO,
    defV,
    defOO,
    defOV,
    defVO,
    defVV,
    defOOO,
    defOOV,
    defOVO,
    defOVV,
    defVOO,
    defVOV,
    defVVO,
    defVVV,
    defOOOO,
    defOOOV,
    defOOVO,
    defOOVV,
    defOVOO,
    defOVOV,
    defOVVO,
    defOVVV,
    defVOOO,
    defVOOV,
    defVOVO,
    defVOVV,
    defVVOO,
    defVVOV,
    defVVVO,
    defVVVV,
    PyObject,
    PythonException,
    exceptionType,
    )
where

import Str
import Data.Digest.Pure.MD5 (md5)
import Foreign.C
import Foreign (FunPtr, ForeignPtr)
import Foreign.Ptr (Ptr, nullPtr)
import Foreign.ForeignPtr (newForeignPtr, withForeignPtr)
import Foreign.Marshal.Alloc (free)
import Data.Aeson (FromJSON, ToJSON, toJSON, encode, decode)
import Data.ByteString.Lazy.Char8 (unpack, pack)
import Data.IORef (IORef, newIORef, writeIORef, readIORef)
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.Map as Map
import Data.Maybe (fromJust)
import Data.List (elemIndex)
import Control.Exception (Exception, throw)
import Data.Typeable
import Data.Monoid (mconcat)

modules :: IORef (Map.Map String RawPyObject)
modules = unsafePerformIO $ newIORef (Map.empty)

data P
type RawPyObject = (Ptr P)
data PyObject a = PyObject (ForeignPtr P) deriving Show

data PythonException = PyException { exceptionType :: String, exceptionValue :: String } | DecodeException 
    deriving (Typeable)

instance Show PythonException where
    show DecodeException = "DecodeException"
    show x = exceptionType x ++ "\n" ++ exceptionValue x

instance Exception PythonException

foreign import ccall "getObject" c_getObject:: CString -> IO (RawPyObject)
foreign import ccall "getObjectInModule" c_getObjectInModule :: CString -> CString -> IO (RawPyObject)
foreign import ccall "execInModule" c_execInModule :: CString -> CString -> IO ()
foreign import ccall "PyRun_SimpleString" pyRun_SimpleString :: CString -> IO ()
foreign import ccall "Py_DecRef" py_DecRef :: RawPyObject -> IO ()
foreign import ccall "Py_Initialize" py_initialize :: IO ()
foreign import ccall "Py_Finalize" finalize :: IO ()
foreign import ccall "Py_BuildValue" py_BuildValueString :: CString -> CString -> IO (RawPyObject)
foreign import ccall "Py_BuildValue" py_BuildValueString2 :: CString -> CString -> CString -> IO (RawPyObject)
foreign import ccall "Py_BuildValue" py_BuildValueObject :: CString -> RawPyObject -> IO (RawPyObject)
foreign import ccall "Py_BuildValue" py_BuildValueObject2 :: CString -> RawPyObject -> RawPyObject -> IO (RawPyObject)
foreign import ccall "Py_BuildValue" py_BuildValueObject3 :: CString -> RawPyObject -> RawPyObject -> RawPyObject -> IO (RawPyObject)
foreign import ccall "Py_BuildValue" py_BuildValueObject4 :: CString -> RawPyObject -> RawPyObject -> RawPyObject -> RawPyObject -> IO (RawPyObject)
foreign import ccall "PyObject_CallObject" pyObject_CallObject :: RawPyObject -> RawPyObject -> IO (RawPyObject)
foreign import ccall "PyString_AsString" pyString_AsString :: RawPyObject -> IO CString
foreign import ccall unsafe "gimmeFunc" gimmeFunc :: CInt -> IO (FunPtr (RawPyObject -> IO ()))
foreign import ccall "checkError" c_checkError :: IO CString

exec :: String -> IO ()
exec s = withCString s pyRun_SimpleString

withCString2 :: String -> String -> (CString -> CString -> IO a) -> IO a
withCString2 s1 s2 f = withCString s1 f' where
    f' cs1 = withCString s2 (f cs1)

execInModule :: String -> String -> IO ()
execInModule moduleName payload = 
    withCString2 payload moduleName c_execInModule

parseException :: String -> PythonException
parseException s = let (t, v) = splitAt (fromJust $ (elemIndex ',' s)) s
                       (_, t2) = splitAt (fromJust $ (elemIndex '\'' t)) t
                       (t3, _) = splitAt (fromJust $ (elemIndex '\'' (tail t2 ))) (tail t2 )
                   in PyException { exceptionType = t3, exceptionValue = tail v }

checkError :: String -> IO ()
checkError funcdef = do
  cs <- c_checkError
  if cs == nullPtr
  then return ()
  else do
    s <- peekCString cs
    free cs
    throw . parseException $ mconcat[s, "\n", take 70 $ cycle "-", "\n", funcdef]

initialize :: IO ()
initialize = do
  py_initialize

getObjectInModule :: String -> String -> IO (RawPyObject)
getObjectInModule moduleName objectName = 
    withCString2 objectName moduleName c_getObjectInModule

getObject :: String -> IO (RawPyObject)
getObject s = withCString s c_getObject

jsonfunc :: String
jsonfunc = [str|
def jsonfunc(argformats):
    def wrapper(f):
        import json
        import traceback
        def json_wrapper(*args):
            try:
                new_args = []
                for (x, format) in zip(args, argformats):
                    if format == 'V':
                        new_x = json.loads(x)
                        new_args.append(new_x)
                    else:
                        new_args.append(x)
                result = f(*new_args)
                if argformats[-1] == 'V':
                    result = json.dumps(result)
                return result
            except Exception as ex:
                assert("," not in str(type(ex)))
                ex.args = (traceback.format_exc(),)
                raise ex
        return json_wrapper
    return wrapper
|]

hash :: String -> String
hash contents = show . md5 $ pack contents

mydecode :: (FromJSON a) => String -> Maybe a
mydecode s = do
    x <- decode . pack . (\x -> "[" ++ x ++ "]") $ s
    return $ head x -- This code is dangerous. prelude's `head` isnt safe


toPyObject :: (ToJSON a) => a -> IO (PyObject b)
toPyObject x = do
    y <- return . unpack . encode $ x
    p <- withCString "s" (\cs -> 
           withCString y (\cy -> 
             py_BuildValueString cs cy) )
    newForeignPyPtr p

fromPyObject :: (FromJSON a) => PyObject b -> IO a
fromPyObject (PyObject fr) = do
    r2 <- withForeignPtr fr $ \r -> peekCString =<< pyString_AsString r
    return $ case mydecode r2 of
               Just x -> x
               Nothing -> throw $ DecodeException

getFunc :: String -> String -> IO RawPyObject
getFunc s argTypes = do
    currentModules <- readIORef $ modules
    key <- return $ hash s
    if Map.member "initialized" currentModules then
      return ()
    else initialize
    case Map.lookup key currentModules of
      Just p -> return p
      Nothing -> do 
        execInModule key jsonfunc
        execInModule key s
        execInModule key $ "export = jsonfunc('" ++ argTypes ++ "')(export)"
        f' <- getObjectInModule key "export"
        if f' == nullPtr
          then error "NameError: name 'export' is not defined"
          else return ()
        writeIORef modules (Map.insert (hash s) f' currentModules)
        return f'

newForeignPyPtr :: RawPyObject -> IO (PyObject b)
newForeignPyPtr r = do
    finalizer <- gimmeFunc 0
    return . PyObject =<< newForeignPtr finalizer r

def1 :: String -> String -> IO (PyObject b)
def1 s argTypes = do
    f <- getFunc s argTypes
    r <- pyObject_CallObject f nullPtr
    checkError s
    newForeignPyPtr r

def2 :: String -> String -> (PyObject a) -> IO (PyObject b)
def2 s argTypes (PyObject fx1) = do
    f <- getFunc s argTypes
    p1 <- withForeignPtr fx1 $ \x1 -> withCString "(O)" (\cs -> py_BuildValueObject cs x1) 
    (PyObject fp) <- newForeignPyPtr p1
    r <- withForeignPtr fp $ \p -> pyObject_CallObject f p
    checkError s
    newForeignPyPtr r

def3 :: String -> String -> (PyObject a1) -> (PyObject a2) -> IO (PyObject b)
def3 s argTypes (PyObject fx1) (PyObject fx2) = do
    f <- getFunc s argTypes
    p1 <- withForeignPtr fx1 $ \x1 -> (
        withForeignPtr fx2 $ \x2 -> (
            withCString "(OO)" (\cs -> py_BuildValueObject2 cs x1 x2) 
        ))
    (PyObject fp) <- newForeignPyPtr p1
    r <- withForeignPtr fp $ \p -> pyObject_CallObject f p
    checkError s
    newForeignPyPtr r

def4 :: String -> String -> (PyObject a1) -> (PyObject a2) -> (PyObject a3) -> IO (PyObject b)
def4 s argTypes (PyObject fx1) (PyObject fx2) (PyObject fx3) = do
    f <- getFunc s argTypes
    p1 <- withForeignPtr fx1 $ \x1 -> (
        withForeignPtr fx2 $ \x2 -> (
            withForeignPtr fx3 $ \x3 -> (
                withCString "(OOO)" (\cs -> py_BuildValueObject3 cs x1 x2 x3) 
        )))
    (PyObject fp) <- newForeignPyPtr p1
    r <- withForeignPtr fp $ \p -> pyObject_CallObject f p
    checkError s
    newForeignPyPtr r

def5 :: String -> String -> (PyObject a1) -> (PyObject a2) -> (PyObject a3) -> (PyObject a4) -> IO (PyObject b)
def5 s argTypes (PyObject fx1) (PyObject fx2) (PyObject fx3) (PyObject fx4) = do
    f <- getFunc s argTypes
    p1 <- withForeignPtr fx1 $ \x1 -> (
        withForeignPtr fx2 $ \x2 -> (
            withForeignPtr fx3 $ \x3 -> (
                withForeignPtr fx4 $ \x4 -> (
                    withCString "(OOOO)" (\cs -> py_BuildValueObject4 cs x1 x2 x3 x4) 
        ))))
    (PyObject fp) <- newForeignPyPtr p1
    r <- withForeignPtr fp $ \p -> pyObject_CallObject f p
    checkError s
    newForeignPyPtr r

defO :: String -> IO (PyObject b)
defO s  = do
    fr <- def1 s "O" 
    return fr

defV :: (FromJSON b) => String -> IO b
defV s  = do
    fr <- def1 s "V" 
    b <- fromPyObject fr
    return b

defOO :: String -> (PyObject a1) -> IO (PyObject b)
defOO s x1 = do
    fr <- def2 s "OO" x1
    return fr

defOV :: (FromJSON b) => String -> (PyObject a1) -> IO b
defOV s x1 = do
    fr <- def2 s "OV" x1
    b <- fromPyObject fr
    return b

defVO :: (ToJSON a1) => String -> a1 -> IO (PyObject b)
defVO s input1 = do
    x1 <- toPyObject input1
    fr <- def2 s "VO" x1
    return fr

defVV :: (ToJSON a1, FromJSON b) => String -> a1 -> IO b
defVV s input1 = do
    x1 <- toPyObject input1
    fr <- def2 s "VV" x1
    b <- fromPyObject fr
    return b

defOOO :: String -> (PyObject a1) -> (PyObject a2) -> IO (PyObject b)
defOOO s x1 x2 = do
    fr <- def3 s "OOO" x1 x2
    return fr

defOOV :: (FromJSON b) => String -> (PyObject a1) -> (PyObject a2) -> IO b
defOOV s x1 x2 = do
    fr <- def3 s "OOV" x1 x2
    b <- fromPyObject fr
    return b

defOVO :: (ToJSON a2) => String -> (PyObject a1) -> a2 -> IO (PyObject b)
defOVO s x1 input2 = do
    x2 <- toPyObject input2
    fr <- def3 s "OVO" x1 x2
    return fr

defOVV :: (ToJSON a2, FromJSON b) => String -> (PyObject a1) -> a2 -> IO b
defOVV s x1 input2 = do
    x2 <- toPyObject input2
    fr <- def3 s "OVV" x1 x2
    b <- fromPyObject fr
    return b

defVOO :: (ToJSON a1) => String -> a1 -> (PyObject a2) -> IO (PyObject b)
defVOO s input1 x2 = do
    x1 <- toPyObject input1
    fr <- def3 s "VOO" x1 x2
    return fr

defVOV :: (ToJSON a1, FromJSON b) => String -> a1 -> (PyObject a2) -> IO b
defVOV s input1 x2 = do
    x1 <- toPyObject input1
    fr <- def3 s "VOV" x1 x2
    b <- fromPyObject fr
    return b

defVVO :: (ToJSON a1, ToJSON a2) => String -> a1 -> a2 -> IO (PyObject b)
defVVO s input1 input2 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    fr <- def3 s "VVO" x1 x2
    return fr

defVVV :: (ToJSON a1, ToJSON a2, FromJSON b) => String -> a1 -> a2 -> IO b
defVVV s input1 input2 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    fr <- def3 s "VVV" x1 x2
    b <- fromPyObject fr
    return b

defOOOO :: String -> (PyObject a1) -> (PyObject a2) -> (PyObject a3) -> IO (PyObject b)
defOOOO s x1 x2 x3 = do
    fr <- def4 s "OOOO" x1 x2 x3
    return fr

defOOOV :: (FromJSON b) => String -> (PyObject a1) -> (PyObject a2) -> (PyObject a3) -> IO b
defOOOV s x1 x2 x3 = do
    fr <- def4 s "OOOV" x1 x2 x3
    b <- fromPyObject fr
    return b

defOOVO :: (ToJSON a3) => String -> (PyObject a1) -> (PyObject a2) -> a3 -> IO (PyObject b)
defOOVO s x1 x2 input3 = do
    x3 <- toPyObject input3
    fr <- def4 s "OOVO" x1 x2 x3
    return fr

defOOVV :: (ToJSON a3, FromJSON b) => String -> (PyObject a1) -> (PyObject a2) -> a3 -> IO b
defOOVV s x1 x2 input3 = do
    x3 <- toPyObject input3
    fr <- def4 s "OOVV" x1 x2 x3
    b <- fromPyObject fr
    return b

defOVOO :: (ToJSON a2) => String -> (PyObject a1) -> a2 -> (PyObject a3) -> IO (PyObject b)
defOVOO s x1 input2 x3 = do
    x2 <- toPyObject input2
    fr <- def4 s "OVOO" x1 x2 x3
    return fr

defOVOV :: (ToJSON a2, FromJSON b) => String -> (PyObject a1) -> a2 -> (PyObject a3) -> IO b
defOVOV s x1 input2 x3 = do
    x2 <- toPyObject input2
    fr <- def4 s "OVOV" x1 x2 x3
    b <- fromPyObject fr
    return b

defOVVO :: (ToJSON a2, ToJSON a3) => String -> (PyObject a1) -> a2 -> a3 -> IO (PyObject b)
defOVVO s x1 input2 input3 = do
    x2 <- toPyObject input2
    x3 <- toPyObject input3
    fr <- def4 s "OVVO" x1 x2 x3
    return fr

defOVVV :: (ToJSON a2, ToJSON a3, FromJSON b) => String -> (PyObject a1) -> a2 -> a3 -> IO b
defOVVV s x1 input2 input3 = do
    x2 <- toPyObject input2
    x3 <- toPyObject input3
    fr <- def4 s "OVVV" x1 x2 x3
    b <- fromPyObject fr
    return b

defVOOO :: (ToJSON a1) => String -> a1 -> (PyObject a2) -> (PyObject a3) -> IO (PyObject b)
defVOOO s input1 x2 x3 = do
    x1 <- toPyObject input1
    fr <- def4 s "VOOO" x1 x2 x3
    return fr

defVOOV :: (ToJSON a1, FromJSON b) => String -> a1 -> (PyObject a2) -> (PyObject a3) -> IO b
defVOOV s input1 x2 x3 = do
    x1 <- toPyObject input1
    fr <- def4 s "VOOV" x1 x2 x3
    b <- fromPyObject fr
    return b

defVOVO :: (ToJSON a1, ToJSON a3) => String -> a1 -> (PyObject a2) -> a3 -> IO (PyObject b)
defVOVO s input1 x2 input3 = do
    x1 <- toPyObject input1
    x3 <- toPyObject input3
    fr <- def4 s "VOVO" x1 x2 x3
    return fr

defVOVV :: (ToJSON a1, ToJSON a3, FromJSON b) => String -> a1 -> (PyObject a2) -> a3 -> IO b
defVOVV s input1 x2 input3 = do
    x1 <- toPyObject input1
    x3 <- toPyObject input3
    fr <- def4 s "VOVV" x1 x2 x3
    b <- fromPyObject fr
    return b

defVVOO :: (ToJSON a1, ToJSON a2) => String -> a1 -> a2 -> (PyObject a3) -> IO (PyObject b)
defVVOO s input1 input2 x3 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    fr <- def4 s "VVOO" x1 x2 x3
    return fr

defVVOV :: (ToJSON a1, ToJSON a2, FromJSON b) => String -> a1 -> a2 -> (PyObject a3) -> IO b
defVVOV s input1 input2 x3 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    fr <- def4 s "VVOV" x1 x2 x3
    b <- fromPyObject fr
    return b

defVVVO :: (ToJSON a1, ToJSON a2, ToJSON a3) => String -> a1 -> a2 -> a3 -> IO (PyObject b)
defVVVO s input1 input2 input3 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    x3 <- toPyObject input3
    fr <- def4 s "VVVO" x1 x2 x3
    return fr

defVVVV :: (ToJSON a1, ToJSON a2, ToJSON a3, FromJSON b) => String -> a1 -> a2 -> a3 -> IO b
defVVVV s input1 input2 input3 = do
    x1 <- toPyObject input1
    x2 <- toPyObject input2
    x3 <- toPyObject input3
    fr <- def4 s "VVVV" x1 x2 x3
    b <- fromPyObject fr
    return b