/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2005-2018. All Rights Reserved.
 * 
 * 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.
 * 
 * %CopyrightEnd%
 */

/*
 * fmt:
 *    '%' <flag>* [ <width> [.<precision>]][<length>]<conversion>
 *
 *  flag: # | O | - | <sp> | + | ' | I
 *  width:      [0-9]+ | '*' 
 *  precision:  [0-9]+ | '*' 
 *  length: hh | h | l | ll | L | j | t | b<sz>
 *  conversion: d,i | o,u,x,X | e,E | f,F | g,G | a,A | c | s | T | R |
 *              p | n | %
 *  sz: 8 | 16 | 32 | 64 | p | e
 */

/* Without this, variable argument lists break on VxWorks */
#ifdef VXWORKS
#include <vxWorks.h>
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef __WIN32__
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <ctype.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "erl_errno.h"
#include <limits.h>
#include "erl_printf.h"
#include "erl_printf_format.h"

#ifdef DEBUG
#include <assert.h>
#define ASSERT(X) assert(X)
#else
#define ASSERT(X) 
#endif

#ifdef __WIN32__
#define long_long		LONGLONG
#define signed_long_long	LONGLONG
#define unsigned_long_long	ULONGLONG
#undef SIZEOF_LONG_LONG
#define SIZEOF_LONG_LONG 8
#else
#if SIZEOF_LONG_LONG
#define long_long		long long
#define signed_long_long	signed long long
#define unsigned_long_long	unsigned long long
#endif
#endif

#ifndef ERTS_SIZEOF_ETERM
#define ERTS_SIZEOF_ETERM SIZEOF_VOID_P
#endif

#if defined(__GNUC__)
#  undef inline
#  define inline __inline__
#elif defined(__WIN32__)
#  undef inline
#  define inline __forceinline
#else
#  ifndef inline
#    define inline
#  endif
#endif

#define FMTC_d    0x0000
/*empty           0x0001 was RELATIVE */
#define FMTC_o    0x0002
#define FMTC_u    0x0003
#define FMTC_x    0x0004
#define FMTC_X    0x0005
#define FMTC_e    0x0006
#define FMTC_E    0x0007
#define FMTC_f    0x0008
#define FMTC_T    0x0009
#define FMTC_g    0x000a
#define FMTC_G    0x000b
#define FMTC_c    0x000c
#define FMTC_s    0x000d
#define FMTC_p    0x000e
#define FMTC_n    0x000f
#define FMTC_MASK 0x000f

#define FMTL_no   0x0000
#define FMTL_hh   0x0010
#define FMTL_h    0x0020
#define FMTL_l    0x0030
#define FMTL_ll   0x0040
#define FMTL_L    0x0050
#define FMTL_j    0x0060
#define FMTL_t    0x0070
#define FMTL_MASK 0x00f0

#define FMTF_alt  0x0100    /* # alterlate form ie 0x */
#define FMTF_pad  0x0200    /* 0 zero pad */
#define FMTF_adj  0x0400    /* left adjust */
#define FMTF_blk  0x0800    /* add blank */
#define FMTF_sgn  0x1000    /* add sign */
#define FMTF_cnv  0x2000    /* decimal conversion */
#define FMTF_cnV  0x4000    /* alternate decimal conversion */
#define FMTF_MASK 0x7f00


static char zeros[]  = "00000000000000000000000000000000";
static char blanks[] = "                                ";
static char hex[] = "0123456789abcdef";
static char heX[] = "0123456789ABCDEF";

#define FMT(fn,arg,buf,len,count) do { \
    int res__ = (fn)((arg),(buf),(len)); \
    if (res__ < 0) \
	return res__; \
    (count) += (len); \
 } while(0)

#define FILL(fn,arg,cs,len, count) do { \
    int __i = (len); \
    while(__i >= sizeof(cs)-1) { \
        FMT((fn),(arg),(cs),sizeof(cs)-1,(count)); \
        __i -= sizeof(cs)-1; \
    } \
    if (__i) FMT((fn),(arg),(cs),__i,(count)); \
 } while(0)

