[gtk/wip/baedert/nodeeditor: 3/11] rendernode: Parse rendernodes from text files



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

    rendernode: Parse rendernodes from text files
    
    We can use the CSS parser for this now.

 gsk/gskrendernodeparser.c | 1244 ++++++++++++++++++++++++++-------------------
 gsk/gsktransform.c        |    2 +
 gtk/gtkcssprovider.h      |   23 +
 3 files changed, 757 insertions(+), 512 deletions(-)
---
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index 0170a49756..0d28d028ce 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1,668 +1,892 @@
 
 #include "gskrendernodeparserprivate.h"
 
-#include "gskcsstokenizerprivate.h"
+#include <gtk/css/gtkcss.h>
+#include "gtk/css/gtkcssparserprivate.h"
 #include "gskroundedrectprivate.h"
 #include "gskrendernodeprivate.h"
-#include "gsktransform.h"
+#include "gsktransformprivate.h"
 
-typedef struct
-{
-  int n_tokens;
-  GskCssToken *tokens;
+typedef struct _Declaration Declaration;
 
-  int pos;
-  const GskCssToken *cur;
-} Parser;
-
-static void
-skip (Parser *p)
+struct _Declaration
 {
-  p->pos ++;
+  const char *name;
+  gboolean (* parse_func) (GtkCssParser *parser, gpointer result);
+  gpointer result;
+};
+
+static gboolean
+parse_color_channel_value (GtkCssParser *parser,
+                           double       *value,
+                           gboolean      is_percentage)
+{
+  if (is_percentage)
+    {
+      if (!gtk_css_parser_consume_percentage (parser, value))
+        return FALSE;
 
-  g_assert_cmpint (p->pos, <, p->n_tokens);
-  p->cur = &p->tokens[p->pos];
+      *value = CLAMP (*value, 0.0, 100.0) / 100.0;
+      return TRUE;
+    }
+  else
+    {
+      if (!gtk_css_parser_consume_number (parser, value))
+        return FALSE;
+
+      *value = CLAMP (*value, 0.0, 255.0) / 255.0;
+      return TRUE;
+    }
 }
 
-static const GskCssToken *
-lookahead (Parser *p,
-           int     lookahead)
+static guint
+parse_color_channel (GtkCssParser *parser,
+                     guint         arg,
+                     gpointer      data)
 {
-  g_assert_cmpint (p->pos, <, p->n_tokens - lookahead);
+  GdkRGBA *rgba = data;
 
-  return &p->tokens[p->pos + lookahead];
-}
+  if (arg == 0)
+    {
+      /* We abuse rgba->alpha to store if we use percentages or numbers */
+      if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_PERCENTAGE))
+        rgba->alpha = 1.0;
+      else
+        rgba->alpha = 0.0;
 
-static void
-expect (Parser *p,
-        int     expected_type)
-{
-  if (p->cur->type != expected_type)
-    g_error ("Expected token type %d but found %d ('%s')",
-             expected_type, p->cur->type, gsk_css_token_to_string (p->cur));
-}
+      if (!parse_color_channel_value (parser, &rgba->red, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 1)
+    {
+      if (!parse_color_channel_value (parser, &rgba->green, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 2)
+    {
+      if (!parse_color_channel_value (parser, &rgba->blue, rgba->alpha != 0.0))
+        return 0;
+    }
+  else if (arg == 3)
+    {
+      if (!gtk_css_parser_consume_number (parser, &rgba->alpha))
+        return FALSE;
 
-static void
-expect_skip (Parser *p,
-             int     expected_type)
-{
-  expect (p, expected_type);
-  skip (p);
+      rgba->alpha = CLAMP (rgba->alpha, 0.0, 1.0);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return 1;
 }
 
-static void
-expect_skip_ident (Parser     *p,
-                   const char *ident)
+static gboolean
+rgba_init_chars (GdkRGBA    *rgba,
+                 const char  s[8])
 {
-  if (!gsk_css_token_is_ident (p->cur, ident))
-    g_error ("Expected ident '%s', but found token %s",
-             ident, p->cur->string.string);
+  guint i;
 
-  skip (p);
-}
+  for (i = 0; i < 8; i++)
+    {
+      if (!g_ascii_isxdigit (s[i]))
+        return FALSE;
+    }
 
-static void
-parser_init (Parser      *p,
-             GskCssToken *tokens,
-             int          n_tokens)
-{
-  p->tokens = tokens;
-  p->pos = 0;
-  p->cur = &tokens[p->pos];
-  p->n_tokens = n_tokens;
+  rgba->red =   (g_ascii_xdigit_value (s[0]) * 16 + g_ascii_xdigit_value (s[1])) / 255.0;
+  rgba->green = (g_ascii_xdigit_value (s[2]) * 16 + g_ascii_xdigit_value (s[3])) / 255.0;
+  rgba->blue =  (g_ascii_xdigit_value (s[4]) * 16 + g_ascii_xdigit_value (s[5])) / 255.0;
+  rgba->alpha = (g_ascii_xdigit_value (s[6]) * 16 + g_ascii_xdigit_value (s[7])) / 255.0;
+
+  return TRUE;
 }
 
-static GskCssToken *
-tokenize (GBytes *bytes,
-          int    *n_tokens)
+static gboolean
+gsk_rgba_parse (GtkCssParser *parser,
+                GdkRGBA      *rgba)
 {
-  GskCssTokenizer *tokenizer;
-  GArray *tokens;
-  GskCssToken token;
+  const GtkCssToken *token;
 
-  tokenizer = gsk_css_tokenizer_new (bytes, NULL, NULL, NULL);
-  tokens = g_array_new (FALSE, TRUE, sizeof (GskCssToken));
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is_function (token, "rgb"))
+    {
+      if (!gtk_css_parser_consume_function (parser, 3, 3, parse_color_channel, rgba))
+        return FALSE;
 
-  for (gsk_css_tokenizer_read_token (tokenizer, &token);
-       !gsk_css_token_is (&token, GSK_CSS_TOKEN_EOF);
-       gsk_css_tokenizer_read_token (tokenizer, &token))
+      rgba->alpha = 1.0;
+      return TRUE;
+    }
+  else if (gtk_css_token_is_function (token, "rgba"))
     {
-      g_array_append_val (tokens, token);
+      return gtk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
     }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_UNRESTRICTED))
+    {
+      const char *s = token->string.string;
 
-  g_array_append_val (tokens, token);
+      switch (strlen (s))
+        {
+          case 3:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], 'F', 'F' }))
+              return TRUE;
+            break;
+
+          case 4:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], s[3], s[3] }))
+              return TRUE;
+            break;
+
+          case 6:
+            if (rgba_init_chars (rgba, (char[8]) {s[0], s[1], s[2], s[3], s[4], s[5], 'F', 'F' }))
+              return TRUE;
+            break;
+
+          case 8:
+            if (rgba_init_chars (rgba, s))
+              return TRUE;
+            break;
+
+          default:
+            break;
+        }
 
