[gtk/wip/otte/css: 22/52] css: Redo for new parser



commit 9aceee10d84009d6ac2df9e1d3064a0a212e5089
Author: Benjamin Otte <otte redhat com>
Date:   Tue Apr 9 04:47:00 2019 +0200

    css: Redo for new parser
    
    This commit is still way too big, but I couldn't make it smaller.
    
    It transitions the old CSS parser to the new parser. CSS parsing is now
    tokenized, everything else is probably still buggy.

 gtk/css/gtkcssparser.c          |   44 +-
 gtk/css/gtkcssparserprivate.h   |    2 +
 gtk/gtkcsscalcvalue.c           |   58 +-
 gtk/gtkcsscolorvalue.c          |  298 ++++------
 gtk/gtkcssdimensionvalue.c      |  113 ++++
 gtk/gtkcssfontfeaturesvalue.c   |    1 -
 gtk/gtkcssfontvariationsvalue.c |    1 -
 gtk/gtkcssimage.c               |    3 +
 gtk/gtkcsskeyframes.c           |   58 +-
 gtk/gtkcssnumbervalue.c         |    9 +-
 gtk/gtkcssparser.c              | 1244 +--------------------------------------
 gtk/gtkcssparserprivate.h       |   92 +--
 gtk/gtkcssprovider.c            |  255 ++++----
 gtk/gtkcsssection.c             |   33 +-
 gtk/gtkcssselector.c            |  816 +++++++++++++++++--------
 gtk/gtkcssshadowvalue.c         |    3 -
 16 files changed, 1011 insertions(+), 2019 deletions(-)
---
diff --git a/gtk/css/gtkcssparser.c b/gtk/css/gtkcssparser.c
index e0bb5f8a8a..6f63393931 100644
--- a/gtk/css/gtkcssparser.c
+++ b/gtk/css/gtkcssparser.c
@@ -352,21 +352,13 @@ gtk_css_parser_end_block (GtkCssParser *self)
 
   if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
     {
-      gtk_css_parser_warn (self,
-                           GTK_CSS_PARSER_WARNING_SYNTAX,
-                           gtk_css_parser_get_block_location (self),
-                           gtk_css_parser_get_start_location (self),
-                           "Unterminated block at end of document");
+      gtk_css_parser_warn_syntax (self, "Unterminated block at end of document");
       g_array_set_size (self->blocks, self->blocks->len - 1);
     }
   else if (gtk_css_token_is (&self->token, block->inherited_end_token))
     {
       g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
-      gtk_css_parser_warn (self,
-                           GTK_CSS_PARSER_WARNING_SYNTAX,
-                           gtk_css_parser_get_block_location (self),
-                           gtk_css_parser_get_start_location (self),
-                           "Expected ';' at end of block");
+      gtk_css_parser_warn_syntax (self, "Expected ';' at end of block");
       g_array_set_size (self->blocks, self->blocks->len - 1);
     }
   else
@@ -434,15 +426,13 @@ gtk_css_parser_skip_until (GtkCssParser    *self,
 }
 
 void
-gtk_css_parser_emit_error (GtkCssParser *self,
-                           const GError *error)
+gtk_css_parser_emit_error (GtkCssParser         *self,
+                           const GtkCssLocation *start,
+                           const GtkCssLocation *end,
+                           const GError         *error)
 {
   if (self->error_func)
-    self->error_func (self,
-                      &self->location,
-                      &self->location,
-                      error,
-                      self->user_data);
+    self->error_func (self, start, end, error, self->user_data);
 }
 
 void
@@ -457,7 +447,10 @@ gtk_css_parser_error_syntax (GtkCssParser *self,
   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
                               GTK_CSS_PARSER_ERROR_SYNTAX,
                               format, args);
-  gtk_css_parser_emit_error (self, error);
+  gtk_css_parser_emit_error (self,
+                             &self->location,
+                             gtk_css_tokenizer_get_location (self->tokenizer),
+                             error);
   g_error_free (error);
   va_end (args);
 }
@@ -474,7 +467,10 @@ gtk_css_parser_error_value (GtkCssParser *self,
   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
                               GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
                               format, args);
-  gtk_css_parser_emit_error (self, error);
+  gtk_css_parser_emit_error (self,
+                             &self->location,
+                             gtk_css_tokenizer_get_location (self->tokenizer),
+                             error);
   g_error_free (error);
   va_end (args);
 }
@@ -491,7 +487,10 @@ gtk_css_parser_warn_syntax (GtkCssParser *self,
   error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
                               GTK_CSS_PARSER_WARNING_SYNTAX,
                               format, args);
-  gtk_css_parser_emit_error (self, error);
+  gtk_css_parser_emit_error (self,
+                             &self->location,
+                             gtk_css_tokenizer_get_location (self->tokenizer),
+                             error);
   g_error_free (error);
   va_end (args);
 }
@@ -864,7 +863,10 @@ gtk_css_parser_consume_url (GtkCssParser *self)
       GError *error = g_error_new (GTK_CSS_PARSER_ERROR,
                                    GTK_CSS_PARSER_ERROR_IMPORT,
                                    "Could not resolve \"%s\" to a valid URL", url);
-      gtk_css_parser_emit_error (self, error);
+      gtk_css_parser_emit_error (self,
+                                 &self->location,
+                                 gtk_css_tokenizer_get_location (self->tokenizer),
+                                 error);
       g_free (url);
       g_error_free (error);
       return NULL;
diff --git a/gtk/css/gtkcssparserprivate.h b/gtk/css/gtkcssparserprivate.h
index 9ae77e2e7e..f39d0025a3 100644
--- a/gtk/css/gtkcssparserprivate.h
+++ b/gtk/css/gtkcssparserprivate.h
@@ -82,6 +82,8 @@ void                    gtk_css_parser_skip_until               (GtkCssParser
                                                                  GtkCssTokenType                 token_type);
 
 void                    gtk_css_parser_emit_error               (GtkCssParser                   *self,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
                                                                  const GError                   *error);
 void                    gtk_css_parser_error_syntax             (GtkCssParser                   *self,
                                                                  const char                     *format,
diff --git a/gtk/gtkcsscalcvalue.c b/gtk/gtkcsscalcvalue.c
index 129dd93c52..aed0326fb6 100644
--- a/gtk/gtkcsscalcvalue.c
+++ b/gtk/gtkcsscalcvalue.c
@@ -322,19 +322,24 @@ gtk_css_calc_value_parse_value (GtkCssParser           *parser,
       return NULL;
     }
 
-  if (_gtk_css_parser_try (parser, "(", TRUE))
+  if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_PARENS))
     {
-      GtkCssValue *result = gtk_css_calc_value_parse_sum (parser, flags);
-      if (result == NULL)
-        return NULL;
+      GtkCssValue *result;
+
+      gtk_css_parser_start_block (parser);
 
-      if (!_gtk_css_parser_try (parser, ")", TRUE))
+      result = gtk_css_calc_value_parse_sum (parser, flags);
+      if (result == NULL)
         {
-          _gtk_css_parser_error (parser, "Missing closing ')' in calc() subterm");
-          _gtk_css_value_unref (result);
+          gtk_css_parser_end_block (parser);
           return NULL;
         }
 
+      if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
+        _gtk_css_parser_error (parser, "Expected closing ')' in calc() subterm");
+
+      gtk_css_parser_end_block (parser);
+
       return result;
     }
 
@@ -454,32 +459,45 @@ fail:
   return NULL;
 }
 
+typedef struct
+{
+  GtkCssNumberParseFlags flags;
+  GtkCssValue *value;
+} ParseCalcData;
+
+static guint
+gtk_css_calc_value_parse_arg (GtkCssParser *parser,
+                              guint         arg,
+                              gpointer      data_)
+{
+  ParseCalcData *data = data_;
+
+  data->value = gtk_css_calc_value_parse_sum (parser, data->flags);
+  if (data->value == NULL)
+    return 0;
+
+  return 1;
+}
+
 GtkCssValue *
 gtk_css_calc_value_parse (GtkCssParser           *parser,
                           GtkCssNumberParseFlags  flags)
 {
-  GtkCssValue *value;
+  ParseCalcData data;
 
   /* This can only be handled at compute time, we allow '-' after all */
-  flags &= ~GTK_CSS_POSITIVE_ONLY;
+  data.flags = flags & ~GTK_CSS_POSITIVE_ONLY;
+  data.value = NULL;
 
-  if (!_gtk_css_parser_try (parser, "calc(", TRUE))
+  if (!gtk_css_parser_has_function (parser, "calc"))
     {
       _gtk_css_parser_error (parser, "Expected 'calc('");
       return NULL;
     }
 
-  value = gtk_css_calc_value_parse_sum (parser, flags);
-  if (value == NULL)
+  if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_calc_value_parse_arg, &data))
     return NULL;
 
-  if (!_gtk_css_parser_try (parser, ")", TRUE))
-    {
-      _gtk_css_value_unref (value);
-      _gtk_css_parser_error (parser, "Expected ')' after calc() statement");
-      return NULL;
-    }
-
-  return value;
+  return data.value;
 }
 
diff --git a/gtk/gtkcsscolorvalue.c b/gtk/gtkcsscolorvalue.c
index 0735d1e9f4..40b3159666 100644
--- a/gtk/gtkcsscolorvalue.c
+++ b/gtk/gtkcsscolorvalue.c
@@ -22,9 +22,10 @@
 #include "gtkcssrgbavalueprivate.h"
 #include "gtkcssstylepropertyprivate.h"
 #include "gtkhslaprivate.h"
+#include "gtkprivate.h"
 #include "gtkstylepropertyprivate.h"
 
-#include "gtkprivate.h"
+#include "gdk/gdkrgbaprivate.h"
 
 typedef enum {
   COLOR_TYPE_LITERAL,
@@ -510,227 +511,144 @@ _gtk_css_color_value_new_current_color (void)
   return _gtk_css_value_ref (&current_color);
 }
 
-typedef enum {
-  COLOR_RGBA,
-  COLOR_RGB,
-  COLOR_LIGHTER,
-  COLOR_DARKER,
-  COLOR_SHADE,
-  COLOR_ALPHA,
-  COLOR_MIX
-} ColorParseType;
-
-static GtkCssValue *
-_gtk_css_color_value_parse_function (GtkCssParser   *parser,
-                                     ColorParseType  color)
+typedef struct 
 {
-  GtkCssValue *value;
-  GtkCssValue *child1, *child2;
-  double d;
-
-  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;
+  GtkCssValue *color;
+  GtkCssValue *color2;
+  double       value;
+} ColorFunctionData;
+
+static guint
+parse_color_mix (GtkCssParser *parser,
+                 guint         arg,
+                 gpointer      data_)
+{
+  ColorFunctionData *data = data_;
 
-      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_consume_number (parser, &tmp))
-            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 ();
-        }
+  switch (arg)
+  {
+    case 0:
+      data->color = _gtk_css_color_value_parse (parser);
+      if (data->color == NULL)
+        return 0;
+      return 1;
+
+    case 1:
+      data->color2 = _gtk_css_color_value_parse (parser);
+      if (data->color2 == NULL)
+        return 0;
+      return 1;
+
+    case 2:
+      if (!gtk_css_parser_consume_number (parser, &data->value))
+        return 0;
+      return 1;
 
-      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_consume_number (parser, &rgba.alpha))
-            return NULL;
-        }
-      else
-        rgba.alpha = 1.0;
-      
-      value = _gtk_css_color_value_new_literal (&rgba);
-    }
-  else
-    {
-      child1 = _gtk_css_color_value_parse (parser);
-      if (child1 == NULL)
-        return NULL;
+    default:
+      g_return_val_if_reached (0);
+  }
+}
 
