/* Output like sprintf to a buffer of specified size.
   Also takes args differently: pass one pointer to an array of strings
   in addition to the format string which is separate.
   Copyright (C) 1995 Free Software Foundation, Inc.
   Rewritten by mly to use varargs.h.
   Rewritten from scratch by Ben Wing (February 1995) for Mule; expanded
   to full printf spec.

This file is part of XEmacs.

XEmacs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

XEmacs is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with XEmacs; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

/* Synched up with: Rewritten.  Not in FSF. */

#include <config.h>
#include "lisp.h"

#include "buffer.h"
#include "lstream.h"

static CONST char *valid_flags = "-+ #0";

static CONST char *valid_converters = "diouxXfeEgGcsS";
static CONST char *int_converters = "dic";
static CONST char *unsigned_int_converters = "ouxX";
static CONST char *double_converters = "feEgG";
static CONST char *string_converters = "sS";

typedef struct printf_spec printf_spec;
struct printf_spec
{
  int argnum; /* which argument does this spec want?  This is one-based:
		 The first argument given is numbered 1, the second
		 is 2, etc.  This is to handle %##$x-type specs. */
  int minwidth;
  int precision;
  unsigned int minus_flag:1;
  unsigned int plus_flag:1;
  unsigned int space_flag:1;
  unsigned int number_flag:1;
  unsigned int zero_flag:1;
  unsigned int h_flag:1;
  unsigned int l_flag:1;
  unsigned int forwarding_precision:1;
  char converter; /* converter character or 0 for dummy marker
		     indicating literal text at the end of the
		     specification */
  Bytecount text_before; /* position of the first character of the
			    block of literal text before this spec */
  Bytecount text_before_len; /* length of that text */
};

typedef union printf_arg printf_arg;
union printf_arg
{
  int i;
  unsigned int ui;
  long l;
  unsigned long ul;
  double d;
  Bufbyte *bp;
};

/* We maintain a list of all the % specs in the specification,
   along with the offset and length of the block of literal text
   before each spec.  In addition, we have a "dummy" spec that
   represents all the literal text at the end of the specification.
   Its converter is 0. */

typedef struct
{
  Dynarr_declare (struct printf_spec);
} printf_spec_dynarr;

typedef struct
{
  Dynarr_declare (union printf_arg);
} printf_arg_dynarr;

/* Append STRING (of length LEN) to STREAM.  MINLEN is the minimum field
   width.  If MINUS_FLAG is set, left-justify the string in its field;
   otherwise, right-justify.  If ZERO_FLAG is set, pad with 0's; otherwise
   pad with spaces.  If MAXLEN is non-negative, the string is first
   truncated to that many character.

   Note that MINLEN and MAXLEN are Charcounts but LEN is a Bytecount. */

static void
doprnt_1 (Lisp_Object stream, CONST Bufbyte *string, Bytecount len,
	  Charcount minlen, Charcount maxlen, int minus_flag, int zero_flag)
{
  Charcount cclen;
  Bufbyte pad;
  Lstream *lstr = XLSTREAM (stream);

  cclen = bytecount_to_charcount (string, len);

  if (zero_flag)
    pad = '0';
  else
    pad = ' ';

  /* Padding at beginning to right-justify ... */
  if (minlen > cclen && !minus_flag)
    {
      int to_add = minlen - cclen;
      while (to_add > 0)
	{
	  Lstream_putc (lstr, pad);
	  to_add--;
	}
    }

  if (maxlen >= 0)
    len = charcount_to_bytecount (string, min (maxlen, cclen));
  Lstream_write (lstr, string, len);

  /* Padding at end to left-justify ... */
  if (minlen > cclen && minus_flag)
    {
      int to_add = minlen - cclen;
      while (to_add > 0)
	{
	  Lstream_putc (lstr, pad);
	  to_add--;
	}
    }
}

