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_parser.h"
28 #include "skin_debug.h"
29 #include "tag_table.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 */
40 /* Auxiliary parsing functions (not visible at global scope) */
41 struct skin_element
* skin_parse_line(char** document
);
42 struct skin_element
* skin_parse_line_optional(char** document
, int conditional
);
43 struct skin_element
* skin_parse_sublines(char** document
);
44 struct skin_element
* skin_parse_sublines_optional(char** document
,
47 int skin_parse_tag(struct skin_element
* element
, char** document
);
48 int skin_parse_text(struct skin_element
* element
, char** document
,
50 int skin_parse_conditional(struct skin_element
* element
, char** document
);
51 int skin_parse_newline(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(char* document
)
58 struct skin_element
* root
= NULL
;
59 struct skin_element
* last
= NULL
;
61 struct skin_element
** to_write
= 0;
63 char* cursor
= document
; /* Keeps track of location in the document */
64 char* bookmark
; /* Used when we need to look ahead */
66 int sublines
= 0; /* Flag for parsing sublines */
70 while(*cursor
!= '\0')
73 /* First, we check to see if this line will contain sublines */
76 while(*cursor
!= '\n' && *cursor
!= '\0')
78 if(*cursor
== MULTILINESYM
)
83 else if(*cursor
== TAGSYM
)
85 /* A ';' directly after a '%' doesn't count */
93 else if(*cursor
== COMMENTSYM
)
95 skip_comment(&cursor
);
99 /* Advancing the cursor as normal */
108 to_write
= &(last
->next
);
112 *to_write
= skin_alloc_element();
113 skin_parse_newline(*to_write
, &cursor
);
119 *to_write
= skin_parse_sublines(&cursor
);
127 *to_write
= skin_parse_line(&cursor
);
134 /* Making sure last is at the end */
144 /* Auxiliary Parsing Functions */
146 struct skin_element
* skin_parse_line(char**document
)
149 return skin_parse_line_optional(document
, 0);
155 * If conditional is set to true, then this will break upon encountering
156 * SEPERATESYM. This should only be used when parsing a line inside a
157 * conditional, otherwise just use the wrapper function skin_parse_line()
159 struct skin_element
* skin_parse_line_optional(char** document
, int conditional
)
161 char* cursor
= *document
;
163 struct skin_element
* root
= NULL
;
164 struct skin_element
* current
= NULL
;
165 struct skin_element
* retval
= NULL
;
167 /* A wrapper for the line */
168 retval
= skin_alloc_element();
170 retval
->line
= skin_line
;
171 retval
->children_count
= 1;
172 retval
->children
= skin_alloc_children(1);
174 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
175 && !((*cursor
== ARGLISTSEPERATESYM
176 || *cursor
== ARGLISTCLOSESYM
177 || *cursor
== ENUMLISTSEPERATESYM
178 || *cursor
== ENUMLISTCLOSESYM
)
181 /* Allocating memory if necessary */
184 current
->next
= skin_alloc_element();
185 current
= current
->next
;
189 current
= skin_alloc_element();
193 /* Parsing the current element */
194 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
196 if(!skin_parse_conditional(current
, &cursor
))
199 else if(*cursor
== TAGSYM
)
201 if(!skin_parse_tag(current
, &cursor
))
204 else if(*cursor
== COMMENTSYM
)
206 if(!skin_parse_comment(current
, &cursor
))
211 if(!skin_parse_text(current
, &cursor
, conditional
))
216 /* Moving up the calling function's pointer */
219 retval
->children
[0] = root
;
223 struct skin_element
* skin_parse_sublines(char** document
)
225 return skin_parse_sublines_optional(document
, 0);
228 struct skin_element
* skin_parse_sublines_optional(char** document
,
231 struct skin_element
* retval
;
232 char* cursor
= *document
;
236 retval
= skin_alloc_element();
237 retval
->type
= SUBLINES
;
239 retval
->line
= skin_line
;
241 /* First we count the sublines */
242 while(*cursor
!= '\0' && *cursor
!= '\n'
243 && !((*cursor
== ARGLISTSEPERATESYM
244 || *cursor
== ARGLISTCLOSESYM
245 || *cursor
== ENUMLISTSEPERATESYM
246 || *cursor
== ENUMLISTCLOSESYM
)
249 if(*cursor
== COMMENTSYM
)
250 skip_comment(&cursor
);
252 /* Accounting for escaped subline symbols */
253 if(*cursor
== TAGSYM
)
256 if(*cursor
== '\0' || *cursor
== '\n')
260 if(*cursor
== MULTILINESYM
)
268 /* ...and then we parse them */
269 retval
->children_count
= sublines
;
270 retval
->children
= skin_alloc_children(sublines
);
273 for(i
= 0; i
< sublines
; i
++)
275 retval
->children
[i
] = skin_parse_line_optional(&cursor
, conditional
);
276 skip_whitespace(&cursor
);
278 if(*cursor
!= MULTILINESYM
&& i
!= sublines
- 1)
280 skin_error(MULTILINE_EXPECTED
);
283 else if(i
!= sublines
- 1)
294 int skin_parse_tag(struct skin_element
* element
, char** document
)
297 char* cursor
= *document
+ 1;
305 int star
= 0; /* Flag for the all-or-none option */
306 int req_args
; /* To mark when we enter optional arguments */
310 /* Checking the tag name */
311 tag_name
[0] = cursor
[0];
312 tag_name
[1] = cursor
[1];
315 /* First we check the two characters after the '%', then a single char */
316 tag_args
= find_tag(tag_name
);
321 tag_args
= find_tag(tag_name
);
331 skin_error(ILLEGAL_TAG
);
335 /* Copying basic tag info */
337 strcpy(element
->name
, tag_name
);
338 element
->line
= skin_line
;
340 /* Checking for the * flag */
341 if(tag_args
[0] == '*')
347 /* If this tag has no arguments, we can bail out now */
348 if(strlen(tag_args
) == 0
349 || (tag_args
[0] == '|' && *cursor
!= ARGLISTOPENSYM
))
355 /* Checking the number of arguments and allocating args */
356 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
358 skin_error(ARGLIST_EXPECTED
);
366 for(bookmark
= cursor
; *cursor
!= '\n' && *cursor
!= '\0' &&
367 *cursor
!= ARGLISTCLOSESYM
; cursor
++)
369 /* Skipping over escaped characters */
370 if(*cursor
== TAGSYM
)
377 /* Skipping comments */
378 if(*cursor
== COMMENTSYM
)
379 skip_comment(&cursor
);
381 /* Commas inside of contained lists don't count */
382 if(*cursor
== ARGLISTOPENSYM
)
383 while(*cursor
!= ARGLISTCLOSESYM
&& *cursor
!= '\0')
386 if(*cursor
== ARGLISTSEPERATESYM
)
390 cursor
= bookmark
; /* Restoring the cursor */
391 element
->params_count
= num_args
;
392 element
->params
= skin_alloc_params(num_args
);
394 /* Now we have to actually parse each argument */
395 for(i
= 0; i
< num_args
; i
++)
397 /* Making sure we haven't run out of arguments */
398 if(*tag_args
== '\0')
400 skin_error(TOO_MANY_ARGS
);
404 /* Checking for the optional bar */
412 /* Scanning the arguments */
413 skip_whitespace(&cursor
);
416 /* Checking for comments */
417 if(*cursor
== COMMENTSYM
)
418 skip_comment(&cursor
);
420 /* Checking a nullable argument for null */
421 if(*cursor
== DEFAULTSYM
)
423 if(islower(*tag_args
))
425 element
->params
[i
].type
= DEFAULT
;
430 skin_error(DEFAULT_NOT_ALLOWED
);
434 else if(tolower(*tag_args
) == 'i')
436 /* Scanning an int argument */
437 if(!isdigit(*cursor
))
439 skin_error(INT_EXPECTED
);
443 element
->params
[i
].type
= NUMERIC
;
444 element
->params
[i
].data
.numeric
= scan_int(&cursor
);
446 else if(tolower(*tag_args
) == 's' || tolower(*tag_args
) == 'f')
448 /* Scanning a string argument */
449 element
->params
[i
].type
= STRING
;
450 element
->params
[i
].data
.text
= scan_string(&cursor
);
453 else if(tolower(*tag_args
) == 'c')
455 /* Recursively parsing a code argument */
456 element
->params
[i
].type
= CODE
;
457 element
->params
[i
].data
.code
= skin_parse_code_as_arg(&cursor
);
458 if(!element
->params
[i
].data
.code
)
462 skip_whitespace(&cursor
);
464 if(*cursor
!= ARGLISTSEPERATESYM
&& i
< num_args
- 1)
466 skin_error(SEPERATOR_EXPECTED
);
469 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
471 skin_error(CLOSE_EXPECTED
);
483 /* Checking for a premature end */
484 if(*tag_args
!= '\0' && !(optional
&& (!star
|| num_args
== req_args
)))
486 skin_error(INSUFFICIENT_ARGS
);
496 * If the conditional flag is set true, then parsing text will stop at an
497 * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional
499 int skin_parse_text(struct skin_element
* element
, char** document
,
502 char* cursor
= *document
;
508 /* First figure out how much text we're copying */
509 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
510 && *cursor
!= COMMENTSYM
511 && !((*cursor
== ARGLISTSEPERATESYM
512 || *cursor
== ARGLISTCLOSESYM
513 || *cursor
== ENUMLISTSEPERATESYM
514 || *cursor
== ENUMLISTCLOSESYM
)
517 /* Dealing with possibility of escaped characters */
518 if(*cursor
== TAGSYM
)
520 if(find_escape_character(cursor
[1]))
532 /* Copying the text into the element struct */
533 element
->type
= TEXT
;
534 element
->line
= skin_line
;
535 element
->next
= NULL
;
536 element
->text
= skin_alloc_string(length
);
538 for(dest
= 0; dest
< length
; dest
++)
540 /* Advancing cursor if we've encountered an escaped character */
541 if(*cursor
== TAGSYM
)
544 element
->text
[dest
] = *cursor
;
547 element
->text
[length
] = '\0';
554 int skin_parse_conditional(struct skin_element
* element
, char** document
)
557 char* cursor
= *document
+ 1; /* Starting past the "%" */
559 struct skin_element
* tag
= skin_alloc_element(); /* The tag to evaluate */
563 element
->type
= CONDITIONAL
;
564 element
->line
= skin_line
;
566 /* Parsing the tag first */
567 if(!skin_parse_tag(tag
, &cursor
))
570 /* Counting the children */
571 if(*(cursor
++) != ENUMLISTOPENSYM
)
573 skin_error(ARGLIST_EXPECTED
);
577 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\n' && *cursor
!= '\0')
579 if(*cursor
== COMMENTSYM
)
581 skip_comment(&cursor
);
585 if(*cursor
== TAGSYM
)
588 if(*cursor
== '\0' || *cursor
== '\n')
594 if(*cursor
== ENUMLISTSEPERATESYM
)
601 /* Parsing the children */
602 element
->children_count
= children
+ 1; /* Make sure to include the tag */
603 element
->children
= skin_alloc_children(children
+ 1);
604 element
->children
[0] = tag
;
606 for(i
= 1; i
< children
+ 1; i
++)
608 element
->children
[i
] = skin_parse_code_as_arg(&cursor
);
609 skip_whitespace(&cursor
);
611 if(i
< children
&& *cursor
!= ENUMLISTSEPERATESYM
)
613 skin_error(SEPERATOR_EXPECTED
);
616 else if(i
== children
&& *cursor
!= ENUMLISTCLOSESYM
)
618 skin_error(CLOSE_EXPECTED
);
632 int skin_parse_newline(struct skin_element
* element
, char** document
)
635 char* cursor
= *document
;
638 skin_error(NEWLINE_EXPECTED
);
643 /* Assembling a skin_element struct for a newline */
644 element
->type
= NEWLINE
;
645 element
->line
= skin_line
;
647 element
->next
= NULL
;
654 int skin_parse_comment(struct skin_element
* element
, char** document
)
656 char* cursor
= *document
;
660 * Finding the index of the ending newline or null-terminator
661 * The length of the string of interest doesn't include the leading #, the
662 * length we need to reserve is the same as the index of the last character
664 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
666 element
->type
= COMMENT
;
667 element
->line
= skin_line
;
668 element
->text
= skin_alloc_string(length
);
669 /* We copy from one char past cursor to leave out the # */
670 memcpy((void*)(element
->text
), (void*)(cursor
+ 1), sizeof(char) * length
);
671 element
->text
[length
] = '\0';
673 if(cursor
[length
] == '\n')
676 *document
+= (length
+ 1); /* Move cursor up past # and all text */
681 struct skin_element
* skin_parse_code_as_arg(char** document
)
685 char* cursor
= *document
;
687 /* Checking for sublines */
688 while(*cursor
!= '\n' && *cursor
!= '\0')
690 if(*cursor
== MULTILINESYM
)
695 else if(*cursor
== TAGSYM
)
697 /* A ';' directly after a '%' doesn't count */
707 /* Advancing the cursor as normal */
713 return skin_parse_sublines_optional(document
, 1);
715 return skin_parse_line_optional(document
, 1);
719 /* Memory management */
720 struct skin_element
* skin_alloc_element()
725 char* retval
= &skin_parse_tree
[skin_current_block
* 4];
727 int delta
= sizeof(struct skin_element
) / (sizeof(char) * 4);
729 /* If one block is partially filled, make sure to advance to the
730 * next one for the next allocation
732 if(sizeof(struct skin_element
) % (sizeof(char) * 4) != 0)
735 skin_current_block
+= delta
;
737 return (struct skin_element
*)retval
;
741 struct skin_element
* retval
= (struct skin_element
*)
742 malloc(sizeof(struct skin_element
));
744 retval
->params_count
= 0;
745 retval
->children_count
= 0;
751 struct skin_tag_parameter
* skin_alloc_params(int count
)
755 char* retval
= &skin_parse_tree
[skin_current_block
* 4];
757 int delta
= sizeof(struct skin_tag_parameter
) / (sizeof(char) * 4);
760 /* Correcting uneven alignment */
761 if(count
* sizeof(struct skin_tag_parameter
) % (sizeof(char) * 4) != 0)
764 skin_current_block
+= delta
;
766 return (struct skin_tag_parameter
*) retval
;
770 return (struct skin_tag_parameter
*)malloc(sizeof(struct skin_tag_parameter
)
775 char* skin_alloc_string(int length
)
779 char* retval
= &skin_parse_tree
[skin_current_block
* 4];
781 /* Checking alignment */
782 length
++; /* Accounting for the null terminator */
783 int delta
= length
/ 4;
787 skin_current_block
+= delta
;
789 if(skin_current_block
>= SKIN_MAX_MEMORY
/ 4)
790 skin_error(MEMORY_LIMIT_EXCEEDED
);
796 return (char*)malloc(sizeof(char) * (length
+ 1));
800 struct skin_element
** skin_alloc_children(int count
)
802 return (struct skin_element
**) malloc(sizeof(struct skin_element
*) * count
);
805 void skin_free_tree(struct skin_element
* root
)
809 /* First make the recursive call */
812 skin_free_tree(root
->next
);
815 if(root
->type
== TEXT
)
818 /* Then recursively free any children, before freeing their pointers */
819 for(i
= 0; i
< root
->children_count
; i
++)
820 skin_free_tree(root
->children
[i
]);
821 if(root
->children_count
> 0)
822 free(root
->children
);
824 /* Free any parameters, making sure to deallocate strings */
825 for(i
= 0; i
< root
->params_count
; i
++)
826 if(root
->params
[i
].type
== STRING
)
827 free(root
->params
[i
].data
.text
);
828 if(root
->params_count
> 0)
831 /* Finally, delete root's memory */