| improvements - json2tsv - JSON to TSV converter | |
| git clone git://git.codemadness.org/json2tsv | |
| Log | |
| Files | |
| Refs | |
| README | |
| LICENSE | |
| --- | |
| commit 2f12ae56971af8acaa52357fe1042d37f71ffbd4 | |
| parent 9a85d6c5f69749ac71c690e5b4d36b8e70b15d16 | |
| Author: Hiltjo Posthuma <[email protected]> | |
| Date: Sun, 20 Oct 2019 14:30:24 +0200 | |
| improvements | |
| - separate into more precise JSON types: primitives to: bool, null, number. | |
| - separate JSON code into a reusable "library": json.c. | |
| - remove errstr from parsejson(): just return an error code for out-of-memory | |
| and a JSON parse error. The tool returns exit code 2 when out-of-memory. | |
| - much more strict JSON parsing for incorrect input. | |
| - make primitives a fixed buffer. The longest size data for primitives are | |
| numbers and cannot be long anyway. | |
| - improve README example (reddit). | |
| Diffstat: | |
| M Makefile | 26 +++++++++++++++++++++----- | |
| M README | 35 ++++++++++++++++-------------… | |
| A json.c | 314 +++++++++++++++++++++++++++++… | |
| A json.h | 26 ++++++++++++++++++++++++++ | |
| M json2tsv.c | 301 +----------------------------… | |
| 5 files changed, 386 insertions(+), 316 deletions(-) | |
| --- | |
| diff --git a/Makefile b/Makefile | |
| @@ -8,35 +8,51 @@ PREFIX = /usr/local | |
| MANPREFIX = ${PREFIX}/man | |
| DOCPREFIX = ${PREFIX}/share/doc/${NAME} | |
| +RANLIB = ranlib | |
| + | |
| BIN = ${NAME} | |
| SRC = ${BIN:=.c} | |
| +HDR = json.h | |
| MAN1 = ${BIN:=.1} | |
| DOC = \ | |
| LICENSE\ | |
| README | |
| +LIBJSON = libjson.a | |
| +LIBJSONSRC = json.c | |
| +LIBJSONOBJ = ${LIBJSONSRC:.c=.o} | |
| + | |
| +LIB = ${LIBJSON} | |
| + | |
| all: ${BIN} | |
| -${BIN}: ${@:=.o} | |
| +${BIN}: ${LIB} ${@:=.o} | |
| -OBJ = ${SRC:.c=.o} | |
| +OBJ = ${SRC:.c=.o} ${LIBJSONOBJ} | |
| + | |
| +${OBJ}: ${HDR} | |
| .o: | |
| - ${CC} ${LDFLAGS} -o $@ $< | |
| + ${CC} ${LDFLAGS} -o $@ $< ${LIB} | |
| .c.o: | |
| ${CC} ${CFLAGS} ${CPPFLAGS} -o $@ -c $< | |
| +${LIBJSON}: ${LIBJSONOBJ} | |
| + ${AR} rc $@ $? | |
| + ${RANLIB} $@ | |
| + | |
| dist: | |
| rm -rf "${NAME}-${VERSION}" | |
| mkdir -p "${NAME}-${VERSION}" | |
| - cp -f ${MAN1} ${DOC} ${SRC} Makefile "${NAME}-${VERSION}" | |
| + cp -f ${MAN1} ${DOC} ${HDR} \ | |
| + ${SRC} ${LIBJSONSRC} Makefile "${NAME}-${VERSION}" | |
| # make tarball | |
| tar -cf - "${NAME}-${VERSION}" | gzip -c > "${NAME}-${VERSION}.tar.gz" | |
| rm -rf "${NAME}-${VERSION}" | |
| clean: | |
| - rm -f ${BIN} ${OBJ} | |
| + rm -f ${BIN} ${OBJ} ${LIB} | |
| install: all | |
| # installing executable files. | |
| diff --git a/README b/README | |
| @@ -16,10 +16,16 @@ The output format per line is: | |
| The nodename and value are escaped (\n, \t and \\). Control-characters are | |
| removed. | |
| -The type can be: o (for object), a (for array), p (for primitive such as true, | |
| -false, null, a number) or s (for string). | |
| +The type field is a single byte and can be: | |
| -Then filtering is easy using some awk script on the node "selector". | |
| + a for array | |
| + b for bool | |
| + n for number | |
| + o for object | |
| + s for string | |
| + ? for null | |
| + | |
| +Filtering on the first field "nodename" is easy using awk for example. | |
| See the json2tsv(1) man page for the full documentation. | |
| @@ -32,29 +38,24 @@ plain-text list using awk: | |
| #!/bin/sh | |
| -curl -s -H 'User-Agent:' 'https://old.reddit.com/.json' | \ | |
| +curl -s -H 'User-Agent:' 'https://old.reddit.com/.json?raw_json=1&limit=100' |… | |
| json2tsv | \ | |
| -awk 'BEGIN { | |
| - FS = OFS = "\t"; | |
| - n = 0; | |
| - title = author = subreddit = ""; | |
| -} | |
| +awk -F '\t' ' | |
| function show() { | |
| - if (length(title) == 0) | |
| + if (length(o["title"]) == 0) | |
| return; | |
| - print n ". " title " by " author " in r/" subreddit; | |
| - print url; | |
| + print n ". " o["title"] " by " o["author"] " in r/" o["subreddit"]; | |
| + print o["url"]; | |
| print ""; | |
| } | |
| $1 == ".data.children[].data" { | |
| show(); | |
| n++; | |
| - title = url = author = subreddit = ""; | |
| + delete o; | |
| +} | |
| +$1 ~ /^\.data\.children\[\]\.data\.[a-zA-Z0-9_]*$/ { | |
| + o[substr($1, 23)] = $3; | |
| } | |
| -$1 == ".data.children[].data.url" { url = $3; } | |
| -$1 == ".data.children[].data.title" { title = $3; } | |
| -$1 == ".data.children[].data.author" { author = $3; } | |
| -$1 == ".data.children[].data.subreddit" { subreddit = $3; } | |
| END { | |
| show(); | |
| }' | |
| diff --git a/json.c b/json.c | |
| @@ -0,0 +1,314 @@ | |
| +#include <ctype.h> | |
| +#include <errno.h> | |
| +#include <stdint.h> | |
| +#include <stdio.h> | |
| +#include <stdlib.h> | |
| +#include <string.h> | |
| + | |
| +#define GETNEXT getchar | |
| + | |
| +#include "json.h" | |
| + | |
| +int | |
| +codepointtoutf8(long r, char *s) | |
| +{ | |
| + if (r == 0) { | |
| + return 0; /* NUL byte */ | |
| + } else if (r <= 0x7F) { | |
| + /* 1 byte: 0aaaaaaa */ | |
| + s[0] = r; | |
| + return 1; | |
| + } else if (r <= 0x07FF) { | |
| + /* 2 bytes: 00000aaa aabbbbbb */ | |
| + s[0] = 0xC0 | ((r & 0x0007C0) >> 6); /* 110aaaaa */ | |
| + s[1] = 0x80 | (r & 0x00003F); /* 10bbbbbb */ | |
| + return 2; | |
| + } else if (r <= 0xFFFF) { | |
| + /* 3 bytes: aaaabbbb bbcccccc */ | |
| + s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */ | |
| + s[1] = 0x80 | ((r & 0x000FC0) >> 6); /* 10bbbbbb */ | |
| + s[2] = 0x80 | (r & 0x00003F); /* 10cccccc */ | |
| + return 3; | |
| + } else { | |
| + /* 4 bytes: 000aaabb bbbbcccc ccdddddd */ | |
| + s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */ | |
| + s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */ | |
| + s[2] = 0x80 | ((r & 0x000FC0) >> 6); /* 10cccccc */ | |
| + s[3] = 0x80 | (r & 0x00003F); /* 10dddddd */ | |
| + return 4; | |
| + } | |
| +} | |
| + | |
| +int | |
| +hexdigit(int c) | |
| +{ | |
| + if (c >= '0' && c <= '9') | |
| + return c - '0'; | |
| + else if (c >= 'a' && c <= 'f') | |
| + return 10 + (c - 'a'); | |
| + else if (c >= 'A' && c <= 'F') | |
| + return 10 + (c - 'A'); | |
| + return 0; | |
| +} | |
| + | |
| +int | |
| +capacity(char **value, size_t *sz, size_t cur, size_t inc) | |
| +{ | |
| + size_t need, newsiz; | |
| + char *newp; | |
| + | |
| + /* check for addition overflow */ | |
| + if (cur > SIZE_MAX - inc) { | |
| + errno = EOVERFLOW; | |
| + return -1; | |
| + } | |
| + need = cur + inc; | |
| + | |
| + if (need > *sz) { | |
| + if (need > SIZE_MAX / 2) { | |
| + newsiz = SIZE_MAX; | |
| + } else { | |
| + for (newsiz = *sz < 64 ? 64 : *sz; newsiz <= need; new… | |
| + ; | |
| + } | |
| + if (!(newp = realloc(*value, newsiz))) | |
| + return -1; /* up to caller to free *value */ | |
| + *value = newp; | |
| + *sz = newsiz; | |
| + } | |
| + return 0; | |
| +} | |
| + | |
| +#define EXPECT_VALUE "{[\"-0123456789tfn" | |
| +#define EXPECT_STRING "\"" | |
| +#define EXPECT_END "}]," | |
| +#define EXPECT_NOTHING "" | |
| +#define EXPECT_OBJECT_STRING EXPECT_STRING "}" | |
| +#define EXPECT_ARRAY_VALUE EXPECT_VALUE "]" | |
| + | |
| +#define JSON_INVALID() do { ret = JSON_ERROR_INVALID; goto end; } while … | |
| + | |
| +int | |
| +parsejson(void (*cb)(struct json_node *, size_t, const char *)) | |
| +{ | |
| + struct json_node nodes[JSON_MAX_NODE_DEPTH] = { 0 }; | |
| + size_t depth = 0, p = 0, len, sz = 0; | |
| + long cp, hi, lo; | |
| + char pri[128], *str = NULL; | |
| + int c, i, escape, iskey = 0, ret = JSON_ERROR_MEM; | |
| + const char *expect = EXPECT_VALUE; | |
| + | |
| + if (capacity(&(nodes[0].name), &(nodes[0].namesiz), 0, 1) == -1) | |
| + goto end; | |
| + nodes[0].name[0] = '\0'; | |
| + | |
| + while (1) { | |
| + c = GETNEXT(); | |
| +handlechr: | |
| + if (c == EOF) | |
| + break; | |
| + | |
| + if (c && strchr(" \t\n\r", c)) /* (no \v, \f, \b etc) */ | |
| + continue; | |
| + | |
| + if (!c || !strchr(expect, c)) | |
| + JSON_INVALID(); | |
| + | |
| + switch (c) { | |
| + case ':': | |
| + /* not in an object or key in object is not a string */ | |
| + if (!depth || nodes[depth - 1].type != TYPE_OBJECT || | |
| + nodes[depth].type != TYPE_STRING) | |
| + JSON_INVALID(); | |
| + iskey = 0; | |
| + expect = EXPECT_VALUE; | |
| + break; | |
| + case '"': | |
| + nodes[depth].type = TYPE_STRING; | |
| + escape = 0; | |
| + len = 0; | |
| + while (1) { | |
| + c = GETNEXT(); | |
| +chr: | |
| + /* EOF or control char: 0x7f is not defined as… | |
| + if (c < 0x20) | |
| + JSON_INVALID(); | |
| + | |
| + if (escape) { | |
| +escchr: | |
| + escape = 0; | |
| + switch (c) { | |
| + case '"': /* FALLTHROUGH */ | |
| + case '\\': | |
| + case '/': 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': /* hex hex hex hex */ | |
| + if (capacity(&str, &sz, len, 4… | |
| + goto end; | |
| + for (i = 12, cp = 0; i >= 0; i… | |
| + if ((c = GETNEXT()) ==… | |
| + JSON_INVALID()… | |
| + cp |= (hexdigit(c) << … | |
| + } | |
| + /* RFC8259 - 7. Strings - surr… | |
| + * 0xd800 - 0xdb7f - high surr… | |
| + if (cp >= 0xd800 && cp <= 0xdb… | |
| + if ((c = GETNEXT()) !=… | |
| + len += codepoi… | |
| + goto chr; | |
| + } | |
| + if ((c = GETNEXT()) !=… | |
| + len += codepoi… | |
| + goto escchr; | |
| + } | |
| + for (hi = cp, i = 12, … | |
| + if ((c = GETNE… | |
| + JSON_I… | |
| + lo |= (hexdigi… | |
| + } | |
| + /* 0xdc00 - 0xdfff - l… | |
| + if (lo >= 0xdc00 && lo… | |
| + cp = (hi << 10… | |
| + } else { | |
| + /* handle grac… | |
| + len += codepoi… | |
| + if (capacity(&… | |
| + goto e… | |
| + len += codepoi… | |
| + continue; | |
| + } | |
| + } | |
| + len += codepointtoutf8(cp, &st… | |
| + continue; | |
| + default: | |
| + JSON_INVALID(); /* invalid esc… | |
| + } | |
| + if (capacity(&str, &sz, len, 1) == -1) | |
| + goto end; | |
| + str[len++] = c; | |
| + } else if (c == '\\') { | |
| + escape = 1; | |
| + } else if (c == '"') { | |
| + if (capacity(&str, &sz, len, 1) == -1) | |
| + goto end; | |
| + str[len++] = '\0'; | |
| + | |
| + if (iskey) { | |
| + if (capacity(&(nodes[depth].na… | |
| + goto end; | |
| + memcpy(nodes[depth].name, str,… | |
| + } else { | |
| + cb(nodes, depth + 1, str); | |
| + } | |
| + break; | |
| + } else { | |
| + if (capacity(&str, &sz, len, 1) == -1) | |
| + goto end; | |
| + str[len++] = c; | |
| + } | |
| + } | |
| + if (iskey) | |
| + expect = ":"; | |
| + else | |
| + expect = EXPECT_END; | |
| + break; | |
| + case '[': | |
| + case '{': | |
| + if (depth + 1 >= JSON_MAX_NODE_DEPTH) | |
| + JSON_INVALID(); /* too deep */ | |
| + | |
| + nodes[depth].index = 0; | |
| + nodes[depth].type = TYPE_OBJECT; | |
| + if (c == '{') { | |
| + iskey = 1; | |
| + nodes[depth].type = TYPE_OBJECT; | |
| + expect = EXPECT_OBJECT_STRING; | |
| + } else if (c == '[') { | |
| + nodes[depth].type = TYPE_ARRAY; | |
| + expect = EXPECT_ARRAY_VALUE; | |
| + } | |
| + | |
| + cb(nodes, depth + 1, ""); | |
| + | |
| + depth++; | |
| + nodes[depth].index = 0; | |
| + if (capacity(&(nodes[depth].name), &(nodes[depth].name… | |
| + goto end; | |
| + nodes[depth].name[0] = '\0'; | |
| + break; | |
| + case ']': | |
| + case '}': | |
| + if (!depth || | |
| + (c == ']' && nodes[depth - 1].type != TYPE_ARRAY) || | |
| + (c == '}' && nodes[depth - 1].type != TYPE_OBJECT)) | |
| + JSON_INVALID(); /* unbalanced nodes */ | |
| + | |
| + nodes[--depth].index++; | |
| + if (!depth) | |
| + expect = EXPECT_NOTHING; | |
| + else | |
| + expect = EXPECT_END; | |
| + break; | |
| + case ',': | |
| + nodes[depth - 1].index++; | |
| + if (nodes[depth - 1].type == TYPE_OBJECT) { | |
| + iskey = 1; | |
| + expect = EXPECT_STRING; | |
| + } else { | |
| + expect = EXPECT_VALUE; | |
| + } | |
| + break; | |
| + case 't': /* true */ | |
| + if (GETNEXT() != 'r' || GETNEXT() != 'u' || GETNEXT() … | |
| + JSON_INVALID(); | |
| + nodes[depth].type = TYPE_BOOL; | |
| + cb(nodes, depth + 1, "true"); | |
| + expect = EXPECT_END; | |
| + break; | |
| + case 'f': /* false */ | |
| + if (GETNEXT() != 'a' || GETNEXT() != 'l' || GETNEXT() … | |
| + JSON_INVALID(); | |
| + nodes[depth].type = TYPE_BOOL; | |
| + cb(nodes, depth + 1, "false"); | |
| + expect = EXPECT_END; | |
| + break; | |
| + case 'n': /* null */ | |
| + if (GETNEXT() != 'u' || GETNEXT() != 'l' || GETNEXT() … | |
| + JSON_INVALID(); | |
| + nodes[depth].type = TYPE_NULL; | |
| + cb(nodes, depth + 1, "null"); | |
| + expect = EXPECT_END; | |
| + break; | |
| + default: /* number */ | |
| + nodes[depth].type = TYPE_NUMBER; | |
| + p = 0; | |
| + pri[p++] = c; | |
| + expect = EXPECT_END; | |
| + while (1) { | |
| + c = GETNEXT(); | |
| + if (!c || !strchr("0123456789eE+-.", c) || | |
| + c == EOF || p + 1 >= sizeof(pri)) { | |
| + pri[p] = '\0'; | |
| + cb(nodes, depth + 1, pri); | |
| + goto handlechr; /* do not read next ch… | |
| + } else { | |
| + pri[p++] = c; | |
| + } | |
| + } | |
| + } | |
| + } | |
| + if (depth) | |
| + JSON_INVALID(); /* unbalanced nodes */ | |
| + | |
| + ret = 0; /* success */ | |
| +end: | |
| + for (depth = 0; depth < sizeof(nodes) / sizeof(nodes[0]); depth++) | |
| + free(nodes[depth].name); | |
| + free(str); | |
| + | |
| + return ret; | |
| +} | |
| diff --git a/json.h b/json.h | |
| @@ -0,0 +1,26 @@ | |
| +#include <stdint.h> | |
| + | |
| +enum JSONType { | |
| + TYPE_ARRAY = 'a', | |
| + TYPE_OBJECT = 'o', | |
| + TYPE_STRING = 's', | |
| + TYPE_BOOL = 'b', | |
| + TYPE_NULL = '?', | |
| + TYPE_NUMBER = 'n' | |
| +}; | |
| + | |
| +enum JSONError { | |
| + JSON_ERROR_MEM = -2, | |
| + JSON_ERROR_INVALID = -1 | |
| +}; | |
| + | |
| +#define JSON_MAX_NODE_DEPTH 64 | |
| + | |
| +struct json_node { | |
| + enum JSONType type; | |
| + char *name; | |
| + size_t namesiz; | |
| + size_t index; /* count/index for array or object type */ | |
| +}; | |
| + | |
| +int parsejson(void (*cb)(struct json_node *, size_t, const char *)); | |
| diff --git a/json2tsv.c b/json2tsv.c | |
| @@ -11,299 +11,10 @@ | |
| #define pledge(a,b) 0 | |
| #endif | |
| -#define GETNEXT getchar | |
| - | |
| -enum JSONType { | |
| - TYPE_PRIMITIVE = 'p', | |
| - TYPE_STRING = 's', | |
| - TYPE_ARRAY = 'a', | |
| - TYPE_OBJECT = 'o' | |
| -}; | |
| - | |
| -#define JSON_MAX_NODE_DEPTH 64 | |
| - | |
| -struct json_node { | |
| - enum JSONType type; | |
| - char *name; | |
| - size_t namesiz; | |
| - size_t index; /* count/index for array or object type */ | |
| -}; | |
| - | |
| -const char *JSON_ERROR_ALLOC = "cannot allocate enough memory"; | |
| -const char *JSON_ERROR_BALANCE = "unbalanced nodes"; | |
| -const char *JSON_ERROR_CODEPOINT = "invalid codepoint"; | |
| -const char *JSON_ERROR_DEPTH = "max node depth reached"; | |
| -const char *JSON_ERROR_ESCAPE_CHAR = "unknown escape character in string"; | |
| -const char *JSON_ERROR_INVALID_CHAR = "invalid character in string"; | |
| -const char *JSON_ERROR_OBJECT_MEMBER = "object member, but not in an object"; | |
| +#include "json.h" | |
| static int showindices = 0; /* -n flag: show indices count for arrays */ | |
| -int | |
| -codepointtoutf8(long r, char *s) | |
| -{ | |
| - if (r == 0) { | |
| - return 0; /* NUL byte */ | |
| - } else if (r <= 0x7F) { | |
| - /* 1 byte: 0aaaaaaa */ | |
| - s[0] = r; | |
| - return 1; | |
| - } else if (r <= 0x07FF) { | |
| - /* 2 bytes: 00000aaa aabbbbbb */ | |
| - s[0] = 0xC0 | ((r & 0x0007C0) >> 6); /* 110aaaaa */ | |
| - s[1] = 0x80 | (r & 0x00003F); /* 10bbbbbb */ | |
| - return 2; | |
| - } else if (r <= 0xFFFF) { | |
| - /* 3 bytes: aaaabbbb bbcccccc */ | |
| - s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */ | |
| - s[1] = 0x80 | ((r & 0x000FC0) >> 6); /* 10bbbbbb */ | |
| - s[2] = 0x80 | (r & 0x00003F); /* 10cccccc */ | |
| - return 3; | |
| - } else { | |
| - /* 4 bytes: 000aaabb bbbbcccc ccdddddd */ | |
| - s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */ | |
| - s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */ | |
| - s[2] = 0x80 | ((r & 0x000FC0) >> 6); /* 10cccccc */ | |
| - s[3] = 0x80 | (r & 0x00003F); /* 10dddddd */ | |
| - return 4; | |
| - } | |
| -} | |
| - | |
| -int | |
| -hexdigit(int c) | |
| -{ | |
| - if (c >= '0' && c <= '9') | |
| - return c - '0'; | |
| - else if (c >= 'a' && c <= 'f') | |
| - return 10 + (c - 'a'); | |
| - else if (c >= 'A' && c <= 'F') | |
| - return 10 + (c - 'A'); | |
| - return 0; | |
| -} | |
| - | |
| -int | |
| -capacity(char **value, size_t *sz, size_t cur, size_t inc) | |
| -{ | |
| - size_t need, newsiz; | |
| - char *newp; | |
| - | |
| - /* check for addition overflow */ | |
| - if (cur > SIZE_MAX - inc) { | |
| - errno = EOVERFLOW; | |
| - return -1; | |
| - } | |
| - need = cur + inc; | |
| - | |
| - if (need > *sz) { | |
| - if (need > SIZE_MAX / 2) { | |
| - newsiz = SIZE_MAX; | |
| - } else { | |
| - for (newsiz = *sz < 64 ? 64 : *sz; newsiz <= need; new… | |
| - ; | |
| - } | |
| - if (!(newp = realloc(*value, newsiz))) | |
| - return -1; /* up to caller to free *value */ | |
| - *value = newp; | |
| - *sz = newsiz; | |
| - } | |
| - return 0; | |
| -} | |
| - | |
| -int | |
| -parsejson(void (*cb)(struct json_node *, size_t, const char *), const char **e… | |
| -{ | |
| - struct json_node nodes[JSON_MAX_NODE_DEPTH] = { 0 }; | |
| - size_t depth = 0, v = 0, vz = 0; | |
| - long cp, hi, lo; | |
| - int c, i, escape, ret = -1; | |
| - char *value = NULL; | |
| - | |
| - *errstr = JSON_ERROR_ALLOC; | |
| - if (capacity(&(nodes[0].name), &(nodes[0].namesiz), 0, 1) == -1) | |
| - goto end; | |
| - nodes[0].name[0] = '\0'; | |
| - nodes[depth].type = TYPE_PRIMITIVE; | |
| - | |
| - while ((c = GETNEXT()) != EOF) { | |
| - /* not whitespace or control character */ | |
| - if (c <= 0x20 || c == 0x7f) | |
| - continue; | |
| - | |
| - switch (c) { | |
| - case ':': | |
| - if (!depth || nodes[depth - 1].type != TYPE_OBJECT) { | |
| - *errstr = JSON_ERROR_OBJECT_MEMBER; | |
| - goto end; | |
| - } | |
| - | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v] = '\0'; | |
| - if (capacity(&(nodes[depth].name), &(nodes[depth].name… | |
| - goto end; | |
| - memcpy(nodes[depth].name, value, v); | |
| - nodes[depth].name[v] = '\0'; | |
| - v = 0; | |
| - nodes[depth].type = TYPE_PRIMITIVE; | |
| - break; | |
| - case '"': | |
| - nodes[depth].type = TYPE_STRING; | |
| - escape = 0; | |
| - for (;;) { | |
| - c = GETNEXT(); | |
| -chr: | |
| - if (c < 0x20) { | |
| - /* EOF or control char: 0x7f is not de… | |
| - *errstr = JSON_ERROR_INVALID_CHAR; | |
| - goto end; | |
| - } | |
| - | |
| - if (escape) { | |
| -escchr: | |
| - escape = 0; | |
| - switch (c) { | |
| - case '"': /* FALLTHROUGH */ | |
| - case '\\': | |
| - case '/': 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': /* hex hex hex hex */ | |
| - if (capacity(&value, &vz, v, 4… | |
| - goto end; | |
| - for (i = 12, cp = 0; i >= 0; i… | |
| - if ((c = GETNEXT()) ==… | |
| - *errstr = JSON… | |
| - goto end; | |
| - } | |
| - cp |= (hexdigit(c) << … | |
| - } | |
| - /* RFC8259 - 7. Strings - surr… | |
| - * 0xd800 - 0xdb7f - high surr… | |
| - if (cp >= 0xd800 && cp <= 0xdb… | |
| - if ((c = GETNEXT()) !=… | |
| - v += codepoint… | |
| - goto chr; | |
| - } | |
| - if ((c = GETNEXT()) !=… | |
| - v += codepoint… | |
| - goto escchr; | |
| - } | |
| - for (hi = cp, i = 12, … | |
| - if ((c = GETNE… | |
| - *errst… | |
| - goto e… | |
| - } | |
| - lo |= (hexdigi… | |
| - } | |
| - /* 0xdc00 - 0xdfff - l… | |
| - if (lo >= 0xdc00 && lo… | |
| - cp = (hi << 10… | |
| - } else { | |
| - /* handle grac… | |
| - v += codepoint… | |
| - if (capacity(&… | |
| - goto e… | |
| - v += codepoint… | |
| - continue; | |
| - } | |
| - } | |
| - v += codepointtoutf8(cp, &valu… | |
| - continue; | |
| - default: | |
| - *errstr = JSON_ERROR_ESCAPE_CH… | |
| - goto end; | |
| - } | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v++] = c; | |
| - } else if (c == '\\') { | |
| - escape = 1; | |
| - } else if (c == '"') { | |
| - break; | |
| - } else { | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v++] = c; | |
| - } | |
| - } | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v] = '\0'; | |
| - break; | |
| - case '[': | |
| - case '{': | |
| - if (depth + 1 >= JSON_MAX_NODE_DEPTH) { | |
| - *errstr = JSON_ERROR_DEPTH; | |
| - goto end; | |
| - } | |
| - | |
| - nodes[depth].index = 0; | |
| - nodes[depth].type = c == '{' ? TYPE_OBJECT : TYPE_ARRA… | |
| - | |
| - cb(nodes, depth + 1, ""); | |
| - v = 0; | |
| - | |
| - depth++; | |
| - nodes[depth].index = 0; | |
| - nodes[depth].type = TYPE_PRIMITIVE; | |
| - if (capacity(&(nodes[depth].name), &(nodes[depth].name… | |
| - goto end; | |
| - nodes[depth].name[0] = '\0'; | |
| - break; | |
| - case ']': | |
| - case '}': | |
| - case ',': | |
| - if (v || nodes[depth].type == TYPE_STRING) { | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v] = '\0'; | |
| - cb(nodes, depth + 1, value); | |
| - v = 0; | |
| - } | |
| - if (!depth || | |
| - (c == ']' && nodes[depth - 1].type != TYPE_ARRAY) … | |
| - (c == '}' && nodes[depth - 1].type != TYPE_OBJECT)… | |
| - *errstr = JSON_ERROR_BALANCE; | |
| - goto end; | |
| - } | |
| - | |
| - if (c == ']' || c == '}') { | |
| - nodes[--depth].index++; | |
| - } else if (c == ',') { | |
| - nodes[depth - 1].index++; | |
| - nodes[depth].type = TYPE_PRIMITIVE; | |
| - } | |
| - break; | |
| - default: | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v++] = c; | |
| - } | |
| - } | |
| - if (depth) { | |
| - *errstr = JSON_ERROR_BALANCE; | |
| - goto end; | |
| - } | |
| - if (v || nodes[depth].type == TYPE_STRING) { | |
| - if (capacity(&value, &vz, v, 1) == -1) | |
| - goto end; | |
| - value[v] = '\0'; | |
| - cb(nodes, depth + 1, value); | |
| - } | |
| - | |
| - ret = 0; /* success */ | |
| - *errstr = NULL; | |
| -end: | |
| - for (depth = 0; depth < sizeof(nodes) / sizeof(nodes[0]); depth++) | |
| - free(nodes[depth].name); | |
| - free(value); | |
| - | |
| - return ret; | |
| -} | |
| - | |
| void | |
| printvalue(const char *s) | |
| { | |
| @@ -356,8 +67,6 @@ processnode(struct json_node *nodes, size_t depth, const cha… | |
| int | |
| main(int argc, char *argv[]) | |
| { | |
| - const char *errstr; | |
| - | |
| if (pledge("stdio", NULL) == -1) { | |
| fprintf(stderr, "pledge stdio: %s\n", strerror(errno)); | |
| return 1; | |
| @@ -366,8 +75,12 @@ main(int argc, char *argv[]) | |
| if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n') | |
| showindices = 1; | |
| - if (parsejson(processnode, &errstr) == -1) { | |
| - fprintf(stderr, "error: %s\n", errstr); | |
| + switch (parsejson(processnode)) { | |
| + case JSON_ERROR_MEM: | |
| + fputs("error: cannot allocate enough memory\n", stderr); | |
| + return 2; | |
| + case JSON_ERROR_INVALID: | |
| + fputs("error: invalid JSON\n", stderr); | |
| return 1; | |
| } | |