[gtk+] css: Rewrite the parser



commit 7ccb9db79e702e507dedf211ed25787be2f32721
Author: Benjamin Otte <otte redhat com>
Date:   Thu Apr 14 04:47:18 2011 +0200

    css: Rewrite the parser
    
    Instead of relying on GScanner and its idea of syntax, code up a parser
    that obeys the CSS spec.
    This also has the great side effect of reporting correct line numbers
    and positions.
    
    Also included is a reorganization of the returned error values. Instead
    of error values describing what type of syntax error was returned, the
    code just returns SYNTAX_ERROR. Other messages exist for when actual
    values don't work or when errors shouldn't be fatal due to backwards
    compatibility.

 gtk/Makefile.am                        |    2 +
 gtk/gtkcssparser.c                     |  938 +++++++++++++++++++++++++
 gtk/gtkcssparserprivate.h              |   85 +++
 gtk/gtkcssprovider.c                   | 1202 ++++++++++++++++++--------------
 gtk/gtkcssprovider.h                   |   10 +-
 gtk/gtkcssstringfuncs.c                |  632 +++++------------
 gtk/gtkcssstringfuncsprivate.h         |    3 -
 tests/css/parser/boolean.errors        |   16 +-
 tests/css/parser/border.errors         |   16 +-
 tests/css/parser/does-not-exist.errors |    2 +-
 tests/css/parser/integer.errors        |   14 +-
 11 files changed, 1902 insertions(+), 1018 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 6ca52ec..f675830 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -386,6 +386,7 @@ gtk_private_h_sources =		\
 	gtkbuilderprivate.h	\
 	gtkbuttonprivate.h	\
 	gtkcellareaboxcontextprivate.h	\
+	gtkcssparserprivate.h	\
 	gtkcssproviderprivate.h	\
 	gtkcssstringfuncsprivate.h	\
 	gtkcustompaperunixdialog.h \
@@ -512,6 +513,7 @@ gtk_base_c_sources = 		\
 	gtkcombobox.c		\
 	gtkcomboboxtext.c	\
 	gtkcontainer.c		\
+	gtkcssparser.c		\
 	gtkcssprovider.c	\
 	gtkcssstringfuncs.c	\
 	gtkdialog.c		\
