diff --git a/2020/18.cpp b/2020/18.cpp index d35564e..804e06a 100644 --- a/2020/18.cpp +++ b/2020/18.cpp @@ -23,30 +23,73 @@ auto Eval( std::vector vals; auto const eval1 = [&](){ - char o = ops.back(); ops.pop_back(); - std::int64_t y = vals.back(); vals.pop_back(); - std::int64_t x = vals.back(); vals.pop_back(); - vals.push_back(cfg[o].second(x,y)); + if (ops.size() < 1 || vals.size() < 2) { + throw std::runtime_error{"parse error"}; + } + if (auto opit = cfg.find(ops.back()); opit != cfg.end()) { + std::int64_t y = vals.back(); vals.pop_back(); + std::int64_t x = vals.back(); vals.pop_back(); + vals.push_back(opit->second.second(x,y)); + ops.pop_back(); + } else { + throw std::runtime_error{"parse error"}; + } }; - for (auto it = input.begin(); it != input.end(); it++) { - char const c = *it; + bool expect_operator = false; + + auto it = input.begin(); + while (it != input.end()) { + char const c = *it++; + + // skip whitespace if (c == ' ') continue; + + // number literal if (std::isdigit(c)) { - vals.push_back(c - '0'); - } else if ('(' == c) { + if (expect_operator) { throw std::runtime_error{"unexpected literal"}; } + expect_operator = true; + + std::int64_t acc = c - '0'; + while (it != input.end() && std::isdigit(*it)) { + acc = acc*10 + (*it++ - '0'); + } + vals.push_back(acc); + continue; + } + + // begin subexpression + if ('(' == c) { + if (expect_operator) { throw std::runtime_error{"unexpected '('"}; } ops.push_back('('); - } else if (')' == c) { - while (ops.back() != '(') eval1(); - ops.pop_back(); - } else if (auto opit = cfg.find(c); opit != cfg.end()) { + continue; + } + + // end subexpression + if (')' == c) { + if (!expect_operator) { throw std::runtime_error{"unexpected ')'"}; } + while (!ops.empty() && ops.back() != '(') eval1(); + if (ops.empty()) throw std::runtime_error{"parse error"}; + ops.pop_back(); // '(' + continue; + } + + // binary operator + if (auto opit = cfg.find(c); opit != cfg.end()) { + if (!expect_operator) { throw std::runtime_error{"unexpected operator"}; } + expect_operator = false; while (!ops.empty() && cfg[ops.back()].first >= opit->second.first) eval1(); ops.push_back(c); + continue; } + + throw std::runtime_error{"unknown character"}; } while (!ops.empty()) eval1(); + if (vals.empty()) { throw std::runtime_error{"empty input"}; } + return vals.back(); } @@ -63,21 +106,36 @@ auto Part2(std::string const& line) { TEST_SUITE("documented examples") { TEST_CASE("part 1") { - REQUIRE(Part1("1 + 2 * 3 + 4 * 5 + 6") == 71); - REQUIRE(Part1("1 + (2 * 3) + (4 * (5 + 6))") == 51); - REQUIRE(Part1("2 * 3 + (4 * 5)") == 26); - REQUIRE(Part1("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 437); - REQUIRE(Part1("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 12240); - REQUIRE(Part1("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 13632); + CHECK(Part1("1 + 2 * 3 + 4 * 5 + 6") == 71); + CHECK(Part1("1 + (2 * 3) + (4 * (5 + 6))") == 51); + CHECK(Part1("2 * 3 + (4 * 5)") == 26); + CHECK(Part1("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 437); + CHECK(Part1("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 12240); + CHECK(Part1("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 13632); } TEST_CASE("part 2") { - REQUIRE(Part2("1 + 2 * 3 + 4 * 5 + 6") == 231); - REQUIRE(Part2("1 + (2 * 3) + (4 * (5 + 6))") == 51); - REQUIRE(Part2("2 * 3 + (4 * 5)") == 46); - REQUIRE(Part2("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 1445); - REQUIRE(Part2("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 669060); - REQUIRE(Part2("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 23340); + CHECK(Part2("1 + 2 * 3 + 4 * 5 + 6") == 231); + CHECK(Part2("1 + (2 * 3) + (4 * (5 + 6))") == 51); + CHECK(Part2("2 * 3 + (4 * 5)") == 46); + CHECK(Part2("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 1445); + CHECK(Part2("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 669060); + CHECK(Part2("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 23340); +} + +TEST_CASE("errors") { + CHECK_THROWS_AS(Part1(""), std::runtime_error); + CHECK_THROWS_AS(Part1("1 + ()"), std::runtime_error); + CHECK_THROWS_AS(Part1("(1"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 (2)"), std::runtime_error); + CHECK_THROWS_AS(Part1(")"), std::runtime_error); + CHECK_THROWS_AS(Part1("1)"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 2"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 +"), std::runtime_error); + CHECK_THROWS_AS(Part1("+ 1"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 /"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 / 2"), std::runtime_error); + CHECK_THROWS_AS(Part1("1 + * 2"), std::runtime_error); } }