[gtk/wip/baedert/nodeeditor: 66/73] rendernode: Redo the rendernode parser



commit 94101ce0b867218c736159c564973bea6f2aa2b7
Author: Benjamin Otte <otte redhat com>
Date:   Tue Mar 19 05:46:59 2019 +0100

    rendernode: Redo the rendernode parser
    
    Resurrect the CSS parser from the tokenizer branch and make it handle a
    render node syntax.
    
    The syntax is different from the old syntax. This is done so that it
    follows CSS specification conventions as close as possible and can
    tehrefor be parsed by a CSS parser.

 gsk/gskcssparser.c        |  444 ++++++++++++++++
 gsk/gskcssparserprivate.h |   83 +++
 gsk/gskrendernodeparser.c | 1244 ++++++++++++++++++++++++++-------------------
 gsk/gsktransform.c        |    2 +
 gsk/meson.build           |    1 +
 gtk/gtkcssprovider.h      |   33 ++
 6 files changed, 1295 insertions(+), 512 deletions(-)
---
diff --git a/gsk/gskcssparser.c b/gsk/gskcssparser.c
new file mode 100644
index 0000000000..6a4e1ffd9e
--- /dev/null
+++ b/gsk/gskcssparser.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright © 2019 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 "gskcssparserprivate.h"
+
+#define GTK_COMPILATION
+#include "gtk/gtkcssprovider.h"
+
+struct _GskCssParser
+{
+  volatile int ref_count;
+
+  GskCssParserErrorFunc error_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GSList *sources;
+  GSList *blocks;
+  GskCssLocation location;
+  GskCssToken token;
+};
+
+GskCssParser *
+gsk_css_parser_new (GskCssParserErrorFunc error_func,
+                    gpointer              user_data,
+                    GDestroyNotify        user_destroy)
+{
+  GskCssParser *self;
+
+  self = g_slice_new0 (GskCssParser);
+
+  self->ref_count = 1;
+  self->error_func = error_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+
+  return self;
+}
+
+static void
+gsk_css_parser_finalize (GskCssParser *self)
+{
+  g_slist_free_full (self->sources, (GDestroyNotify) gsk_css_tokenizer_unref);
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  g_slice_free (GskCssParser, self);
+}
+
+GskCssParser *
+gsk_css_parser_ref (GskCssParser *self)
+{
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+void
+gsk_css_parser_unref (GskCssParser *self)
+{
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    gsk_css_parser_finalize (self);
+}
+
+void
+gsk_css_parser_add_tokenizer (GskCssParser    *self,
+                              GskCssTokenizer *tokenizer)
+{
+  self->sources = g_slist_prepend (self->sources, gsk_css_tokenizer_ref (tokenizer));
+}
+
+void
+gsk_css_parser_add_bytes (GskCssParser *self,
+                          GBytes       *bytes)
+{
+  GskCssTokenizer *tokenizer;
+  
+  tokenizer = gsk_css_tokenizer_new (bytes);
+  gsk_css_parser_add_tokenizer (self, tokenizer);
+  gsk_css_tokenizer_unref (tokenizer);
+}
+
+static void
+gsk_css_parser_ensure_token (GskCssParser *self)
+{
+  GskCssTokenizer *tokenizer;
+  GError *error = NULL;
+
+  if (!gsk_css_token_is (&self->token, GSK_CSS_TOKEN_EOF))
+    return;
+
+  if (self->sources == NULL)
+    return;
+
+  tokenizer = self->sources->data;
+
+  self->location = *gsk_css_tokenizer_get_location (tokenizer);
+  if (!gsk_css_tokenizer_read_token (tokenizer, &self->token, &error))
+    {
+      g_clear_error (&error);
+    }
+}
+
+const GskCssToken *
+gsk_css_parser_peek_token (GskCssParser *self)
+{
+  static const GskCssToken eof_token = { GSK_CSS_TOKEN_EOF, };
+
+  gsk_css_parser_ensure_token (self);
+
+  if (self->blocks && gsk_css_token_is (&self->token, GPOINTER_TO_UINT (self->blocks->data)))
+    return &eof_token;
+
+  return &self->token;
+}
+
+const GskCssToken *
+gsk_css_parser_get_token (GskCssParser *self)
+{
+  const GskCssToken *token;
+
+  for (token = gsk_css_parser_peek_token (self);
+       gsk_css_token_is (token, GSK_CSS_TOKEN_COMMENT) ||
+       gsk_css_token_is (token, GSK_CSS_TOKEN_WHITESPACE);
+       token = gsk_css_parser_peek_token (self))
+    {
+      gsk_css_parser_consume_token (self);
+    }
+
+  return token;
+}
+
+void
+gsk_css_parser_consume_token (GskCssParser *self)
+{
+  gsk_css_parser_ensure_token (self);
+
+  /* unpreserved tokens MUST be consumed via start_block() */
+  g_assert (gsk_css_token_is_preserved (&self->token, NULL));
+
+  gsk_css_token_clear (&self->token);
+}
+
+void
+gsk_css_parser_start_block (GskCssParser *self)
+{
+  GskCssTokenType end_token_type;
+
+  gsk_css_parser_ensure_token (self);
+
+  if (gsk_css_token_is_preserved (&self->token, &end_token_type))
+    {
+      g_critical ("gsk_css_parser_start_block() may only be called for non-preserved tokens");
+      return;
+    }
+
+  self->blocks = g_slist_prepend (self->blocks, GUINT_TO_POINTER (end_token_type));
+
+  gsk_css_token_clear (&self->token);
+}
+
+void
+gsk_css_parser_end_block (GskCssParser *self)
+{
+  g_return_if_fail (self->blocks != NULL);
+
+  gsk_css_parser_skip_until (self, GSK_CSS_TOKEN_EOF);
+
+  if (gsk_css_token_is (&self->token, GSK_CSS_TOKEN_EOF))
+    gsk_css_parser_warn_syntax (self, "Unterminated block at end of document");
+
+  self->blocks = g_slist_remove (self->blocks, self->blocks->data);
+  gsk_css_token_clear (&self->token);
+}
+
+/*
+ * gsk_css_parser_skip:
+ * @self: a #GskCssParser
+ *
+ * Skips a component value.
+ *
+ * This means that if the token is a preserved token, only
+ * this token will be skipped. If the token starts a block,
+ * the whole block will be skipped.
+ **/
+void
+gsk_css_parser_skip (GskCssParser *self)
+{
+  const GskCssToken *token;
+  
+  token = gsk_css_parser_get_token (self);
+  if (gsk_css_token_is_preserved (token, NULL))
+    {
+      gsk_css_parser_consume_token (self);
+    }
+  else
+    {
+      gsk_css_parser_start_block (self);
+      gsk_css_parser_end_block (self);
+    }
+}
+
+/*
+ * gsk_css_parser_skip_until:
+ * @self: a #GskCssParser
+ * @token_type: type of token to skip to
+ *
+ * Repeatedly skips a token until a certain type is reached.
+ * After this called, gsk_css_parser_get_token() will either
+ * return a token of this type or the eof token.
+ *
+ * This function is useful for resyncing a parser after encountering
+ * an error.
+ *
+ * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
+ * as the token type.
+ **/
+void
+gsk_css_parser_skip_until (GskCssParser    *self,
+                           GskCssTokenType  token_type)
+{
+  const GskCssToken *token;
+  
+  for (token = gsk_css_parser_get_token (self);
+       !gsk_css_token_is (token, token_type) &&
+       !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+       token = gsk_css_parser_get_token (self))
+    {
+      gsk_css_parser_skip (self);
+    }
+}
+
+void
+gsk_css_parser_emit_error (GskCssParser *self,
+                           const GError *error)
+{
+  self->error_func (self,
+                    &self->location,
+                    &self->token,
+                    error,
+                    self->user_data);
+}
+
+void
+gsk_css_parser_error_syntax (GskCssParser *self,
+                             const char   *format,
+                             ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
+                              GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                              format, args);
+  gsk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gsk_css_parser_error_value (GskCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
+                              GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
+                              format, args);
+  gsk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gsk_css_parser_warn_syntax (GskCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
+                              GTK_CSS_PROVIDER_WARN_GENERAL,
+                              format, args);
+  gsk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+gboolean
+gsk_css_parser_consume_function (GskCssParser *self,
+                                 guint         min_args,
+                                 guint         max_args,
+                                 guint (* parse_func) (GskCssParser *, guint, gpointer),
+                                 gpointer      data)
+{
+  const GskCssToken *token;
+  gboolean result = FALSE;
+  char *function_name;
+  guint arg;
+
+  token = gsk_css_parser_get_token (self);
+  g_return_val_if_fail (gsk_css_token_is (token, GSK_CSS_TOKEN_FUNCTION), FALSE);
+
+  function_name = g_strdup (token->string.string);
+  gsk_css_parser_start_block (self);
+
+  arg = 0;
+  while (TRUE)
+    {
+      guint parse_args = parse_func (self, arg, data);
+      if (parse_args == 0)
+        break;
+      arg += parse_args;
+      token = gsk_css_parser_get_token (self);
+      if (gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+        {
+          if (arg < min_args)
+            {
+              gsk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, 
min_args);
+              break;
+            }
+          else
+            {
+              result = TRUE;
+              break;
+            }
+        }
+      else if (gsk_css_token_is (token, GSK_CSS_TOKEN_COMMA))
+        {
+          if (arg >= max_args)
+            {
+              gsk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
+              break;
+            }
+
+          gsk_css_parser_consume_token (self);
+          continue;
+        }
+      else
+        {
+          gsk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
+          break;
+        }
+    }
+
+  gsk_css_parser_end_block (self);
+  g_free (function_name);
+
+  return result;
+}
+
+/**
+ * gsk_css_parser_consume_if:
+ * @self: a #GskCssParser
+ * @token_type: type of token to check for
+ *
+ * Consumes the next token if it matches the given @token_type.
+ *
+ * This function can be used in loops like this:
+ * do {
+ *   ... parse one element ...
+ * } while (gsk_css_parser_consume_if (parser, GSK_CSS_TOKEN_COMMA);
+ *
+ * Returns: %TRUE if a token was consumed
+ **/
+gboolean
+gsk_css_parser_consume_if (GskCssParser    *self,
+                           GskCssTokenType  token_type)
+{
+  const GskCssToken *token;
+
+  token = gsk_css_parser_get_token (self);
+
+  if (!gsk_css_token_is (token, token_type))
+    return FALSE;
+
+  gsk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+gboolean
+gsk_css_parser_consume_number (GskCssParser *self,
+                               double       *number)
+{
+  const GskCssToken *token;
+
+  token = gsk_css_parser_get_token (self);
+  if (gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
+      gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNLESS_NUMBER) ||
+      gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
+      gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *number = token->number.number;
+      gsk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gboolean
+gsk_css_parser_consume_percentage (GskCssParser *self,
+                                   double       *number)
+{
+  const GskCssToken *token;
+
+  token = gsk_css_parser_get_token (self);
+  if (gsk_css_token_is (token, GSK_CSS_TOKEN_PERCENTAGE))
+    {
+      *number = token->number.number;
+      gsk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
diff --git a/gsk/gskcssparserprivate.h b/gsk/gskcssparserprivate.h
new file mode 100644
index 0000000000..b3c9da7a32
--- /dev/null
+++ b/gsk/gskcssparserprivate.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2019 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 __GSK_CSS_PARSER_H__
+#define __GSK_CSS_PARSER_H__
+
+#include "gskcsstokenizerprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskCssParser GskCssParser;
+
+typedef void            (* GskCssParserErrorFunc)               (GskCssParser                   *parser,
+                                                                 const GskCssLocation           *location,
+                                                                 const GskCssToken              *token,
+                                                                 const GError                   *error,
+                                                                 gpointer                        user_data);
+
+GskCssParser *          gsk_css_parser_new                      (GskCssParserErrorFunc           error_func,
+                                                                 gpointer                        user_data,
+                                                                 GDestroyNotify                  
user_destroy);
+GskCssParser *          gsk_css_parser_ref                      (GskCssParser                   *self);
+void                    gsk_css_parser_unref                    (GskCssParser                   *self);
+
+void                    gsk_css_parser_add_tokenizer            (GskCssParser                   *self,
+                                                                 GskCssTokenizer                *tokenizer);
+void                    gsk_css_parser_add_bytes                (GskCssParser                   *self,
+                                                                 GBytes                         *bytes);
+
+const GskCssToken *     gsk_css_parser_peek_token               (GskCssParser                   *self);
+const GskCssToken *     gsk_css_parser_get_token                (GskCssParser                   *self);
+void                    gsk_css_parser_consume_token            (GskCssParser                   *self);
+void                    gsk_css_parser_start_block              (GskCssParser                   *self); 
+void                    gsk_css_parser_end_block                (GskCssParser                   *self); 
+void                    gsk_css_parser_skip                     (GskCssParser                   *self);
+void                    gsk_css_parser_skip_until               (GskCssParser                   *self,
+                                                                 GskCssTokenType                 token_type);
+
+void                    gsk_css_parser_emit_error               (GskCssParser                   *self,
+                                                                 const GError                   *error);
+void                    gsk_css_parser_error_syntax             (GskCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gsk_css_parser_error_value              (GskCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gsk_css_parser_warn_syntax              (GskCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+
+
+gboolean                gsk_css_parser_consume_if               (GskCssParser                   *self,
+                                                                 GskCssTokenType                 token_type);
+gboolean                gsk_css_parser_consume_number           (GskCssParser                   *self,
+                                                                 double                         *number);
+gboolean                gsk_css_parser_consume_percentage       (GskCssParser                   *self,
+                                                                 double                         *number);
+gboolean                gsk_css_parser_consume_function         (GskCssParser                   *self,
+                                                                 guint                           min_args,
+                                                                 guint                           max_args,
+                                                                 guint (* parse_func) (GskCssParser *, 
guint, gpointer),
+                                                                 gpointer                        data);
+
+G_END_DECLS
+
+#endif /* __GSK_CSS_PARSER_H__ */
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index 912a7d0822..0d28d028ce 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1,668 +1,892 @@
 
 #include "gskrendernodeparserprivate.h"
 
-#include "gskcsstokenizerprivate.h"
+#include <gtk/css/gtkcss.h>
+#include "gtk/css/gtkcssparserprivate.h"
 #include "gskroundedrectprivate.h"
 #include "gskrendernodeprivate.h"
-#include "gsktransform.h"
+#include "gsktransformprivate.h"
 
-typedef struct
-{
-  int n_tokens;
-  GskCssToken *tokens;
+typedef struct _Declaration Declaration;
 
-  int pos;
-  const GskCssToken *cur;
-} Parser;
-
-static void
-skip (Parser *p)
+struct _Declaration
 {
-  p->pos ++;
+  const char *name;
+  gboolean (* parse_func) (GtkCssParser *parser, gpointer result);
+  gpointer result;
+};
+
+static gboolean
+parse_color_channel_value (GtkCssParser *parser,
+                           double       *value,
+                           gboolean      is_percentage)
+{
+  if (is_percentage)
+    {
+      if (!gtk_css_parser_consume_percentage (parser, value))
+        return FALSE;
 
-  g_assert_cmpint (p->pos, <, p->n_tokens);
-  p->cur = &p->tokens[p->pos];
+      *value = CLAMP (*value, 0.0, 100.0) / 100.0;
+      return TRUE;
+    }
+  else
+    {
+      if (!gtk_css_parser_consume_number (parser, value))
+        return FALSE;
+
+      *value = CLAMP (*value, 0.0, 255.0) / 255.0;
+      return TRUE;
+    }
 }
 
-static const GskCssToken *
-lookahead (Parser *p,
-           int     lookahead)
+static guint
+parse_color_channel (GtkCssParser *parser,
+                     guint         arg,
+                     gpointer      data)
 {
-  g_assert_cmpint (p->pos, <, p->n_tokens - lookahead);
+  GdkRGBA *rgba = data;
 
-  return &p->tokens[p->pos + lookahead];
-}
+  if (arg == 0)
+    {
+      /* We abuse rgba->alpha to store if we use percentages or numbers */
+      if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_PERCENTAGE))
+        rgba->alpha = 1.0;
+      else
+        rgba->alpha = 0.0;
 
-static void
-expect (Parser *p,
-        int     expected_type)
-{
-  if (p->cur->type != expected_type)
-    g_error ("Expected token type %d but found %d ('%s')",
-             expected_type, p->cur->type, gsk_css_token_to_string (p->cur));
-}
+      if (!parse_color_channel_value (parser, &rgba->red, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 1)
+    {
+      if (!parse_color_channel_value (parser, &rgba->green, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 2)
+    {
+      if (!parse_color_channel_value (parser, &rgba->blue, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 3)
+    {
+      if (!gtk_css_parser_consume_number (parser, &rgba->alpha))
+        return FALSE;
 
-static void
-expect_skip (Parser *p,
-             int     expected_type)
-{
-  expect (p, expected_type);
-  skip (p);
+      rgba->alpha = CLAMP (rgba->alpha, 0.0, 1.0);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return 1;
 }
 
-static void
-expect_skip_ident (Parser     *p,
-                   const char *ident)
+static gboolean
+rgba_init_chars (GdkRGBA    *rgba,
+                 const char  s[8])
 {
-  if (!gsk_css_token_is_ident (p->cur, ident))
-    g_error ("Expected ident '%s', but found token %s",
-             ident, p->cur->string.string);
+  guint i;
 
-  skip (p);
-}
+  for (i = 0; i < 8; i++)
+    {
+      if (!g_ascii_isxdigit (s[i]))
+        return FALSE;
+    }
 
-static void
-parser_init (Parser      *p,
-             GskCssToken *tokens,
-             int          n_tokens)
-{
-  p->tokens = tokens;
-  p->pos = 0;
-  p->cur = &tokens[p->pos];
-  p->n_tokens = n_tokens;
+  rgba->red =   (g_ascii_xdigit_value (s[0]) * 16 + g_ascii_xdigit_value (s[1])) / 255.0;
+  rgba->green = (g_ascii_xdigit_value (s[2]) * 16 + g_ascii_xdigit_value (s[3])) / 255.0;
+  rgba->blue =  (g_ascii_xdigit_value (s[4]) * 16 + g_ascii_xdigit_value (s[5])) / 255.0;
+  rgba->alpha = (g_ascii_xdigit_value (s[6]) * 16 + g_ascii_xdigit_value (s[7])) / 255.0;
+
+  return TRUE;
 }
 
-static GskCssToken *
-tokenize (GBytes *bytes,
-          int    *n_tokens)
+static gboolean
+gsk_rgba_parse (GtkCssParser *parser,
+                GdkRGBA      *rgba)
 {
-  GskCssTokenizer *tokenizer;
-  GArray *tokens;
-  GskCssToken token;
+  const GtkCssToken *token;
 
-  tokenizer = gsk_css_tokenizer_new (bytes);
-  tokens = g_array_new (FALSE, TRUE, sizeof (GskCssToken));
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is_function (token, "rgb"))
+    {
+      if (!gtk_css_parser_consume_function (parser, 3, 3, parse_color_channel, rgba))
+        return FALSE;
 
-  for (gsk_css_tokenizer_read_token (tokenizer, &token, NULL);
-       !gsk_css_token_is (&token, GSK_CSS_TOKEN_EOF);
-       gsk_css_tokenizer_read_token (tokenizer, &token, NULL))
+      rgba->alpha = 1.0;
+      return TRUE;
+    }
+  else if (gtk_css_token_is_function (token, "rgba"))
     {
-      g_array_append_val (tokens, token);
+      return gtk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
     }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_UNRESTRICTED))
+    {
+      const char *s = token->string.string;
 
-  g_array_append_val (tokens, token);
+      switch (strlen (s))
+        {
+          case 3:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], 'F', 'F' }))
+              return TRUE;
+            break;
+
+          case 4:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], s[3], s[3] }))
+              return TRUE;
+            break;
+
+          case 6:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[1], s[2], s[3], s[4], s[5], 'F', 'F' }))
+              return TRUE;
+            break;
+
+          case 8:
+            if (rgba_init_chars (rgba, s))
+              return TRUE;
+            break;
+
+          default:
+            break;
+        }
 
-  *n_tokens = (int) tokens->len;
+      gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
+      return FALSE;
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      if (gtk_css_token_is_ident (token, "transparent"))
+        {
+          rgba = &(GdkRGBA) { 0, 0, 0, 0 };
+        }
+      else if (gdk_rgba_parse (rgba, token->string.string))
+        {
+          /* everything's fine */
+        }
+      else
+        {
+          gtk_css_parser_error_value (parser, "\"%s\" is not a known color name.", token->string.string);
+          return FALSE;
+        }
 
-  return (GskCssToken *) g_array_free (tokens, FALSE);
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a valid color.");
+      return FALSE;
+    }
 }
 
