LCOV - code coverage report
Current view: top level - src/utility - argument_parser.cpp Hit Total Coverage
Test: Malbolge Unit Test Code Coverage Lines: 79 79 100.0 %
Date: 2021-02-03 17:18:54
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Cam Mannett 2020
       2             :  *
       3             :  * See LICENSE file
       4             :  */
       5             : 
       6             : #include "malbolge/utility/argument_parser.hpp"
       7             : #include "malbolge/utility/string_view_ops.hpp"
       8             : #include "malbolge/algorithm/container_ops.hpp"
       9             : #include "malbolge/exception.hpp"
      10             : #include "malbolge/version.hpp"
      11             : 
      12             : #include <deque>
      13             : 
      14             : using namespace malbolge;
      15             : using namespace utility::string_view_ops;
      16             : using namespace std::literals::string_view_literals;
      17             : using namespace std::string_literals;
      18             : 
      19             : namespace
      20             : {
      21             : constexpr auto help_flags           = std::array{"--help", "-h"};
      22             : constexpr auto version_flags        = std::array{"--version", "-v"};
      23             : constexpr auto log_flag_prefix      = "-l";
      24             : constexpr auto string_flag          = "--string";
      25             : constexpr auto debugger_script_flag = "--debugger-script";
      26             : constexpr auto force_nn_flag        = "--force-non-normalised";
      27             : }
      28             : 
      29          28 : argument_parser::argument_parser(int argc, char* argv[]) :
      30             :     help_{false},
      31             :     version_{false},
      32             :     p_{program_source::STDIN, ""},
      33             :     log_level_{log::ERROR},
      34          80 :     force_nn_{false}
      35             : {
      36             :     // Convert to string_views, they're easier to work with
      37          40 :     auto args = std::deque<std::string_view>(argc-1);
      38          28 :     std::copy(argv+1, argv+argc, args.begin());
      39             : 
      40             :     // Help
      41          28 :     if (algorithm::any_of_container(args, help_flags)) {
      42           4 :         if (args.size() != 1) {
      43           2 :             throw system_exception{"Help flag must be unique",
      44           4 :                                    std::errc::invalid_argument};
      45             :         }
      46             : 
      47           2 :         help_ = true;
      48           2 :         return;
      49             :     }
      50             : 
      51             :     // Application version
      52          24 :     if (algorithm::any_of_container(args, version_flags)) {
      53           3 :         if (args.size() != 1) {
      54           1 :             throw system_exception{"Version flag must be unique",
      55           2 :                                    std::errc::invalid_argument};
      56             :         }
      57             : 
      58           2 :         version_ = true;
      59           2 :         return;
      60             :     }
      61             : 
      62             :     // Force non-normalised
      63          21 :     auto force_nn_it = std::find(args.begin(), args.end(), force_nn_flag);
      64          21 :     if (force_nn_it != args.end()) {
      65           1 :         force_nn_ = true;
      66           1 :         args.erase(force_nn_it);
      67             :     }
      68             : 
      69          21 :     auto string_it = std::find(args.begin(), args.end(), string_flag);
      70          21 :     if (string_it != args.end()) {
      71             :         // Move the iterator forward one to extract the program data
      72           5 :         if (++string_it == args.end()) {
      73           1 :             throw system_exception{
      74             :                 "String flag set but no program source present",
      75             :                 std::errc::invalid_argument
      76           2 :             };
      77             :         }
      78             : 
      79           4 :         p_.source = program_source::STRING;
      80           4 :         p_.data = std::move(*string_it);
      81             : 
      82           4 :         string_it = args.erase(--string_it);
      83           4 :         args.erase(string_it);
      84             :     }
      85             : 
      86          20 :     auto debugger_script_it = std::find(args.begin(), args.end(), debugger_script_flag);
      87          20 :     if (debugger_script_it != args.end()) {
      88             :         // Move the iterator forward one to extract the script path
      89           3 :         if (++debugger_script_it == args.end()) {
      90           1 :             throw system_exception{
      91             :                 "String flag set but no program source present",
      92             :                 std::errc::invalid_argument
      93           2 :             };
      94             :         }
      95             : 
      96           2 :         debugger_script_ = *debugger_script_it;
      97             : 
      98           2 :         debugger_script_it = args.erase(--debugger_script_it);
      99           2 :         args.erase(debugger_script_it);
     100             :     }
     101             : 
     102             :     // Log level
     103          19 :     if (args.size() && args.front().starts_with(log_flag_prefix)) {
     104             :         // There must only be 'l's
     105           7 :         const auto level = static_cast<std::size_t>(std::count(args.front().begin(),
     106           7 :                                                                args.front().end(),
     107           7 :                                                                'l'));
     108           7 :         if (level == (args.front().size()-1)) {
     109           6 :             if (level >= log::NUM_LOG_LEVELS) {
     110           1 :                 throw system_exception{
     111           2 :                     "Maximum log level is "s + to_string(log::VERBOSE_DEBUG) +
     112           4 :                     " (" + std::to_string(log::NUM_LOG_LEVELS-1) + ")",
     113             :                     std::errc::invalid_argument
     114           2 :                 };
     115             :             }
     116             : 
     117           5 :             log_level_ = static_cast<log::level>(log::NUM_LOG_LEVELS - 1 - level);
     118           5 :             args.pop_front();
     119             :         }
     120             :     }
     121             : 
     122             :     // There should be no other flags here
     123             :     {
     124          18 :         auto it = std::find_if(args.begin(), args.end(), [](auto&& a) {
     125          11 :             return a.starts_with('-');
     126          18 :         });
     127          18 :         if (it != args.end()) {
     128           3 :             throw system_exception{"Unknown argument: "s + *it,
     129           6 :                                    std::errc::invalid_argument};
     130             :         }
     131             :     }
     132             : 
     133             :     // There should either be a path for a file to load, or nothing
     134          15 :     if (!args.empty()) {
     135             :         // Make sure the string flag hadn't already been set
     136           6 :         if (p_.source == program_source::STRING) {
     137           2 :             throw system_exception{"String flag already set",
     138           4 :                                    std::errc::invalid_argument};
     139             :         }
     140             : 
     141           4 :         p_.source = program_source::DISK;
     142           4 :         p_.data = std::move(args.front());
     143           4 :         args.pop_front();
     144             : 
     145           4 :         if (!args.empty()) {
     146           1 :             throw system_exception{"Unknown argument: "s + args.front(),
     147           2 :                                    std::errc::invalid_argument};
     148             :         }
     149             :     }
     150             : }
     151             : 
     152           4 : std::ostream& malbolge::operator<<(std::ostream& stream,
     153             :                                    const argument_parser::program_source& source)
     154             : {
     155             :     static_assert(static_cast<std::size_t>(argument_parser::program_source::MAX) == 3,
     156             :                   "program_source enum has changed size, update operator<<");
     157             : 
     158           4 :     switch (source) {
     159           1 :     case argument_parser::program_source::DISK:
     160           1 :         return stream << "DISK";
     161           1 :     case argument_parser::program_source::STDIN:
     162           1 :         return stream << "STDIN";
     163           1 :     case argument_parser::program_source::STRING:
     164           1 :         return stream << "STRING";
     165           1 :     default:
     166           1 :         return stream << "Unknown";
     167             :     }
     168             : }
     169             : 
     170           1 : std::ostream& malbolge::operator<<(std::ostream& stream, const argument_parser&)
     171             : {
     172             :     return stream << "Malbolge virtual machine v" << project_version
     173             :                   << "\nUsage:"
     174             :                      "\n\tmalbolge [options] <file>\n"
     175             :                      "\tcat <file> | malbolge [options]\n\n"
     176             :                      "Options:\n"
     177           2 :                   << "\t" << help_flags[1] << " " << help_flags[0]
     178             :                   << "\t\tDisplay this help message\n"
     179           2 :                   << "\t" << version_flags[1] << " " << version_flags[0]
     180             :                   << "\t\tDisplay the full application version\n"
     181             :                   << "\t" << log_flag_prefix
     182             :                   << "\t\t\tLog level, repeat the l character for higher logging levels\n"
     183             :                   << "\t" << string_flag
     184             :                   << "\t\tPass a string argument as the program to run\n"
     185             :                   << "\t" << debugger_script_flag
     186             :                   << "\tRun the given debugger script on the program\n"
     187             :                   << "\t" << force_nn_flag
     188           1 :                   << "\tOverride normalised program detection to force to non-normalised";
     189             : }

Generated by: LCOV version 1.14