#define BLANKS(fn,arg,n,count) FILL((fn),(arg),blanks,(n),count)
#define ZEROS(fn,arg,n,count)  FILL((fn),(arg),zeros,(n),count)

#define SIGN(X) ((X) > 0 ? 1 : ((X) < 0 ? -1 : 0)) 
#define USIGN(X) ((X) == 0 ? 0 : 1)

int (*erts_printf_eterm_func)(fmtfn_t, void*, ErlPfEterm, long) = NULL;

static int
noop_fn(void *vfp, char* buf, size_t len)
{
    return 0;
}

static int fmt_fld(fmtfn_t fn,void* arg,
		   char* wbuf, int w, int sign,
		   int width,int precision,int fmt,int* count)
{
    char prefix[8];
    char* pp = prefix;
    int pw   = 0;
    int len;

    /* format the prefix */
    if ((sign || (fmt & (FMTF_sgn|FMTF_blk)))
	&& (fmt & FMTC_MASK) == FMTC_d) {
	if (sign < 0)
	    *pp++ = '-';
	else if ((fmt & FMTF_sgn))
	    *pp++ = '+';
	else if (fmt & FMTF_blk)
	    *pp++ = ' ';
    }

    if ((fmt & FMTF_alt)) {
	switch((fmt & FMTC_MASK)) {
	case FMTC_X: *pp++ = '0'; *pp++ = 'X'; break;
	case FMTC_x: *pp++ = '0'; *pp++ = 'x'; break;
	case FMTC_o: *pp++ = '0'; if (precision>1) precision--; break;
	}
    }

    pw = pp-prefix;
    len = ((w < precision) ? precision : w) + pw;

    if (fmt & FMTF_adj) { /* left adjust */
	if (pw)
	    FMT(fn,arg,prefix,pw,*count);
	if (w < precision)
	    ZEROS(fn,arg,precision-w,*count);
	FMT(fn,arg, wbuf, w, *count);
	if (len < width)
	    BLANKS(fn,arg,width-len,*count);
    }
    else if ((fmt & FMTF_pad) && (precision<0)) { /* pad zeros */
	if (pw)
	    FMT(fn,arg, prefix, pw, *count);
	if (w < precision)
	    ZEROS(fn, arg, precision-w, *count);
	if (len < width)
	    ZEROS(fn,arg,width-len,*count);
	FMT(fn,arg,wbuf,w,*count);
    }
    else {
	if (len < width)
	    BLANKS(fn,arg,width-len,*count);
	if (pw)
	    FMT(fn,arg,prefix,pw,*count);
	if (w < precision)
	    ZEROS(fn,arg,precision-w,*count);
	FMT(fn,arg,wbuf,w,*count);
    }
    return 0;
}

static int fmt_uword(fmtfn_t fn,void* arg,int sign,ErlPfUWord uval,
		    int width,int precision,int fmt,int* count)
{
    char buf[32];
    int base = 10;
    int w    = 0;
    char* dc = hex;
    char* p = buf+sizeof(buf);

    switch(fmt & FMTC_MASK) {
    case FMTC_d:
    case FMTC_u:
	break;
    case FMTC_o: 
	base = 8;
	break;
    case FMTC_X:
	dc = heX;
    case FMTC_x:
	base = 16;
	break;
    default: 
	return -EINVAL;
    }

    /* format the unsigned value */
    if (!sign && precision) {
	*--p = '0';
	w++;
    }
    else {
	while(uval) {
	    *--p = dc[(uval % base)];
	    uval /= base;
	    w++;
	}
    }
    return fmt_fld(fn, arg, p, w, sign, width, precision, fmt, count);
}

#if SIZEOF_LONG_LONG

static inline int
do_div(unsigned_long_long *n, unsigned_long_long base)
{
    unsigned_long_long q = *n/base;
    int mod = (int) (*n - q*base);
    *n = q;
    return mod;
}