-static double
-number_value (Parser *p)
+static gboolean
+parse_semicolon (GtkCssParser *parser)
 {
-  if (!gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
-    expect (p, GSK_CSS_TOKEN_SIGNED_NUMBER);
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_warn_syntax (parser, "No ';' at end of block");
+      return TRUE;
+    }
+  else if (!gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON))
+    {
+      gtk_css_parser_error_syntax (parser, "Expected ';' at end of statement");
+      return FALSE;
+    }
 
-  return p->cur->number.number;
+  gtk_css_parser_consume_token (parser);
+  return TRUE;
 }
 
-static void
-parse_double4 (Parser *p,
-               double *out_values)
+static gboolean
+parse_rect_without_semicolon (GtkCssParser    *parser,
+                              graphene_rect_t *out_rect)
 {
-  int i;
-
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
+  double numbers[4];
+  
+  if (!gtk_css_parser_consume_number (parser, &numbers[0]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[1]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[2]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[3]))
+    return FALSE;
 
-  for (i = 0; i < 3; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      out_values[1 + i] = number_value (p);
-      skip (p);
-    }
+  graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  return TRUE;
 }
 
-static void
-parse_tuple (Parser *p,
-             double *out_values)
+static gboolean
+parse_rect (GtkCssParser *parser,
+            gpointer      out_rect)
 {
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
-
-  expect_skip (p, GSK_CSS_TOKEN_COMMA);
+  graphene_rect_t r;
 
-  out_values[1] = number_value (p);
-  skip (p);
+  if (!parse_rect_without_semicolon (parser, &r) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  graphene_rect_init_from_rect (out_rect, &r);
+  return TRUE;
 }
 
-/*
- * The cases we allow are:
- * (x, y, w, h) (w1, h1) (w2, h2) (w3, h3) (w4, h4) for the full rect
- * (x, y, w, h) s1 s2 s3 s4                         for rect + quad corners
- * (x, y, w, h) s                                   for rect + all corners the same size
- * (x, y, w, h)                                     for just the rect with 0-sized corners
- */
-static void
-parse_rounded_rect (Parser         *p,
-                    GskRoundedRect *result)
+static gboolean
+parse_rounded_rect (GtkCssParser *parser,
+                    gpointer      out_rect)
 {
-  double rect[4];
-  double corner0[2];
-  double corner1[2];
-  double corner2[2];
-  double corner3[2];
+  const GtkCssToken *token;
+  graphene_rect_t r;
+  graphene_size_t corners[4];
+  double d;
+  guint i;
 
-  parse_double4 (p, rect);
+  if (!parse_rect_without_semicolon (parser, &r))
+    return FALSE;
 
-  if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_OPEN_PARENS))
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is_delim (token, '/'))
     {
-      parse_tuple (p, corner0);
-      parse_tuple (p, corner1);
-      parse_tuple (p, corner2);
-      parse_tuple (p, corner3);
+      if (!parse_semicolon (parser))
+        return FALSE;
+      gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
+      return TRUE;
     }
