summaryrefslogtreecommitdiff
path: root/lua-redis-parser-0.13/redis-parser.c
diff options
context:
space:
mode:
authorkaiwu <kaiwu2004@gmail.com>2025-03-01 12:42:23 +0800
committerkaiwu <kaiwu2004@gmail.com>2025-03-01 12:42:23 +0800
commit3f33461e4948bf05e60bdff35ec6c57a649c7860 (patch)
tree284c2ba95a41536ae1bff6bea710db0709a64739 /lua-redis-parser-0.13/redis-parser.c
downloadopenresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.tar.gz
openresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.zip
openresty bundle
Diffstat (limited to 'lua-redis-parser-0.13/redis-parser.c')
-rw-r--r--lua-redis-parser-0.13/redis-parser.c718
1 files changed, 718 insertions, 0 deletions
diff --git a/lua-redis-parser-0.13/redis-parser.c b/lua-redis-parser-0.13/redis-parser.c
new file mode 100644
index 0000000..b29098e
--- /dev/null
+++ b/lua-redis-parser-0.13/redis-parser.c
@@ -0,0 +1,718 @@
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+#include "ddebug.h"
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+
+#define LUA_REDIS_PARSER_VERSION "0.13"
+
+
+enum {
+ BAD_REPLY = 0,
+ STATUS_REPLY = 1,
+ ERROR_REPLY = 2,
+ INTEGER_REPLY = 3,
+ BULK_REPLY = 4,
+ MULTI_BULK_REPLY = 5
+};
+
+
+enum {
+ PARSE_OK = 0,
+ PARSE_ERROR = 1
+};
+
+
+static const char *redis_typenames[] = {
+ "bad reply",
+ "status reply",
+ "error reply",
+ "integer reply",
+ "bulk reply",
+ "multi-bulk reply"
+};
+
+
+#define UINT64_LEN (sizeof("18446744073709551615") - 1)
+
+static void *redis_null = NULL;
+
+static int parse_reply_helper(lua_State *L, char **src, size_t len);
+static int redis_parse_reply(lua_State *L);
+static int redis_parse_replies(lua_State *L);
+static int redis_build_query(lua_State *L);
+static const char * parse_single_line_reply(const char *src, const char *last,
+ size_t *dst_len);
+static const char * parse_bulk_reply(const char *src, const char *last,
+ size_t *dst_len);
+static int parse_multi_bulk_reply(lua_State *L, char **src,
+ const char *last);
+static size_t get_num_size(size_t i);
+static char *sprintf_num(char *dst, int64_t ui64);
+static int redis_typename(lua_State *L);
+
+
+#define redis_nelems(arr) \
+ (sizeof(arr) / sizeof(arr[0]))
+
+
+static const struct luaL_Reg redis_parser[] = {
+ {"parse_reply", redis_parse_reply},
+ {"parse_replies", redis_parse_replies},
+ {"build_query", redis_build_query},
+ {"typename", redis_typename},
+ {NULL, NULL}
+};
+
+
+int
+luaopen_redis_parser(lua_State *L)
+{
+ luaL_register(L, "redis.parser", redis_parser);
+
+ lua_pushliteral(L, LUA_REDIS_PARSER_VERSION);
+ lua_setfield(L, -2, "_VERSION");
+
+ lua_pushlightuserdata(L, redis_null);
+ lua_setfield(L, -2, "null");
+
+ lua_pushnumber(L, BAD_REPLY);
+ lua_setfield(L, -2, "BAD_REPLY");
+
+ lua_pushnumber(L, STATUS_REPLY);
+ lua_setfield(L, -2, "STATUS_REPLY");
+
+ lua_pushnumber(L, ERROR_REPLY);
+ lua_setfield(L, -2, "ERROR_REPLY");
+
+ lua_pushnumber(L, INTEGER_REPLY);
+ lua_setfield(L, -2, "INTEGER_REPLY");
+
+ lua_pushnumber(L, BULK_REPLY);
+ lua_setfield(L, -2, "BULK_REPLY");
+
+ lua_pushnumber(L, MULTI_BULK_REPLY);
+ lua_setfield(L, -2, "MULTI_BULK_REPLY");
+
+ return 1;
+}
+
+
+static int
+redis_parse_reply(lua_State *L)
+{
+ char *p;
+ size_t len;
+
+ if (lua_gettop(L) != 1) {
+ return luaL_error(L, "expected one argument but got %d",
+ lua_gettop(L));
+ }
+
+ p = (char *) luaL_checklstring(L, 1, &len);
+
+ return parse_reply_helper(L, &p, len);
+}
+
+
+static int
+redis_parse_replies(lua_State *L)
+{
+ char *p;
+ char *q;
+ int i, n, nret;
+ size_t len;
+
+ if (lua_gettop(L) != 2) {
+ return luaL_error(L, "expected two arguments but got %d",
+ lua_gettop(L));
+ }
+
+ p = (char *) luaL_checklstring(L, 1, &len);
+
+ n = luaL_checknumber(L, 2);
+
+ dd("n = %d", (int) n);
+
+ lua_pop(L, 1);
+
+ lua_createtable(L, n, 0); /* table */
+
+ for (i = 1; i <= n; i++) {
+ dd("parsing reply %d", i);
+
+ lua_createtable(L, n, 2); /* table table */
+ q = p;
+ nret = parse_reply_helper(L, &p, len); /* table table res typ */
+ if (nret != 2) {
+ return luaL_error(L, "internal error: redis_parse_reply "
+ "returns %d", nret);
+ }
+
+ dd("p = %p, q = %p, len = %d", p, q, (int) len);
+
+ len -= p - q;
+
+ dd("len is now %d", (int) len);
+
+ lua_rawseti(L, -3, 2); /* table table res */
+ lua_rawseti(L, -2, 1); /* table table */
+ lua_rawseti(L, -2, i); /* table */
+ }
+
+ return 1;
+}
+
+
+static int
+parse_reply_helper(lua_State *L, char **src, size_t len)
+{
+ char *p;
+ const char *last;
+ const char *dst;
+ size_t dst_len;
+ lua_Number num;
+ int rc;
+
+ p = *src;
+
+ if (len == 0) {
+ lua_pushliteral(L, "empty reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ last = p + len;
+
+ switch (*p) {
+ case '+':
+ p++;
+ dst = parse_single_line_reply(p, last, &dst_len);
+
+ if (dst_len == -2) {
+ lua_pushliteral(L, "bad status reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
+ lua_pushlstring(L, dst, dst_len);
+ lua_pushnumber(L, STATUS_REPLY);
+ break;
+
+ case '-':
+ p++;
+ dst = parse_single_line_reply(p, last, &dst_len);
+
+ if (dst_len == -2) {
+ lua_pushliteral(L, "bad error reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
+ lua_pushlstring(L, dst, dst_len);
+ lua_pushnumber(L, ERROR_REPLY);
+ break;
+
+ case ':':
+ p++;
+ dst = parse_single_line_reply(p, last, &dst_len);
+
+ if (dst_len == -2) {
+ lua_pushliteral(L, "bad integer reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ *src += dst_len + 1 + sizeof("\r\n") - 1;
+
+ lua_pushlstring(L, dst, dst_len);
+ num = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ lua_pushnumber(L, num);
+ lua_pushnumber(L, INTEGER_REPLY);
+ break;
+
+ case '$':
+ p++;
+ dst = parse_bulk_reply(p, last, &dst_len);
+
+ if (dst_len == -2) {
+ lua_pushliteral(L, "bad bulk reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ if (dst_len == -1) {
+ *src = (char *) dst + sizeof("\r\n") - 1;
+
+ /* lua_pushlightuserdata(L, redis_null); */
+ lua_pushnil(L);
+ lua_pushnumber(L, BULK_REPLY);
+ return 2;
+ }
+
+ *src = (char *) dst + dst_len + sizeof("\r\n") - 1;
+
+ lua_pushlstring(L, dst, dst_len);
+ lua_pushnumber(L, BULK_REPLY);
+ break;
+
+ case '*':
+ p++;
+ rc = parse_multi_bulk_reply(L, &p, last);
+
+ if (rc != PARSE_OK) {
+ lua_pushliteral(L, "bad multi bulk reply");
+ lua_pushnumber(L, BAD_REPLY);
+ return 2;
+ }
+
+ /* rc == PARSE_OK */
+
+ *src = (char *) p;
+
+ lua_pushnumber(L, MULTI_BULK_REPLY);
+ break;
+
+ default:
+ lua_pushliteral(L, "invalid reply");
+ lua_pushnumber(L, BAD_REPLY);
+ break;
+ }
+
+ return 2;
+}
+
+
+static const char *
+parse_single_line_reply(const char *src, const char *last, size_t *dst_len)
+{
+ const char *p = src;
+ int seen_cr = 0;
+
+ while (p != last) {
+
+ if (*p == '\r') {
+ seen_cr = 1;
+
+ } else if (seen_cr) {
+ if (*p == '\n') {
+ *dst_len = p - src - 1;
+ return src;
+ }
+
+ seen_cr = 0;
+ }
+
+ p++;
+ }
+
+ /* CRLF not found at all */
+ *dst_len = -2;
+ return NULL;
+}
+
+
+#define CHECK_EOF if (p >= last) goto invalid;
+
+
+static const char *
+parse_bulk_reply(const char *src, const char *last, size_t *dst_len)
+{
+ const char *p = src;
+ ssize_t size = 0;
+ const char *dst;
+
+ CHECK_EOF
+
+ /* read the bulk size */
+
+ if (*p == '-') {
+ p++;
+ CHECK_EOF
+
+ while (*p != '\r') {
+ if (*p < '0' || *p > '9') {
+ goto invalid;
+ }
+
+ p++;
+ CHECK_EOF
+ }
+
+ /* *p == '\r' */
+
+ if (last - p < size + sizeof("\r\n") - 1) {
+ goto invalid;
+ }
+
+ p++;
+
+ if (*p++ != '\n') {
+ goto invalid;
+ }
+
+ *dst_len = -1;
+ return p - (sizeof("\r\n") - 1);
+ }
+
+ while (*p != '\r') {
+ if (*p < '0' || *p > '9') {
+ goto invalid;
+ }
+
+ size *= 10;
+ size += *p - '0';
+
+ p++;
+ CHECK_EOF
+ }
+
+ /* *p == '\r' */
+
+ p++;
+ CHECK_EOF
+
+ if (*p++ != '\n') {
+ goto invalid;
+ }
+
+ /* read the bulk data */
+
+ if (last - p < size + sizeof("\r\n") - 1) {
+ goto invalid;
+ }
+
+ dst = p;
+
+ p += size;
+
+ if (*p++ != '\r') {
+ goto invalid;
+ }
+
+ if (*p++ != '\n') {
+ goto invalid;
+ }
+
+ *dst_len = size;
+ return dst;
+
+invalid:
+ *dst_len = -2;
+ return NULL;
+}
+
+
+static int
+parse_multi_bulk_reply(lua_State *L, char **src, const char *last)
+{
+ const char *p = *src;
+ int count = 0;
+ int i;
+ size_t dst_len;
+ const char *dst;
+
+ dd("enter multi bulk parser");
+
+ CHECK_EOF
+
+ while (*p != '\r') {
+ if (*p == '-') {
+
+ p++;
+ CHECK_EOF
+
+ if (*p < '0' || *p > '9') {
+ goto invalid;
+ }
+
+ while (*p != '\r') {
+ p++;
+ CHECK_EOF
+ }
+
+ count = -1;
+ break;
+ }
+
+ if (*p < '0' || *p > '9') {
+ dd("expecting digit, but found %c", *p);
+ goto invalid;
+ }
+
+ count *= 10;
+ count += *p - '0';
+
+ p++;
+ CHECK_EOF
+ }
+
+ dd("count = %d", count);
+
+ /* *p == '\r' */
+
+ p++;
+ CHECK_EOF
+
+ if (*p++ != '\n') {
+ goto invalid;
+ }
+
+ dd("reading the individual bulks");
+
+ if (count == -1) {
+
+ /* lua_pushlightuserdata(L, redis_null); */
+ lua_pushnil(L);
+
+ *src = (char *) p;
+ return PARSE_OK;
+ }
+
+ lua_createtable(L, count, 0);
+
+ for (i = 1; i <= count; i++) {
+ CHECK_EOF
+
+ switch (*p) {
+ case '+':
+ case '-':
+ case ':':
+ p++;
+ dst = parse_single_line_reply(p, last, &dst_len);
+ break;
+
+ case '$':
+ p++;
+ dst = parse_bulk_reply(p, last, &dst_len);
+ break;
+
+ default:
+ goto invalid;
+ }
+
+ if (dst_len == -2) {
+ dd("bulk %d reply parse fail for multi bulks", i);
+ return PARSE_ERROR;
+ }
+
+ if (dst_len == -1) {
+ lua_pushnil(L);
+ p = dst + sizeof("\r\n") - 1;
+
+ } else {
+ lua_pushlstring(L, dst, dst_len);
+ p = dst + dst_len + sizeof("\r\n") - 1;
+ }
+
+ lua_rawseti(L, -2, i);
+ }
+
+ *src = (char *) p;
+
+ return PARSE_OK;
+
+invalid:
+ return PARSE_ERROR;
+}
+
+
+static int
+redis_build_query(lua_State *L)
+{
+ int i, n;
+ size_t len, total;
+ const char *p;
+ char *last;
+ char *buf;
+ int flag;
+
+ if (lua_gettop(L) != 1) {
+ return luaL_error(L, "expected one argument but got %d",
+ lua_gettop(L));
+ }
+
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ n = lua_objlen(L, 1);
+
+ if (n == 0) {
+ return luaL_error(L, "empty input param table");
+ }
+
+ total = sizeof("*") - 1
+ + get_num_size(n)
+ + sizeof("\r\n") - 1
+ ;
+
+ for (i = 1; i <= n; i++) {
+ lua_rawgeti(L, 1, i);
+
+ dd("param type: %d (%d)", lua_type(L, -1), LUA_TUSERDATA);
+
+ switch (lua_type(L, -1)) {
+ case LUA_TSTRING:
+ case LUA_TNUMBER:
+ lua_tolstring(L, -1, &len);
+
+ total += sizeof("$") - 1
+ + get_num_size(len)
+ + sizeof("\r\n") - 1
+ + len
+ + sizeof("\r\n") - 1
+ ;
+
+ break;
+
+ case LUA_TBOOLEAN:
+ total += sizeof("$1\r\n1\r\n") - 1;
+ break;
+
+ case LUA_TLIGHTUSERDATA:
+ p = lua_touserdata(L, -1);
+ dd("user data: %p", p);
+ if (p == redis_null) {
+ total += sizeof("$-1\r\n") - 1;
+ break;
+ }
+
+ default:
+ return luaL_error(L, "parameter %d is not a string, number, "
+ "redis.parser.null, or boolean value", i);
+ }
+
+ lua_pop(L, 1);
+ }
+
+ buf = lua_newuserdata(L, total); /* lua_newuserdata never returns NULL */
+
+ last = buf;
+
+ *last++ = '*';
+ last = sprintf_num(last, n);
+ *last++ = '\r'; *last++ = '\n';
+
+ for (i = 1; i <= n; i++) {
+ lua_rawgeti(L, 1, i);
+
+ switch (lua_type(L, -1)) {
+ case LUA_TSTRING:
+ case LUA_TNUMBER:
+ p = luaL_checklstring(L, -1, &len);
+
+ *last++ = '$';
+
+ last = sprintf_num(last, len);
+
+ *last++ = '\r'; *last++ = '\n';
+
+ memcpy(last, p, len);
+ last += len;
+
+ *last++ = '\r'; *last++ = '\n';
+
+ break;
+
+ case LUA_TBOOLEAN:
+ memcpy(last, "$1\r\n", sizeof("$1\r\n") - 1);
+ last += sizeof("$1\r\n") - 1;
+
+ flag = lua_toboolean(L, -1);
+ *last++ = flag ? '1' : '0';
+
+ *last++ = '\r'; *last++ = '\n';
+
+ break;
+
+ case LUA_TLIGHTUSERDATA:
+ /* must be null */
+ memcpy(last, "$-1\r\n", sizeof("$-1\r\n") - 1);
+ last += sizeof("$-1\r\n") - 1;
+ break;
+
+ default:
+ /* cannot reach here */
+ break;
+ }
+
+ lua_pop(L, 1);
+ }
+
+ if (last - buf != (ssize_t) total) {
+ return luaL_error(L, "buffer error");
+ }
+
+ lua_pushlstring(L, buf, total);
+
+ return 1;
+}
+
+
+static size_t
+get_num_size(size_t i)
+{
+ size_t n = 0;
+
+ do {
+ i = i / 10;
+ n++;
+ } while (i > 0);
+
+ return n;
+}
+
+
+static char *
+sprintf_num(char *dst, int64_t ui64)
+{
+ char *p;
+ char temp[UINT64_LEN + 1];
+ size_t len;
+
+ p = temp + UINT64_LEN;
+
+ do {
+ *--p = (char) (ui64 % 10 + '0');
+ } while (ui64 /= 10);
+
+ len = (temp + UINT64_LEN) - p;
+
+ memcpy(dst, p, len);
+
+ return dst + len;
+}
+
+
+static int
+redis_typename(lua_State *L)
+{
+ int typ;
+
+ if (lua_gettop(L) != 1) {
+ return luaL_error(L, "expecting one argument but got %d",
+ lua_gettop(L));
+ }
+
+ typ = (int) luaL_checkinteger(L, 1);
+
+ if (typ < 0 || typ >= redis_nelems(redis_typenames)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushstring(L, redis_typenames[typ]);
+ return 1;
+}
+