hubris 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +31 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +11 -0
  4. data/Haskell/Hubrify.hs +69 -0
  5. data/Haskell/LICENSE +22 -0
  6. data/Haskell/Language/Ruby/Foo.hs +20 -0
  7. data/Haskell/Language/Ruby/Hubris/Binding.hsc +214 -0
  8. data/Haskell/Language/Ruby/Hubris/GHCBuild.hs +46 -0
  9. data/Haskell/Language/Ruby/Hubris/Hash.hs +27 -0
  10. data/Haskell/Language/Ruby/Hubris/Interpolator.hs +22 -0
  11. data/Haskell/Language/Ruby/Hubris/LibraryBuilder.hs +181 -0
  12. data/Haskell/Language/Ruby/Hubris/ZCode.hs +68 -0
  13. data/Haskell/Language/Ruby/Hubris.hs +254 -0
  14. data/Haskell/Language/Ruby/Wrappers.hs +32 -0
  15. data/Haskell/Language/Ruby/testLib.hs +9 -0
  16. data/Haskell/Setup.hs +31 -0
  17. data/Haskell/cbits/rshim.c +46 -0
  18. data/Haskell/cbits/rshim.h +50 -0
  19. data/Haskell/hubris.cabal +53 -0
  20. data/INSTALL +21 -0
  21. data/Manifest.txt +22 -0
  22. data/PostInstall.txt +1 -0
  23. data/README.markdown +107 -0
  24. data/Rakefile +46 -43
  25. data/VERSION +1 -0
  26. data/doc/CommonErrors.txt +18 -0
  27. data/doc/CommonErrors.txt~HEAD +18 -0
  28. data/doc/don_feedback.txt +25 -0
  29. data/doc/haskell-hubris.tex +242 -0
  30. data/doc/new_interface.rb +74 -0
  31. data/doc/ruby-hubris.tex +176 -0
  32. data/doc/wisdom_of_ancients.txt +55 -0
  33. data/ext/hubris.rb +4 -0
  34. data/ext/stub/extconf.rb +5 -0
  35. data/ext/{HubrisStubLoader.c → stub/stub.c} +1 -1
  36. data/hubris.gemspec +31 -0
  37. data/lib/Makefile +181 -0
  38. data/lib/hubris/version.rb +3 -0
  39. data/lib/hubris.rb +16 -13
  40. data/rspec.rake +21 -0
  41. data/sample/Fibonacci.hs +2 -2
  42. data/sample/config.ru +3 -1
  43. data/script/ci.sh +25 -0
  44. data/script/console +10 -0
  45. data/spec/hubris_spec.rb +173 -47
  46. data/tasks/extconf/stub.rake +43 -0
  47. data/tasks/extconf.rake +13 -0
  48. metadata +118 -27
  49. data/ext/extconf.rb +0 -5
