From 54a6cb34c7e7179dbd0655f7d0823f1eae5abbad Mon Sep 17 00:00:00 2001 From: jamboree Date: Thu, 4 Jun 2015 13:59:37 +0800 Subject: [PATCH] Support runtime width specification --- doc/syntax.rst | 2 +- format.cc | 42 +++++++++++++++++++++++++++----- test/format-test.cc | 58 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/doc/syntax.rst b/doc/syntax.rst index 5171aa2a..109b7175 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -73,7 +73,7 @@ The general form of a *standard format specifier* is: fill: align: "<" | ">" | "=" | "^" sign: "+" | "-" | " " - width: `integer` + width: `integer` | "{" `arg_index` "}" precision: `integer` | "{" `arg_index` "}" type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" int_type: "b" | "B" | "d" | "o" | "x" | "X" diff --git a/format.cc b/format.cc index f9182f24..d7c2ca90 100644 --- a/format.cc +++ b/format.cc @@ -1025,16 +1025,46 @@ const Char *fmt::BasicFormatter::format( ++s; } - // Parse width and zero flag. - if ('0' <= *s && *s <= '9') { - if (*s == '0') { + // Parse zero flag. + if (*s == '0') { require_numeric_argument(arg, '0'); spec.align_ = ALIGN_NUMERIC; spec.fill_ = '0'; - } - // Zero may be parsed again as a part of the width, but it is simpler - // and more efficient than checking if the next char is a digit. + ++s; + } + + // Parse width. + if ('0' <= *s && *s <= '9') { spec.width_ = parse_nonnegative_int(s); + } else if (*s == '{') { + ++s; + const Arg &width_arg = parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (width_arg.type) { + case Arg::INT: + if (width_arg.int_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.int_value; + break; + case Arg::UINT: + value = width_arg.uint_value; + break; + case Arg::LONG_LONG: + if (width_arg.long_long_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = width_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("width is not integer")); + } + if (value > INT_MAX) + FMT_THROW(FormatError("number is too big")); + spec.width_ = static_cast(value); } // Parse precision. diff --git a/test/format-test.cc b/test/format-test.cc index cb1279ad..67ce0811 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -902,6 +902,64 @@ TEST(FormatterTest, Width) { EXPECT_EQ("test ", format("{0:13}", TestString("test"))); } +TEST(FormatterTest, RuntimeWidth) { + char format_str[BUFFER_SIZE]; + safe_sprintf(format_str, "{0:{%u", UINT_MAX); + increment(format_str + 4); + EXPECT_THROW_MSG(format(format_str, 0), FormatError, "number is too big"); + std::size_t size = std::strlen(format_str); + format_str[size] = '}'; + format_str[size + 1] = 0; + EXPECT_THROW_MSG(format(format_str, 0), FormatError, "number is too big"); + format_str[size + 1] = '}'; + format_str[size + 2] = 0; + EXPECT_THROW_MSG(format(format_str, 0), FormatError, "number is too big"); + + EXPECT_THROW_MSG(format("{0:{", 0), + FormatError, "invalid format string"); + EXPECT_THROW_MSG(format("{0:{}", 0), + FormatError, "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(format("{0:{x}}", 0), + FormatError, "invalid format string"); + EXPECT_THROW_MSG(format("{0:{1}}", 0), + FormatError, "argument index out of range"); + + EXPECT_THROW_MSG(format("{0:{0:}}", 0), + FormatError, "invalid format string"); + + EXPECT_THROW_MSG(format("{0:{1}}", 0, -1), + FormatError, "negative width"); + EXPECT_THROW_MSG(format("{0:{1}}", 0, (INT_MAX + 1u)), + FormatError, "number is too big"); + EXPECT_THROW_MSG(format("{0:{1}}", 0, -1l), + FormatError, "negative width"); + if (fmt::internal::check(sizeof(long) > sizeof(int))) { + long value = INT_MAX; + EXPECT_THROW_MSG(format("{0:{1}}", 0, (value + 1)), + FormatError, "number is too big"); + } + EXPECT_THROW_MSG(format("{0:{1}}", 0, (INT_MAX + 1ul)), + FormatError, "number is too big"); + + EXPECT_THROW_MSG(format("{0:{1}}", 0, '0'), + FormatError, "width is not integer"); + EXPECT_THROW_MSG(format("{0:{1}}", 0, 0.0), + FormatError, "width is not integer"); + + EXPECT_EQ(" -42", format("{0:{1}}", -42, 4)); + EXPECT_EQ(" 42", format("{0:{1}}", 42u, 5)); + EXPECT_EQ(" -42", format("{0:{1}}", -42l, 6)); + EXPECT_EQ(" 42", format("{0:{1}}", 42ul, 7)); + EXPECT_EQ(" -42", format("{0:{1}}", -42ll, 6)); + EXPECT_EQ(" 42", format("{0:{1}}", 42ull, 7)); + EXPECT_EQ(" -1.23", format("{0:{1}}", -1.23, 8)); + EXPECT_EQ(" -1.23", format("{0:{1}}", -1.23l, 9)); + EXPECT_EQ(" 0xcafe", format("{0:{1}}", reinterpret_cast(0xcafe), 10)); + EXPECT_EQ("x ", format("{0:{1}}", 'x', 11)); + EXPECT_EQ("str ", format("{0:{1}}", "str", 12)); + EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13)); +} + TEST(FormatterTest, Precision) { char format_str[BUFFER_SIZE]; safe_sprintf(format_str, "{0:.%u", UINT_MAX);