[gtk/wip/baedert/nodeeditor: 8/15] rendernode: Redo the rendernode parser
- From: Timm Bäder <baedert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/baedert/nodeeditor: 8/15] rendernode: Redo the rendernode parser
- Date: Tue, 16 Apr 2019 05:02:58 +0000 (UTC)
commit e3423d50bd5035938404a098307bcffd82e4ede0
Author: Benjamin Otte <otte redhat com>
Date: Tue Mar 19 05:46:59 2019 +0100
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.
gsk/gskcssparser.c | 444 +++++++++++++++++
gsk/gskcssparserprivate.h | 83 ++++
gsk/gskrendernodeparser.c | 1206 ++++++++++++++++++++++++++-------------------
gsk/meson.build | 1 +
gtk/gtkcssprovider.h | 33 ++
5 files changed, 1250 insertions(+), 517 deletions(-)
---
diff --git a/gsk/gskcssparser.c b/gsk/gskcssparser.c
new file mode 100644
index 0000000000..6a4e1ffd9e
--- /dev/null
+++ b/gsk/gskcssparser.c
@@ -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;
+}
diff --git a/gsk/gskcssparserprivate.h b/gsk/gskcssparserprivate.h
new file mode 100644
index 0000000000..b3c9da7a32
--- /dev/null
+++ b/gsk/gskcssparserprivate.h
@@ -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__ */
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index 912a7d0822..902c44495a 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1,668 +1,841 @@
#include "gskrendernodeparserprivate.h"
-#include "gskcsstokenizerprivate.h"
+#include "gskcssparserprivate.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gsktransform.h"
-typedef struct
-{
- int n_tokens;
- GskCssToken *tokens;
+typedef struct _Declaration Declaration;
- int pos;
- const GskCssToken *cur;
-} Parser;
-
-static void
-skip (Parser *p)
+struct _Declaration
{
- p->pos ++;
+ const char *name;
+ gboolean (* parse_func) (GskCssParser *parser, gpointer result);
+ gpointer result;
+};
+
+static gboolean
+parse_color_channel_value (GskCssParser *parser,
+ double *value,
+ gboolean is_percentage)
+{
+ if (is_percentage)
+ {
+ if (!gsk_css_parser_consume_percentage (parser, value))
+ return FALSE;
+
+ *value = CLAMP (*value, 0.0, 100.0) / 100.0;
+ return TRUE;
+ }
+ else
+ {
+ if (!gsk_css_parser_consume_number (parser, value))
+ return FALSE;
- g_assert_cmpint (p->pos, <, p->n_tokens);
- p->cur = &p->tokens[p->pos];
+ *value = CLAMP (*value, 0.0, 255.0) / 255.0;
+ return TRUE;
+ }
}
-static const GskCssToken *
-lookahead (Parser *p,
- int lookahead)
+static guint
+parse_color_channel (GskCssParser *parser,
+ guint arg,
+ gpointer data)
{
- g_assert_cmpint (p->pos, <, p->n_tokens - lookahead);
+ GdkRGBA *rgba = data;
- return &p->tokens[p->pos + lookahead];
-}
+ if (arg == 0)
+ {
+ /* We abuse rgba->alpha to store if we use percentages or numbers */
+ if (gsk_css_token_is (gsk_css_parser_get_token (parser), GSK_CSS_TOKEN_PERCENTAGE))
+ rgba->alpha = 1.0;
+ else
+ rgba->alpha = 0.0;
-static void
-expect (Parser *p,
- int expected_type)
-{
- if (p->cur->type != expected_type)
- g_error ("Expected token type %d but found %d ('%s')",
- expected_type, p->cur->type, gsk_css_token_to_string (p->cur));
-}
+ if (!parse_color_channel_value (parser, &rgba->red, rgba->alpha != 0.0))
+ return 0;
+ }
+ else if (arg == 1)
+ {
+ if (!parse_color_channel_value (parser, &rgba->green, rgba->alpha != 0.0))
+ return 0;
+ }
+ else if (arg == 2)
+ {
+ if (!parse_color_channel_value (parser, &rgba->blue, rgba->alpha != 0.0))
+ return 0;
+ }
+ else if (arg == 3)
+ {
+ if (!gsk_css_parser_consume_number (parser, &rgba->alpha))
+ return FALSE;
-static void
-expect_skip (Parser *p,
- int expected_type)
-{
- expect (p, expected_type);
- skip (p);
+ rgba->alpha = CLAMP (rgba->alpha, 0.0, 1.0);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ return 1;
}
-static void
-expect_skip_ident (Parser *p,
- const char *ident)
+static gboolean
+rgba_init_chars (GdkRGBA *rgba,
+ const char s[8])
{
- if (!gsk_css_token_is_ident (p->cur, ident))
- g_error ("Expected ident '%s', but found token %s",
- ident, p->cur->string.string);
+ guint i;
- skip (p);
-}
+ for (i = 0; i < 8; i++)
+ {
+ if (!g_ascii_isxdigit (s[i]))
+ return FALSE;
+ }
-static void
-parser_init (Parser *p,
- GskCssToken *tokens,
- int n_tokens)
-{
- p->tokens = tokens;
- p->pos = 0;
- p->cur = &tokens[p->pos];
- p->n_tokens = n_tokens;
+ rgba->red = (g_ascii_xdigit_value (s[0]) * 16 + g_ascii_xdigit_value (s[1])) / 255.0;
+ rgba->green = (g_ascii_xdigit_value (s[2]) * 16 + g_ascii_xdigit_value (s[3])) / 255.0;
+ rgba->blue = (g_ascii_xdigit_value (s[4]) * 16 + g_ascii_xdigit_value (s[5])) / 255.0;
+ rgba->alpha = (g_ascii_xdigit_value (s[6]) * 16 + g_ascii_xdigit_value (s[7])) / 255.0;
+
+ return TRUE;
}
-static GskCssToken *
-tokenize (GBytes *bytes,
- int *n_tokens)
+static gboolean
+gsk_rgba_parse (GskCssParser *parser,
+ GdkRGBA *rgba)
{
- GskCssTokenizer *tokenizer;
- GArray *tokens;
- GskCssToken token;
+ const GskCssToken *token;
- tokenizer = gsk_css_tokenizer_new (bytes);
- tokens = g_array_new (FALSE, TRUE, sizeof (GskCssToken));
+ token = gsk_css_parser_get_token (parser);
+ if (gsk_css_token_is_function (token, "rgb"))
+ {
+ if (!gsk_css_parser_consume_function (parser, 3, 3, parse_color_channel, rgba))
+ return FALSE;
- for (gsk_css_tokenizer_read_token (tokenizer, &token, NULL);
- !gsk_css_token_is (&token, GSK_CSS_TOKEN_EOF);
- gsk_css_tokenizer_read_token (tokenizer, &token, NULL))
+ rgba->alpha = 1.0;
+ return TRUE;
+ }
+ else if (gsk_css_token_is_function (token, "rgba"))
{
- g_array_append_val (tokens, token);
+ return gsk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
}
+ else if (gsk_css_token_is (token, GSK_CSS_TOKEN_HASH_ID) ||
+ gsk_css_token_is (token, GSK_CSS_TOKEN_HASH_UNRESTRICTED))
+ {
+ const char *s = token->string.string;
- g_array_append_val (tokens, token);
+ switch (strlen (s))
+ {
+ case 3:
+ if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], 'F', 'F' }))
+ return TRUE;
+ break;
+
+ case 4:
+ if (rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], s[3], s[3] }))
+ return TRUE;
+ break;
+
+ case 6:
+ if (rgba_init_chars (rgba, (char[8]) {s[0], s[1], s[2], s[3], s[4], s[5], 'F', 'F' }))
+ return TRUE;
+ break;
+
+ case 8:
+ if (rgba_init_chars (rgba, s))
+ return TRUE;
+ break;
+
+ default:
+ break;
+ }
- *n_tokens = (int) tokens->len;
+ gsk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
+ return FALSE;
+ }
+ else if (gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+ {
+ if (gsk_css_token_is_ident (token, "transparent"))
+ {
+ rgba = &(GdkRGBA) { 0, 0, 0, 0 };
+ }
+ else if (gdk_rgba_parse (rgba, token->string.string))
+ {
+ /* everything's fine */
+ }
+ else
+ {
+ gsk_css_parser_error_value (parser, "\"%s\" is not a known color name.", token->string.string);
+ return FALSE;
+ }
- return (GskCssToken *) g_array_free (tokens, FALSE);
+ gsk_css_parser_consume_token (parser);
+ return TRUE;
+ }
+ else
+ {
+ gsk_css_parser_error_syntax (parser, "Expected a valid color.");
+ return FALSE;
+ }
}
-static double
-number_value (Parser *p)
+static gboolean
+parse_semicolon (GskCssParser *parser)
{
- if (!gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) &&
- !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) &&
- !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) &&
- !gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
- expect (p, GSK_CSS_TOKEN_SIGNED_NUMBER);
+ const GskCssToken *token;
- return p->cur->number.number;
+ token = gsk_css_parser_get_token (parser);
+ if (gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+ {
+ gsk_css_parser_warn_syntax (parser, "No ';' at end of block");
+ return TRUE;
+ }
+ else if (!gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON))
+ {
+ gsk_css_parser_error_syntax (parser, "Expected ';' at end of statement");
+ return FALSE;
+ }
+
+ gsk_css_parser_consume_token (parser);
+ return TRUE;
}
-static void
-parse_double4 (Parser *p,
- double *out_values)
+static gboolean
+parse_rect_without_semicolon (GskCssParser *parser,
+ graphene_rect_t *out_rect)
{
- int i;
+ double numbers[4];
+
+ if (!gsk_css_parser_consume_number (parser, &numbers[0]) ||
+ !gsk_css_parser_consume_number (parser, &numbers[1]) ||
+ !gsk_css_parser_consume_number (parser, &numbers[2]) ||
+ !gsk_css_parser_consume_number (parser, &numbers[3]))
+ return FALSE;
- expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
- out_values[0] = number_value (p);
- skip (p);
+ graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
- for (i = 0; i < 3; i ++)
- {
- expect_skip (p, GSK_CSS_TOKEN_COMMA);
- out_values[1 + i] = number_value (p);
- skip (p);
- }
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+ return TRUE;
}
-static void
-parse_tuple (Parser *p,
- double *out_values)
+static gboolean
+parse_rect (GskCssParser *parser,
+ gpointer out_rect)
{
- expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
- out_values[0] = number_value (p);
- skip (p);
+ graphene_rect_t r;
- expect_skip (p, GSK_CSS_TOKEN_COMMA);
+ if (!parse_rect_without_semicolon (parser, &r) ||
+ !parse_semicolon (parser))
+ return FALSE;
- out_values[1] = number_value (p);
- skip (p);
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
+ graphene_rect_init_from_rect (out_rect, &r);
+ return TRUE;
}
-/*
- * The cases we allow are:
- * (x, y, w, h) (w1, h1) (w2, h2) (w3, h3) (w4, h4) for the full rect
- * (x, y, w, h) s1 s2 s3 s4 for rect + quad corners
- * (x, y, w, h) s for rect + all corners the same size
- * (x, y, w, h) for just the rect with 0-sized corners
- */
-static void
-parse_rounded_rect (Parser *p,
- GskRoundedRect *result)
+static gboolean
+parse_rounded_rect (GskCssParser *parser,
+ gpointer out_rect)
{
- double rect[4];
- double corner0[2];
- double corner1[2];
- double corner2[2];
- double corner3[2];
+ const GskCssToken *token;
+ graphene_rect_t r;
+ graphene_size_t corners[4];
+ double d;
+ guint i;
- parse_double4 (p, rect);
+ if (!parse_rect_without_semicolon (parser, &r))
+ return FALSE;
- if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_OPEN_PARENS))
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is_delim (token, '/'))
{
- parse_tuple (p, corner0);
- parse_tuple (p, corner1);
- parse_tuple (p, corner2);
- parse_tuple (p, corner3);
+ if (!parse_semicolon (parser))
+ return FALSE;
+ gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
+ return TRUE;
}
- else if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+ gsk_css_parser_consume_token (parser);
+
+ for (i = 0; i < 4; i++)
{
- double val = number_value (p);
+ token = gsk_css_parser_get_token (parser);
+ if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+ gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+ break;
+ if (!gsk_css_parser_consume_number (parser, &d))
+ return FALSE;
+ corners[i].width = d;
+ }
- corner0[0] = corner0[1] = val;
+ if (i == 0)
+ {
+ gsk_css_parser_error_syntax (parser, "Expected a number");
+ return FALSE;
+ }
- skip (p);
+ /* The magic (i - 1) >> 1 below makes it take the correct value
+ * according to spec. Feel free to check the 4 cases
+ */
+ for (; i < 4; i++)
+ corners[i].width = corners[(i - 1) >> 1].width;
- if (gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_INTEGER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_INTEGER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNED_NUMBER) ||
- gsk_css_token_is (p->cur, GSK_CSS_TOKEN_SIGNLESS_NUMBER))
+ token = gsk_css_parser_get_token (parser);
+ if (gsk_css_token_is_delim (token, '/'))
+ {
+ gsk_css_parser_consume_token (parser);
+
+ for (i = 0; i < 4; i++)
{
- corner1[0] = corner1[1] = number_value (p);
- skip (p);
- corner2[0] = corner2[1] = number_value (p);
- skip (p);
- corner3[0] = corner3[1] = number_value (p);
- skip (p);
+ token = gsk_css_parser_get_token (parser);
+ if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+ gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+ break;
+ if (!gsk_css_parser_consume_number (parser, &d))
+ return FALSE;
+ corners[i].height = d;
}
- else
+
+ if (i == 0)
{
- corner1[0] = corner1[1] = val;
- corner2[0] = corner2[1] = val;
- corner3[0] = corner3[1] = val;
+ gsk_css_parser_error_syntax (parser, "Expected a number");
+ return FALSE;
}
+
+ for (; i < 4; i++)
+ corners[i].height = corners[(i - 1) >> 1].height;
}
else
{
- corner0[0] = corner0[1] = 0.0;
- corner1[0] = corner1[1] = 0.0;
- corner2[0] = corner2[1] = 0.0;
- corner3[0] = corner3[1] = 0.0;
+ for (i = 0; i < 4; i++)
+ corners[i].height = corners[i].width;
}
- gsk_rounded_rect_init (result,
- &GRAPHENE_RECT_INIT (rect[0], rect[1], rect[2], rect[3]),
- &(graphene_size_t) { corner0[0], corner0[1] },
- &(graphene_size_t) { corner1[0], corner1[1] },
- &(graphene_size_t) { corner2[0], corner2[1] },
- &(graphene_size_t) { corner3[0], corner3[1] });
-}
-
-static void
-parse_matrix (Parser *p,
- graphene_matrix_t *matrix)
-{
- float vals[16];
- int i;
-
- expect_skip (p, GSK_CSS_TOKEN_OPEN_PARENS);
-
- vals[0] = number_value (p);
- skip (p);
+ if (!parse_semicolon (parser))
+ return FALSE;
- for (i = 1; i < 16; i ++)
- {
- expect_skip (p, GSK_CSS_TOKEN_COMMA);
- vals[i] = number_value (p);
- skip (p);
- }
+ gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
- graphene_matrix_init_from_float (matrix, vals);
+ return TRUE;
}
-static double
-parse_float_param (Parser *p,
- const char *param_name)
+static gboolean
+parse_color (GskCssParser *parser,
+ gpointer out_color)
{
- double value;
+ GdkRGBA color;
+
+ if (!gsk_rgba_parse (parser, &color) ||
+ !parse_semicolon (parser))
+ return FALSE;
- expect_skip_ident (p, param_name);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
- value = number_value (p);
- skip (p);
+ *(GdkRGBA *) out_color = color;
- return value;
+ return TRUE;
}
-static void
-parse_tuple_param (Parser *p,
- const char *param_name,
- double *values)
+static gboolean
+parse_double (GskCssParser *parser,
+ gpointer out_double)
{
- expect_skip_ident (p, param_name);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
+ double d;
- parse_tuple (p, values);
-}
+ if (!gsk_css_parser_consume_number (parser, &d) ||
+ !parse_semicolon (parser))
+ return FALSE;
-static void
-parse_double4_param (Parser *p,
- const char *param_name,
- double *values)
-{
- expect_skip_ident (p, param_name);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
+ *(double *) out_double = d;
- parse_double4 (p, values);
+ return TRUE;
}
-static void
-parse_rounded_rect_param (Parser *p,
- const char *param_name,
- GskRoundedRect *rect)
+static gboolean
+parse_point (GskCssParser *parser,
+ gpointer out_point)
{
- expect_skip_ident (p, param_name);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
+ double x, y;
- parse_rounded_rect (p, rect);
-}
+ if (!gsk_css_parser_consume_number (parser, &x) ||
+ !gsk_css_parser_consume_number (parser, &y) ||
+ !parse_semicolon (parser))
+ return FALSE;
-static void
-parse_matrix_param (Parser *p,
- const char *param_name,
- graphene_matrix_t *matrix)
-{
- expect_skip_ident (p, param_name);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
+ graphene_point_init (out_point, x, y);
- parse_matrix (p, matrix);
+ return TRUE;
}
-static GskRenderNode *
-parse_node (Parser *p)
+static gboolean
+parse_string (GskCssParser *parser,
+ gpointer out_string)
{
- GskRenderNode *result = NULL;
-
- if (gsk_css_token_is_ident (p->cur, "color"))
- {
- double color[4];
- double bounds[4];
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ const GskCssToken *token;
+ char *s;
- parse_double4_param (p, "bounds", bounds);
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is (token, GSK_CSS_TOKEN_STRING))
+ return FALSE;
- expect_skip_ident (p, "color");
- expect_skip (p, GSK_CSS_TOKEN_COLON);
- parse_double4 (p, color);
+ s = g_strdup (token->string.string);
+ gsk_css_parser_consume_token (parser);
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_color_node_new (&(GdkRGBA) { color[0], color[1], color[2], color[3] },
- &GRAPHENE_RECT_INIT (bounds[0], bounds[1], bounds[2], bounds[3]));
- }
- else if (gsk_css_token_is_ident (p->cur, "opacity"))
+ if (!parse_semicolon (parser))
{
- double opacity = 0.0;
- GskRenderNode *child;
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- expect_skip_ident (p, "opacity");
- expect_skip (p, GSK_CSS_TOKEN_COLON);
- opacity = number_value (p);
- skip (p);
-
- child = parse_node (p);
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_opacity_node_new (child, opacity);
- gsk_render_node_unref (child);
+ g_free (s);
+ return FALSE;
}
- else if (gsk_css_token_is_ident (p->cur, "container"))
- {
- GPtrArray *children = g_ptr_array_new ();
- guint i;
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- while (p->cur->type != GSK_CSS_TOKEN_CLOSE_CURLY)
- g_ptr_array_add (children, parse_node (p));
+ g_free (*(char **) out_string);
+ *(char **) out_string = s;
+ return TRUE;
+}
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_container_node_new ((GskRenderNode **)children->pdata, children->len);
+static gboolean
+parse_stops (GskCssParser *parser,
+ gpointer out_stops)
+{
+ GArray *stops;
+ GskColorStop stop;
- for (i = 0; i < children->len; i ++)
- gsk_render_node_unref (g_ptr_array_index (children, i));
+ stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
- g_ptr_array_free (children, TRUE);
- }
- else if (gsk_css_token_is_ident (p->cur, "outset_shadow"))
- {
- GskRoundedRect outline;
- double color[4];
- float dx, dy, spread, blur_radius;
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- parse_rounded_rect_param (p, "outline", &outline);
-
- parse_double4_param (p, "color", color);
- dx = parse_float_param (p, "dx");
- dy = parse_float_param (p, "dy");
- spread = parse_float_param (p, "spread");
- blur_radius = parse_float_param (p, "blur_radius");
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_outset_shadow_node_new (&outline,
- &(GdkRGBA) { color[0], color[1], color[2], color[3] },
- dx, dy,
- spread,
- blur_radius);
- }
- else if (gsk_css_token_is_ident (p->cur, "cross_fade"))
+ do
{
- double progress;
- GskRenderNode *start_child;
- GskRenderNode *end_child;
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
- progress = parse_float_param (p, "progress");
- start_child = parse_node (p);
- end_child = parse_node (p);
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_cross_fade_node_new (start_child, end_child, progress);
+ if (!gsk_css_parser_consume_number (parser, &stop.offset) ||
+ !gsk_rgba_parse (parser, &stop.color))
+ { /* do nothing */ }
+ else if (stops->len == 0 && stop.offset < 0)
+ gsk_css_parser_error_value (parser, "Color stop offset must be >= 0");
+ else if (stops->len > 0 && stop.offset < g_array_index (stops, GskColorStop, stops->len - 1).offset)
+ gsk_css_parser_error_value (parser, "Color stop offset must be >= previous value");
+ else if (stop.offset > 1)
+ gsk_css_parser_error_value (parser, "Color stop offset must be <= 1");
+ else
+ {
+ g_array_append_val (stops, stop);
+ continue;
+ }
- gsk_render_node_unref (start_child);
- gsk_render_node_unref (end_child);
+ g_array_free (stops, TRUE);
+ return FALSE;
}
- else if (gsk_css_token_is_ident (p->cur, "clip"))
+ while (gsk_css_parser_consume_if (parser, GSK_CSS_TOKEN_COMMA));
+
+ if (stops->len < 2)
{
- double clip_rect[4];
- GskRenderNode *child;
+ gsk_css_parser_error_value (parser, "At least 2 color stops need to be specified");
+ g_array_free (stops, TRUE);
+ return FALSE;
+ }
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ if (*(GArray **) out_stops)
+ g_array_free (*(GArray **) out_stops, TRUE);
+ *(GArray **) out_stops = stops;
- parse_double4_param (p, "clip", clip_rect);
- child = parse_node (p);
+ return parse_semicolon (parser);
+}
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_clip_node_new (child,
- &GRAPHENE_RECT_INIT (
- clip_rect[0], clip_rect[1],
- clip_rect[2], clip_rect[3]
- ));
+static gboolean
+parse_node (GskCssParser *parser, gpointer out_node);
- gsk_render_node_unref (child);
- }
- else if (gsk_css_token_is_ident (p->cur, "rounded_clip"))
+static GskRenderNode *
+parse_container_node (GskCssParser *parser)
+{
+ GskRenderNode *node;
+ GPtrArray *nodes;
+ const GskCssToken *token;
+
+ nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref);
+
+ for (token = gsk_css_parser_get_token (parser);
+ !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+ token = gsk_css_parser_get_token (parser))
{
- GskRoundedRect clip_rect;
- GskRenderNode *child;
+ node = NULL;
+ if (parse_node (parser, &node))
+ {
+ g_ptr_array_add (nodes, node);
+ }
+ else
+ {
+ gsk_css_parser_skip_until (parser, GSK_CSS_TOKEN_OPEN_CURLY);
+ gsk_css_parser_skip (parser);
+ }
+ }
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ node = gsk_container_node_new ((GskRenderNode **) nodes->pdata, nodes->len);
- parse_rounded_rect_param (p, "clip", &clip_rect);
- child = parse_node (p);
+ g_ptr_array_unref (nodes);
+ return node;
+}
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_rounded_clip_node_new (child, &clip_rect);
+static void
+parse_declarations_sync (GskCssParser *parser)
+{
+ const GskCssToken *token;
- gsk_render_node_unref (child);
- }
- else if (gsk_css_token_is_ident (p->cur, "linear_gradient"))
+ for (token = gsk_css_parser_get_token (parser);
+ !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+ token = gsk_css_parser_get_token (parser))
{
- GArray *stops = g_array_new (FALSE, TRUE, sizeof (GskColorStop));
- double bounds[4];
- double start[2];
- double end[2];
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- parse_double4_param (p, "bounds", bounds);
- parse_tuple_param (p, "start", start);
- parse_tuple_param (p, "end", end);
-
- expect_skip_ident (p, "stops");
- expect_skip (p, GSK_CSS_TOKEN_COLON);
- while (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
+ if (gsk_css_token_is (token, GSK_CSS_TOKEN_SEMICOLON) ||
+ gsk_css_token_is (token, GSK_CSS_TOKEN_OPEN_CURLY))
{
- GskColorStop stop;
- double color[4];
-
- skip (p);
- stop.offset = number_value (p);
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_COMMA);
- parse_double4 (p, color);
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_PARENS);
-
- stop.color = (GdkRGBA) { color[0], color[1], color[2], color[3] };
- g_array_append_val (stops, stop);
+ gsk_css_parser_skip (parser);
+ break;
}
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_linear_gradient_node_new (&GRAPHENE_RECT_INIT (
- bounds[0], bounds[1],
- bounds[2], bounds[3]
- ),
- &(graphene_point_t) { start[0], start[1] },
- &(graphene_point_t) { end[0], end[1] },
- (GskColorStop *)stops->data,
- stops->len);
- g_array_free (stops, TRUE);
+ gsk_css_parser_skip (parser);
}
- else if (gsk_css_token_is_ident (p->cur, "transform"))
- {
- GskTransform *transform;
- GskRenderNode *child;
+}
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- expect_skip_ident (p, "transform");
- expect_skip (p, GSK_CSS_TOKEN_COLON);
+static guint
+parse_declarations (GskCssParser *parser,
+ const Declaration *declarations,
+ guint n_declarations)
+{
+ guint parsed = 0;
+ guint i;
+ const GskCssToken *token;
- if (p->cur->type == GSK_CSS_TOKEN_OPEN_PARENS)
- {
- graphene_matrix_t matrix;
- parse_matrix (p, &matrix);
+ g_assert (n_declarations < 8 * sizeof (guint));
- transform = gsk_transform_matrix (NULL, &matrix);
- }
- else
+ for (token = gsk_css_parser_get_token (parser);
+ !gsk_css_token_is (token, GSK_CSS_TOKEN_EOF);
+ token = gsk_css_parser_get_token (parser))
+ {
+ for (i = 0; i < n_declarations; i++)
{
- expect (p, GSK_CSS_TOKEN_IDENT);
-
- for (transform = NULL;;)
+ if (gsk_css_token_is_ident (token, declarations[i].name))
{
- /* Transform name */
- expect (p, GSK_CSS_TOKEN_IDENT);
-
- if (lookahead (p, 1)->type == GSK_CSS_TOKEN_OPEN_CURLY) /* Start of child node */
- break;
-
- if (gsk_css_token_is_ident (p->cur, "translate"))
+ gsk_css_parser_consume_token (parser);
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is (token, GSK_CSS_TOKEN_COLON))
{
- double offset[2];
- skip (p);
- parse_tuple (p, offset);
- transform = gsk_transform_translate (transform,
- &(graphene_point_t) { offset[0], offset[1] });
-
+ gsk_css_parser_error_syntax (parser, "Expected ':' after variable declaration");
+ parse_declarations_sync (parser);
}
else
{
- g_error ("Unknown transform type: %s", gsk_css_token_to_string (p->cur));
+ gsk_css_parser_consume_token (parser);
+ if (parsed & (1 << i))
+ gsk_css_parser_warn_syntax (parser, "Variable \"%s\" defined multiple times",
declarations[i].name);
+ if (declarations[i].parse_func (parser, declarations[i].result))
+ parsed |= (1 << i);
+ else
+ parse_declarations_sync (parser);
}
+ break;
}
}
+ if (i == n_declarations)
+ {
+ if (gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+ gsk_css_parser_error_syntax (parser, "No variable named \"%s\"", token->string.string);
+ else
+ gsk_css_parser_error_syntax (parser, "Expected a variable name");
+ parse_declarations_sync (parser);
+ }
+ }
- child = parse_node (p);
+ return parsed;
+}
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_transform_node_new (child, transform);
+static GskRenderNode *
+parse_color_node (GskCssParser *parser)
+{
+ graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+ GdkRGBA color = { 0, 0, 0, 0 };
+ const Declaration declarations[] = {
+ { "bounds", parse_rect, &bounds },
+ { "color", parse_color, &color },
+ };
- gsk_transform_unref (transform);
- gsk_render_node_unref (child);
- }
- else if (gsk_css_token_is_ident (p->cur, "color_matrix"))
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+ return gsk_color_node_new (&color, &bounds);
+}
+
+static GskRenderNode *
+parse_linear_gradient_node (GskCssParser *parser)
+{
+ graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+ graphene_point_t start = GRAPHENE_POINT_INIT (0, 0);
+ graphene_point_t end = GRAPHENE_POINT_INIT (0, 0);
+ GArray *stops = NULL;
+ const Declaration declarations[] = {
+ { "bounds", parse_rect, &bounds },
+ { "start", parse_point, &start },
+ { "end", parse_point, &end },
+ { "stops", parse_stops, &stops },
+ };
+ GskRenderNode *result;
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (stops == NULL)
{
- double offset_values[4];
- graphene_matrix_t matrix;
- graphene_vec4_t offset;
- GskRenderNode *child;
+ gsk_css_parser_error_syntax (parser, "No color stops given");
+ return NULL;
+ }
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ result = gsk_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
- parse_matrix_param (p, "matrix", &matrix);
- parse_double4_param (p, "offset", offset_values);
+ g_array_free (stops, TRUE);
- graphene_vec4_init (&offset,
- offset_values[0],
- offset_values[1],
- offset_values[2],
- offset_values[3]);
+ return result;
+}
- child = parse_node (p);
+static GskRenderNode *
+parse_inset_shadow_node (GskCssParser *parser)
+{
+ GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+ GdkRGBA color = { 0, 0, 0, 0 };
+ double dx, dy, blur, spread;
+ const Declaration declarations[] = {
+ { "outline", parse_rounded_rect, &outline },
+ { "color", parse_color, &color },
+ { "dx", parse_double, &dx },
+ { "dy", parse_double, &dy },
+ { "spread", parse_double, &spread },
+ { "blur", parse_double, &blur }
+ };
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+ return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_color_matrix_node_new (child, &matrix, &offset);
+static GskRenderNode *
+parse_outset_shadow_node (GskCssParser *parser)
+{
+ GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+ GdkRGBA color = { 0, 0, 0, 0 };
+ double dx, dy, blur, spread;
+ const Declaration declarations[] = {
+ { "outline", parse_rounded_rect, &outline },
+ { "color", parse_color, &color },
+ { "dx", parse_double, &dx },
+ { "dy", parse_double, &dy },
+ { "spread", parse_double, &spread },
+ { "blur", parse_double, &blur }
+ };
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+
+ return gsk_outset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
+}
- gsk_render_node_unref (child);
- }
- else if (gsk_css_token_is_ident (p->cur, "texture"))
+static GskRenderNode *
+parse_opacity_node (GskCssParser *parser)
+{
+ GskRenderNode *child = NULL;
+ double opacity = 1.0;
+ const Declaration declarations[] = {
+ { "opacity", parse_double, &opacity },
+ { "child", parse_node, &child },
+ };
+ GskRenderNode *result;
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (child == NULL)
{
- G_GNUC_UNUSED guchar *texture_data;
- gsize texture_data_len;
- GdkTexture *texture;
- double bounds[4];
+ gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+ return NULL;
+ }
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ result = gsk_opacity_node_new (child, opacity);
- parse_double4_param (p, "bounds", bounds);
+ gsk_render_node_unref (child);
- expect_skip (p, GSK_CSS_TOKEN_IDENT);
- expect_skip (p, GSK_CSS_TOKEN_COLON);
- expect (p, GSK_CSS_TOKEN_STRING);
+ return result;
+}
- texture_data = g_base64_decode (p->cur->string.string, &texture_data_len);
- guchar data[] = {1, 0, 0, 1, 0, 0,
- 0, 0, 1, 0, 0, 1};
- GBytes *b = g_bytes_new_static (data, 12);
+static GskRenderNode *
+parse_cross_fade_node (GskCssParser *parser)
+{
+ GskRenderNode *start = NULL;
+ GskRenderNode *end = NULL;
+ double progress = 0.5;
+ const Declaration declarations[] = {
+ { "progress", parse_double, &progress },
+ { "start", parse_node, &start },
+ { "end", parse_node, &end },
+ };
+ GskRenderNode *result;
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (start == NULL || end == NULL)
+ {
+ if (start == NULL)
+ gsk_css_parser_error_syntax (parser, "Missing \"start\" property definition");
+ if (end == NULL)
+ gsk_css_parser_error_syntax (parser, "Missing \"end\" property definition");
+ g_clear_pointer (&start, gsk_render_node_unref);
+ g_clear_pointer (&end, gsk_render_node_unref);
+ return NULL;
+ }
- /* TODO: :( */
- texture = gdk_memory_texture_new (2, 2, GDK_MEMORY_R8G8B8,
- b, 6);
+ result = gsk_cross_fade_node_new (start, end, progress);
- expect_skip (p, GSK_CSS_TOKEN_STRING);
+ gsk_render_node_unref (start);
+ gsk_render_node_unref (end);
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_texture_node_new (texture,
- &GRAPHENE_RECT_INIT (
- bounds[0], bounds[1],
- bounds[2], bounds[3]
- ));
- }
- else if (gsk_css_token_is_ident (p->cur, "inset_shadow"))
+ return result;
+}
+
+static GskRenderNode *
+parse_clip_node (GskCssParser *parser)
+{
+ graphene_rect_t clip = GRAPHENE_RECT_INIT (0, 0, 0, 0);
+ GskRenderNode *child = NULL;
+ const Declaration declarations[] = {
+ { "clip", parse_rect, &clip },
+ { "child", parse_node, &child },
+ };
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (child == NULL)
{
- GskRoundedRect outline;
- double color[4];
- float dx, dy, spread, blur_radius;
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
- parse_rounded_rect_param (p, "outline", &outline);
-
- parse_double4_param (p, "color", color);
- dx = parse_float_param (p, "dx");
- dy = parse_float_param (p, "dy");
- spread = parse_float_param (p, "spread");
- blur_radius = parse_float_param (p, "blur_radius");
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_inset_shadow_node_new (&outline,
- &(GdkRGBA) { color[0], color[1], color[2], color[3] },
- dx, dy,
- spread,
- blur_radius);
+ gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+ return NULL;
}
- else if (gsk_css_token_is_ident (p->cur, "border"))
+
+ return gsk_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_rounded_clip_node (GskCssParser *parser)
+{
+ GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
+ GskRenderNode *child = NULL;
+ const Declaration declarations[] = {
+ { "clip", parse_rounded_rect, &clip },
+ { "child", parse_node, &child },
+ };
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (child == NULL)
{
- GskRoundedRect outline;
- double widths[4];
- double colors[4][4];
-
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
-
- parse_rounded_rect_param (p, "outline", &outline);
- parse_double4_param (p, "widths", widths);
-
- expect_skip_ident (p, "colors");
- expect_skip (p, GSK_CSS_TOKEN_COLON);
-
- parse_double4 (p, colors[0]);
- parse_double4 (p, colors[1]);
- parse_double4 (p, colors[2]);
- parse_double4 (p, colors[3]);
-
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
- result = gsk_border_node_new (&outline,
- (float[4]) { widths[0], widths[1], widths[2], widths[3] },
- (GdkRGBA[4]) {
- (GdkRGBA) { colors[0][0], colors[0][1], colors[0][2], colors[0][3] },
- (GdkRGBA) { colors[1][0], colors[1][1], colors[1][2], colors[1][3] },
- (GdkRGBA) { colors[2][0], colors[2][1], colors[2][2], colors[2][3] },
- (GdkRGBA) { colors[3][0], colors[3][1], colors[3][2], colors[3][3] },
- });
+ gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+ return NULL;
}
- else if (gsk_css_token_is_ident (p->cur, "text"))
+
+ return gsk_rounded_clip_node_new (child, &clip);
+}
+
+static GskRenderNode *
+parse_debug_node (GskCssParser *parser)
+{
+ char *message = NULL;
+ GskRenderNode *child = NULL;
+ const Declaration declarations[] = {
+ { "message", parse_string, &message},
+ { "child", parse_node, &child },
+ };
+
+ parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
+ if (child == NULL)
{
- skip (p);
- expect_skip (p, GSK_CSS_TOKEN_OPEN_CURLY);
+ gsk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
+ return NULL;
+ }
- expect_skip (p, GSK_CSS_TOKEN_CLOSE_CURLY);
+ return gsk_debug_node_new (child, message);
+}
- result = gsk_color_node_new (
- &(GdkRGBA) { 0, 1, 0, 1 },
- &GRAPHENE_RECT_INIT (0, 0, 0, 0));
+static gboolean
+parse_node (GskCssParser *parser,
+ gpointer out_node)
+{
+ static struct {
+ const char *name;
+ GskRenderNode * (* func) (GskCssParser *);
+ } node_parsers[] = {
+ { "container", parse_container_node },
+ { "color", parse_color_node },
+#if 0
+ { "cairo", parse_cairo_node },
+#endif
+ { "linear-gradient", parse_linear_gradient_node },
+#if 0
+ { "border", parse_border_node },
+ { "texture", parse_texture_node },
+#endif
+ { "inset-shadow", parse_inset_shadow_node },
+ { "outset-shadow", parse_outset_shadow_node },
+#if 0
+ { "transform", parse_transform_node },
+#endif
+ { "opacity", parse_opacity_node },
+#if 0
+ { "color-matrix", parse_color-matrix_node },
+ { "repeat", parse_repeat_node },
+#endif
+ { "clip", parse_clip_node },
+ { "rounded-clip", parse_rounded_clip_node },
+#if 0
+ { "shadow", parse_shadow_node },
+ { "blend", parse_blend_node },
+#endif
+ { "cross-fade", parse_cross_fade_node },
+#if 0
+ { "text", parse_text_node },
+ { "blur", parse_blur_node },
+#endif
+ { "debug", parse_debug_node }
+ };
+ const GskCssToken *token;
+ guint i;
+
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is (token, GSK_CSS_TOKEN_IDENT))
+ {
+ gsk_css_parser_error_syntax (parser, "Expected a node name");
+ return FALSE;
}
- else
+
+ for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
{
- g_error ("Unknown render node type: %s", gsk_css_token_to_string (p->cur));
+ if (gsk_css_token_is_ident (token, node_parsers[i].name))
+ {
+ GskRenderNode *node;
+
+ gsk_css_parser_consume_token (parser);
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is (token, GSK_CSS_TOKEN_OPEN_CURLY))
+ {
+ gsk_css_parser_error_syntax (parser, "Expected '{' after node name");
+ return FALSE;
+ }
+ gsk_css_parser_start_block (parser);
+ node = node_parsers[i].func (parser);
+ if (node)
+ {
+ token = gsk_css_parser_get_token (parser);
+ if (!gsk_css_token_is (token, GSK_CSS_TOKEN_EOF))
+ gsk_css_parser_error_syntax (parser, "Expected '}' at end of node definition");
+ g_clear_pointer ((GskRenderNode **) out_node, gsk_render_node_unref);
+ *(GskRenderNode **) out_node = node;
+ }
+ gsk_css_parser_end_block (parser);
+
+ return node != NULL;
+ }
}
- return result;
+ gsk_css_parser_error_value (parser, "\"%s\" is not a valid node name", token->string.string);
+ return FALSE;
+}
+
+static void
+gsk_render_node_parser_error (GskCssParser *parser,
+ const GskCssLocation *location,
+ const GskCssToken *token,
+ const GError *error,
+ gpointer user_data)
+{
+ g_print ("ERROR: %zu:%zu: %s\n",
+ location->lines, location->line_chars,
+ error->message);
}
/**
@@ -672,16 +845,15 @@ GskRenderNode *
gsk_render_node_deserialize_from_bytes (GBytes *bytes)
{
GskRenderNode *root = NULL;
- GskCssToken *tokens;
- int n_tokens;
- Parser parser;
-
- tokens = tokenize (bytes, &n_tokens);
+ GskCssParser *parser;
- parser_init (&parser, tokens, n_tokens);
- root = parse_node (&parser);
+ parser = gsk_css_parser_new (gsk_render_node_parser_error,
+ NULL,
+ NULL);
+ gsk_css_parser_add_bytes (parser, bytes);
+ root = parse_container_node (parser);
- g_free (tokens);
+ gsk_css_parser_unref (parser);
return root;
}
diff --git a/gsk/meson.build b/gsk/meson.build
index f316b3f2f6..37882ac164 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -31,6 +31,7 @@ gsk_public_sources = files([
gsk_private_sources = files([
'gskcairoblur.c',
'gskcairorenderer.c',
+ 'gskcssparser.c',
'gskcsstokenizer.c',
'gskdebug.c',
'gskprivate.c',
diff --git a/gtk/gtkcssprovider.h b/gtk/gtkcssprovider.h
index 9f05714173..cebd41bdd7 100644
--- a/gtk/gtkcssprovider.h
+++ b/gtk/gtkcssprovider.h
@@ -30,6 +30,39 @@ G_BEGIN_DECLS
#define GTK_IS_CSS_PROVIDER_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), GTK_TYPE_CSS_PROVIDER))
#define GTK_CSS_PROVIDER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_CSS_PROVIDER,
GtkCssProviderClass))
+/**
+ * GTK_CSS_PROVIDER_ERROR:
+ *
+ * Domain for #GtkCssProvider errors.
+ */
+#define GTK_CSS_PROVIDER_ERROR (gtk_css_provider_error_quark ())
+
+/**
+ * GtkCssProviderError:
+ * @GTK_CSS_PROVIDER_ERROR_FAILED: Failed.
+ * @GTK_CSS_PROVIDER_ERROR_SYNTAX: Syntax error.
+ * @GTK_CSS_PROVIDER_ERROR_IMPORT: Import error.
+ * @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.
+ */
+typedef enum
+{
+ GTK_CSS_PROVIDER_ERROR_FAILED,
+ GTK_CSS_PROVIDER_ERROR_SYNTAX,
+ GTK_CSS_PROVIDER_ERROR_IMPORT,
+ GTK_CSS_PROVIDER_ERROR_NAME,
+ GTK_CSS_PROVIDER_ERROR_DEPRECATED,
+ GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
+ GTK_CSS_PROVIDER_WARN_GENERAL,
+} GtkCssProviderError;
+
+GDK_AVAILABLE_IN_ALL
+GQuark gtk_css_provider_error_quark (void);
+
typedef struct _GtkCssProvider GtkCssProvider;
typedef struct _GtkCssProviderClass GtkCssProviderClass;
typedef struct _GtkCssProviderPrivate GtkCssProviderPrivate;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]