Goto sanos source index

//
// output.c
//
// Print formatting routines 
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <math.h>

#define MAXBUFFER 512

// Flag definitions

#define FL_SIGN       0x00001   // Put plus or minus in front
#define FL_SIGNSP     0x00002   // Put space or minus in front
#define FL_LEFT       0x00004   // Left justify
#define FL_LEADZERO   0x00008   // Pad with leading zeros
#define FL_LONG       0x00010   // Long value given
#define FL_SHORT      0x00020   // Short value given
#define FL_SIGNED     0x00040   // Signed data given
#define FL_ALTERNATE  0x00080   // Alternate form requested
#define FL_NEGATIVE   0x00100   // Value is negative
#define FL_FORCEOCTAL 0x00200   // Force leading '0' for octals

static void cfltcvt(double value, char *buffer, char fmt, int precision, int capexp) {
  int decpt, sign, exp, pos;
  char *digits = NULL;
  char cvtbuf[CVTBUFSIZE];
  int magnitude;

  if (fmt == 'g') {
    digits = ecvtbuf(value, precision, &decpt, &sign, cvtbuf);
    magnitude = decpt - 1;
    if (magnitude < -4  ||  magnitude > precision - 1) {
      fmt = 'e';
      precision -= 1;
    } else {
      fmt = 'f';
      precision -= decpt;
    }
  }

  if (fmt == 'e') {
    digits = ecvtbuf(value, precision + 1, &decpt, &sign, cvtbuf);

    if (sign) *buffer++ = '-';
    *buffer++ = *digits;
    if (precision > 0) *buffer++ = '.';
    memcpy(buffer, digits + 1, precision);
    buffer += precision;
    *buffer++ = capexp ? 'E' : 'e';

    if (decpt == 0) {
      if (value == 0.0) {
        exp = 0;
      } else {
        exp = -1;
      }
    } else {
      exp = decpt - 1;
    }

    if (exp < 0) {
      *buffer++ = '-';
      exp = -exp;
    } else {
      *buffer++ = '+';
    }

    buffer[2] = (exp % 10) + '0';
    exp = exp / 10;
    buffer[1] = (exp % 10) + '0';
    exp = exp / 10;
    buffer[0] = (exp % 10) + '0';
    buffer += 3;
  } else if (fmt == 'f') {
    digits = fcvtbuf(value, precision, &decpt, &sign, cvtbuf);
    if (sign) *buffer++ = '-';
    if (*digits) {
      if (decpt <= 0) {
        *buffer++ = '0';
        *buffer++ = '.';
        for (pos = 0; pos < -decpt; pos++) *buffer++ = '0';
        while (*digits) *buffer++ = *digits++;
      } else {
        pos = 0;
        while (*digits) {
          if (pos++ == decpt) *buffer++ = '.';
          *buffer++ = *digits++;
        }
      }
    } else {
      *buffer++ = '0';
      if (precision > 0) {
        *buffer++ = '.';
        for (pos = 0; pos < precision; pos++) *buffer++ = '0';
      }
    }
  }

  *buffer = '\0';
}

static void forcdecpt(char *buffer) {
  while (*buffer) {
    if (*buffer == '.') return;
    if (*buffer == 'e' || *buffer == 'E') break;
    buffer++;
  }

  if (*buffer) {
    int n = strlen(buffer);
    while (n > 0)  {
      buffer[n + 1] = buffer[n];
      n--;
    }

    *buffer = '.';
  } else {
    *buffer++ = '.';
    *buffer = '\0';
  }
}

static void cropzeros(char *buffer) {
  char *stop;

  while (*buffer && *buffer != '.') buffer++;
  if (*buffer++) {
    while (*buffer && *buffer != 'e' && *buffer != 'E') buffer++;
    stop = buffer--;
    while (*buffer == '0') buffer--;
    if (*buffer == '.') buffer--;
    while (*++buffer = *stop++);
  }
}

