Last active
July 4, 2020 18:08
-
-
Save dodheim/7e7bf7b43d7dfcc48adad39db932660b to your computer and use it in GitHub Desktop.
C++17 solution for /r/dailyprogrammer challenge #317 [intermediate]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdint> | |
#include <type_traits> | |
#include <utility> | |
#include <optional> | |
#include <string_view> | |
#include <array> | |
#include <vector> | |
#include <boost/container/flat_map.hpp> | |
#include <boost/fusion/include/adapt_struct.hpp> | |
#include <boost/spirit/home/x3/core.hpp> | |
#include <boost/spirit/home/x3/nonterminal/rule.hpp> | |
#include <boost/spirit/home/x3/operator.hpp> | |
#include <boost/spirit/home/x3/auxiliary/attr.hpp> | |
#include <boost/spirit/home/x3/auxiliary/eoi.hpp> | |
#include <boost/spirit/home/x3/char.hpp> | |
#include <boost/spirit/home/x3/numeric/uint.hpp> | |
#include <boost/spirit/home/x3/support/ast/variant.hpp> | |
namespace x3 = boost::spirit::x3; | |
struct alignas(4) element { | |
constexpr element(char const upper, char const lower) noexcept : chars_{{upper, lower}} { } | |
explicit constexpr operator std::string_view() const noexcept { | |
return {chars_.data()}; | |
} | |
// HACK: std::array's operator <=> is missing as of MSVC 19.26; #include <compare> once fixed | |
friend bool operator <(element const& lhs, element const& rhs) noexcept { | |
return lhs.chars_ < rhs.chars_; | |
} | |
//friend constexpr auto operator <=>(element const&, element const&) noexcept = default; | |
private: | |
std::array<char, 3> chars_; | |
}; | |
using compound = boost::container::flat_map<element, std::int_fast32_t>; | |
namespace ast { | |
struct component; | |
using formula = std::vector<component>; | |
struct allotrope { | |
char upper, lower; | |
std::int_fast32_t count; | |
constexpr operator element() const noexcept { | |
return {upper, lower}; | |
} | |
}; | |
struct compound { | |
formula components; | |
std::int_fast32_t multiplier; | |
}; | |
struct component : x3::variant<allotrope, compound> { | |
using base_type::base_type; | |
using base_type::operator =; | |
}; | |
struct component_visitor { | |
::compound& comp_; | |
using result_type = void; | |
result_type operator ()(allotrope const a) const noexcept { | |
comp_[a] += a.count; | |
} | |
result_type operator ()(compound const& comp) const noexcept { | |
::compound sub; | |
for (auto const& c : comp.components) { | |
c.apply_visitor(component_visitor{sub}); | |
} | |
for (auto const& [elem, count] : sub) { | |
comp_[elem] += count * comp.multiplier; | |
} | |
} | |
}; | |
} | |
BOOST_FUSION_ADAPT_STRUCT(ast::allotrope, upper, lower, count) | |
BOOST_FUSION_ADAPT_STRUCT(ast::compound, components, multiplier) | |
namespace parsers { | |
x3::rule<struct formula_rule, ast::formula> const formula; | |
x3::rule<struct allotrope_rule, ast::allotrope> const allotrope; | |
x3::rule<struct compound_rule, ast::compound> const compound; | |
const auto formula_def = +(compound | allotrope); | |
constexpr auto allotrope_def = x3::upper | |
>> (x3::lower | x3::attr(std::integral_constant<char, '\0'>{})) | |
>> (x3::uint16 | x3::attr(std::integral_constant<std::uint16_t, 1>{})); | |
const auto compound_def = '(' >> formula >> ')' >> x3::uint16; | |
BOOST_SPIRIT_DEFINE(formula, allotrope, compound) | |
} | |
constexpr auto parse_formula = [](std::string_view const input) -> std::optional<compound> { | |
std::optional<compound> ret; | |
if (ast::formula raw; x3::parse(input.cbegin(), input.cend(), parsers::formula >> x3::eoi, raw)) { | |
ast::component_visitor{ret.emplace()}(ast::compound{std::move(raw), 1}); | |
} | |
return ret; | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
// demo | |
#include <cstdlib> | |
#include <cstdio> | |
#include <exception> | |
#include <string> | |
#include <fmt/format.h> | |
using namespace std::string_view_literals; | |
constexpr auto pnp = [](std::string_view const input) -> void { | |
fmt::print("{}"sv, input); | |
if (auto const comp = parse_formula(input)) { | |
std::putchar('\n'); | |
for (auto const& [elem, count] : *comp) { | |
fmt::print("{:>6}: {}\n"sv, elem, count); | |
} | |
} else { | |
std::puts(" - failed to parse"); | |
} | |
std::putchar('\n'); | |
}; | |
constexpr auto stdin_getline = [](std::string& s) -> bool { | |
s.clear(); | |
if (!s.capacity()) [[unlikely]] { | |
s.reserve(16); | |
} | |
auto* const cstdin = stdin; | |
for (auto len = s.size();;) { | |
if (std::ferror(cstdin) || std::feof(cstdin)) [[unlikely]] { | |
return false; | |
} | |
s.resize(s.capacity()); | |
if (!std::fgets(s.data() + len, s.size() - len, cstdin)) [[unlikely]] { | |
s.erase(len); | |
return false; | |
} | |
len = s.find_last_not_of('\0'); | |
bool const line_finished = s[len] == '\n'; | |
if (!line_finished) { | |
++len; | |
} | |
s.erase(len); | |
if (line_finished) { | |
return true; | |
} | |
// relying on stdlib to apply growth factor to `reserve`; works as desired | |
// on vc++, libc++, libstdc++ but bad in theory and "should" be `resize` | |
s.reserve(s.capacity() + 1); | |
} | |
}; | |
int main() try { | |
#ifdef _DEBUG | |
static constexpr std::array inputs{ | |
"C6H12O6"sv, | |
"CCl2F2"sv, | |
"NaHCO3"sv, | |
"C4H8(OH)2"sv, | |
"PbCl(NH3)2(COOH)2"sv, | |
"PbCl(NH3(H2O)4)2"sv, | |
"Cl((NaH)2CO3)2"sv, | |
"PbCl(NH3)2((CO)2OH)2"sv, | |
"(C3(H2O)3)2"sv, | |
"FPb((NO4)2(COOH)3)4"sv | |
}; | |
for (auto const& input : inputs) { | |
#else | |
for (std::string input; stdin_getline(input) && !input.empty();) { | |
if (auto const trimmed_len = input.find_last_not_of(" \t\r\f\v"sv) + 1; trimmed_len != input.size()) { | |
if (!trimmed_len) { | |
continue; | |
} | |
input.erase(trimmed_len); | |
} | |
#endif | |
pnp(input); | |
} | |
return EXIT_SUCCESS; | |
} catch (std::exception const& ex) { | |
std::fputs(ex.what(), stderr); | |
std::fputc('\n', stderr); | |
return EXIT_FAILURE; | |
} catch (...) { | |
std::fputs("unknown exception (...)\n", stderr); | |
return EXIT_FAILURE; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Revision 2 exorcises iostreams from the code in favor of fmtlib