/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = UTLPRINT.C
 *
 * DESCRIPTIVE NAME = Contains print formatting routines.
 *
 *
 * VERSION = V2.0
 *
 * DATE        : 01/25/88
 *
 * DESCRIPTION : This module contains the core code for implementing
 *               various printf-style output formatting routines.
 *
 * FUNCTIONS - PrintChar
 *           - ScanDigits
 *           - ScanPercentFormat
 *           - ScanToken
 *           - PrintDecimal
 *           - PrintFraction
 *           - PrintHex
 *           - PrintBool
 *           - kprintf
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/

#include "inc\prdinclt.h"
#include "inc\utl.h"
#include "inc\pspagtun.h"             /* V2.174057  Page Tuning */

extern short int _System FractionToDecimal(PB,short int);

extern unsigned long _System LongShiftRight(unsigned long,short);

/*
**  These are tokens for the different format specifier types
*/
#define  TK_END        0     /* End of format string                        */
#define  TK_TEXT       1     /* Plain text                                  */
#define  TK_STR        2     /* Near string                                 */
#define  TK_PTR        3     /* Pointer                                     */
#define  TK_LPTR       4     /* Far ptr                                     */
#define  TK_DEC        5     /* Decimal number                              */
#define  TK_HEX        6     /* Hex number                                  */
#define  TK_LHEX       7     /* Long hex number                             */
#define  TK_CHAR       8     /* Character                                   */
#define  TK_BOOL       9     /* Boolean                                     */
#define  TK_FRAC       10    /* Fractional number                           */
#define  TK_NOP        11    /* This token indicates no operation           */

/*
**  macro to append a character to the output buffer.
*/
#define  CharToBuf(bChar)  if (pcs->cbOut > 0)  {                          \
                       *pcs->lpbOut++ = bChar;                            \
                       --pcs->cbOut;                                      \
                       }

/*
** The current state structure.
*/
typedef struct _tagCS
{
  char *lpbOut;              /* ptr to the buffer                           */
  int cbOut;                 /* The length of the output buffer             */
  int iToken;                /* The token produced by ScanToken             */
  /*
  **  The string value scanned from the format string
  */
  char szScanText[80];

  /*
  **  The digit count specifier in the format string
  */
  int iScanDigits;
  BOOL fParamIsLong;         /* TRUE if the next parameter is a long        */

  /*
  **  TRUE if a parameter is required from the stack
  */
  BOOL fParamRequired;
} CS, *PCS;

static char szTrue[] = "true";
static char szFalse[] = "false";
void PrintBool(long,PCS);
void PrintHex(unsigned long,PCS);
void PrintFraction(long,PCS);
void PrintDecimal(long,PCS);
PSZ  ScanToken(PSZ,PCS);
PSZ  ScanPercentFormat(PSZ,PCS);
PSZ  ScanDigits(PSZ,PCS);

/***************************************************************************
 *
 * FUNCTION NAME = ScanDigits
 *
 * DESCRIPTION   = This routine scans the digit count value in the format
 *                 string and converts it from ASCII to decimal.
 *
 * INPUT         = PSZ pszFormat - The printf style format string
 *                 PCS pcs       - Ptr to the current print state structure
 *
 * OUTPUT        = PSZ
 *
 *                 The function return value is the updated format string
 *                 pointer.  The returned pointer points past the numeric
 *                 digit count.
 *
 *                 The global variable "iScanDigits" is set to the numeric
 *                 value in the format string.
 *
 * RETURN-NORMAL = pszFormat
 * RETURN-ERROR  =
 *
 ****************************************************************************/

PSZ ScanDigits( PSZ pszFormat,PCS pcs )
  /*
  ** PSZ pszFormat;            The printf style format string
  ** PCS pcs;                  Ptr to the current print state structure
  */
{
  char bCh;

  pcs->iScanDigits = 0;

  while (pszFormat)
  {

    if (*pszFormat >= '0' && *pszFormat <= '9')
    {
      pcs->iScanDigits = pcs->iScanDigits *10+(*pszFormat++-'0');
    }
    else
    {
      break;
    }
  }

  /*
  ** Ensure that at least one digit is printed
  */
  if (pcs->iScanDigits == 0)
  {
    pcs->iScanDigits = 1;
  }
  return( pszFormat );
}

