/*
 * qstat 2.14
 * by Steve Jankowski
 * steve@qstat.org
 * http://www.qstat.org
 *
 * Copyright 1996,1997,1998,1999 by Steve Jankowski
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "config.h"

#ifdef __hpux
#define STATIC static
#else
#define STATIC
#endif

#ifdef _WIN32
#define strcasecmp      stricmp
#endif


#ifdef _WIN32
#define HOME_CONFIG_FILE "qstat.cfg"
#else
#define HOME_CONFIG_FILE ".qstatrc"
#endif


static server_type ** config_types;
static int n_config_types;
static int max_config_types;
static int next_gametype_id= LAST_BUILTIN_SERVER+1;

static int load_config_file(char const* filename);
static int try_load_config_file(char const* filename, int const show_error);
static int pf_top_level( char *text, void *context);
static int pf_gametype_new( char *text, void *context);
static int pf_gametype_modify( char *text, void *context);

static int set_game_type_value( server_type *gametype, int key, char *value);
static int modify_game_type_value( server_type *gametype, int key, char *value);
static int set_packet_value(server_type* gametype, char const* value,
	char const* packet_name, char** packet, int* len);

static char *first_token( char *text);
static char *next_token();
static char *next_token_dup();
static char *next_value();
static char * parse_value( char *source, int len);
static unsigned int parse_hex( char *hex, int len, int *error);
static unsigned int parse_oct( char *oct, int len, int *error);
static char *force_lower_case( char *string);
static char *force_upper_case( char *string);
static char *get_token();
static void *memdup( const void *mem, unsigned int len);

int parse_config_file( FILE *file);

static int (*parse_func)( char *, void *);
static void *parse_context;
static int line;
static char const* current_file_name;

static char *parse_text;
static char *parse_end;
static char *lex;
static char *token_end;
static char token_buf[1024];
static int value_len;

static int debug= 0;

#define REPORT_ERROR(a)		print_location(); fprintf a; fprintf(stderr, "\n")

enum {
    CK_NONE = 0,
    CK_MASTER_PROTOCOL,
    CK_MASTER_QUERY,
    CK_MASTER_PACKET,
    CK_FLAGS,
    CK_NAME,
    CK_DEFAULT_PORT,
    CK_GAME_RULE,
    CK_TEMPLATE_VAR,
    CK_MASTER_TYPE,
    CK_STATUS_PACKET,
    CK_STATUS2_PACKET,
    CK_PLAYER_PACKET,
    CK_RULE_PACKET,
    CK_PORT_OFFSET
};

typedef struct _config_key {
    int key;
    char *key_name;
} ConfigKey;

static ConfigKey const new_keys[] = {
{ CK_MASTER_PROTOCOL, "master protocol" },
{ CK_MASTER_QUERY, "master query" },
{ CK_MASTER_PACKET, "master packet" },
{ CK_FLAGS, "flags" },
{ CK_NAME, "name" },
{ CK_DEFAULT_PORT, "default port" },
{ CK_GAME_RULE, "game rule" },
{ CK_TEMPLATE_VAR, "template var" },
{ CK_MASTER_TYPE, "master for gametype" },
{ CK_STATUS_PACKET, "status packet" },
{ CK_STATUS2_PACKET, "status2 packet" },
{ CK_PLAYER_PACKET, "player packet" },
{ CK_RULE_PACKET, "rule packet" },
{ CK_PORT_OFFSET, "status port offset" },
{ 0, NULL },
};

static ConfigKey const modify_keys[] = {
{ CK_MASTER_PROTOCOL, "master protocol" },
{ CK_MASTER_QUERY, "master query" },
{ CK_MASTER_PACKET, "master packet" },
{ CK_FLAGS, "flags" },
{ CK_MASTER_TYPE, "master for gametype" },
{ 0, NULL },
};

typedef struct {
    const char *name;
    int value;
} ServerFlag;

#define SERVER_FLAG(x) { #x, x }
ServerFlag const server_flags[] = {
    SERVER_FLAG(TF_SINGLE_QUERY),
    SERVER_FLAG(TF_OUTFILE),
    SERVER_FLAG(TF_MASTER_MULTI_RESPONSE),
    SERVER_FLAG(TF_TCP_CONNECT),
    SERVER_FLAG(TF_QUERY_ARG),
    SERVER_FLAG(TF_QUERY_ARG_REQUIRED),
    SERVER_FLAG(TF_QUAKE3_NAMES),
    SERVER_FLAG(TF_TRIBES2_NAMES),
    SERVER_FLAG(TF_SOF_NAMES),
    SERVER_FLAG(TF_U2_NAMES),
    SERVER_FLAG(TF_RAW_STYLE_QUAKE),
    SERVER_FLAG(TF_RAW_STYLE_TRIBES),
    SERVER_FLAG(TF_RAW_STYLE_GHOSTRECON),
    SERVER_FLAG(TF_NO_PORT_OFFSET),
    SERVER_FLAG(TF_SHOW_GAME_PORT),
    { NULL, 0 }
};
#undef SERVER_FLAG

static int get_config_key( char *first_token, const ConfigKey *keys);
static void add_config_type( server_type *gametype);
static server_type * get_config_type( char *game_type);
static void copy_server_type( server_type *dest, server_type *source);
static server_type * get_server_type( char *game_type);
static server_type * get_builtin_type( char *game_type);

typedef struct _gametype_context {
    char *type;
    char *extend_type;
    server_type *gametype;
} GameTypeContext;


static void
print_location()
{
    fprintf( stderr, "%s line %d: ", current_file_name, line);
}

int
qsc_load_default_config_files()
{
    int rc= 0;
    char *filename= NULL, *var;
    char path[1024];

    var= getenv( "QSTAT_CONFIG");
    if ( var != NULL && var[0] != '\0')  {
	rc= try_load_config_file( var, 1);
	if ( rc == 0 || rc == -1)
	    return rc;
    }

    var= getenv( "HOME");
    if ( var != NULL && var[0] != '\0')  {
	int len= strlen(var);
	if ( len > 900)
	    len= 900;
	strncpy( path, var, len);
	path[len]= '\0';
	strcat( path, "/");
	strcat( path, HOME_CONFIG_FILE);
/*	sprintf( path, "%s/%s", var, HOME_CONFIG_FILE); */
	rc= try_load_config_file( path, 0);
	if ( rc == 0 || rc == -1)
	    return rc;
    }

