Skip to content

Commit

Permalink
More efficient way of parsing variants
Browse files Browse the repository at this point in the history
  • Loading branch information
liuzicheng1987 committed Apr 10, 2024
1 parent 648d628 commit b38f12e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 33 deletions.
64 changes: 39 additions & 25 deletions include/rfl/parsing/Parser_variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define RFL_PARSING_PARSER_VARIANT_HPP_

#include <map>
#include <optional>
#include <type_traits>
#include <variant>

Expand All @@ -11,43 +12,36 @@
#include "FieldVariantParser.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"
#include "to_single_error_message.hpp"

namespace rfl {
namespace parsing {

template <class R, class W, class... FieldTypes>
requires AreReaderAndWriter<R, W, std::variant<FieldTypes...>>
struct Parser<R, W, std::variant<FieldTypes...>> {
class Parser<R, W, std::variant<FieldTypes...>> {
public:
using InputVarType = typename R::InputVarType;
using OutputVarType = typename W::OutputVarType;

template <int _i = 0>
static Result<std::variant<FieldTypes...>> read(
const R& _r, const InputVarType& _var,
const std::string _errors = "") noexcept {
if constexpr (_i == 0 &&
internal::all_fields<std::tuple<FieldTypes...>>()) {
const R& _r, const InputVarType& _var) noexcept {
if constexpr (internal::all_fields<std::tuple<FieldTypes...>>()) {
return FieldVariantParser<R, W, FieldTypes...>::read(_r, _var);
} else if constexpr (_i == sizeof...(FieldTypes)) {
return Error("Could not parse variant: " + _errors);
} else {
const auto to_variant = [](auto&& _val) {
return std::variant<FieldTypes...>(std::move(_val));
};

const auto try_next = [&_r, _var, &_errors](const auto& _err) {
auto errors = _errors;
errors += std::string("\n" + std::to_string(_i + 1) + ") ") +
internal::strings::replace_all(_err.what(), "\n", "\n ");
return read<_i + 1>(_r, _var, errors);
};

using AltType = std::remove_cvref_t<
std::variant_alternative_t<_i, std::variant<FieldTypes...>>>;

return Parser<R, W, AltType>::read(_r, _var)
.transform(to_variant)
.or_else(try_next);
std::optional<std::variant<FieldTypes...>> result;
std::vector<Error> errors;
read_variant(_r, _var, &result, &errors);
if (result) {
return std::move(*result);
} else {
return Error(
to_single_error_message(errors,
"Could not parse the variant. Each of the "
"possible alternatives failed "
"for the following reasons: ",
100000));
}
}
}

Expand Down Expand Up @@ -84,6 +78,26 @@ struct Parser<R, W, std::variant<FieldTypes...>> {
}
}
}

private:
template <int _i = 0>
static void read_variant(const R& _r, const InputVarType& _var,
std::optional<std::variant<FieldTypes...>>* _result,
std::vector<Error>* _errors) noexcept {
constexpr size_t size = sizeof...(FieldTypes);
if constexpr (_i < size) {
using AltType = std::remove_cvref_t<
std::variant_alternative_t<_i, std::variant<FieldTypes...>>>;
auto res = Parser<R, W, AltType>::read(_r, _var);
if (res) {
*_result = std::move(*res);
return;
} else {
_errors->emplace_back(*res.error());
return read_variant<_i + 1>(_r, _var, _result, _errors);
}
}
}
};

} // namespace parsing
Expand Down
2 changes: 1 addition & 1 deletion include/rfl/parsing/VectorParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ struct VectorParser {
.value();
}

static Result<VecType> to_container(const R& _r, InputArrayType&& _arr) {
static VecType to_container(const R& _r, InputArrayType&& _arr) {
auto input_vars = _r.to_vec(_arr);
VecType vec;
if constexpr (is_forward_list<VecType>()) {
Expand Down
21 changes: 14 additions & 7 deletions include/rfl/parsing/to_single_error_message.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_PARSING_TOSINGLEERRORMESSAGE_HPP_
#define RFL_PARSING_TOSINGLEERRORMESSAGE_HPP_

#include <optional>
#include <string>
#include <vector>

Expand All @@ -9,20 +10,26 @@
namespace rfl::parsing {

/// Combines a set of errors to a single, readable error message.
inline Error to_single_error_message(std::vector<Error> _errors) {
inline Error to_single_error_message(
std::vector<Error> _errors,
std::optional<std::string> _msg_prefix = std::nullopt,
size_t _err_limit = 10) {
if (_errors.size() == 1) {
return std::move(_errors[0]);
} else {
std::string msg = "Found " + std::to_string(_errors.size()) + " errors:";
for (size_t i = 0; i < _errors.size() && i < 10; ++i) {
std::string msg =
_msg_prefix ? *_msg_prefix
: "Found " + std::to_string(_errors.size()) + " errors:";
for (size_t i = 0; i < _errors.size() && i < _err_limit; ++i) {
msg +=
"\n" + std::to_string(i + 1) + ") " +
internal::strings::replace_all(_errors.at(i).what(), "\n", "\n ");
}
if (_errors.size() > 10) {
msg +=
"\n...\nMore than 10 errors occurred, but I am only showing the "
"first 10.";
if (_errors.size() > _err_limit) {
msg += "\n...\nMore than " + std::to_string(_err_limit) +
" errors occurred, but I am only showing the "
"first " +
std::to_string(_err_limit) + ".";
}
return Error(msg);
}
Expand Down

0 comments on commit b38f12e

Please sign in to comment.