diff options
Diffstat (limited to 'utils/themeeditor/parser/skin_parser.c')
-rw-r--r-- | utils/themeeditor/parser/skin_parser.c | 923 |
1 files changed, 923 insertions, 0 deletions
diff --git a/utils/themeeditor/parser/skin_parser.c b/utils/themeeditor/parser/skin_parser.c new file mode 100644 index 0000000000..93a71919bf --- /dev/null +++ b/utils/themeeditor/parser/skin_parser.c @@ -0,0 +1,923 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Robert Bieber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include "skin_parser.h" +#include "skin_debug.h" +#include "tag_table.h" +#include "symbols.h" +#include "skin_scan.h" + +#ifdef ROCKBOX +/* Declaration of parse tree buffer */ +#define SKIN_MAX_MEMORY (30*1024) +static char skin_parse_tree[SKIN_MAX_MEMORY]; +static char *skin_buffer; +#endif + +/* Global variables for the parser */ +int skin_line = 0; + +/* Auxiliary parsing functions (not visible at global scope) */ +static struct skin_element* skin_parse_viewport(char** document); +static struct skin_element* skin_parse_line(char** document); +static struct skin_element* skin_parse_line_optional(char** document, + int conditional); +static struct skin_element* skin_parse_sublines(char** document); +static struct skin_element* skin_parse_sublines_optional(char** document, + int conditional); + +static int skin_parse_tag(struct skin_element* element, char** document); +static int skin_parse_text(struct skin_element* element, char** document, + int conditional); +static int skin_parse_conditional(struct skin_element* element, + char** document); +static int skin_parse_comment(struct skin_element* element, char** document); +static struct skin_element* skin_parse_code_as_arg(char** document); + +struct skin_element* skin_parse(const char* document) +{ + + struct skin_element* root = NULL; + struct skin_element* last = NULL; + + struct skin_element** to_write = 0; + + char* cursor = (char*)document; /*Keeps track of location in the document*/ +#ifdef ROCKBOX + /* FIXME */ + skin_buffer = &skin_parse_tree[0]; +#endif + + skin_line = 1; + + skin_clear_errors(); + + while(*cursor != '\0') + { + + if(!root) + to_write = &root; + else + to_write = &(last->next); + + + *to_write = skin_parse_viewport(&cursor); + last = *to_write; + if(!last) + { + skin_free_tree(root); /* Clearing any memory already used */ + return NULL; + } + + /* Making sure last is at the end */ + while(last->next) + last = last->next; + + } + + return root; + +} + +static struct skin_element* skin_parse_viewport(char** document) +{ + + struct skin_element* root = NULL; + struct skin_element* last = NULL; + struct skin_element* retval = NULL; + + retval = skin_alloc_element(); + retval->type = VIEWPORT; + retval->children_count = 1; + retval->line = skin_line; + + struct skin_element** to_write = 0; + + char* cursor = *document; /* Keeps track of location in the document */ + char* bookmark; /* Used when we need to look ahead */ + + int sublines = 0; /* Flag for parsing sublines */ + + /* Parsing out the viewport tag if there is one */ + if(check_viewport(cursor)) + { + skin_parse_tag(retval, &cursor); + if(*cursor == '\n') + { + cursor++; + skin_line++; + } + } + + retval->children_count = 1; + retval->children = skin_alloc_children(1); + + + do + { + + /* First, we check to see if this line will contain sublines */ + bookmark = cursor; + sublines = 0; + while(*cursor != '\n' && *cursor != '\0' + && !(check_viewport(cursor) && cursor != *document)) + { + if(*cursor == MULTILINESYM) + { + sublines = 1; + break; + } + else if(*cursor == TAGSYM) + { + /* A ';' directly after a '%' doesn't count */ + cursor ++; + + if(*cursor == '\0') + break; + + cursor++; + } + else if(*cursor == COMMENTSYM) + { + skip_comment(&cursor); + } + else if(*cursor == ARGLISTOPENSYM) + { + skip_arglist(&cursor); + } + else if(*cursor == ENUMLISTOPENSYM) + { + skip_enumlist(&cursor); + } + else + { + /* Advancing the cursor as normal */ + cursor++; + } + } + cursor = bookmark; + + if(!root) + to_write = &root; + else + to_write = &(last->next); + + if(sublines) + { + *to_write = skin_parse_sublines(&cursor); + last = *to_write; + if(!last) + return NULL; + } + else + { + + *to_write = skin_parse_line(&cursor); + last = *to_write; + if(!last) + return NULL; + + } + + /* Making sure last is at the end */ + while(last->next) + last = last->next; + + if(*cursor == '\n') + { + cursor++; + skin_line++; + } + } + while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document)); + + *document = cursor; + + retval->children[0] = root; + return retval; + +} + +/* Auxiliary Parsing Functions */ + +static struct skin_element* skin_parse_line(char**document) +{ + + return skin_parse_line_optional(document, 0); + +} + + +/* + * If conditional is set to true, then this will break upon encountering + * SEPERATESYM. This should only be used when parsing a line inside a + * conditional, otherwise just use the wrapper function skin_parse_line() + */ +static struct skin_element* skin_parse_line_optional(char** document, + int conditional) +{ + char* cursor = *document; + + struct skin_element* root = NULL; + struct skin_element* current = NULL; + struct skin_element* retval = NULL; + + /* A wrapper for the line */ + retval = skin_alloc_element(); + retval->type = LINE; + retval->line = skin_line; + if(*cursor != '\0' && *cursor != '\n' + && !(conditional && (*cursor == ARGLISTSEPERATESYM + || *cursor == ARGLISTCLOSESYM + || *cursor == ENUMLISTSEPERATESYM + || *cursor == ENUMLISTCLOSESYM))) + { + retval->children_count = 1; + } + else + { + retval->children_count = 0; + } + + if(retval->children_count > 0) + retval->children = skin_alloc_children(1); + + while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM + && !((*cursor == ARGLISTSEPERATESYM + || *cursor == ARGLISTCLOSESYM + || *cursor == ENUMLISTSEPERATESYM + || *cursor == ENUMLISTCLOSESYM) + && conditional) + && !(check_viewport(cursor) && cursor != *document)) + { + /* Allocating memory if necessary */ + if(root) + { + current->next = skin_alloc_element(); + current = current->next; + } + else + { + current = skin_alloc_element(); + root = current; + } + + /* Parsing the current element */ + if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM) + { + if(!skin_parse_conditional(current, &cursor)) + return NULL; + } + else if(*cursor == TAGSYM && !find_escape_character(cursor[1])) + { + if(!skin_parse_tag(current, &cursor)) + return NULL; + } + else if(*cursor == COMMENTSYM) + { + if(!skin_parse_comment(current, &cursor)) + return NULL; + } + else + { + if(!skin_parse_text(current, &cursor, conditional)) + return NULL; + } + } + + /* Moving up the calling function's pointer */ + *document = cursor; + + if(root) + retval->children[0] = root; + return retval; +} + +static struct skin_element* skin_parse_sublines(char** document) +{ + return skin_parse_sublines_optional(document, 0); +} + +static struct skin_element* skin_parse_sublines_optional(char** document, + int conditional) +{ + struct skin_element* retval; + char* cursor = *document; + int sublines = 1; + int i; + + retval = skin_alloc_element(); + retval->type = SUBLINES; + retval->next = NULL; + retval->line = skin_line; + + /* First we count the sublines */ + while(*cursor != '\0' && *cursor != '\n' + && !((*cursor == ARGLISTSEPERATESYM + || *cursor == ARGLISTCLOSESYM + || *cursor == ENUMLISTSEPERATESYM + || *cursor == ENUMLISTCLOSESYM) + && conditional) + && !(check_viewport(cursor) && cursor != *document)) + { + if(*cursor == COMMENTSYM) + { + skip_comment(&cursor); + } + else if(*cursor == ENUMLISTOPENSYM) + { + skip_enumlist(&cursor); + } + else if(*cursor == ARGLISTOPENSYM) + { + skip_arglist(&cursor); + } + else if(*cursor == TAGSYM) + { + cursor++; + if(*cursor == '\0' || *cursor == '\n') + break; + cursor++; + } + else if(*cursor == MULTILINESYM) + { + sublines++; + cursor++; + } + else + { + cursor++; + } + } + + /* ...and then we parse them */ + retval->children_count = sublines; + retval->children = skin_alloc_children(sublines); + + cursor = *document; + for(i = 0; i < sublines; i++) + { + retval->children[i] = skin_parse_line_optional(&cursor, conditional); + skip_whitespace(&cursor); + + if(*cursor != MULTILINESYM && i != sublines - 1) + { + skin_error(MULTILINE_EXPECTED); + return NULL; + } + else if(i != sublines - 1) + { + cursor++; + } + } + + *document = cursor; + + return retval; +} + +static int skin_parse_tag(struct skin_element* element, char** document) +{ + + char* cursor = *document + 1; + char* bookmark; + + char tag_name[3]; + char* tag_args; + struct tag_info *tag; + + int num_args = 1; + int i; + int star = 0; /* Flag for the all-or-none option */ + int req_args; /* To mark when we enter optional arguments */ + + int optional = 0; + + /* Checking the tag name */ + tag_name[0] = cursor[0]; + tag_name[1] = cursor[1]; + tag_name[2] = '\0'; + + /* First we check the two characters after the '%', then a single char */ + tag = find_tag(tag_name); + + if(!tag) + { + tag_name[1] = '\0'; + tag = find_tag(tag_name); + cursor++; + } + else + { + cursor += 2; + } + + if(!tag) + { + skin_error(ILLEGAL_TAG); + return 0; + } + + /* Copying basic tag info */ + if(element->type != CONDITIONAL && element->type != VIEWPORT) + element->type = TAG; + element->tag = tag; + tag_args = tag->params; + element->line = skin_line; + + /* Checking for the * flag */ + if(tag_args[0] == '*') + { + star = 1; + tag_args++; + } + + /* If this tag has no arguments, we can bail out now */ + if(strlen(tag_args) == 0 + || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM) + || (star && *cursor != ARGLISTOPENSYM)) + { + *document = cursor; + return 1; + } + + /* Checking the number of arguments and allocating args */ + if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|') + { + skin_error(ARGLIST_EXPECTED); + return 0; + } + else + { + cursor++; + } + + bookmark = cursor; + while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM) + { + /* Skipping over escaped characters */ + if(*cursor == TAGSYM) + { + cursor++; + if(*cursor == '\0') + break; + cursor++; + } + else if(*cursor == COMMENTSYM) + { + skip_comment(&cursor); + } + else if(*cursor == ARGLISTOPENSYM) + { + skip_arglist(&cursor); + } + else if(*cursor == ARGLISTSEPERATESYM) + { + num_args++; + cursor++; + } + else + { + cursor++; + } + } + + cursor = bookmark; /* Restoring the cursor */ + element->params_count = num_args; + element->params = skin_alloc_params(num_args); + + /* Now we have to actually parse each argument */ + for(i = 0; i < num_args; i++) + { + /* Making sure we haven't run out of arguments */ + if(*tag_args == '\0') + { + skin_error(TOO_MANY_ARGS); + return 0; + } + + /* Checking for the optional bar */ + if(*tag_args == '|') + { + optional = 1; + req_args = i; + tag_args++; + } + + /* Scanning the arguments */ + skip_whitespace(&cursor); + + + /* Checking for comments */ + if(*cursor == COMMENTSYM) + skip_comment(&cursor); + + /* Storing the type code */ + element->params[i].type_code = *tag_args; + + /* Checking a nullable argument for null */ + if(*cursor == DEFAULTSYM && !isdigit(cursor[1])) + { + if(islower(*tag_args)) + { + element->params[i].type = DEFAULT; + cursor++; + } + else + { + skin_error(DEFAULT_NOT_ALLOWED); + return 0; + } + } + else if(tolower(*tag_args) == 'i') + { + /* Scanning an int argument */ + if(!isdigit(*cursor) && *cursor != '-') + { + skin_error(INT_EXPECTED); + return 0; + } + + element->params[i].type = NUMERIC; + element->params[i].data.numeric = scan_int(&cursor); + } + else if(tolower(*tag_args) == 'n' || + tolower(*tag_args) == 's' || tolower(*tag_args) == 'f') + { + /* Scanning a string argument */ + element->params[i].type = STRING; + element->params[i].data.text = scan_string(&cursor); + + } + else if(tolower(*tag_args) == 'c') + { + /* Recursively parsing a code argument */ + element->params[i].type = CODE; + element->params[i].data.code = skin_parse_code_as_arg(&cursor); + if(!element->params[i].data.code) + return 0; + } + + skip_whitespace(&cursor); + + if(*cursor != ARGLISTSEPERATESYM && i < num_args - 1) + { + skin_error(SEPERATOR_EXPECTED); + return 0; + } + else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1) + { + skin_error(CLOSE_EXPECTED); + return 0; + } + else + { + cursor++; + } + + if (*tag_args != 'N') + tag_args++; + + /* Checking for the optional bar */ + if(*tag_args == '|') + { + optional = 1; + req_args = i + 1; + tag_args++; + } + + } + + /* Checking for a premature end */ + if(*tag_args != '\0' && !optional) + { + skin_error(INSUFFICIENT_ARGS); + return 0; + } + + *document = cursor; + + return 1; +} + +/* + * If the conditional flag is set true, then parsing text will stop at an + * ARGLISTSEPERATESYM. Only set that flag when parsing within a conditional + */ +static int skin_parse_text(struct skin_element* element, char** document, + int conditional) +{ + char* cursor = *document; + int length = 0; + int dest; + char *text = NULL; + + /* First figure out how much text we're copying */ + while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM + && *cursor != COMMENTSYM + && !((*cursor == ARGLISTSEPERATESYM + || *cursor == ARGLISTCLOSESYM + || *cursor == ENUMLISTSEPERATESYM + || *cursor == ENUMLISTCLOSESYM) + && conditional)) + { + /* Dealing with possibility of escaped characters */ + if(*cursor == TAGSYM) + { + if(find_escape_character(cursor[1])) + cursor++; + else + break; + } + + length++; + cursor++; + } + + cursor = *document; + + /* Copying the text into the element struct */ + element->type = TEXT; + element->line = skin_line; + element->next = NULL; + element->data = text = skin_alloc_string(length); + + for(dest = 0; dest < length; dest++) + { + /* Advancing cursor if we've encountered an escaped character */ + if(*cursor == TAGSYM) + cursor++; + + text[dest] = *cursor; + cursor++; + } + text[length] = '\0'; + + *document = cursor; + + return 1; +} + +static int skin_parse_conditional(struct skin_element* element, char** document) +{ + + char* cursor = *document + 1; /* Starting past the "%" */ + char* bookmark; + int children = 1; + int i; + + element->type = CONDITIONAL; + element->line = skin_line; + + /* Parsing the tag first */ + if(!skin_parse_tag(element, &cursor)) + return 0; + + /* Counting the children */ + if(*(cursor++) != ENUMLISTOPENSYM) + { + skin_error(ARGLIST_EXPECTED); + return 0; + } + bookmark = cursor; + while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0') + { + if(*cursor == COMMENTSYM) + { + skip_comment(&cursor); + } + else if(*cursor == ENUMLISTOPENSYM) + { + skip_enumlist(&cursor); + } + else if(*cursor == TAGSYM) + { + cursor++; + if(*cursor == '\0' || *cursor == '\n') + break; + cursor++; + } + else if(*cursor == ENUMLISTSEPERATESYM) + { + children++; + cursor++; + } + else + { + cursor++; + } + } + cursor = bookmark; + + /* Parsing the children */ + element->children = skin_alloc_children(children); + element->children_count = children; + + for(i = 0; i < children; i++) + { + element->children[i] = skin_parse_code_as_arg(&cursor); + skip_whitespace(&cursor); + + if(i < children - 1 && *cursor != ENUMLISTSEPERATESYM) + { + skin_error(SEPERATOR_EXPECTED); + return 0; + } + else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM) + { + skin_error(CLOSE_EXPECTED); + return 0; + } + else + { + cursor++; + } + } + + *document = cursor; + + return 1; +} + +static int skin_parse_comment(struct skin_element* element, char** document) +{ + char* cursor = *document; + char* text = NULL; + + int length; + /* + * Finding the index of the ending newline or null-terminator + * The length of the string of interest doesn't include the leading #, the + * length we need to reserve is the same as the index of the last character + */ + for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++); + + element->type = COMMENT; + element->line = skin_line; +#ifdef ROCKBOX + element->data = NULL; +#else + element->data = text = skin_alloc_string(length); + /* We copy from one char past cursor to leave out the # */ + memcpy((void*)text, (void*)(cursor + 1), + sizeof(char) * (length-1)); + text[length - 1] = '\0'; +#endif + if(cursor[length] == '\n') + skin_line++; + + *document += (length); /* Move cursor up past # and all text */ + if(**document == '\n') + (*document)++; + + return 1; +} + +static struct skin_element* skin_parse_code_as_arg(char** document) +{ + + int sublines = 0; + char* cursor = *document; + + /* Checking for sublines */ + while(*cursor != '\n' && *cursor != '\0' + && *cursor != ENUMLISTSEPERATESYM && *cursor != ARGLISTSEPERATESYM + && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM) + { + if(*cursor == MULTILINESYM) + { + sublines = 1; + break; + } + else if(*cursor == TAGSYM) + { + /* A ';' directly after a '%' doesn't count */ + cursor ++; + + if(*cursor == '\0') + break; + + cursor++; + } + else if(*cursor == ARGLISTOPENSYM) + { + skip_arglist(&cursor); + } + else if(*cursor == ENUMLISTOPENSYM) + { + skip_enumlist(&cursor); + } + else + { + /* Advancing the cursor as normal */ + cursor++; + } + } + + if(sublines) + return skin_parse_sublines_optional(document, 1); + else + return skin_parse_line_optional(document, 1); +} + + +/* Memory management */ +char* skin_alloc(size_t size) +{ +#ifdef ROCKBOX + char *retval = skin_buffer; + skin_buffer = (void *)(((unsigned long)skin_buffer + 3) & ~3); + return retval; +#else + return malloc(size); +#endif +} + +struct skin_element* skin_alloc_element() +{ + struct skin_element* retval = (struct skin_element*) + skin_alloc(sizeof(struct skin_element)); + retval->type = UNKNOWN; + retval->next = NULL; + retval->tag = NULL; + retval->params_count = 0; + retval->children_count = 0; + + return retval; + +} + +struct skin_tag_parameter* skin_alloc_params(int count) +{ + size_t size = sizeof(struct skin_tag_parameter) * count; + return (struct skin_tag_parameter*)skin_alloc(size); + +} + +char* skin_alloc_string(int length) +{ + return (char*)skin_alloc(sizeof(char) * (length + 1)); +} + +struct skin_element** skin_alloc_children(int count) +{ + return (struct skin_element**) + skin_alloc(sizeof(struct skin_element*) * count); +} + +void skin_free_tree(struct skin_element* root) +{ +#ifndef ROCKBOX + int i; + + /* First make the recursive call */ + if(!root) + return; + skin_free_tree(root->next); + + /* Free any text */ + if(root->type == TEXT || root->type == COMMENT) + free(root->data); + + /* Then recursively free any children, before freeing their pointers */ + for(i = 0; i < root->children_count; i++) + skin_free_tree(root->children[i]); + if(root->children_count > 0) + free(root->children); + + /* Free any parameters, making sure to deallocate strings */ + for(i = 0; i < root->params_count; i++) + if(root->params[i].type == STRING) + free(root->params[i].data.text); + if(root->params_count > 0) + free(root->params); + + /* Finally, delete root's memory */ + free(root); +#else + (void)root; +#endif +} |