1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
28 #include "skin_buffer.h"
29 #include "skin_parser.h"
30 #include "skin_debug.h"
31 #include "tag_table.h"
33 #include "skin_scan.h"
35 /* Global variables for the parser */
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
,
44 static struct skin_element
* skin_parse_sublines(char** document
);
45 static struct skin_element
* skin_parse_sublines_optional(char** document
,
48 static int skin_parse_tag(struct skin_element
* element
, char** document
);
49 static int skin_parse_text(struct skin_element
* element
, char** document
,
51 static int skin_parse_conditional(struct skin_element
* element
,
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*/
73 while(*cursor
!= '\0')
79 to_write
= &(last
->next
);
82 *to_write
= skin_parse_viewport(&cursor
);
86 skin_free_tree(root
); /* Clearing any memory already used */
90 /* Making sure last is at the end */
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
);
131 retval
->children_count
= 1;
132 retval
->children
= skin_alloc_children(1);
138 /* First, we check to see if this line will contain sublines */
141 while(*cursor
!= '\n' && *cursor
!= '\0'
142 && !(check_viewport(cursor
) && cursor
!= *document
))
144 if(*cursor
== MULTILINESYM
)
149 else if(*cursor
== TAGSYM
)
151 /* A ';' directly after a '%' doesn't count */
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
);
173 /* Advancing the cursor as normal */
182 to_write
= &(last
->next
);
186 *to_write
= skin_parse_sublines(&cursor
);
194 *to_write
= skin_parse_line(&cursor
);
201 /* Making sure last is at the end */
211 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
215 retval
->children
[0] = root
;
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
,
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();
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;
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
)
270 && !(check_viewport(cursor
) && cursor
!= *document
))
272 /* Allocating memory if necessary */
275 current
->next
= skin_alloc_element();
276 current
= current
->next
;
280 current
= skin_alloc_element();
284 /* Parsing the current element */
285 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
287 if(!skin_parse_conditional(current
, &cursor
))
290 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
292 if(!skin_parse_tag(current
, &cursor
))
295 else if(*cursor
== COMMENTSYM
)
297 if(!skin_parse_comment(current
, &cursor
))
302 if(!skin_parse_text(current
, &cursor
, conditional
))
307 /* Moving up the calling function's pointer */
311 retval
->children
[0] = root
;
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
,
323 struct skin_element
* retval
;
324 char* cursor
= *document
;
328 retval
= skin_alloc_element();
329 retval
->type
= LINE_ALTERNATOR
;
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
)
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
)
357 if(*cursor
== '\0' || *cursor
== '\n')
361 else if(*cursor
== MULTILINESYM
)
372 /* ...and then we parse them */
373 retval
->children_count
= sublines
;
374 retval
->children
= skin_alloc_children(sublines
);
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
);
387 else if(i
!= sublines
- 1)
398 static int skin_parse_tag(struct skin_element
* element
, char** document
)
401 char* cursor
= *document
+ 1;
406 struct tag_info
*tag
;
410 int star
= 0; /* Flag for the all-or-none option */
411 int req_args
; /* To mark when we enter optional arguments */
415 /* Checking the tag name */
416 tag_name
[0] = cursor
[0];
417 tag_name
[1] = cursor
[1];
420 /* First we check the two characters after the '%', then a single char */
421 tag
= find_tag(tag_name
);
426 tag
= find_tag(tag_name
);
436 skin_error(ILLEGAL_TAG
);
440 /* Copying basic tag info */
441 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
444 tag_args
= tag
->params
;
445 element
->line
= skin_line
;
447 /* Checking for the * flag */
448 if(tag_args
[0] == '*')
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
))
464 /* Checking the number of arguments and allocating args */
465 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
467 skin_error(ARGLIST_EXPECTED
);
476 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
478 /* Skipping over escaped characters */
479 if(*cursor
== TAGSYM
)
486 else if(*cursor
== COMMENTSYM
)
488 skip_comment(&cursor
);
490 else if(*cursor
== ARGLISTOPENSYM
)
492 skip_arglist(&cursor
);
494 else if(*cursor
== ARGLISTSEPERATESYM
)
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
);
519 /* Checking for the optional bar */
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
;
548 skin_error(DEFAULT_NOT_ALLOWED
);
552 else if(tolower(*tag_args
) == 'i')
554 /* Scanning an int argument */
555 if(!isdigit(*cursor
) && *cursor
!= '-')
557 skin_error(INT_EXPECTED
);
561 element
->params
[i
].type
= INTEGER
;
562 element
->params
[i
].data
.number
= scan_int(&cursor
);
564 else if(tolower(*tag_args
) == 'd')
567 bool have_point
= false;
568 bool have_tenth
= false;
569 while ( isdigit(*cursor
) || *cursor
== '.' )
574 val
+= *cursor
- '0';
586 if (have_tenth
== false)
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
)
609 skip_whitespace(&cursor
);
611 if(*cursor
!= ARGLISTSEPERATESYM
&& i
< num_args
- 1)
613 skin_error(SEPERATOR_EXPECTED
);
616 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
618 skin_error(CLOSE_EXPECTED
);
626 if (*tag_args
!= 'N')
629 /* Checking for the optional bar */
639 /* Checking for a premature end */
640 if(*tag_args
!= '\0' && !optional
)
642 skin_error(INSUFFICIENT_ARGS
);
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
,
658 char* cursor
= *document
;
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
)
672 /* Dealing with possibility of escaped characters */
673 if(*cursor
== TAGSYM
)
675 if(find_escape_character(cursor
[1]))
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
)
699 text
[dest
] = *cursor
;
709 static int skin_parse_conditional(struct skin_element
* element
, char** document
)
712 char* cursor
= *document
+ 1; /* Starting past the "%" */
717 element
->type
= CONDITIONAL
;
718 element
->line
= skin_line
;
720 /* Parsing the tag first */
721 if(!skin_parse_tag(element
, &cursor
))
724 /* Counting the children */
725 if(*(cursor
++) != ENUMLISTOPENSYM
)
727 skin_error(ARGLIST_EXPECTED
);
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
)
744 if(*cursor
== '\0' || *cursor
== '\n')
748 else if(*cursor
== ENUMLISTSEPERATESYM
)
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
);
774 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
776 skin_error(CLOSE_EXPECTED
);
790 static int skin_parse_comment(struct skin_element
* element
, char** document
)
792 char* cursor
= *document
;
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
;
807 element
->data
= NULL
;
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';
815 if(cursor
[length
] == '\n')
818 *document
+= (length
); /* Move cursor up past # and all text */
819 if(**document
== '\n')
825 static struct skin_element
* skin_parse_code_as_arg(char** document
)
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
)
841 else if(*cursor
== TAGSYM
)
843 /* A ';' directly after a '%' doesn't count */
851 else if(*cursor
== ARGLISTOPENSYM
)
853 skip_arglist(&cursor
);
855 else if(*cursor
== ENUMLISTOPENSYM
)
857 skip_enumlist(&cursor
);
861 /* Advancing the cursor as normal */
867 return skin_parse_sublines_optional(document
, 1);
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
;
881 retval
->params_count
= 0;
882 retval
->children_count
= 0;
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
)
911 /* First make the recursive call */
914 skin_free_tree(root
->next
);
917 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
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)
933 /* Finally, delete root's memory */