/* Portable vsprintf  by Robert A. Larson <blarson@skat.usc.edu> */

/* Copyright 1989 Robert A. Larson.
 * Distribution in any form is allowed as long as the author
 * retains credit, changes are noted by their author and the
 * copyright message remains intact.  This program comes as-is
 * with no warentee of fitness for any purpose.
 *
 * Thanks to Doug Gwyn, Chris Torek, and others who helped clarify
 * the ansi printf specs.
 *
 * Please send any bug fixes and improvements to blarson@skat.usc.edu .
 * The use of goto is NOT a bug.
 */

/* Feb	7, 1989		blarson		First usenet release */
/* Oct 20, 1990     Brad Appleton -- enclosed in #ifdef BSD for parseargs */
/* Oct 21, 1990     Brad Appleton -- added test in #ifdef VPRINTF_TEST */
/* Feb 25, 1990     Brad Appleton -- #included "useful.h" and added #ifdefs
 *                                   to compile for ANSI-C as well as K&R
 */


/* This code implements the vsprintf function, without relying on
 * the existance of _doprint or other system specific code.
 *
 * Define NOVOID if void * is not a supported type.
 *
 * Two compile options are available for efficency:
 *	INTSPRINTF	should be defined if sprintf is int and returns
 *			    the number of chacters formated.
 *	LONGINT		should be defined if sizeof(long) == sizeof(int)
 *
 *	They only make the code smaller and faster, they need not be
 *	defined.
 *
 * UNSIGNEDSPECIAL should be defined if unsigned is treated differently
 * than int in argument passing.  If this is definded, and LONGINT is not,
 * the compiler must support the type unsingned long.
 *
 * Most quirks and bugs of the available sprintf fuction are duplicated,
 * however * in the width and precision fields will work correctly
 * even if sprintf does not support this, as will the n format.
 *
 * Bad format strings, or those with very long width and precision
 * fields (including expanded * fields) will cause undesired results.
 */

   /* Parseargs only needs this stuff for BSD Unix  -- Brad Appleton */
#include <useful.h>

#ifdef BSD
#include <stdio.h>
#include <ctype.h>

#ifdef OSK		/* os9/68k can take advantage of both */
# define LONGINT
# define INTSPRINTF
#endif

/* This must be a typedef not a #define! */
#ifdef NOVOID
  typedef char *pointer;
#else
  typedef void *pointer;
#endif

#ifdef	INTSPRINTF
# define Sprintf(string,format,arg)	(sprintf((string),(format),(arg)))
#else
# define Sprintf(string,format,arg)	(\
   sprintf((string),(format),(arg)),\
   strlen(string)\
)
#endif

typedef int *intp;

#ifdef __ANSI_C__
  int vsprintf(char *dest, register const char *format, va_list args)
#else
  int vsprintf(dest, format, args)
  char *dest;
  register char *format;
  va_list args;
#endif
{
      register char *dp = dest;
      register char c;
      register char *tp;
      char tempfmt[64];
#ifndef LONGINT
      int longflag;
#endif

      tempfmt[0] = '%';
      while( (c = *format++) != 0) {
   if(c=='%') {
       tp = &tempfmt[1];
#ifndef LONGINT
       longflag = 0;
#endif
continue_format:
       switch(c = *format++) {
      case 's':
          *tp++ = c;
          *tp = '\0';
          dp += Sprintf(dp, tempfmt, VA_ARG(args, char *));
          break;
      case 'u':
      case 'x':
      case 'o':
      case 'X':
#ifdef UNSIGNEDSPECIAL
          *tp++ = c;
          *tp = '\0';
#ifndef LONGINT
          if(longflag)
         dp += Sprintf(dp, tempfmt, VA_ARG(args, unsigned long));
          else
#endif
         dp += Sprintf(dp, tempfmt, VA_ARG(args, unsigned));
          break;
#endif
      case 'd':
      case 'c':
      case 'i':
          *tp++ = c;
          *tp = '\0';
#ifndef LONGINT
          if(longflag)
         dp += Sprintf(dp, tempfmt, VA_ARG(args, long));
          else
#endif
         dp += Sprintf(dp, tempfmt, VA_ARG(args, int));
          break;
      case 'f':
      case 'e':
      case 'E':
      case 'g':
      case 'G':
          *tp++ = c;
          *tp = '\0';
          dp += Sprintf(dp, tempfmt, VA_ARG(args, double));
          break;
      case 'p':
          *tp++ = c;
          *tp = '\0';
          dp += Sprintf(dp, tempfmt, VA_ARG(args, pointer));
          break;
      case '-':
      case '+':
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '.':
      case ' ':
      case '#':
      case 'h':
          *tp++ = c;
          goto continue_format;
      case 'l':
#ifndef LONGINT
          longflag = 1;
          *tp++ = c;
#endif
          goto continue_format;
      case '*':
          tp += Sprintf(tp, "%d", VA_ARG(args, int));
          goto continue_format;
      case 'n':
          *VA_ARG(args, intp) = dp - dest;
          break;
      case '%':
      default:
          *dp++ = c;
          break;
       }
   } else *dp++ = c;
      }
      *dp = '\0';
      return dp - dest;
}


