/*
* Copyright (c) 2006 Kolja Waschk rtemsdev/ixo.de
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rtems.com/license/LICENSE.
*
* $Id$
*/
#include <stdio.h>
#include <errno.h>
#include "ptf.h"
#define PTFPARSER_MAXDEPTH 20
#define PTFPARSER_NAMEBUFSIZE 1024
#define PTFPARSER_VALUEBUFSIZE 4096
#define DEBUG_EXPECTATIONS 1
#define DEBUG_READVALUES 2
#define DEBUG_FINDER 4
#define DEBUG 0
struct ptf_parser_state
{
struct ptf *tree;
struct ptf *current_in[PTFPARSER_MAXDEPTH];
struct ptf *current_item;
long line; /* starts with 1, increments whenever a LF (ASCII 10) passes */
int section_level; /* starts at 0, incremented at {, decremented at } */
char *filename;
char name_buffer[PTFPARSER_NAMEBUFSIZE];
int name_length;
char value_buffer[PTFPARSER_VALUEBUFSIZE];
int value_length;
struct
{
unsigned error:1;
unsigned escaped:1;
unsigned single_quoted:1;
unsigned double_quoted:1;
} flag;
enum
{
section_or_item_specification,
more_section_type_or_item_name_chars,
whitespace_after_section_specification,
whitespace_after_section_or_item_name,
whitespace_before_item_value,
more_section_name_chars,
more_item_value_chars,
} expectation;
};
/***************************************************************************/
struct ptf *ptf_alloc_item(ptf_item_type t, char *name, char *value)
{
struct ptf *new_item;
new_item = (struct ptf *)malloc(sizeof(struct ptf));
if(!new_item) return NULL;
new_item->type = t;
new_item->sub = NULL;
new_item->next = NULL;
new_item->name = NULL;
new_item->value = NULL;
if(name != NULL)
{
int n = strlen(name);
if(n > 0)
{
new_item->name = (char *)malloc(n + 1);
if(new_item->name == NULL)
{
free(new_item);
return NULL;
};
strcpy(new_item->name, name);
}
};
if(value != NULL)
{
int n = strlen(value);
if(n > 0)
{
new_item->value = (char *)malloc(n + 1);
if(new_item->value == NULL)
{
if(name != NULL) free(new_item->name);
free(new_item);
return NULL;
};
strcpy(new_item->value, value);
};
};
return new_item;
}
/***************************************************************************/
void add_ptf_item(struct ptf_parser_state *state, struct ptf *item)
{
if(state->current_item == NULL)
{
if(state->section_level > 0)
state->current_in[state->section_level-1]->sub = item;
else
state->tree = item;
}
else
state->current_item->next = item;
}
/***************************************************************************/
void parse_error(struct ptf_parser_state *state, char *errmsg)
{
fprintf(stderr, "Error while parsing %s (line %lu): %s\n",
state->filename, state->line, errmsg);
state->flag.error = 1;
}
/***************************************************************************/
void init_parser(struct ptf_parser_state *state, char *filename)
{
int i;
state->line = 1;
state->flag.error = 0;
state->flag.escaped = 0;
state->flag.double_quoted = 0;
state->flag.single_quoted = 0;
state->section_level = 0;
state->filename = (char *)malloc(strlen(filename)+1);
if(state->filename != NULL) strcpy(state->filename, filename);
state->expectation = section_or_item_specification;
state->tree = NULL;
state->current_item = NULL;
for(i=1; i<PTFPARSER_MAXDEPTH; i++) state->current_in[i] = NULL;
}
/***************************************************************************/
void ptf_free(struct ptf *ptf)
{
struct ptf *this, *next;
for(this = ptf; this != NULL; this = next)
{
next = this->next;
if(this->value != NULL) free(this->value);
if(this->name != NULL) free(this->name);
if(this->type == section) ptf_free(this->sub);
free(this);
};
}
/***************************************************************************/
void abort_parsing(struct ptf_parser_state *state)
{
if(state->filename != NULL) free(state->filename);
ptf_free(state->tree);
}
/***************************************************************************/
int add_char_to_buffer(int *len, char *buf, int maxlen, char c)
{
if(*len >= maxlen) return 0;
buf[(*len)++] = c;
return 1;
}
/***************************************************************************/
void parse_char(struct ptf_parser_state *state, int c)
{
int is_not_quoted;
int is_no_space;
enum { item_parsed, section_opened, section_closed, none } parser_event;
switch(c)
{
case '\\':
{
if(state->flag.escaped == 0)
{
state->flag.escaped = 1;
return;
};
break;
};
case '"':
{
if(!state->flag.escaped && !state->flag.single_quoted)
{
state->flag.double_quoted = 1 - state->flag.double_quoted;
return;
}
break;
};
case '\'':
{
if(!state->flag.escaped && !state->flag.double_quoted)
{
state->flag.single_quoted = 1 - state->flag.single_quoted;
return;
}
break;
};
case '\n':
{
state->line++;
break;
};
default:
break;
};
parser_event = none;
is_not_quoted = !(state->flag.escaped ||
state->flag.single_quoted || state->flag.double_quoted);
is_no_space = (!is_not_quoted || !isspace(c));
state->flag.escaped = 0;
switch(state->expectation)
{
case section_or_item_specification:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: section_or_item_specification\n");
#endif
if(is_not_quoted && c == '}')
{
parser_event = section_closed;
}
else if(is_no_space)
{
state->name_length = 1;
state->name_buffer[0] = c;
state->expectation = more_section_type_or_item_name_chars;
};
break;
};
case more_section_type_or_item_name_chars:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: more_section_type_or_item_name_chars\n");
#endif
/* Item name is stored in name_buffer */
/* Section type is stored in name_buffer */
if(is_no_space)
{
if(!add_char_to_buffer(&state->name_length, state->name_buffer, PTFPARSER_NAMEBUFSIZE, c))
parse_error(state, "First word is too long; I expected a shorter section type or item name");
}
else
{
state->expectation = whitespace_after_section_or_item_name;
}
break;
};
case whitespace_after_section_specification:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: whitespace_after_section_specification\n");
#endif
if(c == '{')
parser_event = section_opened;
else if(is_no_space)
parse_error(state, "Expected section content within brackets {...}");
break;
};
case whitespace_after_section_or_item_name:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: whitespace_after_section_or_item_name\n");
#endif
if(c == '{')
{
state->value_length = 0;
parser_event = section_opened;
}
else if(c == '=')
state->expectation = whitespace_before_item_value;
else if(is_no_space)
{
state->value_length = 1;
state->value_buffer[0] = c;
state->expectation = more_section_name_chars;
};
break;
};
case more_section_name_chars:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: more_section_name_chars\n");
#endif
/* Section name is stored in value_buffer */
if(is_no_space)
{
if(!add_char_to_buffer(&state->value_length, state->value_buffer, PTFPARSER_VALUEBUFSIZE, c))
parse_error(state, "Section name is too long");
}
else
state->expectation = whitespace_after_section_specification;
break;
}
case whitespace_before_item_value:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: whitespace_before_item_value\n");
#endif
if(is_not_quoted && c == ';')
{
state->value_length = 0;
parser_event = item_parsed;
}
else if(is_no_space)
{
state->value_length = 1;
state->value_buffer[0] = c;
state->expectation = more_item_value_chars;
};
break;
};
case more_item_value_chars:
{
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: more_item_value_chars\n");
#endif
/* Item value is stored in value_buffer */
if(is_not_quoted && c == ';')
parser_event = item_parsed;
else if(is_no_space)
{
if(!add_char_to_buffer(&state->value_length, state->value_buffer, PTFPARSER_VALUEBUFSIZE, c))
parse_error(state, "Item value is too long");
}
else
parser_event = item_parsed;
break;
}
default:
#if DEBUG&DEBUG_EXPECTATIONS
printf("Expectation: %d (???)\n", state->expectation);
#endif
parse_error(state, "Internal error: Unhandled state of expectation");
};
switch(parser_event)
{
/* TODO: pointer tuff */
case item_parsed:
{
struct ptf *new_item;
state->name_buffer[state->name_length] = 0;
state->value_buffer[state->value_length] = 0;
#if DEBUG&DEBUG_READVALUES
printf("== Item %s is '%s' ==\n", state->name_buffer, state->value_buffer);
#endif
new_item = ptf_alloc_item(item, state->name_buffer, state->value_buffer);
if(new_item == NULL)
{
parse_error(state, "Internal error: "
"Could not allocate memory for new item");
return;
};
add_ptf_item(state, new_item);
state->current_item = new_item;
state->expectation = section_or_item_specification;
break;
};
case section_opened:
{
struct ptf *new_section;
state->name_buffer[state->name_length] = 0;
state->value_buffer[state->value_length] = 0;
#if DEBUG&DEBUG_READVALUES
printf("== New %s section '%s' opened ==\n", state->name_buffer, state->value_buffer);
#endif
if(state->section_level >= PTFPARSER_MAXDEPTH-1)
{
parse_error(state, "Internal error: "
"cannot handle sections nested as deep as here.");
return;
};
new_section = ptf_alloc_item(section, state->name_buffer, state->value_buffer);
if(new_section == NULL)
{
parse_error(state, "Internal error: "
"Could not allocate memory for new section");
return;
};
add_ptf_item(state, new_section);
state->current_item = NULL;
state->current_in[state->section_level] = new_section;
state->section_level++;
state->expectation = section_or_item_specification;
break;
};
case section_closed:
{
if(state->section_level < 1)
{
parse_error(state, "Found closing '}' without opening '{' before");
return;
};
state->section_level--;
state->current_item = state->current_in[state->section_level];
state->expectation = section_or_item_specification;
#if DEBUG&DEBUG_READVALUES
printf("-- Closed section --\n");
#endif
break;
};
default:
break;
};
}
/***************************************************************************/
struct ptf *ptf_parse_file(char *filename)
{
FILE *f;
char buffer[1024];
struct ptf *root;
struct ptf_parser_state state;
if(filename == NULL)
{
fprintf(stderr, "Internal error: "
"No filename was given to ptf_read()\n");
return NULL;
};
f = fopen(filename, "r");
if(f == NULL)
{
perror(filename);
return NULL;
};
init_parser(&state, filename);
while(!feof(f))
{
size_t r, n;
if(ferror(f))
{
perror(filename);
abort_parsing(&state);
fclose(f);
return NULL;
};
n = fread(buffer, 1, 1024, f);
for(r=0; r<n && (state.flag.error==0); r++) parse_char(&state, buffer[r]);
};
fclose(f);
if(state.section_level != 0)
{
parse_error(&state, "Input file seems to be incomplete, "
"one or more sections are not closed with '}'\n");
};
if(state.flag.error)
{
abort_parsing(&state);
return NULL;
};
return state.tree;
}
/***************************************************************************/
void ptf_printf(FILE *s, struct ptf *tree, char *prefix)
{
struct ptf *leaf;
for(leaf = tree; leaf != NULL; leaf = leaf->next)
{
switch(leaf->type)
{
case section:
{
char *new_prefix;
int new_prefix_len;
new_prefix_len = strlen(prefix) + strlen(leaf->name) + 2;
if(leaf->value != NULL && leaf->value[0] != 0)
{
new_prefix_len += strlen(leaf->value) + 1;
};
new_prefix = (char *)malloc(new_prefix_len);
strcpy(new_prefix, prefix);
strcat(new_prefix, leaf->name);
if(leaf->value != NULL && leaf->value[0] != 0)
{
strcat(new_prefix, "_");
strcat(new_prefix, leaf->value);
};
strcat(new_prefix, "/");
fputs(new_prefix, s);
fputs("\r\n", s);
ptf_printf(s, leaf->sub, new_prefix);
break;
};
case item:
{
char *c;
fputs(prefix, s);
fputs(leaf->name, s);
fputs(" = \"", s);
for(c=leaf->value; *c; c++)
{
if(*c=='\\' || *c=='"') putc('\\', s);
putc(*c, s);
};
fprintf(s, "\"\r\n");
break;
};
default:
break;
};
};
}
/***************************************************************************/
int ptf_advance_one(struct ptf_item *item)
{
int d;
struct ptf *leaf;
d = item->level;
leaf = item->item[d];
if(leaf != NULL)
{
if(leaf->type == section && leaf->sub != NULL)
{
if(item->level >= MAX_SECTION_NESTING-1)
{
/* raise an error? hm, for now we silently ignore the subtree */
}
else
{
d++;
item->item[d] = leaf->sub;
item->level = d;
return 0;
}
}
item->item[item->level] = leaf->next;
};
while(item->item[d] == NULL)
{
if(d == 0)
{
item->level = 0;
item->item[0] = NULL;
errno = ENOENT;
return -1;
}
d --;
leaf = item->item[d];
if(leaf != NULL) item->item[d] = leaf->next;
};
item->level = d;
return 0;
}
/***************************************************************************/
int ptf_advance_until(
struct ptf_item *item,
ptf_item_type ttype,
char *name,
char *value)
{
int r;
struct ptf *leaf;
do
{
leaf = item->item[item->level];
#if DEBUG&DEBUG_FINDER
printf(" Does %s/%s match %s/%s?\n", leaf->name, leaf->value, name, value);
#endif
if(leaf->type == ttype)
{
if(name == NULL)
{
if(value == NULL)
{
return 0; /* got it (any value) */
}
else if (leaf->value != NULL)
{
if(strcmp(leaf->value, value) == 0) return 0; /* got it */
}
}
else if(leaf->name != NULL)
{
if(strcmp(leaf->name, name) == 0)
{
if(value == NULL)
{
return 0; /* got it (any value) */
}
else if(leaf->value != NULL)
{
if(strcmp(leaf->value, value) == 0) return 0; /* got it */
}
}
}
};
r = ptf_advance_one(item);
} while(r == 0);
return r;
}
/***************************************************************************/
struct ptf *ptf_find(
struct ptf *tree,
struct ptf_item *item,
ptf_item_type ttype,
char *name,
char *value)
{
int r;
if(item == NULL) { errno = EINVAL; return NULL; };
if(tree == NULL) { errno = ENOENT; return NULL; };
item->level = 0;
item->item[0] = tree;
if(ptf_advance_until(item, ttype, name, value) != 0) return NULL;
r = item->level;
item->level++; /* To match ptf_match */
return item->item[r];
}
/***************************************************************************/
struct ptf *ptf_next(
struct ptf_item *item,
ptf_item_type ttype,
char *name,
char *value)
{
int r;
struct ptf *leaf;
if(item == NULL) { errno = EINVAL; return NULL; };
if(item->level < 1) return NULL;
item->level--; /* To match ptf_match */
r = ptf_advance_one(item);
if(r == 0) r = ptf_advance_until(item, ttype, name, value);
if(r != 0) return NULL;
r = item->level;
item->level++; /* To match ptf_match */
return item->item[r];
}
/***************************************************************************/
int ptf_match(
struct ptf *const ptf,
struct ptf_item *const match,
const ptf_match_action action,
void *arg)
{
int count;
struct ptf *p;
struct ptf_item pi;
p = ptf;
count = 0;
pi.level = 0;
while(p != NULL)
{
ptf_item_type mtype = match->item[pi.level]->type;
char *mname = match->item[pi.level]->name;
char *mvalue = match->item[pi.level]->value;
#if DEBUG&DEBUG_FINDER
printf("Looking for %s/%s, checking %s/%s\n",
mname, mvalue, p->name, p->value);
#endif
if(mtype == p->type &&
(mname==NULL || p->name==NULL || strcmp(mname, p->name)==0) &&
(mvalue==NULL || p->value==NULL || strcmp(mvalue, p->value)==0))
{
pi.item[pi.level] = p;
if(pi.level == match->level - 1)
{
if(action != NULL) action(&pi, arg);
p = p->next;
count++;
}
else
{
if(p->sub != NULL && pi.level < MAX_SECTION_NESTING-1)
{
pi.item[pi.level] = p;
pi.level++;
p = p->sub;
}
else
{
p = p->next;
};
};
}
else
{
p = p->next;
};
while(p == NULL && pi.level > 0)
{
pi.level--;
p = pi.item[pi.level]->next;
};
};
return count;
}
/***************************************************************************/
char *ptf_defused_name(char *orig_name)
{
int i,j;
char *s = (char *)malloc(1+strlen(orig_name));
if(!s) return NULL;
for(i=j=0;orig_name[i];i++)
{
if(!isalnum(orig_name[i]))
{
if(j>0) if(s[j-1]!='_') s[j++]='_';
}
else
{
s[j++] = toupper(orig_name[i]);
};
};
s[j] = 0;
return s;
}