/* * Copyright (c) 2021, Itamar S. * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace LanguageServers::Cpp { using namespace ::Cpp; class CppComprehensionEngine : public CodeComprehensionEngine { public: CppComprehensionEngine(const FileDB& filedb); virtual Vector get_suggestions(const String& file, const GUI::TextPosition& autocomplete_position) override; virtual void on_edit(const String& file) override; virtual void file_opened([[maybe_unused]] const String& file) override; virtual Optional find_declaration_of(const String& filename, const GUI::TextPosition& identifier_position) override; virtual Optional get_function_params_hint(const String&, const GUI::TextPosition&) override; virtual Vector get_tokens_info(const String& filename) override; private: struct SymbolName { StringView name; Vector scope; static SymbolName create(StringView, Vector&&); static SymbolName create(StringView); String scope_as_string() const; String to_string() const; bool operator==(const SymbolName&) const = default; }; struct Symbol { SymbolName name; NonnullRefPtr declaration; // Local symbols are symbols that should not appear in a global symbol search. // For example, a variable that is declared inside a function will have is_local = true. bool is_local { false }; enum class IsLocal { No, Yes }; static Symbol create(StringView name, const Vector& scope, NonnullRefPtr, IsLocal is_local); }; friend Traits; struct DocumentData { const String& filename() const { return m_filename; } const String& text() const { return m_text; } const Preprocessor& preprocessor() const { VERIFY(m_preprocessor); return *m_preprocessor; } Preprocessor& preprocessor() { VERIFY(m_preprocessor); return *m_preprocessor; } const Parser& parser() const { VERIFY(m_parser); return *m_parser; } Parser& parser() { VERIFY(m_parser); return *m_parser; } String m_filename; String m_text; OwnPtr m_preprocessor; OwnPtr m_parser; HashMap m_symbols; HashTable m_available_headers; }; Vector autocomplete_property(const DocumentData&, const MemberExpression&, const String partial_text) const; Vector autocomplete_name(const DocumentData&, const ASTNode&, const String& partial_text) const; String type_of(const DocumentData&, const Expression&) const; String type_of_property(const DocumentData&, const Identifier&) const; String type_of_variable(const Identifier&) const; bool is_property(const ASTNode&) const; RefPtr find_declaration_of(const DocumentData&, const ASTNode&) const; RefPtr find_declaration_of(const DocumentData&, const SymbolName&) const; RefPtr find_declaration_of(const DocumentData&, const GUI::TextPosition& identifier_position); enum class RecurseIntoScopes { No, Yes }; Vector properties_of_type(const DocumentData& document, const String& type) const; Vector get_child_symbols(const ASTNode&) const; Vector get_child_symbols(const ASTNode&, const Vector& scope, Symbol::IsLocal) const; const DocumentData* get_document_data(const String& file) const; const DocumentData* get_or_create_document_data(const String& file); void set_document_data(const String& file, OwnPtr&& data); OwnPtr create_document_data_for(const String& file); String document_path_from_include_path(StringView include_path) const; void update_declared_symbols(DocumentData&); void update_todo_entries(DocumentData&); GUI::AutocompleteProvider::DeclarationType type_of_declaration(const Declaration&); Vector scope_of_node(const ASTNode&) const; Vector scope_of_reference_to_symbol(const ASTNode&) const; Optional find_preprocessor_definition(const DocumentData&, const GUI::TextPosition&); OwnPtr create_document_data(String&& text, const String& filename); Optional> try_autocomplete_property(const DocumentData&, const ASTNode&, Optional containing_token) const; Optional> try_autocomplete_name(const DocumentData&, const ASTNode&, Optional containing_token) const; Optional> try_autocomplete_include(const DocumentData&, Token include_path_token, Cpp::Position const& cursor_position) const; static bool is_symbol_available(const Symbol&, const Vector& current_scope, const Vector& reference_scope); Optional get_function_params_hint(DocumentData const&, FunctionCall&, size_t argument_index); template void for_each_available_symbol(const DocumentData&, Func) const; template void for_each_included_document_recursive(const DocumentData&, Func) const; GUI::AutocompleteProvider::TokenInfo::SemanticType get_token_semantic_type(DocumentData const&, Token const&); GUI::AutocompleteProvider::TokenInfo::SemanticType get_semantic_type_for_identifier(DocumentData const&, Position); HashMap> m_documents; // A document's path will be in this set if we're currently processing it. // A document is added to this set when we start processing it (e.g because it was #included) and removed when we're done. // We use this to prevent circular #includes from looping indefinitely. HashTable m_unfinished_documents; }; template void CppComprehensionEngine::for_each_available_symbol(const DocumentData& document, Func func) const { for (auto& item : document.m_symbols) { auto decision = func(item.value); if (decision == IterationDecision::Break) return; } for_each_included_document_recursive(document, [&](const DocumentData& document) { for (auto& item : document.m_symbols) { auto decision = func(item.value); if (decision == IterationDecision::Break) return IterationDecision::Break; } return IterationDecision::Continue; }); } template void CppComprehensionEngine::for_each_included_document_recursive(const DocumentData& document, Func func) const { for (auto& included_path : document.m_available_headers) { auto* included_document = get_document_data(included_path); if (!included_document) continue; auto decision = func(*included_document); if (decision == IterationDecision::Break) continue; } } } namespace AK { template<> struct Traits : public GenericTraits { static unsigned hash(const LanguageServers::Cpp::CppComprehensionEngine::SymbolName& key) { unsigned hash = 0; hash = pair_int_hash(hash, string_hash(key.name.characters_without_null_termination(), key.name.length())); for (auto& scope_part : key.scope) { hash = pair_int_hash(hash, string_hash(scope_part.characters_without_null_termination(), scope_part.length())); } return hash; } }; }