summaryrefslogtreecommitdiff
path: root/utils/themeeditor/parser/skin_parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/themeeditor/parser/skin_parser.c')
-rw-r--r--utils/themeeditor/parser/skin_parser.c923
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
+}