From: Michael Schroeder Date: Fri, 23 Nov 2018 13:19:16 +0000 (+0100) Subject: Add a small json parser X-Git-Tag: 0.7.2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fae06e5fc32d16ce65dc0c4b6c4e743eddcf6169;p=thirdparty%2Flibsolv.git Add a small json parser It's always good to have a json parser... --- diff --git a/ext/solv_jsonparser.c b/ext/solv_jsonparser.c new file mode 100644 index 00000000..053ee6f4 --- /dev/null +++ b/ext/solv_jsonparser.c @@ -0,0 +1,294 @@ +/* + * solv_jsonparser.c + * + * Simple JSON stream parser + * + * Copyright (c) 2018, SUSE LLC + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +#include +#include + +#include "util.h" +#include "solv_jsonparser.h" + +struct solv_jsonparser * +jsonparser_init(struct solv_jsonparser *jp, FILE *fp) +{ + memset(jp, 0, sizeof(*jp)); + jp->fp = fp; + jp->state = JP_START; + jp->line = jp->nextline = 1; + jp->nextc = ' '; + queue_init(&jp->stateq); + return jp; +} + +struct solv_jsonparser * +jsonparser_free(struct solv_jsonparser *jp) +{ + queue_free(&jp->stateq); + return 0; +} + +static void +savec(struct solv_jsonparser *jp, char c) +{ + if (jp->nspace == jp->aspace) + { + jp->aspace += 256; + jp->space = solv_realloc(jp->space, jp->aspace); + } + jp->space[jp->nspace++] = c; +} + +static void +saveutf8(struct solv_jsonparser *jp, int c) +{ + int i; + if (c < 0x80) + { + savec(jp, c); + return; + } + i = c < 0x800 ? 1 : c < 0x10000 ? 2 : 3; + savec(jp, (0x1f80 >> i) | (c >> (6 * i))); + while (--i >= 0) + savec(jp, 0x80 | ((c >> (6 * i)) & 0x3f)); +} + +static inline int +nextc(struct solv_jsonparser *jp) +{ + int c = getc(jp->fp); + if (c == '\n') + jp->nextline++; + return c; +} + +static int +skipspace(struct solv_jsonparser *jp) +{ + int c = jp->nextc; + while (c == ' ' || c == '\t' || c == '\r' || c == '\n') + c = nextc(jp); + jp->line = jp->nextline; + return (jp->nextc = c); +} + +static int +parseliteral(struct solv_jsonparser *jp) +{ + size_t nspace = jp->nspace; + int c; + savec(jp, jp->nextc); + for (;;) + { + c = nextc(jp); + if (c < 'a' || c > 'z') + break; + savec(jp, c); + } + jp->nextc = c; + savec(jp, 0); + if (!strcmp(jp->space + nspace, "true")) + return JP_BOOL; + if (!strcmp(jp->space + nspace, "false")) + return JP_BOOL; + if (!strcmp(jp->space + nspace, "null")) + return JP_NULL; + return JP_ERROR; +} + +static int +parsenumber(struct solv_jsonparser *jp) +{ + int c; + savec(jp, jp->nextc); + for (;;) + { + c = nextc(jp); + if ((c < '0' || c > '9') && c != '+' && c != '-' && c != '.' && c != 'e' && c != 'E') + break; + savec(jp, c); + } + jp->nextc = c; + savec(jp, 0); + return JP_NUMBER; +} + +static int +parseutf8(struct solv_jsonparser *jp, int surrogate) +{ + int c, i, r = 0; + /* parse 4-digit hex */ + for (i = 0; i < 4; i++) + { + c = nextc(jp); + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else + return -1; + r = (r << 4) | c; + } + if (!surrogate && r >= 0xd800 && r < 0xdc00) + { + /* utf16 surrogate pair encodes 0x10000 - 0x10ffff */ + int r2; + if (nextc(jp) != '\\' || nextc(jp) != 'u' || (r2 = parseutf8(jp, 1)) < 0xdc00 || r2 >= 0xe000) + return -1; + r = 0x10000 + ((r & 0x3ff) << 10 | (r2 & 0x3ff)); + } + return r; +} + +static int +parsestring(struct solv_jsonparser *jp) +{ + int c; + for (;;) + { + if ((c = nextc(jp)) < 32) + return JP_ERROR; + if (c == '"') + break; + if (c == '\\') + { + switch (c = nextc(jp)) + { + case '"': + case '\\': + case '/': + case '\n': + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + if ((c = parseutf8(jp, 0)) < 0) + return JP_ERROR; + saveutf8(jp, c); + continue; + default: + return JP_ERROR; + } + } + savec(jp, c); + } + jp->nextc = ' '; + savec(jp, 0); + return JP_STRING; +} + +static int +parsevalue(struct solv_jsonparser *jp) +{ + int c = skipspace(jp); + if (c == '"') + return parsestring(jp); + if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') + return parsenumber(jp); + if ((c >= 'a' && c <= 'z')) + return parseliteral(jp); + jp->nextc = ' '; + if (c == '[') + return JP_ARRAY; + if (c == '{') + return JP_OBJECT; + if (c == ']') + return JP_ARRAY_END; + if (c == '}') + return JP_OBJECT_END; + return JP_ERROR; +} + +int +jsonparser_parse(struct solv_jsonparser *jp) +{ + int type; + size_t nspace; + + jp->depth = jp->stateq.count; + jp->key = jp->value = 0; + jp->keylen = jp->valuelen = 0; + nspace = jp->nspace = 0; + + if (jp->state == JP_END) + return JP_END; + if (jp->state == JP_START) + jp->state = JP_END; + type = parsevalue(jp); + if (type <= 0) + return JP_ERROR; + if (type == JP_OBJECT_END || type == JP_ARRAY_END) + { + if (jp->state != type - 1) + return JP_ERROR; + jp->state = queue_pop(&jp->stateq); + } + else if (jp->state == JP_OBJECT) + { + nspace = jp->nspace; + if (type != JP_STRING) + return JP_ERROR; + if (skipspace(jp) != ':') + return JP_ERROR; + jp->nextc = ' '; + type = parsevalue(jp); + if (type == JP_OBJECT_END || type == JP_ARRAY_END) + return JP_ERROR; + jp->key = jp->space; + jp->keylen = nspace - 1; + } + if (type == JP_STRING || type == JP_NUMBER || type == JP_BOOL || type == JP_NULL) + { + jp->value = jp->space + nspace; + jp->valuelen = jp->nspace - nspace - 1; + } + if (type == JP_ARRAY || type == JP_OBJECT) + { + queue_push(&jp->stateq, jp->state); + jp->state = type; + } + else if (jp->state == JP_OBJECT || jp->state == JP_ARRAY) + { + int c = skipspace(jp); + if (c == ',') + jp->nextc = ' '; + else if (c != (jp->state == JP_OBJECT ? '}' : ']')) + return JP_ERROR; + } + return type; +} + +int +jsonparser_skip(struct solv_jsonparser *jp, int type) +{ + if (type == JP_ARRAY || type == JP_OBJECT) + { + int depth = jp->depth + 1, endtype = type + 1; + while (type > 0 && (type != endtype || jp->depth != depth)) + type = jsonparser_parse(jp); + } + return type; +} + diff --git a/ext/solv_jsonparser.h b/ext/solv_jsonparser.h new file mode 100644 index 00000000..d58d9e3a --- /dev/null +++ b/ext/solv_jsonparser.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, SUSE LLC + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +#ifndef SOLV_JSONPARSER_H +#define SOLV_JSONPARSER_H + +#include "queue.h" + +struct solv_jsonparser { + FILE *fp; + int line; + int depth; + + char *key; + size_t keylen; + char *value; + size_t valuelen; + + int state; + Queue stateq; + int nextc; + int nextline; + char *space; + size_t nspace; + size_t aspace; +}; + +#define JP_ERROR -1 +#define JP_END 0 +#define JP_START 1 +#define JP_STRING 2 +#define JP_NUMBER 3 +#define JP_BOOL 4 +#define JP_NULL 5 +#define JP_OBJECT 6 +#define JP_OBJECT_END 7 +#define JP_ARRAY 8 +#define JP_ARRAY_END 9 + +struct solv_jsonparser *jsonparser_init(struct solv_jsonparser *jp, FILE *fp); +struct solv_jsonparser *jsonparser_free(struct solv_jsonparser *jp); +int jsonparser_parse(struct solv_jsonparser *jp); +int jsonparser_skip(struct solv_jsonparser *jp, int type); + +#endif /* SOLV_JSONPARSER_H */