[gtk/wip/otte/css: 198/225] Resurrect the CSS parser from the tokenizer branch



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

    Resurrect the CSS parser from the tokenizer branch
    
    So far that parser is unused.

 gtk/css/gtkcssenums.h         |   5 +-
 gtk/css/gtkcssparser.c        | 975 ++++++++++++++++++++++++++++++++++++++++++
 gtk/css/gtkcssparserprivate.h | 135 ++++++
 gtk/css/meson.build           |   1 +
 4 files changed, 1115 insertions(+), 1 deletion(-)
---
diff --git a/gtk/css/gtkcssenums.h b/gtk/css/gtkcssenums.h
index e3c27eca52..1894b55b74 100644
--- a/gtk/css/gtkcssenums.h
+++ b/gtk/css/gtkcssenums.h
@@ -60,6 +60,8 @@ typedef enum
  * GtkCssParserWarning:
  * @GTK_CSS_PARSER_WARNING_DEPRECATED: The given construct is
  *     deprecated and will be removed in a future version
+ * @GTK_CSS_PARSER_WARNING_SYNTAX: A syntax construct was used
+ *     that should be avoided
  *
  * Warnings that can occur while parsing CSS.
  *
@@ -68,7 +70,8 @@ typedef enum
  */
 typedef enum
 {
-  GTK_CSS_PARSER_WARNING_DEPRECATED
+  GTK_CSS_PARSER_WARNING_DEPRECATED,
+  GTK_CSS_PARSER_WARNING_SYNTAX
 } GtkCssParserWarning;
 
 #endif /* __GTK_CSS_ENUMS_H__ */
