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 : }