File: Objects.hs

package info (click to toggle)
missingpy 0.10.0.2
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 464 kB
  • ctags: 42
  • sloc: haskell: 1,433; makefile: 129; ansic: 107
file content (468 lines) | stat: -rw-r--r-- 17,705 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
{-# OPTIONS -fallow-overlapping-instances #-}

{- arch-tag: Python type instances
Copyright (C) 2005 John Goerzen <jgoerzen@complete.org>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

{- |
   Module     : Python.Objects
   Copyright  : Copyright (C) 2005 John Goerzen
   License    : GNU GPL, version 2 or above

   Maintainer : John Goerzen,
   Maintainer : jgoerzen\@complete.org
   Stability  : provisional
   Portability: portable

Python type instances and object utilities.

For more similar utilities, see "Python.Objects.File" and
"Python.Objects.Dict".

Written by John Goerzen, jgoerzen\@complete.org
-}

module Python.Objects (
                       -- * Basic Object Types
                       PyObject,
                       -- * Conversions between Haskell and Python Objects
                       ToPyObject(..),
                       FromPyObject(..),
                       -- * Information about Python Objects
                       typeOf,
                       strOf,
                       reprOf,
                       showPyObject,
                       dirPyObject,
                       getattr,
                       hasattr,
                       setattr,
                       -- * Conversions between Python Objects
                       pyList_AsTuple,
                       -- * Calling Python Objects
                       pyObject_Call,
                       pyObject_CallHs,
                       pyObject_RunHs,
                       callMethodHs,
                       runMethodHs,
                       noParms,
                       noKwParms
                      )
where
import Python.Types
import Python.Utils
import Foreign.C.Types
import Foreign.C.String
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Alloc
import Data.List
import System.IO.Unsafe
import Python.ForeignImports

{- | Members of this class can be converted from a Haskell type
to a Python object. -}
class ToPyObject a where
    toPyObject :: a -> IO PyObject

{- | Members of this class can be derived from a Python object. -}
class FromPyObject a where
    fromPyObject :: PyObject -> IO a

----------------------------------------------------------------------
-- Functions
----------------------------------------------------------------------
{- | Gets the type of a Python object.  Same as type(x) in Python. -}
typeOf :: PyObject -> IO PyObject
typeOf x = withPyObject x (\pyo -> pyObject_Type pyo >>= fromCPyObject)
      
{- | Gets a string representation of a Python object.  Same 
as str(x) in Python. -}
strOf :: PyObject -> IO String
strOf x = withPyObject x 
            (\pyo -> pyObject_Str pyo >>= fromCPyObject >>= fromPyObject)

                                
{- | Gets the Python representation of a Python object.
Same as repr(x) in Python. -}
reprOf :: PyObject -> IO String
reprOf x = withPyObject x
             (\pyo -> pyObject_Repr pyo >>= fromCPyObject >>= fromPyObject)

{- | Displays a Python object and its type. -}
showPyObject :: PyObject -> IO String
showPyObject x = do typestr <- typeOf x >>= strOf
                    contentstr <- strOf x
                    return $ typestr ++ ": " ++ contentstr

{- | Displays a list of keys contained in the Python object. -}
dirPyObject :: PyObject -> IO [String]
dirPyObject x = withPyObject x (\cpyo ->
                   do dr <- pyObject_Dir cpyo >>= fromCPyObject
                      fromPyObject dr
                               )

{- | Call a Python object with all-Haskell parameters.
Similar to 'PyObject_Call'.  This limits you to a single item type for
the regular arguments and another single item type for the keyword arguments. 
Nevertheless, it could be a handy shortcut at times.

For a higher-level wrapper, see 'Python.Interpreter.callByName'.

You may find 'noParms' and 'noKwParms' useful if you aren't passing any
parameters. -}
pyObject_CallHs :: (ToPyObject a, ToPyObject b, FromPyObject c) =>
                   PyObject     -- ^ Object t
                -> [a]          -- ^ List of non-keyword parameters
                -> [(String, b)] -- ^ List of keyword parameters
                -> IO c          -- ^ Return value
pyObject_CallHs callobj simpleargs kwargs =
    pyObject_Hs callobj simpleargs kwargs >>= fromPyObject

pyObject_Hs :: (ToPyObject a, ToPyObject b) =>
                   PyObject     -- ^ Object t
                -> [a]          -- ^ List of non-keyword parameters
                -> [(String, b)] -- ^ List of keyword parameters
                -> IO PyObject         -- ^ Return value
pyObject_Hs callobj simpleargs kwargs =
    let conv (k, v) = do v1 <- toPyObject v
                         return (k, v1)
        in
        do s <- mapM toPyObject simpleargs
           k <- mapM conv kwargs
           pyObject_Call callobj s k

{- | Like 'PyObject_CallHs', but discards the return value. -}
pyObject_RunHs :: (ToPyObject a, ToPyObject b) =>
                   PyObject     -- ^ Object t
                -> [a]          -- ^ List of non-keyword parameters
                -> [(String, b)] -- ^ List of keyword parameters
                -> IO ()         -- ^ Return value
pyObject_RunHs callobj simpleargs kwargs =
    pyObject_Hs callobj simpleargs kwargs >> return ()

callMethodHs_internal :: (ToPyObject a, ToPyObject b) =>
                         PyObject
                      -> String
                      -> [a]
                      -> [(String, b)]
                      -> IO PyObject
callMethodHs_internal pyo method args kwargs =
    do mobj <- getattr pyo method
       pyObject_Hs mobj args kwargs
                            
{- | Calls the named method of the given object. -}
callMethodHs :: (ToPyObject a, ToPyObject b, FromPyObject c) =>
                PyObject        -- ^ The main object
             -> String          -- ^ Name of method to call
             -> [a]             -- ^ Non-kw args
             -> [(String, b)]   -- ^ Keyword args
             -> IO c            -- ^ Result
callMethodHs pyo method args kwargs =
    callMethodHs_internal pyo method args kwargs >>= fromPyObject

{- | Like 'callMethodHs', but discards the return value. -}
runMethodHs :: (ToPyObject a, ToPyObject b) =>
                PyObject        -- ^ The main object
             -> String          -- ^ Name of method to call
             -> [a]             -- ^ Non-kw args
             -> [(String, b)]   -- ^ Keyword args
             -> IO ()            -- ^ Result
runMethodHs pyo method args kwargs =
    callMethodHs_internal pyo method args kwargs >> return ()

noParms :: [String]
noParms = []

noKwParms :: [(String, String)]
noKwParms = []



{- | Call a Python object (function, etc).

For a higher-level wrapper, see 'Python.Interpreter.callByName'.
 -}
pyObject_Call :: PyObject       -- ^ Object to call
              -> [PyObject]     -- ^ List of non-keyword parameters (may be empty)
              -> [(String, PyObject)] -- ^ List of keyword parameters (may be empty)
              -> IO PyObject    -- ^ Return value
pyObject_Call callobj simpleparams kwparams =
        do pyosimple <- toPyObject simpleparams >>= pyList_AsTuple
           pyokw <- toPyObject kwparams
           cval <- withPyObject callobj (\ccallobj ->
                    withPyObject pyosimple (\cpyosimple ->
                     withPyObject pyokw (\cpyokw ->
                      cpyObject_Call ccallobj cpyosimple cpyokw)))
           fromCPyObject cval
       
-- ^ Converts a Python list to a tuple.
pyList_AsTuple :: PyObject -> IO PyObject
pyList_AsTuple x =
    withPyObject x (\cpo -> cpyList_AsTuple cpo >>= fromCPyObject)

{- | An interface to a function similar to Python's getattr.  This will
look up an attribute (such as a method) of an object. -}
getattr :: PyObject -> String -> IO PyObject
getattr pyo s =
    withPyObject pyo (\cpo ->
     withCString s (\cstr ->
      pyObject_GetAttrString cpo cstr >>= fromCPyObject))

{- | An interface to Python's hasattr.  Returns True if the named
attribute exists; False otherwise. -}
hasattr :: PyObject -> String -> IO Bool
hasattr pyo s =
    withPyObject pyo (\cpo ->
     withCString s (\cstr ->
      do r <- pyObject_HasAttrString cpo cstr >>= checkCInt
         if r == 0
            then return False
            else return True
                   )
                     )
{- | An interface to Python's setattr, used to set attributes of an object.
-}
setattr :: PyObject             -- ^ Object to operate on
        -> String               -- ^ Name of attribute
        -> PyObject             -- ^ Set the attribute to this value
        -> IO ()
setattr pyo s setpyo =
    withPyObject pyo (\cpo ->
     withPyObject setpyo (\csetpyo ->
      withCString s (\cstr ->
       pyObject_SetAttrString cpo cstr csetpyo >>= checkCInt >> return ()
                    )))

----------------------------------------------------------------------
-- Instances
----------------------------------------------------------------------

-- FIXME: ERROR CHECKING!

--------------------------------------------------
-- [PyObject] Lists

-- | Lists from a PyObject
instance ToPyObject [PyObject] where
    toPyObject mainlist =
        do l <- pyList_New 0
           mapM_ (\pyo -> withPyObject pyo (\x -> pyList_Append l x >>= checkCInt)) mainlist
           fromCPyObject l

-- | Tuples and Lists to [PyObject] lists
instance FromPyObject [PyObject] where
    fromPyObject x = 
        let worker cpyo =
                do islist <- pyList_Check cpyo >>= checkCInt
                   istuple <- pyTuple_Check cpyo >>= checkCInt
                   if islist /= 0
                      then fromx pyList_Size pyList_GetItem cpyo
                      else if istuple /= 0
                                 then fromx pyTuple_Size pyTuple_GetItem cpyo
                                 else fail "Error fromPyObject to [PyObject]: Passed object not a list or tuple."
            fromx sizefunc itemfunc cpyo = do size <- sizefunc cpyo
                                              fromx_worker 0 size itemfunc cpyo
            fromx_worker counter size itemfunc cpyo =
                if counter >= size 
                   then return []
                   else do thisitem <- itemfunc cpyo counter
                           py_incref thisitem
                           thisobj <- fromCPyObject thisitem
                           {- This unsafeInterlaveIO caused segfaults.  Theory:
                              parent object would be deallocated before all
                              items would be consumed. -}
                           next <- {-unsafeInterleaveIO $-} fromx_worker (succ counter) size itemfunc cpyo
                           return $ thisobj : next
            in
            withPyObject x worker

--------------------------------------------------
-- Association Lists

-- | Dicts from ALs
instance ToPyObject [(PyObject, PyObject)] where
    toPyObject mainlist =
        do d <- pyDict_New
           mapM_ (setitem d) mainlist
           fromCPyObject d
        where setitem l (key, value) =
                  withPyObject key (\keyo ->
                      withPyObject value (\valueo ->
                          pyObject_SetItem l keyo valueo >>= checkCInt))

-- | ALs from Dicts
instance FromPyObject [(PyObject, PyObject)] where
    fromPyObject pydict = withPyObject pydict (\cpydict ->
           -- Type sigs here are for clarity only
        do -- This gives a PyObject
           items <- (pyMapping_Items cpydict >>= fromCPyObject):: IO PyObject
           -- Now, make a Haskell [[PyObject, PyObject]] list
           itemlist <- (fromPyObject items)::IO [[PyObject]]
           -- Finally, convert it to a list of tuples.
           return $ map list2tup itemlist
                                              )
        where list2tup x = case x of
                                  x1:x2:[] -> (x1, x2)
                                  _ -> error "Expected 2-tuples in fromPyObject dict"
                                       
-- | This is a common variant used for arg lists
instance ToPyObject a => ToPyObject [(a, PyObject)] where
    toPyObject mainlist =
        let conv (k, v) = do k1 <- toPyObject k
                             return (k1, v)
            in mapM conv mainlist >>= toPyObject
instance FromPyObject a => FromPyObject [(a, PyObject)] where
    fromPyObject pyo =
        let conv (k, v) = do k1 <- fromPyObject k
                             return (k1, v)
        in do list <- (fromPyObject pyo)::IO [(PyObject, PyObject)]
              mapM conv list


-- | Dicts from Haskell objects
instance (ToPyObject a, ToPyObject b) => ToPyObject [(a, b)] where
    toPyObject mainlist =
        let convone (i1, i2) = do oi1 <- toPyObject i1
                                  oi2 <- toPyObject i2
                                  return (oi1, oi2)
        in do newl <- mapM convone mainlist
              toPyObject newl

-- | Dicts to Haskell objects
instance (FromPyObject a, FromPyObject b) => FromPyObject [(a, b)] where
    fromPyObject pydict =
        let conv (x, y) = do x1 <- fromPyObject x
                             y1 <- fromPyObject y
                             return (x1, y1)
            in do pyodict <- ((fromPyObject pydict)::IO [(PyObject, PyObject)])
                  mapM conv pyodict

--------------------------------------------------
-- Strings

-- CStringLen to PyObject.  Use CStringLen to handle embedded nulls.
instance ToPyObject CStringLen where
   toPyObject (x, len) = 
       pyString_FromStringAndSize x (fromIntegral len) >>= fromCPyObject

-- String to PyObject
instance ToPyObject String where
    toPyObject x = withCString x (\cstr -> toPyObject (cstr, length x))

-- PyObject to String
instance FromPyObject String where
    fromPyObject x = withPyObject x (\po ->
        alloca (\lenptr ->
           alloca (\strptr ->
            do pyString_AsStringAndSize po strptr lenptr
               len <- peek lenptr
               cstr <- peek strptr
               peekCStringLen (cstr, (fromIntegral) len)
                  )
               )
                                    )

--------------------------------------------------
-- Numbers, Python Ints

-- Python ints are C longs
instance ToPyObject CLong where
    toPyObject x =  pyInt_FromLong x >>= fromCPyObject

-- And convert back.
instance FromPyObject CLong where
    fromPyObject x = withPyObject x pyInt_AsLong

-- We'll also support CInts.
instance ToPyObject CInt where
    toPyObject x = toPyObject ((fromIntegral x)::CLong)

instance FromPyObject CInt where
    fromPyObject x = do y <- (fromPyObject x)::IO CLong
                        return $ fromIntegral y

--------------------------------------------------
-- Numbers, Python Longs

instance ToPyObject Integer where
    toPyObject i = 
        -- Use strings here since no other C type supports
        -- unlimited precision.
        let repr = show i
        in withCString repr (\cstr -> 
             pyLong_FromString cstr nullPtr 10 >>= fromCPyObject)
                                 
instance FromPyObject Integer where
    fromPyObject pyo = 
        do longstr <- strOf pyo
           return $ read longstr

--------------------------------------------------
-- Numbers, anything else.
{- For these, we attempt to guess whether to handle it as an
int or a long. -}
{-
Disabled for now; this is a low-level interface, and it seems to be overly
complex for this.

instance Integral a => ToPyObject a where
    toPyObject x =
        let intval = toInteger x
            in
            if (intval < (toInteger (minBound::CLong)) ||
                intval > (toInteger (maxBound::CLong)))
                then toPyObject intval
                else toPyObject ((fromIntegral x)::CLong)

-- On the return conversion, we see what the bounds for
-- the desired type are, and treat it thusly.
instance (Bounded a, Integral a) => FromPyObject a where
    fromPyObject x =
        let minpyint = toInteger (minBound::CLong)
            maxpyint = toInteger (maxBound::CLong)
            minpassed = toInteger (minBound::a)
            maxpassed = toInteger (maxBound::a)
            in if (minpassed < minpyint || maxpassed > maxpyint)
                  then do intval <- fromPyObject x
                          return $ fromInteger intval
                  else do longval <- ((fromPyObject x)::IO CLong)
                          return $ fromIntegral longval

-}

--------------------------------------------------
-- Floating-Point Values

instance ToPyObject CDouble where
    toPyObject x = pyFloat_FromDouble x >>= fromCPyObject

instance FromPyObject CDouble where
    fromPyObject x = withPyObject x pyFloat_AsDouble

-- | Lists from anything else
instance ToPyObject a => ToPyObject [a] where
    toPyObject mainlist = 
        do newlist <- mapM toPyObject mainlist
           toPyObject newlist

instance FromPyObject a => FromPyObject [a] where
    fromPyObject pylistobj = 
        do pylist <- fromPyObject pylistobj
           mapM fromPyObject pylist