[gtk/wip/otte/nodeeditor: 1/4] rendernode: Redo the rendernode parser



commit 0fa5efa6288b759da3d91b738a95dc5aa0d503e7
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 | 1206 ++++++++++++++++++++++++++-------------------
 gsk/meson.build           |    1 +
 gtk/gtkcssprovider.h      |    4 +-
 5 files changed, 1220 insertions(+), 518 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..902c44495a 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1,668 +1,841 @@
 
 #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;
+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) (GskCssParser *parser, gpointer result);
+  gpointer result;
+};
+
+static gboolean
+parse_color_channel_value (GskCssParser *parser,
+                           double       *value,
+                           gboolean      is_percentage)
+{
+  if (is_percentage)
+    {
+      if (!gsk_css_parser_consume_percentage (parser, value))
+        return FALSE;
+
+      *value = CLAMP (*value, 0.0, 100.0) / 100.0;
+      return TRUE;
+    }
+  else
+    {
+      if (!gsk_css_parser_consume_number (parser, value))
+        return FALSE;
 
-  g_assert_cmpint (p->pos, <, p->n_tokens);
-  p->cur = &p->tokens[p->pos];
+      *value = CLAMP (*value, 0.0, 255.0) / 255.0;
+      return TRUE;
+    }
 }
 
-static const GskCssToken *
-lookahead (Parser *p,
-           int     lookahead)
+static guint
+parse_color_channel (GskCssParser *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 (gsk_css_token_is (gsk_css_parser_get_token (parser), GSK_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 (!gsk_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 (GskCssParser *parser,
+                GdkRGBA      *rgba)
 {
-  GskCssTokenizer *tokenizer;
-  GArray *tokens;
-  GskCssToken token;
+  const GskCssToken *token;
 
-  tokenizer = gsk_css_tokenizer_new (bytes);
-  tokens = g_array_new (FALSE, TRUE, sizeof (GskCssToken));
+  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;
 
-  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 (gsk_css_token_is_function (token, "rgba"))
     {
-      g_array_append_val (tokens, token);
+      return gsk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
     }
+  else if (gsk_css_token_is (token, GSK_CSS_TOKEN_HASH_ID) ||
+           gsk_css_token_is (token, GSK_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;
+      gsk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
+      return FALSE;
+    }
+  else if (gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+    {
+      if (gsk_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
+        {
+          gsk_css_parser_error_value (parser, "\"%s\" is not a known color name.", token->string.string);
+          return FALSE;
+        }
 
-  return (GskCssToken *) g_array_free (tokens, FALSE);
+      gsk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else
+    {
+      gsk_css_parser_error_syntax (parser, "Expected a valid color.");
+      return FALSE;
+    }
 }
 
-static double
-number_value (Parser *p)
+static gboolean
+parse_semicolon (GskCssParser *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 GskCssToken *token;
 
-  return p->cur->number.number;
+  token = gsk_css_parser_get_token (parser);
+  if (gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+    {
+      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;
+    }
+
+  gsk_css_parser_consume_token (parser);
+  return TRUE;
 }
 
-static void
-parse_double4 (Parser *p,
-               double *out_values)
+static gboolean
+parse_rect_without_semicolon (GskCssParser    *parser,
+                              graphene_rect_t *out_rect)
 {
-  int i;
+  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 (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
+  graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
 
-  for (i = 0; i < 3; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      out_values[1 + i] = number_value (p);
-      skip (p);
-    }
-
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  return TRUE;
 }
 
-static void
-parse_tuple (Parser *p,
-             double *out_values)
+static gboolean
+parse_rect (GskCssParser *parser,
+            gpointer      out_rect)
 {
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
+  graphene_rect_t r;
 
-  expect_skip (p, GSK_CSS_TOKEN_COMMA);
+  if (!parse_rect_without_semicolon (parser, &r) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  out_values[1] = number_value (p);
-  skip (p);
-
-  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 (GskCssParser *parser,
+                    gpointer      out_rect)
 {
-  double rect[4];
-  double corner0[2];
-  double corner1[2];
-  double corner2[2];
-  double corner3[2];
+  const GskCssToken *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 = gsk_css_parser_get_token (parser);
+  if (!gsk_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))
+  gsk_css_parser_consume_token (parser);
+
+  for (i = 0; i < 4; i++)
     {
-      double val = number_value (p);
+      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;
+    }
 
-      corner0[0] = corner0[1] = val;
+  if (i == 0)
+    {
+      gsk_css_parser_error_syntax (parser, "Expected a number");
+      return FALSE;
+    }
 
-      skip (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;
 
-      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))
+  token = gsk_css_parser_get_token (parser);
+  if (gsk_css_token_is_delim (token, '/'))
+    {
+      gsk_css_parser_consume_token (parser);
+
+      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 = 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;
         }
-      else
+
+      if (i == 0)
         {
-          corner1[0] = corner1[1] = val;
-          corner2[0] = corner2[1] = val;
-          corner3[0] = corner3[1] = val;
+          gsk_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] });
-}
-
-static void
-parse_matrix (Parser            *p,
-              graphene_matrix_t *matrix)
-{
-  float vals[16];
-  int i;
-
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-
-  vals[0] = number_value (p);
-  skip (p);
+  if (!parse_semicolon (parser))
+    return FALSE;
 
-  for (i = 1; i < 16; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      vals[i] = number_value (p);
-      skip (p);
-    }
+  gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
-  graphene_matrix_init_from_float (matrix, vals);
+  return TRUE;
 }
 
-static double
-parse_float_param (Parser     *p,
-                   const char *param_name)
+static gboolean
+parse_color (GskCssParser *parser,
+             gpointer      out_color)
 {
-  double value;
+  GdkRGBA color;
+
+  if (!gsk_rgba_parse (parser, &color) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
-  value = number_value (p);
-  skip (p);
+  *(GdkRGBA *) out_color = color;
 
-  return value;
+  return TRUE;
 }
 
-static void
-parse_tuple_param (Parser     *p,
-                   const char *param_name,
-                   double     *values)
+static gboolean
+parse_double (GskCssParser *parser,
+              gpointer      out_double)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  double d;
 
-  parse_tuple (p, values);
-}
+  if (!gsk_css_parser_consume_number (parser, &d) ||
+      !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);
+  *(double *) out_double = d;
 
-  parse_double4 (p, values);
+  return TRUE;
 }
 
-static void
-parse_rounded_rect_param (Parser         *p,
-                          const char     *param_name,
-                          GskRoundedRect *rect)
+static gboolean
+parse_point (GskCssParser *parser,
+             gpointer      out_point)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  double x, y;
 
-  parse_rounded_rect (p, rect);
-}
+  if (!gsk_css_parser_consume_number (parser, &x) ||
+      !gsk_css_parser_consume_number (parser, &y) ||
+      !parse_semicolon (parser))
+    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);
+  graphene_point_init (out_point, x, y);
 
-  parse_matrix (p, matrix);
+  return TRUE;
 }
 
-static GskRenderNode *
-parse_node (Parser *p)
+static gboolean
+parse_string (GskCssParser *parser,
+              gpointer      out_string)
 {
-  GskRenderNode *result = NULL;
-
-  if (gsk_css_token_is_ident (p->cur, "color"))
-    {
-      double color[4];
-      double bounds[4];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  const GskCssToken *token;
+  char *s;
 
-      parse_double4_param (p, "bounds", bounds);
+  token = gsk_css_parser_get_token (parser);
+  if (!gsk_css_token_is (token, GSK_CSS_TOKEN_STRING))
+    return FALSE;
 
-      expect_skip_ident (p, "color");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      parse_double4 (p, color);
+  s = g_strdup (token->string.string);
+  gsk_css_parser_consume_token (parser);
 
-      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);
-
-      child = parse_node (p);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_opacity_node_new (child, opacity);
-      gsk_render_node_unref (child);
+      g_free (s);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "container"))
-    {
-      GPtrArray *children = g_ptr_array_new ();
-      guint i;
 
-      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));
+  g_free (*(char **) out_string);
+  *(char **) out_string = s;
 
+  return TRUE;
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_container_node_new ((GskRenderNode **)children->pdata, children->len);
+static gboolean
+parse_stops (GskCssParser *parser,
+             gpointer      out_stops)
+{
+  GArray *stops;
+  GskColorStop stop;
 
-      for (i = 0; i < children->len; i ++)
-        gsk_render_node_unref (g_ptr_array_index (children, i));
+  stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
 
-      g_ptr_array_free (children, TRUE);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "outset_shadow"))
-    {
-      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);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "cross_fade"))
+  do
     {
-      double progress;
-      GskRenderNode *start_child;
-      GskRenderNode *end_child;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      progress = parse_float_param (p, "progress");
-      start_child = parse_node (p);
-      end_child = parse_node (p);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_cross_fade_node_new (start_child, end_child, progress);
+      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
+        {
+          g_array_append_val (stops, stop);
+          continue;
+        }
 
-      gsk_render_node_unref (start_child);
-      gsk_render_node_unref (end_child);
+      g_array_free (stops, TRUE);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "clip"))
+  while (gsk_css_parser_consume_if (parser, GSK_CSS_TOKEN_COMMA));
+
+  if (stops->len < 2)
     {
-      double clip_rect[4];
-      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);
+  if (*(GArray **) out_stops)
+    g_array_free (*(GArray **) out_stops, TRUE);
+  *(GArray **) out_stops = stops;
 
