rri 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.
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