summaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
authorlijia <[email protected]>2024-10-18 16:47:51 +0800
committerlijia <[email protected]>2024-11-07 18:30:58 +0800
commite734af76d81b07090c618b1c4af3b2fdd6b592f3 (patch)
treec9b894fb0eaa9c56bd5f04bfab5628a97592091d /deps
parent99a68d5c9efe500ab339165a2af515a0a7355ada (diff)
rebase onto develop-2.0
Diffstat (limited to 'deps')
-rw-r--r--deps/CMakeLists.txt5
-rw-r--r--deps/linenoise/CMakeLists.txt2
-rw-r--r--deps/linenoise/linenoise.c1225
-rw-r--r--deps/linenoise/linenoise.h77
-rw-r--r--deps/ringbuf/CMakeLists.txt2
-rw-r--r--deps/ringbuf/ringbuf.c430
-rw-r--r--deps/ringbuf/ringbuf.h29
-rw-r--r--deps/ringbuf/utils.h115
-rw-r--r--deps/sds/CMakeLists.txt2
-rw-r--r--deps/sds/sds.c1335
-rw-r--r--deps/sds/sds.h285
-rw-r--r--deps/sds/sdsalloc.h42
-rw-r--r--deps/sds/testhelp.h57
13 files changed, 3605 insertions, 1 deletions
diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index 69ffeec..a00ff10 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -5,4 +5,7 @@ add_subdirectory(rbtree)
add_subdirectory(interval_tree)
add_subdirectory(bitmap)
add_subdirectory(nmx_pool)
-add_subdirectory(logger) \ No newline at end of file
+add_subdirectory(logger)
+add_subdirectory(sds)
+add_subdirectory(linenoise)
+add_subdirectory(ringbuf) \ No newline at end of file
diff --git a/deps/linenoise/CMakeLists.txt b/deps/linenoise/CMakeLists.txt
new file mode 100644
index 0000000..b0cad66
--- /dev/null
+++ b/deps/linenoise/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_library(linenoise STATIC linenoise.c)
+target_include_directories(linenoise PUBLIC ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file
diff --git a/deps/linenoise/linenoise.c b/deps/linenoise/linenoise.c
new file mode 100644
index 0000000..cfe51e7
--- /dev/null
+++ b/deps/linenoise/linenoise.c
@@ -0,0 +1,1225 @@
+/* linenoise.c -- guerrilla line editing library against the idea that a
+ * line editing lib needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (CUrsor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward n chars
+ *
+ * CUB (CUrsor Backward)
+ * Sequence: ESC [ n D
+ * Effect: moves cursor backward n chars
+ *
+ * The following is used to get the terminal width if getting
+ * the width with the TIOCGWINSZ ioctl fails
+ *
+ * DSR (Device Status Report)
+ * Sequence: ESC [ 6 n
+ * Effect: reports the current cusor position as ESC [ n ; m R
+ * where n is the row and m is the column
+ *
+ * When multi line mode is enabled, we also use an additional escape
+ * sequence. However multi line editing is disabled by default.
+ *
+ * CUU (Cursor Up)
+ * Sequence: ESC [ n A
+ * Effect: moves cursor up of n chars.
+ *
+ * CUD (Cursor Down)
+ * Sequence: ESC [ n B
+ * Effect: moves cursor down of n chars.
+ *
+ * When linenoiseClearScreen() is called, two additional escape sequences
+ * are used in order to clear the screen and position the cursor at home
+ * position.
+ *
+ * CUP (Cursor position)
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED (Erase display)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#include <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "linenoise.h"
+
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
+#define LINENOISE_MAX_LINE 4096
+static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
+static linenoiseCompletionCallback *completionCallback = NULL;
+static linenoiseHintsCallback *hintsCallback = NULL;
+static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
+
+static struct termios orig_termios; /* In order to restore at exit.*/
+static int maskmode = 0; /* Show "***" instead of input. For passwords. */
+static int rawmode = 0; /* For atexit() function to check if restore is needed*/
+static int mlmode = 0; /* Multi line mode. Default is single line. */
+static int atexit_registered = 0; /* Register atexit just 1 time. */
+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
+static int history_len = 0;
+static char **history = NULL;
+
+/* The linenoiseState structure represents the state during line editing.
+ * We pass this state to functions implementing specific editing
+ * functionalities. */
+struct linenoiseState {
+ int ifd; /* Terminal stdin file descriptor. */
+ int ofd; /* Terminal stdout file descriptor. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ const char *prompt; /* Prompt to display. */
+ size_t plen; /* Prompt length. */
+ size_t pos; /* Current cursor position. */
+ size_t oldpos; /* Previous refresh cursor position. */
+ size_t len; /* Current edited line length. */
+ size_t cols; /* Number of columns in terminal. */
+ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */
+ int history_index; /* The history index we are currently editing. */
+};
+
+enum KEY_ACTION{
+ KEY_NULL = 0, /* NULL */
+ CTRL_A = 1, /* Ctrl+a */
+ CTRL_B = 2, /* Ctrl-b */
+ CTRL_C = 3, /* Ctrl-c */
+ CTRL_D = 4, /* Ctrl-d */
+ CTRL_E = 5, /* Ctrl-e */
+ CTRL_F = 6, /* Ctrl-f */
+ CTRL_H = 8, /* Ctrl-h */
+ TAB = 9, /* Tab */
+ CTRL_K = 11, /* Ctrl+k */
+ CTRL_L = 12, /* Ctrl+l */
+ ENTER = 13, /* Enter */
+ CTRL_N = 14, /* Ctrl-n */
+ CTRL_P = 16, /* Ctrl-p */
+ CTRL_T = 20, /* Ctrl-t */
+ CTRL_U = 21, /* Ctrl+u */
+ CTRL_W = 23, /* Ctrl+w */
+ ESC = 27, /* Escape */
+ BACKSPACE = 127 /* Backspace */
+};
+
+static void linenoiseAtExit(void);
+int linenoiseHistoryAdd(const char *line);
+static void refreshLine(struct linenoiseState *l);
+
+/* Debugging macro. */
+#if 0
+FILE *lndebug_fp = NULL;
+#define lndebug(...) \
+ do { \
+ if (lndebug_fp == NULL) { \
+ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
+ fprintf(lndebug_fp, \
+ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
+ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
+ (int)l->maxrows,old_rows); \
+ } \
+ fprintf(lndebug_fp, ", " __VA_ARGS__); \
+ fflush(lndebug_fp); \
+ } while (0)
+#else
+#define lndebug(fmt, ...)
+#endif
+
+/* ======================= Low level terminal handling ====================== */
+
+/* Enable "mask mode". When it is enabled, instead of the input that
+ * the user is typing, the terminal will just display a corresponding
+ * number of asterisks, like "****". This is useful for passwords and other
+ * secrets that should not be displayed. */
+void linenoiseMaskModeEnable(void) {
+ maskmode = 1;
+}
+
+/* Disable mask mode. */
+void linenoiseMaskModeDisable(void) {
+ maskmode = 0;
+}
+
+/* Set if to use or not the multi line mode. */
+void linenoiseSetMultiLine(int ml) {
+ mlmode = ml;
+}
+
+/* Return true if the terminal name is in the list of terminals we know are
+ * not able to understand basic escape sequences. */
+static int isUnsupportedTerm(void) {
+ char *term = getenv("TERM");
+ int j;
+
+ if (term == NULL) return 0;
+ for (j = 0; unsupported_term[j]; j++)
+ if (!strcasecmp(term,unsupported_term[j])) return 1;
+ return 0;
+}
+
+/* Raw mode: 1960 magic shit. */
+static int enableRawMode(int fd) {
+ struct termios raw;
+
+ if (!isatty(STDIN_FILENO)) goto fatal;
+ if (!atexit_registered) {
+ atexit(linenoiseAtExit);
+ atexit_registered = 1;
+ }
+ if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
+
+ raw = orig_termios; /* modify the original mode */
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ /* output modes - disable post processing */
+ raw.c_oflag &= ~(OPOST);
+ /* control modes - set 8 bit chars */
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z,^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+ /* put terminal in raw mode after flushing */
+ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
+ rawmode = 1;
+ return 0;
+
+fatal:
+ errno = ENOTTY;
+ return -1;
+}
+
+static void disableRawMode(int fd) {
+ /* Don't even check the return value as it's too late. */
+ if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
+ rawmode = 0;
+}
+
+/* Use the ESC [6n escape sequence to query the horizontal cursor position
+ * and return it. On error -1 is returned, on success the position of the
+ * cursor. */
+static int getCursorPosition(int ifd, int ofd) {
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ /* Report cursor location */
+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
+
+ /* Read the response: ESC [ rows ; cols R */
+ while (i < sizeof(buf)-1) {
+ if (read(ifd,buf+i,1) != 1) break;
+ if (buf[i] == 'R') break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ /* Parse it. */
+ if (buf[0] != ESC || buf[1] != '[') return -1;
+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
+ return cols;
+}
+
+/* Try to get the number of columns in the current terminal, or assume 80
+ * if it fails. */
+static int getColumns(int ifd, int ofd) {
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ /* ioctl() failed. Try to query the terminal itself. */
+ int start, cols;
+
+ /* Get the initial position so we can restore it later. */
+ start = getCursorPosition(ifd,ofd);
+ if (start == -1) goto failed;
+
+ /* Go to right margin and get position. */
+ if (write(ofd,"\x1b[999C",6) != 6) goto failed;
+ cols = getCursorPosition(ifd,ofd);
+ if (cols == -1) goto failed;
+
+ /* Restore position. */
+ if (cols > start) {
+ char seq[32];
+ snprintf(seq,32,"\x1b[%dD",cols-start);
+ if (write(ofd,seq,strlen(seq)) == -1) {
+ /* Can't recover... */
+ }
+ }
+ return cols;
+ } else {
+ return ws.ws_col;
+ }
+
+failed:
+ return 80;
+}
+
+/* Clear the screen. Used to handle ctrl+l */
+void linenoiseClearScreen(void) {
+ if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
+ /* nothing to do, just to avoid warning. */
+ }
+}
+
+/* Beep, used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+static void linenoiseBeep(void) {
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+
+/* ============================== Completion ================================ */
+
+/* Free a list of completion option populated by linenoiseAddCompletion(). */
+static void freeCompletions(linenoiseCompletions *lc) {
+ size_t i;
+ for (i = 0; i < lc->len; i++)
+ free(lc->cvec[i]);
+ if (lc->cvec != NULL)
+ free(lc->cvec);
+}
+
+/* This is an helper function for linenoiseEdit() and is called when the
+ * user types the <tab> key in order to complete the string currently in the
+ * input.
+ *
+ * The state of the editing is encapsulated into the pointed linenoiseState
+ * structure as described in the structure definition. */
+static int completeLine(struct linenoiseState *ls) {
+ linenoiseCompletions lc = { 0, NULL };
+ int nread, nwritten;
+ char c = 0;
+
+ completionCallback(ls->buf,&lc);
+ if (lc.len == 0) {
+ linenoiseBeep();
+ } else {
+ size_t stop = 0, i = 0;
+
+ while(!stop) {
+ /* Show completion or original buffer */
+ if (i < lc.len) {
+ struct linenoiseState saved = *ls;
+
+ ls->len = ls->pos = strlen(lc.cvec[i]);
+ ls->buf = lc.cvec[i];
+ refreshLine(ls);
+ ls->len = saved.len;
+ ls->pos = saved.pos;
+ ls->buf = saved.buf;
+ } else {
+ refreshLine(ls);
+ }
+
+ nread = read(ls->ifd,&c,1);
+ if (nread <= 0) {
+ freeCompletions(&lc);
+ return -1;
+ }
+
+ switch(c) {
+ case 9: /* tab */
+ i = (i+1) % (lc.len+1);
+ if (i == lc.len) linenoiseBeep();
+ break;
+ case 27: /* escape */
+ /* Re-show original buffer */
+ if (i < lc.len) refreshLine(ls);
+ stop = 1;
+ break;
+ default:
+ /* Update buffer and return */
+ if (i < lc.len) {
+ nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
+ ls->len = ls->pos = nwritten;
+ }
+ stop = 1;
+ break;
+ }
+ }
+ }
+
+ freeCompletions(&lc);
+ return c; /* Return last read character */
+}
+
+/* Register a callback function to be called for tab-completion. */
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
+ completionCallback = fn;
+}
+
+/* Register a hits function to be called to show hits to the user at the
+ * right of the prompt. */
+void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
+ hintsCallback = fn;
+}
+
+/* Register a function to free the hints returned by the hints callback
+ * registered with linenoiseSetHintsCallback(). */
+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
+ freeHintsCallback = fn;
+}
+
+/* This function is used by the callback function registered by the user
+ * in order to add completion options given the input string when the
+ * user typed <tab>. See the example.c source code for a very easy to
+ * understand example. */
+void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
+ size_t len = strlen(str);
+ char *copy, **cvec;
+
+ copy = malloc(len+1);
+ if (copy == NULL) return;
+ memcpy(copy,str,len+1);
+ cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+ if (cvec == NULL) {
+ free(copy);
+ return;
+ }
+ lc->cvec = cvec;
+ lc->cvec[lc->len++] = copy;
+}
+
+/* =========================== Line editing ================================= */
+
+/* We define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. This is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects. */
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
+ * to the right of the prompt. */
+void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
+ char seq[64];
+ if (hintsCallback && plen+l->len < l->cols) {
+ int color = -1, bold = 0;
+ char *hint = hintsCallback(l->buf,&color,&bold);
+ if (hint) {
+ int hintlen = strlen(hint);
+ int hintmaxlen = l->cols-(plen+l->len);
+ if (hintlen > hintmaxlen) hintlen = hintmaxlen;
+ if (bold == 1 && color == -1) color = 37;
+ if (color != -1 || bold != 0)
+ snprintf(seq,64,"\033[%d;%d;49m",bold,color);
+ else
+ seq[0] = '\0';
+ abAppend(ab,seq,strlen(seq));
+ abAppend(ab,hint,hintlen);
+ if (color != -1 || bold != 0)
+ abAppend(ab,"\033[0m",4);
+ /* Call the function to free the hint returned. */
+ if (freeHintsCallback) freeHintsCallback(hint);
+ }
+ }
+}
+
+/* Single line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshSingleLine(struct linenoiseState *l) {
+ char seq[64];
+ size_t plen = strlen(l->prompt);
+ int fd = l->ofd;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+
+ while((plen+pos) >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while (plen+len > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ /* Cursor to left edge */
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ if (maskmode == 1) {
+ while (len--) abAppend(&ab,"*",1);
+ } else {
+ abAppend(&ab,buf,len);
+ }
+ /* Show hits if any. */
+ refreshShowHints(&ab,l,plen);
+ /* Erase to right */
+ snprintf(seq,64,"\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+ /* Move cursor to original position. */
+ snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
+ abAppend(&ab,seq,strlen(seq));
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshMultiLine(struct linenoiseState *l) {
+ char seq[64];
+ int plen = strlen(l->prompt);
+ int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
+ int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
+ int rpos2; /* rpos after refresh. */
+ int col; /* colum position, zero-based. */
+ int old_rows = l->maxrows;
+ int fd = l->ofd, j;
+ struct abuf ab;
+
+ /* Update maxrows if needed. */
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ abInit(&ab);
+ if (old_rows-rpos > 0) {
+ lndebug("go down %d", old_rows-rpos);
+ snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Now for every row clear it, go up. */
+ for (j = 0; j < old_rows-1; j++) {
+ lndebug("clear+up");
+ snprintf(seq,64,"\r\x1b[0K\x1b[1A");
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Clean the top line. */
+ lndebug("clear");
+ snprintf(seq,64,"\r\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ if (maskmode == 1) {
+ unsigned int i;
+ for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
+ } else {
+ abAppend(&ab,l->buf,l->len);
+ }
+
+ /* Show hits if any. */
+ refreshShowHints(&ab,l,plen);
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if (l->pos &&
+ l->pos == l->len &&
+ (l->pos+plen) % l->cols == 0)
+ {
+ lndebug("<newline>");
+ abAppend(&ab,"\n",1);
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ rows++;
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+ }
+
+ /* Move cursor to right position. */
+ rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
+ lndebug("rpos2 %d", rpos2);
+
+ /* Go up till we reach the expected positon. */
+ if (rows-rpos2 > 0) {
+ lndebug("go-up %d", rows-rpos2);
+ snprintf(seq,64,"\x1b[%dA", rows-rpos2);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Set column. */
+ col = (plen+(int)l->pos) % (int)l->cols;
+ lndebug("set col %d", 1+col);
+ if (col)
+ snprintf(seq,64,"\r\x1b[%dC", col);
+ else
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+
+ lndebug("\n");
+ l->oldpos = l->pos;
+
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+static void refreshLine(struct linenoiseState *l) {
+ if (mlmode)
+ refreshMultiLine(l);
+ else
+ refreshSingleLine(l);
+}
+
+/* Insert the character 'c' at cursor current position.
+ *
+ * On error writing to the terminal -1 is returned, otherwise 0. */
+int linenoiseEditInsert(struct linenoiseState *l, char c) {
+ if (l->len < l->buflen) {
+ if (l->len == l->pos) {
+ l->buf[l->pos] = c;
+ l->pos++;
+ l->len++;
+ l->buf[l->len] = '\0';
+ if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
+ /* Avoid a full update of the line in the
+ * trivial case. */
+ char d = (maskmode==1) ? '*' : c;
+ if (write(l->ofd,&d,1) == -1) return -1;
+ } else {
+ refreshLine(l);
+ }
+ } else {
+ memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
+ l->buf[l->pos] = c;
+ l->len++;
+ l->pos++;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+ }
+ return 0;
+}
+
+/* Move cursor on the left. */
+void linenoiseEditMoveLeft(struct linenoiseState *l) {
+ if (l->pos > 0) {
+ l->pos--;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor on the right. */
+void linenoiseEditMoveRight(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos++;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the start of the line. */
+void linenoiseEditMoveHome(struct linenoiseState *l) {
+ if (l->pos != 0) {
+ l->pos = 0;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the end of the line. */
+void linenoiseEditMoveEnd(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos = l->len;
+ refreshLine(l);
+ }
+}
+
+/* Substitute the currently edited line with the next or previous history
+ * entry as specified by 'dir'. */
+#define LINENOISE_HISTORY_NEXT 0
+#define LINENOISE_HISTORY_PREV 1
+void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
+ if (history_len > 1) {
+ /* Update the current history entry before to
+ * overwrite it with the next one. */
+ free(history[history_len - 1 - l->history_index]);
+ history[history_len - 1 - l->history_index] = strdup(l->buf);
+ /* Show the new entry */
+ l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
+ if (l->history_index < 0) {
+ l->history_index = 0;
+ return;
+ } else if (l->history_index >= history_len) {
+ l->history_index = history_len-1;
+ return;
+ }
+ strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
+ l->buf[l->buflen-1] = '\0';
+ l->len = l->pos = strlen(l->buf);
+ refreshLine(l);
+ }
+}
+
+/* Delete the character at the right of the cursor without altering the cursor
+ * position. Basically this is what happens with the "Delete" keyboard key. */
+void linenoiseEditDelete(struct linenoiseState *l) {
+ if (l->len > 0 && l->pos < l->len) {
+ memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Backspace implementation. */
+void linenoiseEditBackspace(struct linenoiseState *l) {
+ if (l->pos > 0 && l->len > 0) {
+ memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
+ l->pos--;
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Delete the previosu word, maintaining the cursor at the start of the
+ * current word. */
+void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
+ size_t old_pos = l->pos;
+ size_t diff;
+
+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
+ l->pos--;
+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
+ l->pos--;
+ diff = old_pos - l->pos;
+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+ l->len -= diff;
+ refreshLine(l);
+}
+
+/* This function is the core of the line editing capability of linenoise.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * The function returns the length of the current buffer. */
+static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
+{
+ struct linenoiseState l;
+
+ /* Populate the linenoise state that we pass to functions implementing
+ * specific editing functionalities. */
+ l.ifd = stdin_fd;
+ l.ofd = stdout_fd;
+ l.buf = buf;
+ l.buflen = buflen;
+ l.prompt = prompt;
+ l.plen = strlen(prompt);
+ l.oldpos = l.pos = 0;
+ l.len = 0;
+ l.cols = getColumns(stdin_fd, stdout_fd);
+ l.maxrows = 0;
+ l.history_index = 0;
+
+ /* Buffer starts empty. */
+ l.buf[0] = '\0';
+ l.buflen--; /* Make sure there is always space for the nulterm */
+
+ /* The latest history entry is always our current buffer, that
+ * initially is just an empty string. */
+ linenoiseHistoryAdd("");
+
+ if (write(l.ofd,prompt,l.plen) == -1) return -1;
+ while(1) {
+ char c;
+ int nread;
+ char seq[3];
+
+ nread = read(l.ifd,&c,1);
+ if (nread <= 0) return l.len;
+
+ /* Only autocomplete when the callback is set. It returns < 0 when
+ * there was an error reading from fd. Otherwise it will return the
+ * character that should be handled next. */
+ if (c == 9 && completionCallback != NULL) {
+ c = completeLine(&l);
+ /* Return on errors */
+ if (c < 0) return l.len;
+ /* Read next character when 0 */
+ if (c == 0) continue;
+ }
+
+ switch(c) {
+ case ENTER: /* enter */
+ history_len--;
+ free(history[history_len]);
+ if (mlmode) linenoiseEditMoveEnd(&l);
+ if (hintsCallback) {
+ /* Force a refresh without hints to leave the previous
+ * line as the user typed it after a newline. */
+ linenoiseHintsCallback *hc = hintsCallback;
+ hintsCallback = NULL;
+ refreshLine(&l);
+ hintsCallback = hc;
+ }
+ return (int)l.len;
+ case CTRL_C: /* ctrl-c */
+ errno = EAGAIN;
+ return -1;
+ case BACKSPACE: /* backspace */
+ case 8: /* ctrl-h */
+ linenoiseEditBackspace(&l);
+ break;
+ case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
+ line is empty, act as end-of-file. */
+ if (l.len > 0) {
+ linenoiseEditDelete(&l);
+ } else {
+ history_len--;
+ free(history[history_len]);
+ return -1;
+ }
+ break;
+ case CTRL_T: /* ctrl-t, swaps current character with previous. */
+ if (l.pos > 0 && l.pos < l.len) {
+ int aux = buf[l.pos-1];
+ buf[l.pos-1] = buf[l.pos];
+ buf[l.pos] = aux;
+ if (l.pos != l.len-1) l.pos++;
+ refreshLine(&l);
+ }
+ break;
+ case CTRL_B: /* ctrl-b */
+ linenoiseEditMoveLeft(&l);
+ break;
+ case CTRL_F: /* ctrl-f */
+ linenoiseEditMoveRight(&l);
+ break;
+ case CTRL_P: /* ctrl-p */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
+ break;
+ case CTRL_N: /* ctrl-n */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
+ break;
+ case ESC: /* escape sequence */
+ /* Read the next two bytes representing the escape sequence.
+ * Use two calls to handle slow terminals returning the two
+ * chars at different times. */
+ if (read(l.ifd,seq,1) == -1) break;
+ if (read(l.ifd,seq+1,1) == -1) break;
+
+ /* ESC [ sequences. */
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ /* Extended escape, read additional byte. */
+ if (read(l.ifd,seq+2,1) == -1) break;
+ if (seq[2] == '~') {
+ switch(seq[1]) {
+ case '3': /* Delete key. */
+ linenoiseEditDelete(&l);
+ break;
+ }
+ }
+ } else {
+ switch(seq[1]) {
+ case 'A': /* Up */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
+ break;
+ case 'B': /* Down */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
+ break;
+ case 'C': /* Right */
+ linenoiseEditMoveRight(&l);
+ break;
+ case 'D': /* Left */
+ linenoiseEditMoveLeft(&l);
+ break;
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&l);
+ break;
+ }
+ }
+ }
+
+ /* ESC O sequences. */
+ else if (seq[0] == 'O') {
+ switch(seq[1]) {
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&l);
+ break;
+ }
+ }
+ break;
+ default:
+ if (linenoiseEditInsert(&l,c)) return -1;
+ break;
+ case CTRL_U: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ l.pos = l.len = 0;
+ refreshLine(&l);
+ break;
+ case CTRL_K: /* Ctrl+k, delete from current to end of line. */
+ buf[l.pos] = '\0';
+ l.len = l.pos;
+ refreshLine(&l);
+ break;
+ case CTRL_A: /* Ctrl+a, go to the start of the line */
+ linenoiseEditMoveHome(&l);
+ break;
+ case CTRL_E: /* ctrl+e, go to the end of the line */
+ linenoiseEditMoveEnd(&l);
+ break;
+ case CTRL_L: /* ctrl+l, clear screen */
+ linenoiseClearScreen();
+ refreshLine(&l);
+ break;
+ case CTRL_W: /* ctrl+w, delete previous word */
+ linenoiseEditDeletePrevWord(&l);
+ break;
+ }
+ }
+ return l.len;
+}
+
+/* This special mode is used by linenoise in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the linenoise_example program using the --keycodes option. */
+void linenoisePrintKeyCodes(void) {
+ char quit[4];
+
+ printf("Linenoise key codes debugging mode.\n"
+ "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
+ if (enableRawMode(STDIN_FILENO) == -1) return;
+ memset(quit,' ',4);
+ while(1) {
+ char c;
+ int nread;
+
+ nread = read(STDIN_FILENO,&c,1);
+ if (nread <= 0) continue;
+ memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
+ quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
+ if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n",
+ isprint(c) ? c : '?', (int)c, (int)c);
+ printf("\r"); /* Go left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ disableRawMode(STDIN_FILENO);
+}
+
+/* This function calls the line editing function linenoiseEdit() using
+ * the STDIN file descriptor set in raw mode. */
+static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
+ int count;
+
+ if (buflen == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (enableRawMode(STDIN_FILENO) == -1) return -1;
+ count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
+ disableRawMode(STDIN_FILENO);
+ printf("\n");
+ return count;
+}
+
+/* This function is called when linenoise() is called with the standard
+ * input file descriptor not attached to a TTY. So for example when the
+ * program using linenoise is called in pipe or with a file redirected
+ * to its standard input. In this case, we want to be able to return the
+ * line regardless of its length (by default we are limited to 4k). */
+static char *linenoiseNoTTY(void) {
+ char *line = NULL;
+ size_t len = 0, maxlen = 0;
+
+ while(1) {
+ if (len == maxlen) {
+ if (maxlen == 0) maxlen = 16;
+ maxlen *= 2;
+ char *oldval = line;
+ line = realloc(line,maxlen);
+ if (line == NULL) {
+ if (oldval) free(oldval);
+ return NULL;
+ }
+ }
+ int c = fgetc(stdin);
+ if (c == EOF || c == '\n') {
+ if (c == EOF && len == 0) {
+ free(line);
+ return NULL;
+ } else {
+ line[len] = '\0';
+ return line;
+ }
+ } else {
+ line[len] = c;
+ len++;
+ }
+ }
+}
+
+/* The high level function that is the main API of the linenoise library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of stupid terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions. */
+char *linenoise(const char *prompt) {
+ char buf[LINENOISE_MAX_LINE];
+ int count;
+
+ if (!isatty(STDIN_FILENO)) {
+ /* Not a tty: read from file / pipe. In this mode we don't want any
+ * limit to the line size, so we call a function to handle that. */
+ return linenoiseNoTTY();
+ } else if (isUnsupportedTerm()) {
+ size_t len;
+
+ printf("%s",prompt);
+ fflush(stdout);
+ if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
+ len = strlen(buf);
+ while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
+ len--;
+ buf[len] = '\0';
+ }
+ return strdup(buf);
+ } else {
+ count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
+ if (count == -1) return NULL;
+ return strdup(buf);
+ }
+}
+
+/* This is just a wrapper the user may want to call in order to make sure
+ * the linenoise returned buffer is freed with the same allocator it was
+ * created with. Useful when the main program is using an alternative
+ * allocator. */
+void linenoiseFree(void *ptr) {
+ free(ptr);
+}
+
+/* ================================ History ================================= */
+
+/* Free the history, but does not reset it. Only used when we have to
+ * exit() to avoid memory leaks are reported by valgrind & co. */
+static void freeHistory(void) {
+ if (history) {
+ int j;
+
+ for (j = 0; j < history_len; j++)
+ free(history[j]);
+ free(history);
+ }
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static void linenoiseAtExit(void) {
+ disableRawMode(STDIN_FILENO);
+ freeHistory();
+}
+
+/* This is the API call to add a new entry in the linenoise history.
+ * It uses a fixed array of char pointers that are shifted (memmoved)
+ * when the history max length is reached in order to remove the older
+ * entry and make room for the new one, so it is not exactly suitable for huge
+ * histories, but will work well for a few hundred of entries.
+ *
+ * Using a circular buffer is smarter, but a bit more complex to handle. */
+int linenoiseHistoryAdd(const char *line) {
+ char *linecopy;
+
+ if (history_max_len == 0) return 0;
+
+ /* Initialization on first call. */
+ if (history == NULL) {
+ history = malloc(sizeof(char*)*history_max_len);
+ if (history == NULL) return 0;
+ memset(history,0,(sizeof(char*)*history_max_len));
+ }
+
+ /* Don't add duplicated lines. */
+ if (history_len && !strcmp(history[history_len-1], line)) return 0;
+
+ /* Add an heap allocated copy of the line in the history.
+ * If we reached the max length, remove the older line. */
+ linecopy = strdup(line);
+ if (!linecopy) return 0;
+ if (history_len == history_max_len) {
+ free(history[0]);
+ memmove(history,history+1,sizeof(char*)*(history_max_len-1));
+ history_len--;
+ }
+ history[history_len] = linecopy;
+ history_len++;
+ return 1;
+}
+
+/* Set the maximum length for the history. This function can be called even
+ * if there is already some history, the function will make sure to retain
+ * just the latest 'len' elements if the new history length value is smaller
+ * than the amount of items already inside the history. */
+int linenoiseHistorySetMaxLen(int len) {
+ char **new;
+
+ if (len < 1) return 0;
+ if (history) {
+ int tocopy = history_len;
+
+ new = malloc(sizeof(char*)*len);
+ if (new == NULL) return 0;
+
+ /* If we can't copy everything, free the elements we'll not use. */
+ if (len < tocopy) {
+ int j;
+
+ for (j = 0; j < tocopy-len; j++) free(history[j]);
+ tocopy = len;
+ }
+ memset(new,0,sizeof(char*)*len);
+ memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
+ free(history);
+ history = new;
+ }
+ history_max_len = len;
+ if (history_len > history_max_len)
+ history_len = history_max_len;
+ return 1;
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int linenoiseHistorySave(const char *filename) {
+ mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ FILE *fp;
+ int j;
+
+ fp = fopen(filename,"w");
+ umask(old_umask);
+ if (fp == NULL) return -1;
+ chmod(filename,S_IRUSR|S_IWUSR);
+ for (j = 0; j < history_len; j++)
+ fprintf(fp,"%s\n",history[j]);
+ fclose(fp);
+ return 0;
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int linenoiseHistoryLoad(const char *filename) {
+ FILE *fp = fopen(filename,"r");
+ char buf[LINENOISE_MAX_LINE];
+
+ if (fp == NULL) return -1;
+
+ while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+ char *p;
+
+ p = strchr(buf,'\r');
+ if (!p) p = strchr(buf,'\n');
+ if (p) *p = '\0';
+ linenoiseHistoryAdd(buf);
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/deps/linenoise/linenoise.h b/deps/linenoise/linenoise.h
new file mode 100644
index 0000000..f74cdc0
--- /dev/null
+++ b/deps/linenoise/linenoise.h
@@ -0,0 +1,77 @@
+/* linenoise.h -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * See linenoise.c for more information.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LINENOISE_H
+#define __LINENOISE_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct linenoiseCompletions
+ {
+ size_t len;
+ char **cvec;
+ } linenoiseCompletions;
+
+ typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
+ typedef char *(linenoiseHintsCallback)(const char *, int *color, int *bold);
+ typedef void(linenoiseFreeHintsCallback)(void *);
+ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
+ void linenoiseSetHintsCallback(linenoiseHintsCallback *);
+ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
+ void linenoiseAddCompletion(linenoiseCompletions *, const char *);
+
+ char *linenoise(const char *prompt);
+ void linenoiseFree(void *ptr);
+ int linenoiseHistoryAdd(const char *line);
+ int linenoiseHistorySetMaxLen(int len);
+ int linenoiseHistorySave(const char *filename);
+ int linenoiseHistoryLoad(const char *filename);
+ void linenoiseClearScreen(void);
+ void linenoiseSetMultiLine(int ml);
+ void linenoisePrintKeyCodes(void);
+ void linenoiseMaskModeEnable(void);
+ void linenoiseMaskModeDisable(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LINENOISE_H */
diff --git a/deps/ringbuf/CMakeLists.txt b/deps/ringbuf/CMakeLists.txt
new file mode 100644
index 0000000..16222e2
--- /dev/null
+++ b/deps/ringbuf/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_library(ringbuf STATIC ringbuf.c)
+target_include_directories(ringbuf PUBLIC ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file
diff --git a/deps/ringbuf/ringbuf.c b/deps/ringbuf/ringbuf.c
new file mode 100644
index 0000000..75cfee9
--- /dev/null
+++ b/deps/ringbuf/ringbuf.c
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2016-2017 Mindaugas Rasiukevicius <rmind at noxt eu>
+ * All rights reserved.
+ *
+ * Use is subject to license terms, as specified in the LICENSE file.
+ */
+
+/*
+ * Atomic multi-producer single-consumer ring buffer, which supports
+ * contiguous range operations and which can be conveniently used for
+ * message passing.
+ *
+ * There are three offsets -- think of clock hands:
+ * - NEXT: marks the beginning of the available space,
+ * - WRITTEN: the point up to which the data is actually written.
+ * - Observed READY: point up to which data is ready to be written.
+ *
+ * Producers
+ *
+ * Observe and save the 'next' offset, then request N bytes from
+ * the ring buffer by atomically advancing the 'next' offset. Once
+ * the data is written into the "reserved" buffer space, the thread
+ * clears the saved value; these observed values are used to compute
+ * the 'ready' offset.
+ *
+ * Consumer
+ *
+ * Writes the data between 'written' and 'ready' offsets and updates
+ * the 'written' value. The consumer thread scans for the lowest
+ * seen value by the producers.
+ *
+ * Key invariant
+ *
+ * Producers cannot go beyond the 'written' offset; producers are
+ * also not allowed to catch up with the consumer. Only the consumer
+ * is allowed to catch up with the producer i.e. set the 'written'
+ * offset to be equal to the 'next' offset.
+ *
+ * Wrap-around
+ *
+ * If the producer cannot acquire the requested length due to little
+ * available space at the end of the buffer, then it will wraparound.
+ * WRAP_LOCK_BIT in 'next' offset is used to lock the 'end' offset.
+ *
+ * There is an ABA problem if one producer stalls while a pair of
+ * producer and consumer would both successfully wrap-around and set
+ * the 'next' offset to the stale value of the first producer, thus
+ * letting it to perform a successful CAS violating the invariant.
+ * A counter in the 'next' offset (masked by WRAP_COUNTER) is used
+ * to prevent from this problem. It is incremented on wraparounds.
+ *
+ * The same ABA problem could also cause a stale 'ready' offset,
+ * which could be observed by the consumer. We set WRAP_LOCK_BIT in
+ * the 'seen' value before advancing the 'next' and clear this bit
+ * after the successful advancing; this ensures that only the stable
+ * 'ready' is observed by the consumer.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "ringbuf.h"
+#include "utils.h"
+
+#define RBUF_OFF_MASK (0x00000000ffffffffUL)
+#define WRAP_LOCK_BIT (0x8000000000000000UL)
+#define RBUF_OFF_MAX (UINT64_MAX & ~WRAP_LOCK_BIT)
+
+#define WRAP_COUNTER (0x7fffffff00000000UL)
+#define WRAP_INCR(x) (((x) + 0x100000000UL) & WRAP_COUNTER)
+
+typedef uint64_t ringbuf_off_t;
+
+struct ringbuf_worker {
+ volatile ringbuf_off_t seen_off;
+ int registered;
+};
+
+struct ringbuf {
+ /* Ring buffer space. */
+ size_t space;
+
+ /*
+ * The NEXT hand is atomically updated by the producer.
+ * WRAP_LOCK_BIT is set in case of wrap-around; in such case,
+ * the producer can update the 'end' offset.
+ */
+ volatile ringbuf_off_t next;
+ ringbuf_off_t end;
+
+ /* The following are updated by the consumer. */
+ ringbuf_off_t written;
+ unsigned nworkers;
+ ringbuf_worker_t workers[];
+};
+
+/*
+ * ringbuf_setup: initialise a new ring buffer of a given length.
+ */
+int
+ringbuf_setup(ringbuf_t *rbuf, unsigned nworkers, size_t length)
+{
+ if (length >= RBUF_OFF_MASK) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(rbuf, 0, offsetof(ringbuf_t, workers[nworkers]));
+ rbuf->space = length;
+ rbuf->end = RBUF_OFF_MAX;
+ rbuf->nworkers = nworkers;
+ return 0;
+}
+
+/*
+ * ringbuf_get_sizes: return the sizes of the ringbuf_t and ringbuf_worker_t.
+ */
+void
+ringbuf_get_sizes(unsigned nworkers,
+ size_t *ringbuf_size, size_t *ringbuf_worker_size)
+{
+ if (ringbuf_size)
+ *ringbuf_size = offsetof(ringbuf_t, workers[nworkers]);
+ if (ringbuf_worker_size)
+ *ringbuf_worker_size = sizeof(ringbuf_worker_t);
+}
+
+/*
+ * ringbuf_register: register the worker (thread/process) as a producer
+ * and pass the pointer to its local store.
+ */
+ringbuf_worker_t *
+ringbuf_register(ringbuf_t *rbuf, unsigned i)
+{
+ ringbuf_worker_t *w = &rbuf->workers[i];
+
+ w->seen_off = RBUF_OFF_MAX;
+ atomic_store_explicit(&w->registered, true, memory_order_release);
+ return w;
+}
+
+void
+ringbuf_unregister(ringbuf_t *rbuf, ringbuf_worker_t *w)
+{
+ w->registered = false;
+ (void)rbuf;
+}
+
+/*
+ * stable_nextoff: capture and return a stable value of the 'next' offset.
+ */
+static inline ringbuf_off_t
+stable_nextoff(ringbuf_t *rbuf)
+{
+ unsigned count = SPINLOCK_BACKOFF_MIN;
+ ringbuf_off_t next;
+retry:
+ next = atomic_load_explicit(&rbuf->next, memory_order_acquire);
+ if (next & WRAP_LOCK_BIT) {
+ SPINLOCK_BACKOFF(count);
+ goto retry;
+ }
+ ASSERT((next & RBUF_OFF_MASK) < rbuf->space);
+ return next;
+}
+
+/*
+ * stable_seenoff: capture and return a stable value of the 'seen' offset.
+ */
+static inline ringbuf_off_t
+stable_seenoff(ringbuf_worker_t *w)
+{
+ unsigned count = SPINLOCK_BACKOFF_MIN;
+ ringbuf_off_t seen_off;
+retry:
+ seen_off = atomic_load_explicit(&w->seen_off, memory_order_acquire);
+ if (seen_off & WRAP_LOCK_BIT) {
+ SPINLOCK_BACKOFF(count);
+ goto retry;
+ }
+ return seen_off;
+}
+
+/*
+ * ringbuf_acquire: request a space of a given length in the ring buffer.
+ *
+ * => On success: returns the offset at which the space is available.
+ * => On failure: returns -1.
+ */
+ssize_t
+ringbuf_acquire(ringbuf_t *rbuf, ringbuf_worker_t *w, size_t len)
+{
+ ringbuf_off_t seen, next, target;
+
+ ASSERT(len > 0 && len <= rbuf->space);
+ ASSERT(w->seen_off == RBUF_OFF_MAX);
+
+ do {
+ ringbuf_off_t written;
+
+ /*
+ * Get the stable 'next' offset. Save the observed 'next'
+ * value (i.e. the 'seen' offset), but mark the value as
+ * unstable (set WRAP_LOCK_BIT).
+ *
+ * Note: CAS will issue a memory_order_release for us and
+ * thus ensures that it reaches global visibility together
+ * with new 'next'.
+ */
+ seen = stable_nextoff(rbuf);
+ next = seen & RBUF_OFF_MASK;
+ ASSERT(next < rbuf->space);
+ atomic_store_explicit(&w->seen_off, next | WRAP_LOCK_BIT,
+ memory_order_relaxed);
+
+ /*
+ * Compute the target offset. Key invariant: we cannot
+ * go beyond the WRITTEN offset or catch up with it.
+ */
+ target = next + len;
+ written = rbuf->written;
+ if (__predict_false(next < written && target >= written)) {
+ /* The producer must wait. */
+ atomic_store_explicit(&w->seen_off,
+ RBUF_OFF_MAX, memory_order_release);
+ return -1;
+ }
+
+ if (__predict_false(target >= rbuf->space)) {
+ const bool exceed = target > rbuf->space;
+
+ /*
+ * Wrap-around and start from the beginning.
+ *
+ * If we would exceed the buffer, then attempt to
+ * acquire the WRAP_LOCK_BIT and use the space in
+ * the beginning. If we used all space exactly to
+ * the end, then reset to 0.
+ *
+ * Check the invariant again.
+ */
+ target = exceed ? (WRAP_LOCK_BIT | len) : 0;
+ if ((target & RBUF_OFF_MASK) >= written) {
+ atomic_store_explicit(&w->seen_off,
+ RBUF_OFF_MAX, memory_order_release);
+ return -1;
+ }
+ /* Increment the wrap-around counter. */
+ target |= WRAP_INCR(seen & WRAP_COUNTER);
+ } else {
+ /* Preserve the wrap-around counter. */
+ target |= seen & WRAP_COUNTER;
+ }
+ } while (!atomic_compare_exchange_weak(&rbuf->next, &seen, target));
+
+ /*
+ * Acquired the range. Clear WRAP_LOCK_BIT in the 'seen' value
+ * thus indicating that it is stable now.
+ *
+ * No need for memory_order_release, since CAS issued a fence.
+ */
+ atomic_store_explicit(&w->seen_off, w->seen_off & ~WRAP_LOCK_BIT,
+ memory_order_relaxed);
+
+ /*
+ * If we set the WRAP_LOCK_BIT in the 'next' (because we exceed
+ * the remaining space and need to wrap-around), then save the
+ * 'end' offset and release the lock.
+ */
+ if (__predict_false(target & WRAP_LOCK_BIT)) {
+ /* Cannot wrap-around again if consumer did not catch-up. */
+ ASSERT(rbuf->written <= next);
+ ASSERT(rbuf->end == RBUF_OFF_MAX);
+ rbuf->end = next;
+ next = 0;
+
+ /*
+ * Unlock: ensure the 'end' offset reaches global
+ * visibility before the lock is released.
+ */
+ atomic_store_explicit(&rbuf->next,
+ (target & ~WRAP_LOCK_BIT), memory_order_release);
+ }
+ ASSERT((target & RBUF_OFF_MASK) <= rbuf->space);
+ return (ssize_t)next;
+}
+
+/*
+ * ringbuf_produce: indicate the acquired range in the buffer is produced
+ * and is ready to be consumed.
+ */
+void
+ringbuf_produce(ringbuf_t *rbuf, ringbuf_worker_t *w)
+{
+ (void)rbuf;
+ ASSERT(w->registered);
+ ASSERT(w->seen_off != RBUF_OFF_MAX);
+ atomic_store_explicit(&w->seen_off, RBUF_OFF_MAX, memory_order_release);
+}
+
+/*
+ * ringbuf_consume: get a contiguous range which is ready to be consumed.
+ */
+size_t
+ringbuf_consume(ringbuf_t *rbuf, size_t *offset)
+{
+ ringbuf_off_t written = rbuf->written, next, ready;
+ size_t towrite;
+retry:
+ /*
+ * Get the stable 'next' offset. Note: stable_nextoff() issued
+ * a load memory barrier. The area between the 'written' offset
+ * and the 'next' offset will be the *preliminary* target buffer
+ * area to be consumed.
+ */
+ next = stable_nextoff(rbuf) & RBUF_OFF_MASK;
+ if (written == next) {
+ /* If producers did not advance, then nothing to do. */
+ return 0;
+ }
+
+ /*
+ * Observe the 'ready' offset of each producer.
+ *
+ * At this point, some producer might have already triggered the
+ * wrap-around and some (or all) seen 'ready' values might be in
+ * the range between 0 and 'written'. We have to skip them.
+ */
+ ready = RBUF_OFF_MAX;
+
+ for (unsigned i = 0; i < rbuf->nworkers; i++) {
+ ringbuf_worker_t *w = &rbuf->workers[i];
+ ringbuf_off_t seen_off;
+
+ /*
+ * Skip if the worker has not registered.
+ *
+ * Get a stable 'seen' value. This is necessary since we
+ * want to discard the stale 'seen' values.
+ */
+ if (!atomic_load_explicit(&w->registered, memory_order_relaxed))
+ continue;
+ seen_off = stable_seenoff(w);
+
+ /*
+ * Ignore the offsets after the possible wrap-around.
+ * We are interested in the smallest seen offset that is
+ * not behind the 'written' offset.
+ */
+ if (seen_off >= written) {
+ ready = MIN(seen_off, ready);
+ }
+ ASSERT(ready >= written);
+ }
+
+ /*
+ * Finally, we need to determine whether wrap-around occurred
+ * and deduct the safe 'ready' offset.
+ */
+ if (next < written) {
+ const ringbuf_off_t end = MIN(rbuf->space, rbuf->end);
+
+ /*
+ * Wrap-around case. Check for the cut off first.
+ *
+ * Reset the 'written' offset if it reached the end of
+ * the buffer or the 'end' offset (if set by a producer).
+ * However, we must check that the producer is actually
+ * done (the observed 'ready' offsets are clear).
+ */
+ if (ready == RBUF_OFF_MAX && written == end) {
+ /*
+ * Clear the 'end' offset if was set.
+ */
+ if (rbuf->end != RBUF_OFF_MAX) {
+ rbuf->end = RBUF_OFF_MAX;
+ }
+
+ /*
+ * Wrap-around the consumer and start from zero.
+ */
+ written = 0;
+ atomic_store_explicit(&rbuf->written,
+ written, memory_order_release);
+ goto retry;
+ }
+
+ /*
+ * We cannot wrap-around yet; there is data to consume at
+ * the end. The ready range is smallest of the observed
+ * 'ready' or the 'end' offset. If neither is set, then
+ * the actual end of the buffer.
+ */
+ ASSERT(ready > next);
+ ready = MIN(ready, end);
+ ASSERT(ready >= written);
+ } else {
+ /*
+ * Regular case. Up to the observed 'ready' (if set)
+ * or the 'next' offset.
+ */
+ ready = MIN(ready, next);
+ }
+ towrite = ready - written;
+ *offset = written;
+
+ ASSERT(ready >= written);
+ ASSERT(towrite <= rbuf->space);
+ return towrite;
+}
+
+/*
+ * ringbuf_release: indicate that the consumed range can now be released.
+ */
+void
+ringbuf_release(ringbuf_t *rbuf, size_t nbytes)
+{
+ const size_t nwritten = rbuf->written + nbytes;
+
+ ASSERT(rbuf->written <= rbuf->space);
+ ASSERT(rbuf->written <= rbuf->end);
+ ASSERT(nwritten <= rbuf->space);
+
+ rbuf->written = (nwritten == rbuf->space) ? 0 : nwritten;
+}
diff --git a/deps/ringbuf/ringbuf.h b/deps/ringbuf/ringbuf.h
new file mode 100644
index 0000000..e8fc767
--- /dev/null
+++ b/deps/ringbuf/ringbuf.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016 Mindaugas Rasiukevicius <rmind at noxt eu>
+ * All rights reserved.
+ *
+ * Use is subject to license terms, as specified in the LICENSE file.
+ */
+
+#ifndef _RINGBUF_H_
+#define _RINGBUF_H_
+
+__BEGIN_DECLS
+
+typedef struct ringbuf ringbuf_t;
+typedef struct ringbuf_worker ringbuf_worker_t;
+
+int ringbuf_setup(ringbuf_t *, unsigned, size_t);
+void ringbuf_get_sizes(unsigned, size_t *, size_t *);
+
+ringbuf_worker_t *ringbuf_register(ringbuf_t *, unsigned);
+void ringbuf_unregister(ringbuf_t *, ringbuf_worker_t *);
+
+ssize_t ringbuf_acquire(ringbuf_t *, ringbuf_worker_t *, size_t);
+void ringbuf_produce(ringbuf_t *, ringbuf_worker_t *);
+size_t ringbuf_consume(ringbuf_t *, size_t *);
+void ringbuf_release(ringbuf_t *, size_t);
+
+__END_DECLS
+
+#endif
diff --git a/deps/ringbuf/utils.h b/deps/ringbuf/utils.h
new file mode 100644
index 0000000..413157b
--- /dev/null
+++ b/deps/ringbuf/utils.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Berkeley Software Design, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)cdefs.h 8.8 (Berkeley) 1/9/95
+ */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include <assert.h>
+
+/*
+ * A regular assert (debug/diagnostic only).
+ */
+#if defined(DEBUG)
+#define ASSERT assert
+#else
+#define ASSERT(x)
+#endif
+
+/*
+ * Minimum, maximum and rounding macros.
+ */
+
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+/*
+ * Branch prediction macros.
+ */
+#ifndef __predict_true
+#define __predict_true(x) __builtin_expect((x) != 0, 1)
+#define __predict_false(x) __builtin_expect((x) != 0, 0)
+#endif
+
+/*
+ * Atomic operations and memory barriers. If C11 API is not available,
+ * then wrap the GCC builtin routines.
+ *
+ * Note: This atomic_compare_exchange_weak does not do the C11 thing of
+ * filling *(expected) with the actual value, because we don't need
+ * that here.
+ */
+#ifndef atomic_compare_exchange_weak
+#define atomic_compare_exchange_weak(ptr, expected, desired) \
+ __sync_bool_compare_and_swap(ptr, *(expected), desired)
+#endif
+
+#ifndef atomic_thread_fence
+#define memory_order_relaxed __ATOMIC_RELAXED
+#define memory_order_acquire __ATOMIC_ACQUIRE
+#define memory_order_release __ATOMIC_RELEASE
+#define memory_order_seq_cst __ATOMIC_SEQ_CST
+#define atomic_thread_fence(m) __atomic_thread_fence(m)
+#endif
+#ifndef atomic_store_explicit
+#define atomic_store_explicit __atomic_store_n
+#endif
+#ifndef atomic_load_explicit
+#define atomic_load_explicit __atomic_load_n
+#endif
+
+/*
+ * Exponential back-off for the spinning paths.
+ */
+#define SPINLOCK_BACKOFF_MIN 4
+#define SPINLOCK_BACKOFF_MAX 128
+#if defined(__x86_64__) || defined(__i386__)
+#define SPINLOCK_BACKOFF_HOOK __asm volatile("pause" ::: "memory")
+#else
+#define SPINLOCK_BACKOFF_HOOK
+#endif
+#define SPINLOCK_BACKOFF(count) \
+do { \
+ for (int __i = (count); __i != 0; __i--) { \
+ SPINLOCK_BACKOFF_HOOK; \
+ } \
+ if ((count) < SPINLOCK_BACKOFF_MAX) \
+ (count) += (count); \
+} while (/* CONSTCOND */ 0);
+
+#endif
diff --git a/deps/sds/CMakeLists.txt b/deps/sds/CMakeLists.txt
new file mode 100644
index 0000000..116d6ae
--- /dev/null
+++ b/deps/sds/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_library(sds STATIC sds.c)
+target_include_directories(sds PUBLIC ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file
diff --git a/deps/sds/sds.c b/deps/sds/sds.c
new file mode 100644
index 0000000..9510a8b
--- /dev/null
+++ b/deps/sds/sds.c
@@ -0,0 +1,1335 @@
+/* https://github.com/antirez/sds
+ * SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <limits.h>
+#include "sds.h"
+#include "sdsalloc.h"
+
+const char *SDS_NOINIT = "SDS_NOINIT";
+
+static inline int sdsHdrSize(char type) {
+ switch(type&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return sizeof(struct sdshdr5);
+ case SDS_TYPE_8:
+ return sizeof(struct sdshdr8);
+ case SDS_TYPE_16:
+ return sizeof(struct sdshdr16);
+ case SDS_TYPE_32:
+ return sizeof(struct sdshdr32);
+ case SDS_TYPE_64:
+ return sizeof(struct sdshdr64);
+ }
+ return 0;
+}
+
+static inline char sdsReqType(size_t string_size) {
+ if (string_size < 1<<5)
+ return SDS_TYPE_5;
+ if (string_size < 1<<8)
+ return SDS_TYPE_8;
+ if (string_size < 1<<16)
+ return SDS_TYPE_16;
+#if (LONG_MAX == LLONG_MAX)
+ if (string_size < 1ll<<32)
+ return SDS_TYPE_32;
+ return SDS_TYPE_64;
+#else
+ return SDS_TYPE_32;
+#endif
+}
+
+/* Create a new sds string with the content specified by the 'init' pointer
+ * and 'initlen'.
+ * If NULL is used for 'init' the string is initialized with zero bytes.
+ * If SDS_NOINIT is used, the buffer is left uninitialized;
+ *
+ * The string is always null-terminated (all the sds strings are, always) so
+ * even if you create an sds string with:
+ *
+ * mystring = sdsnewlen("abc",3);
+ *
+ * You can print the string with printf() as there is an implicit \0 at the
+ * end of the string. However the string is binary safe and can contain
+ * \0 characters in the middle, as the length is stored in the sds header. */
+sds sdsnewlen(const void *init, size_t initlen) {
+ void *sh;
+ sds s;
+ char type = sdsReqType(initlen);
+ /* Empty strings are usually created in order to append. Use type 8
+ * since type 5 is not good at this. */
+ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
+ int hdrlen = sdsHdrSize(type);
+ unsigned char *fp; /* flags pointer. */
+
+ sh = s_malloc(hdrlen+initlen+1);
+ if (sh == NULL) return NULL;
+ if (init==SDS_NOINIT)
+ init = NULL;
+ else if (!init)
+ memset(sh, 0, hdrlen+initlen+1);
+ s = (char*)sh+hdrlen;
+ fp = ((unsigned char*)s)-1;
+ switch(type) {
+ case SDS_TYPE_5: {
+ *fp = type | (initlen << SDS_TYPE_BITS);
+ break;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ }
+ if (initlen && init)
+ memcpy(s, init, initlen);
+ s[initlen] = '\0';
+ return s;
+}
+
+/* Create an empty (zero length) sds string. Even in this case the string
+ * always has an implicit null term. */
+sds sdsempty(void) {
+ return sdsnewlen("",0);
+}
+
+/* Create a new sds string starting from a null terminated C string. */
+sds sdsnew(const char *init) {
+ size_t initlen = (init == NULL) ? 0 : strlen(init);
+ return sdsnewlen(init, initlen);
+}
+
+/* Duplicate an sds string. */
+sds sdsdup(const sds s) {
+ return sdsnewlen(s, sdslen(s));
+}
+
+/* Free an sds string. No operation is performed if 's' is NULL. */
+void sdsfree(sds s) {
+ if (s == NULL) return;
+ s_free((char*)s-sdsHdrSize(s[-1]));
+}
+
+/* Set the sds string length to the length as obtained with strlen(), so
+ * considering as content only up to the first null term character.
+ *
+ * This function is useful when the sds string is hacked manually in some
+ * way, like in the following example:
+ *
+ * s = sdsnew("foobar");
+ * s[2] = '\0';
+ * sdsupdatelen(s);
+ * printf("%d\n", sdslen(s));
+ *
+ * The output will be "2", but if we comment out the call to sdsupdatelen()
+ * the output will be "6" as the string was modified but the logical length
+ * remains 6 bytes. */
+void sdsupdatelen(sds s) {
+ size_t reallen = strlen(s);
+ sdssetlen(s, reallen);
+}
+
+/* Modify an sds string in-place to make it empty (zero length).
+ * However all the existing buffer is not discarded but set as free space
+ * so that next append operations will not require allocations up to the
+ * number of bytes previously available. */
+void sdsclear(sds s) {
+ sdssetlen(s, 0);
+ s[0] = '\0';
+}
+
+/* Enlarge the free space at the end of the sds string so that the caller
+ * is sure that after calling this function can overwrite up to addlen
+ * bytes after the end of the string, plus one more byte for nul term.
+ *
+ * Note: this does not change the *length* of the sds string as returned
+ * by sdslen(), but only the free buffer space we have. */
+sds sdsMakeRoomFor(sds s, size_t addlen) {
+ void *sh, *newsh;
+ size_t avail = sdsavail(s);
+ size_t len, newlen;
+#ifndef NDEBUG
+ size_t reqlen;
+#endif
+ char type, oldtype = s[-1] & SDS_TYPE_MASK;
+ int hdrlen;
+
+ /* Return ASAP if there is enough space left. */
+ if (avail >= addlen) return s;
+
+ len = sdslen(s);
+ sh = (char*)s-sdsHdrSize(oldtype);
+#ifndef NDEBUG
+ reqlen = (len+addlen);
+#endif
+ newlen = (len+addlen);
+ if (newlen < SDS_MAX_PREALLOC)
+ newlen *= 2;
+ else
+ newlen += SDS_MAX_PREALLOC;
+
+ type = sdsReqType(newlen);
+
+ /* Don't use type 5: the user is appending to the string and type 5 is
+ * not able to remember empty space, so sdsMakeRoomFor() must be called
+ * at every appending operation. */
+ if (type == SDS_TYPE_5) type = SDS_TYPE_8;
+
+ hdrlen = sdsHdrSize(type);
+ assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
+ if (oldtype==type) {
+ newsh = s_realloc(sh, hdrlen+newlen+1);
+ if (newsh == NULL) return NULL;
+ s = (char*)newsh+hdrlen;
+ } else {
+ /* Since the header size changes, need to move the string forward,
+ * and can't use realloc */
+ newsh = s_malloc(hdrlen+newlen+1);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ sdssetlen(s, len);
+ }
+ sdssetalloc(s, newlen);
+ return s;
+}
+
+/* Reallocate the sds string so that it has no free space at the end. The
+ * contained string remains not altered, but next concatenation operations
+ * will require a reallocation.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdsRemoveFreeSpace(sds s) {
+ void *sh, *newsh;
+ char type, oldtype = s[-1] & SDS_TYPE_MASK;
+ int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
+ size_t len = sdslen(s);
+ size_t avail = sdsavail(s);
+ sh = (char*)s-oldhdrlen;
+
+ /* Return ASAP if there is no space left. */
+ if (avail == 0) return s;
+
+ /* Check what would be the minimum SDS header that is just good enough to
+ * fit this string. */
+ type = sdsReqType(len);
+ hdrlen = sdsHdrSize(type);
+
+ /* If the type is the same, or at least a large enough type is still
+ * required, we just realloc(), letting the allocator to do the copy
+ * only if really needed. Otherwise if the change is huge, we manually
+ * reallocate the string to use the different header type. */
+ if (oldtype==type || type > SDS_TYPE_8) {
+ newsh = s_realloc(sh, oldhdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ s = (char*)newsh+oldhdrlen;
+ } else {
+ newsh = s_malloc(hdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ sdssetlen(s, len);
+ }
+ sdssetalloc(s, len);
+ return s;
+}
+
+/* Return the total size of the allocation of the specified sds string,
+ * including:
+ * 1) The sds header before the pointer.
+ * 2) The string.
+ * 3) The free buffer at the end if any.
+ * 4) The implicit null term.
+ */
+size_t sdsAllocSize(sds s) {
+ size_t alloc = sdsalloc(s);
+ return sdsHdrSize(s[-1])+alloc+1;
+}
+
+/* Return the pointer of the actual SDS allocation (normally SDS strings
+ * are referenced by the start of the string buffer). */
+void *sdsAllocPtr(sds s) {
+ return (void*) (s-sdsHdrSize(s[-1]));
+}
+
+/* Increment the sds length and decrements the left free space at the
+ * end of the string according to 'incr'. Also set the null term
+ * in the new end of the string.
+ *
+ * This function is used in order to fix the string length after the
+ * user calls sdsMakeRoomFor(), writes something after the end of
+ * the current string, and finally needs to set the new length.
+ *
+ * Note: it is possible to use a negative increment in order to
+ * right-trim the string.
+ *
+ * Usage example:
+ *
+ * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
+ * following schema, to cat bytes coming from the kernel to the end of an
+ * sds string without copying into an intermediate buffer:
+ *
+ * oldlen = sdslen(s);
+ * s = sdsMakeRoomFor(s, BUFFER_SIZE);
+ * nread = read(fd, s+oldlen, BUFFER_SIZE);
+ * ... check for nread <= 0 and handle it ...
+ * sdsIncrLen(s, nread);
+ */
+void sdsIncrLen(sds s, ssize_t incr) {
+ unsigned char flags = s[-1];
+ size_t len;
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5: {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ unsigned char oldlen = SDS_TYPE_5_LEN(flags);
+ assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
+ *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
+ len = oldlen+incr;
+ break;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ default: len = 0; /* Just to avoid compilation warnings. */
+ }
+ s[len] = '\0';
+}
+
+/* Grow the sds to have the specified length. Bytes that were not part of
+ * the original length of the sds will be set to zero.
+ *
+ * if the specified length is smaller than the current length, no operation
+ * is performed. */
+sds sdsgrowzero(sds s, size_t len) {
+ size_t curlen = sdslen(s);
+
+ if (len <= curlen) return s;
+ s = sdsMakeRoomFor(s,len-curlen);
+ if (s == NULL) return NULL;
+
+ /* Make sure added region doesn't contain garbage */
+ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
+ sdssetlen(s, len);
+ return s;
+}
+
+/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
+ * end of the specified sds string 's'.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatlen(sds s, const void *t, size_t len) {
+ size_t curlen = sdslen(s);
+
+ s = sdsMakeRoomFor(s,len);
+ if (s == NULL) return NULL;
+ memcpy(s+curlen, t, len);
+ sdssetlen(s, curlen+len);
+ s[curlen+len] = '\0';
+ return s;
+}
+
+/* Append the specified null termianted C string to the sds string 's'.
+ *
+ * After the call, the passed sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscat(sds s, const char *t) {
+ return sdscatlen(s, t, strlen(t));
+}
+
+/* Append the specified sds 't' to the existing sds 's'.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatsds(sds s, const sds t) {
+ return sdscatlen(s, t, sdslen(t));
+}
+
+/* Destructively modify the sds string 's' to hold the specified binary
+ * safe string pointed by 't' of length 'len' bytes. */
+sds sdscpylen(sds s, const char *t, size_t len) {
+ if (sdsalloc(s) < len) {
+ s = sdsMakeRoomFor(s,len-sdslen(s));
+ if (s == NULL) return NULL;
+ }
+ memcpy(s, t, len);
+ s[len] = '\0';
+ sdssetlen(s, len);
+ return s;
+}
+
+/* Like sdscpylen() but 't' must be a null-terminated string so that the length
+ * of the string is obtained with strlen(). */
+sds sdscpy(sds s, const char *t) {
+ return sdscpylen(s, t, strlen(t));
+}
+
+/* Helper for sdscatlonglong() doing the actual number -> string
+ * conversion. 's' must point to a string with room for at least
+ * SDS_LLSTR_SIZE bytes.
+ *
+ * The function returns the length of the null-terminated string
+ * representation stored at 's'. */
+#define SDS_LLSTR_SIZE 21
+int sdsll2str(char *s, long long value) {
+ char *p, aux;
+ unsigned long long v;
+ size_t l;
+
+ /* Generate the string representation, this method produces
+ * an reversed string. */
+ if (value < 0) {
+ /* Since v is unsigned, if value==LLONG_MIN then
+ * -LLONG_MIN will overflow. */
+ if (value != LLONG_MIN) {
+ v = -value;
+ } else {
+ v = ((unsigned long long)LLONG_MAX) + 1;
+ }
+ } else {
+ v = value;
+ }
+
+ p = s;
+ do {
+ *p++ = '0'+(v%10);
+ v /= 10;
+ } while(v);
+ if (value < 0) *p++ = '-';
+
+ /* Compute length and add null term. */
+ l = p-s;
+ *p = '\0';
+
+ /* Reverse the string. */
+ p--;
+ while(s < p) {
+ aux = *s;
+ *s = *p;
+ *p = aux;
+ s++;
+ p--;
+ }
+ return l;
+}
+
+/* Identical sdsll2str(), but for unsigned long long type. */
+int sdsull2str(char *s, unsigned long long v) {
+ char *p, aux;
+ size_t l;
+
+ /* Generate the string representation, this method produces
+ * an reversed string. */
+ p = s;
+ do {
+ *p++ = '0'+(v%10);
+ v /= 10;
+ } while(v);
+
+ /* Compute length and add null term. */
+ l = p-s;
+ *p = '\0';
+
+ /* Reverse the string. */
+ p--;
+ while(s < p) {
+ aux = *s;
+ *s = *p;
+ *p = aux;
+ s++;
+ p--;
+ }
+ return l;
+}
+
+/* Create an sds string from a long long value. It is much faster than:
+ *
+ * sdscatprintf(sdsempty(),"%lld\n", value);
+ */
+sds sdsfromlonglong(long long value) {
+ char buf[SDS_LLSTR_SIZE];
+ int len = sdsll2str(buf,value);
+
+ return sdsnewlen(buf,len);
+}
+
+/* Like sdscatprintf() but gets va_list instead of being variadic. */
+sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
+ va_list cpy;
+ char staticbuf[1024], *buf = staticbuf, *t;
+ size_t buflen = strlen(fmt)*2;
+ int bufstrlen;
+
+ /* We try to start using a static buffer for speed.
+ * If not possible we revert to heap allocation. */
+ if (buflen > sizeof(staticbuf)) {
+ buf = s_malloc(buflen);
+ if (buf == NULL) return NULL;
+ } else {
+ buflen = sizeof(staticbuf);
+ }
+
+ /* Alloc enough space for buffer and \0 after failing to
+ * fit the string in the current buffer size. */
+ while(1) {
+ va_copy(cpy,ap);
+ bufstrlen = vsnprintf(buf, buflen, fmt, cpy);
+ va_end(cpy);
+ if (bufstrlen < 0) {
+ if (buf != staticbuf) s_free(buf);
+ return NULL;
+ }
+ if (((size_t)bufstrlen) >= buflen) {
+ if (buf != staticbuf) s_free(buf);
+ buflen = ((size_t)bufstrlen) + 1;
+ buf = s_malloc(buflen);
+ if (buf == NULL) return NULL;
+ continue;
+ }
+ break;
+ }
+
+ /* Finally concat the obtained string to the SDS string and return it. */
+ t = sdscatlen(s, buf, bufstrlen);
+ if (buf != staticbuf) s_free(buf);
+ return t;
+}
+
+/* Append to the sds string 's' a string obtained using printf-alike format
+ * specifier.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call.
+ *
+ * Example:
+ *
+ * s = sdsnew("Sum is: ");
+ * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
+ *
+ * Often you need to create a string from scratch with the printf-alike
+ * format. When this is the need, just use sdsempty() as the target string:
+ *
+ * s = sdscatprintf(sdsempty(), "... your format ...", args);
+ */
+sds sdscatprintf(sds s, const char *fmt, ...) {
+ va_list ap;
+ char *t;
+ va_start(ap, fmt);
+ t = sdscatvprintf(s,fmt,ap);
+ va_end(ap);
+ return t;
+}
+
+/* This function is similar to sdscatprintf, but much faster as it does
+ * not rely on sprintf() family functions implemented by the libc that
+ * are often very slow. Moreover directly handling the sds string as
+ * new data is concatenated provides a performance improvement.
+ *
+ * However this function only handles an incompatible subset of printf-alike
+ * format specifiers:
+ *
+ * %s - C String
+ * %S - SDS string
+ * %i - signed int
+ * %I - 64 bit signed integer (long long, int64_t)
+ * %u - unsigned int
+ * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
+ * %% - Verbatim "%" character.
+ */
+sds sdscatfmt(sds s, const char *fmt, ...) {
+ size_t initlen = sdslen(s);
+ const char *f;
+ long i;
+ va_list ap;
+
+ /* To avoid continuous reallocations, let's start with a buffer that
+ * can hold at least two times the format string itself. It's not the
+ * best heuristic but seems to work in practice. */
+ s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
+ va_start(ap,fmt);
+ f = (const char *)fmt; /* Next format specifier byte to process. */
+ i = initlen; /* Position of the next byte to write to dest str. */
+ while(*f) {
+ char next, *str;
+ size_t l;
+ long long num;
+ unsigned long long unum;
+
+ /* Make sure there is always space for at least 1 char. */
+ if (sdsavail(s)==0) {
+ s = sdsMakeRoomFor(s,1);
+ }
+
+ switch(*f) {
+ case '%':
+ next = *(f+1);
+ if (next == '\0') break;
+ f++;
+ switch(next) {
+ case 's':
+ case 'S':
+ str = va_arg(ap,char*);
+ l = (next == 's') ? strlen(str) : sdslen(str);
+ if (sdsavail(s) < l) {
+ s = sdsMakeRoomFor(s,l);
+ }
+ memcpy(s+i,str,l);
+ sdsinclen(s,l);
+ i += l;
+ break;
+ case 'i':
+ case 'I':
+ if (next == 'i')
+ num = va_arg(ap,int);
+ else
+ num = va_arg(ap,long long);
+ {
+ char buf[SDS_LLSTR_SIZE];
+ l = sdsll2str(buf,num);
+ if (sdsavail(s) < l) {
+ s = sdsMakeRoomFor(s,l);
+ }
+ memcpy(s+i,buf,l);
+ sdsinclen(s,l);
+ i += l;
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (next == 'u')
+ unum = va_arg(ap,unsigned int);
+ else
+ unum = va_arg(ap,unsigned long long);
+ {
+ char buf[SDS_LLSTR_SIZE];
+ l = sdsull2str(buf,unum);
+ if (sdsavail(s) < l) {
+ s = sdsMakeRoomFor(s,l);
+ }
+ memcpy(s+i,buf,l);
+ sdsinclen(s,l);
+ i += l;
+ }
+ break;
+ default: /* Handle %% and generally %<unknown>. */
+ s[i++] = next;
+ sdsinclen(s,1);
+ break;
+ }
+ break;
+ default:
+ s[i++] = *f;
+ sdsinclen(s,1);
+ break;
+ }
+ f++;
+ }
+ va_end(ap);
+
+ /* Add null-term */
+ s[i] = '\0';
+ return s;
+}
+
+/* Remove the part of the string from left and from right composed just of
+ * contiguous characters found in 'cset', that is a null terminated C string.
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call.
+ *
+ * Example:
+ *
+ * s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
+ * s = sdstrim(s,"Aa. :");
+ * printf("%s\n", s);
+ *
+ * Output will be just "HelloWorld".
+ */
+sds sdstrim(sds s, const char *cset) {
+ char *end, *sp, *ep;
+ size_t len;
+
+ sp = s;
+ ep = end = s+sdslen(s)-1;
+ while(sp <= end && strchr(cset, *sp)) sp++;
+ while(ep > sp && strchr(cset, *ep)) ep--;
+ len = (ep-sp)+1;
+ if (s != sp) memmove(s, sp, len);
+ s[len] = '\0';
+ sdssetlen(s,len);
+ return s;
+}
+
+/* Turn the string into a smaller (or equal) string containing only the
+ * substring specified by the 'start' and 'end' indexes.
+ *
+ * start and end can be negative, where -1 means the last character of the
+ * string, -2 the penultimate character, and so forth.
+ *
+ * The interval is inclusive, so the start and end characters will be part
+ * of the resulting string.
+ *
+ * The string is modified in-place.
+ *
+ * Example:
+ *
+ * s = sdsnew("Hello World");
+ * sdsrange(s,1,-1); => "ello World"
+ */
+void sdsrange(sds s, ssize_t start, ssize_t end) {
+ size_t newlen, len = sdslen(s);
+
+ if (len == 0) return;
+ if (start < 0) {
+ start = len+start;
+ if (start < 0) start = 0;
+ }
+ if (end < 0) {
+ end = len+end;
+ if (end < 0) end = 0;
+ }
+ newlen = (start > end) ? 0 : (end-start)+1;
+ if (newlen != 0) {
+ if (start >= (ssize_t)len) {
+ newlen = 0;
+ } else if (end >= (ssize_t)len) {
+ end = len-1;
+ newlen = (end-start)+1;
+ }
+ }
+ if (start && newlen) memmove(s, s+start, newlen);
+ s[newlen] = 0;
+ sdssetlen(s,newlen);
+}
+
+/* Apply tolower() to every character of the sds string 's'. */
+void sdstolower(sds s) {
+ size_t len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = tolower(s[j]);
+}
+
+/* Apply toupper() to every character of the sds string 's'. */
+void sdstoupper(sds s) {
+ size_t len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = toupper(s[j]);
+}
+
+/* Compare two sds strings s1 and s2 with memcmp().
+ *
+ * Return value:
+ *
+ * positive if s1 > s2.
+ * negative if s1 < s2.
+ * 0 if s1 and s2 are exactly the same binary string.
+ *
+ * If two strings share exactly the same prefix, but one of the two has
+ * additional characters, the longer string is considered to be greater than
+ * the smaller one. */
+int sdscmp(const sds s1, const sds s2) {
+ size_t l1, l2, minlen;
+ int cmp;
+
+ l1 = sdslen(s1);
+ l2 = sdslen(s2);
+ minlen = (l1 < l2) ? l1 : l2;
+ cmp = memcmp(s1,s2,minlen);
+ if (cmp == 0) return l1>l2? 1: (l1<l2? -1: 0);
+ return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of sds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * sdssplit("foo_-_bar","_-_"); will return two
+ * elements "foo" and "bar".
+ *
+ * This version of the function is binary-safe but
+ * requires length arguments. sdssplit() is just the
+ * same function but for zero-terminated strings.
+ */
+sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
+ int elements = 0, slots = 5;
+ long start = 0, j;
+ sds *tokens;
+
+ if (seplen < 1 || len <= 0) {
+ *count = 0;
+ return NULL;
+ }
+
+ tokens = s_malloc(sizeof(sds)*slots);
+ if (tokens == NULL) return NULL;
+
+ for (j = 0; j < (len-(seplen-1)); j++) {
+ /* make sure there is room for the next element and the final one */
+ if (slots < elements+2) {
+ sds *newtokens;
+
+ slots *= 2;
+ newtokens = s_realloc(tokens,sizeof(sds)*slots);
+ if (newtokens == NULL) goto cleanup;
+ tokens = newtokens;
+ }
+ /* search the separator */
+ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
+ tokens[elements] = sdsnewlen(s+start,j-start);
+ if (tokens[elements] == NULL) goto cleanup;
+ elements++;
+ start = j+seplen;
+ j = j+seplen-1; /* skip the separator */
+ }
+ }
+ /* Add the final element. We are sure there is room in the tokens array. */
+ tokens[elements] = sdsnewlen(s+start,len-start);
+ if (tokens[elements] == NULL) goto cleanup;
+ elements++;
+ *count = elements;
+ return tokens;
+
+cleanup:
+ {
+ int i;
+ for (i = 0; i < elements; i++) sdsfree(tokens[i]);
+ s_free(tokens);
+ *count = 0;
+ return NULL;
+ }
+}
+
+/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
+void sdsfreesplitres(sds *tokens, int count) {
+ if (!tokens) return;
+ while(count--)
+ sdsfree(tokens[count]);
+ s_free(tokens);
+}
+
+/* Append to the sds string "s" an escaped string representation where
+ * all the non-printable characters (tested with isprint()) are turned into
+ * escapes in the form "\n\r\a...." or "\x<hex-number>".
+ *
+ * After the call, the modified sds string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds sdscatrepr(sds s, const char *p, size_t len) {
+ s = sdscatlen(s,"\"",1);
+ while(len--) {
+ switch(*p) {
+ case '\\':
+ case '"':
+ s = sdscatprintf(s,"\\%c",*p);
+ break;
+ case '\n': s = sdscatlen(s,"\\n",2); break;
+ case '\r': s = sdscatlen(s,"\\r",2); break;
+ case '\t': s = sdscatlen(s,"\\t",2); break;
+ case '\a': s = sdscatlen(s,"\\a",2); break;
+ case '\b': s = sdscatlen(s,"\\b",2); break;
+ default:
+ if (isprint(*p))
+ s = sdscatprintf(s,"%c",*p);
+ else
+ s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
+ break;
+ }
+ p++;
+ }
+ return sdscatlen(s,"\"",1);
+}
+
+/* Helper function for sdssplitargs() that returns non zero if 'c'
+ * is a valid hex digit. */
+int is_hex_digit(char c) {
+ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F');
+}
+
+/* Helper function for sdssplitargs() that converts a hex digit into an
+ * integer from 0 to 15 */
+int hex_digit_to_int(char c) {
+ switch(c) {
+ case '0': return 0;
+ case '1': return 1;
+ case '2': return 2;
+ case '3': return 3;
+ case '4': return 4;
+ case '5': return 5;
+ case '6': return 6;
+ case '7': return 7;
+ case '8': return 8;
+ case '9': return 9;
+ case 'a': case 'A': return 10;
+ case 'b': case 'B': return 11;
+ case 'c': case 'C': return 12;
+ case 'd': case 'D': return 13;
+ case 'e': case 'E': return 14;
+ case 'f': case 'F': return 15;
+ default: return 0;
+ }
+}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of sds is returned.
+ *
+ * The caller should free the resulting array of sds strings with
+ * sdsfreesplitres().
+ *
+ * Note that sdscatrepr() is able to convert back a string into
+ * a quoted string in the same format sdssplitargs() is able to parse.
+ *
+ * The function returns the allocated tokens on success, even when the
+ * input string is empty, or NULL if the input contains unbalanced
+ * quotes or closed quotes followed by non space characters
+ * as in: "foo"bar or "foo'
+ */
+sds *sdssplitargs(const char *line, int *argc) {
+ const char *p = line;
+ char *current = NULL;
+ char **vector = NULL;
+
+ *argc = 0;
+ while(1) {
+ /* skip blanks */
+ while(*p && isspace(*p)) p++;
+ if (*p) {
+ /* get a token */
+ int inq=0; /* set to 1 if we are in "quotes" */
+ int insq=0; /* set to 1 if we are in 'single quotes' */
+ int done=0;
+
+ if (current == NULL) current = sdsempty();
+ while(!done) {
+ if (inq) {
+ if (*p == '\\' && *(p+1) == 'x' &&
+ is_hex_digit(*(p+2)) &&
+ is_hex_digit(*(p+3)))
+ {
+ unsigned char byte;
+
+ byte = (hex_digit_to_int(*(p+2))*16)+
+ hex_digit_to_int(*(p+3));
+ current = sdscatlen(current,(char*)&byte,1);
+ p += 3;
+ } else if (*p == '\\' && *(p+1)) {
+ char c;
+
+ p++;
+ switch(*p) {
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'b': c = '\b'; break;
+ case 'a': c = '\a'; break;
+ default: c = *p; break;
+ }
+ current = sdscatlen(current,&c,1);
+ } else if (*p == '"') {
+ /* closing quote must be followed by a space or
+ * nothing at all. */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
+ } else {
+ current = sdscatlen(current,p,1);
+ }
+ } else if (insq) {
+ if (*p == '\\' && *(p+1) == '\'') {
+ p++;
+ current = sdscatlen(current,"'",1);
+ } else if (*p == '\'') {
+ /* closing quote must be followed by a space or
+ * nothing at all. */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
+ } else {
+ current = sdscatlen(current,p,1);
+ }
+ } else {
+ switch(*p) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\0':
+ done=1;
+ break;
+ case '"':
+ inq=1;
+ break;
+ case '\'':
+ insq=1;
+ break;
+ default:
+ current = sdscatlen(current,p,1);
+ break;
+ }
+ }
+ if (*p) p++;
+ }
+ /* add the token to the vector */
+ vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ } else {
+ /* Even on empty input string return something not NULL. */
+ if (vector == NULL) vector = s_malloc(sizeof(void*));
+ return vector;
+ }
+ }
+
+err:
+ while((*argc)--)
+ sdsfree(vector[*argc]);
+ s_free(vector);
+ if (current) sdsfree(current);
+ *argc = 0;
+ return NULL;
+}
+
+/* Modify the string substituting all the occurrences of the set of
+ * characters specified in the 'from' string to the corresponding character
+ * in the 'to' array.
+ *
+ * For instance: sdsmapchars(mystring, "ho", "01", 2)
+ * will have the effect of turning the string "hello" into "0ell1".
+ *
+ * The function returns the sds string pointer, that is always the same
+ * as the input pointer since no resize is needed. */
+sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
+ size_t j, i, l = sdslen(s);
+
+ for (j = 0; j < l; j++) {
+ for (i = 0; i < setlen; i++) {
+ if (s[j] == from[i]) {
+ s[j] = to[i];
+ break;
+ }
+ }
+ }
+ return s;
+}
+
+/* Join an array of C strings using the specified separator (also a C string).
+ * Returns the result as an sds string. */
+sds sdsjoin(char **argv, int argc, char *sep) {
+ sds join = sdsempty();
+ int j;
+
+ for (j = 0; j < argc; j++) {
+ join = sdscat(join, argv[j]);
+ if (j != argc-1) join = sdscat(join,sep);
+ }
+ return join;
+}
+
+/* Like sdsjoin, but joins an array of SDS strings. */
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
+ sds join = sdsempty();
+ int j;
+
+ for (j = 0; j < argc; j++) {
+ join = sdscatsds(join, argv[j]);
+ if (j != argc-1) join = sdscatlen(join,sep,seplen);
+ }
+ return join;
+}
+
+/* Wrappers to the allocators used by SDS. Note that SDS will actually
+ * just use the macros defined into sdsalloc.h in order to avoid to pay
+ * the overhead of function calls. Here we define these wrappers only for
+ * the programs SDS is linked to, if they want to touch the SDS internals
+ * even if they use a different allocator. */
+void *sds_malloc(size_t size) { return s_malloc(size); }
+void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }
+void sds_free(void *ptr) { s_free(ptr); }
+
+#if defined(SDS_TEST_MAIN)
+#include <stdio.h>
+#include "testhelp.h"
+#include "limits.h"
+
+#define UNUSED(x) (void)(x)
+int sdsTest(void) {
+ {
+ sds x = sdsnew("foo"), y;
+
+ test_cond("Create a string and obtain the length",
+ sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
+
+ sdsfree(x);
+ x = sdsnewlen("foo",2);
+ test_cond("Create a string with specified length",
+ sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
+
+ x = sdscat(x,"bar");
+ test_cond("Strings concatenation",
+ sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
+
+ x = sdscpy(x,"a");
+ test_cond("sdscpy() against an originally longer string",
+ sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
+
+ x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
+ test_cond("sdscpy() against an originally shorter string",
+ sdslen(x) == 33 &&
+ memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
+
+ sdsfree(x);
+ x = sdscatprintf(sdsempty(),"%d",123);
+ test_cond("sdscatprintf() seems working in the base case",
+ sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
+
+ sdsfree(x);
+ x = sdscatprintf(sdsempty(),"a%cb",0);
+ test_cond("sdscatprintf() seems working with \\0 inside of result",
+ sdslen(x) == 3 && memcmp(x,"a\0""b\0",4) == 0)
+
+ {
+ sdsfree(x);
+ char etalon[1024*1024];
+ for (size_t i = 0; i < sizeof(etalon); i++) {
+ etalon[i] = '0';
+ }
+ x = sdscatprintf(sdsempty(),"%0*d",(int)sizeof(etalon),0);
+ test_cond("sdscatprintf() can print 1MB",
+ sdslen(x) == sizeof(etalon) && memcmp(x,etalon,sizeof(etalon)) == 0)
+ }
+
+ sdsfree(x);
+ x = sdsnew("--");
+ x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
+ test_cond("sdscatfmt() seems working in the base case",
+ sdslen(x) == 60 &&
+ memcmp(x,"--Hello Hi! World -9223372036854775808,"
+ "9223372036854775807--",60) == 0)
+ printf("[%s]\n",x);
+
+ sdsfree(x);
+ x = sdsnew("--");
+ x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
+ test_cond("sdscatfmt() seems working with unsigned numbers",
+ sdslen(x) == 35 &&
+ memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
+
+ sdsfree(x);
+ x = sdsnew(" x ");
+ sdstrim(x," x");
+ test_cond("sdstrim() works when all chars match",
+ sdslen(x) == 0)
+
+ sdsfree(x);
+ x = sdsnew(" x ");
+ sdstrim(x," ");
+ test_cond("sdstrim() works when a single char remains",
+ sdslen(x) == 1 && x[0] == 'x')
+
+ sdsfree(x);
+ x = sdsnew("xxciaoyyy");
+ sdstrim(x,"xy");
+ test_cond("sdstrim() correctly trims characters",
+ sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
+
+ y = sdsdup(x);
+ sdsrange(y,1,1);
+ test_cond("sdsrange(...,1,1)",
+ sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
+
+ sdsfree(y);
+ y = sdsdup(x);
+ sdsrange(y,1,-1);
+ test_cond("sdsrange(...,1,-1)",
+ sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+ sdsfree(y);
+ y = sdsdup(x);
+ sdsrange(y,-2,-1);
+ test_cond("sdsrange(...,-2,-1)",
+ sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
+
+ sdsfree(y);
+ y = sdsdup(x);
+ sdsrange(y,2,1);
+ test_cond("sdsrange(...,2,1)",
+ sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+ sdsfree(y);
+ y = sdsdup(x);
+ sdsrange(y,1,100);
+ test_cond("sdsrange(...,1,100)",
+ sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+ sdsfree(y);
+ y = sdsdup(x);
+ sdsrange(y,100,100);
+ test_cond("sdsrange(...,100,100)",
+ sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("foo");
+ y = sdsnew("foa");
+ test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("bar");
+ y = sdsnew("bar");
+ test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("aar");
+ y = sdsnew("bar");
+ test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnewlen("\a\n\0foo\r",7);
+ y = sdscatrepr(sdsempty(),x,sdslen(x));
+ test_cond("sdscatrepr(...data...)",
+ memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
+
+ {
+ char *p;
+ int step = 10, j, i;
+
+ sdsfree(x);
+ sdsfree(y);
+ x = sdsnew("0");
+ test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0);
+
+ /* Run the test a few times in order to hit the first two
+ * SDS header types. */
+ for (i = 0; i < 10; i++) {
+ int oldlen = sdslen(x);
+ x = sdsMakeRoomFor(x,step);
+ int type = x[-1]&SDS_TYPE_MASK;
+
+ test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen);
+ if (type != SDS_TYPE_5) {
+ test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step);
+ }
+ p = x+oldlen;
+ for (j = 0; j < step; j++) {
+ p[j] = 'A'+j;
+ }
+ sdsIncrLen(x,step);
+ }
+ test_cond("sdsMakeRoomFor() content",
+ memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
+ test_cond("sdsMakeRoomFor() final length",sdslen(x)==101);
+
+ sdsfree(x);
+ }
+ }
+ test_report()
+ return 0;
+}
+#endif
+
+#ifdef SDS_TEST_MAIN
+int main(void) {
+ return sdsTest();
+}
+#endif
diff --git a/deps/sds/sds.h b/deps/sds/sds.h
new file mode 100644
index 0000000..077b882
--- /dev/null
+++ b/deps/sds/sds.h
@@ -0,0 +1,285 @@
+/* https://github.com/antirez/sds
+ * SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define SDS_MAX_PREALLOC (1024*1024)
+extern const char *SDS_NOINIT;
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+typedef char *sds;
+
+/* Note: sdshdr5 is never used, we just access the flags byte directly.
+ * However is here to document the layout of type 5 SDS strings. */
+struct __attribute__ ((__packed__)) sdshdr5 {
+ unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr8 {
+ uint8_t len; /* used */
+ uint8_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr16 {
+ uint16_t len; /* used */
+ uint16_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr32 {
+ uint32_t len; /* used */
+ uint32_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr64 {
+ uint64_t len; /* used */
+ uint64_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+
+#define SDS_TYPE_5 0
+#define SDS_TYPE_8 1
+#define SDS_TYPE_16 2
+#define SDS_TYPE_32 3
+#define SDS_TYPE_64 4
+#define SDS_TYPE_MASK 7
+#define SDS_TYPE_BITS 3
+// #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
+#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
+#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
+#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
+
+static inline size_t sdslen(const sds s) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return SDS_TYPE_5_LEN(flags);
+ case SDS_TYPE_8:
+ return SDS_HDR(8,s)->len;
+ case SDS_TYPE_16:
+ return SDS_HDR(16,s)->len;
+ case SDS_TYPE_32:
+ return SDS_HDR(32,s)->len;
+ case SDS_TYPE_64:
+ return SDS_HDR(64,s)->len;
+ }
+ return 0;
+}
+
+static inline size_t sdsavail(const sds s) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5: {
+ return 0;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ return sh->alloc - sh->len;
+ }
+ }
+ return 0;
+}
+
+static inline void sdssetlen(sds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+ }
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->len = newlen;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->len = newlen;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->len = newlen;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->len = newlen;
+ break;
+ }
+}
+
+static inline void sdsinclen(sds s, size_t inc) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
+ *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+ }
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->len += inc;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->len += inc;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->len += inc;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->len += inc;
+ break;
+ }
+}
+
+/* sdsalloc() = sdsavail() + sdslen() */
+static inline size_t sdsalloc(const sds s) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return SDS_TYPE_5_LEN(flags);
+ case SDS_TYPE_8:
+ return SDS_HDR(8,s)->alloc;
+ case SDS_TYPE_16:
+ return SDS_HDR(16,s)->alloc;
+ case SDS_TYPE_32:
+ return SDS_HDR(32,s)->alloc;
+ case SDS_TYPE_64:
+ return SDS_HDR(64,s)->alloc;
+ }
+ return 0;
+}
+
+static inline void sdssetalloc(sds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ /* Nothing to do, this type has no total allocation info. */
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->alloc = newlen;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->alloc = newlen;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->alloc = newlen;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->alloc = newlen;
+ break;
+ }
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscatsds(sds s, const sds t);
+sds sdscpylen(sds s, const char *t, size_t len);
+sds sdscpy(sds s, const char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdscatfmt(sds s, const char *fmt, ...);
+sds sdstrim(sds s, const char *cset);
+void sdsrange(sds s, ssize_t start, ssize_t end);
+void sdsupdatelen(sds s);
+void sdsclear(sds s);
+int sdscmp(const sds s1, const sds s2);
+sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+sds sdscatrepr(sds s, const char *p, size_t len);
+sds *sdssplitargs(const char *line, int *argc);
+sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
+sds sdsjoin(char **argv, int argc, char *sep);
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
+
+/* Low level functions exposed to the user API */
+sds sdsMakeRoomFor(sds s, size_t addlen);
+void sdsIncrLen(sds s, ssize_t incr);
+sds sdsRemoveFreeSpace(sds s);
+size_t sdsAllocSize(sds s);
+void *sdsAllocPtr(sds s);
+
+/* Export the allocator used by SDS to the program using SDS.
+ * Sometimes the program SDS is linked to, may use a different set of
+ * allocators, but may want to allocate or free things that SDS will
+ * respectively free or allocate. */
+void *sds_malloc(size_t size);
+void *sds_realloc(void *ptr, size_t size);
+void sds_free(void *ptr);
+
+#ifdef REDIS_TEST
+int sdsTest(int argc, char *argv[]);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/deps/sds/sdsalloc.h b/deps/sds/sdsalloc.h
new file mode 100644
index 0000000..f43023c
--- /dev/null
+++ b/deps/sds/sdsalloc.h
@@ -0,0 +1,42 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* SDS allocator selection.
+ *
+ * This file is used in order to change the SDS allocator at compile time.
+ * Just define the following defines to what you want to use. Also add
+ * the include of your alternate allocator if needed (not needed in order
+ * to use the default libc allocator). */
+
+#define s_malloc malloc
+#define s_realloc realloc
+#define s_free free
diff --git a/deps/sds/testhelp.h b/deps/sds/testhelp.h
new file mode 100644
index 0000000..4503340
--- /dev/null
+++ b/deps/sds/testhelp.h
@@ -0,0 +1,57 @@
+/* This is a really minimal testing framework for C.
+ *
+ * Example:
+ *
+ * test_cond("Check if 1 == 1", 1==1)
+ * test_cond("Check if 5 > 10", 5 > 10)
+ * test_report()
+ *
+ * ----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __TESTHELP_H
+#define __TESTHELP_H
+
+int __failed_tests = 0;
+int __test_num = 0;
+#define test_cond(descr,_c) do { \
+ __test_num++; printf("%d - %s: ", __test_num, descr); \
+ if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
+} while(0);
+#define test_report() do { \
+ printf("%d tests, %d passed, %d failed\n", __test_num, \
+ __test_num-__failed_tests, __failed_tests); \
+ if (__failed_tests) { \
+ printf("=== WARNING === We have failed tests here...\n"); \
+ exit(1); \
+ } \
+} while(0);
+
+#endif