static CONST Bufbyte *
parse_off_posnum (CONST Bufbyte *start, CONST Bufbyte *end, int *returned_num)
{
  Bufbyte arg_convert[100];
  REGISTER Bufbyte *arg_ptr = arg_convert;

  *returned_num = -1;
  while (start != end && isdigit (*start))
    {
      if ((size_t) (arg_ptr - arg_convert) >= sizeof (arg_convert) - 1)
	error ("Format converter number too large");
      *arg_ptr++ = *start++;
    }
  *arg_ptr = '\0';
  if (arg_convert != arg_ptr)
    *returned_num = atoi ((char *) arg_convert);
  return start;
}

#define NEXT_ASCII_BYTE(ch)					\
  do {								\
    if (fmt == fmt_end)						\
      error ("Premature end of format string");			\
    ch = *fmt;							\
    if (ch >= 0200)						\
      error ("Non-ASCII character in format converter spec");	\
    fmt++;							\
  } while (0)

#define RESOLVE_FLAG_CONFLICTS(spec)				\
  do {								\
    if (spec.space_flag && spec.plus_flag)			\
      spec.space_flag = 0;					\
    if (spec.zero_flag && spec.space_flag)			\
      spec.zero_flag = 0;					\
  } while (0)

static printf_spec_dynarr *
parse_doprnt_spec (CONST Bufbyte *format, Bytecount format_length)
{
  CONST Bufbyte *fmt = format;
  CONST Bufbyte *fmt_end = format + format_length;
  printf_spec_dynarr *specs = Dynarr_new (printf_spec);
  int prev_argnum = 0;

  while (1)
    {
      struct printf_spec spec;
      CONST Bufbyte *text_end;
      Bufbyte ch;

      xzero (spec);
      if (fmt == fmt_end)
	return specs;
      text_end = (Bufbyte *) memchr (fmt, '%', fmt_end - fmt);
      if (!text_end)
	text_end = fmt_end;
      spec.text_before = fmt - format;
      spec.text_before_len = text_end - fmt;
      fmt = text_end;
      if (fmt != fmt_end)
	{
	  fmt++; /* skip over % */

	  /* A % is special -- no arg number.  According to ANSI specs,
	     field width does not apply to %% conversion. */
	  if (fmt != fmt_end && *fmt == '%')
	    {
	      spec.converter = '%';
	      Dynarr_add (specs, spec);
	      fmt++;
	      continue;
	    }

	  /* Is there a field number specifier? */
	  {
	    CONST Bufbyte *ptr;
	    int fieldspec;

	    ptr = parse_off_posnum (fmt, fmt_end, &fieldspec);
	    if (fieldspec > 0 && ptr != fmt_end && *ptr == '$')
	      {
		/* There is a format specifier */
		prev_argnum = fieldspec;
		fmt = ptr + 1;
	      }
	    else
	      prev_argnum++;
	    spec.argnum = prev_argnum;
	  }

	  /* Parse off any flags */
	  NEXT_ASCII_BYTE (ch);
	  while (strchr (valid_flags, ch))
	    {
	      switch (ch)
		{
		case '-': spec.minus_flag = 1; break;
		case '+': spec.plus_flag = 1; break;
		case ' ': spec.space_flag = 1; break;
		case '#': spec.number_flag = 1; break;
		case '0': spec.zero_flag = 1; break;
		default: abort ();
		}
	      NEXT_ASCII_BYTE (ch);
	    }

	  /* Parse off the minimum field width */
	  fmt--; /* back up */

	  /*
	   * * means the field width was passed as an argument.
	   * Mark the current spec as one that forwards its
	   * field width and flags to the next spec in the array.
	   * Then create a new spec and continue with the parsing.
	   */
	  if (fmt != fmt_end && *fmt == '*')
	    {
	      spec.converter = '*';
	      RESOLVE_FLAG_CONFLICTS(spec);
	      Dynarr_add (specs, spec);
	      xzero (spec);
	      spec.argnum = ++prev_argnum;
	      fmt++;
	    }
	  else
	    {
	      fmt = parse_off_posnum (fmt, fmt_end, &spec.minwidth);
	      if (spec.minwidth == -1)
		spec.minwidth = 0;
	    }

	  /* Parse off any precision specified */
	  NEXT_ASCII_BYTE (ch);
	  if (ch == '.')
	    {
	      /*
	       * * means the precision was passed as an argument.
	       * Mark the current spec as one that forwards its
	       * fieldwidth, flags and precision to the next spec in
	       * the array.  Then create a new spec and continue
	       * with the parse.
	       */
	      if (fmt != fmt_end && *fmt == '*')
		{
		  spec.converter = '*';
		  spec.forwarding_precision = 1;
		  RESOLVE_FLAG_CONFLICTS(spec);
		  Dynarr_add (specs, spec);
		  xzero (spec);
		  spec.argnum = ++prev_argnum;
		  fmt++;
		}
	      else
		{
		  fmt = parse_off_posnum (fmt, fmt_end, &spec.precision);
		  if (spec.precision == -1)
		    spec.precision = 0;
		}
	      NEXT_ASCII_BYTE (ch);
	    }
	  else
	    /* No precision specified */
	    spec.precision = -1;

	  /* Parse off h or l flag */
	  if (ch == 'h' || ch == 'l')
	    {
	      if (ch == 'h')
		spec.h_flag = 1;
	      else
		spec.l_flag = 1;
	      NEXT_ASCII_BYTE (ch);
	    }

	  if (!strchr (valid_converters, ch))
	    error ("Invalid converter character %c", ch);
	  spec.converter = ch;
	}

      RESOLVE_FLAG_CONFLICTS(spec);
      Dynarr_add (specs, spec);
    }

  RETURN_NOT_REACHED(specs) /* suppress compiler warning */
}