-      if (color == COLOR_MIX)
-        {
-          if (!_gtk_css_parser_try (parser, ",", TRUE))
-            {
-              _gtk_css_parser_error (parser, "Expected ',' in color definition");
-              _gtk_css_value_unref (child1);
-              return NULL;
-            }
-
-          child2 = _gtk_css_color_value_parse (parser);
-          if (child2 == NULL)
-            {
-              _gtk_css_value_unref (child1);
-              return NULL;
-            }
-        }
-      else
-        child2 = NULL;
+static guint
+parse_color_number (GtkCssParser *parser,
+                    guint         arg,
+                    gpointer      data_)
+{
+  ColorFunctionData *data = data_;
 
-      if (color == COLOR_LIGHTER)
-        d = 1.3;
-      else if (color == COLOR_DARKER)
-        d = 0.7;
-      else
-        {
-          if (!_gtk_css_parser_try (parser, ",", TRUE))
-            {
-              _gtk_css_parser_error (parser, "Expected ',' in color definition");
-              _gtk_css_value_unref (child1);
-              if (child2)
-                _gtk_css_value_unref (child2);
-              return NULL;
-            }
-
-          if (!gtk_css_parser_consume_number (parser, &d))
-            {
-              _gtk_css_value_unref (child1);
-              if (child2)
-                _gtk_css_value_unref (child2);
-              return NULL;
-            }
-        }
-      
-      switch (color)
-        {
-        case COLOR_LIGHTER:
-        case COLOR_DARKER:
-        case COLOR_SHADE:
-          value = _gtk_css_color_value_new_shade (child1, d);
-          break;
-        case COLOR_ALPHA:
-          value = _gtk_css_color_value_new_alpha (child1, d);
-          break;
-        case COLOR_MIX:
-          value = _gtk_css_color_value_new_mix (child1, child2, d);
-          break;
-        case COLOR_RGB:
-        case COLOR_RGBA:
-        default:
-          g_assert_not_reached ();
-          value = NULL;
-        }
+  switch (arg)
+  {
+    case 0:
+      data->color = _gtk_css_color_value_parse (parser);
+      if (data->color == NULL)
+        return 0;
+      return 1;
 
-      _gtk_css_value_unref (child1);
-      if (child2)
-        _gtk_css_value_unref (child2);
-    }
+    case 1:
+      if (!gtk_css_parser_consume_number (parser, &data->value))
+        return 0;
+      return 1;
 
-  if (!_gtk_css_parser_try (parser, ")", TRUE))
-    {
-      _gtk_css_parser_error (parser, "Expected ')' in color definition");
-      _gtk_css_value_unref (value);
-      return NULL;
-    }
-
-  return value;
+    default:
+      g_return_val_if_reached (0);
+  }
 }
 
 GtkCssValue *
 _gtk_css_color_value_parse (GtkCssParser *parser)
 {
+  ColorFunctionData data = { NULL, };
   GtkCssValue *value;
   GdkRGBA rgba;
-  guint color;
-  const char *names[] = {"rgba", "rgb",  "lighter", "darker", "shade", "alpha", "mix"};
-  char *name;
 
   if (gtk_css_parser_try_ident (parser, "currentColor"))
-    return _gtk_css_color_value_new_current_color ();
-
-  if (gtk_css_parser_try_ident (parser, "transparent"))
     {
-      GdkRGBA transparent = { 0, 0, 0, 0 };
-      
-      return _gtk_css_color_value_new_literal (&transparent);
+      return _gtk_css_color_value_new_current_color ();
     }
-
-  if (_gtk_css_parser_try (parser, "@", FALSE))
+  else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_AT_KEYWORD))
     {
-      name = _gtk_css_parser_try_name (parser, TRUE);
+      const GtkCssToken *token = gtk_css_parser_get_token (parser);
 
-      if (name)
-        {
-          value = _gtk_css_color_value_new_name (name);
-        }
-      else
-        {
-          _gtk_css_parser_error (parser, "'%s' is not a valid color color name", name);
-          value = NULL;
-        }
+      value = _gtk_css_color_value_new_name (token->string.string);
+      gtk_css_parser_consume_token (parser);
 
-      g_free (name);
       return value;
     }
-
-  for (color = 0; color < G_N_ELEMENTS (names); color++)
+  else if (gtk_css_parser_has_function (parser, "lighter"))
     {
-      if (_gtk_css_parser_try (parser, names[color], TRUE))
-        break;
+      if (gtk_css_parser_consume_function (parser, 1, 1, parse_color_number, &data))
+        value = _gtk_css_color_value_new_shade (data.color, 1.3);
+      else
+        value = NULL;
+
+      g_clear_pointer (&data.color, gtk_css_value_unref);
+      return value;
     }
+  else if (gtk_css_parser_has_function (parser, "darker"))
+    {
+      if (gtk_css_parser_consume_function (parser, 1, 1, parse_color_number, &data))
+        value = _gtk_css_color_value_new_shade (data.color, 0.7);
+      else
+        value = NULL;
 
-  if (color < G_N_ELEMENTS (names))
-    return _gtk_css_color_value_parse_function (parser, color);
+      g_clear_pointer (&data.color, gtk_css_value_unref);
+      return value;
+    }
+  else if (gtk_css_parser_has_function (parser, "shade"))
+    {
+      if (gtk_css_parser_consume_function (parser, 2, 2, parse_color_number, &data))
+        value = _gtk_css_color_value_new_shade (data.color, data.value);
+      else
+        value = NULL;
 
-  if (_gtk_css_parser_try_hash_color (parser, &rgba))
-    return _gtk_css_color_value_new_literal (&rgba);
+      g_clear_pointer (&data.color, gtk_css_value_unref);
+      return value;
+    }
+  else if (gtk_css_parser_has_function (parser, "alpha"))
+    {
+      if (gtk_css_parser_consume_function (parser, 2, 2, parse_color_number, &data))
+        value = _gtk_css_color_value_new_alpha (data.color, data.value);
+      else
+        value = NULL;
 
-  name = _gtk_css_parser_try_name (parser, TRUE);
-  if (name)
+      g_clear_pointer (&data.color, gtk_css_value_unref);
+      return value;
+    }
+  else if (gtk_css_parser_has_function (parser, "mix"))
     {
-      if (gdk_rgba_parse (&rgba, name))
-        {
-          value = _gtk_css_color_value_new_literal (&rgba);
-        }
+      if (gtk_css_parser_consume_function (parser, 3, 3, parse_color_mix, &data))
+        value = _gtk_css_color_value_new_mix (data.color, data.color2, data.value);
       else
-        {
-          _gtk_css_parser_error (parser, "'%s' is not a valid color name", name);
-          value = NULL;
-        }
-      g_free (name);
+        value = NULL;
+
+      g_clear_pointer (&data.color, gtk_css_value_unref);
+      g_clear_pointer (&data.color2, gtk_css_value_unref);
       return value;
     }
 
-  _gtk_css_parser_error (parser, "Not a color definition");
-  return NULL;
+  if (gdk_rgba_parser_parse (parser, &rgba))
+    return _gtk_css_color_value_new_literal (&rgba);
+  else
+    return NULL;
 }
 
diff --git a/gtk/gtkcssdimensionvalue.c b/gtk/gtkcssdimensionvalue.c
index c9c378e0cf..feaaf9a991 100644
--- a/gtk/gtkcssdimensionvalue.c
+++ b/gtk/gtkcssdimensionvalue.c
@@ -325,3 +325,116 @@ gtk_css_dimension_value_new (double     value,
   return result;
 }
 
+GtkCssValue *
+gtk_css_dimension_value_parse (GtkCssParser           *parser,
+                               GtkCssNumberParseFlags  flags)
+{
+  static const struct {
+    const char *name;
+    GtkCssUnit unit;
+    GtkCssNumberParseFlags required_flags;
+  } units[] = {
+    { "px",   GTK_CSS_PX,      GTK_CSS_PARSE_LENGTH },
+    { "pt",   GTK_CSS_PT,      GTK_CSS_PARSE_LENGTH },
+    { "em",   GTK_CSS_EM,      GTK_CSS_PARSE_LENGTH },
+    { "ex",   GTK_CSS_EX,      GTK_CSS_PARSE_LENGTH },
+    { "rem",  GTK_CSS_REM,     GTK_CSS_PARSE_LENGTH },
+    { "pc",   GTK_CSS_PC,      GTK_CSS_PARSE_LENGTH },
+    { "in",   GTK_CSS_IN,      GTK_CSS_PARSE_LENGTH },
+    { "cm",   GTK_CSS_CM,      GTK_CSS_PARSE_LENGTH },
+    { "mm",   GTK_CSS_MM,      GTK_CSS_PARSE_LENGTH },
+    { "rad",  GTK_CSS_RAD,     GTK_CSS_PARSE_ANGLE  },
+    { "deg",  GTK_CSS_DEG,     GTK_CSS_PARSE_ANGLE  },
+    { "grad", GTK_CSS_GRAD,    GTK_CSS_PARSE_ANGLE  },
+    { "turn", GTK_CSS_TURN,    GTK_CSS_PARSE_ANGLE  },
+    { "s",    GTK_CSS_S,       GTK_CSS_PARSE_TIME   },
+    { "ms",   GTK_CSS_MS,      GTK_CSS_PARSE_TIME   }
+  };
+  const GtkCssToken *token;
+  GtkCssValue *result;
+  GtkCssUnit unit;
+  double number;
+
+  token = gtk_css_parser_get_token (parser);
+
+  /* Handle percentages first */
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
+    {
+      if (!(flags & GTK_CSS_PARSE_PERCENT))
+        {
+          gtk_css_parser_error_value (parser, "Percentages are not allowed here");
+          return NULL;
+        }
+      number = token->number.number;
+      unit = GTK_CSS_PERCENT;
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
+    {
+      number = token->number.number;
+      if (number == 0.0)
+        {
+          if (flags & GTK_CSS_PARSE_NUMBER)
+            unit = GTK_CSS_NUMBER;
+          else if (flags & GTK_CSS_PARSE_LENGTH)
+            unit = GTK_CSS_PX;
+          else if (flags & GTK_CSS_PARSE_ANGLE)
+            unit = GTK_CSS_DEG;
+          else if (flags & GTK_CSS_PARSE_TIME)
+            unit = GTK_CSS_S;
+          else
+            unit = GTK_CSS_PERCENT;
+        }
+      else if (flags & GTK_CSS_PARSE_NUMBER)
+        {
+          unit = GTK_CSS_NUMBER;
+        }
+      else
+        {
+          gtk_css_parser_error_syntax (parser, "Unit is missing.");
+          return NULL;
+        }
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_DIMENSION))
+    {
+      guint i;
+
+      for (i = 0; i < G_N_ELEMENTS (units); i++)
+        {
+          if (flags & units[i].required_flags &&
+              g_ascii_strcasecmp (token->dimension.dimension, units[i].name) == 0)
+            break;
+        }
+
+      if (i >= G_N_ELEMENTS (units))
+        {
+          gtk_css_parser_error_syntax (parser, "'%s' is not a valid unit.", token->dimension.dimension);
+          return NULL;
+        }
+
+      unit = units[i].unit;
+      number = token->dimension.value;
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (parser, "Expected a number");
+      return NULL;
+    }
+
+  if (flags & GTK_CSS_POSITIVE_ONLY &&
+      number < 0)
+    {
+      gtk_css_parser_error_value (parser, "negative values are not allowed.");
+      return NULL;
+    }
+
+  result = gtk_css_dimension_value_new (number, unit);
+  gtk_css_parser_consume_token (parser);
+
+  return result;
+}
+
diff --git a/gtk/gtkcssfontfeaturesvalue.c b/gtk/gtkcssfontfeaturesvalue.c
index 6f7c2894f0..5f7dce764b 100644
--- a/gtk/gtkcssfontfeaturesvalue.c
+++ b/gtk/gtkcssfontfeaturesvalue.c
@@ -236,7 +236,6 @@ gtk_css_font_features_value_parse (GtkCssParser *parser)
   result = gtk_css_font_features_value_new_empty ();
 
   do {
-    _gtk_css_parser_skip_whitespace (parser);
     name = gtk_css_parser_consume_string (parser);
     if (name == NULL)
       {
diff --git a/gtk/gtkcssfontvariationsvalue.c b/gtk/gtkcssfontvariationsvalue.c
index be3ab90e72..537ebb1d6e 100644
--- a/gtk/gtkcssfontvariationsvalue.c
+++ b/gtk/gtkcssfontvariationsvalue.c
@@ -234,7 +234,6 @@ gtk_css_font_variations_value_parse (GtkCssParser *parser)
   result = gtk_css_font_variations_value_new_empty ();
 
   do {
-    _gtk_css_parser_skip_whitespace (parser);
     name = gtk_css_parser_consume_string (parser);
     if (name == NULL)
       {
diff --git a/gtk/gtkcssimage.c b/gtk/gtkcssimage.c
index ac0eab182a..b1e6d3b6e0 100644
--- a/gtk/gtkcssimage.c
+++ b/gtk/gtkcssimage.c
@@ -513,6 +513,9 @@ gtk_css_image_get_parser_type (GtkCssParser *parser)
         return image_types[i].type_func ();
     }
 
+  if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_URL))
+    return _gtk_css_image_url_get_type ();
+
   return G_TYPE_INVALID;
 }
 
diff --git a/gtk/gtkcsskeyframes.c b/gtk/gtkcsskeyframes.c
index 891da2ea59..16d0e5efda 100644
--- a/gtk/gtkcsskeyframes.c
+++ b/gtk/gtkcsskeyframes.c
@@ -215,23 +215,19 @@ keyframes_set_value (GtkCssKeyframes     *keyframes,
 }
 
 static gboolean
