Guest User

AoC2019 Intcode assembler and VM

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