static int
get_args_needed (printf_spec_dynarr *specs)
{
  int args_needed = 0;
  REGISTER int i;

  /* Figure out how many args are needed.  This may be less than
     the number of specs because a spec could be %% or could be
     missing (literal text at end of format string) or there
     could be specs where the field number is explicitly given.
     We just look for the maximum argument number that's referenced. */

  for (i = 0; i < Dynarr_length (specs); i++)
    {
      char ch = Dynarr_at (specs, i).converter;
      if (ch && ch != '%')
	{
	  int argnum = Dynarr_at (specs, i).argnum;
	  if (argnum > args_needed)
	    args_needed = argnum;
	}
    }

  return args_needed;
}

static printf_arg_dynarr *
get_doprnt_args (printf_spec_dynarr *specs, va_list vargs)
{
  printf_arg_dynarr *args = Dynarr_new (printf_arg);
  union printf_arg arg;
  REGISTER int i;
  int args_needed = get_args_needed (specs);

  xzero (arg);
  for (i = 1; i <= args_needed; i++)
    {
      int j;
      char ch;
      struct printf_spec *spec = 0;

      for (j = 0; j < Dynarr_length (specs); j++)
	{
	  spec = Dynarr_atp (specs, j);
	  if (spec->argnum == i)
	    break;
	}

      if (j == Dynarr_length (specs))
	error ("No conversion spec for argument %d", i);

      ch = spec->converter;

      /* int even if ch == 'c': "the type used in va_arg is supposed to
	 match the actual type **after default promotions**." */

      if (strchr (int_converters, ch))
	{
	  if (spec->h_flag)
	    arg.i = va_arg (vargs, int /* short */);
	  else if (spec->l_flag)
	    arg.l = va_arg (vargs, long);
	  else
	    arg.i = va_arg (vargs, int);
	}
      else if (strchr (unsigned_int_converters, ch))
	{
	  if (spec->h_flag)
	    arg.ui = va_arg (vargs, unsigned int /* unsigned short */);
	  else if (spec->l_flag)
	    arg.ul = va_arg (vargs, unsigned long);
	  else
	    arg.ui = va_arg (vargs, unsigned int);
	}
      else if (strchr (double_converters, ch))
	arg.d = va_arg (vargs, double);
      else if (strchr (string_converters, ch))
	arg.bp = va_arg (vargs, Bufbyte *);
      else abort ();

      Dynarr_add (args, arg);
    }

  return args;
}

