/********************************************
print.c
copyright 2008-2016,2020.  Thomas E. Dickey
copyright 1991-1996,2014.  Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/

/*
 * $MawkId: print.c,v 1.28 2020/01/20 14:08:41 tom Exp $
 */

#include "mawk.h"
#include "bi_vars.h"
#include "bi_funct.h"
#include "memory.h"
#include "field.h"
#include "scan.h"
#include "files.h"
#include "init.h"

static void write_error(void);

/* this can be moved and enlarged  by -W sprintf=num  */
char *sprintf_buff = string_buff;
char *sprintf_limit = string_buff + sizeof(string_buff);

/* Once execute() starts the sprintf code is (belatedly) the only
   code allowed to use string_buff  */

static void
print_cell(CELL *p, FILE *fp)
{
    size_t len;

    switch (p->type) {
    case C_NOINIT:
	break;
    case C_MBSTRN:
    case C_STRING:
    case C_STRNUM:
	switch (len = string(p)->len) {
	case 0:
	    break;
	case 1:
	    putc(string(p)->str[0], fp);
	    break;

	default:
	    fwrite(string(p)->str, (size_t) 1, len, fp);
	}
	break;

    case C_DOUBLE:
	{
	    Int ival = d_to_I(p->dval);

	    /* integers print as "%[l]d" */
	    if ((double) ival == p->dval)
		fprintf(fp, INT_FMT, ival);
	    else
		fprintf(fp, string(OFMT)->str, p->dval);
	}
	break;

    default:
	bozo("bad cell passed to print_cell");
    }
}

/* on entry to bi_print or bi_printf the stack is:

   sp[0] = an integer k
       if ( k < 0 )  output is to a file with name in sp[-1]
       { so open file and sp -= 2 }

   sp[0] = k >= 0 is the number of print args
   sp[-k]   holds the first argument
*/

CELL *
bi_print(
	    CELL *sp)		/* stack ptr passed in */
{
    register CELL *p;
    register int k;
    FILE *fp;

    k = sp->type;
    if (k < 0) {
	/* k holds redirection */
	if ((--sp)->type < C_STRING)
	    cast1_to_s(sp);
	fp = (FILE *) file_find(string(sp), k);
	free_STRING(string(sp));
	k = (--sp)->type;
	/* k now has number of arguments */
    } else
	fp = stdout;

    if (k) {
	p = sp - k;		/* clear k variables off the stack */
	sp = p - 1;
	k--;

	while (k > 0) {
	    print_cell(p, fp);
	    print_cell(OFS, fp);
	    cell_destroy(p);
	    p++;
	    k--;
	}

	print_cell(p, fp);
	cell_destroy(p);
    } else {			/* print $0 */
	sp--;
	print_cell(&field[0], fp);
    }

    print_cell(ORS, fp);
    if (ferror(fp))
	write_error();
    return sp;
}

/*---------- types and defs for doing printf and sprintf----*/
typedef enum {
    PF_C = 0,			/* %c */
    PF_S,			/* %s */
    PF_D,			/* int conversion */
    PF_F,			/* float conversion */
    PF_U,			/* unsigned conversion */
    PF_last
} PF_enum;

/* for switch on number of '*' and type */
#define	 AST(num,type)	((PF_last)*(num)+(type))

/* some picky ANSI compilers go berserk without this */
typedef int (*PRINTER) (PTR, const char *,...);

/*-------------------------------------------------------*/

static void
bad_conversion(int cnt, const char *who, const char *format)
{
    rt_error("improper conversion(number %d) in %s(\"%s\")",
	     cnt, who, format);
}

typedef enum {
    sfmtSPACE = 1,
    sfmtMINUS = 2,
    sfmtZEROS = 4,
    sfmtWIDTH = 8,
    sfmtPREC = 16
} enumSFMT;

/*
 * nawk pads leading 0's if it sees a format such as "%06s".
 * C's printf/sprintf do not do that.
 * So we decode the format and interpret it according to nawk.
 */
