www.pudn.com > COS0.0.1.rar > psnprintf.c


/*
 This is part of psnprintf-0.4, see the file "license-psnprintf-0.4".
 
 Originally by Alex Holkner.
 
 Changed 16/10/04 for cos by Paul Barker:
	Stripped out all floating point stuff.
	Changed headers to fit with cos.
 06/11/04:	Added initialisation for several vars to get rid of warnings.
*/

#include 

#include  /* for memset */
#include  /* for strlen */

#include 

int psnprintf(char *str, size_t n, const char *format, ...)
{
    va_list args;
    int ret;
    
    va_start(args, format);
    ret = pvsnprintf(str, n, format, args);
    va_end(args);
    return ret;
}

#define STATE_NONE 0
#define STATE_OPERATOR 1 /* Just received % */
#define STATE_FLAG 2     /* Just received a flag or prefix or width */
#define STATE_WIDTH 3
#define STATE_BEFORE_PRECISION 4 /* just got dot */
#define STATE_PRECISION 5 /* got at least one number after dot */
#define STATE_PREFIX 6   /* just received prefix (h, l or L) */

#define UNKNOWN_WIDTH 0
#define VARIABLE_WIDTH -2
#define UNKNOWN_PRECISION -1
#define VARIABLE_PRECISION -2

/* Following macros give reusable switch cases, used in combination
 * depending on current state. Sucks to do these as macros, but should 
 * give the compiler lots of freedom to optimize.
 */
#define CHECK_FLAG \
    case '-':  \
        flags |= FLAG_LEFT_ALIGN; \
        state = STATE_FLAG; \
        break; \
    case '+': \
        flags |= FLAG_SIGNED; \
        state = STATE_FLAG; \
        break; \
    case '0': \
        flags |= FLAG_ZERO_PAD; \
        state = STATE_FLAG; \
        break; \
    case ' ': \
        flags |= FLAG_SIGN_PAD; \
        state = STATE_FLAG; \
        break; \
    case '#': \
        flags |= FLAG_HASH; \
        state = STATE_FLAG; \
        break;
        
#define CHECK_WIDTH \
    case '1': \
    case '2': \
    case '3': \
    case '4': \
    case '5': \
    case '6': \
    case '7': \
    case '8': \
    case '9': \
        width = *pfmt - '0'; /* convert to integer */ \
        state = STATE_WIDTH; \
        break; \
    case '*': \
        width = VARIABLE_WIDTH; \
        state = STATE_WIDTH; \
        break;

#define CHECK_PRECISION \
    case '.': \
        precision = 0; \
        state = STATE_BEFORE_PRECISION; \
        break; 

#define CHECK_PREFIX \
    case 'h': \
    case 'l': \
    case 'L': \
        prefix = *pfmt; \
        state = STATE_PREFIX; \
        break;

#define GET_VARS \
    if (width == VARIABLE_WIDTH) \
        width = va_arg(ap, int); \
    if (precision == VARIABLE_PRECISION) \
        precision = va_arg(ap, int);

#define CHECK_TYPE \
    case 'd': \
    case 'i': \
    case 'u': \
    case 'o': \
    case 'x': \
    case 'X': \
    case 'p': \
        GET_VARS \
        ncount += pvsnfmt_int(&pinsertion, &nmax, *pfmt, flags, width, precision, prefix, &ap); \
        state = STATE_NONE; \
        break; \
    case 'c': \
        GET_VARS \
        ncount += pvsnfmt_char(&pinsertion, &nmax, *pfmt, flags, width, precision, prefix, &ap); \
        state = STATE_NONE; \
        break; \
    case 's': \
        GET_VARS \
        ncount += pvsnfmt_str(&pinsertion, &nmax, *pfmt, flags, width, precision, prefix, &ap); \
        state = STATE_NONE; \
        break; \
    case 'n': \
        *(va_arg(ap, int *)) = ncount; \
        state = STATE_NONE; \
        break;

#define PUTCHAR(ch) \
    if (nmax > 1) \
    { \
        *pinsertion++ = ch;  \
        nmax--; \
    } \
    ncount++;
        