-parse_declaration (GtkCssKeyframes *keyframes,
-                   guint            k,
-                   GtkCssParser    *parser)
+gtk_css_keyframes_parse_declaration (GtkCssKeyframes *keyframes,
+                                     guint            k,
+                                     GtkCssParser    *parser)
 {
   GtkStyleProperty *property;
   GtkCssValue *value;
   char *name;
 
-  while (_gtk_css_parser_try (parser, ";", TRUE))
-    {
-      /* SKIP ALL THE THINGS! */
-    }
-
-  name = _gtk_css_parser_try_ident (parser, TRUE);
+  name = gtk_css_parser_consume_ident (parser);
   if (name == NULL)
     {
-      _gtk_css_parser_error (parser, "No property name given");
+      if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
+        gtk_css_parser_error_syntax (parser, "Expected a property name");
       return FALSE;
     }
 
@@ -246,7 +242,7 @@ parse_declaration (GtkCssKeyframes *keyframes,
 
   g_free (name);
 
-  if (!_gtk_css_parser_try (parser, ":", TRUE))
+  if (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COLON))
     {
       _gtk_css_parser_error (parser, "Expected a ':'");
       return FALSE;
@@ -256,8 +252,7 @@ parse_declaration (GtkCssKeyframes *keyframes,
   if (value == NULL)
     return FALSE;
 
-  if (!_gtk_css_parser_try (parser, ";", TRUE) &&
-      !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_CLOSE_CURLY))
+  if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
     {
       _gtk_css_parser_error (parser, "Junk at end of value");
       _gtk_css_value_unref (value);
@@ -297,28 +292,27 @@ parse_declaration (GtkCssKeyframes *keyframes,
 }
 
 static gboolean
-parse_block (GtkCssKeyframes *keyframes,
-             guint            k,
-             GtkCssParser    *parser)
+gtk_css_keyframes_parse_block (GtkCssKeyframes *keyframes,
+                               guint            k,
+                               GtkCssParser    *parser)
 {
-  if (!_gtk_css_parser_try (parser, "{", TRUE))
+  if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_CURLY))
     {
-      _gtk_css_parser_error (parser, "Expected closing bracket after keyframes block");
+      gtk_css_parser_error_syntax (parser, "Expected '{'");
       return FALSE;
     }
 
-  while (!_gtk_css_parser_try (parser, "}", TRUE))
-    {
-      if (!parse_declaration (keyframes, k, parser))
-        _gtk_css_parser_resync (parser, TRUE, '}');
+  gtk_css_parser_start_block (parser);
 
-      if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
-        {
-          _gtk_css_parser_error (parser, "Expected closing '}' after keyframes block");
-          return FALSE;
-        }
+  while (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_start_semicolon_block (parser, GTK_CSS_TOKEN_EOF);
+      gtk_css_keyframes_parse_declaration (keyframes, k, parser);
+      gtk_css_parser_end_block (parser);
     }
 
+  gtk_css_parser_end_block (parser);
+
   return TRUE;
 }
 
@@ -333,19 +327,18 @@ _gtk_css_keyframes_parse (GtkCssParser *parser)
 
   keyframes = gtk_css_keyframes_new ();
 
-  while (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_CLOSE_CURLY))
+  while (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
     {
       if (gtk_css_parser_try_ident (parser, "from"))
         progress = 0;
       else if (gtk_css_parser_try_ident (parser, "to"))
         progress = 1;
-      else if (gtk_css_parser_consume_number (parser, &progress) &&
-               _gtk_css_parser_try (parser, "%", TRUE))
+      else if (gtk_css_parser_consume_percentage (parser, &progress))
         {
           if (progress < 0 || progress > 100)
             {
               /* XXX: should we skip over the block here? */
-              _gtk_css_parser_error (parser, "percentages must be between 0%% and 100%%");
+              gtk_css_parser_error_value (parser, "percentages must be between 0%% and 100%%");
               _gtk_css_keyframes_unref (keyframes);
               return NULL;
             }
@@ -353,14 +346,13 @@ _gtk_css_keyframes_parse (GtkCssParser *parser)
         }
       else
         {
-          _gtk_css_parser_error (parser, "expected a percentage");
           _gtk_css_keyframes_unref (keyframes);
           return NULL;
         }
 
       k = gtk_css_keyframes_add_keyframe (keyframes, progress);
 
-      if (!parse_block (keyframes, k, parser))
+      if (!gtk_css_keyframes_parse_block (keyframes, k, parser))
         {
           _gtk_css_keyframes_unref (keyframes);
           return NULL;
diff --git a/gtk/gtkcssnumbervalue.c b/gtk/gtkcssnumbervalue.c
index 6b742c42e1..e8543bea28 100644
--- a/gtk/gtkcssnumbervalue.c
+++ b/gtk/gtkcssnumbervalue.c
@@ -129,7 +129,14 @@ gtk_css_number_value_transition (GtkCssValue *start,
 gboolean
 gtk_css_number_value_can_parse (GtkCssParser *parser)
 {
-  return _gtk_css_parser_has_number (parser)
+  return gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_NUMBER)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_PERCENTAGE)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)
+      || gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_DIMENSION)
       || gtk_css_parser_has_function (parser, "calc");
 }
 
diff --git a/gtk/gtkcssparser.c b/gtk/gtkcssparser.c
index 502f2ec794..46b8a9786b 100644
--- a/gtk/gtkcssparser.c
+++ b/gtk/gtkcssparser.c
@@ -19,1159 +19,23 @@
 
 #include "gtkcssparserprivate.h"
 
-#include "gtkcssdimensionvalueprivate.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;
-  GFile                 *file;
-  GtkCssParserErrorFunc  error_func;
-  gpointer               user_data;
-
-  const char            *line_start;
-  guint                  line;
-
-  /* Use this for parsing identifiers, names and strings. */
-  GString               *ident_str;
-};
-
-GtkCssParser *
-_gtk_css_parser_new (const char            *data,
-                     GFile                 *file,
-                     GtkCssParserErrorFunc  error_func,
-                     gpointer               user_data)
-{
-  GtkCssParser *parser;
-
-  g_return_val_if_fail (data != NULL, NULL);
-  g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
-
-  parser = g_slice_new0 (GtkCssParser);
-
-  parser->data = data;
-  if (file)
-    parser->file = g_object_ref (file);
-  parser->error_func = error_func;
-  parser->user_data = user_data;
-
-  parser->line_start = data;
-  parser->line = 0;
-
-  parser->ident_str = NULL;
-
-  return parser;
-}
-
-void
-_gtk_css_parser_free (GtkCssParser *parser)
-{
-  g_return_if_fail (GTK_IS_CSS_PARSER (parser));
-
-  if (parser->file)
-    g_object_unref (parser->file);
-
-  if (parser->ident_str)
-    g_string_free (parser->ident_str, TRUE);
-
-  g_slice_free (GtkCssParser, parser);
-}
-
-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;
-}
-
-static GFile *
-gtk_css_parser_get_base_file (GtkCssParser *parser)
-{
-  GFile *base;
-
-  if (parser->file)
-    {
-      base = g_file_get_parent (parser->file);
-    }
-  else
-    {
-      char *dir = g_get_current_dir ();
-      base = g_file_new_for_path (dir);
-      g_free (dir);
-    }
-
-  return base;
-}
-
-GFile *
-gtk_css_parser_resolve_url (GtkCssParser *parser,
-                            const char   *url)
-{
-  GFile *base, *file;
-  char *scheme;
-
-  g_return_val_if_fail (parser != NULL, NULL);
-  g_return_val_if_fail (url != NULL, NULL);
-
-  scheme = g_uri_parse_scheme (url);
-  if (scheme != NULL)
-    {
-      file = g_file_new_for_uri (url);
-      g_free (scheme);
-      return file;
-    }
-  g_free (scheme);
-
-  base = gtk_css_parser_get_base_file (parser);
-  file = g_file_resolve_relative_path (base, url);
-  g_object_unref (base);
-
-  return file;
-}
-
-GFile *
-_gtk_css_parser_get_file (GtkCssParser *parser)
-{
-  g_return_val_if_fail (parser != NULL, NULL);
-
-  return parser->file;
-}
-
-void
-_gtk_css_parser_take_error (GtkCssParser *parser,
-                            GError       *error)
-{
-  parser->error_func (parser, error, parser->user_data);
-
-  g_error_free (error);
-}
-
 void
 _gtk_css_parser_error (GtkCssParser *parser,
                        const char   *format,
                        ...)
 {
-  GError *error;
-
+  GtkCssLocation location;
   va_list args;
+  GError *error;
 
+  gtk_css_parser_get_location (parser, &location);
   va_start (args, format);
   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
-                              GTK_CSS_PARSER_ERROR_SYNTAX,
+                              GTK_CSS_PARSER_ERROR_FAILED,
                               format, args);
+  gtk_css_parser_emit_error (parser, &location, &location, error);
+  g_error_free (error);
   va_end (args);
