Theme Editor: Fixed another conditional child-counting bug
[kugel-rb.git] / utils / themeeditor / skin_parser.c
blobc046dc66adf7a67c72472579291198b4ff7b10ab
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_comment(struct skin_element* element, char** document);
53 struct skin_element* skin_parse_code_as_arg(char** document);
55 struct skin_element* skin_parse(const char* document)
58 struct skin_element* root = NULL;
59 struct skin_element* last = NULL;
61 struct skin_element** to_write = 0;
63 char* cursor = (char*)document; /*Keeps track of location in the document*/
65 skin_line = 1;
67 while(*cursor != '\0')
70 if(!root)
71 to_write = &root;
72 else
73 to_write = &(last->next);
76 *to_write = skin_parse_viewport(&cursor);
77 last = *to_write;
78 if(!last)
79 return NULL;
81 /* Making sure last is at the end */
82 while(last->next)
83 last = last->next;
87 return root;
91 struct skin_element* skin_parse_viewport(char** document)
94 struct skin_element* root = NULL;
95 struct skin_element* last = NULL;
96 struct skin_element* retval = NULL;
98 retval = skin_alloc_element();
99 retval->type = VIEWPORT;
100 retval->children_count = 1;
101 retval->line = skin_line;
103 struct skin_element** to_write = 0;
105 char* cursor = *document; /* Keeps track of location in the document */
106 char* bookmark; /* Used when we need to look ahead */
108 int sublines = 0; /* Flag for parsing sublines */
110 /* Parsing out the viewport tag if there is one */
111 if(check_viewport(cursor))
113 retval->children_count = 2;
114 retval->children = skin_alloc_children(2);
115 retval->children[0] = skin_alloc_element();
116 skin_parse_tag(retval->children[0], &cursor);
117 if(*cursor == '\n')
119 cursor++;
120 skin_line++;
123 else
125 retval->children_count = 1;
126 retval->children = skin_alloc_children(1);
130 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document))
133 /* First, we check to see if this line will contain sublines */
134 bookmark = cursor;
135 sublines = 0;
136 while(*cursor != '\n' && *cursor != '\0'
137 && !(check_viewport(cursor) && cursor != *document))
139 if(*cursor == MULTILINESYM)
141 sublines = 1;
142 break;
144 else if(*cursor == TAGSYM)
146 /* A ';' directly after a '%' doesn't count */
147 cursor ++;
149 if(*cursor == '\0')
150 break;
152 cursor++;
154 else if(*cursor == COMMENTSYM)
156 skip_comment(&cursor);
158 else
160 /* Advancing the cursor as normal */
161 cursor++;
164 cursor = bookmark;
166 if(!root)
167 to_write = &root;
168 else
169 to_write = &(last->next);
171 if(sublines)
173 *to_write = skin_parse_sublines(&cursor);
174 last = *to_write;
175 if(!last)
176 return NULL;
178 else
181 *to_write = skin_parse_line(&cursor);
182 last = *to_write;
183 if(!last)
184 return NULL;
188 /* Making sure last is at the end */
189 while(last->next)
190 last = last->next;
192 if(*cursor == '\n')
194 cursor++;
195 skin_line++;
199 *document = cursor;
201 retval->children[retval->children_count - 1] = root;
202 return retval;
206 /* Auxiliary Parsing Functions */
208 struct skin_element* skin_parse_line(char**document)
211 return skin_parse_line_optional(document, 0);
217 * If conditional is set to true, then this will break upon encountering
218 * SEPERATESYM. This should only be used when parsing a line inside a
219 * conditional, otherwise just use the wrapper function skin_parse_line()
221 struct skin_element* skin_parse_line_optional(char** document, int conditional)
223 char* cursor = *document;
225 struct skin_element* root = NULL;
226 struct skin_element* current = NULL;
227 struct skin_element* retval = NULL;
229 /* A wrapper for the line */
230 retval = skin_alloc_element();
231 retval->type = LINE;
232 retval->line = skin_line;
233 retval->children_count = 1;
234 retval->children = skin_alloc_children(1);
236 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
237 && !((*cursor == ARGLISTSEPERATESYM
238 || *cursor == ARGLISTCLOSESYM
239 || *cursor == ENUMLISTSEPERATESYM
240 || *cursor == ENUMLISTCLOSESYM)
241 && conditional)
242 && !(check_viewport(cursor) && cursor != *document))
244 /* Allocating memory if necessary */
245 if(root)
247 current->next = skin_alloc_element();
248 current = current->next;
250 else
252 current = skin_alloc_element();
253 root = current;
256 /* Parsing the current element */
257 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
259 if(!skin_parse_conditional(current, &cursor))
260 return NULL;
262 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
264 if(!skin_parse_tag(current, &cursor))
265 return NULL;
267 else if(*cursor == COMMENTSYM)
269 if(!skin_parse_comment(current, &cursor))
270 return NULL;
272 else
274 if(!skin_parse_text(current, &cursor, conditional))
275 return NULL;
279 /* Moving up the calling function's pointer */
280 *document = cursor;
282 retval->children[0] = root;
283 return retval;
286 struct skin_element* skin_parse_sublines(char** document)
288 return skin_parse_sublines_optional(document, 0);
291 struct skin_element* skin_parse_sublines_optional(char** document,
292 int conditional)
294 struct skin_element* retval;
295 char* cursor = *document;
296 int sublines = 1;
297 int i;
298 int nested = 0;
300 retval = skin_alloc_element();
301 retval->type = SUBLINES;
302 retval->next = NULL;
303 retval->line = skin_line;
305 /* First we count the sublines */
306 while(*cursor != '\0' && *cursor != '\n'
307 && !((*cursor == ARGLISTSEPERATESYM
308 || *cursor == ARGLISTCLOSESYM
309 || *cursor == ENUMLISTSEPERATESYM
310 || *cursor == ENUMLISTCLOSESYM)
311 && conditional)
312 && !(check_viewport(cursor) && cursor != *document))
314 if(*cursor == COMMENTSYM)
316 skip_comment(&cursor);
317 continue;
320 if(*cursor == ENUMLISTOPENSYM && conditional)
322 nested++;
323 cursor++;
324 while(nested)
326 if(*cursor == ENUMLISTOPENSYM)
327 nested++;
328 if(*cursor == ENUMLISTCLOSESYM)
329 nested--;
330 cursor++;
333 /* Accounting for escaped subline symbols */
334 if(*cursor == TAGSYM)
336 cursor++;
337 if(*cursor == '\0' || *cursor == '\n')
338 break;
341 if(*cursor == MULTILINESYM)
343 sublines++;
346 cursor++;
349 /* ...and then we parse them */
350 retval->children_count = sublines;
351 retval->children = skin_alloc_children(sublines);
353 cursor = *document;
354 for(i = 0; i < sublines; i++)
356 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
357 skip_whitespace(&cursor);
359 if(*cursor != MULTILINESYM && i != sublines - 1)
361 skin_error(MULTILINE_EXPECTED);
362 return NULL;
364 else if(i != sublines - 1)
366 cursor++;
370 *document = cursor;
372 return retval;
375 int skin_parse_tag(struct skin_element* element, char** document)
378 char* cursor = *document + 1;
379 char* bookmark;
381 char tag_name[3];
382 char* tag_args;
383 struct tag_info *tag;
385 int num_args = 1;
386 int i;
387 int star = 0; /* Flag for the all-or-none option */
388 int req_args; /* To mark when we enter optional arguments */
390 int optional = 0;
392 /* Checking the tag name */
393 tag_name[0] = cursor[0];
394 tag_name[1] = cursor[1];
395 tag_name[2] = '\0';
397 /* First we check the two characters after the '%', then a single char */
398 tag = find_tag(tag_name);
400 if(!tag)
402 tag_name[1] = '\0';
403 tag = find_tag(tag_name);
404 cursor++;
406 else
408 cursor += 2;
411 if(!tag)
413 skin_error(ILLEGAL_TAG);
414 return 0;
417 /* Copying basic tag info */
418 element->type = TAG;
419 element->tag = tag;
420 tag_args = tag->params;
421 element->line = skin_line;
423 /* Checking for the * flag */
424 if(tag_args[0] == '*')
426 star = 1;
427 tag_args++;
430 /* If this tag has no arguments, we can bail out now */
431 if(strlen(tag_args) == 0
432 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
433 || (star && *cursor != ARGLISTOPENSYM))
435 *document = cursor;
436 return 1;
439 /* Checking the number of arguments and allocating args */
440 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
442 skin_error(ARGLIST_EXPECTED);
443 return 0;
445 else
447 cursor++;
450 for(bookmark = cursor; *cursor != '\n' && *cursor != '\0' &&
451 *cursor != ARGLISTCLOSESYM; cursor++)
453 /* Skipping over escaped characters */
454 if(*cursor == TAGSYM)
456 cursor++;
457 if(*cursor == '\0')
458 break;
461 /* Skipping comments */
462 if(*cursor == COMMENTSYM)
463 skip_comment(&cursor);
465 /* Commas inside of contained lists don't count */
466 if(*cursor == ARGLISTOPENSYM)
467 while(*cursor != ARGLISTCLOSESYM && *cursor != '\0')
468 cursor++;
470 if(*cursor == ARGLISTSEPERATESYM)
471 num_args++;
474 cursor = bookmark; /* Restoring the cursor */
475 element->params_count = num_args;
476 element->params = skin_alloc_params(num_args);
478 /* Now we have to actually parse each argument */
479 for(i = 0; i < num_args; i++)
481 /* Making sure we haven't run out of arguments */
482 if(*tag_args == '\0')
484 skin_error(TOO_MANY_ARGS);
485 return 0;
488 /* Checking for the optional bar */
489 if(*tag_args == '|')
491 optional = 1;
492 req_args = i;
493 tag_args++;
496 /* Scanning the arguments */
497 skip_whitespace(&cursor);
500 /* Checking for comments */
501 if(*cursor == COMMENTSYM)
502 skip_comment(&cursor);
504 /* Storing the type code */
505 element->params[i].type_code = *tag_args;
507 /* Checking a nullable argument for null */
508 if(*cursor == DEFAULTSYM && !isdigit(cursor[1]))
510 if(islower(*tag_args))
512 element->params[i].type = DEFAULT;
513 cursor++;
515 else
517 skin_error(DEFAULT_NOT_ALLOWED);
518 return 0;
521 else if(tolower(*tag_args) == 'i')
523 /* Scanning an int argument */
524 if(!isdigit(*cursor) && *cursor != '-')
526 skin_error(INT_EXPECTED);
527 return 0;
530 element->params[i].type = NUMERIC;
531 element->params[i].data.numeric = scan_int(&cursor);
533 else if(tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
535 /* Scanning a string argument */
536 element->params[i].type = STRING;
537 element->params[i].data.text = scan_string(&cursor);
540 else if(tolower(*tag_args) == 'c')
542 /* Recursively parsing a code argument */
543 element->params[i].type = CODE;
544 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
545 if(!element->params[i].data.code)
546 return 0;
549 skip_whitespace(&cursor);
551 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
553 skin_error(SEPERATOR_EXPECTED);
554 return 0;
556 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
558 skin_error(CLOSE_EXPECTED);
559 return 0;
561 else
563 cursor++;
566 tag_args++;
568 /* Checking for the optional bar */
569 if(*tag_args == '|')
571 optional = 1;
572 req_args = i + 1;
573 tag_args++;
578 /* Checking for a premature end */
579 if(*tag_args != '\0' && !optional)
581 skin_error(INSUFFICIENT_ARGS);
582 return 0;
585 *document = cursor;
587 return 1;
591 * If the conditional flag is set true, then parsing text will stop at an
592 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
594 int skin_parse_text(struct skin_element* element, char** document,
595 int conditional)
597 char* cursor = *document;
599 int length = 0;
601 int dest;
603 /* First figure out how much text we're copying */
604 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
605 && *cursor != COMMENTSYM
606 && !((*cursor == ARGLISTSEPERATESYM
607 || *cursor == ARGLISTCLOSESYM
608 || *cursor == ENUMLISTSEPERATESYM
609 || *cursor == ENUMLISTCLOSESYM)
610 && conditional))
612 /* Dealing with possibility of escaped characters */
613 if(*cursor == TAGSYM)
615 if(find_escape_character(cursor[1]))
616 cursor++;
617 else
618 break;
621 length++;
622 cursor++;
625 cursor = *document;
627 /* Copying the text into the element struct */
628 element->type = TEXT;
629 element->line = skin_line;
630 element->next = NULL;
631 element->text = skin_alloc_string(length);
633 for(dest = 0; dest < length; dest++)
635 /* Advancing cursor if we've encountered an escaped character */
636 if(*cursor == TAGSYM)
637 cursor++;
639 element->text[dest] = *cursor;
640 cursor++;
642 element->text[length] = '\0';
644 *document = cursor;
646 return 1;
649 int skin_parse_conditional(struct skin_element* element, char** document)
652 char* cursor = *document + 1; /* Starting past the "%" */
653 char* bookmark;
654 struct skin_element* tag = skin_alloc_element(); /* The tag to evaluate */
655 int children = 1;
656 int i;
657 int nested = 0;
659 element->type = CONDITIONAL;
660 element->line = skin_line;
662 /* Parsing the tag first */
663 if(!skin_parse_tag(tag, &cursor))
664 return 0;
666 /* Counting the children */
667 if(*(cursor++) != ENUMLISTOPENSYM)
669 skin_error(ARGLIST_EXPECTED);
670 return 0;
672 bookmark = cursor;
673 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
675 if(*cursor == COMMENTSYM)
677 skip_comment(&cursor);
678 continue;
681 if(*cursor == ENUMLISTOPENSYM)
683 nested++;
684 cursor++;
685 while(nested)
687 if(*cursor == ENUMLISTOPENSYM)
689 nested++;
691 else if(*cursor == ENUMLISTCLOSESYM)
693 nested--;
694 if(nested == 0)
695 break;
697 cursor++;
701 if(*cursor == TAGSYM)
703 cursor++;
704 if(*cursor == '\0' || *cursor == '\n')
705 break;
706 cursor++;
707 continue;
710 if(*cursor == ENUMLISTSEPERATESYM)
711 children++;
713 cursor++;
715 cursor = bookmark;
717 /* Parsing the children */
718 element->children_count = children + 1; /* Make sure to include the tag */
719 element->children = skin_alloc_children(children + 1);
720 element->children[0] = tag;
722 for(i = 1; i < children + 1; i++)
724 element->children[i] = skin_parse_code_as_arg(&cursor);
725 skip_whitespace(&cursor);
727 if(i < children && *cursor != ENUMLISTSEPERATESYM)
729 skin_error(SEPERATOR_EXPECTED);
730 return 0;
732 else if(i == children && *cursor != ENUMLISTCLOSESYM)
734 skin_error(CLOSE_EXPECTED);
735 return 0;
737 else
739 cursor++;
743 *document = cursor;
745 return 1;
748 int skin_parse_comment(struct skin_element* element, char** document)
750 char* cursor = *document;
752 int length;
754 * Finding the index of the ending newline or null-terminator
755 * The length of the string of interest doesn't include the leading #, the
756 * length we need to reserve is the same as the index of the last character
758 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
760 element->type = COMMENT;
761 element->line = skin_line;
762 element->text = skin_alloc_string(length);
763 /* We copy from one char past cursor to leave out the # */
764 memcpy((void*)(element->text), (void*)(cursor + 1),
765 sizeof(char) * (length-1));
766 element->text[length - 1] = '\0';
768 if(cursor[length] == '\n')
769 skin_line++;
771 *document += (length); /* Move cursor up past # and all text */
772 if(**document == '\n')
773 (*document)++;
775 return 1;
778 struct skin_element* skin_parse_code_as_arg(char** document)
781 int sublines = 0;
782 char* cursor = *document;
784 /* Checking for sublines */
785 while(*cursor != '\n' && *cursor != '\0')
787 if(*cursor == MULTILINESYM)
789 sublines = 1;
790 break;
792 else if(*cursor == TAGSYM)
794 /* A ';' directly after a '%' doesn't count */
795 cursor ++;
797 if(*cursor == '\0')
798 break;
800 cursor++;
802 else
804 /* Advancing the cursor as normal */
805 cursor++;
809 if(sublines)
810 return skin_parse_sublines_optional(document, 1);
811 else
812 return skin_parse_line_optional(document, 1);
816 /* Memory management */
817 struct skin_element* skin_alloc_element()
820 #if 0
822 char* retval = &skin_parse_tree[skin_current_block * 4];
824 int delta = sizeof(struct skin_element) / (sizeof(char) * 4);
826 /* If one block is partially filled, make sure to advance to the
827 * next one for the next allocation
829 if(sizeof(struct skin_element) % (sizeof(char) * 4) != 0)
830 delta++;
832 skin_current_block += delta;
834 return (struct skin_element*)retval;
836 #endif
838 struct skin_element* retval = (struct skin_element*)
839 malloc(sizeof(struct skin_element));
840 retval->next = NULL;
841 retval->params_count = 0;
842 retval->children_count = 0;
844 return retval;
848 struct skin_tag_parameter* skin_alloc_params(int count)
850 #if 0
852 char* retval = &skin_parse_tree[skin_current_block * 4];
854 int delta = sizeof(struct skin_tag_parameter) / (sizeof(char) * 4);
855 delta *= count;
857 /* Correcting uneven alignment */
858 if(count * sizeof(struct skin_tag_parameter) % (sizeof(char) * 4) != 0)
859 delta++;
861 skin_current_block += delta;
863 return (struct skin_tag_parameter*) retval;
865 #endif
867 return (struct skin_tag_parameter*)malloc(sizeof(struct skin_tag_parameter)
868 * count);
872 char* skin_alloc_string(int length)
875 #if 0
876 char* retval = &skin_parse_tree[skin_current_block * 4];
878 /* Checking alignment */
879 length++; /* Accounting for the null terminator */
880 int delta = length / 4;
881 if(length % 4 != 0)
882 delta++;
884 skin_current_block += delta;
886 if(skin_current_block >= SKIN_MAX_MEMORY / 4)
887 skin_error(MEMORY_LIMIT_EXCEEDED);
889 return retval;
891 #endif
893 return (char*)malloc(sizeof(char) * (length + 1));
897 struct skin_element** skin_alloc_children(int count)
899 return (struct skin_element**) malloc(sizeof(struct skin_element*) * count);
902 void skin_free_tree(struct skin_element* root)
904 int i;
906 /* First make the recursive call */
907 if(!root)
908 return;
909 skin_free_tree(root->next);
911 /* Free any text */
912 if(root->type == TEXT)
913 free(root->text);
915 /* Then recursively free any children, before freeing their pointers */
916 for(i = 0; i < root->children_count; i++)
917 skin_free_tree(root->children[i]);
918 if(root->children_count > 0)
919 free(root->children);
921 /* Free any parameters, making sure to deallocate strings */
922 for(i = 0; i < root->params_count; i++)
923 if(root->params[i].type == STRING)
924 free(root->params[i].data.text);
925 if(root->params_count > 0)
926 free(root->params);
928 /* Finally, delete root's memory */
929 free(root);