/* Generate output from a format-spec FORMAT, of length FORMAT_LENGTH.
   Output goes in BUFFER, which has room for BUFSIZE bytes.
   If the output does not fit, truncate it to fit.
   Returns the number of bytes stored into BUFFER.
   LARGS or VARGS points to the arguments, and NARGS says how many.
   if LARGS is non-zero, it should be a pointer to NARGS worth of
   Lisp arguments.  Otherwise, VARGS should be a va_list referring
   to the arguments. */

static Bytecount
emacs_doprnt_1 (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		Lisp_Object format_reloc, Bytecount format_length,
		int nargs,
		/* #### Gag me, gag me, gag me */
		CONST Lisp_Object *largs, va_list vargs)
{
  printf_spec_dynarr *specs = 0;
  printf_arg_dynarr *args = 0;
  REGISTER int i;
  int init_byte_count = Lstream_byte_count (XLSTREAM (stream));

  if (!NILP (format_reloc))
    {
      format_nonreloc = XSTRING_DATA (format_reloc);
      format_length = XSTRING_LENGTH (format_reloc);
    }
  if (format_length < 0)
    format_length = (Bytecount) strlen ((CONST char *) format_nonreloc);

  specs = parse_doprnt_spec (format_nonreloc, format_length);
  if (largs)
    {
	/* allow too many args for string, but not too few */
      if (nargs < get_args_needed (specs))
	signal_error (Qwrong_number_of_arguments,
		      list3 (Qformat,
			     make_int (nargs),
			     !NILP (format_reloc) ? format_reloc :
			     make_string (format_nonreloc, format_length)));
    }
  else
    {
      args = get_doprnt_args (specs, vargs);
    }

  for (i = 0; i < Dynarr_length (specs); i++)
    {
      struct printf_spec *spec = Dynarr_atp (specs, i);
      char ch;

      /* Copy the text before */
      if (!NILP (format_reloc)) /* refetch in case of GC below */
	format_nonreloc = XSTRING_DATA (format_reloc);
       doprnt_1 (stream, format_nonreloc + spec->text_before,
		 spec->text_before_len, 0, -1, 0, 0);

      ch = spec->converter;

      if (!ch)
	continue;

      if (ch == '%')
	{
	  doprnt_1 (stream, (Bufbyte *) &ch, 1, 0, -1, 0, 0);
	  continue;
	}

      /* The char '*' as converter means the field width, precision
         was specified as an argument.  Extract the data and forward
         it to the next spec, to which it will apply.  */
      if (ch == '*')
	{
	  struct printf_spec *nextspec = Dynarr_atp (specs, i + 1);
	  Lisp_Object obj = largs[spec->argnum - 1];

	  if (INTP (obj))
	    {
	      if (spec->forwarding_precision)
		{
		  nextspec->precision = XINT (obj);
		  nextspec->minwidth = spec->minwidth;
		}
	      else
		{
		  nextspec->minwidth = XINT (obj);
		  if (XINT(obj) < 0)
		    {
		      spec->minus_flag = 1;
		      nextspec->minwidth = - nextspec->minwidth;
		    }
		}
	      nextspec->minus_flag = spec->minus_flag;
	      nextspec->plus_flag = spec->plus_flag;
	      nextspec->space_flag = spec->space_flag;
	      nextspec->number_flag = spec->number_flag;
	      nextspec->zero_flag = spec->zero_flag;
	    }
	  continue;
	}

      if (largs && (spec->argnum < 1 || spec->argnum > nargs))
	error ("Invalid repositioning argument %d", spec->argnum);

      else if (ch == 'S' || ch == 's')
	{
	  Bufbyte *string;
	  Bytecount string_len;

	  if (!largs)
	    {
	      string = Dynarr_at (args, spec->argnum - 1).bp;
	      /* error() can be called with null string arguments.
		 E.g., in fileio.c, the return value of strerror()
		 is never checked.  We'll print (null), like some
		 printf implementations do.  Would it be better (and safe)
		 to signal an error instead?  Or should we just use the
                 empty string?  -dkindred@cs.cmu.edu 8/1997
	      */
	      if (!string)
		string = (Bufbyte *) "(null)";
	      string_len = strlen ((char *) string);
	    }
	  else
	    {
	      Lisp_Object obj = largs[spec->argnum - 1];
	      struct Lisp_String *ls;

	      if (ch == 'S')
		{
		  /* For `S', prin1 the argument and then treat like
		     a string.  */
		  ls = XSTRING (Fprin1_to_string (obj, Qnil));
		}
	      else if (STRINGP (obj))
		ls = XSTRING (obj);
	      else if (SYMBOLP (obj))
		ls = XSYMBOL (obj)->name;
	      else
		{
		  /* convert to string using princ. */
		  ls = XSTRING (Fprin1_to_string (obj, Qt));
		}
	      string = string_data (ls);
	      string_len = string_length (ls);
	    }

	  doprnt_1 (stream, string, string_len, spec->minwidth,
		    spec->precision, spec->minus_flag, spec->zero_flag);
	}

      else
	{
	  /* Must be a number. */
	  union printf_arg arg;

	  if (!largs)
	    {
	      arg = Dynarr_at (args, spec->argnum - 1);
	    }
	  else
	    {
	      Lisp_Object obj = largs[spec->argnum - 1];
	      if (CHARP (obj))
		CHECK_INT_COERCE_CHAR (obj);
	      if (!INT_OR_FLOATP (obj))
		{
		  error ("format specifier %%%c doesn't match argument type",
			 ch);
		}
	      else if (strchr (double_converters, ch))
		arg.d = XFLOATINT (obj);
	      else
		{
		  int val;

		  if (FLOATP (obj))
		    val = XINT (Ftruncate (obj));
		  else
		    val = XINT (obj);
		  if (strchr (unsigned_int_converters, ch))
		    {
		      if (spec->l_flag)
			arg.ul = (unsigned long) val;
		      else
			arg.ui = (unsigned int) val;
		    }
		  else
		    {
		      if (spec->l_flag)
			arg.l = (long) val;
		      else
			arg.i = val;
		    }
		}
	    }


	  if (ch == 'c')
	    {
	      Emchar a;
	      Bytecount charlen;
	      Bufbyte charbuf[MAX_EMCHAR_LEN];

	      if (spec->l_flag)
		a = (Emchar) arg.l;
	      else
		a = (Emchar) arg.i;

	      if (!valid_char_p (a))
		error ("invalid character value %d to %%c spec", a);

	      charlen = set_charptr_emchar (charbuf, a);
	      doprnt_1 (stream, charbuf, charlen, spec->minwidth,
			-1, spec->minus_flag, spec->zero_flag);
	    }

	  else
	    {
	      char text_to_print[500];
	      char constructed_spec[100];
	      int tem;

	      /* Partially reconstruct the spec and use sprintf() to
		 format the string. */

	      /* Make sure nothing stupid happens */
	      /* DO NOT REMOVE THE (int) CAST!  Incorrect results will
		 follow! */
	      spec->precision = min (spec->precision,
				     (int) (sizeof (text_to_print) - 50));

	      constructed_spec[0] = 0;
	      strcat (constructed_spec, "%");
	      if (spec->plus_flag)
		strcat (constructed_spec, "+");
	      if (spec->space_flag)
		strcat (constructed_spec, " ");
	      if (spec->number_flag)
		strcat (constructed_spec, "#");
	      if (spec->precision >= 0)
		{
		  strcat (constructed_spec, ".");
		  long_to_string (constructed_spec + strlen (constructed_spec),
				  spec->precision);
		}
	      sprintf (constructed_spec + (tem = strlen (constructed_spec)), "%c", ch);

	      /* sprintf the mofo */
	      /* we have to use separate calls to sprintf(), rather than
		 a single big conditional, because of the different types
		 of the arguments */
	      if (strchr (double_converters, ch))
		sprintf (text_to_print, constructed_spec, arg.d);
	      else if (strchr (unsigned_int_converters, ch))
		{
		  if (spec->l_flag)
		    sprintf (text_to_print, constructed_spec, arg.ul);
		  else
		    sprintf (text_to_print, constructed_spec, arg.ui);
		}
	      else
		{
		  /* In the special case of zero padding a signed integer */
		  /* with a field width specified, we overwrite the default */
		  /* format. */
		  if (spec->zero_flag && spec->minwidth)
		    sprintf (constructed_spec + tem,
			     "0%d%c", spec->minwidth, ch);
		  if (spec->l_flag)
		    sprintf (text_to_print, constructed_spec, arg.l);
		  else
		    sprintf (text_to_print, constructed_spec, arg.i);
		}

	      doprnt_1 (stream, (Bufbyte *) text_to_print,
			strlen (text_to_print),
			spec->minwidth, -1, spec->minus_flag, spec->zero_flag);
	    }
	}
    }

  /* #### will not get freed if error */
  if (specs)
    Dynarr_free (specs);
  if (args)
    Dynarr_free (args);
  return Lstream_byte_count (XLSTREAM (stream)) - init_byte_count;
}