-  *n_tokens = (int) tokens->len;
+      gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
+      return FALSE;
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      if (gtk_css_token_is_ident (token, "transparent"))
+        {
+          rgba = &(GdkRGBA) { 0, 0, 0, 0 };
+        }
+      else if (gdk_rgba_parse (rgba, token->string.string))
+        {
+          /* everything's fine */
+        }
+      else
+        {
+          gtk_css_parser_error_value (parser, "\"%s\" is not a known color name.", token->string.string);
+          return FALSE;
+        }
 
-  return (GskCssToken *) g_array_free (tokens, FALSE);
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a valid color.");
+      return FALSE;
+    }
 }
 
-static double
-number_value (Parser *p)
+static gboolean
+parse_semicolon (GtkCssParser *parser)
 {
-  if (!gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) &&
-      !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
-    expect (p, GSK_CSS_TOKEN_SIGNED_NUMBER);
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_warn_syntax (parser, "No ';' at end of block");
+      return TRUE;
+    }
+  else if (!gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON))
+    {
+      gtk_css_parser_error_syntax (parser, "Expected ';' at end of statement");
+      return FALSE;
+    }
 
-  return p->cur->number.number;
+  gtk_css_parser_consume_token (parser);
+  return TRUE;
 }
 
-static void
-parse_double4 (Parser *p,
-               double *out_values)
+static gboolean
+parse_rect_without_semicolon (GtkCssParser    *parser,
+                              graphene_rect_t *out_rect)
 {
-  int i;
-
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
+  double numbers[4];
+  
+  if (!gtk_css_parser_consume_number (parser, &numbers[0]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[1]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[2]) ||
+      !gtk_css_parser_consume_number (parser, &numbers[3]))
+    return FALSE;
 
-  for (i = 0; i < 3; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      out_values[1 + i] = number_value (p);
-      skip (p);
-    }
+  graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  return TRUE;
 }
 
-static void
-parse_tuple (Parser *p,
-             double *out_values)
+static gboolean
+parse_rect (GtkCssParser *parser,
+            gpointer      out_rect)
 {
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-  out_values[0] = number_value (p);
-  skip (p);
-
-  expect_skip (p, GSK_CSS_TOKEN_COMMA);
+  graphene_rect_t r;
 
-  out_values[1] = number_value (p);
-  skip (p);
+  if (!parse_rect_without_semicolon (parser, &r) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  graphene_rect_init_from_rect (out_rect, &r);
+  return TRUE;
 }
 
-/*
- * The cases we allow are:
- * (x, y, w, h) (w1, h1) (w2, h2) (w3, h3) (w4, h4) for the full rect
- * (x, y, w, h) s1 s2 s3 s4                         for rect + quad corners
- * (x, y, w, h) s                                   for rect + all corners the same size
- * (x, y, w, h)                                     for just the rect with 0-sized corners
- */
-static void
-parse_rounded_rect (Parser         *p,
-                    GskRoundedRect *result)
+static gboolean
+parse_rounded_rect (GtkCssParser *parser,
+                    gpointer      out_rect)
 {
-  double rect[4];
-  double corner0[2];
-  double corner1[2];
-  double corner2[2];
-  double corner3[2];
+  const GtkCssToken *token;
+  graphene_rect_t r;
+  graphene_size_t corners[4];
+  double d;
+  guint i;
 
-  parse_double4 (p, rect);
+  if (!parse_rect_without_semicolon (parser, &r))
+    return FALSE;
 
-  if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_OPEN_PARENS))
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is_delim (token, '/'))
     {
-      parse_tuple (p, corner0);
-      parse_tuple (p, corner1);
-      parse_tuple (p, corner2);
-      parse_tuple (p, corner3);
+      if (!parse_semicolon (parser))
+        return FALSE;
+      gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
+      return TRUE;
     }
