/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * ShapeTools/shape program - utils.c
 *
 * by Juergen.Nickelsen@cs.tu-berlin.de
 *
 * $Header: utils.c[8.0] Thu Jun 10 18:30:02 1993 axel@cs.tu-berlin.de frozen $
 */
#ifndef lint
static char *AtFSid = "$Header: utils.c[8.0] Thu Jun 10 18:30:02 1993 axel@cs.tu-berlin.de frozen $";
#endif

#include <errno.h>
#include <ctype.h>
#include "shape.h"
#include "parser.h"

/*
 * The memory management functions do the following: allocate, check
 * if result is valid, initialize fields if necessary
 */


/*** raw allocators ***/

/* allocate memory and check */
char *check_malloc(size)
unsigned size ;
{
    char *tmp ;
    
    tmp = malloc(size) ;
    if (tmp == NULL) {
	fatal(NOMORECORE, NULL) ;
    }
    return tmp ;
}

/* reallocate memory and check */
char *check_realloc(ptr, size)
char *ptr ;
unsigned size ;
{
    char *tmp ;

    tmp = realloc(ptr, size) ;
    if (tmp == NULL) {
	fatal(NOMORECORE, NULL) ;
    }
    return tmp ;
}


/*** string functions ***/


/* like strdup, but only n characters
 * the resulting string is null padded, freeable
 */
char *check_strdup(s)
char *s ;
{
    char *tmp = check_malloc(strlen(s) + 1) ;

    strcpy(tmp, s) ;
    return tmp ;
}


/* like strdup, but only n characters
 * the resulting string is null padded, freeable
 */
char *check_strndup(s, n)
char *s ;
int n ;
{
    char *tmp = check_malloc(n + 1) ;

    strncpy0(tmp, s, n) ;
    return tmp ;
}


/* safe strncpy with null padding but no return value */
void strncpy0(s1, s2, n)
char *s1, *s2 ;
int n ;
{
    strncpy(s1, s2, n) ;
    s1[n] = '\0' ;
}

sb_ptr check_sbcat(sb, string)
sb_ptr sb ;
char *string ;
{
    if ((sb = sbcat(sb, string, strlen(string))) == NULL) {
	fatal(NOMORECORE, NULL) ;
    }

    return sb ;
}

/* add one character to a string buffer */
sb_ptr check_sbaddc(sb, c)
sb_ptr sb ;
char c ;
{
    if ((sb = sbcat(sb, &c, 1)) == NULL) {
	fatal(NOMORECORE, NULL) ;
    }

    return sb ;
}

sb_ptr check_sbnew(len)
int len ;
{
    sb_ptr sb ;
    
    if ((sb = sbnew(len)) == NULL) {
	fatal(NOMORECORE, NULL) ;
    }

    return sb ;
}

/* allocate a struct stringlist. This struct is pushed on list
 * with contents str. */

struct stringlist *push_stringlist(str, list, freeable)
char *str ;			/* string value */
struct stringlist *list ;	/* list to push struct on */
int freeable ;			/* if non-zero, string can be freed */
{
    struct stringlist *s ;	/* pointer to allocated list */

    s = (struct stringlist *) check_malloc(sizeof(struct stringlist)) ;
    s->string = str ;		/* set string value */
    s->freeable = freeable ;	/* for free_stringlist() */
    s->next = list ;		/* put in front of list */

    return s ;			/* return head of new list */
}


/* revert a stringlist. This is necessary for rule.cmds,
 * since the commands are added with push_stringlist().
 */
struct stringlist *revert_stringlist(strlp)
struct stringlist *strlp ;
{
    struct stringlist *tmp, *new ;

    new = NULL ;		/* initialize new list */
    
    while (strlp != NULL) {
				/* put head of old list to
				 * head of new list */
	tmp = strlp->next ;
	strlp->next = new ;
	new = strlp ;
	strlp = tmp ;
    }

    return new ;
}


/* free all elements of a stringlist */
void free_stringlist(strlist)
struct stringlist *strlist ;
{
    struct stringlist *tmp ;

    while (strlist != NULL) {
	tmp = strlist ;
	strlist = strlist->next ;
	if (tmp->freeable) {
	    free(tmp->string) ;
	}
	free(tmp) ;
    }
}


/*** errors and warnings ***/

/* print fatal error message and exit */
void fatal(message, message2)
char *message ;
char *message2 ;
{
    fprintf(stderr, "%s fatal error: %s", stProgramName, message) ;
    if (message2 != NULL) fprintf(stderr, " (%s)", message2) ;
    fputc('\n', stderr) ;

#ifdef DEBUG
    abort() ;
#else 
    cleanup() ;
    exit(3) ;
#endif
}


