/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* printf.c: ANSI draft (X3J11 Oct 86) part of section 4.9 code */
/* Copyright (C) Codemist Ltd., 1988                            */
/* version 0.05b */

/* printf and its friends return the number of characters planted. In    */
/* the case of sprintf this does not include the terminating '\0'.       */
/* Consider using ftell instead of charcount in printf (see scanf).      */
/* see c.ansilib re possible use of #define NO_FLOATING_POINT.           */


#define __system_io 1      /* makes stdio.h declare more */
/* sprintf wants to know about the system part of the FILE descriptor, but
   it doesn't need to know about _extradata */
typedef struct _extradata {void *dummy;} _extradata;

#include "hostsys.h"
#include "kernel.h"
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>

#define intofdigit(x) ((x)-'0')

#ifndef _LJUSTIFY  /* allow 'flat' compilation of library */

/* The following typedef and type of __vfprintf must match  fpprintf.c  */
typedef int (*fp_print)(int ch, double *d, char buff[], int flags,
                        char **lvprefix, int *lvprecision, int *lvbefore_dot,
                        int *lvafter_dot);

/* HIDDEN EXPORTS */
int __vfprintf(FILE *p, const char *fmt, va_list args,
               fp_print fp_display_fn, int lf_terminates);
int _fprintf(FILE *fp, const char *fmt, ...);
int _printf(const char *fmt, ...);
int _sprintf(char *buff, const char *fmt, ...);
int _snprintf(char *buff, size_t n, const char *fmt, ...);
int _vfprintf(FILE *p, const char *fmt, va_list args);
int _vsprintf(char *buff, const char *fmt, va_list a);

/* The following take LF or 0 terminated format strings so they work     */
/* with format strings fetched from message files                        */
int _fprintf_lf(FILE *fp, const char *fmt, ...);
int _sprintf_lf(char *buff, const char *fmt, ...);

/* The following macros must match those in fpprintf.c:                  */
/* The code here tends to be a bit generous about meaningless flag       */
/* combinations of options in the format string - it chooses the set     */
/* that it likes and prints according to that style without commenting   */
/* on the redundant options.                                             */

#define _LJUSTIFY         01
#define _SIGNED           02
#define _BLANKER          04
#define _VARIANT         010
#define _PRECGIVEN       020
#define _LONGSPECIFIER   040
#define _SHORTSPEC      0100
#define _PADZERO        0200    /* *** DEPRECATED FEATURE *** */
#define _FPCONV         0400
#define _CHARSPEC      01000
#define _LONGLONGSPEC  02000

#endif  /* _LJUSTIFY */

#define pr_padding(ch, n, p)  while(--n>=0) charcount++, putc(ch, p);

#define pre_padding(p)                                                    \
        if (!(flags&_LJUSTIFY))                                           \
        {   char padchar = flags & _PADZERO ? '0' : ' ';                  \
            pr_padding(padchar, width, p); }

#define post_padding(p)                                                   \
        if (flags&_LJUSTIFY)                                              \
        {   pr_padding(' ', width, p); }

#ifdef never /* do it this way? */
static int pr_num(unsigned int v, int flags, char *prefix,
                   int width, int precision, FILE *p)
{}
#endif

