/******************************************************************************
 *		           FREXX PROGRAMMING LANGUAGE    		      *
 ******************************************************************************

 sprintf.c

 Sprintf() function source code.

 *****************************************************************************/

/************************************************************************
 *                                                                      *
 * fpl.library - A shared library interpreting script langauge.         *
 * Copyright (C) 1992-1994 FrexxWare                                    *
 * Author: Daniel Stenberg                                              *
 *                                                                      *
 * This program is free software; you may redistribute for non          *
 * commercial purposes only. Commercial programs must have a written    *
 * permission from the author to use FPL. FPL is *NOT* public domain!   *
 * Any provided source code is only for reference and for assurance     *
 * that users should be able to compile FPL on any operating system     *
 * he/she wants to use it in!                                           *
 *                                                                      *
 * You may not change, resource, patch files or in any way reverse      *
 * engineer anything in the FPL package.                                *
 *                                                                      *
 * 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.                 *
 *                                                                      *
 * Daniel Stenberg                                                      *
 * Ankdammsgatan 36, 4tr                                                *
 * S-171 43 Solna                                                       *
 * Sweden                                                               *
 *                                                                      *
 * FidoNet 2:201/328    email:dast@sth.frontec.se                       *
 *                                                                      *
 ************************************************************************/

#ifdef AMIGA
#include <exec/types.h>
#include <dos.h>

#elif defined(UNIX)
#include <sys/types.h>
#include <stdio.h>
#endif

#include "script.h"
#include "debug.h"

/* Lower-case digits.  */
const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";

/* Upper-case digits.  */
const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

#define	OUTCHAR(x) do { CALL(AddCharBuffer(scr, string, (x))); done++; } while(0)

#define BUFFSIZE 40 /* 40 bytes buffer for ltostr() calcs */

#define ADD_STRING_LIMIT 10 /* strings longer than this get appended right
                               away instead of added char by char */

#define ADDBUFFER_SIZE 32 /* buffered size; before appending string */

#define ADD_RESET -1 /* reset buffer */
#define ADD_FLUSH -2 /* flush buffer */

ReturnCode REGARGS
AddCharBuffer(struct Data *scr,
	      struct fplStr **string,
	      int add)
{
    static char buffer[ADDBUFFER_SIZE];
    static long len=0;
    ReturnCode ret;
    switch(add) {
      default:
	if(len<ADDBUFFER_SIZE) {
            buffer[len++] = add;
            break;
	}
      case ADD_FLUSH:
	if(len) {
	    CALL(AppendStringToString(scr, string, buffer, len));
	}
      case ADD_RESET:
	buffer[0]=(add>=0?add:'\0');
        len=(add>=0?1:0);
	break;
    }
    return FPL_OK;
}