diff --git a/gtk/gtkcssparser.c b/gtk/gtkcssparser.c
new file mode 100644
index 0000000..de798e7
--- /dev/null
+++ b/gtk/gtkcssparser.c
@@ -0,0 +1,938 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * 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 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtkcssparserprivate.h"
+
+#include <errno.h>
+#include <string.h>
+
+/* just for the errors, yay! */
+#include "gtkcssprovider.h"
+
+#define NEWLINE_CHARS "\r\n"
+#define WHITESPACE_CHARS "\f \t"
+#define NMSTART "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+#define NMCHAR NMSTART "01234567890-_"
+#define URLCHAR NMCHAR "!#$%&*~"
+
+#define GTK_IS_CSS_PARSER(parser) ((parser) != NULL)
+
+struct _GtkCssParser
+{
+  const char *data;
+  GtkCssParserErrorFunc error_func;
+  gpointer              user_data;
+
+  const char *line_start;
+  guint line;
+};
+
+GtkCssParser *
+_gtk_css_parser_new (const char            *data,
+                     GtkCssParserErrorFunc  error_func,
+                     gpointer               user_data)
+{
+  GtkCssParser *parser;
+
+  g_return_val_if_fail (data != NULL, NULL);
+
+  parser = g_slice_new0 (GtkCssParser);
+
+  parser->data = data;
+  parser->error_func = error_func;
+  parser->user_data = user_data;
+
+  parser->line_start = data;
+  parser->line = 1;
+
+  return parser;
+}
+
+void
+_gtk_css_parser_free (GtkCssParser *parser)
+{
+  g_return_if_fail (GTK_IS_CSS_PARSER (parser));
+
+  g_slice_free (GtkCssParser, parser);
+}
+
+gboolean
+_gtk_css_parser_is_eof (GtkCssParser *parser)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
+
+  return *parser->data == 0;
+}
+
+gboolean
+_gtk_css_parser_begins_with (GtkCssParser *parser,
+                             char          c)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), TRUE);
+
+  return *parser->data == c;
+}
+
+guint
+_gtk_css_parser_get_line (GtkCssParser *parser)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
+
+  return parser->line;
+}
+
+guint
+_gtk_css_parser_get_position (GtkCssParser *parser)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), 1);
+
+  return parser->data - parser->line_start;
+}
+
+void
+_gtk_css_parser_error (GtkCssParser *parser,
+                       const char   *format,
+                       ...)
+{
+  GError *error;
+
+  va_list args;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
+                              GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                              format, args);
+  va_end (args);
+
+  parser->error_func (parser, error, parser->user_data);
+
+  g_error_free (error);
+}
+
+static gboolean
+gtk_css_parser_new_line (GtkCssParser *parser)
+{
+  gboolean result = FALSE;
+
+  if (*parser->data == '\r')
+    {
+      result = TRUE;
+      parser->data++;
+    }
+  if (*parser->data == '\n')
+    {
+      result = TRUE;
+      parser->data++;
+    }
+
+  if (result)
+    {
+      parser->line++;
+      parser->line_start = parser->data;
+    }
+
+  return result;
+}
+
+static gboolean
+gtk_css_parser_skip_comment (GtkCssParser *parser)
+{
+  if (parser->data[0] != '/' ||
+      parser->data[1] != '*')
+    return FALSE;
+
+  parser->data += 2;
+
+  while (*parser->data)
+    {
+      gsize len = strcspn (parser->data, NEWLINE_CHARS "/");
+
+      parser->data += len;
+  
+      if (gtk_css_parser_new_line (parser))
+        continue;
+
+      parser->data++;
+
+      if (parser->data[-2] == '*')
+        return TRUE;
+      if (parser->data[0] == '*')
+        _gtk_css_parser_error (parser, "'/*' in comment block");
+    }
+
+  /* FIXME: position */
+  _gtk_css_parser_error (parser, "Unterminated comment");
+  return TRUE;
+}
+
+void
+_gtk_css_parser_skip_whitespace (GtkCssParser *parser)
+{
+  size_t len;
+
+  while (*parser->data)
+    {
+      if (gtk_css_parser_new_line (parser))
+        continue;
+
+      len = strspn (parser->data, WHITESPACE_CHARS);
+      if (len)
+        {
+          parser->data += len;
+          continue;
+        }
+      
+      if (!gtk_css_parser_skip_comment (parser))
+        break;
+    }
+}
+
+gboolean
+_gtk_css_parser_try (GtkCssParser *parser,
+                     const char   *string,
+                     gboolean      skip_whitespace)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  if (g_ascii_strncasecmp (parser->data, string, strlen (string)) != 0)
+    return FALSE;
+
+  parser->data += strlen (string);
+
+  if (skip_whitespace)
+    _gtk_css_parser_skip_whitespace (parser);
+  return TRUE;
+}
+
+static guint
+get_xdigit (char c)
+{
+  if (c >= 'a')
+    return c - 'a' + 10;
+  else if (c >= 'A')
+    return c - 'A' + 10;
+  else
+    return c - '0';
+}
+
+static void
+_gtk_css_parser_unescape (GtkCssParser *parser,
+                          GString      *str)
+{
+  guint i;
+  gunichar result = 0;
+
+  g_assert (*parser->data == '\\');
+
+  parser->data++;
+
+  for (i = 0; i < 6; i++)
+    {
+      if (!g_ascii_isxdigit (parser->data[i]))
+        break;
+
+      result = (result << 4) + get_xdigit (parser->data[i]);
+    }
+
+  if (i != 0)
+    {
+      g_string_append_unichar (str, result);
+      parser->data += i;
+
+      /* NB: gtk_css_parser_new_line() forward data pointer itself */
+      if (!gtk_css_parser_new_line (parser) &&
+          *parser->data &&
+          strchr (WHITESPACE_CHARS, *parser->data))
+        parser->data++;
+      return;
+    }
+
+  if (gtk_css_parser_new_line (parser))
+    return;
+
+  g_string_append_c (str, *parser->data);
+  parser->data++;
+
+  return;
+}
+
+static gboolean
+_gtk_css_parser_read_char (GtkCssParser *parser,
+                           GString *     str,
+                           const char *  allowed)
+{
+  if (*parser->data == 0)
+    return FALSE;
+
+  if (strchr (allowed, *parser->data))
+    {
+      g_string_append_c (str, *parser->data);
+      parser->data++;
+      return TRUE;
+    }
+  if (*parser->data >= 127)
+    {
+      gsize len = g_utf8_skip[(guint) *(guchar *) parser->data];
+
+      g_string_append_len (str, parser->data, len);
+      parser->data += len;
+      return TRUE;
+    }
+  if (*parser->data == '\\')
+    {
+      _gtk_css_parser_unescape (parser, str);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+char *
+_gtk_css_parser_try_name (GtkCssParser *parser,
+                          gboolean      skip_whitespace)
+{
+  GString *name;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  name = g_string_new (NULL);
+
+  while (_gtk_css_parser_read_char (parser, name, NMCHAR))
+    ;
+
+  if (skip_whitespace)
+    _gtk_css_parser_skip_whitespace (parser);
+
+  return g_string_free (name, FALSE);
+}
+
+char *
+_gtk_css_parser_try_ident (GtkCssParser *parser,
+                           gboolean      skip_whitespace)
+{
+  const char *start;
+  GString *ident;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  start = parser->data;
+  
+  ident = g_string_new (NULL);
+
+  if (*parser->data == '-')
+    {
+      g_string_append_c (ident, '-');
+      parser->data++;
+    }
+
+  if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
+    {
+      parser->data = start;
+      g_string_free (ident, TRUE);
+      return NULL;
+    }
+
+  while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
+    ;
+
+  if (skip_whitespace)
+    _gtk_css_parser_skip_whitespace (parser);
+
+  return g_string_free (ident, FALSE);
+}
+
+gboolean
+_gtk_css_parser_is_string (GtkCssParser *parser)
+{
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
+
+  return *parser->data == '"' || *parser->data == '\'';
+}
+
+char *
+_gtk_css_parser_read_string (GtkCssParser *parser)
+{
+  GString *str;
+  char quote;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  quote = *parser->data;
+  
+  if (quote != '"' && quote != '\'')
+    return NULL;
+  
+  parser->data++;
+  str = g_string_new (NULL);
+
+  while (TRUE)
+    {
+      gsize len = strcspn (parser->data, "\\'\"\n\r\f");
+
+      g_string_append_len (str, parser->data, len);
+
+      parser->data += len;
+
+      switch (*parser->data)
+        {
+        case '\\':
+          _gtk_css_parser_unescape (parser, str);
+          break;
+        case '"':
+        case '\'':
+          if (*parser->data == quote)
+            {
+              parser->data++;
+              _gtk_css_parser_skip_whitespace (parser);
+              return g_string_free (str, FALSE);
+            }
+          
+          g_string_append_c (str, *parser->data);
+          parser->data++;
+          break;
+        case '\0':
+          /* FIXME: position */
+          _gtk_css_parser_error (parser, "Missing end quote in string.");
+          g_string_free (str, TRUE);
+          return NULL;
+        default:
+          _gtk_css_parser_error (parser, 
+                                 "Invalid character in string. Must be escaped.");
+          g_string_free (str, TRUE);
+          return NULL;
+        }
+    }
+
+  g_assert_not_reached ();
+  return NULL;
+}
+
+char *
+_gtk_css_parser_read_uri (GtkCssParser *parser)
+{
+  char *result;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  if (!_gtk_css_parser_try (parser, "url(", TRUE))
+    {
+      _gtk_css_parser_error (parser, "expected 'url('");
+      return NULL;
+    }
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  if (_gtk_css_parser_is_string (parser))
+    {
+      result = _gtk_css_parser_read_string (parser);
+    }
+  else
+    {
+      GString *str = g_string_new (NULL);
+
+      while (_gtk_css_parser_read_char (parser, str, URLCHAR))
+        ;
+      result = g_string_free (str, FALSE);
+      if (result == NULL)
+        _gtk_css_parser_error (parser, "not a url");
+    }
+  
+  if (result == NULL)
+    return NULL;
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  if (*parser->data != ')')
+    {
+      _gtk_css_parser_error (parser, "missing ')' for url");
+      g_free (result);
+      return NULL;
+    }
+
+  parser->data++;
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  return result;
+}
+
+gboolean
+_gtk_css_parser_try_int (GtkCssParser *parser,
+                         int          *value)
+{
+  gint64 result;
+  char *end;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  /* strtoll parses a plus, but we are not allowed to */
+  if (*parser->data == '+')
+    return FALSE;
+
+  errno = 0;
+  result = g_ascii_strtoll (parser->data, &end, 10);
+  if (errno)
+    return FALSE;
+  if (result > G_MAXINT || result < G_MININT)
+    return FALSE;
+  if (parser->data == end)
+    return FALSE;
+
+  parser->data = end;
+  *value = result;
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  return TRUE;
+}
+
+gboolean
+_gtk_css_parser_try_uint (GtkCssParser *parser,
+                          uint         *value)
+{
+  guint64 result;
+  char *end;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  errno = 0;
+  result = g_ascii_strtoull (parser->data, &end, 10);
+  if (errno)
+    return FALSE;
+  if (result > G_MAXUINT)
+    return FALSE;
+  if (parser->data == end)
+    return FALSE;
+
+  parser->data = end;
+  *value = result;
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  return TRUE;
+}
+
+gboolean
+_gtk_css_parser_try_double (GtkCssParser *parser,
+                            gdouble      *value)
+{
+  gdouble result;
+  char *end;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  errno = 0;
+  result = g_ascii_strtod (parser->data, &end);
+  if (errno)
+    return FALSE;
+  if (parser->data == end)
+    return FALSE;
+
+  parser->data = end;
+  *value = result;
+
+  _gtk_css_parser_skip_whitespace (parser);
+
+  return TRUE;
+}
+
+typedef enum {
+  COLOR_RGBA,
+  COLOR_RGB,
+  COLOR_LIGHTER,
+  COLOR_DARKER,
+  COLOR_SHADE,
+  COLOR_ALPHA,
+  COLOR_MIX
+} ColorType;
+
+static GtkSymbolicColor *
+gtk_css_parser_read_symbolic_color_function (GtkCssParser *parser,
+                                             ColorType     color)
+{
+  GtkSymbolicColor *symbolic;
+  GtkSymbolicColor *child1, *child2;
+  double value;
+
+  if (!_gtk_css_parser_try (parser, "(", TRUE))
+    {
+      _gtk_css_parser_error (parser, "Missing opening bracket in color definition");
+      return NULL;
+    }
+
+  if (color == COLOR_RGB || color == COLOR_RGBA)
+    {
+      GdkRGBA rgba;
+      double tmp;
+      guint i;
+
+      for (i = 0; i < 3; i++)
+        {
+          if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
+            {
+              _gtk_css_parser_error (parser, "Expected ',' in color definition");
+              return NULL;
+            }
+
+          if (!_gtk_css_parser_try_double (parser, &tmp))
+            {
+              _gtk_css_parser_error (parser, "Invalid number for color value");
+              return NULL;
+            }
+          if (_gtk_css_parser_try (parser, "%", TRUE))
+            tmp /= 100.0;
+          else
+            tmp /= 255.0;
+          if (i == 0)
+            rgba.red = tmp;
+          else if (i == 1)
+            rgba.green = tmp;
+          else if (i == 2)
+            rgba.blue = tmp;
+          else
+            g_assert_not_reached ();
+        }
+
+      if (color == COLOR_RGBA)
+        {
+          if (i > 0 && !_gtk_css_parser_try (parser, ",", TRUE))
+            {
+              _gtk_css_parser_error (parser, "Expected ',' in color definition");
+              return NULL;
+            }
+
+          if (!_gtk_css_parser_try_double (parser, &rgba.alpha))
+            {
+              _gtk_css_parser_error (parser, "Invalid number for alpha value");
+              return NULL;
+            }
+        }
+      else
+        rgba.alpha = 1.0;
+      
+      symbolic = gtk_symbolic_color_new_literal (&rgba);
+    }
+  else
+    {
+      child1 = _gtk_css_parser_read_symbolic_color (parser);
+      if (child1 == NULL)
+        return NULL;
+
+      if (color == COLOR_MIX)
+        {
+          if (!_gtk_css_parser_try (parser, ",", TRUE))
+            {
+              _gtk_css_parser_error (parser, "Expected ',' in color definition");
+              gtk_symbolic_color_unref (child1);
+              return NULL;
+            }
+
+          child2 = _gtk_css_parser_read_symbolic_color (parser);
+          if (child2 == NULL)
+            {
+              g_object_unref (child1);
+              return NULL;
+            }
+        }
+      else
+        child2 = NULL;
+
+      if (color == COLOR_LIGHTER)
+        value = 1.3;
+      else if (color == COLOR_DARKER)
+        value = 0.7;
+      else
+        {
+          if (!_gtk_css_parser_try (parser, ",", TRUE))
+            {
+              _gtk_css_parser_error (parser, "Expected ',' in color definition");
+              gtk_symbolic_color_unref (child1);
+              if (child2)
+                gtk_symbolic_color_unref (child2);
+              return NULL;
+            }
+
+          if (!_gtk_css_parser_try_double (parser, &value))
+            {
+              _gtk_css_parser_error (parser, "Expected number in color definition");
+              gtk_symbolic_color_unref (child1);
+              if (child2)
+                gtk_symbolic_color_unref (child2);
+              return NULL;
+            }
+        }
+      
+      switch (color)
+        {
+        case COLOR_LIGHTER:
+        case COLOR_DARKER:
+        case COLOR_SHADE:
+          symbolic = gtk_symbolic_color_new_shade (child1, value);
+          break;
+        case COLOR_ALPHA:
+          symbolic = gtk_symbolic_color_new_alpha (child1, value);
+          break;
+        case COLOR_MIX:
+          symbolic = gtk_symbolic_color_new_mix (child1, child2, value);
+          break;
+        default:
+          g_assert_not_reached ();
+          symbolic = NULL;
+        }
+
+      gtk_symbolic_color_unref (child1);
+      if (child2)
+        gtk_symbolic_color_unref (child2);
+    }
+
+  if (!_gtk_css_parser_try (parser, ")", TRUE))
+    {
+      gtk_symbolic_color_unref (symbolic);
+      return NULL;
+    }
+
+  return symbolic;
+}
+
+static GtkSymbolicColor *
+gtk_css_parser_try_hash_color (GtkCssParser *parser)
+{
+  if (parser->data[0] == '#' &&
+      g_ascii_isxdigit (parser->data[1]) &&
+      g_ascii_isxdigit (parser->data[2]) &&
+      g_ascii_isxdigit (parser->data[3]))
+    {
+      GdkRGBA rgba;
+      
+      if (g_ascii_isxdigit (parser->data[4]) &&
+          g_ascii_isxdigit (parser->data[5]) &&
+          g_ascii_isxdigit (parser->data[6]))
+        {
+          rgba.red   = ((get_xdigit (parser->data[1]) << 4) + get_xdigit (parser->data[2])) / 255.0;
+          rgba.green = ((get_xdigit (parser->data[3]) << 4) + get_xdigit (parser->data[4])) / 255.0;
+          rgba.blue  = ((get_xdigit (parser->data[5]) << 4) + get_xdigit (parser->data[6])) / 255.0;
+          rgba.alpha = 1.0;
+          parser->data += 7;
+        }
+      else
+        {
+          rgba.red   = get_xdigit (parser->data[1]) / 15.0;
+          rgba.green = get_xdigit (parser->data[2]) / 15.0;
+          rgba.blue  = get_xdigit (parser->data[3]) / 15.0;
+          rgba.alpha = 1.0;
+          parser->data += 4;
+        }
+
+      _gtk_css_parser_skip_whitespace (parser);
+
+      return gtk_symbolic_color_new_literal (&rgba);
+    }
+
+  return NULL;
+}
+
+GtkSymbolicColor *
+_gtk_css_parser_read_symbolic_color (GtkCssParser *parser)
+{
+  GtkSymbolicColor *symbolic;
+  guint color;
+  const char *names[] = {"rgba", "rgb",  "lighter", "darker", "shade", "alpha", "mix" };
+  char *name;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  if (_gtk_css_parser_try (parser, "@", FALSE))
+    {
+      name = _gtk_css_parser_try_name (parser, TRUE);
+
+      if (name)
+        {
+          symbolic = gtk_symbolic_color_new_name (name);
+        }
+      else
+        {
+          _gtk_css_parser_error (parser, "'%s' is not a valid symbolic color name", name);
+          symbolic = NULL;
+        }
+
+      g_free (name);
+      return symbolic;
+    }
+
+  for (color = 0; color < G_N_ELEMENTS (names); color++)
+    {
+      if (_gtk_css_parser_try (parser, names[color], TRUE))
+        break;
+    }
+
+  if (color < G_N_ELEMENTS (names))
+    return gtk_css_parser_read_symbolic_color_function (parser, color);
+
+  symbolic = gtk_css_parser_try_hash_color (parser);
+  if (symbolic)
+    return symbolic;
+
+  name = _gtk_css_parser_try_name (parser, TRUE);
+  if (name)
+    {
+      GdkRGBA rgba;
+
+      if (gdk_rgba_parse (&rgba, name))
+        {
+          symbolic = gtk_symbolic_color_new_literal (&rgba);
+        }
+      else
+        {
+          _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
+          symbolic = NULL;
+        }
+      g_free (name);
+      return symbolic;
+    }
+
+  _gtk_css_parser_error (parser, "Not a color definition");
+  return NULL;
+}
+
+void
+_gtk_css_parser_resync_internal (GtkCssParser *parser,
+                                 gboolean      sync_at_semicolon,
+                                 gboolean      read_sync_token,
+                                 char          terminator)
+{
+  gsize len;
+
+  do {
+    len = strcspn (parser->data, "\\\"'/()[]{};" NEWLINE_CHARS);
+    parser->data += len;
+
+    if (gtk_css_parser_new_line (parser))
+      continue;
+
+    if (_gtk_css_parser_is_string (parser))
+      {
+        /* Hrm, this emits errors, and i suspect it shouldn't... */
+        char *free_me = _gtk_css_parser_read_string (parser);
+        g_free (free_me);
+        continue;
+      }
+
+    if (gtk_css_parser_skip_comment (parser))
+      continue;
+
+    switch (*parser->data)
+      {
+      case '/':
+        {
+          GString *ignore = g_string_new (NULL);
+          _gtk_css_parser_unescape (parser, ignore);
+          g_string_free (ignore, TRUE);
+        }
+        break;
+      case ';':
+        if (sync_at_semicolon && !read_sync_token)
+          return;
+        parser->data++;
+        if (sync_at_semicolon)
+          {
+            _gtk_css_parser_skip_whitespace (parser);
+            return;
+          }
+        break;
+      case '(':
+        parser->data++;
+        _gtk_css_parser_resync (parser, FALSE, ')');
+        parser->data++;
+        break;
+      case '[':
+        parser->data++;
+        _gtk_css_parser_resync (parser, FALSE, ']');
+        parser->data++;
+        break;
+      case '{':
+        parser->data++;
+        _gtk_css_parser_resync (parser, FALSE, '}');
+        parser->data++;
+        if (sync_at_semicolon || !terminator)
+          {
+            _gtk_css_parser_skip_whitespace (parser);
+            return;
+          }
+        break;
+      case '}':
+      case ')':
+      case ']':
+        if (terminator == *parser->data)
+          {
+            _gtk_css_parser_skip_whitespace (parser);
+            return;
+          }
+        parser->data++;
+        continue;
+      default:
+        break;
+      }
+  } while (*parser->data);
+}
+
+char *
+_gtk_css_parser_read_value (GtkCssParser *parser)
+{
+  const char *start;
+  char *result;
+
+  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
+
+  start = parser->data;
+
+  /* This needs to be done better */
+  _gtk_css_parser_resync_internal (parser, TRUE, FALSE, '}');
+
+  result = g_strndup (start, parser->data - start);
+  if (result)
+    {
+      g_strchomp (result);
+      if (result[0] == 0)
+        {
+          g_free (result);
+          result = NULL;
+        }
+    }
+
+  if (result == NULL)
+    _gtk_css_parser_error (parser, "Expected a property value");
+
+  return result;
+}
+
+void
+_gtk_css_parser_resync (GtkCssParser *parser,
+                        gboolean      sync_at_semicolon,
+                        char          terminator)
+{
+  g_return_if_fail (GTK_IS_CSS_PARSER (parser));
+
+  _gtk_css_parser_resync_internal (parser, sync_at_semicolon, TRUE, terminator);
+}
diff --git a/gtk/gtkcssparserprivate.h b/gtk/gtkcssparserprivate.h
new file mode 100644
index 0000000..609b9f5
--- /dev/null
+++ b/gtk/gtkcssparserprivate.h
@@ -0,0 +1,85 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * 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 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_CSS_PARSER_PRIVATE_H__
+#define __GTK_CSS_PARSER_PRIVATE_H__
+
+#include <gtk/gtksymboliccolor.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssParser GtkCssParser;
+
+typedef void (* GtkCssParserErrorFunc) (GtkCssParser *parser,
+                                        const GError *error,
+                                        gpointer      user_data);
+
+GtkCssParser *  _gtk_css_parser_new               (const char            *data,
+                                                   GtkCssParserErrorFunc  error_func,
+                                                   gpointer               user_data);
+void            _gtk_css_parser_free              (GtkCssParser          *parser);
+
+void            _gtk_css_parser_error             (GtkCssParser          *parser,
+                                                   const char            *format,
+                                                    ...) G_GNUC_PRINTF (2, 3);
+
+guint           _gtk_css_parser_get_line          (GtkCssParser          *parser);
+guint           _gtk_css_parser_get_position      (GtkCssParser          *parser);
+
+gboolean        _gtk_css_parser_is_eof            (GtkCssParser          *parser);
+gboolean        _gtk_css_parser_begins_with       (GtkCssParser          *parser,
+                                                   char                   c);
+gboolean        _gtk_css_parser_is_string         (GtkCssParser          *parser);
+
+/* IMPORTANT:
+ * _try_foo() functions do not modify the data pointer if they fail, nor do they
+ * signal an error. _read_foo() will modify the data pointer and position it at
+ * the first token that is broken and emit an error about the failure.
+ * So only call _read_foo() when you know that you are reading a foo. _try_foo()
+ * however is fine to call if you don't know yet if the token is a foo or a bar,
+ * you can _try_bar() if try_foo() failed.
+ */
+gboolean        _gtk_css_parser_try               (GtkCssParser          *parser,
+                                                   const char            *string,
+                                                   gboolean               skip_whitespace);
+char *          _gtk_css_parser_try_ident         (GtkCssParser          *parser,
+                                                   gboolean               skip_whitespace);
+char *          _gtk_css_parser_try_name          (GtkCssParser          *parser,
+                                                   gboolean               skip_whitespace);
+gboolean        _gtk_css_parser_try_int           (GtkCssParser          *parser,
+                                                   int                   *value);
+gboolean        _gtk_css_parser_try_uint          (GtkCssParser          *parser,
+                                                   uint                  *value);
+gboolean        _gtk_css_parser_try_double        (GtkCssParser          *parser,
+                                                   gdouble               *value);
+
+void            _gtk_css_parser_skip_whitespace   (GtkCssParser          *parser);
+char *          _gtk_css_parser_read_string       (GtkCssParser          *parser);
+char *          _gtk_css_parser_read_uri          (GtkCssParser          *parser);
+char *          _gtk_css_parser_read_value        (GtkCssParser          *parser);
+GtkSymbolicColor *_gtk_css_parser_read_symbolic_color
+                                                  (GtkCssParser          *parser);
+
+void            _gtk_css_parser_resync            (GtkCssParser          *parser,
+                                                   gboolean               sync_at_semicolon,
+                                                   char                   terminator);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_PARSER_PRIVATE_H__ */
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index b65af8c..05b0f5a 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -27,6 +27,7 @@
 
 #include "gtkcssproviderprivate.h"
 
+#include "gtkcssparserprivate.h"
 #include "gtkcssstringfuncsprivate.h"
 #include "gtksymboliccolor.h"
 #include "gtkstyleprovider.h"
@@ -735,7 +736,7 @@
 typedef struct SelectorElement SelectorElement;
 typedef struct SelectorPath SelectorPath;
 typedef struct SelectorStyleInfo SelectorStyleInfo;
-typedef struct _GtkCssScannerPrivate GtkCssScannerPrivate;
+typedef struct _GtkCssScanner GtkCssScanner;
 typedef enum SelectorElementType SelectorElementType;
 typedef enum CombinatorType CombinatorType;
 typedef enum ParserScope ParserScope;
@@ -786,9 +787,11 @@ struct SelectorStyleInfo
   GHashTable *style;
 };
 
-struct _GtkCssScannerPrivate
+struct _GtkCssScanner
 {
-  GScanner *parent;
+  GtkCssProvider *provider;
+  GtkCssParser *parser;
+  GtkCssScanner *parent;
   GFile *file;
   GFile *base;
   GSList *state;
@@ -841,11 +844,9 @@ static guint css_provider_signals[LAST_SIGNAL] = { 0 };
 static void gtk_css_provider_finalize (GObject *object);
 static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
 
-static void scanner_apply_scope (GScanner    *scanner,
-                                 ParserScope  scope);
 static gboolean
 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
-                                GScanner       *scanner,
+                                GtkCssScanner  *scanner,
                                 GFile          *file,
                                 const char     *data,
                                 gsize           length,
@@ -1080,12 +1081,6 @@ selector_path_prepend_combinator (SelectorPath   *path,
   elem->combinator = combinator;
 }
 
-static gint
-selector_path_depth (SelectorPath *path)
-{
-  return g_slist_length (path->elements);
-}
-
 static SelectorStyleInfo *
 selector_style_info_new (SelectorPath *path)
 {
@@ -1132,124 +1127,110 @@ property_value_free (GValue *value)
 }
 
 static void
-gtk_css_scanner_reset (GScanner *scanner)
+gtk_css_scanner_reset (GtkCssScanner *scanner)
 {
-  GtkCssScannerPrivate *priv = scanner->user_data;
+  g_slist_free (scanner->state);
+  scanner->state = NULL;
 
-  g_slist_free (priv->state);
-  priv->state = NULL;
+  g_slist_foreach (scanner->cur_selectors, (GFunc) selector_path_unref, NULL);
+  g_slist_free (scanner->cur_selectors);
+  scanner->cur_selectors = NULL;
 
-  g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
-  g_slist_free (priv->cur_selectors);
-  priv->cur_selectors = NULL;
+  if (scanner->cur_properties)
+    g_hash_table_unref (scanner->cur_properties);
 
-  if (priv->cur_properties)
-    g_hash_table_unref (priv->cur_properties);
+  scanner ->cur_properties = g_hash_table_new_full (g_str_hash,
+                                                    g_str_equal,
+                                                    (GDestroyNotify) g_free,
+                                                    (GDestroyNotify) property_value_free);
+}
 
-  priv->cur_properties = g_hash_table_new_full (g_str_hash,
-                                                g_str_equal,
-                                                (GDestroyNotify) g_free,
-                                                (GDestroyNotify) property_value_free);
+static void
+gtk_css_scanner_destroy (GtkCssScanner *scanner)
+{
+  gtk_css_scanner_reset (scanner);
+
+  g_object_unref (scanner->provider);
+  if (scanner->file)
+    g_object_unref (scanner->file);
+  g_object_unref (scanner->base);
+  g_hash_table_destroy (scanner->cur_properties);
+  _gtk_css_parser_free (scanner->parser);
 
-  scanner_apply_scope (scanner, SCOPE_SELECTOR);
+  g_slice_free (GtkCssScanner, scanner);
 }
 
 static void
-gtk_css_scanner_destroy (GScanner *scanner)
+gtk_css_scanner_parser_error (GtkCssParser *parser,
+                              const GError *error,
+                              gpointer      user_data)
 {
-  GtkCssScannerPrivate *priv = scanner->user_data;
-
-  gtk_css_scanner_reset (scanner);
+  GtkCssScanner *scanner = user_data;
 
-  if (priv->file)
-    g_object_unref (priv->file);
-  g_hash_table_destroy (priv->cur_properties);
-  g_slice_free (GtkCssScannerPrivate, priv);
-  
-  g_scanner_destroy (scanner);
+  gtk_css_provider_take_error_full (scanner->provider,
+                                    scanner->file,
+                                    _gtk_css_parser_get_line (scanner->parser),
+                                    _gtk_css_parser_get_position (scanner->parser),
+                                    g_error_copy (error));
 }
 
-static GScanner *
-gtk_css_scanner_new (GScanner    *parent,
-                     GFile       *file,
-                     const gchar *data,
-                     gsize        length)
+static GtkCssScanner *
+gtk_css_scanner_new (GtkCssProvider *provider,
+                     GtkCssScanner  *parent,
+                     GFile          *file,
+                     const gchar    *data,
+                     gsize           length)
 {
-  GtkCssScannerPrivate *priv;
-  GScanner *scanner;
+  GtkCssScanner *scanner;
 
-  scanner = g_scanner_new (NULL);
+  g_assert (data[length] == 0);
 
-  priv = scanner->user_data = g_slice_new0 (GtkCssScannerPrivate);
+  scanner = g_slice_new0 (GtkCssScanner);
 
-  priv->parent = parent;
+  g_object_ref (provider);
+  scanner->provider = provider;
+  scanner->parent = parent;
 
   if (file)
     {
-      priv->file = g_object_ref (file);
-      priv->base = g_file_get_parent (file);
+      scanner->file = g_object_ref (file);
+      scanner->base = g_file_get_parent (file);
     }
   else
     {
       char *dir = g_get_current_dir ();
-      priv->base = g_file_new_for_path (dir);
+      scanner->base = g_file_new_for_path (dir);
       g_free (dir);
     }
 
-  priv->cur_properties = g_hash_table_new_full (g_str_hash,
-                                                g_str_equal,
-                                                (GDestroyNotify) g_free,
-                                                (GDestroyNotify) property_value_free);
+  scanner->cur_properties = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   (GDestroyNotify) g_free,
+                                                   (GDestroyNotify) property_value_free);
 
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "active", GUINT_TO_POINTER (GTK_STATE_ACTIVE));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "prelight", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "hover", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "selected", GUINT_TO_POINTER (GTK_STATE_SELECTED));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "insensitive", GUINT_TO_POINTER (GTK_STATE_INSENSITIVE));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "inconsistent", GUINT_TO_POINTER (GTK_STATE_INCONSISTENT));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "focused", GUINT_TO_POINTER (GTK_STATE_FOCUSED));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "focus", GUINT_TO_POINTER (GTK_STATE_FOCUSED));
-
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "nth-child", GUINT_TO_POINTER (SYMBOL_NTH_CHILD));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "first-child", GUINT_TO_POINTER (SYMBOL_FIRST_CHILD));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "last-child", GUINT_TO_POINTER (SYMBOL_LAST_CHILD));
-  g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "sorted", GUINT_TO_POINTER (SYMBOL_SORTED_CHILD));
-
-  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "even", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_EVEN));
-  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "odd", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_ODD));
-  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "first", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_FIRST));
-  g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "last", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_LAST));
-
-  scanner_apply_scope (scanner, SCOPE_SELECTOR);
-
-  if (length > G_MAXUINT32)
-    g_warning ("CSS file too large, truncating");
-
-  g_scanner_input_text (scanner, data, length);
+  scanner->parser = _gtk_css_parser_new (data,
+                                         gtk_css_scanner_parser_error,
+                                         scanner);
 
   return scanner;
 }
 
 static GFile *
