Index: include/llvm/ADT/STLExtras.h =================================================================== --- include/llvm/ADT/STLExtras.h +++ 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 @@ 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 //===----------------------------------------------------------------------===// Index: include/llvm/Support/FormatAdapters.h =================================================================== --- /dev/null +++ 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 struct 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 Index: include/llvm/Support/FormatCommon.h =================================================================== --- /dev/null +++ include/llvm/Support/FormatCommon.h @@ -0,0 +1,63 @@ +//===- 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); + } else { + SmallString<32> Item; + raw_svector_ostream Stream(Item); + + Wrapper.format(Stream, Options); + if (Amount <= Item.size()) { + S << Item; + } else { + size_t PadAmount = Amount - Item.size(); + if (Where == AlignStyle::Left) { + S << Item; + S.indent(PadAmount); + } else if (Where == AlignStyle::Center) { + size_t X = PadAmount / 2; + S.indent(X); + S << Item; + S.indent(PadAmount - X); + } else { + S.indent(PadAmount); + S << Item; + } + } + } + } +}; +} + +#endif \ No newline at end of file Index: include/llvm/Support/FormatProviders.h =================================================================== --- /dev/null +++ include/llvm/Support/FormatProviders.h @@ -0,0 +1,412 @@ +//===- 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/Format.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; + } +}; +} + +/// \brief 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); + } +}; + +/// \brief 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); + } +}; + +/// \brief 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); + } +}; + +/// \brief 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); + } + } +}; + +/// \brief 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"); + } +}; + +/// \brief 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> {}; +} + +/// \brief 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 Index: include/llvm/Support/FormatVariadic.h =================================================================== --- /dev/null +++ include/llvm/Support/FormatVariadic.h @@ -0,0 +1,258 @@ +//===- 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 format_string() function and user-defined literal +// operators, which can be used with other LLVM subsystems to provide +// printf-like formatting, but with improved safety and flexibility. The +// preferred syntax is to use the user-defined literal operators, as follows: +// +// // Convert to string. +// std::string S = "{0} {1}"_fmt.string(1234.412, "test"); +// +// // Stream to an existing raw_ostream. +// OS << "{0} {1}"_fmt.stream(1234.412, "test"); +// +// Sometimes it may be necessary to use a non-literal format string. This +// happens most often when writing a helper function that processes the argument +// list before formatting it. In such a case, you can use the `format_string` +// function directly, as follows: +// +// const char *Fmt = "{0} {1}"; +// OS << "Result: " << format_string(Fmt, 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 + +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 format_string_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); + static Optional parseReplacementItem(StringRef Spec); + +public: + format_string_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); +}; + +template +class format_string_object : public format_string_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: + format_string_object(StringRef Fmt, Tuple &&Params) + : format_string_object_base(Fmt, std::tuple_size::value), + Parameters(std::move(Params)) { + Wrappers = apply_tuple(create_wrappers(), Parameters); + } +}; + +// \brief format_string() function. +// +// ===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: +// +// format_string("{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 format_string(const char *Fmt, Ts &&... Vals) + -> format_string_object(Vals))...))> { + using ParamTuple = decltype( + std::make_tuple(detail::build_format_wrapper(std::forward(Vals))...)); + return format_string_object( + Fmt, + std::make_tuple(detail::build_format_wrapper(std::forward(Vals))...)); +} + +class literal_format_obj { + const char *Fmt; + +public: + explicit literal_format_obj(const char *Fmt) : Fmt(Fmt) {} + + template std::string string(Ts &&... Vals) const { + std::string S; + llvm::raw_string_ostream Stream(S); + Stream << format_string(Fmt, std::forward(Vals)...); + Stream.flush(); + return S; + } + + template + auto stream(Ts &&... Vals) const + -> decltype(format_string("", std::forward(Vals)...)) { + return format_string(Fmt, std::forward(Vals)...); + } +}; + +inline literal_format_obj operator"" _fmt(const char *Fmt, std::size_t Size) { + return literal_format_obj(Fmt); +} + +} // end namespace llvm + +#endif Index: include/llvm/Support/FormatVariadicDetails.h =================================================================== --- /dev/null +++ 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 Index: include/llvm/Support/NativeFormatting.h =================================================================== --- include/llvm/Support/NativeFormatting.h +++ include/llvm/Support/NativeFormatting.h @@ -25,12 +25,19 @@ 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); Index: include/llvm/Support/YAMLTraits.h =================================================================== --- include/llvm/Support/YAMLTraits.h +++ 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 @@ // 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; Index: include/llvm/Support/raw_ostream.h =================================================================== --- include/llvm/Support/raw_ostream.h +++ include/llvm/Support/raw_ostream.h @@ -20,6 +20,7 @@ #include namespace llvm { +class format_string_object_base; class format_object_base; class FormattedString; class FormattedNumber; @@ -222,6 +223,8 @@ // Formatted output, see the formatHex() function in Support/Format.h. raw_ostream &operator<<(const FormattedNumber &); + raw_ostream &operator<<(const format_string_object_base &); + /// indent - Insert 'NumSpaces' spaces. raw_ostream &indent(unsigned NumSpaces); Index: lib/Support/CMakeLists.txt =================================================================== --- lib/Support/CMakeLists.txt +++ lib/Support/CMakeLists.txt @@ -56,6 +56,7 @@ FileOutputBuffer.cpp FoldingSet.cpp FormattedStream.cpp + FormatVariadic.cpp GraphWriter.cpp Hashing.cpp IntEqClasses.cpp Index: lib/Support/FormatVariadic.cpp =================================================================== --- /dev/null +++ lib/Support/FormatVariadic.cpp @@ -0,0 +1,157 @@ +//===- 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 format_string_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 +format_string_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 +format_string_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 +format_string_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; +} Index: lib/Support/NativeFormatting.cpp =================================================================== --- lib/Support/NativeFormatting.cpp +++ lib/Support/NativeFormatting.cpp @@ -47,8 +47,8 @@ } 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 @@ 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 @@ } 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, unsigned int N, size_t MinDigits, + IntegerStyle Style) { + write_unsigned(S, N, MinDigits, Style); } -void llvm::write_integer(raw_ostream &S, int 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, IntegerStyle Style) { - write_unsigned(S, N, 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, IntegerStyle Style) { - write_signed(S, N, 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, +void llvm::write_integer(raw_ostream &S, unsigned long long 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, 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,8 +191,9 @@ #if defined(__MINGW32__) // FIXME: It should be generic to C++11. if (N == 0.0 && std::signbit(N)) { - const char *NegativeZero = "-0.000000e+00"; - writePadding(S, Width, strlen(NegativeZero)); + char NegativeZero[] = "-0.000000e+00"; + if (Style == FloatStyle::ExponentUpper) + NegativeZero[strlen(NegativeZero) - 4] = 'E'; S << NegativeZero; return; } @@ -188,7 +202,9 @@ // 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; } @@ -232,6 +248,10 @@ S << '%'; } +bool llvm::isPrefixedHexStyle(HexPrintStyle S) { + return (S == HexPrintStyle::PrefixLower || S == HexPrintStyle::PrefixUpper); +} + size_t llvm::getDefaultPrecision(FloatStyle Style) { switch (Style) { case FloatStyle::Exponent: Index: lib/Support/raw_ostream.cpp =================================================================== --- lib/Support/raw_ostream.cpp +++ 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 @@ } 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_string_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 @@ } 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; Index: unittests/Support/CMakeLists.txt =================================================================== --- unittests/Support/CMakeLists.txt +++ unittests/Support/CMakeLists.txt @@ -20,6 +20,7 @@ ErrorTest.cpp ErrorOrTest.cpp FileOutputBufferTest.cpp + FormatVariadicTest.cpp Host.cpp LEB128Test.cpp LineIteratorTest.cpp Index: unittests/Support/FormatVariadicTest.cpp =================================================================== --- /dev/null +++ unittests/Support/FormatVariadicTest.cpp @@ -0,0 +1,521 @@ +//===- 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 = format_string_object_base::parseFormatString(""); + EXPECT_EQ(0U, Replacements.size()); +} + +TEST(FormatVariadicTest, NoReplacements) { + const StringRef kFormatString = "This is a test"; + auto Replacements = + format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = + format_string_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 = format_string_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 = format_string_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 = format_string_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 = format_string_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 = + format_string_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("", ""_fmt.string()); + EXPECT_EQ("Test", "Test"_fmt.string()); +} + +TEST(FormatVariadicTest, FormatBasicTypesOneReplacement) { + EXPECT_EQ("1", "{0}"_fmt.string(1)); + EXPECT_EQ("c", "{0}"_fmt.string('c')); + EXPECT_EQ("-3", "{0}"_fmt.string(-3)); + EXPECT_EQ("Test", "{0}"_fmt.string("Test")); + EXPECT_EQ("Test2", "{0}"_fmt.string(StringRef("Test2"))); + EXPECT_EQ("Test3", "{0}"_fmt.string(std::string("Test3"))); +} + +TEST(FormatVariadicTest, IntegralHexFormatting) { + // 1. Trivial cases. Make sure hex is not the default. + EXPECT_EQ("0", "{0}"_fmt.string(0)); + EXPECT_EQ("2748", "{0}"_fmt.string(0xABC)); + EXPECT_EQ("-2748", "{0}"_fmt.string(-0xABC)); + + // 3. various hex prefixes. + EXPECT_EQ("0xFF", "{0:X}"_fmt.string(255)); + EXPECT_EQ("0xFF", "{0:X+}"_fmt.string(255)); + EXPECT_EQ("0xff", "{0:x}"_fmt.string(255)); + EXPECT_EQ("0xff", "{0:x+}"_fmt.string(255)); + EXPECT_EQ("FF", "{0:X-}"_fmt.string(255)); + EXPECT_EQ("ff", "{0:x-}"_fmt.string(255)); + + // 5. Precision pads left of the most significant digit but right of the + // prefix (if one exists). + EXPECT_EQ("0xFF", "{0:X2}"_fmt.string(255)); + EXPECT_EQ("0xFF", "{0:X+2}"_fmt.string(255)); + EXPECT_EQ("0x0ff", "{0:x3}"_fmt.string(255)); + EXPECT_EQ("0x0ff", "{0:x+3}"_fmt.string(255)); + EXPECT_EQ("00FF", "{0:X-4}"_fmt.string(255)); + EXPECT_EQ("00ff", "{0:x-4}"_fmt.string(255)); + + // 6. Try some larger types. + EXPECT_EQ("0xDEADBEEFDEADBEEF", "{0:X16}"_fmt.string(-2401053088876216593LL)); + EXPECT_EQ("0xFEEBDAEDFEEBDAED", "{0:X16}"_fmt.string(0xFEEBDAEDFEEBDAEDULL)); + EXPECT_EQ("0x00000000DEADBEEF", "{0:X16}"_fmt.string(0xDEADBEEF)); + + // 7. Padding should take into account the prefix + EXPECT_EQ("0xff", "{0,4:x}"_fmt.string(255)); + EXPECT_EQ(" 0xff", "{0,5:x+}"_fmt.string(255)); + EXPECT_EQ(" FF", "{0,4:X-}"_fmt.string(255)); + EXPECT_EQ(" ff", "{0,5:x-}"_fmt.string(255)); + + // 8. Including when it's been zero-padded + EXPECT_EQ(" 0x0ff", "{0,7:x3}"_fmt.string(255)); + EXPECT_EQ(" 0x00ff", "{0,7:x+4}"_fmt.string(255)); + EXPECT_EQ(" 000FF", "{0,7:X-5}"_fmt.string(255)); + EXPECT_EQ(" 0000ff", "{0,7:x-6}"_fmt.string(255)); + + // 9. Precision with default format specifier should work too + EXPECT_EQ(" 255", "{0,7:3}"_fmt.string(255)); + EXPECT_EQ(" 0255", "{0,7:4}"_fmt.string(255)); + EXPECT_EQ(" 00255", "{0,7:5}"_fmt.string(255)); + EXPECT_EQ(" 000255", "{0,7:6}"_fmt.string(255)); +} + +TEST(FormatVariadicTest, PointerFormatting) { + // 1. Trivial cases. Hex is default. Default Precision is pointer width. + if (sizeof(void *) == 4) { + EXPECT_EQ("0x00000000", "{0}"_fmt.string((void *)0)); + EXPECT_EQ("0x00000ABC", "{0}"_fmt.string((void *)0xABC)); + } else { + EXPECT_EQ("0x0000000000000000", "{0}"_fmt.string((void *)0)); + EXPECT_EQ("0x0000000000000ABC", "{0}"_fmt.string((void *)0xABC)); + } + + // 2. But we can reduce the precision explicitly. + EXPECT_EQ("0x0", "{0:0}"_fmt.string((void *)0)); + EXPECT_EQ("0xABC", "{0:0}"_fmt.string((void *)0xABC)); + EXPECT_EQ("0x0000", "{0:4}"_fmt.string((void *)0)); + EXPECT_EQ("0x0ABC", "{0:4}"_fmt.string((void *)0xABC)); + + // 3. various hex prefixes. + EXPECT_EQ("0x0ABC", "{0:X4}"_fmt.string((void *)0xABC)); + EXPECT_EQ("0x0abc", "{0:x4}"_fmt.string((void *)0xABC)); + EXPECT_EQ("0ABC", "{0:X-4}"_fmt.string((void *)0xABC)); + EXPECT_EQ("0abc", "{0:x-4}"_fmt.string((void *)0xABC)); +} + +TEST(FormatVariadicTest, IntegralNumberFormatting) { + // 1. Test comma grouping with default widths and precisions. + EXPECT_EQ("0", "{0:N}"_fmt.string(0)); + EXPECT_EQ("10", "{0:N}"_fmt.string(10)); + EXPECT_EQ("100", "{0:N}"_fmt.string(100)); + EXPECT_EQ("1,000", "{0:N}"_fmt.string(1000)); + EXPECT_EQ("1,234,567,890", "{0:N}"_fmt.string(1234567890)); + EXPECT_EQ("-10", "{0:N}"_fmt.string(-10)); + EXPECT_EQ("-100", "{0:N}"_fmt.string(-100)); + EXPECT_EQ("-1,000", "{0:N}"_fmt.string(-1000)); + EXPECT_EQ("-1,234,567,890", "{0:N}"_fmt.string(-1234567890)); + + // 2. If there is no comma, width and precision pad to the same absolute + // size. + EXPECT_EQ(" 1", "{0,2:N}"_fmt.string(1)); + + // 3. But if there is a comma or negative sign, width factors them in but + // precision doesn't. + EXPECT_EQ(" 1,000", "{0,6:N}"_fmt.string(1000)); + EXPECT_EQ(" -1,000", "{0,7:N}"_fmt.string(-1000)); + + // 4. Large widths all line up. + EXPECT_EQ(" 1,000", "{0,11:N}"_fmt.string(1000)); + EXPECT_EQ(" -1,000", "{0,11:N}"_fmt.string(-1000)); + EXPECT_EQ(" -100,000", "{0,11:N}"_fmt.string(-100000)); +} + +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, "{0}"_fmt.string(FooArray)); + EXPECT_EQ(FooPtr, "{0}"_fmt.string(FooPtr)); + EXPECT_EQ(FooRef, "{0}"_fmt.string(FooRef)); + EXPECT_EQ(FooString, "{0}"_fmt.string(FooString)); + + // 2. Test that the precision specifier prints the correct number of + // characters. + EXPECT_EQ("FooA", "{0:4}"_fmt.string(FooArray)); + EXPECT_EQ("FooP", "{0:4}"_fmt.string(FooPtr)); + EXPECT_EQ("FooR", "{0:4}"_fmt.string(FooRef)); + EXPECT_EQ("FooS", "{0:4}"_fmt.string(FooString)); + + // 3. And that padding works. + EXPECT_EQ(" FooA", "{0,6:4}"_fmt.string(FooArray)); + EXPECT_EQ(" FooP", "{0,6:4}"_fmt.string(FooPtr)); + EXPECT_EQ(" FooR", "{0,6:4}"_fmt.string(FooRef)); + EXPECT_EQ(" FooS", "{0,6:4}"_fmt.string(FooString)); +} + +TEST(FormatVariadicTest, CharFormatting) { + // 1. Not much to see here. Just print a char with and without padding. + EXPECT_EQ("C", "{0}"_fmt.string('C')); + EXPECT_EQ(" C", "{0,3}"_fmt.string('C')); + + // 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", "{0:D}"_fmt.string((char)37)); + EXPECT_EQ(" 037", "{0,5:D3}"_fmt.string((char)37)); +} + +TEST(FormatVariadicTest, BoolTest) { + // 1. Default style is lowercase text (same as 't') + EXPECT_EQ("true", "{0}"_fmt.string(true)); + EXPECT_EQ("false", "{0}"_fmt.string(false)); + EXPECT_EQ("true", "{0:t}"_fmt.string(true)); + EXPECT_EQ("false", "{0:t}"_fmt.string(false)); + + // 2. T - uppercase text + EXPECT_EQ("TRUE", "{0:T}"_fmt.string(true)); + EXPECT_EQ("FALSE", "{0:T}"_fmt.string(false)); + + // 3. D / d - integral + EXPECT_EQ("1", "{0:D}"_fmt.string(true)); + EXPECT_EQ("0", "{0:D}"_fmt.string(false)); + EXPECT_EQ("1", "{0:d}"_fmt.string(true)); + EXPECT_EQ("0", "{0:d}"_fmt.string(false)); + + // 4. Y - uppercase yes/no + EXPECT_EQ("YES", "{0:Y}"_fmt.string(true)); + EXPECT_EQ("NO", "{0:Y}"_fmt.string(false)); + + // 5. y - lowercase yes/no + EXPECT_EQ("yes", "{0:y}"_fmt.string(true)); + EXPECT_EQ("no", "{0:y}"_fmt.string(false)); +} + +TEST(FormatVariadicTest, DoubleFormatting) { + // Test exponents, fixed point, and percent formatting. + + // 1. Signed, unsigned, and zero exponent format. + EXPECT_EQ("0.000000E+00", "{0:E}"_fmt.string(0.0)); + EXPECT_EQ("-0.000000E+00", "{0:E}"_fmt.string(-0.0)); + EXPECT_EQ("1.100000E+00", "{0:E}"_fmt.string(1.1)); + EXPECT_EQ("-1.100000E+00", "{0:E}"_fmt.string(-1.1)); + EXPECT_EQ("1.234568E+03", "{0:E}"_fmt.string(1234.5678)); + EXPECT_EQ("-1.234568E+03", "{0:E}"_fmt.string(-1234.5678)); + EXPECT_EQ("1.234568E-03", "{0:E}"_fmt.string(.0012345678)); + EXPECT_EQ("-1.234568E-03", "{0:E}"_fmt.string(-.0012345678)); + + // 2. With padding and precision. + EXPECT_EQ(" 0.000E+00", "{0,11:E3}"_fmt.string(0.0)); + EXPECT_EQ(" -1.100E+00", "{0,11:E3}"_fmt.string(-1.1)); + EXPECT_EQ(" 1.235E+03", "{0,11:E3}"_fmt.string(1234.5678)); + EXPECT_EQ(" -1.235E-03", "{0,11:E3}"_fmt.string(-.0012345678)); + + // 3. Signed, unsigned, and zero fixed point format. + EXPECT_EQ("0.00", "{0:F}"_fmt.string(0.0)); + EXPECT_EQ("-0.00", "{0:F}"_fmt.string(-0.0)); + EXPECT_EQ("1.10", "{0:F}"_fmt.string(1.1)); + EXPECT_EQ("-1.10", "{0:F}"_fmt.string(-1.1)); + EXPECT_EQ("1234.57", "{0:F}"_fmt.string(1234.5678)); + EXPECT_EQ("-1234.57", "{0:F}"_fmt.string(-1234.5678)); + EXPECT_EQ("0.00", "{0:F}"_fmt.string(.0012345678)); + EXPECT_EQ("-0.00", "{0:F}"_fmt.string(-.0012345678)); + + // 2. With padding and precision. + EXPECT_EQ(" 0.000", "{0,8:F3}"_fmt.string(0.0)); + EXPECT_EQ(" -1.100", "{0,8:F3}"_fmt.string(-1.1)); + EXPECT_EQ("1234.568", "{0,8:F3}"_fmt.string(1234.5678)); + EXPECT_EQ(" -0.001", "{0,8:F3}"_fmt.string(-.0012345678)); +} + +struct format_tuple { + const char *Fmt; + explicit format_tuple(const char *Fmt) : Fmt(Fmt) {} + + template + auto operator()(Ts &&... Values) const + -> decltype(format_string(Fmt, std::forward(Values)...)) { + return format_string(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 << format_string(Intro, std::tuple_size::value, + llvm::array_lengthof(Ts)) + << "\n"; + Stream << format_string(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", + "{0}"_fmt.string(make_range(IntRange.begin(), IntRange.end()))); + EXPECT_EQ("1, 2, 3, 5, 8", "{0}"_fmt.string(make_range(IntRange.begin() + 1, + IntRange.end() - 1))); + + // 2. Non-default separator + EXPECT_EQ("1/1/2/3/5/8/13", "{0:$[/]}"_fmt.string(make_range( + IntRange.begin(), IntRange.end()))); + + // 3. Default separator, non-default element style. + EXPECT_EQ( + "0x1, 0x1, 0x2, 0x3, 0x5, 0x8, 0xd", + "{0:@[x]}"_fmt.string(make_range(IntRange.begin(), IntRange.end()))); + + // 4. Non-default separator and element style. + EXPECT_EQ("0x1 + 0x1 + 0x2 + 0x3 + 0x5 + 0x8 + 0xd", + "{0:$[ + ]@[x]}"_fmt.string( + make_range(IntRange.begin(), IntRange.end()))); + + // 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>", + "<{0:$[><]@(x)}>"_fmt.string( + make_range(IntRange.begin(), IntRange.end()))); + EXPECT_EQ("[0x1][0x1][0x2][0x3][0x5][0x8][0xd]", + "[{0:$(][)@[x]}]"_fmt.string( + make_range(IntRange.begin(), IntRange.end()))); + EXPECT_EQ("(0x1)(0x1)(0x2)(0x3)(0x5)(0x8)(0xd)", + "({0:$<)(>@})"_fmt.string( + make_range(IntRange.begin(), IntRange.end()))); + + // 5. Empty range. + EXPECT_EQ("", "{0:$[+]@[x]}"_fmt.string( + make_range(IntRange.begin(), IntRange.begin()))); + + // 6. Empty separator and style. + EXPECT_EQ("11235813", "{0:$[]@<>}"_fmt.string( + make_range(IntRange.begin(), IntRange.end()))); +} + +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", "{0}"_fmt.string(Negative(7))); + + int N = 171; + + EXPECT_EQ(" 171 ", "{0}"_fmt.string(fmt_align(N, AlignStyle::Center, 7))); + EXPECT_EQ(" 171 ", "{0}"_fmt.string(fmt_pad(N, 1, 3))); + EXPECT_EQ("171171171171171", "{0}"_fmt.string(fmt_repeat(N, 5))); + + EXPECT_EQ(" ABABABABAB ", + "{0:X-}"_fmt.string(fmt_pad(fmt_repeat(N, 5), 1, 3))); + EXPECT_EQ(" AB AB AB AB AB ", + "{0,=34:X-}"_fmt.string(fmt_repeat(fmt_pad(N, 1, 3), 5))); +} \ No newline at end of file Index: unittests/Support/NativeFormatTests.cpp =================================================================== --- unittests/Support/NativeFormatTests.cpp +++ unittests/Support/NativeFormatTests.cpp @@ -21,7 +21,7 @@ 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; }