| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/text/config_utils.h" | |
| #include <cstdint> | |
| #include <initializer_list> | |
| #include <limits> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/strip.h" | |
| #include "common/util/logging.h" | |
| namespace verible { | |
| using absl::string_view; | |
| using config::NVConfigSpec; | |
| // TODO(hzeller): consider using flex for a more readable tokenization that | |
| // can also much easier deal with whitespaces, strings etc. | |
| absl::Status ParseNameValues(string_view config_string, | |
| const std::initializer_list<NVConfigSpec>& spec) { | |
| if (config_string.empty()) return absl::OkStatus(); | |
| for (const string_view single_config : absl::StrSplit(config_string, ';')) { | |
| const std::pair<string_view, string_view> nv_pair = | |
| absl::StrSplit(single_config, ':'); | |
| const auto value_config = std::find_if( // linear search | |
| spec.begin(), spec.end(), | |
| [&nv_pair](const NVConfigSpec& s) { return nv_pair.first == s.name; }); | |
| if (value_config == spec.end()) { | |
| std::string available; | |
| for (const auto& s : spec) { | |
| if (!available.empty()) available.append(", "); | |
| available.append("'").append(s.name).append("'"); | |
| } | |
| const bool plural = spec.size() > 1; | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| nv_pair.first, ": unknown parameter; supported ", | |
| (plural ? "parameters are " : "parameter is "), available)); | |
| } | |
| if (!value_config->set_value) return absl::OkStatus(); // consume, not use. | |
| absl::Status result = value_config->set_value(nv_pair.second); | |
| if (!result.ok()) { | |
| // We always prepend the parameter name first in the message for | |
| // diagnostic usefulness of the error message. | |
| // Also, this way, the functor can just worry about parsing and does | |
| // not need to know the name of the value. | |
| return absl::InvalidArgumentError( | |
| absl::StrCat(nv_pair.first, ": ", result.message())); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| namespace config { | |
| ConfigValueSetter SetInt(int* value, int minimum, int maximum) { | |
| CHECK(value) << "Must provide pointer to integer to store."; | |
| return [value, minimum, maximum](string_view v) { | |
| int parsed_value; | |
| if (!absl::SimpleAtoi(v, &parsed_value)) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("'", v, "': Cannot parse integer")); | |
| } | |
| if (parsed_value < minimum || parsed_value > maximum) { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| parsed_value, " out of range [", minimum, "...", maximum, "]")); | |
| } | |
| *value = parsed_value; | |
| return absl::OkStatus(); | |
| }; | |
| } | |
| ConfigValueSetter SetInt(int* value) { | |
| return SetInt(value, std::numeric_limits<int>::min(), | |
| std::numeric_limits<int>::max()); | |
| } | |
| ConfigValueSetter SetBool(bool* value) { | |
| CHECK(value) << "Must provide pointer to boolean to store."; | |
| return [value](string_view v) { | |
| // clang-format off | |
| if (v.empty() || v == "1" | |
| || absl::EqualsIgnoreCase(v, "true") | |
| || absl::EqualsIgnoreCase(v, "on")) { | |
| *value = true; | |
| return absl::OkStatus(); | |
| } | |
| if (v == "0" | |
| || absl::EqualsIgnoreCase(v, "false") | |
| || absl::EqualsIgnoreCase(v, "off")) { | |
| *value = false; | |
| return absl::OkStatus(); | |
| } | |
| // clange-format on | |
| // We accept 1 and 0 silently to encourage somewhat | |
| // consistent use of values. | |
| return absl::InvalidArgumentError("Boolean value should be one of " | |
| "'true', 'on' or 'false', 'off'"); | |
| }; | |
| } | |
| // TODO(hzeller): For strings, consider to allow quoted strings that then also | |
| // allow C-Escapes. But that would require that ParseNameValues() can properly | |
| // deal with whitespace, which it might after converted to flex. | |
| ConfigValueSetter SetString(std::string* value) { | |
| return [value](string_view v) { | |
| value->assign(v.data(), v.length()); | |
| return absl::OkStatus(); | |
| }; | |
| } | |
| ConfigValueSetter SetStringOneOf(std::string* value, | |
| const std::vector<string_view>& allowed) { | |
| CHECK(value) << "Must provide pointer to string to store."; | |
| return [value, allowed](string_view v) { | |
| auto item = find(allowed.begin(), allowed.end(), v); | |
| if (item == allowed.end()) { | |
| // If the list only contains one element, provide a more suited | |
| // error message. | |
| if (allowed.size() == 1) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Value can only be '", allowed[0], "'; got '", | |
| v, "'")); | |
| } else { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Value can only be one of ['", | |
| absl::StrJoin(allowed, "', '"), | |
| "']; got '", v, "'")); | |
| } | |
| } | |
| value->assign(v.data(), v.length()); | |
| return absl::OkStatus(); | |
| }; | |
| } | |
| ConfigValueSetter SetNamedBits( | |
| uint32_t* value, | |
| const std::vector<absl::string_view>& choices) { | |
| CHECK(value) << "Must provide pointer to uint32_t to store."; | |
| CHECK_LE(choices.size(), 32) << "Too many choices for 32-bit bitmap"; | |
| return [value, choices](string_view v) { | |
| uint32_t result = 0; | |
| for (auto bitname : absl::StrSplit(v, '|', absl::SkipWhitespace())) { | |
| bitname = absl::StripAsciiWhitespace(bitname); | |
| const auto item_pos = find_if( | |
| choices.begin(), choices.end(), | |
| [bitname](string_view c) { | |
| return absl::EqualsIgnoreCase(bitname, c); | |
| }); | |
| if (item_pos == choices.end()) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("'", bitname, | |
| "' is not in the available choices {", | |
| absl::StrJoin(choices, ", "), "}")); | |
| } | |
| result |= (1 << (std::distance(choices.begin(), item_pos))); | |
| } | |
| // Parsed all bits successfully. | |
| *value = result; | |
| return absl::OkStatus(); | |
| }; | |
| } | |
| } // namespace config | |
| } // namespace verible |