-gtk_css_scanner_get_base_url (GScanner *scanner)
+gtk_css_scanner_get_base_url (GtkCssScanner *scanner)
 {
-  GtkCssScannerPrivate *priv = scanner->user_data;
-
-  return priv->base;
+  return scanner->base;
 }
 
 static gboolean
-gtk_css_scanner_would_recurse (GScanner *scanner,
-                               GFile    *file)
+gtk_css_scanner_would_recurse (GtkCssScanner *scanner,
+                               GFile         *file)
 {
   while (scanner)
     {
-      GtkCssScannerPrivate *priv = scanner->user_data;
-
-      if (priv->file && g_file_equal (priv->file, file))
+      if (scanner->file && g_file_equal (scanner->file, file))
         return TRUE;
 
-      scanner = priv->parent;
+      scanner = scanner->parent;
     }
 
   return FALSE;
@@ -1677,21 +1658,19 @@ gtk_css_provider_new (void)
 
 static void
 gtk_css_provider_take_error (GtkCssProvider *provider,
-                             GScanner       *scanner,
+                             GtkCssScanner  *scanner,
                              GError         *error)
 {
-  GtkCssScannerPrivate *priv = scanner->user_data;
-
   gtk_css_provider_take_error_full (provider,
-                                    priv->file,
-                                    scanner->line,
-                                    scanner->position,
+                                    scanner->file,
+                                    _gtk_css_parser_get_line (scanner->parser),
+                                    _gtk_css_parser_get_position (scanner->parser),
                                     error);
 }
 
 static void
 gtk_css_provider_error_literal (GtkCssProvider *provider,
-                                GScanner       *scanner,
+                                GtkCssScanner  *scanner,
                                 GQuark          domain,
                                 gint            code,
                                 const char     *message)
@@ -1703,14 +1682,14 @@ gtk_css_provider_error_literal (GtkCssProvider *provider,
 
 static void
 gtk_css_provider_error (GtkCssProvider *provider,
-                        GScanner       *scanner,
+                        GtkCssScanner  *scanner,
                         GQuark          domain,
                         gint            code,
                         const char     *format,
                         ...)  G_GNUC_PRINTF (5, 6);
 static void
 gtk_css_provider_error (GtkCssProvider *provider,
-                        GScanner       *scanner,
+                        GtkCssScanner  *scanner,
                         GQuark          domain,
                         gint            code,
                         const char     *format,
@@ -1728,7 +1707,7 @@ gtk_css_provider_error (GtkCssProvider *provider,
 
 static void
 gtk_css_provider_invalid_token (GtkCssProvider *provider,
-                                GScanner       *scanner,
+                                GtkCssScanner  *scanner,
                                 const char     *expected)
 {
   gtk_css_provider_error (provider,
@@ -1738,118 +1717,32 @@ gtk_css_provider_invalid_token (GtkCssProvider *provider,
                           "expected a valid %s", expected);
 }
 
-static void
-scanner_apply_scope (GScanner    *scanner,
-                     ParserScope  scope)
-{
-  g_scanner_set_scope (scanner, scope);
-
-  if (scope == SCOPE_VALUE)
-    {
-      scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_\"'";
-      scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_ +(),.%\t\n'/\"";
-      scanner->config->scan_identifier_1char = TRUE;
-      scanner->config->scan_string_sq = FALSE;
-      scanner->config->scan_string_dq = FALSE;
-    }
-  else if (scope == SCOPE_BINDING_SET)
-    {
-      scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_";
-      scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_ +(){}<>,.%\t\n'/\"";
-      scanner->config->scan_identifier_1char = TRUE;
-      scanner->config->scan_string_sq = TRUE;
-      scanner->config->scan_string_dq = TRUE;
-    }
-  else if (scope == SCOPE_SELECTOR)
-    {
-      scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "*@";
-      scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_#.";
-      scanner->config->scan_identifier_1char = TRUE;
-      scanner->config->scan_string_sq = TRUE;
-      scanner->config->scan_string_dq = TRUE;
-    }
-  else if (scope == SCOPE_PSEUDO_CLASS ||
-           scope == SCOPE_NTH_CHILD ||
-           scope == SCOPE_DECLARATION)
-    {
-      scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "-_";
-      scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_";
-      scanner->config->scan_identifier_1char = FALSE;
-      scanner->config->scan_string_sq = TRUE;
-      scanner->config->scan_string_dq = TRUE;
-    }
-  else
-    g_assert_not_reached ();
-
-  scanner->config->scan_float = FALSE;
-  scanner->config->cpair_comment_single = NULL;
-}
-
-static void
-gtk_css_scanner_push_scope (GScanner    *scanner,
-                            ParserScope  scope)
-{
-  GtkCssScannerPrivate *priv;
-
-  priv = scanner->user_data;
-  priv->state = g_slist_prepend (priv->state, GUINT_TO_POINTER (scope));
-
-  scanner_apply_scope (scanner, scope);
-}
-
-static void
-gtk_css_scanner_pop_scope (GScanner *scanner)
-{
-  GtkCssScannerPrivate *priv;
-  ParserScope scope = SCOPE_SELECTOR;
-
-  priv = scanner->user_data;
-
-  if (!priv->state)
-    {
-      g_warning ("Push/pop calls to parser scope aren't paired");
-      scanner_apply_scope (scanner, SCOPE_SELECTOR);
-      return;
-    }
-
-  priv->state = g_slist_delete_link (priv->state, priv->state);
-
-  /* Fetch new scope */
-  if (priv->state)
-    scope = GPOINTER_TO_INT (priv->state->data);
-
-  scanner_apply_scope (scanner, scope);
-}
-
-static void
+static void 
 css_provider_commit (GtkCssProvider *css_provider,
-                     GScanner       *scanner)
+                     GSList         *selectors,
+                     GHashTable     *properties)
 {
-  GtkCssScannerPrivate *scanner_priv;
   GtkCssProviderPrivate *priv;
   GSList *l;
 
   priv = css_provider->priv;
-  scanner_priv = scanner->user_data;
-
-  l = scanner_priv->cur_selectors;
 
-  if (g_hash_table_size (scanner_priv->cur_properties) == 0)
+  if (g_hash_table_size (properties) == 0)
     return;
 
-  while (l)
+  for (l = selectors; l; l = l->next)
     {
       SelectorPath *path = l->data;
       SelectorStyleInfo *info;
 
       info = selector_style_info_new (path);
-      selector_style_info_set_style (info, scanner_priv->cur_properties);
+      selector_style_info_set_style (info, properties);
 
       g_ptr_array_add (priv->selectors_info, info);
-      l = l->next;
     }
 }
 
+#if 0
 static GTokenType
 parse_nth_child (GtkCssProvider *css_provider,
                  GScanner       *scanner,
@@ -2084,6 +1977,7 @@ parse_selector (GtkCssProvider  *css_provider,
                   if ((pos = strchr (name, '.')) != NULL)
                     *pos = '\0';
 
+              selector_path_prepend_combinator (path, COMBINATOR_CHILD);
                   selector_path_prepend_name (path, name);
 
                   /* Parse any remaining classes */
@@ -2177,6 +2071,7 @@ parse_selector (GtkCssProvider  *css_provider,
 
   return G_TOKEN_NONE;
 }
+#endif
 
 static void
 resolve_binding_sets (const gchar *value_str,
@@ -2204,467 +2099,714 @@ resolve_binding_sets (const gchar *value_str,
   g_strfreev (bindings);
 }
 
-static GTokenType
-parse_rule (GtkCssProvider  *css_provider,
-            GScanner        *scanner)
+static void
+gtk_css_provider_reset (GtkCssProvider *css_provider)
 {
-  GtkCssScannerPrivate *priv;
-  GTokenType expected_token;
-  SelectorPath *selector;
-
-  priv = scanner->user_data;
-
-  gtk_css_scanner_push_scope (scanner, SCOPE_SELECTOR);
+  GtkCssProviderPrivate *priv;
 
-  /* Handle directives */
-  if (scanner->token == G_TOKEN_IDENTIFIER &&
-      scanner->value.v_identifier[0] == '@')
-    {
-      gchar *directive;
+  priv = css_provider->priv;
 
-      directive = &scanner->value.v_identifier[1];
+  if (priv->selectors_info->len > 0)
+    g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+}
 
-      if (strcmp (directive, "define-color") == 0)
-        {
-          GtkSymbolicColor *color;
-          gchar *color_name, *color_str;
-          GError *error = NULL;
+static void
+gtk_css_provider_propagate_error (GtkCssProvider  *provider,
+                                  const gchar     *path,
+                                  guint            line,
+                                  guint            position,
+                                  const GError    *error,
+                                  GError         **propagate_to)
+{
+  /* we already set an error. And we'd like to keep the first one */
+  if (*propagate_to)
+    return;
 
-          /* Directive is a color mapping */
-          g_scanner_get_next_token (scanner);
+  *propagate_to = g_error_copy (error);
+  g_prefix_error (propagate_to, "%s:%u:%u: ", path ? path : "<unknown>", line, position);
+}
 
-          if (scanner->token != G_TOKEN_IDENTIFIER)
-            {
-              gtk_css_provider_invalid_token (css_provider, scanner, "Color name");
-              return G_TOKEN_IDENTIFIER;
-            }
+static void
+parse_import (GtkCssScanner *scanner)
+{
+  GFile *file;
+  char *uri;
 
-          color_name = g_strdup (scanner->value.v_identifier);
-          gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
-          g_scanner_get_next_token (scanner);
+  uri = _gtk_css_parser_read_uri (scanner->parser);
+  if (uri == NULL)
+    {
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      return;
+    }
 
-          if (scanner->token != G_TOKEN_IDENTIFIER)
-            {
-              gtk_css_provider_invalid_token (css_provider, scanner, "Color definition");
-              return G_TOKEN_IDENTIFIER;
-            }
+  file = g_file_resolve_relative_path (gtk_css_scanner_get_base_url (scanner), uri);
+  g_free (uri);
 
-          color_str = g_strstrip (scanner->value.v_identifier);
-          color = _gtk_css_parse_symbolic_color (color_str, &error);
-          if (!color)
-            {
-              gtk_css_provider_take_error (css_provider, scanner, error);
-              return G_TOKEN_IDENTIFIER;
-            }
+  if (gtk_css_scanner_would_recurse (scanner, file))
+    {
+       char *path = g_file_get_path (file);
+       gtk_css_provider_error (scanner->provider,
+                               scanner,
+                               GTK_CSS_PROVIDER_ERROR,
+                               GTK_CSS_PROVIDER_ERROR_IMPORT,
+                               "Loading '%s' would recurse",
+                               path);
+       g_free (path);
+    }
+  else
+    {
+      gtk_css_provider_load_internal (scanner->provider,
+                                      scanner,
+                                      file,
+                                      NULL, 0,
+                                      NULL);
+    }
 
-          g_hash_table_insert (css_provider->priv->symbolic_colors, color_name, color);
+  if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
+    {
+      g_object_unref (file);
+      gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      return;
+    }
 
-          gtk_css_scanner_pop_scope (scanner);
-          g_scanner_get_next_token (scanner);
+  g_object_unref (file);
+}
 
-          if (scanner->token != ';')
-            return ';';
+static void
+parse_color_definition (GtkCssScanner *scanner)
+{
+  GtkSymbolicColor *symbolic;
+  char *name;
 
-          return G_TOKEN_NONE;
-        }
-      else if (strcmp (directive, "import") == 0)
-        {
-          gchar *path = NULL;
-          GFile *actual;
-          GError *error = NULL;
+  name = _gtk_css_parser_try_name (scanner->parser, TRUE);
+  if (name == NULL)
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Not a valid color name");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      return;
+    }
 
-          gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
-          g_scanner_get_next_token (scanner);
+  symbolic = _gtk_css_parser_read_symbolic_color (scanner->parser);
+  if (symbolic == NULL)
+    {
+      g_free (name);
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      return;
+    }
 
-          if (scanner->token == G_TOKEN_IDENTIFIER &&
-              g_str_has_prefix (scanner->value.v_identifier, "url"))
-            path = g_strstrip (scanner->value.v_identifier);
-          else if (scanner->token == G_TOKEN_STRING)
-            path = g_strstrip (scanner->value.v_string);
-          else
-            {
-              gtk_css_provider_invalid_token (css_provider, scanner, "File URL");
-              return G_TOKEN_IDENTIFIER;
-            }
+  if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
+    {
+      g_free (name);
+      gtk_symbolic_color_unref (symbolic);
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Missing semicolon at end of color definition");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      return;
+    }
 
-          actual = _gtk_css_parse_url (gtk_css_scanner_get_base_url (scanner),
-                                       path,
-                                       NULL,
-                                       &error);
+  g_hash_table_insert (scanner->provider->priv->symbolic_colors, name, symbolic);
+}
 
-          if (actual == NULL)
-            {
-              gtk_css_provider_take_error (css_provider, scanner, error);
-              return G_TOKEN_IDENTIFIER;
-            }
+static void
+parse_binding_set (GtkCssScanner *scanner)
+{
+  GtkBindingSet *binding_set;
+  char *name;
 
-          gtk_css_scanner_pop_scope (scanner);
-          g_scanner_get_next_token (scanner);
+  name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
+  if (name == NULL)
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected name for binding set");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      goto skip_semicolon;
+    }
 
-          if (scanner->token != ';')
-            {
-              g_object_unref (actual);
-              return ';';
-            }
+  binding_set = gtk_binding_set_find (name);
+  if (!binding_set)
+    {
+      binding_set = gtk_binding_set_new (name);
+      binding_set->parsed = TRUE;
+    }
+  g_free (name);
 
-          if (gtk_css_scanner_would_recurse (scanner, actual))
-            {
-              char *path = g_file_get_path (actual);
-              gtk_css_provider_error (css_provider,
+  if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
                                       scanner,
                                       GTK_CSS_PROVIDER_ERROR,
-                                      GTK_CSS_PROVIDER_ERROR_IMPORT,
-                                      "Loading '%s' would recurse",
-                                      path);
-              g_free (path);
-            }
-          else
-            {
-              gtk_css_provider_load_internal (css_provider,
-                                              scanner,
-                                              actual,
-                                              NULL, 0,
-                                              NULL);
-            }
-
-          g_object_unref (actual);
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected '{' for binding set");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      goto skip_semicolon;
+    }
 
-          return G_TOKEN_NONE;
-        }
-      else if (strcmp (directive, "binding-set") == 0)
+  while (!_gtk_css_parser_is_eof (scanner->parser) &&
+         !_gtk_css_parser_begins_with (scanner->parser, '}'))
+    {
+      name = _gtk_css_parser_read_value (scanner->parser);
+      if (name == NULL)
         {
-          GtkBindingSet *binding_set;
-          gchar *binding_set_name;
+          _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+          continue;
+        }
 
-          g_scanner_get_next_token (scanner);
+      gtk_binding_entry_add_signal_from_string (binding_set, name);
+      g_free (name);
 
-          if (scanner->token != G_TOKEN_IDENTIFIER)
+      if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
+        {
+          if (!_gtk_css_parser_begins_with (scanner->parser, '}') &&
+              !_gtk_css_parser_is_eof (scanner->parser))
             {
-              gtk_css_provider_invalid_token (css_provider, scanner, "Binding name");
-              return G_TOKEN_IDENTIFIER;
+              gtk_css_provider_error_literal (scanner->provider,
+                                              scanner,
+                                              GTK_CSS_PROVIDER_ERROR,
+                                              GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                              "Expected semicolon");
+              _gtk_css_parser_resync (scanner->parser, TRUE, '}');
             }
+        }
+    }
 
-          binding_set_name = scanner->value.v_identifier;
-          binding_set = gtk_binding_set_find (binding_set_name);
+  if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "expected '}' after declarations");
+      if (!_gtk_css_parser_is_eof (scanner->parser))
+        _gtk_css_parser_resync (scanner->parser, FALSE, 0);
+    }
 
-          if (!binding_set)
-            {
-              binding_set = gtk_binding_set_new (binding_set_name);
-              binding_set->parsed = TRUE;
-            }
+skip_semicolon:
+  if (_gtk_css_parser_try (scanner->parser, ";", TRUE))
+    gtk_css_provider_error_literal (scanner->provider,
+                                    scanner,
+                                    GTK_CSS_PROVIDER_ERROR,
+                                    GTK_CSS_PROVIDER_ERROR_DEPRECATED,
+                                    "Nonstandard semicolon at end of binding set");
+}
 
-          g_scanner_get_next_token (scanner);
+static void
+parse_at_keyword (GtkCssScanner *scanner)
+{
+  if (_gtk_css_parser_try (scanner->parser, "@import", TRUE))
+    parse_import (scanner);
+  else if (_gtk_css_parser_try (scanner->parser, "@define-color", TRUE))
+    parse_color_definition (scanner);
+  else if (_gtk_css_parser_try (scanner->parser, "@binding-set", TRUE))
+    parse_binding_set (scanner);
+  else
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "unknown @ rule");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+    }
+}
 
-          if (scanner->token != G_TOKEN_LEFT_CURLY)
-            return G_TOKEN_LEFT_CURLY;
+static gboolean
+parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
+{
+  struct {
+    const char *name;
+    GtkStateFlags flag;
+  } classes[] = {
+    { "active", GTK_STATE_FLAG_ACTIVE },
+    { "prelight", GTK_STATE_FLAG_PRELIGHT },
+    { "hover", GTK_STATE_FLAG_PRELIGHT },
+    { "selected", GTK_STATE_FLAG_SELECTED },
+    { "insensitive", GTK_STATE_FLAG_INSENSITIVE },
+    { "inconsistent", GTK_STATE_FLAG_INCONSISTENT },
+    { "focused", GTK_STATE_FLAG_FOCUSED },
+    { "focus", GTK_STATE_FLAG_FOCUSED }
+  };
+  guint i;
 
-          gtk_css_scanner_push_scope (scanner, SCOPE_BINDING_SET);
-          g_scanner_get_next_token (scanner);
+  for (i = 0; i < G_N_ELEMENTS (classes); i++)
+    {
+      if (_gtk_css_parser_try (scanner->parser, classes[i].name, FALSE))
+        {
+          path->state |= classes[i].flag;
+          return TRUE;
+        }
+    }
 
-          do
-            {
-              GTokenType ret;
+  gtk_css_provider_error_literal (scanner->provider,
+                                  scanner,
+                                  GTK_CSS_PROVIDER_ERROR,
+                                  GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                  "Expected a valid state name");
+  return FALSE;
+}
 
-              if (scanner->token != G_TOKEN_IDENTIFIER)
-                {
-                  gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
-                  return G_TOKEN_IDENTIFIER;
-                }
+static gboolean
+parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
+{
+  char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
 
-              ret = gtk_binding_entry_add_signal_from_string (binding_set,
-                                                              scanner->value.v_identifier);
-              if (ret != G_TOKEN_NONE)
-                {
-                  gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
-                  return ret;
-                }
+  if (name == NULL)
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected a valid name");
+      return FALSE;
+    }
 
-              g_scanner_get_next_token (scanner);
+  selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+  selector_path_prepend_class (path, name);
+  return TRUE;
+}
 
-              if (scanner->token != ';')
-                return ';';
+static gboolean
+parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
+{
+  char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
 
-              g_scanner_get_next_token (scanner);
-            }
-          while (scanner->token != G_TOKEN_RIGHT_CURLY);
+  if (name == NULL)
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected a valid name");
+      return FALSE;
+    }
 