diff --git a/gtk/css/gtkcssparser.c b/gtk/css/gtkcssparser.c
new file mode 100644
index 0000000000..dc7287582e
--- /dev/null
+++ b/gtk/css/gtkcssparser.c
@@ -0,0 +1,975 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#include "config.h"
+
+#include "gtkcssparserprivate.h"
+
+#include "gtkcssenums.h"
+#include "gtkcsserror.h"
+
+typedef struct _GtkCssParserBlock GtkCssParserBlock;
+
+struct _GtkCssParser
+{
+  volatile int ref_count;
+
+  GtkCssTokenizer *tokenizer;
+  GFile *file;
+  GFile *directory;
+  GtkCssParserErrorFunc error_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GArray *blocks;
+  GtkCssLocation location;
+  GtkCssToken token;
+};
+
+struct _GtkCssParserBlock
+{
+  GtkCssLocation start_location;
+  GtkCssTokenType end_token;
+  GtkCssTokenType inherited_end_token;
+  GtkCssTokenType alternative_token;
+};
+
+static GtkCssParser *
+gtk_css_parser_new (GtkCssTokenizer       *tokenizer,
+                    GFile                 *file,
+                    GFile                 *base_directory,
+                    GtkCssParserErrorFunc  error_func,
+                    gpointer               user_data,
+                    GDestroyNotify         user_destroy)
+{
+  GtkCssParser *self;
+
+  self = g_slice_new0 (GtkCssParser);
+
+  self->ref_count = 1;
+  self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
+  if (file)
+    self->file = g_object_ref (file);
+  if (base_directory)
+    self->directory = g_object_ref (base_directory);
+  else if (file)
+    self->directory = g_file_get_parent (file);
+  self->error_func = error_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+  self->blocks = g_array_new (FALSE, FALSE, sizeof (GtkCssParserBlock));
+
+  return self;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_file (GFile                 *file,
+                             GtkCssParserErrorFunc  error_func,
+                             gpointer               user_data,
+                             GDestroyNotify         user_destroy,
+                             GError               **error)
+{
+  GBytes *bytes;
+  GtkCssParser *result;
+
+  bytes = g_file_load_bytes (file, NULL, NULL, error);
+  if (bytes == NULL)
+    return NULL;
+
+  result = gtk_css_parser_new_for_bytes (bytes, file, NULL, error_func, user_data, user_destroy);
+
+  g_bytes_unref (bytes);
+
+  return result;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_bytes (GBytes                *bytes,
+                              GFile                 *file,
+                              GFile                 *base_directory,
+                              GtkCssParserErrorFunc  error_func,
+                              gpointer               user_data,
+                              GDestroyNotify         user_destroy)
+{
+  GtkCssTokenizer *tokenizer;
+  GtkCssParser *result;
+  
+  tokenizer = gtk_css_tokenizer_new (bytes);
+  result = gtk_css_parser_new (tokenizer, file, base_directory, error_func, user_data, user_destroy);
+  gtk_css_tokenizer_unref (tokenizer);
+
+  return result;
+}
+
+static void
+gtk_css_parser_finalize (GtkCssParser *self)
+{
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
+  g_clear_object (&self->file);
+  g_clear_object (&self->directory);
+  if (self->blocks->len)
+    g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
+  g_array_free (self->blocks, TRUE);
+
+  g_slice_free (GtkCssParser, self);
+}
+
+GtkCssParser *
+gtk_css_parser_ref (GtkCssParser *self)
+{
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+void
+gtk_css_parser_unref (GtkCssParser *self)
+{
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    gtk_css_parser_finalize (self);
+}
+
+/**
+ * gtk_css_parser_get_file:
+ * @self: a #GtkCssParser
+ *
+ * Gets the file being parsed. If no file is associated with @self -
+ * for example when raw data is parsed - %NULL is returned.
+ *
+ * Returns: (nullable) (transfer none): The file being parsed
+ *   or %NULL.
+ **/
+GFile *
+gtk_css_parser_get_file (GtkCssParser *self)
+{
+  return self->file;
+}
+
+/**
+ * gtk_css_parser_resolve_url:
+ * @self: a #GtkCssParser
+ * @url: the URL to resolve
+ *
+ * Resolves a given URL against the parser's location.
+ *
+ * Returns: (nullable) (transfer full): a new #GFile for the
+ *     resolved URL or %NULL if the URI cannot be resolved.
+ **/
+GFile *
+gtk_css_parser_resolve_url (GtkCssParser *self,
+                            const char   *url)
+{
+  char *scheme;
+
+  scheme = g_uri_parse_scheme (url);
+  if (scheme != NULL)
+    {
+      GFile *file = g_file_new_for_uri (url);
+      g_free (scheme);
+      return file;
+    }
+  g_free (scheme);
+
+  if (self->directory == NULL)
+    return NULL;
+
+  return g_file_resolve_relative_path (self->directory, url);
+}
+
+/**
+ * gtk_css_parser_get_location:
+ * @self: a #GtkCssParser
+ * @out_location: (caller-allocates) Place to store the location
+ *
+ * Queries the current location of the parser.
+ **/
+void
+gtk_css_parser_get_location (GtkCssParser   *self,
+                             GtkCssLocation *out_location)
+{
+  *out_location = self->location;
+}
+
+static void
+gtk_css_parser_ensure_token (GtkCssParser *self)
+{
+  GError *error = NULL;
+
+  if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
+    return;
+
+  self->location = *gtk_css_tokenizer_get_location (self->tokenizer);
+  if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error))
+    {
+      /* We ignore the error here, because the resulting token will
+       * likely already trigger an error in the parsing code and
+       * duplicate errors are rather useless.
+       */
+      g_clear_error (&error);
+    }
+}
+
+const GtkCssToken *
+gtk_css_parser_peek_token (GtkCssParser *self)
+{
+  static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
+
+  gtk_css_parser_ensure_token (self);
+
+  if (self->blocks->len)
+    {
+      GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+      if (gtk_css_token_is (&self->token, block->end_token) ||
+          gtk_css_token_is (&self->token, block->inherited_end_token) ||
+          gtk_css_token_is (&self->token, block->alternative_token))
+        return &eof_token;
+    }
+
+  return &self->token;
+}
+
+const GtkCssToken *
+gtk_css_parser_get_token (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+
+  for (token = gtk_css_parser_peek_token (self);
+       gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
+       gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
+       token = gtk_css_parser_peek_token (self))
+    {
+      gtk_css_parser_consume_token (self);
+    }
+
+  return token;
+}
+
+void
+gtk_css_parser_consume_token (GtkCssParser *self)
+{
+  gtk_css_parser_ensure_token (self);
+
+  /* unpreserved tokens MUST be consumed via start_block() */
+  g_assert (gtk_css_token_is_preserved (&self->token, NULL));
+
+  gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_block (GtkCssParser *self)
+{
+  GtkCssParserBlock block;
+
+  gtk_css_parser_ensure_token (self);
+
+  if (gtk_css_token_is_preserved (&self->token, &block.end_token))
+    {
+      g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
+      return;
+    }
+
+  block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+  block.alternative_token = GTK_CSS_TOKEN_EOF;
+  block.start_location = self->location;
+  g_array_append_val (self->blocks, block);
+
+  gtk_css_token_clear (&self->token);
+}
+
+void
+gtk_css_parser_start_semicolon_block (GtkCssParser    *self,
+                                      GtkCssTokenType  alternative_token)
+{
+  GtkCssParserBlock block;
+
+  block.end_token = GTK_CSS_TOKEN_SEMICOLON;
+  if (self->blocks->len)
+    block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 
1).end_token;
+  else
+    block.inherited_end_token = GTK_CSS_TOKEN_EOF;
+  block.alternative_token = alternative_token;
+  block.start_location = self->location;
+  g_array_append_val (self->blocks, block);
+}
+
+void
+gtk_css_parser_end_block_prelude (GtkCssParser *self)
+{
+  GtkCssParserBlock *block;
+
+  g_return_if_fail (self->blocks->len > 0);
+
+  block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+  if (block->alternative_token == GTK_CSS_TOKEN_EOF)
+    return;
+
+  gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+  if (gtk_css_token_is (&self->token, block->alternative_token))
+    {
+      if (gtk_css_token_is_preserved (&self->token, &block->end_token))
+        {
+          g_critical ("alternative token is not preserved");
+          return;
+        }
+      block->alternative_token = GTK_CSS_TOKEN_EOF;
+      block->inherited_end_token = GTK_CSS_TOKEN_EOF;
+      gtk_css_token_clear (&self->token);
+    }
+}
+
+void
+gtk_css_parser_end_block (GtkCssParser *self)
+{
+  GtkCssParserBlock *block;
+
+  g_return_if_fail (self->blocks->len > 0);
+
+  gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
+
+  block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+
+  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");
+      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");
+      g_array_set_size (self->blocks, self->blocks->len - 1);
+    }
+  else
+    {
+      g_array_set_size (self->blocks, self->blocks->len - 1);
+      gtk_css_parser_skip (self);
+    }
+}
+
+/*
+ * gtk_css_parser_skip:
+ * @self: a #GtkCssParser
+ *
+ * Skips a component value.
+ *
+ * This means that if the token is a preserved token, only
+ * this token will be skipped. If the token starts a block,
+ * the whole block will be skipped.
+ **/
+void
+gtk_css_parser_skip (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is_preserved (token, NULL))
+    {
+      gtk_css_parser_consume_token (self);
+    }
+  else
+    {
+      gtk_css_parser_start_block (self);
+      gtk_css_parser_end_block (self);
+    }
+}
+
+/*
+ * gtk_css_parser_skip_until:
+ * @self: a #GtkCssParser
+ * @token_type: type of token to skip to
+ *
+ * Repeatedly skips a token until a certain type is reached.
+ * After this called, gtk_css_parser_get_token() will either
+ * return a token of this type or the eof token.
+ *
+ * This function is useful for resyncing a parser after encountering
+ * an error.
+ *
+ * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
+ * as the token type.
+ **/
+void
+gtk_css_parser_skip_until (GtkCssParser    *self,
+                           GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+  
+  for (token = gtk_css_parser_get_token (self);
+       !gtk_css_token_is (token, token_type) &&
+       !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
+       token = gtk_css_parser_get_token (self))
+    {
+      gtk_css_parser_skip (self);
+    }
+}
+
+void
+gtk_css_parser_emit_error (GtkCssParser *self,
+                           const GError *error)
+{
+  self->error_func (self,
+                    &self->location,
+                    &self->location,
+                    error,
+                    self->user_data);
+}
+
+void
+gtk_css_parser_error_syntax (GtkCssParser *self,
+                             const char   *format,
+                             ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              GTK_CSS_PARSER_ERROR_SYNTAX,
+                              format, args);
+  gtk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_error_value (GtkCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+                              GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
+                              format, args);
+  gtk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+void
+gtk_css_parser_warn_syntax (GtkCssParser *self,
+                            const char   *format,
+                            ...)
+{
+  va_list args;
+  GError *error;
+
+  va_start (args, format);
+  error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+                              GTK_CSS_PARSER_WARNING_SYNTAX,
+                              format, args);
+  gtk_css_parser_emit_error (self, error);
+  g_error_free (error);
+  va_end (args);
+}
+
+gboolean
+gtk_css_parser_consume_function (GtkCssParser *self,
+                                 guint         min_args,
+                                 guint         max_args,
+                                 guint (* parse_func) (GtkCssParser *, guint, gpointer),
+                                 gpointer      data)
+{
+  const GtkCssToken *token;
+  gboolean result = FALSE;
+  char *function_name;
+  guint arg;
+
+  token = gtk_css_parser_get_token (self);
+  g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
+
+  function_name = g_strdup (token->string.string);
+  gtk_css_parser_start_block (self);
+
+  arg = 0;
+  while (TRUE)
+    {
+      guint parse_args = parse_func (self, arg, data);
+      if (parse_args == 0)
+        break;
+      arg += parse_args;
+      token = gtk_css_parser_get_token (self);
+      if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
+        {
+          if (arg < min_args)
+            {
+              gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, 
min_args);
+              break;
+            }
+          else
+            {
+              result = TRUE;
+              break;
+            }
+        }
+      else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
+        {
+          if (arg >= max_args)
+            {
+              gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
+              break;
+            }
+
+          gtk_css_parser_consume_token (self);
+          continue;
+        }
+      else
+        {
+          gtk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
+          break;
+        }
+    }
+
+  gtk_css_parser_end_block (self);
+  g_free (function_name);
+
+  return result;
+}
+
+/**
+ * gtk_css_parser_has_token:
+ * @self: a #GtkCssParser
+ * @token_type: type of the token to check
+ *
+ * Checks if the next token is of @token_type.
+ *
+ * Returns: %TRUE if the next token is of @token_type
+ **/
+gboolean
+gtk_css_parser_has_token (GtkCssParser    *self,
+                          GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, token_type);
+}
+
+/**
+ * gtk_css_parser_has_ident:
+ * @self: a #GtkCssParser
+ * @ident: name of identifier
+ *
+ * Checks if the next token is an identifier with the given @name.
+ *
+ * Returns: %TRUE if the next token is an identifier with the given @name
+ **/
+gboolean
+gtk_css_parser_has_ident (GtkCssParser *self,
+                          const char   *ident)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
+         g_ascii_strcasecmp (token->string.string, ident) == 0;
+}
+
+gboolean
+gtk_css_parser_has_integer (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+         gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+/**
+ * gtk_css_parser_has_function:
+ * @self: a #GtkCssParser
+ * @name: name of function
+ *
+ * Checks if the next token is a function with the given @name.
+ *
+ * Returns: %TRUE if the next token is a function with the given @name
+ **/
+gboolean
+gtk_css_parser_has_function (GtkCssParser *self,
+                             const char   *name)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
+         g_ascii_strcasecmp (token->string.string, name) == 0;
+}
+
+/**
+ * gtk_css_parser_try_delim:
+ * @self: a #GtkCssParser
+ * @codepoint: unicode character codepoint to check
+ *
+ * Checks if the current token is a delimiter matching the given
+ * @codepoint. If that is the case, the token is consumed and
+ * %TRUE is returned.
+ *
+ * Keep in mind that not every unicode codepoint can be a delim
+ * token.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_delim (GtkCssParser *self,
+                          gunichar      codepoint)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
+      codepoint != token->delim.delim)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_ident:
+ * @self: a #GtkCssParser
+ * @ident: identifier to check for
+ *
+ * Checks if the current token is an identifier matching the given
+ * @ident string. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_ident (GtkCssParser *self,
+                          const char   *ident)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
+      g_ascii_strcasecmp (token->string.string, ident) != 0)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_at_keyword:
+ * @self: a #GtkCssParser
+ * @keyword: name of keyword to check for
+ *
+ * Checks if the current token is an at-keyword token with the
+ * given @keyword. If that is the case, the token is consumed
+ * and %TRUE is returned.
+ *
+ * Returns: %TRUE if the token matched and was consumed.
+ **/
+gboolean
+gtk_css_parser_try_at_keyword (GtkCssParser *self,
+                               const char   *keyword)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
+      g_ascii_strcasecmp (token->string.string, keyword) != 0)
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_try_token:
+ * @self: a #GtkCssParser
+ * @token_type: type of token to try
+ *
+ * Consumes the next token if it matches the given @token_type.
+ *
+ * This function can be used in loops like this:
+ * do {
+ *   ... parse one element ...
+ * } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
+ *
+ * Returns: %TRUE if a token was consumed
+ **/
+gboolean
+gtk_css_parser_try_token (GtkCssParser    *self,
+                          GtkCssTokenType  token_type)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, token_type))
+    return FALSE;
+
+  gtk_css_parser_consume_token (self);
+  return TRUE;
+}
+
+/**
+ * gtk_css_parser_consume_ident:
+ * @self: a #GtkCssParser
+ *
+ * If the current token is an identifier, consumes it and returns
+ * its name.  
+ * If the current token is not an identifier, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed identifier
+ *     or %NULL on error
+ **/
+char *
+gtk_css_parser_consume_ident (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  char *ident;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+    {
+      gtk_css_parser_error_syntax (self, "Expected an identifier");
+      return NULL;
+    }
+
+  ident = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (self);
+
+  return ident;
+}
+
+/**
+ * gtk_css_parser_consume_string:
+ * @self: a #GtkCssParser
+ *
+ * If the current token is a string, consumes it and return the string.  
+ * If the current token is not a string, an error is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): the name of the consumed string 
+ *     or %NULL on error
+ **/
+char *
+gtk_css_parser_consume_string (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  char *ident;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+    {
+      gtk_css_parser_error_syntax (self, "Expected a string");
+      return NULL;
+    }
+
+  ident = g_strdup (token->string.string);
+  gtk_css_parser_consume_token (self);
+
+  return ident;
+}
+
+static guint
+gtk_css_parser_parse_url_arg (GtkCssParser *parser,
+                              guint         arg,
+                              gpointer      data)
+{
+  char **out_url = data;
+
+  *out_url = gtk_css_parser_consume_string (parser);
+  if (*out_url == NULL)
+    return 0;
+  
+  return 1;
+}
+
+/**
+ * gtk_css_parser_consume_url:
+ * @self: a #GtkCssParser
+ *
+ * If the parser matches the <url> token from the [CSS
+ * specification](https://drafts.csswg.org/css-values-4/#url-value),
+ * consumes it, resolves the URL and resturns the resulting #GFile.
+ * On failure, an error is emitted and %NULL is returned.
+ *
+ * Returns: (nullable) (transfer full): the resulting URL or %NULL on error
+ **/
+GFile *
+gtk_css_parser_consume_url (GtkCssParser *self)
+{
+  const GtkCssToken *token;
+  GFile *result;
+  char *url;
+
+  token = gtk_css_parser_get_token (self);
+
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
+    {
+      url = g_strdup (token->string.string);
+      gtk_css_parser_consume_token (self);
+    }
+  else if (gtk_css_token_is_function (token, "url"))
+    {
+      if (!gtk_css_parser_consume_function (self, 1, 1, gtk_css_parser_parse_url_arg, &url))
+        return NULL;
+    }
+  else
+    {
+      gtk_css_parser_error_syntax (self, "Expected a URL");
+      return NULL;
+    }
+  
+  result = gtk_css_parser_resolve_url (self, url);
+  if (result == NULL)
+    {
+      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);
+      g_free (url);
+      g_error_free (error);
+      return NULL;
+    }
+  g_free (url);
+
+  return result;
+}
+
+gboolean
+gtk_css_parser_consume_number (GtkCssParser *self,
+                               double       *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected a number");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_integer (GtkCssParser *self,
+                                int          *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
+      gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected an integer");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+gboolean
+gtk_css_parser_consume_percentage (GtkCssParser *self,
+                                   double       *number)
+{
+  const GtkCssToken *token;
+
+  token = gtk_css_parser_get_token (self);
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
+    {
+      *number = token->number.number;
+      gtk_css_parser_consume_token (self);
+      return TRUE;
+    }
+
+  gtk_css_parser_error_syntax (self, "Expected a percentage");
+  /* FIXME: Implement calc() */
+  return FALSE;
+}
+
+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_syntax (parser, "No valid value given");
+      return result;
+    }
+
+  return result;
+}
diff --git a/gtk/css/gtkcssparserprivate.h b/gtk/css/gtkcssparserprivate.h
new file mode 100644
index 0000000000..9ae77e2e7e
--- /dev/null
+++ b/gtk/css/gtkcssparserprivate.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_CSS_PARSER_H__
+#define __GTK_CSS_PARSER_H__
+
+#include "gtkcsstokenizerprivate.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssParser GtkCssParser;
+
+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;
+};
+
+typedef void            (* GtkCssParserErrorFunc)               (GtkCssParser                   *parser,
+                                                                 const GtkCssLocation           *start,
+                                                                 const GtkCssLocation           *end,
+                                                                 const GError                   *error,
+                                                                 gpointer                        user_data);
+
+GtkCssParser *          gtk_css_parser_new_for_file             (GFile                          *file,
+                                                                 GtkCssParserErrorFunc           error_func,
+                                                                 gpointer                        user_data,
+                                                                 GDestroyNotify                  
user_destroy,
+                                                                 GError                        **error);
+GtkCssParser *          gtk_css_parser_new_for_bytes            (GBytes                         *bytes,
+                                                                 GFile                          *file,
+                                                                 GFile                          
*base_directory,
+                                                                 GtkCssParserErrorFunc           error_func,
+                                                                 gpointer                        user_data,
+                                                                 GDestroyNotify                  
user_destroy);
+GtkCssParser *          gtk_css_parser_ref                      (GtkCssParser                   *self);
+void                    gtk_css_parser_unref                    (GtkCssParser                   *self);
+
+GFile *                 gtk_css_parser_get_file                 (GtkCssParser                   *self);
+GFile *                 gtk_css_parser_resolve_url              (GtkCssParser                   *self,
+                                                                 const char                     *url);
+void                    gtk_css_parser_get_location             (GtkCssParser                   *self,
+                                                                 GtkCssLocation                 
*out_location);
+
+const GtkCssToken *     gtk_css_parser_peek_token               (GtkCssParser                   *self);
+const GtkCssToken *     gtk_css_parser_get_token                (GtkCssParser                   *self);
+void                    gtk_css_parser_consume_token            (GtkCssParser                   *self);
+
+void                    gtk_css_parser_start_block              (GtkCssParser                   *self); 
+void                    gtk_css_parser_start_semicolon_block    (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 
alternative_token);
+void                    gtk_css_parser_end_block_prelude        (GtkCssParser                   *self);
+void                    gtk_css_parser_end_block                (GtkCssParser                   *self); 
+void                    gtk_css_parser_skip                     (GtkCssParser                   *self);
+void                    gtk_css_parser_skip_until               (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+
+void                    gtk_css_parser_emit_error               (GtkCssParser                   *self,
+                                                                 const GError                   *error);
+void                    gtk_css_parser_error_syntax             (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_css_parser_error_value              (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+void                    gtk_css_parser_warn_syntax              (GtkCssParser                   *self,
+                                                                 const char                     *format,
+                                                                 ...) G_GNUC_PRINTF(2, 3);
+
+
+gboolean                gtk_css_parser_has_token                (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+gboolean                gtk_css_parser_has_ident                (GtkCssParser                   *self,
+                                                                 const char                     *ident);
+gboolean                gtk_css_parser_has_integer              (GtkCssParser                   *self);
+gboolean                gtk_css_parser_has_function             (GtkCssParser                   *self,
+                                                                 const char                     *name);
+
+gboolean                gtk_css_parser_try_delim                (GtkCssParser                   *self,
+                                                                 gunichar                        codepoint);
+gboolean                gtk_css_parser_try_ident                (GtkCssParser                   *self,
+                                                                 const char                     *ident);
+gboolean                gtk_css_parser_try_at_keyword           (GtkCssParser                   *self,
+                                                                 const char                     *keyword);
+gboolean                gtk_css_parser_try_token                (GtkCssParser                   *self,
+                                                                 GtkCssTokenType                 token_type);
+
+char *                  gtk_css_parser_consume_ident            (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT;
+char *                  gtk_css_parser_consume_string           (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT;
+GFile *                 gtk_css_parser_consume_url              (GtkCssParser                   *self) 
G_GNUC_WARN_UNUSED_RESULT;
+gboolean                gtk_css_parser_consume_number           (GtkCssParser                   *self,
+                                                                 double                         *number);
+gboolean                gtk_css_parser_consume_integer          (GtkCssParser                   *self,
+                                                                 int                            *number);
+gboolean                gtk_css_parser_consume_percentage       (GtkCssParser                   *self,
+                                                                 double                         *number);
+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);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_PARSER_H__ */
diff --git a/gtk/css/meson.build b/gtk/css/meson.build
index 0dda25f6aa..18480ed88c 100644
--- a/gtk/css/meson.build
+++ b/gtk/css/meson.build
@@ -4,6 +4,7 @@ gtk_css_public_sources = files([
 ])
 
 gtk_css_private_sources = files([
+  'gtkcssparser.c',
   'gtkcsstokenizer.c',
 ])
 



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