-
-  _gtk_css_parser_take_error (parser, 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 (len > 0 && 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)
-{
-  gsize string_len;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-  g_return_val_if_fail (string != NULL, FALSE);
-
-  string_len = strlen (string);
-
-  if (g_ascii_strncasecmp (parser->data, string, string_len) != 0)
-    return FALSE;
-
-  parser->data += string_len;
-
-  if (skip_whitespace)
-    _gtk_css_parser_skip_whitespace (parser);
-  return TRUE;
-}
-
-gboolean
-gtk_css_parser_try_at_keyword (GtkCssParser *parser,
-                               const char   *keyword)
-{
-  gsize len;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-  g_return_val_if_fail (keyword != NULL, FALSE);
-
-  len = strlen (keyword);
-
-  if (parser->data[0] != '@' ||
-      g_ascii_strncasecmp (&parser->data[1], keyword, len) != 0)
-    return FALSE;
-
-  parser->data += len + 1;
-
-  _gtk_css_parser_skip_whitespace (parser);
-
-  return TRUE;
-}
-
-gboolean
-gtk_css_parser_try_ident (GtkCssParser *parser,
-                          const char   *ident)
-{
-  gsize len;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-  g_return_val_if_fail (ident != NULL, FALSE);
-
-  len = strlen (ident);
-
-  if (g_ascii_strncasecmp (parser->data, ident, len) != 0 ||
-      parser->data[len] == '(')
-    return FALSE;
-
-  parser->data += len;
-
-  _gtk_css_parser_skip_whitespace (parser);
-
-  return TRUE;
-}
-
-gboolean
-gtk_css_parser_try_delim (GtkCssParser *parser,
-                          gunichar      delim)
-{
-  if (*parser->data != delim)
-    return FALSE;
-  parser->data += 1;
-  _gtk_css_parser_skip_whitespace (parser);
-  return TRUE;
-}
-
-gboolean
-gtk_css_parser_try_token (GtkCssParser    *parser,
-                          GtkCssTokenType  type)
-{
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-
-  switch (type)
-  {
-    case GTK_CSS_TOKEN_OPEN_CURLY:
-      if (*parser->data != '{')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_CLOSE_CURLY:
-      if (*parser->data != '}')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_OPEN_PARENS:
-      if (*parser->data != '(')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_CLOSE_PARENS:
-      if (*parser->data != ')')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_COMMA:
-      if (*parser->data != ',')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_COLON:
-      if (*parser->data != ':')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    case GTK_CSS_TOKEN_SEMICOLON:
-      if (*parser->data != ';')
-        return FALSE;
-      parser->data += 1;
-      _gtk_css_parser_skip_whitespace (parser);
-      return TRUE;
-
-    default:
-    case GTK_CSS_TOKEN_STRING:
-    case GTK_CSS_TOKEN_AT_KEYWORD:
-    case GTK_CSS_TOKEN_IDENT:
-    case GTK_CSS_TOKEN_FUNCTION:
-    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
-    case GTK_CSS_TOKEN_HASH_ID:
-    case GTK_CSS_TOKEN_URL:
-    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
-    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
-    case GTK_CSS_TOKEN_DIMENSION:
-    case GTK_CSS_TOKEN_EOF:
-    case GTK_CSS_TOKEN_WHITESPACE:
-    case GTK_CSS_TOKEN_OPEN_SQUARE:
-    case GTK_CSS_TOKEN_CLOSE_SQUARE:
-    case GTK_CSS_TOKEN_CDC:
-    case GTK_CSS_TOKEN_CDO:
-    case GTK_CSS_TOKEN_DELIM:
-    case GTK_CSS_TOKEN_SIGNED_INTEGER:
-    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
-    case GTK_CSS_TOKEN_SIGNED_NUMBER:
-    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
-    case GTK_CSS_TOKEN_PERCENTAGE:
-    case GTK_CSS_TOKEN_INCLUDE_MATCH:
-    case GTK_CSS_TOKEN_DASH_MATCH:
-    case GTK_CSS_TOKEN_PREFIX_MATCH:
-    case GTK_CSS_TOKEN_SUFFIX_MATCH:
-    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
-    case GTK_CSS_TOKEN_COLUMN:
-    case GTK_CSS_TOKEN_BAD_STRING:
-    case GTK_CSS_TOKEN_BAD_URL:
-    case GTK_CSS_TOKEN_COMMENT:
-      g_assert_not_reached ();
-      return FALSE;
-    }
-}
-
-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;
-}
-
-static char *
-_gtk_css_parser_get_ident (GtkCssParser *parser)
-{
-  char *result;
-  gsize len;
-
-  len = parser->ident_str->len;
-
-  result = g_new (char, len + 1);
-  memcpy (result, parser->ident_str->str, len + 1);
-  g_string_set_size (parser->ident_str, 0);
-
-  return result;
-}
-
-char *
-_gtk_css_parser_try_name (GtkCssParser *parser,
-                          gboolean      skip_whitespace)
-{
-  GString *name;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
-
-  if (parser->ident_str == NULL)
-    parser->ident_str = g_string_new (NULL);
-
-  name = parser->ident_str;
-
-  while (_gtk_css_parser_read_char (parser, name, NMCHAR))
-    ;
-
-  if (skip_whitespace)
-    _gtk_css_parser_skip_whitespace (parser);
-
-  return _gtk_css_parser_get_ident (parser);
-}
-
-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;
-  
-  if (parser->ident_str == NULL)
-    parser->ident_str = g_string_new (NULL);
-
-  ident = parser->ident_str;
-
-  if (*parser->data == '-')
-    {
-      g_string_append_c (ident, '-');
-      parser->data++;
-    }
-
-  if (!_gtk_css_parser_read_char (parser, ident, NMSTART))
-    {
-      parser->data = start;
-      g_string_set_size (ident, 0);
-      return NULL;
-    }
-
-  while (_gtk_css_parser_read_char (parser, ident, NMCHAR))
-    ;
-
-  if (skip_whitespace)
-    _gtk_css_parser_skip_whitespace (parser);
-
-  return _gtk_css_parser_get_ident (parser);
-}
-
-gboolean
-gtk_css_parser_has_token (GtkCssParser    *parser,
-                          GtkCssTokenType  type)
-{
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-
-  switch (type)
-  {
-    case GTK_CSS_TOKEN_STRING:
-      return *parser->data == '"' || *parser->data == '\'';
-
-    case GTK_CSS_TOKEN_OPEN_CURLY:
-      return *parser->data == '{';
-
-    case GTK_CSS_TOKEN_CLOSE_CURLY:
-      return *parser->data == '}';
-
-    case GTK_CSS_TOKEN_OPEN_PARENS:
-      return *parser->data == '(';
-
-    case GTK_CSS_TOKEN_CLOSE_PARENS:
-      return *parser->data == ')';
-
-    case GTK_CSS_TOKEN_COMMA:
-      return *parser->data == ',';
-
-    case GTK_CSS_TOKEN_COLON:
-      return *parser->data == ':';
-
-    case GTK_CSS_TOKEN_SEMICOLON:
-      return *parser->data == ';';
-
-    case GTK_CSS_TOKEN_AT_KEYWORD:
-      return *parser->data == '@';
-
-    case GTK_CSS_TOKEN_EOF:
-      return *parser->data == 0;
-
-    case GTK_CSS_TOKEN_IDENT:
-      return *parser->data != 0 &&
-             strchr (NMSTART "-", *parser->data) != NULL;
-
-    default:
-    case GTK_CSS_TOKEN_FUNCTION:
-    case GTK_CSS_TOKEN_HASH_UNRESTRICTED:
-    case GTK_CSS_TOKEN_HASH_ID:
-    case GTK_CSS_TOKEN_URL:
-    case GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION:
-    case GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION:
-    case GTK_CSS_TOKEN_DIMENSION:
-    case GTK_CSS_TOKEN_WHITESPACE:
-    case GTK_CSS_TOKEN_OPEN_SQUARE:
-    case GTK_CSS_TOKEN_CLOSE_SQUARE:
-    case GTK_CSS_TOKEN_CDC:
-    case GTK_CSS_TOKEN_CDO:
-    case GTK_CSS_TOKEN_DELIM:
-    case GTK_CSS_TOKEN_SIGNED_INTEGER:
-    case GTK_CSS_TOKEN_SIGNLESS_INTEGER:
-    case GTK_CSS_TOKEN_SIGNED_NUMBER:
-    case GTK_CSS_TOKEN_SIGNLESS_NUMBER:
-    case GTK_CSS_TOKEN_PERCENTAGE:
-    case GTK_CSS_TOKEN_INCLUDE_MATCH:
-    case GTK_CSS_TOKEN_DASH_MATCH:
-    case GTK_CSS_TOKEN_PREFIX_MATCH:
-    case GTK_CSS_TOKEN_SUFFIX_MATCH:
-    case GTK_CSS_TOKEN_SUBSTRING_MATCH:
-    case GTK_CSS_TOKEN_COLUMN:
-    case GTK_CSS_TOKEN_BAD_STRING:
-    case GTK_CSS_TOKEN_BAD_URL:
-    case GTK_CSS_TOKEN_COMMENT:
-      g_assert_not_reached ();
-      return FALSE;
-    }
-}
-
-gboolean
-gtk_css_parser_has_ident (GtkCssParser *parser,
-                          const char   *ident)
-{
-  gsize len;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-  g_return_val_if_fail (ident != NULL, FALSE);
-
-  len = strlen (ident);
-
-  return g_ascii_strncasecmp (parser->data, ident, len) == 0 &&
-         parser->data[len] != '(';
-}
-
-gboolean
-gtk_css_parser_has_integer (GtkCssParser *parser)
-{
-  guint i;
-
-  i = 0;
-  if (parser->data[0] == '-')
-    i++;
-
-  if (parser->data[i] >= '0' && parser->data[i] <= '9')
-    return TRUE;
-
-  return FALSE;
-}
-
-gboolean
-gtk_css_parser_has_function (GtkCssParser    *parser,
-                             const char      *name)
-{
-  gsize len;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), FALSE);
-  g_return_val_if_fail (name != NULL, FALSE);
-
-  len = strlen (name);
-
-  return g_ascii_strncasecmp (parser->data, name, len) == 0 &&
-         parser->data[len] == '(';
-}
-
-char *
-gtk_css_parser_consume_string (GtkCssParser *parser)
-{
-  GString *str;
-  char quote;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
-
-  quote = *parser->data;
-  
-  if (quote != '"' && quote != '\'')
-    {
-      _gtk_css_parser_error (parser, "Expected a string.");
-      return NULL;
-    }
-  
-  parser->data++;
-
-  if (parser->ident_str == NULL)
-    parser->ident_str = g_string_new (NULL);
-
-  str = parser->ident_str;
-  g_assert (str->len == 0);
-
-  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 _gtk_css_parser_get_ident (parser);
-            }
-          
-          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_set_size (str, 0);
-          return NULL;
-        default:
-          _gtk_css_parser_error (parser, 
-                                 "Invalid character in string. Must be escaped.");
-          g_string_set_size (str, 0);
-          return NULL;
-        }
-    }
-
-  g_assert_not_reached ();
-  return NULL;
-}
-
-gboolean
-gtk_css_parser_consume_integer (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 == '+')
-    goto fail;
-
-  errno = 0;
-  result = g_ascii_strtoll (parser->data, &end, 10);
-  if (errno)
-    goto fail;
-  if (result > G_MAXINT || result < G_MININT)
-    goto fail;
-  if (parser->data == end)
-    goto fail;
-
-  parser->data = end;
-  *value = result;
-
-  _gtk_css_parser_skip_whitespace (parser);
-
-  return TRUE;
-
-fail:
-  _gtk_css_parser_error (parser, "Expected an integer");
-  return FALSE;
-}
-
-gboolean
-gtk_css_parser_consume_number (GtkCssParser *self,
-                               double       *number)
-{
-  gdouble result;
-  char *end;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (self), FALSE);
-  g_return_val_if_fail (number != NULL, FALSE);
-
-  errno = 0;
-  result = g_ascii_strtod (self->data, &end);
-  if (errno ||
-      self->data == end)
-    {
-      _gtk_css_parser_error (self, "Expected a number");
-      return FALSE;
-    }
-
-  self->data = end;
-  *number = result;
-
-  _gtk_css_parser_skip_whitespace (self);
-
-  return TRUE;
-}
-
-char *
-gtk_css_parser_consume_ident (GtkCssParser *self)
-{
-  char *result;
-
-  result = _gtk_css_parser_try_ident (self, TRUE);
-  if (result == NULL)
-    _gtk_css_parser_error (self, "Expected an identifier");
-
-  return result;
-}
-
-gboolean
-_gtk_css_parser_has_number (GtkCssParser *parser)
-{
-  char c;
-
-  if (parser->data[0] == '-' || parser->data[0] == '+')
-    c = parser->data[1];
-  else
-    c = parser->data[0];
-
-  /* ahem */
-  return g_ascii_isdigit (c) || c == '.';
-}
-
-GtkCssValue *
-gtk_css_dimension_value_parse (GtkCssParser           *parser,
-                               GtkCssNumberParseFlags  flags)
-{
-  static const struct {
-    const char *name;
-    GtkCssUnit unit;
-    GtkCssNumberParseFlags required_flags;
-  } units[] = {
-    { "px",   GTK_CSS_PX,      GTK_CSS_PARSE_LENGTH },
-    { "pt",   GTK_CSS_PT,      GTK_CSS_PARSE_LENGTH },
-    { "em",   GTK_CSS_EM,      GTK_CSS_PARSE_LENGTH },
-    { "ex",   GTK_CSS_EX,      GTK_CSS_PARSE_LENGTH },
-    { "rem",  GTK_CSS_REM,     GTK_CSS_PARSE_LENGTH },
-    { "pc",   GTK_CSS_PC,      GTK_CSS_PARSE_LENGTH },
-    { "in",   GTK_CSS_IN,      GTK_CSS_PARSE_LENGTH },
-    { "cm",   GTK_CSS_CM,      GTK_CSS_PARSE_LENGTH },
-    { "mm",   GTK_CSS_MM,      GTK_CSS_PARSE_LENGTH },
-    { "rad",  GTK_CSS_RAD,     GTK_CSS_PARSE_ANGLE  },
-    { "deg",  GTK_CSS_DEG,     GTK_CSS_PARSE_ANGLE  },
-    { "grad", GTK_CSS_GRAD,    GTK_CSS_PARSE_ANGLE  },
-    { "turn", GTK_CSS_TURN,    GTK_CSS_PARSE_ANGLE  },
-    { "s",    GTK_CSS_S,       GTK_CSS_PARSE_TIME   },
-    { "ms",   GTK_CSS_MS,      GTK_CSS_PARSE_TIME   }
-  };
-  char *end, *unit_name;
-  double value;
-  GtkCssUnit unit;
-
-  g_return_val_if_fail (GTK_IS_CSS_PARSER (parser), NULL);
-
-  errno = 0;
-  value = g_ascii_strtod (parser->data, &end);
-  if (errno)
-    {
-      _gtk_css_parser_error (parser, "not a number: %s", g_strerror (errno));
-      return NULL;
-    }
-  if (parser->data == end)
-    {
-      _gtk_css_parser_error (parser, "not a number");
-      return NULL;
-    }
-
-  parser->data = end;
-
-  if (flags & GTK_CSS_POSITIVE_ONLY &&
-      value < 0)
-    {
-      _gtk_css_parser_error (parser, "negative values are not allowed.");
-      return NULL;
-    }
-
-  unit_name = _gtk_css_parser_try_ident (parser, FALSE);
-
-  if (unit_name)
-    {
-      guint i;
-
-      for (i = 0; i < G_N_ELEMENTS (units); i++)
-        {
-          if (flags & units[i].required_flags &&
-              g_ascii_strcasecmp (unit_name, units[i].name) == 0)
-            break;
-        }
-
-      if (i >= G_N_ELEMENTS (units))
-        {
-          _gtk_css_parser_error (parser, "'%s' is not a valid unit.", unit_name);
-          g_free (unit_name);
-          return NULL;
-        }
-
-      unit = units[i].unit;
-
-      g_free (unit_name);
-    }
-  else
-    {
-      if ((flags & GTK_CSS_PARSE_PERCENT) &&
-          _gtk_css_parser_try (parser, "%", FALSE))
-        {
-          unit = GTK_CSS_PERCENT;
-        }
-      else if (value == 0.0)
-        {
-          if (flags & GTK_CSS_PARSE_NUMBER)
-            unit = GTK_CSS_NUMBER;
-          else if (flags & GTK_CSS_PARSE_LENGTH)
-            unit = GTK_CSS_PX;
-          else if (flags & GTK_CSS_PARSE_ANGLE)
-            unit = GTK_CSS_DEG;
-          else if (flags & GTK_CSS_PARSE_TIME)
-            unit = GTK_CSS_S;
-          else
-            unit = GTK_CSS_PERCENT;
-        }
-      else if (flags & GTK_CSS_PARSE_NUMBER)
-        {
-          unit = GTK_CSS_NUMBER;
-        }
-      else
-        {
-          _gtk_css_parser_error (parser, "Unit is missing.");
-          return NULL;
-        }
-    }
-
-  _gtk_css_parser_skip_whitespace (parser);
-
-  return gtk_css_dimension_value_new (value, unit);
-}
-
-gboolean
-_gtk_css_parser_try_hash_color (GtkCssParser *parser,
-                                GdkRGBA      *rgba)
-{
-  if (parser->data[0] == '#' &&
-      g_ascii_isxdigit (parser->data[1]) &&
-      g_ascii_isxdigit (parser->data[2]) &&
-      g_ascii_isxdigit (parser->data[3]))
-    {
-      if (g_ascii_isxdigit (parser->data[4]))
-        { 
-          if (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;
-              if (g_ascii_isxdigit (parser->data[7]) &&
-                  g_ascii_isxdigit (parser->data[8]))
-                {
-                  rgba->alpha = ((get_xdigit (parser->data[7]) << 4) + get_xdigit (parser->data[8])) / 255.0;
-                  parser->data += 9;
-                }
-              else
-                {
-                  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 = get_xdigit (parser->data[4]) / 15.0;
-              parser->data += 5;
-            }
-        }
-      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 TRUE;
-    }
-
-  return FALSE;
-}
-
-GFile *
-gtk_css_parser_consume_url (GtkCssParser *parser)
-{
-  gchar *path;
-  GFile *file;
-
-  if (_gtk_css_parser_try (parser, "url", FALSE))
-    {
-      if (!_gtk_css_parser_try (parser, "(", TRUE))
-        {
-          _gtk_css_parser_error (parser, "Expected '(' after 'url'");
-          return NULL;
-        }
-
-      path = gtk_css_parser_consume_string (parser);
-      if (path == NULL)
-        return NULL;
-
-      if (!_gtk_css_parser_try (parser, ")", TRUE))
-        {
-          _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
-          g_free (path);
-          return NULL;
-        }
-    }
-  else
-    {
-      path = _gtk_css_parser_try_name (parser, TRUE);
-      if (path == NULL)
-        {
-          _gtk_css_parser_error (parser, "Not a valid url");
-          return NULL;
-        }
-    }
-
-  file = gtk_css_parser_resolve_url (parser, path);
-  g_free (path);
-
-  return file;
-}
-
-static 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_has_token (parser, GTK_CSS_TOKEN_STRING))
-      {
-        /* Hrm, this emits errors, and i suspect it shouldn't... */
-        char *free_me = gtk_css_parser_consume_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, ')');
-        if (*parser->data)
-          parser->data++;
-        break;
-      case '[':
-        parser->data++;
-        _gtk_css_parser_resync (parser, FALSE, ']');
-        if (*parser->data)
-          parser->data++;
-        break;
-      case '{':
-        parser->data++;
-        _gtk_css_parser_resync (parser, FALSE, '}');
-        if (*parser->data)
-          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;
-      case '\0':
-        break;
-      case '/':
-      default:
-        parser->data++;
-        break;
-      }
-  } while (*parser->data);
-}
-
-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);
 }
 
 void
