aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/smugfs/json.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/smugfs/json.c')
-rw-r--r--src/cmd/smugfs/json.c555
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);
+}