diff --git a/src/frontend/cxx/cxx_document.cc b/src/frontend/cxx/cxx_document.cc index fab243cd..c83b9974 100644 --- a/src/frontend/cxx/cxx_document.cc +++ b/src/frontend/cxx/cxx_document.cc @@ -21,13 +21,8 @@ #include "cxx_document.h" #include -#include -#include #include #include -#include -#include -#include #include #include #include @@ -209,43 +204,28 @@ auto CxxDocument::translationUnit() const -> TranslationUnit* { return &d->unit; } -namespace { - -struct Visit { - CxxDocument* doc_; - std::function visitor; - ASTSlot slotInfo; - - void preVisit(AST* ast) { - if (!ast) return; - - if (visitor(ast)) return; - - // do a pre-visit using the low-level AST API +auto CxxDocument::textOf(AST* ast) -> std::optional { + return textInRange(ast->firstSourceLocation(), ast->lastSourceLocation()); +} - const auto slotCount = slotInfo(ast, 0).slotCount; +auto CxxDocument::textInRange(SourceLocation start, SourceLocation end) + -> std::optional { + auto& unit = d->unit; + auto preprocessor = unit.preprocessor(); - for (int index = 0; index < slotCount; ++index) { - const auto childInfo = slotInfo(ast, index); + const auto startToken = unit.tokenAt(start); + const auto endToken = unit.tokenAt(end.previous()); - if (childInfo.kind == ASTSlotKind::kNode) { - auto child = reinterpret_cast(childInfo.handle); - if (child) preVisit(child); - } else if (childInfo.kind == ASTSlotKind::kNodeList) { - auto list = reinterpret_cast*>(childInfo.handle); - for (auto node : ListView{list}) { - preVisit(node); - } - } - } + if (startToken.fileId() != endToken.fileId()) { + return std::nullopt; } -}; -} // namespace + std::string_view source = preprocessor->source(startToken.fileId()); + + const auto offset = startToken.offset(); + const auto length = endToken.offset() + endToken.length() - offset; -void CxxDocument::preVisit(std::function visitor) { - auto ast = d->unit.ast(); - Visit{this, std::move(visitor)}.preVisit(ast); + return source.substr(offset, length); } } // namespace cxx::lsp \ No newline at end of file diff --git a/src/frontend/cxx/cxx_document.h b/src/frontend/cxx/cxx_document.h index 5dddeb95..b3add991 100644 --- a/src/frontend/cxx/cxx_document.h +++ b/src/frontend/cxx/cxx_document.h @@ -23,8 +23,7 @@ #include #include -#include -#include +#include #include #include "cli.h" @@ -43,7 +42,10 @@ class CxxDocument { [[nodiscard]] auto translationUnit() const -> TranslationUnit*; - void preVisit(std::function visitor); + [[nodiscard]] auto textOf(AST* ast) -> std::optional; + + [[nodiscard]] auto textInRange(SourceLocation start, SourceLocation end) + -> std::optional; private: struct Private; diff --git a/src/frontend/cxx/lsp_server.cc b/src/frontend/cxx/lsp_server.cc index 8c6d2ab1..f5f65fae 100644 --- a/src/frontend/cxx/lsp_server.cc +++ b/src/frontend/cxx/lsp_server.cc @@ -20,9 +20,14 @@ #include "lsp_server.h" +#include #include #include #include +#include +#include +#include +#include #include #include @@ -433,12 +438,15 @@ void Server::operator()(DocumentSymbolRequest request) { auto uri = request.params().textDocument().uri(); auto doc = latestDocument(uri); + auto id = request.id(); - withUnsafeJson([&](json storage) { - DocumentSymbolResponse response(storage); - response.id(request.id()); - (void)response.result(); - sendToClient(response); + run([=, this] { + withUnsafeJson([&](json storage) { + DocumentSymbolResponse response(storage); + response.id(id); + (void)response.result(); + sendToClient(response); + }); }); } @@ -462,7 +470,7 @@ void Server::operator()(SetTraceNotification notification) { trace_ = notification.params().value(); if (trace_ != TraceValue::kOff) { - logTrace("Trace level set to {}", to_string(trace_)); + logTrace(std::format("Trace level set to {}", to_string(trace_))); return; } } diff --git a/src/parser/cxx/ast.h b/src/parser/cxx/ast.h index be9c95ba..120ad799 100644 --- a/src/parser/cxx/ast.h +++ b/src/parser/cxx/ast.h @@ -2262,6 +2262,7 @@ class InitDeclaratorAST final : public AST { DeclaratorAST* declarator = nullptr; RequiresClauseAST* requiresClause = nullptr; ExpressionAST* initializer = nullptr; + Symbol* symbol = nullptr; void accept(ASTVisitor* visitor) override { visitor->visit(this); } diff --git a/src/parser/cxx/ast_cursor.cc b/src/parser/cxx/ast_cursor.cc new file mode 100644 index 00000000..0cc5818f --- /dev/null +++ b/src/parser/cxx/ast_cursor.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2024 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include +#include + +namespace cxx { + +ASTCursor::ASTCursor(AST* root, std::string_view name) { + stack_.push_back(Node{root, name}); +} + +ASTCursor::~ASTCursor() {} + +void ASTCursor::step() { + ASTSlot slotInfo; + + while (!stack_.empty()) { + auto [node, kind] = stack_.back(); + stack_.pop_back(); + + if (auto ast = std::get_if(&node)) { + if (!*ast) continue; + + const auto slotCount = slotInfo(*ast, 0).slotCount; + + std::vector slots; + slots.reserve(slotCount); + + for (int i = 0; i < slotCount; ++i) { + auto childInfo = slotInfo(*ast, i); + slots.push_back(childInfo); + } + + for (const auto& slot : slots | std::views::reverse) { + auto name = to_string(slot.nameIndex); + if (slot.kind == ASTSlotKind::kNode) { + auto node = reinterpret_cast(slot.handle); + stack_.push_back(Node{node, name}); + } else if (slot.kind == ASTSlotKind::kNodeList) { + auto list = reinterpret_cast*>(slot.handle); + stack_.push_back(Node{list, name}); + } + } + + break; + } else if (auto list = std::get_if*>(&node)) { + if (!*list) continue; + + std::vector children; + + for (auto ast : ListView{*list}) { + if (ast) children.push_back(ast); + } + + for (const auto& ast : children | std::views::reverse) { + stack_.push_back(Node{ast, kind}); + } + + break; + } + } +} + +} // namespace cxx \ No newline at end of file diff --git a/src/parser/cxx/ast_cursor.h b/src/parser/cxx/ast_cursor.h new file mode 100644 index 00000000..7afdb0be --- /dev/null +++ b/src/parser/cxx/ast_cursor.h @@ -0,0 +1,66 @@ +// Copyright (c) 2024 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include + +#include +#include +#include + +namespace cxx { + +class ASTCursor { + public: + struct Node { + std::variant*> node; + std::string_view name; + }; + + ASTCursor() = default; + ~ASTCursor(); + + ASTCursor(const ASTCursor&) = default; + ASTCursor& operator=(const ASTCursor&) = default; + + ASTCursor(ASTCursor&&) = default; + ASTCursor& operator=(ASTCursor&&) = default; + + ASTCursor(AST* root, std::string_view name); + + explicit operator bool() const { return !empty(); } + + [[nodiscard]] auto empty() const -> bool { return stack_.empty(); } + + [[nodiscard]] auto operator*() const -> const Node& { return stack_.back(); } + + void step(); + + auto operator++() -> ASTCursor& { + step(); + return *this; + } + + private: + std::deque stack_; +}; + +} // namespace cxx \ No newline at end of file diff --git a/src/parser/cxx/parser.cc b/src/parser/cxx/parser.cc index a669fad6..07b4ff56 100644 --- a/src/parser/cxx/parser.cc +++ b/src/parser/cxx/parser.cc @@ -14,9 +14,9 @@ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FRnewOM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. #include @@ -6726,6 +6726,7 @@ auto Parser::parse_init_declarator(InitDeclaratorAST*& yyast, auto Parser::parse_init_declarator(InitDeclaratorAST*& yyast, DeclaratorAST* declarator, Decl& decl) -> bool { + Symbol* declaredSynbol = nullptr; if (auto declId = decl.declaratorId; declId) { auto symbolType = GetDeclaratorType{this}(declarator, decl.specs.getType()); const auto name = convertName(declId->unqualifiedId); @@ -6735,6 +6736,7 @@ auto Parser::parse_init_declarator(InitDeclaratorAST*& yyast, symbol->setName(name); symbol->setType(symbolType); std::invoke(DeclareSymbol{this, scope_}, symbol); + declaredSynbol = symbol; } else if (getFunctionPrototype(declarator)) { auto functionSymbol = control_->newFunctionSymbol(scope_, decl.location()); @@ -6742,15 +6744,18 @@ auto Parser::parse_init_declarator(InitDeclaratorAST*& yyast, functionSymbol->setName(name); functionSymbol->setType(symbolType); std::invoke(DeclareSymbol{this, scope_}, functionSymbol); + declaredSynbol = functionSymbol; } else { auto symbol = control_->newVariableSymbol(scope_, decl.location()); applySpecifiers(symbol, decl.specs); symbol->setName(name); symbol->setType(symbolType); std::invoke(DeclareSymbol{this, scope_}, symbol); + declaredSynbol = symbol; } } } + RequiresClauseAST* requiresClause = nullptr; ExpressionAST* initializer = nullptr; @@ -6765,6 +6770,7 @@ auto Parser::parse_init_declarator(InitDeclaratorAST*& yyast, ast->declarator = declarator; ast->requiresClause = requiresClause; ast->initializer = initializer; + ast->symbol = declaredSynbol; return true; } diff --git a/src/parser/cxx/preprocessor.cc b/src/parser/cxx/preprocessor.cc index 03875861..ba94e700 100644 --- a/src/parser/cxx/preprocessor.cc +++ b/src/parser/cxx/preprocessor.cc @@ -2796,6 +2796,17 @@ void Preprocessor::setOnWillIncludeHeader( void Preprocessor::squeeze() { d->pool_.reset(); } +auto Preprocessor::sourceFileName(uint32_t sourceFileId) const + -> const std::string & { + assert(sourceFileId > 0); + return d->sourceFiles_[sourceFileId - 1]->fileName; +} + +auto Preprocessor::source(uint32_t sourceFileId) const -> const std::string & { + assert(sourceFileId > 0); + return d->sourceFiles_[sourceFileId - 1]->source; +} + void Preprocessor::preprocess(std::string source, std::string fileName, std::vector &tokens) { struct { diff --git a/src/parser/cxx/preprocessor.h b/src/parser/cxx/preprocessor.h index 5fb5cf1f..a0533e0b 100644 --- a/src/parser/cxx/preprocessor.h +++ b/src/parser/cxx/preprocessor.h @@ -119,6 +119,11 @@ class Preprocessor { [[nodiscard]] auto continuePreprocessing(std::vector &outputTokens) -> Status; + [[nodiscard]] auto sourceFileName(uint32_t sourceFileId) const + -> const std::string &; + + [[nodiscard]] auto source(uint32_t sourceFileId) const -> const std::string &; + void preprocess(std::string source, std::string fileName, std::vector &tokens); diff --git a/tests/unit_tests/lsp/document_symbols.yml b/tests/unit_tests/lsp/document_symbols.yml new file mode 100644 index 00000000..46b3c7c3 --- /dev/null +++ b/tests/unit_tests/lsp/document_symbols.yml @@ -0,0 +1,21 @@ +# RUN: %cxx -lsp-test < %s | %filecheck %s + +{ "method": "initialize", "id": 0 } + +# CHECK: "id": 0 + +{ "method": "textDocument/didOpen", "id": 1, "params": { "textDocument": { "uri": "test:///source.cc", "version": 0, "text": "#include \nauto main() -> int;" } } } + +{ "method": "$/setTrace", "id": 2, "params": { "value": "verbose" } } + +{ "method": "textDocument/documentSymbol", "id": 3, "params": { "textDocument": { "uri": "test:///source.cc" } } } + +# CHECK: "message": "Did receive DocumentSymbolRequest" +# CHECK: "id": 3 + +{ "method": "shutdown", "id": 4 } + +{ "method": "exit" } + + +