-  else if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+  gtk_css_parser_consume_token (parser);
+
+  for (i = 0; i < 4; i++)
     {
-      double val = number_value (p);
+      token = gtk_css_parser_get_token (parser);
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+          gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+        break;
+      if (!gtk_css_parser_consume_number (parser, &d))
+        return FALSE;
+      corners[i].width = d;
+    }
 
-      corner0[0] = corner0[1] = val;
+  if (i == 0)
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a number");
+      return FALSE;
+    }
+
+  /* The magic (i - 1) >> 1 below makes it take the correct value
+   * according to spec. Feel free to check the 4 cases
+   */
+  for (; i < 4; i++)
+    corners[i].width = corners[(i - 1) >> 1].width;
 
-      skip (p);
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is_delim (token, '/'))
+    {
+      gtk_css_parser_consume_token (parser);
 
-      if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+      for (i = 0; i < 4; i++)
         {
-          corner1[0] = corner1[1] = number_value (p);
-          skip (p);
-          corner2[0] = corner2[1] = number_value (p);
-          skip (p);
-          corner3[0] = corner3[1] = number_value (p);
-          skip (p);
+          token = gtk_css_parser_get_token (parser);
+          if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+              gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+            break;
+          if (!gtk_css_parser_consume_number (parser, &d))
+            return FALSE;
+          corners[i].height = d;
         }
-      else
+
+      if (i == 0)
         {
-          corner1[0] = corner1[1] = val;
-          corner2[0] = corner2[1] = val;
-          corner3[0] = corner3[1] = val;
+          gtk_css_parser_error_syntax (parser, "Expected a number");
+          return FALSE;
         }
+
+      for (; i < 4; i++)
+        corners[i].height = corners[(i - 1) >> 1].height;
     }
   else
     {
-      corner0[0] = corner0[1] = 0.0;
-      corner1[0] = corner1[1] = 0.0;
-      corner2[0] = corner2[1] = 0.0;
-      corner3[0] = corner3[1] = 0.0;
+      for (i = 0; i < 4; i++)
+        corners[i].height = corners[i].width;
     }
 
