hiredis 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,116 @@
1
+ /*
2
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
4
+ *
5
+ * All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions are met:
9
+ *
10
+ * * Redistributions of source code must retain the above copyright notice,
11
+ * this list of conditions and the following disclaimer.
12
+ * * Redistributions in binary form must reproduce the above copyright
13
+ * notice, this list of conditions and the following disclaimer in the
14
+ * documentation and/or other materials provided with the distribution.
15
+ * * Neither the name of Redis nor the names of its contributors may be used
16
+ * to endorse or promote products derived from this software without
17
+ * specific prior written permission.
18
+ *
19
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ * POSSIBILITY OF SUCH DAMAGE.
30
+ */
31
+
32
+
33
+ #ifndef __HIREDIS_READ_H
34
+ #define __HIREDIS_READ_H
35
+ #include <stdio.h> /* for size_t */
36
+
37
+ #define REDIS_ERR -1
38
+ #define REDIS_OK 0
39
+
40
+ /* When an error occurs, the err flag in a context is set to hold the type of
41
+ * error that occured. REDIS_ERR_IO means there was an I/O error and you
42
+ * should use the "errno" variable to find out what is wrong.
43
+ * For other values, the "errstr" field will hold a description. */
44
+ #define REDIS_ERR_IO 1 /* Error in read or write */
45
+ #define REDIS_ERR_EOF 3 /* End of file */
46
+ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */
47
+ #define REDIS_ERR_OOM 5 /* Out of memory */
48
+ #define REDIS_ERR_OTHER 2 /* Everything else... */
49
+
50
+ #define REDIS_REPLY_STRING 1
51
+ #define REDIS_REPLY_ARRAY 2
52
+ #define REDIS_REPLY_INTEGER 3
53
+ #define REDIS_REPLY_NIL 4
54
+ #define REDIS_REPLY_STATUS 5
55
+ #define REDIS_REPLY_ERROR 6
56
+
57
+ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
58
+
59
+ #ifdef __cplusplus
60
+ extern "C" {
61
+ #endif
62
+
63
+ typedef struct redisReadTask {
64
+ int type;
65
+ int elements; /* number of elements in multibulk container */
66
+ int idx; /* index in parent (array) object */
67
+ void *obj; /* holds user-generated value for a read task */
68
+ struct redisReadTask *parent; /* parent task */
69
+ void *privdata; /* user-settable arbitrary field */
70
+ } redisReadTask;
71
+
72
+ typedef struct redisReplyObjectFunctions {
73
+ void *(*createString)(const redisReadTask*, char*, size_t);
74
+ void *(*createArray)(const redisReadTask*, int);
75
+ void *(*createInteger)(const redisReadTask*, long long);
76
+ void *(*createNil)(const redisReadTask*);
77
+ void (*freeObject)(void*);
78
+ } redisReplyObjectFunctions;
79
+
80
+ typedef struct redisReader {
81
+ int err; /* Error flags, 0 when there is no error */
82
+ char errstr[128]; /* String representation of error when applicable */
83
+
84
+ char *buf; /* Read buffer */
85
+ size_t pos; /* Buffer cursor */
86
+ size_t len; /* Buffer length */
87
+ size_t maxbuf; /* Max length of unused buffer */
88
+
89
+ redisReadTask rstack[9];
90
+ int ridx; /* Index of current read task */
91
+ void *reply; /* Temporary reply pointer */
92
+
93
+ redisReplyObjectFunctions *fn;
94
+ void *privdata;
95
+ } redisReader;
96
+
97
+ /* Public API for the protocol parser. */
98
+ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
99
+ void redisReaderFree(redisReader *r);
100
+ int redisReaderFeed(redisReader *r, const char *buf, size_t len);
101
+ int redisReaderGetReply(redisReader *r, void **reply);
102
+
103
+ /* Backwards compatibility, can be removed on big version bump. */
104
+ #define redisReplyReaderCreate redisReaderCreate
105
+ #define redisReplyReaderFree redisReaderFree
106
+ #define redisReplyReaderFeed redisReaderFeed
107
+ #define redisReplyReaderGetReply redisReaderGetReply
108
+ #define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
109
+ #define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
110
+ #define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
111
+
112
+ #ifdef __cplusplus
113
+ }
114
+ #endif
115
+
116
+ #endif
@@ -1,6 +1,6 @@
1
- /* SDSLib, A C dynamic strings library
1
+ /* SDS (Simple Dynamic Strings), A C dynamic strings library.
2
2
  *
3
- * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
3
+ * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
4
4
  * All rights reserved.
5
5
  *
6
6
  * Redistribution and use in source and binary forms, with or without
@@ -32,83 +32,188 @@
32
32
  #include <stdlib.h>
33
33
  #include <string.h>
34
34
  #include <ctype.h>
35
- #include "sds.h"
35
+ #include <assert.h>
36
36
 
37
- #ifdef SDS_ABORT_ON_OOM
38
- static void sdsOomAbort(void) {
39
- fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
40
- abort();
41
- }
42
- #endif
37
+ #include "sds.h"
43
38
 
39
+ /* Create a new sds string with the content specified by the 'init' pointer
40
+ * and 'initlen'.
41
+ * If NULL is used for 'init' the string is initialized with zero bytes.
42
+ *
43
+ * The string is always null-termined (all the sds strings are, always) so
44
+ * even if you create an sds string with:
45
+ *
46
+ * mystring = sdsnewlen("abc",3");
47
+ *
48
+ * You can print the string with printf() as there is an implicit \0 at the
49
+ * end of the string. However the string is binary safe and can contain
50
+ * \0 characters in the middle, as the length is stored in the sds header. */
44
51
  sds sdsnewlen(const void *init, size_t initlen) {
45
52
  struct sdshdr *sh;
46
53
 
47
- sh = malloc(sizeof(struct sdshdr)+initlen+1);
48
- #ifdef SDS_ABORT_ON_OOM
49
- if (sh == NULL) sdsOomAbort();
50
- #else
54
+ if (init) {
55
+ sh = malloc(sizeof *sh+initlen+1);
56
+ } else {
57
+ sh = calloc(sizeof *sh+initlen+1,1);
58
+ }
51
59
  if (sh == NULL) return NULL;
52
- #endif
53
60
  sh->len = initlen;
54
61
  sh->free = 0;
55
- if (initlen) {
56
- if (init) memcpy(sh->buf, init, initlen);
57
- else memset(sh->buf,0,initlen);
58
- }
62
+ if (initlen && init)
63
+ memcpy(sh->buf, init, initlen);
59
64
  sh->buf[initlen] = '\0';
60
65
  return (char*)sh->buf;
61
66
  }
