rendernode: Redo the rendernode parser

Resurrect the CSS parser from the tokenizer branch and make it handle a
render node syntax.

The syntax is different from the old syntax. This is done so that it
follows CSS specification conventions as close as possible and can
tehrefor be parsed by a CSS parser.
This commit is contained in:
Benjamin Otte
2019-03-19 05:46:59 +01:00
parent 9a875a7515
commit 0fa5efa628
5 changed files with 1310 additions and 608 deletions

444
gsk/gskcssparser.c Normal file
View File

@@ -0,0 +1,444 @@
/*
* 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 "gskcssparserprivate.h"
#define GTK_COMPILATION
#include "gtk/gtkcssprovider.h"
struct _GskCssParser
{
volatile int ref_count;
GskCssParserErrorFunc error_func;
gpointer user_data;
GDestroyNotify user_destroy;
GSList *sources;
GSList *blocks;
GskCssLocation location;
GskCssToken token;
};
GskCssParser *
gsk_css_parser_new (GskCssParserErrorFunc error_func,
gpointer user_data,
GDestroyNotify user_destroy)
{
GskCssParser *self;
self = g_slice_new0 (GskCssParser);
self->ref_count = 1;
self->error_func = error_func;
self->user_data = user_data;
self->user_destroy = user_destroy;
return self;
}
static void
gsk_css_parser_finalize (GskCssParser *self)
{
g_slist_free_full (self->sources, (GDestroyNotify) gsk_css_tokenizer_unref);
if (self->user_destroy)
self->user_destroy (self->user_data);
g_slice_free (GskCssParser, self);
}
GskCssParser *
gsk_css_parser_ref (GskCssParser *self)
{
g_atomic_int_inc (&self->ref_count);
return self;
}
void
gsk_css_parser_unref (GskCssParser *self)
{
if (g_atomic_int_dec_and_test (&self->ref_count))
gsk_css_parser_finalize (self);
}
void
gsk_css_parser_add_tokenizer (GskCssParser *self,
GskCssTokenizer *tokenizer)
{
self->sources = g_slist_prepend (self->sources, gsk_css_tokenizer_ref (tokenizer));
}
void
gsk_css_parser_add_bytes (GskCssParser *self,
GBytes *bytes)
{
GskCssTokenizer *tokenizer;
tokenizer = gsk_css_tokenizer_new (bytes);
gsk_css_parser_add_tokenizer (self, tokenizer);
gsk_css_tokenizer_unref (tokenizer);
}
static void
gsk_css_parser_ensure_token (GskCssParser *self)
{
GskCssTokenizer *tokenizer;
GError *error = NULL;
if (!gsk_css_token_is (&self->token, GSK_CSS_TOKEN_EOF))
return;
if (self->sources == NULL)
return;
tokenizer = self->sources->data;
self->location = *gsk_css_tokenizer_get_location (tokenizer);
if (!gsk_css_tokenizer_read_token (tokenizer, &self->token, &error))
{
g_clear_error (&error);
}
}
const GskCssToken *
gsk_css_parser_peek_token (GskCssParser *self)
{
static const GskCssToken eof_token = { GSK_CSS_TOKEN_EOF, };
gsk_css_parser_ensure_token (self);
if (self->blocks && gsk_css_token_is (&self->token, GPOINTER_TO_UINT (self->blocks->data)))
return &eof_token;
return &self->token;
}
const GskCssToken *
gsk_css_parser_get_token (GskCssParser *self)
{
const GskCssToken *token;
for (token = gsk_css_parser_peek_token (self);
gsk_css_token_is (token, GSK_CSS_TOKEN_COMMENT) ||
gsk_css_token_is (token, GSK_CSS_TOKEN_WHITESPACE);
token = gsk_css_parser_peek_token (self))
{
gsk_css_parser_consume_token (self);
}
return token;
}
void
gsk_css_parser_consume_token (GskCssParser *self)
{
gsk_css_parser_ensure_token (self);
/* unpreserved tokens MUST be consumed via start_block() */
g_assert (gsk_css_token_is_preserved (&self->token, NULL));
gsk_css_token_clear (&self->token);
}
void
gsk_css_parser_start_block (GskCssParser *self)
{
GskCssTokenType end_token_type;
gsk_css_parser_ensure_token (self);
if (gsk_css_token_is_preserved (&self->token, &end_token_type))
{
g_critical ("gsk_css_parser_start_block() may only be called for non-preserved tokens");
return;
}
self->blocks = g_slist_prepend (self->blocks, GUINT_TO_POINTER (end_token_type));
gsk_css_token_clear (&self->token);
}
void
gsk_css_parser_end_block (GskCssParser *self)
{
g_return_if_fail (self->blocks != NULL);
gsk_css_parser_skip_until (self, GSK_CSS_TOKEN_EOF);
if (gsk_css_token_is (&self->token, GSK_CSS_TOKEN_EOF))
gsk_css_parser_warn_syntax (self, "Unterminated block at end of document");
self->blocks = g_slist_remove (self->blocks, self->blocks->data);
gsk_css_token_clear (&self->token);
}
/*
* gsk_css_parser_skip:
* @self: a #GskCssParser
*
* 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
gsk_css_parser_skip (GskCssParser *self)
{
const GskCssToken *token;
token = gsk_css_parser_get_token (self);
if (gsk_css_token_is_preserved (token, NULL))
{
gsk_css_parser_consume_token (self);
}
else
{
gsk_css_parser_start_block (self);
gsk_css_parser_end_block (self);
}
}
/*
* gsk_css_parser_skip_until:
* @self: a #GskCssParser
* @token_type: type of token to skip to
*
* Repeatedly skips a token until a certain type is reached.
* After this called, gsk_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
gsk_css_parser_skip_until (GskCssParser *self,
GskCssTokenType token_type)
{
const GskCssToken *token;
for (token = gsk_css_parser_get_token (self);
!gsk_css_token_is (token, token_type) &&
!gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
token = gsk_css_parser_get_token (self))
{
gsk_css_parser_skip (self);
}
}
void
gsk_css_parser_emit_error (GskCssParser *self,
const GError *error)
{
self->error_func (self,
&self->location,
&self->token,
error,
self->user_data);
}
void
gsk_css_parser_error_syntax (GskCssParser *self,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_SYNTAX,
format, args);
gsk_css_parser_emit_error (self, error);
g_error_free (error);
va_end (args);
}
void
gsk_css_parser_error_value (GskCssParser *self,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
format, args);
gsk_css_parser_emit_error (self, error);
g_error_free (error);
va_end (args);
}
void
gsk_css_parser_warn_syntax (GskCssParser *self,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_WARN_GENERAL,
format, args);
gsk_css_parser_emit_error (self, error);
g_error_free (error);
va_end (args);
}
gboolean
gsk_css_parser_consume_function (GskCssParser *self,
guint min_args,
guint max_args,
guint (* parse_func) (GskCssParser *, guint, gpointer),
gpointer data)
{
const GskCssToken *token;
gboolean result = FALSE;
char *function_name;
guint arg;
token = gsk_css_parser_get_token (self);
g_return_val_if_fail (gsk_css_token_is (token, GSK_CSS_TOKEN_FUNCTION), FALSE);
function_name = g_strdup (token->string.string);
gsk_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 = gsk_css_parser_get_token (self);
if (gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
{
if (arg < min_args)
{
gsk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, min_args);
break;
}
else
{
result = TRUE;
break;
}
}
else if (gsk_css_token_is (token, GSK_CSS_TOKEN_COMMA))
{
if (arg >= max_args)
{
gsk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
break;
}
gsk_css_parser_consume_token (self);
continue;
}
else
{
gsk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
break;
}
}
gsk_css_parser_end_block (self);
g_free (function_name);
return result;
}
/**
* gsk_css_parser_consume_if:
* @self: a #GskCssParser
* @token_type: type of token to check for
*
* 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 (gsk_css_parser_consume_if (parser, GSK_CSS_TOKEN_COMMA);
*
* Returns: %TRUE if a token was consumed
**/
gboolean
gsk_css_parser_consume_if (GskCssParser *self,
GskCssTokenType token_type)
{
const GskCssToken *token;
token = gsk_css_parser_get_token (self);
if (!gsk_css_token_is (token, token_type))
return FALSE;
gsk_css_parser_consume_token (self);
return TRUE;
}
gboolean
gsk_css_parser_consume_number (GskCssParser *self,
double *number)
{
const GskCssToken *token;
token = gsk_css_parser_get_token (self);
if (gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNLESS_NUMBER) ||
gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
gsk_css_token_is (token, GSK_CSS_TOKEN_SIGNLESS_INTEGER))
{
*number = token->number.number;
gsk_css_parser_consume_token (self);
return TRUE;
}
/* FIXME: Implement calc() */
return FALSE;
}
gboolean
gsk_css_parser_consume_percentage (GskCssParser *self,
double *number)
{
const GskCssToken *token;
token = gsk_css_parser_get_token (self);
if (gsk_css_token_is (token, GSK_CSS_TOKEN_PERCENTAGE))
{
*number = token->number.number;
gsk_css_parser_consume_token (self);
return TRUE;
}
/* FIXME: Implement calc() */
return FALSE;
}

