rinruby 1.1.1 → 1.2.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/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/ruby
2
+ # -*- ruby -*-
3
+ # -*- coding: utf-8 -*-
4
+ $:.unshift(File.dirname(__FILE__)+'/lib/')
5
+ require 'rubygems'
6
+ require 'hoe'
7
+ require './lib/rinruby'
8
+
9
+ Hoe.plugin :git
10
+
11
+ Hoe.spec 'rinruby' do
12
+ self.testlib=:rspec
13
+ self.version=RinRuby::VERSION
14
+ # self.rubyforge_name = 'rinruby' # if different than 'rinruby2'
15
+ self.developer('David Dahl', 'rinruby_AT_ddahl.org')
16
+ self.developer('Claudio Bustos', 'clbustos_AT_gmail.com')
17
+ self.url = "http://rinruby.ddahl.org/"
18
+
19
+ end
20
+
21
+ # vim: syntax=ruby
data/lib/rinruby.rb CHANGED
@@ -56,16 +56,23 @@
56
56
  #
57
57
  #
58
58
  #The files "java" and "readline" are used when available to add functionality.
59
-
59
+ require 'matrix'
60
60
  class RinRuby
61
+
62
+ require 'socket'
61
63
 
62
- VERSION = '1.1.1'
64
+
65
+ VERSION = '1.2.0'
63
66
 
64
- require 'socket'
65
67
 
66
68
  attr_reader :interactive
67
69
  attr_reader :readline
68
-
70
+ # Exception for closed engine
71
+ EngineClosed=Class.new(Exception)
72
+ # Parse error
73
+ ParseError=Class.new(Exception)
74
+
75
+
69
76
  #RinRuby is invoked within a Ruby script (or the interactive "irb" prompt denoted >>) using:
70
77
  #
71
78
  # >> require "rinruby"
@@ -96,20 +103,20 @@ class RinRuby
96
103
  while true
97
104
  begin
98
105
  @port_number = port_number + rand(port_width)
99
- @server_socket = TCPServer::new("127.0.0.1",@port_number)
106
+ @server_socket = TCPServer::new("127.0.0.1", @port_number)
100
107
  break
101
108
  rescue Errno::EADDRINUSE
102
- sleep 1 if port_width == 1
109
+ sleep 0.5 if port_width == 1
103
110
  end
104
111
  end
105
112
  @echo_enabled = echo
106
113
  @echo_stderr = false
107
114
  @interactive = interactive
108
115
  @platform = case RUBY_PLATFORM
109
- when /mswin/: 'windows'
110
- when /mingw/: 'windows'
111
- when /bccwin/: 'windows'
112
- when /cygwin/: 'windows-cygwin'
116
+ when /mswin/ then 'windows'
117
+ when /mingw/ then 'windows'
118
+ when /bccwin/ then 'windows'
119
+ when /cygwin/ then 'windows-cygwin'
113
120
  when /java/
114
121
  require 'java' #:nodoc:
115
122
  if java.lang.System.getProperty("os.name") =~ /[Ww]indows/
@@ -137,10 +144,11 @@ class RinRuby
137
144
  @engine = IO.popen(cmd,"w+")
138
145
  @reader = @engine
139
146
  @writer = @engine
147
+ raise "Engine closed" if @engine.closed?
140
148
  @writer.puts <<-EOF
141
149
  #{RinRuby_KeepTrying_Variable} <- TRUE