#ifdef __ANSI_C__
  int vfprintf(FILE *dest, register const char *format, va_list args)
#else
  int vfprintf(dest, format, args)
  FILE *dest;
  register char *format;
  va_list args;
#endif
{
      register char c;
      register char *tp;
      register int count = 0;
      char tempfmt[64];
#ifndef LONGINT
      int longflag;
#endif

      tempfmt[0] = '%';
      while(c = *format++) {
   if(c=='%') {
       tp = &tempfmt[1];
#ifndef LONGINT
       longflag = 0;
#endif
continue_format:
       switch(c = *format++) {
      case 's':
          *tp++ = c;
          *tp = '\0';
          count += fprintf(dest, tempfmt, VA_ARG(args, char *));
          break;
      case 'u':
      case 'x':
      case 'o':
      case 'X':
#ifdef UNSIGNEDSPECIAL
          *tp++ = c;
          *tp = '\0';
#ifndef LONGINT
          if(longflag)
         count += fprintf(dest, tempfmt, VA_ARG(args, unsigned long));
          else
#endif
         count += fprintf(dest, tempfmt, VA_ARG(args, unsigned));
          break;
#endif
      case 'd':
      case 'c':
      case 'i':
          *tp++ = c;
          *tp = '\0';
#ifndef LONGINT
          if(longflag)
         count += fprintf(dest, tempfmt, VA_ARG(args, long));
          else
#endif
         count += fprintf(dest, tempfmt, VA_ARG(args, int));
          break;
      case 'f':
      case 'e':
      case 'E':
      case 'g':
      case 'G':
          *tp++ = c;
          *tp = '\0';
          count += fprintf(dest, tempfmt, VA_ARG(args, double));
          break;
      case 'p':
          *tp++ = c;
          *tp = '\0';
          count += fprintf(dest, tempfmt, VA_ARG(args, pointer));
          break;
      case '-':
      case '+':
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '.':
      case ' ':
      case '#':
      case 'h':
          *tp++ = c;
          goto continue_format;
      case 'l':
#ifndef LONGINT
          longflag = 1;
          *tp++ = c;
#endif
          goto continue_format;
      case '*':
          tp += Sprintf(tp, "%d", VA_ARG(args, int));
          goto continue_format;
      case 'n':
          *VA_ARG(args, intp) = count;
          break;
      case '%':
      default:
          putc(c, dest);
          count++;
          break;
       }
   } else {
       putc(c, dest);
       count++;
   }
      }
      return count;
}

#ifdef __ANSI_C__
  int vprintf(const char *format, va_list args)
#else
  int vprintf(format, args)
  char *format;
  va_list args;
#endif
{
      return vfprintf(stdout, format, args);
}
#endif  /* BSD Unix ONLY */

	/* use a VERY SIMPLE test case to test this out -- Brad Appleton */
#ifdef VPRINTF_TEST

/*VARARGS1*/
#ifdef __ANSI_C__
  int vtest( char *fmt, ... )
#else
  int vtest( fmt, va_alist )
  char *fmt;
  va_dcl
#endif
{
   va_list   ap;
   int   rc;

   VA_START(ap, fmt);
   rc = vprintf( fmt, ap );
   VA_END(ap);

   return   rc;
}

void main()
{
   printf( "its a %s %% day in the %d neighborhood for %*s\n",
          "pitiful", 4, 8, "fun" );

   vtest( "its a %s %% day in the %d neighborhood for %*s\n",
          "pitiful", 4, 8, "fun" );
}

#endif  /* VPRINTF_TEST */