static int printf_display(FILE *p, int flags, int ch, int precision, int width,
                   unsigned long long v, fp_print fp_display_fn, char *prefix,
                   char *hextab, double *d)
{
    int charcount = 0;
    int len = 0, before_dot = -1, after_dot = -1;
    char buff[32];       /* used to accumulate value to print    */
/* here at the end of the switch statement I gather together code that   */
/* is concerned with displaying integers.                                */
/* AM: maybe this would be better as a proc if we could get arg count down */
            if ((flags & _FPCONV+_PRECGIVEN)==0) precision = 1;
            switch (ch)
            {
    case 'p':
    case 'X':
    case 'x':   while(v!=0)
                {   buff[len++] = hextab[v & 0xf];
                    v = v >> 4;
                }
                break;
    case 'o':   while(v!=0)
                {   buff[len++] = '0' + (v & 07);
                    v = v >> 3;
                }
                break;
    case 'u':
    case 'i':
    case 'd':   while (v != 0)
                {   unsigned long long vDiv10 = v / 10U;
                    buff[len++] = '0' + v - vDiv10 * 10U;
                    v = vDiv10;
                }
                break;

#ifndef NO_FLOATING_POINT
    case 'f':   case 'F':
    case 'g':   case 'G':
    case 'e':   case 'E':
    case 'a':   case 'A':
                len = fp_display_fn(ch, d, buff, flags,
/* The following arguments are set by fp_display_fn                      */
                                    &prefix, &precision,
                                    &before_dot, &after_dot);
                break;

#else
/* If floating point is not supported I display ALL %e, %f and %g        */
/* items as 0.0                                                          */
    default:    buff[0] = '0';
                buff[1] = '.';
                buff[2] = '0';
                len = 3;
                break;
#endif
            }
/* now work out how many leading '0's are needed for precision specifier */
/* _FPCONV is the case of FP printing in which case extra digits to make */
/* up the precision come within the number as marked by characters '<'   */
/* and '>' in the buffer.                                                */
            if (flags & _FPCONV)
            {   precision = 0;
                if (before_dot>0) precision = before_dot-1;
                if (after_dot>0) precision += after_dot-1;
            }
            else if ((precision -= len)<0) precision = 0;

/* and how much padding is needed */
            width -= (precision + len + strlen(prefix));

/* AM: ANSI appear (Oct 86) to suggest that the padding (even if with '0') */
/*     occurs before the possible sign!  Treat this as fatuous for now.    */
            if (!(flags & _PADZERO)) pre_padding(p);

            {   int c;                                      /* prefix    */
                while((c=*prefix++)!=0) { putc(c, p); charcount++; }
            }

            pre_padding(p);

/* floating point numbers are in buff[] the normal way around, while     */
/* integers have been pushed in with the digits in reverse order.        */
            if (flags & _FPCONV)
            {   int i, c;
                for (i = 0; i<len; i++)
                {   switch (c = buff[i])
                    {
            case '<':   pr_padding('0', before_dot, p);
                        break;
            case '>':   pr_padding('0', after_dot, p);
                        break;
            default:    putc(c, p);
                        charcount++;
                        break;
                    }
                }
            }
            else
            {   pr_padding('0', precision, p);
                charcount += len;
                while((len--)>0) putc(buff[len], p);
            }

/* By here if the padding has already been printed width will be zero    */
            post_padding(p);
            return charcount;
}

int __vfprintf(FILE *p, const char *fmt, va_list args,
              fp_print fp_display_fn, int lf_terminates)
