[gtk/wip/otte/nodeeditor: 9/11] rendernode: Redo the rendernode parser



commit 032eca0c9ba18df12a96da15a293d58d0506d810
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 | 1159 ++++++++++++++++++++++++---------------------
 gsk/meson.build           |    1 +
 gtk/gtkcssprovider.h      |    4 +-
 5 files changed, 1162 insertions(+), 529 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..a9de79a624 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1,668 +1,772 @@
 
 #include "gskrendernodeparserprivate.h"
 
-#include "gskcsstokenizerprivate.h"
+#include "gskcssparserprivate.h"
 #include "gskroundedrectprivate.h"
 #include "gskrendernodeprivate.h"
 #include "gsktransform.h"
 
-typedef struct
-{
-  int n_tokens;
-  GskCssToken *tokens;
-
-  int pos;
-  const GskCssToken *cur;
-} Parser;
-
-static void
-skip (Parser *p)
-{
-  p->pos ++;
-
-  g_assert_cmpint (p->pos, <, p->n_tokens);
-  p->cur = &p->tokens[p->pos];
-}
+typedef struct _Declaration Declaration;
 
-static const GskCssToken *
-lookahead (Parser *p,
-           int     lookahead)
+struct _Declaration
 {
-  g_assert_cmpint (p->pos, <, p->n_tokens - lookahead);
-
-  return &p->tokens[p->pos + lookahead];
-}
-
-static void
-expect (Parser *p,
-        int     expected_type)
+  const char *name;
+  gboolean (* parse_func) (GskCssParser *parser, gpointer result);
+  gpointer result;
+};
+
+static gboolean
+parse_color_channel_value (GskCssParser *parser,
+                           double       *value,
+                           gboolean      is_percentage)
 {
-  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));
-}
-
-static void
-expect_skip (Parser *p,
-             int     expected_type)
-{
-  expect (p, expected_type);
-  skip (p);
-}
-
-static void
-expect_skip_ident (Parser     *p,
-                   const char *ident)
-{
-  if (!gsk_css_token_is_ident (p->cur, ident))
-    g_error ("Expected ident '%s', but found token %s",
-             ident, p->cur->string.string);
+  if (is_percentage)
+    {
+      if (!gsk_css_parser_consume_percentage (parser, value))
+        return FALSE;
 
-  skip (p);
-}
+      *value = CLAMP (*value, 0.0, 100.0) / 100.0;
+      return TRUE;
+    }
+  else
+    {
+      if (!gsk_css_parser_consume_number (parser, value))
+        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;
+      *value = CLAMP (*value, 0.0, 255.0) / 255.0;
+      return TRUE;
+    }
 }
 
-static GskCssToken *
-tokenize (GBytes *bytes,
-          int    *n_tokens)
+static guint
+parse_color_channel (GskCssParser *parser,
+                     guint         arg,
+                     gpointer      data)
 {
-  GskCssTokenizer *tokenizer;
-  GArray *tokens;
-  GskCssToken token;
+  GdkRGBA *rgba = data;
 
-  tokenizer = gsk_css_tokenizer_new (bytes);
-  tokens = g_array_new (FALSE, TRUE, sizeof (GskCssToken));
+  if (arg == 0)
+    {
+      /* We abuse rgba->alpha to store if we use percentages or numbers */
+      if (gsk_css_token_is (gsk_css_parser_get_token (parser), GSK_CSS_TOKEN_PERCENTAGE))
+        rgba->alpha = 1.0;
+      else
+        rgba->alpha = 0.0;
 
-  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))
+      if (!parse_color_channel_value (parser, &rgba->red, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 1)
     {
-      g_array_append_val (tokens, token);
+      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 (!gsk_css_parser_consume_number (parser, &rgba->alpha))
+        return FALSE;
 
-  g_array_append_val (tokens, token);
-
-  *n_tokens = (int) tokens->len;
-
-  return (GskCssToken *) g_array_free (tokens, FALSE);
-}
-
-static double
-number_value (Parser *p)
-{
-  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);
-
-  return p->cur->number.number;
-}
-
-static void
-parse_double4 (Parser *p,
-               double *out_values)
-{
-  int i;
-
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
-
-  for (i = 0; i < 3; i ++)
+      rgba->alpha = CLAMP (rgba->alpha, 0.0, 1.0);
+    }
+  else
     {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      out_values[1 + i] = number_value (p);
-      skip (p);
+      g_assert_not_reached ();
     }
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  return 1;
 }
 
