
/********************************************
print.c

libmawk changes (C) 2009-2010, Tibor 'Igor2' Palinkas;
based on mawk code coming with the below copyright:

copyright 1991-1993.  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.
********************************************/

#include "mawk.h"
#include <stdio.h>
#include <stdarg.h>
#include "bi_vars.h"
#include "bi_funct.h"
#include "memory.h"
#include "field.h"
#include "scan.h"
#include "files.h"
#include "vio.h"
#include "cell.h"

static mawk_string_t *do_printf(mawk_state_t *, FILE_NODE *, char *, unsigned, mawk_cell_t *);
static void bad_conversion(mawk_state_t *, int, char *, char *);
static void write_error(mawk_state_t * MAWK);

/* prototyping fprintf() or sprintf() is a loser as ellipses will
   always cause problems with ansi compilers depending on what
   they've already seen,
   but we need them here and sometimes they are missing
*/

#ifdef NO_FPRINTF_IN_STDIO
int fprintf(FILE *, const char *, ...);
#endif
#ifdef NO_SPRINTF_IN_STDIO
int sprintf(char *, const char *, ...);
#endif

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

void mawk_print_cell(mawk_state_t *MAWK, register mawk_cell_t *p, register FILE_NODE *fnode)
{
	int 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:
			mawk_vio_putc(MAWK, fnode->vf, string(p)->str[0]);
			break;

		default:
			mawk_vio_write(MAWK, fnode->vf, string(p)->str, len);
		}
		break;

	case C_NUM:
		{
			Int ival = mawk_d_to_I(p->d.dval);
			const char *txt;

			if (P_isnan(p->d.dval)) {
				mawk_vio_write_str(MAWK, fnode->vf, "nan");
				break;
			}

			txt = mawk_num_print_spec(ival);
			if (txt != NULL)
				mawk_vio_write_str(MAWK, fnode->vf, txt);
			else if ((mawk_num_t) ival == p->d.dval) { /* integers print as "%[l]d" */
				char buff[64];
				int len;
				len = sprintf(buff, INT_FMT, ival);
				mawk_vio_write(MAWK, fnode->vf, buff, len);
			}
			else {
				fnode->vf->imp->vprintf(MAWK, fnode->vf, string(MAWK_OFMT)->str, p->d.dval);
			}
		}
		break;

	default:
		mawk_bozo(MAWK, "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
*/

mawk_cell_t *mawk_bi_print(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	/* sp is stack ptr passed in */
	register mawk_cell_t *p;
	register int k;
	FILE_NODE *fnode;

	k = sp->type;
	if (k < 0) {
		/* k holds redirection */
		if ((--sp)->type < C_STRING)
			mawk_cast1_to_str(MAWK, sp);
		fnode =  mawk_file_find(MAWK, string(sp), k, 1);
		free_STRING(string(sp));
		k = (--sp)->type;
		/* k now has number of arguments */
	}
	else
		fnode = MAWK->fnode_stdout;

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

		while (k > 0) {
			if (fnode != NULL) {
				mawk_print_cell(MAWK, p, fnode);
				mawk_print_cell(MAWK, OFS, fnode);
			}
			mawk_cell_destroy(MAWK, p);
			p++;
			k--;
		}

		if (fnode != NULL)
			mawk_print_cell(MAWK, p, fnode);
		mawk_cell_destroy(MAWK, p);
	}
	else {												/* print $0 */
		sp--;
		if (fnode != NULL)
			mawk_print_cell(MAWK, &MAWK->field[0], fnode);
	}

	if (fnode != NULL) {
		mawk_print_cell(MAWK, ORS, fnode);
		if (mawk_vio_error(MAWK, fnode->vf))
			write_error(MAWK);
	}
	return sp;
}

/*---------- types and defs for doing printf and sprintf----*/
#define	 PF_C		0								/* %c */
#define	 PF_S		1								/* %s */
#define	 PF_D		2								/* int conversion */
#define	 PF_F		3								/* float conversion */

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

typedef int (*PRINTER) (PTR, const char *, ...);
/*-------------------------------------------------------*/

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


int sprintf_wrapper(mawk_state_t *MAWK, char *ostr, const char *fmt, ...)
{
	int ret;
	va_list ap;
	va_start(ap, fmt);
	ret = vsprintf(ostr, fmt, ap);
	va_end(ap);
	return ret;
}