ReturnCode REGARGS
Sprintf(struct Data *scr,
	struct fplStr **string,
	char *format,
	void **param_array,
	char *param_types)
{
  /* Base-36 digits for numbers.  */
  const char *digits = lower_digits;

  /* Pointer into the format string.  */
  register char *f;

  /* Number of characters written.  */
  register size_t done = 0;

  long param_num=1; /* start with the first parameter */
  ReturnCode ret;

  long param; /* current parameter to read */

  void *value; /* general purpose value holder */

  CALL(AddCharBuffer(scr, string, ADD_RESET));

  f = format;
  while (*f != '\0') {
      /* Type modifiers.  */
      char is_short, is_long, is_long_double;

      /* Format spec modifiers.  */
      char space, showsign, left, alt;

      /* Padding character: ' ' or '0'.  */
      char pad;
      /* Width of a field.  */
      register long width;
      /* Precision of a field.  */
      long prec;

      /* Decimal integer is negative.  */
      char is_neg;

      /* Current character of the format.  */
      char fc;

      /* Base of a number to be written.  */
      long base;
      /* Integral values to be written.  */
      unsigned long num;
      long signed_num;

      param = 0;

      if (*f != '%') {
	  /* This isn't a format spec, so write
	     everything out until the next one.  */
	  char *next = strchr(f + 1, '%');
	  if (next == NULL)
	    next = strchr(f + 1, '\0');
	  if (next - f > ADD_STRING_LIMIT) {
	      /*
	       * bonka pa hela klabbet pa en gang!
	       */
              CALL(AddCharBuffer(scr, string, ADD_FLUSH));
	      CALL(AppendStringToString(scr, string, f, next - f));
	      done += next - f;
	      f = next;
	  }
	  else
	    while (f < next)
	      OUTCHAR(*f++);
	  continue;
      }

      ++f;

      /* Check for "%%".  Note that although the ANSI standard lists
	 '%' as a conversion specifier, it says "The complete format
	 specification shall be `%%'," so we can avoid all the width
	 and precision processing.  */
      if (*f == '%') {
	  ++f;
	  OUTCHAR('%');
	  continue;
      }

      /* Check for spec modifiers.  */
      space = showsign = left = alt = 0;
      pad = ' ';
      while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0') {
	switch (*f++) {
	  case ' ':
	    /* Output a space in place of a sign, when there is no sign.  */
	    space = 1;
	    break;
	  case '+':
	    /* Always output + or - for numbers.  */
	    showsign = 1;
	    break;
	  case '-':
	    /* Left-justify things.  */
	    left = 1;
	    break;
	  case '#':
	    /* Use the "alternate form":
	       Hex has 0x or 0X, FP always has a decimal point.  */
	    alt = 1;
	    break;
	  case '0':
	    /* Pad with 0s.  */
	    pad = '0';
	    break;
	}
      }
      if (left)
	pad = ' ';

      /* Get the field width.  */
      width = 0;
      if (*f == '*') {
	  /* The field width is given in an argument.
	     A negative field width indicates left justification.  */
        if(isdigit(f[1]) && '$' == f[2]) {
          width = f[1] - '0';
          if(width<1)
            width=1;
          width = (long) param_array[ width ];
        }
        else
          width = (long) param_array[param_num++];
        if (width < 0) {
          width = - width;
          left = 1;
        }
        ++f;
      }
      else {
        if(isdigit(f[1]) && '$' == f[2]) {
          param = f[1]-'0';
          if(param<1)
            param=1; /* use no less than 1! */
        }
	while (isdigit(*f)) {
          width *= 10;
          width += *f++ - '0';
	}
      }
      /* Get the precision.  */
      /* -1 means none given; 0 means explicit 0.  */
      prec = -1;
      if (*f == '.') {
	  ++f;
	  if (*f == '*') {
            if(isdigit(f[1]) && '$' == f[2]) {
              prec = f[1] - '0';
              if(prec<1)
                prec=1;
              prec = (long) param_array[ prec ];
            }
            else
              /* The precision is given in an argument.  */
              prec = (long) param_array[param_num++];;
            /* Avoid idiocy.  */
            if (prec < 0)
              prec = -1;
            ++f;
          }
	  else if (isdigit(*f)) {
	      prec = 0;
	      while (*f != '\0' && isdigit(*f))
		{
		  prec *= 10;
		  prec += *f++ - '0';
		}
	    }
      }

      /* Check for type modifiers.  */
      is_short = is_long = is_long_double = 0;
      while (*f == 'h' || *f == 'l' || *f == 'L') {
	switch (*f++) {
        case 'h':
          /* int's are short int's.  */
#if 0
          /* ignored in FPL since there are no true shorts! */
          is_short = 1;
#endif
          break;
        case 'l':
          /* int's are long int's.  */
          is_long = 1;
          break;
        case 'L':
          /* double's are long double's, and int's are long long int's.  */
          is_long_double = 1;
          break;
        }
      }
      /* Format specification.  */
      fc = *f++;

      value = (void *)(param?param_array[param]:param_array[param_num++]);


      switch (fc) {
	  case 'i':
	  case 'd':
	    /* Decimal integer.  */
	    base = 10;
            signed_num = (long) value;

	    is_neg = signed_num < 0;
	    num = is_neg ? (- signed_num) : signed_num;
	    goto number;

	  case 'u':
	    /* Decimal unsigned integer.  */
	    base = 10;
	    goto unsigned_number;

          case 'b':
            /* Binary unsigned integer.  */
            base = 2;
            goto unsigned_number;

	  case 'o':
	    /* Octal unsigned integer.  */
	    base = 8;
	    goto unsigned_number;

	  case 'X':
	    /* Hexadecimal unsigned integer.  */
	  case 'x':
	    /* Hex with lower-case digits.  */

	    digits = fc == 'X' ? upper_digits : lower_digits;

	    base = 16;

	  unsigned_number:;
	    /* Unsigned number of base BASE.  */
            num = (unsigned long) value;

	    is_neg = 0;

	  number:;
	    /* Number of base BASE.  */
	    {
	      char work[BUFFSIZE];
	      char *workend = &work[sizeof(work) - 1];
	      register char *w;

	      /* Supply a default precision if none was given.  */
	      if (prec == -1)
		prec = 1;

	      /* Put the number in WORK.  */
	      w = workend;
	      while (num > 0) {
                *w-- = digits[num % base];
                num /= base;
              }
	      width -= workend - w;
	      prec -= workend - w;

	      if (alt && base == 8 && prec <= 0) {
                *w-- = '0';
                --width;
              }

	      if (prec > 0) {
                width -= prec;
                while (prec-- > 0)
                  *w-- = '0';
              }

	      if (alt && base == 16)
		width -= 2;

	      if (is_neg || showsign || space)
		--width;

	      if (!left && pad == ' ')
		while (width-- > 0)
		  OUTCHAR(' ');

	      if (is_neg)
		OUTCHAR('-');
	      else if (showsign)
		OUTCHAR('+');
	      else if (space)
		OUTCHAR(' ');

	      if (!left && pad == '0')
		while (width-- > 0)
		  OUTCHAR('0');

	      if (alt && base == 16) {
		  OUTCHAR('0');
		  OUTCHAR(fc);
              }

	      /* Write the number.  */
	      while (++w <= workend) {
		OUTCHAR(*w);
              }

	      if (left)
		while (width-- > 0)
		  OUTCHAR(' ');
	    }
	    break;

	  case 'c':
	    /* Character.  */
	    num = (long) value;
	    if (!left)
	      while (--width > 0)
		OUTCHAR(' ');
	    OUTCHAR((unsigned char) num);
	    if (left)
	      while (--width > 0)
		OUTCHAR(' ');
	    break;

	  case 's':
	    /* String.  */
	    {
	      static char null[] = "[ERR]";
              char *str;
	      size_t len;

	      str = (char *) value;
	      if ( str == NULL ||
                  (param?param_types[param]:param_types[param_num-1]) !=
                  FPL_STRARG )
		/* Write "(err)" if there's space.  */
		if (prec == -1 || prec >= (long) sizeof(null) - 1)
		  {
		    str = null;
		    len = sizeof(null) - 1;
		  }
		else
		  {
		    str = "";
		    len = 0;
		  }
	      else
		len = strlen(str);

	      if (prec != -1 && (size_t) prec < len)
		len = prec;
	      width -= len;

	      if (!left)
		while (width-- > 0)
		  OUTCHAR(' ');

	      if (len < ADD_STRING_LIMIT)
                  while (len-- > 0)
                      OUTCHAR(*str++);
	      else {
                  CALL(AddCharBuffer(scr, string, ADD_FLUSH));
                  CALL(AppendStringToString(scr, string, str, len));
                  done += len;
              }
	      if (left)
		while (width-- > 0)
		  OUTCHAR(' ');
	    }
	    break;

	  case 'p':
          case 'P': /* support this too, not ANSI though! */
	    /* Generic pointer.  */
	    {
              void *ptr;
	      ptr = (void *) value;
	      if (ptr != NULL) {
                /* If the pointer is not NULL, write it as a %#x spec.  */
                base = 16;
                digits = fc == 'P' ? upper_digits : lower_digits;
                fc = fc == 'P'?'X':'x';
                alt = 1;
                num = (unsigned long) ptr;
                is_neg = 0;
                goto number;
              }
	      else {
                /* Write "(nil)" for a nil pointer.  */
                static char nil[] = "(nil)";
                register char *p;

                width -= sizeof(nil) - 1;
                if (left)
                  while (width-- > 0)
                    OUTCHAR(' ');
                for (p = nil; *p != '\0'; ++p)
                  OUTCHAR(*p);
                if (!left)
                  while (width-- > 0)
                    OUTCHAR(' ');
              }
	    }
	    break;

	  case 'n':
	    /* Answer the count of characters written.  */
            {
              struct Identifier *ident;
              if((param?param_types[param]:param_types[param_num]) ==
                 FPL_INTVARARG) {
                ident = (struct Identifier *) value;
                ident->data.variable.var.val32[0] = done;
              }
            }
	    break;
	}

  }

  CALL(AddCharBuffer(scr, string, ADD_FLUSH)); /* flush temp buffer */

  return FPL_OK;
}