data/.gitignore ADDED
@@ -0,0 +1,31 @@
1
+ *.aux
2
+ *.dvi
3
+ *.gem
4
+ *.hi
5
+ *.log
6
+ *.nav
7
+ *.o
8
+ *.out
9
+ *.snm
10
+ *.toc
11
+ *.vrb
12
+ .#*
13
+ .bundle
14
+ .jhci-hist
15
+ RubyMap.hi
16
+ RubyMap.hs
17
+ RubyMap.o
18
+ Setup
19
+ dist
20
+ hs.out
21
+ hs.out_code.c
22
+ lib/RubyMap.chi
23
+ lib/RubyMap.chs.h
24
+ lib/RubyMap.hi
25
+ lib/RubyMap.hs
26
+ lib/RubyMap.o
27
+ lib/rshim.o
28
+ pkg/
29
+ pkg/*
30
+ sample/tmp
31
+ sample/tmp.old
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_install_on_use_flag=1
2
+ rvm 1.9.1
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+ # gem 'jeweler', '1.5.1'
3
+ # gem 'gemcutter'
4
+ # gem 'rspec', '1.3.0'
5
+ # gem 'rake'
6
+ # gem 'rake-compiler'
7
+ # gem 'open4'
8
+ # source "http://rubygems.org"
9
+
10
+ # Specify your gem's dependencies in Hubris.gemspec
11
+ gemspec
@@ -0,0 +1,69 @@
1
+ module Main where
2
+ import Language.Ruby.Hubris.LibraryBuilder
3
+ import System
4
+ import System.Exit
5
+ -- import Control.Monad (when)
6
+ import System.Console.GetOpt
7
+
8
+ data Options = Options
9
+ { optVerbose :: Bool
10
+ , optStrict :: Bool
11
+ , optShowVersion :: Bool
12
+ , optOutput :: FilePath
13
+ , optModule :: String
14
+ , optInput :: Maybe FilePath
15
+ , optPackages :: [String]
16
+ } deriving Show
17
+
18
+ defaultOptions :: Options
19
+ defaultOptions = Options
20
+ { optVerbose = False
21
+ , optShowVersion = False
22
+ , optOutput = error "output must be defined"
23
+ , optModule = error "module must be defined"
24
+ , optStrict = False
25
+ , optInput = Nothing
26
+ , optPackages = []
27
+ }
28
+
29
+ options :: [OptDescr (Options -> Options)]
30
+ options =
31
+ [ Option "v" ["verbose"]
32
+ (NoArg (\opts -> opts { optVerbose = True }))
33
+ "chatty output on stderr"
34
+ , Option [] ["strict"]
35
+ (NoArg (\opts -> opts { optStrict = True }))
36
+ "bondage and discipline mode"
37
+ , Option "o" ["output"]
38
+ (ReqArg (\f opts -> opts { optOutput = f }) "libFile")
39
+ "output FILE"
40
+ , Option "m" ["module"]
41
+ (ReqArg (\f opts -> opts { optModule = f }) "module")
42
+ "module to be wrapped"
43
+ , Option "p" ["package"]
44
+ (ReqArg (\d opts -> opts { optPackages = optPackages opts ++ [d] }) "DIR")
45
+ "package"
46
+ ]
47
+
48
+ hubrisOpts :: [String] -> IO (Options, [String])
49
+ hubrisOpts argv =
50
+ case getOpt Permute options argv of
51
+ (o, n, []) -> return (foldl (flip id) defaultOptions o, n)
52
+ (_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options))
53
+ where header = "Usage: Hubrify --module MODULE --output LIBFILE (--packages PACKAGE1 ...) sourceFiles ..."
54
+
55
+ main :: IO ()
56
+ main = do
57
+ (o, srcs) <- getArgs >>= hubrisOpts
58
+ -- HACK this may be the worst thing ever
59
+
60
+ let ghcArgs = if optStrict o
61
+ then ["-Wall", "-Werror", "-fno-warn-unused-imports"]
62
+ else []
63
+ -- putStrLn $ show $ optPackages o
64
+
65
+ res <- generateLib (optOutput o) srcs (optModule o) ("-fPIC":ghcArgs) (optPackages o)
66
+ -- print res
67
+ case res of
68
+ Left a -> putStrLn ("Failed: " ++ a) >> exitFailure
69
+ Right _ -> exitSuccess
data/Haskell/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009 Mark Wotton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ module Foo (wrap, testString, wrap2, external, can_be_called, cannot_be_called) where
2
+ can_be_called :: Int -> Int
3
+ can_be_called x = 2 * x
4
+
5
+ could_be_called :: Int -> Int
6
+ could_be_called x = 3 * x
7
+ wrap :: Int -> Int
8
+ wrap x = 3 - x
9
+
10
+ wrap2 :: Int -> Int
11
+ wrap2 x = 3 - x
12
+
13
+ cannot_be_called = map
14
+
15
+ internal = 1
16
+ external = 10
17
+
18
+
19
+ testString :: Int -> String
20
+ testString x = show x
@@ -0,0 +1,214 @@
1
+ {-# LANGUAGE ForeignFunctionInterface, CPP, TypeSynonymInstances #-}
2
+
3
+ {- TODO
4
+
5
+ Rip the array trnaslation stuff out to a utility function. same with hashes.
6
+
7
+ install as package. This is a bit iffy for GHC/JHC compatibility - if we commit to
8
+ Cabal, that leaves JHC out in the cold.
9
+
10
+ perhaps need cabal file for ghc and equivalent for jhc.
11
+
12
+ also: do we want to support different versions of ruby? for the moment, you just get
13
+ whichever ruby.h is first in the search place.
14
+
15
+ -}
16
+
17
+
18
+ module Language.Ruby.Hubris.Binding where
19
+
20
+ #include "rshim.h"
21
+ #include <ruby.h>
22
+
23
+ import Control.Applicative
24
+ -- import Control.Monad
25
+ import Data.Word
26
+ -- import Foreign.Ptr
27
+ import Foreign.C.Types
28
+ import Foreign.C.String
29
+ import System.IO.Unsafe (unsafePerformIO)
30
+ import Data.Maybe
31
+
32
+ rubyType :: Value -> RubyType
33
+ rubyType = toEnum . rtype
34
+
35
+ -- this is awful, but I don't want to pull in C2HS - it pulls in
36
+ -- alex and happy, and they're difficult to specify as dependencies
37
+ -- in cabal.
38
+
39
+ #{enum Int,,
40
+ T_NONE,
41
+ T_NIL,
42
+ T_OBJECT,
43
+ T_CLASS,
44
+ T_ICLASS,
45
+ T_MODULE ,
46
+ T_FLOAT ,
47
+ T_STRING ,
48
+ T_REGEXP ,
49
+ T_ARRAY ,
50
+ T_FIXNUM ,
51
+ T_HASH ,
52
+ T_STRUCT ,
53
+ T_BIGNUM ,
54
+ T_FILE ,
55
+
56
+ T_TRUE ,
57
+ T_FALSE ,
58
+ T_DATA ,
59
+ T_MATCH ,
60
+ T_SYMBOL ,
61
+
62
+ T_UNDEF ,
63
+ T_NODE ,
64
+ T_MASK }
65
+
66
+ data RubyType = RT_NONE
67
+ | RT_NIL
68
+ | RT_OBJECT
69
+ | RT_CLASS
70
+ | RT_ICLASS
71
+ | RT_MODULE
72
+ | RT_FLOAT
73
+ | RT_STRING
74
+ | RT_REGEXP
75
+ | RT_ARRAY
76
+ | RT_FIXNUM
77
+ | RT_HASH
78
+ | RT_STRUCT
79
+ | RT_BIGNUM
80
+ | RT_FILE
81
+
82
+ | RT_TRUE
83
+ | RT_FALSE
84
+ | RT_DATA
85
+ | RT_MATCH
86
+ | RT_SYMBOL
87
+
88
+ | RT_UNDEF
89
+ | RT_NODE
90
+ | RT_MASK
91
+ deriving (Eq, Show)
92
+
93
+ instance Enum RubyType where
94
+ fromEnum RT_NONE = tNone
95
+
96
+ fromEnum RT_NIL = tNil
97
+ fromEnum RT_OBJECT = tObject
98
+ fromEnum RT_CLASS = tClass
99
+ fromEnum RT_ICLASS = tIclass
100
+ fromEnum RT_MODULE = tModule
101
+ fromEnum RT_FLOAT = tFloat
102
+ fromEnum RT_STRING = tString
103
+ fromEnum RT_REGEXP = tRegexp
104
+ fromEnum RT_ARRAY = tArray
105
+ fromEnum RT_FIXNUM = tFixnum
106
+ fromEnum RT_HASH = tHash
107
+ fromEnum RT_STRUCT = tStruct
108
+ fromEnum RT_BIGNUM = tBignum
109
+ fromEnum RT_FILE = tFile
110
+
111
+ fromEnum RT_TRUE = tTrue
112
+ fromEnum RT_FALSE = tFalse
113
+ fromEnum RT_DATA = tData
114
+ fromEnum RT_MATCH = tMatch
115
+ fromEnum RT_SYMBOL = tSymbol
116
+ fromEnum RT_UNDEF = tUndef
117
+ fromEnum RT_NODE = tNode
118
+
119
+ fromEnum RT_MASK = tMask
120
+ -- this is unnecessarily slow. fix it later.
121
+ toEnum x = fromJust $ lookup x assoc
122
+ where assoc = [( tNone , RT_NONE)
123
+ ,( tNil , RT_NIL )
124
+ ,( tObject , RT_OBJECT )
125
+ ,( tClass , RT_CLASS )
126
+ ,( tIclass , RT_ICLASS )
127
+ ,( tModule , RT_MODULE )
128
+ ,( tFloat , RT_FLOAT )
129
+ ,( tString , RT_STRING )
130
+ ,( tRegexp , RT_REGEXP )
131
+ ,( tArray , RT_ARRAY )
132
+ ,( tFixnum , RT_FIXNUM )
133
+ ,( tHash , RT_HASH )
134
+ ,( tStruct , RT_STRUCT )
135
+ ,( tBignum , RT_BIGNUM )
136
+ ,( tFile , RT_FILE )
137
+
138
+ ,( tTrue , RT_TRUE )
139
+ ,( tFalse , RT_FALSE )
140
+ ,( tData , RT_DATA )
141
+ ,( tMatch , RT_MATCH )
142
+ ,( tSymbol , RT_SYMBOL )
143
+ ,( tUndef , RT_UNDEF )
144
+ ,( tNode , RT_NODE )
145
+
146
+ ,( tMask , RT_MASK )]
147
+
148
+
149
+
150
+
151
+ constToRuby :: RubyConst -> Value
152
+ constToRuby = fromIntegral . fromEnum
153
+ -- RUBY_VERSION_CODE <= 187
154
+ data RubyConst = RUBY_Qfalse
155
+ | RUBY_Qtrue
156
+ | RUBY_Qnil
157
+ | RUBY_Qundef
158
+
159
+ instance Enum RubyConst where
160
+ fromEnum RUBY_Qfalse = 0
161
+ fromEnum RUBY_Qtrue = 2
162
+ fromEnum RUBY_Qnil = 4
163
+ fromEnum RUBY_Qundef = 6
164
+
165
+ toEnum 0 = RUBY_Qfalse
166
+ toEnum 2 = RUBY_Qtrue
167
+ toEnum 4 = RUBY_Qnil
168
+ toEnum 6 = RUBY_Qundef
169
+ toEnum 12 = RUBY_Qnil
170
+ -- {# enum ruby_special_consts as RubyConst {} deriving (Eq,Show) #}
171
+
172
+ str2cstr str = rb_str2cstr str 0
173
+ type Value = CULong -- FIXME, we'd prefer to import the type VALUE directly
174
+ foreign import ccall safe "ruby.h rb_str2cstr" rb_str2cstr :: Value -> CInt -> IO CString
175
+ foreign import ccall safe "ruby.h rb_str_new2" rb_str_new2 :: CString -> IO Value
176
+ foreign import ccall safe "ruby.h rb_str_new2" rb_str_new_ :: CString -> Int -> IO Value
177
+ foreign import ccall safe "ruby.h rb_ary_new2" rb_ary_new2 :: CLong -> IO Value
178
+ foreign import ccall safe "ruby.h rb_ary_push" rb_ary_push :: Value -> Value -> IO ()
179
+ foreign import ccall safe "ruby.h rb_float_new" rb_float_new :: Double -> Value
180
+ foreign import ccall safe "ruby.h rb_big2str" rb_big2str :: Value -> Int -> IO Value
181
+ foreign import ccall safe "ruby.h rb_str_to_inum" rb_str_to_inum :: Value -> Int -> Int -> Value
182
+ -- foreign import ccall safe "ruby.h ruby_init" ruby_init :: IO ()
183
+
184
+ rb_str_new = uncurry rb_str_new_
185
+
186
+ -- we're being a bit filthy here - the interface is all macros, so we're digging in to find what it actually is
187
+ foreign import ccall safe "rshim.h rb_ary_len" rb_ary_len :: Value -> CUInt
188
+ foreign import ccall safe "rshim.h rtype" rtype :: Value -> Int
189
+
190
+ foreign import ccall safe "rshim.h int2fix" int2fix :: Int -> Value
191
+ foreign import ccall safe "rshim.h fix2int" fix2int :: Value -> Int
192
+ foreign import ccall safe "rshim.h num2dbl" num2dbl :: Value -> Double -- technically CDoubles, but jhc promises they're the same
193
+ foreign import ccall safe "rshim.h keys" rb_keys :: Value -> IO Value
194
+ foreign import ccall safe "rshim.h buildException" buildException :: CString -> IO Value
195
+ -- foreign import ccall safe "ruby.h rb_funcall" rb_funcall :: Value -> ID ->
196
+
197
+ -- this line crashes jhc
198
+ foreign import ccall safe "intern.h rb_ary_entry" rb_ary_entry :: Value -> CLong -> IO Value
199
+
200
+ foreign import ccall safe "ruby.h rb_raise" rb_raise :: Value -> CString -> IO ()
201
+ foreign import ccall safe "ruby.h rb_eval_string" rb_eval_string :: CString -> IO Value
202
+
203
+ foreign import ccall safe "intern.h rb_hash_aset" rb_hash_aset :: Value -> Value -> Value -> IO ()
204
+ foreign import ccall safe "intern.h rb_hash_new" rb_hash_new :: IO Value
205
+ foreign import ccall safe "intern.h rb_hash_aref" rb_hash_aref :: Value -> Value -> IO Value
206
+
207
+
208
+ createException :: String -> IO Value
209
+ createException s = newCAString s >>= buildException
210
+
211
+
212
+
213
+
214
+
@@ -0,0 +1,46 @@
1
+ module Language.Ruby.Hubris.GHCBuild (ghcBuild, defaultGHCOptions, GHCOptions(..), withTempFile) where
2
+ import Config
3
+ import Debug.Trace
4
+ import DynFlags
5
+ import GHC
6
+ import GHC.Paths
7
+ import Outputable
8
+ import StringBuffer
9
+ import System.Process
10
+ import Control.Monad(forM_,guard)
11
+ import System.IO(hPutStr, hClose, openTempFile)
12
+ import System( exitWith, system)
13
+ import System.Exit
14
+ import Language.Ruby.Hubris.Includes (extraIncludeDirs) -- this is generated by Cabal
15
+
16
+
17
+ newtype GHCOptions = GHCOptions { strict :: Bool }
18
+ defaultGHCOptions = GHCOptions { strict = True }
19
+ type Filename = String
20
+
21
+
22
+ standardGHCFlags = (words $ "--make -shared -dynamic -fPIC -optc-DHAVE_SNPRINTF -lHSrts-ghc" ++Config.cProjectVersion)
23
+ ++ map ("-I"++) extraIncludeDirs
24
+
25
+ withTempFile :: String -> String -> IO String
26
+ withTempFile pattern code = do (name, handle) <- openTempFile "/tmp" pattern
27
+ hPutStr handle code
28
+ hClose handle
29
+ return name
30
+
31
+ ghcBuild :: Filename -> String -> String -> [Filename] -> [Filename] -> [String]-> IO (Either String Filename)
32
+ ghcBuild libFile immediateSource modName extra_sources c_sources args =
33
+ do -- putStrLn ("modname is " ++ modName)
34
+ putStrLn immediateSource
35
+ haskellSrcFile <- withTempFile "hubris_XXXXX.hs" immediateSource
36
+ putStrLn ("ghc is " ++ ghc)
37
+ (code, out, err) <- noisySystem ghc $ standardGHCFlags ++ ["-o",libFile,"-optl-Wl,-rpath," ++ libdir,
38
+ haskellSrcFile, "-L" ++ libdir] ++ extra_sources ++ c_sources ++ args
39
+ return $ case code of
40
+ ExitSuccess -> Right libFile
41
+ otherCode -> Left $ unlines ["Errcode: " ++show code,"output: " ++ out, "error: " ++ err]
42
+
43
+ noisySystem :: String -> [String] -> IO (ExitCode, String,String)
44
+ noisySystem cmd args = (putStrLn . unwords) (cmd:args) >> readProcessWithExitCode cmd args ""
45
+
46
+
@@ -0,0 +1,27 @@
1
+ module Language.Ruby.Hubris.Hash where
2
+ import qualified Language.Ruby.Hubris.Binding as Ruby
3
+ import Prelude hiding(lookup)
4
+ import Control.Applicative
5
+
6
+ newtype RubyHash = RubyHash Ruby.Value
7
+
8
+ -- can only call these functions when we have a ruby interpreter
9
+ -- initialised. shouldn't usually be a problem, but needs to be
10
+ -- done when testing from haskell.
11
+
12
+ -- to test: does this break horribly when we have multiple threads?
13
+
14
+ new = RubyHash <$> Ruby.rb_hash_new
15
+ insert (RubyHash v) = Ruby.rb_hash_aset v
16
+
17
+ -- no Maybe here - we'd just need to test again later, as we're passing
18
+ -- a Ruby value
19
+ lookup (RubyHash v) key = Ruby.rb_hash_aref v key
20
+
21
+ -- maybe should extract strings?
22
+ keys :: RubyHash -> IO [Ruby.RValue]
23
+ keys (RubyHash v) = do Ruby.T_ARRAY res <- Ruby.fromVal <$> Ruby.rb_keys v
24
+ return res
25
+
26
+ toList rhash = keys rhash >>= mapM (\k -> lookup rhash (Ruby.fromRVal k) >>= \v -> return (k,v))
27
+
@@ -0,0 +1,22 @@
1
+ {-# LANGUAGE TemplateHaskell #-}
2
+
3
+ module Interpolator where
4
+ import Language.Haskell.TH
5
+ import Language.Haskell.TH.Syntax
6
+ import Language.Haskell.Meta.Parse
7
+
8
+ -- The first string in each pair is literal, the second is a variable to
9
+ -- be interpolated.
10
+ parse :: String -> [(String, String)]
11
+ -- parse "Foo#{foo}rah#{foo}" = [("Foo", "foo ++ bar"), ("rah", "foo")]
12
+ parse str =
13
+ gen :: [(String, String)] -> Q Exp -> Q Exp
14
+ gen [] x = x
15
+ -- gen ((string,variable) : xs) x = gen xs [| $x ++ $(lift string) ++ $(return $ VarE $ mkName variable) |]
16
+ gen ((string,expr) : xs) x = gen xs [| $x ++ $(lift string) ++ $(return $ lift $ parseExp expr) |]
17
+ -- gen ((string,variable) : xs) x = gen xs [| $x ++ $(lift string) ++ $(stringE variable) |]
18
+
19
+ -- Here we generate the Haskell code for the splice
20
+ -- from an input format string.
21
+ interpolate :: String -> Q Exp
22
+ interpolate s = gen (parse s) [| "" |]
@@ -0,0 +1,181 @@
1
+
2
+ {-# LANGUAGE TemplateHaskell, QuasiQuotes, ScopedTypeVariables #-}
3
+ module Language.Ruby.Hubris.LibraryBuilder where
4
+ import Language.Ruby.Hubris
5
+ import Language.Haskell.Interpreter
6
+ -- import Language.Haskell.Meta.QQ.HsHere
7
+ import Language.Ruby.Hubris.GHCBuild
8
+
9
+ import List(intersperse)
10
+ import Data.List(intercalate)
11
+ import qualified Debug.Trace
12
+ import Control.Applicative
13
+ import Control.Monad
14
+ import Control.Monad.Error.Class
15
+ import Data.Maybe(catMaybes,fromJust, isJust)
16
+
17
+ import GHC(parseStaticFlags, noLoc)
18
+ import System.IO(hPutStr, hClose, openTempFile)
19
+ import System.Exit
20
+ import Language.Ruby.Hubris.ZCode (zenc,Zname(..))
21
+
22
+ type Filename = String
23
+ dotrace a b = b
24
+
25
+ -- weirdly, mapMaybeM doesn't exist.
26
+ mapMaybeM :: (Functor m, Monad m) => (a -> m (Maybe b)) -> [a] -> m [b]
27
+ mapMaybeM func ls = catMaybes <$> (sequence $ map func ls)
28
+
29
+ generateLib :: Filename -> [Filename] -> ModuleName -> [String] -> [String] -> IO (Either Filename String)
30
+ generateLib libFile sources moduleName buildArgs packages = do
31
+ -- set up the static args once
32
+ GHC.parseStaticFlags $ map noLoc $ map ("-package "++) ("hubris":packages)
33
+
34
+ s <- generateSource sources moduleName
35
+ case s of
36
+ Right (c,mod) -> do bindings <- withTempFile "hubris_interface_XXXXX.c" c
37
+ ghcBuild libFile mod ("Language.Ruby.Hubris.Exports." ++ moduleName) sources [bindings] buildArgs
38
+ Left x -> return . Left $ show x
39
+
40
+ type Funcname = String
41
+ type Wrapper = String
42
+
43
+
44
+ callable ::String -> InterpreterT IO (Maybe Int)
45
+ callable func = do
46
+ ok <- typeChecks str
47
+ if not ok
48
+ then return Nothing
49
+ else do res <- interpret str (as::Int)
50
+ return $ Just res
51
+ where str = "Language.Ruby.Hubris.arity " ++ parens func
52
+
53
+
54
+ -- ok, let's see if we can come up with an expression of the right type
55
+ exportable :: String -> String -> InterpreterT IO (Maybe (Funcname, Int, Wrapper))
56
+ exportable moduleName func = do say $ "checking " ++ qualName
57
+ -- here's the problem - i want callable to return Maybe, not bomb
58
+ -- all the way back out to the outer runInterpreter
59
+ match <- callable qualName
60
+ case match of
61
+ Nothing -> return Nothing
62
+ Just i -> do
63
+ let wrapped = genApp qualName i
64
+ let eqn = wrapped ++ " == " ++ haskellVal
65
+ say ("to check: " ++ eqn)
66
+ checked <- typeChecks eqn
67
+ say ("Succeeded? " ++ show checked)
68
+ return $ guard checked>> return (func, i, genWrapper (func,i) moduleName)
69
+
70
+ where qualName = moduleName ++ "." ++ func
71
+ rubyVal = "(fromIntegral $ fromEnum $ Language.Ruby.Hubris.Binding.RUBY_Qtrue)"
72
+ haskellVal = "(Language.Ruby.Hubris.toHaskell " ++ rubyVal ++ ")"
73
+ genApp qualName i = unwords (qualName:(take i $ repeat haskellVal))
74
+
75
+ generateSource :: [Filename] -> -- optional haskell source to load into the interpreter
76
+ ModuleName -> -- name of the module to build a wrapper for
77
+ IO (Either InterpreterError (String,String))
78
+ generateSource sources moduleName = runInterpreter $ do
79
+ loadModules sources
80
+ setImportsQ $ [(mod,Just mod) | mod <- ["Language.Ruby.Hubris","Language.Ruby.Hubris.Binding", moduleName]]
81
+ funcs <- getFunctions moduleName
82
+ say ("Candidates: " ++ show funcs)
83
+ mapM (exportable moduleName) funcs >>= \x -> say (show x)
84
+ exports :: [(Funcname, Int, Wrapper)] <- mapMaybeM (exportable moduleName) funcs
85
+ say ("Exportable: " ++ show exports)
86
+ -- return (undefined, undefined)
87
+ return (genC [(a,b) | (a,b,_) <- exports] (zenc moduleName),
88
+ unlines (haskellBoilerplate moduleName:[wrapper | (_,_,wrapper) <- exports]))
89
+
90
+ getFunctions moduleName = (\ x -> [a |Fun a <- x]) <$> getModuleExports moduleName
91
+
92
+
93
+ genC :: [(String,Int)] -> Zname -> String
94
+ genC exports (Zname zmoduleName) = unlines $
95
+ ["#include <stdio.h>"
96
+ ,"#include <stdlib.h>"
97
+ ,"#define HAVE_STRUCT_TIMESPEC 1"
98
+ ,"#include <ruby.h>"
99
+ -- ,"#define DEBUG 1"
100
+ ,"#ifdef DEBUG"
101
+ ,"#define eprintf printf"
102
+ ,"#else"
103
+ ,"int eprintf(const char *f, ...){}"
104
+ ,"#endif"
105
+ ] ++
106
+ -- map (("VALUE hubrish_"++) . (++"(VALUE);")) exports ++
107
+ -- map (("VALUE hubrish_"++) . (++"(VALUE);")) exports ++
108
+ map cWrapper exports ++
109
+ ["extern void safe_hs_init();"
110
+ ,"extern VALUE Exports;"
111
+ ,"void Init_" ++ zmoduleName ++ "(){"
112
+ ," eprintf(\"loading " ++ zmoduleName ++ "\\n\");"
113
+ ," VALUE Fake = Qnil;"
114
+ ," safe_hs_init();"
115
+ ," Fake = rb_define_module_under(Exports, \"" ++ zmoduleName ++ "\");"
116
+ ] ++ map cDef exports ++ ["}"]
117
+ where
118
+ cWrapper :: (String,Int) -> String
119
+ cWrapper (f,arity) =
120
+ let res = unlines
121
+ ["VALUE hubrish_" ++ f ++ "("++ (concat . intersperse "," . take arity $ repeat "VALUE") ++ ");",
122
+ "VALUE " ++ f ++ "(VALUE mod, VALUE v){"
123
+ ," eprintf(\""++f++" has been called\\n\");"
124
+ -- also needs to curry on the ruby side
125
+
126
+ -- v is actually an array now, so we need to stash each element in
127
+ -- a nested haskell tuple. for the moment, let's just take the first one.
128
+
129
+ ," unsigned long res = hubrish_" ++ f ++ "(" ++ intercalate "," ["rb_ary_entry(v," ++ show i ++ ")"| i<- [0..(arity-1)]] ++ ");"
130
+ ," eprintf(\"hubrish "++f++" has been called\\n\");"
131
+ ," eprintf(\"result is %p\\n\",res);"
132
+ -- ," res = res | 0x100000000;"
133
+ ," if (rb_obj_is_kind_of(res,rb_eException)) {"
134
+ ," eprintf(\""++f++" has provoked an exception\\n\");"
135
+ ," rb_exc_raise(res);"
136
+ ," } else {"
137
+ ," eprintf(\"returning from "++f++"\\n\");"
138
+ ," return res;"
139
+ ," }"
140
+ ,"}"]
141
+ in res
142
+
143
+ cDef :: (String,Int) -> String
144
+ -- adef f = " eprintf(\"Defining |" ++ f ++ "|\\n\");\n" ++ "rb_define_method(Fake, \"" ++ f ++"\","++ f++", 1);"
145
+ cDef (f,_arity) = " eprintf(\"Defining |" ++ f ++ "|\\n\");\n" ++ "rb_define_method(Fake, \"" ++ f ++"\","++ f++", -2);"
146
+
147
+ haskellBoilerplate moduleName = unlines ["{-# LANGUAGE ForeignFunctionInterface, ScopedTypeVariables #-}",
148
+ "module Language.Ruby.Hubris.Exports." ++ moduleName ++ " where",
149
+ "import Language.Ruby.Hubris",
150
+ "import Language.Ruby.Hubris.Binding",
151
+ "import System.IO.Unsafe (unsafePerformIO)",
152
+ "import Control.Monad",
153
+ "import Control.Exception",
154
+ "import Data.Either",
155
+ "import Data.Function(($))",
156
+ "import qualified Prelude as P(show,putStrLn)",
157
+ "import Data.Tuple (uncurry)",
158
+ "import Foreign.C.Types",
159
+ "import qualified " ++ moduleName]
160
+
161
+
162
+
163
+ -- wrapper = func ++ " b = (Language.Ruby.Hubris.wrap " ++ moduleName ++ "." ++ func ++ ") b",
164
+ genWrapper (func,arity) mod = unlines $ [func ++ " :: " ++ myType
165
+ ,func ++ " " ++ unwords symbolArgs ++ " = " ++ defHask
166
+ ,"foreign export ccall \"hubrish_" ++ func ++ "\" " ++ func ++ " :: " ++ myType]
167
+ where myType = intercalate "->" (take (1+arity) $ repeat " CULong ")
168
+ -- mark's patented gensyms. just awful.
169
+ symbolArgs = take arity $ map ( \ x -> "fake_arg_symbol_"++[x]) ['a' .. 'z']
170
+ defHask = "unsafePerformIO $ do\n r <- try $ evaluate $ toRuby $" ++ mod ++"."++ func ++ " " ++ unwords (map (\ x -> "(toHaskell " ++ x ++ ")") symbolArgs) ++ "\n case r of\n" ++
171
+ -- unlines [" Left (e::SomeException) -> createException (P.show e) `traces` (\"died in haskell wrapper\" P.++ P.show e) ",
172
+ unlines [" Left (e::SomeException) -> createException (P.show e)" ,
173
+ " Right a -> return a"]
174
+
175
+ say :: String -> InterpreterT IO ()
176
+ -- say = liftIO . putStrLn
177
+ say _ = return ()
178
+
179
+ -- Local Variables:
180
+ -- compile-command: "cd ../../../; ./Setup build"
181
+ -- End: