Theme Editor: Made Viewport the top level parse tree element, along with a bugfix...
[kugel-rb.git] / utils / themeeditor / skin_parser.c
blob74ba7bd71db5ba7df8f647449237fc4c83fea674
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(char* document)
59 struct skin_element* root = NULL;
60 struct skin_element* last = NULL;
62 struct skin_element** to_write = 0;
64 char* cursor = 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 = skin_alloc_children(1);
102 retval->children_count = 1;
103 retval->line = skin_line;
105 struct skin_element** to_write = 0;
107 char* cursor = *document; /* Keeps track of location in the document */
108 char* bookmark; /* Used when we need to look ahead */
110 int sublines = 0; /* Flag for parsing sublines */
112 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document))
115 /* First, we check to see if this line will contain sublines */
116 bookmark = cursor;
117 sublines = 0;
118 while(*cursor != '\n' && *cursor != '\0'
119 && !(check_viewport(cursor) && cursor != *document))
121 if(*cursor == MULTILINESYM)
123 sublines = 1;
124 break;
126 else if(*cursor == TAGSYM)
128 /* A ';' directly after a '%' doesn't count */
129 cursor ++;
131 if(*cursor == '\0')
132 break;
134 cursor++;
136 else if(*cursor == COMMENTSYM)
138 skip_comment(&cursor);
140 else
142 /* Advancing the cursor as normal */
143 cursor++;
146 cursor = bookmark;
148 if(!root)
149 to_write = &root;
150 else
151 to_write = &(last->next);
153 if(*cursor == '\n')
155 *to_write = skin_alloc_element();
156 skin_parse_newline(*to_write, &cursor);
157 if(!last)
158 return NULL;
160 else if(sublines)
162 *to_write = skin_parse_sublines(&cursor);
163 last = *to_write;
164 if(!last)
165 return NULL;
167 else
170 *to_write = skin_parse_line(&cursor);
171 last = *to_write;
172 if(!last)
173 return NULL;
177 /* Making sure last is at the end */
178 while(last->next)
179 last = last->next;
183 *document = cursor;
185 retval->children[0] = root;
186 return retval;
190 /* Auxiliary Parsing Functions */
192 struct skin_element* skin_parse_line(char**document)
195 return skin_parse_line_optional(document, 0);
201 * If conditional is set to true, then this will break upon encountering
202 * SEPERATESYM. This should only be used when parsing a line inside a
203 * conditional, otherwise just use the wrapper function skin_parse_line()
205 struct skin_element* skin_parse_line_optional(char** document, int conditional)
207 char* cursor = *document;
209 struct skin_element* root = NULL;
210 struct skin_element* current = NULL;
211 struct skin_element* retval = NULL;
213 /* A wrapper for the line */
214 retval = skin_alloc_element();
215 retval->type = LINE;
216 retval->line = skin_line;
217 retval->children_count = 1;
218 retval->children = skin_alloc_children(1);
220 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
221 && !((*cursor == ARGLISTSEPERATESYM
222 || *cursor == ARGLISTCLOSESYM
223 || *cursor == ENUMLISTSEPERATESYM
224 || *cursor == ENUMLISTCLOSESYM)
225 && conditional)
226 && !(check_viewport(cursor) && cursor != *document))
228 /* Allocating memory if necessary */
229 if(root)
231 current->next = skin_alloc_element();
232 current = current->next;
234 else
236 current = skin_alloc_element();
237 root = current;
240 /* Parsing the current element */
241 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
243 if(!skin_parse_conditional(current, &cursor))
244 return NULL;
246 else if(*cursor == TAGSYM)
248 if(!skin_parse_tag(current, &cursor))
249 return NULL;
251 else if(*cursor == COMMENTSYM)
253 if(!skin_parse_comment(current, &cursor))
254 return NULL;
256 else
258 if(!skin_parse_text(current, &cursor, conditional))
259 return NULL;
263 /* Moving up the calling function's pointer */
264 *document = cursor;
266 retval->children[0] = root;
267 return retval;
270 struct skin_element* skin_parse_sublines(char** document)
272 return skin_parse_sublines_optional(document, 0);
275 struct skin_element* skin_parse_sublines_optional(char** document,
276 int conditional)
278 struct skin_element* retval;
279 char* cursor = *document;
280 int sublines = 1;
281 int i;
283 retval = skin_alloc_element();
284 retval->type = SUBLINES;
285 retval->next = NULL;
286 retval->line = skin_line;
288 /* First we count the sublines */
289 while(*cursor != '\0' && *cursor != '\n'
290 && !((*cursor == ARGLISTSEPERATESYM
291 || *cursor == ARGLISTCLOSESYM
292 || *cursor == ENUMLISTSEPERATESYM
293 || *cursor == ENUMLISTCLOSESYM)
294 && conditional)
295 && !(check_viewport(cursor) && cursor != *document))
297 if(*cursor == COMMENTSYM)
298 skip_comment(&cursor);
300 /* Accounting for escaped subline symbols */
301 if(*cursor == TAGSYM)
303 cursor++;
304 if(*cursor == '\0' || *cursor == '\n')
305 break;
308 if(*cursor == MULTILINESYM)
310 sublines++;
313 cursor++;
316 /* ...and then we parse them */
317 retval->children_count = sublines;
318 retval->children = skin_alloc_children(sublines);
320 cursor = *document;
321 for(i = 0; i < sublines; i++)
323 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
324 skip_whitespace(&cursor);
326 if(*cursor != MULTILINESYM && i != sublines - 1)
328 skin_error(MULTILINE_EXPECTED);
329 return NULL;
331 else if(i != sublines - 1)
333 cursor++;
337 *document = cursor;
339 return retval;
342 int skin_parse_tag(struct skin_element* element, char** document)
345 char* cursor = *document + 1;
346 char* bookmark;
348 char tag_name[3];
349 char* tag_args;
351 int num_args = 1;
352 int i;
353 int star = 0; /* Flag for the all-or-none option */
354 int req_args; /* To mark when we enter optional arguments */
356 int optional = 0;
358 /* Checking the tag name */
359 tag_name[0] = cursor[0];
360 tag_name[1] = cursor[1];
361 tag_name[2] = '\0';
363 /* First we check the two characters after the '%', then a single char */
364 tag_args = find_tag(tag_name);
366 if(!tag_args)
368 tag_name[1] = '\0';
369 tag_args = find_tag(tag_name);
370 cursor++;
372 else
374 cursor += 2;
377 if(!tag_args)
379 skin_error(ILLEGAL_TAG);
380 return 0;
383 /* Copying basic tag info */
384 element->type = TAG;
385 strcpy(element->name, tag_name);
386 element->line = skin_line;
388 /* Checking for the * flag */
389 if(tag_args[0] == '*')
391 star = 1;
392 tag_args++;
395 /* If this tag has no arguments, we can bail out now */
396 if(strlen(tag_args) == 0
397 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM))
399 *document = cursor;
400 return 1;
403 /* Checking the number of arguments and allocating args */
404 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
406 skin_error(ARGLIST_EXPECTED);
407 return 0;
409 else
411 cursor++;
414 for(bookmark = cursor; *cursor != '\n' && *cursor != '\0' &&
415 *cursor != ARGLISTCLOSESYM; cursor++)
417 /* Skipping over escaped characters */
418 if(*cursor == TAGSYM)
420 cursor++;
421 if(*cursor == '\0')
422 break;
425 /* Skipping comments */
426 if(*cursor == COMMENTSYM)
427 skip_comment(&cursor);
429 /* Commas inside of contained lists don't count */
430 if(*cursor == ARGLISTOPENSYM)
431 while(*cursor != ARGLISTCLOSESYM && *cursor != '\0')
432 cursor++;
434 if(*cursor == ARGLISTSEPERATESYM)
435 num_args++;
438 cursor = bookmark; /* Restoring the cursor */
439 element->params_count = num_args;
440 element->params = skin_alloc_params(num_args);
442 /* Now we have to actually parse each argument */
443 for(i = 0; i < num_args; i++)
445 /* Making sure we haven't run out of arguments */
446 if(*tag_args == '\0')
448 skin_error(TOO_MANY_ARGS);
449 return 0;
452 /* Checking for the optional bar */
453 if(*tag_args == '|')
455 optional = 1;
456 req_args = i;
457 tag_args++;
460 /* Scanning the arguments */
461 skip_whitespace(&cursor);
464 /* Checking for comments */
465 if(*cursor == COMMENTSYM)
466 skip_comment(&cursor);
468 /* Checking a nullable argument for null */
469 if(*cursor == DEFAULTSYM)
471 if(islower(*tag_args))
473 element->params[i].type = DEFAULT;
474 cursor++;
476 else
478 skin_error(DEFAULT_NOT_ALLOWED);
479 return 0;
482 else if(tolower(*tag_args) == 'i')
484 /* Scanning an int argument */
485 if(!isdigit(*cursor))
487 skin_error(INT_EXPECTED);
488 return 0;
491 element->params[i].type = NUMERIC;
492 element->params[i].data.numeric = scan_int(&cursor);
494 else if(tolower(*tag_args) == 's' || tolower(*tag_args) == 'f')
496 /* Scanning a string argument */
497 element->params[i].type = STRING;
498 element->params[i].data.text = scan_string(&cursor);
501 else if(tolower(*tag_args) == 'c')
503 /* Recursively parsing a code argument */
504 element->params[i].type = CODE;
505 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
506 if(!element->params[i].data.code)
507 return 0;
510 skip_whitespace(&cursor);
512 if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1)
514 skin_error(SEPERATOR_EXPECTED);
515 return 0;
517 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
519 skin_error(CLOSE_EXPECTED);
520 return 0;
522 else
524 cursor++;
527 tag_args++;
529 /* Checking for the optional bar */
530 if(*tag_args == '|')
532 optional = 1;
533 req_args = i + 1;
534 tag_args++;
539 /* Checking for a premature end */
540 if(*tag_args != '\0' && !(optional && (!star || num_args == req_args)))
542 skin_error(INSUFFICIENT_ARGS);
543 return 0;
546 *document = cursor;
548 return 1;
552 * If the conditional flag is set true, then parsing text will stop at an
553 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
555 int skin_parse_text(struct skin_element* element, char** document,
556 int conditional)
558 char* cursor = *document;
560 int length = 0;
562 int dest;
564 /* First figure out how much text we're copying */
565 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
566 && *cursor != COMMENTSYM
567 && !((*cursor == ARGLISTSEPERATESYM
568 || *cursor == ARGLISTCLOSESYM
569 || *cursor == ENUMLISTSEPERATESYM
570 || *cursor == ENUMLISTCLOSESYM)
571 && conditional))
573 /* Dealing with possibility of escaped characters */
574 if(*cursor == TAGSYM)
576 if(find_escape_character(cursor[1]))
577 cursor++;
578 else
579 break;
582 length++;
583 cursor++;
586 cursor = *document;
588 /* Copying the text into the element struct */
589 element->type = TEXT;
590 element->line = skin_line;
591 element->next = NULL;
592 element->text = skin_alloc_string(length);
594 for(dest = 0; dest < length; dest++)
596 /* Advancing cursor if we've encountered an escaped character */
597 if(*cursor == TAGSYM)
598 cursor++;
600 element->text[dest] = *cursor;
601 cursor++;
603 element->text[length] = '\0';
605 *document = cursor;
607 return 1;
610 int skin_parse_conditional(struct skin_element* element, char** document)
613 char* cursor = *document + 1; /* Starting past the "%" */
614 char* bookmark;
615 struct skin_element* tag = skin_alloc_element(); /* The tag to evaluate */
616 int children = 1;
617 int i;
619 element->type = CONDITIONAL;
620 element->line = skin_line;
622 /* Parsing the tag first */
623 if(!skin_parse_tag(tag, &cursor))
624 return 0;
626 /* Counting the children */
627 if(*(cursor++) != ENUMLISTOPENSYM)
629 skin_error(ARGLIST_EXPECTED);
630 return 0;
632 bookmark = cursor;
633 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
635 if(*cursor == COMMENTSYM)
637 skip_comment(&cursor);
638 continue;
641 if(*cursor == TAGSYM)
643 cursor++;
644 if(*cursor == '\0' || *cursor == '\n')
645 break;
646 cursor++;
647 continue;
650 if(*cursor == ENUMLISTSEPERATESYM)
651 children++;
653 cursor++;
655 cursor = bookmark;
657 /* Parsing the children */
658 element->children_count = children + 1; /* Make sure to include the tag */
659 element->children = skin_alloc_children(children + 1);
660 element->children[0] = tag;
662 for(i = 1; i < children + 1; i++)
664 element->children[i] = skin_parse_code_as_arg(&cursor);
665 skip_whitespace(&cursor);
667 if(i < children && *cursor != ENUMLISTSEPERATESYM)
669 skin_error(SEPERATOR_EXPECTED);
670 return 0;
672 else if(i == children && *cursor != ENUMLISTCLOSESYM)
674 skin_error(CLOSE_EXPECTED);
675 return 0;
677 else
679 cursor++;
683 *document = cursor;
685 return 1;
688 int skin_parse_newline(struct skin_element* element, char** document)
691 char* cursor = *document;
692 if(*cursor != '\n')
694 skin_error(NEWLINE_EXPECTED);
695 return 0;
697 cursor++;
699 /* Assembling a skin_element struct for a newline */
700 element->type = NEWLINE;
701 element->line = skin_line;
702 skin_line++;
703 element->next = NULL;
705 *document = cursor;
707 return 1;
710 int skin_parse_comment(struct skin_element* element, char** document)
712 char* cursor = *document;
714 int length;
716 * Finding the index of the ending newline or null-terminator
717 * The length of the string of interest doesn't include the leading #, the
718 * length we need to reserve is the same as the index of the last character
720 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
722 element->type = COMMENT;
723 element->line = skin_line;
724 element->text = skin_alloc_string(length);
725 /* We copy from one char past cursor to leave out the # */
726 memcpy((void*)(element->text), (void*)(cursor + 1), sizeof(char) * length);
727 element->text[length] = '\0';
729 if(cursor[length] == '\n')
730 skin_line++;
732 *document += (length + 1); /* Move cursor up past # and all text */
734 return 1;
737 struct skin_element* skin_parse_code_as_arg(char** document)
740 int sublines = 0;
741 char* cursor = *document;
743 /* Checking for sublines */
744 while(*cursor != '\n' && *cursor != '\0')
746 if(*cursor == MULTILINESYM)
748 sublines = 1;
749 break;
751 else if(*cursor == TAGSYM)
753 /* A ';' directly after a '%' doesn't count */
754 cursor ++;
756 if(*cursor == '\0')
757 break;
759 cursor++;
761 else
763 /* Advancing the cursor as normal */
764 cursor++;
768 if(sublines)
769 return skin_parse_sublines_optional(document, 1);
770 else
771 return skin_parse_line_optional(document, 1);
775 /* Memory management */
776 struct skin_element* skin_alloc_element()
779 #if 0
781 char* retval = &skin_parse_tree[skin_current_block * 4];
783 int delta = sizeof(struct skin_element) / (sizeof(char) * 4);
785 /* If one block is partially filled, make sure to advance to the
786 * next one for the next allocation
788 if(sizeof(struct skin_element) % (sizeof(char) * 4) != 0)
789 delta++;
791 skin_current_block += delta;
793 return (struct skin_element*)retval;
795 #endif
797 struct skin_element* retval = (struct skin_element*)
798 malloc(sizeof(struct skin_element));
799 retval->next = NULL;
800 retval->params_count = 0;
801 retval->children_count = 0;
803 return retval;
807 struct skin_tag_parameter* skin_alloc_params(int count)
809 #if 0
811 char* retval = &skin_parse_tree[skin_current_block * 4];
813 int delta = sizeof(struct skin_tag_parameter) / (sizeof(char) * 4);
814 delta *= count;
816 /* Correcting uneven alignment */
817 if(count * sizeof(struct skin_tag_parameter) % (sizeof(char) * 4) != 0)
818 delta++;
820 skin_current_block += delta;
822 return (struct skin_tag_parameter*) retval;
824 #endif
826 return (struct skin_tag_parameter*)malloc(sizeof(struct skin_tag_parameter)
827 * count);
831 char* skin_alloc_string(int length)
834 #if 0
835 char* retval = &skin_parse_tree[skin_current_block * 4];
837 /* Checking alignment */
838 length++; /* Accounting for the null terminator */
839 int delta = length / 4;
840 if(length % 4 != 0)
841 delta++;
843 skin_current_block += delta;
845 if(skin_current_block >= SKIN_MAX_MEMORY / 4)
846 skin_error(MEMORY_LIMIT_EXCEEDED);
848 return retval;
850 #endif
852 return (char*)malloc(sizeof(char) * (length + 1));
856 struct skin_element** skin_alloc_children(int count)
858 return (struct skin_element**) malloc(sizeof(struct skin_element*) * count);
861 void skin_free_tree(struct skin_element* root)
863 int i;
865 /* First make the recursive call */
866 if(!root)
867 return;
868 skin_free_tree(root->next);
870 /* Free any text */
871 if(root->type == TEXT)
872 free(root->text);
874 /* Then recursively free any children, before freeing their pointers */
875 for(i = 0; i < root->children_count; i++)
876 skin_free_tree(root->children[i]);
877 if(root->children_count > 0)
878 free(root->children);
880 /* Free any parameters, making sure to deallocate strings */
881 for(i = 0; i < root->params_count; i++)
882 if(root->params[i].type == STRING)
883 free(root->params[i].data.text);
884 if(root->params_count > 0)
885 free(root->params);
887 /* Finally, delete root's memory */
888 free(root);