/* Copyright (c) 1992, 1999, 2001 John E. Davis
 * This file is part of the S-Lang library.
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Perl Artistic License.
 */
#include "slinclud.h"

#include "slang.h"
#include "_slang.h"

#define DEBUG_MALLOC 0

#if DEBUG_MALLOC
# define SLREALLOC_FUN	SLdebug_realloc
# define SLMALLOC_FUN	SLdebug_malloc
# define SLFREE_FUN	SLdebug_free
#else
# define SLREALLOC_FUN	SLREALLOC
# define SLMALLOC_FUN	SLMALLOC
# define SLFREE_FUN	SLFREE
#endif

/* Version information goes here since this file is always needed. */
int SLang_Version = SLANG_VERSION;
char *SLang_Version_String = SLANG_VERSION_STRING;

char *SLmake_string(char *str)
{
   return SLmake_nstring(str, strlen (str));
}

char *SLmake_nstring (char *str, unsigned int n)
{
   char *ptr;

   if (NULL == (ptr = SLmalloc(n + 1)))
     {
	return NULL;
     }
   SLMEMCPY (ptr, str, n);
   ptr[n] = 0;
   return(ptr);
}

void SLmake_lut (unsigned char *lut, unsigned char *range, unsigned char reverse)
{
   register unsigned char *l = lut, *lmax = lut + 256;
   int i, r1, r2;

   while (l < lmax) *l++ = reverse;
   reverse = !reverse;

   r1 = *range++;
   while (r1)
     {
	r2 = *range++;
	if ((r2 == '-') && (*range != 0))
	  {
	     r2 = *range++;
	     for (i = r1; i <= r2; i++) lut[i] = reverse;
	     r1 = *range++;
	     continue;
	  }
	lut[r1] = reverse;
	r1 = r2;
     }
}

char *SLmalloc (unsigned int len)
{
   char *p;

   p = (char *) SLMALLOC_FUN (len);
   if (p == NULL)
     SLang_Error = SL_MALLOC_ERROR;

   return p;
}

void SLfree (char *p)
{
   if (p != NULL) SLFREE_FUN (p);
}

char *SLrealloc (char *p, unsigned int len)
{
   if (len == 0)
     {
	SLfree (p);
	return NULL;
     }

   if (p == NULL) p = SLmalloc (len);
   else
     {
	p = (char *)SLREALLOC_FUN (p, len);
	if (p == NULL)
	  SLang_Error = SL_MALLOC_ERROR;
     }
   return p;
}

char *SLcalloc (unsigned int nelems, unsigned int len)
{
   char *p;

   len = nelems * len;
   p = SLmalloc (len);
   if (p != NULL) SLMEMSET (p, 0, len);
   return p;
}

/* p and ch may point to the same buffer */
char *_SLexpand_escaped_char(char *p, char *ch)
{
   int i = 0;
   int max = 0, num, base = 0;
   char ch1;

   ch1 = *p++;

   switch (ch1)
     {
      default: num = ch1; break;
      case 'n': num = '\n'; break;
      case 't': num = '\t'; break;
      case 'v': num = '\v'; break;
      case 'b': num = '\b'; break;
      case 'r': num = '\r'; break;
      case 'f': num = '\f'; break;
      case 'E': case 'e': num = 27; break;
      case 'a': num = 7;
	break;

	/* octal */
      case '0': case '1': case '2': case '3':
      case '4': case '5': case '6': case '7':
	max = '7';
	base = 8; i = 2; num = ch1 - '0';
	break;

      case 'd':			       /* decimal -- S-Lang extension */
	base = 10;
	i = 3;
	max = '9';
	num = 0;
	break;

      case 'x':			       /* hex */
	base = 16;
	max = '9';
	i = 2;
	num = 0;
	break;
     }

   while (i--)
     {
	ch1 = *p;

	if ((ch1 <= max) && (ch1 >= '0'))
	  {
	     num = base * num + (ch1 - '0');
	  }
	else if (base == 16)
	  {
	     ch1 |= 0x20;
	     if ((ch1 < 'a') || ((ch1 > 'f'))) break;
	     num = base * num + 10 + (ch1 - 'a');
	  }
	else break;
	p++;
     }

   *ch = (char) num;
   return p;
}

