blob: efddb92771f709e8deaab111e4b4842026446580 [file] [log] [blame]
// 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