#ifdef sysconfdir
    strcpy( path, sysconfdir "/qstat.cfg");
    filename= path;
#elif defined(_WIN32)
    if ( filename == NULL && _pgmptr && strchr( _pgmptr, '\\'))  {
	    char *slash= strrchr( _pgmptr, '\\');
	    strncpy( path, _pgmptr, slash - _pgmptr);
	    path[slash - _pgmptr]= '\0';
	    strcat( path, "\\qstat.cfg");
	    filename= path;
    }
#endif

    if ( filename != NULL)  {
	rc= try_load_config_file( filename, 0);
	if ( rc == 0 || rc == -1)
	    return rc;
    }
    return rc;
/*
    if ( rc == -2 && show_error)  {
	perror( filename);
	fprintf( stderr, "Error: Could not open config file \"%s\"\n",
		filename);
    }
    else if ( rc == -1 && show_error)
	fprintf( stderr, "Error: Error loading $QSTAT_CONFIG file\n");
    return rc;
*/

#ifdef foo
    filename= getenv( "HOME");
    if ( filename != NULL && filename[0] != '\0')  {
	char path[1024];
	snprintf( path, (sizeof(path) -1),"%s/%s", var, HOME_CONFIG_FILE);
    }
/* 1. $QSTAT_CONFIG
   2. UNIX: $HOME/.qstatrc         WIN: $HOME/qstat.cfg
   3. UNIX: sysconfdir/qstat.cfg   WIN: qstat.exe-dir/qstat.cfg
*/

    rc= load_config_file( "qstat.cfg");
    if ( rc == -1)
	fprintf( stderr, "Warning: Error loading default qstat.cfg\n");
    return 0;
#endif
}

