Skip to content

Commit e1409e0

Browse files
committed
Merge pull request #9 from sbromberger/newparser
Newparser
2 parents 3e1dfb9 + 679e7a1 commit e1409e0

File tree

6 files changed

+75
-76
lines changed

6 files changed

+75
-76
lines changed

REQUIRE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Compat 0.4

src/Redis.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
module Redis
2+
using Compat
3+
4+
import Base.get, Base.TcpSocket
25

36
export RedisException, ConnectionException, ServerException, ProtocolException, ClientException
47
export RedisConnection, SentinelConnection, TransactionConnection, SubscriptionConnection,
@@ -51,9 +54,9 @@ export sentinel_masters, sentinel_master, sentinel_slaves, sentinel_getmasteradd
5154
sentinel_reset, sentinel_failover, sentinel_monitor, sentinel_remove, sentinel_set
5255

5356
include("exceptions.jl")
54-
include("parser.jl")
5557
include("connection.jl")
5658
include("client.jl")
59+
include("parser.jl")
5760
include("commands.jl")
5861

5962
end

src/client.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function subscription_loop(conn::SubscriptionConnection, err_callback::Function)
4646
# socket will block until a message is received
4747
sleep(.1)
4848
nb_available(conn.socket) > 0 || continue
49-
reply = parse_reply(readavailable(conn.socket))
49+
l = getline(conn.socket)
50+
reply = parseline(l, conn.socket)
5051
message = SubscriptionMessage(reply)
5152
if message.message_type == SubscriptionMessageType.Message
5253
conn.callbacks[message.channel](message.message)
@@ -62,6 +63,8 @@ end
6263
macro redisfunction(command, ret_type, args...)
6364
func_name = esc(symbol(command))
6465
command = split(command, '_')
66+
67+
6568
if length(args) > 0
6669
return quote
6770
function $(func_name)(conn::RedisConnection, $(args...))

src/connection.jl

-7
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,3 @@ end
9797
function send_command(conn::RedisConnectionBase, command::String)
9898
write(conn.socket, command)
9999
end
100-
101-
function execute_command(conn::RedisConnectionBase, command)
102-
is_connected(conn) || throw(ConnectionException("Socket is disconnected"))
103-
send_command(conn, pack_command(command))
104-
reply = parse_reply(readavailable(conn.socket))
105-
reply.response
106-
end

src/parser.jl

+65-66
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,69 @@
1-
const CRLF = "\r\n"
2-
3-
immutable ParsedReply
4-
response # The type of the response is determined by the server (Int, String, Array)
5-
reply_length::Integer
1+
function getline(s::TcpSocket)
2+
l = chomp(readline(s))
3+
length(l) > 1 || throw(ProtocolException("Invalid response received: $l"))
4+
return l
65
end
76

8-
function parse_reply(reply)
9-
first_crlf = search(reply, CRLF)
10-
length(reply) >= 3 || throw(ProtocolException(reply))
11-
first_crlf.start > 0 || throw(ProtocolException(reply))
12-
reply_type = reply[1]
13-
if reply_type == '+'
14-
parse_simple_string_reply(reply, first_crlf)
15-
elseif reply_type == '-'
16-
parse_error_reply(reply, first_crlf)
17-
elseif reply_type == ':'
18-
parse_integer_reply(reply, first_crlf)
19-
elseif reply_type == '\$'
20-
parse_bulk_reply(reply, first_crlf)
21-
elseif reply_type == '*'
22-
parse_array_reply(reply, first_crlf)
23-
else
24-
throw(ProtocolException(reply))
25-
end
7+
function parse_simple_string(l::AbstractString)
8+
return l
269
end
2710

28-
# Simple strings replys extend to the first encountered CRLF
29-
function parse_simple_string_reply(reply, first_crlf)
30-
ParsedReply(reply[2:first_crlf.start-1], first_crlf.stop)
11+
function parse_error(l::AbstractString)
12+
throw(ServerException(l))
3113
end
3214

33-
# Errors are the same as simple strings, except that their first token specifies
34-
# the error type
35-
function parse_error_reply(reply, first_crlf)
36-
first_space = search(reply, ' ')
37-
first_space > 0 || throw(ProtocolException(reply))
38-
throw(ServerException(reply[2:first_space-1], reply[first_space+1:first_crlf.start-1]))
15+
function parse_integer(l)
16+
return parse(Int, l)
3917
end
4018

