summaryrefslogtreecommitdiff
path: root/pango/css/gtkcssparser.c
diff options
context:
space:
mode:
Diffstat (limited to 'pango/css/gtkcssparser.c')
-rw-r--r--pango/css/gtkcssparser.c1108
1 files changed, 1108 insertions, 0 deletions
diff --git a/pango/css/gtkcssparser.c b/pango/css/gtkcssparser.c
new file mode 100644
index 00000000..7668bfc7
--- /dev/null
+++ b/pango/css/gtkcssparser.c
@@ -0,0 +1,1108 @@
+/*
+ * 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"
+#include "gtkcsslocationprivate.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,
+ 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);
+ 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, error_func, user_data, user_destroy);
+
+ g_bytes_unref (bytes);
+
+ return result;
+}
+
+GtkCssParser *
+gtk_css_parser_new_for_bytes (GBytes *bytes,
+ GFile *file,
+ 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, 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
+ */
+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
+ */
+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_start_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the start of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's start
+ * location will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_end_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the start location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_start_location (GtkCssParser *self)
+{
+ return &self->location;
+}
+
+/**
+ * gtk_css_parser_get_end_location:
+ * @self: a `GtkCssParser`
+ * @out_location: (caller-allocates) Place to store the location
+ *
+ * Queries the location of the current token.
+ *
+ * This function will return the location of the end of the
+ * current token. In the case a token has been consumed, but no
+ * new token has been queried yet via gtk_css_parser_peek_token()
+ * or gtk_css_parser_get_token(), the previous token's end location
+ * will be returned.
+ *
+ * This function may return the same location as
+ * gtk_css_parser_get_start_location() - in particular at the
+ * beginning and end of the document.
+ *
+ * Returns: the end location
+ **/
+const GtkCssLocation *
+gtk_css_parser_get_end_location (GtkCssParser *self)
+{
+ return gtk_css_tokenizer_get_location (self->tokenizer);
+}
+
+/**
+ * gtk_css_parser_get_block_location:
+ * @self: a `GtkCssParser`
+ *
+ * Queries the start location of the token that started the current
+ * block that is being parsed.
+ *
+ * If no block is currently parsed, the beginning of the document
+ * is returned.
+ *
+ * Returns: The start location of the current block
+ */
+const GtkCssLocation *
+gtk_css_parser_get_block_location (GtkCssParser *self)
+{
+ const GtkCssParserBlock *block;
+
+ if (self->blocks->len == 0)
+ {
+ static const GtkCssLocation start_of_document = { 0, };
+ return &start_of_document;
+ }
+
+ block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
+ return &block->start_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)
+ {
+ const 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));
+
+ /* Don't consume any tokens at the end of a block */
+ if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
+ 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);
+ if (gtk_css_token_is_preserved (&self->token, NULL))
+ {
+ gtk_css_token_clear (&self->token);
+ }
+ else
+ {
+ gtk_css_parser_start_block (self);
+ gtk_css_parser_end_block (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 GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const GError *error)
+{
+ if (self->error_func)
+ self->error_func (self, start, end, error, self->user_data);
+}
+
+void
+gtk_css_parser_error (GtkCssParser *self,
+ GtkCssParserError code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
+ code,
+ format, args);
+ gtk_css_parser_emit_error (self, start, end, error);
+ g_error_free (error);
+ va_end (args);
+}
+
+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,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (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,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_error_import (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_IMPORT,
+ format, args);
+ gtk_css_parser_emit_error (self,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (self),
+ error);
+ g_error_free (error);
+ va_end (args);
+}
+
+void
+gtk_css_parser_warn (GtkCssParser *self,
+ GtkCssParserWarning code,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const char *format,
+ ...)
+{
+ va_list args;
+ GError *error;
+
+ va_start (args, format);
+ error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
+ code,
+ format, args);
+ gtk_css_parser_emit_error (self, start, end, 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,
+ gtk_css_parser_get_start_location (self),
+ gtk_css_parser_get_end_location (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
+ */
+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
+ **/
+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 returns the resulting `GFile`.
+ * On failure, an error is emitted and %NULL is returned.
+ *
+ * Returns: (nullable) (transfer full): the resulting URL
+ **/
+char *
+gtk_css_parser_consume_url (GtkCssParser *self)
+{
+ const GtkCssToken *token;
+ 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;
+ }
+
+ return url;
+}
+
+gboolean
+gtk_css_parser_has_number (GtkCssParser *self)
+{
+ return gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_NUMBER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_INTEGER)
+ || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
+}
+
+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 != (1u << n_options) - 1u)
+ {
+ 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;
+}