Module: R::Support

Defined in:
lib/R_interface/rsupport.rb,
lib/R_interface/r_methods.rb,
lib/R_interface/rsupport_scope.rb

Overview


This is a support module for evaluating R functions


Constant Summary collapse

@@exec_counter =
0
@@exec_from_ruby =

Using this method gives us more control over what happens when calling do.call and allows for debugging. Use it in exec_function when debugging is needed.

Polyglot.eval("R", <<-R)
  function(build_method, ...) {
    # function 'invoke' from package rlang should do a cleaner than
    # do.call (this is what the documentation says).
    # res = do.call(...);
    res = invoke(...);
    res2 = build_method(res);
    res2
  }
R

Class Method Summary collapse

Class Method Details

.captureObject


Captures the R output to a variable and returns it.




32
33
34
35
36
37
38
39
40
41
42
# File 'lib/R_interface/r_methods.rb', line 32

def self.capture
  Polyglot.eval("R", <<-R)
    function(obj, ...) {
      sink(tt <- textConnection("results","w"), split=FALSE, type = c("output", "message"));
      print(obj, ...);
      sink();
      close(tt);
      results
    }
  R
end

.capture2Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/R_interface/r_methods.rb', line 44

def self.capture2
  Polyglot.eval("R", <<-R)
    function(obj, ...) {
      tryCatch({
        sink(tt <- textConnection("results","w"), split=FALSE, type = c("output", "message"));
        print(obj, ...);
        sink();
        results
      }, finally = {
        close(tt);
      })
    }
  R
end

.convert_symbol2r(symbol) ⇒ Object


Converts a Ruby symbol onto an R symbol. Converts ‘__’ to ‘.’ and ‘rclass’ to class


Parameters:

  • symbol (Symbol)

    A Ruby symbol to convert to R



200
201
202
203
204
205
206
207
208
# File 'lib/R_interface/rsupport.rb', line 200

def self.convert_symbol2r(symbol)
  name = symbol.to_s
  # convert '__' to '.'
  name.gsub!(/__/,".")
  # Method 'rclass' is a substitute for R method 'class'.  Needed, as 'class' is also
  # a Ruby method on an object
  name.gsub!("rclass", "class")
  name
end

.create_bin_expr(operator) ⇒ Object





152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/R_interface/r_methods.rb', line 152