static int fmt_long_long(fmtfn_t fn,void* arg,int sign,
			 unsigned_long_long uval,
			 int width,int precision,int fmt,int* count)
{
    char buf[32];
    int base = 10;
    int w    = 0;
    char* dc = hex;
    char* p = buf+sizeof(buf);

    switch(fmt & FMTC_MASK) {
    case FMTC_d:
    case FMTC_u:
	break;
    case FMTC_o: 
	base = 8;
	break;
    case FMTC_X:
	dc = heX;
    case FMTC_x:
	base = 16;
	break;
    default: 
	return -EINVAL;
    }

    /* format the unsigned value */
    if (!sign && precision) {
	*--p = '0';
	w++;
    }
    else {
	while(uval) {
	    int m = do_div(&uval,base);
	    *--p = dc[m];
	    w++;
	}
    }
    return fmt_fld(fn, arg, p, w, sign, width, precision, fmt, count);
}

#endif /* #if SIZEOF_LONG_LONG */

static int fmt_double(fmtfn_t fn,void*arg,double val,
		      int width, int precision, int fmt,int* count)
{
    int res;
    int fi = 0;
    char format_str[8];
    char sbuf[32];
    char *bufp = sbuf;
    double dexp;
    int exp;
    size_t max_size = 2;  /* including possible sign */
    int size;
    int new_fmt = fmt;
    int fpe_was_unmasked;

    fpe_was_unmasked = erts_printf_block_fpe ? (*erts_printf_block_fpe)() : 0;

    if (val < 0.0)
	dexp = log10(-val);
    else if (val == 0.0)
	dexp = 0.0;
    else
	dexp = log10(val);
    exp = (int) dexp;

    new_fmt &= ~FMTF_sgn;
    new_fmt &= ~FMTF_blk;

    format_str[fi++] = '%';
    if (fmt & FMTF_alt)
	format_str[fi++] = '#';
    if (fmt & FMTF_sgn)
	format_str[fi++] = '+';
    else if (fmt & FMTF_blk)
	format_str[fi++] = ' ';
    format_str[fi++] = '0';
    format_str[fi++] = '.';
    format_str[fi++] = '*';
	
    switch(fmt & FMTC_MASK) {
    case FMTC_G:
	format_str[fi] = 'E';
	goto gG_common;
    case FMTC_g:
	format_str[fi] = 'e';
    gG_common:
	if (dexp < -4.0 || exp >= precision) {
	    fi++;
	    precision--;
	    if (precision < 1)
		precision = 1;
	    goto eE_common;
	}
	/* fall through ... */
    case FMTC_f:
	format_str[fi++] = 'f';
	max_size += exp > 0 ? exp : 1;
	max_size++;
	if (precision)
	    max_size += precision;
	else if (fmt & FMTF_alt)
	    max_size++;
	break;
    case FMTC_E:
	format_str[fi++] = 'E';
	goto eE_common;
    case FMTC_e:
	format_str[fi++] = 'e';
    eE_common: {
	int aexp;

	max_size += 4;
	if (precision)
	    max_size += precision;
	else if (fmt & FMTF_alt)
	    max_size++;
	aexp = exp >= 0 ? exp : -exp;
	if (aexp < 100)
	    max_size += 2;
	else {
	    while (aexp) {
		max_size++;
		aexp /= 10;
	    }
	}
	break;
    }
    default:
	res = -EINVAL;
	goto out;
    }

    format_str[fi++] = '\0';
    ASSERT(fi <= sizeof(format_str));

    max_size++; /* '\0' */

    if (max_size >= sizeof(sbuf)) {
	bufp = (char *) malloc(sizeof(char)*max_size);
	if (!bufp) {
	    res = -ENOMEM;
	    /* Make sure not to trigger free */
	    bufp = sbuf;
	    goto out;
	}
    }

    size = sprintf(bufp, format_str, precision, val);
    if (size < 0) {
	if (errno > 0)
	    res = -errno;
	else
	    res = -EIO;
	goto out;
    }

    ASSERT(max_size >= size);

    res = fmt_fld(fn, arg, bufp, size, 0, width, 0, new_fmt, count);

 out:
    if (bufp != sbuf)
	free((void *) bufp);

    if (erts_printf_unblock_fpe)
	(*erts_printf_unblock_fpe)(fpe_was_unmasked);
    return res;
}