83
gsk/gskcssparserprivate.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* 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 __GSK_CSS_PARSER_H__
#define __GSK_CSS_PARSER_H__
#include "gskcsstokenizerprivate.h"
G_BEGIN_DECLS
typedef struct _GskCssParser GskCssParser;
typedef void (* GskCssParserErrorFunc) (GskCssParser *parser,
const GskCssLocation *location,
const GskCssToken *token,
const GError *error,
gpointer user_data);
GskCssParser * gsk_css_parser_new (GskCssParserErrorFunc error_func,
gpointer user_data,
GDestroyNotify user_destroy);
GskCssParser * gsk_css_parser_ref (GskCssParser *self);
void gsk_css_parser_unref (GskCssParser *self);
void gsk_css_parser_add_tokenizer (GskCssParser *self,
GskCssTokenizer *tokenizer);
void gsk_css_parser_add_bytes (GskCssParser *self,
GBytes *bytes);
const GskCssToken * gsk_css_parser_peek_token (GskCssParser *self);
const GskCssToken * gsk_css_parser_get_token (GskCssParser *self);
void gsk_css_parser_consume_token (GskCssParser *self);
void gsk_css_parser_start_block (GskCssParser *self);
void gsk_css_parser_end_block (GskCssParser *self);
void gsk_css_parser_skip (GskCssParser *self);
void gsk_css_parser_skip_until (GskCssParser *self,
GskCssTokenType token_type);
void gsk_css_parser_emit_error (GskCssParser *self,
const GError *error);
void gsk_css_parser_error_syntax (GskCssParser *self,
const char *format,
...) G_GNUC_PRINTF(2, 3);
void gsk_css_parser_error_value (GskCssParser *self,
const char *format,
...) G_GNUC_PRINTF(2, 3);
void gsk_css_parser_warn_syntax (GskCssParser *self,
const char *format,
...) G_GNUC_PRINTF(2, 3);
gboolean gsk_css_parser_consume_if (GskCssParser *self,
GskCssTokenType token_type);
gboolean gsk_css_parser_consume_number (GskCssParser *self,
double *number);
gboolean gsk_css_parser_consume_percentage (GskCssParser *self,
double *number);
gboolean gsk_css_parser_consume_function (GskCssParser *self,
guint min_args,
guint max_args,
guint (* parse_func) (GskCssParser *, guint, gpointer),
gpointer data);
G_END_DECLS
#endif /* __GSK_CSS_PARSER_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ gsk_public_sources = files([
gsk_private_sources = files([
'gskcairoblur.c',
'gskcairorenderer.c',
'gskcssparser.c',
'gskcsstokenizer.c',
'gskdebug.c',
'gskprivate.c',

View File

@@ -45,6 +45,7 @@ G_BEGIN_DECLS
* @GTK_CSS_PROVIDER_ERROR_NAME: Name error.
* @GTK_CSS_PROVIDER_ERROR_DEPRECATED: Deprecation error.
* @GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE: Unknown value.
* @GTK_CSS_PROVIDER_WARN_GENERAL: A general warning.
*
* Error codes for %GTK_CSS_PROVIDER_ERROR.
*/
@@ -55,7 +56,8 @@ typedef enum
GTK_CSS_PROVIDER_ERROR_IMPORT,
GTK_CSS_PROVIDER_ERROR_NAME,
GTK_CSS_PROVIDER_ERROR_DEPRECATED,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
GTK_CSS_PROVIDER_WARN_GENERAL,
} GtkCssProviderError;
GDK_AVAILABLE_IN_ALL