[gtk/wip/otte/json: 1/3] Add GtkJsonParser




commit 967ce70d4bfea2073e3374608c6db15e9a8ecac0
Author: Benjamin Otte <otte redhat com>
Date:   Wed Nov 24 13:01:55 2021 +0100

    Add GtkJsonParser

 gtk/css/gtkjsonparser.c        | 923 +++++++++++++++++++++++++++++++++++++++++
 gtk/css/gtkjsonparserprivate.h |  80 ++++
 gtk/css/meson.build            |   3 +-
 3 files changed, 1005 insertions(+), 1 deletion(-)
---
diff --git a/gtk/css/gtkjsonparser.c b/gtk/css/gtkjsonparser.c
new file mode 100644
index 0000000000..db42f35874
--- /dev/null
+++ b/gtk/css/gtkjsonparser.c
@@ -0,0 +1,923 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#include "config.h"
+
+#include "gtkjsonparserprivate.h"
+
+typedef struct _GtkJsonBlock GtkJsonBlock;
+typedef struct _GtkJsonReader GtkJsonReader;
+
+struct _GtkJsonReader
+{
+  const guchar *data;
+  const guchar *end;
+
+  gsize lines;
+  const guchar *line_start;
+};
+
+typedef enum {
+  GTK_JSON_BLOCK_TOPLEVEL,
+  GTK_JSON_BLOCK_OBJECT,
+  GTK_JSON_BLOCK_ARRAY,
+} GtkJsonBlockType;
+
+struct _GtkJsonBlock
+{
+  GtkJsonBlockType type;
+  const guchar *value; /* start of current value to be consumed by external code */
+  const guchar *member_name; /* name of current value, only used for object types */
+  gsize index; /* index of the current element */
+};
+
+struct _GtkJsonParser
+{
+  int ref_count;
+
+  GBytes *bytes;
+  GtkJsonReader reader; /* current read head, pointing as far as we've read */
+
+  GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */
+
+  GtkJsonBlock *block; /* current block */
+  GtkJsonBlock *blocks; /* blocks array */
+  GtkJsonBlock *blocks_end; /* blocks array */
+  GtkJsonBlock blocks_preallocated[128]; /* preallocated */
+};
+
+static void
+gtk_json_set_syntax_error (GError     **error,
+                           const char  *format,
+                           ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_set_syntax_error (GError     **error,
+                           const char  *format,
+                           ...)
+{
+  va_list args;
+
+  if (error == NULL)
+    return;
+
+  va_start (args, format);
+  *error = g_error_new_valist (G_FILE_ERROR,
+                               G_FILE_ERROR_FAILED,
+                               format, args);
+  va_end (args);
+}
+
+static void
+gtk_json_parser_value_error (GtkJsonParser *self,
+                             const char  *format,
+                             ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_value_error (GtkJsonParser *self,
+                             const char  *format,
+                             ...)
+{
+  va_list args;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  self->error = g_error_new_valist (G_FILE_ERROR,
+                                    G_FILE_ERROR_FAILED,
+                                    format, args);
+  va_end (args);
+}
+
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...)
+{
+  va_list args;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  self->error = g_error_new_valist (G_FILE_ERROR,
+                                    G_FILE_ERROR_FAILED,
+                                    format, args);
+  va_end (args);
+}
+
+static void
+gtk_json_reader_init (GtkJsonReader *reader,
+                      const guchar  *data,
+                      gsize          size)
+{
+  reader->data = data;
+  reader->end = data + size;
+  reader->lines = 0;
+  reader->line_start = data;
+}
+
+static gboolean
+gtk_json_reader_is_eof (GtkJsonReader *reader)
+{
+  return reader->data >= reader->end || *reader->data == '\0';
+}
+
+static gsize
+gtk_json_reader_remaining (GtkJsonReader *reader)
+{
+  g_return_val_if_fail (reader->data <= reader->end, 0);
+
+  return reader->end - reader->data;
+}
+
+static void
+gtk_json_reader_skip_whitespace (GtkJsonReader *reader)
+{
+  while (reader->data < reader->end)
+    {
+      switch (*reader->data)
+        {
+        case ' ':
+        case '\t':
+          reader->data++;
+          break;
+        case '\r':
+          if (gtk_json_reader_remaining (reader) >= 2 && reader->data[1] == '\n')
+            reader->data++;
+          G_GNUC_FALLTHROUGH;
+        case '\n':
+          reader->data++;
+          reader->lines++;
+          reader->line_start = reader->data;
+          break;
+        default:
+          return;
+        }
+    }
+}
+
+static gboolean
+gtk_json_reader_has_char (GtkJsonReader *reader,
+                          char           c)
+{
+  return gtk_json_reader_remaining (reader) && *reader->data == c;
+}
+
+static gboolean
+gtk_json_reader_try_char (GtkJsonReader *reader,
+                          char           c)
+{
+  if (!gtk_json_reader_has_char (reader, c))
+    return FALSE;
+
+  reader->data++;
+  return TRUE;
+}
+
+static gboolean
+gtk_json_reader_try_identifier_len (GtkJsonReader *reader,
+                                    const char    *ident,
+                                    gsize          len)
+{
+  if (gtk_json_reader_remaining (reader) < len)
+    return FALSE;
+
+  if (memcmp (reader->data, ident, len) != 0)
+    return FALSE;
+
+  reader->data += len;
+  return TRUE;
+}
+
+#define gtk_json_reader_try_identifier(reader, ident) gtk_json_reader_try_identifier_len(reader, ident, 
strlen(ident))
+
+static gboolean
+gtk_json_reader_parse_string (GtkJsonReader  *reader,
+                              char          **out_string,
+                              GError        **error)
+{
+  GString *string;
+  const guchar *last;
+
+  if (!gtk_json_reader_try_char (reader, '"'))
+    {
+      gtk_json_set_syntax_error (error, "Not a string");
+      return FALSE;
+    }
+
+  if (out_string)
+    string = g_string_new (NULL);
+  else
+    string = NULL;
+
+  last = reader->data;
+
+  while (gtk_json_reader_remaining (reader))
+    {
+      switch (*reader->data)
+      {
+        case '\0':
+          goto end;
+
+        case '\r':
+          if (reader->data + 1 < reader->end && reader->data[1] == '\n')
+            reader->data++;
+          G_GNUC_FALLTHROUGH;
+        case '\n':
+          reader->data++;
+          reader->lines++;
+          reader->line_start = reader->data;
+          break;
+
+        case '"':
+          if (string)
+            {
+              g_string_append_len (string, (const char *) last, reader->data - last);
+              *out_string = g_string_free (string, FALSE);
+            }
+          reader->data++;
+          return TRUE;
+
+        case '\\':
+          if (gtk_json_reader_remaining (reader) < 2)
+            goto end;
+          if (string)
+            g_string_append_len (string, (const char *) last, reader->data - last);
+          reader->data++;
+          switch (*reader->data)
+            {
+            case '"':
+            case '\\':
+            case '/':
+              if (string)
+                g_string_append_c (string, *reader->data);
+              break;
+            case 'b':
+              if (string)
+                g_string_append_c (string, '\b');
+              break;
+            case 'f':
+              if (string)
+                g_string_append_c (string, '\f');
+              break;
+            case 'n':
+              if (string)
+                g_string_append_c (string, '\n');
+              break;
+            case 'r':
+              if (string)
+                g_string_append_c (string, '\r');
+              break;
+            case 't':
+              if (string)
+                g_string_append_c (string, '\t');
+              break;
+            case 'u':
+              if (gtk_json_reader_remaining (reader) < 5 ||
+                  !g_ascii_isxdigit (reader->data[1]) ||
+                  !g_ascii_isxdigit (reader->data[2]) ||
+                  !g_ascii_isxdigit (reader->data[3]) ||
+                  !g_ascii_isxdigit (reader->data[4]))
+                {
+                  if (string)
+                    g_string_free (string, TRUE);
+                  gtk_json_set_syntax_error (error, "Invalid Unicode escape sequence");
+                  return FALSE;
+                }
+              if (string)
+                g_string_append_unichar (string, 
+                                         (g_ascii_xdigit_value (reader->data[1]) << 24) |
+                                         (g_ascii_xdigit_value (reader->data[2]) << 16) |
+                                         (g_ascii_xdigit_value (reader->data[3]) << 8 ) |
+                                         (g_ascii_xdigit_value (reader->data[4])));
+              reader->data += 5;
+              break;
+            default:
+              if (string)
+                g_string_free (string, TRUE);
+              gtk_json_set_syntax_error (error, "Unknown escape sequence");
+              return FALSE;
+            }
+          last = reader->data + 1;
+          break;
+
+        default:
+          if (*reader->data < 0x20)
+            {
+              if (string)
+                g_string_free (string, TRUE);
+              gtk_json_set_syntax_error (error, "Disallowed control character in string literal");
+              return FALSE;
+            }
+          break;
+      }
+      reader->data++;
+    }
+
+end:
+  if (string)
+    g_string_free (string, TRUE);
+  gtk_json_set_syntax_error (error, "Unterminated string literal");
+  return FALSE;
+}
+
+static gboolean
+gtk_json_reader_parse_number (GtkJsonReader  *reader,
+                              GError        **error)
+{
+  /* sign */
+  gtk_json_reader_try_char (reader, '-');
+
+  /* integer part */
+  if (!gtk_json_reader_try_char (reader, '0'))
+    {
+      if (reader->data >= reader->end ||
+          !g_ascii_isalnum (*reader->data))
+        goto out;
+
+      reader->data++;
+
+      while (reader->data < reader->end && g_ascii_isalnum (*reader->data))
+        reader->data++;
+    }
+
+  /* fractional part */
+  if (gtk_json_reader_remaining (reader) >= 2 && *reader->data == '.' && g_ascii_isalnum (reader->data[1]))
+    {
+      reader->data += 2;
+
+      while (reader->data < reader->end && g_ascii_isalnum (*reader->data))
+        reader->data++;
+    }
+
+  /* exponent */
+  if (gtk_json_reader_remaining (reader) >= 2 && (reader->data[0] == 'e' || reader->data[0] == 'E') &&
+      (g_ascii_isalnum (reader->data[1]) ||
+       (gtk_json_reader_remaining (reader) >= 3 && (reader->data[1] == '+' || reader->data[1] == '-') && 
g_ascii_isalnum (reader->data[2]))))
+    {
+      reader->data += 2;
+
+      while (reader->data < reader->end && g_ascii_isalnum (*reader->data))
+        reader->data++;
+    }
+  return TRUE;
+
+out:
+  gtk_json_set_syntax_error (error, "Not a valid number");
+  return FALSE;
+}
+
+static void
+gtk_json_parser_push_block (GtkJsonParser    *self,
+                            GtkJsonBlockType  type)
+{
+  self->block++;
+  if (self->block == self->blocks_end)
+    {
+      gsize old_size = self->blocks_end - self->blocks;
+      gsize new_size = old_size + 128;
+
+      if (self->blocks == self->blocks_preallocated)
+        {
+          self->blocks = g_new (GtkJsonBlock, new_size);
+          memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS 
(self->blocks_preallocated));
+        }
+      else
+        {
+          self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
+        }
+      self->blocks_end = self->blocks + new_size;
+      self->block = self->blocks + old_size;
+    }
+
+  self->block->type = type;
+  self->block->member_name = 0;
+  self->block->value = 0;
+  self->block->index = 0;
+}
+                     
+static void
+gtk_json_parser_pop_block (GtkJsonParser *self)
+{
+  g_assert (self->block > self->blocks);
+  self->block--;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_string (const char *string,
+                                gssize      size)
+{
+  GtkJsonParser *self;
+  GBytes *bytes;
+
+  bytes = g_bytes_new (string, size >= 0 ? size : strlen (string));
+
+  self = gtk_json_parser_new_for_bytes (bytes);
+
+  g_bytes_unref (bytes);
+
+  return self;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_bytes (GBytes *bytes)
+{
+  GtkJsonParser *self;
+  const guchar *data;
+  gsize size;
+
+  g_return_val_if_fail (bytes != NULL, NULL);
+
+  self = g_slice_new0 (GtkJsonParser);
+  self->ref_count = 1;
+
+  self->bytes = g_bytes_ref (bytes);
+  data = g_bytes_get_data (bytes, &size);
+  gtk_json_reader_init (&self->reader, data, size);
+
+  self->blocks = self->blocks_preallocated;
+  self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
+  self->block = self->blocks;
+  self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
+
+  gtk_json_reader_skip_whitespace (&self->reader);
+  self->block->value = self->reader.data;
+
+  return self;
+}
+
+GtkJsonParser *
+gtk_json_parser_ref (GtkJsonParser *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  self->ref_count++;
+
+  return self;
+}
+
+void
+gtk_json_parser_unref (GtkJsonParser *self)
+{
+  if (self == NULL)
+    return;
+
+  self->ref_count -= 1;
+  if (self->ref_count > 0)
+    return;
+
+  g_bytes_unref (self->bytes);
+
+  if (self->blocks != self->blocks_preallocated)
+    g_free (self->blocks);
+
+  g_slice_free (GtkJsonParser, self);
+}
+
+static gboolean
+gtk_json_parser_has_skipped_value (GtkJsonParser *self)
+{
+  return self->reader.data != self->block->value;
+}
+
+static gboolean
+gtk_json_parser_skip_value (GtkJsonParser *self)
+{
+  /* read the value already */
+  if (gtk_json_parser_has_skipped_value (self))
+    return TRUE;
+
+  if (gtk_json_reader_is_eof (&self->reader))
+    {
+      gtk_json_parser_syntax_error (self, "Unexpected end of file");
+      return FALSE;
+    }
+
+  switch (*self->reader.data)
+  {
+    case '"':
+      return gtk_json_reader_parse_string (&self->reader, NULL, &self->error);
+    
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      return gtk_json_reader_parse_number (&self->reader, &self->error);
+
+    case 'f':
+      if (gtk_json_reader_try_identifier (&self->reader, "false"))
+        return TRUE;
+      break;
+
+    case 'n':
+      if (gtk_json_reader_try_identifier (&self->reader, "null"))
+        return TRUE;
+      break;
+
+    case 't':
+      if (gtk_json_reader_try_identifier (&self->reader, "true"))
+        return TRUE;
+      break;
+
+    case '{':
+      return gtk_json_parser_start_object (self) &&
+             gtk_json_parser_end (self);
+
+    case '[':
+      return gtk_json_parser_start_array (self) &&
+             gtk_json_parser_end (self);
+
+    default:
+      break;
+  }
+
+  gtk_json_parser_syntax_error (self, "Unexpected character in document");
+  return FALSE;
+}
+
+gboolean
+gtk_json_parser_next (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (gtk_json_reader_is_eof (&self->reader))
+    return TRUE;
+
+  if (!gtk_json_parser_skip_value (self))
+    return FALSE;
+
+  switch (self->block->type)
+    {
+    case GTK_JSON_BLOCK_TOPLEVEL:
+      gtk_json_reader_skip_whitespace (&self->reader);
+      if (gtk_json_reader_is_eof (&self->reader))
+        {
+          self->block->value = NULL;
+        }
+      else
+        {
+          gtk_json_parser_syntax_error (self, "Data at end of document");
+        }
+      return FALSE;
+
+    case GTK_JSON_BLOCK_OBJECT:
+      gtk_json_reader_skip_whitespace (&self->reader);
+      if (gtk_json_reader_is_eof (&self->reader) || gtk_json_reader_has_char (&self->reader, '}'))
+        {
+          self->block->member_name = NULL;
+          self->block->value = NULL;
+          return FALSE;
+        }
+      if (!gtk_json_reader_try_char (&self->reader, ','))
+        {
+          gtk_json_parser_syntax_error (self, "Expected a ',' to separate object members");
+          return FALSE;
+        }
+      gtk_json_reader_skip_whitespace (&self->reader);
+      self->block->member_name = self->reader.data;
+
+      if (!gtk_json_reader_parse_string (&self->reader, NULL, &self->error))
+        return FALSE;
+      gtk_json_reader_skip_whitespace (&self->reader);
+      if (!gtk_json_reader_try_char (&self->reader, ':'))
+        {
+          gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+          return FALSE;
+        }
+
+      gtk_json_reader_skip_whitespace (&self->reader);
+      self->block->value = self->reader.data;
+      break;
+
+    case GTK_JSON_BLOCK_ARRAY:
+      gtk_json_reader_skip_whitespace (&self->reader);
+      if (gtk_json_reader_is_eof (&self->reader) || gtk_json_reader_has_char (&self->reader, ']'))
+        {
+          self->block->value = NULL;
+          return FALSE;
+        }
+
+      if (!gtk_json_reader_try_char (&self->reader, ','))
+        {
+          gtk_json_parser_syntax_error (self, "Expected a ',' to separate array members");
+          return FALSE;
+        }
+
+      gtk_json_reader_skip_whitespace (&self->reader);
+      self->block->value = self->reader.data;
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return TRUE;
+}
+
+GtkJsonNode
+gtk_json_parser_get_node (GtkJsonParser *self)
+{
+  if (self->error)
+    return GTK_JSON_NONE;
+
+  if (self->block->value == NULL)
+    return GTK_JSON_NONE;
+
+  switch (*self->block->value)
+    {
+    case '"':
+      return GTK_JSON_STRING;
+    
+    case '{':
+      return GTK_JSON_OBJECT;
+
+    case '[':
+      return GTK_JSON_ARRAY;
+
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      return GTK_JSON_NUMBER;
+
+    case 't':
+    case 'f':
+      return GTK_JSON_BOOLEAN;
+
+    case 'n':
+      return GTK_JSON_NULL;
+
+    default:
+      return GTK_JSON_NONE;
+    }
+}
+
+const GError *
+gtk_json_parser_get_error (GtkJsonParser *self)
+{
+  return self->error;
+}
+
+char *
+gtk_json_parser_get_member_name (GtkJsonParser *self)
+{
+  GtkJsonReader reader;
+  char *result;
+
+  if (self->error)
+    return NULL;
+
+  if (self->block->type != GTK_JSON_BLOCK_OBJECT)
+    return NULL;
+
+  if (self->block->member_name == NULL)
+    return NULL;
+
+  gtk_json_reader_init (&reader,
+                        self->block->member_name,
+                        self->reader.end - self->block->member_name);
+  if (!gtk_json_reader_parse_string (&reader, &result, NULL))
+    return NULL;
+
+  return result;
+}
+
+gboolean
+gtk_json_parser_get_boolean (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (self->block->value == NULL)
+    return FALSE;
+
+  if (!gtk_json_parser_skip_value (self))
+    return FALSE;
+
+  if (*self->block->value == 't')
+    return TRUE;
+  else if (*self->block->value == 'f')
+    return FALSE;
+
+  gtk_json_parser_value_error (self, "Expected a boolean value");
+  return FALSE;
+}
+
+double
+gtk_json_parser_get_number (GtkJsonParser *self)
+{
+  double result;
+
+  if (self->error)
+    return 0;
+
+  if (self->block->value == NULL)
+    return 0;
+
+  if (!gtk_json_parser_skip_value (self))
+    return FALSE;
+
+  if (!strchr ("-0123456789", *self->block->value))
+    {
+      gtk_json_parser_value_error (self, "Expected a number");
+      return 0;
+    }
+
+  errno = 0;
+  if (gtk_json_reader_remaining (&self->reader) == 0)
+    {
+      /* need terminated string here */
+      char *s = g_strndup ((const char *) self->block->value, self->reader.data - self->block->value);
+      result = g_ascii_strtod (s, NULL);
+      g_free (s);
+    }
+  else
+    {
+      result = g_ascii_strtod ((const char *) self->block->value, NULL);
+    }
+
+  if (errno)
+    {
+      if (errno == ERANGE)
+        gtk_json_parser_value_error (self, "Number out of range");
+      else
+        gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+      return 0;
+    }
+
+  return result;
+}
+
+#if 0
+int                     gtk_json_parser_get_int                 (GtkJsonParser          *self);
+guint                   gtk_json_parser_get_uint                (GtkJsonParser          *self);
+#endif
+
+char *
+gtk_json_parser_get_string (GtkJsonParser *self)
+{
+  char *result;
+
+  if (self->error)
+    return NULL;
+
+  if (self->block->value == NULL)
+    return NULL;
+
+  if (gtk_json_parser_has_skipped_value (self))
+    {
+      GtkJsonReader reader;
+      gtk_json_reader_init (&reader, self->block->value, self->reader.end - self->block->value);
+      if (!gtk_json_reader_parse_string (&reader, &result, NULL))
+        return NULL;
+    }
+  else
+    {
+      if (!gtk_json_reader_parse_string (&self->reader, &result, &self->error))
+        return NULL;
+    }
+
+  return result;
+}
+
+gboolean
+gtk_json_parser_start_object (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (!gtk_json_reader_try_char (&self->reader, '{'))
+    {
+      gtk_json_parser_value_error (self, "Expected an object");
+      return FALSE;
+    }
+
+  gtk_json_parser_push_block (self, GTK_JSON_BLOCK_OBJECT);
+
+  gtk_json_reader_skip_whitespace (&self->reader);
+  if (gtk_json_reader_is_eof (&self->reader) || gtk_json_reader_has_char (&self->reader, '}'))
+    return TRUE;
+  self->block->member_name = self->reader.data;
+
+  if (!gtk_json_reader_parse_string (&self->reader, NULL, &self->error))
+    return FALSE;
+  gtk_json_reader_skip_whitespace (&self->reader);
+  if (!gtk_json_reader_try_char (&self->reader, ':'))
+    {
+      gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+      return FALSE;
+    }
+
+  gtk_json_reader_skip_whitespace (&self->reader);
+  self->block->value = self->reader.data;
+
+  return TRUE;
+}
+
+gboolean
+gtk_json_parser_start_array (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (!gtk_json_reader_try_char (&self->reader, '['))
+    {
+      gtk_json_parser_value_error (self, "Expected an array");
+      return FALSE;
+    }
+
+  gtk_json_parser_push_block (self, GTK_JSON_BLOCK_ARRAY);
+  gtk_json_reader_skip_whitespace (&self->reader);
+  if (gtk_json_reader_is_eof (&self->reader) || gtk_json_reader_has_char (&self->reader, ']'))
+    {
+      self->block->value = NULL;
+      return TRUE;
+    }
+  self->block->value = self->reader.data;
+
+  return TRUE;
+}
+
+gboolean
+gtk_json_parser_end (GtkJsonParser *self)
+{
+  char bracket;
+
+  if (self->error)
+    return FALSE;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+
+  switch (self->block->type)
+    {
+    case GTK_JSON_BLOCK_OBJECT:
+      bracket = '}';
+      break;
+    case GTK_JSON_BLOCK_ARRAY:
+      bracket = ']';
+      break;
+    case GTK_JSON_BLOCK_TOPLEVEL:
+    default:
+      g_return_val_if_reached (FALSE);
+    }
+
+  while (!gtk_json_reader_try_char (&self->reader, bracket))
+    {
+      if (!gtk_json_parser_next (self))
+        return FALSE;
+    }
+
+  gtk_json_parser_pop_block (self);
+
+  return TRUE;
+}
+
+#if 0
+void                    gtk_json_parser_emit_error              (GtkJsonParser          *self,
+                                                                 gsize                   start_line,
+                                                                 gsize                   start_position,
+                                                                 gsize                   end_line,
+                                                                 gsize                   end_position,
+                                                                 GError                 *error);
+void                    gtk_json_parser_error_syntax             (GtkJsonParser         *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+#endif
diff --git a/gtk/css/gtkjsonparserprivate.h b/gtk/css/gtkjsonparserprivate.h
new file mode 100644
index 0000000000..3e80d3ba56
--- /dev/null
+++ b/gtk/css/gtkjsonparserprivate.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_JSON_PARSER_H__
+#define __GTK_JSON_PARSER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  GTK_JSON_NONE,
+  GTK_JSON_NULL,
+  GTK_JSON_BOOLEAN,
+  GTK_JSON_NUMBER,
+  GTK_JSON_STRING,
+  GTK_JSON_OBJECT,
+  GTK_JSON_ARRAY
+} GtkJsonNode;
+
+typedef struct _GtkJsonParser GtkJsonParser;
+
+GtkJsonParser *         gtk_json_parser_new_for_bytes           (GBytes                 *bytes);
+GtkJsonParser *         gtk_json_parser_new_for_string          (const char             *string,
+                                                                 gssize                  size);
+
+GtkJsonParser *         gtk_json_parser_ref                     (GtkJsonParser          *self);
+void                    gtk_json_parser_unref                   (GtkJsonParser          *self);
+
+gsize                   gtk_json_parser_get_start_line          (GtkJsonParser          *self) G_GNUC_PURE;
+gsize                   gtk_json_parser_get_start_position      (GtkJsonParser          *self) G_GNUC_PURE;
+gsize                   gtk_json_parser_get_end_line            (GtkJsonParser          *self) G_GNUC_PURE;
+gsize                   gtk_json_parser_get_end_position        (GtkJsonParser          *self) G_GNUC_PURE;
+
+gboolean                gtk_json_parser_next                    (GtkJsonParser          *self);
+GtkJsonNode             gtk_json_parser_get_node                (GtkJsonParser          *self);
+const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
+char *                  gtk_json_parser_get_member_name         (GtkJsonParser          *self);
+
+gboolean                gtk_json_parser_get_boolean             (GtkJsonParser          *self);
+double                  gtk_json_parser_get_number              (GtkJsonParser          *self);
+int                     gtk_json_parser_get_int                 (GtkJsonParser          *self);
+guint                   gtk_json_parser_get_uint                (GtkJsonParser          *self);
+char *                  gtk_json_parser_get_string              (GtkJsonParser          *self);
+
+gboolean                gtk_json_parser_start_object            (GtkJsonParser          *self);
+gboolean                gtk_json_parser_start_array             (GtkJsonParser          *self);
+gboolean                gtk_json_parser_end                     (GtkJsonParser          *self);
+
+void                    gtk_json_parser_emit_error              (GtkJsonParser          *self,
+                                                                 gsize                   start_line,
+                                                                 gsize                   start_position,
+                                                                 gsize                   end_line,
+                                                                 gsize                   end_position,
+                                                                 GError                 *error);
+void                    gtk_json_parser_error_syntax             (GtkJsonParser         *self,
+                                                                 const char             *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+
+
+G_END_DECLS
+
+#endif /* __GTK_JSON_PARSER_H__ */
diff --git a/gtk/css/meson.build b/gtk/css/meson.build
index 2785f41374..136672784d 100644
--- a/gtk/css/meson.build
+++ b/gtk/css/meson.build
@@ -7,8 +7,9 @@ gtk_css_public_sources = files([
 gtk_css_private_sources = files([
   'gtkcssdataurl.c',
   'gtkcssparser.c',
-  'gtkcsstokenizer.c',
   'gtkcssserializer.c',
+  'gtkcsstokenizer.c',
+  'gtkjsonparser.c',
 ])
 
 gtk_css_public_headers = files([


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]