Initial cleanup for libwmapro.
[kugel-rb.git] / lib / skin_parser / skin_parser.c
blob2ce41c6d9a26a9517859bd9488bbf4134e037b12
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_buffer.h"
28 #include "skin_parser.h"
29 #include "skin_debug.h"
30 #include "tag_table.h"
31 #include "symbols.h"
32 #include "skin_scan.h"
34 /* Global variables for the parser */
35 int skin_line = 0;
36 int viewport_line = 0;
38 /* Auxiliary parsing functions (not visible at global scope) */
39 static struct skin_element* skin_parse_viewport(char** document);
40 static struct skin_element* skin_parse_line(char** document);
41 static struct skin_element* skin_parse_line_optional(char** document,
42 int conditional);
43 static struct skin_element* skin_parse_sublines(char** document);
44 static struct skin_element* skin_parse_sublines_optional(char** document,
45 int conditional);
47 static int skin_parse_tag(struct skin_element* element, char** document);
48 static int skin_parse_text(struct skin_element* element, char** document,
49 int conditional);
50 static int skin_parse_conditional(struct skin_element* element,
51 char** document);
52 static int skin_parse_comment(struct skin_element* element, char** document);
53 static struct skin_element* skin_parse_code_as_arg(char** document);
57 struct skin_element* skin_parse(const char* document)
60 struct skin_element* root = NULL;
61 struct skin_element* last = NULL;
63 struct skin_element** to_write = 0;
65 char* cursor = (char*)document; /*Keeps track of location in the document*/
67 skin_line = 1;
68 viewport_line = 0;
70 skin_clear_errors();
72 while(*cursor != '\0')
75 if(!root)
76 to_write = &root;
77 else
78 to_write = &(last->next);
81 *to_write = skin_parse_viewport(&cursor);
82 last = *to_write;
83 if(!last)
85 skin_free_tree(root); /* Clearing any memory already used */
86 return NULL;
89 /* Making sure last is at the end */
90 while(last->next)
91 last = last->next;
95 return root;
99 static struct skin_element* skin_parse_viewport(char** document)
102 struct skin_element* root = NULL;
103 struct skin_element* last = NULL;
104 struct skin_element* retval = NULL;
106 retval = skin_alloc_element();
107 retval->type = VIEWPORT;
108 retval->children_count = 1;
109 retval->line = skin_line;
110 viewport_line = skin_line;
112 struct skin_element** to_write = 0;
114 char* cursor = *document; /* Keeps track of location in the document */
115 char* bookmark; /* Used when we need to look ahead */
117 int sublines = 0; /* Flag for parsing sublines */
119 /* Parsing out the viewport tag if there is one */
120 if(check_viewport(cursor))
122 skin_parse_tag(retval, &cursor);
123 if(*cursor == '\n')
125 cursor++;
126 skin_line++;
130 retval->children_count = 1;
131 retval->children = skin_alloc_children(1);
137 /* First, we check to see if this line will contain sublines */
138 bookmark = cursor;
139 sublines = 0;
140 while(*cursor != '\n' && *cursor != '\0'
141 && !(check_viewport(cursor) && cursor != *document))
143 if(*cursor == MULTILINESYM)
145 sublines = 1;
146 break;
148 else if(*cursor == TAGSYM)
150 /* A ';' directly after a '%' doesn't count */
151 cursor ++;
153 if(*cursor == '\0')
154 break;
156 cursor++;
158 else if(*cursor == COMMENTSYM)
160 skip_comment(&cursor);
162 else if(*cursor == ARGLISTOPENSYM)
164 skip_arglist(&cursor);
166 else if(*cursor == ENUMLISTOPENSYM)
168 skip_enumlist(&cursor);
170 else
172 /* Advancing the cursor as normal */
173 cursor++;
176 cursor = bookmark;
178 if(!root)
179 to_write = &root;
180 else
181 to_write = &(last->next);
183 if(sublines)
185 *to_write = skin_parse_sublines(&cursor);
186 last = *to_write;
187 if(!last)
188 return NULL;
190 else
193 *to_write = skin_parse_line(&cursor);
194 last = *to_write;
195 if(!last)
196 return NULL;
200 /* Making sure last is at the end */
201 while(last->next)
202 last = last->next;
204 if(*cursor == '\n')
206 cursor++;
207 skin_line++;
210 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document));
212 *document = cursor;
214 retval->children[0] = root;
215 return retval;
219 /* Auxiliary Parsing Functions */
221 static struct skin_element* skin_parse_line(char**document)
224 return skin_parse_line_optional(document, 0);
230 * If conditional is set to true, then this will break upon encountering
231 * SEPERATESYM. This should only be used when parsing a line inside a
232 * conditional, otherwise just use the wrapper function skin_parse_line()
234 static struct skin_element* skin_parse_line_optional(char** document,
235 int conditional)
237 char* cursor = *document;
239 struct skin_element* root = NULL;
240 struct skin_element* current = NULL;
241 struct skin_element* retval = NULL;
243 /* A wrapper for the line */
244 retval = skin_alloc_element();
245 retval->type = LINE;
246 retval->line = skin_line;
247 if(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
248 && !(conditional && (*cursor == ARGLISTSEPERATESYM
249 || *cursor == ARGLISTCLOSESYM
250 || *cursor == ENUMLISTSEPERATESYM
251 || *cursor == ENUMLISTCLOSESYM)))
253 retval->children_count = 1;
255 else
257 retval->children_count = 0;
260 if(retval->children_count > 0)
261 retval->children = skin_alloc_children(1);
263 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
264 && !((*cursor == ARGLISTSEPERATESYM
265 || *cursor == ARGLISTCLOSESYM
266 || *cursor == ENUMLISTSEPERATESYM
267 || *cursor == ENUMLISTCLOSESYM)
268 && conditional)
269 && !(check_viewport(cursor) && cursor != *document))
271 /* Allocating memory if necessary */
272 if(root)
274 current->next = skin_alloc_element();
275 current = current->next;
277 else
279 current = skin_alloc_element();
280 root = current;
283 /* Parsing the current element */
284 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
286 if(!skin_parse_conditional(current, &cursor))
287 return NULL;
289 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
291 if(!skin_parse_tag(current, &cursor))
292 return NULL;
294 else if(*cursor == COMMENTSYM)
296 if(!skin_parse_comment(current, &cursor))
297 return NULL;
299 else
301 if(!skin_parse_text(current, &cursor, conditional))
302 return NULL;
306 /* Moving up the calling function's pointer */
307 *document = cursor;
309 if(root)
310 retval->children[0] = root;
311 return retval;
314 static struct skin_element* skin_parse_sublines(char** document)
316 return skin_parse_sublines_optional(document, 0);
319 static struct skin_element* skin_parse_sublines_optional(char** document,
320 int conditional)
322 struct skin_element* retval;
323 char* cursor = *document;
324 int sublines = 1;
325 int i;
327 retval = skin_alloc_element();
328 retval->type = LINE_ALTERNATOR;
329 retval->next = NULL;
330 retval->line = skin_line;
332 /* First we count the sublines */
333 while(*cursor != '\0' && *cursor != '\n'
334 && !((*cursor == ARGLISTSEPERATESYM
335 || *cursor == ARGLISTCLOSESYM
336 || *cursor == ENUMLISTSEPERATESYM
337 || *cursor == ENUMLISTCLOSESYM)
338 && conditional)
339 && !(check_viewport(cursor) && cursor != *document))
341 if(*cursor == COMMENTSYM)
343 skip_comment(&cursor);
345 else if(*cursor == ENUMLISTOPENSYM)
347 skip_enumlist(&cursor);
349 else if(*cursor == ARGLISTOPENSYM)
351 skip_arglist(&cursor);
353 else if(*cursor == TAGSYM)
355 cursor++;
356 if(*cursor == '\0' || *cursor == '\n')
357 break;
358 cursor++;
360 else if(*cursor == MULTILINESYM)
362 sublines++;
363 cursor++;
365 else
367 cursor++;
371 /* ...and then we parse them */
372 retval->children_count = sublines;
373 retval->children = skin_alloc_children(sublines);
375 cursor = *document;
376 for(i = 0; i < sublines; i++)
378 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
379 skip_whitespace(&cursor);
381 if(*cursor != MULTILINESYM && i != sublines - 1)
383 skin_error(MULTILINE_EXPECTED);
384 return NULL;
386 else if(i != sublines - 1)
388 cursor++;
392 *document = cursor;
394 return retval;
397 static int skin_parse_tag(struct skin_element* element, char** document)
400 char* cursor = *document + 1;
401 char* bookmark;
403 char tag_name[3];
404 char* tag_args;
405 struct tag_info *tag;
407 int num_args = 1;
408 int i;
409 int star = 0; /* Flag for the all-or-none option */
410 int req_args; /* To mark when we enter optional arguments */
412 int optional = 0;
414 /* Checking the tag name */
415 tag_name[0] = cursor[0];
416 tag_name[1] = cursor[1];
417 tag_name[2] = '\0';
419 /* First we check the two characters after the '%', then a single char */
420 tag = find_tag(tag_name);
422 if(!tag)
424 tag_name[1] = '\0';
425 tag = find_tag(tag_name);
426 cursor++;
428 else
430 cursor += 2;
433 if(!tag)
435 skin_error(ILLEGAL_TAG);
436 return 0;
439 /* Copying basic tag info */
440 if(element->type != CONDITIONAL && element->type != VIEWPORT)
441 element->type = TAG;
442 element->tag = tag;
443 tag_args = tag->params;
444 element->line = skin_line;
446 /* Checking for the * flag */
447 if(tag_args[0] == '*')
449 star = 1;
450 tag_args++;
453 /* If this tag has no arguments, we can bail out now */
454 if(strlen(tag_args) == 0
455 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
456 || (star && *cursor != ARGLISTOPENSYM))
458 *document = cursor;
459 return 1;
463 /* Checking the number of arguments and allocating args */
464 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
466 skin_error(ARGLIST_EXPECTED);
467 return 0;
469 else
471 cursor++;
474 bookmark = cursor;
475 while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM)
477 /* Skipping over escaped characters */
478 if(*cursor == TAGSYM)
480 cursor++;
481 if(*cursor == '\0')
482 break;
483 cursor++;
485 else if(*cursor == COMMENTSYM)
487 skip_comment(&cursor);
489 else if(*cursor == ARGLISTOPENSYM)
491 skip_arglist(&cursor);
493 else if(*cursor == ARGLISTSEPERATESYM)
495 num_args++;
496 cursor++;
498 else
500 cursor++;
504 cursor = bookmark; /* Restoring the cursor */
505 element->params_count = num_args;
506 element->params = skin_alloc_params(num_args);
508 /* Now we have to actually parse each argument */
509 for(i = 0; i < num_args; i++)
511 /* Making sure we haven't run out of arguments */
512 if(*tag_args == '\0')
514 skin_error(TOO_MANY_ARGS);
515 return 0;
518 /* Checking for the optional bar */
519 if(*tag_args == '|')
521 optional = 1;
522 req_args = i;
523 tag_args++;
526 /* Scanning the arguments */
527 skip_whitespace(&cursor);
530 /* Checking for comments */
531 if(*cursor == COMMENTSYM)
532 skip_comment(&cursor);
534 /* Storing the type code */
535 element->params[i].type_code = *tag_args;
537 /* Checking a nullable argument for null */
538 if(*cursor == DEFAULTSYM && !isdigit(cursor[1]))
540 if(islower(*tag_args))
542 element->params[i].type = DEFAULT;
543 cursor++;
545 else
547 skin_error(DEFAULT_NOT_ALLOWED);
548 return 0;
551 else if(tolower(*tag_args) == 'i')
553 /* Scanning an int argument */
554 if(!isdigit(*cursor) && *cursor != '-')
556 skin_error(INT_EXPECTED);
557 return 0;
560 element->params[i].type = NUMERIC;
561 element->params[i].data.numeric = scan_int(&cursor);
563 else if(tolower(*tag_args) == 'n' ||
564 tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
566 /* Scanning a string argument */
567 element->params[i].type = STRING;
568 element->params[i].data.text = scan_string(&cursor);
571 else if(tolower(*tag_args) == 'c')
573 /* Recursively parsing a code argument */
574 element->params[i].type = CODE;
575 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
576 if(!element->params[i].data.code)
577 return 0;
580 skip_whitespace(&cursor);
582 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
584 skin_error(SEPERATOR_EXPECTED);
585 return 0;
587 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
589 skin_error(CLOSE_EXPECTED);
590 return 0;
592 else
594 cursor++;
597 if (*tag_args != 'N')
598 tag_args++;
600 /* Checking for the optional bar */
601 if(*tag_args == '|')
603 optional = 1;
604 req_args = i + 1;
605 tag_args++;
610 /* Checking for a premature end */
611 if(*tag_args != '\0' && !optional)
613 skin_error(INSUFFICIENT_ARGS);
614 return 0;
617 *document = cursor;
619 return 1;
623 * If the conditional flag is set true, then parsing text will stop at an
624 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
626 static int skin_parse_text(struct skin_element* element, char** document,
627 int conditional)
629 char* cursor = *document;
630 int length = 0;
631 int dest;
632 char *text = NULL;
634 /* First figure out how much text we're copying */
635 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
636 && *cursor != COMMENTSYM
637 && !((*cursor == ARGLISTSEPERATESYM
638 || *cursor == ARGLISTCLOSESYM
639 || *cursor == ENUMLISTSEPERATESYM
640 || *cursor == ENUMLISTCLOSESYM)
641 && conditional))
643 /* Dealing with possibility of escaped characters */
644 if(*cursor == TAGSYM)
646 if(find_escape_character(cursor[1]))
647 cursor++;
648 else
649 break;
652 length++;
653 cursor++;
656 cursor = *document;
658 /* Copying the text into the element struct */
659 element->type = TEXT;
660 element->line = skin_line;
661 element->next = NULL;
662 element->data = text = skin_alloc_string(length);
664 for(dest = 0; dest < length; dest++)
666 /* Advancing cursor if we've encountered an escaped character */
667 if(*cursor == TAGSYM)
668 cursor++;
670 text[dest] = *cursor;
671 cursor++;
673 text[length] = '\0';
675 *document = cursor;
677 return 1;
680 static int skin_parse_conditional(struct skin_element* element, char** document)
683 char* cursor = *document + 1; /* Starting past the "%" */
684 char* bookmark;
685 int children = 1;
686 int i;
688 element->type = CONDITIONAL;
689 element->line = skin_line;
691 /* Parsing the tag first */
692 if(!skin_parse_tag(element, &cursor))
693 return 0;
695 /* Counting the children */
696 if(*(cursor++) != ENUMLISTOPENSYM)
698 skin_error(ARGLIST_EXPECTED);
699 return 0;
701 bookmark = cursor;
702 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
704 if(*cursor == COMMENTSYM)
706 skip_comment(&cursor);
708 else if(*cursor == ENUMLISTOPENSYM)
710 skip_enumlist(&cursor);
712 else if(*cursor == TAGSYM)
714 cursor++;
715 if(*cursor == '\0' || *cursor == '\n')
716 break;
717 cursor++;
719 else if(*cursor == ENUMLISTSEPERATESYM)
721 children++;
722 cursor++;
724 else
726 cursor++;
729 cursor = bookmark;
731 /* Parsing the children */
732 element->children = skin_alloc_children(children);
733 element->children_count = children;
735 for(i = 0; i < children; i++)
737 element->children[i] = skin_parse_code_as_arg(&cursor);
738 skip_whitespace(&cursor);
740 if(i < children - 1 && *cursor != ENUMLISTSEPERATESYM)
742 skin_error(SEPERATOR_EXPECTED);
743 return 0;
745 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
747 skin_error(CLOSE_EXPECTED);
748 return 0;
750 else
752 cursor++;
756 *document = cursor;
758 return 1;
761 static int skin_parse_comment(struct skin_element* element, char** document)
763 char* cursor = *document;
764 #ifndef ROCKBOX
765 char* text = NULL;
766 #endif
767 int length;
769 * Finding the index of the ending newline or null-terminator
770 * The length of the string of interest doesn't include the leading #, the
771 * length we need to reserve is the same as the index of the last character
773 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
775 element->type = COMMENT;
776 element->line = skin_line;
777 #ifdef ROCKBOX
778 element->data = NULL;
779 #else
780 element->data = text = skin_alloc_string(length);
781 /* We copy from one char past cursor to leave out the # */
782 memcpy((void*)text, (void*)(cursor + 1),
783 sizeof(char) * (length-1));
784 text[length - 1] = '\0';
785 #endif
786 if(cursor[length] == '\n')
787 skin_line++;
789 *document += (length); /* Move cursor up past # and all text */
790 if(**document == '\n')
791 (*document)++;
793 return 1;
796 static struct skin_element* skin_parse_code_as_arg(char** document)
799 int sublines = 0;
800 char* cursor = *document;
802 /* Checking for sublines */
803 while(*cursor != '\n' && *cursor != '\0'
804 && *cursor != ENUMLISTSEPERATESYM && *cursor != ARGLISTSEPERATESYM
805 && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM)
807 if(*cursor == MULTILINESYM)
809 sublines = 1;
810 break;
812 else if(*cursor == TAGSYM)
814 /* A ';' directly after a '%' doesn't count */
815 cursor ++;
817 if(*cursor == '\0')
818 break;
820 cursor++;
822 else if(*cursor == ARGLISTOPENSYM)
824 skip_arglist(&cursor);
826 else if(*cursor == ENUMLISTOPENSYM)
828 skip_enumlist(&cursor);
830 else
832 /* Advancing the cursor as normal */
833 cursor++;
837 if(sublines)
838 return skin_parse_sublines_optional(document, 1);
839 else
840 return skin_parse_line_optional(document, 1);
844 /* Memory management */
845 struct skin_element* skin_alloc_element()
847 struct skin_element* retval = (struct skin_element*)
848 skin_buffer_alloc(sizeof(struct skin_element));
849 retval->type = UNKNOWN;
850 retval->next = NULL;
851 retval->tag = NULL;
852 retval->params_count = 0;
853 retval->children_count = 0;
855 return retval;
859 struct skin_tag_parameter* skin_alloc_params(int count)
861 size_t size = sizeof(struct skin_tag_parameter) * count;
862 return (struct skin_tag_parameter*)skin_buffer_alloc(size);
866 char* skin_alloc_string(int length)
868 return (char*)skin_buffer_alloc(sizeof(char) * (length + 1));
871 struct skin_element** skin_alloc_children(int count)
873 return (struct skin_element**)
874 skin_buffer_alloc(sizeof(struct skin_element*) * count);
877 void skin_free_tree(struct skin_element* root)
879 #ifndef ROCKBOX
880 int i;
882 /* First make the recursive call */
883 if(!root)
884 return;
885 skin_free_tree(root->next);
887 /* Free any text */
888 if(root->type == TEXT || root->type == COMMENT)
889 free(root->data);
891 /* Then recursively free any children, before freeing their pointers */
892 for(i = 0; i < root->children_count; i++)
893 skin_free_tree(root->children[i]);
894 if(root->children_count > 0)
895 free(root->children);
897 /* Free any parameters, making sure to deallocate strings */
898 for(i = 0; i < root->params_count; i++)
899 if(root->params[i].type == STRING)
900 free(root->params[i].data.text);
901 if(root->params_count > 0)
902 free(root->params);
904 /* Finally, delete root's memory */
905 free(root);
906 #else
907 (void)root;
908 #endif