/*
 * utils.c  -  Various utility routines for MGL compiler
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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
 *  any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: utils.c,v 1.13 2007/01/06 18:31:29 gkminix Exp $
 */

#include "mknbi.h"
#include "mgl.h"



/*
 *****************************************************************************
 *
 * Routines to handle temporary variables
 *
 *****************************************************************************
 */

/*
 * Stack of temporary variables
 */
struct tmpdef {
	addr_t         varsize;			/* Size of element */
	struct expr   *varexpr;			/* Symbol for element */
	struct tmpdef *next;
};

static unsigned long tmpnum = 0;		/* Current number of temp var */
static struct tmpdef *tmpstack = NULL;		/* Stack of temp vars */
static addr_t tmpsize = 0;			/* Total size of stack */

#define MAX_TMPNUM	65535L			/* Total max no of temp vars */



/*
 * Create a temporary variable of a certain type
 */
struct expr *createtmpvar __F((tp, funcsym),
				struct typesdef *tp AND
				struct sym *funcsym)
{
  struct expr *ep;
  struct sym *sp;
  struct tmpdef *dp;
  struct varinfo *vp;
  char namebuf[8];
  addr_t varsize, varaddr;

  /* Compute the size of the new variable, rounded up to a word boundary */
  varsize = tp->size;
  if (tp->type == EXPR_STRING)
	varsize++;
  varsize = (varsize + 1) & 0xfffe;

  /*
   * Create a temporary variable name. We use a dollar sign at the beginning
   * of the name because that sign can never appear in any user symbol name.
   */
  if (tmpnum >= MAX_TMPNUM) {
	prnerr("overflow in temporary variable name space");
	nbexit(EXIT_MGL_OVERFLOW);
  }
  sprintf(namebuf, "$L%05lu", tmpnum++);

  /*
   * Scan through the list of stack elements to find a free one which is
   * large enough to hold our temporary variable.
   */
  varaddr = 0;
  for (dp = tmpstack; dp != NULL; dp = dp->next) {
	if (dp->varexpr == NULL && dp->varsize >= varsize)
		break;
	varaddr += dp->varsize;
  }
  if (dp != NULL) {
	/*
	 * We found a free block in the temporary stack. If the block is
	 * significantly larger than the size of the new variable, we can
	 * split it into two blocks, of which one with the remaining space
	 * is kept free.
	 */
	if ((dp->varsize - varsize) >= 2) {
		struct tmpdef *dp1;

		dp1 = (struct tmpdef *)nbmalloc(sizeof(struct tmpdef));
		dp1->varexpr = NULL;
		dp1->varsize = dp->varsize - varsize;
		dp1->next = dp->next;
		dp->varsize = varsize;
		dp->next = dp1;
	}
  } else {
	/*
	 * There hasn't been a free block, so lets create a new one, advancing
	 * the top of the stack and it's size accordingly.
	 */
	dp = (struct tmpdef *)nbmalloc(sizeof(struct tmpdef));
	dp->varexpr = NULL;
	dp->varsize = varsize;
	dp->next = tmpstack;
	tmpstack = dp;
	if ((varaddr + varsize) > tmpsize)
		tmpsize = varaddr + varsize;
  }

  /*
   * Now create a new symbol for the temporary variable
   */
  sp = addsym(namebuf, SYMBOL_NOREF);
  sp->type = varsym;
  sp->loc.addr = -(funcsym->def.f.varsize + varaddr + varsize);
  sp->def.v.t = tp;
  sp->def.v.class = CLASS_LOCAL;

  /*
   * Create a new variable expression
   */
  vp = (struct varinfo *)nbmalloc(sizeof(struct varinfo));
  vp->cmd = basevar;
  vp->vartype = tp;
  vp->spec.basesym = sp;
  vp->next = NULL;
  ep = (struct expr *)nbmalloc(sizeof(struct expr));
  ep->type = tp;
  ep->opcode = CMD_TMPVAR;
  ep->exprnum = 0;
  ep->spec.var = vp;
  dp->varexpr = ep;
  return(ep);
}



