2017-06-16 08:43:26 +08:00
|
|
|
//===- OptTable.cpp - Option Table Implementation -------------------------===//
|
2012-12-05 08:29:32 +08:00
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2012-12-05 08:29:32 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2016-08-12 06:21:41 +08:00
|
|
|
#include "llvm/ADT/STLExtras.h"
|
2017-06-16 08:43:26 +08:00
|
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
#include "llvm/ADT/StringSet.h"
|
2012-12-05 08:29:32 +08:00
|
|
|
#include "llvm/Option/Arg.h"
|
|
|
|
#include "llvm/Option/ArgList.h"
|
|
|
|
#include "llvm/Option/Option.h"
|
2017-06-16 08:43:26 +08:00
|
|
|
#include "llvm/Option/OptSpecifier.h"
|
|
|
|
#include "llvm/Option/OptTable.h"
|
|
|
|
#include "llvm/Support/Compiler.h"
|
2012-12-05 08:29:32 +08:00
|
|
|
#include "llvm/Support/ErrorHandling.h"
|
2013-01-02 18:22:59 +08:00
|
|
|
#include "llvm/Support/raw_ostream.h"
|
2012-12-05 08:29:32 +08:00
|
|
|
#include <algorithm>
|
2017-06-16 08:43:26 +08:00
|
|
|
#include <cassert>
|
2013-08-29 04:04:31 +08:00
|
|
|
#include <cctype>
|
2017-06-16 08:43:26 +08:00
|
|
|
#include <cstring>
|
2012-12-05 08:29:32 +08:00
|
|
|
#include <map>
|
2017-06-16 08:43:26 +08:00
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
using namespace llvm::opt;
|
|
|
|
|
2013-08-29 04:04:31 +08:00
|
|
|
namespace llvm {
|
|
|
|
namespace opt {
|
2012-12-05 08:29:32 +08:00
|
|
|
|
2013-08-29 04:04:31 +08:00
|
|
|
// Ordering on Info. The ordering is *almost* case-insensitive lexicographic,
|
2017-08-25 05:20:41 +08:00
|
|
|
// with an exception. '\0' comes at the end of the alphabet instead of the
|
2013-08-29 04:04:31 +08:00
|
|
|
// beginning (thus options precede any other options which prefix them).
|
|
|
|
static int StrCmpOptionNameIgnoreCase(const char *A, const char *B) {
|
|
|
|
const char *X = A, *Y = B;
|
|
|
|
char a = tolower(*A), b = tolower(*B);
|
2013-08-28 08:02:06 +08:00
|
|
|
while (a == b) {
|
|
|
|
if (a == '\0')
|
|
|
|
return 0;
|
|
|
|
|
2013-08-29 04:04:31 +08:00
|
|
|
a = tolower(*++X);
|
|
|
|
b = tolower(*++Y);
|
2013-08-28 08:02:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a == '\0') // A is a prefix of B.
|
|
|
|
return 1;
|
|
|
|
if (b == '\0') // B is a prefix of A.
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
// Otherwise lexicographic.
|
|
|
|
return (a < b) ? -1 : 1;
|
2013-08-28 07:47:01 +08:00
|
|
|
}
|
2012-12-05 08:29:32 +08:00
|
|
|
|
2013-09-11 07:22:56 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
static int StrCmpOptionName(const char *A, const char *B) {
|
|
|
|
if (int N = StrCmpOptionNameIgnoreCase(A, B))
|
|
|
|
return N;
|
|
|
|
return strcmp(A, B);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool operator<(const OptTable::Info &A, const OptTable::Info &B) {
|
|
|
|
if (&A == &B)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (int N = StrCmpOptionName(A.Name, B.Name))
|
|
|
|
return N < 0;
|
|
|
|
|
|
|
|
for (const char * const *APre = A.Prefixes,
|
|
|
|
* const *BPre = B.Prefixes;
|
2014-04-15 14:32:26 +08:00
|
|
|
*APre != nullptr && *BPre != nullptr; ++APre, ++BPre){
|
2013-09-11 07:22:56 +08:00
|
|
|
if (int N = StrCmpOptionName(*APre, *BPre))
|
|
|
|
return N < 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Names are the same, check that classes are in order; exactly one
|
|
|
|
// should be joined, and it should succeed the other.
|
|
|
|
assert(((A.Kind == Option::JoinedClass) ^ (B.Kind == Option::JoinedClass)) &&
|
|
|
|
"Unexpected classes for options with same name.");
|
|
|
|
return B.Kind == Option::JoinedClass;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-12-05 08:29:32 +08:00
|
|
|
// Support lower_bound between info and an option name.
|
|
|
|
static inline bool operator<(const OptTable::Info &I, const char *Name) {
|
2013-08-29 04:04:31 +08:00
|
|
|
return StrCmpOptionNameIgnoreCase(I.Name, Name) < 0;
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
2017-06-16 08:43:26 +08:00
|
|
|
|
|
|
|
} // end namespace opt
|
|
|
|
} // end namespace llvm
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {}
|
|
|
|
|
2015-10-22 00:30:42 +08:00
|
|
|
OptTable::OptTable(ArrayRef<Info> OptionInfos, bool IgnoreCase)
|
2017-06-16 08:43:26 +08:00
|
|
|
: OptionInfos(OptionInfos), IgnoreCase(IgnoreCase) {
|
2012-12-05 08:29:32 +08:00
|
|
|
// Explicitly zero initialize the error to work around a bug in array
|
|
|
|
// value-initialization on MinGW with gcc 4.3.5.
|
|
|
|
|
|
|
|
// Find start of normal options.
|
|
|
|
for (unsigned i = 0, e = getNumOptions(); i != e; ++i) {
|
|
|
|
unsigned Kind = getInfo(i + 1).Kind;
|
|
|
|
if (Kind == Option::InputClass) {
|
|
|
|
assert(!TheInputOptionID && "Cannot have multiple input options!");
|
|
|
|
TheInputOptionID = getInfo(i + 1).ID;
|
|
|
|
} else if (Kind == Option::UnknownClass) {
|
|
|
|
assert(!TheUnknownOptionID && "Cannot have multiple unknown options!");
|
|
|
|
TheUnknownOptionID = getInfo(i + 1).ID;
|
|
|
|
} else if (Kind != Option::GroupClass) {
|
|
|
|
FirstSearchableIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(FirstSearchableIndex != 0 && "No searchable options?");
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// Check that everything after the first searchable option is a
|
|
|
|
// regular option class.
|
|
|
|
for (unsigned i = FirstSearchableIndex, e = getNumOptions(); i != e; ++i) {
|
|
|
|
Option::OptionClass Kind = (Option::OptionClass) getInfo(i + 1).Kind;
|
|
|
|
assert((Kind != Option::InputClass && Kind != Option::UnknownClass &&
|
|
|
|
Kind != Option::GroupClass) &&
|
|
|
|
"Special options should be defined first!");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that options are in order.
|
|
|
|
for (unsigned i = FirstSearchableIndex + 1, e = getNumOptions(); i != e; ++i){
|
|
|
|
if (!(getInfo(i) < getInfo(i + 1))) {
|
|
|
|
getOption(i).dump();
|
|
|
|
getOption(i + 1).dump();
|
|
|
|
llvm_unreachable("Options are not in order!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Build prefixes.
|
|
|
|
for (unsigned i = FirstSearchableIndex + 1, e = getNumOptions() + 1;
|
|
|
|
i != e; ++i) {
|
|
|
|
if (const char *const *P = getInfo(i).Prefixes) {
|
2014-04-15 14:32:26 +08:00
|
|
|
for (; *P != nullptr; ++P) {
|
2012-12-05 08:29:32 +08:00
|
|
|
PrefixesUnion.insert(*P);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build prefix chars.
|
2017-06-16 08:43:26 +08:00
|
|
|
for (StringSet<>::const_iterator I = PrefixesUnion.begin(),
|
|
|
|
E = PrefixesUnion.end(); I != E; ++I) {
|
2012-12-05 08:29:32 +08:00
|
|
|
StringRef Prefix = I->getKey();
|
|
|
|
for (StringRef::const_iterator C = Prefix.begin(), CE = Prefix.end();
|
|
|
|
C != CE; ++C)
|
2016-08-12 06:21:41 +08:00
|
|
|
if (!is_contained(PrefixChars, *C))
|
2012-12-05 08:29:32 +08:00
|
|
|
PrefixChars.push_back(*C);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-16 08:43:26 +08:00
|
|
|
OptTable::~OptTable() = default;
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
const Option OptTable::getOption(OptSpecifier Opt) const {
|
|
|
|
unsigned id = Opt.getID();
|
|
|
|
if (id == 0)
|
2014-04-15 14:32:26 +08:00
|
|
|
return Option(nullptr, nullptr);
|
2012-12-05 08:29:32 +08:00
|
|
|
assert((unsigned) (id - 1) < getNumOptions() && "Invalid ID.");
|
|
|
|
return Option(&getInfo(id), this);
|
|
|
|
}
|
|
|
|
|
2017-06-16 08:43:26 +08:00
|
|
|
static bool isInput(const StringSet<> &Prefixes, StringRef Arg) {
|
2012-12-05 08:29:32 +08:00
|
|
|
if (Arg == "-")
|
|
|
|
return true;
|
2017-06-16 08:43:26 +08:00
|
|
|
for (StringSet<>::const_iterator I = Prefixes.begin(),
|
|
|
|
E = Prefixes.end(); I != E; ++I)
|
2012-12-05 08:29:32 +08:00
|
|
|
if (Arg.startswith(I->getKey()))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \returns Matched size. 0 means no match.
|
2013-08-29 04:04:31 +08:00
|
|
|
static unsigned matchOption(const OptTable::Info *I, StringRef Str,
|
|
|
|
bool IgnoreCase) {
|
2014-04-15 14:32:26 +08:00
|
|
|
for (const char * const *Pre = I->Prefixes; *Pre != nullptr; ++Pre) {
|
2012-12-05 08:29:32 +08:00
|
|
|
StringRef Prefix(*Pre);
|
2013-08-29 04:04:31 +08:00
|
|
|
if (Str.startswith(Prefix)) {
|
|
|
|
StringRef Rest = Str.substr(Prefix.size());
|
|
|
|
bool Matched = IgnoreCase
|
2013-11-05 03:22:50 +08:00
|
|
|
? Rest.startswith_lower(I->Name)
|
2013-08-29 04:04:31 +08:00
|
|
|
: Rest.startswith(I->Name);
|
|
|
|
if (Matched)
|
|
|
|
return Prefix.size() + StringRef(I->Name).size();
|
|
|
|
}
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-21 00:31:31 +08:00
|
|
|
// Returns true if one of the Prefixes + In.Names matches Option
|
|
|
|
static bool optionMatches(const OptTable::Info &In, StringRef Option) {
|
2017-08-29 08:09:31 +08:00
|
|
|
if (In.Prefixes)
|
2017-06-21 00:31:31 +08:00
|
|
|
for (size_t I = 0; In.Prefixes[I]; I++)
|
|
|
|
if (Option == std::string(In.Prefixes[I]) + In.Name)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function is for flag value completion.
|
|
|
|
// Eg. When "-stdlib=" and "l" was passed to this function, it will return
|
|
|
|
// appropiriate values for stdlib, which starts with l.
|
|
|
|
std::vector<std::string>
|
|
|
|
OptTable::suggestValueCompletions(StringRef Option, StringRef Arg) const {
|
|
|
|
// Search all options and return possible values.
|
2017-08-29 08:09:31 +08:00
|
|
|
for (size_t I = FirstSearchableIndex, E = OptionInfos.size(); I < E; I++) {
|
|
|
|
const Info &In = OptionInfos[I];
|
|
|
|
if (!In.Values || !optionMatches(In, Option))
|
2017-06-21 00:31:31 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
SmallVector<StringRef, 8> Candidates;
|
|
|
|
StringRef(In.Values).split(Candidates, ",", -1, false);
|
|
|
|
|
|
|
|
std::vector<std::string> Result;
|
|
|
|
for (StringRef Val : Candidates)
|
2018-03-05 16:54:20 +08:00
|
|
|
if (Val.startswith(Arg) && Arg.compare(Val))
|
2017-06-21 00:31:31 +08:00
|
|
|
Result.push_back(Val);
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-07-09 01:48:59 +08:00
|
|
|
std::vector<std::string>
|
|
|
|
OptTable::findByPrefix(StringRef Cur, unsigned short DisableFlags) const {
|
2017-05-24 02:39:08 +08:00
|
|
|
std::vector<std::string> Ret;
|
2017-08-29 08:09:31 +08:00
|
|
|
for (size_t I = FirstSearchableIndex, E = OptionInfos.size(); I < E; I++) {
|
|
|
|
const Info &In = OptionInfos[I];
|
2017-07-05 10:36:32 +08:00
|
|
|
if (!In.Prefixes || (!In.HelpText && !In.GroupID))
|
2017-05-24 02:39:08 +08:00
|
|
|
continue;
|
2017-07-09 01:48:59 +08:00
|
|
|
if (In.Flags & DisableFlags)
|
|
|
|
continue;
|
|
|
|
|
2017-05-24 02:39:08 +08:00
|
|
|
for (int I = 0; In.Prefixes[I]; I++) {
|
2017-07-26 21:36:58 +08:00
|
|
|
std::string S = std::string(In.Prefixes[I]) + std::string(In.Name) + "\t";
|
|
|
|
if (In.HelpText)
|
|
|
|
S += In.HelpText;
|
2018-03-05 16:54:20 +08:00
|
|
|
if (StringRef(S).startswith(Cur) && S.compare(std::string(Cur) + "\t"))
|
2017-05-24 02:39:08 +08:00
|
|
|
Ret.push_back(S);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Ret;
|
|
|
|
}
|
|
|
|
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
unsigned OptTable::findNearest(StringRef Option, std::string &NearestString,
|
|
|
|
unsigned FlagsToInclude, unsigned FlagsToExclude,
|
|
|
|
unsigned MinimumLength) const {
|
|
|
|
assert(!Option.empty());
|
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
// Consider each option as a candidate, finding the closest match.
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
unsigned BestDistance = UINT_MAX;
|
|
|
|
for (const Info &CandidateInfo :
|
|
|
|
ArrayRef<Info>(OptionInfos).drop_front(FirstSearchableIndex)) {
|
|
|
|
StringRef CandidateName = CandidateInfo.Name;
|
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
// Ignore option candidates with empty names, such as "--", or names
|
|
|
|
// that do not meet the minimum length.
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
if (CandidateName.empty() || CandidateName.size() < MinimumLength)
|
|
|
|
continue;
|
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
// If FlagsToInclude were specified, ignore options that don't include
|
|
|
|
// those flags.
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude))
|
|
|
|
continue;
|
2018-09-04 02:13:46 +08:00
|
|
|
// Ignore options that contain the FlagsToExclude.
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
if (CandidateInfo.Flags & FlagsToExclude)
|
|
|
|
continue;
|
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
// Ignore positional argument option candidates (which do not
|
|
|
|
// have prefixes).
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
if (!CandidateInfo.Prefixes)
|
|
|
|
continue;
|
2018-09-04 02:13:46 +08:00
|
|
|
// Find the most appropriate prefix. For example, if a user asks for
|
|
|
|
// "--helm", suggest "--help" over "-help".
|
|
|
|
StringRef Prefix = CandidateInfo.Prefixes[0];
|
|
|
|
for (int P = 1; CandidateInfo.Prefixes[P]; P++) {
|
|
|
|
if (Option.startswith(CandidateInfo.Prefixes[P]))
|
|
|
|
Prefix = CandidateInfo.Prefixes[P];
|
|
|
|
}
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
// Check if the candidate ends with a character commonly used when
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
// delimiting an option from its value, such as '=' or ':'. If it does,
|
|
|
|
// attempt to split the given option based on that delimiter.
|
|
|
|
std::string Delimiter = "";
|
|
|
|
char Last = CandidateName.back();
|
|
|
|
if (Last == '=' || Last == ':')
|
|
|
|
Delimiter = std::string(1, Last);
|
|
|
|
|
|
|
|
StringRef LHS, RHS;
|
|
|
|
if (Delimiter.empty())
|
|
|
|
LHS = Option;
|
|
|
|
else
|
|
|
|
std::tie(LHS, RHS) = Option.split(Last);
|
|
|
|
|
2018-09-04 02:13:46 +08:00
|
|
|
std::string NormalizedName =
|
|
|
|
(LHS.drop_front(Prefix.size()) + Delimiter).str();
|
|
|
|
unsigned Distance =
|
|
|
|
CandidateName.edit_distance(NormalizedName, /*AllowReplacements=*/true,
|
|
|
|
/*MaxEditDistance=*/BestDistance);
|
|
|
|
if (Distance < BestDistance) {
|
|
|
|
BestDistance = Distance;
|
|
|
|
NearestString = (Prefix + CandidateName + RHS).str();
|
[Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.
The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.
As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.
Test Plan: `check-llvm`
Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs
Reviewed By: jroelofs
Subscribers: jroelofs, llvm-commits
Differential Revision: https://reviews.llvm.org/D41732
llvm-svn: 321877
2018-01-06 01:10:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return BestDistance;
|
|
|
|
}
|
|
|
|
|
2017-08-29 08:09:31 +08:00
|
|
|
bool OptTable::addValues(const char *Option, const char *Values) {
|
|
|
|
for (size_t I = FirstSearchableIndex, E = OptionInfos.size(); I < E; I++) {
|
|
|
|
Info &In = OptionInfos[I];
|
|
|
|
if (optionMatches(In, Option)) {
|
|
|
|
In.Values = Values;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-07-20 02:04:57 +08:00
|
|
|
Arg *OptTable::ParseOneArg(const ArgList &Args, unsigned &Index,
|
|
|
|
unsigned FlagsToInclude,
|
|
|
|
unsigned FlagsToExclude) const {
|
2012-12-05 08:29:32 +08:00
|
|
|
unsigned Prev = Index;
|
|
|
|
const char *Str = Args.getArgString(Index);
|
|
|
|
|
|
|
|
// Anything that doesn't start with PrefixesUnion is an input, as is '-'
|
|
|
|
// itself.
|
|
|
|
if (isInput(PrefixesUnion, Str))
|
|
|
|
return new Arg(getOption(TheInputOptionID), Str, Index++, Str);
|
|
|
|
|
2017-08-29 08:09:31 +08:00
|
|
|
const Info *Start = OptionInfos.data() + FirstSearchableIndex;
|
|
|
|
const Info *End = OptionInfos.data() + OptionInfos.size();
|
2012-12-05 08:29:32 +08:00
|
|
|
StringRef Name = StringRef(Str).ltrim(PrefixChars);
|
|
|
|
|
|
|
|
// Search for the first next option which could be a prefix.
|
|
|
|
Start = std::lower_bound(Start, End, Name.data());
|
|
|
|
|
|
|
|
// Options are stored in sorted order, with '\0' at the end of the
|
|
|
|
// alphabet. Since the only options which can accept a string must
|
|
|
|
// prefix it, we iteratively search for the next option which could
|
|
|
|
// be a prefix.
|
|
|
|
//
|
|
|
|
// FIXME: This is searching much more than necessary, but I am
|
|
|
|
// blanking on the simplest way to make it fast. We can solve this
|
|
|
|
// problem when we move to TableGen.
|
|
|
|
for (; Start != End; ++Start) {
|
|
|
|
unsigned ArgSize = 0;
|
|
|
|
// Scan for first option which is a proper prefix.
|
|
|
|
for (; Start != End; ++Start)
|
2013-08-29 04:04:31 +08:00
|
|
|
if ((ArgSize = matchOption(Start, Str, IgnoreCase)))
|
2012-12-05 08:29:32 +08:00
|
|
|
break;
|
|
|
|
if (Start == End)
|
|
|
|
break;
|
|
|
|
|
2013-07-20 02:04:57 +08:00
|
|
|
Option Opt(Start, this);
|
|
|
|
|
|
|
|
if (FlagsToInclude && !Opt.hasFlag(FlagsToInclude))
|
|
|
|
continue;
|
|
|
|
if (Opt.hasFlag(FlagsToExclude))
|
|
|
|
continue;
|
|
|
|
|
2012-12-05 08:29:32 +08:00
|
|
|
// See if this option matches.
|
2013-07-20 02:04:57 +08:00
|
|
|
if (Arg *A = Opt.accept(Args, Index, ArgSize))
|
2012-12-05 08:29:32 +08:00
|
|
|
return A;
|
|
|
|
|
|
|
|
// Otherwise, see if this argument was missing values.
|
|
|
|
if (Prev != Index)
|
2014-04-15 14:32:26 +08:00
|
|
|
return nullptr;
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
|
2013-07-20 02:04:57 +08:00
|
|
|
// If we failed to find an option and this arg started with /, then it's
|
|
|
|
// probably an input path.
|
|
|
|
if (Str[0] == '/')
|
|
|
|
return new Arg(getOption(TheInputOptionID), Str, Index++, Str);
|
|
|
|
|
2012-12-05 08:29:32 +08:00
|
|
|
return new Arg(getOption(TheUnknownOptionID), Str, Index++, Str);
|
|
|
|
}
|
|
|
|
|
2015-06-23 06:06:37 +08:00
|
|
|
InputArgList OptTable::ParseArgs(ArrayRef<const char *> ArgArr,
|
|
|
|
unsigned &MissingArgIndex,
|
|
|
|
unsigned &MissingArgCount,
|
|
|
|
unsigned FlagsToInclude,
|
|
|
|
unsigned FlagsToExclude) const {
|
|
|
|
InputArgList Args(ArgArr.begin(), ArgArr.end());
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
// FIXME: Handle '@' args (or at least error on them).
|
|
|
|
|
|
|
|
MissingArgIndex = MissingArgCount = 0;
|
2015-06-21 14:31:53 +08:00
|
|
|
unsigned Index = 0, End = ArgArr.size();
|
2012-12-05 08:29:32 +08:00
|
|
|
while (Index < End) {
|
2014-08-23 03:29:17 +08:00
|
|
|
// Ingore nullptrs, they are response file's EOL markers
|
2015-06-23 06:06:37 +08:00
|
|
|
if (Args.getArgString(Index) == nullptr) {
|
2014-08-23 03:29:17 +08:00
|
|
|
++Index;
|
|
|
|
continue;
|
|
|
|
}
|
2012-12-05 08:29:32 +08:00
|
|
|
// Ignore empty arguments (other things may still take them as arguments).
|
2015-06-23 06:06:37 +08:00
|
|
|
StringRef Str = Args.getArgString(Index);
|
2013-08-03 05:20:27 +08:00
|
|
|
if (Str == "") {
|
2012-12-05 08:29:32 +08:00
|
|
|
++Index;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned Prev = Index;
|
2015-06-23 06:06:37 +08:00
|
|
|
Arg *A = ParseOneArg(Args, Index, FlagsToInclude, FlagsToExclude);
|
2012-12-05 08:29:32 +08:00
|
|
|
assert(Index > Prev && "Parser failed to consume argument.");
|
|
|
|
|
|
|
|
// Check for missing argument error.
|
|
|
|
if (!A) {
|
|
|
|
assert(Index >= End && "Unexpected parser error.");
|
|
|
|
assert(Index - Prev - 1 && "No missing arguments!");
|
|
|
|
MissingArgIndex = Prev;
|
|
|
|
MissingArgCount = Index - Prev - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-06-23 06:06:37 +08:00
|
|
|
Args.append(A);
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
|
2015-06-23 07:16:02 +08:00
|
|
|
return Args;
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static std::string getOptionHelpName(const OptTable &Opts, OptSpecifier Id) {
|
|
|
|
const Option O = Opts.getOption(Id);
|
|
|
|
std::string Name = O.getPrefixedName();
|
|
|
|
|
|
|
|
// Add metavar, if used.
|
|
|
|
switch (O.getKind()) {
|
|
|
|
case Option::GroupClass: case Option::InputClass: case Option::UnknownClass:
|
|
|
|
llvm_unreachable("Invalid option with help text.");
|
|
|
|
|
|
|
|
case Option::MultiArgClass:
|
2014-08-16 05:35:07 +08:00
|
|
|
if (const char *MetaVarName = Opts.getOptionMetaVar(Id)) {
|
|
|
|
// For MultiArgs, metavar is full list of all argument names.
|
|
|
|
Name += ' ';
|
|
|
|
Name += MetaVarName;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// For MultiArgs<N>, if metavar not supplied, print <value> N times.
|
|
|
|
for (unsigned i=0, e=O.getNumArgs(); i< e; ++i) {
|
|
|
|
Name += " <value>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
case Option::FlagClass:
|
|
|
|
break;
|
|
|
|
|
2017-06-21 00:31:31 +08:00
|
|
|
case Option::ValuesClass:
|
|
|
|
break;
|
|
|
|
|
2012-12-05 08:29:32 +08:00
|
|
|
case Option::SeparateClass: case Option::JoinedOrSeparateClass:
|
2016-04-15 08:23:30 +08:00
|
|
|
case Option::RemainingArgsClass: case Option::RemainingArgsJoinedClass:
|
2012-12-05 08:29:32 +08:00
|
|
|
Name += ' ';
|
2016-08-17 13:10:15 +08:00
|
|
|
LLVM_FALLTHROUGH;
|
2012-12-05 08:29:32 +08:00
|
|
|
case Option::JoinedClass: case Option::CommaJoinedClass:
|
|
|
|
case Option::JoinedAndSeparateClass:
|
|
|
|
if (const char *MetaVarName = Opts.getOptionMetaVar(Id))
|
|
|
|
Name += MetaVarName;
|
|
|
|
else
|
|
|
|
Name += "<value>";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Name;
|
|
|
|
}
|
|
|
|
|
2017-07-18 18:59:30 +08:00
|
|
|
namespace {
|
|
|
|
struct OptionInfo {
|
|
|
|
std::string Name;
|
|
|
|
StringRef HelpText;
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2012-12-05 08:29:32 +08:00
|
|
|
static void PrintHelpOptionList(raw_ostream &OS, StringRef Title,
|
2017-07-18 18:59:30 +08:00
|
|
|
std::vector<OptionInfo> &OptionHelp) {
|
2012-12-05 08:29:32 +08:00
|
|
|
OS << Title << ":\n";
|
|
|
|
|
|
|
|
// Find the maximum option length.
|
|
|
|
unsigned OptionFieldWidth = 0;
|
|
|
|
for (unsigned i = 0, e = OptionHelp.size(); i != e; ++i) {
|
|
|
|
// Limit the amount of padding we are willing to give up for alignment.
|
2017-07-18 18:59:30 +08:00
|
|
|
unsigned Length = OptionHelp[i].Name.size();
|
2012-12-05 08:29:32 +08:00
|
|
|
if (Length <= 23)
|
|
|
|
OptionFieldWidth = std::max(OptionFieldWidth, Length);
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned InitialPad = 2;
|
|
|
|
for (unsigned i = 0, e = OptionHelp.size(); i != e; ++i) {
|
2017-07-18 18:59:30 +08:00
|
|
|
const std::string &Option = OptionHelp[i].Name;
|
2012-12-05 08:29:32 +08:00
|
|
|
int Pad = OptionFieldWidth - int(Option.size());
|
|
|
|
OS.indent(InitialPad) << Option;
|
|
|
|
|
|
|
|
// Break on long option names.
|
|
|
|
if (Pad < 0) {
|
|
|
|
OS << "\n";
|
|
|
|
Pad = OptionFieldWidth + InitialPad;
|
|
|
|
}
|
2017-07-18 18:59:30 +08:00
|
|
|
OS.indent(Pad + 1) << OptionHelp[i].HelpText << '\n';
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *getOptionHelpGroup(const OptTable &Opts, OptSpecifier Id) {
|
|
|
|
unsigned GroupID = Opts.getOptionGroupID(Id);
|
|
|
|
|
|
|
|
// If not in a group, return the default help group.
|
|
|
|
if (!GroupID)
|
|
|
|
return "OPTIONS";
|
|
|
|
|
|
|
|
// Abuse the help text of the option groups to store the "help group"
|
|
|
|
// name.
|
|
|
|
//
|
|
|
|
// FIXME: Split out option groups.
|
|
|
|
if (const char *GroupHelp = Opts.getOptionHelpText(GroupID))
|
|
|
|
return GroupHelp;
|
|
|
|
|
|
|
|
// Otherwise keep looking.
|
|
|
|
return getOptionHelpGroup(Opts, GroupID);
|
|
|
|
}
|
|
|
|
|
2018-10-10 08:15:31 +08:00
|
|
|
void OptTable::PrintHelp(raw_ostream &OS, const char *Usage, const char *Title,
|
2017-07-26 17:09:56 +08:00
|
|
|
bool ShowHidden, bool ShowAllAliases) const {
|
2018-10-10 08:15:31 +08:00
|
|
|
PrintHelp(OS, Usage, Title, /*Include*/ 0, /*Exclude*/
|
2017-07-26 17:09:56 +08:00
|
|
|
(ShowHidden ? 0 : HelpHidden), ShowAllAliases);
|
2013-06-14 02:12:12 +08:00
|
|
|
}
|
|
|
|
|
2018-10-10 08:15:31 +08:00
|
|
|
void OptTable::PrintHelp(raw_ostream &OS, const char *Usage, const char *Title,
|
2017-07-26 17:09:56 +08:00
|
|
|
unsigned FlagsToInclude, unsigned FlagsToExclude,
|
|
|
|
bool ShowAllAliases) const {
|
2018-10-10 08:15:31 +08:00
|
|
|
OS << "OVERVIEW: " << Title << "\n\n";
|
|
|
|
OS << "USAGE: " << Usage << "\n\n";
|
2012-12-05 08:29:32 +08:00
|
|
|
|
|
|
|
// Render help text into a map of group-name to a list of (option, help)
|
|
|
|
// pairs.
|
2018-03-13 02:31:07 +08:00
|
|
|
std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp;
|
2012-12-05 08:29:32 +08:00
|
|
|
|
2018-03-13 02:30:47 +08:00
|
|
|
for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) {
|
2012-12-05 08:29:32 +08:00
|
|
|
// FIXME: Split out option groups.
|
|
|
|
if (getOptionKind(Id) == Option::GroupClass)
|
|
|
|
continue;
|
|
|
|
|
2013-06-14 02:12:12 +08:00
|
|
|
unsigned Flags = getInfo(Id).Flags;
|
|
|
|
if (FlagsToInclude && !(Flags & FlagsToInclude))
|
|
|
|
continue;
|
|
|
|
if (Flags & FlagsToExclude)
|
2012-12-05 08:29:32 +08:00
|
|
|
continue;
|
|
|
|
|
2017-07-26 17:09:56 +08:00
|
|
|
// If an alias doesn't have a help text, show a help text for the aliased
|
|
|
|
// option instead.
|
|
|
|
const char *HelpText = getOptionHelpText(Id);
|
|
|
|
if (!HelpText && ShowAllAliases) {
|
|
|
|
const Option Alias = getOption(Id).getAlias();
|
|
|
|
if (Alias.isValid())
|
|
|
|
HelpText = getOptionHelpText(Alias.getID());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HelpText) {
|
2012-12-05 08:29:32 +08:00
|
|
|
const char *HelpGroup = getOptionHelpGroup(*this, Id);
|
|
|
|
const std::string &OptName = getOptionHelpName(*this, Id);
|
2017-07-26 17:09:56 +08:00
|
|
|
GroupedOptionHelp[HelpGroup].push_back({OptName, HelpText});
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-13 02:31:07 +08:00
|
|
|
for (auto& OptionGroup : GroupedOptionHelp) {
|
|
|
|
if (OptionGroup.first != GroupedOptionHelp.begin()->first)
|
2012-12-05 08:29:32 +08:00
|
|
|
OS << "\n";
|
2018-03-13 02:31:07 +08:00
|
|
|
PrintHelpOptionList(OS, OptionGroup.first, OptionGroup.second);
|
2012-12-05 08:29:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
OS.flush();
|
|
|
|
}
|