-          gtk_css_scanner_pop_scope (scanner);
-          g_scanner_get_next_token (scanner);
+  selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+  selector_path_prepend_name (path, name);
+  return TRUE;
+}
 
-          return G_TOKEN_NONE;
+static gboolean
+parse_selector_pseudo_class_for_region (GtkCssScanner  *scanner,
+                                        SelectorPath   *path,
+                                        GtkRegionFlags *flags_to_modify)
+{
+  struct {
+    const char *name;
+    GtkRegionFlags flag;
+  } classes[] = {
+    { "first", GTK_REGION_FIRST },
+    { "last", GTK_REGION_LAST },
+    { "sorted", GTK_REGION_SORTED }
+  }, nth_child[] = {
+    { "first", GTK_REGION_FIRST },
+    { "last", GTK_REGION_LAST },
+    { "even", GTK_REGION_EVEN },
+    { "odd", GTK_REGION_ODD }
+  };
+  guint i;
+
+  for (i = 0; i < G_N_ELEMENTS (classes); i++)
+    {
+      if (_gtk_css_parser_try (scanner->parser, classes[i].name, FALSE))
+        {
+          *flags_to_modify |=classes[i].flag;
+          return TRUE;
         }
-      else
+    }
+
+  if (!_gtk_css_parser_try (scanner->parser, "nth-child(", TRUE))
+    return parse_selector_pseudo_class (scanner, path);
+
+  for (i = 0; i < G_N_ELEMENTS (nth_child); i++)
+    {
+      if (_gtk_css_parser_try (scanner->parser, nth_child[i].name, TRUE))
         {
-          gtk_css_provider_invalid_token (css_provider, scanner, "Directive");
-          return G_TOKEN_IDENTIFIER;
+          *flags_to_modify |= nth_child[i].flag;
+          break;
         }
     }
 
-  expected_token = parse_selector (css_provider, scanner, &selector);
+  if (i == G_N_ELEMENTS (nth_child))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Not a valid value for nth-child");
+      return FALSE;
+    }
 
