| #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); |
| } |