/* ACN: I apologize for this function - it seems long and ugly. Some of  */
/*      that is dealing with all the jolly flag options available with   */
/*      printf, and rather a lot more is a cautious floating point print */
/*      package that takes great care to avoid the corruption of its     */
/*      input by rounding, and to generate consistent decimal versions   */
/*      of all possible values in all possible formats.                  */
{
    int ch, charcount = 0;
    while ((ch = *fmt++) != 0 && (!lf_terminates || ch != '\n'))
    {   if (ch != '%') { putc(ch,p); charcount++; }
        else
        {   int flags = 0, width = 0, precision = 0;
/* The initialisation of hextab is spurious in that it will be set       */
/* to a real string before use, but necessary in that passing unset      */
/* parameters to functions is illegal in C.                              */
            char *prefix, *hextab = 0;
            unsigned long long v;
#ifndef NO_FLOATING_POINT
            double d;
#endif
/* This decodes all the nasty flags and options associated with an       */
/* entry in the format list. For some entries many of these options      */
/* will be useless, but I parse them all the same.                       */
            for (;;)
            {   switch (ch = *fmt++)
                {
/* '-'  Left justify converted flag. Only relevant if width specified    */
/* explicitly and converted value is too short to fill it.               */
        case '-':   flags = _LJUSTIFY | (flags & ~_PADZERO);
                    continue;

/* '+'  Always print either '+' or '-' at start of numbers.              */
        case '+':   flags |= _SIGNED;
                    continue;

/* ' '  Print either ' ' or '-' at start of numbers.                     */
        case ' ':   flags |= _BLANKER;
                    continue;

/* '#'  Variant on main print routine (effect varies across different    */
/*      styles, but for instance %#x puts 0x on the front of displayed   */
/*      numbers.                                                         */
        case '#':   flags |= _VARIANT;
                    continue;

/* '0'  Leading blanks are printed as zeros                              */
/*        This is a *** DEPRECATED FEATURE *** (precision subsumes)      */
        case '0':   flags |= _PADZERO;
                    continue;

        default:    break;
                }
                break;
            }

            /* now look for 'width' spec */
            {   int t = 0;
                if (ch=='*')
                {   t = va_arg(args, int);
/* If a negative width is passed as an argument I take its absolute      */
/* value and use the negativeness to indicate the presence of the '-'    */
/* flag (left justification). If '-' was already specified I lose it.    */
                    if (t<0)
                    {   t = - t;
                        flags ^= _LJUSTIFY;
                    }
                    ch = *fmt++;
                }
                else
                {   while (isdigit(ch))
                    {   t = t*10 + intofdigit(ch);
                        ch = *fmt++;
                    }
                }
                width = t>=0 ? t : 0;                 /* disallow -ve arg */
            }
            if (ch == '.')                            /* precision spec */
            {   int t = 0;
                ch = *fmt++;
                if (ch=='*')
                {   t = va_arg(args, int);
                    ch = *fmt++;
                }
                else while (isdigit(ch))
                {   t = t*10 + intofdigit(ch);
                    ch = *fmt++;
                }
                if (t >= 0) flags |= _PRECGIVEN, precision = t;
            }
            if (ch=='l' || ch=='L' || ch=='z' || ch=='t')
/* 'l'  Indicate that a numeric argument is 'long'. Here int and long    */
/*      are the same (32 bits) and so I can ignore this flag!            */
/* 'L'  Marks floating arguments as being of type long double. Here this */
/*      is the same as just double, and so I can ignore the flag.        */
/* 'z'  Indicates that a numeric argument is 'size_t', or that a %n      */
/*      argument is a pointer to a size_t. We can ignore it.             */
/* 't'  Indicates that a numeric argument is 'ptrdiff_t', or that a %n   */
/*      argument is a pointer to a ptrdiff_t. We can ignore it.          */
            {   int last = ch;
                flags |= _LONGSPECIFIER;
                ch = *fmt++;
/* 'll' Indicates that a numeric argument is 'long long', or that a %n   */
/*      argument is a pointer to long long int.                          */
                if (ch=='l' && last =='l')
                {   flags |= _LONGLONGSPEC;
                    ch = *fmt++;
                }
            }
            else if (ch=='h')
/* 'h'  Indicates that an integer value is to be treated as short.        */
            {   flags |= _SHORTSPEC;
                ch = *fmt++;
/* 'hh' Indicates that an integer value is to be treated as char.        */
                if (ch=='h')
                {   flags |= _CHARSPEC;
                    ch = *fmt++;
                }
            }
            else if (ch=='j')
/* 'j'  Indicates that a numeric argument is '[u]intmax_t', or than a %n */
/*      argument is a pointer to intmax_t.                               */
            {   flags |= _LONGSPECIFIER|_LONGLONGSPEC;
                ch = *fmt++;
            }

/* Now the options have been decoded - I can process the main dispatch   */
            switch (ch)
            {

/* %c causes a single character to be fetched from the argument list     */
/* and printed. This is subject to padding.                              */
    case 'c':   ch = va_arg(args, int);
                /* drop through */

/* %? where ? is some character not properly defined as a command char   */
/* for printf causes ? to be displayed with padding and field widths     */
/* as specified by the various modifers. %% is handled by this general   */
/* mechanism.                                                            */
    default:    width--;                        /* char width is 1       */
                pre_padding(p);
                putc(ch, p);
                charcount++;
                post_padding(p);
                continue;

/* If a '%' occurs at the end of a format string (possibly with a few    */
/* width specifiers and qualifiers after it) I end up here with a '\0'   */
/* in my hand. Unless I do something special the fact that the format    */
/* string terminated gets lost...                                        */
/* Ditto for '\n' terminated strings. "%\n" doesn't mean anything anyway */
    case '\n':
    case 0:     fmt--;
                continue;

/* %n assigns the number of chars printed so far to the next arg (which  */
/* is expected to be of type (int *), or (long long *) if 'j' or 'll'.   */
    case 'n':   if (flags & _LONGLONGSPEC)
                {   long long *xp = va_arg(args, long long *);
                    *xp = charcount;
                }
                else
                {   int *xp = va_arg(args, int *);
                    *xp = charcount;
                }
                continue;

/* %s prints a string. If a precision is given it can limit the number   */
/* of characters taken from the string, and padding and justification    */
/* behave as usual.                                                      */
    case 's':   {   char *str = va_arg(args, char *);
                    int i, n;
                    if (flags&_PRECGIVEN) {
                      n = 0;
                      while ((n < precision) && (str[n] != 0)) n++;
                    } else
                      n = strlen(str);
                    width -= n;
                    pre_padding(p);
                    for (i=0; i<n; i++) putc(str[i], p);
                    charcount += n;
                    post_padding(p);
                }
                continue;

/* %x prints in hexadecimal. %X does the same, but uses upper case       */
/* when printing things that are not (decimal) digits.                   */
/* I can share some messy decoding here with the code that deals with    */
/* octal and decimal output via %o and %d.                               */
    case 'X':   v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long)
                                            : va_arg(args, unsigned int);
                if (flags & _SHORTSPEC) v = (unsigned short)v;
                if (flags & _CHARSPEC) v = (unsigned char)v;
                hextab = "0123456789ABCDEF";
                prefix = ((flags&_VARIANT) != 0 && v != 0)? "0X" : "";
                if (flags & _PRECGIVEN) flags &= ~_PADZERO;
                break;

    case 'x':   v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long)
                                            : va_arg(args, unsigned int);
                if (flags & _SHORTSPEC) v = (unsigned short)v;
                if (flags & _CHARSPEC) v = (unsigned char)v;
                hextab = "0123456789abcdef";
                prefix = ((flags&_VARIANT) != 0 && v != 0)? "0x" : "";
                if (flags & _PRECGIVEN) flags &= ~_PADZERO;
                break;

/* %p is for printing a pointer - I print it as a hex number with the    */
/* precision always forced to 8.                                         */
    case 'p':   v = (unsigned int)va_arg(args, void *);
                hextab = "0123456789abcdef";
                prefix = (flags&_VARIANT) ? "@" : "";
                flags |= _PRECGIVEN;
                precision = 8;
                break;

    case 'o':   v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long)
                                            : va_arg(args, unsigned int);
                if (flags & _SHORTSPEC) v = (unsigned short)v;
                if (flags & _CHARSPEC) v = (unsigned char)v;
                prefix = (flags&_VARIANT) ? "0" : "";
                if (flags & _PRECGIVEN) flags &= ~_PADZERO;
                break;

    case 'u':   v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long)
                                            : va_arg(args, unsigned int);
                if (flags & _SHORTSPEC) v = (unsigned short)v;
                if (flags & _CHARSPEC) v = (unsigned char)v;
                prefix = "";
                if (flags & _PRECGIVEN) flags &= ~_PADZERO;
                break;

    case 'i':
    case 'd':   {   long long w;
                    w = (flags & _LONGLONGSPEC) ? va_arg(args, long long)
                                                : va_arg(args, int);
                    if (flags & _SHORTSPEC) w = (signed short)w;
                    if (flags & _CHARSPEC) w = (signed char)w;
                    if (w<0) v = 0ULL-w, prefix = "-";
                    else
                        v = w, prefix = (flags&_SIGNED) ? "+" :
                                        (flags&_BLANKER) ? " " : "";
                }
                if (flags & _PRECGIVEN) flags &= ~_PADZERO;
                break;

    case 'f':
    case 'F':
    case 'e':
    case 'E':
    case 'g':
    case 'G':
    case 'a':
    case 'A':   flags |= _FPCONV;
                if (!(flags & _PRECGIVEN)) precision = 6;
