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