Skip to content

Commit

Permalink
Improve flexibility of encoding strings
Browse files Browse the repository at this point in the history
This will also help when we move to supporting `std::byte`.
  • Loading branch information
jimporter committed Aug 31, 2024
1 parent b82c9c4 commit a80f7e6
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Require C++20
- To decode only the next bencode object in a string or stream, you must now
call `bencode::decode_some`
- To encode into an iterator or stream, you must now call `bencode::encode_to`

### Bug fixes
- `bencode::decode` and friends now throw an exception if there's any data
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,17 +182,17 @@ Encoding data is also straightforward:
auto str = bencode::encode(42);
// Encode and output to an std::ostream.
bencode::encode(std::cout, 42);
bencode::encode_to(std::cout, 42);
// Encode and output to an iterator.
std::vector<char> vec;
bencode::encode(std::back_inserter(vec), 42);
bencode::encode_to(std::back_inserter(vec), 42);
```

You can also construct more-complex data structures:

```c++
bencode::encode(std::cout, bencode::dict{
bencode::encode_to(std::cout, bencode::dict{
{"one", 1},
{"two", bencode::list{1, "foo", 2}},
{"three", "3"}
Expand Down
58 changes: 38 additions & 20 deletions include/bencode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,16 @@ namespace bencode {
};

template<typename T>
concept sequence = iterable<T> && !std::convertible_to<T, std::string_view>;
concept stringish = iterable<T> && requires(T &t) {
std::size(t);
requires std::same_as<std::iter_value_t<decltype(std::begin(t))>, char>;
};

template<typename T>
concept mapping = sequence<T> && requires {
concept mapping = iterable<T> && requires {
typename T::key_type;
typename T::mapped_type;
requires stringish<typename T::key_type>;
};

template<std::integral Integer>
Expand Down Expand Up @@ -736,28 +740,42 @@ namespace bencode {
}

template<detail::output_iterator_ref Iter>
inline void encode(Iter &&iter, integer value) {
inline void encode_to(Iter &&iter, integer value) {
*iter++ = u8'i';
detail::write_integer(iter, value);
*iter++ = u8'e';
}

template<detail::output_iterator_ref Iter, detail::stringish Str>
requires(!std::is_array_v<Str>)
inline void encode_to(Iter &&iter, const Str &value) {
detail::write_integer(iter, std::size(value));
*iter++ = u8':';
std::copy(std::begin(value), std::end(value), iter);
}

template<detail::output_iterator_ref Iter>
inline void encode(Iter &&iter, const string_view &value) {
detail::write_integer(iter, value.size());
inline void encode_to(Iter &&iter, const char *value, std::size_t length) {
detail::write_integer(iter, length);
*iter++ = u8':';
std::copy(value.begin(), value.end(), iter);
std::copy(value, value + length, iter);
}

template<detail::output_iterator_ref Iter, std::size_t N>
inline void encode_to(Iter &&iter, const char (&value)[N]) {
// Don't write the null terminator.
encode_to(std::forward<Iter>(iter), value, N - 1);
}

template<detail::output_iterator_ref Iter, detail::sequence Seq>
void encode(Iter &&iter, const Seq &value) {
template<detail::output_iterator_ref Iter, detail::iterable Seq>
void encode_to(Iter &&iter, const Seq &value) {
detail::list_encoder e(iter);
for(auto &&i : value)
e.add(i);
}

template<detail::output_iterator_ref Iter, detail::mapping Map>
void encode(Iter &&iter, const Map &value) {
void encode_to(Iter &&iter, const Map &value) {
detail::dict_encoder e(iter);
for(auto &&i : value)
e.add(i.first, i.second);
Expand All @@ -771,7 +789,7 @@ namespace bencode {

template<typename T>
void operator ()(T &&operand) const {
encode(iter, std::forward<T>(operand));
encode_to(iter, std::forward<T>(operand));
}
private:
Iter &iter;
Expand All @@ -781,38 +799,38 @@ namespace bencode {
template<detail::output_iterator_ref Iter,
template<typename ...> typename Variant, typename I, typename S,
template<typename ...> typename L, template<typename ...> typename D>
void encode(Iter &&iter, const basic_data<Variant, I, S, L, D> &value) {
void encode_to(Iter &&iter, const basic_data<Variant, I, S, L, D> &value) {
variant_traits<Variant>::visit(detail::encode_visitor(iter), value);
}

namespace detail {
template<detail::output_iterator_ref Iter>
template<typename T>
inline list_encoder<Iter> & list_encoder<Iter>::add(T &&value) {
encode(iter, std::forward<T>(value));
encode_to(iter, std::forward<T>(value));
return *this;
}

template<detail::output_iterator_ref Iter>
template<typename T>
inline dict_encoder<Iter> &
dict_encoder<Iter>::add(const string_view &key, T &&value) {
encode(iter, key);
encode(iter, std::forward<T>(value));
encode_to(iter, key);
encode_to(iter, std::forward<T>(value));
return *this;
}
}

template<typename T>
std::string encode(T &&t) {
template<typename ...T>
std::string encode(T &&...t) {
std::stringstream ss;
encode(std::ostreambuf_iterator(ss), std::forward<T>(t));
encode_to(std::ostreambuf_iterator(ss), std::forward<T>(t)...);
return ss.str();
}

template<typename T>
void encode(std::ostream &os, T &&t) {
encode(std::ostreambuf_iterator(os), std::forward<T>(t));
template<typename ...T>
void encode_to(std::ostream &os, T &&...t) {
encode_to(std::ostreambuf_iterator(os), std::forward<T>(t)...);
}

}
Expand Down
19 changes: 10 additions & 9 deletions test/test_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ suite<> test_encode("test encoder", [](auto &_) {

_.test("string", []() {
expect(bencode::encode("foo"), equal_to("3:foo"));
expect(bencode::encode((char*)"foo", 3), equal_to("3:foo"));
expect(bencode::encode(std::string("foo")), equal_to("3:foo"));
expect(bencode::encode(bencode::string("foo")), equal_to("3:foo"));
});
Expand Down Expand Up @@ -131,22 +132,22 @@ suite<> test_encode("test encoder", [](auto &_) {

subsuite<std::stringstream>(_, "to std::ostream", [](auto &_) {
_.test("integer", [](std::stringstream &ss) {
bencode::encode(ss, 42);
bencode::encode_to(ss, 42);
expect(ss.str(), equal_to("i42e"));
});

_.test("string", [](std::stringstream &ss) {
bencode::encode(ss, "foo");
bencode::encode_to(ss, "foo");
expect(ss.str(), equal_to("3:foo"));
});

_.test("list", [](std::stringstream &ss) {
bencode::encode(ss, bencode::list{1, "foo", 2});
bencode::encode_to(ss, bencode::list{1, "foo", 2});
expect(ss.str(), equal_to("l" "i1e" "3:foo" "i2e" "e"));
});

_.test("dict", [](std::stringstream &ss) {
bencode::encode(ss, bencode::dict{
bencode::encode_to(ss, bencode::dict{
{"one", 1},
{"two", "foo"},
{"three", 2}
Expand All @@ -157,29 +158,29 @@ suite<> test_encode("test encoder", [](auto &_) {
});

_.test("data", [](std::stringstream &ss) {
bencode::encode(ss, bencode::data{bencode::list{1, "foo", 2}});
bencode::encode_to(ss, bencode::data{bencode::list{1, "foo", 2}});
expect(ss.str(), equal_to("l" "i1e" "3:foo" "i2e" "e"));
});
});

subsuite<std::vector<char>>(_, "to std::vector", [](auto &_) {
_.test("integer", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), 42);
bencode::encode_to(std::back_inserter(v), 42);
expect(v, array('i', '4', '2', 'e'));
});

_.test("string", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), "foo");
bencode::encode_to(std::back_inserter(v), "foo");
expect(v, array('3', ':', 'f', 'o', 'o'));
});

_.test("list", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), bencode::list{1, 2});
bencode::encode_to(std::back_inserter(v), bencode::list{1, 2});
expect(v, array('l', 'i', '1', 'e', 'i', '2', 'e', 'e'));
});

_.test("dict", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), bencode::dict{
bencode::encode_to(std::back_inserter(v), bencode::dict{
{"one", 1},
{"two", 2},
});
Expand Down

0 comments on commit a80f7e6

Please sign in to comment.