initialise the element->type value so TAG types dont accidently get VIEWPORT if it...
[kugel-rb.git] / utils / themeeditor / skin_parser.c
blob93a71919bff1a02b3084d537cd9ecb0113f462fb
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 #ifdef ROCKBOX
34 /* Declaration of parse tree buffer */
35 #define SKIN_MAX_MEMORY (30*1024)
36 static char skin_parse_tree[SKIN_MAX_MEMORY];
37 static char *skin_buffer;
38 #endif
40 /* Global variables for the parser */
41 int skin_line = 0;
43 /* Auxiliary parsing functions (not visible at global scope) */
44 static struct skin_element* skin_parse_viewport(char** document);
45 static struct skin_element* skin_parse_line(char** document);
46 static struct skin_element* skin_parse_line_optional(char** document,
47 int conditional);
48 static struct skin_element* skin_parse_sublines(char** document);
49 static struct skin_element* skin_parse_sublines_optional(char** document,
50 int conditional);
52 static int skin_parse_tag(struct skin_element* element, char** document);
53 static int skin_parse_text(struct skin_element* element, char** document,
54 int conditional);
55 static int skin_parse_conditional(struct skin_element* element,
56 char** document);
57 static int skin_parse_comment(struct skin_element* element, char** document);
58 static struct skin_element* skin_parse_code_as_arg(char** document);
60 struct skin_element* skin_parse(const char* document)
63 struct skin_element* root = NULL;
64 struct skin_element* last = NULL;
66 struct skin_element** to_write = 0;
68 char* cursor = (char*)document; /*Keeps track of location in the document*/
69 #ifdef ROCKBOX
70 /* FIXME */
71 skin_buffer = &skin_parse_tree[0];
72 #endif
74 skin_line = 1;
76 skin_clear_errors();
78 while(*cursor != '\0')
81 if(!root)
82 to_write = &root;
83 else
84 to_write = &(last->next);
87 *to_write = skin_parse_viewport(&cursor);
88 last = *to_write;
89 if(!last)
91 skin_free_tree(root); /* Clearing any memory already used */
92 return NULL;
95 /* Making sure last is at the end */
96 while(last->next)
97 last = last->next;
101 return root;
105 static struct skin_element* skin_parse_viewport(char** document)
108 struct skin_element* root = NULL;
109 struct skin_element* last = NULL;
110 struct skin_element* retval = NULL;
112 retval = skin_alloc_element();
113 retval->type = VIEWPORT;
114 retval->children_count = 1;
115 retval->line = skin_line;
117 struct skin_element** to_write = 0;
119 char* cursor = *document; /* Keeps track of location in the document */
120 char* bookmark; /* Used when we need to look ahead */
122 int sublines = 0; /* Flag for parsing sublines */
124 /* Parsing out the viewport tag if there is one */
125 if(check_viewport(cursor))
127 skin_parse_tag(retval, &cursor);
128 if(*cursor == '\n')
130 cursor++;
131 skin_line++;
135 retval->children_count = 1;
136 retval->children = skin_alloc_children(1);
142 /* First, we check to see if this line will contain sublines */
143 bookmark = cursor;
144 sublines = 0;
145 while(*cursor != '\n' && *cursor != '\0'
146 && !(check_viewport(cursor) && cursor != *document))
148 if(*cursor == MULTILINESYM)
150 sublines = 1;
151 break;
153 else if(*cursor == TAGSYM)
155 /* A ';' directly after a '%' doesn't count */
156 cursor ++;
158 if(*cursor == '\0')
159 break;
161 cursor++;
163 else if(*cursor == COMMENTSYM)
165 skip_comment(&cursor);
167 else if(*cursor == ARGLISTOPENSYM)
169 skip_arglist(&cursor);
171 else if(*cursor == ENUMLISTOPENSYM)
173 skip_enumlist(&cursor);
175 else
177 /* Advancing the cursor as normal */
178 cursor++;
181 cursor = bookmark;
183 if(!root)
184 to_write = &root;
185 else
186 to_write = &(last->next);
188 if(sublines)
190 *to_write = skin_parse_sublines(&cursor);
191 last = *to_write;
192 if(!last)
193 return NULL;
195 else
198 *to_write = skin_parse_line(&cursor);
199 last = *to_write;
200 if(!last)
201 return NULL;
205 /* Making sure last is at the end */
206 while(last->next)
207 last = last->next;
209 if(*cursor == '\n')
211 cursor++;
212 skin_line++;
215 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document));
217 *document = cursor;
219 retval->children[0] = root;
220 return retval;
224 /* Auxiliary Parsing Functions */
226 static struct skin_element* skin_parse_line(char**document)
229 return skin_parse_line_optional(document, 0);
235 * If conditional is set to true, then this will break upon encountering
236 * SEPERATESYM. This should only be used when parsing a line inside a
237 * conditional, otherwise just use the wrapper function skin_parse_line()
239 static struct skin_element* skin_parse_line_optional(char** document,
240 int conditional)
242 char* cursor = *document;
244 struct skin_element* root = NULL;
245 struct skin_element* current = NULL;
246 struct skin_element* retval = NULL;
248 /* A wrapper for the line */
249 retval = skin_alloc_element();
250 retval->type = LINE;
251 retval->line = skin_line;
252 if(*cursor != '\0' && *cursor != '\n'
253 && !(conditional && (*cursor == ARGLISTSEPERATESYM
254 || *cursor == ARGLISTCLOSESYM
255 || *cursor == ENUMLISTSEPERATESYM
256 || *cursor == ENUMLISTCLOSESYM)))
258 retval->children_count = 1;
260 else
262 retval->children_count = 0;
265 if(retval->children_count > 0)
266 retval->children = skin_alloc_children(1);
268 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
269 && !((*cursor == ARGLISTSEPERATESYM
270 || *cursor == ARGLISTCLOSESYM
271 || *cursor == ENUMLISTSEPERATESYM
272 || *cursor == ENUMLISTCLOSESYM)
273 && conditional)
274 && !(check_viewport(cursor) && cursor != *document))
276 /* Allocating memory if necessary */
277 if(root)
279 current->next = skin_alloc_element();
280 current = current->next;
282 else
284 current = skin_alloc_element();
285 root = current;
288 /* Parsing the current element */
289 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
291 if(!skin_parse_conditional(current, &cursor))
292 return NULL;
294 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
296 if(!skin_parse_tag(current, &cursor))
297 return NULL;
299 else if(*cursor == COMMENTSYM)
301 if(!skin_parse_comment(current, &cursor))
302 return NULL;
304 else
306 if(!skin_parse_text(current, &cursor, conditional))
307 return NULL;
311 /* Moving up the calling function's pointer */
312 *document = cursor;
314 if(root)
315 retval->children[0] = root;
316 return retval;
319 static struct skin_element* skin_parse_sublines(char** document)
321 return skin_parse_sublines_optional(document, 0);
324 static struct skin_element* skin_parse_sublines_optional(char** document,
325 int conditional)
327 struct skin_element* retval;
328 char* cursor = *document;
329 int sublines = 1;
330 int i;
332 retval = skin_alloc_element();
333 retval->type = SUBLINES;
334 retval->next = NULL;
335 retval->line = skin_line;
337 /* First we count the sublines */
338 while(*cursor != '\0' && *cursor != '\n'
339 && !((*cursor == ARGLISTSEPERATESYM
340 || *cursor == ARGLISTCLOSESYM
341 || *cursor == ENUMLISTSEPERATESYM
342 || *cursor == ENUMLISTCLOSESYM)
343 && conditional)
344 && !(check_viewport(cursor) && cursor != *document))
346 if(*cursor == COMMENTSYM)
348 skip_comment(&cursor);
350 else if(*cursor == ENUMLISTOPENSYM)
352 skip_enumlist(&cursor);
354 else if(*cursor == ARGLISTOPENSYM)
356 skip_arglist(&cursor);
358 else if(*cursor == TAGSYM)
360 cursor++;
361 if(*cursor == '\0' || *cursor == '\n')
362 break;
363 cursor++;
365 else if(*cursor == MULTILINESYM)
367 sublines++;
368 cursor++;
370 else
372 cursor++;
376 /* ...and then we parse them */
377 retval->children_count = sublines;
378 retval->children = skin_alloc_children(sublines);
380 cursor = *document;
381 for(i = 0; i < sublines; i++)
383 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
384 skip_whitespace(&cursor);
386 if(*cursor != MULTILINESYM && i != sublines - 1)
388 skin_error(MULTILINE_EXPECTED);
389 return NULL;
391 else if(i != sublines - 1)
393 cursor++;
397 *document = cursor;
399 return retval;
402 static int skin_parse_tag(struct skin_element* element, char** document)
405 char* cursor = *document + 1;
406 char* bookmark;
408 char tag_name[3];
409 char* tag_args;
410 struct tag_info *tag;
412 int num_args = 1;
413 int i;
414 int star = 0; /* Flag for the all-or-none option */
415 int req_args; /* To mark when we enter optional arguments */
417 int optional = 0;
419 /* Checking the tag name */
420 tag_name[0] = cursor[0];
421 tag_name[1] = cursor[1];
422 tag_name[2] = '\0';
424 /* First we check the two characters after the '%', then a single char */
425 tag = find_tag(tag_name);
427 if(!tag)
429 tag_name[1] = '\0';
430 tag = find_tag(tag_name);
431 cursor++;
433 else
435 cursor += 2;
438 if(!tag)
440 skin_error(ILLEGAL_TAG);
441 return 0;
444 /* Copying basic tag info */
445 if(element->type != CONDITIONAL && element->type != VIEWPORT)
446 element->type = TAG;
447 element->tag = tag;
448 tag_args = tag->params;
449 element->line = skin_line;
451 /* Checking for the * flag */
452 if(tag_args[0] == '*')
454 star = 1;
455 tag_args++;
458 /* If this tag has no arguments, we can bail out now */
459 if(strlen(tag_args) == 0
460 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
461 || (star && *cursor != ARGLISTOPENSYM))
463 *document = cursor;
464 return 1;
467 /* Checking the number of arguments and allocating args */
468 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
470 skin_error(ARGLIST_EXPECTED);
471 return 0;
473 else
475 cursor++;
478 bookmark = cursor;
479 while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM)
481 /* Skipping over escaped characters */
482 if(*cursor == TAGSYM)
484 cursor++;
485 if(*cursor == '\0')
486 break;
487 cursor++;
489 else if(*cursor == COMMENTSYM)
491 skip_comment(&cursor);
493 else if(*cursor == ARGLISTOPENSYM)
495 skip_arglist(&cursor);
497 else if(*cursor == ARGLISTSEPERATESYM)
499 num_args++;
500 cursor++;
502 else
504 cursor++;
508 cursor = bookmark; /* Restoring the cursor */
509 element->params_count = num_args;
510 element->params = skin_alloc_params(num_args);
512 /* Now we have to actually parse each argument */
513 for(i = 0; i < num_args; i++)
515 /* Making sure we haven't run out of arguments */
516 if(*tag_args == '\0')
518 skin_error(TOO_MANY_ARGS);
519 return 0;
522 /* Checking for the optional bar */
523 if(*tag_args == '|')
525 optional = 1;
526 req_args = i;
527 tag_args++;
530 /* Scanning the arguments */
531 skip_whitespace(&cursor);
534 /* Checking for comments */
535 if(*cursor == COMMENTSYM)
536 skip_comment(&cursor);
538 /* Storing the type code */
539 element->params[i].type_code = *tag_args;
541 /* Checking a nullable argument for null */
542 if(*cursor == DEFAULTSYM && !isdigit(cursor[1]))
544 if(islower(*tag_args))
546 element->params[i].type = DEFAULT;
547 cursor++;
549 else
551 skin_error(DEFAULT_NOT_ALLOWED);
552 return 0;
555 else if(tolower(*tag_args) == 'i')
557 /* Scanning an int argument */
558 if(!isdigit(*cursor) && *cursor != '-')
560 skin_error(INT_EXPECTED);
561 return 0;
564 element->params[i].type = NUMERIC;
565 element->params[i].data.numeric = scan_int(&cursor);
567 else if(tolower(*tag_args) == 'n' ||
568 tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
570 /* Scanning a string argument */
571 element->params[i].type = STRING;
572 element->params[i].data.text = scan_string(&cursor);
575 else if(tolower(*tag_args) == 'c')
577 /* Recursively parsing a code argument */
578 element->params[i].type = CODE;
579 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
580 if(!element->params[i].data.code)
581 return 0;
584 skip_whitespace(&cursor);
586 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
588 skin_error(SEPERATOR_EXPECTED);
589 return 0;
591 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
593 skin_error(CLOSE_EXPECTED);
594 return 0;
596 else
598 cursor++;
601 if (*tag_args != 'N')
602 tag_args++;
604 /* Checking for the optional bar */
605 if(*tag_args == '|')
607 optional = 1;
608 req_args = i + 1;
609 tag_args++;
614 /* Checking for a premature end */
615 if(*tag_args != '\0' && !optional)
617 skin_error(INSUFFICIENT_ARGS);
618 return 0;
621 *document = cursor;
623 return 1;
627 * If the conditional flag is set true, then parsing text will stop at an
628 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
630 static int skin_parse_text(struct skin_element* element, char** document,
631 int conditional)
633 char* cursor = *document;
634 int length = 0;
635 int dest;
636 char *text = NULL;
638 /* First figure out how much text we're copying */
639 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
640 && *cursor != COMMENTSYM
641 && !((*cursor == ARGLISTSEPERATESYM
642 || *cursor == ARGLISTCLOSESYM
643 || *cursor == ENUMLISTSEPERATESYM
644 || *cursor == ENUMLISTCLOSESYM)
645 && conditional))
647 /* Dealing with possibility of escaped characters */
648 if(*cursor == TAGSYM)
650 if(find_escape_character(cursor[1]))
651 cursor++;
652 else
653 break;
656 length++;
657 cursor++;
660 cursor = *document;
662 /* Copying the text into the element struct */
663 element->type = TEXT;
664 element->line = skin_line;
665 element->next = NULL;
666 element->data = text = skin_alloc_string(length);
668 for(dest = 0; dest < length; dest++)
670 /* Advancing cursor if we've encountered an escaped character */
671 if(*cursor == TAGSYM)
672 cursor++;
674 text[dest] = *cursor;
675 cursor++;
677 text[length] = '\0';
679 *document = cursor;
681 return 1;
684 static int skin_parse_conditional(struct skin_element* element, char** document)
687 char* cursor = *document + 1; /* Starting past the "%" */
688 char* bookmark;
689 int children = 1;
690 int i;
692 element->type = CONDITIONAL;
693 element->line = skin_line;
695 /* Parsing the tag first */
696 if(!skin_parse_tag(element, &cursor))
697 return 0;
699 /* Counting the children */
700 if(*(cursor++) != ENUMLISTOPENSYM)
702 skin_error(ARGLIST_EXPECTED);
703 return 0;
705 bookmark = cursor;
706 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
708 if(*cursor == COMMENTSYM)
710 skip_comment(&cursor);
712 else if(*cursor == ENUMLISTOPENSYM)
714 skip_enumlist(&cursor);
716 else if(*cursor == TAGSYM)
718 cursor++;
719 if(*cursor == '\0' || *cursor == '\n')
720 break;
721 cursor++;
723 else if(*cursor == ENUMLISTSEPERATESYM)
725 children++;
726 cursor++;
728 else
730 cursor++;
733 cursor = bookmark;
735 /* Parsing the children */
736 element->children = skin_alloc_children(children);
737 element->children_count = children;
739 for(i = 0; i < children; i++)
741 element->children[i] = skin_parse_code_as_arg(&cursor);
742 skip_whitespace(&cursor);
744 if(i < children - 1 && *cursor != ENUMLISTSEPERATESYM)
746 skin_error(SEPERATOR_EXPECTED);
747 return 0;
749 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
751 skin_error(CLOSE_EXPECTED);
752 return 0;
754 else
756 cursor++;
760 *document = cursor;
762 return 1;
765 static int skin_parse_comment(struct skin_element* element, char** document)
767 char* cursor = *document;
768 char* text = NULL;
770 int length;
772 * Finding the index of the ending newline or null-terminator
773 * The length of the string of interest doesn't include the leading #, the
774 * length we need to reserve is the same as the index of the last character
776 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
778 element->type = COMMENT;
779 element->line = skin_line;
780 #ifdef ROCKBOX
781 element->data = NULL;
782 #else
783 element->data = text = skin_alloc_string(length);
784 /* We copy from one char past cursor to leave out the # */
785 memcpy((void*)text, (void*)(cursor + 1),
786 sizeof(char) * (length-1));
787 text[length - 1] = '\0';
788 #endif
789 if(cursor[length] == '\n')
790 skin_line++;
792 *document += (length); /* Move cursor up past # and all text */
793 if(**document == '\n')
794 (*document)++;
796 return 1;
799 static struct skin_element* skin_parse_code_as_arg(char** document)
802 int sublines = 0;
803 char* cursor = *document;
805 /* Checking for sublines */
806 while(*cursor != '\n' && *cursor != '\0'
807 && *cursor != ENUMLISTSEPERATESYM && *cursor != ARGLISTSEPERATESYM
808 && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM)
810 if(*cursor == MULTILINESYM)
812 sublines = 1;
813 break;
815 else if(*cursor == TAGSYM)
817 /* A ';' directly after a '%' doesn't count */
818 cursor ++;
820 if(*cursor == '\0')
821 break;
823 cursor++;
825 else if(*cursor == ARGLISTOPENSYM)
827 skip_arglist(&cursor);
829 else if(*cursor == ENUMLISTOPENSYM)
831 skip_enumlist(&cursor);
833 else
835 /* Advancing the cursor as normal */
836 cursor++;
840 if(sublines)
841 return skin_parse_sublines_optional(document, 1);
842 else
843 return skin_parse_line_optional(document, 1);
847 /* Memory management */
848 char* skin_alloc(size_t size)
850 #ifdef ROCKBOX
851 char *retval = skin_buffer;
852 skin_buffer = (void *)(((unsigned long)skin_buffer + 3) & ~3);
853 return retval;
854 #else
855 return malloc(size);
856 #endif
859 struct skin_element* skin_alloc_element()
861 struct skin_element* retval = (struct skin_element*)
862 skin_alloc(sizeof(struct skin_element));
863 retval->type = UNKNOWN;
864 retval->next = NULL;
865 retval->tag = NULL;
866 retval->params_count = 0;
867 retval->children_count = 0;
869 return retval;
873 struct skin_tag_parameter* skin_alloc_params(int count)
875 size_t size = sizeof(struct skin_tag_parameter) * count;
876 return (struct skin_tag_parameter*)skin_alloc(size);
880 char* skin_alloc_string(int length)
882 return (char*)skin_alloc(sizeof(char) * (length + 1));
885 struct skin_element** skin_alloc_children(int count)
887 return (struct skin_element**)
888 skin_alloc(sizeof(struct skin_element*) * count);
891 void skin_free_tree(struct skin_element* root)
893 #ifndef ROCKBOX
894 int i;
896 /* First make the recursive call */
897 if(!root)
898 return;
899 skin_free_tree(root->next);
901 /* Free any text */
902 if(root->type == TEXT || root->type == COMMENT)
903 free(root->data);
905 /* Then recursively free any children, before freeing their pointers */
906 for(i = 0; i < root->children_count; i++)
907 skin_free_tree(root->children[i]);
908 if(root->children_count > 0)
909 free(root->children);
911 /* Free any parameters, making sure to deallocate strings */
912 for(i = 0; i < root->params_count; i++)
913 if(root->params[i].type == STRING)
914 free(root->params[i].data.text);
915 if(root->params_count > 0)
916 free(root->params);
918 /* Finally, delete root's memory */
919 free(root);
920 #else
921 (void)root;
922 #endif