diff --git a/ext/Converters.c b/ext/Converters.c index e8c7d54..b45043e 100644 --- a/ext/Converters.c +++ b/ext/Converters.c @@ -36,11 +36,18 @@ // ************** Converters from Ruby to R *********// +VALUE ruby_to_Robj(VALUE self, VALUE args){ + + SEXP robj; + VALUE val; + robj = ruby_to_R(args); + val = to_ruby_with_mode(robj,NO_CONVERSION); + return val; +} SEXP ruby_to_R(VALUE obj) { SEXP robj; VALUE str; - char buf [100]; //Return nil if object is nil if (obj == Qnil) { @@ -104,8 +111,7 @@ SEXP ruby_to_R(VALUE obj) { str = rb_funcall(obj,rb_intern("inspect"),0); str = rb_funcall(str,rb_intern("slice"),2,INT2NUM(0),INT2NUM(60)); - sprintf(buf,"Unsupported object '%s' passed to R.\n",RSTRING_PTR(str)); - rb_raise(rb_eArgError,"%s",buf); + rb_raise(rb_eArgError, "Unsupported object '%s' passed to R.\n", RSTRING_PTR(str)); PROTECT(robj = NULL); /* Protected to avoid stack inbalance */ } @@ -309,10 +315,9 @@ int to_ruby_vector(SEXP robj, VALUE *obj, int mode) { VALUE it, tmp; - VALUE params[2]; SEXP names, dim; int len, *integers, i, type; - char *strings, *thislevel; + const char *strings, *thislevel; double *reals; Rcomplex *complexes; @@ -352,11 +357,11 @@ to_ruby_vector(SEXP robj, VALUE *obj, int mode) if(isFactor(robj)) { /* Watch for NA's! */ if(integers[i]==NA_INTEGER) - it = rb_str_new2(CHAR(NA_STRING)); + it = rb_external_str_new_cstr(CHAR(NA_STRING)); else { thislevel = CHAR(STRING_ELT(GET_LEVELS(robj), integers[i]-1)); - if (!(it = rb_str_new2(thislevel))) + if (!(it = rb_external_str_new_cstr(thislevel))) return -1; } } @@ -380,11 +385,11 @@ to_ruby_vector(SEXP robj, VALUE *obj, int mode) break; case STRSXP: if(STRING_ELT(robj, i)==R_NaString) - it = rb_str_new2(CHAR(NA_STRING)); + it = rb_external_str_new_cstr(CHAR(NA_STRING)); else { strings = CHAR(STRING_ELT(robj, i)); - if (!(it = rb_str_new2(strings))) + if (!(it = rb_external_str_new_cstr(strings))) return -1; } break; @@ -404,8 +409,7 @@ to_ruby_vector(SEXP robj, VALUE *obj, int mode) dim = GET_DIM(robj); if (dim != R_NilValue) { - len = GET_LENGTH(dim); - *obj = to_ruby_array(tmp, INTEGER(dim), len); + *obj = to_ruby_array(tmp, robj); return 1; } @@ -545,7 +549,7 @@ VALUE from_class_table(SEXP robj) for (i=0; i/dev/null).gsub(/[\r\n]/,"") +end + File.open("config.h", "w") do |f| f.puts("#ifndef R_CONFIG_H") f.puts("#define R_CONFIG_H") - r_home = $configure_args.has_key?('--with-R-dir') ? $configure_args['--with-R-dir'].inspect : 'NULL' - f.puts("#define RSRUBY_R_HOME #{r_home}") + f.puts("#define RSRUBY_R_HOME \"#{r_home}\"") if Dir.exist?(r_home) f.puts("#endif") end $extconf_h = 'config.h' diff --git a/ext/rsruby.c b/ext/rsruby.c index fa88bba..c85e2c4 100644 --- a/ext/rsruby.c +++ b/ext/rsruby.c @@ -37,8 +37,7 @@ /* This is inspired in $R_SRC/src/main/memory.c */ static SEXP R_References; void protect_robj(SEXP robj){ - R_References = CONS(robj, R_References); - SET_SYMVALUE(install("R.References"), R_References); + R_PreserveObject(robj); } SEXP @@ -55,16 +54,10 @@ RecursiveRelease(SEXP obj, SEXP list) /* TODO: This needs implementing as a Ruby destructor for each RObj */ void -Robj_dealloc(VALUE self) +Robj_dealloc(SEXP robj) { - SEXP robj; - - Data_Get_Struct(self, struct SEXPREC, robj); - - R_References = RecursiveRelease(robj, R_References); - SET_SYMVALUE(install("R.References"), R_References); - - return; + R_ReleaseObject(robj); + } @@ -101,9 +94,6 @@ VALUE get_fun(VALUE self, VALUE name){ //TODO - This function does not appear to be working correctly void r_finalize(void) { - unsigned char buf[1024]; - char * tmpdir; - R_dot_Last(); R_gc(); /* Remove any remaining R objects from memory */ } @@ -124,7 +114,7 @@ VALUE rs_shutdown(VALUE self){ */ VALUE rr_init(VALUE self){ - + init_R(0,NULL); // Initialize the list of protected objects R_References = R_NilValue; @@ -141,10 +131,15 @@ void init_R(int argc, char **argv){ char *defaultArgv[] = {"rsruby","-q","--vanilla"}; - if (RSRUBY_R_HOME) { - setenv("R_HOME", RSRUBY_R_HOME, 0); - } - Rf_initEmbeddedR(sizeof(defaultArgv) / sizeof(defaultArgv[0]), defaultArgv); +#ifdef RSRUBY_R_HOME + setenv("R_HOME", RSRUBY_R_HOME, 0); +#endif + + // Rf_initEmbeddedR(sizeof(defaultArgv) / sizeof(defaultArgv[0]), defaultArgv); + Rf_initialize_R(sizeof(defaultArgv) / sizeof(defaultArgv[0]), defaultArgv); + R_Interactive = TRUE; + R_CStackLimit = (uintptr_t)-1; //disable stack limit checking + setup_Rmainloop(); R_Interactive = FALSE; //Remove crash menu (and other interactive R features) } @@ -170,13 +165,14 @@ void Init_rsruby_c(){ rb_define_method(cRRuby, "shutdown", rs_shutdown, 0); rb_define_method(cRRuby, "crash", crash, 0); + rb_define_method(cRRuby, "to_R", ruby_to_Robj, 1); //Add the lcall method to RObj cRObj = rb_const_get(rb_cObject,rb_intern("RObj")); rb_define_method(cRObj, "lcall", RObj_lcall, 1); rb_define_method(cRObj, "__init_lcall__", RObj_init_lcall, 1); rb_define_method(cRObj, "to_ruby", RObj_to_ruby, -2); - rb_define_method(cRObj, "to_R", ruby_to_R, 1); + } diff --git a/ext/rsruby.h b/ext/rsruby.h index 39b6ede..ffde0d8 100644 --- a/ext/rsruby.h +++ b/ext/rsruby.h @@ -31,14 +31,14 @@ #ifndef R_RUBY_MAIN #define R_RUBY_MAIN - +#define CSTACK_DEFNS #include "ruby.h" - #include "R.h" #include "Rdefines.h" #include "Rinternals.h" #include "Rdefines.h" - +#include "Rinterface.h" +#include "Rembedded.h" #include "signal.h" #include "R_eval.h" @@ -58,8 +58,6 @@ /* Missing definitions from Rinterface.h or RStartup.h */ # define CleanEd Rf_CleanEd -extern int Rf_initEmbeddedR(int argc, char **argv); -extern int R_Interactive; extern void CleanEd(void); extern int R_CollectWarnings; # define PrintWarnings Rf_PrintWarnings @@ -84,6 +82,6 @@ VALUE RObj_init_lcall(VALUE self, VALUE args); VALUE RObj_to_ruby(VALUE self, VALUE args); int make_argl(VALUE args, SEXP *e); void protect_robj(SEXP robj); -void Robj_dealloc(VALUE self); +void Robj_dealloc(SEXP robj); #endif diff --git a/lib/rsruby.rb b/lib/rsruby.rb index 91f3f78..34641e4 100644 --- a/lib/rsruby.rb +++ b/lib/rsruby.rb @@ -1,4 +1,5 @@ require 'rsruby/robj' +require 'rsruby/rarray' require 'rsruby_c' require 'singleton' @@ -133,8 +134,7 @@ def method_missing(r_id,*args) #Translate Ruby method call to R robj_name = RSRuby.convert_method_name(r_id.to_s) - #Retrieve it - robj = self.__getitem__(robj_name) + robj = robj_name =~ /(.+)::(.+)/ ? self.send('::',$1,$2) : self.__getitem__(robj_name) #TODO perhaps this is not neccessary - always call these methods #use the [] syntax for variables etc... @@ -181,9 +181,9 @@ def RSRuby.convert_method_name(name) if name.length > 1 and name[-1].chr == '_' and name[-2].chr != '_' name = name[0..-2] end - name.gsub!(/__/,'<-') - name.gsub!(/_/, '.') - return name + newname = name.gsub(/__/,'<-') + newname = name.gsub(/_/, '.') + return newname end #Converts an Array of function arguments into lcall format. If the last @@ -299,4 +299,22 @@ def __getitem__(name,init=false) end class RException < RuntimeError + def initialize(_msg) + e = RSRuby.get_default_mode + RSRuby.set_default_mode(RSRuby::VECTOR_CONVERSION ) + if RSRuby.instance.exists('traceback.character')[0] + @r_traceback = RSRuby.instance.traceback_character('max.lines'=>10) + else + r_full_traceback = RSRuby.instance.get(".Traceback") + r_shortened_traceback = r_full_traceback.map{|x| x.first(10)} + @r_traceback = r_shortened_traceback.flatten + end + RSRuby.set_default_mode(e) + super + end + def backtrace + x = super + return x if x.nil? + @r_traceback +x + end end diff --git a/lib/rsruby/rarray.rb b/lib/rsruby/rarray.rb new file mode 100644 index 0000000..f535606 --- /dev/null +++ b/lib/rsruby/rarray.rb @@ -0,0 +1,89 @@ +require 'rsruby' +class RArray + attr_reader :array + def initialize(_array,_dimnames,_dimnamesorder) + @array = _array + @dimnames = _dimnames + @dimnamesorder = _dimnamesorder + end +# def method_missing(m,*args) +# if args.length>0 +# @array.send(m,args) +# else +# @array.send(m) +# end +# end + def [](index) + @array[index] + end + # trim the innermost dimension to n + # innermost dimension is outermost dimension in R + def trim(_n) + end + #we must handle either array or hash of dim names + #since we don't know what rsruby is going to give + def dimension_count + @dimnames.length + end + def subset(_keys,_dim) + all_keys = dimnames_along_dimension(_dim) + new_order = _keys.map{|x| + all_keys.index(x) + } + new_array = subset_helper(@array,new_order,0,_dim) + if @dimnames.is_a? Array + new_dimnames = @dimnames.dup + new_dimnames[_dim] = _keys + RArray.new(new_array,new_dimnames,nil) + else #hash + new_dimnames = @dimnames.merge({@dimnamesorder[_dim] => _keys}) + RArray.new(new_array,new_dimnames,@dimnamesorder.dup) + end + end + def subset_helper(_array,_new_order,_current_depth,_target_depth) + if _current_depth == _target_depth + _new_order.map{|x| + x.nil? ? nil : _array.fetch(x) + } + else + _array.map{|x| + subset_helper(x,_new_order,_current_depth+1,_target_depth) + } + end + end + def get(*_args) + indices = _args.each_with_index.map{|x,i| + d = dimnames_along_dimension(i) + return nil unless d + j = d.index(x) + return nil unless j + j + } + a=@array + indices.each{|i| + a=a[i] + } + a + end +# def first(_n) +# new_array = @array.first(_n) +# new_dimnames = nil +# if @dimnames.is_a? Array +# new_dimnames = dimnames.dup +# new_dimnames[0] = new_dimnames[0].first(_n) +# else #hash +# new_dimnames = @dimnames.merge ({@dimnamesorder[0] => dimnames(0).first(_n)}) +# end +# RArray.new(new_array,new_dimnames,@dimnamesorder) +# end + + + def dimnames_along_dimension(_index) + return @dimnames[_index] if @dimnames.is_a? Array + return @dimnames[@dimnamesorder[_index]] if @dimnames.is_a? Hash + raise "unsupported dimnames" + end + def dimension_names + return @dimnamesorder + end +end diff --git a/rsruby.gemspec b/rsruby.gemspec index 3c0284a..e08e94c 100644 --- a/rsruby.gemspec +++ b/rsruby.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = %q{rsruby} - s.version = "0.5.1.1" + s.version = "0.5.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Alex Gutteridge"] @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.email = %q{ag357@cam.ac.uk} s.extensions = ["ext/extconf.rb"] s.extra_rdoc_files = ["README.txt", "History.txt", "License.txt", "examples/bioc.rb", "examples/dataframe.rb", "examples/arrayfields.rb", "examples/erobj.rb"] - s.files = ["History.txt", "License.txt", "Manifest.txt", "README.txt", "Rakefile.rb", "examples/arrayfields.rb", "examples/bioc.rb", "examples/dataframe.rb", "examples/erobj.rb", "ext/Converters.c", "ext/Converters.h", "ext/R_eval.c", "ext/R_eval.h", "ext/extconf.rb", "ext/robj.c", "ext/rsruby.c", "ext/rsruby.h", "lib/rsruby.rb", "lib/rsruby/dataframe.rb", "lib/rsruby/erobj.rb", "lib/rsruby/robj.rb", "test/table.txt", "test/tc_array.rb", "test/tc_boolean.rb", "test/tc_cleanup.rb", "test/tc_eval.rb", "test/tc_extensions.rb", "test/tc_init.rb", "test/tc_io.rb", "test/tc_library.rb", "test/tc_matrix.rb", "test/tc_modes.rb", "test/tc_robj.rb", "test/tc_sigint.rb", "test/tc_to_r.rb", "test/tc_to_ruby.rb", "test/tc_util.rb", "test/tc_vars.rb", "test/test_all.rb"] + s.files = ["History.txt", "License.txt", "Manifest.txt", "README.txt", "Rakefile.rb", "examples/arrayfields.rb", "examples/bioc.rb", "examples/dataframe.rb", "examples/erobj.rb", "ext/Converters.c", "ext/Converters.h", "ext/R_eval.c", "ext/R_eval.h", "ext/extconf.rb", "ext/robj.c", "ext/rsruby.c", "ext/rsruby.h", "lib/rsruby.rb", "lib/rsruby/dataframe.rb", "lib/rsruby/erobj.rb", "lib/rsruby/rarray.rb", "lib/rsruby/robj.rb", "test/table.txt", "test/tc_array.rb", "test/tc_boolean.rb", "test/tc_cleanup.rb", "test/tc_eval.rb", "test/tc_extensions.rb", "test/tc_init.rb", "test/tc_io.rb", "test/tc_library.rb", "test/tc_matrix.rb", "test/tc_modes.rb", "test/tc_robj.rb", "test/tc_sigint.rb", "test/tc_to_r.rb", "test/tc_to_ruby.rb", "test/tc_util.rb", "test/tc_vars.rb", "test/test_all.rb"] s.has_rdoc = true s.homepage = %q{http://web.kuicr.kyoto-u.ac.jp/~alexg/rsruby/} s.rdoc_options = ["--exclude", "test/*", "--main", "README.txt", "--inline-source"] diff --git a/test/tc_mem.rb b/test/tc_mem.rb new file mode 100644 index 0000000..8f8a407 --- /dev/null +++ b/test/tc_mem.rb @@ -0,0 +1,35 @@ +require 'test/unit' +require 'rsruby' + +class TestMem < Test::Unit::TestCase + + def setup + @r = RSRuby.instance + RSRuby.set_default_mode(RSRuby::NO_DEFAULT) + @r.class_table.clear + @r.proc_table.clear + end + + #test that robjects are not killed + def test_robjpersist + RSRuby.set_default_mode(RSRuby::NO_CONVERSION) + randarray = r.eval_R("x=runif(100,0,1)") + RSRuby.set_default_mode(RSRuby::BASIC_CONVERSION) + vals = r.x + r.eval_R("rm(x)") + RSRuby.set_default_mode(RSRuby::NO_CONVERSION) + + #do a bunch of stuff + 10000.times do |n| + a = RSRuby.instance.parse(:text =>"x#{n} = c(1:10000);") + RSRuby.instance.eval(a) + RSRuby.instance.eval_R("rm(x#{n})") + end + + RSRuby.set_default_mode(RSRuby::BASIC_CONVERSION) + assert_equal(vals, r.x) + + end + +end + diff --git a/test/tc_robj.rb b/test/tc_robj.rb index 30e2589..1f680d2 100644 --- a/test/tc_robj.rb +++ b/test/tc_robj.rb @@ -80,5 +80,8 @@ def test_lcall RSRuby.set_default_mode(RSRuby::BASIC_CONVERSION) assert_equal(@r.names(arr), ['','a','b','c']) end - + def test_initialize + robj = RObj.new([1,2,3]) + assert_equal('RObj',robj.class) + end end diff --git a/test/test_memory_usage.rb b/test/test_memory_usage.rb index c52ddbb..9038457 100644 --- a/test/test_memory_usage.rb +++ b/test/test_memory_usage.rb @@ -2,6 +2,7 @@ 10000.times do |n| a = RSRuby.instance.parse(:text =>"x#{n} = c(1:1000000);") RSRuby.instance.eval(a) + RSRuby.instance.eval_R("rm(x#{n})") #RSRuby.instance.eval_R("x#{n} = c(1:1000000)") end