/***************************************************************************
 *
 * FUNCTION NAME = ScanPercentFormat
 *
 * DESCRIPTION   = This routine scans a percent format specifier in a printf
 *                 style format string.
 *
 * INPUT         = PSZ pszFormat - The printf style format string
 *                 PCS pcs       - Ptr to the current print state
 *
 * OUTPUT        = PSZ
 *
 *        1. The function return value is the updated format string
 *           pointer.  The returned pointer points past the percent
 *           format specifier.
 *
 *        2. The global variable "iToken" is set the the type of
 *           format specifier.
 *
 *        3. The global variable "iScanDigits" is set to the numeric
 *           value in the format specifier.
 *
 * RETURN-NORMAL = pszFormat
 * RETURN-ERROR  =
 *
 ****************************************************************************/

PSZ ScanPercentFormat( PSZ pszFormat, PCS pcs )
  /*
  ** PSZ pszFormat;        The printf style format string
  ** PCS pcs;              Ptr to the current print state
  */
{
  ASSERT(pcs->fParamIsLong == FALSE);
  ASSERT(pcs->fParamRequired == FALSE);

  if (*pszFormat == '%')     /* Handle the case where percent is quoted     */
  {
    pcs->szScanText[0] = '%';
    pcs->szScanText[1] = 0;
    pcs->iToken = TK_TEXT;
    ++pszFormat;
  }
  else
  {

    /*
    **  Get the number of digits (e.g. %05x)
    */
    pszFormat = ScanDigits(pszFormat, pcs);

    /*
    **  Check to see if the parameter is a long (e.g %05lx)
    */
    if (*pszFormat == 'L' || *pszFormat == 'l')
    {
      pcs->fParamIsLong = TRUE;
      ++pszFormat;
    }

    /*
    **  Assume that all formats require a parameter
    */
    pcs->fParamRequired = TRUE;

    /*
    **  Check for all the possible format letters and set the token value
    */

    switch (*pszFormat)
    {
    case 'd':
    case 'D':
         pcs->iToken = TK_DEC;
         break;

    case 'x':
    case 'X':
         if (pcs->fParamIsLong)
         {
           pcs->iToken = TK_LHEX;
         }
         else
         {
           pcs->iToken = TK_HEX;
         }
         break;

    case 'p':
    case 'P':
         if (pcs->fParamIsLong)
         {
           pcs->iToken = TK_LPTR;
         }
         else
         {
           pcs->iToken = TK_PTR;
         }
         break;

    case 'b':
    case 'B':
         pcs->iToken = TK_BOOL;
         break;

    case 'c':
    case 'C':
         pcs->iToken = TK_CHAR;
         break;

    case 's':
    case 'S':
         pcs->iToken = TK_STR;
         break;

    case 'f':
    case 'F':
         pcs->iToken = TK_FRAC;
         break;

    default:
         --pszFormat;        /* put the pointer back where it belongs.      */
         pcs->fParamRequired = FALSE;
         pcs->iToken = TK_NOP;
         break;
    }

    /*
    **  update the string pointer.
    */
    ++pszFormat;
  }

  /*
  ** Return the format string ptr updated past the format specifier
  */
  return( pszFormat );
}

/***************************************************************************
 *
 * FUNCTION NAME = ScanToken
 *
 * DESCRIPTION   = This routine scans a printf style format string and
 *                 sets the relavent global variables which describe the
 *                 next format specifier.
 *
 * INPUT         = PSZ pszFormat - The printf style format string
 *                 PCS pcs       - Ptr to the current print state
 *
 * OUTPUT        = PSZ
 *
 *                1. The function return value is the updated format string
 *                   pointer.  The returned pointer points past the next
 *                   format specifier.
 *
 *                2. The global variable "iToken" is set the the type of
 *                   format specifier.
 *
 *                3. The global variable "iScanDigits" is set to the numeric
 *                   value in the format specifier.
 *
 *                4. The global string "szScanText" contains any plain text
 *                   extracted from the format string.
 *
 *
 * RETURN-NORMAL = pszFormat
 * RETURN-ERROR  =
 *
 ****************************************************************************/