static int
make_sfmt(const char *format,
	  int *fill_in,
	  int *width,
	  int *prec,
	  int *flags)
{
    int ch;
    int parts = 0;
    int digits = 0;
    int value = 0;
    int used = 0;
    int success = 1;
    int ok_flag = 1;

    *width = 0;
    *prec = 0;
    *flags = 0;

    ++format;
    while (*format != '\0' && ok_flag) {
	switch (*format++) {
	case ' ':
	    *flags |= sfmtSPACE;
	    break;
	case '-':
	    *flags |= sfmtMINUS;
	    break;
	case '0':
	    *flags |= sfmtZEROS;
	    break;
	case '+':
	    /* FALLTHRU */
	case '#':
	    /* ignore for %s */
	    break;
	default:
	    ok_flag = 0;
	    --format;
	    break;
	}
    }

    while (*format != '\0' && success) {
	switch (ch = *format++) {
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	    if (digits < 0) {
		success = 0;
	    } else if (digits++ == 0) {
		value = (ch - '0');
	    } else {
		value = (10 * value) + (ch - '0');
	    }
	    break;
	case '.':
	    if (parts) {
		success = 0;
	    } else {
		if (digits) {
		    *width = value;
		    *flags |= sfmtWIDTH;
		    digits = 0;
		    value = 0;
		}
		parts = 1;
	    }
	    break;
	case '*':
	    if (digits == 0) {
		value = fill_in[used++];
		digits = -1;
	    } else {
		success = 0;
	    }
	    break;
	case 'c':
	    /* FALLTHRU */
	case 's':
	    if (digits) {
		if (parts) {
		    *prec = value;
		    *flags |= sfmtPREC;
		} else {
		    *width = value;
		    *flags |= sfmtWIDTH;
		}
	    }
	    if (*format != '\0')
		success = 0;
	    break;
	default:
	    success = 0;
	    break;
	}
    }

    return success;
}

#define SprintfOverflow(bfr, need) \
    if (bfr + need + 1 >= sprintf_limit) { \
	    rt_overflow("sprintf buffer", \
			(unsigned) (sprintf_limit - sprintf_buff)); \
    }

static char *
SprintfChar(char *buffer, int ch)
{
    SprintfOverflow(buffer, 1);
    *buffer++ = (char) ch;
    return buffer;
}

static char *
SprintfFill(char *buffer, int ch, int fill)
{
    SprintfOverflow(buffer, fill);
    memset(buffer, ch, (size_t) fill);
    return buffer + fill;
}

static char *
SprintfBlock(char *buffer, char *source, int length)
{
    SprintfOverflow(buffer, length);
    memcpy(buffer, source, (size_t) length);
    return buffer + length;
}

/*
 * Write the s-format text.
 */
static PTR
puts_sfmt(PTR target,
	  FILE *fp,
	  CELL *source,
	  STRING * onechr,
	  int width,
	  int prec,
	  int flags)
{
    char *src_str = onechr ? onechr->str : string(source)->str;
    int src_len = onechr ? 1 : (int) string(source)->len;

    if (width < 0) {
	width = -width;
	flags |= sfmtMINUS;
    }

    if (fp != 0) {

	if (flags & sfmtSPACE) {
	    if (src_len == 0)
		fputc(' ', fp);
	}
	if (flags & sfmtPREC) {
	    if (src_len > prec)
		src_len = prec;
	}
	if (!(flags & sfmtWIDTH)) {
	    width = src_len;
	}
	if (!(flags & sfmtMINUS)) {
	    while (src_len < width) {
		fputc(' ', fp);
		--width;
	    }
	}
	fwrite(src_str, sizeof(char), (size_t) src_len, fp);
	while (src_len < width) {
	    fputc(' ', fp);
	    --width;
	}
    } else {
	char *buffer = (char *) target;		/* points into sprintf_buff */

	if (flags & sfmtSPACE) {
	    if (src_len == 0) {
		buffer = SprintfChar(buffer, ' ');
	    }
	}
	if (flags & sfmtPREC) {
	    if (src_len > prec)
		src_len = prec;
	}
	if (!(flags & sfmtWIDTH)) {
	    width = src_len;
	}
	if (!(flags & sfmtMINUS)) {
	    if (src_len < width) {
		buffer = SprintfFill(buffer, ' ', width - src_len);
		width = src_len;
	    }
	}
	buffer = SprintfBlock(buffer, src_str, src_len);
	if (src_len < width) {
	    buffer = SprintfFill(buffer, ' ', width - src_len);
	}
	target = buffer;
    }
    return target;
}

/*
 * Note: caller must do CELL cleanup.
 * The format parameter is modified, but restored.
 *
 * This routine does both printf and sprintf (if fp==0)
 */