/*
 * Release a previously allocated temporary variable
 */
void releasetmpvar __F((ep), struct expr *ep)
{
  struct tmpdef *dp1, *dp2;

  /* Just for safety */
  if (ep == NULL)
	return;

  /* First find the symbol in the list of temporary variables */
  dp1 = NULL;
  dp2 = tmpstack;
  while (dp2 != NULL) {
	if (dp2->varexpr == ep)
		break;
	dp1 = dp2;
	dp2 = dp2->next;
  }
  if (dp2 == NULL)
	return;

  /*
   * If the element preceding or following the current one is free,
   * we can join both together. If the current one is at the end of
   * the list, we can simply remove it. Otherwise, we mark the element
   * as free. Note that we never free the corresponding symbol. It will
   * be deleted automatically when the procedure or function ends.
   */
  ep->opcode = CMD_NONE;
  dp2->varexpr = NULL;
  if (dp1 != NULL && dp1->varexpr == NULL) {
	dp1->varsize += dp2->varsize;
	dp1->next = dp2->next;
	free(dp2);
  } else if (dp2->next != NULL && dp2->next->varexpr == NULL) {
	dp1 = dp2->next;
	dp2->varsize += dp1->varsize;
	dp2->next = dp1->next;
	free(dp1);
  } else if (dp2->next == NULL) {
	if (dp1 == NULL)
		tmpstack = NULL;
	else
		dp1->next = NULL;
	free(dp2);
  }
}



/*
 * Free all temporary variables and return the total amount of stack space
 * required to hold all.
 */
addr_t freetmpvars __F_NOARGS
{
  struct tmpdef *dp1, *dp2;
  addr_t retval;

  /* Free the whole linked list */
  dp1 = tmpstack;
  while (dp1 != NULL) {
	dp2 = dp1;
	dp1 = dp1->next;
	free(dp2);
  }

  /* Reset the global variables for another round. Do NOT reset tmpnum! */
  retval = tmpsize;
  tmpsize = 0;
  tmpstack = NULL;
  return(retval);
}




/*
 *****************************************************************************
 *
 * Miscellaneous routines
 *
 *****************************************************************************
 */

/*
 * Convert a host string into an IP number
 */
byte_t *getinet __F((name, noerror), const char *name AND int noerror)
{
  byte_t *retaddr;
#ifdef HAVE_INET
  struct in_addr addr;
  struct hostent *hp;
#endif
  const char *cp, *dot;
  size_t i;

  /*
   * If the string contains a dotted-decimal IP address, we
   * can convert it regardless if the host system supports
   * IP networking.
   */
  retaddr = (byte_t *)nbmalloc(IPADDR_SIZE * sizeof(byte_t));
  if (name[0] >= '0' && name[0] <= '9') {
	for (cp = name, dot = name, i = 0; *cp; cp++)
		if (*cp == '.') {
			if ((int)(cp - dot) <= 1)
				break;
			dot = cp;
			i++;
			if (i >= IPADDR_SIZE)
				break;
		} else if (*cp >= '0' && *cp <= '9') {
			retaddr[i] = retaddr[i] * 10 + (byte_t)(*cp - '0');
			if (retaddr[i] > 255)
				break;
		} else
			break;
	if (i == (IPADDR_SIZE - 1) && *cp == '\0' && (int)(cp - dot) > 1)
		return(retaddr);
  }

  /*
   * Otherwise we use the host runtime library to resolve the name.
   */
#ifdef HAVE_INET
  addr.s_addr = INADDR_ANY;
  if ((hp = gethostbyname(name)) == NULL) {
	if (!noerror) {
		free(retaddr);
		return(NULL);
	}
	warning("invalid IP address, assuming 0.0.0.0");
  } else
	addr = *((struct in_addr *)(hp->h_addr));
  cp = (char *)(&addr);
  for (i = 0; i < IPADDR_SIZE; i++)
	retaddr[i] = (byte_t)(*cp++);
#else
  if (!noerror) {
	free(retaddr);
	return(NULL);
  }
  warning("invalid IP address, assuming 0.0.0.0");
  memzero(retaddr, IPADDR_SIZE * sizeof(byte_t));
#endif
  return(retaddr);
}