int
qsc_load_config_file(char const* filename)
{
    int rc= load_config_file( filename);
    if ( rc == -2)  {
	perror( filename);
	fprintf( stderr, "Could not open config file \"%s\"\n",
		filename);
	return -1;
    }
    return rc;
}

server_type **
qsc_get_config_server_types( int *n_config_types_ref)
{
    if ( n_config_types_ref)
	*n_config_types_ref= n_config_types;
    return config_types;
}

STATIC int
try_load_config_file(char const* filename, int const show_error)
{
    int rc= load_config_file( filename);
    if ( rc == -2 && show_error)  {
	perror( filename);
	fprintf( stderr, "Error: Could not open config file \"%s\"\n",
		filename);
    }
    else if ( rc == -1)
	fprintf( stderr, "Error: Could not load config file \"%s\"\n",
		filename);
    return rc;
}

STATIC int
load_config_file(char const* filename)
{
    FILE *file;
    int rc;

    file= fopen( filename, "r");
    if ( file == NULL)
	return -2;

    current_file_name= filename;
    rc= parse_config_file( file);
    fclose(file);
    return rc;
}

int
parse_config_file( FILE *file)
{
    char buf[4096];
    int rc;

    line= 0;
    parse_func= pf_top_level;

    while ( fgets( buf, sizeof(buf), file) != NULL)  {
	int len= strlen(buf);
	while ( len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
	   len--;
	buf[len]= '\0';
	line++;
	rc= parse_func( buf, parse_context);
	if ( rc != 0)
	    return rc;
    }
    return 0;
}


/* Top level
 *   Keywords: gametype
 */
STATIC int
pf_top_level( char *text, void *_context)
{
    GameTypeContext *context;
    server_type *extend;
    char *token, *game_type, *extend_type;
    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "gametype") != 0)  {
	REPORT_ERROR(( stderr, "Unknown config command \"%s\"", token));
	return -1;
    }

    game_type= next_token_dup();
    if ( game_type == NULL)  {
	REPORT_ERROR(( stderr, "Missing game type"));
	return -1;
    }

    force_lower_case( game_type);

    token= next_token();
    if ( token == NULL)  {
	REPORT_ERROR(( stderr, "Expecting \"new\" or \"modify\""));
	return -1;
    }

    if ( strcmp( token, "new") == 0)  {
	parse_func= pf_gametype_new;
    }
    else if ( strcmp( token, "modify") == 0)  {
	parse_func= pf_gametype_modify;
    }
    else  {
	REPORT_ERROR(( stderr, "Expecting \"new\" or \"modify\""));
	return -1;
    }

    context= (GameTypeContext*) malloc( sizeof(GameTypeContext));
    context->type= game_type;
    context->extend_type= NULL;
    context->gametype= NULL;

    token= next_token();

    if ( parse_func == pf_gametype_modify)  {
	if ( token != NULL)  {
	    REPORT_ERROR(( stderr, "Extra text after gametype modify"));
	    return -1;
	}
	context->gametype= get_server_type( game_type);
	if ( context->gametype == NULL)  {
	    REPORT_ERROR(( stderr, "Unknown game type \"%s\"", game_type));
	    free(context);
	    return -1;
	}
	parse_context= context;
	return 0;
    }

    if ( token == NULL || strcmp( token, "extend") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"extend\""));
	return -1;
    }

    extend_type= next_token();

    if ( extend_type == NULL)  {
	REPORT_ERROR(( stderr, "Missing extend game type"));
	return -1;
    }

    force_lower_case( extend_type);

    if ( strcasecmp( extend_type, game_type) == 0)  {
	REPORT_ERROR(( stderr, "Identical game type and extend type"));
	return -1;
    }

    context->extend_type= extend_type;
    extend= get_server_type( extend_type);
    if ( extend == NULL)  {
	REPORT_ERROR(( stderr, "Unknown extend game type \"%s\"", extend_type));
	return -1;
    }

    /* Over-write a previous gametype new */
    context->gametype= get_config_type( game_type);
    if ( context->gametype == NULL)
	context->gametype= (server_type*) malloc( sizeof(server_type));
    copy_server_type( context->gametype, extend);

    /* Set flag for new type-id if not re-defining previous config type */
    if ( get_config_type( game_type) == NULL)
	context->gametype->id= 0;

    context->gametype->type_string= game_type;
    context->gametype->type_prefix= strdup(game_type);
    force_upper_case( context->gametype->type_prefix);
    context->gametype->type_option= (char*)malloc( strlen(game_type)+2);
    context->gametype->type_option[0]= '-';
    strcpy( &context->gametype->type_option[1], game_type);

    parse_context= context;

    return 0;
}

