/*      $NetBSD: subr_prf.c,v 1.31 2024/08/31 07:12:57 mlelstv Exp $    */

/*-
* Copyright (c) 1993
*      The Regents of the University of California.  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 University 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 REGENTS 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 REGENTS 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.
*
*      @(#)printf.c    8.1 (Berkeley) 6/11/93
*/

/*
* Scaled down version of printf(3).
*/

#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/stdint.h>         /* XXX: for intptr_t */

#include "stand.h"

#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
#define INTMAX_T        longlong_t
#define UINTMAX_T       u_longlong_t
#else
#define INTMAX_T        long
#define UINTMAX_T       u_long
#endif

#if 0 /* XXX: abuse intptr_t until the situation with ptrdiff_t is clear */
#define PTRDIFF_T       ptrdiff_t
#else
#define PTRDIFF_T       intptr_t
#endif

#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
static void kprintn(void (*)(int), UINTMAX_T, int, int, int);
#else
static void kprintn(void (*)(int), UINTMAX_T, int);
#endif
static void sputchar(int);
static void kdoprnt(void (*)(int), const char *, va_list);

static char *sbuf, *ebuf;

const char hexdigits[16] = "0123456789abcdef";

#define LONG            0x01
#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
#define LLONG           0x02
#endif
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
#define ALT             0x04
#define SPACE           0x08
#define LADJUST         0x10
#define SIGN            0x20
#define ZEROPAD         0x40
#define NEGATIVE        0x80
#define KPRINTN(base)   kprintn(put, ul, base, lflag, width)
#define RADJUSTZEROPAD()                                        \
do {                                                            \
       if ((lflag & (ZEROPAD|LADJUST)) == ZEROPAD) {           \
               while (width-- > 0)                             \
                       put('0');                               \
       }                                                       \
} while (/*CONSTCOND*/0)
#define LADJUSTPAD()                                            \
do {                                                            \
       if (lflag & LADJUST) {                                  \
               while (width-- > 0)                             \
                       put(' ');                               \
       }                                                       \
} while (/*CONSTCOND*/0)
#define RADJUSTPAD()                                            \
do {                                                            \
       if ((lflag & (ZEROPAD|LADJUST)) == 0) {                 \
               while (width-- > 0)                             \
                       put(' ');                               \
       }                                                       \
} while (/*CONSTCOND*/0)
#else   /* LIBSA_PRINTF_WIDTH_SUPPORT */
#define KPRINTN(base)   kprintn(put, ul, base)
#define RADJUSTZEROPAD()        /**/
#define LADJUSTPAD()            /**/
#define RADJUSTPAD()            /**/
#endif  /* LIBSA_PRINTF_WIDTH_SUPPORT */

#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
#define KPRINT(base)                                            \
do {                                                            \
       ul = (lflag & LLONG)                                    \
           ? va_arg(ap, u_longlong_t)                          \
           : (lflag & LONG)                                    \
               ? va_arg(ap, u_long)                            \
               : va_arg(ap, u_int);                            \
       KPRINTN(base);                                          \
} while (/*CONSTCOND*/0)
#else   /* LIBSA_PRINTF_LONGLONG_SUPPORT */
#define KPRINT(base)                                            \
do {                                                            \
       ul = (lflag & LONG)                                     \
           ? va_arg(ap, u_long) : va_arg(ap, u_int);           \
       KPRINTN(base);                                          \
} while (/*CONSTCOND*/0)
#endif  /* LIBSA_PRINTF_LONGLONG_SUPPORT */

static void
null_sputchar(int c)
{
       sbuf++;
}

static void
sputchar(int c)
{

       if (sbuf < ebuf)
               *sbuf++ = c;
}

void
vprintf(const char *fmt, va_list ap)
{

       kdoprnt(putchar, fmt, ap);
}

int
vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
{

       sbuf = buf;
       ebuf = buf + size - 1;
       kdoprnt(buf == NULL ? null_sputchar : sputchar, fmt, ap);
       if (buf != NULL)
               *sbuf = '\0';
       return sbuf - buf;
}

