/*  >  DPRINTF.C
 *  dprintf -- Source Code
 *  (C)  August 30  1989  Arkin Asaf
 *  All rights reserved
 *  References:
 *  C: A Reference Manual/Chapter 17, pp 328-340
 *  The Waite Group's Guide to ANSI C/Chapter 7, pp 84-87 */

/*  Include files: */

#include  <ctype.h>
#include  <setjmp.h>
#include  <stdarg.h>
#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>

/*  Macro constants:- TRUE/FALSE, Flags mask bits, and
 *  argument size types.
 *  Macros:- Maximum and Minimum expand to yield the maximum
 *  or minimum of two expressions; ToValue gives the decimal
 *  value of an ASCII digit and ToDigit returns a digit from
 *  a value in any radix. N.B.: It goes without saying that
 *  the macro parameters must contain no operations of con-
 *  sequence, for they will be carried out more than once. */

#define  TRUE  1
#define  FALSE 0

#define  MaskJustify  0x01  /* -  Left justify value within
                             *    field                     */
#define  MaskPlusSign 0x02  /* +  Precede positive value
                             *    with plus                 */
#define  MaskSpace    0x04  /* sp Precede positive value
                             *    with space                */
#define  MaskZeros    0x08  /* 0  Justify value with zeros  */
#define  MaskVarient  0x10  /* #  Output value in varient
                             *    format                    */

#define  TypeNormal  1  /*  int/double           */
#define  TypeShort   2  /*  short (meaningless)  */
#define  TypeLong    3  /*  long int             */
#define  TypeDouble  4  /*  long double          */

#define  Maximum(a,b)  ((a)>(b)?(a):(b))
#define  Minimum(a,b)  ((a)<(b)?(a):(b))
#define  ToValue(a)    ((a)-'0')
#define  ToDigit(a)    ((a)<10?(a)+'0':(a)-10+'A')

/*  OutFunc (of type dprintf_fp) points to a putchar-like
 *  function, which performs all output. Called with a
 *  character int as parameter, the function returns EOF
 *  only if an output error occured.*/

typedef  int (*dprintf_fp)(int);
static dprintf_fp  OutFunc;

/*  Function declarations: */

int  dprintf(dprintf_fp, const char *, ...);
int  vdprintf(dprintf_fp, const char *, va_list);
static void  PrintDecimal(long, int, int, char, int *);
static void  PrintRadix(unsigned long, int, int, char, char,
                        int *);
static void  PrintFloat(long double, int, int, char, char,
                        int *);
static void  Print(char *, char *, int, int, char, int *);
static int  ToInteger(char **, unsigned long, int, int);
static void  dputc(int);

/*  dputc employs this longjmp buffer in the event of an
 *  output error. */

jmp_buf  dputc_Buf;

/*  int  dprintf(dprintf_fp, const char *, ...)
 *  dprintf accepts pointers to a putchar-like function and
 *  a format string. It then passes them to vdprintf, along
 *  with a pointer to the variable arguments list. */

int  dprintf(dprintf_fp Func, const char *Format, ...)
{
  int  Return;
  va_list  Args;

  va_start(Args,Format);
  Return=vdprintf(Func,Format,Args);
  va_end(Args);
  return  Return;
}

/*  int  vdprintf(dprintf_fp, const char *, va_list)
 *  vdprintf is an implementation of vprintf, as defined in
 *  the ANSI standard, with an additional
 *  pointer-to-function as its first parameter. On exit,
 *  vdprintf returns the number of characters successfully
 *  printed, EOF if an error occured. */

