Index: include/clang/ASTMatchers/Dynamic/Parser.h =================================================================== --- include/clang/ASTMatchers/Dynamic/Parser.h +++ include/clang/ASTMatchers/Dynamic/Parser.h @@ -144,6 +144,13 @@ 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 + completeExpression(StringRef Code, unsigned CompletionOffset); + private: class CodeTokenizer; struct TokenInfo; @@ -154,9 +161,17 @@ 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 > ContextStackTy; + ContextStackTy ContextStack; + std::vector Completions; }; } // namespace dynamic Index: lib/ASTMatchers/Dynamic/Parser.cpp =================================================================== --- lib/ASTMatchers/Dynamic/Parser.cpp +++ lib/ASTMatchers/Dynamic/Parser.cpp @@ -29,15 +29,16 @@ 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. @@ -57,7 +58,15 @@ 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(); } @@ -79,6 +88,13 @@ 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 = ""; @@ -123,8 +139,21 @@ 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); @@ -225,6 +254,7 @@ unsigned Line; Diagnostics *Error; TokenInfo NextToken; + const char *CodeCompletionLocation; }; Parser::Sema::~Sema() {} @@ -245,6 +275,7 @@ llvm::Optional Ctor = S->lookupMatcherCtor(NameToken.Text, NameToken.Range, Error); + ContextStack.push_back(std::make_pair(*Ctor, 0u)); std::vector Args; TokenInfo EndToken; @@ -260,6 +291,7 @@ if (CommaToken.Kind != TokenInfo::TK_Comma) { Error->addError(CommaToken.Range, Error->ET_ParserNoComma) << CommaToken.Text; + ContextStack.pop_back(); return false; } } @@ -269,11 +301,17 @@ ParserValue ArgValue; ArgValue.Text = Tokenizer->peekNextToken().Text; ArgValue.Range = Tokenizer->peekNextToken().Range; - if (!parseExpressionImpl(&ArgValue.Value)) return false; + if (!parseExpressionImpl(&ArgValue.Value)) { + ContextStack.pop_back(); + return false; + } Args.push_back(ArgValue); + ++ContextStack.back().second; } + ContextStack.pop_back(); + if (EndToken.Kind == TokenInfo::TK_Eof) { Error->addError(OpenToken.Range, Error->ET_ParserNoCloseParen); return false; @@ -284,6 +322,11 @@ // 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(); @@ -326,6 +369,39 @@ 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 RegCompletions = + Registry::getCompletions(ContextStack); + for (std::vector::iterator I = RegCompletions.begin(), + E = RegCompletions.end(); + I != E; ++I) { + addCompletion(CompToken, I->TypedText, I->MatcherDecl); + } +} + /// \brief Parse an bool Parser::parseExpressionImpl(VariantValue *Value) { switch (Tokenizer->nextTokenKind()) { @@ -336,6 +412,10 @@ 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); @@ -402,6 +482,18 @@ return true; } +std::vector +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 Parser::parseMatcherExpression(StringRef Code, Diagnostics *Error) { RegistrySema S; Index: unittests/ASTMatchers/Dynamic/ParserTest.cpp =================================================================== --- unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -246,6 +246,20 @@ ParseWithError("callee(\"A\")")); } +TEST(ParserTest, Completion) { + std::vector Comps = + Parser::completeExpression("while", 5); + ASSERT_EQ(1u, Comps.size()); + EXPECT_EQ("Stmt(", Comps[0].TypedText); + EXPECT_EQ("Matcher whileStmt(Matcher...)", + 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