From: Nikita Popov Date: Tue, 21 Apr 2020 14:54:22 +0000 (+0200) Subject: Add support for * width and precision in printf() X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0221b8b2ab8cd53845a03cedb31add36e58e390f;p=php Add support for * width and precision in printf() If * is used for width/precision in printf, then the width/precision is provided by a printf argument instead of being part of the format string. Semantics generally match those of printf in C. This can be used to easily reproduce PHP's float printing behavior: // Locale-sensitive using precision ini setting. // Used prior to PHP 8.0. sprintf("%.*G", (int) ini_get('precision'), $float); // Locale-insensitive using precision ini setting. // Used since to PHP 8.0. sprintf("%.*H", (int) ini_get('precision'), $float); // Locale-insensitive using serialize_precision ini setting. // Used in serialize(), json_encode() etc. sprintf("%.*H", (int) ini_get('serialize_precision'), $float); Closes GH-5432. --- diff --git a/UPGRADING b/UPGRADING index 6f3abbfc7c..81b945d204 100644 --- a/UPGRADING +++ b/UPGRADING @@ -539,6 +539,13 @@ PHP 8.0 UPGRADE NOTES . printf() and friends how support the %h and %H format specifiers. These are the same as %g and %G, but always use "." as the decimal separator, rather than determining it through the LC_NUMERIC locale. + . printf() and friends now support using "*" as width or precision, in which + case the width/precision is passed as an argument to printf. This also + allows using precision -1 with %g, %G, %h and %H. For example, the following + code can be used to reproduce PHP's default floating point formatting: + + printf("%.*H", (int) ini_get("precision"), $float); + printf("%.*H", (int) ini_get("serialize_precision"), $float); - Zip: . Extension updated to version 1.19.0 diff --git a/ext/standard/formatted_print.c b/ext/standard/formatted_print.c index f32f9f8abf..b39cc32860 100644 --- a/ext/standard/formatted_print.c +++ b/ext/standard/formatted_print.c @@ -373,6 +373,27 @@ php_sprintf_getnumber(char **buffer, size_t *len) } /* }}} */ +#define ARG_NUM_NEXT -1 +#define ARG_NUM_INVALID -2 + +int php_sprintf_get_argnum(char **format, size_t *format_len) { + char *temppos = *format; + while (isdigit((int) *temppos)) temppos++; + if (*temppos != '$') { + return ARG_NUM_NEXT; + } + + int argnum = php_sprintf_getnumber(format, format_len); + if (argnum <= 0) { + zend_value_error("Argument number must be greater than zero"); + return ARG_NUM_INVALID; + } + + (*format)++; /* skip the '$' */ + (*format_len)--; + return argnum - 1; +} + /* php_formatted_print() {{{ * New sprintf implementation for PHP. * @@ -447,23 +468,12 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n *format, format - Z_STRVAL_P(z_format))); if (isalpha((int)*format)) { width = precision = 0; - argnum = currarg++; + argnum = ARG_NUM_NEXT; } else { /* first look for argnum */ - temppos = format; - while (isdigit((int)*temppos)) temppos++; - if (*temppos == '$') { - argnum = php_sprintf_getnumber(&format, &format_len); - - if (argnum <= 0) { - zend_value_error("Argument number must be greater than zero"); - goto fail; - } - argnum--; - format++; /* skip the '$' */ - format_len--; - } else { - argnum = currarg++; + argnum = php_sprintf_get_argnum(&format, &format_len); + if (argnum == ARG_NUM_INVALID) { + goto fail; } /* after argnum comes modifiers */ @@ -498,7 +508,34 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n /* after modifiers comes width */ - if (isdigit((int)*format)) { + if (*format == '*') { + format++; + format_len--; + + int width_argnum = php_sprintf_get_argnum(&format, &format_len); + if (width_argnum == ARG_NUM_INVALID) { + goto fail; + } + if (width_argnum == ARG_NUM_NEXT) { + width_argnum = currarg++; + } + if (width_argnum >= argc) { + max_missing_argnum = MAX(max_missing_argnum, width_argnum); + continue; + } + tmp = &args[width_argnum]; + ZVAL_DEREF(tmp); + if (Z_TYPE_P(tmp) != IS_LONG) { + zend_value_error("Width must be an integer"); + goto fail; + } + if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) { + zend_value_error("Width must be greater than zero and less than %d", INT_MAX); + goto fail; + } + width = Z_LVAL_P(tmp); + adjusting |= ADJ_WIDTH; + } else if (isdigit((int)*format)) { PRINTF_DEBUG(("sprintf: getting width\n")); if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) { zend_value_error("Width must be greater than zero and less than %d", INT_MAX); @@ -515,7 +552,35 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n format++; format_len--; PRINTF_DEBUG(("sprintf: getting precision\n")); - if (isdigit((int)*format)) { + if (*format == '*') { + format++; + format_len--; + + int prec_argnum = php_sprintf_get_argnum(&format, &format_len); + if (prec_argnum == ARG_NUM_INVALID) { + goto fail; + } + if (prec_argnum == ARG_NUM_NEXT) { + prec_argnum = currarg++; + } + if (prec_argnum >= argc) { + max_missing_argnum = MAX(max_missing_argnum, prec_argnum); + continue; + } + tmp = &args[prec_argnum]; + ZVAL_DEREF(tmp); + if (Z_TYPE_P(tmp) != IS_LONG) { + zend_value_error("Precision must be an integer"); + goto fail; + } + if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) { + zend_value_error("Precision must be between -1 and %d", INT_MAX); + goto fail; + } + precision = Z_LVAL_P(tmp); + adjusting |= ADJ_PRECISION; + expprec = 1; + } else if (isdigit((int)*format)) { if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) { zend_value_error("Precision must be greater than zero and less than %d", INT_MAX); goto fail; @@ -537,11 +602,20 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n } PRINTF_DEBUG(("sprintf: format character='%c'\n", *format)); + if (argnum == ARG_NUM_NEXT) { + argnum = currarg++; + } if (argnum >= argc) { max_missing_argnum = MAX(max_missing_argnum, argnum); continue; } + if (expprec && precision == -1 + && *format != 'g' && *format != 'G' && *format != 'h' && *format != 'H') { + zend_value_error("Precision -1 is only supported for %%g, %%G, %%h and %%H"); + goto fail; + } + /* now we expect to find a type specifier */ tmp = &args[argnum]; switch (*format) { diff --git a/ext/standard/tests/strings/sprintf_star.phpt b/ext/standard/tests/strings/sprintf_star.phpt new file mode 100644 index 0000000000..0e3e16c326 --- /dev/null +++ b/ext/standard/tests/strings/sprintf_star.phpt @@ -0,0 +1,98 @@ +--TEST-- +Star width and precision in sprintf() +--FILE-- +getMessage(), "\n"; +} + +try { + printf("%.*G\n", -100, 1.5); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + printf("%.*s\n", -1, "Foo"); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + printf("%*G\n", -1, $f); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +float(1.2345678901234567) +float(1.2345678901234569E+100) +1.2345678901 +1.23456789 +1.2345678901234569e+100 +1.2345678901234569E+100 +1.2345678901234569e+100 +1.2345678901234569E+100 +foo + + 1.234568 + 1.23457 + foobar + + 1.235 + 1.23 + foo + +1.2345678901 +1.2345678901 +1.2345678901 + 1.234568 + 1.234568 + 1.234568 + 1.235 + 1.235 + 1.235 + +Precision must be an integer +Precision must be between -1 and 2147483647 +Precision -1 is only supported for %g, %G, %h and %H +Width must be greater than zero and less than 2147483647