/*                                                      =====> SPRINTF.C     */
/* TIPC Scheme '84 Runtime Support - C Compatible I/O Routines
   (C) Copyright 1984,1985 by Texas Instruments Incorporated.
   All rights reserved.

   Author:  John C. Jensen
   Installation:  Texas Instruments Incorporated, Dallas, Texas
   Division:  Central Research Laboratories
   Cost Center:  Computer Science Laboratory
   Project:  Computer Architecture Branch
   Date Written:  7 December 1984
   Last Modification:  10 September 1985 by Rusty Haddock
*/
#include "scheme.h"

/************************************************************************/
/*                      Main Print Driver - printf                      */
/*                                                                      */
/* Acknowledgement:  This routines perform formatted output functions   */
/*                      similar to that of the Lattice C compiler.      */
/*                      Some of the following descriptions are          */
/*                      excerpted from the Lattice 8086/8088 C Compiler */
/*                      manual.                                         */
/*                                                                      */
/* Description:  The control string (format statement) contains         */
/*                      ordinary characters, which are sent without     */
/*                      modification to the standard output port, and   */
/*                      format specifiers of the form:                  */
/*                                                                      */
/*                                      %-m.plX                         */
/*                                                                      */
/*                      where (1) the optional "-" indicates the field  */
/*                      is to be left justified (right justified is the */
/*                      default); (2) the optional "m" field is a       */
/*                      decimal number specifying a minimum field width;*/
/*                      (3) the optional ".p" field is the character '.'*/
/*                      followed by a decimal number specifying the     */
/*                      precision of a floating point image or the      */
/*                      maximum number of characters to be printed from */
/*                      a string; (4) the optional "l" (letter ell)     */
/*                      indicates that the item to be formatted is      */
/*                      "long"; and (5) "X" is one of the format type   */
/*                      indicators from the following list:             */
/*                                                                      */
/*                         d -- decimal signed integer                  */
/*                         u -- decimal unsigned integer                */
/*                         x -- hexadecimal integer                     */
/*                         o -- octal integer                           */
/*                         s -- character string                        */
/*                         c -- single character                        */
/*                         f -- fixed decimal floating point            */
/*                         e -- exponential floating point              */
/*                         g -- use "e" or "f", whichever is shorter    */
/*                                                                      */
/*                      The format type must be specified in lower case.*/
/*                      Characters in the control string which are not  */
/*                      part of a format specified are sent to the      */
/*                      output port; a % may be sent by using the       */
/*                      sequence %%.  See the Kernighan and Ritchie     */
/*                      text for a more detailed explanation of the     */
/*                      formatted output functions.                     */
/************************************************************************/

  /* working variables visible to all output routines in this module */
  static int auto_CR = TRUE; /* flag for inserting a "carriage return"
                                   whenever a "line feed" is encountered */
  static int field_width = 0;/* width (number of characters) of format field */
  static int leading_zeros = FALSE;  /* indicates if leading zeros desired */
  static int left_justified = FALSE; /* indicates if field left justified */
  static int long_object = FALSE;    /* indicates if a data item is "long" */
  static int precision = 0;  /* the precision of a floating point image, or
                                the maximum number of character to be printed
                                from a string */

  static char digit[16] = {'0','1','2','3','4','5','6','7','8','9',
                           'A','B','C','D','E','F'};

  char *concat_str();           /* concatenate two character strings */
  char *copy_str();             /* make copy of a character string */
  char *fmt_hex();              /* format a hexadecimal value */
  char *fmt_int();              /* format a signed integer value */
  char *fmt_long();             /* format a long signed integer value */
  char *fmt_unsigned();         /* format an unsigned integer value */
  char *getmem();               /* Lattice C's memory allocation routine */

