From 11db2642fb54487ef18c18cc8555d7ff2f5e823b Mon Sep 17 00:00:00 2001 From: Zachary Turner Date: Fri, 11 Nov 2016 23:57:40 +0000 Subject: [PATCH] [Support] Introduce llvm::formatv() function. This introduces a new type-safe general purpose formatting library. It provides compile-time type safety, does not require a format specifier (since the type is deduced), and provides mechanisms for extending the format capability to user defined types, and overriding the formatting behavior for existing types. This patch additionally adds documentation for the API to the LLVM programmer's manual. Mailing List Thread: http://lists.llvm.org/pipermail/llvm-dev/2016-October/105836.html Differential Revision: https://reviews.llvm.org/D25587 llvm-svn: 286682 --- llvm/docs/ProgrammersManual.rst | 130 +++++ llvm/include/llvm/ADT/STLExtras.h | 17 + llvm/include/llvm/Support/FormatAdapters.h | 92 +++ llvm/include/llvm/Support/FormatCommon.h | 69 +++ llvm/include/llvm/Support/FormatProviders.h | 411 +++++++++++++ llvm/include/llvm/Support/FormatVariadic.h | 246 ++++++++ .../llvm/Support/FormatVariadicDetails.h | 149 +++++ llvm/include/llvm/Support/NativeFormatting.h | 19 +- llvm/include/llvm/Support/YAMLTraits.h | 5 +- llvm/include/llvm/Support/raw_ostream.h | 4 + llvm/lib/Support/CMakeLists.txt | 1 + llvm/lib/Support/FormatVariadic.cpp | 156 +++++ llvm/lib/Support/NativeFormatting.cpp | 83 ++- llvm/lib/Support/raw_ostream.cpp | 17 +- llvm/unittests/Support/CMakeLists.txt | 1 + llvm/unittests/Support/FormatVariadicTest.cpp | 538 ++++++++++++++++++ llvm/unittests/Support/NativeFormatTests.cpp | 2 +- 17 files changed, 1893 insertions(+), 47 deletions(-) create mode 100644 llvm/include/llvm/Support/FormatAdapters.h create mode 100644 llvm/include/llvm/Support/FormatCommon.h create mode 100644 llvm/include/llvm/Support/FormatProviders.h create mode 100644 llvm/include/llvm/Support/FormatVariadic.h create mode 100644 llvm/include/llvm/Support/FormatVariadicDetails.h create mode 100644 llvm/lib/Support/FormatVariadic.cpp create mode 100644 llvm/unittests/Support/FormatVariadicTest.cpp diff --git a/llvm/docs/ProgrammersManual.rst b/llvm/docs/ProgrammersManual.rst index 9c3b424cf97b..b30133948310 100644 --- a/llvm/docs/ProgrammersManual.rst +++ b/llvm/docs/ProgrammersManual.rst @@ -263,6 +263,136 @@ almost never be stored or mentioned directly. They are intended solely for use when defining a function which should be able to efficiently accept concatenated strings. +.. _formatting_strings: + +Formatting strings (the ``formatv`` function) +--------------------------------------------- +While LLVM doesn't necessarily do a lot of string manipulation and parsing, it +does do a lot of string formatting. From diagnostic messages, to llvm tool +outputs such as ``llvm-readobj`` to printing verbose disassembly listings and +LLDB runtime logging, the need for string formatting is pervasive. + +The ``formatv`` is similar in spirit to ``printf``, but uses a different syntax +which borrows heavily from Python and C#. Unlike ``printf`` it deduces the type +to be formatted at compile time, so it does not need a format specifier such as +``%d``. This reduces the mental overhead of trying to construct portable format +strings, especially for platform-specific types like ``size_t`` or pointer types. +Unlike both ``printf`` and Python, it additionally fails to compile if LLVM does +not know how to format the type. These two properties ensure that the function +is both safer and simpler to use than traditional formatting methods such as +the ``printf`` family of functions. + +Simple formatting +^^^^^^^^^^^^^^^^^ + +A call to ``formatv`` involves a single **format string** consisting of 0 or more +**replacement sequences**, followed by a variable length list of **replacement values**. +A replacement sequence is a string of the form ``{N[[,align]:style]}``. + +``N`` refers to the 0-based index of the argument from the list of replacement +values. Note that this means it is possible to reference the same parameter +multiple times, possibly with different style and/or alignment options, in any order. + +``align`` is an optional string specifying the width of the field to format +the value into, and the alignment of the value within the field. It is specified as +an optional **alignment style** followed by a positive integral **field width**. The +alignment style can be one of the characters ``-`` (left align), ``=`` (center align), +or ``+`` (right align). The default is right aligned. + +``style`` is an optional string consisting of a type specific that controls the +formatting of the value. For example, to format a floating point value as a percentage, +you can use the style option ``P``. + +Custom formatting +^^^^^^^^^^^^^^^^^ + +There are two ways to customize the formatting behavior for a type. + +1. Provide a template specialization of ``llvm::format_provider`` for your + type ``T`` with the appropriate static format method. + + .. code-block:: c++ + + namespace llvm { + template<> + struct format_provider { + static void format(const MyFooBar &V, raw_ostream &Stream, StringRef Style) { + // Do whatever is necessary to format `V` into `Stream` + } + }; + void foo() { + MyFooBar X; + std::string S = formatv("{0}", X); + } + } + + This is a useful extensibility mechanism for adding support for formatting your own + custom types with your own custom Style options. But it does not help when you want + to extend the mechanism for formatting a type that the library already knows how to + format. For that, we need something else. + +2. Provide a **format adapter** with a non-static format method. + + .. code-block:: c++ + + namespace anything { + struct format_int_custom { + int N; + explicit format_int_custom(int N) : N(N) {} + void format(llvm::raw_ostream &Stream, StringRef Style) { + // Do whatever is necessary to format ``N`` into ``Stream`` + } + }; + } + namespace llvm { + void foo() { + std::string S = formatv("{0}", anything::format_int_custom(42)); + } + } + + If the search for a specialization of ``format_provider`` for the given type + fails, ``formatv`` will subsequently check the argument for an instance method + named ``format`` with the signature described above. If so, it will call the + ``format`` method on the argument passing in the specified style. This allows + one to provide custom formatting of any type, including one which already has + a builtin format provider. + +``formatv`` Examples +^^^^^^^^^^^^^^^^^^^^ +Below is intended to provide an incomplete set of examples demonstrating +the usage of ``formatv``. More information can be found by reading the +doxygen documentation or by looking at the unit test suite. + + +.. code-block:: c++ + + std::string S; + // Simple formatting of basic types and implicit string conversion. + S = formatv("{0} ({1:P})", 7, 0.35); // S == "7 (35.00%)" + + // Out-of-order referencing and multi-referencing + outs() << formatv("{0} {2} {1} {0}", 1, "test", 3); // prints "1 3 test 1" + + // Left, right, and center alignment + S = formatv("{0,7}", 'a'); // S == " a"; + S = formatv("{0,-7}", 'a'); // S == "a "; + S = formatv("{0,=7}", 'a'); // S == " a "; + S = formatv("{0,+7}", 'a'); // S == " a"; + + // Custom styles + S = formatv("{0:N} - {0:x} - {1:E}", 12345, 123908342); // S == "12,345 - 0x3039 - 1.24E8" + + // Adapters + S = formatv("{0}", fmt_align(42, AlignStyle::Center, 7)); // S == " 42 " + S = formatv("{0}", fmt_repeat("hi", 3)); // S == "hihihi" + S = formatv("{0}", fmt_pad("hi", 2, 6)); // S == " hi " + + // Ranges + std::vector V = {8, 9, 10}; + S = formatv("{0}", make_range(V.begin(), V.end())); // S == "8, 9, 10" + S = formatv("{0:$[+]}", make_range(V.begin(), V.end())); // S == "8+9+10" + S = formatv("{0:$[ + ]@[x]}", make_range(V.begin(), V.end())); // S == "0x8 + 0x9 + 0xA" + .. _error_apis: Error handling diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h index b6ed93e9946b..1d3242dfb07f 100644 --- a/llvm/include/llvm/ADT/STLExtras.h +++ b/llvm/include/llvm/ADT/STLExtras.h @@ -33,6 +33,11 @@ #include "llvm/Support/Compiler.h" namespace llvm { + +// Only used by compiler if both template types are the same. Useful when +// using SFINAE to test for the existence of member functions. +template struct SameType; + namespace detail { template @@ -477,6 +482,18 @@ struct index_sequence_for : build_index_impl {}; template struct rank : rank {}; template <> struct rank<0> {}; +/// \brief traits class for checking whether type T is one of any of the given +/// types in the variadic list. +template struct is_one_of { + static const bool value = false; +}; + +template +struct is_one_of { + static const bool value = + std::is_same::value || is_one_of::value; +}; + //===----------------------------------------------------------------------===// // Extra additions for arrays //===----------------------------------------------------------------------===// diff --git a/llvm/include/llvm/Support/FormatAdapters.h b/llvm/include/llvm/Support/FormatAdapters.h new file mode 100644 index 000000000000..b5fd9459e166 --- /dev/null +++ b/llvm/include/llvm/Support/FormatAdapters.h @@ -0,0 +1,92 @@ +//===- FormatAdapters.h - Formatters for common LLVM types -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FORMATADAPTERS_H +#define LLVM_SUPPORT_FORMATADAPTERS_H + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatCommon.h" +#include "llvm/Support/FormatVariadicDetails.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { +template class AdapterBase { +protected: + explicit AdapterBase(T &&Item) : Item(Item) {} + + T Item; + static_assert(!detail::uses_missing_provider::value, + "Item does not have a format provider!"); +}; + +namespace detail { +template class AlignAdapter : public AdapterBase { + AlignStyle Where; + size_t Amount; + +public: + AlignAdapter(T &&Item, AlignStyle Where, size_t Amount) + : AdapterBase(std::forward(Item)), Where(Where), Amount(Amount) {} + + void format(llvm::raw_ostream &Stream, StringRef Style) { + auto Wrapper = detail::build_format_wrapper(std::forward(Item)); + FmtAlign(Wrapper, Where, Amount).format(Stream, Style); + } +}; + +template class PadAdapter : public AdapterBase { + size_t Left; + size_t Right; + +public: + PadAdapter(T &&Item, size_t Left, size_t Right) + : AdapterBase(std::forward(Item)), Left(Left), Right(Right) {} + + void format(llvm::raw_ostream &Stream, StringRef Style) { + auto Wrapper = detail::build_format_wrapper(std::forward(Item)); + Stream.indent(Left); + Wrapper.format(Stream, Style); + Stream.indent(Right); + } +}; + +template class RepeatAdapter : public AdapterBase { + size_t Count; + +public: + RepeatAdapter(T &&Item, size_t Count) + : AdapterBase(std::forward(Item)), Count(Count) {} + + void format(llvm::raw_ostream &Stream, StringRef Style) { + auto Wrapper = detail::build_format_wrapper(std::forward(Item)); + for (size_t I = 0; I < Count; ++I) { + Wrapper.format(Stream, Style); + } + } +}; +} + +template +detail::AlignAdapter fmt_align(T &&Item, AlignStyle Where, size_t Amount) { + return detail::AlignAdapter(std::forward(Item), Where, Amount); +} + +template +detail::PadAdapter fmt_pad(T &&Item, size_t Left, size_t Right) { + return detail::PadAdapter(std::forward(Item), Left, Right); +} + +template +detail::RepeatAdapter fmt_repeat(T &&Item, size_t Count) { + return detail::RepeatAdapter(std::forward(Item), Count); +} +} + +#endif \ No newline at end of file diff --git a/llvm/include/llvm/Support/FormatCommon.h b/llvm/include/llvm/Support/FormatCommon.h new file mode 100644 index 000000000000..9c8d1980b8c4 --- /dev/null +++ b/llvm/include/llvm/Support/FormatCommon.h @@ -0,0 +1,69 @@ +//===- FormatAdapters.h - Formatters for common LLVM types -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FORMATCOMMON_H +#define LLVM_SUPPORT_FORMATCOMMON_H + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FormatVariadicDetails.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { +enum class AlignStyle { Left, Center, Right }; + +struct FmtAlign { + detail::format_wrapper &Wrapper; + AlignStyle Where; + size_t Amount; + + FmtAlign(detail::format_wrapper &Wrapper, AlignStyle Where, size_t Amount) + : Wrapper(Wrapper), Where(Where), Amount(Amount) {} + + void format(raw_ostream &S, StringRef Options) { + // If we don't need to align, we can format straight into the underlying + // stream. Otherwise we have to go through an intermediate stream first + // in order to calculate how long the output is so we can align it. + // TODO: Make the format method return the number of bytes written, that + // way we can also skip the intermediate stream for left-aligned output. + if (Amount == 0) { + Wrapper.format(S, Options); + return; + } + SmallString<64> Item; + raw_svector_ostream Stream(Item); + + Wrapper.format(Stream, Options); + if (Amount <= Item.size()) { + S << Item; + return; + } + + size_t PadAmount = Amount - Item.size(); + switch (Where) { + case AlignStyle::Left: + S << Item; + S.indent(PadAmount); + break; + case AlignStyle::Center: { + size_t X = PadAmount / 2; + S.indent(X); + S << Item; + S.indent(PadAmount - X); + break; + } + default: + S.indent(PadAmount); + S << Item; + break; + } + } +}; +} + +#endif \ No newline at end of file diff --git a/llvm/include/llvm/Support/FormatProviders.h b/llvm/include/llvm/Support/FormatProviders.h new file mode 100644 index 000000000000..a7c774428f5a --- /dev/null +++ b/llvm/include/llvm/Support/FormatProviders.h @@ -0,0 +1,411 @@ +//===- FormatProviders.h - Formatters for common LLVM types -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements format providers for many common LLVM types, for example +// allowing precision and width specifiers for scalar and string types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FORMATPROVIDERS_H +#define LLVM_SUPPORT_FORMATPROVIDERS_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/FormatVariadicDetails.h" +#include "llvm/Support/NativeFormatting.h" + +#include + +namespace llvm { +namespace detail { +template +struct use_integral_formatter + : public std::integral_constant< + bool, is_one_of::value> {}; + +template +struct use_char_formatter + : public std::integral_constant::value> {}; + +template +struct is_cstring + : public std::integral_constant::value> { +}; + +template +struct use_string_formatter + : public std::integral_constant< + bool, is_one_of::value || + is_cstring::value> {}; + +template +struct use_pointer_formatter + : public std::integral_constant::value && + !is_cstring::value> {}; + +template +struct use_double_formatter + : public std::integral_constant::value> {}; + +class HelperFunctions { +protected: + static Optional parseNumericPrecision(StringRef Str) { + size_t Prec; + Optional Result; + if (Str.empty()) + Result = None; + else if (Str.getAsInteger(10, Prec)) { + assert(false && "Invalid precision specifier"); + Result = None; + } else { + assert(Prec < 100 && "Precision out of range"); + Result = std::min(99u, Prec); + } + return Result; + } + + static bool consumeHexStyle(StringRef &Str, HexPrintStyle &Style) { + if (!Str.startswith_lower("x")) + return false; + + if (Str.consume_front("x-")) + Style = HexPrintStyle::Lower; + else if (Str.consume_front("X-")) + Style = HexPrintStyle::Upper; + else if (Str.consume_front("x+") || Str.consume_front("x")) + Style = HexPrintStyle::PrefixLower; + else if (Str.consume_front("X+") || Str.consume_front("X")) + Style = HexPrintStyle::PrefixUpper; + return true; + } + + static size_t consumeNumHexDigits(StringRef &Str, HexPrintStyle Style, + size_t Default) { + Str.consumeInteger(10, Default); + if (isPrefixedHexStyle(Style)) + Default += 2; + return Default; + } +}; +} + +/// Implementation of format_provider for integral arithmetic types. +/// +/// The options string of an integral type has the grammar: +/// +/// integer_options :: [style][digits] +/// style :: +/// digits :: 0-99 +/// +/// ========================================================================== +/// | style | Meaning | Example | Digits Meaning | +/// -------------------------------------------------------------------------- +/// | | | Input | Output | | +/// ========================================================================== +/// | x- | Hex no prefix, lower | 42 | 2a | Minimum # digits | +/// | X- | Hex no prefix, upper | 42 | 2A | Minimum # digits | +/// | x+ / x | Hex + prefix, lower | 42 | 0x2a | Minimum # digits | +/// | X+ / X | Hex + prefix, upper | 42 | 0x2A | Minimum # digits | +/// | N / n | Digit grouped number | 123456 | 123,456 | Ignored | +/// | D / d | Integer | 100000 | 100000 | Ignored | +/// | (empty) | Same as D / d | | | | +/// ========================================================================== +/// + +template +struct format_provider< + T, typename std::enable_if::value>::type> + : public detail::HelperFunctions { +private: +public: + static void format(const T &V, llvm::raw_ostream &Stream, StringRef Style) { + HexPrintStyle HS; + size_t Digits = 0; + if (consumeHexStyle(Style, HS)) { + Digits = consumeNumHexDigits(Style, HS, 0); + write_hex(Stream, V, HS, Digits); + return; + } + + IntegerStyle IS = IntegerStyle::Integer; + if (Style.consume_front("N") || Style.consume_front("n")) + IS = IntegerStyle::Number; + else if (Style.consume_front("D") || Style.consume_front("d")) + IS = IntegerStyle::Integer; + + Style.consumeInteger(10, Digits); + assert(Style.empty() && "Invalid integral format style!"); + write_integer(Stream, V, Digits, IS); + } +}; + +/// Implementation of format_provider for integral pointer types. +/// +/// The options string of a pointer type has the grammar: +/// +/// pointer_options :: [style][precision] +/// style :: +/// digits :: 0-sizeof(void*) +/// +/// ========================================================================== +/// | S | Meaning | Example | +/// -------------------------------------------------------------------------- +/// | | | Input | Output | +/// ========================================================================== +/// | x- | Hex no prefix, lower | 0xDEADBEEF | deadbeef | +/// | X- | Hex no prefix, upper | 0xDEADBEEF | DEADBEEF | +/// | x+ / x | Hex + prefix, lower | 0xDEADBEEF | 0xdeadbeef | +/// | X+ / X | Hex + prefix, upper | 0xDEADBEEF | 0xDEADBEEF | +/// | (empty) | Same as X+ / X | | | +/// ========================================================================== +/// +/// The default precision is the number of nibbles in a machine word, and in all +/// cases indicates the minimum number of nibbles to print. +template +struct format_provider< + T, typename std::enable_if::value>::type> + : public detail::HelperFunctions { +private: +public: + static void format(const T &V, llvm::raw_ostream &Stream, StringRef Style) { + HexPrintStyle HS = HexPrintStyle::PrefixUpper; + consumeHexStyle(Style, HS); + size_t Digits = consumeNumHexDigits(Style, HS, sizeof(void *) * 2); + write_hex(Stream, reinterpret_cast(V), HS, Digits); + } +}; + +/// Implementation of format_provider for c-style strings and string +/// objects such as std::string and llvm::StringRef. +/// +/// The options string of a string type has the grammar: +/// +/// string_options :: [length] +/// +/// where `length` is an optional integer specifying the maximum number of +/// characters in the string to print. If `length` is omitted, the string is +/// printed up to the null terminator. + +template +struct format_provider< + T, typename std::enable_if::value>::type> { + static void format(const T &V, llvm::raw_ostream &Stream, StringRef Style) { + size_t N = StringRef::npos; + if (!Style.empty() && Style.getAsInteger(10, N)) { + assert(false && "Style is not a valid integer"); + } + llvm::StringRef S(V); + Stream << S.substr(0, N); + } +}; + +/// Implementation of format_provider for characters. +/// +/// The options string of a character type has the grammar: +/// +/// char_options :: (empty) | [integer_options] +/// +/// If `char_options` is empty, the character is displayed as an ASCII +/// character. Otherwise, it is treated as an integer options string. +/// +template +struct format_provider< + T, typename std::enable_if::value>::type> { + static void format(const char &V, llvm::raw_ostream &Stream, + StringRef Style) { + if (Style.empty()) + Stream << V; + else { + int X = static_cast(V); + format_provider::format(X, Stream, Style); + } + } +}; + +/// Implementation of format_provider for type `bool` +/// +/// The options string of a boolean type has the grammar: +/// +/// bool_options :: "" | "Y" | "y" | "D" | "d" | "T" | "t" +/// +/// ================================== +/// | C | Meaning | +/// ================================== +/// | Y | YES / NO | +/// | y | yes / no | +/// | D / d | Integer 0 or 1 | +/// | T | TRUE / FALSE | +/// | t | true / false | +/// | (empty) | Equivalent to 't' | +/// ================================== +template <> struct format_provider { + static void format(const bool &B, llvm::raw_ostream &Stream, + StringRef Style) { + Stream << StringSwitch(Style) + .Case("Y", B ? "YES" : "NO") + .Case("y", B ? "yes" : "no") + .CaseLower("D", B ? "1" : "0") + .Case("T", B ? "TRUE" : "FALSE") + .Cases("t", "", B ? "true" : "false") + .Default(B ? "1" : "0"); + } +}; + +/// Implementation of format_provider for floating point types. +/// +/// The options string of a floating point type has the format: +/// +/// float_options :: [style][precision] +/// style :: +/// precision :: 0-99 +/// +/// ===================================================== +/// | style | Meaning | Example | +/// ----------------------------------------------------- +/// | | | Input | Output | +/// ===================================================== +/// | P / p | Percentage | 0.05 | 5.00% | +/// | F / f | Fixed point | 1.0 | 1.00 | +/// | E | Exponential with E | 100000 | 1.0E+05 | +/// | e | Exponential with e | 100000 | 1.0e+05 | +/// | (empty) | Same as F / f | | | +/// ===================================================== +/// +/// The default precision is 6 for exponential (E / e) and 2 for everything +/// else. + +template +struct format_provider< + T, typename std::enable_if::value>::type> + : public detail::HelperFunctions { + static void format(const T &V, llvm::raw_ostream &Stream, StringRef Style) { + FloatStyle S; + if (Style.consume_front("P") || Style.consume_front("p")) + S = FloatStyle::Percent; + else if (Style.consume_front("F") || Style.consume_front("f")) + S = FloatStyle::Fixed; + else if (Style.consume_front("E")) + S = FloatStyle::ExponentUpper; + else if (Style.consume_front("e")) + S = FloatStyle::Exponent; + else + S = FloatStyle::Fixed; + + Optional Precision = parseNumericPrecision(Style); + if (!Precision.hasValue()) + Precision = getDefaultPrecision(S); + + write_double(Stream, static_cast(V), S, Precision); + } +}; + +namespace detail { +template +using IterValue = typename std::iterator_traits::value_type; + +template +struct range_item_has_provider + : public std::integral_constant< + bool, !uses_missing_provider>::value> {}; +} + +/// Implementation of format_provider for ranges. +/// +/// This will print an arbitrary range as a delimited sequence of items. +/// +/// The options string of a range type has the grammar: +/// +/// range_style ::= [separator] [element_style] +/// separator ::= "$" delimeted_expr +/// element_style ::= "@" delimeted_expr +/// delimeted_expr ::= "[" expr "]" | "(" expr ")" | "<" expr ">" +/// expr ::= +/// +/// where the separator expression is the string to insert between consecutive +/// items in the range and the argument expression is the Style specification to +/// be used when formatting the underlying type. The default separator if +/// unspecified is ' ' (space). The syntax of the argument expression follows +/// whatever grammar is dictated by the format provider or format adapter used +/// to format the value type. +/// +/// Note that attempting to format an `iterator_range` where no format +/// provider can be found for T will result in a compile error. +/// + +template class format_provider> { + using value = typename std::iterator_traits::value_type; + using reference = typename std::iterator_traits::reference; + + static StringRef consumeOneOption(StringRef &Style, char Indicator, + StringRef Default) { + if (Style.empty()) + return Default; + if (Style.front() != Indicator) + return Default; + Style = Style.drop_front(); + if (Style.empty()) { + assert(false && "Invalid range style"); + return Default; + } + + std::vector Delims = {"[]", "<>", "()"}; + for (const char *D : Delims) { + if (Style.front() != D[0]) + continue; + size_t End = Style.find_first_of(D[1]); + if (End == StringRef::npos) { + assert(false && "Missing range option end delimeter!"); + return Default; + } + StringRef Result = Style.slice(1, End); + Style = Style.drop_front(End + 1); + return Result; + } + assert(false && "Invalid range style!"); + return Default; + } + + static std::pair parseOptions(StringRef Style) { + StringRef Sep = consumeOneOption(Style, '$', ", "); + StringRef Args = consumeOneOption(Style, '@', ""); + assert(Style.empty() && "Unexpected text in range option string!"); + return std::make_pair(Sep, Args); + } + +public: + static_assert(detail::range_item_has_provider::value, + "Range value_type does not have a format provider!"); + static void format(const llvm::iterator_range &V, + llvm::raw_ostream &Stream, StringRef Style) { + StringRef Sep; + StringRef ArgStyle; + std::tie(Sep, ArgStyle) = parseOptions(Style); + auto Begin = V.begin(); + auto End = V.end(); + if (Begin != End) { + auto Wrapper = + detail::build_format_wrapper(std::forward(*Begin)); + Wrapper.format(Stream, ArgStyle); + ++Begin; + } + while (Begin != End) { + Stream << Sep; + auto Wrapper = + detail::build_format_wrapper(std::forward(*Begin)); + Wrapper.format(Stream, ArgStyle); + ++Begin; + } + } +}; +} + +#endif diff --git a/llvm/include/llvm/Support/FormatVariadic.h b/llvm/include/llvm/Support/FormatVariadic.h new file mode 100644 index 000000000000..1f3ed73edbe0 --- /dev/null +++ b/llvm/include/llvm/Support/FormatVariadic.h @@ -0,0 +1,246 @@ +//===- FormatVariadic.h - Efficient type-safe string formatting --*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the formatv() function which can be used with other LLVM +// subsystems to provide printf-like formatting, but with improved safety and +// flexibility. The result of `formatv` is an object which can be streamed to +// a raw_ostream or converted to a std::string or llvm::SmallString. +// +// // Convert to std::string. +// std::string S = formatv("{0} {1}", 1234.412, "test").str(); +// +// // Convert to llvm::SmallString +// SmallString<8> S = formatv("{0} {1}", 1234.412, "test").sstr<8>(); +// +// // Stream to an existing raw_ostream. +// OS << formatv("{0} {1}", 1234.412, "test"); +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FORMATVARIADIC_H +#define LLVM_SUPPORT_FORMATVARIADIC_H + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/DataTypes.h" +#include "llvm/Support/FormatCommon.h" +#include "llvm/Support/FormatProviders.h" +#include "llvm/Support/FormatVariadicDetails.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include + +namespace llvm { + +enum class ReplacementType { Empty, Format, Literal }; + +struct ReplacementItem { + ReplacementItem() {} + explicit ReplacementItem(StringRef Literal) + : Type(ReplacementType::Literal), Spec(Literal) {} + ReplacementItem(StringRef Spec, size_t Index, size_t Align, AlignStyle Where, + char Pad, StringRef Options) + : Type(ReplacementType::Format), Spec(Spec), Index(Index), Align(Align), + Where(Where), Pad(Pad), Options(Options) {} + ReplacementType Type = ReplacementType::Empty; + StringRef Spec; + size_t Index = 0; + size_t Align = 0; + AlignStyle Where = AlignStyle::Right; + char Pad; + StringRef Options; +}; + +class formatv_object_base { +protected: + // The parameters are stored in a std::tuple, which does not provide runtime + // indexing capabilities. In order to enable runtime indexing, we use this + // structure to put the parameters into a std::vector. Since the parameters + // are not all the same type, we use some type-erasure by wrapping the + // parameters in a template class that derives from a non-template superclass. + // Essentially, we are converting a std::tuple> to a + // std::vector. + struct create_wrappers { + template + std::vector operator()(Ts &... Items) { + return std::vector{&Items...}; + } + }; + + StringRef Fmt; + std::vector Wrappers; + std::vector Replacements; + + static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where, + size_t &Align, char &Pad); + + static std::pair + splitLiteralAndReplacement(StringRef Fmt); + +public: + formatv_object_base(StringRef Fmt, std::size_t ParamCount) + : Fmt(Fmt), Replacements(parseFormatString(Fmt)) { + Wrappers.reserve(ParamCount); + return; + } + + void format(raw_ostream &S) const { + for (auto &R : Replacements) { + if (R.Type == ReplacementType::Empty) + continue; + if (R.Type == ReplacementType::Literal) { + S << R.Spec; + continue; + } + if (R.Index >= Wrappers.size()) { + S << R.Spec; + continue; + } + + auto W = Wrappers[R.Index]; + + FmtAlign Align(*W, R.Where, R.Align); + Align.format(S, R.Options); + } + } + static std::vector parseFormatString(StringRef Fmt); + + static Optional parseReplacementItem(StringRef Spec); + + std::string str() const { + std::string Result; + raw_string_ostream Stream(Result); + Stream << *this; + Stream.flush(); + return Result; + } + + template llvm::SmallString sstr() const { + SmallString Result; + raw_svector_ostream Stream(Result); + Stream << *this; + return Result; + } + + template operator SmallString() const { return sstr(); } + + operator std::string() const { return str(); } +}; + +template class formatv_object : public formatv_object_base { + // Storage for the parameter wrappers. Since the base class erases the type + // of the parameters, we have to own the storage for the parameters here, and + // have the base class store type-erased pointers into this tuple. + Tuple Parameters; + +public: + formatv_object(StringRef Fmt, Tuple &&Params) + : formatv_object_base(Fmt, std::tuple_size::value), + Parameters(std::move(Params)) { + Wrappers = apply_tuple(create_wrappers(), Parameters); + } +}; + +// \brief Format text given a format string and replacement parameters. +// +// ===General Description=== +// +// Formats textual output. `Fmt` is a string consisting of one or more +// replacement sequences with the following grammar: +// +// rep_field ::= "{" [index] ["," layout] [":" format] "}" +// index ::= +// layout ::= [[[char]loc]width] +// format ::= +// char ::= +// loc ::= "-" | "=" | "+" +// width ::= +// +// index - A non-negative integer specifying the index of the item in the +// parameter pack to print. Any other value is invalid. +// layout - A string controlling how the field is laid out within the available +// space. +// format - A type-dependent string used to provide additional options to +// the formatting operation. Refer to the documentation of the +// various individual format providers for per-type options. +// char - The padding character. Defaults to ' ' (space). Only valid if +// `loc` is also specified. +// loc - Where to print the formatted text within the field. Only valid if +// `width` is also specified. +// '-' : The field is left aligned within the available space. +// '=' : The field is centered within the available space. +// '+' : The field is right aligned within the available space (this +// is the default). +// width - The width of the field within which to print the formatted text. +// If this is less than the required length then the `char` and `loc` +// fields are ignored, and the field is printed with no leading or +// trailing padding. If this is greater than the required length, +// then the text is output according to the value of `loc`, and padded +// as appropriate on the left and/or right by `char`. +// +// ===Special Characters=== +// +// The characters '{' and '}' are reserved and cannot appear anywhere within a +// replacement sequence. Outside of a replacement sequence, in order to print +// a literal '{' or '}' it must be doubled -- "{{" to print a literal '{' and +// "}}" to print a literal '}'. +// +// ===Parameter Indexing=== +// `index` specifies the index of the paramter in the parameter pack to format +// into the output. Note that it is possible to refer to the same parameter +// index multiple times in a given format string. This makes it possible to +// output the same value multiple times without passing it multiple times to the +// function. For example: +// +// formatv("{0} {1} {0}", "a", "bb") +// +// would yield the string "abba". This can be convenient when it is expensive +// to compute the value of the parameter, and you would otherwise have had to +// save it to a temporary. +// +// ===Formatter Search=== +// +// For a given parameter of type T, the following steps are executed in order +// until a match is found: +// +// 1. If the parameter is of class type, and contains a method +// void format(raw_ostream &Stream, StringRef Options) +// Then this method is invoked to produce the formatted output. The +// implementation should write the formatted text into `Stream`. +// 2. If there is a suitable template specialization of format_provider<> +// for type T containing a method whose signature is: +// void format(const T &Obj, raw_ostream &Stream, StringRef Options) +// Then this method is invoked as described in Step 1. +// +// If a match cannot be found through either of the above methods, a compiler +// error is generated. +// +// ===Invalid Format String Handling=== +// +// In the case of a format string which does not match the grammar described +// above, the output is undefined. With asserts enabled, LLVM will trigger an +// assertion. Otherwise, it will try to do something reasonable, but in general +// the details of what that is are undefined. +// +template +inline auto formatv(const char *Fmt, Ts &&... Vals) -> formatv_object(Vals))...))> { + using ParamTuple = decltype( + std::make_tuple(detail::build_format_wrapper(std::forward(Vals))...)); + return formatv_object( + Fmt, + std::make_tuple(detail::build_format_wrapper(std::forward(Vals))...)); +} + +} // end namespace llvm + +#endif diff --git a/llvm/include/llvm/Support/FormatVariadicDetails.h b/llvm/include/llvm/Support/FormatVariadicDetails.h new file mode 100644 index 000000000000..5d8b0b69bbf9 --- /dev/null +++ b/llvm/include/llvm/Support/FormatVariadicDetails.h @@ -0,0 +1,149 @@ +//===- FormatVariadicDetails.h - Helpers for FormatVariadic.h ----*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FORMATVARIADIC_DETAILS_H +#define LLVM_SUPPORT_FORMATVARIADIC_DETAILS_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +#include + +namespace llvm { +template struct format_provider {}; + +namespace detail { + +class format_wrapper { +protected: + ~format_wrapper() {} + +public: + virtual void format(llvm::raw_ostream &S, StringRef Options) = 0; +}; + +template class member_format_wrapper : public format_wrapper { + T Item; + +public: + explicit member_format_wrapper(T &&Item) : Item(Item) {} + + void format(llvm::raw_ostream &S, StringRef Options) override { + Item.format(S, Options); + } +}; + +template class provider_format_wrapper : public format_wrapper { + T Item; + +public: + explicit provider_format_wrapper(T &&Item) : Item(Item) {} + + void format(llvm::raw_ostream &S, StringRef Options) override { + format_provider::type>::format(Item, S, Options); + } +}; + +template class missing_format_wrapper : public format_wrapper { +public: + missing_format_wrapper() { + static_assert(false, "T does not have a format_provider"); + } + void format(llvm::raw_ostream &S, StringRef Options) override {} +}; + +// Test if T is a class that contains a member function with the signature: +// +// void format(raw_ostream &, StringRef); +// +template class has_FormatMember { +public: + static bool const value = false; +}; + +template +class has_FormatMember::value>::type> { + using Signature_format = void (T::*)(llvm::raw_ostream &S, StringRef Options); + + template + static char test2(SameType *); + + template static double test2(...); + +public: + static bool const value = (sizeof(test2(nullptr)) == 1); +}; + +// Test if format_provider is defined on T and contains a member function +// with the signature: +// static void format(const T&, raw_stream &, StringRef); +// +template class has_FormatProvider { +public: + using Decayed = typename std::decay::type; + typedef void (*Signature_format)(const Decayed &, llvm::raw_ostream &, + StringRef); + + template + static char test(SameType *); + + template static double test(...); + + static bool const value = + (sizeof(test>(nullptr)) == 1); +}; + +// Simple template that decides whether a type T should use the member-function +// based format() invocation. +template +struct uses_format_member + : public std::integral_constant::value> {}; + +// Simple template that decides whether a type T should use the format_provider +// based format() invocation. The member function takes priority, so this test +// will only be true if there is not ALSO a format member. +template +struct uses_format_provider + : public std::integral_constant::value && + has_FormatProvider::value> {}; + +// Simple template that decides whether a type T has neither a member-function +// nor format_provider based implementation that it can use. Mostly used so +// that the compiler spits out a nice diagnostic when a type with no format +// implementation can be located. +template +struct uses_missing_provider + : public std::integral_constant::value && + !has_FormatProvider::value> {}; + +template +typename std::enable_if::value, + member_format_wrapper>::type +build_format_wrapper(T &&Item) { + return member_format_wrapper(std::forward(Item)); +} + +template +typename std::enable_if::value, + provider_format_wrapper>::type +build_format_wrapper(T &&Item) { + return provider_format_wrapper(std::forward(Item)); +} + +template +typename std::enable_if::value, + missing_format_wrapper>::type +build_format_wrapper(T &&Item) { + return missing_format_wrapper(); +} +} +} + +#endif diff --git a/llvm/include/llvm/Support/NativeFormatting.h b/llvm/include/llvm/Support/NativeFormatting.h index 56da16373b55..bd4a0c9cb8cc 100644 --- a/llvm/include/llvm/Support/NativeFormatting.h +++ b/llvm/include/llvm/Support/NativeFormatting.h @@ -25,12 +25,19 @@ enum class HexPrintStyle { Upper, Lower, PrefixUpper, PrefixLower }; size_t getDefaultPrecision(FloatStyle Style); -void write_integer(raw_ostream &S, unsigned int N, IntegerStyle Style); -void write_integer(raw_ostream &S, int N, IntegerStyle Style); -void write_integer(raw_ostream &S, unsigned long N, IntegerStyle Style); -void write_integer(raw_ostream &S, long N, IntegerStyle Style); -void write_integer(raw_ostream &S, unsigned long long N, IntegerStyle Style); -void write_integer(raw_ostream &S, long long N, IntegerStyle Style); +bool isPrefixedHexStyle(HexPrintStyle S); + +void write_integer(raw_ostream &S, unsigned int N, size_t MinDigits, + IntegerStyle Style); +void write_integer(raw_ostream &S, int N, size_t MinDigits, IntegerStyle Style); +void write_integer(raw_ostream &S, unsigned long N, size_t MinDigits, + IntegerStyle Style); +void write_integer(raw_ostream &S, long N, size_t MinDigits, + IntegerStyle Style); +void write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits, + IntegerStyle Style); +void write_integer(raw_ostream &S, long long N, size_t MinDigits, + IntegerStyle Style); void write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style, Optional Width = None); diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h index 19757234a925..f38c85af75f7 100644 --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -11,6 +11,7 @@ #define LLVM_SUPPORT_YAMLTRAITS_H #include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -201,10 +202,6 @@ struct DocumentListTraits { // static T::value_type& element(IO &io, T &seq, size_t index); }; -// Only used by compiler if both template types are the same -template -struct SameType; - // Only used for better diagnostics of missing traits template struct MissingTrait; diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h index 3753a8ff0cd2..df8de09b5814 100644 --- a/llvm/include/llvm/Support/raw_ostream.h +++ b/llvm/include/llvm/Support/raw_ostream.h @@ -20,6 +20,7 @@ #include namespace llvm { +class formatv_object_base; class format_object_base; class FormattedString; class FormattedNumber; @@ -223,6 +224,9 @@ public: // Formatted output, see the formatHex() function in Support/Format.h. raw_ostream &operator<<(const FormattedNumber &); + // Formatted output, see the formatv() function in Support/FormatVariadic.h. + raw_ostream &operator<<(const formatv_object_base &); + // Formatted output, see the format_bytes() function in Support/Format.h. raw_ostream &operator<<(const FormattedBytes &); diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 5b14c828db01..c31db7cd0e35 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -55,6 +55,7 @@ add_llvm_library(LLVMSupport FileOutputBuffer.cpp FoldingSet.cpp FormattedStream.cpp + FormatVariadic.cpp GraphWriter.cpp Hashing.cpp IntEqClasses.cpp diff --git a/llvm/lib/Support/FormatVariadic.cpp b/llvm/lib/Support/FormatVariadic.cpp new file mode 100644 index 000000000000..de61dae814b5 --- /dev/null +++ b/llvm/lib/Support/FormatVariadic.cpp @@ -0,0 +1,156 @@ +//===- FormatVariadic.cpp - Format string parsing and analysis ----*-C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +//===----------------------------------------------------------------------===// + +#include "llvm/Support/FormatVariadic.h" + +using namespace llvm; + +static Optional translateLocChar(char C) { + switch (C) { + case '-': + return AlignStyle::Left; + case '=': + return AlignStyle::Center; + case '+': + return AlignStyle::Right; + default: + return None; + } + LLVM_BUILTIN_UNREACHABLE; +} + +bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where, + size_t &Align, char &Pad) { + Where = AlignStyle::Right; + Align = 0; + Pad = ' '; + if (Spec.empty()) + return true; + + if (Spec.size() > 1) { + // A maximum of 2 characters at the beginning can be used for something + // other + // than the width. + // If Spec[1] is a loc char, then Spec[0] is a pad char and Spec[2:...] + // contains the width. + // Otherwise, if Spec[0] is a loc char, then Spec[1:...] contains the width. + // Otherwise, Spec[0:...] contains the width. + if (auto Loc = translateLocChar(Spec[1])) { + Pad = Spec[0]; + Where = *Loc; + Spec = Spec.drop_front(2); + } else if (auto Loc = translateLocChar(Spec[0])) { + Where = *Loc; + Spec = Spec.drop_front(1); + } + } + + bool Failed = Spec.consumeInteger(0, Align); + return !Failed; +} + +Optional +formatv_object_base::parseReplacementItem(StringRef Spec) { + StringRef RepString = Spec.trim("{}"); + + // If the replacement sequence does not start with a non-negative integer, + // this is an error. + char Pad = ' '; + std::size_t Align = 0; + AlignStyle Where = AlignStyle::Right; + StringRef Options; + size_t Index = 0; + RepString = RepString.trim(); + if (RepString.consumeInteger(0, Index)) { + assert(false && "Invalid replacement sequence index!"); + return ReplacementItem{}; + } + RepString = RepString.trim(); + if (!RepString.empty() && RepString.front() == ',') { + RepString = RepString.drop_front(); + if (!consumeFieldLayout(RepString, Where, Align, Pad)) + assert(false && "Invalid replacement field layout specification!"); + } + RepString = RepString.trim(); + if (!RepString.empty() && RepString.front() == ':') { + Options = RepString.drop_front().trim(); + RepString = StringRef(); + } + RepString = RepString.trim(); + if (!RepString.empty()) { + assert(false && "Unexpected characters found in replacement string!"); + } + + return ReplacementItem{Spec, Index, Align, Where, Pad, Options}; +} + +std::pair +formatv_object_base::splitLiteralAndReplacement(StringRef Fmt) { + StringRef Rep; + StringRef Remainder; + std::size_t From = 0; + while (From < Fmt.size() && From != StringRef::npos) { + std::size_t BO = Fmt.find_first_of('{', From); + // Everything up until the first brace is a literal. + if (BO != 0) + return std::make_pair(ReplacementItem{Fmt.substr(0, BO)}, Fmt.substr(BO)); + + StringRef Braces = + Fmt.drop_front(BO).take_while([](char C) { return C == '{'; }); + // If there is more than one brace, then some of them are escaped. Treat + // these as replacements. + if (Braces.size() > 1) { + size_t NumEscapedBraces = Braces.size() / 2; + StringRef Middle = Fmt.substr(BO, NumEscapedBraces); + StringRef Right = Fmt.drop_front(BO + NumEscapedBraces * 2); + return std::make_pair(ReplacementItem{Middle}, Right); + } + // An unterminated open brace is undefined. We treat the rest of the string + // as a literal replacement, but we assert to indicate that this is + // undefined and that we consider it an error. + std::size_t BC = Fmt.find_first_of('}', BO); + if (BC == StringRef::npos) { + assert( + false && + "Unterminated brace sequence. Escape with {{ for a literal brace."); + return std::make_pair(ReplacementItem{Fmt}, StringRef()); + } + + // Even if there is a closing brace, if there is another open brace before + // this closing brace, treat this portion as literal, and try again with the + // next one. + std::size_t BO2 = Fmt.find_first_of('{', BO + 1); + if (BO2 < BC) + return std::make_pair(ReplacementItem{Fmt.substr(0, BO2)}, + Fmt.substr(BO2)); + + StringRef Spec = Fmt.slice(BO + 1, BC); + StringRef Right = Fmt.substr(BC + 1); + + auto RI = parseReplacementItem(Spec); + if (RI.hasValue()) + return std::make_pair(*RI, Right); + + // If there was an error parsing the replacement item, treat it as an + // invalid replacement spec, and just continue. + From = BC + 1; + } + return std::make_pair(ReplacementItem{Fmt}, StringRef()); +} + +std::vector +formatv_object_base::parseFormatString(StringRef Fmt) { + std::vector Replacements; + ReplacementItem I; + while (!Fmt.empty()) { + std::tie(I, Fmt) = splitLiteralAndReplacement(Fmt); + if (I.Type != ReplacementType::Empty) + Replacements.push_back(I); + } + return Replacements; +} diff --git a/llvm/lib/Support/NativeFormatting.cpp b/llvm/lib/Support/NativeFormatting.cpp index dbfe658e87c2..bb8689141098 100644 --- a/llvm/lib/Support/NativeFormatting.cpp +++ b/llvm/lib/Support/NativeFormatting.cpp @@ -47,8 +47,8 @@ static void writeWithCommas(raw_ostream &S, ArrayRef Buffer) { } template -static void write_unsigned_impl(raw_ostream &S, T N, IntegerStyle Style, - bool IsNegative) { +static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits, + IntegerStyle Style, bool IsNegative) { static_assert(std::is_unsigned::value, "Value is not unsigned!"); char NumberBuffer[128]; @@ -59,6 +59,12 @@ static void write_unsigned_impl(raw_ostream &S, T N, IntegerStyle Style, if (IsNegative) S << '-'; + + if (Len < MinDigits && Style != IntegerStyle::Number) { + for (size_t I = Len; I < MinDigits; ++I) + S << '0'; + } + if (Style == IntegerStyle::Number) { writeWithCommas(S, ArrayRef(std::end(NumberBuffer) - Len, Len)); } else { @@ -67,53 +73,60 @@ static void write_unsigned_impl(raw_ostream &S, T N, IntegerStyle Style, } template -static void write_unsigned(raw_ostream &S, T N, IntegerStyle Style, - bool IsNegative = false) { +static void write_unsigned(raw_ostream &S, T N, size_t MinDigits, + IntegerStyle Style, bool IsNegative = false) { // Output using 32-bit div/mod if possible. if (N == static_cast(N)) - write_unsigned_impl(S, static_cast(N), Style, IsNegative); + write_unsigned_impl(S, static_cast(N), MinDigits, Style, + IsNegative); else - write_unsigned_impl(S, N, Style, IsNegative); + write_unsigned_impl(S, N, MinDigits, Style, IsNegative); } template -static void write_signed(raw_ostream &S, T N, IntegerStyle Style) { +static void write_signed(raw_ostream &S, T N, size_t MinDigits, + IntegerStyle Style) { static_assert(std::is_signed::value, "Value is not signed!"); using UnsignedT = typename std::make_unsigned::type; if (N >= 0) { - write_unsigned(S, static_cast(N), Style); + write_unsigned(S, static_cast(N), MinDigits, Style); return; } UnsignedT UN = -(UnsignedT)N; - write_unsigned(S, UN, Style, true); + write_unsigned(S, UN, MinDigits, Style, true); } -void llvm::write_integer(raw_ostream &S, unsigned int N, IntegerStyle Style) { - write_unsigned(S, N, Style); -} - -void llvm::write_integer(raw_ostream &S, int N, IntegerStyle Style) { - write_signed(S, N, Style); -} - -void llvm::write_integer(raw_ostream &S, unsigned long N, IntegerStyle Style) { - write_unsigned(S, N, Style); -} - -void llvm::write_integer(raw_ostream &S, long N, IntegerStyle Style) { - write_signed(S, N, Style); -} - -void llvm::write_integer(raw_ostream &S, unsigned long long N, +void llvm::write_integer(raw_ostream &S, unsigned int N, size_t MinDigits, IntegerStyle Style) { - write_unsigned(S, N, Style); + write_unsigned(S, N, MinDigits, Style); } -void llvm::write_integer(raw_ostream &S, long long N, IntegerStyle Style) { - write_signed(S, N, Style); +void llvm::write_integer(raw_ostream &S, int N, size_t MinDigits, + IntegerStyle Style) { + write_signed(S, N, MinDigits, Style); +} + +void llvm::write_integer(raw_ostream &S, unsigned long N, size_t MinDigits, + IntegerStyle Style) { + write_unsigned(S, N, MinDigits, Style); +} + +void llvm::write_integer(raw_ostream &S, long N, size_t MinDigits, + IntegerStyle Style) { + write_signed(S, N, MinDigits, Style); +} + +void llvm::write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits, + IntegerStyle Style) { + write_unsigned(S, N, MinDigits, Style); +} + +void llvm::write_integer(raw_ostream &S, long long N, size_t MinDigits, + IntegerStyle Style) { + write_signed(S, N, MinDigits, Style); } void llvm::write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style, @@ -178,7 +191,9 @@ void llvm::write_double(raw_ostream &S, double N, FloatStyle Style, #if defined(__MINGW32__) // FIXME: It should be generic to C++11. if (N == 0.0 && std::signbit(N)) { - const char *NegativeZero = "-0.000000e+00"; + char NegativeZero[] = "-0.000000e+00"; + if (Style == FloatStyle::ExponentUpper) + NegativeZero[strlen(NegativeZero) - 4] = 'E'; S << NegativeZero; return; } @@ -187,7 +202,9 @@ void llvm::write_double(raw_ostream &S, double N, FloatStyle Style, // negative zero if (fpcl == _FPCLASS_NZ) { - const char *NegativeZero = "-0.000000e+00"; + char NegativeZero[] = "-0.000000e+00"; + if (Style == FloatStyle::ExponentUpper) + NegativeZero[strlen(NegativeZero) - 4] = 'E'; S << NegativeZero; return; } @@ -231,6 +248,10 @@ void llvm::write_double(raw_ostream &S, double N, FloatStyle Style, S << '%'; } +bool llvm::isPrefixedHexStyle(HexPrintStyle S) { + return (S == HexPrintStyle::PrefixLower || S == HexPrintStyle::PrefixUpper); +} + size_t llvm::getDefaultPrecision(FloatStyle Style) { switch (Style) { case FloatStyle::Exponent: diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp index 218d4986fff9..92c7967cd8f3 100644 --- a/llvm/lib/Support/raw_ostream.cpp +++ b/llvm/lib/Support/raw_ostream.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/NativeFormatting.h" #include "llvm/Support/Process.h" @@ -114,22 +115,22 @@ void raw_ostream::SetBufferAndMode(char *BufferStart, size_t Size, } raw_ostream &raw_ostream::operator<<(unsigned long N) { - write_integer(*this, static_cast(N), IntegerStyle::Integer); + write_integer(*this, static_cast(N), 0, IntegerStyle::Integer); return *this; } raw_ostream &raw_ostream::operator<<(long N) { - write_integer(*this, static_cast(N), IntegerStyle::Integer); + write_integer(*this, static_cast(N), 0, IntegerStyle::Integer); return *this; } raw_ostream &raw_ostream::operator<<(unsigned long long N) { - write_integer(*this, static_cast(N), IntegerStyle::Integer); + write_integer(*this, static_cast(N), 0, IntegerStyle::Integer); return *this; } raw_ostream &raw_ostream::operator<<(long long N) { - write_integer(*this, static_cast(N), IntegerStyle::Integer); + write_integer(*this, static_cast(N), 0, IntegerStyle::Integer); return *this; } @@ -318,6 +319,12 @@ raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) { } } +raw_ostream &raw_ostream::operator<<(const formatv_object_base &Obj) { + SmallString<128> S; + Obj.format(*this); + return *this; +} + raw_ostream &raw_ostream::operator<<(const FormattedString &FS) { unsigned Len = FS.Str.size(); int PadAmount = FS.Width - Len; @@ -344,7 +351,7 @@ raw_ostream &raw_ostream::operator<<(const FormattedNumber &FN) { } else { llvm::SmallString<16> Buffer; llvm::raw_svector_ostream Stream(Buffer); - llvm::write_integer(Stream, FN.DecValue, IntegerStyle::Integer); + llvm::write_integer(Stream, FN.DecValue, 0, IntegerStyle::Integer); if (Buffer.size() < FN.Width) indent(FN.Width - Buffer.size()); (*this) << Buffer; diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index 695f157140ac..d887e2179423 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_unittest(SupportTests ErrorTest.cpp ErrorOrTest.cpp FileOutputBufferTest.cpp + FormatVariadicTest.cpp Host.cpp LEB128Test.cpp LineIteratorTest.cpp diff --git a/llvm/unittests/Support/FormatVariadicTest.cpp b/llvm/unittests/Support/FormatVariadicTest.cpp new file mode 100644 index 000000000000..7dd23cfa2f85 --- /dev/null +++ b/llvm/unittests/Support/FormatVariadicTest.cpp @@ -0,0 +1,538 @@ +//===- FormatVariadicTest.cpp - Unit tests for string formatting ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/FormatVariadic.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(FormatVariadicTest, EmptyFormatString) { + auto Replacements = formatv_object_base::parseFormatString(""); + EXPECT_EQ(0U, Replacements.size()); +} + +TEST(FormatVariadicTest, NoReplacements) { + const StringRef kFormatString = "This is a test"; + auto Replacements = formatv_object_base::parseFormatString(kFormatString); + ASSERT_EQ(1U, Replacements.size()); + EXPECT_EQ(kFormatString, Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type); +} + +TEST(FormatVariadicTest, EscapedBrace) { + // {{ should be replaced with { + auto Replacements = formatv_object_base::parseFormatString("{{"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("{", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type); + + // An even number N of braces should be replaced with N/2 braces. + Replacements = formatv_object_base::parseFormatString("{{{{{{"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("{{{", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type); +} + +TEST(FormatVariadicTest, ValidReplacementSequence) { + // 1. Simple replacement - parameter index only + auto Replacements = formatv_object_base::parseFormatString("{0}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ("", Replacements[0].Options); + + Replacements = formatv_object_base::parseFormatString("{1}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(1u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ("", Replacements[0].Options); + + // 2. Parameter index with right alignment + Replacements = formatv_object_base::parseFormatString("{0,3}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ("", Replacements[0].Options); + + // 3. And left alignment + Replacements = formatv_object_base::parseFormatString("{0,-3}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Left, Replacements[0].Where); + EXPECT_EQ("", Replacements[0].Options); + + // 4. And center alignment + Replacements = formatv_object_base::parseFormatString("{0,=3}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Center, Replacements[0].Where); + EXPECT_EQ("", Replacements[0].Options); + + // 4. Parameter index with option string + Replacements = formatv_object_base::parseFormatString("{0:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ("foo", Replacements[0].Options); + + // 5. Parameter index with alignment before option string + Replacements = formatv_object_base::parseFormatString("{0,-3:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Left, Replacements[0].Where); + EXPECT_EQ("foo", Replacements[0].Options); + + // 7. Parameter indices, options, and alignment can all have whitespace. + Replacements = formatv_object_base::parseFormatString("{ 0, -3 : foo }"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Left, Replacements[0].Where); + EXPECT_EQ("foo", Replacements[0].Options); + + // 8. Everything after the first option specifier is part of the style, even + // if it contains another option specifier. + Replacements = formatv_object_base::parseFormatString("{0:0:1}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0:0:1", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ("0:1", Replacements[0].Options); +} + +TEST(FormatVariadicTest, DefaultReplacementValues) { + // 2. If options string is missing, it defaults to empty. + auto Replacements = formatv_object_base::parseFormatString("{0,3}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ("", Replacements[0].Options); + + // Including if the colon is present but contains no text. + Replacements = formatv_object_base::parseFormatString("{0,3:}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(3u, Replacements[0].Align); + EXPECT_EQ("", Replacements[0].Options); + + // 3. If alignment is missing, it defaults to 0, right, space + Replacements = formatv_object_base::parseFormatString("{0:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ(' ', Replacements[0].Pad); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ("foo", Replacements[0].Options); +} + +TEST(FormatVariadicTest, MultipleReplacements) { + auto Replacements = + formatv_object_base::parseFormatString("{0} {1:foo}-{2,-3:bar}"); + ASSERT_EQ(5u, Replacements.size()); + // {0} + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(0u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ("", Replacements[0].Options); + + // " " + EXPECT_EQ(ReplacementType::Literal, Replacements[1].Type); + EXPECT_EQ(" ", Replacements[1].Spec); + + // {1:foo} - Options=foo + EXPECT_EQ(ReplacementType::Format, Replacements[2].Type); + EXPECT_EQ(1u, Replacements[2].Index); + EXPECT_EQ(0u, Replacements[2].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[2].Where); + EXPECT_EQ("foo", Replacements[2].Options); + + // "-" + EXPECT_EQ(ReplacementType::Literal, Replacements[3].Type); + EXPECT_EQ("-", Replacements[3].Spec); + + // {2:bar,-3} - Options=bar, Align=-3 + EXPECT_EQ(ReplacementType::Format, Replacements[4].Type); + EXPECT_EQ(2u, Replacements[4].Index); + EXPECT_EQ(3u, Replacements[4].Align); + EXPECT_EQ(AlignStyle::Left, Replacements[4].Where); + EXPECT_EQ("bar", Replacements[4].Options); +} + +TEST(FormatVariadicTest, FormatNoReplacements) { + EXPECT_EQ("", formatv("").str()); + EXPECT_EQ("Test", formatv("Test").str()); +} + +TEST(FormatVariadicTest, FormatBasicTypesOneReplacement) { + EXPECT_EQ("1", formatv("{0}", 1).str()); + EXPECT_EQ("c", formatv("{0}", 'c').str()); + EXPECT_EQ("-3", formatv("{0}", -3).str()); + EXPECT_EQ("Test", formatv("{0}", "Test").str()); + EXPECT_EQ("Test2", formatv("{0}", StringRef("Test2")).str()); + EXPECT_EQ("Test3", formatv("{0}", std::string("Test3")).str()); +} + +TEST(FormatVariadicTest, IntegralHexFormatting) { + // 1. Trivial cases. Make sure hex is not the default. + EXPECT_EQ("0", formatv("{0}", 0).str()); + EXPECT_EQ("2748", formatv("{0}", 0xABC).str()); + EXPECT_EQ("-2748", formatv("{0}", -0xABC).str()); + + // 3. various hex prefixes. + EXPECT_EQ("0xFF", formatv("{0:X}", 255).str()); + EXPECT_EQ("0xFF", formatv("{0:X+}", 255).str()); + EXPECT_EQ("0xff", formatv("{0:x}", 255).str()); + EXPECT_EQ("0xff", formatv("{0:x+}", 255).str()); + EXPECT_EQ("FF", formatv("{0:X-}", 255).str()); + EXPECT_EQ("ff", formatv("{0:x-}", 255).str()); + + // 5. Precision pads left of the most significant digit but right of the + // prefix (if one exists). + EXPECT_EQ("0xFF", formatv("{0:X2}", 255).str()); + EXPECT_EQ("0xFF", formatv("{0:X+2}", 255).str()); + EXPECT_EQ("0x0ff", formatv("{0:x3}", 255).str()); + EXPECT_EQ("0x0ff", formatv("{0:x+3}", 255).str()); + EXPECT_EQ("00FF", formatv("{0:X-4}", 255).str()); + EXPECT_EQ("00ff", formatv("{0:x-4}", 255).str()); + + // 6. Try some larger types. + EXPECT_EQ("0xDEADBEEFDEADBEEF", + formatv("{0:X16}", -2401053088876216593LL).str()); + EXPECT_EQ("0xFEEBDAEDFEEBDAED", + formatv("{0:X16}", 0xFEEBDAEDFEEBDAEDULL).str()); + EXPECT_EQ("0x00000000DEADBEEF", formatv("{0:X16}", 0xDEADBEEF).str()); + + // 7. Padding should take into account the prefix + EXPECT_EQ("0xff", formatv("{0,4:x}", 255).str()); + EXPECT_EQ(" 0xff", formatv("{0,5:x+}", 255).str()); + EXPECT_EQ(" FF", formatv("{0,4:X-}", 255).str()); + EXPECT_EQ(" ff", formatv("{0,5:x-}", 255).str()); + + // 8. Including when it's been zero-padded + EXPECT_EQ(" 0x0ff", formatv("{0,7:x3}", 255).str()); + EXPECT_EQ(" 0x00ff", formatv("{0,7:x+4}", 255).str()); + EXPECT_EQ(" 000FF", formatv("{0,7:X-5}", 255).str()); + EXPECT_EQ(" 0000ff", formatv("{0,7:x-6}", 255).str()); + + // 9. Precision with default format specifier should work too + EXPECT_EQ(" 255", formatv("{0,7:3}", 255).str()); + EXPECT_EQ(" 0255", formatv("{0,7:4}", 255).str()); + EXPECT_EQ(" 00255", formatv("{0,7:5}", 255).str()); + EXPECT_EQ(" 000255", formatv("{0,7:6}", 255).str()); +} + +TEST(FormatVariadicTest, PointerFormatting) { + // 1. Trivial cases. Hex is default. Default Precision is pointer width. + if (sizeof(void *) == 4) { + EXPECT_EQ("0x00000000", formatv("{0}", (void *)0).str()); + EXPECT_EQ("0x00000ABC", formatv("{0}", (void *)0xABC).str()); + } else { + EXPECT_EQ("0x0000000000000000", formatv("{0}", (void *)0).str()); + EXPECT_EQ("0x0000000000000ABC", formatv("{0}", (void *)0xABC).str()); + } + + // 2. But we can reduce the precision explicitly. + EXPECT_EQ("0x0", formatv("{0:0}", (void *)0).str()); + EXPECT_EQ("0xABC", formatv("{0:0}", (void *)0xABC).str()); + EXPECT_EQ("0x0000", formatv("{0:4}", (void *)0).str()); + EXPECT_EQ("0x0ABC", formatv("{0:4}", (void *)0xABC).str()); + + // 3. various hex prefixes. + EXPECT_EQ("0x0ABC", formatv("{0:X4}", (void *)0xABC).str()); + EXPECT_EQ("0x0abc", formatv("{0:x4}", (void *)0xABC).str()); + EXPECT_EQ("0ABC", formatv("{0:X-4}", (void *)0xABC).str()); + EXPECT_EQ("0abc", formatv("{0:x-4}", (void *)0xABC).str()); +} + +TEST(FormatVariadicTest, IntegralNumberFormatting) { + // 1. Test comma grouping with default widths and precisions. + EXPECT_EQ("0", formatv("{0:N}", 0).str()); + EXPECT_EQ("10", formatv("{0:N}", 10).str()); + EXPECT_EQ("100", formatv("{0:N}", 100).str()); + EXPECT_EQ("1,000", formatv("{0:N}", 1000).str()); + EXPECT_EQ("1,234,567,890", formatv("{0:N}", 1234567890).str()); + EXPECT_EQ("-10", formatv("{0:N}", -10).str()); + EXPECT_EQ("-100", formatv("{0:N}", -100).str()); + EXPECT_EQ("-1,000", formatv("{0:N}", -1000).str()); + EXPECT_EQ("-1,234,567,890", formatv("{0:N}", -1234567890).str()); + + // 2. If there is no comma, width and precision pad to the same absolute + // size. + EXPECT_EQ(" 1", formatv("{0,2:N}", 1).str()); + + // 3. But if there is a comma or negative sign, width factors them in but + // precision doesn't. + EXPECT_EQ(" 1,000", formatv("{0,6:N}", 1000).str()); + EXPECT_EQ(" -1,000", formatv("{0,7:N}", -1000).str()); + + // 4. Large widths all line up. + EXPECT_EQ(" 1,000", formatv("{0,11:N}", 1000).str()); + EXPECT_EQ(" -1,000", formatv("{0,11:N}", -1000).str()); + EXPECT_EQ(" -100,000", formatv("{0,11:N}", -100000).str()); +} + +TEST(FormatVariadicTest, StringFormatting) { + const char FooArray[] = "FooArray"; + const char *FooPtr = "FooPtr"; + llvm::StringRef FooRef("FooRef"); + std::string FooString("FooString"); + // 1. Test that we can print various types of strings. + EXPECT_EQ(FooArray, formatv("{0}", FooArray).str()); + EXPECT_EQ(FooPtr, formatv("{0}", FooPtr).str()); + EXPECT_EQ(FooRef, formatv("{0}", FooRef).str()); + EXPECT_EQ(FooString, formatv("{0}", FooString).str()); + + // 2. Test that the precision specifier prints the correct number of + // characters. + EXPECT_EQ("FooA", formatv("{0:4}", FooArray).str()); + EXPECT_EQ("FooP", formatv("{0:4}", FooPtr).str()); + EXPECT_EQ("FooR", formatv("{0:4}", FooRef).str()); + EXPECT_EQ("FooS", formatv("{0:4}", FooString).str()); + + // 3. And that padding works. + EXPECT_EQ(" FooA", formatv("{0,6:4}", FooArray).str()); + EXPECT_EQ(" FooP", formatv("{0,6:4}", FooPtr).str()); + EXPECT_EQ(" FooR", formatv("{0,6:4}", FooRef).str()); + EXPECT_EQ(" FooS", formatv("{0,6:4}", FooString).str()); +} + +TEST(FormatVariadicTest, CharFormatting) { + // 1. Not much to see here. Just print a char with and without padding. + EXPECT_EQ("C", formatv("{0}", 'C').str()); + EXPECT_EQ(" C", formatv("{0,3}", 'C').str()); + + // 2. char is really an integral type though, where the only difference is + // that the "default" is to print the ASCII. So if a non-default presentation + // specifier exists, it should print as an integer. + EXPECT_EQ("37", formatv("{0:D}", (char)37).str()); + EXPECT_EQ(" 037", formatv("{0,5:D3}", (char)37).str()); +} + +TEST(FormatVariadicTest, BoolTest) { + // 1. Default style is lowercase text (same as 't') + EXPECT_EQ("true", formatv("{0}", true).str()); + EXPECT_EQ("false", formatv("{0}", false).str()); + EXPECT_EQ("true", formatv("{0:t}", true).str()); + EXPECT_EQ("false", formatv("{0:t}", false).str()); + + // 2. T - uppercase text + EXPECT_EQ("TRUE", formatv("{0:T}", true).str()); + EXPECT_EQ("FALSE", formatv("{0:T}", false).str()); + + // 3. D / d - integral + EXPECT_EQ("1", formatv("{0:D}", true).str()); + EXPECT_EQ("0", formatv("{0:D}", false).str()); + EXPECT_EQ("1", formatv("{0:d}", true).str()); + EXPECT_EQ("0", formatv("{0:d}", false).str()); + + // 4. Y - uppercase yes/no + EXPECT_EQ("YES", formatv("{0:Y}", true).str()); + EXPECT_EQ("NO", formatv("{0:Y}", false).str()); + + // 5. y - lowercase yes/no + EXPECT_EQ("yes", formatv("{0:y}", true).str()); + EXPECT_EQ("no", formatv("{0:y}", false).str()); +} + +TEST(FormatVariadicTest, DoubleFormatting) { + // Test exponents, fixed point, and percent formatting. + + // 1. Signed, unsigned, and zero exponent format. + EXPECT_EQ("0.000000E+00", formatv("{0:E}", 0.0).str()); + EXPECT_EQ("-0.000000E+00", formatv("{0:E}", -0.0).str()); + EXPECT_EQ("1.100000E+00", formatv("{0:E}", 1.1).str()); + EXPECT_EQ("-1.100000E+00", formatv("{0:E}", -1.1).str()); + EXPECT_EQ("1.234568E+03", formatv("{0:E}", 1234.5678).str()); + EXPECT_EQ("-1.234568E+03", formatv("{0:E}", -1234.5678).str()); + EXPECT_EQ("1.234568E-03", formatv("{0:E}", .0012345678).str()); + EXPECT_EQ("-1.234568E-03", formatv("{0:E}", -.0012345678).str()); + + // 2. With padding and precision. + EXPECT_EQ(" 0.000E+00", formatv("{0,11:E3}", 0.0).str()); + EXPECT_EQ(" -1.100E+00", formatv("{0,11:E3}", -1.1).str()); + EXPECT_EQ(" 1.235E+03", formatv("{0,11:E3}", 1234.5678).str()); + EXPECT_EQ(" -1.235E-03", formatv("{0,11:E3}", -.0012345678).str()); + + // 3. Signed, unsigned, and zero fixed point format. + EXPECT_EQ("0.00", formatv("{0:F}", 0.0).str()); + EXPECT_EQ("-0.00", formatv("{0:F}", -0.0).str()); + EXPECT_EQ("1.10", formatv("{0:F}", 1.1).str()); + EXPECT_EQ("-1.10", formatv("{0:F}", -1.1).str()); + EXPECT_EQ("1234.57", formatv("{0:F}", 1234.5678).str()); + EXPECT_EQ("-1234.57", formatv("{0:F}", -1234.5678).str()); + EXPECT_EQ("0.00", formatv("{0:F}", .0012345678).str()); + EXPECT_EQ("-0.00", formatv("{0:F}", -.0012345678).str()); + + // 2. With padding and precision. + EXPECT_EQ(" 0.000", formatv("{0,8:F3}", 0.0).str()); + EXPECT_EQ(" -1.100", formatv("{0,8:F3}", -1.1).str()); + EXPECT_EQ("1234.568", formatv("{0,8:F3}", 1234.5678).str()); + EXPECT_EQ(" -0.001", formatv("{0,8:F3}", -.0012345678).str()); +} + +struct format_tuple { + const char *Fmt; + explicit format_tuple(const char *Fmt) : Fmt(Fmt) {} + + template + auto operator()(Ts &&... Values) const + -> decltype(formatv(Fmt, std::forward(Values)...)) { + return formatv(Fmt, std::forward(Values)...); + } +}; + +TEST(FormatVariadicTest, BigTest) { + using Tuple = + std::tuple; + Tuple Ts[] = { + Tuple('a', 1, "Str", StringRef(), std::string(), 3.14159, -.17532f, + (void *)nullptr, 123456, 6.02E23, -908234908423, 908234908422234, + std::numeric_limits::quiet_NaN(), 0xAB), + Tuple('x', 0xDDB5B, "LongerStr", "StringRef", "std::string", -2.7, + .08215f, (void *)nullptr, 0, 6.62E-34, -908234908423, + 908234908422234, std::numeric_limits::infinity(), 0x0)}; + // Test long string formatting with many edge cases combined. + const char *Intro = + "There are {{{0}} items in the tuple, and {{{1}} tuple(s) in the array."; + const char *Header = + "{0,6}|{1,8}|{2,=10}|{3,=10}|{4,=13}|{5,7}|{6,7}|{7,10}|{8," + "-7}|{9,10}|{10,16}|{11,17}|{12,6}|{13,4}"; + const char *Line = + "{0,6}|{1,8:X}|{2,=10}|{3,=10:5}|{4,=13}|{5,7:3}|{6,7:P2}|{7," + "10:X8}|{8,-7:N}|{9,10:E4}|{10,16:N}|{11,17:D}|{12,6}|{13," + "4:X}"; + + std::string S; + llvm::raw_string_ostream Stream(S); + Stream << formatv(Intro, std::tuple_size::value, + llvm::array_lengthof(Ts)) + << "\n"; + Stream << formatv(Header, "Char", "HexInt", "Str", "Ref", "std::str", + "double", "float", "pointer", "comma", "exp", "bigint", + "bigint2", "limit", "byte") + << "\n"; + for (auto &Item : Ts) { + Stream << llvm::apply_tuple(format_tuple(Line), Item) << "\n"; + } + Stream.flush(); + const char *Expected = + R"foo(There are {14} items in the tuple, and {2} tuple(s) in the array. + Char| HexInt| Str | Ref | std::str | double| float| pointer|comma | exp| bigint| bigint2| limit|byte + a| 0x1| Str | | | 3.142|-17.53%|0x00000000|123,456|6.0200E+23|-908,234,908,423| 908234908422234| nan|0xAB + x| 0xDDB5B|LongerStr | Strin | std::string | -2.700| 8.21%|0x00000000|0 |6.6200E-34|-908,234,908,423| 908234908422234| INF| 0x0 +)foo"; + + EXPECT_EQ(Expected, S); +} + +TEST(FormatVariadicTest, Range) { + std::vector IntRange = {1, 1, 2, 3, 5, 8, 13}; + + // 1. Simple range with default separator and element style. + EXPECT_EQ("1, 1, 2, 3, 5, 8, 13", + formatv("{0}", make_range(IntRange.begin(), IntRange.end())).str()); + EXPECT_EQ("1, 2, 3, 5, 8", + formatv("{0}", make_range(IntRange.begin() + 1, IntRange.end() - 1)) + .str()); + + // 2. Non-default separator + EXPECT_EQ( + "1/1/2/3/5/8/13", + formatv("{0:$[/]}", make_range(IntRange.begin(), IntRange.end())).str()); + + // 3. Default separator, non-default element style. + EXPECT_EQ( + "0x1, 0x1, 0x2, 0x3, 0x5, 0x8, 0xd", + formatv("{0:@[x]}", make_range(IntRange.begin(), IntRange.end())).str()); + + // 4. Non-default separator and element style. + EXPECT_EQ( + "0x1 + 0x1 + 0x2 + 0x3 + 0x5 + 0x8 + 0xd", + formatv("{0:$[ + ]@[x]}", make_range(IntRange.begin(), IntRange.end())) + .str()); + + // 5. Element style and/or separator using alternate delimeters to allow using + // delimeter characters as part of the separator. + EXPECT_EQ( + "<0x1><0x1><0x2><0x3><0x5><0x8><0xd>", + formatv("<{0:$[><]@(x)}>", make_range(IntRange.begin(), IntRange.end())) + .str()); + EXPECT_EQ( + "[0x1][0x1][0x2][0x3][0x5][0x8][0xd]", + formatv("[{0:$(][)@[x]}]", make_range(IntRange.begin(), IntRange.end())) + .str()); + EXPECT_EQ( + "(0x1)(0x1)(0x2)(0x3)(0x5)(0x8)(0xd)", + formatv("({0:$<)(>@})", make_range(IntRange.begin(), IntRange.end())) + .str()); + + // 5. Empty range. + EXPECT_EQ("", formatv("{0:$[+]@[x]}", + make_range(IntRange.begin(), IntRange.begin())) + .str()); + + // 6. Empty separator and style. + EXPECT_EQ("11235813", + formatv("{0:$[]@<>}", make_range(IntRange.begin(), IntRange.end())) + .str()); +} + +TEST(FormatVariadicTest, Adapter) { + class Negative { + int N; + + public: + explicit Negative(int N) : N(N) {} + void format(raw_ostream &S, StringRef Options) { S << -N; } + }; + + EXPECT_EQ("-7", formatv("{0}", Negative(7)).str()); + + int N = 171; + + EXPECT_EQ(" 171 ", + formatv("{0}", fmt_align(N, AlignStyle::Center, 7)).str()); + EXPECT_EQ(" 171 ", formatv("{0}", fmt_pad(N, 1, 3)).str()); + EXPECT_EQ("171171171171171", formatv("{0}", fmt_repeat(N, 5)).str()); + + EXPECT_EQ(" ABABABABAB ", + formatv("{0:X-}", fmt_pad(fmt_repeat(N, 5), 1, 3)).str()); + EXPECT_EQ(" AB AB AB AB AB ", + formatv("{0,=34:X-}", fmt_repeat(fmt_pad(N, 1, 3), 5)).str()); +} + +TEST(FormatVariadicTest, ImplicitConversions) { + std::string S = formatv("{0} {1}", 1, 2); + EXPECT_EQ("1 2", S); + + SmallString<4> S2 = formatv("{0} {1}", 1, 2); + EXPECT_EQ("1 2", S2); +} \ No newline at end of file diff --git a/llvm/unittests/Support/NativeFormatTests.cpp b/llvm/unittests/Support/NativeFormatTests.cpp index 94508b7367da..52acb6a99337 100644 --- a/llvm/unittests/Support/NativeFormatTests.cpp +++ b/llvm/unittests/Support/NativeFormatTests.cpp @@ -21,7 +21,7 @@ namespace { template std::string format_number(T N, IntegerStyle Style) { std::string S; llvm::raw_string_ostream Str(S); - write_integer(Str, N, Style); + write_integer(Str, N, 0, Style); Str.flush(); return S; }