STATIC int
pf_gametype_modify( char *text, void *_context)
{
    GameTypeContext *context= (GameTypeContext*) _context;
    char *token;
    int key;

    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "end") == 0)  {
	parse_func= pf_top_level;
	parse_context= NULL;
	return 0;
    }

    key= get_config_key( token, modify_keys);

    token= next_token();

    if ( strcmp( token, "=") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"=\", found \"%s\"", token));
	return -1;
    }

    token= next_value();
    if ( token == NULL)  {
	REPORT_ERROR(( stderr, "Missing value after \"=\""));
	return -1;
    }

    if ( debug)
	printf( "%d %s = <%s>\n", key, modify_keys[key-1].key_name,
		token?token:"");

    return modify_game_type_value( context->gametype, key, token);
}

STATIC int
pf_gametype_new( char *text, void *_context)
{
    GameTypeContext *context= (GameTypeContext*) _context;
    char *token;
    int key;

    token= first_token( text);
    if ( token == NULL)
	return 0;

    if ( strcmp( token, "end") == 0)  {
	add_config_type( context->gametype);
	parse_func= pf_top_level;
	parse_context= NULL;
	return 0;
    }

    key= get_config_key( token, new_keys);

    if ( key <= 0)
	return key;

    token= next_token();
    if ( strcmp( token, "=") != 0)  {
	REPORT_ERROR(( stderr, "Expecting \"=\", found \"%s\"", token));
	return -1;
    }

    token= next_value();
    if ( token == NULL && (key != CK_MASTER_PROTOCOL && key != CK_MASTER_QUERY))  {
	REPORT_ERROR(( stderr, "Missing value after \"=\""));
	return -1;
    }

    if ( debug)
	printf("%d %s = <%s>\n", key, new_keys[key-1].key_name, token?token:"");

    return set_game_type_value( context->gametype, key, token);
}

STATIC int
get_config_key( char *first_token, const ConfigKey *keys)
{
    char key_name[1024], *token;
    int key= 0;

    strcpy( key_name, first_token);
    do  {
	int k;
	for ( k= 0; keys[k].key_name; k++)  {
	    if ( strcmp( keys[k].key_name, key_name) == 0)  {
		key= keys[k].key;
		break;
	    }
	}
	if ( key)
	    break;
	token= next_token();
	if ( token == NULL || strcmp( token, "=") == 0)
	    break;
	if ( strlen(key_name) + strlen(token) > sizeof(key_name)-2)  {
	    REPORT_ERROR(( stderr, "Key name too long"));
	    return -1;
	}
	strcat( key_name, " ");
	strcat( key_name, token);
    } while ( 1);

    if ( key == 0)  {
	REPORT_ERROR(( stderr, "Unknown config key \"%s\"", key_name));
	return -1;
    }

    return key;
}

STATIC int get_server_flag_value(const char* value, unsigned len)
{
    int i= 0;

    for ( i= 0; server_flags[i].name; ++i)  {
	if ( len == strlen(server_flags[i].name) &&
		strncmp( server_flags[i].name, value, len) == 0)  {
	    return server_flags[i].value;
	}
    }

    return -1;
}