def self.create_bin_expr(operator)
  is_formula = (operator == "`~`")? 'TRUE' : 'FALSE'
  Polyglot.eval("R", <<-R)
    function(op1, op2) {
      o1 = enexpr(op1)
      o2 = enexpr(op2)
      if (typeof(o1) == 'symbol' && o1 == expr(`__`)) {
        o1 = expr(.)
      }
      if (typeof(o2) == 'symbol' && o2 == expr(`__`)) {
        o2 = expr(.)
      }
      exp = expr(#{operator}(!!o1, !!o2))
      if (#{is_formula}) {
        as.formula(exp)
      } else {
        exp
      }
    }
    R
end

.dbk_indexObject





81
82
83
84
85
86
87
# File 'lib/R_interface/r_methods.rb', line 81

def self.dbk_index
  Polyglot.eval("R", <<-R)
    function(obj, ...) {
      obj[[...]]
    }
  R
end

.enquoObject





122
123
124
125
126
127
128
# File 'lib/R_interface/r_methods.rb', line 122

def self.enquo
  Polyglot.eval("R", <<-R)
    function(x, ...) {
      enquo(x)          
    } 
  R
end

.eval(string) ⇒ Object


@TODO: Fix the ‘rescue’ clause and open issue with fastR Evaluates an R code


Parameters:

  • string (String)

    A string of R code that can be correctly parsed by R



86
87
88
89
90
91
92
# File 'lib/R_interface/rsupport.rb', line 86

def self.eval(string)
  begin
    Polyglot.eval("R", string)
  rescue RuntimeError
    raise NoMethodError.new("Method #{string} not found in R environment")
  end
end

.exec_function(function, *args) ⇒ Object


wrap the return object in a Ruby object


Parameters:

  • function (R function (Interop))

    R function to execute

  • internal (Boolean)

    true if returning to an internal object, i.e., does not



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/R_interface/rsupport.rb', line 217

def self.exec_function(function, *args)

  begin
    
    # If the execution counter is 0, function was not recursively called
    # Starts capturing output
    if (@@exec_counter == 0)
      R::Support.eval("unlockBinding('r_capture', globalenv())")
      @@con = R::Support.start_capture.call("r_capture")
    end
    
    @@exec_counter = @@exec_counter + 1
    
    # function has no arguments, call it directly
    if (args.length == 0)
      res = R::Object.build(function.call)
    else
      pl = R::Support.parse2list(*args)
      res = @@exec_from_ruby.call(R::Object.method(:build), function, pl)
      # R::Object.build(R::Support.eval("do.call").call(function, pl))
    end
    
    @@exec_counter = @@exec_counter - 1
    
    # When execution counter back to 0, print the captured output if the length
    # of the output is greater than 0
    if (@@exec_counter == 0)
      R::Support.stop_capture.call(@@con)
      if (R::Support.eval("length(r_capture) > 0")[0])
        cap = R::Object.build(R::Support.eval("r_capture"))
        (0...cap.size).each do |i|
          puts cap >> i
        end
      end
    end
    
  rescue StandardError => e
    R::Support.stop_capture.call(@@con) if (@@exec_counter == 0)
    raise e
  end
  
  res
  
end

.exec_function_i(function, *args) ⇒ Object


Executes the given R function with the given arguments.




278
279
280
281
# File 'lib/R_interface/rsupport.rb', line 278

def self.exec_function_i(function, *args)
  pl = R::Support.parse2list(*args)
  R::Support.eval("invoke").call(function, pl)
end

.exec_function_name(function_name, *args) ⇒ Object



Parameters:

  • function_name (String)

    Name of the R function to execute



267
268
269
270
271
272
# File 'lib/R_interface/rsupport.rb', line 267

def self.exec_function_name(function_name, *args)
  # @TODO: should check all that can go wrong when calling eval(function_name) to
  # raise the proper exception
  f = R::Support.eval(function_name)
  R::Support.exec_function(f, *args)
end

.interop(object) ⇒ Object



Parameters:

  • object (Object)

    Any Ruby object

Returns:

  • boolean if the object is an interop or not



99
100
101
# File 'lib/R_interface/rsupport.rb', line 99

def self.interop(object)
  Truffle::Interop.foreign?(object)
end

.md_indexObject


multi-dimensional indexing




93
94
95
96
97
98
99
# File 'lib/R_interface/r_methods.rb', line 93

def self.md_index
  Polyglot.eval("R", <<-R)
    function(mdobject, ...) { 
      mdobject[...];
    }
  R
end

.new_scope(symbol, *args, &block) ⇒ Object



68
69
70
71
72
73
# File 'lib/R_interface/rsupport_scope.rb', line 68

def self.new_scope(symbol, *args, &block)
  executionScope = Scope.with(symbol, *args)
  scope = executionScope.new
  scope.instance_eval(&block)
  # scope
end

.parse2list(*args) ⇒ Object


Parses the Ruby arguments into a R list of R objects


Parameters:

  • args (Ruby Parameter list)


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/R_interface/rsupport.rb', line 171

def self.parse2list(*args)
  
  params = Polyglot.eval("R", "list()")
  
  args.each_with_index do |arg, i|
    if (arg.is_a? Hash)
      params = parse_hash(arg, params)
    elsif (arg.is_a? NotAvailable)
      params = R::Support.eval("c").call(params, arg.r_interop)
    elsif (arg.is_a? NilClass)
      params = R::Support.eval("`length<-`").call(params, i+1)
    else
      params = R::Support.eval("`[[<-`").
                 call(params, i+1, R::Support.parse_arg(arg))
    end
  end
  
  # if the parameter is an empty list, then add the element NULL to the list
  (Polyglot.eval("R", "length").call(params) == 0) ?
    Polyglot.eval("R", "list").call(nil) : params
  
end

.parse_arg(arg) ⇒ Object


whatever needs it


Parameters:

  • arg (Object)

    A Ruby object to be converted to R to be used by an R function, or

Returns:

  • Object that can be used in R



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/R_interface/rsupport.rb', line 109

def self.parse_arg(arg)

  case(arg)
  when -> (arg) { interop(arg) }
    arg
  when R::Object
    arg.r_interop
  when Numeric
    R::Support.eval('c').call(arg)
  when NegRange
    final_value = (arg.exclude_end?)? (arg.last - 1) : arg.last
    R::Support.eval('seq').call(arg.first, final_value)
  when Range
    final_value = (arg.exclude_end?)? (arg.last - 1) : arg.last
    R::Support.eval('seq').call(arg.first, final_value)
  when :all
    R.empty_symbol
  when Symbol
    arg = R::Support.eval('as.name').call(arg.to_s.gsub(/__/,"."))
  when Proc, Method
    R::RubyCallback.build(arg)
  # This is a Ruby argument that will be automatically converted
  # by the low level Polyglot interface to R
  else 
    arg
  end

end

.parse_hash(hsh, lst) ⇒ Object


Given a hash, convert it to an R argument list




142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/R_interface/rsupport.rb', line 142

def self.parse_hash(hsh, lst)

  hsh.each_pair do |key, value|
    k = key.to_s.gsub(/__/,".")
    
    case 
    when (value.is_a? NotAvailable)
      na_named = R::Support.eval("`names<-`").call(value.r_interop, k)
      lst = R::Support.eval("c").call(lst, na_named)
    when (k == 'all')
      lst = R::Support.eval("`[[<-`").
              call(lst, R::Support.eval('`+`').
                          call(R::Support.eval('length').call(lst), 1),
                   R::Support.parse_arg(value))
    else
      lst = R::Support.eval("`[[<-`").
              call(lst, k, R::Support.parse_arg(value))
    end
  end
  
  lst
  
end

Prints a foreign R interop pointer. Used for debug.




354
355
356
357
358
# File 'lib/R_interface/rsupport.rb', line 354

def self.print_foreign(interop)
  puts "===External value==="
  R::Support.eval("print").call(interop)
  puts "===end external value==="
end




339
340
341
342
343
344
345
346
347
348
# File 'lib/R_interface/rsupport.rb', line 339

def self.print_str(obj)
  
  lst = obj.as__list
  puts lst
=begin      
  (1..lst.length >> 0).each do |i|
    puts lst[[i]]
  end
=end      
end

.process_missing(symbol, internal, *args) ⇒ Object


Process the missing method it should not wrap the return value inside an R::Object it is not applied to an object


Parameters:

  • symbol (Symbol)
  • internal (Boolean)

    true if the method will return to an internal method, i.e.,

  • object (Ruby Object)

    the ruby object to which the method is applied, false if



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/R_interface/rsupport.rb', line 316

def self.process_missing(symbol, internal, *args)

  name = R::Support.convert_symbol2r(symbol)
  
  case name
  # when missing method has an '=' sign in it...
  when ->(x) { x =~ /(.*)=$/ }
    R::Support.set_symbol($1, *args)
  # when missing method is 'eval'... needs special treatment
  when "eval"
    R::Support.r_evaluate(*args)
  else
    function = R::Support.eval(name)
    internal ? R::Support.exec_function_i(function, *args) :
      R::Support.exec_function(function, *args)
  end

end

.r_evaluate(*args) ⇒ Object


Calls the R ‘eval’ function. This requires some special semantics R function ‘eval’ needs to be called in a special way, since it expects the second argument to be an environment. If the arguments are packed into a list, then there is no second argument and the function fails to use the second argument as environment




302
303
304
305
# File 'lib/R_interface/rsupport.rb', line 302

def self.r_evaluate(*args)
  r_args = args.map { |arg| R::Support.parse_arg(arg) }
  R::Object.build(R::Support.eval("eval").call(*r_args))
end

.rangeObject





134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/R_interface/r_methods.rb', line 134

def self.range
  Polyglot.eval("R", <<-R)
    function(x, y, neg = FALSE) {
      e1 = enexpr(x)
      e2 = enexpr(y)
      if (neg) {
        expr(-(!!e1:!!e2))
      } else {
        expr(!!e1:!!e2)
      }
    }
  R
end

.ruby_callback_methodObject


R function that returns another R function that calls back a Ruby Method or Proc Some R functions that receive a function as argument will test to see if their parameters is a function or a symbol, so the Ruby method needs to be wrapped inside an R function in order for it to pass this test.




108
109
110
111
112
113
114
115
116
# File 'lib/R_interface/r_methods.rb', line 108

def self.ruby_callback_method
  Polyglot.eval("R", <<-R)
    function(rb_method) {
      function(...) {
      rb_method(...)
      }
    }
  R
end

.set_symbol(name, *args) ⇒ Object


sets the R symbol <name> to the given value


Parameters:

  • name (String)

    String representation of the R symbol that needs to be assigned

  • args (Array)

    Array with one object to be set to the symbol



289
290
291
292
# File 'lib/R_interface/rsupport.rb', line 289

def self.set_symbol(name, *args)
  args << R::Support.eval("globalenv").call()
  R::Support.exec_function_name("assign", name, *args)
end

.start_captureObject



59
60
61
62
63
64
65
66
# File 'lib/R_interface/r_methods.rb', line 59

def self.start_capture
  Polyglot.eval("R", <<-R)
    function(cap_variable) {
      sink(con <- textConnection(cap_variable,"w"), split=FALSE, type = c("output"));
      con
    }
  R
end

.stop_captureObject



68
69
70
71
72
73
74
75
# File 'lib/R_interface/r_methods.rb', line 68

def self.stop_capture
  Polyglot.eval("R", <<-R)
    function(con) {
      sink();
      close(con);
    }
  R
end