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 ****************************************************************************/
27 #include "skin_buffer.h"
28 #include "skin_parser.h"
29 #include "skin_debug.h"
30 #include "tag_table.h"
32 #include "skin_scan.h"
34 /* Global variables for the parser */
36 int viewport_line
= 0;
38 /* Auxiliary parsing functions (not visible at global scope) */
39 static struct skin_element
* skin_parse_viewport(char** document
);
40 static struct skin_element
* skin_parse_line(char** document
);
41 static struct skin_element
* skin_parse_line_optional(char** document
,
43 static struct skin_element
* skin_parse_sublines(char** document
);
44 static struct skin_element
* skin_parse_sublines_optional(char** document
,
47 static int skin_parse_tag(struct skin_element
* element
, char** document
);
48 static int skin_parse_text(struct skin_element
* element
, char** document
,
50 static int skin_parse_conditional(struct skin_element
* element
,
52 static int skin_parse_comment(struct skin_element
* element
, char** document
);
53 static struct skin_element
* skin_parse_code_as_arg(char** document
);
57 struct skin_element
* skin_parse(const char* document
)
60 struct skin_element
* root
= NULL
;
61 struct skin_element
* last
= NULL
;
63 struct skin_element
** to_write
= 0;
65 char* cursor
= (char*)document
; /*Keeps track of location in the document*/
72 while(*cursor
!= '\0')
78 to_write
= &(last
->next
);
81 *to_write
= skin_parse_viewport(&cursor
);
85 skin_free_tree(root
); /* Clearing any memory already used */
89 /* Making sure last is at the end */
99 static struct skin_element
* skin_parse_viewport(char** document
)
102 struct skin_element
* root
= NULL
;
103 struct skin_element
* last
= NULL
;
104 struct skin_element
* retval
= NULL
;
106 retval
= skin_alloc_element();
107 retval
->type
= VIEWPORT
;
108 retval
->children_count
= 1;
109 retval
->line
= skin_line
;
110 viewport_line
= skin_line
;
112 struct skin_element
** to_write
= 0;
114 char* cursor
= *document
; /* Keeps track of location in the document */
115 char* bookmark
; /* Used when we need to look ahead */
117 int sublines
= 0; /* Flag for parsing sublines */
119 /* Parsing out the viewport tag if there is one */
120 if(check_viewport(cursor
))
122 skin_parse_tag(retval
, &cursor
);
130 retval
->children_count
= 1;
131 retval
->children
= skin_alloc_children(1);
137 /* First, we check to see if this line will contain sublines */
140 while(*cursor
!= '\n' && *cursor
!= '\0'
141 && !(check_viewport(cursor
) && cursor
!= *document
))
143 if(*cursor
== MULTILINESYM
)
148 else if(*cursor
== TAGSYM
)
150 /* A ';' directly after a '%' doesn't count */
158 else if(*cursor
== COMMENTSYM
)
160 skip_comment(&cursor
);
162 else if(*cursor
== ARGLISTOPENSYM
)
164 skip_arglist(&cursor
);
166 else if(*cursor
== ENUMLISTOPENSYM
)
168 skip_enumlist(&cursor
);
172 /* Advancing the cursor as normal */
181 to_write
= &(last
->next
);
185 *to_write
= skin_parse_sublines(&cursor
);
193 *to_write
= skin_parse_line(&cursor
);
200 /* Making sure last is at the end */
210 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
214 retval
->children
[0] = root
;
219 /* Auxiliary Parsing Functions */
221 static struct skin_element
* skin_parse_line(char**document
)
224 return skin_parse_line_optional(document
, 0);
230 * If conditional is set to true, then this will break upon encountering
231 * SEPERATESYM. This should only be used when parsing a line inside a
232 * conditional, otherwise just use the wrapper function skin_parse_line()
234 static struct skin_element
* skin_parse_line_optional(char** document
,
237 char* cursor
= *document
;
239 struct skin_element
* root
= NULL
;
240 struct skin_element
* current
= NULL
;
241 struct skin_element
* retval
= NULL
;
243 /* A wrapper for the line */
244 retval
= skin_alloc_element();
246 retval
->line
= skin_line
;
247 if(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
248 && !(conditional
&& (*cursor
== ARGLISTSEPERATESYM
249 || *cursor
== ARGLISTCLOSESYM
250 || *cursor
== ENUMLISTSEPERATESYM
251 || *cursor
== ENUMLISTCLOSESYM
)))
253 retval
->children_count
= 1;
257 retval
->children_count
= 0;
260 if(retval
->children_count
> 0)
261 retval
->children
= skin_alloc_children(1);
263 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
264 && !((*cursor
== ARGLISTSEPERATESYM
265 || *cursor
== ARGLISTCLOSESYM
266 || *cursor
== ENUMLISTSEPERATESYM
267 || *cursor
== ENUMLISTCLOSESYM
)
269 && !(check_viewport(cursor
) && cursor
!= *document
))
271 /* Allocating memory if necessary */
274 current
->next
= skin_alloc_element();
275 current
= current
->next
;
279 current
= skin_alloc_element();
283 /* Parsing the current element */
284 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
286 if(!skin_parse_conditional(current
, &cursor
))
289 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
291 if(!skin_parse_tag(current
, &cursor
))
294 else if(*cursor
== COMMENTSYM
)
296 if(!skin_parse_comment(current
, &cursor
))
301 if(!skin_parse_text(current
, &cursor
, conditional
))
306 /* Moving up the calling function's pointer */
310 retval
->children
[0] = root
;
314 static struct skin_element
* skin_parse_sublines(char** document
)
316 return skin_parse_sublines_optional(document
, 0);
319 static struct skin_element
* skin_parse_sublines_optional(char** document
,
322 struct skin_element
* retval
;
323 char* cursor
= *document
;
327 retval
= skin_alloc_element();
328 retval
->type
= LINE_ALTERNATOR
;
330 retval
->line
= skin_line
;
332 /* First we count the sublines */
333 while(*cursor
!= '\0' && *cursor
!= '\n'
334 && !((*cursor
== ARGLISTSEPERATESYM
335 || *cursor
== ARGLISTCLOSESYM
336 || *cursor
== ENUMLISTSEPERATESYM
337 || *cursor
== ENUMLISTCLOSESYM
)
339 && !(check_viewport(cursor
) && cursor
!= *document
))
341 if(*cursor
== COMMENTSYM
)
343 skip_comment(&cursor
);
345 else if(*cursor
== ENUMLISTOPENSYM
)
347 skip_enumlist(&cursor
);
349 else if(*cursor
== ARGLISTOPENSYM
)
351 skip_arglist(&cursor
);
353 else if(*cursor
== TAGSYM
)
356 if(*cursor
== '\0' || *cursor
== '\n')
360 else if(*cursor
== MULTILINESYM
)
371 /* ...and then we parse them */
372 retval
->children_count
= sublines
;
373 retval
->children
= skin_alloc_children(sublines
);
376 for(i
= 0; i
< sublines
; i
++)
378 retval
->children
[i
] = skin_parse_line_optional(&cursor
, conditional
);
379 skip_whitespace(&cursor
);
381 if(*cursor
!= MULTILINESYM
&& i
!= sublines
- 1)
383 skin_error(MULTILINE_EXPECTED
);
386 else if(i
!= sublines
- 1)
397 static int skin_parse_tag(struct skin_element
* element
, char** document
)
400 char* cursor
= *document
+ 1;
405 struct tag_info
*tag
;
409 int star
= 0; /* Flag for the all-or-none option */
410 int req_args
; /* To mark when we enter optional arguments */
414 /* Checking the tag name */
415 tag_name
[0] = cursor
[0];
416 tag_name
[1] = cursor
[1];
419 /* First we check the two characters after the '%', then a single char */
420 tag
= find_tag(tag_name
);
425 tag
= find_tag(tag_name
);
435 skin_error(ILLEGAL_TAG
);
439 /* Copying basic tag info */
440 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
443 tag_args
= tag
->params
;
444 element
->line
= skin_line
;
446 /* Checking for the * flag */
447 if(tag_args
[0] == '*')
453 /* If this tag has no arguments, we can bail out now */
454 if(strlen(tag_args
) == 0
455 || (tag_args
[0] == '|' && *cursor
!= ARGLISTOPENSYM
)
456 || (star
&& *cursor
!= ARGLISTOPENSYM
))
463 /* Checking the number of arguments and allocating args */
464 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
466 skin_error(ARGLIST_EXPECTED
);
475 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
477 /* Skipping over escaped characters */
478 if(*cursor
== TAGSYM
)
485 else if(*cursor
== COMMENTSYM
)
487 skip_comment(&cursor
);
489 else if(*cursor
== ARGLISTOPENSYM
)
491 skip_arglist(&cursor
);
493 else if(*cursor
== ARGLISTSEPERATESYM
)
504 cursor
= bookmark
; /* Restoring the cursor */
505 element
->params_count
= num_args
;
506 element
->params
= skin_alloc_params(num_args
);
508 /* Now we have to actually parse each argument */
509 for(i
= 0; i
< num_args
; i
++)
511 /* Making sure we haven't run out of arguments */
512 if(*tag_args
== '\0')
514 skin_error(TOO_MANY_ARGS
);
518 /* Checking for the optional bar */
526 /* Scanning the arguments */
527 skip_whitespace(&cursor
);
530 /* Checking for comments */
531 if(*cursor
== COMMENTSYM
)
532 skip_comment(&cursor
);
534 /* Storing the type code */
535 element
->params
[i
].type_code
= *tag_args
;
537 /* Checking a nullable argument for null */
538 if(*cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
540 if(islower(*tag_args
))
542 element
->params
[i
].type
= DEFAULT
;
547 skin_error(DEFAULT_NOT_ALLOWED
);
551 else if(tolower(*tag_args
) == 'i')
553 /* Scanning an int argument */
554 if(!isdigit(*cursor
) && *cursor
!= '-')
556 skin_error(INT_EXPECTED
);
560 element
->params
[i
].type
= NUMERIC
;
561 element
->params
[i
].data
.numeric
= scan_int(&cursor
);
563 else if(tolower(*tag_args
) == 'n' ||
564 tolower(*tag_args
) == 's' || tolower(*tag_args
) == 'f')
566 /* Scanning a string argument */
567 element
->params
[i
].type
= STRING
;
568 element
->params
[i
].data
.text
= scan_string(&cursor
);
571 else if(tolower(*tag_args
) == 'c')
573 /* Recursively parsing a code argument */
574 element
->params
[i
].type
= CODE
;
575 element
->params
[i
].data
.code
= skin_parse_code_as_arg(&cursor
);
576 if(!element
->params
[i
].data
.code
)
580 skip_whitespace(&cursor
);
582 if(*cursor
!= ARGLISTSEPERATESYM
&& i
< num_args
- 1)
584 skin_error(SEPERATOR_EXPECTED
);
587 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
589 skin_error(CLOSE_EXPECTED
);
597 if (*tag_args
!= 'N')
600 /* Checking for the optional bar */
610 /* Checking for a premature end */
611 if(*tag_args
!= '\0' && !optional
)
613 skin_error(INSUFFICIENT_ARGS
);
623 * If the conditional flag is set true, then parsing text will stop at an
624 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
626 static int skin_parse_text(struct skin_element
* element
, char** document
,
629 char* cursor
= *document
;
634 /* First figure out how much text we're copying */
635 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
636 && *cursor
!= COMMENTSYM
637 && !((*cursor
== ARGLISTSEPERATESYM
638 || *cursor
== ARGLISTCLOSESYM
639 || *cursor
== ENUMLISTSEPERATESYM
640 || *cursor
== ENUMLISTCLOSESYM
)
643 /* Dealing with possibility of escaped characters */
644 if(*cursor
== TAGSYM
)
646 if(find_escape_character(cursor
[1]))
658 /* Copying the text into the element struct */
659 element
->type
= TEXT
;
660 element
->line
= skin_line
;
661 element
->next
= NULL
;
662 element
->data
= text
= skin_alloc_string(length
);
664 for(dest
= 0; dest
< length
; dest
++)
666 /* Advancing cursor if we've encountered an escaped character */
667 if(*cursor
== TAGSYM
)
670 text
[dest
] = *cursor
;
680 static int skin_parse_conditional(struct skin_element
* element
, char** document
)
683 char* cursor
= *document
+ 1; /* Starting past the "%" */
688 element
->type
= CONDITIONAL
;
689 element
->line
= skin_line
;
691 /* Parsing the tag first */
692 if(!skin_parse_tag(element
, &cursor
))
695 /* Counting the children */
696 if(*(cursor
++) != ENUMLISTOPENSYM
)
698 skin_error(ARGLIST_EXPECTED
);
702 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\n' && *cursor
!= '\0')
704 if(*cursor
== COMMENTSYM
)
706 skip_comment(&cursor
);
708 else if(*cursor
== ENUMLISTOPENSYM
)
710 skip_enumlist(&cursor
);
712 else if(*cursor
== TAGSYM
)
715 if(*cursor
== '\0' || *cursor
== '\n')
719 else if(*cursor
== ENUMLISTSEPERATESYM
)
731 /* Parsing the children */
732 element
->children
= skin_alloc_children(children
);
733 element
->children_count
= children
;
735 for(i
= 0; i
< children
; i
++)
737 element
->children
[i
] = skin_parse_code_as_arg(&cursor
);
738 skip_whitespace(&cursor
);
740 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPERATESYM
)
742 skin_error(SEPERATOR_EXPECTED
);
745 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
747 skin_error(CLOSE_EXPECTED
);
761 static int skin_parse_comment(struct skin_element
* element
, char** document
)
763 char* cursor
= *document
;
769 * Finding the index of the ending newline or null-terminator
770 * The length of the string of interest doesn't include the leading #, the
771 * length we need to reserve is the same as the index of the last character
773 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
775 element
->type
= COMMENT
;
776 element
->line
= skin_line
;
778 element
->data
= NULL
;
780 element
->data
= text
= skin_alloc_string(length
);
781 /* We copy from one char past cursor to leave out the # */
782 memcpy((void*)text
, (void*)(cursor
+ 1),
783 sizeof(char) * (length
-1));
784 text
[length
- 1] = '\0';
786 if(cursor
[length
] == '\n')
789 *document
+= (length
); /* Move cursor up past # and all text */
790 if(**document
== '\n')
796 static struct skin_element
* skin_parse_code_as_arg(char** document
)
800 char* cursor
= *document
;
802 /* Checking for sublines */
803 while(*cursor
!= '\n' && *cursor
!= '\0'
804 && *cursor
!= ENUMLISTSEPERATESYM
&& *cursor
!= ARGLISTSEPERATESYM
805 && *cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= ARGLISTCLOSESYM
)
807 if(*cursor
== MULTILINESYM
)
812 else if(*cursor
== TAGSYM
)
814 /* A ';' directly after a '%' doesn't count */
822 else if(*cursor
== ARGLISTOPENSYM
)
824 skip_arglist(&cursor
);
826 else if(*cursor
== ENUMLISTOPENSYM
)
828 skip_enumlist(&cursor
);
832 /* Advancing the cursor as normal */
838 return skin_parse_sublines_optional(document
, 1);
840 return skin_parse_line_optional(document
, 1);
844 /* Memory management */
845 struct skin_element
* skin_alloc_element()
847 struct skin_element
* retval
= (struct skin_element
*)
848 skin_buffer_alloc(sizeof(struct skin_element
));
849 retval
->type
= UNKNOWN
;
852 retval
->params_count
= 0;
853 retval
->children_count
= 0;
859 struct skin_tag_parameter
* skin_alloc_params(int count
)
861 size_t size
= sizeof(struct skin_tag_parameter
) * count
;
862 return (struct skin_tag_parameter
*)skin_buffer_alloc(size
);
866 char* skin_alloc_string(int length
)
868 return (char*)skin_buffer_alloc(sizeof(char) * (length
+ 1));
871 struct skin_element
** skin_alloc_children(int count
)
873 return (struct skin_element
**)
874 skin_buffer_alloc(sizeof(struct skin_element
*) * count
);
877 void skin_free_tree(struct skin_element
* root
)
882 /* First make the recursive call */
885 skin_free_tree(root
->next
);
888 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
891 /* Then recursively free any children, before freeing their pointers */
892 for(i
= 0; i
< root
->children_count
; i
++)
893 skin_free_tree(root
->children
[i
]);
894 if(root
->children_count
> 0)
895 free(root
->children
);
897 /* Free any parameters, making sure to deallocate strings */
898 for(i
= 0; i
< root
->params_count
; i
++)
899 if(root
->params
[i
].type
== STRING
)
900 free(root
->params
[i
].data
.text
);
901 if(root
->params_count
> 0)
904 /* Finally, delete root's memory */