-  gsk_rounded_rect_init (result,
-                         &GRAPHENE_RECT_INIT (rect[0], rect[1], rect[2], rect[3]),
-                         &(graphene_size_t) { corner0[0], corner0[1] },
-                         &(graphene_size_t) { corner1[0], corner1[1] },
-                         &(graphene_size_t) { corner2[0], corner2[1] },
-                         &(graphene_size_t) { corner3[0], corner3[1] });
-}
+  if (!parse_semicolon (parser))
+    return FALSE;
 
-static void
-parse_matrix (Parser            *p,
-              graphene_matrix_t *matrix)
-{
-  float vals[16];
-  int i;
+  gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
 
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
+  return TRUE;
+}
 
-  vals[0] = number_value (p);
-  skip (p);
+static gboolean
+parse_color (GtkCssParser *parser,
+             gpointer      out_color)
+{
+  GdkRGBA color;
 
-  for (i = 1; i < 16; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      vals[i] = number_value (p);
-      skip (p);
-    }
+  if (!gsk_rgba_parse (parser, &color) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  *(GdkRGBA *) out_color = color;
 
-  graphene_matrix_init_from_float (matrix, vals);
+  return TRUE;
 }
 
-static double
-parse_float_param (Parser     *p,
-                   const char *param_name)
+static gboolean
+parse_double (GtkCssParser *parser,
+              gpointer      out_double)
 {
-  double value;
+  double d;
+
+  if (!gtk_css_parser_consume_number (parser, &d) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
-  value = number_value (p);
-  skip (p);
+  *(double *) out_double = d;
 
-  return value;
+  return TRUE;
 }
 
-static void
-parse_tuple_param (Parser     *p,
-                   const char *param_name,
-                   double     *values)
+static gboolean
+parse_point (GtkCssParser *parser,
+             gpointer      out_point)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  double x, y;
 
-  parse_tuple (p, values);
-}
+  if (!gtk_css_parser_consume_number (parser, &x) ||
+      !gtk_css_parser_consume_number (parser, &y) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-static void
-parse_double4_param (Parser     *p,
-                     const char *param_name,
-                     double     *values)
-{
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  graphene_point_init (out_point, x, y);
 
-  parse_double4 (p, values);
+  return TRUE;
 }
 
-static void
-parse_rounded_rect_param (Parser         *p,
-                          const char     *param_name,
-                          GskRoundedRect *rect)
+static gboolean
+parse_transform (GtkCssParser *parser,
+                 gpointer      out_transform)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  GskTransform *transform;
 
-  parse_rounded_rect (p, rect);
-}
+  if (!gsk_transform_parser_parse (parser, &transform) ||
+      !parse_semicolon (parser))
+    {
+      gsk_transform_unref (transform);
+      return FALSE;
+    }
 
-static void
-parse_matrix_param (Parser            *p,
-                    const char        *param_name,
-                    graphene_matrix_t *matrix)
-{
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  gsk_transform_unref (*(GskTransform **) out_transform);
+  *(GskTransform **) out_transform = transform;
 
-  parse_matrix (p, matrix);
+  return TRUE;
 }
 
-static GskRenderNode *
-parse_node (Parser *p)
+static gboolean
+parse_string (GtkCssParser *parser,
+              gpointer      out_string)
 {
-  GskRenderNode *result = NULL;
-
-  if (gsk_css_token_is_ident (p->cur, "color"))
-    {
-      double color[4];
-      double bounds[4];
+  const GtkCssToken *token;
+  char *s;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+    return FALSE;
 
-      parse_double4_param (p, "bounds", bounds);
+  s = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (parser);
 
-      expect_skip_ident (p, "color");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      parse_double4 (p, color);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_color_node_new (&(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                   &GRAPHENE_RECT_INIT (bounds[0], bounds[1], bounds[2], bounds[3]));
-    }
-  else if (gsk_css_token_is_ident (p->cur, "opacity"))
+  if (!parse_semicolon (parser))
     {
-      double opacity = 0.0;
-      GskRenderNode *child;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      expect_skip_ident (p, "opacity");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      opacity = number_value (p);
-      skip (p);
+      g_free (s);
+      return FALSE;
+    }
 
-      child = parse_node (p);
+  g_free (*(char **) out_string);
+  *(char **) out_string = s;
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_opacity_node_new (child, opacity);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "container"))
-    {
-      GPtrArray *children = g_ptr_array_new ();
-      guint i;
+  return TRUE;
+}
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      while (p->cur->type != GSK_CSS_TOKEN_CLOSE_CURLY)
-        g_ptr_array_add (children, parse_node (p));
+static gboolean
+parse_stops (GtkCssParser *parser,
+             gpointer      out_stops)
+{
+  GArray *stops;
+  GskColorStop stop;
 
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_container_node_new ((GskRenderNode **)children->pdata, children->len);
+  stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
 
-      for (i = 0; i < children->len; i ++)
-        gsk_render_node_unref (g_ptr_array_index (children, i));
+  do
+    {
+      gtk_css_parser_skip (parser);
+
+      if (!gtk_css_parser_consume_number (parser, &stop.offset) ||
+          !gsk_rgba_parse (parser, &stop.color))
+        { /* do nothing */ }
+      else if (stops->len == 0 && stop.offset < 0)
+        gtk_css_parser_error_value (parser, "Color stop offset must be >= 0");
+      else if (stops->len > 0 && stop.offset < g_array_index (stops, GskColorStop, stops->len - 1).offset)
+        gtk_css_parser_error_value (parser, "Color stop offset must be >= previous value");
+      else if (stop.offset > 1)
+        gtk_css_parser_error_value (parser, "Color stop offset must be <= 1");
+      else
+        {
+          g_array_append_val (stops, stop);
+          continue;
+        }
 
-      g_ptr_array_free (children, TRUE);
+      g_array_free (stops, TRUE);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "outset_shadow"))
+  while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA));
+
+  if (stops->len < 2)
     {
-      GskRoundedRect outline;
-      double color[4];
-      float dx, dy, spread, blur_radius;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_rounded_rect_param (p, "outline", &outline);
-
-      parse_double4_param (p, "color", color);
-      dx = parse_float_param (p, "dx");
-      dy = parse_float_param (p, "dy");
-      spread = parse_float_param (p, "spread");
-      blur_radius = parse_float_param (p, "blur_radius");
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_outset_shadow_node_new (&outline,
-                                           &(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                           dx, dy,
-                                           spread,
-                                           blur_radius);
+      gtk_css_parser_error_value (parser, "At least 2 color stops need to be specified");
+      g_array_free (stops, TRUE);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "cross_fade"))
-    {
-      double progress;
-      GskRenderNode *start_child;
-      GskRenderNode *end_child;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  if (*(GArray **) out_stops)
+    g_array_free (*(GArray **) out_stops, TRUE);
+  *(GArray **) out_stops = stops;
 
-      progress = parse_float_param (p, "progress");
-      start_child = parse_node (p);
-      end_child = parse_node (p);
+  return parse_semicolon (parser);
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_cross_fade_node_new (start_child, end_child, progress);
+static gboolean
+parse_node (GtkCssParser *parser, gpointer out_node);
 
-      gsk_render_node_unref (start_child);
-      gsk_render_node_unref (end_child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "clip"))
+static GskRenderNode *
+parse_container_node (GtkCssParser *parser)
+{
+  GskRenderNode *node;
+  GPtrArray *nodes;
+  const GtkCssToken *token;
+  
+  nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref);
+
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
     {
-      double clip_rect[4];
-      GskRenderNode *child;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      parse_double4_param (p, "clip", clip_rect);
-      child = parse_node (p);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_clip_node_new (child,
-                                  &GRAPHENE_RECT_INIT (
-                                    clip_rect[0], clip_rect[1],
-                                    clip_rect[2], clip_rect[3]
-                                  ));
-
-      gsk_render_node_unref (child);
+      node = NULL;
+      if (parse_node (parser, &node))
+        {
+          g_ptr_array_add (nodes, node);
+        }
+      else
+        {
+          gtk_css_parser_skip_until (parser, GTK_CSS_TOKEN_OPEN_CURLY);
+          gtk_css_parser_skip (parser);
+        }
     }
