Skip to content
This repository has been archived by the owner on May 29, 2020. It is now read-only.

Commit

Permalink
Format spec cleanup (#12)
Browse files Browse the repository at this point in the history
* format_value takes a format_spec directly now

* doctest build fixes

* Bitfields in format_spec

* Cleanup format traits digit handling

Almost every use was just accessing 0 anyway

* Split format and printf spec parsing functions

* Begin making format_spec a reasonable public API type

* constexpr + noexcept on basic_format_spec constructor

* Pass format_spec by const& because it's not tiny

* Rename format_spec.remaining to user

* Rename format_spec to format_options

* Renames

* Finally get rid of options.has_precision

The default precision is now ~0u, which I dislike (I like default == 0) but this gets it done.

* Update README
  • Loading branch information
Sean Middleditch authored Mar 24, 2019
1 parent adae9d8 commit 20c79ca
Show file tree
Hide file tree
Showing 19 changed files with 382 additions and 256 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ set(FORMATXX_PRIVATE_HEADERS
include/formatxx/_detail/format_traits.h
include/formatxx/_detail/format_util.h
include/formatxx/_detail/parse_format.h
include/formatxx/_detail/parse_printf.h
include/formatxx/_detail/parse_unsigned.h
include/formatxx/_detail/printf_impl.h
include/formatxx/_detail/write_float.h
Expand Down
56 changes: 29 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,53 +26,55 @@ necessary. Users can easily write their own buffer systems as well.
The underlying method of operation of formatxx is to collect a list of arguments via variadic
templates, lookup a `format_value` function for each of those arguments, and then pass the format
string, an array of format functions, and an array of `void` pointers to arguments into the
the actual formatting function. The header mechanisms that generate these lists of functions
and pointers are intended to be as absolutel light-weight on the compiler as possible. The
actual formatting work is all implemented in a source file and not the header, to keep the
the actual formatting function. Builtin C++ types go through a slightly different mechanism for
the sake of avoiding excessing ADL noise. The header mechanisms that generate these lists of
functions and pointers are intended to be light-weight on the compiler to the extent reasonable.
The actual formatting work is all implemented in a source file and not the header, to keep the
header small and cheap to include.

## Usage

formatxx can write into user-defined buffers, or a user may use one of the provided buffer
types. Formatting is support for any type that has an appropriate `format_value` free function with
the signature `void format_value(formatxx::IWriter&, TheType, formatxx::format_spec)`. For instance:
the signature `void format_value(formatxx::IWriter&, TheType, formatxx::format_options const&)`.
For instance:

```c++
#include <formatxx/format.h>
#include <string>

```C++
struct Foo { int value };

void format_value(formatxx::writer& out, Foo const& foo, formatxx::string_view spec)
{
format(out, "Foo({})", foo.value);
void format_value(formatxx::writer& out, Foo const& foo, formatxx::format_options const& opts) {
format_to(out, "Foo({})", foo.value);
}

int main()
{
std::cout << formatxx::FormatString<>("testing {0}", Foo{123});
int main() {
std::cout << formatxx::format_as<std::string>("testing {0}", Foo{123});
}
```
The above will print `testing Foo(123)` to standard output.
The `spec` argument are additional options passed to the formatter. These can be
interpreted by the `format_value` function anyway it sees fit. The
`formatxx::parse_format_spec` function will return a `formatxx::format_spec` structure
with various printf-style flags and options parsed, which are used by default for built-in
format types like integers, floats, and strings.
The `options` argument are additional options passed to the formatter. These are parsed from
format string options. In the `format_to` case user-provided arguments may be provided,
otherwise they are interpreted by Python or printf rules.
The functions `formatxx::parse_format_spec` and `formatxx::parse_printf_spec` can be used
to interpret format specs from string inputs.
The `formatxx::format<StringT = std::string>(string_view, ...)` template can be used
for formatting a series of arguments into a `std::string` or any compatible string type.
The `formatxx::format_as<ResultT>(string_view, ...)` template can be used
for formatting a series of arguments into any result type that implements a `string`-like
`append` method. Including `formatxx/std_string.h` also provides a `format_string` function
that defaults to returning `std::string` results.
The `formatxx::format(formatxx::writer&, string_view, ...)` template can be used to
write into a write buffer.
The `formatxx::format_to(formatxx::writer&, string_view, ...)` template can be used to
write into a write buffer. This is the recommended way of formatting.
The provided write buffers are:
- `fmt::fixed_writer<N>` - a write buffer that will never allocate but only support
`N`-1 characters.
- `fmt::string_writer<StringT>` - a write buffer that writes into a `std::string`-
compatible type.
- `fmt::buffered_writer<N, AllocatorT = std::allocator<char>>` - a write buffer that
will not allocate for strings up to `N`-1 characters long but will allocate when
necessary if that length is exceeded.
- `formatxx::append_writer<StringT>` - writes to a `string`-like object using `append`.
- `fmt::container_writer<ContainerT>` - writes to a container using `insert` at the end.
- `fmt::span_writer<CharT>` - writes to a pre-allocated buffer.
All three of the provided write buffers guarantee NUL-terminated strings, but support
use with string types that are not NUL-terminated (another important use case for
Expand Down
3 changes: 2 additions & 1 deletion external/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
add_subdirectory(litexx EXCLUDE_FROM_ALL)

if(FORMATXX_BUILD_TESTS)
if(FORMATXX_BUILD_TESTS AND NOT TARGET doctest)
set(DOCTEST_WITH_TESTS OFF CACHE BOOL "enable doctest tests")
set(DOCTEST_WITH_MAIN_IN_STATIC_LIB OFF CACHE BOOL "enable doctest static library")
add_subdirectory(doctest EXCLUDE_FROM_ALL)
endif()
2 changes: 1 addition & 1 deletion external/litexx
14 changes: 7 additions & 7 deletions include/formatxx/_detail/format_arg.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ enum class formatxx::_detail::format_arg_type {
template <typename CharT>
class formatxx::_detail::basic_format_arg {
public:
using thunk_type = result_code(FORMATXX_API*)(basic_format_writer<CharT>&, void const*, basic_string_view<CharT>);
using thunk_type = result_code(FORMATXX_API*)(basic_format_writer<CharT>&, void const*, basic_format_options<CharT>);

constexpr basic_format_arg() noexcept = default;
constexpr basic_format_arg(_detail::format_arg_type type, void const* value) noexcept : _type(type), _value(value) {}
constexpr basic_format_arg(thunk_type thunk, void const* value) noexcept : _type(_detail::format_arg_type::custom), _thunk(thunk), _value(value) {}

FORMATXX_PUBLIC result_code FORMATXX_API format_into(basic_format_writer<CharT>& output, basic_string_view<CharT> spec) const;
FORMATXX_PUBLIC result_code FORMATXX_API format_into(basic_format_writer<CharT>& output, basic_format_options<CharT> const& options) const;

private:
_detail::format_arg_type _type = _detail::format_arg_type::unknown;
Expand All @@ -91,8 +91,8 @@ class formatxx::_detail::basic_format_arg_list {
constexpr basic_format_arg_list() noexcept = default;
constexpr basic_format_arg_list(std::initializer_list<format_arg_type> args) noexcept : _args(args.begin()), _count(args.size()) {}

constexpr result_code format_arg(basic_format_writer<CharT>& output, size_type index, basic_string_view<CharT> spec) const {
return index < _count ? _args[index].format_into(output, spec) : result_code::out_of_range;
constexpr result_code format_arg(basic_format_writer<CharT>& output, size_type index, basic_format_options<CharT> const& options) const {
return index < _count ? _args[index].format_into(output, options) : result_code::out_of_range;
}

private:
Expand All @@ -110,7 +110,7 @@ namespace formatxx::_detail {
template <typename C, typename T, typename V = void>
struct has_format_value { static constexpr bool value = false; };
template <typename C, typename T>
struct has_format_value<C, T, std::void_t<decltype(format_value(std::declval<basic_format_writer<C>&>(), std::declval<T>(), std::declval<basic_string_view<C>>()))>> {
struct has_format_value<C, T, std::void_t<decltype(format_value(std::declval<basic_format_writer<C>&>(), std::declval<T>(), std::declval<basic_format_options<C>>()))>> {
static constexpr bool value = true;
};

Expand Down Expand Up @@ -141,8 +141,8 @@ namespace formatxx::_detail {
#undef FORMTAXX_TYPE

template <typename CharT, typename T>
result_code FORMATXX_API format_value_thunk(basic_format_writer<CharT>& out, void const* ptr, basic_string_view<CharT> spec) {
format_value(out, *static_cast<T const*>(ptr), spec);
result_code FORMATXX_API format_value_thunk(basic_format_writer<CharT>& out, void const* ptr, basic_format_options<CharT> options) {
format_value(out, *static_cast<T const*>(ptr), options);
return result_code::success;
}

Expand Down
42 changes: 21 additions & 21 deletions include/formatxx/_detail/format_arg_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,67 +36,67 @@
#include <cinttypes>

template <typename CharT>
formatxx::result_code FORMATXX_API formatxx::_detail::basic_format_arg<CharT>::format_into(basic_format_writer<CharT>& output, basic_string_view<CharT> spec) const {
formatxx::result_code FORMATXX_API formatxx::_detail::basic_format_arg<CharT>::format_into(basic_format_writer<CharT>& output, basic_format_options<CharT> const& options) const {
switch (_type) {
case _detail::format_arg_type::char_t:
_detail::write_char(output, *static_cast<char const*>(_value), spec);
_detail::write_char(output, *static_cast<char const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::wchar:
_detail::write_char(output, *static_cast<wchar_t const*>(_value), spec);
_detail::write_char(output, *static_cast<wchar_t const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::signed_char:
_detail::write_integer(output, *static_cast<signed char const*>(_value), spec);
_detail::write_integer(output, *static_cast<signed char const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::unsigned_char:
_detail::write_integer(output, *static_cast<unsigned char const*>(_value), spec);
_detail::write_integer(output, *static_cast<unsigned char const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::signed_int:
_detail::write_integer(output, *static_cast<signed int const*>(_value), spec);
_detail::write_integer(output, *static_cast<signed int const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::unsigned_int:
_detail::write_integer(output, *static_cast<unsigned int const*>(_value), spec);
_detail::write_integer(output, *static_cast<unsigned int const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::signed_short_int:
_detail::write_integer(output, *static_cast<signed short const*>(_value), spec);
_detail::write_integer(output, *static_cast<signed short const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::unsigned_short_int:
_detail::write_integer(output, *static_cast<unsigned short const*>(_value), spec);
_detail::write_integer(output, *static_cast<unsigned short const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::signed_long_int:
_detail::write_integer(output, *static_cast<signed long const*>(_value), spec);
_detail::write_integer(output, *static_cast<signed long const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::unsigned_long_int:
_detail::write_integer(output, *static_cast<unsigned long const*>(_value), spec);
_detail::write_integer(output, *static_cast<unsigned long const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::signed_long_long_int:
_detail::write_integer(output, *static_cast<signed long long const*>(_value), spec);
_detail::write_integer(output, *static_cast<signed long long const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::unsigned_long_long_int:
_detail::write_integer(output, *static_cast<unsigned long long const*>(_value), spec);
_detail::write_integer(output, *static_cast<unsigned long long const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::single_float:
_detail::write_float(output, *static_cast<float const*>(_value), spec);
_detail::write_float(output, *static_cast<float const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::double_float:
_detail::write_float(output, *static_cast<double const*>(_value), spec);
_detail::write_float(output, *static_cast<double const*>(_value), options);
return result_code::success;
case _detail::format_arg_type::boolean:
_detail::write_string(output, *static_cast<bool const*>(_value) ? _detail::FormatTraits<CharT>::sTrue : _detail::FormatTraits<CharT>::sFalse, spec);
_detail::write_string(output, *static_cast<bool const*>(_value) ? _detail::FormatTraits<CharT>::sTrue : _detail::FormatTraits<CharT>::sFalse, options);
return result_code::success;
case _detail::format_arg_type::char_string:
_detail::write_string(output, string_view(*static_cast<char const* const*>(_value)), spec);
_detail::write_string(output, string_view(*static_cast<char const* const*>(_value)), options);
return result_code::success;
case _detail::format_arg_type::wchar_string:
_detail::write_string(output, wstring_view(*static_cast<wchar_t const* const*>(_value)), spec);
_detail::write_string(output, wstring_view(*static_cast<wchar_t const* const*>(_value)), options);
return result_code::success;
case _detail::format_arg_type::null_pointer:
_detail::write_string(output, _detail::FormatTraits<CharT>::sNullptr, spec);
_detail::write_string(output, _detail::FormatTraits<CharT>::sNullptr, options);
return result_code::success;
case _detail::format_arg_type::void_pointer:
_detail::write_integer(output, reinterpret_cast<std::uintptr_t>(*static_cast<void const* const*>(_value)), spec);
_detail::write_integer(output, reinterpret_cast<std::uintptr_t>(*static_cast<void const* const*>(_value)), options);
return result_code::success;
case _detail::format_arg_type::custom:
return _thunk(output, _value, spec);
return _thunk(output, _value, options);
default:
return result_code::success;
}
Expand Down
18 changes: 13 additions & 5 deletions include/formatxx/_detail/format_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#pragma once

#include "parse_unsigned.h"
#include "parse_format.h"

namespace formatxx::_detail {

Expand Down Expand Up @@ -86,7 +87,7 @@ namespace formatxx::_detail {
index = next_index;
}

basic_string_view<CharT> spec;
basic_format_options<CharT> options;

// if a : follows the number, we have some formatting controls
if (*iter == FormatTraits<CharT>::cFormatSep) {
Expand All @@ -98,23 +99,30 @@ namespace formatxx::_detail {
}

if (iter == end) {
// invalid spec
// invalid options
result = result_code::malformed_input;
break;
}

spec = basic_string_view<CharT>(spec_begin, iter);
basic_parse_spec_result<CharT> const spec_result = parse_format_spec<CharT>({ spec_begin, iter });
if (spec_result.code != result_code::success) {
result = spec_result.code;
break;
}

options = spec_result.options;
options.user = spec_result.unparsed;
}

// after the index/spec, we expect an end to the format marker
// after the index/options, we expect an end to the format marker
if (*iter != FormatTraits<CharT>::cFormatEnd) {
// we have something besides a number, no bueno
result = result_code::malformed_input;
begin = iter; // make sure we're set up for the next begin, which starts at this unknown character
continue;
}

result_code const arg_result = args.format_arg(out, index, spec);
result_code const arg_result = args.format_arg(out, index, options);
if (arg_result != result_code::success) {
result = arg_result;
}
Expand Down
30 changes: 16 additions & 14 deletions include/formatxx/_detail/format_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,19 @@ namespace formatxx::_detail {
static constexpr char cSpace = ' ';
static constexpr char cHash = '#';
static constexpr char cDot = '.';

static constexpr char to_digit(char c) { return c + '0'; }
static constexpr char cZero = '0';

static constexpr char cPrintfSpec = '%';
static constexpr char cPrintfIndex = '$';

static constexpr string_view sTrue{ "true", 4 };
static constexpr string_view sFalse{ "false", 5 };
static constexpr string_view sNullptr{ "nullptr", 7 };
static constexpr string_view sTrue{ "true" };
static constexpr string_view sFalse{ "false" };
static constexpr string_view sNullptr{ "nullptr" };

static constexpr string_view sFormatSpecifiers{ "bcsdioxXfFeEaAgG" };

static constexpr string_view sPrintfSpecifiers{ "bcCsSdioxXufFeEaAgGp", 20 };
static constexpr string_view sPrintfModifiers{ "hljztL", 6 };
static constexpr string_view sPrintfSpecifiers{ "bcCsSdioxXufFeEaAgGp" };
static constexpr string_view sPrintfModifiers{ "hljztL" };

static constexpr char const sDecimalPairs[] =
"00010203040506070809"
Expand All @@ -84,18 +85,19 @@ namespace formatxx::_detail {
static constexpr wchar_t cSpace = L' ';
static constexpr wchar_t cHash = L'#';
static constexpr wchar_t cDot = L'.';

static constexpr wchar_t to_digit(wchar_t c) { return c + L'0'; }
static constexpr wchar_t cZero = L'0';

static constexpr wchar_t cPrintfSpec = L'%';
static constexpr wchar_t cPrintfIndex = L'$';

static constexpr wstring_view sTrue{ L"true", 4 };
static constexpr wstring_view sFalse{ L"false", 5 };
static constexpr wstring_view sNullptr{ L"nullptr", 7 };
static constexpr wstring_view sTrue{ L"true" };
static constexpr wstring_view sFalse{ L"false" };
static constexpr wstring_view sNullptr{ L"nullptr" };

static constexpr wstring_view sFormatSpecifiers{ L"bcsdioxXfFeEaAgG" };

static constexpr wstring_view sPrintfSpecifiers{ L"bcCsSdioxXufFeEaAgGp", 20 };
static constexpr wstring_view sPrintfModifiers{ L"hljztL", 6 };
static constexpr wstring_view sPrintfSpecifiers{ L"bcCsSdioxXufFeEaAgGp" };
static constexpr wstring_view sPrintfModifiers{ L"hljztL" };

static constexpr wchar_t const sDecimalPairs[] =
L"00010203040506070809"
Expand Down
Loading

0 comments on commit 20c79ca

Please sign in to comment.