1 /*
2  * Copyright © 2019 Benjamin Otte
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19 
20 
21 #include "config.h"
22 
23 #include "gtkcssparserprivate.h"
24 
25 #include "gtkcssenums.h"
26 #include "gtkcsserror.h"
27 #include "gtkcsslocationprivate.h"
28 
29 typedef struct _GtkCssParserBlock GtkCssParserBlock;
30 
31 struct _GtkCssParser
32 {
33   volatile int ref_count;
34 
35   GtkCssTokenizer *tokenizer;
36   GFile *file;
37   GFile *directory;
38   GtkCssParserErrorFunc error_func;
39   gpointer user_data;
40   GDestroyNotify user_destroy;
41 
42   GArray *blocks;
43   GtkCssLocation location;
44   GtkCssToken token;
45 };
46 
47 struct _GtkCssParserBlock
48 {
49   GtkCssLocation start_location;
50   GtkCssTokenType end_token;
51   GtkCssTokenType inherited_end_token;
52   GtkCssTokenType alternative_token;
53 };
54 
55 static GtkCssParser *
gtk_css_parser_new(GtkCssTokenizer * tokenizer,GFile * file,GFile * base_directory,GtkCssParserErrorFunc error_func,gpointer user_data,GDestroyNotify user_destroy)56 gtk_css_parser_new (GtkCssTokenizer       *tokenizer,
57                     GFile                 *file,
58                     GFile                 *base_directory,
59                     GtkCssParserErrorFunc  error_func,
60                     gpointer               user_data,
61                     GDestroyNotify         user_destroy)
62 {
63   GtkCssParser *self;
64 
65   self = g_slice_new0 (GtkCssParser);
66 
67   self->ref_count = 1;
68   self->tokenizer = gtk_css_tokenizer_ref (tokenizer);
69   if (file)
70     self->file = g_object_ref (file);
71   if (base_directory)
72     self->directory = g_object_ref (base_directory);
73   else if (file)
74     self->directory = g_file_get_parent (file);
75   self->error_func = error_func;
76   self->user_data = user_data;
77   self->user_destroy = user_destroy;
78   self->blocks = g_array_new (FALSE, FALSE, sizeof (GtkCssParserBlock));
79 
80   return self;
81 }
82 
83 GtkCssParser *
gtk_css_parser_new_for_file(GFile * file,GtkCssParserErrorFunc error_func,gpointer user_data,GDestroyNotify user_destroy,GError ** error)84 gtk_css_parser_new_for_file (GFile                 *file,
85                              GtkCssParserErrorFunc  error_func,
86                              gpointer               user_data,
87                              GDestroyNotify         user_destroy,
88                              GError               **error)
89 {
90   GBytes *bytes;
91   GtkCssParser *result;
92 
93   bytes = g_file_load_bytes (file, NULL, NULL, error);
94   if (bytes == NULL)
95     return NULL;
96 
97   result = gtk_css_parser_new_for_bytes (bytes, file, NULL, error_func, user_data, user_destroy);
98 
99   g_bytes_unref (bytes);
100 
101   return result;
102 }
103 
104 GtkCssParser *
gtk_css_parser_new_for_bytes(GBytes * bytes,GFile * file,GFile * base_directory,GtkCssParserErrorFunc error_func,gpointer user_data,GDestroyNotify user_destroy)105 gtk_css_parser_new_for_bytes (GBytes                *bytes,
106                               GFile                 *file,
107                               GFile                 *base_directory,
108                               GtkCssParserErrorFunc  error_func,
109                               gpointer               user_data,
110                               GDestroyNotify         user_destroy)
111 {
112   GtkCssTokenizer *tokenizer;
113   GtkCssParser *result;
114 
115   tokenizer = gtk_css_tokenizer_new (bytes);
116   result = gtk_css_parser_new (tokenizer, file, base_directory, error_func, user_data, user_destroy);
117   gtk_css_tokenizer_unref (tokenizer);
118 
119   return result;
120 }
121 
122 static void
gtk_css_parser_finalize(GtkCssParser * self)123 gtk_css_parser_finalize (GtkCssParser *self)
124 {
125   if (self->user_destroy)
126     self->user_destroy (self->user_data);
127 
128   g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref);
129   g_clear_object (&self->file);
130   g_clear_object (&self->directory);
131   if (self->blocks->len)
132     g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len);
133   g_array_free (self->blocks, TRUE);
134 
135   g_slice_free (GtkCssParser, self);
136 }
137 
138 GtkCssParser *
gtk_css_parser_ref(GtkCssParser * self)139 gtk_css_parser_ref (GtkCssParser *self)
140 {
141   g_atomic_int_inc (&self->ref_count);
142 
143   return self;
144 }
145 
146 void
gtk_css_parser_unref(GtkCssParser * self)147 gtk_css_parser_unref (GtkCssParser *self)
148 {
149   if (g_atomic_int_dec_and_test (&self->ref_count))
150     gtk_css_parser_finalize (self);
151 }
152 
153 /**
154  * gtk_css_parser_get_file:
155  * @self: a `GtkCssParser`
156  *
157  * Gets the file being parsed. If no file is associated with @self -
158  * for example when raw data is parsed - %NULL is returned.
159  *
160  * Returns: (nullable) (transfer none): The file being parsed
161  */
162 GFile *
gtk_css_parser_get_file(GtkCssParser * self)163 gtk_css_parser_get_file (GtkCssParser *self)
164 {
165   return self->file;
166 }
167 
168 /**
169  * gtk_css_parser_resolve_url:
170  * @self: a `GtkCssParser`
171  * @url: the URL to resolve
172  *
173  * Resolves a given URL against the parser's location.
174  *
175  * Returns: (nullable) (transfer full): a new `GFile` for the
176  *   resolved URL
177  */
178 GFile *
gtk_css_parser_resolve_url(GtkCssParser * self,const char * url)179 gtk_css_parser_resolve_url (GtkCssParser *self,
180                             const char   *url)
181 {
182   char *scheme;
183 
184   scheme = g_uri_parse_scheme (url);
185   if (scheme != NULL)
186     {
187       GFile *file = g_file_new_for_uri (url);
188       g_free (scheme);
189       return file;
190     }
191   g_free (scheme);
192 
193   if (self->directory == NULL)
194     return NULL;
195 
196   return g_file_resolve_relative_path (self->directory, url);
197 }
198 
199 /**
200  * gtk_css_parser_get_start_location:
201  * @self: a `GtkCssParser`
202  *
203  * Queries the location of the current token.
204  *
205  * This function will return the location of the start of the
206  * current token. In the case a token has been consumed, but no
207  * new token has been queried yet via gtk_css_parser_peek_token()
208  * or gtk_css_parser_get_token(), the previous token's start
209  * location will be returned.
210  *
211  * This function may return the same location as
212  * gtk_css_parser_get_end_location() - in particular at the
213  * beginning and end of the document.
214  *
215  * Returns: the start location
216  **/
217 const GtkCssLocation *
gtk_css_parser_get_start_location(GtkCssParser * self)218 gtk_css_parser_get_start_location (GtkCssParser *self)
219 {
220   return &self->location;
221 }
222 
223 /**
224  * gtk_css_parser_get_end_location:
225  * @self: a `GtkCssParser`
226  * @out_location: (caller-allocates) Place to store the location
227  *
228  * Queries the location of the current token.
229  *
230  * This function will return the location of the end of the
231  * current token. In the case a token has been consumed, but no
232  * new token has been queried yet via gtk_css_parser_peek_token()
233  * or gtk_css_parser_get_token(), the previous token's end location
234  * will be returned.
235  *
236  * This function may return the same location as
237  * gtk_css_parser_get_start_location() - in particular at the
238  * beginning and end of the document.
239  *
240  * Returns: the end location
241  **/
242 const GtkCssLocation *
gtk_css_parser_get_end_location(GtkCssParser * self)243 gtk_css_parser_get_end_location (GtkCssParser *self)
244 {
245   return gtk_css_tokenizer_get_location (self->tokenizer);
246 }
247 
248 /**
249  * gtk_css_parser_get_block_location:
250  * @self: a `GtkCssParser`
251  *
252  * Queries the start location of the token that started the current
253  * block that is being parsed.
254  *
255  * If no block is currently parsed, the beginning of the document
256  * is returned.
257  *
258  * Returns: The start location of the current block
259  */
260 const GtkCssLocation *
gtk_css_parser_get_block_location(GtkCssParser * self)261 gtk_css_parser_get_block_location (GtkCssParser *self)
262 {
263   const GtkCssParserBlock *block;
264 
265   if (self->blocks->len == 0)
266     {
267       static const GtkCssLocation start_of_document = { 0, };
268       return &start_of_document;
269     }
270 
271   block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
272   return &block->start_location;
273 }
274 
275 static void
gtk_css_parser_ensure_token(GtkCssParser * self)276 gtk_css_parser_ensure_token (GtkCssParser *self)
277 {
278   GError *error = NULL;
279 
280   if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
281     return;
282 
283   self->location = *gtk_css_tokenizer_get_location (self->tokenizer);
284   if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error))
285     {
286       /* We ignore the error here, because the resulting token will
287        * likely already trigger an error in the parsing code and
288        * duplicate errors are rather useless.
289        */
290       g_clear_error (&error);
291     }
292 }
293 
294 const GtkCssToken *
gtk_css_parser_peek_token(GtkCssParser * self)295 gtk_css_parser_peek_token (GtkCssParser *self)
296 {
297   static const GtkCssToken eof_token = { GTK_CSS_TOKEN_EOF, };
298 
299   gtk_css_parser_ensure_token (self);
300 
301   if (self->blocks->len)
302     {
303       const GtkCssParserBlock *block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
304       if (gtk_css_token_is (&self->token, block->end_token) ||
305           gtk_css_token_is (&self->token, block->inherited_end_token) ||
306           gtk_css_token_is (&self->token, block->alternative_token))
307         return &eof_token;
308     }
309 
310   return &self->token;
311 }
312 
313 const GtkCssToken *
gtk_css_parser_get_token(GtkCssParser * self)314 gtk_css_parser_get_token (GtkCssParser *self)
315 {
316   const GtkCssToken *token;
317 
318   for (token = gtk_css_parser_peek_token (self);
319        gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) ||
320        gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE);
321        token = gtk_css_parser_peek_token (self))
322     {
323       gtk_css_parser_consume_token (self);
324     }
325 
326   return token;
327 }
328 
329 void
gtk_css_parser_consume_token(GtkCssParser * self)330 gtk_css_parser_consume_token (GtkCssParser *self)
331 {
332   gtk_css_parser_ensure_token (self);
333 
334   /* unpreserved tokens MUST be consumed via start_block() */
335   g_assert (gtk_css_token_is_preserved (&self->token, NULL));
336 
337   /* Don't consume any tokens at the end of a block */
338   if (!gtk_css_token_is (gtk_css_parser_peek_token (self), GTK_CSS_TOKEN_EOF))
339     gtk_css_token_clear (&self->token);
340 }
341 
342 void
gtk_css_parser_start_block(GtkCssParser * self)343 gtk_css_parser_start_block (GtkCssParser *self)
344 {
345   GtkCssParserBlock block;
346 
347   gtk_css_parser_ensure_token (self);
348 
349   if (gtk_css_token_is_preserved (&self->token, &block.end_token))
350     {
351       g_critical ("gtk_css_parser_start_block() may only be called for non-preserved tokens");
352       return;
353     }
354 
355   block.inherited_end_token = GTK_CSS_TOKEN_EOF;
356   block.alternative_token = GTK_CSS_TOKEN_EOF;
357   block.start_location = self->location;
358   g_array_append_val (self->blocks, block);
359 
360   gtk_css_token_clear (&self->token);
361 }
362 
363 void
gtk_css_parser_start_semicolon_block(GtkCssParser * self,GtkCssTokenType alternative_token)364 gtk_css_parser_start_semicolon_block (GtkCssParser    *self,
365                                       GtkCssTokenType  alternative_token)
366 {
367   GtkCssParserBlock block;
368 
369   block.end_token = GTK_CSS_TOKEN_SEMICOLON;
370   if (self->blocks->len)
371     block.inherited_end_token = g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1).end_token;
372   else
373     block.inherited_end_token = GTK_CSS_TOKEN_EOF;
374   block.alternative_token = alternative_token;
375   block.start_location = self->location;
376   g_array_append_val (self->blocks, block);
377 }
378 
379 void
gtk_css_parser_end_block_prelude(GtkCssParser * self)380 gtk_css_parser_end_block_prelude (GtkCssParser *self)
381 {
382   GtkCssParserBlock *block;
383 
384   g_return_if_fail (self->blocks->len > 0);
385 
386   block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
387 
388   if (block->alternative_token == GTK_CSS_TOKEN_EOF)
389     return;
390 
391   gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
392 
393   if (gtk_css_token_is (&self->token, block->alternative_token))
394     {
395       if (gtk_css_token_is_preserved (&self->token, &block->end_token))
396         {
397           g_critical ("alternative token is not preserved");
398           return;
399         }
400       block->alternative_token = GTK_CSS_TOKEN_EOF;
401       block->inherited_end_token = GTK_CSS_TOKEN_EOF;
402       gtk_css_token_clear (&self->token);
403     }
404 }
405 
406 void
gtk_css_parser_end_block(GtkCssParser * self)407 gtk_css_parser_end_block (GtkCssParser *self)
408 {
409   GtkCssParserBlock *block;
410 
411   g_return_if_fail (self->blocks->len > 0);
412 
413   gtk_css_parser_skip_until (self, GTK_CSS_TOKEN_EOF);
414 
415   block = &g_array_index (self->blocks, GtkCssParserBlock, self->blocks->len - 1);
416 
417   if (gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF))
418     {
419       gtk_css_parser_warn (self,
420                            GTK_CSS_PARSER_WARNING_SYNTAX,
421                            gtk_css_parser_get_block_location (self),
422                            gtk_css_parser_get_start_location (self),
423                            "Unterminated block at end of document");
424       g_array_set_size (self->blocks, self->blocks->len - 1);
425     }
426   else if (gtk_css_token_is (&self->token, block->inherited_end_token))
427     {
428       g_assert (block->end_token == GTK_CSS_TOKEN_SEMICOLON);
429       gtk_css_parser_warn (self,
430                            GTK_CSS_PARSER_WARNING_SYNTAX,
431                            gtk_css_parser_get_block_location (self),
432                            gtk_css_parser_get_start_location (self),
433                            "Expected ';' at end of block");
434       g_array_set_size (self->blocks, self->blocks->len - 1);
435     }
436   else
437     {
438       g_array_set_size (self->blocks, self->blocks->len - 1);
439       if (gtk_css_token_is_preserved (&self->token, NULL))
440         {
441           gtk_css_token_clear (&self->token);
442         }
443       else
444         {
445           gtk_css_parser_start_block (self);
446           gtk_css_parser_end_block (self);
447         }
448     }
449 }
450 
451 /*
452  * gtk_css_parser_skip:
453  * @self: a `GtkCssParser`
454  *
455  * Skips a component value.
456  *
457  * This means that if the token is a preserved token, only
458  * this token will be skipped. If the token starts a block,
459  * the whole block will be skipped.
460  **/
461 void
gtk_css_parser_skip(GtkCssParser * self)462 gtk_css_parser_skip (GtkCssParser *self)
463 {
464   const GtkCssToken *token;
465 
466   token = gtk_css_parser_get_token (self);
467   if (gtk_css_token_is_preserved (token, NULL))
468     {
469       gtk_css_parser_consume_token (self);
470     }
471   else
472     {
473       gtk_css_parser_start_block (self);
474       gtk_css_parser_end_block (self);
475     }
476 }
477 
478 /*
479  * gtk_css_parser_skip_until:
480  * @self: a `GtkCssParser`
481  * @token_type: type of token to skip to
482  *
483  * Repeatedly skips a token until a certain type is reached.
484  * After this called, gtk_css_parser_get_token() will either
485  * return a token of this type or the eof token.
486  *
487  * This function is useful for resyncing a parser after encountering
488  * an error.
489  *
490  * If you want to skip until the end, use %GSK_TOKEN_TYPE_EOF
491  * as the token type.
492  **/
493 void
gtk_css_parser_skip_until(GtkCssParser * self,GtkCssTokenType token_type)494 gtk_css_parser_skip_until (GtkCssParser    *self,
495                            GtkCssTokenType  token_type)
496 {
497   const GtkCssToken *token;
498 
499   for (token = gtk_css_parser_get_token (self);
500        !gtk_css_token_is (token, token_type) &&
501        !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
502        token = gtk_css_parser_get_token (self))
503     {
504       gtk_css_parser_skip (self);
505     }
506 }
507 
508 void
gtk_css_parser_emit_error(GtkCssParser * self,const GtkCssLocation * start,const GtkCssLocation * end,const GError * error)509 gtk_css_parser_emit_error (GtkCssParser         *self,
510                            const GtkCssLocation *start,
511                            const GtkCssLocation *end,
512                            const GError         *error)
513 {
514   if (self->error_func)
515     self->error_func (self, start, end, error, self->user_data);
516 }
517 
518 void
gtk_css_parser_error(GtkCssParser * self,GtkCssParserError code,const GtkCssLocation * start,const GtkCssLocation * end,const char * format,...)519 gtk_css_parser_error (GtkCssParser         *self,
520                       GtkCssParserError     code,
521                       const GtkCssLocation *start,
522                       const GtkCssLocation *end,
523                       const char           *format,
524                       ...)
525 {
526   va_list args;
527   GError *error;
528 
529   va_start (args, format);
530   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
531                               code,
532                               format, args);
533   gtk_css_parser_emit_error (self, start, end, error);
534   g_error_free (error);
535   va_end (args);
536 }
537 
538 void
gtk_css_parser_error_syntax(GtkCssParser * self,const char * format,...)539 gtk_css_parser_error_syntax (GtkCssParser *self,
540                              const char   *format,
541                              ...)
542 {
543   va_list args;
544   GError *error;
545 
546   va_start (args, format);
547   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
548                               GTK_CSS_PARSER_ERROR_SYNTAX,
549                               format, args);
550   gtk_css_parser_emit_error (self,
551                              gtk_css_parser_get_start_location (self),
552                              gtk_css_parser_get_end_location (self),
553                              error);
554   g_error_free (error);
555   va_end (args);
556 }
557 
558 void
gtk_css_parser_error_value(GtkCssParser * self,const char * format,...)559 gtk_css_parser_error_value (GtkCssParser *self,
560                             const char   *format,
561                             ...)
562 {
563   va_list args;
564   GError *error;
565 
566   va_start (args, format);
567   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
568                               GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
569                               format, args);
570   gtk_css_parser_emit_error (self,
571                              gtk_css_parser_get_start_location (self),
572                              gtk_css_parser_get_end_location (self),
573                              error);
574   g_error_free (error);
575   va_end (args);
576 }
577 
578 void
gtk_css_parser_error_import(GtkCssParser * self,const char * format,...)579 gtk_css_parser_error_import (GtkCssParser *self,
580                              const char   *format,
581                              ...)
582 {
583   va_list args;
584   GError *error;
585 
586   va_start (args, format);
587   error = g_error_new_valist (GTK_CSS_PARSER_ERROR,
588                               GTK_CSS_PARSER_ERROR_IMPORT,
589                               format, args);
590   gtk_css_parser_emit_error (self,
591                              gtk_css_parser_get_start_location (self),
592                              gtk_css_parser_get_end_location (self),
593                              error);
594   g_error_free (error);
595   va_end (args);
596 }
597 
598 void
gtk_css_parser_warn(GtkCssParser * self,GtkCssParserWarning code,const GtkCssLocation * start,const GtkCssLocation * end,const char * format,...)599 gtk_css_parser_warn (GtkCssParser         *self,
600                      GtkCssParserWarning   code,
601                      const GtkCssLocation *start,
602                      const GtkCssLocation *end,
603                      const char           *format,
604                      ...)
605 {
606   va_list args;
607   GError *error;
608 
609   va_start (args, format);
610   error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
611                               code,
612                               format, args);
613   gtk_css_parser_emit_error (self, start, end, error);
614   g_error_free (error);
615   va_end (args);
616 }
617 
618 void
gtk_css_parser_warn_syntax(GtkCssParser * self,const char * format,...)619 gtk_css_parser_warn_syntax (GtkCssParser *self,
620                             const char   *format,
621                             ...)
622 {
623   va_list args;
624   GError *error;
625 
626   va_start (args, format);
627   error = g_error_new_valist (GTK_CSS_PARSER_WARNING,
628                               GTK_CSS_PARSER_WARNING_SYNTAX,
629                               format, args);
630   gtk_css_parser_emit_error (self,
631                              gtk_css_parser_get_start_location (self),
632                              gtk_css_parser_get_end_location (self),
633                              error);
634   g_error_free (error);
635   va_end (args);
636 }
637 
638 gboolean
gtk_css_parser_consume_function(GtkCssParser * self,guint min_args,guint max_args,guint (* parse_func)(GtkCssParser *,guint,gpointer),gpointer data)639 gtk_css_parser_consume_function (GtkCssParser *self,
640                                  guint         min_args,
641                                  guint         max_args,
642                                  guint (* parse_func) (GtkCssParser *, guint, gpointer),
643                                  gpointer      data)
644 {
645   const GtkCssToken *token;
646   gboolean result = FALSE;
647   char *function_name;
648   guint arg;
649 
650   token = gtk_css_parser_get_token (self);
651   g_return_val_if_fail (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION), FALSE);
652 
653   function_name = g_strdup (token->string.string);
654   gtk_css_parser_start_block (self);
655 
656   arg = 0;
657   while (TRUE)
658     {
659       guint parse_args = parse_func (self, arg, data);
660       if (parse_args == 0)
661         break;
662       arg += parse_args;
663       token = gtk_css_parser_get_token (self);
664       if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
665         {
666           if (arg < min_args)
667             {
668               gtk_css_parser_error_syntax (self, "%s() requires at least %u arguments", function_name, min_args);
669               break;
670             }
671           else
672             {
673               result = TRUE;
674               break;
675             }
676         }
677       else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA))
678         {
679           if (arg >= max_args)
680             {
681               gtk_css_parser_error_syntax (self, "Expected ')' at end of %s()", function_name);
682               break;
683             }
684 
685           gtk_css_parser_consume_token (self);
686           continue;
687         }
688       else
689         {
690           gtk_css_parser_error_syntax (self, "Unexpected data at end of %s() argument", function_name);
691           break;
692         }
693     }
694 
695   gtk_css_parser_end_block (self);
696   g_free (function_name);
697 
698   return result;
699 }
700 
701 /**
702  * gtk_css_parser_has_token:
703  * @self: a `GtkCssParser`
704  * @token_type: type of the token to check
705  *
706  * Checks if the next token is of @token_type.
707  *
708  * Returns: %TRUE if the next token is of @token_type
709  **/
710 gboolean
gtk_css_parser_has_token(GtkCssParser * self,GtkCssTokenType token_type)711 gtk_css_parser_has_token (GtkCssParser    *self,
712                           GtkCssTokenType  token_type)
713 {
714   const GtkCssToken *token;
715 
716   token = gtk_css_parser_get_token (self);
717 
718   return gtk_css_token_is (token, token_type);
719 }
720 
721 /**
722  * gtk_css_parser_has_ident:
723  * @self: a `GtkCssParser`
724  * @ident: name of identifier
725  *
726  * Checks if the next token is an identifier with the given @name.
727  *
728  * Returns: %TRUE if the next token is an identifier with the given @name
729  **/
730 gboolean
gtk_css_parser_has_ident(GtkCssParser * self,const char * ident)731 gtk_css_parser_has_ident (GtkCssParser *self,
732                           const char   *ident)
733 {
734   const GtkCssToken *token;
735 
736   token = gtk_css_parser_get_token (self);
737 
738   return gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
739          g_ascii_strcasecmp (token->string.string, ident) == 0;
740 }
741 
742 gboolean
gtk_css_parser_has_integer(GtkCssParser * self)743 gtk_css_parser_has_integer (GtkCssParser *self)
744 {
745   const GtkCssToken *token;
746 
747   token = gtk_css_parser_get_token (self);
748 
749   return gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
750          gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
751 }
752 
753 /**
754  * gtk_css_parser_has_function:
755  * @self: a `GtkCssParser`
756  * @name: name of function
757  *
758  * Checks if the next token is a function with the given @name.
759  *
760  * Returns: %TRUE if the next token is a function with the given @name
761  **/
762 gboolean
gtk_css_parser_has_function(GtkCssParser * self,const char * name)763 gtk_css_parser_has_function (GtkCssParser *self,
764                              const char   *name)
765 {
766   const GtkCssToken *token;
767 
768   token = gtk_css_parser_get_token (self);
769 
770   return gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION) &&
771          g_ascii_strcasecmp (token->string.string, name) == 0;
772 }
773 
774 /**
775  * gtk_css_parser_try_delim:
776  * @self: a `GtkCssParser`
777  * @codepoint: unicode character codepoint to check
778  *
779  * Checks if the current token is a delimiter matching the given
780  * @codepoint. If that is the case, the token is consumed and
781  * %TRUE is returned.
782  *
783  * Keep in mind that not every unicode codepoint can be a delim
784  * token.
785  *
786  * Returns: %TRUE if the token matched and was consumed.
787  **/
788 gboolean
gtk_css_parser_try_delim(GtkCssParser * self,gunichar codepoint)789 gtk_css_parser_try_delim (GtkCssParser *self,
790                           gunichar      codepoint)
791 {
792   const GtkCssToken *token;
793 
794   token = gtk_css_parser_get_token (self);
795 
796   if (!gtk_css_token_is (token, GTK_CSS_TOKEN_DELIM) ||
797       codepoint != token->delim.delim)
798     return FALSE;
799 
800   gtk_css_parser_consume_token (self);
801   return TRUE;
802 }
803 
804 /**
805  * gtk_css_parser_try_ident:
806  * @self: a `GtkCssParser`
807  * @ident: identifier to check for
808  *
809  * Checks if the current token is an identifier matching the given
810  * @ident string. If that is the case, the token is consumed
811  * and %TRUE is returned.
812  *
813  * Returns: %TRUE if the token matched and was consumed.
814  **/
815 gboolean
gtk_css_parser_try_ident(GtkCssParser * self,const char * ident)816 gtk_css_parser_try_ident (GtkCssParser *self,
817                           const char   *ident)
818 {
819   const GtkCssToken *token;
820 
821   token = gtk_css_parser_get_token (self);
822 
823   if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) ||
824       g_ascii_strcasecmp (token->string.string, ident) != 0)
825     return FALSE;
826 
827   gtk_css_parser_consume_token (self);
828   return TRUE;
829 }
830 
831 /**
832  * gtk_css_parser_try_at_keyword:
833  * @self: a `GtkCssParser`
834  * @keyword: name of keyword to check for
835  *
836  * Checks if the current token is an at-keyword token with the
837  * given @keyword. If that is the case, the token is consumed
838  * and %TRUE is returned.
839  *
840  * Returns: %TRUE if the token matched and was consumed.
841  **/
842 gboolean
gtk_css_parser_try_at_keyword(GtkCssParser * self,const char * keyword)843 gtk_css_parser_try_at_keyword (GtkCssParser *self,
844                                const char   *keyword)
845 {
846   const GtkCssToken *token;
847 
848   token = gtk_css_parser_get_token (self);
849 
850   if (!gtk_css_token_is (token, GTK_CSS_TOKEN_AT_KEYWORD) ||
851       g_ascii_strcasecmp (token->string.string, keyword) != 0)
852     return FALSE;
853 
854   gtk_css_parser_consume_token (self);
855   return TRUE;
856 }
857 
858 /**
859  * gtk_css_parser_try_token:
860  * @self: a `GtkCssParser`
861  * @token_type: type of token to try
862  *
863  * Consumes the next token if it matches the given @token_type.
864  *
865  * This function can be used in loops like this:
866  * do {
867  *   ... parse one element ...
868  * } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA);
869  *
870  * Returns: %TRUE if a token was consumed
871  **/
872 gboolean
gtk_css_parser_try_token(GtkCssParser * self,GtkCssTokenType token_type)873 gtk_css_parser_try_token (GtkCssParser    *self,
874                           GtkCssTokenType  token_type)
875 {
876   const GtkCssToken *token;
877 
878   token = gtk_css_parser_get_token (self);
879 
880   if (!gtk_css_token_is (token, token_type))
881     return FALSE;
882 
883   gtk_css_parser_consume_token (self);
884   return TRUE;
885 }
886 
887 /**
888  * gtk_css_parser_consume_ident:
889  * @self: a `GtkCssParser`
890  *
891  * If the current token is an identifier, consumes it and returns
892  * its name.
893  *
894  * If the current token is not an identifier, an error is emitted
895  * and %NULL is returned.
896  *
897  * Returns: (transfer full): the name of the consumed identifier
898  */
899 char *
gtk_css_parser_consume_ident(GtkCssParser * self)900 gtk_css_parser_consume_ident (GtkCssParser *self)
901 {
902   const GtkCssToken *token;
903   char *ident;
904 
905   token = gtk_css_parser_get_token (self);
906 
907   if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
908     {
909       gtk_css_parser_error_syntax (self, "Expected an identifier");
910       return NULL;
911     }
912 
913   ident = g_strdup (token->string.string);
914   gtk_css_parser_consume_token (self);
915 
916   return ident;
917 }
918 
919 /**
920  * gtk_css_parser_consume_string:
921  * @self: a `GtkCssParser`
922  *
923  * If the current token is a string, consumes it and return the string.
924  *
925  * If the current token is not a string, an error is emitted
926  * and %NULL is returned.
927  *
928  * Returns: (transfer full): the name of the consumed string
929  **/
930 char *
gtk_css_parser_consume_string(GtkCssParser * self)931 gtk_css_parser_consume_string (GtkCssParser *self)
932 {
933   const GtkCssToken *token;
934   char *ident;
935 
936   token = gtk_css_parser_get_token (self);
937 
938   if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
939     {
940       gtk_css_parser_error_syntax (self, "Expected a string");
941       return NULL;
942     }
943 
944   ident = g_strdup (token->string.string);
945   gtk_css_parser_consume_token (self);
946 
947   return ident;
948 }
949 
950 static guint
gtk_css_parser_parse_url_arg(GtkCssParser * parser,guint arg,gpointer data)951 gtk_css_parser_parse_url_arg (GtkCssParser *parser,
952                               guint         arg,
953                               gpointer      data)
954 {
955   char **out_url = data;
956 
957   *out_url = gtk_css_parser_consume_string (parser);
958   if (*out_url == NULL)
959     return 0;
960 
961   return 1;
962 }
963 
964 /**
965  * gtk_css_parser_consume_url:
966  * @self: a `GtkCssParser`
967  *
968  * If the parser matches the <url> token from the [CSS
969  * specification](https://drafts.csswg.org/css-values-4/#url-value),
970  * consumes it, resolves the URL and returns the resulting `GFile`.
971  * On failure, an error is emitted and %NULL is returned.
972  *
973  * Returns: (nullable) (transfer full): the resulting URL
974  **/
975 char *
gtk_css_parser_consume_url(GtkCssParser * self)976 gtk_css_parser_consume_url (GtkCssParser *self)
977 {
978   const GtkCssToken *token;
979   char *url;
980 
981   token = gtk_css_parser_get_token (self);
982 
983   if (gtk_css_token_is (token, GTK_CSS_TOKEN_URL))
984     {
985       url = g_strdup (token->string.string);
986       gtk_css_parser_consume_token (self);
987     }
988   else if (gtk_css_token_is_function (token, "url"))
989     {
990       if (!gtk_css_parser_consume_function (self, 1, 1, gtk_css_parser_parse_url_arg, &url))
991         return NULL;
992     }
993   else
994     {
995       gtk_css_parser_error_syntax (self, "Expected a URL");
996       return NULL;
997     }
998 
999   return url;
1000 }
1001 
1002 gboolean
gtk_css_parser_has_number(GtkCssParser * self)1003 gtk_css_parser_has_number (GtkCssParser *self)
1004 {
1005   return gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_NUMBER)
1006       || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_NUMBER)
1007       || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNED_INTEGER)
1008       || gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SIGNLESS_INTEGER);
1009 }
1010 
1011 gboolean
gtk_css_parser_consume_number(GtkCssParser * self,double * number)1012 gtk_css_parser_consume_number (GtkCssParser *self,
1013                                double       *number)
1014 {
1015   const GtkCssToken *token;
1016 
1017   token = gtk_css_parser_get_token (self);
1018   if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
1019       gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
1020       gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
1021       gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
1022     {
1023       *number = token->number.number;
1024       gtk_css_parser_consume_token (self);
1025       return TRUE;
1026     }
1027 
1028   gtk_css_parser_error_syntax (self, "Expected a number");
1029   /* FIXME: Implement calc() */
1030   return FALSE;
1031 }
1032 
1033 gboolean
gtk_css_parser_consume_integer(GtkCssParser * self,int * number)1034 gtk_css_parser_consume_integer (GtkCssParser *self,
1035                                 int          *number)
1036 {
1037   const GtkCssToken *token;
1038 
1039   token = gtk_css_parser_get_token (self);
1040   if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER) ||
1041       gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
1042     {
1043       *number = token->number.number;
1044       gtk_css_parser_consume_token (self);
1045       return TRUE;
1046     }
1047 
1048   gtk_css_parser_error_syntax (self, "Expected an integer");
1049   /* FIXME: Implement calc() */
1050   return FALSE;
1051 }
1052 
1053 gboolean
gtk_css_parser_consume_percentage(GtkCssParser * self,double * number)1054 gtk_css_parser_consume_percentage (GtkCssParser *self,
1055                                    double       *number)
1056 {
1057   const GtkCssToken *token;
1058 
1059   token = gtk_css_parser_get_token (self);
1060   if (gtk_css_token_is (token, GTK_CSS_TOKEN_PERCENTAGE))
1061     {
1062       *number = token->number.number;
1063       gtk_css_parser_consume_token (self);
1064       return TRUE;
1065     }
1066 
1067   gtk_css_parser_error_syntax (self, "Expected a percentage");
1068   /* FIXME: Implement calc() */
1069   return FALSE;
1070 }
1071 
1072 gsize
gtk_css_parser_consume_any(GtkCssParser * parser,const GtkCssParseOption * options,gsize n_options,gpointer user_data)1073 gtk_css_parser_consume_any (GtkCssParser            *parser,
1074                             const GtkCssParseOption *options,
1075                             gsize                    n_options,
1076                             gpointer                 user_data)
1077 {
1078   gsize result;
1079   gsize i;
1080 
1081   g_return_val_if_fail (parser != NULL, 0);
1082   g_return_val_if_fail (options != NULL, 0);
1083   g_return_val_if_fail (n_options < sizeof (gsize) * 8 - 1, 0);
1084 
1085   result = 0;
1086   while (result != (1 << n_options) - 1)
1087     {
1088       for (i = 0; i < n_options; i++)
1089         {
1090           if (result & (1 << i))
1091             continue;
1092           if (options[i].can_parse && !options[i].can_parse (parser, options[i].data, user_data))
1093             continue;
1094           if (!options[i].parse (parser, options[i].data, user_data))
1095             return 0;
1096           result |= 1 << i;
1097           break;
1098         }
1099       if (i == n_options)
1100         break;
1101     }
1102 
1103   if (result == 0)
1104     {
1105       gtk_css_parser_error_syntax (parser, "No valid value given");
1106       return result;
1107     }
1108 
1109   return result;
1110 }
1111