-  else if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
-           gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+  gtk_css_parser_consume_token (parser);
+
+  for (i = 0; i < 4; i++)
     {
-      double val = number_value (p);
+      token = gtk_css_parser_get_token (parser);
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+          gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+        break;
+      if (!gtk_css_parser_consume_number (parser, &d))
+        return FALSE;
+      corners[i].width = d;
+    }
 
-      corner0[0] = corner0[1] = val;
+  if (i == 0)
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a number");
+      return FALSE;
+    }
+
+  /* The magic (i - 1) >> 1 below makes it take the correct value
+   * according to spec. Feel free to check the 4 cases
+   */
+  for (; i < 4; i++)
+    corners[i].width = corners[(i - 1) >> 1].width;
 
-      skip (p);
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_token_is_delim (token, '/'))
+    {
+      gtk_css_parser_consume_token (parser);
 
-      if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
-          gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+      for (i = 0; i < 4; i++)
         {
-          corner1[0] = corner1[1] = number_value (p);
-          skip (p);
-          corner2[0] = corner2[1] = number_value (p);
-          skip (p);
-          corner3[0] = corner3[1] = number_value (p);
-          skip (p);
+          token = gtk_css_parser_get_token (parser);
+          if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+              gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+            break;
+          if (!gtk_css_parser_consume_number (parser, &d))
+            return FALSE;
+          corners[i].height = d;
         }
-      else
+
+      if (i == 0)
         {
-          corner1[0] = corner1[1] = val;
-          corner2[0] = corner2[1] = val;
-          corner3[0] = corner3[1] = val;
+          gtk_css_parser_error_syntax (parser, "Expected a number");
+          return FALSE;
         }
+
+      for (; i < 4; i++)
+        corners[i].height = corners[(i - 1) >> 1].height;
     }
   else
     {
-      corner0[0] = corner0[1] = 0.0;
-      corner1[0] = corner1[1] = 0.0;
-      corner2[0] = corner2[1] = 0.0;
-      corner3[0] = corner3[1] = 0.0;
+      for (i = 0; i < 4; i++)
+        corners[i].height = corners[i].width;
     }
 
-  gsk_rounded_rect_init (result,
-                         &GRAPHENE_RECT_INIT (rect[0], rect[1], rect[2], rect[3]),
-                         &(graphene_size_t) { corner0[0], corner0[1] },
-                         &(graphene_size_t) { corner1[0], corner1[1] },
-                         &(graphene_size_t) { corner2[0], corner2[1] },
-                         &(graphene_size_t) { corner3[0], corner3[1] });
-}
+  if (!parse_semicolon (parser))
+    return FALSE;
 
-static void
-parse_matrix (Parser            *p,
-              graphene_matrix_t *matrix)
-{
-  float vals[16];
-  int i;
+  gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
 
-  expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
+  return TRUE;
+}
 