-  if (expected_token != G_TOKEN_NONE)
+  if (!_gtk_css_parser_try (scanner->parser, ")", FALSE))
     {
-      selector_path_unref (selector);
-      gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
-      return expected_token;
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Missing closing bracket");
+      return FALSE;
     }
 
-  priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
+  return TRUE;
+}
 
-  while (scanner->token == ',')
+static gboolean
+parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
+{
+  char *name;
+  gboolean parsed_something;
+  
+  name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
+  if (name)
     {
-      g_scanner_get_next_token (scanner);
+      if (_gtk_style_context_check_region_name (name))
+        {
+          GtkRegionFlags flags;
+          
+          flags = 0;
 
-      expected_token = parse_selector (css_provider, scanner, &selector);
+          while (_gtk_css_parser_try (scanner->parser, ":", FALSE))
+            {
+              if (!parse_selector_pseudo_class_for_region (scanner, path, &flags))
+                {
+                  g_free (name);
+                  return FALSE;
+                }
+            }
 
-      if (expected_token != G_TOKEN_NONE)
+          selector_path_prepend_region (path, name, flags);
+          g_free (name);
+          _gtk_css_parser_skip_whitespace (scanner->parser);
+          return TRUE;
+        }
+      else
         {
-          selector_path_unref (selector);
-          gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
-          return expected_token;
+          selector_path_prepend_type (path, name);
+          parsed_something = TRUE;
         }
-
-      priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
     }
-
-  gtk_css_scanner_pop_scope (scanner);
-
-  if (scanner->token != G_TOKEN_LEFT_CURLY)
-    return G_TOKEN_LEFT_CURLY;
-
-  /* Declarations parsing */
-  gtk_css_scanner_push_scope (scanner, SCOPE_DECLARATION);
-
-  g_scanner_get_next_token (scanner);
-    
-  while (scanner->token != G_TOKEN_RIGHT_CURLY &&
-         !g_scanner_eof (scanner))
+  else
     {
-      gchar *value_str = NULL;
-      GtkStylePropertyParser parse_func = NULL;
-      GParamSpec *pspec = NULL;;
-      gchar *prop;
+      parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE);
+      selector_path_prepend_glob (path);
+    }
 
-      if (scanner->token == ';')
+  do {
+      if (_gtk_css_parser_try (scanner->parser, "#", FALSE))
         {
-          g_scanner_get_next_token (scanner);
-          continue;
+          if (!parse_selector_name (scanner, path))
+            return FALSE;
         }
-
-      if (scanner->token != G_TOKEN_IDENTIFIER)
+      else if (_gtk_css_parser_try (scanner->parser, ".", FALSE))
+        {
+          if (!parse_selector_class (scanner, path))
+            return FALSE;
+        }
+      else if (_gtk_css_parser_try (scanner->parser, ":", FALSE))
+        {
+          if (!parse_selector_pseudo_class (scanner, path))
+            return FALSE;
+        }
+      else if (!parsed_something)
         {
-          gtk_css_provider_error_literal (css_provider,
+          gtk_css_provider_error_literal (scanner->provider,
                                           scanner,
                                           GTK_CSS_PROVIDER_ERROR,
-                                          GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
-                                          "Expected a valid property name");
-          goto find_end_of_declaration;
+                                          GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                          "Expected a valid selector");
+          return FALSE;
         }
+      else
+        break;
 
-      prop = g_strdup (scanner->value.v_identifier);
+      parsed_something = TRUE;
+    }
+  while (!_gtk_css_parser_is_eof (scanner->parser));
 
-      if (!gtk_style_properties_lookup_property (prop, &parse_func, &pspec) &&
-          prop[0] != '-')
-        {
-          gtk_css_provider_error (css_provider,
-                                  scanner,
-                                  GTK_CSS_PROVIDER_ERROR,
-                                  GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
-                                  "'%s' is not a valid property name",
-                                  prop);
-          g_free (prop);
-          goto find_end_of_declaration;
-        }
+  _gtk_css_parser_skip_whitespace (scanner->parser);
+  return TRUE;
+}
 
