rri 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.document +3 -0
  2. data/.gemtest +0 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/ChangeLog.md +4 -0
  6. data/Gemfile +16 -0
  7. data/Guardfile +6 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +131 -0
  10. data/Rakefile +66 -0
  11. data/examples/create_pdf.rb +18 -0
  12. data/examples/ggplot.rb +42 -0
  13. data/examples/simple.rb +19 -0
  14. data/gemspec.yml +11 -0
  15. data/lib/rri.rb +11 -0
  16. data/lib/rri/callback_objects.rb +1 -0
  17. data/lib/rri/callback_objects/r_engine_std_output.rb +6 -0
  18. data/lib/rri/engine.rb +390 -0
  19. data/lib/rri/java_gd.rb +23 -0
  20. data/lib/rri/jri.rb +24 -0
  21. data/lib/rri/r_converters.rb +4 -0
  22. data/lib/rri/r_converters/array_converter.rb +59 -0
  23. data/lib/rri/r_converters/float_converter.rb +28 -0
  24. data/lib/rri/r_converters/integer_converter.rb +28 -0
  25. data/lib/rri/r_converters/string_converter.rb +28 -0
  26. data/lib/rri/rexp.rb +8 -0
  27. data/lib/rri/rri_exception.rb +5 -0
  28. data/lib/rri/ruby_converters.rb +2 -0
  29. data/lib/rri/ruby_converters/double_converter.rb +28 -0
  30. data/lib/rri/ruby_converters/integer_converter.rb +28 -0
  31. data/lib/rri/version.rb +5 -0
  32. data/rri.gemspec +15 -0
  33. data/spec/rri/engine_spec.rb +203 -0
  34. data/spec/rri/r_converters/array_converter_spec.rb +56 -0
  35. data/spec/rri/r_converters/float_converter_spec.rb +19 -0
  36. data/spec/rri/r_converters/integer_converter_spec.rb +19 -0
  37. data/spec/rri/r_converters/string_converter_spec.rb +19 -0
  38. data/spec/rri/ruby_converters/double_converter.rb +20 -0
  39. data/spec/rri/ruby_converters/integer_converter_spec.rb +20 -0
  40. data/spec/rri_spec.rb +8 -0
  41. data/spec/spec_helper.rb +4 -0
  42. metadata +126 -0
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.*
3
+ LICENSE.txt
File without changes
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --markup markdown --title "rri Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2011-03-27
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source :rubygems
2
+
3
+ #gemspec
4
+
5
+ group :development do
6
+ gem 'rake', '~> 0.8.7'
7
+ gem 'ore-tasks', '~> 0.4'
8
+ gem 'rspec', '~> 2.5'
9
+ gem 'kramdown'
10
+ gem 'yard', '~> 0.6.0'
11
+ gem 'yardstick'
12
+ gem 'awesome_print'
13
+ gem 'guard', '~> 0.3.0'
14
+ gem 'guard-rspec', '~> 0.2.0'
15
+ gem 'rcov', '~> 0.9.9'
16
+ end
@@ -0,0 +1,6 @@
1
+ # rspec
2
+ guard 'rspec' do
3
+ watch(%r{^spec/.+_spec\.rb})
4
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Patrik Sundberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,131 @@
1
+ # rri
2
+
3
+ * [Homepage](https://github.com/sundbp/rri)
4
+ * [Documentation](http://rubydoc.info/gems/rri/frames)
5
+
6
+ ## Description
7
+
8
+ This gem provides an interface from ruby to R by using [JRI](http://www.rforge.net/JRI/)
9
+ (the Java/R Interface) from JRuby. JRI is a well established way of interfacing with R from Java
10
+ and hence a great building block to enable JRuby to interface with R.
11
+
12
+ This gem also enables the use of the R package [JavaGD](http://www.rforge.net/JavaGD/). JavaGD is
13
+ a R graphics device used to output graphics to a Java class. This makes it possible to easily
14
+ integrate R graphics with java/JVM GUI applications (for example it is used by
15
+ [JGR](http://www.rforge.net/JGR/)). JavaGD allows you to provide a custom class used for painting
16
+ which could well be implemented in jruby. Read the JavaGD package documentation for more information.
17
+
18
+ At the moment this gem is mostly aimed towards using R from ruby, but there's nothing stopping us
19
+ from taking advantage of [rJava](http://www.rforge.net/rJava/) to also allow R to call into jruby.
20
+
21
+ ## Features
22
+
23
+ * Call R functions from ruby
24
+ * An extendible system for converting between R and ruby data types (in both directions)
25
+ * Plot R graphics using user supplied class
26
+
27
+ ## Examples
28
+
29
+ require 'rri'
30
+
31
+ engine = Rri::Engine.new
32
+
33
+ # high level API, converts results and arguments from and to R/ruby
34
+ result = engine.eval_and_convert("1.23 + 4.56") # result will be a noromal ruby Float (5.79)
35
+ engine.convert_and_assign(result, :x) # x = 5.79 in R
36
+ result = engine.get_and_convert(:x) # result is a ruby Float
37
+
38
+ # helpers for the low level API, useful when you're not bothered
39
+ # about converting any values to/from R/ruby
40
+ result = engine.simple_eval("x + 1") # returns a reference to R object
41
+ engine.simple_assign("y", result) # y = 6.79 in R, assumes the value is an R object,
42
+ # no conversion takes place.
43
+ result = engine.simple_get(:y) # returns a reference to R object
44
+
45
+ # we should always close an engine, if we don't the R thread
46
+ # seem to hang around forever causing the ruby program to not
47
+ # exit. There is however also a finalizer defined that should
48
+ # automatically take care of this.
49
+ #
50
+ # use normal JRIEngine API via method missing forwarding
51
+ engine.close
52
+
53
+ Please also see the examples in the 'examples/' directory for more interesting use cases.
54
+
55
+ ## Requirements
56
+
57
+ * An installation of [R](http://www.r-project.org/)
58
+ * The R packages **rJava** and **JavaGD**
59
+
60
+ ## Install
61
+
62
+ First we need the rri gem:
63
+
64
+ $ gem install rri
65
+
66
+ Next we switch to R to install the required R packages:
67
+
68
+ > install.packages(c("rJava", "JavaGD"))
69
+
70
+ After this we need to make sure rri is able to find the needed jar-files. rri will attempt
71
+ to read the jars in the path specified by the environment variables:
72
+
73
+ * **RRI_JRI_JAR_PATH**
74
+ * **RRI_JAVAGD_JAR_PATH**
75
+
76
+ Set the first environment variable to the path given by the following R command:
77
+
78
+ > system.file("jri",package="rJava")
79
+
80
+ and the second one to the path given by:
81
+
82
+ > system.file("java", package="JavaGD")
83
+
84
+ Make sure the OS can load dynamic libraries from the R directory. On Windows that means
85
+ making it part of the path. On my machine it means adding "C:\Program Files\R\R-2.12.1\bin\i386"
86
+ to the **PATH**.
87
+
88
+ The last thing we need to make sure of is that the shared library distributed with JRI is
89
+ accessible by the operating system. I have not gone over this exercise on unix type systems
90
+ yet but on Windows this means that the path set in **RRI_JRI_JAR_PATH** is also part of the
91
+ **PATH**.
92
+
93
+ I tried to add this to the path within the rri gem itself before loading the JRI jars
94
+ but it seems the jruby class loader fails to pick it up so it needs to be part of the **PATH**
95
+ before a program using rri is launched. On linux like systems I'd expect there may be a need
96
+ for something similar in relation to **LD_LIBRARY_PATH** but I haven't had a chance to test it yet.
97
+
98
+ ## Type conversions
99
+
100
+ R and ruby have quite different type systems, but at least for the most common types of
101
+ data we can implement some natural conversions (in both directions).
102
+
103
+ Type conversion in both directions are tried in 3 levels:
104
+ * first custom specified (user) converters for a given engine are tried
105
+ * then custom specified (user) converters applicable to all engines are tried
106
+ * and finally a set of default converters specified by rri itself are applied
107
+
108
+ For custom converters the order in which they are tried is the reverse order
109
+ in which they were added, so the latest added converter is tried first.
110
+
111
+ To write your own custom converters please take a look at the default ones
112
+ in rri/r_converters and rri/ruby_converters. For help with the R type system
113
+ the REXP [JavaDocs](http://www.rforge.net/org/docs/org/rosuda/REngine/REXP.html)
114
+ are a good place to start.
115
+
116
+ **TODO**: describe the default converters, for now best docs are the specs
117
+
118
+ ## Note about instantiating engines many times in the same process
119
+
120
+ There seem to be issues in the C layer of either JRI or the R library when
121
+ you create an engine many times in the same process. That applies also if
122
+ you never have more than one engine created at any one time.
123
+
124
+ Emperically I get an infinite hang in the C layer if I re-create an engine
125
+ more than ~12 times in the same process.
126
+
127
+ ## Copyright
128
+
129
+ Copyright (c) 2011 Patrik Sundberg
130
+
131
+ See {file:LICENSE.txt} for details.
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError => e
6
+ STDERR.puts e.message
7
+ STDERR.puts "Run `gem install bundler` to install Bundler."
8
+ exit e.status_code
9
+ end
10
+
11
+ begin
12
+ Bundler.setup(:development)
13
+ rescue Bundler::BundlerError => e
14
+ STDERR.puts e.message
15
+ STDERR.puts "Run `bundle install` to install missing gems."
16
+ exit e.status_code
17
+ end
18
+
19
+ require 'rake'
20
+
21
+ require 'ore/tasks'
22
+ Ore::Tasks.new
23
+
24
+ require 'rspec/core/rake_task'
25
+ RSpec::Core::RakeTask.new
26
+
27
+ task :test => :spec
28
+ task :default => :spec
29
+
30
+ require 'yard'
31
+ YARD::Rake::YardocTask.new
32
+ task :doc => :yard
33
+
34
+ require 'yardstick/rake/measurement'
35
+ Yardstick::Rake::Measurement.new(:yardstick_measure) do |measurement|
36
+ measurement.output = 'measurement/report.txt'
37
+ end
38
+
39
+ require 'yardstick/rake/verify'
40
+ Yardstick::Rake::Verify.new do |verify|
41
+ verify.threshold = 80
42
+ end
43
+
44
+ desc "Run specs with rcov"
45
+ RSpec::Core::RakeTask.new("spec:rcov") do |t|
46
+ t.rcov = true
47
+ t.rcov_opts = %w{--exclude "spec\/,jsignal_internal"}
48
+ end
49
+
50
+ # for rcov threshold testing
51
+ rcov_coverage_threshold = 90
52
+ require_exact_rcov_threshold = false
53
+
54
+ desc "Verify that rcov coverage is at least #{rcov_coverage_threshold}%"
55
+ task :verify_rcov => "spec:rcov" do
56
+ total_coverage = 0
57
+ File.open("coverage/index.html").each_line do |line|
58
+ if line =~ /<tt class='coverage_total'>\s*(\d+\.\d+)%\s*<\/tt>/
59
+ total_coverage = $1.to_f
60
+ break
61
+ end
62
+ end
63
+ puts "Coverage: #{total_coverage}% (threshold: #{rcov_coverage_threshold}%)"
64
+ raise "Coverage must be at least #{rcov_coverage_threshold}% but was #{total_coverage}%" if total_coverage < rcov_coverage_threshold
65
+ raise "Coverage has increased above the threshold of #{rcov_coverage_threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_rcov_threshold
66
+ end
@@ -0,0 +1,18 @@
1
+ # example of producing a pdf from an R plot
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
4
+ require 'rri'
5
+
6
+ engine = Rri::Engine.new
7
+
8
+ filename = "plot_from_r.pdf"
9
+ begin
10
+ engine.simple_eval("data(iris)")
11
+ engine.simple_eval("pdf('#{filename}')")
12
+ engine.simple_eval("print(stripchart(iris[, 1:4], method = 'stack', pch = 16, cex = 0.4, offset = 0.6))")
13
+ engine.simple_eval("dev.off()")
14
+ rescue => e
15
+ puts "Caught exception: " + e
16
+ end
17
+
18
+ puts "Checkout plot generated in file: #{filename}"
@@ -0,0 +1,42 @@
1
+ # example of drawing a density with ggplot2
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
4
+ require 'rri'
5
+
6
+ # Ruby Gaussian Random Number Generator
7
+ # Author: Glenn
8
+ # http://webhost101.net/rails/typo/articles/2007/07/31/ruby-gaussian-random-number-generator
9
+
10
+ def gaussian_rand
11
+ u1 = u2 = w = g1 = g2 = 0 # declare
12
+ begin
13
+ u1 = 2 * rand - 1
14
+ u2 = 2 * rand - 1
15
+ w = u1 * u1 + u2 * u2
16
+ end while w >= 1
17
+
18
+ w = Math::sqrt( ( -2 * Math::log(w)) / w )
19
+ g2 = u1 * w;
20
+ g1 = u2 * w;
21
+ # g1 is returned
22
+ end
23
+
24
+ random_numbers = []
25
+ 1000.times { random_numbers << gaussian_rand }
26
+
27
+ # use the std out callback object to get some printouts from R, good for debugging.
28
+ engine = Rri::Engine.new(:callback_object => Rri::CallbackObjects::REngineStdOutput.new)
29
+
30
+ filename = "ggplot.pdf"
31
+ begin
32
+ engine.simple_eval("library(ggplot2)")
33
+ engine.convert_and_assign(random_numbers, :y)
34
+ engine.simple_eval("df <- data.frame(y)")
35
+ engine.simple_eval("pdf('#{filename}')")
36
+ engine.simple_eval("print(qplot(y, data=df, geom='histogram', binwidth=0.1))")
37
+ engine.simple_eval("dev.off()")
38
+ rescue => e
39
+ puts "Caught exception: " + e
40
+ end
41
+
42
+ puts "Checkout plot generated in file: #{filename}"
@@ -0,0 +1,19 @@
1
+ # example of just setting up an engine, adding two numbers in R and retrieving the result
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
4
+ require 'rri'
5
+
6
+ engine = Rri::Engine.new
7
+
8
+ # low level interface
9
+ result = engine.simple_eval("1.23 + 4.56")
10
+ puts "Result is: #{result} (which is of type #{result.class})"
11
+ engine.simple_assign(:x, result)
12
+
13
+ # higher level interface including conversions
14
+ result = engine.eval_and_convert("y <- x + 1")
15
+ result = engine.get_and_convert(:y)
16
+ puts "Result is: #{result} (which is of type #{result.class})"
17
+
18
+ # close the engine
19
+ engine.close
@@ -0,0 +1,11 @@
1
+ name: rri
2
+ summary: "Ruby/R Interface for JRuby"
3
+ description: "Ruby/R Interface - A wrapper of jri (Java/R Interface) for JRuby"
4
+ license: MIT
5
+ authors: Patrik Sundberg
6
+ homepage: http://rubygems.org/gems/rri
7
+ has_yard: true
8
+
9
+ development_dependencies:
10
+ bundler: ~> 1.0.0
11
+ yard: ~> 0.6.0
@@ -0,0 +1,11 @@
1
+ require 'rri/version'
2
+
3
+ require 'rri/rri_exception'
4
+
5
+ require 'rri/jri'
6
+ require 'rri/java_gd'
7
+ require 'rri/rexp'
8
+
9
+ require 'rri/engine'
10
+
11
+ require 'rri/callback_objects'
@@ -0,0 +1 @@
1
+ require 'rri/callback_objects/r_engine_std_output'
@@ -0,0 +1,6 @@
1
+
2
+ module Rri
3
+ module CallbackObjects
4
+ java_import "org.rosuda.REngine.REngineStdOutput"
5
+ end
6
+ end
@@ -0,0 +1,390 @@
1
+ require 'rri/jri'
2
+ require 'rri/r_converters'
3
+ require 'rri/ruby_converters'
4
+
5
+ module Rri
6
+
7
+ # The main class to interface with R
8
+ #
9
+ # Engine internally holds an instance of the java class JRIEngine
10
+ # which is used for all interaction with R. On top of this it provides
11
+ # a few abstractions to easily convert between ruby and R types.
12
+ #
13
+ # The main methods of the high level API are {#eval_and_convert} and {#convert_and_assign}.
14
+ # The high level API converts to/from R/ruby using a type conversion system described
15
+ # in the README.
16
+ #
17
+ # It also provides a couple of helper methods just to make the users life easier when
18
+ # working with the lower level java API, most notably {#simple_eval} and {#simple_assign}.
19
+ # These functions do not convert values but instead work with references to R objects
20
+ # and are preferred when you don't really care about converting to/from ruby (e.g.
21
+ # intermediate steps of calculations and similar)
22
+ #
23
+ # Apart from the methods on this ruby class the user can also use any other method
24
+ # on JRIEngine, it will be forwarded via {#method_missing}.
25
+ class Engine
26
+
27
+ # Default options used when creating an engine
28
+ DEFAULT_ENGINE_OPTIONS = {
29
+ :r_arguments => ["--no-save"],
30
+ :callback_object => nil,
31
+ :run_repl => false
32
+ }
33
+
34
+ # Add a custom converter used to convert ruby objects to R objects
35
+ #
36
+ # @param [#convert] converter the converter to add
37
+ # @return [Array] known (class level) converters
38
+ def self.add_r_converter(converter)
39
+ @@r_converters ||= []
40
+ @@r_converters << converter
41
+ end
42
+
43
+ # Get all class level custom converters to convert ruby objects to R objects
44
+ #
45
+ # Converters are returned in the reversed order to how they were added
46
+ # (i.e. the last added is applied first)
47
+ #
48
+ # @return [Array] all class level custom converters
49
+ def self.r_converters
50
+ @@r_converters ||= []
51
+ @@r_converters.reverse
52
+ end
53
+
54
+ # Clear all class level custom R converters
55
+ def self.clear_r_converters
56
+ @@r_converters = []
57
+ end
58
+
59
+ # Get default converters to convert ruby objects to R objects
60
+ #
61
+ # @return [Array] all default converters
62
+ def self.default_r_converters
63
+ [
64
+ RConverters::FloatConverter.new,
65
+ RConverters::IntegerConverter.new,
66
+ RConverters::StringConverter.new,
67
+ RConverters::ArrayConverter.new
68
+ ]
69
+ end
70
+
71
+ # Add a custom converter used to convert R objects to ruby objects
72
+ #
73
+ # @param [#convert] converter the converter to add
74
+ # @return [Array] known (class level) converters
75
+ def self.add_ruby_converter(converter)
76
+ @@ruby_converters ||= []
77
+ @@ruby_converters << converter
78
+ end
79
+
80
+ # Get all class level custom converters to convert R objects to ruby objects
81
+ #
82
+ # Converters are returned in the reversed order to how they were added
83
+ # (i.e. the last added is applied first)
84
+ #
85
+ # @return [Array] all class level custom converters
86
+ def self.ruby_converters
87
+ @@ruby_converters ||= []
88
+ @@ruby_converters.reverse
89
+ end
90
+
91
+ # Clear all class level custom ruby converters
92
+ def self.clear_ruby_converters
93
+ @@ruby_converters = []
94
+ end
95
+
96
+ # Get default converters to convert R objects to ruby objects
97
+ #
98
+ # @return [Array] all default converters
99
+ def self.default_ruby_converters
100
+ [
101
+ RubyConverters::DoubleConverter.new,
102
+ RubyConverters::IntegerConverter.new,
103
+ #RubyConverters::StringConverter.new,
104
+ ]
105
+ end
106
+
107
+ # Helper to make sure all engines are finalized so the R thread dies as it should
108
+ #
109
+ # @param engine engine to finalize
110
+ # @return [Proc] that closes the engine
111
+ def self.finalize(engine)
112
+ proc { engine.close }
113
+ end
114
+
115
+ # Create a new instance of Engine
116
+ #
117
+ # @param [Hash] options A Hash of options to override the defaults, or not given
118
+ # in which case the defaults will be used.
119
+ def initialize(options = {})
120
+ combined_options = DEFAULT_ENGINE_OPTIONS.merge(options)
121
+
122
+ @engine = Jri::JRIEngine.new(combined_options[:r_arguments].to_java(:string),
123
+ combined_options[:callback_object],
124
+ combined_options[:run_repl])
125
+
126
+ # the R thread wont die unless we call #close on @engine, so make sure this
127
+ # happens when this object is finalized.
128
+ ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
129
+ end
130
+
131
+ # Forward any method calls not recognized to JRIEngine
132
+ def method_missing(method, *args, &block)
133
+ if @engine.respond_to? method
134
+ @engine.send(method, *args, &block)
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ # Add a custom converter used to convert ruby objects to R objects
141
+ #
142
+ # @param [#convert] converter the converter to add
143
+ # @return [Array] known (instance level) converters
144
+ def add_r_converter(converter)
145
+ @r_converters ||= []
146
+ @r_converters << converter
147
+ end
148
+
149
+ # Get all instance level custom converters to convert ruby objects to R objects
150
+ #
151
+ # Converters are returned in the reversed order to how they were added
152
+ # (i.e. the last added is applied first)
153
+ #
154
+ # @return [Array] all instance level custom converters
155
+ def r_converters
156
+ @r_converters ||= []
157
+ @r_converters.reverse
158
+ end
159
+
160
+ # Clear all instance level custom R converters
161
+ def clear_r_converters
162
+ @r_converters = []
163
+ end
164
+
165
+ # Add a custom converter used to convert R objects to ruby objects
166
+ #
167
+ # @param [#convert] converter the converter to add
168
+ # @return [Array] known (instance level) converters
169
+ def add_ruby_converter(converter)
170
+ @ruby_converters ||= []
171
+ @ruby_converters << converter
172
+ end
173
+
174
+ # Clear all instance level custom R converters
175
+ def clear_ruby_converters
176
+ @ruby_converters = []
177
+ end
178
+
179
+ # Get all instance level custom converters to convert R objects to ruby objects
180
+ #
181
+ # Converters are returned in the reversed order to how they were added
182
+ # (i.e. the last added is applied first)
183
+ #
184
+ # @return [Array] all instance level custom converters
185
+ def ruby_converters
186
+ @ruby_converters ||= []
187
+ @ruby_converters.reverse
188
+ end
189
+
190
+ # Helper method to evaluate expressions but avoid any R-to-ruby conversions
191
+ #
192
+ # Always uses the global environment and returns an R reference which the
193
+ # user can pass along to other R functions later on.
194
+ #
195
+ # @param [String] expression the R expression to evaluate
196
+ # @return [REXPReference] reference to the result of the expression
197
+ def simple_eval(expression)
198
+ parsed_expression = @engine.parse(expression, false)
199
+ @engine.eval(parsed_expression, nil, false)
200
+ end
201
+
202
+ # Eval expression and convert result to the corresponding ruby type
203
+ #
204
+ # @param [String] expression the R epxression to evaluate
205
+ # @return the result converted to the corresponding ruby type
206
+ def eval_and_convert(expression)
207
+ success, value = convert_to_ruby_object(@engine.parseAndEval(expression))
208
+ raise RriException.new("Failed to convert R object to ruby object for: #{value}") unless success
209
+ value
210
+ end
211
+
212
+ # Helper method to assign expressions to R variables
213
+ #
214
+ # Always uses the global environment.
215
+ #
216
+ # @param [#to_s] symbol what to call the R variable
217
+ # @return [nil]
218
+ def simple_assign(symbol, rexp)
219
+ @engine.assign(symbol.to_s, rexp, nil)
220
+ end
221
+
222
+ # Convert a ruby value to an R type and assign it to an R variable.
223
+ #
224
+ # @param obj ruby object to convert and assign to R varible
225
+ # @param [#to_s] symbol what to call the R variable
226
+ # @return [nil]
227
+ def convert_and_assign(obj, symbol)
228
+ success, rexp = convert_to_r_object(obj)
229
+ raise RriException.new("Failed to convert ruby object to R object for: #{obj}") unless success
230
+ simple_assign(symbol, rexp)
231
+ end
232
+
233
+ # Helper method to get a variable from R
234
+ #
235
+ # Always uses the global environment.
236
+ #
237
+ # @param [#to_s] symbol name of the R variable to get
238
+ # @return [REXPReference] reference to the R variable
239
+ def simple_get(symbol)
240
+ @engine.get(symbol.to_s, nil, false)
241
+ end
242
+
243
+ # Get a variable from R and convert it to a ruby object
244
+ #
245
+ # Always uses the global environment.
246
+ #
247
+ # @param [#to_s] symbol name of the R variable to get
248
+ # @return the R variable converted to a ruby object
249
+ def get_and_convert(symbol)
250
+ rexp = @engine.get(symbol.to_s, nil, true)
251
+ success, value = convert_to_ruby_object(rexp)
252
+ raise RriException.new("Failed to convert R object to ruby object for: #{value}") unless success
253
+ value
254
+ end
255
+
256
+ # Convert a ruby object to a R object
257
+ #
258
+ # Applies converters in 3 levels:
259
+ # * custom converters that is set for only this engine instance
260
+ # * custom converters that are set for all engine instances
261
+ # * default converters
262
+ #
263
+ # Converters are applied in the reverse order they were added in.
264
+ #
265
+ # @param obj ruby object to convert
266
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
267
+ # and the second element is the converted object if conversion successful (otherwise
268
+ # the original obj)
269
+ def convert_to_r_object(obj)
270
+ # first try converters defined for just this instance
271
+ success, value = apply_local_r_converters(obj)
272
+ return [success, value] if success
273
+
274
+ # then try converters defined in general
275
+ success, value = apply_r_converters(obj)
276
+ return [success, value] if success
277
+
278
+ # and finally apply the default converters
279
+ success, value = apply_default_r_converters(obj)
280
+ return [success, value] if success
281
+
282
+ # give up
283
+ [false, obj]
284
+ end
285
+
286
+ # Convert an R object to a ruby object
287
+ #
288
+ # Applies converters in 3 levels:
289
+ # * custom converters that is set for only this engine instance
290
+ # * custom converters that are set for all engine instances
291
+ # * default converters
292
+ #
293
+ # Converters are applied in the reverse order they were added in.
294
+ #
295
+ # @param [REXP] rexp R object to convert
296
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
297
+ # and the second element is the converted object if conversion was successful (otherwise
298
+ # the original rexp)
299
+ def convert_to_ruby_object(rexp)
300
+ # first try converters defined for just this instance
301
+ success, value = apply_local_ruby_converters(rexp)
302
+ return [success, value] if success
303
+
304
+ # then try converters defined in general
305
+ success, value = apply_ruby_converters(rexp)
306
+ return [success, value] if success
307
+
308
+ # and finally apply the default converters
309
+ success, value = apply_default_ruby_converters(rexp)
310
+ return [success, value] if success
311
+
312
+ # give up
313
+ [false, rexp]
314
+ end
315
+
316
+ ############################ PRIVATE METHODS ##############################
317
+
318
+ private
319
+
320
+ # Helper method to apply converters
321
+ #
322
+ # @param [Array] converters array of converters to try to apply
323
+ # @param obj object to try to convert
324
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
325
+ # and the second element is the converted object if conversion was successful
326
+ def apply_converters(converters, obj)
327
+ converters.each do |converter|
328
+ success, value = converter.convert(obj)
329
+ return [success, value] if success
330
+ end
331
+ # if no success, return failure and nil for value
332
+ [false, nil]
333
+ end
334
+
335
+ # Apply custom converters that are defined just for this engine to a ruby object
336
+ #
337
+ # @param obj ruby object to convert to R type
338
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
339
+ # and the second element is the converted object if conversion was successful
340
+ def apply_local_r_converters(obj)
341
+ apply_converters(r_converters, obj)
342
+ end
343
+
344
+ # Apply custom converters that are defined for all engines to a ruby object
345
+ #
346
+ # @param obj ruby object to convert to R type
347
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
348
+ # and the second element is the converted object if conversion was successful
349
+ def apply_r_converters(obj)
350
+ apply_converters(Engine.r_converters, obj)
351
+ end
352
+
353
+ # Apply default converters to ruby object
354
+ #
355
+ # @param obj ruby object to convert to R type
356
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
357
+ # and the second element is the converted object if conversion was successful
358
+ def apply_default_r_converters(obj)
359
+ apply_converters(Engine.default_r_converters, obj)
360
+ end
361
+
362
+ # Apply custom converters that are defined just for this engine to an R object
363
+ #
364
+ # @param [REXP] rexp object to convert to ruby object
365
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
366
+ # and the second element is the converted object if conversion was successful
367
+ def apply_local_ruby_converters(rexp)
368
+ apply_converters(ruby_converters, rexp)
369
+ end
370
+
371
+ # Apply custom converters that are defined for all engines to an R object
372
+ #
373
+ # @param [REXP] rexp R object to convert to ruby object
374
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
375
+ # and the second element is the converted object if conversion was successful
376
+ def apply_ruby_converters(rexp)
377
+ apply_converters(Engine.ruby_converters, rexp)
378
+ end
379
+
380
+ # Apply default converters to R object
381
+ #
382
+ # @param [REXP] rexp R object to convert to ruby object
383
+ # @return [Array] an array of size 2 where first element is a boolean indicating succes,
384
+ # and the second element is the converted object if conversion was successful
385
+ def apply_default_ruby_converters(rexp)
386
+ apply_converters(Engine.default_ruby_converters, rexp)
387
+ end
388
+
389
+ end
390
+ end