rinruby 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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