-static void
-parse_tuple (Parser *p,
-             double *out_values)
+static gboolean
+rgba_init_chars (GdkRGBA    *rgba,
+                 const char  s[8])
 {
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
+  guint i;
 
-  expect_skip (p, GSK_CSS_TOKEN_COMMA);
+  for (i = 0; i < 8; i++)
+    {
+      if (!g_ascii_isxdigit (s[i]))
+        return FALSE;
+    }
 
-  out_values[1] = number_value (p);
-  skip (p);
+  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;
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  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
+gsk_rgba_parse (GskCssParser *parser,
+                GdkRGBA      *rgba)
 {
-  double rect[4];
-  double corner0[2];
-  double corner1[2];
-  double corner2[2];
-  double corner3[2];
+  const GskCssToken *token;
 
-  parse_double4 (p, rect);
+  token = gsk_css_parser_get_token (parser);
+  if (gsk_css_token_is_function (token, "rgb"))
+    {
+      if (!gsk_css_parser_consume_function (parser, 3, 3, parse_color_channel, rgba))
+        return FALSE;
 
-  if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_OPEN_PARENS))
+      rgba->alpha = 1.0;
+      return TRUE;
+    }
+  else if (gsk_css_token_is_function (token, "rgba"))
     {
-      parse_tuple (p, corner0);
-      parse_tuple (p, corner1);
-      parse_tuple (p, corner2);
-      parse_tuple (p, corner3);
+      return gsk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
     }
-  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))
+  else if (gsk_css_token_is (token, GSK_CSS_TOKEN_HASH_ID) ||
+           gsk_css_token_is (token, GSK_CSS_TOKEN_HASH_UNRESTRICTED))
     {
-      double val = number_value (p);
-
-      corner0[0] = corner0[1] = val;
-
-      skip (p);
+      const char *s = token->string.string;
 
-      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))
+      switch (strlen (s))
         {
-          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);
+          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;
         }
-      else
+
+      gsk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
+      return FALSE;
+    }
+  else if (gsk_css_token_is_ident (token, "transparent"))
+    {
+      rgba = &(GdkRGBA) { 0, 0, 0, 0 };
+      return TRUE;
+    }
+  else if (gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+    {
+      if (!gdk_rgba_parse (rgba, token->string.string))
         {
-          corner1[0] = corner1[1] = val;
-          corner2[0] = corner2[1] = val;
-          corner3[0] = corner3[1] = val;
+          gsk_css_parser_error_value (parser, "\"%s\" is not a known color name.", token->string.string);
+          return FALSE;
         }
+
+      gsk_css_parser_consume_token (parser);
+      return TRUE;
     }
   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;
+      gsk_css_parser_error_syntax (parser, "Expected a valid color.");
+      return FALSE;
     }
-
-  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] });
 }
 
-static void
-parse_matrix (Parser            *p,
-              graphene_matrix_t *matrix)
+static gboolean
+parse_semicolon (GskCssParser *parser)
 {
-  float vals[16];
-  int i;
-
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
+  const GskCssToken *token;
 
-  vals[0] = number_value (p);
-  skip (p);
-
-  for (i = 1; i < 16; i ++)
+  token = gsk_css_parser_get_token (parser);
+  if (gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
     {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      vals[i] = number_value (p);
-      skip (p);
+      gsk_css_parser_warn_syntax (parser, "No ';' at end of block");
+      return TRUE;
+    }
+  else if (!gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON))
+    {
+      gsk_css_parser_error_syntax (parser, "Expected ';' at end of statement");
+      return FALSE;
     }
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
-  graphene_matrix_init_from_float (matrix, vals);
+  gsk_css_parser_consume_token (parser);
+  return TRUE;
 }
 
