rootapp-rinruby 3.0.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/lib/rinruby.rb ADDED
@@ -0,0 +1,739 @@
1
+ #=RinRuby: Accessing the R[http://www.r-project.org] interpreter from pure Ruby
2
+ #
3
+ # RinRuby is a Ruby library that integrates the R interpreter in Ruby, making
4
+ # R's statistical routines and graphics available within Ruby. The library
5
+ # consists of a single Ruby script that is simple to install and does not
6
+ # require any special compilation or installation of R. Since the library is
7
+ # 100% pure Ruby, it works on a variety of operating systems, Ruby
8
+ # implementations, and versions of R. RinRuby's methods are simple, making for
9
+ # readable code. The {website [rinruby.ddahl.org]}[http://rinruby.ddahl.org]
10
+ # describes RinRuby usage, provides comprehensive documentation, gives several
11
+ # examples, and discusses RinRuby's implementation.
12
+ #
13
+ # Below is a simple example of RinRuby usage for simple linear regression. The
14
+ # simulation parameters are defined in Ruby, computations are performed in R,
15
+ # and Ruby reports the results. In a more elaborate application, the simulation
16
+ # parameter might come from input from a graphical user interface, the
17
+ # statistical analysis might be more involved, and the results might be an HTML
18
+ # page or PDF report.
19
+ #
20
+ # <b>Code</b>:
21
+ #
22
+ # require "rinruby"
23
+ # n = 10
24
+ # beta_0 = 1
25
+ # beta_1 = 0.25
26
+ # alpha = 0.05
27
+ # seed = 23423
28
+ # R.x = (1..n).entries
29
+ # R.eval <<EOF
30
+ # set.seed(#{seed})
31
+ # y <- #{beta_0} + #{beta_1}*x + rnorm(#{n})
32
+ # fit <- lm( y ~ x )
33
+ # est <- round(coef(fit),3)
34
+ # pvalue <- summary(fit)$coefficients[2,4]
35
+ # EOF
36
+ # puts "E(y|x) ~= #{R.est[0]} + #{R.est[1]} * x"
37
+ # if R.pvalue < alpha
38
+ # puts "Reject the null hypothesis and conclude that x and y are related."
39
+ # else
40
+ # puts "There is insufficient evidence to conclude that x and y are related."
41
+ # end
42
+ #
43
+ # <b>Output</b>:
44
+ #
45
+ # E(y|x) ~= 1.264 + 0.273 * x
46
+ # Reject the null hypothesis and conclude that x and y are related.
47
+ #
48
+ # Coded by:: David B. Dahl
49
+ # Documented by:: David B. Dahl & Scott Crawford
50
+ # Maintained by:: Claudio Bustos
51
+ # Copyright:: 2005-2009
52
+ # Web page:: http://rinruby.ddahl.org
53
+ # E-mail:: mailto:rinruby@ddahl.org
54
+ # License:: GNU Lesser General Public License (LGPL), version 3 or later
55
+ #
56
+ #--
57
+ # This program is free software: you can redistribute it and/or modify
58
+ # it under the terms of the GNU Lesser General Public License as published by
59
+ # the Free Software Foundation, either version 3 of the License, or
60
+ # (at your option) any later version.
61
+ #
62
+ # This program is distributed in the hope that it will be useful,
63
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
64
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
65
+ # GNU General Public License for more details.
66
+ #
67
+ # You should have received a copy of the GNU General Public License
68
+ # along with this program. If not, see <http://www.gnu.org/licenses/>
69
+ #++
70
+ require 'matrix'
71
+ require 'socket'
72
+ require 'rinruby/version'
73
+
74
+ class RinRuby
75
+
76
+ attr_accessor :echo_enabled
77
+ attr_reader :executable
78
+ attr_reader :port_number
79
+ attr_reader :port_width
80
+ attr_reader :hostname
81
+
82
+ # Exception for closed engine
83
+ EngineClosed = Class.new(Exception)
84
+
85
+ # Parse error
86
+ ParseError = Class.new(Exception)
87
+
88
+ # Attempt to pull undefined variable
89
+ UndefinedVariableError = Class.new(Exception)
90
+
91
+ # Cannot convert data type to one that can be sent over wire
92
+ UnsupportedTypeError = Class.new(Exception)
93
+
94
+ DEFAULT_OPTIONS = {
95
+ :echo => true,
96
+ :executable => nil,
97
+ :port_number => 38442,
98
+ :port_width => 1000,
99
+ :hostname => '127.0.0.1'
100
+ }.freeze
101
+
102
+ # RinRuby is invoked within a Ruby script (or the interactive "irb" prompt
103
+ # denoted >>) using:
104
+ #
105
+ # >> require "rinruby"
106
+ #
107
+ # The previous statement reads the definition of the RinRuby class into the
108
+ # current Ruby interpreter and creates an instance of the RinRuby class named
109
+ # R. There is a second method for starting an instance of R which allows the
110
+ # user to use any name for the instance, in this case myr:
111
+ #
112
+ # >> require "rinruby"
113
+ # >> myr = RinRuby.new
114
+ # >> myr.eval "rnorm(1)"
115
+ #
116
+ # Any number of independent instances of R can be created in this way.
117
+ #
118
+ # <b>Parameters that can be passed to the new method using a Hash:</b>
119
+ #
120
+ # * :echo: By setting the echo to false, output from R is suppressed,
121
+ # although warnings are still printed. This option can be changed later by
122
+ # using the echo method. The default is true.
123
+ #
124
+ # * :executable: The path of the R executable (which is "R" in Linux and Mac
125
+ # OS X, or "Rterm.exe" in Windows) can be set with the executable argument.
126
+ # The default is nil which makes RinRuby use the registry keys to find the
127
+ # path (on Windows) or use the path defined by $PATH (on Linux and Mac OS X).
128
+ #
129
+ # * :port_number: This is the smallest port number on the local host that
130
+ # could be used to pass data between Ruby and R. The actual port number used
131
+ # depends on port_width.
132
+ #
133
+ # * :port_width: RinRuby will randomly select a uniform number between
134
+ # port_number and port_number + port_width - 1 (inclusive) to pass data
135
+ # between Ruby and R. If the randomly selected port is not available, RinRuby
136
+ # will continue selecting random ports until it finds one that is available.
137
+ # By setting port_width to 1, RinRuby will wait until port_number is
138
+ # available. The default port_width is 1000.
139
+ #
140
+ # It may be desirable to change the parameters to the instance of R, but
141
+ # still call it by the name of R. In that case the old instance of R which
142
+ # was created with the 'require "rinruby"' statement should be closed first
143
+ # using the quit method which is explained below. Unless the previous
144
+ # instance is killed, it will continue to use system resources until exiting
145
+ # Ruby. The following shows an example by changing the parameter echo:
146
+ #
147
+ # >> require "rinruby"
148
+ # >> R.quit
149
+ # >> R = RinRuby.new(false)
150
+ def initialize(*args)
151
+ opts = Hash.new
152
+
153
+ if args.size == 1 and args[0].is_a? Hash
154
+ opts = args[0]
155
+ else
156
+ opts[:echo] = args.shift unless args.size==0
157
+ opts[:executable] = args.shift unless args.size==0
158
+ opts[:port_number] = args.shift unless args.size==0
159
+ opts[:port_width] = args.shift unless args.size==0
160
+ end
161
+
162
+ @opts = DEFAULT_OPTIONS.merge(opts)
163
+ @port_width = @opts[:port_width]
164
+ @executable = @opts[:executable]
165
+ @hostname = @opts[:hostname]
166
+ @echo_enabled = @opts[:echo]
167
+ @echo_stderr = false
168
+ @echo_writer = @opts.fetch(:echo_writer, $stdout)
169
+
170
+ # find available port
171
+ while true
172
+ begin
173
+ @port_number = @opts[:port_number] + rand(port_width)
174
+ @server_socket = TCPServer::new(@hostname, @port_number)
175
+ break
176
+ rescue Errno::EADDRINUSE
177
+ sleep 0.5 if port_width == 1
178
+ end
179
+ end
180
+
181
+ @executable ||= "R"
182
+ cmd = "#{executable} --slave"
183
+
184
+ # spawn R process
185
+ @engine = IO.popen(cmd,"w+")
186
+ @reader = @engine
187
+ @writer = @engine
188
+ raise "Engine closed" if @engine.closed?
189
+
190
+ # connect to the server
191
+ @writer.puts <<-EOF
192
+ #{RinRuby_KeepTrying_Variable} <- TRUE
193
+ while ( #{RinRuby_KeepTrying_Variable} ) {
194
+ #{RinRuby_Socket} <- try(suppressWarnings(socketConnection("#{@hostname}", #{@port_number}, blocking=TRUE, open="rb")),TRUE)
195
+ if ( inherits(#{RinRuby_Socket},"try-error") ) {
196
+ Sys.sleep(0.1)
197
+ } else {
198
+ #{RinRuby_KeepTrying_Variable} <- FALSE
199
+ }
200
+ }
201
+ rm(#{RinRuby_KeepTrying_Variable})
202
+ EOF
203
+ r_rinruby_get_value
204
+ r_rinruby_pull
205
+ r_rinruby_parseable
206
+
207
+ @socket = @server_socket.accept
208
+ end
209
+
210
+ # The quit method will properly close the bridge between Ruby and R, freeing
211
+ # up system resources. This method does not need to be run when a Ruby script
212
+ # ends.
213
+ def quit
214
+ begin
215
+ @writer.puts "q(save='no')"
216
+ # TODO: Verify if read is needed
217
+ @socket.read()
218
+ @engine.close
219
+
220
+ @server_socket.close
221
+ true
222
+ ensure
223
+ @engine.close unless @engine.closed?
224
+ @server_socket.close unless @server_socket.closed?
225
+ end
226
+ end
227
+
228
+ # The eval instance method passes the R commands contained in the supplied
229
+ # string and displays any resulting plots or prints the output. For example:
230
+ #
231
+ # >> sample_size = 10
232
+ # >> R.eval "x <- rnorm(#{sample_size})"
233
+ # >> R.eval "summary(x)"
234
+ # >> R.eval "sd(x)"
235
+ #
236
+ # produces the following:
237
+ #
238
+ # Min. 1st Qu. Median Mean 3rd Qu. Max.
239
+ # -1.88900 -0.84930 -0.45220 -0.49290 -0.06069 0.78160
240
+ # [1] 0.7327981
241
+ #
242
+ # This example used a string substitution to make the argument to first eval
243
+ # method equivalent to x <- rnorm(10). This example used three invocations of
244
+ # the eval method, but a single invoke is possible using a here document:
245
+ #
246
+ # >> R.eval <<EOF
247
+ # x <- rnorm(#{sample_size})
248
+ # summary(x)
249
+ # sd(x)
250
+ # EOF
251
+ #
252
+ # <b>Parameters that can be passed to the eval method</b>
253
+ #
254
+ # * string: The string parameter is the code which is to be passed to R, for
255
+ # example, string = "hist(gamma(1000,5,3))". The string can also span several
256
+ # lines of code by use of a here document, as shown:
257
+ # R.eval <<EOF
258
+ # x<-rgamma(1000,5,3)
259
+ # hist(x)
260
+ # EOF
261
+ #
262
+ # * echo_override: This argument allows one to set the echo behavior for this
263
+ # call only. The default for echo_override is nil, which does not override
264
+ # the current echo behavior.
265
+ def eval(string, echo_override=nil)
266
+ raise EngineClosed if @engine.closed?
267
+ echo_enabled = ( echo_override != nil ) ? echo_override : @echo_enabled
268
+ if complete?(string)
269
+ @writer.puts string
270
+ @writer.puts "warning('#{RinRuby_Stderr_Flag}',immediate.=TRUE)" if @echo_stderr
271
+ @writer.puts "print('#{RinRuby_Eval_Flag}')"
272
+ else
273
+ raise ParseError, "Parse error on eval:#{string}"
274
+ end
275
+ Signal.trap('INT') do
276
+ @writer.print ''
277
+ @reader.gets
278
+ Signal.trap('INT') do
279
+ end
280
+ return true
281
+ end
282
+ found_eval_flag = false
283
+ found_stderr_flag = false
284
+ while true
285
+ echo_eligible = true
286
+ begin
287
+ line = @reader.gets
288
+ rescue
289
+ return false
290
+ end
291
+ if ! line
292
+ return false
293
+ end
294
+ while line.chomp!
295
+ end
296
+ line = line[8..-1] if line[0] == 27 # Delete escape sequence
297
+ if line == "[1] \"#{RinRuby_Eval_Flag}\""
298
+ found_eval_flag = true
299
+ echo_eligible = false
300
+ end
301
+ if line == "Warning: #{RinRuby_Stderr_Flag}"
302
+ found_stderr_flag = true
303
+ echo_eligible = false
304
+ end
305
+ break if found_eval_flag && ( found_stderr_flag == @echo_stderr )
306
+ return false if line == RinRuby_Exit_Flag
307
+ if echo_enabled && echo_eligible
308
+ @echo_writer.puts(line)
309
+ @echo_writer.flush
310
+ end
311
+ end
312
+ Signal.trap('INT') do
313
+ end
314
+ true
315
+ end
316
+
317
+ # Data is copied from Ruby to R using the assign method or a short-hand
318
+ # equivalent. For example:
319
+ #
320
+ # >> names = ["Lisa","Teasha","Aaron","Thomas"]
321
+ # >> R.assign "people", names
322
+ # >> R.eval "sort(people)"
323
+ #
324
+ # produces the following:
325
+ #
326
+ # [1] "Aaron" "Lisa" "Teasha" "Thomas"
327
+ #
328
+ # The short-hand equivalent to the assign method is simply:
329
+ #
330
+ # >> R.people = names
331
+ #
332
+ # Some care is needed when using the short-hand of the assign method since
333
+ # the label (i.e., people in this case) must be a valid method name in Ruby.
334
+ # For example, R.copy.of.names = names will not work, but R.copy_of_names =
335
+ # names is permissible.
336
+ #
337
+ # The assign method supports Ruby variables of type Fixnum (i.e., integer),
338
+ # Bignum (i.e., integer), Float (i.e., double), String, and arrays of one of
339
+ # those three fundamental types. Note that Fixnum or Bignum values that
340
+ # exceed the capacity of R's integers are silently converted to doubles.
341
+ # Data in other formats must be coerced when copying to R.
342
+ #
343
+ # <b>Parameters that can be passed to the assign method:</b>
344
+ #
345
+ # * name: The name of the variable desired in R.
346
+ #
347
+ # * value: The value the R variable should have. The assign method supports
348
+ # Ruby variables of type Fixnum (i.e., integer), Bignum (i.e., integer),
349
+ # Float (i.e., double), String, and arrays of one of those three fundamental
350
+ # types. Note that Fixnum or Bignum values that exceed the capacity of R's
351
+ # integers are silently converted to doubles. Data in other formats must be
352
+ # coerced when copying to R.
353
+ #
354
+ # When assigning an array containing differing types of variables, RinRuby
355
+ # will follow R’s conversion conventions. An array that contains any Strings
356
+ # will result in a character vector in R. If the array does not contain any
357
+ # Strings, but it does contain a Float or a large integer (in absolute
358
+ # value), then the result will be a numeric vector of Doubles in R. If there
359
+ # are only integers that are suffciently small (in absolute value), then the
360
+ # result will be a numeric vector of integers in R.
361
+ def assign(name, value)
362
+ raise EngineClosed if @engine.closed?
363
+ if assignable?(name)
364
+ assign_engine(name,value)
365
+ else
366
+ raise ParseError, "Parse error"
367
+ end
368
+ end
369
+
370
+ # Data is copied from R to Ruby using the pull method or a short-hand
371
+ # equivalent. The R object x defined with an eval method can be copied to
372
+ # Ruby object copy_of_x as follows:
373
+ #
374
+ # >> R.eval "x <- rnorm(10)"
375
+ # >> copy_of_x = R.pull "x"
376
+ # >> puts copy_of_x
377
+ #
378
+ # which produces the following :
379
+ #
380
+ # -0.376404489256671
381
+ # -1.0759798269397
382
+ # -0.494240140140996
383
+ # 0.131171385795721
384
+ # -0.878328334369391
385
+ # -0.762290423047929
386
+ # -0.410227216105828
387
+ # 0.0445512804225151
388
+ # -1.88887454545995
389
+ # 0.781602719849499
390
+ #
391
+ # The explicit assign method, however, can take an arbitrary R statement. For
392
+ # example:
393
+ #
394
+ # >> summary_of_x = R.pull "as.numeric(summary(x))"
395
+ # >> puts summary_of_x
396
+ #
397
+ # produces the following:
398
+ #
399
+ # -1.889
400
+ # -0.8493
401
+ # -0.4522
402
+ # -0.4929
403
+ # -0.06069
404
+ # 0.7816
405
+ #
406
+ # Notice the use above of the as.numeric function in R. This is necessary
407
+ # since the pull method only supports R vectors which are numeric (i.e.,
408
+ # integers or doubles) and character (i.e., strings). Data in other formats
409
+ # must be coerced when copying to Ruby.
410
+ #
411
+ # <b>Parameters that can be passed to the pull method:</b>
412
+ #
413
+ # * string: The name of the variable that should be pulled from R. The pull
414
+ # method only supports R vectors which are numeric (i.e., integers or
415
+ # doubles) or character (i.e., strings). The R value of NA is pulled as nil
416
+ # into Ruby. Data in other formats must be coerced when copying to Ruby.
417
+ #
418
+ # * singletons: R represents a single number as a vector of length one, but
419
+ # in Ruby it is often more convenient to use a number rather than an array of
420
+ # length one. Setting singleton=false will cause the pull method to shed the
421
+ # array, while singletons=true will return the number of string within an
422
+ # array. The default is false.
423
+ def pull(string, singletons=false)
424
+ raise EngineClosed if @engine.closed?
425
+ if complete?(string)
426
+ result = pull_engine(string)
427
+ if ( ! singletons ) && ( result.length == 1 ) && ( result.class != String )
428
+ result = result[0]
429
+ end
430
+ result
431
+ else
432
+ raise ParseError, "Parse error"
433
+ end
434
+ end
435
+
436
+ # The echo method controls whether the eval method displays output from R
437
+ # and, if echo is enabled, whether messages, warnings, and errors from stderr
438
+ # are also displayed.
439
+ #
440
+ # <b>Parameters that can be passed to the eval method</b>
441
+ #
442
+ # * enable: Setting enable to false will turn all output off until the echo
443
+ # command is used again with enable equal to true. The default is nil, which
444
+ # will return the current setting.
445
+ #
446
+ # * stderr: Setting stderr to true will force messages, warnings, and errors
447
+ # from R to be routed through stdout. Using stderr redirection is typically
448
+ # not needed for the C implementation of Ruby and is thus not not enabled by
449
+ # default for this implementation. It is typically necessary for jRuby and
450
+ # is enabled by default in this case. This redirection works well in
451
+ # practice but it can lead to interleaving output which may confuse RinRuby.
452
+ # In such cases, stderr redirection should not be used. Echoing must be
453
+ # enabled when using stderr redirection.
454
+ def echo(enable=nil,stderr=nil)
455
+ if ( enable == false ) && ( stderr == true )
456
+ raise "You can only redirect stderr if you are echoing is enabled."
457
+ end
458
+ if ( enable != nil ) && ( enable != @echo_enabled )
459
+ echo(nil,false) if ! enable
460
+ @echo_enabled = ! @echo_enabled
461
+ end
462
+ if @echo_enabled && ( stderr != nil ) && ( stderr != @echo_stderr )
463
+ @echo_stderr = ! @echo_stderr
464
+ if @echo_stderr
465
+ eval "sink(stdout(),type='message')"
466
+ else
467
+ eval "sink(type='message')"
468
+ end
469
+ end
470
+ [ @echo_enabled, @echo_stderr ]
471
+ end
472
+
473
+ # Captures the stdout from R for the duration of the block
474
+ # Usage:
475
+ # output = r.capture do
476
+ # r.eval "1 + 1"
477
+ # end
478
+ def capture(&_block)
479
+ old_echo_enabled, old_echo_writer = @echo_enabled, @echo_writer
480
+ @echo_enabled = true
481
+ @echo_writer = StringIO.new
482
+
483
+ yield
484
+
485
+ @echo_writer.rewind
486
+ @echo_writer.read
487
+ ensure
488
+ @echo_enabled = old_echo_enabled
489
+ @echo_writer = old_echo_writer
490
+ end
491
+
492
+ private
493
+
494
+ #:stopdoc:
495
+ RinRuby_Type_NotFound = -2
496
+ RinRuby_Type_Unknown = -1
497
+ RinRuby_Type_Double = 0
498
+ RinRuby_Type_Integer = 1
499
+ RinRuby_Type_String = 2
500
+ RinRuby_Type_String_Array = 3
501
+ RinRuby_Type_Matrix = 4
502
+
503
+ RinRuby_KeepTrying_Variable = ".RINRUBY.KEEPTRYING.VARIABLE"
504
+ RinRuby_Length_Variable = ".RINRUBY.PULL.LENGTH.VARIABLE"
505
+ RinRuby_Type_Variable = ".RINRUBY.PULL.TYPE.VARIABLE"
506
+ RinRuby_Socket = ".RINRUBY.PULL.SOCKET"
507
+ RinRuby_Variable = ".RINRUBY.PULL.VARIABLE"
508
+ RinRuby_Parse_String = ".RINRUBY.PARSE.STRING"
509
+ RinRuby_Eval_Flag = "RINRUBY.EVAL.FLAG"
510
+ RinRuby_Stderr_Flag = "RINRUBY.STDERR.FLAG"
511
+ RinRuby_Exit_Flag = "RINRUBY.EXIT.FLAG"
512
+ RinRuby_Max_Unsigned_Integer = 2**32
513
+ RinRuby_Half_Max_Unsigned_Integer = 2**31
514
+ RinRuby_NA_R_Integer = 2**31
515
+ RinRuby_Max_R_Integer = 2**31-1
516
+ RinRuby_Min_R_Integer = -2**31+1
517
+ #:startdoc:
518
+
519
+
520
+ def r_rinruby_parseable
521
+ @writer.puts <<-EOF
522
+ rinruby_parseable<-function(var) {
523
+ result=try(parse(text=var),TRUE)
524
+ if(inherits(result, "try-error")) {
525
+ writeBin(as.integer(-1),#{RinRuby_Socket}, endian="big")
526
+ } else {
527
+ writeBin(as.integer(1),#{RinRuby_Socket}, endian="big")
528
+ }
529
+ }
530
+ EOF
531
+ end
532
+
533
+ # Create function on ruby to get values
534
+ def r_rinruby_get_value
535
+ @writer.puts <<-EOF
536
+ rinruby_get_value <-function() {
537
+ value <- NULL
538
+ type <- readBin(#{RinRuby_Socket}, integer(), 1, endian="big")
539
+ length <- readBin(#{RinRuby_Socket},integer(),1,endian="big")
540
+ if ( type == #{RinRuby_Type_Double} ) {
541
+ value <- readBin(#{RinRuby_Socket},numeric(), length,endian="big")
542
+ } else if ( type == #{RinRuby_Type_Integer} ) {
543
+ value <- readBin(#{RinRuby_Socket},integer(), length, endian="big")
544
+ } else if ( type == #{RinRuby_Type_String} ) {
545
+ value <- readBin(#{RinRuby_Socket},character(),1,endian="big")
546
+ } else {
547
+ value <-NULL
548
+ }
549
+ value
550
+ }
551
+ EOF
552
+ end
553
+
554
+ def r_rinruby_pull
555
+ @writer.puts <<-EOF
556
+ rinruby_pull <- function(var)
557
+ {
558
+ if (inherits(var, "try-error")) {
559
+ writeBin(as.integer(#{RinRuby_Type_NotFound}),#{RinRuby_Socket},endian="big")
560
+ } else {
561
+ if (is.matrix(var)) {
562
+ writeBin(as.integer(#{RinRuby_Type_Matrix}),#{RinRuby_Socket},endian="big")
563
+ writeBin(as.integer(dim(var)[1]),#{RinRuby_Socket},endian="big")
564
+ writeBin(as.integer(dim(var)[2]),#{RinRuby_Socket},endian="big")
565
+ } else if (is.double(var)) {
566
+ writeBin(as.integer(#{RinRuby_Type_Double}),#{RinRuby_Socket},endian="big")
567
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
568
+ writeBin(var,#{RinRuby_Socket},endian="big")
569
+ } else if (is.integer(var)) {
570
+ writeBin(as.integer(#{RinRuby_Type_Integer}),#{RinRuby_Socket},endian="big")
571
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
572
+ writeBin(var,#{RinRuby_Socket},endian="big")
573
+ } else if (is.character(var) && (length(var) == 1)) {
574
+ writeBin(as.integer(#{RinRuby_Type_String}),#{RinRuby_Socket},endian="big")
575
+ writeBin(as.integer(nchar(var)),#{RinRuby_Socket},endian="big")
576
+ writeBin(var,#{RinRuby_Socket},endian="big")
577
+ } else if ( is.character(var) && (length(var) > 1)) {
578
+ writeBin(as.integer(#{RinRuby_Type_String_Array}),#{RinRuby_Socket},endian="big")
579
+ writeBin(as.integer(length(var)),#{RinRuby_Socket},endian="big")
580
+ } else {
581
+ unknownType = paste(class(var), typeof(var), " ")
582
+ writeBin(as.integer(#{RinRuby_Type_Unknown}),#{RinRuby_Socket},endian="big")
583
+ writeBin(as.integer(nchar(unknownType)),#{RinRuby_Socket},endian="big")
584
+ writeBin(unknownType,#{RinRuby_Socket},endian="big")
585
+ }
586
+ }
587
+ }
588
+ EOF
589
+ end
590
+
591
+ def to_signed_int(y)
592
+ if y.kind_of?(Integer)
593
+ ( y > RinRuby_Half_Max_Unsigned_Integer ) ? -(RinRuby_Max_Unsigned_Integer-y) : ( y == RinRuby_NA_R_Integer ? nil : y )
594
+ else
595
+ y.collect { |x| ( x > RinRuby_Half_Max_Unsigned_Integer ) ? -(RinRuby_Max_Unsigned_Integer-x) : ( x == RinRuby_NA_R_Integer ? nil : x ) }
596
+ end
597
+ end
598
+
599
+ def assign_engine(name, value)
600
+ original_value = value
601
+ # Special assign for matrixes
602
+ if value.kind_of?(::Matrix)
603
+ values=value.row_size.times.collect {|i| value.column_size.times.collect {|j| value[i,j]}}.flatten
604
+ eval "#{name}=matrix(c(#{values.join(',')}), #{value.row_size}, #{value.column_size}, TRUE)"
605
+ return original_value
606
+ end
607
+
608
+ if value.kind_of?(String)
609
+ type = RinRuby_Type_String
610
+ length = 1
611
+ elsif value.kind_of?(Integer)
612
+ if ( value >= RinRuby_Min_R_Integer ) && ( value <= RinRuby_Max_R_Integer )
613
+ value = [ value.to_i ]
614
+ type = RinRuby_Type_Integer
615
+ else
616
+ value = [ value.to_f ]
617
+ type = RinRuby_Type_Double
618
+ end
619
+ length = 1
620
+ elsif value.kind_of?(Float)
621
+ value = [ value.to_f ]
622
+ type = RinRuby_Type_Double
623
+ length = 1
624
+ elsif value.kind_of?(Array)
625
+ begin
626
+ if value.any? { |x| x.kind_of?(String) }
627
+ eval "#{name} <- character(#{value.length})"
628
+ for index in 0...value.length
629
+ assign_engine("#{name}[#{index}+1]",value[index])
630
+ end
631
+ return original_value
632
+ elsif value.any? { |x| x.kind_of?(Float) }
633
+ type = RinRuby_Type_Double
634
+ value = value.collect { |x| x.to_f }
635
+ elsif value.all? { |x| x.kind_of?(Integer) }
636
+ if value.all? { |x| ( x >= RinRuby_Min_R_Integer ) && ( x <= RinRuby_Max_R_Integer ) }
637
+ type = RinRuby_Type_Integer
638
+ else
639
+ value = value.collect { |x| x.to_f }
640
+ type = RinRuby_Type_Double
641
+ end
642
+ else
643
+ raise "Unsupported data type on Ruby's end"
644
+ end
645
+ rescue
646
+ raise "Unsupported data type on Ruby's end"
647
+ end
648
+ length = value.length
649
+ else
650
+ raise "Unsupported data type on Ruby's end"
651
+ end
652
+ @writer.puts "#{name} <- rinruby_get_value()"
653
+
654
+ @socket.write([type,length].pack('NN'))
655
+ if ( type == RinRuby_Type_String )
656
+ @socket.write(value)
657
+ @socket.write([0].pack('C')) # zero-terminated strings
658
+ else
659
+ @socket.write(value.pack( ( type==RinRuby_Type_Double ? 'G' : 'N' )*length ))
660
+ end
661
+ original_value
662
+ end
663
+
664
+ def pull_engine(variable)
665
+ @writer.puts <<-EOF
666
+ rinruby_pull(try(#{variable}))
667
+ EOF
668
+
669
+ buffer = ""
670
+ @socket.read(4, buffer)
671
+ type = to_signed_int(buffer.unpack('N')[0].to_i)
672
+ if ( type == RinRuby_Type_Unknown )
673
+ @socket.read(4, buffer)
674
+ length = to_signed_int(buffer.unpack('N')[0].to_i)
675
+ @socket.read(length, buffer)
676
+ result = buffer.dup
677
+ @socket.read(1, buffer) # zero-terminated string
678
+ raise UnsupportedTypeError, "Unsupported R data type '#{result}'"
679
+ end
680
+ if ( type == RinRuby_Type_NotFound )
681
+ raise RinRuby::UndefinedVariableError, "Undefined variable #{variable}"
682
+ end
683
+ @socket.read(4,buffer)
684
+ length = to_signed_int(buffer.unpack('N')[0].to_i)
685
+
686
+ if ( type == RinRuby_Type_Double )
687
+ @socket.read(8*length,buffer)
688
+ result = buffer.unpack('G'*length)
689
+ elsif ( type == RinRuby_Type_Integer )
690
+ @socket.read(4*length,buffer)
691
+ result = to_signed_int(buffer.unpack('N'*length))
692
+ elsif ( type == RinRuby_Type_String )
693
+ @socket.read(length,buffer)
694
+ result = buffer.dup
695
+ @socket.read(1,buffer) # zero-terminated string
696
+ result
697
+ elsif ( type == RinRuby_Type_String_Array )
698
+ result = Array.new(length,'')
699
+ for index in 0...length
700
+ result[index] = pull "#{variable}[#{index+1}]"
701
+ end
702
+ elsif (type == RinRuby_Type_Matrix)
703
+ rows=length
704
+ @socket.read(4,buffer)
705
+ cols = to_signed_int(buffer.unpack('N')[0].to_i)
706
+ elements=pull "as.vector(#{variable})"
707
+ index=0
708
+ result=Matrix.rows(rows.times.collect {|i|
709
+ cols.times.collect {|j|
710
+ elements[(j*rows)+i]
711
+ }
712
+ })
713
+ def result.length; 2;end
714
+ else
715
+ raise "Unsupported data type on Ruby's end"
716
+ end
717
+ result
718
+ end
719
+
720
+ def complete?(string)
721
+ assign_engine(RinRuby_Parse_String, string)
722
+ @writer.puts "rinruby_parseable(#{RinRuby_Parse_String})"
723
+ buffer=""
724
+ @socket.read(4,buffer)
725
+ @writer.puts "rm(#{RinRuby_Parse_String})"
726
+ result = to_signed_int(buffer.unpack('N')[0].to_i)
727
+ return result==-1 ? false : true
728
+ end
729
+ public :complete?
730
+
731
+ def assignable?(string)
732
+ raise ParseError, "Parse error" if ! complete?(string)
733
+ assign_engine(RinRuby_Parse_String,string)
734
+ result = pull_engine("as.integer(ifelse(inherits(try({eval(parse(text=paste(#{RinRuby_Parse_String},'<- 1')))}, silent=TRUE),'try-error'),1,0))")
735
+ @writer.puts "rm(#{RinRuby_Parse_String})"
736
+ return true if result == [0]
737
+ raise ParseError, "Parse error"
738
+ end
739
+ end