static void
kdoprnt(void (*put)(int), const char *fmt, va_list ap)
{
       char *p;
       int ch;
       UINTMAX_T ul;
       int lflag;
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
       int width;
       char *q;
#endif

       for (;;) {
               while ((ch = *fmt++) != '%') {
                       if (ch == '\0')
                               return;
                       put(ch);
               }
               lflag = 0;
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
               width = 0;
#endif
reswitch:
               switch (ch = *fmt++) {
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
               case '#':
                       lflag |= ALT;
                       goto reswitch;
               case ' ':
                       lflag |= SPACE;
                       goto reswitch;
               case '-':
                       lflag |= LADJUST;
                       goto reswitch;
               case '+':
                       lflag |= SIGN;
                       goto reswitch;
               case '0':
                       if (width == 0) {
                               lflag |= ZEROPAD;
                               goto reswitch;
                       }
                       /* FALLTHROUGH */
               case '1': case '2': case '3': case '4': case '5':
               case '6': case '7': case '8': case '9':
                       for (;;) {
                               width *= 10;
                               width += ch - '0';
                               ch = *fmt;
                               if ((unsigned)ch - '0' > 9)
                                       break;
                               ++fmt;
                       }
                       goto reswitch;
#endif
               case 'l':
#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
                       if (*fmt == 'l') {
                               ++fmt;
                               lflag |= LLONG;
                       } else
#endif
                               lflag |= LONG;
                       goto reswitch;
               case 'j':
#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
                       if (sizeof(intmax_t) == sizeof(long long))
                               lflag |= LLONG;
                       else
#endif
                       if (sizeof(intmax_t) == sizeof(long))
                               lflag |= LONG;
                       goto reswitch;
               case 't':
                       if (sizeof(PTRDIFF_T) == sizeof(long))
                               lflag |= LONG;
                       goto reswitch;
               case 'z':
                       if (sizeof(ssize_t) == sizeof(long))
                               lflag |= LONG;
                       goto reswitch;
               case 'c':
                       ch = va_arg(ap, int);
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
                       --width;
#endif
                       RADJUSTPAD();
                       put(ch & 0xFF);
                       LADJUSTPAD();
                       break;
               case 's':
                       p = va_arg(ap, char *);
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
                       for (q = p; *q != '\0'; ++q)
                               continue;
                       width -= q - p;
#endif
                       RADJUSTPAD();
                       while ((ch = (unsigned char)*p++))
                               put(ch);
                       LADJUSTPAD();
                       break;
               case 'd':
                       ul =
#ifdef LIBSA_PRINTF_LONGLONG_SUPPORT
                       (lflag & LLONG) ? va_arg(ap, longlong_t) :
#endif
                       (lflag & LONG) ? va_arg(ap, long) : va_arg(ap, int);
                       if ((INTMAX_T)ul < 0) {
                               ul = -(INTMAX_T)ul;
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
                               lflag |= NEGATIVE;
#else
                               put('-');
#endif
                       }
                       KPRINTN(10);
                       break;
               case 'o':
                       KPRINT(8);
                       break;
               case 'u':
                       KPRINT(10);
                       break;
               case 'p':
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
                       lflag |= (LONG|ALT);
#else
                       put('0');
                       put('x');
#endif
                       /* FALLTHROUGH */
               case 'x':
                       KPRINT(16);
                       break;
               default:
                       if (ch == '\0')
                               return;
                       put(ch);
                       break;
               }
       }
}

static void
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
kprintn(void (*put)(int), UINTMAX_T ul, int base, int lflag, int width)
#else
kprintn(void (*put)(int), UINTMAX_T ul, int base)
#endif
{
                                       /* hold a INTMAX_T in base 8 */
       char *p, buf[(sizeof(INTMAX_T) * NBBY / 3) + 1 + 2 /* ALT + SIGN */];
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
       char *q;
#endif

       p = buf;
       do {
               *p++ = hexdigits[ul % base];
       } while (ul /= base);
#ifdef LIBSA_PRINTF_WIDTH_SUPPORT
       q = p;
       if (lflag & ALT && *(p - 1) != '0') {
               if (base == 8) {
                       *p++ = '0';
               } else if (base == 16) {
                       *p++ = 'x';
                       *p++ = '0';
               }
       }
       if (lflag & NEGATIVE)
               *p++ = '-';
       else if (lflag & SIGN)
               *p++ = '+';
       else if (lflag & SPACE)
               *p++ = ' ';
       width -= p - buf;
       if (lflag & ZEROPAD) {
               while (p > q)
                       put(*--p);
       }
#endif
       RADJUSTPAD();
       RADJUSTZEROPAD();
       do {
               put(*--p);
       } while (p > buf);
       LADJUSTPAD();
}