#ifndef NO_FLOATING_POINT
                d = va_arg(args, double);
                /* technically, for the call to printf_display() below to  */
                /* be legal and not reference an undefined variable we     */
                /* need to do the following (overwritten in fp_display_fn) */
                /* (It also stops dataflow analysis (-fa) complaining!)    */
                prefix = 0, hextab = 0, v = 0;
#else  /* NO_FLOATING_POINT */
                {   int w = va_arg(args, int);
                    w = va_arg(args, int);
/* If the pre-processor symbol FLOATING_POINT is not set I assume that   */
/* floating point is not available, and so support %e, %f, %g and %a     */
/* with a fragment of code that skips over the relevant argument.        */
/* I also assume that a double takes two int-sized arg positions.        */
                    prefix = (flags&_SIGNED) ? "+" :
                             (flags&_BLANKER) ? " " : "";
                }
#endif /* NO_FLOATING_POINT */
                break;

            }
            charcount += printf_display(p, flags, ch, precision, width, v,
                                        fp_display_fn, prefix, hextab, &d);
            continue;
        }
    }
    return ferror(p) && !(p->__flag & _IOSTRG) ? EOF : charcount;
}

static int no_fp_display(int ch, double *d, char buff[], int flags,
                         char **lvprefix, int *lvprecision, int *lvbefore_dot,
                         int *lvafter_dot)
{
    ch = ch;
    d = d;
    buff = buff;
    flags = flags;
    lvprefix = lvprefix;
    lvprecision = lvprecision;
    lvbefore_dot = lvbefore_dot;
    lvafter_dot = lvafter_dot;
    return 0;
}

