From d9800053c02f813e37c28e86cafd530bf4463ffa Mon Sep 17 00:00:00 2001
From: Martin Nilsson <nilsson@opera.com>
Date: Mon, 27 May 2013 18:26:17 +0200
Subject: [PATCH] sprintf is not a module anymore

---
 src/Makefile.in  |    1 +
 src/builtin.cmod |    4 -
 src/module.c     |    5 +
 src/sprintf.c    | 2694 ++++++++++++++++++++++++++++++++++++++++++++++
 src/stralloc.h   |    3 +
 src/testsuite.in |  304 ++++++
 6 files changed, 3007 insertions(+), 4 deletions(-)
 create mode 100644 src/sprintf.c

diff --git a/src/Makefile.in b/src/Makefile.in
index f0fa965202..c13fa86ca9 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -175,6 +175,7 @@ CORE_OBJ= \
  program.o \
  rbtree.o \
  rusage.o \
+ sprintf.o \
  sscanf.o \
  stralloc.o \
  stuff.o \
diff --git a/src/builtin.cmod b/src/builtin.cmod
index d01c3e8f07..1c8b3d82b3 100644
--- a/src/builtin.cmod
+++ b/src/builtin.cmod
@@ -2867,9 +2867,6 @@ PIKECLASS Buffer
   void f_Buffer_get( INT32 args );
   void f_Buffer_add( INT32 args );
 
-  typedef void (_low_f_sprintf)(INT32, int, struct string_builder *);
-  static _low_f_sprintf *low_f_sprintf;
-
   /*! @decl void create(int initial_size)
    *!
    *!   Initializes a new buffer.
@@ -3223,7 +3220,6 @@ PIKECLASS Buffer
     {
       struct Buffer_struct *str = THIS;
       MEMSET( str, 0, sizeof( *str ) );
-      low_f_sprintf = PIKE_MODULE_IMPORT(sprintf, low_f_sprintf);
     }
 
   EXIT
diff --git a/src/module.c b/src/module.c
index 1373973508..4036ca023c 100644
--- a/src/module.c
+++ b/src/module.c
@@ -124,6 +124,10 @@ static void init_builtin_modules(void)
 
   init_facetgroup();
 #endif
+
+  TRACE((stderr, "Init sprintf...\n"));
+
+  init_sprintf();
 }
 
 static void exit_builtin_modules(void)
@@ -136,6 +140,7 @@ static void exit_builtin_modules(void)
 
   /* Clear various global references. */
 
+  exit_sprintf();
 #ifdef AUTO_BIGNUM
   exit_auto_bignum();
 #endif