@@ -1219,99 +83,3 @@ out:
   g_string_append_c (str, '"');
 }
 
-gboolean
-gtk_css_parser_consume_function (GtkCssParser *self,
-                                 guint         min_args,
-                                 guint         max_args,
-                                 guint (* parse_func) (GtkCssParser *, guint, gpointer),
-                                 gpointer      data)
-{
-  gboolean result = FALSE;
-  char *function_name;
-  guint arg;
-
-  function_name = _gtk_css_parser_try_ident (self, FALSE);
-  g_return_val_if_fail (function_name != NULL, FALSE);
-  g_return_val_if_fail (_gtk_css_parser_try (self, "(", TRUE), FALSE);
-
-  arg = 0;
-  while (TRUE)
-    {
-      guint parse_args = parse_func (self, arg, data);
-      if (parse_args == 0)
-        break;
-      arg += parse_args;
-      if (gtk_css_parser_try_token (self, GTK_CSS_TOKEN_CLOSE_PARENS))
-        {
-          if (arg < min_args)
-            {
-              _gtk_css_parser_error (self, "%s() requires at least %u arguments", function_name, min_args);
-              break;
-            }
-          else
-            {
-              result = TRUE;
-              break;
-            }
-        }
-      else if (gtk_css_parser_try_token (self, GTK_CSS_TOKEN_COMMA))
-        {
-          if (arg >= max_args)
-            {
-              _gtk_css_parser_error (self, "Expected ')' at end of %s()", function_name);
-              break;
-            }
-
-          continue;
-        }
-      else
-        {
-          _gtk_css_parser_error (self, "Unexpected data at end of %s() argument", function_name);
-          break;
-        }
-    }
-
-  g_free (function_name);
-
-  return result;
-}
-
-gsize
-gtk_css_parser_consume_any (GtkCssParser            *parser,
-                            const GtkCssParseOption *options,
-                            gsize                    n_options,
-                            gpointer                 user_data)
-{
-  gsize result;
-  gsize i;
-
-  g_return_val_if_fail (parser != NULL, 0);
-  g_return_val_if_fail (options != NULL, 0);
-  g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
-
-  result = 0;
-  while (result != (1 << n_options) - 1)
-    {
-      for (i = 0; i < n_options; i++)
-        {
-          if (result & (1 << i))
-            continue;
-          if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
-            continue;
-          if (!options[i].parse (parser, options[i].data, user_data))
-            return 0;
-          result |= 1 << i;
-          break;
-        }
-      if (i == n_options)
-        break;
-    }
-  if (result == 0)
-    {
-      _gtk_css_parser_error (parser, "No valid value given");
-      return result;
-    }
-
-  return result;
-}
-
diff --git a/gtk/gtkcssparserprivate.h b/gtk/gtkcssparserprivate.h
index fec25fceff..68de9ea207 100644
--- a/gtk/gtkcssparserprivate.h
+++ b/gtk/gtkcssparserprivate.h
@@ -22,104 +22,14 @@
 
 #include <gtk/css/gtkcss.h>
 #include "gtk/css/gtkcsstokenizerprivate.h"
+#include "gtk/css/gtkcssparserprivate.h"
 
 G_BEGIN_DECLS
 
-typedef struct _GtkCssParser GtkCssParser;
-
-typedef void (* GtkCssParserErrorFunc) (GtkCssParser *parser,
-                                        const GError *error,
-                                        gpointer      user_data);
-
-typedef struct _GtkCssParseOption GtkCssParseOption;
-
-struct _GtkCssParseOption
-{
-  gboolean (* can_parse)  (GtkCssParser *parser,
-                           gpointer      option_data,
-                           gpointer      user_data);
-  gboolean (* parse)      (GtkCssParser *parser,
-                           gpointer      option_data,
-                           gpointer      user_data);
-  gpointer data;
-};
-
-GtkCssParser *  _gtk_css_parser_new               (const char            *data,
-                                                   GFile                 *file,
-                                                   GtkCssParserErrorFunc  error_func,
-                                                   gpointer               user_data);
-void            _gtk_css_parser_free              (GtkCssParser          *parser);
-
-void            _gtk_css_parser_take_error        (GtkCssParser          *parser,
-                                                   GError                *error);
 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);
-GFile *         _gtk_css_parser_get_file          (GtkCssParser          *parser);
-GFile *         gtk_css_parser_resolve_url        (GtkCssParser          *parser,
-                                                   const char            *url);
-
-gboolean        gtk_css_parser_has_token          (GtkCssParser          *parser,
-                                                   GtkCssTokenType        token_type);
-gboolean        gtk_css_parser_has_ident          (GtkCssParser          *parser,
-                                                   const char            *name);
-gboolean        gtk_css_parser_has_integer        (GtkCssParser          *parser);
-gboolean        gtk_css_parser_has_function       (GtkCssParser          *parser,
-                                                   const char            *name);
-
-/* 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_ident          (GtkCssParser          *parser,
-                                                   const char            *ident);
-gboolean        gtk_css_parser_try_delim          (GtkCssParser          *parser,
-                                                   gunichar               delim);
-gboolean        gtk_css_parser_try_at_keyword     (GtkCssParser          *parser,
-                                                   const char            *keyword);
-gboolean        gtk_css_parser_try_token          (GtkCssParser          *parser,
-                                                   GtkCssTokenType        token_type);
-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_hash_color    (GtkCssParser          *parser,
-                                                   GdkRGBA               *rgba);
-
-char *          gtk_css_parser_consume_ident      (GtkCssParser          *self);
-char *          gtk_css_parser_consume_string     (GtkCssParser          *self);
-GFile *         gtk_css_parser_consume_url        (GtkCssParser          *self);
-gboolean        gtk_css_parser_consume_number     (GtkCssParser          *self,
-                                                   double                *number);
-gboolean        gtk_css_parser_consume_integer    (GtkCssParser          *parser,
-                                                   int                   *value);
-gboolean        gtk_css_parser_consume_function   (GtkCssParser          *self,
-                                                   guint                  min_args,
-                                                   guint                  max_args,
-                                                   guint (* parse_func) (GtkCssParser *, guint, gpointer),
-                                                   gpointer               data);
-gsize           gtk_css_parser_consume_any        (GtkCssParser          *parser,
-                                                   const GtkCssParseOption *options,
-                                                   gsize                  n_options,
-                                                   gpointer               user_data);
-
-gboolean        _gtk_css_parser_has_number        (GtkCssParser          *parser);
-
-void            _gtk_css_parser_skip_whitespace   (GtkCssParser          *parser);
-void            _gtk_css_parser_resync            (GtkCssParser          *parser,
-                                                   gboolean               sync_at_semicolon,
-                                                   char                   terminator);
-
 /* XXX: Find better place to put it? */
 void            _gtk_css_print_string             (GString               *str,
                                                    const char            *string);
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index 1abcaa8472..8f99c79579 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -140,7 +140,7 @@ static void
 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
                                 GtkCssScanner  *scanner,
                                 GFile          *file,
-                                const char     *data);
+                                GBytes         *bytes);
 
 G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
                         G_ADD_PRIVATE (GtkCssProvider)
@@ -312,7 +312,7 @@ gtk_css_scanner_destroy (GtkCssScanner *scanner)
   if (scanner->section)
     gtk_css_section_unref (scanner->section);
   g_object_unref (scanner->provider);
-  _gtk_css_parser_free (scanner->parser);
+  gtk_css_parser_unref (scanner->parser);
 
   g_slice_free (GtkCssScanner, scanner);
 }
@@ -336,9 +336,11 @@ gtk_css_provider_emit_error (GtkCssProvider *provider,
 }
 
 static void
-gtk_css_scanner_parser_error (GtkCssParser *parser,
-                              const GError *error,
-                              gpointer      user_data)
+gtk_css_scanner_parser_error (GtkCssParser         *parser,
+                              const GtkCssLocation *start,
+                              const GtkCssLocation *end,
+                              const GError         *error,
+                              gpointer              user_data)
 {
   GtkCssScanner *scanner = user_data;
 
@@ -350,7 +352,7 @@ gtk_css_scanner_new (GtkCssProvider *provider,
                      GtkCssScanner  *parent,
                      GtkCssSection  *section,
                      GFile          *file,
-                     const gchar    *text)
+                     GBytes         *bytes)
 {
   GtkCssScanner *scanner;
 
@@ -362,10 +364,12 @@ gtk_css_scanner_new (GtkCssProvider *provider,
   if (section)
     scanner->section = gtk_css_section_ref (section);
 
-  scanner->parser = _gtk_css_parser_new (text,
-                                         file,
-                                         gtk_css_scanner_parser_error,
-                                         scanner);
+  scanner->parser = gtk_css_parser_new_for_bytes (bytes,
+                                                  file,
+                                                  NULL,
+                                                  gtk_css_scanner_parser_error,
+                                                  scanner,
+                                                  NULL);
 
   return scanner;
 }
@@ -376,7 +380,7 @@ gtk_css_scanner_would_recurse (GtkCssScanner *scanner,
 {
   while (scanner)
     {
-      GFile *parser_file = _gtk_css_parser_get_file (scanner->parser);
+      GFile *parser_file = gtk_css_parser_get_file (scanner->parser);
       if (parser_file && g_file_equal (parser_file, file))
         return TRUE;
 
@@ -766,7 +770,6 @@ gtk_css_provider_reset (GtkCssProvider *css_provider)
   g_array_set_size (priv->rulesets, 0);
   _gtk_css_selector_tree_free (priv->tree);
   priv->tree = NULL;
-
 }
 
 static gboolean
@@ -790,6 +793,15 @@ parse_import (GtkCssScanner *scanner)
       if (url)
         {
           file = gtk_css_parser_resolve_url (scanner->parser, url);
+          if (file == NULL)
+            {
+              gtk_css_provider_error (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PARSER_ERROR,
+                                      GTK_CSS_PARSER_ERROR_IMPORT,
+                                      "Could not resolve \"%s\" to a valid URL",
+                                      url);
+            }
           g_free (url);
         }
       else
@@ -802,15 +814,11 @@ parse_import (GtkCssScanner *scanner)
 
   if (file == NULL)
     {
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
-      gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT);
-      return TRUE;
+      /* nothing to do */
     }
-
-  if (!_gtk_css_parser_try (scanner->parser, ";", FALSE))
+  else if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
       gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
     }
   else if (gtk_css_scanner_would_recurse (scanner, file))
     {
@@ -831,10 +839,9 @@ parse_import (GtkCssScanner *scanner)
                                       NULL);
     }
 
-  g_object_unref (file);
+  g_clear_object (&file);
 
   gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT);
-  _gtk_css_parser_skip_whitespace (scanner->parser);
 
   return TRUE;
 }
@@ -854,15 +861,9 @@ parse_color_definition (GtkCssScanner *scanner)
       return FALSE;
     }
 
-  name = _gtk_css_parser_try_name (scanner->parser, TRUE);
+  name = gtk_css_parser_consume_ident (scanner->parser);
   if (name == NULL)
     {
-      gtk_css_provider_error_literal (scanner->provider,
-                                      scanner,
-                                      GTK_CSS_PARSER_ERROR,
-                                      GTK_CSS_PARSER_ERROR_SYNTAX,
-                                      "Not a valid color name");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
       return TRUE;
     }
@@ -871,12 +872,11 @@ parse_color_definition (GtkCssScanner *scanner)
   if (color == NULL)
     {
       g_free (name);
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
       return TRUE;
     }
 
-  if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
+  if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
       g_free (name);
       _gtk_css_value_unref (color);
@@ -885,7 +885,6 @@ parse_color_definition (GtkCssScanner *scanner)
                                       GTK_CSS_PARSER_ERROR,
                                       GTK_CSS_PARSER_ERROR_SYNTAX,
                                       "Missing semicolon at end of color definition");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
 
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
       return TRUE;
@@ -912,52 +911,39 @@ parse_keyframes (GtkCssScanner *scanner)
       return FALSE;
     }
 
-  name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
+  name = gtk_css_parser_consume_ident (scanner->parser);
   if (name == NULL)
     {
-      gtk_css_provider_error_literal (scanner->provider,
-                                      scanner,
-                                      GTK_CSS_PARSER_ERROR,
-                                      GTK_CSS_PARSER_ERROR_SYNTAX,
-                                      "Expected name for keyframes");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
-      goto exit;
+      gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
+      return FALSE;
     }
 
-  if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
+  if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
       gtk_css_provider_error_literal (scanner->provider,
                                       scanner,
                                       GTK_CSS_PARSER_ERROR,
                                       GTK_CSS_PARSER_ERROR_SYNTAX,
                                       "Expected '{' for keyframes");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
-      g_free (name);
-      goto exit;
+      gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
+      return FALSE;
     }
 
-  keyframes = _gtk_css_keyframes_parse (scanner->parser);
-  if (keyframes == NULL)
-    {
-      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
-      g_free (name);
-      goto exit;
-    }
+  gtk_css_parser_end_block_prelude (scanner->parser);
 
