/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#include "config.h"

#include <assert.h>
#include <inttypes.h>
#include	<sfio/sfhdr.h>
#include	<stddef.h>
#include	<stdio.h>
#include	<string.h>
#include <util/gv_ctype.h>
#include <util/gv_math.h>

/*	The engine for formatting data
**
**	Written by Kiem-Phong Vo.
*/

#define HIGHBITI	(~((~0u) >> 1))
#define HIGHBITL	(~((~0ull) >> 1))

#define SFFMT_PREFIX	(SFFMT_MINUS|SFFMT_SIGN|SFFMT_BLANK)

#define FPRECIS		6	/* default precision for floats         */

/**
 * @param f file to print to
 * @param format Structure describing how to print
 */
int sfprint(FILE *f, Sffmt_t *format) {
    int v = 0, n_s, base, fmt, flags;
    long long lv;
    char *sp, *ssp, *endsp, *ep, *endep;
    int dot, width, precis, n, n_output = 0;
    int sign, decpt;
    ssize_t size;
    double dval;
    char *tls[2], **ls;		/* for %..[separ]s              */
    const char *t_str;		/* stuff between ()             */
    ssize_t n_str;		/* its length                   */

    Argv_t argv;		/* for extf to return value     */
    Sffmt_t *ft;		/* format environment           */

    int argn;		/* arg position and number      */

#define SLACK		1024
    char buf[SF_MAXDIGITS + SLACK];
    char decimal = 0, thousand = 0;

#define SFputc(f,c) do { \
    if (putc((c), (f)) == EOF) { \
      goto done; \
    } \
} while (0)
#define SFnputc(f,c,n)  do { \
    for (int i_ = 0; i_ < (n); ++i_) { \
      if (putc((c), (f)) == EOF) { \
        goto done; \
      } \
    } \
    (n) = 0; \
} while (0)
#define SFwrite(f,s,n) do { \
  if ((n) > 0 && fwrite((s), (size_t)(n), 1, (f)) < 1) { \
    goto done; \
  } \
} while (0)

    tls[1] = NULL;

    ft = NULL;

    argn = -1;

    // stack a new environment
    argv.ft = format;
    assert(argv.ft != NULL);
    assert(argv.ft->form != NULL);

    const char *form = argv.ft->form;

    ft = argv.ft;

    while ((n = *form)) {
	if (n != '%') {		/* collect the non-pattern chars */
	    size_t form_len = 1;
	    while (form[form_len] != '\0' && form[form_len] != '%')
		++form_len;
	    SFwrite(f, form, form_len);
	    form += form_len;
	    continue;
	} else
	    form += 1;

	flags = 0;
	size = width = precis = base = n_s = -1;
	ssp = _Sfdigits;
	endep = ep = NULL;
	endsp = sp = buf + (sizeof(buf) - 1);
	t_str = NULL;
	n_str = dot = 0;

      loop_flags:		/* LOOP FOR \0, %, FLAGS, WIDTH, PRECISION, BASE, TYPE */
	switch ((fmt = *form++)) {
	case '\0':
	    SFputc(f, '%');
	    goto done;
	case '%':
	    SFputc(f, '%');
	    continue;

	case LEFTP:		/* get the type enclosed in balanced parens */
	    t_str = form;
	    for (v = 1;;) {
		switch (*form++) {
		case 0:	/* not balancable, retract */
		    form = t_str;
		    t_str = NULL;
		    n_str = 0;
		    goto loop_flags;
		case LEFTP:	/* increasing nested level */
		    v += 1;
		    continue;
		case RIGHTP:	/* decreasing nested level */
		    if ((v -= 1) != 0)
			continue;
		    if (*t_str != '*')
			n_str = (form - 1) - t_str;
		    else {
			++argn;

			FMTSET(ft, form, LEFTP, 0, 0, 0, 0, 0, NULL, 0);
			n = ft->extf(&argv, ft);
			if (n < 0)
			    goto done;
			assert(ft->flags & SFFMT_VALUE);
			if ((t_str = argv.s) && (n_str = (int)ft->size) < 0)
			    n_str = (ssize_t)strlen(t_str);
		    }
		    goto loop_flags;
		default:
		    // skip over
		    break;
		}
	    }

	case '-':
	    flags = (flags & ~SFFMT_ZERO) | SFFMT_LEFT;
	    goto loop_flags;
	case '0':
	    if (!(flags & SFFMT_LEFT))
		flags |= SFFMT_ZERO;
	    goto loop_flags;
	case ' ':
	    if (!(flags & SFFMT_SIGN))
		flags |= SFFMT_BLANK;
	    goto loop_flags;
	case '+':
	    flags = (flags & ~SFFMT_BLANK) | SFFMT_SIGN;
	    goto loop_flags;
	case '#':
	    flags |= SFFMT_ALTER;
	    goto loop_flags;
	case QUOTE:
	    SFSETLOCALE(decimal, thousand);
	    if (thousand)
		flags |= SFFMT_THOUSAND;
	    goto loop_flags;

	case '.':
	    dot += 1;
	    if (dot == 1) {	/* so base can be defined without setting precis */
		if (*form != '.')
		    precis = 0;
	    } else if (dot == 2) {
		base = 0;	/* for %s,%c */
		if (*form == 'c' || *form == 's')
		    goto loop_flags;
		if (*form && !gv_isalnum(*form) &&
		    (form[1] == 'c' || form[1] == 's')) {
		    if (*form == '*')
			goto do_star;
		    else {
			base = *form++;
			goto loop_flags;
		    }
		}
	    }

	    if (gv_isdigit(*form)) {
		fmt = *form++;
		goto dot_size;
	    } else if (*form != '*')
		goto loop_flags;
	  do_star:
	    form += 1;		/* fall through */
	case '*':
	    form = _Sffmtintf(form);
	    n = ++argn;

	    FMTSET(ft, form, '.', dot, 0, 0, 0, 0, NULL, 0);
	    if (ft->extf(&argv, ft) < 0)
		goto done;
	    assert(ft->flags & SFFMT_VALUE);
	    v = argv.i;
	    goto dot_set;

	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	  dot_size:
	    for (v = fmt - '0'; gv_isdigit(*form); ++form)
		v = v * 10 + (*form - '0');
	  dot_set:
	    if (dot == 0) {
		if ((width = v) < 0) {
		    width = -width;
		    flags = (flags & ~SFFMT_ZERO) | SFFMT_LEFT;
		}
	    } else if (dot == 1)
		precis = v;
	    else if (dot == 2)
		base = v;
	    goto loop_flags;

	case 'I':		/* object length */
	    size = 0;
	    flags = (flags & ~SFFMT_TYPES) | SFFMT_IFLAG;
	    if (gv_isdigit(*form)) {
		for (n = *form; gv_isdigit(n); n = *++form)
		    size = size * 10 + (n - '0');
	    } else if (*form == '*') {
		form = _Sffmtintf(form + 1);
		n = ++argn;

		FMTSET(ft, form, 'I', sizeof(int), 0, 0, 0, 0, NULL, 0);
		if (ft->extf(&argv, ft) < 0)
		    goto done;
		assert(ft->flags & SFFMT_VALUE);
		size = argv.i;
	    }
	    goto loop_flags;

	case 'l':
	    size = -1;
	    flags &= ~SFFMT_TYPES;
	    if (*form == 'l') {
		form += 1;
		flags |= SFFMT_LLONG;
	    } else
		flags |= SFFMT_LONG;
	    goto loop_flags;
	case 'h':
	    size = -1;
	    flags &= ~SFFMT_TYPES;
	    if (*form == 'h') {
		form += 1;
		flags |= SFFMT_SSHORT;
	    } else
		flags |= SFFMT_SHORT;
	    goto loop_flags;
	case 'L':
	    size = -1;
	    flags = (flags & ~SFFMT_TYPES) | SFFMT_LDOUBLE;
	    goto loop_flags;

	case 'j':
	    size = -1;
	    flags = (flags & ~SFFMT_TYPES) | SFFMT_JFLAG;
	    goto loop_flags;
	case 'z':
	    size = -1;
	    flags = (flags & ~SFFMT_TYPES) | SFFMT_ZFLAG;
	    goto loop_flags;
	case 't':
	    size = -1;
	    flags = (flags & ~SFFMT_TYPES) | SFFMT_TFLAG;
	    goto loop_flags;
	default: // continue with logic below
	    break;
	}

	/* set the correct size */
	if (flags & (SFFMT_TYPES & ~SFFMT_IFLAG)) {
	    if (_Sftype[fmt] & (SFFMT_INT | SFFMT_UINT)) {
		size = (flags & SFFMT_LLONG)  ? (ssize_t)sizeof(long long) :
		       (flags & SFFMT_LONG)   ? (ssize_t)sizeof(long) :
		       (flags & SFFMT_SHORT)  ? (ssize_t)sizeof(short) :
		       (flags & SFFMT_SSHORT) ? (ssize_t)sizeof(char) :
		       (flags & SFFMT_JFLAG)  ? (ssize_t)sizeof(long long) :
		       (flags & SFFMT_TFLAG)  ? (ssize_t)sizeof(ptrdiff_t) :
		       (flags & SFFMT_ZFLAG)  ? (ssize_t)sizeof(size_t) : -1;
	    } else if (_Sftype[fmt] & SFFMT_FLOAT) {
		size = (flags & SFFMT_LDOUBLE)              ? (ssize_t)sizeof(long double) :
		       (flags & (SFFMT_LONG | SFFMT_LLONG)) ? (ssize_t)sizeof(double) : -1;
	    }
	}

	++argn;
	FMTSET(ft, form, fmt, size, flags, width, precis, base, t_str, n_str);
	v = ft->extf(&argv, ft);

	if (v < 0)
	    goto done;
	else if (v == 0) { // extf did not output
	    FMTGET(ft, form, fmt, size, flags, width, precis, base);
	    assert(ft->flags & SFFMT_VALUE);
	} else if (v > 0) { // extf output v bytes
	    n_output += v;
	    continue;
	}

	switch (fmt) {		/* PRINTF DIRECTIVES */
	default:		/* unknown directive */
	    form -= 1;
	    argn -= 1;
	    continue;

	case 's':
	    if (base >= 0) {	/* list of strings */
		if (!(ls = argv.sp) || !ls[0])
		    continue;
	    } else {
		if (!(sp = argv.s))
		    sp = "(null)";
		ls = tls;
		tls[0] = sp;
	    }
	    for (sp = *ls;;) {
		if ((v = size) >= 0) {
		    if (precis >= 0 && v > precis)
			v = precis;
		} else if (precis < 0)
		    v = strlen(sp);
		else {		/* precis >= 0 means min(strlen,precis) */
		    for (v = 0; v < precis; ++v)
			if (sp[v] == 0)
			    break;
		}
		if ((n = width - v) > 0) {
		    if (flags & SFFMT_ZERO) {
			SFnputc(f, '0', n);
		    } else if (!(flags & SFFMT_LEFT)) {
			SFnputc(f, ' ', n);
		    }
		}
		SFwrite(f, sp, v);
		if (n > 0) {
		    SFnputc(f, ' ', n);
		}
		if (!(sp = *++ls))
		    break;
		else if (base > 0) {
		    SFputc(f, base);
		}
	    }
	    continue;

	case 'c':		/* an array of characters */
	    if (base >= 0) {
		if (!(sp = argv.s) || !sp[0])
		    continue;
	    } else {
		fmt = (int) argv.c;
		sp = buf;
		buf[0] = fmt;
		buf[1] = 0;
	    }
	    if (precis <= 0)
		precis = 1;
	    for (fmt = *sp;;) {
		if ((n = width - precis) > 0 && !(flags & SFFMT_LEFT)) {
		    SFnputc(f, ' ', n);
		}
		v = precis;
		SFnputc(f, fmt, v);
		if (n > 0) {
		    SFnputc(f, ' ', n);
		}
		if (!(fmt = *++sp))
		    break;
		else if (base > 0) {
		    SFputc(f, base);
		}
	    }
	    continue;

	case 'p':		/* pointer value */
	    fmt = 'x';
	    base = 16;
	    n_s = 15;
	    n = 4;
	    flags =
		(flags & ~(SFFMT_SIGN | SFFMT_BLANK | SFFMT_ZERO)) |
		SFFMT_ALTER;
	    if (sizeof(void*) > sizeof(int)) {
		lv = (long long)(intptr_t)argv.vp;
		goto long_cvt;
	    } else {
		v = (int)(intptr_t)argv.vp;
		goto int_cvt;
	    }
	case 'o':
	    base = 8;
	    n_s = 7;
	    n = 3;
	    flags &= ~(SFFMT_SIGN | SFFMT_BLANK);
	    goto int_arg;
	case 'X':
	    ssp = "0123456789ABCDEF";
	    // fall through
	case 'x':
	    base = 16;
	    n_s = 15;
	    n = 4;
	    flags &= ~(SFFMT_SIGN | SFFMT_BLANK);
	    goto int_arg;
	case 'i':
	    fmt = 'd';
	    goto d_format;
	case 'u':
	    flags &= ~(SFFMT_SIGN | SFFMT_BLANK);
	    // fall through
	case 'd':
	  d_format:
	    if (base < 2 || base > SF_RADIX)
		base = 10;
	    if ((base & (n_s = base - 1)) == 0) {
		if (base < 8)
		    n = base < 4 ? 1 : 2;
		else if (base < 32)
		    n = base < 16 ? 3 : 4;
		else
		    n = base < 64 ? 5 : 6;
	    } else
		n_s = base == 10 ? -1 : 0;

	  int_arg:
	    if ((sizeof(long) > sizeof(int) || sizeof(void*) > sizeof(int))
	        && FMTCMP(size, long long, long long)) {
		lv = argv.ll;
		goto long_cvt;
	    } else if ((sizeof(long) > sizeof(int) || sizeof(void*) > sizeof(int))
	               && FMTCMP(size, long, long long)) {
		if (fmt == 'd')
		    lv = (long long)argv.l;
		else
		    lv = (long long)(ulong)argv.l;
	      long_cvt:
		if (lv == 0 && precis == 0)
		    break;
		if (lv < 0 && fmt == 'd') {
		    flags |= SFFMT_MINUS;
		    if ((unsigned long long)lv == HIGHBITL) { // avoid overflow
			lv = (long long)(HIGHBITL / (unsigned long long)base);
			*--sp = _Sfdigits[HIGHBITL -
			                  (unsigned long long)lv * (unsigned long long)base];
		    } else
			lv = -lv;
		}
		if (n_s < 0) {	/* base 10 */
		    char scratch[41]; // space to print ≤128-bit integer
		    const int nv = snprintf(scratch, sizeof(scratch), "%lld", lv);
		    sp -= nv;
		    memcpy(sp, scratch, (size_t)nv);
		} else if (n_s > 0) {	/* base power-of-2 */
		    do {
			*--sp = ssp[lv & n_s];
		    } while ((lv = (unsigned long long)lv >> n));
		} else {	/* general base */
		    do {
			*--sp = ssp[(unsigned long long)lv % (unsigned long long)base];
		    } while ((lv = (unsigned long long)lv / (unsigned long long)base));
		}
	    } else
	    if (sizeof(short) < sizeof(int) && FMTCMP(size, short, long long)) {
		if (ft->flags & SFFMT_VALUE) {
		    if (fmt == 'd')
			v = (int) ((short) argv.h);
		    else
			v = (int) ((ushort) argv.h);
		} else {
		    if (fmt == 'd')
			v = (int) ((short) argv.i);
		    else
			v = (int) ((ushort) argv.i);
		}
		goto int_cvt;
	    } else if (size == sizeof(char)) {
		if (ft->flags & SFFMT_VALUE) {
		    if (fmt == 'd')
			v = (int) ((char) argv.c);
		    else
			v = (int) ((uchar) argv.c);
		} else {
		    if (fmt == 'd')
			v = (int) ((char) argv.i);
		    else
			v = (int) ((uchar) argv.i);
		}
		goto int_cvt;
	    } else {
		v = argv.i;
	      int_cvt:
		if (v == 0 && precis == 0)
		    break;
		if (v < 0 && fmt == 'd') {
		    flags |= SFFMT_MINUS;
		    if ((unsigned)v == HIGHBITI) { // avoid overflow
			v = (int)(HIGHBITI / (unsigned)base);
			*--sp = _Sfdigits[HIGHBITI - (unsigned)v * (unsigned)base];
		    } else
			v = -v;
		}
		if (n_s < 0) {	/* base 10 */
		    char scratch[41]; // space to print ≤128-bit integer
		    const int nv = snprintf(scratch, sizeof(scratch), "%d", v);
		    sp -= nv;
		    memcpy(sp, scratch, (size_t)nv);
		} else if (n_s > 0) {	/* base power-of-2 */
		    do {
			*--sp = ssp[v & n_s];
		    } while ((v = (unsigned)v >> n));
		} else {	/* n_s == 0, general base */
		    do {
			*--sp = ssp[(unsigned)v % (unsigned)base];
		    } while ((v = (unsigned)v / (unsigned)base));
		}
	    }

	    if (n_s < 0 && (flags & SFFMT_THOUSAND)
		&& (n = endsp - sp) > 3) {
		if ((n %= 3) == 0)
		    n = 3;
		for (ep = buf + SLACK, endep = ep + n;;) {
		    while (ep < endep)
			*ep++ = *sp++;
		    if (sp == endsp)
			break;
		    if (sp <= endsp - 3)
			*ep++ = thousand;
		    endep = ep + 3;
		}
		sp = buf + SLACK;
		endsp = ep;
	    }

	    /* zero padding for precision if have room in buffer */
	    if (precis > 0 && (precis -= (endsp - sp)) < (sp - buf) - 64)
		while (precis-- > 0)
		    *--sp = '0';

	    if (flags & SFFMT_ALTER) {	/* prefix */
		if (fmt == 'o') {
		    if (*sp != '0')
			*--sp = '0';
		} else {
		    if (width > 0 && (flags & SFFMT_ZERO)) {	/* do 0 padding first */
			if (fmt == 'x' || fmt == 'X')
			    n = 0;
			else if (dot < 2)
			    n = width;
			else
			    n = base < 10 ? 2 : 3;
			n += (flags & (SFFMT_MINUS | SFFMT_SIGN)) ? 1 : 0;
			n = width - (n + (endsp - sp));
			while (n-- > 0)
			    *--sp = '0';
		    }
		    if (fmt == 'x' || fmt == 'X') {
			*--sp = (char) fmt;
			*--sp = '0';
		    } else if (dot >= 2) {	/* base#value notation */
			*--sp = '#';
			if (base < 10)
			    *--sp = (char) ('0' + base);
			else {
			    *--sp = _Sfdec[(base <<= 1) + 1];
			    *--sp = _Sfdec[base];
			}
		    }
		}
	    }

	    break;

	case 'g':
	case 'G':		/* these ultimately become %e or %f */
	case 'e':
	case 'E':
	case 'f':
	    if (!(ft->flags & SFFMT_VALUE) || FMTCMP(size, double, long double))
		 dval = argv.d;
	    else
		dval = (double) argv.f;

	    if (fmt == 'e' || fmt == 'E') {
		n = (precis = precis < 0 ? FPRECIS : precis) + 1;
		{
		    ep = _sfcvt(&dval, imin(n, SF_FDIGITS),
				&decpt, &sign, SFFMT_EFORMAT);
		}
		goto e_format;
	    } else if (fmt == 'f' || fmt == 'F') {
		precis = precis < 0 ? FPRECIS : precis;
		{
		    ep = _sfcvt(&dval, imin(precis, SF_FDIGITS),
				&decpt, &sign, 0);
		}
		goto f_format;
	    }

	    /* 'g' or 'G' format */
	    precis = precis < 0 ? FPRECIS : precis == 0 ? 1 : precis;
	    {
		ep = _sfcvt(&dval, imin(precis, SF_FDIGITS),
			    &decpt, &sign, SFFMT_EFORMAT);
		if (dval == 0.)
		    decpt = 1;
		else if (*ep == 'I')
		    goto infinite;
	    }

	    if (!(flags & SFFMT_ALTER)) {	/* zap trailing 0s */
		if ((n = sfslen()) > precis)
		    n = precis;
		while ((n -= 1) >= 1 && ep[n] == '0');
		n += 1;
	    } else
		n = precis;

	    if (decpt < -3 || decpt > precis) {
		precis = n - 1;
		goto e_format;
	    } else {
		precis = n - decpt;
		goto f_format;
	    }

	  e_format:		/* build the x.yyyy string */
	    if (gv_isalpha(*ep))
		goto infinite;
	    sp = endsp = buf + 1;	/* reserve space for sign */
	    *endsp++ = *ep ? *ep++ : '0';

	    SFSETLOCALE(decimal, thousand);
	    if (precis > 0 || (flags & SFFMT_ALTER))
		*endsp++ = decimal;
	    ssp = endsp;
	    endep = ep + precis;
	    while ((*endsp++ = *ep++) && ep <= endep);
	    precis -= (endsp -= 1) - ssp;

	    /* build the exponent */
	    ep = endep = buf + (sizeof(buf) - 1);
	    if (dval != 0.) {
		if ((n = decpt - 1) < 0)
		    n = -n;
		while (n > 9) {
		    v = n;
		    n /= 10;
		    *--ep = (char) ('0' + (v - n * 10));
		}
	    } else
		n = 0;
	    *--ep = (char) ('0' + n);
	    if (endep - ep <= 1)	/* at least 2 digits */
		*--ep = '0';

	    /* the e/Exponent separator and sign */
	    *--ep = (decpt > 0 || dval == 0.) ? '+' : '-';
	    *--ep = gv_isupper(fmt) ? 'E' : 'e';

	    goto end_efg;

	  f_format:		/* data before the decimal point */
	    if (gv_isalpha(*ep)) {
	      infinite:
		endsp = (sp = ep) + sfslen();
		ep = endep;
		precis = 0;
		goto end_efg;
	    }

	    SFSETLOCALE(decimal, thousand);
	    endsp = sp = buf + 1;	/* save a space for sign */
	    endep = ep + decpt;
	    if (decpt > 3 && (flags & SFFMT_THOUSAND)) {
		if ((n = decpt % 3) == 0)
		    n = 3;
		while (ep < endep && (*endsp++ = *ep++)) {
		    if (--n == 0 && (ep <= endep - 3)) {
			*endsp++ = thousand;
			n = 3;
		    }
		}
	    } else {
		while (ep < endep && (*endsp++ = *ep++));
	    }
	    if (endsp == sp)
		*endsp++ = '0';

	    if (precis > 0 || (flags & SFFMT_ALTER))
		*endsp++ = decimal;

	    if ((n = -decpt) > 0) {	/* output zeros for negative exponent */
		ssp = endsp + imin(n, precis);
		precis -= n;
		while (endsp < ssp)
		    *endsp++ = '0';
	    }

	    ssp = endsp;
	    endep = ep + precis;
	    while ((*endsp++ = *ep++) && ep <= endep);
	    precis -= (endsp -= 1) - ssp;
	    ep = endep;
	  end_efg:
	    flags |= SFFMT_FLOAT;
	    if (sign)
		flags |= SFFMT_MINUS;
	    break;
	}

	if (flags == 0 && width <= 0)
	    goto do_output;

	if (flags & SFFMT_PREFIX)
	    fmt =
		(flags & SFFMT_MINUS) ? '-' : (flags & SFFMT_SIGN) ? '+' :
		' ';

	n = (endsp - sp) + (endep - ep) + (precis <= 0 ? 0 : precis) +
	    ((flags & SFFMT_PREFIX) ? 1 : 0);
	if ((v = width - n) <= 0)
	    v = 0;
	else if (!(flags & SFFMT_ZERO)) {	/* right padding */
	    if (flags & SFFMT_LEFT)
		v = -v;
	    else if (flags & SFFMT_PREFIX) {	/* blank padding, output prefix now */
		*--sp = fmt;
		flags &= ~SFFMT_PREFIX;
	    }
	}

	if (flags & SFFMT_PREFIX) {	/* put out the prefix */
	    SFputc(f, fmt);
	    if (fmt != ' ')
		flags |= SFFMT_ZERO;
	}

	if ((n = v) > 0) {	/* left padding */
	    v = (flags & SFFMT_ZERO) ? '0' : ' ';
	    SFnputc(f, v, n);
	}

	if ((n = precis) > 0 && !(flags & SFFMT_FLOAT)) {	/* padding for integer precision */
	    SFnputc(f, '0', n);
	    precis = 0;
	}

      do_output:
	if ((n = endsp - sp) > 0)
	    SFwrite(f, sp, n);

	if (flags & (SFFMT_FLOAT | SFFMT_LEFT)) {	/* SFFMT_FLOAT: right padding for float precision */
	    if ((n = precis) > 0)
		SFnputc(f, '0', n);

	    /* SFFMT_FLOAT: the exponent of %eE */
	    if ((n = endep - (sp = ep)) > 0)
		SFwrite(f, sp, n);

	    /* SFFMT_LEFT: right padding */
	    if ((n = -v) > 0)
		SFnputc(f, ' ', n);
	}
    }

  done:

    return n_output;
}