diff --git a/src/sprintf.c b/src/sprintf.c
new file mode 100644
index 0000000000..2a2c649847
--- /dev/null
+++ b/src/sprintf.c
@@ -0,0 +1,2694 @@
+/*
+|| This file is part of Pike. For copyright information see COPYRIGHT.
+|| Pike is distributed under GPL, LGPL and MPL. See the file COPYING
+|| for more information.
+*/
+
+/* TODO: use ONERROR to cleanup fsp */
+
+/*
+  Pike Sprintf v2.0 By Fredrik Hubinette (Profezzorn@nannymud)
+  Should be reasonably compatible and somewhat faster than v1.05+ of Lynscar's
+  sprintf. It requires the buffering function provided in dynamic_buffer.c
+   Fail-safe memory-leak-protection is implemented through a stack that can
+   be deallocated at any time. If something fails horribly this stack will be
+  deallocated at next call of sprintf. Most operators doesn't need this
+  feature though as they allocate their buffers with alloca() or simply use
+  pointers into other strings.
+  It also has a lot more features.
+
+ Ideas yet to be implemented:
+   Line-break with fill? Lower-case? Upper case? Capitalize?
+   Replace? Justify on decimal point?
+   Change european/american decimal notation?
+   nroff-format? Crack root password and erase all disks?
+   Print-optimize? (space to tab, strip trailing spaces)
+   '>' Kill this field in all lines but the first one
+
+ Examples:
+
+   A short 'people' function (without sort)
+
+   sprintf("%{%-14s %2d %-30s %s\n%}\n",map(users(),lambda(object x)
+       {
+           return ({x->query_real_name(),
+		x->query_level(),
+		query_ip_name(x),
+		file_name(environment(x))
+	      });
+       }))
+
+
+   an 'ls'
+   sprintf("%-#*{%s\n%}\n",width,get_dir(dir));
+
+   debug-dump
+   sprintf("%78*O",width,foo);
+
+   A newspaper
+   sprintf("%-=*s %-=*s\n",width/2,article1,width/2,article2);
+
+   A 'dotted-line' pricelist row 
+   sprintf("%'.'-10s.%'.'4d\n",item,cost);
+
+*/
+
+/*! @decl string sprintf(strict_sprintf_format format, sprintf_args ... args)
+ *!
+ *!   Print formated output to string.
+ *!
+ *!   The @[format] string is a string containing a description of how to
+ *!   output the data in @[args]. This string should generally speaking
+ *!   have one @tt{%@i{<modifiers>@}@i{<operator>@}@} format specifier
+ *!   (examples: @tt{%s@}, @tt{%0d@}, @tt{%-=20s@}) for each of the arguments.
+ *!
+ *!   The following modifiers are supported:
+ *!   @int
+ *!     @value '0'
+ *!       Zero pad numbers (implies right justification).
+ *!     @value '!'
+ *!       Toggle truncation.
+ *!     @value ' '
+ *!       Pad positive integers with a space.
+ *!     @value '+'
+ *!       Pad positive integers with a plus sign.
+ *!     @value '-'
+ *!       Left adjust within field size (default is right).
+ *!     @value '|'
+ *!       Centered within field size.
+ *!     @value '='
+ *!       Column mode if strings are greater than field size. Breaks
+ *!       between words (possibly skipping or adding spaces). Can not be
+ *!       used together with @expr{'/'@}.
+ *!     @value '/'
+ *!       Column mode with rough line break (break at exactly field size
+ *!       instead of between words). Can not be used together with @expr{'='@}.
+ *!     @value '#'
+ *!       Table mode, print a list of @expr{'\n'@} separated words
+ *!       (top-to-bottom order).
+ *!     @value '$'
+ *!       Inverse table mode (left-to-right order).
+ *!     @value 'n'
+ *!     @value ':n'
+ *!       (Where n is a number or *) field width specifier.
+ *!     @value '.n'
+ *!       Precision specifier.
+ *!     @value ';n'
+ *!       Column width specifier.
+ *!     @value '*'
+ *!       If n is a @tt{*@} then next argument is used for precision/field
+ *!       size. The argument may either be an integer, or a modifier mapping
+ *!       as received by @[lfun::_sprintf()]:
+ *!       @mapping
+ *!         @member int|void "precision"
+ *!           Precision.
+ *!         @member int(0..)|void "width"
+ *!           Field width.
+ *!         @member int(0..1)|void "flag_left"
+ *!           Indicates that the output should be left-aligned.
+ *!         @member int(0..)|void "indent"
+ *!           Indentation level in @tt{%O@}-mode.
+ *!       @endmapping
+ *!     @value "'"
+ *!       Set a pad string. @tt{'@} cannot be a part of the pad string (yet).
+ *!     @value '~'
+ *!       Get pad string from argument list.
+ *!     @value '<'
+ *!       Use same argument again.
+ *!     @value '^'
+ *!       Repeat this on every line produced.
+ *!     @value '@'
+ *!       Repeat this format for each element in the argument array.
+ *!     @value '>'
+ *!       Put the string at the bottom end of column instead of top.
+ *!     @value '_'
+ *!       Set width to the length of data.
+ *!     @value '[n]'
+ *!       Select argument number @tt{@i{n@}@}. Use @tt{*@} to use the next
+ *!       argument as selector. The arguments are numbered starting from
+ *!       @expr{0@} (zero) for the first argument after the @[format].
+ *!       Note that this only affects where the current operand is fetched.
+ *!   @endint
+ *!
+ *!   The following operators are supported:
+ *!   @int
+ *!     @value '%'
+ *!       Percent.
+ *!     @value 'b'
+ *!       Signed binary integer.
+ *!     @value 'd'
+ *!       Signed decimal integer.
+ *!     @value 'u'
+ *!       Unsigned decimal integer.
+ *!     @value 'o'
+ *!       Signed octal integer.
+ *!     @value 'x'
+ *!       Lowercase signed hexadecimal integer.
+ *!     @value 'X'
+ *!       Uppercase signed hexadecimal integer.
+ *!     @value 'c'
+ *!       Character. If a fieldsize has been specified this will output
+ *!       the low-order bytes of the integer in network (big endian) byte
+ *!       order. To get little endian byte order, negate the field size.
+ *!     @value 'f'
+ *!       Float. (Locale dependent formatting.)
+ *!     @value 'g'
+ *!       Heuristically chosen representation of float.
+ *!       (Locale dependent formatting.)
+ *!     @value 'G'
+ *!       Like @tt{%g@}, but uses uppercase @tt{E@} for exponent.
+ *!     @value 'e'
+ *!       Exponential notation float. (Locale dependent output.)
+ *!     @value 'E'
+ *!       Like @tt{%e@}, but uses uppercase @tt{E@} for exponent.
+ *!     @value 'F'
+ *!       Binary IEEE representation of float (@tt{%4F@} gives 
+ *!       single precision, @tt{%8F@} gives double precision)
+ *!       in network (big endian) byte order. To get little endian
+ *!       byte order, negate the field size.
+ *!     @value 's'
+ *!       String.
+ *!     @value 'q'
+ *!       Quoted string. Escapes all control and non-8-bit characters,
+ *!       as well as the quote characters @tt{'\\'@} and @tt{'\"'@}.
+ *!     @value 'O'
+ *!       Any value, debug style. Do not rely on the exact formatting;
+ *!       how the result looks can vary depending on locale, phase of
+ *!       the moon or anything else the @[lfun::_sprintf()] method
+ *!       implementor wanted for debugging.
+ *!     @value 'H'
+ *!       Binary Hollerith string. Equivalent to @expr{sprintf("%c%s",
+ *!       strlen(str), str)@}. Arguments (such as width etc) adjust the
+ *!       length-part of the format. Requires 8-bit strings.
+ *!     @value 'n'
+ *!       No argument. Same as @expr{"%s"@} with an empty string as argument.
+ *!       Note: Does take an argument array (but ignores its content)
+ *!       if the modifier @expr{'@@'@} is active.
+ *!     @value 't'
+ *!       Type of the argument.
+ *!     @value '{'
+ *!     @value '}'
+ *!       Perform the enclosed format for every element of the argument array.
+ *!   @endint
+ *!
+ *!   Most modifiers and operators are combinable in any fashion, but some
+ *!   combinations may render strange results.
+ *!
+ *!   If an argument is an object that implements @[lfun::_sprintf()], that
+ *!   callback will be called with the operator as the first argument, and
+ *!   the current modifiers as the second. The callback is expected to return
+ *!   a string.
+ *!
+ *! @note
+ *!   sprintf-style formatting is applied by many formatting functions, such
+ *!   @[write()] and @[werror()]. It is also possible to get sprintf-style
+ *!   compile-time argument checking by using the type-attributes
+ *!   @[sprintf_format] or @[strict_sprintf_format] in combination
+ *!   with @[sprintf_args].
+ *!
+ *! @note
+ *!   The @expr{'q'@} operator was added in Pike 7.7.
+ *!
+ *! @note
+ *!   Support for specifying modifiers via a mapping was added in Pike 7.8.
+ *!   This support can be tested for with the constant
+ *!   @[String.__HAVE_SPRINTF_STAR_MAPPING__].
+ *!
+ *! @note
+ *!   Support for specifying little endian byte order to @expr{'F'@}
+ *!   was added in Pike 7.8. This support can be tested for with the
+ *!   constant @[String.__HAVE_SPRINTF_NEGATIVE_F__].
+ *!
+ *! @example
+ *! @code
+ *! Pike v7.8 release 263 running Hilfe v3.5 (Incremental Pike Frontend)
+ *! > sprintf("The unicode character %c has character code %04X.", 'A', 'A');
+ *! (1) Result: "The unicode character A has character code 0041."
+ *! > sprintf("#%@@02X is the HTML code for purple.", Image.Color.purple->rgb());
+ *! (2) Result: "#A020F0 is the HTML code for purple."
+ *! > int n=4711;
+ *! > sprintf("%d = hexadecimal %x = octal %o = %b binary", n, n, n, n);
+ *! (3) Result: "4711 = hexadecimal 1267 = octal 11147 = 1001001100111 binary"
+ *! > write(#"Formatting examples:
+ *! Left adjusted  [%-10d]
+ *! Centered       [%|10d]
+ *! Right adjusted [%10d]
+ *! Zero padded    [%010d]
+ *! ", n, n, n, n);
+ *! Formatting examples:
+ *! Left adjusted  [4711      ]
+ *! Centered       [   4711   ]
+ *! Right adjusted [      4711]
+ *! Zero padded    [0000004711]
+ *! (5) Result: 142
+ *! int screen_width=70;
+ *! > write("%-=*s\n", screen_width,
+ *! >> "This will wordwrap the specified string within the "+
+ *! >> "specified field size, this is useful say, if you let "+
+ *! >> "users specify their screen size, then the room "+
+ *! >> "descriptions will automagically word-wrap as appropriate.\n"+
+ *! >> "slosh-n's will of course force a new-line when needed.\n");
+ *! This will wordwrap the specified string within the specified field
+ *! size, this is useful say, if you let users specify their screen size,
+ *! then the room descriptions will automagically word-wrap as
+ *! appropriate.
+ *! slosh-n's will of course force a new-line when needed.
+ *! (6) Result: 355
+ *! > write("%-=*s %-=*s\n", screen_width/2,
+ *! >> "Two columns next to each other (any number of columns will "+
+ *! >> "of course work) independantly word-wrapped, can be useful.",
+ *! >> screen_width/2-1,
+ *! >> "The - is to specify justification, this is in addherence "+
+ *! >> "to std sprintf which defaults to right-justification, "+
+ *! >> "this version also supports centre and right justification.");
+ *! Two columns next to each other (any The - is to specify justification,
+ *! number of columns will of course    this is in addherence to std
+ *! work) independantly word-wrapped,   sprintf which defaults to
+ *! can be useful.                      right-justification, this version
+ *!                                     also supports centre and right
+ *!                                     justification.
+ *! (7) Result: 426
+ *! > write("%-$*s\n", screen_width,
+ *! >> "Given a\nlist of\nslosh-n\nseparated\n'words',\nthis option\n"+
+ *! >> "creates a\ntable out\nof them\nthe number of\ncolumns\n"+
+ *! >> "be forced\nby specifying a\npresision.\nThe most obvious\n"+
+ *! >> "use is for\nformatted\nls output.");
+ *! Given a          list of          slosh-n
+ *! separated        'words',         this option
+ *! creates a        table out        of them
+ *! the number of    columns          be forced
+ *! by specifying a  presision.       The most obvious
+ *! use is for       formatted        ls output.
+ *! (8) Result: 312
+ *! > write("%-#*s\n", screen_width,
+ *! >> "Given a\nlist of\nslosh-n\nseparated\n'words',\nthis option\n"+
+ *! >> "creates a\ntable out\nof them\nthe number of\ncolumns\n"+
+ *! >> "be forced\nby specifying a\npresision.\nThe most obvious\n"+
+ *! >> "use is for\nformatted\nls output.");
+ *! Given a          creates a        by specifying a
+ *! list of          table out        presision.
+ *! slosh-n          of them          The most obvious
+ *! separated        the number of    use is for
+ *! 'words',         columns          formatted
+ *! this option      be forced        ls output.
+ *! (9) Result: 312
+ *! > sample = ([ "align":"left", "valign":"middle" ]);
+ *! (10) Result: ([ @xml{/@}* 2 elements *@xml{/@}
+ *!          "align":"left",
+ *!          "valign":"middle"
+ *!        ])
+ *! > write("<td%{ %s='%s'%}>\n", (array)sample);
+ *! <td valign='middle' align='left'>
+ *! (11) Result: 34
+ *! >  write("Of course all the simple printf options "+
+ *! >> "are supported:\n %s: %d %x %o %c\n",
+ *! >> "65 as decimal, hex, octal and a char",
+ *! >> 65, 65, 65, 65);
+ *! Of course all the simple printf options are supported:
+ *!  65 as decimal, hex, octal and a char: 65 41 101 A
+ *! (12) Result: 106
+ *! > write("%[0]d, %[0]x, %[0]X, %[0]o, %[0]c\n", 75);
+ *! 75, 4b, 4B, 113, K
+ *! (13) Result: 19
+ *! > write("%|*s\n",screen_width, "THE END");
+ *!                                THE END
+ *! (14) Result: 71
+ *! > write("%|*s\n", ([ "width":screen_width ]), "ALTERNATIVE END");
+ *!                            ALTERNATIVE END
+ *! (15) Result: 71
+ *! @endcode
+ *!
+ *! @seealso
+ *!   @[lfun::_sprintf()], @[strict_sprintf_format], @[sprintf_format],
+ *!   @[sprintf_args], @[String.__HAVE_SPRINTF_STAR_MAPPING__],
+ *!   @[String.__HAVE_SPRINTF_NEGATIVE_F__].
+ */
+#include "global.h"
+#include "pike_error.h"
+#include "array.h"
+#include "svalue.h"
+#include "stralloc.h"
+#include "dynamic_buffer.h"
+#include "pike_types.h"
+#include "constants.h"
+#include "interpret.h"
+#include "pike_memory.h"
+#include "pike_macros.h"
+#include "object.h"
+#include "bignum.h"
+#include "mapping.h"
+#include "builtin_functions.h"
+#include "operators.h"
+#include "opcodes.h"
+#include "cyclic.h"
+#include "module.h"
+#include "pike_float.h"
+#include <ctype.h>
+#include "module_support.h"
+
+#include <math.h>
+
+#define FORMAT_INFO_STACK_SIZE 200
+#define RETURN_SHARED_STRING
+
+#define SPRINTF_UNDECIDED -1027
+
+struct format_info
+{
+  char *fi_free_string;
+  struct pike_string *to_free_string;
+  PCHARP b;		/* Buffer to format */
+  ptrdiff_t len;	/* Buffer length */
+  ptrdiff_t width;	/* Field width (handled by fix_field) */
+  int precision;	/* Field precision (handled by 'boduxXefgEGsq') */
+  PCHARP pad_string;	/* Padding (handled by fix_field) */
+  ptrdiff_t pad_length;	/* Padding length (handled by fix_field) */
+  int column_entries;	/* Number of column entries (handled by do_one) */
+  short flags;
+  char pos_pad;		/* Positive padding (handled by fix_field) */
+  int column_width;
+  ptrdiff_t column_modulo;
+};
+
+/* FIXME:
+ * re-allocate the format stack if it's too small /Hubbe
+ */
+
+struct format_stack
+{
+  struct format_info *fsp;
+  struct format_info format_info_stack[FORMAT_INFO_STACK_SIZE];
+};
+
+#define FIELD_LEFT	(1<<0)
+#define FIELD_CENTER	(1<<1)
+#define PAD_POSITIVE	(1<<2)
+#define LINEBREAK	(1<<3)
+#define COLUMN_MODE	(1<<4)
+#define ZERO_PAD	(1<<5)
+#define ROUGH_LINEBREAK	(1<<6)
+#define DO_TRUNC	(1<<7)
+#define REPEAT		(1<<8)
+#define SNURKEL		(1<<9)
+#define INVERSE_COLUMN_MODE (1<<10)
+#define MULTI_LINE	(1<<11)
+#define WIDTH_OF_DATA	(1<<12)
+#define MULTI_LINE_BREAK (1<<13)
+
+#define MULTILINE (LINEBREAK | COLUMN_MODE | ROUGH_LINEBREAK | \
+		   INVERSE_COLUMN_MODE | MULTI_LINE | REPEAT)
+
+
+/* Generate binary IEEE strings on a machine which uses a different kind
+   of floating point internally */
+
+#if !defined(NEED_CUSTOM_IEEE) && (SIZEOF_FLOAT_TYPE > 4)
+#define NEED_CUSTOM_IEEE
+#endif
+
+#ifdef NEED_CUSTOM_IEEE
+
+#ifndef HAVE_FPCLASS
+#ifdef HAVE_FP_CLASS_D
+#define fpclass fp_class_d
+#define FP_NZERO FP_NEG_ZERO
+#define FP_PZERO FP_POS_ZERO
+#define FP_NINF FP_NEG_INF
+#define FP_PINF FP_POS_INF
+#define FP_NNORM FP_NEG_NORM
+#define FP_PNORM FP_POS_NORM
+#define FP_NDENORM FP_NEG_DENORM
+#define FP_PDENORM FP_POS_DENORM
+#define HAVE_FPCLASS
+#endif
+#endif
+
+INLINE static void low_write_IEEE_float(char *b, double d, int sz)
+{
+  int maxexp;
+  unsigned INT32 maxf;
+  int s = 0, e = -1;
+  unsigned INT32 f = 0, extra_f=0;
+
+  if(sz==4) {
+    maxexp = 255;
+    maxf   = 0x7fffff;
+  } else {
+    maxexp = 2047;
+    maxf   = 0x0fffff; /* This is just the high part of the mantissa... */
+  }
+
+#ifdef HAVE_FPCLASS
+  switch(fpclass(d)) {
+  case FP_SNAN:
+    e = maxexp; f = 2; break;
+  case FP_QNAN:
+    e = maxexp; f = maxf; break;
+  case FP_NINF:
+    s = 1; /* FALLTHRU */
+  case FP_PINF:
+    e = maxexp; break;
+  case FP_NZERO:
+    s = 1; /* FALLTHRU */
+  case FP_PZERO:
+    e = 0; break;
+  case FP_NNORM:
+  case FP_NDENORM:
+    s = 1; d = fabs(d); break;
+  case FP_PNORM:
+  case FP_PDENORM:
+    break;
+  default:
+    if(d<0.0) {
+      s = 1;
+      d = fabs(d);
+    }
+    break;
+  }
+#else
+#ifdef HAVE_ISINF
+  if(isinf(d))
+    e = maxexp;
+  else
+#endif
+  if(PIKE_ISNAN(d)) {
+    e = maxexp; f = maxf;
+  } else
+#ifdef HAVE_ISZERO
+  if(iszero(d))
+    e = 0;
+  else
+#endif
+#ifdef HAVE_FINITE
+  if(!finite(d))
+    e = maxexp;
+#endif
+  ; /* Terminate any remaining else */
+#ifdef HAVE_SIGNBIT
+  if((s = signbit(d)))
+    d = fabs(d);
+#else
+  if(d<0.0) {
+    s = 1;
+    d = fabs(d);
+  }
+#endif
+#endif
+
+  if(e<0) {
+    d = FREXP(d, &e);
+    if(d == 1.0) {
+      d=0.5;
+      e++;
+    }
+    if(d == 0.0) {
+      e = 0;
+      f = 0;
+    } else if(sz==4) {
+      e += 126;
+      d *= 16777216.0;
+      if(e<=0) {
+	d = LDEXP(d, e-1);
+	e = 0;
+      }
+      f = ((INT32)floor(d))&maxf;
+    } else {
+      double d2;
+      e += 1022;
+      d *= 2097152.0;
+      if(e<=0) {
+	d = LDEXP(d, e-1);
+	e = 0;
+      }
+      d2 = floor(d);
+      f = ((INT32)d2)&maxf;
+      d -= d2;
+      d += 1.0;
+      extra_f = (unsigned INT32)(floor(d * 4294967296.0)-4294967296.0);
+    }
+
+    if(e>=maxexp) {
+      e = maxexp;
+      f = extra_f = 0;
+    }
+  }
+
+  if(sz==4) {
+    b[0] = (s? 128:0)|((e&0xfe)>>1);
+    b[1] = ((e&1)<<7)|((f&0x7f0000)>>16);
+    b[2] = (f&0xff00)>>8;
+    b[3] = f&0xff;
+  } else {
+    b[0] = (s? 128:0)|((e&0x7f0)>>4);
+    b[1] = ((e&0xf)<<4)|((f&0x0f0000)>>16);
+    b[2] = (f&0xff00)>>8;
+    b[3] = f&0xff;
+    b[4] = (extra_f&0xff000000)>>24;
+    b[5] = (extra_f&0xff0000)>>16;
+    b[6] = (extra_f&0xff00)>>8;
+    b[7] = extra_f&0xff;
+  }
+}
+#endif
+
+/* Position a string inside a field with fill */
+
+INLINE static void fix_field(struct string_builder *r,
+			     PCHARP b,
+			     ptrdiff_t len,
+			     int flags,
+			     ptrdiff_t width,
+			     PCHARP pad_string,
+			     ptrdiff_t pad_length,
+			     char pos_pad)
+{
+  ptrdiff_t e;
+  if(!width || width==SPRINTF_UNDECIDED)
+  {
+    if(pos_pad && EXTRACT_PCHARP(b)!='-') string_builder_putchar(r,pos_pad);
+    string_builder_append(r,b,len);
+    return;
+  }
+
+  if(!(flags & DO_TRUNC) && len+(pos_pad && EXTRACT_PCHARP(b)!='-')>=width)
+  {
+    if(pos_pad && EXTRACT_PCHARP(b)!='-') string_builder_putchar(r,pos_pad);
+    string_builder_append(r,b,len);
+    return;
+  }
+
+  if (flags & (ZERO_PAD|FIELD_CENTER|FIELD_LEFT)) {
+    /* Some flag is set. */
+
+    if(flags & ZERO_PAD)		/* zero pad is kind of special... */
+    {
+      if(EXTRACT_PCHARP(b)=='-')
+      {
+  	string_builder_putchar(r,'-');
+  	INC_PCHARP(b,1);
+  	len--;
+  	width--;
+      }else{
+  	if(pos_pad)
+  	{
+  	  string_builder_putchar(r,pos_pad);
+  	  width--;
+  	}
+      }
+#if 1
+      string_builder_fill(r,width-len,MKPCHARP("0",0),1,0);
+#else
+      for(;width>len;width--) string_builder_putchar(r,'0');
+#endif
+      string_builder_append(r,b,len);
+      return;
+    }
+  
+    if (flags & (FIELD_CENTER|FIELD_LEFT)) {
+      ptrdiff_t d=0;
+  
+      if(flags & FIELD_CENTER)
+      {
+  	e=len;
+  	if(pos_pad && EXTRACT_PCHARP(b)!='-') e++;
+  	e=(width-e)/2;
+  	if(e>0)
+  	{
+  	  string_builder_fill(r, e, pad_string, pad_length, 0);
+  	  width-=e;
+  	}
+      }
+  
+      /* Left adjust */
+      if(pos_pad && EXTRACT_PCHARP(b)!='-')
+      {
+  	string_builder_putchar(r,pos_pad);
+  	width--;
+  	d++;
+      }
+
+#if 1
+      len = MINIMUM(width, len);
+      if (len) {
+        d += len;
+	string_builder_append(r, b, len);
+	width -= len;
+      }
+#else /* 0 */  
+      d+=MINIMUM(width,len);
+      while(len && width)
+      {
+  	string_builder_putchar(r,EXTRACT_PCHARP(b));
+  	INC_PCHARP(b,1);
+  	len--;
+  	width--;
+      }
+#endif /* 1 */
+  
+      if(width>0)
+      {
+	d%=pad_length;
+	string_builder_fill(r, width, pad_string, pad_length, d);
+      }
+      
+      return;
+    }
+  }
+
+  /* Right-justification */
+
+  if(pos_pad && EXTRACT_PCHARP(b)!='-') len++;
+  e=width-len;
+  if(e>0)
+  {
+    string_builder_fill(r, e, pad_string, pad_length, 0);
+    width-=e;
+  }
+
+  if(pos_pad && EXTRACT_PCHARP(b)!='-' && len==width)
+  {
+    string_builder_putchar(r,pos_pad);
+    len--;
+    width--;
+  }
+  INC_PCHARP(b,len-width);
+  string_builder_append(r,b,width);
+}
+
+static void free_sprintf_strings(struct format_stack *fs)
+{
+  for(;fs->fsp>=fs->format_info_stack;fs->fsp--)
+  {
+    if(fs->fsp->fi_free_string) free(fs->fsp->fi_free_string);
+    fs->fsp->fi_free_string=0;
+    if(fs->fsp->to_free_string) free_string(fs->fsp->to_free_string);
+    fs->fsp->to_free_string=0;
+  }
+}
+
+static void sprintf_error(struct format_stack *fs,
+			  char *s,...) ATTRIBUTE((noreturn,format (printf, 2, 3)));
+static void sprintf_error(struct format_stack *fs,
+			  char *s,...)
+{
+  char buf[100];
+  va_list args;
+  va_start(args,s);
+  free_sprintf_strings(fs);
+
+  sprintf(buf,"sprintf: %s",s);
+  va_error(buf,args);
+  va_end(args);
+}
+
+/* This is called once for every '%' on every outputted line
+ * it takes care of linebreak and column mode. It returns 1
+ * if there is more for next line.
+ */
+
+INLINE static int do_one(struct format_stack *fs,
+			 struct string_builder *r,
+			 struct format_info *f)
+{
+  PCHARP rest;
+  ptrdiff_t e, d, lastspace;
+
+  rest.ptr=0;
+  if(f->flags & (LINEBREAK|ROUGH_LINEBREAK))
+  {
+    if(f->width==SPRINTF_UNDECIDED)
+      sprintf_error(fs, "Must have field width for linebreak.\n");
+    lastspace=-1;
+    for(e=0;e<f->len && e<=f->width;e++)
+    {
+      switch(INDEX_PCHARP(f->b,e))
+      {
+	case '\n':
+	  lastspace=e;
+	  rest=ADD_PCHARP(f->b,e+1);
+	  break;
+
+	case ' ':
+	  if(f->flags & LINEBREAK)
+	  {
+	    lastspace=e;
+	    rest=ADD_PCHARP(f->b,e+1);
+	  }
+	default:
+	  continue;
+      }
+      break;
+    }
+    if(e==f->len && f->len<=f->width)
+    {
+      lastspace=e;
+      rest=ADD_PCHARP(f->b,lastspace);
+    }else if(lastspace==-1){
+      lastspace=MINIMUM(f->width,f->len);
+      rest=ADD_PCHARP(f->b,lastspace);
+    }
+    fix_field(r,
+	      f->b,
+	      lastspace,
+	      f->flags,
+	      f->width,
+	      f->pad_string,
+	      f->pad_length,
+	      f->pos_pad);
+  }
+  else if(f->flags & INVERSE_COLUMN_MODE)
+  {
+    if(f->width==SPRINTF_UNDECIDED)
+      sprintf_error(fs, "Must have field width for column mode.\n");
+    e=f->width/(f->column_width+1);
+    if(!f->column_width || e<1) e=1;
+
+    rest=f->b;
+    for(d=0;INDEX_PCHARP(rest,d) && e;d++)
+    {
+#if 0
+      if(rest != f->b)
+	fix_field(" ",1,0,1," ",1,0);
+#endif
+
+      while(INDEX_PCHARP(rest,d) && INDEX_PCHARP(rest,d)!='\n')
+	d++;
+
+      fix_field(r,
+		rest,
+		d,
+		f->flags,
+		f->column_width,
+		f->pad_string,
+		f->pad_length,
+		f->pos_pad);
+
+      e--;
+      INC_PCHARP(rest,d);
+      d=-1;
+      if(EXTRACT_PCHARP(rest)) INC_PCHARP(rest,1);
+    }
+  }
+  else if(f->flags & COLUMN_MODE)
+  {
+    ptrdiff_t mod;
+    ptrdiff_t col;
+    PCHARP end;
+
+    if(f->width==SPRINTF_UNDECIDED)
+      sprintf_error(fs, "Must have field width for column mode.\n");
+    mod=f->column_modulo;
+    col=f->width/(f->column_width+1);
+    if(!f->column_width || col<1) col=1;
+    rest=f->b;
+    end=ADD_PCHARP(rest,f->len);
+
+    for(d=0;rest.ptr && d<col;d++)
+    {
+#if 0
+      if(rest != f->b)
+	fix_field(" ",1,0,1," ",1,0);
+#endif
+
+      /* Find end of entry */
+      for(e=0;COMPARE_PCHARP(ADD_PCHARP(rest, e),<,end) &&
+	    INDEX_PCHARP(rest,e)!='\n';e++);
+
+      fix_field(r,rest,e,f->flags,f->column_width,
+		f->pad_string,f->pad_length,f->pos_pad);
+
+      f->column_entries--;
+
+      /* Advance to after entry */
+      INC_PCHARP(rest,e);
+      if(!COMPARE_PCHARP(rest,<,end)) break;
+      INC_PCHARP(rest,1);
+
+      for(e=1;e<mod;e++)
+      {
+	PCHARP s=MEMCHR_PCHARP(rest,'\n',SUBTRACT_PCHARP(end,rest));
+	if(s.ptr)
+	{
+	  rest=ADD_PCHARP(s,1);
+	}else{
+	  rest.ptr=0;
+	  break;
+	}
+      }
+    }
+    if(f->column_entries>0)
+    {
+      for(rest=f->b;COMPARE_PCHARP(rest,<,end) &&
+	    EXTRACT_PCHARP(rest)!='\n';INC_PCHARP(rest,1));
+      if(COMPARE_PCHARP(rest,<,end)) INC_PCHARP(rest,1);
+    }else{
+      rest.ptr=0;
+    }
+  }
+  else
+  {
+    fix_field(r,f->b,f->len,f->flags,f->width,
+	      f->pad_string,f->pad_length,f->pos_pad);
+  }
+
+  if(f->flags & REPEAT) return 0;
+  if(rest.ptr)
+  {
+    f->len-=SUBTRACT_PCHARP(rest,f->b);
+    f->b=rest;
+  }else{
+    f->len=0;
+    f->b=MKPCHARP("",0);
+  }
+  return f->len>0;
+}
+
+
+#define GET_SVALUE(VAR) \
+  if(arg) \
+  { \
+    VAR=arg; \
+    arg=0; \
+  }else{ \
+    if(argument >= num_arg) \
+    { \
+      sprintf_error(fs, "Too few arguments to sprintf.\n"); \
+      break; /* make gcc happy */ \
+    } \
+    VAR=lastarg=argp+(argument++); \
+  }
+
+#define PEEK_SVALUE(VAR) \
+  if(arg) \
+  { \
+    VAR=arg; \
+  }else{ \
+    if(argument >= num_arg) \
+    { \
+      sprintf_error(fs, "Too few arguments to sprintf.\n"); \
+      break; /* make gcc happy */ \
+    } \
+    VAR=argp+argument; \
+  }
+
+#define GET(VAR,PIKE_TYPE,TYPE_NAME,EXTENSION) \
+  { \
+    struct svalue *tmp_; \
+    GET_SVALUE(tmp_); \
+    if(TYPEOF(*tmp_) != PIKE_TYPE) \
+    { \
+      sprintf_error(fs, "Wrong type for argument %d: expected %s, got %s.\n",argument+1,TYPE_NAME, \
+		    get_name_of_type(TYPEOF(*tmp_))); \
+      break; /* make gcc happy */ \
+    } \
+    VAR=tmp_->u.EXTENSION; \
+  }
+
+#define GET_INT(VAR) GET(VAR,T_INT,"integer",integer)
+#define GET_STRING(VAR) GET(VAR,T_STRING,"string",string)
+#define GET_FLOAT(VAR) GET(VAR,T_FLOAT,"float",float_number)
+#define GET_ARRAY(VAR) GET(VAR,T_ARRAY,"array",array)
+#define GET_OBJECT(VAR) GET(VAR,T_OBJECT,"object",object)
+
+#define DO_OP()								\
+   if(fs->fsp->flags & SNURKEL)						\
+   {									\
+     ONERROR _e;							\
+     struct array *_v;							\
+     struct string_builder _b;						\
+     init_string_builder(&_b,0);					\
+     SET_ONERROR(_e, free_string_builder, &_b);				\
+     GET_ARRAY(_v);							\
+     for(tmp=0;tmp<_v->size;tmp++)					\
+     {									\
+       struct svalue *save_sp=Pike_sp;					\
+       array_index_no_free(Pike_sp,_v,tmp);				\
+       Pike_sp++;							\
+       low_pike_sprintf(fs, &_b,begin,SUBTRACT_PCHARP(a,begin)+1,	\
+			Pike_sp-1,1,nosnurkel+1, compat_mode);		\
+       if(save_sp < Pike_sp) pop_stack();				\
+     }									\
+     fs->fsp->b=MKPCHARP_STR(_b.s);					\
+     fs->fsp->len=_b.s->len;						\
+     fs->fsp->fi_free_string=(char *)_b.s;				\
+     fs->fsp->pad_string=MKPCHARP(" ",0);				\
+     fs->fsp->pad_length=1;						\
+     fs->fsp->column_width=0;						\
+     fs->fsp->pos_pad=0;						\
+     fs->fsp->flags=0;							\
+     fs->fsp->width=fs->fsp->precision=SPRINTF_UNDECIDED;		\
+     UNSET_ONERROR(_e);							\
+     break;                                                             \
+   }
+
+#define CHECK_OBJECT_SPRINTF()						      \
+	{								      \
+	   /* NOTE: It would be nice if this was a do { } while(0) macro */   \
+	   /* but it cannot be since we need to break out of the case... */   \
+	  struct svalue *sv;						      \
+	  PEEK_SVALUE(sv);						      \
+	  if(TYPEOF(*sv) == T_OBJECT && sv->u.object->prog)		      \
+	  {                                                                   \
+            ptrdiff_t fun=FIND_LFUN(sv->u.object->prog, LFUN__SPRINTF);	      \
+	    if (fun != -1) {						      \
+              DECLARE_CYCLIC();						      \
+              if (!BEGIN_CYCLIC(sv->u.object, fun))			      \
+              {                                                               \
+              	int n=0;						      \
+	      	push_int(EXTRACT_PCHARP(a));				      \
+	      	if (fs->fsp->precision!=SPRINTF_UNDECIDED)		      \
+	      	{							      \
+	      	   push_constant_text("precision");			      \
+	      	   push_int(fs->fsp->precision);			      \
+              	   n+=2;						      \
+	      	}							      \
+	      	if (fs->fsp->width!=SPRINTF_UNDECIDED)			      \
+	      	{							      \
+	      	   push_constant_text("width");	           		      \
+	      	   push_int64(fs->fsp->width);				      \
+              	   n+=2;						      \
+	      	}							      \
+	      	if ((fs->fsp->flags&FIELD_LEFT))			      \
+	      	{							      \
+	      	   push_constant_text("flag_left");	       		      \
+	      	   push_int(1);						      \
+              	   n+=2;						      \
+	      	}							      \
+	      	f_aggregate_mapping(n);					      \
+	      								      \
+	      	SET_CYCLIC_RET(1);					      \
+	      	apply_low(sv->u.object, fun, 2);                              \
+									      \
+		if(TYPEOF(Pike_sp[-1]) == T_STRING)			      \
+	      	{	                                                      \
+              	  DO_IF_DEBUG( if(fs->fsp->to_free_string)                    \
+              		       Pike_fatal("OOps in sprintf\n"); )             \
+              	  fs->fsp->to_free_string = (--Pike_sp)->u.string;            \
+	      								      \
+	      	  fs->fsp->b = MKPCHARP_STR(fs->fsp->to_free_string);	      \
+	      	  fs->fsp->len = fs->fsp->to_free_string->len;		      \
+	      								      \
+              	  /* We have to lift one argument from the format stack. */   \
+              	  GET_SVALUE(sv);                                             \
+                  END_CYCLIC();						      \
+	      	  break;						      \
+	      	}							      \
+	      	if(!SAFE_IS_ZERO(Pike_sp-1))				      \
+	      	{							      \
+	      	   sprintf_error(fs,"argument %d (object) returned "	      \
+	      			 "illegal value from _sprintf()\n",	      \
+				 argument+1);				      \
+	      	}							      \
+	      	pop_stack();						      \
+              }								      \
+              END_CYCLIC();						      \
+	    }								      \
+	  }								      \
+	}
+
+/* This is the main pike_sprintf function, note that it calls itself
+ * recursively during the '%{ %}' parsing. The string is stored in
+ * the buffer in save_objectII.c
+ */
+
+#if 0
+/* Looks to me like forgotten debug code. Anyway, we can't make this
+ * fatal since it might trig in _sprintf/sprintf recursions in pike
+ * code. /mast */
+#undef check_c_stack
+#define check_c_stack(X) do {                                           \
+    ptrdiff_t x_= (((char *)&x_) - Pike_interpreter.stack_top) +	\
+      STACK_DIRECTION * (Pike_interpreter.c_stack_margin + (X));	\
+  x_*=STACK_DIRECTION;                                                  \
+  if(x_>0) {                                                            \
+    /*low_error(Pike_check_c_stack_errmsg);*/                           \
+    Pike_fatal("C stack overflow (%d): x_:%p &x_:%p top: %p margin: %p\n",	\
+               (X), x_, &x_, Pike_interpreter.stack_top,		\
+               Pike_interpreter.c_stack_margin);                        \
+  }                                                                     \
+  }while(0)
+#endif
+
+
+static void low_pike_sprintf(struct format_stack *fs,
+			     struct string_builder *r,
+			     PCHARP format,
+			     ptrdiff_t format_len,
+			     struct svalue *argp,
+			     ptrdiff_t num_arg,
+			     int nosnurkel,
+			     int compat_mode)
+{
+  int argument=0;
+  int tmp,setwhat,d,e,indent;
+  char buffer[140];
+  struct format_info *f,*start;
+  double tf;
+  struct svalue *arg=0;	/* pushback argument */
+  struct svalue *lastarg=0;
+
+  PCHARP a,begin;
+  PCHARP format_end=ADD_PCHARP(format,format_len);
+
+  check_c_stack(500);
+
+  start=fs->fsp;
+  for(a=format;COMPARE_PCHARP(a,<,format_end);INC_PCHARP(a,1))
+  {
+    int num_snurkel;
+
+    if(fs->fsp-fs->format_info_stack==FORMAT_INFO_STACK_SIZE - 1)
+      sprintf_error(fs, "Sprintf stack overflow.\n");
+    fs->fsp++;
+#ifdef PIKE_DEBUG
+    if(fs->fsp < fs->format_info_stack)
+      Pike_fatal("sprintf: fs->fsp out of bounds.\n");
+#endif
+    fs->fsp->pad_string=MKPCHARP(" ",0);
+    fs->fsp->pad_length=1;
+    fs->fsp->fi_free_string=0;
+    fs->fsp->to_free_string=0;
+    fs->fsp->column_width=0;
+    fs->fsp->pos_pad = 0;
+    fs->fsp->flags = 0;
+    fs->fsp->width=fs->fsp->precision=SPRINTF_UNDECIDED;
+
+    if(EXTRACT_PCHARP(a)!='%')
+    {
+      for(e=0;INDEX_PCHARP(a,e)!='%' &&
+	    COMPARE_PCHARP(ADD_PCHARP(a,e),<,format_end);e++);
+      fs->fsp->b=a;
+      fs->fsp->len=e;
+      fs->fsp->width=e;
+      INC_PCHARP(a,e-1);
+      continue;
+    }
+    num_snurkel=0;
+    arg=NULL;
+    setwhat=0;
+    begin=a;
+    indent = 0;
+
+    for(INC_PCHARP(a,1);;INC_PCHARP(a,1))
+    {
+#if 0
+      fprintf(stderr,"sprintf-flop: %d (%c)\n",
+	      EXTRACT_PCHARP(a),EXTRACT_PCHARP(a));
+#endif
+      switch(EXTRACT_PCHARP(a))
+      {
+      default:
+	if(EXTRACT_PCHARP(a) < 256 && 
+	   isprint(EXTRACT_PCHARP(a)))
+	{
+	  sprintf_error(fs, "Error in format string, %c is not a format.\n",
+			EXTRACT_PCHARP(a));
+	}else{
+	  sprintf_error(fs,"Error in format string, U%08x is not a format.\n",
+			EXTRACT_PCHARP(a));
+	}
+
+      /* First the modifiers */
+      case '0':
+	 if (setwhat<2) 
+	 { 
+	    fs->fsp->flags|=ZERO_PAD; 
+	    continue; 
+	 }
+      case '1': case '2': case '3':
+      case '4': case '5': case '6':
+      case '7': case '8': case '9':
+	tmp=STRTOL_PCHARP(a,&a,10);
+	INC_PCHARP(a,-1);
+	goto got_arg;
+
+      case '*':
+	{
+	  struct svalue *sval;
+	  struct mapping *m;
+	  GET_SVALUE(sval);
+	  if (TYPEOF(*sval) == T_INT) {
+	    tmp = sval->u.integer;
+	    goto got_arg;
+	  } else if (TYPEOF(*sval) != T_MAPPING) {
+	    sprintf_error(fs, "Wrong type for argument %d: "
+			  "expected %s, got %s.\n",
+			  argument+1, "int|mapping(string:int)",
+			  get_name_of_type(TYPEOF(*sval)));
+	  }
+	  m = sval->u.mapping;
+	  if ((sval = simple_mapping_string_lookup(m, "precision")) &&
+	      (TYPEOF(*sval) == T_INT)) {
+	    fs->fsp->precision = sval->u.integer;
+	  }
+	  if ((sval = simple_mapping_string_lookup(m, "width")) &&
+	      (TYPEOF(*sval) == T_INT) && (sval->u.integer >= 0)) {
+	    fs->fsp->width = sval->u.integer;
+	  }
+	  if ((sval = simple_mapping_string_lookup(m, "flag_left")) &&
+	      (TYPEOF(*sval) == T_INT)) {
+	    if (sval->u.integer) {
+	      fs->fsp->flags |= FIELD_LEFT;
+	    } else {
+	      fs->fsp->flags &= ~FIELD_LEFT;
+	    }
+	  }
+	  if ((sval = simple_mapping_string_lookup(m, "indent")) &&
+	      (TYPEOF(*sval) == T_INT) && (sval->u.integer >= 0)) {
+	    indent = sval->u.integer;
+	  }
+	  continue;
+	}
+
+      got_arg:
+	switch(setwhat)
+	{
+	case 0:
+	case 1:
+	  if(tmp < 0) sprintf_error(fs, "Illegal width %d.\n", tmp);
+	  fs->fsp->width=tmp;
+	  if (!setwhat) break;
+	case 2: fs->fsp->precision=tmp; break;
+	case 3: fs->fsp->column_width=tmp; break;
+	case 4: fs->fsp->precision=-tmp; break;
+	}
+	continue;
+
+      case ';': setwhat=3; continue;
+      case '.': setwhat=2; continue;
+      case ':': setwhat=1; continue;
+
+      case '=': fs->fsp->flags|=LINEBREAK;
+	if (fs->fsp->flags & ROUGH_LINEBREAK)
+	  sprintf_error(fs,
+			"Combining modifiers '=' and '/' is not allowed.\n");
+	continue;
+      case '/': fs->fsp->flags|=ROUGH_LINEBREAK;
+	if (fs->fsp->flags & LINEBREAK)
+	  sprintf_error(fs,
+			"Combining modifiers '=' and '/' is not allowed.\n");
+	continue;
+      case '#': fs->fsp->flags|=COLUMN_MODE; continue;
+      case '$': fs->fsp->flags|=INVERSE_COLUMN_MODE; continue;
+
+      case '-':
+	if(setwhat==2)
+	  setwhat=4;
+	else
+	  fs->fsp->flags|=FIELD_LEFT;
+	continue;
+      case '|': fs->fsp->flags|=FIELD_CENTER; continue;
+      case ' ': fs->fsp->pos_pad=' '; continue;
+      case '+': fs->fsp->pos_pad='+'; continue;
+      case '!': fs->fsp->flags^=DO_TRUNC; continue;
+      case '^': fs->fsp->flags|=REPEAT; continue;
+      case '>': fs->fsp->flags|=MULTI_LINE_BREAK; continue;
+      case '_': fs->fsp->flags|=WIDTH_OF_DATA; continue;
+      case '@':
+	if(++num_snurkel > nosnurkel)
+	  fs->fsp->flags|=SNURKEL;
+	continue;
+
+      case '\'':
+	tmp=0;
+	for(INC_PCHARP(a,1);INDEX_PCHARP(a,tmp)!='\'';tmp++)
+	{
+#if 0
+	  fprintf(stderr, "Sprinf-glop: %d (%c)\n",
+		  INDEX_PCHARP(a,tmp), INDEX_PCHARP(a,tmp));
+#endif
+	  if(COMPARE_PCHARP(ADD_PCHARP(a, tmp),>=,format_end))
+	    sprintf_error(fs, "Unfinished pad string in format string.\n");
+	}
+	if(tmp)
+	{
+	  fs->fsp->pad_string=a;
+	  fs->fsp->pad_length=tmp;
+	}
+	INC_PCHARP(a,tmp);
+	continue;
+
+      case '~':
+      {
+	struct pike_string *s;
+	GET_STRING(s);
+	if (s->len) {
+	  fs->fsp->pad_string=MKPCHARP_STR(s);
+	  fs->fsp->pad_length=s->len;
+	}
+	continue;
+      }
+
+      case '<':
+	if(!lastarg)
+	  sprintf_error(fs, "No last argument.\n");
+	arg=lastarg;
+	continue;
+
+      case '[':
+	INC_PCHARP(a,1);
+	if(EXTRACT_PCHARP(a)=='*') {
+	  GET_INT(tmp);
+	  INC_PCHARP(a,1);
+	} else
+	  tmp=STRTOL_PCHARP(a,&a,10);
+	if(EXTRACT_PCHARP(a)!=']') 
+	  sprintf_error(fs, "Expected ] in format string, not %c.\n",
+			EXTRACT_PCHARP(a));
+	if(tmp >= num_arg)
+	  sprintf_error(fs, "Not enough arguments to [%d].\n",tmp);
+	arg = argp+tmp;
+	continue;
+	
+        /* now the real operators */
+
+      case '{':
+      {
+	struct array *w;
+	struct string_builder b;
+#ifdef PIKE_DEBUG
+	struct format_info *fsp_save=fs->fsp;
+#endif
+	DO_OP();
+	for(e=1,tmp=1;tmp;e++)
+	{
+	  if (!INDEX_PCHARP(a,e) &&
+	      !COMPARE_PCHARP(ADD_PCHARP(a,e),<,format_end)) {
+	    sprintf_error(fs, "Missing %%} in format string.\n");
+	    break;		/* UNREACHED */
+	  } else if(INDEX_PCHARP(a,e)=='%') {
+	    switch(INDEX_PCHARP(a,e+1))
+	    {
+	    case '%': e++; break;
+	    case '}': tmp--; break;
+	    case '{': tmp++; break;
+	    }
+	  }
+	}
+            
+	GET_ARRAY(w);
+	if(!w->size)
+	{
+	  fs->fsp->b=MKPCHARP("",0);
+	  fs->fsp->len=0;
+	}else{
+	  ONERROR err;
+	  init_string_builder(&b,0);
+	  SET_ONERROR(err,free_string_builder,&b);
+	  for(tmp=0;tmp<w->size;tmp++)
+	  {
+	    struct svalue *s;
+	    union anything *q;
+
+/*	    check_threads_etc(); */
+	    q=low_array_get_item_ptr(w,tmp,T_ARRAY);
+	    s=Pike_sp;
+	    if(q)
+	    {
+	      add_ref(q->array);
+	      push_array_items(q->array);
+	    }else{
+	      array_index_no_free(Pike_sp,w,tmp);
+	      Pike_sp++;
+	    }
+	    low_pike_sprintf(fs, &b,ADD_PCHARP(a,1),e-2,s,Pike_sp-s,0,
+			     compat_mode);
+	    pop_n_elems(Pike_sp-s);
+	  }
+#ifdef PIKE_DEBUG
+	  if(fs->fsp < fs->format_info_stack)
+	    Pike_fatal("sprintf: fs->fsp out of bounds.\n");
+	  if(fs->fsp!=fsp_save)
+	    Pike_fatal("sprintf: fs->fsp incorrect after recursive sprintf.\n");
+#endif
+	  fs->fsp->b=MKPCHARP_STR(b.s);
+	  fs->fsp->len=b.s->len;
+	  fs->fsp->fi_free_string=(char *)b.s;
+	  UNSET_ONERROR(err);
+	}
+	
+	INC_PCHARP(a,e);
+	break;
+      }
+
+      case '%':
+	fs->fsp->b=MKPCHARP("%",0);
+	fs->fsp->len=fs->fsp->width=1;
+	break;
+
+      case 'n':
+	DO_OP();
+	fs->fsp->b=MKPCHARP("",0);
+	fs->fsp->len=0;
+	break;
+
+      case 't':
+      {
+	struct svalue *t;
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_SVALUE(t);
+	fs->fsp->b = MKPCHARP(get_name_of_type(TYPEOF(*t)),0);
+	fs->fsp->len=strlen((char *)fs->fsp->b.ptr);
+	break;
+      }
+
+      case 'c':
+      {
+        INT_TYPE tmp;
+	ptrdiff_t l,n;
+	char *x;
+        DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	if(fs->fsp->width == SPRINTF_UNDECIDED)
+	{
+	  GET_INT(tmp);
+	  x=(char *)alloca(4);
+	  if(tmp<256) fs->fsp->b=MKPCHARP(x,0);
+	  else if(tmp<65536) fs->fsp->b=MKPCHARP(x,1);
+	  else  fs->fsp->b=MKPCHARP(x,2);
+	  SET_INDEX_PCHARP(fs->fsp->b,0,tmp);
+	  fs->fsp->len=1;
+	}
+	else if ( (fs->fsp->flags&FIELD_LEFT) )
+	{
+	  l=1;
+	  if(fs->fsp->width > 0) l=fs->fsp->width;
+	  x=(char *)alloca(l);
+	  fs->fsp->b=MKPCHARP(x,0);
+	  fs->fsp->len=l;
+	  GET_INT(tmp);
+	  n=0;
+	  while(n<l)
+	  {
+	    x[n++]=tmp & 0xff;
+	    tmp>>=8;
+	  }
+	}
+	else 
+	{
+	  l=1;
+	  if(fs->fsp->width > 0) l=fs->fsp->width;
+	  x=(char *)alloca(l);
+	  fs->fsp->b=MKPCHARP(x,0);
+	  fs->fsp->len=l;
+	  GET_INT(tmp);
+	  while(--l>=0)
+	  {
+	    x[l]=tmp & 0xff;
+	    tmp>>=8;
+	  }
+	}
+	break;
+      }
+
+      case 'H':
+      {
+	struct string_builder buf;
+	struct pike_string *s;
+        ptrdiff_t tmp;
+	ptrdiff_t l,n;
+	char *x;
+
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_STRING(s);
+	if( s->size_shift )
+	  sprintf_error(fs, "%%H requires all characters in the string "
+			"to be at most eight bits large\n");
+
+	tmp = s->len;
+        l=1;
+        if(fs->fsp->width > 0)
+          l=fs->fsp->width;
+        else if(fs->fsp->flags&ZERO_PAD)
+          sprintf_error(fs, "Length of string to %%H is 0.\n");
+
+	/* Note: The >>-operator performs an implicit % (sizeof(tmp)*8)
+	 *       on the shift operand on some architectures. */
+        if( (l < (ptrdiff_t) sizeof(tmp)) && (tmp>>(l*8)))
+	  sprintf_error(fs, "Length of string to %%%"PRINTPTRDIFFT"dH "
+			"too large.\n", l);
+
+
+        x=(char *)alloca(l);
+        fs->fsp->b=MKPCHARP(x,0);
+        fs->fsp->len=l;
+
+        if ( (fs->fsp->flags&FIELD_LEFT) )
+	{
+	  n=0;
+	  while(n<l)
+	  {
+	    x[n++]=tmp & 0xff;
+	    tmp>>=8;
+	  }
+	}
+	else 
+	{
+	  while(--l>=0)
+	  {
+	    x[l]=tmp & 0xff;
+	    tmp>>=8;
+	  }
+	}
+
+	init_string_builder_alloc(&buf, s->len+fs->fsp->len, 0);
+	string_builder_append(&buf,fs->fsp->b,fs->fsp->len);
+	string_builder_shared_strcat(&buf,s);
+	
+	fs->fsp->b = MKPCHARP_STR(buf.s);
+	fs->fsp->len = buf.s->len;
+	buf.s->len = buf.malloced;
+	fs->fsp->to_free_string = buf.s;
+	break;
+      }
+
+
+      /*
+	WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+	WARNING                                                 WARNING
+	WARNING   This routine is not very well tested, so it   WARNING
+	WARNING   may give errouneous results.   /Noring        WARNING
+	WARNING                                                 WARNING
+	WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+       */
+      case 'b':
+      case 'o':
+      case 'd':
+      case 'u':
+      case 'x':
+      case 'X':
+      {
+	int base = 0, mask_size = 0;
+	char mode, *x;
+	INT_TYPE val;
+	
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_INT(val);
+
+	if(fs->fsp->precision != SPRINTF_UNDECIDED && fs->fsp->precision > 0)
+	  mask_size = fs->fsp->precision;
+	
+	mode=EXTRACT_PCHARP(a);
+	x=(char *)alloca(sizeof(val)*CHAR_BIT + 4 + mask_size);
+	fs->fsp->b=MKPCHARP(x,0);
+	
+	switch(mode)
+	{
+	  case 'b': base = 1; break;
+	  case 'o': base = 3; break;
+	  case 'x': base = 4; break;
+	  case 'X': base = 4; break;
+	}
+
+	if(base)
+	{
+	  char *p = x;
+	  ptrdiff_t l;
+
+	  if(mask_size || val>=0)
+	  {
+	    do {
+	      if((*p++ = '0'|(val&((1<<base)-1)))>'9')
+		p[-1] += (mode=='X'? 'A'-'9'-1 : 'a'-'9'-1);
+	      val >>= base;
+	    } while(--mask_size && val);
+	    l = p-x;
+	  }
+	  else
+	  {
+	    *p++ = '-';
+	    val = -val;
+	    do {
+	      if((*p++ = '0'|(val&((1<<base)-1)))>'9')
+		p[-1] += (mode=='X'? 'A'-'9'-1 : 'a'-'9'-1);
+	      val = ((unsigned INT_TYPE)val) >> base;
+	    } while(val);
+	    l = p-x-1;
+	  }
+	  *p = '\0';
+	  while(l>1) {
+	    char t = p[-l];
+	    p[-l] = p[-1];
+	    p[-1] = t;
+	    --p;
+	    l -= 2;
+	  }
+	}
+	else if(mode == 'u')
+	  sprintf(x, "%"PRINTPIKEINT"u", (unsigned INT_TYPE) val);
+	else
+	  sprintf(x, "%"PRINTPIKEINT"d", val);
+
+	fs->fsp->len=strlen(x);
+	break;
+      }
+
+      case 'e':
+      case 'f':
+      case 'g':
+      case 'E':
+      case 'G':
+      {
+	char *x;
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_FLOAT(tf);
+
+	/* Special casing for infinity and not a number,
+	 * since many libc's forget about them...
+	 */
+	if (PIKE_ISNAN(tf)) {
+	  /* NaN */
+	  fs->fsp->b = MKPCHARP("nan", 0);
+	  fs->fsp->len = 3;
+	  break;
+	} else if (PIKE_ISINF(tf)) {
+	  /* Infinity. */
+	  if (tf > 0.0) {
+	    fs->fsp->b = MKPCHARP("inf", 0);
+	    fs->fsp->len = 3;
+	  } else {
+	    fs->fsp->b = MKPCHARP("-inf", 0);
+	    fs->fsp->len = 4;
+	  }
+	  break;
+	}
+
+	if (fs->fsp->precision==SPRINTF_UNDECIDED) fs->fsp->precision=3;
+
+	/* FIXME: The constant (320) is good for IEEE double precision
+	 * float, but will definitely fail for bigger precision! --aldem
+	 */
+	x=(char *)xalloc(320+MAXIMUM(fs->fsp->precision,3));
+	fs->fsp->fi_free_string=x;
+	fs->fsp->b=MKPCHARP(x,0);
+	sprintf(buffer,"%%*.*%c",EXTRACT_PCHARP(a));
+
+	if(fs->fsp->precision<0) {
+	  double m=pow(10.0, (double)fs->fsp->precision);
+	  tf = RINT(tf*m)/m;
+	} else if (fs->fsp->precision==0) {
+	  tf = RINT(tf);
+        }
+
+	debug_malloc_touch(x);
+	sprintf(x,buffer,1,fs->fsp->precision<0?0:fs->fsp->precision,tf);
+	debug_malloc_touch(x);
+	fs->fsp->len=strlen(x);
+	
+	/* Make sure that the last digits really are zero. */
+	if(fs->fsp->precision<0)
+	{
+	  ptrdiff_t i, j;
+	  /* Find the ending of the number.  Yes, this can be made
+	     simpler now when the alignment bug for floats is fixed. */
+	  for(i=fs->fsp->len-1; i>=0; i--)
+ 	    if('0'<=x[i] && x[i]<='9')
+	    {
+	      i+=fs->fsp->precision+1;
+	      if(i>=0 && '0'<=x[i] && x[i]<='9')
+		for(j=0; j<-fs->fsp->precision; j++)
+		  x[i+j]='0';
+	      break;
+	    }
+	}
+	break;
+      }
+
+      case 'F':
+      {
+        ptrdiff_t l;
+	char *x;
+        DO_OP();
+        l=4;
+        if(fs->fsp->width > 0) l=fs->fsp->width;
+	if(l != 4 && l != 8)
+	  sprintf_error(fs, "Invalid IEEE width %ld.\n",
+			PTRDIFF_T_TO_LONG(l));
+	x=(char *)alloca(l);
+	fs->fsp->b=MKPCHARP(x,0);
+	fs->fsp->len=l;
+	GET_FLOAT(tf);
+	switch(l) {
+	case 4:
+	  {
+	    float f = DO_NOT_WARN((float)tf);
+#if SIZEOF_FLOAT_TYPE > 4
+	    /* Some paranoia in case libc doesn't handle
+	     * conversion to denormalized floats. */
+	    if ((f != 0.0) || (tf == 0.0)) {
+#endif
+#ifdef FLOAT_IS_IEEE_BIG
+	      MEMCPY(x, &f, 4);
+#elif defined(FLOAT_IS_IEEE_LITTLE)
+	      x[0] = ((char *)&f)[3];
+	      x[1] = ((char *)&f)[2];
+	      x[2] = ((char *)&f)[1];
+	      x[3] = ((char *)&f)[0];
+#else
+	      low_write_IEEE_float(x, tf, 4);
+#endif /* IEEE */
+#if SIZEOF_FLOAT_TYPE > 4
+	      break;
+	    }
+	    low_write_IEEE_float(x, tf, 4);
+#endif
+	  }
+	  break;
+	case 8:
+#ifdef DOUBLE_IS_IEEE_BIG
+	  MEMCPY(x, &tf, 8);
+#elif defined(DOUBLE_IS_IEEE_LITTLE)
+	  x[0] = ((char *)&tf)[7];
+	  x[1] = ((char *)&tf)[6];
+	  x[2] = ((char *)&tf)[5];
+	  x[3] = ((char *)&tf)[4];
+	  x[4] = ((char *)&tf)[3];
+	  x[5] = ((char *)&tf)[2];
+	  x[6] = ((char *)&tf)[1];
+	  x[7] = ((char *)&tf)[0];
+#else
+	  low_write_IEEE_float(x, tf, 8);
+#endif
+	}
+	if (fs->fsp->flags & FIELD_LEFT) {
+	  /* Reverse the byte order. */
+	  int i;
+	  char c;
+	  l--;
+	  for (i=0; i < (l-i); i++) {
+	    c = x[i];
+	    x[i] = x[l-i];
+	    x[l-i] = c;
+	  }
+	}
+	break;
+      }
+
+      case 'O':
+      {
+	struct svalue *t;
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	/* Its necessary to use CHECK_OBJECT_SPRINTF() here,
+	   because describe_svalue encodes \t and others 
+	   when returned by _sprintf */
+	GET_SVALUE(t);
+	if (compat_mode && (compat_mode <= 76)) {
+	  /* We don't care about the nested case, since it
+	   * contains line feeds and comments and stuff anyway.
+	   */
+	  if (TYPEOF(*t) == T_STRING) {
+	    struct string_builder buf;
+	    init_string_builder_alloc(&buf, t->u.string->len+2, 0);
+	    string_builder_putchar(&buf, '"');
+	    string_builder_quote_string(&buf, t->u.string, 0, 0x7fffffff, 0);
+	    string_builder_putchar(&buf, '"');
+	    
+	    fs->fsp->b = MKPCHARP_STR(buf.s);
+	    fs->fsp->len = buf.s->len;
+	    /* NOTE: We need to do this since we're not
+	     *       using free_string_builder(). */
+	    buf.s->len = buf.malloced;
+	    fs->fsp->to_free_string = buf.s;
+	    break;
+	  }
+	}
+	{
+	  dynamic_buffer save_buf;
+	  dynbuf_string s;
+
+	  init_buf(&save_buf);
+	  describe_svalue(t,indent,0);
+	  s=complex_free_buf(&save_buf);
+	  fs->fsp->b=MKPCHARP(s.str,0);
+	  fs->fsp->len=s.len;
+	  fs->fsp->fi_free_string=s.str;
+	  break;
+	}
+      }
+
+#if 0
+      /* This can be useful when doing low level debugging. */
+      case 'p':
+      {
+	dynamic_buffer save_buf;
+	dynbuf_string s;
+	char buf[50];
+	struct svalue *t;
+	DO_OP();
+	GET_SVALUE(t);
+	init_buf(&save_buf);
+	sprintf (buf, "%p", t->u.refs);
+	my_strcat (buf);
+	s=complex_free_buf(&save_buf);
+	fs->fsp->b=MKPCHARP(s.str,0);
+	fs->fsp->len=s.len;
+	fs->fsp->fi_free_string=s.str;
+	break;
+      }
+#endif
+
+      case 's':
+      {
+	struct pike_string *s;
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_STRING(s);
+	fs->fsp->b=MKPCHARP_STR(s);
+	fs->fsp->len=s->len;
+	if(fs->fsp->precision != SPRINTF_UNDECIDED && fs->fsp->precision < fs->fsp->len)
+	  fs->fsp->len = (fs->fsp->precision < 0 ? 0 : fs->fsp->precision);
+	break;
+      }
+
+      case 'q':
+      {
+	struct string_builder buf;
+	struct pike_string *s;
+
+	DO_OP();
+	CHECK_OBJECT_SPRINTF()
+	GET_STRING(s);
+
+	init_string_builder_alloc(&buf, s->len+2, 0);
+	string_builder_putchar(&buf, '"');
+	string_builder_quote_string(&buf, s, 0,
+				    (fs->fsp->precision == SPRINTF_UNDECIDED)?
+				    0x7fffffff:fs->fsp->precision-1,
+				    QUOTE_NO_STRING_CONCAT);
+	string_builder_putchar(&buf, '"');
+
+	fs->fsp->b = MKPCHARP_STR(buf.s);
+	fs->fsp->len = buf.s->len;
+	/* NOTE: We need to do this since we're not
+	 *       using free_string_builder(). */
+	buf.s->len = buf.malloced;
+	fs->fsp->to_free_string = buf.s;
+	break;
+      }
+      }
+      break;
+    }
+  }
+
+  for(f=fs->fsp;f>start;f--)
+  {
+    if(f->flags & WIDTH_OF_DATA)
+      f->width=f->len;
+
+    if(((f->flags & INVERSE_COLUMN_MODE) && !f->column_width) ||
+       (f->flags & COLUMN_MODE))
+    {
+      int max_len,nr;
+      ptrdiff_t columns;
+      tmp=1;
+      for(max_len=nr=e=0;e<f->len;e++)
+      {
+	if(INDEX_PCHARP(f->b,e)=='\n')
+	{
+	  nr++;
+	  if(max_len<tmp) max_len=tmp;
+	  tmp=0;
+	}
+	tmp++;
+      }
+      nr++;
+      if(max_len<tmp)
+	max_len=tmp;
+      if(!f->column_width)
+	f->column_width=max_len;
+      f->column_entries=nr;
+      columns=f->width/(f->column_width+1);
+      
+      if(f->column_width<1 || columns<1)
+	columns=1;
+      f->column_modulo=(nr+columns-1)/columns;
+    }
+  }
+
+  /* Here we do some DWIM */
+  for(f=fs->fsp-1;f>start;f--)
+  {
+    if((f[1].flags & MULTILINE) &&
+       !(f[0].flags & (MULTILINE|MULTI_LINE_BREAK)))
+    {
+      if(! MEMCHR_PCHARP(f->b, '\n', f->len).ptr ) f->flags|=MULTI_LINE;
+    }
+  }
+  
+  for(f=start+1;f<=fs->fsp;)
+  {
+    for(;f<=fs->fsp && !(f->flags&MULTILINE);f++)
+      do_one(fs, r, f);
+    
+    do {
+      d=0;
+      for(e=0;f+e<=fs->fsp && (f[e].flags & MULTILINE);e++)
+	d |= do_one(fs, r, f+e);
+      if(d)
+	string_builder_putchar(r,'\n');
+    } while(d);
+
+    for(;f<=fs->fsp && (f->flags&MULTILINE); f++);
+  }
+
+  while(fs->fsp>start)
+  {
+#ifdef PIKE_DEBUG
+    if(fs->fsp < fs->format_info_stack)
+      Pike_fatal("sprintf: fsp out of bounds.\n");
+#endif
+    
+    if(fs->fsp->fi_free_string)
+      free(fs->fsp->fi_free_string);
+    fs->fsp->fi_free_string=0;
+    
+    if(fs->fsp->to_free_string)
+      free_string(fs->fsp->to_free_string);
+    fs->fsp->to_free_string=0;
+    
+    fs->fsp--;
+  }
+}
+
+static void free_f_sprintf_data (struct format_stack *fs)
+{
+  free_sprintf_strings (fs);
+  free (fs);
+}
+
+/* The efun */
+void low_f_sprintf(INT32 args, int compat_mode, struct string_builder *r)
+{
+  ONERROR uwp;
+  struct pike_string *ret;
+  struct svalue *argp;
+  struct format_stack *fs;
+
+  argp=Pike_sp-args;
+
+  if(TYPEOF(argp[0]) != T_STRING) {
+    if (TYPEOF(argp[0]) == T_OBJECT) {
+      /* Try checking if we can cast it to a string... */
+      ref_push_object(argp[0].u.object);
+      o_cast(string_type_string, PIKE_T_STRING);
+      if (TYPEOF(Pike_sp[-1]) != T_STRING) {
+	/* We don't accept objects... */
+	Pike_error("sprintf(): Cast to string failed.\n");
+      }
+      /* Replace the original object with the new string. */
+      assign_svalue(argp, Pike_sp-1);
+      /* Clean up the stack. */
+      pop_stack();
+    } else {
+      SIMPLE_BAD_ARG_ERROR("sprintf", 1, "string|object");
+    }
+  }
+
+  fs = ALLOC_STRUCT (format_stack);
+  fs->fsp = fs->format_info_stack-1;
+
+  SET_ONERROR(uwp, free_f_sprintf_data, fs);
+  low_pike_sprintf(fs,
+		   r,
+		   MKPCHARP_STR(argp->u.string),
+		   argp->u.string->len,
+		   argp+1,
+		   args-1,
+		   0, compat_mode);
+  UNSET_ONERROR(uwp);
+  free (fs);
+}
+
+void f_sprintf(INT32 args)
+{
+  ONERROR uwp;
+  struct string_builder r;
+  SET_ONERROR(uwp, free_string_builder, &r);
+  init_string_builder(&r,0);
+  low_f_sprintf(args, 0, &r);
+  UNSET_ONERROR(uwp);
+  pop_n_elems(args);
+  push_string(finish_string_builder(&r));
+}
+
+/* Compatibility notes regarding %O:
+ *
+ * In Pike 0.5 and earlier only the characters '\\' and '"' were quoted
+ * by %O.
+ *
+ * Quoting of '\n', '\t', '\b' and '\r' was added in Pike 0.6.
+ *
+ * Quoting of '\f', '\a' and '\v' was added in Pike 7.5.
+ *
+ * Quoting of '\e' was added in Pike 7.7.
+ */
+
+static void f_sprintf_76(INT32 args)
+{
+  ONERROR uwp;
+  struct string_builder r;
+  SET_ONERROR(uwp, free_string_builder, &r);
+  init_string_builder(&r,0);
+  low_f_sprintf(args, 76, &r);
+  UNSET_ONERROR(uwp);
+  pop_n_elems(args);
+  push_string(finish_string_builder(&r));
+}
+
+/* Push the types corresponding to the %-directives in the format string.
+ *
+ *   severity is the severity level if any syntax errors
+ *            are encountered in the format string.
+ *
+ * Returns 1 on success.
+ *         0 on unhandled (ie position-dependent args).
+ *        -1 on syntax error.
+ */
+static int push_sprintf_argument_types(PCHARP format, ptrdiff_t format_len,
+				       int severity)
+{
+  int tmp, setwhat, e;
+  int ret = 1;		/* OK */
+  struct svalue *arg=0;	/* pushback argument */
+  struct svalue *lastarg=0;
+
+  PCHARP a,begin;
+  PCHARP format_end=ADD_PCHARP(format,format_len);
+
+  check_c_stack(500);
+
+  /* if (num_arg < 0) num_arg = MAX_INT32; */
+
+  for(a=format;COMPARE_PCHARP(a,<,format_end);INC_PCHARP(a,1))
+  {
+    int num_snurkel, column;
+
+    if(EXTRACT_PCHARP(a)!='%')
+    {
+      for(e=0;INDEX_PCHARP(a,e)!='%' &&
+	    COMPARE_PCHARP(ADD_PCHARP(a,e),<,format_end);e++);
+      INC_PCHARP(a,e-1);
+      continue;
+    }
+    num_snurkel=0;
+    column=0;
+    arg=NULL;
+    setwhat=0;
+    begin=a;
+
+    for(INC_PCHARP(a,1);;INC_PCHARP(a,1))
+    {
+      switch(EXTRACT_PCHARP(a))
+      {
+      default:
+	if(EXTRACT_PCHARP(a) < 256 && isprint(EXTRACT_PCHARP(a)))
+	{
+	  yyreport(severity, type_check_system_string,
+		   0, "Error in format string, %c is not a format.",
+		   EXTRACT_PCHARP(a));
+	}else{
+	  yyreport(severity, type_check_system_string,
+		   0, "Error in format string, U%08x is not a format.",
+		   EXTRACT_PCHARP(a));
+	}
+	ret = -1;
+	num_snurkel = 0;
+	break;
+
+      /* First the modifiers */
+      case '*':
+	tmp = 0;
+	if (setwhat < 2) {
+	  push_int_type(0, MAX_INT32);
+	} else {
+	  push_int_type(MIN_INT32, MAX_INT32);
+	}
+	/* Allow a mapping in all cases. */
+	push_int_type(MIN_INT32, MAX_INT32);
+	push_type(T_STRING);
+	push_int_type(MIN_INT32, MAX_INT32);
+	push_reverse_type(T_MAPPING);
+	push_type(T_OR);
+	continue;
+
+      case '0':
+	if (setwhat<2) continue;
+      case '1': case '2': case '3':
+      case '4': case '5': case '6':
+      case '7': case '8': case '9':
+	tmp=STRTOL_PCHARP(a,&a,10);
+	INC_PCHARP(a,-1);
+	switch(setwhat)
+	{
+	case 0: case 1:
+	  if(tmp < 0) {
+	    yyreport(severity, type_check_system_string,
+		     0, "Illegal width %d.", tmp);
+	    ret = -1;
+	  }
+	case 2: case 3: case 4: break;
+	}
+	continue;
+
+      case ';': setwhat=3; continue;
+      case '.': setwhat=2; continue;
+      case ':': setwhat=1; continue;
+      case '-':
+	if(setwhat==2) setwhat=4;
+	continue;
+
+      case '/':
+        column |= ROUGH_LINEBREAK;
+        if( column & LINEBREAK ) {
+	  yyreport(severity, type_check_system_string,
+		   0, "Can not use both the modifiers / and =.");
+	  ret = -1;
+	}
+        continue;
+
+      case '=':
+        column |= LINEBREAK;
+        if( column & ROUGH_LINEBREAK ) {
+	  yyreport(severity, type_check_system_string,
+		   0, "Can not use both the modifiers / and =.");
+	  ret = -1;
+	}
+        continue;
+
+      case '#': case '$': case '|': case ' ': case '+':
+      case '!': case '^': case '>': case '_':
+	continue;
+
+      case '@':
+	++num_snurkel;
+	continue;
+
+      case '\'':
+	tmp=0;
+	for(INC_PCHARP(a,1);
+	    COMPARE_PCHARP(ADD_PCHARP(a, tmp),<,format_end)
+	    && INDEX_PCHARP(a,tmp)!='\'';tmp++);
+
+	if (COMPARE_PCHARP(ADD_PCHARP(a, tmp),<,format_end)) {
+	    INC_PCHARP(a,tmp);
+	    continue;
+	} else {
+	    INC_PCHARP(a,tmp);
+	    yyreport(severity, type_check_system_string,
+		     0, "Unfinished pad string in format string.");
+	    ret = -1;
+	    break;
+	}
+
+      case '~':
+      {
+	push_finished_type(int_type_string);
+	push_type(T_STRING);
+	continue;
+      }
+
+      case '<':
+	/* FIXME: !!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+	if (ret > 0) ret = 0;	/* FAILURE! */
+#if 0
+	if(!lastarg) {
+	  yyreport(severity, type_check_system_string,
+		   0, "No last argument.");
+	  ret = -1;
+	}
+	arg=lastarg;
+#endif /* 0 */
+	continue;
+
+      case '[':
+	/* FIXME: !!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+	if (ret > 0) ret = 0;	/* FAILURE! */
+	INC_PCHARP(a,1);
+	if(EXTRACT_PCHARP(a)=='*') {
+	  push_int_type(0, /*num_arg*/ MAX_INT32);
+	  INC_PCHARP(a,1);
+	  tmp = 0;
+	} else
+	  tmp=STRTOL_PCHARP(a,&a,10);
+	if(EXTRACT_PCHARP(a)!=']')  {
+	  yyreport(severity, type_check_system_string,
+		   0, "Expected ] in format string, not %c.",
+		   EXTRACT_PCHARP(a));
+	  ret = -1;
+	}
+#if 0
+	if(tmp >= num_arg) {
+	  yyreport(severity, type_check_system_string,
+		   0, "Not enough arguments to [%d].", tmp);
+	  ret = -1;
+	}
+#endif /* 0 */
+	/* arg = argp+tmp; */
+	continue;
+	
+        /* now the real operators */
+
+      case '{':
+      {
+	ptrdiff_t cnt;
+	for(e=1,tmp=1;tmp;e++)
+	{
+	  if (!INDEX_PCHARP(a,e) &&
+	      !COMPARE_PCHARP(ADD_PCHARP(a,e),<,format_end)) {
+	    yyreport(severity, type_check_system_string,
+		     0, "Missing %%} in format string.");
+	    ret = -1;
+	    break;
+	  } else if(INDEX_PCHARP(a,e)=='%') {
+	    switch(INDEX_PCHARP(a,e+1))
+	    {
+	    case '%': e++; break;
+	    case '}': tmp--; break;
+	    case '{': tmp++; break;
+	    }
+	  }
+	}
+            
+	type_stack_mark();
+	/* Note: No need to check the return value, since we
+	 *       simply or all the types together. Thus the
+	 *       argument order isn't significant.
+	 *
+	 * ... Unless there was a syntax error...
+	 */
+	if (push_sprintf_argument_types(ADD_PCHARP(a,1), e-2, severity) < 0) {
+	  ret = -1;
+	}
+	/* Join the argument types for our array. */
+	push_type(PIKE_T_ZERO);
+	for (cnt = pop_stack_mark(); cnt > 1; cnt--) {
+	  push_type(T_OR);
+	}
+	push_finished_type(peek_type_stack());	/* dup() */
+	push_type(PIKE_T_ARRAY);
+	push_type(T_OR);
+
+	push_type(PIKE_T_ARRAY);
+	
+	INC_PCHARP(a,e);
+	break;
+      }
+
+      case '%':
+	num_snurkel = 0;
+	break;
+
+      case 'n':
+	if (num_snurkel) {
+	  /* NOTE: Does take an argument if '@' is active! */
+	  push_type(PIKE_T_ZERO);
+	}
+	break;
+
+      case 't':
+      {
+	push_type(T_MIXED);
+	break;
+      }
+
+      case 'c':
+      case 'b':
+      case 'o':
+      case 'd':
+      case 'u':
+      case 'x':
+      case 'X':
+      {
+	push_object_type(0, 0);
+	push_int_type(MIN_INT32, MAX_INT32);
+	push_type(T_OR);
+	break;
+      }
+
+      case 'e':
+      case 'f':
+      case 'g':
+      case 'E':
+      case 'G':
+      {
+	push_object_type(0, 0);
+	push_type(PIKE_T_FLOAT);
+	push_type(T_OR);
+	break;
+      }
+
+      case 'F':
+      {
+	push_type(PIKE_T_FLOAT);
+	/* FIXME: Check field width. */
+	break;
+      }
+
+      case 'O':
+      {
+	push_type(T_MIXED);
+	break;
+      }
+
+#if 0
+      /* This can be useful when doing low level debugging. */
+      case 'p':
+      {
+	push_type(T_MIXED);
+	break;
+      }
+#endif
+      case 'H':
+      {
+	push_object_type(0, 0);
+	push_int_type(0, 255);
+	push_type(T_STRING);
+	push_type(T_OR);
+	break;
+      }
+
+      case 'q':
+      case 's':
+      {
+	push_object_type(0, 0);
+	push_finished_type(int_type_string);
+	push_type(T_STRING);
+	push_type(T_OR);
+	break;
+      }
+
+      }
+      break;
+    }
+    while (num_snurkel--) push_type(T_ARRAY);
+  }
+  return ret;
+}
+
+static node *optimize_sprintf(node *n)
+{
+  node **arg0 = my_get_arg(&_CDR(n), 0);
+  node **arg1 = my_get_arg(&_CDR(n), 1);
+  node *ret = NULL;
+  int num_args=count_args(CDR(n));
+  if(arg0 &&
+     (*arg0)->token == F_CONSTANT &&
+     TYPEOF((*arg0)->u.sval) == T_STRING)
+  {
+    /* First argument is a constant string. */
+    struct pike_string *fmt = (*arg0)->u.sval.u.string;
+
+    if(arg1 && num_args == 2 && 
+       fmt->size_shift == 0 && fmt->len == 2 && STR0(fmt)[0]=='%')
+    {
+      /* First argument is a two character format string. */
+      switch(STR0(fmt)[1])
+      {
+      case 'c':
+        ADD_NODE_REF2(*arg1,
+		      ret = mkefuncallnode("int2char",*arg1);
+          );
+	return ret;
+
+      case 't':
+        ADD_NODE_REF2(*arg1,
+		      ret = mkefuncallnode("basetype",*arg1);
+	  );
+	return ret;
+
+      case 'x':
+	ADD_NODE_REF2(*arg1,
+		      ret = mkefuncallnode("int2hex",*arg1);
+	  );
+	return ret;
+      case '%':
+	{
+	  /* FIXME: This code can be removed when the generic
+	   *        argument check is in place.
+	   */
+	  struct pike_string *percent_string;
+	  /* yywarning("Ignoring second argument to sprintf."); */
+	  MAKE_CONST_STRING(percent_string, "%");
+	  ADD_NODE_REF2(*arg1,
+			ret = mknode(F_COMMA_EXPR, *arg1,
+				     mkstrnode(percent_string)));
+	  return ret;
+	}
+
+      default: break;
+      }
+    }
+  }
+  /* FIXME: Convert into compile_sprintf(args[0])->format(@args[1..])? */
+  return ret;
+}
+
+/*! @decl type(mixed) __handle_sprintf_format(string attr, string fmt, @
+ *!                                           type arg_type, type cont_type)
+ *!
+ *!   Type attribute handler for @expr{"sprintf_format"@}.
+ *!
+ *! @param attr
+ *!   Attribute to handle, either @expr{"sprintf_format"@}
+ *!   or @expr{"strict_sprintf_format"@}.
+ *!
+ *! @param fmt
+ *!   Sprintf-style formatting string to generate type information from.
+ *!
+ *! @param arg_type
+ *!   Declared type of the @[fmt] argument (typically @expr{string@}).
+ *!
+ *! @param cont_type
+ *!   Continuation function type after the @[fmt] argument. This is
+ *!   scanned for the type attribute @expr{"sprintf_args"@} to
+ *!   determine where the remaining arguments to @[sprintf()] will
+ *!   come from.
+ *!
+ *! This function is typically called from
+ *! @[PikeCompiler()->apply_attribute_constant()] and is used to perform
+ *! stricter compile-time argument checking of @[sprintf()]-style functions.
+ *!
+ *! It currently implements two operating modes depending on the value of
+ *! @[attr]:
+ *! @string
+ *!   @value "strict_sprintf_format"
+ *!     The formatting string @[fmt] is known to always be passed to
+ *!     @[sprintf()].
+ *!   @value "sprintf_format"
+ *!     The formatting string @[fmt] is passed to @[sprintf()] only
+ *!     if there are @expr{"sprintf_args"@}.
+ *! @endstring
+ *!
+ *! @seealso
+ *!   @[PikeCompiler()->apply_attribute_constant()], @[sprintf()]
+ */
+void f___handle_sprintf_format(INT32 args)
+{  
+  struct pike_type *res;
+  struct pike_type *tmp;
+  struct pike_string *attr;
+  struct pike_string *fmt;
+  int severity = REPORT_ERROR;
+  int found = 0;
+  int fmt_count;
+
+  if (args != 4)
+    SIMPLE_WRONG_NUM_ARGS_ERROR("__handle_sprintf_format", 4);
+  if (TYPEOF(Pike_sp[-4]) != PIKE_T_STRING)
+    SIMPLE_ARG_TYPE_ERROR("__handle_sprintf_format", 1, "string");
+  if (TYPEOF(Pike_sp[-3]) != PIKE_T_STRING)
+    SIMPLE_ARG_TYPE_ERROR("__handle_sprintf_format", 2, "string");
+  if (TYPEOF(Pike_sp[-2]) != PIKE_T_TYPE)
+    SIMPLE_ARG_TYPE_ERROR("__handle_sprintf_format", 3, "type");
+  if (TYPEOF(Pike_sp[-1]) != PIKE_T_TYPE)
+    SIMPLE_ARG_TYPE_ERROR("__handle_sprintf_format", 4, "type");
+
+  tmp = Pike_sp[-1].u.type;
+  if ((tmp->type != PIKE_T_FUNCTION) && (tmp->type != T_MANY)) {
+    SIMPLE_ARG_TYPE_ERROR("__handle_sprintf_format", 4, "type(function)");
+  }
+
+#if 0
+  fprintf(stderr, "__handle_sprintf_format(\"%s\", \"%s\", ...)\n",
+	  Pike_sp[-4].u.string->str, Pike_sp[-3].u.string->str);
+#endif /* 0 */
+
+  MAKE_CONST_STRING(attr, "sprintf_format");
+  if (Pike_sp[-4].u.string == attr) {
+    /* Don't complain so loud about syntax errors in
+     * relaxed mode.
+     */
+    severity = REPORT_NOTICE;
+  } else {
+    MAKE_CONST_STRING(attr, "strict_sprintf_format");
+    if (Pike_sp[-4].u.string != attr) {
+      Pike_error("Bad argument 1 to __handle_sprintf_format(), expected "
+		 "\"sprintf_format\" or \"strict_sprintf_format\", "
+		 "got \"%S\".\n", Pike_sp[-4].u.string);
+    }
+  }
+
+  fmt = Pike_sp[-3].u.string;
+  MAKE_CONST_STRING(attr, "sprintf_args");  
+
+  type_stack_mark();
+  type_stack_mark();
+  for (; tmp; tmp = tmp->cdr) {
+    struct pike_type *arg = tmp->car;
+    int array_cnt = 0;
+    while(arg) {
+      switch(arg->type) {
+      case PIKE_T_ATTRIBUTE:
+	if (arg->car == (struct pike_type *)attr)
+	  break;
+	/* FALL_THROUGH */
+      case PIKE_T_NAME:
+	arg = arg->cdr;
+	continue;
+      case PIKE_T_ARRAY:
+	array_cnt++;
+	arg = arg->car;
+	continue;
+      default:
+	arg = NULL;
+	break;
+      }
+      break;
+    }
+    if (arg) {
+      type_stack_mark();
+      switch (push_sprintf_argument_types(MKPCHARP(fmt->str, fmt->size_shift),
+					  fmt->len, severity)) {
+      case 1:
+	/* Ok. */
+	if (!array_cnt) {
+	  pop_stack_mark();
+	  push_type(T_VOID);	/* No more args */
+	  while (tmp->type == PIKE_T_FUNCTION) {
+	    tmp = tmp->cdr;
+	  }
+	  push_finished_type(tmp->cdr);	/* Return type */
+	  push_reverse_type(T_MANY);
+	  fmt_count = pop_stack_mark();
+	  while (fmt_count > 1) {
+	    push_reverse_type(T_FUNCTION);
+	    fmt_count--;
+	  }
+	  if (severity < REPORT_ERROR) {
+	    /* Add the type where the fmt isn't sent to sprintf(). */
+	    type_stack_mark();
+	    for (arg = Pike_sp[-1].u.type; arg != tmp; arg = arg->cdr) {
+	      push_finished_type(arg->car);
+	    }
+	    push_type(T_VOID);			/* No more args */
+	    push_finished_type(tmp->cdr);	/* Return type */
+	    push_reverse_type(T_MANY);
+	    fmt_count = pop_stack_mark();
+	    while (fmt_count > 1) {
+	      push_reverse_type(T_FUNCTION);
+	      fmt_count--;
+	    }
+	    push_type(T_OR);
+	  }
+	  res = pop_unfinished_type();
+	  pop_n_elems(args);
+	  push_type_value(res);
+	  return;
+	} else {
+	  /* Join the argument types into the array. */
+	  push_type(PIKE_T_ZERO);
+	  for (fmt_count = pop_stack_mark(); fmt_count > 1; fmt_count--) {
+	    push_type(T_OR);
+	  }
+	  while (array_cnt--) {
+	    push_type(PIKE_T_ARRAY);
+	  }
+	  if (severity < REPORT_ERROR) {
+	    push_type(T_VOID);
+	    push_type(T_OR);
+	  }
+	  found = 1;
+	}
+	break;
+      case -1:
+	/* Syntax error. */
+	if (severity < REPORT_ERROR) {
+	  /* Add the type where the fmt isn't sent to sprintf(). */
+	  type_stack_pop_to_mark();
+	  if (array_cnt--) {
+	    push_type(PIKE_T_ZERO);	/* No args */
+	    while (array_cnt--) {
+	      push_type(PIKE_T_ARRAY);
+	      push_type(PIKE_T_ZERO);
+	      push_type(T_OR);
+	    }
+	    push_type(T_VOID);
+	    push_type(T_OR);
+	    push_finished_type(tmp->cdr);	/* Rest type */
+	    push_reverse_type(tmp->type);
+	  } else {
+	    push_type(T_VOID);	/* No more args */
+	    while (tmp->type == PIKE_T_FUNCTION) {
+	      tmp = tmp->cdr;
+	    }
+	    push_finished_type(tmp->cdr);	/* Return type */
+	    push_reverse_type(T_MANY);
+	  }
+	  fmt_count = pop_stack_mark();
+	  while (fmt_count > 1) {
+	    push_reverse_type(T_FUNCTION);
+	    fmt_count--;
+	  }
+	  res = pop_unfinished_type();
+	  pop_n_elems(args);
+	  push_type_value(res);
+	  return;
+	}
+	/* FALL_THROUGH */
+      case 0:
+	/* There was a position argument or a parse error in strict mode. */
+	pop_stack_mark();
+	pop_stack_mark();
+	type_stack_pop_to_mark();
+	pop_n_elems(args);
+	push_undefined();
+	return;
+      }
+    } else {
+      push_finished_type(tmp->car);
+    }
+    if (tmp->type == T_MANY) {
+      tmp = tmp->cdr;
+      break;
+    }
+  }
+  if (found) {
+    /* Found, but inside an array, so we need to build the function
+     * type here.
+     */
+    push_finished_type(tmp);	/* Return type. */
+    push_reverse_type(T_MANY);
+    fmt_count = pop_stack_mark();
+    while (fmt_count > 1) {
+      push_reverse_type(T_FUNCTION);
+      fmt_count--;
+    }
+    res = pop_unfinished_type();
+    pop_n_elems(args);
+    push_type_value(res);
+  } else {
+    /* No marker found. */
+#if 0
+    simple_describe_type(Pike_sp[-1].u.type);
+    fprintf(stderr, " ==> No marker found.\n");
+#endif /* 0 */
+    pop_stack_mark();
+    type_stack_pop_to_mark();
+    pop_n_elems(args);
+    push_undefined();
+  }
+}
+
+/*! @decl constant sprintf_format = __attribute__("sprintf_format")
+ *!
+ *!   Type constant used for typing arguments that are optionally
+ *!   sent to @[sprintf()] depending on the presence of extra arguments.
+ *!
+ *! @seealso
+ *!   @[strict_sprintf_format], @[sprintf_args], @[sprintf()]
+ */
+
+/*! @decl constant strict_sprintf_format = @
+ *!         __attribute__("strict_sprintf_format")
+ *!
+ *!   Type constant used for typing arguments that are always
+ *!   sent to @[sprintf()] regardless of the presence of extra arguments.
+ *!
+ *! @seealso
+ *!   @[sprintf_format], @[sprintf_args], @[sprintf()]
+ */
+
+/*! @decl constant sprintf_args = __attribute__("sprintf_args")
+ *!
+ *!   Type constant used for typing extra arguments that are
+ *!   sent to @[sprintf()].
+ *!
+ *! @seealso
+ *!   @[strict_sprintf_format], @[sprintf_format], @[sprintf()]
+ */
+
+/*! @module String
+ */
+
+/*! @decl constant __HAVE_SPRINTF_STAR_MAPPING__ = 1
+ *!
+ *!   Presence of this symbol indicates that @[sprintf()] supports
+ *!   mappings for the @tt{'*'@}-modifier syntax.
+ *!
+ *! @seealso
+ *!   @[sprintf()], @[lfun::_sprintf()]
+ */
+
+/*! @decl constant __HAVE_SPRINTF_NEGATIVE_F__ = 1
+ *!
+ *!   Presence of this symbol indicates that @[sprintf()] supports
+ *!   little endian output for the @tt{'F'@}-format specifier.
+ *!
+ *! @seealso
+ *!   @[sprintf()], @[lfun::_sprintf()]
+ */
+
+/*! @endmodule
+ */
+
+void init_sprintf()
+{
+  struct pike_string *attr;
+  struct svalue s;
+
+  ADD_EFUN("__handle_sprintf_format", f___handle_sprintf_format,
+	   tFunc(tStr tStr tType(tMix) tType(tMix), tType(tMix)),
+	   0);
+
+  MAKE_CONST_STRING(attr, "sprintf_format");
+  SET_SVAL(s, T_TYPE, 0, type,
+	   make_pike_type(tAttr("sprintf_format", tOr(tStr, tObj))));
+  low_add_efun(attr, &s);
+  free_type(s.u.type);
+
+  MAKE_CONST_STRING(attr, "strict_sprintf_format");
+  SET_SVAL(s, T_TYPE, 0, type,
+	   make_pike_type(tAttr("strict_sprintf_format", tOr(tStr, tObj))));
+  low_add_efun(attr, &s);
+  free_type(s.u.type);
+
+  MAKE_CONST_STRING(attr, "sprintf_args");
+  s.u.type = make_pike_type(tAttr("sprintf_args", tMix));
+  low_add_efun(attr, &s);
+  free_type(s.u.type);
+
+  /* function(string|object, mixed ... : string) */
+  ADD_EFUN2("sprintf", 
+	    f_sprintf,
+	    tFuncV(tAttr("strict_sprintf_format", tOr(tStr, tObj)),
+		   tAttr("sprintf_args", tMix), tStr),
+	    OPT_TRY_OPTIMIZE,
+	    optimize_sprintf,
+	    0);
+
+  ADD_EFUN2("sprintf_76", 
+	    f_sprintf_76,
+	    tFuncV(tOr(tStr, tObj), tMix, tStr),
+	    OPT_TRY_OPTIMIZE,
+	    optimize_sprintf,
+	    0);
+}
+
+void exit_sprintf()
+{
+}
diff --git a/src/stralloc.h b/src/stralloc.h
index 7665d10e50..779c6772b5 100644
--- a/src/stralloc.h
+++ b/src/stralloc.h
@@ -497,5 +497,8 @@ static INLINE void string_builder_binary_strcat(struct string_builder *s,
 
 PMOD_EXPORT void f_sprintf(INT32 num_arg);
 void f___handle_sprintf_format(INT32 args);
+void low_f_sprintf(INT32 args, int compat_mode, struct string_builder *r);
+void init_sprintf();
+void exit_sprintf();
 
 #endif /* STRALLOC_H */
diff --git a/src/testsuite.in b/src/testsuite.in
index bbc8eeea2a..27ace989e1 100644
--- a/src/testsuite.in
+++ b/src/testsuite.in
@@ -13184,4 +13184,308 @@ test_any_equal([[
   };
   return X()->test(1,2);
 ]],[[ ({}) ]])