-  g_hash_table_insert (priv->keyframes, name, keyframes);
+  keyframes = _gtk_css_keyframes_parse (scanner->parser);
+  if (keyframes != NULL)
+    g_hash_table_insert (priv->keyframes, name, keyframes);
 
-  if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
+  if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
       gtk_css_provider_error_literal (scanner->provider,
                                       scanner,
                                       GTK_CSS_PARSER_ERROR,
                                       GTK_CSS_PARSER_ERROR_SYNTAX,
                                       "expected '}' after declarations");
-      if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
-        _gtk_css_parser_resync (scanner->parser, FALSE, 0);
     }
 
-exit:
   gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
 
   return TRUE;
@@ -966,22 +952,20 @@ exit:
 static void
 parse_at_keyword (GtkCssScanner *scanner)
 {
-  if (parse_import (scanner))
-    return;
-  if (parse_color_definition (scanner))
-    return;
-  if (parse_keyframes (scanner))
-    return;
+  gtk_css_parser_start_semicolon_block (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
 
-  else
+  if (!parse_import (scanner) &&
+      !parse_color_definition (scanner) &&
+      !parse_keyframes (scanner))
     {
       gtk_css_provider_error_literal (scanner->provider,
                                       scanner,
                                       GTK_CSS_PARSER_ERROR,
                                       GTK_CSS_PARSER_ERROR_SYNTAX,
                                       "unknown @ rule");
-      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
     }
+
+  gtk_css_parser_end_block (scanner->parser);
 }
 
 static GSList *
@@ -996,15 +980,13 @@ parse_selector_list (GtkCssScanner *scanner)
 
       if (select == NULL)
         {
-          g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
-          _gtk_css_parser_resync (scanner->parser, FALSE, 0);
           gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR);
           return NULL;
         }
 
       selectors = g_slist_prepend (selectors, select);
     }
-  while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
+  while (gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COMMA));
 
   gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR);
 
@@ -1019,27 +1001,37 @@ parse_declaration (GtkCssScanner *scanner,
   char *name;
 
   gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DECLARATION);
+  gtk_css_parser_start_semicolon_block (scanner->parser, GTK_CSS_TOKEN_EOF);
 
-  name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
-  if (name == NULL)
-    goto check_for_semicolon;
-
-  property = _gtk_style_property_lookup (name);
+  if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
+    {
+      gtk_css_parser_warn_syntax (scanner->parser, "Empty declaration");
+      gtk_css_parser_end_block (scanner->parser);
+      return;
+    }
 
-  if (!gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COLON))
+  name = gtk_css_parser_consume_ident (scanner->parser);
+  if (name == NULL)
     {
-      gtk_css_provider_invalid_token (scanner->provider, scanner, "':'");
-      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
-      g_free (name);
+      gtk_css_parser_end_block (scanner->parser);
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
       return;
     }
 
+  property = _gtk_style_property_lookup (name);
+
   if (property)
     {
       GtkCssValue *value;
 
-      g_free (name);
+      if (!gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COLON))
+        {
+          gtk_css_parser_error_syntax (scanner->parser, "Expected ':'");
+          g_free (name);
+          gtk_css_parser_end_block (scanner->parser);
+          gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
+          return;
+        }
 
       gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE);
 
@@ -1048,22 +1040,20 @@ parse_declaration (GtkCssScanner *scanner,
 
       if (value == NULL)
         {
-          _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+          gtk_css_parser_end_block (scanner->parser);
           gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
           gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
           return;
         }
 
-      if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_SEMICOLON) &&
-          !gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CLOSE_CURLY) &&
-          !gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
+      if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
         {
           gtk_css_provider_error (scanner->provider,
                                   scanner,
                                   GTK_CSS_PARSER_ERROR,
                                   GTK_CSS_PARSER_ERROR_SYNTAX,
                                   "Junk at end of value for %s", property->name);
-          _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+          gtk_css_parser_end_block (scanner->parser);
           gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
           gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
           return;
@@ -1098,32 +1088,21 @@ parse_declaration (GtkCssScanner *scanner,
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
     }
   else
-    g_free (name);
-
-check_for_semicolon:
-  gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
-
-  if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
     {
-      if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CLOSE_CURLY) &&
-          !gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
-        {
-          gtk_css_provider_error_literal (scanner->provider,
-                                          scanner,
-                                          GTK_CSS_PARSER_ERROR,
-                                          GTK_CSS_PARSER_ERROR_SYNTAX,
-                                          "Expected semicolon");
-          _gtk_css_parser_resync (scanner->parser, TRUE, '}');
-        }
+      gtk_css_parser_error_value (scanner->parser, "No property named \"%s\"", name);
     }
+
+  g_free (name);
+
+  gtk_css_parser_end_block (scanner->parser);
+  gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
 }
 
 static void
 parse_declarations (GtkCssScanner *scanner,
                     GtkCssRuleset *ruleset)
 {
-  while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF) &&
-         !gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CLOSE_CURLY))
+  while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
       parse_declaration (scanner, ruleset);
     }
@@ -1140,40 +1119,31 @@ parse_ruleset (GtkCssScanner *scanner)
   selectors = parse_selector_list (scanner);
   if (selectors == NULL)
     {
+      gtk_css_parser_skip_until (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
+      gtk_css_parser_skip (scanner->parser);
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
       return;
     }
 
-  if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
+  if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY))
     {
       gtk_css_provider_error_literal (scanner->provider,
                                       scanner,
                                       GTK_CSS_PARSER_ERROR,
                                       GTK_CSS_PARSER_ERROR_SYNTAX,
                                       "expected '{' after selectors");
-      _gtk_css_parser_resync (scanner->parser, FALSE, 0);
       g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
+      gtk_css_parser_skip_until (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
+      gtk_css_parser_skip (scanner->parser);
       gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
       return;
     }
 
+  gtk_css_parser_start_block (scanner->parser);
+
   parse_declarations (scanner, &ruleset);
 
-  if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
-    {
-      gtk_css_provider_error_literal (scanner->provider,
-                                      scanner,
-                                      GTK_CSS_PARSER_ERROR,
-                                      GTK_CSS_PARSER_ERROR_SYNTAX,
-                                      "expected '}' after declarations");
-      if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
-        {
-          _gtk_css_parser_resync (scanner->parser, FALSE, 0);
-          g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
-          gtk_css_ruleset_clear (&ruleset);
-          gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
-        }
-    }
+  gtk_css_parser_end_block (scanner->parser);
 
   css_provider_commit (scanner->provider, selectors, &ruleset);
   gtk_css_ruleset_clear (&ruleset);
@@ -1194,13 +1164,14 @@ parse_stylesheet (GtkCssScanner *scanner)
 {
   gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DOCUMENT);
 
-  _gtk_css_parser_skip_whitespace (scanner->parser);
-
   while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
     {
-      if (_gtk_css_parser_try (scanner->parser, "<!--", TRUE) ||
-          _gtk_css_parser_try (scanner->parser, "-->", TRUE))
-        continue;
+      if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CDO) ||
+          gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CDC))
+        {
+          gtk_css_parser_consume_token (scanner->parser);
+          continue;
+        }
 
       parse_statement (scanner);
     }
@@ -1265,30 +1236,27 @@ static void
 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
                                 GtkCssScanner  *parent,
                                 GFile          *file,
-                                const char     *text)
+                                GBytes         *bytes)
 {
   GtkCssScanner *scanner;
-  GBytes *bytes;
 
-  if (text == NULL)
+  if (bytes == NULL)
     {
       GError *load_error = NULL;
 
       bytes = g_file_load_bytes (file, NULL, NULL, &load_error);
 
-      if (bytes)
-        {
-          text = g_bytes_get_data (bytes, NULL);
-        }
-      else
+      if (bytes == NULL)
         {
           if (parent == NULL)
             {
+              GBytes *tmp_bytes = g_bytes_new_static ("", 0);
               scanner = gtk_css_scanner_new (css_provider,
                                              NULL,
                                              NULL,
                                              file,
-                                             "");
+                                             tmp_bytes);
+              g_bytes_unref (tmp_bytes);
 
               gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DOCUMENT);
             }
@@ -1310,18 +1278,14 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
             }
         }
     }
-  else
-    {
-      bytes = NULL;
-    }
 
-  if (text)
+  if (bytes)
     {
       scanner = gtk_css_scanner_new (css_provider,
                                      parent,
                                      parent ? parent->section : NULL,
                                      file,
-                                     text);
+                                     bytes);
 
       parse_stylesheet (scanner);
 
@@ -1330,9 +1294,6 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
       if (parent == NULL)
         gtk_css_provider_postprocess (css_provider);
     }
