diff options
author | Matthias Clasen <mclasen@redhat.com> | 2021-11-13 11:01:27 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2021-11-13 11:08:50 -0500 |
commit | e6b3cb4c7aae79d1b02cabcc3659fec145d0954e (patch) | |
tree | cebe6cbb00c8f8c42a3b0759b11da3c6924b696a /pango/css/gtkcssparser.c | |
parent | befadb396f3f48a1c2fe0ca07b00e783e0878e02 (diff) | |
download | pango-e6b3cb4c7aae79d1b02cabcc3659fec145d0954e.tar.gz |
Wedge the gtk css parser into pango
This is a brute-force job, just to see how far I get.
Diffstat (limited to 'pango/css/gtkcssparser.c')
-rw-r--r-- | pango/css/gtkcssparser.c | 1108 |
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; +} |