/* strnlen doesn't exist everywhere */
static size_t my_strnlen(const char *s, size_t maxlen)
{
    size_t i = 0;
    while (i < maxlen && s[i] != '\0')
	i++;
    return i;
}

int erts_printf_format(fmtfn_t fn, void* arg, char* fmt, va_list ap)
{
    char* ptr0 = fmt;
    char* ptr = ptr0;
    int count = 0;
    int n;
    int res = 0;

    while(*ptr) {
	ErlPfUWord ul_val;
	int fmt        = 0;
	int width      = -1;
	int precision  = -1;

	if (res < 0)
	    return res;

	if (*ptr == '%') {
	    if ((n=ptr-ptr0))
		FMT(fn,arg,ptr0,n,count);
	    ptr++;

	do_flag:
	    switch(*ptr) {
	    case '#':  fmt |= FMTF_alt; ptr++; goto do_flag;
	    case '0':  fmt |= FMTF_pad; ptr++; goto do_flag;
	    case '-':  fmt |= FMTF_adj; ptr++; goto do_flag;
	    case ' ':  fmt |= FMTF_blk; ptr++; goto do_flag;
	    case '+':  fmt |= FMTF_sgn; ptr++; goto do_flag;
	    case '\'': fmt |= FMTF_cnv; ptr++; goto do_flag;
	    case 'I':  fmt |= FMTF_cnV; ptr++; goto do_flag;
	    }

	    /* width */
	    if (*ptr == '*') {
		width = va_arg(ap, int);
		ptr++;
	    }
	    else if (isdigit((int) *ptr)) {
		width = *ptr++ - '0';
		while(isdigit((int) *ptr))
		    width = 10*width + (*ptr++ - '0');
	    }

	    /* precision */
	    if (*ptr == '.') {
		ptr++;
		if (*ptr == '*') {
		    precision = va_arg(ap, int);
		    ptr++;
		}
		else if (isdigit((int) *ptr)) {
		    precision = *ptr++ - '0';
		    while(isdigit((int) *ptr))
			precision = 10*precision + (*ptr++ - '0');
		}
	    }

	    /* length modifier */
	    switch(*ptr) {
	    case 'b': {
		ptr++;
		if (*ptr == 'p') {
		    ptr++;
#if SIZEOF_INT == SIZEOF_VOID_P
#elif SIZEOF_LONG == SIZEOF_VOID_P
		    fmt |= FMTL_l;
#elif SIZEOF_LONG_LONG == SIZEOF_VOID_P
		    fmt |= FMTL_ll;
#else
#error No integer datatype with the same size as 'void *' found
#endif
		}
		else if (*ptr == 'e') {
		    ptr++;
#if SIZEOF_INT == ERTS_SIZEOF_ETERM
#elif SIZEOF_LONG == ERTS_SIZEOF_ETERM
		    fmt |= FMTL_l;
#elif SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM
		    fmt |= FMTL_ll;
#else
#error No integer datatype with the same size as Eterm found
#endif
		}
		else {
		    int bits = 0;
		    while(isdigit((int) *ptr))
			bits = 10*bits + (*ptr++ - '0');
		    switch (bits) {
		    case 64:
#if SIZEOF_INT == 8
#elif SIZEOF_LONG == 8
			fmt |= FMTL_l;
#elif SIZEOF_LONG_LONG == 8
			fmt |= FMTL_ll;
#else
#error No 64-bit integer datatype found
#endif
			break;
		    case 32:
#if SIZEOF_INT == 4
#elif SIZEOF_SHORT == 4
			fmt |= FMTL_h;
#elif SIZEOF_LONG == 4
			fmt |= FMTL_l;
#elif SIZEOF_LONG_LONG == 4
			fmt |= FMTL_ll;
#else
#error No 32-bit integer datatype found
#endif
			break;
		    case 16:
#if SIZEOF_INT == 2
#elif SIZEOF_SHORT == 2
			fmt |= FMTL_h;
#elif SIZEOF_LONG == 2
			fmt |= FMTL_l;
#else
#error No 16-bit integer datatype found
#endif
		    case 8:
#if SIZEOF_CHAR == 1
			fmt |= FMTL_hh;
#else
#error Unexpected size of char
#endif
			break;
		    default:
			return -EINVAL;
		    }
		}
		break;
	    }
	    case 'h': 
		ptr++;
		if (*ptr == 'h') {
		    ptr++;
		    fmt |= FMTL_hh;
		}
		else
		    fmt |= FMTL_h;
		break;
	    case 'l':
		ptr++;
		if (*ptr == 'l') {
		    ptr++;
#if SIZEOF_LONG_LONG
		    fmt |= FMTL_ll;
#else
		    fmt |= FMTL_l;
#endif
		}
		else
		    fmt |= FMTL_l;
		break;
	    case 'L': ptr++; fmt |= FMTL_L; break;
	    case 'j': ptr++; fmt |= FMTL_j; break;
	    case 't': ptr++; fmt |= FMTL_t; break;
	    }

	    /* specifier */
	    switch(*ptr) {
	    case 'd': ptr++; fmt |= FMTC_d; break;
	    case 'i': ptr++; fmt |= FMTC_d; break;
	    case 'o': ptr++; fmt |= FMTC_o; break;
	    case 'u': ptr++; fmt |= FMTC_u; break;
	    case 'x': ptr++; fmt |= FMTC_x; break;
	    case 'X': ptr++; fmt |= FMTC_X; break;
	    case 'e': ptr++; fmt |= FMTC_e; break;
	    case 'E': ptr++; fmt |= FMTC_E; break;
	    case 'f': ptr++; fmt |= FMTC_f; break;
	    case 'g': ptr++; fmt |= FMTC_g; break;
	    case 'G': ptr++; fmt |= FMTC_G; break;
	    case 'c': ptr++; fmt |= FMTC_c; break;
	    case 's': ptr++; fmt |= FMTC_s; break;
	    case 'p': ptr++; fmt |= FMTC_p; break;
	    case 'n': ptr++; fmt |= FMTC_n; break;
	    case 'T': ptr++; fmt |= FMTC_T; break;
	    case '%':
		FMT(fn,arg,ptr,1,count);
		ptr++;
		ptr0 = ptr;
		continue;
	    default:
		/* ignore */
		ptr0 = ptr;
		continue;
	    }

	    switch(fmt & FMTC_MASK) {
	    case FMTC_d:
		switch(fmt & FMTL_MASK) {
		case FMTL_hh: {
		    signed char tval = (signed char) va_arg(ap,int);
		    ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval);
		    res = fmt_uword(fn,arg,SIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		case FMTL_h: {
		    signed short tval = (signed short) va_arg(ap,int);
		    ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval);
		    res = fmt_uword(fn,arg,SIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		case FMTL_l: {
		    signed long tval = (signed long) va_arg(ap,long);
		    ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval);
		    res = fmt_uword(fn,arg,SIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
#if SIZEOF_LONG_LONG
		case FMTL_ll: {
		    unsigned_long_long ull_val;
		    signed_long_long tval;
		    tval = (signed_long_long) va_arg(ap,long_long);
		    ull_val = (unsigned_long_long) (tval < 0 ? (-tval) : tval);
		    res = fmt_long_long(fn,arg,SIGN(tval),ull_val,
					width,precision,fmt,&count);
		    break;
		}
#endif
		default: {
		    signed int tval = (signed int) va_arg(ap,int);
		    ul_val = (ErlPfUWord) (tval < 0 ? (-tval) : tval);
		    res = fmt_uword(fn,arg,SIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		}
		break;
	    case FMTC_o:
	    case FMTC_u:
	    case FMTC_x:
	    case FMTC_X:
		switch(fmt & FMTL_MASK) {
		case FMTL_hh: {
		    unsigned char tval = (unsigned char) va_arg(ap,int);
		    ul_val = (ErlPfUWord) tval;
		    res = fmt_uword(fn,arg,USIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		case FMTL_h: {
		    unsigned short tval = (unsigned short) va_arg(ap,int);
		    ul_val = (ErlPfUWord) tval;
		    res = fmt_uword(fn,arg,USIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		case FMTL_l: {
		    ul_val = (ErlPfUWord) va_arg(ap,long);
		    res = fmt_uword(fn,arg,USIGN(ul_val),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
#if SIZEOF_LONG_LONG
		case FMTL_ll: {
		    unsigned_long_long ull_val;
		    ull_val = (signed_long_long) va_arg(ap,long_long);
		    res = fmt_long_long(fn,arg,USIGN(ull_val),ull_val,
					width,precision,fmt,&count);
		    break;
		}
#endif
		default: {
		    unsigned int tval = (unsigned int) va_arg(ap,int);
		    ul_val = (ErlPfUWord) tval;
		    res = fmt_uword(fn,arg,USIGN(tval),ul_val,
				   width,precision,fmt,&count);
		    break;
		}
		}
		break;
	    case FMTC_e:
	    case FMTC_E:
	    case FMTC_f:
	    case FMTC_g:
	    case FMTC_G:
		if (precision < 0)
		    precision = 6;
		switch(fmt & FMTL_MASK) {
		case FMTL_L:
		    return -EINVAL;
		    break;
		default:
		    res = fmt_double(fn,arg,va_arg(ap,double),
				     width,precision,fmt,&count);
		    break;
		}
		break;

	    case FMTC_c: {
		/* fixme: add wide char support l-modifier */
		char c = va_arg(ap,int);
		int len = 1;
		if (precision == 0)
		    len = 0;
		if (width > 0 && !(fmt & FMTF_adj)) {
		    if (width > len)
			BLANKS(fn, arg, width - len, count);
		}
		if (len)
		    FMT(fn,arg,&c,len,count);
		if (width > len && fmt & FMTF_adj)
		    BLANKS(fn, arg, width - len, count);
		break;
	    }
		
	    case FMTC_s: {
		char* str = va_arg(ap,char*);
		int len = (precision >= 0) ? my_strnlen(str,precision) : strlen(str);
		if (width > 0 && !(fmt & FMTF_adj)) {
		    if (width > len)
			BLANKS(fn, arg, width - len, count);
		}
		if (len)
		    FMT(fn,arg,str,len,count);
		if (width > len && fmt & FMTF_adj)
		    BLANKS(fn, arg, width - len, count);
		break;
	    }

	    case FMTC_p: {
		void* addr = va_arg(ap, void*);

		res = fmt_uword(fn,
			       arg,
			       USIGN((ErlPfUWord) addr),
			       (ErlPfUWord) addr,
			       width < 0 ? ((int) 2*sizeof(void *)) : width,
			       (precision < 0
				? ((int) 2*sizeof(void *))
				: precision),
			       FMTC_x|FMTF_pad|FMTF_alt,
			       &count);
		break;
	    }

	    case FMTC_n:
		switch(fmt & FMTL_MASK) {
		case FMTL_hh: *va_arg(ap,char*) = count; break;
		case FMTL_h:  *va_arg(ap,short*) = count; break;
		case FMTL_l:  *va_arg(ap,long*) = count; break;
#if SIZEOF_LONG_LONG
		case FMTL_ll: *va_arg(ap,long_long*) = count; break;
#endif
		default: *va_arg(ap,int*) = count; break;
		}
		break;
	    case FMTC_T: {    /* Eterm */
		long prec;
		ErlPfEterm eterm;
		
		if (!erts_printf_eterm_func)
		    return -EINVAL;
		if (precision < 0)
		    prec = 100000;
		else if (precision == INT_MAX)
		    prec = LONG_MAX;
		else
		    prec = (long) precision;
		eterm = va_arg(ap, ErlPfEterm);
		if (width > 0 && !(fmt & FMTF_adj)) {
		    res = (*erts_printf_eterm_func)(noop_fn, NULL, eterm, prec);
		    if (res < 0)
			return res;
		    if (width > res)
			BLANKS(fn, arg, width - res, count);
		}
		res = (*erts_printf_eterm_func)(fn, arg, eterm, prec);
		if (res < 0)
		    return res;
		count += res;
		if (width > res && fmt & FMTF_adj)
		    BLANKS(fn, arg, width - res, count);
		break;
	    }
	    default:
		if ((n=ptr-ptr0))
		    FMT(fn,arg,ptr0,n,count);
	    }
	    ptr0 = ptr;
	}
	else 
	    ptr++;
    }
    
    if ((n=ptr-ptr0))
	FMT(fn,arg,ptr0,n,count);
    return count;
}


int
erts_printf_char(fmtfn_t fn, void *arg, char c)
{
    return (*fn)(arg, &c, 1);
}

int
erts_printf_string(fmtfn_t fn, void *arg, char *str)
{
    size_t sz = strlen(str);
    return (*fn)(arg, str, sz);
}

int
erts_printf_buf(fmtfn_t fn, void *arg, char *buf, size_t sz)
{
    return (*fn)(arg, buf, sz);
}

int
erts_printf_pointer(fmtfn_t fn, void *arg, void *ptr)
{
    int count = 0;
    int res = fmt_uword(fn, arg, USIGN((ErlPfUWord) ptr),
		       (ErlPfUWord) ptr, 2*sizeof(void *),
		       2*sizeof(void *), FMTC_x|FMTF_pad|FMTF_alt, &count);
    if (res < 0)
	return res;
    return count;
}

int
erts_printf_uword(fmtfn_t fn, void *arg, char conv, int pad, int width,
		  ErlPfUWord val)
{
    int count = 0;
    int res;
    int fmt = 0;
    int prec = -1;
    switch (conv) {
    case 'o': fmt |= FMTC_o; break;
    case 'u': fmt |= FMTC_u; break;
    case 'x': fmt |= FMTC_x; break;
    case 'X': fmt |= FMTC_X; break;
    case 'p': fmt |= FMTC_p; break;
    default:
	return -EINVAL;
    }
    if (pad)
	prec = width;
    res = fmt_uword(fn, arg, USIGN(val), val, width, prec, fmt, &count);
    if (res < 0)
	return res;
    return count;
}

int
erts_printf_sword(fmtfn_t fn, void *arg, char conv, int pad, int width,
		  ErlPfSWord val)
{
    int count = 0;
    int res;
    int fmt = 0;
    int prec = -1;
    ErlPfUWord ul_val;
    switch (conv) {
    case 'd': fmt |= FMTC_d; break;
    case 'i': fmt |= FMTC_d; break;
    case 'o': fmt |= FMTC_o; break;
    case 'x': fmt |= FMTC_x; break;
    case 'X': fmt |= FMTC_X; break;
    default:
	return -EINVAL;
    }
    if (pad)
	prec = width;
    ul_val = (ErlPfUWord) (val < 0 ? -val : val);
    res = fmt_uword(fn, arg, SIGN(val), ul_val, width, prec, fmt, &count);
    if (res < 0)
	return res;
    return count;
}

int
erts_printf_double(fmtfn_t fn, void *arg, char conv, int precision, int width,
		   double val)
{
    int count = 0;
    int res;
    int fmt = 0;
    switch (conv) {
    case 'e': fmt |= FMTC_e; break;
    case 'E': fmt |= FMTC_E; break;
    case 'f': fmt |= FMTC_f; break;
    case 'g': fmt |= FMTC_g; break;
    case 'G': fmt |= FMTC_G; break;
    default:
	return -EINVAL;
    }
    res = fmt_double(fn, arg, val, width, precision, fmt, &count);
    if (res < 0)
	return res;
    return count;
}