-
-  if (bytes)
-    g_bytes_unref (bytes);
 }
 
 /**
@@ -1351,27 +1312,21 @@ gtk_css_provider_load_from_data (GtkCssProvider  *css_provider,
                                  const gchar     *data,
                                  gssize           length)
 {
-  char *free_data;
+  GBytes *bytes;
 
   g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
   g_return_if_fail (data != NULL);
 
   if (length < 0)
-    {
-      length = strlen (data);
-      free_data = NULL;
-    }
-  else
-    {
-      free_data = g_strndup (data, length);
-      data = free_data;
-    }
+    length = strlen (data);
+
+  bytes = g_bytes_new_static (data, length);
 
   gtk_css_provider_reset (css_provider);
 
-  gtk_css_provider_load_internal (css_provider, NULL, NULL, data);
+  gtk_css_provider_load_internal (css_provider, NULL, NULL, bytes);
 
-  g_free (free_data);
+  g_bytes_unref (bytes);
 
   gtk_style_provider_changed (GTK_STYLE_PROVIDER (css_provider));
 }
diff --git a/gtk/gtkcsssection.c b/gtk/gtkcsssection.c
index b8e15f2c98..747069212a 100644
--- a/gtk/gtkcsssection.c
+++ b/gtk/gtkcsssection.c
@@ -43,6 +43,7 @@ _gtk_css_section_new (GtkCssSection     *parent,
                       GtkCssParser      *parser)
 {
   GtkCssSection *section;
+  GtkCssLocation location;
 
   gtk_internal_return_val_if_fail (parser != NULL, NULL);
 
@@ -52,12 +53,13 @@ _gtk_css_section_new (GtkCssSection     *parent,
   section->section_type = type;
   if (parent)
     section->parent = gtk_css_section_ref (parent);
-  section->file = _gtk_css_parser_get_file (parser);
+  section->file = gtk_css_parser_get_file (parser);
   if (section->file)
     g_object_ref (section->file);
-  section->start_line = _gtk_css_parser_get_line (parser);
-  section->start_position = _gtk_css_parser_get_position (parser);
   section->parser = parser;
+  gtk_css_parser_get_location (section->parser, &location);
+  section->start_line = location.lines;
+  section->start_position = location.line_chars;
 
   return section;
 }
@@ -82,11 +84,14 @@ _gtk_css_section_new_for_file (GtkCssSectionType  type,
 void
 _gtk_css_section_end (GtkCssSection *section)
 {
+  GtkCssLocation location;
+
   gtk_internal_return_if_fail (section != NULL);
   gtk_internal_return_if_fail (section->parser != NULL);
 
-  section->end_line = _gtk_css_parser_get_line (section->parser);
-  section->end_position = _gtk_css_parser_get_position (section->parser);
+  gtk_css_parser_get_location (section->parser, &location);
+  section->end_line = location.lines;
+  section->end_position = location.line_chars;
   section->parser = NULL;
 }
 
@@ -243,12 +248,15 @@ gtk_css_section_get_start_position (const GtkCssSection *section)
 guint
 gtk_css_section_get_end_line (const GtkCssSection *section)
 {
+  GtkCssLocation location;
+
   gtk_internal_return_val_if_fail (section != NULL, 0);
 
-  if (section->parser)
-    return _gtk_css_parser_get_line (section->parser);
-  else
+  if (!section->parser)
     return section->end_line;
+
+  gtk_css_parser_get_location (section->parser, &location);
+  return location.lines;
 }
 
 /**
@@ -269,12 +277,15 @@ gtk_css_section_get_end_line (const GtkCssSection *section)
 guint
 gtk_css_section_get_end_position (const GtkCssSection *section)
 {
+  GtkCssLocation location;
+
   gtk_internal_return_val_if_fail (section != NULL, 0);
 
-  if (section->parser)
-    return _gtk_css_parser_get_position (section->parser);
-  else
+  if (!section->parser)
     return section->end_position;
+
+  gtk_css_parser_get_location (section->parser, &location);
+  return location.line_chars;
 }
 
 void
diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c
index 30de213cc7..bd1b1523f2 100644
--- a/gtk/gtkcssselector.c
+++ b/gtk/gtkcssselector.c
@@ -25,6 +25,7 @@
 #include "gtkcssprovider.h"
 #include "gtkstylecontextprivate.h"
 
+#include <errno.h>
 #if defined(_MSC_VER) && _MSC_VER >= 1500
 # include <intrin.h>
 #endif
@@ -922,296 +923,544 @@ gtk_css_selector_new (const GtkCssSelectorClass *class,
 }
 
 static GtkCssSelector *
-parse_selector_class (GtkCssParser   *parser,
-                      GtkCssSelector *selector,
-                      gboolean        negate)
+gtk_css_selector_parse_selector_class (GtkCssParser   *parser,
+                                       GtkCssSelector *selector,
+                                       gboolean        negate)
 {
-  char *name;
-    
-  name = _gtk_css_parser_try_name (parser, FALSE);
+  const GtkCssToken *token;
+
+  gtk_css_parser_consume_token (parser);
+  for (token = gtk_css_parser_peek_token (parser);
+       gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT);
+       token = gtk_css_parser_peek_token (parser))
+    {
+      gtk_css_parser_consume_token (parser);
+    }
 
-  if (name == NULL)
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_CLASS
+                                              : &GTK_CSS_SELECTOR_CLASS,
+                                       selector);
+      selector->style_class.style_class = g_quark_from_string (token->string.string);
+      gtk_css_parser_consume_token (parser);
+      return selector;
+    }
+  else
     {
-      _gtk_css_parser_error (parser, "Expected a valid name for class");
+      gtk_css_parser_error_syntax (parser, "No class name after '.' in selector");
       if (selector)
         _gtk_css_selector_free (selector);
       return NULL;
     }
+}
 
-  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_CLASS
-                                          : &GTK_CSS_SELECTOR_CLASS,
-                                   selector);
-  selector->style_class.style_class = g_quark_from_string (name);
+static gboolean
+string_has_number (const char *string,
+                   const char *prefix,
+                   int        *number)
+{
+  gsize len = strlen (prefix);
+  char *end;
 
-  g_free (name);
+  if (g_ascii_strncasecmp (string, prefix, len) != 0)
+    return FALSE;
 
-  return selector;
+  errno = 0;
+  *number = strtoul (string + len, &end, 10);
+  if (*end != '\0' || errno != 0)
+    return FALSE;
+
+  return TRUE;
 }
 
-static GtkCssSelector *
-parse_selector_id (GtkCssParser   *parser,
-                   GtkCssSelector *selector,
-                   gboolean        negate)
+static gboolean
+parse_plus_b (GtkCssParser *parser,
+              gboolean      negate,
+              gint         *b)
 {
-  char *name;
-    
-  name = _gtk_css_parser_try_name (parser, FALSE);
+  const GtkCssToken *token;
+  gboolean has_seen_sign;
+
+  token = gtk_css_parser_get_token (parser);
 
-  if (name == NULL)
+  if (negate)
     {
-      _gtk_css_parser_error (parser, "Expected a valid name for id");
-      if (selector)
-        _gtk_css_selector_free (selector);
-      return NULL;
+      has_seen_sign = TRUE;
+    }
+  else
+    {
+      if (gtk_css_token_is_delim (token, '+'))
+        {
+          gtk_css_parser_consume_token (parser);
+          has_seen_sign = TRUE;
+        }
+      else if (gtk_css_token_is_delim (token, '-'))
+        {
+          gtk_css_parser_consume_token (parser);
+          negate = TRUE;
+          has_seen_sign = TRUE;
+        }
+      else
+        {
+          has_seen_sign = FALSE;
+        }
     }
 
-  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_ID
-                                          : &GTK_CSS_SELECTOR_ID,
-                                   selector);
-  selector->id.name = g_intern_string (name);
+  token = gtk_css_parser_get_token (parser);
+  if (!has_seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER))
+    {
+      *b = token->number.number;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else if (has_seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *b = token->number.number;
+      if (negate)
+        *b = - *b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else if (!has_seen_sign)
+    {
+      *b = 0;
+      return TRUE;
+    }
+  
+  gtk_css_parser_error_syntax (parser, "Not a valid an+b type");
+  return FALSE;
+}
 
-  g_free (name);
+static gboolean
+parse_n_plus_b (GtkCssParser *parser,
+                gint          before,
+                gint         *a,
+                gint         *b)
+{
+  const GtkCssToken *token;
 
-  return selector;
-}
+  token = gtk_css_parser_get_token (parser);
 
-static GtkCssSelector *
-parse_selector_pseudo_class_nth_child (GtkCssParser   *parser,
-                                       GtkCssSelector *selector,
-                                       PositionType    type,
-                                       gboolean        negate)
+  if (gtk_css_token_is_ident (token, "n"))
+    {
+      *a = before;
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, FALSE, b);
+    }
+  else if (gtk_css_token_is_ident (token, "n-"))
+    {
+      *a = before;
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, TRUE, b);
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+           string_has_number (token->string.string, "n-", b))
+    {
+      *a = before;
+      *b = -*b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else
+    {
+      *b = before;
+      *a = 0;
+      return TRUE;
+    }
+  
+  gtk_css_parser_error_syntax (parser, "Not a valid an+b type");
+  return FALSE;
+}
+                
+static gboolean
+parse_a_n_plus_b (GtkCssParser *parser,
+                  gint          seen_sign,
+                  gint         *a,
+                  gint         *b)
 {
-  int a, b;
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (parser);
 
-  if (!_gtk_css_parser_try (parser, "(", TRUE))
+  if (!seen_sign && gtk_css_token_is_ident (token, "even"))
     {
-      _gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
-      if (selector)
-        _gtk_css_selector_free (selector);
-      return NULL;
+      *a = 2;
+      *b = 0;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else if (!seen_sign && gtk_css_token_is_ident (token, "odd"))
+    {
+      *a = 2;
+      *b = 1;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
     }
+  else if (!seen_sign && gtk_css_token_is_delim (token, '+'))
+    {
+      gtk_css_parser_consume_token (parser);
+      return parse_a_n_plus_b (parser, 1, a, b);
+    }
+  else if (!seen_sign && gtk_css_token_is_delim (token, '-'))
+    {
+      gtk_css_parser_consume_token (parser);
+      return parse_a_n_plus_b (parser, -1, a, b);
+    }
+  else if ((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER)) ||
+           gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      int x = token->number.number * (seen_sign ? seen_sign : 1);
+      gtk_css_parser_consume_token (parser);
 
-  if (_gtk_css_parser_try (parser, "even", TRUE))
+      return parse_n_plus_b (parser, x , a, b);
+    }
+  else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) ||
+            gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) &&
+           g_ascii_strcasecmp (token->dimension.dimension, "n") == 0)
+    {
+      *a = token->dimension.value * (seen_sign ? seen_sign : 1);
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, FALSE, b);
+    }
+  else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) ||
+            gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) &&
+           g_ascii_strcasecmp (token->dimension.dimension, "n-") == 0)
+    {
+      *a = token->dimension.value * (seen_sign ? seen_sign : 1);
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, TRUE, b);
+    }
+  else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) ||
+            gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) &&
+           string_has_number (token->dimension.dimension, "n-", b))
+    {
+      *a = token->dimension.value * (seen_sign ? seen_sign : 1);
+      *b = -*b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else if (!seen_sign && gtk_css_token_is_ident (token, "-n"))
+    {
+      *a = -1;
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, FALSE, b);
+    }
+  else if (!seen_sign && gtk_css_token_is_ident (token, "-n-"))
+    {
+      *a = -1;
+      gtk_css_parser_consume_token (parser);
+      return parse_plus_b (parser, TRUE, b);
+    }
+  else if (!seen_sign &&
+           gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+           string_has_number (token->string.string, "-n-", b))
+    {
+      *a = -1;
+      *b = -*b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
+    }
+  else if (gtk_css_token_is_ident (token, "n") ||
+           gtk_css_token_is_ident (token, "n-"))
     {
-      a = 2;
-      b = 0;
+      return parse_n_plus_b (parser, seen_sign ? seen_sign : 1, a, b);
     }
-  else if (_gtk_css_parser_try (parser, "odd", TRUE))
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+           string_has_number (token->string.string, "n-", b))
     {
-      a = 2;
-      b = 1;
+      *a = seen_sign ? seen_sign : 1;
+      *b = -*b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
     }
-  else if (type == POSITION_FORWARD &&
-           _gtk_css_parser_try (parser, "first", TRUE))
+  else if (!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+           string_has_number (token->string.string, "-n-", b))
     {
-      a = 0;
-      b = 1;
+      *a = -1;
+      *b = -*b;
+      gtk_css_parser_consume_token (parser);
+      return TRUE;
     }
-  else if (type == POSITION_FORWARD &&
-           _gtk_css_parser_try (parser, "last", TRUE))
+  
+  gtk_css_parser_error_syntax (parser, "Not a valid an+b type");
+  return FALSE;
+}
+
+static guint
+parse_a_n_plus_b_arg (GtkCssParser *parser,
+                      guint         arg,
+                      gpointer      data)
+{
+  gint *ab = data;
+
+  if (!parse_a_n_plus_b (parser, FALSE, &ab[0], &ab[1]))
+    return 0;
+
+  return 1;
+}
+
+static guint
+parse_dir_arg (GtkCssParser *parser,
+               guint         arg,
+               gpointer      data)
+{
+  GtkStateFlags *flag = data;
+
+  if (gtk_css_parser_try_ident (parser, "ltr"))
+    {
+      *flag = GTK_STATE_FLAG_DIR_LTR;
+      return 1;
+    }
+  else if (gtk_css_parser_try_ident (parser, "rtl"))
     {
-      a = 0;
-      b = 1;
-      type = POSITION_BACKWARD;
+      *flag = GTK_STATE_FLAG_DIR_RTL;
+      return 1;
     }
   else
     {
-      int multiplier;
+      gtk_css_parser_error_value (parser, "Expected \"ltr\" or \"rtl\"");
+      return 0;
+    }
+}
+
+static guint
+parse_identifier_arg (GtkCssParser *parser,
+                      guint         arg,
+                      gpointer      data)
+{
+  const char *ident = data;
+  
+  if (!gtk_css_parser_try_ident (parser, ident))
+    {
+      gtk_css_parser_error_value (parser, "Expected \"%s\"", ident);
+      return 0;
+    }
 
-      if (_gtk_css_parser_try (parser, "+", TRUE))
-        multiplier = 1;
-      else if (_gtk_css_parser_try (parser, "-", TRUE))
-        multiplier = -1;
-      else
-        multiplier = 1;
+  return 1;
+}
+
+static GtkCssSelector *
+gtk_css_selector_parse_selector_pseudo_class (GtkCssParser   *parser,
+                                              GtkCssSelector *selector,
+                                              gboolean        negate)
+{
+  const GtkCssToken *token;
+
+  gtk_css_parser_consume_token (parser);
+  for (token = gtk_css_parser_peek_token (parser);
+       gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT);
+       token = gtk_css_parser_peek_token (parser))
+    {
+      gtk_css_parser_consume_token (parser);
+    }
 
-      if (gtk_css_parser_has_integer (parser))
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      static const struct {
+        const char    *name;
+        GtkStateFlags  state_flag;
+        PositionType   position_type;
+        int            position_a;
+        int            position_b;
+      } pseudo_classes[] = {
+        { "first-child",    0,                           POSITION_FORWARD,  0, 1 },
+        { "last-child",     0,                           POSITION_BACKWARD, 0, 1 },
+        { "only-child",     0,                           POSITION_ONLY,     0, 0 },
+        { "active",         GTK_STATE_FLAG_ACTIVE, },
+        { "hover",          GTK_STATE_FLAG_PRELIGHT, },
+        { "selected",       GTK_STATE_FLAG_SELECTED, },
+        { "disabled",       GTK_STATE_FLAG_INSENSITIVE, },
+        { "indeterminate",  GTK_STATE_FLAG_INCONSISTENT, },
+        { "focus",          GTK_STATE_FLAG_FOCUSED, },
+        { "backdrop",       GTK_STATE_FLAG_BACKDROP, },
+        { "link",           GTK_STATE_FLAG_LINK, },
+        { "visited",        GTK_STATE_FLAG_VISITED, },
+        { "checked",        GTK_STATE_FLAG_CHECKED, },
+      };
+      guint i;
+
+      for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
         {
-          if (!gtk_css_parser_consume_integer (parser, &a))
+          if (g_ascii_strcasecmp (pseudo_classes[i].name, token->string.string) == 0)
+            {
+              if (pseudo_classes[i].state_flag)
+                {
+                  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
+                                                          : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                                   selector);
+                  selector->state.state = pseudo_classes[i].state_flag;
+                }
+              else
+                {
+                  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
+                                                          : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
+                                                   selector);
+                  selector->position.type = pseudo_classes[i].position_type;
+                  selector->position.a = pseudo_classes[i].position_a;
+                  selector->position.b = pseudo_classes[i].position_b;
+                }
+              gtk_css_parser_consume_token (parser);
+              return selector;
+            }
+        }
+          
+      gtk_css_parser_error_value (parser, "Unknown name of pseudo-class");
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
+    }
+  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION))
+    {
+      if (gtk_css_token_is_function (token, "nth-child"))
+        {
+          gint ab[2];
+
+          if (!gtk_css_parser_consume_function (parser, 1, 1, parse_a_n_plus_b_arg, ab))
             {
               if (selector)
                 _gtk_css_selector_free (selector);
               return NULL;
             }
-          if (a < 0)
+
+          selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
+                                                  : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
+                                           selector);
+          selector->position.type = POSITION_FORWARD;
+          selector->position.a = ab[0];
+          selector->position.b = ab[1];
+        }
+      else if (gtk_css_token_is_function (token, "nth-last-child"))
+        {
+          gint ab[2];
+
+          if (!gtk_css_parser_consume_function (parser, 1, 1, parse_a_n_plus_b_arg, ab))
             {
-              _gtk_css_parser_error (parser, "Expected an integer");
               if (selector)
                 _gtk_css_selector_free (selector);
               return NULL;
             }
-          a *= multiplier;
-        }
-      else if (gtk_css_parser_has_ident (parser, "n"))
-        {
-          a = multiplier;
-        }
-      else
-        {
-          _gtk_css_parser_error (parser, "Expected an integer");
-          if (selector)
-            _gtk_css_selector_free (selector);
-          return NULL;
-        }
 
-      if (_gtk_css_parser_try (parser, "n", TRUE))
+          selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
+                                                  : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
+                                           selector);
+          selector->position.type = POSITION_BACKWARD;
+          selector->position.a = ab[0];
+          selector->position.b = ab[1];
+        }
+      else if (gtk_css_token_is_function (token, "not"))
         {
-          if (_gtk_css_parser_try (parser, "+", TRUE))
-            multiplier = 1;
-          else if (_gtk_css_parser_try (parser, "-", TRUE))
-            multiplier = -1;
+          if (negate)
+            {
+              gtk_css_parser_error_syntax (parser, "Nesting of :not() not allowed");
+              if (selector)
+                _gtk_css_selector_free (selector);
+              return NULL;
+            }
           else
-            multiplier = 1;
-
-          if (gtk_css_parser_has_integer (parser))
             {
-              if (!gtk_css_parser_consume_integer (parser, &b))
+              gtk_css_parser_start_block (parser);
+              token = gtk_css_parser_get_token (parser);
+
+              if (gtk_css_token_is_delim (token, '*'))
+                {
+                  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_ANY, selector);
+                  gtk_css_parser_consume_token (parser);
+                }
+              else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+                {
+                  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_NAME, selector);
+                  selector->name.name = g_intern_string (token->string.string);
+                  gtk_css_parser_consume_token (parser);
+                }
+              else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID))
+                {
+                  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_ID, selector);
+                  selector->id.name = g_intern_string (token->string.string);
+                  gtk_css_parser_consume_token (parser);
+                }
+              else if (gtk_css_token_is_delim (token, '.'))
+                {
+                  selector = gtk_css_selector_parse_selector_class (parser, selector, TRUE);
+                }
+              else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
                 {
+                  selector = gtk_css_selector_parse_selector_pseudo_class (parser, selector, TRUE);
+                }
+              else
+                {
+                  gtk_css_parser_error_syntax (parser, "Invalid contents of :not() selector");
                   if (selector)
                     _gtk_css_selector_free (selector);
+                  selector = NULL;
                   return NULL;
                 }
-              if (b < 0)
+
+              token = gtk_css_parser_get_token (parser);
+              if (!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
                 {
-                  _gtk_css_parser_error (parser, "Expected an integer");
+                  gtk_css_parser_error_syntax (parser, "Invalid contents of :not() selector");
                   if (selector)
                     _gtk_css_selector_free (selector);
+                  selector = NULL;
                   return NULL;
                 }
+              gtk_css_parser_end_block (parser);
             }
-          else
-            b = 0;
-
-          b *= multiplier;
         }
-      else
+      else if (gtk_css_token_is_function (token, "dir"))
         {
-          b = a;
-          a = 0;
-        }
-    }
+          GtkStateFlags flag;
 
-  if (!_gtk_css_parser_try (parser, ")", FALSE))
-    {
-      _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
-      if (selector)
-        _gtk_css_selector_free (selector);
-      return NULL;
-    }
-
-  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
-                                          : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
-                                   selector);
-  selector->position.type = type;
-  selector->position.a = a;
-  selector->position.b = b;
-
-  return selector;
-}
-
-static GtkCssSelector *
-parse_selector_pseudo_class (GtkCssParser   *parser,
-                             GtkCssSelector *selector,
-                             gboolean        negate)
-{
-  static const struct {
-    const char    *name;
-    GtkStateFlags  state_flag;
-    PositionType   position_type;
-    int            position_a;
-    int            position_b;
-  } pseudo_classes[] = {
-    { "first-child",    0,                           POSITION_FORWARD,  0, 1 },
-    { "last-child",     0,                           POSITION_BACKWARD, 0, 1 },
-    { "only-child",     0,                           POSITION_ONLY,     0, 0 },
-    { "active",         GTK_STATE_FLAG_ACTIVE, },
-    { "hover",          GTK_STATE_FLAG_PRELIGHT, },
-    { "selected",       GTK_STATE_FLAG_SELECTED, },
-    { "disabled",       GTK_STATE_FLAG_INSENSITIVE, },
-    { "indeterminate",  GTK_STATE_FLAG_INCONSISTENT, },
-    { "focus(visible)", GTK_STATE_FLAG_FOCUS_VISIBLE, },
-    { "focus",          GTK_STATE_FLAG_FOCUSED, },
-    { "backdrop",       GTK_STATE_FLAG_BACKDROP, },
-    { "dir(ltr)",       GTK_STATE_FLAG_DIR_LTR, },
-    { "dir(rtl)",       GTK_STATE_FLAG_DIR_RTL, },
-    { "link",           GTK_STATE_FLAG_LINK, },
-    { "visited",        GTK_STATE_FLAG_VISITED, },
-    { "checked",        GTK_STATE_FLAG_CHECKED, },
-    { "drop(active)",   GTK_STATE_FLAG_DROP_ACTIVE, }
-  };
-
-  guint i;
-
-  if (_gtk_css_parser_try (parser, "nth-child", FALSE))
-    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD, negate);
-  else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
-    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD, negate);
+          if (!gtk_css_parser_consume_function (parser, 1, 1, parse_dir_arg, &flag))
+            {
+              if (selector)
+                _gtk_css_selector_free (selector);
+              return NULL;
+            }
 
-  for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
-    {
-      if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
+          selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
+                                                  : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                           selector);
+          selector->state.state = flag;
+        }
+      else if (gtk_css_token_is_function (token, "drop"))
         {
-          if (pseudo_classes[i].state_flag)
+          if (!gtk_css_parser_consume_function (parser, 1, 1, parse_identifier_arg, (gpointer) "active"))
             {
-              selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
-                                                      : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
-                                               selector);
-              selector->state.state = pseudo_classes[i].state_flag;
+              if (selector)
+                _gtk_css_selector_free (selector);
+              return NULL;
             }
-          else
+          selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
+                                                  : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                           selector);
+          selector->state.state = GTK_STATE_FLAG_DROP_ACTIVE;
+        }
+      else if (gtk_css_token_is_function (token, "focus"))
+        {
+          if (!gtk_css_parser_consume_function (parser, 1, 1, parse_identifier_arg, (gpointer) "visible"))
             {
-              selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
-                                                      : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
-                                               selector);
-              selector->position.type = pseudo_classes[i].position_type;
-              selector->position.a = pseudo_classes[i].position_a;
-              selector->position.b = pseudo_classes[i].position_b;
+              if (selector)
+                _gtk_css_selector_free (selector);
+              return NULL;
             }
-          return selector;
+          selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
+                                                  : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                           selector);
+          selector->state.state = GTK_STATE_FLAG_FOCUS_VISIBLE;
+        }
+      else
+        {
+          gtk_css_parser_error_value (parser, "Unknown pseudoclass");
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
         }
     }
-
-  _gtk_css_parser_error (parser, "Invalid name of pseudo-class");
-  if (selector)
-    _gtk_css_selector_free (selector);
-  return NULL;
-}
-
-static GtkCssSelector *
-parse_selector_negation (GtkCssParser   *parser,
-                         GtkCssSelector *selector)
-{
-  char *name;
-
-  name = _gtk_css_parser_try_ident (parser, FALSE);
-  if (name)
-    {
-      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_NAME,
-                                       selector);
-      selector->name.name = g_intern_string (name);
-      g_free (name);
-    }
-  else if (_gtk_css_parser_try (parser, "*", FALSE))
-    selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_ANY, selector);
-  else if (_gtk_css_parser_try (parser, "#", FALSE))
-    selector = parse_selector_id (parser, selector, TRUE);
-  else if (_gtk_css_parser_try (parser, ".", FALSE))
-    selector = parse_selector_class (parser, selector, TRUE);
-  else if (_gtk_css_parser_try (parser, ":", FALSE))
-    selector = parse_selector_pseudo_class (parser, selector, TRUE);
   else
     {
-      _gtk_css_parser_error (parser, "Not a valid selector for :not()");
-      if (selector)
-        _gtk_css_selector_free (selector);
-      return NULL;
-    }
-  
-  _gtk_css_parser_skip_whitespace (parser);
-
-  if (!_gtk_css_parser_try (parser, ")", FALSE))
-    {
-      _gtk_css_parser_error (parser, "Missing closing bracket for :not()");
+      gtk_css_parser_error_value (parser, "Unknown pseudoclass");
       if (selector)
         _gtk_css_selector_free (selector);
       return NULL;
@@ -1221,50 +1470,60 @@ parse_selector_negation (GtkCssParser   *parser,
 }
 
 static GtkCssSelector *
-parse_simple_selector (GtkCssParser   *parser,
-                       GtkCssSelector *selector)
+gtk_css_selector_parse_simple_selector (GtkCssParser   *parser,
+                                        GtkCssSelector *selector)
 {
   gboolean parsed_something = FALSE;
-  char *name;
-
-  name = _gtk_css_parser_try_ident (parser, FALSE);
-  if (name)
-    {
-      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NAME, selector);
-      selector->name.name = g_intern_string (name);
-      g_free (name);
-      parsed_something = TRUE;
-    }
-  else if (_gtk_css_parser_try (parser, "*", FALSE))
-    {
-      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector);
-      parsed_something = TRUE;
-    }
+  const GtkCssToken *token;
 
   do {
-      if (_gtk_css_parser_try (parser, "#", FALSE))
-        selector = parse_selector_id (parser, selector, FALSE);
-      else if (_gtk_css_parser_try (parser, ".", FALSE))
-        selector = parse_selector_class (parser, selector, FALSE);
-      else if (_gtk_css_parser_try (parser, ":not(", TRUE))
-        selector = parse_selector_negation (parser, selector);
-      else if (_gtk_css_parser_try (parser, ":", FALSE))
-        selector = parse_selector_pseudo_class (parser, selector, FALSE);
-      else if (!parsed_something)
+      for (token = gtk_css_parser_peek_token (parser);
+           gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT);
+           token = gtk_css_parser_peek_token (parser))
         {
-          _gtk_css_parser_error (parser, "Expected a valid selector");
-          if (selector)
-            _gtk_css_selector_free (selector);
-          return NULL;
+          gtk_css_parser_consume_token (parser);
+        }
+
+      if (!parsed_something && gtk_css_token_is_delim (token, '*'))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (!parsed_something && gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NAME, selector);
+          selector->name.name = g_intern_string (token->string.string);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID, selector);
+          selector->id.name = g_intern_string (token->string.string);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is_delim (token, '.'))
+        {
+          selector = gtk_css_selector_parse_selector_class (parser, selector, FALSE);
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
+        {
+          selector = gtk_css_selector_parse_selector_pseudo_class (parser, selector, FALSE);
         }
       else
-        break;
+        {
+          if (!parsed_something)
+            {
+              gtk_css_parser_error_syntax (parser, "Expected a valid selector");
+              if (selector)
+                _gtk_css_selector_free (selector);
+              selector = NULL;
+            }
+          break;
+        }
 
       parsed_something = TRUE;
     }
-  while (selector && !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF));
-
-  _gtk_css_parser_skip_whitespace (parser);
+  while (TRUE);
 
   return selector;
 }
@@ -1273,20 +1532,59 @@ GtkCssSelector *
 _gtk_css_selector_parse (GtkCssParser *parser)
 {
   GtkCssSelector *selector = NULL;
+  const GtkCssToken *token;
 
-  while ((selector = parse_simple_selector (parser, selector)) &&
-         !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF) &&
-         !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA) &&
-         !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_OPEN_CURLY))
+  while (TRUE)
     {
-      if (_gtk_css_parser_try (parser, "+", TRUE))
-        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector);
-      else if (_gtk_css_parser_try (parser, "~", TRUE))
-        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector);
-      else if (_gtk_css_parser_try (parser, ">", TRUE))
-        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector);
+      gboolean seen_whitespace = FALSE;
+
+      /* skip all whitespace and comments */
+      gtk_css_parser_get_token (parser);
+
+      selector = gtk_css_selector_parse_simple_selector (parser, selector);
+      if (selector == NULL)
+        return NULL;
+
+      for (token = gtk_css_parser_peek_token (parser);
+           gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) || 
+           gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
+           token = gtk_css_parser_peek_token (parser))
+        {
+          seen_whitespace |= gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
+          gtk_css_parser_consume_token (parser);
+        }
+
+      if (gtk_css_token_is_delim (token, '+'))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is_delim (token, '~'))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is_delim (token, '>'))
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector);
+          gtk_css_parser_consume_token (parser);
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF) ||
+               gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA) ||
+               gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY))
+        {
+          break;
+        }
+      else if (seen_whitespace)
+        {
+          selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector);
+        }
       else
-        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector);
+        {
+           gtk_css_parser_error_syntax (parser, "Expected a valid selector");
+           _gtk_css_selector_free (selector);
+          return NULL;
+        }
     }
 
   return selector;
diff --git a/gtk/gtkcssshadowvalue.c b/gtk/gtkcssshadowvalue.c
index bd8746ab13..072396f6c9 100644
--- a/gtk/gtkcssshadowvalue.c
+++ b/gtk/gtkcssshadowvalue.c
@@ -266,10 +266,7 @@ _gtk_css_shadow_value_parse (GtkCssParser *parser,
       }
     else if (!inset && box_shadow_mode && gtk_css_parser_try_ident (parser, "inset"))
       {
-        if (values[HOFFSET] == NULL)
-          goto fail;
         inset = TRUE;
-        break;
       }
     else if (values[COLOR] == NULL)
       {


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