From 5da463e1f730a284679369fa3049168f3bf3df48 Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Wed, 9 Apr 2014 20:43:45 -0700 Subject: [PATCH 01/10] Redis: optional pipelining and better tested transactions --- lib/pure/redis.nim | 354 ++++++++++++++++++++++++++++++--------------- 1 file changed, 234 insertions(+), 120 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index f4c45b99c3..e4d47e43d2 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -19,11 +19,18 @@ import sockets, os, strutils, parseutils const redisNil* = "\0\0" +type + TPipeline = object + enabled: bool + buffer: ref string + expected: int ## number of replies expected if pipelined + type TRedis* {.pure, final.} = object socket: TSocket connected: bool - + pipeline: ref TPipeline + TRedisStatus* = string TRedisInteger* = biggestInt TRedisString* = string ## Bulk reply @@ -32,15 +39,23 @@ type EInvalidReply* = object of ESynch ## Invalid reply from redis ERedis* = object of ESynch ## Error in redis +proc newPipeline(): ref TPipeLine = + new(result) + result.buffer = new string + result.buffer[] = "" + result.enabled = false + result.expected = 0 + proc open*(host = "localhost", port = 6379.TPort): TRedis = ## Opens a connection to the redis server. result.socket = socket(buffered = false) if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.connect(host, port) + result.pipeline = newPipeline() proc raiseInvalidReply(expected, got: char) = - raise newException(EInvalidReply, + raise newException(EInvalidReply, "Expected '$1' at the beginning of a status reply got '$2'" % [$expected, $got]) @@ -48,9 +63,13 @@ proc raiseNoOK(status: string) = if status != "QUEUED" and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) -proc parseStatus(r: TRedis): TRedisStatus = - var line = "" - r.socket.readLine(line) +proc parseStatus(r: TRedis, lineIn: string = ""): TRedisStatus = + if r.pipeline.enabled: + return "OK" + + var line = lineIn + if line == "": + r.socket.readLine(line) if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -58,12 +77,15 @@ proc parseStatus(r: TRedis): TRedisStatus = raise newException(ERedis, strip(line)) if line[0] != '+': raiseInvalidReply('+', line[0]) - + return line.substr(1) # Strip '+' - -proc parseInteger(r: TRedis): TRedisInteger = - var line = "" - r.socket.readLine(line) + +proc parseInteger(r: TRedis, lineIn: string = ""): TRedisInteger = + if r.pipeline.enabled: return -1 + + var line = lineIn + if line == "": + r.socket.readLine(line) if line == "+QUEUED": # inside of multi return -1 @@ -75,32 +97,31 @@ proc parseInteger(r: TRedis): TRedisInteger = raise newException(ERedis, strip(line)) if line[0] != ':': raiseInvalidReply(':', line[0]) - + # Strip ':' if parseBiggestInt(line, result, 1) == 0: - raise newException(EInvalidReply, "Unable to parse integer.") + raise newException(EInvalidReply, "Unable to parse integer.") proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString if sock.recv(cstring(result), size) != size: raise newException(EInvalidReply, "recv failed") -proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = +proc parseSingleString(r: TRedis, line:string, allowMBNil = False): TRedisString = + if r.pipeline.enabled: return "" + # Error. if line[0] == '-': raise newException(ERedis, strip(line)) - + # Some commands return a /bulk/ value or a /multi-bulk/ nil. Odd. if allowMBNil: if line == "*-1": return RedisNil - - if line == "+QUEUED" or line == "+OK" : # inside of a transaction (multi) - return nil if line[0] != '$': raiseInvalidReply('$', line[0]) - + var numBytes = parseInt(line.substr(1)) if numBytes == -1: return RedisNil @@ -108,41 +129,86 @@ proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = var s = r.socket.recv(numBytes+2) result = strip(s.string) -proc parseMultiLines(r: TRedis, countLine:string): TRedisList = +proc parseNext(r: TRedis): TRedisList + +proc parseArrayLines(r: TRedis, countLine:string): TRedisList = if countLine.string[0] != '*': raiseInvalidReply('*', countLine.string[0]) var numElems = parseInt(countLine.string.substr(1)) if numElems == -1: return nil result = @[] + for i in 1..numElems: - var line = "" - r.socket.readLine(line.TaintedString) - if line[0] == '*': # after exec() may contain more multi-bulk replies - var parsed = r.parseMultiLines(line) + var parsed = r.parseNext() + if not isNil(parsed): for item in parsed: result.add(item) - else: - result.add(r.parseSingle(line)) -proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = - var line = "" - r.socket.readLine(line.TaintedString) +proc parseBulkString(r: TRedis, allowMBNil = False, lineIn:string = ""): TRedisString = + if r.pipeline.enabled: return "" - if line == "+QUEUED" or line == "+OK": # inside of a transaction (multi) - return nil + var line = lineIn + if line == "": + r.socket.readLine(line.TaintedString) - return r.parseSingle(line, allowMBNil) + return r.parseSingleString(line, allowMBNil) -proc parseMultiBulk(r: TRedis): TRedisList = +proc parseArray(r: TRedis): TRedisList = + if r.pipeline.enabled: return @[] var line = TaintedString"" r.socket.readLine(line) - if line == "+QUEUED": # inside of a transaction (multi) - return nil - - return r.parseMultiLines(line) + return r.parseArrayLines(line) +proc parseNext(r: TRedis): TRedisList = + if r.pipeline.enabled: return @[] + var line = TaintedString"" + r.socket.readLine(line) + + var res = case line[0] + of '+': @[r.parseStatus(line)] + of '-': @[r.parseStatus(line)] + of ':': @[$(r.parseInteger(line))] + of '$': @[r.parseBulkString(true,line)] + of '*': r.parseArrayLines(line) + else: + raise newException(EInvalidReply, "parseNext failed on line: " & line) + nil + r.pipeline.expected -= 1 + return res + +proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = + ## Send buffered commands, clear buffer, return results + if r.pipeline.buffer[].len > 0: + r.socket.send(r.pipeline.buffer[]) + r.pipeline.buffer[] = "" + + var prevState = r.pipeline.enabled + r.pipeline.enabled = false + result = @[] + + var tot = r.pipeline.expected + + for i in 0..tot-1: + var ret = r.parseNext() + if ret.len == 1 and (ret[0] == "OK" or ret[0] == "QUEUED"): + # Skip acknowledgement replies in multi + if not wasMulti: result.add(ret) + else: + result.add(ret) + + r.pipeline.expected = 0 + r.pipeline.enabled = prevState + +proc setPipeline*(r: TRedis, state: bool) = + ## Enable or disable command pipelining (reduces network roundtrips). + ## Note that when enabled, you must call flushPipeline to actually send commands, except + ## for multi/exec() which enable and flush the pipeline automatically. + ## Commands return immediately with dummy values; actual results returned from + ## flushPipeline() or exec() + r.pipeline.expected = 0 + r.pipeline.enabled = state proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = var request = "*" & $(1 + args.len()) & "\c\L" @@ -151,7 +217,12 @@ proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.buffer[].add(request) + r.pipeline.expected += 1 + else: + r.socket.send(request) proc sendCommand(r: TRedis, cmd: string, arg1: string, args: varargs[string]) = @@ -163,7 +234,12 @@ proc sendCommand(r: TRedis, cmd: string, arg1: string, for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.expected += 1 + r.pipeline.buffer[].add(request) + else: + r.socket.send(request) # Keys @@ -192,7 +268,7 @@ proc expireAt*(r: TRedis, key: string, timestamp: int): bool = proc keys*(r: TRedis, pattern: string): TRedisList = ## Find all keys matching the given pattern r.sendCommand("KEYS", pattern) - return r.parseMultiBulk() + return r.parseArray() proc move*(r: TRedis, key: string, db: int): bool = ## Move a key to another database. Returns `true` on a successful move. @@ -204,11 +280,11 @@ proc persist*(r: TRedis, key: string): bool = ## Returns `true` when the timeout was removed. r.sendCommand("PERSIST", key) return r.parseInteger() == 1 - + proc randomKey*(r: TRedis): TRedisString = ## Return a random key from the keyspace r.sendCommand("RANDOMKEY") - return r.parseBulk() + return r.parseBulkString() proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## Rename a key. @@ -216,7 +292,7 @@ proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) raiseNoOK(r.parseStatus()) - + proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. ## Returns `true` if key was renamed. @@ -227,12 +303,12 @@ proc ttl*(r: TRedis, key: string): TRedisInteger = ## Get the time to live for a key r.sendCommand("TTL", key) return r.parseInteger() - + proc keyType*(r: TRedis, key: string): TRedisStatus = ## Determine the type stored at key r.sendCommand("TYPE", key) return r.parseStatus() - + # Strings @@ -245,16 +321,16 @@ proc decr*(r: TRedis, key: string): TRedisInteger = ## Decrement the integer value of a key by one r.sendCommand("DECR", key) return r.parseInteger() - + proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger = ## Decrement the integer value of a key by the given number r.sendCommand("DECRBY", key, $decrement) return r.parseInteger() - + proc get*(r: TRedis, key: string): TRedisString = ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. r.sendCommand("GET", key) - return r.parseBulk() + return r.parseBulkString() proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger = ## Returns the bit value at offset in the string value stored at key @@ -264,13 +340,13 @@ proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger = proc getRange*(r: TRedis, key: string, start, stop: int): TRedisString = ## Get a substring of the string stored at a key r.sendCommand("GETRANGE", key, $start, $stop) - return r.parseBulk() + return r.parseBulkString() proc getSet*(r: TRedis, key: string, value: string): TRedisString = ## Set the string value of a key and return its old value. Returns `redisNil` ## when key doesn't exist. r.sendCommand("GETSET", key, value) - return r.parseBulk() + return r.parseBulkString() proc incr*(r: TRedis, key: string): TRedisInteger = ## Increment the integer value of a key by one. @@ -282,7 +358,7 @@ proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger = r.sendCommand("INCRBY", key, $increment) return r.parseInteger() -proc setk*(r: TRedis, key, value: string) = +proc setk*(r: TRedis, key, value: string) = ## Set the string value of a key. ## ## NOTE: This function had to be renamed due to a clash with the `set` type. @@ -295,18 +371,18 @@ proc setNX*(r: TRedis, key, value: string): bool = r.sendCommand("SETNX", key, value) return r.parseInteger() == 1 -proc setBit*(r: TRedis, key: string, offset: int, +proc setBit*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Sets or clears the bit at offset in the string value stored at key r.sendCommand("SETBIT", key, $offset, value) return r.parseInteger() - + proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) raiseNoOK(r.parseStatus()) -proc setRange*(r: TRedis, key: string, offset: int, +proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Overwrite part of a string at key starting at the specified offset r.sendCommand("SETRANGE", key, $offset, value) @@ -332,12 +408,12 @@ proc hExists*(r: TRedis, key, field: string): bool = proc hGet*(r: TRedis, key, field: string): TRedisString = ## Get the value of a hash field r.sendCommand("HGET", key, field) - return r.parseBulk() + return r.parseBulkString() proc hGetAll*(r: TRedis, key: string): TRedisList = ## Get all the fields and values in a hash r.sendCommand("HGETALL", key) - return r.parseMultiBulk() + return r.parseArray() proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger = ## Increment the integer value of a hash field by the given number @@ -347,7 +423,7 @@ proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger = proc hKeys*(r: TRedis, key: string): TRedisList = ## Get all the fields in a hash r.sendCommand("HKEYS", key) - return r.parseMultiBulk() + return r.parseArray() proc hLen*(r: TRedis, key: string): TRedisInteger = ## Get the number of fields in a hash @@ -357,9 +433,9 @@ proc hLen*(r: TRedis, key: string): TRedisInteger = proc hMGet*(r: TRedis, key: string, fields: varargs[string]): TRedisList = ## Get the values of all the given hash fields r.sendCommand("HMGET", key, fields) - return r.parseMultiBulk() + return r.parseArray() -proc hMSet*(r: TRedis, key: string, +proc hMSet*(r: TRedis, key: string, fieldValues: openarray[tuple[field, value: string]]) = ## Set multiple hash fields to multiple values var args = @[key] @@ -373,7 +449,7 @@ proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field r.sendCommand("HSET", key, field, value) return r.parseInteger() - + proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the value of a hash field, only if the field does **not** exist r.sendCommand("HSETNX", key, field, value) @@ -382,8 +458,8 @@ proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = proc hVals*(r: TRedis, key: string): TRedisList = ## Get all the values in a hash r.sendCommand("HVALS", key) - return r.parseMultiBulk() - + return r.parseArray() + # Lists proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = @@ -393,7 +469,7 @@ proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BLPOP", args) - return r.parseMultiBulk() + return r.parseArray() proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = ## Remove and get the *last* element in a list, or block until one @@ -402,7 +478,7 @@ proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BRPOP", args) - return r.parseMultiBulk() + return r.parseArray() proc bRPopLPush*(r: TRedis, source, destination: string, timeout: int): TRedisString = @@ -411,12 +487,12 @@ proc bRPopLPush*(r: TRedis, source, destination: string, ## ## http://redis.io/commands/brpoplpush r.sendCommand("BRPOPLPUSH", source, destination, $timeout) - return r.parseBulk(true) # Multi-Bulk nil allowed. + return r.parseBulkString(true) # Multi-Bulk nil allowed. proc lIndex*(r: TRedis, key: string, index: int): TRedisString = ## Get an element from a list by its index r.sendCommand("LINDEX", key, $index) - return r.parseBulk() + return r.parseBulkString() proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): TRedisInteger = @@ -424,7 +500,7 @@ proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): var pos = if before: "BEFORE" else: "AFTER" r.sendCommand("LINSERT", key, pos, pivot, value) return r.parseInteger() - + proc lLen*(r: TRedis, key: string): TRedisInteger = ## Get the length of a list r.sendCommand("LLEN", key) @@ -433,7 +509,7 @@ proc lLen*(r: TRedis, key: string): TRedisInteger = proc lPop*(r: TRedis, key: string): TRedisString = ## Remove and get the first element in a list r.sendCommand("LPOP", key) - return r.parseBulk() + return r.parseBulkString() proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Prepend a value to a list. Returns the length of the list after the push. @@ -450,7 +526,7 @@ proc lRange*(r: TRedis, key: string, start, stop: int): TRedisList = ## Get a range of elements from a list. Returns `nil` when `key` ## doesn't exist. r.sendCommand("LRANGE", key, $start, $stop) - return r.parseMultiBulk() + return r.parseArray() proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger = ## Remove elements from a list. Returns the number of elements that have been @@ -471,13 +547,13 @@ proc lTrim*(r: TRedis, key: string, start, stop: int) = proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list r.sendCommand("RPOP", key) - return r.parseBulk() - + return r.parseBulkString() + proc rPopLPush*(r: TRedis, source, destination: string): TRedisString = ## Remove the last element in a list, append it to another list and return it r.sendCommand("RPOPLPUSH", source, destination) - return r.parseBulk() - + return r.parseBulkString() + proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Append a value to a list. Returns the length of the list after the push. ## The ``create`` param specifies whether a list should be created if it @@ -504,7 +580,7 @@ proc scard*(r: TRedis, key: string): TRedisInteger = proc sdiff*(r: TRedis, keys: varargs[string]): TRedisList = ## Subtract multiple sets r.sendCommand("SDIFF", keys) - return r.parseMultiBulk() + return r.parseArray() proc sdiffstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = @@ -515,7 +591,7 @@ proc sdiffstore*(r: TRedis, destination: string, proc sinter*(r: TRedis, keys: varargs[string]): TRedisList = ## Intersect multiple sets r.sendCommand("SINTER", keys) - return r.parseMultiBulk() + return r.parseArray() proc sinterstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = @@ -531,7 +607,7 @@ proc sismember*(r: TRedis, key: string, member: string): TRedisInteger = proc smembers*(r: TRedis, key: string): TRedisList = ## Get all the members in a set r.sendCommand("SMEMBERS", key) - return r.parseMultiBulk() + return r.parseArray() proc smove*(r: TRedis, source: string, destination: string, member: string): TRedisInteger = @@ -542,12 +618,12 @@ proc smove*(r: TRedis, source: string, destination: string, proc spop*(r: TRedis, key: string): TRedisString = ## Remove and return a random member from a set r.sendCommand("SPOP", key) - return r.parseBulk() + return r.parseBulkString() proc srandmember*(r: TRedis, key: string): TRedisString = ## Get a random member from a set r.sendCommand("SRANDMEMBER", key) - return r.parseBulk() + return r.parseBulkString() proc srem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a set @@ -557,7 +633,7 @@ proc srem*(r: TRedis, key: string, member: string): TRedisInteger = proc sunion*(r: TRedis, keys: varargs[string]): TRedisList = ## Add multiple sets r.sendCommand("SUNION", keys) - return r.parseMultiBulk() + return r.parseArray() proc sunionstore*(r: TRedis, destination: string, key: varargs[string]): TRedisInteger = @@ -586,7 +662,7 @@ proc zincrby*(r: TRedis, key: string, increment: string, member: string): TRedisString = ## Increment the score of a member in a sorted set r.sendCommand("ZINCRBY", key, increment, member) - return r.parseBulk() + return r.parseBulkString() proc zinterstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -595,16 +671,16 @@ proc zinterstore*(r: TRedis, destination: string, numkeys: string, ## a new key var args = @[destination, numkeys] for i in items(keys): args.add(i) - + if weights.len != 0: args.add("WITHSCORE") for i in items(weights): args.add(i) if aggregate.len != 0: args.add("AGGREGATE") args.add(aggregate) - + r.sendCommand("ZINTERSTORE", args) - + return r.parseInteger() proc zrange*(r: TRedis, key: string, start: string, stop: string, @@ -614,27 +690,27 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string, r.sendCommand("ZRANGE", key, start, stop) else: r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) - return r.parseMultiBulk() + return r.parseArray() -proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, +proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, limitOffset: int = 0, limitCount: int = 0): TRedisList = ## Return a range of members in a sorted set, by score var args = @[key, min, max] - + if withScore: args.add("WITHSCORE") - if limit: + if limit: args.add("LIMIT") args.add($limitOffset) args.add($limitCount) - + r.sendCommand("ZRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.parseArray() proc zrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set r.sendCommand("ZRANK", key, member) - return r.parseBulk() + return r.parseBulkString() proc zrem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a sorted set @@ -660,34 +736,34 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string, if withScore: r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop) else: r.sendCommand("ZREVRANGE", key, start, stop) - return r.parseMultiBulk() + return r.parseArray() -proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, +proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, limitOffset: int = 0, limitCount: int = 0): TRedisList = ## Return a range of members in a sorted set, by score, with ## scores ordered from high to low var args = @[key, min, max] - + if withScore: args.add("WITHSCORE") - if limit: + if limit: args.add("LIMIT") args.add($limitOffset) args.add($limitCount) - + r.sendCommand("ZREVRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.parseArray() proc zrevrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set, with ## scores ordered from high to low r.sendCommand("ZREVRANK", key, member) - return r.parseBulk() + return r.parseBulkString() proc zscore*(r: TRedis, key: string, member: string): TRedisString = ## Get the score associated with the given member in a sorted set r.sendCommand("ZSCORE", key, member) - return r.parseBulk() + return r.parseBulkString() proc zunionstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -695,16 +771,16 @@ proc zunionstore*(r: TRedis, destination: string, numkeys: string, ## Add multiple sorted sets and store the resulting sorted set in a new key var args = @[destination, numkeys] for i in items(keys): args.add(i) - + if weights.len != 0: args.add("WEIGHTS") for i in items(weights): args.add(i) if aggregate.len != 0: args.add("AGGREGATE") args.add(aggregate) - + r.sendCommand("ZUNIONSTORE", args) - + return r.parseInteger() @@ -733,7 +809,7 @@ proc subscribe*(r: TRedis, channel: openarray[string]): ???? = return ??? proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = - ## Stop listening for messages posted to the given channels + ## Stop listening for messages posted to the given channels r.socket.send("UNSUBSCRIBE $# $#\c\L" % [[channel.join(), ]) return ??? @@ -749,11 +825,14 @@ proc discardMulti*(r: TRedis) = proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI r.sendCommand("EXEC") - - return r.parseMultiBulk() + r.pipeline.enabled = false + # Will reply with +OK for MULTI/EXEC and +QUEUED for every command + # between, then with the results + return r.flushPipeline(true) proc multi*(r: TRedis) = ## Mark the start of a transaction block + r.setPipeline(true) r.sendCommand("MULTI") raiseNoOK(r.parseStatus()) @@ -777,7 +856,7 @@ proc auth*(r: TRedis, password: string) = proc echoServ*(r: TRedis, message: string): TRedisString = ## Echo the given string r.sendCommand("ECHO", message) - return r.parseBulk() + return r.parseBulkString() proc ping*(r: TRedis): TRedisStatus = ## Ping the server @@ -809,7 +888,7 @@ proc bgsave*(r: TRedis) = proc configGet*(r: TRedis, parameter: string): TRedisList = ## Get the value of a configuration parameter r.sendCommand("CONFIG", "GET", parameter) - return r.parseMultiBulk() + return r.parseArray() proc configSet*(r: TRedis, parameter: string, value: string) = ## Set a configuration parameter to the given value @@ -848,7 +927,7 @@ proc flushdb*(r: TRedis): TRedisStatus = proc info*(r: TRedis): TRedisString = ## Get information and statistics about the server r.sendCommand("INFO") - return r.parseBulk() + return r.parseBulkString() proc lastsave*(r: TRedis): TRedisInteger = ## Get the UNIX time stamp of the last successful save to disk @@ -881,7 +960,7 @@ proc slaveof*(r: TRedis, host: string, port: string) = iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. - var + var contents = r.hGetAll(key) k = "" for i in items(contents): @@ -890,33 +969,68 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = else: yield (k, i) k = "" - -when false: - # sorry, deactivated for the test suite - var r = open() - r.auth("pass") +proc someTests(r: TRedis) = + #r.auth("pass") r.setk("nim:test", "Testing something.") r.setk("nim:utf8", "こんにちは") r.setk("nim:esc", "\\ths ągt\\") - - echo r.get("nim:esc") - echo r.incr("nim:int") - echo r.incr("nim:int") + r.setk("nim:int", "1") + echo(r.get("nim:esc")) + echo(r.incr("nim:int")) echo r.get("nim:int") echo r.get("nim:utf8") + echo r.hSet("test1", "name", "A Test") + var res = r.hGetAll("test1") echo repr(r.get("blahasha")) echo r.randomKey() - + discard r.lpush("mylist","itema") + discard r.lpush("mylist","itemb") + r.ltrim("mylist",0,1) var p = r.lrange("mylist", 0, -1) - for i in items(p): - echo(" ", i) - echo(r.debugObject("test")) + for i in items(p): + if not isNil(i): + echo(" ", i) + + echo(r.debugObject("mylist")) r.configSet("timeout", "299") for i in items(r.configGet("timeout")): echo ">> ", i echo r.echoServ("BLAH") + +when false: + var r = open() + + # Test with no pipelining + echo("----------------------------------------------") + echo("Testing without pipelining.") + r.someTests() + + # Test with pipelining enabled + echo("//////////////////////////////////////////////") + echo() + echo("Testing with pipelining.") + echo() + r.setPipeline(true) + r.someTests() + var list = r.flushPipeline() + r.setPipeline(false) + echo("-- list length is " & $list.len & " --") + for item in list: + if not isNil(item): + echo item + + # Test with multi/exec() (automatic pipelining) + echo("************************************************") + echo("Testing with transaction (automatic pipelining)") + r.multi() + r.someTests() + list = r.exec() + echo("-- list length is " & $list.len & " --") + for item in list: + if not isNil(item): + echo item From 9a728b15a2e3d1f3cc0f69144a8591c3cc427bdc Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Thu, 10 Apr 2014 16:46:26 -0700 Subject: [PATCH 02/10] Don't need ref string; use PPipeline instead of ref TPipeline --- lib/pure/redis.nim | 154 ++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index e4d47e43d2..51e8880dc7 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -19,18 +19,18 @@ import sockets, os, strutils, parseutils const redisNil* = "\0\0" -type - TPipeline = object +type + PPipeline = ref object enabled: bool - buffer: ref string + buffer: string expected: int ## number of replies expected if pipelined type TRedis* {.pure, final.} = object socket: TSocket connected: bool - pipeline: ref TPipeline - + pipeline: PPipeline + TRedisStatus* = string TRedisInteger* = biggestInt TRedisString* = string ## Bulk reply @@ -39,10 +39,9 @@ type EInvalidReply* = object of ESynch ## Invalid reply from redis ERedis* = object of ESynch ## Error in redis -proc newPipeline(): ref TPipeLine = +proc newPipeline(): PPipeline = new(result) - result.buffer = new string - result.buffer[] = "" + result.buffer = "" result.enabled = false result.expected = 0 @@ -52,10 +51,10 @@ proc open*(host = "localhost", port = 6379.TPort): TRedis = if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.connect(host, port) - result.pipeline = newPipeline() + result.pipeline = newPipeline() proc raiseInvalidReply(expected, got: char) = - raise newException(EInvalidReply, + raise newException(EInvalidReply, "Expected '$1' at the beginning of a status reply got '$2'" % [$expected, $got]) @@ -77,12 +76,12 @@ proc parseStatus(r: TRedis, lineIn: string = ""): TRedisStatus = raise newException(ERedis, strip(line)) if line[0] != '+': raiseInvalidReply('+', line[0]) - + return line.substr(1) # Strip '+' - + proc parseInteger(r: TRedis, lineIn: string = ""): TRedisInteger = if r.pipeline.enabled: return -1 - + var line = lineIn if line == "": r.socket.readLine(line) @@ -97,10 +96,10 @@ proc parseInteger(r: TRedis, lineIn: string = ""): TRedisInteger = raise newException(ERedis, strip(line)) if line[0] != ':': raiseInvalidReply(':', line[0]) - + # Strip ':' if parseBiggestInt(line, result, 1) == 0: - raise newException(EInvalidReply, "Unable to parse integer.") + raise newException(EInvalidReply, "Unable to parse integer.") proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString @@ -109,19 +108,19 @@ proc recv(sock: TSocket, size: int): TaintedString = proc parseSingleString(r: TRedis, line:string, allowMBNil = False): TRedisString = if r.pipeline.enabled: return "" - + # Error. if line[0] == '-': raise newException(ERedis, strip(line)) - + # Some commands return a /bulk/ value or a /multi-bulk/ nil. Odd. if allowMBNil: if line == "*-1": return RedisNil - + if line[0] != '$': raiseInvalidReply('$', line[0]) - + var numBytes = parseInt(line.substr(1)) if numBytes == -1: return RedisNil @@ -144,7 +143,7 @@ proc parseArrayLines(r: TRedis, countLine:string): TRedisList = if not isNil(parsed): for item in parsed: result.add(item) - + proc parseBulkString(r: TRedis, allowMBNil = False, lineIn:string = ""): TRedisString = if r.pipeline.enabled: return "" @@ -158,7 +157,7 @@ proc parseArray(r: TRedis): TRedisList = if r.pipeline.enabled: return @[] var line = TaintedString"" r.socket.readLine(line) - + return r.parseArrayLines(line) proc parseNext(r: TRedis): TRedisList = @@ -172,7 +171,7 @@ proc parseNext(r: TRedis): TRedisList = of ':': @[$(r.parseInteger(line))] of '$': @[r.parseBulkString(true,line)] of '*': r.parseArrayLines(line) - else: + else: raise newException(EInvalidReply, "parseNext failed on line: " & line) nil r.pipeline.expected -= 1 @@ -180,14 +179,14 @@ proc parseNext(r: TRedis): TRedisList = proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = ## Send buffered commands, clear buffer, return results - if r.pipeline.buffer[].len > 0: - r.socket.send(r.pipeline.buffer[]) - r.pipeline.buffer[] = "" - + if r.pipeline.buffer.len > 0: + r.socket.send(r.pipeline.buffer) + r.pipeline.buffer = "" + var prevState = r.pipeline.enabled r.pipeline.enabled = false result = @[] - + var tot = r.pipeline.expected for i in 0..tot-1: @@ -195,13 +194,13 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = if ret.len == 1 and (ret[0] == "OK" or ret[0] == "QUEUED"): # Skip acknowledgement replies in multi if not wasMulti: result.add(ret) - else: + else: result.add(ret) r.pipeline.expected = 0 r.pipeline.enabled = prevState -proc setPipeline*(r: TRedis, state: bool) = +proc sePPipeline*(r: TRedis, state: bool) = ## Enable or disable command pipelining (reduces network roundtrips). ## Note that when enabled, you must call flushPipeline to actually send commands, except ## for multi/exec() which enable and flush the pipeline automatically. @@ -217,9 +216,9 @@ proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - + if r.pipeline.enabled: - r.pipeline.buffer[].add(request) + r.pipeline.buffer.add(request) r.pipeline.expected += 1 else: r.socket.send(request) @@ -234,10 +233,10 @@ proc sendCommand(r: TRedis, cmd: string, arg1: string, for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - + if r.pipeline.enabled: r.pipeline.expected += 1 - r.pipeline.buffer[].add(request) + r.pipeline.buffer.add(request) else: r.socket.send(request) @@ -280,7 +279,7 @@ proc persist*(r: TRedis, key: string): bool = ## Returns `true` when the timeout was removed. r.sendCommand("PERSIST", key) return r.parseInteger() == 1 - + proc randomKey*(r: TRedis): TRedisString = ## Return a random key from the keyspace r.sendCommand("RANDOMKEY") @@ -292,7 +291,7 @@ proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) raiseNoOK(r.parseStatus()) - + proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. ## Returns `true` if key was renamed. @@ -303,12 +302,12 @@ proc ttl*(r: TRedis, key: string): TRedisInteger = ## Get the time to live for a key r.sendCommand("TTL", key) return r.parseInteger() - + proc keyType*(r: TRedis, key: string): TRedisStatus = ## Determine the type stored at key r.sendCommand("TYPE", key) return r.parseStatus() - + # Strings @@ -321,12 +320,12 @@ proc decr*(r: TRedis, key: string): TRedisInteger = ## Decrement the integer value of a key by one r.sendCommand("DECR", key) return r.parseInteger() - + proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger = ## Decrement the integer value of a key by the given number r.sendCommand("DECRBY", key, $decrement) return r.parseInteger() - + proc get*(r: TRedis, key: string): TRedisString = ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. r.sendCommand("GET", key) @@ -358,7 +357,7 @@ proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger = r.sendCommand("INCRBY", key, $increment) return r.parseInteger() -proc setk*(r: TRedis, key, value: string) = +proc setk*(r: TRedis, key, value: string) = ## Set the string value of a key. ## ## NOTE: This function had to be renamed due to a clash with the `set` type. @@ -371,18 +370,18 @@ proc setNX*(r: TRedis, key, value: string): bool = r.sendCommand("SETNX", key, value) return r.parseInteger() == 1 -proc setBit*(r: TRedis, key: string, offset: int, +proc setBit*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Sets or clears the bit at offset in the string value stored at key r.sendCommand("SETBIT", key, $offset, value) return r.parseInteger() - + proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) raiseNoOK(r.parseStatus()) -proc setRange*(r: TRedis, key: string, offset: int, +proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Overwrite part of a string at key starting at the specified offset r.sendCommand("SETRANGE", key, $offset, value) @@ -435,7 +434,7 @@ proc hMGet*(r: TRedis, key: string, fields: varargs[string]): TRedisList = r.sendCommand("HMGET", key, fields) return r.parseArray() -proc hMSet*(r: TRedis, key: string, +proc hMSet*(r: TRedis, key: string, fieldValues: openarray[tuple[field, value: string]]) = ## Set multiple hash fields to multiple values var args = @[key] @@ -449,7 +448,7 @@ proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field r.sendCommand("HSET", key, field, value) return r.parseInteger() - + proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the value of a hash field, only if the field does **not** exist r.sendCommand("HSETNX", key, field, value) @@ -459,7 +458,7 @@ proc hVals*(r: TRedis, key: string): TRedisList = ## Get all the values in a hash r.sendCommand("HVALS", key) return r.parseArray() - + # Lists proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = @@ -500,7 +499,7 @@ proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): var pos = if before: "BEFORE" else: "AFTER" r.sendCommand("LINSERT", key, pos, pivot, value) return r.parseInteger() - + proc lLen*(r: TRedis, key: string): TRedisInteger = ## Get the length of a list r.sendCommand("LLEN", key) @@ -548,12 +547,12 @@ proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list r.sendCommand("RPOP", key) return r.parseBulkString() - + proc rPopLPush*(r: TRedis, source, destination: string): TRedisString = ## Remove the last element in a list, append it to another list and return it r.sendCommand("RPOPLPUSH", source, destination) return r.parseBulkString() - + proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Append a value to a list. Returns the length of the list after the push. ## The ``create`` param specifies whether a list should be created if it @@ -671,16 +670,16 @@ proc zinterstore*(r: TRedis, destination: string, numkeys: string, ## a new key var args = @[destination, numkeys] for i in items(keys): args.add(i) - + if weights.len != 0: args.add("WITHSCORE") for i in items(weights): args.add(i) if aggregate.len != 0: args.add("AGGREGATE") args.add(aggregate) - + r.sendCommand("ZINTERSTORE", args) - + return r.parseInteger() proc zrange*(r: TRedis, key: string, start: string, stop: string, @@ -692,18 +691,18 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string, r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) return r.parseArray() -proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, +proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, limitOffset: int = 0, limitCount: int = 0): TRedisList = ## Return a range of members in a sorted set, by score var args = @[key, min, max] - + if withScore: args.add("WITHSCORE") - if limit: + if limit: args.add("LIMIT") args.add($limitOffset) args.add($limitCount) - + r.sendCommand("ZRANGEBYSCORE", args) return r.parseArray() @@ -738,19 +737,19 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string, else: r.sendCommand("ZREVRANGE", key, start, stop) return r.parseArray() -proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, +proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, limitOffset: int = 0, limitCount: int = 0): TRedisList = ## Return a range of members in a sorted set, by score, with ## scores ordered from high to low var args = @[key, min, max] - + if withScore: args.add("WITHSCORE") - if limit: + if limit: args.add("LIMIT") args.add($limitOffset) args.add($limitCount) - + r.sendCommand("ZREVRANGEBYSCORE", args) return r.parseArray() @@ -771,16 +770,16 @@ proc zunionstore*(r: TRedis, destination: string, numkeys: string, ## Add multiple sorted sets and store the resulting sorted set in a new key var args = @[destination, numkeys] for i in items(keys): args.add(i) - + if weights.len != 0: args.add("WEIGHTS") for i in items(weights): args.add(i) if aggregate.len != 0: args.add("AGGREGATE") args.add(aggregate) - + r.sendCommand("ZUNIONSTORE", args) - + return r.parseInteger() @@ -809,7 +808,7 @@ proc subscribe*(r: TRedis, channel: openarray[string]): ???? = return ??? proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = - ## Stop listening for messages posted to the given channels + ## Stop listening for messages posted to the given channels r.socket.send("UNSUBSCRIBE $# $#\c\L" % [[channel.join(), ]) return ??? @@ -824,15 +823,16 @@ proc discardMulti*(r: TRedis) = proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI - r.sendCommand("EXEC") + r.sendCommand("EXEC") r.pipeline.enabled = false # Will reply with +OK for MULTI/EXEC and +QUEUED for every command # between, then with the results return r.flushPipeline(true) + proc multi*(r: TRedis) = ## Mark the start of a transaction block - r.setPipeline(true) + r.sePPipeline(true) r.sendCommand("MULTI") raiseNoOK(r.parseStatus()) @@ -960,7 +960,7 @@ proc slaveof*(r: TRedis, host: string, port: string) = iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. - var + var contents = r.hGetAll(key) k = "" for i in items(contents): @@ -969,7 +969,7 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = else: yield (k, i) k = "" - + proc someTests(r: TRedis) = #r.auth("pass") @@ -989,22 +989,22 @@ proc someTests(r: TRedis) = discard r.lpush("mylist","itemb") r.ltrim("mylist",0,1) var p = r.lrange("mylist", 0, -1) - + for i in items(p): if not isNil(i): - echo(" ", i) - + echo(" ", i) + echo(r.debugObject("mylist")) r.configSet("timeout", "299") for i in items(r.configGet("timeout")): echo ">> ", i echo r.echoServ("BLAH") - - + + when false: var r = open() - + # Test with no pipelining echo("----------------------------------------------") echo("Testing without pipelining.") @@ -1015,15 +1015,15 @@ when false: echo() echo("Testing with pipelining.") echo() - r.setPipeline(true) + r.sePPipeline(true) r.someTests() var list = r.flushPipeline() - r.setPipeline(false) + r.sePPipeline(false) echo("-- list length is " & $list.len & " --") for item in list: if not isNil(item): echo item - + # Test with multi/exec() (automatic pipelining) echo("************************************************") echo("Testing with transaction (automatic pipelining)") From be02aaec72c1a2dff1d1ec20d73647522f46ac35 Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Thu, 10 Apr 2014 22:23:33 -0700 Subject: [PATCH 03/10] factor per comments --- lib/pure/redis.nim | 298 +++++++++++++++++++++++---------------------- 1 file changed, 155 insertions(+), 143 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 51e8880dc7..37a2a3f165 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -62,13 +62,17 @@ proc raiseNoOK(status: string) = if status != "QUEUED" and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) -proc parseStatus(r: TRedis, lineIn: string = ""): TRedisStatus = +template readSocket(r: TRedis, dummyVal:expr): stmt = + var line {.inject.} :TaintedString = "" + if r.pipeline.enabled: + return dummyVal + else: + readLine(r.socket, line) + +proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = if r.pipeline.enabled: return "OK" - var line = lineIn - if line == "": - r.socket.readLine(line) if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -78,14 +82,16 @@ proc parseStatus(r: TRedis, lineIn: string = ""): TRedisStatus = raiseInvalidReply('+', line[0]) return line.substr(1) # Strip '+' - -proc parseInteger(r: TRedis, lineIn: string = ""): TRedisInteger = + +proc readStatus(r:TRedis): TRedisStatus = + echo "top of readStatus" + r.readSocket("OK") + echo "line is " & line + return r.parseStatus(line) + +proc parseInteger(r: TRedis, line: string = ""): TRedisInteger = if r.pipeline.enabled: return -1 - var line = lineIn - if line == "": - r.socket.readLine(line) - if line == "+QUEUED": # inside of multi return -1 @@ -101,6 +107,10 @@ proc parseInteger(r: TRedis, lineIn: string = ""): TRedisInteger = if parseBiggestInt(line, result, 1) == 0: raise newException(EInvalidReply, "Unable to parse integer.") +proc readInteger(r: TRedis): TRedisInteger = + r.readSocket(-1) + return r.parseInteger(line) + proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString if sock.recv(cstring(result), size) != size: @@ -128,7 +138,11 @@ proc parseSingleString(r: TRedis, line:string, allowMBNil = False): TRedisString var s = r.socket.recv(numBytes+2) result = strip(s.string) -proc parseNext(r: TRedis): TRedisList +proc readSingleString(r: TRedis): TRedisString = + r.readSocket("") + return r.parseSingleString(line) + +proc readNext(r: TRedis): TRedisList proc parseArrayLines(r: TRedis, countLine:string): TRedisList = if countLine.string[0] != '*': @@ -139,40 +153,38 @@ proc parseArrayLines(r: TRedis, countLine:string): TRedisList = result = @[] for i in 1..numElems: - var parsed = r.parseNext() + var parsed = r.readNext() if not isNil(parsed): for item in parsed: result.add(item) - -proc parseBulkString(r: TRedis, allowMBNil = False, lineIn:string = ""): TRedisString = - if r.pipeline.enabled: return "" - var line = lineIn - if line == "": - r.socket.readLine(line.TaintedString) +proc readArrayLines(r: TRedis): TRedisList = + r.readSocket(nil) + return r.parseArrayLines(line) + +proc parseBulkString(r: TRedis, allowMBNil = False, line:string = ""): TRedisString = + if r.pipeline.enabled: return "" return r.parseSingleString(line, allowMBNil) -proc parseArray(r: TRedis): TRedisList = - if r.pipeline.enabled: return @[] - var line = TaintedString"" - r.socket.readLine(line) - +proc readBulkString(r: TRedis, allowMBNil = false): TRedisString = + r.readSocket("") + return r.parseBulkString(allowMBNil, line) + +proc readArray(r: TRedis): TRedisList = + r.readSocket(@[]) return r.parseArrayLines(line) -proc parseNext(r: TRedis): TRedisList = - if r.pipeline.enabled: return @[] - var line = TaintedString"" - r.socket.readLine(line) +proc readNext(r: TRedis): TRedisList = + r.readSocket(@[]) var res = case line[0] - of '+': @[r.parseStatus(line)] - of '-': @[r.parseStatus(line)] + of '+', '-': @[r.parseStatus(line)] of ':': @[$(r.parseInteger(line))] of '$': @[r.parseBulkString(true,line)] of '*': r.parseArrayLines(line) else: - raise newException(EInvalidReply, "parseNext failed on line: " & line) + raise newException(EInvalidReply, "readNext failed on line: " & line) nil r.pipeline.expected -= 1 return res @@ -190,7 +202,7 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = var tot = r.pipeline.expected for i in 0..tot-1: - var ret = r.parseNext() + var ret = r.readNext() if ret.len == 1 and (ret[0] == "OK" or ret[0] == "QUEUED"): # Skip acknowledgement replies in multi if not wasMulti: result.add(ret) @@ -200,7 +212,7 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = r.pipeline.expected = 0 r.pipeline.enabled = prevState -proc sePPipeline*(r: TRedis, state: bool) = +proc setPipeline*(r: TRedis, state: bool) = ## Enable or disable command pipelining (reduces network roundtrips). ## Note that when enabled, you must call flushPipeline to actually send commands, except ## for multi/exec() which enable and flush the pipeline automatically. @@ -245,68 +257,68 @@ proc sendCommand(r: TRedis, cmd: string, arg1: string, proc del*(r: TRedis, keys: varargs[string]): TRedisInteger = ## Delete a key or multiple keys r.sendCommand("DEL", keys) - return r.parseInteger() + return r.readInteger() proc exists*(r: TRedis, key: string): bool = ## Determine if a key exists r.sendCommand("EXISTS", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expire*(r: TRedis, key: string, seconds: int): bool = ## Set a key's time to live in seconds. Returns `false` if the key could ## not be found or the timeout could not be set. r.sendCommand("EXPIRE", key, $seconds) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expireAt*(r: TRedis, key: string, timestamp: int): bool = ## Set the expiration for a key as a UNIX timestamp. Returns `false` ## if the key could not be found or the timeout could not be set. r.sendCommand("EXPIREAT", key, $timestamp) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc keys*(r: TRedis, pattern: string): TRedisList = ## Find all keys matching the given pattern r.sendCommand("KEYS", pattern) - return r.parseArray() + return r.readArray() proc move*(r: TRedis, key: string, db: int): bool = ## Move a key to another database. Returns `true` on a successful move. r.sendCommand("MOVE", key, $db) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc persist*(r: TRedis, key: string): bool = ## Remove the expiration from a key. ## Returns `true` when the timeout was removed. r.sendCommand("PERSIST", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc randomKey*(r: TRedis): TRedisString = ## Return a random key from the keyspace r.sendCommand("RANDOMKEY") - return r.parseBulkString() + return r.readBulkString() proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## Rename a key. ## ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. ## Returns `true` if key was renamed. r.sendCommand("RENAMENX", key, newkey) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc ttl*(r: TRedis, key: string): TRedisInteger = ## Get the time to live for a key r.sendCommand("TTL", key) - return r.parseInteger() + return r.readInteger() proc keyType*(r: TRedis, key: string): TRedisStatus = ## Determine the type stored at key r.sendCommand("TYPE", key) - return r.parseStatus() + return r.readStatus() # Strings @@ -314,125 +326,125 @@ proc keyType*(r: TRedis, key: string): TRedisStatus = proc append*(r: TRedis, key, value: string): TRedisInteger = ## Append a value to a key r.sendCommand("APPEND", key, value) - return r.parseInteger() + return r.readInteger() proc decr*(r: TRedis, key: string): TRedisInteger = ## Decrement the integer value of a key by one r.sendCommand("DECR", key) - return r.parseInteger() + return r.readInteger() proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger = ## Decrement the integer value of a key by the given number r.sendCommand("DECRBY", key, $decrement) - return r.parseInteger() + return r.readInteger() proc get*(r: TRedis, key: string): TRedisString = ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. r.sendCommand("GET", key) - return r.parseBulkString() + return r.readBulkString() proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger = ## Returns the bit value at offset in the string value stored at key r.sendCommand("GETBIT", key, $offset) - return r.parseInteger() + return r.readInteger() proc getRange*(r: TRedis, key: string, start, stop: int): TRedisString = ## Get a substring of the string stored at a key r.sendCommand("GETRANGE", key, $start, $stop) - return r.parseBulkString() + return r.readBulkString() proc getSet*(r: TRedis, key: string, value: string): TRedisString = ## Set the string value of a key and return its old value. Returns `redisNil` ## when key doesn't exist. r.sendCommand("GETSET", key, value) - return r.parseBulkString() + return r.readBulkString() proc incr*(r: TRedis, key: string): TRedisInteger = ## Increment the integer value of a key by one. r.sendCommand("INCR", key) - return r.parseInteger() + return r.readInteger() proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger = ## Increment the integer value of a key by the given number r.sendCommand("INCRBY", key, $increment) - return r.parseInteger() + return r.readInteger() proc setk*(r: TRedis, key, value: string) = ## Set the string value of a key. ## ## NOTE: This function had to be renamed due to a clash with the `set` type. r.sendCommand("SET", key, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc setNX*(r: TRedis, key, value: string): bool = ## Set the value of a key, only if the key does not exist. Returns `true` ## if the key was set. r.sendCommand("SETNX", key, value) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc setBit*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Sets or clears the bit at offset in the string value stored at key r.sendCommand("SETBIT", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Overwrite part of a string at key starting at the specified offset r.sendCommand("SETRANGE", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc strlen*(r: TRedis, key: string): TRedisInteger = ## Get the length of the value stored in a key. Returns 0 when key doesn't ## exist. r.sendCommand("STRLEN", key) - return r.parseInteger() + return r.readInteger() # Hashes proc hDel*(r: TRedis, key, field: string): bool = ## Delete a hash field at `key`. Returns `true` if the field was removed. r.sendCommand("HDEL", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hExists*(r: TRedis, key, field: string): bool = ## Determine if a hash field exists. r.sendCommand("HEXISTS", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hGet*(r: TRedis, key, field: string): TRedisString = ## Get the value of a hash field r.sendCommand("HGET", key, field) - return r.parseBulkString() + return r.readBulkString() proc hGetAll*(r: TRedis, key: string): TRedisList = ## Get all the fields and values in a hash r.sendCommand("HGETALL", key) - return r.parseArray() + return r.readArray() proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger = ## Increment the integer value of a hash field by the given number r.sendCommand("HINCRBY", key, field, $incr) - return r.parseInteger() + return r.readInteger() proc hKeys*(r: TRedis, key: string): TRedisList = ## Get all the fields in a hash r.sendCommand("HKEYS", key) - return r.parseArray() + return r.readArray() proc hLen*(r: TRedis, key: string): TRedisInteger = ## Get the number of fields in a hash r.sendCommand("HLEN", key) - return r.parseInteger() + return r.readInteger() proc hMGet*(r: TRedis, key: string, fields: varargs[string]): TRedisList = ## Get the values of all the given hash fields r.sendCommand("HMGET", key, fields) - return r.parseArray() + return r.readArray() proc hMSet*(r: TRedis, key: string, fieldValues: openarray[tuple[field, value: string]]) = @@ -442,22 +454,22 @@ proc hMSet*(r: TRedis, key: string, args.add(field) args.add(value) r.sendCommand("HMSET", args) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field r.sendCommand("HSET", key, field, value) - return r.parseInteger() + return r.readInteger() proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the value of a hash field, only if the field does **not** exist r.sendCommand("HSETNX", key, field, value) - return r.parseInteger() + return r.readInteger() proc hVals*(r: TRedis, key: string): TRedisList = ## Get all the values in a hash r.sendCommand("HVALS", key) - return r.parseArray() + return r.readArray() # Lists @@ -468,7 +480,7 @@ proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BLPOP", args) - return r.parseArray() + return r.readArray() proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = ## Remove and get the *last* element in a list, or block until one @@ -477,7 +489,7 @@ proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BRPOP", args) - return r.parseArray() + return r.readArray() proc bRPopLPush*(r: TRedis, source, destination: string, timeout: int): TRedisString = @@ -486,29 +498,29 @@ proc bRPopLPush*(r: TRedis, source, destination: string, ## ## http://redis.io/commands/brpoplpush r.sendCommand("BRPOPLPUSH", source, destination, $timeout) - return r.parseBulkString(true) # Multi-Bulk nil allowed. + return r.readBulkString(true) # Multi-Bulk nil allowed. proc lIndex*(r: TRedis, key: string, index: int): TRedisString = ## Get an element from a list by its index r.sendCommand("LINDEX", key, $index) - return r.parseBulkString() + return r.readBulkString() proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): TRedisInteger = ## Insert an element before or after another element in a list var pos = if before: "BEFORE" else: "AFTER" r.sendCommand("LINSERT", key, pos, pivot, value) - return r.parseInteger() + return r.readInteger() proc lLen*(r: TRedis, key: string): TRedisInteger = ## Get the length of a list r.sendCommand("LLEN", key) - return r.parseInteger() + return r.readInteger() proc lPop*(r: TRedis, key: string): TRedisString = ## Remove and get the first element in a list r.sendCommand("LPOP", key) - return r.parseBulkString() + return r.readBulkString() proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Prepend a value to a list. Returns the length of the list after the push. @@ -519,39 +531,39 @@ proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("LPUSH", key, value) else: r.sendCommand("LPUSHX", key, value) - return r.parseInteger() + return r.readInteger() proc lRange*(r: TRedis, key: string, start, stop: int): TRedisList = ## Get a range of elements from a list. Returns `nil` when `key` ## doesn't exist. r.sendCommand("LRANGE", key, $start, $stop) - return r.parseArray() + return r.readArray() proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger = ## Remove elements from a list. Returns the number of elements that have been ## removed. r.sendCommand("LREM", key, $count, value) - return r.parseInteger() + return r.readInteger() proc lSet*(r: TRedis, key: string, index: int, value: string) = ## Set the value of an element in a list by its index r.sendCommand("LSET", key, $index, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc lTrim*(r: TRedis, key: string, start, stop: int) = ## Trim a list to the specified range r.sendCommand("LTRIM", key, $start, $stop) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list r.sendCommand("RPOP", key) - return r.parseBulkString() + return r.readBulkString() proc rPopLPush*(r: TRedis, source, destination: string): TRedisString = ## Remove the last element in a list, append it to another list and return it r.sendCommand("RPOPLPUSH", source, destination) - return r.parseBulkString() + return r.readBulkString() proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Append a value to a list. Returns the length of the list after the push. @@ -562,106 +574,106 @@ proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("RPUSH", key, value) else: r.sendCommand("RPUSHX", key, value) - return r.parseInteger() + return r.readInteger() # Sets proc sadd*(r: TRedis, key: string, member: string): TRedisInteger = ## Add a member to a set r.sendCommand("SADD", key, member) - return r.parseInteger() + return r.readInteger() proc scard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a set r.sendCommand("SCARD", key) - return r.parseInteger() + return r.readInteger() proc sdiff*(r: TRedis, keys: varargs[string]): TRedisList = ## Subtract multiple sets r.sendCommand("SDIFF", keys) - return r.parseArray() + return r.readArray() proc sdiffstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Subtract multiple sets and store the resulting set in a key r.sendCommand("SDIFFSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sinter*(r: TRedis, keys: varargs[string]): TRedisList = ## Intersect multiple sets r.sendCommand("SINTER", keys) - return r.parseArray() + return r.readArray() proc sinterstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Intersect multiple sets and store the resulting set in a key r.sendCommand("SINTERSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sismember*(r: TRedis, key: string, member: string): TRedisInteger = ## Determine if a given value is a member of a set r.sendCommand("SISMEMBER", key, member) - return r.parseInteger() + return r.readInteger() proc smembers*(r: TRedis, key: string): TRedisList = ## Get all the members in a set r.sendCommand("SMEMBERS", key) - return r.parseArray() + return r.readArray() proc smove*(r: TRedis, source: string, destination: string, member: string): TRedisInteger = ## Move a member from one set to another r.sendCommand("SMOVE", source, destination, member) - return r.parseInteger() + return r.readInteger() proc spop*(r: TRedis, key: string): TRedisString = ## Remove and return a random member from a set r.sendCommand("SPOP", key) - return r.parseBulkString() + return r.readBulkString() proc srandmember*(r: TRedis, key: string): TRedisString = ## Get a random member from a set r.sendCommand("SRANDMEMBER", key) - return r.parseBulkString() + return r.readBulkString() proc srem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a set r.sendCommand("SREM", key, member) - return r.parseInteger() + return r.readInteger() proc sunion*(r: TRedis, keys: varargs[string]): TRedisList = ## Add multiple sets r.sendCommand("SUNION", keys) - return r.parseArray() + return r.readArray() proc sunionstore*(r: TRedis, destination: string, key: varargs[string]): TRedisInteger = ## Add multiple sets and store the resulting set in a key r.sendCommand("SUNIONSTORE", destination, key) - return r.parseInteger() + return r.readInteger() # Sorted sets proc zadd*(r: TRedis, key: string, score: int, member: string): TRedisInteger = ## Add a member to a sorted set, or update its score if it already exists r.sendCommand("ZADD", key, $score, member) - return r.parseInteger() + return r.readInteger() proc zcard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a sorted set r.sendCommand("ZCARD", key) - return r.parseInteger() + return r.readInteger() proc zcount*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Count the members in a sorted set with scores within the given values r.sendCommand("ZCOUNT", key, min, max) - return r.parseInteger() + return r.readInteger() proc zincrby*(r: TRedis, key: string, increment: string, member: string): TRedisString = ## Increment the score of a member in a sorted set r.sendCommand("ZINCRBY", key, increment, member) - return r.parseBulkString() + return r.readBulkString() proc zinterstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -680,7 +692,7 @@ proc zinterstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZINTERSTORE", args) - return r.parseInteger() + return r.readInteger() proc zrange*(r: TRedis, key: string, start: string, stop: string, withScores: bool): TRedisList = @@ -689,7 +701,7 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string, r.sendCommand("ZRANGE", key, start, stop) else: r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) - return r.parseArray() + return r.readArray() proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -704,29 +716,29 @@ proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZRANGEBYSCORE", args) - return r.parseArray() + return r.readArray() proc zrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set r.sendCommand("ZRANK", key, member) - return r.parseBulkString() + return r.readBulkString() proc zrem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a sorted set r.sendCommand("ZREM", key, member) - return r.parseInteger() + return r.readInteger() proc zremrangebyrank*(r: TRedis, key: string, start: string, stop: string): TRedisInteger = ## Remove all members in a sorted set within the given indexes r.sendCommand("ZREMRANGEBYRANK", key, start, stop) - return r.parseInteger() + return r.readInteger() proc zremrangebyscore*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Remove all members in a sorted set within the given scores r.sendCommand("ZREMRANGEBYSCORE", key, min, max) - return r.parseInteger() + return r.readInteger() proc zrevrange*(r: TRedis, key: string, start: string, stop: string, withScore: bool): TRedisList = @@ -735,7 +747,7 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string, if withScore: r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop) else: r.sendCommand("ZREVRANGE", key, start, stop) - return r.parseArray() + return r.readArray() proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -751,18 +763,18 @@ proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZREVRANGEBYSCORE", args) - return r.parseArray() + return r.readArray() proc zrevrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set, with ## scores ordered from high to low r.sendCommand("ZREVRANK", key, member) - return r.parseBulkString() + return r.readBulkString() proc zscore*(r: TRedis, key: string, member: string): TRedisString = ## Get the score associated with the given member in a sorted set r.sendCommand("ZSCORE", key, member) - return r.parseBulkString() + return r.readBulkString() proc zunionstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -780,7 +792,7 @@ proc zunionstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZUNIONSTORE", args) - return r.parseInteger() + return r.readInteger() # Pub/Sub @@ -795,7 +807,7 @@ proc psubscribe*(r: TRedis, pattern: openarray[string]): ???? = proc publish*(r: TRedis, channel: string, message: string): TRedisInteger = ## Post a message to a channel r.socket.send("PUBLISH $# $#\c\L" % [channel, message]) - return r.parseInteger() + return r.readInteger() proc punsubscribe*(r: TRedis, [pattern: openarray[string], : string): ???? = ## Stop listening for messages posted to channels matching the given patterns @@ -819,7 +831,7 @@ proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = proc discardMulti*(r: TRedis) = ## Discard all commands issued after MULTI r.sendCommand("DISCARD") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI @@ -832,83 +844,83 @@ proc exec*(r: TRedis): TRedisList = proc multi*(r: TRedis) = ## Mark the start of a transaction block - r.sePPipeline(true) + r.setPipeline(true) r.sendCommand("MULTI") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc unwatch*(r: TRedis) = ## Forget about all watched keys r.sendCommand("UNWATCH") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc watch*(r: TRedis, key: varargs[string]) = ## Watch the given keys to determine execution of the MULTI/EXEC block r.sendCommand("WATCH", key) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) # Connection proc auth*(r: TRedis, password: string) = ## Authenticate to the server r.sendCommand("AUTH", password) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc echoServ*(r: TRedis, message: string): TRedisString = ## Echo the given string r.sendCommand("ECHO", message) - return r.parseBulkString() + return r.readBulkString() proc ping*(r: TRedis): TRedisStatus = ## Ping the server r.sendCommand("PING") - return r.parseStatus() + return r.readStatus() proc quit*(r: TRedis) = ## Close the connection r.sendCommand("QUIT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc select*(r: TRedis, index: int): TRedisStatus = ## Change the selected database for the current connection r.sendCommand("SELECT", $index) - return r.parseStatus() + return r.readStatus() # Server proc bgrewriteaof*(r: TRedis) = ## Asynchronously rewrite the append-only file r.sendCommand("BGREWRITEAOF") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc bgsave*(r: TRedis) = ## Asynchronously save the dataset to disk r.sendCommand("BGSAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc configGet*(r: TRedis, parameter: string): TRedisList = ## Get the value of a configuration parameter r.sendCommand("CONFIG", "GET", parameter) - return r.parseArray() + return r.readArray() proc configSet*(r: TRedis, parameter: string, value: string) = ## Set a configuration parameter to the given value r.sendCommand("CONFIG", "SET", parameter, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc configResetStat*(r: TRedis) = ## Reset the stats returned by INFO r.sendCommand("CONFIG", "RESETSTAT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc dbsize*(r: TRedis): TRedisInteger = ## Return the number of keys in the selected database r.sendCommand("DBSIZE") - return r.parseInteger() + return r.readInteger() proc debugObject*(r: TRedis, key: string): TRedisStatus = ## Get debugging information about a key r.sendCommand("DEBUG", "OBJECT", key) - return r.parseStatus() + return r.readStatus() proc debugSegfault*(r: TRedis) = ## Make the server crash @@ -917,34 +929,34 @@ proc debugSegfault*(r: TRedis) = proc flushall*(r: TRedis): TRedisStatus = ## Remove all keys from all databases r.sendCommand("FLUSHALL") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc flushdb*(r: TRedis): TRedisStatus = ## Remove all keys from the current database r.sendCommand("FLUSHDB") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc info*(r: TRedis): TRedisString = ## Get information and statistics about the server r.sendCommand("INFO") - return r.parseBulkString() + return r.readBulkString() proc lastsave*(r: TRedis): TRedisInteger = ## Get the UNIX time stamp of the last successful save to disk r.sendCommand("LASTSAVE") - return r.parseInteger() + return r.readInteger() discard """ proc monitor*(r: TRedis) = ## Listen for all requests received by the server in real time r.socket.send("MONITOR\c\L") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) """ proc save*(r: TRedis) = ## Synchronously save the dataset to disk r.sendCommand("SAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server @@ -956,7 +968,7 @@ proc shutdown*(r: TRedis) = proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master r.sendCommand("SLAVEOF", host, port) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus()) iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. @@ -1015,10 +1027,10 @@ when false: echo() echo("Testing with pipelining.") echo() - r.sePPipeline(true) + r.setPipeline(true) r.someTests() var list = r.flushPipeline() - r.sePPipeline(false) + r.setPipeline(false) echo("-- list length is " & $list.len & " --") for item in list: if not isNil(item): From ebe174c8687e02404a986e17dffc773378dc98e7 Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Thu, 10 Apr 2014 22:27:09 -0700 Subject: [PATCH 04/10] delete echo statements used for debugging --- lib/pure/redis.nim | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 37a2a3f165..147a7a82a4 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -84,9 +84,7 @@ proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = return line.substr(1) # Strip '+' proc readStatus(r:TRedis): TRedisStatus = - echo "top of readStatus" r.readSocket("OK") - echo "line is " & line return r.parseStatus(line) proc parseInteger(r: TRedis, line: string = ""): TRedisInteger = From 1068022dfbf170dc34bf39e45a44451851fcb06f Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Thu, 17 Apr 2014 03:47:44 -0700 Subject: [PATCH 05/10] Allow QUEUED reply only if pipelined; don't return status replies from flushPipeline; Rewrite someTests --- lib/pure/redis.nim | 184 +++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 90 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 147a7a82a4..c7fcc033f5 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -58,8 +58,10 @@ proc raiseInvalidReply(expected, got: char) = "Expected '$1' at the beginning of a status reply got '$2'" % [$expected, $got]) -proc raiseNoOK(status: string) = - if status != "QUEUED" and status != "OK": +proc raiseNoOK(status: string, pipelineEnabled:bool) = + if pipelineEnabled and not (status == "QUEUED" or status == "PIPELINED"): + raise newException(EInvalidReply, "Expected \"QUEUED\" or \"PIPELINED\" got \"$1\"" % status) + elif not pipelineEnabled and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) template readSocket(r: TRedis, dummyVal:expr): stmt = @@ -71,7 +73,7 @@ template readSocket(r: TRedis, dummyVal:expr): stmt = proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = if r.pipeline.enabled: - return "OK" + return "PIPELINED" if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -84,14 +86,14 @@ proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = return line.substr(1) # Strip '+' proc readStatus(r:TRedis): TRedisStatus = - r.readSocket("OK") + r.readSocket("PIPELINED") return r.parseStatus(line) proc parseInteger(r: TRedis, line: string = ""): TRedisInteger = if r.pipeline.enabled: return -1 - if line == "+QUEUED": # inside of multi - return -1 + #if line == "+QUEUED": # inside of multi + # return -1 if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -193,7 +195,6 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = r.socket.send(r.pipeline.buffer) r.pipeline.buffer = "" - var prevState = r.pipeline.enabled r.pipeline.enabled = false result = @[] @@ -201,23 +202,21 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = for i in 0..tot-1: var ret = r.readNext() - if ret.len == 1 and (ret[0] == "OK" or ret[0] == "QUEUED"): - # Skip acknowledgement replies in multi - if not wasMulti: result.add(ret) - else: - result.add(ret) + for item in ret: + var isOK = item.contains("OK") + if not (item.contains("OK") or item.contains("QUEUED")): + result.add(item) r.pipeline.expected = 0 - r.pipeline.enabled = prevState -proc setPipeline*(r: TRedis, state: bool) = - ## Enable or disable command pipelining (reduces network roundtrips). +proc startPipelining*(r: TRedis) = + ## Enable command pipelining (reduces network roundtrips). ## Note that when enabled, you must call flushPipeline to actually send commands, except ## for multi/exec() which enable and flush the pipeline automatically. ## Commands return immediately with dummy values; actual results returned from ## flushPipeline() or exec() r.pipeline.expected = 0 - r.pipeline.enabled = state + r.pipeline.enabled = true proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = var request = "*" & $(1 + args.len()) & "\c\L" @@ -300,7 +299,7 @@ proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. @@ -372,7 +371,7 @@ proc setk*(r: TRedis, key, value: string) = ## ## NOTE: This function had to be renamed due to a clash with the `set` type. r.sendCommand("SET", key, value) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setNX*(r: TRedis, key, value: string): bool = ## Set the value of a key, only if the key does not exist. Returns `true` @@ -389,7 +388,7 @@ proc setBit*(r: TRedis, key: string, offset: int, proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = @@ -452,7 +451,7 @@ proc hMSet*(r: TRedis, key: string, args.add(field) args.add(value) r.sendCommand("HMSET", args) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field @@ -546,12 +545,12 @@ proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger proc lSet*(r: TRedis, key: string, index: int, value: string) = ## Set the value of an element in a list by its index r.sendCommand("LSET", key, $index, value) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) -proc lTrim*(r: TRedis, key: string, start, stop: int) = +proc lTrim*(r: TRedis, key: string, start, stop: int) = ## Trim a list to the specified range r.sendCommand("LTRIM", key, $start, $stop) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list @@ -829,7 +828,7 @@ proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = proc discardMulti*(r: TRedis) = ## Discard all commands issued after MULTI r.sendCommand("DISCARD") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI @@ -842,26 +841,26 @@ proc exec*(r: TRedis): TRedisList = proc multi*(r: TRedis) = ## Mark the start of a transaction block - r.setPipeline(true) + r.startPipelining() r.sendCommand("MULTI") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc unwatch*(r: TRedis) = ## Forget about all watched keys r.sendCommand("UNWATCH") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc watch*(r: TRedis, key: varargs[string]) = ## Watch the given keys to determine execution of the MULTI/EXEC block r.sendCommand("WATCH", key) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) # Connection proc auth*(r: TRedis, password: string) = ## Authenticate to the server r.sendCommand("AUTH", password) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc echoServ*(r: TRedis, message: string): TRedisString = ## Echo the given string @@ -876,7 +875,7 @@ proc ping*(r: TRedis): TRedisStatus = proc quit*(r: TRedis) = ## Close the connection r.sendCommand("QUIT") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc select*(r: TRedis, index: int): TRedisStatus = ## Change the selected database for the current connection @@ -888,12 +887,12 @@ proc select*(r: TRedis, index: int): TRedisStatus = proc bgrewriteaof*(r: TRedis) = ## Asynchronously rewrite the append-only file r.sendCommand("BGREWRITEAOF") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc bgsave*(r: TRedis) = ## Asynchronously save the dataset to disk r.sendCommand("BGSAVE") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configGet*(r: TRedis, parameter: string): TRedisList = ## Get the value of a configuration parameter @@ -903,12 +902,12 @@ proc configGet*(r: TRedis, parameter: string): TRedisList = proc configSet*(r: TRedis, parameter: string, value: string) = ## Set a configuration parameter to the given value r.sendCommand("CONFIG", "SET", parameter, value) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configResetStat*(r: TRedis) = ## Reset the stats returned by INFO r.sendCommand("CONFIG", "RESETSTAT") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc dbsize*(r: TRedis): TRedisInteger = ## Return the number of keys in the selected database @@ -927,12 +926,12 @@ proc debugSegfault*(r: TRedis) = proc flushall*(r: TRedis): TRedisStatus = ## Remove all keys from all databases r.sendCommand("FLUSHALL") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc flushdb*(r: TRedis): TRedisStatus = ## Remove all keys from the current database r.sendCommand("FLUSHDB") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc info*(r: TRedis): TRedisString = ## Get information and statistics about the server @@ -948,13 +947,13 @@ discard """ proc monitor*(r: TRedis) = ## Listen for all requests received by the server in real time r.socket.send("MONITOR\c\L") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) """ proc save*(r: TRedis) = ## Synchronously save the dataset to disk r.sendCommand("SAVE") - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server @@ -966,7 +965,7 @@ proc shutdown*(r: TRedis) = proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master r.sendCommand("SLAVEOF", host, port) - raiseNoOK(r.readStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. @@ -979,68 +978,73 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = else: yield (k, i) k = "" - -proc someTests(r: TRedis) = - #r.auth("pass") + +proc someTests(r: TRedis, how: string):seq[string] = + var list:seq[string] = @[] + + case how + of "pipelined": + r.startPipelining() + of "multi": + r.multi() r.setk("nim:test", "Testing something.") r.setk("nim:utf8", "こんにちは") r.setk("nim:esc", "\\ths ągt\\") r.setk("nim:int", "1") - echo(r.get("nim:esc")) - echo(r.incr("nim:int")) - echo r.get("nim:int") - echo r.get("nim:utf8") - echo r.hSet("test1", "name", "A Test") + list.add(r.get("nim:esc")) + list.add($(r.incr("nim:int"))) + list.add(r.get("nim:int")) + list.add(r.get("nim:utf8")) + list.add($(r.hSet("test1", "name", "A Test"))) var res = r.hGetAll("test1") - echo repr(r.get("blahasha")) - echo r.randomKey() - discard r.lpush("mylist","itema") - discard r.lpush("mylist","itemb") + for r in res: + list.add(r) + list.add(r.get("invalid_key")) + list.add($(r.lpush("mylist","itema"))) + list.add($(r.lpush("mylist","itemb"))) r.ltrim("mylist",0,1) var p = r.lrange("mylist", 0, -1) - + for i in items(p): if not isNil(i): - echo(" ", i) - - echo(r.debugObject("mylist")) + list.add(i) + + list.add(r.debugObject("mylist")) r.configSet("timeout", "299") - for i in items(r.configGet("timeout")): echo ">> ", i + var g = r.configGet("timeout") + for i in items(g): + list.add(i) - echo r.echoServ("BLAH") - - -when false: - var r = open() - - # Test with no pipelining - echo("----------------------------------------------") - echo("Testing without pipelining.") - r.someTests() + list.add(r.echoServ("BLAH")) - # Test with pipelining enabled - echo("//////////////////////////////////////////////") - echo() - echo("Testing with pipelining.") - echo() - r.setPipeline(true) - r.someTests() - var list = r.flushPipeline() - r.setPipeline(false) - echo("-- list length is " & $list.len & " --") - for item in list: - if not isNil(item): - echo item - - # Test with multi/exec() (automatic pipelining) - echo("************************************************") - echo("Testing with transaction (automatic pipelining)") - r.multi() - r.someTests() - list = r.exec() - echo("-- list length is " & $list.len & " --") - for item in list: - if not isNil(item): - echo item + case how + of "normal": + return list + of "pipelined": + return r.flushPipeline() + of "multi": + return r.exec() + +proc assertListsIdentical(listA, listB: seq[string]) = + assert(listA.len == listB.len) + var i = 0 + for item in listA: + assert(item == listB[i]) + i = i + 1 + +when isMainModule: + when false: + var r = open() + + # Test with no pipelining + var listNormal = r.someTests("normal") + + # Test with pipelining enabled + var listPipelined = r.someTests("pipelined") + assertListsIdentical(listNormal, listPipelined) + + # Test with multi/exec() (automatic pipelining) + var listMulti = r.someTests("multi") + assertListsIdentical(listNormal, listMulti) From 7584d867e1740db3f95d0138c64baa89602efc91 Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Thu, 17 Apr 2014 03:52:33 -0700 Subject: [PATCH 06/10] delete extra statement from debugging --- lib/pure/redis.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index c7fcc033f5..ae61a2b687 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -203,7 +203,6 @@ proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = for i in 0..tot-1: var ret = r.readNext() for item in ret: - var isOK = item.contains("OK") if not (item.contains("OK") or item.contains("QUEUED")): result.add(item) From d7caba8b6510560930e76230624b7ff62f1cb15f Mon Sep 17 00:00:00 2001 From: Jason Livesay Date: Sun, 20 Apr 2014 04:29:24 -0700 Subject: [PATCH 07/10] Use enum --- lib/pure/redis.nim | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index ae61a2b687..959f5c6efd 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -25,6 +25,10 @@ type buffer: string expected: int ## number of replies expected if pipelined +type + TSendMode = enum + normal, pipelined, multiple + type TRedis* {.pure, final.} = object socket: TSocket @@ -978,15 +982,14 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = yield (k, i) k = "" -proc someTests(r: TRedis, how: string):seq[string] = +proc someTests(r: TRedis, how: TSendMode):seq[string] = var list:seq[string] = @[] - case how - of "pipelined": + if how == pipelined: r.startPipelining() - of "multi": + elif how == multiple: r.multi() - + r.setk("nim:test", "Testing something.") r.setk("nim:utf8", "こんにちは") r.setk("nim:esc", "\\ths ągt\\") @@ -1019,11 +1022,11 @@ proc someTests(r: TRedis, how: string):seq[string] = list.add(r.echoServ("BLAH")) case how - of "normal": + of normal: return list - of "pipelined": + of pipelined: return r.flushPipeline() - of "multi": + of multiple: return r.exec() proc assertListsIdentical(listA, listB: seq[string]) = @@ -1038,12 +1041,12 @@ when isMainModule: var r = open() # Test with no pipelining - var listNormal = r.someTests("normal") + var listNormal = r.someTests(normal) # Test with pipelining enabled - var listPipelined = r.someTests("pipelined") + var listPipelined = r.someTests(pipelined) assertListsIdentical(listNormal, listPipelined) # Test with multi/exec() (automatic pipelining) - var listMulti = r.someTests("multi") + var listMulti = r.someTests(multiple) assertListsIdentical(listNormal, listMulti) From cf3b54fdcb2a3a5f7a061632c8f9156fa9cddbb4 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 20 Apr 2014 13:29:08 +0100 Subject: [PATCH 08/10] Removes tthreadanalysis3 from threadTests spec. --- tests/testament/categories.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index faccfed57d..bb9c90d2ac 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -151,7 +151,7 @@ proc threadTests(r: var TResults, cat: Category, options: string) = #test "tthreadanalysis" #test "tthreadsort" test "tthreadanalysis2" - test "tthreadanalysis3" + #test "tthreadanalysis3" test "tthreadheapviolation1" # ------------------------- IO tests ------------------------------------------ From 232d2528859f7eb6a47bdc274bcba67c8fdaedff Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 20 Apr 2014 14:33:44 +0100 Subject: [PATCH 09/10] Added new future module with a closure macro. --- doc/lib.txt | 3 + lib/pure/future.nim | 111 ++++++++++++++++++++++++++++++++ tests/closure/tclosuremacro.nim | 43 +++++++++++++ web/nimrod.ini | 2 +- 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 lib/pure/future.nim create mode 100644 tests/closure/tclosuremacro.nim diff --git a/doc/lib.txt b/doc/lib.txt index a209357f7c..3ca519c9ed 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -373,6 +373,9 @@ Miscellaneous * `logging `_ This module implements a simple logger. +* `future `_ + This module implements new experimental features. Currently the syntax + sugar for anonymous procedures. Database support ---------------- diff --git a/lib/pure/future.nim b/lib/pure/future.nim new file mode 100644 index 0000000000..1eb95df2c0 --- /dev/null +++ b/lib/pure/future.nim @@ -0,0 +1,111 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements experimental features which may soon be moved to +## the system module (or other more appropriate modules). + +import macros + +proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} = + #echo treeRepr(p) + #echo treeRepr(b) + result = newNimNode(nnkProcTy) + var formalParams = newNimNode(nnkFormalParams) + + expectKind(b, nnkIdent) + formalParams.add b + + case p.kind + of nnkPar: + for i in 0 .. `*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for anonymous procedures. + ## + ## ..code-block:: nimrod + ## + ## proc passTwoAndTwo(f: (int, int) -> int): int = + ## f(2, 2) + ## + ## passTwoAndTwo((x, y) => x + y) # 4 + + echo treeRepr(p) + #echo(treeRepr(b)) + var params: seq[PNimrodNode] = @[newIdentNode("auto")] + + case p.kind + of nnkPar: + for c in children(p): + var identDefs = newNimNode(nnkIdentDefs) + case c.kind + of nnkExprColonExpr: + identDefs.add(c[0]) + identDefs.add(c[1]) + identDefs.add(newEmptyNode()) + of nnkIdent: + identDefs.add(c) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + else: + error("Incorrect procedure parameter list.") + params.add(identDefs) + of nnkIdent: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add(p) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + params.add(identDefs) + of nnkInfix: + if p[0].kind == nnkIdent and p[0].ident == !"->": + var procTy = createProcType(p[1], p[2]) + params[0] = procTy[0][0] + for i in 1 .. ) got (" & $p[0].ident & ").") + else: + error("Incorrect procedure parameter list.") + result = newProc(params = params, body = b, procType = nnkLambda) + #echo(result.treeRepr) + echo(result.toStrLit()) + #return result # TODO: Bug? + +macro `->`*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for procedure types. + ## + ## ..code-block:: nimrod + ## + ## proc pass2(f: (float, float) -> float): float = + ## f(2, 2) + ## + ## # is the same as: + ## + ## proc pass2(f: proc (x, y: float): float): float = + ## f(2, 2) + + createProcType(p, b) diff --git a/tests/closure/tclosuremacro.nim b/tests/closure/tclosuremacro.nim new file mode 100644 index 0000000000..80d89a0901 --- /dev/null +++ b/tests/closure/tclosuremacro.nim @@ -0,0 +1,43 @@ +discard """ + output: '''10 +10 +10 +3 +3 +noReturn +''' +""" + +import future + +when false: + proc twoParams(x: (int, int) -> int): int = + result = x(5, 5) + + proc oneParam(x: int -> int): int = + x(5) + + proc noParams(x: () -> int): int = + result = x() + + proc noReturn(x: () -> void) = + x() + + proc doWithOneAndTwo(f: (int, int) -> int): int = + f(1,2) + + echo twoParams(proc (a, b): auto = a + b) + echo twoParams((x, y) => x + y) + + echo oneParam(x => x+5) + + echo noParams(() => 3) + + echo doWithOneAndTwo((x, y) => x + y) + + noReturn(() -> void => echo("noReturn")) + +proc pass2(f: (int, int) -> int): (int) -> int = + (x: int) -> int => f(2, x) + +#echo pass2((x, y) => x + y) diff --git a/web/nimrod.ini b/web/nimrod.ini index b29bcff309..14701ecea4 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -63,7 +63,7 @@ srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" srcdoc2: "packages/docutils/rstgen;pure/logging;pure/asyncdispatch;pure/asyncnet" -srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors" +srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" From ed935dfef1bd3b43932c3f9190ff68a68213cc3b Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 20 Apr 2014 14:38:10 +0100 Subject: [PATCH 10/10] Remove echo from => macro and fix tclosuremacro test. --- lib/pure/future.nim | 4 +-- tests/closure/tclosuremacro.nim | 58 ++++++++++++++++----------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 1eb95df2c0..2401c4f722 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -54,7 +54,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} = ## ## passTwoAndTwo((x, y) => x + y) # 4 - echo treeRepr(p) + #echo treeRepr(p) #echo(treeRepr(b)) var params: seq[PNimrodNode] = @[newIdentNode("auto")] @@ -92,7 +92,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} = error("Incorrect procedure parameter list.") result = newProc(params = params, body = b, procType = nnkLambda) #echo(result.treeRepr) - echo(result.toStrLit()) + #echo(result.toStrLit()) #return result # TODO: Bug? macro `->`*(p, b: expr): expr {.immediate.} = diff --git a/tests/closure/tclosuremacro.nim b/tests/closure/tclosuremacro.nim index 80d89a0901..008078bbb6 100644 --- a/tests/closure/tclosuremacro.nim +++ b/tests/closure/tclosuremacro.nim @@ -10,34 +10,34 @@ noReturn import future +proc twoParams(x: (int, int) -> int): int = + result = x(5, 5) + +proc oneParam(x: int -> int): int = + x(5) + +proc noParams(x: () -> int): int = + result = x() + +proc noReturn(x: () -> void) = + x() + +proc doWithOneAndTwo(f: (int, int) -> int): int = + f(1,2) + +echo twoParams(proc (a, b): auto = a + b) +echo twoParams((x, y) => x + y) + +echo oneParam(x => x+5) + +echo noParams(() => 3) + +echo doWithOneAndTwo((x, y) => x + y) + +noReturn(() -> void => echo("noReturn")) + when false: - proc twoParams(x: (int, int) -> int): int = - result = x(5, 5) + proc pass2(f: (int, int) -> int): (int) -> int = + (x: int) -> int => f(2, x) - proc oneParam(x: int -> int): int = - x(5) - - proc noParams(x: () -> int): int = - result = x() - - proc noReturn(x: () -> void) = - x() - - proc doWithOneAndTwo(f: (int, int) -> int): int = - f(1,2) - - echo twoParams(proc (a, b): auto = a + b) - echo twoParams((x, y) => x + y) - - echo oneParam(x => x+5) - - echo noParams(() => 3) - - echo doWithOneAndTwo((x, y) => x + y) - - noReturn(() -> void => echo("noReturn")) - -proc pass2(f: (int, int) -> int): (int) -> int = - (x: int) -> int => f(2, x) - -#echo pass2((x, y) => x + y) + #echo pass2((x, y) => x + y)