-static double
-parse_float_param (Parser     *p,
-                   const char *param_name)
+static gboolean
+parse_rect_without_semicolon (GskCssParser    *parser,
+                              graphene_rect_t *out_rect)
 {
-  double value;
+  double numbers[4];
+  
+  if (!gsk_css_parser_consume_number (parser, &numbers[0]) ||
+      !gsk_css_parser_consume_number (parser, &numbers[1]) ||
+      !gsk_css_parser_consume_number (parser, &numbers[2]) ||
+      !gsk_css_parser_consume_number (parser, &numbers[3]))
+    return FALSE;
 
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
-  value = number_value (p);
-  skip (p);
+  graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
 
-  return value;
+  return TRUE;
 }
 
-static void
-parse_tuple_param (Parser     *p,
-                   const char *param_name,
-                   double     *values)
+static gboolean
+parse_rect (GskCssParser *parser,
+            gpointer      out_rect)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  if (!parse_rect_without_semicolon (parser, out_rect))
+    return FALSE;
 
-  parse_tuple (p, values);
+  return parse_semicolon (parser);
 }
 
-static void
-parse_double4_param (Parser     *p,
-                     const char *param_name,
-                     double     *values)
+static gboolean
+parse_rounded_rect (GskCssParser *parser,
+                    gpointer      out_rect)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  const GskCssToken *token;
+  graphene_rect_t r;
+  graphene_size_t corners[4];
+  double d;
+  guint i;
 
-  parse_double4 (p, values);
-}
+  if (!parse_rect_without_semicolon (parser, &r))
+    return FALSE;
 
-static void
-parse_rounded_rect_param (Parser         *p,
-                          const char     *param_name,
-                          GskRoundedRect *rect)
-{
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
-
-  parse_rounded_rect (p, rect);
-}
-
-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);
-
-  parse_matrix (p, matrix);
-}
-
-static GskRenderNode *
-parse_node (Parser *p)
-{
-  GskRenderNode *result = NULL;
-
-  if (gsk_css_token_is_ident (p->cur, "color"))
+  token = gsk_css_parser_get_token (parser);
+  if (!gsk_css_token_is_delim (token, '/'))
     {
-      double color[4];
-      double bounds[4];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      parse_double4_param (p, "bounds", bounds);
-
-      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]));
+      gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
+      return parse_semicolon (parser);;
     }
-  else if (gsk_css_token_is_ident (p->cur, "opacity"))
-    {
-      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);
+  gsk_css_parser_consume_token (parser);
 
-      child = parse_node (p);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_opacity_node_new (child, opacity);
-      gsk_render_node_unref (child);
+  for (i = 0; i < 4; i++)
+    {
+      token = gsk_css_parser_get_token (parser);
+      if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+          gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+        break;
+      if (!gsk_css_parser_consume_number (parser, &d))
+        return FALSE;
+      corners[i].width = d;
     }
-  else if (gsk_css_token_is_ident (p->cur, "container"))
+
+  if (i == 0)
     {
-      GPtrArray *children = g_ptr_array_new ();
-      guint i;
+      gsk_css_parser_error_syntax (parser, "Expected a number");
+      return FALSE;
+    }
 