-  else if (gsk_css_token_is_ident (p->cur, "rounded_clip"))
-    {
-      GskRoundedRect clip_rect;
-      GskRenderNode *child;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  node = gsk_container_node_new ((GskRenderNode **) nodes->pdata, nodes->len);
 
-      parse_rounded_rect_param (p, "clip", &clip_rect);
-      child = parse_node (p);
+  g_ptr_array_unref (nodes);
 
+  return node;
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_rounded_clip_node_new (child, &clip_rect);
+static void
+parse_declarations_sync (GtkCssParser *parser)
+{
+  const GtkCssToken *token;
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "linear_gradient"))
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
     {
-      GArray *stops = g_array_new (FALSE, TRUE, sizeof (GskColorStop));
-      double bounds[4];
-      double start[2];
-      double end[2];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_double4_param (p, "bounds", bounds);
-      parse_tuple_param (p, "start", start);
-      parse_tuple_param (p, "end", end);
-
-      expect_skip_ident (p, "stops");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      while (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+          gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY))
         {
-          GskColorStop stop;
-          double color[4];
-
-          skip (p);
-          stop.offset = number_value (p);
-          skip (p);
-          expect_skip (p, GSK_CSS_TOKEN_COMMA);
-          parse_double4 (p, color);
-          expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
-          stop.color = (GdkRGBA) { color[0], color[1], color[2], color[3] };
-          g_array_append_val (stops, stop);
+          gtk_css_parser_skip (parser);
+          break;
         }
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_linear_gradient_node_new (&GRAPHENE_RECT_INIT (
-                                               bounds[0], bounds[1],
-                                               bounds[2], bounds[3]
-                                             ),
-                                             &(graphene_point_t) { start[0], start[1] },
-                                             &(graphene_point_t) { end[0], end[1] },
-                                             (GskColorStop *)stops->data,
-                                             stops->len);
-      g_array_free (stops, TRUE);
+      gtk_css_parser_skip (parser);
     }