62
67
 
68
+ /* Create an empty (zero length) sds string. Even in this case the string
69
+ * always has an implicit null term. */
63
70
  sds sdsempty(void) {
64
71
  return sdsnewlen("",0);
65
72
  }
66
73
 
74
+ /* Create a new sds string starting from a null termined C string. */
67
75
  sds sdsnew(const char *init) {
68
76
  size_t initlen = (init == NULL) ? 0 : strlen(init);
69
77
  return sdsnewlen(init, initlen);
70
78
  }
71
79
 
80
+ /* Duplicate an sds string. */
72
81
  sds sdsdup(const sds s) {
73
82
  return sdsnewlen(s, sdslen(s));
74
83
  }
75
84
 
85
+ /* Free an sds string. No operation is performed if 's' is NULL. */
76
86
  void sdsfree(sds s) {
77
87
  if (s == NULL) return;
78
88
  free(s-sizeof(struct sdshdr));
79
89
  }
80
90
 
91
+ /* Set the sds string length to the length as obtained with strlen(), so
92
+ * considering as content only up to the first null term character.
93
+ *
94
+ * This function is useful when the sds string is hacked manually in some
95
+ * way, like in the following example:
96
+ *
97
+ * s = sdsnew("foobar");
98
+ * s[2] = '\0';
99
+ * sdsupdatelen(s);
100
+ * printf("%d\n", sdslen(s));
101
+ *
102
+ * The output will be "2", but if we comment out the call to sdsupdatelen()
103
+ * the output will be "6" as the string was modified but the logical length
104
+ * remains 6 bytes. */
81
105
  void sdsupdatelen(sds s) {
82
- struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
106
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
83
107
  int reallen = strlen(s);
84
108
  sh->free += (sh->len-reallen);
85
109
  sh->len = reallen;
86
110
  }
87
111
 