/* You really don't want to know why this is necessary... */
static Bytecount
emacs_doprnt_2 (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		Lisp_Object format_reloc, Bytecount format_length, int nargs,
		CONST Lisp_Object *largs, ...)
{
  va_list vargs;
  Bytecount val;
  va_start (vargs, largs);
  val = emacs_doprnt_1 (stream, format_nonreloc, format_reloc,
			format_length, nargs, largs, vargs);
  va_end (vargs);
  return val;
}

/*********************** external entry points ***********************/

#ifdef I18N3
  /* A note about I18N3 translating: the format string should get
     translated, but not under all circumstances.  When the format
     string is a Lisp string, what should happen is that Fformat()
     should format the untranslated args[0] and return that, and also
     call Fgettext() on args[0] and, if that is different, format it
     and store it in the `string-translatable' property of
     the returned string.  See Fgettext(). */
#endif

/* Send formatted output to STREAM.  The format string comes from
   either FORMAT_NONRELOC (of length FORMAT_LENGTH; -1 means use
   strlen() to determine the length) or from FORMAT_RELOC, which
   should be a Lisp string.  Return the number of bytes written
   to the stream.

   DO NOT pass the data from a Lisp string as the FORMAT_NONRELOC
   parameter, because this function can cause GC. */

Bytecount
emacs_doprnt_c (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		Lisp_Object format_reloc, Bytecount format_length,
		...)
{
  int val;
  va_list vargs;

  va_start (vargs, format_length);
  val = emacs_doprnt_1 (stream, format_nonreloc, format_reloc,
			format_length, 0, 0, vargs);
  va_end (vargs);
  return val;
}

