hubris 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +31 -0
- data/.rvmrc +2 -0
- data/Gemfile +11 -0
- data/Haskell/Hubrify.hs +69 -0
- data/Haskell/LICENSE +22 -0
- data/Haskell/Language/Ruby/Foo.hs +20 -0
- data/Haskell/Language/Ruby/Hubris/Binding.hsc +214 -0
- data/Haskell/Language/Ruby/Hubris/GHCBuild.hs +46 -0
- data/Haskell/Language/Ruby/Hubris/Hash.hs +27 -0
- data/Haskell/Language/Ruby/Hubris/Interpolator.hs +22 -0
- data/Haskell/Language/Ruby/Hubris/LibraryBuilder.hs +181 -0
- data/Haskell/Language/Ruby/Hubris/ZCode.hs +68 -0
- data/Haskell/Language/Ruby/Hubris.hs +254 -0
- data/Haskell/Language/Ruby/Wrappers.hs +32 -0
- data/Haskell/Language/Ruby/testLib.hs +9 -0
- data/Haskell/Setup.hs +31 -0
- data/Haskell/cbits/rshim.c +46 -0
- data/Haskell/cbits/rshim.h +50 -0
- data/Haskell/hubris.cabal +53 -0
- data/INSTALL +21 -0
- data/Manifest.txt +22 -0
- data/PostInstall.txt +1 -0
- data/README.markdown +107 -0
- data/Rakefile +46 -43
- data/VERSION +1 -0
- data/doc/CommonErrors.txt +18 -0
- data/doc/CommonErrors.txt~HEAD +18 -0
- data/doc/don_feedback.txt +25 -0
- data/doc/haskell-hubris.tex +242 -0
- data/doc/new_interface.rb +74 -0
- data/doc/ruby-hubris.tex +176 -0
- data/doc/wisdom_of_ancients.txt +55 -0
- data/ext/hubris.rb +4 -0
- data/ext/stub/extconf.rb +5 -0
- data/ext/{HubrisStubLoader.c → stub/stub.c} +1 -1
- data/hubris.gemspec +31 -0
- data/lib/Makefile +181 -0
- data/lib/hubris/version.rb +3 -0
- data/lib/hubris.rb +16 -13
- data/rspec.rake +21 -0
- data/sample/Fibonacci.hs +2 -2
- data/sample/config.ru +3 -1
- data/script/ci.sh +25 -0
- data/script/console +10 -0
- data/spec/hubris_spec.rb +173 -47
- data/tasks/extconf/stub.rake +43 -0
- data/tasks/extconf.rake +13 -0
- metadata +118 -27
- 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
data/Gemfile
ADDED
data/Haskell/Hubrify.hs
ADDED
@@ -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:
|