88
- static sds sdsMakeRoomFor(sds s, size_t addlen) {
112
+ /* Modify an sds string on-place to make it empty (zero length).
113
+ * However all the existing buffer is not discarded but set as free space
114
+ * so that next append operations will not require allocations up to the
115
+ * number of bytes previously available. */
116
+ void sdsclear(sds s) {
117
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
118
+ sh->free += sh->len;
119
+ sh->len = 0;
120
+ sh->buf[0] = '\0';
121
+ }
122
+
123
+ /* Enlarge the free space at the end of the sds string so that the caller
124
+ * is sure that after calling this function can overwrite up to addlen
125
+ * bytes after the end of the string, plus one more byte for nul term.
126
+ *
127
+ * Note: this does not change the *length* of the sds string as returned
128
+ * by sdslen(), but only the free buffer space we have. */
129
+ sds sdsMakeRoomFor(sds s, size_t addlen) {
89
130
  struct sdshdr *sh, *newsh;
90
131
  size_t free = sdsavail(s);
91
132
  size_t len, newlen;
92
133
 
93
134
  if (free >= addlen) return s;
94
135
  len = sdslen(s);
95
- sh = (void*) (s-(sizeof(struct sdshdr)));
96
- newlen = (len+addlen)*2;
97
- newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
98
- #ifdef SDS_ABORT_ON_OOM
99
- if (newsh == NULL) sdsOomAbort();
100
- #else
136
+ sh = (void*) (s-sizeof *sh);;
137
+ newlen = (len+addlen);
138
+ if (newlen < SDS_MAX_PREALLOC)
139
+ newlen *= 2;
140
+ else
141
+ newlen += SDS_MAX_PREALLOC;
142
+ newsh = realloc(sh, sizeof *newsh+newlen+1);
101
143
  if (newsh == NULL) return NULL;
102
- #endif
103
144
 
104
145
  newsh->free = newlen - len;
105
146
  return newsh->buf;
106
147
  }
107
148
 
149
+ /* Reallocate the sds string so that it has no free space at the end. The
150
+ * contained string remains not altered, but next concatenation operations
151
+ * will require a reallocation.
152
+ *
153
+ * After the call, the passed sds string is no longer valid and all the
154
+ * references must be substituted with the new pointer returned by the call. */
155
+ sds sdsRemoveFreeSpace(sds s) {
156
+ struct sdshdr *sh;
157
+
158
+ sh = (void*) (s-sizeof *sh);;
159
+ sh = realloc(sh, sizeof *sh+sh->len+1);
160
+ sh->free = 0;
161
+ return sh->buf;
162
+ }
163
+
164
+ /* Return the total size of the allocation of the specifed sds string,
165
+ * including:
166
+ * 1) The sds header before the pointer.
167
+ * 2) The string.
168
+ * 3) The free buffer at the end if any.
169
+ * 4) The implicit null term.
170
+ */
171
+ size_t sdsAllocSize(sds s) {
172
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
173
+
174
+ return sizeof(*sh)+sh->len+sh->free+1;
175
+ }
176
+
177
+ /* Increment the sds length and decrements the left free space at the
178
+ * end of the string according to 'incr'. Also set the null term
179
+ * in the new end of the string.
180
+ *
181
+ * This function is used in order to fix the string length after the
182
+ * user calls sdsMakeRoomFor(), writes something after the end of
183
+ * the current string, and finally needs to set the new length.
184
+ *
185
+ * Note: it is possible to use a negative increment in order to
186
+ * right-trim the string.
187
+ *
188
+ * Usage example:
189
+ *
190
+ * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
191
+ * following schema, to cat bytes coming from the kernel to the end of an
192
+ * sds string without copying into an intermediate buffer:
193
+ *
194
+ * oldlen = sdslen(s);
195
+ * s = sdsMakeRoomFor(s, BUFFER_SIZE);
196
+ * nread = read(fd, s+oldlen, BUFFER_SIZE);
197
+ * ... check for nread <= 0 and handle it ...
198
+ * sdsIncrLen(s, nread);
199
+ */
200
+ void sdsIncrLen(sds s, int incr) {
201
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
202
+
203
+ assert(sh->free >= incr);
204
+ sh->len += incr;
205
+ sh->free -= incr;
206
+ assert(sh->free >= 0);
207
+ s[sh->len] = '\0';
208
+ }
209
+
108
210
  /* Grow the sds to have the specified length. Bytes that were not part of
109
- * the original length of the sds will be set to zero. */
211
+ * the original length of the sds will be set to zero.
212
+ *
213
+ * if the specified length is smaller than the current length, no operation
214
+ * is performed. */
110
215
  sds sdsgrowzero(sds s, size_t len) {
111
- struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
216
+ struct sdshdr *sh = (void*) (s-sizeof *sh);
112
217
  size_t totlen, curlen = sh->len;
113
218
 
114
219
  if (len <= curlen) return s;
@@ -116,7 +221,7 @@ sds sdsgrowzero(sds s, size_t len) {
116
221
  if (s == NULL) return NULL;
117
222
 
118
223
  /* Make sure added region doesn't contain garbage */
119
- sh = (void*)(s-(sizeof(struct sdshdr)));
224
+ sh = (void*)(s-sizeof *sh);
120
225
  memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
121
226
  totlen = sh->len+sh->free;
122
227
  sh->len = len;
@@ -124,13 +229,18 @@ sds sdsgrowzero(sds s, size_t len) {
124
229
  return s;
125
230
  }
126
231
 
232
+ /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
233
+ * end of the specified sds string 's'.
234
+ *
235
+ * After the call, the passed sds string is no longer valid and all the
236
+ * references must be substituted with the new pointer returned by the call. */
127
237
  sds sdscatlen(sds s, const void *t, size_t len) {
128
238
  struct sdshdr *sh;
129
239
  size_t curlen = sdslen(s);
130
240
 
131
241
  s = sdsMakeRoomFor(s,len);
132
242
  if (s == NULL) return NULL;
133
- sh = (void*) (s-(sizeof(struct sdshdr)));
243
+ sh = (void*) (s-sizeof *sh);;
134
244
  memcpy(s+curlen, t, len);
135
245
  sh->len = curlen+len;
136
246
  sh->free = sh->free-len;
@@ -138,18 +248,32 @@ sds sdscatlen(sds s, const void *t, size_t len) {
138
248
  return s;
139
249
  }
140
250
 
251
+ /* Append the specified null termianted C string to the sds string 's'.
252
+ *
253
+ * After the call, the passed sds string is no longer valid and all the
254
+ * references must be substituted with the new pointer returned by the call. */
141
255
  sds sdscat(sds s, const char *t) {
142
256
  return sdscatlen(s, t, strlen(t));
143
257
  }
144
258
 
145
- sds sdscpylen(sds s, char *t, size_t len) {
146
- struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
259
+ /* Append the specified sds 't' to the existing sds 's'.
260
+ *
261
+ * After the call, the modified sds string is no longer valid and all the
262
+ * references must be substituted with the new pointer returned by the call. */
263
+ sds sdscatsds(sds s, const sds t) {
264
+ return sdscatlen(s, t, sdslen(t));
265
+ }
266
+
267
+ /* Destructively modify the sds string 's' to hold the specified binary
268
+ * safe string pointed by 't' of length 'len' bytes. */
269
+ sds sdscpylen(sds s, const char *t, size_t len) {
270
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
147
271
  size_t totlen = sh->free+sh->len;
148
272
 
149
273
  if (totlen < len) {
150
274
  s = sdsMakeRoomFor(s,len-sh->len);
151
275
  if (s == NULL) return NULL;
152
- sh = (void*) (s-(sizeof(struct sdshdr)));
276
+ sh = (void*) (s-sizeof *sh);;
153
277
  totlen = sh->free+sh->len;
154
278
  }
155
279
  memcpy(s, t, len);
@@ -159,10 +283,80 @@ sds sdscpylen(sds s, char *t, size_t len) {
159
283
  return s;
160
284
  }
161
285
 
162
- sds sdscpy(sds s, char *t) {
286
+ /* Like sdscpylen() but 't' must be a null-termined string so that the length
287
+ * of the string is obtained with strlen(). */
288
+ sds sdscpy(sds s, const char *t) {
163
289
  return sdscpylen(s, t, strlen(t));
164
290
  }
165
291
 
292
+ /* Helper for sdscatlonglong() doing the actual number -> string
293
+ * conversion. 's' must point to a string with room for at least
294
+ * SDS_LLSTR_SIZE bytes.
295
+ *
296
+ * The function returns the lenght of the null-terminated string
297
+ * representation stored at 's'. */
298
+ #define SDS_LLSTR_SIZE 21
299
+ int sdsll2str(char *s, long long value) {
300
+ char *p, aux;
301
+ unsigned long long v;
302
+ size_t l;
303
+
304
+ /* Generate the string representation, this method produces
305
+ * an reversed string. */
306
+ v = (value < 0) ? -value : value;
307
+ p = s;
308
+ do {
309
+ *p++ = '0'+(v%10);
310
+ v /= 10;
311
+ } while(v);
312
+ if (value < 0) *p++ = '-';
313
+
314
+ /* Compute length and add null term. */
315
+ l = p-s;
316
+ *p = '\0';
317
+
318
+ /* Reverse the string. */
319
+ p--;
320
+ while(s < p) {
321
+ aux = *s;
322
+ *s = *p;
323
+ *p = aux;
324
+ s++;
325
+ p--;
326
+ }
327
+ return l;
328
+ }
329
+
330
+ /* Identical sdsll2str(), but for unsigned long long type. */
331
+ int sdsull2str(char *s, unsigned long long v) {
332
+ char *p, aux;
333
+ size_t l;
334
+
335
+ /* Generate the string representation, this method produces
336
+ * an reversed string. */
337
+ p = s;
338
+ do {
339
+ *p++ = '0'+(v%10);
340
+ v /= 10;
341
+ } while(v);
342
+
343
+ /* Compute length and add null term. */
344
+ l = p-s;
345
+ *p = '\0';
346
+
347
+ /* Reverse the string. */
348
+ p--;
349
+ while(s < p) {
350
+ aux = *s;
351
+ *s = *p;
352
+ *p = aux;
353
+ s++;
354
+ p--;
355
+ }
356
+ return l;
357
+ }
358
+
359
+ /* Like sdscatpritf() but gets va_list instead of being variadic. */
166
360
  sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
167
361
  va_list cpy;
168
362
  char *buf, *t;
@@ -170,11 +364,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
170
364
 
171
365
  while(1) {
172
366
  buf = malloc(buflen);
173
- #ifdef SDS_ABORT_ON_OOM
174
- if (buf == NULL) sdsOomAbort();
175
- #else
176
367
  if (buf == NULL) return NULL;
177
- #endif
178
368
  buf[buflen-2] = '\0';
179
369
  va_copy(cpy,ap);
180
370
  vsnprintf(buf, buflen, fmt, cpy);
@@ -190,6 +380,22 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
190
380
  return t;
191
381
  }
192
382
 
383
+ /* Append to the sds string 's' a string obtained using printf-alike format
384
+ * specifier.
385
+ *
386
+ * After the call, the modified sds string is no longer valid and all the
387
+ * references must be substituted with the new pointer returned by the call.
388
+ *
389
+ * Example:
390
+ *
391
+ * s = sdsnew("Sum is: ");
392
+ * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
393
+ *
394
+ * Often you need to create a string from scratch with the printf-alike
395
+ * format. When this is the need, just use sdsempty() as the target string:
396
+ *
397
+ * s = sdscatprintf(sdsempty(), "... your format ...", args);
398
+ */
193
399
  sds sdscatprintf(sds s, const char *fmt, ...) {
194
400
  va_list ap;
195
401
  char *t;
@@ -199,8 +405,143 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
199
405
  return t;
200
406
  }
201
407
 
202
- sds sdstrim(sds s, const char *cset) {
408
+ /* This function is similar to sdscatprintf, but much faster as it does
409
+ * not rely on sprintf() family functions implemented by the libc that
410
+ * are often very slow. Moreover directly handling the sds string as
411
+ * new data is concatenated provides a performance improvement.
412
+ *
413
+ * However this function only handles an incompatible subset of printf-alike
414
+ * format specifiers:
415
+ *
416
+ * %s - C String
417
+ * %S - SDS string
418
+ * %i - signed int
419
+ * %I - 64 bit signed integer (long long, int64_t)
420
+ * %u - unsigned int
421
+ * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
422
+ * %T - A size_t variable.
423
+ * %% - Verbatim "%" character.
424
+ */
425
+ sds sdscatfmt(sds s, char const *fmt, ...) {
203
426
  struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
427
+ size_t initlen = sdslen(s);
428
+ const char *f = fmt;
429
+ int i;
430
+ va_list ap;
431
+
432
+ va_start(ap,fmt);
433
+ f = fmt; /* Next format specifier byte to process. */
434
+ i = initlen; /* Position of the next byte to write to dest str. */
435
+ while(*f) {
436
+ char next, *str;
437
+ int l;
438
+ long long num;
439
+ unsigned long long unum;
440
+
441
+ /* Make sure there is always space for at least 1 char. */
442
+ if (sh->free == 0) {
443
+ s = sdsMakeRoomFor(s,1);
444
+ sh = (void*) (s-(sizeof(struct sdshdr)));
445
+ }
446
+
447
+ switch(*f) {
448
+ case '%':
449
+ next = *(f+1);
450
+ f++;
451
+ switch(next) {
452
+ case 's':
453
+ case 'S':
454
+ str = va_arg(ap,char*);
455
+ l = (next == 's') ? strlen(str) : sdslen(str);
456
+ if (sh->free < l) {
457
+ s = sdsMakeRoomFor(s,l);
458
+ sh = (void*) (s-(sizeof(struct sdshdr)));
459
+ }
460
+ memcpy(s+i,str,l);
461
+ sh->len += l;
462
+ sh->free -= l;
463
+ i += l;
464
+ break;
465
+ case 'i':
466
+ case 'I':
467
+ if (next == 'i')
468
+ num = va_arg(ap,int);
469
+ else
470
+ num = va_arg(ap,long long);
471
+ {
472
+ char buf[SDS_LLSTR_SIZE];
473
+ l = sdsll2str(buf,num);
474
+ if (sh->free < l) {
475
+ s = sdsMakeRoomFor(s,l);
476
+ sh = (void*) (s-(sizeof(struct sdshdr)));
477
+ }
478
+ memcpy(s+i,buf,l);
479
+ sh->len += l;
480
+ sh->free -= l;
481
+ i += l;
482
+ }
483
+ break;
484
+ case 'u':
485
+ case 'U':
486
+ case 'T':
487
+ if (next == 'u')
488
+ unum = va_arg(ap,unsigned int);
489
+ else if(next == 'U')
490
+ unum = va_arg(ap,unsigned long long);
491
+ else
492
+ unum = (unsigned long long)va_arg(ap,size_t);
493
+ {
494
+ char buf[SDS_LLSTR_SIZE];
495
+ l = sdsull2str(buf,unum);
496
+ if (sh->free < l) {
497
+ s = sdsMakeRoomFor(s,l);
498
+ sh = (void*) (s-(sizeof(struct sdshdr)));
499
+ }
500
+ memcpy(s+i,buf,l);
501
+ sh->len += l;
502
+ sh->free -= l;
503
+ i += l;
504
+ }
505
+ break;
506
+ default: /* Handle %% and generally %<unknown>. */
507
+ s[i++] = next;
508
+ sh->len += 1;
509
+ sh->free -= 1;
510
+ break;
511
+ }
512
+ break;
513
+ default:
514
+ s[i++] = *f;
515
+ sh->len += 1;
516
+ sh->free -= 1;
517
+ break;
518
+ }
519
+ f++;
520
+ }
521
+ va_end(ap);
522
+
523
+ /* Add null-term */
524
+ s[i] = '\0';
525
+ return s;
526
+ }
527
+
528
+
529
+ /* Remove the part of the string from left and from right composed just of
530
+ * contiguous characters found in 'cset', that is a null terminted C string.
531
+ *
532
+ * After the call, the modified sds string is no longer valid and all the
533
+ * references must be substituted with the new pointer returned by the call.
534
+ *
535
+ * Example:
536
+ *
537
+ * s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
538
+ * s = sdstrim(s,"A. :");
539
+ * printf("%s\n", s);
540
+ *
541
+ * Output will be just "Hello World".
542
+ */
543
+ void sdstrim(sds s, const char *cset) {
544
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
204
545
  char *start, *end, *sp, *ep;
205
546
  size_t len;
206
547
 
@@ -213,14 +554,29 @@ sds sdstrim(sds s, const char *cset) {
213
554
  sh->buf[len] = '\0';
214
555
  sh->free = sh->free+(sh->len-len);
215
556
  sh->len = len;
216
- return s;
217
557
  }
218
558
 
219
- sds sdsrange(sds s, int start, int end) {
220
- struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
559
+ /* Turn the string into a smaller (or equal) string containing only the
560
+ * substring specified by the 'start' and 'end' indexes.
561
+ *
562
+ * start and end can be negative, where -1 means the last character of the
563
+ * string, -2 the penultimate character, and so forth.
564
+ *
565
+ * The interval is inclusive, so the start and end characters will be part
566
+ * of the resulting string.
567
+ *
568
+ * The string is modified in-place.
569
+ *
570
+ * Example:
571
+ *
572
+ * s = sdsnew("Hello World");
573
+ * sdsrange(s,1,-1); => "ello World"
574
+ */
575
+ void sdsrange(sds s, int start, int end) {
576
+ struct sdshdr *sh = (void*) (s-sizeof *sh);;
221
577
  size_t newlen, len = sdslen(s);
222
578
 
223
- if (len == 0) return s;
579
+ if (len == 0) return;
224
580
  if (start < 0) {
225
581
  start = len+start;
226
582
  if (start < 0) start = 0;
@@ -244,22 +600,34 @@ sds sdsrange(sds s, int start, int end) {
244
600
  sh->buf[newlen] = 0;
245
601
  sh->free = sh->free+(sh->len-newlen);
246
602
  sh->len = newlen;
247
- return s;
248
603
  }
249
604
 
605
+ /* Apply tolower() to every character of the sds string 's'. */
250
606
  void sdstolower(sds s) {
251
607
  int len = sdslen(s), j;
252
608
 
253
609
  for (j = 0; j < len; j++) s[j] = tolower(s[j]);
254
610
  }
255
611
 
612
+ /* Apply toupper() to every character of the sds string 's'. */
256
613
  void sdstoupper(sds s) {
257
614
  int len = sdslen(s), j;
258
615
 
259
616
  for (j = 0; j < len; j++) s[j] = toupper(s[j]);
260
617
  }
261
618
 
262
- int sdscmp(sds s1, sds s2) {
619
+ /* Compare two sds strings s1 and s2 with memcmp().
620
+ *
621
+ * Return value:
622
+ *
623
+ * 1 if s1 > s2.
624
+ * -1 if s1 < s2.
625
+ * 0 if s1 and s2 are exactly the same binary string.
626
+ *
627
+ * If two strings share exactly the same prefix, but one of the two has
628
+ * additional characters, the longer string is considered to be greater than
629
+ * the smaller one. */
630
+ int sdscmp(const sds s1, const sds s2) {
263
631
  size_t l1, l2, minlen;
264
632
  int cmp;
265
633
 
@@ -287,14 +655,15 @@ int sdscmp(sds s1, sds s2) {
287
655
  * requires length arguments. sdssplit() is just the
288
656
  * same function but for zero-terminated strings.
289
657
  */
290
- sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
658
+ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
291
659
  int elements = 0, slots = 5, start = 0, j;
660
+ sds *tokens;
661
+
662
+ if (seplen < 1 || len < 0) return NULL;
663
+
664
+ tokens = malloc(sizeof(sds)*slots);
665
+ if (tokens == NULL) return NULL;
292
666
 
293
- sds *tokens = malloc(sizeof(sds)*slots);
294
- #ifdef SDS_ABORT_ON_OOM
295
- if (tokens == NULL) sdsOomAbort();
296
- #endif
297
- if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
298
667
  if (len == 0) {
299
668
  *count = 0;
300
669
  return tokens;
@@ -306,25 +675,13 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
306
675
 
307
676
  slots *= 2;
308
677
  newtokens = realloc(tokens,sizeof(sds)*slots);
309
- if (newtokens == NULL) {
310
- #ifdef SDS_ABORT_ON_OOM
311
- sdsOomAbort();
312
- #else
313
- goto cleanup;
314
- #endif
315
- }
678
+ if (newtokens == NULL) goto cleanup;
316
679
  tokens = newtokens;
317
680
  }
318
681
  /* search the separator */
319
682
  if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
320
683
  tokens[elements] = sdsnewlen(s+start,j-start);
321
- if (tokens[elements] == NULL) {
322
- #ifdef SDS_ABORT_ON_OOM
323
- sdsOomAbort();
324
- #else
325
- goto cleanup;
326
- #endif
327
- }
684
+ if (tokens[elements] == NULL) goto cleanup;
328
685
  elements++;
329
686
  start = j+seplen;
330
687
  j = j+seplen-1; /* skip the separator */
@@ -332,28 +689,22 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
332
689
  }
333
690
  /* Add the final element. We are sure there is room in the tokens array. */
334
691
  tokens[elements] = sdsnewlen(s+start,len-start);
335
- if (tokens[elements] == NULL) {
336
- #ifdef SDS_ABORT_ON_OOM
337
- sdsOomAbort();
338
- #else
339
- goto cleanup;
340
- #endif
341
- }
692
+ if (tokens[elements] == NULL) goto cleanup;
342
693
  elements++;
343
694
  *count = elements;
344
695
  return tokens;
345
696
 
346
- #ifndef SDS_ABORT_ON_OOM
347
697
  cleanup:
348
698
  {
349
699
  int i;
350
700
  for (i = 0; i < elements; i++) sdsfree(tokens[i]);
351
701
  free(tokens);
702
+ *count = 0;
352
703
  return NULL;
353
704
  }
354
- #endif
355
705
  }
356
706
 
707
+ /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
357
708
  void sdsfreesplitres(sds *tokens, int count) {
358
709
  if (!tokens) return;
359
710
  while(count--)
@@ -361,6 +712,10 @@ void sdsfreesplitres(sds *tokens, int count) {
361
712
  free(tokens);
362
713
  }
363
714
 
715
+ /* Create an sds string from a long long value. It is much faster than:
716
+ *
717
+ * sdscatprintf(sdsempty(),"%lld\n", value);
718
+ */
364
719
  sds sdsfromlonglong(long long value) {
365
720
  char buf[32], *p;
366
721
  unsigned long long v;
@@ -376,10 +731,14 @@ sds sdsfromlonglong(long long value) {
376
731
  return sdsnewlen(p,32-(p-buf));
377
732
  }
378
733
 
379
- sds sdscatrepr(sds s, char *p, size_t len) {
734
+ /* Append to the sds string "s" an escaped string representation where
735
+ * all the non-printable characters (tested with isprint()) are turned into
736
+ * escapes in the form "\n\r\a...." or "\x<hex-number>".
737
+ *
738
+ * After the call, the modified sds string is no longer valid and all the
739
+ * references must be substituted with the new pointer returned by the call. */
740
+ sds sdscatrepr(sds s, const char *p, size_t len) {
380
741
  s = sdscatlen(s,"\"",1);
381
- if (s == NULL) return NULL;
382
-
383
742
  while(len--) {
384
743
  switch(*p) {
385
744
  case '\\':
@@ -399,27 +758,64 @@ sds sdscatrepr(sds s, char *p, size_t len) {
399
758
  break;
400
759
  }
401
760
  p++;
402
- if (s == NULL) return NULL;
403
761
  }
404
762
  return sdscatlen(s,"\"",1);
405
763
  }
406
764
 
765
+ /* Helper function for sdssplitargs() that returns non zero if 'c'
766
+ * is a valid hex digit. */
767
+ int is_hex_digit(char c) {
768
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
769
+ (c >= 'A' && c <= 'F');
770
+ }
771
+
772
+ /* Helper function for sdssplitargs() that converts a hex digit into an
773
+ * integer from 0 to 15 */
774
+ int hex_digit_to_int(char c) {
775
+ switch(c) {
776
+ case '0': return 0;
777
+ case '1': return 1;
778
+ case '2': return 2;
779
+ case '3': return 3;
780
+ case '4': return 4;
781
+ case '5': return 5;
782
+ case '6': return 6;
783
+ case '7': return 7;
784
+ case '8': return 8;
785
+ case '9': return 9;
786
+ case 'a': case 'A': return 10;
787
+ case 'b': case 'B': return 11;
788
+ case 'c': case 'C': return 12;
789
+ case 'd': case 'D': return 13;
790
+ case 'e': case 'E': return 14;
791
+ case 'f': case 'F': return 15;
792
+ default: return 0;
793
+ }
794
+ }
795
+
407
796
  /* Split a line into arguments, where every argument can be in the
408
797
  * following programming-language REPL-alike form:
409
798
  *
410
799
  * foo bar "newline are supported\n" and "\xff\x00otherstuff"
411
800
  *
412
801
  * The number of arguments is stored into *argc, and an array
413
- * of sds is returned. The caller should sdsfree() all the returned
414
- * strings and finally free() the array itself.
802
+ * of sds is returned.
803
+ *
804
+ * The caller should free the resulting array of sds strings with
805
+ * sdsfreesplitres().
415
806
  *
416
807
  * Note that sdscatrepr() is able to convert back a string into
417
808
  * a quoted string in the same format sdssplitargs() is able to parse.
809
+ *
810
+ * The function returns the allocated tokens on success, even when the
811
+ * input string is empty, or NULL if the input contains unbalanced
812
+ * quotes or closed quotes followed by non space characters
813
+ * as in: "foo"bar or "foo'
418
814
  */
419
- sds *sdssplitargs(char *line, int *argc) {
420
- char *p = line;
815
+ sds *sdssplitargs(const char *line, int *argc) {
816
+ const char *p = line;
421
817
  char *current = NULL;
422
- char **vector = NULL, **_vector = NULL;
818
+ char **vector = NULL;
423
819
 
424
820
  *argc = 0;
425
821
  while(1) {
@@ -427,17 +823,24 @@ sds *sdssplitargs(char *line, int *argc) {
427
823
  while(*p && isspace(*p)) p++;
428
824
  if (*p) {
429
825
  /* get a token */
430
- int inq=0; /* set to 1 if we are in "quotes" */
826
+ int inq=0; /* set to 1 if we are in "quotes" */
827
+ int insq=0; /* set to 1 if we are in 'single quotes' */
431
828
  int done=0;
432
829
 
433
- if (current == NULL) {
434
- current = sdsempty();
435
- if (current == NULL) goto err;
436
- }
437
-
830
+ if (current == NULL) current = sdsempty();
438
831
  while(!done) {
439
832
  if (inq) {
440
- if (*p == '\\' && *(p+1)) {
833
+ if (*p == '\\' && *(p+1) == 'x' &&
834
+ is_hex_digit(*(p+2)) &&
835
+ is_hex_digit(*(p+3)))
836
+ {
837
+ unsigned char byte;
838
+
839
+ byte = (hex_digit_to_int(*(p+2))*16)+
840
+ hex_digit_to_int(*(p+3));
841
+ current = sdscatlen(current,(char*)&byte,1);
842
+ p += 3;
843
+ } else if (*p == '\\' && *(p+1)) {
441
844
  char c;
442
845
 
443
846
  p++;
@@ -451,7 +854,23 @@ sds *sdssplitargs(char *line, int *argc) {
451
854
  }
452
855
  current = sdscatlen(current,&c,1);
453
856
  } else if (*p == '"') {
454
- /* closing quote must be followed by a space */
857
+ /* closing quote must be followed by a space or
858
+ * nothing at all. */
859
+ if (*(p+1) && !isspace(*(p+1))) goto err;
860
+ done=1;
861
+ } else if (!*p) {
862
+ /* unterminated quotes */
863
+ goto err;
864
+ } else {
865
+ current = sdscatlen(current,p,1);
866
+ }
867
+ } else if (insq) {
868
+ if (*p == '\\' && *(p+1) == '\'') {
869
+ p++;
870
+ current = sdscatlen(current,"'",1);
871
+ } else if (*p == '\'') {
872
+ /* closing quote must be followed by a space or
873
+ * nothing at all. */
455
874
  if (*(p+1) && !isspace(*(p+1))) goto err;
456
875
  done=1;
457
876
  } else if (!*p) {
@@ -472,23 +891,24 @@ sds *sdssplitargs(char *line, int *argc) {
472
891
  case '"':
473
892
  inq=1;
474
893
  break;
894
+ case '\'':
895
+ insq=1;
896
+ break;
475
897
  default:
476
898
  current = sdscatlen(current,p,1);
477
899
  break;
478
900
  }
479
901
  }
480
902
  if (*p) p++;
481
- if (current == NULL) goto err;
482
903
  }
483
904
  /* add the token to the vector */
484
- _vector = realloc(vector,((*argc)+1)*sizeof(char*));
485
- if (_vector == NULL) goto err;
486
-
487
- vector = _vector;
905
+ vector = realloc(vector,((*argc)+1)*sizeof(char*));
488
906
  vector[*argc] = current;
489
907
  (*argc)++;
490
908
  current = NULL;
491
909
  } else {
910
+ /* Even on empty input string return something not NULL. */
911
+ if (vector == NULL) vector = malloc(sizeof(void*));
492
912
  return vector;
493
913
  }
494
914
  }
@@ -496,30 +916,67 @@ sds *sdssplitargs(char *line, int *argc) {
496
916
  err:
497
917
  while((*argc)--)
498
918
  sdsfree(vector[*argc]);
499
- if (vector != NULL) free(vector);
500
- if (current != NULL) sdsfree(current);
919
+ free(vector);
920
+ if (current) sdsfree(current);
921
+ *argc = 0;
501
922
  return NULL;
502
923
  }
503
924
 
925
+ /* Modify the string substituting all the occurrences of the set of
926
+ * characters specified in the 'from' string to the corresponding character
927
+ * in the 'to' array.
928
+ *
929
+ * For instance: sdsmapchars(mystring, "ho", "01", 2)
930
+ * will have the effect of turning the string "hello" into "0ell1".
931
+ *
932
+ * The function returns the sds string pointer, that is always the same
933
+ * as the input pointer since no resize is needed. */
934
+ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
935
+ size_t j, i, l = sdslen(s);
936
+
937
+ for (j = 0; j < l; j++) {
938
+ for (i = 0; i < setlen; i++) {
939
+ if (s[j] == from[i]) {
940
+ s[j] = to[i];
941
+ break;
942
+ }
943
+ }
944
+ }
945
+ return s;
946
+ }
947
+
948
+ /* Join an array of C strings using the specified separator (also a C string).
949
+ * Returns the result as an sds string. */
950
+ sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) {
951
+ sds join = sdsempty();
952
+ int j;
953
+
954
+ for (j = 0; j < argc; j++) {
955
+ join = sdscat(join, argv[j]);
956
+ if (j != argc-1) join = sdscatlen(join,sep,seplen);
957
+ }
958
+ return join;
959
+ }
960
+
961
+ /* Like sdsjoin, but joins an array of SDS strings. */
962
+ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
963
+ sds join = sdsempty();
964
+ int j;
965
+
966
+ for (j = 0; j < argc; j++) {
967
+ join = sdscatsds(join, argv[j]);
968
+ if (j != argc-1) join = sdscatlen(join,sep,seplen);
969
+ }
970
+ return join;
971
+ }
972
+
504
973
  #ifdef SDS_TEST_MAIN
505
974
  #include <stdio.h>
506
-
507
- int __failed_tests = 0;
508
- int __test_num = 0;
509
- #define test_cond(descr,_c) do { \
510
- __test_num++; printf("%d - %s: ", __test_num, descr); \
511
- if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
512
- } while(0);
513
- #define test_report() do { \
514
- printf("%d tests, %d passed, %d failed\n", __test_num, \
515
- __test_num-__failed_tests, __failed_tests); \
516
- if (__failed_tests) { \
517
- printf("=== WARNING === We have failed tests here...\n"); \
518
- } \
519
- } while(0);
975
+ #include "testhelp.h"
520
976
 
521
977
  int main(void) {
522
978
  {
979
+ struct sdshdr *sh;
523
980
  sds x = sdsnew("foo"), y;
524
981
 
525
982
  test_cond("Create a string and obtain the length",
@@ -549,36 +1006,43 @@ int main(void) {
549
1006
  sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
550
1007
 
551
1008
  sdsfree(x);
552
- x = sdstrim(sdsnew("xxciaoyyy"),"xy");
1009
+ x = sdsnew("xxciaoyyy");
1010
+ sdstrim(x,"xy");
553
1011
  test_cond("sdstrim() correctly trims characters",
554
1012
  sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
555
1013
 
556
- y = sdsrange(sdsdup(x),1,1);
1014
+ y = sdsdup(x);
1015
+ sdsrange(y,1,1);
557
1016
  test_cond("sdsrange(...,1,1)",
558
1017
  sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
559
1018
 
560
1019
  sdsfree(y);
561
- y = sdsrange(sdsdup(x),1,-1);
1020
+ y = sdsdup(x);
1021
+ sdsrange(y,1,-1);
562
1022
  test_cond("sdsrange(...,1,-1)",
563
1023
  sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
564
1024
 
565
1025
  sdsfree(y);
566
- y = sdsrange(sdsdup(x),-2,-1);
1026
+ y = sdsdup(x);
1027
+ sdsrange(y,-2,-1);
567
1028
  test_cond("sdsrange(...,-2,-1)",
568
1029
  sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
569
1030
 
570
1031
  sdsfree(y);
571
- y = sdsrange(sdsdup(x),2,1);
1032
+ y = sdsdup(x);
1033
+ sdsrange(y,2,1);
572
1034
  test_cond("sdsrange(...,2,1)",
573
1035
  sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
574
1036
 
575
1037
  sdsfree(y);
576
- y = sdsrange(sdsdup(x),1,100);
1038
+ y = sdsdup(x);
1039
+ sdsrange(y,1,100);
577
1040
  test_cond("sdsrange(...,1,100)",
578
1041
  sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
579
1042
 
580
1043
  sdsfree(y);
581
- y = sdsrange(sdsdup(x),100,100);
1044
+ y = sdsdup(x);
1045
+ sdsrange(y,100,100);
582
1046
  test_cond("sdsrange(...,100,100)",
583
1047
  sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
584
1048
 
@@ -599,7 +1063,33 @@ int main(void) {
599
1063
  x = sdsnew("aar");
600
1064
  y = sdsnew("bar");
601
1065
  test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
1066
+
1067
+ sdsfree(y);
1068
+ sdsfree(x);
1069
+ x = sdsnewlen("\a\n\0foo\r",7);
1070
+ y = sdscatrepr(sdsempty(),x,sdslen(x));
1071
+ test_cond("sdscatrepr(...data...)",
1072
+ memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
1073
+
1074
+ {
1075
+ int oldfree;
1076
+
1077
+ sdsfree(x);
1078
+ x = sdsnew("0");
1079
+ sh = (void*) (x-(sizeof(struct sdshdr)));
1080
+ test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
1081
+ x = sdsMakeRoomFor(x,1);
1082
+ sh = (void*) (x-(sizeof(struct sdshdr)));
1083
+ test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
1084
+ oldfree = sh->free;
1085
+ x[1] = '1';
1086
+ sdsIncrLen(x,1);
1087
+ test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
1088
+ test_cond("sdsIncrLen() -- len", sh->len == 2);
1089
+ test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
1090
+ }
602
1091
  }
603
1092
  test_report()
1093
+ return 0;
604
1094
  }
605
1095
  #endif