STATIC int parse_server_flags(const char* value)
{
    int val = 0, v, first = 1;
    const char* s = value;
    const char* e;

    while(*s)
    {
	while(isspace((unsigned char)*s))
	    ++s;

	if(!first)
	{
	    if(*s != '|') {
		REPORT_ERROR(( stderr, "Syntax error: expecting |"));
		val = -1;
		break;
	    }

	    ++s;

	    while(isspace((unsigned char)*s))
		++s;
	}
	else
	    first = 0;

	e = s;

	while(isalnum((unsigned char)*e) || *e == '_')
	    ++e;

	if(e == s) {
	    REPORT_ERROR(( stderr, "Syntax error: expecting flag"));
	    val = -1;
	    break;
	}

	v = get_server_flag_value(s, e-s);
	if(v == -1) {
	    REPORT_ERROR(( stderr, "Syntax error: invalid flag"));
	    val = -1;
	    break;
	}

	s = e;
	val |= v;
    }

    return val;
}

STATIC int
set_game_type_value( server_type *gametype, int key, char *value)
{
    switch ( key)  {
    case CK_NAME:
	gametype->game_name= strdup(value);
	break;
    case CK_FLAGS:
	{
	    int flags = parse_server_flags(value);
	    if(flags == -1)
		return -1;
	    gametype->flags = flags;
	}
	break;
    case CK_DEFAULT_PORT: {
	unsigned short port;
	if ( sscanf( value, "%hu", &port) != 1)  {
	    REPORT_ERROR(( stderr, "Syntax error on port.  Should be a number between 1 and 65535."));
	    return -1;
	}
	gametype->default_port= port;
	break;
	}
    case CK_GAME_RULE:
	gametype->game_rule= strdup(value);
	break;
    case CK_TEMPLATE_VAR:
	gametype->template_var= strdup(value);
	force_upper_case( gametype->template_var);
	break;
    case CK_MASTER_PROTOCOL:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type\n"));
	    return -1;
	}
	if ( value)
	    gametype->master_protocol= strdup(value);
	else
	    gametype->master_protocol= NULL;
	break;
    case CK_MASTER_QUERY:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master query on non-master game type\n"));
	    return -1;
	}
	if ( value)
	    gametype->master_query= strdup(value);
	else
	    gametype->master_query= NULL;
	break;
    case CK_MASTER_TYPE:
	{
	server_type *type;
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n"));
	    return -1;
	}
	force_lower_case( value);
	type= get_server_type( value);
	if ( type == NULL)  {
	    REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value));
	    return -1;
	}
	gametype->master= type->id;
	}
	break;
    case CK_MASTER_PACKET:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
	    return -1;
	}
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty master packet"));
	    return -1;
	}
	gametype->master_packet= memdup( value, value_len);
	gametype->master_len= value_len;
	break;
    case CK_STATUS_PACKET:
	return set_packet_value( gametype, value, "status",
		&gametype->status_packet, &gametype->status_len);
    case CK_STATUS2_PACKET:
	return set_packet_value( gametype, value, "status2",
		&gametype->rule_packet, &gametype->rule_len);
    case CK_PLAYER_PACKET:
	return set_packet_value( gametype, value, "player",
		&gametype->player_packet, &gametype->player_len);
    case CK_RULE_PACKET:
	return set_packet_value( gametype, value, "rule",
		&gametype->rule_packet, &gametype->rule_len);
    case CK_PORT_OFFSET:  {
	short port;
	if ( sscanf( value, "%hd", &port) != 1)  {
	    REPORT_ERROR(( stderr, "Syntax error on port.  Should be a number between 32767 and -32768."));
	    return -1;
	}
	gametype->port_offset= port;
	break;
	}
    }

    return 0;
}

STATIC int
set_packet_value(server_type* gametype, char const* value, char const* packet_name,
	char **packet, int *len)
{
    if ( gametype->master)  {
	REPORT_ERROR((stderr, "Cannot set info packet on master game type"));
	return -1;
    }
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty %s packet", packet_name));
	    return -1;
	}
	// Removed as this doesn't seem to be any reason why we can't do this and it works just fine for warsow
	//if ( *packet == NULL)  {
	//    REPORT_ERROR((stderr, "Cannot set %s packet; extend game type does not define a %s packet", packet_name, packet_name));
	//    return -1;
	//}

	*packet= memdup( value, value_len);
	*len= value_len;
    return 0;
}