-      g_scanner_get_next_token (scanner);
+static SelectorPath *
+parse_selector (GtkCssScanner *scanner)
+{
+  SelectorPath *path;
 
-      if (scanner->token != ':')
+  path = selector_path_new ();
+
+  do {
+      if (!parse_simple_selector (scanner, path))
         {
-          g_free (prop);
-          gtk_css_provider_invalid_token (css_provider, scanner, "':'");
-          goto find_end_of_declaration;
+          selector_path_unref (path);
+          return NULL;
         }
 
-      gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
-      g_scanner_get_next_token (scanner);
+      if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
+        selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+    }
+  while (path->state == 0 &&
+         !_gtk_css_parser_is_eof (scanner->parser) &&
+         !_gtk_css_parser_begins_with (scanner->parser, ',') &&
+         !_gtk_css_parser_begins_with (scanner->parser, '{'));
 
-      if (scanner->token != G_TOKEN_IDENTIFIER)
-        {
-          g_free (prop);
-          /* the error value here is hacky. But strings should be used for
-           * strings really, so a string is not a syntax error but a broken
-           * value for everything that we support. */
-          gtk_css_provider_error (css_provider,
-                                  scanner,
-                                  GTK_CSS_PROVIDER_ERROR,
-                                  GTK_CSS_PROVIDER_ERROR_SYNTAX,
-                                  "Not a property value");
-          gtk_css_scanner_pop_scope (scanner);
-          goto find_end_of_declaration;
-        }
+  return path;
+}
 
-      value_str = scanner->value.v_identifier;
-      g_strchomp (value_str);
+static GSList *
+parse_selector_list (GtkCssScanner *scanner)
+{
+  GSList *selectors = NULL;
 
-      gtk_css_scanner_pop_scope (scanner);
-      g_scanner_peek_next_token (scanner);
+  do {
+      SelectorPath *path = parse_selector (scanner);
 
-      if (scanner->next_token != ';' &&
-          scanner->next_token != G_TOKEN_RIGHT_CURLY &&
-          scanner->next_token != G_TOKEN_EOF)
+      if (path == NULL)
         {
-          gtk_css_provider_invalid_token (css_provider, scanner, "';'");
-          g_free (prop);
-          goto find_end_of_declaration;
+          g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+          _gtk_css_parser_resync (scanner->parser, FALSE, 0);
+          return NULL;
         }
 
-      if (pspec)
-        {
-          GValue *val;
+      selectors = g_slist_prepend (selectors, path);
+    }
+  while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
 
-          val = g_slice_new0 (GValue);
-          g_value_init (val, pspec->value_type);
+  return selectors;
+}
 
-          if (strcmp (value_str, "none") == 0)
-            {
-              /* Insert the default value, so it has an opportunity
-               * to override other style providers when merged
-               */
-              g_param_value_set_default (pspec, val);
-              g_hash_table_insert (priv->cur_properties, prop, val);
-            }
-          else if (strcmp (prop, "gtk-key-bindings") == 0)
-            {
-              /* Private property holding the binding sets */
-              resolve_binding_sets (value_str, val);
-              g_hash_table_insert (priv->cur_properties, prop, val);
-            }
-          else if (parse_func)
+static void
+parse_declaration (GtkCssScanner *scanner, GHashTable *properties)
+{
+  GtkStylePropertyParser parse_func = NULL;
+  GParamSpec *pspec = NULL;
+  char *name, *value_str;
+
+  name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
+  if (name == NULL)
+    goto check_for_semicolon;
+
+  if (!gtk_style_properties_lookup_property (name, &parse_func, &pspec) &&
+      name[0] != '-')
+    {
+      gtk_css_provider_error (scanner->provider,
+                              scanner,
+                              GTK_CSS_PROVIDER_ERROR,
+                              GTK_CSS_PROVIDER_ERROR_NAME,
+                              "'%s' is not a valid property name",
+                              name);
+      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+      g_free (name);
+      return;
+    }
+
+  if (!_gtk_css_parser_try (scanner->parser, ":", TRUE))
+    {
+      gtk_css_provider_invalid_token (scanner->provider, scanner, "':'");
+      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+      g_free (name);
+      return;
+    }
+
+  value_str = _gtk_css_parser_read_value (scanner->parser);
+  if (value_str == NULL)
+    {
+      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+      g_free (name);
+      return;
+    }
+  
+  if (pspec)
+    {
+      GValue *val;
+
+      val = g_slice_new0 (GValue);
+      g_value_init (val, pspec->value_type);
+
+      if (strcmp (value_str, "none") == 0)
+        {
+          /* Insert the default value, so it has an opportunity
+           * to override other style providers when merged
+           */
+          g_param_value_set_default (pspec, val);
+          g_hash_table_insert (properties, name, val);
+        }
+      else if (strcmp (name, "gtk-key-bindings") == 0)
+        {
+          /* Private property holding the binding sets */
+          resolve_binding_sets (value_str, val);
+          g_hash_table_insert (properties, name, val);
+        }
+      else if (parse_func)
+        {
+          GError *error = NULL;
+          
+          if ((*parse_func) (value_str, val, &error))
+            g_hash_table_insert (properties, name, val);
+          else
+            gtk_css_provider_take_error (scanner->provider, scanner, error);
+        }
+      else
+        {
+          GError *error = NULL;
+          
+          if (_gtk_css_value_from_string (val,
+                                          gtk_css_scanner_get_base_url (scanner),
+                                          value_str,
+                                          &error))
             {
-              GError *error = NULL;
-              
-              if ((*parse_func) (value_str, val, &error))
-                g_hash_table_insert (priv->cur_properties, prop, val);
-              else
-                gtk_css_provider_take_error (css_provider, scanner, error);
+              g_hash_table_insert (properties, name, val);
             }
           else
             {
-              GError *error = NULL;
-              
-              if (_gtk_css_value_from_string (val,
-                                              gtk_css_scanner_get_base_url (scanner),
-                                              value_str,
-                                              &error))
-                {
-                  g_hash_table_insert (priv->cur_properties, prop, val);
-                }
-              else
-                {
-                  g_value_unset (val);
-                  g_slice_free (GValue, val);
-                  g_free (prop);
+              g_value_unset (val);
+              g_slice_free (GValue, val);
+              g_free (name);
 
-                  gtk_css_provider_take_error (css_provider, scanner, error);
-                }
+              gtk_css_provider_take_error (scanner->provider, scanner, error);
             }
         }
-      else if (prop[0] == '-')
-        {
-          GValue *val;
+    }
+  else if (name[0] == '-')
+    {
+      GValue *val;
 
-          val = g_slice_new0 (GValue);
-          g_value_init (val, G_TYPE_STRING);
-          g_value_set_string (val, value_str);
+      val = g_slice_new0 (GValue);
+      g_value_init (val, G_TYPE_STRING);
+      g_value_set_string (val, value_str);
 
-          g_hash_table_insert (priv->cur_properties, prop, val);
-        }
-      else
-        g_free (prop);
+      g_hash_table_insert (properties, name, val);
+    }
+  else
+    g_free (name);
 
-      g_scanner_get_next_token (scanner);
+  g_free (value_str);
 
-      if (g_scanner_eof (scanner))
+check_for_semicolon:
+  if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
+    {
+      if (!_gtk_css_parser_begins_with (scanner->parser, '}') &&
+          !_gtk_css_parser_is_eof (scanner->parser))
         {
-          gtk_css_provider_invalid_token (css_provider, scanner, "}");
-          break;
+          gtk_css_provider_error_literal (scanner->provider,
+                                          scanner,
+                                          GTK_CSS_PROVIDER_ERROR,
+                                          GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                          "Expected semicolon");
+          _gtk_css_parser_resync (scanner->parser, TRUE, '}');
         }
-
-find_end_of_declaration:
-      while (scanner->token != ';' &&
-             scanner->token != G_TOKEN_RIGHT_CURLY &&
-             !g_scanner_eof (scanner))
-        g_scanner_get_next_token (scanner);
     }
+}
 
-  gtk_css_scanner_pop_scope (scanner);
+static GHashTable *
+parse_declarations (GtkCssScanner *scanner)
+{
+  GHashTable *properties;
 
-  return G_TOKEN_NONE;
+  properties = g_hash_table_new_full (g_str_hash,
+                                      g_str_equal,
+                                      (GDestroyNotify) g_free,
+                                      (GDestroyNotify) property_value_free);
+
+  while (!_gtk_css_parser_is_eof (scanner->parser) &&
+         !_gtk_css_parser_begins_with (scanner->parser, '}'))
+    {
+      parse_declaration (scanner, properties);
+    }
+
+  return properties;
 }
 
 static void
-gtk_css_provider_reset (GtkCssProvider *css_provider)
+parse_ruleset (GtkCssScanner *scanner)
 {
-  GtkCssProviderPrivate *priv;
+  GSList *selectors;
+  GHashTable *properties;
 
-  priv = css_provider->priv;
+  selectors = parse_selector_list (scanner);
+  if (selectors == NULL)
+    return;
 
-  if (priv->selectors_info->len > 0)
-    g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+  if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "expected '{' after selectors");
+      _gtk_css_parser_resync (scanner->parser, FALSE, 0);
+      g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+      return;
+    }
+
+  properties = parse_declarations (scanner);
+
+  if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "expected '}' after declarations");
+      if (!_gtk_css_parser_is_eof (scanner->parser))
+        {
+          _gtk_css_parser_resync (scanner->parser, FALSE, 0);
+          g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+          if (properties)
+            g_hash_table_unref (properties);
+          return;
+        }
+    }
+
+  if (properties)
+    {
+      css_provider_commit (scanner->provider, selectors, properties);
+      g_hash_table_unref (properties);
+    }
+  g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
 }
 
 static void
-gtk_css_provider_propagate_error (GtkCssProvider  *provider,
-                                  const gchar     *path,
-                                  guint            line,
-                                  guint            position,
-                                  const GError    *error,
-                                  GError         **propagate_to)
+parse_statement (GtkCssScanner *scanner)
 {
-  /* we already set an error. And we'd like to keep the first one */
-  if (*propagate_to)
-    return;
-
-  *propagate_to = g_error_copy (error);
-  g_prefix_error (propagate_to, "%s:%u:%u: ", path ? path : "<unknown>", line, position);
+  if (_gtk_css_parser_begins_with (scanner->parser, '@'))
+    parse_at_keyword (scanner);
+  else
+    parse_ruleset (scanner);
 }
 
 static void
-parse_stylesheet (GtkCssProvider  *css_provider,
-                  GScanner        *scanner)
+parse_stylesheet (GtkCssScanner   *scanner)
 {
-  g_scanner_get_next_token (scanner);
+  _gtk_css_parser_skip_whitespace (scanner->parser);
 
-  while (!g_scanner_eof (scanner))
+  while (!_gtk_css_parser_is_eof (scanner->parser))
     {
-      GTokenType expected_token;
-
-      expected_token = parse_rule (css_provider, scanner);
-
-      if (expected_token != G_TOKEN_NONE)
-        {
-          while (!g_scanner_eof (scanner) &&
-                 scanner->token != G_TOKEN_RIGHT_CURLY)
-            g_scanner_get_next_token (scanner);
-        }
-      else
-        css_provider_commit (css_provider, scanner);
+      if (_gtk_css_parser_try (scanner->parser, "<!--", TRUE) ||
+          _gtk_css_parser_try (scanner->parser, "-->", TRUE))
+        continue;
 
-      g_scanner_get_next_token (scanner);
-      
-      gtk_css_scanner_reset (scanner);
+      parse_statement (scanner);
     }
 }
 
 static gboolean
 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