/* Like emacs_doprnt_c but the args come in va_list format. */

Bytecount
emacs_doprnt_va (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		 Lisp_Object format_reloc, Bytecount format_length,
		 va_list vargs)
{
  return emacs_doprnt_1 (stream, format_nonreloc, format_reloc,
			 format_length, 0, 0, vargs);
}

/* Like emacs_doprnt_c but the args are Lisp objects instead of
   C arguments.  This causes somewhat different behavior from
   the above two functions (which should act like printf).
   See `format' for a description of this behavior. */

Bytecount
emacs_doprnt_lisp (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		   Lisp_Object format_reloc, Bytecount format_length,
		   int nargs, CONST Lisp_Object *largs)
{
  return emacs_doprnt_2 (stream, format_nonreloc, format_reloc,
			 format_length, nargs, largs);
}

/* Like the previous function but takes a variable number of arguments. */

Bytecount
emacs_doprnt_lisp_2 (Lisp_Object stream, CONST Bufbyte *format_nonreloc,
		     Lisp_Object format_reloc, Bytecount format_length,
		     int nargs, ...)
{
  va_list vargs;
  int i;
  Lisp_Object *foo = alloca_array (Lisp_Object, nargs);

  va_start (vargs, nargs);
  for (i = 0; i < nargs; i++)
    foo[i] = va_arg (vargs, Lisp_Object);
  va_end (vargs);

  return emacs_doprnt_2 (stream, format_nonreloc, format_reloc,
			 format_length, nargs, foo);
}

