hiredis 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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