STATIC int
modify_game_type_value( server_type *gametype, int key, char *value)
{
    switch ( key)  {
    case CK_FLAGS:
	{
	    int flags = parse_server_flags(value);
	    if(flags == -1)
		return -1;
	    gametype->flags = flags;
	}
	break;
    case CK_MASTER_PROTOCOL:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type"));
	    return -1;
	}
	if ( value)
	    gametype->master_protocol= strdup(value);
	else
	    gametype->master_protocol= NULL;
	break;
    case CK_MASTER_QUERY:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master query on non-master game type"));
	    return -1;
	}
	if ( value)
	    gametype->master_query= strdup(value);
	else
	    gametype->master_query= NULL;
	break;
    case CK_MASTER_PACKET:
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master packet on non-master game type"));
	    return -1;
	}
	if ( value == NULL || value[0] == '\0')  {
	    REPORT_ERROR((stderr, "Empty master packet"));
	    return -1;
	}
	gametype->master_packet= memdup( value, value_len);
	gametype->master_len= value_len;
	break;
    case CK_MASTER_TYPE:
	{
	server_type *type;
	if ( ! gametype->master)  {
	    REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n"));
	    return -1;
	}
	force_lower_case( value);
	type= get_server_type( value);
	if ( type == NULL)  {
	    REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value));
	    return -1;
	}
	gametype->master= type->id;
	}
	break;
    }
    return 0;
}

STATIC server_type *
get_server_type( char *game_type)
{
    server_type *result;
    result= get_builtin_type( game_type);
    if ( result != NULL)
	return result;

    return get_config_type( game_type);
}

STATIC server_type*
get_builtin_type( char* type_string)
{
    server_type *type= &builtin_types[0];

    for ( ; type->id != Q_UNKNOWN_TYPE; type++)
	if ( strcmp( type->type_string, type_string) == 0)
	    return type;
    return NULL;
}

STATIC void
add_config_type( server_type *gametype)
{
    if ( gametype->id == 0)  {
	if ( next_gametype_id >= MASTER_SERVER)  {
	    REPORT_ERROR(( stderr, "Exceeded game type limit, ignoring \"%s\"",
		gametype->type_string));
	    return;
	}
	gametype->id= next_gametype_id;
	if ( gametype->master)
	    gametype->id |= MASTER_SERVER;
	next_gametype_id++;
    }

    if ( max_config_types == 0)  {
	max_config_types= 4;
	config_types= (server_type**) malloc(sizeof(server_type*) * (max_config_types + 1));
    }
    else if ( n_config_types >= max_config_types)  {
	max_config_types*= 2;
	config_types= (server_type**) realloc( config_types,
		sizeof(server_type*) * (max_config_types + 1));
    }
    config_types[ n_config_types]= gametype;
    n_config_types++;
}

STATIC server_type *
get_config_type( char *game_type)
{
    int i;
    for ( i= 0; i < n_config_types; i++)
	if ( strcmp( config_types[i]->type_string, game_type) == 0)
	    return config_types[i];

    return NULL;
}

STATIC void
copy_server_type( server_type *dest, server_type *source)
{
    *dest= *source;
}

STATIC void *
memdup( const void *mem, unsigned int len)
{
    void *result= malloc( len);
	if ( NULL == result )
	{
	    REPORT_ERROR(( stderr, "Failed to malloc %d bytes of memory", len ) );
		return NULL;
	}
	memcpy( result, mem, len);
    return result;
}

/* Parsing primitives
 */

STATIC char *
first_token( char *text)
{
    parse_text= text;
    parse_end= parse_text + strlen( parse_text);
    lex= text;
    token_end= text;

    if ( *lex == '#')
	return NULL;

    return get_token();
}

STATIC char *
next_token()
{
    lex= token_end;
    return get_token();
}

STATIC char *
next_token_dup()
{
    return strdup( next_token());
}

STATIC char *
get_token()
{
    char *token= &token_buf[0];
    while ( isspace((unsigned char)*token_end))
	token_end++;

    if ( token_end == parse_end)
	return NULL;

    while ( (isalnum((unsigned char)*token_end) || *token_end == '.' || *token_end == '_' || *token_end == '-')
		&& token < &token_buf[0] + sizeof(token_buf))
	*token++= *token_end++;

    if ( token == &token_buf[0])
	*token++= *token_end++;
    *token= '\0';
    return &token_buf[0];
}