-      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));
+  /* 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;
 
+  token = gsk_css_parser_get_token (parser);
+  if (gsk_css_token_is_delim (token, '/'))
+    {
+      gsk_css_parser_consume_token (parser);
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_container_node_new ((GskRenderNode **)children->pdata, children->len);
+      for (i = 0; i < 4; i++)
+        {
+          token = gsk_css_parser_get_token (parser);
+          if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+              gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+            break;
+          if (!gsk_css_parser_consume_number (parser, &d))
+            return FALSE;
+          corners[i].height = d;
+        }
 
-      for (i = 0; i < children->len; i ++)
-        gsk_render_node_unref (g_ptr_array_index (children, i));
+      if (i == 0)
+        {
+          gsk_css_parser_error_syntax (parser, "Expected a number");
+          return FALSE;
+        }
 
-      g_ptr_array_free (children, TRUE);
+      for (; i < 4; i++)
+        corners[i].height = corners[(i - 1) >> 1].height;
     }
-  else if (gsk_css_token_is_ident (p->cur, "outset_shadow"))
+  else
     {
-      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);
+      for (i = 0; i < 4; i++)
+        corners[i].height = corners[i].width;
     }
-  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);
+  gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
 
-      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_color (GskCssParser *parser,
+             gpointer      out_color)
+{
+  if (!gsk_rgba_parse (parser, out_color))
+    return FALSE;
 
-      gsk_render_node_unref (start_child);
-      gsk_render_node_unref (end_child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "clip"))
-    {
-      double clip_rect[4];
-      GskRenderNode *child;
+  return parse_semicolon (parser);
+}
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+static gboolean
+parse_double (GskCssParser *parser,
+              gpointer      out_color)
+{
+  if (!gsk_css_parser_consume_number (parser, out_color))
+    return FALSE;
 
-      parse_double4_param (p, "clip", clip_rect);
-      child = parse_node (p);
+  return parse_semicolon (parser);
+}
 
-      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]
-                                  ));
+static gboolean
+parse_point (GskCssParser *parser,
+             gpointer      out_point)
+{
+  double x, y;
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "rounded_clip"))
-    {
-      GskRoundedRect clip_rect;
-      GskRenderNode *child;
+  if (!gsk_css_parser_consume_number (parser, &x) ||
+      !gsk_css_parser_consume_number (parser, &y))
+    return FALSE;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  graphene_point_init (out_point, x, y);
 
-      parse_rounded_rect_param (p, "clip", &clip_rect);
-      child = parse_node (p);
+  return parse_semicolon (parser);
+}
 
+static gboolean
+parse_stops (GskCssParser *parser,
+             gpointer      out_stops)
+{
+  GArray *stops;
+  GskColorStop stop;
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_rounded_clip_node_new (child, &clip_rect);
+  stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "linear_gradient"))
+  do
     {
-      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 (!gsk_css_parser_consume_number (parser, &stop.offset) ||
+          !gsk_rgba_parse (parser, &stop.color))
+        { /* do nothing */ }
+      else if (stops->len == 0 && stop.offset < 0)
+        gsk_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)
+        gsk_css_parser_error_value (parser, "Color stop offset must be >= previous value");
+      else if (stop.offset > 1)
+        gsk_css_parser_error_value (parser, "Color stop offset must be <= 1");
+      else
         {
-          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);
+          continue;
         }
 
-      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);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "transform"))
+  while (gsk_css_parser_consume_if (parser, GSK_CSS_TOKEN_COMMA));
+
+  if (stops->len < 2)
     {
-      GskTransform *transform;
-      GskRenderNode *child;
+      gsk_css_parser_error_value (parser, "At least 2 color stops need to be specified");
+      g_array_free (stops, TRUE);
+      return FALSE;
+    }
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      expect_skip_ident (p, "transform");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
+  if (*(GArray **) out_stops)
+    g_array_free (*(GArray **) out_stops, TRUE);
+  *(GArray **) out_stops = stops;
 
