manual: Play/Pause button for Clip keymap is 'Play'
[kugel-rb.git] / lib / skin_parser / skin_parser.c
blob5bc5984fb7ed0eff2c0a2755885d2107dcb87f99
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>
26 #include <stdbool.h>
28 #include "skin_buffer.h"
29 #include "skin_parser.h"
30 #include "skin_debug.h"
31 #include "tag_table.h"
32 #include "symbols.h"
33 #include "skin_scan.h"
35 /* Global variables for the parser */
36 int skin_line = 0;
37 int viewport_line = 0;
39 /* Auxiliary parsing functions (not visible at global scope) */
40 static struct skin_element* skin_parse_viewport(char** document);
41 static struct skin_element* skin_parse_line(char** document);
42 static struct skin_element* skin_parse_line_optional(char** document,
43 int conditional);
44 static struct skin_element* skin_parse_sublines(char** document);
45 static struct skin_element* skin_parse_sublines_optional(char** document,
46 int conditional);
48 static int skin_parse_tag(struct skin_element* element, char** document);
49 static int skin_parse_text(struct skin_element* element, char** document,
50 int conditional);
51 static int skin_parse_conditional(struct skin_element* element,
52 char** document);
53 static int skin_parse_comment(struct skin_element* element, char** document);
54 static struct skin_element* skin_parse_code_as_arg(char** document);
58 struct skin_element* skin_parse(const char* document)
61 struct skin_element* root = NULL;
62 struct skin_element* last = NULL;
64 struct skin_element** to_write = 0;
66 char* cursor = (char*)document; /*Keeps track of location in the document*/
68 skin_line = 1;
69 viewport_line = 0;
71 skin_clear_errors();
73 while(*cursor != '\0')
76 if(!root)
77 to_write = &root;
78 else
79 to_write = &(last->next);
82 *to_write = skin_parse_viewport(&cursor);
83 last = *to_write;
84 if(!last)
86 skin_free_tree(root); /* Clearing any memory already used */
87 return NULL;
90 /* Making sure last is at the end */
91 while(last->next)
92 last = last->next;
96 return root;
100 static struct skin_element* skin_parse_viewport(char** document)
103 struct skin_element* root = NULL;
104 struct skin_element* last = NULL;
105 struct skin_element* retval = NULL;
107 retval = skin_alloc_element();
108 retval->type = VIEWPORT;
109 retval->children_count = 1;
110 retval->line = skin_line;
111 viewport_line = skin_line;
113 struct skin_element** to_write = 0;
115 char* cursor = *document; /* Keeps track of location in the document */
116 char* bookmark; /* Used when we need to look ahead */
118 int sublines = 0; /* Flag for parsing sublines */
120 /* Parsing out the viewport tag if there is one */
121 if(check_viewport(cursor))
123 skin_parse_tag(retval, &cursor);
124 if(*cursor == '\n')
126 cursor++;
127 skin_line++;
131 retval->children_count = 1;
132 retval->children = skin_alloc_children(1);
138 /* First, we check to see if this line will contain sublines */
139 bookmark = cursor;
140 sublines = 0;
141 while(*cursor != '\n' && *cursor != '\0'
142 && !(check_viewport(cursor) && cursor != *document))
144 if(*cursor == MULTILINESYM)
146 sublines = 1;
147 break;
149 else if(*cursor == TAGSYM)
151 /* A ';' directly after a '%' doesn't count */
152 cursor ++;
154 if(*cursor == '\0')
155 break;
157 cursor++;
159 else if(*cursor == COMMENTSYM)
161 skip_comment(&cursor);
163 else if(*cursor == ARGLISTOPENSYM)
165 skip_arglist(&cursor);
167 else if(*cursor == ENUMLISTOPENSYM)
169 skip_enumlist(&cursor);
171 else
173 /* Advancing the cursor as normal */
174 cursor++;
177 cursor = bookmark;
179 if(!root)
180 to_write = &root;
181 else
182 to_write = &(last->next);
184 if(sublines)
186 *to_write = skin_parse_sublines(&cursor);
187 last = *to_write;
188 if(!last)
189 return NULL;
191 else
194 *to_write = skin_parse_line(&cursor);
195 last = *to_write;
196 if(!last)
197 return NULL;
201 /* Making sure last is at the end */
202 while(last->next)
203 last = last->next;
205 if(*cursor == '\n')
207 cursor++;
208 skin_line++;
211 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document));
213 *document = cursor;
215 retval->children[0] = root;
216 return retval;
220 /* Auxiliary Parsing Functions */
222 static struct skin_element* skin_parse_line(char**document)
225 return skin_parse_line_optional(document, 0);
231 * If conditional is set to true, then this will break upon encountering
232 * SEPERATESYM. This should only be used when parsing a line inside a
233 * conditional, otherwise just use the wrapper function skin_parse_line()
235 static struct skin_element* skin_parse_line_optional(char** document,
236 int conditional)
238 char* cursor = *document;
240 struct skin_element* root = NULL;
241 struct skin_element* current = NULL;
242 struct skin_element* retval = NULL;
244 /* A wrapper for the line */
245 retval = skin_alloc_element();
246 retval->type = LINE;
247 retval->line = skin_line;
248 if(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
249 && !(conditional && (*cursor == ARGLISTSEPERATESYM
250 || *cursor == ARGLISTCLOSESYM
251 || *cursor == ENUMLISTSEPERATESYM
252 || *cursor == ENUMLISTCLOSESYM)))
254 retval->children_count = 1;
256 else
258 retval->children_count = 0;
261 if(retval->children_count > 0)
262 retval->children = skin_alloc_children(1);
264 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
265 && !((*cursor == ARGLISTSEPERATESYM
266 || *cursor == ARGLISTCLOSESYM
267 || *cursor == ENUMLISTSEPERATESYM
268 || *cursor == ENUMLISTCLOSESYM)
269 && conditional)
270 && !(check_viewport(cursor) && cursor != *document))
272 /* Allocating memory if necessary */
273 if(root)
275 current->next = skin_alloc_element();
276 current = current->next;
278 else
280 current = skin_alloc_element();
281 root = current;
284 /* Parsing the current element */
285 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
287 if(!skin_parse_conditional(current, &cursor))
288 return NULL;
290 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
292 if(!skin_parse_tag(current, &cursor))
293 return NULL;
295 else if(*cursor == COMMENTSYM)
297 if(!skin_parse_comment(current, &cursor))
298 return NULL;
300 else
302 if(!skin_parse_text(current, &cursor, conditional))
303 return NULL;
307 /* Moving up the calling function's pointer */
308 *document = cursor;
310 if(root)
311 retval->children[0] = root;
312 return retval;
315 static struct skin_element* skin_parse_sublines(char** document)
317 return skin_parse_sublines_optional(document, 0);
320 static struct skin_element* skin_parse_sublines_optional(char** document,
321 int conditional)
323 struct skin_element* retval;
324 char* cursor = *document;
325 int sublines = 1;
326 int i;
328 retval = skin_alloc_element();
329 retval->type = LINE_ALTERNATOR;
330 retval->next = NULL;
331 retval->line = skin_line;
333 /* First we count the sublines */
334 while(*cursor != '\0' && *cursor != '\n'
335 && !((*cursor == ARGLISTSEPERATESYM
336 || *cursor == ARGLISTCLOSESYM
337 || *cursor == ENUMLISTSEPERATESYM
338 || *cursor == ENUMLISTCLOSESYM)
339 && conditional)
340 && !(check_viewport(cursor) && cursor != *document))
342 if(*cursor == COMMENTSYM)
344 skip_comment(&cursor);
346 else if(*cursor == ENUMLISTOPENSYM)
348 skip_enumlist(&cursor);
350 else if(*cursor == ARGLISTOPENSYM)
352 skip_arglist(&cursor);
354 else if(*cursor == TAGSYM)
356 cursor++;
357 if(*cursor == '\0' || *cursor == '\n')
358 break;
359 cursor++;
361 else if(*cursor == MULTILINESYM)
363 sublines++;
364 cursor++;
366 else
368 cursor++;
372 /* ...and then we parse them */
373 retval->children_count = sublines;
374 retval->children = skin_alloc_children(sublines);
376 cursor = *document;
377 for(i = 0; i < sublines; i++)
379 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
380 skip_whitespace(&cursor);
382 if(*cursor != MULTILINESYM && i != sublines - 1)
384 skin_error(MULTILINE_EXPECTED);
385 return NULL;
387 else if(i != sublines - 1)
389 cursor++;
393 *document = cursor;
395 return retval;
398 static int skin_parse_tag(struct skin_element* element, char** document)
401 char* cursor = *document + 1;
402 char* bookmark;
404 char tag_name[3];
405 char* tag_args;
406 struct tag_info *tag;
408 int num_args = 1;
409 int i;
410 int star = 0; /* Flag for the all-or-none option */
411 int req_args; /* To mark when we enter optional arguments */
413 int optional = 0;
415 /* Checking the tag name */
416 tag_name[0] = cursor[0];
417 tag_name[1] = cursor[1];
418 tag_name[2] = '\0';
420 /* First we check the two characters after the '%', then a single char */
421 tag = find_tag(tag_name);
423 if(!tag)
425 tag_name[1] = '\0';
426 tag = find_tag(tag_name);
427 cursor++;
429 else
431 cursor += 2;
434 if(!tag)
436 skin_error(ILLEGAL_TAG);
437 return 0;
440 /* Copying basic tag info */
441 if(element->type != CONDITIONAL && element->type != VIEWPORT)
442 element->type = TAG;
443 element->tag = tag;
444 tag_args = tag->params;
445 element->line = skin_line;
447 /* Checking for the * flag */
448 if(tag_args[0] == '*')
450 star = 1;
451 tag_args++;
454 /* If this tag has no arguments, we can bail out now */
455 if(strlen(tag_args) == 0
456 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
457 || (star && *cursor != ARGLISTOPENSYM))
459 *document = cursor;
460 return 1;
464 /* Checking the number of arguments and allocating args */
465 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
467 skin_error(ARGLIST_EXPECTED);
468 return 0;
470 else
472 cursor++;
475 bookmark = cursor;
476 while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM)
478 /* Skipping over escaped characters */
479 if(*cursor == TAGSYM)
481 cursor++;
482 if(*cursor == '\0')
483 break;
484 cursor++;
486 else if(*cursor == COMMENTSYM)
488 skip_comment(&cursor);
490 else if(*cursor == ARGLISTOPENSYM)
492 skip_arglist(&cursor);
494 else if(*cursor == ARGLISTSEPERATESYM)
496 num_args++;
497 cursor++;
499 else
501 cursor++;
505 cursor = bookmark; /* Restoring the cursor */
506 element->params_count = num_args;
507 element->params = skin_alloc_params(num_args);
509 /* Now we have to actually parse each argument */
510 for(i = 0; i < num_args; i++)
512 /* Making sure we haven't run out of arguments */
513 if(*tag_args == '\0')
515 skin_error(TOO_MANY_ARGS);
516 return 0;
519 /* Checking for the optional bar */
520 if(*tag_args == '|')
522 optional = 1;
523 req_args = i;
524 tag_args++;
527 /* Scanning the arguments */
528 skip_whitespace(&cursor);
531 /* Checking for comments */
532 if(*cursor == COMMENTSYM)
533 skip_comment(&cursor);
535 /* Storing the type code */
536 element->params[i].type_code = *tag_args;
538 /* Checking a nullable argument for null. */
539 if(*cursor == DEFAULTSYM && !isdigit(cursor[1]))
541 if(islower(*tag_args))
543 element->params[i].type = DEFAULT;
544 cursor++;
546 else
548 skin_error(DEFAULT_NOT_ALLOWED);
549 return 0;
552 else if(tolower(*tag_args) == 'i')
554 /* Scanning an int argument */
555 if(!isdigit(*cursor) && *cursor != '-')
557 skin_error(INT_EXPECTED);
558 return 0;
561 element->params[i].type = INTEGER;
562 element->params[i].data.number = scan_int(&cursor);
564 else if(tolower(*tag_args) == 'd')
566 int val = 0;
567 bool have_point = false;
568 bool have_tenth = false;
569 while ( isdigit(*cursor) || *cursor == '.' )
571 if (*cursor != '.')
573 val *= 10;
574 val += *cursor - '0';
575 if (have_point)
577 have_tenth = true;
578 cursor++;
579 break;
582 else
583 have_point = true;
584 cursor++;
586 if (have_tenth == false)
587 val *= 10;
589 element->params[i].type = DECIMAL;
590 element->params[i].data.number = val;
592 else if(tolower(*tag_args) == 'n' ||
593 tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
595 /* Scanning a string argument */
596 element->params[i].type = STRING;
597 element->params[i].data.text = scan_string(&cursor);
600 else if(tolower(*tag_args) == 'c')
602 /* Recursively parsing a code argument */
603 element->params[i].type = CODE;
604 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
605 if(!element->params[i].data.code)
606 return 0;
609 skip_whitespace(&cursor);
611 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
613 skin_error(SEPERATOR_EXPECTED);
614 return 0;
616 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
618 skin_error(CLOSE_EXPECTED);
619 return 0;
621 else
623 cursor++;
626 if (*tag_args != 'N')
627 tag_args++;
629 /* Checking for the optional bar */
630 if(*tag_args == '|')
632 optional = 1;
633 req_args = i + 1;
634 tag_args++;
639 /* Checking for a premature end */
640 if(*tag_args != '\0' && !optional)
642 skin_error(INSUFFICIENT_ARGS);
643 return 0;
646 *document = cursor;
648 return 1;
652 * If the conditional flag is set true, then parsing text will stop at an
653 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
655 static int skin_parse_text(struct skin_element* element, char** document,
656 int conditional)
658 char* cursor = *document;
659 int length = 0;
660 int dest;
661 char *text = NULL;
663 /* First figure out how much text we're copying */
664 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
665 && *cursor != COMMENTSYM
666 && !((*cursor == ARGLISTSEPERATESYM
667 || *cursor == ARGLISTCLOSESYM
668 || *cursor == ENUMLISTSEPERATESYM
669 || *cursor == ENUMLISTCLOSESYM)
670 && conditional))
672 /* Dealing with possibility of escaped characters */
673 if(*cursor == TAGSYM)
675 if(find_escape_character(cursor[1]))
676 cursor++;
677 else
678 break;
681 length++;
682 cursor++;
685 cursor = *document;
687 /* Copying the text into the element struct */
688 element->type = TEXT;
689 element->line = skin_line;
690 element->next = NULL;
691 element->data = text = skin_alloc_string(length);
693 for(dest = 0; dest < length; dest++)
695 /* Advancing cursor if we've encountered an escaped character */
696 if(*cursor == TAGSYM)
697 cursor++;
699 text[dest] = *cursor;
700 cursor++;
702 text[length] = '\0';
704 *document = cursor;
706 return 1;
709 static int skin_parse_conditional(struct skin_element* element, char** document)
712 char* cursor = *document + 1; /* Starting past the "%" */
713 char* bookmark;
714 int children = 1;
715 int i;
717 element->type = CONDITIONAL;
718 element->line = skin_line;
720 /* Parsing the tag first */
721 if(!skin_parse_tag(element, &cursor))
722 return 0;
724 /* Counting the children */
725 if(*(cursor++) != ENUMLISTOPENSYM)
727 skin_error(ARGLIST_EXPECTED);
728 return 0;
730 bookmark = cursor;
731 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
733 if(*cursor == COMMENTSYM)
735 skip_comment(&cursor);
737 else if(*cursor == ENUMLISTOPENSYM)
739 skip_enumlist(&cursor);
741 else if(*cursor == TAGSYM)
743 cursor++;
744 if(*cursor == '\0' || *cursor == '\n')
745 break;
746 cursor++;
748 else if(*cursor == ENUMLISTSEPERATESYM)
750 children++;
751 cursor++;
753 else
755 cursor++;
758 cursor = bookmark;
760 /* Parsing the children */
761 element->children = skin_alloc_children(children);
762 element->children_count = children;
764 for(i = 0; i < children; i++)
766 element->children[i] = skin_parse_code_as_arg(&cursor);
767 skip_whitespace(&cursor);
769 if(i < children - 1 && *cursor != ENUMLISTSEPERATESYM)
771 skin_error(SEPERATOR_EXPECTED);
772 return 0;
774 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
776 skin_error(CLOSE_EXPECTED);
777 return 0;
779 else
781 cursor++;
785 *document = cursor;
787 return 1;
790 static int skin_parse_comment(struct skin_element* element, char** document)
792 char* cursor = *document;
793 #ifndef ROCKBOX
794 char* text = NULL;
795 #endif
796 int length;
798 * Finding the index of the ending newline or null-terminator
799 * The length of the string of interest doesn't include the leading #, the
800 * length we need to reserve is the same as the index of the last character
802 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
804 element->type = COMMENT;
805 element->line = skin_line;
806 #ifdef ROCKBOX
807 element->data = NULL;
808 #else
809 element->data = text = skin_alloc_string(length);
810 /* We copy from one char past cursor to leave out the # */
811 memcpy((void*)text, (void*)(cursor + 1),
812 sizeof(char) * (length-1));
813 text[length - 1] = '\0';
814 #endif
815 if(cursor[length] == '\n')
816 skin_line++;
818 *document += (length); /* Move cursor up past # and all text */
819 if(**document == '\n')
820 (*document)++;
822 return 1;
825 static struct skin_element* skin_parse_code_as_arg(char** document)
828 int sublines = 0;
829 char* cursor = *document;
831 /* Checking for sublines */
832 while(*cursor != '\n' && *cursor != '\0'
833 && *cursor != ENUMLISTSEPERATESYM && *cursor != ARGLISTSEPERATESYM
834 && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM)
836 if(*cursor == MULTILINESYM)
838 sublines = 1;
839 break;
841 else if(*cursor == TAGSYM)
843 /* A ';' directly after a '%' doesn't count */
844 cursor ++;
846 if(*cursor == '\0')
847 break;
849 cursor++;
851 else if(*cursor == ARGLISTOPENSYM)
853 skip_arglist(&cursor);
855 else if(*cursor == ENUMLISTOPENSYM)
857 skip_enumlist(&cursor);
859 else
861 /* Advancing the cursor as normal */
862 cursor++;
866 if(sublines)
867 return skin_parse_sublines_optional(document, 1);
868 else
869 return skin_parse_line_optional(document, 1);
873 /* Memory management */
874 struct skin_element* skin_alloc_element()
876 struct skin_element* retval = (struct skin_element*)
877 skin_buffer_alloc(sizeof(struct skin_element));
878 retval->type = UNKNOWN;
879 retval->next = NULL;
880 retval->tag = NULL;
881 retval->params_count = 0;
882 retval->children_count = 0;
884 return retval;
888 struct skin_tag_parameter* skin_alloc_params(int count)
890 size_t size = sizeof(struct skin_tag_parameter) * count;
891 return (struct skin_tag_parameter*)skin_buffer_alloc(size);
895 char* skin_alloc_string(int length)
897 return (char*)skin_buffer_alloc(sizeof(char) * (length + 1));
900 struct skin_element** skin_alloc_children(int count)
902 return (struct skin_element**)
903 skin_buffer_alloc(sizeof(struct skin_element*) * count);
906 void skin_free_tree(struct skin_element* root)
908 #ifndef ROCKBOX
909 int i;
911 /* First make the recursive call */
912 if(!root)
913 return;
914 skin_free_tree(root->next);
916 /* Free any text */
917 if(root->type == TEXT || root->type == COMMENT)
918 free(root->data);
920 /* Then recursively free any children, before freeing their pointers */
921 for(i = 0; i < root->children_count; i++)
922 skin_free_tree(root->children[i]);
923 if(root->children_count > 0)
924 free(root->children);
926 /* Free any parameters, making sure to deallocate strings */
927 for(i = 0; i < root->params_count; i++)
928 if(root->params[i].type == STRING)
929 free(root->params[i].data.text);
930 if(root->params_count > 0)
931 free(root->params);
933 /* Finally, delete root's memory */
934 free(root);
935 #else
936 (void)root;
937 #endif