static STRING *
do_printf(
	     FILE *fp,
	     char *format,
	     unsigned argcnt,	/* number of args on eval stack */
	     CELL *cp)		/* ptr to an array of arguments
				   (on the eval stack) */
{
    char save;
    char *p;
    register char *q = format;
    register char *target;
    int l_flag, h_flag;		/* seen %ld or %hd  */
    int ast_cnt;
    int ast[2];
    UInt Uval = 0;
    Int Ival = 0;
    int sfmt_width, sfmt_prec, sfmt_flags, s_format;
    int num_conversion = 0;	/* for error messages */
    const char *who;		/*ditto */
    int pf_type = 0;		/* conversion type */
    PRINTER printer;		/* pts at fprintf() or sprintf() */
    STRING onechr;

#ifdef	 SHORT_INTS
    char xbuff[256];		/* splice in l qualifier here */
#endif

    if (fp == (FILE *) 0) {	/* doing sprintf */
	target = sprintf_buff;
	printer = (PRINTER) sprintf;
	who = "sprintf";
    } else {			/* doing printf */
	target = (char *) fp;	/* will never change */
	printer = (PRINTER) fprintf;
	who = "printf";
    }

    while (1) {
	if (fp) {		/* printf */
	    while (*q != '%') {
		if (*q == 0) {
		    if (ferror(fp))
			write_error();
		    /* return is ignored */
		    return (STRING *) 0;
		} else {
		    putc(*q, fp);
		    q++;
		}
	    }
	} else {		/* sprintf */
	    while (*q != '%') {
		if (*q == 0) {
		    if (target > sprintf_limit)		/* damaged */
		    {
			/* hope this works */
			rt_overflow("sprintf buffer",
				    (unsigned) (sprintf_limit - sprintf_buff));
		    } else {	/* really done */
			return new_STRING1(sprintf_buff,
					   (size_t) (target - sprintf_buff));
		    }
		} else {
		    *target++ = *q++;
		}
	    }
	}

	/* *q == '%' */
	num_conversion++;

	if (*++q == '%') {	/* %% */
	    if (fp)
		putc(*q, fp);
	    else
		*target++ = *q;

	    q++;
	    continue;
	}

	/* mark the '%' with p */
	p = q - 1;

	/* eat the flags */
	while (*q == '-' || *q == '+' || *q == ' ' ||
	       *q == '#' || *q == '0' || *q == '\'')
	    q++;

	ast_cnt = 0;
	ast[0] = 0;
	if (*q == '*') {
	    if (cp->type != C_DOUBLE)
		cast1_to_d(cp);
	    ast[ast_cnt++] = d_to_i(cp++->dval);
	    argcnt--;
	    q++;
	} else
	    while (scan_code[*(unsigned char *) q] == SC_DIGIT)
		q++;
	/* width is done */

	if (*q == '.') {	/* have precision */
	    q++;
	    if (*q == '*') {
		if (cp->type != C_DOUBLE)
		    cast1_to_d(cp);
		ast[ast_cnt++] = d_to_i(cp++->dval);
		argcnt--;
		q++;
	    } else {
		while (scan_code[*(unsigned char *) q] == SC_DIGIT)
		    q++;
	    }
	}

	if ((int) argcnt <= 0)
	    rt_error("not enough arguments passed to %s(\"%s\")",
		     who, format);

	l_flag = h_flag = 0;

	if (*q == 'l') {
	    q++;
	    l_flag = 1;
	} else if (*q == 'h') {
	    q++;
	    h_flag = 1;
	}
	switch (*q++) {
	case 's':
	    if (l_flag + h_flag)
		bad_conversion(num_conversion, who, format);
	    if (cp->type < C_STRING)
		cast1_to_s(cp);
	    pf_type = PF_S;
	    break;

	case 'c':
	    if (l_flag + h_flag)
		bad_conversion(num_conversion, who, format);

	    switch (cp->type) {
	    case C_NOINIT:
		Ival = 0;
		break;

	    case C_STRNUM:
	    case C_DOUBLE:
		Ival = d_to_I(cp->dval);
		break;

	    case C_STRING:
		Ival = string(cp)->str[0];
		break;

	    case C_MBSTRN:
		check_strnum(cp);
		Ival = ((cp->type == C_STRING)
			? string(cp)->str[0]
			: d_to_I(cp->dval));
		break;

	    default:
		bozo("printf %c");
	    }
	    onechr.len = 1;
	    onechr.str[0] = (char) Ival;

	    pf_type = PF_C;
	    break;

	case 'd':
	case 'i':
	    if (cp->type != C_DOUBLE)
		cast1_to_d(cp);
	    Ival = d_to_I(cp->dval);
	    pf_type = PF_D;
	    break;

	case 'o':
	case 'x':
	case 'X':
	case 'u':
	    if (cp->type != C_DOUBLE)
		cast1_to_d(cp);
	    Uval = d_to_U(cp->dval);
	    pf_type = PF_U;
	    break;

	case 'e':
	case 'g':
	case 'f':
	case 'E':
	case 'G':
	    if (h_flag + l_flag)
		bad_conversion(num_conversion, who, format);
	    if (cp->type != C_DOUBLE)
		cast1_to_d(cp);
	    pf_type = PF_F;
	    break;

	default:
	    bad_conversion(num_conversion, who, format);
	}

	save = *q;
	*q = 0;

#ifdef	SHORT_INTS
	if (pf_type == PF_D) {
	    /* need to splice in long modifier */
	    strcpy(xbuff, p);

	    if (l_flag) /* do nothing */ ;
	    else {
		int k = q - p;

		if (h_flag) {
		    Ival = (short) Ival;
		    /* replace the 'h' with 'l' (really!) */
		    xbuff[k - 2] = 'l';
		    if (xbuff[k - 1] != 'd' && xbuff[k - 1] != 'i')
			Ival &= 0xffff;
		} else {
		    /* the usual case */
		    xbuff[k] = xbuff[k - 1];
		    xbuff[k - 1] = 'l';
		    xbuff[k + 1] = 0;
		}
	    }
	}
#endif

#define PUTS_C_ARGS target, fp, 0,  &onechr, sfmt_width, sfmt_prec, sfmt_flags
#define PUTS_S_ARGS target, fp, cp, 0,       sfmt_width, sfmt_prec, sfmt_flags

	/* ready to call printf() */
	s_format = 0;
	switch (AST(ast_cnt, pf_type)) {
	case AST(0, PF_C):
	    /* FALLTHRU */
	case AST(1, PF_C):
	    /* FALLTHRU */
	case AST(2, PF_C):
	    s_format = 1;
	    make_sfmt(p, ast, &sfmt_width, &sfmt_prec, &sfmt_flags);
	    target = puts_sfmt(PUTS_C_ARGS);
	    break;

	case AST(0, PF_S):
	    /* FALLTHRU */
	case AST(1, PF_S):
	    /* FALLTHRU */
	case AST(2, PF_S):
	    s_format = 1;
	    make_sfmt(p, ast, &sfmt_width, &sfmt_prec, &sfmt_flags);
	    target = puts_sfmt(PUTS_S_ARGS);
	    break;

#ifdef	SHORT_INTS
#define FMT	xbuff		/* format in xbuff */
#else
#define FMT	p		/* p -> format */
#endif
	case AST(0, PF_D):
	    (*printer) ((PTR) target, FMT, Ival);
	    break;

	case AST(1, PF_D):
	    (*printer) ((PTR) target, FMT, ast[0], Ival);
	    break;

	case AST(2, PF_D):
	    (*printer) ((PTR) target, FMT, ast[0], ast[1], Ival);
	    break;

	case AST(0, PF_U):
	    (*printer) ((PTR) target, FMT, Uval);
	    break;

	case AST(1, PF_U):
	    (*printer) ((PTR) target, FMT, ast[0], Uval);
	    break;

	case AST(2, PF_U):
	    (*printer) ((PTR) target, FMT, ast[0], ast[1], Uval);
	    break;

#undef	FMT

	case AST(0, PF_F):
	    (*printer) ((PTR) target, p, cp->dval);
	    break;

	case AST(1, PF_F):
	    (*printer) ((PTR) target, p, ast[0], cp->dval);
	    break;

	case AST(2, PF_F):
	    (*printer) ((PTR) target, p, ast[0], ast[1], cp->dval);
	    break;
	}
	if (fp == (FILE *) 0 && !s_format) {
	    while (*target)
		target++;
	}
	*q = save;
	argcnt--;
	cp++;
    }
}