int  vdprintf(dprintf_fp Func, const char *Format,
              va_list Args)
{
  char  Flags,  Size,  *Ptr;
  int  Width,  Precis,  OutCnt = 0;
  long  Int;
  unsigned long  UnsgInt;
  long double  Float;
  char  *FlagsList = "-+ 0#",  *TypesList = "hlL";

  /*  The pointer-to-function assigns to static variable
   *  OutFunc, rather than being passed through three layers
   *  of functions. The longjmp buffer is then initialized,
   *  so dputc can return in case of an output error. */
  OutFunc=Func;
  if (setjmp(dputc_Buf))
    return  EOF;
  /*  The format string is scanned a character at a time:
   *  %'s are processed, all other characters are merely
   *  echoed to the output. */
  for (; *Format; ++Format)
  {
    if (*Format!='%')
    {
      dputc(*Format);
      ++OutCnt;
      continue;
    }
    /*  An output format can start with a combination of
     *  five flags: - + spc 0 #. Flags is set accordingly. */
    if (!*++Format)
      return  EOF;
    Flags=0;
    while ((Ptr=strchr(FlagsList,*Format))!=NULL)
    {
      Flags|=1<<(Ptr-FlagsList);
      ++Format;
    }
    /*  Read width (zero assumed, if absent) and precision
     *  (-1 assumed, if absent): width must not start with
     *  a zero; precision precedes with a period -- if no
     *  precision follows the period, zero is assumed; an
     *  int argument is consumed for the width or precision,
     *  if an * replaces the value of either. */
    Width=0;
    if (*Format=='*')
    {
      Width=va_arg(Args,int);
      ++Format;
    }
    else
    while (isdigit(*Format))
      Width=Width*10+ToValue(*Format++);
    if (*Format=='.')
    {
      Precis=0;
      if (*++Format=='*')
      {
        Precis=va_arg(Args,int);
        ++Format;
      }
      else
      while (isdigit(*Format))
        Precis=Precis*10+ToValue(*Format++);
    }
    else
      Precis=-1;
    /*  An argument is either int (default), short int ('h'
     *  -- meaningless), long int ('l'), double (default
     *  float), or long double ('L'). */
     if ((Ptr=strchr(TypesList,*Format))!=NULL)
     {
       Size=Ptr-TypesList+TypeShort;
       ++Format;
     }
     else
       Size=TypeNormal;
    /*  Consume one output format letter.
     *  Auxiliary functions process most formats, keeping
     *  vdprintf short, or else it may fail to compile. */
    switch (*Format)
    {
      case 'd':
      case 'i':
        if (Size==TypeLong)
          Int=va_arg(Args,long);
        else
          Int=va_arg(Args,int);
        PrintDecimal(Int,Width,Precis,Flags,&OutCnt);
        break;
      case 'u':
      case 'o':
      case 'x': case 'X':
        if (Size==TypeLong)
          UnsgInt=va_arg(Args,unsigned long);
        else
          UnsgInt=va_arg(Args,unsigned);
        PrintRadix(UnsgInt,Width,Precis,Flags,*Format,
                   &OutCnt);
        break;
      case 'c':
      {
        static char  Char[2]= {0,0};

        Char[0]=va_arg(Args,unsigned char);
        Print(Char,NULL,Width,-1,Flags,&OutCnt);
        break;
      }
      case 's':
        Ptr=va_arg(Args,char *);
        Print(Ptr,NULL,Width,Precis,Flags,&OutCnt);
        break;
      case 'f':
      case 'e': case 'E':
      case 'g': case 'G':
        if (Size==TypeDouble)
          Float=va_arg(Args,long double);
        else
          Float=va_arg(Args,double);
        PrintFloat(Float,Width,Precis,Flags,*Format,&OutCnt);
        break;
      case 'p':
        /*  The pointer-to-void argument is cast to long
         *  unsigned, assuming pointer representation to
         *  remain intact. */
        UnsgInt=(unsigned long) va_arg(Args,void *);
        PrintRadix(UnsgInt,Width,Precis,Flags,*Format,
                   &OutCnt);
        break;
      case 'n':
        *(va_arg(Args,int *))=OutCnt;
        break;
      case '%':
        Print("%",NULL,Width,-1,Flags,&OutCnt);
        break;
      default:
        return EOF;
    }
  }
  return  OutCnt;
}

/*  void  PrintDecimal(long, int, int, char, int *)
 *  Print a decimal value (%d or %i) with a sign prefix. */

static void  PrintDecimal(long Int, int Width, int Precis,
                          char Flags, int *OutCnt)
{
  char  *Prefix;
  char  *Buffer;

  if(Int<0)
  {
    Int=-Int;
    Prefix="-";
  }
  else
  {
    if (Flags&MaskPlusSign)
      Prefix="+";
    else
    if (Flags&MaskSpace)
      Prefix=" ";
    else
      Prefix=NULL;
  }
  ToInteger(&Buffer,Int,10,Precis);
  Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
  free(Buffer);
}

