From: Eric S. Raymond Date: Tue, 23 Sep 2014 16:39:23 +0000 (-0400) Subject: Shorten the name of the linkable module and headeer file. X-Git-Tag: 1.0~15 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=a6b3fe7812a120d03df3b3329f570fe7b6dcafe0;p=microjson.git Shorten the name of the linkable module and headeer file. --- diff --git a/Makefile b/Makefile index addfc82..57cf3c5 100644 --- a/Makefile +++ b/Makefile @@ -7,19 +7,21 @@ CFLAGS = -O # Add DEBUG_ENABLE for the tracing code CFLAGS += -DDEBUG_ENABLE -g -microjson.o: microjson.c microjson.h +mjson.o: mjson.c mjson.h -test_microjson: test_microjson.o microjson.o - $(CC) $(CFLAGS) -o test_microjson test_microjson.o microjson.o +test_microjson: test_microjson.o mjson.o + $(CC) $(CFLAGS) -o test_microjson test_microjson.o mjson.o # Regression test check: test_microjson test_microjson -# Worked examples -example1: example1.c microjson.c -example2: example2.c microjson.c -example3: example3.c microjson.c +make testbuild: mjson.o test_microjson example1 example2 example3 + +# Worked examples. These are essentially subsets of the regresion test. +example1: example1.c mjson.c +example2: example2.c mjson.c +example3: example3.c mjson.c clean: rm -f microjson.o test_microjson.o test_microjson example[123] @@ -36,5 +38,5 @@ microjson-$(VERSION).tar.gz: $(ALL) dist: microjson-$(VERSION).tar.gz -release: microjson-$(VERS).tar.gz microjson.html +release: microjson-$(VERS).tar.gz mjson.html shipper version=$(VERS) | sh -e -x diff --git a/example1.c b/example1.c index 81d370a..949b2f4 100644 --- a/example1.c +++ b/example1.c @@ -8,7 +8,7 @@ #include #include -#include "microjson.h" +#include "mjson.h" static bool flag1, flag2; static int count; diff --git a/example2.c b/example2.c index 8780619..bfff422 100644 --- a/example2.c +++ b/example2.c @@ -6,7 +6,7 @@ #include #include -#include "microjson.h" +#include "mjson.h" #define MAXCHANNELS 72 diff --git a/example3.c b/example3.c index 9b4c8e1..6c2c786 100644 --- a/example3.c +++ b/example3.c @@ -9,7 +9,7 @@ #include #include -#include "microjson.h" +#include "mjson.h" #define MAXUSERDEVS 4 diff --git a/microjson.asc b/microjson.asc index d8551ba..61de65e 100644 --- a/microjson.asc +++ b/microjson.asc @@ -45,7 +45,7 @@ Here is nearly the simplest possible example: #include #include -#include "microjson.h" +#include "mjson.h" static bool flag1, flag2; static int count; @@ -181,7 +181,7 @@ in this example: #include #include -#include "microjson.h" +#include "mjson.h" #define MAXCHANNELS 72 @@ -255,7 +255,7 @@ array of JSON objects directly into an array of C structs. #include #include -#include "microjson.h" +#include "mjson.h" #define MAXUSERDEVS 4 diff --git a/microjson.c b/microjson.c deleted file mode 100644 index 921c815..0000000 --- a/microjson.c +++ /dev/null @@ -1,966 +0,0 @@ -/**************************************************************************** - -NAME - microjson.c - parse JSON into fixed-extent data structures - -DESCRIPTION - This module parses a large subset of JSON (JavaScript Object -Notation). Unlike more general JSON parsers, it doesn't use malloc(3) -and doesn't support polymorphism; you need to give it a set of -template structures describing the expected shape of the incoming -JSON, and it will error out if that shape is not matched. When the -parse succeeds, attribute values will be extracted into static -locations specified in the template structures. - - The "shape" of a JSON object in the type signature of its -attributes (and attribute values, and so on recursively down through -all nestings of objects and arrays). This parser is indifferent to -the order of attributes at any level, but you have to tell it in -advance what the type of each attribute value will be and where the -parsed value will be stored. The template structures may supply -default values to be used when an expected attribute is omitted. - - The preceding paragraph told one fib. A single attribute may -actually have a span of multiple specifications with different -syntactically distinguishable types (e.g. string vs. real vs. integer -vs. boolean, but not signed integer vs. unsigned integer). The parser -will match the right spec against the actual data. - - The dialect this parses has some limitations. First, it cannot -recognize the JSON "null" value. Second, all elements of an array must -be of the same type. Third, some types (t_time, t_character) may not -ve array elements (this restriction could be lifted) - - There are separate entry points for beginning a parse of either -JSON object or a JSON array. JSON "float" quantities are actually -stored as doubles. - - This parser processes object arrays in one of two different ways, -defending on whether the array subtype is declared as object or -structobject. - - Object arrays take one base address per object subfield, and are -mapped into parallel C arrays (one per subfield). Strings are not -supported in this kind of array, as they don't have a "natural" size -to use as an offset multiplier. - - Structobjects arrays are a way to parse a list of objects to a set -of modifications to a corresponding array of C structs. The trick is -that the array object initialization has to specify both the C struct -array's base address and the stride length (the size of the C struct). -If you initialize the offset fields with the correct offsetof calls, -everything will work. Strings are supported but all string storage -has to be inline in the struct. - -PERMISSIONS - This file is Copyright (c) 2014 by Eric S. Raymond - BSD terms apply: see the file COPYING in the distribution root for details. - -***************************************************************************/ - -/* The strptime prototype is not provided unless explicitly requested. - * We also need to set the value high enough to signal inclusion of - * newer features (like clock_gettime). See the POSIX spec for more info: - * http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_01_02 */ -#define _XOPEN_SOURCE 600 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "microjson.h" - -#ifdef DEBUG_ENABLE -static int debuglevel = 0; -static FILE *debugfp; - -void json_enable_debug(int level, FILE * fp) -/* control the level and destination of debug trace messages */ -{ - debuglevel = level; - debugfp = fp; -} - -static void json_trace(int errlevel, const char *fmt, ...) -/* assemble command in printf(3) style */ -{ - if (errlevel <= debuglevel) { - char buf[BUFSIZ]; - va_list ap; - - (void)strncpy(buf, "json: ", BUFSIZ-1); - buf[BUFSIZ-1] = '\0'; - va_start(ap, fmt); - (void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, - ap); - va_end(ap); - - (void)fputs(buf, debugfp); - } -} - -# define json_debug_trace(args) (void) json_trace args -#else -# define json_debug_trace(args) /*@i1@*/do { } while (0) -#endif /* DEBUG_ENABLE */ - -/* - * Berkeley implementation of strtod(), inlined to avoid locale problems - * with the decimal point and stripped down to an atof()-equivalent. - */ - -/*@-shiftimplementation +charint@*/ -static double safe_atof(const char *string) -/* Takes a decimal ASCII floating-point number, optionally - * preceded by white space. Must have form "-I.FE-X", - * where I is the integer part of the mantissa, F is - * the fractional part of the mantissa, and X is the - * exponent. Either of the signs may be "+", "-", or - * omitted. Either I or F may be omitted, or both. - * The decimal point isn't necessary unless F is - * present. The "E" may actually be an "e". E and X - * may both be omitted (but not just one). - */ -{ - static int maxExponent = 511; /* Largest possible base 10 exponent. Any - * exponent larger than this will already - * produce underflow or overflow, so there's - * no need to worry about additional digits. - */ - static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ - 10., /* is 10^2^i. Used to convert decimal */ - 100., /* exponents into floating-point numbers. */ - 1.0e4, - 1.0e8, - 1.0e16, - 1.0e32, - 1.0e64, - 1.0e128, - 1.0e256 - }; - - bool sign, expSign = false; - double fraction, dblExp, *d; - register const char *p; - register int c; - int exp = 0; /* Exponent read from "EX" field. */ - int fracExp = 0; /* Exponent that derives from the fractional - * part. Under normal circumstatnces, it is - * the negative of the number of digits in F. - * However, if I is very long, the last digits - * of I get dropped (otherwise a long I with a - * large negative exponent could cause an - * unnecessary overflow on I alone). In this - * case, fracExp is incremented one for each - * dropped digit. */ - int mantSize; /* Number of digits in mantissa. */ - int decPt; /* Number of mantissa digits BEFORE decimal - * point. */ - const char *pExp; /* Temporarily holds location of exponent - * in string. */ - - /* - * Strip off leading blanks and check for a sign. - */ - - p = string; - while (isspace((unsigned char) *p)) { - p += 1; - } - if (*p == '-') { - sign = true; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - sign = false; - } - - /* - * Count the number of digits in the mantissa (including the decimal - * point), and also locate the decimal point. - */ - - decPt = -1; - for (mantSize = 0; ; mantSize += 1) - { - c = *p; - if (!isdigit(c)) { - if ((c != '.') || (decPt >= 0)) { - break; - } - decPt = mantSize; - } - p += 1; - } - - /* - * Now suck up the digits in the mantissa. Use two integers to - * collect 9 digits each (this is faster than using floating-point). - * If the mantissa has more than 18 digits, ignore the extras, since - * they can't affect the value anyway. - */ - - pExp = p; - p -= mantSize; - if (decPt < 0) { - decPt = mantSize; - } else { - mantSize -= 1; /* One of the digits was the point. */ - } - if (mantSize > 18) { - fracExp = decPt - 18; - mantSize = 18; - } else { - fracExp = decPt - mantSize; - } - if (mantSize == 0) { - fraction = 0.0; - //p = string; - goto done; - } else { - int frac1, frac2; - frac1 = 0; - for ( ; mantSize > 9; mantSize -= 1) - { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac1 = 10*frac1 + (c - '0'); - } - frac2 = 0; - for (; mantSize > 0; mantSize -= 1) - { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac2 = 10*frac2 + (c - '0'); - } - fraction = (1.0e9 * frac1) + frac2; - } - - /* - * Skim off the exponent. - */ - - p = pExp; - if ((*p == 'E') || (*p == 'e')) { - p += 1; - if (*p == '-') { - expSign = true; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - expSign = false; - } - while (isdigit((unsigned char) *p)) { - exp = exp * 10 + (*p - '0'); - p += 1; - } - } - if (expSign) { - exp = fracExp - exp; - } else { - exp = fracExp + exp; - } - - /* - * Generate a floating-point number that represents the exponent. - * Do this by processing the exponent one bit at a time to combine - * many powers of 2 of 10. Then combine the exponent with the - * fraction. - */ - - if (exp < 0) { - expSign = true; - exp = -exp; - } else { - expSign = false; - } - if (exp > maxExponent) { - exp = maxExponent; - errno = ERANGE; - } - dblExp = 1.0; - for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { - if (exp & 01) { - dblExp *= *d; - } - } - if (expSign) { - fraction /= dblExp; - } else { - fraction *= dblExp; - } - -done: - if (sign) { - return -fraction; - } - return fraction; -} -/*@+shiftimplementation -charint@*/ - -#define MONTHSPERYEAR 12 /* months per calendar year */ - -static time_t mkutctime(register struct tm * t) -/* struct tm to seconds since Unix epoch */ -{ - register int year; - register time_t result; - static const int cumdays[MONTHSPERYEAR] = - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; - - /*@ +matchanyintegral @*/ - year = 1900 + t->tm_year + t->tm_mon / MONTHSPERYEAR; - result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR]; - result += (year - 1968) / 4; - result -= (year - 1900) / 100; - result += (year - 1600) / 400; - if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) && - (t->tm_mon % MONTHSPERYEAR) < 2) - result--; - result += t->tm_mday - 1; - result *= 24; - result += t->tm_hour; - result *= 60; - result += t->tm_min; - result *= 60; - result += t->tm_sec; - /*@ -matchanyintegral @*/ - return (result); -} - -static double iso8601_to_unix(/*@in@*/const char *isotime) -/* ISO8601 UTC to Unix UTC */ -{ - char *dp = NULL; - double usec; - struct tm tm; - - /*@i1@*/ dp = strptime(isotime, "%Y-%m-%dT%H:%M:%S", &tm); - if (dp != NULL && *dp == '.') - usec = strtod(dp, NULL); - else - usec = 0; - return mkutctime(&tm) + usec; -} - -/*@-immediatetrans -dependenttrans -usereleased -compdef@*/ -static /*@null@*/ char *json_target_address(const struct json_attr_t *cursor, - /*@null@*/ - const struct json_array_t - *parent, int offset) -{ - char *targetaddr = NULL; - if (parent == NULL || parent->element_type != t_structobject) { - /* ordinary case - use the address in the cursor structure */ - switch (cursor->type) { - case t_ignore: - targetaddr = NULL; - break; - case t_integer: - targetaddr = (char *)&cursor->addr.integer[offset]; - break; - case t_uinteger: - targetaddr = (char *)&cursor->addr.uinteger[offset]; - break; - case t_time: - case t_real: - targetaddr = (char *)&cursor->addr.real[offset]; - break; - case t_string: - targetaddr = cursor->addr.string; - break; - case t_boolean: - targetaddr = (char *)&cursor->addr.boolean[offset]; - break; - case t_character: - targetaddr = (char *)&cursor->addr.character[offset]; - break; - default: - targetaddr = NULL; - break; - } - } else - /* tricky case - hacking a member in an array of structures */ - targetaddr = - parent->arr.objects.base + (offset * parent->arr.objects.stride) + - cursor->addr.offset; - json_debug_trace((1, "Target address for %s (offset %d) is %p\n", - cursor->attribute, offset, targetaddr)); - return targetaddr; -} - -/*@-immediatetrans -dependenttrans +usereleased +compdef@*/ - -static int json_internal_read_object(const char *cp, - const struct json_attr_t *attrs, - /*@null@*/ - const struct json_array_t *parent, - int offset, - /*@null@*/ const char **end) -{ - /*@ -nullstate -nullderef -mustfreefresh -nullpass -usedef @*/ - enum - { init, await_attr, in_attr, await_value, in_val_string, - in_escape, in_val_token, post_val, post_array - } state = 0; -#ifdef DEBUG_ENABLE - char *statenames[] = { - "init", "await_attr", "in_attr", "await_value", "in_val_string", - "in_escape", "in_val_token", "post_val", "post_array", - }; -#endif /* DEBUG_ENABLE */ - char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL; - char valbuf[JSON_VAL_MAX + 1], *pval = NULL; - bool value_quoted = false; - char uescape[5]; /* enough space for 4 hex digits and a NUL */ - const struct json_attr_t *cursor; - int substatus, n, maxlen = 0; - unsigned int u; - const struct json_enum_t *mp; - char *lptr; - -#ifdef S_SPLINT_S - /* prevents gripes about buffers not being completely defined */ - memset(valbuf, '\0', sizeof(valbuf)); - memset(attrbuf, '\0', sizeof(attrbuf)); -#endif /* S_SPLINT_S */ - - if (end != NULL) - *end = NULL; /* give it a well-defined value on parse failure */ - - /* stuff fields with defaults in case they're omitted in the JSON input */ - for (cursor = attrs; cursor->attribute != NULL; cursor++) - if (!cursor->nodefault) { - lptr = json_target_address(cursor, parent, offset); - if (lptr != NULL) - switch (cursor->type) { - case t_integer: - memcpy(lptr, &cursor->dflt.integer, sizeof(int)); - break; - case t_uinteger: - memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int)); - break; - case t_time: - case t_real: - memcpy(lptr, &cursor->dflt.real, sizeof(double)); - break; - case t_string: - if (parent != NULL - && parent->element_type != t_structobject - && offset > 0) - return JSON_ERR_NOPARSTR; - lptr[0] = '\0'; - break; - case t_boolean: - memcpy(lptr, &cursor->dflt.boolean, sizeof(bool)); - break; - case t_character: - lptr[0] = cursor->dflt.character; - break; - case t_object: /* silences a compiler warning */ - case t_structobject: - case t_array: - case t_check: - case t_ignore: - break; - } - } - - json_debug_trace((1, "JSON parse of '%s' begins.\n", cp)); - - /* parse input JSON */ - for (; *cp != '\0'; cp++) { - json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n", - statenames[state], *cp, cp)); - switch (state) { - case init: - if (isspace((unsigned char) *cp)) - continue; - else if (*cp == '{') - state = await_attr; - else { - json_debug_trace((1, - "Non-WS when expecting object start.\n")); - return JSON_ERR_OBSTART; - } - break; - case await_attr: - if (isspace((unsigned char) *cp)) - continue; - else if (*cp == '"') { - state = in_attr; - pattr = attrbuf; - } else if (*cp == '}') - break; - else { - json_debug_trace((1, "Non-WS when expecting attribute.\n")); - return JSON_ERR_ATTRSTART; - } - break; - case in_attr: - if (pattr == NULL) - return JSON_ERR_NULLPTR; - if (*cp == '"') { - *pattr++ = '\0'; - json_debug_trace((1, "Collected attribute name %s\n", - attrbuf)); - for (cursor = attrs; cursor->attribute != NULL; cursor++) { - json_debug_trace((2, "Checking against %s\n", - cursor->attribute)); - if (strcmp(cursor->attribute, attrbuf) == 0) - break; - } - if (cursor->attribute == NULL) { - json_debug_trace((1, - "Unknown attribute name '%s' (attributes begin with '%s').\n", - attrbuf, attrs->attribute)); - return JSON_ERR_BADATTR; - } - state = await_value; - if (cursor->type == t_string) - maxlen = (int)cursor->len - 1; - else if (cursor->type == t_check) - maxlen = (int)strlen(cursor->dflt.check); - else if (cursor->type == t_time || cursor->type == t_ignore) - maxlen = JSON_VAL_MAX; - else if (cursor->map != NULL) - maxlen = (int)sizeof(valbuf) - 1; - pval = valbuf; - } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) { - json_debug_trace((1, "Attribute name too long.\n")); - return JSON_ERR_ATTRLEN; - } else - *pattr++ = *cp; - break; - case await_value: - if (isspace((unsigned char) *cp) || *cp == ':') - continue; - else if (*cp == '[') { - if (cursor->type != t_array) { - json_debug_trace((1, - "Saw [ when not expecting array.\n")); - return JSON_ERR_NOARRAY; - } - substatus = json_read_array(cp, &cursor->addr.array, &cp); - if (substatus != 0) - return substatus; - state = post_array; - } else if (cursor->type == t_array) { - json_debug_trace((1, - "Array element was specified, but no [.\n")); - return JSON_ERR_NOBRAK; - } else if (*cp == '"') { - value_quoted = true; - state = in_val_string; - pval = valbuf; - } else { - value_quoted = false; - state = in_val_token; - pval = valbuf; - *pval++ = *cp; - } - break; - case in_val_string: - if (pval == NULL) - return JSON_ERR_NULLPTR; - if (*cp == '\\') - state = in_escape; - else if (*cp == '"') { - *pval++ = '\0'; - json_debug_trace((1, "Collected string value %s\n", valbuf)); - state = post_val; - } else if (pval > valbuf + JSON_VAL_MAX - 1 - || pval > valbuf + maxlen) { - json_debug_trace((1, "String value too long.\n")); - return JSON_ERR_STRLONG; /* */ - } else - *pval++ = *cp; - break; - case in_escape: - if (pval == NULL) - return JSON_ERR_NULLPTR; - switch (*cp) { - case 'b': - *pval++ = '\b'; - break; - case 'f': - *pval++ = '\f'; - break; - case 'n': - *pval++ = '\n'; - break; - case 'r': - *pval++ = '\r'; - break; - case 't': - *pval++ = '\t'; - break; - case 'u': - for (n = 0; n < 4 && cp[n] != '\0'; n++) - uescape[n] = *cp++; - --cp; - (void)sscanf(uescape, "%04x", &u); - *pval++ = (char)u; /* will truncate values above 0xff */ - break; - default: /* handles double quote and solidus */ - *pval++ = *cp; - break; - } - state = in_val_string; - break; - case in_val_token: - if (pval == NULL) - return JSON_ERR_NULLPTR; - if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') { - *pval = '\0'; - json_debug_trace((1, "Collected token value %s.\n", valbuf)); - state = post_val; - if (*cp == '}' || *cp == ',') - --cp; - } else if (pval > valbuf + JSON_VAL_MAX - 1) { - json_debug_trace((1, "Token value too long.\n")); - return JSON_ERR_TOKLONG; - } else - *pval++ = *cp; - break; - case post_val: - /* - * We know that cursor points at the first spec matching - * the current attribute. We don't know that it's *the* - * correct spec; our dialect allows there to be any number - * of adjacent ones with the same attrname but different - * types. Here's where we try to seek forward for a - * matching type/attr pair if we're not looking at one. - */ - for (;;) { - int seeking = cursor->type; - if (value_quoted && (cursor->type == t_string || cursor->type == t_time)) - break; - if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0) - && seeking == t_boolean) - break; - if (isdigit((unsigned char) valbuf[0])) { - bool decimal = strchr(valbuf, '.') != NULL; - if (decimal && seeking == t_real) - break; - if (!decimal && (seeking == t_integer || seeking == t_uinteger)) - break; - } - if (cursor[1].attribute==NULL) /* out of possiblities */ - break; - if (strcmp(cursor[1].attribute, attrbuf)!=0) - break; - ++cursor; - } - if (value_quoted - && (cursor->type != t_string && cursor->type != t_character - && cursor->type != t_check && cursor->type != t_time - && cursor->type != t_ignore && cursor->map == 0)) { - json_debug_trace((1, - "Saw quoted value when expecting non-string.\n")); - return JSON_ERR_QNONSTRING; - } - if (!value_quoted - && (cursor->type == t_string || cursor->type == t_check - || cursor->type == t_time || cursor->map != 0)) { - json_debug_trace((1, - "Didn't see quoted value when expecting string.\n")); - return JSON_ERR_NONQSTRING; - } - if (cursor->map != 0) { - for (mp = cursor->map; mp->name != NULL; mp++) - if (strcmp(mp->name, valbuf) == 0) { - goto foundit; - } - json_debug_trace((1, "Invalid enumerated value string %s.\n", - valbuf)); - return JSON_ERR_BADENUM; - foundit: - (void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value); - } - lptr = json_target_address(cursor, parent, offset); - if (lptr != NULL) - switch (cursor->type) { - case t_integer: - { - int tmp = atoi(valbuf); - memcpy(lptr, &tmp, sizeof(int)); - } - break; - case t_uinteger: - { - unsigned int tmp = (unsigned int)atoi(valbuf); - memcpy(lptr, &tmp, sizeof(unsigned int)); - } - break; - case t_time: - { - double tmp = iso8601_to_unix(valbuf); - memcpy(lptr, &tmp, sizeof(double)); - } - break; - case t_real: - { - double tmp = safe_atof(valbuf); - memcpy(lptr, &tmp, sizeof(double)); - } - break; - case t_string: - if (parent != NULL - && parent->element_type != t_structobject - && offset > 0) - return JSON_ERR_NOPARSTR; - (void)strncpy(lptr, valbuf, cursor->len); - valbuf[sizeof(valbuf)-1] = '\0'; - break; - case t_boolean: - { - bool tmp = (strcmp(valbuf, "true") == 0); - memcpy(lptr, &tmp, sizeof(bool)); - } - break; - case t_character: - if (strlen(valbuf) > 1) - return JSON_ERR_STRLONG; - else - lptr[0] = valbuf[0]; - break; - case t_ignore: /* silences a compiler warning */ - case t_object: /* silences a compiler warning */ - case t_structobject: - case t_array: - break; - case t_check: - if (strcmp(cursor->dflt.check, valbuf) != 0) { - json_debug_trace((1, - "Required attribute value %s not present.\n", - cursor->dflt.check)); - return JSON_ERR_CHECKFAIL; - } - break; - } - /*@fallthrough@*/ - case post_array: - if (isspace((unsigned char) *cp)) - continue; - else if (*cp == ',') - state = await_attr; - else if (*cp == '}') { - ++cp; - goto good_parse; - } else { - json_debug_trace((1, "Garbage while expecting comma or }\n")); - return JSON_ERR_BADTRAIL; - } - break; - } - } - - good_parse: - /* in case there's another object following, consume trailing WS */ - while (isspace((unsigned char) *cp)) - ++cp; - if (end != NULL) - *end = cp; - json_debug_trace((1, "JSON parse ends.\n")); - return 0; - /*@ +nullstate +nullderef +mustfreefresh +nullpass +usedef @*/ -} - -int json_read_array(const char *cp, const struct json_array_t *arr, - const char **end) -{ - /*@-nullstate -onlytrans@*/ - int substatus, offset, arrcount; - char *tp; - - if (end != NULL) - *end = NULL; /* give it a well-defined value on parse failure */ - - json_debug_trace((1, "Entered json_read_array()\n")); - - while (isspace((unsigned char) *cp)) - cp++; - if (*cp != '[') { - json_debug_trace((1, "Didn't find expected array start\n")); - return JSON_ERR_ARRAYSTART; - } else - cp++; - - tp = arr->arr.strings.store; - arrcount = 0; - - /* Check for empty array */ - while (isspace((unsigned char) *cp)) - cp++; - if (*cp == ']') - goto breakout; - - for (offset = 0; offset < arr->maxlen; offset++) { - char *ep = NULL; - json_debug_trace((1, "Looking at %s\n", cp)); - switch (arr->element_type) { - case t_string: - if (isspace((unsigned char) *cp)) - cp++; - if (*cp != '"') - return JSON_ERR_BADSTRING; - else - ++cp; - arr->arr.strings.ptrs[offset] = tp; - for (; tp - arr->arr.strings.store < arr->arr.strings.storelen; - tp++) - if (*cp == '"') { - ++cp; - *tp++ = '\0'; - goto stringend; - } else if (*cp == '\0') { - json_debug_trace((1, - "Bad string syntax in string list.\n")); - return JSON_ERR_BADSTRING; - } else { - *tp = *cp++; - } - json_debug_trace((1, "Bad string syntax in string list.\n")); - return JSON_ERR_BADSTRING; - stringend: - break; - case t_object: - case t_structobject: - substatus = - json_internal_read_object(cp, arr->arr.objects.subtype, arr, - offset, &cp); - if (substatus != 0) - return substatus; - break; - case t_integer: - arr->arr.integers.store[offset] = (int)strtol(cp, &ep, 0); - if (ep == cp) - return JSON_ERR_BADNUM; - else - cp = ep; - break; - case t_uinteger: - arr->arr.uintegers.store[offset] = (unsigned int)strtoul(cp, &ep, 0); - if (ep == cp) - return JSON_ERR_BADNUM; - else - cp = ep; - break; - case t_real: - arr->arr.reals.store[offset] = strtod(cp, &ep); - if (ep == cp) - return JSON_ERR_BADNUM; - else - cp = ep; - break; - case t_boolean: - if (strncmp(cp, "true", 4) == 0) { - arr->arr.booleans.store[offset] = true; - cp += 4; - } - else if (strncmp(cp, "false", 5) == 0) { - arr->arr.booleans.store[offset] = false; - cp += 5; - } - break; - case t_time: - case t_character: - case t_array: - case t_check: - case t_ignore: - json_debug_trace((1, "Invalid array subtype.\n")); - return JSON_ERR_SUBTYPE; - } - arrcount++; - if (isspace((unsigned char) *cp)) - cp++; - if (*cp == ']') { - json_debug_trace((1, "End of array found.\n")); - goto breakout; - } else if (*cp == ',') - cp++; - else { - json_debug_trace((1, "Bad trailing syntax on array.\n")); - return JSON_ERR_BADSUBTRAIL; - } - } - json_debug_trace((1, "Too many elements in array.\n")); - return JSON_ERR_SUBTOOLONG; - breakout: - if (arr->count != NULL) - *(arr->count) = arrcount; - if (end != NULL) - *end = cp; - /*@ -nullderef @*/ - json_debug_trace((1, "leaving json_read_array() with %d elements\n", - arrcount)); - /*@ +nullderef @*/ - return 0; - /*@+nullstate +onlytrans@*/ -} - -int json_read_object(const char *cp, const struct json_attr_t *attrs, - /*@null@*/ const char **end) -{ - int st; - - json_debug_trace((1, "json_read_object() sees '%s'\n", cp)); - st = json_internal_read_object(cp, attrs, NULL, 0, end); - return st; -} - -const /*@observer@*/ char *json_error_string(int err) -{ - const char *errors[] = { - "unknown error while parsing JSON", - "non-whitespace when expecting object start", - "non-whitespace when expecting attribute start", - "unknown attribute name", - "attribute name too long", - "saw [ when not expecting array", - "array element specified, but no [", - "string value too long", - "token value too long", - "garbage while expecting comma or } or ]", - "didn't find expected array start", - "error while parsing object array", - "too many array elements", - "garbage while expecting array comma", - "unsupported array element type", - "error while string parsing", - "check attribute not matched", - "can't support strings in parallel arrays", - "invalid enumerated value", - "saw quoted value when expecting nonstring", - "didn't see quoted value when expecting string", - "other data conversion error", - "unexpected null value or attribute pointer", - }; - - if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0]))) - return errors[0]; - else - return errors[err]; -} - -/* end */ - diff --git a/microjson.h b/microjson.h deleted file mode 100644 index 8323d95..0000000 --- a/microjson.h +++ /dev/null @@ -1,139 +0,0 @@ -/* Structures for JSON parsing using only fixed-extent memory - * - * This file is Copyright (c) 2014 by Eric S. Raymond. - * BSD terms apply: see the file COPYING in the distribution root for details. - */ - -#include -#include - -#define NITEMS(x) (int)(sizeof(x)/sizeof(x[0])) - -typedef enum {t_integer, t_uinteger, t_real, - t_string, t_boolean, t_character, - t_time, - t_object, t_structobject, t_array, - t_check, t_ignore} - json_type; - -struct json_enum_t { - char *name; - int value; -}; - -struct json_array_t { - json_type element_type; - union { - struct { - const struct json_attr_t *subtype; - char *base; - size_t stride; - } objects; - struct { - char **ptrs; - char *store; - int storelen; - } strings; - struct { - int *store; - } integers; - struct { - unsigned int *store; - } uintegers; - struct { - double *store; - } reals; - struct { - bool *store; - } booleans; - } arr; - int *count, maxlen; -}; - -struct json_attr_t { - char *attribute; - json_type type; - union { - int *integer; - unsigned int *uinteger; - double *real; - char *string; - bool *boolean; - char *character; - struct json_array_t array; - size_t offset; - } addr; - union { - int integer; - unsigned int uinteger; - double real; - bool boolean; - char character; - char *check; - } dflt; - size_t len; - const struct json_enum_t *map; - bool nodefault; -}; - -#define JSON_ATTR_MAX 31 /* max chars in JSON attribute name */ -#define JSON_VAL_MAX 512 /* max chars in JSON value part */ - -#ifdef __cplusplus -extern "C" { -#endif -int json_read_object(const char *, const struct json_attr_t *, - /*@null@*/const char **); -int json_read_array(const char *, const struct json_array_t *, - /*@null@*/const char **); -const /*@observer@*/char *json_error_string(int); - -void json_enable_debug(int, FILE *); -#ifdef __cplusplus -} -#endif - -#define JSON_ERR_OBSTART 1 /* non-WS when expecting object start */ -#define JSON_ERR_ATTRSTART 2 /* non-WS when expecting attrib start */ -#define JSON_ERR_BADATTR 3 /* unknown attribute name */ -#define JSON_ERR_ATTRLEN 4 /* attribute name too long */ -#define JSON_ERR_NOARRAY 5 /* saw [ when not expecting array */ -#define JSON_ERR_NOBRAK 6 /* array element specified, but no [ */ -#define JSON_ERR_STRLONG 7 /* string value too long */ -#define JSON_ERR_TOKLONG 8 /* token value too long */ -#define JSON_ERR_BADTRAIL 9 /* garbage while expecting comma or } or ] */ -#define JSON_ERR_ARRAYSTART 10 /* didn't find expected array start */ -#define JSON_ERR_OBJARR 11 /* error while parsing object array */ -#define JSON_ERR_SUBTOOLONG 12 /* too many array elements */ -#define JSON_ERR_BADSUBTRAIL 13 /* garbage while expecting array comma */ -#define JSON_ERR_SUBTYPE 14 /* unsupported array element type */ -#define JSON_ERR_BADSTRING 15 /* error while string parsing */ -#define JSON_ERR_CHECKFAIL 16 /* check attribute not matched */ -#define JSON_ERR_NOPARSTR 17 /* can't support strings in parallel arrays */ -#define JSON_ERR_BADENUM 18 /* invalid enumerated value */ -#define JSON_ERR_QNONSTRING 19 /* saw quoted value when expecting nonstring */ -#define JSON_ERR_NONQSTRING 19 /* didn't see quoted value when expecting string */ -#define JSON_ERR_MISC 20 /* other data conversion error */ -#define JSON_ERR_BADNUM 21 /* error while parsing a numerical argument */ -#define JSON_ERR_NULLPTR 22 /* unexpected null value or attribute pointer */ - -/* - * Use the following macros to declare template initializers for structobject - * arrays. Writing the equivalents out by hand is error-prone. - * - * STRUCTOBJECT takes a structure name s, and a fieldname f in s. - * - * STRUCTARRAY takes the name of a structure array, a pointer to a an - * initializer defining the subobject type, and the address of an integer to - * store the length in. - */ -#define STRUCTOBJECT(s, f) .addr.offset = offsetof(s, f) -#define STRUCTARRAY(a, e, n) \ - .addr.array.element_type = t_structobject, \ - .addr.array.arr.objects.subtype = e, \ - .addr.array.arr.objects.base = (char*)a, \ - .addr.array.arr.objects.stride = sizeof(a[0]), \ - .addr.array.count = n, \ - .addr.array.maxlen = (int)(sizeof(a)/sizeof(a[0])) - -/* json.h ends here */ diff --git a/mjson.c b/mjson.c new file mode 100644 index 0000000..0bcef5e --- /dev/null +++ b/mjson.c @@ -0,0 +1,966 @@ +/**************************************************************************** + +NAME + mjson.c - parse JSON into fixed-extent data structures + +DESCRIPTION + This module parses a large subset of JSON (JavaScript Object +Notation). Unlike more general JSON parsers, it doesn't use malloc(3) +and doesn't support polymorphism; you need to give it a set of +template structures describing the expected shape of the incoming +JSON, and it will error out if that shape is not matched. When the +parse succeeds, attribute values will be extracted into static +locations specified in the template structures. + + The "shape" of a JSON object in the type signature of its +attributes (and attribute values, and so on recursively down through +all nestings of objects and arrays). This parser is indifferent to +the order of attributes at any level, but you have to tell it in +advance what the type of each attribute value will be and where the +parsed value will be stored. The template structures may supply +default values to be used when an expected attribute is omitted. + + The preceding paragraph told one fib. A single attribute may +actually have a span of multiple specifications with different +syntactically distinguishable types (e.g. string vs. real vs. integer +vs. boolean, but not signed integer vs. unsigned integer). The parser +will match the right spec against the actual data. + + The dialect this parses has some limitations. First, it cannot +recognize the JSON "null" value. Second, all elements of an array must +be of the same type. Third, some types (t_time, t_character) may not +ve array elements (this restriction could be lifted) + + There are separate entry points for beginning a parse of either +JSON object or a JSON array. JSON "float" quantities are actually +stored as doubles. + + This parser processes object arrays in one of two different ways, +defending on whether the array subtype is declared as object or +structobject. + + Object arrays take one base address per object subfield, and are +mapped into parallel C arrays (one per subfield). Strings are not +supported in this kind of array, as they don't have a "natural" size +to use as an offset multiplier. + + Structobjects arrays are a way to parse a list of objects to a set +of modifications to a corresponding array of C structs. The trick is +that the array object initialization has to specify both the C struct +array's base address and the stride length (the size of the C struct). +If you initialize the offset fields with the correct offsetof calls, +everything will work. Strings are supported but all string storage +has to be inline in the struct. + +PERMISSIONS + This file is Copyright (c) 2014 by Eric S. Raymond + BSD terms apply: see the file COPYING in the distribution root for details. + +***************************************************************************/ + +/* The strptime prototype is not provided unless explicitly requested. + * We also need to set the value high enough to signal inclusion of + * newer features (like clock_gettime). See the POSIX spec for more info: + * http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_01_02 */ +#define _XOPEN_SOURCE 600 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mjson.h" + +#ifdef DEBUG_ENABLE +static int debuglevel = 0; +static FILE *debugfp; + +void json_enable_debug(int level, FILE * fp) +/* control the level and destination of debug trace messages */ +{ + debuglevel = level; + debugfp = fp; +} + +static void json_trace(int errlevel, const char *fmt, ...) +/* assemble command in printf(3) style */ +{ + if (errlevel <= debuglevel) { + char buf[BUFSIZ]; + va_list ap; + + (void)strncpy(buf, "json: ", BUFSIZ-1); + buf[BUFSIZ-1] = '\0'; + va_start(ap, fmt); + (void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, + ap); + va_end(ap); + + (void)fputs(buf, debugfp); + } +} + +# define json_debug_trace(args) (void) json_trace args +#else +# define json_debug_trace(args) /*@i1@*/do { } while (0) +#endif /* DEBUG_ENABLE */ + +/* + * Berkeley implementation of strtod(), inlined to avoid locale problems + * with the decimal point and stripped down to an atof()-equivalent. + */ + +/*@-shiftimplementation +charint@*/ +static double safe_atof(const char *string) +/* Takes a decimal ASCII floating-point number, optionally + * preceded by white space. Must have form "-I.FE-X", + * where I is the integer part of the mantissa, F is + * the fractional part of the mantissa, and X is the + * exponent. Either of the signs may be "+", "-", or + * omitted. Either I or F may be omitted, or both. + * The decimal point isn't necessary unless F is + * present. The "E" may actually be an "e". E and X + * may both be omitted (but not just one). + */ +{ + static int maxExponent = 511; /* Largest possible base 10 exponent. Any + * exponent larger than this will already + * produce underflow or overflow, so there's + * no need to worry about additional digits. + */ + static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ + 10., /* is 10^2^i. Used to convert decimal */ + 100., /* exponents into floating-point numbers. */ + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + + bool sign, expSign = false; + double fraction, dblExp, *d; + register const char *p; + register int c; + int exp = 0; /* Exponent read from "EX" field. */ + int fracExp = 0; /* Exponent that derives from the fractional + * part. Under normal circumstatnces, it is + * the negative of the number of digits in F. + * However, if I is very long, the last digits + * of I get dropped (otherwise a long I with a + * large negative exponent could cause an + * unnecessary overflow on I alone). In this + * case, fracExp is incremented one for each + * dropped digit. */ + int mantSize; /* Number of digits in mantissa. */ + int decPt; /* Number of mantissa digits BEFORE decimal + * point. */ + const char *pExp; /* Temporarily holds location of exponent + * in string. */ + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (isspace((unsigned char) *p)) { + p += 1; + } + if (*p == '-') { + sign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = false; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0; ; mantSize += 1) + { + c = *p; + if (!isdigit(c)) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to + * collect 9 digits each (this is faster than using floating-point). + * If the mantissa has more than 18 digits, ignore the extras, since + * they can't affect the value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + //p = string; + goto done; + } else { + int frac1, frac2; + frac1 = 0; + for ( ; mantSize > 9; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10*frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10*frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = false; + } + while (isdigit((unsigned char) *p)) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. + * Do this by processing the exponent one bit at a time to combine + * many powers of 2 of 10. Then combine the exponent with the + * fraction. + */ + + if (exp < 0) { + expSign = true; + exp = -exp; + } else { + expSign = false; + } + if (exp > maxExponent) { + exp = maxExponent; + errno = ERANGE; + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + +done: + if (sign) { + return -fraction; + } + return fraction; +} +/*@+shiftimplementation -charint@*/ + +#define MONTHSPERYEAR 12 /* months per calendar year */ + +static time_t mkutctime(register struct tm * t) +/* struct tm to seconds since Unix epoch */ +{ + register int year; + register time_t result; + static const int cumdays[MONTHSPERYEAR] = + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + + /*@ +matchanyintegral @*/ + year = 1900 + t->tm_year + t->tm_mon / MONTHSPERYEAR; + result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR]; + result += (year - 1968) / 4; + result -= (year - 1900) / 100; + result += (year - 1600) / 400; + if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) && + (t->tm_mon % MONTHSPERYEAR) < 2) + result--; + result += t->tm_mday - 1; + result *= 24; + result += t->tm_hour; + result *= 60; + result += t->tm_min; + result *= 60; + result += t->tm_sec; + /*@ -matchanyintegral @*/ + return (result); +} + +static double iso8601_to_unix(/*@in@*/const char *isotime) +/* ISO8601 UTC to Unix UTC */ +{ + char *dp = NULL; + double usec; + struct tm tm; + + /*@i1@*/ dp = strptime(isotime, "%Y-%m-%dT%H:%M:%S", &tm); + if (dp != NULL && *dp == '.') + usec = strtod(dp, NULL); + else + usec = 0; + return mkutctime(&tm) + usec; +} + +/*@-immediatetrans -dependenttrans -usereleased -compdef@*/ +static /*@null@*/ char *json_target_address(const struct json_attr_t *cursor, + /*@null@*/ + const struct json_array_t + *parent, int offset) +{ + char *targetaddr = NULL; + if (parent == NULL || parent->element_type != t_structobject) { + /* ordinary case - use the address in the cursor structure */ + switch (cursor->type) { + case t_ignore: + targetaddr = NULL; + break; + case t_integer: + targetaddr = (char *)&cursor->addr.integer[offset]; + break; + case t_uinteger: + targetaddr = (char *)&cursor->addr.uinteger[offset]; + break; + case t_time: + case t_real: + targetaddr = (char *)&cursor->addr.real[offset]; + break; + case t_string: + targetaddr = cursor->addr.string; + break; + case t_boolean: + targetaddr = (char *)&cursor->addr.boolean[offset]; + break; + case t_character: + targetaddr = (char *)&cursor->addr.character[offset]; + break; + default: + targetaddr = NULL; + break; + } + } else + /* tricky case - hacking a member in an array of structures */ + targetaddr = + parent->arr.objects.base + (offset * parent->arr.objects.stride) + + cursor->addr.offset; + json_debug_trace((1, "Target address for %s (offset %d) is %p\n", + cursor->attribute, offset, targetaddr)); + return targetaddr; +} + +/*@-immediatetrans -dependenttrans +usereleased +compdef@*/ + +static int json_internal_read_object(const char *cp, + const struct json_attr_t *attrs, + /*@null@*/ + const struct json_array_t *parent, + int offset, + /*@null@*/ const char **end) +{ + /*@ -nullstate -nullderef -mustfreefresh -nullpass -usedef @*/ + enum + { init, await_attr, in_attr, await_value, in_val_string, + in_escape, in_val_token, post_val, post_array + } state = 0; +#ifdef DEBUG_ENABLE + char *statenames[] = { + "init", "await_attr", "in_attr", "await_value", "in_val_string", + "in_escape", "in_val_token", "post_val", "post_array", + }; +#endif /* DEBUG_ENABLE */ + char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL; + char valbuf[JSON_VAL_MAX + 1], *pval = NULL; + bool value_quoted = false; + char uescape[5]; /* enough space for 4 hex digits and a NUL */ + const struct json_attr_t *cursor; + int substatus, n, maxlen = 0; + unsigned int u; + const struct json_enum_t *mp; + char *lptr; + +#ifdef S_SPLINT_S + /* prevents gripes about buffers not being completely defined */ + memset(valbuf, '\0', sizeof(valbuf)); + memset(attrbuf, '\0', sizeof(attrbuf)); +#endif /* S_SPLINT_S */ + + if (end != NULL) + *end = NULL; /* give it a well-defined value on parse failure */ + + /* stuff fields with defaults in case they're omitted in the JSON input */ + for (cursor = attrs; cursor->attribute != NULL; cursor++) + if (!cursor->nodefault) { + lptr = json_target_address(cursor, parent, offset); + if (lptr != NULL) + switch (cursor->type) { + case t_integer: + memcpy(lptr, &cursor->dflt.integer, sizeof(int)); + break; + case t_uinteger: + memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int)); + break; + case t_time: + case t_real: + memcpy(lptr, &cursor->dflt.real, sizeof(double)); + break; + case t_string: + if (parent != NULL + && parent->element_type != t_structobject + && offset > 0) + return JSON_ERR_NOPARSTR; + lptr[0] = '\0'; + break; + case t_boolean: + memcpy(lptr, &cursor->dflt.boolean, sizeof(bool)); + break; + case t_character: + lptr[0] = cursor->dflt.character; + break; + case t_object: /* silences a compiler warning */ + case t_structobject: + case t_array: + case t_check: + case t_ignore: + break; + } + } + + json_debug_trace((1, "JSON parse of '%s' begins.\n", cp)); + + /* parse input JSON */ + for (; *cp != '\0'; cp++) { + json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n", + statenames[state], *cp, cp)); + switch (state) { + case init: + if (isspace((unsigned char) *cp)) + continue; + else if (*cp == '{') + state = await_attr; + else { + json_debug_trace((1, + "Non-WS when expecting object start.\n")); + return JSON_ERR_OBSTART; + } + break; + case await_attr: + if (isspace((unsigned char) *cp)) + continue; + else if (*cp == '"') { + state = in_attr; + pattr = attrbuf; + } else if (*cp == '}') + break; + else { + json_debug_trace((1, "Non-WS when expecting attribute.\n")); + return JSON_ERR_ATTRSTART; + } + break; + case in_attr: + if (pattr == NULL) + return JSON_ERR_NULLPTR; + if (*cp == '"') { + *pattr++ = '\0'; + json_debug_trace((1, "Collected attribute name %s\n", + attrbuf)); + for (cursor = attrs; cursor->attribute != NULL; cursor++) { + json_debug_trace((2, "Checking against %s\n", + cursor->attribute)); + if (strcmp(cursor->attribute, attrbuf) == 0) + break; + } + if (cursor->attribute == NULL) { + json_debug_trace((1, + "Unknown attribute name '%s' (attributes begin with '%s').\n", + attrbuf, attrs->attribute)); + return JSON_ERR_BADATTR; + } + state = await_value; + if (cursor->type == t_string) + maxlen = (int)cursor->len - 1; + else if (cursor->type == t_check) + maxlen = (int)strlen(cursor->dflt.check); + else if (cursor->type == t_time || cursor->type == t_ignore) + maxlen = JSON_VAL_MAX; + else if (cursor->map != NULL) + maxlen = (int)sizeof(valbuf) - 1; + pval = valbuf; + } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) { + json_debug_trace((1, "Attribute name too long.\n")); + return JSON_ERR_ATTRLEN; + } else + *pattr++ = *cp; + break; + case await_value: + if (isspace((unsigned char) *cp) || *cp == ':') + continue; + else if (*cp == '[') { + if (cursor->type != t_array) { + json_debug_trace((1, + "Saw [ when not expecting array.\n")); + return JSON_ERR_NOARRAY; + } + substatus = json_read_array(cp, &cursor->addr.array, &cp); + if (substatus != 0) + return substatus; + state = post_array; + } else if (cursor->type == t_array) { + json_debug_trace((1, + "Array element was specified, but no [.\n")); + return JSON_ERR_NOBRAK; + } else if (*cp == '"') { + value_quoted = true; + state = in_val_string; + pval = valbuf; + } else { + value_quoted = false; + state = in_val_token; + pval = valbuf; + *pval++ = *cp; + } + break; + case in_val_string: + if (pval == NULL) + return JSON_ERR_NULLPTR; + if (*cp == '\\') + state = in_escape; + else if (*cp == '"') { + *pval++ = '\0'; + json_debug_trace((1, "Collected string value %s\n", valbuf)); + state = post_val; + } else if (pval > valbuf + JSON_VAL_MAX - 1 + || pval > valbuf + maxlen) { + json_debug_trace((1, "String value too long.\n")); + return JSON_ERR_STRLONG; /* */ + } else + *pval++ = *cp; + break; + case in_escape: + if (pval == NULL) + return JSON_ERR_NULLPTR; + switch (*cp) { + case 'b': + *pval++ = '\b'; + break; + case 'f': + *pval++ = '\f'; + break; + case 'n': + *pval++ = '\n'; + break; + case 'r': + *pval++ = '\r'; + break; + case 't': + *pval++ = '\t'; + break; + case 'u': + for (n = 0; n < 4 && cp[n] != '\0'; n++) + uescape[n] = *cp++; + --cp; + (void)sscanf(uescape, "%04x", &u); + *pval++ = (char)u; /* will truncate values above 0xff */ + break; + default: /* handles double quote and solidus */ + *pval++ = *cp; + break; + } + state = in_val_string; + break; + case in_val_token: + if (pval == NULL) + return JSON_ERR_NULLPTR; + if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') { + *pval = '\0'; + json_debug_trace((1, "Collected token value %s.\n", valbuf)); + state = post_val; + if (*cp == '}' || *cp == ',') + --cp; + } else if (pval > valbuf + JSON_VAL_MAX - 1) { + json_debug_trace((1, "Token value too long.\n")); + return JSON_ERR_TOKLONG; + } else + *pval++ = *cp; + break; + case post_val: + /* + * We know that cursor points at the first spec matching + * the current attribute. We don't know that it's *the* + * correct spec; our dialect allows there to be any number + * of adjacent ones with the same attrname but different + * types. Here's where we try to seek forward for a + * matching type/attr pair if we're not looking at one. + */ + for (;;) { + int seeking = cursor->type; + if (value_quoted && (cursor->type == t_string || cursor->type == t_time)) + break; + if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0) + && seeking == t_boolean) + break; + if (isdigit((unsigned char) valbuf[0])) { + bool decimal = strchr(valbuf, '.') != NULL; + if (decimal && seeking == t_real) + break; + if (!decimal && (seeking == t_integer || seeking == t_uinteger)) + break; + } + if (cursor[1].attribute==NULL) /* out of possiblities */ + break; + if (strcmp(cursor[1].attribute, attrbuf)!=0) + break; + ++cursor; + } + if (value_quoted + && (cursor->type != t_string && cursor->type != t_character + && cursor->type != t_check && cursor->type != t_time + && cursor->type != t_ignore && cursor->map == 0)) { + json_debug_trace((1, + "Saw quoted value when expecting non-string.\n")); + return JSON_ERR_QNONSTRING; + } + if (!value_quoted + && (cursor->type == t_string || cursor->type == t_check + || cursor->type == t_time || cursor->map != 0)) { + json_debug_trace((1, + "Didn't see quoted value when expecting string.\n")); + return JSON_ERR_NONQSTRING; + } + if (cursor->map != 0) { + for (mp = cursor->map; mp->name != NULL; mp++) + if (strcmp(mp->name, valbuf) == 0) { + goto foundit; + } + json_debug_trace((1, "Invalid enumerated value string %s.\n", + valbuf)); + return JSON_ERR_BADENUM; + foundit: + (void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value); + } + lptr = json_target_address(cursor, parent, offset); + if (lptr != NULL) + switch (cursor->type) { + case t_integer: + { + int tmp = atoi(valbuf); + memcpy(lptr, &tmp, sizeof(int)); + } + break; + case t_uinteger: + { + unsigned int tmp = (unsigned int)atoi(valbuf); + memcpy(lptr, &tmp, sizeof(unsigned int)); + } + break; + case t_time: + { + double tmp = iso8601_to_unix(valbuf); + memcpy(lptr, &tmp, sizeof(double)); + } + break; + case t_real: + { + double tmp = safe_atof(valbuf); + memcpy(lptr, &tmp, sizeof(double)); + } + break; + case t_string: + if (parent != NULL + && parent->element_type != t_structobject + && offset > 0) + return JSON_ERR_NOPARSTR; + (void)strncpy(lptr, valbuf, cursor->len); + valbuf[sizeof(valbuf)-1] = '\0'; + break; + case t_boolean: + { + bool tmp = (strcmp(valbuf, "true") == 0); + memcpy(lptr, &tmp, sizeof(bool)); + } + break; + case t_character: + if (strlen(valbuf) > 1) + return JSON_ERR_STRLONG; + else + lptr[0] = valbuf[0]; + break; + case t_ignore: /* silences a compiler warning */ + case t_object: /* silences a compiler warning */ + case t_structobject: + case t_array: + break; + case t_check: + if (strcmp(cursor->dflt.check, valbuf) != 0) { + json_debug_trace((1, + "Required attribute value %s not present.\n", + cursor->dflt.check)); + return JSON_ERR_CHECKFAIL; + } + break; + } + /*@fallthrough@*/ + case post_array: + if (isspace((unsigned char) *cp)) + continue; + else if (*cp == ',') + state = await_attr; + else if (*cp == '}') { + ++cp; + goto good_parse; + } else { + json_debug_trace((1, "Garbage while expecting comma or }\n")); + return JSON_ERR_BADTRAIL; + } + break; + } + } + + good_parse: + /* in case there's another object following, consume trailing WS */ + while (isspace((unsigned char) *cp)) + ++cp; + if (end != NULL) + *end = cp; + json_debug_trace((1, "JSON parse ends.\n")); + return 0; + /*@ +nullstate +nullderef +mustfreefresh +nullpass +usedef @*/ +} + +int json_read_array(const char *cp, const struct json_array_t *arr, + const char **end) +{ + /*@-nullstate -onlytrans@*/ + int substatus, offset, arrcount; + char *tp; + + if (end != NULL) + *end = NULL; /* give it a well-defined value on parse failure */ + + json_debug_trace((1, "Entered json_read_array()\n")); + + while (isspace((unsigned char) *cp)) + cp++; + if (*cp != '[') { + json_debug_trace((1, "Didn't find expected array start\n")); + return JSON_ERR_ARRAYSTART; + } else + cp++; + + tp = arr->arr.strings.store; + arrcount = 0; + + /* Check for empty array */ + while (isspace((unsigned char) *cp)) + cp++; + if (*cp == ']') + goto breakout; + + for (offset = 0; offset < arr->maxlen; offset++) { + char *ep = NULL; + json_debug_trace((1, "Looking at %s\n", cp)); + switch (arr->element_type) { + case t_string: + if (isspace((unsigned char) *cp)) + cp++; + if (*cp != '"') + return JSON_ERR_BADSTRING; + else + ++cp; + arr->arr.strings.ptrs[offset] = tp; + for (; tp - arr->arr.strings.store < arr->arr.strings.storelen; + tp++) + if (*cp == '"') { + ++cp; + *tp++ = '\0'; + goto stringend; + } else if (*cp == '\0') { + json_debug_trace((1, + "Bad string syntax in string list.\n")); + return JSON_ERR_BADSTRING; + } else { + *tp = *cp++; + } + json_debug_trace((1, "Bad string syntax in string list.\n")); + return JSON_ERR_BADSTRING; + stringend: + break; + case t_object: + case t_structobject: + substatus = + json_internal_read_object(cp, arr->arr.objects.subtype, arr, + offset, &cp); + if (substatus != 0) + return substatus; + break; + case t_integer: + arr->arr.integers.store[offset] = (int)strtol(cp, &ep, 0); + if (ep == cp) + return JSON_ERR_BADNUM; + else + cp = ep; + break; + case t_uinteger: + arr->arr.uintegers.store[offset] = (unsigned int)strtoul(cp, &ep, 0); + if (ep == cp) + return JSON_ERR_BADNUM; + else + cp = ep; + break; + case t_real: + arr->arr.reals.store[offset] = strtod(cp, &ep); + if (ep == cp) + return JSON_ERR_BADNUM; + else + cp = ep; + break; + case t_boolean: + if (strncmp(cp, "true", 4) == 0) { + arr->arr.booleans.store[offset] = true; + cp += 4; + } + else if (strncmp(cp, "false", 5) == 0) { + arr->arr.booleans.store[offset] = false; + cp += 5; + } + break; + case t_time: + case t_character: + case t_array: + case t_check: + case t_ignore: + json_debug_trace((1, "Invalid array subtype.\n")); + return JSON_ERR_SUBTYPE; + } + arrcount++; + if (isspace((unsigned char) *cp)) + cp++; + if (*cp == ']') { + json_debug_trace((1, "End of array found.\n")); + goto breakout; + } else if (*cp == ',') + cp++; + else { + json_debug_trace((1, "Bad trailing syntax on array.\n")); + return JSON_ERR_BADSUBTRAIL; + } + } + json_debug_trace((1, "Too many elements in array.\n")); + return JSON_ERR_SUBTOOLONG; + breakout: + if (arr->count != NULL) + *(arr->count) = arrcount; + if (end != NULL) + *end = cp; + /*@ -nullderef @*/ + json_debug_trace((1, "leaving json_read_array() with %d elements\n", + arrcount)); + /*@ +nullderef @*/ + return 0; + /*@+nullstate +onlytrans@*/ +} + +int json_read_object(const char *cp, const struct json_attr_t *attrs, + /*@null@*/ const char **end) +{ + int st; + + json_debug_trace((1, "json_read_object() sees '%s'\n", cp)); + st = json_internal_read_object(cp, attrs, NULL, 0, end); + return st; +} + +const /*@observer@*/ char *json_error_string(int err) +{ + const char *errors[] = { + "unknown error while parsing JSON", + "non-whitespace when expecting object start", + "non-whitespace when expecting attribute start", + "unknown attribute name", + "attribute name too long", + "saw [ when not expecting array", + "array element specified, but no [", + "string value too long", + "token value too long", + "garbage while expecting comma or } or ]", + "didn't find expected array start", + "error while parsing object array", + "too many array elements", + "garbage while expecting array comma", + "unsupported array element type", + "error while string parsing", + "check attribute not matched", + "can't support strings in parallel arrays", + "invalid enumerated value", + "saw quoted value when expecting nonstring", + "didn't see quoted value when expecting string", + "other data conversion error", + "unexpected null value or attribute pointer", + }; + + if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0]))) + return errors[0]; + else + return errors[err]; +} + +/* end */ + diff --git a/mjson.h b/mjson.h new file mode 100644 index 0000000..8323d95 --- /dev/null +++ b/mjson.h @@ -0,0 +1,139 @@ +/* Structures for JSON parsing using only fixed-extent memory + * + * This file is Copyright (c) 2014 by Eric S. Raymond. + * BSD terms apply: see the file COPYING in the distribution root for details. + */ + +#include +#include + +#define NITEMS(x) (int)(sizeof(x)/sizeof(x[0])) + +typedef enum {t_integer, t_uinteger, t_real, + t_string, t_boolean, t_character, + t_time, + t_object, t_structobject, t_array, + t_check, t_ignore} + json_type; + +struct json_enum_t { + char *name; + int value; +}; + +struct json_array_t { + json_type element_type; + union { + struct { + const struct json_attr_t *subtype; + char *base; + size_t stride; + } objects; + struct { + char **ptrs; + char *store; + int storelen; + } strings; + struct { + int *store; + } integers; + struct { + unsigned int *store; + } uintegers; + struct { + double *store; + } reals; + struct { + bool *store; + } booleans; + } arr; + int *count, maxlen; +}; + +struct json_attr_t { + char *attribute; + json_type type; + union { + int *integer; + unsigned int *uinteger; + double *real; + char *string; + bool *boolean; + char *character; + struct json_array_t array; + size_t offset; + } addr; + union { + int integer; + unsigned int uinteger; + double real; + bool boolean; + char character; + char *check; + } dflt; + size_t len; + const struct json_enum_t *map; + bool nodefault; +}; + +#define JSON_ATTR_MAX 31 /* max chars in JSON attribute name */ +#define JSON_VAL_MAX 512 /* max chars in JSON value part */ + +#ifdef __cplusplus +extern "C" { +#endif +int json_read_object(const char *, const struct json_attr_t *, + /*@null@*/const char **); +int json_read_array(const char *, const struct json_array_t *, + /*@null@*/const char **); +const /*@observer@*/char *json_error_string(int); + +void json_enable_debug(int, FILE *); +#ifdef __cplusplus +} +#endif + +#define JSON_ERR_OBSTART 1 /* non-WS when expecting object start */ +#define JSON_ERR_ATTRSTART 2 /* non-WS when expecting attrib start */ +#define JSON_ERR_BADATTR 3 /* unknown attribute name */ +#define JSON_ERR_ATTRLEN 4 /* attribute name too long */ +#define JSON_ERR_NOARRAY 5 /* saw [ when not expecting array */ +#define JSON_ERR_NOBRAK 6 /* array element specified, but no [ */ +#define JSON_ERR_STRLONG 7 /* string value too long */ +#define JSON_ERR_TOKLONG 8 /* token value too long */ +#define JSON_ERR_BADTRAIL 9 /* garbage while expecting comma or } or ] */ +#define JSON_ERR_ARRAYSTART 10 /* didn't find expected array start */ +#define JSON_ERR_OBJARR 11 /* error while parsing object array */ +#define JSON_ERR_SUBTOOLONG 12 /* too many array elements */ +#define JSON_ERR_BADSUBTRAIL 13 /* garbage while expecting array comma */ +#define JSON_ERR_SUBTYPE 14 /* unsupported array element type */ +#define JSON_ERR_BADSTRING 15 /* error while string parsing */ +#define JSON_ERR_CHECKFAIL 16 /* check attribute not matched */ +#define JSON_ERR_NOPARSTR 17 /* can't support strings in parallel arrays */ +#define JSON_ERR_BADENUM 18 /* invalid enumerated value */ +#define JSON_ERR_QNONSTRING 19 /* saw quoted value when expecting nonstring */ +#define JSON_ERR_NONQSTRING 19 /* didn't see quoted value when expecting string */ +#define JSON_ERR_MISC 20 /* other data conversion error */ +#define JSON_ERR_BADNUM 21 /* error while parsing a numerical argument */ +#define JSON_ERR_NULLPTR 22 /* unexpected null value or attribute pointer */ + +/* + * Use the following macros to declare template initializers for structobject + * arrays. Writing the equivalents out by hand is error-prone. + * + * STRUCTOBJECT takes a structure name s, and a fieldname f in s. + * + * STRUCTARRAY takes the name of a structure array, a pointer to a an + * initializer defining the subobject type, and the address of an integer to + * store the length in. + */ +#define STRUCTOBJECT(s, f) .addr.offset = offsetof(s, f) +#define STRUCTARRAY(a, e, n) \ + .addr.array.element_type = t_structobject, \ + .addr.array.arr.objects.subtype = e, \ + .addr.array.arr.objects.base = (char*)a, \ + .addr.array.arr.objects.stride = sizeof(a[0]), \ + .addr.array.count = n, \ + .addr.array.maxlen = (int)(sizeof(a)/sizeof(a[0])) + +/* json.h ends here */ diff --git a/test_microjson.c b/test_microjson.c index 70c9cb2..5b73ec6 100644 --- a/test_microjson.c +++ b/test_microjson.c @@ -1,4 +1,4 @@ -/* test_microjson.c - unit test for JSON parsing into fixed-extent structures +/* test_mjson.c - unit test for JSON parsing into fixed-extent structures * * This file is Copyright (c) 2010 by the GPSD project * BSD terms apply: see the file COPYING in the distribution root for details. @@ -17,7 +17,7 @@ #include #include -#include "microjson.h" +#include "mjson.h" /* * Many of these structures and examples were dissected out of the GPSD code.