int _fprintf(FILE *fp, const char *fmt, ...)
{
    va_list a;
    int n;

    va_start(a, fmt);
    n = __vfprintf(fp, fmt, a, no_fp_display, 0);
    va_end(a);
    return n;
}

int _fprintf_lf(FILE *fp, const char *fmt, ...)
{
    va_list a;
    int n;

    va_start(a, fmt);
    n = __vfprintf(fp, fmt, a, no_fp_display, 1);
    va_end(a);
    return n;
}

int _printf(const char *fmt, ...)
{
    va_list a;
    int n;
    va_start(a, fmt);
    n = __vfprintf(stdout, fmt, a, no_fp_display, 0);
    va_end(a);
    return n;
}

int _sprintf(char *buff, const char *fmt, ...)
{
    FILE hack;
    va_list a;
/*************************************************************************/
/* Note that this code interacts in a dubious way with the putc macro.   */
/*************************************************************************/
    int length;
    va_start(a, fmt);
    memclr(&hack, sizeof(FILE));
    hack.__flag = _IOSTRG+_IOWRITE;
    hack.__ptr = (unsigned char *)buff;
    hack.__ocnt = 0x7fffffff;
    length = __vfprintf(&hack, fmt, a, no_fp_display, 0);
    putc(0,&hack);
    va_end(a);
    return(length);
}

int _snprintf(char *buff, size_t n, const char *fmt, ...)
{
    FILE hack;
    va_list a;
/*************************************************************************/
/* Note that this code interacts in a dubious way with the putc macro.   */
/*************************************************************************/
    int length;
    va_start(a, fmt);
    memclr(&hack, sizeof(FILE));
    hack.__flag = _IOSTRG+_IOWRITE;
    hack.__ptr = (unsigned char *)buff;
    hack.__ocnt = n == 0 ? 0 : n-1;
    length = __vfprintf(&hack, fmt, a, no_fp_display, 0);
    if (n != 0) *hack.__ptr = 0;
    va_end(a);
    return(length);
}

int _sprintf_lf(char *buff, const char *fmt, ...)
{
    FILE hack;
    va_list a;
/*************************************************************************/
/* Note that this code interacts in a dubious way with the putc macro.   */
/*************************************************************************/
    int length;
    va_start(a, fmt);
    memclr(&hack, sizeof(FILE));
    hack.__flag = _IOSTRG+_IOWRITE;
    hack.__ptr = (unsigned char *)buff;
    hack.__ocnt = 0x7fffffff;
    length = __vfprintf(&hack, fmt, a, no_fp_display, 1);
    putc(0,&hack);
    va_end(a);
    return(length);
}

int _vfprintf(FILE *p, const char *fmt, va_list args)
{
    return __vfprintf(p, fmt, args, no_fp_display, 0);
}

int _vsprintf(char *buff, const char *fmt, va_list a)
{
    FILE hack;
/*************************************************************************/
/* Note that this code interacts in a dubious way with the putc macro.   */
/*************************************************************************/
    int length;
    memclr(&hack, sizeof(FILE));
    hack.__flag = _IOSTRG+_IOWRITE;
    hack.__ptr = (unsigned char *)buff;
    hack.__ocnt = 0x7fffffff;
    length = __vfprintf(&hack, fmt, a, no_fp_display, 0);
    putc(0,&hack);
    return(length);
}

/* End of printf.c */