This commit was manufactured by cvs2svn to create branch 'chise-r21-4-18'.
[chise/xemacs-chise.git-] / src / doprnt.c
diff --git a/src/doprnt.c b/src/doprnt.c
new file mode 100644 (file)
index 0000000..6b78e77
--- /dev/null
@@ -0,0 +1,849 @@
+/* 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 * const valid_flags = "-+ #0";
+static const char * const valid_converters = "dic" "ouxX" "feEgG" "sS";
+static const char * const int_converters = "dic";
+static const char * const unsigned_int_converters = "ouxX";
+static const char * const double_converters = "feEgG";
+static const char * const 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
+{
+  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 bytes) 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 on the
+    right to that many characters.
+
+   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)
+{
+  Lstream *lstr = XLSTREAM (stream);
+  Charcount cclen = bytecount_to_charcount (string, len);
+  int to_add = minlen - cclen;
+
+  /* Padding at beginning to right-justify ... */
+  if (!minus_flag)
+    while (to_add-- > 0)
+      Lstream_putc (lstr, zero_flag ? '0' : ' ');
+
+  if (0 <= maxlen && maxlen < cclen)
+    len = charcount_to_bytecount (string, maxlen);
+  Lstream_write (lstr, string, len);
+
+  /* Padding at end to left-justify ... */
+  if (minus_flag)
+    while (to_add-- > 0)
+      Lstream_putc (lstr, zero_flag ? '0' : ' ');
+}
+
+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;
+
+      if (strchr (int_converters, ch))
+       {
+         if (spec->l_flag)
+           arg.l = va_arg (vargs, long);
+         else
+           /* int even if ch == 'c' or spec->h_flag:
+              "the type used in va_arg is supposed to match the
+              actual type **after default promotions**."
+              Hence we read an int, not a short, if spec->h_flag. */
+           arg.l = va_arg (vargs, int);
+       }
+      else if (strchr (unsigned_int_converters, ch))
+       {
+         if (spec->l_flag)
+           arg.ul = va_arg (vargs, unsigned long);
+         else
+           /* unsigned int even if ch == 'c' or spec->h_flag */
+           arg.ul = (unsigned long) 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];
+             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))
+               obj = make_int (XCHAR (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
+               {
+                 if (FLOATP (obj))
+                   obj = Ftruncate (obj);
+
+                 if (strchr (unsigned_int_converters, ch))
+                   arg.ul = (unsigned long) XINT (obj);
+                 else
+                   arg.l = XINT (obj);
+               }
+           }
+
+
+         if (ch == 'c')
+           {
+             Emchar a;
+             Bytecount charlen;
+             Bufbyte charbuf[MAX_EMCHAR_LEN];
+
+             a = (Emchar) arg.l;
+
+             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
+           {
+             /* ASCII Decimal representation uses 2.4 times as many
+                bits as machine binary.  */
+             char *text_to_print =
+               alloca_array (char, 32 +
+                             max (spec->minwidth,
+                                  (EMACS_INT)
+                                   max (sizeof (double), sizeof (long)) * 3 +
+                                  max (spec->precision, 0)));
+             char constructed_spec[100];
+             char *p = constructed_spec;
+
+             /* Mostly reconstruct the spec and use sprintf() to
+                format the string. */
+
+             *p++ = '%';
+             if (spec->plus_flag)   *p++ = '+';
+             if (spec->space_flag)  *p++ = ' ';
+             if (spec->number_flag) *p++ = '#';
+             if (spec->minus_flag)  *p++ = '-';
+             if (spec->zero_flag)   *p++ = '0';
+
+             if (spec->minwidth >= 0)
+               p = long_to_string (p, spec->minwidth);
+             if (spec->precision >= 0)
+               {
+                 *p++ = '.';
+                 p = long_to_string (p, spec->precision);
+               }
+             
+             if (strchr (double_converters, ch))
+               {
+                 *p++ = ch;
+                 *p++ = '\0';
+                 sprintf (text_to_print, constructed_spec, arg.d);
+               }
+             else
+               {
+                 *p++ = 'l';   /* Always use longs with sprintf() */
+                 *p++ = ch;
+                 *p++ = '\0';
+
+                 if (strchr (unsigned_int_converters, ch))
+                   sprintf (text_to_print, constructed_spec, arg.ul);
+                 else
+                   sprintf (text_to_print, constructed_spec, arg.l);
+               }
+
+             doprnt_1 (stream, (Bufbyte *) text_to_print,
+                       strlen (text_to_print), 0, -1, 0, 0);
+           }
+       }
+    }
+
+  /* #### 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;
+}