diff src/doprnt.c @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 9ee227acff29
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doprnt.c	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,802 @@
+/* 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";
+
+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;
+  int minus_flag:1;
+  int plus_flag:1;
+  int space_flag:1;
+  int number_flag:1;
+  int zero_flag:1;
+  int h_flag:1;
+  int l_flag: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 */
+};
+
+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 (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)
+
+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 (struct printf_spec);
+  int prev_argnum = 0;
+
+  while (1)
+    {
+      struct printf_spec spec;
+      CONST Bufbyte *text_end;
+      Bufbyte ch;
+
+      memset (&spec, 0, sizeof (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 */
+	  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 == '.')
+	    {
+	      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;
+	}
+
+      if (spec.space_flag && spec.plus_flag)
+	spec.space_flag = 0;
+      if (spec.zero_flag && spec.space_flag)
+	spec.zero_flag = 0;
+
+      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 (union printf_arg);
+  union printf_arg arg;
+  REGISTER int i;
+  int args_needed = get_args_needed (specs);
+
+  memset (&arg, 0, sizeof (union printf_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, 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 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 = string_data (XSTRING (format_reloc));
+      format_length = string_length (XSTRING (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 = string_data (XSTRING (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;
+	}
+
+      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;
+	      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 (!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];
+
+	      /* 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, ".");
+		  sprintf (constructed_spec + strlen (constructed_spec), "%d",
+			   spec->precision);
+		}
+	      sprintf (constructed_spec + 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
+		{
+		  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, ...)
+{
+  Lisp_Object *foo;
+  va_list vargs;
+  int i;
+
+  foo = (Lisp_Object *) alloca (nargs * sizeof (Lisp_Object));
+  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;
+  Lisp_Object *foo;
+  va_list vargs;
+  int i;
+
+  foo = (Lisp_Object *) alloca (nargs * sizeof (Lisp_Object));
+  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;
+}