-                                GScanner       *parent,
+                                GtkCssScanner  *parent,
                                 GFile          *file,
                                 const char     *data,
                                 gsize           length,
                                 GError        **error)
 {
-  GScanner *scanner;
+  GtkCssScanner *scanner;
   gulong error_handler;
   char *free_data;
 
@@ -2712,9 +2854,9 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
 
   if (data)
     {
-      scanner = gtk_css_scanner_new (parent, file, data, length);
+      scanner = gtk_css_scanner_new (css_provider, parent, file, data, length);
 
-      parse_stylesheet (css_provider, scanner);
+      parse_stylesheet (scanner);
 
       gtk_css_scanner_destroy (scanner);
     }
diff --git a/gtk/gtkcssprovider.h b/gtk/gtkcssprovider.h
index 65292df..b49640c 100644
--- a/gtk/gtkcssprovider.h
+++ b/gtk/gtkcssprovider.h
@@ -37,15 +37,9 @@ typedef enum
 {
   GTK_CSS_PROVIDER_ERROR_FAILED,
   GTK_CSS_PROVIDER_ERROR_SYNTAX,
-  GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
-  GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-  GTK_CSS_PROVIDER_ERROR_SELECTOR,
-  GTK_CSS_PROVIDER_ERROR_COMBINATOR,
-  GTK_CSS_PROVIDER_ERROR_CLASS,
-  GTK_CSS_PROVIDER_ERROR_PSEUDO_CLASS,
-  GTK_CSS_PROVIDER_ERROR_AT_RULE,
   GTK_CSS_PROVIDER_ERROR_IMPORT,
-  GTK_CSS_PROVIDER_ERROR_DEFINE_COLOR
+  GTK_CSS_PROVIDER_ERROR_NAME,
+  GTK_CSS_PROVIDER_ERROR_DEPRECATED
 } GtkCssProviderError;
 
 GQuark gtk_css_provider_error_quark (void);
diff --git a/gtk/gtkcssstringfuncs.c b/gtk/gtkcssstringfuncs.c
index f36e942..53a5ccc 100644
--- a/gtk/gtkcssstringfuncs.c
+++ b/gtk/gtkcssstringfuncs.c
@@ -29,6 +29,7 @@
 #include <cairo-gobject.h>
 
 #include "gtkcssprovider.h"
+#include "gtkcssparserprivate.h"
 
 /* the actual parsers we have */
 #include "gtkanimationdescription.h"
@@ -62,7 +63,7 @@ set_default_error (GError **error,
 {
   g_set_error (error,
                GTK_CSS_PROVIDER_ERROR,
-               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                "Could not convert property value to type '%s'",
                g_type_name (type));
   return FALSE;
@@ -73,257 +74,35 @@ set_default_error (GError **error,
 #define SKIP_SPACES(s) while (g_ascii_isspace (*(s))) (s)++
 #define SKIP_SPACES_BACK(s) while (g_ascii_isspace (*(s))) (s)--
 
-static GtkSymbolicColor *
-symbolic_color_parse_str (const gchar  *string,
-                          gchar       **end_ptr)
+static void
+propagate_parser_error (GtkCssParser *parser,
+                        const GError *error,
+                        gpointer      user_data)
 {
-  GtkSymbolicColor *symbolic_color = NULL;
-  gchar *str;
-
-  str = (gchar *) string;
-  *end_ptr = str;
-
-  if (str[0] == '@')
-    {
-      const gchar *end;
-      gchar *name;
-
-      str++;
-      end = str;
-
-      while (*end == '-' || *end == '_' || g_ascii_isalnum (*end))
-        end++;
-
-      name = g_strndup (str, end - str);
-      symbolic_color = gtk_symbolic_color_new_name (name);
-      g_free (name);
-
-      *end_ptr = (gchar *) end;
-    }
-  else if (g_str_has_prefix (str, "lighter") ||
-           g_str_has_prefix (str, "darker"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_lighter = FALSE;
-
-      is_lighter = g_str_has_prefix (str, "lighter");
-
-      if (is_lighter)
-        str += strlen ("lighter");
-      else
-        str += strlen ("darker");
-
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
-
-      if (!param_color)
-        return NULL;
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (*str != ')')
-        {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
-
-      if (is_lighter)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
-      else
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
-
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "shade") ||
-           g_str_has_prefix (str, "alpha"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_shade = FALSE;
-      gdouble factor;
-
-      is_shade = g_str_has_prefix (str, "shade");
-
-      if (is_shade)
-        str += strlen ("shade");
-      else
-        str += strlen ("alpha");
-
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
-
-      if (!param_color)
-        return NULL;
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (param_color);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
-        {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
-
-      if (is_shade)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
-      else
-        symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
-
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "mix"))
-    {
-      GtkSymbolicColor *color1, *color2;
-      gdouble factor;
-
-      str += strlen ("mix");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      color1 = symbolic_color_parse_str (str, end_ptr);
-
-      if (!color1)
-        return NULL;
+  GError **propagate_here = user_data;
 
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      color2 = symbolic_color_parse_str (str, end_ptr);
-
-      if (!color2 || *end_ptr[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          return NULL;
-        }
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          return NULL;
-        }
-
-      symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
-      gtk_symbolic_color_unref (color1);
-      gtk_symbolic_color_unref (color2);
-      (*end_ptr)++;
-    }
-  else
-    {
-      GdkRGBA color;
-      gchar *color_str;
-      const gchar *end;
-
-      end = str + 1;
-
-      if (str[0] == '#')
-        {
-          /* Color in hex format */
-          while (g_ascii_isxdigit (*end))
-            end++;
-        }
-      else if (g_str_has_prefix (str, "rgb"))
-        {
-          /* color in rgb/rgba format */
-          while (*end != ')' && *end != '\0')
-            end++;
-
-          if (*end == ')')
-            end++;
-        }
-      else
-        {
-          /* Color name */
-          while (*end != '\0' &&
-                 (g_ascii_isalnum (*end) || *end == ' '))
-            end++;
-        }
+  if (propagate_here == NULL)
+    return;
 
-      color_str = g_strndup (str, end - str);
-      *end_ptr = (gchar *) end;
+  /* only copy the first error */
+  if (*propagate_here == NULL)
+    *propagate_here = g_error_copy (error);
+}
 
-      if (!gdk_rgba_parse (&color, color_str))
-        {
-          g_free (color_str);
-          return NULL;
-        }
+static GtkSymbolicColor *
+_gtk_css_parse_symbolic_color (const char  *str,
+                               GError     **error)
+{
+  GtkSymbolicColor *symbolic;
+  GtkCssParser *parser;
 
-      symbolic_color = gtk_symbolic_color_new_literal (&color);
-      g_free (color_str);
-    }
+  parser = _gtk_css_parser_new (str,
+                                propagate_parser_error,
+                                error);
+  symbolic = _gtk_css_parser_read_symbolic_color (parser);
+  _gtk_css_parser_free (parser);
 
-  return symbolic_color;
+  return symbolic;
 }
 
 static gboolean 
@@ -489,6 +268,9 @@ int_value_from_string (const char  *str,
   gint64 i;
   char *end;
 
+  if (*str == '+')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
   i = g_ascii_strtoll (str, &end, 10);
 
   if (*end != '\0')
@@ -498,7 +280,7 @@ int_value_from_string (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Number too big");
       return FALSE;
     }
@@ -522,6 +304,9 @@ uint_value_from_string (const char  *str,
   guint64 u;
   char *end;
 
+  if (*str == '+')
+    return set_default_error (error, G_VALUE_TYPE (value));
+
   u = g_ascii_strtoull (str, &end, 10);
 
   if (*end != '\0')
@@ -531,7 +316,7 @@ uint_value_from_string (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Number too big");
       return FALSE;
     }
@@ -564,7 +349,7 @@ double_value_from_string (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Number not representable");
       return FALSE;
     }
@@ -601,7 +386,7 @@ float_value_from_string (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Number not representable");
       return FALSE;
     }
@@ -634,7 +419,7 @@ gtk_css_string_unescape (const char  *string,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "String value not properly quoted.");
       return NULL;
     }
@@ -659,7 +444,7 @@ gtk_css_string_unescape (const char  *string,
             {
               g_set_error_literal (error,
                                    GTK_CSS_PROVIDER_ERROR,
-                                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                                   GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                    "FIXME: Implement unicode escape sequences.");
               g_string_free (str, TRUE);
               return NULL;
@@ -685,7 +470,7 @@ gtk_css_string_unescape (const char  *string,
                 {
                   g_set_error_literal (error,
                                        GTK_CSS_PROVIDER_ERROR,
-                                       GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                                       GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                        "Junk after end of string.");
                   g_string_free (str, TRUE);
                   return NULL;
@@ -695,14 +480,14 @@ gtk_css_string_unescape (const char  *string,
         case '\0':
           g_set_error_literal (error,
                                GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                "Missing end quote in string.");
           g_string_free (str, TRUE);
           return NULL;
         default:
           g_set_error_literal (error,
                                GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                "Invalid character in string. Must be escaped.");
           g_string_free (str, TRUE);
           return NULL;
@@ -783,7 +568,7 @@ theming_engine_value_from_string (const char  *str,
     {
       g_set_error (error,
                    GTK_CSS_PROVIDER_ERROR,
-                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                   GTK_CSS_PROVIDER_ERROR_SYNTAX,
                    "Themeing engine '%s' not found", str);
       return FALSE;
     }
@@ -850,7 +635,7 @@ parse_border_value (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Number out of range for border");
       return FALSE;
     }
@@ -859,7 +644,7 @@ parse_border_value (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "No number given for border value");
       return FALSE;
     }
@@ -875,7 +660,7 @@ parse_border_value (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Junk at end of border value");
       return FALSE;
     }
@@ -921,7 +706,7 @@ border_value_from_string (const char  *str,
     {
       g_set_error_literal (error,
                            GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                           GTK_CSS_PROVIDER_ERROR_SYNTAX,
                            "Junk at end of border value");
       return FALSE;
     }
@@ -947,143 +732,92 @@ border_value_to_string (const GValue *value)
     return g_strdup_printf ("%d", border->top);
 }
 
-static gboolean 
-gradient_value_from_string (const char  *str,
-                            GFile       *base,
-                            GValue      *value,
-                            GError     **error)
+static GtkGradient *
+_gtk_css_parse_gradient (GtkCssParser  *parser)
 {
   GtkGradient *gradient;
   cairo_pattern_type_t type;
   gdouble coords[6];
-  gchar *end;
   guint i;
 
-  str += strlen ("-gtk-gradient");
-  SKIP_SPACES (str);
-
-  if (*str != '(')
+  if (!_gtk_css_parser_try (parser, "-gtk-gradient", TRUE))
     {
-      g_set_error_literal (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-                           "Expected '(' after '-gtk-gradient'");
-      return FALSE;
+      _gtk_css_parser_error (parser,
+                             "Expected '-gtk-gradient'");
+      return NULL;
     }
 
-  str++;
-  SKIP_SPACES (str);
-
-  /* Parse gradient type */
-  if (g_str_has_prefix (str, "linear"))
-    {
-      type = CAIRO_PATTERN_TYPE_LINEAR;
-      str += strlen ("linear");
-    }
-  else if (g_str_has_prefix (str, "radial"))
+  if (!_gtk_css_parser_try (parser, "(", TRUE))
     {
-      type = CAIRO_PATTERN_TYPE_RADIAL;
-      str += strlen ("radial");
+      _gtk_css_parser_error (parser,
+                             "Expected '(' after '-gtk-gradient'");
+      return NULL;
     }
+
+  /* Parse gradient type */
+  if (_gtk_css_parser_try (parser, "linear", TRUE))
+    type = CAIRO_PATTERN_TYPE_LINEAR;
+  else if (_gtk_css_parser_try (parser, "radial", TRUE))
+    type = CAIRO_PATTERN_TYPE_RADIAL;
   else
     {
-      g_set_error_literal (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-                           "Gradient type must be 'radial' or 'linear'");
-      return FALSE;
+      _gtk_css_parser_error (parser,
+                             "Gradient type must be 'radial' or 'linear'");
+      return NULL;
     }
 
-  SKIP_SPACES (str);
-
   /* Parse start/stop position parameters */
   for (i = 0; i < 2; i++)
     {
-      if (*str != ',')
+      if (! _gtk_css_parser_try (parser, ",", TRUE))
         {
-          g_set_error_literal (error,
-                               GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-                               "Expected ','");
-          return FALSE;
+          _gtk_css_parser_error (parser,
+                                 "Expected ','");
+          return NULL;
         }
 
-      str++;
-      SKIP_SPACES (str);
-
-      if (strncmp (str, "left", 4) == 0)
-        {
-          coords[i * 3] = 0;
-          str += strlen ("left");
-        }
-      else if (strncmp (str, "right", 5) == 0)
-        {
-          coords[i * 3] = 1;
-          str += strlen ("right");
-        }
-      else if (strncmp (str, "center", 6) == 0)
-        {
-          coords[i * 3] = 0.5;
-          str += strlen ("center");
-        }
-      else
+      if (_gtk_css_parser_try (parser, "left", TRUE))
+        coords[i * 3] = 0;
+      else if (_gtk_css_parser_try (parser, "right", TRUE))
+        coords[i * 3] = 1;
+      else if (_gtk_css_parser_try (parser, "center", TRUE))
+        coords[i * 3] = 0.5;
+      else if (!_gtk_css_parser_try_double (parser, &coords[i * 3]))
         {
-          coords[i * 3] = g_ascii_strtod (str, &end);
-
-          if (str == end)
-            return set_default_error (error, G_VALUE_TYPE (value));
-
-          str = end;
+          _gtk_css_parser_error (parser,
+                                 "Expected a valid X coordinate");
+          return NULL;
         }
 
-      SKIP_SPACES (str);
-
-      if (strncmp (str, "top", 3) == 0)
-        {
-          coords[(i * 3) + 1] = 0;
-          str += strlen ("top");
-        }
-      else if (strncmp (str, "bottom", 6) == 0)
-        {
-          coords[(i * 3) + 1] = 1;
-          str += strlen ("bottom");
-        }
-      else if (strncmp (str, "center", 6) == 0)
-        {
-          coords[(i * 3) + 1] = 0.5;
-          str += strlen ("center");
-        }
-      else
+      if (_gtk_css_parser_try (parser, "top", TRUE))
+        coords[i * 3 + 1] = 0;
+      else if (_gtk_css_parser_try (parser, "bottom", TRUE))
+        coords[i * 3 + 1] = 1;
+      else if (_gtk_css_parser_try (parser, "center", TRUE))
+        coords[i * 3 + 1] = 0.5;
+      else if (!_gtk_css_parser_try_double (parser, &coords[i * 3 + 1]))
         {
-          coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
-
-          if (str == end)
-            return set_default_error (error, G_VALUE_TYPE (value));
-
-          str = end;
+          _gtk_css_parser_error (parser,
+                                 "Expected a valid Y coordinate");
+          return NULL;
         }
 
-      SKIP_SPACES (str);
-
       if (type == CAIRO_PATTERN_TYPE_RADIAL)
         {
           /* Parse radius */
-          if (*str != ',')
+          if (! _gtk_css_parser_try (parser, ",", TRUE))
             {
-              g_set_error_literal (error,
-                                   GTK_CSS_PROVIDER_ERROR,
-                                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-                                   "Expected ','");
-              return FALSE;
+              _gtk_css_parser_error (parser,
+                                     "Expected ','");
+              return NULL;
             }
 
-          str++;
-          SKIP_SPACES (str);
-
-          coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
-          str = end;
-
-          SKIP_SPACES (str);
+          if (! _gtk_css_parser_try_double (parser, &coords[(i * 3) + 2]))
+            {
+              _gtk_css_parser_error (parser,
+                                     "Expected a numer for the radius");
+              return NULL;
+            }
         }
     }
 
@@ -1093,102 +827,120 @@ gradient_value_from_string (const char  *str,
     gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
                                         coords[3], coords[4], coords[5]);
 
-  while (*str == ',')
+  while (_gtk_css_parser_try (parser, ",", TRUE))
     {
       GtkSymbolicColor *color;
       gdouble position;
 
-      str++;
-      SKIP_SPACES (str);
-
-      if (g_str_has_prefix (str, "from"))
+      if (_gtk_css_parser_try (parser, "from", TRUE))
         {
           position = 0;
-          str += strlen ("from");
-          SKIP_SPACES (str);
 
-          if (*str != '(')
+          if (!_gtk_css_parser_try (parser, "(", TRUE))
             {
-              g_object_unref (gradient);
-              return set_default_error (error, G_VALUE_TYPE (value));
+              gtk_gradient_unref (gradient);
+              _gtk_css_parser_error (parser,
+                                     "Expected '('");
+              return NULL;
             }
+
         }
-      else if (g_str_has_prefix (str, "to"))
+      else if (_gtk_css_parser_try (parser, "to", TRUE))
         {
           position = 1;
-          str += strlen ("to");
-          SKIP_SPACES (str);
 
-          if (*str != '(')
+          if (!_gtk_css_parser_try (parser, "(", TRUE))
             {
-              g_object_unref (gradient);
-              return set_default_error (error, G_VALUE_TYPE (value));
+              gtk_gradient_unref (gradient);
+              _gtk_css_parser_error (parser,
+                                     "Expected '('");
+              return NULL;
             }
+
         }
-      else if (g_str_has_prefix (str, "color-stop"))
+      else if (_gtk_css_parser_try (parser, "color-stop", TRUE))
         {
-          str += strlen ("color-stop");
-          SKIP_SPACES (str);
-
-          if (*str != '(')
+          if (!_gtk_css_parser_try (parser, "(", TRUE))
             {
-              g_object_unref (gradient);
-              return set_default_error (error, G_VALUE_TYPE (value));
+              gtk_gradient_unref (gradient);
+              _gtk_css_parser_error (parser,
+                                     "Expected '('");
+              return NULL;
             }
 
-          str++;
-          SKIP_SPACES (str);
-
-          position = g_ascii_strtod (str, &end);
-
-          str = end;
-          SKIP_SPACES (str);
+          if (!_gtk_css_parser_try_double (parser, &position))
+            {
+              gtk_gradient_unref (gradient);
+              _gtk_css_parser_error (parser,
+                                     "Expected a valid number");
+              return NULL;
+            }
 
-          if (*str != ',')
+          if (!_gtk_css_parser_try (parser, ",", TRUE))
             {
-              g_object_unref (gradient);
-              return set_default_error (error, G_VALUE_TYPE (value));
+              gtk_gradient_unref (gradient);
+              _gtk_css_parser_error (parser,
+                                     "Expected a comma");
+              return NULL;
             }
         }
       else
         {
-          g_object_unref (gradient);
-          return set_default_error (error, G_VALUE_TYPE (value));
+          gtk_gradient_unref (gradient);
+          _gtk_css_parser_error (parser,
+                                 "Not a valid color-stop definition");
+          return NULL;
         }
 
-      str++;
-      SKIP_SPACES (str);
-
-      color = symbolic_color_parse_str (str, &end);
-
-      str = end;
-      SKIP_SPACES (str);
-
-      if (*str != ')')
+      color = _gtk_css_parser_read_symbolic_color (parser);
+      if (color == NULL)
         {
-          if (color)
-            gtk_symbolic_color_unref (color);
-          g_object_unref (gradient);
-          return set_default_error (error, G_VALUE_TYPE (value));
+          gtk_gradient_unref (gradient);
+          return NULL;
         }
 
-      str++;
-      SKIP_SPACES (str);
+      gtk_gradient_add_color_stop (gradient, position, color);
+      gtk_symbolic_color_unref (color);
 
-      if (color)
+      if (!_gtk_css_parser_try (parser, ")", TRUE))
         {
-          gtk_gradient_add_color_stop (gradient, position, color);
-          gtk_symbolic_color_unref (color);
+          gtk_gradient_unref (gradient);
+          _gtk_css_parser_error (parser,
+                                 "Expected ')'");
+          return NULL;
         }
     }
 
-  if (*str != ')')
+  if (!_gtk_css_parser_try (parser, ")", TRUE))
     {
-      g_object_unref (gradient);
-      return set_default_error (error, G_VALUE_TYPE (value));
+      gtk_gradient_unref (gradient);
+      _gtk_css_parser_error (parser,
+                             "Expected ')'");
+      return NULL;
     }
 
-  g_value_take_boxed (value, gradient);
+  return gradient;
+}
+
+static gboolean 
+gradient_value_from_string (const char  *str,
+                            GFile       *base,
+                            GValue      *value,
+                            GError     **error)
+{
+  GtkGradient *gradient;
+  GtkCssParser *parser;
+
+  parser = _gtk_css_parser_new (str,
+                                propagate_parser_error,
+                                error);
+  gradient = _gtk_css_parse_gradient (parser);
+  _gtk_css_parser_free (parser);
+
+  if (gradient == NULL)
+    return FALSE;
+
+  g_value_set_boxed (value, gradient);
   return TRUE;
 }
 
@@ -1422,7 +1174,7 @@ flags_value_from_string (const char  *str,
         {
           g_set_error (error,
                        GTK_CSS_PROVIDER_ERROR,
-                       GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+                       GTK_CSS_PROVIDER_ERROR_SYNTAX,
                        "Unknown flag value '%s' for type '%s'",
                        strv[i], g_type_name (G_VALUE_TYPE (value)));
           g_type_class_unref (flags_class);
@@ -1559,7 +1311,7 @@ _gtk_css_value_from_string (GValue        *value,
     {
       g_set_error (error,
                    GTK_CSS_PROVIDER_ERROR,
-                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                   GTK_CSS_PROVIDER_ERROR_SYNTAX,
                    "Cannot convert to type '%s'",
                    g_type_name (G_VALUE_TYPE (value)));
       return FALSE;
@@ -1587,32 +1339,6 @@ _gtk_css_value_to_string (const GValue *value)
   return g_strdup_value_contents (value);
 }
 
-GtkSymbolicColor *
-_gtk_css_parse_symbolic_color (const char    *str,
-                               GError       **error)
-{
-  GtkSymbolicColor *color;
-  gchar *end;
-
-  color = symbolic_color_parse_str (str, &end);
-
-  if (*end != '\0')
-    {
-      if (color)
-        {
-          gtk_symbolic_color_unref (color);
-          color = NULL;
-        }
-
-      g_set_error_literal (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
-                           "Failed to parse symbolic color");
-    }
-
-  return color;
-}
-
 GFile *
 _gtk_css_parse_url (GFile       *base,
                     const char  *str,
@@ -1631,7 +1357,7 @@ _gtk_css_parse_url (GFile       *base,
         {
           g_set_error_literal (error,
                                GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                "Expected '(' after 'url'");
           return NULL;
         }
@@ -1641,7 +1367,7 @@ _gtk_css_parse_url (GFile       *base,
         {
           g_set_error_literal (error,
                                GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                "No closing ')' found for 'url'");
           return NULL;
         }
@@ -1665,7 +1391,7 @@ _gtk_css_parse_url (GFile       *base,
             {
               g_set_error_literal (error,
                                    GTK_CSS_PROVIDER_ERROR,
-                                   GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                                   GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                    "Did not find closing quote for url");
               return NULL;
             }
@@ -1674,7 +1400,7 @@ _gtk_css_parse_url (GFile       *base,
         {
           g_set_error_literal (error,
                                GTK_CSS_PROVIDER_ERROR,
-                               GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
+                               GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                "url not properly escaped");
           return NULL;
         }
diff --git a/gtk/gtkcssstringfuncsprivate.h b/gtk/gtkcssstringfuncsprivate.h
index 900d38c..8a910f9 100644
--- a/gtk/gtkcssstringfuncsprivate.h
+++ b/gtk/gtkcssstringfuncsprivate.h
@@ -30,9 +30,6 @@ gboolean                _gtk_css_value_from_string        (GValue        *value,
                                                            GError       **error);
 char *                  _gtk_css_value_to_string          (const GValue  *value);
 
-GtkSymbolicColor *      _gtk_css_parse_symbolic_color     (const char    *str,
-                                                           GError       **error);
-
 GFile *                 _gtk_css_parse_url                (GFile         *base,
                                                            const char    *str,
                                                            char         **end,
diff --git a/tests/css/parser/boolean.errors b/tests/css/parser/boolean.errors
index d609f13..93dcca5 100644
--- a/tests/css/parser/boolean.errors
+++ b/tests/css/parser/boolean.errors
@@ -1,8 +1,8 @@
-boolean.css:26: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:29: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:32: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:35: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:38: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:41: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:44: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-boolean.css:47: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
+boolean.css:26: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:29: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:32: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:35: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:38: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:41: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:44: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+boolean.css:47: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
diff --git a/tests/css/parser/border.errors b/tests/css/parser/border.errors
index a002e60..7d17ea0 100644
--- a/tests/css/parser/border.errors
+++ b/tests/css/parser/border.errors
@@ -1,8 +1,8 @@
-border.css:26: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:30: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:34: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:38: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:42: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:46: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:50: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-border.css:54: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME
+border.css:26: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:30: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:34: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:38: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:42: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:46: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:50: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+border.css:54: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
diff --git a/tests/css/parser/does-not-exist.errors b/tests/css/parser/does-not-exist.errors
index 22d7743..512b42f 100644
--- a/tests/css/parser/does-not-exist.errors
+++ b/tests/css/parser/does-not-exist.errors
@@ -1 +1 @@
-does-not-exist.css:2: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME
+does-not-exist.css:2: error: GTK_CSS_PROVIDER_ERROR_NAME
diff --git a/tests/css/parser/integer.errors b/tests/css/parser/integer.errors
index 4cb80b3..8cd7044 100644
--- a/tests/css/parser/integer.errors
+++ b/tests/css/parser/integer.errors
@@ -1,8 +1,8 @@
-integer.css:17: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-integer.css:20: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-integer.css:23: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-integer.css:29: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-integer.css:32: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
+integer.css:17: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+integer.css:20: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+integer.css:23: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+integer.css:29: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+integer.css:32: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
 integer.css:35: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
-integer.css:38: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
-integer.css:41: error: GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
+integer.css:38: error: GTK_CSS_PROVIDER_ERROR_SYNTAX
+integer.css:41: error: GTK_CSS_PROVIDER_ERROR_SYNTAX



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