/* s and t could represent the same space */
void SLexpand_escaped_string (register char *s, register char *t,
			      register char *tmax)
{
   char ch;

   while (t < tmax)
     {
	ch = *t++;
	if (ch == '\\')
	  {
	     t = _SLexpand_escaped_char (t, &ch);
	  }
	*s++ = ch;
     }
   *s = 0;
}

int SLextract_list_element (char *list, unsigned int nth, char delim,
			    char *elem, unsigned int buflen)
{
   char *el, *elmax;
   char ch;

   while (nth > 0)
     {
	while ((0 != (ch = *list)) && (ch != delim))
	  list++;

	if (ch == 0) return -1;

	list++;
	nth--;
     }

   el = elem;
   elmax = el + (buflen - 1);

   while ((0 != (ch = *list)) && (ch != delim) && (el < elmax))
     *el++ = *list++;
   *el = 0;

   return 0;
}

#ifndef HAVE_VSNPRINTF
int _SLvsnprintf (char *buf, unsigned int buflen, char *fmt, va_list ap)
{
#if 1
   unsigned int len;

   /* On some systems vsprintf returns useless information.  So, punt */
   vsprintf (buf, fmt, ap);
   len = strlen (buf);
   if (len >= buflen)
     {
	SLang_exit_error ("\
Your system lacks the vsnprintf system call and vsprintf overflowed a buffer.\n\
The integrity of this program has been violated.\n");
	return EOF;		       /* NOT reached */
     }
   return (int)len;
#else
   int status;

   status = vsprintf (buf, fmt, ap);
   if (status >= (int) buflen)
     {
	/* If we are lucky, we will get this far.  The real solution is to
	 * provide a working version of vsnprintf
	 */
	SLang_exit_error ("\
Your system lacks the vsnprintf system call and vsprintf overflowed a buffer.\n\
The integrity of this program has been violated.\n");
	return EOF;		       /* NOT reached */
     }
   return status;
#endif
}
#endif

#ifndef HAVE_SNPRINTF
int _SLsnprintf (char *buf, unsigned int buflen, char *fmt, ...)
{
   int status;

   va_list ap;

   va_start (ap, fmt);
   status = _SLvsnprintf (buf, buflen, fmt, ap);
   va_end (ap);

   return status;
}
#endif

typedef struct _Cleanup_Function_Type
{
   struct _Cleanup_Function_Type *next;
   void (*f)(void);
}
Cleanup_Function_Type;

static Cleanup_Function_Type *Cleanup_Function_List;

static void cleanup_slang (void)
{
   while (Cleanup_Function_List != NULL)
     {
	Cleanup_Function_Type *next = Cleanup_Function_List->next;
	(*Cleanup_Function_List->f)();
	SLFREE_FUN ((char *) Cleanup_Function_List);
	Cleanup_Function_List = next;
     }
}

#ifndef HAVE_ATEXIT
# ifdef HAVE_ON_EXIT
static void on_exit_cleanup_slang (int arg_unused)
{
   (void) arg_unused;
   cleanup_slang ();
}
# endif
#endif

int SLang_add_cleanup_function (void (*f)(void))
{
   Cleanup_Function_Type *l;

   l = (Cleanup_Function_Type *) SLMALLOC_FUN (sizeof (Cleanup_Function_Type));
   if (l == NULL)
     return -1;

   l->f = f;
   l->next = Cleanup_Function_List;

   if (Cleanup_Function_List == NULL)
     {
#ifdef HAVE_ATEXIT
	(void) atexit (cleanup_slang);
#else
# ifdef HAVE_ON_EXIT
	(void) on_exit (on_exit_cleanup_slang, 0);
# endif
#endif
     }
   Cleanup_Function_List = l;
   return 0;
}