PSZ ScanToken( PSZ pszFormat, PCS pcs )
  /*
  ** PSZ pszFormat;           The printf style format string
  ** PCS pcs;                 Ptr to the current print state
  */
{
  PB   lpbDst;
  int  cbDst;
  char b;

  pcs->fParamRequired = FALSE;  /* Assume that no parameter required    */
  pcs->fParamIsLong = FALSE;    /* Assume parameter is a short              */

  switch (*pszFormat)
  {
  case 0:
       pcs->iToken = TK_END;  /* End of format string token                 */
       break;

  case '%':
       ++pszFormat;
       pszFormat = ScanPercentFormat(pszFormat, pcs);
       break;

  default:
       pcs->iToken = TK_TEXT; /* Copy plain text from format str            */
       lpbDst = pcs->szScanText;
       cbDst = sizeof(pcs->szScanText)-1;

       /*
       ** Copy the literal string into the output buffer
       */
       while (TRUE)
       {
         /*
         **  Get the next byte and break at end of line or percent
         */
         b = *pszFormat;

         if (b == 0 || b == '%')
         {
           break;
         }
         ++pszFormat;

         /*
         **  Expand new line into Cr/Lf
         */
         if (b == '\n')
         {
           if (--cbDst > 0)
           {
             *lpbDst++ = '\r';
           }
         }

         /*
         **  Store the byte
         */
         if (--cbDst > 0)
         {
           *lpbDst++ = b;
         }
       }
       *lpbDst = 0;
       break;
  }
  return (pszFormat);
}