/* The following four functions work like the above three but
   return their output as a Lisp string instead of sending it
   to a stream. */

Lisp_Object
emacs_doprnt_string_c (CONST Bufbyte *format_nonreloc,
		       Lisp_Object format_reloc, Bytecount format_length,
		       ...)
{
  va_list vargs;
  Lisp_Object obj;
  Lisp_Object stream = make_resizing_buffer_output_stream ();
  struct gcpro gcpro1;

  GCPRO1 (stream);
  va_start (vargs, format_length);
  emacs_doprnt_1 (stream, format_nonreloc, format_reloc,
		  format_length, 0, 0, vargs);
  va_end (vargs);
  Lstream_flush (XLSTREAM (stream));
  obj = make_string (resizing_buffer_stream_ptr (XLSTREAM (stream)),
		     Lstream_byte_count (XLSTREAM (stream)));
  UNGCPRO;
  Lstream_delete (XLSTREAM (stream));
  return obj;
}

Lisp_Object
emacs_doprnt_string_va (CONST Bufbyte *format_nonreloc,
			Lisp_Object format_reloc, Bytecount format_length,
			va_list vargs)
{
  /* I'm fairly sure that this function cannot actually GC.
     That can only happen when the arguments to emacs_doprnt_1() are
     Lisp objects rather than C args. */
  Lisp_Object obj;
  Lisp_Object stream = make_resizing_buffer_output_stream ();
  struct gcpro gcpro1;

  GCPRO1 (stream);
  emacs_doprnt_1 (stream, format_nonreloc, format_reloc,
		  format_length, 0, 0, vargs);
  Lstream_flush (XLSTREAM (stream));
  obj = make_string (resizing_buffer_stream_ptr (XLSTREAM (stream)),
		     Lstream_byte_count (XLSTREAM (stream)));
  UNGCPRO;
  Lstream_delete (XLSTREAM (stream));
  return obj;
}

Lisp_Object
emacs_doprnt_string_lisp (CONST Bufbyte *format_nonreloc,
			  Lisp_Object format_reloc, Bytecount format_length,
			  int nargs, CONST Lisp_Object *largs)
{
  Lisp_Object obj;
  Lisp_Object stream = make_resizing_buffer_output_stream ();
  struct gcpro gcpro1;

  GCPRO1 (stream);
  emacs_doprnt_2 (stream, format_nonreloc, format_reloc,
		  format_length, nargs, largs);
  Lstream_flush (XLSTREAM (stream));
  obj = make_string (resizing_buffer_stream_ptr (XLSTREAM (stream)),
		     Lstream_byte_count (XLSTREAM (stream)));
  UNGCPRO;
  Lstream_delete (XLSTREAM (stream));
  return obj;
}

Lisp_Object
emacs_doprnt_string_lisp_2 (CONST Bufbyte *format_nonreloc,
			    Lisp_Object format_reloc, Bytecount format_length,
			    int nargs, ...)
{
  Lisp_Object obj;
  Lisp_Object stream = make_resizing_buffer_output_stream ();
  struct gcpro gcpro1;
  va_list vargs;
  int i;
  Lisp_Object *foo = alloca_array (Lisp_Object, nargs);

  va_start (vargs, nargs);
  for (i = 0; i < nargs; i++)
    foo[i] = va_arg (vargs, Lisp_Object);
  va_end (vargs);

  GCPRO1 (stream);
  emacs_doprnt_2 (stream, format_nonreloc, format_reloc,
		  format_length, nargs, foo);
  Lstream_flush (XLSTREAM (stream));
  obj = make_string (resizing_buffer_stream_ptr (XLSTREAM (stream)),
		     Lstream_byte_count (XLSTREAM (stream)));
  UNGCPRO;
  Lstream_delete (XLSTREAM (stream));
  return obj;
}