/*  void  PrintRadix(unsigned long, int, int, char, char,
 *  int *) Print an unsigned int in decimal (%u), octal
 *  (%o), hexadecimal (%x or %X), or pointer (%p) form. In
 *  the varient format octals prefix with a 0, hexadecimals
 *  with a 0x, and pointers with an @. (Hexadecimal letters
 *  are in the same case as is the format letter.) */

static void  PrintRadix(unsigned long Int, int Width, int
                        Precis, char Flags, char Format,
                        int *OutCnt)
{
  char  *Prefix = NULL;
  char  *Buffer;
  int  Length;

  if (Format=='u')
  {
    ToInteger(&Buffer,Int,10,Precis);
    Print(Buffer,NULL,Width,-1,Flags,OutCnt);
  }
  else
  if (Format=='o')
  {
    if (Flags&MaskVarient)
       Prefix="0";
    ToInteger(&Buffer,Int,8,Precis);
    Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
  }
  else
  if (Format=='p')
  {
    /*  Various architectures impose different pointer rep-
     *  resentations, both in memory and in writing. As is,
     *  an 8-digit hexadecimal number prints (upper case
     *  letters), prefixed with an @ in the varient format. */
    if (Flags&MaskVarient)
      Prefix="@";
    ToInteger(&Buffer,Int,16,8);
    Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
  }
  else
  {
    if (Flags&MaskVarient)
      Prefix=(Format=='x')?"0x":"0X";
    Length=ToInteger(&Buffer,Int,16,Precis);
    if (Format=='x')
    {
      for (; Length>0; --Length)
        Buffer[Length-1]=tolower(Buffer[Length-1]);
    }
    Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
  }
  free(Buffer);
}

/*  void  PrintFloat(long double, int, int, char, char,
 *  int *) Print a floating point number in standard (%f) or
 *  engineering (%e) form; the %g format requires that the
 *  shortest of the two be selected. The number divides into
 *  integer, fraction and exponent parts; each is cast into
 *  a long int, stringized with ToInteger, and Printed. */

static void  PrintFloat(long double Float, int Width, int
                        Precis, char Flags, char Format,
                        int *OutCnt)
{
  char  *Prefix;
  char  *BufferI,  *BufferF = NULL,  *BufferE = NULL;
  int  LengthI,  LengthF,  LengthE = 0;
  int  Short = (Format=='g' || Format=='G');
  int  Exponent = 0;
  unsigned long  Int;

  /*  Determine prefix according to sign and format flags.
   *  If no precision was given, six is assumed. */
  if(Float<0)
  {
    Float=-Float;
    Prefix="-";
  }
    else
  {
    if (Flags&MaskPlusSign)
      Prefix="+";
    else
    if (Flags&MaskSpace)
      Prefix=" ";
    else
      Prefix=NULL;
  }
  if (Precis<0)
    Precis=6;
  if (Format=='e' || Format=='E' || Short)
  {
    long double  TempFloat = Float;

    /*  For %e and %g formats, establish the exponent:
     *  Float is divided and multiplied by ten, until it's
     *  value rests between zero and one. Exponent totals
     *  all divisions minus all multiplications. */
    if (Float!=0)
    {
      while (Float>10)
      {
        Float/=10;
        ++Exponent;
      }
      while (Float<1)
      {
        Float*=10;
        --Exponent;
      }
    }
    LengthE=ToInteger(&BufferE,Exponent,10,2)+2;
    /*  If the %f format is shorter, %g requires that
     *  the exponent be cancelled and that amount of
     *  precision be lost; It states that one precision
     *  digit be lost in any case. */
    if (Short)
    {
      if (Precis>0)
        --Precis;
      if (Exponent>=-3 && Exponent<=Precis)
      {
        LengthE=0;
        Precis-=Exponent;
        Float=TempFloat;
      }
    }
  }
  /*  The mantissa divides into integer and fraction parts,
   *  stringized by ToInteger: the last digit always rounds
   *  up; a period is printed only before a fraction or in
   *  the varient format; the %g format allows trailing zeros
   *  in the fraction to be lost. N.B.: Too long floating
   *  numbers may raise an exception on conversion to long
   *  int, or otherwise fail to convert properly. */
  Int=(unsigned long) Float;
  Float-=(long double) Int;
  if (Precis<=0 && Float>=.5)
    ++Int;
  LengthI=ToInteger(&BufferI,Int,10,-1);
  if (Precis>0)
  {
    for (LengthF=0; LengthF<Precis; ++LengthF)
      Float*=10;
    Int=(unsigned long) Float;
    Float-=(long double) Int;
    if (Float>=.5)
      ++Int;
    LengthF=ToInteger(&BufferF,Int,10,Precis);
  }
  else
    LengthF=0;
  if (Short && !(Flags&MaskVarient))
    while (LengthF>0 && BufferF[LengthF-1]=='0')
      --LengthF;

  if (Flags&MaskVarient || LengthF>0)
    --Width;
  Width-=LengthF+LengthE;
  Print(BufferI,Prefix,Width,-1,Flags,OutCnt);
  if (Flags&MaskVarient || LengthF>0)
  {
    dputc('.');
    ++*OutCnt;
  }
  if (LengthF>0)
    Print(BufferF,NULL,LengthF,LengthF,MaskZeros,OutCnt);
  /*  Print exponent part of number, with an 'e' in the same
   *  case as is the format letter. Exponents must have a
   *  sign and at least two digits.  */
  if (LengthE>0)
  {
    if (Format=='g')
      Format='e';
    else
    if (Format=='G')
      Format='E';
    dputc(Format);
    if (Exponent<0)
    {
      Exponent=-Exponent;
      Prefix="-";
    }
    else
      Prefix="+";
    Print(BufferE,Prefix,3,-1,MaskZeros,OutCnt);
  }
  free(BufferI);
  free(BufferF);
  free(BufferE);
}

