rserve-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,3 @@
1
+ === 0.1.0 / 2010-05-21
2
+
3
+ * First operational version. Can void_eval and eval on vectors. List needs more work
@@ -0,0 +1,31 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/rserve.rb
6
+ lib/rserve/connection.rb
7
+ lib/rserve/engine.rb
8
+ lib/rserve/packet.rb
9
+ lib/rserve/protocol.rb
10
+ lib/rserve/protocol/rexpfactory.rb
11
+ lib/rserve/rexp.rb
12
+ lib/rserve/rexp/double.rb
13
+ lib/rserve/rexp/genericvector.rb
14
+ lib/rserve/rexp/integer.rb
15
+ lib/rserve/rexp/list.rb
16
+ lib/rserve/rexp/logical.rb
17
+ lib/rserve/rexp/string.rb
18
+ lib/rserve/rexp/symbol.rb
19
+ lib/rserve/rexp/vector.rb
20
+ lib/rserve/rlist.rb
21
+ lib/rserve/talk.rb
22
+ spec/rserve_connection_spec.rb
23
+ spec/rserve_double_spec.rb
24
+ spec/rserve_integer_spec.rb
25
+ spec/rserve_packet_spec.rb
26
+ spec/rserve_protocol_spec.rb
27
+ spec/rserve_rexpfactory_spec.rb
28
+ spec/rserve_spec.rb
29
+ spec/rserve_talk_spec.rb
30
+ spec/spec.opts
31
+ spec/spec_helper.rb
@@ -0,0 +1,50 @@
1
+ = rserve-client
2
+
3
+ * http://github.com/clbustos/Rserve-Ruby-client
4
+
5
+
6
+ == DESCRIPTION:
7
+
8
+ Ruby client for Rserve, a Binary R server (http://www.rforge.net/Rserve/).
9
+
10
+ Follows closely the new Java client API, but maintains all Ruby conventions when possible.
11
+
12
+ Connection to Rserve not yet developed, but between June-July 2010 the system should be operational.
13
+
14
+ == SYNOPSIS:
15
+
16
+ require 'rserve'
17
+ con=Rserve::Connection.new
18
+ con.eval("x<-renorm(1)")
19
+ => #<Rserve::REXP::Double:0x000000011a13c8
20
+ @payload=[(5339785585931699/2251799813685248)],
21
+ @attr=nil>
22
+
23
+ == REQUIREMENTS:
24
+
25
+ * R
26
+ * Rserve
27
+
28
+ == INSTALL:
29
+
30
+ sudo gem install rserve-client
31
+
32
+ == LICENSE:
33
+
34
+ REngine - Java interface to R
35
+ Copyright (C) 2004,5,6,7 Simon Urbanek
36
+ Copyrigth (C) 2010 Claudio Bustos (Ruby version)
37
+
38
+ This library is free software; you can redistribute it and/or
39
+ modify it under the terms of the GNU Lesser General Public
40
+ License as published by the Free Software Foundation; either
41
+ version 2.1 of the License, or (at your option) any later version.
42
+
43
+ This library is distributed in the hope that it will be useful,
44
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
45
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46
+ Lesser General Public License for more details.
47
+
48
+ You should have received a copy of the GNU Lesser General Public
49
+ License along with this library; if not, write to the Free Software
50
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+ $:.unshift(File.dirname(__FILE__)+"/lib")
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'rserve'
6
+ Hoe.plugin :git
7
+ Hoe.spec 'rserve-client' do
8
+ self.testlib=:rspec
9
+ self.version=Rserve::VERSION
10
+ self.rubyforge_name = 'ruby-statsample' # if different than 'rserve'
11
+ self.developer('Claudio Bustos', 'clbustos_AT_gmail.com')
12
+ end
13
+
14
+ # vim: syntax=ruby
@@ -0,0 +1,14 @@
1
+ require 'socket'
2
+
3
+ module Rserve
4
+ VERSION = '0.1.0'
5
+ end
6
+
7
+
8
+ require 'rserve/protocol'
9
+ require 'rserve/packet'
10
+ require 'rserve/talk'
11
+ require 'rserve/rexp'
12
+ require 'rserve/engine'
13
+ require 'rserve/connection'
14
+ require 'rserve/rlist'
@@ -0,0 +1,138 @@
1
+ module Rserve
2
+ class Connection < Rserve::Engine
3
+ include Rserve::Protocol
4
+ ServerNotInstalled=Class.new(Exception)
5
+ IncorrectServer=Class.new(Exception)
6
+ IncorrectServerVersion=Class.new(Exception)
7
+ IncorrectProtocol=Class.new(Exception)
8
+ NotConnected=Class.new(Exception)
9
+ EvalError=Class.new(Exception)
10
+ attr_reader :protocol
11
+ attr_reader :last_error
12
+ attr_reader :connected
13
+ attr_reader :auth_req
14
+ attr_reader :auth_type
15
+ attr_reader :key
16
+ attr_reader :rt
17
+ attr_reader :s
18
+ attr_reader :port
19
+ attr_writer :transfer_charset
20
+ attr_reader :rsrv_version
21
+ AT_plain=0
22
+ AT_crypt=1
23
+
24
+ def host
25
+ @hostname
26
+ end
27
+ def initialize(opts=Hash.new)
28
+ @auth_req=false
29
+ @transfer_charset="UTF-8"
30
+ @auth_type=AT_plain
31
+ @hostname="127.0.0.1"
32
+ @port_number=6311
33
+ @tries=0
34
+ @max_tries=5
35
+ @connected=false
36
+ begin
37
+ #puts "Tryin to connect..."
38
+ connect
39
+ rescue Errno::ECONNREFUSED
40
+ if @tries<@max_tries
41
+ #puts "Init RServe"
42
+ if system "R CMD Rserve"
43
+ #puts "Ok"
44
+ retry
45
+ else
46
+ raise ServerNotInstalled, "Rserve not installed"
47
+ end
48
+ else
49
+ raise
50
+ end
51
+ end
52
+
53
+ end
54
+ def is
55
+ @s
56
+ end
57
+ def os
58
+ @s
59
+ end
60
+ def connect
61
+ close if @connected
62
+ @s = TCPSocket::new(@hostname, @port_number)
63
+ @rt=Rserve::Talk.new(@s)
64
+ #puts "Connected"
65
+ # Accept first input
66
+ input=@s.recv(32).unpack("a4a4a4a20")
67
+ raise IncorrectServer,"Handshake failed: Rsrv signature expected, but received #{input[0]}" unless input[0]=="Rsrv"
68
+ @rsrv_version=input[1].to_i
69
+ raise IncorrectServerVersion, "Handshake failed: The server uses more recent protocol than this client." if @rsrv_version>103
70
+ @protocol=input[2]
71
+ raise IncorrectProtocol, "Handshake failed: unsupported transfer protocol #{@protocol}, I talk only QAP1." if @protocol!="QAP1"
72
+ @extra=input[4]
73
+ @connected=true
74
+ @last_error="OK"
75
+
76
+ end
77
+ def connected?
78
+ @connected
79
+ end
80
+ def close
81
+ @s.shutdown if !@s.nil? and !@s.closed?
82
+ @connected=false
83
+ return true
84
+ end
85
+ def get_server_version
86
+ @rsrv_version
87
+ end
88
+
89
+ # evaluates the given command, but does not fetch the result (useful for assignment operations)
90
+ # * @param cmd command/expression string */
91
+ def void_eval(cmd)
92
+ raise NotConnected if !connected? or rt.nil?
93
+ rp=rt.request(:cmd=>Rserve::Protocol::CMD_voidEval, :cont=>cmd+"\n")
94
+ if !rp.nil? and rp.ok?
95
+ true
96
+ else
97
+ raise EvalError, "voidEval failed: #{rp.to_s}"
98
+ end
99
+
100
+ end
101
+
102
+
103
+ # evaluates the given command and retrieves the result
104
+ # * @param cmd command/expression string
105
+ # * @return R-xpression or <code>null</code> if an error occured */
106
+ def eval(cmd)
107
+ raise NotConnected if !connected? or rt.nil?
108
+ rp=rt.request(:cmd=>Rserve::Protocol::CMD_eval, :cont=>cmd+"\n")
109
+ if !rp.nil? and rp.ok?
110
+ parse_eval_response(rp)
111
+ else
112
+ raise EvalError, "voidEval failed: #{rp.to_s}"
113
+ end
114
+ end
115
+ def parse_eval_response(rp)
116
+ rxo=0
117
+ pc=rp.cont
118
+ if (rsrv_version>100) # /* since 0101 eval responds correctly by using DT_SEXP type/len header which is 4 bytes long */
119
+ rxo=4
120
+ # we should check parameter type (should be DT_SEXP) and fail if it's not
121
+ if (pc[0]!=Rserve::Protocol::DT_SEXP && pc[0]!=(Rserve::Protocol::DT_SEXP|Rserve::Protocol::DT_LARGE))
122
+ raise "Error while processing eval output: SEXP (type "+Rserve::Protocol::DT_SEXP+") expected but found result type "+pc[0].to_s+"."
123
+ end
124
+ if (pc[0]==(Rserve::Protocol::DT_SEXP|Rserve::Protocol::DT_LARGE))
125
+ rxo=8; # large data need skip of 8 bytes
126
+ end
127
+ # warning: we are not checking or using the length - we assume that only the one SEXP is returned. This is true for the current CMD_eval implementation, but may not be in the future. */
128
+ end
129
+ if pc.length>rxo
130
+ rx=REXPFactory.new;
131
+ rx.parse_REXP(pc, rxo);
132
+ return rx.get_REXP();
133
+ else
134
+ return nil
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,40 @@
1
+ module Rserve
2
+ class Engine
3
+ def parse(text,resolve);end
4
+ def evaluate(what, where, resolve); end;
5
+ def assign(symbol, value, env=nil);end;
6
+ def get(symbol, env, resolve);end;
7
+ def resolve_reference(ref);end;
8
+ def create_reference(value);end;
9
+ def finalize_reference(ref);end;
10
+ def get_parent_enviroment(env,resolve);end;
11
+ def new_enviroment(parent,resolve);end;
12
+ def parse_and_eval(text,where=nil,resolve=true)
13
+ p=parse(text,false)
14
+ evaluate(p,where,resolve)
15
+ end
16
+ def close;
17
+ false
18
+ end
19
+ def supports_references?
20
+ false
21
+ end
22
+ def supports_enviroments?
23
+ false
24
+ end
25
+ def supports_REPL?
26
+ false
27
+ end
28
+ def suuports_locking?
29
+ false
30
+ end
31
+ def try_lock
32
+ 0
33
+ end
34
+ def lock
35
+ 0
36
+ end
37
+ def unlock;end;
38
+
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module Rserve
2
+ class Packet
3
+ attr_reader :cont
4
+ attr_reader :cmd
5
+ def initialize(cmd, cont)
6
+ raise "cont [#{cont.class} - #{cont.to_s}] should respond to :length" if !cont.nil? and !cont.respond_to? :length
7
+ @cmd=cmd
8
+ @cont=cont
9
+ end
10
+ def cont_len
11
+ @cont.nil? ? 0 : @cont.length
12
+ end
13
+ def ok?
14
+ @cmd&15==1
15
+ end
16
+ def error?
17
+ @cmd&15==2
18
+ end
19
+ def stat
20
+ (@cmd>>24)&127
21
+ end
22
+ def to_s
23
+ if error?
24
+ status="status=error - #{stat}"
25
+ else
26
+ status="status=ok"
27
+ end
28
+ "Packet[cmd=#{@cmd},len="+((cont.nil?)?"<nil>":(""+cont.length.to_s))+", con='"+(cont.nil? ? "<nil>" : cont.pack("C*"))+"', status=#{status}]"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,183 @@
1
+ module Rserve
2
+ #
3
+ # This module encapsulates methods and constants related to QAP1 protocol used by Rserv. Follows almost exactly the interface on RTalk class
4
+ # on Java version, except for use of undescores instead of CamelCase
5
+ # Implementation could differ if a cleaner/faster ruby version is available
6
+ #
7
+ # Policy: No other class should know about the internal of protocol!
8
+ # See Rtalk class on Java version.
9
+ module Protocol
10
+ # Defines from Rsrv.h
11
+ CMD_RESP=0x010000 # all responses have this flag set
12
+ RESP_OK=(CMD_RESP|0x0001) # command succeeded; returned parameters depend on the command issued
13
+ RESP_ERR=(CMD_RESP|0x0002) # command failed, check stats code attached string may describe the error
14
+
15
+ ERR_auth_failed=0x41 # auth.failed or auth.requested but no login came. in case of authentification failure due to name/pwd mismatch, server may send CMD_accessDenied instead
16
+ ERR_conn_broken=0x42 # connection closed or broken packet killed it */
17
+ ERR_inv_cmd=0x43 # unsupported/invalid command */
18
+ ERR_inv_par=0x44 # some parameters are invalid */
19
+ ERR_Rerror=0x45 # R-error occured, usually followed by connection shutdown */
20
+ ERR_IOerror=0x46 # I/O error */
21
+ ERR_notOpen=0x47 # attempt to perform fileRead/Write on closed file */
22
+ ERR_accessDenied=0x48 # this answer is also valid on CMD_login; otherwise it's sent if the server deosn;t allow the user to issue the specified command. (e.g. some server admins may block file I/O operations for some users)
23
+ ERR_unsupportedCmd=0x49 # unsupported command */
24
+ ERR_unknownCmd=0x4a # unknown command - the difference between unsupported and unknown is that unsupported commands are known to the server but for some reasons (e.g. platform dependent) it's not supported. unknown commands are simply not recognized by the server at all. */
25
+ ERR_data_overflow=0x4b # incoming packet is too big. currently there is a limit as of the size of an incoming packet. */
26
+ ERR_object_too_big=0x4c # the requested object is too big to be transported in that way. If received after CMD_eval then the evaluation itself was successful. optional parameter is the size of the object
27
+ ERR_out_of_mem=0x4d # out of memory. the connection is usually closed after this error was sent
28
+ ERR_ctrl_closed=0x4e # control pipe to the master process is closed or broken
29
+ ERR_session_busy=0x50 # session is still busy */
30
+ ERR_detach_failed=0x51 # unable to detach seesion (cannot determine peer IP or problems creating a listening socket for resume) */
31
+
32
+
33
+ CMD_login=0x001 # "name\npwd" : - */
34
+ CMD_voidEval=0x002 # string : - */
35
+ CMD_eval=0x003 # string : encoded SEXP */
36
+ CMD_shutdown=0x004 # [admin-pwd] : - */
37
+
38
+ #/* file I/O routines. server may answe */
39
+ CMD_openFile=0x010 # fn : - */
40
+ CMD_createFile=0x011 # fn : - */
41
+ CMD_closeFile=0x012 # - : - */
42
+ CMD_readFile=0x013 # [int size] : data... ; if size not present,
43
+ #server is free to choose any value - usually
44
+ #it uses the size of its static buffer */
45
+ CMD_writeFile=0x014 # data : - */
46
+ CMD_removeFile=0x015 # fn : - */
47
+
48
+ # /* object manipulation */
49
+ CMD_setSEXP=0x020 # string(name), REXP : - */
50
+ CMD_assignSEXP=0x021 # string(name), REXP : - ; same as setSEXP except that the name is parsed */
51
+
52
+ # /* session management (since 0.4-0) */
53
+ CMD_detachSession=0x030 # : session key */
54
+ CMD_detachedVoidEval=0x031 # string : session key; doesn't */
55
+ CMD_attachSession=0x032 # session key : - */
56
+
57
+ # control commands (since 0.6-0) - passed on to the master process */
58
+ # Note: currently all control commands are asychronous, i.e. RESP_OK
59
+ # indicates that the command was enqueued in the master pipe, but there
60
+ # is no guarantee that it will be processed. Moreover non-forked
61
+ # connections (e.g. the default debug setup) don't process any
62
+ # control commands until the current client connection is closed so
63
+ # the connection issuing the control command will never see its
64
+ # result.
65
+ CMD_ctrl=0x40 # -- not a command - just a constant -- */
66
+ CMD_ctrlEval=0x42 # string : - */
67
+ CMD_ctrlSource=0x45 # string : - */
68
+ CMD_ctrlShutdown=0x44 # - : - */
69
+
70
+ # /* 'internal' commands (since 0.1-9) */
71
+ CMD_setBufferSize=0x081 # [int sendBufSize] this commad allow clients to request bigger buffer sizes if large data is to be transported from Rserve to the client. (incoming buffer is resized automatically) */
72
+ CMD_setEncoding=0x082 # string (one of "native","latin1","utf8") : -; since 0.5-3 */
73
+
74
+ # /* special commands - the payload of packages with this mask does not contain defined parameters */
75
+
76
+ CMD_SPECIAL_MASK=0xf0
77
+
78
+ CMD_serEval=0xf5 # serialized eval - the packets are raw serialized data without data header */
79
+ CMD_serAssign=0xf6 # serialized assign - serialized list with [[1]]=name, [[2]]=value */
80
+ CMD_serEEval=0xf7 # serialized expression eval - like serEval with one additional evaluation round */
81
+
82
+ # data types for the transport protocol (QAP1)do NOT confuse with XT_.. values.
83
+
84
+ DT_INT=1 # int */
85
+ DT_CHAR=2 # char */
86
+ DT_DOUBLE=3 # double */
87
+ DT_STRING=4 # 0 terminted string */
88
+ DT_BYTESTREAM=5 # stream of bytes (unlike DT_STRING may contain 0) */
89
+ DT_SEXP=10 # encoded SEXP */
90
+ DT_ARRAY=11 # array of objects (i.e. first 4 bytes specify how many subsequent objects are part of the array; 0 is legitimate) */
91
+ DT_LARGE=64 # new in 0102: if this flag is set then the length of the object is coded as 56-bit integer enlarging the header by 4 bytes */
92
+
93
+ # writes bit-wise int to a byte buffer at specified position in Intel-endian form
94
+ # Internal: byte buffer will be the result of unpack("CCCC") an integer.
95
+ # @param v value to be written
96
+ # @param buf buffer
97
+ # @param o offset in the buffer to start at. An int takes always 4 bytes */
98
+ def set_int(v, buf, o)
99
+ buf[o]=(v&255);o+=1
100
+ buf[o]=((v&0xff00)>>8); o+=1
101
+ buf[o]=((v&0xff0000)>>16); o+=1
102
+ buf[o]=((v&0xff000000)>>24);
103
+ end
104
+
105
+ # writes cmd/resp/type byte + 3/7 bytes len into a byte buffer at specified offset.
106
+ # @param ty type/cmd/resp byte
107
+ # @param len length
108
+ # @param buf buffer
109
+ # @param o offset
110
+ # @return offset in buf just after the header. Please note that since Rserve 0.3 the header can be either 4 or 8 bytes long, depending on the len parameter.
111
+ def set_hdr(ty, len, buf, o)
112
+ buf[o]=((ty&255)|((len>0xfffff0) ? DT_LARGE : 0)); o+=1;
113
+ buf[o]=(len&255); o+=1;
114
+ buf[o]=((len&0xff00)>>8); o+=1;
115
+ buf[o]=((len&0xff0000)>>16); o+=1;
116
+ if (len>0xfffff0) # for large data we need to set the next 4 bytes as well
117
+ buf[o]=((len&0xff000000)>>24); o+=1;
118
+ buf[o]=0; o+=1; # since len is int, we get 32-bits only
119
+ buf[o]=0; o+=1;
120
+ buf[o]=0; o+=1;
121
+ end
122
+ o
123
+ end
124
+ # creates a new header according to the type and length of the parameter
125
+ # @param ty type/cmd/resp byte
126
+ # @param len length
127
+ def new_hdr(ty, len)
128
+ hdr=Array.new( (len>0xfffff0)?8:4)
129
+ set_hdr(ty,len,hdr,0);
130
+ hdr
131
+ end
132
+ #
133
+ # converts bit-wise stored int in Intel-endian form into ruby int
134
+ #
135
+ # @param buf buffer containg the representation
136
+ # @param o offset where to start (4 bytes will be used)
137
+ # @return the int value. no bounds checking is done so you need to
138
+ # make sure that the buffer is big enough
139
+
140
+ def get_int(buf, o)
141
+ return buf.slice(o,4).pack("C*").unpack("I")[0]
142
+ #return ((buf[o]&255)|((buf[o+1]&255)<<8)|((buf[o+2]&255)<<16)|((buf[o+3]&255)<<24));
143
+ end
144
+
145
+ # converts bit-wise stored length from a header. "long" format is supported up to 32-bit
146
+ # @param buf buffer
147
+ # @param o offset of the header (length is at o+1)
148
+ # @return length */
149
+ def get_len(buf, o)
150
+ # // "long" format; still - we support 32-bit only
151
+ if ((buf[o]&64)>0)
152
+ (buf[o+1]&255)|((buf[o+2]&255)<<8)|((buf[o+3]&255)<<16)|((buf[o+4]&255)<<24)
153
+ else
154
+ (buf[o+1]&255)|((buf[o+2]&255)<<8)|((buf[o+3]&255)<<16)
155
+ end
156
+ end
157
+
158
+ # converts bit-wise Intel-endian format into long
159
+ # @param buf buffer
160
+ # @param o offset (8 bytes will be used)
161
+ # @return long value */
162
+ def get_long(buf, o)
163
+ low=(get_int(buf,o))&0xffffffff;
164
+ hi=(get_int(buf,o+4))&0xffffffff;
165
+ hi<<=32; hi|=low;
166
+ hi
167
+ end
168
+ def longBitsToDouble(bits)
169
+ s = ((bits >> 63) == 0) ? 1 : -1;
170
+ e = ((bits >> 52) & 0x7ff)
171
+ m = (e == 0) ?
172
+ (bits & 0xfffffffffffff) << 1 :
173
+ (bits & 0xfffffffffffff) | 0x10000000000000;
174
+ s*m*2**(e-1075)
175
+ end
176
+ def set_long(l, buf, o)
177
+ set_int((l&0xffffffff),buf,o)
178
+ set_int((l>>32),buf,o+4)
179
+ end
180
+ end
181
+ end
182
+
183
+ require 'rserve/protocol/rexpfactory'