int _output(FILE *stream, const char *format, va_list args) {
  char *text;
  int textlen;
  int ch;
  int cnt;
  int radix, hexadd, padding;
  double fltval;
  char buffer[MAXBUFFER];
  char *fmt = (char *) format;

  cnt = 0;
  while (*fmt) {
    // Initialize formating state
    char prefix[2];
    int prefixlen = 0;
    int flags = 0;
    int precision = -1;
    int qualifier = -1;
    int width = 0;
    int capexp = 0;

    // Go forward until next format specifier or end of string
    while (*fmt != 0 && *fmt != '%') {
      //if ((stream->flag & _IOCRLF) && *fmt == '\n') putc('\r', stream);
      putc(*fmt, stream);
      fmt++;
      cnt++;
    }
    if (*fmt++ == 0) break;

    // Check for flags
    switch (*fmt++) {
      case '-': flags |= FL_LEFT;      break;  // '-' => Left justify
      case '+': flags |= FL_SIGN;      break;  // '+' => Force sign indicator
      case ' ': flags |= FL_SIGNSP;    break;  // ' ' => Force sign or space
      case '#': flags |= FL_ALTERNATE; break;  // '#' => Alternate form
      case '0': flags |= FL_LEADZERO;  break;  // '0' => Pad with leading zeros
      default : fmt--; // No format flag
    }

    // Check for width value
    if (*fmt == '*') {
      // Get width from arg list
      width = va_arg(args, int);
      if (width < 0) {
        // ANSI says neg fld width means '-' flag and pos width
        flags |= FL_LEFT;
        width = -width;
      }
      fmt++;
    } else {
      // Get field width
      while (*fmt >= '0' && *fmt <= '9') width = width * 10 + (*fmt++ - '0');
    }
    
    // Check for precision
    if (*fmt == '.') {
      // Zero the precision, since dot with no number means 0, not default, according to ANSI
      precision = 0;
      fmt++;

      if (*fmt == '*') {
        // Get precision from arg list
        precision = va_arg(args, int);
        if (precision < 0) precision = -1; // Negative precision means default
        fmt++;
      } else {
        // Get precision
        while (*fmt >= '0' && *fmt <= '9') precision = precision * 10 + (*fmt++ - '0');
      }
    }
    
    // Get the conversion qualifier
    switch (*fmt) {
      case 'h':
        // Half-word, i.e. 16-bit int
        qualifier = 'h';
        fmt++;
        break;

      case 'L':
        // Long-word, i.e. 32-bit int
        qualifier = 'l';
        fmt++;
        break;

      case 'q':
        // Quad-word, i.e. 64-bit int (BSD)
        qualifier = 'q';
        fmt++;
        break;

      case 'l':
        // Long-word (l) or quad-word (ll), i.e. 32/64-bit int
        qualifier = 'l';
        fmt++;
        if (*fmt == 'l') {
          qualifier = 'q';
          fmt++;
        }
        break;

      case 'I':
        qualifier = 'l';
        fmt++;
        if (fmt[1] == '6' && fmt[2] == '4') {
          // Quad-word (I64), i.e. 64-bit int (MSVC)
          qualifier = 'q';
          fmt += 2;
        } else if (fmt[1] == '3' && fmt[2] == '2') {
          // Long-word (I32), i.e. 32-bit int (MSVC)
          qualifier = 'l';
          fmt += 2;
        }
        break;
    }

    // Format output according to type specifier    
    ch = *fmt++;
    switch (ch) {
      case 'c': // One character
        buffer[0] = va_arg(args, char);
        text = buffer;
        textlen = 1;
        break;
     
      case 's': // Zero terminated character string
        text = va_arg(args, char *);
        if (text == NULL) {
          text = "(null)";
          textlen = 6;
        } else {
          char *p;

          int maxlen = precision == -1 ? INT_MAX : precision;
          textlen = 0;
          p = text;
          while (maxlen-- && *p) p++;
          textlen = p - text;
        }
        break;

      case 'n': // Number of characters successfully written so far
        if (qualifier == 'l') {
          long *ip = va_arg(args, long *);
          *ip = cnt;
        } else {
          int *ip = va_arg(args, int *);
          *ip = cnt;
        }
        continue;

      case 'd': // Signed decimal integer
      case 'i':
        flags |= FL_SIGNED;
        radix = 10;
        goto case_int;

      case 'u': // Unsigned decimal integer
        radix = 10;
        goto case_int;

      case 'p': // Pointer
        precision = 2 * sizeof(void *);  // Number of hex digits needed
        // Drop through to hex formatting

      case 'X': // Unsigned upper hex integer
         hexadd = 'A' - '9' - 1; // Set hexadd for uppercase hex
         goto case_hex;

      case 'x': // Unsigned lower hex integer
         hexadd = 'a' - '9' - 1; // Set hexadd for lowercase hex
         // Drop through to hex formatting

      case_hex:
         radix = 16;
         if (flags & FL_ALTERNATE) {
           // Alternate form means '0x' prefix
           prefix[0] = '0';
           prefix[1] = 'x' - 'a' + '9' + 1 + hexadd;  // 'x' or 'X'
           prefixlen = 2;
         }
         goto case_int;

      case 'o': // Unsigned octal integer
        radix = 8;
        if (flags & FL_ALTERNATE) {
          // Alternate form means force a leading 0
          flags |= FL_FORCEOCTAL;
        }
        // Drop through to int formatting

      case_int: {
        unsigned int number;       // Number to convert (32 bit)
        unsigned __int64 number64; // Number to convert (64 bit)
        int digit;                 // ASCII value of digit

        // Read argument and check for negative
        if (qualifier == 'q') {
          if (flags & FL_SIGNED) {
            __int64 n = va_arg(args, __int64);
            if (n < 0) {
              number64 = -n;
              flags |= FL_NEGATIVE;
            } else {
              number64 = n;
            }
          } else {
            number64 = va_arg(args, unsigned __int64);
          }
        } else if (qualifier == 'l') {
          if (flags & FL_SIGNED) {
            long n = va_arg(args, long);
            if (n < 0) {
              number = -n;
              flags |= FL_NEGATIVE;
            } else {
              number = n;
            }
          } else {
            number = va_arg(args, unsigned long);
          }
        } else if (qualifier == 'h') {
          if (flags & FL_SIGNED) {
            short n = va_arg(args, short);
            if (n < 0) {
              number = -n;
              flags |= FL_NEGATIVE;
            } else {
              number = n;
            }
          } else {
            number = va_arg(args, unsigned short);
          }
        } else if (flags & FL_SIGNED) {
          int n = va_arg(args, int);
          if (n < 0) {
            number = -n;
            flags |= FL_NEGATIVE;
          } else {
            number = n;
          }
        } else {
          number = va_arg(args, unsigned int);
        }

        // Check precision value for default; non-default turns off 0 flag, according to ANSI
        if (precision < 0) {
          precision = 1;  // Default precision
        } else {
          flags &= ~FL_LEADZERO;
        }

        // Convert data to ASCII -- note if precision is zero and number is zero, we get no digits at all
        text = &buffer[MAXBUFFER - 1]; // Last digit at end of buffer
        if (qualifier == 'q') {
          // Check if data is 0; if so, turn off hex prefix
          if (number64 == 0) prefixlen = 0;

          while (precision-- > 0 || number64 != 0) {
            digit = (int) (number64 % radix) + '0';
            number64 /= radix; // Reduce number
            if (digit > '9') digit += hexadd;
            *text-- = digit;
          }
        } else {
          // Check if data is 0; if so, turn off hex prefix
          if (number == 0) prefixlen = 0;

          while (precision-- > 0 || number != 0) {
            digit = (int) (number % radix) + '0';
            number /= radix; // Reduce number
            if (digit > '9') digit += hexadd;
            *text-- = digit;
          }
        }

        textlen = &buffer[MAXBUFFER - 1] - text; // Compute length of number
        text++; // Text points to first digit now

        // Force a leading zero if FORCEOCTAL flag set */
        if ((flags & FL_FORCEOCTAL) && (*text != '0' || textlen == 0)) {
          *--text = '0';
          textlen++;
        }
        break;
      }
    
      case 'E':
      case 'G':
        capexp = 1;      // Capitalize exponent
        ch += 'a' - 'A'; // Convert format char to lower
        // Drop through to float formatting

      case 'e':
      case 'f':
      case 'g':
        flags |= FL_SIGNED; // Floating point is signed conversion
        text = buffer;      // Put result in buffer

        // Get floating point value
        fltval = va_arg(args, double);

        if (isfinite(fltval)) {
          // Compute the precision value
          if (precision < 0) {
            precision = 6; // Default precision: 6
          } else if (precision == 0 && ch == 'g') {
            precision = 1; // ANSI specified
          }

          // Convert floating point number to text
          cfltcvt(fltval, text, ch, precision, capexp);

          // '#' and precision == 0 means force a decimal point
          if ((flags & FL_ALTERNATE) && precision == 0) forcdecpt(text);

          // 'g' format means crop zero unless '#' given
          if (ch == 'g' && !(flags & FL_ALTERNATE)) cropzeros(text);
        } else if (isnan(fltval)) {
          strcpy(buffer, "NaN");
        } else if (fltval == HUGE_VAL) {
          strcpy(buffer, "+INF");
        } else if (fltval == -HUGE_VAL) {
          strcpy(buffer, "-INF");
        } else {
          strcpy(buffer, "?FLT?");
        }

        // Check if result was negative, save '-' for later and point to positive part (this is for '0' padding)
        if (*text == '-') {
          flags |= FL_NEGATIVE;
          text++;
        }
        textlen = strlen(text); // Compute length of text
        break;

      case '%':
        text = "%";
        textlen = 1;
        break;
        
      default:
        text = "";
        textlen = 0;
    }

    // Check for signs
    if (flags & FL_SIGNED) {
      if (flags & FL_NEGATIVE) {
        prefix[0] = '-';
        prefixlen = 1;
      } else if (flags & FL_SIGN) {
        prefix[0] = '+';
        prefixlen = 1;
      } else if (flags & FL_SIGNSP) {
        prefix[0] = ' ';
        prefixlen = 1;
      }
    }

    // Calculate amount of padding -- might be negative, but this will just mean zero
    padding = width - textlen - prefixlen;

    // Put out the padding, prefix, and text, in the correct order
    if (!(flags & (FL_LEFT | FL_LEADZERO))) {
      // Pad on left with blanks
      while (padding > 0) {
        putc(' ', stream);
        cnt++;
        padding--;
      }
    }

    // Write prefix
    if (prefixlen > 0) {
      int n;

      for (n = 0; n < prefixlen; n++) {
        putc(prefix[n], stream);
        cnt++;
      }
    }

    // Put out leading zeroes
    if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) {
      // Write leading zeros
      while (padding > 0) {
        putc('0', stream);
        cnt++;
        padding--;
      }
    }

    // Put out text
    while (textlen > 0) {
      putc(*text, stream);
      text++;
      textlen--;
      cnt++;
    }

    // Put out trailling blanks
    if (flags & FL_LEFT) {
      // Pad on right with blanks
      while (padding > 0) {
        putc(' ', stream);
        cnt++;
        padding--;
      }
    }
  }
  
  return cnt;
}