/
Hubris.hs
198 lines (149 loc) · 7.43 KB
/
Hubris.hs
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
{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
module Language.Ruby.Hubris where
import Data.Word
import Data.Map as Map
-- import Language.Ruby.Hubris.Binding
import System.IO.Unsafe (unsafePerformIO)
import Foreign.C.Types
import Language.Ruby.Hubris.Binding
import Control.Monad (forM)
import Control.Applicative
import Debug.Trace
import Foreign.C.String
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Internal(w2c,c2w)
-- type Value = CULong
import System.IO.Unsafe
import Data.Array.IArray
import Data.Maybe
import Control.Exception
import Prelude hiding(catch)
import Monad hiding (when)
import Data.Typeable
wrap :: (Haskellable a, Show b, Rubyable b) => (a->b) -> (Value -> Value)
wrap func v= unsafePerformIO $ do r <- try (evaluate $ toRuby . func $ toHaskell v)
case r of
Left (e::HubrisException) -> createException "Blah" `traces` "died in haskell"
Right a -> return a
data HubrisException = HubrisException
deriving(Show, Typeable)
instance Exception HubrisException
-- utility stuff:
sshow s = Prelude.map w2c $S.unpack s
lshow s = Prelude.map w2c $L.unpack s
--traces = flip trace
traces a b = a
when v b c = if (rubyType v == b)
then c
else throw HubrisException
class Haskellable a where
toHaskell :: Value -> a
class Rubyable a where
toRuby :: a -> Value
instance Haskellable Int where
toHaskell v = when v RT_FIXNUM $ fix2int v
instance Rubyable Int where
toRuby i = int2fix i
instance Haskellable Integer where
toHaskell v = case rubyType v of
RT_BIGNUM -> read $ unsafePerformIO (rb_big2str v 10 >>= str2cstr >>= peekCString)
RT_FIXNUM -> fromIntegral $ fix2int v
_ -> throw HubrisException -- wonder if it's kosher to just let the pattern match fail...
instance Rubyable Integer where
toRuby i = rb_str_to_inum (unsafePerformIO $ (newCAString $ show i) >>= rb_str_new2) 10 1
instance Haskellable Bool where
toHaskell v = case rubyType v of
RT_TRUE -> True
RT_FALSE -> False
_ -> throw HubrisException
instance Rubyable Bool where
toRuby True = constToRuby RUBY_Qtrue
toRuby False = constToRuby RUBY_Qfalse
instance Rubyable Double where
toRuby d = rb_float_new d
instance Haskellable Double where
toHaskell v = case rubyType v of
RT_FLOAT -> num2dbl v
RT_FIXNUM -> fromIntegral $ fix2int v
_ -> throw HubrisException
instance Rubyable Value where
toRuby v = v
instance Haskellable Value where
toHaskell v = v
instance Haskellable S.ByteString where
toHaskell v = when v RT_STRING $ unsafePerformIO $
do a <- str2cstr v >>= S.packCString
return a `traces` ("strict to Haskell: " ++ sshow a)
instance Rubyable S.ByteString where
toRuby s = unsafePerformIO $ S.useAsCStringLen s $
\(cs,len) -> rb_str_new (cs,len) `traces` ("sstrict back to ruby:" ++ (show $ S.unpack s))
instance Haskellable L.ByteString where
toHaskell v = L.fromChunks [toHaskell v]
instance Rubyable L.ByteString where
toRuby s = let res = S.concat $ L.toChunks s
in trace ("lazy back to ruby: " ++ show (S.unpack res)) (toRuby res)
instance Haskellable a => Haskellable [a] where
toHaskell v = when v RT_ARRAY $ Prelude.map toHaskell $ unsafePerformIO $ mapM (rb_ary_entry v . fromIntegral) [0..(rb_ary_len v) - 1]
instance Rubyable a => Rubyable [a] where
toRuby l = unsafePerformIO $ do ary <- rb_ary_new2 $ fromIntegral $ Prelude.length l
mapM_ (\x -> rb_ary_push ary (toRuby x)) l
return ary
-- this one is probably horribly inefficient.
instance (Integral a, Ix a, Haskellable b) => Haskellable (Array a b) where
toHaskell v = let x = toHaskell v in (listArray (0, fromIntegral $ Prelude.length x) x)
-- could be more efficient, perhaps, but it's space-efficient still thanks to laziness
instance (Rubyable b, Ix a) => Rubyable (Array a b) where
toRuby a = toRuby $ Data.Array.IArray.elems a
instance Haskellable RubyHash where
toHaskell v = when v RT_HASH $ RubyHash v
instance Rubyable RubyHash where
toRuby (RubyHash v) = v
-- Nil maps to Nothing - all the other falsey values map to real haskell values.
instance Haskellable a => Haskellable (Maybe a) where
toHaskell v = case rubyType v of
RT_NIL -> Nothing `traces` "Haskell got nothing"
_ -> Just (toHaskell v) `traces` "Haskell got a value"
instance Rubyable a => Rubyable (Maybe a) where
toRuby Nothing = constToRuby RUBY_Qnil `traces` "Sending ruby a nil"
toRuby (Just a) = toRuby a `traces` "Sending a value back"
newtype RubyHash = RubyHash Value -- don't export constructor
instance (Ord a, Eq a, Rubyable a, Rubyable b) => Rubyable (Map.Map a b) where
toRuby s = unsafePerformIO $
do hash <- rb_hash_new
mapM_ (\(k,v) -> rb_hash_aset hash (toRuby k) (toRuby v)) (toList s)
return hash
instance (Ord a, Eq a, Haskellable b, Haskellable a) => Haskellable (Map.Map a b) where
toHaskell hash = when hash RT_HASH $ unsafePerformIO $
-- fromJust is legit, rb_keys will always return list
do l :: [Value] <- toHaskell <$> rb_keys hash
foldM (\m k -> do val <- rb_hash_aref hash k
return $ Map.insert (toHaskell k)
(toHaskell val)
m)
Map.empty l
-- This is a tricky case.
-- The ruby FFI wants us to pass a C callback which it can apply to each key-value pair
-- of the hash, so Haskell cannot be fully in control of the process - this makes building
-- up a Data.Map object in the natural way a bit tricky.
-- current thoughts:
-- 1. write a direct binding to the ruby API, include a C level function for getting the keys.
-- just eat the cost of transferring through a keys call + looping over the elements.
-- One big benefit - while iteration is expensive, using it as a hash table should be cheap
-- (although probably needs to stay in the IO monad, which is less convenient.)
--
-- 2. write a binding to the Judy library that creates a Judy object directly. If we can convince
-- HsJudy to accept that, then we're golden - we still have to copy over, but keys operations
-- should be cheap (and hopefully lazy, but test to make sure).
--
-- These are of course not mutually exclusive.
--
-- The first should probably be a part of the base package. The second needs access to internals,
-- but should probably be an optional package. This means that in Hubris.Internals, we should expose
-- > rb_foreach :: Value {- HASH -} -> (CFunction ((Key,Value,a) -> a)) -> a -> IO a
--
--
-- instance Haskellable (Map.Map a b ) where
-- toHaskell (T_HASH s) = unsafePerformIO $
-- get_each
-- toHaskell _ = Nothing