/* specials for the parser */

/* char *skip_over_XXX(char *p): skip over item XXX */

char *skip_over_blanks(p)
char *p ;
{
    while (*p == ' ') p++ ;
    return p ;
}


/* stops at NUL byte */
char *skip_over_whitespace(p)
char *p ;
{
    while (*p && isspace(*p)) p++ ;
    return p ;
}

/* skip over a name or macro reference */

char *skip_over_name(p)
char *p ;
{
    return skip_over_name_with_stop(p, "") ;
}


/* skip over a name or macro reference. Stops at notnamecomponents
 * and at characters in stop */

char *skip_over_name_with_stop(p, stop)
char *p ;
char *stop ;			/* additional characters to stop at */
{
    char in_quote = 0 ;		/* initially outside of quoting */
    int ignore_parens ;		/* if non-zero, don't do paren matching */
    int parenth_level = 0 ;	/* initially no parantheses */

    ignore_parens = strchr(stop, ')') || strchr(stop, '(') ;
    /* loop over string */
    while (*p) {
	switch (*p) {
	  case '\"':		/* quoting character encountered */
	  case '\'':
	  case '`':		/* backquote counts as quoting here */
	    if (in_quote) {
		if (in_quote == *p) { /* end of quoting */
		    in_quote = 0 ;
		}
	    } else {		/* beginning of quoting */
		in_quote = *p ;
	    }
	    break ;
	  case '(':
	    /* if parentheses are to be ignored, we have to fall
	     * through to the "default:" label. This looks pretty ugly,
	     * I know!
	     */
	    if (!ignore_parens) {
		parenth_level++ ;
		break ;
	    }
	  case ')':
	    if (!ignore_parens) {
		if (parenth_level <= 0) {
		    parsing_error("unmatched closing parenthesis", p, NULL) ;
		}
		parenth_level-- ;
		break ;
	    }
	  default:
	    if (!in_quote &&
		(is_no_namecomponent(*p) || strchr(stop, *p)) &&
		(ignore_parens || parenth_level == 0)) {
		/* return if:
		 *  - outside of quoting
		 *  - all parentheses closed (or ignored)
		 *  - character is
		 *      - no namecomponent
		 *      - or is stop character
		 */
		
		return p ;	/* points at first character after name */
	    }
	}
	p++ ;
    }

    /* quoting must be terminated at end of line */
    if (in_quote) {
	parsing_error("non-terminated quoting at end of line", p - 1, NULL) ;
    }

    /* all parentheses must be matched */
    if (!ignore_parens && parenth_level != 0) {
	parsing_error("mismatched parenthesis", p, NULL) ;
    }

    return p ;
}

/* copies a name after point *linep points to a string
 * and returns a pointer to the allocated string. *linep is incremented
 * to point to the first character after the name.
 * Any quoting is removed from the name.
 * macro references are treated as names. */

char *get_name(linep)
char **linep ;			/* pointer to pointer to string */
{
    char *name_beg ;		/* pointer to beginning of name */
    char *name_end ;		/* pointer to end of name */
    
    name_beg = skip_over_whitespace(*linep) ;
    name_end = skip_over_name(name_beg) ;

    if (name_beg == name_end) {
	parsing_error("name or macro expected", *linep, NULL) ;
    }

    *linep = name_end ;
    return unquote(check_strndup(name_beg, name_end - name_beg)) ;
}


/* remove pairs of quoting characters from string. The quoting characters
 * are already guaranteed to be matching by skip_over_name().
 * The unquoting is done in place, but the address of the string
 * is also returned.
 */

char *unquote(string)
char *string ;
{
    int distance = 0 ;		/* distance to move folling characters */
    char in_quote = 0 ;		/* character of current quoting */
    char *from ;		/* place to copy characters from... */
    char *to ;			/* ... and to. */

    to = from = string ;
    while (*from) {
	if (in_quote) {
	    if (*from == in_quote) {
		from++ ;
		in_quote = 0 ;
		distance++ ;
	    } else {
		*to++ = *from++ ;
	    }
	} else {
	    if (*from == '\'' || *from == '\"') {
		in_quote = *from ;
		from++ ;
		distance++ ;
	    } else {
		*to++ = *from++ ;
	    }
	}
    }
    *to = '\0' ;
#ifdef DEBUG
    if (distance != from - to) {
	fatal("wrong distance in unquote()", "Hmm...") ;
    }
#endif
    
    return string ;
}


/* int is_no_namecomponent(char c) */

char notnamecomponents[] = "#:;= " ;

/* may become a macro eventually */
/* is true for NUL character */
int is_no_namecomponent(c)
long c ;
{
    return (int) strchr(notnamecomponents, c) ;
}



/*EOF*/