CELL *
bi_printf(CELL *sp)
{
    register int k;
    register CELL *p;
    FILE *fp;

    TRACE_FUNC("bi_printf", sp);

    k = sp->type;
    if (k < 0) {
	/* k has redirection */
	if ((--sp)->type < C_STRING)
	    cast1_to_s(sp);
	fp = (FILE *) file_find(string(sp), k);
	free_STRING(string(sp));
	k = (--sp)->type;
	/* k is now number of args including format */
    } else
	fp = stdout;

    sp -= k;			/* sp points at the format string */
    k--;

    if (sp->type < C_STRING)
	cast1_to_s(sp);
    do_printf(fp, string(sp)->str, (unsigned) k, sp + 1);
    free_STRING(string(sp));

    /* cleanup arguments on eval stack */
    for (p = sp + 1; k; k--, p++)
	cell_destroy(p);
    return --sp;
}

CELL *
bi_sprintf(CELL *sp)
{
    CELL *p;
    int argcnt = sp->type;
    STRING *sval;

    TRACE_FUNC("bi_sprintf", sp);

    sp -= argcnt;		/* sp points at the format string */
    argcnt--;

    if (sp->type != C_STRING)
	cast1_to_s(sp);
    sval = do_printf((FILE *) 0, string(sp)->str, (unsigned) argcnt, sp + 1);
    free_STRING(string(sp));
    sp->ptr = (PTR) sval;

    /* cleanup */
    for (p = sp + 1; argcnt; argcnt--, p++)
	cell_destroy(p);

    return sp;
}

static void
write_error(void)
{
    errmsg(errno, "write failure");
    mawk_exit(2);
}
