* @file
*
* This file implements the OptionTable related classes.
*/
#include "cangjie/Option/OptionTable.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <vector>
#include "cangjie/Basic/Print.h"
namespace {
#define BACKEND(backend) Cangjie::Options::Backend::backend
#define GROUP(group) Cangjie::Options::Group::group
#define PREDEFVALUE(NAME, VALUE) std::vector<Cangjie::Options::OptionValue> NAME = VALUE
#define VALUE(VALUE, HELPTEXT, BACKENDS, GROUPS) \
Cangjie::Options::OptionValue{std::string(VALUE), std::string(HELPTEXT), BACKENDS, GROUPS}
#include "cangjie/Option/Options.inc"
#undef VALUE
#undef PREDEFVALUE
const std::vector<Cangjie::OptionTable::OptionInfo> INFO_LIST = {
#define OPTION(NAME, id, KIND, BACKENDS, GROUPS, ALIAS, FLAGS, OCCURRENCE, HELPTEXT) \
{NAME, static_cast<uint16_t>(Cangjie::Options::ID::id), Cangjie::Options::Kind::KIND, BACKENDS, GROUPS, ALIAS, \
FLAGS, Cangjie::Options::Occurrence::OCCURRENCE, HELPTEXT},
#include "cangjie/Option/Options.inc"
#undef OPTION
};
#undef GROUP
#undef BACKEND
}
using namespace Cangjie;
using namespace Cangjie::Options;
static bool CheckValueInFlags(const std::string& value, const OptionTable::OptionInfo& info)
{
auto optionValues = info.GetOptionValues();
if (!optionValues.empty()) {
return std::any_of(optionValues.begin(), optionValues.end(), [value](auto item) {
return item.value == value;
});
}
return true;
}
std::pair<std::string, std::optional<std::string>> OptionTable::ParseArgumentName(const std::string& argStr) const
{
size_t pos = argStr.find_first_of('=');
std::string argName;
if (pos != std::string::npos) {
argName = argStr.substr(0, pos);
return std::make_pair(argName, std::make_optional(argStr.substr(pos + 1)));
}
argName = argStr;
return std::make_pair(argName, std::nullopt);
}
bool OptionTable::ShouldArgumentBeRecognized(
const std::string& argName, bool hasValue, const OptionTable::OptionInfo& i)
{
#ifdef CANGJIE_VISIBLE_OPTIONS_ONLY
if (std::count(i.groups.begin(), i.groups.end(), Options::Group::VISIBLE) == 0) {
return false;
};
#endif
if (argName != i.name && argName != (i.alias ? i.alias : "")) {
return false;
}
if (!BelongsTo(enabledBackends, i.backends, Options::Backend::ALL) ||
!BelongsTo(enabledGroups, i.groups, Options::Group::GLOBAL)) {
return false;
}
if (i.kind == Kind::FLAG_WITH_ARG && hasValue) {
return true;
}
if (i.kind != Kind::SEPARATED && hasValue) {
return false;
}
return true;
}
std::optional<OptionArgInstance> OptionTable::ParseOptionArg(const std::string& argStr)
{
std::string argName;
std::optional<std::string> maybeValue;
std::tie(argName, maybeValue) = ParseArgumentName(argStr);
auto result = std::find_if(INFO_LIST.begin(), INFO_LIST.end(), [&argName, hasValue = maybeValue.has_value(), this]
(const OptionTable::OptionInfo& i) { return ShouldArgumentBeRecognized(argName, hasValue, i); });
auto value = maybeValue.value_or("");
bool isSeparatedOptionWithoutSpace = false;
if (result == std::end(INFO_LIST)) {
result = std::find_if(INFO_LIST.begin(), INFO_LIST.end(), [&argStr, this] (const OptionTable::OptionInfo& i) {
#ifdef CANGJIE_VISIBLE_OPTIONS_ONLY
if (std::count(i.groups.begin(), i.groups.end(), Options::Group::VISIBLE) == 0) {
return false;
};
#endif
auto shouldArgumentBeRecognized = BelongsTo(enabledBackends, i.backends, Options::Backend::ALL) &&
BelongsTo(enabledGroups, i.groups, Options::Group::GLOBAL);
return shouldArgumentBeRecognized && ((i.kind == Kind::SEPARATED &&
i.alias && argStr.find(i.alias, 0) == 0) ||
(i.kind == Kind::CONTINOUS && argStr.find(i.name, 0) == 0));
});
if (result != std::end(INFO_LIST)) {
isSeparatedOptionWithoutSpace = true;
argName = result->kind == Kind::SEPARATED ? result->alias : result->name;
value = argStr.substr(argName.size());
}
}
if (result == std::end(INFO_LIST)) {
Errorf("invalid option: '%s'.\n", argName.c_str());
return std::nullopt;
}
OptionArgInstance arg = OptionArgInstance(*result, argName);
arg.str = argStr;
arg.value = value;
arg.hasJoinedValue = maybeValue.has_value();
if (!frontendMode && arg.info.BelongsGroup(Options::Group::FRONTEND)) {
Errorf("invalid option: '%s'.\n", argName.c_str());
return std::nullopt;
}
return SetArgValue(arg, value, isSeparatedOptionWithoutSpace);
}
std::optional<OptionArgInstance> OptionTable::SetArgValue(
OptionArgInstance& arg, const std::string& value, bool isSeparatedOptionWithoutSpace) const
{
if (arg.hasJoinedValue || arg.info.GetKind() == Kind::CONTINOUS) {
if (value.empty() && arg.info.GetID() != Options::ID::LTO_KEEP_PKG_VISIBILITY) {
Warningf("option '%s' requires some values, format: option=value or option=\"v1, v2...\"\n",
arg.name.c_str());
}
if (!CheckValueInFlags(value, arg.info)) {
Errorf("invalid value: '%s'\n", value.c_str());
return std::nullopt;
}
arg.value = value;
} else if (isSeparatedOptionWithoutSpace) {
if (!CheckValueInFlags(value, arg.info)) {
Errorf("invalid value: '%s'\n", value.c_str());
return std::nullopt;
}
arg.value = value;
}
bool partialParsed =
!arg.hasJoinedValue && arg.info.GetKind() == Options::Kind::SEPARATED && !isSeparatedOptionWithoutSpace;
arg.argType = partialParsed ? ArgType::PartiallyParsed : ArgType::FullyParsed;
return {arg};
}
bool OptionTable::ParseShortTermArgs(const std::vector<std::string>& argsStrs, size_t& idx,
ArgList& argList)
{
auto isSpace = [](bool res, const char c) { return res && isspace(c); };
const auto& argStr = argsStrs[idx];
if (argStr.empty() || std::accumulate(argStr.begin(), argStr.end(), true, isSpace)) {
return true;
}
if (argStr[0] != '-') {
argList.args.emplace_back(std::make_unique<InputArgInstance>(argStr));
return true;
}
if (argStr.size() <= 1) {
Errorln("invalid option: '-'.");
return false;
}
std::optional<OptionArgInstance> maybeArg = ParseOptionArg(argStr);
if (!maybeArg) {
return false;
}
auto arg = maybeArg.value();
if (arg.argType == ArgType::PartiallyParsed && !ParseSeparatedArgValue(arg, argsStrs, idx)) {
return false;
}
argList.args.emplace_back(std::make_unique<OptionArgInstance>(arg));
return true;
}
bool OptionTable::ParseSeparatedArgValue(
OptionArgInstance& arg, const std::vector<std::string>& argsStrs, size_t& idx) const
{
idx++;
if (idx >= argsStrs.size()) {
Errorf("this Option needs a value: '%s'\n", arg.name.c_str());
return false;
}
if (CheckValueInFlags(argsStrs[idx], arg.info)) {
arg.value = {argsStrs[idx]};
arg.str = arg.name + " " + argsStrs[idx];
return true;
}
Errorf("invalid value: '%s'\n", argsStrs[idx].c_str());
return false;
}
bool OptionTable::ParseArgs(const std::vector<std::string>& argsStrs, ArgList& argList)
{
if (argsStrs.size() <= 1) {
return true;
}
size_t idx{1};
size_t len = argsStrs.size();
while (idx < len) {
if (!ParseShortTermArgs(argsStrs, idx, argList)) {
return false;
}
idx++;
}
return true;
}
void OptionTable::PrintInfo(const OptionInfo& info, Options::Backend backend, bool showExperimental) const
{
std::string name(info.name);
if (info.alias != nullptr) {
name = std::string(info.alias) + ", " + name;
}
if (info.kind == Options::Kind::SEPARATED) {
name = name + " <value>";
} else if (info.kind == Options::Kind::CONTINOUS) {
name = name + "<value>";
}
std::string desc(info.help);
if (std::count(info.groups.begin(), info.groups.end(), Options::Group::STABLE) == 0) {
desc += " (Experimental)";
}
if (name.length() < OPTION_WIDTH) {
PrintCommandDesc(name, desc, OPTION_WIDTH);
} else {
PrintCommandDesc(name, "", OPTION_WIDTH);
PrintCommandDesc("", desc, OPTION_WIDTH);
}
for (const Options::OptionValue& optionValue : info.values) {
#if defined(CANGJIE_VISIBLE_OPTIONS_ONLY)
if (std::count(optionValue.groups.begin(), optionValue.groups.end(), Options::Group::VISIBLE) == 0) {
continue;
}
#endif
if (std::count(optionValue.backends.begin(), optionValue.backends.end(), Options::Backend::ALL) == 0 &&
std::count(optionValue.backends.begin(), optionValue.backends.end(), backend) == 0) {
continue;
}
if (std::count(optionValue.groups.begin(), optionValue.groups.end(), Options::Group::STABLE) == 0 &&
!showExperimental) {
continue;
}
std::string value = " <value>=" + optionValue.value;
std::string help = " " + optionValue.help;
if (std::count(optionValue.groups.begin(), optionValue.groups.end(), Options::Group::STABLE) == 0) {
help += " (Experimental)";
}
PrintCommandDesc(value, help, OPTION_WIDTH);
}
}
void OptionTable::Usage(Options::Backend backend, std::set<Options::Group> groups, bool showExperimental)
{
if (optionInfos.empty()) {
return;
}
const std::string usage = "Usage:";
Println(usage);
PrintIndentOnly(static_cast<unsigned int>(usage.size()), 1);
if (groups.find(Options::Group::FRONTEND) != groups.end()) {
Println("cjc-frontend [option] file...");
} else {
Println("cjc [option] file...");
}
Println();
Println("Options:");
for (const auto& info : optionInfos) {
#if defined(CANGJIE_VISIBLE_OPTIONS_ONLY)
bool doPrint = std::count(info.groups.begin(), info.groups.end(), Options::Group::VISIBLE) != 0;
#else
bool doPrint = true;
#endif
if (doPrint && info.BelongsToAnyOfGroup(groups) && info.BelongsToBackend(backend)) {
PrintInfo(info, backend, showExperimental);
}
}
}
namespace Cangjie {
std::unique_ptr<OptionTable> CreateOptionTable(bool frontendMode)
{
return std::make_unique<OptionTable>(INFO_LIST, frontendMode);
}
}