int pvsnprintf(char *str, size_t nmax, const char *format, va_list ap)
{
    /* nmax gives total size of buffer including null
     * null is ALWAYS added, even if buffer too small for format 
     * (contrary to C99)
     */
     
    char *pinsertion = str;
    const char *pfmt = format;
    int ncount = 0;     /* number of characters printed so far */
    int state = STATE_NONE;
    char flags = 0;
    int width = 0;
    int precision = 0;
    char prefix = 0;
    
    while (*pfmt)
    {
        switch (state)
        {
        case STATE_NONE:
            switch (*pfmt)
            {
            case '%':
                state = STATE_OPERATOR;
                flags = FLAG_DEFAULT;
                width = UNKNOWN_WIDTH;
                precision = UNKNOWN_PRECISION;
                prefix = '\0';
                break;
                
            default:
                PUTCHAR(*pfmt)
            }
            break;
            
        case STATE_OPERATOR:
            switch (*pfmt)
            {
                CHECK_FLAG
                CHECK_WIDTH
                CHECK_PRECISION
                CHECK_PREFIX
                CHECK_TYPE
            default:
                PUTCHAR(*pfmt) /* Unknown format, just print it (e.g. "%%") */
                state = STATE_NONE;
            }
            break;

        case STATE_FLAG:
            switch (*pfmt)
            {
                CHECK_FLAG
                CHECK_WIDTH
                CHECK_PRECISION
                CHECK_PREFIX
                CHECK_TYPE
            }
            break;
        
        case STATE_WIDTH:
            if (*pfmt >= '0' && *pfmt <= '9' && width != -1)
            {
                width = width * 10 + (*pfmt - '0');
                break;
            }
            switch (*pfmt)
            {
                CHECK_PRECISION
                CHECK_PREFIX
                CHECK_TYPE
            }
            break;
            
        case STATE_BEFORE_PRECISION:
            if (*pfmt >= '0' && *pfmt <= '9')
            {
                precision = *pfmt - '0';
                state = STATE_PRECISION;
            }
            else if (*pfmt == '*')
            {
                precision = VARIABLE_PRECISION;
                state = STATE_PRECISION;
            }
            switch (*pfmt)
            {
                CHECK_PREFIX
                CHECK_TYPE
            }
            break;
            
        case STATE_PRECISION:
            if (*pfmt >= '0' && *pfmt <= '9' && precision != -1)
            {
                precision = precision * 10 + (*pfmt - '0');
                break;
            }
            switch (*pfmt)
            {
                CHECK_PREFIX
                CHECK_TYPE
            }
            break;
            
        case STATE_PREFIX:
            switch (*pfmt)
            {
                CHECK_TYPE
            }
            
            
        } /* switch state */
        pfmt++;
        
    } /* while *pfmt */
    
    /* Add null if there is room
     * NOTE there is always room even if str doesn't fit unless
     * nmax initially passed in as 0.  fmt functions take care to
     * always leave at least one free byte at end.
     */
    if (nmax > 0)
        *pinsertion = '\0';
    
    return ncount;
}

int pvsnfmt_char(char **pinsertion, size_t *nmax, const char fmt, int flags, 
                 int width, int precision, char prefix, va_list *ap)
{
    if (*nmax > 1)
    {
        *(*pinsertion)++ = (char) va_arg(*ap, int);
        (*nmax)--;
    }
    return 1;
}

/* strnlen not available on all platforms.. maybe autoconf it? */
size_t pstrnlen(const char *s, size_t count)
{
    const char *p = s;
    while (*p && count-- > 0)
        p++;
    
    return p - s;
}
    
/* Format a string into the buffer.  Parameters:
 *   **pinsertion   Pointer to pointer to buffer (can be reference to NULL)
 *   *nmax          Pointer to size of buffer.  This is may be modified
 *   fmt            Format character ('s')
 *   flags          0 or combination of flags (see .h file for #defines)
 *   width          Width of string, as defined in printf
 *   precision      Precision of string, as defined in printf
 *   ap             Argument list
 */
    
