diff options
Diffstat (limited to 'src/cmd/smugfs/json.c')
-rw-r--r-- | src/cmd/smugfs/json.c | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/src/cmd/smugfs/json.c b/src/cmd/smugfs/json.c new file mode 100644 index 00000000..d6472b4d --- /dev/null +++ b/src/cmd/smugfs/json.c @@ -0,0 +1,555 @@ +#include "a.h" + +static Json *parsevalue(char**); + +static char* +wskip(char *p) +{ + while(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\v') + p++; + return p; +} + +static int +ishex(int c) +{ + return '0' <= c && c <= '9' || + 'a' <= c && c <= 'f' || + 'A' <= c && c <= 'F'; +} + +static Json* +newjval(int type) +{ + Json *v; + + v = emalloc(sizeof *v); + v->ref = 1; + v->type = type; + return v; +} + +static Json* +badjval(char **pp, char *fmt, ...) +{ + char buf[ERRMAX]; + va_list arg; + + if(fmt){ + va_start(arg, fmt); + vsnprint(buf, sizeof buf, fmt, arg); + va_end(arg); + errstr(buf, sizeof buf); + } + *pp = nil; + return nil; +} + +static char* +_parsestring(char **pp, int *len) +{ + char *p, *q, *w, *s, *r; + char buf[5]; + Rune rune; + + p = wskip(*pp); + if(*p != '"'){ + badjval(pp, "missing opening quote for string"); + return nil; + } + for(q=p+1; *q && *q != '\"'; q++){ + if(*q == '\\' && *(q+1) != 0) + q++; + if((*q & 0xFF) < 0x20){ // no control chars + badjval(pp, "control char in string"); + return nil; + } + } + if(*q == 0){ + badjval(pp, "no closing quote in string"); + return nil; + } + s = emalloc(q - p); + w = s; + for(r=p+1; r<q; ){ + if(*r != '\\'){ + *w++ = *r++; + continue; + } + r++; + switch(*r){ + default: + free(s); + badjval(pp, "bad escape \\%c in string", *r&0xFF); + return nil; + case '\\': + case '\"': + case '/': + *w++ = *r++; + break; + case 'b': + *w++ = '\b'; + r++; + break; + case 'f': + *w++ = '\f'; + r++; + break; + case 'n': + *w++ = '\n'; + r++; + break; + case 'r': + *w++ = '\r'; + r++; + break; + case 't': + *w++ = '\t'; + r++; + break; + case 'u': + r++; + if(!ishex(r[0]) || !ishex(r[1]) || !ishex(r[2]) || !ishex(r[3])){ + free(s); + badjval(pp, "bad hex \\u%.4s", r); + return nil; + } + memmove(buf, r, 4); + buf[4] = 0; + rune = strtol(buf, 0, 16); + if(rune == 0){ + free(s); + badjval(pp, "\\u0000 in string"); + return nil; + } + r += 4; + w += runetochar(w, &rune); + break; + } + } + *w = 0; + if(len) + *len = w - s; + *pp = q+1; + return s; +} + +static Json* +parsenumber(char **pp) +{ + char *p, *q; + char *t; + double d; + Json *v; + + /* -?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([Ee][-+]?[0-9]+) */ + p = wskip(*pp); + q = p; + if(*q == '-') + q++; + if(*q == '0') + q++; + else{ + if(*q < '1' || *q > '9') + return badjval(pp, "invalid number"); + while('0' <= *q && *q <= '9') + q++; + } + if(*q == '.'){ + q++; + if(*q < '0' || *q > '9') + return badjval(pp, "invalid number"); + while('0' <= *q && *q <= '9') + q++; + } + if(*q == 'e' || *q == 'E'){ + q++; + if(*q == '-' || *q == '+') + q++; + if(*q < '0' || *q > '9') + return badjval(pp, "invalid number"); + while('0' <= *q && *q <= '9') + q++; + } + + t = emalloc(q-p+1); + memmove(t, p, q-p); + t[q-p] = 0; + errno = 0; + d = strtod(t, nil); + if(errno != 0){ + free(t); + return badjval(pp, nil); + } + free(t); + v = newjval(Jnumber); + v->number = d; + *pp = q; + return v; +} + +static Json* +parsestring(char **pp) +{ + char *s; + Json *v; + int len; + + s = _parsestring(pp, &len); + if(s == nil) + return nil; + v = newjval(Jstring); + v->string = s; + v->len = len; + return v; +} + +static Json* +parsename(char **pp) +{ + if(strncmp(*pp, "true", 4) == 0){ + *pp += 4; + return newjval(Jtrue); + } + if(strncmp(*pp, "false", 5) == 0){ + *pp += 5; + return newjval(Jfalse); + } + if(strncmp(*pp, "null", 4) == 0){ + *pp += 4; + return newjval(Jtrue); + } + return badjval(pp, "invalid name"); +} + +static Json* +parsearray(char **pp) +{ + char *p; + Json *v; + + p = *pp; + if(*p++ != '[') + return badjval(pp, "missing bracket for array"); + v = newjval(Jarray); + p = wskip(p); + if(*p != ']'){ + for(;;){ + if(v->len%32 == 0) + v->value = erealloc(v->value, (v->len+32)*sizeof v->value[0]); + if((v->value[v->len++] = parsevalue(&p)) == nil){ + jclose(v); + return badjval(pp, nil); + } + p = wskip(p); + if(*p == ']') + break; + if(*p++ != ','){ + jclose(v); + return badjval(pp, "missing comma in array"); + } + } + } + p++; + *pp = p; + return v; +} + +static Json* +parseobject(char **pp) +{ + char *p; + Json *v; + + p = *pp; + if(*p++ != '{') + return badjval(pp, "missing brace for object"); + v = newjval(Jobject); + p = wskip(p); + if(*p != '}'){ + for(;;){ + if(v->len%32 == 0){ + v->name = erealloc(v->name, (v->len+32)*sizeof v->name[0]); + v->value = erealloc(v->value, (v->len+32)*sizeof v->value[0]); + } + if((v->name[v->len++] = _parsestring(&p, nil)) == nil){ + jclose(v); + return badjval(pp, nil); + } + p = wskip(p); + if(*p++ != ':'){ + jclose(v); + return badjval(pp, "missing colon in object"); + } + if((v->value[v->len-1] = parsevalue(&p)) == nil){ + jclose(v); + return badjval(pp, nil); + } + p = wskip(p); + if(*p == '}') + break; + if(*p++ != ','){ + jclose(v); + return badjval(pp, "missing comma in object"); + } + } + } + p++; + *pp = p; + return v; +} + +static Json* +parsevalue(char **pp) +{ + *pp = wskip(*pp); + switch(**pp){ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return parsenumber(pp); + case 't': + case 'f': + case 'n': + return parsename(pp); + case '\"': + return parsestring(pp); + case '[': + return parsearray(pp); + case '{': + return parseobject(pp); + default: + return badjval(pp, "unexpected char <%02x>", **pp & 0xFF); + } +} + +Json* +parsejson(char *text) +{ + Json *v; + + v = parsevalue(&text); + if(v && text && *wskip(text) != 0){ + jclose(v); + werrstr("extra data in json"); + return nil; + } + return v; +} + +void +_printjval(Fmt *fmt, Json *v, int n) +{ + int i; + + if(v == nil){ + fmtprint(fmt, "nil"); + return; + } + switch(v->type){ + case Jstring: + fmtprint(fmt, "\"%s\"", v->string); + break; + case Jnumber: + if(floor(v->number) == v->number) + fmtprint(fmt, "%.0f", v->number); + else + fmtprint(fmt, "%g", v->number); + break; + case Jobject: + fmtprint(fmt, "{"); + if(n >= 0) + n++; + for(i=0; i<v->len; i++){ + if(n > 0) + fmtprint(fmt, "\n%*s", n*4, ""); + fmtprint(fmt, "\"%s\" : ", v->name[i]); + _printjval(fmt, v->value[i], n); + fmtprint(fmt, ","); + } + if(n > 0){ + n--; + if(v->len > 0) + fmtprint(fmt, "\n%*s", n*4); + } + fmtprint(fmt, "}"); + break; + case Jarray: + fmtprint(fmt, "["); + if(n >= 0) + n++; + for(i=0; i<v->len; i++){ + if(n > 0) + fmtprint(fmt, "\n%*s", n*4, ""); + _printjval(fmt, v->value[i], n); + fmtprint(fmt, ","); + } + if(n > 0){ + n--; + if(v->len > 0) + fmtprint(fmt, "\n%*s", n*4); + } + fmtprint(fmt, "]"); + break; + case Jtrue: + fmtprint(fmt, "true"); + break; + case Jfalse: + fmtprint(fmt, "false"); + break; + case Jnull: + fmtprint(fmt, "null"); + break; + } +} + +/* +void +printjval(Json *v) +{ + Fmt fmt; + char buf[256]; + + fmtfdinit(&fmt, 1, buf, sizeof buf); + _printjval(&fmt, v, 0); + fmtprint(&fmt, "\n"); + fmtfdflush(&fmt); +} +*/ + +int +jsonfmt(Fmt *fmt) +{ + Json *v; + + v = va_arg(fmt->args, Json*); + if(fmt->flags&FmtSharp) + _printjval(fmt, v, 0); + else + _printjval(fmt, v, -1); + return 0; +} + +Json* +jincref(Json *v) +{ + if(v == nil) + return nil; + ++v->ref; + return v; +} + +void +jclose(Json *v) +{ + int i; + + if(v == nil) + return; + if(--v->ref > 0) + return; + if(v->ref < 0) + sysfatal("jclose: ref %d", v->ref); + + switch(v->type){ + case Jstring: + free(v->string); + break; + case Jarray: + for(i=0; i<v->len; i++) + jclose(v->value[i]); + free(v->value); + break; + case Jobject: + for(i=0; i<v->len; i++){ + free(v->name[i]); + jclose(v->value[i]); + } + free(v->value); + free(v->name); + break; + } + free(v); +} + +Json* +jlookup(Json *v, char *name) +{ + int i; + + if(v->type != Jobject) + return nil; + for(i=0; i<v->len; i++) + if(strcmp(v->name[i], name) == 0) + return v->value[i]; + return nil; +} + +Json* +jwalk(Json *v, char *path) +{ + char elem[128], *p, *next; + int n; + + for(p=path; *p && v; p=next){ + next = strchr(p, '/'); + if(next == nil) + next = p+strlen(p); + if(next-p >= sizeof elem) + sysfatal("jwalk path elem too long - %s", path); + memmove(elem, p, next-p); + elem[next-p] = 0; + if(*next == '/') + next++; + if(v->type == Jarray && *elem && (n=strtol(elem, &p, 10)) >= 0 && *p == 0){ + if(n >= v->len) + return nil; + v = v->value[n]; + }else + v = jlookup(v, elem); + } + return v; +} + +char* +jstring(Json *jv) +{ + if(jv == nil || jv->type != Jstring) + return nil; + return jv->string; +} + +vlong +jint(Json *jv) +{ + if(jv == nil || jv->type != Jnumber) + return -1; + return jv->number; +} + +double +jnumber(Json *jv) +{ + if(jv == nil || jv->type != Jnumber) + return 0; + return jv->number; +} + +int +jstrcmp(Json *jv, char *s) +{ + char *t; + + t = jstring(jv); + if(t == nil) + return -2; + return strcmp(t, s); +} |