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; }