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