255 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |                                                                      |
 | 
						|
 |      vsnprintf.c -- the heart of the fprintf/sprintf family          |
 | 
						|
 |                                                                      |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
/*
 | 
						|
 *  Copyright (C) 2019, Marcel van Kervinck
 | 
						|
 *  All rights reserved
 | 
						|
 *
 | 
						|
 *  Please read the enclosed file `LICENSE' or retrieve this document
 | 
						|
 *  from https://github.com/kervinck/gigatron-rom for terms and conditions.
 | 
						|
 */
 | 
						|
 | 
						|
// TODO Handle size, flags, width and precision
 | 
						|
// TODO Implement %o %x %X %p
 | 
						|
// TODO Cross-check with C89 standard instead of K&R2
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |      Includes                                                        |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
// C standard
 | 
						|
#include <ctype.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <stdio.h>
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |      Definitions                                                     |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
/*
 | 
						|
 *  Our vsnprintf can work with string outputs and streams. This way
 | 
						|
 *  we don't need to duplicate the implementation for the formatting
 | 
						|
 *  logic, or work with intermediate buffers in memory. Strings are
 | 
						|
 *  char pointers into memory. Streams are passed through the "backdoor"
 | 
						|
 *  as `size' and marked with a NULL value for `str'.
 | 
						|
 */
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |      Data                                                            |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
static const unsigned radixtable[] = { 1, 10, 100, 1000, 10000 };
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |      Functions                                                       |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
// Helpers
 | 
						|
static const char *_parsenum(const char *str, int *num);
 | 
						|
