Skip to content

Commit

Permalink
[Support] Introduce llvm::formatv() function.
Browse files Browse the repository at this point in the history
This introduces a new type-safe general purpose formatting
library.  It provides compile-time type safety, does not require
a format specifier (since the type is deduced), and provides
mechanisms for extending the format capability to user defined
types, and overriding the formatting behavior for existing types.

This patch additionally adds documentation for the API to the
LLVM programmer's manual.

Mailing List Thread:
http://lists.llvm.org/pipermail/llvm-dev/2016-October/105836.html

Differential Revision: https://reviews.llvm.org/D25587

llvm-svn: 286682
  • Loading branch information
Zachary Turner committed Nov 11, 2016
1 parent da6b572 commit 11db264
Show file tree
Hide file tree
Showing 17 changed files with 1,885 additions and 39 deletions.
130 changes: 130 additions & 0 deletions llvm/docs/ProgrammersManual.rst
Expand Up @@ -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<T>`` for your
type ``T`` with the appropriate static format method.

.. code-block:: c++

namespace llvm {
template<>
struct format_provider<MyFooBar> {
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<T>`` 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<int> 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
Expand Down
17 changes: 17 additions & 0 deletions llvm/include/llvm/ADT/STLExtras.h
Expand Up @@ -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 <typename T, T> struct SameType;

namespace detail {

template <typename RangeT>
Expand Down Expand Up @@ -477,6 +482,18 @@ struct index_sequence_for : build_index_impl<sizeof...(Ts)> {};
template <int N> struct rank : rank<N - 1> {};
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 <typename T, typename... Ts> struct is_one_of {
static const bool value = false;
};

template <typename T, typename U, typename... Ts>
struct is_one_of<T, U, Ts...> {
static const bool value =
std::is_same<T, U>::value || is_one_of<T, Ts...>::value;
};

//===----------------------------------------------------------------------===//
// Extra additions for arrays
//===----------------------------------------------------------------------===//
Expand Down
92 changes: 92 additions & 0 deletions 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 <typename T> class AdapterBase {
protected:
explicit AdapterBase(T &&Item) : Item(Item) {}

T Item;
static_assert(!detail::uses_missing_provider<T>::value,
"Item does not have a format provider!");
};

namespace detail {
template <typename T> class AlignAdapter : public AdapterBase<T> {
AlignStyle Where;
size_t Amount;

public:
AlignAdapter(T &&Item, AlignStyle Where, size_t Amount)
: AdapterBase(std::forward<T>(Item)), Where(Where), Amount(Amount) {}

void format(llvm::raw_ostream &Stream, StringRef Style) {
auto Wrapper = detail::build_format_wrapper(std::forward<T>(Item));
FmtAlign(Wrapper, Where, Amount).format(Stream, Style);
}
};

template <typename T> class PadAdapter : public AdapterBase<T> {
size_t Left;
size_t Right;

public:
PadAdapter(T &&Item, size_t Left, size_t Right)
: AdapterBase(std::forward<T>(Item)), Left(Left), Right(Right) {}

void format(llvm::raw_ostream &Stream, StringRef Style) {
auto Wrapper = detail::build_format_wrapper(std::forward<T>(Item));
Stream.indent(Left);
Wrapper.format(Stream, Style);
Stream.indent(Right);
}
};

template <typename T> class RepeatAdapter : public AdapterBase<T> {
size_t Count;

public:
RepeatAdapter(T &&Item, size_t Count)
: AdapterBase(std::forward<T>(Item)), Count(Count) {}

void format(llvm::raw_ostream &Stream, StringRef Style) {
auto Wrapper = detail::build_format_wrapper(std::forward<T>(Item));
for (size_t I = 0; I < Count; ++I) {
Wrapper.format(Stream, Style);
}
}
};
}

template <typename T>
detail::AlignAdapter<T> fmt_align(T &&Item, AlignStyle Where, size_t Amount) {
return detail::AlignAdapter<T>(std::forward<T>(Item), Where, Amount);
}

template <typename T>
detail::PadAdapter<T> fmt_pad(T &&Item, size_t Left, size_t Right) {
return detail::PadAdapter<T>(std::forward<T>(Item), Left, Right);
}

template <typename T>
detail::RepeatAdapter<T> fmt_repeat(T &&Item, size_t Count) {
return detail::RepeatAdapter<T>(std::forward<T>(Item), Count);
}
}

#endif
69 changes: 69 additions & 0 deletions 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

0 comments on commit 11db264

Please sign in to comment.