41-
# Integer replies are just ints followed by CRLF
42-
function parse_integer_reply(reply, first_crlf)
43-
try
44-
ParsedReply(parseint(reply[2:first_crlf.start-1]), first_crlf.stop)
45-
catch
46-
throw(ProtocolException(reply))
19+
function parse_bulk_string(s::TcpSocket, len::Int)
20+
b = readbytes(s, len+2) # add crlf
21+
if length(b) != len + 2
22+
throw(ProtocolException(
23+
"Bulk string read error: expected $len bytes; received $(length(b))"
24+
))
25+
else
26+
return join(@compat map(Char,b[1:end-2]))
4727
end
4828
end
4929

50-
# Bulk replies specify their length and then the binary-safe string
51-
function parse_bulk_reply(reply, first_crlf)
52-
try
53-
bulk_length = parseint(reply[2:first_crlf.start-1])
54-
bulk_length == -1 && return ParsedReply(nothing, first_crlf.stop)
55-
reply_end = first_crlf.stop+bulk_length
56-
ParsedReply(reply[first_crlf.stop+1:reply_end], reply_end+2)
57-
catch
58-
throw(ProtocolException(reply))
30+
function parse_integer(l::AbstractString)
31+
return parse(Int, l)
32+
end
33+
34+
function parse_array(s::TcpSocket, n::Int)
35+
a = Any[]
36+
for i = 1:n
37+
l = getline(s)
38+
r = parseline(l, s)
39+
push!(a, r)
5940
end
41+
return a
6042
end
6143

62-
# Array replies specify the number of elements and then other reply types
63-
# for each item in length
64-
function parse_array_reply(reply, first_crlf)
65-
try
66-
array_length = parseint(reply[2:first_crlf.start-1])
67-
array_length == -1 && return ParsedReply(nothing, first_crlf.stop)
68-
reply = reply[first_crlf.stop+1:end]
69-
reply_length = first_crlf.stop
70-
response = Any[]
71-
for i=1:array_length
72-
parsed_element = parse_reply(reply)
73-
push!(response, parsed_element.response)
74-
reply_length += parsed_element.reply_length
75-
reply = reply[parsed_element.reply_length+1:end]
44+
function parseline(l::AbstractString, s::TcpSocket)
45+
reply_type = l[1]
46+
reply_token = l[2:end]
47+
if reply_type == '+'
48+
parse_simple_string(reply_token)
49+
elseif reply_type == '-'
50+
parse_error(reply_token)
51+
elseif reply_type == ':'
52+
parse_integer(reply_token)
53+
elseif reply_type == '$'
54+
len = parse_integer(reply_token)
55+
if len == -1
56+
return nothing
57+
else
58+
parse_bulk_string(s, len)
59+
end
60+
elseif reply_type == '*'
61+
len = parse_integer(reply_token)
62+
if len == -1
63+
return nothing
64+
else
65+
parse_array(s, len)
7666
end
77-
ParsedReply(response, reply_length)
78-
catch
79-
throw(ProtocolException(reply))
8067
end
8168
end
8269

@@ -89,6 +76,18 @@ function pack_command(command)
8976
packed_command
9077
end
9178

79+
80+
81+
function execute_command(conn::RedisConnectionBase, command)
82+
is_connected(conn) || throw(ConnectionException("Socket is disconnected"))
83+
send_command(conn, pack_command(command))
84+
l = getline(conn.socket)
85+
reply = parseline(l, conn.socket)
86+
return reply
87+
end
88+
89+
90+
9291
baremodule SubscriptionMessageType
9392
const Message = 0
9493
const Pmessage = 1
@@ -100,8 +99,8 @@ immutable SubscriptionMessage
10099
channel::String
101100
message::String
102101

103-
function SubscriptionMessage(reply)
104-
notification = reply.response
102+
function SubscriptionMessage(reply::AbstractArray)
103+
notification = reply
105104
message_type = notification[1]
106105
if message_type == "message"
107106
new(SubscriptionMessageType.Message, notification[2], notification[3])

test/redis_tests.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ flushall(conn)
1111
@test set(conn, "testkey", "testvalue")
1212
@test get(conn, "testkey") == "testvalue"
1313
@test exists(conn, "testkey")
14-
@test keys(conn, "*") == Set({"testkey"})
14+
@test Redis.keys(conn, "*") == Set({"testkey"})
1515
@test del(conn, "testkey", "nothing", "noway") == 1
1616
@test get(conn, "testkey") == nothing
1717

0 commit comments

Comments
 (0)