/* * Copyright © 2016 Red Hat Inc. * * 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 . * * Authors: Benjamin Otte */ #include "config.h" #include "gtkcsstokensourceprivate.h" #include "gtkcssnumbervalueprivate.h" #include "gtkcssprovider.h" typedef struct _GtkCssTokenSourceTokenizer GtkCssTokenSourceTokenizer; struct _GtkCssTokenSourceTokenizer { GtkCssTokenSource parent; GtkCssTokenizer *tokenizer; GFile *location; GtkCssToken current_token; }; static void gtk_css_token_source_tokenizer_finalize (GtkCssTokenSource *source) { GtkCssTokenSourceTokenizer *tok = (GtkCssTokenSourceTokenizer *) source; gtk_css_token_clear (&tok->current_token); gtk_css_tokenizer_unref (tok->tokenizer); if (tok->location) g_object_unref (tok->location); } static void gtk_css_token_source_tokenizer_consume_token (GtkCssTokenSource *source, GObject *consumer) { GtkCssTokenSourceTokenizer *tok = (GtkCssTokenSourceTokenizer *) source; gtk_css_token_clear (&tok->current_token); } static const GtkCssToken * gtk_css_token_source_tokenizer_peek_token (GtkCssTokenSource *source) { GtkCssTokenSourceTokenizer *tok = (GtkCssTokenSourceTokenizer *) source; if (gtk_css_token_is (&tok->current_token, GTK_CSS_TOKEN_EOF)) { gtk_css_tokenizer_read_token (tok->tokenizer, &tok->current_token); #if 0 if (!gtk_css_token_is (&tok->current_token, GTK_CSS_TOKEN_EOF)) { char *s = gtk_css_token_to_string (&tok->current_token); g_print ("%3zu:%02zu %2d %s\n", gtk_css_tokenizer_get_line (tok->tokenizer), gtk_css_tokenizer_get_line_char (tok->tokenizer), tok->current_token.type, s); g_free (s); } #endif } return &tok->current_token; } static void gtk_css_token_source_tokenizer_error (GtkCssTokenSource *source, const GError *error) { GtkCssTokenSourceTokenizer *tok = (GtkCssTokenSourceTokenizer *) source; const GtkCssLocation *pos = gtk_css_tokenizer_get_location (tok->tokenizer); /* XXX */ g_print ("ERROR: %zu:%zu: %s\n", pos->lines, pos->line_chars, error->message); } static GFile * gtk_css_token_source_tokenizer_get_location (GtkCssTokenSource *source) { GtkCssTokenSourceTokenizer *tok = (GtkCssTokenSourceTokenizer *) source; return tok->location; } const GtkCssTokenSourceClass GTK_CSS_TOKEN_SOURCE_TOKENIZER = { gtk_css_token_source_tokenizer_finalize, gtk_css_token_source_tokenizer_consume_token, gtk_css_token_source_tokenizer_peek_token, gtk_css_token_source_tokenizer_error, gtk_css_token_source_tokenizer_get_location, }; GtkCssTokenSource * gtk_css_token_source_new_for_tokenizer (GtkCssTokenizer *tokenizer, GFile *location) { GtkCssTokenSourceTokenizer *source; g_return_val_if_fail (tokenizer != NULL, NULL); g_return_val_if_fail (location == NULL || G_IS_FILE (location), NULL); source = gtk_css_token_source_new (GtkCssTokenSourceTokenizer, >K_CSS_TOKEN_SOURCE_TOKENIZER); source->tokenizer = gtk_css_tokenizer_ref (tokenizer); if (location) source->location = g_object_ref (location); return &source->parent; } typedef struct _GtkCssTokenSourcePart GtkCssTokenSourcePart; struct _GtkCssTokenSourcePart { GtkCssTokenSource parent; GtkCssTokenSource *source; GtkCssTokenType end_type; GSList *blocks; /* of GPOINTER_TO_UINT(GtkCssTokenType) */ }; static void gtk_css_token_source_part_finalize (GtkCssTokenSource *source) { GtkCssTokenSourcePart *part = (GtkCssTokenSourcePart *) source; gtk_css_token_source_unref (part->source); g_slist_free (part->blocks); } static void gtk_css_token_source_part_consume_token (GtkCssTokenSource *source, GObject *consumer) { GtkCssTokenSourcePart *part = (GtkCssTokenSourcePart *) source; const GtkCssToken *token; token = gtk_css_token_source_peek_token (part->source); if (part->blocks) { if (token->type == GPOINTER_TO_UINT (part->blocks->data)) part->blocks = g_slist_remove (part->blocks, part->blocks->data); } else if (gtk_css_token_is (token, part->end_type)) { return; } switch (token->type) { case GTK_CSS_TOKEN_FUNCTION: case GTK_CSS_TOKEN_OPEN_PARENS: part->blocks = g_slist_prepend (part->blocks, GUINT_TO_POINTER (GTK_CSS_TOKEN_CLOSE_PARENS)); break; case GTK_CSS_TOKEN_OPEN_SQUARE: part->blocks = g_slist_prepend (part->blocks, GUINT_TO_POINTER (GTK_CSS_TOKEN_CLOSE_SQUARE)); break; case GTK_CSS_TOKEN_OPEN_CURLY: part->blocks = g_slist_prepend (part->blocks, GUINT_TO_POINTER (GTK_CSS_TOKEN_CLOSE_CURLY)); break; default: break; } gtk_css_token_source_consume_token_as (part->source, consumer); } static const GtkCssToken * gtk_css_token_source_part_peek_token (GtkCssTokenSource *source) { GtkCssTokenSourcePart *part = (GtkCssTokenSourcePart *) source; static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF }; const GtkCssToken *token; token = gtk_css_token_source_peek_token (part->source); if (part->blocks == NULL && gtk_css_token_is (token, part->end_type)) return &eof_token; return token; } static void gtk_css_token_source_part_error (GtkCssTokenSource *source, const GError *error) { GtkCssTokenSourcePart *part = (GtkCssTokenSourcePart *) source; gtk_css_token_source_emit_error (part->source, error); } static GFile * gtk_css_token_source_part_get_location (GtkCssTokenSource *source) { GtkCssTokenSourcePart *part = (GtkCssTokenSourcePart *) source; return gtk_css_token_source_get_location (part->source); } const GtkCssTokenSourceClass GTK_CSS_TOKEN_SOURCE_PART = { gtk_css_token_source_part_finalize, gtk_css_token_source_part_consume_token, gtk_css_token_source_part_peek_token, gtk_css_token_source_part_error, gtk_css_token_source_part_get_location, }; GtkCssTokenSource * gtk_css_token_source_new_for_part (GtkCssTokenSource *source, GtkCssTokenType end_type) { GtkCssTokenSourcePart *part; g_return_val_if_fail (source != NULL, NULL); g_return_val_if_fail (end_type != GTK_CSS_TOKEN_EOF, NULL); part = gtk_css_token_source_new (GtkCssTokenSourcePart, >K_CSS_TOKEN_SOURCE_PART); part->source = gtk_css_token_source_ref (source); part->end_type = end_type; gtk_css_token_source_set_consumer (&part->parent, gtk_css_token_source_get_consumer (source)); return &part->parent; } GtkCssTokenSource * gtk_css_token_source_alloc (gsize struct_size, const GtkCssTokenSourceClass *klass) { GtkCssTokenSource *source; source = g_malloc0 (struct_size); source->klass = klass; source->ref_count = 1; return source; } GtkCssTokenSource * gtk_css_token_source_ref (GtkCssTokenSource *source) { source->ref_count++; return source; } void gtk_css_token_source_unref (GtkCssTokenSource *source) { source->ref_count--; if (source->ref_count > 0) return; source->klass->finalize (source); g_clear_object (&source->consumer); g_free (source); } void gtk_css_token_source_consume_token (GtkCssTokenSource *source) { gtk_css_token_source_consume_token_as (source, source->consumer); } void gtk_css_token_source_consume_token_as (GtkCssTokenSource *source, GObject *consumer) { source->klass->consume_token (source, consumer); } const GtkCssToken * gtk_css_token_source_peek_token (GtkCssTokenSource *source) { return source->klass->peek_token (source); } const GtkCssToken * gtk_css_token_source_get_token (GtkCssTokenSource *source) { const GtkCssToken *token; for (token = gtk_css_token_source_peek_token (source); gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) || gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE); token = gtk_css_token_source_peek_token (source)) { gtk_css_token_source_consume_token (source); } return token; } void gtk_css_token_source_consume_all (GtkCssTokenSource *source) { const GtkCssToken *token; for (token = gtk_css_token_source_get_token (source); !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF); token = gtk_css_token_source_get_token (source)) { gtk_css_token_source_consume_token (source); } } char * gtk_css_token_source_consume_to_string (GtkCssTokenSource *source) { GString *string; const GtkCssToken *token; string = g_string_new (NULL); for (token = gtk_css_token_source_peek_token (source); !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF); token = gtk_css_token_source_peek_token (source)) { if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT)) continue; gtk_css_token_print (token, string); gtk_css_token_source_consume_token (source); } return g_string_free (string, FALSE); } gboolean gtk_css_token_source_consume_function (GtkCssTokenSource *source, guint min_args, guint max_args, guint (* parse_func) (GtkCssTokenSource *, guint, gpointer), gpointer data) { const GtkCssToken *token; GtkCssTokenSource *func_source; gboolean result = FALSE; char *function_name; guint arg; token = gtk_css_token_source_get_token (source); g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE); function_name = g_strdup (token->string.string); gtk_css_token_source_consume_token (source); func_source = gtk_css_token_source_new_for_part (source, GTK_CSS_TOKEN_CLOSE_PARENS); arg = 0; while (arg < max_args) { guint parse_args = parse_func (func_source, arg, data); if (parse_args == 0) { gtk_css_token_source_consume_all (func_source); break; } arg += parse_args; token = gtk_css_token_source_get_token (func_source); if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) { if (arg < min_args) { gtk_css_token_source_error (source, "%s() requires at least %u arguments", function_name, min_args); gtk_css_token_source_consume_all (source); } else { result = TRUE; } break; } else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA)) { gtk_css_token_source_consume_token (func_source); continue; } else { gtk_css_token_source_error (func_source, "Unexpected data at end of %s() argument", function_name); gtk_css_token_source_consume_all (func_source); break; } } gtk_css_token_source_unref (func_source); token = gtk_css_token_source_get_token (source); if (!gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_PARENS)) { gtk_css_token_source_error (source, "Expected ')' at end of %s()", function_name); gtk_css_token_source_consume_all (source); g_free (function_name); return FALSE; } gtk_css_token_source_consume_token (source); g_free (function_name); return result; } gboolean gtk_css_token_source_consume_number (GtkCssTokenSource *source, double *number) { const GtkCssToken *token; GtkCssValue *value; token = gtk_css_token_source_get_token (source); 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_token_source_consume_token (source); return TRUE; } /* because CSS allows calc() in numbers. Go CSS! */ value = gtk_css_number_value_token_parse (source, GTK_CSS_PARSE_NUMBER); if (value == NULL) return FALSE; *number = _gtk_css_number_value_get (value, 100); _gtk_css_value_unref (value); return TRUE; } gboolean gtk_css_token_source_consume_integer (GtkCssTokenSource *source, int *number) { const GtkCssToken *token; token = gtk_css_token_source_get_token (source); 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_token_source_consume_token (source); return TRUE; } /* XXX: parse calc() */ gtk_css_token_source_error (source, "Expected an integer"); gtk_css_token_source_consume_all (source); return FALSE; } GFile * gtk_css_token_source_resolve_url (GtkCssTokenSource *source, const char *url) { char *scheme; GFile *file, *location, *base; scheme = g_uri_parse_scheme (url); if (scheme != NULL) { file = g_file_new_for_uri (url); g_free (scheme); return file; } location = gtk_css_token_source_get_location (source); if (location) { base = g_file_get_parent (location); } else { char *dir = g_get_current_dir (); base = g_file_new_for_path (dir); g_free (dir); } file = g_file_resolve_relative_path (base, url); g_object_unref (base); return file; } GFile * gtk_css_token_source_consume_url (GtkCssTokenSource *source) { const GtkCssToken *token; GFile *file; token = gtk_css_token_source_get_token (source); if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL)) { file = gtk_css_token_source_resolve_url (source, token->string.string); gtk_css_token_source_consume_token (source); return file; } else if (gtk_css_token_is_function (token, "url")) { gtk_css_token_source_consume_token (source); token = gtk_css_token_source_get_token (source); if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING)) { gtk_css_token_source_error (source, "Expected string inside url()"); gtk_css_token_source_consume_all (source); return NULL; } file = gtk_css_token_source_resolve_url (source, token->string.string); gtk_css_token_source_consume_token (source); token = gtk_css_token_source_get_token (source); if (!gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_PARENS)) { gtk_css_token_source_error (source, "Expected closing ')' for url()"); gtk_css_token_source_consume_all (source); g_object_unref (file); return NULL; } gtk_css_token_source_consume_token (source); return file; } else { gtk_css_token_source_error (source, "Expected url()"); gtk_css_token_source_consume_all (source); return NULL; } } void gtk_css_token_source_emit_error (GtkCssTokenSource *source, const GError *error) { source->klass->error (source, error); } void gtk_css_token_source_error (GtkCssTokenSource *source, 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); gtk_css_token_source_emit_error (source, error); g_error_free (error); va_end (args); } void gtk_css_token_source_unknown (GtkCssTokenSource *source, 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); gtk_css_token_source_emit_error (source, error); g_error_free (error); va_end (args); } void gtk_css_token_source_deprecated (GtkCssTokenSource *source, 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_DEPRECATED, format, args); gtk_css_token_source_emit_error (source, error); g_error_free (error); va_end (args); } GFile * gtk_css_token_source_get_location (GtkCssTokenSource *source) { return source->klass->get_location (source); } GObject * gtk_css_token_source_get_consumer (GtkCssTokenSource *source) { return source->consumer; } void gtk_css_token_source_set_consumer (GtkCssTokenSource *source, GObject *consumer) { g_set_object (&source->consumer, consumer); }