printf(fmt, data)
char *fmt;              /* the "format statement" describing the I/O */
int  data;              /* the first data object to be printed */
 {
  char *buffer;         /* temporary output buffer for converted values */
  int *next;            /* pointer to the next object to be printed */
  double *next_float;   /* pointer to the next floating point object */
  long *next_long;      /* pointer to the next long object to be printed */

  ENTER(printf);

  /* set the default port address for the I/O operation */
  ssetadr(ADJPAGE(OUT_PAGE), OUT_DISP);

  next = &data;         /* create a pointer to the next object to print */

  /* Continue output until format is exhausted */
  while (*fmt)
   {
    if (*fmt == '%')
     {
      buffer = NULL;
      leading_zeros = FALSE;
      left_justified = FALSE;
      long_object = FALSE;
      field_width = 0;
      precision = 0;
      fmt++;            /* advance pointer to next character in format */

      /* test if field is to be left justified */
      if (*fmt == '-')
       {
        left_justified = TRUE;
        fmt++;
       }

      /* test for request for leading zeros ('0' follows the '%') */
      if (*fmt == '0')
       {
        leading_zeros = TRUE;
        fmt++;
       }

      /* determine the field width, if specified */
      while (isdigit(*fmt))
       {
        field_width = (field_width * 10) + *fmt - '0';
        fmt++;
       }

      /* test for a "precision" field */
      if (*fmt == '.')
       {
        fmt++;
        while (isdigit(*fmt))
         {
          precision = (precision * 10) + *fmt - '0';
          fmt++;
         }
       } /* end:  if (*fmt == '.') */

      /* test for a "long" object */
      if (*fmt == 'l')
       {
        long_object = TRUE;
        fmt++;
       }

      /* decode the format specifier */
      switch (*fmt)
       {
        case 'c': /* single character */
                  prtf_character(*next);
                  next++;
                  break;

        case 'd': /* decimal signed integer */
                  if (long_object)
                   {
                    next_long = (long *) next;
                    buffer = fmt_long(*next_long);
                    next++;
                   }
                  else
                   {
                    buffer = fmt_int(*next);
                   }
                  next++;
                  break;

        case 'e':
        case 'f':
        case 'g': /* Floating-point numbers */
                  next_float = (double *) next;
                  fmt_float(*next_float,*fmt);
                  next += 4;
                  break;

/***** WATCH OUT!!!  Lattice C allows nested comments!!!
        case 'o': /* octal integer */
                  prtf_string("<%o not implemented>");
                  next++;
                  break;
*****/
        case 's': /* character string */
                  prtf_string(*next);
                  next++;
                  break;

        case 'u': /* decimal unsigned integer */
                  if (long_object)
                   {
                    prtf_string("<long objects not implemented>");
                    next++;
                   }
                  else
                   {
                    buffer = fmt_unsigned(*next);
                   }
                  next++;
                  break;

        case 'x': /* hexadecimal integer */
                  if (long_object)
                   {
                    prtf_string("<long objects not implemented>");
                    next++;
                   }
                  else
                   {
                    buffer = fmt_hex(*next);
                   }
                  next++;
                  break;

        case '%': /* the percent sign itself */
                  outchar('%');
                  break;

        case '\0': /* unexpected end of format specification */
                  prtf_string("<unexpected end of format>\n");
                  fmt--;
                  break;

        default:  /* unexpected format specifier */
                  outchar('%');
                  outchar(*fmt);
                  prtf_string("< invalid format specifier>", 0, 0, 0);
                  break;
       } /* end:  switch (*fmt) */

      if (buffer)
       {
        prtf_string(buffer);
        rlsstr(buffer);
       }
     }
    else /* just a character to print (not a % formatting directive) */
     {
      outchar(*fmt);
     }

    fmt++;              /* advance pointer to next character in format */
   } /* end:  while (*fmt) */

  /* reset format control variables to permit calls to sub-entry points */
  leading_zeros = FALSE;
  left_justified = FALSE;
  long_object = FALSE;
  field_width = 0;
  precision = 0;
 }

/************************************************************************/
/*                      Print a single character                        */
/************************************************************************/
prtf_character(ch)
char ch;
 {
/*%%int i;                /* our old favorite index variable */*/

  if (field_width)
   {
    if (left_justified)
     {
      outchar(ch);
      pad_with_blanks(field_width - 1);
     }
    else /* right justified */
     {
      pad_with_blanks(field_width - 1);
      outchar(ch);
     }
   }
  else outchar(ch);    /* just print the single character */

 } /* end of function:  prtf_character(ch) */

/************************************************************************/
/*                      Print a character string                        */
/************************************************************************/
prtf_string(string)
char *string;
 {
  int i;                /* our old favorite index variable */
  int len = 0;          /* the actual length of the character string */

  /* determine string length (search for '\0' end-of-string mark) */
  while (string[len]) len++;

  /* if precision field specified, reduce "len" if too long */
  if (precision && precision < len) len = precision;

  /* output string with appropriate justification */
  if (left_justified)
   {
    for (i = 0; i < len; i++) outchar(string[i]);
    pad_with_blanks(field_width - len);
   }
  else /* right justified */
   {
    pad_with_blanks(field_width - len);
    for (i = 0; i < len; i++) outchar(string[i]);
   }
 }


/************************************************************************/
/*                      Format a floating point number                  */
/************************************************************************/
fmt_float(value,type)
double value;
char type;
 {
  char buf[32];
  int siz;
  siz = ((type=='g')? (outrange(value)) : (type=='e'));
  siz = makeflo(value,buf,precision,siz);
  buf[siz] = '\0';
  prtf_string(buf);
 } /* end of function:  fmt_float(value,type) */