/*  int  ToInteger(char **, unsigned long, int, int)
 *
 *  Convert an unsigned int to a NULL-terminated string of
 *  digits in the given radix. If the string has less digits
 *  than the precision, additional zeros are inserted at the
 *  start of it. ToInteger allocates a memory block in which
 *  it stores the string -- Buffer returns its address. */

static int  ToInteger(char **Buffer, unsigned long Int,
                      int Radix, int Precis)
{
  int  Cnt,  Length;
  unsigned long  TempInt = Int;

  if (Precis<0)
    Precis=1;
  for (Cnt=0; TempInt!=0; TempInt/=Radix, ++Cnt)  ;
  *Buffer=malloc(Maximum(Precis,Cnt)+1);
  if (*Buffer==NULL)
    return  0;
  for (Length=0; Length+Cnt<Precis; )
    (*Buffer)[Length++]='0';
  Cnt= Length=Maximum(Length+Cnt,Precis);
  (*Buffer)[Length]='\0';
  for (; Int>0; Int/=Radix)
    (*Buffer)[--Cnt]=ToDigit(Int%Radix);
  return  Length;
}


/*  void  Print(char *, char *, int, int, char, int *)
 *  Print prefix followed by value, incrementing OutCnt by
 *  the total number of characters printed: by default, the
 *  string is right justified within the field with spaces;
 *  the 0 flag places zeros between prefix and value; and
 *  the - flag left justifies by appending spaces at the
 *  end. Note that if Maximum is not -1, no more than that
 *  number of characters are printed.  */

static void  Print(char *String, char *Prefix, int Width,
                   int Maximum, char Flags, int *OutCnt)
{
  int  Length = strlen(String);

  if (Prefix)
    Length+=strlen(Prefix);
  if (Maximum>=0 && Length>Maximum)
    Length=Maximum;
  *OutCnt+=Maximum(Length,Width);

  if (!(Flags&MaskJustify || Flags&MaskZeros))
    for (; Length<Width; --Width)
      dputc(' ');
  while (Prefix && *Prefix!='\0')
  {
    dputc(*Prefix++);
    --Length;
    --Width;
  }
  if (!(Flags&MaskJustify))
    for (; Length<Width; --Width)
      dputc('0');
  while (*String!='\0' && Length>0)
  {
    dputc(*String++);
    --Length;
    --Width;
  }
  for (; Width>0; --Width)
    dputc(' ');
}

/*  void  dputc(int)
 *  Perform character output through the putchar-like
 *  function provided. If it returns EOF, a longjmp to
 *  vdprintf will make it return EOF to its caller. */

static void  dputc(int Char)
{
  if (OutFunc(Char)==EOF)
    longjmp(dputc_Buf,EOF);
}
