Guest User

AoC2019 Intcode assembler and VM

a guest
Jan 4th, 2020
535
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 48.26 KB | None | 0 0
  1. #include <tclap/CmdLine.h>
  2. #include <tclap/MultiArg.h>
  3. #include <tclap/SwitchArg.h>
  4. #include <tclap/ValueArg.h>
  5.  
  6. #include <algorithm>
  7. #include <array>
  8. #include <cctype>
  9. #include <cinttypes>
  10. #include <cstdint>
  11. #include <cstdlib>
  12. #include <deque>
  13. #include <fstream>
  14. #include <iostream>
  15. #include <iterator>
  16. #include <memory>
  17. #include <optional>
  18. #include <regex>
  19. #include <stdexcept>
  20. #include <string>
  21. #include <string_view>
  22. #include <tuple>
  23. #include <type_traits>
  24. #include <unordered_map>
  25. #include <variant>
  26. #include <vector>
  27.  
  28. namespace intcode {
  29.  
  30. using memory_cell = std::int_fast64_t;
  31. using memory = std::vector<memory_cell>;
  32.  
  33. namespace type_support {
  34.  
  35. template <typename T> struct type_tag { using type = T; };
  36.  
  37. template <typename V> struct variant_types {};
  38.  
  39. template <typename... Ts> struct variant_types<std::variant<Ts...>> {
  40.   using type = std::tuple<type_tag<Ts>...>;
  41. };
  42.  
  43. } // namespace type_support
  44.  
  45. namespace util {
  46.  
  47. std::string read_stream(std::istream &in) {
  48.   std::string result;
  49.   std::copy(std::istreambuf_iterator<char>{in},
  50.             std::istreambuf_iterator<char>{}, std::back_inserter(result));
  51.   return result;
  52. }
  53.  
  54. template <typename Container,
  55.           std::enable_if_t<std::is_convertible_v<
  56.                                std::string, typename Container::value_type>,
  57.                            int> = 0>
  58. Container split(std::string_view string, std::regex const &separator) {
  59.   Container tokens;
  60.  
  61.   using token_iter =
  62.       std::regex_token_iterator<std::string_view::const_iterator>;
  63.   std::copy(token_iter{string.begin(), string.end(), separator, -1},
  64.             token_iter{}, std::back_inserter(tokens));
  65.  
  66.   return tokens;
  67. }
  68.  
  69. std::tuple<std::string_view, std::optional<std::string_view>>
  70. split_once(std::string_view string, char const separator) {
  71.   if (std::size_t split_pos = string.find(separator);
  72.       split_pos != std::string_view::npos) {
  73.     return std::make_tuple(
  74.         string.substr(0, split_pos),
  75.         string.substr(split_pos + 1, std::string_view::npos));
  76.   } else {
  77.     return std::make_tuple(string, std::nullopt);
  78.   }
  79. }
  80.  
  81. template <typename Container, typename Separator>
  82. std::ostream &join_to_stream(std::ostream &os, Container &&c, Separator &&sep) {
  83.   bool first = true;
  84.   for (auto const &value : c) {
  85.     if (!first)
  86.       os << sep;
  87.     os << value;
  88.     first = false;
  89.   }
  90.   return os;
  91. }
  92.  
  93. std::intmax_t string_to_intmax(std::string const &s) {
  94.   // NOTE that strtoimax clamps the value to the maximum range, and there
  95.   // is nothing we can do about that. The errno == ERANGE case is useless
  96.   // because the call may have been successful and the error value may have been
  97.   // left by a previous call.
  98.   char *end_ptr;
  99.   std::intmax_t result = std::strtoimax(s.c_str(), &end_ptr, 10);
  100.   if (end_ptr != s.data() + s.size())
  101.     throw std::invalid_argument{"Failed to parse as intmax_t: " + s};
  102.   return result;
  103. }
  104.  
  105. template <typename Integer, std::enable_if_t<std::is_integral_v<Integer> &&
  106.                                                  std::is_signed_v<Integer>,
  107.                                              int> = 0>
  108. Integer string_to_integer(std::string const &s) {
  109.   auto const i = string_to_intmax(s);
  110.   if (i < std::numeric_limits<Integer>::min() ||
  111.       i > std::numeric_limits<Integer>::max())
  112.     throw std::range_error{"Value out of range: " + std::to_string(i)};
  113.   return static_cast<Integer>(i);
  114. }
  115.  
  116. void upcase(std::string &s) {
  117.   std::transform(s.begin(), s.end(), s.begin(),
  118.                  [](unsigned char c) -> char { return std::toupper(c); });
  119. }
  120.  
  121. } // namespace util
  122.  
  123. namespace parser {
  124.  
  125. struct parse_error : std::runtime_error {
  126.   using runtime_error::runtime_error;
  127. };
  128.  
  129. template <typename Container,
  130.           std::enable_if_t<std::is_convertible_v<
  131.                                memory_cell, typename Container::value_type>,
  132.                            int> = 0>
  133. void parse_int_list_into(Container &target, std::string_view input) {
  134.   static thread_local std::regex const comma_regex{","};
  135.   static thread_local std::regex const num_regex{"\\s*([+-]?[0-9]+)\\s*"};
  136.  
  137.   using token_iter =
  138.       std::regex_token_iterator<std::string_view::const_iterator>;
  139.   std::transform(token_iter{input.begin(), input.end(), comma_regex, -1},
  140.                  token_iter{}, std::back_inserter(target),
  141.                  [&](std::string const &t) {
  142.                    std::smatch m;
  143.                    if (std::regex_match(t, m, num_regex)) {
  144.                      return util::string_to_integer<memory_cell>(m[1]);
  145.                    } else {
  146.                      throw parse_error{"Invalid token: " + t};
  147.                    }
  148.                  });
  149. }
  150.  
  151. template <typename Container,
  152.           std::enable_if_t<std::is_convertible_v<
  153.                                memory_cell, typename Container::value_type>,
  154.                            int> = 0>
  155. Container parse_int_list(std::string_view input) {
  156.   Container content;
  157.   parse_int_list_into(content, input);
  158.   return content;
  159. }
  160.  
  161. } // namespace parser
  162.  
  163. struct execution_error : std::runtime_error {
  164.   using runtime_error::runtime_error;
  165. };
  166.  
  167. namespace io {
  168. struct input_pipe {
  169.   virtual ~input_pipe() = default;
  170.   virtual std::optional<memory_cell> read() = 0;
  171. };
  172.  
  173. struct output_pipe {
  174.   virtual ~output_pipe() = default;
  175.   virtual void write(memory_cell) = 0;
  176. };
  177. } // namespace io
  178.  
  179. class virtual_machine final {
  180. public:
  181.   explicit virtual_machine(memory m) : mem{std::move(m)} {}
  182.  
  183.   memory_cell fetch_at_ip() {
  184.     auto index = ip++;
  185.     if (index >= mem.size())
  186.       return 0;
  187.     else
  188.       return mem.at(index);
  189.   }
  190.  
  191.   void reset_ip() { ip = 0; }
  192.  
  193.   std::size_t current_ip() const { return ip; }
  194.  
  195.   memory_cell load(memory_cell const address) const {
  196.     auto index = address_to_index(address);
  197.     if (index < mem.size())
  198.       return mem[index];
  199.     else
  200.       return 0;
  201.   }
  202.  
  203.   void store(memory_cell const address, memory_cell const value) {
  204.     auto index = address_to_index(address);
  205.     if (index == std::numeric_limits<std::size_t>::max())
  206.       throw execution_error{"Required memory size not representable as size_t"};
  207.  
  208.     if (index >= mem.size()) {
  209.       if (memory_limit && index >= *memory_limit)
  210.         throw execution_error{
  211.             "Required memory size exceeds configured memory limit"};
  212.       mem.resize(index + 1);
  213.     }
  214.  
  215.     mem[index] = value;
  216.   }
  217.  
  218.   void jump(memory_cell const address) { ip = address_to_index(address); }
  219.  
  220.   memory_cell get_base_pointer() const { return base_pointer; }
  221.  
  222.   void adjust_base_pointer(memory_cell const offset) { base_pointer += offset; }
  223.  
  224.   memory_cell input() {
  225.     if (!indev)
  226.       throw execution_error{"No input device set"};
  227.     if (auto value = indev->read())
  228.       return *value;
  229.     else
  230.       throw execution_error{"No input available"};
  231.   }
  232.  
  233.   void output(memory_cell const value) {
  234.     if (!outdev)
  235.       throw execution_error{"No output device set"};
  236.     outdev->write(value);
  237.   }
  238.  
  239.   void set_output_device(std::shared_ptr<io::output_pipe> pipe) {
  240.     outdev = std::move(pipe);
  241.   }
  242.  
  243.   void set_input_device(std::shared_ptr<io::input_pipe> pipe) {
  244.     indev = std::move(pipe);
  245.   }
  246.  
  247.   void set_memory_limit(std::optional<std::size_t> const l) {
  248.     memory_limit = l;
  249.   }
  250.  
  251.   memory const &get_memory() const { return mem; }
  252.  
  253. private:
  254.   std::size_t address_to_index(memory_cell const address) const {
  255.     if (address < 0)
  256.       throw execution_error{"Negative address: " + std::to_string(address)};
  257.     if constexpr (sizeof(memory_cell) > sizeof(std::size_t)) {
  258.       if (address >
  259.           static_cast<memory_cell>(std::numeric_limits<std::size_t>::max()))
  260.         throw execution_error{"Address too large: " + std::to_string(address)};
  261.     }
  262.     auto index = static_cast<std::size_t>(address);
  263.     return index;
  264.   }
  265.  
  266.   memory mem;
  267.   memory_cell base_pointer = 0;
  268.   std::size_t ip = 0;
  269.   std::optional<std::size_t> memory_limit;
  270.   std::shared_ptr<io::output_pipe> outdev;
  271.   std::shared_ptr<io::input_pipe> indev;
  272. };
  273.  
  274. namespace io {
  275.  
  276. class cin_pipe : public input_pipe {
  277. public:
  278.   std::optional<memory_cell> read() override {
  279.     memory_cell value;
  280.     std::cerr << "The program is expecting input: " << std::flush;
  281.     if (!(std::cin >> value))
  282.       throw execution_error{"No input available"};
  283.     return value;
  284.   }
  285. };
  286.  
  287. class cout_pipe : public output_pipe {
  288. public:
  289.   void write(memory_cell const value) override { std::cout << value << '\n'; }
  290. };
  291.  
  292. class deque_pipe : public input_pipe, public output_pipe {
  293. public:
  294.   deque_pipe() {}
  295.  
  296.   explicit deque_pipe(std::deque<memory_cell> initial_content)
  297.       : queue{std::move(initial_content)} {}
  298.  
  299.   std::optional<memory_cell> read() override {
  300.     if (queue.empty())
  301.       return std::nullopt;
  302.     auto const v = queue.front();
  303.     queue.pop_front();
  304.     return v;
  305.   }
  306.  
  307.   void write(memory_cell const v) override { queue.push_back(v); }
  308.  
  309.   std::deque<memory_cell> const &get_queue() const & { return queue; }
  310.   void set_queue(std::deque<memory_cell> q) { queue = std::move(q); }
  311.  
  312. private:
  313.   std::deque<memory_cell> queue;
  314. };
  315.  
  316. class combining_input_pipe : public input_pipe {
  317. public:
  318.   explicit combining_input_pipe(std::vector<std::shared_ptr<input_pipe>> inputs)
  319.       : inputs{std::move(inputs)} {
  320.     for (auto const &p : inputs) {
  321.       if (!p)
  322.         throw std::invalid_argument{"Null pointers not allowed for inputs"};
  323.     }
  324.   }
  325.  
  326.   std::optional<memory_cell> read() override {
  327.     for (auto const &input : inputs) {
  328.       if (auto value = input->read())
  329.         return *value;
  330.     }
  331.     return std::nullopt;
  332.   }
  333.  
  334. private:
  335.   std::vector<std::shared_ptr<input_pipe>> inputs;
  336. };
  337.  
  338. class basic_ostream_output_pipe : public output_pipe {
  339. public:
  340.   void write(memory_cell const value) override {
  341.     std::ostream &os = get_ostream();
  342.     if (!first)
  343.       os << ',';
  344.     os << value;
  345.     first = false;
  346.   }
  347.  
  348. protected:
  349.   virtual std::ostream &get_ostream() = 0;
  350.  
  351. private:
  352.   bool first = true;
  353. };
  354.  
  355. class file_output_pipe : public basic_ostream_output_pipe {
  356. public:
  357.   explicit file_output_pipe(std::string const &filename) : file{filename} {
  358.     if (!file.is_open())
  359.       throw std::runtime_error{"Unable to open output file " + filename};
  360.   }
  361.  
  362. protected:
  363.   std::ostream &get_ostream() override { return file; }
  364.  
  365. private:
  366.   std::ofstream file;
  367. };
  368.  
  369. class cout_output_pipe : public basic_ostream_output_pipe {
  370. protected:
  371.   std::ostream &get_ostream() override { return std::cout; }
  372. };
  373.  
  374. } // namespace io
  375.  
  376. enum class operand_mode { address, immediate, relative_address };
  377.  
  378. struct operand final {
  379.   operand_mode mode;
  380.   memory_cell value;
  381. };
  382.  
  383. std::ostream &operator<<(std::ostream &os, operand const op) {
  384.   switch (op.mode) {
  385.   case operand_mode::immediate:
  386.     os << op.value;
  387.     break;
  388.   case operand_mode::address:
  389.     os << '[' << op.value << ']';
  390.     break;
  391.   case operand_mode::relative_address:
  392.     os << "[BP ";
  393.     if (op.value < 0)
  394.       os << "- " << -op.value;
  395.     else
  396.       os << "+ " << op.value;
  397.     os << ']';
  398.     break;
  399.   }
  400.   return os;
  401. }
  402.  
  403. namespace vm_util {
  404. memory_cell load_operand(virtual_machine const &vm, operand const op) {
  405.   switch (op.mode) {
  406.   case operand_mode::immediate:
  407.     return op.value;
  408.   case operand_mode::address:
  409.     return vm.load(op.value);
  410.   case operand_mode::relative_address:
  411.     return vm.load(op.value + vm.get_base_pointer());
  412.   }
  413. }
  414.  
  415. void store_operand(virtual_machine &vm, operand const op,
  416.                    memory_cell const value) {
  417.   switch (op.mode) {
  418.   case operand_mode::immediate:
  419.     throw execution_error{"Cannot store to immediate operand"};
  420.   case operand_mode::address:
  421.     vm.store(op.value, value);
  422.     break;
  423.   case operand_mode::relative_address:
  424.     vm.store(op.value + vm.get_base_pointer(), value);
  425.     break;
  426.   }
  427. }
  428. } // namespace vm_util
  429.  
  430. namespace instructions {
  431. struct adder final {
  432.   static memory_cell compute(memory_cell const left, memory_cell const right) {
  433.     return left + right;
  434.   }
  435.   static void output_mnemonic(std::ostream &os) { os << "ADD"; }
  436. };
  437.  
  438. struct multiplier final {
  439.   static memory_cell compute(memory_cell const left, memory_cell const right) {
  440.     return left * right;
  441.   }
  442.   static void output_mnemonic(std::ostream &os) { os << "MUL"; }
  443. };
  444.  
  445. template <memory_cell Opcode, typename Operator> class basic_binary_operation {
  446. public:
  447.   static constexpr memory_cell opcode = Opcode;
  448.   static constexpr std::size_t operand_count = 3;
  449.  
  450.   basic_binary_operation(operand src1, operand src2, operand dst)
  451.       : src_op_left{src1}, src_op_right{src2}, dst_op{dst} {}
  452.  
  453.   void execute(virtual_machine &vm) const {
  454.     memory_cell const left_input = vm_util::load_operand(vm, src_op_left);
  455.     memory_cell const right_input = vm_util::load_operand(vm, src_op_right);
  456.     memory_cell const result = Operator::compute(left_input, right_input);
  457.     vm_util::store_operand(vm, dst_op, result);
  458.   }
  459.  
  460.   static std::ostream &output_mnemonic(std::ostream &os) {
  461.     Operator::output_mnemonic(os);
  462.     return os;
  463.   }
  464.  
  465.   std::array<operand, operand_count> operands() const {
  466.     return {src_op_left, src_op_right, dst_op};
  467.   }
  468.  
  469. private:
  470.   operand src_op_left;
  471.   operand src_op_right;
  472.   operand dst_op;
  473. };
  474.  
  475. using add = basic_binary_operation<1, adder>;
  476. using multiply = basic_binary_operation<2, multiplier>;
  477.  
  478. class input final {
  479. public:
  480.   static constexpr memory_cell opcode = 3;
  481.   static constexpr std::size_t operand_count = 1;
  482.  
  483.   explicit input(operand dst) : dst_op{dst} {}
  484.  
  485.   void execute(virtual_machine &vm) const {
  486.     vm_util::store_operand(vm, dst_op, vm.input());
  487.   }
  488.  
  489.   static std::ostream &output_mnemonic(std::ostream &os) { return os << "INP"; }
  490.  
  491.   std::array<operand, operand_count> operands() const { return {dst_op}; }
  492.  
  493. private:
  494.   operand dst_op;
  495. };
  496.  
  497. class output final {
  498. public:
  499.   static constexpr memory_cell opcode = 4;
  500.   static constexpr std::size_t operand_count = 1;
  501.  
  502.   explicit output(operand src) : src_op{src} {}
  503.  
  504.   void execute(virtual_machine &vm) const {
  505.     vm.output(vm_util::load_operand(vm, src_op));
  506.   }
  507.  
  508.   static std::ostream &output_mnemonic(std::ostream &os) { return os << "OUT"; }
  509.  
  510.   std::array<operand, operand_count> operands() const { return {src_op}; }
  511.  
  512. private:
  513.   operand src_op;
  514. };
  515.  
  516. struct condition_nonzero final {
  517.   static bool met(memory_cell const value) { return value != 0; }
  518.   static void output_mnemonic_fragment(std::ostream &os) { os << "NZ"; }
  519. };
  520.  
  521. struct condition_zero final {
  522.   static bool met(memory_cell const value) { return value == 0; }
  523.   static void output_mnemonic_fragment(std::ostream &os) { os << "Z"; }
  524. };
  525.  
  526. struct condition_less final {
  527.   static bool met(memory_cell const left, memory_cell const right) {
  528.     return left < right;
  529.   }
  530.   static void output_mnemonic_fragment(std::ostream &os) { os << "LT"; }
  531. };
  532.  
  533. struct condition_equal final {
  534.   static bool met(memory_cell const left, memory_cell const right) {
  535.     return left == right;
  536.   }
  537.   static void output_mnemonic_fragment(std::ostream &os) { os << "EQ"; }
  538. };
  539.  
  540. template <memory_cell Opcode, typename Condition> class conditional_jump final {
  541. public:
  542.   static constexpr memory_cell opcode = Opcode;
  543.   static constexpr std::size_t operand_count = 2;
  544.  
  545.   explicit conditional_jump(operand cond, operand tgt)
  546.       : condition{cond}, target{tgt} {}
  547.  
  548.   void execute(virtual_machine &vm) const {
  549.     if (Condition::met(vm_util::load_operand(vm, condition)))
  550.       vm.jump(vm_util::load_operand(vm, target));
  551.   }
  552.  
  553.   static std::ostream &output_mnemonic(std::ostream &os) {
  554.     Condition::output_mnemonic_fragment(os << 'J');
  555.     return os;
  556.   }
  557.  
  558.   std::array<operand, operand_count> operands() const {
  559.     return {condition, target};
  560.   }
  561.  
  562. private:
  563.   operand condition;
  564.   operand target;
  565. };
  566.  
  567. template <memory_cell Opcode, typename Condition> class comparison final {
  568. public:
  569.   static constexpr memory_cell opcode = Opcode;
  570.   static constexpr std::size_t operand_count = 3;
  571.  
  572.   explicit comparison(operand left, operand right, operand dest)
  573.       : left{left}, right{right}, destination{dest} {}
  574.  
  575.   void execute(virtual_machine &vm) const {
  576.     memory_cell const res = Condition::met(vm_util::load_operand(vm, left),
  577.                                            vm_util::load_operand(vm, right))
  578.                                 ? 1
  579.                                 : 0;
  580.     vm_util::store_operand(vm, destination, res);
  581.   }
  582.  
  583.   static std::ostream &output_mnemonic(std::ostream &os) {
  584.     Condition::output_mnemonic_fragment(os << "CMP");
  585.     return os;
  586.   }
  587.  
  588.   std::array<operand, operand_count> operands() const {
  589.     return {left, right, destination};
  590.   }
  591.  
  592. private:
  593.   operand left;
  594.   operand right;
  595.   operand destination;
  596. };
  597.  
  598. using jump_if_nonzero = conditional_jump<5, condition_nonzero>;
  599. using jump_if_zero = conditional_jump<6, condition_zero>;
  600. using compare_less = comparison<7, condition_less>;
  601. using compare_equal = comparison<8, condition_equal>;
  602.  
  603. class adjust_relative_base final {
  604. public:
  605.   static constexpr memory_cell opcode = 9;
  606.   static constexpr std::size_t operand_count = 1;
  607.  
  608.   explicit adjust_relative_base(operand base_offset)
  609.       : base_offset{base_offset} {}
  610.  
  611.   void execute(virtual_machine &vm) const {
  612.     vm.adjust_base_pointer(vm_util::load_operand(vm, base_offset));
  613.   }
  614.  
  615.   static std::ostream &output_mnemonic(std::ostream &os) { return os << "ABP"; }
  616.  
  617.   std::array<operand, operand_count> operands() const { return {base_offset}; }
  618.  
  619. private:
  620.   operand base_offset;
  621. };
  622. } // namespace instructions
  623.  
  624. using instruction =
  625.     std::variant<instructions::add, instructions::multiply, instructions::input,
  626.                  instructions::output, instructions::jump_if_nonzero,
  627.                  instructions::jump_if_zero, instructions::compare_less,
  628.                  instructions::compare_equal,
  629.                  instructions::adjust_relative_base>;
  630.  
  631. namespace executor {
  632. std::ostream &disassemble_instruction(std::ostream &os,
  633.                                       instruction const &inst) {
  634.   std::visit(
  635.       [&](auto const &inst) {
  636.         util::join_to_stream(inst.output_mnemonic(os) << ' ', inst.operands(),
  637.                              ", ");
  638.       },
  639.       inst);
  640.   return os;
  641. }
  642.  
  643. operand_mode decode_operand_mode(memory_cell const value) {
  644.   switch (value) {
  645.   case 0:
  646.     return operand_mode::address;
  647.   case 1:
  648.     return operand_mode::immediate;
  649.   case 2:
  650.     return operand_mode::relative_address;
  651.   default:
  652.     throw execution_error{"Invalid operand mode: " + std::to_string(value)};
  653.   }
  654. }
  655.  
  656. template <std::size_t Count>
  657. std::array<memory_cell, Count> fetch_operand_values(virtual_machine &vm) {
  658.   std::array<memory_cell, Count> result;
  659.   for (std::size_t i = 0; i < Count; ++i)
  660.     result.at(i) = vm.fetch_at_ip();
  661.   return result;
  662. }
  663.  
  664. // nullopt means HALT
  665. std::optional<instruction> fetch_instruction(virtual_machine &vm) {
  666.   memory_cell const head = vm.fetch_at_ip();
  667.   if (head < 0 || head > 99999)
  668.     throw execution_error{"Invalid instruction head: " + std::to_string(head)};
  669.  
  670.   memory_cell const opcode = head % 100;
  671.   std::array<operand_mode, 3> operand_modes{
  672.       decode_operand_mode((head / 100) % 10),
  673.       decode_operand_mode((head / 1000) % 10),
  674.       decode_operand_mode((head / 10000) % 10)};
  675.  
  676.   if (opcode == 99)
  677.     return std::nullopt;
  678.  
  679.   std::optional<instruction> inst;
  680.   std::apply(
  681.       [&](auto... tags) {
  682.         (
  683.             [&](auto tag) {
  684.               using inst_type = typename decltype(tag)::type;
  685.               if (inst_type::opcode == opcode) {
  686.                 if (inst) {
  687.                   throw std::logic_error{
  688.                       "Bug: Multiple instructions with opcode " +
  689.                       std::to_string(opcode)};
  690.                 }
  691.                 if constexpr (inst_type::operand_count == 0) {
  692.                   inst = inst_type{};
  693.                 } else {
  694.                   static_assert(inst_type::operand_count <= 3);
  695.                   auto operand_values =
  696.                       fetch_operand_values<inst_type::operand_count>(vm);
  697.                   std::array<operand, inst_type::operand_count> operands;
  698.                   for (std::size_t i = 0; i < inst_type::operand_count; ++i)
  699.                     operands.at(i) =
  700.                         operand{operand_modes.at(i), operand_values.at(i)};
  701.                   inst = std::make_from_tuple<inst_type>(operands);
  702.                 }
  703.               }
  704.             }(tags),
  705.             ...);
  706.       },
  707.       typename type_support::variant_types<instruction>::type{});
  708.  
  709.   if (inst)
  710.     return *inst;
  711.   else
  712.     throw execution_error{"Invalid opcode: " + std::to_string(opcode)};
  713. }
  714.  
  715. // false means HALT
  716. bool execute_next(virtual_machine &vm) {
  717.   if (auto inst = fetch_instruction(vm)) {
  718.     std::visit([&](auto const inst) { inst.execute(vm); }, *inst);
  719.     return true;
  720.   } else {
  721.     return false;
  722.   }
  723. }
  724.  
  725. // false means HALT
  726. bool disassemble_next(virtual_machine &vm, std::ostream &os) {
  727.   os << vm.current_ip() << ": ";
  728.   if (auto inst = fetch_instruction(vm)) {
  729.     std::visit(
  730.         [&](auto const inst) { disassemble_instruction(os, inst) << '\n'; },
  731.         *inst);
  732.     return true;
  733.   } else {
  734.     os << "HALT\n";
  735.     return false;
  736.   }
  737. }
  738.  
  739. // false means HALT
  740. bool execute_and_disassemble_next(virtual_machine &vm, std::ostream &os) {
  741.   os << vm.current_ip() << ": ";
  742.   if (auto inst = fetch_instruction(vm)) {
  743.     std::visit(
  744.         [&](auto const inst) {
  745.           disassemble_instruction(os, inst) << '\n';
  746.           inst.execute(vm);
  747.         },
  748.         *inst);
  749.     return true;
  750.   } else {
  751.     os << "HALT\n";
  752.     return false;
  753.   }
  754. }
  755. } // namespace executor
  756.  
  757. class assembler final {
  758. public:
  759.   struct assembly_error : std::runtime_error {
  760.     using runtime_error::runtime_error;
  761.   };
  762.  
  763.   assembler() {
  764.     std::apply(
  765.         [&](auto... tags) {
  766.           (
  767.               [&](auto tag) {
  768.                 using instruction_type = typename decltype(tag)::type;
  769.                 std::ostringstream str;
  770.                 instruction_type::output_mnemonic(str);
  771.                 mnemonic_map.emplace(
  772.                     str.str(),
  773.                     instruction_descriptor{instruction_type::opcode,
  774.                                            instruction_type::operand_count});
  775.               }(tags),
  776.               ...);
  777.         },
  778.         typename type_support::variant_types<instruction>::type{});
  779.   }
  780.  
  781.   memory parse_assembly(std::istream &is) {
  782.     static thread_local std::regex const blank_line_regex{"\\s*(;.*)?"};
  783.     static thread_local std::regex const line_regex{
  784.         "\\s*([^\\s;]([^;]*[^\\s;])?)\\s*(;.*)?"};
  785.  
  786.     parse_context context;
  787.  
  788.     std::string line;
  789.     while (std::getline(is, line)) {
  790.       ++context.lineno;
  791.       if (std::regex_match(line, blank_line_regex))
  792.         continue;
  793.       std::smatch line_match;
  794.       if (std::regex_match(line, line_match, line_regex)) {
  795.         parse_line(context, line_match[1].str());
  796.       } else {
  797.         throw_line_error(context, "Malformatted line: " + line);
  798.       }
  799.     }
  800.  
  801.     apply_relocations(context);
  802.  
  803.     return std::move(context).mem;
  804.   }
  805.  
  806. private:
  807.   static constexpr memory_cell canary_value = 11198;
  808.  
  809.   struct instruction_descriptor final {
  810.     memory_cell opcode;
  811.     std::size_t operand_count;
  812.   };
  813.  
  814.   struct relocation final {
  815.     std::size_t address;
  816.     std::string symbol;
  817.   };
  818.  
  819.   struct parse_context final {
  820.     memory mem;
  821.     std::vector<relocation> relocations;
  822.     std::unordered_map<std::string, std::size_t> labels;
  823.     std::size_t lineno{};
  824.   };
  825.  
  826.   [[noreturn]] void throw_line_error(parse_context &context,
  827.                                      std::string const &message) {
  828.     throw assembly_error{"Line " + std::to_string(context.lineno) + ": " +
  829.                          message};
  830.   }
  831.  
  832.   memory_cell encode_operand_mode(operand_mode const value) {
  833.     switch (value) {
  834.     case operand_mode::address:
  835.       return 0;
  836.     case operand_mode::immediate:
  837.       return 1;
  838.     case operand_mode::relative_address:
  839.       return 2;
  840.     }
  841.   }
  842.  
  843.   std::variant<std::string, memory_cell>
  844.   parse_constant_or_relocation(parse_context &context, std::string const &raw) {
  845.     static thread_local std::regex const number_regex{"[+-]?[0-9]+"};
  846.     static thread_local std::regex const identifier_regex{
  847.         "[a-zA-Z_][a-zA-Z0-9_]*"};
  848.     if (std::regex_match(raw, number_regex)) {
  849.       return util::string_to_integer<memory_cell>(raw);
  850.     } else if (std::regex_match(raw, identifier_regex)) {
  851.       return raw;
  852.     } else {
  853.       throw_line_error(context, "Expected number or identifier: " + raw);
  854.     }
  855.   }
  856.  
  857.   void parse_and_push_constant_or_relocation(parse_context &context,
  858.                                              std::string const &raw) {
  859.     auto const value = parse_constant_or_relocation(context, raw);
  860.     if (std::holds_alternative<std::string>(value)) {
  861.       context.relocations.push_back(
  862.           relocation{context.mem.size(), std::get<std::string>(value)});
  863.       context.mem.push_back(canary_value);
  864.     } else {
  865.       context.mem.push_back(std::get<memory_cell>(value));
  866.     }
  867.   }
  868.  
  869.   void label_current_address(parse_context &context,
  870.                              std::string const &symbol) {
  871.     auto const [it, was_inserted] =
  872.         context.labels.emplace(symbol, context.mem.size());
  873.     if (!was_inserted)
  874.       throw_line_error(context, "Duplicate label " + symbol);
  875.   }
  876.  
  877.   void parse_data_definition(parse_context &context,
  878.                              std::string const &raw_arg) {
  879.     static thread_local std::regex const di_arg_regex{
  880.         "\\s*(([a-zA-Z_][a-zA-Z_0-9]*)\\s*:\\s*)?([^\\s]+)\\s*"};
  881.     std::smatch m;
  882.     if (!std::regex_match(raw_arg, m, di_arg_regex))
  883.       throw_line_error(context, "Malformed argument to DI: " + raw_arg);
  884.     if (m[2].length() > 0)
  885.       label_current_address(context, m[2].str());
  886.     parse_and_push_constant_or_relocation(context, m[3].str());
  887.   }
  888.  
  889.   operand_mode parse_operand(parse_context &context, std::string const &raw) {
  890.     static thread_local std::regex const over_regex{
  891.         "\\s*(([a-zA-Z_][a-zA-Z_0-9]*)\\s*:\\s*)?"
  892.         "([^\\s](.*[^\\s])?)\\s*"};
  893.     static thread_local std::regex const relative_operand_regex{
  894.         "\\[\\s*[Bb][Pp]\\s*([+-])\\s*([0-9]+)\\s*\\]"};
  895.     static thread_local std::regex const absolute_operand_regex{
  896.         "\\[\\s*([+-]?[0-9]+|[a-zA-Z_][a-zA-Z0-9_]*)\\s*\\]"};
  897.     static thread_local std::regex const immediate_operand_regex{
  898.         "[+-]?[0-9]+|[a-zA-Z_][a-zA-Z0-9_]*"};
  899.  
  900.     std::smatch over_match;
  901.     if (!std::regex_match(raw, over_match, over_regex))
  902.       throw_line_error(context, "Malformatted operand: " + raw);
  903.     if (over_match[2].length() > 0)
  904.       label_current_address(context, over_match[2].str());
  905.     std::string raw_operand = over_match[3].str();
  906.  
  907.     std::smatch m;
  908.     if (std::regex_match(raw_operand, m, relative_operand_regex)) {
  909.       auto const value = parse_constant_or_relocation(context, m[2].str());
  910.       if (!std::holds_alternative<memory_cell>(
  911.               value)) // Should be impossible due to regex
  912.         throw_line_error(context, "Cannot relocate relative operands");
  913.       auto number = std::get<memory_cell>(value);
  914.       if ("-" == m[1])
  915.         number = -number;
  916.       context.mem.push_back(number);
  917.       return operand_mode::relative_address;
  918.     } else if (std::regex_match(raw_operand, m, absolute_operand_regex)) {
  919.       parse_and_push_constant_or_relocation(context, m[1].str());
  920.       return operand_mode::address;
  921.     } else if (std::regex_match(raw_operand, m, immediate_operand_regex)) {
  922.       parse_and_push_constant_or_relocation(context, raw_operand);
  923.       return operand_mode::immediate;
  924.     } else {
  925.       throw_line_error(context, "Malformed operand: " + raw_operand);
  926.     }
  927.   }
  928.  
  929.   void parse_instruction(parse_context &context, std::string const &mnemonic,
  930.                          std::vector<std::string> const &raw_operands) {
  931.     if (auto it = mnemonic_map.find(mnemonic); it != mnemonic_map.end()) {
  932.       instruction_descriptor const &instruction_descriptor = it->second;
  933.       if (instruction_descriptor.operand_count != raw_operands.size()) {
  934.         throw_line_error(
  935.             context, "Invalid number of operands to " + mnemonic +
  936.                          " instruction, expects " +
  937.                          std::to_string(instruction_descriptor.operand_count));
  938.       }
  939.       std::size_t const instruction_address = context.mem.size();
  940.       context.mem.push_back(instruction_descriptor.opcode);
  941.       memory_cell operand_mode_multiplier = 100;
  942.       for (std::string const &raw_operand : raw_operands) {
  943.         operand_mode const opmode = parse_operand(context, raw_operand);
  944.         context.mem.at(instruction_address) +=
  945.             operand_mode_multiplier * encode_operand_mode(opmode);
  946.         operand_mode_multiplier *= 10;
  947.       }
  948.     } else {
  949.       throw_line_error(context, "Invalid instruction mnemonic: " + mnemonic);
  950.     }
  951.   }
  952.  
  953.   void parse_line(parse_context &context, std::string const &line) {
  954.     static thread_local std::regex const label_only_regex{
  955.         "([a-zA-Z_][a-zA-Z_0-9]*)\\s*:"};
  956.     static thread_local std::regex const assembly_regex{
  957.         "(([a-zA-Z_][a-zA-Z_0-9]*)\\s*:\\s*)?" // Label (optional)
  958.         "([a-zA-Z][a-zA-Z0-9]*)"               // The instruction mnemonic
  959.         "\\s*([^\\s](.*[^\\s])?)?"             // Operand list (optional)
  960.     };
  961.     static thread_local std::regex const comma_regex{","};
  962.  
  963.     std::smatch m;
  964.     if (std::regex_match(line, m, label_only_regex)) {
  965.       label_current_address(context, m[1].str());
  966.     } else if (std::regex_match(line, m, assembly_regex)) {
  967.       if (m[2].length() > 0) {
  968.         label_current_address(context, m[2].str());
  969.       }
  970.       std::string mnemonic = m[3].str();
  971.       util::upcase(mnemonic);
  972.       if ("DI" == mnemonic) {
  973.         if (m[4].length() <= 0)
  974.           throw_line_error(context, "DI requires argument");
  975.         parse_data_definition(context, m[4].str());
  976.       } else if ("HALT" == mnemonic) {
  977.         if (m[4].length() > 0)
  978.           throw_line_error(context, "HALT does not accept arguments");
  979.         context.mem.push_back(99);
  980.       } else {
  981.         std::vector<std::string> raw_operands;
  982.         if (m[4].length() > 0)
  983.           raw_operands =
  984.               util::split<std::vector<std::string>>(m[4].str(), comma_regex);
  985.         parse_instruction(context, mnemonic, raw_operands);
  986.       }
  987.     } else {
  988.       throw_line_error(context, "Malformed line: " + line);
  989.     }
  990.   }
  991.  
  992.   void apply_relocations(parse_context &context) {
  993.     for (relocation const &reloc : context.relocations) {
  994.       if (auto it = context.labels.find(reloc.symbol);
  995.           it != context.labels.end()) {
  996.         context.mem.at(reloc.address) = it->second;
  997.       } else {
  998.         throw assembly_error{"Relocation at address " +
  999.                              std::to_string(reloc.address) +
  1000.                              " references undefined label " + reloc.symbol};
  1001.       }
  1002.     }
  1003.   }
  1004.  
  1005.   std::unordered_map<std::string, instruction_descriptor> mnemonic_map;
  1006. };
  1007.  
  1008. } // namespace intcode
  1009.  
  1010. #ifdef SELF_TEST
  1011. namespace self_test {
  1012. struct test_failure : std::logic_error {
  1013.   using logic_error::logic_error;
  1014. };
  1015.  
  1016. class vm_test_case final {
  1017. public:
  1018.   explicit vm_test_case(std::string const &source_code)
  1019.       : vm{intcode::parser::parse_int_list<intcode::memory>(source_code)} {
  1020.     vm.set_memory_limit(std::size_t{640}
  1021.                         << 10); // Ought to be enough for everybody
  1022.     auto output_dev = std::make_shared<intcode::io::deque_pipe>();
  1023.     vm.set_output_device(output_dev);
  1024.     while (intcode::executor::execute_next(vm))
  1025.       ;
  1026.     output = output_dev->get_queue();
  1027.   }
  1028.  
  1029.   vm_test_case(std::string const &source_code,
  1030.                std::deque<intcode::memory_cell> inp)
  1031.       : vm{intcode::parser::parse_int_list<intcode::memory>(source_code)} {
  1032.     vm.set_memory_limit(std::size_t{640}
  1033.                         << 10); // Ought to be enough for everybody
  1034.     auto input_dev = std::make_shared<intcode::io::deque_pipe>();
  1035.     input_dev->set_queue(std::move(inp));
  1036.     auto output_dev = std::make_shared<intcode::io::deque_pipe>();
  1037.     vm.set_output_device(output_dev);
  1038.     vm.set_input_device(input_dev);
  1039.     while (intcode::executor::execute_next(vm))
  1040.       ;
  1041.     output = output_dev->get_queue();
  1042.   }
  1043.  
  1044.   void assert_memory(intcode::memory_cell const address,
  1045.                      intcode::memory_cell const expected_content) {
  1046.     auto actual = vm.load(address);
  1047.     if (expected_content != actual) {
  1048.       throw test_failure{"Expected " + std::to_string(expected_content) +
  1049.                          " at address " + std::to_string(address) + ", found " +
  1050.                          std::to_string(actual)};
  1051.     }
  1052.   }
  1053.  
  1054.   void assert_ip(std::size_t const expected) {
  1055.     auto actual = vm.current_ip();
  1056.     if (expected != actual) {
  1057.       throw test_failure{"Expected IP to be at " + std::to_string(expected) +
  1058.                          " but it's at " + std::to_string(actual)};
  1059.     }
  1060.   }
  1061.  
  1062.   void assert_output(std::deque<intcode::memory_cell> const &expected) {
  1063.     if (expected.size() != output.size()) {
  1064.       throw test_failure{"Expected " + std::to_string(expected.size()) +
  1065.                          " output values, got " +
  1066.                          std::to_string(output.size())};
  1067.     }
  1068.     auto expected_it = expected.begin();
  1069.     auto expected_end = expected.end();
  1070.     auto actual_it = output.begin();
  1071.     auto actual_end = output.end();
  1072.     while (actual_it != actual_end && expected_it != expected_end) {
  1073.       if (*expected_it != *actual_it) {
  1074.         throw test_failure{"Expected an output of " +
  1075.                            std::to_string(*expected_it) + ", got " +
  1076.                            std::to_string(*actual_it)};
  1077.       }
  1078.       ++actual_it;
  1079.       ++expected_it;
  1080.     }
  1081.   }
  1082.  
  1083. private:
  1084.   std::deque<intcode::memory_cell> output;
  1085.   intcode::virtual_machine vm;
  1086. };
  1087.  
  1088. class asm_test_case final {
  1089. public:
  1090.   explicit asm_test_case(std::string const &source_code) {
  1091.     std::istringstream stream{source_code};
  1092.     intcode::assembler assem;
  1093.     mem = assem.parse_assembly(stream);
  1094.   }
  1095.  
  1096.   void assert_memory(intcode::memory const &expected) {
  1097.     if (expected.size() != mem.size()) {
  1098.       std::ostringstream stream;
  1099.       output_mem_inequality_message(stream, expected)
  1100.           << "; size differs: Expected " << expected.size() << ", actual "
  1101.           << mem.size();
  1102.       throw test_failure{stream.str()};
  1103.     }
  1104.     std::size_t const size = expected.size();
  1105.     for (std::size_t i = 0; i < size; ++i) {
  1106.       if (expected.at(i) != mem.at(i)) {
  1107.         std::ostringstream stream;
  1108.         output_mem_inequality_message(stream, expected)
  1109.             << "; mismatch at " << i << ": Expected " << expected.at(i)
  1110.             << ", actual " << mem.at(i);
  1111.         throw test_failure{stream.str()};
  1112.       }
  1113.     }
  1114.   }
  1115.  
  1116. private:
  1117.   std::ostream &output_mem_inequality_message(std::ostream &os,
  1118.                                               intcode::memory const &expected) {
  1119.     os << "[";
  1120.     intcode::util::join_to_stream(os, expected, ",");
  1121.     os << "] != [";
  1122.     intcode::util::join_to_stream(os, mem, ",");
  1123.     os << "]";
  1124.     return os;
  1125.   }
  1126.  
  1127.   intcode::memory mem;
  1128. };
  1129.  
  1130. void run() {
  1131.   {
  1132.     vm_test_case c{"1101,5,6,5,99,0"};
  1133.     c.assert_ip(5);
  1134.     c.assert_memory(5, 11);
  1135.   }
  1136.   {
  1137.     vm_test_case c{"1,5,6,5,99,13,55"};
  1138.     c.assert_memory(5, 68);
  1139.     c.assert_memory(6, 55);
  1140.   }
  1141.   {
  1142.     vm_test_case c{"1102,-4,10,5,99,0"};
  1143.     c.assert_memory(5, -40);
  1144.   }
  1145.   {
  1146.     vm_test_case c{"3,3,99,0", {77}};
  1147.     c.assert_memory(3, 77);
  1148.   }
  1149.   {
  1150.     vm_test_case c{"104,66,99"};
  1151.     c.assert_output({66});
  1152.   }
  1153.   {
  1154.     vm_test_case c{"3,0, 3,1, 3,2, 2,0,1,1, 1,2,1,0, 4,0, 99", {4, 7, 15}};
  1155.     c.assert_ip(17);
  1156.     c.assert_output({43});
  1157.   }
  1158.   {
  1159.     vm_test_case c{"1105, 0, 7,  101, 30, 15, 15,  1105, 1, 14,  101, 300, 15, "
  1160.                    "15,  99,  3"};
  1161.     c.assert_ip(15);
  1162.     c.assert_memory(15, 33);
  1163.   }
  1164.   {
  1165.     vm_test_case c{"1106, 0, 7,  101, 30, 15, 15,  1106, 1, 14,  101, 300, 15, "
  1166.                    "15,  99,  3"};
  1167.     c.assert_ip(15);
  1168.     c.assert_memory(15, 303);
  1169.   }
  1170.   {
  1171.     vm_test_case c{"1107, 66, 77, 9,  1107, 77, 66, 10,  99,  2, 2"};
  1172.     c.assert_memory(9, 1);
  1173.     c.assert_memory(10, 0);
  1174.   }
  1175.   {
  1176.     vm_test_case c{"1108, 66, 77, 9,  1108, 66, 66, 10,  99,  2, 2"};
  1177.     c.assert_memory(9, 0);
  1178.     c.assert_memory(10, 1);
  1179.   }
  1180.   {
  1181.     vm_test_case c{"109, 17,  21101, 13, 0, 0,  21101, 6, 0, 1,  22202, 0, 1, "
  1182.                    "0,  204, 0,  99"};
  1183.     c.assert_output({13 * 6});
  1184.   }
  1185.   {
  1186.     vm_test_case c{"109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99"};
  1187.     c.assert_output({109, 1, 204, -1, 1001, 100, 1, 100, 1008, 100, 16, 101,
  1188.                      1006, 101, 0, 99});
  1189.   }
  1190.   {
  1191.     vm_test_case c{"1102,34915192,34915192,9,4,9,104,1125899906842624,99,0"};
  1192.     c.assert_output(
  1193.         {intcode::memory_cell{34915192} * intcode::memory_cell{34915192},
  1194.          1125899906842624});
  1195.   }
  1196.   {
  1197.     asm_test_case c{R"ASM(
  1198.                 ; Should parse just fine with comments
  1199.                 add 5, 7, [0]   ; Add constants 5 and 7 and store result at address 0
  1200.                 out [0]         ; Output value at address 0
  1201.                 halt            ; Stop executing
  1202.             )ASM"};
  1203.     c.assert_memory({1101, 5, 7, 0, 4, 0, 99});
  1204.   }
  1205.   {
  1206.     asm_test_case c{R"ASM(
  1207.                 mul 5, 7, [foo]
  1208.                 out [foo]
  1209.                 halt
  1210.                 foo: di 789
  1211.             )ASM"};
  1212.     c.assert_memory({1102, 5, 7, 7, 4, 7, 99, 789});
  1213.   }
  1214.   {
  1215.     asm_test_case c{R"ASM(
  1216.                 abp data_start
  1217.                 loop_start:
  1218.                 cmplt 0, [bp+0], [bp+1]
  1219.                 jnz [bp+1], loop_start
  1220.                 halt
  1221.  
  1222.                 data_start:
  1223.                 di 65535
  1224.             )ASM"};
  1225.     c.assert_memory({109, 10, 22107, 0, 0, 1, 1205, 1, 2, 99, 65535});
  1226.   }
  1227.   {
  1228.     asm_test_case c{R"ASM(
  1229.                 jz value:16, end
  1230.                 add -1, [value], [value]
  1231.                 end: halt
  1232.             )ASM"};
  1233.     c.assert_memory({1106, 16, 7, 101, -1, 1, 1, 99});
  1234.   }
  1235.   {
  1236.     asm_test_case c{R"ASM(
  1237.                 inp [55]
  1238.                 out [55]
  1239.                 halt
  1240.             )ASM"};
  1241.     c.assert_memory({3, 55, 4, 55, 99});
  1242.   }
  1243.   {
  1244.     asm_test_case c{R"ASM(
  1245.                 halt
  1246.                 standalone_label:
  1247.                 di 0
  1248.                 line_label: di 1
  1249.                 di argument_label:2
  1250.                 di argument_label
  1251.                 di line_label
  1252.                 di standalone_label
  1253.             )ASM"};
  1254.     c.assert_memory({99, 0, 1, 2, 3, 2, 1});
  1255.   }
  1256. }
  1257. } // namespace self_test
  1258. #endif
  1259.  
  1260. struct memory_dumper {
  1261.   virtual ~memory_dumper() = default;
  1262.   virtual void dump_memory(intcode::memory const &) = 0;
  1263. };
  1264.  
  1265. class file_memory_dumper : public memory_dumper {
  1266. public:
  1267.   explicit file_memory_dumper(std::string filename)
  1268.       : filename{std::move(filename)} {}
  1269.  
  1270.   void dump_memory(intcode::memory const &mem) override {
  1271.     std::ofstream file{filename};
  1272.     if (!file.is_open())
  1273.       throw std::runtime_error{"Failed to open " + filename};
  1274.     intcode::util::join_to_stream(file, mem, ",");
  1275.     file.close();
  1276.   }
  1277.  
  1278. private:
  1279.   std::string filename;
  1280. };
  1281.  
  1282. class cout_memory_dumper : public memory_dumper {
  1283. public:
  1284.   void dump_memory(intcode::memory const &mem) override {
  1285.     intcode::util::join_to_stream(std::cout, mem, ",");
  1286.   }
  1287. };
  1288.  
  1289. struct options_error : std::runtime_error {
  1290.   using runtime_error::runtime_error;
  1291. };
  1292.  
  1293. struct options final {
  1294.   std::shared_ptr<intcode::io::input_pipe> program_input;
  1295.   std::shared_ptr<intcode::io::output_pipe> program_output;
  1296.   intcode::memory program_memory;
  1297.   bool execute;
  1298.   bool disassemble;
  1299.   std::optional<std::size_t> memory_limit;
  1300.   std::shared_ptr<memory_dumper> mem_dumper;
  1301. };
  1302.  
  1303. void real_main(options const &options) {
  1304.   intcode::virtual_machine vm{std::move(options).program_memory};
  1305.   vm.set_memory_limit(options.memory_limit);
  1306.   vm.set_input_device(options.program_input);
  1307.   vm.set_output_device(options.program_output);
  1308.  
  1309.   while (true) {
  1310.     if (options.execute) {
  1311.       if (options.disassemble) {
  1312.         if (!intcode::executor::execute_and_disassemble_next(vm, std::cerr))
  1313.           break;
  1314.         std::cerr << std::flush;
  1315.       } else {
  1316.         if (!intcode::executor::execute_next(vm))
  1317.           break;
  1318.       }
  1319.     } else if (options.disassemble) {
  1320.       if (!intcode::executor::disassemble_next(vm, std::cerr))
  1321.         break;
  1322.       std::cerr << std::flush;
  1323.     } else {
  1324.       break;
  1325.     }
  1326.   }
  1327.  
  1328.   if (options.mem_dumper) {
  1329.     options.mem_dumper->dump_memory(vm.get_memory());
  1330.   }
  1331. }
  1332.  
  1333. template <
  1334.     typename Container,
  1335.     std::enable_if_t<std::is_convertible_v<intcode::memory_cell,
  1336.                                            typename Container::value_type>,
  1337.                      int> = 0>
  1338. void process_input_option(Container &mem, std::string_view option) {
  1339.   auto [selector, value] = intcode::util::split_once(option, ':');
  1340.   if ("stdin" == selector) {
  1341.     if (value)
  1342.       throw options_error{"'stdin' input does not require an argument"};
  1343.     intcode::parser::parse_int_list_into(mem,
  1344.                                          intcode::util::read_stream(std::cin));
  1345.   } else if ("file" == selector) {
  1346.     if (!value)
  1347.       throw options_error{"'file' input requires a filename"};
  1348.     std::ifstream stream{static_cast<std::string>(*value)};
  1349.     if (!stream.is_open())
  1350.       throw std::runtime_error{"Failed to open file: " +
  1351.                                static_cast<std::string>(*value)};
  1352.     intcode::parser::parse_int_list_into(mem,
  1353.                                          intcode::util::read_stream(stream));
  1354.   } else if ("literal" == selector) {
  1355.     if (!value)
  1356.       throw options_error{"'literal' input requires the literal input"};
  1357.     intcode::parser::parse_int_list_into(mem, *value);
  1358.   } else if ("asm" == selector) {
  1359.     intcode::assembler assembler;
  1360.     intcode::memory temp_mem;
  1361.     if (value) {
  1362.       std::ifstream stream{static_cast<std::string>(*value)};
  1363.       if (!stream.is_open())
  1364.         throw std::runtime_error{"Failed to open file: " +
  1365.                                  static_cast<std::string>(*value)};
  1366.       temp_mem = assembler.parse_assembly(stream);
  1367.     } else {
  1368.       temp_mem = assembler.parse_assembly(std::cin);
  1369.     }
  1370.     std::copy(temp_mem.begin(), temp_mem.end(), std::back_inserter(mem));
  1371.   } else if ("sizeof" == selector) {
  1372.     if (!value)
  1373.       throw options_error{"'sizeof' input requires input specification"};
  1374.     intcode::memory temp_mem;
  1375.     process_input_option(temp_mem, *value);
  1376.     mem.push_back(static_cast<intcode::memory_cell>(temp_mem.size()));
  1377.   } else if ("sizeprefix" == selector) {
  1378.     if (!value)
  1379.       throw options_error{"'sizeprefix' input requires input specification"};
  1380.     intcode::memory temp_mem;
  1381.     process_input_option(temp_mem, *value);
  1382.     mem.push_back(static_cast<intcode::memory_cell>(temp_mem.size()));
  1383.     std::copy(temp_mem.begin(), temp_mem.end(), std::back_inserter(mem));
  1384.   } else {
  1385.     throw options_error{"Invalid source specification: " +
  1386.                         static_cast<std::string>(selector)};
  1387.   }
  1388. }
  1389.  
  1390. int main(int argc, char **argv) {
  1391. #ifdef SELF_TEST
  1392.   std::cerr << "Running self-test ... " << std::flush;
  1393.   self_test::run();
  1394.   std::cerr << "Passed.\n";
  1395.   return 0;
  1396. #endif
  1397.  
  1398.   TCLAP::CmdLine cmd{
  1399.       "Advent of Code 2019 Intcode virtual machine and assembler"};
  1400.   TCLAP::MultiArg<std::string> source_arg{
  1401.       "s",
  1402.       "source",
  1403.       "Source to read the memory of the virtual intcode machine from."
  1404.       " The argument can be one of stdin, file:<filename>, literal:<int-list>,"
  1405.       " asm, or asm:<filename>."
  1406.       " stdin, file and literal read a comma-separated list of integers from"
  1407.       " standard input, a file, or directly from the command line, "
  1408.       "respectively."
  1409.       " asm reads assembly source code from either standard input (if no "
  1410.       "filename"
  1411.       " is supplied), or a file."
  1412.       " Additionally, the argument may be prefixed with sizeprefix: or sizeof:."
  1413.       " sizeprefix prepends the number of elements to the list, sizeof replaces"
  1414.       " the list with a single element that is the number of elements in the "
  1415.       "original"
  1416.       " list."
  1417.       " This option may be repeated, and all sources will be concatenated.",
  1418.       true,
  1419.       "source_specification",
  1420.       cmd};
  1421.   TCLAP::MultiArg<std::string> input_arg{
  1422.       "i",
  1423.       "input",
  1424.       "Source to read the program input from. The argument to this option "
  1425.       "supports"
  1426.       " the same specifiers as the --source option, including the asm type."
  1427.       " If no input option is specified, or after the program has exhausted "
  1428.       "the input(s)"
  1429.       " specified by this option, input is read from STDIN as"
  1430.       " whitespace-separated integers, as the program executes."
  1431.       " This is not the same as specifying an 'stdin' input, which expects"
  1432.       " comma-separated integers and reads the entire input before execution "
  1433.       "starts.",
  1434.       false,
  1435.       "input_specification",
  1436.       cmd};
  1437.   TCLAP::ValueArg<std::string> output_arg{
  1438.       "o",
  1439.       "output",
  1440.       "File to write the program output to, as a comma-separated list of "
  1441.       "integers."
  1442.       " If no output option is specified, the program output is written to "
  1443.       "STDOUT"
  1444.       " as whitespace-separated integers as the program executes. If this "
  1445.       "option"
  1446.       " is specified with a special value of '-' (a single dash), the program "
  1447.       "output"
  1448.       " is written to standard output, but comma-separated as it would be with "
  1449.       "a file.",
  1450.       false,
  1451.       "",
  1452.       "filename",
  1453.       cmd};
  1454.   TCLAP::ValueArg<std::string> dump_arg{
  1455.       "m",
  1456.       "dump",
  1457.       "File to write memory dump to, as a comma-separated list of integers."
  1458.       " If execution is enabled, the dump is written after the program has"
  1459.       " halted."
  1460.       " If the argument is '-' (a single dash), the dump is written to standard"
  1461.       " output.",
  1462.       false,
  1463.       "",
  1464.       "filename",
  1465.       cmd};
  1466.   TCLAP::SwitchArg execute_arg{"x", "execute", "Execute code", cmd};
  1467.   TCLAP::SwitchArg disassemble_arg{
  1468.       "d", "disassemble",
  1469.       "Show disassembly, affected by self-modifying code if also executing",
  1470.       cmd};
  1471.   TCLAP::SwitchArg unlimited_memory_arg{
  1472.       "u", "unlimited-memory",
  1473.       "Remove the default memory limit. This allows the VM to consume an "
  1474.       "unlimited amount of memory."
  1475.       " Use with care.",
  1476.       cmd};
  1477.   TCLAP::ValueArg<std::size_t> memory_limit_arg{
  1478.       "l",
  1479.       "memory-limit",
  1480.       "Specify the VM memory limit. The default is 256M.",
  1481.       false,
  1482.       256,
  1483.       "megabytes",
  1484.       cmd};
  1485.  
  1486.   try {
  1487.  
  1488.     cmd.parse(argc, argv);
  1489.  
  1490.     options options;
  1491.  
  1492.     options.execute = execute_arg.getValue();
  1493.     options.disassemble = disassemble_arg.getValue();
  1494.  
  1495.     if (input_arg.isSet()) {
  1496.       std::deque<intcode::memory_cell> fixed_input;
  1497.       for (std::string const &inputspec : input_arg.getValue()) {
  1498.         process_input_option(fixed_input, inputspec);
  1499.       }
  1500.       options.program_input =
  1501.           std::make_shared<intcode::io::combining_input_pipe>(
  1502.               std::vector<std::shared_ptr<intcode::io::input_pipe>>{
  1503.                   std::make_shared<intcode::io::deque_pipe>(
  1504.                       std::move(fixed_input)),
  1505.                   std::make_shared<intcode::io::cin_pipe>()});
  1506.     } else {
  1507.       options.program_input = std::make_shared<intcode::io::cin_pipe>();
  1508.     }
  1509.  
  1510.     for (std::string const &inputspec : source_arg.getValue()) {
  1511.       process_input_option(options.program_memory, inputspec);
  1512.     }
  1513.  
  1514.     if (dump_arg.isSet()) {
  1515.       std::string const arg = dump_arg.getValue();
  1516.       if ("-" == arg) {
  1517.         options.mem_dumper = std::make_shared<cout_memory_dumper>();
  1518.       } else {
  1519.         options.mem_dumper = std::make_shared<file_memory_dumper>(arg);
  1520.         options.program_output = std::make_shared<intcode::io::cout_pipe>();
  1521.       }
  1522.     } else {
  1523.       options.program_output = std::make_shared<intcode::io::cout_pipe>();
  1524.     }
  1525.  
  1526.     if (output_arg.isSet()) {
  1527.       std::string const arg = output_arg.getValue();
  1528.       if ("-" == arg)
  1529.         options.program_output =
  1530.             std::make_shared<intcode::io::cout_output_pipe>();
  1531.       else
  1532.         options.program_output =
  1533.             std::make_shared<intcode::io::file_output_pipe>(arg);
  1534.     }
  1535.  
  1536.     if (unlimited_memory_arg.getValue())
  1537.       options.memory_limit = std::nullopt;
  1538.     else
  1539.       options.memory_limit =
  1540.           (memory_limit_arg.getValue() << 20) / sizeof(intcode::memory_cell);
  1541.  
  1542.     real_main(options);
  1543.  
  1544.   } catch (std::exception const &e) {
  1545.     std::cerr << "FATAL: " << e.what() << '\n';
  1546.     return EXIT_FAILURE;
  1547.   }
  1548. }
Advertisement
Add Comment
Please, Sign In to add comment