hubris 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: