/* grecs - Gray's Extensible Configuration System
   Copyright (C) 2007-2021 Sergey Poznyakoff

   Grecs 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 3 of the License, or (at your
   option) any later version.

   Grecs is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with Grecs. If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include "grecs.h"

struct grecs_sockaddr_hints *grecs_sockaddr_hints;

void
grecs_value_free_content(struct grecs_value *val)
{
	int i;

	if (!val)
		return;
	switch (val->type) {
	case GRECS_TYPE_STRING:
		grecs_free(val->v.string);
		break;

	case GRECS_TYPE_LIST:
		grecs_list_free(val->v.list);
		break;

	case GRECS_TYPE_ARRAY:
		for (i = 0; i < val->v.arg.c; i++)
			grecs_value_free(val->v.arg.v[i]);
		free(val->v.arg.v);
	}
}

void
grecs_value_free(struct grecs_value *val)
{
	grecs_value_free_content(val);
	grecs_free(val);
}

const char *
grecs_value_type_string(int t)
{
	static const char *types[] = {
		[GRECS_TYPE_STRING] = N_("string"),
		[GRECS_TYPE_LIST]   = N_("list"),
		[GRECS_TYPE_ARRAY]  = N_("one or more arguments")
	};
	if (t >= 0 && t < sizeof(types) / sizeof(types[0]))
		return types[t];
	return N_("unrecognized type; please report");
}

struct grecs_node *
grecs_node_create(enum grecs_node_type type, grecs_locus_t *loc)
{
	struct grecs_node *np = grecs_zalloc(sizeof(*np));
	np->type = type;
	if (loc)
		np->locus = *loc;
	return np;
}

struct grecs_node *
grecs_node_create_points(enum grecs_node_type type,
			 struct grecs_locus_point beg,
			 struct grecs_locus_point end)
{
	grecs_locus_t loc;

	loc.beg = beg;
	loc.end = end;
	return grecs_node_create(type, &loc);
}

void
grecs_node_bind(struct grecs_node *master, struct grecs_node *node, int dn)
{
	struct grecs_node *np;

	if (!node)
		return;
	if (dn) {
		if (!master->down) {
			master->down = node;
			node->prev = NULL;
		} else {
			for (np = master->down; np->next; np = np->next)
				;
			np->next = node;
			node->prev = np;
		}
		for (; node; node = node->next)
			node->up = master;
	} else {
		if (!master->next) {
			master->next = node;
			node->prev = master;
		} else {
			for (np = master->next; np->next; np = np->next)
				;
			np->next = node;
			node->prev = np;
		}
		node->up = master->up;
	}
}

int
grecs_node_unlink(struct grecs_node *node)
{
	if (node->prev)
		node->prev->next = node->next;
	else if (node->up)
		node->up->down = node->next;
	else
		return 1;
	if (node->next)
		node->next->prev = node->prev;
	node->up = node->prev = node->next = NULL;
	return 0;
}

static void
listel_dispose(void *el)
{
	grecs_free(el);
}
 
struct grecs_list *
_grecs_simple_list_create(int dispose)
{
	struct grecs_list *lp = grecs_list_create();
	if (dispose)
		lp->free_entry = listel_dispose;
	return lp;
}




static enum grecs_tree_recurse_res
_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun,
	      void *data)
{
	enum grecs_tree_recurse_res res;
#define CKRES()						\
	switch (res) {					\
        case grecs_tree_recurse_fail:			\
        case grecs_tree_recurse_stop:			\
		return res;				\
	default:					\
		break;					\
	}

	while (node) {
		struct grecs_node *next = node->next;
		if (node->type == grecs_node_stmt) {
			res = recfun(grecs_tree_recurse_set, node, data);
			CKRES();
		} else {
			switch (recfun(grecs_tree_recurse_pre, node, data)) {
			case grecs_tree_recurse_ok:
				res = _tree_recurse(node->down, recfun, data);
				CKRES();
				res = recfun(grecs_tree_recurse_post, node,
					     data);
				CKRES();
				break;
			case grecs_tree_recurse_fail:
				return grecs_tree_recurse_fail;
			case grecs_tree_recurse_stop:
				return grecs_tree_recurse_stop;
			case grecs_tree_recurse_skip:
				break;
			}
		}
		node = next;
	}
	return grecs_tree_recurse_ok;
#undef CKRES
}

int
grecs_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun,
		   void *data)
{
	switch (_tree_recurse(node, recfun, data)) {
	case grecs_tree_recurse_ok:
	case grecs_tree_recurse_stop:
		return 0;
	default:
		break;
	}
	return 1;
}

void
grecs_node_free(struct grecs_node *node)
{
	if (!node)
		return;
	switch (node->type) {
	case grecs_node_root:
		grecs_symtab_free(node->v.texttab);
		break;
	default:
		grecs_value_free(node->v.value);
	}
	grecs_free(node->ident);
	grecs_free(node);
}

static enum grecs_tree_recurse_res
freeproc(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
{
	switch (op) {
	case grecs_tree_recurse_set:
	case grecs_tree_recurse_post:
		grecs_node_unlink(node);
		grecs_node_free(node);
		break;
	case grecs_tree_recurse_pre:
		/* descend into the subtree */
		break;
	}
	return grecs_tree_recurse_ok;
}

int
grecs_tree_free(struct grecs_node *node)
{
	if (!node)
		return 0;
	if (node->type != grecs_node_root) {
		errno = EINVAL;
		return 1;
	}
	grecs_tree_recurse(node, freeproc, NULL);
	return 0;
}



static int
fake_callback(
#if GRECS_TREE_API
	enum grecs_callback_command cmd,
	grecs_node_t *node,
	void *varptr,
	void *cb_data
#else	
	enum grecs_callback_command cmd,
	grecs_locus_t *locus,
	void *varptr,
	grecs_value_t *value,
	void *cb_data
#endif	
	)
{
	return 0;
}

static struct grecs_keyword fake = {
	"*",
	NULL,
	NULL,
	grecs_type_void,
	GRECS_DFLT,
	NULL,
	0,
	fake_callback,
	NULL,
	&fake
};

static struct grecs_keyword *
find_keyword(struct grecs_keyword *cursect, grecs_node_t *node)
{
	if (cursect && cursect->kwd && cursect != &fake) {
		struct grecs_keyword *found = NULL, *kwp;
		char const *msg;

		for (kwp = cursect->kwd; kwp->ident; kwp++) {
			if (strcmp(kwp->ident, node->ident) == 0) {
				found = kwp;
				if (kwp->callback
				    || (node->down
				         ? kwp->type == grecs_type_section
					: kwp->type != grecs_type_section))
					return kwp;
			}
		}
		if (found) {
			if (found->type == grecs_type_section) {
				msg = N_("section keyword used as a scalar");
			} else {
				msg = N_("scalar keyword used as a section");
			}
		} else {
			msg = N_("unknown keyword");
		}
		grecs_error(&node->idloc, 0, "%s", gettext(msg));
	} else {
		return &fake;
	}
	return NULL;
}

static void *
target_ptr(struct grecs_keyword *kwp, char *base)
{
	if (kwp->varptr)
		base = (char*) kwp->varptr + kwp->offset;
	else if (base)
		base += kwp->offset;
	
	return base;
}

static int
string_to_bool(const char *string, int *pval, grecs_locus_t const *locus)
{
	if (strcmp(string, "yes") == 0
	    || strcmp(string, "true") == 0
	    || strcmp(string, "t") == 0
	    || strcmp(string, "1") == 0)
		*pval = 1;
	else if (strcmp(string, "no") == 0
		 || strcmp(string, "false") == 0
		 || strcmp(string, "nil") == 0
		 || strcmp(string, "0") == 0)
		*pval = 0;
	else {
		grecs_error(locus, 0,
			     _("%s: not a valid boolean value"),
			     string);
		return 1;
	}
	return 0;
}

static int
string_to_host(struct in_addr *in, const char *string,
	       grecs_locus_t const *locus)
{
	if (inet_aton(string, in) == 0) {
		struct hostent *hp;

		hp = gethostbyname(string);
		if (hp == NULL)
			return 1;
		memcpy(in, hp->h_addr, sizeof(struct in_addr));
	}
	return 0;
}

#if !GRECS_SOCKADDR_LIST
static int
string_to_sockaddr(struct grecs_sockaddr *sp, const char *string,
		   grecs_locus_t const *locus)
{
	if (string[0] == '/') {
		struct sockaddr_un s_un;
		if (strlen(string) >= sizeof(s_un.sun_path)) {
			grecs_error(locus, 0,
				     _("%s: UNIX socket name too long"),
				     string);
			return 1;
		}
		s_un.sun_family = AF_UNIX;
		strcpy(s_un.sun_path, string);
		sp->len = sizeof(s_un);
		sp->sa = grecs_malloc(sp->len);
		memcpy(sp->sa, &s_un, sp->len);
	} else {
		char *p = strchr(string, ':');
		size_t len;
		struct sockaddr_in sa;
		
		sa.sin_family = AF_INET;
		if (p) 
			len = p - string;
		else
			len = strlen(string);
		
		if (len == 0)
			sa.sin_addr.s_addr = INADDR_ANY;
		else {
			char *host = grecs_malloc(len + 1);
			memcpy(host, string, len);
			host[len] = 0;
			
			if (string_to_host(&sa.sin_addr, host, locus)) {
				grecs_error(locus, 0,
					     _("%s: not a valid IP address or hostname"),
					     host);
				grecs_free(host);
				return 1;
			}
			grecs_free(host);
		}
		
		if (p) {
			struct servent *serv;
			
			p++;
			serv = getservbyname(p, "tcp");
			if (serv != NULL)
				sa.sin_port = serv->s_port;
			else {
				unsigned long l;
				char *q;
				
				/* Not in services, maybe a number? */
				l = strtoul(p, &q, 0);
				
				if (*q || l > USHRT_MAX) {
					grecs_error(locus, 0,
						     _("%s: not a valid port number"), p);
					return 1;
				}
				sa.sin_port = htons(l);
			}
		} else if (grecs_default_port)
			sa.sin_port = grecs_default_port;
		else {
			grecs_error(locus, 0, _("missing port number"));
			return 1;
		}
		sp->len = sizeof(sa);
		sp->sa = grecs_malloc(sp->len);
		memcpy(sp->sa, &sa, sp->len);
	}
	return 0;
}
#endif

/* The TYPE_* defines come from gnulib's intprops.h */

/* True if the arithmetic type T is signed.  */
# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))

/* The maximum and minimum values for the integer type T.  These
   macros have undefined behavior if T is signed and has padding bits. */
# define TYPE_MINIMUM(t) \
  ((t) (! TYPE_SIGNED(t) \
        ? (t) 0 \
        : TYPE_SIGNED_MAGNITUDE(t) \
        ? ~ (t) 0 \
        : ~ TYPE_MAXIMUM(t)))
# define TYPE_MAXIMUM(t) \
  ((t) (! TYPE_SIGNED(t) \
        ? (t) -1 \
        : ((((t) 1 << (sizeof(t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
# define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1)


#define STRTONUM(s, type, base, res, limit, loc)			\
  {									\
    type sum = 0;							\
    									\
    for (; *s; s++)							\
      {									\
	type x;								\
									\
	if ('0' <= *s && *s <= '9')					\
	  x = sum * base + *s - '0';					\
	else if (base == 16 && 'a' <= *s && *s <= 'f')			\
	  x = sum * base + *s - 'a';					\
	else if (base == 16 && 'A' <= *s && *s <= 'F')			\
	  x = sum * base + *s - 'A';					\
	else								\
	  break;							\
	if (x <= sum)							\
	  {								\
	    grecs_error(loc, 0, _("numeric overflow"));		        \
	    return 1;							\
	  }								\
	else if (limit && x > limit)					\
	  {								\
	    grecs_error(loc, 0, _("value out of allowed range"));	\
	    return 1;							\
	  }								\
	sum = x;							\
      }									\
    res = sum;								\
  }

#define STRxTONUM(s, type, res, limit, loc)				\
  {									\
    int base;								\
    if (*s == '0')							\
      {									\
	s++;								\
	if (*s == 0)							\
	  base = 10;							\
	else if (*s == 'x' || *s == 'X')				\
	  {								\
	    s++;							\
	    base = 16;							\
	  }								\
	else								\
	  base = 8;							\
      } else								\
      base = 10;							\
    STRTONUM(s, type, base, res, limit, loc);				\
  }

#define GETUNUM(str, type, res, loc)					\
  {									\
    type tmpres;							\
    const char *s = str;						\
    STRxTONUM(s, type, tmpres, 0, loc);				\
    if (*s)								\
      {									\
	grecs_error(loc, 0, _("not a number (stopped near `%s')"),	\
		     s);						\
	return 1;							\
      }									\
    res = tmpres;							\
  }

#define GETSNUM(str, type, res, loc)					\
  {									\
    unsigned type tmpres;						\
    const char *s = str;						\
    int sign;							        \
    unsigned type limit;						\
    									\
    if (*s == '-')							\
      {									\
	sign = 1;							\
	s++;								\
	limit = TYPE_MINIMUM(type);					\
	limit = - limit;						\
      }									\
    else								\
      {									\
	sign = 0;							\
	limit = TYPE_MAXIMUM(type);					\
      }									\
    									\
    STRxTONUM(s, unsigned type, tmpres, limit, loc);			\
    if (*s)								\
      {									\
	grecs_error(loc, 0, _("not a number (stopped near `%s')"), s);	\
	return 1;							\
      }									\
    res = sign ? - tmpres : tmpres;					\
  }


int
grecs_string_convert(void *target, enum grecs_data_type type,
		     const char *string, grecs_locus_t const *locus)
{
  switch (type) {
  case grecs_type_void:
	  abort();

  case grecs_type_null:
	  break;
	  
  case grecs_type_string:
	  *(char**)target = grecs_strdup(string);
	  break;
	    
  case grecs_type_short:
	  GETUNUM(string, short, *(short*)target, locus);
	  break;
	    
  case grecs_type_ushort:
	  GETUNUM(string, unsigned short, *(unsigned short*)target, locus);
	  break;
	    
  case grecs_type_bool:
	  return string_to_bool(string, (int*)target, locus);
	    
  case grecs_type_int:
	  GETSNUM(string, int, *(int*)target, locus);
	  break;
	    
  case grecs_type_uint:
	  GETUNUM(string, unsigned int, *(unsigned int*)target, locus);
	  break;
      
  case grecs_type_long:
	  GETSNUM(string, long, *(long*)target, locus);
	  break;
	    
  case grecs_type_ulong:
	  GETUNUM(string, unsigned long, *(unsigned long*)target, locus);
	  break;
	    
  case grecs_type_size:
	  GETUNUM(string, size_t, *(size_t*)target, locus);
	  break;
	  /*FIXME	    
  case grecs_type_off:
          GETSNUM(string, off_t, *(off_t*)target, locus);
	  break;
	  */    
  case grecs_type_time:
	  /*FIXME: Use getdate */
	  GETUNUM(string, time_t, *(time_t*)target, locus);
	  break;

  case grecs_type_ipv4:
	  if (inet_aton(string, (struct in_addr *)target)) {
		  grecs_error(locus, 0, _("%s: not a valid IP address"),
			       string);
		  return 1;
	  }
	  break;
	  
  case grecs_type_host:
	  if (string_to_host((struct in_addr *)target, string, locus)) {
		  grecs_error(locus, 0,
			       _("%s: not a valid IP address or hostname"),
			       string);
		  return 1;
	  }
	  break;    
	    
  case grecs_type_sockaddr:
#if GRECS_SOCKADDR_LIST
	  return grecs_str_to_sockaddr((struct grecs_sockaddr **)target,
				       string, grecs_sockaddr_hints,
				       locus);
#else
	  return string_to_sockaddr((struct grecs_sockaddr *)target, string,
				    locus);
#endif

  case grecs_type_cidr:
	  return grecs_str_to_cidr((struct grecs_cidr *)target, string, locus);
	    
  case grecs_type_section:
	  grecs_error(locus, 0,
		       _("invalid use of block statement"));
	  return 1;
  }
  return 0;
}

struct grecs_prop
{
	size_t size;
	int (*cmp)(const void *, const void *);
};

static int
string_cmp(const void *elt1, const void *elt2)
{
	return strcmp((const char *)elt1,(const char *)elt2);
}

#define __grecs_name_cat__(a,b) a ## b
#define NUMCMP(type) __grecs_name_cat__(type,_cmp)
#define __DECL_NUMCMP(type,ctype)			\
  static int						\
  NUMCMP(type)(const void *elt1, const void *elt2)	\
  {							\
    return memcmp(elt1, elt2, sizeof(ctype));		\
  }
#define DECL_NUMCMP(type) __DECL_NUMCMP(type,type)

DECL_NUMCMP(short)
DECL_NUMCMP(int)
DECL_NUMCMP(long)
DECL_NUMCMP(size_t)
/*FIXME DECL_NUMCMP(off_t)*/
DECL_NUMCMP(time_t)
__DECL_NUMCMP(in_addr, struct in_addr)
__DECL_NUMCMP(grecs_sockaddr, struct grecs_sockaddr)

static int
cidr_cmp(const void *elt1, const void *elt2)
{
	struct grecs_cidr const *cp1 = elt1, *cp2 = elt2;
	return !(cp1->family == cp2->family
		 && cp1->len == cp2->len
		 && memcmp(cp1->address, cp2->address, cp1->len) == 0
		 && memcmp(cp1->netmask, cp2->netmask, cp1->len) == 0);
}

struct grecs_prop grecs_prop_tab[] = {
	{ 0, NULL },                                 /* grecs_type_void */
	{ sizeof(char*), string_cmp },               /* grecs_type_string */
	{ sizeof(short), NUMCMP(short) },            /* grecs_type_short */
	{ sizeof(unsigned short), NUMCMP(short) },   /* grecs_type_ushort */
	{ sizeof(int), NUMCMP(int) },                /* grecs_type_int */
	{ sizeof(unsigned int), NUMCMP(int) },       /* grecs_type_uint */
	{ sizeof(long), NUMCMP(long) },              /* grecs_type_long */
	{ sizeof(unsigned long), NUMCMP(long) },     /* grecs_type_ulong */
	{ sizeof(size_t), NUMCMP(size_t) },          /* grecs_type_size */
#if 0
	FIXME
	{ sizeof(off_t), NUMCMP(off_t) },            /* grecs_type_off */
#endif
	{ sizeof(time_t), NUMCMP(time_t) },          /* grecs_type_time */
	{ sizeof(int), NUMCMP(int) },                /* grecs_type_bool */
	{ sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_ipv4 */
	{ sizeof(struct grecs_cidr), cidr_cmp },     /* grecs_type_cidr */
	{ sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_host */ 
	{ sizeof(struct grecs_sockaddr), NUMCMP(grecs_sockaddr) },
                                               	      /* grecs_type_sockaddr */
	{ 0, NULL },                                  /* grecs_type_section */
	{ 0, NULL }                                   /* grecs_type_null */
};
#define grecs_prop_count \
 (sizeof(grecs_prop_tab) / sizeof(grecs_prop_tab[0]))

void
grecs_process_ident(struct grecs_keyword *kwp, grecs_value_t *value,
		     void *base, grecs_locus_t *locus)
{
	void *target;

	if (!kwp)
		return;
	
	target = target_ptr(kwp, (char *) base);
	
	if (kwp->callback) {
#if GRECS_TREE_API
		struct grecs_node node = { 0 };
		node.locus = *locus;
		node.v.value = value;
		node.ident = (char*) kwp->ident;
		kwp->callback(grecs_callback_set_value,
			      &node,
			      target,
			      &kwp->callback_data);
#else
		kwp->callback(grecs_callback_set_value,
			      locus,
			      target,
			      value,
			      &kwp->callback_data);
#endif
	} else if (kwp->type == grecs_type_void || target == NULL)
		return;
	else if (!value) {
		grecs_error(locus, 0, "%s has no value", kwp->ident);
		return;
	} else if (value->type == GRECS_TYPE_ARRAY) {
		grecs_error(locus, 0,
			     _("too many arguments to `%s'; missing semicolon?"),
			     kwp->ident);
		return;
	} else if (value->type == GRECS_TYPE_LIST) {
		if (kwp->flags & GRECS_LIST) {
			struct grecs_list_entry *ep;
			enum grecs_data_type type = kwp->type;
			int num = 1;
			struct grecs_list *list;
			size_t size;
			
			if (type >= grecs_prop_count
			    || (size = grecs_prop_tab[type].size) == 0) {
				grecs_error(locus, 0,
					     _("INTERNAL ERROR at %s:%d: "
					       "unhandled data type %d"),
					     __FILE__, __LINE__, type);
				abort();
			}

			list = _grecs_simple_list_create(
				     type == grecs_type_string);
			list->cmp = grecs_prop_tab[type].cmp;
				
			for (ep = value->v.list->head; ep; ep = ep->next) {
				const grecs_value_t *vp = ep->data;

				if (vp->type != GRECS_TYPE_STRING)
					grecs_error(&vp->locus, 0,
			     _("%s: incompatible data type in list item #%d"),
						     kwp->ident, num);
				else if (type == grecs_type_string)
					grecs_list_append(list,
							  grecs_strdup(vp->v.string));
				else {
					void *ptr = grecs_malloc(size);
					if (grecs_string_convert(ptr,
								 type,
								 vp->v.string,
								 &vp->locus)
					    == 0) 
						grecs_list_append(list, ptr);
					else
						grecs_free(ptr);
				}
			}
			*(struct grecs_list**)target = list;
		} else {
			grecs_error(locus, 0,
				     _("incompatible data type for `%s'"),
				     kwp->ident);
			return;
		}
	} else if (kwp->flags & GRECS_LIST) {
		struct grecs_list *list;
		enum grecs_data_type type = kwp->type;
		size_t size;
		void *ptr;
      
		if (type >= grecs_prop_count
		    || (size = grecs_prop_tab[type].size) == 0) {
			grecs_error(locus, 0,
				     _("INTERNAL ERROR at %s:%d: unhandled data type %d"),
				     __FILE__, __LINE__, type);
			abort();
		}
		
		list = _grecs_simple_list_create(1);
		list->cmp = grecs_prop_tab[type].cmp;
		if (type == grecs_type_string)
			grecs_list_append(list,
					  grecs_strdup(value->v.string));
		else {
			ptr = grecs_malloc(size);
			if (grecs_string_convert(ptr, type,
						 value->v.string,
						 &value->locus)) {
				grecs_free(ptr);
				grecs_list_free(list);
				return;
			}
			grecs_list_append(list, ptr);
		}
		*(struct grecs_list**)target = list;
	} else {
		if (kwp->type == grecs_type_string
		    && !(kwp->flags & GRECS_CONST))
			free(*(char**)target);
		grecs_string_convert(target, kwp->type,
				     value->v.string,
				     &value->locus);
	}
	kwp->flags &= ~GRECS_CONST;
}


struct nodeproc_closure {
	struct grecs_keyword *cursect;
	struct grecs_list *sections;
	int flags;
};
#define CURRENT_BASE(clos) \
	((char*)((clos)->cursect ? (clos)->cursect->callback_data : NULL))

static void
stmt_begin(struct nodeproc_closure *clos,
	   struct grecs_keyword *kwp, struct grecs_node *node)
{
	void *target;
	
	grecs_list_push(clos->sections, clos->cursect);
	if (kwp) {
		target = target_ptr(kwp, CURRENT_BASE(clos));
		clos->cursect = kwp;
		if (kwp->callback) {
#if GRECS_TREE_API			
			if (kwp->callback(grecs_callback_section_begin,
					  node,
					  target,
					  &kwp->callback_data))
#else
			if (kwp->callback(grecs_callback_section_begin,
					  &node->locus,
					  target,
					  node->v.value,
					  &kwp->callback_data))
#endif
				clos->cursect = &fake;
		} else
			kwp->callback_data = target;
	} else
		/* install an "ignore-all" section */
		clos->cursect = kwp;
}

static void
stmt_end(struct nodeproc_closure *clos, struct grecs_node *node)
{
	grecs_callback_fn callback = NULL;
	void *dataptr = NULL;
	struct grecs_keyword *kwp = clos->cursect;
	
	if (clos->cursect && clos->cursect->callback) {
		callback = clos->cursect->callback;
		dataptr = &clos->cursect->callback_data;
	}

	clos->cursect = (struct grecs_keyword *)grecs_list_pop(clos->sections);
	if (!clos->cursect)
		abort();
	if (callback)
#if GRECS_TREE_API			
		callback(grecs_callback_section_end,
			 node,
			 kwp ? target_ptr(kwp, CURRENT_BASE(clos)) : NULL,
			 dataptr);
#else		
		callback(grecs_callback_section_end,
			 &node->locus,
			 kwp ? target_ptr(kwp, CURRENT_BASE(clos)) : NULL,
			 NULL,
			 dataptr);
#endif
	if (kwp)
		kwp->callback_data = NULL;
}

static enum grecs_tree_recurse_res
nodeproc(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
{
	struct nodeproc_closure *clos = data;
	struct grecs_keyword *kwp;
	
	switch (op) {
	case grecs_tree_recurse_set:
		kwp = find_keyword(clos->cursect, node);
		if (!kwp)
			return grecs_tree_recurse_skip;
		grecs_process_ident(kwp, node->v.value, CURRENT_BASE(clos),
				    &node->idloc);
		break;
		
	case grecs_tree_recurse_pre:
		kwp = find_keyword(clos->cursect, node);
		if (!kwp)
			return grecs_tree_recurse_skip;
		stmt_begin(clos, kwp, node);
		break;
		
	case grecs_tree_recurse_post:
		stmt_end(clos, node);
		break;
	}
	return grecs_tree_recurse_ok;
}

int
grecs_tree_process(struct grecs_node *node, struct grecs_keyword *kwd)
{
	int rc;
	struct nodeproc_closure clos;
	struct grecs_keyword config_keywords;

	memset(&config_keywords, 0, sizeof(config_keywords));
	config_keywords.kwd = kwd;
	clos.cursect = &config_keywords;
	clos.sections = grecs_list_create();
	if (node->type == grecs_node_root)
		node = node->down;
	rc = grecs_tree_recurse(node, nodeproc, &clos);
	grecs_list_free(clos.sections);
	return rc;
}


int
grecs_node_eq(struct grecs_node *a, struct grecs_node *b)
{
	if (a->type != b->type)
		return 1;
	if (a->type == grecs_node_root)
		return 0;
	if (strcmp(a->ident, b->ident))
		return 1;
	if (a->type == grecs_node_block &&
	    !grecs_value_eq(a->v.value, b->v.value))
		return 1;
	return 0;
}

static void
free_value_entry(void *ptr)
{
	struct grecs_value *v = ptr;
	grecs_value_free(v);
}

struct grecs_list *
grecs_value_list_create()
{
	struct grecs_list *list = grecs_list_create();
	list->free_entry = free_value_entry;
	return list;
}

static void
value_to_list(struct grecs_value *val)
{
	struct grecs_list *list;
	int i;
	
	if (val->type == GRECS_TYPE_LIST)
		return;
	list = grecs_value_list_create();
	switch (val->type) {
	case GRECS_TYPE_STRING:
		grecs_list_append(list, grecs_value_ptr_from_static(val));
		break;

	case GRECS_TYPE_ARRAY:
		for (i = 0; i < val->v.arg.c; i++)
			grecs_list_append(list, val->v.arg.v[i]);
	}
	val->type = GRECS_TYPE_LIST;
	val->v.list = list;
}

static void
value_to_array(struct grecs_value *val)
{
	if (val->type == GRECS_TYPE_ARRAY)
		return;
	else {
		struct grecs_value **vp;
		vp = grecs_calloc(1, sizeof(*vp));
		vp[0] = grecs_value_ptr_from_static(val);
		val->type = GRECS_TYPE_ARRAY;
		val->v.arg.c = 1;
		val->v.arg.v = vp;
	}
}

static void
array_add(struct grecs_value *vx, struct grecs_value *vy)
{
	size_t i;
	
	vx->v.arg.v = grecs_realloc(vx->v.arg.v,
				    (vx->v.arg.c + vy->v.arg.c) *
				       sizeof(vx->v.arg.v[0]));
	for (i = 0; i < vy->v.arg.c; i++)
		vx->v.arg.v[i + vy->v.arg.c] = vy->v.arg.v[i];
	grecs_free(vy->v.arg.v);
	vy->v.arg.v = NULL;
	vy->v.arg.c = 0;
}

static void
node_aggregate_stmt(struct grecs_node *dst, struct grecs_node *src, int islist)
{
	if (islist) {
		struct grecs_list *t;
		/* Coerce both arguments to lists */
		value_to_list(dst->v.value);
		value_to_list(src->v.value);
		/* Aggregate two lists in order */
		grecs_list_add(src->v.value->v.list, dst->v.value->v.list);
		/* Swap them */
		t = dst->v.value->v.list;
		dst->v.value->v.list = src->v.value->v.list;
		src->v.value->v.list = t;
	} else {
		value_to_array(dst->v.value);
		value_to_array(src->v.value);
		array_add(dst->v.value, src->v.value);
	}
}

static void
node_merge_stmt(struct grecs_node *to_node, struct grecs_node *from_node,
		struct grecs_keyword *kwp, int flags)
{
	if (kwp &&
	    (flags & GRECS_AGGR) ^ (kwp->flags & GRECS_AGGR) &&
	    ((kwp->flags & GRECS_LIST) || kwp->callback))
		node_aggregate_stmt(to_node, from_node,
				    kwp->flags & GRECS_LIST);
	else {
		grecs_value_free(from_node->v.value);
		from_node->v.value = NULL;
	}
}

static void
node_merge_block(struct grecs_node *to_node, struct grecs_node *from_node,
		 struct grecs_keyword *kwp)
{
	struct grecs_node *sp;

	if (!from_node->down)
		return;
	for (sp = from_node->down; ; sp = sp->next) {
		sp->up = to_node;
		if (!sp->next)
			break;
	}

	sp->next = to_node->down;
	to_node->down->prev = sp;
	
	to_node->down = from_node->down;
	from_node->down = NULL;
}

static int
node_reduce(struct grecs_node *node, struct grecs_keyword *kwp, int flags)
{
	struct grecs_node *p;

	for (p = node->next; p; p = p->next)
		if (grecs_node_eq(p, node) == 0)
			break;
	if (p) {
		switch (node->type) {
		case grecs_node_root:
			return 0;
		case grecs_node_stmt:
			node_merge_stmt(p, node, kwp, flags);
			break;
		case grecs_node_block:
			node_merge_block(p, node, kwp);
			break;
		}
		grecs_node_unlink(node);
		grecs_node_free(node);
		return 1;
	}
	return 0;
}

static enum grecs_tree_recurse_res
reduceproc(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
{
	struct nodeproc_closure *clos = data;
	
	if (op == grecs_tree_recurse_post) {
		if (clos->sections)
			clos->cursect = (struct grecs_keyword *)
				           grecs_list_pop(clos->sections);
	} else {
		struct grecs_keyword *kwp = NULL;
		if (clos->cursect) {
			kwp = find_keyword(clos->cursect, node);
			if (!kwp)
				return grecs_tree_recurse_skip;
			if (kwp->flags & GRECS_INAC)
				return grecs_tree_recurse_skip;
			if (!(kwp->flags & GRECS_MULT) &&
			    node_reduce(node, kwp, clos->flags))
				return grecs_tree_recurse_skip;
			if (op == grecs_tree_recurse_pre) {
				grecs_list_push(clos->sections, clos->cursect);
				clos->cursect = kwp;
			} 
		} else if (node_reduce(node, kwp, clos->flags))
			return grecs_tree_recurse_skip;
	}
	return grecs_tree_recurse_ok;
}
		
int
grecs_tree_reduce(struct grecs_node *node, struct grecs_keyword *kwd,
		  int flags)
{
	int rc;
	struct nodeproc_closure clos;
	struct grecs_keyword config_keywords;

	memset(&config_keywords, 0, sizeof(config_keywords));
	config_keywords.kwd = kwd;
	if (kwd) {
		clos.cursect = &config_keywords;
		clos.sections = grecs_list_create();
	} else {
		clos.cursect = NULL;
		clos.sections = NULL;
	}
	clos.flags = flags;
	rc = grecs_tree_recurse(node->down, reduceproc, &clos);
	grecs_list_free(clos.sections);
	return rc;
}


struct grecs_node *
grecs_tree_first_node(struct grecs_node *tree)
{
	if (tree->type == grecs_node_root)
		return tree->down;
	return tree;
}

struct grecs_node *
grecs_next_node(struct grecs_node *node)
{
	if (!node)
		return NULL;
	if (node->down)
		return node->down;
	while (!node->next) {
		node = node->up;
		if (!node || node->type == grecs_node_root)
			return NULL;
	}
	return node->next;
}