-      if (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
-        {
-          graphene_matrix_t matrix;
-          parse_matrix (p, &matrix);
+  return parse_semicolon (parser);
+}
+
+static gboolean
+parse_node (GskCssParser *parser, gpointer out_node);
 
-          transform = gsk_transform_matrix (NULL, &matrix);
+static GskRenderNode *
+parse_container_node (GskCssParser *parser)
+{
+  GskRenderNode *node;
+  GPtrArray *nodes;
+  const GskCssToken *token;
+  
+  nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref);
+
+  for (token = gsk_css_parser_get_token (parser);
+       !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+       token = gsk_css_parser_get_token (parser))
+    {
+      node = NULL;
+      if (parse_node (parser, &node))
+        {
+          g_ptr_array_add (nodes, node);
         }
       else
         {
-          expect (p, GSK_CSS_TOKEN_IDENT);
+          gsk_css_parser_skip_until (parser, GSK_CSS_TOKEN_OPEN_CURLY);
+          gsk_css_parser_skip (parser);
+        }
+    }
 
-          for (transform = NULL;;)
-            {
-              /* Transform name */
-              expect (p, GSK_CSS_TOKEN_IDENT);
+  node = gsk_container_node_new ((GskRenderNode **) nodes->pdata, nodes->len);
 
-              if (lookahead (p, 1)->type == GSK_CSS_TOKEN_OPEN_CURLY) /* Start of child node */
-                break;
+  g_ptr_array_unref (nodes);
 
-              if (gsk_css_token_is_ident (p->cur, "translate"))
-                {
-                  double offset[2];
-                  skip (p);
-                  parse_tuple (p, offset);
-                  transform = gsk_transform_translate (transform,
-                                                       &(graphene_point_t) { offset[0], offset[1] });
+  return node;
+}
+
+static void
+parse_declarations_sync (GskCssParser *parser)
+{
+  const GskCssToken *token;
+
+  for (token = gsk_css_parser_get_token (parser);
+       !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+       token = gsk_css_parser_get_token (parser))
+    {
+      if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+          gsk_css_token_is (token, GSK_CSS_TOKEN_OPEN_CURLY))
+        {
+          gsk_css_parser_skip (parser);
+          break;
+        }
+      gsk_css_parser_skip (parser);
+    }
+}
+
+static guint
+parse_declarations (GskCssParser      *parser,
+                    const Declaration *declarations,
+                    guint              n_declarations)
+{
+  guint parsed = 0;
+  guint i;
+  const GskCssToken *token;
+
+  g_assert (n_declarations < 8 * sizeof (guint));
 
+  for (token = gsk_css_parser_get_token (parser);
+       !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+       token = gsk_css_parser_get_token (parser))
+    {
+      for (i = 0; i < n_declarations; i++)
+        {
+          if (gsk_css_token_is_ident (token, declarations[i].name))
+            {
+              gsk_css_parser_consume_token (parser);
+              token = gsk_css_parser_get_token (parser);
+              if (!gsk_css_token_is (token, GSK_CSS_TOKEN_COLON))
+                {
+                  gsk_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));
+                  gsk_css_parser_consume_token (parser);
+                  if (parsed & (1 << i))
+                    gsk_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 (gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+            gsk_css_parser_error_syntax (parser, "No variable named \"%s\"", token->string.string);
+          else
+            gsk_css_parser_error_syntax (parser, "Expected a variable name");
+          parse_declarations_sync (parser);
+        }
+    }
 
-      child = parse_node (p);
+  return parsed;
+}
+
+static GskRenderNode *
+parse_color_node (GskCssParser *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 },
+  };
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_transform_node_new (child, transform);
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
 
-      gsk_transform_unref (transform);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "color_matrix"))
+  return gsk_color_node_new (&color, &bounds);
+}
+
+static GskRenderNode *
+parse_linear_gradient_node (GskCssParser *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;
+      gsk_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 (GskCssParser *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 (GskCssParser *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_opacity_node (GskCssParser *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)
     {
-      G_GNUC_UNUSED guchar *texture_data;
-      gsize texture_data_len;
-      GdkTexture *texture;
-      double bounds[4];
+      gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
+    }
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  result = gsk_opacity_node_new (child, opacity);
 
-      parse_double4_param (p, "bounds", bounds);
+  gsk_render_node_unref (child);
 
-      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_cross_fade_node (GskCssParser *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)
+        gsk_css_parser_error_syntax (parser, "Missing \"start\" property definition");
+      if (end == NULL)
+        gsk_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;
+    }
 
-      /* TODO: :( */
-      texture = gdk_memory_texture_new (2, 2, GDK_MEMORY_R8G8B8,
-                                        b, 6);
+  result = gsk_cross_fade_node_new (start, end, progress);
 
-      expect_skip (p, GSK_CSS_TOKEN_STRING);
+  gsk_render_node_unref (start);
+  gsk_render_node_unref (end);
 
-      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]
-                                     ));
-    }
-  else if (gsk_css_token_is_ident (p->cur, "inset_shadow"))
+  return result;
+}
+
+static GskRenderNode *
+parse_clip_node (GskCssParser *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);
+      gsk_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 (GskCssParser *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] },
-                                    });
+      gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "text"))
-    {
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
+  return gsk_rounded_clip_node_new (child, &clip);
+}
 