static int _discard(int c, FILE *stream);
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |       vsnprintf                                                      |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
int vsnprintf(char *str, size_t size, const char *format, va_list ap)
 | 
						|
{
 | 
						|
  enum { minus = 1, plus = 2, zero = 4, space = 8, hash = 16 };
 | 
						|
 | 
						|
  int count = 0; // Return value is the number of chars produced
 | 
						|
  int flags, width, precision;
 | 
						|
 | 
						|
  // For conversion
 | 
						|
  int sign, i, digit, m, radix;
 | 
						|
  const char *s;
 | 
						|
 | 
						|
  struct _iobuf strbuf, *stream;
 | 
						|
 | 
						|
  // Write out strings through a dummy FILE structure instead of str
 | 
						|
  #define charout(c) do{\
 | 
						|
    if (putc(c, stream) < 0)\
 | 
						|
      return EOF;\
 | 
						|
    count++;\
 | 
						|
  }while(0)
 | 
						|
 | 
						|
  // String or stream?
 | 
						|
  if (str) {                    // String
 | 
						|
    if (size == 0)
 | 
						|
      return 0;                 // No space for anything
 | 
						|
    strbuf._ptr = str;
 | 
						|
    strbuf._n = size;
 | 
						|
    strbuf._flush = _discard;
 | 
						|
    stream = &strbuf;
 | 
						|
  } else                        // Stream
 | 
						|
    stream = (FILE*)size;
 | 
						|
 | 
						|
  for (; *format; format++) {
 | 
						|
    if (*format != '%') {
 | 
						|
      charout(*format);         // Verbatim
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    // Process formatter in the remainder of body
 | 
						|
 | 
						|
    // Parse flags
 | 
						|
    flags = 0;
 | 
						|
    while (1) {
 | 
						|
      switch (*++format) {
 | 
						|
      case '-': flags |= minus; continue;
 | 
						|
      case '+': flags |= plus;  continue;
 | 
						|
      case '0': flags |= zero;  continue;
 | 
						|
      case ' ': flags |= space; continue;
 | 
						|
      case '#': flags |= hash;  continue;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    // Parse minimum width
 | 
						|
    width = -1;
 | 
						|
    if (*format == '*') {
 | 
						|
      width = va_arg(ap, int);
 | 
						|
      format++;
 | 
						|
    } else
 | 
						|
      format = _parsenum(format, &width);
 | 
						|
 | 
						|
    // Parse floating point precision or maximum string width
 | 
						|
    precision = -1;
 | 
						|
    if (*format == '.') {
 | 
						|
      format++;
 | 
						|
      if (*format == '*') {
 | 
						|
        precision = va_arg(ap, int);
 | 
						|
        format++;
 | 
						|
      } else
 | 
						|
        format = _parsenum(format, &precision);
 | 
						|
    }
 | 
						|
 | 
						|
    // Parse length modifier
 | 
						|
    switch (*format) {
 | 
						|
    case 'h': // short or unsigned short
 | 
						|
    case 'l': // long or unsigned long
 | 
						|
    case 'L': // long double
 | 
						|
      format++;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    // Process conversion
 | 
						|
    switch (*format) {
 | 
						|
    case 'd': case 'i':         // int: signed decimal
 | 
						|
    case 'u':                   // int: unsigned decimal
 | 
						|
      m = va_arg(ap, int);
 | 
						|
 | 
						|
      // Split magnitude from sign
 | 
						|
      digit = sign = 0;
 | 
						|
      if (m < 0) {
 | 
						|
        if (*format == 'u') {   // Large unsigned int
 | 
						|
          m -= 30000;           // This can have signed overflow
 | 
						|
          digit = 3;            // Make first digit safe for radix loop below
 | 
						|
        } else {                // Negative signed int
 | 
						|
          m = -m;               // Don't worry about -INT_MIN overflow
 | 
						|
          sign = '-';
 | 
						|
          width--;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (!sign && (flags & plus)) {
 | 
						|
        sign = '+';             // Force plus sign
 | 
						|
        width--;
 | 
						|
      }
 | 
						|
 | 
						|
      if (sign && (flags & zero)) {
 | 
						|
        charout(sign);          // Emit sign first when padding with zeroes
 | 
						|
        sign = 0;
 | 
						|
        digit += '0';           // digit != 0 causes padding with zeroes
 | 
						|
      }
 | 
						|
 | 
						|
      // Emit digits
 | 
						|
      for (i=4; i>=0; i--) {
 | 
						|
        radix = radixtable[i];  // Simpler and faster than division/modulo
 | 
						|
        while (1) {
 | 
						|
          m -= radix;
 | 
						|
          if (m < 0)            // This relies on signed overflow for %u
 | 
						|
            break;
 | 
						|
          digit++;
 | 
						|
        }
 | 
						|
        m += radix;
 | 
						|
 | 
						|
        if (digit || radix == 1U) {// Non-zero or last digit?
 | 
						|
          while (--width > i)   // Padding with spaces or zeroes
 | 
						|
            charout(flags & zero ? '0' : ' ');
 | 
						|
          if (sign) {
 | 
						|
            charout(sign);      // Emit sign before first digit
 | 
						|
            sign = 0;
 | 
						|
          }
 | 
						|
          charout(digit|'0');   // Emit digit as printable ASCII
 | 
						|
          digit = '0';          // Enable all following digits
 | 
						|
        }
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    case 'c':                   // int: single character, cast to unsigned char
 | 
						|
      charout(va_arg(ap, unsigned char));
 | 
						|
      break;
 | 
						|
    case 's':                   // char*s: string
 | 
						|
      for (s=va_arg(ap, const char*); *s; s++)
 | 
						|
        charout(*s);
 | 
						|
      break;
 | 
						|
    case 'n':                   // int*n: count write-back, no conversion
 | 
						|
      *va_arg(ap, int*) = count;
 | 
						|
      break;
 | 
						|
    case '%':                   // literal %-sign, no conversion
 | 
						|
      charout('%');
 | 
						|
      break;
 | 
						|
#if 0
 | 
						|
    case 'o':                   // int: unsigned octal (without a leading zero)
 | 
						|
    case 'x': case 'X':         // int: unsigned hex (without leading 0x or 0X)
 | 
						|
    case 'p':                   // void*: implementation-specific pointer
 | 
						|
    case 'f':                   // double: [-]mmm.ddd, default precision 6, no decimal point for 0
 | 
						|
    case 'e': case 'E':         // double: [-]m.dddddde+/-xx or [-]m.ddddddE+/-xx
 | 
						|
    case 'g': case 'G':         // double: %e/%E if xx<-4 or xx >= precision, otherwise %f
 | 
						|
#endif
 | 
						|
    default:                    // Unknown or not implemented conversion
 | 
						|
      charout('@');
 | 
						|
      (void) va_arg(ap, int);   // Swallow an argument and hope for the best
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (str)
 | 
						|
    *stream->_ptr = '\0';       // Terminating zero for strings
 | 
						|
 | 
						|
  return count;
 | 
						|
}
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |      Helpers                                                         |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 | 
						|
/*
 | 
						|
 *  Parse optional number from string, returns advanced pointer.
 | 
						|
 *  Only writes back the result through *num in case a number is present.
 | 
						|
 */
 | 
						|
static const char *_parsenum(const char *s, int *num)
 | 
						|
{
 | 
						|
  int n = 0;
 | 
						|
  while (isdigit(*s)) {
 | 
						|
    n = n * 10 + *s++ - '0';
 | 
						|
    *num = n;
 | 
						|
  }
 | 
						|
  return s;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *  Throw away chars that don't fit in the caller's string space
 | 
						|
 */
 | 
						|
static int _discard(int c, FILE *stream)
 | 
						|
{
 | 
						|
  return (stream->_n = 1);
 | 
						|
}
 | 
						|
 | 
						|
/*----------------------------------------------------------------------+
 | 
						|
 |                                                                      |
 | 
						|
 +----------------------------------------------------------------------*/
 | 
						|
 |