/************************************************************************/
/*                      Format a signed decimal integer                 */
/************************************************************************/
char *fmt_int(value)
int value;
 {
  long lvalue;
  lvalue = value;
  return(fmt_long(lvalue));
/*****
  char buffer[100];     /* buffer to hold characters of converted value */
  int digit_count = 0;  /* count of significant digits */
  int i,j;              /* index variables */
  int negative = FALSE; /* flag to indicate sign of number */

  /* test the sign of the integer-- if negative, record that fact */
  if (value < 0)
   {
    negative = TRUE;
    value = -value;
    field_width--;
   }

  /* convert the integer to ASCII */
  i = sizeof(buffer) - 1;
  buffer[i--] = '\0';           /* insert end of string indicator */
  do {
      buffer[i--] = digit[value % 10];
      value /= 10;
      digit_count++;
     } while (value);

  /* if leading zeros are required, insert said */
  if (leading_zeros)
   {
    for (j = digit_count; j < field_width; j++)
      buffer[i--] = '0';
   }

  /* if negative, insert a '-' sign */
  if (negative)
   {
    buffer[i--] = '-';
   }

  /* return the formatted integer */
  return(copy_str(buffer+i+1));
*****/
 } /* end of function:  char *fmt_int(value) */


/************************************************************************/
/*              Format a long signed decimal integer                    */
/************************************************************************/
char *fmt_long(value)
long value;
 {
  char buffer[100];     /* buffer to hold characters of converted value */
  int digit_count = 0;  /* count of significant digits */
  int i,j;              /* index variables */
  int negative = FALSE; /* flag to indicate sign of number */

  /* test the sign of the integer-- if negative, record that fact */
  if (value < 0)
   {
    negative = TRUE;
    value = -value;
    field_width--;
   }

  /* convert the integer to ASCII */
  i = sizeof(buffer) - 1;
  buffer[i--] = '\0';           /* insert end of string indicator */
  do {
      buffer[i--] = digit[value % 10];
      value /= 10;
      digit_count++;
     } while (value);

  /* if leading zeros are required, insert said */
  if (leading_zeros)
   {
    for (j = digit_count; j < field_width; j++)
      buffer[i--] = '0';
   }

  /* if negative, insert a '-' sign */
  if (negative)
   {
    buffer[i--] = '-';
   }

  /* return the formatted integer */
  return(copy_str(buffer+i+1));

 } /* end of function:  char *fmt_long(value) */


/************************************************************************/
/*                      Format an unsigned decimal integer              */
/************************************************************************/
char *fmt_unsigned(value)
unsigned value;
 {
  int i,j;              /* index variables */
  char buffer[100];     /* buffer to hold characters of converted value */
  int digit_count = 0;  /* count of significant digits */

  /* convert the integer to ASCII */
  i = sizeof(buffer) - 1;
  buffer[i--] = '\0';           /* insert end of string indicator */
  do {
      buffer[i--] = digit[value % 10];
      value /= 10;
      digit_count++;
     } while (value);

  /* if leading zeros are required, insert said */
  if (leading_zeros)
   {
    for (j = digit_count; j < field_width; j++)
      buffer[i--] = '0';
   }

  /* return the formatted unsigned integer */
  return(copy_str(buffer+i+1));

 } /* end of function:  char *fmt_unsigned(value) */


/************************************************************************/
/*                      Format a hexadecimal integer                    */
/************************************************************************/
char *fmt_hex(value)
unsigned value;
 {
  int i,j;              /* index variables */
  char buffer[100];     /* buffer to hold characters of converted value */
  int digit_count = 0;  /* count of significant digits */

  /* convert the integer to ASCII */
  i = sizeof(buffer) - 1;
  buffer[i--] = '\0';           /* insert end of string indicator */
  do {
      buffer[i--] = digit[value & 0xf];
      value = value >> 4;
      digit_count++;
     } while (value);

  /* if leading zeros are required, insert said */
  if (leading_zeros)
   {
    for (j = digit_count; j < field_width; j++)
      buffer[i--] = '0';
   }

  /* return the hexadecimal integer value */
  return(copy_str(buffer+i+1));

 } /* end of function:  char *fmt_hex(value) */


/************************************************************************/
/*                            Pad with blanks                           */
/************************************************************************/
pad_with_blanks(number)
int number;             /* the number of blanks to output */
 {
  int i;                /* index variable */

  /* spew out the appropriate number of blanks */
  for (i = 0; i < number; i++) outchar(' ');

 } /* end of function:  pad_with_blanks */


/************************************************************************/
/*                      Create copy of a character string               */
/************************************************************************/
char *copy_str(string)
char *string;
 {
  char *ret_str;
  ENTER(copy_str);
  if (!(ret_str = getmem(strlen(string)+1))) getmem_error(rtn_name);
  else
   {
    strcpy(ret_str, string);
   }
  return(ret_str);
 }


/************************************************************************/
/*           Create concatenation of two character strings              */
/************************************************************************/
char *concat_str(str1,str2)
char *str1,*str2;
 {
  ENTER(concat_str);
  char *ret_str;
  if (!(ret_str = getmem(strlen(str1) + strlen(str2) + 1)))
    getmem_error(rtn_name);
  else
   {
    strcpy(ret_str, str1);
    strcat(ret_str, str2);
   }
  return(ret_str);
 }