-  vals[0] = number_value (p);
-  skip (p);
+static gboolean
+parse_color (GtkCssParser *parser,
+             gpointer      out_color)
+{
+  GdkRGBA color;
 
-  for (i = 1; i < 16; i ++)
-    {
-      expect_skip (p, GSK_CSS_TOKEN_COMMA);
-      vals[i] = number_value (p);
-      skip (p);
-    }
+  if (!gsk_rgba_parse (parser, &color) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+  *(GdkRGBA *) out_color = color;
 
-  graphene_matrix_init_from_float (matrix, vals);
+  return TRUE;
 }
 
-static double
-parse_float_param (Parser     *p,
-                   const char *param_name)
+static gboolean
+parse_double (GtkCssParser *parser,
+              gpointer      out_double)
 {
-  double value;
+  double d;
+
+  if (!gtk_css_parser_consume_number (parser, &d) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
-  value = number_value (p);
-  skip (p);
+  *(double *) out_double = d;
 
-  return value;
+  return TRUE;
 }
 
-static void
-parse_tuple_param (Parser     *p,
-                   const char *param_name,
-                   double     *values)
+static gboolean
+parse_point (GtkCssParser *parser,
+             gpointer      out_point)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  double x, y;
 
-  parse_tuple (p, values);
-}
+  if (!gtk_css_parser_consume_number (parser, &x) ||
+      !gtk_css_parser_consume_number (parser, &y) ||
+      !parse_semicolon (parser))
+    return FALSE;
 
-static void
-parse_double4_param (Parser     *p,
-                     const char *param_name,
-                     double     *values)
-{
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  graphene_point_init (out_point, x, y);
 
-  parse_double4 (p, values);
+  return TRUE;
 }
 
-static void
-parse_rounded_rect_param (Parser         *p,
-                          const char     *param_name,
-                          GskRoundedRect *rect)
+static gboolean
+parse_transform (GtkCssParser *parser,
+                 gpointer      out_transform)
 {
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  GskTransform *transform;
 
-  parse_rounded_rect (p, rect);
-}
+  if (!gsk_transform_parser_parse (parser, &transform) ||
+      !parse_semicolon (parser))
+    {
+      gsk_transform_unref (transform);
+      return FALSE;
+    }
 
-static void
-parse_matrix_param (Parser            *p,
-                    const char        *param_name,
-                    graphene_matrix_t *matrix)
-{
-  expect_skip_ident (p, param_name);
-  expect_skip (p, GSK_CSS_TOKEN_COLON);
+  gsk_transform_unref (*(GskTransform **) out_transform);
+  *(GskTransform **) out_transform = transform;
 
-  parse_matrix (p, matrix);
+  return TRUE;
 }
 
-static GskRenderNode *
-parse_node (Parser *p)
+static gboolean
+parse_string (GtkCssParser *parser,
+              gpointer      out_string)
 {
-  GskRenderNode *result = NULL;
-
-  if (gsk_css_token_is_ident (p->cur, "color"))
-    {
-      double color[4];
-      double bounds[4];
+  const GtkCssToken *token;
+  char *s;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+    return FALSE;
 
-      parse_double4_param (p, "bounds", bounds);
+  s = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (parser);
 
-      expect_skip_ident (p, "color");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      parse_double4 (p, color);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_color_node_new (&(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                   &GRAPHENE_RECT_INIT (bounds[0], bounds[1], bounds[2], bounds[3]));
-    }
-  else if (gsk_css_token_is_ident (p->cur, "opacity"))
+  if (!parse_semicolon (parser))
     {
-      double opacity = 0.0;
-      GskRenderNode *child;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      expect_skip_ident (p, "opacity");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      opacity = number_value (p);
-      skip (p);
+      g_free (s);
+      return FALSE;
+    }
 
-      child = parse_node (p);
+  g_free (*(char **) out_string);
+  *(char **) out_string = s;
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_opacity_node_new (child, opacity);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "container"))
-    {
-      GPtrArray *children = g_ptr_array_new ();
-      guint i;
+  return TRUE;
+}
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      while (p->cur->type != GSK_CSS_TOKEN_CLOSE_CURLY)
-        g_ptr_array_add (children, parse_node (p));
+static gboolean
+parse_stops (GtkCssParser *parser,
+             gpointer      out_stops)
+{
+  GArray *stops;
+  GskColorStop stop;
 
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_container_node_new ((GskRenderNode **)children->pdata, children->len);
+  stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
 
-      for (i = 0; i < children->len; i ++)
-        gsk_render_node_unref (g_ptr_array_index (children, i));
+  do
+    {
+      gtk_css_parser_skip (parser);
+
+      if (!gtk_css_parser_consume_number (parser, &stop.offset) ||
+          !gsk_rgba_parse (parser, &stop.color))
+        { /* do nothing */ }
+      else if (stops->len == 0 && stop.offset < 0)
+        gtk_css_parser_error_value (parser, "Color stop offset must be >= 0");
+      else if (stops->len > 0 && stop.offset < g_array_index (stops, GskColorStop, stops->len - 1).offset)
+        gtk_css_parser_error_value (parser, "Color stop offset must be >= previous value");
+      else if (stop.offset > 1)
+        gtk_css_parser_error_value (parser, "Color stop offset must be <= 1");
+      else
+        {
+          g_array_append_val (stops, stop);
+          continue;
+        }
 
-      g_ptr_array_free (children, TRUE);
+      g_array_free (stops, TRUE);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "outset_shadow"))
+  while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA));
+
+  if (stops->len < 2)
     {
-      GskRoundedRect outline;
-      double color[4];
-      float dx, dy, spread, blur_radius;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_rounded_rect_param (p, "outline", &outline);
-
-      parse_double4_param (p, "color", color);
-      dx = parse_float_param (p, "dx");
-      dy = parse_float_param (p, "dy");
-      spread = parse_float_param (p, "spread");
-      blur_radius = parse_float_param (p, "blur_radius");
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_outset_shadow_node_new (&outline,
-                                           &(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                           dx, dy,
-                                           spread,
-                                           blur_radius);
+      gtk_css_parser_error_value (parser, "At least 2 color stops need to be specified");
+      g_array_free (stops, TRUE);
+      return FALSE;
     }
-  else if (gsk_css_token_is_ident (p->cur, "cross_fade"))
-    {
-      double progress;
-      GskRenderNode *start_child;
-      GskRenderNode *end_child;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  if (*(GArray **) out_stops)
+    g_array_free (*(GArray **) out_stops, TRUE);
+  *(GArray **) out_stops = stops;
 
-      progress = parse_float_param (p, "progress");
-      start_child = parse_node (p);
-      end_child = parse_node (p);
+  return parse_semicolon (parser);
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_cross_fade_node_new (start_child, end_child, progress);
+static gboolean
+parse_node (GtkCssParser *parser, gpointer out_node);
 
-      gsk_render_node_unref (start_child);
-      gsk_render_node_unref (end_child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "clip"))
+static GskRenderNode *
+parse_container_node (GtkCssParser *parser)
+{
+  GskRenderNode *node;
+  GPtrArray *nodes;
+  const GtkCssToken *token;
+  
+  nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref);
+
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
     {
-      double clip_rect[4];
-      GskRenderNode *child;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      parse_double4_param (p, "clip", clip_rect);
-      child = parse_node (p);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_clip_node_new (child,
-                                  &GRAPHENE_RECT_INIT (
-                                    clip_rect[0], clip_rect[1],
-                                    clip_rect[2], clip_rect[3]
-                                  ));
-
-      gsk_render_node_unref (child);
+      node = NULL;
+      if (parse_node (parser, &node))
+        {
+          g_ptr_array_add (nodes, node);
+        }
+      else
+        {
+          gtk_css_parser_skip_until (parser, GTK_CSS_TOKEN_OPEN_CURLY);
+          gtk_css_parser_skip (parser);
+        }
     }
-  else if (gsk_css_token_is_ident (p->cur, "rounded_clip"))
-    {
-      GskRoundedRect clip_rect;
-      GskRenderNode *child;
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  node = gsk_container_node_new ((GskRenderNode **) nodes->pdata, nodes->len);
 
-      parse_rounded_rect_param (p, "clip", &clip_rect);
-      child = parse_node (p);
+  g_ptr_array_unref (nodes);
 
+  return node;
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_rounded_clip_node_new (child, &clip_rect);
+static void
+parse_declarations_sync (GtkCssParser *parser)
+{
+  const GtkCssToken *token;
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "linear_gradient"))
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
     {
-      GArray *stops = g_array_new (FALSE, TRUE, sizeof (GskColorStop));
-      double bounds[4];
-      double start[2];
-      double end[2];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_double4_param (p, "bounds", bounds);
-      parse_tuple_param (p, "start", start);
-      parse_tuple_param (p, "end", end);
-
-      expect_skip_ident (p, "stops");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      while (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_SEMICOLON) ||
+          gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY))
         {
-          GskColorStop stop;
-          double color[4];
-
-          skip (p);
-          stop.offset = number_value (p);
-          skip (p);
-          expect_skip (p, GSK_CSS_TOKEN_COMMA);
-          parse_double4 (p, color);
-          expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
-          stop.color = (GdkRGBA) { color[0], color[1], color[2], color[3] };
-          g_array_append_val (stops, stop);
+          gtk_css_parser_skip (parser);
+          break;
         }
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_linear_gradient_node_new (&GRAPHENE_RECT_INIT (
-                                               bounds[0], bounds[1],
-                                               bounds[2], bounds[3]
-                                             ),
-                                             &(graphene_point_t) { start[0], start[1] },
-                                             &(graphene_point_t) { end[0], end[1] },
-                                             (GskColorStop *)stops->data,
-                                             stops->len);
-      g_array_free (stops, TRUE);
+      gtk_css_parser_skip (parser);
     }