/***************************************************************************
 *
 * FUNCTION NAME = PrintDecimal
 *
 * DESCRIPTION   = This routine converts the value of its parameter to
 *                 ASCII-Decimal.
 *
 * INPUT         =  long lValue
 *                  PCS  pcs
 *
 * OUTPUT        = void
 *
 *                  The converted value is in the buffer pointed by lpbOut.
 *                  lpbOut is updated so that it points past the string and
 *                  the buffer size cbOut is reduced by the length of the string.
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

void PrintDecimal( long lValue, PCS pcs )
{
  static long int aPowersOfTen[] =
  {
    1000000000L,100000000L,10000000L,1000000L,100000L,10000L,1000L,100L,10L,1
  };
  register int   i, j;
  register long *pPowers;

  /*
  **    Print the minus sign if the number is negative
  */
  if (lValue < 0)
  {
    lValue = -lValue;
    CharToBuf('-');
  }

  /*
  **    Convert from binary to ASCII coded decimal
  */
  pPowers = aPowersOfTen;

  for (i = 10; i > 0; --i)
  {
    for (j = 0; j < 10; ++j)
    {
      if ((lValue-*pPowers) >= 0)
      {
        lValue -= *pPowers;
      }
      else
      {
        if (j != 0 || i <= pcs->iScanDigits)
        {
          /*
          **    Set iScanDigits to ensure we print remaining digits
          */
          pcs->iScanDigits = i;
          CharToBuf( (CHAR) j + '0' );   /* Output digit to buffer        */
        }
        break;
      }
    }
    ++pPowers;
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = PrintFraction
 *
 * DESCRIPTION   = This routine converts the fixed point 16.16 value of
 *                 its parameter into ASCII.
 *
 * INPUT         = long  lValue
 *                 PCS   pcs
 *
 * OUTPUT        = void
 *
 *                 The converted value is in the buffer pointed by lpbOut.
 *                 lpbOut is updated so that it points past the string and
 *                 the buffer size cbOut is reduced by the length of the
 *                 string.
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

void PrintFraction(long lValue,PCS pcs)
{
  char abFrac[5];
  PB lpbSrc;
  int nDigits;

  /*
  ** If the value is negative, print a minus sign and negate
  ** the value.
  */
  if (lValue < 0L)
  {
    CharToBuf( '-' );
    lValue = -lValue;
  }

  /*
  ** Print the integer portion
  */
  PrintDecimal( LongShiftRight( lValue, 16 ), pcs );

  /*
  ** Output the integer portion of the value
  */
  nDigits = FractionToDecimal( abFrac, (short int) lValue );

  /*
  ** Check to see if there is a non-zero fractional part
  */
  if (nDigits > 0)
  {

    /*
    ** Output the decimal point
    */
    CharToBuf( '.' );

    /*
    ** Output the fractional part
    */
    lpbSrc = abFrac;

    while ( --nDigits >= 0 )
    {
      CharToBuf( *lpbSrc );
      lpbSrc++;
    }
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = PrintHex()
 *
 * DESCRIPTION   = This routine converts the value of its parameter
 *                 to ASCII-Hex.
 *
 * INPUT         = unsigned long lValue
 *                 PCS           pcs
 *
 * OUTPUT        = void
 *
 *                 The converted value is in the buffer pointed by lpbOut.
 *                 lpbOut is updated so that it points past the string and
 *                 bthe buffer size cbOut is reduced by the length of the
 *                 string.
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

void PrintHex( unsigned long lValue, PCS pcs )
{
  int  i;
  char ab[32];
  PB   lpbDst;
  char bCh;

  /*
  **    Limit the digit count so that the buffer does not overflow
  */
  if (pcs->iScanDigits > (sizeof( ab ) - 1))
  {
    pcs->iScanDigits = sizeof( ab ) - 1;
  }
  lpbDst = (PB) &ab[sizeof( ab )];
  *--lpbDst = 0;

  /*
  **    Convert the number to ASCII-Hex
  */
  for (i = 0; i < (sizeof(ab)-1) && lValue != 0; ++i)
  {
    bCh = (char)(lValue & 0x0fL );/* Get a nibble                                   */
    *--lpbDst = bCh > 9?bCh+'A'-10:bCh+'0';
    lValue = LongShiftRight( lValue, 4 );
    --pcs->iScanDigits;
  }

  /*
  ** If the value was zero, make sure at least one digit is printed
  */
  if (*lpbDst == 0)
  {
    CharToBuf( '0' );
    --pcs->iScanDigits;
  }

  /*
  ** Store the leading zeros in the output buffer
  */
  while (--pcs->iScanDigits >= 0)
  {
    CharToBuf( '0' );
  }

  /*
  ** Copy the ASCII value to the output buffer
  */
  while (*lpbDst != 0)
  {
    CharToBuf( *lpbDst );
    lpbDst++;
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = PrintBool
 *
 * DESCRIPTION   = This routine converts the value of its parameter to a
 *                 "TRUE" or "FALSE" ASCII string.
 *
 * INPUT         = long lValue
 *                 PCS  pcs    - Ptr to the current state information
 *
 * OUTPUT        = void
 *
 *                 The converted value is in the buffer pointed by lpbOut.
 *                 lpbOut is updated so that it points past the string and
 *                 the buffer size cbOut is reduced by the length of the
 *                 string.
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

void PrintBool( long lValue, PCS pcs )
  /*
  **  PCS pcs - Ptr to the current state information
  */
{
  PB lpbSrc;

  /*
  ** Get a pointer to either "true" or "false"
  */
  if (lValue)
  {
    lpbSrc = szTrue;
  }
  else
  {
    lpbSrc = szFalse;
  }

  /*
  ** Copy the value to the output buffer
  */
  while (*lpbSrc)
  {
    CharToBuf( *lpbSrc );
    lpbSrc++;
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = kprintf
 *
 * DESCRIPTION   = This is the printf kernel entry point that is used by
 *                 all the variations on printf such as the entry point
 *                 logging routine, etc.
 *
 * INPUT         = BOOL fPascal       - TRUE if stack uses Pascal calling
 *                                      convention
 *                 PB  lpbBuf         - Ptr to the output string buffer
 *                 int cbBuf          - The output buffer length in bytes
 *                 PSZ  pszFormat     - The format string
 *                 ULONG  *lpStack - Ptr to the values on the stack
 *
 * OUTPUT        = int
 *
 *                 1. The length of the output string exclusive of the null
 *                    terminator.
 *
 *                 2. The formatted output string is returned in the output
 *                    buffer.
 *
 * RETURN-NORMAL = cbBuf - cs.cbOut - 1
 * RETURN-ERROR  =
 *
 ****************************************************************************/

ULONG kprintf( BOOL fPascal, PB lpbBuf, int cbBuf, PSZ pszFormat, ULONG *lpStack )

  /*
  ** BOOL fPascal;         TRUE if stack uses Pascal calling convention
  ** PB  lpbBuf;                   Ptr to the output string buffer
  ** int cbBuf;            The output buffer length in bytes
  ** PSZ        pszFormat;         The format string
  ** ULONG *lpStack;       Ptr to the values on the stack
  */
{
  PB     lpb;
  CS     cs;                 /* The current state structure                 */
  PCS    pcs;
  long   lval;
  short *pshort;

  /*
  **  save a pointer to the cs structure.  needed for CharToBuf macro.
  */
  pcs = &cs;

  /*
  ** Store these as globals so we don't have to pass them on the stack
  */
  cs.lpbOut = lpbBuf;
  cs.cbOut = cbBuf-1;        /* Reserve a byte for the null terminator      */

  while (cs.cbOut > 0)
  {
    pszFormat = ScanToken( pszFormat, &cs );

    /*
    **  If a parameter is required load it into lVal from the stack
    */
    if (cs.fParamRequired)
    {
      if (cs.fParamIsLong || cs.iToken == TK_FRAC)
      {
        lval = *( lpStack++ );
      }
      else
      {
        /*
        ** pshort  = (short *)lpStack;
        ** lval    = *(pshort++ );
        ** lpStack = (ULONG *) pshort;
        */

        lval = *(lpStack++ );
      }
    }

    switch (cs.iToken)
    {
    case TK_END:
         goto EXIT;

    case TK_TEXT:      /* Format = plain text                      */
         lpb = cs.szScanText;

         while (*lpb)
         {
           CharToBuf( *lpb );
           lpb++;
         }
         break;

    case TK_STR:            /* Fomat = string                              */
         /*
         **  Copy the string to the output buffer
         */
         lpb = (PB)lval;

         while (*lpb)
         {
           CharToBuf( *lpb );
           lpb++;
         }
         break;

    case TK_PTR:            /* Format = near pointer                       */
         cs.iScanDigits = 4;
         PrintHex(lval&0x0ffffL, &cs );
         break;

    case TK_LPTR :         /* Format = far pointer                        */
         /*
         **  Print the segment number
         */
         cs.iScanDigits = 4;
         PrintHex(LongShiftRight(lval, 16)&0x0ffffL, &cs );

         /*
         **  Seperate segment and offset with a colon
         */
         CharToBuf( ':' );

         /*
         **  Print the offset
         */
         cs.iScanDigits = 4;
         PrintHex( lval & 0x0ffffL, &cs );
         break;

    case TK_DEC:            /* Format = short decimal                      */
         PrintDecimal( lval, &cs );
         break;

    case TK_HEX:            /* Format = short hex                          */
         PrintHex( lval & 0x0ffffL, &cs );
         break;

    case TK_LHEX:           /* Format = long hex                           */
         PrintHex( lval, &cs );
         break;

    case TK_CHAR:           /* Format = character                          */
         CharToBuf( (char)lval );
         break;

    case TK_BOOL:           /* Format = boolean                            */
         PrintBool( lval, &cs );
         break;

    case TK_FRAC:
         PrintFraction( lval, &cs );
         break;
    }
  }

EXIT:

  *cs.lpbOut = 0;
  return ((ULONG) (cbBuf - cs.cbOut - 1) );
}
