|
| 1 | +/** |
| 2 | + * @file |
| 3 | + * @author Alex Singer |
| 4 | + * @date June 2025 |
| 5 | + * @brief Implementation of utility functions used for parsing AP arguments. |
| 6 | + */ |
| 7 | +#include "ap_argparse_utils.h" |
| 8 | +#include <regex> |
| 9 | +#include <string> |
| 10 | +#include <unordered_map> |
| 11 | +#include <vector> |
| 12 | +#include "vtr_assert.h" |
| 13 | +#include "vtr_log.h" |
| 14 | +#include "vpr_error.h" |
| 15 | + |
| 16 | +/** |
| 17 | + * @brief Helper method to convert a string into a float with error checking. |
| 18 | + */ |
| 19 | +static float str_to_float_or_error(const std::string& str); |
| 20 | + |
| 21 | +/** |
| 22 | + * @brief Parse the given key, value string argument. The string is expected to |
| 23 | + * be of the form: |
| 24 | + * "<key_regex>:<val1>,<val2>,<val3>" |
| 25 | + * |
| 26 | + * This method returns a tuple containing the regex string and a vector of the |
| 27 | + * values. The vector will be of the length expected_num_vals_per_key. |
| 28 | + */ |
| 29 | +static std::tuple<std::string, std::vector<float>> |
| 30 | +parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key); |
| 31 | + |
| 32 | +std::unordered_map<std::string, std::vector<float>> |
| 33 | +key_to_float_argument_parser(const std::vector<std::string>& arg_vals, |
| 34 | + const std::vector<std::string>& valid_keys, |
| 35 | + unsigned expected_num_vals_per_key) { |
| 36 | + |
| 37 | + // Create the key to float map which will be returned from this method. |
| 38 | + std::unordered_map<std::string, std::vector<float>> key_to_float_map; |
| 39 | + |
| 40 | + // Go through each of the arguments to parse. |
| 41 | + for (const std::string& arg_val : arg_vals) { |
| 42 | + // Parse this argument. |
| 43 | + // Key is the regex string, vals is the vector of values. |
| 44 | + auto [key, vals] = parse_key_val_arg(arg_val, expected_num_vals_per_key); |
| 45 | + |
| 46 | + // Create a regex object to be used to match for valid keys. |
| 47 | + std::regex key_regex(key); |
| 48 | + |
| 49 | + // Go through each valid key and find which ones match the regex. |
| 50 | + bool found_match = false; |
| 51 | + for (const std::string& valid_key : valid_keys) { |
| 52 | + bool is_match = std::regex_match(valid_key, key_regex); |
| 53 | + if (!is_match) |
| 54 | + continue; |
| 55 | + |
| 56 | + // If this key matches the regex, set the map to the given values. |
| 57 | + key_to_float_map[valid_key] = vals; |
| 58 | + found_match = true; |
| 59 | + } |
| 60 | + |
| 61 | + // If no match is found for this key regex, raise a warning to the user. |
| 62 | + // They may have made a mistake and may want to be warned about it. |
| 63 | + if (!found_match) { |
| 64 | + VTR_LOG_WARN("Unable to find a valid key that matches regex pattern: %s\n", |
| 65 | + key.c_str()); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + // Return the map. |
| 70 | + return key_to_float_map; |
| 71 | +} |
| 72 | + |
| 73 | +static std::tuple<std::string, std::vector<float>> |
| 74 | +parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key) { |
| 75 | + // Verify the format of the string. It must have one and only one colon. |
| 76 | + unsigned colon_count = 0; |
| 77 | + for (char c : arg) { |
| 78 | + if (c == ':') |
| 79 | + colon_count++; |
| 80 | + } |
| 81 | + if (colon_count != 1) { |
| 82 | + VTR_LOG_ERROR("Invalid argument string: %s\n", |
| 83 | + arg.c_str()); |
| 84 | + VPR_FATAL_ERROR(VPR_ERROR_PACK, |
| 85 | + "Error when parsing argument string"); |
| 86 | + } |
| 87 | + |
| 88 | + // Split the string along the colon. |
| 89 | + auto del_pos = arg.find(':'); |
| 90 | + std::string key_regex_str = arg.substr(0, del_pos); |
| 91 | + std::string val_list_str = arg.substr(del_pos + 1, std::string::npos); |
| 92 | + |
| 93 | + // Verify that there are a correct number of commas given the expected number |
| 94 | + // of values. |
| 95 | + unsigned comma_count = 0; |
| 96 | + for (char c : val_list_str) { |
| 97 | + if (c == ',') |
| 98 | + comma_count++; |
| 99 | + } |
| 100 | + if (comma_count != expected_num_vals_per_key - 1) { |
| 101 | + VTR_LOG_ERROR("Invalid argument string (too many commas): %s\n", |
| 102 | + arg.c_str()); |
| 103 | + VPR_FATAL_ERROR(VPR_ERROR_PACK, |
| 104 | + "Error when parsing argument string"); |
| 105 | + } |
| 106 | + |
| 107 | + // Collect the comma seperated values into a vector. |
| 108 | + std::vector<float> vals; |
| 109 | + vals.reserve(expected_num_vals_per_key); |
| 110 | + |
| 111 | + // As we are reading each comma-seperated value, keep track of the current |
| 112 | + // part of the string we are reading. We read from left to right. |
| 113 | + std::string acc_val_list_str = val_list_str; |
| 114 | + |
| 115 | + // For each expected value up to the last one, parse the current value before |
| 116 | + // the comma. |
| 117 | + VTR_ASSERT(expected_num_vals_per_key > 0); |
| 118 | + for (unsigned i = 0; i < expected_num_vals_per_key - 1; i++) { |
| 119 | + // Split the string before and after the comma. |
| 120 | + auto comma_pos = acc_val_list_str.find(","); |
| 121 | + VTR_ASSERT(comma_pos != std::string::npos); |
| 122 | + std::string current_val_str = val_list_str.substr(0, comma_pos); |
| 123 | + // Send the string after the comma to the next iteration. |
| 124 | + acc_val_list_str = val_list_str.substr(comma_pos + 1, std::string::npos); |
| 125 | + |
| 126 | + // Cast the string before the comma into a float and store it. |
| 127 | + float current_val = str_to_float_or_error(current_val_str); |
| 128 | + vals.push_back(current_val); |
| 129 | + } |
| 130 | + |
| 131 | + // Parse the last value in the list. This one should not have a comma in it. |
| 132 | + VTR_ASSERT(acc_val_list_str.find(",") == std::string::npos); |
| 133 | + float last_val = str_to_float_or_error(acc_val_list_str); |
| 134 | + vals.push_back(last_val); |
| 135 | + |
| 136 | + // Return the results as a tuple. |
| 137 | + return std::make_tuple(key_regex_str, vals); |
| 138 | +} |
| 139 | + |
| 140 | +static float str_to_float_or_error(const std::string& str) { |
| 141 | + float val = -1; |
| 142 | + try { |
| 143 | + val = std::stof(str); |
| 144 | + } catch (const std::invalid_argument& e) { |
| 145 | + VTR_LOG_ERROR("Error while parsing float arg value: %s\n" |
| 146 | + "Failed with invalid argument: %s\n", |
| 147 | + str.c_str(), |
| 148 | + e.what()); |
| 149 | + } catch (const std::out_of_range& e) { |
| 150 | + VTR_LOG_ERROR("Error while parsing float arg value: %s\n" |
| 151 | + "Failed with out of range: %s\n", |
| 152 | + str.c_str(), |
| 153 | + e.what()); |
| 154 | + } |
| 155 | + return val; |
| 156 | +} |
0 commit comments