]> git.ipfire.org Git - thirdparty/libsolv.git/commitdiff
Add a small json parser
authorMichael Schroeder <mls@suse.de>
Fri, 23 Nov 2018 13:19:16 +0000 (14:19 +0100)
committerMichael Schroeder <mls@suse.de>
Fri, 23 Nov 2018 13:19:16 +0000 (14:19 +0100)
It's always good to have a json parser...

ext/solv_jsonparser.c [new file with mode: 0644]
ext/solv_jsonparser.h [new file with mode: 0644]

diff --git a/ext/solv_jsonparser.c b/ext/solv_jsonparser.c
new file mode 100644 (file)
index 0000000..053ee6f
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * solv_jsonparser.c
+ *
+ * Simple JSON stream parser
+ *
+ * Copyright (c) 2018, SUSE LLC
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+#include "solv_jsonparser.h"
+
+struct solv_jsonparser *
+jsonparser_init(struct solv_jsonparser *jp, FILE *fp)
+{
+  memset(jp, 0, sizeof(*jp));
+  jp->fp = fp;
+  jp->state = JP_START;
+  jp->line = jp->nextline = 1;
+  jp->nextc = ' ';
+  queue_init(&jp->stateq);
+  return jp;
+}
+
+struct solv_jsonparser *
+jsonparser_free(struct solv_jsonparser *jp)
+{
+  queue_free(&jp->stateq);
+  return 0;
+}
+
+static void
+savec(struct solv_jsonparser *jp, char c)
+{
+  if (jp->nspace == jp->aspace)
+    {
+      jp->aspace += 256;
+      jp->space = solv_realloc(jp->space, jp->aspace);
+    }
+  jp->space[jp->nspace++] = c;
+}
+
+static void
+saveutf8(struct solv_jsonparser *jp, int c)
+{
+  int i;
+  if (c < 0x80)
+    {
+      savec(jp, c);
+      return;
+    }
+  i = c < 0x800 ? 1 : c < 0x10000 ? 2 : 3;
+  savec(jp, (0x1f80 >> i) | (c >> (6 * i)));
+  while (--i >= 0)
+    savec(jp, 0x80 | ((c >> (6 * i)) & 0x3f));
+}
+
+static inline int
+nextc(struct solv_jsonparser *jp)
+{
+  int c = getc(jp->fp);
+  if (c == '\n')
+    jp->nextline++;
+  return c;
+}
+
+static int
+skipspace(struct solv_jsonparser *jp)
+{
+  int c = jp->nextc;
+  while (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+    c = nextc(jp);
+  jp->line = jp->nextline;
+  return (jp->nextc = c);
+}
+
+static int
+parseliteral(struct solv_jsonparser *jp)
+{
+  size_t nspace = jp->nspace;
+  int c;
+  savec(jp, jp->nextc);
+  for (;;)
+    {
+      c = nextc(jp);
+      if (c < 'a' || c > 'z')
+       break;
+      savec(jp, c);
+    }
+  jp->nextc = c;
+  savec(jp, 0);
+  if (!strcmp(jp->space + nspace, "true"))
+    return JP_BOOL;
+  if (!strcmp(jp->space + nspace, "false"))
+    return JP_BOOL;
+  if (!strcmp(jp->space + nspace, "null"))
+    return JP_NULL;
+  return JP_ERROR;
+}
+
+static int
+parsenumber(struct solv_jsonparser *jp)
+{
+  int c;
+  savec(jp, jp->nextc);
+  for (;;)
+    {
+      c = nextc(jp);
+      if ((c < '0' || c > '9') && c != '+' && c != '-' && c != '.' && c != 'e' && c != 'E')
+       break;
+      savec(jp, c);
+    }
+  jp->nextc = c;
+  savec(jp, 0);
+  return JP_NUMBER;
+}
+
+static int
+parseutf8(struct solv_jsonparser *jp, int surrogate)
+{
+  int c, i, r = 0;
+  /* parse 4-digit hex */
+  for (i = 0; i < 4; i++)
+    {
+      c = nextc(jp);
+      if (c >= '0' && c <= '9')
+       c -= '0';
+      else if (c >= 'a' && c <= 'f')
+       c -= 'a' - 10;
+      else if (c >= 'A' && c <= 'F')
+       c -= 'A' - 10;
+      else
+       return -1;
+      r = (r << 4) | c;
+    }
+  if (!surrogate && r >= 0xd800 && r < 0xdc00)
+    {
+      /* utf16 surrogate pair encodes 0x10000 - 0x10ffff */
+      int r2;
+      if (nextc(jp) != '\\' || nextc(jp) != 'u' || (r2 = parseutf8(jp, 1)) < 0xdc00 || r2 >= 0xe000)
+       return -1;
+      r = 0x10000 + ((r & 0x3ff) << 10 | (r2 & 0x3ff));
+    }
+  return r;
+}
+
+static int
+parsestring(struct solv_jsonparser *jp)
+{
+  int c;
+  for (;;)
+    {
+      if ((c = nextc(jp)) < 32)
+       return JP_ERROR;
+      if (c == '"')
+       break;
+      if (c == '\\')
+       {
+         switch (c = nextc(jp))
+           {
+           case '"':
+           case '\\':
+           case '/':
+           case '\n':
+             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':
+             if ((c = parseutf8(jp, 0)) < 0)
+               return JP_ERROR;
+             saveutf8(jp, c);
+             continue;
+           default:
+             return JP_ERROR;
+           }
+       }
+      savec(jp, c);
+    }
+  jp->nextc = ' ';
+  savec(jp, 0);
+  return JP_STRING;
+}
+
+static int
+parsevalue(struct solv_jsonparser *jp)
+{
+  int c = skipspace(jp);
+  if (c == '"')
+    return parsestring(jp);
+  if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')
+    return parsenumber(jp);
+  if ((c >= 'a' && c <= 'z'))
+    return parseliteral(jp);
+  jp->nextc = ' ';
+  if (c == '[')
+    return JP_ARRAY;
+  if (c == '{')
+    return JP_OBJECT;
+  if (c == ']')
+    return JP_ARRAY_END;
+  if (c == '}')
+    return JP_OBJECT_END;
+  return JP_ERROR;
+}
+
+int
+jsonparser_parse(struct solv_jsonparser *jp)
+{
+  int type;
+  size_t nspace;
+
+  jp->depth = jp->stateq.count;
+  jp->key = jp->value = 0;
+  jp->keylen = jp->valuelen = 0;
+  nspace = jp->nspace = 0;
+
+  if (jp->state == JP_END)
+    return JP_END;
+  if (jp->state == JP_START)
+    jp->state = JP_END;
+  type = parsevalue(jp);
+  if (type <= 0)
+    return JP_ERROR;
+  if (type == JP_OBJECT_END || type == JP_ARRAY_END)
+    {
+      if (jp->state != type - 1)
+        return JP_ERROR;
+      jp->state = queue_pop(&jp->stateq);
+    }
+  else if (jp->state == JP_OBJECT)
+    {
+      nspace = jp->nspace;
+      if (type != JP_STRING)
+       return JP_ERROR;
+      if (skipspace(jp) != ':')
+       return JP_ERROR;
+      jp->nextc = ' ';
+      type = parsevalue(jp);
+      if (type == JP_OBJECT_END || type == JP_ARRAY_END)
+       return JP_ERROR;
+      jp->key = jp->space;
+      jp->keylen = nspace - 1;
+    }
+  if (type == JP_STRING || type == JP_NUMBER || type == JP_BOOL || type == JP_NULL)
+    {
+      jp->value = jp->space + nspace;
+      jp->valuelen = jp->nspace - nspace - 1;
+    }
+  if (type == JP_ARRAY || type == JP_OBJECT)
+    {
+      queue_push(&jp->stateq, jp->state);
+      jp->state = type;
+    }
+  else if (jp->state == JP_OBJECT || jp->state == JP_ARRAY)
+    {
+      int c = skipspace(jp);
+      if (c == ',')
+       jp->nextc = ' ';
+      else if (c != (jp->state == JP_OBJECT ? '}' : ']'))
+       return JP_ERROR;
+    }
+  return type;
+}
+
+int
+jsonparser_skip(struct solv_jsonparser *jp, int type)
+{
+  if (type == JP_ARRAY || type == JP_OBJECT)
+    {
+      int depth = jp->depth + 1, endtype = type + 1;
+      while (type > 0 && (type != endtype || jp->depth != depth))
+        type = jsonparser_parse(jp);
+    }
+  return type;
+}
+
diff --git a/ext/solv_jsonparser.h b/ext/solv_jsonparser.h
new file mode 100644 (file)
index 0000000..d58d9e3
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018, SUSE LLC
+ *
+ * This program is licensed under the BSD license, read LICENSE.BSD
+ * for further information
+ */
+
+#ifndef SOLV_JSONPARSER_H
+#define SOLV_JSONPARSER_H
+
+#include "queue.h"
+
+struct solv_jsonparser {
+  FILE *fp;
+  int line;
+  int depth;
+
+  char *key;
+  size_t keylen;
+  char *value;
+  size_t valuelen;
+
+  int state;
+  Queue stateq;
+  int nextc;
+  int nextline;
+  char *space;
+  size_t nspace;
+  size_t aspace;
+};
+
+#define JP_ERROR       -1
+#define JP_END         0
+#define JP_START       1
+#define JP_STRING      2
+#define JP_NUMBER      3
+#define JP_BOOL                4
+#define JP_NULL                5
+#define JP_OBJECT      6
+#define JP_OBJECT_END  7
+#define JP_ARRAY       8
+#define JP_ARRAY_END   9
+
+struct solv_jsonparser *jsonparser_init(struct solv_jsonparser *jp, FILE *fp);
+struct solv_jsonparser *jsonparser_free(struct solv_jsonparser *jp);
+int jsonparser_parse(struct solv_jsonparser *jp);
+int jsonparser_skip(struct solv_jsonparser *jp, int type);
+
+#endif /* SOLV_JSONPARSER_H */