int pvsnfmt_str(char **pinsertion, size_t *nmax, const char fmt, int flags, 
                int width, int precision, char prefix, va_list *ap)
{
    const char *str = va_arg(*ap, const char *);
    int nprinted;
    int len;
    int pad = 0;
    
    /* Get width magnitude, set aligment flag */
    if (width < 0)
    {
        width = -width;
        flags |= FLAG_LEFT_ALIGN;
    }
    
    /* Truncate due to precision */
    if (precision < 0)
        len = strlen(str);
    else
        len = pstrnlen(str, precision);
        
    /* Determine padding length */
    if (width > len)
        pad = width - len;
    
    /* Exit if just counting (not printing) */
    if (*nmax <= 1)
        return len + pad;
    
    /* If right-aligned, print pad */
    if ( !(flags & FLAG_LEFT_ALIGN) )
    {
        char padchar;
        if (flags & FLAG_ZERO_PAD)
            padchar = '0';
        else
            padchar = ' ';
        
        if (*nmax - 1 < pad)
            nprinted = *nmax - 1;
        else
            nprinted = pad;
        
        memset(*pinsertion, padchar, nprinted);
        *pinsertion += nprinted;
        *nmax -= nprinted;
    }

    /* Output string */
    if (*nmax <= 1)
        nprinted = 0;
    else if (*nmax - 1 < len)
        nprinted = *nmax - 1;
    else
        nprinted = len;
        
    memcpy(*pinsertion, str, nprinted);
    *pinsertion += nprinted;
    *nmax -= nprinted;
    
    /* If left aligned, add pad */
    if (flags & FLAG_LEFT_ALIGN)
    {
        if (*nmax <= 1)
            nprinted = 0;
        else if (*nmax - 1 < pad)
            nprinted = *nmax - 1;
        else
            nprinted = pad;
            
        memset(*pinsertion, ' ', nprinted);
        *pinsertion += nprinted;
        *nmax -= nprinted;
    }
    
    return len + pad; /* Return total length of pad + string even if some
                       * was truncated
                       */
}

/* Format an integer into the buffer.  Parameters:
 *   **pinsertion   Pointer to pointer to buffer (can be reference to NULL)
 *   *nmax          Pointer to size of buffer.  This is may be modified
 *   fmt            Format character (one of "diuoxX")
 *   flags          0 or combination of flags (see .h file for #defines)
 *   width          Width of integer, as defined in printf
 *   precision      Precision of integer, as defined in printf
 *   ap             Argument list
 */