-  else if (gsk_css_token_is_ident (p->cur, "transform"))
-    {
-      GskTransform *transform;
-      GskRenderNode *child;
+}
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      expect_skip_ident (p, "transform");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
+static guint
+parse_declarations (GtkCssParser      *parser,
+                    const Declaration *declarations,
+                    guint              n_declarations)
+{
+  guint parsed = 0;
+  guint i;
+  const GtkCssToken *token;
 
-      if (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
-        {
-          graphene_matrix_t matrix;
-          parse_matrix (p, &matrix);
+  g_assert (n_declarations < 8 * sizeof (guint));
 
-          transform = gsk_transform_matrix (NULL, &matrix);
-        }
-      else
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
+    {
+      for (i = 0; i < n_declarations; i++)
         {
-          expect (p, GSK_CSS_TOKEN_IDENT);
-
-          for (transform = NULL;;)
+          if (gtk_css_token_is_ident (token, declarations[i].name))
             {
-              /* Transform name */
-              expect (p, GSK_CSS_TOKEN_IDENT);
-
-              if (lookahead (p, 1)->type == GSK_CSS_TOKEN_OPEN_CURLY) /* Start of child node */
-                break;
-
-              if (gsk_css_token_is_ident (p->cur, "translate"))
+              gtk_css_parser_consume_token (parser);
+              token = gtk_css_parser_get_token (parser);
+              if (!gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
                 {
-                  double offset[2];
-                  skip (p);
-                  parse_tuple (p, offset);
-                  transform = gsk_transform_translate (transform,
-                                                       &(graphene_point_t) { offset[0], offset[1] });
-
+                  gtk_css_parser_error_syntax (parser, "Expected ':' after variable declaration");
+                  parse_declarations_sync (parser);
                 }
               else
                 {
-                  g_error ("Unknown transform type: %s", gsk_css_token_to_string (p->cur));
+                  gtk_css_parser_consume_token (parser);
+                  if (parsed & (1 << i))
+                    gtk_css_parser_warn_syntax (parser, "Variable \"%s\" defined multiple times", 
declarations[i].name);
+                  if (declarations[i].parse_func (parser, declarations[i].result))
+                    parsed |= (1 << i);
+                  else
+                    parse_declarations_sync (parser);
                 }
+              break;
             }
         }
+      if (i == n_declarations)
+        {
+          if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+            gtk_css_parser_error_syntax (parser, "No variable named \"%s\"", token->string.string);
+          else
+            gtk_css_parser_error_syntax (parser, "Expected a variable name");
+          parse_declarations_sync (parser);
+        }
+    }
+
+  return parsed;
+}
+
+static GskRenderNode *
+parse_color_node (GtkCssParser *parser)
+{
+  graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  const Declaration declarations[] = {
+    { "bounds", parse_rect, &bounds },
+    { "color", parse_color, &color },
+  };
 
-      child = parse_node (p);
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_transform_node_new (child, transform);
+  return gsk_color_node_new (&color, &bounds);
+}
 
-      gsk_transform_unref (transform);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "color_matrix"))
+static GskRenderNode *
+parse_linear_gradient_node (GtkCssParser *parser)
+{
+  graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  graphene_point_t start = GRAPHENE_POINT_INIT (0, 0);
+  graphene_point_t end = GRAPHENE_POINT_INIT (0, 0);
+  GArray *stops = NULL;
+  const Declaration declarations[] = {
+    { "bounds", parse_rect, &bounds },
+    { "start", parse_point, &start },
+    { "end", parse_point, &end },
+    { "stops", parse_stops, &stops },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (stops == NULL)
     {
-      double offset_values[4];
-      graphene_matrix_t matrix;
-      graphene_vec4_t offset;
-      GskRenderNode *child;
+      gtk_css_parser_error_syntax (parser, "No color stops given");
+      return NULL;
+    }
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  result = gsk_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
 
-      parse_matrix_param (p, "matrix", &matrix);
-      parse_double4_param (p, "offset", offset_values);
+  g_array_free (stops, TRUE);
 
-      graphene_vec4_init (&offset,
-                          offset_values[0],
-                          offset_values[1],
-                          offset_values[2],
-                          offset_values[3]);
+  return result;
+}
 
-      child = parse_node (p);
+static GskRenderNode *
+parse_inset_shadow_node (GtkCssParser *parser)
+{
+  GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  double dx, dy, blur, spread;
+  const Declaration declarations[] = {
+    { "outline", parse_rounded_rect, &outline },
+    { "color", parse_color, &color },
+    { "dx", parse_double, &dx },
+    { "dy", parse_double, &dy },
+    { "spread", parse_double, &spread },
+    { "blur", parse_double, &blur }
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+  return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_color_matrix_node_new (child, &matrix, &offset);
+static GskRenderNode *
+parse_outset_shadow_node (GtkCssParser *parser)
+{
+  GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  double dx, dy, blur, spread;
+  const Declaration declarations[] = {
+    { "outline", parse_rounded_rect, &outline },
+    { "color", parse_color, &color },
+    { "dx", parse_double, &dx },
+    { "dy", parse_double, &dy },
+    { "spread", parse_double, &spread },
+    { "blur", parse_double, &blur }
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+  return gsk_outset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "texture"))
+static GskRenderNode *
+parse_transform_node (GtkCssParser *parser)
+{
+  GskRenderNode *child = NULL;
+  GskTransform *transform = NULL;
+  const Declaration declarations[] = {
+    { "transform", parse_transform, &transform },
+    { "child", parse_node, &child },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      G_GNUC_UNUSED guchar *texture_data;
-      gsize texture_data_len;
-      GdkTexture *texture;
-      double bounds[4];
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      gsk_transform_unref (transform);
+      return NULL;
+    }
+  /* This is very much cheating, isn't it? */
+  if (transform == NULL)
+    transform = gsk_transform_new ();
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  result = gsk_transform_node_new (child, transform);
 
-      parse_double4_param (p, "bounds", bounds);
+  gsk_render_node_unref (child);
+  gsk_transform_unref (transform);
 
-      expect_skip (p, GSK_CSS_TOKEN_IDENT);
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      expect (p, GSK_CSS_TOKEN_STRING);
+  return result;
+}
 
-      texture_data = g_base64_decode (p->cur->string.string, &texture_data_len);
-      guchar data[] = {1, 0, 0, 1, 0, 0,
-                       0, 0, 1, 0, 0, 1};
-      GBytes *b = g_bytes_new_static (data, 12);
+static GskRenderNode *
+parse_opacity_node (GtkCssParser *parser)
+{
+  GskRenderNode *child = NULL;
+  double opacity = 1.0;
+  const Declaration declarations[] = {
+    { "opacity", parse_double, &opacity },
+    { "child", parse_node, &child },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
+    {
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
+    }
 
-      /* TODO: :( */
-      texture = gdk_memory_texture_new (2, 2, GDK_MEMORY_R8G8B8,
-                                        b, 6);
+  result = gsk_opacity_node_new (child, opacity);
 
-      expect_skip (p, GSK_CSS_TOKEN_STRING);
+  gsk_render_node_unref (child);
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_texture_node_new (texture,
-                                     &GRAPHENE_RECT_INIT (
-                                       bounds[0], bounds[1],
-                                       bounds[2], bounds[3]
-                                     ));
+  return result;
+}
+
+static GskRenderNode *
+parse_cross_fade_node (GtkCssParser *parser)
+{
+  GskRenderNode *start = NULL;
+  GskRenderNode *end = NULL;
+  double progress = 0.5;
+  const Declaration declarations[] = {
+    { "progress", parse_double, &progress },
+    { "start", parse_node, &start },
+    { "end", parse_node, &end },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (start == NULL || end == NULL)
+    {
+      if (start == NULL)
+        gtk_css_parser_error_syntax (parser, "Missing \"start\" property definition");
+      if (end == NULL)
+        gtk_css_parser_error_syntax (parser, "Missing \"end\" property definition");
+      g_clear_pointer (&start, gsk_render_node_unref);
+      g_clear_pointer (&end, gsk_render_node_unref);
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "inset_shadow"))
+
+  result = gsk_cross_fade_node_new (start, end, progress);
+
+  gsk_render_node_unref (start);
+  gsk_render_node_unref (end);
+
+  return result;
+}
+
+static GskRenderNode *
+parse_clip_node (GtkCssParser *parser)
+{
+  graphene_rect_t clip = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "clip", parse_rect, &clip },
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      GskRoundedRect outline;
-      double color[4];
-      float dx, dy, spread, blur_radius;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_rounded_rect_param (p, "outline", &outline);
-
-      parse_double4_param (p, "color", color);
-      dx = parse_float_param (p, "dx");
-      dy = parse_float_param (p, "dy");
-      spread = parse_float_param (p, "spread");
-      blur_radius = parse_float_param (p, "blur_radius");
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_inset_shadow_node_new (&outline,
-                                          &(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                          dx, dy,
-                                          spread,
-                                          blur_radius);
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "border"))
+
+  return gsk_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_rounded_clip_node (GtkCssParser *parser)
+{
+  GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "clip", parse_rounded_rect, &clip },
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      GskRoundedRect outline;
-      double widths[4];
-      double colors[4][4];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      parse_rounded_rect_param (p, "outline", &outline);
-      parse_double4_param (p, "widths", widths);
-
-      expect_skip_ident (p, "colors");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-
-      parse_double4 (p, colors[0]);
-      parse_double4 (p, colors[1]);
-      parse_double4 (p, colors[2]);
-      parse_double4 (p, colors[3]);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_border_node_new (&outline,
-                                    (float[4]) { widths[0], widths[1], widths[2], widths[3] },
-                                    (GdkRGBA[4]) {
-                                      (GdkRGBA) { colors[0][0], colors[0][1], colors[0][2], colors[0][3] },
-                                      (GdkRGBA) { colors[1][0], colors[1][1], colors[1][2], colors[1][3] },
-                                      (GdkRGBA) { colors[2][0], colors[2][1], colors[2][2], colors[2][3] },
-                                      (GdkRGBA) { colors[3][0], colors[3][1], colors[3][2], colors[3][3] },
-                                    });
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "text"))
+
+  return gsk_rounded_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_debug_node (GtkCssParser *parser)
+{
+  char *message = NULL;
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "message", parse_string, &message},
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
+    }
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
+  return gsk_debug_node_new (child, message);
+}
 
