forked from OSchip/llvm-project
Introduce Parser::completeExpression.
This function returns a list of completions for a given expression and completion position. Differential Revision: http://llvm-reviews.chandlerc.com/D2261 llvm-svn: 200497
This commit is contained in:
parent
538b52a2fc
commit
252444a777
|
@ -144,8 +144,16 @@ public:
|
|||
static bool parseExpression(StringRef Code, Sema *S,
|
||||
VariantValue *Value, Diagnostics *Error);
|
||||
|
||||
/// \brief Complete an expression at the given offset.
|
||||
///
|
||||
/// \return The list of completions, which may be empty if there are no
|
||||
/// available completions or if an error occurred.
|
||||
static std::vector<MatcherCompletion>
|
||||
completeExpression(StringRef Code, unsigned CompletionOffset);
|
||||
|
||||
private:
|
||||
class CodeTokenizer;
|
||||
struct ScopedContextEntry;
|
||||
struct TokenInfo;
|
||||
|
||||
Parser(CodeTokenizer *Tokenizer, Sema *S,
|
||||
|
@ -154,9 +162,17 @@ private:
|
|||
bool parseExpressionImpl(VariantValue *Value);
|
||||
bool parseMatcherExpressionImpl(VariantValue *Value);
|
||||
|
||||
void addCompletion(const TokenInfo &CompToken, StringRef TypedText,
|
||||
StringRef Decl);
|
||||
void addExpressionCompletions();
|
||||
|
||||
CodeTokenizer *const Tokenizer;
|
||||
Sema *const S;
|
||||
Diagnostics *const Error;
|
||||
|
||||
typedef std::vector<std::pair<MatcherCtor, unsigned> > ContextStackTy;
|
||||
ContextStackTy ContextStack;
|
||||
std::vector<MatcherCompletion> Completions;
|
||||
};
|
||||
|
||||
} // namespace dynamic
|
||||
|
|
|
@ -28,15 +28,16 @@ namespace dynamic {
|
|||
struct Parser::TokenInfo {
|
||||
/// \brief Different possible tokens.
|
||||
enum TokenKind {
|
||||
TK_Eof = 0,
|
||||
TK_OpenParen = 1,
|
||||
TK_CloseParen = 2,
|
||||
TK_Comma = 3,
|
||||
TK_Period = 4,
|
||||
TK_Literal = 5,
|
||||
TK_Ident = 6,
|
||||
TK_InvalidChar = 7,
|
||||
TK_Error = 8
|
||||
TK_Eof,
|
||||
TK_OpenParen,
|
||||
TK_CloseParen,
|
||||
TK_Comma,
|
||||
TK_Period,
|
||||
TK_Literal,
|
||||
TK_Ident,
|
||||
TK_InvalidChar,
|
||||
TK_Error,
|
||||
TK_CodeCompletion
|
||||
};
|
||||
|
||||
/// \brief Some known identifiers.
|
||||
|
@ -56,7 +57,15 @@ const char* const Parser::TokenInfo::ID_Bind = "bind";
|
|||
class Parser::CodeTokenizer {
|
||||
public:
|
||||
explicit CodeTokenizer(StringRef MatcherCode, Diagnostics *Error)
|
||||
: Code(MatcherCode), StartOfLine(MatcherCode), Line(1), Error(Error) {
|
||||
: Code(MatcherCode), StartOfLine(MatcherCode), Line(1), Error(Error),
|
||||
CodeCompletionLocation(0) {
|
||||
NextToken = getNextToken();
|
||||
}
|
||||
|
||||
CodeTokenizer(StringRef MatcherCode, Diagnostics *Error,
|
||||
unsigned CodeCompletionOffset)
|
||||
: Code(MatcherCode), StartOfLine(MatcherCode), Line(1), Error(Error),
|
||||
CodeCompletionLocation(MatcherCode.data() + CodeCompletionOffset) {
|
||||
NextToken = getNextToken();
|
||||
}
|
||||
|
||||
|
@ -78,6 +87,13 @@ private:
|
|||
TokenInfo Result;
|
||||
Result.Range.Start = currentLocation();
|
||||
|
||||
if (CodeCompletionLocation && CodeCompletionLocation <= Code.data()) {
|
||||
Result.Kind = TokenInfo::TK_CodeCompletion;
|
||||
Result.Text = StringRef(CodeCompletionLocation, 0);
|
||||
CodeCompletionLocation = 0;
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (Code.empty()) {
|
||||
Result.Kind = TokenInfo::TK_Eof;
|
||||
Result.Text = "";
|
||||
|
@ -122,8 +138,21 @@ private:
|
|||
if (isAlphanumeric(Code[0])) {
|
||||
// Parse an identifier
|
||||
size_t TokenLength = 1;
|
||||
while (TokenLength < Code.size() && isAlphanumeric(Code[TokenLength]))
|
||||
while (1) {
|
||||
// A code completion location in/immediately after an identifier will
|
||||
// cause the portion of the identifier before the code completion
|
||||
// location to become a code completion token.
|
||||
if (CodeCompletionLocation == Code.data() + TokenLength) {
|
||||
CodeCompletionLocation = 0;
|
||||
Result.Kind = TokenInfo::TK_CodeCompletion;
|
||||
Result.Text = Code.substr(0, TokenLength);
|
||||
Code = Code.drop_front(TokenLength);
|
||||
return Result;
|
||||
}
|
||||
if (TokenLength == Code.size() || !isAlphanumeric(Code[TokenLength]))
|
||||
break;
|
||||
++TokenLength;
|
||||
}
|
||||
Result.Kind = TokenInfo::TK_Ident;
|
||||
Result.Text = Code.substr(0, TokenLength);
|
||||
Code = Code.drop_front(TokenLength);
|
||||
|
@ -224,10 +253,27 @@ private:
|
|||
unsigned Line;
|
||||
Diagnostics *Error;
|
||||
TokenInfo NextToken;
|
||||
const char *CodeCompletionLocation;
|
||||
};
|
||||
|
||||
Parser::Sema::~Sema() {}
|
||||
|
||||
struct Parser::ScopedContextEntry {
|
||||
Parser *P;
|
||||
|
||||
ScopedContextEntry(Parser *P, MatcherCtor C) : P(P) {
|
||||
P->ContextStack.push_back(std::make_pair(C, 0u));
|
||||
}
|
||||
|
||||
~ScopedContextEntry() {
|
||||
P->ContextStack.pop_back();
|
||||
}
|
||||
|
||||
void nextArg() {
|
||||
++P->ContextStack.back().second;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief Parse and validate a matcher expression.
|
||||
/// \return \c true on success, in which case \c Value has the matcher parsed.
|
||||
/// If the input is malformed, or some argument has an error, it
|
||||
|
@ -244,33 +290,41 @@ bool Parser::parseMatcherExpressionImpl(VariantValue *Value) {
|
|||
|
||||
llvm::Optional<MatcherCtor> Ctor =
|
||||
S->lookupMatcherCtor(NameToken.Text, NameToken.Range, Error);
|
||||
|
||||
std::vector<ParserValue> Args;
|
||||
TokenInfo EndToken;
|
||||
while (Tokenizer->nextTokenKind() != TokenInfo::TK_Eof) {
|
||||
if (Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen) {
|
||||
// End of args.
|
||||
EndToken = Tokenizer->consumeNextToken();
|
||||
break;
|
||||
}
|
||||
if (Args.size() > 0) {
|
||||
// We must find a , token to continue.
|
||||
const TokenInfo CommaToken = Tokenizer->consumeNextToken();
|
||||
if (CommaToken.Kind != TokenInfo::TK_Comma) {
|
||||
Error->addError(CommaToken.Range, Error->ET_ParserNoComma)
|
||||
<< CommaToken.Text;
|
||||
|
||||
{
|
||||
ScopedContextEntry SCE(this, Ctor ? *Ctor : 0);
|
||||
|
||||
while (Tokenizer->nextTokenKind() != TokenInfo::TK_Eof) {
|
||||
if (Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen) {
|
||||
// End of args.
|
||||
EndToken = Tokenizer->consumeNextToken();
|
||||
break;
|
||||
}
|
||||
if (Args.size() > 0) {
|
||||
// We must find a , token to continue.
|
||||
const TokenInfo CommaToken = Tokenizer->consumeNextToken();
|
||||
if (CommaToken.Kind != TokenInfo::TK_Comma) {
|
||||
Error->addError(CommaToken.Range, Error->ET_ParserNoComma)
|
||||
<< CommaToken.Text;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Diagnostics::Context Ctx(Diagnostics::Context::MatcherArg, Error,
|
||||
NameToken.Text, NameToken.Range,
|
||||
Args.size() + 1);
|
||||
ParserValue ArgValue;
|
||||
ArgValue.Text = Tokenizer->peekNextToken().Text;
|
||||
ArgValue.Range = Tokenizer->peekNextToken().Range;
|
||||
if (!parseExpressionImpl(&ArgValue.Value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Args.push_back(ArgValue);
|
||||
SCE.nextArg();
|
||||
}
|
||||
|
||||
Diagnostics::Context Ctx(Diagnostics::Context::MatcherArg, Error,
|
||||
NameToken.Text, NameToken.Range, Args.size() + 1);
|
||||
ParserValue ArgValue;
|
||||
ArgValue.Text = Tokenizer->peekNextToken().Text;
|
||||
ArgValue.Range = Tokenizer->peekNextToken().Range;
|
||||
if (!parseExpressionImpl(&ArgValue.Value)) return false;
|
||||
|
||||
Args.push_back(ArgValue);
|
||||
}
|
||||
|
||||
if (EndToken.Kind == TokenInfo::TK_Eof) {
|
||||
|
@ -283,6 +337,11 @@ bool Parser::parseMatcherExpressionImpl(VariantValue *Value) {
|
|||
// Parse .bind("foo")
|
||||
Tokenizer->consumeNextToken(); // consume the period.
|
||||
const TokenInfo BindToken = Tokenizer->consumeNextToken();
|
||||
if (BindToken.Kind == TokenInfo::TK_CodeCompletion) {
|
||||
addCompletion(BindToken, "bind(\"", "bind");
|
||||
return false;
|
||||
}
|
||||
|
||||
const TokenInfo OpenToken = Tokenizer->consumeNextToken();
|
||||
const TokenInfo IDToken = Tokenizer->consumeNextToken();
|
||||
const TokenInfo CloseToken = Tokenizer->consumeNextToken();
|
||||
|
@ -325,6 +384,39 @@ bool Parser::parseMatcherExpressionImpl(VariantValue *Value) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// If the prefix of this completion matches the completion token, add it to
|
||||
// Completions minus the prefix.
|
||||
void Parser::addCompletion(const TokenInfo &CompToken, StringRef TypedText,
|
||||
StringRef Decl) {
|
||||
if (TypedText.size() >= CompToken.Text.size() &&
|
||||
TypedText.substr(0, CompToken.Text.size()) == CompToken.Text) {
|
||||
Completions.push_back(
|
||||
MatcherCompletion(TypedText.substr(CompToken.Text.size()), Decl));
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::addExpressionCompletions() {
|
||||
const TokenInfo CompToken = Tokenizer->consumeNextToken();
|
||||
assert(CompToken.Kind == TokenInfo::TK_CodeCompletion);
|
||||
|
||||
// We cannot complete code if there is an invalid element on the context
|
||||
// stack.
|
||||
for (ContextStackTy::iterator I = ContextStack.begin(),
|
||||
E = ContextStack.end();
|
||||
I != E; ++I) {
|
||||
if (!I->first)
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<MatcherCompletion> RegCompletions =
|
||||
Registry::getCompletions(ContextStack);
|
||||
for (std::vector<MatcherCompletion>::iterator I = RegCompletions.begin(),
|
||||
E = RegCompletions.end();
|
||||
I != E; ++I) {
|
||||
addCompletion(CompToken, I->TypedText, I->MatcherDecl);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Parse an <Expresssion>
|
||||
bool Parser::parseExpressionImpl(VariantValue *Value) {
|
||||
switch (Tokenizer->nextTokenKind()) {
|
||||
|
@ -335,6 +427,10 @@ bool Parser::parseExpressionImpl(VariantValue *Value) {
|
|||
case TokenInfo::TK_Ident:
|
||||
return parseMatcherExpressionImpl(Value);
|
||||
|
||||
case TokenInfo::TK_CodeCompletion:
|
||||
addExpressionCompletions();
|
||||
return false;
|
||||
|
||||
case TokenInfo::TK_Eof:
|
||||
Error->addError(Tokenizer->consumeNextToken().Range,
|
||||
Error->ET_ParserNoCode);
|
||||
|
@ -401,6 +497,18 @@ bool Parser::parseExpression(StringRef Code, Sema *S,
|
|||
return true;
|
||||
}
|
||||
|
||||
std::vector<MatcherCompletion>
|
||||
Parser::completeExpression(StringRef Code, unsigned CompletionOffset) {
|
||||
Diagnostics Error;
|
||||
CodeTokenizer Tokenizer(Code, &Error, CompletionOffset);
|
||||
RegistrySema S;
|
||||
Parser P(&Tokenizer, &S, &Error);
|
||||
VariantValue Dummy;
|
||||
P.parseExpressionImpl(&Dummy);
|
||||
|
||||
return P.Completions;
|
||||
}
|
||||
|
||||
llvm::Optional<DynTypedMatcher>
|
||||
Parser::parseMatcherExpression(StringRef Code, Diagnostics *Error) {
|
||||
RegistrySema S;
|
||||
|
|
|
@ -245,6 +245,20 @@ TEST(ParserTest, OverloadErrors) {
|
|||
ParseWithError("callee(\"A\")"));
|
||||
}
|
||||
|
||||
TEST(ParserTest, Completion) {
|
||||
std::vector<MatcherCompletion> Comps =
|
||||
Parser::completeExpression("while", 5);
|
||||
ASSERT_EQ(1u, Comps.size());
|
||||
EXPECT_EQ("Stmt(", Comps[0].TypedText);
|
||||
EXPECT_EQ("Matcher<Stmt> whileStmt(Matcher<WhileStmt>...)",
|
||||
Comps[0].MatcherDecl);
|
||||
|
||||
Comps = Parser::completeExpression("whileStmt().", 12);
|
||||
ASSERT_EQ(1u, Comps.size());
|
||||
EXPECT_EQ("bind(\"", Comps[0].TypedText);
|
||||
EXPECT_EQ("bind", Comps[0].MatcherDecl);
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
} // end namespace dynamic
|
||||
} // end namespace ast_matchers
|
||||
|
|
Loading…
Reference in New Issue