int pvsnfmt_int(char **pinsertion, size_t *nmax, char fmt, int flags, 
                int width, int precision, char prefix, va_list *ap)
{
    long int number = 0;
    unsigned long int unumber = 0;
    char numbersigned = 1; 
    char iszero = 0; /* bool */
    int base = 0;
    int len = 0; /* length of number component (no sign or padding) */
    char char10 = 0;
    char sign = 0;
    int widthpad = 0;
    int addprefix = 0; /* optional "0x" = 2 */
    int totallen = 0;

    /* Stack used to hold digits, which are generated backwards
     * and need to be popped off in the correct order
     */
    char numstack[22];    /* largest 64 bit number has 22 octal digits */
    char *stackpos = numstack;
    
#define PUSH(x) \
    *stackpos++ = x
    
#define POP() \
    *(--stackpos)
      
    /* Retrieve value */
    switch (prefix)
    {
    case 'h':
        switch (fmt)
        {
            case 'd':
            case 'i':
                number = (signed short int) va_arg(*ap, int);
                break;
            case 'u':
            case 'o':
            case 'x':
            case 'X':
                unumber = (unsigned short int) va_arg(*ap, int);
                numbersigned = 0;
                break;
             case 'p':
                unumber = (unsigned long) va_arg(*ap, void *);
                numbersigned = 0;
        }
        break;
    case 'l':
        switch (fmt)
        {
            case 'd':
            case 'i':
                number = va_arg(*ap, signed long int);
                break;
            case 'u':
            case 'o':
            case 'x':
            case 'X':
                unumber = va_arg(*ap, unsigned long int);
                numbersigned = 0;
                break;
             case 'p':
                unumber = (unsigned long) va_arg(*ap, void *);
                numbersigned = numbersigned;
        }
        break;
    default:
        switch (fmt)
        {
            case 'd':
            case 'i':
                number = va_arg(*ap, signed int);
                break;
            case 'u':
            case 'o':
            case 'x':
            case 'X':
                unumber = va_arg(*ap, unsigned int);
                numbersigned = 0;
                break;
             case 'p':
                unumber = (unsigned long) va_arg(*ap, void *);
                numbersigned = 0;
         }
    } /* switch fmt to retrieve number */

    if (fmt == 'p')
    {
        fmt = 'x';
        flags |= FLAG_HASH;
    }
    
    /* Discover base */
    switch (fmt)
    {
        case 'd':
        case 'i':
        case 'u':
            base = 10;
            break;
        case 'o':
            base = 8;
            break;
        case 'X':
            base = 16;
            char10 = 'A';
            break;
        case 'x':
            base = 16;
            char10 = 'a';
    }

    if (numbersigned)
    {    
        if (number < 0)
        {
            /* Deal with negativity */
            sign = '-';
            number = -number;
        }
        else if (flags & FLAG_SIGNED)
        {
            sign = '+';
        }
        else if (flags & FLAG_SIGN_PAD)
        {
            sign = ' ';
        }
    }
    
    /* Create number */
    if (numbersigned)
    {     
        if (number == 0)
            iszero = 1;
        do
        {
            PUSH(number % base);
            number /= base;
            len++;
        } while (number != 0);
    }
    else
    {
        if (unumber == 0)
            iszero = 1;
        do
        {
            PUSH(unumber % base);
            unumber /= base;
            len++;
        } while (unumber != 0);
    }        

    /* Octal hash character (alternate form) */
    if (fmt == 'o' && (flags & FLAG_HASH) && precision <= len &&
        precision != 0 && !iszero )
    {
        precision = len + 1;
    }

    /* Determine width of sign, if any. */
    if ( (fmt == 'x' || fmt == 'X') && (flags & FLAG_HASH) && !iszero )
        addprefix = 2;
    else if (sign != 0)
        addprefix = 1;

    /* Make up precision (zero pad on left) */
    while (len < precision)
    {
        PUSH(0);
        len++;
    }
       

    if (len + addprefix < width)
    {
        totallen = width;
        widthpad = width - (len + addprefix);
    }
    else
        totallen = len + addprefix;
           
    if (*nmax <= 1)
        return totallen;

    /* Write sign or "0x" */
    if (flags & FLAG_ZERO_PAD)
    {
        if (addprefix == 2) /* 0x */
        {
            if (*nmax > 1)
            {
                *(*pinsertion)++ = '0';
                (*nmax)--;
            }
            if (*nmax > 1)
            {
                *(*pinsertion)++ = fmt;
                (*nmax)--;
            }
        }
        else if (addprefix == 1) /* sign */
        {
            if (*nmax > 1)
            {
                *(*pinsertion)++ = sign;
                (*nmax)--;
            }
        }
    }

    /* Width pad */
    if ( !(flags & FLAG_LEFT_ALIGN) )
    {
        if (*nmax <= 1)
            widthpad = 0;
        else if (*nmax + 1 < widthpad)
            widthpad = *nmax - 1;
        
        if (flags & FLAG_ZERO_PAD)
            memset(*pinsertion, '0', widthpad);
        else
            memset(*pinsertion, ' ', widthpad);

        *pinsertion += widthpad;
        *nmax -= widthpad;
    }
    
    /* Write sign or "0x" */
    if ( !(flags & FLAG_ZERO_PAD) )
    {
        if (addprefix == 2) /* 0x */
        {
            if (*nmax > 1)
            {
                *(*pinsertion)++ = '0';
                (*nmax)--;
            }
            if (*nmax > 1)
            {
                *(*pinsertion)++ = fmt;
                (*nmax)--;
            }
        }
        else if (addprefix == 1) /* sign */
        {
            if (*nmax > 1)
            {
                *(*pinsertion)++ = sign;
                (*nmax)--;
            }
        }
    }
    
    /* Write number */
    if (*nmax <= 1)
        len = 0;
    else if (*nmax + 1 < len)
        len = *nmax - 1;
    
    for (; len > 0; len--)
    {
        char n = POP();
        if (n <= 9)
            *(*pinsertion)++ = n + '0';
        else
            *(*pinsertion)++ = n - 10 + char10;
    }        
    *nmax -= len;
    
    if (flags & FLAG_LEFT_ALIGN)
    {
        if (*nmax <= 1)
            widthpad = 0;
        else if (*nmax + 1 < widthpad)
            widthpad = *nmax - 1;
            
        memset(*pinsertion, ' ', widthpad);
        *pinsertion += widthpad;
        *nmax -= widthpad;
    }
        
    return totallen;
}

/*
 Almost 400 lines of floating point stuff have been cut out.
 Sorry Alex!
*/