#include #include #include #include #include #include #include // Algorithm overview: // // Because I'm lazy we basically build up a list of 'instructions' and // repeatedly walking through them executing the ones we can. We assume that // each pass will achieve more than the previous pass as more signal values will // have been determined (and they don't change between passes). Eventually the // VM reaches a steady state and at that point we can determine what the value // of the wire 'a'. // // This is fast enough and simple enough for our purposes. // // A "better" way would be to start from the instruction that sets 'a' and then // execute the instructions that determine the signals coming into it, and so // on. This would only require a single pass through the instruction list so // would probably be significantly quicker. (But requires more thought in the // implementation) // helper type for the visitor #4 template struct Overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) template Overloaded(Ts...) -> Overloaded; /// Instruction action enum class Action { Set, ///< Set value And, ///< And two values Or, ///< Or two values LShift, ///< Left shift RShift, ///< Right shift Not ///< Bitwise not }; /// Pretty-print action auto operator<<(std::ostream& os, Action act) -> std::ostream& { switch (act) { case Action::Set: os << "SET"; break; case Action::And: os << "AND"; break; case Action::Or: os << "OR"; break; case Action::LShift: os << "LSHIFT"; break; case Action::RShift: os << "RSHIFT"; break; case Action::Not: os << "NOT"; break; } return os; } using Value = std::uint16_t; ///< Value using Wire = std::string; ///< Wire name (string) using Signal = std::variant; ///< Either a wire or explicit value /// Outputter for a signal auto operator<<(std::ostream& os, Signal const& signal) -> std::ostream& { return std::visit( [&os](auto&& arg) -> std::ostream& { os << arg; return os; }, signal); } /** \brief An instruction. */ struct Instruction { /** \brief Construct an instruction. * * \subsection Grammar * * wire := [a-z]+ * value := [0-9]+ * signal := value | wire * set := signal '->' wire * not := 'NOT' set * op := 'AND' | 'OR' | 'LSHIFT' | 'RSHIFT' * binop := signal op signal '->' wire * instr := binop | not | set */ explicit Instruction(std::string const& s) { if (parse_bin_op(s)) { return; } if (parse_not(s)) { return; } if (parse_set(s)) { return; } std::cout << "Unrecognised string: " << s << "\n"; assert(false); } /// Get action [[nodiscard]] auto action() const noexcept -> Action { return act_; } /// Get the destination wire [[nodiscard]] auto dest() const noexcept -> Wire const& { return dest_; } /// Get the first (or only) source [[nodiscard]] auto src1() const noexcept -> Signal const& { return src1_; } /// Get the second source [[nodiscard]] auto src2() const noexcept -> Signal const& { assert(act_ != Action::Set && act_ != Action::Not); return src2_; } private: /// Parse a instruction. Return true if successful. auto parse_not(std::string const& s) -> bool { if (s.substr(0, 4) == "NOT ") { std::string::size_type pos = 4; while (s[pos] == ' ') { ++pos; } return parse_set(s.substr(pos), Action::Not); } return false; } /// Parse a instruction. Return true if successful. auto parse_bin_op(std::string const& s) -> bool { static const std::regex re("^([[:lower:][:digit:]]+) ([[:upper:]]+) " "([[:lower:][:digit:]]+) -> ([[:lower:]]+)"); std::smatch m; if (!std::regex_search(s, m, re)) { return false; } if (m.str(2) == "AND") { act_ = Action::And; } else if (m.str(2) == "OR") { act_ = Action::Or; } else if (m.str(2) == "LSHIFT") { act_ = Action::LShift; } else if (m.str(2) == "RSHIFT") { act_ = Action::RShift; } else { return false; } dest_ = m.str(4); src1_ = make_signal(m.str(1)); src2_ = make_signal(m.str(3)); std::cout << act_ << " " << dest_ << ", " << src1_ << ", " << src2_ << "\n"; return true; } /// Parse a instruction. /// /// Also used for the latter half of parsing. ACT tells you what is /// being parsed. Returns true if parsing successful. auto parse_set(std::string const& s, Action act = Action::Set) -> bool { static const std::regex re("^([[:lower:][:digit:]]+) -> ([[:lower:]]+)"); std::smatch m; if (!std::regex_search(s, m, re)) { return false; } act_ = act; dest_ = m.str(2); src1_ = make_signal(m.str(1)); std::cout << act_ << " " << dest_ << ", " << src1_ << "\n"; return true; } /// Make a Signal from a string. static auto make_signal(std::string const& s) -> Signal { if (std::isdigit(s[0]) == 1) { auto u = std::stoul(s, nullptr, 10); assert(u <= UINT16_MAX); return {static_cast(u)}; } return {s}; } Action act_{Action::Not}; ///< Action Wire dest_; ///< Destination wire Signal src1_, src2_; ///< Source signals }; /// Outputter for an instruction. auto operator<<(std::ostream& os, Instruction const& instr) -> std::ostream& { os << instr.action() << " " << instr.dest() << ", " << instr.src1(); if (instr.action() != Action::Set && instr.action() != Action::Not) { os << ", " << instr.src2(); } return os; } /// Ma using ValueMap = std::map; ///< Map wires to values using Instructions = std::vector; ///< Instructions to execute struct VM { /// Add an instruction the the list we have void add_instr(Instruction const& instr) { instructions_.push_back(instr); } /// Has this wire a known value? [[nodiscard]] auto has_value(Wire const& w) const noexcept -> bool { return values_.find(w) != values_.end(); } /// Has this signal a known value? [[nodiscard]] auto has_value(Signal const& s) const noexcept -> bool { return std::visit( Overloaded{[](Value /*v*/) { return true; }, [&](Wire const& w) { return has_value(w); }}, s); } /// Get the value on the wire [[nodiscard]] auto value(Wire const& w) const noexcept -> Value { assert(has_value(w)); return values_.find(w)->second; } /// Get the value of a signal [[nodiscard]] auto value(Signal const& s) const noexcept -> Value { return std::visit( Overloaded{[](Value v) { return v; }, [&](Wire const& w) { return value(w); }}, s); } /// Set the value of a wire void value(Wire const& w, Value value) { auto [it, success] = values_.insert({w, value}); assert(success); } /// Set the value of a signal void value(Signal const& s, Value v) { std::visit( Overloaded{[v](Value v2) { assert(v == v2); }, [&, v](Wire const& w) { value(w, v); }}, // NOLINT(bugprone-lambda-function-name) s); // NOLINT(bugprone-lambda-function-name) } /// Execute the instructions. Returns true if we have updated some wire /// values. auto execute() -> bool { bool done_anything = false; for (auto const& instr : instructions_) { done_anything |= execute_instr(instr); } return done_anything; } private: /** \brief Attempt to execute an instruction * \param instr Instruction * \return True if instruction was executed. * * An instruction may not be executed if the incoming signals have not been * set yet. */ auto execute_instr(Instruction const& instr) -> bool { std::cout << instr << " # "; // First of all check there is something to do - i.e. that the destination // register has not been set already. auto const& dest = instr.dest(); if (has_value(dest)) { std::cout << "already has value: " << dest << " = " << value(dest) << "\n"; return false; } switch (instr.action()) { case Action::Set: return execute_single_src(instr, [](Value src) { return src; }); case Action::Not: return execute_single_src(instr, [](Value src) { return ~src; }); case Action::And: return execute_double_src(instr, [](Value src1, Value src2) { return src1 & src2; }); case Action::Or: return execute_double_src(instr, [](Value src1, Value src2) { return src1 | src2; }); case Action::LShift: return execute_double_src(instr, [](Value src1, Value src2) { return src1 << src2; }); case Action::RShift: return execute_double_src(instr, [](Value src1, Value src2) { return src1 >> src2; }); } return false; } /** \brief Attempt to execute a single source instruction. * \param instr Instruction * \param fn How to modify the source value to the dest. * \return True if we executed the function. */ auto execute_single_src(Instruction const& instr, const std::function& fn) -> bool { const Wire& dest = instr.dest(); const Signal& src = instr.src1(); if (has_value(src)) { value(dest, fn(value(src))); std::cout << "setting wire to: " << dest << " = " << value(dest) << "\n"; return true; } std::cout << "missing value for signal: " << src << "\n"; return false; } /** \brief Attempt to execute a two source instruction. * \param instr Instruction * \param fn How to modify the source values to the dest. * \return True if we executed the function. */ auto execute_double_src(Instruction const& instr, const std::function& fn) -> bool { const Wire& dest = instr.dest(); const Signal& src1 = instr.src1(); const Signal& src2 = instr.src2(); if (has_value(src1) && has_value(src2)) { value(dest, fn(value(src1), value(src2))); std::cout << "setting wire to: " << dest << " = " << value(dest) << "\n"; return true; } std::cout << "missing value for signals: " << src1 << ", " << src2 << "\n"; return false; } ValueMap values_; Instructions instructions_; }; auto main() -> int { VM vm; // Parse the input for (std::string line; std::getline(std::cin, line);) { Instruction instr(line); vm.add_instr(instr); } // Execute the VM until it reaches a steady state bool changed = true; while (changed) { changed = vm.execute(); } // Get the value of wire 'a' Wire a = Wire("a"); std::cout << "a = "; if (!vm.has_value(a)) { std::cout << "UNSET\n"; } else { std::cout << vm.value(a) << "\n"; } return 0; }