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 ****************************************************************************/
29 #include "skin_buffer.h"
30 #include "skin_parser.h"
31 #include "skin_debug.h"
32 #include "tag_table.h"
34 #include "skin_scan.h"
36 /* Global variables for the parser */
39 int viewport_line
= 0;
41 static int tag_recursion_level
= 0;
44 static skin_callback callback
= NULL
;
45 static void* callback_data
;
48 /* Auxiliary parsing functions (not visible at global scope) */
49 static struct skin_element
* skin_parse_viewport(const char** document
);
50 static struct skin_element
* skin_parse_line(const char** document
);
51 static struct skin_element
* skin_parse_line_optional(const char** document
,
53 static struct skin_element
* skin_parse_sublines(const char** document
);
54 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
57 static int skin_parse_tag(struct skin_element
* element
, const char** document
);
58 static int skin_parse_text(struct skin_element
* element
, const char** document
,
60 static int skin_parse_conditional(struct skin_element
* element
,
61 const char** document
);
62 static int skin_parse_comment(struct skin_element
* element
, const char** document
);
63 static struct skin_element
* skin_parse_code_as_arg(const char** document
);
66 static void skip_whitespace(const char** document
)
68 while(**document
== ' ' || **document
== '\t')
73 struct skin_element
* skin_parse(const char* document
,
74 skin_callback cb
, void* cb_data
)
77 callback_data
= cb_data
;
79 struct skin_element
* skin_parse(const char* document
)
82 struct skin_element
* root
= NULL
;
83 struct skin_element
* last
= NULL
;
85 struct skin_element
** to_write
= 0;
87 const char* cursor
= document
; /*Keeps track of location in the document*/
90 skin_start
= (char*)document
;
95 while(*cursor
!= '\0')
101 to_write
= &(last
->next
);
104 *to_write
= skin_parse_viewport(&cursor
);
108 skin_free_tree(root
); /* Clearing any memory already used */
112 /* Making sure last is at the end */
121 static struct skin_element
* skin_parse_viewport(const char** document
)
123 struct skin_element
* root
= NULL
;
124 struct skin_element
* last
= NULL
;
125 struct skin_element
* retval
= NULL
;
127 tag_recursion_level
= 0;
129 retval
= skin_alloc_element();
132 retval
->type
= VIEWPORT
;
133 retval
->children_count
= 1;
134 retval
->line
= skin_line
;
135 viewport_line
= skin_line
;
137 struct skin_element
** to_write
= 0;
139 const char* cursor
= *document
; /* Keeps track of location in the document */
140 const char* bookmark
; /* Used when we need to look ahead */
142 int sublines
= 0; /* Flag for parsing sublines */
144 /* Parsing out the viewport tag if there is one */
145 if(check_viewport(cursor
))
147 skin_parse_tag(retval
, &cursor
);
157 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
162 if (check_viewport(cursor
))
164 retval
->children_count
= 0;
168 retval
->children_count
= 1;
169 retval
->children
= skin_alloc_children(1);
170 if (!retval
->children
)
175 /* First, we check to see if this line will contain sublines */
178 while(*cursor
!= '\n' && *cursor
!= '\0'
179 && !(check_viewport(cursor
) && cursor
!= *document
))
181 if(*cursor
== MULTILINESYM
)
186 else if(*cursor
== TAGSYM
)
188 /* A ';' directly after a '%' doesn't count */
196 else if(*cursor
== COMMENTSYM
)
198 skip_comment(&cursor
);
200 else if(*cursor
== ARGLISTOPENSYM
)
202 skip_arglist(&cursor
);
204 else if(*cursor
== ENUMLISTOPENSYM
)
206 skip_enumlist(&cursor
);
210 /* Advancing the cursor as normal */
219 to_write
= &(last
->next
);
223 *to_write
= skin_parse_sublines(&cursor
);
231 *to_write
= skin_parse_line(&cursor
);
237 /* Making sure last is at the end */
247 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
251 retval
->children
[0] = root
;
255 /* Auxiliary Parsing Functions */
257 static struct skin_element
* skin_parse_line(const char**document
)
259 return skin_parse_line_optional(document
, 0);
263 * If conditional is set to true, then this will break upon encountering
264 * SEPARATESYM. This should only be used when parsing a line inside a
265 * conditional, otherwise just use the wrapper function skin_parse_line()
267 static struct skin_element
* skin_parse_line_optional(const char** document
,
270 const char* cursor
= *document
;
272 struct skin_element
* root
= NULL
;
273 struct skin_element
* current
= NULL
;
274 struct skin_element
* retval
= NULL
;
276 /* A wrapper for the line */
277 retval
= skin_alloc_element();
281 retval
->line
= skin_line
;
282 if(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
283 && !(conditional
&& (*cursor
== ARGLISTSEPARATESYM
284 || *cursor
== ARGLISTCLOSESYM
285 || *cursor
== ENUMLISTSEPARATESYM
286 || *cursor
== ENUMLISTCLOSESYM
)))
288 retval
->children_count
= 1;
292 retval
->children_count
= 0;
295 if(retval
->children_count
> 0)
297 retval
->children
= skin_alloc_children(1);
298 if (!retval
->children
)
305 switch (callback(retval
, callback_data
))
315 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
316 && !((*cursor
== ARGLISTSEPARATESYM
317 || *cursor
== ARGLISTCLOSESYM
318 || *cursor
== ENUMLISTSEPARATESYM
319 || *cursor
== ENUMLISTCLOSESYM
)
321 && !(check_viewport(cursor
) && cursor
!= *document
))
323 /* Allocating memory if necessary */
326 current
->next
= skin_alloc_element();
329 current
= current
->next
;
333 current
= skin_alloc_element();
339 /* Parsing the current element */
340 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
342 if(!skin_parse_conditional(current
, &cursor
))
345 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
347 if(!skin_parse_tag(current
, &cursor
))
350 else if(*cursor
== COMMENTSYM
)
352 if(!skin_parse_comment(current
, &cursor
))
357 if(!skin_parse_text(current
, &cursor
, conditional
))
363 /* Moving up the calling function's pointer */
367 retval
->children
[0] = root
;
371 static struct skin_element
* skin_parse_sublines(const char** document
)
373 return skin_parse_sublines_optional(document
, 0);
376 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
379 struct skin_element
* retval
;
380 const char* cursor
= *document
;
384 retval
= skin_alloc_element();
387 retval
->type
= LINE_ALTERNATOR
;
389 retval
->line
= skin_line
;
391 /* First we count the sublines */
392 while(*cursor
!= '\0' && *cursor
!= '\n'
393 && !((*cursor
== ARGLISTSEPARATESYM
394 || *cursor
== ARGLISTCLOSESYM
395 || *cursor
== ENUMLISTSEPARATESYM
396 || *cursor
== ENUMLISTCLOSESYM
)
398 && !(check_viewport(cursor
) && cursor
!= *document
))
400 if(*cursor
== COMMENTSYM
)
402 skip_comment(&cursor
);
404 else if(*cursor
== ENUMLISTOPENSYM
)
406 skip_enumlist(&cursor
);
408 else if(*cursor
== ARGLISTOPENSYM
)
410 skip_arglist(&cursor
);
412 else if(*cursor
== TAGSYM
)
415 if(*cursor
== '\0' || *cursor
== '\n')
419 else if(*cursor
== MULTILINESYM
)
430 /* ...and then we parse them */
431 retval
->children_count
= sublines
;
432 retval
->children
= skin_alloc_children(sublines
);
433 if (!retval
->children
)
437 for(i
= 0; i
< sublines
; i
++)
439 retval
->children
[i
] = skin_parse_line_optional(&cursor
, conditional
);
440 skip_whitespace(&cursor
);
442 if(*cursor
!= MULTILINESYM
&& i
!= sublines
- 1)
444 skin_error(MULTILINE_EXPECTED
, cursor
);
447 else if(i
!= sublines
- 1)
456 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
465 static int skin_parse_tag(struct skin_element
* element
, const char** document
)
467 const char* cursor
= *document
+ 1;
468 const char* bookmark
;
472 const struct tag_info
*tag
;
476 int star
= 0; /* Flag for the all-or-none option */
477 int req_args
; /* To mark when we enter optional arguments */
480 tag_recursion_level
++;
482 /* Checking the tag name */
483 tag_name
[0] = cursor
[0];
484 tag_name
[1] = cursor
[1];
487 /* First we check the two characters after the '%', then a single char */
488 tag
= find_tag(tag_name
);
493 tag
= find_tag(tag_name
);
503 skin_error(ILLEGAL_TAG
, cursor
);
507 /* Copying basic tag info */
508 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
511 tag_args
= tag
->params
;
512 element
->line
= skin_line
;
514 /* Checking for the * flag */
515 if(tag_args
[0] == '*')
521 /* If this tag has no arguments, we can bail out now */
522 if(strlen(tag_args
) == 0
523 || (tag_args
[0] == '|' && *cursor
!= ARGLISTOPENSYM
)
524 || (star
&& *cursor
!= ARGLISTOPENSYM
))
530 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
538 /* Checking the number of arguments and allocating args */
539 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
541 skin_error(ARGLIST_EXPECTED
, cursor
);
550 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
552 /* Skipping over escaped characters */
553 if(*cursor
== TAGSYM
)
560 else if(*cursor
== COMMENTSYM
)
562 skip_comment(&cursor
);
564 else if(*cursor
== ARGLISTOPENSYM
)
566 skip_arglist(&cursor
);
568 else if(*cursor
== ARGLISTSEPARATESYM
)
579 cursor
= bookmark
; /* Restoring the cursor */
580 element
->params_count
= num_args
;
581 element
->params
= skin_alloc_params(num_args
, tag_recursion_level
<=1);
582 if (!element
->params
)
585 /* Now we have to actually parse each argument */
586 for(i
= 0; i
< num_args
; i
++)
589 /* Making sure we haven't run out of arguments */
590 if(*tag_args
== '\0')
592 skin_error(TOO_MANY_ARGS
, cursor
);
596 /* Checking for the optional bar */
604 /* Scanning the arguments */
605 skip_whitespace(&cursor
);
607 /* Checking for comments */
608 if(*cursor
== COMMENTSYM
)
609 skip_comment(&cursor
);
611 if (*tag_args
== '[')
613 /* we need to guess which type of param it is.
614 * guess using this priority:
615 * default > decimal/integer > single tag/code > string
618 bool canbedefault
= false;
619 bool haspercent
= false, number
= true, hasdecimal
= false;
622 while (*tag_args
!= ']')
624 if (*tag_args
>= 'a' && *tag_args
<= 'z')
626 temp_params
[j
++] = tolower(*tag_args
++);
628 temp_params
[j
] = '\0';
630 while (cursor
[j
] && cursor
[j
] != ',' && cursor
[j
] != ')')
632 haspercent
= haspercent
|| (cursor
[j
] == '%');
633 hasdecimal
= hasdecimal
|| (cursor
[j
] == '.');
634 number
= number
&& (isdigit(cursor
[j
]) ||
635 (cursor
[j
] == '.') ||
640 if (canbedefault
&& *cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
644 else if (number
&& hasdecimal
&& strchr(temp_params
, 'd'))
649 (strchr(temp_params
, 'i') || strchr(temp_params
, 'd')))
651 type_code
= strchr(temp_params
, 'i') ? 'i' : 'd';
653 else if (haspercent
&&
654 (strchr(temp_params
, 't') || strchr(temp_params
, 'c')))
656 type_code
= strchr(temp_params
, 't') ? 't' : 'c';
658 else if (strchr(temp_params
, 's'))
662 if (type_code
== '*')
664 skin_error(INSUFFICIENT_ARGS
, cursor
);
669 type_code
= *tag_args
;
670 /* Storing the type code */
671 element
->params
[i
].type_code
= type_code
;
673 /* Checking a nullable argument for null. */
674 if(*cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
676 if(islower(type_code
))
678 element
->params
[i
].type
= DEFAULT
;
683 skin_error(DEFAULT_NOT_ALLOWED
, cursor
);
687 else if(tolower(type_code
) == 'i')
689 /* Scanning an int argument */
690 if(!isdigit(*cursor
) && *cursor
!= '-')
692 skin_error(INT_EXPECTED
, cursor
);
696 element
->params
[i
].type
= INTEGER
;
697 element
->params
[i
].data
.number
= scan_int(&cursor
);
699 else if(tolower(type_code
) == 'd')
702 bool have_point
= false;
703 bool have_tenth
= false;
704 while ( isdigit(*cursor
) || *cursor
== '.' )
709 val
+= *cursor
- '0';
721 if (have_tenth
== false)
723 element
->params
[i
].type
= DECIMAL
;
724 element
->params
[i
].data
.number
= val
;
726 else if(tolower(type_code
) == 'n' ||
727 tolower(type_code
) == 's' || tolower(type_code
) == 'f')
729 /* Scanning a string argument */
730 element
->params
[i
].type
= STRING
;
731 element
->params
[i
].data
.text
= scan_string(&cursor
);
734 else if(tolower(type_code
) == 'c')
736 /* Recursively parsing a code argument */
737 element
->params
[i
].type
= CODE
;
738 element
->params
[i
].data
.code
= skin_parse_code_as_arg(&cursor
);
739 if(!element
->params
[i
].data
.code
)
742 else if (tolower(type_code
) == 't')
744 struct skin_element
* child
= skin_alloc_element();
746 if (!skin_parse_tag(child
, &cursor
))
749 element
->params
[i
].type
= CODE
;
750 element
->params
[i
].data
.code
= child
;
754 skip_whitespace(&cursor
);
756 if(*cursor
!= ARGLISTSEPARATESYM
&& i
< num_args
- 1)
758 skin_error(SEPARATOR_EXPECTED
, cursor
);
761 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
763 skin_error(CLOSE_EXPECTED
, cursor
);
771 if (*tag_args
!= 'N')
774 /* Checking for the optional bar */
783 /* Checking for a premature end */
784 if(*tag_args
!= '\0' && !optional
)
786 skin_error(INSUFFICIENT_ARGS
, cursor
);
792 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
797 tag_recursion_level
--;
803 * If the conditional flag is set true, then parsing text will stop at an
804 * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional
806 static int skin_parse_text(struct skin_element
* element
, const char** document
,
809 const char* cursor
= *document
;
814 /* First figure out how much text we're copying */
815 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
816 && *cursor
!= COMMENTSYM
817 && !((*cursor
== ARGLISTSEPARATESYM
818 || *cursor
== ARGLISTCLOSESYM
819 || *cursor
== ENUMLISTSEPARATESYM
820 || *cursor
== ENUMLISTCLOSESYM
)
823 /* Dealing with possibility of escaped characters */
824 if(*cursor
== TAGSYM
)
826 if(find_escape_character(cursor
[1]))
838 /* Copying the text into the element struct */
839 element
->type
= TEXT
;
840 element
->line
= skin_line
;
841 element
->next
= NULL
;
842 element
->data
= text
= skin_alloc_string(length
);
846 for(dest
= 0; dest
< length
; dest
++)
848 /* Advancing cursor if we've encountered an escaped character */
849 if(*cursor
== TAGSYM
)
852 text
[dest
] = *cursor
;
860 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
870 static int skin_parse_conditional(struct skin_element
* element
, const char** document
)
872 const char* cursor
= *document
+ 1; /* Starting past the "%" */
873 const char* bookmark
;
878 bool feature_available
= true;
879 const char *false_branch
= NULL
;
880 const char *conditional_end
= NULL
;
883 /* Some conditional tags allow for target feature checking,
884 * so to handle that call the callback as usual with type == TAG
885 * then call it a second time with type == CONDITIONAL and check the return
888 element
->line
= skin_line
;
890 /* Parsing the tag first */
891 if(!skin_parse_tag(element
, &cursor
))
894 element
->type
= CONDITIONAL
;
898 switch (callback(element
, callback_data
))
900 case FEATURE_NOT_AVAILABLE
:
901 feature_available
= false;
911 /* Counting the children */
912 if(*(cursor
++) != ENUMLISTOPENSYM
)
914 skin_error(ARGLIST_EXPECTED
, cursor
);
918 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\n' && *cursor
!= '\0')
920 if(*cursor
== COMMENTSYM
)
922 skip_comment(&cursor
);
924 else if(*cursor
== ENUMLISTOPENSYM
)
926 skip_enumlist(&cursor
);
928 else if(*cursor
== TAGSYM
)
931 if(*cursor
== '\0' || *cursor
== '\n')
935 else if(*cursor
== ENUMLISTSEPARATESYM
)
940 if (false_branch
== NULL
&& !feature_available
)
942 false_branch
= cursor
;
953 if (*cursor
== ENUMLISTCLOSESYM
&&
954 false_branch
== NULL
&& !feature_available
)
956 false_branch
= cursor
+1;
959 if (element
->tag
->flags
&FEATURE_TAG
)
961 if (feature_available
&& children
> 1)
964 conditional_end
= cursor
;
965 /* if we are skipping the true branch fix that up */
966 cursor
= false_branch
? false_branch
: bookmark
;
970 /* Parsing the children */
971 element
->children
= skin_alloc_children(children
);
972 if (!element
->children
)
974 element
->children_count
= children
;
976 for(i
= 0; i
< children
; i
++)
978 element
->children
[i
] = skin_parse_code_as_arg(&cursor
);
979 if (element
->children
[i
] == NULL
)
981 skip_whitespace(&cursor
);
983 if ((element
->tag
->flags
&FEATURE_TAG
) && feature_available
)
984 cursor
= conditional_end
;
987 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPARATESYM
)
989 skin_error(SEPARATOR_EXPECTED
, cursor
);
992 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
994 skin_error(CLOSE_EXPECTED
, cursor
);
1007 static int skin_parse_comment(struct skin_element
* element
, const char** document
)
1009 const char* cursor
= *document
;
1015 * Finding the index of the ending newline or null-terminator
1016 * The length of the string of interest doesn't include the leading #, the
1017 * length we need to reserve is the same as the index of the last character
1019 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
1021 element
->type
= COMMENT
;
1022 element
->line
= skin_line
;
1024 element
->data
= NULL
;
1026 element
->data
= text
= skin_alloc_string(length
);
1029 /* We copy from one char past cursor to leave out the # */
1030 memcpy((void*)text
, (void*)(cursor
+ 1),
1031 sizeof(char) * (length
-1));
1032 text
[length
- 1] = '\0';
1034 if(cursor
[length
] == '\n')
1037 *document
+= (length
); /* Move cursor up past # and all text */
1038 if(**document
== '\n')
1044 static struct skin_element
* skin_parse_code_as_arg(const char** document
)
1047 const char* cursor
= *document
;
1049 /* Checking for sublines */
1050 while(*cursor
!= '\n' && *cursor
!= '\0'
1051 && *cursor
!= ENUMLISTSEPARATESYM
&& *cursor
!= ARGLISTSEPARATESYM
1052 && *cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= ARGLISTCLOSESYM
)
1054 if(*cursor
== MULTILINESYM
)
1059 else if(*cursor
== TAGSYM
)
1061 /* A ';' directly after a '%' doesn't count */
1069 else if(*cursor
== ARGLISTOPENSYM
)
1071 skip_arglist(&cursor
);
1073 else if(*cursor
== ENUMLISTOPENSYM
)
1075 skip_enumlist(&cursor
);
1079 /* Advancing the cursor as normal */
1085 return skin_parse_sublines_optional(document
, 1);
1087 return skin_parse_line_optional(document
, 1);
1091 /* Memory management */
1092 struct skin_element
* skin_alloc_element()
1094 struct skin_element
* retval
= (struct skin_element
*)
1095 skin_buffer_alloc(sizeof(struct skin_element
));
1098 retval
->type
= UNKNOWN
;
1099 retval
->next
= NULL
;
1101 retval
->params_count
= 0;
1102 retval
->children_count
= 0;
1107 /* On a ROCKBOX build we try to save space as much as possible
1108 * so if we can, use a shared param pool which should be more then large
1109 * enough for any tag. params should be used straight away by the callback
1112 struct skin_tag_parameter
* skin_alloc_params(int count
, bool use_shared_params
)
1115 static struct skin_tag_parameter params
[MAX_TAG_PARAMS
];
1116 if (use_shared_params
&& count
<= MAX_TAG_PARAMS
)
1118 memset(params
, 0, sizeof(params
));
1122 size_t size
= sizeof(struct skin_tag_parameter
) * count
;
1123 return (struct skin_tag_parameter
*)skin_buffer_alloc(size
);
1127 char* skin_alloc_string(int length
)
1129 return (char*)skin_buffer_alloc(sizeof(char) * (length
+ 1));
1132 struct skin_element
** skin_alloc_children(int count
)
1134 return (struct skin_element
**)
1135 skin_buffer_alloc(sizeof(struct skin_element
*) * count
);
1138 void skin_free_tree(struct skin_element
* root
)
1143 /* First make the recursive call */
1146 skin_free_tree(root
->next
);
1149 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
1152 /* Then recursively free any children, before freeing their pointers */
1153 for(i
= 0; i
< root
->children_count
; i
++)
1154 skin_free_tree(root
->children
[i
]);
1155 if(root
->children_count
> 0)
1156 free(root
->children
);
1158 /* Free any parameters, making sure to deallocate strings */
1159 for(i
= 0; i
< root
->params_count
; i
++)
1160 if(root
->params
[i
].type
== STRING
)
1161 free(root
->params
[i
].data
.text
);
1162 if(root
->params_count
> 0)
1165 /* Finally, delete root's memory */