142
150
  while ( #{RinRuby_KeepTrying_Variable} ) {
143
- #{RinRuby_Socket} <- try(suppressWarnings(socketConnection("127.0.0.1",#{@port_number},blocking=TRUE,open="rb")),TRUE)
151
+ #{RinRuby_Socket} <- try(suppressWarnings(socketConnection("127.0.0.1", #{@port_number}, blocking=TRUE, open="rb")),TRUE)
144
152
  if ( inherits(#{RinRuby_Socket},"try-error") ) {
145
153
  Sys.sleep(0.1)
146
154
  } else {
@@ -149,6 +157,9 @@ class RinRuby
149
157
  }
150
158
  rm(#{RinRuby_KeepTrying_Variable})
151
159
  EOF
160
+ r_rinruby_get_value
161
+ r_rinruby_pull
162
+ r_rinruby_parseable
152
163
  @socket = @server_socket.accept
153
164
  echo(nil,true) if @platform =~ /.*-java/ # Redirect error messages on the Java platform
154
165
  end
@@ -156,13 +167,21 @@ class RinRuby
156
167
  #The quit method will properly close the bridge between Ruby and R, freeing up system resources. This method does not need to be run when a Ruby script ends.
157
168
 
158
169
  def quit
159
- @writer.puts "q(save='no')"
160
- @socket.close rescue true
161
- @server_socket::close rescue true
162
- @reader.close rescue true
163
- @writer.close rescue true
164
- @engine.close rescue true
165
- true
170
+ begin
171
+ @writer.puts "q(save='no')"
172
+ @socket.read()
173
+ #@socket.close
174
+ @engine.close
175
+
176
+
177
+ @server_socket.close
178
+ #@reader.close
179
+ #@writer.close
180
+ true
181
+ ensure
182
+ @engine.close unless @engine.closed?
183
+ @server_socket.close unless @server_socket.closed?
184
+ end
166
185
  end
167
186
 
168
187
 
@@ -198,13 +217,14 @@ class RinRuby
198
217
  #* echo_override: This argument allows one to set the echo behavior for this call only. The default for echo_override is nil, which does not override the current echo behavior.
199
218
 
200
219
  def eval(string, echo_override=nil)
220
+ raise EngineClosed if @engine.closed?
201
221
  echo_enabled = ( echo_override != nil ) ? echo_override : @echo_enabled
202
222
  if complete?(string)
203
223
  @writer.puts string
204
224
  @writer.puts "warning('#{RinRuby_Stderr_Flag}',immediate.=TRUE)" if @echo_stderr
205
225
  @writer.puts "print('#{RinRuby_Eval_Flag}')"
206
226
  else
207
- raise "Parse error"
227
+ raise ParseError, "Parse error on eval"
208
228
  end
209
229
  Signal.trap('INT') do
210
230
  @writer.print ''
@@ -313,10 +333,11 @@ class RinRuby
313
333
  def method_missing(symbol, *args)
314
334
  name = symbol.id2name
315
335
  if name =~ /(.*)=$/
316
- raise "Unsupported method" if args.length != 1
336
+ raise ArgumentError, "You shouldn't assign nil" if args==[nil]
337
+ super if args.length != 1
317
338
  assign($1,args[0])
318
339
  else
319
- raise "Unsupported method" if args.length != 0
340
+ super if args.length != 0
320
341
  pull(name)
321
342
  end
322
343
  end
@@ -358,10 +379,11 @@ class RinRuby
358
379
  #When assigning an array containing differing types of variables, RinRuby will follow R’s conversion conventions. An array that contains any Strings will result in a character vector in R. If the array does not contain any Strings, but it does contain a Float or a large integer (in absolute value), then the result will be a numeric vector of Doubles in R. If there are only integers that are suffciently small (in absolute value), then the result will be a numeric vector of integers in R.
359
380
 
360
381
  def assign(name, value)
382
+ raise EngineClosed if @engine.closed?
361
383
  if assignable?(name)
362
384
  assign_engine(name,value)
363
385
  else
364
- raise "Parse error"
386
+ raise ParseError, "Parse error"
365
387
  end
366
388
  end
367
389
 
@@ -419,6 +441,7 @@ class RinRuby
419
441
  # >> puts R.pull("test")
420
442
 
421
443
  def pull(string, singletons=false)
444
+ raise EngineClosed if @engine.closed?
422
445
  if complete?(string)
423
446
  result = pull_engine(string)
424
447
  if ( ! singletons ) && ( result.length == 1 ) && ( result.class != String )
@@ -426,7 +449,7 @@ class RinRuby
426
449
  end
427
450
  result
428
451
  else
429
- raise "Parse error"
452
+ raise ParseError, "Parse error"
430
453
  end
431
454
  end
432
455
 
@@ -466,6 +489,8 @@ class RinRuby
466
489
  RinRuby_Type_Integer = 1
467
490
  RinRuby_Type_String = 2
468
491
  RinRuby_Type_String_Array = 3
492
+ RinRuby_Type_Matrix = 4
493
+
469
494
  RinRuby_KeepTrying_Variable = ".RINRUBY.KEEPTRYING.VARIABLE"
470
495
  RinRuby_Length_Variable = ".RINRUBY.PULL.LENGTH.VARIABLE"
471
496
  RinRuby_Type_Variable = ".RINRUBY.PULL.TYPE.VARIABLE"
@@ -481,7 +506,77 @@ class RinRuby
481
506
  RinRuby_Max_R_Integer = 2**31-1
482
507
  RinRuby_Min_R_Integer = -2**31+1
483
508
  #:startdoc:
484
-
509
+
510
+
511
+ def r_rinruby_parseable
512
+ @writer.puts <<-EOF
513
+ rinruby_parseable<-function(var) {
514
+ result=try(parse(text=var),TRUE)
515
+ if(inherits(result, "try-error")) {
516
+ writeBin(as.integer(-1),#{RinRuby_Socket}, endian="big")
517
+ } else {
518
+ writeBin(as.integer(1),#{RinRuby_Socket}, endian="big")
519
+ }
520
+ }
521
+ EOF
522
+ end
523
+ # Create function on ruby to get values
524
+ def r_rinruby_get_value
525
+ @writer.puts <<-EOF
526
+ rinruby_get_value <-function() {
527
+ value <- NULL
528
+ type <- readBin(#{RinRuby_Socket}, integer(), 1, endian="big")
529
+ length <- readBin(#{RinRuby_Socket},integer(),1,endian="big")
530
+ if ( type == #{RinRuby_Type_Double} ) {
531
+ value <- readBin(#{RinRuby_Socket},numeric(), length,endian="big")
532
+ } else if ( type == #{RinRuby_Type_Integer} ) {
533
+ value <- readBin(#{RinRuby_Socket},integer(), length, endian="big")
534
+ } else if ( type == #{RinRuby_Type_String} ) {
535
+ value <- readBin(#{RinRuby_Socket},character(),1,endian="big")
536
+ } else {
537
+ value <-NULL
538
+ }
539
+ value
540
+ }
541
+ EOF
542
+ end
543
+
544
+ def r_rinruby_pull
545
+ @writer.puts <<-EOF
546
+ rinruby_pull <-function(var)
547
+ {
548
+ if ( inherits(var ,"try-error") ) {
549
+ writeBin(as.integer(#{RinRuby_Type_NotFound}),#{RinRuby_Socket},endian="big")
550
+ } else {
551
+ if (is.matrix(var)) {
552
+ writeBin(as.integer(#{RinRuby_Type_Matrix}),#{RinRuby_Socket},endian="big")
553
+ writeBin(as.integer(dim(var)[1]),#{RinRuby_Socket},endian="big")
554
+ writeBin(as.integer(dim(var)[2]),#{RinRuby_Socket},endian="big")
555
+
556
+ } else if ( is.double(var) ) {
557
+ writeBin(as.integer(#{RinRuby_Type_Double}),#{RinRuby_Socket},endian="big")
558
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
559
+ writeBin(var,#{RinRuby_Socket},endian="big")
560
+ } else if ( is.integer(var) ) {
561
+ writeBin(as.integer(#{RinRuby_Type_Integer}),#{RinRuby_Socket},endian="big")
562
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
563
+ writeBin(var,#{RinRuby_Socket},endian="big")
564
+ } else if ( is.character(var) && ( length(var) == 1 ) ) {
565
+ writeBin(as.integer(#{RinRuby_Type_String}),#{RinRuby_Socket},endian="big")
566
+ writeBin(as.integer(nchar(var)),#{RinRuby_Socket},endian="big")
567
+ writeBin(var,#{RinRuby_Socket},endian="big")
568
+ } else if ( is.character(var) && ( length(var) > 1 ) ) {
569
+ writeBin(as.integer(#{RinRuby_Type_String_Array}),#{RinRuby_Socket},endian="big")
570
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
571
+ } else {
572
+ writeBin(as.integer(#{RinRuby_Type_Unknown}),#{RinRuby_Socket},endian="big")
573
+ }
574
+ }
575
+ }
576
+ EOF
577
+
578
+
579
+ end
485
580
  def to_signed_int(y)
486
581
  if y.kind_of?(Integer)
487
582
  ( y > RinRuby_Half_Max_Unsigned_Integer ) ? -(RinRuby_Max_Unsigned_Integer-y) : ( y == RinRuby_NA_R_Integer ? nil : y )
@@ -492,6 +587,13 @@ class RinRuby
492
587
 
493
588
  def assign_engine(name, value)
494
589
  original_value = value
590
+ # Special assign for matrixes
591
+ if value.kind_of?(::Matrix)
592
+ values=value.row_size.times.collect {|i| value.column_size.times.collect {|j| value[i,j]}}.flatten
593
+ eval "#{name}=matrix(c(#{values.join(',')}), #{value.row_size}, #{value.column_size}, TRUE)"
594
+ return original_value
595
+ end
596
+
495
597
  if value.kind_of?(String)
496
598
  type = RinRuby_Type_String
497
599
  length = 1
@@ -536,18 +638,8 @@ class RinRuby
536
638
  else
537
639
  raise "Unsupported data type on Ruby's end"
538
640
  end
539
- @writer.puts <<-EOF
540
- #{RinRuby_Type_Variable} <- readBin(#{RinRuby_Socket},integer(),1,endian="big")
541
- #{RinRuby_Length_Variable} <- readBin(#{RinRuby_Socket},integer(),1,endian="big")
542
- if ( #{RinRuby_Type_Variable} == #{RinRuby_Type_Double} ) {
543
- #{name} <- readBin(#{RinRuby_Socket},numeric(),#{RinRuby_Length_Variable},endian="big")
544
- } else if ( #{RinRuby_Type_Variable} == #{RinRuby_Type_Integer} ) {
545
- #{name} <- readBin(#{RinRuby_Socket},integer(),#{RinRuby_Length_Variable},endian="big")
546
- } else if ( #{RinRuby_Type_Variable} == #{RinRuby_Type_String} ) {
547
- #{name} <- readBin(#{RinRuby_Socket},character(),1,endian="big")
548
- } else { }
549
- rm(#{RinRuby_Type_Variable},#{RinRuby_Length_Variable})
550
- EOF
641
+ @writer.puts "#{name} <- rinruby_get_value()"
642
+
551
643
  @socket.write([type,length].pack('NN'))
552
644
  if ( type == RinRuby_Type_String )
553
645
  @socket.write(value)
@@ -560,31 +652,9 @@ class RinRuby
560
652
 
561
653
  def pull_engine(string)
562
654
  @writer.puts <<-EOF
563
- #{RinRuby_Variable} <- try(#{string})
564
- if ( inherits(#{RinRuby_Variable},"try-error") ) {
565
- writeBin(as.integer(#{RinRuby_Type_NotFound}),#{RinRuby_Socket},endian="big")
566
- } else {
567
- if ( is.double(#{RinRuby_Variable}) ) {
568
- writeBin(as.integer(#{RinRuby_Type_Double}),#{RinRuby_Socket},endian="big")
569
- writeBin(as.integer(length(#{RinRuby_Variable})),#{RinRuby_Socket},endian="big")
570
- writeBin(#{RinRuby_Variable},#{RinRuby_Socket},endian="big")
571
- } else if ( is.integer(#{RinRuby_Variable}) ) {
572
- writeBin(as.integer(#{RinRuby_Type_Integer}),#{RinRuby_Socket},endian="big")
573
- writeBin(as.integer(length(#{RinRuby_Variable})),#{RinRuby_Socket},endian="big")
574
- writeBin(#{RinRuby_Variable},#{RinRuby_Socket},endian="big")
575
- } else if ( is.character(#{RinRuby_Variable}) && ( length(#{RinRuby_Variable}) == 1 ) ) {
576
- writeBin(as.integer(#{RinRuby_Type_String}),#{RinRuby_Socket},endian="big")
577
- writeBin(as.integer(nchar(#{RinRuby_Variable})),#{RinRuby_Socket},endian="big")
578
- writeBin(#{RinRuby_Variable},#{RinRuby_Socket},endian="big")
579
- } else if ( is.character(#{RinRuby_Variable}) && ( length(#{RinRuby_Variable}) > 1 ) ) {
580
- writeBin(as.integer(#{RinRuby_Type_String_Array}),#{RinRuby_Socket},endian="big")
581
- writeBin(as.integer(length(#{RinRuby_Variable})),#{RinRuby_Socket},endian="big")
582
- } else {
583
- writeBin(as.integer(#{RinRuby_Type_Unknown}),#{RinRuby_Socket},endian="big")
584
- }
585
- }
586
- rm(#{RinRuby_Variable})
655
+ rinruby_pull(try(#{string}))
587
656
  EOF
657
+
588
658
  buffer = ""
589
659
  @socket.read(4,buffer)
590
660
  type = to_signed_int(buffer.unpack('N')[0].to_i)
@@ -596,6 +666,7 @@ class RinRuby
596
666
  end
597
667
  @socket.read(4,buffer)
598
668
  length = to_signed_int(buffer.unpack('N')[0].to_i)
669
+
599
670
  if ( type == RinRuby_Type_Double )
600
671
  @socket.read(8*length,buffer)
601
672
  result = buffer.unpack('G'*length)
@@ -612,6 +683,18 @@ class RinRuby
612
683
  for index in 0...length
613
684
  result[index] = pull "#{string}[#{index+1}]"
614
685
  end
686
+ elsif (type == RinRuby_Type_Matrix)
687
+ rows=length
688
+ @socket.read(4,buffer)
689
+ cols = to_signed_int(buffer.unpack('N')[0].to_i)
690
+ elements=pull "as.vector(#{string})"
691
+ index=0
692
+ result=Matrix.rows(rows.times.collect {|i|
693
+ cols.times.collect {|j|
694
+ elements[(j*rows)+i]
695
+ }
696
+ })
697
+ def result.length; 2;end
615
698
  else
616
699
  raise "Unsupported data type on Ruby's end"
617
700
  end
@@ -619,22 +702,32 @@ class RinRuby
619
702
  end
620
703
 
621
704
  def complete?(string)
622
- assign_engine(RinRuby_Parse_String,string)
705
+ assign_engine(RinRuby_Parse_String, string)
706
+ @writer.puts "rinruby_parseable(#{RinRuby_Parse_String})"
707
+ buffer=""
708
+ @socket.read(4,buffer)
709
+ @writer.puts "rm(#{RinRuby_Parse_String})"
710
+ result = to_signed_int(buffer.unpack('N')[0].to_i)
711
+ return result==-1 ? false : true
712
+
713
+ =begin
714
+
623
715
  result = pull_engine("unlist(lapply(c('.*','^Error in parse.*','^Error in parse.*unexpected end of input.*'),
624
716
  grep,try({parse(text=#{RinRuby_Parse_String}); 1}, silent=TRUE)))")
625
- @writer.puts "rm(#{RinRuby_Parse_String})"
717
+
626
718
  return true if result.length == 1
627
719
  return false if result.length == 3
628
- raise "Parse error"
720
+ raise ParseError, "Parse error"
721
+ =end
629
722
  end
630
-
723
+ public :complete?
631
724
  def assignable?(string)
632
- raise "Parse error" if ! complete?(string)
725
+ raise ParseError, "Parse error" if ! complete?(string)
633
726
  assign_engine(RinRuby_Parse_String,string)
634
727
  result = pull_engine("as.integer(ifelse(inherits(try({eval(parse(text=paste(#{RinRuby_Parse_String},'<- 1')))}, silent=TRUE),'try-error'),1,0))")
635
728
  @writer.puts "rm(#{RinRuby_Parse_String})"
636
729
  return true if result == [0]
637
- raise "Parse error"
730
+ raise ParseError, "Parse error"
638
731
  end
639
732
 
640
733
  def find_R_on_windows(cygwin)
@@ -0,0 +1,105 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe RinRuby do
4
+ before do
5
+ R.echo(false)
6
+ end
7
+ subject {R}
8
+ context "basic methods" do
9
+ it {should respond_to :eval}
10
+ it {should respond_to :quit}
11
+ it {should respond_to :assign}
12
+ it {should respond_to :pull}
13
+ it {should respond_to :quit}
14
+ it {should respond_to :echo}
15
+ it "return correct values for complete?" do
16
+ R.eval("x<-1").should be_true
17
+ end
18
+ it "return false for complete? for incorrect expressions" do
19
+ R.complete?("x<-").should be_false
20
+ end
21
+ it "correct eval should return true" do
22
+ R.complete?("x<-1").should be_true
23
+ end
24
+ it "incorrect eval should raise an ParseError" do
25
+ lambda {R.eval("x<-")}.should raise_error(RinRuby::ParseError)
26
+ end
27
+ end
28
+ context "on assing" do
29
+ it "should assign correctly" do
30
+ x=rand
31
+ R.assign("x",x)
32
+ R.pull("x").should==x
33
+ end
34
+ it "should be the same using assign than R#= methods" do
35
+ x=rand
36
+ R.assign("x1",x)
37
+ R.x2=x
38
+ R.pull("x1").should==x
39
+ R.pull("x2").should==x
40
+ end
41
+ it "should raise an ArgumentError error on setter with 0 parameters" do
42
+ lambda {R.unknown_method=() }.should raise_error(ArgumentError)
43
+ end
44
+
45
+ end
46
+ context "on pull" do
47
+ it "should be the same using pull than R# methods" do
48
+ x=rand
49
+ R.x=x
50
+ R.pull("x").should==x
51
+ R.x.should==x
52
+ end
53
+ it "should raise an NoMethod error on getter with 1 or more parameters" do
54
+ lambda {R.unknown_method(1) }.should raise_error(NoMethodError)
55
+ end
56
+
57
+ it "should pull a String" do
58
+ R.eval("x<-'Value'")
59
+ R.pull('x').should=='Value'
60
+ end
61
+ it "should pull an Integer" do
62
+ R.eval("x<-1")
63
+ R.pull('x').should==1
64
+ end
65
+ it "should pull a Float" do
66
+ R.eval("x<-1.5")
67
+ R.pull('x').should==1.5
68
+ end
69
+ it "should pull an Array of Numeric" do
70
+ R.eval("x<-c(1,2.5,3)")
71
+ R.pull('x').should==[1,2.5,3]
72
+ end
73
+ it "should pull an Array of strings" do
74
+ R.eval("x<-c('a','b')")
75
+ R.pull('x').should==['a','b']
76
+ end
77
+
78
+ it "should push a Matrix" do
79
+ matrix=Matrix[[rand,rand,rand],[rand,rand,rand]]
80
+ lambda {R.assign('x',matrix)}.should_not raise_error
81
+ rx=R.x
82
+ matrix.row_size.times {|i|
83
+ matrix.column_size.times {|j|
84
+ matrix[i,j].should be_close(rx[i,j],1e-10)
85
+ }
86
+ }
87
+ end
88
+
89
+ end
90
+
91
+ context "on quit" do
92
+ before(:each) do
93
+ @r=RinRuby.new(false)
94
+ end
95
+ it "return true" do
96
+ @r.quit.should be_true
97
+ end
98
+ it "returns an error if used again" do
99
+ @r.quit
100
+ lambda {@r.eval("x=1")}.should raise_error(RinRuby::EngineClosed)
101
+ end
102
+ end
103
+
104
+
105
+ end