-      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_node (GskCssParser *parser, gpointer out_node);
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "rounded_clip"))
+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))
     {
-      GskRoundedRect clip_rect;
-      GskRenderNode *child;
+      node = NULL;
+      if (parse_node (parser, &node))
+        {
+          g_ptr_array_add (nodes, node);
+        }
+      else
+        {
+          gsk_css_parser_skip_until (parser, GSK_CSS_TOKEN_OPEN_CURLY);
+          gsk_css_parser_skip (parser);
+        }
+    }
 
-      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 (GskCssParser *parser)
+{
+  const GskCssToken *token;
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "linear_gradient"))
+  for (token = gsk_css_parser_get_token (parser);
+       !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+       token = gsk_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 (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+          gsk_css_token_is (token, GSK_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);
+          gsk_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);
+      gsk_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 (GskCssParser      *parser,
+                    const Declaration *declarations,
+                    guint              n_declarations)
+{
+  guint parsed = 0;
+  guint i;
+  const GskCssToken *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 = 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++)
         {
-          expect (p, GSK_CSS_TOKEN_IDENT);
-
-          for (transform = NULL;;)
+          if (gsk_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"))
+              gsk_css_parser_consume_token (parser);
+              token = gsk_css_parser_get_token (parser);
+              if (!gsk_css_token_is (token, GSK_CSS_TOKEN_COLON))
                 {
-                  double offset[2];
-                  skip (p);
-                  parse_tuple (p, offset);
-                  transform = gsk_transform_translate (transform,
-                                                       &(graphene_point_t) { offset[0], offset[1] });
-
+                  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;
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_transform_node_new (child, transform);
+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 },
+  };
 
-      gsk_transform_unref (transform);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "color_matrix"))
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+  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"))
+
+  return gsk_rounded_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_debug_node (GskCssParser *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);
+      gsk_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 (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 },
+#endif
+    { "debug", parse_debug_node }
+  };
+  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 +845,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]