-      result = gsk_color_node_new (
-                                   &(GdkRGBA) { 0, 1, 0, 1 },
-                                   &GRAPHENE_RECT_INIT (0, 0, 0, 0));
+static gboolean
+parse_node (GskCssParser *parser,
+            gpointer      out_node)
+{
+  static struct {
+    const char *name;
+    GskRenderNode * (* func) (GskCssParser *);
+  } 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 },
+#if 0
+    { "transform", parse_transform_node },
+#endif
+    { "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 },
+    { "debug", parse_debug_node }
+#endif
+  };
+  const GskCssToken *token;
+  guint i;
+  
+  token = gsk_css_parser_get_token (parser);
+  if (!gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+    {
+      gsk_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 (gsk_css_token_is_ident (token, node_parsers[i].name))
+        {
+          GskRenderNode *node;
+
+          gsk_css_parser_consume_token (parser);
+          token = gsk_css_parser_get_token (parser);
+          if (!gsk_css_token_is (token, GSK_CSS_TOKEN_OPEN_CURLY))
+            {
+              gsk_css_parser_error_syntax (parser, "Expected '{' after node name");
+              return FALSE;
+            }
+          gsk_css_parser_start_block (parser);
+          node = node_parsers[i].func (parser);
+          if (node)
+            {
+              token = gsk_css_parser_get_token (parser);
+              if (!gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+                gsk_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;
+            }
+          gsk_css_parser_end_block (parser);
+
+          return node != NULL;
+        }
     }
 
-  return result;
+  gsk_css_parser_error_value (parser, "\"%s\" is not a valid node name", token->string.string);
+  return FALSE;
+}
+
+static void
+gsk_render_node_parser_error (GskCssParser         *parser,
+                              const GskCssLocation *location,
+                              const GskCssToken    *token,
+                              const GError         *error,
+                              gpointer              user_data)
+{
+  g_print ("ERROR: %zu:%zu: %s\n",
+           location->lines, location->line_chars,
+           error->message);
 }
 
 /**
@@ -672,16 +776,15 @@ GskRenderNode *
 gsk_render_node_deserialize_from_bytes (GBytes *bytes)
 {
   GskRenderNode *root = NULL;
-  GskCssToken *tokens;
-  int n_tokens;
-  Parser parser;
-
-  tokens = tokenize (bytes, &n_tokens);
+  GskCssParser *parser;
 
-  parser_init (&parser, tokens, n_tokens);
-  root = parse_node (&parser);
+  parser = gsk_css_parser_new (gsk_render_node_parser_error,
+                               NULL,
+                               NULL);
+  gsk_css_parser_add_bytes (parser, bytes);
+  root = parse_container_node (parser);
 
-  g_free (tokens);
+  gsk_css_parser_unref (parser);
 
   return root;
 }
diff --git a/gsk/meson.build b/gsk/meson.build
index 895e003d32..fdfc5a626d 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 4d5550373d..2e5c5a99aa 100644
--- a/gtk/gtkcssprovider.h
+++ b/gtk/gtkcssprovider.h
@@ -45,6 +45,7 @@ G_BEGIN_DECLS
  * @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.
  */
@@ -55,7 +56,8 @@ typedef enum
   GTK_CSS_PROVIDER_ERROR_IMPORT,
   GTK_CSS_PROVIDER_ERROR_NAME,
   GTK_CSS_PROVIDER_ERROR_DEPRECATED,
-  GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE
+  GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
+  GTK_CSS_PROVIDER_WARN_GENERAL,
 } GtkCssProviderError;
 
 GDK_AVAILABLE_IN_ALL


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