/*
 ********************************************************************
 *
 * Copy a constant string, creating a new string buffer
 */
byte_t *copy_string __F((mglstr), const byte_t *mglstr)
{
  byte_t *ret;

  ret = (byte_t *)nbmalloc((size_t)((mglstr[0] + 1) * sizeof(byte_t)));
  if (mglstr[0] != 0)
	memcpy(ret, mglstr, (size_t)((mglstr[0] + 1) * sizeof(byte_t)));
  else
	ret[0] = 0;
  return(ret);
}



/*
 ********************************************************************
 *
 * Make a host character string out of a MGL string
 */
char *get_string __F((mglstr), const byte_t *mglstr)
{
  size_t mgllen, slen, clen, i;
  char *ret, *cp;

  mgllen = (size_t)(mglstr[0]);
  for (i = 0, slen = 0; i < mgllen; i++)
	slen += charlen(chartohost((__u8)(mglstr[i + 1] & 0xff)));
  ret = (char *)nbmalloc(slen + 1);
  for (i = 0, cp = ret; i < mgllen; i++) {
	clen = savechar(chartohost((__u8)(mglstr[i + 1] & 0xff)), cp, slen);
	cp += clen;
	slen -= clen;
  }
  *cp = '\0';
  return(ret);
}



/*
 ********************************************************************
 *
 * Make a MGL string out of a host character string
 */
byte_t *make_string __F((str), const char *str)
{
  size_t slen, i;
  const char *cp;
  hchar_t c;
  byte_t *ret;

  /* Determine the number of characters in the host string */
  slen = 0;
  cp = str;
  while (*cp) {
	if ((c = charcollect(*cp)) != 0)
		slen += charlen(c);
	cp++;
  }
  if (slen > MAX_STR_LEN)
	slen = MAX_STR_LEN;

  /* Now copy the string while converting the characters */
  ret = (byte_t *)nbmalloc((slen + 1) * sizeof(byte_t));
  i = 1;
  cp = str;
  while (*cp && i <= slen) {
	if ((c = charcollect(*cp)) != 0)
		ret[i++] = (byte_t)chartotarget(c);
	cp++;
  }
  ret[0] = (byte_t)(i & 0xff);
  return(ret);
}



/*
 ********************************************************************
 *
 * Generate a new string type. Strings are special because different
 * string sizes are actually different types.
 */
struct typesdef *newstrtype __F((len), int len)
{
  struct typesdef *tp, *index;
  addr_t size;

  /* Check if size is correct */
  if (len == 0 || len > MAX_STR_LEN)
	len = MAX_STR_LEN;
  size = (addr_t)len * char_type.size + 1;

  /* Let's see if we have such a string type and index type already */
  index = NULL;
  for (tp = typetab; tp != NULL; tp = tp->next) {
	if (tp->type == EXPR_STRING && tp->size == size)
		return(tp);
	if (tp->type == EXPR_NUM && tp->def.s.min == 0 && tp->def.s.max == len)
		index = tp;
  }

  /* Create a new index type */
  if (index == NULL) {
	index = (struct typesdef *)nbmalloc(sizeof(struct typesdef));
	index->size = strindex_type.size;
	index->type = strindex_type.type;
	index->def.s.min = 0;
	index->def.s.max = len;
	index->def.s.boundaddr = -1;
	index->next = typetab;
	typetab = tp;
  }

  /* Now create a new string type */
  tp = (struct typesdef *)nbmalloc(sizeof(struct typesdef));
  tp->size = size;
  tp->type = EXPR_STRING;
  tp->def.a.indextype = index;
  tp->def.a.basetype = &char_type;
  tp->next = typetab;
  typetab = tp;
  return(tp);
}