-      result = gsk_color_node_new (
-                                   &(GdkRGBA) { 0, 1, 0, 1 },
-                                   &GRAPHENE_RECT_INIT (0, 0, 0, 0));
+static gboolean
+parse_node (GtkCssParser *parser,
+            gpointer      out_node)
+{
+  static struct {
+    const char *name;
+    GskRenderNode * (* func) (GtkCssParser *);
+  } node_parsers[] = {
+    { "container", parse_container_node },
+    { "color", parse_color_node },
+#if 0
+    { "cairo", parse_cairo_node },
+#endif
+    { "linear-gradient", parse_linear_gradient_node },
+#if 0
+    { "border", parse_border_node },
+    { "texture", parse_texture_node },
+#endif
+    { "inset-shadow", parse_inset_shadow_node },
+    { "outset-shadow", parse_outset_shadow_node },
+    { "transform", parse_transform_node },
+    { "opacity", parse_opacity_node },
+#if 0
+    { "color-matrix", parse_color-matrix_node },
+    { "repeat", parse_repeat_node },
+#endif
+    { "clip", parse_clip_node },
+    { "rounded-clip", parse_rounded_clip_node },
+#if 0
+    { "shadow", parse_shadow_node },
+    { "blend", parse_blend_node },
+#endif
+    { "cross-fade", parse_cross_fade_node },
+#if 0
+    { "text", parse_text_node },
+    { "blur", parse_blur_node },
+#endif
+    { "debug", parse_debug_node }
+  };
+  const GtkCssToken *token;
+  guint i;
+  
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a node name");
+      return FALSE;
     }