/* the contents of format are preserved,
   caller does mawk_cell_t cleanup

   This routine does both printf and sprintf (if fp==0)
*/
static mawk_string_t *do_printf(mawk_state_t *MAWK, FILE_NODE *fnode, char *format, unsigned argcnt, mawk_cell_t *cp)
{
	/* argcnt            number of args on eval stack */
	/* mawk_cell_t *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];
	Int Ival;
	int num_conversion = 0;				/* for error messages */
	char *who;										/*ditto */
	int pf_type;									/* conversion type */
	PRINTER printer;							/* pts at fprintf() or sprintf() */

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

	if (fnode == NULL) {				/* doing sprintf */
		target = MAWK->sprintf_buff;
		printer = (PRINTER) sprintf_wrapper;
		who = "sprintf";
	}
	else {												/* doing printf */
		target = (char *) fnode->vf;				/* will never change */
		printer = (PRINTER) fnode->vf->imp->vprintf;
		who = "printf";
	}

	while (1) {
		if (fnode) {										/* printf */
			while (*q != '%') {
				if (*q == 0) {
					if (mawk_vio_error(MAWK, fnode->vf))
						write_error(MAWK);
					/* return is ignored */
					return (mawk_string_t *) 0;
				}
				else {
					mawk_vio_putc(MAWK, fnode->vf, *q);
					q++;
				}
			}
		}
		else {											/* sprintf */

			while (*q != '%')
				if (*q == 0) {
					if (target > MAWK->sprintf_limit) {	/* mawk_damaged */
						/* hope this works */
						mawk_rt_overflow(MAWK, "sprintf buffer", MAWK->sprintf_limit - MAWK->sprintf_buff);
					}
					else {								/* really done */

						mawk_string_t *retval;
						int len = target - MAWK->sprintf_buff;

						retval = mawk_new_STRING0(MAWK, len);
						memcpy(retval->str, MAWK->sprintf_buff, len);
						return retval;
					}
				}
				else
					*target++ = *q++;
		}


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

		if (*++q == '%') {					/* %% */
			if (fnode)
				mawk_vio_putc(MAWK, fnode->vf, *q);
			else
				*target++ = *q;

			q++;
			continue;
		}

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

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

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

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

		if (argcnt <= 0)
			mawk_rt_error(MAWK, "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(MAWK, num_conversion, who, format);
			if (cp->type < C_STRING)
				mawk_cast1_to_str(MAWK, cp);
			pf_type = PF_S;
			break;

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

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

			case C_STRNUM:
			case C_NUM:
				Ival = mawk_d_to_I(cp->d.dval);
				break;

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

			case C_MBSTRN:
				mawk_check_strnum(MAWK, cp);
				Ival = cp->type == C_STRING ? string(cp)->str[0] : mawk_d_to_I(cp->d.dval);
				break;

			default:
				mawk_bozo(MAWK, "printf %c");
			}

			pf_type = PF_C;
			break;

		case 'd':
		case 'o':
		case 'x':
		case 'X':
		case 'i':
		case 'u':
			{
				const char *txt;

				if (cp->type != C_NUM)
					mawk_cast1_to_num(MAWK, cp);
#ifdef MAWK_PRINTF_INFNAN
				txt = mawk_num_print_spec(cp->d.dval);
				if (txt == NULL) {
#endif
					Ival = mawk_d_to_I(cp->d.dval);
					pf_type = PF_D;
#ifdef MAWK_PRINTF_INFNAN
				}
				else {
					cp->ptr = (PTR) mawk_new_STRING(MAWK, txt);
					cp->type = C_STRING;
					cp->d.dval = 42;
					p = "%s";
					pf_type = PF_S;
				}
#endif
			}
			break;

#ifndef MAWK_NO_FLOAT
		case 'e':
		case 'g':
		case 'f':
		case 'E':
		case 'G':
			if (h_flag + l_flag)
				bad_conversion(MAWK, num_conversion, who, format);
			if (cp->type != C_NUM)
				mawk_cast1_to_num(MAWK, cp);
			if (P_isnan(cp->d.dval)) {
				cp->ptr = (PTR) mawk_new_STRING(MAWK, "nan");
				cp->type = C_STRING;
				cp->d.dval = 42;
				p = "%s";
				pf_type = PF_S;
			}
			else
				pf_type = PF_F;
			break;
#endif

		default:
			bad_conversion(MAWK, 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

		/* ready to call printf() */
		switch (AST(ast_cnt, pf_type)) {
		case AST(0, PF_C):
			(*printer) (MAWK, (PTR) target, p, (int) Ival);
			break;

		case AST(1, PF_C):
			(*printer) (MAWK, (PTR) target, p, ast[0], (int) Ival);
			break;

		case AST(2, PF_C):
			(*printer) (MAWK, (PTR) target, p, ast[0], ast[1], (int) Ival);
			break;

		case AST(0, PF_S):
			(*printer) (MAWK, (PTR) target, p, string(cp)->str);
			break;

		case AST(1, PF_S):
			(*printer) (MAWK, (PTR) target, p, ast[0], string(cp)->str);
			break;

		case AST(2, PF_S):
			(*printer) (MAWK, (PTR) target, p, ast[0], ast[1], string(cp)->str);
			break;

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

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

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

#undef	FMT


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

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

		case AST(2, PF_F):
			(*printer) (MAWK, (PTR) target, p, ast[0], ast[1], cp->d.dval);
			break;
		}
		if (fnode == NULL)
			while (*target)
				target++;
		*q = save;
		argcnt--;
		cp++;
	}
}

mawk_cell_t *mawk_bi_printf(mawk_state_t *MAWK, register mawk_cell_t *sp)
{
	register int k;
	register mawk_cell_t *p;
	FILE_NODE *fnode;

	k = sp->type;
	if (k < 0) {
		/* k has redirection */
		if ((--sp)->type < C_STRING)
			mawk_cast1_to_str(MAWK, sp);
		fnode = mawk_file_find(MAWK, string(sp), k, 1);
		free_STRING(string(sp));
		k = (--sp)->type;
		/* k is now number of args including format */
	}
	else
		fnode = MAWK->fnode_stdout;

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

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	do_printf(MAWK, fnode, string(sp)->str, k, sp + 1);
	free_STRING(string(sp));

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

mawk_cell_t *mawk_bi_sprintf(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_cell_t *p;
	int argcnt = sp->type;
	mawk_string_t *sval;

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

	if (sp->type != C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	sval = do_printf(MAWK, NULL, string(sp)->str, argcnt, sp + 1);
	free_STRING(string(sp));
	sp->ptr = (PTR) sval;

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

	return sp;
}


static void write_error(mawk_state_t * MAWK)
{
	mawk_errmsg(MAWK, errno, "write failure");
	mawk_exit(MAWK, 2);
}