STATIC char *
next_value()
{
    char *token= &token_buf[0];
    while ( isspace((unsigned char)*token_end))
	token_end++;

    if ( token_end == parse_end)
	return NULL;

    while ( token_end < parse_end && token < &token_buf[0] + sizeof(token_buf))
	*token++= *token_end++;

    *token--= '\0';

    if ( strchr( token_buf, '\\'))
	return parse_value(token_buf, (token - token_buf)+1);

    while ( isspace((unsigned char)*token))
	*token--= '\0';

    value_len= token - token_buf + 1;

    return &token_buf[0];
}

STATIC char *
parse_value( char *source, int len)
{
    char *value, *v, *end;
    int error= 0;

    value= (char*)malloc( len + 1);
    end = v = value;

/*
printf( "parse_value <%.*s>\n", len, source);
*/

    for ( ; len; len--, source++)  {
	if ( *source != '\\')  {
	    *v++= *source;
	    if ( *source != ' ')
		end= v;
	    continue;
	}
	source++; len--;
	if ( len == 0)
	    break;
	if ( *source == '\\')
	    *v++= *source;
	else if ( *source == 'n')
	    *v++= '\n';
	else if ( *source == 'r')
	    *v++= '\r';
	else if ( *source == ' ')
	    *v++= ' ';
	else if ( *source == 'x')  {
	    source++; len--;
	    if ( len < 2)
		break;
	    *v++= parse_hex(source, 2, &error);
	    if ( error)
		break;
	    source++; len--;
	}
	else if ( isdigit( (unsigned char)*source))  {
	    if ( len < 3)
		break;
	    *v++= parse_oct(source, 3, &error);
	    if ( error)
		break;
	    source++; len--;
	    source++; len--;
	}
	else  {
	    error= 1;
	    REPORT_ERROR(( stderr, "Invalid character escape \"%.*s\"", 2,
		source-1));
	    break;
	}
	end= v;
    }

    if ( error)  {
	free(value);
	return NULL;
    }

    value_len= end - value;
    memcpy( token_buf, value, value_len);
    token_buf[value_len]= '\0';

    free(value);
    return &token_buf[0];
}

STATIC unsigned int
parse_hex( char *hex, int len, int *error)
{
    char *save_hex= hex;
    int save_len= len;
    unsigned int result= 0;
    *error= 0;
    for ( ; len; len--, hex++)  {
	result <<= 4;
	if ( ! isxdigit( (unsigned char)*hex))  {
	    *error= 1;
	    REPORT_ERROR(( stderr, "Invalid hex \"%.*s\"", save_len+2,
		save_hex-2));
	    return 0;
	}
	if ( *hex >= '0' && *hex <= '9')
	    result|= *hex - '0';
	else if ( *hex >= 'A' && *hex <= 'F')
	    result|= ((*hex - 'A') + 10);
	else if ( *hex >= 'a' && *hex <= 'f')
	    result|= ((*hex - 'a') + 10);
    }
    return result;
}

STATIC unsigned int
parse_oct( char *oct, int len, int *error)
{
    char *save_oct= oct;
    int save_len= len;
    unsigned int result= 0;
    *error= 0;
    for ( ; len; len--, oct++)  {
	result <<= 3;
	if ( *oct >= '0' && *oct <= '7')
	    result|= *oct - '0';
	else  {
	    *error= 1;
	    REPORT_ERROR(( stderr, "Invalid octal \"%.*s\"", save_len+1,
		save_oct-1));
	    return 0;
	}
    }
    return result;
}

STATIC char *
force_lower_case( char *string)
{
    char *s= string;
    for ( ; *s; s++)
	*s= tolower(*s);
    return string;
}

STATIC char *
force_upper_case( char *string)
{
    char *s= string;
    for ( ; *s; s++)
	*s= toupper(*s);
    return string;
}