-  else
+
+  for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
     {
-      g_error ("Unknown render node type: %s", gsk_css_token_to_string (p->cur));
+      if (gtk_css_token_is_ident (token, node_parsers[i].name))
+        {
+          GskRenderNode *node;
+
+          gtk_css_parser_consume_token (parser);
+          token = gtk_css_parser_get_token (parser);
+          if (!gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY))
+            {
+              gtk_css_parser_error_syntax (parser, "Expected '{' after node name");
+              return FALSE;
+            }
+          gtk_css_parser_start_block (parser);
+          node = node_parsers[i].func (parser);
+          if (node)
+            {
+              token = gtk_css_parser_get_token (parser);
+              if (!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+                gtk_css_parser_error_syntax (parser, "Expected '}' at end of node definition");
+              g_clear_pointer ((GskRenderNode **) out_node, gsk_render_node_unref);
+              *(GskRenderNode **) out_node = node;
+            }
+          gtk_css_parser_end_block (parser);
+
+          return node != NULL;
+        }
     }
 
-  return result;
+  gtk_css_parser_error_value (parser, "\"%s\" is not a valid node name", token->string.string);
+  return FALSE;
+}
+
+static void
+gsk_render_node_parser_error (GtkCssParser         *parser,
+                              const GtkCssLocation *start,
+                              const GtkCssLocation *end,
+                              const GError         *error,
+                              gpointer              user_data)
+{
+  g_print ("ERROR: %zu:%zu: %s\n",
+           start->lines, start->line_chars,
+           error->message);
 }
 
 /**
@@ -672,16 +896,12 @@ GskRenderNode *
 gsk_render_node_deserialize_from_bytes (GBytes *bytes)
 {
   GskRenderNode *root = NULL;
-  GskCssToken *tokens;
-  int n_tokens;
-  Parser parser;
-
-  tokens = tokenize (bytes, &n_tokens);
+  GtkCssParser *parser;
 
-  parser_init (&parser, tokens, n_tokens);
-  root = parse_node (&parser);
+  parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, gsk_render_node_parser_error, NULL, NULL);
+  root = parse_container_node (parser);
 
-  g_free (tokens);
+  gtk_css_parser_unref (parser);
 
   return root;
 }
diff --git a/gsk/gsktransform.c b/gsk/gsktransform.c
index 1dfd9b9740..3ada22ff8a 100644
--- a/gsk/gsktransform.c
+++ b/gsk/gsktransform.c
@@ -1695,6 +1695,7 @@ gsk_transform_parser_parse (GtkCssParser  *parser,
   if (gtk_css_token_is_ident (token, "none"))
     {
       gtk_css_parser_consume_token (parser);
+
       *out_transform = NULL;
       return TRUE;
     }
@@ -1902,6 +1903,7 @@ gsk_transform_parse (const char    *string,
       result = FALSE;
     }
   gtk_css_parser_unref (parser);
+
   g_bytes_unref (bytes);
 
   return result; 
diff --git a/gsk/meson.build b/gsk/meson.build
index f316b3f2f6..37882ac164 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -31,6 +31,7 @@ gsk_public_sources = files([
 gsk_private_sources = files([
   'gskcairoblur.c',
   'gskcairorenderer.c',
+  'gskcssparser.c',
   'gskcsstokenizer.c',
   'gskdebug.c',
   'gskprivate.c',
diff --git a/gtk/gtkcssprovider.h b/gtk/gtkcssprovider.h
index 051305e573..61857d8344 100644
--- a/gtk/gtkcssprovider.h
+++ b/gtk/gtkcssprovider.h
@@ -30,6 +30,39 @@ G_BEGIN_DECLS
 #define GTK_IS_CSS_PROVIDER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE    ((c), GTK_TYPE_CSS_PROVIDER))
 #define GTK_CSS_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS  ((o), GTK_TYPE_CSS_PROVIDER, 
GtkCssProviderClass))
 
+/**
+ * GTK_CSS_PROVIDER_ERROR:
+ *
+ * Domain for #GtkCssProvider errors.
+ */
+#define GTK_CSS_PROVIDER_ERROR (gtk_css_provider_error_quark ())
+
+/**
+ * GtkCssProviderError:
+ * @GTK_CSS_PROVIDER_ERROR_FAILED: Failed.
+ * @GTK_CSS_PROVIDER_ERROR_SYNTAX: Syntax error.
+ * @GTK_CSS_PROVIDER_ERROR_IMPORT: Import error.
+ * @GTK_CSS_PROVIDER_ERROR_NAME: Name error.
+ * @GTK_CSS_PROVIDER_ERROR_DEPRECATED: Deprecation error.
+ * @GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE: Unknown value.
+ * @GTK_CSS_PROVIDER_WARN_GENERAL: A general warning.
+ *
+ * Error codes for %GTK_CSS_PROVIDER_ERROR.
+ */
+typedef enum
+{
+  GTK_CSS_PROVIDER_ERROR_FAILED,
+  GTK_CSS_PROVIDER_ERROR_SYNTAX,
+  GTK_CSS_PROVIDER_ERROR_IMPORT,
+  GTK_CSS_PROVIDER_ERROR_NAME,
+  GTK_CSS_PROVIDER_ERROR_DEPRECATED,
+  GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
+  GTK_CSS_PROVIDER_WARN_GENERAL,
+} GtkCssProviderError;
+
+GDK_AVAILABLE_IN_ALL
+GQuark gtk_css_provider_error_quark (void);
+
 typedef struct _GtkCssProvider GtkCssProvider;
 typedef struct _GtkCssProviderClass GtkCssProviderClass;
 typedef struct _GtkCssProviderPrivate GtkCssProviderPrivate;


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