Theme Editor: Fixed a small bug with asterisk handling in tag parameter argument...
[kugel-rb.git] / utils / themeeditor / skin_parser.c
blob860970b7e78601308de21969fd17ba647cec72fc
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2010 Robert Bieber
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <ctype.h>
27 #include "skin_parser.h"
28 #include "skin_debug.h"
29 #include "tag_table.h"
30 #include "symbols.h"
31 #include "skin_scan.h"
33 /* Declaration of parse tree buffer */
34 char skin_parse_tree[SKIN_MAX_MEMORY];
35 int skin_current_block = 0;
37 /* Global variables for the parser */
38 int skin_line = 0;
40 /* Auxiliary parsing functions (not visible at global scope) */
41 struct skin_element* skin_parse_viewport(char** document);
42 struct skin_element* skin_parse_line(char** document);
43 struct skin_element* skin_parse_line_optional(char** document, int conditional);
44 struct skin_element* skin_parse_sublines(char** document);
45 struct skin_element* skin_parse_sublines_optional(char** document,
46 int conditional);
48 int skin_parse_tag(struct skin_element* element, char** document);
49 int skin_parse_text(struct skin_element* element, char** document,
50 int conditional);
51 int skin_parse_conditional(struct skin_element* element, char** document);
52 int skin_parse_newline(struct skin_element* element, char** document);
53 int skin_parse_comment(struct skin_element* element, char** document);
54 struct skin_element* skin_parse_code_as_arg(char** document);
56 struct skin_element* skin_parse(const char* document)
59 struct skin_element* root = NULL;
60 struct skin_element* last = NULL;
62 struct skin_element** to_write = 0;
64 char* cursor = (char*)document; /*Keeps track of location in the document*/
66 skin_line = 1;
68 while(*cursor != '\0')
71 if(!root)
72 to_write = &root;
73 else
74 to_write = &(last->next);
77 *to_write = skin_parse_viewport(&cursor);
78 last = *to_write;
79 if(!last)
80 return NULL;
82 /* Making sure last is at the end */
83 while(last->next)
84 last = last->next;
88 return root;
92 struct skin_element* skin_parse_viewport(char** document)
95 struct skin_element* root = NULL;
96 struct skin_element* last = NULL;
97 struct skin_element* retval = NULL;
99 retval = skin_alloc_element();
100 retval->type = VIEWPORT;
101 retval->children_count = 1;
102 retval->line = skin_line;
104 struct skin_element** to_write = 0;
106 char* cursor = *document; /* Keeps track of location in the document */
107 char* bookmark; /* Used when we need to look ahead */
109 int sublines = 0; /* Flag for parsing sublines */
111 /* Parsing out the viewport tag if there is one */
112 if(check_viewport(cursor))
114 retval->children_count = 2;
115 retval->children = skin_alloc_children(2);
116 retval->children[0] = skin_alloc_element();
117 skin_parse_tag(retval->children[0], &cursor);
119 else
121 retval->children_count = 1;
122 retval->children = skin_alloc_children(1);
126 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document))
129 /* First, we check to see if this line will contain sublines */
130 bookmark = cursor;
131 sublines = 0;
132 while(*cursor != '\n' && *cursor != '\0'
133 && !(check_viewport(cursor) && cursor != *document))
135 if(*cursor == MULTILINESYM)
137 sublines = 1;
138 break;
140 else if(*cursor == TAGSYM)
142 /* A ';' directly after a '%' doesn't count */
143 cursor ++;
145 if(*cursor == '\0')
146 break;
148 cursor++;
150 else if(*cursor == COMMENTSYM)
152 skip_comment(&cursor);
154 else
156 /* Advancing the cursor as normal */
157 cursor++;
160 cursor = bookmark;
162 if(!root)
163 to_write = &root;
164 else
165 to_write = &(last->next);
167 if(*cursor == '\n')
169 *to_write = skin_alloc_element();
170 skin_parse_newline(*to_write, &cursor);
171 if(!last)
172 return NULL;
174 else if(sublines)
176 *to_write = skin_parse_sublines(&cursor);
177 last = *to_write;
178 if(!last)
179 return NULL;
181 else
184 *to_write = skin_parse_line(&cursor);
185 last = *to_write;
186 if(!last)
187 return NULL;
191 /* Making sure last is at the end */
192 while(last->next)
193 last = last->next;
197 *document = cursor;
199 retval->children[retval->children_count - 1] = root;
200 return retval;
204 /* Auxiliary Parsing Functions */
206 struct skin_element* skin_parse_line(char**document)
209 return skin_parse_line_optional(document, 0);
215 * If conditional is set to true, then this will break upon encountering
216 * SEPERATESYM. This should only be used when parsing a line inside a
217 * conditional, otherwise just use the wrapper function skin_parse_line()
219 struct skin_element* skin_parse_line_optional(char** document, int conditional)
221 char* cursor = *document;
223 struct skin_element* root = NULL;
224 struct skin_element* current = NULL;
225 struct skin_element* retval = NULL;
227 /* A wrapper for the line */
228 retval = skin_alloc_element();
229 retval->type = LINE;
230 retval->line = skin_line;
231 retval->children_count = 1;
232 retval->children = skin_alloc_children(1);
234 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
235 && !((*cursor == ARGLISTSEPERATESYM
236 || *cursor == ARGLISTCLOSESYM
237 || *cursor == ENUMLISTSEPERATESYM
238 || *cursor == ENUMLISTCLOSESYM)
239 && conditional)
240 && !(check_viewport(cursor) && cursor != *document))
242 /* Allocating memory if necessary */
243 if(root)
245 current->next = skin_alloc_element();
246 current = current->next;
248 else
250 current = skin_alloc_element();
251 root = current;
254 /* Parsing the current element */
255 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
257 if(!skin_parse_conditional(current, &cursor))
258 return NULL;
260 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
262 if(!skin_parse_tag(current, &cursor))
263 return NULL;
265 else if(*cursor == COMMENTSYM)
267 if(!skin_parse_comment(current, &cursor))
268 return NULL;
270 else
272 if(!skin_parse_text(current, &cursor, conditional))
273 return NULL;
277 /* Moving up the calling function's pointer */
278 *document = cursor;
280 retval->children[0] = root;
281 return retval;
284 struct skin_element* skin_parse_sublines(char** document)
286 return skin_parse_sublines_optional(document, 0);
289 struct skin_element* skin_parse_sublines_optional(char** document,
290 int conditional)
292 struct skin_element* retval;
293 char* cursor = *document;
294 int sublines = 1;
295 int i;
297 retval = skin_alloc_element();
298 retval->type = SUBLINES;
299 retval->next = NULL;
300 retval->line = skin_line;
302 /* First we count the sublines */
303 while(*cursor != '\0' && *cursor != '\n'
304 && !((*cursor == ARGLISTSEPERATESYM
305 || *cursor == ARGLISTCLOSESYM
306 || *cursor == ENUMLISTSEPERATESYM
307 || *cursor == ENUMLISTCLOSESYM)
308 && conditional)
309 && !(check_viewport(cursor) && cursor != *document))
311 if(*cursor == COMMENTSYM)
312 skip_comment(&cursor);
314 /* Accounting for escaped subline symbols */
315 if(*cursor == TAGSYM)
317 cursor++;
318 if(*cursor == '\0' || *cursor == '\n')
319 break;
322 if(*cursor == MULTILINESYM)
324 sublines++;
327 cursor++;
330 /* ...and then we parse them */
331 retval->children_count = sublines;
332 retval->children = skin_alloc_children(sublines);
334 cursor = *document;
335 for(i = 0; i < sublines; i++)
337 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
338 skip_whitespace(&cursor);
340 if(*cursor != MULTILINESYM && i != sublines - 1)
342 skin_error(MULTILINE_EXPECTED);
343 return NULL;
345 else if(i != sublines - 1)
347 cursor++;
351 *document = cursor;
353 return retval;
356 int skin_parse_tag(struct skin_element* element, char** document)
359 char* cursor = *document + 1;
360 char* bookmark;
362 char tag_name[3];
363 char* tag_args;
364 struct tag_info *tag;
366 int num_args = 1;
367 int i;
368 int star = 0; /* Flag for the all-or-none option */
369 int req_args; /* To mark when we enter optional arguments */
371 int optional = 0;
373 /* Checking the tag name */
374 tag_name[0] = cursor[0];
375 tag_name[1] = cursor[1];
376 tag_name[2] = '\0';
378 /* First we check the two characters after the '%', then a single char */
379 tag = find_tag(tag_name);
381 if(!tag)
383 tag_name[1] = '\0';
384 tag = find_tag(tag_name);
385 cursor++;
387 else
389 cursor += 2;
392 if(!tag)
394 skin_error(ILLEGAL_TAG);
395 return 0;
398 /* Copying basic tag info */
399 element->type = TAG;
400 element->tag = tag;
401 tag_args = tag->params;
402 element->line = skin_line;
404 /* Checking for the * flag */
405 if(tag_args[0] == '*')
407 star = 1;
408 tag_args++;
411 /* If this tag has no arguments, we can bail out now */
412 if(strlen(tag_args) == 0
413 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
414 || (star && *cursor != ARGLISTOPENSYM))
416 *document = cursor;
417 return 1;
420 /* Checking the number of arguments and allocating args */
421 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
423 skin_error(ARGLIST_EXPECTED);
424 return 0;
426 else
428 cursor++;
431 for(bookmark = cursor; *cursor != '\n' && *cursor != '\0' &&
432 *cursor != ARGLISTCLOSESYM; cursor++)
434 /* Skipping over escaped characters */
435 if(*cursor == TAGSYM)
437 cursor++;
438 if(*cursor == '\0')
439 break;
442 /* Skipping comments */
443 if(*cursor == COMMENTSYM)
444 skip_comment(&cursor);
446 /* Commas inside of contained lists don't count */
447 if(*cursor == ARGLISTOPENSYM)
448 while(*cursor != ARGLISTCLOSESYM && *cursor != '\0')
449 cursor++;
451 if(*cursor == ARGLISTSEPERATESYM)
452 num_args++;
455 cursor = bookmark; /* Restoring the cursor */
456 element->params_count = num_args;
457 element->params = skin_alloc_params(num_args);
459 /* Now we have to actually parse each argument */
460 for(i = 0; i < num_args; i++)
462 /* Making sure we haven't run out of arguments */
463 if(*tag_args == '\0')
465 skin_error(TOO_MANY_ARGS);
466 return 0;
469 /* Checking for the optional bar */
470 if(*tag_args == '|')
472 optional = 1;
473 req_args = i;
474 tag_args++;
477 /* Scanning the arguments */
478 skip_whitespace(&cursor);
481 /* Checking for comments */
482 if(*cursor == COMMENTSYM)
483 skip_comment(&cursor);
485 /* Storing the type code */
486 element->params[i].type_code = *tag_args;
488 /* Checking a nullable argument for null */
489 if(*cursor == DEFAULTSYM)
491 if(islower(*tag_args))
493 element->params[i].type = DEFAULT;
494 cursor++;
496 else
498 skin_error(DEFAULT_NOT_ALLOWED);
499 return 0;
502 else if(tolower(*tag_args) == 'i')
504 /* Scanning an int argument */
505 if(!isdigit(*cursor))
507 skin_error(INT_EXPECTED);
508 return 0;
511 element->params[i].type = NUMERIC;
512 element->params[i].data.numeric = scan_int(&cursor);
514 else if(tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
516 /* Scanning a string argument */
517 element->params[i].type = STRING;
518 element->params[i].data.text = scan_string(&cursor);
521 else if(tolower(*tag_args) == 'c')
523 /* Recursively parsing a code argument */
524 element->params[i].type = CODE;
525 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
526 if(!element->params[i].data.code)
527 return 0;
530 skip_whitespace(&cursor);
532 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
534 skin_error(SEPERATOR_EXPECTED);
535 return 0;
537 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
539 skin_error(CLOSE_EXPECTED);
540 return 0;
542 else
544 cursor++;
547 tag_args++;
549 /* Checking for the optional bar */
550 if(*tag_args == '|')
552 optional = 1;
553 req_args = i + 1;
554 tag_args++;
559 /* Checking for a premature end */
560 if(*tag_args != '\0' && !optional)
562 skin_error(INSUFFICIENT_ARGS);
563 return 0;
566 *document = cursor;
568 return 1;
572 * If the conditional flag is set true, then parsing text will stop at an
573 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
575 int skin_parse_text(struct skin_element* element, char** document,
576 int conditional)
578 char* cursor = *document;
580 int length = 0;
582 int dest;
584 /* First figure out how much text we're copying */
585 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
586 && *cursor != COMMENTSYM
587 && !((*cursor == ARGLISTSEPERATESYM
588 || *cursor == ARGLISTCLOSESYM
589 || *cursor == ENUMLISTSEPERATESYM
590 || *cursor == ENUMLISTCLOSESYM)
591 && conditional))
593 /* Dealing with possibility of escaped characters */
594 if(*cursor == TAGSYM)
596 if(find_escape_character(cursor[1]))
597 cursor++;
598 else
599 break;
602 length++;
603 cursor++;
606 cursor = *document;
608 /* Copying the text into the element struct */
609 element->type = TEXT;
610 element->line = skin_line;
611 element->next = NULL;
612 element->text = skin_alloc_string(length);
614 for(dest = 0; dest < length; dest++)
616 /* Advancing cursor if we've encountered an escaped character */
617 if(*cursor == TAGSYM)
618 cursor++;
620 element->text[dest] = *cursor;
621 cursor++;
623 element->text[length] = '\0';
625 *document = cursor;
627 return 1;
630 int skin_parse_conditional(struct skin_element* element, char** document)
633 char* cursor = *document + 1; /* Starting past the "%" */
634 char* bookmark;
635 struct skin_element* tag = skin_alloc_element(); /* The tag to evaluate */
636 int children = 1;
637 int i;
639 element->type = CONDITIONAL;
640 element->line = skin_line;
642 /* Parsing the tag first */
643 if(!skin_parse_tag(tag, &cursor))
644 return 0;
646 /* Counting the children */
647 if(*(cursor++) != ENUMLISTOPENSYM)
649 skin_error(ARGLIST_EXPECTED);
650 return 0;
652 bookmark = cursor;
653 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
655 if(*cursor == COMMENTSYM)
657 skip_comment(&cursor);
658 continue;
661 if(*cursor == TAGSYM)
663 cursor++;
664 if(*cursor == '\0' || *cursor == '\n')
665 break;
666 cursor++;
667 continue;
670 if(*cursor == ENUMLISTSEPERATESYM)
671 children++;
673 cursor++;
675 cursor = bookmark;
677 /* Parsing the children */
678 element->children_count = children + 1; /* Make sure to include the tag */
679 element->children = skin_alloc_children(children + 1);
680 element->children[0] = tag;
682 for(i = 1; i < children + 1; i++)
684 element->children[i] = skin_parse_code_as_arg(&cursor);
685 skip_whitespace(&cursor);
687 if(i < children && *cursor != ENUMLISTSEPERATESYM)
689 skin_error(SEPERATOR_EXPECTED);
690 return 0;
692 else if(i == children && *cursor != ENUMLISTCLOSESYM)
694 skin_error(CLOSE_EXPECTED);
695 return 0;
697 else
699 cursor++;
703 *document = cursor;
705 return 1;
708 int skin_parse_newline(struct skin_element* element, char** document)
711 char* cursor = *document;
712 if(*cursor != '\n')
714 skin_error(NEWLINE_EXPECTED);
715 return 0;
717 cursor++;
719 /* Assembling a skin_element struct for a newline */
720 element->type = NEWLINE;
721 element->line = skin_line;
722 skin_line++;
723 element->next = NULL;
725 *document = cursor;
727 return 1;
730 int skin_parse_comment(struct skin_element* element, char** document)
732 char* cursor = *document;
734 int length;
736 * Finding the index of the ending newline or null-terminator
737 * The length of the string of interest doesn't include the leading #, the
738 * length we need to reserve is the same as the index of the last character
740 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
742 element->type = COMMENT;
743 element->line = skin_line;
744 element->text = skin_alloc_string(length);
745 /* We copy from one char past cursor to leave out the # */
746 memcpy((void*)(element->text), (void*)(cursor + 1),
747 sizeof(char) * (length-1));
748 element->text[length - 1] = '\0';
750 if(cursor[length] == '\n')
751 skin_line++;
753 *document += (length); /* Move cursor up past # and all text */
754 if(**document == '\n')
755 (*document)++;
757 return 1;
760 struct skin_element* skin_parse_code_as_arg(char** document)
763 int sublines = 0;
764 char* cursor = *document;
766 /* Checking for sublines */
767 while(*cursor != '\n' && *cursor != '\0')
769 if(*cursor == MULTILINESYM)
771 sublines = 1;
772 break;
774 else if(*cursor == TAGSYM)
776 /* A ';' directly after a '%' doesn't count */
777 cursor ++;
779 if(*cursor == '\0')
780 break;
782 cursor++;
784 else
786 /* Advancing the cursor as normal */
787 cursor++;
791 if(sublines)
792 return skin_parse_sublines_optional(document, 1);
793 else
794 return skin_parse_line_optional(document, 1);
798 /* Memory management */
799 struct skin_element* skin_alloc_element()
802 #if 0
804 char* retval = &skin_parse_tree[skin_current_block * 4];
806 int delta = sizeof(struct skin_element) / (sizeof(char) * 4);
808 /* If one block is partially filled, make sure to advance to the
809 * next one for the next allocation
811 if(sizeof(struct skin_element) % (sizeof(char) * 4) != 0)
812 delta++;
814 skin_current_block += delta;
816 return (struct skin_element*)retval;
818 #endif
820 struct skin_element* retval = (struct skin_element*)
821 malloc(sizeof(struct skin_element));
822 retval->next = NULL;
823 retval->params_count = 0;
824 retval->children_count = 0;
826 return retval;
830 struct skin_tag_parameter* skin_alloc_params(int count)
832 #if 0
834 char* retval = &skin_parse_tree[skin_current_block * 4];
836 int delta = sizeof(struct skin_tag_parameter) / (sizeof(char) * 4);
837 delta *= count;
839 /* Correcting uneven alignment */
840 if(count * sizeof(struct skin_tag_parameter) % (sizeof(char) * 4) != 0)
841 delta++;
843 skin_current_block += delta;
845 return (struct skin_tag_parameter*) retval;
847 #endif
849 return (struct skin_tag_parameter*)malloc(sizeof(struct skin_tag_parameter)
850 * count);
854 char* skin_alloc_string(int length)
857 #if 0
858 char* retval = &skin_parse_tree[skin_current_block * 4];
860 /* Checking alignment */
861 length++; /* Accounting for the null terminator */
862 int delta = length / 4;
863 if(length % 4 != 0)
864 delta++;
866 skin_current_block += delta;
868 if(skin_current_block >= SKIN_MAX_MEMORY / 4)
869 skin_error(MEMORY_LIMIT_EXCEEDED);
871 return retval;
873 #endif
875 return (char*)malloc(sizeof(char) * (length + 1));
879 struct skin_element** skin_alloc_children(int count)
881 return (struct skin_element**) malloc(sizeof(struct skin_element*) * count);
884 void skin_free_tree(struct skin_element* root)
886 int i;
888 /* First make the recursive call */
889 if(!root)
890 return;
891 skin_free_tree(root->next);
893 /* Free any text */
894 if(root->type == TEXT)
895 free(root->text);
897 /* Then recursively free any children, before freeing their pointers */
898 for(i = 0; i < root->children_count; i++)
899 skin_free_tree(root->children[i]);
900 if(root->children_count > 0)
901 free(root->children);
903 /* Free any parameters, making sure to deallocate strings */
904 for(i = 0; i < root->params_count; i++)
905 if(root->params[i].type == STRING)
906 free(root->params[i].data.text);
907 if(root->params_count > 0)
908 free(root->params);
910 /* Finally, delete root's memory */
911 free(root);