view src/doprnt.c @ 617:af57a77cbc92

[xemacs-hg @ 2001-06-18 07:09:50 by ben] --------------------------------------------------------------- DOCUMENTATION FIXES: --------------------------------------------------------------- eval.c: Correct documentation. elhash.c: Doc correction. --------------------------------------------------------------- LISP OBJECT CLEANUP: --------------------------------------------------------------- bytecode.h, buffer.h, casetab.h, chartab.h, console-msw.h, console.h, database.c, device.h, eldap.h, elhash.h, events.h, extents.h, faces.h, file-coding.h, frame.h, glyphs.h, gui-x.h, gui.h, keymap.h, lisp-disunion.h, lisp-union.h, lisp.h, lrecord.h, lstream.h, mule-charset.h, objects.h, opaque.h, postgresql.h, process.h, rangetab.h, specifier.h, toolbar.h, tooltalk.h, ui-gtk.h: Add wrap_* to all objects (it was already there for a few of them) -- an expression to encapsulate a pointer into a Lisp object, rather than the inconvenient XSET*. "wrap" was chosen because "make" as in make_int(), make_char() is not appropriate. (It implies allocation. The issue does not exist for ints and chars because they are not allocated.) Full error checking has been added to these expressions. When used without error checking, non-union build, use of these expressions will incur no loss of efficiency. (In fact, XSET* is now defined in terms of wrap_* in a non-union build.) In a union build, you will also get no loss of efficiency provided that you have a decent optimizing compiler, and a compiler that either understands inlines or automatically inlines those particular functions. (And since people don't normally do their production builds on union, it doesn't matter.) Update the sample Lisp object definition in lrecord.h accordingly. dumper.c: Fix places in dumper that referenced wrap_object to reference its new name, wrap_pointer_1. buffer.c, bufslots.h, conslots.h, console.c, console.h, devslots.h, device.c, device.h, frame.c, frame.h, frameslots.h, window.c, window.h, winslots.h: -- Extract out the Lisp objects of `struct device' into devslots.h, just like for the other structures. -- Extract out the remaining (not copied into the window config) Lisp objects in `struct window' into winslots.h; use different macros (WINDOW_SLOT vs. WINDOW_SAVED_SLOT) to differentiate them. -- Eliminate the `dead' flag of `struct frame', since it duplicates information already available in `framemeths', and fix FRAME_LIVE_P accordingly. (Devices and consoles already work this way.) -- In *slots.h, switch to system where MARKED_SLOT is automatically undef'd at the end of the file. (Follows what winslots.h already does.) -- Update the comments at the beginning of *slots.h to be accurate. -- When making any of the above objects dead, zero it out entirely and reset all Lisp object slots to Qnil. (We were already doing this somewhat, but not consistently.) This (1) Eliminates the possibility of extra objects hanging around that ought to be GC'd, (2) Causes an immediate crash if anyone tries to access a structure in one of these objects, (3) Ensures consistent behavior wrt dead objects. dialog-msw.c: Use internal_object_printer, since this object should not escape. --------------------------------------------------------------- FIXING A CRASH THAT I HIT ONCE (AND A RELATED BAD BEHAVIOR): --------------------------------------------------------------- eval.c: Fix up some comments about the FSF implementation. Fix two nasty bugs: (1) condition_case_unwind frees the conses sitting in the catch->tag slot too quickly, resulting in a crash that I hit. (2) catches need to be unwound one at a time when calling unwind-protect code, rather than all at once at the end; otherwise, incorrect behavior can result. (A comment shows exactly how.) backtrace.h: Improve comment about FSF differences in the handler stack. --------------------------------------------------------------- FIXING A CRASH THAT I REPEATEDLY HIT WHEN USING THE MOUSE WHEEL UNDER MSWINDOWS: --------------------------------------------------------------- Basic idea: My crash is due either to a dead, non-marked, GC-collected frame inside of a window mirror, or a prematurely freed window mirror. We need to mark the Lisp objects inside of window mirrors. Tracking the lifespan of window mirrors and scrollbar instances is extremely hard, and there may well be lurking bugs where such objects are freed too soon. The only safe way to fix these problems (and it fixes both problems at once) is to make both of these structures Lisp objects. lrecord.h, emacs.c, inline.c, scrollbar-gtk.c, scrollbar-msw.c, scrollbar-x.c, scrollbar.c, scrollbar.h, symsinit.h: Make scrollbar instances actual Lisp objects. Mark the window mirrors in them. inline.c needs to know about scrollbar.h now. Record the new type in lrecord.h. Fix up scrollbar-*.c appropriately. Create a hash table in scrollbar-msw.c so that the scrollbar instances stored in scrollbar HWND's are properly GC-protected. Create complex_vars_of_scrollbar_mswindows() to create the hash table at startup, and call it from emacs.c. Don't store the scrollbar instance as a property of the GTK scrollbar, as it's not used and if we did this, we'd have to separately GC-protect it in a hash table, like in MS Windows. lrecord.h, frame.h, frame.c, frameslots.h, redisplay.c, window.c, window.h: Move mark_window_mirror from redisplay.c to window.c. Make window mirrors actual Lisp objects. Tell lrecord.h about them. Change the window mirror member of struct frame from a pointer to a Lisp object, and add XWINDOW_MIRROR in appropriate places. Mark the scrollbar instances in the window mirror. redisplay.c, redisplay.h, alloc.c: Delete mark_redisplay. Don't call mark_redisplay. We now mark frame-specific structures in mark_frame. NOTE: I also deleted an extremely questionable call to update_frame_window_mirrors(). It was extremely questionable before, and now totally impossible, since it will create Lisp objects during redisplay. frame.c: Mark the scrollbar instances, which are now Lisp objects. Call mark_gutter() here, not in mark_redisplay(). gutter.c: Update comments about correct marking. --------------------------------------------------------------- ISSUES BROUGHT UP BY MARTIN: --------------------------------------------------------------- buffer.h: Put back these macros the way Steve T and I think they ought to be. I already explained in a previous changelog entry why I think these macros should be the way I'd defined them. Once again: We fix these macros so they don't care about the type of their lvalues. The non-C-string equivalents of these already function in the same way, and it's correct because it should be OK to pass in a CBufbyte *, a BufByte *, a Char_Binary *, an UChar_Binary *, etc. The whole reason for these different types is to work around errors caused by signed-vs-unsigned non-matching types. Any possible error that might be caught in a DFC macro would also be caught wherever the argument is used elsewhere. So creating multiple macro versions would add no useful error-checking and just further complicate an already complicated area. As for Martin's "ANSI aliasing" bug, XEmacs is not ANSI-aliasing clean and probably never will be. Unless the board agrees to change XEmacs in this way (and we really don't want to go down that road), this is not a bug. sound.h: Undo Martin's type change. signal.c: Fix problem identified by Martin with Linux and g++ due to non-standard declaration of setitimer(). systime.h: Update the docs for "qxe_" to point out why making the encapsulation explicit is always the right way to go. (setitimer() itself serves as an example.) For 21.4: update-elc-2.el: Correct misplaced parentheses, making lisp/mule not get recompiled.
author ben
date Mon, 18 Jun 2001 07:10:32 +0000
parents 910449c92002
children b39c14581166
line wrap: on
line source

/* 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)
 syntax_error ("Format converter number too large", Qunbound);
      *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)						\
      syntax_error ("Premature end of format string", Qunbound);			\
    ch = *fmt;							\
    if (ch >= 0200)						\
      syntax_error ("Non-ASCII character in format converter spec", Qunbound);	\
    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))
	    syntax_error ("Invalid converter character", make_char (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))
 syntax_error ("No conversion spec for argument", make_int (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_1 (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))
 syntax_error ("Invalid repositioning argument", make_int (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))
		{
		  syntax_error ("format specifier %%%c doesn't match argument type",
			 make_char (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))
	 syntax_error ("invalid character value %d to %%c spec", make_char (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,
				   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)
		{
		  long_to_string (p, spec->minwidth);
		  p += strlen (p);
		}
	      if (spec->precision >= 0)
		{
		  *p++ = '.';
		  long_to_string (p, spec->precision);
		  p += strlen (p);
		}
	      
	      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;
}