rubiskell 0.1.0
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.
- data/README +19 -0
- data/example/fib.hs +4 -0
- data/example/fib.rb +7 -0
- data/example/plus.hs +2 -0
- data/example/plus.rb +7 -0
- data/example/sample.rb +12 -0
- data/lib/rubiskell.rb +114 -0
- data/spec/fib.hs +4 -0
- data/spec/spec_rubiskell.rb +61 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
! Rubiskell
|
2
|
+
|
3
|
+
!! What's this?
|
4
|
+
Rubiskell allows you to call haskell function from ruby script.
|
5
|
+
|
6
|
+
!! Example
|
7
|
+
require 'rubiskell'
|
8
|
+
|
9
|
+
FIB = <<EOD
|
10
|
+
fib :: Int -> Int
|
11
|
+
fib 0 = 0
|
12
|
+
fib 1 = 1
|
13
|
+
fib n = fib (n-2) + fib (n-1)
|
14
|
+
EOD
|
15
|
+
|
16
|
+
fib = Haskell.new(FIB)
|
17
|
+
puts "fib 5 is #{fib.run(5)}."
|
18
|
+
|
19
|
+
you can also write fib[5] instead of fib.run(5).
|
data/example/fib.hs
ADDED
data/example/fib.rb
ADDED
data/example/plus.hs
ADDED
data/example/plus.rb
ADDED
data/example/sample.rb
ADDED
data/lib/rubiskell.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
class Haskell
|
4
|
+
def self.load(fname)
|
5
|
+
new(File.read(fname))
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.tuple_form(args)
|
9
|
+
a = args.join(",")
|
10
|
+
return args.size == 1 ? a : "(#{a})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(code, option={})
|
14
|
+
@interpreter = option[:interpreter] || "runghc"
|
15
|
+
@signetures = parse_signetures(code)
|
16
|
+
@code = code
|
17
|
+
@k = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(sig, *args)
|
21
|
+
if String === sig
|
22
|
+
sig = @signetures.find{|sig| sig.name == sig}
|
23
|
+
raise ArgumentError, "unknown method" if sig.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
src = make_src(@code, sig)
|
27
|
+
path = make_tempfile(src)
|
28
|
+
cmd = make_cmd(path, args)
|
29
|
+
convert_result(`#{cmd}`.chomp, sig.ret)
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(*args)
|
33
|
+
call(@signetures[0], *args)
|
34
|
+
end
|
35
|
+
alias [] run
|
36
|
+
|
37
|
+
def make_cmd(path, args)
|
38
|
+
arg_list = Haskell.tuple_form(args.map{|x| x.inspect})
|
39
|
+
arg_list.gsub!(/"/, '\\"')
|
40
|
+
%Q<#{@interpreter} "#{path}" "#{arg_list}">
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_result(result, type)
|
44
|
+
case type.to_s
|
45
|
+
when "Integer"
|
46
|
+
result.to_i
|
47
|
+
when "String"
|
48
|
+
result[1..-2] #cut \"
|
49
|
+
else
|
50
|
+
raise "unknown type: #{type}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
TYPE_TABLE = [
|
55
|
+
["Int", Integer],
|
56
|
+
["String", String],
|
57
|
+
]
|
58
|
+
class Signeture
|
59
|
+
def initialize(name, args, ret)
|
60
|
+
@name, @args, @ret = name, args, ret
|
61
|
+
end
|
62
|
+
attr_reader :name, :args, :ret
|
63
|
+
|
64
|
+
def tuple_form
|
65
|
+
Haskell.tuple_form(@args.map{|ty|TYPE_TABLE.rassoc(ty)[0]})
|
66
|
+
end
|
67
|
+
|
68
|
+
def tuple_vars
|
69
|
+
"(" + make_vars().join(",") + ")"
|
70
|
+
end
|
71
|
+
|
72
|
+
def list_vars
|
73
|
+
make_vars().join(" ")
|
74
|
+
end
|
75
|
+
|
76
|
+
# arity must be <= 26 :P
|
77
|
+
def make_vars
|
78
|
+
("a".."z").to_a.slice(0, @args.size)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_signetures(code)
|
83
|
+
code.scan(/^(\w+)\s*::\s*(\w+(\s*->\s*\w+)*)\s*$/).map do |sig|
|
84
|
+
name = $1.strip
|
85
|
+
args = $2.split(/->/).map{|x|
|
86
|
+
pair = TYPE_TABLE.assoc(x.strip)
|
87
|
+
pair ? pair[1] : (raise ArgumentError, "unknown Haskell type #{x.strip}")
|
88
|
+
}
|
89
|
+
ret = args.pop
|
90
|
+
Signeture.new(name, args, ret)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def make_src(code, sig)
|
95
|
+
src = "import System\n"
|
96
|
+
src << code
|
97
|
+
src << "\n"
|
98
|
+
src << "main = do args <- getArgs\n"
|
99
|
+
src << " print $ apply #{sig.name} $ _read $ head args\n"
|
100
|
+
src << " where\n"
|
101
|
+
src << " _read :: String -> #{sig.tuple_form}\n"
|
102
|
+
src << " _read = read\n"
|
103
|
+
src << " apply f #{sig.tuple_vars} = f #{sig.list_vars}\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
def make_tempfile(src)
|
107
|
+
while File.exist?(fname = "rubiskell.#{Process.pid}.#{@k}.hs")
|
108
|
+
@k += 1
|
109
|
+
end
|
110
|
+
open(fname, "w"){|f| f.write src }
|
111
|
+
at_exit{ File.unlink(fname) }
|
112
|
+
fname
|
113
|
+
end
|
114
|
+
end
|
data/spec/fib.hs
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#
|
2
|
+
# spec_rubiskell.rb - Unit test with RSpec
|
3
|
+
#
|
4
|
+
$LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
|
5
|
+
require 'rubiskell'
|
6
|
+
|
7
|
+
FIB = <<EOD
|
8
|
+
fib :: Int -> Int
|
9
|
+
fib 0 = 0
|
10
|
+
fib 1 = 1
|
11
|
+
fib n = fib (n-2) + fib (n-1)
|
12
|
+
EOD
|
13
|
+
|
14
|
+
MANY_ARGS = <<EOD
|
15
|
+
foo :: String -> Int -> String -> String
|
16
|
+
foo s1 n s2 = s1 ++ (show n) ++ s2
|
17
|
+
EOD
|
18
|
+
|
19
|
+
describe "parse_signetures" do
|
20
|
+
before(:each) do
|
21
|
+
@hs = Haskell.new("")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should parse Int -> Int" do
|
25
|
+
sigs = @hs.parse_signetures(FIB)
|
26
|
+
sigs.should have(1).items
|
27
|
+
|
28
|
+
sig = sigs.first
|
29
|
+
sig.name.should == "fib"
|
30
|
+
sig.args.should == [Integer]
|
31
|
+
sig.ret.should == Integer
|
32
|
+
sig.tuple_form.should == "Int"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should parse many args" do
|
36
|
+
sigs = @hs.parse_signetures("foo :: String->Int->String -> Int")
|
37
|
+
sig = sigs.first
|
38
|
+
sig.name.should == "foo"
|
39
|
+
sig.args.should == [String, Integer, String]
|
40
|
+
sig.ret.should == Integer
|
41
|
+
sig.tuple_form.should == "(String,Int,String)"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Rubiskell" do
|
46
|
+
it "should run fib" do
|
47
|
+
fib = Haskell.new(FIB)
|
48
|
+
fib[2].should == 1
|
49
|
+
fib[3].should == 2
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should pass many args" do
|
53
|
+
many = Haskell.new(MANY_ARGS)
|
54
|
+
many["1", 2, "3"].should == "123"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should run file" do
|
58
|
+
fib = Haskell.load(File.expand_path("fib.hs", File.dirname(__FILE__)))
|
59
|
+
fib[2].should == 1
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubiskell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yhara (Yutaka HARA)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-07-07 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: yhara.at.kmc.gr.jp
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- lib/rubiskell.rb
|
27
|
+
- example/fib.hs
|
28
|
+
- example/fib.rb
|
29
|
+
- example/plus.hs
|
30
|
+
- example/plus.rb
|
31
|
+
- example/sample.rb
|
32
|
+
- spec/fib.hs
|
33
|
+
- spec/spec_rubiskell.rb
|
34
|
+
has_rdoc: false
|
35
|
+
homepage:
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project: rubiskell
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: Run haskell snippet from ruby
|
60
|
+
test_files: []
|
61
|
+
|