-  else if (gsk_css_token_is_ident (p->cur, "transform"))
-    {
-      GskTransform *transform;
-      GskRenderNode *child;
+}
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      expect_skip_ident (p, "transform");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
+static guint
+parse_declarations (GtkCssParser      *parser,
+                    const Declaration *declarations,
+                    guint              n_declarations)
+{
+  guint parsed = 0;
+  guint i;
+  const GtkCssToken *token;
 
-      if (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
-        {
-          graphene_matrix_t matrix;
-          parse_matrix (p, &matrix);
+  g_assert (n_declarations < 8 * sizeof (guint));
 
-          transform = gsk_transform_matrix (NULL, &matrix);
-        }
-      else
+  for (token = gtk_css_parser_get_token (parser);
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (parser))
+    {
+      for (i = 0; i < n_declarations; i++)
         {
-          expect (p, GSK_CSS_TOKEN_IDENT);
-
-          for (transform = NULL;;)
+          if (gtk_css_token_is_ident (token, declarations[i].name))
             {
-              /* Transform name */
-              expect (p, GSK_CSS_TOKEN_IDENT);
-
-              if (lookahead (p, 1)->type == GSK_CSS_TOKEN_OPEN_CURLY) /* Start of child node */
-                break;
-
-              if (gsk_css_token_is_ident (p->cur, "translate"))
+              gtk_css_parser_consume_token (parser);
+              token = gtk_css_parser_get_token (parser);
+              if (!gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
                 {
-                  double offset[2];
-                  skip (p);
-                  parse_tuple (p, offset);
-                  transform = gsk_transform_translate (transform,
-                                                       &(graphene_point_t) { offset[0], offset[1] });
-
+                  gtk_css_parser_error_syntax (parser, "Expected ':' after variable declaration");
+                  parse_declarations_sync (parser);
                 }
               else
                 {
-                  g_error ("Unknown transform type: %s", gsk_css_token_to_string (p->cur));
+                  gtk_css_parser_consume_token (parser);
+                  if (parsed & (1 << i))
+                    gtk_css_parser_warn_syntax (parser, "Variable \"%s\" defined multiple times", 
declarations[i].name);
+                  if (declarations[i].parse_func (parser, declarations[i].result))
+                    parsed |= (1 << i);
+                  else
+                    parse_declarations_sync (parser);
                 }
+              break;
             }
         }
+      if (i == n_declarations)
+        {
+          if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+            gtk_css_parser_error_syntax (parser, "No variable named \"%s\"", token->string.string);
+          else
+            gtk_css_parser_error_syntax (parser, "Expected a variable name");
+          parse_declarations_sync (parser);
+        }
+    }
+
+  return parsed;
+}
+
+static GskRenderNode *
+parse_color_node (GtkCssParser *parser)
+{
+  graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  const Declaration declarations[] = {
+    { "bounds", parse_rect, &bounds },
+    { "color", parse_color, &color },
+  };
 
-      child = parse_node (p);
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_transform_node_new (child, transform);
+  return gsk_color_node_new (&color, &bounds);
+}
 
-      gsk_transform_unref (transform);
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "color_matrix"))
+static GskRenderNode *
+parse_linear_gradient_node (GtkCssParser *parser)
+{
+  graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  graphene_point_t start = GRAPHENE_POINT_INIT (0, 0);
+  graphene_point_t end = GRAPHENE_POINT_INIT (0, 0);
+  GArray *stops = NULL;
+  const Declaration declarations[] = {
+    { "bounds", parse_rect, &bounds },
+    { "start", parse_point, &start },
+    { "end", parse_point, &end },
+    { "stops", parse_stops, &stops },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (stops == NULL)
     {
-      double offset_values[4];
-      graphene_matrix_t matrix;
-      graphene_vec4_t offset;
-      GskRenderNode *child;
+      gtk_css_parser_error_syntax (parser, "No color stops given");
+      return NULL;
+    }
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  result = gsk_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
 
-      parse_matrix_param (p, "matrix", &matrix);
-      parse_double4_param (p, "offset", offset_values);
+  g_array_free (stops, TRUE);
 
-      graphene_vec4_init (&offset,
-                          offset_values[0],
-                          offset_values[1],
-                          offset_values[2],
-                          offset_values[3]);
+  return result;
+}
 
-      child = parse_node (p);
+static GskRenderNode *
+parse_inset_shadow_node (GtkCssParser *parser)
+{
+  GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  double dx, dy, blur, spread;
+  const Declaration declarations[] = {
+    { "outline", parse_rounded_rect, &outline },
+    { "color", parse_color, &color },
+    { "dx", parse_double, &dx },
+    { "dy", parse_double, &dy },
+    { "spread", parse_double, &spread },
+    { "blur", parse_double, &blur }
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+  return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_color_matrix_node_new (child, &matrix, &offset);
+static GskRenderNode *
+parse_outset_shadow_node (GtkCssParser *parser)
+{
+  GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GdkRGBA color = { 0, 0, 0, 0 };
+  double dx, dy, blur, spread;
+  const Declaration declarations[] = {
+    { "outline", parse_rounded_rect, &outline },
+    { "color", parse_color, &color },
+    { "dx", parse_double, &dx },
+    { "dy", parse_double, &dy },
+    { "spread", parse_double, &spread },
+    { "blur", parse_double, &blur }
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+  return gsk_outset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
 
-      gsk_render_node_unref (child);
-    }
-  else if (gsk_css_token_is_ident (p->cur, "texture"))
+static GskRenderNode *
+parse_transform_node (GtkCssParser *parser)
+{
+  GskRenderNode *child = NULL;
+  GskTransform *transform = NULL;
+  const Declaration declarations[] = {
+    { "transform", parse_transform, &transform },
+    { "child", parse_node, &child },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      G_GNUC_UNUSED guchar *texture_data;
-      gsize texture_data_len;
-      GdkTexture *texture;
-      double bounds[4];
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      gsk_transform_unref (transform);
+      return NULL;
+    }
+  /* This is very much cheating, isn't it? */
+  if (transform == NULL)
+    transform = gsk_transform_new ();
 
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+  result = gsk_transform_node_new (child, transform);
 
-      parse_double4_param (p, "bounds", bounds);
+  gsk_render_node_unref (child);
+  gsk_transform_unref (transform);
 
-      expect_skip (p, GSK_CSS_TOKEN_IDENT);
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-      expect (p, GSK_CSS_TOKEN_STRING);
+  return result;
+}
 
-      texture_data = g_base64_decode (p->cur->string.string, &texture_data_len);
-      guchar data[] = {1, 0, 0, 1, 0, 0,
-                       0, 0, 1, 0, 0, 1};
-      GBytes *b = g_bytes_new_static (data, 12);
+static GskRenderNode *
+parse_opacity_node (GtkCssParser *parser)
+{
+  GskRenderNode *child = NULL;
+  double opacity = 1.0;
+  const Declaration declarations[] = {
+    { "opacity", parse_double, &opacity },
+    { "child", parse_node, &child },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
+    {
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
+    }
 
-      /* TODO: :( */
-      texture = gdk_memory_texture_new (2, 2, GDK_MEMORY_R8G8B8,
-                                        b, 6);
+  result = gsk_opacity_node_new (child, opacity);
 
-      expect_skip (p, GSK_CSS_TOKEN_STRING);
+  gsk_render_node_unref (child);
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_texture_node_new (texture,
-                                     &GRAPHENE_RECT_INIT (
-                                       bounds[0], bounds[1],
-                                       bounds[2], bounds[3]
-                                     ));
+  return result;
+}
+
+static GskRenderNode *
+parse_cross_fade_node (GtkCssParser *parser)
+{
+  GskRenderNode *start = NULL;
+  GskRenderNode *end = NULL;
+  double progress = 0.5;
+  const Declaration declarations[] = {
+    { "progress", parse_double, &progress },
+    { "start", parse_node, &start },
+    { "end", parse_node, &end },
+  };
+  GskRenderNode *result;
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (start == NULL || end == NULL)
+    {
+      if (start == NULL)
+        gtk_css_parser_error_syntax (parser, "Missing \"start\" property definition");
+      if (end == NULL)
+        gtk_css_parser_error_syntax (parser, "Missing \"end\" property definition");
+      g_clear_pointer (&start, gsk_render_node_unref);
+      g_clear_pointer (&end, gsk_render_node_unref);
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "inset_shadow"))
+
+  result = gsk_cross_fade_node_new (start, end, progress);
+
+  gsk_render_node_unref (start);
+  gsk_render_node_unref (end);
+
+  return result;
+}
+
+static GskRenderNode *
+parse_clip_node (GtkCssParser *parser)
+{
+  graphene_rect_t clip = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "clip", parse_rect, &clip },
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      GskRoundedRect outline;
-      double color[4];
-      float dx, dy, spread, blur_radius;
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-      parse_rounded_rect_param (p, "outline", &outline);
-
-      parse_double4_param (p, "color", color);
-      dx = parse_float_param (p, "dx");
-      dy = parse_float_param (p, "dy");
-      spread = parse_float_param (p, "spread");
-      blur_radius = parse_float_param (p, "blur_radius");
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_inset_shadow_node_new (&outline,
-                                          &(GdkRGBA) { color[0], color[1], color[2], color[3] },
-                                          dx, dy,
-                                          spread,
-                                          blur_radius);
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "border"))
+
+  return gsk_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_rounded_clip_node (GtkCssParser *parser)
+{
+  GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "clip", parse_rounded_rect, &clip },
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      GskRoundedRect outline;
-      double widths[4];
-      double colors[4][4];
-
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
-      parse_rounded_rect_param (p, "outline", &outline);
-      parse_double4_param (p, "widths", widths);
-
-      expect_skip_ident (p, "colors");
-      expect_skip (p, GSK_CSS_TOKEN_COLON);
-
-      parse_double4 (p, colors[0]);
-      parse_double4 (p, colors[1]);
-      parse_double4 (p, colors[2]);
-      parse_double4 (p, colors[3]);
-
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
-      result = gsk_border_node_new (&outline,
-                                    (float[4]) { widths[0], widths[1], widths[2], widths[3] },
-                                    (GdkRGBA[4]) {
-                                      (GdkRGBA) { colors[0][0], colors[0][1], colors[0][2], colors[0][3] },
-                                      (GdkRGBA) { colors[1][0], colors[1][1], colors[1][2], colors[1][3] },
-                                      (GdkRGBA) { colors[2][0], colors[2][1], colors[2][2], colors[2][3] },
-                                      (GdkRGBA) { colors[3][0], colors[3][1], colors[3][2], colors[3][3] },
-                                    });
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
     }
-  else if (gsk_css_token_is_ident (p->cur, "text"))
+
+  return gsk_rounded_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_debug_node (GtkCssParser *parser)
+{
+  char *message = NULL;
+  GskRenderNode *child = NULL;
+  const Declaration declarations[] = {
+    { "message", parse_string, &message},
+    { "child", parse_node, &child },
+  };
+
+  parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+  if (child == NULL)
     {
-      skip (p);
-      expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+      gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+      return NULL;
+    }
 
-      expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
+  return gsk_debug_node_new (child, message);
+}
 
-      result = gsk_color_node_new (
-                                   &(GdkRGBA) { 0, 1, 0, 1 },
-                                   &GRAPHENE_RECT_INIT (0, 0, 0, 0));
+static gboolean
+parse_node (GtkCssParser *parser,
+            gpointer      out_node)
+{
+  static struct {
+    const char *name;
+    GskRenderNode * (* func) (GtkCssParser *);
+  } node_parsers[] = {
+    { "container", parse_container_node },
+    { "color", parse_color_node },
+#if 0
+    { "cairo", parse_cairo_node },
+#endif
+    { "linear-gradient", parse_linear_gradient_node },
+#if 0
+    { "border", parse_border_node },
+    { "texture", parse_texture_node },
+#endif
+    { "inset-shadow", parse_inset_shadow_node },
+    { "outset-shadow", parse_outset_shadow_node },
+    { "transform", parse_transform_node },
+    { "opacity", parse_opacity_node },
+#if 0
+    { "color-matrix", parse_color-matrix_node },
+    { "repeat", parse_repeat_node },
+#endif
+    { "clip", parse_clip_node },
+    { "rounded-clip", parse_rounded_clip_node },
+#if 0
+    { "shadow", parse_shadow_node },
+    { "blend", parse_blend_node },
+#endif
+    { "cross-fade", parse_cross_fade_node },
+#if 0
+    { "text", parse_text_node },
+    { "blur", parse_blur_node },
+#endif
+    { "debug", parse_debug_node }
+  };
+  const GtkCssToken *token;
+  guint i;
+  
+  token = gtk_css_parser_get_token (parser);
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a node name");
+      return FALSE;
     }
-  else
+
+  for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
     {
-      g_error ("Unknown render node type: %s", gsk_css_token_to_string (p->cur));
+      if (gtk_css_token_is_ident (token, node_parsers[i].name))
+        {
+          GskRenderNode *node;
+
+          gtk_css_parser_consume_token (parser);
+          token = gtk_css_parser_get_token (parser);
+          if (!gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY))
+            {
+              gtk_css_parser_error_syntax (parser, "Expected '{' after node name");
+              return FALSE;
+            }
+          gtk_css_parser_start_block (parser);
+          node = node_parsers[i].func (parser);
+          if (node)
+            {
+              token = gtk_css_parser_get_token (parser);
+              if (!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+                gtk_css_parser_error_syntax (parser, "Expected '}' at end of node definition");
+              g_clear_pointer ((GskRenderNode **) out_node, gsk_render_node_unref);
+              *(GskRenderNode **) out_node = node;
+            }
+          gtk_css_parser_end_block (parser);
+
+          return node != NULL;
+        }
     }
 
-  return result;
+  gtk_css_parser_error_value (parser, "\"%s\" is not a valid node name", token->string.string);
+  return FALSE;
+}
+
+static void
+gsk_render_node_parser_error (GtkCssParser         *parser,
+                              const GtkCssLocation *start,
+                              const GtkCssLocation *end,
+                              const GError         *error,
+                              gpointer              user_data)
+{
+  g_print ("ERROR: %zu:%zu: %s\n",
+           start->lines, start->line_chars,
+           error->message);
 }
 
 /**
@@ -672,16 +896,12 @@ GskRenderNode *
 gsk_render_node_deserialize_from_bytes (GBytes *bytes)
 {
   GskRenderNode *root = NULL;
-  GskCssToken *tokens;
-  int n_tokens;
-  Parser parser;
-
-  tokens = tokenize (bytes, &n_tokens);
+  GtkCssParser *parser;
 
-  parser_init (&parser, tokens, n_tokens);
-  root = parse_node (&parser);
+  parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, gsk_render_node_parser_error, NULL, NULL);
+  root = parse_container_node (parser);
 
-  g_free (tokens);
+  gtk_css_parser_unref (parser);
 
   return root;
 }
diff --git a/gsk/gsktransform.c b/gsk/gsktransform.c
index 1dfd9b9740..3ada22ff8a 100644
--- a/gsk/gsktransform.c
+++ b/gsk/gsktransform.c
@@ -1695,6 +1695,7 @@ gsk_transform_parser_parse (GtkCssParser  *parser,
   if (gtk_css_token_is_ident (token, "none"))
     {
       gtk_css_parser_consume_token (parser);
+
       *out_transform = NULL;
       return TRUE;
     }
@@ -1902,6 +1903,7 @@ gsk_transform_parse (const char    *string,
       result = FALSE;
     }
   gtk_css_parser_unref (parser);
+
   g_bytes_unref (bytes);
 
   return result; 
diff --git a/gtk/gtkcssprovider.h b/gtk/gtkcssprovider.h
index 051305e573..ed791174a5 100644
--- a/gtk/gtkcssprovider.h
+++ b/gtk/gtkcssprovider.h
@@ -30,6 +30,29 @@ G_BEGIN_DECLS
 #define GTK_IS_CSS_PROVIDER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE    ((c), GTK_TYPE_CSS_PROVIDER))
 #define GTK_CSS_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS  ((o), GTK_TYPE_CSS_PROVIDER, 
GtkCssProviderClass))
 
+/**
+ * GtkCssProviderError:
+ * @GTK_CSS_PROVIDER_ERROR_FAILED: Failed.
+ * @GTK_CSS_PROVIDER_ERROR_SYNTAX: Syntax error.
+ * @GTK_CSS_PROVIDER_ERROR_IMPORT: Import error.
+ * @GTK_CSS_PROVIDER_ERROR_NAME: Name error.
+ * @GTK_CSS_PROVIDER_ERROR_DEPRECATED: Deprecation error.
+ * @GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE: Unknown value.
+ * @GTK_CSS_PROVIDER_WARN_GENERAL: A general warning.
+ *
+ * Error codes for %GTK_CSS_PROVIDER_ERROR.
+ */
+typedef enum
+{
+  GTK_CSS_PROVIDER_ERROR_FAILED,
+  GTK_CSS_PROVIDER_ERROR_SYNTAX,
+  GTK_CSS_PROVIDER_ERROR_IMPORT,
+  GTK_CSS_PROVIDER_ERROR_NAME,
+  GTK_CSS_PROVIDER_ERROR_DEPRECATED,
+  GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
+  GTK_CSS_PROVIDER_WARN_GENERAL,
+} GtkCssProviderError;
+
 typedef struct _GtkCssProvider GtkCssProvider;
 typedef struct _GtkCssProviderClass GtkCssProviderClass;
 typedef struct _GtkCssProviderPrivate GtkCssProviderPrivate;



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