+
+
+// - sprintf module
+
+dnl This really belongs to sscanf, but...
+test_eq([[ array_sscanf(sprintf("%1c", -1), "%1c")[0] ]], 255)
+test_eq([[ array_sscanf(sprintf("%2c", -1), "%2c")[0] ]], 65535)
+test_eq([[ array_sscanf(sprintf("%3c", -1), "%3c")[0] ]], 16777215)
+
+test_eq([[ sprintf("%4c",16909060) ]],"\1\2\3\4")
+test_eq([[ sprintf("%-4c",16909060) ]],"\4\3\2\1")
+test_eq([[ sprintf("%2c",16909060) ]],"\3\4")
+test_eq([[ sprintf("%-2c",16909060) ]],"\4\3")
+test_eq([[ sprintf("%4c",2147483648) ]],"\200\0\0\0")
+test_eq([[ sprintf("%-4c",2147483648) ]],"\0\0\0\200")
+test_eq([[ sprintf("%2c",2147483648) ]],"\0\0")
+test_eq([[ sprintf("%-2c",2147483648) ]],"\0\0")
+
+cond([[ sizeof( cpp("__AUTO_BIGNUM__")/"__AUTO_BIGNUM__" ) == 1 ]])
+
+  test_eq([[ sprintf("%1c", 0x1abcd7893) ]], "\223")
+  test_eq([[ sprintf("%2c", 0x1abcd7893) ]], "x\223")
+  test_eq([[ sprintf("%3c", 0x1abcd7893) ]], "\315x\223")
+  test_eq([[ sprintf("%4c", 0x1abcd7893) ]], "\253\315x\223")
+  test_eq([[ sprintf("%5c", 0x1abcd7893) ]], "\1\253\315x\223")
+  test_eq([[ sprintf("%6c", 0x1abcd7893) ]], "\0\1\253\315x\223")
+  test_eq([[ sprintf("%7c", 0x1abcd7893) ]], "\0\0\1\253\315x\223")
+
+  test_eq([[ sprintf("%1c", -0x1abcd7893) ]], "m")
+  test_eq([[ sprintf("%2c", -0x1abcd7893) ]], "\207m")
+  test_eq([[ sprintf("%3c", -0x1abcd7893) ]], "2\207m")
+  test_eq([[ sprintf("%4c", -0x1abcd7893) ]], "T2\207m")
+  test_eq([[ sprintf("%5c", -0x1abcd7893) ]], "\376T2\207m")
+  test_eq([[ sprintf("%6c", -0x1abcd7893) ]], "\377\376T2\207m")
+  test_eq([[ sprintf("%7c", -0x1abcd7893) ]], "\377\377\376T2\207m")
+
+  test_eq([[ array_sscanf(sprintf("%4c", -1), "%4c")[0] ]], 4294967295)
+  test_eq([[ array_sscanf(sprintf("%5c", -1), "%5c")[0] ]], 1099511627775)
+  test_eq([[ array_sscanf(sprintf("%6c", -1), "%6c")[0] ]], 281474976710655)
+  test_eq([[ array_sscanf(sprintf("%7c", -1), "%7c")[0] ]], 72057594037927935)
+  test_eq([[ array_sscanf(sprintf("%8c", -1), "%8c")[0] ]], 18446744073709551615)
+  test_eq([[ array_sscanf(sprintf("%9c", -1), "%9c")[0] ]], 4722366482869645213695)
+  test_eq([[ array_sscanf(sprintf("%10c", -1), "%10c")[0] ]], 1208925819614629174706175)
+  test_eq([[ array_sscanf(sprintf("%11c", -1), "%11c")[0] ]], 309485009821345068724781055)
+
+  test_eq("\25363274223", [[ sprintf("%c", 0x1abcd7893) ]])
+  test_eq("\12414503555", [[ sprintf("%c", -0x1abcd7893) ]])
+
+cond_end // __AUTO_BIGNUM__
+
+test_eq([[ sprintf("%x", -1) ]], "-1")
+test_eq([[ sprintf("%4x", -1) ]], "  -1")
+test_eq([[ sprintf("%10x", -1) ]], "        -1")
+test_eq([[ sprintf("%10x", -15) ]], "        -f")
+test_eq([[ sprintf("%010x", -15) ]], "-00000000f")
+
+test_eq([[ sprintf("%08x", -1) ]], "-0000001")
+test_eq([[ sprintf("%016x", -15) ]], "-00000000000000f")
+test_eq([[ sprintf("%x", 65535) ]], "ffff")
+test_eq([[ sprintf("%x", -0x80000000) ]], "-80000000")
+
+test_eq("f", [[ sprintf("%.1x", -1) ]])
+test_eq("ff", [[ sprintf("%.2x", -1) ]])
+test_eq("fff", [[ sprintf("%.3x", -1) ]])
+test_eq("ffffffffffffffffffff", [[ sprintf("%.20x", -1) ]])
+test_eq("1", [[ sprintf("%.3x", 1) ]])
+test_eq("1", [[ sprintf("%0.3x", 1) ]])
+test_eq("fff", [[ sprintf("%0.3x", -1) ]])
+test_eq("1", [[ sprintf("%0.16x", 1) ]])
+test_eq("ffffffffffffffff", [[ sprintf("%0.16x", -1) ]])
+test_eq("  ff", [[ sprintf("%4.2x", -1) ]])
+test_eq("00ff", [[ sprintf("%04.2x", -1) ]])
+test_eq("0001", [[ sprintf("%04.2x", 1) ]])
+test_eq("00cc", [[ sprintf("%04.2x", -0x1234) ]])
+
+cond_begin([[ sizeof( cpp("__AUTO_BIGNUM__")/"__AUTO_BIGNUM__" ) == 1 ]])
+
+  test_eq("-123456789123456789", [[ sprintf("%x", -0x123456789123456789) ]])
+  test_eq("ba9877", [[ sprintf("%.6x", -0x123456789123456789) ]])
+  test_eq("876edcba9877", [[ sprintf("%.12x", -0x123456789123456789) ]])
+  test_eq("        876edcba9877", [[ sprintf("%20.12x",-0x123456789123456789)]])
+
+cond_end // __AUTO_BIGNUM__
+
+test_eq("77777777760000000000", [[ sprintf("%.20o", -0x80000000) ]])
+
+test_true(stringp(sprintf("")))
+test_true(sprintf("--real %1.20f --imaginary %1.20f --scale %1.20f\n",-0.9,-0.9,-0.9))
+test_eq(sprintf("%%"),"%")
+test_eq(sprintf("%d",1),"1")
+test_eq(sprintf("%d",-1),"-1")
+test_eq(sprintf("%o",1),"1")
+test_eq(sprintf("%u",1<<31),"2147483648")
+test_false(sprintf("%u",-1)=="-1")
+test_eq(sprintf("%o",255),"377")
+test_eq(sprintf("%o",-9),"-11")
+test_eq(sprintf("%o",012345670),"12345670")
+test_eq(sprintf("%x",255),"ff")
+test_eq(sprintf("%x",-27),"-1b")
+test_eq(sprintf("%X",255),"FF")
+test_eq(sprintf("%X",-27),"-1B")
+test_eq(sprintf("%c",255),"\377")
+test_eq(sprintf("%2c",65535),"\377\377")
+test_eq(sprintf("%3c",0xffffff),"\377\377\377")
+test_true(stringp(sprintf("%f",255.0)))
+test_true(stringp(sprintf("%g",255.0)))
+test_true(stringp(sprintf("%G",255.0)))
+test_true(stringp(sprintf("%e",255.0)))
+test_true(stringp(sprintf("%E",255.0)))
+
+test_eq(sprintf("%.1f",31415.9267),"31415.9")
+test_eq(sprintf("%.0f",31415.9267),"31416")
+test_eq(sprintf("%.-1f",31415.9267),"31420")
+test_eq(sprintf("%.-2f",31415.9267),"31400")
+test_eq(sprintf("%.-2f",-31415.9267),"-31400")
+test_eq(sprintf("%.-10f",31415.9267),"0")
+test_eq(sprintf("%20.-3f", 31415.92670),"               31000")
+
+
+dnl test for high exponent problems
+dnl (this might only effect --with-double-precision et al)
+test_true(stringp(sprintf("%f",exp(700))))
+test_true(stringp(sprintf("%g",exp(700))))
+test_true(stringp(sprintf("%G",exp(700))))
+test_true(stringp(sprintf("%e",exp(700))))
+test_true(stringp(sprintf("%E",exp(700))))
+
+dnl test for "inf" problems
+test_eq(lower_case(sprintf("%f",Math.inf)),"inf") 
+test_eq(lower_case(sprintf("%g",Math.inf)),"inf")
+test_eq(lower_case(sprintf("%G",Math.inf)),"inf")
+test_eq(lower_case(sprintf("%e",Math.inf)),"inf")
+test_eq(lower_case(sprintf("%E",Math.inf)),"inf")
+test_eq(lower_case(sprintf("%f",-Math.inf)),"-inf") 
+test_eq(lower_case(sprintf("%g",-Math.inf)),"-inf")
+test_eq(lower_case(sprintf("%G",-Math.inf)),"-inf")
+test_eq(lower_case(sprintf("%e",-Math.inf)),"-inf")
+test_eq(lower_case(sprintf("%E",-Math.inf)),"-inf")
+
+dnl test for "nan" problems
+dnl At least the following variants exist: "nan", "Nan", "NaN", "NaNQ".
+test_eq(lower_case(sprintf("%f",Math.nan)[..2]),"nan") 
+test_eq(lower_case(sprintf("%g",Math.nan)[..2]),"nan")
+test_eq(lower_case(sprintf("%G",Math.nan)[..2]),"nan")
+test_eq(lower_case(sprintf("%e",Math.nan)[..2]),"nan")
+test_eq(lower_case(sprintf("%E",Math.nan)[..2]),"nan")
+
+test_eq(sprintf("%s","foobaR"),"foobaR")
+test_eq(sprintf("%s","foo\nbar"),"foo\nbar")
+test_eq(sprintf("%8.3s","foobar"),"     foo")
+test_true(stringp(sprintf("%O",this_object())))
+test_true(stringp(sprintf("%O",({}))))
+test_eq(sprintf("%n"),"")
+test_eq(sprintf("%t",1),"int")
+test_eq(sprintf("%t",1.0),"float")
+test_eq(sprintf("%t",""),"string")
+test_eq(sprintf("%t",this_object()),"object")
+test_eq(sprintf("%t", ({"a"})), "array")
+test_any([[array(string) a = ({"a"}); return sprintf("%t", a);]], "array")
+test_eq(sprintf("%t", 0), "int")
+test_any([[array(string) a = 0; return sprintf("%t", a);]], "int")
+test_eq(sprintf("%t", (<>)), "multiset")
+test_eq(sprintf("%t", ([])), "mapping")
+test_eq(sprintf("%t", sin), "function")
+test_eq(sprintf("%t", class {}), "program")
+test_eq(sprintf("%t", typeof(3)), "type")
+
+test_eq(strlen(sprintf("%1000s","")),1000)
+test_eq(sprintf("%2d",1)," 1")
+test_eq(sprintf("%2d",1)," 1")
+test_eq(sprintf("foo_%3d",1),"foo_  1")
+test_eq(sprintf("%2d",2222),"2222")
+test_eq(sprintf("%!2d",2222),"22")
+test_eq(sprintf("%!!2d",2222),"2222")
+test_eq(sprintf("% d",2)," 2")
+test_eq(sprintf("% d",-2),"-2")
+test_eq(sprintf("%+d",2),"+2")
+test_eq(sprintf("%+d",-2),"-2")
+test_eq(sprintf("%-2d",2),"2 ")
+test_eq(sprintf("%|3d",2)," 2 ")
+test_eq(sprintf("%-=3s","f o bar gaz"),"f o\nbar\ngaz")
+dnl test_eq(sprintf("%/3s","f o bargaz"),"f o\nbar\ngaz")
+test_true(stringp(sprintf("%3#s","f\no\nbargaz\nonk")))
+test_true(stringp(sprintf("%3$s","f\no\nbargaz\nonk")))
+test_eq(sprintf("%-$79s","foo\nbar\ngazonk"),"foo    bar    gazonk ")
+test_eq(sprintf("%-$20s","foo\nbar\ngazonk"),"foo    bar    \ngazonk ")
+test_eq(sprintf("%-#79s","foo\nbar\ngazonk"),"foo    bar    gazonk ")
+test_eq(sprintf("%-#20s","foo\nbar\ngazonk"),"foo    gazonk \nbar    ")
+
+test_eq(sprintf("%.0f",17.23456),"17")
+
+test_eq(sprintf("%*d",3,3),"  3")
+test_eq(sprintf("%'FOO'10s","BAR"),"FOOFOOFBAR")
+test_eq(sprintf("%d %<d %<d",2),"2 2 2")
+test_true(stringp(sprintf("%O",({1,2,"foo"}))))
+test_true(stringp(sprintf("%O",([1:2,"foo":"bar"]))))
+test_eq(sprintf("%@4d", (array)"hi!")," 104 105  33")
+test_eq(strlen(sprintf("%@c",allocate(1000))),1000)
+
+test_eq(sprintf("test \0 \n"),"test \0 \n")
+test_eq(sprintf("test \0"),"test \0")
+test_eq(sprintf("%~*n","f",5),"fffff")
+test_eq(sprintf("%'\000'*n",5),"\000\000\000\000\000")
+
+test_eq(sprintf("%{%d\n%}",({1,2,3,4})),"1\n2\n3\n4\n")
+
+test_true([[sprintf("%{%{%s%}\n%}",({({({"hej"})}),({({"hop"})})}))]])
+test_true([[sprintf("%{%{%s%}\n%}",({({({"hej"})}),({({"hop"})})}))]])
+test_true([[sprintf("%{%{%s%}\n%}",({({({"hej"})}),({({"hop"})})}))]])
+test_true([[sprintf("%{%{%s%}\n%}",({({({"hej"})}),({({"hop"})})}))]])
+
+test_eq([[sprintf("%{%d %d %d\n%}",({ ({1,2,3}), ({4,5,6}) }) )]],"1 2 3\n4 5 6\n")
+
+test_eq([[sprintf("%4F", 0.0)]], "\000\000\000\000")
+test_eq([[sprintf("%4F", 1.0)]], "\077\200\000\000")
+test_eq([[sprintf("%4F", 0.5)]], "\077\000\000\000")
+test_eq([[sprintf("%4F", 2.0)]], "\100\000\000\000")
+test_eq([[sprintf("%4F", 1.5)]], "\077\300\000\000")
+test_eq([[sprintf("%4F", 1048576.125)]], "\111\200\000\001")
+test_eq([[sprintf("%4F", -17.5)]], "\301\214\000\000")
+cond([[pow(2.0,-128.0)!=0.0]],
+[[
+  test_eq([[sprintf("%4F", pow(2.0,-128.0))]], "\000\040\000\000")
+]])
+test_eq([[sprintf("%4F", 0.033)]], "\075\007\053\002")
+test_eq([[sprintf("%4F", pow(2.0,128.0))]], "\177\200\000\000")
+test_eq([[sprintf("%8F", 0.0)]], "\000\000\000\000\000\000\000\000")
+test_eq([[sprintf("%8F", 1.0)]], "\077\360\000\000\000\000\000\000")
+test_eq([[sprintf("%8F", 0.5)]], "\077\340\000\000\000\000\000\000")
+test_eq([[sprintf("%8F", 2.0)]], "\100\000\000\000\000\000\000\000")
+test_eq([[sprintf("%8F", 1.5)]], "\077\370\000\000\000\000\000\000")
+test_eq([[sprintf("%8F", 1048576.125)]], "\101\060\000\000\040\000\000\000")
+test_eq([[sprintf("%8F", -17.5)]], "\300\061\200\000\000\000\000\000")
+cond([[pow(2.0,-128.0)!=0.0]],
+[[
+  test_eq([[sprintf("%8F", pow(2.0,-128.0))]], "\067\360\000\000\000\000\000\000")
+]])
+test_eq([[sprintf("%8F", 0.032999999821186065673828125)]], "\077\240\345\140\100\000\000\000")
+
+cond([[pow(2.0,1024.0)!=0.0]],
+[[
+  test_eq([[sprintf("%8F", pow(2.0,1024.0))]], "\177\360\000\000\000\000\000\000")
+]])
+
+dnl Make sure that _sprintf is called and that the argument list isn't screwed.
+test_eq(sprintf("%d%c%s%t%o%d%u%x%X%e%f%g%E%G%O%d", 4711,
+        @allocate(14, (class { string _sprintf(int t)
+                                { mixed x = "not a number";
+                                  string s;
+                                  catch {
+                                    s = sprintf("%d%d%d%d", 1, 2, x, 3);
+                                  };
+                                  return sprintf("[%c%c%c]", 'a', t, 'b'); }
+                             })()), 42),
+        "4711[acb][asb][atb][aob][adb][aub][axb][aXb]"
+        "[aeb][afb][agb][aEb][aGb][aOb]42")
+
+test_eq(sprintf("%[*]s %s",2,"a","b"),"b a")
+test_eq(sprintf("%[1]s %s %[2]s %s","b","a","r"),"a b r a")
+
+test_do(catch(sprintf("%d-" *101, @allocate(101))))
+
+test_eq(sprintf("%O", "a\nb"),"\"a\\n\"\n\"b\"")
+test_eq(sprintf("%O", "a\0b"),"\"a\\0b\"")
+test_eq(sprintf("%O", "a\0""0"),"\"a\\0\"\"0\"")
+
+test_eq(sprintf_76("%O", "a\nb"),"\"a\\nb\"")
+test_eq(sprintf_76("%O", "a\0b"),"\"a\\0b\"")
+test_eq(sprintf_76("%O", "a\0""0"),"\"a\\0\"\"0\"")
+
+test_eq(sprintf("%q", "a\nb"),"\"a\\nb\"")
+test_eq(sprintf("%q", "a\0b"),"\"a\\0b\"")
+test_eq(sprintf("%q", "a\0""0"),"\"a\\u00000\"")
+test_eq(sprintf("%q", "\177\177""0"),"\"\\177\\u007f0\"")
+
+test_eq(sprintf("%H", ""),"\0")
+test_eq(sprintf("%H", "hello"),"\5hello")
+
+test_eval_error(return sprintf("%0H", ""))
+test_eval_error(return sprintf("%0H", "hello"))
+
+test_eq(sprintf("%1H", ""),"\0")
+test_eq(sprintf("%1H", "hello"),"\5hello")
+test_eq(sprintf("%-1H", ""),"\0")
+test_eq(sprintf("%-1H", "hello"),"\5hello")
+test_eq(sprintf("%2H", ""),"\0\0")
+test_eq(sprintf("%2H", "hello"),"\0\5hello")
+test_eq(sprintf("%-2H", ""),"\0\0")
+test_eq(sprintf("%-2H", "hello"),"\5\0hello")
+test_eq(sprintf("%4H", ""),"\0\0\0\0")
+test_eq(sprintf("%4H", "hello"),"\0\0\0\5hello")
+test_eq(sprintf("%-4H", ""),"\0\0\0\0")
+test_eq(sprintf("%-4H", "hello"),"\5\0\0\0hello")
+test_do(sprintf("%9H", "x"*300););
+
+test_eval_error(return sprintf("%H", "\x100");)
+test_do(return sprintf("%1H", "x"*255);)
+test_eval_error(return sprintf("%1H", "x"*256);)
+
+dnl / : ; ^ _ > hasn't been tested
+test_eval_error(return sprintf("%d");)
+
+test_eq(sprintf("%O", class { string _sprintf(int type) { return "\t"; } }()), "\t")
+
 END_MARKER
-- 
GitLab